NATIVE MIR: THE LAST HONEST PLACE BEFORE INSTRUCTIONS
Native MIR is deliberately smaller than the source language and deliberately richer than raw machine code. It exists so lowering, optimization and register allocation can argue over explicit facts instead of guessing what an earlier pass meant.
00 one semantic input
The input is Concrete HIR. Generic shape, ownership decisions, overload resolution and language-level meaning are already settled. Native lowering is not permitted to reopen those questions. That boundary is the whole point: once the native pipeline starts, the remaining work is representation, target legality and code quality.
There are old lowering paths in the repository because history is useful. They are differential oracles. If a new Native MIR result disagrees with an established path, that disagreement is evidence worth investigating. It is not permission to keep two production semantics engines forever.
Two compilers that are both “probably right” is not redundancy. It is two places for a bug to hide.
10 blocks and control flow
A Native MIR function is a deterministic set of basic blocks. A block owns an ordered instruction sequence and terminates with explicit control flow. Branch targets are block identities, not source labels smuggled through the pipeline. This makes CFG cleanup, branch simplification and later instruction selection mechanical.
block0:
v0 = arg 0
v1 = const 0
jump block1
block1:
v2 = cmp.lt v1, v0
branch v2, block2, block3
block2:
v1 = add v1, 1
jump block1
block3:
return v1The exact dump syntax is an inspection format, not a language feature. What matters is the invariant: edges are explicit and stable enough to diff.
20 virtual registers
Scalar locals prefer virtual registers. A virtual register has a target-sized machine type and a lifetime that can be measured. This is a major break from lowering strategies that immediately assign every source local a stack address.
Aggregates and address-observable locals are different. If the program can observe an address, or the value cannot legally live in one scalar register class, it becomes an explicit stack object. The distinction is semantic and mechanical, not a heuristic based on variable names.
30 memory effects are visible
Loads, stores, allocator calls, volatile operations and other memory effects stay explicit. O1 can only forward a load or remove a store when alias information and side-effect rules prove the transformation safe. A hidden memory effect would poison every optimization after it.
MACHINE ROOM. If a pass needs to ask “could this instruction secretly touch memory?”, the IR already failed you.
40 calls, intrinsics and relocations
By Native MIR time calls are resolved to the form the target pipeline needs. Intrinsics are explicit operations, not source-name fallbacks. External addresses remain relocation-bearing values until the object writer owns them. That keeps the compiler from confusing “address known eventually” with “address known now”.
50 source spans survive
Source spans are retained because low-level debugging without provenance is stupidly expensive. A bad instruction sequence should be traceable back through Native MIR to the source region that created it. The machine cannot explain your compiler bug for you; give yourself breadcrumbs before you need them.
60 target legalization
Legalization runs before selection so impossible operand forms, fixed-register requirements and width constraints are represented deliberately. I686 and x86_64 have different legal machine shapes. That difference belongs in legalization and selection, not in the language contract.
| Language asks | Compute the operation with defined DByte semantics. |
|---|---|
| Native MIR asks | Represent values, effects and control flow explicitly. |
| Legalizer asks | Can this target perform the operation in this operand form? |
| Selector asks | Which actual instructions encode the legal operation? |
70 next wire
Native MIR makes optimization possible without lying. Continue to O0 and O1, then register allocation.