Please have a look at this little coroutines library ccoro
: http://sam.nipl.net/code/ccoro
I'd appreciate a general code and style review, and your kind comments!
ccoro.h
/*
* ccoro - Coroutines in C
* Sam Watkins, 2009
* this code is public domain
*
* ccoro uses setjmp and longjmp to achieve coroutines in plain C.
*/
#ifndef CCORO_H
#define CCORO_H 1
#ifdef __cplusplus
extern "C" {
#endif
#include <setjmp.h>
#if defined (__GNUC__)
#define noret void __attribute__((noreturn))
#else
#define noret void
#endif
extern int coro_pad;
enum { coro_code_done = -1, coro_code_alloc = -2, coro_code_dead = -3 };
typedef struct coro coro;
typedef void (*coro_func)(coro *caller);
struct coro
{
coro *next;
coro *prev;
jmp_buf j;
};
coro *new_coro(coro_func f);
int yield(coro **c);
int yield_val(coro **c, int val);
#ifdef __cplusplus
}
#endif
#endif
ccoro.c
/*/ 2>/dev/null; exec cc -c -Wall -Wextra -O2 ccoro.c ; exit $? # */
/*
* ccoro - Coroutines in C
* Sam Watkins, 2009
* this code is public domain
*
* ccoro uses setjmp and longjmp to achieve coroutines in plain C,
* non-preemptive threading. It works by allocating each thread some stack
* space on the normal stack. It makes sure that each thread has enough space
* to run using a padding variable, of size 8k by default, which is inserted by
* a function that starts the thread. Simple, huh? It uses some fairly simple
* trickery to create new threads that don't overlap with the other threads.
*
* I don't claim that this code is legal by the book, but it seems to work
* with at gcc, tcc, tendra and lcc on Linux i386 and x86_64, and with mingw
* gcc-4.4 and Visual C++ 98 Express on Windows.
*
* As is, this requires C99. To run with C89, use an enum for coro_pad.
*
* An earlier version was reported not to work with tendra on netbsd.
* I have fixed bugs since then so maybe it works now.
*
* An earlier version did not work with -O2 on some systems, it seems to be
* working now.
*
* An earlier version did not work with current glibc, it gave an error:
* *** longjmp causes uninitialized stack frame ***
* I have avoided this check by using the internal function __libc_longjmp.
*
* TODO use a free-list instead of always allocating new coroutines at the top
*/
#include <stdlib.h>
#include <setjmp.h>
#include <stdio.h>
#include "ccoro.h"
#ifdef __GLIBC__
/* use glibc internal longjmp to bypass fortify checks */
noret __libc_longjmp (jmp_buf buf, int val);
#define longjmp __libc_longjmp
#endif
static void coro_init(void);
static noret new_coro_2(coro_func f, coro *caller);
static noret new_coro_3(coro_func f, coro *caller);
static int yield_val_2(coro *c, int val);
int coro_pad = 8192;
/* enum { coro_pad = 8192 }; */
static int coro_init_done = 0;
static coro main_coro;
static coro *coro_top = &main_coro;
static coro *current_coro = &main_coro;
static coro_func new_coro_f;
void coro_init(void)
{
main_coro.prev = NULL;
main_coro.next = NULL;
coro_init_done = 1;
}
int yield_val_2(coro *c, int val)
{
coro *me = current_coro;
int v = setjmp(me->j);
if (v == 0) /* yield */
longjmp(c->j, val);
else if (v == coro_code_alloc) { /* new - this is top */
coro *caller = current_coro;
current_coro = me;
new_coro_2(new_coro_f, caller);
/* cannot return */
}
/* else returned */
current_coro = me;
return v;
}
int yield_val(coro **c, int val)
{
if (*c) {
int v = yield_val_2(*c, val);
if (v == coro_code_done)
*c = NULL;
return v;
}
return coro_code_dead;
}
int yield(coro **c)
{
return yield_val(c, 1);
}
noret new_coro_3(coro_func f, coro *caller)
{
coro c;
current_coro = &c;
if (coro_top)
coro_top->next = &c;
c.prev = coro_top;
c.next = NULL;
coro_top = &c;
(*f)(caller);
if (current_coro->prev)
current_coro->prev->next = current_coro->next;
if (current_coro->next)
current_coro->next->prev = current_coro->prev;
if (current_coro == coro_top)
coro_top = current_coro->prev;
current_coro = NULL;
longjmp(caller->j, coro_code_done); /* finished */
}
noret new_coro_2(coro_func f, coro *caller)
{
volatile char pad[coro_pad];
pad[0] = pad[coro_pad-1] = 0;
new_coro_3(f, caller);
pad[0] = pad[coro_pad-1] = 0;
/* cannot return */
}
coro *new_coro(coro_func f)
{
int v;
coro *me = current_coro;
coro *yielder;
if (!coro_init_done)
coro_init();
new_coro_f = f;
v = setjmp(current_coro->j);
if (v == 0) { /* new */
if (current_coro == coro_top) {
new_coro_2(f, current_coro);
} else {
longjmp(coro_top->j, coro_code_alloc);
}
/* cannot return */
}
/* else yielded */
yielder = current_coro;
current_coro = me;
return yielder;
}
demo.c
/*/ 2>/dev/null; cc -o demo -Wall -Wextra -O2 demo.c ccoro.o && exec ./demo "$@" ; exit $? # */
/*
* ccoro - Coroutines in C
* Sam Watkins, 2009
* this code is public domain
*
* This demo runs three coroutines:
* count, print factorials, print fibonacci sequence.
*/
#include <stdio.h>
#include "ccoro.h"
int factorial(int n)
{
if (n == 0)
return 1;
return n * factorial(n-1);
}
int fibonacci(int n)
{
if (n == 0 || n == 1)
return n;
return fibonacci(n-1) + fibonacci(n-2);
}
void factorial_coro(coro *caller)
{
int i;
for(i=9; i>=0; --i) {
int j = factorial(i);
printf(" %d %d\n", i, j);
yield(&caller);
}
}
void fibonacci_coro(coro *caller)
{
int i;
for(i=1; i<8; ++i) {
int j = fibonacci(i);
printf(" %d %d\n", i, j);
yield(&caller);
}
}
int main(void)
{
coro *c = new_coro(fibonacci_coro);
coro *c2 = new_coro(factorial_coro);
int i;
for(i=0; i<10; ++i) {
printf("%d\n", i);
yield(&c);
yield(&c2);
}
if (c || c2) {
fprintf(stderr, "the coros did not finish\n");
return 1;
}
return 0;
}
-
4\$\begingroup\$ also, don't forget this one! It is much lighter-weight and less risky. chiark.greenend.org.uk/~sgtatham/coroutines.html \$\endgroup\$Sam Watkins– Sam Watkins2015年02月03日 12:30:06 +00:00Commented Feb 3, 2015 at 12:30
-
\$\begingroup\$ One thing I can suggest to myself, to make the internal function names more meaningful. I prefer to minimize comments, but that means I should use very meaningful identifers so the code can be understood. \$\endgroup\$Sam Watkins– Sam Watkins2015年02月04日 02:40:31 +00:00Commented Feb 4, 2015 at 2:40
1 Answer 1
ccoro.h
#define CCORO_H 1
Random note, but you don't need to define that to 1
, a simple #define CCORO_H
would work fine.
enum { coro_code_done = -1, coro_code_alloc = -2, coro_code_dead = -3 };
I'd make it a little clearer that these are constants. Due to C's general lack of typing or other constructs to make code self-documenting, prefixing constants with k
or using ALL_CAPS
is helpful.
ccoro.c
static noret new_coro_2(coro_func f, coro *caller);
static noret new_coro_3(coro_func f, coro *caller);
static int yield_val_2(coro *c, int val);
I know you mentioned this already, but these function names are meaningless and confusing. Make them more meaningful to make the code clearer.
int coro_pad = 8192;
What does this do? Add a comment to document this constant's purpose.
Also, coro_pad
is a constant, right? Why not use const
to declare it as such?
if (v == 0) /* yield */
longjmp(c->j, val);
else if (v == coro_code_alloc) { /* new - this is top */
coro *caller = current_coro;
current_coro = me;
new_coro_2(new_coro_f, caller);
/* cannot return */
}
Braces, please! If you're going to have an else
clause with braces, put braces around the if
clause, too.
noret new_coro_3(coro_func f, coro *caller)
{ ... }
Honestly, I have no idea what this function is doing. Changing the name would help, as mentioned above, but adding some inline comments would be nice, too!
-
\$\begingroup\$ Thanks, much appreciated.
coro_pad
can potentially be changed by the client before creating any coros, if it will need more stack space per coro, that's why it's not a constant. Other than that, I agree with everything you said and it's very helpful. To be honest I'll have to have a careful look at thatnew_coro_3
myself before I can write good comments for it! \$\endgroup\$Sam Watkins– Sam Watkins2015年02月09日 04:52:27 +00:00Commented Feb 9, 2015 at 4:52