summaryrefslogtreecommitdiff
path: root/libcr_ipc
diff options
context:
space:
mode:
Diffstat (limited to 'libcr_ipc')
-rw-r--r--libcr_ipc/chan.c151
-rw-r--r--libcr_ipc/include/libcr_ipc/chan.h110
-rw-r--r--libcr_ipc/tests/test_select.c5
3 files changed, 126 insertions, 140 deletions
diff --git a/libcr_ipc/chan.c b/libcr_ipc/chan.c
index 6cbe890..b7ecfc8 100644
--- a/libcr_ipc/chan.c
+++ b/libcr_ipc/chan.c
@@ -4,60 +4,22 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <alloca.h> /* for alloca() */
#include <string.h> /* for memcpy() */
#include <libcr/coroutine.h> /* for cid_t, cr_* */
#include <libmisc/assert.h>
#include <libmisc/rand.h>
+#define IMPLEMENTATION_FOR_LIBCR_IPC_CHAN_H YES
#include <libcr_ipc/chan.h>
-/* base channels **************************************************************/
-
-struct cr_chan_waiter {
+struct _cr_select_waiter {
cid_t cid;
- void *val_ptr;
- void (*dequeue)(void *, size_t);
- void *dequeue_arg1;
- size_t dequeue_arg2;
-};
-DLIST_DECLARE_NODE(_cr_chan_waiter_list, struct cr_chan_waiter);
-
-void cr_chan_dequeue(void *_ch, size_t) {
- struct _cr_chan *ch = _ch;
- dlist_pop_from_front(&ch->waiters);
-}
-
-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);
+ struct _cr_select_arg_list_node *arg_vec;
+ size_t arg_cnt;
- if (ch->waiters.front && ch->waiter_typ != self_typ) { /* non-blocking fast-path */
- /* Copy. */
- struct _cr_chan_waiter_list_node *front = ch->waiters.front;
- if (self_typ == _CR_CHAN_SENDER)
- memcpy(front->val.val_ptr, val_ptr, val_size);
- else
- memcpy(val_ptr, front->val.val_ptr, val_size);
- cr_unpause(front->val.cid);
- front->val.dequeue(front->val.dequeue_arg1,
- front->val.dequeue_arg2);
- cr_yield();
- } else { /* blocking slow-path */
- struct _cr_chan_waiter_list_node self = { .val = {
- .cid = cr_getcid(),
- .val_ptr = val_ptr,
- .dequeue = cr_chan_dequeue,
- .dequeue_arg1 = ch,
- }};
- dlist_push_to_rear(&ch->waiters, &self);
- ch->waiter_typ = self_typ;
- cr_pause_and_yield();
- }
-}
-
-/* select *********************************************************************/
+ size_t ret;
+};
enum cr_select_class {
CR_SELECT_CLASS_DEFAULT,
@@ -65,40 +27,26 @@ enum cr_select_class {
CR_SELECT_CLASS_NONBLOCK,
};
-struct cr_select_waiters {
- size_t cnt;
- struct cr_select_arg *args;
- struct _cr_chan_waiter_list_node *nodes;
-};
-
-static inline enum cr_select_class cr_select_getclass(struct cr_select_arg arg) {
- switch (arg.op) {
+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)
+ if (arg->ch->waiters.front && arg->ch->waiters.front->val.op == _CR_SELECT_OP_SEND)
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)
+ if (arg->ch->waiters.front && arg->ch->waiters.front->val.op == _CR_SELECT_OP_RECV)
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");
+ assert_notreached("invalid arg->op");
}
}
-void cr_select_dequeue(void *_waiters, size_t idx) {
- struct cr_select_waiters *waiters = _waiters;
- for (size_t i = 0; i < waiters->cnt; i++)
- dlist_remove(&(waiters->args[i].ch->waiters),
- &(waiters->nodes[i]));
- waiters->cnt = idx;
-}
-
-size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
+size_t cr_select_v(size_t arg_cnt, struct _cr_select_arg_list_node arg_vec[]) {
size_t cnt_blocking = 0;
size_t cnt_nonblock = 0;
size_t cnt_default = 0;
@@ -108,7 +56,7 @@ size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
cr_assert_in_coroutine();
for (size_t i = 0; i < arg_cnt; i++) {
- switch (cr_select_getclass(arg_vec[i])) {
+ switch (cr_select_getclass(&arg_vec[i].val)) {
case CR_SELECT_CLASS_BLOCKING:
cnt_blocking++;
break;
@@ -122,46 +70,65 @@ size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
}
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;
- }
+ size_t choice_among_nonblock = rand_uint63n(cnt_nonblock);
+ size_t choice_among_all = arg_cnt;
+ for (size_t i = 0, seen = 0; i < choice_among_all; i++) {
+ if (cr_select_getclass(&arg_vec[i].val) == CR_SELECT_CLASS_NONBLOCK) {
+ if (seen == choice_among_nonblock)
+ choice_among_all = i;
seen++;
}
}
- assert_notreached("should have returned from inside for() loop");
+ assert(choice_among_all < arg_cnt);
+
+ struct _cr_select_arg *this = &arg_vec[choice_among_all].val;
+ assert(this->ch->waiters.front);
+ struct _cr_select_arg *other = &this->ch->waiters.front->val;
+ assert(this->val_siz == other->val_siz);
+ assert(this->ch == other->ch);
+ switch (this->op) {
+ case _CR_SELECT_OP_SEND:
+ assert(other->op == _CR_SELECT_OP_RECV);
+ memcpy(other->val_ptr, this->val_ptr, this->val_siz);
+ break;
+ case _CR_SELECT_OP_RECV:
+ assert(other->op == _CR_SELECT_OP_SEND);
+ memcpy(this->val_ptr, other->val_ptr, this->val_siz);
+ break;
+ case _CR_SELECT_OP_DEFAULT:
+ assert_notreached("_CR_SELECT_OP_DEFAULT is not CR_SELECT_CLASS_NONBLOCK");
+ }
+ struct _cr_select_waiter *waiter = other->waiter;
+ for (size_t i = 0; i < waiter->arg_cnt; i++) {
+ waiter->arg_vec[i].val.ch->nwaiters--;
+ dlist_remove(&waiter->arg_vec[i].val.ch->waiters, &waiter->arg_vec[i]);
+ if (&waiter->arg_vec[i].val == other)
+ waiter->ret = i;
+ }
+ cr_unpause(waiter->cid);
+ cr_yield();
+ return choice_among_all;
}
if (cnt_default) {
for (size_t i = 0; i < arg_cnt; i++)
- if (cr_select_getclass(arg_vec[i]) == CR_SELECT_CLASS_DEFAULT)
+ if (cr_select_getclass(&arg_vec[i].val) == 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),
+ assert(cnt_blocking && cnt_blocking == arg_cnt);
+
+ struct _cr_select_waiter waiter = {
+ .cid = cr_getcid(),
+ .arg_vec = arg_vec,
+ .arg_cnt = arg_cnt,
};
for (size_t i = 0; i < arg_cnt; i++) {
- waiters.nodes[i] = (struct _cr_chan_waiter_list_node){ .val = {
- .cid = cr_getcid(),
- .val_ptr = arg_vec[i].val_ptr,
- .dequeue = cr_select_dequeue,
- .dequeue_arg1 = &waiters,
- .dequeue_arg2 = i,
- }};
- dlist_push_to_rear(&arg_vec[i].ch->waiters, &waiters.nodes[i]);
+ arg_vec[i].val.waiter = &waiter;
+ arg_vec[i].val.ch->nwaiters++;
+ dlist_push_to_rear(&arg_vec[i].val.ch->waiters, &arg_vec[i]);
}
cr_pause_and_yield();
- return waiters.cnt;
+ return waiter.ret;
}
diff --git a/libcr_ipc/include/libcr_ipc/chan.h b/libcr_ipc/include/libcr_ipc/chan.h
index 5a87643..853b4ad 100644
--- a/libcr_ipc/include/libcr_ipc/chan.h
+++ b/libcr_ipc/include/libcr_ipc/chan.h
@@ -11,6 +11,7 @@
#include <stddef.h> /* for size_t */
#include <libmisc/linkedlist.h> /* for DLIST_DECLARE() */
+#include <libmisc/private.h>
/* base channels **************************************************************/
@@ -44,11 +45,9 @@
*
* void cr_chan_send(NAME##_t *ch, VAL_T val);
*/
-#define cr_chan_send(CH, VAL) do { \
- cr_assert_in_coroutine(); \
- typeof((CH)->val_typ[0]) _val_lvalue = VAL; \
- _cr_chan_xfer(_CR_CHAN_SENDER, &(CH)->core, \
- &_val_lvalue, sizeof(_val_lvalue)); \
+#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)
/**
@@ -60,12 +59,10 @@
*
* VAL_T cr_chan_recv(NAME##_T ch);
*/
-#define cr_chan_recv(CH) ({ \
- cr_assert_in_coroutine(); \
- typeof((CH)->val_typ[0]) _val_lvalue; \
- _cr_chan_xfer(_CR_CHAN_RECVER, &(CH)->core, \
- &_val_lvalue, sizeof(_val_lvalue)); \
- _val_lvalue; \
+#define cr_chan_recv(CH) ({ \
+ typeof((CH)->val_typ[0]) _val_lvalue; \
+ (void)cr_select_l(CR_SELECT_RECV(CH, &_val_lvalue)); \
+ _val_lvalue; \
})
/**
@@ -78,10 +75,10 @@
*
* bool cr_chan_can_send(NAME##_t *ch);
*/
-#define cr_chan_can_send(CH) ({ \
- cr_assert_in_coroutine(); \
- (bool)((CH)->core.waiters.front && \
- (CH)->core.waiter_typ == _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); \
})
/**
@@ -94,78 +91,101 @@
*
* 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.waiter_typ == _CR_CHAN_SENDER); \
+#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); \
+})
-enum _cr_chan_waiter_typ {
- _CR_CHAN_SENDER,
- _CR_CHAN_RECVER,
-};
-
-DLIST_DECLARE(_cr_chan_waiter_list);
-
+DLIST_DECLARE(_cr_select_arg_list);
struct _cr_chan {
- enum _cr_chan_waiter_typ waiter_typ;
- struct _cr_chan_waiter_list waiters;
+ struct _cr_select_arg_list waiters;
+ size_t nwaiters;
};
-void _cr_chan_xfer(enum _cr_chan_waiter_typ self_typ, struct _cr_chan *ch, void *val_ptr, size_t val_size);
-
/* cr_select arguments ********************************************************/
/**
* Do not populate cr_select_arg yourself; use the
* CR_SELECT_{RECV,SEND,DEFAULT} macros.
*/
-struct cr_select_arg {
+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;
- size_t val_siz;
+ } op;
+ struct _cr_chan *ch;
+ void *val_ptr;
+ 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
-#define CR_SELECT_RECV(CH, VALP) ((struct cr_select_arg){ \
+#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){ \
+#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]), \
-})
+}})
-#define CR_SELECT_DEFAULT ((struct cr_select_arg){ \
- .op = _CR_SELECT_OP_DEFAULT, \
-})
+#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, ...) ******************************************/
-#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])); \
+/**
+ * @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/tests/test_select.c b/libcr_ipc/tests/test_select.c
index f0a71a3..3da1c78 100644
--- a/libcr_ipc/tests/test_select.c
+++ b/libcr_ipc/tests/test_select.c
@@ -53,9 +53,8 @@ COROUTINE cr_consumer(void *) {
test_assert(ret_arg == 0);
send = 890;
- args[0] = CR_SELECT_SEND(&fch, &send);
- args[1] = CR_SELECT_DEFAULT;
- ret_arg = cr_select_v(2, args);
+ ret_arg = cr_select_l(CR_SELECT_SEND(&fch, &send),
+ CR_SELECT_DEFAULT);
test_assert(ret_arg == 1);
cr_end();