1#![allow(unsafe_code)]
15#![allow(clippy::similar_names)]
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19use std::sync::atomic::{AtomicU64, Ordering};
20use std::sync::{Arc, RwLock};
21
22use cranelift_codegen::Context;
23use cranelift_codegen::ir::condcodes::{FloatCC, IntCC};
24use cranelift_codegen::ir::{AbiParam, BlockArg, InstBuilder, MemFlags, Signature, Value, types};
25use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
26use cranelift_jit::{JITBuilder, JITModule};
27use cranelift_module::{Linkage, Module};
28
29use crate::ast::projection::{AstNode, AstProjection};
30use crate::dag::symbol::{FnId, OpKind, SymbolKind};
31use crate::jit::analysis::{NodeAnalysis, PowExpansion, analyze};
32use crate::jit::passes;
33
34pub type CompiledExprFn = extern "C" fn(*const f64) -> f64;
39
40pub type CustomFn1 = extern "C" fn(f64) -> f64;
45
46pub type CustomFn2 = extern "C" fn(f64, f64) -> f64;
51
52pub type CustomFn3 = extern "C" fn(f64, f64, f64) -> f64;
57
58pub type CompiledBatchFn =
68 extern "C" fn(vars_cols: *const *const f64, n_rows: usize, out: *mut f64);
69
70#[derive(Debug, Clone)]
72#[allow(clippy::struct_excessive_bools)]
75pub struct OptConfig {
76 pub max_int_pow: u32,
79 pub expand_sqrt: bool,
81 pub allow_reciprocal_math: bool,
84 pub elide_nan_guard: bool,
87 pub enable_cse: bool,
89}
90
91impl Default for OptConfig {
92 fn default() -> Self {
93 Self {
94 max_int_pow: 16,
95 expand_sqrt: true,
96 allow_reciprocal_math: false,
97 elide_nan_guard: true,
98 enable_cse: true,
99 }
100 }
101}
102
103extern "C" fn jit_powf(base: f64, exp: f64) -> f64 {
104 base.powf(exp)
105}
106
107extern "C" fn jit_fmod(lhs: f64, rhs: f64) -> f64 {
108 lhs % rhs
109}
110
111#[derive(Clone, Copy)]
113struct CustomFnEntry {
114 ptr: usize,
116 arity: u8,
118}
119
120type CustomFnRegistry = Arc<RwLock<HashMap<u32, CustomFnEntry>>>;
127
128static JIT_FUNC_COUNTER: AtomicU64 = AtomicU64::new(0);
136
137pub struct JitCompiler {
139 module: JITModule,
140 builder_ctx: FunctionBuilderContext,
141 custom_fns: CustomFnRegistry,
146 work_stack: Vec<Frame>,
150 work_values: Vec<Value>,
152 custom_op_registry: Option<Arc<crate::custom::descriptor::CustomOpRegistry>>,
157}
158
159impl std::fmt::Debug for JitCompiler {
160 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
161 f.debug_struct("JitCompiler").finish_non_exhaustive()
162 }
163}
164
165impl Default for JitCompiler {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171impl JitCompiler {
172 pub fn try_new() -> Result<Self, crate::error::JitError> {
184 let mut isa_builder =
185 cranelift_native::builder().map_err(|_| crate::error::JitError::InitFailed)?;
186
187 #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
188 {
189 for flag in &[
193 "has_sse3",
194 "has_ssse3",
195 "has_sse41",
196 "has_sse42",
197 "has_avx",
198 "has_avx2",
199 "has_bmi1",
200 "has_bmi2",
201 "has_lzcnt",
202 "has_popcnt",
203 ] {
204 let _ = cranelift_codegen::settings::Configurable::enable(&mut isa_builder, flag);
205 }
206 }
207
208 #[cfg(target_arch = "aarch64")]
209 {
210 for flag in &["has_lse", "has_pauth", "has_sve"] {
213 let _ = cranelift_codegen::settings::Configurable::enable(&mut isa_builder, flag);
214 }
215 }
216
217 let mut flag_builder = cranelift_codegen::settings::builder();
218 cranelift_codegen::settings::Configurable::set(&mut flag_builder, "opt_level", "speed")
220 .map_err(|_| crate::error::JitError::InitFailed)?;
221 let _ = cranelift_codegen::settings::Configurable::set(
224 &mut flag_builder,
225 "enable_verifier",
226 "false",
227 );
228 let _ = cranelift_codegen::settings::Configurable::set(
230 &mut flag_builder,
231 "enable_probestack",
232 "false",
233 );
234
235 let isa = isa_builder
236 .finish(cranelift_codegen::settings::Flags::new(flag_builder))
237 .map_err(|_| crate::error::JitError::InitFailed)?;
238
239 let mut builder = JITBuilder::with_isa(isa, cranelift_module::default_libcall_names());
240
241 builder.symbol("powf", jit_powf as *const u8);
242 builder.symbol("fmod", jit_fmod as *const u8);
243
244 let custom_fns: CustomFnRegistry = Arc::new(RwLock::new(HashMap::new()));
245 let lookup_registry = Arc::clone(&custom_fns);
246 builder.symbol_lookup_fn(Box::new(move |name: &str| -> Option<*const u8> {
247 let id_str = name.strip_prefix("rssn_custom_fn_")?;
248 let id: u32 = id_str.parse().ok()?;
249 let guard = lookup_registry
250 .read()
251 .unwrap_or_else(std::sync::PoisonError::into_inner);
252 guard.get(&id).map(|entry| entry.ptr as *const u8)
253 }));
254
255 let module = JITModule::new(builder);
256 Ok(Self {
257 module,
258 builder_ctx: FunctionBuilderContext::new(),
259 custom_fns,
260 work_stack: Vec::with_capacity(64),
261 work_values: Vec::with_capacity(64),
262 custom_op_registry: None,
263 })
264 }
265
266 #[must_use]
272 pub fn new() -> Self {
273 Self::try_new().expect("JIT compiler initialization failed")
274 }
275
276 pub fn register_custom_function(&self, fn_id: FnId, func: CustomFn1) {
283 let mut guard = self
286 .custom_fns
287 .write()
288 .unwrap_or_else(std::sync::PoisonError::into_inner);
289 guard.insert(
290 fn_id.0,
291 CustomFnEntry {
292 ptr: func as usize,
293 arity: 1,
294 },
295 );
296 }
297
298 pub fn register_custom_function_2(&self, fn_id: FnId, func: CustomFn2) {
305 let mut guard = self
306 .custom_fns
307 .write()
308 .unwrap_or_else(std::sync::PoisonError::into_inner);
309 guard.insert(
310 fn_id.0,
311 CustomFnEntry {
312 ptr: func as usize,
313 arity: 2,
314 },
315 );
316 }
317
318 pub fn register_custom_function_3(&self, fn_id: FnId, func: CustomFn3) {
324 let mut guard = self
325 .custom_fns
326 .write()
327 .unwrap_or_else(std::sync::PoisonError::into_inner);
328 guard.insert(
329 fn_id.0,
330 CustomFnEntry {
331 ptr: func as usize,
332 arity: 3,
333 },
334 );
335 }
336
337 pub fn set_custom_op_registry(
351 &mut self,
352 registry: Arc<crate::custom::descriptor::CustomOpRegistry>,
353 ) {
354 let mut guard = self
356 .custom_fns
357 .write()
358 .unwrap_or_else(std::sync::PoisonError::into_inner);
359 for desc in registry.ops_iter() {
360 let (ptr, arity) = match desc.eval_fn {
361 crate::custom::descriptor::EvalFn::Arity1(f) => (f as usize, 1u8),
362 crate::custom::descriptor::EvalFn::Arity2(f) => (f as usize, 2u8),
363 crate::custom::descriptor::EvalFn::Arity3(f) => (f as usize, 3u8),
364 };
365 guard.insert(desc.fn_id.0, CustomFnEntry { ptr, arity });
366 }
367 drop(guard);
368 self.custom_op_registry = Some(registry);
369 }
370
371 pub fn compile(
379 &mut self,
380 ast: &AstProjection,
381 ) -> Result<CompiledExprFn, crate::error::JitError> {
382 self.compile_with_opts(ast, &OptConfig::default())
383 }
384
385 pub fn compile_with_opts(
391 &mut self,
392 ast: &AstProjection,
393 opts: &OptConfig,
394 ) -> Result<CompiledExprFn, crate::error::JitError> {
395 if ast.is_empty() {
396 return crate::error::cold_jit_error_malformed_node();
397 }
398
399 let analysis = analyze(ast);
401
402 let mut ctx = Context::new();
403
404 ctx.func.signature.call_conv = self.module.target_config().default_call_conv;
414
415 let ptr_type = self.module.target_config().pointer_type();
417 ctx.func.signature.params.push(AbiParam::new(ptr_type));
418 ctx.func.signature.returns.push(AbiParam::new(types::F64));
419
420 let mut func_builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_ctx);
421 let entry_block = func_builder.create_block();
422 func_builder.append_block_params_for_function_params(entry_block);
423 func_builder.switch_to_block(entry_block);
424 func_builder.seal_block(entry_block);
425
426 let vars_ptr = func_builder.block_params(entry_block)[0];
427
428 let mut powf_sig = Signature::new(self.module.target_config().default_call_conv);
430 powf_sig.params.push(AbiParam::new(types::F64));
431 powf_sig.params.push(AbiParam::new(types::F64));
432 powf_sig.returns.push(AbiParam::new(types::F64));
433
434 let powf_name = self
435 .module
436 .declare_function("powf", Linkage::Import, &powf_sig)
437 .map_err(|_| crate::error::JitError::InitFailed)?;
438 let powf_func_ref = self
439 .module
440 .declare_func_in_func(powf_name, func_builder.func);
441
442 let fmod_name = self
444 .module
445 .declare_function("fmod", Linkage::Import, &powf_sig)
446 .map_err(|_| crate::error::JitError::InitFailed)?;
447 let fmod_func_ref = self
448 .module
449 .declare_func_in_func(fmod_name, func_builder.func);
450
451 let mut custom_refs: HashMap<u32, cranelift_codegen::ir::FuncRef> = HashMap::new();
455
456 let registered_entries: HashMap<u32, u8> = {
459 let guard = self
460 .custom_fns
461 .read()
462 .unwrap_or_else(std::sync::PoisonError::into_inner);
463 guard.iter().map(|(&id, e)| (id, e.arity)).collect()
464 };
465
466 let default_call_conv = self.module.target_config().default_call_conv;
468
469 let make_fn_sig = |arity: u8| -> Signature {
471 let mut sig = Signature::new(default_call_conv);
472 for _ in 0..arity {
473 sig.params.push(AbiParam::new(types::F64));
474 }
475 sig.returns.push(AbiParam::new(types::F64));
476 sig
477 };
478
479 for node in &ast.nodes {
480 if let SymbolKind::Function(fn_id) = node.kind {
481 if custom_refs.contains_key(&fn_id.0) {
482 continue;
483 }
484 let arity = registered_entries
485 .get(&fn_id.0)
486 .copied()
487 .ok_or(crate::error::JitError::UnknownFunction)?;
488 let sig = make_fn_sig(arity);
489 let sym = format!("rssn_custom_fn_{}", fn_id.0);
490 let fid = self
491 .module
492 .declare_function(&sym, Linkage::Import, &sig)
493 .map_err(|_| crate::error::JitError::InitFailed)?;
494 let fr = self.module.declare_func_in_func(fid, func_builder.func);
495 custom_refs.insert(fn_id.0, fr);
496 }
497 }
498
499 self.work_stack.clear();
502 self.work_values.clear();
503 let root_val = compile_ast_iterative(
504 ast,
505 &analysis,
506 opts,
507 &mut func_builder,
508 vars_ptr,
509 powf_func_ref,
510 fmod_func_ref,
511 &custom_refs,
512 &mut self.work_stack,
513 &mut self.work_values,
514 )?;
515
516 func_builder.ins().return_(&[root_val]);
517 func_builder.finalize();
518
519 let fn_name = format!("expr_{}", JIT_FUNC_COUNTER.fetch_add(1, Ordering::Relaxed));
520 let func_id = self
521 .module
522 .declare_function(&fn_name, Linkage::Export, &ctx.func.signature)
523 .map_err(|_| crate::error::JitError::VerifierRejected)?;
524
525 self.module
526 .define_function(func_id, &mut ctx)
527 .map_err(|_| crate::error::JitError::VerifierRejected)?;
528
529 self.module.clear_context(&mut ctx);
530
531 self.module
532 .finalize_definitions()
533 .map_err(|_| crate::error::JitError::VerifierRejected)?;
534
535 let code_ptr = self.module.get_finalized_function(func_id);
536
537 let compiled_fn: CompiledExprFn = unsafe { std::mem::transmute(code_ptr) };
540 Ok(compiled_fn)
541 }
542
543 pub fn compile_batch_f64x2(
571 &mut self,
572 ast: &AstProjection,
573 ) -> Result<Option<CompiledBatchFn>, crate::error::JitError> {
574 if ast.is_empty() {
575 return crate::error::cold_jit_error_malformed_node().map(Some);
576 }
577
578 let analysis = analyze(ast);
579 let opts = OptConfig::default();
580
581 if !is_vectorizable_ast(ast, &analysis, self.custom_op_registry.as_deref()) {
584 return Ok(None);
585 }
586
587 let sym_ids: Vec<u32> = {
590 let mut seen: HashSet<u32> = HashSet::new();
591 let mut ordered: Vec<u32> = Vec::new();
592 for node in &ast.nodes {
593 if let SymbolKind::Variable(sid) = node.kind
594 && seen.insert(sid.0)
595 {
596 ordered.push(sid.0);
597 }
598 }
599 ordered
600 };
601
602 let mut ctx = Context::new();
603
604 ctx.func.signature.call_conv = self.module.target_config().default_call_conv;
609
610 let ptr_type = self.module.target_config().pointer_type();
613 ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); let mut func_builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_ctx);
618
619 let mut powf_sig = Signature::new(self.module.target_config().default_call_conv);
622 powf_sig.params.push(AbiParam::new(types::F64));
623 powf_sig.params.push(AbiParam::new(types::F64));
624 powf_sig.returns.push(AbiParam::new(types::F64));
625 let powf_name = self
626 .module
627 .declare_function("powf", Linkage::Import, &powf_sig)
628 .map_err(|_| crate::error::JitError::InitFailed)?;
629
630 let entry_block = func_builder.create_block();
632 let loop_check = func_builder.create_block();
633 let vec_body = func_builder.create_block();
634 let scalar_check = func_builder.create_block();
635 let scalar_body = func_builder.create_block();
636 let ret_block = func_builder.create_block();
637
638 func_builder.append_block_params_for_function_params(entry_block);
640 func_builder.append_block_param(loop_check, ptr_type); func_builder.append_block_param(vec_body, ptr_type); func_builder.append_block_param(scalar_check, ptr_type); func_builder.append_block_param(scalar_body, ptr_type); func_builder.switch_to_block(entry_block);
647 let params = func_builder.block_params(entry_block);
648 let vars_cols_val = params[0];
649 let n_rows_val = params[1];
650 let out_ptr_val = params[2];
651
652 let ptr_size = i64::from(ptr_type.bytes());
654 let mut var_col_ptrs = HashMap::new();
655 for &sid in &sym_ids {
656 let col_offset = func_builder
657 .ins()
658 .iconst(ptr_type, i64::from(sid).wrapping_mul(ptr_size));
659 let col_ptr_addr = func_builder.ins().iadd(vars_cols_val, col_offset);
660 let col_ptr = func_builder
661 .ins()
662 .load(ptr_type, MemFlags::new(), col_ptr_addr, 0);
663 var_col_ptrs.insert(sid, col_ptr);
664 }
665
666 let zero_i = func_builder.ins().iconst(ptr_type, 0);
667 let zero_i_ba = BlockArg::Value(zero_i);
668 func_builder.ins().jump(loop_check, &[zero_i_ba]);
669 func_builder.seal_block(entry_block);
670
671 func_builder.switch_to_block(loop_check);
673 let i_lc = func_builder.block_params(loop_check)[0];
674 let remaining = func_builder.ins().isub(n_rows_val, i_lc);
675 let two_i = func_builder.ins().iconst(ptr_type, 2);
676 let can_vec = func_builder
677 .ins()
678 .icmp(IntCC::UnsignedGreaterThanOrEqual, remaining, two_i);
679 let i_lc_ba = BlockArg::Value(i_lc);
680 func_builder
681 .ins()
682 .brif(can_vec, vec_body, &[i_lc_ba], scalar_check, &[i_lc_ba]);
683
684 func_builder.switch_to_block(vec_body);
689 let i_vb = func_builder.block_params(vec_body)[0];
690
691 let byte_off_vec = func_builder.ins().ishl_imm(i_vb, 3);
693
694 let mut var_vals_vec: HashMap<u32, Value> = HashMap::new();
696 for &sid in &sym_ids {
697 let col_ptr = *var_col_ptrs
698 .get(&sid)
699 .ok_or(crate::error::JitError::MalformedNode)?;
700 let elem_addr = func_builder.ins().iadd(col_ptr, byte_off_vec);
701 let vec_val = func_builder
703 .ins()
704 .load(types::F64X2, MemFlags::new(), elem_addr, 0);
705 var_vals_vec.insert(sid, vec_val);
706 }
707
708 let powf_func_ref_vb = self
711 .module
712 .declare_func_in_func(powf_name, func_builder.func);
713
714 let result_vec = emit_ast_simd_f64x2(
716 ast,
717 &analysis,
718 &opts,
719 &mut func_builder,
720 &var_vals_vec,
721 powf_func_ref_vb,
722 )?;
723
724 let out_addr_vec = func_builder.ins().iadd(out_ptr_val, byte_off_vec);
726 func_builder
727 .ins()
728 .store(MemFlags::new(), result_vec, out_addr_vec, 0);
729
730 let i_vb_next = func_builder.ins().iadd_imm(i_vb, 2);
731 let i_vb_next_ba = BlockArg::Value(i_vb_next);
732 func_builder.ins().jump(loop_check, &[i_vb_next_ba]);
733
734 func_builder.seal_block(loop_check);
736 func_builder.seal_block(vec_body);
737
738 func_builder.switch_to_block(scalar_check);
740 let i_scheck = func_builder.block_params(scalar_check)[0];
741 let done = func_builder
742 .ins()
743 .icmp(IntCC::UnsignedGreaterThanOrEqual, i_scheck, n_rows_val);
744 let i_scheck_ba = BlockArg::Value(i_scheck);
745 func_builder.ins().brif(
746 done,
747 ret_block,
748 &[] as &[BlockArg],
749 scalar_body,
750 &[i_scheck_ba],
751 );
752
753 func_builder.switch_to_block(scalar_body);
755 let i_sbody = func_builder.block_params(scalar_body)[0];
756
757 let byte_off_sb = func_builder.ins().ishl_imm(i_sbody, 3);
758 let mut var_vals_sb: HashMap<u32, Value> = HashMap::new();
759 for &sid in &sym_ids {
760 let col_ptr = *var_col_ptrs
761 .get(&sid)
762 .ok_or(crate::error::JitError::MalformedNode)?;
763 let addr = func_builder.ins().iadd(col_ptr, byte_off_sb);
764 let v = func_builder
765 .ins()
766 .load(types::F64, MemFlags::new(), addr, 0);
767 var_vals_sb.insert(sid, v);
768 }
769
770 let powf_ref_sbody = self
771 .module
772 .declare_func_in_func(powf_name, func_builder.func);
773
774 let mut work_stack_sb: Vec<Frame> = Vec::with_capacity(32);
775 let mut work_vals_sb: Vec<Value> = Vec::with_capacity(32);
776 let res_sb = emit_ast_scalar_with_vars(
777 ast,
778 &analysis,
779 &opts,
780 &mut func_builder,
781 &var_vals_sb,
782 powf_ref_sbody,
783 &HashMap::new(),
784 &mut work_stack_sb,
785 &mut work_vals_sb,
786 )?;
787
788 let out_addr_sb = func_builder.ins().iadd(out_ptr_val, byte_off_sb);
789 func_builder
790 .ins()
791 .store(MemFlags::new(), res_sb, out_addr_sb, 0);
792
793 let i_sbody_next = func_builder.ins().iadd_imm(i_sbody, 1);
794 let i_sbody_next_ba = BlockArg::Value(i_sbody_next);
795 func_builder.ins().jump(scalar_check, &[i_sbody_next_ba]);
796
797 func_builder.seal_block(scalar_check);
798 func_builder.seal_block(scalar_body);
799
800 func_builder.switch_to_block(ret_block);
802 func_builder.ins().return_(&[]);
803 func_builder.seal_block(ret_block);
804
805 func_builder.finalize();
806
807 let fn_name = format!(
808 "batch_expr_{}",
809 JIT_FUNC_COUNTER.fetch_add(1, Ordering::Relaxed)
810 );
811 let func_id = self
812 .module
813 .declare_function(&fn_name, Linkage::Export, &ctx.func.signature)
814 .map_err(|_| crate::error::JitError::VerifierRejected)?;
815
816 self.module
817 .define_function(func_id, &mut ctx)
818 .map_err(|_| crate::error::JitError::VerifierRejected)?;
819
820 self.module.clear_context(&mut ctx);
821
822 self.module
823 .finalize_definitions()
824 .map_err(|_| crate::error::JitError::VerifierRejected)?;
825
826 let code_ptr = self.module.get_finalized_function(func_id);
827
828 let batch_fn: CompiledBatchFn = unsafe { std::mem::transmute(code_ptr) };
831 Ok(Some(batch_fn))
832 }
833
834 pub fn compile_batch_f64x4(
848 &mut self,
849 ast: &AstProjection,
850 ) -> Result<Option<CompiledBatchFn>, crate::error::JitError> {
851 if ast.is_empty() {
852 return crate::error::cold_jit_error_malformed_node().map(Some);
853 }
854
855 let analysis = analyze(ast);
856 let opts = OptConfig::default();
857
858 if !is_vectorizable_ast(ast, &analysis, self.custom_op_registry.as_deref()) {
860 return Ok(None);
861 }
862
863 let sym_ids: Vec<u32> = {
865 let mut seen: HashSet<u32> = HashSet::new();
866 let mut ordered: Vec<u32> = Vec::new();
867 for node in &ast.nodes {
868 if let SymbolKind::Variable(sid) = node.kind
869 && seen.insert(sid.0)
870 {
871 ordered.push(sid.0);
872 }
873 }
874 ordered
875 };
876
877 let mut ctx = Context::new();
878 ctx.func.signature.call_conv = self.module.target_config().default_call_conv;
879
880 let ptr_type = self.module.target_config().pointer_type();
882 ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); let mut func_builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_ctx);
887
888 let mut powf_sig = Signature::new(self.module.target_config().default_call_conv);
889 powf_sig.params.push(AbiParam::new(types::F64));
890 powf_sig.params.push(AbiParam::new(types::F64));
891 powf_sig.returns.push(AbiParam::new(types::F64));
892 let powf_name = self
893 .module
894 .declare_function("powf", Linkage::Import, &powf_sig)
895 .map_err(|_| crate::error::JitError::InitFailed)?;
896
897 let entry_block = func_builder.create_block();
899 let loop_check = func_builder.create_block();
900 let vec_body = func_builder.create_block();
901 let scalar_check = func_builder.create_block();
902 let scalar_body = func_builder.create_block();
903 let ret_block = func_builder.create_block();
904
905 func_builder.append_block_params_for_function_params(entry_block);
906 func_builder.append_block_param(loop_check, ptr_type); func_builder.append_block_param(vec_body, ptr_type); func_builder.append_block_param(scalar_check, ptr_type); func_builder.append_block_param(scalar_body, ptr_type); func_builder.switch_to_block(entry_block);
913 let params = func_builder.block_params(entry_block);
914 let vars_cols_val = params[0];
915 let n_rows_val = params[1];
916 let out_ptr_val = params[2];
917
918 let ptr_size = i64::from(ptr_type.bytes());
920 let mut var_col_ptrs = HashMap::new();
921 for &sid in &sym_ids {
922 let col_offset = func_builder
923 .ins()
924 .iconst(ptr_type, i64::from(sid).wrapping_mul(ptr_size));
925 let col_ptr_addr = func_builder.ins().iadd(vars_cols_val, col_offset);
926 let col_ptr = func_builder
927 .ins()
928 .load(ptr_type, MemFlags::new(), col_ptr_addr, 0);
929 var_col_ptrs.insert(sid, col_ptr);
930 }
931
932 let zero_i = func_builder.ins().iconst(ptr_type, 0);
933 let zero_i_ba = BlockArg::Value(zero_i);
934 func_builder.ins().jump(loop_check, &[zero_i_ba]);
935 func_builder.seal_block(entry_block);
936
937 func_builder.switch_to_block(loop_check);
939 let i_lc = func_builder.block_params(loop_check)[0];
940 let remaining = func_builder.ins().isub(n_rows_val, i_lc);
941 let four_i = func_builder.ins().iconst(ptr_type, 4);
942 let can_vec = func_builder
943 .ins()
944 .icmp(IntCC::UnsignedGreaterThanOrEqual, remaining, four_i);
945 let i_lc_ba = BlockArg::Value(i_lc);
946 func_builder
947 .ins()
948 .brif(can_vec, vec_body, &[i_lc_ba], scalar_check, &[i_lc_ba]);
949
950 func_builder.switch_to_block(vec_body);
955 let i_vb = func_builder.block_params(vec_body)[0];
956
957 let byte_off_vec = func_builder.ins().ishl_imm(i_vb, 3);
959
960 let mut var_vals_vec1: HashMap<u32, Value> = HashMap::new();
964 let mut var_vals_vec2: HashMap<u32, Value> = HashMap::new();
965 for &sid in &sym_ids {
966 let col_ptr = *var_col_ptrs
967 .get(&sid)
968 .ok_or(crate::error::JitError::MalformedNode)?;
969 let elem_addr1 = func_builder.ins().iadd(col_ptr, byte_off_vec);
970 let vec_val1 = func_builder
971 .ins()
972 .load(types::F64X2, MemFlags::new(), elem_addr1, 0);
973 var_vals_vec1.insert(sid, vec_val1);
974
975 let elem_addr2 = func_builder.ins().iadd_imm(elem_addr1, 16); let vec_val2 = func_builder
977 .ins()
978 .load(types::F64X2, MemFlags::new(), elem_addr2, 0);
979 var_vals_vec2.insert(sid, vec_val2);
980 }
981
982 let powf_func_ref_vb = self
983 .module
984 .declare_func_in_func(powf_name, func_builder.func);
985
986 let result_vec1 = emit_ast_simd_f64x2(
988 ast,
989 &analysis,
990 &opts,
991 &mut func_builder,
992 &var_vals_vec1,
993 powf_func_ref_vb,
994 )?;
995
996 let result_vec2 = emit_ast_simd_f64x2(
997 ast,
998 &analysis,
999 &opts,
1000 &mut func_builder,
1001 &var_vals_vec2,
1002 powf_func_ref_vb,
1003 )?;
1004
1005 let out_addr1 = func_builder.ins().iadd(out_ptr_val, byte_off_vec);
1007 func_builder
1008 .ins()
1009 .store(MemFlags::new(), result_vec1, out_addr1, 0);
1010
1011 let out_addr2 = func_builder.ins().iadd_imm(out_addr1, 16);
1012 func_builder
1013 .ins()
1014 .store(MemFlags::new(), result_vec2, out_addr2, 0);
1015
1016 let i_vb_next = func_builder.ins().iadd_imm(i_vb, 4);
1017 let i_vb_next_ba = BlockArg::Value(i_vb_next);
1018 func_builder.ins().jump(loop_check, &[i_vb_next_ba]);
1019
1020 func_builder.seal_block(loop_check);
1021 func_builder.seal_block(vec_body);
1022
1023 func_builder.switch_to_block(scalar_check);
1025 let i_scheck = func_builder.block_params(scalar_check)[0];
1026 let done = func_builder
1027 .ins()
1028 .icmp(IntCC::UnsignedGreaterThanOrEqual, i_scheck, n_rows_val);
1029 let i_scheck_ba = BlockArg::Value(i_scheck);
1030 func_builder.ins().brif(
1031 done,
1032 ret_block,
1033 &[] as &[BlockArg],
1034 scalar_body,
1035 &[i_scheck_ba],
1036 );
1037
1038 func_builder.switch_to_block(scalar_body);
1040 let i_sbody = func_builder.block_params(scalar_body)[0];
1041
1042 let byte_off_sb = func_builder.ins().ishl_imm(i_sbody, 3);
1043 let mut var_vals_sb: HashMap<u32, Value> = HashMap::new();
1044 for &sid in &sym_ids {
1045 let col_ptr = *var_col_ptrs
1046 .get(&sid)
1047 .ok_or(crate::error::JitError::MalformedNode)?;
1048 let addr = func_builder.ins().iadd(col_ptr, byte_off_sb);
1049 let v = func_builder
1050 .ins()
1051 .load(types::F64, MemFlags::new(), addr, 0);
1052 var_vals_sb.insert(sid, v);
1053 }
1054
1055 let powf_ref_sbody = self
1056 .module
1057 .declare_func_in_func(powf_name, func_builder.func);
1058
1059 let mut work_stack_sb: Vec<Frame> = Vec::with_capacity(32);
1060 let mut work_vals_sb: Vec<Value> = Vec::with_capacity(32);
1061 let res_sb = emit_ast_scalar_with_vars(
1062 ast,
1063 &analysis,
1064 &opts,
1065 &mut func_builder,
1066 &var_vals_sb,
1067 powf_ref_sbody,
1068 &HashMap::new(),
1069 &mut work_stack_sb,
1070 &mut work_vals_sb,
1071 )?;
1072
1073 let out_addr_sb = func_builder.ins().iadd(out_ptr_val, byte_off_sb);
1074 func_builder
1075 .ins()
1076 .store(MemFlags::new(), res_sb, out_addr_sb, 0);
1077
1078 let i_sbody_next = func_builder.ins().iadd_imm(i_sbody, 1);
1079 let i_sbody_next_ba = BlockArg::Value(i_sbody_next);
1080 func_builder.ins().jump(scalar_check, &[i_sbody_next_ba]);
1081
1082 func_builder.seal_block(scalar_check);
1083 func_builder.seal_block(scalar_body);
1084
1085 func_builder.switch_to_block(ret_block);
1087 func_builder.ins().return_(&[]);
1088 func_builder.seal_block(ret_block);
1089
1090 func_builder.finalize();
1091
1092 let fn_name = format!(
1093 "batch_expr_{}",
1094 JIT_FUNC_COUNTER.fetch_add(1, Ordering::Relaxed)
1095 );
1096 let func_id = self
1097 .module
1098 .declare_function(&fn_name, Linkage::Export, &ctx.func.signature)
1099 .map_err(|_| crate::error::JitError::VerifierRejected)?;
1100
1101 self.module
1102 .define_function(func_id, &mut ctx)
1103 .map_err(|_| crate::error::JitError::VerifierRejected)?;
1104
1105 self.module.clear_context(&mut ctx);
1106
1107 self.module
1108 .finalize_definitions()
1109 .map_err(|_| crate::error::JitError::VerifierRejected)?;
1110
1111 let code_ptr = self.module.get_finalized_function(func_id);
1112
1113 let batch_fn: CompiledBatchFn = unsafe { std::mem::transmute(code_ptr) };
1114 Ok(Some(batch_fn))
1115 }
1116
1117 pub fn compile_batch_f64x8(
1134 &mut self,
1135 ast: &AstProjection,
1136 ) -> Result<Option<CompiledBatchFn>, crate::error::JitError> {
1137 if ast.is_empty() {
1138 return crate::error::cold_jit_error_malformed_node().map(Some);
1139 }
1140
1141 let analysis = analyze(ast);
1142 let opts = OptConfig::default();
1143
1144 if !is_vectorizable_ast(ast, &analysis, self.custom_op_registry.as_deref()) {
1145 return Ok(None);
1146 }
1147
1148 let sym_ids: Vec<u32> = {
1149 let mut seen: HashSet<u32> = HashSet::new();
1150 let mut ordered: Vec<u32> = Vec::new();
1151 for node in &ast.nodes {
1152 if let SymbolKind::Variable(sid) = node.kind
1153 && seen.insert(sid.0)
1154 {
1155 ordered.push(sid.0);
1156 }
1157 }
1158 ordered
1159 };
1160
1161 let mut ctx = Context::new();
1162 ctx.func.signature.call_conv = self.module.target_config().default_call_conv;
1163
1164 let ptr_type = self.module.target_config().pointer_type();
1165 ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); ctx.func.signature.params.push(AbiParam::new(ptr_type)); let mut func_builder = FunctionBuilder::new(&mut ctx.func, &mut self.builder_ctx);
1170
1171 let mut powf_sig = Signature::new(self.module.target_config().default_call_conv);
1172 powf_sig.params.push(AbiParam::new(types::F64));
1173 powf_sig.params.push(AbiParam::new(types::F64));
1174 powf_sig.returns.push(AbiParam::new(types::F64));
1175 let powf_name = self
1176 .module
1177 .declare_function("powf", Linkage::Import, &powf_sig)
1178 .map_err(|_| crate::error::JitError::InitFailed)?;
1179
1180 let entry_block = func_builder.create_block();
1181 let loop_check = func_builder.create_block();
1182 let vec_body = func_builder.create_block();
1183 let scalar_check = func_builder.create_block();
1184 let scalar_body = func_builder.create_block();
1185 let ret_block = func_builder.create_block();
1186
1187 func_builder.append_block_params_for_function_params(entry_block);
1188 func_builder.append_block_param(loop_check, ptr_type);
1189 func_builder.append_block_param(vec_body, ptr_type);
1190 func_builder.append_block_param(scalar_check, ptr_type);
1191 func_builder.append_block_param(scalar_body, ptr_type);
1192
1193 func_builder.switch_to_block(entry_block);
1195 let params = func_builder.block_params(entry_block);
1196 let vars_cols_val = params[0];
1197 let n_rows_val = params[1];
1198 let out_ptr_val = params[2];
1199
1200 let ptr_size = i64::from(ptr_type.bytes());
1201 let mut var_col_ptrs: HashMap<u32, Value> = HashMap::new();
1202 for &sid in &sym_ids {
1203 let col_offset = func_builder
1204 .ins()
1205 .iconst(ptr_type, i64::from(sid).wrapping_mul(ptr_size));
1206 let col_ptr_addr = func_builder.ins().iadd(vars_cols_val, col_offset);
1207 let col_ptr = func_builder
1208 .ins()
1209 .load(ptr_type, MemFlags::new(), col_ptr_addr, 0);
1210 var_col_ptrs.insert(sid, col_ptr);
1211 }
1212
1213 let zero_i = func_builder.ins().iconst(ptr_type, 0);
1214 func_builder
1215 .ins()
1216 .jump(loop_check, &[BlockArg::Value(zero_i)]);
1217 func_builder.seal_block(entry_block);
1218
1219 func_builder.switch_to_block(loop_check);
1221 let i_lc = func_builder.block_params(loop_check)[0];
1222 let remaining = func_builder.ins().isub(n_rows_val, i_lc);
1223 let eight_i = func_builder.ins().iconst(ptr_type, 8);
1224 let can_vec =
1225 func_builder
1226 .ins()
1227 .icmp(IntCC::UnsignedGreaterThanOrEqual, remaining, eight_i);
1228 func_builder.ins().brif(
1229 can_vec,
1230 vec_body,
1231 &[BlockArg::Value(i_lc)],
1232 scalar_check,
1233 &[BlockArg::Value(i_lc)],
1234 );
1235
1236 func_builder.switch_to_block(vec_body);
1238 let i_vb = func_builder.block_params(vec_body)[0];
1239 let byte_off = func_builder.ins().ishl_imm(i_vb, 3); let offsets: [i64; 4] = [0, 16, 32, 48]; let mut var_vals: [HashMap<u32, Value>; 4] = [
1246 HashMap::new(),
1247 HashMap::new(),
1248 HashMap::new(),
1249 HashMap::new(),
1250 ];
1251 for &sid in &sym_ids {
1252 let col_ptr = *var_col_ptrs
1253 .get(&sid)
1254 .ok_or(crate::error::JitError::MalformedNode)?;
1255 let base = func_builder.ins().iadd(col_ptr, byte_off);
1256 for (k, &off) in offsets.iter().enumerate() {
1257 let addr = if off == 0 {
1258 base
1259 } else {
1260 func_builder.ins().iadd_imm(base, off)
1261 };
1262 let v = func_builder
1263 .ins()
1264 .load(types::F64X2, MemFlags::new(), addr, 0);
1265 var_vals[k].insert(sid, v);
1266 }
1267 }
1268
1269 let powf_ref_vb = self
1270 .module
1271 .declare_func_in_func(powf_name, func_builder.func);
1272
1273 let results: [Value; 4] = [
1275 emit_ast_simd_f64x2(
1276 ast,
1277 &analysis,
1278 &opts,
1279 &mut func_builder,
1280 &var_vals[0],
1281 powf_ref_vb,
1282 )?,
1283 emit_ast_simd_f64x2(
1284 ast,
1285 &analysis,
1286 &opts,
1287 &mut func_builder,
1288 &var_vals[1],
1289 powf_ref_vb,
1290 )?,
1291 emit_ast_simd_f64x2(
1292 ast,
1293 &analysis,
1294 &opts,
1295 &mut func_builder,
1296 &var_vals[2],
1297 powf_ref_vb,
1298 )?,
1299 emit_ast_simd_f64x2(
1300 ast,
1301 &analysis,
1302 &opts,
1303 &mut func_builder,
1304 &var_vals[3],
1305 powf_ref_vb,
1306 )?,
1307 ];
1308
1309 let out_base = func_builder.ins().iadd(out_ptr_val, byte_off);
1311 for (k, &off) in offsets.iter().enumerate() {
1312 let addr = if off == 0 {
1313 out_base
1314 } else {
1315 func_builder.ins().iadd_imm(out_base, off)
1316 };
1317 func_builder
1318 .ins()
1319 .store(MemFlags::new(), results[k], addr, 0);
1320 }
1321
1322 let i_next = func_builder.ins().iadd_imm(i_vb, 8);
1323 func_builder
1324 .ins()
1325 .jump(loop_check, &[BlockArg::Value(i_next)]);
1326
1327 func_builder.seal_block(loop_check);
1328 func_builder.seal_block(vec_body);
1329
1330 func_builder.switch_to_block(scalar_check);
1332 let i_sc = func_builder.block_params(scalar_check)[0];
1333 let done = func_builder
1334 .ins()
1335 .icmp(IntCC::UnsignedGreaterThanOrEqual, i_sc, n_rows_val);
1336 func_builder.ins().brif(
1337 done,
1338 ret_block,
1339 &[] as &[BlockArg],
1340 scalar_body,
1341 &[BlockArg::Value(i_sc)],
1342 );
1343
1344 func_builder.switch_to_block(scalar_body);
1346 let i_sb = func_builder.block_params(scalar_body)[0];
1347 let byte_off_sb = func_builder.ins().ishl_imm(i_sb, 3);
1348 let mut var_vals_sb: HashMap<u32, Value> = HashMap::new();
1349 for &sid in &sym_ids {
1350 let col_ptr = *var_col_ptrs
1351 .get(&sid)
1352 .ok_or(crate::error::JitError::MalformedNode)?;
1353 let addr = func_builder.ins().iadd(col_ptr, byte_off_sb);
1354 let v = func_builder
1355 .ins()
1356 .load(types::F64, MemFlags::new(), addr, 0);
1357 var_vals_sb.insert(sid, v);
1358 }
1359
1360 let powf_ref_sb = self
1361 .module
1362 .declare_func_in_func(powf_name, func_builder.func);
1363 let mut wstack: Vec<Frame> = Vec::with_capacity(32);
1364 let mut wvals: Vec<Value> = Vec::with_capacity(32);
1365 let res_sb = emit_ast_scalar_with_vars(
1366 ast,
1367 &analysis,
1368 &opts,
1369 &mut func_builder,
1370 &var_vals_sb,
1371 powf_ref_sb,
1372 &HashMap::new(),
1373 &mut wstack,
1374 &mut wvals,
1375 )?;
1376
1377 let out_addr_sb = func_builder.ins().iadd(out_ptr_val, byte_off_sb);
1378 func_builder
1379 .ins()
1380 .store(MemFlags::new(), res_sb, out_addr_sb, 0);
1381 let i_sb_next = func_builder.ins().iadd_imm(i_sb, 1);
1382 func_builder
1383 .ins()
1384 .jump(scalar_check, &[BlockArg::Value(i_sb_next)]);
1385
1386 func_builder.seal_block(scalar_check);
1387 func_builder.seal_block(scalar_body);
1388
1389 func_builder.switch_to_block(ret_block);
1391 func_builder.ins().return_(&[]);
1392 func_builder.seal_block(ret_block);
1393 func_builder.finalize();
1394
1395 let fn_name = format!(
1396 "batch8_expr_{}",
1397 JIT_FUNC_COUNTER.fetch_add(1, Ordering::Relaxed)
1398 );
1399 let func_id = self
1400 .module
1401 .declare_function(&fn_name, Linkage::Export, &ctx.func.signature)
1402 .map_err(|_| crate::error::JitError::VerifierRejected)?;
1403
1404 if let Err(e) = self.module.define_function(func_id, &mut ctx) {
1405 eprintln!("JIT f64x8 define_function failed: {e:?}");
1406 return Err(crate::error::JitError::VerifierRejected);
1407 }
1408 self.module.clear_context(&mut ctx);
1409 if let Err(e) = self.module.finalize_definitions() {
1410 eprintln!("JIT f64x8 finalize_definitions failed: {e:?}");
1411 return Err(crate::error::JitError::VerifierRejected);
1412 }
1413
1414 let code_ptr = self.module.get_finalized_function(func_id);
1415 let batch_fn: CompiledBatchFn = unsafe { std::mem::transmute(code_ptr) };
1416 Ok(Some(batch_fn))
1417 }
1418
1419 pub fn compile_dag(
1429 &mut self,
1430 arena: &crate::dag::arena::DagArena,
1431 root: crate::dag::node::DagNodeId,
1432 ) -> Result<CompiledExprFn, crate::error::JitError> {
1433 let ast = crate::ast::convert::dag_to_ast(arena, root);
1434 self.compile(&ast)
1435 }
1436}
1437
1438struct Frame {
1448 idx: usize,
1449 arity: usize,
1450 cursor: usize,
1451}
1452
1453#[allow(clippy::too_many_arguments)] fn compile_ast_iterative(
1455 ast: &AstProjection,
1456 analysis: &[NodeAnalysis],
1457 opts: &OptConfig,
1458 builder: &mut FunctionBuilder<'_>,
1459 vars_ptr: Value,
1460 powf_func_ref: cranelift_codegen::ir::FuncRef,
1461 fmod_func_ref: cranelift_codegen::ir::FuncRef,
1462 custom_refs: &HashMap<u32, cranelift_codegen::ir::FuncRef>,
1463 stack: &mut Vec<Frame>,
1464 values: &mut Vec<Value>,
1465) -> Result<Value, crate::error::JitError> {
1466 let mut mul_factors: HashMap<Value, (Value, Value)> = HashMap::new();
1472
1473 let duplicate_dag_ids: HashSet<u32> = if opts.enable_cse {
1475 let mut dag_id_count: HashMap<u32, u32> = HashMap::new();
1476 for node in &ast.nodes {
1477 *dag_id_count.entry(node.dag_id.0).or_insert(0) = dag_id_count
1478 .get(&node.dag_id.0)
1479 .copied()
1480 .unwrap_or(0)
1481 .saturating_add(1);
1482 }
1483 dag_id_count
1484 .into_iter()
1485 .filter(|&(_, c)| c > 1)
1486 .map(|(id, _)| id)
1487 .collect()
1488 } else {
1489 HashSet::new()
1490 };
1491 let mut cse_map: HashMap<u32, Value> = HashMap::new();
1493
1494 let root_node = ast
1496 .nodes
1497 .first()
1498 .ok_or(crate::error::JitError::MalformedNode)?;
1499 stack.push(Frame {
1500 idx: 0,
1501 arity: root_node.children.len(),
1502 cursor: 0,
1503 });
1504
1505 while let Some(top) = stack.last_mut() {
1506 if opts.enable_cse && top.cursor == 0 && !duplicate_dag_ids.is_empty() {
1509 let dag_id = ast.nodes.get(top.idx).map_or(u32::MAX, |n| n.dag_id.0);
1510 if duplicate_dag_ids.contains(&dag_id)
1511 && let Some(&cached) = cse_map.get(&dag_id)
1512 {
1513 stack.pop();
1514 values.push(cached);
1515 continue;
1516 }
1517 }
1518
1519 let action = if top.cursor < top.arity {
1523 let Some(node) = ast.nodes.get(top.idx) else {
1524 return Err(crate::error::JitError::MalformedNode);
1525 };
1526 let Some(&child_ptr) = node.children.as_slice().get(top.cursor) else {
1527 return Err(crate::error::JitError::MalformedNode);
1528 };
1529 let child_idx = child_ptr
1530 .resolve(top.idx)
1531 .ok_or(crate::error::JitError::MalformedNode)?;
1532 top.cursor += 1;
1533 Action::Descend(child_idx)
1534 } else {
1535 Action::Emit(top.idx, top.arity)
1536 };
1537
1538 match action {
1539 Action::Descend(child_idx) => {
1540 let Some(child_node) = ast.nodes.get(child_idx) else {
1541 return Err(crate::error::JitError::MalformedNode);
1542 };
1543 stack.push(Frame {
1544 idx: child_idx,
1545 arity: child_node.children.len(),
1546 cursor: 0,
1547 });
1548 }
1549 Action::Emit(idx, arity) => {
1550 stack.pop();
1551 emit_one_node(
1552 ast,
1553 analysis,
1554 opts,
1555 idx,
1556 arity,
1557 builder,
1558 vars_ptr,
1559 powf_func_ref,
1560 fmod_func_ref,
1561 custom_refs,
1562 values,
1563 &mut mul_factors,
1564 )?;
1565 if opts.enable_cse
1567 && !duplicate_dag_ids.is_empty()
1568 && let Some(node) = ast.nodes.get(idx)
1569 {
1570 let dag_id = node.dag_id.0;
1571 if duplicate_dag_ids.contains(&dag_id)
1572 && let Some(&v) = values.last()
1573 {
1574 cse_map.insert(dag_id, v);
1575 }
1576 }
1577 }
1578 }
1579 }
1580
1581 let result = values.pop().ok_or(crate::error::JitError::MalformedNode)?;
1582 if !values.is_empty() {
1583 return Err(crate::error::JitError::VerifierRejected);
1584 }
1585 Ok(result)
1586}
1587
1588enum Action {
1589 Descend(usize),
1590 Emit(usize, usize),
1591}
1592
1593#[allow(clippy::too_many_arguments)]
1594fn emit_one_node(
1595 ast: &AstProjection,
1596 analysis: &[NodeAnalysis],
1597 opts: &OptConfig,
1598 idx: usize,
1599 arity: usize,
1600 builder: &mut FunctionBuilder<'_>,
1601 vars_ptr: Value,
1602 powf_func_ref: cranelift_codegen::ir::FuncRef,
1603 fmod_func_ref: cranelift_codegen::ir::FuncRef,
1604 custom_refs: &HashMap<u32, cranelift_codegen::ir::FuncRef>,
1605 values: &mut Vec<Value>,
1606 mul_factors: &mut HashMap<Value, (Value, Value)>,
1607) -> Result<(), crate::error::JitError> {
1608 let node = ast
1609 .nodes
1610 .get(idx)
1611 .ok_or(crate::error::JitError::MalformedNode)?;
1612
1613 match node.kind {
1614 SymbolKind::Constant(_) => {
1615 values.push(builder.ins().f64const(node.value)); }
1617 SymbolKind::Variable(sym_id) => {
1618 let val = emit_variable_load(builder, vars_ptr, sym_id.0);
1619 values.push(val);
1620 }
1621 SymbolKind::Operator(op) => {
1622 let split_at = values
1623 .len()
1624 .checked_sub(arity)
1625 .ok_or(crate::error::JitError::MalformedNode)?;
1626 let child_vals: Vec<Value> = values.drain(split_at..).collect();
1629
1630 let children = node.children.as_slice_with_pool(&ast.children_pool);
1634 let mut child_analyses: Vec<Option<&NodeAnalysis>> = children
1635 .iter()
1636 .map(|ptr| ptr.resolve(idx).and_then(|ci| analysis.get(ci)))
1637 .collect();
1638 child_analyses.push(analysis.get(idx));
1640
1641 let result = emit_operator(
1642 builder,
1643 op,
1644 &child_vals,
1645 powf_func_ref,
1646 fmod_func_ref,
1647 node,
1648 &child_analyses,
1649 opts,
1650 mul_factors,
1651 )?;
1652 values.push(result);
1653 }
1654 SymbolKind::Function(fn_id) => {
1655 let func_ref = custom_refs
1660 .get(&fn_id.0)
1661 .copied()
1662 .ok_or(crate::error::JitError::UnknownFunction)?;
1663 let split_at = values
1664 .len()
1665 .checked_sub(arity)
1666 .ok_or(crate::error::JitError::MalformedNode)?;
1667 let child_vals: Vec<Value> = values.drain(split_at..).collect();
1668 if child_vals.is_empty() || child_vals.len() > 3 {
1669 return Err(crate::error::JitError::MalformedNode);
1670 }
1671 let call = builder.ins().call(func_ref, &child_vals);
1672 let result = builder
1673 .inst_results(call)
1674 .first()
1675 .copied()
1676 .ok_or(crate::error::JitError::VerifierRejected)?;
1677 values.push(result);
1678 }
1679 SymbolKind::ControlFlow(ctrl) => {
1680 use crate::dag::symbol::CtrlKind;
1681 let split_at = values
1682 .len()
1683 .checked_sub(arity)
1684 .ok_or(crate::error::JitError::MalformedNode)?;
1685 let child_vals: Vec<Value> = values.drain(split_at..).collect();
1686
1687 match ctrl {
1688 CtrlKind::Select => {
1693 if child_vals.len() != 3 {
1694 return Err(crate::error::JitError::MalformedNode);
1695 }
1696 let cond_f64 = child_vals[0];
1697 let then_val = child_vals[1];
1698 let else_val = child_vals[2];
1699 let zero = builder.ins().f64const(0.0);
1700 let cond_i8 = builder.ins().fcmp(FloatCC::NotEqual, cond_f64, zero);
1702 let result = builder.ins().select(cond_i8, then_val, else_val);
1703 values.push(result);
1704 }
1705
1706 CtrlKind::IfElse => {
1712 if child_vals.len() != 3 {
1713 return Err(crate::error::JitError::MalformedNode);
1714 }
1715 let cond_f64 = child_vals[0];
1721 let then_val = child_vals[1];
1722 let else_val = child_vals[2];
1723 let zero = builder.ins().f64const(0.0);
1724 let cond_bool = builder.ins().fcmp(FloatCC::NotEqual, cond_f64, zero);
1725
1726 let then_block = builder.create_block();
1728 let else_block = builder.create_block();
1729 let merge_block = builder.create_block();
1730 builder.append_block_param(merge_block, types::F64);
1732
1733 builder
1735 .ins()
1736 .brif(cond_bool, then_block, &[], else_block, &[]);
1737
1738 builder.switch_to_block(then_block);
1740 builder.seal_block(then_block);
1741 builder.ins().jump(merge_block, &[BlockArg::from(then_val)]);
1742
1743 builder.switch_to_block(else_block);
1745 builder.seal_block(else_block);
1746 builder.ins().jump(merge_block, &[BlockArg::from(else_val)]);
1747
1748 builder.switch_to_block(merge_block);
1750 builder.seal_block(merge_block);
1751 let result = builder.block_params(merge_block)[0];
1752 values.push(result);
1753 }
1754
1755 CtrlKind::ForLoop => {
1763 if child_vals.len() != 4 {
1764 return Err(crate::error::JitError::MalformedNode);
1765 }
1766 let init_val = child_vals[0]; let limit_val = child_vals[1]; let step_val = child_vals[2]; let body_val = child_vals[3]; let loop_header = builder.create_block();
1773 let loop_body = builder.create_block();
1774 let loop_exit = builder.create_block();
1775
1776 builder.append_block_param(loop_header, types::F64); builder.append_block_param(loop_header, types::F64); builder.append_block_param(loop_exit, types::F64); let zero = builder.ins().f64const(0.0);
1783 builder.ins().jump(
1784 loop_header,
1785 &[BlockArg::from(zero), BlockArg::from(init_val)],
1786 );
1787
1788 builder.switch_to_block(loop_header);
1790 let idx = builder.block_params(loop_header)[0];
1791 let acc = builder.block_params(loop_header)[1];
1792 let cond = builder.ins().fcmp(FloatCC::LessThan, idx, limit_val);
1793 builder
1794 .ins()
1795 .brif(cond, loop_body, &[], loop_exit, &[BlockArg::from(acc)]);
1796 builder.seal_block(loop_header);
1797
1798 builder.switch_to_block(loop_body);
1800 builder.seal_block(loop_body);
1801 let new_acc = builder.ins().fadd(acc, body_val);
1802 let new_idx = builder.ins().fadd(idx, step_val);
1803 builder.ins().jump(
1804 loop_header,
1805 &[BlockArg::from(new_idx), BlockArg::from(new_acc)],
1806 );
1807
1808 builder.switch_to_block(loop_exit);
1810 builder.seal_block(loop_exit);
1811 let result = builder.block_params(loop_exit)[0];
1812 values.push(result);
1813 }
1814 }
1815 }
1816 }
1817 Ok(())
1818}
1819
1820fn emit_variable_load(builder: &mut FunctionBuilder<'_>, vars_ptr: Value, sym_idx: u32) -> Value {
1821 let offset = i64::from(sym_idx).wrapping_mul(8);
1823 let addr = builder.ins().iadd_imm(vars_ptr, offset);
1824 builder.ins().load(types::F64, MemFlags::new(), addr, 0)
1825}
1826
1827#[allow(clippy::too_many_arguments)]
1833fn emit_operator(
1834 builder: &mut FunctionBuilder<'_>,
1835 op: OpKind,
1836 child_vals: &[Value],
1837 powf_func_ref: cranelift_codegen::ir::FuncRef,
1838 fmod_func_ref: cranelift_codegen::ir::FuncRef,
1839 ast_node: &AstNode,
1840 child_analyses: &[Option<&NodeAnalysis>],
1841 opts: &OptConfig,
1842 mul_factors: &mut HashMap<Value, (Value, Value)>,
1843) -> Result<Value, crate::error::JitError> {
1844 use crate::jit::primitives::{simplify_add, simplify_mul};
1845
1846 let constants: Vec<Option<f64>> = child_vals
1850 .iter()
1851 .map(|v| constant_behind(builder, *v))
1852 .collect();
1853
1854 let arity = child_vals.len();
1855 let _ = ast_node;
1857
1858 match op {
1859 OpKind::Add => {
1860 if arity != 2 {
1861 return Err(crate::error::JitError::MalformedNode);
1862 }
1863 match (constants[0], constants[1]) {
1865 (Some(l), Some(r)) => {
1866 let folded = simplify_add(l, r).unwrap_or(l + r);
1867 Ok(builder.ins().f64const(folded))
1868 }
1869 (Some(0.0), _) => Ok(child_vals[1]),
1870 (_, Some(0.0)) => Ok(child_vals[0]),
1871 _ => {
1872 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
1878 Ok(builder.ins().fma(a, b, child_vals[1]))
1879 } else if let Some(&(a, b)) = mul_factors.get(&child_vals[1]) {
1880 Ok(builder.ins().fma(a, b, child_vals[0]))
1881 } else {
1882 Ok(builder.ins().fadd(child_vals[0], child_vals[1]))
1883 }
1884 }
1885 }
1886 }
1887 OpKind::Sub => {
1888 if arity != 2 {
1889 return Err(crate::error::JitError::MalformedNode);
1890 }
1891 if child_vals[0] == child_vals[1] {
1893 return Ok(builder.ins().f64const(0.0));
1894 }
1895 match (constants[0], constants[1]) {
1896 (Some(l), Some(r)) => Ok(builder.ins().f64const(l - r)),
1897 (_, Some(0.0)) => Ok(child_vals[0]),
1898 (Some(0.0), _) => Ok(builder.ins().fneg(child_vals[1])),
1900 _ => {
1901 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
1903 let neg_c = builder.ins().fneg(child_vals[1]);
1904 return Ok(builder.ins().fma(a, b, neg_c));
1905 }
1906 Ok(builder.ins().fsub(child_vals[0], child_vals[1]))
1907 }
1908 }
1909 }
1910 OpKind::Mul => {
1911 if arity != 2 {
1912 return Err(crate::error::JitError::MalformedNode);
1913 }
1914 match (constants[0], constants[1]) {
1918 (Some(l), Some(r)) => {
1919 let folded = simplify_mul(l, r).unwrap_or(l * r);
1920 Ok(builder.ins().f64const(folded))
1921 }
1922 (Some(0.0), _) | (_, Some(0.0)) => Ok(builder.ins().f64const(0.0)),
1923 (Some(1.0), _) => Ok(child_vals[1]),
1924 (_, Some(1.0)) => Ok(child_vals[0]),
1925 (Some(-1.0), _) => Ok(builder.ins().fneg(child_vals[1])),
1927 (_, Some(-1.0)) => Ok(builder.ins().fneg(child_vals[0])),
1928 (Some(2.0), _) => Ok(builder.ins().fadd(child_vals[1], child_vals[1])),
1933 (_, Some(2.0)) => Ok(builder.ins().fadd(child_vals[0], child_vals[0])),
1934 _ => {
1935 let result = builder.ins().fmul(child_vals[0], child_vals[1]);
1938 mul_factors.insert(result, (child_vals[0], child_vals[1]));
1939 Ok(result)
1940 }
1941 }
1942 }
1943 OpKind::Div => {
1944 if arity != 2 {
1945 return Err(crate::error::JitError::MalformedNode);
1946 }
1947 let lhs = child_vals[0];
1948 let rhs = child_vals[1];
1949
1950 if constants[0] == Some(0.0) {
1953 let rhs_nonzero = child_analyses
1954 .get(1)
1955 .and_then(|a| *a)
1956 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
1957 let rhs_const_nonzero = constants[1].is_some_and(|c| c != 0.0 && !c.is_nan());
1958 if rhs_nonzero || rhs_const_nonzero {
1959 return Ok(builder.ins().f64const(0.0));
1960 }
1961 }
1962
1963 if lhs == rhs {
1966 let lhs_nonzero = child_analyses
1967 .first()
1968 .and_then(|a| *a)
1969 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
1970 if lhs_nonzero {
1971 return Ok(builder.ins().f64const(1.0));
1972 }
1973 }
1974
1975 if let (Some(lval), Some(rval)) = (constants[0], constants[1]) {
1977 let result = if rval == 0.0 { f64::NAN } else { lval / rval };
1980 return Ok(builder.ins().f64const(result));
1981 }
1982 if constants[1] == Some(0.0) {
1984 return Ok(builder.ins().f64const(f64::NAN));
1985 }
1986
1987 if opts.allow_reciprocal_math
1989 && let Some(c) = constants[1]
1990 && c != 0.0
1991 {
1992 let recip = builder.ins().f64const(1.0 / c);
1993 return Ok(builder.ins().fmul(lhs, recip));
1994 }
1995
1996 let rhs_is_const_nonzero = constants[1].is_some_and(|c| c != 0.0 && !c.is_nan());
1999 let rhs_nonzero = child_analyses
2000 .get(1)
2001 .and_then(|a| *a)
2002 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2003 let skip_guard = opts.elide_nan_guard && (rhs_nonzero || rhs_is_const_nonzero);
2004
2005 if skip_guard {
2006 Ok(builder.ins().fdiv(lhs, rhs))
2008 } else {
2009 let zero = builder.ins().f64const(0.0);
2014 let nan_val = builder.ins().f64const(f64::NAN);
2015 let is_zero = builder.ins().fcmp(FloatCC::Equal, rhs, zero);
2016 let div_result = builder.ins().fdiv(lhs, rhs);
2017 Ok(builder.ins().select(is_zero, nan_val, div_result))
2018 }
2019 }
2020 OpKind::Pow => {
2021 if arity != 2 {
2022 return Err(crate::error::JitError::MalformedNode);
2023 }
2024 match (constants[0], constants[1]) {
2026 (Some(l), Some(r)) => return Ok(builder.ins().f64const(l.powf(r))),
2027 (_, Some(0.0)) => return Ok(builder.ins().f64const(1.0)),
2028 (_, Some(1.0)) => return Ok(child_vals[0]),
2029 _ => {}
2030 }
2031
2032 if constants[0] == Some(1.0) {
2034 return Ok(builder.ins().f64const(1.0));
2035 }
2036
2037 if constants[0] == Some(0.0) {
2040 let exp_positive = child_analyses
2041 .get(1)
2042 .and_then(|a| *a)
2043 .is_some_and(|a| a.is_positive);
2044 let exp_const_positive = constants[1].is_some_and(|e| e > 0.0 && !e.is_nan());
2045 if exp_positive || exp_const_positive {
2046 return Ok(builder.ins().f64const(0.0));
2047 }
2048 }
2049
2050 let node_an: Option<&NodeAnalysis> = child_analyses.get(2).and_then(|a| *a);
2059 let expansion = node_an.map(|a| &a.pow_expansion);
2060 match expansion {
2061 Some(PowExpansion::Sqrt) if opts.expand_sqrt => {
2062 Ok(passes::emit_sqrt(builder, child_vals[0]))
2063 }
2064 Some(PowExpansion::IntPow(n)) if *n >= 2 && *n <= opts.max_int_pow => {
2065 Ok(passes::emit_int_pow(builder, child_vals[0], *n))
2066 }
2067 Some(PowExpansion::NegIntPow(n)) => {
2068 let n = *n;
2069 let base = child_vals[0];
2070 let x_n = if n == 1 {
2071 base
2072 } else {
2073 passes::emit_int_pow(builder, base, n)
2074 };
2075 let one = builder.ins().f64const(1.0);
2076 let base_nonzero = child_analyses
2078 .first()
2079 .and_then(|a| *a)
2080 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2081 if opts.elide_nan_guard && base_nonzero {
2082 Ok(builder.ins().fdiv(one, x_n))
2083 } else {
2084 let zero = builder.ins().f64const(0.0);
2085 let nan_val = builder.ins().f64const(f64::NAN);
2086 let is_zero = builder.ins().fcmp(FloatCC::Equal, x_n, zero);
2087 let div_result = builder.ins().fdiv(one, x_n);
2088 Ok(builder.ins().select(is_zero, nan_val, div_result))
2089 }
2090 }
2091 _ => {
2092 if let Some(exp) = constants[1] {
2096 if opts.expand_sqrt && (exp - 0.5_f64).abs() < f64::EPSILON {
2098 return Ok(passes::emit_sqrt(builder, child_vals[0]));
2099 }
2100 let n = exp as u32;
2101 if exp.to_bits() == f64::from(n).to_bits()
2102 && n >= 2
2103 && n <= opts.max_int_pow
2104 {
2105 return Ok(passes::emit_int_pow(builder, child_vals[0], n));
2106 }
2107 }
2108 let call = builder.ins().call(powf_func_ref, child_vals);
2109 Ok(builder.inst_results(call)[0])
2110 }
2111 }
2112 }
2113 OpKind::Mod => {
2114 if arity != 2 {
2115 return Err(crate::error::JitError::MalformedNode);
2116 }
2117 let lhs = child_vals[0];
2118 let rhs = child_vals[1];
2119 if let (Some(lval), Some(rval)) = (constants[0], constants[1]) {
2120 let result = if rval == 0.0 { f64::NAN } else { lval % rval };
2121 return Ok(builder.ins().f64const(result));
2122 }
2123 if constants[1] == Some(0.0) {
2124 return Ok(builder.ins().f64const(f64::NAN));
2125 }
2126 let rhs_is_const_nonzero = constants[1].is_some_and(|c| c != 0.0 && !c.is_nan());
2130 let rhs_nonzero = child_analyses
2131 .get(1)
2132 .and_then(|a| *a)
2133 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2134 if opts.elide_nan_guard && (rhs_nonzero || rhs_is_const_nonzero) {
2135 let call = builder.ins().call(fmod_func_ref, &[lhs, rhs]);
2136 Ok(builder.inst_results(call)[0])
2137 } else {
2138 let zero = builder.ins().f64const(0.0);
2139 let nan_val = builder.ins().f64const(f64::NAN);
2140 let is_zero = builder.ins().fcmp(FloatCC::Equal, rhs, zero);
2141 let call = builder.ins().call(fmod_func_ref, &[lhs, rhs]);
2142 let rem_result = builder.inst_results(call)[0];
2143 Ok(builder.ins().select(is_zero, nan_val, rem_result))
2144 }
2145 }
2146 OpKind::Neg => {
2147 if arity != 1 {
2148 return Err(crate::error::JitError::MalformedNode);
2149 }
2150 if let Some(c) = constants[0] {
2151 return Ok(builder.ins().f64const(-c));
2152 }
2153 Ok(builder.ins().fneg(child_vals[0]))
2154 }
2155 }
2156}
2157
2158fn f64x2_const(builder: &mut FunctionBuilder<'_>, v: f64) -> Value {
2163 use cranelift_codegen::ir::ConstantData;
2164 let bits = v.to_bits().to_le_bytes();
2165 let data: [u8; 16] = [
2166 bits[0], bits[1], bits[2], bits[3], bits[4], bits[5], bits[6], bits[7], bits[0], bits[1],
2167 bits[2], bits[3], bits[4], bits[5], bits[6], bits[7],
2168 ];
2169 let constant_handle = builder
2170 .func
2171 .dfg
2172 .constants
2173 .insert(ConstantData::from(&data[..]));
2174 builder.ins().vconst(types::F64X2, constant_handle)
2175}
2176
2177fn emit_ast_simd_f64x2(
2186 ast: &AstProjection,
2187 analysis: &[NodeAnalysis],
2188 opts: &OptConfig,
2189 builder: &mut FunctionBuilder<'_>,
2190 var_vals_vec: &HashMap<u32, Value>, _powf_func_ref: cranelift_codegen::ir::FuncRef,
2192) -> Result<Value, crate::error::JitError> {
2193 let mut stack: Vec<Frame> = Vec::with_capacity(64);
2194 let mut values: Vec<Value> = Vec::with_capacity(64);
2195 let mut mul_factors: HashMap<Value, (Value, Value)> = HashMap::new();
2196 let mut const_cache: HashMap<u64, Value> = HashMap::new();
2199
2200 let root_node = ast
2201 .nodes
2202 .first()
2203 .ok_or(crate::error::JitError::MalformedNode)?;
2204 stack.push(Frame {
2205 idx: 0,
2206 arity: root_node.children.len(),
2207 cursor: 0,
2208 });
2209
2210 while let Some(top) = stack.last_mut() {
2211 let action = if top.cursor < top.arity {
2212 let Some(node) = ast.nodes.get(top.idx) else {
2213 return Err(crate::error::JitError::MalformedNode);
2214 };
2215 let Some(&child_ptr) = node.children.as_slice().get(top.cursor) else {
2216 return Err(crate::error::JitError::MalformedNode);
2217 };
2218 let child_idx = child_ptr
2219 .resolve(top.idx)
2220 .ok_or(crate::error::JitError::MalformedNode)?;
2221 top.cursor += 1;
2222 Action::Descend(child_idx)
2223 } else {
2224 Action::Emit(top.idx, top.arity)
2225 };
2226
2227 match action {
2228 Action::Descend(child_idx) => {
2229 let Some(child_node) = ast.nodes.get(child_idx) else {
2230 return Err(crate::error::JitError::MalformedNode);
2231 };
2232 stack.push(Frame {
2233 idx: child_idx,
2234 arity: child_node.children.len(),
2235 cursor: 0,
2236 });
2237 }
2238 Action::Emit(idx, arity) => {
2239 stack.pop();
2240 let node = ast
2241 .nodes
2242 .get(idx)
2243 .ok_or(crate::error::JitError::MalformedNode)?;
2244 match node.kind {
2245 SymbolKind::Constant(_) => {
2246 let bits = node.value.to_bits();
2248 let v = *const_cache
2249 .entry(bits)
2250 .or_insert_with(|| f64x2_const(builder, node.value));
2251 values.push(v);
2252 }
2253 SymbolKind::Variable(sym_id) => {
2254 let v = var_vals_vec
2255 .get(&sym_id.0)
2256 .copied()
2257 .ok_or(crate::error::JitError::MalformedNode)?;
2258 values.push(v);
2259 }
2260 SymbolKind::Operator(op) => {
2261 let split_at = values
2262 .len()
2263 .checked_sub(arity)
2264 .ok_or(crate::error::JitError::MalformedNode)?;
2265 let child_v: Vec<Value> = values.drain(split_at..).collect();
2266
2267 let children = node.children.as_slice_with_pool(&ast.children_pool);
2269 let mut child_analyses: Vec<Option<&NodeAnalysis>> = children
2270 .iter()
2271 .map(|ptr| ptr.resolve(idx).and_then(|ci| analysis.get(ci)))
2272 .collect();
2273 child_analyses.push(analysis.get(idx));
2274
2275 let result = emit_operator_simd_f64x2(
2276 builder,
2277 op,
2278 &child_v,
2279 node,
2280 &child_analyses,
2281 opts,
2282 &mut mul_factors,
2283 )?;
2284 values.push(result);
2285 }
2286 SymbolKind::Function(_) | SymbolKind::ControlFlow(_) => {
2287 return Err(crate::error::JitError::MalformedNode);
2289 }
2290 }
2291 }
2292 }
2293 }
2294
2295 values.pop().ok_or(crate::error::JitError::MalformedNode)
2296}
2297
2298#[allow(clippy::too_many_arguments)]
2304fn emit_operator_simd_f64x2(
2305 builder: &mut FunctionBuilder<'_>,
2306 op: OpKind,
2307 child_vals: &[Value],
2308 ast_node: &AstNode,
2309 child_analyses: &[Option<&NodeAnalysis>],
2310 opts: &OptConfig,
2311 mul_factors: &mut HashMap<Value, (Value, Value)>,
2312) -> Result<Value, crate::error::JitError> {
2313 let _ = ast_node;
2314 let arity = child_vals.len();
2315
2316 match op {
2317 OpKind::Add => {
2318 if arity != 2 {
2319 return Err(crate::error::JitError::MalformedNode);
2320 }
2321 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2323 return Ok(builder.ins().fma(a, b, child_vals[1]));
2324 }
2325 if let Some(&(a, b)) = mul_factors.get(&child_vals[1]) {
2326 return Ok(builder.ins().fma(a, b, child_vals[0]));
2327 }
2328 Ok(builder.ins().fadd(child_vals[0], child_vals[1]))
2329 }
2330 OpKind::Sub => {
2331 if arity != 2 {
2332 return Err(crate::error::JitError::MalformedNode);
2333 }
2334 if child_vals[0] == child_vals[1] {
2335 return Ok(f64x2_const(builder, 0.0));
2336 }
2337 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2339 let neg_c = builder.ins().fneg(child_vals[1]);
2340 return Ok(builder.ins().fma(a, b, neg_c));
2341 }
2342 Ok(builder.ins().fsub(child_vals[0], child_vals[1]))
2343 }
2344 OpKind::Mul => {
2345 if arity != 2 {
2346 return Err(crate::error::JitError::MalformedNode);
2347 }
2348 let result = builder.ins().fmul(child_vals[0], child_vals[1]);
2349 mul_factors.insert(result, (child_vals[0], child_vals[1]));
2351 Ok(result)
2352 }
2353 OpKind::Div => {
2354 if arity != 2 {
2355 return Err(crate::error::JitError::MalformedNode);
2356 }
2357 let lhs = child_vals[0];
2358 let rhs = child_vals[1];
2359 let rhs_nonzero = child_analyses
2361 .get(1)
2362 .and_then(|a| *a)
2363 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2364 if opts.elide_nan_guard && rhs_nonzero {
2365 Ok(builder.ins().fdiv(lhs, rhs))
2366 } else {
2367 let zero_vec = f64x2_const(builder, 0.0);
2369 let nan_vec = f64x2_const(builder, f64::NAN);
2370 let div_result = builder.ins().fdiv(lhs, rhs);
2371 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, rhs, zero_vec);
2373 let is_zero_mask = builder
2374 .ins()
2375 .bitcast(types::F64X2, MemFlags::new(), is_zero_bv);
2376 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2377 }
2378 }
2379 OpKind::Pow => {
2380 if arity != 2 {
2381 return Err(crate::error::JitError::MalformedNode);
2382 }
2383 let node_an: Option<&NodeAnalysis> = child_analyses.get(2).and_then(|a| *a);
2385 let expansion = node_an.map(|a| &a.pow_expansion);
2386 match expansion {
2387 Some(PowExpansion::Sqrt) if opts.expand_sqrt => {
2388 Ok(builder.ins().sqrt(child_vals[0]))
2390 }
2391 Some(PowExpansion::IntPow(n)) if *n >= 2 && *n <= opts.max_int_pow => {
2392 Ok(passes::emit_int_pow(builder, child_vals[0], *n))
2394 }
2395 Some(PowExpansion::NegIntPow(n)) => {
2396 let n = *n;
2397 let base_vec = child_vals[0];
2398 let x_n = if n == 1 {
2399 base_vec
2400 } else {
2401 passes::emit_int_pow(builder, base_vec, n)
2402 };
2403 let one_vec = f64x2_const(builder, 1.0);
2404 let base_nonzero = child_analyses
2405 .first()
2406 .and_then(|a| *a)
2407 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2408 if opts.elide_nan_guard && base_nonzero {
2409 Ok(builder.ins().fdiv(one_vec, x_n))
2410 } else {
2411 let zero_vec = f64x2_const(builder, 0.0);
2412 let nan_vec = f64x2_const(builder, f64::NAN);
2413 let div_result = builder.ins().fdiv(one_vec, x_n);
2414 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, x_n, zero_vec);
2415 let is_zero_mask =
2416 builder
2417 .ins()
2418 .bitcast(types::F64X2, MemFlags::new(), is_zero_bv);
2419 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2420 }
2421 }
2422 _ => {
2423 Err(crate::error::JitError::MalformedNode)
2425 }
2426 }
2427 }
2428 OpKind::Mod => {
2429 Err(crate::error::JitError::MalformedNode)
2431 }
2432 OpKind::Neg => {
2433 if arity != 1 {
2434 return Err(crate::error::JitError::MalformedNode);
2435 }
2436 Ok(builder.ins().fneg(child_vals[0]))
2437 }
2438 }
2439}
2440
2441#[allow(dead_code)]
2446fn f64x4_const(builder: &mut FunctionBuilder<'_>, v: f64) -> Value {
2447 use cranelift_codegen::ir::ConstantData;
2448 let bits = v.to_bits().to_le_bytes();
2449 let mut data = [0u8; 32];
2450 for i in 0..4 {
2451 data[i * 8..(i + 1) * 8].copy_from_slice(&bits);
2452 }
2453 let constant_handle = builder
2454 .func
2455 .dfg
2456 .constants
2457 .insert(ConstantData::from(&data[..]));
2458 builder.ins().vconst(types::F64X4, constant_handle)
2459}
2460
2461#[allow(dead_code)]
2470fn emit_ast_simd_f64x4(
2471 ast: &AstProjection,
2472 analysis: &[NodeAnalysis],
2473 opts: &OptConfig,
2474 builder: &mut FunctionBuilder<'_>,
2475 var_vals_vec: &HashMap<u32, Value>, _powf_func_ref: cranelift_codegen::ir::FuncRef,
2477) -> Result<Value, crate::error::JitError> {
2478 let mut stack: Vec<Frame> = Vec::with_capacity(64);
2479 let mut values: Vec<Value> = Vec::with_capacity(64);
2480 let mut mul_factors: HashMap<Value, (Value, Value)> = HashMap::new();
2481 let mut const_cache: HashMap<u64, Value> = HashMap::new();
2482
2483 let root_node = ast
2484 .nodes
2485 .first()
2486 .ok_or(crate::error::JitError::MalformedNode)?;
2487 stack.push(Frame {
2488 idx: 0,
2489 arity: root_node.children.len(),
2490 cursor: 0,
2491 });
2492
2493 while let Some(top) = stack.last_mut() {
2494 let action = if top.cursor < top.arity {
2495 let Some(node) = ast.nodes.get(top.idx) else {
2496 return Err(crate::error::JitError::MalformedNode);
2497 };
2498 let Some(&child_ptr) = node.children.as_slice().get(top.cursor) else {
2499 return Err(crate::error::JitError::MalformedNode);
2500 };
2501 let child_idx = child_ptr
2502 .resolve(top.idx)
2503 .ok_or(crate::error::JitError::MalformedNode)?;
2504 top.cursor += 1;
2505 Action::Descend(child_idx)
2506 } else {
2507 Action::Emit(top.idx, top.arity)
2508 };
2509
2510 match action {
2511 Action::Descend(child_idx) => {
2512 let Some(child_node) = ast.nodes.get(child_idx) else {
2513 return Err(crate::error::JitError::MalformedNode);
2514 };
2515 stack.push(Frame {
2516 idx: child_idx,
2517 arity: child_node.children.len(),
2518 cursor: 0,
2519 });
2520 }
2521 Action::Emit(idx, arity) => {
2522 stack.pop();
2523 let node = ast
2524 .nodes
2525 .get(idx)
2526 .ok_or(crate::error::JitError::MalformedNode)?;
2527 match node.kind {
2528 SymbolKind::Constant(_) => {
2529 let bits = node.value.to_bits();
2530 let v = *const_cache
2531 .entry(bits)
2532 .or_insert_with(|| f64x4_const(builder, node.value));
2533 values.push(v);
2534 }
2535 SymbolKind::Variable(sym_id) => {
2536 let v = var_vals_vec
2537 .get(&sym_id.0)
2538 .copied()
2539 .ok_or(crate::error::JitError::MalformedNode)?;
2540 values.push(v);
2541 }
2542 SymbolKind::Operator(op) => {
2543 let split_at = values
2544 .len()
2545 .checked_sub(arity)
2546 .ok_or(crate::error::JitError::MalformedNode)?;
2547 let child_v: Vec<Value> = values.drain(split_at..).collect();
2548
2549 let children = node.children.as_slice_with_pool(&ast.children_pool);
2551 let mut child_analyses: Vec<Option<&NodeAnalysis>> = children
2552 .iter()
2553 .map(|ptr| ptr.resolve(idx).and_then(|ci| analysis.get(ci)))
2554 .collect();
2555 child_analyses.push(analysis.get(idx));
2556
2557 let result = emit_operator_simd_f64x4(
2558 builder,
2559 op,
2560 &child_v,
2561 node,
2562 &child_analyses,
2563 opts,
2564 &mut mul_factors,
2565 )?;
2566 values.push(result);
2567 }
2568 SymbolKind::Function(_) | SymbolKind::ControlFlow(_) => {
2569 return Err(crate::error::JitError::MalformedNode);
2571 }
2572 }
2573 }
2574 }
2575 }
2576
2577 values.pop().ok_or(crate::error::JitError::MalformedNode)
2578}
2579
2580#[allow(dead_code)]
2586#[allow(clippy::too_many_arguments)]
2587fn emit_operator_simd_f64x4(
2588 builder: &mut FunctionBuilder<'_>,
2589 op: OpKind,
2590 child_vals: &[Value],
2591 ast_node: &AstNode,
2592 child_analyses: &[Option<&NodeAnalysis>],
2593 opts: &OptConfig,
2594 mul_factors: &mut HashMap<Value, (Value, Value)>,
2595) -> Result<Value, crate::error::JitError> {
2596 let _ = ast_node;
2597 let arity = child_vals.len();
2598
2599 match op {
2600 OpKind::Add => {
2601 if arity != 2 {
2602 return Err(crate::error::JitError::MalformedNode);
2603 }
2604 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2606 return Ok(builder.ins().fma(a, b, child_vals[1]));
2607 }
2608 if let Some(&(a, b)) = mul_factors.get(&child_vals[1]) {
2609 return Ok(builder.ins().fma(a, b, child_vals[0]));
2610 }
2611 Ok(builder.ins().fadd(child_vals[0], child_vals[1]))
2612 }
2613 OpKind::Sub => {
2614 if arity != 2 {
2615 return Err(crate::error::JitError::MalformedNode);
2616 }
2617 if child_vals[0] == child_vals[1] {
2618 return Ok(f64x4_const(builder, 0.0));
2619 }
2620 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2622 let neg_c = builder.ins().fneg(child_vals[1]);
2623 return Ok(builder.ins().fma(a, b, neg_c));
2624 }
2625 Ok(builder.ins().fsub(child_vals[0], child_vals[1]))
2626 }
2627 OpKind::Mul => {
2628 if arity != 2 {
2629 return Err(crate::error::JitError::MalformedNode);
2630 }
2631 let result = builder.ins().fmul(child_vals[0], child_vals[1]);
2632 mul_factors.insert(result, (child_vals[0], child_vals[1]));
2634 Ok(result)
2635 }
2636 OpKind::Div => {
2637 if arity != 2 {
2638 return Err(crate::error::JitError::MalformedNode);
2639 }
2640 let lhs = child_vals[0];
2641 let rhs = child_vals[1];
2642 let rhs_nonzero = child_analyses
2644 .get(1)
2645 .and_then(|a| *a)
2646 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2647 if opts.elide_nan_guard && rhs_nonzero {
2648 Ok(builder.ins().fdiv(lhs, rhs))
2649 } else {
2650 let zero_vec = f64x4_const(builder, 0.0);
2652 let nan_vec = f64x4_const(builder, f64::NAN);
2653 let div_result = builder.ins().fdiv(lhs, rhs);
2654 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, rhs, zero_vec);
2655 let is_zero_mask = builder
2656 .ins()
2657 .bitcast(types::F64X4, MemFlags::new(), is_zero_bv);
2658 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2659 }
2660 }
2661 OpKind::Pow => {
2662 if arity != 2 {
2663 return Err(crate::error::JitError::MalformedNode);
2664 }
2665 let node_an: Option<&NodeAnalysis> = child_analyses.get(2).and_then(|a| *a);
2667 let expansion = node_an.map(|a| &a.pow_expansion);
2668 match expansion {
2669 Some(PowExpansion::Sqrt) if opts.expand_sqrt => {
2670 Ok(builder.ins().sqrt(child_vals[0]))
2672 }
2673 Some(PowExpansion::IntPow(n)) if *n >= 2 && *n <= opts.max_int_pow => {
2674 Ok(passes::emit_int_pow(builder, child_vals[0], *n))
2676 }
2677 Some(PowExpansion::NegIntPow(n)) => {
2678 let n = *n;
2679 let base_vec = child_vals[0];
2680 let x_n = if n == 1 {
2681 base_vec
2682 } else {
2683 passes::emit_int_pow(builder, base_vec, n)
2684 };
2685 let one_vec = f64x4_const(builder, 1.0);
2686 let base_nonzero = child_analyses
2687 .first()
2688 .and_then(|a| *a)
2689 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2690 if opts.elide_nan_guard && base_nonzero {
2691 Ok(builder.ins().fdiv(one_vec, x_n))
2692 } else {
2693 let zero_vec = f64x4_const(builder, 0.0);
2694 let nan_vec = f64x4_const(builder, f64::NAN);
2695 let div_result = builder.ins().fdiv(one_vec, x_n);
2696 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, x_n, zero_vec);
2697 let is_zero_mask =
2698 builder
2699 .ins()
2700 .bitcast(types::F64X4, MemFlags::new(), is_zero_bv);
2701 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2702 }
2703 }
2704 _ => {
2705 Err(crate::error::JitError::MalformedNode)
2707 }
2708 }
2709 }
2710 OpKind::Mod => {
2711 Err(crate::error::JitError::MalformedNode)
2713 }
2714 OpKind::Neg => {
2715 if arity != 1 {
2716 return Err(crate::error::JitError::MalformedNode);
2717 }
2718 Ok(builder.ins().fneg(child_vals[0]))
2719 }
2720 }
2721}
2722
2723#[allow(dead_code)]
2725fn f64x8_const(builder: &mut FunctionBuilder<'_>, v: f64) -> Value {
2726 use cranelift_codegen::ir::ConstantData;
2727 let bits = v.to_bits().to_le_bytes();
2728 let mut data = [0u8; 64];
2729 for i in 0..8 {
2730 data[i * 8..(i + 1) * 8].copy_from_slice(&bits);
2731 }
2732 let constant_handle = builder
2733 .func
2734 .dfg
2735 .constants
2736 .insert(ConstantData::from(&data[..]));
2737 builder.ins().vconst(types::F64X8, constant_handle)
2738}
2739
2740#[allow(dead_code)]
2742fn emit_ast_simd_f64x8(
2743 ast: &AstProjection,
2744 analysis: &[NodeAnalysis],
2745 opts: &OptConfig,
2746 builder: &mut FunctionBuilder<'_>,
2747 var_vals_vec: &HashMap<u32, Value>, _powf_func_ref: cranelift_codegen::ir::FuncRef,
2749) -> Result<Value, crate::error::JitError> {
2750 let mut stack: Vec<Frame> = Vec::with_capacity(64);
2751 let mut values: Vec<Value> = Vec::with_capacity(64);
2752 let mut mul_factors: HashMap<Value, (Value, Value)> = HashMap::new();
2753 let mut const_cache: HashMap<u64, Value> = HashMap::new();
2754
2755 let root_node = ast
2756 .nodes
2757 .first()
2758 .ok_or(crate::error::JitError::MalformedNode)?;
2759 stack.push(Frame {
2760 idx: 0,
2761 arity: root_node.children.len(),
2762 cursor: 0,
2763 });
2764
2765 while let Some(top) = stack.last_mut() {
2766 let action = if top.cursor < top.arity {
2767 let Some(node) = ast.nodes.get(top.idx) else {
2768 return Err(crate::error::JitError::MalformedNode);
2769 };
2770 let Some(&child_ptr) = node.children.as_slice().get(top.cursor) else {
2771 return Err(crate::error::JitError::MalformedNode);
2772 };
2773 let child_idx = child_ptr
2774 .resolve(top.idx)
2775 .ok_or(crate::error::JitError::MalformedNode)?;
2776 top.cursor += 1;
2777 Action::Descend(child_idx)
2778 } else {
2779 Action::Emit(top.idx, top.arity)
2780 };
2781
2782 match action {
2783 Action::Descend(child_idx) => {
2784 let Some(child_node) = ast.nodes.get(child_idx) else {
2785 return Err(crate::error::JitError::MalformedNode);
2786 };
2787 stack.push(Frame {
2788 idx: child_idx,
2789 arity: child_node.children.len(),
2790 cursor: 0,
2791 });
2792 }
2793 Action::Emit(idx, arity) => {
2794 stack.pop();
2795 let node = ast
2796 .nodes
2797 .get(idx)
2798 .ok_or(crate::error::JitError::MalformedNode)?;
2799 match node.kind {
2800 SymbolKind::Constant(_) => {
2801 let bits = node.value.to_bits();
2802 let v = *const_cache
2803 .entry(bits)
2804 .or_insert_with(|| f64x8_const(builder, node.value));
2805 values.push(v);
2806 }
2807 SymbolKind::Variable(sym_id) => {
2808 let v = var_vals_vec
2809 .get(&sym_id.0)
2810 .copied()
2811 .ok_or(crate::error::JitError::MalformedNode)?;
2812 values.push(v);
2813 }
2814 SymbolKind::Operator(op) => {
2815 let split_at = values
2816 .len()
2817 .checked_sub(arity)
2818 .ok_or(crate::error::JitError::MalformedNode)?;
2819 let child_v: Vec<Value> = values.drain(split_at..).collect();
2820
2821 let children = node.children.as_slice_with_pool(&ast.children_pool);
2822 let mut child_analyses: Vec<Option<&NodeAnalysis>> = children
2823 .iter()
2824 .map(|ptr| ptr.resolve(idx).and_then(|ci| analysis.get(ci)))
2825 .collect();
2826 child_analyses.push(analysis.get(idx));
2827
2828 let result = emit_operator_simd_f64x8(
2829 builder,
2830 op,
2831 &child_v,
2832 node,
2833 &child_analyses,
2834 opts,
2835 &mut mul_factors,
2836 )?;
2837 values.push(result);
2838 }
2839 SymbolKind::Function(_) | SymbolKind::ControlFlow(_) => {
2840 return Err(crate::error::JitError::MalformedNode);
2841 }
2842 }
2843 }
2844 }
2845 }
2846
2847 values.pop().ok_or(crate::error::JitError::MalformedNode)
2848}
2849
2850#[allow(dead_code)]
2852#[allow(clippy::too_many_arguments)]
2853fn emit_operator_simd_f64x8(
2854 builder: &mut FunctionBuilder<'_>,
2855 op: OpKind,
2856 child_vals: &[Value],
2857 ast_node: &AstNode,
2858 child_analyses: &[Option<&NodeAnalysis>],
2859 opts: &OptConfig,
2860 mul_factors: &mut HashMap<Value, (Value, Value)>,
2861) -> Result<Value, crate::error::JitError> {
2862 let _ = ast_node;
2863 let arity = child_vals.len();
2864
2865 match op {
2866 OpKind::Add => {
2867 if arity != 2 {
2868 return Err(crate::error::JitError::MalformedNode);
2869 }
2870 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2871 return Ok(builder.ins().fma(a, b, child_vals[1]));
2872 }
2873 if let Some(&(a, b)) = mul_factors.get(&child_vals[1]) {
2874 return Ok(builder.ins().fma(a, b, child_vals[0]));
2875 }
2876 Ok(builder.ins().fadd(child_vals[0], child_vals[1]))
2877 }
2878 OpKind::Sub => {
2879 if arity != 2 {
2880 return Err(crate::error::JitError::MalformedNode);
2881 }
2882 if child_vals[0] == child_vals[1] {
2883 return Ok(f64x8_const(builder, 0.0));
2884 }
2885 if let Some(&(a, b)) = mul_factors.get(&child_vals[0]) {
2886 let neg_c = builder.ins().fneg(child_vals[1]);
2887 return Ok(builder.ins().fma(a, b, neg_c));
2888 }
2889 Ok(builder.ins().fsub(child_vals[0], child_vals[1]))
2890 }
2891 OpKind::Mul => {
2892 if arity != 2 {
2893 return Err(crate::error::JitError::MalformedNode);
2894 }
2895 let result = builder.ins().fmul(child_vals[0], child_vals[1]);
2896 mul_factors.insert(result, (child_vals[0], child_vals[1]));
2897 Ok(result)
2898 }
2899 OpKind::Div => {
2900 if arity != 2 {
2901 return Err(crate::error::JitError::MalformedNode);
2902 }
2903 let lhs = child_vals[0];
2904 let rhs = child_vals[1];
2905 let rhs_nonzero = child_analyses
2906 .get(1)
2907 .and_then(|a| *a)
2908 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2909 if opts.elide_nan_guard && rhs_nonzero {
2910 Ok(builder.ins().fdiv(lhs, rhs))
2911 } else {
2912 let zero_vec = f64x8_const(builder, 0.0);
2913 let nan_vec = f64x8_const(builder, f64::NAN);
2914 let div_result = builder.ins().fdiv(lhs, rhs);
2915 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, rhs, zero_vec);
2916 let is_zero_mask = builder
2917 .ins()
2918 .bitcast(types::F64X8, MemFlags::new(), is_zero_bv);
2919 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2920 }
2921 }
2922 OpKind::Pow => {
2923 if arity != 2 {
2924 return Err(crate::error::JitError::MalformedNode);
2925 }
2926 let node_an: Option<&NodeAnalysis> = child_analyses.get(2).and_then(|a| *a);
2927 let expansion = node_an.map(|a| &a.pow_expansion);
2928 match expansion {
2929 Some(PowExpansion::Sqrt) if opts.expand_sqrt => {
2930 Ok(builder.ins().sqrt(child_vals[0]))
2931 }
2932 Some(PowExpansion::IntPow(n)) if *n >= 2 && *n <= opts.max_int_pow => {
2933 Ok(passes::emit_int_pow(builder, child_vals[0], *n))
2934 }
2935 Some(PowExpansion::NegIntPow(n)) => {
2936 let n = *n;
2937 let base_vec = child_vals[0];
2938 let x_n = if n == 1 {
2939 base_vec
2940 } else {
2941 passes::emit_int_pow(builder, base_vec, n)
2942 };
2943 let one_vec = f64x8_const(builder, 1.0);
2944 let base_nonzero = child_analyses
2945 .first()
2946 .and_then(|a| *a)
2947 .is_some_and(super::analysis::NodeAnalysis::is_nonzero);
2948 if opts.elide_nan_guard && base_nonzero {
2949 Ok(builder.ins().fdiv(one_vec, x_n))
2950 } else {
2951 let zero_vec = f64x8_const(builder, 0.0);
2952 let nan_vec = f64x8_const(builder, f64::NAN);
2953 let div_result = builder.ins().fdiv(one_vec, x_n);
2954 let is_zero_bv = builder.ins().fcmp(FloatCC::Equal, x_n, zero_vec);
2955 let is_zero_mask =
2956 builder
2957 .ins()
2958 .bitcast(types::F64X8, MemFlags::new(), is_zero_bv);
2959 Ok(builder.ins().bitselect(is_zero_mask, nan_vec, div_result))
2960 }
2961 }
2962 _ => Err(crate::error::JitError::MalformedNode),
2963 }
2964 }
2965 OpKind::Mod => Err(crate::error::JitError::MalformedNode),
2966 OpKind::Neg => {
2967 if arity != 1 {
2968 return Err(crate::error::JitError::MalformedNode);
2969 }
2970 Ok(builder.ins().fneg(child_vals[0]))
2971 }
2972 }
2973}
2974
2975fn constant_behind(builder: &FunctionBuilder<'_>, v: Value) -> Option<f64> {
2981 use cranelift_codegen::ir::{InstructionData, ValueDef};
2982 let dfg = &builder.func.dfg;
2983 let ValueDef::Result(inst, _) = dfg.value_def(v) else {
2984 return None;
2985 };
2986 if let InstructionData::UnaryIeee64 { imm, .. } = dfg.insts[inst] {
2987 Some(f64::from_bits(imm.bits()))
2988 } else {
2989 None
2990 }
2991}
2992
2993fn is_vectorizable_ast(
3002 ast: &AstProjection,
3003 analysis: &[NodeAnalysis],
3004 custom_ops: Option<&crate::custom::descriptor::CustomOpRegistry>,
3005) -> bool {
3006 for (node, an) in ast.nodes.iter().zip(analysis.iter()) {
3007 match node.kind {
3008 SymbolKind::Function(fn_id) => {
3009 if custom_ops.is_some_and(|r| r.is_vectorizable(fn_id)) {
3011 continue;
3012 }
3013 return false;
3014 }
3015 SymbolKind::Operator(OpKind::Mod) => return false,
3016 SymbolKind::Operator(OpKind::Pow) => {
3017 if matches!(an.pow_expansion, PowExpansion::None) {
3018 return false;
3019 }
3020 }
3021 _ => {}
3022 }
3023 }
3024 true
3025}
3026
3027#[allow(clippy::too_many_arguments)]
3033fn emit_ast_scalar_with_vars(
3034 ast: &AstProjection,
3035 analysis: &[NodeAnalysis],
3036 opts: &OptConfig,
3037 builder: &mut FunctionBuilder<'_>,
3038 var_vals: &HashMap<u32, Value>,
3039 powf_func_ref: cranelift_codegen::ir::FuncRef,
3040 custom_refs: &HashMap<u32, cranelift_codegen::ir::FuncRef>,
3041 stack: &mut Vec<Frame>,
3042 values: &mut Vec<Value>,
3043) -> Result<Value, crate::error::JitError> {
3044 stack.clear();
3045 values.clear();
3046
3047 let mut mul_factors: HashMap<Value, (Value, Value)> = HashMap::new();
3048
3049 let fmod_dummy = powf_func_ref;
3053
3054 let root_node = ast
3055 .nodes
3056 .first()
3057 .ok_or(crate::error::JitError::MalformedNode)?;
3058 stack.push(Frame {
3059 idx: 0,
3060 arity: root_node.children.len(),
3061 cursor: 0,
3062 });
3063
3064 while let Some(top) = stack.last_mut() {
3065 let action = if top.cursor < top.arity {
3066 let Some(node) = ast.nodes.get(top.idx) else {
3067 return Err(crate::error::JitError::MalformedNode);
3068 };
3069 let Some(&child_ptr) = node.children.as_slice().get(top.cursor) else {
3070 return Err(crate::error::JitError::MalformedNode);
3071 };
3072 let child_idx = child_ptr
3073 .resolve(top.idx)
3074 .ok_or(crate::error::JitError::MalformedNode)?;
3075 top.cursor += 1;
3076 Action::Descend(child_idx)
3077 } else {
3078 Action::Emit(top.idx, top.arity)
3079 };
3080
3081 match action {
3082 Action::Descend(child_idx) => {
3083 let Some(child_node) = ast.nodes.get(child_idx) else {
3084 return Err(crate::error::JitError::MalformedNode);
3085 };
3086 stack.push(Frame {
3087 idx: child_idx,
3088 arity: child_node.children.len(),
3089 cursor: 0,
3090 });
3091 }
3092 Action::Emit(idx, arity) => {
3093 stack.pop();
3094 let node = ast
3095 .nodes
3096 .get(idx)
3097 .ok_or(crate::error::JitError::MalformedNode)?;
3098 match node.kind {
3099 SymbolKind::Constant(_) => {
3100 values.push(builder.ins().f64const(node.value));
3101 }
3102 SymbolKind::Variable(sym_id) => {
3103 let v = var_vals
3104 .get(&sym_id.0)
3105 .copied()
3106 .ok_or(crate::error::JitError::MalformedNode)?;
3107 values.push(v);
3108 }
3109 SymbolKind::Operator(op) => {
3110 let split_at = values
3111 .len()
3112 .checked_sub(arity)
3113 .ok_or(crate::error::JitError::MalformedNode)?;
3114 let child_v: Vec<Value> = values.drain(split_at..).collect();
3115 let children = node.children.as_slice_with_pool(&ast.children_pool);
3116 let mut child_analyses: Vec<Option<&NodeAnalysis>> = children
3117 .iter()
3118 .map(|ptr| ptr.resolve(idx).and_then(|ci| analysis.get(ci)))
3119 .collect();
3120 child_analyses.push(analysis.get(idx));
3122 let result = emit_operator(
3123 builder,
3124 op,
3125 &child_v,
3126 powf_func_ref,
3127 fmod_dummy,
3128 node,
3129 &child_analyses,
3130 opts,
3131 &mut mul_factors,
3132 )?;
3133 values.push(result);
3134 }
3135 SymbolKind::Function(fn_id) => {
3136 let func_ref = custom_refs
3137 .get(&fn_id.0)
3138 .copied()
3139 .ok_or(crate::error::JitError::UnknownFunction)?;
3140 let split_at = values
3141 .len()
3142 .checked_sub(arity)
3143 .ok_or(crate::error::JitError::MalformedNode)?;
3144 let child_v: Vec<Value> = values.drain(split_at..).collect();
3145 if child_v.is_empty() || child_v.len() > 3 {
3146 return Err(crate::error::JitError::MalformedNode);
3147 }
3148 let call = builder.ins().call(func_ref, &child_v);
3149 let result = builder
3150 .inst_results(call)
3151 .first()
3152 .copied()
3153 .ok_or(crate::error::JitError::VerifierRejected)?;
3154 values.push(result);
3155 }
3156 SymbolKind::ControlFlow(_) => {
3157 return Err(crate::error::JitError::MalformedNode);
3158 }
3159 }
3160 }
3161 }
3162 }
3163
3164 let result = values.pop().ok_or(crate::error::JitError::MalformedNode)?;
3165 Ok(result)
3166}
3167
3168#[cfg(test)]
3169mod tests {
3170 use super::*;
3171 use crate::ast::convert::dag_to_ast;
3172 use crate::dag::builder::DagBuilder;
3173 use crate::parser::expr::parse_expression;
3174
3175 #[test]
3176 fn test_jit_compile_and_execute() {
3177 let mut builder = DagBuilder::new();
3178 let id = parse_expression("x * 2.5 + y ^ 2.0", &mut builder).unwrap();
3179 let ast = dag_to_ast(builder.arena(), id);
3180
3181 let mut compiler = JitCompiler::new();
3182 let compiled_fn = compiler.compile(&ast).unwrap();
3183
3184 let vars = vec![3.0, 4.0];
3185 let result = compiled_fn(vars.as_ptr());
3186 let expected = 3.0 * 2.5 + 4.0_f64.powf(2.0);
3187 assert!((result - expected).abs() < f64::EPSILON);
3188 }
3189
3190 #[test]
3191 fn test_jit_divide_by_zero_yields_nan() {
3192 let mut builder = DagBuilder::new();
3193 let id = parse_expression("x / y", &mut builder).unwrap();
3194 let ast = dag_to_ast(builder.arena(), id);
3195
3196 let mut compiler = JitCompiler::new();
3197 let compiled_fn = compiler.compile(&ast).unwrap();
3198
3199 let safe_vars = vec![10.0, 2.0];
3200 let safe_res = compiled_fn(safe_vars.as_ptr());
3201 assert!((safe_res - 5.0).abs() < f64::EPSILON);
3202
3203 let zero_vars = vec![10.0, 0.0];
3205 let nan_res = compiled_fn(zero_vars.as_ptr());
3206 assert!(nan_res.is_nan(), "x/0 must be NaN; got {nan_res}");
3207 }
3208
3209 #[test]
3210 fn test_jit_deep_chain_no_stack_overflow() {
3211 let mut b = DagBuilder::new();
3213 let x = b.variable("x");
3214 let mut acc = x;
3215 for _ in 0..2500 {
3216 acc = b.add(acc, x);
3217 }
3218 let ast = dag_to_ast(b.arena(), acc);
3219
3220 let mut compiler = JitCompiler::new();
3221 let compiled_fn = compiler.compile(&ast).unwrap();
3222 let vars = vec![1.0];
3223 let result = compiled_fn(vars.as_ptr());
3224 let expected = 2501.0;
3226 assert!((result - expected).abs() < f64::EPSILON);
3227 }
3228
3229 #[test]
3230 fn test_peephole_x_plus_zero() {
3231 let mut b = DagBuilder::new();
3232 let id = parse_expression("x + 0", &mut b).unwrap();
3233 let ast = dag_to_ast(b.arena(), id);
3234 let mut compiler = JitCompiler::new();
3235 let f = compiler.compile(&ast).unwrap();
3236 assert!((f([3.5_f64].as_ptr()) - 3.5).abs() < f64::EPSILON);
3237 }
3238
3239 #[test]
3240 fn test_peephole_x_times_zero() {
3241 let mut b = DagBuilder::new();
3242 let id = parse_expression("x * 0", &mut b).unwrap();
3243 let ast = dag_to_ast(b.arena(), id);
3244 let mut compiler = JitCompiler::new();
3245 let f = compiler.compile(&ast).unwrap();
3246 assert!(f([42.0_f64].as_ptr()).abs() < f64::EPSILON);
3247 }
3248
3249 #[test]
3250 fn test_peephole_constant_fold() {
3251 let mut b = DagBuilder::new();
3252 let id = parse_expression("3 + 4", &mut b).unwrap();
3254 let ast = dag_to_ast(b.arena(), id);
3255 let mut compiler = JitCompiler::new();
3256 let f = compiler.compile(&ast).unwrap();
3257 let r = f([].as_ptr());
3258 assert!((r - 7.0).abs() < f64::EPSILON);
3259 }
3260
3261 extern "C" fn rssn_test_double(x: f64) -> f64 {
3262 x * 2.0
3263 }
3264
3265 #[test]
3266 fn test_custom_function_jit_round_trip() {
3267 use crate::dag::metadata::NodeFlags;
3268 use crate::dag::symbol::{FnId, SymbolKind as SK};
3269
3270 let mut b = DagBuilder::new();
3271 let x = b.variable("x");
3272 let fn_id = FnId(42);
3274 let expr = b.operator(SK::Function(fn_id), &[x], NodeFlags::EMPTY);
3275 let ast = dag_to_ast(b.arena(), expr);
3276
3277 let mut compiler = JitCompiler::new();
3278 compiler.register_custom_function(fn_id, rssn_test_double);
3279 let f = compiler.compile(&ast).expect("compile with custom fn");
3280
3281 let vars = vec![3.5_f64];
3282 let r = f(vars.as_ptr());
3283 assert!((r - 7.0).abs() < f64::EPSILON, "expected 3.5 * 2 = 7.0");
3284 }
3285
3286 #[test]
3287 fn test_custom_function_unregistered_fails_cleanly() {
3288 use crate::dag::metadata::NodeFlags;
3289 use crate::dag::symbol::{FnId, SymbolKind as SK};
3290
3291 let mut b = DagBuilder::new();
3292 let x = b.variable("x");
3293 let expr = b.operator(SK::Function(FnId(99)), &[x], NodeFlags::EMPTY);
3294 let ast = dag_to_ast(b.arena(), expr);
3295
3296 let mut compiler = JitCompiler::new();
3297 let err = compiler.compile(&ast).expect_err("must error");
3299 assert_eq!(
3300 err,
3301 crate::error::JitError::UnknownFunction,
3302 "unregistered fn id must yield UnknownFunction; got: {err:?}"
3303 );
3304 }
3305
3306 #[test]
3307 fn test_power_expansion_x_squared_no_powf() {
3308 let mut b = DagBuilder::new();
3310 let id = parse_expression("x ^ 2", &mut b).unwrap();
3311 let ast = dag_to_ast(b.arena(), id);
3312 let mut compiler = JitCompiler::new();
3313 let f = compiler.compile(&ast).unwrap();
3314 let result = f([3.0_f64].as_ptr());
3316 assert!(
3317 (result - 9.0).abs() < f64::EPSILON,
3318 "3^2 should be 9; got {result}"
3319 );
3320 }
3321
3322 #[test]
3323 fn test_power_expansion_sqrt() {
3324 let mut b = DagBuilder::new();
3325 let id = parse_expression("x ^ 0.5", &mut b).unwrap();
3326 let ast = dag_to_ast(b.arena(), id);
3327 let mut compiler = JitCompiler::new();
3328 let f = compiler.compile(&ast).unwrap();
3329 let result = f([4.0_f64].as_ptr());
3330 assert!(
3331 (result - 2.0).abs() < 1e-10,
3332 "4^0.5 should be 2; got {result}"
3333 );
3334 }
3335
3336 #[test]
3337 fn test_nan_guard_elision_constant_denominator() {
3338 let mut b = DagBuilder::new();
3340 let id = parse_expression("x / 3", &mut b).unwrap();
3341 let ast = dag_to_ast(b.arena(), id);
3342 let mut compiler = JitCompiler::new();
3343 let f = compiler.compile(&ast).unwrap();
3344 let result = f([9.0_f64].as_ptr());
3345 assert!(
3346 (result - 3.0).abs() < f64::EPSILON,
3347 "9/3 should be 3; got {result}"
3348 );
3349 }
3350
3351 #[test]
3352 fn test_batch_f64x2_correctness() {
3353 let mut b = DagBuilder::new();
3354 let id = parse_expression("x + y", &mut b).unwrap();
3355 let ast = dag_to_ast(b.arena(), id);
3356 let mut compiler = JitCompiler::new();
3357 let batch_fn = compiler
3358 .compile_batch_f64x2(&ast)
3359 .expect("compile ok")
3360 .expect("should be vectorizable");
3361
3362 let x_col = vec![1.0_f64, 3.0, 5.0, 7.0];
3364 let y_col = vec![2.0_f64, 4.0, 6.0, 8.0];
3365 let cols: Vec<*const f64> = vec![x_col.as_ptr(), y_col.as_ptr()];
3366 let mut out = vec![0.0_f64; 4];
3367 batch_fn(cols.as_ptr(), 4, out.as_mut_ptr());
3368
3369 let expected = [3.0_f64, 7.0, 11.0, 15.0];
3370 for (i, (&got, &exp)) in out.iter().zip(expected.iter()).enumerate() {
3371 assert!(
3372 (got - exp).abs() < f64::EPSILON,
3373 "row {i}: expected {exp}, got {got}"
3374 );
3375 }
3376 }
3377
3378 #[test]
3379 fn test_neg_int_pow_x_inv() {
3380 let mut b = DagBuilder::new();
3382 let id = parse_expression("x ^ -1", &mut b).unwrap();
3383 let ast = dag_to_ast(b.arena(), id);
3384 let mut compiler = JitCompiler::new();
3385 let f = compiler.compile(&ast).unwrap();
3386 let result = f([3.0_f64].as_ptr());
3387 let expected = 1.0_f64 / 3.0;
3388 assert!(
3389 (result - expected).abs() < 1e-14,
3390 "x^(-1) for x=3 should be ~{expected}; got {result}"
3391 );
3392 let nan_result = f([0.0_f64].as_ptr());
3394 assert!(
3395 nan_result.is_nan(),
3396 "x^(-1) for x=0 should be NaN; got {nan_result}"
3397 );
3398 }
3399
3400 #[test]
3401 fn test_analysis_x_squared_plus_1_is_positive() {
3402 let mut b = DagBuilder::new();
3404 let id = parse_expression("x ^ 2 + 1", &mut b).unwrap();
3405 let ast = dag_to_ast(b.arena(), id);
3406 let analysis = crate::jit::analysis::analyze(&ast);
3407 let root_an = &analysis[0];
3409 assert!(
3410 root_an.is_positive,
3411 "x^2 + 1 should be provably positive; got {root_an:?}"
3412 );
3413 assert!(
3414 root_an.is_nonnegative,
3415 "x^2 + 1 should be provably nonneg; got {root_an:?}"
3416 );
3417 }
3418
3419 #[test]
3420 fn test_nan_guard_elision_x_sq_plus_1_denominator() {
3421 let mut b = DagBuilder::new();
3424 let id = parse_expression("x / (x ^ 2 + 1)", &mut b).unwrap();
3425 let ast = dag_to_ast(b.arena(), id);
3426 let mut compiler = JitCompiler::new();
3427 let f = compiler.compile(&ast).unwrap();
3428
3429 let test_cases: &[(f64, f64)] = &[
3431 (0.0, 0.0), (1.0, 0.5), (2.0, 0.4), (-1.0, -0.5), (3.0, 3.0 / 10.0), ];
3437 for &(x, expected) in test_cases {
3438 let result = f([x].as_ptr());
3439 assert!(
3440 (result - expected).abs() < 1e-14,
3441 "x / (x^2+1) for x={x}: expected {expected}, got {result}"
3442 );
3443 }
3444 }
3445
3446 #[test]
3447 fn test_batch_f64x2_true_simd() {
3448 let mut b = DagBuilder::new();
3451 let id = parse_expression("x * x + 1", &mut b).unwrap();
3452 let ast = dag_to_ast(b.arena(), id);
3453 let mut compiler = JitCompiler::new();
3454 let batch_fn = compiler
3455 .compile_batch_f64x2(&ast)
3456 .expect("compile ok")
3457 .expect("should be vectorizable");
3458
3459 let x_col = vec![0.0_f64, 1.0, 2.0, 3.0, 4.0];
3461 let cols: Vec<*const f64> = vec![x_col.as_ptr()];
3462 let mut out = vec![0.0_f64; 5];
3463 batch_fn(cols.as_ptr(), 5, out.as_mut_ptr());
3464
3465 let expected = [1.0_f64, 2.0, 5.0, 10.0, 17.0]; for (i, (&got, &exp)) in out.iter().zip(expected.iter()).enumerate() {
3467 assert!(
3468 (got - exp).abs() < f64::EPSILON,
3469 "row {i}: expected {exp}, got {got}"
3470 );
3471 }
3472 }
3473
3474 #[test]
3475 fn test_zero_minus_x_is_fneg() {
3476 let mut b = DagBuilder::new();
3478 let id = parse_expression("0 - x", &mut b).unwrap();
3479 let ast = dag_to_ast(b.arena(), id);
3480 let mut compiler = JitCompiler::new();
3481 let f = compiler.compile(&ast).unwrap();
3482 let result = f([3.0_f64].as_ptr());
3483 assert!(
3484 (result - (-3.0)).abs() < f64::EPSILON,
3485 "0 - 3 should be -3; got {result}"
3486 );
3487 let result2 = f([0.0_f64].as_ptr());
3488 assert!(
3489 result2 == 0.0 || result2 == -0.0,
3490 "0 - 0 should be ±0; got {result2}"
3491 );
3492 }
3493}