pub struct MirToIR<'a, 'b> { /* private fields */ }Expand description
MIR-to-Cranelift IR compiler.
Each instance compiles a single MIR function. Reuses the JIT’s existing FFI infrastructure (250+ function references) and type mapping.
Implementations§
Source§impl<'a, 'b> MirToIR<'a, 'b>
impl<'a, 'b> MirToIR<'a, 'b>
Sourcepub fn v2_array_get(
&mut self,
arr_ptr: Value,
index: Value,
elem_type: NativeKind,
) -> Value
pub fn v2_array_get( &mut self, arr_ptr: Value, index: Value, elem_type: NativeKind, ) -> Value
Inline typed array element read.
Emits:
- Load
datapointer from[arr_ptr + 8] - Load
len(u32) from[arr_ptr + 16] - Bounds check:
if index >= lenraise an out-of-bounds error (earlyreturn_ofJIT_SIGNAL_INDEX_OUT_OF_BOUNDS— VM/JIT parity) - Compute element address:
data + index * elem_size - Load element with the correct Cranelift type
arr_ptr is a Cranelift i64 value pointing to a TypedArrayHeader.
index is a Cranelift i32 value (unsigned index).
Returns the loaded element value (type depends on elem_type).
Sourcepub fn v2_array_len(&mut self, arr_ptr: Value) -> Value
pub fn v2_array_len(&mut self, arr_ptr: Value) -> Value
Inline typed array length.
Emits a single load i32 [arr_ptr + 16].
Sourcepub fn v2_array_set(
&mut self,
arr_ptr: Value,
index: Value,
val: Value,
elem_type: NativeKind,
)
pub fn v2_array_set( &mut self, arr_ptr: Value, index: Value, val: Value, elem_type: NativeKind, )
Inline typed array element write.
Emits:
- Load
datapointer from[arr_ptr + 8] - Load
len(u32) from[arr_ptr + 16] - Bounds check:
if index >= lenraise an out-of-bounds error (earlyreturn_ofJIT_SIGNAL_INDEX_OUT_OF_BOUNDS— VM/JIT parity) - Compute element address:
data + index * elem_size - Store element with the correct Cranelift type
val must be a Cranelift value whose type matches elem_type.
Source§impl<'a, 'b> MirToIR<'a, 'b>
impl<'a, 'b> MirToIR<'a, 'b>
Sourcepub fn new(
builder: &'a mut FunctionBuilder<'b>,
ctx_ptr: Value,
ffi: FFIFuncRefs,
mir_data: &'a MirFunctionData,
slot_kinds: Vec<Option<NativeKind>>,
strings: &'a [String],
entry_block: Block,
function_indices: &'a HashMap<String, u16>,
user_func_refs: HashMap<u16, FuncRef>,
user_func_arities: HashMap<u16, u16>,
) -> Self
pub fn new( builder: &'a mut FunctionBuilder<'b>, ctx_ptr: Value, ffi: FFIFuncRefs, mir_data: &'a MirFunctionData, slot_kinds: Vec<Option<NativeKind>>, strings: &'a [String], entry_block: Block, function_indices: &'a HashMap<String, u16>, user_func_refs: HashMap<u16, FuncRef>, user_func_arities: HashMap<u16, u16>, ) -> Self
Create a new MIR-to-IR compiler.
entry_block is the Cranelift block already created by the caller
(with function parameters appended). MIR bb0 maps to this block.
Sourcepub fn new_with_concrete_types(
builder: &'a mut FunctionBuilder<'b>,
ctx_ptr: Value,
ffi: FFIFuncRefs,
mir_data: &'a MirFunctionData,
slot_kinds: Vec<Option<NativeKind>>,
concrete_types: Vec<ConcreteType>,
strings: &'a [String],
entry_block: Block,
function_indices: &'a HashMap<String, u16>,
user_func_refs: HashMap<u16, FuncRef>,
user_func_arities: HashMap<u16, u16>,
) -> Self
pub fn new_with_concrete_types( builder: &'a mut FunctionBuilder<'b>, ctx_ptr: Value, ffi: FFIFuncRefs, mir_data: &'a MirFunctionData, slot_kinds: Vec<Option<NativeKind>>, concrete_types: Vec<ConcreteType>, strings: &'a [String], entry_block: Block, function_indices: &'a HashMap<String, u16>, user_func_refs: HashMap<u16, FuncRef>, user_func_arities: HashMap<u16, u16>, ) -> Self
Same as new but also accepts a per-slot ConcreteType vector for
the v2 typed-array fast path. Empty vec → legacy NaN-boxed behaviour.
Sourcepub fn new_with_closure_layouts(
builder: &'a mut FunctionBuilder<'b>,
ctx_ptr: Value,
ffi: FFIFuncRefs,
mir_data: &'a MirFunctionData,
slot_kinds: Vec<Option<NativeKind>>,
concrete_types: Vec<ConcreteType>,
strings: &'a [String],
entry_block: Block,
function_indices: &'a HashMap<String, u16>,
user_func_refs: HashMap<u16, FuncRef>,
user_func_arities: HashMap<u16, u16>,
closure_function_layouts: HashMap<u16, Arc<ClosureLayout>>,
) -> Self
pub fn new_with_closure_layouts( builder: &'a mut FunctionBuilder<'b>, ctx_ptr: Value, ffi: FFIFuncRefs, mir_data: &'a MirFunctionData, slot_kinds: Vec<Option<NativeKind>>, concrete_types: Vec<ConcreteType>, strings: &'a [String], entry_block: Block, function_indices: &'a HashMap<String, u16>, user_func_refs: HashMap<u16, FuncRef>, user_func_arities: HashMap<u16, u16>, closure_function_layouts: HashMap<u16, Arc<ClosureLayout>>, ) -> Self
Closure-spec Phase H1 constructor: also accepts a
function_id → ClosureLayout map so emit_heap_closure can lay out
captures for escaping closures without going through the
jit_make_closure FFI. Passing an empty map degrades gracefully to
the legacy FFI path (same behaviour as new_with_concrete_types).
Sourcepub fn set_operator_trait_dispatch_sites(
&mut self,
sites: HashMap<Span, (String, u16)>,
)
pub fn set_operator_trait_dispatch_sites( &mut self, sites: HashMap<Span, (String, u16)>, )
W10 jit-call-method-user-trait-fix (2026-05-17): install the
bytecode compiler’s operator_trait_dispatch_sites side-table so
compile_rvalue’s Rvalue::BinaryOp / Rvalue::UnaryOp arms can
re-emit user-type operator overloading as a method call. Sibling
of set_monomorph_routing_context — same threading pattern.
Sourcepub fn set_monomorph_routing_context(
&mut self,
sites: HashMap<(Span, Option<usize>), usize>,
caller_function_id: Option<usize>,
)
pub fn set_monomorph_routing_context( &mut self, sites: HashMap<(Span, Option<usize>), usize>, caller_function_id: Option<usize>, )
V3-S6c JIT method-monomorph routing: install the bytecode compiler’s
monomorphized_method_call_sites side-table + the caller function
id used for the (span, caller_function_id) composite key. Callers
normally clone program.monomorphized_method_call_sites and pass
the post-monomorphization func_idx: usize (per-function path) or
None (top-level path). An empty map / None caller is sound —
every Method-call falls through to the existing jit_call_method
trampoline path, preserving V3-S6b baseline behaviour.
Sourcepub fn set_bounds_elision_plan(&mut self, plan: BoundsElisionPlan)
pub fn set_bounds_elision_plan(&mut self, plan: BoundsElisionPlan)
Install a precomputed bounds-elision plan so Place::Index codegen
can skip the inline bounds check on trusted access pairs.
Callers normally invoke bounds_elision::analyze(&mir_data.mir) and
pass the result here. Leaving the plan empty (the default) is
always sound — every access falls back to the bounds-checked path,
matching pre-elision behaviour and preserving the v2_array_tests
OOB zero-default semantics.
Sourcepub fn populate_field_byte_offsets_from_schemas(
&mut self,
registry: &TypeSchemaRegistry,
)
pub fn populate_field_byte_offsets_from_schemas( &mut self, registry: &TypeSchemaRegistry, )
W14.2-E-followup-jit-trait-method-arity-soundness fix (SURFACE-A2,
2026-05-19, v0.3-gating SOUNDNESS BUG): pre-populate
field_byte_offsets from the program’s type_schema_registry for
every field name visible in this function’s MIR field_name_table.
Background. The existing field_byte_offsets map is populated
only by StatementKind::ObjectStore walks at codegen time
(mir_compiler/statements.rs:243). Trait-impl method bodies (and
generally any function that READS fields but does not CONSTRUCT
typed objects) never emit ObjectStore, so the map stays empty
and try_resolve_field_byte_offset returns None. Field reads
then fall through to the jit_get_prop(obj_bits, key_bits) FFI
(places.rs:899-906), whose heap_kind(obj_bits) discriminator
(ffi/value_ffi.rs:331-336) requires is_heap(bits) — i.e.
is_tagged(bits) && get_tag(bits) == TAG_HEAP_BITS. Under ADR-006
§2.7.5 the JIT typed-object allocator (jit_typed_object_alloc at
ffi/typed_object/allocation.rs:83) returns raw Box::into_raw
pointers without NaN-box tag bits, so is_heap always returns
false and jit_get_prop returns TAG_NULL for a TypedObject
receiver — the empirical garbage NaN-bits at the
vm_trait_method_self_field_access_n0 reproducer.
Fix. Use the program-wide schema registry to map each field
name in the MIR to its position in the carrying schema. The JIT
data layout (typed_object_alloc(schema_id, field_count * 8))
uses 8-byte slots per field regardless of declared field type, so
byte_offset = field_index * 8. Same shape as the existing
ObjectStore-walk at statements.rs:243.
Discriminator caveat. This shares the “last-writer-wins on
name collision across distinct struct types” caveat documented at
field_native_kinds’s comment (mod.rs:170-180). For impl method
bodies the receiver is one specific struct type, so the collision
is benign in practice; the principled schema-aware
(StructLayoutId, FieldIdx) → offset registry remains the
long-term shape per that comment’s “out of scope” note. The W12-
jit-binop-after-heap-read-kind-tracker invariant is preserved:
when a function contains both ObjectStore (local-populate at
statements.rs:243) AND field reads on different types, the
schema-pre-pass runs FIRST (here, at MirToIR construction time)
and the local ObjectStore-walk overwrites for the constructed
type — matching the existing single-name single-offset contract.
Per ADR-006 §2.7.5 producer-side stamp: schema field positions
are stamped at AST→bytecode-compile time (the canonical schema
registry); the JIT’s field_byte_offsets is a derived index, not
a runtime decode.
Sourcepub fn register_owned_mutable_capture_slots(
&mut self,
captures_count: u16,
layout: &ClosureLayout,
)
pub fn register_owned_mutable_capture_slots( &mut self, captures_count: u16, layout: &ClosureLayout, )
Track A.1D.2: register the leading capture param slots that back
an OwnedMutable capture cell for the closure body currently being
compiled.
captures_count is the number of leading entries in
MirFunction::param_slots that correspond to closure captures
(the caller ABI stores captures before user params: [ctx_ptr, capture_0..N, user_param_0..M]). layout is the
ClosureLayout for this function’s function_id, so
layout.capture_storage_kind(i) reports the per-capture
CaptureKind. Slots whose kind is OwnedMutable are flagged —
read_place and write_place then emit a pointer-deref load /
store through the raw *mut ValueWord bits, matching the A.1B
interpreter handlers.
Also patches self.slot_kinds for each capture param slot using
the layout’s capture_types[i]. Closure params are untyped at
the bytecode compiler level (see compile_expr_closure in
expressions/closures.rs — capture params are synthesised with
type_annotation: None), so MIR-level inference leaves them
Unknown. Without per-capture kinds the Rvalue::BinaryOp
lowering falls through to the dynamic-binop path, which
unconditionally errors out (see compile_binop at
rvalues.rs::~411). Patching the slot kind here lets the
typed binop pickers (compile_binop_int64, compile_binop_f64,
etc.) engage for x + 1-style closure-body arithmetic. For
OwnedMutable slots, read_place always emits load.i64 through
the cell — the kind informs the binop picker about the inner
value’s representation (NaN-boxed int, NaN-boxed float, etc.),
not the width of the slot itself.
No-op for non-closure functions (captures_count == 0) and for
closures whose layout marks every capture as Immutable. A.1E
extends this registration to populate shared_capture_slots
alongside owned_mutable_capture_slots; both side-tables are
parallel in structure but drive different lowering paths (see
their doc-comments on MirToIR).
Sourcepub fn compile(&mut self) -> Result<(), String>
pub fn compile(&mut self) -> Result<(), String>
Compile the MIR function to Cranelift IR.
Returns Ok(()) on success. The actual return instructions are emitted by compile_terminator for TerminatorKind::Return blocks. Full compilation: create blocks, declare locals, initialize, compile body. Used when the caller hasn’t set up blocks/locals externally.
Sourcepub fn compile_body(&mut self) -> Result<(), String>
pub fn compile_body(&mut self) -> Result<(), String>
Compile the MIR function body (blocks already created, locals already declared).
Called after the caller has optionally stored function params to local variables.
param_count indicates how many leading slots are function params (skip init).
Auto Trait Implementations§
impl<'a, 'b> !Freeze for MirToIR<'a, 'b>
impl<'a, 'b> !RefUnwindSafe for MirToIR<'a, 'b>
impl<'a, 'b> !Sync for MirToIR<'a, 'b>
impl<'a, 'b> !UnwindSafe for MirToIR<'a, 'b>
impl<'a, 'b> Send for MirToIR<'a, 'b>
impl<'a, 'b> Unpin for MirToIR<'a, 'b>
impl<'a, 'b> UnsafeUnpin for MirToIR<'a, 'b>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more