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