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