CO_AWAITING COROUTINES

来源:https://blog.panicsoftware.com/co_awaiting-coroutines/

Awaitable

As I have mentioned in the previous posts, the suspend_always and suspend_never are types, that fulfill the Awaitable concept.

We can get the Awaitable object in two ways:

  • Direct creation of Awaitable,
  • Transformation of the object into Awaitable because of await_transform function.

co_await operator and Awaiter

The co_await operator is actually responsible for two things:

  • Forcing compiler to generate some coroutine boilerplate code
  • Creating the Awaiter object.

So first let’s have a look at how is the awaiter object is created. The co_await operator is responsible for the creation of the awaiter object. The co_await operator declaration is looked upon in the awaitable object and if it’s found this co_await operator is executed to obtain awaiter object. Otherwise, if the appropriate function is not found, then awaitable becomes the awaiter.

Whenever compiler encounters occurrence of the co_await operator it generates the more or less following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
std::exception_ptr exception = nullptr;
if (not a.await_ready()) {
__builtin_coro_save(); //suspend_coroutine();

//if await_suspend returns void
try {
a.await_suspend(coroutine_handle);
return_to_the_caller();
} catch (...) {
exception = std::current_exception();
goto resume_point;
}
//endif


//if await_suspend returns bool
bool await_suspend_result;
try {
await_suspend_result = a.await_suspend(coroutine_handle);
} catch (...) {
exception = std::current_exception();
goto resume_point;
}
if (not await_suspend_result)
goto resume_point;
return_to_the_caller();
//endif

//if await_suspend returns another coroutine_handle
decltype(a.await_suspend(std::declval<coro_handle_t>())) another_coro_handle;
try {
another_coro_handle = a.await_suspend(coroutine_handle);
} catch (...) {
exception = std::current_exception();
goto resume_point;
}
another_coro_handle.resume();
return_to_the_caller();
//endif
}

resume_point:
if(exception)
std::rethrow_exception(exception);
"return" a.await_resume();

Coroutine suspension

Immediately after evaluating await_ready expression the coroutine is suspended (on condition, that await_ready evaluated to false). It doesn’t mean, that control flow returns to the caller. It means that the needed local data is saved (possibly on the heap) to be restored when coroutine is suspended.

作者的此番解释,源于他在上述样本代码中误用 suspend 表达了 save 的概念。 suspend = (save something of coroutine) + (return to the caller)

我在上述样本代码中使用 __builtin_coro_save() 替换了 suspend_coroutine(),前者来源于 从HelloWold开始,深入浅出C++ 20 Coroutine TS,这篇帖子里的后续的 __builtin_coro_suspend() // jmp 反而不如此处的 return_to_the_caller() 表意明确。

Summary

When looking into the coroutines you will definitely feel (or should feel) that this is not something you want to write as your daily work and you are right! In the future standard library will come with predefined sets of implemented Awaitables, and coroutine types. Once C++20 comes out, make yourself and your friend the pleasure and use cppcoro instead writing your own implementations.

当查看协程时,您肯定会感觉(或应该感觉到)这不是您日常工作中想要写的东西,而且您是对的!将来,标准库将附带已实现的预定义集合以及已实现的协程类型。一旦C ++ 20发布,让您自己和您的朋友感到高兴,并使用cppcoro代替编写您自己的实现。

那么关键的 __builtin_coro_save()return_to_the_caller() 组合是如何实现功能的呢?作者并没有介绍,系列的三篇文章只是详细地讲解了各个代码定制点以及编译器如何展开并生成样本的代码,却对关键的“如何保存与跳转”只字未提。