summaryrefslogtreecommitdiff
path: root/coroutine/coroutine.ha
diff options
context:
space:
mode:
Diffstat (limited to 'coroutine/coroutine.ha')
-rw-r--r--coroutine/coroutine.ha96
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;