This commit teaches the std::coroutine_handle pretty-printer to
devirtualize type-erased promise types. This is particularly useful to
resonstruct call stacks, either of asynchronous control flow or of
recursive invocations of std::generator. For the example recently
introduced by https://reviews.llvm.org/D132451, printing the __promise
variable now shows
(std::__coroutine_traits_sfinae<task, void>::promise_type) __promise = { continuation = coro frame = 0x555555562430 { resume = 0x0000555555556310 (a.out`task detail::chain_fn<1>() at llvm-nested-example.cpp:66) destroy = 0x0000555555556700 (a.out`task detail::chain_fn<1>() at llvm-nested-example.cpp:66) promise = { continuation = coro frame = 0x5555555623e0 { resume = 0x0000555555557070 (a.out`task detail::chain_fn<2>() at llvm-nested-example.cpp:66) destroy = 0x0000555555557460 (a.out`task detail::chain_fn<2>() at llvm-nested-example.cpp:66) promise = { ... } } result = 0 } } result = 0 }
(shortened to keep the commit message readable) instead of
(std::__coroutine_traits_sfinae<task, void>::promise_type) __promise = { continuation = coro frame = 0x555555562430 { resume = 0x0000555555556310 (a.out`task detail::chain_fn<1>() at llvm-nested-example.cpp:66) destroy = 0x0000555555556700 (a.out`task detail::chain_fn<1>() at llvm-nested-example.cpp:66) } result = 0 }
Note how the new debug output reveals the complete asynchronous call
stack: our own function resumes chain_fn<1> which in turn will resume
chain_fn<2> and so on. Thereby this change allows users of lldb to
inspect the logical coroutine call stack without using any custom debug
scripts (although the display is still a bit clumsy. It would be nicer
to also integrate this into lldb's backtrace feature, but I don't know
how to do so)
The devirtualization currently works by introspecting the function
pointed to by the destroy pointer. (The resume pointer is not worth
much, given that for the final suspend point resume is set to a
nullptr. We have to use the destroy pointer instead.) We then look
for a __promise variable inside the destroy function. This
__promise variable is synthetically generated by LLVM, and looking at
its type reveals the type-erased promise_type.
This approach only works for clang-generated code, though. While gcc
also adds a _Coro_promise variable to the resume function, it does
not do so for the destroy function. However, we can't use the resume
function, as it will be reset to a nullptr at the final suspension
point. For the time being, I am happy with de-virtualization only working
for clang. A follow-up commit will further improve devirtualization and
also expose the variables spilled to the coroutine frame. As part of
this, I will also revisit gcc support.