Coroutines in C++: simple but nice discovery!


#1

Hello!
Recently I was looking for a way to move all my logic implementation for a plugin/instrument in a single-separated area of my project.

For this purpose many VST instruments feature a scripting language. The scripting language can be used to build a GUI, to handle the MIDI part of the project and to control the “internals” of the instrument in a nice way.

Many scripting languages often contain an instruction called “wait(time)”.

Now it’s clear that I can’t “wait” in the processBlock callback, unless I want to make the VST to die horribly. What the wait function actually does is to skip executing the rest of the code of the function until the wait is over and then resume the function from where it was interrupted.

A non scripted solution could be to use a different thread (like the message thread), as many applications do, but it would mean that I would be limited somehow in the number of concurrent scripts running (since thread switching have a cost), I would have race concurrency problems and any kind of headache that the threads have. Another option would be to have a kind of scheduler (a very common solution) which I could use to post functions to be executed later in time.
But maybe the code I want to execute can’t be easily wrapped inside a function. Consider this example:

for (int i; i< 100; ++i) {
wait(100);
if (x==true){
//do stuff
wait(1000);
} else
{
//do other stuff
wait(200);
}
//do other stuff
}

(how can I indent with the forum tools?)

Unless wrapping each slice of code into its own function and post them all together in the scheduler, it’s a very complex case scenario to handle, for a such simple code.

Is implementing a scripting language the only way to handle this behaviour? Maybe not.

Welcome Coroutines!
Coroutines are a very common thing in scripted languages like Lua. They are also called “green threads” or “fibers”. They are basically functions that can be interrupted and resumed from the point they were interrupted. In any code that involves a constant loop (like game engines) they are very popular. The tricky part is that coroutines are NOT available in the current version of C++. The good news is that they are being implemented in C++20. But what about now? How can we implement such a delightful thing in the current version of C++? It turns out that the problem is widely known and addressed with a nice trick by “spoiling” the switch statement. A code like this is legit in C++ (and C):

int function(void) {
static int i, state = 0;
switch (state) {
case 0: /* start of function /
for (i = 0; i < 10; i++) {
state = 1; /
so we will come back to “case 1” /
return i;
case 1:; /
resume control straight after the return */
}
}
}

The switch statement acts like a kind of dynamic GOTO and it can jump to different scopes of a function. This is ugly, but useful, because we can move into our code exactly like a scripting engine does. You can wrap the switch statement in a macro and with some cleverness have your custom coroutines. These are stackless coroutines, since the scope of the function is destroyed each time the function exits, the static keyword prevents this, but this also means that concurrent coroutines will share the same variables. To prevent this you can pass the coroutine pointer to a custom object containing its unique data and use the static keyword for data shared by all the running coroutines. These coroutines can return an integer value that you can use to make the coroutine “wait” for a certain amount of time (or samples).

This is the article by Simon Taham on the subject:
https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html

There are many implementations of coroutines in c++, some based on an assembly trick to jump around code. Some are very heavy, since they often save all the function stack in memory which is a big no in the processBlock method. This implementation of coroutines is actually very light.

I just wanted to share this little discovery :slight_smile:


#2

use 3 backticks before and after your snippet to format your code with indentation.


#3

Last I checked, I think that the microsoft compiler has the best implementation of Coroutines at the moment. In theory, the compiler has the knowledge of exactly what variables are on the stack at any given time, it can generate that switch statement for you along with the loading/storing of the state. Many implementations of coroutines just save the whole stack, but Microsoft uses this knowledge to only save what is really necessary so that it doesn’t need to do a whole context switch.

Other compilers/implementations may have caught up in the meantime.

Here is an article with much more information on the theory of co-routines:

https://lewissbaker.github.io/2017/09/25/coroutine-theory


#4

Yes, actually the best implementation of Coroutines has to be implemented at language level, since C++ is a static scoped language (in contrast of dynamic scoped languages like Perl, Lua and other interpreted languages). The Coroutines implementation “breaks” how functions work in C++. In interpreted languages, there’s probably a kind of pointer to a set of bytecoded instructions that can jump around and implementing the logic in a custom way.

Since I’m working on a Mac in XCode, I relied on that little hack for my coroutine implementation since it’s portable on both platforms, Windows and OSX. The microsoft compiler implementation as far as I know is actually the proposed Coroutine TS implementation, but as far as I know it’s unavailable on XCode.

One thing I’ve realized is that I spent a whole month in researching how to achieve this behavior and I was almost convinced than a scripted language was the only option. Then one day I found that the word “coroutine” was the solution to my problem. Sometimes solving a programming dilemma is just a matter of finding the right word to identify it.


#5

For those using/prepared to use boost, there’s this too https://www.boost.org/doc/libs/1_67_0/libs/coroutine2/doc/html/index.html


#6

Seems like Xcode supports it via the -fcoroutines-ts compiler flag:

See here:
https://clang.llvm.org/cxx_status.html


#7

As far as I know the Boost implementation do a lot of work in the background so they may be not suitable for realtime use in the audio thread (also I’m not a fan of that lambda implementation…).

But @fabian do you know how to make coroutines work with the flag in the current version of Xcode? I know that XCode uses Clang, but I can’t understand if the current version of the compiler actually matches the latest version of Clang.
I tried to use the flag but it didn’t work.


#8

Just add the compiler flag in the build settings in Xcode

I just tried this little example program and it worked in Xcode on macOS:

#include <experimental/coroutine>
#include <memory>
#include <iostream>

template<typename T>
struct sync {
    std::shared_ptr<T> value;
    sync(std::shared_ptr<T> p)
    : value(p) {
        std::cout << "Created a sync object" << std::endl;
    }
    sync(const sync &s)
    : value(s.value) {
        std::cout << "Copied a sync object" << std::endl;
    }
    ~sync() {
        std::cout << "Sync gone" << std::endl;
    }
    T get() {
        std::cout << "We got asked for the return value..." << std::endl;
        return *value;
    }
    struct promise_type {
        std::shared_ptr<T> ptr;
        promise_type()
        : ptr(std::make_shared<T>()) {
            std::cout << "Promise created" << std::endl;
        }
        ~promise_type() {
            std::cout << "Promise died" << std::endl;
        }
        auto get_return_object() {
            std::cout << "Send back a sync" << std::endl;
            return ptr;
        }
        auto initial_suspend() {
            std::cout << "Started the coroutine, don't stop now!" << std::endl;
            return std::experimental::suspend_never{};
        }
        auto return_value(T v) {
            std::cout << "Got an answer of " << v << std::endl;
            *ptr = v;
            return std::experimental::suspend_never{};
        }
        auto final_suspend() {
            std::cout << "Finished the coro" << std::endl;
            return std::experimental::suspend_never{};
        }
        void unhandled_exception() {
            std::exit(1);
        }
    };
};

sync<int> answer() {
    std::cout << "Thinking deep thoughts..." << std::endl;
    co_return 42;
}

int main() {
    auto a = answer();
    std::cout << "Got a coroutine, let's get a value" << std::endl;
    auto v = a.get();
    std::cout << "And the coroutine value is: " << v << std::endl;
    return 0;
}

#9

Hello Fabian, I tried using the flag, but I get a linker error, so I suppose I have to select -std=c++2a option in the language dialect options. But I don’t have it, I have all until c++17. If I add it in flags I get an error anyway… Sorry for bothering with my inexperience…!


#10

Maybe I just need to download the most recent LLVM from here:
http://releases.llvm.org/download.html#6.0.0

Trying…


#11

Nevermind. Just updated to the latest version of XCode and works, it has just been added and works! Thank you :slight_smile: