use std::sync::Arc;
use indexmap::IndexMap;
use lex_bytecode::{Const, Op, Program, Value, Vm};
use lex_bytecode::program::{compute_body_hash, Function, ZERO_BODY_HASH};
fn xy_program(name: &str, locals_count: u16, code: Vec<Op>) -> Arc<Program> {
let constants = vec![
Const::FieldName("x".into()),
Const::FieldName("y".into()),
Const::Int(7),
Const::Int(9),
];
let mut function_names = IndexMap::new();
function_names.insert(name.to_string(), 0);
Arc::new(Program {
constants,
functions: vec![Function {
name: name.into(),
arity: 0,
locals_count,
code,
effects: vec![],
body_hash: ZERO_BODY_HASH,
refinements: vec![],
field_ic_sites: 4, }],
function_names,
module_aliases: IndexMap::new(),
entry: Some(0),
record_shapes: vec![vec![0, 1]], })
}
fn tup_program(name: &str, locals_count: u16, code: Vec<Op>) -> Arc<Program> {
let constants = vec![Const::Int(11), Const::Int(13)];
let mut function_names = IndexMap::new();
function_names.insert(name.to_string(), 0);
Arc::new(Program {
constants,
functions: vec![Function {
name: name.into(),
arity: 0,
locals_count,
code,
effects: vec![],
body_hash: ZERO_BODY_HASH,
refinements: vec![],
field_ic_sites: 0,
}],
function_names,
module_aliases: IndexMap::new(),
entry: Some(0),
record_shapes: vec![],
})
}
#[test]
fn arena_alloc_inside_scope_routes_to_slab() {
let code = vec![
Op::PushConst(2), Op::PushConst(3), Op::AllocArenaRecord { shape_idx: 0, field_count: 2 }, Op::GetField { name_idx: 0, site_idx: 0 }, Op::Return,
];
let prog = xy_program("read_x", 0, code);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let result = vm.invoke(0, vec![]).unwrap();
vm.exit_request_scope(scope);
assert_eq!(result, Value::Int(7));
assert_eq!(vm.arena_record_allocs, 1);
assert_eq!(vm.arena_record_heap_fallbacks, 0);
}
#[test]
fn arena_alloc_outside_scope_falls_back_to_heap() {
let code = vec![
Op::PushConst(2),
Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Return,
];
let prog = xy_program("alloc_no_scope", 0, code);
let mut vm = Vm::new(&prog);
let result = vm.invoke(0, vec![]).unwrap();
match &result {
Value::Record { shape_id, fields } => {
assert_eq!(*shape_id, 0);
assert_eq!(fields.len(), 2);
assert_eq!(fields.get("x"), Some(&Value::Int(7)));
assert_eq!(fields.get("y"), Some(&Value::Int(9)));
}
other => panic!("expected fallback Value::Record, got {other:?}"),
}
assert_eq!(vm.arena_record_allocs, 0);
assert_eq!(vm.arena_record_heap_fallbacks, 1);
}
#[test]
fn arena_tuple_alloc_outside_scope_falls_back_to_heap() {
let code = vec![
Op::PushConst(0), Op::PushConst(1), Op::AllocArenaTuple { arity: 2 },
Op::Return,
];
let prog = tup_program("tup_no_scope", 0, code);
let mut vm = Vm::new(&prog);
let result = vm.invoke(0, vec![]).unwrap();
assert_eq!(result, Value::Tuple(vec![Value::Int(11), Value::Int(13)]));
assert_eq!(vm.arena_record_heap_fallbacks, 1);
}
#[test]
fn exit_request_scope_truncates_slab() {
let code = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Pop,
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Pop,
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Pop,
Op::PushConst(2),
Op::Return,
];
let prog = xy_program("three_drops", 0, code);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let r = vm.invoke(0, vec![]).unwrap();
assert_eq!(r, Value::Int(7));
assert_eq!(vm.arena_record_allocs, 3);
vm.exit_request_scope(scope);
let scope2 = vm.enter_request_scope();
let r2 = vm.invoke(0, vec![]).unwrap();
assert_eq!(r2, Value::Int(7));
assert_eq!(vm.arena_record_allocs, 6); vm.exit_request_scope(scope2);
}
#[test]
fn nested_scopes_pop_in_lifo_order() {
let code = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::GetField { name_idx: 0, site_idx: 0 },
Op::Return,
];
let prog = xy_program("alloc_and_read", 0, code);
let mut vm = Vm::new(&prog);
let outer = vm.enter_request_scope();
let r1 = vm.invoke(0, vec![]).unwrap();
assert_eq!(r1, Value::Int(7));
let inner = vm.enter_request_scope();
let r2 = vm.invoke(0, vec![]).unwrap();
assert_eq!(r2, Value::Int(7));
vm.exit_request_scope(inner);
let allocs_before = vm.arena_record_allocs;
let _ = vm.invoke(0, vec![]).unwrap();
assert_eq!(vm.arena_record_allocs, allocs_before + 1);
assert_eq!(vm.arena_record_heap_fallbacks, 0);
vm.exit_request_scope(outer);
let _ = vm.invoke(0, vec![]).unwrap();
assert_eq!(vm.arena_record_heap_fallbacks, 1);
}
#[test]
fn polymorphic_get_field_arena_record_caches_then_hits() {
let code = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::GetField { name_idx: 1, site_idx: 0 }, Op::Return,
];
let prog = xy_program("read_y_twice", 0, code);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let a = vm.invoke(0, vec![]).unwrap();
let b = vm.invoke(0, vec![]).unwrap();
vm.exit_request_scope(scope);
assert_eq!(a, Value::Int(9));
assert_eq!(b, Value::Int(9));
assert_eq!(vm.arena_record_allocs, 2);
}
#[test]
fn arena_tuple_alloc_and_get_elem_inside_scope() {
let code = vec![
Op::PushConst(0), Op::PushConst(1), Op::AllocArenaTuple { arity: 2 },
Op::GetElem(1), Op::Return,
];
let prog = tup_program("tup_read", 0, code);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let r = vm.invoke(0, vec![]).unwrap();
vm.exit_request_scope(scope);
assert_eq!(r, Value::Int(13));
assert_eq!(vm.arena_record_allocs, 1);
}
#[test]
fn body_hash_invariance_record() {
let make = vec![
Op::PushConst(2), Op::PushConst(3),
Op::MakeRecord { shape_idx: 0, field_count: 2 },
Op::Return,
];
let arena = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Return,
];
let shapes: Vec<Vec<u32>> = vec![vec![0, 1]];
let h_make = compute_body_hash(0, 0, &make, &shapes);
let h_arena = compute_body_hash(0, 0, &arena, &shapes);
assert_eq!(h_make, h_arena,
"AllocArenaRecord must hash as MakeRecord for #222 closure identity");
let stack = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocStackRecord { shape_idx: 0, field_count: 2 },
Op::Return,
];
assert_eq!(h_make, compute_body_hash(0, 0, &stack, &shapes));
}
#[test]
fn body_hash_invariance_tuple() {
let make = vec![
Op::PushConst(0), Op::PushConst(1),
Op::MakeTuple(2),
Op::Return,
];
let arena = vec![
Op::PushConst(0), Op::PushConst(1),
Op::AllocArenaTuple { arity: 2 },
Op::Return,
];
let shapes: Vec<Vec<u32>> = vec![];
assert_eq!(compute_body_hash(0, 0, &make, &shapes),
compute_body_hash(0, 0, &arena, &shapes),
"AllocArenaTuple must hash as MakeTuple for #222 closure identity");
}
#[test]
fn arena_record_round_trips_through_local() {
let code = vec![
Op::PushConst(2), Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::StoreLocal(0),
Op::LoadLocal(0),
Op::GetField { name_idx: 1, site_idx: 0 }, Op::Return,
];
let prog = xy_program("roundtrip", 1, code);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let r = vm.invoke(0, vec![]).unwrap();
vm.exit_request_scope(scope);
assert_eq!(r, Value::Int(9));
}
fn handler_returning_xy_record() -> Arc<Program> {
let code = vec![
Op::PushConst(2),
Op::PushConst(3),
Op::AllocArenaRecord { shape_idx: 0, field_count: 2 },
Op::Return,
];
xy_program("build_xy", 0, code)
}
#[test]
fn materialize_passthrough_primitives() {
let prog = xy_program("noop", 0, vec![Op::PushConst(2), Op::Return]);
let vm = Vm::new(&prog);
assert_eq!(vm.materialize_arena_handles(Value::Int(42)), Value::Int(42));
assert_eq!(vm.materialize_arena_handles(Value::Bool(true)), Value::Bool(true));
assert_eq!(vm.materialize_arena_handles(Value::Unit), Value::Unit);
}
#[test]
fn materialize_passthrough_heap_record() {
let prog = xy_program("noop", 0, vec![Op::PushConst(2), Op::Return]);
let vm = Vm::new(&prog);
let mut fields: IndexMap<smol_str::SmolStr, Value> = IndexMap::new();
fields.insert("x".into(), Value::Int(7));
fields.insert("y".into(), Value::Int(9));
let v = Value::Record { shape_id: 0, fields: Box::new(fields) };
assert_eq!(vm.materialize_arena_handles(v.clone()), v);
}
#[test]
fn materialize_arena_record_becomes_heap_record() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
assert!(matches!(arena_value, Value::ArenaRecord { .. }));
let heap_value = vm.materialize_arena_handles(arena_value);
vm.exit_request_scope(scope);
match heap_value {
Value::Record { shape_id, fields } => {
assert_eq!(shape_id, 0);
assert_eq!(fields.get("x"), Some(&Value::Int(7)));
assert_eq!(fields.get("y"), Some(&Value::Int(9)));
}
other => panic!("expected materialized Value::Record, got {other:?}"),
}
}
#[test]
fn materialize_arena_tuple_becomes_heap_tuple() {
let prog = tup_program("build_tup", 0, vec![
Op::PushConst(0), Op::PushConst(1),
Op::AllocArenaTuple { arity: 2 },
Op::Return,
]);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
assert!(matches!(arena_value, Value::ArenaTuple { .. }));
let heap_value = vm.materialize_arena_handles(arena_value);
vm.exit_request_scope(scope);
assert_eq!(heap_value, Value::Tuple(vec![Value::Int(11), Value::Int(13)]));
}
#[test]
fn materialize_recurses_into_list_elements() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_a = vm.invoke(0, vec![]).unwrap();
let arena_b = vm.invoke(0, vec![]).unwrap();
let list = Value::List([arena_a, arena_b].into_iter().collect());
let materialized = vm.materialize_arena_handles(list);
vm.exit_request_scope(scope);
match materialized {
Value::List(items) => {
assert_eq!(items.len(), 2);
for item in items {
assert!(matches!(item, Value::Record { .. }),
"list element should be heap Record, got {item:?}");
}
}
other => panic!("expected Value::List, got {other:?}"),
}
}
#[test]
fn materialize_recurses_into_record_field_value() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let inner = vm.invoke(0, vec![]).unwrap(); let mut fields: IndexMap<smol_str::SmolStr, Value> = IndexMap::new();
fields.insert("nested".into(), inner);
let outer = Value::Record { shape_id: 0, fields: Box::new(fields) };
let materialized = vm.materialize_arena_handles(outer);
vm.exit_request_scope(scope);
match materialized {
Value::Record { fields, .. } => {
let nested = fields.get("nested").expect("nested field present");
assert!(matches!(nested, Value::Record { .. }),
"nested field should be heap Record after materialization, got {nested:?}");
}
other => panic!("expected Value::Record outer, got {other:?}"),
}
}
#[test]
fn materialize_to_json_roundtrip_does_not_panic() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
let heap_value = vm.materialize_arena_handles(arena_value);
vm.exit_request_scope(scope);
let json = heap_value.to_json();
assert_eq!(json["x"], serde_json::json!(7));
assert_eq!(json["y"], serde_json::json!(9));
}
#[test]
fn materialize_is_idempotent() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
let once = vm.materialize_arena_handles(arena_value);
let twice = vm.materialize_arena_handles(once.clone());
vm.exit_request_scope(scope);
assert_eq!(once, twice);
}
#[test]
fn get_record_field_reads_arena_record() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
assert!(matches!(arena_value, Value::ArenaRecord { .. }),
"test precondition: arena handle, got {arena_value:?}");
let x = vm.get_record_field(&arena_value, "x");
let y = vm.get_record_field(&arena_value, "y");
let missing = vm.get_record_field(&arena_value, "z");
assert_eq!(x, Some(Value::Int(7)));
assert_eq!(y, Some(Value::Int(9)));
assert_eq!(missing, None);
vm.exit_request_scope(scope);
}
#[test]
fn get_record_field_reads_heap_record_uniformly() {
let prog = xy_program("noop", 0, vec![Op::PushConst(2), Op::Return]);
let vm = Vm::new(&prog);
let mut fields: IndexMap<smol_str::SmolStr, Value> = IndexMap::new();
fields.insert("x".into(), Value::Int(7));
fields.insert("y".into(), Value::Int(9));
let heap = Value::Record { shape_id: 0, fields: Box::new(fields) };
assert_eq!(vm.get_record_field(&heap, "x"), Some(Value::Int(7)));
assert_eq!(vm.get_record_field(&heap, "missing"), None);
}
#[test]
fn get_record_field_returns_none_on_non_record() {
let prog = xy_program("noop", 0, vec![Op::PushConst(2), Op::Return]);
let vm = Vm::new(&prog);
assert_eq!(vm.get_record_field(&Value::Int(42), "x"), None);
assert_eq!(vm.get_record_field(&Value::Unit, "x"), None);
}
#[test]
fn get_tuple_elem_reads_arena_tuple() {
let prog = tup_program("build_tup", 0, vec![
Op::PushConst(0), Op::PushConst(1),
Op::AllocArenaTuple { arity: 2 },
Op::Return,
]);
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_tup = vm.invoke(0, vec![]).unwrap();
assert!(matches!(arena_tup, Value::ArenaTuple { .. }));
assert_eq!(vm.get_tuple_elem(&arena_tup, 0), Some(Value::Int(11)));
assert_eq!(vm.get_tuple_elem(&arena_tup, 1), Some(Value::Int(13)));
assert_eq!(vm.get_tuple_elem(&arena_tup, 2), None);
vm.exit_request_scope(scope);
}
#[test]
fn get_tuple_elem_reads_heap_tuple_uniformly() {
let prog = tup_program("noop", 0, vec![Op::PushConst(0), Op::Return]);
let vm = Vm::new(&prog);
let heap = Value::Tuple(vec![Value::Int(11), Value::Int(13)]);
assert_eq!(vm.get_tuple_elem(&heap, 0), Some(Value::Int(11)));
assert_eq!(vm.get_tuple_elem(&heap, 5), None);
}
#[test]
fn value_to_json_matches_materialize_then_to_json_for_arena_record() {
let prog = handler_returning_xy_record();
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
let direct = vm.value_to_json(&arena_value);
let materialized = vm.materialize_arena_handles(arena_value).to_json();
vm.exit_request_scope(scope);
assert_eq!(direct, materialized);
assert_eq!(direct["x"], serde_json::json!(7));
assert_eq!(direct["y"], serde_json::json!(9));
}
#[test]
fn value_to_json_recurses_through_nested_arena_records() {
let constants = vec![
Const::FieldName("inner".into()),
Const::FieldName("status".into()),
Const::FieldName("a".into()),
Const::Int(200),
Const::Int(42),
];
let mut function_names = IndexMap::new();
function_names.insert("nested".to_string(), 0);
let prog = Arc::new(Program {
constants,
functions: vec![Function {
name: "nested".into(),
arity: 0,
locals_count: 0,
code: vec![
Op::PushConst(4), Op::AllocArenaRecord { shape_idx: 0, field_count: 1 }, Op::PushConst(3), Op::AllocArenaRecord { shape_idx: 1, field_count: 2 }, Op::Return,
],
effects: vec![],
body_hash: ZERO_BODY_HASH,
refinements: vec![],
field_ic_sites: 4,
}],
function_names,
module_aliases: IndexMap::new(),
entry: Some(0),
record_shapes: vec![vec![2], vec![0, 1]], });
let mut vm = Vm::new(&prog);
let scope = vm.enter_request_scope();
let arena_value = vm.invoke(0, vec![]).unwrap();
let direct = vm.value_to_json(&arena_value);
vm.exit_request_scope(scope);
assert_eq!(direct["status"], serde_json::json!(200));
assert_eq!(direct["inner"]["a"], serde_json::json!(42));
}
#[test]
fn value_to_json_passes_primitives_through() {
let prog = xy_program("noop", 0, vec![Op::PushConst(2), Op::Return]);
let vm = Vm::new(&prog);
assert_eq!(vm.value_to_json(&Value::Int(42)), serde_json::json!(42));
assert_eq!(vm.value_to_json(&Value::Bool(true)), serde_json::json!(true));
assert_eq!(vm.value_to_json(&Value::Unit), serde_json::Value::Null);
assert_eq!(vm.value_to_json(&Value::Str("hi".into())), serde_json::json!("hi"));
}