Index: lib/fletch/fletch.dart |
diff --git a/lib/fletch/fletch.dart b/lib/fletch/fletch.dart |
deleted file mode 100644 |
index f1ddafb223d44308083f31b6e056cb0bddbbd6a6..0000000000000000000000000000000000000000 |
--- a/lib/fletch/fletch.dart |
+++ /dev/null |
@@ -1,559 +0,0 @@ |
-// Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE.md file. |
- |
-library dart.fletch; |
- |
-import 'dart:fletch._system' as fletch; |
- |
-/// Fibers are lightweight co-operative multitask units of execution. They |
-/// are scheduled on top of OS-level threads, but they are cheap to create |
-/// and block. |
-class Fiber { |
- |
- // We keep track of the top of the coroutine stack and |
- // the list of other fibers that are waiting for this |
- // fiber to exit. |
- Coroutine _coroutine; |
- List<Fiber> _joiners; |
- |
- // When a fiber exits, we keep the result of running |
- // its code around so we can return to other fibers |
- // that join it. |
- bool _isDone = false; |
- var _result; |
- |
- // The ready fibers are linked together. |
- Fiber _previous; |
- Fiber _next; |
- |
- // We do not use an initializer for [_current] to ensure we can treeshake |
- // the [Fiber] class. |
- static Fiber _current; |
- static Fiber _idleFibers; |
- |
- Fiber._initial() { |
- _previous = this; |
- _next = this; |
- } |
- |
- Fiber._forked(entry) { |
- current; // Force initialization of fiber sub-system. |
- _coroutine = new Coroutine((ignore) { |
- fletch.runToEnd(entry); |
- }); |
- } |
- |
- static Fiber get current { |
- var current = _current; |
- if (current != null) return current; |
- return _current = new Fiber._initial(); |
- } |
- |
- static Fiber fork(entry) { |
- Fiber fiber = new Fiber._forked(entry); |
- _markReady(fiber); |
- return fiber; |
- } |
- |
- static void yield() { |
- // Handle messages so that fibers that are blocked on receiving |
- // messages can wake up. |
- Process._handleMessages(); |
- _current._coroutine = Coroutine._coroutineCurrent(); |
- _schedule(_current._next); |
- } |
- |
- static void exit([value]) { |
- // If we never created a fiber, we can just go ahead and halt now. |
- if (_current == null) fletch.halt(); |
- |
- _current._exit(value); |
- } |
- |
- join() { |
- // If the fiber is already done, we just return the result. |
- if (_isDone) return _result; |
- |
- // Add the current fiber to the list of fibers waiting |
- // to join [this] fiber. |
- Fiber fiber = _current; |
- if (_joiners == null) { |
- _joiners = [ fiber ]; |
- } else { |
- _joiners.add(fiber); |
- } |
- |
- // Suspend the current fiber and change to the scheduler. |
- // When we get back, the [this] fiber has exited and we |
- // can go ahead and return the result. |
- Fiber next = _suspendFiber(fiber, false); |
- fiber._coroutine = Coroutine._coroutineCurrent(); |
- _schedule(next); |
- return _result; |
- } |
- |
- void _exit(value) { |
- Fiber fiber = this; |
- List<Fiber> joiners = fiber._joiners; |
- if (joiners != null) { |
- for (Fiber joiner in joiners) _resumeFiber(joiner); |
- joiners.clear(); |
- } |
- |
- fiber._isDone = true; |
- fiber._result = value; |
- |
- // Suspend the current fiber. It will never wake up again. |
- Fiber next = _suspendFiber(fiber, true); |
- fiber._coroutine = null; |
- _schedule(next); |
- } |
- |
- static void _resumeFiber(Fiber fiber) { |
- _idleFibers = _unlink(fiber); |
- _markReady(fiber); |
- } |
- |
- static void _markReady(Fiber fiber) { |
- _current = _link(fiber, _current); |
- } |
- |
- static Fiber _link(Fiber fiber, Fiber list) { |
- if (list == null) { |
- fiber._next = fiber; |
- fiber._previous = fiber; |
- return fiber; |
- } |
- |
- Fiber next = list._next; |
- list._next = fiber; |
- next._previous = fiber; |
- fiber._previous = list; |
- fiber._next = next; |
- return list; |
- } |
- |
- static Fiber _unlink(Fiber fiber) { |
- Fiber next = fiber._next; |
- if (identical(fiber, next)) { |
- return null; |
- } |
- |
- Fiber previous = fiber._previous; |
- previous._next = next; |
- next._previous = previous; |
- return next; |
- } |
- |
- static Fiber _suspendFiber(Fiber fiber, bool exiting) { |
- Fiber current = _current = _unlink(fiber); |
- |
- if (exiting) { |
- fiber._next = null; |
- fiber._previous = null; |
- } else { |
- _idleFibers = _link(fiber, _idleFibers); |
- } |
- |
- if (current != null) return current; |
- |
- // If we don't have any idle fibers, halt. |
- if (exiting && _idleFibers == null) fletch.halt(); |
- |
- while (true) { |
- Process._handleMessages(); |
- // A call to _handleMessages can handle more messages than signaled, so |
- // we can get a following false-positive wakeup. If no new _current is |
- // set, simply yield again. |
- current = _current; |
- if (current != null) return current; |
- fletch.yield(fletch.InterruptKind.yield.index); |
- } |
- } |
- |
- static void _yieldTo(Fiber from, Fiber to) { |
- from._coroutine = Coroutine._coroutineCurrent(); |
- _schedule(to); |
- } |
- |
- // TODO(kasperl): This is temporary debugging support. We |
- // should probably replace this support for passing in an |
- // id of some sort when forking a fiber. |
- static int _count = 0; |
- int _index = _count++; |
- toString() => "fiber:$_index"; |
- |
- static void _schedule(Fiber to) { |
- _current = to; |
- fletch.coroutineChange(to._coroutine, null); |
- } |
-} |
- |
-class Coroutine { |
- |
- // TODO(kasperl): The VM has assumptions about the layout |
- // of coroutine fields. We should validate that the code |
- // agrees with those assumptions. |
- var _stack; |
- var _caller; |
- |
- Coroutine(entry) { |
- _stack = _coroutineNewStack(this, entry); |
- } |
- |
- bool get isSuspended => identical(_caller, null); |
- bool get isRunning => !isSuspended && !isDone; |
- bool get isDone => identical(_caller, this); |
- |
- call(argument) { |
- if (!isSuspended) throw "Cannot call non-suspended coroutine"; |
- _caller = _coroutineCurrent(); |
- var result = fletch.coroutineChange(this, argument); |
- |
- // If the called coroutine is done now, we clear the |
- // stack reference in it so the memory can be reclaimed. |
- if (isDone) { |
- _stack = null; |
- } else { |
- _caller = null; |
- } |
- return result; |
- } |
- |
- static yield(value) { |
- Coroutine caller = _coroutineCurrent()._caller; |
- if (caller == null) throw "Cannot yield outside coroutine"; |
- return fletch.coroutineChange(caller, value); |
- } |
- |
- _coroutineStart(entry) { |
- // The first call to changeStack is actually skipped but we need |
- // it to make sure the newly started coroutine is suspended in |
- // exactly the same way as we do when yielding. |
- var argument = fletch.coroutineChange(0, 0); |
- var result = entry(argument); |
- |
- // Mark this coroutine as done and change back to the caller. |
- Coroutine caller = _caller; |
- _caller = this; |
- fletch.coroutineChange(caller, result); |
- } |
- |
- @fletch.native external static _coroutineCurrent(); |
- @fletch.native external static _coroutineNewStack(coroutine, entry); |
-} |
- |
-class ProcessDeath { |
- final Process process; |
- final int _reason; |
- |
- ProcessDeath._(this.process, this._reason); |
- |
- DeathReason get reason => DeathReason.values[_reason]; |
-} |
- |
-// TODO: Keep these in sync with src/vm/process.h:Signal::Kind |
-enum DeathReason { |
- CompileTimeError, |
- Terminated, |
- UncaughtException, |
- UnhandledSignal, |
- Killed, |
-} |
- |
-class Process { |
- // This is the address of the native process/4 so that it fits in a Smi. |
- final int _nativeProcessHandle; |
- |
- bool operator==(other) { |
- return |
- other is Process && |
- other._nativeProcessHandle == _nativeProcessHandle; |
- } |
- |
- int get hashCode => _nativeProcessHandle.hashCode; |
- |
- @fletch.native bool link() { |
- throw fletch.nativeError; |
- } |
- |
- @fletch.native void unlink() { |
- switch (fletch.nativeError) { |
- case fletch.wrongArgumentType: |
- throw new StateError("Cannot unlink from parent process."); |
- default: |
- throw fletch.nativeError; |
- } |
- } |
- |
- @fletch.native bool monitor(Port port) { |
- switch (fletch.nativeError) { |
- case fletch.wrongArgumentType: |
- throw new StateError("The argument to monitor must be a Port object."); |
- default: |
- throw fletch.nativeError; |
- } |
- } |
- |
- @fletch.native void unmonitor(Port port) { |
- switch (fletch.nativeError) { |
- case fletch.wrongArgumentType: |
- throw new StateError( |
- "The argument to unmonitor must be a Port object."); |
- default: |
- throw fletch.nativeError; |
- } |
- } |
- |
- @fletch.native void kill() { |
- throw fletch.nativeError; |
- } |
- |
- static Process spawn(Function fn, [argument]) { |
- if (!isImmutable(fn)) { |
- throw new ArgumentError( |
- 'The closure passed to Process.spawn() must be immutable.'); |
- } |
- |
- if (!isImmutable(argument)) { |
- throw new ArgumentError( |
- 'The optional argument passed to Process.spawn() must be immutable.'); |
- } |
- |
- return _spawn(_entry, fn, argument, true, true, null); |
- } |
- |
- static Process spawnDetached(Function fn, {Port monitor}) { |
- if (!isImmutable(fn)) { |
- throw new ArgumentError( |
- 'The closure passed to Process.spawnDetached() must be immutable.'); |
- } |
- |
- return _spawn(_entry, fn, null, true, false, monitor); |
- } |
- |
- /** |
- * Divide the elements in [arguments] into a matching number of processes |
- * with [fn] as entry. The current process blocks until all processes have |
- * terminated. |
- * |
- * The elements in [arguments] can be any immutable (see [isImmutable]) |
- * object. |
- * |
- * The function [fn] must be a top-level or static function. |
- */ |
- static List divide(fn(argument), List arguments) { |
- if (fn == null) { |
- throw new ArgumentError.notNull("fn"); |
- } |
- if (!isImmutable(fn)) { |
- throw new ArgumentError.value( |
- fn, "fn", "Closure passed to Process.divide must be immutable."); |
- } |
- if (arguments == null) { |
- throw new ArgumentError.notNull("arguments"); |
- } |
- |
- int length = arguments.length; |
- for (int i = 0; i < length; i++) { |
- if (!isImmutable(arguments[i])) { |
- throw new ArgumentError.value( |
- arguments[i], "@$i", |
- "Cannot pass mutable arguments to subprocess via Process.divide."); |
- } |
- } |
- |
- List channels = new List(length); |
- for (int i = 0; i < length; i++) { |
- channels[i] = new Channel(); |
- |
- final argument = arguments[i]; |
- final port = new Port(channels[i]); |
- Process.spawnDetached(() { |
- try { |
- Process.exit(value: fn(argument), to: port); |
- } finally { |
- // TODO(kustermann): Handle error properly. Once we do this, we can |
- // remove the 'fn == null' check above. |
- Process.exit(to: port); |
- } |
- }); |
- } |
- for (int i = 0; i < length; i++) { |
- channels[i] = channels[i].receive(); |
- } |
- return channels; |
- } |
- |
- /** |
- * Exit the current process. If a non-null [to] port is provided, |
- * the process will send the provided [value] to the [to] port as |
- * its final action. |
- */ |
- static void exit({value, Port to}) { |
- try { |
- if (to != null) to._sendExit(value); |
- } finally { |
- fletch.yield(fletch.InterruptKind.terminate.index); |
- } |
- } |
- |
- // Low-level entry for spawned processes. |
- static void _entry(fn, argument) { |
- if (argument == null) { |
- fletch.runToEnd(fn); |
- } else { |
- fletch.runToEnd(() => fn(argument)); |
- } |
- } |
- |
- // Low-level helper function for spawning. |
- @fletch.native static Process _spawn(Function entry, |
- Function fn, |
- argument, |
- bool linkToChild, |
- bool linkFromChild, |
- Port monitor) { |
- throw new ArgumentError(); |
- } |
- |
- static void _handleMessages() { |
- Channel channel; |
- while ((channel = _queueGetChannel()) != null) { |
- var message = _queueGetMessage(); |
- if (message is ProcessDeath) { |
- message = _queueSetupProcessDeath(message); |
- } |
- channel.send(message); |
- } |
- } |
- |
- @fletch.native external static Process get current; |
- @fletch.native external static _queueGetMessage(); |
- @fletch.native external static _queueSetupProcessDeath(ProcessDeath message); |
- @fletch.native external static Channel _queueGetChannel(); |
-} |
- |
-// Ports allow you to send messages to a channel. Ports are |
-// are transferable and can be sent between processes. |
-class Port { |
- // A Smi stores the aligned pointer to the C++ port object. |
- final int _port; |
- |
- factory Port(Channel channel) { |
- return Port._create(channel); |
- } |
- |
- // TODO(kasperl): Temporary debugging aid. |
- int get id => _port; |
- |
- // Send a message to the channel. Not blocking. |
- @fletch.native void send(message) { |
- switch (fletch.nativeError) { |
- case fletch.wrongArgumentType: |
- throw new ArgumentError(); |
- case fletch.illegalState: |
- throw new StateError("Port is closed."); |
- default: |
- throw fletch.nativeError; |
- } |
- } |
- |
- @fletch.native void _sendExit(value) { |
- throw new StateError("Port is closed."); |
- } |
- |
- @fletch.native external static Port _create(Channel channel); |
-} |
- |
-class Channel { |
- Fiber _receiver; // TODO(kasperl): Should this be a queue too? |
- |
- // TODO(kasperl): Maybe make this a bit smarter and store |
- // the elements in a growable list? Consider allowing bounds |
- // on the queue size. |
- _ChannelEntry _head; |
- _ChannelEntry _tail; |
- |
- // Deliver the message synchronously. If the receiver |
- // isn't ready to receive yet, the sender blocks. |
- void deliver(message) { |
- Fiber sender = Fiber.current; |
- _enqueue(new _ChannelEntry(message, sender)); |
- Fiber next = Fiber._suspendFiber(sender, false); |
- // TODO(kasperl): Should we yield to receiver if possible? |
- Fiber._yieldTo(sender, next); |
- } |
- |
- // Send a message to the channel. Not blocking. |
- void send(message) { |
- _enqueue(new _ChannelEntry(message, null)); |
- } |
- |
- // Receive a message. If no messages are available |
- // the receiver blocks. |
- receive() { |
- if (_receiver != null) { |
- throw new StateError("Channel cannot have multiple receivers (yet)."); |
- } |
- |
- if (_head == null) { |
- Fiber receiver = Fiber.current; |
- _receiver = receiver; |
- Fiber next = Fiber._suspendFiber(receiver, false); |
- Fiber._yieldTo(receiver, next); |
- } |
- |
- return _dequeue(); |
- } |
- |
- _enqueue(_ChannelEntry entry) { |
- if (_tail == null) { |
- _head = _tail = entry; |
- } else { |
- _tail = _tail.next = entry; |
- } |
- |
- // Signal the receiver (if any). |
- Fiber receiver = _receiver; |
- if (receiver != null) { |
- _receiver = null; |
- Fiber._resumeFiber(receiver); |
- } |
- } |
- |
- _dequeue() { |
- _ChannelEntry entry = _head; |
- _ChannelEntry next = entry.next; |
- _head = next; |
- if (next == null) _tail = next; |
- Fiber sender = entry.sender; |
- if (sender != null) Fiber._resumeFiber(sender); |
- return entry.message; |
- } |
-} |
- |
-class _ChannelEntry { |
- final message; |
- final Fiber sender; |
- _ChannelEntry next; |
- _ChannelEntry(this.message, this.sender); |
-} |
- |
-bool isImmutable(Object object) => _isImmutable(object); |
- |
-@fletch.native external bool _isImmutable(String string); |
- |
-/// Returns a channel that will receive a message in [milliseconds] |
-/// milliseconds. |
-// TODO(sigurdm): Move this function? |
-Channel sleep(int milliseconds) { |
- if (milliseconds is! int) throw new ArgumentError(milliseconds); |
- Channel channel = new Channel(); |
- Port port = new Port(channel); |
- _sleep(milliseconds, port); |
- return channel; |
-} |
- |
-@fletch.native external void _sleep(int milliseconds, Port port); |