The MIR pipeline turns concrete HIR functions into validated control-flow graphs ready for WebAssembly-oriented canonicalization. It makes suspension, frames, calling conventions, and ownership operations explicit.
This chapter describes orchestration. The MIR node and type specification belongs in MIR.
Build boundary
MIR accepts monomorphic HIR. Generic source bodies remain in the project for tooling but are filtered from lowering; every backend-visible call must target a concrete body or an explicitly supported external/intrinsic operation.
Initial lowering builds:
- functions and basic blocks;
- block parameters and typed locals;
- operations with source origins;
- explicit branch, jump, return, error, call, and suspension terminators;
- facts from which later passes select frames and continuations.
Suspending operations are terminators because control leaves the current activation. Treating them as ordinary calls would allow later operations to move across a scheduler boundary unsafely.
Pipeline shape
The pipeline is not one flat list of local optimizations:
HIR lowering
↓
unit-wide discovery and analysis
↓
declarative per-function pass manifest
↓
unit-wide continuation, frame, ABI, and cleanup work
↓
canonical MIRSome analyses need the complete call graph or can change more than one function. Others are safe to run independently on each function. This distinction governs both parallel execution and incremental invalidation.
Pass manifest
Per-function work is represented by a declarative manifest. A step can be:
| Step | Purpose |
|---|---|
| Managed pass | Runs through PassManager with declared fact reads and writes |
| Diagnostic check | Observes a function and appends diagnostics |
| Direct mutation | Performs pipeline-owned transformation outside the managed trait |
| Liveness refresh | Recomputes liveness after a transformation invalidates it |
| Fixed-point group | Repeats a bounded set of cleanup steps until stable |
The manifest centralizes pass order, skip behavior, tracing, dumps, timings, and failure propagation. A pass should not be invoked from a hidden second order that bypasses those controls.
Facts
Optimization and validation share derived facts such as liveness, dominance,
alias information, ranges, and control-flow properties. PassManager
associates each managed pass with a read/write declaration:
- ensure required facts are available;
- execute the pass;
- invalidate facts affected by the declared write set;
- optionally verify preserved facts in debug builds.
An incomplete write declaration is dangerous because a later pass may consume stale analysis and silently miscompile. A declaration that is too broad is safe but needlessly recomputes facts.
Major responsibilities
Although exact pass order evolves, the pipeline has stable responsibility groups:
Control flow
Canonicalize loops and branches, remove unreachable blocks, simplify jumps, split error paths, and maintain block-parameter arity. CFG mutation must update every edge and invalidate dominance/liveness as required.
Optimization
Fold constants, eliminate dead work, specialize call shapes, inline when policy permits, remove bounds checks proved redundant, and promote eligible aggregates or frame fields. Optimization cannot erase source-observable error, host, ownership, or suspension behavior.
Continuations
Classify suspending functions, split continuations, determine resume and loop entry points, cluster compatible continuation shapes, and deduplicate where safe. Cross-suspension locals become frame-resident according to liveness and layout.
Ownership
Insert explicit Clone and Drop operations, perform RC elision and reuse
analysis, and retain enough metadata for codegen’s allocation and drop glue.
The public build output is post-ownership insertion; consumers must not assume
managed values remain implicit.
ABI and layout
Select direct versus continuation-aware calling forms, finalize frame slots, reuse compatible slots, and canonicalize types and operation shapes for the backend.
Validation
Validation runs at meaningful boundaries, not only after all transformations. Checks include CFG well-formedness, block argument agreement, type/layout consistency, valid continuation shape, ownership invariants, and raw-pointer safety.
Two source-facing pointer diagnostics are especially important:
- a raw pointer cannot remain live across suspension;
- a raw pointer cannot point into relocatable arena storage in a way the model cannot preserve.
These checks belong in MIR because liveness, suspension, and chosen storage are all explicit there.
A failed managed pass sets a fatal pipeline state. Unit-wide work must not continue over a partially transformed function.
Checkpoints
The build API can capture named checkpoints, including:
- before normal optimization;
- after normal optimization;
- after higher-order specialization;
- after ABI selection;
- after canonicalization;
- after final ABI layout.
The CLI’s IR dump commands use these checkpoints to inspect one function or a project without manually editing the pass pipeline. Checkpoints are debug surfaces, not extra semantic stages.
Optimization policy
IrOptions selects the optimization profile, pass-disable set, dump focus,
trace behavior, and timings. -O0, -O1, and -O2 choose different optional
transforms while retaining mandatory lowering, validation, ownership, and ABI
work.
A pass-disable option is a bisect tool. If disabling an optional pass changes program meaning rather than only performance or representation, either the pass is wrong or a mandatory canonicalization was mislabeled optional.
Parallelism
The per-function manifest is a natural parallel map when its project inputs are immutable. Unit-wide passes remain barriers. Any parallel driver must merge diagnostics, new helper functions, facts, and IDs in canonical order, not worker completion order.
Subset builds are useful for unit emission, but their parity must be checked against a whole-project build because cross-function classification and layout can affect the supposedly isolated function.
After canonicalization, WebAssembly lowers MIR to WasmIR and module bytes. The normative contracts are split across Control, Memory, and Validation.