summaryrefslogtreecommitdiff
path: root/coroutine.h
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-18 15:10:44 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2024-09-18 15:10:44 -0600
commitf24523d46c4da32870e3368f4e0caecafb19090f (patch)
tree09448e33d8c9d7a309bf9757c2b8e0ee73c5e144 /coroutine.h
parent4469398272d78adb81968178276180f16cc8e647 (diff)
tidy, comments
Diffstat (limited to 'coroutine.h')
-rw-r--r--coroutine.h131
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_ */