Skip to content

Control

WasmIR blocks, SSA edges, function shapes, terminators, and structuring.

Updated View as Markdown

WasmIR retains a CFG long enough for local optimization and verification. It does not force every earlier MIR block to become a WebAssembly function, nor does it require the encoder to recover arbitrary unstructured control flow.

Blocks and parameters

BlockId is a one-based nonzero index into the function’s block vector. A block contains ordered SSA parameters, straight-line instructions, one terminator, and an optional debug label.

Function parameters and block parameters are different:

  • function parameters are defined at function entry and occupy the first virtual registers;
  • block parameters are defined on entry to one block and receive values from predecessor edges;
  • the entry block has no block parameters.

Every jump and branch arm supplies exactly the target parameter count, in order, with matching WasmTys.

Terminators

Terminator Encoding role
Jump Continue at a target with block arguments
Branch Select one of two targets from an I32 condition
Return Return values matching the function result signature
ReturnCall Tail-call a known WebAssembly function index
YieldReturn Bare return after an asynchronous barrier
LoopBackedge Branch to the active structured loop header
Unreachable Emit a trap

YieldReturn is distinct from an empty ordinary return. It proves that runtime handoff instructions intentionally ended the current activation and that the trampoline will re-enter through a continuation.

LoopBackedge names the loop header whose relative structured branch depth will be known during encoding. It is not an arbitrary CFG successor for reachability calculation.

Calls

Calls are instructions rather than CFG terminators because canonical MIR has already expanded suspension into explicit runtime operations and return shapes. A call identifies:

  • zero, one, or multiple result registers;
  • a resolved function or import index;
  • ordered argument registers;
  • HostImport or IntraUnit call kind.

Both kinds conservatively clobber memory for optimization. Async handoff uses dedicated instructions, not a magic call-index list interpreted by the optimizer.

Structuring

The CFG is transformed into encodable WebAssembly constructs after local optimization. Depending on graph shape, a transfer can become:

  • fall-through inside an emitted region;
  • if/else;
  • a branch to an enclosing block or loop;
  • a call or return-call to a hoisted block function.

Hoisted BlockFn functions receive the arena base and frame offset followed by typed live-ins. Their signature therefore records the values that were CFG edges before hoisting.

Structuring must preserve:

  • edge argument/parameter correspondence;
  • virtual-register definitions and dominance;
  • effect and async-barrier order;
  • loop backedge identity;
  • instruction provenance or an explicit synthetic absence;
  • function kind and signature.

It may change physical nesting and choose block functions without changing observable Atoll control flow.

Direct and continuation shapes

A direct function has natural value parameters/results and no frame prefix. A continuation has exactly the five-I32 runtime prefix:

(arena_base, frame_off, handle, tag, value)

A block function has:

(arena_base, frame_off, ...live_ins)

The verifier checks these shapes. An encoder must not add missing parameters based on a continuation kind, because doing so would hide a malformed WasmIR producer.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close