Skip to main content

cljrs_compiler/
codegen.rs

1//! Cranelift code generation: translate [`IrFunction`] to native machine code.
2//!
3//! The translator maps each IR instruction to calls into the C-ABI runtime
4//! bridge (`rt_abi`).  Every Clojure `Value` is represented as an opaque
5//! pointer (`I64` / `I32` depending on target) in the CLIF IR.
6
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use cranelift_codegen::ir::types;
11use cranelift_codegen::ir::{AbiParam, BlockArg, InstBuilder};
12use cranelift_codegen::isa::CallConv;
13use cranelift_codegen::settings::{self, Configurable};
14use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext, Variable};
15use cranelift_module::{FuncId, Linkage, Module};
16use cranelift_object::{ObjectBuilder, ObjectModule};
17
18use crate::ir::{BlockId, Const, Inst, IrFunction, KnownFn, RegionAllocKind, Terminator, VarId};
19
20// ── Error type ──────────────────────────────────────────────────────────────
21
22#[derive(Debug)]
23pub enum CodegenError {
24    Module(cranelift_module::ModuleError),
25    Codegen(String),
26}
27
28impl From<cranelift_module::ModuleError> for CodegenError {
29    fn from(e: cranelift_module::ModuleError) -> Self {
30        CodegenError::Module(e)
31    }
32}
33
34pub type CodegenResult<T> = Result<T, CodegenError>;
35
36// ── Runtime function declarations ───────────────────────────────────────────
37
38/// Cached `FuncId`s for runtime bridge functions.
39struct RuntimeFuncs {
40    rt_safepoint: FuncId,
41    rt_const_nil: FuncId,
42    rt_const_true: FuncId,
43    rt_const_false: FuncId,
44    rt_const_long: FuncId,
45    rt_const_double: FuncId,
46    rt_const_char: FuncId,
47    rt_const_string: FuncId,
48    rt_const_keyword: FuncId,
49    rt_const_symbol: FuncId,
50    rt_truthiness: FuncId,
51    rt_add: FuncId,
52    rt_sub: FuncId,
53    rt_mul: FuncId,
54    rt_div: FuncId,
55    rt_rem: FuncId,
56    rt_eq: FuncId,
57    rt_lt: FuncId,
58    rt_gt: FuncId,
59    rt_lte: FuncId,
60    rt_gte: FuncId,
61    rt_alloc_vector: FuncId,
62    rt_alloc_map: FuncId,
63    rt_alloc_set: FuncId,
64    rt_alloc_list: FuncId,
65    rt_alloc_cons: FuncId,
66    rt_get: FuncId,
67    rt_count: FuncId,
68    rt_first: FuncId,
69    rt_rest: FuncId,
70    rt_assoc: FuncId,
71    rt_conj: FuncId,
72    rt_call: FuncId,
73    rt_deref: FuncId,
74    rt_println: FuncId,
75    rt_pr: FuncId,
76    rt_is_nil: FuncId,
77    rt_is_vector: FuncId,
78    rt_is_map: FuncId,
79    rt_is_seq: FuncId,
80    rt_identical: FuncId,
81    rt_str: FuncId,
82    rt_load_global: FuncId,
83    rt_def_var: FuncId,
84    rt_make_fn: FuncId,
85    rt_make_fn_variadic: FuncId,
86    rt_make_fn_multi: FuncId,
87    rt_throw: FuncId,
88    rt_try: FuncId,
89    rt_dissoc: FuncId,
90    rt_disj: FuncId,
91    rt_nth: FuncId,
92    rt_contains: FuncId,
93    rt_seq: FuncId,
94    rt_lazy_seq: FuncId,
95    rt_transient: FuncId,
96    rt_assoc_bang: FuncId,
97    rt_conj_bang: FuncId,
98    rt_persistent_bang: FuncId,
99    rt_atom_reset: FuncId,
100    rt_atom_swap: FuncId,
101    rt_apply: FuncId,
102    rt_set_bang: FuncId,
103    rt_with_bindings: FuncId,
104    rt_load_var: FuncId,
105    rt_reduce2: FuncId,
106    rt_reduce3: FuncId,
107    rt_map: FuncId,
108    rt_filter: FuncId,
109    rt_mapv: FuncId,
110    rt_filterv: FuncId,
111    rt_some: FuncId,
112    rt_every: FuncId,
113    rt_into: FuncId,
114    rt_into3: FuncId,
115    rt_group_by: FuncId,
116    rt_partition2: FuncId,
117    rt_partition3: FuncId,
118    rt_partition4: FuncId,
119    rt_frequencies: FuncId,
120    rt_keep: FuncId,
121    rt_remove: FuncId,
122    rt_map_indexed: FuncId,
123    rt_zipmap: FuncId,
124    rt_juxt: FuncId,
125    rt_comp: FuncId,
126    rt_partial: FuncId,
127    rt_complement: FuncId,
128    rt_concat: FuncId,
129    rt_range1: FuncId,
130    rt_range2: FuncId,
131    rt_range3: FuncId,
132    rt_take: FuncId,
133    rt_drop: FuncId,
134    rt_reverse: FuncId,
135    rt_sort: FuncId,
136    rt_sort_by: FuncId,
137    rt_keys: FuncId,
138    rt_vals: FuncId,
139    rt_merge: FuncId,
140    rt_update: FuncId,
141    rt_get_in: FuncId,
142    rt_assoc_in: FuncId,
143    rt_is_number: FuncId,
144    rt_is_string: FuncId,
145    rt_is_keyword: FuncId,
146    rt_is_symbol: FuncId,
147    rt_is_bool: FuncId,
148    rt_is_int: FuncId,
149    rt_prn: FuncId,
150    rt_print: FuncId,
151    rt_atom: FuncId,
152    rt_str_n: FuncId,
153    rt_println_n: FuncId,
154    rt_with_out_str: FuncId,
155    rt_peek: FuncId,
156    rt_pop: FuncId,
157    rt_vec: FuncId,
158    rt_mapcat: FuncId,
159    rt_is_empty: FuncId,
160    rt_repeatedly: FuncId,
161    // Region allocation
162    rt_region_start: FuncId,
163    rt_region_end: FuncId,
164    rt_region_alloc_vector: FuncId,
165    rt_region_alloc_map: FuncId,
166    rt_region_alloc_set: FuncId,
167    rt_region_alloc_list: FuncId,
168    rt_region_alloc_cons: FuncId,
169}
170
171// ── Compiler context ────────────────────────────────────────────────────────
172
173/// AOT compiler: translates IR functions to native object code via Cranelift.
174pub struct Compiler {
175    module: ObjectModule,
176    ctx: cranelift_codegen::Context,
177    fb_ctx: FunctionBuilderContext,
178    rt: RuntimeFuncs,
179    ptr_type: types::Type,
180    /// Maps user-defined function names to their FuncIds.
181    user_funcs: HashMap<Arc<str>, FuncId>,
182}
183
184impl Compiler {
185    /// Create a new compiler targeting the host architecture.
186    pub fn new() -> CodegenResult<Self> {
187        let mut flag_builder = settings::builder();
188        flag_builder.set("opt_level", "speed").unwrap();
189        flag_builder.set("is_pic", "true").unwrap();
190
191        let isa_builder = cranelift_native::builder()
192            .map_err(|e| CodegenError::Codegen(format!("failed to create ISA builder: {e}")))?;
193        let isa = isa_builder
194            .finish(settings::Flags::new(flag_builder))
195            .map_err(|e| CodegenError::Codegen(format!("failed to build ISA: {e}")))?;
196
197        let ptr_type = isa.pointer_type();
198
199        let obj_builder = ObjectBuilder::new(
200            isa,
201            "clojurust_aot",
202            cranelift_module::default_libcall_names(),
203        )?;
204        let mut module = ObjectModule::new(obj_builder);
205
206        let rt = declare_runtime_funcs(&mut module, ptr_type)?;
207
208        Ok(Self {
209            ctx: module.make_context(),
210            fb_ctx: FunctionBuilderContext::new(),
211            module,
212            rt,
213            ptr_type,
214            user_funcs: HashMap::new(),
215        })
216    }
217
218    /// Declare a user function (makes it available for calls before definition).
219    pub fn declare_function(&mut self, name: &str, param_count: usize) -> CodegenResult<FuncId> {
220        let mut sig = self.module.make_signature();
221        for _ in 0..param_count {
222            sig.params.push(AbiParam::new(self.ptr_type));
223        }
224        sig.returns.push(AbiParam::new(self.ptr_type));
225        let func_id = self.module.declare_function(name, Linkage::Export, &sig)?;
226        self.user_funcs.insert(Arc::from(name), func_id);
227        Ok(func_id)
228    }
229
230    /// Compile an IR function and define it in the module.
231    pub fn compile_function(&mut self, ir_func: &IrFunction, func_id: FuncId) -> CodegenResult<()> {
232        self.ctx.func.signature = self
233            .module
234            .declarations()
235            .get_function_decl(func_id)
236            .signature
237            .clone();
238        self.ctx.func.name = cranelift_codegen::ir::UserFuncName::user(0, func_id.as_u32());
239
240        {
241            let mut builder = FunctionBuilder::new(&mut self.ctx.func, &mut self.fb_ctx);
242            {
243                let mut translator = FunctionTranslator {
244                    builder: &mut builder,
245                    module: &mut self.module,
246                    rt: &self.rt,
247                    ptr_type: self.ptr_type,
248                    var_map: HashMap::new(),
249                    block_map: HashMap::new(),
250                    user_funcs: &self.user_funcs,
251                };
252                translator.translate(ir_func)?;
253            }
254            builder.finalize();
255        }
256
257        self.module.define_function(func_id, &mut self.ctx)?;
258        self.ctx.clear();
259        Ok(())
260    }
261
262    /// Finish compilation and return the object code bytes.
263    pub fn finish(self) -> Vec<u8> {
264        let product = self.module.finish();
265        product.emit().expect("failed to emit object code")
266    }
267}
268
269// ── Function translator ─────────────────────────────────────────────────────
270
271/// Translates a single [`IrFunction`] into Cranelift IR using a
272/// [`FunctionBuilder`].
273struct FunctionTranslator<'a, 'b> {
274    builder: &'b mut FunctionBuilder<'a>,
275    module: &'b mut ObjectModule,
276    rt: &'b RuntimeFuncs,
277    ptr_type: types::Type,
278    /// Maps IR VarId → Cranelift Variable.
279    var_map: HashMap<VarId, Variable>,
280    /// Maps function names → FuncId (for referencing compiled subfunctions).
281    user_funcs: &'b HashMap<Arc<str>, FuncId>,
282    /// Maps IR BlockId → Cranelift Block.
283    block_map: HashMap<BlockId, cranelift_codegen::ir::Block>,
284}
285
286impl<'a, 'b> FunctionTranslator<'a, 'b> {
287    fn translate(&mut self, ir_func: &IrFunction) -> CodegenResult<()> {
288        // Create all CLIF blocks upfront.
289        for block in &ir_func.blocks {
290            let clif_block = self.builder.create_block();
291            self.block_map.insert(block.id, clif_block);
292        }
293
294        // Entry block: append params.
295        let entry_block = self.block_map[&ir_func.blocks[0].id];
296        self.builder.switch_to_block(entry_block);
297        self.builder
298            .append_block_params_for_function_params(entry_block);
299
300        // Bind function parameters to variables.
301        for (i, (_name, var_id)) in ir_func.params.iter().enumerate() {
302            let var = self.ensure_var(*var_id);
303            let param_val = self.builder.block_params(entry_block)[i];
304            self.builder.def_var(var, param_val);
305        }
306
307        // GC safepoint at function entry.
308        self.emit_safepoint();
309
310        // Translate each block.
311        for (block_idx, ir_block) in ir_func.blocks.iter().enumerate() {
312            let clif_block = self.block_map[&ir_block.id];
313
314            if block_idx > 0 {
315                self.builder.switch_to_block(clif_block);
316            }
317
318            // Phi nodes become block parameters in Cranelift.
319            // We handle them specially: each phi adds a block parameter,
320            // and predecessor jumps pass the right value.
321            // For now, phi values are pre-declared as variables.
322            for inst in &ir_block.phis {
323                if let Inst::Phi(dst, _) = inst {
324                    let var = self.ensure_var(*dst);
325                    let param = self.builder.append_block_param(clif_block, self.ptr_type);
326                    self.builder.def_var(var, param);
327                }
328            }
329
330            // Translate regular instructions.
331            for inst in &ir_block.insts {
332                self.translate_inst(inst)?;
333            }
334
335            // Translate terminator.
336            self.translate_terminator(&ir_block.terminator, ir_block.id, ir_func)?;
337        }
338
339        self.builder.seal_all_blocks();
340        Ok(())
341    }
342
343    fn translate_inst(&mut self, inst: &Inst) -> CodegenResult<()> {
344        match inst {
345            Inst::Const(dst, c) => {
346                let val = self.emit_const(c)?;
347                let var = self.ensure_var(*dst);
348                self.builder.def_var(var, val);
349            }
350
351            Inst::LoadLocal(dst, _name) => {
352                // In AOT, locals are already bound via parameters or let bindings.
353                // LoadLocal should have been resolved by the ANF lowering.
354                // For now, define as nil.
355                let val = self.call_rt_0(self.rt.rt_const_nil)?;
356                let var = self.ensure_var(*dst);
357                self.builder.def_var(var, val);
358            }
359
360            Inst::LoadGlobal(dst, ns, name) => {
361                let val = self.emit_load_global(ns, name)?;
362                let var = self.ensure_var(*dst);
363                self.builder.def_var(var, val);
364            }
365
366            Inst::LoadVar(dst, ns, name) => {
367                let val = self.emit_load_var(ns, name)?;
368                let var = self.ensure_var(*dst);
369                self.builder.def_var(var, val);
370            }
371
372            Inst::AllocVector(dst, elems) => {
373                let val = self.emit_alloc_collection(self.rt.rt_alloc_vector, elems)?;
374                let var = self.ensure_var(*dst);
375                self.builder.def_var(var, val);
376            }
377
378            Inst::AllocMap(dst, pairs) => {
379                let flat: Vec<VarId> = pairs.iter().flat_map(|(k, v)| [*k, *v]).collect();
380                let val = self.emit_alloc_collection(self.rt.rt_alloc_map, &flat)?;
381                let var = self.ensure_var(*dst);
382                self.builder.def_var(var, val);
383            }
384
385            Inst::AllocSet(dst, elems) => {
386                let val = self.emit_alloc_collection(self.rt.rt_alloc_set, elems)?;
387                let var = self.ensure_var(*dst);
388                self.builder.def_var(var, val);
389            }
390
391            Inst::AllocList(dst, elems) => {
392                let val = self.emit_alloc_collection(self.rt.rt_alloc_list, elems)?;
393                let var = self.ensure_var(*dst);
394                self.builder.def_var(var, val);
395            }
396
397            Inst::AllocCons(dst, head, tail) => {
398                let h = self.use_var(*head);
399                let t = self.use_var(*tail);
400                let val = self.call_rt_2(self.rt.rt_alloc_cons, h, t)?;
401                let var = self.ensure_var(*dst);
402                self.builder.def_var(var, val);
403            }
404
405            Inst::CallKnown(dst, known_fn, args) => {
406                let val = self.emit_known_call(known_fn, args)?;
407                let var = self.ensure_var(*dst);
408                self.builder.def_var(var, val);
409            }
410
411            Inst::Call(dst, callee, args) => {
412                let val = self.emit_unknown_call(*callee, args)?;
413                let var = self.ensure_var(*dst);
414                self.builder.def_var(var, val);
415            }
416
417            Inst::CallDirect(dst, fn_name, args) => {
418                let val = self.emit_direct_call(fn_name, args)?;
419                let var = self.ensure_var(*dst);
420                self.builder.def_var(var, val);
421            }
422
423            Inst::Deref(dst, src) => {
424                let s = self.use_var(*src);
425                let val = self.call_rt_1(self.rt.rt_deref, s)?;
426                let var = self.ensure_var(*dst);
427                self.builder.def_var(var, val);
428            }
429
430            Inst::DefVar(dst, ns, name, val_var) => {
431                let val = self.emit_def_var(ns, name, *val_var)?;
432                let var = self.ensure_var(*dst);
433                self.builder.def_var(var, val);
434            }
435
436            Inst::SetBang(var, val) => {
437                let var_v = self.use_var(*var);
438                let val_v = self.use_var(*val);
439                let func_ref = self.import_func(self.rt.rt_set_bang);
440                self.builder.ins().call(func_ref, &[var_v, val_v]);
441            }
442
443            Inst::Throw(val) => {
444                let v = self.use_var(*val);
445                let func_ref = self.import_func(self.rt.rt_throw);
446                self.builder.ins().call(func_ref, &[v]);
447                // rt_throw stores the exception in a thread-local and returns.
448                // The block ends with Unreachable which returns nil, allowing
449                // the caller (rt_try) to check the thread-local.
450            }
451
452            Inst::Phi(_, _) => {
453                // Handled above in block preamble.
454            }
455
456            Inst::Recur(_) => {
457                // Handled by RecurJump terminator.
458            }
459
460            Inst::SourceLoc(_) => {
461                // No-op in codegen (could add debug info later).
462            }
463
464            Inst::AllocClosure(dst, template, captures) => {
465                if template.arity_fn_names.is_empty() {
466                    // No compiled arities — fall back to nil.
467                    let val = self.call_rt_0(self.rt.rt_const_nil)?;
468                    let var = self.ensure_var(*dst);
469                    self.builder.def_var(var, val);
470                } else {
471                    // Emit the function name as a data constant.
472                    let name_str = template
473                        .name
474                        .as_deref()
475                        .unwrap_or(&template.arity_fn_names[0]);
476                    let name_data = self.module.declare_anonymous_data(false, false)?;
477                    let mut name_desc = cranelift_module::DataDescription::new();
478                    name_desc.define(name_str.as_bytes().to_vec().into_boxed_slice());
479                    self.module.define_data(name_data, &name_desc)?;
480                    let name_gv = self
481                        .module
482                        .declare_data_in_func(name_data, self.builder.func);
483                    let name_ptr = self.builder.ins().global_value(self.ptr_type, name_gv);
484                    let name_len = self.builder.ins().iconst(types::I64, name_str.len() as i64);
485
486                    // Spill captures to stack.
487                    let ncaptures = captures.len();
488                    let (captures_ptr, ncaptures_val) = if ncaptures == 0 {
489                        let null = self.builder.ins().iconst(self.ptr_type, 0);
490                        let zero = self.builder.ins().iconst(types::I64, 0);
491                        (null, zero)
492                    } else {
493                        let slot = self.builder.create_sized_stack_slot(
494                            cranelift_codegen::ir::StackSlotData::new(
495                                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
496                                (ncaptures * 8) as u32,
497                                3,
498                            ),
499                        );
500                        for (i, cap_var) in captures.iter().enumerate() {
501                            let cap_val = self.use_var(*cap_var);
502                            self.builder
503                                .ins()
504                                .stack_store(cap_val, slot, (i * 8) as i32);
505                        }
506                        let slot_addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
507                        let n = self.builder.ins().iconst(types::I64, ncaptures as i64);
508                        (slot_addr, n)
509                    };
510
511                    let n_arities = template.arity_fn_names.len();
512                    if n_arities == 1 && !template.is_variadic[0] {
513                        // Single fixed arity — use rt_make_fn (simpler path).
514                        let arity_fn_name = &template.arity_fn_names[0];
515                        let param_count = template.param_counts[0];
516                        let arity_func_id = self.user_funcs[arity_fn_name];
517                        let func_ref = self
518                            .module
519                            .declare_func_in_func(arity_func_id, self.builder.func);
520                        let fn_ptr = self.builder.ins().func_addr(self.ptr_type, func_ref);
521                        let param_count_val =
522                            self.builder.ins().iconst(types::I64, param_count as i64);
523
524                        let rt_ref = self.import_func(self.rt.rt_make_fn);
525                        let call = self.builder.ins().call(
526                            rt_ref,
527                            &[
528                                name_ptr,
529                                name_len,
530                                fn_ptr,
531                                param_count_val,
532                                captures_ptr,
533                                ncaptures_val,
534                            ],
535                        );
536                        let result = self.builder.inst_results(call)[0];
537                        let var = self.ensure_var(*dst);
538                        self.builder.def_var(var, result);
539                    } else if n_arities == 1 && template.is_variadic[0] {
540                        // Single variadic arity — use rt_make_fn_variadic.
541                        let arity_fn_name = &template.arity_fn_names[0];
542                        let param_count = template.param_counts[0];
543                        let arity_func_id = self.user_funcs[arity_fn_name];
544                        let func_ref = self
545                            .module
546                            .declare_func_in_func(arity_func_id, self.builder.func);
547                        let fn_ptr = self.builder.ins().func_addr(self.ptr_type, func_ref);
548                        let param_count_val =
549                            self.builder.ins().iconst(types::I64, param_count as i64);
550
551                        let rt_ref = self.import_func(self.rt.rt_make_fn_variadic);
552                        let call = self.builder.ins().call(
553                            rt_ref,
554                            &[
555                                name_ptr,
556                                name_len,
557                                fn_ptr,
558                                param_count_val,
559                                captures_ptr,
560                                ncaptures_val,
561                            ],
562                        );
563                        let result = self.builder.inst_results(call)[0];
564                        let var = self.ensure_var(*dst);
565                        self.builder.def_var(var, result);
566                    } else {
567                        // Multi-arity — spill fn_ptrs, param_counts, and is_variadic arrays,
568                        // then call rt_make_fn_multi.
569
570                        // Stack-spill function pointers array.
571                        let fn_ptrs_slot = self.builder.create_sized_stack_slot(
572                            cranelift_codegen::ir::StackSlotData::new(
573                                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
574                                (n_arities * 8) as u32,
575                                3,
576                            ),
577                        );
578                        for (i, arity_fn_name) in template.arity_fn_names.iter().enumerate() {
579                            let arity_func_id = self.user_funcs[arity_fn_name];
580                            let func_ref = self
581                                .module
582                                .declare_func_in_func(arity_func_id, self.builder.func);
583                            let fn_ptr = self.builder.ins().func_addr(self.ptr_type, func_ref);
584                            self.builder
585                                .ins()
586                                .stack_store(fn_ptr, fn_ptrs_slot, (i * 8) as i32);
587                        }
588                        let fn_ptrs_addr =
589                            self.builder
590                                .ins()
591                                .stack_addr(self.ptr_type, fn_ptrs_slot, 0);
592
593                        // Stack-spill param_counts array (as i64 values).
594                        let pc_slot = self.builder.create_sized_stack_slot(
595                            cranelift_codegen::ir::StackSlotData::new(
596                                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
597                                (n_arities * 8) as u32,
598                                3,
599                            ),
600                        );
601                        for (i, &pc) in template.param_counts.iter().enumerate() {
602                            let pc_val = self.builder.ins().iconst(types::I64, pc as i64);
603                            self.builder
604                                .ins()
605                                .stack_store(pc_val, pc_slot, (i * 8) as i32);
606                        }
607                        let pc_addr = self.builder.ins().stack_addr(self.ptr_type, pc_slot, 0);
608
609                        // Stack-spill is_variadic array (as u8 values, 1 byte each).
610                        let var_slot = self.builder.create_sized_stack_slot(
611                            cranelift_codegen::ir::StackSlotData::new(
612                                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
613                                n_arities as u32,
614                                0,
615                            ),
616                        );
617                        for (i, &v) in template.is_variadic.iter().enumerate() {
618                            let v_val = self.builder.ins().iconst(types::I8, if v { 1 } else { 0 });
619                            self.builder.ins().stack_store(v_val, var_slot, i as i32);
620                        }
621                        let var_addr = self.builder.ins().stack_addr(self.ptr_type, var_slot, 0);
622
623                        let n_arities_val = self.builder.ins().iconst(types::I64, n_arities as i64);
624
625                        // Call rt_make_fn_multi(name_ptr, name_len, fn_ptrs, param_counts,
626                        //                      is_variadic, n_arities, captures, ncaptures)
627                        let rt_ref = self.import_func(self.rt.rt_make_fn_multi);
628                        let call = self.builder.ins().call(
629                            rt_ref,
630                            &[
631                                name_ptr,
632                                name_len,
633                                fn_ptrs_addr,
634                                pc_addr,
635                                var_addr,
636                                n_arities_val,
637                                captures_ptr,
638                                ncaptures_val,
639                            ],
640                        );
641                        let result = self.builder.inst_results(call)[0];
642                        let var = self.ensure_var(*dst);
643                        self.builder.def_var(var, result);
644                    }
645                }
646            }
647
648            Inst::RegionStart(dst) => {
649                // Allocate and activate a bump region on the thread-local stack.
650                let val = self.call_rt_0(self.rt.rt_region_start)?;
651                let var = self.ensure_var(*dst);
652                self.builder.def_var(var, val);
653            }
654
655            Inst::RegionAlloc(dst, region, kind, operands) => {
656                let region_handle = self.use_var(*region);
657                let val = match kind {
658                    RegionAllocKind::Vector => self.emit_region_alloc_collection(
659                        self.rt.rt_region_alloc_vector,
660                        region_handle,
661                        operands,
662                    )?,
663                    RegionAllocKind::Map => self.emit_region_alloc_collection(
664                        self.rt.rt_region_alloc_map,
665                        region_handle,
666                        operands,
667                    )?,
668                    RegionAllocKind::Set => self.emit_region_alloc_collection(
669                        self.rt.rt_region_alloc_set,
670                        region_handle,
671                        operands,
672                    )?,
673                    RegionAllocKind::List => self.emit_region_alloc_collection(
674                        self.rt.rt_region_alloc_list,
675                        region_handle,
676                        operands,
677                    )?,
678                    RegionAllocKind::Cons => {
679                        if operands.len() == 2 {
680                            let h = self.use_var(operands[0]);
681                            let t = self.use_var(operands[1]);
682                            let func_ref = self.import_func(self.rt.rt_region_alloc_cons);
683                            let call = self.builder.ins().call(func_ref, &[region_handle, h, t]);
684                            self.builder.inst_results(call)[0]
685                        } else {
686                            self.call_rt_0(self.rt.rt_const_nil)?
687                        }
688                    }
689                };
690                let var = self.ensure_var(*dst);
691                self.builder.def_var(var, val);
692            }
693
694            Inst::RegionEnd(region) => {
695                // Pop and free the bump region.
696                let handle = self.use_var(*region);
697                let func_ref = self.import_func(self.rt.rt_region_end);
698                self.builder.ins().call(func_ref, &[handle]);
699            }
700
701            Inst::RegionParam(dst) => {
702                // Marker bound at the entry of a region-parameterised callee
703                // variant.  The actual region is inherited via the
704                // thread-local region stack, so the operand is just nil.
705                let val = self.call_rt_0(self.rt.rt_const_nil)?;
706                let var = self.ensure_var(*dst);
707                self.builder.def_var(var, val);
708            }
709
710            Inst::CallWithRegion(dst, fn_name, args) => {
711                // Direct call to a region-parameterised variant.  The caller's
712                // surrounding `RegionStart`/`RegionEnd` keeps the region
713                // active on the thread-local stack across this call.
714                let val = self.emit_direct_call(fn_name, args)?;
715                let var = self.ensure_var(*dst);
716                self.builder.def_var(var, val);
717            }
718        }
719        Ok(())
720    }
721
722    fn translate_terminator(
723        &mut self,
724        term: &Terminator,
725        current_block_id: BlockId,
726        ir_func: &IrFunction,
727    ) -> CodegenResult<()> {
728        match term {
729            Terminator::Return(var_id) => {
730                let val = self.use_var(*var_id);
731                self.builder.ins().return_(&[val]);
732            }
733
734            Terminator::Jump(target) => {
735                let clif_block = self.block_map[target];
736                let phi_args = self.collect_phi_args(*target, current_block_id, ir_func);
737                self.builder.ins().jump(clif_block, &phi_args);
738            }
739
740            Terminator::Branch {
741                cond,
742                then_block,
743                else_block,
744            } => {
745                let cond_val = self.use_var(*cond);
746                let truthy = self.call_rt_1_i8(self.rt.rt_truthiness, cond_val)?;
747                let then_b = self.block_map[then_block];
748                let else_b = self.block_map[else_block];
749                let then_args = self.collect_phi_args(*then_block, current_block_id, ir_func);
750                let else_args = self.collect_phi_args(*else_block, current_block_id, ir_func);
751                self.builder
752                    .ins()
753                    .brif(truthy, then_b, &then_args, else_b, &else_args);
754            }
755
756            Terminator::RecurJump { target, args } => {
757                // GC safepoint before looping back, so tight recur
758                // loops cooperate with the collector.
759                self.emit_safepoint();
760                let clif_block = self.block_map[target];
761                let arg_vals: Vec<BlockArg> = args
762                    .iter()
763                    .map(|a| BlockArg::Value(self.use_var(*a)))
764                    .collect();
765                self.builder.ins().jump(clif_block, &arg_vals);
766            }
767
768            Terminator::Unreachable => {
769                // Return nil as a safe fallback. In practice, throw paths
770                // return before reaching here (see Inst::Throw above).
771                let nil_ref = self.import_func(self.rt.rt_const_nil);
772                let nil_call = self.builder.ins().call(nil_ref, &[]);
773                let nil_val = self.builder.inst_results(nil_call)[0];
774                self.builder.ins().return_(&[nil_val]);
775            }
776        }
777        Ok(())
778    }
779
780    /// Collect phi arguments needed when jumping from `from_block` to `to_block`.
781    fn collect_phi_args(
782        &mut self,
783        to_block: BlockId,
784        from_block: BlockId,
785        ir_func: &IrFunction,
786    ) -> Vec<BlockArg> {
787        let target = ir_func.blocks.iter().find(|b| b.id == to_block);
788        let Some(target) = target else {
789            return vec![];
790        };
791        target
792            .phis
793            .iter()
794            .filter_map(|inst| {
795                if let Inst::Phi(_, entries) = inst {
796                    // Find the entry for the predecessor block.
797                    entries
798                        .iter()
799                        .find(|(pred, _)| *pred == from_block)
800                        .map(|(_, var_id)| BlockArg::Value(self.use_var(*var_id)))
801                } else {
802                    None
803                }
804            })
805            .collect()
806    }
807
808    // ── Helpers ─────────────────────────────────────────────────────────────
809
810    /// Emit a call to `rt_safepoint()`.
811    fn emit_safepoint(&mut self) {
812        let func_ref = self.import_func(self.rt.rt_safepoint);
813        self.builder.ins().call(func_ref, &[]);
814    }
815
816    fn ensure_var(&mut self, var_id: VarId) -> Variable {
817        if let Some(&var) = self.var_map.get(&var_id) {
818            var
819        } else {
820            let var = self.builder.declare_var(self.ptr_type);
821            self.var_map.insert(var_id, var);
822            var
823        }
824    }
825
826    fn use_var(&mut self, var_id: VarId) -> cranelift_codegen::ir::Value {
827        let var = self.ensure_var(var_id);
828        self.builder.use_var(var)
829    }
830
831    /// Emit a constant value.
832    fn emit_const(&mut self, c: &Const) -> CodegenResult<cranelift_codegen::ir::Value> {
833        match c {
834            Const::Nil => self.call_rt_0(self.rt.rt_const_nil),
835            Const::Bool(true) => self.call_rt_0(self.rt.rt_const_true),
836            Const::Bool(false) => self.call_rt_0(self.rt.rt_const_false),
837            Const::Long(n) => {
838                let func_ref = self.import_func(self.rt.rt_const_long);
839                let arg = self.builder.ins().iconst(types::I64, *n);
840                let call = self.builder.ins().call(func_ref, &[arg]);
841                Ok(self.builder.inst_results(call)[0])
842            }
843            Const::Double(f) => {
844                let func_ref = self.import_func(self.rt.rt_const_double);
845                let arg = self.builder.ins().f64const(*f);
846                let call = self.builder.ins().call(func_ref, &[arg]);
847                Ok(self.builder.inst_results(call)[0])
848            }
849            Const::Char(ch) => {
850                let func_ref = self.import_func(self.rt.rt_const_char);
851                let arg = self.builder.ins().iconst(types::I32, *ch as i64);
852                let call = self.builder.ins().call(func_ref, &[arg]);
853                Ok(self.builder.inst_results(call)[0])
854            }
855            Const::Str(s) => self.emit_string_const(self.rt.rt_const_string, s),
856            Const::Keyword(s) => self.emit_string_const(self.rt.rt_const_keyword, s),
857            Const::Symbol(s) => self.emit_string_const(self.rt.rt_const_symbol, s),
858        }
859    }
860
861    /// Emit a call to a runtime function that takes (ptr, len) for a string.
862    fn emit_string_const(
863        &mut self,
864        func_id: FuncId,
865        s: &str,
866    ) -> CodegenResult<cranelift_codegen::ir::Value> {
867        // Store the string bytes as a data object in the module.
868        let data_id = self
869            .module
870            .declare_anonymous_data(false, false)
871            .map_err(CodegenError::Module)?;
872
873        let mut data_desc = cranelift_module::DataDescription::new();
874        data_desc.define(s.as_bytes().to_vec().into_boxed_slice());
875        self.module
876            .define_data(data_id, &data_desc)
877            .map_err(CodegenError::Module)?;
878
879        let global_val = self.module.declare_data_in_func(data_id, self.builder.func);
880        let ptr = self.builder.ins().global_value(self.ptr_type, global_val);
881        let len = self.builder.ins().iconst(types::I64, s.len() as i64);
882
883        let func_ref = self.import_func(func_id);
884        let call = self.builder.ins().call(func_ref, &[ptr, len]);
885        Ok(self.builder.inst_results(call)[0])
886    }
887
888    /// Emit a LoadGlobal (ns/name lookup).
889    fn emit_load_global(
890        &mut self,
891        ns: &str,
892        name: &str,
893    ) -> CodegenResult<cranelift_codegen::ir::Value> {
894        // Create data objects for ns and name strings.
895        let ns_data = self.module.declare_anonymous_data(false, false)?;
896        let mut ns_desc = cranelift_module::DataDescription::new();
897        ns_desc.define(ns.as_bytes().to_vec().into_boxed_slice());
898        self.module.define_data(ns_data, &ns_desc)?;
899
900        let name_data = self.module.declare_anonymous_data(false, false)?;
901        let mut name_desc = cranelift_module::DataDescription::new();
902        name_desc.define(name.as_bytes().to_vec().into_boxed_slice());
903        self.module.define_data(name_data, &name_desc)?;
904
905        let ns_gv = self.module.declare_data_in_func(ns_data, self.builder.func);
906        let ns_ptr = self.builder.ins().global_value(self.ptr_type, ns_gv);
907        let ns_len = self.builder.ins().iconst(types::I64, ns.len() as i64);
908
909        let name_gv = self
910            .module
911            .declare_data_in_func(name_data, self.builder.func);
912        let name_ptr = self.builder.ins().global_value(self.ptr_type, name_gv);
913        let name_len = self.builder.ins().iconst(types::I64, name.len() as i64);
914
915        let func_ref = self.import_func(self.rt.rt_load_global);
916        let call = self
917            .builder
918            .ins()
919            .call(func_ref, &[ns_ptr, ns_len, name_ptr, name_len]);
920        Ok(self.builder.inst_results(call)[0])
921    }
922
923    /// Emit a LoadVar (ns/name lookup) — returns the Var object, not its value.
924    fn emit_load_var(
925        &mut self,
926        ns: &str,
927        name: &str,
928    ) -> CodegenResult<cranelift_codegen::ir::Value> {
929        let ns_data = self.module.declare_anonymous_data(false, false)?;
930        let mut ns_desc = cranelift_module::DataDescription::new();
931        ns_desc.define(ns.as_bytes().to_vec().into_boxed_slice());
932        self.module.define_data(ns_data, &ns_desc)?;
933
934        let name_data = self.module.declare_anonymous_data(false, false)?;
935        let mut name_desc = cranelift_module::DataDescription::new();
936        name_desc.define(name.as_bytes().to_vec().into_boxed_slice());
937        self.module.define_data(name_data, &name_desc)?;
938
939        let ns_gv = self.module.declare_data_in_func(ns_data, self.builder.func);
940        let ns_ptr = self.builder.ins().global_value(self.ptr_type, ns_gv);
941        let ns_len = self.builder.ins().iconst(types::I64, ns.len() as i64);
942
943        let name_gv = self
944            .module
945            .declare_data_in_func(name_data, self.builder.func);
946        let name_ptr = self.builder.ins().global_value(self.ptr_type, name_gv);
947        let name_len = self.builder.ins().iconst(types::I64, name.len() as i64);
948
949        let func_ref = self.import_func(self.rt.rt_load_var);
950        let call = self
951            .builder
952            .ins()
953            .call(func_ref, &[ns_ptr, ns_len, name_ptr, name_len]);
954        Ok(self.builder.inst_results(call)[0])
955    }
956
957    /// Emit a `(def ns/name val)` — interns the var in the global env.
958    fn emit_def_var(
959        &mut self,
960        ns: &str,
961        name: &str,
962        val_var: VarId,
963    ) -> CodegenResult<cranelift_codegen::ir::Value> {
964        let ns_data = self.module.declare_anonymous_data(false, false)?;
965        let mut ns_desc = cranelift_module::DataDescription::new();
966        ns_desc.define(ns.as_bytes().to_vec().into_boxed_slice());
967        self.module.define_data(ns_data, &ns_desc)?;
968
969        let name_data = self.module.declare_anonymous_data(false, false)?;
970        let mut name_desc = cranelift_module::DataDescription::new();
971        name_desc.define(name.as_bytes().to_vec().into_boxed_slice());
972        self.module.define_data(name_data, &name_desc)?;
973
974        let ns_gv = self.module.declare_data_in_func(ns_data, self.builder.func);
975        let ns_ptr = self.builder.ins().global_value(self.ptr_type, ns_gv);
976        let ns_len = self.builder.ins().iconst(types::I64, ns.len() as i64);
977
978        let name_gv = self
979            .module
980            .declare_data_in_func(name_data, self.builder.func);
981        let name_ptr = self.builder.ins().global_value(self.ptr_type, name_gv);
982        let name_len = self.builder.ins().iconst(types::I64, name.len() as i64);
983
984        let val = self.use_var(val_var);
985
986        let func_ref = self.import_func(self.rt.rt_def_var);
987        let call = self
988            .builder
989            .ins()
990            .call(func_ref, &[ns_ptr, ns_len, name_ptr, name_len, val]);
991        Ok(self.builder.inst_results(call)[0])
992    }
993
994    /// Emit allocation of a collection.  Spills element pointers to the stack,
995    /// then calls the runtime allocator.
996    fn emit_alloc_collection(
997        &mut self,
998        func_id: FuncId,
999        elems: &[VarId],
1000    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1001        let n = elems.len();
1002        if n == 0 {
1003            let func_ref = self.import_func(func_id);
1004            // Pass null pointer and 0.
1005            let null = self.builder.ins().iconst(self.ptr_type, 0);
1006            let zero = self.builder.ins().iconst(types::I64, 0);
1007            let call = self.builder.ins().call(func_ref, &[null, zero]);
1008            return Ok(self.builder.inst_results(call)[0]);
1009        }
1010
1011        // Allocate stack space for element pointers.
1012        let slot = self
1013            .builder
1014            .create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
1015                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
1016                (n * 8) as u32,
1017                3, // align to 8 bytes
1018            ));
1019
1020        // Store each element pointer.
1021        for (i, elem_var) in elems.iter().enumerate() {
1022            let val = self.use_var(*elem_var);
1023            self.builder.ins().stack_store(val, slot, (i * 8) as i32);
1024        }
1025
1026        // Get the stack slot address.
1027        let slot_addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
1028        let count = self.builder.ins().iconst(types::I64, n as i64);
1029
1030        let func_ref = self.import_func(func_id);
1031        // For maps, count is number of pairs (n/2).
1032        let actual_count = if func_id == self.rt.rt_alloc_map {
1033            self.builder.ins().iconst(types::I64, (n / 2) as i64)
1034        } else {
1035            count
1036        };
1037        let call = self
1038            .builder
1039            .ins()
1040            .call(func_ref, &[slot_addr, actual_count]);
1041        Ok(self.builder.inst_results(call)[0])
1042    }
1043
1044    /// Emit a region-aware collection allocation call.
1045    ///
1046    /// Like `emit_alloc_collection` but prepends the region handle as the first
1047    /// argument: `rt_region_alloc_*(handle, elems_ptr, count)`.
1048    fn emit_region_alloc_collection(
1049        &mut self,
1050        func_id: FuncId,
1051        region_handle: cranelift_codegen::ir::Value,
1052        elems: &[VarId],
1053    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1054        let n = elems.len();
1055        if n == 0 {
1056            let func_ref = self.import_func(func_id);
1057            let null = self.builder.ins().iconst(self.ptr_type, 0);
1058            let zero = self.builder.ins().iconst(types::I64, 0);
1059            let call = self
1060                .builder
1061                .ins()
1062                .call(func_ref, &[region_handle, null, zero]);
1063            return Ok(self.builder.inst_results(call)[0]);
1064        }
1065
1066        let slot = self
1067            .builder
1068            .create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
1069                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
1070                (n * 8) as u32,
1071                3,
1072            ));
1073
1074        for (i, elem_var) in elems.iter().enumerate() {
1075            let val = self.use_var(*elem_var);
1076            self.builder.ins().stack_store(val, slot, (i * 8) as i32);
1077        }
1078
1079        let slot_addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
1080        let func_ref = self.import_func(func_id);
1081        let actual_count = if func_id == self.rt.rt_region_alloc_map {
1082            self.builder.ins().iconst(types::I64, (n / 2) as i64)
1083        } else {
1084            self.builder.ins().iconst(types::I64, n as i64)
1085        };
1086        let call = self
1087            .builder
1088            .ins()
1089            .call(func_ref, &[region_handle, slot_addr, actual_count]);
1090        Ok(self.builder.inst_results(call)[0])
1091    }
1092
1093    /// Emit a call to a known function.
1094    fn emit_known_call(
1095        &mut self,
1096        known_fn: &KnownFn,
1097        args: &[VarId],
1098    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1099        // Collection constructors use variadic stack-spill pattern.
1100        match known_fn {
1101            KnownFn::Vector => return self.emit_alloc_collection(self.rt.rt_alloc_vector, args),
1102            KnownFn::HashMap => return self.emit_alloc_collection(self.rt.rt_alloc_map, args),
1103            KnownFn::HashSet => return self.emit_alloc_collection(self.rt.rt_alloc_set, args),
1104            KnownFn::List => return self.emit_alloc_collection(self.rt.rt_alloc_list, args),
1105            KnownFn::AtomSwap => return self.emit_atom_swap(args),
1106            KnownFn::WithBindings => return self.emit_with_bindings(args),
1107            KnownFn::Concat => return self.emit_alloc_collection(self.rt.rt_concat, args),
1108            KnownFn::Str if args.len() != 1 => {
1109                return self.emit_alloc_collection(self.rt.rt_str_n, args);
1110            }
1111            KnownFn::Println if args.len() != 1 => {
1112                return self.emit_alloc_collection(self.rt.rt_println_n, args);
1113            }
1114            KnownFn::Merge => return self.emit_alloc_collection(self.rt.rt_merge, args),
1115            KnownFn::Juxt => return self.emit_alloc_collection(self.rt.rt_juxt, args),
1116            KnownFn::Comp => return self.emit_alloc_collection(self.rt.rt_comp, args),
1117            KnownFn::Partial => return self.emit_alloc_collection(self.rt.rt_partial, args),
1118            _ => {}
1119        }
1120
1121        let rt_func = match known_fn {
1122            KnownFn::Add => self.rt.rt_add,
1123            KnownFn::Sub => self.rt.rt_sub,
1124            KnownFn::Mul => self.rt.rt_mul,
1125            KnownFn::Div => self.rt.rt_div,
1126            KnownFn::Rem => self.rt.rt_rem,
1127            KnownFn::Eq => self.rt.rt_eq,
1128            KnownFn::Lt => self.rt.rt_lt,
1129            KnownFn::Gt => self.rt.rt_gt,
1130            KnownFn::Lte => self.rt.rt_lte,
1131            KnownFn::Gte => self.rt.rt_gte,
1132            KnownFn::Get => self.rt.rt_get,
1133            KnownFn::Count => self.rt.rt_count,
1134            KnownFn::First => self.rt.rt_first,
1135            KnownFn::Rest | KnownFn::Next => self.rt.rt_rest,
1136            KnownFn::Assoc => self.rt.rt_assoc,
1137            KnownFn::Conj => self.rt.rt_conj,
1138            KnownFn::Deref | KnownFn::AtomDeref => self.rt.rt_deref,
1139            KnownFn::Println => self.rt.rt_println,
1140            KnownFn::Pr => self.rt.rt_pr,
1141            KnownFn::IsNil => self.rt.rt_is_nil,
1142            KnownFn::IsVector => self.rt.rt_is_vector,
1143            KnownFn::IsMap => self.rt.rt_is_map,
1144            KnownFn::IsSeq => self.rt.rt_is_seq,
1145            KnownFn::Identical => self.rt.rt_identical,
1146            KnownFn::Str => self.rt.rt_str,
1147            KnownFn::TryCatchFinally => self.rt.rt_try,
1148            KnownFn::Dissoc => self.rt.rt_dissoc,
1149            KnownFn::Disj => self.rt.rt_disj,
1150            KnownFn::Nth => self.rt.rt_nth,
1151            KnownFn::Contains => self.rt.rt_contains,
1152            KnownFn::Cons => self.rt.rt_alloc_cons,
1153            KnownFn::Seq => self.rt.rt_seq,
1154            KnownFn::LazySeq => self.rt.rt_lazy_seq,
1155            KnownFn::Transient => self.rt.rt_transient,
1156            KnownFn::AssocBang => self.rt.rt_assoc_bang,
1157            KnownFn::ConjBang => self.rt.rt_conj_bang,
1158            KnownFn::PersistentBang => self.rt.rt_persistent_bang,
1159            KnownFn::AtomReset => self.rt.rt_atom_reset,
1160            KnownFn::Apply => self.rt.rt_apply,
1161            KnownFn::SetBangVar => self.rt.rt_set_bang,
1162            KnownFn::Reduce2 => self.rt.rt_reduce2,
1163            KnownFn::Reduce3 => self.rt.rt_reduce3,
1164            KnownFn::Map => self.rt.rt_map,
1165            KnownFn::Filter => self.rt.rt_filter,
1166            KnownFn::Mapv => self.rt.rt_mapv,
1167            KnownFn::Filterv => self.rt.rt_filterv,
1168            KnownFn::Some => self.rt.rt_some,
1169            KnownFn::Every => self.rt.rt_every,
1170            KnownFn::Into => self.rt.rt_into,
1171            KnownFn::Into3 => self.rt.rt_into3,
1172            KnownFn::Range1 => self.rt.rt_range1,
1173            KnownFn::Range2 => self.rt.rt_range2,
1174            KnownFn::Range3 => self.rt.rt_range3,
1175            KnownFn::Take => self.rt.rt_take,
1176            KnownFn::Drop => self.rt.rt_drop,
1177            KnownFn::Reverse => self.rt.rt_reverse,
1178            KnownFn::Sort => self.rt.rt_sort,
1179            KnownFn::SortBy => self.rt.rt_sort_by,
1180            KnownFn::Keys => self.rt.rt_keys,
1181            KnownFn::Vals => self.rt.rt_vals,
1182            KnownFn::Update => self.rt.rt_update,
1183            KnownFn::GetIn => self.rt.rt_get_in,
1184            KnownFn::AssocIn => self.rt.rt_assoc_in,
1185            KnownFn::IsNumber => self.rt.rt_is_number,
1186            KnownFn::IsString => self.rt.rt_is_string,
1187            KnownFn::IsKeyword => self.rt.rt_is_keyword,
1188            KnownFn::IsSymbol => self.rt.rt_is_symbol,
1189            KnownFn::IsBool => self.rt.rt_is_bool,
1190            KnownFn::IsInt => self.rt.rt_is_int,
1191            KnownFn::Prn => self.rt.rt_prn,
1192            KnownFn::Print => self.rt.rt_print,
1193            KnownFn::Atom => self.rt.rt_atom,
1194            KnownFn::GroupBy => self.rt.rt_group_by,
1195            KnownFn::Partition2 => self.rt.rt_partition2,
1196            KnownFn::Partition3 => self.rt.rt_partition3,
1197            KnownFn::Partition4 => self.rt.rt_partition4,
1198            KnownFn::Frequencies => self.rt.rt_frequencies,
1199            KnownFn::Keep => self.rt.rt_keep,
1200            KnownFn::Remove => self.rt.rt_remove,
1201            KnownFn::MapIndexed => self.rt.rt_map_indexed,
1202            KnownFn::Zipmap => self.rt.rt_zipmap,
1203            KnownFn::Complement => self.rt.rt_complement,
1204            KnownFn::WithOutStr => self.rt.rt_with_out_str,
1205            KnownFn::Peek => self.rt.rt_peek,
1206            KnownFn::Pop => self.rt.rt_pop,
1207            KnownFn::Vec => self.rt.rt_vec,
1208            KnownFn::Mapcat => self.rt.rt_mapcat,
1209            KnownFn::IsEmpty => self.rt.rt_is_empty,
1210            KnownFn::Repeatedly => self.rt.rt_repeatedly,
1211            _ => {
1212                return self.emit_unknown_call_from_args(args);
1213            }
1214        };
1215
1216        // Call the specific runtime function with the right arity.
1217        let arg_vals: Vec<_> = args.iter().map(|a| self.use_var(*a)).collect();
1218        let func_ref = self.import_func(rt_func);
1219        let call = self.builder.ins().call(func_ref, &arg_vals);
1220        Ok(self.builder.inst_results(call)[0])
1221    }
1222
1223    /// Emit `(swap! atom f extra-args...)` — variadic via stack-spill.
1224    fn emit_atom_swap(&mut self, args: &[VarId]) -> CodegenResult<cranelift_codegen::ir::Value> {
1225        // args[0] = atom, args[1] = f, args[2..] = extra args
1226        let atom_val = self.use_var(args[0]);
1227        let f_val = self.use_var(args[1]);
1228        let extra = &args[2..];
1229        let n = extra.len();
1230
1231        let (extra_ptr, extra_count) = if n > 0 {
1232            let slot =
1233                self.builder
1234                    .create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
1235                        cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
1236                        (n * 8) as u32,
1237                        3,
1238                    ));
1239            for (i, arg) in extra.iter().enumerate() {
1240                let val = self.use_var(*arg);
1241                self.builder.ins().stack_store(val, slot, (i * 8) as i32);
1242            }
1243            let addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
1244            let count = self.builder.ins().iconst(types::I64, n as i64);
1245            (addr, count)
1246        } else {
1247            let null = self.builder.ins().iconst(self.ptr_type, 0);
1248            let zero = self.builder.ins().iconst(types::I64, 0);
1249            (null, zero)
1250        };
1251
1252        let func_ref = self.import_func(self.rt.rt_atom_swap);
1253        let call = self
1254            .builder
1255            .ins()
1256            .call(func_ref, &[atom_val, f_val, extra_ptr, extra_count]);
1257        Ok(self.builder.inst_results(call)[0])
1258    }
1259
1260    /// Emit `(binding [var val ...] body)` via rt_with_bindings.
1261    ///
1262    /// args layout: [var0, val0, var1, val1, ..., body_closure]
1263    fn emit_with_bindings(
1264        &mut self,
1265        args: &[VarId],
1266    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1267        // Last arg is the body closure, everything before is var/val pairs
1268        let body_var = *args.last().unwrap();
1269        let binding_args = &args[..args.len() - 1];
1270        let npairs = binding_args.len() / 2;
1271
1272        let body_val = self.use_var(body_var);
1273
1274        let (bindings_ptr, npairs_val) = if npairs > 0 {
1275            let n = binding_args.len();
1276            let slot =
1277                self.builder
1278                    .create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
1279                        cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
1280                        (n * 8) as u32,
1281                        3,
1282                    ));
1283            for (i, arg) in binding_args.iter().enumerate() {
1284                let val = self.use_var(*arg);
1285                self.builder.ins().stack_store(val, slot, (i * 8) as i32);
1286            }
1287            let addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
1288            let count = self.builder.ins().iconst(types::I64, npairs as i64);
1289            (addr, count)
1290        } else {
1291            let null = self.builder.ins().iconst(self.ptr_type, 0);
1292            let zero = self.builder.ins().iconst(types::I64, 0);
1293            (null, zero)
1294        };
1295
1296        let func_ref = self.import_func(self.rt.rt_with_bindings);
1297        let call = self
1298            .builder
1299            .ins()
1300            .call(func_ref, &[bindings_ptr, npairs_val, body_val]);
1301        Ok(self.builder.inst_results(call)[0])
1302    }
1303
1304    /// Emit a direct function call (bypasses rt_call dynamic dispatch).
1305    fn emit_direct_call(
1306        &mut self,
1307        fn_name: &str,
1308        args: &[VarId],
1309    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1310        let func_id = self.user_funcs.get(fn_name).ok_or_else(|| {
1311            CodegenError::Codegen(format!("CallDirect: unknown function {fn_name}"))
1312        })?;
1313        let func_ref = self.import_func(*func_id);
1314        let arg_vals: Vec<_> = args.iter().map(|a| self.use_var(*a)).collect();
1315        let call = self.builder.ins().call(func_ref, &arg_vals);
1316        Ok(self.builder.inst_results(call)[0])
1317    }
1318
1319    /// Emit an unknown function call through rt_call.
1320    fn emit_unknown_call(
1321        &mut self,
1322        callee: VarId,
1323        args: &[VarId],
1324    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1325        let callee_val = self.use_var(callee);
1326        let n = args.len();
1327
1328        if n == 0 {
1329            let null = self.builder.ins().iconst(self.ptr_type, 0);
1330            let zero = self.builder.ins().iconst(types::I64, 0);
1331            let func_ref = self.import_func(self.rt.rt_call);
1332            let call = self.builder.ins().call(func_ref, &[callee_val, null, zero]);
1333            return Ok(self.builder.inst_results(call)[0]);
1334        }
1335
1336        // Spill args to stack.
1337        let slot = self
1338            .builder
1339            .create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
1340                cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
1341                (n * 8) as u32,
1342                3,
1343            ));
1344        for (i, arg) in args.iter().enumerate() {
1345            let val = self.use_var(*arg);
1346            self.builder.ins().stack_store(val, slot, (i * 8) as i32);
1347        }
1348        let slot_addr = self.builder.ins().stack_addr(self.ptr_type, slot, 0);
1349        let count = self.builder.ins().iconst(types::I64, n as i64);
1350
1351        let func_ref = self.import_func(self.rt.rt_call);
1352        let call = self
1353            .builder
1354            .ins()
1355            .call(func_ref, &[callee_val, slot_addr, count]);
1356        Ok(self.builder.inst_results(call)[0])
1357    }
1358
1359    /// Fallback for known functions without a specific rt_ bridge.
1360    fn emit_unknown_call_from_args(
1361        &mut self,
1362        _args: &[VarId],
1363    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1364        // For now, return nil.  A real implementation would look up the
1365        // function by name and call through rt_call.
1366        self.call_rt_0(self.rt.rt_const_nil)
1367    }
1368
1369    // ── Call helpers ────────────────────────────────────────────────────────
1370
1371    fn import_func(&mut self, func_id: FuncId) -> cranelift_codegen::ir::FuncRef {
1372        self.module.declare_func_in_func(func_id, self.builder.func)
1373    }
1374
1375    fn call_rt_0(&mut self, func_id: FuncId) -> CodegenResult<cranelift_codegen::ir::Value> {
1376        let func_ref = self.import_func(func_id);
1377        let call = self.builder.ins().call(func_ref, &[]);
1378        Ok(self.builder.inst_results(call)[0])
1379    }
1380
1381    fn call_rt_1(
1382        &mut self,
1383        func_id: FuncId,
1384        arg: cranelift_codegen::ir::Value,
1385    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1386        let func_ref = self.import_func(func_id);
1387        let call = self.builder.ins().call(func_ref, &[arg]);
1388        Ok(self.builder.inst_results(call)[0])
1389    }
1390
1391    /// Call a runtime function that returns u8 (for truthiness).
1392    fn call_rt_1_i8(
1393        &mut self,
1394        func_id: FuncId,
1395        arg: cranelift_codegen::ir::Value,
1396    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1397        let func_ref = self.import_func(func_id);
1398        let call = self.builder.ins().call(func_ref, &[arg]);
1399        Ok(self.builder.inst_results(call)[0])
1400    }
1401
1402    fn call_rt_2(
1403        &mut self,
1404        func_id: FuncId,
1405        a: cranelift_codegen::ir::Value,
1406        b: cranelift_codegen::ir::Value,
1407    ) -> CodegenResult<cranelift_codegen::ir::Value> {
1408        let func_ref = self.import_func(func_id);
1409        let call = self.builder.ins().call(func_ref, &[a, b]);
1410        Ok(self.builder.inst_results(call)[0])
1411    }
1412}
1413
1414// ── Runtime function declaration ────────────────────────────────────────────
1415
1416/// Helper to declare a single runtime function.
1417fn declare_rt(
1418    module: &mut ObjectModule,
1419    name: &str,
1420    params: &[types::Type],
1421    ret: types::Type,
1422) -> CodegenResult<FuncId> {
1423    let mut sig = module.make_signature();
1424    sig.call_conv = CallConv::SystemV;
1425    for &t in params {
1426        sig.params.push(AbiParam::new(t));
1427    }
1428    sig.returns.push(AbiParam::new(ret));
1429    Ok(module.declare_function(name, Linkage::Import, &sig)?)
1430}
1431
1432/// Declare all runtime bridge functions and return cached FuncIds.
1433fn declare_runtime_funcs(
1434    module: &mut ObjectModule,
1435    ptr: types::Type,
1436) -> CodegenResult<RuntimeFuncs> {
1437    // Declare rt_safepoint: void -> void.  We declare it as returning ptr
1438    // (ignored) to keep the signature uniform with declare_rt.
1439    let rt_safepoint = {
1440        let mut sig = module.make_signature();
1441        sig.call_conv = CallConv::SystemV;
1442        module.declare_function("rt_safepoint", Linkage::Import, &sig)?
1443    };
1444
1445    Ok(RuntimeFuncs {
1446        rt_safepoint,
1447        rt_const_nil: declare_rt(module, "rt_const_nil", &[], ptr)?,
1448        rt_const_true: declare_rt(module, "rt_const_true", &[], ptr)?,
1449        rt_const_false: declare_rt(module, "rt_const_false", &[], ptr)?,
1450        rt_const_long: declare_rt(module, "rt_const_long", &[types::I64], ptr)?,
1451        rt_const_double: declare_rt(module, "rt_const_double", &[types::F64], ptr)?,
1452        rt_const_char: declare_rt(module, "rt_const_char", &[types::I32], ptr)?,
1453        rt_const_string: declare_rt(module, "rt_const_string", &[ptr, types::I64], ptr)?,
1454        rt_const_keyword: declare_rt(module, "rt_const_keyword", &[ptr, types::I64], ptr)?,
1455        rt_const_symbol: declare_rt(module, "rt_const_symbol", &[ptr, types::I64], ptr)?,
1456        rt_truthiness: declare_rt(module, "rt_truthiness", &[ptr], types::I8)?,
1457        rt_add: declare_rt(module, "rt_add", &[ptr, ptr], ptr)?,
1458        rt_sub: declare_rt(module, "rt_sub", &[ptr, ptr], ptr)?,
1459        rt_mul: declare_rt(module, "rt_mul", &[ptr, ptr], ptr)?,
1460        rt_div: declare_rt(module, "rt_div", &[ptr, ptr], ptr)?,
1461        rt_rem: declare_rt(module, "rt_rem", &[ptr, ptr], ptr)?,
1462        rt_eq: declare_rt(module, "rt_eq", &[ptr, ptr], ptr)?,
1463        rt_lt: declare_rt(module, "rt_lt", &[ptr, ptr], ptr)?,
1464        rt_gt: declare_rt(module, "rt_gt", &[ptr, ptr], ptr)?,
1465        rt_lte: declare_rt(module, "rt_lte", &[ptr, ptr], ptr)?,
1466        rt_gte: declare_rt(module, "rt_gte", &[ptr, ptr], ptr)?,
1467        rt_alloc_vector: declare_rt(module, "rt_alloc_vector", &[ptr, types::I64], ptr)?,
1468        rt_alloc_map: declare_rt(module, "rt_alloc_map", &[ptr, types::I64], ptr)?,
1469        rt_alloc_set: declare_rt(module, "rt_alloc_set", &[ptr, types::I64], ptr)?,
1470        rt_alloc_list: declare_rt(module, "rt_alloc_list", &[ptr, types::I64], ptr)?,
1471        rt_alloc_cons: declare_rt(module, "rt_alloc_cons", &[ptr, ptr], ptr)?,
1472        rt_get: declare_rt(module, "rt_get", &[ptr, ptr], ptr)?,
1473        rt_count: declare_rt(module, "rt_count", &[ptr], ptr)?,
1474        rt_first: declare_rt(module, "rt_first", &[ptr], ptr)?,
1475        rt_rest: declare_rt(module, "rt_rest", &[ptr], ptr)?,
1476        rt_assoc: declare_rt(module, "rt_assoc", &[ptr, ptr, ptr], ptr)?,
1477        rt_conj: declare_rt(module, "rt_conj", &[ptr, ptr], ptr)?,
1478        rt_call: declare_rt(module, "rt_call", &[ptr, ptr, types::I64], ptr)?,
1479        rt_deref: declare_rt(module, "rt_deref", &[ptr], ptr)?,
1480        rt_println: declare_rt(module, "rt_println", &[ptr], ptr)?,
1481        rt_pr: declare_rt(module, "rt_pr", &[ptr], ptr)?,
1482        rt_is_nil: declare_rt(module, "rt_is_nil", &[ptr], ptr)?,
1483        rt_is_vector: declare_rt(module, "rt_is_vector", &[ptr], ptr)?,
1484        rt_is_map: declare_rt(module, "rt_is_map", &[ptr], ptr)?,
1485        rt_is_seq: declare_rt(module, "rt_is_seq", &[ptr], ptr)?,
1486        rt_identical: declare_rt(module, "rt_identical", &[ptr, ptr], ptr)?,
1487        rt_str: declare_rt(module, "rt_str", &[ptr], ptr)?,
1488        rt_load_global: declare_rt(
1489            module,
1490            "rt_load_global",
1491            &[ptr, types::I64, ptr, types::I64],
1492            ptr,
1493        )?,
1494        rt_def_var: declare_rt(
1495            module,
1496            "rt_def_var",
1497            &[ptr, types::I64, ptr, types::I64, ptr],
1498            ptr,
1499        )?,
1500        rt_make_fn: declare_rt(
1501            module,
1502            "rt_make_fn",
1503            &[ptr, types::I64, ptr, types::I64, ptr, types::I64],
1504            ptr,
1505        )?,
1506        // rt_make_fn_variadic(name_ptr, name_len, fn_ptr, fixed_param_count, captures, ncaptures)
1507        rt_make_fn_variadic: declare_rt(
1508            module,
1509            "rt_make_fn_variadic",
1510            &[ptr, types::I64, ptr, types::I64, ptr, types::I64],
1511            ptr,
1512        )?,
1513        // rt_make_fn_multi(name_ptr, name_len, fn_ptrs, param_counts, is_variadic, n_arities, captures, ncaptures)
1514        rt_make_fn_multi: declare_rt(
1515            module,
1516            "rt_make_fn_multi",
1517            &[ptr, types::I64, ptr, ptr, ptr, types::I64, ptr, types::I64],
1518            ptr,
1519        )?,
1520        rt_throw: declare_rt(module, "rt_throw", &[ptr], ptr)?,
1521        rt_try: declare_rt(module, "rt_try", &[ptr, ptr, ptr], ptr)?,
1522        rt_dissoc: declare_rt(module, "rt_dissoc", &[ptr, ptr], ptr)?,
1523        rt_disj: declare_rt(module, "rt_disj", &[ptr, ptr], ptr)?,
1524        rt_nth: declare_rt(module, "rt_nth", &[ptr, ptr], ptr)?,
1525        rt_contains: declare_rt(module, "rt_contains", &[ptr, ptr], ptr)?,
1526        rt_seq: declare_rt(module, "rt_seq", &[ptr], ptr)?,
1527        rt_lazy_seq: declare_rt(module, "rt_lazy_seq", &[ptr], ptr)?,
1528        rt_transient: declare_rt(module, "rt_transient", &[ptr], ptr)?,
1529        rt_assoc_bang: declare_rt(module, "rt_assoc_bang", &[ptr, ptr, ptr], ptr)?,
1530        rt_conj_bang: declare_rt(module, "rt_conj_bang", &[ptr, ptr], ptr)?,
1531        rt_persistent_bang: declare_rt(module, "rt_persistent_bang", &[ptr], ptr)?,
1532        rt_atom_reset: declare_rt(module, "rt_atom_reset", &[ptr, ptr], ptr)?,
1533        rt_atom_swap: declare_rt(module, "rt_atom_swap", &[ptr, ptr, ptr, types::I64], ptr)?,
1534        rt_apply: declare_rt(module, "rt_apply", &[ptr, ptr], ptr)?,
1535        rt_set_bang: declare_rt(module, "rt_set_bang", &[ptr, ptr], ptr)?,
1536        rt_with_bindings: declare_rt(module, "rt_with_bindings", &[ptr, types::I64, ptr], ptr)?,
1537        rt_load_var: declare_rt(
1538            module,
1539            "rt_load_var",
1540            &[ptr, types::I64, ptr, types::I64],
1541            ptr,
1542        )?,
1543        rt_reduce2: declare_rt(module, "rt_reduce2", &[ptr, ptr], ptr)?,
1544        rt_reduce3: declare_rt(module, "rt_reduce3", &[ptr, ptr, ptr], ptr)?,
1545        rt_map: declare_rt(module, "rt_map", &[ptr, ptr], ptr)?,
1546        rt_filter: declare_rt(module, "rt_filter", &[ptr, ptr], ptr)?,
1547        rt_mapv: declare_rt(module, "rt_mapv", &[ptr, ptr], ptr)?,
1548        rt_filterv: declare_rt(module, "rt_filterv", &[ptr, ptr], ptr)?,
1549        rt_some: declare_rt(module, "rt_some", &[ptr, ptr], ptr)?,
1550        rt_every: declare_rt(module, "rt_every", &[ptr, ptr], ptr)?,
1551        rt_into: declare_rt(module, "rt_into", &[ptr, ptr], ptr)?,
1552        rt_into3: declare_rt(module, "rt_into3", &[ptr, ptr, ptr], ptr)?,
1553        rt_group_by: declare_rt(module, "rt_group_by", &[ptr, ptr], ptr)?,
1554        rt_partition2: declare_rt(module, "rt_partition2", &[ptr, ptr], ptr)?,
1555        rt_partition3: declare_rt(module, "rt_partition3", &[ptr, ptr, ptr], ptr)?,
1556        rt_partition4: declare_rt(module, "rt_partition4", &[ptr, ptr, ptr, ptr], ptr)?,
1557        rt_frequencies: declare_rt(module, "rt_frequencies", &[ptr], ptr)?,
1558        rt_keep: declare_rt(module, "rt_keep", &[ptr, ptr], ptr)?,
1559        rt_remove: declare_rt(module, "rt_remove", &[ptr, ptr], ptr)?,
1560        rt_map_indexed: declare_rt(module, "rt_map_indexed", &[ptr, ptr], ptr)?,
1561        rt_zipmap: declare_rt(module, "rt_zipmap", &[ptr, ptr], ptr)?,
1562        rt_juxt: declare_rt(module, "rt_juxt", &[ptr, types::I64], ptr)?,
1563        rt_comp: declare_rt(module, "rt_comp", &[ptr, types::I64], ptr)?,
1564        rt_partial: declare_rt(module, "rt_partial", &[ptr, types::I64], ptr)?,
1565        rt_complement: declare_rt(module, "rt_complement", &[ptr], ptr)?,
1566        rt_concat: declare_rt(module, "rt_concat", &[ptr, types::I64], ptr)?,
1567        rt_range1: declare_rt(module, "rt_range1", &[ptr], ptr)?,
1568        rt_range2: declare_rt(module, "rt_range2", &[ptr, ptr], ptr)?,
1569        rt_range3: declare_rt(module, "rt_range3", &[ptr, ptr, ptr], ptr)?,
1570        rt_take: declare_rt(module, "rt_take", &[ptr, ptr], ptr)?,
1571        rt_drop: declare_rt(module, "rt_drop", &[ptr, ptr], ptr)?,
1572        rt_reverse: declare_rt(module, "rt_reverse", &[ptr], ptr)?,
1573        rt_sort: declare_rt(module, "rt_sort", &[ptr], ptr)?,
1574        rt_sort_by: declare_rt(module, "rt_sort_by", &[ptr, ptr], ptr)?,
1575        rt_keys: declare_rt(module, "rt_keys", &[ptr], ptr)?,
1576        rt_vals: declare_rt(module, "rt_vals", &[ptr], ptr)?,
1577        rt_merge: declare_rt(module, "rt_merge", &[ptr, types::I64], ptr)?,
1578        rt_update: declare_rt(module, "rt_update", &[ptr, ptr, ptr], ptr)?,
1579        rt_get_in: declare_rt(module, "rt_get_in", &[ptr, ptr], ptr)?,
1580        rt_assoc_in: declare_rt(module, "rt_assoc_in", &[ptr, ptr, ptr], ptr)?,
1581        rt_is_number: declare_rt(module, "rt_is_number", &[ptr], ptr)?,
1582        rt_is_string: declare_rt(module, "rt_is_string", &[ptr], ptr)?,
1583        rt_is_keyword: declare_rt(module, "rt_is_keyword", &[ptr], ptr)?,
1584        rt_is_symbol: declare_rt(module, "rt_is_symbol", &[ptr], ptr)?,
1585        rt_is_bool: declare_rt(module, "rt_is_bool", &[ptr], ptr)?,
1586        rt_is_int: declare_rt(module, "rt_is_int", &[ptr], ptr)?,
1587        rt_prn: declare_rt(module, "rt_prn", &[ptr], ptr)?,
1588        rt_print: declare_rt(module, "rt_print", &[ptr], ptr)?,
1589        rt_atom: declare_rt(module, "rt_atom", &[ptr], ptr)?,
1590        rt_str_n: declare_rt(module, "rt_str_n", &[ptr, types::I64], ptr)?,
1591        rt_println_n: declare_rt(module, "rt_println_n", &[ptr, types::I64], ptr)?,
1592        rt_with_out_str: declare_rt(module, "rt_with_out_str", &[ptr], ptr)?,
1593        rt_peek: declare_rt(module, "rt_peek", &[ptr], ptr)?,
1594        rt_pop: declare_rt(module, "rt_pop", &[ptr], ptr)?,
1595        rt_vec: declare_rt(module, "rt_vec", &[ptr], ptr)?,
1596        rt_mapcat: declare_rt(module, "rt_mapcat", &[ptr, ptr], ptr)?,
1597        rt_is_empty: declare_rt(module, "rt_is_empty", &[ptr], ptr)?,
1598        rt_repeatedly: declare_rt(module, "rt_repeatedly", &[ptr, ptr], ptr)?,
1599        // Region allocation
1600        rt_region_start: declare_rt(module, "rt_region_start", &[], ptr)?,
1601        rt_region_end: declare_rt(module, "rt_region_end", &[ptr], ptr)?,
1602        rt_region_alloc_vector: declare_rt(
1603            module,
1604            "rt_region_alloc_vector",
1605            &[ptr, ptr, types::I64],
1606            ptr,
1607        )?,
1608        rt_region_alloc_map: declare_rt(
1609            module,
1610            "rt_region_alloc_map",
1611            &[ptr, ptr, types::I64],
1612            ptr,
1613        )?,
1614        rt_region_alloc_set: declare_rt(
1615            module,
1616            "rt_region_alloc_set",
1617            &[ptr, ptr, types::I64],
1618            ptr,
1619        )?,
1620        rt_region_alloc_list: declare_rt(
1621            module,
1622            "rt_region_alloc_list",
1623            &[ptr, ptr, types::I64],
1624            ptr,
1625        )?,
1626        rt_region_alloc_cons: declare_rt(module, "rt_region_alloc_cons", &[ptr, ptr, ptr], ptr)?,
1627    })
1628}
1629
1630// ── Tests ───────────────────────────────────────────────────────────────────
1631
1632#[cfg(test)]
1633mod tests {
1634    use super::*;
1635    use cljrs_reader::Parser;
1636
1637    fn parse_body(src: &str) -> Vec<cljrs_reader::Form> {
1638        let mut parser = Parser::new(src.to_string(), "<test>".to_string());
1639        let mut forms = Vec::new();
1640        while let Ok(Some(form)) = parser.parse_one() {
1641            forms.push(form);
1642        }
1643        forms
1644    }
1645
1646    fn lower(
1647        name: &str,
1648        params: &[Arc<str>],
1649        body: &[cljrs_reader::Form],
1650    ) -> crate::ir::IrFunction {
1651        // Run on a thread with a larger stack since Clojure eval is deeply recursive.
1652        let name = name.to_string();
1653        let params = params.to_vec();
1654        let body = body.to_vec();
1655        std::thread::Builder::new()
1656            .stack_size(8 * 1024 * 1024)
1657            .spawn(move || {
1658                let globals = cljrs_stdlib::standard_env();
1659                let mut env = cljrs_eval::Env::new(globals, "user");
1660                crate::aot::lower_via_rust(Some(&name), "user", &params, &body, &mut env).unwrap()
1661            })
1662            .unwrap()
1663            .join()
1664            .unwrap()
1665    }
1666
1667    #[test]
1668    fn test_compile_constant_function() {
1669        // (defn f [] 42)
1670        let body = parse_body("42");
1671        let ir = lower("f", &[], &body);
1672
1673        let mut compiler = Compiler::new().unwrap();
1674        let func_id = compiler.declare_function("f", 0).unwrap();
1675        compiler.compile_function(&ir, func_id).unwrap();
1676        let obj = compiler.finish();
1677        assert!(!obj.is_empty(), "should produce non-empty object code");
1678    }
1679
1680    #[test]
1681    fn test_compile_add_function() {
1682        // (defn add [a b] (+ a b))
1683        let body = parse_body("(+ a b)");
1684        let params: Vec<Arc<str>> = vec![Arc::from("a"), Arc::from("b")];
1685        let ir = lower("add", &params, &body);
1686
1687        let mut compiler = Compiler::new().unwrap();
1688        let func_id = compiler.declare_function("add", 2).unwrap();
1689        compiler.compile_function(&ir, func_id).unwrap();
1690        let obj = compiler.finish();
1691        assert!(!obj.is_empty());
1692    }
1693
1694    #[test]
1695    fn test_compile_if_expression() {
1696        // (defn f [x] (if x 1 2))
1697        let body = parse_body("(if x 1 2)");
1698        let params: Vec<Arc<str>> = vec![Arc::from("x")];
1699        let ir = lower("f", &params, &body);
1700
1701        let mut compiler = Compiler::new().unwrap();
1702        let func_id = compiler.declare_function("f", 1).unwrap();
1703        compiler.compile_function(&ir, func_id).unwrap();
1704        let obj = compiler.finish();
1705        assert!(!obj.is_empty());
1706    }
1707
1708    #[test]
1709    fn test_compile_let_expression() {
1710        // (defn f [x] (let [y (+ x 1)] y))
1711        let body = parse_body("(let [y (+ x 1)] y)");
1712        let params: Vec<Arc<str>> = vec![Arc::from("x")];
1713        let ir = lower("f", &params, &body);
1714
1715        let mut compiler = Compiler::new().unwrap();
1716        let func_id = compiler.declare_function("f", 1).unwrap();
1717        compiler.compile_function(&ir, func_id).unwrap();
1718        let obj = compiler.finish();
1719        assert!(!obj.is_empty());
1720    }
1721
1722    #[test]
1723    fn test_compile_loop_recur() {
1724        // (defn sum [n] (loop [i 0 acc 0] (if (= i n) acc (recur (+ i 1) (+ acc i)))))
1725        let body = parse_body("(loop [i 0 acc 0] (if (= i n) acc (recur (+ i 1) (+ acc i))))");
1726        let params: Vec<Arc<str>> = vec![Arc::from("n")];
1727        let ir = lower("sum", &params, &body);
1728
1729        let mut compiler = Compiler::new().unwrap();
1730        let func_id = compiler.declare_function("sum", 1).unwrap();
1731        compiler.compile_function(&ir, func_id).unwrap();
1732        let obj = compiler.finish();
1733        assert!(!obj.is_empty());
1734    }
1735}