Skip to main content

MirToIR

Struct MirToIR 

Source
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>

Source

pub fn v2_array_get( &mut self, arr_ptr: Value, index: Value, elem_type: NativeKind, ) -> Value

Inline typed array element read.

Emits:

  1. Load data pointer from [arr_ptr + 8]
  2. Load len (u32) from [arr_ptr + 16]
  3. Bounds check: if index >= len raise an out-of-bounds error (early return_ of JIT_SIGNAL_INDEX_OUT_OF_BOUNDS — VM/JIT parity)
  4. Compute element address: data + index * elem_size
  5. 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).

Source

pub fn v2_array_len(&mut self, arr_ptr: Value) -> Value

Inline typed array length.

Emits a single load i32 [arr_ptr + 16].

Source

pub fn v2_array_set( &mut self, arr_ptr: Value, index: Value, val: Value, elem_type: NativeKind, )

Inline typed array element write.

Emits:

  1. Load data pointer from [arr_ptr + 8]
  2. Load len (u32) from [arr_ptr + 16]
  3. Bounds check: if index >= len raise an out-of-bounds error (early return_ of JIT_SIGNAL_INDEX_OUT_OF_BOUNDS — VM/JIT parity)
  4. Compute element address: data + index * elem_size
  5. 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>

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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> Send for MirToIR<'a, 'b>

§

impl<'a, 'b> !Sync for MirToIR<'a, 'b>

§

impl<'a, 'b> Unpin for MirToIR<'a, 'b>

§

impl<'a, 'b> UnsafeUnpin for MirToIR<'a, 'b>

§

impl<'a, 'b> !UnwindSafe for MirToIR<'a, 'b>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts 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
Source§

impl<T> Pointable for T

Source§

const ALIGN: usize

The alignment of pointer.
Source§

type Init = T

The type for initializers.
Source§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
Source§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
Source§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
Source§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more