When is the coroutine frame allocated and freed, and can the allocation be elided?
The frame is allocated (normally on the heap) once when the coroutine is first called, and freed when destroy() runs. The compiler may elide the heap allocation (HALO) when the coroutine's lifetime is fully visible to it.
- ✗Believing the frame is reallocated on each resume rather than once at call
- ✗Thinking the frame frees itself at
co_returninstead of atdestroy() - ✗Assuming HALO always happens rather than only when lifetime is fully visible
- →What conditions let the compiler apply HALO and inline the frame?
- →How can you supply a custom allocator for the coroutine frame?
Frame lifecycle
Task<int> compute() {
int local = 0; // lives in the coroutine frame
co_await something(); // frame is NOT reallocated here
co_return local + 1;
}
void caller() {
Task<int> t = compute(); // <-- frame allocated here (exactly once)
int r = t.get();
} // <-- ~Task() → handle.destroy() → frame freed
Allocation happens once when the coroutine is called — not on every resume(). The frame size is known at compile time, so it is a single fixed-size block. Freeing happens at destroy(), normally invoked by the RAII wrapper (~Task).
Allocation elision (HALO)
When the compiler can prove the coroutine's lifetime is fully nested inside the caller's lifetime (created, used, and destroyed in one scope, everything inlined), it may remove the heap allocation and place the frame on the caller's stack. This is HALO — the Heap Allocation eLision Optimization.
// HALO-friendly: the coroutine does not escape, everything inlines
int sum() {
auto t = compute(); // compiler may place the frame on sum()'s stack
return t.get();
}
HALO is not guaranteed by the standard — it is a quality-of-implementation optimization. If the handle is stored, passed outward, or has a non-obvious lifetime, the allocation stays on the heap.