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::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}
236pub(super) fn is_scalar_type(ty: &LcnfType) -> bool {
238 matches!(
239 ty,
240 LcnfType::Nat | LcnfType::Erased | LcnfType::Unit | LcnfType::Irrelevant
241 )
242}
243pub(super) fn needs_rc(cty: &CType) -> bool {
245 matches!(cty, CType::LeanObject | CType::Ptr(_))
246}
247pub(super) fn lean_inc_ref(var: &str) -> CStmt {
249 CStmt::Expr(CExpr::call("lean_inc_ref", vec![CExpr::var(var)]))
250}
251pub(super) fn lean_dec_ref(var: &str) -> CStmt {
253 CStmt::Expr(CExpr::call("lean_dec_ref", vec![CExpr::var(var)]))
254}
255pub(super) fn lean_is_exclusive(var: &str) -> CExpr {
257 CExpr::call("lean_is_exclusive", vec![CExpr::var(var)])
258}
259pub(super) fn lean_box(expr: CExpr) -> CExpr {
261 CExpr::call("lean_box", vec![expr])
262}
263pub(super) fn lean_unbox(expr: CExpr) -> CExpr {
265 CExpr::call("lean_unbox", vec![expr])
266}
267pub(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}
278pub(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}
285pub(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}
292pub(super) fn lean_obj_tag(obj: &str) -> CExpr {
294 CExpr::call("lean_obj_tag", vec![CExpr::var(obj)])
295}
296pub(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}
315pub(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}
350pub(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}
406pub(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}
420pub(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}
425pub(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}
434pub fn compile_to_c(module: &LcnfModule, config: CEmitConfig) -> COutput {
436 let mut backend = CBackend::new(config);
437 backend.emit_module(module)
438}
439pub fn compile_to_c_default(module: &LcnfModule) -> COutput {
441 compile_to_c(module, CEmitConfig::default())
442}
443pub(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}
455pub(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}
467pub(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(¶ms), "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}