atoll-codegen-wasm turns canonical MIR into one WebAssembly module per
compiler unit. It also owns the program linker described in
Linking.
Unit discovery
Code generation begins by identifying roots. The current source convention
uses main and functions named entry_*; test infrastructure can provide
additional roots.
For each root, discovery computes a transitive static closure containing the functions needed by that unit. The closure includes ordinary callees and can also include compiler-required helpers such as drop methods, comparisons, or hashing implementations. It excludes generic source bodies after their calls have been rewritten to concrete specializations.
Closure membership is ordered deterministically. Module layout must not depend on hash iteration or parallel worker completion.
Backend boundary
Codegen expects MIR to have already decided:
- concrete value and frame layouts;
- direct versus continuation-aware ABI;
- suspension and resume entry points;
- ownership operations and drop behavior;
- intrinsic or host-operation identity;
- field offsets, discriminants, and storage widths.
If one of these facts is missing, codegen emits a diagnostic. It must not query source syntax and make a replacement language decision.
WasmIR
Canonical MIR lowers to WasmIR before byte encoding:
canonical IrFunction
↓ mechanical lowering
WasmIRFn in SSA form
↓ backend passes
optimized WasmIRFn
↓ register allocation, stack residency, relooper
allocated and structured WasmIRFn
↓ encoder
WebAssembly function bodyWasmIR is intentionally small. Its core invariants are:
- every instruction is self-describing apart from read-only frame layout and encoder import indices;
- every virtual register has a natural Wasm type:
i32,i64,f32,f64, orv128; - non-natural storage widths carry explicit load/store width and signedness information at the boundary;
- conversions are explicit rather than inferred by the encoder;
- instruction effects are classified for optimization and reordering.
Reference-shaped Atoll values are represented as i32 arena-relative offsets.
This is a runtime ABI choice, not permission to treat them as arbitrary raw
integers.
Effects and barriers
WasmIR classifies pure operations, loads, stores, synchronous host calls, and asynchronous barriers. Backend passes use that classification:
- dead-code elimination may remove an unused pure value;
- common-subexpression elimination may reuse a load only while its alias domain remains valid;
- stores and host calls are retained;
- asynchronous barriers cannot be reordered or deleted.
The classification must fully describe optimization safety. A pass should not inspect an opcode name and invent a second effect model.
Verification
The WasmIR verifier checks structural IDs, SSA dominance, operand types, block-parameter arity, jump argument types, barrier ordering, frame-slot ranges, reachability, and function ABI shape.
Debug builds run verification after lowering and backend passes. A verifier failure is a compiler bug and includes an IR dump. Source programs that request an unsupported operation should have produced an ordinary diagnostic before reaching that condition.
The final module is validated with wasmparser. Validator-clean bytes are a
hard success criterion, not an optional test-only property.
Module contents
A unit module contains:
- the unit entry export;
- scheduler continuation and block helper functions;
- mutable globals used for the active arena, task, yield, and related runtime state;
- imports selected by the unit’s actual runtime needs;
- optional debug, cancellation, type-schema, and tracing custom data;
- no independent allocator implementation.
Allocator and arena operations are imported from the atoll_flat_wasm
namespace. Scheduler operations use atoll_scheduler. Selected math
operations use atoll_libm_wasm. Other host domains have their own explicit
namespaces.
Imports are capability surfaces. Sema gates unavailable host operations before codegen, while the emitter declares only the imports required by the selected unit plan where the ABI permits that narrowing.
Suspension ABI
Continuation functions exchange state through frames and scheduler imports. Entry, resume, loop-step, and completion paths are already expressed as ordinary canonical operations before WasmIR encoding.
WasmIR does not rediscover “what kind of continuation is this?” to synthesize hidden behavior. ABI header writes, result tags, caller-continuation packing, and enqueue decisions arrive as explicit instructions from MIR canonicalization.
Arena relocation
Arena references are relative offsets, but codegen still tracks the active arena base used for linear-memory access. Under relocating growth, a host call can change and republish that base. Backend load reuse and frame addressing must therefore respect relocation barriers and reread the base where required.
This behavior connects directly to the runtime’s Arena and Flat contracts.
Options
CodegenOptions controls optimization mode, tracing, dump behavior, debug
sections and safepoints, profiling hooks, per-unit builds, pass selection, and
runtime-sensitive emission choices. Explicit CLI options override compatible
development environment controls.
Changing an effective codegen option may legitimately change module bytes. Holding source, compiler revision, and effective options fixed must produce the same bytes.
Failure policy
Unsupported builtins, incomplete unit closures, missing ABI layouts, invalid MIR shapes, and WasmIR-lowering failures become codegen or IR diagnostics with source attribution when available. The emitter has no silent “emit zero” fallback.
Successful unit bytes then flow either to the ordinary build artifact writer or to Linking for deployment assembly. For the function-level representation rather than module orchestration, read WasmIR Values, Control, and Effects.