1use 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 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 | 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 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 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)); 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)); 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 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 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 let mut sig = self.module.make_signature();
558 sig.params.push(AbiParam::new(types::I64)); sig.params.push(AbiParam::new(types::I64)); sig.returns.push(AbiParam::new(types::I64)); 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 let ffi = self.build_ffi_refs(&mut builder);
585
586 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 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 if let Some(mut fv) = feedback {
660 fv.rebase(func.entry_point);
661 compiler.set_feedback(fv);
662 }
663
664 const LOCALS_BYTE_OFFSET: i32 = 64; 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 for dp in &mut deopt_points {
682 dp.resume_ip += func.entry_point;
683 }
684 shape_guards = compiler.take_shape_guards();
685 drop(compiler);
687
688 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 builder.switch_to_block(deopt_block);
708 builder.seal_block(deopt_block);
709 let deopt_sentinel = builder.ins().iconst(types::I64, -1i64); builder.ins().jump(merge_block, &[deopt_sentinel]);
711
712 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 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 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 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)); for _ in 0..func.arity {
793 dc_sig.params.push(AbiParam::new(types::I64));
794 }
795 dc_sig.returns.push(AbiParam::new(types::I32)); 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)); 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 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 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 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 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 let full_candidates = BytecodeToIR::analyze_inline_candidates(program);
890 let mut sub_instructions_vec = target_instructions.to_vec();
891 let mut callee_inline_map: HashMap<u16, InlineCandidate> = HashMap::new();
893 let mut callee_skip_ranges: Vec<(usize, usize)> = Vec::new();
895 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 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 {
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 for (callee_id, candidate) in &callee_inline_map {
980 compiler.inline_candidates.insert(*callee_id, candidate.clone());
981 }
982 compiler.skip_ranges = callee_skip_ranges;
985
986 let mut rebased_feedback = feedback;
988 rebased_feedback.rebase(func.entry_point);
989 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 BytecodeToIR::verify_deopt_points(
1013 &deopt_points,
1014 &compiler.unboxed_int_locals,
1015 &compiler.unboxed_f64_locals,
1016 )?;
1017
1018 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 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 self.compiled_dc_funcs
1060 .insert(func_index as u16, (target_dc_func_id, func.arity));
1061 }
1062
1063 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)); wrapper_sig.params.push(AbiParam::new(types::I64)); wrapper_sig.returns.push(AbiParam::new(types::I64)); 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 let target_ref = self
1092 .module
1093 .declare_func_in_func(target_dc_func_id, builder.func);
1094
1095 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]; 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 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 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 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 {
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 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 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)); 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 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 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 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 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}