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