
Coroutines in C (2000) - wooby
https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html
======
edsiper2
A really good library that we use is libco[0], it supports different
architectures and mechanisms. We use it extensively on Fluent Bit[1] to manage
async IO network operations (epoll + coroutines).

\- [0] [https://byuu.org/library/libco/](https://byuu.org/library/libco/)

\- [1] [https://fluentbit.io](https://fluentbit.io)

~~~
jimbo1qaz
Do you use
[https://github.com/Tencent/libco](https://github.com/Tencent/libco) or
[https://byuu.org/library/libco/](https://byuu.org/library/libco/) ?

~~~
edsiper2
[https://byuu.org/library/libco/](https://byuu.org/library/libco/)

------
brobdingnagians
A cool library that implements C coroutines, available for both idiomatic C
([http://libdill.org/](http://libdill.org/)) and in the same style as Go
([http://libmill.org/](http://libmill.org/)) with line by line translations

~~~
saagarjha
For those wondering how it works, libdill keeps track of context between
function calls and does some clever stack modification in assembly.

~~~
giovannibajo1
I wonder if it would work from Rust. I was looking at something minimal like
this.

~~~
giornogiovanna
I haven't used it at all, but doesn't Rust have experimental support for
coroutines?

[https://github.com/rust-lang/rust/issues/43122](https://github.com/rust-
lang/rust/issues/43122)

~~~
Matthias247
libdill and co implement stackful coroutines. Each coroutine has a native call
stack which gets swapped out by a userspace scheduler. Rusts proposed
coroutines and async functions are stackless. They are implemented as pure
Compiler Transformations, and don’t allow to yield at arbitrary points.

Rust actually had stackless coroutines in the past (libgreen), but moved from
it due to the overhead it brought for code that didn’t require it.

------
jeremycw
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](https://github.com/jeremycw/zasync/blob/master/echo_server.c)

------
plaguuuuuu
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
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)

------
c-smile
And here is my C++ version of Simon's idea, C++ generators in 10 lines header
file:

[https://www.codeproject.com/Tips/29524/Generators-
in-C](https://www.codeproject.com/Tips/29524/Generators-in-C)

------
entelechy
We tried to implement coroutines based on the CoroutineTS here:
[https://github.com/LoopPerfect/conduit](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
I thought this was going to be an article about setjmp() and longjmp(), which
can be used to implement this kind of mechanism.

------
evancox100
Has anyone used these to write interrupt service routines? Seems like there's
an opportunity here. You can take your existing synchronous code and not have
to rewrite it into something like continuation-passing style. This is a source
of a lot of bugs.

~~~
nraynaud
I have, it's not pretty either: [https://github.com/nraynaud/webgcode/blob/gh-
pages/interpola...](https://github.com/nraynaud/webgcode/blob/gh-
pages/interpolator/main.c#L156)

~~~
convolvatron
i have done cps plumbing in C with poor mans closures for the entire interrupt
path, and given its natural event-like nature, it really all comes out pretty
nice.

~~~
srean
If its possible would love to learn from it. Any chance of taking a look at it
?

~~~
convolvatron
the actual work was done under contract as in proprietary, but the closure
library is the interesting part, is owned by me, and something i can send you.

accountname @gmail.com

if you're interested we can go over the interrupt path, its pretty
straightforward at that point. by flipping the control flow around, the virtio
virtqueue support for example becomes nice and self-contained. it provides an
interrupt continuation to the interrupt layer, and each queue is initialized
with a continuation to call on a dequeue. vector assignment is still a bit
cross-cutting.

~~~
srean
Thanks that is extremely generous of you.

I would still encourage that you put it up somewhere with a license of your
choice. Many would get an opportunity to learn a thing or two.

------
mcwoods
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/](http://dunkels.com/adam/pt/)

~~~
srean
Answered here
[https://news.ycombinator.com/item?id=19109008](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)_

------
hectorhector
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);
      }

~~~
tonyedgecombe
Decompression, the first byte is the number of repeats and the second is the
byte to repeat.

~~~
hectorhector
Ah, thanks!

~~~
stevekemp
Specifically this is known as "Run Length Encoding".

------
s-macke
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
Aren't Web Workers (which can run WASM) supposed to fill that role?

~~~
s-macke
The web worker helps a little bit. You can send data to the UI thread whenever
you want. However on the receiving side you have the same problem. The web
worker only receives data when it is idle. For example if you want to send key
presses to the web worker, the web worker has to idle. The utilization of the
CPU to 100% with simultaneous responsiveness is quite difficult to implement.
Actually you have to play an endless game of message ping pong so that the
message queue is always filled.

------
dang
Several previous discussions:
[https://hn.algolia.com/?query=Coroutines%20in%20C%20points%3...](https://hn.algolia.com/?query=Coroutines%20in%20C%20points%3E40&sort=byDate&dateRange=all&type=story&storyText=false&prefix=false&page=0)

------
jfries
I really like that trick. It's very powerful. But I wonder what the difference
is between this and protothreads?

~~~
VygmraMGVl
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) [1].

[1]
[http://dunkels.com/adam/pt/expansion.html](http://dunkels.com/adam/pt/expansion.html)

------
0db532a0
You could use thread_local variables these days for re-entrancy instead of the
ctx->i idea.

~~~
guenthert
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.

~~~
0db532a0
You’re right. Thanks.

------
bluesnowmonkey
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
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.

~~~
zeveb
Back in the day, we had multiple windows on the screen at one time, so this
wasn't a problem. I _think_ it was Windows which defaulted to full-screen, and
led to the plague of sites assuming that the browser would be full-width on a
landscape screen, but I could be wrong.

Although I love using a tiling window manager now, I kinda miss my old System
8 Macintosh setup, with Netscape open in one window & Think C (or was it
CodeWarrior?) open in another.

~~~
bunderbunder
On a Mac, Spectacle gets you pretty much there.

I haven't found something similar for Windows, but I'm sure there is one.

~~~
npstr
I'm a very happy user of Amethyst, a tiling WM
[https://github.com/ianyh/Amethyst](https://github.com/ianyh/Amethyst) Makes
OSX actually usable.

------
yydcool
python has this:

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

