Stackless Coroutines for LDC — Section Index
This is the hub for the stackless half of the Coroutines for LDC survey: the eight deep-dives that map LLVM's stackless-coroutine machinery, the C++20 design it was built for, how production compilers lower async/await/yield, and what it would take to give D a stackless surface on top. The stackless track is the near-term, portable win — a coroutine is rewritten into an ordinary state machine plus a heap-or-elided frame entirely in the compiler, so it needs no runtime stack-switch and no wasm engine support. For the suspend-from-any-call-depth model (core.thread.Fiber, green threads, WasmFX), see the sibling stackful track; for the shared vocabulary both tracks use, see concepts.
Last reviewed: June 4, 2026
The stackless thesis
Three load-bearing claims frame this entire track. Each is grounded in the local LLVM 23.0.0git, LDC v1.42, and DMD checkouts and developed in the deep-dives below.
(1) The LLVM half is a mature, target-neutral, reusable framework — and LDC already runs it. LLVM 23 ships three lowering ABIs (switched-resume, returned-continuation, async), the full llvm.coro.* intrinsic family (38 generic intrinsics in Intrinsics.td:1875-1967), and the CoroSplit transform — "Converts a coroutine into a state machine" (CoroSplit.cpp:1). Crucially, LDC already runs the Coro* passes in its default pipeline at every optimization level, including -O0: buildO0DefaultPipeline runs MPM.addPass(buildCoroWrapper(Phase)) near its tail (PassBuilderPipelines.cpp:2490), and that wrapper self-activates the moment a module declares any llvm.coro.* intrinsic (CoroConditionalWrapper.cpp:18-23). No new pass plumbing is required — an LDC that merely emits the intrinsics inherits the whole state-machine transform for free. See llvm-coroutines, llvm-internals, and ldc-codegen.
(2) D has no coroutine surface — that is the actual work. D has no coroutine syntax today; the only suspension story is the stackful core.thread.Fiber and the std.concurrency.Generator built on it (see d-fiber). Adding stackless coroutines therefore means designing a D surface and a lowering. The shared DMD frontend already contains every mechanism a lowering needs (loop-body → delegate conversion, the closure/frame-struct machinery, the object._d_* lowering habit), so the question is where the state machine gets built: a portable frontend state machine (the Rust/C#/Kotlin model — lives in the shared frontend, so DMD/GDC/LDC all benefit) or LLVM coro intrinsics in LDC glue (the Swift model — reuses LLVM's mature frame packing and HALO, but is LDC-only). See d-design, comparison, and cpp.
(3) Stackless coroutines compile to plain wasm with zero engine support. The Coro* passes run in the middle-end CGSCC pipeline before WebAssembly instruction selection (PassBuilderPipelines.cpp:480), so a coroutine is fully lowered to an ordinary state machine + a heap/stack frame before the wasm backend ever sees it — which is why the WebAssembly backend has no coroutine or stack-switching intrinsics at all. Stackless coroutines thus compile to plain wasm 1.0 on any conforming engine today. WasmFX stack-switching is the right substrate for stackful fibers — the workloads CoroSplit handles poorly — and is a future, complementary track, not a prerequisite. See wasm and the stackful track.
IMPORTANT
"Compatibility across LLVM releases is not guaranteed." (Coroutines.rst:9-10). The whole llvm.coro.* IR contract is version-unstable; LDC v1.42 pins LLVM 23.0.0git, and any LDC lowering must be re-validated against the linked LLVM — especially the intrinsics with no Coroutines.rst section. See llvm-internals.
Document map
One row per deep-dive in this stackless subtree, in the order the survey builds the argument: the LLVM mechanism, the C++20 template, the cross-language comparison, the D surface, the LDC glue, the attribute/memory story, and the synthesized plan.
| Document | One-line | Link |
|---|---|---|
| LLVM coroutine model | The three ABIs, the llvm.coro.* intrinsic catalog, the worked switched-resume transformation, HALO, exception handling, custom-ABI plugins | llvm-coroutines |
| LLVM coro internals | What the passes do: CoroEarly/CoroSplit/CoroFrame/CoroElide/CoroCleanup, frame discovery & layout, the undocumented intrinsics | llvm-internals |
| C++20 coroutines | The design template: promise + awaiter + handle, the compiler-synthesized body rewrite, symmetric transfer, HALO, the surface → intrinsic bridge tables | cpp |
| Cross-language survey | How Rust, C#, Kotlin, Swift, Python, JS lower async/await/yield; frontend-state-machine vs LLVM-intrinsic; the design lessons for D | comparison |
| D language design | Candidate D surfaces (generator / async / library), frontend lowering precedents (foreach/lazy/scope/_d_*/closureVars), frontend-vs-glue | d-design |
| LDC code generation | Where coro.id/begin/suspend/end would be emitted; pragma(LDC_intrinsic)/inline-IR; the closure-frame analog; the pipeline already runs Coro* | ldc-codegen |
| Attributes & memory | @nogc/@safe/nothrow/pure/scope × a heap frame; the frame-is-an-escaping-closure insight; checkClosure/setGC; custom allocator and HALO | attributes |
| Roadmap | The synthesized, phased plan: library prototype → ABI decision → shared-frontend surface → @nogc/EH → wasm → WasmFX as a future track | roadmap |
NOTE
The roadmap is the synthesis leaf — it consumes every other document in this subtree (plus the stackful track) and turns them into a phased engineering plan. Read it last, or read it first for the executive summary and follow the cross-links back into the evidence.
Reading paths within the stackless track
Pick the entry point that matches what you are after. All paths assume the shared vocabulary in concepts; start there if "ramp", "frame", "suspend point", or "promise/awaiter/handle" are unfamiliar.
- "I want the mechanism — how does a coroutine become a state machine?"llvm-coroutines → llvm-internals. The intrinsic surface a frontend emits, then what
CoroSplit/CoroFramedo with it. - "I want the C++ design template." cpp → llvm-coroutines (§ the construct → intrinsic bridge). The promise + awaiter + handle shape
llvm.coro.*was explicitly built to lower. - "I am designing the D surface." comparison → d-design → cpp. Frontend-state-machine vs LLVM-intrinsic across seven production compilers, then the existing
foreach/lazy/closureVarsmachinery and three candidate syntaxes mapped onto an ABI. - "How would LDC actually emit this?" ldc-codegen → llvm-internals. The glue-layer insertion strategies (library-via-
pragmavs first-class emission) and what the passes expect to see. - "I care about
@nogc/@safe/-betterC." attributes → d-design (§ the allocation choice) → ldc-codegen (§ the_d_allocmemoryframe hook). The GC-frame hard error and the custom-allocator / HALO escape hatches. - "Just give me the plan." → roadmap.
For the wasm angle — plain-wasm stackless today vs WasmFX stack-switching for stackful fibers later — see wasm.
Where this sits in the survey
Coroutines for LDC (umbrella) [index]
├── concepts stackless vs stackful, the shared glossary [concepts]
├── wasm-and-wasmfx plain wasm today; WasmFX for stackful later [wasm]
├── stackless/ ◀── YOU ARE HERE the compiler-built state machine
│ └── 8 deep-dives (mapped above)
└── stackful/ the runtime stack-switch model [stackful-index]
└── d-fiber, context-switching, green-threads, stack-management, wasmfx-as-target- Up to the umbrella index for the cross-track thesis, the full catalog of approaches, and the milestone timeline.
- Across to the stackful track for
core.thread.Fiber, green threads, and WasmFX-as-target — the suspend-from-any-call-depth model stackless coroutines deliberately do not provide. - Sideways to concepts (vocabulary) and wasm (the wasm story both tracks share).
Cross-tree, the parallel algebraic-effects corpus and async-io survey are siblings: the WasmFX deep-dive is the authority on stack-switching, OCaml effects and the D landscape tie coroutine resumption to completion-driven I/O.
Sources
This is a navigational hub; each deep-dive carries its own citations. The artifacts behind the thesis above:
- LLVM 23.0.0git (local,
$REPOS/llvm-project):llvm/docs/Coroutines.rst,llvm/include/llvm/IR/Intrinsics.td,llvm/lib/Transforms/Coroutines/{CoroSplit,CoroConditionalWrapper}.cpp,llvm/lib/Passes/PassBuilderPipelines.cpp,llvm/include/llvm/IR/IntrinsicsWebAssembly.td. - LDC v1.42 (local,
$REPOS/dlang/ldc):runtime/druntime/src/core/thread/fiber/base.d,runtime/phobos/std/concurrency.d,gen/nested.cpp. - The eight stackless deep-dives this index points to, and the cross-track stackful section.