Coroutine TS

A new way of thinking

Meeting C++ 2018

Andreas Reischuck

herb suttermill
herb coroutines
herb wasnotadopted
rebuild logo

Rebuild language project

C++17 Coroutine-TS

C++17 Coroutine-TS

What is a Coroutine?

function
coroutine

yield

What is it for?

Generators

Generators ?

Simple Coroutine

 

auto generate() -> Generator<long> {
  co_yield 23;
  co_yield 42;
}
void test_generate() {
  auto f = generate();
  cout << static_cast<bool>(f) << '\n'; // true
  ++f;
  cout << static_cast<bool>(f) << '\n'; // true
  cout << *f << '\n';                   // 23
  ++f;
  cout << static_cast<bool>(f) << '\n'; // true
  cout << *f << '\n';                   // 42
  ++f;
  cout << static_cast<bool>(f) << '\n'; // false
}

Iota

 

auto iota(long n) -> Generator<long> {
  for (auto i = 0u; i < n; ++i) {
    co_yield i;
}
}

Iota Usage

 

void test_iota() {
  for (auto i : iota(5)) {
    std::cout << i << '\n';
}
}
0
1
2
3
4

Endless

 

auto from(long n) -> Generator<long> {
  while (true) {
    co_yield n++;
}
}

Generator<T> & co_yield

Antique Molder

Real World Use Case

Test Scenario:

Write a parser for source code!

Design

UTF8 Decoder

 

auto utf8Decode(MemoryView)
    -> Generator<Codepoint>;

With Coroutine

auto utf8Decode(MemoryView view)
    -> Generator<CodePoint> {
  while (!view.empty()) {
    /* … */
    co_yield CodePoint{/* … */};
}
}

More Details

auto utf8Decode(MemoryView view) -> Generator<CodePoint> {
  auto take = [&]() -> CodePoint { return *view.begin++; };
  while (!view.empty()) {
    auto c0 = take();
    if ((c0 & 0x80) != 0x80)
      co_yield c0;
    else if ((c0 & 0xE0) == 0xC0) {
      if (view.empty()) co_return;
      auto c1 = take();
      if ((c1 & 0xC0) != 0x80) co_yield {};
      else co_yield { ((c0 & 0x1Fu) << 6) | (c1 & 0x3Fu) };
}
    else { /* same for 3 & 4 bytes */ }
}
}

Tokenizer

auto tokenize(Generator<CodePoint>)
    -> Generator<Token>

Without Coroutine

auto tokenize(Generator<CodePoint> input)
    -> Generator<Token> {
  struct Scanner {
    Generator<CodePoint> input;
    TextPosition position{};
    CodePoint cp{};
    operator bool() const { /* ? */ }
    auto next() -> Token { /* … */ }
}; return Scanner{std::move(input)};
}

As Coroutine

auto tokenize(Generator<CodePoint> input) -> Generator<Token> {
  auto position = TextPosition{};
  auto next = [&] { return (++input) ? *input : CodePoint{}; };
  auto cp = next();
  while (cp) {
    auto token = [begin = position, end = &position](auto data) {
      return Token{TextRange{begin, end}, data};
};
    if (cp == ':') {
      cp = next(); position.column++;
      co_yield token(Colon{});
      continue;
}
    // …
}
}

Summary

State Machines

Elevator

statemachine

Elevator

statemachine with broken

States

struct Idle {};
enum class Move { Up, Down };
struct Broken {};
using State = std::variant<Idle, Move, Broken>;

Events

struct Called { int toFloor; };
struct FloorSensor { int floor; };
struct AlarmPressed {};
using Event =
  std::variant<Called, FloorSensor, AlarmPressed>;

Create the state machine

inline auto create() -> StateMachine<Event, State> {
    int current = 0;
    int target{};
    State state = Idle{};
    while (true) {
        Event event = co_yield state;
        // state * event => next_state
}
}

co_yield expression

// state * event => next_state
auto matchers = overloaded{
    [](auto, AlarmPressed) -> State {
        return Broken{};
},
    [&](Idle, Called c) -> State {
        target = c.toFloor;
        return c.toFloor < current ? Move::Down
                                   : Move::Up;
}
}; state = std::visit(matchers, state, event);

Summary

async tasks

co_await

mcnellis cppcon2016

 

Awaiting Passengers

Coroutines

Performance

 

Compiler Explorer

template<size_t N>
auto fixedSum() {
    auto sum = 0u;
    for (auto v : iota(N + 1)) sum += v;
return sum;
}
auto fixedTest() {
    return fixedSum<100>();
}
fixedTest():
        mov     eax, 5050         ; eax = 5050
        ret                       ; return eax
auto simpleSum(size_t n) {
    auto sum = 0u;
    for (auto v = 0; v <= n; ++v) sum += v;
    return sum;
}
simpleSum(unsigned long):         ; rdi = n
        lea     rax, [rdi - 1]    ; rax = rdi - 1
        imul    rax, rdi          ; rax *= rdi
        shr     rax, 1            ; rax /= 2
        add     edi, eax          ; edi += eax
        mov     eax, edi          ; eax = edi
        ret                       ; return eax
auto dynSum(size_t n) {
    auto sum = 0u;
    for (auto v : iota(n + 1)) sum += v;
    return sum;
}
dynSum(unsigned long):            ; rdi = n
        add     rdi, 1            ; rdi += 1
        sete    cl                ; cl = rdi == 0
        je      .LBB1_1           ; if (rdi == 0) goto LBB1_1
        xor     eax, eax          ; eax = 0
        xor     esi, esi          ; esi = 0
.LBB1_4:
        mov     edx, esi          ; edx = esi
        add     eax, esi          ; eax += esi
        mov     esi, 0            ; esi = 0
        test    cl, 1             ; cl & 1 <=> 0
        mov     ecx, 0            ; ecx = 0
        jne     .LBB1_4           ; if (cl != 0) goto LBB1_4
        add     edx, 1            ; edx += 1
        xor     ecx, ecx          ; ecx = 0
        mov     esi, edx          ; esi = edx
        cmp     rdi, rdx          ; rdi <=> rdx
        ja      .LBB1_4           ; if (rdi > rdx) goto LBB1_4
        ret                       ; return eax
.LBB1_1:
        xor     eax, eax          ; eax = 0
        ret                       ; return eax

Results

Summary

Advantages

Disadvantages

Links

andreas

 

hicknhackLogo new text

 

Work with us…

cppug

 

Give a Talk
& get a Dresden tour

rebuild logo

Rebuild language project

 

Collaborate

Play with more features!

Play with Coroutine!

Photo Credits

co_await question_ready()