Skip to main content

shape_jit/compiler/
program.rs

1//! Program compilation with multiple functions
2
3use cranelift::codegen::ir::FuncRef;
4use cranelift::prelude::*;
5use cranelift_module::{Linkage, Module};
6use std::collections::{BTreeMap, HashMap};
7
8use super::setup::JITCompiler;
9use crate::context::{JittedFn, JittedStrategyFn};
10use crate::mixed_table::{FunctionEntry, MixedFunctionTable};
11use crate::numeric_compiler::compile_numeric_program;
12use shape_vm::bytecode::{BytecodeProgram, OpCode};
13
14#[derive(Default)]
15struct NumericOpcodeStats {
16    typed: usize,
17    generic: usize,
18    typed_breakdown: BTreeMap<String, usize>,
19    generic_breakdown: BTreeMap<String, usize>,
20}
21
22fn bump_breakdown(map: &mut BTreeMap<String, usize>, opcode: OpCode) {
23    let key = format!("{:?}", opcode);
24    *map.entry(key).or_insert(0) += 1;
25}
26
27fn collect_numeric_opcode_stats(program: &BytecodeProgram) -> NumericOpcodeStats {
28    let mut stats = NumericOpcodeStats::default();
29    for instr in &program.instructions {
30        match instr.opcode {
31            // Typed arithmetic opcodes
32            OpCode::AddInt
33            | OpCode::SubInt
34            | OpCode::MulInt
35            | OpCode::DivInt
36            | OpCode::ModInt
37            | OpCode::PowInt
38            | OpCode::AddNumber
39            | OpCode::SubNumber
40            | OpCode::MulNumber
41            | OpCode::DivNumber
42            | OpCode::ModNumber
43            | OpCode::PowNumber
44            // Typed comparisons
45            | OpCode::GtInt
46            | OpCode::LtInt
47            | OpCode::GteInt
48            | OpCode::LteInt
49            | OpCode::GtNumber
50            | OpCode::LtNumber
51            | OpCode::GteNumber
52            | OpCode::LteNumber
53            | OpCode::EqInt
54            | OpCode::EqNumber
55            | OpCode::NeqInt
56            | OpCode::NeqNumber
57            | OpCode::EqString
58            | OpCode::GtString
59            | OpCode::LtString
60            | OpCode::GteString
61            | OpCode::LteString
62            | OpCode::EqDecimal
63            | OpCode::IsNull
64            | OpCode::NegInt
65            | OpCode::NegNumber => {
66                stats.typed += 1;
67                bump_breakdown(&mut stats.typed_breakdown, instr.opcode);
68            }
69            // Generic arithmetic/comparison opcodes (DELETED in Phase 2 — left
70            // here as a no-op arm for future-proofing if a generic class is
71            // re-introduced).
72            _ => {}
73        }
74    }
75    stats
76}
77
78fn maybe_emit_numeric_metrics(program: &BytecodeProgram) {
79    // Cluster-2 closure-wave-F tracing-crate migration (2026-05-16):
80    // `tracing::enabled!` collapses to `false` under feature-OFF builds
81    // (`release_max_level_off`), so the early return executes and the
82    // stat-collection work is skipped exactly as before. Replaces the
83    // legacy `SHAPE_JIT_METRICS` / `SHAPE_JIT_METRICS_DETAIL` env-var
84    // gating; CLI selector is `--trace-jit=shape_jit::metrics=info` (the
85    // `_DETAIL` suffix maps to trace level on the same target).
86    if !tracing::enabled!(target: "shape_jit::metrics", tracing::Level::INFO) {
87        return;
88    }
89    let static_stats = collect_numeric_opcode_stats(program);
90    let static_total = static_stats.typed + static_stats.generic;
91    let static_coverage_pct = if static_total == 0 {
92        100.0
93    } else {
94        (static_stats.typed as f64 * 100.0) / (static_total as f64)
95    };
96    // Report effective coverage conservatively: generic opcodes remain generic
97    // unless the frontend/runtime has concretely emitted typed variants.
98    let effective_typed = static_stats.typed;
99    let effective_generic = static_stats.generic;
100    let effective_coverage_pct = static_coverage_pct;
101    tracing::info!(
102        target: "shape_jit::metrics",
103        typed_numeric_ops = effective_typed,
104        generic_numeric_ops = effective_generic,
105        typed_numeric_coverage_pct = effective_coverage_pct,
106        static_typed_numeric_ops = static_stats.typed,
107        static_generic_numeric_ops = static_stats.generic,
108        static_typed_numeric_coverage_pct = static_coverage_pct,
109        "shape-jit-metrics numeric coverage",
110    );
111    if tracing::enabled!(target: "shape_jit::metrics", tracing::Level::TRACE) {
112        let fmt_breakdown = |breakdown: &BTreeMap<String, usize>| -> String {
113            breakdown
114                .iter()
115                .map(|(name, count)| format!("{}:{}", name, count))
116                .collect::<Vec<_>>()
117                .join(",")
118        };
119        tracing::trace!(
120            target: "shape_jit::metrics",
121            typed_breakdown = %fmt_breakdown(&static_stats.typed_breakdown),
122            generic_breakdown = %fmt_breakdown(&static_stats.generic_breakdown),
123            "shape-jit-metrics-detail breakdown",
124        );
125    }
126}
127
128impl JITCompiler {
129    #[inline(always)]
130    pub fn compile(&mut self, name: &str, program: &BytecodeProgram) -> Result<JittedFn, String> {
131        let mut sig = self.module.make_signature();
132        sig.params.push(AbiParam::new(types::I64));
133        sig.params.push(AbiParam::new(types::I64));
134        sig.params.push(AbiParam::new(types::I64));
135        sig.returns.push(AbiParam::new(types::F64));
136
137        let func_id = self
138            .module
139            .declare_function(name, Linkage::Export, &sig)
140            .map_err(|e| format!("Failed to declare function: {}", e))?;
141
142        let mut ctx = self.module.make_context();
143        ctx.func.signature = sig;
144
145        {
146            let mut builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_context);
147            let entry_block = builder.create_block();
148            builder.append_block_params_for_function_params(entry_block);
149            builder.switch_to_block(entry_block);
150            builder.seal_block(entry_block);
151
152            let stack_ptr = builder.block_params(entry_block)[0];
153            let constants_ptr = builder.block_params(entry_block)[1];
154
155            let result = compile_numeric_program(&mut builder, program, stack_ptr, constants_ptr)?;
156
157            builder.ins().return_(&[result]);
158            builder.finalize();
159        }
160
161        self.module
162            .define_function(func_id, &mut ctx)
163            .map_err(|e| format!("Failed to define function: {}", e))?;
164
165        self.module.clear_context(&mut ctx);
166        self.module
167            .finalize_definitions()
168            .map_err(|e| format!("Failed to finalize: {}", e))?;
169
170        let code_ptr = self.module.get_finalized_function(func_id);
171        self.compiled_functions.insert(name.to_string(), code_ptr);
172
173        Ok(unsafe { std::mem::transmute(code_ptr) })
174    }
175
176    #[inline(always)]
177    pub fn compile_program(
178        &mut self,
179        name: &str,
180        program: &BytecodeProgram,
181    ) -> Result<JittedStrategyFn, String> {
182        maybe_emit_numeric_metrics(program);
183
184        let mut user_func_arities: HashMap<u16, u16> = HashMap::new();
185        let mut user_func_ids: HashMap<u16, cranelift_module::FuncId> = HashMap::new();
186
187        for (idx, func) in program.functions.iter().enumerate() {
188            let func_name = format!("{}_{}", name, func.name.replace("::", "__"));
189            let mut user_sig = self.module.make_signature();
190            user_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
191            for _ in 0..func.arity {
192                user_sig.params.push(AbiParam::new(types::I64));
193            }
194            user_sig.returns.push(AbiParam::new(types::I32));
195            let func_id = self
196                .module
197                .declare_function(&func_name, Linkage::Local, &user_sig)
198                .map_err(|e| format!("Failed to pre-declare function {}: {}", func.name, e))?;
199            user_func_ids.insert(idx as u16, func_id);
200            user_func_arities.insert(idx as u16, func.arity);
201        }
202
203        let main_func_id = self.compile_strategy_with_user_funcs(
204            name,
205            program,
206            &user_func_ids,
207            &user_func_arities,
208        )?;
209
210        for (idx, func) in program.functions.iter().enumerate() {
211            let func_name = format!("{}_{}", name, func.name.replace("::", "__"));
212            self.compile_function_with_user_funcs(
213                &func_name,
214                program,
215                idx,
216                &user_func_ids,
217                &user_func_arities,
218            )?;
219        }
220
221        self.module
222            .finalize_definitions()
223            .map_err(|e| format!("Failed to finalize definitions: {:?}", e))?;
224
225        let main_code_ptr = self.module.get_finalized_function(main_func_id);
226        self.compiled_functions
227            .insert(name.to_string(), main_code_ptr);
228
229        self.function_table.clear();
230        for (idx, func) in program.functions.iter().enumerate() {
231            let func_name = format!("{}_{}", name, func.name.replace("::", "__"));
232            if let Some(&func_id) = user_func_ids.get(&(idx as u16)) {
233                let ptr = self.module.get_finalized_function(func_id);
234                while self.function_table.len() <= idx {
235                    self.function_table.push(std::ptr::null());
236                }
237                self.function_table[idx] = ptr;
238                self.compiled_functions.insert(func_name, ptr);
239            }
240        }
241
242        Ok(unsafe { std::mem::transmute(main_code_ptr) })
243    }
244
245    fn compile_function_with_user_funcs(
246        &mut self,
247        name: &str,
248        program: &BytecodeProgram,
249        func_idx: usize,
250        user_func_ids: &HashMap<u16, cranelift_module::FuncId>,
251        user_func_arities: &HashMap<u16, u16>,
252    ) -> Result<(), String> {
253        let func = &program.functions[func_idx];
254        let func_id = *user_func_ids
255            .get(&(func_idx as u16))
256            .ok_or_else(|| format!("Function {} not pre-declared", name))?;
257
258        let mut sig = self.module.make_signature();
259        sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
260        // Closures receive captures as leading native args, followed by user params.
261        let effective_arity = func.captures_count + func.arity;
262        for _ in 0..effective_arity {
263            sig.params.push(AbiParam::new(types::I64));
264        }
265        sig.returns.push(AbiParam::new(types::I32));
266
267        let mut ctx = self.module.make_context();
268        ctx.func.signature = sig;
269
270        let mut func_builder_ctx = FunctionBuilderContext::new();
271        {
272            let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_builder_ctx);
273            let entry_block = builder.create_block();
274            builder.append_block_params_for_function_params(entry_block);
275            builder.switch_to_block(entry_block);
276            builder.seal_block(entry_block);
277
278            let ctx_ptr = builder.block_params(entry_block)[0];
279            let mut user_func_refs: HashMap<u16, FuncRef> = HashMap::new();
280            for (&fn_idx, &fn_id) in user_func_ids {
281                let func_ref = self.module.declare_func_in_func(fn_id, builder.func);
282                user_func_refs.insert(fn_idx, func_ref);
283            }
284
285            let ffi = self.build_ffi_refs(&mut builder)?;
286
287            let func_end = func.entry_point + func.body_length;
288            let sub_instructions = &program.instructions[func.entry_point..func_end];
289            let sub_program = BytecodeProgram {
290                instructions: sub_instructions.to_vec(),
291                constants: program.constants.clone(),
292                strings: program.strings.clone(),
293                // Use empty functions list: the sub_program only contains ONE function's
294                // body, so the original entry points are meaningless in the rebased index
295                // space. This prevents analyze_inline_candidates from using wrong instruction
296                // ranges. Direct calls between functions use user_func_refs instead.
297                functions: Vec::new(),
298                debug_info: Default::default(),
299                data_schema: program.data_schema.clone(),
300                module_binding_names: program.module_binding_names.clone(),
301                top_level_locals_count: program.top_level_locals_count,
302                top_level_local_storage_hints: program
303                    .function_local_storage_hints
304                    .get(func_idx)
305                    .cloned()
306                    .unwrap_or_default(),
307                type_schema_registry: program.type_schema_registry.clone(),
308                module_binding_storage_hints: program.module_binding_storage_hints.clone(),
309                function_local_storage_hints: Vec::new(),
310                top_level_frame: None,
311                top_level_local_concrete_types: Vec::new(),
312                function_local_concrete_types: Vec::new(),
313                function_return_concrete_types: Vec::new(),
314                monomorphized_method_call_sites: Default::default(),
315                value_call_return_concrete_types: Default::default(),
316                operator_trait_dispatch_sites: Default::default(),
317                top_level_mir: None,
318                compiled_annotations: program.compiled_annotations.clone(),
319                trait_method_symbols: program.trait_method_symbols.clone(),
320                expanded_function_defs: program.expanded_function_defs.clone(),
321                string_index: Default::default(),
322                foreign_functions: program.foreign_functions.clone(),
323                native_struct_layouts: program.native_struct_layouts.clone(),
324                content_addressed: None,
325                function_blob_hashes: Vec::new(),
326                monomorphization_keys: Vec::new(),
327                closure_function_layouts: program.closure_function_layouts.clone(),
328                trait_vtables: program.trait_vtables.clone(),
329                has_imported_const_inline: program.has_imported_const_inline,
330                has_w17_marshal_residual: program.has_w17_marshal_residual,
331            };
332
333            // MirToIR is the ONLY JIT compilation path (Phase 4: BytecodeToIR removed).
334            // All functions must have valid MIR data. If not, report the error.
335            let mir_data = func.mir_data.as_ref().ok_or_else(|| {
336                format!("MirToIR: function '{}' has no MIR data (bytecode-only functions are no longer supported)", func.name)
337            })?;
338            let preflight = crate::mir_compiler::preflight(mir_data);
339            if !preflight.can_compile {
340                return Err(format!(
341                    "MirToIR: function '{}' failed preflight: {}",
342                    func.name,
343                    preflight.blockers.join("; ")
344                ));
345            }
346
347            {
348                let slot_kinds: Vec<Option<shape_vm::type_tracking::NativeKind>> = func
349                    .frame_descriptor
350                    .as_ref()
351                    .map(|fd| fd.slots.iter().copied().map(Some).collect())
352                    .unwrap_or_default();
353                // ADR-006 §2.7.5 conduit: thread the bytecode compiler's
354                // proven per-MIR-slot `ConcreteType` for THIS user function
355                // into MirToIR (W12-jit-aggregate-non-array close,
356                // 2026-05-12). The producer
357                // (`infer_top_level_concrete_types_from_mir`) was already
358                // landed for top-level code by Round 3; its body is generic
359                // over any MirFunction, and Round 5B extends the populate
360                // site to per-user-function MIR via
361                // `BytecodeProgram.function_local_concrete_types`. The
362                // top-level conduit's user-visible benefit (Smoke 3
363                // `Point{}` literal short-circuit) now extends to user
364                // function bodies (`Ok(v)`/`Err(e)`/`Some(x)` inside
365                // `divide` / `first_positive` / 28 stdlib helpers).
366                //
367                // Empty inner vec (function has no MIR data, or the conduit
368                // couldn't prove a particular slot) → MirToIR's v2 fast
369                // path falls through to the legacy NaN-boxed path / surfaces
370                // honestly per ADR-006 §2.7.5.1 (no Bool-default).
371                let concrete_types: Vec<shape_value::v2::ConcreteType> =
372                    program
373                        .function_local_concrete_types
374                        .get(func_idx)
375                        .cloned()
376                        .unwrap_or_default();
377                // Build function name → index map for Call terminator resolution.
378                // Use the original program's functions (sub_program has empty functions list).
379                let function_indices: std::collections::HashMap<String, u16> = program
380                    .functions
381                    .iter()
382                    .enumerate()
383                    .map(|(i, f)| (f.name.clone(), i as u16))
384                    .collect();
385                // Closure-spec Phase H1: thread the per-function
386                // ClosureLayout map into MirToIR so `emit_heap_closure`
387                // can lay out captures at natural-width offsets without
388                // going through the legacy `jit_make_closure` FFI.
389                let closure_function_layouts: std::collections::HashMap<
390                    u16,
391                    std::sync::Arc<shape_value::v2::closure_layout::ClosureLayout>,
392                > = program
393                    .closure_function_layouts
394                    .iter()
395                    .enumerate()
396                    .filter_map(|(i, opt)| opt.as_ref().map(|l| (i as u16, l.clone())))
397                    .collect();
398                let mut mir_compiler = crate::mir_compiler::MirToIR::new_with_closure_layouts(
399                    &mut builder,
400                    ctx_ptr,
401                    ffi,
402                    mir_data,
403                    slot_kinds,
404                    concrete_types,
405                    &sub_program.strings,
406                    entry_block,
407                    &function_indices,
408                    user_func_refs.clone(),
409                    user_func_arities.clone(),
410                    closure_function_layouts,
411                );
412                // V3-S6c-jit-method-monomorph-routing (ADR-006 §2.7.5
413                // stamp-at-compile-time; supervisor 2026-05-15 PATH α-prime
414                // RATIFIED): thread the V3-S6b side-table from the ORIGINAL
415                // `program: &BytecodeProgram` (the `sub_program` above
416                // clears it at line ~305 to keep the per-function compile
417                // scope minimal) so the Call-terminator pass can re-route
418                // `MirConstant::Method` sites to direct FuncRef calls.
419                //
420                // Composite key `(call_site_span, caller_function_id)`:
421                // `caller_function_id = Some(func_idx)` matches the
422                // bytecode compiler's `self.current_function` at
423                // specialization time (`expressions/function_calls.rs:3278`).
424                mir_compiler.set_monomorph_routing_context(
425                    program.monomorphized_method_call_sites.clone(),
426                    Some(func_idx),
427                );
428                // W10 jit-call-method-user-trait-fix (2026-05-17): install
429                // the bytecode compiler's operator-trait-dispatch side-
430                // table so the per-user-function MirToIR consumer can
431                // re-emit `Rvalue::BinaryOp` / `Rvalue::UnaryOp` at
432                // trait-dispatch spans as method-call IR.
433                mir_compiler.set_operator_trait_dispatch_sites(
434                    program.operator_trait_dispatch_sites.clone(),
435                );
436                // Bounds-check elision: install the per-function plan
437                // before MIR codegen so `Place::Index` lowering can
438                // bypass the inline bounds check on trusted (arr, iv)
439                // pairs. Default empty plan keeps every access checked.
440                let elision_plan =
441                    crate::mir_compiler::bounds_elision::analyze(&mir_data.mir);
442                mir_compiler.set_bounds_elision_plan(elision_plan);
443                // W14.2-E-followup SURFACE-A2 fix (2026-05-19, v0.3-gating
444                // SOUNDNESS BUG per supervisor ratify): pre-populate
445                // `field_byte_offsets` from the program's
446                // `type_schema_registry` so trait-impl method bodies (and
447                // any function that reads typed-object fields without
448                // emitting a local `ObjectStore`) can resolve field byte
449                // offsets at JIT compile time. Without this pre-pass,
450                // `try_resolve_field_byte_offset` returns `None` for impl
451                // bodies and `Place::Field` falls through to
452                // `jit_get_prop`, whose `heap_kind(obj_bits)` predicate
453                // returns `None` under ADR-006 §2.7.5 raw `Box::into_raw`
454                // typed-object carriers — empirically returning `TAG_NULL`
455                // for every `self.field` read (`vm_trait_method_self_field
456                // _access_n0` reproducer's garbage NaN-bits root cause).
457                //
458                // Per ADR-006 §2.7.5 producer-side stamp: schema field
459                // positions are stamped at AST→bytecode-compile time in
460                // the canonical schema registry. The JIT consumes the
461                // stamp through `populate_field_byte_offsets_from_schemas`
462                // — a derived index, not a runtime decode.
463                mir_compiler
464                    .populate_field_byte_offsets_from_schemas(&program.type_schema_registry);
465                // Track A.1D.2: flag the leading capture param slots whose
466                // `ClosureLayout` marks them as `OwnedMutable`. `read_place`
467                // / `write_place` then route through the cell pointer bits
468                // stored in those slots, matching the interpreter's
469                // `Load/StoreOwnedMutableCapture` handlers. The lookup is
470                // keyed on this function's own `func_idx`, which doubles as
471                // the closure body's `function_id` when it is a closure.
472                // Non-closure functions hit no entry in the layout map →
473                // the side-table stays empty, preserving pre-A.1D.2
474                // behaviour for ordinary functions.
475                if func.is_closure && func.captures_count > 0 {
476                    if let Some(layout) = program
477                        .closure_function_layouts
478                        .get(func_idx)
479                        .and_then(|o| o.as_ref())
480                    {
481                        mir_compiler.register_owned_mutable_capture_slots(
482                            func.captures_count,
483                            layout.as_ref(),
484                        );
485                    }
486                }
487                // Set up blocks and locals, then store function parameters.
488                mir_compiler.create_blocks();
489                mir_compiler.declare_locals();
490
491                // Store function parameters to MIR local variables.
492                // MIR slot layout: [return_slot(0), param0(1), param1(2), ..., locals...]
493                // Entry block params: [ctx_ptr, capture0..N, param0..N]
494                // Use mir.param_slots to map params to their actual MIR slots.
495                let entry_params = mir_compiler.builder.block_params(entry_block).to_vec();
496                let param_slots = &mir_data.mir.param_slots;
497
498                // Initialize ALL locals with type-appropriate defaults.
499                mir_compiler.initialize_locals();
500
501                // Session 1 Commit 3: allocate Arc<SharedCell>s for
502                // every SharedCow local slot (outer-scope `var` bindings
503                // that escape into closures). After this call every
504                // SharedCow slot's Cranelift var holds the raw
505                // `*const SharedCell` pointer bits; subsequent
506                // read_place / write_place route through the lock-gated
507                // pointer-deref lowering, and `emit_drop` on the slot
508                // emits `jit_arc_shared_release` to balance the share.
509                mir_compiler.initialize_shared_local_slots();
510
511                // Store function parameters (including captures) to MIR local variables.
512                // MIR param_slots includes capture slots followed by user param slots.
513                // Entry block params: [ctx_ptr, capture0..N, param0..M]
514                // param_slots aligns 1:1 with captures+params, so native_idx = param_idx + 1.
515                //
516                // R4.2E: callee ABI delivers params as uniform I64 bit-patterns.
517                // When the MIR slot is a native narrow type, reduce I64 → narrow
518                // inline (bitcast for F64, ireduce for I32/I16/I8). No NaN-box
519                // tag stripping — raw bit-patterns only.
520                for (param_idx, &mir_slot) in param_slots.iter().enumerate() {
521                    let native_idx = param_idx + 1; // +1 for ctx_ptr
522                    if native_idx < entry_params.len() {
523                        if let Some(&var) = mir_compiler.locals.get(&mir_slot) {
524                            let kind = crate::mir_compiler::types::slot_kind_for_local(
525                                &mir_compiler.slot_kinds,
526                                mir_slot.0,
527                            );
528                            let param_val = entry_params[native_idx];
529                            let converted = match kind {
530                                Some(shape_vm::type_tracking::NativeKind::Float64) => mir_compiler
531                                    .builder
532                                    .ins()
533                                    .bitcast(types::F64, MemFlags::new(), param_val),
534                                Some(shape_vm::type_tracking::NativeKind::Int32)
535                                | Some(shape_vm::type_tracking::NativeKind::UInt32) => {
536                                    mir_compiler.builder.ins().ireduce(types::I32, param_val)
537                                }
538                                Some(shape_vm::type_tracking::NativeKind::Bool)
539                                | Some(shape_vm::type_tracking::NativeKind::Int8)
540                                | Some(shape_vm::type_tracking::NativeKind::UInt8) => {
541                                    mir_compiler.builder.ins().ireduce(types::I8, param_val)
542                                }
543                                Some(shape_vm::type_tracking::NativeKind::Int16)
544                                | Some(shape_vm::type_tracking::NativeKind::UInt16) => {
545                                    mir_compiler.builder.ins().ireduce(types::I16, param_val)
546                                }
547                                _ => param_val,
548                            };
549                            mir_compiler.builder.def_var(var, converted);
550                        }
551                    }
552                }
553                mir_compiler.compile_body()?;
554                tracing::debug!(
555                    target: "shape_jit",
556                    func_name = %func.name,
557                    "jit-mir compiled function via MirToIR",
558                );
559            }
560            builder.finalize();
561        }
562
563        self.module
564            .define_function(func_id, &mut ctx)
565            .map_err(|e| format!("Failed to define function: {:?}", e))?;
566
567        self.module.clear_context(&mut ctx);
568
569        Ok(())
570    }
571
572    /// Compile a single function for Tier 1 whole-function JIT.
573    ///
574    /// This path previously used BytecodeToIR which has been removed.
575    /// Tier 1 JIT is deprecated; use compile_program_selective instead.
576    pub fn compile_single_function(
577        &mut self,
578        _program: &BytecodeProgram,
579        _func_index: usize,
580        _feedback: Option<shape_vm::feedback::FeedbackVector>,
581    ) -> Result<
582        (
583            *const u8,
584            Vec<shape_vm::bytecode::DeoptInfo>,
585            Vec<shape_value::shape_graph::ShapeId>,
586        ),
587        String,
588    > {
589        Err("Tier 1 JIT is deprecated".to_string())
590    }
591
592    /// Compile a function for Tier 2 optimizing JIT with feedback-guided speculation.
593    ///
594    /// This path previously used BytecodeToIR which has been removed.
595    /// Optimizing JIT is deprecated; use compile_program_selective instead.
596    pub fn compile_optimizing_function(
597        &mut self,
598        _program: &BytecodeProgram,
599        _func_index: usize,
600        _feedback: shape_vm::feedback::FeedbackVector,
601        _callee_feedback: &HashMap<u16, shape_vm::feedback::FeedbackVector>,
602    ) -> Result<
603        (
604            *const u8,
605            Vec<shape_vm::bytecode::DeoptInfo>,
606            Vec<shape_value::shape_graph::ShapeId>,
607        ),
608        String,
609    > {
610        Err("Optimizing JIT is deprecated".to_string())
611    }
612
613    /// Selectively compile a program, JIT-compiling compatible functions and
614    /// falling back to interpreter entries for incompatible ones.
615    ///
616    /// Returns a `MixedFunctionTable` mapping each function index to either
617    /// a `Native` pointer (JIT-compiled) or `Interpreted` marker.
618    ///
619    /// The main strategy body is always compiled. Only user-defined functions
620    /// go through per-function preflight.
621    pub fn compile_program_selective(
622        &mut self,
623        name: &str,
624        program: &BytecodeProgram,
625    ) -> Result<(JittedStrategyFn, MixedFunctionTable), String> {
626        use super::accessors::preflight_instructions;
627
628        maybe_emit_numeric_metrics(program);
629
630        // Phase 1: Per-function preflight to classify each function.
631        // A function is JIT-compatible if its bytecode passes instruction
632        // preflight OR it has MIR data that passes MirToIR preflight.
633        // MirToIR is the compilation path — bytecode preflight only gates eligibility.
634        let mut jit_compatible: Vec<bool> = Vec::with_capacity(program.functions.len());
635
636        for (_idx, func) in program.functions.iter().enumerate() {
637            if func.body_length == 0 {
638                jit_compatible.push(false);
639                continue;
640            }
641            let func_end = func.entry_point + func.body_length;
642            let instructions = &program.instructions[func.entry_point..func_end];
643            let report = preflight_instructions(instructions);
644            let bytecode_ok = report.can_jit();
645            let mir_ok = func.mir_data.as_ref().is_some_and(|md| {
646                crate::mir_compiler::preflight(md).can_compile
647            });
648            // Track A.1D / A.1D.2: the A.1B/A.1C.1/A.1C.3 mutable-cell
649            // opcodes carry runtime semantics the MIR layer cannot
650            // reconstruct from its slot-based model — MIR just sees
651            // `LoadLocal` / `StoreLocal`, erasing the pointer-deref
652            // semantics the cell opcodes encode.
653            //
654            // A.1D.2 closes the gap for `LoadOwnedMutableCapture` /
655            // `StoreOwnedMutableCapture` via a JIT-side side-table that
656            // patches `read_place` / `write_place` on flagged capture
657            // slots (see `MirToIR::register_owned_mutable_capture_slots`).
658            // Those two opcodes have been removed from
659            // `vm_only_opcode_reason`, so `bytecode_ok` is now `true`
660            // for functions whose only cell opcodes are OwnedMutable.
661            //
662            // A.1E closed the gap for the closure-body Shared-cell
663            // opcodes (`LoadSharedCapture` / `StoreSharedCapture`) via
664            // the `MirToIR::shared_capture_slots` side-table.
665            //
666            // Session 1 Commit 3 lands the MirToIR infrastructure for
667            // the outer-scope `var` cell lifecycle — the
668            // `MirToIR::shared_local_slots` side-table is populated
669            // from `StoragePlan::slot_classes`, function entry
670            // allocates one `Arc<SharedCell>` per SharedCow slot via
671            // `jit_alloc_shared_cell`, and `read_place`/`write_place`
672            // /`emit_drop` branch to lock-gated access +
673            // `jit_arc_shared_release`. The preflight gate for the
674            // four local opcodes (`AllocSharedLocal` /
675            // `LoadSharedLocal` / `StoreSharedLocal` /
676            // `DropSharedLocal`) REMAINS IN PLACE pending resolution
677            // of the outer-frame cell-identity handshake —
678            // lifting the gate prematurely segfaults the JIT'd
679            // outer frame's interaction with closure dispatch (see
680            // memory note `project_jit_closure_fix.md`).
681            //
682            // Still gated after this commit:
683            //   * the four outer-scope `var` local opcodes above;
684            //   * the three module-binding opcodes
685            //     (`AllocSharedModuleBinding`,
686            //     `LoadSharedModuleBinding`,
687            //     `StoreSharedModuleBinding`) — per-module side-table,
688            //     separate lowering (A.1C.3 follow-up).
689            let _ = mir_ok;
690            jit_compatible.push(bytecode_ok);
691        }
692
693        // Phase 1b: Preflight main code (non-stdlib, non-function-body instructions).
694        // Without this, unsupported builtins in top-level code slip through.
695        {
696            let skip_ranges = Self::compute_skip_ranges(program);
697            let main_instructions: Vec<_> = program
698                .instructions
699                .iter()
700                .enumerate()
701                .filter(|(i, _)| !skip_ranges.iter().any(|(s, e)| *i >= *s && *i < *e))
702                .map(|(_, instr)| instr.clone())
703                .collect();
704            let main_report = preflight_instructions(&main_instructions);
705            if !main_report.can_jit() {
706                return Err(format!(
707                    "Main code contains unsupported constructs: {:?}",
708                    main_report
709                ));
710            }
711        }
712
713        // v0.3 WS-6: a generic free function specialized on a struct type
714        // argument (`fn id<T>(x: T) -> T` called as `id(P { .. })`) produces
715        // a `<base>::struct_<name>` specialization. The JIT MIR codegen for
716        // a struct value flowing out of such a specialization is currently
717        // unsound — the returned `HeapKind::TypedObject` handle is
718        // mishandled when the result is stored to a slot and a field is
719        // later read, producing a use-after-free. The bytecode VM handles
720        // this case correctly. Per the CLAUDE.md surface-and-stop discipline
721        // (refuse what cannot be lowered soundly rather than emit crashing
722        // native code), surface here so `--mode jit` cleanly falls back to
723        // the interpreter for the whole program. Enum / Option / Result /
724        // Array / HashMap monomorphizations are unaffected — only the
725        // struct-typed free-function specialization is gated. (Generic
726        // struct args were rejected outright at the compile stage before
727        // WS-6, so this is a strict improvement: such programs now run
728        // correctly on the interpreter rather than failing to compile.)
729        if program
730            .functions
731            .iter()
732            .any(|func| func.name.contains("::struct_"))
733        {
734            return Err(
735                "WS-6 surface-and-stop: program uses a generic free function \
736                 specialized on a struct type argument; the JIT struct-value \
737                 codegen for that specialization is not yet sound — falling \
738                 back to the bytecode interpreter"
739                    .to_string(),
740            );
741        }
742
743        // Phase 2: Pre-declare ALL functions (both JIT and interpreted) in
744        // Cranelift so that JIT functions can call other JIT functions.
745        // Interpreted functions get declared too (for uniform call tables)
746        // but won't have a body defined - they'll use the trampoline.
747        let mut user_func_arities: HashMap<u16, u16> = HashMap::new();
748        let mut user_func_ids: HashMap<u16, cranelift_module::FuncId> = HashMap::new();
749
750        for (idx, func) in program.functions.iter().enumerate() {
751            if !jit_compatible[idx] {
752                user_func_arities.insert(idx as u16, func.arity);
753                continue;
754            }
755            // Use function index in the name to avoid collisions between
756            // closures with the same auto-generated name but different arities
757            // (e.g., multiple __closure_0 from different stdlib modules).
758            let func_name = format!("{}_f{}_{}", name, idx, func.name.replace("::", "__"));
759            let mut user_sig = self.module.make_signature();
760            user_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
761            // Closures receive captures as leading native args, followed by user params.
762            let effective_arity = func.captures_count + func.arity;
763            for _ in 0..effective_arity {
764                user_sig.params.push(AbiParam::new(types::I64));
765            }
766            user_sig.returns.push(AbiParam::new(types::I32));
767            let func_id = self
768                .module
769                .declare_function(&func_name, Linkage::Local, &user_sig)
770                .map_err(|e| format!("Failed to pre-declare function {}: {}", func.name, e))?;
771            user_func_ids.insert(idx as u16, func_id);
772            // Store user-visible arity (without captures) for CallValue arg count checks
773            user_func_arities.insert(idx as u16, func.arity);
774        }
775
776        // Phase 3: Compile main strategy body.
777        let main_func_id = self.compile_strategy_with_user_funcs(
778            name,
779            program,
780            &user_func_ids,
781            &user_func_arities,
782        )?;
783
784        // Phase 4: Compile only JIT-compatible function bodies.
785        // Functions that fail to compile are demoted to interpreted fallback.
786        for (idx, func) in program.functions.iter().enumerate() {
787            if jit_compatible[idx] || func.mir_data.is_some() {
788                tracing::debug!(
789                    target: "shape_jit",
790                    idx,
791                    func_name = %func.name,
792                    jit_compat = jit_compatible[idx],
793                    has_mir = func.mir_data.is_some(),
794                    "jit-mir per-function classification",
795                );
796            }
797            if !jit_compatible[idx] {
798                continue;
799            }
800            let func_name = format!("{}_f{}_{}", name, idx, func.name.replace("::", "__"));
801            if func.mir_data.is_some() {
802                tracing::debug!(
803                    target: "shape_jit",
804                    idx,
805                    func_name = %func.name,
806                    "jit-mir compiling function",
807                );
808            }
809            if let Err(e) = self.compile_function_with_user_funcs(
810                &func_name,
811                program,
812                idx,
813                &user_func_ids,
814                &user_func_arities,
815            ) {
816                tracing::debug!(
817                    target: "shape_jit",
818                    func_name = %func.name,
819                    error = %e,
820                    "jit-mir compile failed",
821                );
822                // Define a stub body so Cranelift doesn't panic on undefined symbol.
823                // The stub returns signal -1 (error), causing the caller to deopt.
824                //
825                // W12-jit-linker-resolve (`docs/cluster-audits/w12-jit-linker-audit.md`):
826                // Cranelift's `iconst` immediate-bounds rule requires the I32
827                // immediate to be the unsigned bit-pattern, not the signed
828                // value. `iconst.i32 -1` is rejected by the verifier because
829                // `-1i64 as u64 = 0xFFFFFFFFFFFFFFFF` exceeds the I32 mask
830                // `u32::MAX = 0xFFFFFFFF`. Pass the two's-complement unsigned
831                // bit pattern instead — see `cranelift-codegen/src/verifier/
832                // mod.rs:1644-1665` for the documented invariant.
833                //
834                // Also: previously the stub `define_function` failure was
835                // silently swallowed via `let _ = ...`, which left the
836                // declared FuncId with no body and caused `finalize_definitions`
837                // to panic with `can't resolve symbol main_f{idx}_{name}` —
838                // the very surface this audit traced. Surface the stub
839                // failure under `SHAPE_JIT_DEBUG=1` so future regressions
840                // don't hide beneath the linker panic.
841                if let Some(&fid) = user_func_ids.get(&(idx as u16)) {
842                    let mut stub_sig = self.module.make_signature();
843                    stub_sig.params.push(AbiParam::new(types::I64));
844                    let effective_arity = func.captures_count + func.arity;
845                    for _ in 0..effective_arity {
846                        stub_sig.params.push(AbiParam::new(types::I64));
847                    }
848                    stub_sig.returns.push(AbiParam::new(types::I32));
849                    let mut stub_ctx = self.module.make_context();
850                    stub_ctx.func.signature = stub_sig;
851                    let mut stub_builder_ctx = FunctionBuilderContext::new();
852                    {
853                        let mut b = FunctionBuilder::new(&mut stub_ctx.func, &mut stub_builder_ctx);
854                        let block = b.create_block();
855                        b.append_block_params_for_function_params(block);
856                        b.switch_to_block(block);
857                        b.seal_block(block);
858                        // Cranelift I32 iconst convention: pass the unsigned
859                        // bit-pattern, not the signed value. `-1i32` is
860                        // `0xFFFFFFFF` as a `u32`.
861                        let neg = b.ins().iconst(types::I32, (-1i32 as u32) as i64);
862                        b.ins().return_(&[neg]);
863                        b.finalize();
864                    }
865                    if let Err(stub_err) = self.module.define_function(fid, &mut stub_ctx) {
866                        tracing::debug!(
867                            target: "shape_jit",
868                            func_name = %func.name,
869                            idx,
870                            fid = ?fid,
871                            error = ?stub_err,
872                            "jit-mir stub define_function failed",
873                        );
874                        // Surface-and-stop: a failed stub leaves the declared
875                        // FuncId with no body, which propagates to
876                        // `finalize_definitions` as a `can't resolve symbol`
877                        // panic. Convert to a structured error here so the
878                        // caller sees a typed JIT-compilation failure, not a
879                        // panic through `catch_unwind`. The stub itself was
880                        // supposed to be a recovery path; if recovery fails,
881                        // the whole JIT compilation is unsound.
882                        return Err(format!(
883                            "JIT stub fallback failed for function '{}' (idx={}): {:?}. \
884                             The Cranelift module is in an inconsistent state — \
885                             this is a JIT-compiler bug, not a user-code error. \
886                             See docs/cluster-audits/w12-jit-linker-audit.md.",
887                            func.name, idx, stub_err
888                        ));
889                    }
890                    self.module.clear_context(&mut stub_ctx);
891                }
892                jit_compatible[idx] = false;
893            }
894        }
895
896        self.module
897            .finalize_definitions()
898            .map_err(|e| format!("Failed to finalize definitions: {:?}", e))?;
899
900        let main_code_ptr = self.module.get_finalized_function(main_func_id);
901        self.compiled_functions
902            .insert(name.to_string(), main_code_ptr);
903
904        // Phase 5: Build the MixedFunctionTable.
905        let mut mixed_table = MixedFunctionTable::with_capacity(program.functions.len());
906
907        self.function_table.clear();
908        for (idx, func) in program.functions.iter().enumerate() {
909            if jit_compatible[idx] {
910                if let Some(&func_id) = user_func_ids.get(&(idx as u16)) {
911                    let ptr = self.module.get_finalized_function(func_id);
912                    while self.function_table.len() <= idx {
913                        self.function_table.push(std::ptr::null());
914                    }
915                    self.function_table[idx] = ptr;
916                    let func_name = format!("{}_f{}_{}", name, idx, func.name.replace("::", "__"));
917                    self.compiled_functions.insert(func_name, ptr);
918                    mixed_table.insert(idx, FunctionEntry::Native(ptr));
919                }
920            } else {
921                while self.function_table.len() <= idx {
922                    self.function_table.push(std::ptr::null());
923                }
924                // Leave function_table[idx] as null for interpreted functions.
925                mixed_table.insert(idx, FunctionEntry::Interpreted(idx as u16));
926            }
927        }
928
929        let jit_fn = unsafe { std::mem::transmute(main_code_ptr) };
930        Ok((jit_fn, mixed_table))
931    }
932}