use std::sync::{Arc, Mutex, OnceLock};
use lex_ast::canonicalize_program;
use lex_bytecode::vm::Vm;
use lex_bytecode::{compile_program, Op, Program, Value};
use lex_syntax::parse_source;
fn compile_lock() -> &'static Mutex<()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
}
fn compile(src: &str) -> Program {
let _g = compile_lock().lock().unwrap();
let p = parse_source(src).unwrap();
let stages = canonicalize_program(&p);
compile_program(&stages)
}
fn compile_with_no_arena(src: &str) -> Program {
let _g = compile_lock().lock().unwrap();
unsafe { std::env::set_var("LEX_NO_ARENA_RECORDS", "1"); }
let p = parse_source(src).unwrap();
let stages = canonicalize_program(&p);
let prog = compile_program(&stages);
unsafe { std::env::remove_var("LEX_NO_ARENA_RECORDS"); }
prog
}
fn fn_code<'a>(prog: &'a Program, name: &str) -> &'a [Op] {
let idx = prog.function_names[name];
&prog.functions[idx as usize].code
}
fn count<F: Fn(&Op) -> bool>(code: &[Op], pred: F) -> usize {
code.iter().filter(|op| pred(op)).count()
}
#[test]
fn returned_record_lowers_to_alloc_arena_record() {
let src = r#"
fn handler() -> { status :: Int, total :: Int } {
{ status: 200, total: 42 }
}
"#;
let p = compile(src);
let code = fn_code(&p, "handler");
assert_eq!(count(code, |op| matches!(op, Op::AllocStackRecord { .. })), 0,
"returned record is frame-escaping — not stack: {code:?}");
assert_eq!(count(code, |op| matches!(op, Op::AllocArenaRecord { .. })), 1,
"should lower to arena: {code:?}");
assert_eq!(count(code, |op| matches!(op, Op::MakeRecord { .. })), 0,
"no heap MakeRecord should remain: {code:?}");
}
#[test]
fn returned_tuple_lowers_to_alloc_arena_tuple() {
let src = r#"
fn handler() -> Tuple[Int, Int] { (3, 4) }
"#;
let p = compile(src);
let code = fn_code(&p, "handler");
assert_eq!(count(code, |op| matches!(op, Op::AllocArenaTuple { .. })), 1);
assert_eq!(count(code, |op| matches!(op, Op::MakeTuple(_))), 0);
}
#[test]
fn frame_local_record_prefers_stack_tier_over_arena() {
let src = r#"
fn drop_and_read() -> Int {
let r := { x: 7, y: 9 }
r.x
}
"#;
let p = compile(src);
let code = fn_code(&p, "drop_and_read");
assert_eq!(count(code, |op| matches!(op, Op::AllocStackRecord { .. })), 1,
"frame-local record should land on stack tier: {code:?}");
assert_eq!(count(code, |op| matches!(op, Op::AllocArenaRecord { .. })), 0,
"stack pass runs first and takes the cheaper tier: {code:?}");
}
#[test]
fn record_passed_to_call_stays_on_heap_tier() {
let src = r#"
fn use_it(r :: { x :: Int, y :: Int }) -> Int { r.x }
fn caller() -> Int { use_it({ x: 1, y: 2 }) }
"#;
let p = compile(src);
let caller_code = fn_code(&p, "caller");
assert_eq!(count(caller_code, |op| matches!(op, Op::AllocStackRecord { .. })), 0);
assert_eq!(count(caller_code, |op| matches!(op, Op::AllocArenaRecord { .. })), 0,
"Call-passed record escapes the request — not arena: {caller_code:?}");
assert_eq!(count(caller_code, |op| matches!(op, Op::MakeRecord { .. })), 1,
"stays on heap tier: {caller_code:?}");
}
#[test]
fn per_site_lowering_mixes_all_three_tiers() {
let src = r#"
fn helper(r :: { a :: Int }) -> Int { r.a }
fn mix() -> { z :: Int } {
let temp := { a: 1, b: 2 }
let _ := temp.a
let _ := helper({ a: 99 })
{ z: 99 }
}
"#;
let p = compile(src);
let code = fn_code(&p, "mix");
assert_eq!(count(code, |op| matches!(op, Op::AllocStackRecord { .. })), 1,
"dropped `temp` should be stack: {code:?}");
assert_eq!(count(code, |op| matches!(op, Op::AllocArenaRecord { .. })), 1,
"returned `{{z}}` should be arena: {code:?}");
assert_eq!(count(code, |op| matches!(op, Op::MakeRecord { .. })), 1,
"helper argument should stay heap: {code:?}");
}
#[test]
fn arena_handler_actually_routes_to_slab_inside_scope() {
let src = r#"
fn handler() -> { status :: Int, total :: Int } {
{ status: 200, total: 42 }
}
"#;
let p = compile(src);
let mut vm = Vm::new(&p);
let scope = vm.enter_request_scope();
let result = vm.invoke(p.function_names["handler"], vec![]).unwrap();
let materialized = vm.materialize_arena_handles(result);
vm.exit_request_scope(scope);
assert_eq!(vm.arena_record_allocs, 1, "should have used arena path");
assert_eq!(vm.arena_record_heap_fallbacks, 0, "should not have fallen back");
match materialized {
Value::Record { fields, .. } => {
assert_eq!(fields.get("status"), Some(&Value::Int(200)));
assert_eq!(fields.get("total"), Some(&Value::Int(42)));
}
other => panic!("expected materialized Record, got {other:?}"),
}
}
#[test]
fn arena_handler_outside_scope_falls_back_to_heap() {
let src = r#"
fn handler() -> { status :: Int } {
{ status: 200 }
}
"#;
let p = compile(src);
let mut vm = Vm::new(&p);
let result = vm.invoke(p.function_names["handler"], vec![]).unwrap();
assert_eq!(vm.arena_record_allocs, 0);
assert_eq!(vm.arena_record_heap_fallbacks, 1);
match result {
Value::Record { fields, .. } => {
assert_eq!(fields.get("status"), Some(&Value::Int(200)));
}
other => panic!("expected heap fallback Record, got {other:?}"),
}
}
#[test]
fn lex_no_arena_records_disables_the_pass() {
let src = r#"
fn handler() -> { status :: Int } { { status: 200 } }
"#;
let on = compile(src);
let off = compile_with_no_arena(src);
let on_code = fn_code(&on, "handler");
let off_code = fn_code(&off, "handler");
assert_eq!(count(on_code, |op| matches!(op, Op::AllocArenaRecord { .. })), 1,
"default: pass fires");
assert_eq!(count(off_code, |op| matches!(op, Op::AllocArenaRecord { .. })), 0,
"env var: pass disabled");
assert_eq!(count(off_code, |op| matches!(op, Op::MakeRecord { .. })), 1,
"env var: record stays heap MakeRecord");
}
#[test]
fn body_hash_unchanged_by_arena_lowering() {
let src = r#"
fn handler() -> { status :: Int, total :: Int } {
{ status: 200, total: 42 }
}
"#;
let on = Arc::new(compile(src));
let off = Arc::new(compile_with_no_arena(src));
let on_fn = &on.functions[on.function_names["handler"] as usize];
let off_fn = &off.functions[off.function_names["handler"] as usize];
assert_eq!(on_fn.body_hash, off_fn.body_hash,
"arena lowering must not perturb body_hash (closure identity #222)");
}
#[test]
fn match_arm_records_both_lower_to_arena_under_precision() {
let src = r#"
fn handler(cond :: Bool) -> { status :: Int, total :: Int } {
match cond {
true => { status: 200, total: 42 },
false => { status: 400, total: 0 },
}
}
"#;
let p = compile(src);
let code = fn_code(&p, "handler");
let arena_sites = count(code, |op| matches!(op, Op::AllocArenaRecord { .. }));
let heap_sites = count(code, |op| matches!(op, Op::MakeRecord { .. }));
assert_eq!(arena_sites, 2,
"expected both match arms to lower to AllocArenaRecord, got code: {code:?}");
assert_eq!(heap_sites, 0,
"no heap MakeRecord should remain after the precision refinement");
let mut vm = Vm::new(&p);
let scope = vm.enter_request_scope();
let r_true = vm.invoke(p.function_names["handler"], vec![Value::Bool(true)]).unwrap();
let r_true = vm.materialize_arena_handles(r_true);
let r_false = vm.invoke(p.function_names["handler"], vec![Value::Bool(false)]).unwrap();
let r_false = vm.materialize_arena_handles(r_false);
vm.exit_request_scope(scope);
match r_true {
Value::Record { fields, .. } => {
assert_eq!(fields.get("status"), Some(&Value::Int(200)));
assert_eq!(fields.get("total"), Some(&Value::Int(42)));
}
other => panic!("expected materialized true-arm Record, got {other:?}"),
}
match r_false {
Value::Record { fields, .. } => {
assert_eq!(fields.get("status"), Some(&Value::Int(400)));
assert_eq!(fields.get("total"), Some(&Value::Int(0)));
}
other => panic!("expected materialized false-arm Record, got {other:?}"),
}
assert_eq!(vm.arena_record_allocs, 2);
assert_eq!(vm.arena_record_heap_fallbacks, 0);
}
#[test]
fn deep_leaf_nested_handler_lowers_both_to_arena() {
let src = r#"
fn build_inner() -> { x :: Int, y :: Int } {
{ x: 1, y: 2 }
}
fn handler() -> { status :: Int, body :: { x :: Int, y :: Int } } {
{ status: 200, body: { x: 7, y: 9 } }
}
"#;
let p = compile(src);
let handler_code = fn_code(&p, "handler");
let arena = count(handler_code, |op| matches!(op, Op::AllocArenaRecord { .. }));
let heap = count(handler_code, |op| matches!(op, Op::MakeRecord { .. }));
assert_eq!(arena, 2,
"expected both nested records to lower to arena: {handler_code:?}");
assert_eq!(heap, 0,
"no heap records should remain after the deep-leaf refinement");
let mut vm = Vm::new(&p);
let scope = vm.enter_request_scope();
let r = vm.invoke(p.function_names["handler"], vec![]).unwrap();
let r = vm.materialize_arena_handles(r);
vm.exit_request_scope(scope);
match r {
Value::Record { fields, .. } => {
assert_eq!(fields.get("status"), Some(&Value::Int(200)));
match fields.get("body") {
Some(Value::Record { fields: inner, .. }) => {
assert_eq!(inner.get("x"), Some(&Value::Int(7)));
assert_eq!(inner.get("y"), Some(&Value::Int(9)));
}
other => panic!("expected nested body Record, got {other:?}"),
}
}
other => panic!("expected outer Record, got {other:?}"),
}
assert_eq!(vm.arena_record_allocs, 2);
assert_eq!(vm.arena_record_heap_fallbacks, 0);
}