use cemc::ast::types::{Effect, StackType, Type};
use cemc::ast::{Expr, Program, SourceLoc, WordDef};
use cemc::codegen::{CodeGen, compile_to_object, link_program};
use std::process::Command;
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_end_to_end_compilation() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success(), "Runtime build failed");
let word = WordDef {
name: "fortytwo".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(42, SourceLoc::unknown())],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program(&program)
.expect("Failed to generate IR");
assert!(ir.contains("define ptr @fortytwo"));
assert!(ir.contains("call ptr @push_int"));
assert!(ir.contains("i64 42"));
compile_to_object(&ir, "test_fortytwo").expect("Failed to compile IR to object");
std::fs::remove_file("test_fortytwo.o").ok();
std::fs::remove_file("test_fortytwo.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_arithmetic_compilation() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "eight".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::IntLit(5, SourceLoc::unknown()),
Expr::IntLit(3, SourceLoc::unknown()),
Expr::WordCall("add".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program(&program)
.expect("Failed to generate IR");
assert!(ir.contains("@eight"));
assert!(ir.contains("@add"));
compile_to_object(&ir, "test_eight").expect("Failed to compile");
std::fs::remove_file("test_eight.o").ok();
std::fs::remove_file("test_eight.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_executable_with_main() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "fortytwo".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(42, SourceLoc::unknown())],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("fortytwo"))
.expect("Failed to generate IR");
assert!(ir.contains("define i32 @main()"));
assert!(ir.contains("strand_spawn(ptr @fortytwo")); assert!(ir.contains("ret i32 0"));
link_program(&ir, "runtime/libcem_runtime.a", "test_fortytwo_exe").expect("Failed to link");
let output = Command::new("./test_fortytwo_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_fortytwo_exe").ok();
std::fs::remove_file("test_fortytwo_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_multiply_executable() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "product".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::IntLit(6, SourceLoc::unknown()),
Expr::IntLit(7, SourceLoc::unknown()),
Expr::WordCall("multiply".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("product"))
.expect("Failed to generate IR");
link_program(&ir, "runtime/libcem_runtime.a", "test_product_exe").expect("Failed to link");
let output = Command::new("./test_product_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_product_exe").ok();
std::fs::remove_file("test_product_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_if_expression() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "test_if".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::BoolLit(true, SourceLoc::unknown()),
Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(42, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(0, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
},
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("test_if"))
.expect("Failed to generate IR");
assert!(ir.contains("br i1 %")); assert!(ir.contains("then_"));
assert!(ir.contains("else_"));
assert!(ir.contains("merge_"));
assert!(ir.contains("phi ptr"));
link_program(&ir, "runtime/libcem_runtime.a", "test_if_exe").expect("Failed to link");
let output = Command::new("./test_if_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_if_exe").ok();
std::fs::remove_file("test_if_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_tail_call_optimization() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let identity = WordDef {
name: "identity".to_string(),
effect: Effect {
inputs: StackType::Empty.push(Type::Int),
outputs: StackType::Empty.push(Type::Int),
},
body: vec![], loc: SourceLoc::unknown(),
};
let call_identity = WordDef {
name: "call_identity".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::IntLit(42, SourceLoc::unknown()),
Expr::WordCall("identity".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![identity, call_identity],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("call_identity"))
.expect("Failed to generate IR");
assert!(
ir.contains("musttail call"),
"Expected musttail optimization for tail call"
);
link_program(&ir, "runtime/libcem_runtime.a", "test_tail_call_exe").expect("Failed to link");
let output = Command::new("./test_tail_call_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_tail_call_exe").ok();
std::fs::remove_file("test_tail_call_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_if_false_branch() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "test_if_false".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::BoolLit(false, SourceLoc::unknown()), Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(42, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(99, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
},
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("test_if_false"))
.expect("Failed to generate IR");
link_program(&ir, "runtime/libcem_runtime.a", "test_if_false_exe").expect("Failed to link");
let output = Command::new("./test_if_false_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_if_false_exe").ok();
std::fs::remove_file("test_if_false_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_tail_call_in_if_branch() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let passthrough = WordDef {
name: "passthrough".to_string(),
effect: Effect {
inputs: StackType::Empty.push(Type::Int),
outputs: StackType::Empty.push(Type::Int),
},
body: vec![], loc: SourceLoc::unknown(),
};
let conditional_call = WordDef {
name: "conditional_call".to_string(),
effect: Effect {
inputs: StackType::Empty.push(Type::Bool),
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![
Expr::IntLit(42, SourceLoc::unknown()),
Expr::WordCall("passthrough".to_string(), SourceLoc::unknown()),
],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![
Expr::IntLit(99, SourceLoc::unknown()),
Expr::WordCall("passthrough".to_string(), SourceLoc::unknown()),
],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
}],
loc: SourceLoc::unknown(),
};
let test_entry = WordDef {
name: "test_entry".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::BoolLit(true, SourceLoc::unknown()),
Expr::WordCall("conditional_call".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![passthrough, conditional_call, test_entry],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("test_entry"))
.expect("Failed to generate IR");
assert!(
ir.contains("musttail call ptr @passthrough"),
"Expected musttail optimization for tail calls in if branches"
);
link_program(&ir, "runtime/libcem_runtime.a", "test_tail_in_if_exe").expect("Failed to link");
let output = Command::new("./test_tail_in_if_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_tail_in_if_exe").ok();
std::fs::remove_file("test_tail_in_if_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_nested_if_expressions() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let nested_if = WordDef {
name: "nested_if".to_string(),
effect: Effect {
inputs: StackType::Empty.push(Type::Bool).push(Type::Bool),
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![
Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(1, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(2, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
},
],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![
Expr::If {
then_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(3, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
else_branch: Box::new(Expr::Quotation(
vec![Expr::IntLit(4, SourceLoc::unknown())],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
},
],
SourceLoc::unknown(),
)),
loc: SourceLoc::unknown(),
}],
loc: SourceLoc::unknown(),
};
let test_true_true = WordDef {
name: "test_true_true".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::BoolLit(true, SourceLoc::unknown()), Expr::BoolLit(true, SourceLoc::unknown()), Expr::WordCall("nested_if".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![nested_if, test_true_true],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("test_true_true"))
.expect("Failed to generate IR");
assert!(ir.contains("then_"), "Expected then branch labels");
assert!(ir.contains("else_"), "Expected else branch labels");
assert!(ir.contains("merge_"), "Expected merge block labels");
std::fs::write("test_nested_if_debug.ll", &ir).expect("Failed to write IR");
link_program(&ir, "runtime/libcem_runtime.a", "test_nested_if_exe").expect("Failed to link");
let output = Command::new("./test_nested_if_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_nested_if_exe").ok();
std::fs::remove_file("test_nested_if_exe.ll").ok();
}
#[test]
#[cfg_attr(
target_os = "linux",
ignore = "Pre-existing test failure (not epoll-related)"
)]
fn test_scheduler_linkage() {
let runtime_status = Command::new("just")
.arg("build-runtime")
.status()
.expect("Failed to build runtime");
assert!(runtime_status.success());
let word = WordDef {
name: "test_scheduler".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![
Expr::IntLit(5, SourceLoc::unknown()),
Expr::WordCall("test_yield".to_string(), SourceLoc::unknown()),
Expr::IntLit(10, SourceLoc::unknown()),
Expr::WordCall("add".to_string(), SourceLoc::unknown()),
],
loc: SourceLoc::unknown(),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program_with_main(&program, Some("test_scheduler"))
.expect("Failed to generate IR");
assert!(ir.contains("declare ptr @test_yield(ptr)"));
assert!(ir.contains("call ptr @test_yield"));
link_program(&ir, "runtime/libcem_runtime.a", "test_scheduler_exe").expect("Failed to link");
let output = Command::new("./test_scheduler_exe")
.output()
.expect("Failed to run executable");
assert!(output.status.success());
std::fs::remove_file("test_scheduler_exe").ok();
std::fs::remove_file("test_scheduler_exe.ll").ok();
}
#[test]
fn test_debug_metadata_emission() {
let word = WordDef {
name: "fortytwo".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(
42,
SourceLoc::new(1, 25, "test.cem".to_string()),
)],
loc: SourceLoc::new(1, 1, "test.cem".to_string()),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program(&program)
.expect("Failed to generate IR");
assert!(ir.contains("!DIFile"), "Should contain DIFile metadata");
assert!(
ir.contains("!DICompileUnit"),
"Should contain DICompileUnit metadata"
);
assert!(
ir.contains("!DISubprogram"),
"Should contain DISubprogram metadata"
);
assert!(
ir.contains("!DILocation"),
"Should contain DILocation metadata"
);
assert!(ir.contains("!llvm.dbg.cu"), "Should contain llvm.dbg.cu");
assert!(
ir.contains("!llvm.module.flags"),
"Should contain module flags"
);
assert!(
ir.contains(", !dbg !"),
"Instructions should have !dbg annotations"
);
assert!(
ir.contains("define ptr @fortytwo(ptr %stack) !dbg !"),
"Function should reference DISubprogram"
);
}
#[test]
fn test_debug_metadata_filename_escaping() {
let word = WordDef {
name: "test".to_string(),
effect: Effect {
inputs: StackType::Empty,
outputs: StackType::Empty.push(Type::Int),
},
body: vec![Expr::IntLit(
42,
SourceLoc::new(1, 1, "test\"file.cem".to_string()),
)],
loc: SourceLoc::new(1, 1, "test\"file.cem".to_string()),
};
let program = Program {
type_defs: vec![],
word_defs: vec![word],
};
let mut codegen = CodeGen::new();
let ir = codegen
.compile_program(&program)
.expect("Failed to generate IR");
assert!(
ir.contains(r#"!DIFile(filename: "test\"file.cem""#),
"Filename with quotes should be escaped"
);
}