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 crate::translator::BytecodeToIR;
13use crate::translator::types::InlineCandidate;
14use shape_vm::bytecode::{BytecodeProgram, OpCode, Operand};
15
16#[derive(Default)]
17struct NumericOpcodeStats {
18    typed: usize,
19    generic: usize,
20    typed_breakdown: BTreeMap<String, usize>,
21    generic_breakdown: BTreeMap<String, usize>,
22}
23
24fn bump_breakdown(map: &mut BTreeMap<String, usize>, opcode: OpCode) {
25    let key = format!("{:?}", opcode);
26    *map.entry(key).or_insert(0) += 1;
27}
28
29fn collect_numeric_opcode_stats(program: &BytecodeProgram) -> NumericOpcodeStats {
30    let mut stats = NumericOpcodeStats::default();
31    for instr in &program.instructions {
32        match instr.opcode {
33            // Typed arithmetic opcodes
34            OpCode::AddInt
35            | OpCode::SubInt
36            | OpCode::MulInt
37            | OpCode::DivInt
38            | OpCode::ModInt
39            | OpCode::PowInt
40            | OpCode::AddNumber
41            | OpCode::SubNumber
42            | OpCode::MulNumber
43            | OpCode::DivNumber
44            | OpCode::ModNumber
45            | OpCode::PowNumber
46            // Typed comparisons
47            | OpCode::GtInt
48            | OpCode::LtInt
49            | OpCode::GteInt
50            | OpCode::LteInt
51            | OpCode::GtNumber
52            | OpCode::LtNumber
53            | OpCode::GteNumber
54            | OpCode::LteNumber
55            | OpCode::EqInt
56            | OpCode::EqNumber
57            | OpCode::NeqInt
58            | OpCode::NeqNumber => {
59                stats.typed += 1;
60                bump_breakdown(&mut stats.typed_breakdown, instr.opcode);
61            }
62            // Generic arithmetic/comparison opcodes
63            OpCode::Add
64            | OpCode::Sub
65            | OpCode::Mul
66            | OpCode::Div
67            | OpCode::Mod
68            | OpCode::Pow
69            | OpCode::Gt
70            | OpCode::Lt
71            | OpCode::Gte
72            | OpCode::Lte
73            | OpCode::Eq
74            | OpCode::Neq => {
75                stats.generic += 1;
76                bump_breakdown(&mut stats.generic_breakdown, instr.opcode);
77            }
78            _ => {}
79        }
80    }
81    stats
82}
83
84fn maybe_emit_numeric_metrics(program: &BytecodeProgram) {
85    if std::env::var_os("SHAPE_JIT_METRICS").is_none() {
86        return;
87    }
88    let static_stats = collect_numeric_opcode_stats(program);
89    let static_total = static_stats.typed + static_stats.generic;
90    let static_coverage_pct = if static_total == 0 {
91        100.0
92    } else {
93        (static_stats.typed as f64 * 100.0) / (static_total as f64)
94    };
95    // Report effective coverage conservatively: generic opcodes remain generic
96    // unless the frontend/runtime has concretely emitted typed variants.
97    let effective_typed = static_stats.typed;
98    let effective_generic = static_stats.generic;
99    let effective_coverage_pct = static_coverage_pct;
100    eprintln!(
101        "[shape-jit-metrics] typed_numeric_ops={} generic_numeric_ops={} typed_numeric_coverage_pct={:.2} static_typed_numeric_ops={} static_generic_numeric_ops={} static_typed_numeric_coverage_pct={:.2}",
102        effective_typed,
103        effective_generic,
104        effective_coverage_pct,
105        static_stats.typed,
106        static_stats.generic,
107        static_coverage_pct
108    );
109    if std::env::var_os("SHAPE_JIT_METRICS_DETAIL").is_some() {
110        let fmt_breakdown = |breakdown: &BTreeMap<String, usize>| -> String {
111            breakdown
112                .iter()
113                .map(|(name, count)| format!("{}:{}", name, count))
114                .collect::<Vec<_>>()
115                .join(",")
116        };
117        eprintln!(
118            "[shape-jit-metrics-detail] typed_breakdown={} generic_breakdown={}",
119            fmt_breakdown(&static_stats.typed_breakdown),
120            fmt_breakdown(&static_stats.generic_breakdown)
121        );
122    }
123}
124
125fn stack_effect_for_param_analysis(op: OpCode) -> Option<(i32, i32)> {
126    let effect = match op {
127        OpCode::LoadLocal
128        | OpCode::LoadModuleBinding
129        | OpCode::LoadClosure
130        | OpCode::PushConst
131        | OpCode::PushNull
132        | OpCode::DerefLoad => (0, 1),
133        OpCode::IntToNumber | OpCode::NumberToInt | OpCode::Neg | OpCode::Not => (1, 1),
134        OpCode::Add
135        | OpCode::Sub
136        | OpCode::Mul
137        | OpCode::Div
138        | OpCode::Mod
139        | OpCode::Pow
140        | OpCode::AddInt
141        | OpCode::SubInt
142        | OpCode::MulInt
143        | OpCode::DivInt
144        | OpCode::ModInt
145        | OpCode::PowInt
146        | OpCode::AddIntTrusted
147        | OpCode::SubIntTrusted
148        | OpCode::MulIntTrusted
149        | OpCode::DivIntTrusted
150        | OpCode::AddNumber
151        | OpCode::SubNumber
152        | OpCode::MulNumber
153        | OpCode::DivNumber
154        | OpCode::ModNumber
155        | OpCode::PowNumber
156        | OpCode::AddNumberTrusted
157        | OpCode::SubNumberTrusted
158        | OpCode::MulNumberTrusted
159        | OpCode::DivNumberTrusted
160        | OpCode::Gt
161        | OpCode::Lt
162        | OpCode::Gte
163        | OpCode::Lte
164        | OpCode::Eq
165        | OpCode::Neq
166        | OpCode::GtInt
167        | OpCode::LtInt
168        | OpCode::GteInt
169        | OpCode::LteInt
170        | OpCode::GtIntTrusted
171        | OpCode::LtIntTrusted
172        | OpCode::GteIntTrusted
173        | OpCode::LteIntTrusted
174        | OpCode::GtNumber
175        | OpCode::LtNumber
176        | OpCode::GteNumber
177        | OpCode::LteNumber
178        | OpCode::GtNumberTrusted
179        | OpCode::LtNumberTrusted
180        | OpCode::GteNumberTrusted
181        | OpCode::LteNumberTrusted
182        | OpCode::EqInt
183        | OpCode::EqNumber
184        | OpCode::NeqInt
185        | OpCode::NeqNumber
186        | OpCode::GetProp => (2, 1),
187        OpCode::Dup => (1, 2),
188        OpCode::Swap => (2, 2),
189        OpCode::StoreLocal | OpCode::StoreLocalTyped | OpCode::Pop => (1, 0),
190        _ => return None,
191    };
192    Some(effect)
193}
194
195fn source_local_for_stack_pos(
196    program: &BytecodeProgram,
197    before_idx: usize,
198    mut pos_from_top: i32,
199) -> Option<u16> {
200    for j in (0..before_idx).rev() {
201        let instr = &program.instructions[j];
202        let (pops, pushes) = stack_effect_for_param_analysis(instr.opcode)?;
203        if pos_from_top < pushes {
204            return match instr.opcode {
205                OpCode::LoadLocal => match &instr.operand {
206                    Some(Operand::Local(idx)) => Some(*idx),
207                    _ => None,
208                },
209                _ => None,
210            };
211        }
212        pos_from_top = pos_from_top - pushes + pops;
213        if pos_from_top < 0 {
214            return None;
215        }
216    }
217    None
218}
219
220fn collect_numeric_param_hints(
221    program: &BytecodeProgram,
222    arity: u16,
223    ref_params: &[bool],
224) -> std::collections::HashSet<u16> {
225    let mut params = std::collections::HashSet::new();
226    for (i, instr) in program.instructions.iter().enumerate() {
227        let is_numeric_consumer = matches!(
228            instr.opcode,
229            OpCode::Add
230                | OpCode::Sub
231                | OpCode::Mul
232                | OpCode::Div
233                | OpCode::Mod
234                | OpCode::Pow
235                | OpCode::AddInt
236                | OpCode::SubInt
237                | OpCode::MulInt
238                | OpCode::DivInt
239                | OpCode::ModInt
240                | OpCode::PowInt
241                | OpCode::AddIntTrusted
242                | OpCode::SubIntTrusted
243                | OpCode::MulIntTrusted
244                | OpCode::DivIntTrusted
245                | OpCode::AddNumber
246                | OpCode::SubNumber
247                | OpCode::MulNumber
248                | OpCode::DivNumber
249                | OpCode::ModNumber
250                | OpCode::PowNumber
251                | OpCode::AddNumberTrusted
252                | OpCode::SubNumberTrusted
253                | OpCode::MulNumberTrusted
254                | OpCode::DivNumberTrusted
255                | OpCode::Gt
256                | OpCode::Lt
257                | OpCode::Gte
258                | OpCode::Lte
259                | OpCode::Eq
260                | OpCode::Neq
261                | OpCode::GtInt
262                | OpCode::LtInt
263                | OpCode::GteInt
264                | OpCode::LteInt
265                | OpCode::GtIntTrusted
266                | OpCode::LtIntTrusted
267                | OpCode::GteIntTrusted
268                | OpCode::LteIntTrusted
269                | OpCode::GtNumber
270                | OpCode::LtNumber
271                | OpCode::GteNumber
272                | OpCode::LteNumber
273                | OpCode::GtNumberTrusted
274                | OpCode::LtNumberTrusted
275                | OpCode::GteNumberTrusted
276                | OpCode::LteNumberTrusted
277                | OpCode::EqInt
278                | OpCode::EqNumber
279                | OpCode::NeqInt
280                | OpCode::NeqNumber
281        );
282        if !is_numeric_consumer {
283            continue;
284        }
285        for pos in 0..2 {
286            if let Some(local_idx) = source_local_for_stack_pos(program, i, pos) {
287                if local_idx >= arity {
288                    continue;
289                }
290                let is_ref = ref_params.get(local_idx as usize).copied().unwrap_or(false);
291                if !is_ref {
292                    params.insert(local_idx);
293                }
294            }
295        }
296    }
297    params
298}
299
300impl JITCompiler {
301    #[inline(always)]
302    pub fn compile(&mut self, name: &str, program: &BytecodeProgram) -> Result<JittedFn, String> {
303        let mut sig = self.module.make_signature();
304        sig.params.push(AbiParam::new(types::I64));
305        sig.params.push(AbiParam::new(types::I64));
306        sig.params.push(AbiParam::new(types::I64));
307        sig.returns.push(AbiParam::new(types::F64));
308
309        let func_id = self
310            .module
311            .declare_function(name, Linkage::Export, &sig)
312            .map_err(|e| format!("Failed to declare function: {}", e))?;
313
314        let mut ctx = self.module.make_context();
315        ctx.func.signature = sig;
316
317        {
318            let mut builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_context);
319            let entry_block = builder.create_block();
320            builder.append_block_params_for_function_params(entry_block);
321            builder.switch_to_block(entry_block);
322            builder.seal_block(entry_block);
323
324            let stack_ptr = builder.block_params(entry_block)[0];
325            let constants_ptr = builder.block_params(entry_block)[1];
326
327            let result = compile_numeric_program(&mut builder, program, stack_ptr, constants_ptr)?;
328
329            builder.ins().return_(&[result]);
330            builder.finalize();
331        }
332
333        self.module
334            .define_function(func_id, &mut ctx)
335            .map_err(|e| format!("Failed to define function: {}", e))?;
336
337        self.module.clear_context(&mut ctx);
338        self.module
339            .finalize_definitions()
340            .map_err(|e| format!("Failed to finalize: {}", e))?;
341
342        let code_ptr = self.module.get_finalized_function(func_id);
343        self.compiled_functions.insert(name.to_string(), code_ptr);
344
345        Ok(unsafe { std::mem::transmute(code_ptr) })
346    }
347
348    #[inline(always)]
349    pub fn compile_program(
350        &mut self,
351        name: &str,
352        program: &BytecodeProgram,
353    ) -> Result<JittedStrategyFn, String> {
354        maybe_emit_numeric_metrics(program);
355
356        let mut user_func_arities: HashMap<u16, u16> = HashMap::new();
357        let mut user_func_ids: HashMap<u16, cranelift_module::FuncId> = HashMap::new();
358
359        for (idx, func) in program.functions.iter().enumerate() {
360            let func_name = format!("{}_{}", name, func.name);
361            let mut user_sig = self.module.make_signature();
362            user_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
363            for _ in 0..func.arity {
364                user_sig.params.push(AbiParam::new(types::I64));
365            }
366            user_sig.returns.push(AbiParam::new(types::I32));
367            let func_id = self
368                .module
369                .declare_function(&func_name, Linkage::Local, &user_sig)
370                .map_err(|e| format!("Failed to pre-declare function {}: {}", func.name, e))?;
371            user_func_ids.insert(idx as u16, func_id);
372            user_func_arities.insert(idx as u16, func.arity);
373        }
374
375        let main_func_id = self.compile_strategy_with_user_funcs(
376            name,
377            program,
378            &user_func_ids,
379            &user_func_arities,
380        )?;
381
382        for (idx, func) in program.functions.iter().enumerate() {
383            let func_name = format!("{}_{}", name, func.name);
384            self.compile_function_with_user_funcs(
385                &func_name,
386                program,
387                idx,
388                &user_func_ids,
389                &user_func_arities,
390            )?;
391        }
392
393        self.module
394            .finalize_definitions()
395            .map_err(|e| format!("Failed to finalize definitions: {:?}", e))?;
396
397        let main_code_ptr = self.module.get_finalized_function(main_func_id);
398        self.compiled_functions
399            .insert(name.to_string(), main_code_ptr);
400
401        self.function_table.clear();
402        for (idx, func) in program.functions.iter().enumerate() {
403            let func_name = format!("{}_{}", name, func.name);
404            if let Some(&func_id) = user_func_ids.get(&(idx as u16)) {
405                let ptr = self.module.get_finalized_function(func_id);
406                while self.function_table.len() <= idx {
407                    self.function_table.push(std::ptr::null());
408                }
409                self.function_table[idx] = ptr;
410                self.compiled_functions.insert(func_name, ptr);
411            }
412        }
413
414        Ok(unsafe { std::mem::transmute(main_code_ptr) })
415    }
416
417    fn compile_function_with_user_funcs(
418        &mut self,
419        name: &str,
420        program: &BytecodeProgram,
421        func_idx: usize,
422        user_func_ids: &HashMap<u16, cranelift_module::FuncId>,
423        user_func_arities: &HashMap<u16, u16>,
424    ) -> Result<(), String> {
425        let func = &program.functions[func_idx];
426        let func_id = *user_func_ids
427            .get(&(func_idx as u16))
428            .ok_or_else(|| format!("Function {} not pre-declared", name))?;
429
430        let mut sig = self.module.make_signature();
431        sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
432        for _ in 0..func.arity {
433            sig.params.push(AbiParam::new(types::I64));
434        }
435        sig.returns.push(AbiParam::new(types::I32));
436
437        let mut ctx = self.module.make_context();
438        ctx.func.signature = sig;
439
440        let mut func_builder_ctx = FunctionBuilderContext::new();
441        {
442            let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_builder_ctx);
443            let entry_block = builder.create_block();
444            builder.append_block_params_for_function_params(entry_block);
445            builder.switch_to_block(entry_block);
446            builder.seal_block(entry_block);
447
448            let ctx_ptr = builder.block_params(entry_block)[0];
449            let mut user_func_refs: HashMap<u16, FuncRef> = HashMap::new();
450            for (&fn_idx, &fn_id) in user_func_ids {
451                let func_ref = self.module.declare_func_in_func(fn_id, builder.func);
452                user_func_refs.insert(fn_idx, func_ref);
453            }
454
455            let ffi = self.build_ffi_refs(&mut builder);
456
457            let func_end = func.entry_point + func.body_length;
458            let sub_instructions = &program.instructions[func.entry_point..func_end];
459            let sub_program = BytecodeProgram {
460                instructions: sub_instructions.to_vec(),
461                constants: program.constants.clone(),
462                strings: program.strings.clone(),
463                // Use empty functions list: the sub_program only contains ONE function's
464                // body, so the original entry points are meaningless in the rebased index
465                // space. This prevents analyze_inline_candidates from using wrong instruction
466                // ranges. Direct calls between functions use user_func_refs instead.
467                functions: Vec::new(),
468                debug_info: Default::default(),
469                data_schema: program.data_schema.clone(),
470                module_binding_names: program.module_binding_names.clone(),
471                top_level_locals_count: program.top_level_locals_count,
472                top_level_local_storage_hints: program
473                    .function_local_storage_hints
474                    .get(func_idx)
475                    .cloned()
476                    .unwrap_or_default(),
477                type_schema_registry: program.type_schema_registry.clone(),
478                module_binding_storage_hints: program.module_binding_storage_hints.clone(),
479                function_local_storage_hints: Vec::new(),
480                top_level_frame: None,
481                compiled_annotations: program.compiled_annotations.clone(),
482                trait_method_symbols: program.trait_method_symbols.clone(),
483                expanded_function_defs: program.expanded_function_defs.clone(),
484                string_index: Default::default(),
485                foreign_functions: program.foreign_functions.clone(),
486                native_struct_layouts: program.native_struct_layouts.clone(),
487                content_addressed: None,
488                function_blob_hashes: Vec::new(),
489            };
490
491            let mut compiler = BytecodeToIR::new(
492                &mut builder,
493                &sub_program,
494                ctx_ptr,
495                ffi,
496                user_func_refs,
497                user_func_arities.clone(),
498            );
499            compiler.numeric_param_hints =
500                collect_numeric_param_hints(&sub_program, func.arity, &func.ref_params);
501
502            let entry_params = compiler.builder.block_params(entry_block).to_vec();
503            for arg_idx in 0..func.arity {
504                let arg_val = entry_params[(arg_idx as usize) + 1];
505                let var = compiler.get_or_create_local(arg_idx);
506                compiler.builder.def_var(var, arg_val);
507            }
508
509            let result = compiler.compile()?;
510            builder.ins().return_(&[result]);
511            builder.finalize();
512        }
513
514        self.module
515            .define_function(func_id, &mut ctx)
516            .map_err(|e| format!("Failed to define function: {:?}", e))?;
517
518        self.module.clear_context(&mut ctx);
519
520        Ok(())
521    }
522
523    /// Compile a single function for Tier 1 whole-function JIT.
524    ///
525    /// ABI matches JitFnPtr: `extern "C" fn(*mut u8, *const u8) -> u64`
526    /// - param 0 (i64): ctx_ptr — pointer to a JITContext-shaped buffer
527    /// - param 1 (i64): unused (kept for ABI compatibility with OSR)
528    /// - return (i64): NaN-boxed result value, or u64::MAX for deopt
529    ///
530    /// Args are loaded from the ctx locals area at LOCALS_U64_OFFSET.
531    /// Cross-function calls deopt to interpreter (empty user_funcs).
532    ///
533    /// Returns `(code_ptr, deopt_points, shape_guards)` on success.
534    pub fn compile_single_function(
535        &mut self,
536        program: &BytecodeProgram,
537        func_index: usize,
538        feedback: Option<shape_vm::feedback::FeedbackVector>,
539    ) -> Result<
540        (
541            *const u8,
542            Vec<shape_vm::bytecode::DeoptInfo>,
543            Vec<shape_value::shape_graph::ShapeId>,
544        ),
545        String,
546    > {
547        use cranelift_module::{Linkage, Module};
548
549        let func = program
550            .functions
551            .get(func_index)
552            .ok_or_else(|| format!("Function {} not found in program", func_index))?;
553
554        let func_name = format!("tier1_fn_{}", func.name);
555
556        // JitFnPtr ABI: (ctx_ptr: i64, unused: i64) -> i64
557        let mut sig = self.module.make_signature();
558        sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
559        sig.params.push(AbiParam::new(types::I64)); // unused
560        sig.returns.push(AbiParam::new(types::I64)); // NaN-boxed result
561
562        let cr_func_id = self
563            .module
564            .declare_function(&func_name, Linkage::Export, &sig)
565            .map_err(|e| format!("Failed to declare function: {}", e))?;
566
567        let mut ctx = self.module.make_context();
568        ctx.func.signature = sig;
569
570        let mut deopt_points;
571        let shape_guards;
572
573        let mut func_builder_ctx = FunctionBuilderContext::new();
574        {
575            let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_builder_ctx);
576            let entry_block = builder.create_block();
577            builder.append_block_params_for_function_params(entry_block);
578            builder.switch_to_block(entry_block);
579            builder.seal_block(entry_block);
580
581            let ctx_ptr = builder.block_params(entry_block)[0];
582            // param 1 is unused
583
584            let ffi = self.build_ffi_refs(&mut builder);
585
586            // Compute function body range: next function's entry point, or
587            // end of instruction stream if this is the last/only function.
588            let func_end = program
589                .functions
590                .iter()
591                .enumerate()
592                .filter(|(i, _)| *i != func_index)
593                .filter_map(|(_, f)| {
594                    if f.entry_point > func.entry_point {
595                        Some(f.entry_point)
596                    } else {
597                        None
598                    }
599                })
600                .min()
601                .unwrap_or(program.instructions.len());
602
603            let sub_instructions = &program.instructions[func.entry_point..func_end];
604            let sub_program = BytecodeProgram {
605                instructions: sub_instructions.to_vec(),
606                constants: program.constants.clone(),
607                strings: program.strings.clone(),
608                functions: Vec::new(),
609                debug_info: Default::default(),
610                data_schema: program.data_schema.clone(),
611                module_binding_names: program.module_binding_names.clone(),
612                top_level_locals_count: program.top_level_locals_count,
613                top_level_local_storage_hints: program
614                    .function_local_storage_hints
615                    .get(func_index)
616                    .cloned()
617                    .unwrap_or_default(),
618                type_schema_registry: program.type_schema_registry.clone(),
619                module_binding_storage_hints: program.module_binding_storage_hints.clone(),
620                function_local_storage_hints: Vec::new(),
621                top_level_frame: None,
622                compiled_annotations: program.compiled_annotations.clone(),
623                trait_method_symbols: program.trait_method_symbols.clone(),
624                expanded_function_defs: program.expanded_function_defs.clone(),
625                string_index: Default::default(),
626                foreign_functions: program.foreign_functions.clone(),
627                native_struct_layouts: program.native_struct_layouts.clone(),
628                content_addressed: None,
629                function_blob_hashes: Vec::new(),
630            };
631
632            // Populate function arities for arity validation and speculation hints.
633            // user_funcs (FuncRef map) stays empty: single-function compilation
634            // can't pre-declare other functions in the Cranelift module without
635            // defining them (which requires compiling all functions together, as
636            // compile_program does). Speculative direct calls therefore deopt to
637            // the interpreter. Speculative arithmetic works (only needs feedback).
638            let user_func_arities: HashMap<u16, u16> = program
639                .functions
640                .iter()
641                .enumerate()
642                .map(|(i, f)| (i as u16, f.arity))
643                .collect();
644            let mut compiler = BytecodeToIR::new(
645                &mut builder,
646                &sub_program,
647                ctx_ptr,
648                ffi,
649                HashMap::new(),
650                user_func_arities,
651            );
652            compiler.numeric_param_hints =
653                collect_numeric_param_hints(&sub_program, func.arity, &func.ref_params);
654            compiler.func_locals_count = func.locals_count;
655
656            // Attach feedback vector for Tier 2 speculation if available.
657            // Rebase slot keys: interpreter records at absolute IP, but the
658            // sub-program starts at index 0.
659            if let Some(mut fv) = feedback {
660                fv.rebase(func.entry_point);
661                compiler.set_feedback(fv);
662            }
663
664            // Load args from JITContext locals area.
665            // The executor marshals args into ctx_buf[LOCALS_U64_OFFSET + i].
666            const LOCALS_BYTE_OFFSET: i32 = 64; // byte 64 in JITContext
667            for arg_idx in 0..func.arity {
668                let offset = LOCALS_BYTE_OFFSET + (arg_idx as i32) * 8;
669                let arg_val =
670                    compiler
671                        .builder
672                        .ins()
673                        .load(types::I64, MemFlags::trusted(), ctx_ptr, offset);
674                let var = compiler.get_or_create_local(arg_idx);
675                compiler.builder.def_var(var, arg_val);
676            }
677
678            let signal = compiler.compile()?;
679            deopt_points = compiler.take_deopt_points();
680            // Rebase deopt resume_ip from sub-program-local to global program IP.
681            for dp in &mut deopt_points {
682                dp.resume_ip += func.entry_point;
683            }
684            shape_guards = compiler.take_shape_guards();
685            // Drop compiler to release the mutable borrow on builder
686            drop(compiler);
687
688            // compile() returns an i32 signal (0 = success, negative = deopt)
689            // and stores the NaN-boxed result in ctx[STACK_OFFSET].
690            // For JitFnPtr ABI, we need to return:
691            //   - u64::MAX on deopt (signal < 0)
692            //   - NaN-boxed result (loaded from ctx stack) on success
693            use crate::context::STACK_OFFSET;
694            let zero = builder.ins().iconst(types::I32, 0);
695            let is_deopt = builder.ins().icmp(IntCC::SignedLessThan, signal, zero);
696
697            let deopt_block = builder.create_block();
698            let success_block = builder.create_block();
699            let merge_block = builder.create_block();
700            builder.append_block_param(merge_block, types::I64);
701
702            builder
703                .ins()
704                .brif(is_deopt, deopt_block, &[], success_block, &[]);
705
706            // Deopt path: return u64::MAX
707            builder.switch_to_block(deopt_block);
708            builder.seal_block(deopt_block);
709            let deopt_sentinel = builder.ins().iconst(types::I64, -1i64); // u64::MAX
710            builder.ins().jump(merge_block, &[deopt_sentinel]);
711
712            // Success path: load result from ctx stack
713            builder.switch_to_block(success_block);
714            builder.seal_block(success_block);
715            let result_val =
716                builder
717                    .ins()
718                    .load(types::I64, MemFlags::trusted(), ctx_ptr, STACK_OFFSET);
719            builder.ins().jump(merge_block, &[result_val]);
720
721            // Merge: return the selected value
722            builder.switch_to_block(merge_block);
723            builder.seal_block(merge_block);
724            let ret_val = builder.block_params(merge_block)[0];
725            builder.ins().return_(&[ret_val]);
726            builder.finalize();
727        }
728
729        self.module
730            .define_function(cr_func_id, &mut ctx)
731            .map_err(|e| format!("Failed to define function: {:?}", e))?;
732
733        self.module.clear_context(&mut ctx);
734        self.module
735            .finalize_definitions()
736            .map_err(|e| format!("Failed to finalize: {:?}", e))?;
737
738        let code_ptr = self.module.get_finalized_function(cr_func_id);
739        self.compiled_functions.insert(func_name, code_ptr);
740
741        Ok((code_ptr, deopt_points, shape_guards))
742    }
743
744    /// Compile a function for Tier 2 optimizing JIT with feedback-guided speculation.
745    ///
746    /// The target function's own FuncRef is declared with `Linkage::Local` for
747    /// self-recursive direct calls. Cross-function monomorphic call sites get
748    /// speculative direct calls when the callee has already been Tier-2 compiled:
749    /// the callee's `opt_dc_*` FuncId is looked up in `compiled_dc_funcs` and
750    /// a FuncRef is declared, enabling `compile_direct_call` to emit a true
751    /// `call` instruction (not FFI). A callee identity guard protects every
752    /// speculative call; on guard failure the JIT deopts to the interpreter.
753    ///
754    /// ABI: Returns a JitFnPtr wrapper `(ctx_ptr, unused) -> u64` that loads
755    /// args from the ctx locals area, calls the direct-call function, and
756    /// converts the result.
757    pub fn compile_optimizing_function(
758        &mut self,
759        program: &BytecodeProgram,
760        func_index: usize,
761        feedback: shape_vm::feedback::FeedbackVector,
762        callee_feedback: &HashMap<u16, shape_vm::feedback::FeedbackVector>,
763    ) -> Result<
764        (
765            *const u8,
766            Vec<shape_vm::bytecode::DeoptInfo>,
767            Vec<shape_value::shape_graph::ShapeId>,
768        ),
769        String,
770    > {
771        use cranelift_module::{Linkage, Module};
772
773        let func = program
774            .functions
775            .get(func_index)
776            .ok_or_else(|| format!("Function {} not found in program", func_index))?;
777
778        // Phase 1: Declare the TARGET function with direct-call ABI.
779        //
780        // Self-recursive calls get full direct-call via FuncRef.
781        // Cross-function monomorphic calls get speculative direct calls when
782        // the callee has already been Tier-2 compiled (FuncRef from
783        // compiled_dc_funcs). Otherwise, guard + FFI fallthrough.
784        let mut user_func_arities: HashMap<u16, u16> = HashMap::new();
785        for (idx, f) in program.functions.iter().enumerate() {
786            user_func_arities.insert(idx as u16, f.arity);
787        }
788
789        let dc_name = format!("opt_dc_{}_{}", func_index, func.name);
790        let mut dc_sig = self.module.make_signature();
791        dc_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
792        for _ in 0..func.arity {
793            dc_sig.params.push(AbiParam::new(types::I64));
794        }
795        dc_sig.returns.push(AbiParam::new(types::I32)); // signal
796        let target_dc_func_id = self
797            .module
798            .declare_function(&dc_name, Linkage::Local, &dc_sig)
799            .map_err(|e| format!("Failed to declare function {}: {}", func.name, e))?;
800
801        let mut deopt_points;
802        let shape_guards;
803        {
804            let mut dc_sig = self.module.make_signature();
805            dc_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
806            for _ in 0..func.arity {
807                dc_sig.params.push(AbiParam::new(types::I64));
808            }
809            dc_sig.returns.push(AbiParam::new(types::I32));
810
811            let mut ctx = self.module.make_context();
812            ctx.func.signature = dc_sig;
813
814            let mut func_builder_ctx = FunctionBuilderContext::new();
815            {
816                let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_builder_ctx);
817                let entry_block = builder.create_block();
818                builder.append_block_params_for_function_params(entry_block);
819                builder.switch_to_block(entry_block);
820                builder.seal_block(entry_block);
821
822                let ctx_ptr = builder.block_params(entry_block)[0];
823
824                // Self-recursive FuncRef for direct calls.
825                let mut user_func_refs: HashMap<u16, FuncRef> = HashMap::new();
826                {
827                    let func_ref = self
828                        .module
829                        .declare_func_in_func(target_dc_func_id, builder.func);
830                    user_func_refs.insert(func_index as u16, func_ref);
831                }
832
833                // Cross-function direct-call speculation: scan feedback for
834                // monomorphic call targets that have already been Tier-2 compiled.
835                // For each such callee, declare a FuncRef so compile_call_value
836                // can emit a direct `call` instead of going through FFI.
837                for (_slot_offset, slot) in feedback.slots.iter() {
838                    if let shape_vm::feedback::FeedbackSlot::Call(fb) = slot {
839                        if fb.state == shape_vm::feedback::ICState::Monomorphic {
840                            if let Some(target) = fb.targets.first() {
841                                let target_id = target.function_id;
842                                if target_id != func_index as u16
843                                    && !user_func_refs.contains_key(&target_id)
844                                {
845                                    // Check if this callee was already Tier-2 compiled
846                                    if let Some(&(callee_func_id, _callee_arity)) =
847                                        self.compiled_dc_funcs.get(&target_id)
848                                    {
849                                        let callee_ref = self
850                                            .module
851                                            .declare_func_in_func(callee_func_id, builder.func);
852                                        user_func_refs.insert(target_id, callee_ref);
853                                    }
854                                }
855                            }
856                        }
857                    }
858                }
859
860                let ffi = self.build_ffi_refs(&mut builder);
861
862                // Build sub-program for target function.
863                let func_end = program
864                    .functions
865                    .iter()
866                    .enumerate()
867                    .filter(|(i, _)| *i != func_index)
868                    .filter_map(|(_, f)| {
869                        if f.entry_point > func.entry_point {
870                            Some(f.entry_point)
871                        } else {
872                            None
873                        }
874                    })
875                    .min()
876                    .unwrap_or(program.instructions.len());
877
878                let target_instructions = &program.instructions[func.entry_point..func_end];
879                let target_instr_count = target_instructions.len();
880
881                // ---- Tier-2 inlining: find inline candidates and append callees ----
882                //
883                // analyze_inline_candidates needs the full program to determine
884                // function boundaries and eligibility. We scan the target function's
885                // bytecode for Call(fn_id) instructions referencing candidates, then
886                // append those callees' instructions to the sub-program. The main
887                // compilation loop uses skip_ranges to avoid compiling them;
888                // compile_inline_call reads them via entry_point.
889                let full_candidates = BytecodeToIR::analyze_inline_candidates(program);
890                let mut sub_instructions_vec = target_instructions.to_vec();
891                // Maps callee fn_id → rebased entry_point in the sub_program
892                let mut callee_inline_map: HashMap<u16, InlineCandidate> = HashMap::new();
893                // Skip ranges for appended callee instruction regions
894                let mut callee_skip_ranges: Vec<(usize, usize)> = Vec::new();
895                // Track callee offsets for feedback merging
896                let mut callee_feedback_offsets: Vec<(u16, usize)> = Vec::new();
897
898                for instr in target_instructions {
899                    if let shape_vm::bytecode::OpCode::Call = instr.opcode {
900                        if let Some(shape_vm::bytecode::Operand::Function(fn_id)) = &instr.operand {
901                            let callee_id = fn_id.0;
902                            // Skip self-recursive and already-processed callees
903                            if callee_id == func_index as u16 || callee_inline_map.contains_key(&callee_id) {
904                                continue;
905                            }
906                            if let Some(candidate) = full_candidates.get(&callee_id) {
907                                let callee_start = candidate.entry_point;
908                                let callee_end = callee_start + candidate.instruction_count;
909                                if callee_end <= program.instructions.len() {
910                                    let callee_instrs = &program.instructions[callee_start..callee_end];
911                                    let rebased_entry = sub_instructions_vec.len();
912                                    sub_instructions_vec.extend_from_slice(callee_instrs);
913                                    let rebased_end = sub_instructions_vec.len();
914
915                                    callee_inline_map.insert(callee_id, InlineCandidate {
916                                        entry_point: rebased_entry,
917                                        instruction_count: candidate.instruction_count,
918                                        arity: candidate.arity,
919                                        locals_count: candidate.locals_count,
920                                    });
921                                    callee_skip_ranges.push((rebased_entry, rebased_end));
922                                    callee_feedback_offsets.push((callee_id, rebased_entry));
923                                }
924                            }
925                        }
926                    }
927                }
928
929                let sub_program = BytecodeProgram {
930                    instructions: sub_instructions_vec,
931                    constants: program.constants.clone(),
932                    strings: program.strings.clone(),
933                    functions: Vec::new(),
934                    debug_info: Default::default(),
935                    data_schema: program.data_schema.clone(),
936                    module_binding_names: program.module_binding_names.clone(),
937                    top_level_locals_count: program.top_level_locals_count,
938                    top_level_local_storage_hints: program
939                        .function_local_storage_hints
940                        .get(func_index)
941                        .cloned()
942                        .unwrap_or_default(),
943                    type_schema_registry: program.type_schema_registry.clone(),
944                    module_binding_storage_hints: program.module_binding_storage_hints.clone(),
945                    function_local_storage_hints: Vec::new(),
946                    top_level_frame: None,
947                    compiled_annotations: program.compiled_annotations.clone(),
948                    trait_method_symbols: program.trait_method_symbols.clone(),
949                    expanded_function_defs: program.expanded_function_defs.clone(),
950                    string_index: Default::default(),
951                    foreign_functions: program.foreign_functions.clone(),
952                    native_struct_layouts: program.native_struct_layouts.clone(),
953                    content_addressed: None,
954                    function_blob_hashes: Vec::new(),
955                };
956
957                let mut compiler = BytecodeToIR::new(
958                    &mut builder,
959                    &sub_program,
960                    ctx_ptr,
961                    ffi,
962                    user_func_refs,
963                    user_func_arities.clone(),
964                );
965                // Only scan the outer function's instructions for numeric param hints,
966                // not the appended callee instructions. Using the callee's Add/Mul etc.
967                // would incorrectly mark outer's params as numeric.
968                {
969                    let mut outer_only = sub_program.clone();
970                    outer_only.instructions.truncate(target_instr_count);
971                    compiler.numeric_param_hints =
972                        collect_numeric_param_hints(&outer_only, func.arity, &func.ref_params);
973                }
974                compiler.func_locals_count = func.locals_count;
975                compiler.compiling_function_id = func_index as u16;
976
977                // Inject inline candidates with rebased entry_points.
978                // These are keyed by original fn_id so compile_call can find them.
979                for (callee_id, candidate) in &callee_inline_map {
980                    compiler.inline_candidates.insert(*callee_id, candidate.clone());
981                }
982                // Set skip_ranges so the main compilation loop doesn't process
983                // appended callee instructions (they're only used by compile_inline_call).
984                compiler.skip_ranges = callee_skip_ranges;
985
986                // Rebase feedback slots from absolute IP to sub-program-local indices.
987                let mut rebased_feedback = feedback;
988                rebased_feedback.rebase(func.entry_point);
989                // Merge callee feedback at their sub-program offsets so speculative
990                // guards fire inside inlined code.
991                for (callee_id, sub_offset) in &callee_feedback_offsets {
992                    if let Some(callee_fv) = callee_feedback.get(callee_id) {
993                        let mut callee_rebased = callee_fv.clone();
994                        let callee_func = &program.functions[*callee_id as usize];
995                        callee_rebased.rebase(callee_func.entry_point);
996                        rebased_feedback.merge_at_offset(&callee_rebased, *sub_offset);
997                    }
998                }
999                compiler.set_feedback(rebased_feedback);
1000
1001                let entry_params = compiler.builder.block_params(entry_block).to_vec();
1002                for arg_idx in 0..func.arity {
1003                    let arg_val = entry_params[(arg_idx as usize) + 1];
1004                    let var = compiler.get_or_create_local(arg_idx);
1005                    compiler.builder.def_var(var, arg_val);
1006                }
1007
1008                let result = compiler.compile()?;
1009                deopt_points = compiler.take_deopt_points();
1010
1011                // Verify deopt metadata consistency before rebasing.
1012                BytecodeToIR::verify_deopt_points(
1013                    &deopt_points,
1014                    &compiler.unboxed_int_locals,
1015                    &compiler.unboxed_f64_locals,
1016                )?;
1017
1018                // Rebase deopt resume_ip from sub-program-local to global program IP.
1019                //
1020                // Sub-program layout: [outer instrs @ 0..N] [callee1 @ N..N+M] ...
1021                // Each region maps back to a different base in the original program:
1022                //   - outer region [0, target_instr_count): rebase by +func.entry_point
1023                //   - callee region [rebased_entry, rebased_end): rebase to callee.entry_point
1024                //
1025                // rebase_ip closure handles both regions.
1026                let rebase_ip = |sub_ip: usize| -> usize {
1027                    for (callee_id, candidate) in &callee_inline_map {
1028                        let re = candidate.entry_point;
1029                        let re_end = re + candidate.instruction_count;
1030                        if sub_ip >= re && sub_ip < re_end {
1031                            let callee_entry = program.functions[*callee_id as usize].entry_point;
1032                            return callee_entry + (sub_ip - re);
1033                        }
1034                    }
1035                    // Falls in the outer function's region
1036                    func.entry_point + sub_ip
1037                };
1038
1039                for dp in &mut deopt_points {
1040                    dp.resume_ip = rebase_ip(dp.resume_ip);
1041                    for iframe in &mut dp.inline_frames {
1042                        iframe.resume_ip = rebase_ip(iframe.resume_ip);
1043                    }
1044                }
1045                shape_guards = compiler.take_shape_guards();
1046                drop(compiler);
1047
1048                builder.ins().return_(&[result]);
1049                builder.finalize();
1050            }
1051
1052            self.module
1053                .define_function(target_dc_func_id, &mut ctx)
1054                .map_err(|e| format!("Failed to define target function: {:?}", e))?;
1055            self.module.clear_context(&mut ctx);
1056
1057            // Record this function's direct-call FuncId for cross-function
1058            // speculation by future compilations.
1059            self.compiled_dc_funcs
1060                .insert(func_index as u16, (target_dc_func_id, func.arity));
1061        }
1062
1063        // Phase 2: Create JitFnPtr wrapper that marshals from (ctx_ptr, unused) -> u64
1064        // to (ctx_ptr, args...) -> i32 ABI.
1065        let wrapper_name = format!("opt_wrapper_{}", func.name);
1066        let mut wrapper_sig = self.module.make_signature();
1067        wrapper_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
1068        wrapper_sig.params.push(AbiParam::new(types::I64)); // unused
1069        wrapper_sig.returns.push(AbiParam::new(types::I64)); // NaN-boxed result
1070
1071        let wrapper_func_id = self
1072            .module
1073            .declare_function(&wrapper_name, Linkage::Export, &wrapper_sig)
1074            .map_err(|e| format!("Failed to declare wrapper: {}", e))?;
1075
1076        {
1077            let mut ctx = self.module.make_context();
1078            ctx.func.signature = wrapper_sig;
1079
1080            let mut func_builder_ctx = FunctionBuilderContext::new();
1081            {
1082                let mut builder = FunctionBuilder::new(&mut ctx.func, &mut func_builder_ctx);
1083                let entry_block = builder.create_block();
1084                builder.append_block_params_for_function_params(entry_block);
1085                builder.switch_to_block(entry_block);
1086                builder.seal_block(entry_block);
1087
1088                let ctx_ptr = builder.block_params(entry_block)[0];
1089
1090                // Import the target direct-call function
1091                let target_ref = self
1092                    .module
1093                    .declare_func_in_func(target_dc_func_id, builder.func);
1094
1095                // Load args from JITContext locals area and call the target
1096                const LOCALS_BYTE_OFFSET: i32 = 64;
1097                let mut call_args = vec![ctx_ptr];
1098                for arg_idx in 0..func.arity {
1099                    let offset = LOCALS_BYTE_OFFSET + (arg_idx as i32) * 8;
1100                    let arg_val =
1101                        builder
1102                            .ins()
1103                            .load(types::I64, MemFlags::trusted(), ctx_ptr, offset);
1104                    call_args.push(arg_val);
1105                }
1106
1107                let inst = builder.ins().call(target_ref, &call_args);
1108                let signal = builder.inst_results(inst)[0]; // i32
1109
1110                // Convert signal to JitFnPtr return value:
1111                // signal < 0 → u64::MAX (deopt), signal >= 0 → load result from ctx stack
1112                use crate::context::STACK_OFFSET;
1113                let zero = builder.ins().iconst(types::I32, 0);
1114                let is_deopt = builder.ins().icmp(IntCC::SignedLessThan, signal, zero);
1115
1116                let deopt_block = builder.create_block();
1117                let success_block = builder.create_block();
1118                let merge_block = builder.create_block();
1119                builder.append_block_param(merge_block, types::I64);
1120
1121                builder
1122                    .ins()
1123                    .brif(is_deopt, deopt_block, &[], success_block, &[]);
1124
1125                builder.switch_to_block(deopt_block);
1126                builder.seal_block(deopt_block);
1127                let deopt_sentinel = builder.ins().iconst(types::I64, -1i64);
1128                builder.ins().jump(merge_block, &[deopt_sentinel]);
1129
1130                builder.switch_to_block(success_block);
1131                builder.seal_block(success_block);
1132                let result_val =
1133                    builder
1134                        .ins()
1135                        .load(types::I64, MemFlags::trusted(), ctx_ptr, STACK_OFFSET);
1136                builder.ins().jump(merge_block, &[result_val]);
1137
1138                builder.switch_to_block(merge_block);
1139                builder.seal_block(merge_block);
1140                let ret_val = builder.block_params(merge_block)[0];
1141                builder.ins().return_(&[ret_val]);
1142                builder.finalize();
1143            }
1144
1145            self.module
1146                .define_function(wrapper_func_id, &mut ctx)
1147                .map_err(|e| format!("Failed to define wrapper: {:?}", e))?;
1148            self.module.clear_context(&mut ctx);
1149        }
1150
1151        // Phase 3: Finalize and return the wrapper code pointer.
1152        self.module
1153            .finalize_definitions()
1154            .map_err(|e| format!("Failed to finalize: {:?}", e))?;
1155
1156        let code_ptr = self.module.get_finalized_function(wrapper_func_id);
1157        self.compiled_functions.insert(wrapper_name, code_ptr);
1158
1159        Ok((code_ptr, deopt_points, shape_guards))
1160    }
1161
1162    /// Selectively compile a program, JIT-compiling compatible functions and
1163    /// falling back to interpreter entries for incompatible ones.
1164    ///
1165    /// Returns a `MixedFunctionTable` mapping each function index to either
1166    /// a `Native` pointer (JIT-compiled) or `Interpreted` marker.
1167    ///
1168    /// The main strategy body is always compiled. Only user-defined functions
1169    /// go through per-function preflight.
1170    pub fn compile_program_selective(
1171        &mut self,
1172        name: &str,
1173        program: &BytecodeProgram,
1174    ) -> Result<(JittedStrategyFn, MixedFunctionTable), String> {
1175        use super::accessors::preflight_instructions;
1176
1177        maybe_emit_numeric_metrics(program);
1178
1179        // Phase 1: Per-function preflight to classify each function.
1180        let mut jit_compatible: Vec<bool> = Vec::with_capacity(program.functions.len());
1181
1182        for (idx, func) in program.functions.iter().enumerate() {
1183            if func.body_length == 0 {
1184                jit_compatible.push(false);
1185                continue;
1186            }
1187            let func_end = func.entry_point + func.body_length;
1188            let instructions = &program.instructions[func.entry_point..func_end];
1189            let report = preflight_instructions(instructions);
1190            jit_compatible.push(report.can_jit());
1191        }
1192
1193        // Phase 1b: Preflight main code (non-stdlib, non-function-body instructions).
1194        // Without this, unsupported builtins in top-level code slip through.
1195        {
1196            let skip_ranges = Self::compute_skip_ranges(program);
1197            let main_instructions: Vec<_> = program
1198                .instructions
1199                .iter()
1200                .enumerate()
1201                .filter(|(i, _)| !skip_ranges.iter().any(|(s, e)| *i >= *s && *i < *e))
1202                .map(|(_, instr)| instr.clone())
1203                .collect();
1204            let main_report = preflight_instructions(&main_instructions);
1205            if !main_report.can_jit() {
1206                return Err(format!(
1207                    "Main code contains unsupported constructs: {:?}",
1208                    main_report
1209                ));
1210            }
1211        }
1212
1213        // Phase 2: Pre-declare ALL functions (both JIT and interpreted) in
1214        // Cranelift so that JIT functions can call other JIT functions.
1215        // Interpreted functions get declared too (for uniform call tables)
1216        // but won't have a body defined - they'll use the trampoline.
1217        let mut user_func_arities: HashMap<u16, u16> = HashMap::new();
1218        let mut user_func_ids: HashMap<u16, cranelift_module::FuncId> = HashMap::new();
1219
1220        for (idx, func) in program.functions.iter().enumerate() {
1221            if !jit_compatible[idx] {
1222                user_func_arities.insert(idx as u16, func.arity);
1223                continue;
1224            }
1225            // Use function index in the name to avoid collisions between
1226            // closures with the same auto-generated name but different arities
1227            // (e.g., multiple __closure_0 from different stdlib modules).
1228            let func_name = format!("{}_f{}_{}", name, idx, func.name);
1229            let mut user_sig = self.module.make_signature();
1230            user_sig.params.push(AbiParam::new(types::I64)); // ctx_ptr
1231            for _ in 0..func.arity {
1232                user_sig.params.push(AbiParam::new(types::I64));
1233            }
1234            user_sig.returns.push(AbiParam::new(types::I32));
1235            let func_id = self
1236                .module
1237                .declare_function(&func_name, Linkage::Local, &user_sig)
1238                .map_err(|e| format!("Failed to pre-declare function {}: {}", func.name, e))?;
1239            user_func_ids.insert(idx as u16, func_id);
1240            user_func_arities.insert(idx as u16, func.arity);
1241        }
1242
1243        // Phase 3: Compile main strategy body.
1244        let main_func_id = self.compile_strategy_with_user_funcs(
1245            name,
1246            program,
1247            &user_func_ids,
1248            &user_func_arities,
1249        )?;
1250
1251        // Phase 4: Compile only JIT-compatible function bodies.
1252        for (idx, func) in program.functions.iter().enumerate() {
1253            if !jit_compatible[idx] {
1254                continue;
1255            }
1256            let func_name = format!("{}_f{}_{}", name, idx, func.name);
1257            self.compile_function_with_user_funcs(
1258                &func_name,
1259                program,
1260                idx,
1261                &user_func_ids,
1262                &user_func_arities,
1263            )?;
1264        }
1265
1266        self.module
1267            .finalize_definitions()
1268            .map_err(|e| format!("Failed to finalize definitions: {:?}", e))?;
1269
1270        let main_code_ptr = self.module.get_finalized_function(main_func_id);
1271        self.compiled_functions
1272            .insert(name.to_string(), main_code_ptr);
1273
1274        // Phase 5: Build the MixedFunctionTable.
1275        let mut mixed_table = MixedFunctionTable::with_capacity(program.functions.len());
1276
1277        self.function_table.clear();
1278        for (idx, func) in program.functions.iter().enumerate() {
1279            if jit_compatible[idx] {
1280                if let Some(&func_id) = user_func_ids.get(&(idx as u16)) {
1281                    let ptr = self.module.get_finalized_function(func_id);
1282                    while self.function_table.len() <= idx {
1283                        self.function_table.push(std::ptr::null());
1284                    }
1285                    self.function_table[idx] = ptr;
1286                    let func_name = format!("{}_f{}_{}", name, idx, func.name);
1287                    self.compiled_functions.insert(func_name, ptr);
1288                    mixed_table.insert(idx, FunctionEntry::Native(ptr));
1289                }
1290            } else {
1291                while self.function_table.len() <= idx {
1292                    self.function_table.push(std::ptr::null());
1293                }
1294                // Leave function_table[idx] as null for interpreted functions.
1295                mixed_table.insert(idx, FunctionEntry::Interpreted(idx as u16));
1296            }
1297        }
1298
1299        let jit_fn = unsafe { std::mem::transmute(main_code_ptr) };
1300        Ok((jit_fn, mixed_table))
1301    }
1302}