Coroutine Stack Layout
A coroutine stack layout determines how the coroutine suspends and resumes its frame — segmented stacks grow on demand, pool stacks avoid allocation — trading memory for suspension speed.
Definition
Coroutine stack layout is the memory arrangement for a suspendable computation — a stackful coroutine has its own stack (segmented or pooled), a stackless coroutine stores only the live variables in a compiler-generated frame — and the choice trades memory overhead against the cost of suspend and resume.
Context and scope
Applied in Go goroutines, Lua coroutines, C++20 coroutines, and any runtime where millions of suspendable units must fit in memory.
Common misconceptions
- Stackless coroutines are always smaller — they are smaller for the live variables, but the state machine can exceed a small stackful frame for complex control flow
- Stackful coroutines are always slower — the suspend cost is a register spill either way; the difference is in memory, not speed
Anti-patterns
- Using stackful coroutines with a fixed large stack for millions of idle units, exhausting memory on stack alone
- Mixing stackful and stackless coroutines in the same scheduler and hitting ABI incompatibilities at the yield boundary
Related concepts
Authoritative source
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0912r2.html
What it is
Coroutine stack layout is the memory arrangement for a suspendable computation — a stackful coroutine has its own stack (segmented or pooled), a stackless coroutine stores only the live variables in a compiler-generated frame — and the choice trades memory overhead against the cost of suspend and resume. Applied in Go goroutines, Lua coroutines, C++20 coroutines, and any runtime where millions of suspendable units must fit in memory. The central difficulty is that interleaving is nondeterministic: the set of possible executions grows super-exponentially with the number of operations, and testing samples a vanishing fraction.
How it works
Correctness is established by reasoning about happens-before relationships, not by running more tests. Synchronization primitives are chosen for the specific guarantee needed — mutual exclusion, publication, or ordering — and composed only with a documented proof of absence of deadlock. Bounded resources apply backpressure rather than unbounded queuing that hides overload until memory exhausts.
When to apply and pitfalls
Apply whenever multiple flows share mutable state or resources. The dominant pitfall is reasoning sequentially about concurrent code — “this can’t happen because A finishes before B” — when in fact the scheduler can interleave at any instruction boundary. The second is using a coarse lock for simplicity and turning parallelism back into serialization.