summaryrefslogtreecommitdiff
path: root/coroutine/coroutine.ha
blob: 00536d8151c525b96c717ddd94aa452626c716d0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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;