Coroutines in C (3/3)
Tags: c, coroutines, threads
Published: 2021-07-08 07:52:34

All this talk about [coroutines](https://dev.to/rdentato/coroutines-in-c-1-2-45ig) and [*bees*](https://dev.to/rdentato/coroutines-in-c-2-3-h0j) 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](https://github.com/rdentato/bee/blob/main/bee.h) in another window).

When we define a *bee*:

``` C
beedef(iter, int n;)
{
  ... // code to be executed here.
  beereturn;
}
```

what really happens is that:
  - a new type `iter` (a pointer to a structure) is defined.
  - a function `bee_fly_iter()` is defined.

Here is the code above after the preprocessor magic:

``` C
 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 `bee_s` holds the basic information: 
  - which function to call when we want the *bee* to fly
  - which line the *bee* should resume its work

``` C
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:

``` C
#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:

``` C
 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:

``` C
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 `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.