mod control_flow;
mod error;
mod ffi_wrappers;
mod globals;
mod inline;
mod layout;
mod platform;
mod program;
mod runtime;
mod specialization;
mod state;
mod statements;
mod types;
mod virtual_stack;
mod words;
pub use error::CodeGenError;
pub use platform::{ffi_c_args, ffi_return_type, get_target_triple};
pub use runtime::{BUILTIN_SYMBOLS, RUNTIME_DECLARATIONS, emit_runtime_decls};
pub use state::CodeGen;
use state::{
BranchResult, MAX_VIRTUAL_STACK, QuotationFunctions, TailPosition, UNREACHABLE_PREDECESSOR,
VirtualValue, mangle_name,
};
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Program, Statement, WordDef};
use crate::config::CompilerConfig;
use std::collections::HashMap;
#[test]
fn test_codegen_hello_world() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::StringLiteral("Hello, World!".to_string()),
Statement::WordCall {
name: "io.write-line".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(ir.contains("define i32 @main(i32 %argc, ptr %argv)"));
assert!(ir.contains("define ptr @seq_main(ptr %stack)"));
assert!(ir.contains("call ptr @patch_seq_push_string"));
assert!(ir.contains("call ptr @patch_seq_write_line"));
assert!(ir.contains("\"Hello, World!\\00\""));
}
#[test]
fn test_codegen_io_write() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::StringLiteral("no newline".to_string()),
Statement::WordCall {
name: "io.write".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(ir.contains("call ptr @patch_seq_push_string"));
assert!(ir.contains("call ptr @patch_seq_write"));
assert!(ir.contains("\"no newline\\00\""));
}
#[test]
fn test_codegen_arithmetic() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(2),
Statement::IntLiteral(3),
Statement::WordCall {
name: "i.add".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(ir.contains("add i64 0, 2"), "Should create SSA var for 2");
assert!(ir.contains("add i64 0, 3"), "Should create SSA var for 3");
assert!(ir.contains("add i64 %"), "Should add SSA variables");
}
#[test]
fn test_pure_inline_test_mode() {
let mut codegen = CodeGen::new_pure_inline_test();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(5),
Statement::IntLiteral(3),
Statement::WordCall {
name: "i.add".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(!ir.contains("call void @patch_seq_scheduler_init"));
assert!(!ir.contains("call i64 @patch_seq_strand_spawn"));
assert!(ir.contains("call ptr @seq_stack_new_default()"));
assert!(ir.contains("call ptr @seq_main(ptr %stack_base)"));
assert!(
ir.lines()
.any(|l| l.contains("trunc i64 %") && l.contains("to i32")),
"Expected a trunc i64 %N to i32 instruction"
);
assert!(ir.contains("ret i32 %exit_code"));
assert!(!ir.contains("call ptr @patch_seq_push_int"));
assert!(ir.contains("add i64 0, 5"), "Should create SSA var for 5");
assert!(ir.contains("add i64 0, 3"), "Should create SSA var for 3");
assert!(!ir.contains("call ptr @patch_seq_add"));
assert!(ir.contains("add i64 %"), "Should add SSA variables");
}
#[test]
fn test_escape_llvm_string() {
assert_eq!(CodeGen::escape_llvm_string("hello").unwrap(), "hello");
assert_eq!(CodeGen::escape_llvm_string("a\nb").unwrap(), r"a\0Ab");
assert_eq!(CodeGen::escape_llvm_string("a\tb").unwrap(), r"a\09b");
assert_eq!(CodeGen::escape_llvm_string("a\"b").unwrap(), r"a\22b");
}
#[test]
#[allow(deprecated)] fn test_external_builtins_declared() {
use crate::config::{CompilerConfig, ExternalBuiltin};
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None, body: vec![
Statement::IntLiteral(42),
Statement::WordCall {
name: "my-external-op".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let config = CompilerConfig::new()
.with_builtin(ExternalBuiltin::new("my-external-op", "test_runtime_my_op"));
let ir = codegen
.codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
.unwrap();
assert!(
ir.contains("declare ptr @test_runtime_my_op(ptr)"),
"IR should declare external builtin"
);
assert!(
ir.contains("call ptr @test_runtime_my_op"),
"IR should call external builtin"
);
}
#[test]
#[allow(deprecated)] fn test_multiple_external_builtins() {
use crate::config::{CompilerConfig, ExternalBuiltin};
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None, body: vec![
Statement::WordCall {
name: "actor-self".to_string(),
span: None,
},
Statement::WordCall {
name: "journal-append".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let config = CompilerConfig::new()
.with_builtin(ExternalBuiltin::new("actor-self", "seq_actors_self"))
.with_builtin(ExternalBuiltin::new(
"journal-append",
"seq_actors_journal_append",
));
let ir = codegen
.codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
.unwrap();
assert!(ir.contains("declare ptr @seq_actors_self(ptr)"));
assert!(ir.contains("declare ptr @seq_actors_journal_append(ptr)"));
assert!(ir.contains("call ptr @seq_actors_self"));
assert!(ir.contains("call ptr @seq_actors_journal_append"));
}
#[test]
#[allow(deprecated)] fn test_external_builtins_with_library_paths() {
use crate::config::{CompilerConfig, ExternalBuiltin};
let config = CompilerConfig::new()
.with_builtin(ExternalBuiltin::new("my-op", "runtime_my_op"))
.with_library_path("/custom/lib")
.with_library("myruntime");
assert_eq!(config.external_builtins.len(), 1);
assert_eq!(config.library_paths, vec!["/custom/lib"]);
assert_eq!(config.libraries, vec!["myruntime"]);
}
#[test]
fn test_external_builtin_full_pipeline() {
use crate::compile_to_ir_with_config;
use crate::config::{CompilerConfig, ExternalBuiltin};
use crate::types::{Effect, StackType, Type};
let source = r#"
: main ( -- Int )
42 my-transform
0
;
"#;
let effect = Effect::new(StackType::singleton(Type::Int), StackType::Empty);
let config = CompilerConfig::new().with_builtin(ExternalBuiltin::with_effect(
"my-transform",
"ext_runtime_transform",
effect,
));
let result = compile_to_ir_with_config(source, &config);
assert!(
result.is_ok(),
"Compilation should succeed: {:?}",
result.err()
);
let ir = result.unwrap();
assert!(ir.contains("declare ptr @ext_runtime_transform(ptr)"));
assert!(ir.contains("call ptr @ext_runtime_transform"));
}
#[test]
fn test_external_builtin_without_config_fails() {
use crate::compile_to_ir;
let source = r#"
: main ( -- Int )
42 unknown-builtin
0
;
"#;
let result = compile_to_ir(source);
assert!(result.is_err());
assert!(result.unwrap_err().contains("unknown-builtin"));
}
#[test]
fn test_match_exhaustiveness_error() {
use crate::compile_to_ir;
let source = r#"
union Result { Ok { value: Int } Err { msg: String } }
: handle ( Variant -- Int )
match
Ok -> drop 1
# Missing Err arm!
end
;
: main ( -- ) 42 Make-Ok handle drop ;
"#;
let result = compile_to_ir(source);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("Non-exhaustive match"));
assert!(err.contains("Result"));
assert!(err.contains("Err"));
}
#[test]
fn test_match_exhaustive_compiles() {
use crate::compile_to_ir;
let source = r#"
union Result { Ok { value: Int } Err { msg: String } }
: handle ( Variant -- Int )
match
Ok -> drop 1
Err -> drop 0
end
;
: main ( -- ) 42 Make-Ok handle drop ;
"#;
let result = compile_to_ir(source);
assert!(
result.is_ok(),
"Exhaustive match should compile: {:?}",
result
);
}
#[test]
fn test_codegen_symbol() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::Symbol("hello".to_string()),
Statement::WordCall {
name: "symbol->string".to_string(),
span: None,
},
Statement::WordCall {
name: "io.write-line".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(ir.contains("call ptr @patch_seq_push_interned_symbol"));
assert!(ir.contains("call ptr @patch_seq_symbol_to_string"));
assert!(ir.contains("\"hello\\00\""));
}
#[test]
fn test_symbol_interning_dedup() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::Symbol("hello".to_string()),
Statement::Symbol("hello".to_string()),
Statement::Symbol("world".to_string()), ],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
let sym_defs: Vec<_> = ir
.lines()
.filter(|l| l.trim().starts_with("@.sym."))
.collect();
assert_eq!(
sym_defs.len(),
2,
"Expected 2 symbol globals, got: {:?}",
sym_defs
);
let hello_uses: usize = ir.matches("@.sym.0").count();
assert_eq!(
hello_uses, 3,
"Expected 3 occurrences of .sym.0 (1 def + 2 uses)"
);
assert!(
ir.contains("i64 0, i8 1"),
"Symbol global should have capacity=0 and global=1"
);
}
#[test]
fn test_dup_optimization_for_int() {
let mut codegen = CodeGen::new();
use crate::types::Type;
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "test_dup".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(42), Statement::WordCall {
name: "dup".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "test_dup".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let mut statement_types = HashMap::new();
statement_types.insert(("test_dup".to_string(), 1), Type::Int);
let ir = codegen
.codegen_program(&program, HashMap::new(), statement_types)
.unwrap();
let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
let test_dup_fn = &ir[func_start..func_end];
assert!(
test_dup_fn.contains("load i64"),
"Optimized dup should use 'load i64', got:\n{}",
test_dup_fn
);
assert!(
test_dup_fn.contains("store i64"),
"Optimized dup should use 'store i64', got:\n{}",
test_dup_fn
);
assert!(
!test_dup_fn.contains("@patch_seq_clone_value"),
"Optimized dup should NOT call clone_value for Int, got:\n{}",
test_dup_fn
);
}
#[test]
fn test_dup_optimization_after_literal() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "test_dup".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(42), Statement::WordCall {
name: "dup".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "test_dup".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
let test_dup_fn = &ir[func_start..func_end];
assert!(
test_dup_fn.contains("load i64"),
"Dup after int literal should use optimized load, got:\n{}",
test_dup_fn
);
assert!(
test_dup_fn.contains("store i64"),
"Dup after int literal should use optimized store, got:\n{}",
test_dup_fn
);
assert!(
!test_dup_fn.contains("@patch_seq_clone_value"),
"Dup after int literal should NOT call clone_value, got:\n{}",
test_dup_fn
);
}
#[test]
fn test_dup_no_optimization_after_word_call() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "get_value".to_string(),
effect: None,
body: vec![Statement::IntLiteral(42)],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "test_dup".to_string(),
effect: None,
body: vec![
Statement::WordCall {
name: "get_value".to_string(),
span: None,
},
Statement::WordCall {
name: "dup".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "test_dup".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
let func_start = ir.find("define tailcc ptr @seq_test_dup").unwrap();
let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
let test_dup_fn = &ir[func_start..func_end];
assert!(
test_dup_fn.contains("@patch_seq_clone_value"),
"Dup after word call should call clone_value, got:\n{}",
test_dup_fn
);
}
#[test]
fn test_roll_constant_optimization() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "test_roll".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(1),
Statement::IntLiteral(2),
Statement::IntLiteral(3),
Statement::IntLiteral(2), Statement::WordCall {
name: "roll".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "test_roll".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
let func_start = ir.find("define tailcc ptr @seq_test_roll").unwrap();
let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
let test_roll_fn = &ir[func_start..func_end];
assert!(
!test_roll_fn.contains("= add i64 %"),
"Constant roll should use constant offset, not dynamic add, got:\n{}",
test_roll_fn
);
assert!(
!test_roll_fn.contains("@llvm.memmove"),
"2 roll should not use memmove, got:\n{}",
test_roll_fn
);
}
#[test]
fn test_pick_constant_optimization() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "test_pick".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(10),
Statement::IntLiteral(20),
Statement::IntLiteral(1), Statement::WordCall {
name: "pick".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "test_pick".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
let func_start = ir.find("define tailcc ptr @seq_test_pick").unwrap();
let func_end = ir[func_start..].find("\n}\n").unwrap() + func_start + 3;
let test_pick_fn = &ir[func_start..func_end];
assert!(
!test_pick_fn.contains("= add i64 %"),
"Constant pick should use constant offset, not dynamic add, got:\n{}",
test_pick_fn
);
assert!(
test_pick_fn.contains("i64 -3"),
"1 pick should use offset -3 (-(1+2)), got:\n{}",
test_pick_fn
);
}
#[test]
fn test_small_word_marked_alwaysinline() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "double".to_string(), effect: None,
body: vec![
Statement::WordCall {
name: "dup".to_string(),
span: None,
},
Statement::WordCall {
name: "i.+".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(21),
Statement::WordCall {
name: "double".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(
ir.contains("define tailcc ptr @seq_double(ptr %stack) alwaysinline"),
"Small word should have alwaysinline attribute, got:\n{}",
ir.lines()
.filter(|l| l.contains("define"))
.collect::<Vec<_>>()
.join("\n")
);
assert!(
ir.contains("define ptr @seq_main(ptr %stack) {"),
"main should not have alwaysinline, got:\n{}",
ir.lines()
.filter(|l| l.contains("define"))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn test_recursive_word_not_inlined() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "countdown".to_string(), effect: None,
body: vec![
Statement::WordCall {
name: "dup".to_string(),
span: None,
},
Statement::If {
then_branch: vec![
Statement::IntLiteral(1),
Statement::WordCall {
name: "i.-".to_string(),
span: None,
},
Statement::WordCall {
name: "countdown".to_string(), span: None,
},
],
else_branch: Some(vec![]),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(5),
Statement::WordCall {
name: "countdown".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(
ir.contains("define tailcc ptr @seq_countdown(ptr %stack) {"),
"Recursive word should NOT have alwaysinline, got:\n{}",
ir.lines()
.filter(|l| l.contains("define"))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn test_recursive_word_in_match_not_inlined() {
use crate::ast::{MatchArm, Pattern, UnionDef, UnionVariant};
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![UnionDef {
name: "Option".to_string(),
variants: vec![
UnionVariant {
name: "Some".to_string(),
fields: vec![],
source: None,
},
UnionVariant {
name: "None".to_string(),
fields: vec![],
source: None,
},
],
source: None,
}],
words: vec![
WordDef {
name: "process".to_string(), effect: None,
body: vec![Statement::Match {
arms: vec![
MatchArm {
pattern: Pattern::Variant("Some".to_string()),
body: vec![Statement::WordCall {
name: "process".to_string(), span: None,
}],
span: None,
},
MatchArm {
pattern: Pattern::Variant("None".to_string()),
body: vec![],
span: None,
},
],
span: None,
}],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "process".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(
ir.contains("define tailcc ptr @seq_process(ptr %stack) {"),
"Recursive word in match should NOT have alwaysinline, got:\n{}",
ir.lines()
.filter(|l| l.contains("define"))
.collect::<Vec<_>>()
.join("\n")
);
}
#[test]
fn test_issue_338_specialized_call_in_if_branch_has_terminator() {
use crate::types::{Effect, StackType, Type};
let mut codegen = CodeGen::new();
let get_value_effect = Effect {
inputs: StackType::Cons {
rest: Box::new(StackType::RowVar("S".to_string())),
top: Type::Int,
},
outputs: StackType::Cons {
rest: Box::new(StackType::RowVar("S".to_string())),
top: Type::Int,
},
effects: vec![],
};
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "get-value".to_string(),
effect: Some(get_value_effect),
body: vec![Statement::WordCall {
name: "dup".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "test-caller".to_string(),
effect: None,
body: vec![Statement::If {
then_branch: vec![Statement::WordCall {
name: "get-value".to_string(),
span: None,
}],
else_branch: Some(vec![
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
Statement::IntLiteral(0),
]),
span: None,
}],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::BoolLiteral(true),
Statement::IntLiteral(42),
Statement::WordCall {
name: "test-caller".to_string(),
span: None,
},
Statement::WordCall {
name: "drop".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
},
],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.expect("Issue #338: codegen should succeed for specialized call in if branch");
assert!(
ir.contains("@seq_get_value_i64"),
"Should generate specialized version of get-value"
);
assert!(
ir.contains("define tailcc ptr @seq_test_caller"),
"Should generate test-caller function"
);
assert!(
ir.contains("musttail call tailcc ptr @seq_get_value"),
"Then branch should use tail call to stack-based version, not specialized dispatch"
);
}
#[test]
fn test_report_call_in_normal_mode() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![
Statement::IntLiteral(42),
Statement::WordCall {
name: "io.write-line".to_string(),
span: None,
},
],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(
ir.contains("call void @patch_seq_report()"),
"Normal mode should emit report call"
);
}
#[test]
fn test_report_call_absent_in_pure_inline() {
let mut codegen = CodeGen::new_pure_inline_test();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::IntLiteral(42)],
source: None,
allowed_lints: vec![],
}],
};
let ir = codegen
.codegen_program(&program, HashMap::new(), HashMap::new())
.unwrap();
assert!(
!ir.contains("call void @patch_seq_report()"),
"Pure inline mode should not emit report call"
);
}
#[test]
fn test_instrument_emits_counters_and_atomicrmw() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![
WordDef {
name: "helper".to_string(),
effect: None,
body: vec![Statement::IntLiteral(1)],
source: None,
allowed_lints: vec![],
},
WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::WordCall {
name: "helper".to_string(),
span: None,
}],
source: None,
allowed_lints: vec![],
},
],
};
let config = CompilerConfig {
instrument: true,
..CompilerConfig::default()
};
let ir = codegen
.codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
.unwrap();
assert!(
ir.contains("@seq_word_counters = global [2 x i64] zeroinitializer"),
"Should emit counter array for 2 words"
);
assert!(
ir.contains("@seq_word_name_"),
"Should emit word name constants"
);
assert!(
ir.contains("@seq_word_names = private constant [2 x ptr]"),
"Should emit name pointer table"
);
assert!(
ir.contains("atomicrmw add ptr %instr_ptr_"),
"Should emit atomicrmw add for word counters"
);
assert!(
ir.contains("call void @patch_seq_report_init(ptr @seq_word_counters, ptr @seq_word_names, i64 2)"),
"Should emit report_init call with correct count"
);
}
#[test]
fn test_no_instrument_no_counters() {
let mut codegen = CodeGen::new();
let program = Program {
includes: vec![],
unions: vec![],
words: vec![WordDef {
name: "main".to_string(),
effect: None,
body: vec![Statement::IntLiteral(42)],
source: None,
allowed_lints: vec![],
}],
};
let config = CompilerConfig::default();
assert!(!config.instrument);
let ir = codegen
.codegen_program_with_config(&program, HashMap::new(), HashMap::new(), &config)
.unwrap();
assert!(
!ir.contains("@seq_word_counters"),
"Should not emit counters when instrument=false"
);
assert!(
!ir.contains("atomicrmw"),
"Should not emit atomicrmw when instrument=false"
);
assert!(
!ir.contains("call void @patch_seq_report_init"),
"Should not emit report_init when instrument=false"
);
}
}