use std::sync::Arc;
use std::time::Instant;
use lex_ast::canonicalize_program;
use lex_bytecode::vm::Vm;
use lex_bytecode::{compile_program, Op, Program, Value};
use lex_syntax::parse_source;
const SRC: &str = r#"
type Response = { status :: Int, total :: Int }
fn handle(user_id :: Int, item_id :: Int, qty :: Int) -> Response {
let v1 := { a: user_id, b: item_id, c: qty }
let v2 := { d: v1.a, e: v1.b, f: v1.c, g: v1.a * 2 }
let v3 := { h: v2.d, i: v2.e, j: v2.f, k: v2.g }
let v4 := { l: v3.h * 3, m: v3.i * 5, n: v3.j * 7, o: v3.k }
let v5 := { p: v4.l + v4.m, q: v4.n + v4.o, r: v4.l - v4.m }
let v6 := { s: v5.p + v5.q, t: v5.q + v5.r, u: v5.p - v5.r }
match v6.s > 0 {
true => { status: 200, total: v6.s + v6.t + v6.u },
false => { status: 400, total: 0 },
}
}
fn drive(n :: Int) -> Int {
match n {
0 => 0,
_ => {
let r := handle(n, 7, 3)
r.total + drive(n - 1)
},
}
}
"#;
fn compile_with_env(src: &str, no_stack_records: bool) -> Arc<Program> {
if no_stack_records {
unsafe { std::env::set_var("LEX_NO_STACK_RECORDS", "1"); }
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
lex_types::check_program(&stages).expect("typecheck");
let p = Arc::new(compile_program(&stages));
unsafe { std::env::remove_var("LEX_NO_STACK_RECORDS"); }
p
} else {
let prog = parse_source(src).expect("parse");
let stages = canonicalize_program(&prog);
lex_types::check_program(&stages).expect("typecheck");
Arc::new(compile_program(&stages))
}
}
fn count_record_sites(p: &Program) -> (usize, usize) {
let mut make_record = 0usize;
let mut alloc_stack = 0usize;
for f in &p.functions {
for op in &f.code {
match op {
Op::MakeRecord { .. } => make_record += 1,
Op::AllocStackRecord { .. } => alloc_stack += 1,
_ => {}
}
}
}
(make_record, alloc_stack)
}
#[test]
fn at_least_60_percent_of_records_on_stack() {
let p = compile_with_env(SRC, false);
let (make_record_sites, alloc_stack_sites) = count_record_sites(&p);
assert!(alloc_stack_sites > 0, "expected lowering to fire");
assert!(make_record_sites > 0, "expected at least one escaping record (the Response)");
let mut vm = Vm::new(&p);
vm.set_step_limit(u64::MAX);
let r = vm.call("drive", vec![Value::Int(200)]).unwrap();
assert!(matches!(r, Value::Int(_)), "expected Int return, got {r:?}");
let stack = vm.stack_record_allocs;
let heap = vm.heap_record_allocs;
let fallback = vm.stack_record_heap_fallbacks;
let total = stack + heap + fallback;
assert!(total > 0);
let rate = stack as f64 / total as f64;
println!(
"[#464 step 3] record alloc breakdown: stack={stack}, heap={heap}, \
fallback={fallback}, total={total}, stack_rate={:.2}%",
rate * 100.0
);
assert!(rate >= 0.60,
"stack-allocation rate {:.2}% is below the 60% acceptance bar",
rate * 100.0);
assert_eq!(fallback, 0,
"no budget exhaustion expected for this workload");
assert_eq!(stack, 200 * 6, "expected 6 stack records per handle × 200");
assert_eq!(heap, 200, "expected 1 heap record (Response) per handle × 200");
}
#[test]
#[ignore = "timing-based; see doc comment for rationale"]
fn lowering_speedup_at_least_1_3x() {
let enabled = compile_with_env(SRC, false);
let disabled = compile_with_env(SRC, true);
for prog in [&enabled, &disabled] {
let mut vm = Vm::new(prog);
vm.set_step_limit(u64::MAX);
let _ = vm.call("drive", vec![Value::Int(50)]).unwrap();
}
let n: i64 = 500;
let iters: usize = 200;
let time_arm = |prog: &Arc<Program>| -> f64 {
let t0 = Instant::now();
for _ in 0..iters {
let mut vm = Vm::new(prog);
vm.set_step_limit(u64::MAX);
std::hint::black_box(
vm.call("drive", vec![Value::Int(n)]).unwrap());
}
t0.elapsed().as_secs_f64()
};
let mut enabled_times = [time_arm(&enabled), time_arm(&enabled), time_arm(&enabled)];
let mut disabled_times = [time_arm(&disabled), time_arm(&disabled), time_arm(&disabled)];
enabled_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
disabled_times.sort_by(|a, b| a.partial_cmp(b).unwrap());
let enabled_t = enabled_times[0];
let disabled_t = disabled_times[0];
let ratio = disabled_t / enabled_t;
println!(
"[#464 step 3] response_build best-of-3: enabled={enabled_t:.4}s, \
disabled={disabled_t:.4}s, speedup={ratio:.2}×"
);
assert!(ratio >= 1.3,
"speedup {ratio:.2}× is below the 1.3× regression floor \
(issue acceptance bar is 1.5×; the criterion bench has the \
precise number)");
}