Five EmbedDev logo Five EmbedDev

An Embedded RISC-V Blog

Creating the coroutines runtime infrastructure

A simple coroutine example was presented in “C++20 coroutines, header only, without an OS”. This post describes the runtime used for that example in detail.

Project X

This story is also published on Medium.

Summary of the runtime files

The runtime for this example is a set of include files in include/coro. These files are used:

NOTE: All classes here are designed to not use the heap for allocation. They will allocate all memory from statically declared buffers.

The coroutine task concept

The nop_task class in nop_task.hpp file implements the coroutine task concept.

A coroutine task includes a promise concept with no return values. The important structures in this file arestruct nop_task / struct nop_task::promise_type. This is implemented as described in CPP Reference.

This task structure will be allocated each time a coroutine is called. To avoid heap allocation static memory allocation is used (to be described below). When using a memory constrained platform it is important to understand that the number of coroutines that can be called is restricted by the memory allocated for nop_task::task_heap_.

The relationships between the task classes is shown in the following class diagram:

Task

The awaitable concept

The classes in awaitable_timer.hpp and awaitable_priority.hpp represent asynchronous events that pause the coroutine task until an event occurs.

These classes are designed to be returned from a co_await, this ensures a task can be scheduled to be resumed on a later event.

The awaitable_timer class implements the awaitable concept described in CPP Reference, and also the co_await operator that is overloaded to take the scheduler_delay struct and return awaitable_timer. An additional concept of the scheduler class is being used to manage the coroutine handle and wake up conditions that are used to implement coroutine task pause.

The relationships between the awaitable classes is shown in the following class diagram:

Awaitable

The scheduler class

The classes in scheduler.hpp are designed to do the work of managing the coroutines that are paused. It is a template class, parameterized according to the type of event that should be scheduled, and the maximum number of concurrent active tasks.

The scheduler does the work that would be done by an RTOS or General Purpose OS. It manages a task list of waiting tasks with wake conditions and resumes them on the wake event.

The awaitable classes, introduced above, will insert paused tasks via insert(). The active execution context must call resume() to resume the paused tasks. Each entry in the task list is a schedule_entry structure. The classe are templates specialized by the wake up condition.

This scheduler class is not a concept required by C++ coroutines, but in this example it is needed as there is no operating system scheduler.

The relationships between scheduler classes is shown in the following class diagram:

Software Timer

Using the awaitable and scheduler classes to create a software timer

The awaitable class and scheduler are combined to implement the software timer feature. The following diagram shows how the classes relate.

Software Timer

Walk through of the detailed sequence of suspend and resume

Now the concrete classes have been defined, the sequence to suspend and resume a coroutine class can be show.

It is shown below in 3 stages in relation to the simple timer example.

1. Setup a coroutine, and suspend on the first wait.

Task Sequence

2. Resume and suspend, iterate over several time delays.

Task Sequence

3. Complete iterating and exit coroutines.

Task Sequence

Testing

The runtime has some basic unit testing implemented in test using the unity test framework. The tests are not comprehensive, but run independent of hardware as the runtime is host & OS independent. The tests are compiled for the host OS and run locally.

Summary

The runtime presented in this article is not meant for production usage and has the bare minimal functionality to implement a re-entrant function using a software timer.

However, it does show the potential of C++ coroutines to be applied to real time applications that are portable across different OS and target architectures.

Appendix

References

I won’t explain the details of C++ coroutines, there are much better resources. I used the following to understand coroutines:

A Few implementation details