Skip to main content

oxilean_codegen/c_backend/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::lcnf::*;
6use std::collections::{HashMap, HashSet};
7
8use super::types::{
9    CBAnalysisCache, CBConstantFoldingHelper, CBDepGraph, CBDominatorTree, CBLivenessInfo,
10    CBPassConfig, CBPassPhase, CBPassRegistry, CBPassStats, CBWorklist, CBackend, CBinOp,
11    CCodeWriter, CDecl, CEmitConfig, CEmitStats, CExpr, COutput, CStmt, CType, CUnaryOp,
12    ClosureInfo, StructLayout,
13};
14
15/// Emit a single C statement to the writer.
16pub(super) fn emit_stmt(w: &mut CCodeWriter, stmt: &CStmt) {
17    match stmt {
18        CStmt::VarDecl { ty, name, init } => {
19            if let Some(init_expr) = init {
20                w.writeln(&format!("{} {} = {};", ty, name, init_expr));
21            } else {
22                w.writeln(&format!("{} {};", ty, name));
23            }
24        }
25        CStmt::Assign(lhs, rhs) => {
26            w.writeln(&format!("{} = {};", lhs, rhs));
27        }
28        CStmt::If {
29            cond,
30            then_body,
31            else_body,
32        } => {
33            w.writeln(&format!("if ({}) {{", cond));
34            w.indent();
35            for s in then_body {
36                emit_stmt(w, s);
37            }
38            w.dedent();
39            if else_body.is_empty() {
40                w.writeln("}");
41            } else {
42                w.writeln("} else {");
43                w.indent();
44                for s in else_body {
45                    emit_stmt(w, s);
46                }
47                w.dedent();
48                w.writeln("}");
49            }
50        }
51        CStmt::Switch {
52            scrutinee,
53            cases,
54            default,
55        } => {
56            w.writeln(&format!("switch ({}) {{", scrutinee));
57            w.indent();
58            for (tag, body) in cases {
59                w.writeln(&format!("case {}:", tag));
60                w.indent();
61                for s in body {
62                    emit_stmt(w, s);
63                }
64                w.writeln("break;");
65                w.dedent();
66            }
67            if !default.is_empty() {
68                w.writeln("default:");
69                w.indent();
70                for s in default {
71                    emit_stmt(w, s);
72                }
73                w.writeln("break;");
74                w.dedent();
75            }
76            w.dedent();
77            w.writeln("}");
78        }
79        CStmt::While { cond, body } => {
80            w.writeln(&format!("while ({}) {{", cond));
81            w.indent();
82            for s in body {
83                emit_stmt(w, s);
84            }
85            w.dedent();
86            w.writeln("}");
87        }
88        CStmt::Return(expr) => {
89            if let Some(e) = expr {
90                w.writeln(&format!("return {};", e));
91            } else {
92                w.writeln("return;");
93            }
94        }
95        CStmt::Block(stmts) => {
96            w.writeln("{");
97            w.indent();
98            for s in stmts {
99                emit_stmt(w, s);
100            }
101            w.dedent();
102            w.writeln("}");
103        }
104        CStmt::Expr(e) => {
105            w.writeln(&format!("{};", e));
106        }
107        CStmt::Comment(text) => {
108            w.writeln(&format!("/* {} */", text));
109        }
110        CStmt::Blank => {
111            w.write_blank();
112        }
113        CStmt::Label(name) => {
114            let saved = w.indent_level;
115            w.indent_level = 0;
116            w.writeln(&format!("{}:", name));
117            w.indent_level = saved;
118        }
119        CStmt::Goto(name) => {
120            w.writeln(&format!("goto {};", name));
121        }
122        CStmt::Break => {
123            w.writeln("break;");
124        }
125    }
126}
127/// Emit a C declaration to the writer.
128pub(super) fn emit_decl(w: &mut CCodeWriter, decl: &CDecl) {
129    match decl {
130        CDecl::Function {
131            ret_type,
132            name,
133            params,
134            body,
135            is_static,
136        } => {
137            let static_kw = if *is_static { "static " } else { "" };
138            let params_str = format_params(params);
139            w.writeln(&format!(
140                "{}{} {}({}) {{",
141                static_kw, ret_type, name, params_str
142            ));
143            w.indent();
144            for s in body {
145                emit_stmt(w, s);
146            }
147            w.dedent();
148            w.writeln("}");
149            w.write_blank();
150        }
151        CDecl::Struct { name, fields } => {
152            w.writeln(&format!("typedef struct {} {{", name));
153            w.indent();
154            for (ty, fname) in fields {
155                w.writeln(&format!("{} {};", ty, fname));
156            }
157            w.dedent();
158            w.writeln(&format!("}} {};", name));
159            w.write_blank();
160        }
161        CDecl::Typedef { original, alias } => {
162            w.writeln(&format!("typedef {} {};", original, alias));
163        }
164        CDecl::Global {
165            ty,
166            name,
167            init,
168            is_static,
169        } => {
170            let static_kw = if *is_static { "static " } else { "" };
171            if let Some(init_expr) = init {
172                w.writeln(&format!("{}{} {} = {};", static_kw, ty, name, init_expr));
173            } else {
174                w.writeln(&format!("{}{} {};", static_kw, ty, name));
175            }
176        }
177        CDecl::ForwardDecl {
178            ret_type,
179            name,
180            params,
181        } => {
182            let params_str = format_params(params);
183            w.writeln(&format!("{} {}({});", ret_type, name, params_str));
184        }
185    }
186}
187/// Format a parameter list as a comma-separated string.
188pub(super) fn format_params(params: &[(CType, String)]) -> String {
189    if params.is_empty() {
190        return "void".to_string();
191    }
192    params
193        .iter()
194        .map(|(ty, name)| format!("{} {}", ty, name))
195        .collect::<Vec<_>>()
196        .join(", ")
197}
198/// Mangle an LCNF name into a valid C identifier.
199pub(super) fn mangle_name(name: &str) -> String {
200    let mut result = String::with_capacity(name.len() + 8);
201    result.push_str("_oxl_");
202    for c in name.chars() {
203        match c {
204            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => result.push(c),
205            '.' => result.push_str("__"),
206            '<' => result.push_str("_lt_"),
207            '>' => result.push_str("_gt_"),
208            ' ' => result.push('_'),
209            _ => {
210                result.push_str(&format!("_u{:04x}_", c as u32));
211            }
212        }
213    }
214    result
215}
216/// Convert an LCNF variable ID to a C variable name.
217pub(super) fn var_name(id: LcnfVarId) -> String {
218    format!("_x{}", id.0)
219}
220/// Map an LCNF type to a C type.
221pub(super) fn lcnf_type_to_ctype(ty: &LcnfType) -> CType {
222    match ty {
223        LcnfType::Erased | LcnfType::Irrelevant | LcnfType::Unit => CType::Void,
224        LcnfType::Nat => CType::SizeT,
225        LcnfType::LcnfString => CType::Ptr(Box::new(CType::Char)),
226        LcnfType::Object => CType::LeanObject,
227        LcnfType::Var(_) => CType::LeanObject,
228        LcnfType::Fun(params, ret) => {
229            let c_params: Vec<CType> = params.iter().map(lcnf_type_to_ctype).collect();
230            let c_ret = lcnf_type_to_ctype(ret);
231            CType::FnPtr(c_params, Box::new(c_ret))
232        }
233        LcnfType::Ctor(name, _) => CType::Ptr(Box::new(CType::Struct(mangle_name(name)))),
234    }
235}
236/// Determine whether an LCNF type is "scalar" (unboxed in C).
237pub(super) fn is_scalar_type(ty: &LcnfType) -> bool {
238    matches!(
239        ty,
240        LcnfType::Nat | LcnfType::Erased | LcnfType::Unit | LcnfType::Irrelevant
241    )
242}
243/// Determine whether a C type needs reference counting.
244pub(super) fn needs_rc(cty: &CType) -> bool {
245    matches!(cty, CType::LeanObject | CType::Ptr(_))
246}
247/// Generate a `lean_inc_ref(x)` call statement.
248pub(super) fn lean_inc_ref(var: &str) -> CStmt {
249    CStmt::Expr(CExpr::call("lean_inc_ref", vec![CExpr::var(var)]))
250}
251/// Generate a `lean_dec_ref(x)` call statement.
252pub(super) fn lean_dec_ref(var: &str) -> CStmt {
253    CStmt::Expr(CExpr::call("lean_dec_ref", vec![CExpr::var(var)]))
254}
255/// Generate a `lean_is_exclusive(x)` expression.
256pub(super) fn lean_is_exclusive(var: &str) -> CExpr {
257    CExpr::call("lean_is_exclusive", vec![CExpr::var(var)])
258}
259/// Generate a `lean_box(n)` expression (box a scalar).
260pub(super) fn lean_box(expr: CExpr) -> CExpr {
261    CExpr::call("lean_box", vec![expr])
262}
263/// Generate a `lean_unbox(obj)` expression (unbox to scalar).
264pub(super) fn lean_unbox(expr: CExpr) -> CExpr {
265    CExpr::call("lean_unbox", vec![expr])
266}
267/// Generate a `lean_alloc_ctor(tag, num_objs, scalar_sz)` call.
268pub(super) fn lean_alloc_ctor(tag: u32, num_objs: usize, scalar_sz: usize) -> CExpr {
269    CExpr::call(
270        "lean_alloc_ctor",
271        vec![
272            CExpr::UIntLit(tag as u64),
273            CExpr::UIntLit(num_objs as u64),
274            CExpr::UIntLit(scalar_sz as u64),
275        ],
276    )
277}
278/// Generate a `lean_ctor_get(obj, i)` expression.
279pub(super) fn lean_ctor_get(obj: &str, idx: usize) -> CExpr {
280    CExpr::call(
281        "lean_ctor_get",
282        vec![CExpr::var(obj), CExpr::UIntLit(idx as u64)],
283    )
284}
285/// Generate a `lean_ctor_set(obj, i, val)` statement.
286pub(super) fn lean_ctor_set(obj: &str, idx: usize, val: CExpr) -> CStmt {
287    CStmt::Expr(CExpr::call(
288        "lean_ctor_set",
289        vec![CExpr::var(obj), CExpr::UIntLit(idx as u64), val],
290    ))
291}
292/// Generate `lean_obj_tag(obj)` expression.
293pub(super) fn lean_obj_tag(obj: &str) -> CExpr {
294    CExpr::call("lean_obj_tag", vec![CExpr::var(obj)])
295}
296/// Generate a closure struct declaration.
297pub(super) fn gen_closure_struct(info: &ClosureInfo) -> CDecl {
298    let mut fields = vec![
299        (CType::LeanObject, "m_header".to_string()),
300        (
301            CType::FnPtr(vec![], Box::new(CType::LeanObject)),
302            info.fn_ptr_field.clone(),
303        ),
304        (CType::U8, "m_arity".to_string()),
305        (CType::U8, "m_num_fixed".to_string()),
306    ];
307    for (fname, fty) in &info.env_fields {
308        fields.push((fty.clone(), fname.clone()));
309    }
310    CDecl::Struct {
311        name: info.struct_name.clone(),
312        fields,
313    }
314}
315/// Generate code to allocate and initialize a closure.
316pub(super) fn gen_closure_create(
317    info: &ClosureInfo,
318    fn_name: &str,
319    env_vars: &[String],
320) -> Vec<CStmt> {
321    let mut stmts = Vec::new();
322    let closure_var = format!("_closure_{}", info.struct_name);
323    stmts.push(CStmt::VarDecl {
324        ty: CType::LeanObject,
325        name: closure_var.clone(),
326        init: Some(CExpr::call(
327            "lean_alloc_closure",
328            vec![
329                CExpr::Cast(
330                    CType::Ptr(Box::new(CType::Void)),
331                    Box::new(CExpr::var(fn_name)),
332                ),
333                CExpr::UIntLit(info.arity as u64),
334                CExpr::UIntLit(env_vars.len() as u64),
335            ],
336        )),
337    });
338    for (i, env_var) in env_vars.iter().enumerate() {
339        stmts.push(CStmt::Expr(CExpr::call(
340            "lean_closure_set",
341            vec![
342                CExpr::var(&closure_var),
343                CExpr::UIntLit(i as u64),
344                CExpr::var(env_var),
345            ],
346        )));
347    }
348    stmts
349}
350/// Generate code to apply a closure to arguments.
351pub(super) fn gen_closure_apply(closure_var: &str, args: &[CExpr], result_var: &str) -> Vec<CStmt> {
352    let mut stmts = Vec::new();
353    match args.len() {
354        0 => {
355            stmts.push(CStmt::VarDecl {
356                ty: CType::LeanObject,
357                name: result_var.to_string(),
358                init: Some(CExpr::call(
359                    "lean_apply_1",
360                    vec![
361                        CExpr::var(closure_var),
362                        CExpr::call("lean_box", vec![CExpr::UIntLit(0)]),
363                    ],
364                )),
365            });
366        }
367        1 => {
368            stmts.push(CStmt::VarDecl {
369                ty: CType::LeanObject,
370                name: result_var.to_string(),
371                init: Some(CExpr::call(
372                    "lean_apply_1",
373                    vec![CExpr::var(closure_var), args[0].clone()],
374                )),
375            });
376        }
377        2 => {
378            stmts.push(CStmt::VarDecl {
379                ty: CType::LeanObject,
380                name: result_var.to_string(),
381                init: Some(CExpr::call(
382                    "lean_apply_2",
383                    vec![CExpr::var(closure_var), args[0].clone(), args[1].clone()],
384                )),
385            });
386        }
387        _ => {
388            let mut current = CExpr::var(closure_var);
389            for (i, arg) in args.iter().enumerate() {
390                let tmp = if i == args.len() - 1 {
391                    result_var.to_string()
392                } else {
393                    format!("_app_tmp_{}", i)
394                };
395                stmts.push(CStmt::VarDecl {
396                    ty: CType::LeanObject,
397                    name: tmp.clone(),
398                    init: Some(CExpr::call("lean_apply_1", vec![current, arg.clone()])),
399                });
400                current = CExpr::var(&tmp);
401            }
402        }
403    }
404    stmts
405}
406/// Generate a standard C header preamble with runtime includes.
407pub(super) fn generate_header_preamble(module_name: &str) -> String {
408    let guard = module_name.to_uppercase().replace('.', "_");
409    format!(
410        "#ifndef {guard}_H\n\
411         #define {guard}_H\n\
412         \n\
413         #include <stdint.h>\n\
414         #include <stddef.h>\n\
415         #include <stdbool.h>\n\
416         #include \"lean_runtime.h\"\n\
417         \n",
418    )
419}
420/// Generate a standard C header epilogue.
421pub(super) fn generate_header_epilogue(module_name: &str) -> String {
422    let guard = module_name.to_uppercase().replace('.', "_");
423    format!("\n#endif /* {guard}_H */\n")
424}
425/// Generate the standard C source preamble with includes.
426pub(super) fn generate_source_preamble(module_name: &str) -> String {
427    format!(
428        "#include \"{module_name}.h\"\n\
429         \n\
430         /* Generated by OxiLean C backend */\n\
431         \n",
432    )
433}
434/// Convenience function: compile an LCNF module to C source code.
435pub fn compile_to_c(module: &LcnfModule, config: CEmitConfig) -> COutput {
436    let mut backend = CBackend::new(config);
437    backend.emit_module(module)
438}
439/// Convenience function: compile with default configuration.
440pub fn compile_to_c_default(module: &LcnfModule) -> COutput {
441    compile_to_c(module, CEmitConfig::default())
442}
443/// Compute the size in bytes of a C type (for layout purposes).
444pub(super) fn c_type_size(ty: &CType) -> usize {
445    match ty {
446        CType::Void => 0,
447        CType::Bool | CType::Char | CType::U8 => 1,
448        CType::Int | CType::UInt | CType::SizeT | CType::LeanObject => 8,
449        CType::Ptr(_) => 8,
450        CType::FnPtr(_, _) => 8,
451        CType::Array(elem, count) => c_type_size(elem) * count,
452        CType::Struct(_) => 8,
453    }
454}
455/// Compute the alignment of a C type.
456pub(super) fn c_type_align(ty: &CType) -> usize {
457    match ty {
458        CType::Void => 1,
459        CType::Bool | CType::Char | CType::U8 => 1,
460        CType::Int | CType::UInt | CType::SizeT | CType::LeanObject => 8,
461        CType::Ptr(_) => 8,
462        CType::FnPtr(_, _) => 8,
463        CType::Array(elem, _) => c_type_align(elem),
464        CType::Struct(_) => 8,
465    }
466}
467/// Compute the layout of a struct given its fields.
468pub(super) fn compute_struct_layout(name: &str, fields: &[(CType, String)]) -> StructLayout {
469    let mut offset = 0usize;
470    let mut max_align = 1usize;
471    let mut layout_fields = Vec::new();
472    for (ty, fname) in fields {
473        let align = c_type_align(ty);
474        let size = c_type_size(ty);
475        max_align = max_align.max(align);
476        let padding = (align - (offset % align)) % align;
477        offset += padding;
478        layout_fields.push((fname.clone(), ty.clone(), offset));
479        offset += size;
480    }
481    let final_padding = (max_align - (offset % max_align)) % max_align;
482    offset += final_padding;
483    StructLayout {
484        name: name.to_string(),
485        fields: layout_fields,
486        total_size: offset,
487        alignment: max_align,
488    }
489}
490#[cfg(test)]
491mod tests {
492    use super::*;
493    pub(super) fn vid(n: u64) -> LcnfVarId {
494        LcnfVarId(n)
495    }
496    pub(super) fn mk_param(n: u64, name: &str) -> LcnfParam {
497        LcnfParam {
498            id: vid(n),
499            name: name.to_string(),
500            ty: LcnfType::Nat,
501            erased: false,
502            borrowed: false,
503        }
504    }
505    pub(super) fn mk_fun_decl(name: &str, body: LcnfExpr) -> LcnfFunDecl {
506        LcnfFunDecl {
507            name: name.to_string(),
508            original_name: None,
509            params: vec![mk_param(0, "n")],
510            ret_type: LcnfType::Nat,
511            body,
512            is_recursive: false,
513            is_lifted: false,
514            inline_cost: 1,
515        }
516    }
517    #[test]
518    pub(super) fn test_ctype_display() {
519        assert_eq!(CType::Void.to_string(), "void");
520        assert_eq!(CType::Int.to_string(), "int64_t");
521        assert_eq!(CType::UInt.to_string(), "uint64_t");
522        assert_eq!(CType::Bool.to_string(), "uint8_t");
523        assert_eq!(CType::SizeT.to_string(), "size_t");
524        assert_eq!(CType::LeanObject.to_string(), "lean_object*");
525    }
526    #[test]
527    pub(super) fn test_ctype_ptr_display() {
528        let ptr = CType::Ptr(Box::new(CType::Int));
529        assert_eq!(ptr.to_string(), "int64_t*");
530    }
531    #[test]
532    pub(super) fn test_cbinop_display() {
533        assert_eq!(CBinOp::Add.to_string(), "+");
534        assert_eq!(CBinOp::Eq.to_string(), "==");
535        assert_eq!(CBinOp::And.to_string(), "&&");
536    }
537    #[test]
538    pub(super) fn test_cunaryop_display() {
539        assert_eq!(CUnaryOp::Neg.to_string(), "-");
540        assert_eq!(CUnaryOp::Not.to_string(), "!");
541    }
542    #[test]
543    pub(super) fn test_cexpr_var() {
544        let e = CExpr::Var("x".to_string());
545        assert_eq!(e.to_string(), "x");
546    }
547    #[test]
548    pub(super) fn test_cexpr_call() {
549        let e = CExpr::call("f", vec![CExpr::var("x"), CExpr::IntLit(42)]);
550        assert_eq!(e.to_string(), "f(x, 42LL)");
551    }
552    #[test]
553    pub(super) fn test_cexpr_binop() {
554        let e = CExpr::binop(CBinOp::Add, CExpr::var("a"), CExpr::var("b"));
555        assert_eq!(e.to_string(), "(a + b)");
556    }
557    #[test]
558    pub(super) fn test_mangle_name() {
559        assert_eq!(mangle_name("Nat.add"), "_oxl_Nat__add");
560        assert_eq!(mangle_name("foo"), "_oxl_foo");
561    }
562    #[test]
563    pub(super) fn test_var_name() {
564        assert_eq!(var_name(LcnfVarId(42)), "_x42");
565    }
566    #[test]
567    pub(super) fn test_lcnf_type_to_ctype() {
568        assert_eq!(lcnf_type_to_ctype(&LcnfType::Nat), CType::SizeT);
569        assert_eq!(lcnf_type_to_ctype(&LcnfType::Object), CType::LeanObject);
570        assert_eq!(lcnf_type_to_ctype(&LcnfType::Unit), CType::Void);
571    }
572    #[test]
573    pub(super) fn test_is_scalar_type() {
574        assert!(is_scalar_type(&LcnfType::Nat));
575        assert!(is_scalar_type(&LcnfType::Unit));
576        assert!(!is_scalar_type(&LcnfType::Object));
577        assert!(!is_scalar_type(&LcnfType::Ctor("List".into(), vec![])));
578    }
579    #[test]
580    pub(super) fn test_emit_simple_function() {
581        let body = LcnfExpr::Return(LcnfArg::Var(vid(0)));
582        let decl = mk_fun_decl("identity", body);
583        let mut backend = CBackend::default_backend();
584        let c_decl = backend.emit_fun_decl(&decl);
585        if let CDecl::Function { name, body, .. } = &c_decl {
586            assert!(name.contains("identity"));
587            assert!(body.iter().any(|s| matches!(s, CStmt::Return(_))));
588        } else {
589            panic!("expected Function declaration");
590        }
591    }
592    #[test]
593    pub(super) fn test_emit_case_expression() {
594        let body = LcnfExpr::Case {
595            scrutinee: vid(0),
596            scrutinee_ty: LcnfType::Ctor("Bool".into(), vec![]),
597            alts: vec![
598                LcnfAlt {
599                    ctor_name: "False".into(),
600                    ctor_tag: 0,
601                    params: vec![],
602                    body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))),
603                },
604                LcnfAlt {
605                    ctor_name: "True".into(),
606                    ctor_tag: 1,
607                    params: vec![],
608                    body: LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(1))),
609                },
610            ],
611            default: None,
612        };
613        let decl = mk_fun_decl("to_nat", body);
614        let mut backend = CBackend::default_backend();
615        let c_decl = backend.emit_fun_decl(&decl);
616        if let CDecl::Function { body, .. } = &c_decl {
617            let has_switch = body.iter().any(|s| matches!(s, CStmt::Switch { .. }));
618            assert!(has_switch, "expected a switch statement in the body");
619        } else {
620            panic!("expected Function");
621        }
622    }
623    #[test]
624    pub(super) fn test_emit_rc_calls() {
625        let body = LcnfExpr::Let {
626            id: vid(1),
627            name: "result".to_string(),
628            ty: LcnfType::Ctor("Pair".into(), vec![]),
629            value: LcnfLetValue::Ctor(
630                "Pair".into(),
631                0,
632                vec![LcnfArg::Var(vid(0)), LcnfArg::Var(vid(0))],
633            ),
634            body: Box::new(LcnfExpr::Return(LcnfArg::Var(vid(1)))),
635        };
636        let decl = mk_fun_decl("mk_pair", body);
637        let mut backend = CBackend::new(CEmitConfig {
638            use_rc: true,
639            ..CEmitConfig::default()
640        });
641        let _c_decl = backend.emit_fun_decl(&decl);
642    }
643    #[test]
644    pub(super) fn test_emit_module() {
645        let decl = mk_fun_decl("main", LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0))));
646        let module = LcnfModule {
647            fun_decls: vec![decl],
648            extern_decls: vec![],
649            name: "test".to_string(),
650            metadata: LcnfModuleMetadata::default(),
651        };
652        let mut backend = CBackend::default_backend();
653        let output = backend.emit_module(&module);
654        assert!(!output.header.is_empty());
655        assert!(!output.source.is_empty());
656        assert!(output.header.contains("#ifndef"));
657        assert!(output.header.contains("#endif"));
658    }
659    #[test]
660    pub(super) fn test_c_emit_config_default() {
661        let cfg = CEmitConfig::default();
662        assert!(cfg.emit_comments);
663        assert!(cfg.inline_small);
664        assert!(cfg.use_rc);
665    }
666    #[test]
667    pub(super) fn test_c_emit_stats_display() {
668        let stats = CEmitStats {
669            functions_emitted: 5,
670            structs_emitted: 2,
671            ..Default::default()
672        };
673        let s = stats.to_string();
674        assert!(s.contains("fns=5"));
675        assert!(s.contains("structs=2"));
676    }
677    #[test]
678    pub(super) fn test_struct_layout() {
679        let fields = vec![
680            (CType::U8, "tag".to_string()),
681            (CType::UInt, "value".to_string()),
682        ];
683        let layout = compute_struct_layout("TestStruct", &fields);
684        assert!(layout.total_size > 0);
685        assert_eq!(layout.alignment, 8);
686        assert_eq!(layout.fields.len(), 2);
687    }
688    #[test]
689    pub(super) fn test_format_params() {
690        let params = vec![
691            (CType::Int, "x".to_string()),
692            (CType::Bool, "flag".to_string()),
693        ];
694        assert_eq!(format_params(&params), "int64_t x, uint8_t flag");
695        assert_eq!(format_params(&[]), "void");
696    }
697    #[test]
698    pub(super) fn test_compile_to_c_default() {
699        let module = LcnfModule {
700            fun_decls: vec![mk_fun_decl(
701                "test_fn",
702                LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(42))),
703            )],
704            extern_decls: vec![],
705            name: "test_mod".to_string(),
706            metadata: LcnfModuleMetadata::default(),
707        };
708        let output = compile_to_c_default(&module);
709        assert!(!output.source.is_empty());
710        assert!(!output.declarations.is_empty());
711    }
712    #[test]
713    pub(super) fn test_closure_struct_generation() {
714        let info = ClosureInfo {
715            struct_name: "Closure_add".to_string(),
716            fn_ptr_field: "fn_ptr".to_string(),
717            env_fields: vec![("captured_x".to_string(), CType::LeanObject)],
718            arity: 1,
719        };
720        let decl = gen_closure_struct(&info);
721        if let CDecl::Struct { name, fields } = &decl {
722            assert_eq!(name, "Closure_add");
723            assert!(fields.len() >= 4);
724        } else {
725            panic!("expected Struct declaration");
726        }
727    }
728    #[test]
729    pub(super) fn test_emit_let_chain() {
730        let body = LcnfExpr::Let {
731            id: vid(1),
732            name: "a".to_string(),
733            ty: LcnfType::Nat,
734            value: LcnfLetValue::Lit(LcnfLit::Nat(42)),
735            body: Box::new(LcnfExpr::Let {
736                id: vid(2),
737                name: "b".to_string(),
738                ty: LcnfType::Nat,
739                value: LcnfLetValue::App(LcnfArg::Var(vid(99)), vec![LcnfArg::Var(vid(1))]),
740                body: Box::new(LcnfExpr::Return(LcnfArg::Var(vid(2)))),
741            }),
742        };
743        let decl = mk_fun_decl("chain", body);
744        let mut backend = CBackend::default_backend();
745        let c_decl = backend.emit_fun_decl(&decl);
746        if let CDecl::Function { body, .. } = &c_decl {
747            let var_decl_count = body
748                .iter()
749                .filter(|s| matches!(s, CStmt::VarDecl { .. }))
750                .count();
751            assert!(var_decl_count >= 2);
752        } else {
753            panic!("expected Function");
754        }
755    }
756    #[test]
757    pub(super) fn test_needs_rc() {
758        assert!(needs_rc(&CType::LeanObject));
759        assert!(needs_rc(&CType::Ptr(Box::new(CType::Int))));
760        assert!(!needs_rc(&CType::Int));
761        assert!(!needs_rc(&CType::SizeT));
762        assert!(!needs_rc(&CType::Void));
763    }
764    #[test]
765    pub(super) fn test_c_type_size() {
766        assert_eq!(c_type_size(&CType::Void), 0);
767        assert_eq!(c_type_size(&CType::U8), 1);
768        assert_eq!(c_type_size(&CType::Int), 8);
769        assert_eq!(c_type_size(&CType::UInt), 8);
770        assert_eq!(c_type_size(&CType::Ptr(Box::new(CType::Int))), 8);
771    }
772    #[test]
773    pub(super) fn test_cexpr_string_lit() {
774        let e = CExpr::StringLit("hello world".to_string());
775        assert_eq!(e.to_string(), "\"hello world\"");
776    }
777    #[test]
778    pub(super) fn test_cexpr_null() {
779        assert_eq!(CExpr::Null.to_string(), "NULL");
780    }
781    #[test]
782    pub(super) fn test_cstmt_comment() {
783        let mut w = CCodeWriter::new("  ");
784        emit_stmt(&mut w, &CStmt::Comment("test comment".to_string()));
785        assert!(w.result().contains("/* test comment */"));
786    }
787    #[test]
788    pub(super) fn test_emit_tail_call() {
789        let body = LcnfExpr::TailCall(
790            LcnfArg::Var(vid(99)),
791            vec![LcnfArg::Var(vid(0)), LcnfArg::Lit(LcnfLit::Nat(1))],
792        );
793        let decl = mk_fun_decl("rec_fn", body);
794        let mut backend = CBackend::default_backend();
795        let c_decl = backend.emit_fun_decl(&decl);
796        if let CDecl::Function { body, .. } = &c_decl {
797            assert!(body.iter().any(|s| matches!(s, CStmt::Return(_))));
798        } else {
799            panic!("expected Function");
800        }
801    }
802    #[test]
803    pub(super) fn test_emit_unreachable() {
804        let body = LcnfExpr::Unreachable;
805        let decl = mk_fun_decl("unreachable_fn", body);
806        let mut backend = CBackend::default_backend();
807        let c_decl = backend.emit_fun_decl(&decl);
808        if let CDecl::Function { body, .. } = &c_decl {
809            let has_panic = body.iter().any(|s| {
810                if let CStmt::Expr(CExpr::Call(name, _)) = s {
811                    name.contains("panic")
812                } else {
813                    false
814                }
815            });
816            assert!(has_panic, "expected panic call for unreachable");
817        } else {
818            panic!("expected Function");
819        }
820    }
821}
822#[cfg(test)]
823mod CB_infra_tests {
824    use super::*;
825    #[test]
826    pub(super) fn test_pass_config() {
827        let config = CBPassConfig::new("test_pass", CBPassPhase::Transformation);
828        assert!(config.enabled);
829        assert!(config.phase.is_modifying());
830        assert_eq!(config.phase.name(), "transformation");
831    }
832    #[test]
833    pub(super) fn test_pass_stats() {
834        let mut stats = CBPassStats::new();
835        stats.record_run(10, 100, 3);
836        stats.record_run(20, 200, 5);
837        assert_eq!(stats.total_runs, 2);
838        assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
839        assert!((stats.success_rate() - 1.0).abs() < 0.01);
840        let s = stats.format_summary();
841        assert!(s.contains("Runs: 2/2"));
842    }
843    #[test]
844    pub(super) fn test_pass_registry() {
845        let mut reg = CBPassRegistry::new();
846        reg.register(CBPassConfig::new("pass_a", CBPassPhase::Analysis));
847        reg.register(CBPassConfig::new("pass_b", CBPassPhase::Transformation).disabled());
848        assert_eq!(reg.total_passes(), 2);
849        assert_eq!(reg.enabled_count(), 1);
850        reg.update_stats("pass_a", 5, 50, 2);
851        let stats = reg.get_stats("pass_a").expect("stats should exist");
852        assert_eq!(stats.total_changes, 5);
853    }
854    #[test]
855    pub(super) fn test_analysis_cache() {
856        let mut cache = CBAnalysisCache::new(10);
857        cache.insert("key1".to_string(), vec![1, 2, 3]);
858        assert!(cache.get("key1").is_some());
859        assert!(cache.get("key2").is_none());
860        assert!((cache.hit_rate() - 0.5).abs() < 0.01);
861        cache.invalidate("key1");
862        assert!(!cache.entries["key1"].valid);
863        assert_eq!(cache.size(), 1);
864    }
865    #[test]
866    pub(super) fn test_worklist() {
867        let mut wl = CBWorklist::new();
868        assert!(wl.push(1));
869        assert!(wl.push(2));
870        assert!(!wl.push(1));
871        assert_eq!(wl.len(), 2);
872        assert_eq!(wl.pop(), Some(1));
873        assert!(!wl.contains(1));
874        assert!(wl.contains(2));
875    }
876    #[test]
877    pub(super) fn test_dominator_tree() {
878        let mut dt = CBDominatorTree::new(5);
879        dt.set_idom(1, 0);
880        dt.set_idom(2, 0);
881        dt.set_idom(3, 1);
882        assert!(dt.dominates(0, 3));
883        assert!(dt.dominates(1, 3));
884        assert!(!dt.dominates(2, 3));
885        assert!(dt.dominates(3, 3));
886    }
887    #[test]
888    pub(super) fn test_liveness() {
889        let mut liveness = CBLivenessInfo::new(3);
890        liveness.add_def(0, 1);
891        liveness.add_use(1, 1);
892        assert!(liveness.defs[0].contains(&1));
893        assert!(liveness.uses[1].contains(&1));
894    }
895    #[test]
896    pub(super) fn test_constant_folding() {
897        assert_eq!(CBConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
898        assert_eq!(CBConstantFoldingHelper::fold_div_i64(10, 0), None);
899        assert_eq!(CBConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
900        assert_eq!(
901            CBConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
902            0b1000
903        );
904        assert_eq!(CBConstantFoldingHelper::fold_bitnot_i64(0), -1);
905    }
906    #[test]
907    pub(super) fn test_dep_graph() {
908        let mut g = CBDepGraph::new();
909        g.add_dep(1, 2);
910        g.add_dep(2, 3);
911        g.add_dep(1, 3);
912        assert_eq!(g.dependencies_of(2), vec![1]);
913        let topo = g.topological_sort();
914        assert_eq!(topo.len(), 3);
915        assert!(!g.has_cycle());
916        let pos: std::collections::HashMap<u32, usize> =
917            topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
918        assert!(pos[&1] < pos[&2]);
919        assert!(pos[&1] < pos[&3]);
920        assert!(pos[&2] < pos[&3]);
921    }
922}