// License: MPL-2.0 // (c) 2022 citrons 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;