| OLD | NEW |
| 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dartino project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE.md file. | 3 // BSD-style license that can be found in the LICENSE.md file. |
| 4 | 4 |
| 5 library dart.fletch; | 5 library dart.dartino; |
| 6 | 6 |
| 7 import 'dart:fletch._system' as fletch; | 7 import 'dart:dartino._system' as dartino; |
| 8 | 8 |
| 9 /// Fibers are lightweight co-operative multitask units of execution. They | 9 /// Fibers are lightweight co-operative multitask units of execution. They |
| 10 /// are scheduled on top of OS-level threads, but they are cheap to create | 10 /// are scheduled on top of OS-level threads, but they are cheap to create |
| 11 /// and block. | 11 /// and block. |
| 12 class Fiber { | 12 class Fiber { |
| 13 | 13 |
| 14 // We keep track of the top of the coroutine stack and | 14 // We keep track of the top of the coroutine stack and |
| 15 // the list of other fibers that are waiting for this | 15 // the list of other fibers that are waiting for this |
| 16 // fiber to exit. | 16 // fiber to exit. |
| 17 Coroutine _coroutine; | 17 Coroutine _coroutine; |
| (...skipping 15 matching lines...) Expand all Loading... |
| 33 static Fiber _idleFibers; | 33 static Fiber _idleFibers; |
| 34 | 34 |
| 35 Fiber._initial() { | 35 Fiber._initial() { |
| 36 _previous = this; | 36 _previous = this; |
| 37 _next = this; | 37 _next = this; |
| 38 } | 38 } |
| 39 | 39 |
| 40 Fiber._forked(entry) { | 40 Fiber._forked(entry) { |
| 41 current; // Force initialization of fiber sub-system. | 41 current; // Force initialization of fiber sub-system. |
| 42 _coroutine = new Coroutine((ignore) { | 42 _coroutine = new Coroutine((ignore) { |
| 43 fletch.runToEnd(entry); | 43 dartino.runToEnd(entry); |
| 44 }); | 44 }); |
| 45 } | 45 } |
| 46 | 46 |
| 47 static Fiber get current { | 47 static Fiber get current { |
| 48 var current = _current; | 48 var current = _current; |
| 49 if (current != null) return current; | 49 if (current != null) return current; |
| 50 return _current = new Fiber._initial(); | 50 return _current = new Fiber._initial(); |
| 51 } | 51 } |
| 52 | 52 |
| 53 static Fiber fork(entry) { | 53 static Fiber fork(entry) { |
| 54 Fiber fiber = new Fiber._forked(entry); | 54 Fiber fiber = new Fiber._forked(entry); |
| 55 _markReady(fiber); | 55 _markReady(fiber); |
| 56 return fiber; | 56 return fiber; |
| 57 } | 57 } |
| 58 | 58 |
| 59 static void yield() { | 59 static void yield() { |
| 60 // Handle messages so that fibers that are blocked on receiving | 60 // Handle messages so that fibers that are blocked on receiving |
| 61 // messages can wake up. | 61 // messages can wake up. |
| 62 Process._handleMessages(); | 62 Process._handleMessages(); |
| 63 _current._coroutine = Coroutine._coroutineCurrent(); | 63 _current._coroutine = Coroutine._coroutineCurrent(); |
| 64 _schedule(_current._next); | 64 _schedule(_current._next); |
| 65 } | 65 } |
| 66 | 66 |
| 67 static void exit([value]) { | 67 static void exit([value]) { |
| 68 // If we never created a fiber, we can just go ahead and halt now. | 68 // If we never created a fiber, we can just go ahead and halt now. |
| 69 if (_current == null) fletch.halt(); | 69 if (_current == null) dartino.halt(); |
| 70 | 70 |
| 71 _current._exit(value); | 71 _current._exit(value); |
| 72 } | 72 } |
| 73 | 73 |
| 74 join() { | 74 join() { |
| 75 // If the fiber is already done, we just return the result. | 75 // If the fiber is already done, we just return the result. |
| 76 if (_isDone) return _result; | 76 if (_isDone) return _result; |
| 77 | 77 |
| 78 // Add the current fiber to the list of fibers waiting | 78 // Add the current fiber to the list of fibers waiting |
| 79 // to join [this] fiber. | 79 // to join [this] fiber. |
| (...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 152 if (exiting) { | 152 if (exiting) { |
| 153 fiber._next = null; | 153 fiber._next = null; |
| 154 fiber._previous = null; | 154 fiber._previous = null; |
| 155 } else { | 155 } else { |
| 156 _idleFibers = _link(fiber, _idleFibers); | 156 _idleFibers = _link(fiber, _idleFibers); |
| 157 } | 157 } |
| 158 | 158 |
| 159 if (current != null) return current; | 159 if (current != null) return current; |
| 160 | 160 |
| 161 // If we don't have any idle fibers, halt. | 161 // If we don't have any idle fibers, halt. |
| 162 if (exiting && _idleFibers == null) fletch.halt(); | 162 if (exiting && _idleFibers == null) dartino.halt(); |
| 163 | 163 |
| 164 while (true) { | 164 while (true) { |
| 165 Process._handleMessages(); | 165 Process._handleMessages(); |
| 166 // A call to _handleMessages can handle more messages than signaled, so | 166 // A call to _handleMessages can handle more messages than signaled, so |
| 167 // we can get a following false-positive wakeup. If no new _current is | 167 // we can get a following false-positive wakeup. If no new _current is |
| 168 // set, simply yield again. | 168 // set, simply yield again. |
| 169 current = _current; | 169 current = _current; |
| 170 if (current != null) return current; | 170 if (current != null) return current; |
| 171 fletch.yield(fletch.InterruptKind.yield.index); | 171 dartino.yield(dartino.InterruptKind.yield.index); |
| 172 } | 172 } |
| 173 } | 173 } |
| 174 | 174 |
| 175 static void _yieldTo(Fiber from, Fiber to) { | 175 static void _yieldTo(Fiber from, Fiber to) { |
| 176 from._coroutine = Coroutine._coroutineCurrent(); | 176 from._coroutine = Coroutine._coroutineCurrent(); |
| 177 _schedule(to); | 177 _schedule(to); |
| 178 } | 178 } |
| 179 | 179 |
| 180 // TODO(kasperl): This is temporary debugging support. We | 180 // TODO(kasperl): This is temporary debugging support. We |
| 181 // should probably replace this support for passing in an | 181 // should probably replace this support for passing in an |
| 182 // id of some sort when forking a fiber. | 182 // id of some sort when forking a fiber. |
| 183 static int _count = 0; | 183 static int _count = 0; |
| 184 int _index = _count++; | 184 int _index = _count++; |
| 185 toString() => "fiber:$_index"; | 185 toString() => "fiber:$_index"; |
| 186 | 186 |
| 187 static void _schedule(Fiber to) { | 187 static void _schedule(Fiber to) { |
| 188 _current = to; | 188 _current = to; |
| 189 fletch.coroutineChange(to._coroutine, null); | 189 dartino.coroutineChange(to._coroutine, null); |
| 190 } | 190 } |
| 191 } | 191 } |
| 192 | 192 |
| 193 class Coroutine { | 193 class Coroutine { |
| 194 | 194 |
| 195 // TODO(kasperl): The VM has assumptions about the layout | 195 // TODO(kasperl): The VM has assumptions about the layout |
| 196 // of coroutine fields. We should validate that the code | 196 // of coroutine fields. We should validate that the code |
| 197 // agrees with those assumptions. | 197 // agrees with those assumptions. |
| 198 var _stack; | 198 var _stack; |
| 199 var _caller; | 199 var _caller; |
| 200 | 200 |
| 201 Coroutine(entry) { | 201 Coroutine(entry) { |
| 202 _stack = _coroutineNewStack(this, entry); | 202 _stack = _coroutineNewStack(this, entry); |
| 203 } | 203 } |
| 204 | 204 |
| 205 bool get isSuspended => identical(_caller, null); | 205 bool get isSuspended => identical(_caller, null); |
| 206 bool get isRunning => !isSuspended && !isDone; | 206 bool get isRunning => !isSuspended && !isDone; |
| 207 bool get isDone => identical(_caller, this); | 207 bool get isDone => identical(_caller, this); |
| 208 | 208 |
| 209 call(argument) { | 209 call(argument) { |
| 210 if (!isSuspended) throw "Cannot call non-suspended coroutine"; | 210 if (!isSuspended) throw "Cannot call non-suspended coroutine"; |
| 211 _caller = _coroutineCurrent(); | 211 _caller = _coroutineCurrent(); |
| 212 var result = fletch.coroutineChange(this, argument); | 212 var result = dartino.coroutineChange(this, argument); |
| 213 | 213 |
| 214 // If the called coroutine is done now, we clear the | 214 // If the called coroutine is done now, we clear the |
| 215 // stack reference in it so the memory can be reclaimed. | 215 // stack reference in it so the memory can be reclaimed. |
| 216 if (isDone) { | 216 if (isDone) { |
| 217 _stack = null; | 217 _stack = null; |
| 218 } else { | 218 } else { |
| 219 _caller = null; | 219 _caller = null; |
| 220 } | 220 } |
| 221 return result; | 221 return result; |
| 222 } | 222 } |
| 223 | 223 |
| 224 static yield(value) { | 224 static yield(value) { |
| 225 Coroutine caller = _coroutineCurrent()._caller; | 225 Coroutine caller = _coroutineCurrent()._caller; |
| 226 if (caller == null) throw "Cannot yield outside coroutine"; | 226 if (caller == null) throw "Cannot yield outside coroutine"; |
| 227 return fletch.coroutineChange(caller, value); | 227 return dartino.coroutineChange(caller, value); |
| 228 } | 228 } |
| 229 | 229 |
| 230 _coroutineStart(entry) { | 230 _coroutineStart(entry) { |
| 231 // The first call to changeStack is actually skipped but we need | 231 // The first call to changeStack is actually skipped but we need |
| 232 // it to make sure the newly started coroutine is suspended in | 232 // it to make sure the newly started coroutine is suspended in |
| 233 // exactly the same way as we do when yielding. | 233 // exactly the same way as we do when yielding. |
| 234 var argument = fletch.coroutineChange(0, 0); | 234 var argument = dartino.coroutineChange(0, 0); |
| 235 var result = entry(argument); | 235 var result = entry(argument); |
| 236 | 236 |
| 237 // Mark this coroutine as done and change back to the caller. | 237 // Mark this coroutine as done and change back to the caller. |
| 238 Coroutine caller = _caller; | 238 Coroutine caller = _caller; |
| 239 _caller = this; | 239 _caller = this; |
| 240 fletch.coroutineChange(caller, result); | 240 dartino.coroutineChange(caller, result); |
| 241 } | 241 } |
| 242 | 242 |
| 243 @fletch.native external static _coroutineCurrent(); | 243 @dartino.native external static _coroutineCurrent(); |
| 244 @fletch.native external static _coroutineNewStack(coroutine, entry); | 244 @dartino.native external static _coroutineNewStack(coroutine, entry); |
| 245 } | 245 } |
| 246 | 246 |
| 247 class ProcessDeath { | 247 class ProcessDeath { |
| 248 final Process process; | 248 final Process process; |
| 249 final int _reason; | 249 final int _reason; |
| 250 | 250 |
| 251 ProcessDeath._(this.process, this._reason); | 251 ProcessDeath._(this.process, this._reason); |
| 252 | 252 |
| 253 DeathReason get reason => DeathReason.values[_reason]; | 253 DeathReason get reason => DeathReason.values[_reason]; |
| 254 } | 254 } |
| (...skipping 12 matching lines...) Expand all Loading... |
| 267 final int _nativeProcessHandle; | 267 final int _nativeProcessHandle; |
| 268 | 268 |
| 269 bool operator==(other) { | 269 bool operator==(other) { |
| 270 return | 270 return |
| 271 other is Process && | 271 other is Process && |
| 272 other._nativeProcessHandle == _nativeProcessHandle; | 272 other._nativeProcessHandle == _nativeProcessHandle; |
| 273 } | 273 } |
| 274 | 274 |
| 275 int get hashCode => _nativeProcessHandle.hashCode; | 275 int get hashCode => _nativeProcessHandle.hashCode; |
| 276 | 276 |
| 277 @fletch.native bool link() { | 277 @dartino.native bool link() { |
| 278 throw fletch.nativeError; | 278 throw dartino.nativeError; |
| 279 } | 279 } |
| 280 | 280 |
| 281 @fletch.native void unlink() { | 281 @dartino.native void unlink() { |
| 282 switch (fletch.nativeError) { | 282 switch (dartino.nativeError) { |
| 283 case fletch.wrongArgumentType: | 283 case dartino.wrongArgumentType: |
| 284 throw new StateError("Cannot unlink from parent process."); | 284 throw new StateError("Cannot unlink from parent process."); |
| 285 default: | 285 default: |
| 286 throw fletch.nativeError; | 286 throw dartino.nativeError; |
| 287 } | 287 } |
| 288 } | 288 } |
| 289 | 289 |
| 290 @fletch.native bool monitor(Port port) { | 290 @dartino.native bool monitor(Port port) { |
| 291 switch (fletch.nativeError) { | 291 switch (dartino.nativeError) { |
| 292 case fletch.wrongArgumentType: | 292 case dartino.wrongArgumentType: |
| 293 throw new StateError("The argument to monitor must be a Port object."); | 293 throw new StateError("The argument to monitor must be a Port object."); |
| 294 default: | 294 default: |
| 295 throw fletch.nativeError; | 295 throw dartino.nativeError; |
| 296 } | 296 } |
| 297 } | 297 } |
| 298 | 298 |
| 299 @fletch.native void unmonitor(Port port) { | 299 @dartino.native void unmonitor(Port port) { |
| 300 switch (fletch.nativeError) { | 300 switch (dartino.nativeError) { |
| 301 case fletch.wrongArgumentType: | 301 case dartino.wrongArgumentType: |
| 302 throw new StateError( | 302 throw new StateError( |
| 303 "The argument to unmonitor must be a Port object."); | 303 "The argument to unmonitor must be a Port object."); |
| 304 default: | 304 default: |
| 305 throw fletch.nativeError; | 305 throw dartino.nativeError; |
| 306 } | 306 } |
| 307 } | 307 } |
| 308 | 308 |
| 309 @fletch.native void kill() { | 309 @dartino.native void kill() { |
| 310 throw fletch.nativeError; | 310 throw dartino.nativeError; |
| 311 } | 311 } |
| 312 | 312 |
| 313 static Process spawn(Function fn, [argument]) { | 313 static Process spawn(Function fn, [argument]) { |
| 314 if (!isImmutable(fn)) { | 314 if (!isImmutable(fn)) { |
| 315 throw new ArgumentError( | 315 throw new ArgumentError( |
| 316 'The closure passed to Process.spawn() must be immutable.'); | 316 'The closure passed to Process.spawn() must be immutable.'); |
| 317 } | 317 } |
| 318 | 318 |
| 319 if (!isImmutable(argument)) { | 319 if (!isImmutable(argument)) { |
| 320 throw new ArgumentError( | 320 throw new ArgumentError( |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 388 | 388 |
| 389 /** | 389 /** |
| 390 * Exit the current process. If a non-null [to] port is provided, | 390 * Exit the current process. If a non-null [to] port is provided, |
| 391 * the process will send the provided [value] to the [to] port as | 391 * the process will send the provided [value] to the [to] port as |
| 392 * its final action. | 392 * its final action. |
| 393 */ | 393 */ |
| 394 static void exit({value, Port to}) { | 394 static void exit({value, Port to}) { |
| 395 try { | 395 try { |
| 396 if (to != null) to._sendExit(value); | 396 if (to != null) to._sendExit(value); |
| 397 } finally { | 397 } finally { |
| 398 fletch.yield(fletch.InterruptKind.terminate.index); | 398 dartino.yield(dartino.InterruptKind.terminate.index); |
| 399 } | 399 } |
| 400 } | 400 } |
| 401 | 401 |
| 402 // Low-level entry for spawned processes. | 402 // Low-level entry for spawned processes. |
| 403 static void _entry(fn, argument) { | 403 static void _entry(fn, argument) { |
| 404 if (argument == null) { | 404 if (argument == null) { |
| 405 fletch.runToEnd(fn); | 405 dartino.runToEnd(fn); |
| 406 } else { | 406 } else { |
| 407 fletch.runToEnd(() => fn(argument)); | 407 dartino.runToEnd(() => fn(argument)); |
| 408 } | 408 } |
| 409 } | 409 } |
| 410 | 410 |
| 411 // Low-level helper function for spawning. | 411 // Low-level helper function for spawning. |
| 412 @fletch.native static Process _spawn(Function entry, | 412 @dartino.native static Process _spawn(Function entry, |
| 413 Function fn, | 413 Function fn, |
| 414 argument, | 414 argument, |
| 415 bool linkToChild, | 415 bool linkToChild, |
| 416 bool linkFromChild, | 416 bool linkFromChild, |
| 417 Port monitor) { | 417 Port monitor) { |
| 418 throw new ArgumentError(); | 418 throw new ArgumentError(); |
| 419 } | 419 } |
| 420 | 420 |
| 421 static void _handleMessages() { | 421 static void _handleMessages() { |
| 422 Channel channel; | 422 Channel channel; |
| 423 while ((channel = _queueGetChannel()) != null) { | 423 while ((channel = _queueGetChannel()) != null) { |
| 424 var message = _queueGetMessage(); | 424 var message = _queueGetMessage(); |
| 425 if (message is ProcessDeath) { | 425 if (message is ProcessDeath) { |
| 426 message = _queueSetupProcessDeath(message); | 426 message = _queueSetupProcessDeath(message); |
| 427 } | 427 } |
| 428 channel.send(message); | 428 channel.send(message); |
| 429 } | 429 } |
| 430 } | 430 } |
| 431 | 431 |
| 432 @fletch.native external static Process get current; | 432 @dartino.native external static Process get current; |
| 433 @fletch.native external static _queueGetMessage(); | 433 @dartino.native external static _queueGetMessage(); |
| 434 @fletch.native external static _queueSetupProcessDeath(ProcessDeath message); | 434 @dartino.native external static _queueSetupProcessDeath(ProcessDeath message); |
| 435 @fletch.native external static Channel _queueGetChannel(); | 435 @dartino.native external static Channel _queueGetChannel(); |
| 436 } | 436 } |
| 437 | 437 |
| 438 // Ports allow you to send messages to a channel. Ports are | 438 // Ports allow you to send messages to a channel. Ports are |
| 439 // are transferable and can be sent between processes. | 439 // are transferable and can be sent between processes. |
| 440 class Port { | 440 class Port { |
| 441 // A Smi stores the aligned pointer to the C++ port object. | 441 // A Smi stores the aligned pointer to the C++ port object. |
| 442 final int _port; | 442 final int _port; |
| 443 | 443 |
| 444 factory Port(Channel channel) { | 444 factory Port(Channel channel) { |
| 445 return Port._create(channel); | 445 return Port._create(channel); |
| 446 } | 446 } |
| 447 | 447 |
| 448 // TODO(kasperl): Temporary debugging aid. | 448 // TODO(kasperl): Temporary debugging aid. |
| 449 int get id => _port; | 449 int get id => _port; |
| 450 | 450 |
| 451 // Send a message to the channel. Not blocking. | 451 // Send a message to the channel. Not blocking. |
| 452 @fletch.native void send(message) { | 452 @dartino.native void send(message) { |
| 453 switch (fletch.nativeError) { | 453 switch (dartino.nativeError) { |
| 454 case fletch.wrongArgumentType: | 454 case dartino.wrongArgumentType: |
| 455 throw new ArgumentError(); | 455 throw new ArgumentError(); |
| 456 case fletch.illegalState: | 456 case dartino.illegalState: |
| 457 throw new StateError("Port is closed."); | 457 throw new StateError("Port is closed."); |
| 458 default: | 458 default: |
| 459 throw fletch.nativeError; | 459 throw dartino.nativeError; |
| 460 } | 460 } |
| 461 } | 461 } |
| 462 | 462 |
| 463 @fletch.native void _sendExit(value) { | 463 @dartino.native void _sendExit(value) { |
| 464 throw new StateError("Port is closed."); | 464 throw new StateError("Port is closed."); |
| 465 } | 465 } |
| 466 | 466 |
| 467 @fletch.native external static Port _create(Channel channel); | 467 @dartino.native external static Port _create(Channel channel); |
| 468 } | 468 } |
| 469 | 469 |
| 470 class Channel { | 470 class Channel { |
| 471 Fiber _receiver; // TODO(kasperl): Should this be a queue too? | 471 Fiber _receiver; // TODO(kasperl): Should this be a queue too? |
| 472 | 472 |
| 473 // TODO(kasperl): Maybe make this a bit smarter and store | 473 // TODO(kasperl): Maybe make this a bit smarter and store |
| 474 // the elements in a growable list? Consider allowing bounds | 474 // the elements in a growable list? Consider allowing bounds |
| 475 // on the queue size. | 475 // on the queue size. |
| 476 _ChannelEntry _head; | 476 _ChannelEntry _head; |
| 477 _ChannelEntry _tail; | 477 _ChannelEntry _tail; |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 536 | 536 |
| 537 class _ChannelEntry { | 537 class _ChannelEntry { |
| 538 final message; | 538 final message; |
| 539 final Fiber sender; | 539 final Fiber sender; |
| 540 _ChannelEntry next; | 540 _ChannelEntry next; |
| 541 _ChannelEntry(this.message, this.sender); | 541 _ChannelEntry(this.message, this.sender); |
| 542 } | 542 } |
| 543 | 543 |
| 544 bool isImmutable(Object object) => _isImmutable(object); | 544 bool isImmutable(Object object) => _isImmutable(object); |
| 545 | 545 |
| 546 @fletch.native external bool _isImmutable(String string); | 546 @dartino.native external bool _isImmutable(String string); |
| 547 | 547 |
| 548 /// Returns a channel that will receive a message in [milliseconds] | 548 /// Returns a channel that will receive a message in [milliseconds] |
| 549 /// milliseconds. | 549 /// milliseconds. |
| 550 // TODO(sigurdm): Move this function? | 550 // TODO(sigurdm): Move this function? |
| 551 Channel sleep(int milliseconds) { | 551 Channel sleep(int milliseconds) { |
| 552 if (milliseconds is! int) throw new ArgumentError(milliseconds); | 552 if (milliseconds is! int) throw new ArgumentError(milliseconds); |
| 553 Channel channel = new Channel(); | 553 Channel channel = new Channel(); |
| 554 Port port = new Port(channel); | 554 Port port = new Port(channel); |
| 555 _sleep(milliseconds, port); | 555 _sleep(milliseconds, port); |
| 556 return channel; | 556 return channel; |
| 557 } | 557 } |
| 558 | 558 |
| 559 @fletch.native external void _sleep(int milliseconds, Port port); | 559 @dartino.native external void _sleep(int milliseconds, Port port); |
| OLD | NEW |