Skip to main content

diffsl/execution/llvm/
codegen.rs

1use aliasable::boxed::AliasableBox;
2use anyhow::{anyhow, Result};
3use core::panic;
4use inkwell::attributes::{Attribute, AttributeLoc};
5use inkwell::basic_block::BasicBlock;
6use inkwell::builder::Builder;
7use inkwell::context::{AsContextRef, Context};
8use inkwell::debug_info::AsDIScope;
9use inkwell::debug_info::{DICompileUnit, DIFlags, DIFlagsConstants, DebugInfoBuilder};
10use inkwell::intrinsics::Intrinsic;
11use inkwell::module::{Linkage, Module};
12use inkwell::passes::PassBuilderOptions;
13use inkwell::targets::{FileType, InitializationConfig, Target, TargetMachine, TargetTriple};
14use inkwell::types::{
15    BasicMetadataTypeEnum, BasicType, BasicTypeEnum, FloatType, FunctionType, IntType, PointerType,
16};
17use inkwell::values::{
18    AsValueRef, BasicMetadataValueEnum, BasicValue, BasicValueEnum, CallSiteValue, FloatValue,
19    FunctionValue, GlobalValue, IntValue, PointerValue,
20};
21use inkwell::{
22    AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, GlobalVisibility, IntPredicate,
23};
24use llvm_sys::core::{
25    LLVMBuildCall2, LLVMGetArgOperand, LLVMGetBasicBlockParent, LLVMGetGlobalParent,
26    LLVMGetInstructionParent, LLVMGetNamedFunction, LLVMGlobalGetValueType, LLVMIsMultithreaded,
27};
28use llvm_sys::prelude::{LLVMBuilderRef, LLVMValueRef};
29use pest::Span;
30use std::collections::HashMap;
31use std::ffi::CString;
32use std::iter::zip;
33use std::pin::Pin;
34use target_lexicon::Triple;
35
36use crate::ast::{Ast, AstKind, StringSpan};
37use crate::discretise::{DiscreteModel, Tensor, TensorBlock};
38use crate::enzyme::{
39    CConcreteType_DT_Anything, CConcreteType_DT_Double, CConcreteType_DT_Float,
40    CConcreteType_DT_Integer, CConcreteType_DT_Pointer, CDerivativeMode_DEM_ForwardMode,
41    CDerivativeMode_DEM_ReverseModeCombined, CFnTypeInfo, CreateEnzymeLogic, CreateTypeAnalysis,
42    DiffeGradientUtils, EnzymeCreateForwardDiff, EnzymeCreatePrimalAndGradient, EnzymeFreeTypeTree,
43    EnzymeGradientUtilsNewFromOriginal, EnzymeLogicRef, EnzymeMergeTypeTree, EnzymeNewTypeTreeCT,
44    EnzymeRegisterCallHandler, EnzymeTypeAnalysisRef, EnzymeTypeTreeOnlyEq, FreeEnzymeLogic,
45    FreeTypeAnalysis, GradientUtils, IntList, LLVMOpaqueContext, CDIFFE_TYPE_DFT_CONSTANT,
46    CDIFFE_TYPE_DFT_DUP_ARG, CDIFFE_TYPE_DFT_DUP_NONEED,
47};
48use crate::execution::compiler::CompilerOptions;
49use crate::execution::module::{
50    CodegenModule, CodegenModuleCompile, CodegenModuleEmit, CodegenModuleJit, CodegenModuleLink,
51};
52use crate::execution::object::ObjectModule;
53use crate::execution::scalar::RealType;
54use crate::execution::{DataLayout, Translation, TranslationFrom, TranslationTo};
55use lazy_static::lazy_static;
56use std::sync::Mutex;
57
58lazy_static! {
59    static ref my_mutex: Mutex<i32> = Mutex::new(0i32);
60}
61
62struct ImmovableLlvmModule {
63    // actually has lifetime of `context`
64    // declared first so it's droped before `context`
65    codegen: Option<CodeGen<'static>>,
66    // safety: we must never move out of this box as long as codgen is alive
67    context: AliasableBox<Context>,
68    _pin: std::marker::PhantomPinned,
69}
70
71pub struct LlvmModule {
72    inner: Pin<Box<ImmovableLlvmModule>>,
73    machine: TargetMachine,
74    jit_module: Option<ObjectModule>,
75}
76
77unsafe impl Send for LlvmModule {}
78unsafe impl Sync for LlvmModule {}
79
80impl LlvmModule {
81    fn new(
82        triple: Option<Triple>,
83        model: &DiscreteModel,
84        threaded: bool,
85        real_type: RealType,
86        debug: bool,
87        constants_use_jit: bool,
88    ) -> Result<Self> {
89        let initialization_config = &InitializationConfig::default();
90        Target::initialize_all(initialization_config);
91        let host_triple = Triple::host();
92        let (triple_str, native) = match triple {
93            Some(ref triple) => (triple.to_string(), false),
94            None => (host_triple.to_string(), true),
95        };
96        let triple = TargetTriple::create(triple_str.as_str());
97        let target = Target::from_triple(&triple).unwrap();
98        let cpu = if native {
99            TargetMachine::get_host_cpu_name().to_string()
100        } else {
101            "generic".to_string()
102        };
103        let features = if native {
104            TargetMachine::get_host_cpu_features().to_string()
105        } else {
106            "".to_string()
107        };
108        let machine = target
109            .create_target_machine(
110                &triple,
111                cpu.as_str(),
112                features.as_str(),
113                inkwell::OptimizationLevel::Aggressive,
114                inkwell::targets::RelocMode::PIC,
115                inkwell::targets::CodeModel::Default,
116            )
117            .unwrap();
118
119        let context = AliasableBox::from_unique(Box::new(Context::create()));
120        let mut pinned = Self {
121            inner: Box::pin(ImmovableLlvmModule {
122                codegen: None,
123                context,
124                _pin: std::marker::PhantomPinned,
125            }),
126            machine,
127            jit_module: None,
128        };
129
130        let context_ref = pinned.inner.context.as_ref();
131        let real_type_llvm = match real_type {
132            RealType::F32 => context_ref.f32_type(),
133            RealType::F64 => context_ref.f64_type(),
134        };
135        let int_type_llvm = context_ref.i32_type();
136        let ptr_size_bits = pinned
137            .machine
138            .get_target_data()
139            .get_bit_size(&context_ref.ptr_type(AddressSpace::default()));
140        let real_size_bits = pinned
141            .machine
142            .get_target_data()
143            .get_bit_size(&real_type_llvm);
144        let int_size_bits = pinned
145            .machine
146            .get_target_data()
147            .get_bit_size(&int_type_llvm);
148        let codegen = CodeGen::new(
149            model,
150            context_ref,
151            real_type,
152            real_type_llvm,
153            int_type_llvm,
154            threaded,
155            ptr_size_bits,
156            real_size_bits,
157            int_size_bits,
158            debug,
159            constants_use_jit,
160        )?;
161        let codegen = unsafe { std::mem::transmute::<CodeGen<'_>, CodeGen<'static>>(codegen) };
162        unsafe { pinned.inner.as_mut().get_unchecked_mut().codegen = Some(codegen) };
163        Ok(pinned)
164    }
165
166    fn pre_autodiff_optimisation(&mut self) -> Result<()> {
167        //let pass_manager_builder = PassManagerBuilder::create();
168        //pass_manager_builder.set_optimization_level(inkwell::OptimizationLevel::Default);
169        //let pass_manager = PassManager::create(());
170        //pass_manager_builder.populate_module_pass_manager(&pass_manager);
171        //pass_manager.run_on(self.codegen().module());
172
173        //self.codegen().module().print_to_stderr();
174        // optimise at -O2 no unrolling before giving to enzyme
175        let pass_options = PassBuilderOptions::create();
176        //pass_options.set_verify_each(true);
177        //pass_options.set_debug_logging(true);
178        //pass_options.set_loop_interleaving(true);
179        pass_options.set_loop_vectorization(false);
180        pass_options.set_loop_slp_vectorization(false);
181        pass_options.set_loop_unrolling(false);
182        //pass_options.set_forget_all_scev_in_loop_unroll(true);
183        //pass_options.set_licm_mssa_opt_cap(1);
184        //pass_options.set_licm_mssa_no_acc_for_promotion_cap(10);
185        //pass_options.set_call_graph_profile(true);
186        //pass_options.set_merge_functions(true);
187        //let path = "jit_module_before_pre_autodiff_opt.ll";
188        //self.codegen()
189        //    .module()
190        //    .print_to_file(path)
191        //    .map_err(|e| anyhow!("Failed to print module to file: {:?}", e))?;
192
193        //let passes = "default<O2>";
194        let passes_base = "annotation2metadata,forceattrs,inferattrs,coro-early,function<eager-inv>(lower-expect,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;no-switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>,early-cse<>),openmp-opt,ipsccp,called-value-propagation,globalopt,function(mem2reg),function<eager-inv>(instcombine,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>),require<globals-aa>,function(invalidate<aa>),require<profile-summary>,cgscc(devirt<4>(inline<only-mandatory>,inline,function-attrs,openmp-opt-cgscc,function<eager-inv>(early-cse<memssa>,speculative-execution,jump-threading,correlated-propagation,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>,instcombine,libcalls-shrinkwrap,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>,reassociate,require<opt-remark-emit>,loop-mssa(loop-instsimplify,loop-simplifycfg,licm<no-allowspeculation>,loop-rotate,licm<allowspeculation>,simple-loop-unswitch<no-nontrivial;trivial>),simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>,instcombine,loop(loop-idiom,indvars,loop-deletion),vector-combine,mldst-motion<no-split-footer-bb>,gvn<>,sccp,bdce,instcombine,jump-threading,correlated-propagation,adce,memcpyopt,dse,loop-mssa(licm<allowspeculation>),coro-elide,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;hoist-common-insts;sink-common-insts>,instcombine),coro-split)),deadargelim,coro-cleanup,globalopt,globaldce,elim-avail-extern,rpo-function-attrs,recompute-globalsaa,function<eager-inv>(float2int,lower-constant-intrinsics,loop(loop-rotate,loop-deletion),loop-distribute,inject-tli-mappings,loop-load-elim,instcombine,simplifycfg<bonus-inst-threshold=1;forward-switch-cond;switch-range-to-icmp;switch-to-lookup;no-keep-loops;hoist-common-insts;sink-common-insts>,vector-combine,instcombine,transform-warning,instcombine,require<opt-remark-emit>,loop-mssa(licm<allowspeculation>),alignment-from-assumptions,loop-sink,instsimplify,div-rem-pairs,tailcallelim,simplifycfg<bonus-inst-threshold=1;no-forward-switch-cond;switch-range-to-icmp;no-switch-to-lookup;keep-loops;no-hoist-common-insts;no-sink-common-insts>),globaldce,constmerge,cg-profile,rel-lookup-table-converter,function(annotation-remarks),verify";
195        let passes = if cfg!(feature = "inkwell-221") {
196            passes_base.replace("instcombine", "instcombine<no-verify-fixpoint>")
197        } else {
198            passes_base.to_string()
199        };
200        let (codegen, machine) = self.codegen_and_machine_mut();
201        codegen
202            .module()
203            .run_passes(&passes, machine, pass_options)
204            .map_err(|e| anyhow!("Failed to run passes: {:?}", e))
205
206        //let path = "jit_module_after_pre_autodiff_opt.ll";
207        //self.codegen()
208        //    .module()
209        //    .print_to_file(path)
210        //    .map_err(|e| anyhow!("Failed to print module to file: {:?}", e))
211    }
212
213    fn post_autodiff_optimisation(&mut self) -> Result<()> {
214        // remove noinline attribute from barrier function as only needed for enzyme
215        if let Some(barrier_func) = self.codegen_mut().module().get_function("barrier") {
216            let nolinline_kind_id = Attribute::get_named_enum_kind_id("noinline");
217            barrier_func.remove_enum_attribute(AttributeLoc::Function, nolinline_kind_id);
218        }
219
220        // remove all preprocess_* functions
221        for f in self.codegen_mut().module.get_functions() {
222            if f.get_name().to_str().unwrap().starts_with("preprocess_") {
223                unsafe { f.delete() };
224            }
225        }
226
227        //self.codegen()
228        //    .module()
229        //    .print_to_file("jit_module_before_post_autodiff_opt.ll")
230        //    .unwrap();
231
232        let passes = "default<O3>";
233        let (codegen, machine) = self.codegen_and_machine_mut();
234        codegen
235            .module()
236            .run_passes(passes, machine, PassBuilderOptions::create())
237            .map_err(|e| anyhow!("Failed to run passes: {:?}", e))?;
238
239        //self.codegen()
240        //    .module()
241        //    .print_to_file("jit_module_after_post_autodiff_opt.ll")
242        //    .unwrap();
243
244        Ok(())
245    }
246
247    pub fn print(&self) {
248        self.codegen().module().print_to_stderr();
249    }
250    fn codegen_mut(&mut self) -> &mut CodeGen<'static> {
251        unsafe {
252            self.inner
253                .as_mut()
254                .get_unchecked_mut()
255                .codegen
256                .as_mut()
257                .unwrap()
258        }
259    }
260    fn codegen_and_machine_mut(&mut self) -> (&mut CodeGen<'static>, &TargetMachine) {
261        (
262            unsafe {
263                self.inner
264                    .as_mut()
265                    .get_unchecked_mut()
266                    .codegen
267                    .as_mut()
268                    .unwrap()
269            },
270            &self.machine,
271        )
272    }
273
274    fn codegen(&self) -> &CodeGen<'static> {
275        self.inner.as_ref().get_ref().codegen.as_ref().unwrap()
276    }
277
278    pub fn to_dynamic_library(self, output_path: impl Into<std::path::PathBuf>) -> Result<()> {
279        use std::fs;
280        use std::process::Command;
281
282        let output_path = output_path.into();
283        let object_buffer = self.to_object()?;
284
285        // Create a temporary object file.
286        let temp_dir = std::env::temp_dir();
287        let obj_path = temp_dir.join("diffsl_temp_object.o");
288        fs::write(&obj_path, object_buffer)
289            .map_err(|e| anyhow!("Failed to write temporary object file: {}", e))?;
290
291        let lld = option_env!("DIFFSL_LLVM_LLD")
292            .ok_or_else(|| anyhow!("DIFFSL_LLVM_LLD not set by build script"))?;
293        let linker_name = std::path::Path::new(lld)
294            .file_name()
295            .and_then(|name| name.to_str())
296            .unwrap_or(lld);
297        let is_clang_driver = linker_name.starts_with("clang");
298
299        let mut command = Command::new(lld);
300        if cfg!(target_os = "windows") {
301            if is_clang_driver {
302                command.arg("-shared");
303                command.arg("-o");
304                command.arg(&output_path);
305                command.arg(&obj_path);
306            } else {
307                command.arg("-flavor").arg("link");
308                command.arg("/DLL");
309                command.arg(format!("/OUT:{}", output_path.display()));
310                command.arg(&obj_path);
311            }
312        } else if cfg!(target_os = "macos") {
313            if !lld.ends_with("ld64.lld") && !is_clang_driver {
314                command.arg("-flavor").arg("darwin");
315            }
316            let arch = if cfg!(target_arch = "aarch64") {
317                "arm64"
318            } else if cfg!(target_arch = "x86_64") {
319                "x86_64"
320            } else {
321                return Err(anyhow!("Unsupported macOS architecture for lld invocation"));
322            };
323            let deployment_target =
324                std::env::var("MACOSX_DEPLOYMENT_TARGET").unwrap_or_else(|_| "11.0".to_string());
325            if is_clang_driver {
326                command.arg("-dynamiclib");
327                command.arg("-arch");
328                command.arg(arch);
329                command.arg(format!("-mmacosx-version-min={deployment_target}"));
330                command.arg("-o");
331                command.arg(&output_path);
332                command.arg(&obj_path);
333            } else {
334                command.arg("-arch");
335                command.arg(arch);
336                command.arg("-platform_version");
337                command.arg("macos");
338                command.arg(&deployment_target);
339                command.arg(&deployment_target);
340                command.arg("-dylib");
341                command.arg("-o");
342                command.arg(&output_path);
343                command.arg(&obj_path);
344            }
345        } else {
346            if is_clang_driver {
347                command.arg("-shared");
348                command.arg("-o");
349                command.arg(&output_path);
350                command.arg(&obj_path);
351            } else {
352                command.arg("-flavor").arg("gnu");
353                command.arg("-shared");
354                command.arg("-o");
355                command.arg(&output_path);
356                command.arg(&obj_path);
357            }
358        }
359
360        let status = command
361            .status()
362            .map_err(|e| anyhow!("Failed to invoke lld: {}", e))?;
363        if !status.success() {
364            return Err(anyhow!(
365                "Dynamic library link failed with status: {}",
366                status
367            ));
368        }
369
370        let _ = fs::remove_file(&obj_path);
371        Ok(())
372    }
373}
374
375impl CodegenModule for LlvmModule {}
376
377impl CodegenModuleCompile for LlvmModule {
378    fn from_discrete_model(
379        model: &DiscreteModel,
380        options: CompilerOptions,
381        triple: Option<Triple>,
382        real_type: RealType,
383        code: Option<&str>,
384    ) -> Result<Self> {
385        let thread_dim = options.mode.thread_dim(model.state().nnz());
386        let threaded = thread_dim > 1;
387        if (unsafe { LLVMIsMultithreaded() } <= 0) {
388            return Err(anyhow!(
389                "LLVM is not compiled with multithreading support, but this codegen module requires it."
390            ));
391        }
392
393        let constants_use_jit = options.constants_use_jit;
394        let mut module = Self::new(
395            triple,
396            model,
397            threaded,
398            real_type,
399            options.debug,
400            constants_use_jit,
401        )?;
402
403        let set_u0 = module.codegen_mut().compile_set_u0(model, code)?;
404        let calc_stop = module.codegen_mut().compile_calc_stop(model, false, code)?;
405        let calc_stop_full = module.codegen_mut().compile_calc_stop(model, true, code)?;
406        let reset = module.codegen_mut().compile_reset(model, false, code)?;
407        let reset_full = module.codegen_mut().compile_reset(model, true, code)?;
408        let rhs = module.codegen_mut().compile_rhs(model, false, code)?;
409        let rhs_full = module.codegen_mut().compile_rhs(model, true, code)?;
410        let mass = module.codegen_mut().compile_mass(model, code)?;
411        let calc_out = module.codegen_mut().compile_calc_out(model, false, code)?;
412        let calc_out_full = module.codegen_mut().compile_calc_out(model, true, code)?;
413        let _set_id = module.codegen_mut().compile_set_id(model)?;
414        let _get_dims = module.codegen_mut().compile_get_dims(model)?;
415        let set_inputs = module.codegen_mut().compile_inputs(model, false)?;
416        let _get_inputs = module.codegen_mut().compile_inputs(model, true)?;
417        let _set_constants =
418            module
419                .codegen_mut()
420                .compile_set_constants(model, code, constants_use_jit)?;
421        let tensor_info = module
422            .codegen()
423            .layout
424            .tensors()
425            .map(|(name, is_constant)| (name.to_string(), is_constant))
426            .collect::<Vec<_>>();
427        for (tensor, is_constant) in tensor_info {
428            if is_constant {
429                module
430                    .codegen_mut()
431                    .compile_get_constant(model, tensor.as_str())?;
432            } else {
433                module
434                    .codegen_mut()
435                    .compile_get_tensor(model, tensor.as_str())?;
436            }
437        }
438
439        module.pre_autodiff_optimisation()?;
440
441        module.codegen_mut().compile_gradient(
442            set_u0,
443            &[
444                CompileGradientArgType::DupNoNeed,
445                CompileGradientArgType::DupNoNeed,
446                CompileGradientArgType::Const,
447                CompileGradientArgType::Const,
448            ],
449            CompileMode::Forward,
450            "set_u0_grad",
451        )?;
452
453        module.codegen_mut().compile_gradient(
454            rhs,
455            &[
456                CompileGradientArgType::Const,
457                CompileGradientArgType::DupNoNeed,
458                CompileGradientArgType::DupNoNeed,
459                CompileGradientArgType::DupNoNeed,
460                CompileGradientArgType::Const,
461                CompileGradientArgType::Const,
462            ],
463            CompileMode::Forward,
464            "rhs_grad",
465        )?;
466
467        module.codegen_mut().compile_gradient(
468            reset,
469            &[
470                CompileGradientArgType::Const,
471                CompileGradientArgType::DupNoNeed,
472                CompileGradientArgType::DupNoNeed,
473                CompileGradientArgType::DupNoNeed,
474                CompileGradientArgType::Const,
475                CompileGradientArgType::Const,
476            ],
477            CompileMode::Forward,
478            "reset_grad",
479        )?;
480
481        module.codegen_mut().compile_gradient(
482            calc_stop,
483            &[
484                CompileGradientArgType::Const,
485                CompileGradientArgType::DupNoNeed,
486                CompileGradientArgType::DupNoNeed,
487                CompileGradientArgType::DupNoNeed,
488                CompileGradientArgType::Const,
489                CompileGradientArgType::Const,
490            ],
491            CompileMode::Forward,
492            "calc_stop_grad",
493        )?;
494
495        module.codegen_mut().compile_gradient(
496            calc_out,
497            &[
498                CompileGradientArgType::Const,
499                CompileGradientArgType::DupNoNeed,
500                CompileGradientArgType::DupNoNeed,
501                CompileGradientArgType::DupNoNeed,
502                CompileGradientArgType::Const,
503                CompileGradientArgType::Const,
504            ],
505            CompileMode::Forward,
506            "calc_out_grad",
507        )?;
508        module.codegen_mut().compile_gradient(
509            set_inputs,
510            &[
511                CompileGradientArgType::DupNoNeed,
512                CompileGradientArgType::DupNoNeed,
513                CompileGradientArgType::Const,
514            ],
515            CompileMode::Forward,
516            "set_inputs_grad",
517        )?;
518
519        module.codegen_mut().compile_gradient(
520            set_u0,
521            &[
522                CompileGradientArgType::DupNoNeed,
523                CompileGradientArgType::DupNoNeed,
524                CompileGradientArgType::Const,
525                CompileGradientArgType::Const,
526            ],
527            CompileMode::Reverse,
528            "set_u0_rgrad",
529        )?;
530
531        module.codegen_mut().compile_gradient(
532            mass,
533            &[
534                CompileGradientArgType::Const,
535                CompileGradientArgType::DupNoNeed,
536                CompileGradientArgType::DupNoNeed,
537                CompileGradientArgType::DupNoNeed,
538                CompileGradientArgType::Const,
539                CompileGradientArgType::Const,
540            ],
541            CompileMode::Reverse,
542            "mass_rgrad",
543        )?;
544
545        module.codegen_mut().compile_gradient(
546            rhs,
547            &[
548                CompileGradientArgType::Const,
549                CompileGradientArgType::DupNoNeed,
550                CompileGradientArgType::DupNoNeed,
551                CompileGradientArgType::DupNoNeed,
552                CompileGradientArgType::Const,
553                CompileGradientArgType::Const,
554            ],
555            CompileMode::Reverse,
556            "rhs_rgrad",
557        )?;
558        module.codegen_mut().compile_gradient(
559            reset,
560            &[
561                CompileGradientArgType::Const,
562                CompileGradientArgType::DupNoNeed,
563                CompileGradientArgType::DupNoNeed,
564                CompileGradientArgType::DupNoNeed,
565                CompileGradientArgType::Const,
566                CompileGradientArgType::Const,
567            ],
568            CompileMode::Reverse,
569            "reset_rgrad",
570        )?;
571        module.codegen_mut().compile_gradient(
572            calc_stop,
573            &[
574                CompileGradientArgType::Const,
575                CompileGradientArgType::DupNoNeed,
576                CompileGradientArgType::DupNoNeed,
577                CompileGradientArgType::DupNoNeed,
578                CompileGradientArgType::Const,
579                CompileGradientArgType::Const,
580            ],
581            CompileMode::Reverse,
582            "calc_stop_rgrad",
583        )?;
584        module.codegen_mut().compile_gradient(
585            calc_out,
586            &[
587                CompileGradientArgType::Const,
588                CompileGradientArgType::DupNoNeed,
589                CompileGradientArgType::DupNoNeed,
590                CompileGradientArgType::DupNoNeed,
591                CompileGradientArgType::Const,
592                CompileGradientArgType::Const,
593            ],
594            CompileMode::Reverse,
595            "calc_out_rgrad",
596        )?;
597
598        module.codegen_mut().compile_gradient(
599            set_inputs,
600            &[
601                CompileGradientArgType::DupNoNeed,
602                CompileGradientArgType::DupNoNeed,
603                CompileGradientArgType::Const,
604            ],
605            CompileMode::Reverse,
606            "set_inputs_rgrad",
607        )?;
608
609        module.codegen_mut().compile_gradient(
610            rhs_full,
611            &[
612                CompileGradientArgType::Const,
613                CompileGradientArgType::Const,
614                CompileGradientArgType::DupNoNeed,
615                CompileGradientArgType::DupNoNeed,
616                CompileGradientArgType::Const,
617                CompileGradientArgType::Const,
618            ],
619            CompileMode::ForwardSens,
620            "rhs_sgrad",
621        )?;
622
623        module.codegen_mut().compile_gradient(
624            reset_full,
625            &[
626                CompileGradientArgType::Const,
627                CompileGradientArgType::Const,
628                CompileGradientArgType::DupNoNeed,
629                CompileGradientArgType::DupNoNeed,
630                CompileGradientArgType::Const,
631                CompileGradientArgType::Const,
632            ],
633            CompileMode::ForwardSens,
634            "reset_sgrad",
635        )?;
636
637        module.codegen_mut().compile_gradient(
638            calc_stop_full,
639            &[
640                CompileGradientArgType::Const,
641                CompileGradientArgType::Const,
642                CompileGradientArgType::DupNoNeed,
643                CompileGradientArgType::DupNoNeed,
644                CompileGradientArgType::Const,
645                CompileGradientArgType::Const,
646            ],
647            CompileMode::ForwardSens,
648            "calc_stop_sgrad",
649        )?;
650
651        module.codegen_mut().compile_gradient(
652            set_u0,
653            &[
654                CompileGradientArgType::DupNoNeed,
655                CompileGradientArgType::DupNoNeed,
656                CompileGradientArgType::Const,
657                CompileGradientArgType::Const,
658            ],
659            CompileMode::ForwardSens,
660            "set_u0_sgrad",
661        )?;
662
663        module.codegen_mut().compile_gradient(
664            calc_out_full,
665            &[
666                CompileGradientArgType::Const,
667                CompileGradientArgType::Const,
668                CompileGradientArgType::DupNoNeed,
669                CompileGradientArgType::DupNoNeed,
670                CompileGradientArgType::Const,
671                CompileGradientArgType::Const,
672            ],
673            CompileMode::ForwardSens,
674            "calc_out_sgrad",
675        )?;
676        module.codegen_mut().compile_gradient(
677            calc_out_full,
678            &[
679                CompileGradientArgType::Const,
680                CompileGradientArgType::Const,
681                CompileGradientArgType::DupNoNeed,
682                CompileGradientArgType::DupNoNeed,
683                CompileGradientArgType::Const,
684                CompileGradientArgType::Const,
685            ],
686            CompileMode::ReverseSens,
687            "calc_out_srgrad",
688        )?;
689
690        module.codegen_mut().compile_gradient(
691            rhs_full,
692            &[
693                CompileGradientArgType::Const,
694                CompileGradientArgType::Const,
695                CompileGradientArgType::DupNoNeed,
696                CompileGradientArgType::DupNoNeed,
697                CompileGradientArgType::Const,
698                CompileGradientArgType::Const,
699            ],
700            CompileMode::ReverseSens,
701            "rhs_srgrad",
702        )?;
703
704        module.codegen_mut().compile_gradient(
705            reset_full,
706            &[
707                CompileGradientArgType::Const,
708                CompileGradientArgType::Const,
709                CompileGradientArgType::DupNoNeed,
710                CompileGradientArgType::DupNoNeed,
711                CompileGradientArgType::Const,
712                CompileGradientArgType::Const,
713            ],
714            CompileMode::ReverseSens,
715            "reset_srgrad",
716        )?;
717
718        module.codegen_mut().compile_gradient(
719            calc_stop_full,
720            &[
721                CompileGradientArgType::Const,
722                CompileGradientArgType::Const,
723                CompileGradientArgType::DupNoNeed,
724                CompileGradientArgType::DupNoNeed,
725                CompileGradientArgType::Const,
726                CompileGradientArgType::Const,
727            ],
728            CompileMode::ReverseSens,
729            "calc_stop_srgrad",
730        )?;
731
732        module.post_autodiff_optimisation()?;
733        Ok(module)
734    }
735}
736
737impl CodegenModuleEmit for LlvmModule {
738    fn to_object(&self) -> Result<Vec<u8>> {
739        let module = self.codegen().module();
740        //module.print_to_stderr();
741        let buffer = self
742            .machine
743            .write_to_memory_buffer(module, FileType::Object)
744            .unwrap()
745            .as_slice()
746            .to_vec();
747        Ok(buffer)
748    }
749}
750impl CodegenModuleJit for LlvmModule {
751    fn jit(&mut self) -> Result<HashMap<String, *const u8>> {
752        let object = self.to_object()?;
753        let mut jit_module = ObjectModule::from_object(&object)?;
754        let symbols = jit_module.jit()?;
755        self.jit_module = Some(jit_module);
756        Ok(symbols)
757    }
758}
759
760struct Globals<'ctx> {
761    indices: Option<GlobalValue<'ctx>>,
762    constants: Option<GlobalValue<'ctx>>,
763    thread_counter: Option<GlobalValue<'ctx>>,
764    model_index: GlobalValue<'ctx>,
765}
766
767impl<'ctx> Globals<'ctx> {
768    fn new(
769        layout: &DataLayout,
770        module: &Module<'ctx>,
771        int_type: IntType<'ctx>,
772        real_type: FloatType<'ctx>,
773        threaded: bool,
774        constants_use_jit: bool,
775    ) -> Self {
776        let thread_counter = if threaded {
777            let tc = module.add_global(
778                int_type,
779                Some(AddressSpace::default()),
780                "enzyme_const_thread_counter",
781            );
782            // todo: for some reason this doesn't make enzyme think it's inactive
783            // but using enzyme_const in the name does
784            // todo: also, adding this metadata causes the print of the module to segfault,
785            // so maybe a bug in inkwell
786            //let md_string = context.metadata_string("enzyme_inactive");
787            //tc.set_metadata(md_string, 0);
788            let tc_value = int_type.const_zero();
789            tc.set_visibility(GlobalVisibility::Hidden);
790            tc.set_initializer(&tc_value.as_basic_value_enum());
791            Some(tc)
792        } else {
793            None
794        };
795        let constants = if layout.constants().is_empty() {
796            None
797        } else {
798            let constants_array_type =
799                real_type.array_type(u32::try_from(layout.constants().len()).unwrap());
800            let constants = module.add_global(
801                constants_array_type,
802                Some(AddressSpace::default()),
803                "enzyme_const_constants",
804            );
805            constants.set_visibility(GlobalVisibility::Hidden);
806            constants.set_constant(!constants_use_jit);
807            let constants_array_values = layout
808                .constants()
809                .iter()
810                .map(|&value| real_type.const_float(value))
811                .collect::<Vec<_>>();
812            let constants_value = real_type.const_array(constants_array_values.as_slice());
813            constants.set_initializer(&constants_value);
814            Some(constants)
815        };
816        let indices = if layout.indices().is_empty() {
817            None
818        } else {
819            let indices_array_type =
820                int_type.array_type(u32::try_from(layout.indices().len()).unwrap());
821            let indices_array_values = layout
822                .indices()
823                .iter()
824                .map(|&i| int_type.const_int(i as u64, true))
825                .collect::<Vec<IntValue>>();
826            let indices_value = int_type.const_array(indices_array_values.as_slice());
827            let indices = module.add_global(
828                indices_array_type,
829                Some(AddressSpace::default()),
830                "enzyme_const_indices",
831            );
832            indices.set_constant(true);
833            indices.set_visibility(GlobalVisibility::Hidden);
834            indices.set_initializer(&indices_value);
835            Some(indices)
836        };
837        let model_index = module.add_global(
838            int_type,
839            Some(AddressSpace::default()),
840            "enzyme_const_model_index",
841        );
842        model_index.set_visibility(GlobalVisibility::Hidden);
843        model_index.set_constant(false);
844        model_index.set_initializer(&int_type.const_zero());
845        Self {
846            indices,
847            thread_counter,
848            constants,
849            model_index,
850        }
851    }
852}
853
854pub enum CompileGradientArgType {
855    Const,
856    Dup,
857    DupNoNeed,
858}
859
860pub enum CompileMode {
861    Forward,
862    ForwardSens,
863    Reverse,
864    ReverseSens,
865}
866
867pub struct CodeGen<'ctx> {
868    context: &'ctx inkwell::context::Context,
869    module: Module<'ctx>,
870    builder: Builder<'ctx>,
871    dibuilder: Option<DebugInfoBuilder<'ctx>>,
872    compile_unit: Option<DICompileUnit<'ctx>>,
873    variables: HashMap<String, PointerValue<'ctx>>,
874    functions: HashMap<String, FunctionValue<'ctx>>,
875    fn_value_opt: Option<FunctionValue<'ctx>>,
876    tensor_ptr_opt: Option<PointerValue<'ctx>>,
877    diffsl_real_type: RealType,
878    real_type: FloatType<'ctx>,
879    real_ptr_type: PointerType<'ctx>,
880    int_type: IntType<'ctx>,
881    int_ptr_type: PointerType<'ctx>,
882    ptr_size_bits: u64,
883    int_size_bits: u64,
884    real_size_bits: u64,
885    layout: DataLayout,
886    globals: Globals<'ctx>,
887    threaded: bool,
888}
889
890unsafe extern "C" fn fwd_handler(
891    _builder: LLVMBuilderRef,
892    _call_instruction: LLVMValueRef,
893    _gutils: *mut GradientUtils,
894    _dcall: *mut LLVMValueRef,
895    _normal_return: *mut LLVMValueRef,
896    _shadow_return: *mut LLVMValueRef,
897) -> u8 {
898    1
899}
900
901unsafe extern "C" fn rev_handler(
902    builder: LLVMBuilderRef,
903    call_instruction: LLVMValueRef,
904    gutils: *mut DiffeGradientUtils,
905    _tape: LLVMValueRef,
906) {
907    let call_block = LLVMGetInstructionParent(call_instruction);
908    let call_function = LLVMGetBasicBlockParent(call_block);
909    let module = LLVMGetGlobalParent(call_function);
910    let name_c_str = CString::new("barrier_grad").unwrap();
911    let barrier_func = LLVMGetNamedFunction(module, name_c_str.as_ptr());
912    let barrier_func_type = LLVMGlobalGetValueType(barrier_func);
913    let barrier_num = LLVMGetArgOperand(call_instruction, 0);
914    let total_barriers = LLVMGetArgOperand(call_instruction, 1);
915    let thread_count = LLVMGetArgOperand(call_instruction, 2);
916    let barrier_num = EnzymeGradientUtilsNewFromOriginal(gutils as *mut GradientUtils, barrier_num);
917    let total_barriers =
918        EnzymeGradientUtilsNewFromOriginal(gutils as *mut GradientUtils, total_barriers);
919    let thread_count =
920        EnzymeGradientUtilsNewFromOriginal(gutils as *mut GradientUtils, thread_count);
921    let mut args = [barrier_num, total_barriers, thread_count];
922    let name_c_str = CString::new("").unwrap();
923    LLVMBuildCall2(
924        builder,
925        barrier_func_type,
926        barrier_func,
927        args.as_mut_ptr(),
928        args.len() as u32,
929        name_c_str.as_ptr(),
930    );
931}
932
933#[allow(dead_code)]
934enum PrintValue<'ctx> {
935    Real(FloatValue<'ctx>),
936    Int(IntValue<'ctx>),
937}
938
939impl<'ctx> CodeGen<'ctx> {
940    #[allow(clippy::too_many_arguments)]
941    pub fn new(
942        model: &DiscreteModel,
943        context: &'ctx inkwell::context::Context,
944        diffsl_real_type: RealType,
945        real_type: FloatType<'ctx>,
946        int_type: IntType<'ctx>,
947        threaded: bool,
948        ptr_size_bits: u64,
949        real_size_bits: u64,
950        int_size_bits: u64,
951        debug: bool,
952        constants_use_jit: bool,
953    ) -> Result<Self> {
954        let builder = context.create_builder();
955        let layout = DataLayout::new(model)?;
956        let module = context.create_module(model.name());
957        let (dibuilder, compile_unit) = if debug {
958            let (dib, dic) = module.create_debug_info_builder(
959                true,
960                inkwell::debug_info::DWARFSourceLanguage::C,
961                model.name(),
962                ".",
963                "diffsl compiler",
964                true,
965                "",
966                0,
967                "",
968                inkwell::debug_info::DWARFEmissionKind::Full,
969                0,
970                false,
971                false,
972                "",
973                "",
974            );
975            (Some(dib), Some(dic))
976        } else {
977            (None, None)
978        };
979        let globals = Globals::new(
980            &layout,
981            &module,
982            int_type,
983            real_type,
984            threaded,
985            constants_use_jit,
986        );
987        let real_ptr_type = Self::pointer_type(context, real_type.into());
988        let int_ptr_type = Self::pointer_type(context, int_type.into());
989        let mut ret = Self {
990            context,
991            module,
992            builder,
993            dibuilder,
994            compile_unit,
995            real_type,
996            real_ptr_type,
997            variables: HashMap::new(),
998            functions: HashMap::new(),
999            fn_value_opt: None,
1000            tensor_ptr_opt: None,
1001            layout,
1002            diffsl_real_type,
1003            int_type,
1004            int_ptr_type,
1005            globals,
1006            threaded,
1007            ptr_size_bits,
1008            real_size_bits,
1009            int_size_bits,
1010        };
1011        if threaded {
1012            ret.compile_barrier_init()?;
1013            ret.compile_barrier()?;
1014            ret.compile_barrier_grad()?;
1015            // todo: think I can remove this unless I want to call enzyme using a llvm pass
1016            //ret.globals.add_registered_barrier(ret.context, &ret.module);
1017        }
1018        Ok(ret)
1019    }
1020
1021    fn start_function(
1022        &mut self,
1023        function: FunctionValue<'ctx>,
1024        _code: Option<&str>,
1025    ) -> BasicBlock<'ctx> {
1026        let basic_block = self.context.append_basic_block(function, "entry");
1027        self.fn_value_opt = Some(function);
1028        self.builder.position_at_end(basic_block);
1029
1030        if let Some(dibuilder) = &self.dibuilder {
1031            let scope = self
1032                .fn_value_opt
1033                .unwrap()
1034                .get_subprogram()
1035                .unwrap()
1036                .as_debug_info_scope();
1037            let loc = dibuilder.create_debug_location(self.context, 0, 0, scope, None);
1038            self.builder.set_current_debug_location(loc);
1039        }
1040        basic_block
1041    }
1042
1043    fn add_function(
1044        &mut self,
1045        name: &str,
1046        arg_names: &[&str],
1047        arg_types: &[BasicMetadataTypeEnum<'ctx>],
1048        linkage: Option<Linkage>,
1049        is_real_return: bool,
1050    ) -> FunctionValue<'ctx> {
1051        let function_type = if is_real_return {
1052            self.real_type.fn_type(arg_types, false)
1053        } else {
1054            self.context.void_type().fn_type(arg_types, false)
1055        };
1056        let function = self.module.add_function(name, function_type, linkage);
1057        if let (Some(dibuilder), Some(compile_unit)) = (&self.dibuilder, &self.compile_unit) {
1058            let ditypes = arg_names
1059                .iter()
1060                .zip(arg_types.iter())
1061                .map(|(&name, &ty)| {
1062                    let size_in_bits = if ty.is_float_type() {
1063                        self.real_size_bits
1064                    } else if ty.is_int_type() {
1065                        self.int_size_bits
1066                    } else if ty.is_pointer_type() {
1067                        self.ptr_size_bits
1068                    } else {
1069                        unreachable!("Unsupported argument type for debug info")
1070                    };
1071                    dibuilder
1072                        .create_basic_type(
1073                            name,
1074                            size_in_bits,
1075                            0x00,
1076                            <DIFlags as DIFlagsConstants>::PUBLIC,
1077                        )
1078                        .unwrap()
1079                        .as_type()
1080                })
1081                .collect::<Vec<_>>();
1082            let subroutine_type = dibuilder.create_subroutine_type(
1083                compile_unit.get_file(),
1084                None,
1085                &ditypes,
1086                <DIFlags as DIFlagsConstants>::PUBLIC,
1087            );
1088            let func_scope = dibuilder.create_function(
1089                compile_unit.as_debug_info_scope(),
1090                name,
1091                None,
1092                compile_unit.get_file(),
1093                0,
1094                subroutine_type,
1095                true,
1096                true,
1097                0,
1098                <DIFlags as DIFlagsConstants>::PUBLIC,
1099                true,
1100            );
1101            function.set_subprogram(func_scope);
1102        }
1103        self.functions.insert(name.to_owned(), function);
1104        function
1105    }
1106
1107    #[allow(dead_code)]
1108    fn compile_print_value(
1109        &mut self,
1110        name: &str,
1111        value: PrintValue<'ctx>,
1112    ) -> Result<CallSiteValue<'_>> {
1113        // get printf function or declare it if it doesn't exist
1114        let printf = match self.module.get_function("printf") {
1115            Some(f) => f,
1116            // int printf(const char *format, ...)
1117            None => self.add_function(
1118                "printf",
1119                &["format"],
1120                &[self.int_ptr_type.into()],
1121                Some(Linkage::External),
1122                false,
1123            ),
1124        };
1125        let (format_str, format_str_name) = match value {
1126            PrintValue::Real(_) => (format!("{name}: %f\n"), format!("real_format_{name}")),
1127            PrintValue::Int(_) => (format!("{name}: %d\n"), format!("int_format_{name}")),
1128        };
1129        // change format_str to c string
1130        let format_str = CString::new(format_str).unwrap();
1131        // if format_str_name doesn not already exist as a global, add it
1132        let format_str_global = match self.module.get_global(format_str_name.as_str()) {
1133            Some(g) => g,
1134            None => {
1135                let format_str = self.context.const_string(format_str.as_bytes(), true);
1136                let fmt_str =
1137                    self.module
1138                        .add_global(format_str.get_type(), None, format_str_name.as_str());
1139                fmt_str.set_initializer(&format_str);
1140                fmt_str.set_visibility(GlobalVisibility::Hidden);
1141                fmt_str
1142            }
1143        };
1144        // call printf with the format string and the value
1145        let format_str_ptr = self.builder.build_pointer_cast(
1146            format_str_global.as_pointer_value(),
1147            self.int_ptr_type,
1148            "format_str_ptr",
1149        )?;
1150        let value: BasicMetadataValueEnum = match value {
1151            PrintValue::Real(v) => v.into(),
1152            PrintValue::Int(v) => v.into(),
1153        };
1154        self.builder
1155            .build_call(printf, &[format_str_ptr.into(), value], "printf_call")
1156            .map_err(|e| anyhow!("Error building call to printf: {}", e))
1157    }
1158
1159    fn compile_set_constants(
1160        &mut self,
1161        model: &DiscreteModel,
1162        code: Option<&str>,
1163        constants_use_jit: bool,
1164    ) -> Result<FunctionValue<'ctx>> {
1165        self.clear();
1166        let fn_arg_names = &["thread_id", "thread_dim"];
1167        let function = self.add_function(
1168            "set_constants",
1169            fn_arg_names,
1170            &[self.int_type.into(), self.int_type.into()],
1171            None,
1172            false,
1173        );
1174        let _basic_block = self.start_function(function, code);
1175
1176        for (i, arg) in function.get_param_iter().enumerate() {
1177            let name = fn_arg_names[i];
1178            let alloca = self.function_arg_alloca(name, arg);
1179            self.insert_param(name, alloca);
1180        }
1181
1182        self.insert_indices();
1183        self.insert_constants(model);
1184
1185        if constants_use_jit {
1186            let mut nbarriers = 0;
1187            let total_barriers = (model.constant_defns().len()) as u64;
1188            let total_barriers_val = self.int_type.const_int(total_barriers, false);
1189            #[allow(clippy::explicit_counter_loop)]
1190            for a in model.constant_defns() {
1191                self.jit_compile_tensor(a, Some(*self.get_var(a)), code)?;
1192                let barrier_num = self.int_type.const_int(nbarriers + 1, false);
1193                self.jit_compile_call_barrier(barrier_num, total_barriers_val);
1194                nbarriers += 1;
1195            }
1196        }
1197
1198        self.builder.build_return(None)?;
1199
1200        if function.verify(true) {
1201            Ok(function)
1202        } else {
1203            function.print_to_stderr();
1204            self.module.print_to_stderr();
1205            unsafe {
1206                function.delete();
1207            }
1208            Err(anyhow!("Invalid generated function."))
1209        }
1210    }
1211
1212    fn compile_barrier_init(&mut self) -> Result<FunctionValue<'ctx>> {
1213        self.clear();
1214        let function = self.add_function("barrier_init", &[], &[], None, false);
1215        let _entry_block = self.start_function(function, None);
1216
1217        let thread_counter = self.globals.thread_counter.unwrap().as_pointer_value();
1218        self.builder
1219            .build_store(thread_counter, self.int_type.const_zero())?;
1220
1221        self.builder.build_return(None)?;
1222
1223        if function.verify(true) {
1224            Ok(function)
1225        } else {
1226            function.print_to_stderr();
1227            unsafe {
1228                function.delete();
1229            }
1230            Err(anyhow!("Invalid generated function."))
1231        }
1232    }
1233
1234    fn compile_barrier(&mut self) -> Result<FunctionValue<'ctx>> {
1235        self.clear();
1236        let function = self.add_function(
1237            "barrier",
1238            &["barrier_num", "total_barriers", "thread_count"],
1239            &[
1240                self.int_type.into(),
1241                self.int_type.into(),
1242                self.int_type.into(),
1243            ],
1244            None,
1245            false,
1246        );
1247        let nolinline_kind_id = Attribute::get_named_enum_kind_id("noinline");
1248        let noinline = self.context.create_enum_attribute(nolinline_kind_id, 0);
1249        function.add_attribute(AttributeLoc::Function, noinline);
1250
1251        let _entry_block = self.start_function(function, None);
1252        let increment_block = self.context.append_basic_block(function, "increment");
1253        let wait_loop_block = self.context.append_basic_block(function, "wait_loop");
1254        let barrier_done_block = self.context.append_basic_block(function, "barrier_done");
1255
1256        let thread_counter = self.globals.thread_counter.unwrap().as_pointer_value();
1257        let barrier_num = function.get_nth_param(0).unwrap().into_int_value();
1258        let total_barriers = function.get_nth_param(1).unwrap().into_int_value();
1259        let thread_count = function.get_nth_param(2).unwrap().into_int_value();
1260
1261        let nbarrier_equals_total_barriers = self
1262            .builder
1263            .build_int_compare(
1264                IntPredicate::EQ,
1265                barrier_num,
1266                total_barriers,
1267                "nbarrier_equals_total_barriers",
1268            )
1269            .unwrap();
1270        // branch to barrier_done if nbarrier == total_barriers
1271        self.builder.build_conditional_branch(
1272            nbarrier_equals_total_barriers,
1273            barrier_done_block,
1274            increment_block,
1275        )?;
1276        self.builder.position_at_end(increment_block);
1277
1278        let barrier_num_times_thread_count = self
1279            .builder
1280            .build_int_mul(barrier_num, thread_count, "barrier_num_times_thread_count")
1281            .unwrap();
1282
1283        // Atomically increment the barrier counter
1284        let i32_type = self.context.i32_type();
1285        let one = i32_type.const_int(1, false);
1286        self.builder.build_atomicrmw(
1287            AtomicRMWBinOp::Add,
1288            thread_counter,
1289            one,
1290            AtomicOrdering::Monotonic,
1291        )?;
1292
1293        // wait_loop:
1294        self.builder.build_unconditional_branch(wait_loop_block)?;
1295        self.builder.position_at_end(wait_loop_block);
1296
1297        let current_value = self
1298            .builder
1299            .build_load(i32_type, thread_counter, "current_value")?
1300            .into_int_value();
1301
1302        current_value
1303            .as_instruction_value()
1304            .unwrap()
1305            .set_atomic_ordering(AtomicOrdering::Monotonic)
1306            .map_err(|e| anyhow!("Error setting atomic ordering: {:?}", e))?;
1307
1308        let all_threads_done = self.builder.build_int_compare(
1309            IntPredicate::UGE,
1310            current_value,
1311            barrier_num_times_thread_count,
1312            "all_threads_done",
1313        )?;
1314
1315        self.builder.build_conditional_branch(
1316            all_threads_done,
1317            barrier_done_block,
1318            wait_loop_block,
1319        )?;
1320        self.builder.position_at_end(barrier_done_block);
1321
1322        self.builder.build_return(None)?;
1323
1324        if function.verify(true) {
1325            Ok(function)
1326        } else {
1327            function.print_to_stderr();
1328            unsafe {
1329                function.delete();
1330            }
1331            Err(anyhow!("Invalid generated function."))
1332        }
1333    }
1334
1335    fn compile_barrier_grad(&mut self) -> Result<FunctionValue<'ctx>> {
1336        self.clear();
1337        let function = self.add_function(
1338            "barrier_grad",
1339            &["barrier_num", "total_barriers", "thread_count"],
1340            &[
1341                self.int_type.into(),
1342                self.int_type.into(),
1343                self.int_type.into(),
1344            ],
1345            None,
1346            false,
1347        );
1348
1349        let _entry_block = self.start_function(function, None);
1350        let wait_loop_block = self.context.append_basic_block(function, "wait_loop");
1351        let barrier_done_block = self.context.append_basic_block(function, "barrier_done");
1352
1353        let thread_counter = self.globals.thread_counter.unwrap().as_pointer_value();
1354        let barrier_num = function.get_nth_param(0).unwrap().into_int_value();
1355        let total_barriers = function.get_nth_param(1).unwrap().into_int_value();
1356        let thread_count = function.get_nth_param(2).unwrap().into_int_value();
1357
1358        let twice_total_barriers = self
1359            .builder
1360            .build_int_mul(
1361                total_barriers,
1362                self.int_type.const_int(2, false),
1363                "twice_total_barriers",
1364            )
1365            .unwrap();
1366        let twice_total_barriers_minus_barrier_num = self
1367            .builder
1368            .build_int_sub(
1369                twice_total_barriers,
1370                barrier_num,
1371                "twice_total_barriers_minus_barrier_num",
1372            )
1373            .unwrap();
1374        let twice_total_barriers_minus_barrier_num_times_thread_count = self
1375            .builder
1376            .build_int_mul(
1377                twice_total_barriers_minus_barrier_num,
1378                thread_count,
1379                "twice_total_barriers_minus_barrier_num_times_thread_count",
1380            )
1381            .unwrap();
1382
1383        // Atomically increment the barrier counter
1384        let i32_type = self.context.i32_type();
1385        let one = i32_type.const_int(1, false);
1386        self.builder.build_atomicrmw(
1387            AtomicRMWBinOp::Add,
1388            thread_counter,
1389            one,
1390            AtomicOrdering::Monotonic,
1391        )?;
1392
1393        // wait_loop:
1394        self.builder.build_unconditional_branch(wait_loop_block)?;
1395        self.builder.position_at_end(wait_loop_block);
1396
1397        let current_value = self
1398            .builder
1399            .build_load(i32_type, thread_counter, "current_value")?
1400            .into_int_value();
1401        current_value
1402            .as_instruction_value()
1403            .unwrap()
1404            .set_atomic_ordering(AtomicOrdering::Monotonic)
1405            .map_err(|e| anyhow!("Error setting atomic ordering: {:?}", e))?;
1406
1407        let all_threads_done = self.builder.build_int_compare(
1408            IntPredicate::UGE,
1409            current_value,
1410            twice_total_barriers_minus_barrier_num_times_thread_count,
1411            "all_threads_done",
1412        )?;
1413
1414        self.builder.build_conditional_branch(
1415            all_threads_done,
1416            barrier_done_block,
1417            wait_loop_block,
1418        )?;
1419        self.builder.position_at_end(barrier_done_block);
1420
1421        self.builder.build_return(None)?;
1422
1423        if function.verify(true) {
1424            Ok(function)
1425        } else {
1426            function.print_to_stderr();
1427            unsafe {
1428                function.delete();
1429            }
1430            Err(anyhow!("Invalid generated function."))
1431        }
1432    }
1433
1434    fn jit_compile_call_barrier(
1435        &mut self,
1436        barrier_num: IntValue<'ctx>,
1437        total_barriers: IntValue<'ctx>,
1438    ) {
1439        if !self.threaded {
1440            return;
1441        }
1442        let thread_dim = self.get_param("thread_dim");
1443        let thread_dim = self
1444            .builder
1445            .build_load(self.int_type, *thread_dim, "thread_dim")
1446            .unwrap()
1447            .into_int_value();
1448        let barrier = self.get_function("barrier").unwrap();
1449        self.builder
1450            .build_call(
1451                barrier,
1452                &[
1453                    BasicMetadataValueEnum::IntValue(barrier_num),
1454                    BasicMetadataValueEnum::IntValue(total_barriers),
1455                    BasicMetadataValueEnum::IntValue(thread_dim),
1456                ],
1457                "barrier",
1458            )
1459            .unwrap();
1460    }
1461
1462    fn jit_threading_limits(
1463        &mut self,
1464        size: IntValue<'ctx>,
1465    ) -> Result<(
1466        IntValue<'ctx>,
1467        IntValue<'ctx>,
1468        BasicBlock<'ctx>,
1469        BasicBlock<'ctx>,
1470    )> {
1471        let one = self.int_type.const_int(1, false);
1472        let thread_id = self.get_param("thread_id");
1473        let thread_id = self
1474            .builder
1475            .build_load(self.int_type, *thread_id, "thread_id")
1476            .unwrap()
1477            .into_int_value();
1478        let thread_dim = self.get_param("thread_dim");
1479        let thread_dim = self
1480            .builder
1481            .build_load(self.int_type, *thread_dim, "thread_dim")
1482            .unwrap()
1483            .into_int_value();
1484
1485        // start index is i * size / thread_dim
1486        let i_times_size = self
1487            .builder
1488            .build_int_mul(thread_id, size, "i_times_size")?;
1489        let start = self
1490            .builder
1491            .build_int_unsigned_div(i_times_size, thread_dim, "start")?;
1492
1493        // the ending index for thread i is (i+1) * size / thread_dim
1494        let i_plus_one = self.builder.build_int_add(thread_id, one, "i_plus_one")?;
1495        let i_plus_one_times_size =
1496            self.builder
1497                .build_int_mul(i_plus_one, size, "i_plus_one_times_size")?;
1498        let end = self
1499            .builder
1500            .build_int_unsigned_div(i_plus_one_times_size, thread_dim, "end")?;
1501
1502        let test_done = self.builder.get_insert_block().unwrap();
1503        let next_block = self
1504            .context
1505            .append_basic_block(self.fn_value_opt.unwrap(), "threading_block");
1506        self.builder.position_at_end(next_block);
1507
1508        Ok((start, end, test_done, next_block))
1509    }
1510
1511    fn jit_end_threading(
1512        &mut self,
1513        start: IntValue<'ctx>,
1514        end: IntValue<'ctx>,
1515        test_done: BasicBlock<'ctx>,
1516        next: BasicBlock<'ctx>,
1517    ) -> Result<()> {
1518        let exit = self
1519            .context
1520            .append_basic_block(self.fn_value_opt.unwrap(), "exit");
1521        self.builder.build_unconditional_branch(exit)?;
1522        self.builder.position_at_end(test_done);
1523        // done if start == end
1524        let done = self
1525            .builder
1526            .build_int_compare(IntPredicate::EQ, start, end, "done")?;
1527        self.builder.build_conditional_branch(done, exit, next)?;
1528        self.builder.position_at_end(exit);
1529        Ok(())
1530    }
1531
1532    pub fn write_bitcode_to_path(&self, path: &std::path::Path) {
1533        self.module.write_bitcode_to_path(path);
1534    }
1535
1536    fn insert_constants(&mut self, model: &DiscreteModel) {
1537        if let Some(constants) = self.globals.constants.as_ref() {
1538            self.insert_param("constants", constants.as_pointer_value());
1539            for tensor in model.constant_defns() {
1540                self.insert_tensor(tensor, true);
1541            }
1542        }
1543    }
1544
1545    fn insert_data(&mut self, model: &DiscreteModel) {
1546        self.insert_model_index();
1547        self.insert_constants(model);
1548
1549        if let Some(input) = model.input() {
1550            self.insert_tensor(input, false);
1551        }
1552        for tensor in model.input_dep_defns() {
1553            self.insert_tensor(tensor, false);
1554        }
1555        for tensor in model.time_dep_defns() {
1556            self.insert_tensor(tensor, false);
1557        }
1558        for tensor in model.state_dep_defns() {
1559            self.insert_tensor(tensor, false);
1560        }
1561        for tensor in model.state_dep_post_f_defns() {
1562            self.insert_tensor(tensor, false);
1563        }
1564    }
1565
1566    fn pointer_type(context: &'ctx Context, _ty: BasicTypeEnum<'ctx>) -> PointerType<'ctx> {
1567        context.ptr_type(AddressSpace::default())
1568    }
1569
1570    fn fn_pointer_type(context: &'ctx Context, _ty: FunctionType<'ctx>) -> PointerType<'ctx> {
1571        context.ptr_type(AddressSpace::default())
1572    }
1573
1574    fn insert_indices(&mut self) {
1575        if let Some(indices) = self.globals.indices.as_ref() {
1576            let i32_type = self.context.i32_type();
1577            let zero = i32_type.const_int(0, false);
1578            let ptr = unsafe {
1579                indices
1580                    .as_pointer_value()
1581                    .const_in_bounds_gep(i32_type, &[zero])
1582            };
1583            self.variables.insert("indices".to_owned(), ptr);
1584        }
1585    }
1586
1587    fn insert_model_index(&mut self) {
1588        self.insert_param("model_index", self.globals.model_index.as_pointer_value());
1589    }
1590
1591    fn insert_param(&mut self, name: &str, value: PointerValue<'ctx>) {
1592        self.variables.insert(name.to_owned(), value);
1593    }
1594
1595    fn build_gep<T: BasicType<'ctx>>(
1596        &self,
1597        ty: T,
1598        ptr: PointerValue<'ctx>,
1599        ordered_indexes: &[IntValue<'ctx>],
1600        name: &str,
1601    ) -> Result<PointerValue<'ctx>> {
1602        unsafe {
1603            self.builder
1604                .build_gep(ty, ptr, ordered_indexes, name)
1605                .map_err(|e| e.into())
1606        }
1607    }
1608
1609    fn build_load<T: BasicType<'ctx>>(
1610        &self,
1611        ty: T,
1612        ptr: PointerValue<'ctx>,
1613        name: &str,
1614    ) -> Result<BasicValueEnum<'ctx>> {
1615        self.builder.build_load(ty, ptr, name).map_err(|e| e.into())
1616    }
1617
1618    fn get_ptr_to_index<T: BasicType<'ctx>>(
1619        builder: &Builder<'ctx>,
1620        ty: T,
1621        ptr: &PointerValue<'ctx>,
1622        index: IntValue<'ctx>,
1623        name: &str,
1624    ) -> PointerValue<'ctx> {
1625        unsafe {
1626            builder
1627                .build_in_bounds_gep(ty, *ptr, &[index], name)
1628                .unwrap()
1629        }
1630    }
1631
1632    fn insert_state(&mut self, u: &Tensor) {
1633        let mut data_index = 0;
1634        for blk in u.elmts() {
1635            if let Some(name) = blk.name() {
1636                let ptr = self.variables.get("u").unwrap();
1637                let i = self
1638                    .context
1639                    .i32_type()
1640                    .const_int(data_index.try_into().unwrap(), false);
1641                let alloca = Self::get_ptr_to_index(
1642                    &self.create_entry_block_builder(),
1643                    self.real_type,
1644                    ptr,
1645                    i,
1646                    blk.name().unwrap(),
1647                );
1648                self.variables.insert(name.to_owned(), alloca);
1649            }
1650            data_index += blk.nnz();
1651        }
1652    }
1653    fn insert_dot_state(&mut self, dudt: &Tensor) {
1654        let mut data_index = 0;
1655        for blk in dudt.elmts() {
1656            if let Some(name) = blk.name() {
1657                let ptr = self.variables.get("dudt").unwrap();
1658                let i = self
1659                    .context
1660                    .i32_type()
1661                    .const_int(data_index.try_into().unwrap(), false);
1662                let alloca = Self::get_ptr_to_index(
1663                    &self.create_entry_block_builder(),
1664                    self.real_type,
1665                    ptr,
1666                    i,
1667                    blk.name().unwrap(),
1668                );
1669                self.variables.insert(name.to_owned(), alloca);
1670            }
1671            data_index += blk.nnz();
1672        }
1673    }
1674    fn insert_tensor(&mut self, tensor: &Tensor, is_constant: bool) {
1675        let var_name = if is_constant { "constants" } else { "data" };
1676        let ptr = *self.variables.get(var_name).unwrap();
1677        let mut data_index = self.layout.get_data_index(tensor.name()).unwrap();
1678        let i = self
1679            .context
1680            .i32_type()
1681            .const_int(data_index.try_into().unwrap(), false);
1682        let alloca = Self::get_ptr_to_index(
1683            &self.create_entry_block_builder(),
1684            self.real_type,
1685            &ptr,
1686            i,
1687            tensor.name(),
1688        );
1689        self.variables.insert(tensor.name().to_owned(), alloca);
1690
1691        //insert any named blocks
1692        for blk in tensor.elmts() {
1693            if let Some(name) = blk.name() {
1694                let i = self
1695                    .context
1696                    .i32_type()
1697                    .const_int(data_index.try_into().unwrap(), false);
1698                let alloca = Self::get_ptr_to_index(
1699                    &self.create_entry_block_builder(),
1700                    self.real_type,
1701                    &ptr,
1702                    i,
1703                    name,
1704                );
1705                self.variables.insert(name.to_owned(), alloca);
1706            }
1707            // named blocks only supported for rank <= 1, so we can just add the nnz to get the next data index
1708            data_index += blk.nnz();
1709        }
1710    }
1711    fn get_param(&self, name: &str) -> &PointerValue<'ctx> {
1712        self.variables.get(name).unwrap()
1713    }
1714
1715    fn get_var(&self, tensor: &Tensor) -> &PointerValue<'ctx> {
1716        self.variables.get(tensor.name()).unwrap()
1717    }
1718
1719    fn get_function(&mut self, name: &str) -> Option<FunctionValue<'ctx>> {
1720        // Check cache for function
1721        if let Some(&func) = self.functions.get(name) {
1722            return Some(func);
1723        }
1724        let current_block = self.builder.get_insert_block().unwrap();
1725        let current_loc = self.builder.get_current_debug_location().unwrap();
1726        let current_function = self.fn_value_opt.unwrap();
1727
1728        let function = match name {
1729            // support some llvm intrinsics
1730            "sin" | "cos" | "tan" | "exp" | "log" | "log10" | "sqrt" | "abs" | "copysign"
1731            | "pow" | "min" | "max" => {
1732                let intrinsic_name = match name {
1733                    "min" => "minnum",
1734                    "max" => "maxnum",
1735                    "abs" => "fabs",
1736                    _ => name,
1737                };
1738                let llvm_name =
1739                    format!("llvm.{}.{}", intrinsic_name, self.diffsl_real_type.as_str());
1740
1741                let args_types: Vec<BasicTypeEnum> = vec![self.real_type.into()];
1742                self.builder.unset_current_debug_location();
1743
1744                // Try intrinsic first, fall back to libm for all functions
1745                Some(match Intrinsic::find(&llvm_name) {
1746                    Some(intrinsic) => intrinsic.get_declaration(&self.module, &args_types)?,
1747                    None => {
1748                        // Fallback: declare external libm function
1749                        let args_types_meta: Vec<BasicMetadataTypeEnum> =
1750                            vec![self.real_type.into()];
1751                        let function_type = self.real_type.fn_type(&args_types_meta, false);
1752                        self.module
1753                            .add_function(name, function_type, Some(Linkage::External))
1754                    }
1755                })
1756            }
1757            // some custom functions
1758            "sigmoid" => {
1759                let arg_len = 1;
1760                let ret_type = self.real_type;
1761
1762                let args_types = std::iter::repeat_n(ret_type, arg_len)
1763                    .map(|f| f.into())
1764                    .collect::<Vec<BasicMetadataTypeEnum>>();
1765
1766                let fn_val = self.add_function(name, &["x"], &args_types, None, true);
1767
1768                for arg in fn_val.get_param_iter() {
1769                    arg.into_float_value().set_name("x");
1770                }
1771                let _basic_block = self.start_function(fn_val, None);
1772
1773                let x = fn_val.get_nth_param(0)?.into_float_value();
1774                let one = self.real_type.const_float(1.0);
1775                let negx = self.builder.build_float_neg(x, name).ok()?;
1776                let exp = self.get_function("exp").unwrap();
1777                let exp_negx = self
1778                    .builder
1779                    .build_call(exp, &[BasicMetadataValueEnum::FloatValue(negx)], name)
1780                    .ok()?;
1781                let one_plus_exp_negx = self
1782                    .builder
1783                    .build_float_add(
1784                        exp_negx
1785                            .try_as_basic_value()
1786                            .unwrap_basic()
1787                            .into_float_value(),
1788                        one,
1789                        name,
1790                    )
1791                    .ok()?;
1792                let sigmoid = self
1793                    .builder
1794                    .build_float_div(one, one_plus_exp_negx, name)
1795                    .ok()?;
1796                self.builder.build_return(Some(&sigmoid)).ok();
1797                Some(fn_val)
1798            }
1799            "arcsinh" | "arccosh" => {
1800                let arg_len = 1;
1801                let ret_type = self.real_type;
1802
1803                let args_types = std::iter::repeat_n(ret_type, arg_len)
1804                    .map(|f| f.into())
1805                    .collect::<Vec<BasicMetadataTypeEnum>>();
1806
1807                let fn_val = self.add_function(name, &["x"], &args_types, None, true);
1808
1809                for arg in fn_val.get_param_iter() {
1810                    arg.into_float_value().set_name("x");
1811                }
1812
1813                let _basic_block = self.start_function(fn_val, None);
1814                let x = fn_val.get_nth_param(0)?.into_float_value();
1815                let one = match name {
1816                    "arccosh" => self.real_type.const_float(-1.0),
1817                    "arcsinh" => self.real_type.const_float(1.0),
1818                    _ => panic!("unknown function"),
1819                };
1820                let x_squared = self.builder.build_float_mul(x, x, name).ok()?;
1821                let one_plus_x_squared = self.builder.build_float_add(x_squared, one, name).ok()?;
1822                let sqrt = self.get_function("sqrt").unwrap();
1823                let sqrt_one_plus_x_squared = self
1824                    .builder
1825                    .build_call(
1826                        sqrt,
1827                        &[BasicMetadataValueEnum::FloatValue(one_plus_x_squared)],
1828                        name,
1829                    )
1830                    .unwrap()
1831                    .try_as_basic_value()
1832                    .unwrap_basic()
1833                    .into_float_value();
1834                let x_plus_sqrt_one_plus_x_squared = self
1835                    .builder
1836                    .build_float_add(x, sqrt_one_plus_x_squared, name)
1837                    .ok()?;
1838                let ln = self.get_function("log").unwrap();
1839                let result = self
1840                    .builder
1841                    .build_call(
1842                        ln,
1843                        &[BasicMetadataValueEnum::FloatValue(
1844                            x_plus_sqrt_one_plus_x_squared,
1845                        )],
1846                        name,
1847                    )
1848                    .unwrap()
1849                    .try_as_basic_value()
1850                    .unwrap_basic()
1851                    .into_float_value();
1852                self.builder.build_return(Some(&result)).ok();
1853                Some(fn_val)
1854            }
1855            "heaviside" => {
1856                let arg_len = 1;
1857                let ret_type = self.real_type;
1858
1859                let args_types = std::iter::repeat_n(ret_type, arg_len)
1860                    .map(|f| f.into())
1861                    .collect::<Vec<BasicMetadataTypeEnum>>();
1862
1863                let fn_val = self.add_function(name, &["x"], &args_types, None, true);
1864
1865                for arg in fn_val.get_param_iter() {
1866                    arg.into_float_value().set_name("x");
1867                }
1868
1869                let _basic_block = self.start_function(fn_val, None);
1870                let x = fn_val.get_nth_param(0)?.into_float_value();
1871                let zero = self.real_type.const_float(0.0);
1872                let one = self.real_type.const_float(1.0);
1873                let result = self
1874                    .builder
1875                    .build_select(
1876                        self.builder
1877                            .build_float_compare(FloatPredicate::OGE, x, zero, "x >= 0")
1878                            .unwrap(),
1879                        one,
1880                        zero,
1881                        name,
1882                    )
1883                    .ok()?;
1884                self.builder.build_return(Some(&result)).ok();
1885                Some(fn_val)
1886            }
1887            "tanh" | "sinh" | "cosh" => {
1888                let arg_len = 1;
1889                let ret_type = self.real_type;
1890
1891                let args_types = std::iter::repeat_n(ret_type, arg_len)
1892                    .map(|f| f.into())
1893                    .collect::<Vec<BasicMetadataTypeEnum>>();
1894
1895                let fn_val = self.add_function(name, &["x"], &args_types, None, true);
1896
1897                for arg in fn_val.get_param_iter() {
1898                    arg.into_float_value().set_name("x");
1899                }
1900
1901                let _basic_block = self.start_function(fn_val, None);
1902                let x = fn_val.get_nth_param(0)?.into_float_value();
1903                let negx = self.builder.build_float_neg(x, name).ok()?;
1904                let exp = self.get_function("exp").unwrap();
1905                let exp_negx = self
1906                    .builder
1907                    .build_call(exp, &[BasicMetadataValueEnum::FloatValue(negx)], name)
1908                    .ok()?;
1909                let expx = self
1910                    .builder
1911                    .build_call(exp, &[BasicMetadataValueEnum::FloatValue(x)], name)
1912                    .ok()?;
1913                let expx_minus_exp_negx = self
1914                    .builder
1915                    .build_float_sub(
1916                        expx.try_as_basic_value().unwrap_basic().into_float_value(),
1917                        exp_negx
1918                            .try_as_basic_value()
1919                            .unwrap_basic()
1920                            .into_float_value(),
1921                        name,
1922                    )
1923                    .ok()?;
1924                let expx_plus_exp_negx = self
1925                    .builder
1926                    .build_float_add(
1927                        expx.try_as_basic_value().unwrap_basic().into_float_value(),
1928                        exp_negx
1929                            .try_as_basic_value()
1930                            .unwrap_basic()
1931                            .into_float_value(),
1932                        name,
1933                    )
1934                    .ok()?;
1935                let result = match name {
1936                    "tanh" => self
1937                        .builder
1938                        .build_float_div(expx_minus_exp_negx, expx_plus_exp_negx, name)
1939                        .ok()?,
1940                    "sinh" => self
1941                        .builder
1942                        .build_float_div(expx_minus_exp_negx, self.real_type.const_float(2.0), name)
1943                        .ok()?,
1944                    "cosh" => self
1945                        .builder
1946                        .build_float_div(expx_plus_exp_negx, self.real_type.const_float(2.0), name)
1947                        .ok()?,
1948                    _ => panic!("unknown function"),
1949                };
1950                self.builder.build_return(Some(&result)).ok();
1951                Some(fn_val)
1952            }
1953            "interp1d_impl" => self.compile_interp1d_impl(name).ok(),
1954            _ => None,
1955        }?;
1956
1957        if !function.verify(true) {
1958            function.print_to_stderr();
1959            panic!("Invalid generated function for {}", name);
1960        }
1961
1962        self.builder.position_at_end(current_block);
1963        if self.dibuilder.is_some() {
1964            self.builder.set_current_debug_location(current_loc);
1965        }
1966        self.fn_value_opt = Some(current_function);
1967
1968        self.functions.insert(name.to_owned(), function);
1969        Some(function)
1970    }
1971
1972    /// Compile a shared helper for piecewise-linear interpolation on arbitrary
1973    /// (non-uniform) x/y data.  One caller per query point; takes the base
1974    /// pointer into the constants array, the element offsets of x and y, the
1975    /// number of entries, and the query value `q`.
1976    ///
1977    /// The generated function body performs a classic binary search:
1978    ///
1979    /// * Clamp `q` to `[x[0], x[n-1]]`.
1980    /// * Loop block with `lo`/`hi` phis (initial `(0, n-1)`):
1981    ///   mid = (lo + hi + 1) // 2
1982    ///   if x[mid] <= q → lo = mid  else  hi = mid - 1
1983    /// * Exit block: clamp index, load `x[k], x[k+1], y[k], y[k+1]`, blend.
1984    ///
1985    /// Enzyme can auto-diff through the generated branches and arithmetic.
1986    fn compile_interp1d_impl(&mut self, name: &str) -> Result<FunctionValue<'ctx>> {
1987        let ret_type = self.real_type;
1988        let args_types = vec![
1989            self.real_ptr_type.into(), // constants ptr
1990            self.int_type.into(),      // x_off
1991            self.int_type.into(),      // y_off
1992            self.int_type.into(),      // n
1993            ret_type.into(),           // q
1994        ];
1995        let fn_val = self.add_function(
1996            name,
1997            &["constants", "x_off", "y_off", "n", "q"],
1998            &args_types,
1999            None,
2000            true,
2001        );
2002
2003        let entry = self.start_function(fn_val, None);
2004        let constants_ptr = fn_val
2005            .get_nth_param(0)
2006            .ok_or_else(|| anyhow!("missing param 0"))?
2007            .into_pointer_value();
2008        let x_off = fn_val
2009            .get_nth_param(1)
2010            .ok_or_else(|| anyhow!("missing param 1"))?
2011            .into_int_value();
2012        let y_off = fn_val
2013            .get_nth_param(2)
2014            .ok_or_else(|| anyhow!("missing param 2"))?
2015            .into_int_value();
2016        let n_val = fn_val
2017            .get_nth_param(3)
2018            .ok_or_else(|| anyhow!("missing param 3"))?
2019            .into_int_value();
2020        let q = fn_val
2021            .get_nth_param(4)
2022            .ok_or_else(|| anyhow!("missing param 4"))?
2023            .into_float_value();
2024
2025        // --- clamp q to [x[0], x[n-1]] ---
2026        let x0_ptr = self.build_gep(self.real_type, constants_ptr, &[x_off], "x0_ptr")?;
2027        let x0 = self
2028            .build_load(self.real_type, x0_ptr, "x0")?
2029            .into_float_value();
2030        let one = self.int_type.const_int(1, false);
2031        let nm1 = self.builder.build_int_sub(n_val, one, "nm1")?;
2032        let xn_off = self.builder.build_int_add(x_off, nm1, "xn_off")?;
2033        let xn_ptr = self.build_gep(self.real_type, constants_ptr, &[xn_off], "xn_ptr")?;
2034        let xn = self
2035            .build_load(self.real_type, xn_ptr, "xn")?
2036            .into_float_value();
2037        let q_lt_xn = self
2038            .builder
2039            .build_float_compare(FloatPredicate::OLT, q, xn, "q_lt_xn")?;
2040        let q = self
2041            .builder
2042            .build_select(q_lt_xn, q, xn, "q_min")?
2043            .into_float_value();
2044        let q_gt_x0 = self
2045            .builder
2046            .build_float_compare(FloatPredicate::OGT, q, x0, "q_gt_x0")?;
2047        let q = self
2048            .builder
2049            .build_select(q_gt_x0, q, x0, "q_max")?
2050            .into_float_value();
2051
2052        // --- binary search loop: while lo < hi ---
2053        let loop_block = self.context.append_basic_block(fn_val, "loop");
2054        let exit_block = self.context.append_basic_block(fn_val, "exit");
2055        self.builder.build_unconditional_branch(loop_block)?;
2056        self.builder.position_at_end(loop_block);
2057
2058        let lo = self.builder.build_phi(self.int_type, "lo")?;
2059        let hi = self.builder.build_phi(self.int_type, "hi")?;
2060        let zero = self.int_type.const_zero();
2061        let nm2 = self
2062            .builder
2063            .build_int_sub(n_val, self.int_type.const_int(2, false), "nm2")?;
2064        lo.add_incoming(&[(&zero, entry)]);
2065        hi.add_incoming(&[(&nm1, entry)]);
2066
2067        let lo_v = lo.as_basic_value().into_int_value();
2068        let hi_v = hi.as_basic_value().into_int_value();
2069        let cmp = self
2070            .builder
2071            .build_int_compare(IntPredicate::SLT, lo_v, hi_v, "cmp")?;
2072        let body_block = self.context.append_basic_block(fn_val, "body");
2073        self.builder
2074            .build_conditional_branch(cmp, body_block, exit_block)?;
2075
2076        // body: mid = (lo + hi + 1) // 2, compare x[mid] with q
2077        self.builder.position_at_end(body_block);
2078        let sum = self.builder.build_int_add(lo_v, hi_v, "sum")?;
2079        let sum1 = self.builder.build_int_add(sum, one, "sum1")?;
2080        let two = self.int_type.const_int(2, false);
2081        let mid = self.builder.build_int_unsigned_div(sum1, two, "mid")?;
2082        let x_mid_off = self.builder.build_int_add(x_off, mid, "x_mid_off")?;
2083        let x_mid_ptr = self.build_gep(self.real_type, constants_ptr, &[x_mid_off], "x_mid_ptr")?;
2084        let x_mid = self
2085            .build_load(self.real_type, x_mid_ptr, "x_mid")?
2086            .into_float_value();
2087        let x_le_q = self
2088            .builder
2089            .build_float_compare(FloatPredicate::OLE, x_mid, q, "x_le_q")?;
2090        let mid_m1 = self.builder.build_int_sub(mid, one, "mid_m1")?;
2091        let new_lo = self
2092            .builder
2093            .build_select(x_le_q, mid, lo_v, "new_lo")?
2094            .into_int_value();
2095        let new_hi = self
2096            .builder
2097            .build_select(x_le_q, hi_v, mid_m1, "new_hi")?
2098            .into_int_value();
2099
2100        // branch back to loop with updated lo/hi
2101        self.builder.build_unconditional_branch(loop_block)?;
2102        lo.add_incoming(&[(&new_lo, body_block)]);
2103        hi.add_incoming(&[(&new_hi, body_block)]);
2104
2105        // --- exit: clamp k, load bracket values, blend ---
2106        self.builder.position_at_end(exit_block);
2107        let k_raw = lo.as_basic_value().into_int_value();
2108        let k_gt = self
2109            .builder
2110            .build_int_compare(IntPredicate::SGT, k_raw, nm2, "k_gt")?;
2111        let k = self
2112            .builder
2113            .build_select(k_gt, nm2, k_raw, "k")?
2114            .into_int_value();
2115        let kp1 = self.builder.build_int_add(k, one, "kp1")?;
2116
2117        let xk_off = self.builder.build_int_add(x_off, k, "xk_off")?;
2118        let xk1_off = self.builder.build_int_add(x_off, kp1, "xk1_off")?;
2119        let yk_off = self.builder.build_int_add(y_off, k, "yk_off")?;
2120        let yk1_off = self.builder.build_int_add(y_off, kp1, "yk1_off")?;
2121
2122        let xk_ptr = self.build_gep(self.real_type, constants_ptr, &[xk_off], "xk_ptr")?;
2123        let xk = self
2124            .build_load(self.real_type, xk_ptr, "xk")?
2125            .into_float_value();
2126        let xk1_ptr = self.build_gep(self.real_type, constants_ptr, &[xk1_off], "xk1_ptr")?;
2127        let xk1 = self
2128            .build_load(self.real_type, xk1_ptr, "xk1")?
2129            .into_float_value();
2130        let yk_ptr = self.build_gep(self.real_type, constants_ptr, &[yk_off], "yk_ptr")?;
2131        let yk = self
2132            .build_load(self.real_type, yk_ptr, "yk")?
2133            .into_float_value();
2134        let yk1_ptr = self.build_gep(self.real_type, constants_ptr, &[yk1_off], "yk1_ptr")?;
2135        let yk1 = self
2136            .build_load(self.real_type, yk1_ptr, "yk1")?
2137            .into_float_value();
2138
2139        let dx = self.builder.build_float_sub(xk1, xk, "dx")?;
2140        let dq = self.builder.build_float_sub(q, xk, "dq")?;
2141        let t = self.builder.build_float_div(dq, dx, "t")?;
2142        let dy = self.builder.build_float_sub(yk1, yk, "dy")?;
2143        let tdy = self.builder.build_float_mul(t, dy, "tdy")?;
2144        let result = self.builder.build_float_add(yk, tdy, name)?;
2145
2146        self.builder.build_return(Some(&result)).ok();
2147        Ok(fn_val)
2148    }
2149
2150    /// Returns the `FunctionValue` representing the function being compiled.
2151    #[inline]
2152    fn fn_value(&self) -> FunctionValue<'ctx> {
2153        self.fn_value_opt.unwrap()
2154    }
2155
2156    #[inline]
2157    fn tensor_ptr(&self) -> PointerValue<'ctx> {
2158        self.tensor_ptr_opt.unwrap()
2159    }
2160
2161    /// Creates a new builder in the entry block of the function.
2162    fn create_entry_block_builder(&self) -> Builder<'ctx> {
2163        let builder = self.context.create_builder();
2164        let entry = self.fn_value().get_first_basic_block().unwrap();
2165        match entry.get_first_instruction() {
2166            Some(first_instr) => builder.position_before(&first_instr),
2167            None => builder.position_at_end(entry),
2168        }
2169        builder
2170    }
2171
2172    fn jit_compile_scalar(
2173        &mut self,
2174        a: &Tensor,
2175        res_ptr_opt: Option<PointerValue<'ctx>>,
2176    ) -> Result<PointerValue<'ctx>> {
2177        let res_type = self.real_type;
2178        let res_ptr = match res_ptr_opt {
2179            Some(ptr) => ptr,
2180            None => self
2181                .create_entry_block_builder()
2182                .build_alloca(res_type, a.name())?,
2183        };
2184        let name = a.name();
2185        let elmt = a.elmts().first().unwrap();
2186
2187        // if threaded then only the first thread will evaluate the scalar
2188        let curr_block = self.builder.get_insert_block().unwrap();
2189        let mut next_block_opt = None;
2190        if self.threaded {
2191            let next_block = self.context.append_basic_block(self.fn_value(), "next");
2192            self.builder.position_at_end(next_block);
2193            next_block_opt = Some(next_block);
2194        }
2195
2196        let zero = self.int_type.const_zero();
2197        let float_value = self.jit_compile_expr(name, elmt.expr(), &[], elmt, zero)?;
2198        self.builder.build_store(res_ptr, float_value)?;
2199
2200        // complete the threading block
2201        if self.threaded {
2202            let exit_block = self.context.append_basic_block(self.fn_value(), "exit");
2203            self.builder.build_unconditional_branch(exit_block)?;
2204            self.builder.position_at_end(curr_block);
2205
2206            let thread_id = self.get_param("thread_id");
2207            let thread_id = self
2208                .builder
2209                .build_load(self.int_type, *thread_id, "thread_id")
2210                .unwrap()
2211                .into_int_value();
2212            let is_first_thread = self.builder.build_int_compare(
2213                IntPredicate::EQ,
2214                thread_id,
2215                self.int_type.const_zero(),
2216                "is_first_thread",
2217            )?;
2218            self.builder.build_conditional_branch(
2219                is_first_thread,
2220                next_block_opt.unwrap(),
2221                exit_block,
2222            )?;
2223
2224            self.builder.position_at_end(exit_block);
2225        }
2226
2227        Ok(res_ptr)
2228    }
2229
2230    fn jit_compile_tensor(
2231        &mut self,
2232        a: &Tensor,
2233        res_ptr_opt: Option<PointerValue<'ctx>>,
2234        code: Option<&str>,
2235    ) -> Result<PointerValue<'ctx>> {
2236        // treat scalar as a special case (only if not a contraction)
2237        if a.rank() == 0
2238            && a.elmts()
2239                .first()
2240                .is_none_or(|b| b.expr_layout().rank() == 0)
2241        {
2242            return self.jit_compile_scalar(a, res_ptr_opt);
2243        }
2244
2245        let res_type = self.real_type;
2246        let res_ptr = match res_ptr_opt {
2247            Some(ptr) => ptr,
2248            None => self
2249                .create_entry_block_builder()
2250                .build_alloca(res_type, a.name())?,
2251        };
2252
2253        // set up the tensor storage pointer and index into this data
2254        self.tensor_ptr_opt = Some(res_ptr);
2255
2256        for (i, blk) in a.elmts().iter().enumerate() {
2257            if blk.has_values() {
2258                continue;
2259            }
2260            let default = format!("{}-{}", a.name(), i);
2261            let name = blk.name().unwrap_or(default.as_str());
2262            self.jit_compile_block(name, a, blk, code)?;
2263        }
2264        Ok(res_ptr)
2265    }
2266
2267    fn jit_compile_block(
2268        &mut self,
2269        name: &str,
2270        tensor: &Tensor,
2271        elmt: &TensorBlock,
2272        code: Option<&str>,
2273    ) -> Result<()> {
2274        let translation = Translation::new(
2275            elmt.expr_layout(),
2276            elmt.layout(),
2277            elmt.start(),
2278            tensor.layout_ptr(),
2279        );
2280
2281        if let Some(dibuilder) = &self.dibuilder {
2282            let (line_no, column_no) = match (elmt.expr().span, code) {
2283                (Some(s), Some(c)) => {
2284                    let s = Span::new(c, s.pos_start, s.pos_end).unwrap();
2285                    s.start_pos().line_col()
2286                }
2287                _ => (0, 0),
2288            };
2289            let scope = self
2290                .fn_value_opt
2291                .unwrap()
2292                .get_subprogram()
2293                .unwrap()
2294                .as_debug_info_scope();
2295            let loc = dibuilder.create_debug_location(
2296                self.context,
2297                line_no.try_into().unwrap(),
2298                column_no.try_into().unwrap(),
2299                scope,
2300                None,
2301            );
2302            self.builder.set_current_debug_location(loc);
2303        }
2304
2305        // Diagonal 1D->0D contraction produces DenseContraction (since contract_last_axis
2306        // turns it into a Dense scalar); route to dense path for accumulation support.
2307        if elmt.expr_layout().is_dense()
2308            || matches!(translation.source, TranslationFrom::DenseContraction { .. })
2309        {
2310            self.jit_compile_dense_block(name, elmt, &translation)
2311        } else if elmt.expr_layout().is_diagonal() {
2312            self.jit_compile_diagonal_block(name, elmt, &translation)
2313        } else if elmt.expr_layout().is_sparse() {
2314            match translation.source {
2315                TranslationFrom::SparseContraction { .. } => {
2316                    self.jit_compile_sparse_contraction_block(name, elmt, &translation)
2317                }
2318                _ => self.jit_compile_sparse_block(name, elmt, &translation),
2319            }
2320        } else {
2321            Err(anyhow!(
2322                "unsupported block layout: {:?}",
2323                elmt.expr_layout()
2324            ))
2325        }
2326    }
2327
2328    // for dense blocks we can loop through the nested loops to calculate the index, then we compile the expression passing in this index
2329    fn jit_compile_dense_block(
2330        &mut self,
2331        name: &str,
2332        elmt: &TensorBlock,
2333        translation: &Translation,
2334    ) -> Result<()> {
2335        let int_type = self.int_type;
2336
2337        let mut preblock = self.builder.get_insert_block().unwrap();
2338        let expr_rank = elmt.expr_layout().rank();
2339        let expr_shape = elmt
2340            .expr_layout()
2341            .shape()
2342            .mapv(|n| int_type.const_int(n.try_into().unwrap(), false));
2343        let one = int_type.const_int(1, false);
2344
2345        let mut expr_strides = vec![1; expr_rank];
2346        if expr_rank > 0 {
2347            for i in (0..expr_rank - 1).rev() {
2348                expr_strides[i] = expr_strides[i + 1] * elmt.expr_layout().shape()[i + 1];
2349            }
2350        }
2351        let expr_strides = expr_strides
2352            .iter()
2353            .map(|&s| int_type.const_int(s.try_into().unwrap(), false))
2354            .collect::<Vec<IntValue>>();
2355
2356        // setup indices, loop through the nested loops
2357        let mut indices = Vec::new();
2358        let mut blocks = Vec::new();
2359
2360        // allocate the contract sum if needed
2361        let (contract_sum, contract_by, contract_strides) =
2362            if let TranslationFrom::DenseContraction {
2363                contract_by,
2364                contract_len: _,
2365            } = translation.source
2366            {
2367                let contract_rank = expr_rank - contract_by;
2368                let mut contract_strides = vec![1; contract_rank];
2369                for i in (0..contract_rank.saturating_sub(1)).rev() {
2370                    contract_strides[i] =
2371                        contract_strides[i + 1] * elmt.expr_layout().shape()[i + 1];
2372                }
2373                let contract_strides = contract_strides
2374                    .iter()
2375                    .map(|&s| int_type.const_int(s.try_into().unwrap(), false))
2376                    .collect::<Vec<IntValue>>();
2377                (
2378                    Some(self.builder.build_alloca(self.real_type, "contract_sum")?),
2379                    contract_by,
2380                    Some(contract_strides),
2381                )
2382            } else {
2383                (None, 0, None)
2384            };
2385
2386        // we will thread the output loop, except if we are contracting to a scalar
2387        let (thread_start, thread_end, test_done, next) = if self.threaded {
2388            let (start, end, test_done, next) =
2389                self.jit_threading_limits(*expr_shape.get(0).unwrap_or(&one))?;
2390            preblock = next;
2391            (Some(start), Some(end), Some(test_done), Some(next))
2392        } else {
2393            (None, None, None, None)
2394        };
2395
2396        // if all axes are contracted, initialize the sum before the loops
2397        if contract_by == expr_rank {
2398            if let Some(contract_sum) = contract_sum {
2399                self.builder
2400                    .build_store(contract_sum, self.real_type.const_zero())?;
2401            }
2402        }
2403
2404        for i in 0..expr_rank {
2405            let block = self.context.append_basic_block(self.fn_value(), name);
2406            self.builder.build_unconditional_branch(block)?;
2407            self.builder.position_at_end(block);
2408
2409            let start_index = if i == 0 && self.threaded {
2410                thread_start.unwrap()
2411            } else {
2412                self.int_type.const_zero()
2413            };
2414
2415            let curr_index = self.builder.build_phi(int_type, format!["i{i}"].as_str())?;
2416            curr_index.add_incoming(&[(&start_index, preblock)]);
2417
2418            let init_point = (expr_rank - contract_by).saturating_sub(1);
2419            if i == init_point && contract_by != expr_rank {
2420                if let Some(contract_sum) = contract_sum {
2421                    self.builder
2422                        .build_store(contract_sum, self.real_type.const_zero())?;
2423                }
2424            }
2425
2426            indices.push(curr_index);
2427            blocks.push(block);
2428            preblock = block;
2429        }
2430
2431        let indices_int: Vec<IntValue> = indices
2432            .iter()
2433            .map(|i| i.as_basic_value().into_int_value())
2434            .collect();
2435
2436        // if indices = (i, j, k) and shape = (a, b, c) calculate expr_index = (k + j*b + i*b*c)
2437        let mut expr_index = *indices_int.last().unwrap_or(&int_type.const_zero());
2438        let mut stride = 1u64;
2439        if !indices.is_empty() {
2440            for i in (0..indices.len() - 1).rev() {
2441                let iname_i = indices_int[i];
2442                let shapei: u64 = elmt.expr_layout().shape()[i + 1].try_into().unwrap();
2443                stride *= shapei;
2444                let stride_intval = self.context.i32_type().const_int(stride, false);
2445                let stride_mul_i = self.builder.build_int_mul(stride_intval, iname_i, name)?;
2446                expr_index = self.builder.build_int_add(expr_index, stride_mul_i, name)?;
2447            }
2448        }
2449
2450        let float_value =
2451            self.jit_compile_expr(name, elmt.expr(), indices_int.as_slice(), elmt, expr_index)?;
2452
2453        if let Some(contract_sum) = contract_sum {
2454            let contract_sum_value = self
2455                .build_load(self.real_type, contract_sum, "contract_sum")?
2456                .into_float_value();
2457            let new_contract_sum_value = self.builder.build_float_add(
2458                contract_sum_value,
2459                float_value,
2460                "new_contract_sum",
2461            )?;
2462            self.builder
2463                .build_store(contract_sum, new_contract_sum_value)?;
2464        } else {
2465            let expr_index = indices_int.iter().zip(expr_strides.iter()).fold(
2466                self.int_type.const_zero(),
2467                |acc, (i, s)| {
2468                    let tmp = self.builder.build_int_mul(*i, *s, "expr_index").unwrap();
2469                    self.builder.build_int_add(acc, tmp, "acc").unwrap()
2470                },
2471            );
2472            self.jit_compile_broadcast_and_store(name, elmt, float_value, expr_index, translation)?;
2473        }
2474
2475        let mut postblock = self.builder.get_insert_block().unwrap();
2476
2477        // unwind the nested loops
2478        for i in (0..expr_rank).rev() {
2479            // increment index
2480            let next_index = self.builder.build_int_add(indices_int[i], one, name)?;
2481            indices[i].add_incoming(&[(&next_index, postblock)]);
2482            let store_point = (expr_rank - contract_by).saturating_sub(1);
2483            if i == store_point {
2484                if let Some(contract_sum) = contract_sum {
2485                    let contract_sum_value = self
2486                        .build_load(self.real_type, contract_sum, "contract_sum")?
2487                        .into_float_value();
2488                    let contract_strides = contract_strides.as_ref().unwrap();
2489                    let elmt_index = indices_int
2490                        .iter()
2491                        .take(contract_strides.len())
2492                        .zip(contract_strides.iter())
2493                        .fold(self.int_type.const_zero(), |acc, (i, s)| {
2494                            let tmp = self.builder.build_int_mul(*i, *s, "elmt_index").unwrap();
2495                            self.builder.build_int_add(acc, tmp, "acc").unwrap()
2496                        });
2497                    self.jit_compile_store(
2498                        name,
2499                        elmt,
2500                        elmt_index,
2501                        contract_sum_value,
2502                        translation,
2503                    )?;
2504                }
2505            }
2506
2507            let end_index = if i == 0 && self.threaded {
2508                thread_end.unwrap()
2509            } else {
2510                expr_shape[i]
2511            };
2512
2513            // loop condition
2514            let loop_while =
2515                self.builder
2516                    .build_int_compare(IntPredicate::ULT, next_index, end_index, name)?;
2517            let block = self.context.append_basic_block(self.fn_value(), name);
2518            self.builder
2519                .build_conditional_branch(loop_while, blocks[i], block)?;
2520            self.builder.position_at_end(block);
2521            postblock = block;
2522        }
2523
2524        if self.threaded {
2525            self.jit_end_threading(
2526                thread_start.unwrap(),
2527                thread_end.unwrap(),
2528                test_done.unwrap(),
2529                next.unwrap(),
2530            )?;
2531        }
2532        Ok(())
2533    }
2534
2535    fn jit_compile_sparse_contraction_block(
2536        &mut self,
2537        name: &str,
2538        elmt: &TensorBlock,
2539        translation: &Translation,
2540    ) -> Result<()> {
2541        match translation.source {
2542            TranslationFrom::SparseContraction { .. } => {}
2543            _ => {
2544                panic!("expected sparse contraction")
2545            }
2546        }
2547        let int_type = self.int_type;
2548
2549        let translation_index = self
2550            .layout
2551            .get_translation_index(elmt.expr_layout(), elmt.layout())
2552            .unwrap();
2553        let translation_index = translation_index + translation.get_from_index_in_data_layout();
2554
2555        let final_contract_index =
2556            int_type.const_int(elmt.layout().nnz().try_into().unwrap(), false);
2557        let (thread_start, thread_end, test_done, next) = if self.threaded {
2558            let (start, end, test_done, next) = self.jit_threading_limits(final_contract_index)?;
2559            (Some(start), Some(end), Some(test_done), Some(next))
2560        } else {
2561            (None, None, None, None)
2562        };
2563
2564        let preblock = self.builder.get_insert_block().unwrap();
2565        let contract_sum_ptr = self.builder.build_alloca(self.real_type, "contract_sum")?;
2566
2567        // loop through each contraction
2568        let block = self.context.append_basic_block(self.fn_value(), name);
2569        self.builder.build_unconditional_branch(block)?;
2570        self.builder.position_at_end(block);
2571
2572        let contract_index = self.builder.build_phi(int_type, "i")?;
2573        let contract_start = if self.threaded {
2574            thread_start.unwrap()
2575        } else {
2576            int_type.const_zero()
2577        };
2578        contract_index.add_incoming(&[(&contract_start, preblock)]);
2579
2580        let start_index = self.builder.build_int_add(
2581            int_type.const_int(translation_index.try_into().unwrap(), false),
2582            self.builder.build_int_mul(
2583                int_type.const_int(2, false),
2584                contract_index.as_basic_value().into_int_value(),
2585                name,
2586            )?,
2587            name,
2588        )?;
2589        let end_index =
2590            self.builder
2591                .build_int_add(start_index, int_type.const_int(1, false), name)?;
2592        let start_ptr = self.build_gep(
2593            self.int_type,
2594            *self.get_param("indices"),
2595            &[start_index],
2596            "start_index_ptr",
2597        )?;
2598        let start_contract = self
2599            .build_load(self.int_type, start_ptr, "start")?
2600            .into_int_value();
2601        let end_ptr = self.build_gep(
2602            self.int_type,
2603            *self.get_param("indices"),
2604            &[end_index],
2605            "end_index_ptr",
2606        )?;
2607        let end_contract = self
2608            .build_load(self.int_type, end_ptr, "end")?
2609            .into_int_value();
2610
2611        // initialise the contract sum
2612        self.builder
2613            .build_store(contract_sum_ptr, self.real_type.const_float(0.0))?;
2614
2615        // loop through each element in the contraction
2616        let start_contract_block = self
2617            .context
2618            .append_basic_block(self.fn_value(), format!("{name}_contract").as_str());
2619        self.builder
2620            .build_unconditional_branch(start_contract_block)?;
2621        self.builder.position_at_end(start_contract_block);
2622
2623        let expr_index_phi = self.builder.build_phi(int_type, "j")?;
2624        expr_index_phi.add_incoming(&[(&start_contract, block)]);
2625
2626        let expr_index = expr_index_phi.as_basic_value().into_int_value();
2627        let indices_int = self.expr_indices_from_elmt_index(expr_index, elmt, name)?;
2628
2629        // loop body - eval expression and increment sum
2630        let float_value =
2631            self.jit_compile_expr(name, elmt.expr(), indices_int.as_slice(), elmt, expr_index)?;
2632        let contract_sum_value = self
2633            .build_load(self.real_type, contract_sum_ptr, "contract_sum")?
2634            .into_float_value();
2635        let new_contract_sum_value =
2636            self.builder
2637                .build_float_add(contract_sum_value, float_value, "new_contract_sum")?;
2638        self.builder
2639            .build_store(contract_sum_ptr, new_contract_sum_value)?;
2640
2641        let end_contract_block = self.builder.get_insert_block().unwrap();
2642
2643        // increment contract loop index
2644        let next_elmt_index =
2645            self.builder
2646                .build_int_add(expr_index, int_type.const_int(1, false), name)?;
2647        expr_index_phi.add_incoming(&[(&next_elmt_index, end_contract_block)]);
2648
2649        // contract loop condition
2650        let loop_while = self.builder.build_int_compare(
2651            IntPredicate::ULT,
2652            next_elmt_index,
2653            end_contract,
2654            name,
2655        )?;
2656        let post_contract_block = self.context.append_basic_block(self.fn_value(), name);
2657        self.builder.build_conditional_branch(
2658            loop_while,
2659            start_contract_block,
2660            post_contract_block,
2661        )?;
2662        self.builder.position_at_end(post_contract_block);
2663
2664        // store the result
2665        self.jit_compile_store(
2666            name,
2667            elmt,
2668            contract_index.as_basic_value().into_int_value(),
2669            new_contract_sum_value,
2670            translation,
2671        )?;
2672
2673        // increment outer loop index
2674        let next_contract_index = self.builder.build_int_add(
2675            contract_index.as_basic_value().into_int_value(),
2676            int_type.const_int(1, false),
2677            name,
2678        )?;
2679        contract_index.add_incoming(&[(&next_contract_index, post_contract_block)]);
2680
2681        // outer loop condition
2682        let loop_while = self.builder.build_int_compare(
2683            IntPredicate::ULT,
2684            next_contract_index,
2685            thread_end.unwrap_or(final_contract_index),
2686            name,
2687        )?;
2688        let post_block = self.context.append_basic_block(self.fn_value(), name);
2689        self.builder
2690            .build_conditional_branch(loop_while, block, post_block)?;
2691        self.builder.position_at_end(post_block);
2692
2693        if self.threaded {
2694            self.jit_end_threading(
2695                thread_start.unwrap(),
2696                thread_end.unwrap(),
2697                test_done.unwrap(),
2698                next.unwrap(),
2699            )?;
2700        }
2701
2702        Ok(())
2703    }
2704
2705    fn expr_indices_from_elmt_index(
2706        &mut self,
2707        elmt_index: IntValue<'ctx>,
2708        elmt: &TensorBlock,
2709        name: &str,
2710    ) -> Result<Vec<IntValue<'ctx>>, anyhow::Error> {
2711        let layout_index = self.layout.get_layout_index(elmt.expr_layout()).unwrap();
2712        let int_type = self.int_type;
2713        // loop body - load index from layout
2714        let elmt_index_mult_rank = self.builder.build_int_mul(
2715            elmt_index,
2716            int_type.const_int(elmt.expr_layout().rank().try_into().unwrap(), false),
2717            name,
2718        )?;
2719        (0..elmt.expr_layout().rank())
2720            .map(|i| {
2721                let layout_index_plus_offset =
2722                    int_type.const_int((layout_index + i).try_into().unwrap(), false);
2723                let curr_index = self.builder.build_int_add(
2724                    elmt_index_mult_rank,
2725                    layout_index_plus_offset,
2726                    name,
2727                )?;
2728                let ptr = Self::get_ptr_to_index(
2729                    &self.builder,
2730                    self.int_type,
2731                    self.get_param("indices"),
2732                    curr_index,
2733                    name,
2734                );
2735                Ok(self.build_load(self.int_type, ptr, name)?.into_int_value())
2736            })
2737            .collect::<Result<Vec<_>, anyhow::Error>>()
2738    }
2739
2740    // for sparse blocks we can loop through the non-zero elements and extract the index from the layout, then we compile the expression passing in this index
2741    // TODO: havn't implemented contractions yet
2742    fn jit_compile_sparse_block(
2743        &mut self,
2744        name: &str,
2745        elmt: &TensorBlock,
2746        translation: &Translation,
2747    ) -> Result<()> {
2748        let int_type = self.int_type;
2749
2750        let start_index = int_type.const_int(0, false);
2751        let end_index = int_type.const_int(elmt.expr_layout().nnz().try_into().unwrap(), false);
2752
2753        let (thread_start, thread_end, test_done, next) = if self.threaded {
2754            let (start, end, test_done, next) = self.jit_threading_limits(end_index)?;
2755            (Some(start), Some(end), Some(test_done), Some(next))
2756        } else {
2757            (None, None, None, None)
2758        };
2759
2760        // loop through the non-zero elements
2761        let preblock = self.builder.get_insert_block().unwrap();
2762        let loop_block = self.context.append_basic_block(self.fn_value(), name);
2763        self.builder.build_unconditional_branch(loop_block)?;
2764        self.builder.position_at_end(loop_block);
2765
2766        let curr_index = self.builder.build_phi(int_type, "i")?;
2767        curr_index.add_incoming(&[(&thread_start.unwrap_or(start_index), preblock)]);
2768
2769        let elmt_index = curr_index.as_basic_value().into_int_value();
2770        let indices_int = self.expr_indices_from_elmt_index(elmt_index, elmt, name)?;
2771
2772        // loop body - eval expression
2773        let float_value =
2774            self.jit_compile_expr(name, elmt.expr(), indices_int.as_slice(), elmt, elmt_index)?;
2775
2776        self.jit_compile_broadcast_and_store(name, elmt, float_value, elmt_index, translation)?;
2777
2778        // jit_compile_expr or jit_compile_broadcast_and_store may have changed the current block
2779        let end_loop_block = self.builder.get_insert_block().unwrap();
2780
2781        // increment loop index
2782        let one = int_type.const_int(1, false);
2783        let next_index = self.builder.build_int_add(elmt_index, one, name)?;
2784        curr_index.add_incoming(&[(&next_index, end_loop_block)]);
2785
2786        // loop condition
2787        let loop_while = self.builder.build_int_compare(
2788            IntPredicate::ULT,
2789            next_index,
2790            thread_end.unwrap_or(end_index),
2791            name,
2792        )?;
2793        let post_block = self.context.append_basic_block(self.fn_value(), name);
2794        self.builder
2795            .build_conditional_branch(loop_while, loop_block, post_block)?;
2796        self.builder.position_at_end(post_block);
2797
2798        if self.threaded {
2799            self.jit_end_threading(
2800                thread_start.unwrap(),
2801                thread_end.unwrap(),
2802                test_done.unwrap(),
2803                next.unwrap(),
2804            )?;
2805        }
2806
2807        Ok(())
2808    }
2809
2810    // for diagonal blocks we can loop through the diagonal elements and the index is just the same for each element, then we compile the expression passing in this index
2811    fn jit_compile_diagonal_block(
2812        &mut self,
2813        name: &str,
2814        elmt: &TensorBlock,
2815        translation: &Translation,
2816    ) -> Result<()> {
2817        let int_type = self.int_type;
2818
2819        let start_index = int_type.const_int(0, false);
2820        let end_index = int_type.const_int(elmt.expr_layout().nnz().try_into().unwrap(), false);
2821
2822        let (thread_start, thread_end, test_done, next) = if self.threaded {
2823            let (start, end, test_done, next) = self.jit_threading_limits(end_index)?;
2824            (Some(start), Some(end), Some(test_done), Some(next))
2825        } else {
2826            (None, None, None, None)
2827        };
2828
2829        // loop through the non-zero elements
2830        let preblock = self.builder.get_insert_block().unwrap();
2831        let start_loop_block = self.context.append_basic_block(self.fn_value(), name);
2832        self.builder.build_unconditional_branch(start_loop_block)?;
2833        self.builder.position_at_end(start_loop_block);
2834
2835        let curr_index = self.builder.build_phi(int_type, "i")?;
2836        curr_index.add_incoming(&[(&thread_start.unwrap_or(start_index), preblock)]);
2837
2838        // loop body - index is just the same for each element
2839        let elmt_index = curr_index.as_basic_value().into_int_value();
2840        let indices_int: Vec<IntValue> =
2841            (0..elmt.expr_layout().rank()).map(|_| elmt_index).collect();
2842
2843        // loop body - eval expression
2844        let float_value =
2845            self.jit_compile_expr(name, elmt.expr(), indices_int.as_slice(), elmt, elmt_index)?;
2846
2847        // loop body - store result
2848        self.jit_compile_broadcast_and_store(name, elmt, float_value, elmt_index, translation)?;
2849
2850        let end_loop_block = self.builder.get_insert_block().unwrap();
2851
2852        // increment loop index
2853        let one = int_type.const_int(1, false);
2854        let next_index = self.builder.build_int_add(elmt_index, one, name)?;
2855        curr_index.add_incoming(&[(&next_index, end_loop_block)]);
2856
2857        // loop condition
2858        let loop_while = self.builder.build_int_compare(
2859            IntPredicate::ULT,
2860            next_index,
2861            thread_end.unwrap_or(end_index),
2862            name,
2863        )?;
2864        let post_block = self.context.append_basic_block(self.fn_value(), name);
2865        self.builder
2866            .build_conditional_branch(loop_while, start_loop_block, post_block)?;
2867        self.builder.position_at_end(post_block);
2868
2869        if self.threaded {
2870            self.jit_end_threading(
2871                thread_start.unwrap(),
2872                thread_end.unwrap(),
2873                test_done.unwrap(),
2874                next.unwrap(),
2875            )?;
2876        }
2877
2878        Ok(())
2879    }
2880
2881    fn jit_compile_broadcast_and_store(
2882        &mut self,
2883        name: &str,
2884        elmt: &TensorBlock,
2885        float_value: FloatValue<'ctx>,
2886        expr_index: IntValue<'ctx>,
2887        translation: &Translation,
2888    ) -> Result<()> {
2889        let int_type = self.int_type;
2890        let one = int_type.const_int(1, false);
2891        let zero = int_type.const_int(0, false);
2892        let pre_block = self.builder.get_insert_block().unwrap();
2893        match translation.source {
2894            TranslationFrom::Broadcast {
2895                broadcast_by: _,
2896                broadcast_len,
2897            } => {
2898                let bcast_start_index = zero;
2899                let bcast_end_index = int_type.const_int(broadcast_len.try_into().unwrap(), false);
2900
2901                // setup loop block
2902                let bcast_block = self.context.append_basic_block(self.fn_value(), name);
2903                self.builder.build_unconditional_branch(bcast_block)?;
2904                self.builder.position_at_end(bcast_block);
2905                let bcast_index = self.builder.build_phi(int_type, "broadcast_index")?;
2906                bcast_index.add_incoming(&[(&bcast_start_index, pre_block)]);
2907
2908                // store value
2909                let store_index = self.builder.build_int_add(
2910                    self.builder
2911                        .build_int_mul(expr_index, bcast_end_index, "store_index")?,
2912                    bcast_index.as_basic_value().into_int_value(),
2913                    "bcast_store_index",
2914                )?;
2915                self.jit_compile_store(name, elmt, store_index, float_value, translation)?;
2916
2917                // increment index
2918                let bcast_next_index = self.builder.build_int_add(
2919                    bcast_index.as_basic_value().into_int_value(),
2920                    one,
2921                    name,
2922                )?;
2923                bcast_index.add_incoming(&[(&bcast_next_index, bcast_block)]);
2924
2925                // loop condition
2926                let bcast_cond = self.builder.build_int_compare(
2927                    IntPredicate::ULT,
2928                    bcast_next_index,
2929                    bcast_end_index,
2930                    "broadcast_cond",
2931                )?;
2932                let post_bcast_block = self.context.append_basic_block(self.fn_value(), name);
2933                self.builder
2934                    .build_conditional_branch(bcast_cond, bcast_block, post_bcast_block)?;
2935                self.builder.position_at_end(post_bcast_block);
2936                Ok(())
2937            }
2938            TranslationFrom::ElementWise | TranslationFrom::DiagonalContraction { .. } => {
2939                self.jit_compile_store(name, elmt, expr_index, float_value, translation)?;
2940                Ok(())
2941            }
2942            _ => Err(anyhow!("Invalid translation")),
2943        }
2944    }
2945
2946    fn jit_compile_store(
2947        &mut self,
2948        name: &str,
2949        elmt: &TensorBlock,
2950        store_index: IntValue<'ctx>,
2951        float_value: FloatValue<'ctx>,
2952        translation: &Translation,
2953    ) -> Result<()> {
2954        let int_type = self.int_type;
2955        let res_index = match &translation.target {
2956            TranslationTo::Contiguous { start, end: _ } => {
2957                let start_const = int_type.const_int((*start).try_into().unwrap(), false);
2958                self.builder.build_int_add(start_const, store_index, name)?
2959            }
2960            TranslationTo::Sparse { indices: _ } => {
2961                // load store index from layout
2962                let translate_index = self
2963                    .layout
2964                    .get_translation_index(elmt.expr_layout(), elmt.layout())
2965                    .unwrap();
2966                let translate_store_index =
2967                    translate_index + translation.get_to_index_in_data_layout();
2968                let translate_store_index =
2969                    int_type.const_int(translate_store_index.try_into().unwrap(), false);
2970                let elmt_index_strided = store_index;
2971                let curr_index =
2972                    self.builder
2973                        .build_int_add(elmt_index_strided, translate_store_index, name)?;
2974                let ptr = Self::get_ptr_to_index(
2975                    &self.builder,
2976                    self.int_type,
2977                    self.get_param("indices"),
2978                    curr_index,
2979                    name,
2980                );
2981                self.build_load(self.int_type, ptr, name)?.into_int_value()
2982            }
2983        };
2984
2985        let resi_ptr = Self::get_ptr_to_index(
2986            &self.builder,
2987            self.real_type,
2988            &self.tensor_ptr(),
2989            res_index,
2990            name,
2991        );
2992        self.builder.build_store(resi_ptr, float_value)?;
2993        Ok(())
2994    }
2995
2996    fn jit_compile_expr(
2997        &mut self,
2998        name: &str,
2999        expr: &Ast,
3000        index: &[IntValue<'ctx>],
3001        elmt: &TensorBlock,
3002        expr_index: IntValue<'ctx>,
3003    ) -> Result<FloatValue<'ctx>> {
3004        let name = elmt.name().unwrap_or(name);
3005        match &expr.kind {
3006            AstKind::Binop(binop) => {
3007                let lhs =
3008                    self.jit_compile_expr(name, binop.left.as_ref(), index, elmt, expr_index)?;
3009                let rhs =
3010                    self.jit_compile_expr(name, binop.right.as_ref(), index, elmt, expr_index)?;
3011                match binop.op {
3012                    '*' => Ok(self.builder.build_float_mul(lhs, rhs, name)?),
3013                    '/' => Ok(self.builder.build_float_div(lhs, rhs, name)?),
3014                    '-' => Ok(self.builder.build_float_sub(lhs, rhs, name)?),
3015                    '+' => Ok(self.builder.build_float_add(lhs, rhs, name)?),
3016                    unknown => Err(anyhow!("unknown binop op '{}'", unknown)),
3017                }
3018            }
3019            AstKind::Monop(monop) => {
3020                let child =
3021                    self.jit_compile_expr(name, monop.child.as_ref(), index, elmt, expr_index)?;
3022                match monop.op {
3023                    '-' => Ok(self.builder.build_float_neg(child, name)?),
3024                    '+' => Ok(child),
3025                    unknown => Err(anyhow!("unknown monop op '{}'", unknown)),
3026                }
3027            }
3028            AstKind::Call(call) if call.fn_name == "interp1d" => {
3029                self.jit_compile_interp1d(name, call, index, elmt, expr_index, expr.span)
3030            }
3031            AstKind::Call(call) if call.fn_name == "piecewise" => {
3032                self.jit_compile_piecewise(name, call, index, elmt, expr_index)
3033            }
3034            AstKind::Call(call) => match self.get_function(call.fn_name) {
3035                Some(function) => {
3036                    let mut args: Vec<BasicMetadataValueEnum> = Vec::new();
3037                    for arg in call.args.iter() {
3038                        let arg_val =
3039                            self.jit_compile_expr(name, arg.as_ref(), index, elmt, expr_index)?;
3040                        args.push(BasicMetadataValueEnum::FloatValue(arg_val));
3041                    }
3042                    let ret_value = self
3043                        .builder
3044                        .build_call(function, args.as_slice(), name)?
3045                        .try_as_basic_value()
3046                        .unwrap_basic()
3047                        .into_float_value();
3048                    Ok(ret_value)
3049                }
3050                None => Err(anyhow!("unknown function call '{}'", call.fn_name)),
3051            },
3052            AstKind::CallArg(arg) => {
3053                self.jit_compile_expr(name, &arg.expression, index, elmt, expr_index)
3054            }
3055            AstKind::Number(value) => Ok(self.real_type.const_float(*value)),
3056            AstKind::Name(iname) => {
3057                if iname.name == "N" {
3058                    if iname.is_tangent {
3059                        return Ok(self.real_type.const_float(0.0));
3060                    }
3061                    let model_index = self
3062                        .build_load(self.int_type, *self.get_param("model_index"), "model_index")?
3063                        .into_int_value();
3064                    let n_value = self.builder.build_signed_int_to_float(
3065                        model_index,
3066                        self.real_type,
3067                        "n_as_real",
3068                    )?;
3069                    return Ok(n_value);
3070                }
3071                let ptr = *self.get_param(iname.name);
3072                let layout = self.layout.get_layout(iname.name).unwrap().clone();
3073                let iname_elmt_index = if layout.is_dense() {
3074                    // permute indices based on the index chars of this tensor
3075                    let mut no_transform = true;
3076                    let mut iname_index = Vec::new();
3077                    for (i, c) in iname.indices.iter().enumerate() {
3078                        // find the position index of this index char in the tensor's index chars,
3079                        // if it's not found then it must be a contraction index so is at the end
3080                        let pi = elmt
3081                            .indices()
3082                            .iter()
3083                            .position(|x| x == c)
3084                            .unwrap_or(elmt.indices().len());
3085                        // if we are indexing, add the start indice to index[pi]
3086                        if let Some(indice_ast) = iname.indice.as_ref() {
3087                            let Some(indice) = indice_ast.kind.as_indice() else {
3088                                return Err(anyhow!("invalid index expression '{}'", indice_ast));
3089                            };
3090                            let start_intval =
3091                                self.jit_compile_integer_expr(indice.first.as_ref(), name)?;
3092                            let index_pi = if indice.last.is_some() {
3093                                // range indexing: add loop index to start
3094                                let index_pi = if pi >= index.len() {
3095                                    self.context.i32_type().const_int(0, false)
3096                                } else {
3097                                    index[pi]
3098                                };
3099                                self.builder.build_int_add(index_pi, start_intval, name)?
3100                            } else {
3101                                // single-element indexing: constant lookup
3102                                start_intval
3103                            };
3104                            iname_index.push(index_pi);
3105                        } else {
3106                            let index_pi = if pi >= index.len() {
3107                                self.context.i32_type().const_int(0, false)
3108                            } else {
3109                                index[pi]
3110                            };
3111                            iname_index.push(index_pi);
3112                        }
3113                        no_transform = no_transform && pi == i;
3114                    }
3115                    // broadcasting, if the shape is 1 then the index is always 0
3116                    let iname_index: Vec<IntValue> = iname_index
3117                        .iter()
3118                        .enumerate()
3119                        .map(|(i, &idx)| {
3120                            if layout.shape()[i] == 1 {
3121                                self.context.i32_type().const_int(0, false)
3122                            } else {
3123                                idx
3124                            }
3125                        })
3126                        .collect();
3127                    // calculate the element index using iname_index and the shape of the tensor
3128                    // TODO: can we optimise this by using expr_index, and also including elmt_index?
3129                    if !iname_index.is_empty() {
3130                        // if iname_index is (a, b, c) and shape is s calculate iname_elmt_index = ( c * s[2] + b * s[2] * s[1] + a * s[2] * s[1] * s[0] )
3131                        let mut iname_elmt_index = *iname_index.last().unwrap();
3132                        let mut stride = 1u64;
3133                        for i in (0..iname_index.len() - 1).rev() {
3134                            let iname_i = iname_index[i];
3135                            let shapei: u64 = layout.shape()[i + 1].try_into().unwrap();
3136                            stride *= shapei;
3137                            let stride_intval = self.context.i32_type().const_int(stride, false);
3138                            let stride_mul_i =
3139                                self.builder.build_int_mul(stride_intval, iname_i, name)?;
3140                            iname_elmt_index =
3141                                self.builder
3142                                    .build_int_add(iname_elmt_index, stride_mul_i, name)?;
3143                        }
3144                        iname_elmt_index
3145                    } else {
3146                        // zero if we are not indexing, otherwise use the start value of indice
3147                        let zero = self.context.i32_type().const_int(0, false);
3148                        zero
3149                    }
3150                } else if layout.is_sparse() || layout.is_diagonal() {
3151                    let expr_layout = elmt.expr_layout();
3152
3153                    if expr_layout != &layout {
3154                        // get correct index from binary layout map, ie. indices[ binary_layout_index + expr_index ]
3155                        // if its a -1 then return a 0
3156                        // ie. expr_index = binary_layout[expr_index]
3157                        //.    if expr_index == -1 then return 0 as the value of the expression
3158                        //.    otherwise load the value at that index
3159                        // we are doing an if statement so I think we need to return early here
3160                        let permutation =
3161                            DataLayout::permutation(elmt, iname.indices.as_slice(), &layout);
3162                        if let Some(base_binary_layout_index) =
3163                            self.layout
3164                                .get_binary_layout_index(&layout, expr_layout, permutation)
3165                        {
3166                            let binary_layout_index = self.builder.build_int_add(
3167                                self.int_type
3168                                    .const_int(base_binary_layout_index.try_into().unwrap(), false),
3169                                expr_index,
3170                                name,
3171                            )?;
3172
3173                            let indices_ptr = Self::get_ptr_to_index(
3174                                &self.builder,
3175                                self.int_type,
3176                                self.get_param("indices"),
3177                                binary_layout_index,
3178                                name,
3179                            );
3180                            let mapped_index = self
3181                                .build_load(self.int_type, indices_ptr, name)?
3182                                .into_int_value();
3183                            let is_less_than_zero = self.builder.build_int_compare(
3184                                IntPredicate::SLT,
3185                                mapped_index,
3186                                self.int_type.const_int(0, true),
3187                                "sparse_index_check",
3188                            )?;
3189                            let is_less_than_zero_block =
3190                                self.context.append_basic_block(self.fn_value(), "lt_zero");
3191                            let not_less_than_zero_block = self
3192                                .context
3193                                .append_basic_block(self.fn_value(), "not_lt_zero");
3194                            let merge_block =
3195                                self.context.append_basic_block(self.fn_value(), "merge");
3196                            self.builder.build_conditional_branch(
3197                                is_less_than_zero,
3198                                is_less_than_zero_block,
3199                                not_less_than_zero_block,
3200                            )?;
3201
3202                            // if mapped index < 0 return 0
3203                            self.builder.position_at_end(is_less_than_zero_block);
3204                            let zero_value = self.real_type.const_float(0.);
3205                            self.builder.build_unconditional_branch(merge_block)?;
3206
3207                            // if mapped index >=0 load value at that index
3208                            self.builder.position_at_end(not_less_than_zero_block);
3209                            let value_ptr = Self::get_ptr_to_index(
3210                                &self.builder,
3211                                self.real_type,
3212                                &ptr,
3213                                mapped_index,
3214                                name,
3215                            );
3216                            let value = self
3217                                .build_load(self.real_type, value_ptr, name)?
3218                                .into_float_value();
3219                            self.builder.build_unconditional_branch(merge_block)?;
3220
3221                            // return value or 0 from if statement
3222                            self.builder.position_at_end(merge_block);
3223                            let if_return_value =
3224                                self.builder.build_phi(self.real_type, "sparse_value")?;
3225                            if_return_value.add_incoming(&[(&zero_value, is_less_than_zero_block)]);
3226                            if_return_value.add_incoming(&[(&value, not_less_than_zero_block)]);
3227
3228                            let phi_value = if_return_value.as_basic_value().into_float_value();
3229                            return Ok(phi_value);
3230                        } else {
3231                            expr_index
3232                        }
3233                    } else {
3234                        // we can just use the elmt_index since the layouts are the same
3235                        expr_index
3236                    }
3237                } else {
3238                    panic!("unexpected layout");
3239                };
3240                let value_ptr = Self::get_ptr_to_index(
3241                    &self.builder,
3242                    self.real_type,
3243                    &ptr,
3244                    iname_elmt_index,
3245                    name,
3246                );
3247                Ok(self
3248                    .build_load(self.real_type, value_ptr, name)?
3249                    .into_float_value())
3250            }
3251            AstKind::NamedGradient(name) => {
3252                let name_str = name.to_string();
3253                let ptr = self.get_param(name_str.as_str());
3254                Ok(self
3255                    .build_load(self.real_type, *ptr, name_str.as_str())?
3256                    .into_float_value())
3257            }
3258            AstKind::Index(_) => todo!(),
3259            AstKind::Slice(_) => todo!(),
3260            AstKind::Integer(_) => todo!(),
3261            _ => panic!("unexprected astkind"),
3262        }
3263    }
3264
3265    fn jit_compile_interp1d(
3266        &mut self,
3267        name: &str,
3268        call: &crate::ast::Call,
3269        index: &[IntValue<'ctx>],
3270        elmt: &TensorBlock,
3271        expr_index: IntValue<'ctx>,
3272        span: Option<StringSpan>,
3273    ) -> Result<FloatValue<'ctx>> {
3274        let mut hasher = std::collections::hash_map::DefaultHasher::new();
3275        if call.is_tangent {
3276            // tangent has doubled args: take original (x, y, q) at indices 0, 2, 4
3277            std::hash::Hash::hash(&format!("{:?}", call.args[0]), &mut hasher);
3278            std::hash::Hash::hash(&format!("{:?}", call.args[2]), &mut hasher);
3279            std::hash::Hash::hash(&format!("{:?}", call.args[4]), &mut hasher);
3280        } else {
3281            std::hash::Hash::hash(&format!("{:?}", call.args[0]), &mut hasher);
3282            std::hash::Hash::hash(&format!("{:?}", call.args[1]), &mut hasher);
3283            std::hash::Hash::hash(&format!("{:?}", call.args[2]), &mut hasher);
3284        }
3285        let hash_key = std::hash::Hasher::finish(&hasher);
3286        let info = self
3287            .layout
3288            .get_interp1d_info(span.as_ref(), hash_key)
3289            .ok_or_else(|| anyhow!("interp1d: no precomputed data for this call"))?;
3290
3291        let n = info.n;
3292        let x_offset = info.x_offset;
3293        let y_offset = info.y_offset;
3294        let dx_opt = info.dx;
3295
3296        let q = self.jit_compile_expr(name, &call.args[2], index, elmt, expr_index)?;
3297
3298        if let Some(dx) = dx_opt {
3299            // --- uniform path: O(1) per element ---
3300            let constants_slice = self.layout.constants();
3301            let x_first = self.real_type.const_float(constants_slice[x_offset]);
3302            let x_last = self
3303                .real_type
3304                .const_float(constants_slice[x_offset + n - 1]);
3305            let q_lt_last =
3306                self.builder
3307                    .build_float_compare(FloatPredicate::OLT, q, x_last, "q_lt_last")?;
3308            let q = self
3309                .builder
3310                .build_select(q_lt_last, q, x_last, "q_min")?
3311                .into_float_value();
3312            let q_gt_first =
3313                self.builder
3314                    .build_float_compare(FloatPredicate::OGT, q, x_first, "q_gt_first")?;
3315            let q = self
3316                .builder
3317                .build_select(q_gt_first, q, x_first, "q_max")?
3318                .into_float_value();
3319
3320            let dx_const = self.real_type.const_float(dx);
3321            let q_minus_x0 = self.builder.build_float_sub(q, x_first, "dq")?;
3322            let k_float = self.builder.build_float_div(q_minus_x0, dx_const, "kf")?;
3323            let k = self
3324                .builder
3325                .build_float_to_signed_int(k_float, self.int_type, "k")?;
3326
3327            let zero = self.int_type.const_zero();
3328            let nm2 = self.int_type.const_int((n - 2) as u64, false);
3329            let k_lt_0 = self
3330                .builder
3331                .build_int_compare(IntPredicate::SLT, k, zero, "k_lt_0")?;
3332            let k = self
3333                .builder
3334                .build_select(k_lt_0, zero, k, "k_clamp_lo")?
3335                .into_int_value();
3336            let k_gt = self
3337                .builder
3338                .build_int_compare(IntPredicate::SGT, k, nm2, "k_gt")?;
3339            let k = self
3340                .builder
3341                .build_select(k_gt, nm2, k, "k_clamp_hi")?
3342                .into_int_value();
3343
3344            let constants_ptr = if let Some(g) = self.globals.constants {
3345                self.builder.build_pointer_cast(
3346                    g.as_pointer_value(),
3347                    self.real_ptr_type,
3348                    "constants_ptr",
3349                )?
3350            } else {
3351                return Err(anyhow!("constants global not available"));
3352            };
3353
3354            let y_off = self.int_type.const_int(y_offset as u64, false);
3355            let one = self.int_type.const_int(1, false);
3356            let yk_off = self.builder.build_int_add(y_off, k, "yk_off")?;
3357            let yk1_off = self.builder.build_int_add(yk_off, one, "yk1_off")?;
3358
3359            let yk_ptr = self.build_gep(self.real_type, constants_ptr, &[yk_off], "yk_ptr")?;
3360            let yk = self
3361                .build_load(self.real_type, yk_ptr, "yk")?
3362                .into_float_value();
3363            let yk1_ptr = self.build_gep(self.real_type, constants_ptr, &[yk1_off], "yk1_ptr")?;
3364            let yk1 = self
3365                .build_load(self.real_type, yk1_ptr, "yk1")?
3366                .into_float_value();
3367
3368            let k_float2 = self
3369                .builder
3370                .build_signed_int_to_float(k, self.real_type, "kf2")?;
3371            let t = self.builder.build_float_sub(k_float, k_float2, "t")?;
3372            let dy = self.builder.build_float_sub(yk1, yk, "dy")?;
3373            let tdy = self.builder.build_float_mul(t, dy, "tdy")?;
3374            return Ok(self.builder.build_float_add(yk, tdy, "r")?);
3375        }
3376
3377        // --- non-uniform path: call shared interp1d_impl helper ---
3378        let function = self
3379            .get_function("interp1d_impl")
3380            .ok_or_else(|| anyhow!("interp1d_impl not compiled"))?;
3381
3382        let constants_ptr = if let Some(g) = self.globals.constants {
3383            self.builder.build_pointer_cast(
3384                g.as_pointer_value(),
3385                self.real_ptr_type,
3386                "constants_ptr",
3387            )?
3388        } else {
3389            return Err(anyhow!("constants global not available"));
3390        };
3391
3392        let args: Vec<BasicMetadataValueEnum> = vec![
3393            constants_ptr.into(),
3394            self.int_type.const_int(x_offset as u64, false).into(),
3395            self.int_type.const_int(y_offset as u64, false).into(),
3396            self.int_type.const_int(n as u64, false).into(),
3397            q.into(),
3398        ];
3399        let ret = self
3400            .builder
3401            .build_call(function, &args, name)?
3402            .try_as_basic_value()
3403            .unwrap_basic()
3404            .into_float_value();
3405        Ok(ret)
3406    }
3407
3408    fn jit_compile_piecewise(
3409        &mut self,
3410        name: &str,
3411        call: &crate::ast::Call,
3412        index: &[IntValue<'ctx>],
3413        elmt: &TensorBlock,
3414        expr_index: IntValue<'ctx>,
3415    ) -> Result<FloatValue<'ctx>> {
3416        let n = call.args.len();
3417        let num_branches = (n - 1) / 2;
3418
3419        let fallback = self.jit_compile_expr(name, &call.args[n - 1], index, elmt, expr_index)?;
3420
3421        let mut result = fallback;
3422        for i in (0..num_branches).rev() {
3423            let check = self.jit_compile_expr(name, &call.args[2 * i], index, elmt, expr_index)?;
3424            let value =
3425                self.jit_compile_expr(name, &call.args[2 * i + 1], index, elmt, expr_index)?;
3426            let zero = self.real_type.const_float(0.0);
3427            let cond = self
3428                .builder
3429                .build_float_compare(FloatPredicate::OGE, check, zero, name)?;
3430            result = self
3431                .builder
3432                .build_select(cond, value, result, name)?
3433                .into_float_value();
3434        }
3435        Ok(result)
3436    }
3437
3438    fn jit_compile_integer_expr(&mut self, expr: &Ast, name: &str) -> Result<IntValue<'ctx>> {
3439        match &expr.kind {
3440            AstKind::Integer(value) => Ok(self.int_type.const_int(*value as u64, true)),
3441            AstKind::Number(value) => {
3442                if value.fract() != 0.0 {
3443                    return Err(anyhow!(
3444                        "non-integer value '{}' in integer expression",
3445                        value
3446                    ));
3447                }
3448                Ok(self.int_type.const_int(*value as u64, true))
3449            }
3450            AstKind::Name(iname) => {
3451                if iname.name == "N" {
3452                    Ok(self
3453                        .build_load(self.int_type, *self.get_param("model_index"), name)?
3454                        .into_int_value())
3455                } else {
3456                    Err(anyhow!(
3457                        "unsupported name '{}' in integer expression",
3458                        iname.name
3459                    ))
3460                }
3461            }
3462            AstKind::Monop(monop) => {
3463                let child = self.jit_compile_integer_expr(monop.child.as_ref(), name)?;
3464                match monop.op {
3465                    '+' => Ok(child),
3466                    '-' => self.builder.build_int_neg(child, name).map_err(Into::into),
3467                    _ => Err(anyhow!("unknown integer unary op '{}'", monop.op)),
3468                }
3469            }
3470            AstKind::Binop(binop) => {
3471                let lhs = self.jit_compile_integer_expr(binop.left.as_ref(), name)?;
3472                let rhs = self.jit_compile_integer_expr(binop.right.as_ref(), name)?;
3473                match binop.op {
3474                    '+' => self
3475                        .builder
3476                        .build_int_add(lhs, rhs, name)
3477                        .map_err(Into::into),
3478                    '-' => self
3479                        .builder
3480                        .build_int_sub(lhs, rhs, name)
3481                        .map_err(Into::into),
3482                    '*' => self
3483                        .builder
3484                        .build_int_mul(lhs, rhs, name)
3485                        .map_err(Into::into),
3486                    '/' => self
3487                        .builder
3488                        .build_int_signed_div(lhs, rhs, name)
3489                        .map_err(Into::into),
3490                    '%' => self
3491                        .builder
3492                        .build_int_signed_rem(lhs, rhs, name)
3493                        .map_err(Into::into),
3494                    _ => Err(anyhow!("unknown integer binary op '{}'", binop.op)),
3495                }
3496            }
3497            _ => Err(anyhow!("unsupported integer expression '{}'", expr)),
3498        }
3499    }
3500
3501    fn clear(&mut self) {
3502        self.variables.clear();
3503        //self.functions.clear();
3504        self.fn_value_opt = None;
3505        self.tensor_ptr_opt = None;
3506    }
3507
3508    fn build_dep_call(
3509        &mut self,
3510        dep_fn: FunctionValue<'ctx>,
3511        call_name: &str,
3512        barrier_start: u64,
3513        total_barriers: u64,
3514    ) -> Result<()> {
3515        let t = self
3516            .build_load(self.real_type, *self.get_param("t"), "t")?
3517            .into_float_value();
3518        let u = *self.get_param("u");
3519        let data = *self.get_param("data");
3520        let thread_id = self
3521            .build_load(self.int_type, *self.get_param("thread_id"), "thread_id")?
3522            .into_int_value();
3523        let thread_dim = self
3524            .build_load(self.int_type, *self.get_param("thread_dim"), "thread_dim")?
3525            .into_int_value();
3526        let barrier_start = self.int_type.const_int(barrier_start, false);
3527        let total_barriers = self.int_type.const_int(total_barriers, false);
3528        self.builder.build_call(
3529            dep_fn,
3530            &[
3531                t.into(),
3532                u.into(),
3533                data.into(),
3534                thread_id.into(),
3535                thread_dim.into(),
3536                barrier_start.into(),
3537                total_barriers.into(),
3538            ],
3539            call_name,
3540        )?;
3541        Ok(())
3542    }
3543
3544    fn ensure_time_dep_fn<'m>(
3545        &mut self,
3546        model: &'m DiscreteModel,
3547        code: Option<&str>,
3548    ) -> Result<FunctionValue<'ctx>> {
3549        if let Some(function) = self.module.get_function("calc_time_dep") {
3550            return Ok(function);
3551        }
3552        self.compile_dep_defns(model, "calc_time_dep", model.time_dep_defns(), code)
3553    }
3554
3555    fn ensure_state_dep_fn<'m>(
3556        &mut self,
3557        model: &'m DiscreteModel,
3558        code: Option<&str>,
3559    ) -> Result<FunctionValue<'ctx>> {
3560        if let Some(function) = self.module.get_function("calc_state_dep") {
3561            return Ok(function);
3562        }
3563        self.compile_dep_defns(model, "calc_state_dep", model.state_dep_defns(), code)
3564    }
3565
3566    fn ensure_state_dep_post_f_fn<'m>(
3567        &mut self,
3568        model: &'m DiscreteModel,
3569        code: Option<&str>,
3570    ) -> Result<FunctionValue<'ctx>> {
3571        if let Some(function) = self.module.get_function("calc_state_dep_post_f") {
3572            return Ok(function);
3573        }
3574        self.compile_dep_defns(
3575            model,
3576            "calc_state_dep_post_f",
3577            model.state_dep_post_f_defns(),
3578            code,
3579        )
3580    }
3581
3582    fn function_arg_alloca(&mut self, name: &str, arg: BasicValueEnum<'ctx>) -> PointerValue<'ctx> {
3583        match arg {
3584            BasicValueEnum::PointerValue(v) => v,
3585            BasicValueEnum::FloatValue(v) => {
3586                let alloca = self
3587                    .create_entry_block_builder()
3588                    .build_alloca(arg.get_type(), name)
3589                    .unwrap();
3590                self.builder.build_store(alloca, v).unwrap();
3591                alloca
3592            }
3593            BasicValueEnum::IntValue(v) => {
3594                let alloca = self
3595                    .create_entry_block_builder()
3596                    .build_alloca(arg.get_type(), name)
3597                    .unwrap();
3598                self.builder.build_store(alloca, v).unwrap();
3599                alloca
3600            }
3601            _ => unreachable!(),
3602        }
3603    }
3604
3605    pub fn compile_set_u0<'m>(
3606        &mut self,
3607        model: &'m DiscreteModel,
3608        code: Option<&str>,
3609    ) -> Result<FunctionValue<'ctx>> {
3610        self.clear();
3611        let fn_arg_names = &["u0", "data", "thread_id", "thread_dim"];
3612        let function = self.add_function(
3613            "set_u0",
3614            fn_arg_names,
3615            &[
3616                self.real_ptr_type.into(),
3617                self.real_ptr_type.into(),
3618                self.int_type.into(),
3619                self.int_type.into(),
3620            ],
3621            None,
3622            false,
3623        );
3624
3625        // add noalias
3626        let alias_id = Attribute::get_named_enum_kind_id("noalias");
3627        let noalign = self.context.create_enum_attribute(alias_id, 0);
3628        for i in &[0, 1] {
3629            function.add_attribute(AttributeLoc::Param(*i), noalign);
3630        }
3631
3632        let _basic_block = self.start_function(function, code);
3633
3634        for (i, arg) in function.get_param_iter().enumerate() {
3635            let name = fn_arg_names[i];
3636            let alloca = self.function_arg_alloca(name, arg);
3637            self.insert_param(name, alloca);
3638        }
3639
3640        self.insert_data(model);
3641        self.insert_indices();
3642
3643        let mut nbarriers = 0;
3644        let total_barriers = (model.input_dep_defns().len() + 1) as u64;
3645        let total_barriers_val = self.int_type.const_int(total_barriers, false);
3646        #[allow(clippy::explicit_counter_loop)]
3647        for a in model.input_dep_defns() {
3648            self.jit_compile_tensor(a, Some(*self.get_var(a)), code)?;
3649            let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3650            self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3651            nbarriers += 1;
3652        }
3653
3654        self.jit_compile_tensor(model.state(), Some(*self.get_param("u0")), code)?;
3655        let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3656        self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3657
3658        self.builder.build_return(None)?;
3659
3660        if function.verify(true) {
3661            Ok(function)
3662        } else {
3663            function.print_to_stderr();
3664            unsafe {
3665                function.delete();
3666            }
3667            Err(anyhow!("Invalid generated function."))
3668        }
3669    }
3670
3671    pub fn compile_calc_out<'m>(
3672        &mut self,
3673        model: &'m DiscreteModel,
3674        include_constants: bool,
3675        code: Option<&str>,
3676    ) -> Result<FunctionValue<'ctx>> {
3677        let time_dep_fn = self.ensure_time_dep_fn(model, code)?;
3678        let state_dep_fn = self.ensure_state_dep_fn(model, code)?;
3679        let state_dep_post_f_fn = self.ensure_state_dep_post_f_fn(model, code)?;
3680        self.clear();
3681        let fn_arg_names = &["t", "u", "data", "out", "thread_id", "thread_dim"];
3682        let function_name = if include_constants {
3683            "calc_out_full"
3684        } else {
3685            "calc_out"
3686        };
3687        let function = self.add_function(
3688            function_name,
3689            fn_arg_names,
3690            &[
3691                self.real_type.into(),
3692                self.real_ptr_type.into(),
3693                self.real_ptr_type.into(),
3694                self.real_ptr_type.into(),
3695                self.int_type.into(),
3696                self.int_type.into(),
3697            ],
3698            None,
3699            false,
3700        );
3701
3702        // add noalias
3703        let alias_id = Attribute::get_named_enum_kind_id("noalias");
3704        let noalign = self.context.create_enum_attribute(alias_id, 0);
3705        for i in &[1, 2, 3] {
3706            function.add_attribute(AttributeLoc::Param(*i), noalign);
3707        }
3708
3709        let _basic_block = self.start_function(function, code);
3710
3711        for (i, arg) in function.get_param_iter().enumerate() {
3712            let name = fn_arg_names[i];
3713            let alloca = self.function_arg_alloca(name, arg);
3714            self.insert_param(name, alloca);
3715        }
3716
3717        self.insert_state(model.state());
3718        self.insert_data(model);
3719        self.insert_indices();
3720
3721        // print thread_id and thread_dim
3722        //let thread_id = function.get_nth_param(3).unwrap();
3723        //let thread_dim = function.get_nth_param(4).unwrap();
3724        //self.compile_print_value("thread_id", PrintValue::Int(thread_id.into_int_value()))?;
3725        //self.compile_print_value("thread_dim", PrintValue::Int(thread_dim.into_int_value()))?;
3726        if let Some(out) = model.out() {
3727            let mut nbarriers = 0;
3728            let mut total_barriers = (model.time_dep_defns().len()
3729                + model.state_dep_defns().len()
3730                + model.state_dep_post_f_defns().len()
3731                + 1) as u64;
3732            if include_constants {
3733                total_barriers += model.input_dep_defns().len() as u64;
3734            }
3735            let total_barriers_val = self.int_type.const_int(total_barriers, false);
3736            if include_constants {
3737                // calculate time independant definitions
3738                for tensor in model.input_dep_defns() {
3739                    self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
3740                    let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3741                    self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3742                    nbarriers += 1;
3743                }
3744            }
3745
3746            // calculate time dependant definitions
3747            if !model.time_dep_defns().is_empty() {
3748                self.build_dep_call(time_dep_fn, "time_dep", nbarriers, total_barriers)?;
3749                nbarriers += model.time_dep_defns().len() as u64;
3750            }
3751
3752            // calculate state dependant definitions
3753            if !model.state_dep_defns().is_empty() {
3754                self.build_dep_call(state_dep_fn, "state_dep", nbarriers, total_barriers)?;
3755                nbarriers += model.state_dep_defns().len() as u64;
3756            }
3757            if !model.state_dep_post_f_defns().is_empty() {
3758                self.build_dep_call(state_dep_post_f_fn, "state_dep", nbarriers, total_barriers)?;
3759                nbarriers += model.state_dep_post_f_defns().len() as u64;
3760            }
3761
3762            self.jit_compile_tensor(out, Some(*self.get_var(model.out().unwrap())), code)?;
3763            let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3764            self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3765        }
3766        self.builder.build_return(None)?;
3767
3768        if function.verify(true) {
3769            Ok(function)
3770        } else {
3771            function.print_to_stderr();
3772            unsafe {
3773                function.delete();
3774            }
3775            Err(anyhow!("Invalid generated function."))
3776        }
3777    }
3778
3779    fn compile_dep_defns<'m>(
3780        &mut self,
3781        model: &'m DiscreteModel,
3782        fn_name: &str,
3783        tensors: &[Tensor<'m>],
3784        code: Option<&str>,
3785    ) -> Result<FunctionValue<'ctx>> {
3786        self.clear();
3787        let fn_arg_names = &[
3788            "t",
3789            "u",
3790            "data",
3791            "thread_id",
3792            "thread_dim",
3793            "barrier_start",
3794            "total_barriers",
3795        ];
3796        let function = self.add_function(
3797            fn_name,
3798            fn_arg_names,
3799            &[
3800                self.real_type.into(),
3801                self.real_ptr_type.into(),
3802                self.real_ptr_type.into(),
3803                self.int_type.into(),
3804                self.int_type.into(),
3805                self.int_type.into(),
3806                self.int_type.into(),
3807            ],
3808            None,
3809            false,
3810        );
3811
3812        // add noalias
3813        let alias_id = Attribute::get_named_enum_kind_id("noalias");
3814        let noalign = self.context.create_enum_attribute(alias_id, 0);
3815        for i in &[1, 2] {
3816            function.add_attribute(AttributeLoc::Param(*i), noalign);
3817        }
3818
3819        let _basic_block = self.start_function(function, code);
3820
3821        for (i, arg) in function.get_param_iter().enumerate() {
3822            let name = fn_arg_names[i];
3823            let alloca = self.function_arg_alloca(name, arg);
3824            self.insert_param(name, alloca);
3825        }
3826
3827        self.insert_state(model.state());
3828        self.insert_data(model);
3829        self.insert_indices();
3830
3831        let barrier_start = self
3832            .build_load(
3833                self.int_type,
3834                *self.get_param("barrier_start"),
3835                "barrier_start",
3836            )?
3837            .into_int_value();
3838        let total_barriers = self
3839            .build_load(
3840                self.int_type,
3841                *self.get_param("total_barriers"),
3842                "total_barriers",
3843            )?
3844            .into_int_value();
3845        let one = self.int_type.const_int(1, false);
3846
3847        for (index, tensor) in tensors.iter().enumerate() {
3848            self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
3849            let index = self.int_type.const_int(index as u64, false);
3850            let barrier_num_base =
3851                self.builder
3852                    .build_int_add(barrier_start, index, "barrier_num_base")?;
3853            let barrier_num = self
3854                .builder
3855                .build_int_add(barrier_num_base, one, "barrier_num")?;
3856            self.jit_compile_call_barrier(barrier_num, total_barriers);
3857        }
3858
3859        self.builder.build_return(None)?;
3860
3861        if function.verify(true) {
3862            Ok(function)
3863        } else {
3864            function.print_to_stderr();
3865            unsafe {
3866                function.delete();
3867            }
3868            Err(anyhow!("Invalid generated function."))
3869        }
3870    }
3871
3872    pub fn compile_calc_stop<'m>(
3873        &mut self,
3874        model: &'m DiscreteModel,
3875        include_constants: bool,
3876        code: Option<&str>,
3877    ) -> Result<FunctionValue<'ctx>> {
3878        let time_dep_fn = self.ensure_time_dep_fn(model, code)?;
3879        let state_dep_fn = self.ensure_state_dep_fn(model, code)?;
3880        let state_dep_post_f_fn = self.ensure_state_dep_post_f_fn(model, code)?;
3881        self.clear();
3882        let fn_arg_names = &["t", "u", "data", "root", "thread_id", "thread_dim"];
3883        let function_name = if include_constants {
3884            "calc_stop_full"
3885        } else {
3886            "calc_stop"
3887        };
3888        let function = self.add_function(
3889            function_name,
3890            fn_arg_names,
3891            &[
3892                self.real_type.into(),
3893                self.real_ptr_type.into(),
3894                self.real_ptr_type.into(),
3895                self.real_ptr_type.into(),
3896                self.int_type.into(),
3897                self.int_type.into(),
3898            ],
3899            None,
3900            false,
3901        );
3902
3903        // add noalias
3904        let alias_id = Attribute::get_named_enum_kind_id("noalias");
3905        let noalign = self.context.create_enum_attribute(alias_id, 0);
3906        for i in &[1, 2, 3] {
3907            function.add_attribute(AttributeLoc::Param(*i), noalign);
3908        }
3909
3910        let _basic_block = self.start_function(function, code);
3911
3912        for (i, arg) in function.get_param_iter().enumerate() {
3913            let name = fn_arg_names[i];
3914            let alloca = self.function_arg_alloca(name, arg);
3915            self.insert_param(name, alloca);
3916        }
3917
3918        self.insert_state(model.state());
3919        self.insert_data(model);
3920        self.insert_indices();
3921
3922        if let Some(stop) = model.stop() {
3923            // calculate time dependant definitions
3924            let mut nbarriers = 0;
3925            let mut total_barriers = (model.time_dep_defns().len()
3926                + model.state_dep_defns().len()
3927                + model.state_dep_post_f_defns().len()
3928                + 1) as u64;
3929            if include_constants {
3930                total_barriers += model.input_dep_defns().len() as u64;
3931            }
3932            let total_barriers_val = self.int_type.const_int(total_barriers, false);
3933            if include_constants {
3934                // calculate time independent definitions
3935                for tensor in model.input_dep_defns() {
3936                    self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
3937                    let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3938                    self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3939                    nbarriers += 1;
3940                }
3941            }
3942            if !model.time_dep_defns().is_empty() {
3943                self.build_dep_call(time_dep_fn, "time_dep", nbarriers, total_barriers)?;
3944                nbarriers += model.time_dep_defns().len() as u64;
3945            }
3946
3947            // calculate state dependant definitions
3948            if !model.state_dep_defns().is_empty() {
3949                self.build_dep_call(state_dep_fn, "state_dep", nbarriers, total_barriers)?;
3950                nbarriers += model.state_dep_defns().len() as u64;
3951            }
3952            if !model.state_dep_post_f_defns().is_empty() {
3953                self.build_dep_call(state_dep_post_f_fn, "state_dep", nbarriers, total_barriers)?;
3954                nbarriers += model.state_dep_post_f_defns().len() as u64;
3955            }
3956
3957            let res_ptr = self.get_param("root");
3958            self.jit_compile_tensor(stop, Some(*res_ptr), code)?;
3959            let barrier_num = self.int_type.const_int(nbarriers + 1, false);
3960            self.jit_compile_call_barrier(barrier_num, total_barriers_val);
3961        }
3962        self.builder.build_return(None)?;
3963
3964        if function.verify(true) {
3965            Ok(function)
3966        } else {
3967            function.print_to_stderr();
3968            unsafe {
3969                function.delete();
3970            }
3971            Err(anyhow!("Invalid generated function."))
3972        }
3973    }
3974
3975    pub fn compile_reset<'m>(
3976        &mut self,
3977        model: &'m DiscreteModel,
3978        include_constants: bool,
3979        code: Option<&str>,
3980    ) -> Result<FunctionValue<'ctx>> {
3981        let time_dep_fn = self.ensure_time_dep_fn(model, code)?;
3982        let state_dep_fn = self.ensure_state_dep_fn(model, code)?;
3983        let state_dep_post_f_fn = self.ensure_state_dep_post_f_fn(model, code)?;
3984        self.clear();
3985        let fn_arg_names = &["t", "u", "data", "reset", "thread_id", "thread_dim"];
3986        let function_name = if include_constants {
3987            "reset_full"
3988        } else {
3989            "reset"
3990        };
3991        let function = self.add_function(
3992            function_name,
3993            fn_arg_names,
3994            &[
3995                self.real_type.into(),
3996                self.real_ptr_type.into(),
3997                self.real_ptr_type.into(),
3998                self.real_ptr_type.into(),
3999                self.int_type.into(),
4000                self.int_type.into(),
4001            ],
4002            None,
4003            false,
4004        );
4005
4006        let alias_id = Attribute::get_named_enum_kind_id("noalias");
4007        let noalign = self.context.create_enum_attribute(alias_id, 0);
4008        for i in &[1, 2, 3] {
4009            function.add_attribute(AttributeLoc::Param(*i), noalign);
4010        }
4011
4012        let _basic_block = self.start_function(function, code);
4013
4014        for (i, arg) in function.get_param_iter().enumerate() {
4015            let name = fn_arg_names[i];
4016            let alloca = self.function_arg_alloca(name, arg);
4017            self.insert_param(name, alloca);
4018        }
4019
4020        self.insert_state(model.state());
4021        self.insert_data(model);
4022        self.insert_indices();
4023
4024        if let Some(reset) = model.reset() {
4025            let mut nbarriers = 0;
4026            let mut total_barriers = (model.time_dep_defns().len()
4027                + model.state_dep_defns().len()
4028                + model.state_dep_post_f_defns().len()
4029                + 1) as u64;
4030            if include_constants {
4031                total_barriers += model.input_dep_defns().len() as u64;
4032            }
4033            let total_barriers_val = self.int_type.const_int(total_barriers, false);
4034            if include_constants {
4035                // calculate time independent definitions
4036                for tensor in model.input_dep_defns() {
4037                    self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
4038                    let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4039                    self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4040                    nbarriers += 1;
4041                }
4042            }
4043            if !model.time_dep_defns().is_empty() {
4044                self.build_dep_call(time_dep_fn, "time_dep", nbarriers, total_barriers)?;
4045                nbarriers += model.time_dep_defns().len() as u64;
4046            }
4047
4048            if !model.state_dep_defns().is_empty() {
4049                self.build_dep_call(state_dep_fn, "state_dep", nbarriers, total_barriers)?;
4050                nbarriers += model.state_dep_defns().len() as u64;
4051            }
4052            if !model.state_dep_post_f_defns().is_empty() {
4053                self.build_dep_call(state_dep_post_f_fn, "state_dep", nbarriers, total_barriers)?;
4054                nbarriers += model.state_dep_post_f_defns().len() as u64;
4055            }
4056
4057            let res_ptr = self.get_param("reset");
4058            self.jit_compile_tensor(reset, Some(*res_ptr), code)?;
4059            let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4060            self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4061        }
4062        self.builder.build_return(None)?;
4063
4064        if function.verify(true) {
4065            Ok(function)
4066        } else {
4067            function.print_to_stderr();
4068            unsafe {
4069                function.delete();
4070            }
4071            Err(anyhow!("Invalid generated function."))
4072        }
4073    }
4074
4075    pub fn compile_rhs<'m>(
4076        &mut self,
4077        model: &'m DiscreteModel,
4078        include_constants: bool,
4079        code: Option<&str>,
4080    ) -> Result<FunctionValue<'ctx>> {
4081        let time_dep_fn = self.ensure_time_dep_fn(model, code)?;
4082        let state_dep_fn = self.ensure_state_dep_fn(model, code)?;
4083        self.clear();
4084        let fn_arg_names = &["t", "u", "data", "rr", "thread_id", "thread_dim"];
4085        let function_name = if include_constants { "rhs_full" } else { "rhs" };
4086        let function = self.add_function(
4087            function_name,
4088            fn_arg_names,
4089            &[
4090                self.real_type.into(),
4091                self.real_ptr_type.into(),
4092                self.real_ptr_type.into(),
4093                self.real_ptr_type.into(),
4094                self.int_type.into(),
4095                self.int_type.into(),
4096            ],
4097            None,
4098            false,
4099        );
4100
4101        // add noalias
4102        let alias_id = Attribute::get_named_enum_kind_id("noalias");
4103        let noalign = self.context.create_enum_attribute(alias_id, 0);
4104        for i in &[1, 2, 3] {
4105            function.add_attribute(AttributeLoc::Param(*i), noalign);
4106        }
4107
4108        let _basic_block = self.start_function(function, code);
4109
4110        for (i, arg) in function.get_param_iter().enumerate() {
4111            let name = fn_arg_names[i];
4112            let alloca = self.function_arg_alloca(name, arg);
4113            self.insert_param(name, alloca);
4114        }
4115
4116        self.insert_state(model.state());
4117        self.insert_data(model);
4118        self.insert_indices();
4119
4120        let mut nbarriers = 0;
4121        let mut total_barriers =
4122            (model.time_dep_defns().len() + model.state_dep_defns().len() + 1) as u64;
4123        if include_constants {
4124            total_barriers += model.input_dep_defns().len() as u64;
4125            // calculate constant definitions
4126            for tensor in model.input_dep_defns() {
4127                self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
4128                let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4129                let total_barriers_val = self.int_type.const_int(total_barriers, false);
4130                self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4131                nbarriers += 1;
4132            }
4133        }
4134
4135        // calculate time dependant definitions
4136        if !model.time_dep_defns().is_empty() {
4137            self.build_dep_call(time_dep_fn, "time_dep", nbarriers, total_barriers)?;
4138            nbarriers += model.time_dep_defns().len() as u64;
4139        }
4140
4141        if !model.state_dep_defns().is_empty() {
4142            self.build_dep_call(state_dep_fn, "state_dep", nbarriers, total_barriers)?;
4143            nbarriers += model.state_dep_defns().len() as u64;
4144        }
4145
4146        // F
4147        let res_ptr = self.get_param("rr");
4148        self.jit_compile_tensor(model.rhs(), Some(*res_ptr), code)?;
4149        let total_barriers_val = self.int_type.const_int(total_barriers, false);
4150        let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4151        self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4152
4153        self.builder.build_return(None)?;
4154
4155        if function.verify(true) {
4156            Ok(function)
4157        } else {
4158            function.print_to_stderr();
4159            unsafe {
4160                function.delete();
4161            }
4162            Err(anyhow!("Invalid generated function."))
4163        }
4164    }
4165
4166    pub fn compile_mass<'m>(
4167        &mut self,
4168        model: &'m DiscreteModel,
4169        code: Option<&str>,
4170    ) -> Result<FunctionValue<'ctx>> {
4171        self.clear();
4172        let fn_arg_names = &["t", "dudt", "data", "rr", "thread_id", "thread_dim"];
4173        let function = self.add_function(
4174            "mass",
4175            fn_arg_names,
4176            &[
4177                self.real_type.into(),
4178                self.real_ptr_type.into(),
4179                self.real_ptr_type.into(),
4180                self.real_ptr_type.into(),
4181                self.int_type.into(),
4182                self.int_type.into(),
4183            ],
4184            None,
4185            false,
4186        );
4187
4188        // add noalias
4189        let alias_id = Attribute::get_named_enum_kind_id("noalias");
4190        let noalign = self.context.create_enum_attribute(alias_id, 0);
4191        for i in &[1, 2, 3] {
4192            function.add_attribute(AttributeLoc::Param(*i), noalign);
4193        }
4194
4195        let _basic_block = self.start_function(function, code);
4196
4197        for (i, arg) in function.get_param_iter().enumerate() {
4198            let name = fn_arg_names[i];
4199            let alloca = self.function_arg_alloca(name, arg);
4200            self.insert_param(name, alloca);
4201        }
4202
4203        // only put code in this function if we have a state_dot and lhs
4204        if model.state_dot().is_some() && model.lhs().is_some() {
4205            let state_dot = model.state_dot().unwrap();
4206            let lhs = model.lhs().unwrap();
4207
4208            self.insert_dot_state(state_dot);
4209            self.insert_data(model);
4210            self.insert_indices();
4211
4212            // calculate time dependant definitions
4213            let mut nbarriers = 0;
4214            let total_barriers =
4215                (model.time_dep_defns().len() + model.dstate_dep_defns().len() + 1) as u64;
4216            let total_barriers_val = self.int_type.const_int(total_barriers, false);
4217            for tensor in model.time_dep_defns() {
4218                self.jit_compile_tensor(tensor, Some(*self.get_var(tensor)), code)?;
4219                let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4220                self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4221                nbarriers += 1;
4222            }
4223
4224            for a in model.dstate_dep_defns() {
4225                self.jit_compile_tensor(a, Some(*self.get_var(a)), code)?;
4226                let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4227                self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4228                nbarriers += 1;
4229            }
4230
4231            // mass
4232            let res_ptr = self.get_param("rr");
4233            self.jit_compile_tensor(lhs, Some(*res_ptr), code)?;
4234            let barrier_num = self.int_type.const_int(nbarriers + 1, false);
4235            self.jit_compile_call_barrier(barrier_num, total_barriers_val);
4236        }
4237
4238        self.builder.build_return(None)?;
4239
4240        if function.verify(true) {
4241            Ok(function)
4242        } else {
4243            function.print_to_stderr();
4244            unsafe {
4245                function.delete();
4246            }
4247            Err(anyhow!("Invalid generated function."))
4248        }
4249    }
4250
4251    pub fn compile_gradient(
4252        &mut self,
4253        original_function: FunctionValue<'ctx>,
4254        args_type: &[CompileGradientArgType],
4255        mode: CompileMode,
4256        fn_name: &str,
4257    ) -> Result<FunctionValue<'ctx>> {
4258        self.clear();
4259
4260        // construct the gradient function
4261        let mut fn_type: Vec<BasicMetadataTypeEnum> = Vec::new();
4262
4263        let orig_fn_type_ptr = Self::fn_pointer_type(self.context, original_function.get_type());
4264
4265        let mut enzyme_fn_type: Vec<BasicMetadataTypeEnum> = vec![orig_fn_type_ptr.into()];
4266        let mut start_param_index: Vec<u32> = Vec::new();
4267        let mut ptr_arg_indices: Vec<u32> = Vec::new();
4268        for (i, arg) in original_function.get_param_iter().enumerate() {
4269            let start_index = u32::try_from(fn_type.len()).unwrap();
4270            start_param_index.push(start_index);
4271            let arg_type = arg.get_type();
4272            fn_type.push(arg_type.into());
4273
4274            // constant args with type T in the original funciton have 2 args of type [int, T]
4275            enzyme_fn_type.push(self.int_type.into());
4276            enzyme_fn_type.push(arg.get_type().into());
4277
4278            if arg_type.is_pointer_type() {
4279                ptr_arg_indices.push(start_index);
4280            }
4281
4282            match args_type[i] {
4283                CompileGradientArgType::Dup | CompileGradientArgType::DupNoNeed => {
4284                    fn_type.push(arg.get_type().into());
4285                    enzyme_fn_type.push(arg.get_type().into());
4286                    if arg_type.is_pointer_type() {
4287                        ptr_arg_indices.push(start_index + 1);
4288                    }
4289                }
4290                CompileGradientArgType::Const => {}
4291            }
4292        }
4293        let fn_arg_names = fn_type
4294            .iter()
4295            .enumerate()
4296            .map(|(i, _)| format!("arg{}", i))
4297            .collect::<Vec<String>>();
4298        let fn_arg_names_ref = fn_arg_names
4299            .iter()
4300            .map(|s| s.as_str())
4301            .collect::<Vec<&str>>();
4302        let function = self.add_function(fn_name, &fn_arg_names_ref, &fn_type, None, false);
4303
4304        // add noalias
4305        let alias_id = Attribute::get_named_enum_kind_id("noalias");
4306        let noalign = self.context.create_enum_attribute(alias_id, 0);
4307        for i in ptr_arg_indices {
4308            function.add_attribute(AttributeLoc::Param(i), noalign);
4309        }
4310
4311        let _basic_block = self.start_function(function, None);
4312
4313        let mut enzyme_fn_args: Vec<BasicMetadataValueEnum> = Vec::new();
4314        let mut input_activity = Vec::new();
4315        let mut arg_trees = Vec::new();
4316        for (i, arg) in original_function.get_param_iter().enumerate() {
4317            let param_index = start_param_index[i];
4318            let fn_arg = function.get_nth_param(param_index).unwrap();
4319
4320            // we'll probably only get double or pointers to doubles, so let assume this for now
4321            // todo: perhaps refactor this into a recursive function, might be overkill
4322            let concrete_type = match arg.get_type() {
4323                BasicTypeEnum::PointerType(_t) => CConcreteType_DT_Pointer,
4324                BasicTypeEnum::FloatType(_t) => match self.diffsl_real_type {
4325                    RealType::F32 => CConcreteType_DT_Float,
4326                    RealType::F64 => CConcreteType_DT_Double,
4327                },
4328                BasicTypeEnum::IntType(_) => CConcreteType_DT_Integer,
4329                _ => panic!("unsupported type"),
4330            };
4331            let new_tree = unsafe {
4332                EnzymeNewTypeTreeCT(
4333                    concrete_type,
4334                    self.context.as_ctx_ref() as *mut LLVMOpaqueContext,
4335                )
4336            };
4337            unsafe { EnzymeTypeTreeOnlyEq(new_tree, -1) };
4338
4339            // pointer to real type
4340            if concrete_type == CConcreteType_DT_Pointer {
4341                let inner_concrete_type = match self.diffsl_real_type {
4342                    RealType::F32 => CConcreteType_DT_Float,
4343                    RealType::F64 => CConcreteType_DT_Double,
4344                };
4345                let inner_new_tree = unsafe {
4346                    EnzymeNewTypeTreeCT(
4347                        inner_concrete_type,
4348                        self.context.as_ctx_ref() as *mut LLVMOpaqueContext,
4349                    )
4350                };
4351                unsafe { EnzymeTypeTreeOnlyEq(inner_new_tree, -1) };
4352                unsafe { EnzymeTypeTreeOnlyEq(inner_new_tree, -1) };
4353                unsafe { EnzymeMergeTypeTree(new_tree, inner_new_tree) };
4354            }
4355
4356            arg_trees.push(new_tree);
4357            match args_type[i] {
4358                CompileGradientArgType::Dup => {
4359                    // pass in the arg value
4360                    enzyme_fn_args.push(fn_arg.into());
4361
4362                    // pass in the darg value
4363                    let fn_darg = function.get_nth_param(param_index + 1).unwrap();
4364                    enzyme_fn_args.push(fn_darg.into());
4365
4366                    input_activity.push(CDIFFE_TYPE_DFT_DUP_ARG);
4367                }
4368                CompileGradientArgType::DupNoNeed => {
4369                    // pass in the arg value
4370                    enzyme_fn_args.push(fn_arg.into());
4371
4372                    // pass in the darg value
4373                    let fn_darg = function.get_nth_param(param_index + 1).unwrap();
4374                    enzyme_fn_args.push(fn_darg.into());
4375
4376                    input_activity.push(CDIFFE_TYPE_DFT_DUP_NONEED);
4377                }
4378                CompileGradientArgType::Const => {
4379                    // pass in the arg value
4380                    enzyme_fn_args.push(fn_arg.into());
4381
4382                    input_activity.push(CDIFFE_TYPE_DFT_CONSTANT);
4383                }
4384            }
4385        }
4386        // if we have void ret, this must be false;
4387        let ret_primary_ret = false;
4388        let diff_ret = false;
4389        let ret_activity = CDIFFE_TYPE_DFT_CONSTANT;
4390        let ret_tree = unsafe {
4391            EnzymeNewTypeTreeCT(
4392                CConcreteType_DT_Anything,
4393                self.context.as_ctx_ref() as *mut LLVMOpaqueContext,
4394            )
4395        };
4396
4397        // always optimize
4398        let fnc_opt_base = true;
4399        let logic_ref: EnzymeLogicRef = unsafe { CreateEnzymeLogic(fnc_opt_base as u8) };
4400
4401        let kv_tmp = IntList {
4402            data: std::ptr::null_mut(),
4403            size: 0,
4404        };
4405        let mut known_values = vec![kv_tmp; input_activity.len()];
4406
4407        let fn_type_info = CFnTypeInfo {
4408            Arguments: arg_trees.as_mut_ptr(),
4409            Return: ret_tree,
4410            KnownValues: known_values.as_mut_ptr(),
4411        };
4412
4413        let type_analysis: EnzymeTypeAnalysisRef =
4414            unsafe { CreateTypeAnalysis(logic_ref, std::ptr::null_mut(), std::ptr::null_mut(), 0) };
4415
4416        let mut args_uncacheable = vec![0; arg_trees.len()];
4417
4418        let enzyme_function = match mode {
4419            CompileMode::Forward | CompileMode::ForwardSens => unsafe {
4420                EnzymeCreateForwardDiff(
4421                    logic_ref, // Logic
4422                    std::ptr::null_mut(),
4423                    std::ptr::null_mut(),
4424                    original_function.as_value_ref(),
4425                    ret_activity, // LLVM function, return type
4426                    input_activity.as_mut_ptr(),
4427                    input_activity.len(), // constant arguments
4428                    type_analysis,        // type analysis struct
4429                    ret_primary_ret as u8,
4430                    CDerivativeMode_DEM_ForwardMode, // return value, dret_used, top_level which was 1
4431                    1,                               // free memory
4432                    0,                               // runtime activity
4433                    0,                               // strong zero
4434                    1,                               // vector mode width
4435                    std::ptr::null_mut(),            // additional argument
4436                    fn_type_info,                    // additional_arg, type info (return + args)
4437                    1,                               // subsequent calls may write
4438                    args_uncacheable.as_mut_ptr(),   // overwritten args
4439                    args_uncacheable.len(),          // overwritten args length
4440                    std::ptr::null_mut(),            // write augmented function to this
4441                )
4442            },
4443            CompileMode::Reverse | CompileMode::ReverseSens => {
4444                let mut call_enzyme = || unsafe {
4445                    EnzymeCreatePrimalAndGradient(
4446                        logic_ref,
4447                        std::ptr::null_mut(),
4448                        std::ptr::null_mut(),
4449                        original_function.as_value_ref(),
4450                        ret_activity,
4451                        input_activity.as_mut_ptr(),
4452                        input_activity.len(),
4453                        type_analysis,
4454                        ret_primary_ret as u8,
4455                        diff_ret as u8,
4456                        CDerivativeMode_DEM_ReverseModeCombined,
4457                        0,
4458                        0, // strong zero
4459                        1,
4460                        1,
4461                        std::ptr::null_mut(),
4462                        0, // force annonymous tape
4463                        fn_type_info,
4464                        0, // subsequent calls may write
4465                        args_uncacheable.as_mut_ptr(),
4466                        args_uncacheable.len(),
4467                        std::ptr::null_mut(),
4468                        if self.threaded { 1 } else { 0 }, // atomic add
4469                    )
4470                };
4471                if self.threaded {
4472                    // the register call handler alters a global variable, so we need to lock it
4473                    let _lock = my_mutex.lock().unwrap();
4474                    let barrier_string = CString::new("barrier").unwrap();
4475                    unsafe {
4476                        EnzymeRegisterCallHandler(
4477                            barrier_string.as_ptr(),
4478                            Some(fwd_handler),
4479                            Some(rev_handler),
4480                        )
4481                    };
4482                    let ret = call_enzyme();
4483                    // unregister it so some other thread doesn't use it
4484                    unsafe { EnzymeRegisterCallHandler(barrier_string.as_ptr(), None, None) };
4485                    ret
4486                } else {
4487                    call_enzyme()
4488                }
4489            }
4490        };
4491
4492        // free everything
4493        unsafe { FreeEnzymeLogic(logic_ref) };
4494        unsafe { FreeTypeAnalysis(type_analysis) };
4495        unsafe { EnzymeFreeTypeTree(ret_tree) };
4496        for tree in arg_trees {
4497            unsafe { EnzymeFreeTypeTree(tree) };
4498        }
4499
4500        // call enzyme function
4501        let enzyme_function =
4502            unsafe { FunctionValue::new(enzyme_function as LLVMValueRef) }.unwrap();
4503        self.builder
4504            .build_call(enzyme_function, enzyme_fn_args.as_slice(), "enzyme_call")?;
4505
4506        // return
4507        self.builder.build_return(None)?;
4508
4509        if function.verify(true) {
4510            Ok(function)
4511        } else {
4512            function.print_to_stderr();
4513            enzyme_function.print_to_stderr();
4514            unsafe {
4515                function.delete();
4516            }
4517            Err(anyhow!("Invalid generated function."))
4518        }
4519    }
4520
4521    pub fn compile_get_dims(&mut self, model: &DiscreteModel) -> Result<FunctionValue<'ctx>> {
4522        self.clear();
4523        let fn_arg_names = &[
4524            "states",
4525            "inputs",
4526            "outputs",
4527            "data",
4528            "stop",
4529            "has_mass",
4530            "has_reset",
4531        ];
4532        let function = self.add_function(
4533            "get_dims",
4534            fn_arg_names,
4535            &[
4536                self.int_ptr_type.into(),
4537                self.int_ptr_type.into(),
4538                self.int_ptr_type.into(),
4539                self.int_ptr_type.into(),
4540                self.int_ptr_type.into(),
4541                self.int_ptr_type.into(),
4542                self.int_ptr_type.into(),
4543            ],
4544            None,
4545            false,
4546        );
4547        let _block = self.start_function(function, None);
4548
4549        for (i, arg) in function.get_param_iter().enumerate() {
4550            let name = fn_arg_names[i];
4551            let alloca = self.function_arg_alloca(name, arg);
4552            self.insert_param(name, alloca);
4553        }
4554
4555        self.insert_indices();
4556
4557        let number_of_states = model.state().nnz() as u64;
4558        let number_of_inputs = model.input().map(|inp| inp.nnz()).unwrap_or(0) as u64;
4559        let number_of_outputs = match model.out() {
4560            Some(out) => out.nnz() as u64,
4561            None => 0,
4562        };
4563        let number_of_stop = if let Some(stop) = model.stop() {
4564            stop.nnz() as u64
4565        } else {
4566            0
4567        };
4568        let has_mass = match model.lhs().is_some() {
4569            true => 1u64,
4570            false => 0u64,
4571        };
4572        let has_reset = match model.reset().is_some() {
4573            true => 1u64,
4574            false => 0u64,
4575        };
4576        let data_len = self.layout.data().len() as u64;
4577        self.builder.build_store(
4578            *self.get_param("states"),
4579            self.int_type.const_int(number_of_states, false),
4580        )?;
4581        self.builder.build_store(
4582            *self.get_param("inputs"),
4583            self.int_type.const_int(number_of_inputs, false),
4584        )?;
4585        self.builder.build_store(
4586            *self.get_param("outputs"),
4587            self.int_type.const_int(number_of_outputs, false),
4588        )?;
4589        self.builder.build_store(
4590            *self.get_param("data"),
4591            self.int_type.const_int(data_len, false),
4592        )?;
4593        self.builder.build_store(
4594            *self.get_param("stop"),
4595            self.int_type.const_int(number_of_stop, false),
4596        )?;
4597        self.builder.build_store(
4598            *self.get_param("has_mass"),
4599            self.int_type.const_int(has_mass, false),
4600        )?;
4601        self.builder.build_store(
4602            *self.get_param("has_reset"),
4603            self.int_type.const_int(has_reset, false),
4604        )?;
4605        self.builder.build_return(None)?;
4606
4607        if function.verify(true) {
4608            Ok(function)
4609        } else {
4610            function.print_to_stderr();
4611            unsafe {
4612                function.delete();
4613            }
4614            Err(anyhow!("Invalid generated function."))
4615        }
4616    }
4617
4618    pub fn compile_get_tensor(
4619        &mut self,
4620        model: &DiscreteModel,
4621        name: &str,
4622    ) -> Result<FunctionValue<'ctx>> {
4623        self.clear();
4624        let real_ptr_ptr_type = Self::pointer_type(self.context, self.real_ptr_type.into());
4625        let function_name = format!("get_tensor_{name}");
4626        let fn_arg_names = &["data", "tensor_data", "tensor_size"];
4627        let function = self.add_function(
4628            function_name.as_str(),
4629            fn_arg_names,
4630            &[
4631                self.real_ptr_type.into(),
4632                real_ptr_ptr_type.into(),
4633                self.int_ptr_type.into(),
4634            ],
4635            None,
4636            false,
4637        );
4638        let _basic_block = self.start_function(function, None);
4639
4640        for (i, arg) in function.get_param_iter().enumerate() {
4641            let name = fn_arg_names[i];
4642            let alloca = self.function_arg_alloca(name, arg);
4643            self.insert_param(name, alloca);
4644        }
4645
4646        self.insert_data(model);
4647        let ptr = self.get_param(name);
4648        let tensor_size = self.layout.get_layout(name).unwrap().nnz() as u64;
4649        let tensor_size_value = self.int_type.const_int(tensor_size, false);
4650        self.builder
4651            .build_store(*self.get_param("tensor_data"), ptr.as_basic_value_enum())?;
4652        self.builder
4653            .build_store(*self.get_param("tensor_size"), tensor_size_value)?;
4654        self.builder.build_return(None)?;
4655
4656        if function.verify(true) {
4657            Ok(function)
4658        } else {
4659            function.print_to_stderr();
4660            unsafe {
4661                function.delete();
4662            }
4663            Err(anyhow!("Invalid generated function."))
4664        }
4665    }
4666
4667    pub fn compile_get_constant(
4668        &mut self,
4669        model: &DiscreteModel,
4670        name: &str,
4671    ) -> Result<FunctionValue<'ctx>> {
4672        self.clear();
4673        let real_ptr_ptr_type = Self::pointer_type(self.context, self.real_ptr_type.into());
4674        let function_name = format!("get_constant_{name}");
4675        let fn_arg_names = &["tensor_data", "tensor_size"];
4676        let function = self.add_function(
4677            function_name.as_str(),
4678            fn_arg_names,
4679            &[real_ptr_ptr_type.into(), self.int_ptr_type.into()],
4680            None,
4681            false,
4682        );
4683        let _basic_block = self.start_function(function, None);
4684
4685        for (i, arg) in function.get_param_iter().enumerate() {
4686            let name = fn_arg_names[i];
4687            let alloca = self.function_arg_alloca(name, arg);
4688            self.insert_param(name, alloca);
4689        }
4690
4691        self.insert_constants(model);
4692        let ptr = self.get_param(name);
4693        let tensor_size = self.layout.get_layout(name).unwrap().nnz() as u64;
4694        let tensor_size_value = self.int_type.const_int(tensor_size, false);
4695        self.builder
4696            .build_store(*self.get_param("tensor_data"), ptr.as_basic_value_enum())?;
4697        self.builder
4698            .build_store(*self.get_param("tensor_size"), tensor_size_value)?;
4699        self.builder.build_return(None)?;
4700
4701        if function.verify(true) {
4702            Ok(function)
4703        } else {
4704            function.print_to_stderr();
4705            unsafe {
4706                function.delete();
4707            }
4708            Err(anyhow!("Invalid generated function."))
4709        }
4710    }
4711
4712    pub fn compile_inputs(
4713        &mut self,
4714        model: &DiscreteModel,
4715        is_get: bool,
4716    ) -> Result<FunctionValue<'ctx>> {
4717        self.clear();
4718        let function_name = if is_get { "get_inputs" } else { "set_inputs" };
4719        let fn_arg_names: &[&str] = if is_get {
4720            &["inputs", "data"]
4721        } else {
4722            &["inputs", "data", "model_index"]
4723        };
4724        let fn_arg_types: &[BasicMetadataTypeEnum<'ctx>] = if is_get {
4725            &[self.real_ptr_type.into(), self.real_ptr_type.into()]
4726        } else {
4727            &[
4728                self.real_ptr_type.into(),
4729                self.real_ptr_type.into(),
4730                self.int_type.into(),
4731            ]
4732        };
4733        let function = self.add_function(function_name, fn_arg_names, fn_arg_types, None, false);
4734        let block = self.start_function(function, None);
4735
4736        for (i, arg) in function.get_param_iter().enumerate() {
4737            let name = fn_arg_names[i];
4738            let alloca = self.function_arg_alloca(name, arg);
4739            self.insert_param(name, alloca);
4740        }
4741
4742        if !is_get {
4743            let model_index = self
4744                .build_load(self.int_type, *self.get_param("model_index"), "model_index")?
4745                .into_int_value();
4746            self.builder
4747                .build_store(self.globals.model_index.as_pointer_value(), model_index)?;
4748        }
4749
4750        if let Some(input) = model.input() {
4751            let name = input.name();
4752            self.insert_tensor(input, false);
4753            let ptr = self.get_var(input);
4754            // loop thru the elements of this input and set/get them using the inputs ptr
4755            let inputs_start_index = self.int_type.const_int(0, false);
4756            let start_index = self.int_type.const_int(0, false);
4757            let end_index = self
4758                .int_type
4759                .const_int(input.nnz().try_into().unwrap(), false);
4760
4761            let input_block = self.context.append_basic_block(function, name);
4762            self.builder.build_unconditional_branch(input_block)?;
4763            self.builder.position_at_end(input_block);
4764            let index = self.builder.build_phi(self.int_type, "i")?;
4765            index.add_incoming(&[(&start_index, block)]);
4766
4767            // loop body - copy value from inputs to data
4768            let curr_input_index = index.as_basic_value().into_int_value();
4769            let input_ptr =
4770                Self::get_ptr_to_index(&self.builder, self.real_type, ptr, curr_input_index, name);
4771            let curr_inputs_index =
4772                self.builder
4773                    .build_int_add(inputs_start_index, curr_input_index, name)?;
4774            let inputs_ptr = Self::get_ptr_to_index(
4775                &self.builder,
4776                self.real_type,
4777                self.get_param("inputs"),
4778                curr_inputs_index,
4779                name,
4780            );
4781            if is_get {
4782                let input_value = self
4783                    .build_load(self.real_type, input_ptr, name)?
4784                    .into_float_value();
4785                self.builder.build_store(inputs_ptr, input_value)?;
4786            } else {
4787                let input_value = self
4788                    .build_load(self.real_type, inputs_ptr, name)?
4789                    .into_float_value();
4790                self.builder.build_store(input_ptr, input_value)?;
4791            }
4792
4793            // increment loop index
4794            let one = self.int_type.const_int(1, false);
4795            let next_index = self.builder.build_int_add(curr_input_index, one, name)?;
4796            index.add_incoming(&[(&next_index, input_block)]);
4797
4798            // loop condition
4799            let loop_while =
4800                self.builder
4801                    .build_int_compare(IntPredicate::ULT, next_index, end_index, name)?;
4802            let post_block = self.context.append_basic_block(function, name);
4803            self.builder
4804                .build_conditional_branch(loop_while, input_block, post_block)?;
4805            self.builder.position_at_end(post_block);
4806        }
4807        self.builder.build_return(None)?;
4808
4809        if function.verify(true) {
4810            Ok(function)
4811        } else {
4812            function.print_to_stderr();
4813            unsafe {
4814                function.delete();
4815            }
4816            Err(anyhow!("Invalid generated function."))
4817        }
4818    }
4819
4820    pub fn compile_set_id(&mut self, model: &DiscreteModel) -> Result<FunctionValue<'ctx>> {
4821        self.clear();
4822        let fn_arg_names = &["id"];
4823        let function = self.add_function(
4824            "set_id",
4825            fn_arg_names,
4826            &[self.real_ptr_type.into()],
4827            None,
4828            false,
4829        );
4830        let mut block = self.start_function(function, None);
4831
4832        for (i, arg) in function.get_param_iter().enumerate() {
4833            let name = fn_arg_names[i];
4834            let alloca = self.function_arg_alloca(name, arg);
4835            self.insert_param(name, alloca);
4836        }
4837
4838        let mut id_index = 0usize;
4839        for (blk, is_algebraic) in zip(model.state().elmts(), model.is_algebraic()) {
4840            let name = blk.name().unwrap_or("unknown");
4841            // loop thru the elements of this state blk and set the corresponding elements of id
4842            let id_start_index = self.int_type.const_int(id_index as u64, false);
4843            let blk_start_index = self.int_type.const_int(0, false);
4844            let blk_end_index = self
4845                .int_type
4846                .const_int(blk.nnz().try_into().unwrap(), false);
4847
4848            let blk_block = self.context.append_basic_block(function, name);
4849            self.builder.build_unconditional_branch(blk_block)?;
4850            self.builder.position_at_end(blk_block);
4851            let index = self.builder.build_phi(self.int_type, "i")?;
4852            index.add_incoming(&[(&blk_start_index, block)]);
4853
4854            // loop body - copy value from inputs to data
4855            let curr_blk_index = index.as_basic_value().into_int_value();
4856            let curr_id_index = self
4857                .builder
4858                .build_int_add(id_start_index, curr_blk_index, name)?;
4859            let id_ptr = Self::get_ptr_to_index(
4860                &self.builder,
4861                self.real_type,
4862                self.get_param("id"),
4863                curr_id_index,
4864                name,
4865            );
4866            let is_algebraic_float = if *is_algebraic { 0.0 } else { 1.0 };
4867            let is_algebraic_value = self.real_type.const_float(is_algebraic_float);
4868            self.builder.build_store(id_ptr, is_algebraic_value)?;
4869
4870            // increment loop index
4871            let one = self.int_type.const_int(1, false);
4872            let next_index = self.builder.build_int_add(curr_blk_index, one, name)?;
4873            index.add_incoming(&[(&next_index, blk_block)]);
4874
4875            // loop condition
4876            let loop_while = self.builder.build_int_compare(
4877                IntPredicate::ULT,
4878                next_index,
4879                blk_end_index,
4880                name,
4881            )?;
4882            let post_block = self.context.append_basic_block(function, name);
4883            self.builder
4884                .build_conditional_branch(loop_while, blk_block, post_block)?;
4885            self.builder.position_at_end(post_block);
4886
4887            // get ready for next blk
4888            block = post_block;
4889            id_index += blk.nnz();
4890        }
4891        self.builder.build_return(None)?;
4892
4893        if function.verify(true) {
4894            Ok(function)
4895        } else {
4896            function.print_to_stderr();
4897            unsafe {
4898                function.delete();
4899            }
4900            Err(anyhow!("Invalid generated function."))
4901        }
4902    }
4903
4904    pub fn module(&self) -> &Module<'ctx> {
4905        &self.module
4906    }
4907}