summaryrefslogtreecommitdiff
path: root/coroutine.h
blob: 4bdc4f83ade4d919b057b40ccbec2e2cba7e2f4d (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
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/* coroutine.h - Simple embeddable coroutine implementation
 *
 * Copyright (C) 2024  Luke T. Shumaker <lukeshu@lukeshu.com>
 * SPDX-Licence-Identifier: AGPL-3.0-or-later
 */

/**
 * A coroutine is a cooperatively-multitasked form of threading.
 *
 * This coroutine.{h,c} form a lightweight coroutine implementation
 * for non-multithreaded environments (only one coroutine may be
 * running at a time (so no m:n scheduling), coroutine_{add,main} may
 * not be called concurrently with eachother).
 *
 * Unlike many other lightweight or embeddable coroutine
 * implementations, coroutine.{h,c} allow coroutines to use the stack
 * as normal C functions and do not forbid switch() blocks in
 * coroutines.
 *
 * See also: coroutine_rpc.h is a request/response system built on top
 * of coroutine.{h,c}.
 *
 * See also: coroutine_chan.h is a 1-way channel system built on top
 * of coroutine.{h,c}.
 */
#ifndef _COROUTINE_H_
#define _COROUTINE_H_

#include <stddef.h>  /* for size_t */
#include <stdbool.h> /* for bool */

/* typedefs *******************************************************************/

/**
 * A cid_t is a "coroutine ID", used to refer to a coroutine.
 *
 * May be reused if a coroutine exits.
 *
 * Valid IDs are `1 <= cid <= COROUTINE_NUM`; `0` is used for
 * "none/invalid".
 */
typedef size_t cid_t;

/**
 * The root function for a coroutine; a coroutine function should be
 * declared as
 *
 *    COROUTINE myfunc(void *_args) {
 *        myargtype *args = args;
 *        cr_begin();
 *
 *        ...
 *
 *        cr_end();
 *    }
 *
 * The function MUST NOT ever return (GCC enforces this); call
 * cr_exit() or cr_end() instead of returning.
 *
 * When creating a coroutine with coroutine_add(), the bit before
 * cr_begin() runs before coroutine_add() returns; if `_args` points
 * to a place on another coroutine's stack that may go away, then this
 * is an opportunity to copy it to this coroutine's stack.  Otherwise
 * you shouldn't do anything else before calling cr_begin().
 * Specifically, coroutine_add() and
 * cr_{yield,pause_and_yield,exit,end}() are explicitly forbidden to
 * call from within a coroutine before cr_begin() (note that the
 * cr_rpc_*() and cr_chan_*() macros call these functions).
 */
typedef void (*cr_fn_t)(void *args);
#define COROUTINE __attribute__ ((noreturn)) void

/* managing coroutines ********************************************************/

/**
 * Call `fn(args)` in a new coroutine with stack size `stack_size`.
 *
 * See the doc comment on c_fn_t for the requirements imposed on fn.
 *
 * May be called from outside any coroutine (before calling
 * coroutine_main()) or from inside of a coroutine (after it cas
 * called cr_begin()).
 *
 * Returns the cid of the newly-created coroutine.  May return 0 if
 * there are already COROUTINE_NUM active coroutines.
 */
cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args);

/**
 * Like coroutine_add_with_stack_size(), but uses a default stack size so
 * you don't need to think about it.
 */
#define coroutine_add(fn, args) coroutine_add_with_stack_size(0, fn, args)

/**
 * The main scheduler loop.
 *
 * "Should" never return, but will print a message to stderr and
 * return if the program has deadlocked and there are no runnable
 * coroutines.  So be sure to call coroutine_add() at least once
 * before calling this.
 */
void coroutine_main(void);

/* inside of coroutines *******************************************************/

/** cr_begin() goes at the beginning of a coroutine, after it has initialized its stack.  */
void cr_begin( void);
/** cr_exit() terminates the currently-running coroutine.  */
__attribute__ ((noreturn)) void cr_exit(void);
/** cr_yield() switches to another coroutine (if there is another runnable coroutine to switch to).  */
void cr_yield(void);
/** cr_pause_and_yield() marks the current coroutine as not-runnable and switches to another coroutine.  */
void cr_pause_and_yield(void);
/** cr_unpause() marks a coroutine as runnable that has previously marked itself as non-runnable with cr_pause_and_yield().  */
void cr_unpause(cid_t);
/** cr_end() is a counterpart to cr_begin(), but is really just cr_exit().  */
#define cr_end cr_exit

/** cr_getcid() returns the cid of the currently-running coroutine.  */
cid_t cr_getcid(void);

/**
 * It is not possible for one coroutine to kill another or to mark
 * another as paused; a coroutine may only do those things to itself.
 */

#endif /* _COROUTINE_H_ */