1use 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
15pub(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}
127pub(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}
187pub(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}
198pub(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}
216pub(super) fn var_name(id: LcnfVarId) -> String {
218 format!("_x{}", id.0)
219}
220pub(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}
237pub(super) fn is_scalar_type(ty: &LcnfType) -> bool {
239 matches!(
240 ty,
241 LcnfType::Nat | LcnfType::Erased | LcnfType::Unit | LcnfType::Irrelevant
242 )
243}
244pub(super) fn needs_rc(cty: &CType) -> bool {
246 matches!(cty, CType::LeanObject | CType::Ptr(_))
247}
248pub(super) fn lean_inc_ref(var: &str) -> CStmt {
250 CStmt::Expr(CExpr::call("lean_inc_ref", vec![CExpr::var(var)]))
251}
252pub(super) fn lean_dec_ref(var: &str) -> CStmt {
254 CStmt::Expr(CExpr::call("lean_dec_ref", vec![CExpr::var(var)]))
255}
256pub(super) fn lean_is_exclusive(var: &str) -> CExpr {
258 CExpr::call("lean_is_exclusive", vec![CExpr::var(var)])
259}
260pub(super) fn lean_box(expr: CExpr) -> CExpr {
262 CExpr::call("lean_box", vec![expr])
263}
264pub(super) fn lean_unbox(expr: CExpr) -> CExpr {
266 CExpr::call("lean_unbox", vec![expr])
267}
268pub(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}
279pub(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}
286pub(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}
293pub(super) fn lean_obj_tag(obj: &str) -> CExpr {
295 CExpr::call("lean_obj_tag", vec![CExpr::var(obj)])
296}
297pub(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}
316pub(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}
351pub(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}
407pub(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}
421pub(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}
426pub(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}
435pub fn compile_to_c(module: &LcnfModule, config: CEmitConfig) -> COutput {
437 let mut backend = CBackend::new(config);
438 backend.emit_module(module)
439}
440pub fn compile_to_c_default(module: &LcnfModule) -> COutput {
442 compile_to_c(module, CEmitConfig::default())
443}
444pub(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}
456pub(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}
468pub(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(¶ms), "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}