24
Coroutines in C (3/3)
All this talk about coroutines and bees is nice but how do they work? What's under the hood?
As mentioned in the previous posts, the implementation is in a single header of less than 70 lines. Let's have a look at it piece-wise (I suggest you open the file from Gituhub in another window).
When we define a bee:
beedef(iter, int n;)
{
... // code to be executed here.
beereturn;
}
what really happens is that:
iter
(a pointer to a structure) is defined.bee_fly_iter()
is defined.Here is the code above after the preprocessor magic:
typedef struct iter_s {
struct bee_s bee_;
int n; // <- This is the preserved var
} *iter;
int bee_fly_iter(iter bee)
{
if (bee == NULL) goto bee_return;
switch(bee->bee_.line) {
default: goto bee_return;
case 0: {
... // code to be executed here.
}
}
bee_return: bee->bee_.line = -1;
return BEE_DONE;
}
Seems a pretty normal looking code, right?
The structure
The structure
bee_s
holds the basic information: typedef struct bee_s {
int32_t line;
int (*fly)();
} *bee_t;
When a bee is created, the value for
line
is set to 0
, so the first time the fly function is called the switch
will execute the code following case 0:
.When yelding, the value for
line
will be set to the current line number and a new case
entry is created as you can see from the beeyeld
definition:#define beeyeld do { \
bee->bee_.line = __LINE__ ; \
return BEE_READY; \
case __LINE__ : ; \
} while(0)
By yelding, the the
fly
function returns to the caller and next time it is called, the switch will start the execution from the case __LINE__:
statement.I believe that comparing the code before and after the macro expansions will clarify things better:
1: #include "bee.h"
2:
3: beedef(iter, int n;)
4: {
5: for (bee->n = 0; bee->n < 10; bee->n++) {
6: beeyeld;
7: }
8: beereturn;
9: }
10:
11: int main(int argc, char *argv[])
12: {
13: iter counter = beenew(iter);
14: while (beefly(counter)) {
15: printf("%d\n",counter->n);
16: }
17: }
becomes:
typedef struct iter_s {
struct bee_s bee_;
int n;
} *iter;
int bee_fly_iter(iter bee)
{
if (bee == ((void *)0)) goto bee_return;
switch(bee->bee_.line) {
default: goto bee_return;
case 0: {
for (bee->n = 0; bee->n < 10; bee->n++) {
do {
bee->bee_.line = 6;
return 1;
case 6: ;
} while(0);
}
}
}
bee_return: bee->bee_.line = -1;
return 0;
}
int main(int argc, char *argv[])
{
iter counter = bee_new(sizeof(struct iter_s), bee_fly_iter);
while (beefly(counter)) {
printf("%d\n",counter->n);
}
}
You may notice that
The Duff's device appears here in the form of a
6
is the line where beeyeld
appears.The Duff's device appears here in the form of a
for
loop nested in a switch
statement. The evil is that when resuming, we'll jump straight into the loop body.Note that for how much convoluted this may seem, there's no undefined beheviour or dependency on a specific compiler involved. Everything is played according the C standard rules.
I believe that once the basic concepts are understood, the rest is pretty simple to get.
As I said, this is just the basic mechanism, more work is needed to use bees within your project. Nevertheless I believe this is a nice tool to have at your disposal and I hope you'll find it useful.
Don't hesitate to ask and provide feedback, I'll be very happy to respond.
24