diff options
author | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-18 15:10:44 -0600 |
---|---|---|
committer | Luke T. Shumaker <lukeshu@lukeshu.com> | 2024-09-18 15:10:44 -0600 |
commit | f24523d46c4da32870e3368f4e0caecafb19090f (patch) | |
tree | 09448e33d8c9d7a309bf9757c2b8e0ee73c5e144 /coroutine.h | |
parent | 4469398272d78adb81968178276180f16cc8e647 (diff) |
tidy, comments
Diffstat (limited to 'coroutine.h')
-rw-r--r-- | coroutine.h | 131 |
1 files changed, 96 insertions, 35 deletions
diff --git a/coroutine.h b/coroutine.h index 13f9797..afe1b1a 100644 --- a/coroutine.h +++ b/coroutine.h @@ -1,68 +1,129 @@ -/* coroutine.h - Simple coroutine and request/response implementation +/* 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_chan.h is a request/response 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 */ +/* configuration **************************************************************/ + +#define COROUTINE_DEFAULT_STACK_SIZE (8*1024) + /* typedefs *******************************************************************/ -typedef size_t cid_t; /* 0=none; otherwise 1-indexed */ +/** + * 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; -#define COROUTINE __attribute__ ((noreturn, no_split_stack)) void +/** + * 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_chan_*() macros call these functions). + */ typedef void (*cr_fn_t)(void *args); +#define COROUTINE __attribute__ ((noreturn, no_split_stack)) void /* managing coroutines ********************************************************/ -void coroutine_init(void); -cid_t coroutine_add(cr_fn_t fn, void *args); +/** + * 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. + */ +__attribute__ ((no_split_stack)) 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(COROUTINE_DEFAULT_STACK_SIZE, 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. */ __attribute__ ((no_split_stack)) bool cr_begin( void); +/** cr_exit() terminates the currently-running coroutine. */ __attribute__ ((no_split_stack, noreturn)) void cr_exit(void); +/** cr_yield() switches to another coroutine (if there is another runnable coroutine to switch to). */ __attribute__ ((no_split_stack)) void cr_yield(void); +/** cr_pause_and_yield() marks the current coroutine as not-runnable and switches to another coroutine. */ __attribute__ ((no_split_stack)) 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(). */ __attribute__ ((no_split_stack)) 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); -/* request/response channels **************************************************/ - -#define cr_chan_t(req_t, resp_t) struct { \ - cid_t requester; \ - cid_t responder; \ - req_t req; \ - resp_t resp; \ - } -#define cr_chan_req(ch, _resp_p, _req) do { \ - (ch)->requester = cr_getcid(); \ - (ch)->req = (_req); \ - if ((ch)->responder != 0) \ - cr_unpause((ch)->responder); \ - cr_pause_and_yield(); \ - if ((typeof(&(ch)->resp))(_resp_p)) \ - *((typeof(&(ch)->resp))(_resp_p)) = (ch)->resp; \ - } while (0) -#define cr_chan_have_req(ch) ((ch)->requester != 0) -#define cr_chan_recv_req(ch, _req_p) do { \ - (ch)->responder = cr_getcid(); \ - if ((ch)->requester == 0) \ - cr_pause_and_yield(); \ - *(_req_p) = (ch)->req; \ - } while (0) -#define cr_chan_send_resp(ch, _resp) do { \ - cr_unpause((ch)->requester); \ - (ch)->responder = 0; \ - (ch)->requester = 0; \ - (ch)->resp = (_resp); \ - } while (0) +/** + * 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_ */ |