diff options
Diffstat (limited to 'coroutine/coroutine.ha')
-rw-r--r-- | coroutine/coroutine.ha | 96 |
1 files changed, 96 insertions, 0 deletions
diff --git a/coroutine/coroutine.ha b/coroutine/coroutine.ha new file mode 100644 index 0000000..00536d8 --- /dev/null +++ b/coroutine/coroutine.ha @@ -0,0 +1,96 @@ +// License: MPL-2.0 +// (c) 2022 citrons <citrons@mondecitronne.com> +use rt; +use fmt; + +def STACK_SIZE: size = 8192; + +// A coroutine. +export type coroutine = struct { + // The function comprising the body of the coroutine. + func: *func, + // The current execution status of the coroutine. + status: status, + stack: *void, + state: arch_state, +}; + +// Represents the execution status of a coroutine. +export type status = enum { + // The coroutine has been suspended or has not been executed yet. + suspended, + // The coroutine is currently executing. + running, + // The coroutine has completed. + dead, + dying, +}; + +// A function that can be run as a coroutine. +export type func = fn(arg: nullable *void) nullable *void; + +// Create a coroutine which will execute the function f. When done using the +// coroutine, use [[destroy]]. +export fn new(f: *func) *coroutine = { + let stack = rt::malloc(STACK_SIZE) as *void; + let co = alloc(coroutine {func = f, stack = stack, ...}); + arch_init(co.func, &co.state, stack, STACK_SIZE); + return co; +}; + +// Stop a coroutine. +export fn terminate(co: *coroutine) void = { + if (co.status == status::dead) return; + assert(co.status != status::running); + rt::free_(co.stack); + co.status = status::dead; +}; + +// Free a coroutine. +export fn destroy(co: *coroutine) void = { + terminate(co); + free(co); +}; + +let running_: nullable *coroutine = null; + +// Returns the currently running coroutine. +export fn running() nullable *coroutine = running_; + +// Start or resume a coroutine. When called for the first time on a coroutine, +// arg is passed as the argument to the body. If the coroutine has suspended, +// [[resume]] restarts it, and arg is returned as the result of [[suspend]]. +// When the body completes, [[resume]] returns its return value. +export fn resume(co: *coroutine, arg: nullable *void) nullable *void = { + if (co.status == status::dead) return null; + assert(co.status == status::suspended, + "cannot resume non-suspended coroutine"); + let prev = running_; + defer running_ = prev; + running_ = co; + defer if (co.status == status::dying) terminate(co); + co.status = status::running; + return arch_resume(&co.state); +}; + +// Suspend the execution of the present coroutine. arg is returned by +// [[resume]]. +export fn suspend(arg: nullable *void) nullable *void = { + let co = running_ as *coroutine; + if (co.status == status::running) + co.status = status::suspended; + return arch_suspend(arg); +}; + +export @noreturn fn _start(f: *func) void = { + let arg = arch_suspend(null); + let retval = f(arg); + let co = running_ as *coroutine; + co.status = status::dying; + suspend(retval); + assert(false); +}; + +fn arch_suspend(arg: nullable *void) nullable *void; +fn arch_init(f: *func, s: *arch_state, stack: *void, stack_size: size) void; +fn arch_resume(s: *arch_state) nullable *void; |