20
\$\begingroup\$

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;
}
\$\endgroup\$
  • 3
    \$\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 Feb 3 '15 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 Feb 4 '15 at 2:40
8
\$\begingroup\$

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!

\$\endgroup\$
  • \$\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 that new_coro_3 myself before I can write good comments for it! \$\endgroup\$ – Sam Watkins Feb 9 '15 at 4:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.