Mid-Level IR (MIR)
The Mid-Level IR (MIR) represents the stage in the RunMat compilation pipeline where High-Level IR (HIR) is lowered into a Control-Flow Graph (CFG) of Basic Blocks. While HIR maintains a structure close to the original MATLAB AST (nested loops, if-statements), MIR flattens these into explicit jumps, branch targets, and local variable slots (locals). This representation is used for dataflow analysis, type inference, and as the primary input for bytecode generation.
Lowering HIR to MIR mirrors the approach used by modern compilers such as the Rust compiler. The flattened control-flow graph lets RunMat perform dataflow analysis and type inference, and it serves as the primary input for bytecode generation, enabling static analysis and optimization of runtime branches before execution.
MIR Structure & CFG
A MIR program is organized into a MirAssembly, which contains a collection of MirBody objects indexed by their FunctionId. Each MirBody consists of a list of MirLocal declarations and a graph of BasicBlock nodes.
Basic Blocks and Control Flow
Every BasicBlock contains a sequence of MirStmt and exactly one MirTerminator.
MirStmt: Linear operations like assignments (Assign), multi-assignments (MultiAssign), or expressions evaluated for side effects (Expr).MirTerminator: Dictates the transfer of control out of the block.
The terminator kind makes every exit edge explicit:
pub enum MirTerminatorKind {
Goto(BasicBlockId),
Branch { cond, then_block, else_block },
Switch { discr, cases, otherwise },
For { binding, iterable, body_block, exit_block },
TryCatch { try_block, catch_block, catch_binding },
Return(Vec<MirOperand>),
Await { future, result, resume },
Unreachable,
}MIR Data Entity Mapping
The following diagram bridges the conceptual MATLAB control flow to the internal MIR representation.
MIR Lowering (HIR to MIR)
Lowering is performed by the lower_assembly function, which iterates through HIR functions and utilizes a ControlFlowBuilder to construct the CFG.
ControlFlowBuilder & Continuation Passing
The ControlFlowBuilder handles the conversion of nested HIR structures into flat blocks using a continuation-passing approach. When encountering a branch or an await point, the builder:
- Allocates a
fresh_block()for the continuation. - Lowers the "current" block's terminator to point to the new block.
- Recursively lowers the remaining statements into the continuation block.
MirLocal Slots
MIR replaces HIR BindingId references with MirLocalId slots. Locals are categorized by MirLocalKind:
Parameter: Input arguments to the function.Output: Variables that will be returned.Binding: Standard local variables.Capture: Variables captured from an outer scope (closures).Temporary: Compiler-generated slots for intermediate expression results.
Rvalues and Indexing Plans
MIR expressions are represented as MirRvalue. Unlike HIR expressions, MirRvalue is shallow; its operands are usually MirOperand::Local or MirOperand::Constant.
Indexing Operations
MATLAB indexing is complex (supporting end, :, and logical masks). MIR lowers these into a MirIndexing structure containing MirIndexComponents.
MirIndexPlan: Determines if the access isScalar,Slice, orCell.MirRvalue::Index: Represents a read operation.MirStmtKind::AssignwithMirPlace::Index: Represents a write/mutation operation.
Rvalue Kinds
| Kind | Description |
|---|---|
| Use | Simple move or copy of an operand. |
| Binary / Unary | Arithmetic and logical operations. |
| Call | Function invocation with MirCallee (Static or Dynamic). |
| Aggregate | Construction of Tensors or Cell arrays. |
| ShortCircuit | Short-circuit logical && and || with their own branch evaluation. |
MIR Dataflow Analysis
Once lowered, analyze_assembly computes an AnalysisStore over every MIR body. Its fixed-point engine retains source-addressable program-point facts across each control-flow graph; a final-local compatibility map is not a second analysis product.
Analysis Logic Flow
The diagram below illustrates how the analysis engine processes a MirBody to produce type and shape facts.
Key Analysis Facts
InitFact: Tracks if a local isUnassigned,MaybeAssigned, orDefinitelyAssigned. Used for definite assignment validation.ValueFact: The canonical, dependency-light semantic fact fromrunmat-types; it combines kind, shape, storage, execution, and certainty information without depending on runtime values.SpawnSafetyFact: Analyzes if a closure or function is safe tospawnon a background thread based on its captures and effects.
Verified Native IR boundary
The portable executable product retains MIR, its AnalysisStore, source identities, region contracts, interop requirements, and parallel requirements together. Native compilation starts from that complete product; it does not reparse source or run a second type-inference pass.
runmat-native-codegen lowers the retained program into a target-bound Native IR. The representation uses block parameters and SSA values, preserves shared ValueFact analysis when it is available, and leaves values generic when the compiler cannot prove a fact. Runtime slow paths, suspension points, side effects, source locations, region boundaries, and materializable frame state remain explicit so later native compilation can preserve VM behavior.
Before a Native IR product is accepted, its verifier checks it against the executable manifest and canonical MIR. This includes complete construct coverage, target and runtime-ABI compatibility, cache identity, SSA use ordering, control-flow edge arguments, safepoints, effect ordering, source identity, and exact region guard/live-value state. Distributed-value and collective operations currently produce an explicit capability diagnostic at this boundary; they are not silently omitted or treated as local operations.
The VM compiler also records the bytecode instruction corresponding to each eligible empty-stack MIR boundary. A native executor can therefore materialize the exact live frame and continue in the interpreter after a failed specialization guard or safepoint check. Continuation is admitted only when the mapping exists and all active state has a VM representation; native-only loop iterators, exception handlers, nested mutations, and pending awaits continue through the exact generic-native site instead. Both paths preserve completed effects and never restart the function.
This boundary is compiler infrastructure rather than a new user-selectable execution mode. The VM remains the semantic baseline, and user-facing native execution behavior is documented with the JIT/AOT feature that exposes it.