summaryrefslogtreecommitdiff
path: root/libcr_ipc/include
diff options
context:
space:
mode:
Diffstat (limited to 'libcr_ipc/include')
-rw-r--r--libcr_ipc/include/libcr_ipc/_linkedlist.h100
-rw-r--r--libcr_ipc/include/libcr_ipc/chan.h271
-rw-r--r--libcr_ipc/include/libcr_ipc/mutex.h46
-rw-r--r--libcr_ipc/include/libcr_ipc/owned_mutex.h79
-rw-r--r--libcr_ipc/include/libcr_ipc/rpc.h233
-rw-r--r--libcr_ipc/include/libcr_ipc/rwmutex.h78
-rw-r--r--libcr_ipc/include/libcr_ipc/select.h180
-rw-r--r--libcr_ipc/include/libcr_ipc/sema.h62
8 files changed, 361 insertions, 688 deletions
diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h
deleted file mode 100644
index 67bff24..0000000
--- a/libcr_ipc/include/libcr_ipc/_linkedlist.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* libcr_ipc/_linkedlist.h - Common low-level linked lists for use in libcr_ipc
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIBCR_IPC__LINKEDLIST_H_
-#define _LIBCR_IPC__LINKEDLIST_H_
-
-#include <libmisc/assert.h>
-
-/* singly linked list *********************************************************/
-
-typedef struct __cr_ipc_sll_node {
- struct __cr_ipc_sll_node *rear;
-} _cr_ipc_sll_node;
-
-typedef struct {
- _cr_ipc_sll_node *front, *rear;
-} _cr_ipc_sll_root;
-
-#define _cr_ipc_sll_node_cast(node_typ, node_ptr) \
- ({ \
- static_assert(_Generic(node_ptr, _cr_ipc_sll_node *: 1, default: 0), \
- "typeof("#node_ptr") != _cr_ipc_sll_node *"); \
- assert(node_ptr); \
- static_assert(offsetof(node_typ, _cr_ipc_sll_node) == 0); \
- ((node_typ*)(node_ptr)); \
- })
-
-static inline void _cr_ipc_sll_push_to_rear(_cr_ipc_sll_root *root, _cr_ipc_sll_node *node) {
- assert(root);
- node->rear = NULL;
- if (root->rear)
- root->rear->rear = node;
- else {
- root->front = node;
- root->rear = node;
- }
-}
-
-static inline void _cr_ipc_sll_pop_from_front(_cr_ipc_sll_root *root) {
- assert(root);
- assert(root->front);
- root->front = root->front->rear;
- if (!root->front)
- root->rear = NULL;
-}
-
-/* doubly linked list *********************************************************/
-
-typedef struct __cr_ipc_dll_node {
- struct __cr_ipc_dll_node *front, *rear;
-} _cr_ipc_dll_node;
-
-typedef struct {
- _cr_ipc_dll_node *front, *rear;
-} _cr_ipc_dll_root;
-
-#define _cr_ipc_dll_node_cast(node_typ, node_ptr) \
- ({ \
- static_assert(_Generic(node_ptr, _cr_ipc_dll_node *: 1, default: 0), \
- "typeof("#node_ptr") != _cr_ipc_dll_node *"); \
- assert(node_ptr); \
- static_assert(offsetof(node_typ, _cr_ipc_dll_node) == 0); \
- ((node_typ*)(node_ptr)); \
- })
-
-static inline void _cr_ipc_dll_push_to_rear(_cr_ipc_dll_root *root, _cr_ipc_dll_node *node) {
- assert(root);
- assert(node);
- node->front = root->rear;
- node->rear = NULL;
- if (root->rear)
- root->rear->rear = node;
- else
- root->front = node;
- root->rear = node;
-}
-
-static inline void _cr_ipc_dll_remove(_cr_ipc_dll_root *root, _cr_ipc_dll_node *node) {
- assert(root);
- assert(node);
- if (node->front)
- node->front->rear = node->rear;
- else
- root->front = node->rear;
- if (node->rear)
- node->rear->front = node->front;
- else
- root->rear = node->front;
-}
-
-static inline void _cr_ipc_dll_pop_from_front(_cr_ipc_dll_root *root) {
- assert(root);
- assert(root->front);
- _cr_ipc_dll_remove(root, root->front);
-}
-
-#endif /* _LIBCR_IPC__LINKEDLIST_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h
index da4f08e..c57979a 100644
--- a/libcr_ipc/include/libcr_ipc/chan.h
+++ b/libcr_ipc/include/libcr_ipc/chan.h
@@ -1,6 +1,6 @@
/* libcr_ipc/chan.h - Simple channels for libcr
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,19 +9,16 @@
#include <stdbool.h> /* for bool */
#include <stddef.h> /* for size_t */
-#include <string.h> /* for memcpy */
-#include <libcr/coroutine.h> /* for cid_t, cr_* */
+#include <libmisc/linkedlist.h> /* for DLIST_DECLARE() */
+#include <libmisc/private.h>
-#include <libcr_ipc/_linkedlist.h>
+/* base channels **************************************************************/
/**
- * CR_CHAN_DECLARE(NAME, VAL_T) declares the following type and
- * methods:
+ * CR_CHAN_DECLARE(NAME, VAL_T) declares the following type:
*
- * type:
- *
- * /**
+ * / **
* * A NAME##_t is a fair unbuffered channel that transports
* * values of type `VAL_T`.
* *
@@ -32,127 +29,163 @@
* * something from an interrupt handler.
* * /
* typedef ... NAME##_t;
+ */
+#define CR_CHAN_DECLARE(NAME, VAL_T) \
+ typedef struct { \
+ struct _cr_chan core; \
+ VAL_T val_typ[0]; \
+ } NAME##_t
+
+/**
+ * cr_chan_send(ch, val) sends `val` over `ch`.
*
- * methods:
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields always
*
- * /**
- * * NAME##_send(ch, val) sends `val` over `ch`.
- * *
- * * @runs_in coroutine
- * * @cr_pauses maybe
- * * @cr_yields always
- * * /
- * void NAME##_send(NAME##_t *ch, VAL_T val);
+ * void cr_chan_send(NAME##_t *ch, VAL_T val);
+ */
+#define cr_chan_send(CH, VAL) do { \
+ typeof((CH)->val_typ[0]) _val_lvalue = VAL; \
+ (void)cr_select_l(CR_SELECT_SEND(CH, &_val_lvalue)); \
+} while (0)
+
+/**
+ * cr_chan_recv(ch) reads and returns a value from ch.
*
- * /**
- * * NAME##_recv(ch) reads and returns a value from ch.
- * *
- * * @runs_in coroutine
- * * @cr_pauses maybe
- * * @cr_yields always
- * * /
- * VAL_T NAME##_recv(NAME##_t *ch);
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields always
*
- * /**
- * * NAME##_can_send(ch) returns whether NAME##_send(ch, val)
- * * would run without pausing.
- * *
- * * @runs_in coroutine
- * * @cr_pauses never
- * * @cr_yields never
- * * /
- * bool NAME##_can_send(NAME##_t *ch);
+ * VAL_T cr_chan_recv(NAME##_T ch);
+ */
+#define cr_chan_recv(CH) ({ \
+ typeof((CH)->val_typ[0]) _val_lvalue; \
+ (void)cr_select_l(CR_SELECT_RECV(CH, &_val_lvalue)); \
+ _val_lvalue; \
+})
+
+/**
+ * cr_chan_can_send(ch) returns whether cr_chan_send(ch, val) would
+ * run without pausing.
*
- * /**
- * * NAME##_can_recv(ch) returns whether NAME##_recv(ch) would
- * * return without pausing.
- * *
- * * @runs_in coroutine
- * * @cr_pauses never
- * * @cr_yields never
- * * /
- * NAME##_can_recv(NAME##_t *ch);
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ *
+ * bool cr_chan_can_send(NAME##_t *ch);
*/
-#define CR_CHAN_DECLARE(NAME, VAL_T) \
- typedef struct { \
- struct _cr_chan core; \
- VAL_T vals[0]; \
- } NAME##_t; \
- \
- static inline void NAME##_send(NAME##_t *ch, VAL_T val) { \
- cr_assert_in_coroutine(); \
- _cr_chan_xfer(_CR_CHAN_SENDER, &ch->core, &val, sizeof(val)); \
- } \
- \
- static inline VAL_T NAME##_recv(NAME##_t *ch) { \
- cr_assert_in_coroutine(); \
- VAL_T val; \
- _cr_chan_xfer(_CR_CHAN_RECVER, &ch->core, &val, sizeof(val)); \
- return val; \
- } \
- \
- static inline bool NAME##_can_send(NAME##_t *ch) { \
- cr_assert_in_coroutine(); \
- return ch->core.waiters.front && \
- ch->core.waiter_typ == _CR_CHAN_RECVER; \
- } \
- \
- static inline bool NAME##_can_recv(NAME##_t *ch) { \
- cr_assert_in_coroutine(); \
- return ch->core.waiters.front && \
- ch->core.waiter_typ == _CR_CHAN_SENDER; \
- }
-
-enum _cr_chan_waiter_typ {
- _CR_CHAN_SENDER,
- _CR_CHAN_RECVER,
+#define cr_chan_can_send(CH) ({ \
+ cr_assert_in_coroutine(); \
+ (bool)((CH)->core.waiters.front && \
+ (CH)->core.waiters.front->val.op == _CR_SELECT_OP_RECV); \
+})
+
+/**
+ * cr_chan_can_recv(ch) returns whether cr_chan_recv(ch) would return
+ * without pausing.
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ *
+ * bool cr_chan_can_recv(NAME##_t *ch);
+ */
+#define cr_chan_can_recv(CH) ({ \
+ cr_assert_in_coroutine(); \
+ (bool)((CH)->core.waiters.front && \
+ (CH)->core.waiters.front->val.op == _CR_SELECT_OP_SEND); \
+})
+
+/**
+ * cr_chan_num_waiters(ch) returns the number of coroutines currently
+ * blocked on the channel.
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ *
+ * size_t cr_chan_num_waiters(NAME##_t *ch);
+ */
+#define cr_chan_num_waiters(CH) ({ \
+ cr_assert_in_coroutine(); \
+ ((CH)->core.nwaiters); \
+})
+
+DLIST_DECLARE(_cr_select_arg_list);
+struct _cr_chan {
+ struct _cr_select_arg_list waiters;
+ size_t nwaiters;
};
-struct _cr_chan_waiter {
- _cr_ipc_dll_node;
- cid_t cid;
+/* cr_select arguments ********************************************************/
+
+/**
+ * Do not populate cr_select_arg yourself; use the
+ * CR_SELECT_{RECV,SEND,DEFAULT} macros.
+ */
+struct _cr_select_waiter;
+struct _cr_select_arg {
+ enum {
+ _CR_SELECT_OP_RECV,
+ _CR_SELECT_OP_SEND,
+ _CR_SELECT_OP_DEFAULT,
+ } op;
+ struct _cr_chan *ch;
void *val_ptr;
- void (*dequeue)(void *, size_t);
- void *dequeue_arg1;
- size_t dequeue_arg2;
+ size_t val_siz;
+ BEGIN_PRIVATE(LIBCR_IPC_CHAN_H);
+ struct _cr_select_waiter *waiter;
+ END_PRIVATE(LIBCR_IPC_CHAN_H);
};
+DLIST_DECLARE_NODE(_cr_select_arg_list, struct _cr_select_arg);
+#define cr_select_arg _cr_select_arg_list_node
-struct _cr_chan {
- enum _cr_chan_waiter_typ waiter_typ;
- _cr_ipc_dll_root waiters;
-};
+#define CR_SELECT_RECV(CH, VALP) ((struct cr_select_arg){ .val = { \
+ .op = _CR_SELECT_OP_RECV, \
+ .ch = &((CH)->core), \
+ /* The _valp temporary variable is to get the compiler to check that \
+ * the types are compatible. */ \
+ .val_ptr = ({ typeof((CH)->val_typ[0]) *_valp = VALP; _valp; }), \
+ .val_siz = sizeof((CH)->val_typ[0]), \
+}})
+
+/* BUG: It's bogus that CR_SELECT_SEND takes VALP instead of VAL, but
+ * since we need an address, taking VAL would introduce uncomfortable
+ * questions about where VAL sits on the stack. */
+#define CR_SELECT_SEND(CH, VALP) ((struct cr_select_arg){ .val = { \
+ .op = _CR_SELECT_OP_SEND, \
+ .ch = &((CH)->core), \
+ /* The _valp temporary variable is to get the compiler to check that \
+ * the types are compatible. */ \
+ .val_ptr = ({ typeof((CH)->val_typ[0]) *_valp = VALP; _valp; }), \
+ .val_siz = sizeof((CH)->val_typ[0]), \
+}})
-static void _cr_chan_dequeue(void *_ch, size_t) {
- struct _cr_chan *ch = _ch;
- _cr_ipc_dll_pop_from_front(&ch->waiters);
-}
-
-static inline void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size) {
- assert(ch);
- assert(val_ptr);
-
- if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */
- /* Copy. */
- struct _cr_chan_waiter *front = _cr_ipc_dll_node_cast(struct _cr_chan_waiter, ch->waiters.front);
- if (self_typ == _CR_CHAN_SENDER)
- memcpy(front->val_ptr, val_ptr, val_size);
- else
- memcpy(val_ptr, front->val_ptr, val_size);
- cr_unpause(front->cid);
- front->dequeue(front->dequeue_arg1,
- front->dequeue_arg2);
- cr_yield();
- } else { /* blocking slow-path */
- struct _cr_chan_waiter self = {
- .cid = cr_getcid(),
- .val_ptr = val_ptr,
- .dequeue = _cr_chan_dequeue,
- .dequeue_arg1 = ch,
- };
- _cr_ipc_dll_push_to_rear(&ch->waiters, &self);
- ch->waiter_typ = self_typ;
- cr_pause_and_yield();
- }
-}
+#define CR_SELECT_DEFAULT ((struct cr_select_arg){ .val = { \
+ .op = _CR_SELECT_OP_DEFAULT, \
+}})
+
+/* cr_select_v(arg_cnt, arg_vec) **********************************************/
+
+/**
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields always
+ */
+size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]);
+
+/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/
+
+/**
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields always
+ */
+#define cr_select_l(...) ({ \
+ struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \
+ cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0]), \
+ _cr_select_args); \
+})
#endif /* _LIBCR_IPC_CHAN_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/mutex.h b/libcr_ipc/include/libcr_ipc/mutex.h
index cba8c19..e5f43c8 100644
--- a/libcr_ipc/include/libcr_ipc/mutex.h
+++ b/libcr_ipc/include/libcr_ipc/mutex.h
@@ -1,6 +1,6 @@
/* libcr_ipc/mutex.h - Simple mutexes for libcr
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,14 +9,10 @@
#include <stdbool.h> /* for bool */
-#include <libcr/coroutine.h> /* for cid_t, cr_* */
+#include <libmisc/linkedlist.h> /* for SLIST_DECLARE() */
+#include <libmisc/private.h>
-#include <libcr_ipc/_linkedlist.h>
-
-struct _cr_mutex_waiter {
- _cr_ipc_sll_node;
- cid_t cid;
-};
+SLIST_DECLARE(_cr_mutex_waiter_list);
/**
* A cr_mutex_t is a fair mutex.
@@ -27,8 +23,10 @@ struct _cr_mutex_waiter {
* first place, then it has no business unlocking one.
*/
typedef struct {
- bool locked;
- _cr_ipc_sll_root waiters;
+ BEGIN_PRIVATE(LIBCR_IPC_MUTEX_H);
+ bool locked;
+ struct _cr_mutex_waiter_list waiters;
+ END_PRIVATE(LIBCR_IPC_MUTEX_H);
} cr_mutex_t;
/**
@@ -38,21 +36,7 @@ typedef struct {
* @cr_pauses maybe
* @cr_yields maybe
*/
-static inline void cr_mutex_lock(cr_mutex_t *mu) {
- assert(mu);
- cr_assert_in_coroutine();
-
- if (!mu->locked) /* non-blocking fast-path */
- mu->locked = true;
- else { /* blocking slow-path */
- struct _cr_mutex_waiter self = {
- .cid = cr_getcid(),
- };
- _cr_ipc_sll_push_to_rear(&mu->waiters, &self);
- cr_pause_and_yield();
- }
- assert(mu->locked);
-}
+void cr_mutex_lock(cr_mutex_t *mu);
/**
* Unlock the mutex. Unblocks a coroutine that is blocked on
@@ -62,16 +46,6 @@ static inline void cr_mutex_lock(cr_mutex_t *mu) {
* @cr_pauses never
* @cr_yields never
*/
-static inline void cr_mutex_unlock(cr_mutex_t *mu) {
- assert(mu);
- cr_assert_in_coroutine();
-
- assert(mu->locked);
- if (mu->waiters.front) {
- cr_unpause(_cr_ipc_sll_node_cast(struct _cr_mutex_waiter, mu->waiters.front)->cid);
- _cr_ipc_sll_pop_from_front(&mu->waiters);
- } else
- mu->locked = false;
-}
+void cr_mutex_unlock(cr_mutex_t *mu);
#endif /* _LIBCR_IPC_MUTEX_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/owned_mutex.h b/libcr_ipc/include/libcr_ipc/owned_mutex.h
deleted file mode 100644
index c66240f..0000000
--- a/libcr_ipc/include/libcr_ipc/owned_mutex.h
+++ /dev/null
@@ -1,79 +0,0 @@
-/* libcr_ipc/owned_mutex.h - Owned mutexes for libcr
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIBCR_IPC_OWNED_MUTEX_H_
-#define _LIBCR_IPC_OWNED_MUTEX_H_
-
-#include <stdbool.h> /* for bool */
-
-#include <libcr/coroutine.h> /* for cid_t, cr_unpause(), cr_pause_and_yield() */
-
-#include <libcr_ipc/_linkedlist.h>
-
-struct _cr_owned_mutex_waiter {
- _cr_ipc_sll_node;
- cid_t cid;
-};
-
-/**
- * A cr_owned_mutex_t is a fair mutex that tracks not just whether it
- * is locked, but which coroutine has it locked.
- *
- * None of the methods have `_from_intrhandler` variants because (1)
- * an interrupt handler can't block, so it shouldn't ever lock a mutex
- * because that can block; and (2) if it can't lock a mutex in the
- * first place, then it has no business unlocking one.
- */
-typedef struct {
- cid_t owner;
- _cr_ipc_sll_root waiters;
-} cr_owned_mutex_t;
-
-/**
- * Lock the mutex. Blocks if it is already locked.
- *
- * @runs_in coroutine
- * @cr_pauses maybe
- * @cr_yields maybe
- */
-static inline void cr_owned_mutex_lock(cr_owned_mutex_t *mu) {
- assert(mu);
-
- if (!mu->owner) /* non-blocking fast-path */
- mu->owner = cr_getcid();
- else { /* blocking slow-path */
- struct _cr_owned_mutex_waiter self = {
- .cid = cr_getcid(),
- };
- _cr_ipc_sll_push_to_rear(&mu->waiters, &self);
- cr_pause_and_yield();
- }
- assert(mu->owner == cr_getcid());
-}
-
-/**
- * Unlock the mutex. Unblocks a coroutine that is blocked on
- * cr_owned_mutex_lock(). Must be the called from the same coroutine
- * that called cr_owned_mutex_lock().
- *
- * @runs_in coroutine
- * @cr_pauses never
- * @cr_yields never
- */
-static inline void cr_owned_mutex_unlock(cr_owned_mutex_t *mu) {
- assert(mu);
-
- assert(mu->owner == cr_getcid());
- if (mu->waiters.front) {
- cid_t waiter = _cr_ipc_sll_node_cast(struct _cr_owned_mutex_waiter, mu->waiters.front)->cid;
- cr_unpause(waiter);
- mu->owner = waiter;
- _cr_ipc_sll_pop_from_front(&mu->waiters);
- } else
- mu->owner = 0;
-}
-
-#endif /* _LIBCR_IPC_OWNED_MUTEX_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/rpc.h b/libcr_ipc/include/libcr_ipc/rpc.h
index 0d6d25e..bfa0a04 100644
--- a/libcr_ipc/include/libcr_ipc/rpc.h
+++ b/libcr_ipc/include/libcr_ipc/rpc.h
@@ -1,6 +1,6 @@
/* libcr_ipc/rpc.h - Simple request/response system for libcr
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@@ -9,17 +9,12 @@
#include <stdbool.h> /* for bool */
-#include <libcr/coroutine.h> /* for cid_t, cr_* */
-
-#include <libcr_ipc/_linkedlist.h>
+#include <libmisc/linkedlist.h> /* for SLIST_DECLARE() */
/**
- * CR_RPC_DECLARE(NAME, REQ_T, RESP_T) declares the following types
- * and methods:
- *
- * type:
+ * CR_RPC_DECLARE(NAME, REQ_T, RESP_T) declares the following types:
*
- * /**
+ * / **
* * A NAME##_t is a fair rpc-channel on which the requester submits a
* * value of type `REQ_T` and the responder responds with a value of
* * type `RESP_T`.
@@ -34,129 +29,117 @@
* * _recv_req() and _send_resp().
* typedef ... NAME##_t;
*
- * methods:
+ * / **
+ * * A NAME##_req_t is handle that wraps a REQ_T and is used to return
+ * * the response RESP_T to the correct requester. `REQ_T req` is the
+ * * only public member.
+ * typedef struct { REQ_T req; ... } NAME##_req_t;
+ */
+#define CR_RPC_DECLARE(NAME, REQ_T, RESP_T) \
+ typedef struct { \
+ REQ_T req; \
+ \
+ RESP_T *_resp; /* where to write resp to */ \
+ cid_t _requester; \
+ } NAME##_req_t; \
+ \
+ typedef struct { \
+ struct _cr_rpc core; \
+ NAME##_req_t handle_typ[0]; \
+ } NAME##_t
+
+/* Methods for NAME##_t *******************************************************/
+
+/**
+ * cr_rpc_send_req(ch, req) submits the `req` request over `ch` and
+ * returns the response.
*
- * /**
- * * NAME##_send_req(ch, req) submits the `req` request over `ch` and
- * * returns the response.
- * *
- * * @runs_in coroutine
- * * @cr_pauses always
- * * @cr_yields always
- * * /
- * RESP_T NAME##_send_req(NAME##_t *ch, REQ_T req);
+ * @runs_in coroutine
+ * @cr_pauses always
+ * @cr_yields always
*
- * /**
- * * NAME##_recv_req(ch) reads a request from ch, and returns a
- * * NAME##_req_t handle wrapping that request.
- * *
- * * @runs_in coroutine
- * * @cr_pauses maybe
- * * @cr_yields maybe
- * * /
- * NAME##_req_t NAME##_recv_req(NAME##_t *ch);
+ * RESP_T cr_rpc_send_req(NAME##_t *ch, REQ_T req);
+ */
+#define cr_rpc_send_req(CH, REQ) ({ \
+ cr_assert_in_coroutine(); \
+ typeof((CH)->handle_typ[0].req) _req_lvalue = REQ; \
+ typeof(*(CH)->handle_typ[0]._resp) _resp_lvalue; \
+ _cr_rpc_send_req(&(CH)->core, \
+ &_req_lvalue, sizeof(_req_lvalue), \
+ &_resp_lvalue); \
+ _resp_lvalue; \
+})
+
+/**
+ * cr_rpc_recv_req(ch) reads a request from ch, and returns a
+ * NAME##_req_t handle wrapping that request.
*
- * /**
- * * NAME##_can_recv_req(ch) returns whether NAME##_recv_req(ch)
- * * would return without pausing.
- * *
- * * @runs_in coroutine
- * * @cr_pauses never
- * * @cr_yields never
- * * /
- * bool NAME##_can_recv_req(NAME##_t *ch);
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields maybe
+ *
+ * NAME##_req_t cr_rcp_recv_req(NAME##_t *ch);
+ */
+#define cr_rpc_recv_req(CH) ({ \
+ cr_assert_in_coroutine(); \
+ typeof((CH)->handle_typ[0]) ret; \
+ _cr_rpc_recv_req(&(CH)->core, \
+ &ret.req, sizeof(ret.req), \
+ (void **)&ret._resp, \
+ &ret._requester); \
+ ret; \
+})
+
+/**
+ * cr_rpc_can_recv_req(ch) returns whether NAME##_recv_req(ch)
+ * would return without pausing.
*
- * type:
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
*
- * /**
- * * A NAME##_req_t is a handle that wraps a REQ_T, and is a channel
- * * that a response may be written to.
- * * /
- * typedef ... NAME##_req_t;
+ * bool cr_rpc_can_recv_req(NAME##_t *ch);
+ */
+#define cr_rpc_can_recv_req(CH) ({ \
+ cr_assert_in_coroutine(); \
+ (bool)((CH)->core.waiters.front && \
+ (CH)->core.waiter_typ == _CR_RPC_REQUESTER); \
+})
+
+/* Methods for NAME##_req_t ***************************************************/
+
+/**
+ * cr_rpc_send_resp(req, resp) sends the given response to the given
+ * request.
*
- * methods:
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields always
*
- * /**
- * * cr_rpc_send_resp(req, resp) sends the given response to the given
- * * request.
- * *
- * * @runs_in coroutine
- * * @cr_pauses never
- * * @cr_yields always
- * * /
- * void NAME##_send_resp(NAME##_req_t req, RESP_T resp);
+ * void cr_rpc_send_resp(NAME##_req_t req, RESP_T resp);
*/
-#define CR_RPC_DECLARE(NAME, REQ_T, RESP_T) \
- typedef struct { \
- REQ_T req; \
- \
- RESP_T *_resp; \
- cid_t _requester; \
- } NAME##_req_t; \
- \
- struct _##NAME##_waiting_req { \
- _cr_ipc_sll_node; \
- REQ_T *req; \
- RESP_T *resp; \
- cid_t cid; \
- }; \
- \
- struct _##NAME##_waiting_resp { \
- _cr_ipc_sll_node; \
- cid_t cid; \
- }; \
- \
- typedef struct { \
- _cr_ipc_sll_root waiting_reqs; \
- _cr_ipc_sll_root waiting_resps; \
- } NAME##_t; \
- \
- static inline RESP_T NAME##_send_req(NAME##_t *ch, REQ_T req) { \
- cr_assert_in_coroutine(); \
- RESP_T resp; \
- struct _##NAME##_waiting_req self = { \
- .req = &req, \
- .resp = &resp, \
- .cid = cr_getcid(), \
- }; \
- _cr_ipc_sll_push_to_rear(&ch->waiting_reqs, &self); \
- if (ch->waiting_resps.front) \
- cr_unpause(_cr_ipc_sll_node_cast(struct _##NAME##_waiting_resp, ch->waiting_resps.front)->cid); \
- cr_pause_and_yield(); \
- return resp; \
- } \
- \
- static inline NAME##_req_t NAME##_recv_req(NAME##_t *ch) { \
- cr_assert_in_coroutine(); \
- if (!ch->waiting_reqs.front) { \
- struct _##NAME##_waiting_resp self = { \
- .cid = cr_getcid(), \
- }; \
- _cr_ipc_sll_push_to_rear(&ch->waiting_resps, &self); \
- cr_pause_and_yield(); \
- } \
- assert(ch->waiting_reqs.front); \
- struct _##NAME##_waiting_req *front_req = \
- _cr_ipc_sll_node_cast(struct _##NAME##_waiting_req, ch->waiting_reqs.front); \
- NAME##_req_t ret = { \
- .req = *(front_req->req), \
- ._resp = front_req->resp, \
- ._requester = front_req->cid, \
- }; \
- _cr_ipc_sll_pop_from_front(&ch->waiting_reqs); \
- return ret; \
- } \
- \
- static inline bool NAME##_can_recv_req(NAME##_t *ch) { \
- cr_assert_in_coroutine(); \
- return ch->waiting_reqs.front != NULL; \
- } \
- \
- static inline void NAME##_send_resp(NAME##_req_t req, RESP_T resp) { \
- cr_assert_in_coroutine(); \
- *(req._resp) = resp; \
- cr_unpause(req._requester); \
- cr_yield(); \
- }
+#define cr_rpc_send_resp(REQ, RESP) { \
+ cr_assert_in_coroutine(); \
+ *((REQ)._resp) = RESP; \
+ cr_unpause(REQ._requester); \
+ cr_yield(); \
+} while (0)
+
+/* Background details *********************************************************/
+
+enum _cr_rpc_waiter_typ {
+ _CR_RPC_REQUESTER,
+ _CR_RPC_RESPONDER,
+};
+
+SLIST_DECLARE(_cr_rpc_waiter_list);
+
+struct _cr_rpc {
+ enum _cr_rpc_waiter_typ waiter_typ;
+ struct _cr_rpc_waiter_list waiters;
+};
+
+void _cr_rpc_send_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void *resp_ptr);
+void _cr_rpc_recv_req(struct _cr_rpc *ch, void *req_ptr, size_t req_size, void **ret_resp_ptr, cid_t *ret_requester);
#endif /* _LIBCR_IPC_RPC_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/rwmutex.h b/libcr_ipc/include/libcr_ipc/rwmutex.h
new file mode 100644
index 0000000..8ccae63
--- /dev/null
+++ b/libcr_ipc/include/libcr_ipc/rwmutex.h
@@ -0,0 +1,78 @@
+/* libcr_ipc/rwmutex.h - Simple read/write mutexes for libcr
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBCR_IPC_RWMUTEX_H_
+#define _LIBCR_IPC_RWMUTEX_H_
+
+#include <stdbool.h>
+
+#include <libmisc/linkedlist.h> /* for SLIST_DECLARE() */
+#include <libmisc/private.h>
+
+SLIST_DECLARE(_cr_rwmutex_waiter_list);
+
+/**
+ * A cr_rwmutex_t is a fair read/write mutex.
+ *
+ * A waiting writer blocks any new readers; this ensures that the
+ * writer is able to eventually get the lock.
+ *
+ * None of the methods have `_from_intrhandler` variants because (1)
+ * an interrupt handler can't block, so it shouldn't ever lock a mutex
+ * because that can block; and (2) if it can't lock a mutex in the
+ * first place, then it has no business unlocking one.
+ */
+typedef struct {
+ BEGIN_PRIVATE(LIBCR_IPC_RWMUTEX_H);
+ unsigned nreaders;
+ bool locked;
+ bool unpausing;
+ struct _cr_rwmutex_waiter_list waiters;
+ END_PRIVATE(LIBCR_IPC_RWMUTEX_H);
+} cr_rwmutex_t;
+
+/**
+ * Lock the mutex for writing. Blocks if it is already locked.
+ *
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields maybe
+ */
+void cr_rwmutex_lock(cr_rwmutex_t *mu);
+
+/**
+ * Undo a single cr_rwmutex_lock() call. Unblocks either a single
+ * coroutine that is blocked on cr_rwmutex_lock() or arbitrarily many
+ * coroutines that are blocked on cr_rwmutex_rlock().
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ */
+void cr_rwmutex_unlock(cr_rwmutex_t *mu);
+
+/**
+ * Lock the mutex for reading. Blocks if it is already locked for
+ * writing.
+ *
+ * @runs_in coroutine
+ * @cr_pauses maybe
+ * @cr_yields maybe
+ */
+void cr_rwmutex_rlock(cr_rwmutex_t *mu);
+
+/**
+ * Undo a single cr_rwmutext_rock() call. If the reader count is
+ * reduced to zero, unblocks a single coroutine that is blocked on
+ * cr_rwmutex_lock().
+ *
+ * @runs_in coroutine
+ * @cr_pauses never
+ * @cr_yields never
+ */
+void cr_rwmutex_runlock(cr_rwmutex_t *mu);
+
+#endif /* _LIBCR_IPC_RWMUTEX_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h
deleted file mode 100644
index ee49cca..0000000
--- a/libcr_ipc/include/libcr_ipc/select.h
+++ /dev/null
@@ -1,180 +0,0 @@
-/* libcr_ipc/select.h - Select between channels
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#include <alloca.h> /* for alloca() */
-#include <stddef.h> /* for size_t */
-
-#include <libmisc/assert.h>
-#include <libmisc/rand.h>
-
-#include <libcr_ipc/chan.h>
-
-#ifndef _LIBCR_IPC_SELECT_H_
-#define _LIBCR_IPC_SELECT_H_
-
-/* arguments ******************************************************************/
-
-/**
- * Do not populate cr_select_arg yourself; use the
- * CR_SELECT_{RECV,SEND,DEFAULT} macros.
- */
-struct cr_select_arg {
- enum {
- _CR_SELECT_OP_RECV,
- _CR_SELECT_OP_SEND,
- _CR_SELECT_OP_DEFAULT,
- } op;
- struct _cr_chan *ch;
- void *val_ptr;
- size_t val_siz;
-};
-
-#define CR_SELECT_RECV(CH, VALP) ({ \
- assert(CH); \
- assert(VALP); \
- /* The _valp indirection is to get the \
- * compiler to check that the types are \
- * compatible. */ \
- typeof((CH)->vals[0]) *_valp = VALP; \
- ((struct cr_select_arg){ \
- .op = _CR_SELECT_OP_RECV, \
- .ch = &((CH)->core), \
- .val_ptr = _valp, \
- .val_siz = sizeof((CH)->vals[0]), \
- }); \
- })
-#define CR_SELECT_SEND(CH, VAL) ({ \
- assert(CH); \
- typeof((CH)->vals[0]) val_lvalue = VAL; \
- ((struct cr_select_arg){ \
- .op = _CR_SELECT_OP_SEND, \
- .ch = &((CH)->core), \
- .val_ptr = &val_lvalue;, \
- .val_siz = sizeof((CH)->vals[0]), \
- }); \
- })
-#define CR_SELECT_DEFAULT \
- ((struct cr_select_arg){ \
- .op = _CR_SELECT_OP_DEFAULT, \
- })
-
-/* cr_select_v(arg_cnt, arg_vec) **********************************************/
-
-enum _cr_select_class {
- _CR_SELECT_CLASS_DEFAULT,
- _CR_SELECT_CLASS_BLOCKING,
- _CR_SELECT_CLASS_NONBLOCK,
-};
-
-static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg) {
- switch (arg.op) {
- case _CR_SELECT_OP_RECV:
- if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_SENDER)
- return _CR_SELECT_CLASS_NONBLOCK;
- else
- return _CR_SELECT_CLASS_BLOCKING;
- case _CR_SELECT_OP_SEND:
- if (arg.ch->waiters.front && arg.ch->waiter_typ == _CR_CHAN_RECVER)
- return _CR_SELECT_CLASS_NONBLOCK;
- else
- return _CR_SELECT_CLASS_BLOCKING;
- case _CR_SELECT_OP_DEFAULT:
- return _CR_SELECT_CLASS_DEFAULT;
- default:
- assert_notreached("invalid arg.op");
- }
-}
-
-struct _cr_select_waiters {
- size_t cnt;
- struct cr_select_arg *args;
- struct _cr_chan_waiter *nodes;
-};
-
-static void _cr_select_dequeue(void *_waiters, size_t idx) {
- struct _cr_select_waiters *waiters = waiters;
- for (size_t i = 0; i < waiters->cnt; i++)
- _cr_ipc_dll_remove(&(waiters->args[i].ch->waiters),
- &(waiters->nodes[i]));
- waiters->cnt = idx;
-}
-
-static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
- size_t cnt_blocking = 0;
- size_t cnt_nonblock = 0;
- size_t cnt_default = 0;
-
- assert(arg_cnt);
- assert(arg_vec);
- cr_assert_in_coroutine();
-
- for (size_t i = 0; i < arg_cnt; i++) {
- switch (_cr_select_getclass(arg_vec[i])) {
- case _CR_SELECT_CLASS_BLOCKING:
- cnt_blocking++;
- break;
- case _CR_SELECT_CLASS_NONBLOCK:
- cnt_nonblock++;
- break;
- case _CR_SELECT_CLASS_DEFAULT:
- cnt_default++;
- break;
- }
- }
-
- if (cnt_nonblock) {
- size_t choice = rand_uint63n(cnt_nonblock);
- for (size_t i = 0, seen = 0; i < arg_cnt; i++) {
- if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) {
- if (seen == choice) {
- _cr_chan_xfer(arg_vec[i].op == _CR_SELECT_OP_RECV
- ? _CR_CHAN_RECVER
- : _CR_CHAN_SENDER,
- arg_vec[i].ch,
- arg_vec[i].val_ptr,
- arg_vec[i].val_siz);
- return i;
- }
- seen++;
- }
- }
- assert_notreached("should have returned from inside for() loop");
- }
-
- if (cnt_default) {
- for (size_t i = 0; i < arg_cnt; i++)
- if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT)
- return i;
- assert_notreached("should have returned from inside for() loop");
- }
-
- struct _cr_select_waiters waiters = {
- .cnt = arg_cnt,
- .args = arg_vec,
- .nodes = alloca(sizeof(struct _cr_chan_waiter) * arg_cnt),
- };
- for (size_t i = 0; i < arg_cnt; i++) {
- waiters.nodes[i] = (struct _cr_chan_waiter){
- .cid = cr_getcid(),
- .val_ptr = arg_vec[i].val_ptr,
- .dequeue = _cr_select_dequeue,
- .dequeue_arg1 = &waiters,
- .dequeue_arg2 = i,
- };
- _cr_ipc_dll_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]);
- }
- cr_pause_and_yield();
- return waiters.cnt;
-}
-
-/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/
-
-#define cr_select_l(...) ({ \
- struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \
- cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \
-})
-
-#endif /* _LIBCR_IPC_SELECT_H_ */
diff --git a/libcr_ipc/include/libcr_ipc/sema.h b/libcr_ipc/include/libcr_ipc/sema.h
index 1064182..8b5ac5b 100644
--- a/libcr_ipc/include/libcr_ipc/sema.h
+++ b/libcr_ipc/include/libcr_ipc/sema.h
@@ -1,20 +1,18 @@
/* libcr_ipc/sema.h - Simple semaphores for libcr
*
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#ifndef _LIBCR_IPC_SEMA_H_
#define _LIBCR_IPC_SEMA_H_
-#include <libcr/coroutine.h> /* for cid_t, cr_* */
+#include <stdbool.h>
-#include <libcr_ipc/_linkedlist.h>
+#include <libmisc/linkedlist.h> /* for SLIST_DECLARE() */
+#include <libmisc/private.h>
-struct _cr_sema_waiter {
- _cr_ipc_sll_node;
- cid_t cid;
-};
+SLIST_DECLARE(_cr_sema_waiter_list);
/**
* A cr_sema_t is a fair unbounded[1] counting semaphore.
@@ -22,8 +20,11 @@ struct _cr_sema_waiter {
* [1]: Well, UINT_MAX
*/
typedef struct {
- unsigned int cnt;
- _cr_ipc_sll_root waiters;
+ BEGIN_PRIVATE(LIBCR_IPC_SEMA_H);
+ unsigned int cnt;
+ bool unpausing;
+ struct _cr_sema_waiter_list waiters;
+ END_PRIVATE(LIBCR_IPC_SEMA_H);
} cr_sema_t;
/**
@@ -33,36 +34,14 @@ typedef struct {
* @cr_pauses never
* @cr_yields never
*/
-static inline void cr_sema_signal(cr_sema_t *sema) {
- assert(sema);
- cr_assert_in_coroutine();
-
- bool saved = cr_save_and_disable_interrupts();
- sema->cnt++;
- if (sema->waiters.front) {
- cr_unpause(
- _cr_ipc_sll_node_cast(struct _cr_sema_waiter, sema->waiters.front)->cid);
- _cr_ipc_sll_pop_from_front(&sema->waiters);
- }
- cr_restore_interrupts(saved);
-}
+void cr_sema_signal(cr_sema_t *sema);
/**
* Like cr_sema_signal(), but for use from an interrupt handler.
*
* @runs_in intrhandler
*/
-static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) {
- assert(sema);
- cr_assert_in_intrhandler();
-
- sema->cnt++;
- if (sema->waiters.front) {
- cr_unpause_from_intrhandler(
- _cr_ipc_sll_node_cast(struct _cr_sema_waiter, sema->waiters.front)->cid);
- _cr_ipc_sll_pop_from_front(&sema->waiters);
- }
-}
+void cr_sema_signal_from_intrhandler(cr_sema_t *sema);
/**
* Wait until the semaphore is >0, then decrement it.
@@ -71,21 +50,6 @@ static inline void cr_sema_signal_from_intrhandler(cr_sema_t *sema) {
* @cr_pauses maybe
* @cr_yields maybe
*/
-static inline void cr_sema_wait(cr_sema_t *sema) {
- assert(sema);
- cr_assert_in_coroutine();
-
- bool saved = cr_save_and_disable_interrupts();
- if (!sema->cnt) {
- struct _cr_sema_waiter self = {
- .cid = cr_getcid(),
- };
- _cr_ipc_sll_push_to_rear(&sema->waiters, &self);
- cr_pause_and_yield();
- }
- assert(sema->cnt);
- sema->cnt--;
- cr_restore_interrupts(saved);
-}
+void cr_sema_wait(cr_sema_t *sema);
#endif /* _LIBCR_IPC_SEMA_H_ */