Skip to content

Commit

Permalink
Updated coroutine.h - added cco_yield_task(), i.e. support for symmet…
Browse files Browse the repository at this point in the history
…ric coroutines.

- Updated docs and examples.
- Made cco_routine again the preferred name for entering coroutine scope (cco_scope will be deprecated).
  • Loading branch information
tylov committed Nov 10, 2024
1 parent a8f0ca4 commit ff18a83
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 297 deletions.
218 changes: 111 additions & 107 deletions docs/coroutine_api.md

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions docs/hmap_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ bool c_memcmp_eq(const i_keyraw* a, const i_keyraw* b); // !memcmp

## Examples

[ [Run this code](https://godbolt.org/z/5v3nT4ser) ]
[ [Run this code](https://godbolt.org/z/zocez8x1c) ]
```c
#include "stc/cstr.h"

Expand All @@ -146,7 +146,8 @@ int main(void)

// Iterate and print keys and values of unordered map
c_foreach (n, hmap_cstr, umap) {
printf("Key:[%s] Value:[%s]\n", cstr_str(&n.ref->first), cstr_str(&n.ref->second));
hmap_cstr_raw v = hmap_cstr_value_toraw(n.ref);
printf("Key:[%s] Value:[%s]\n", v.first, v.second);
}

// Add two new entries to the unordered map
Expand Down Expand Up @@ -253,7 +254,7 @@ static inline void Viking_drop(Viking* vp) {
}
#define i_type Vikings
#define i_keyclass Viking
#define i_keyclass Viking // binds the four Viking_xxxx() functions above
#define i_val int
#include <stc/hmap.h>
Expand Down Expand Up @@ -284,7 +285,7 @@ In example 4 we needed to construct a lookup key which may allocate strings, and
In this example we use keyraw feature to make it simpler to use and avoids the creation of a Viking object
entirely when doing lookup.

[ [Run this code](https://godbolt.org/z/qe86Kxs8W) ]
[ [Run this code](https://godbolt.org/z/18v5vYq4M) ]
```c
#include "stc/cstr.h"

Expand All @@ -308,49 +309,48 @@ Viking Viking_clone(Viking v) {
return v;
}

// Define VikRaw, a Viking lookup struct with eq, hash and convertion functions between them:
typedef struct VikRaw {
// Define Viking_raw, a Viking lookup struct with eq, hash and convertion functions between them:
typedef struct {
const char* name;
const char* country;
} VikRaw;
} Viking_raw;

bool VikRaw_eq(const VikRaw* rx, const VikRaw* ry) {
bool Viking_raw_eq(const Viking_raw* rx, const Viking_raw* ry) {
return strcmp(rx->name, ry->name)==0 && strcmp(rx->country, ry->country)==0;
}

size_t VikRaw_hash(const VikRaw* rv) {
size_t Viking_raw_hash(const Viking_raw* rv) {
return c_hash_mix(c_hash_str(rv->name), c_hash_str(rv->country));
}

Viking Viking_from(VikRaw raw) { // note: parameter is by value
Viking Viking_from(Viking_raw raw) { // note: parameter is by value
Viking v = {cstr_from(raw.name), cstr_from(raw.country)}; return v;
}

VikRaw Viking_toraw(const Viking* vp) {
VikRaw rv = {cstr_str(&vp->name), cstr_str(&vp->country)}; return rv;
Viking_raw Viking_toraw(const Viking* vp) {
Viking_raw rv = {cstr_str(&vp->name), cstr_str(&vp->country)}; return rv;
}

// With this in place, we define the Viking => int hash map type:
#define i_type Vikings
#define i_keyclass Viking // key "class": binds Viking_drop, Viking_clone, Viking_from, Viking_toraw
#define i_rawclass VikRaw // lookup "class": binds VikRaw_cmp, VikRaw_eq, VikRaw_hash
#define i_val int // mapped health
#define i_type Vikings
#define i_keypro Viking // key "class": binds Viking_drop, Viking_clone, Viking_from, Viking_toraw
#define i_val int // mapped health
#include "stc/hmap.h"

int main(void)
{
Vikings vikings = c_init(Vikings, {
{(VikRaw){"Einar", "Norway"}, 25},
{(VikRaw){"Olaf", "Denmark"}, 24},
{(VikRaw){"Harald", "Iceland"}, 12},
{{"Einar", "Norway"}, 25},
{{"Olaf", "Denmark"}, 24},
{{"Harald", "Iceland"}, 12},
});

// Now lookup is using VikRaw, not Viking:
printf("Lookup: Olaf of Denmark has %d hp\n\n", *Vikings_at(&vikings, (VikRaw){"Olaf", "Denmark"}));
// Now lookup is using Viking_raw, not Viking:
printf("Lookup: Olaf of Denmark has %d hp\n\n", *Vikings_at(&vikings, (Viking_raw){"Olaf", "Denmark"}));

c_foreach_kv (viking, health, Vikings, vikings) {
printf("%s of %s has %d hp\n", cstr_str(&viking->name),
cstr_str(&viking->country), *health);
c_foreach (v, Vikings, vikings) {
Vikings_raw r = Vikings_value_toraw(v.ref);
printf("%s of %s has %d hp\n", r.first.name, r.first.country, r.second);
}
Vikings_drop(&vikings);
}
Expand Down
73 changes: 48 additions & 25 deletions include/stc/coroutine.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ struct iterpair {
};
int iterpair(struct iterpair* I) {
cco_scope(I) {
cco_routine (I) {
for (I->x = 0; I->x < I->max_x; I->x++)
for (I->y = 0; I->y < I->max_y; I->y++)
cco_yield;
cco_yield; // suspend
cco_cleanup: // required if there is cleanup code
puts("final");
puts("done");
}
return 0; // CCO_DONE
}
Expand Down Expand Up @@ -78,7 +78,7 @@ typedef struct {
#define cco_done(co) ((co)->cco.state == CCO_STATE_DONE)
#define cco_active(co) ((co)->cco.state != CCO_STATE_DONE)

#define cco_scope(co) \
#define cco_routine(co) \
for (int* _state = &(co)->cco.state; *_state != CCO_STATE_DONE; *_state = CCO_STATE_DONE) \
_resume: switch (*_state) case CCO_STATE_INIT: // thanks, @liigo!

Expand All @@ -87,7 +87,7 @@ typedef struct {
/* fall through */ \
case CCO_STATE_CLEANUP

#define cco_routine cco_scope // [deprecated]
#define cco_scope cco_routine // [deprecated]
#define cco_final cco_cleanup // [deprecated]

#define cco_return \
Expand All @@ -97,9 +97,9 @@ typedef struct {
} while (0)

#define cco_yield cco_yield_v(CCO_YIELD)
#define cco_yield_v(ret) \
#define cco_yield_v(value) \
do { \
*_state = __LINE__; return ret; goto _resume; \
*_state = __LINE__; return value; goto _resume; \
case __LINE__:; \
} while (0)

Expand All @@ -110,12 +110,12 @@ typedef struct {
return value; \
} while (0)

#define cco_await(until) cco_await_with_return(until, CCO_AWAIT)
#define cco_await_with_return(until, ret) \
#define cco_await(until) cco_await_v(until, CCO_AWAIT)
#define cco_await_v(until, value) \
do { \
*_state = __LINE__; \
/* fall through */ \
case __LINE__: if (!(until)) {return ret; goto _resume;} \
case __LINE__: if (!(until)) {return value; goto _resume;} \
} while (0)

/* cco_await_coroutine(): assumes coroutine returns a cco_result value (int) */
Expand All @@ -141,14 +141,19 @@ typedef struct {
*_s = *_s >= CCO_STATE_INIT ? CCO_STATE_CLEANUP : CCO_STATE_DONE; \
} while (0)

#define cco_cancel(co, func) \
do { \
cco_stop(co); func(co); \
} while (0)

#define cco_reset(co) \
(void)((co)->cco.state = 0)

/* ============ ADVANCED, OPTIONAL ============= */

/*
* // Iterators for coroutine generators
*
*
* typedef struct { // The generator
* ...
* } Gen, Gen_value;
Expand All @@ -166,7 +171,7 @@ typedef struct {
* // The iterator coroutine, produce the next value:
* int Gen_next(Gen_iter* it) {
* Gen* g = it->ref;
* cco_scope (it) {
* cco_routine (it) {
* ...
* cco_yield; // suspend exec, gen with value ready
* ...
Expand Down Expand Up @@ -208,7 +213,7 @@ struct cco_runtime;
struct Task; \
typedef struct { \
int (*func)(struct Task*, struct cco_runtime*); \
int state, expect; \
int state, await; \
} Task##_state; \
struct Task

Expand All @@ -223,15 +228,34 @@ typedef struct cco_runtime {
#define cco_cast_task(task) \
((struct cco_task *)(task) + 0*sizeof((task)->cco.func(task, (cco_runtime*)0) + ((int*)0 == &(task)->cco.state)))

#define cco_check_task_struct(name) \
(void)c_static_assert(offsetof(struct name, cco.func) == 0);

#define cco_resume_task(task, rt) \
(task)->cco.func(task, rt)

#define cco_cancel_task(task, rt) \
do { \
cco_task* _t = cco_cast_task(task); \
cco_stop(_t); _t->cco.func(_t, rt); \
} while (0)

#define cco_await_task(...) c_MACRO_OVERLOAD(cco_await_task, __VA_ARGS__)
#define cco_await_task_2(task, rt) cco_await_task_3(task, rt, CCO_DONE)
#define cco_await_task_3(task, rt, awaitbits) \
#define cco_await_task_2(task, rt) cco_await_3(task, rt, CCO_DONE)
#define cco_await_task_3(task, rt, awaitbits) cco_await_task_v(task, rt, awaitbits, CCO_AWAIT)
#define cco_await_task_v(task, rt, awaitbits, value) \
do { \
((rt)->stack[++(rt)->top] = cco_cast_task(task))->cco.await = (awaitbits); \
cco_yield_v(value); \
} while (0)

#define cco_yield_task(...) c_MACRO_OVERLOAD(cco_yield_task, __VA_ARGS__)
#define cco_yield_task_2(task, rt) cco_yield_task_3(task, rt, CCO_DONE)
#define cco_yield_task_3(task, rt, awaitbits) cco_yield_task_v(task, rt, awaitbits, CCO_AWAIT)
#define cco_yield_task_v(task, rt, awaitbits, value) \
do { \
((rt)->stack[++(rt)->top] = cco_cast_task(task))->cco.expect = (awaitbits); \
cco_yield_v(CCO_AWAIT); \
((rt)->stack[(rt)->top] = cco_cast_task(task))->cco.await = (awaitbits); \
cco_yield_v(value); \
} while (0)

#define cco_run_task(...) c_MACRO_OVERLOAD(cco_run_task, __VA_ARGS__)
Expand All @@ -240,7 +264,7 @@ typedef struct cco_runtime {
for (struct { int result, top; struct cco_task* stack[STACKDEPTH]; } \
rt = {.stack = {cco_cast_task(task)}} ; \
(((rt.result = cco_resume_task(rt.stack[rt.top], (cco_runtime*)&rt)) & \
~rt.stack[rt.top]->cco.expect) || --rt.top >= 0) ; )
~rt.stack[rt.top]->cco.await) || --rt.top >= 0) ; )

/*
* Iterate containers with already defined iterator (prefer to use in coroutines only):
Expand All @@ -258,10 +282,10 @@ typedef struct cco_runtime {

typedef struct { ptrdiff_t count; } cco_semaphore;

#define cco_await_semaphore(sem) cco_await_semaphore_with_return(sem, CCO_AWAIT)
#define cco_await_semaphore_with_return(sem, ret) \
#define cco_await_semaphore(sem) cco_await_semaphore_v(sem, CCO_AWAIT)
#define cco_await_semaphore_v(sem, value) \
do { \
cco_await_with_return((sem)->count > 0, ret); \
cco_await_v((sem)->count > 0, value); \
--(sem)->count; \
} while (0)

Expand Down Expand Up @@ -321,12 +345,11 @@ static inline cco_timer cco_timer_make(double sec) {
return tm;
}

#define cco_await_timer(tm, sec) cco_await_timer_with_return(tm, sec, CCO_AWAIT)
#define cco_await_timer_v(...) c_MACRO_OVERLOAD(cco_await_timer_v, __VA_ARGS__)
#define cco_await_timer_with_return(tm, sec, ret) \
#define cco_await_timer(tm, sec) cco_await_timer_v(tm, sec, CCO_AWAIT)
#define cco_await_timer_v(tm, sec, value) \
do { \
cco_timer_start(tm, sec); \
cco_await_with_return(cco_timer_expired(tm), ret); \
cco_await_v(cco_timer_expired(tm), value); \
} while (0)

static inline void cco_timer_start(cco_timer* tm, double sec) {
Expand Down
2 changes: 1 addition & 1 deletion include/stc/cstr.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

#endif // STC_CSTR_H_INCLUDED

#if defined i_implement || defined i_static
#if defined i_implement
#include "priv/cstr_prv.c"
#endif // i_implement

Expand Down
4 changes: 2 additions & 2 deletions misc/examples/coroutines/cointerleave.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct GenValue {

static long get_value(struct GenValue* g)
{
cco_scope(g) {
cco_routine (g) {
cco_foreach (g->it, IVec, *g->v)
cco_yield_v(*g->it.ref);
}
Expand All @@ -28,7 +28,7 @@ struct Generator {

int interleaved(struct Generator* g)
{
cco_scope(g) {
cco_routine (g) {
do {
g->value = get_value(&g->x);
g->xact = cco_active(&g->x);
Expand Down
2 changes: 1 addition & 1 deletion misc/examples/coroutines/coread.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct file_read {

int file_read(struct file_read* g)
{
cco_scope(g) {
cco_routine (g) {
g->fp = fopen(g->filename, "r");
if (g->fp == NULL) cco_return;
g->line = (cstr){0};
Expand Down
8 changes: 4 additions & 4 deletions misc/examples/coroutines/coroutines.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct prime {
enum { YIELD_PRM = 1<<0, YIELD_FIB = 1<<1};

int prime(struct prime* g) {
cco_scope(g) {
cco_routine (g) {
if (g->value < 2)
g->value = 2;
if (g->value == 2) {
Expand Down Expand Up @@ -52,7 +52,7 @@ struct fibonacci {
int fibonacci(struct fibonacci* g) {
assert(g->count < 94);
long long tmp;
cco_scope(g) {
cco_routine (g) {
if (g->value == 0)
g->b = 1;
while (true) {
Expand All @@ -79,7 +79,7 @@ struct combined {
};

int sequenced(struct combined* g) {
cco_scope(g) {
cco_routine (g) {
cco_await_coroutine( prime(&g->prm) );
cco_await_coroutine( fibonacci(&g->fib) );

Expand All @@ -90,7 +90,7 @@ int sequenced(struct combined* g) {
}

int parallel(struct combined* g) {
cco_scope(g) {
cco_routine (g) {
cco_await_coroutine( prime(&g->prm) | fibonacci(&g->fib) );

cco_cleanup:
Expand Down
Loading

0 comments on commit ff18a83

Please sign in to comment.