top | item 19106796

Coroutines in C (2000)

181 points| wooby | 7 years ago |chiark.greenend.org.uk | reply

59 comments

order
[+] brobdingnagians|7 years ago|reply
A cool library that implements C coroutines, available for both idiomatic C (http://libdill.org/) and in the same style as Go (http://libmill.org/) with line by line translations
[+] saagarjha|7 years ago|reply
For those wondering how it works, libdill keeps track of context between function calls and does some clever stack modification in assembly.
[+] jeremycw|7 years ago|reply
I ran with this idea last year and wrote a proof of concept library that wraps libev using using this concept to allow you to write async C code inline. It's about 250 lines of macro abuse and the resulting code looks pretty funky but it ultimately works. I have a simple echo server example here: https://github.com/jeremycw/zasync/blob/master/echo_server.c
[+] plaguuuuuu|7 years ago|reply
As an OOP dev this kind of horrifies me, even though it works okay (though it's surely not thread-safe?)

Nevertheless, even in C, I'd find it conceptually simpler to understand if the functions were written as pure functions, returning a tuple with (c, state) and passing the state back into the caller. Using non-idiomatic tricks and macros in this way is harder to read and understand.

[+] wahern|7 years ago|reply
Here's a POSIX compliant portable getopt() routine I wrote which uses this style. Is this hard to read? Note that it's several times shorter than every other implementation I've ever seen, and if I do say so myself I think it's eminently readable.

  static int
  u_getopt_r(int argc, char *const argv[], const char *shortopts, struct u_getopt_r *K)
  {
    K->optarg = NULL;
    K->optopt = 0;
  
    GETOPT_ENTER;
  
    while (K->optind < argc) {
      K->cp = argv[K->optind];
  
      if (!K->cp || *(K->cp) != '-' || !strcmp(K->cp, "-")) {
        break;
      } else if (!strcmp(K->cp, "--")) {
        K->optind++;
        break;
      }
  
      for (;;) {
        char *shortopt;
  
        if (!(K->optopt = *++K->cp)) {
          K->optind++;
          break;
        } else if (!(shortopt = strchr(shortopts, K->optopt))) {
          getopt_err(argc, argv, shortopts, K, "illegal option -- %c\n", K->optopt);
          GETOPT_YIELD('?');
        } else if (shortopt[1] != ':') {
          GETOPT_YIELD(K->optopt);
        } else if (K->cp[1]) {
          K->optarg = &K->cp[1];
          K->optind++;
          GETOPT_YIELD(K->optopt);
          break;
        } else if (K->optind + 1 < argc) {
          K->optarg = argv[K->optind + 1];
          K->optind += 2;
          GETOPT_YIELD(K->optopt);
          break;
        } else {
          getopt_err(argc, argv, shortopts, K, "option requires an argument -- %c\n", K->optopt);
          K->optind++;
          GETOPT_YIELD((*shortopts == ':')? ':' : '?');
          break;
        }
      }
    }
  
    GETOPT_LEAVE;
  
    return -1;
  }
  
Here's macro magic, the complexity of which is more than made up for by the readability of the core code:

  #define GETOPT_ENTER \
    do { \
    static const int pc0 = __LINE__; \
    switch (pc0 + K->pc) { \
    case __LINE__: (void)0
  
  #define GETOPT_SAVE_AND_DO(do_statement) \
    do { \
      K->pc = __LINE__ - pc0; \
      do_statement; \
      case __LINE__: (void)0; \
    } while (0)
  
  #define GETOPT_YIELD(rv) \
    GETOPT_SAVE_AND_DO(return (rv))
  
  #define GETOPT_LEAVE \
    GETOPT_SAVE_AND_DO(break); \
    } \
    } while (0)
[+] entelechy|7 years ago|reply
We tried to implement coroutines based on the CoroutineTS here: https://github.com/LoopPerfect/conduit

CoroutineTS uses coroutines that are implemented on the LLVM-IR level, as a result they get optimized away in many cases.

Unfortunately the CoroutineTS has also some design flaws around allocations and ownership

[+] ThJ|7 years ago|reply
I thought this was going to be an article about setjmp() and longjmp(), which can be used to implement this kind of mechanism.
[+] mcwoods|7 years ago|reply
Isn't this just an application of a protothread[1]?

There are implementations that use the GCC goto and label pointer, which then avoid the need for a switch statement.

[1] http://dunkels.com/adam/pt/

[+] srean|7 years ago|reply
Answered here https://news.ycombinator.com/item?id=19109008 by VygmraMGVl

In lining the answer.

> Protothreads is a library implementation of this trick. In fact, the protothreads creator even references the linked site in their explanation of protothreads (very last paragraph)

[+] boomlinde|7 years ago|reply
Rather, the protothreads library is an application of this style of coroutine.
[+] hectorhector|7 years ago|reply
What's the purpose of this code in the original decompressor? Assuming c is an uchar, aren't EOF and 0xFF equal?

  if (c == 0xFF) {
    len = getchar();
    c = getchar();
    while (len--)
    emit(c);
  }
[+] pm215|7 years ago|reply
c should not be a char or unsigned char, because the return type of getchar() is "int". If you put it into a char-width variable then you lose the distinction between EOF (which is -1) and the byte 0xff. Getting the type of 'c' wrong is quite a common bug, because the API makes it an easy mistake to make. In this case, if you look down at the eventual transformed code in the "Evaluation" section you'll see that 'c' is indeed correctly declared with 'int' type.
[+] varjag|7 years ago|reply
getchar() returns an int to accommodate 257 different values: all byte chars + EOF (typically -1).

The code snippet itself is run length encoding with 0xff as an escape.

[+] tonyedgecombe|7 years ago|reply
Decompression, the first byte is the number of repeats and the second is the byte to repeat.
[+] s-macke|7 years ago|reply
I guess one of the applications of Coroutines could be WebAssembly. When you run WebAssembly on a website which needs 100% of the CPU for more than a few ms, you have to use the "setTimeout(fn, 0)" trick to prevent the "this website stopped responding" message. That means you have to store the current state at some point and continue in the function fn (after waiting 0 ms). Languages which support coroutines out of the box would help a lot here.
[+] tangent128|7 years ago|reply
Aren't Web Workers (which can run WASM) supposed to fill that role?
[+] jfries|7 years ago|reply
I really like that trick. It's very powerful. But I wonder what the difference is between this and protothreads?
[+] 0db532a0|7 years ago|reply
You could use thread_local variables these days for re-entrancy instead of the ctx->i idea.
[+] guenthert|7 years ago|reply
Thread-local variables make it thread-safe, but still not re-entrant. That's ok, if you don't need more than one state per thread.
[+] bluesnowmonkey|7 years ago|reply
That page uses very simple HTML. Just paragraphs of text divided into sections with headings. Some code examples laid out side-by-side with a table. Tiny stylesheet. No JS. Loads quickly, easy to read. Warms the cockles of my heart.
[+] sandov|7 years ago|reply
The only issue of these websites is the width: using the full width of the screen is just too much for reading.

If you add {max-width: 60em; margin: auto;} to the body tag it looks much better.

[+] Intermernet|7 years ago|reply
This page is written by the creator of PuTTY. They could probably release this as a straight TXT file and still get the readership they deserve. Simon Tatham know's what he's doing!
[+] airstrike|7 years ago|reply
Well, it was written in 2000. Call me nostalgic, but there's something to be said about how simple the internet was back then.
[+] slezyr|7 years ago|reply
> easy to read.

Not on 27" with browser in full screen.

[+] yydcool|7 years ago|reply
python has this:

  def function():
      for i in range(10):
           yield i