The Core Problem: Stack Overflow via Asymmetric Transfer
In early C++ Coroutines (Coroutines TS), resuming a coroutine meant calling .resume() inside `await_suspend()`. When coroutines synchronously complete in a loop or tail-recurse, every .resume() call pushes a new C++ stack frame without popping the old one, leading to stack overflow.
// ASYMMETRIC TRANSFER (Naive Approach)
void await_suspend(std::coroutine_handle<> h) {
// Calling .resume() pushes a new stack frame.
// If done in a deep loop or recursive chain, stack space explodes!
other_coro_.resume();
}
Stack Frame Accumulation:
[ loop_coroutine$resume ]
└─> [ task::awaiter::await_suspend ]
└─> [ child_coroutine$resume ]
└─> [ final_awaiter::await_suspend ]
└─> [ loop_coroutine$resume ] <-- STACK OVERFLOW!
The Solution: Symmetric Transfer
Symmetric transfer allows `await_suspend()` to return a std::coroutine_handle<> instead of void.
Returning a handle suspends the current coroutine frame, pops the current stack frame, and transfers execution directly to the returned handle via a tail call.
Stack usage remains O(1) regardless of how many synchronous suspension/resumes occur.
// SYMMETRIC TRANSFER (Modern C++20)
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
// Return the handle to transfer control directly.
// The compiler generates a tail-call: pops current stack frame, then resumes target.
return other_coro_;
}
Key Implementations
A. The Awaiter (`task::operator co_await`)
When `co_await child_task;` executes, transfer control directly to the child's handle:
struct task_awaiter {
std::coroutine_handle<promise_type> child_coro_;
bool await_ready() noexcept { return false; }
// Symmetric Transfer: Returns child handle to resume
std::coroutine_handle<> await_suspend(std::coroutine_handle<> awaiting_coro) noexcept {
// 1. Store caller as continuation in child's promise
child_coro_.promise().continuation = awaiting_coro;
// 2. Return child handle -> tail-call into child_coro_
return child_coro_;
}
void await_resume() noexcept {}
};
B. The Final Suspend (`promise_type::final_suspend`)
When a child coroutine finishes at `co_return`, transfer control back to its continuation (the caller):
struct final_awaiter {
bool await_ready() noexcept { return false; }
// Symmetric Transfer: Returns caller's handle to resume
std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> me) noexcept {
// Returns parent handle -> tail-call back to parent coroutine
return me.promise().continuation;
}
void await_resume() noexcept {}
};
struct promise_type {
std::coroutine_handle<> continuation{std::noop_coroutine()};
final_awaiter final_suspend() noexcept { return {}; }
// ...
};
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.