Skip to content

MIR Pipeline

CFG construction, pass facts, ownership insertion, validation, and checkpoints.

Updated View as Markdown

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 MIR

Some 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:

  1. ensure required facts are available;
  2. execute the pass;
  3. invalidate facts affected by the declared write set;
  4. 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.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close