|
|
@ -286,7 +286,77 @@ It can also be written like this: |
|
|
|
... |
|
|
|
... |
|
|
|
) |
|
|
|
) |
|
|
|
``` |
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Coroutines |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Croissant implements something that could be called "pre-emptive coroutines". They do not provide any performance gain, |
|
|
|
|
|
|
|
but add asynchronicity to the program, and can work as generators! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There is no true parallelism, it is difficult to implement safely and efficiently with a global state. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Spawning |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*Any procedure can be used as a coroutine.* |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A coroutine is created using the `spawn` instruction, which produces an object handle. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
(spawn r0 do_stuff 1 2 3) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
At this point, the program is evenly divided between the original and the coroutine "thread". |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The spawned coroutine is scheduled to run immediately after being spawned. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Task switching |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Coroutines take turns to execute the program. The scheduling interval can be configured. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Control can be given up using the `yield` instruction; for example, when waiting for a mutex. This happens automatically when |
|
|
|
|
|
|
|
a `sleep` instruction is invoked. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Race conditions |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Take care when working with objects, resources and global registers: you can get race conditions |
|
|
|
|
|
|
|
with coroutines. Use atomic instructions (`cas`, `casXX`, `bfcas`…) to implement synchronization. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The `casXX` instruction is very powerful: you can use one bit of a register as a mutex and the rest of it to store some useful data. |
|
|
|
|
|
|
|
You can also use one register for up to 64 mutexes. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Remember to only use global registers (or buffer items) as mutexes: `g0`-`g15`. Each coroutine has its own set of *regular* registers. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Using coroutines as generators |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A coroutine can "yield a value" by invoking the `yield` instruction with an operand. This can be done any number of times. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
(yield r0) |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The coroutine is blocked until the value is consumed by someone. To consume a yielded value, read the coroutine object handle: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
(spawn r5 foo) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(ld r0 @r5) ; read a yielded value |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### Joining a coroutine |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Use the `join` instruction with a coroutine object handle to wait for its completion. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A coroutine completes by calling `ret` at its top level. This naturally means that a coroutine can return values! |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The returned values are placed in the result registers, just like with the `call` instruction. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
(spawn r5 foo) |
|
|
|
|
|
|
|
; ... |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
(join @r0) |
|
|
|
|
|
|
|
; res0-res15 now contain return values |
|
|
|
|
|
|
|
``` |
|
|
|
|
|
|
|
|
|
|
|
# Instruction Set |
|
|
|
# Instruction Set |
|
|
|
|
|
|
|
|
|
|
|
Crsn instruction set is composed of extensions. |
|
|
|
Crsn instruction set is composed of extensions. |
|
|
|