#![cfg(feature = "jit")]
use fusevm::{ChunkBuilder, JitCompiler, Op, TraceJitConfig, TraceMetadata, VMResult, Value, VM};
fn build_counter_loop(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
fn ensure_slots(vm: &mut VM, n: usize) {
let frame = vm.frames.last_mut().unwrap();
while frame.slots.len() < n {
frame.slots.push(Value::Int(0));
}
}
#[test]
fn trace_compiles_and_runs_hot_counter() {
let (chunk, anchor) = build_counter_loop(200);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 200, "loop should count to limit");
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace at anchor {} should have compiled after hot loop",
anchor
);
assert!(
!jit.trace_is_blacklisted(&chunk, anchor),
"trace should not be blacklisted on golden path"
);
}
#[test]
fn cold_loop_below_threshold_does_not_compile() {
let (chunk, anchor) = build_counter_loop(20);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
assert!(
!jit.trace_is_compiled(&chunk, anchor),
"cold loop should not produce a compiled trace"
);
}
#[test]
fn tracing_disabled_by_default() {
let (chunk, anchor) = build_counter_loop(500);
let mut vm = VM::new(chunk.clone());
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
assert!(
!jit.trace_is_compiled(&chunk, anchor),
"tracing JIT must be opt-in: a VM with default settings should never compile a trace"
);
}
#[test]
fn second_run_reuses_compiled_trace() {
let (chunk, anchor) = build_counter_loop(200);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace should be in cache after first run"
);
let mut vm2 = VM::new(chunk.clone());
vm2.enable_tracing_jit();
ensure_slots(&mut vm2, 1);
let result = vm2.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 200);
}
#[test]
fn float_slot_at_anchor_triggers_guard_mismatch() {
let (chunk, anchor) = build_counter_loop(150);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
let float_anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(120), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, float_anchor);
b.emit(Op::GetSlot(1), 1);
let float_chunk = b.build();
let mut vm = VM::new(float_chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 120, "loop must still produce correct result");
assert!(
jit.trace_is_compiled(&float_chunk, float_anchor),
"trace touching only int slot should compile despite a float slot 0 \
in the frame — entry guard only covers slots the trace references"
);
}
#[test]
fn ineligible_loop_body_aborts_recording() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::Pop, 1); b.emit(Op::Nop, 1); b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(80), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
assert_eq!(
match result {
VMResult::Ok(Value::Int(n)) => n,
_ => unreachable!(),
},
80
);
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}
#[test]
fn is_trace_eligible_rejects_non_closing_last_op() {
let jit = JitCompiler::new();
let ops_no_close = vec![Op::LoadInt(1), Op::LoadInt(2), Op::Add];
assert!(!jit.is_trace_eligible(&ops_no_close, 0));
let ops_wrong_target = vec![Op::LoadInt(1), Op::JumpIfTrue(99)];
assert!(!jit.is_trace_eligible(&ops_wrong_target, 0));
let ops_good = vec![
Op::PreIncSlotVoid(0),
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(jit.is_trace_eligible(&ops_good, 0));
}
#[test]
fn is_trace_eligible_rejects_internal_backward_jumps() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::JumpIfTrue(0), Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(!jit.is_trace_eligible(&ops, 0));
}
fn build_loop_with_constant_helper(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
let name = b.add_name("seven");
let skip = b.emit(Op::Jump(0), 1);
let helper_entry = b.current_pos();
b.emit(Op::LoadInt(7), 1);
b.emit(Op::ReturnValue, 1);
b.add_sub_entry(name, helper_entry);
let main_start = b.current_pos();
b.patch_jump(skip, main_start);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::Call(name, 0), 1);
b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
#[test]
fn inlined_constant_helper_compiles_and_runs() {
let (chunk, anchor) = build_loop_with_constant_helper(180);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 180);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace should compile with inlined helper call"
);
assert!(!jit.trace_is_blacklisted(&chunk, anchor));
}
fn build_loop_with_argpassing_helper(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
let name = b.add_name("double");
let skip = b.emit(Op::Jump(0), 1);
let helper_entry = b.current_pos();
b.emit(Op::SetSlot(0), 1); b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Mul, 1);
b.emit(Op::ReturnValue, 1);
b.add_sub_entry(name, helper_entry);
let main_start = b.current_pos();
b.patch_jump(skip, main_start);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1); b.emit(Op::Call(name, 1), 1);
b.emit(Op::Pop, 1); b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
#[test]
fn inlined_arg_passing_helper_runs_correctly() {
let (chunk, anchor) = build_loop_with_argpassing_helper(160);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 160);
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}
#[test]
fn call_builtin_in_loop_aborts_recording() {
fn zero_builtin(_vm: &mut VM, _argc: u8) -> Value {
Value::Int(0)
}
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::CallBuiltin(7, 0), 1); b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(120), 1);
b.emit(Op::NumLt, 1);
let jmp = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(jmp, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
vm.register_builtin(7, zero_builtin);
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
assert!(
!jit.trace_is_compiled(&chunk, anchor),
"Op::CallBuiltin in the loop body must abort recording"
);
}
#[test]
fn is_trace_eligible_rejects_unbalanced_frames() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::Call(0, 0),
Op::LoadInt(0),
Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(!jit.is_trace_eligible(&ops, 0));
let ops_underflow = vec![Op::PreIncSlotVoid(0), Op::ReturnValue, Op::JumpIfTrue(0)];
assert!(!jit.is_trace_eligible(&ops_underflow, 0));
}
#[test]
fn is_trace_eligible_accepts_callee_with_internal_branch() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::Call(0, 0),
Op::LoadInt(1),
Op::JumpIfFalse(99), Op::LoadInt(0),
Op::ReturnValue,
Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(jit.is_trace_eligible(&ops, 0));
}
#[test]
fn is_trace_eligible_accepts_balanced_inlined_call() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::Call(0, 1),
Op::SetSlot(0), Op::LoadInt(2),
Op::GetSlot(0),
Op::Mul,
Op::ReturnValue,
Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(jit.is_trace_eligible(&ops, 0));
}
#[test]
fn is_trace_eligible_rejects_callbuiltin() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::CallBuiltin(0, 0),
Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(!jit.is_trace_eligible(&ops, 0));
}
#[test]
fn is_trace_eligible_accepts_caller_internal_branch() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::LoadInt(1),
Op::JumpIfFalse(99), Op::Nop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(jit.is_trace_eligible(&ops, 0));
}
#[test]
fn is_trace_eligible_rejects_keep_variants() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::LoadInt(1),
Op::JumpIfTrueKeep(99), Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(!jit.is_trace_eligible(&ops, 0));
}
#[test]
fn is_trace_eligible_rejects_caller_backward_jump_to_anchor() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::Jump(0), Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(!jit.is_trace_eligible(&ops, 0));
}
fn build_loop_with_stable_branch(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumGt, 1);
let if_jmp = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::Nop, 1);
let after_if = b.current_pos();
b.patch_jump(if_jmp, after_if);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
#[test]
fn loop_with_caller_internal_branch_compiles_and_runs() {
let (chunk, anchor) = build_loop_with_stable_branch(140);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 140);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace with internal caller-frame branch should compile in phase 3"
);
assert!(!jit.trace_is_blacklisted(&chunk, anchor));
}
fn build_loop_with_data_dependent_branch(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(1), 1);
let if_jmp = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::PreIncSlotVoid(0), 1);
let after_if = b.current_pos();
b.patch_jump(if_jmp, after_if);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
fn build_loop_with_branching_helper(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
let name = b.add_name("clamp_pos");
let skip = b.emit(Op::Jump(0), 1);
let helper_entry = b.current_pos();
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumGt, 1);
let jif = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(0), 1);
let after_if_jmp = b.emit(Op::Jump(0), 1);
let else_arm = b.current_pos();
b.patch_jump(jif, else_arm);
b.emit(Op::LoadInt(0), 1);
let after_if = b.current_pos();
b.patch_jump(after_if_jmp, after_if);
b.emit(Op::ReturnValue, 1);
b.add_sub_entry(name, helper_entry);
let main_start = b.current_pos();
b.patch_jump(skip, main_start);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1); b.emit(Op::Call(name, 1), 1);
b.emit(Op::Pop, 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
fn build_loop_with_stack_at_branch(limit: i64) -> (fusevm::Chunk, usize) {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumGt, 1);
let jit = b.emit(Op::JumpIfTrue(0), 1);
b.emit(Op::Pop, 1);
let after_pop = b.emit(Op::Jump(0), 1);
let alt = b.current_pos();
b.patch_jump(jit, alt);
b.emit(Op::Pop, 1);
let done = b.current_pos();
b.patch_jump(after_pop, done);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(limit), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
(b.build(), anchor)
}
#[test]
fn loop_with_stack_at_internal_branch_compiles_and_runs() {
let (chunk, anchor) = build_loop_with_stack_at_branch(150);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 150);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace with non-empty abstract stack at branch should compile in phase 5"
);
}
#[test]
fn is_trace_eligible_accepts_branch_with_int_stack() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(0),
Op::LoadInt(7), Op::GetSlot(0),
Op::LoadInt(0),
Op::NumGt,
Op::JumpIfTrue(99), Op::Pop,
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(jit.is_trace_eligible(&ops, 0));
}
#[test]
fn config_default_round_trip() {
let jit = JitCompiler::new();
let original = jit.get_config();
let custom = TraceJitConfig {
trace_threshold: 5,
..original
};
jit.set_config(custom);
assert_eq!(jit.get_config().trace_threshold, 5);
jit.set_config(original);
assert_eq!(jit.get_config().trace_threshold, original.trace_threshold);
}
#[test]
fn lower_threshold_compiles_trace_with_fewer_iterations() {
let (chunk, anchor) = build_counter_loop(30);
let jit = JitCompiler::new();
let original = jit.get_config();
jit.set_config(TraceJitConfig {
trace_threshold: 5,
..original
});
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
assert_eq!(n, 30);
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace should compile with threshold=5"
);
jit.set_config(original);
}
#[test]
fn export_all_then_import_all_roundtrip() {
let (chunk, anchor) = build_counter_loop(180);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let exported = jit.trace_export_all(&chunk);
assert!(
!exported.is_empty(),
"at least one trace should be exportable"
);
assert!(
exported.iter().any(|m| m.anchor_ip == anchor),
"the installed trace's anchor should appear in the export"
);
let bytes = serde_json::to_vec(&exported).unwrap();
let decoded: Vec<TraceMetadata> = serde_json::from_slice(&bytes).unwrap();
let installed_count = jit.trace_import_all(&chunk, &decoded);
assert!(
installed_count >= 1,
"import_all should re-install at least the one trace; got {}",
installed_count
);
}
#[test]
fn float_slot_loop_compiles_and_runs() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadFloat(0.0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadFloat(1.0), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadFloat(100.5), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Float(n)) => n,
VMResult::Ok(Value::Int(n)) => n as f64,
other => panic!("expected Float, got {:?}", other),
};
assert!(
(n - 101.0).abs() < 1e-9,
"float counter should land at 101.0, got {}",
n
);
let jit = JitCompiler::new();
let _ = jit.trace_is_compiled(&chunk, anchor);
}
#[test]
fn chained_dispatch_observable_via_side_exit_count_no_bump_when_handled() {
let (chunk, anchor) = build_loop_with_data_dependent_branch(220);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
vm.frames.last_mut().unwrap().slots[1] = Value::Int(1);
let _ = vm.run();
}
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
let mut vm2 = VM::new(chunk.clone());
vm2.enable_tracing_jit();
ensure_slots(&mut vm2, 2);
let _ = vm2.run();
let _ = jit.trace_side_exit_count(&chunk, anchor);
}
#[test]
fn trace_loop_anchors_returns_metadata() {
let (chunk, anchor) = build_counter_loop(140);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let pair = jit.trace_loop_anchors(&chunk, anchor);
assert!(
pair.is_some(),
"anchors should be queryable for installed trace"
);
let (recorded_anchor, fallthrough) = pair.unwrap();
assert_eq!(recorded_anchor, anchor);
assert!(fallthrough > anchor);
}
#[test]
fn side_trace_install_with_kind_distinct_record_and_close() {
let (chunk, anchor) = build_counter_loop(120);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let meta = jit
.trace_export(&chunk, anchor)
.expect("export should succeed");
let record_anchor = anchor.wrapping_add(1000);
let installed = jit.trace_install_with_kind(
&chunk,
record_anchor,
meta.anchor_ip,
meta.fallthrough_ip,
&meta.ops,
&meta.recorded_ips,
&meta.slot_kinds_at_anchor,
);
assert!(
installed,
"trace_install_with_kind should accept record/close anchor split"
);
assert!(
jit.trace_is_compiled(&chunk, record_anchor),
"installed trace should be queryable at the synthetic record_anchor"
);
}
#[test]
fn loop_with_float_stack_at_branch_compiles_and_runs() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::LoadFloat(0.5), 1); b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumGt, 1);
let jit = b.emit(Op::JumpIfTrue(0), 1);
b.emit(Op::Pop, 1);
let after_pop = b.emit(Op::Jump(0), 1);
let alt = b.current_pos();
b.patch_jump(jit, alt);
b.emit(Op::Pop, 1);
let done = b.current_pos();
b.patch_jump(after_pop, done);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(120), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
assert_eq!(final_i, 120);
let jit_compiler = JitCompiler::new();
assert!(
jit_compiler.trace_is_compiled(&chunk, anchor),
"trace with Float on abstract stack at branch should compile in phase 5b"
);
}
#[test]
fn side_exit_count_observable_via_jit_compiler() {
let (chunk, anchor) = build_loop_with_data_dependent_branch(120);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
vm.frames.last_mut().unwrap().slots[1] = Value::Int(1);
let _ = vm.run();
}
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
let mut vm2 = VM::new(chunk.clone());
vm2.enable_tracing_jit();
ensure_slots(&mut vm2, 2);
let _ = vm2.run();
let side_exits = jit.trace_side_exit_count(&chunk, anchor);
assert!(
side_exits > 0,
"expected mid-trace side-exits to be observable; got {}",
side_exits
);
}
#[test]
fn trace_metadata_roundtrip_via_export_import() {
let (chunk, anchor) = build_counter_loop(180);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let meta: TraceMetadata = jit
.trace_export(&chunk, anchor)
.expect("trace should be exportable after install");
let serialized = serde_json::to_string(&meta).expect("TraceMetadata should serialize");
let deserialized: TraceMetadata =
serde_json::from_str(&serialized).expect("TraceMetadata should deserialize");
assert_eq!(deserialized.chunk_op_hash, chunk.op_hash);
assert_eq!(deserialized.anchor_ip, anchor);
assert_eq!(deserialized.ops, meta.ops);
assert_eq!(deserialized.recorded_ips, meta.recorded_ips);
assert!(jit.trace_import(&chunk, &deserialized));
}
#[test]
fn trace_import_rejects_chunk_hash_mismatch() {
let (chunk_a, anchor) = build_counter_loop(120);
{
let mut vm = VM::new(chunk_a.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let mut meta = jit
.trace_export(&chunk_a, anchor)
.expect("trace should be exportable");
meta.chunk_op_hash = meta.chunk_op_hash.wrapping_add(1);
assert!(
!jit.trace_import(&chunk_a, &meta),
"import must reject when chunk_op_hash mismatches"
);
}
#[test]
fn callee_with_internal_branch_compiles_and_runs() {
let (chunk, anchor) = build_loop_with_branching_helper(120);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {:?}", other),
};
assert_eq!(final_i, 120);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace inlining a branching callee should compile in phase 4"
);
assert!(!jit.trace_is_blacklisted(&chunk, anchor));
}
#[test]
fn deopt_from_callee_materializes_frame_correctly() {
let (chunk, anchor) = build_loop_with_branching_helper(80);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
assert_eq!(
vm.frames.len(),
1,
"frame stack must be balanced after loop completes (no leaked synthetic frames)"
);
}
#[test]
fn side_exit_fires_when_branch_flips() {
let (chunk, anchor) = build_loop_with_data_dependent_branch(160);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
vm.frames.last_mut().unwrap().slots[1] = Value::Int(1);
let result = vm.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
assert_eq!(final_i, 160);
}
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"trace must install during the recording run"
);
let mut vm2 = VM::new(chunk.clone());
vm2.enable_tracing_jit();
ensure_slots(&mut vm2, 2);
let result = vm2.run();
let final_i = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
assert_eq!(
final_i, 160,
"side-exit cleanup must restore correct slot state for the interpreter"
);
}
#[test]
fn threshold_zero_compiles_on_first_backedge() {
let (chunk, anchor) = build_counter_loop(20);
let jit = JitCompiler::new();
let original = jit.get_config();
jit.set_config(TraceJitConfig {
trace_threshold: 0,
..original
});
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
assert!(
jit.trace_is_compiled(&chunk, anchor),
"threshold=0 should compile after the first iteration"
);
jit.set_config(original);
}
#[test]
fn threshold_huge_never_compiles() {
let (chunk, anchor) = build_counter_loop(30);
let jit = JitCompiler::new();
let original = jit.get_config();
jit.set_config(TraceJitConfig {
trace_threshold: 1_000_000,
..original
});
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
assert!(
!jit.trace_is_compiled(&chunk, anchor),
"threshold=1M should not allow a 30-iteration loop to compile"
);
jit.set_config(original);
}
#[test]
fn max_trace_len_aborts_long_recording() {
let (chunk, anchor) = build_counter_loop(150);
let jit = JitCompiler::new();
let original = jit.get_config();
jit.set_config(TraceJitConfig {
max_trace_len: 3,
trace_threshold: 5,
..original
});
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
assert_eq!(n, 150);
assert!(
!jit.trace_is_compiled(&chunk, anchor),
"trace longer than max_trace_len must abort"
);
jit.set_config(original);
}
#[test]
fn arithmetic_ops_in_trace_compile_correctly() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(1), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Add, 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Mul, 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Sub, 1);
b.emit(Op::LoadInt(1024), 1);
b.emit(Op::Mod, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(200), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
let result = vm.run();
let final_v = match result {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int, got {:?}", other),
};
let mut i: i64 = 1;
for _ in 0..200 {
i = ((i + 3) * 2 - 1) % 1024;
}
assert_eq!(
final_v, i,
"trace-compiled arithmetic must match interpreter"
);
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}
#[test]
fn bitwise_ops_in_trace_compile_correctly() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0xCAFE_F00D), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(0xFF), 1);
b.emit(Op::BitAnd, 1);
b.emit(Op::LoadInt(0xA5A5), 1);
b.emit(Op::BitXor, 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Shl, 1);
b.emit(Op::LoadInt(0x1), 1);
b.emit(Op::BitOr, 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Shr, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(150), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
let result = vm.run();
let final_v = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
let mut x: i64 = 0xCAFE_F00D;
for _ in 0..150 {
x = (x & 0xFF) ^ 0xA5A5;
x = x.wrapping_shl(2 & 63);
x |= 0x1;
x = x.wrapping_shr(1 & 63);
}
assert_eq!(final_v, x, "bitwise trace must match interpreter");
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}
#[test]
fn comparison_ops_produce_correct_truthiness_in_trace() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Mod, 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::NumEq, 1);
let if_jmp = b.emit(Op::JumpIfFalse(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
let merge_jmp = b.emit(Op::Jump(0), 1);
let odd_arm = b.current_pos();
b.patch_jump(if_jmp, odd_arm);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(2), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
let merge = b.current_pos();
b.patch_jump(merge_jmp, merge);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(200), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let final_v = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
let mut i: i64 = 0;
while i < 200 {
if i % 2 == 0 {
i += 1;
} else {
i += 2;
}
}
assert_eq!(final_v, i);
}
#[test]
fn multiple_traces_in_same_chunk() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor1 = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(100), 1);
b.emit(Op::NumLt, 1);
let c1 = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(c1, anchor1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
let anchor2 = b.current_pos();
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(100), 1);
b.emit(Op::NumLt, 1);
let c2 = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(c2, anchor2);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
assert_eq!(n, 200);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, anchor1),
"first loop's trace should compile"
);
assert!(
jit.trace_is_compiled(&chunk, anchor2),
"second loop's trace should compile (independent cache entry)"
);
assert_ne!(
anchor1, anchor2,
"anchors must differ for independent loops"
);
}
#[test]
fn slot_index_at_max_boundary_compiles() {
let mut b = ChunkBuilder::new();
let high_slot: u16 = 63; b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(high_slot), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(high_slot), 1);
b.emit(Op::GetSlot(high_slot), 1);
b.emit(Op::LoadInt(120), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(high_slot), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 64);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
assert_eq!(n, 120);
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}
#[test]
fn slot_index_above_max_rejects() {
let jit = JitCompiler::new();
let ops = vec![
Op::PreIncSlotVoid(64),
Op::GetSlot(0),
Op::LoadInt(10),
Op::NumLt,
Op::JumpIfTrue(0),
];
assert!(
!jit.is_trace_eligible(&ops, 0),
"slot index >= MAX_TRACE_SLOT must reject eligibility"
);
}
#[test]
fn auto_dispatch_runs_block_eligible_chunk_via_block_jit() {
let (chunk, anchor) = build_counter_loop(200);
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
for _ in 0..15 {
let mut vm2 = VM::new(chunk.clone());
vm2.enable_tracing_jit();
ensure_slots(&mut vm2, 1);
let r = match vm2.run() {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
assert_eq!(r, 200);
}
let _ = jit.trace_is_compiled(&chunk, anchor);
}
#[test]
fn export_returns_none_when_no_trace_installed() {
let (chunk, anchor) = build_counter_loop(5);
let jit = JitCompiler::new();
assert!(
jit.trace_export(&chunk, anchor).is_none(),
"export of an unrecorded anchor should yield None"
);
}
#[test]
fn import_filters_to_matching_chunk_hash() {
let (chunk_a, anchor) = build_counter_loop(120);
{
let mut vm = VM::new(chunk_a.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let metas = jit.trace_export_all(&chunk_a);
assert!(!metas.is_empty());
let (chunk_b, _) = build_counter_loop(140); assert_ne!(chunk_a.op_hash, chunk_b.op_hash);
let installed = jit.trace_import_all(&chunk_b, &metas);
assert_eq!(
installed, 0,
"metadata for chunk A must not install on chunk B"
);
assert!(
!jit.trace_is_compiled(&chunk_b, anchor),
"no trace should land on the wrong chunk"
);
}
#[test]
fn config_partial_override_preserves_other_fields() {
let jit = JitCompiler::new();
let original = jit.get_config();
let custom = TraceJitConfig {
trace_threshold: 7,
..original
};
jit.set_config(custom);
let read_back = jit.get_config();
assert_eq!(read_back.trace_threshold, 7);
assert_eq!(read_back.max_side_exits, original.max_side_exits);
assert_eq!(
read_back.max_inline_recursion,
original.max_inline_recursion
);
assert_eq!(read_back.max_trace_chain, original.max_trace_chain);
assert_eq!(read_back.max_trace_len, original.max_trace_len);
jit.set_config(original);
}
#[test]
fn trace_eligible_minimum_loop_shape() {
let jit = JitCompiler::new();
let ops = vec![
Op::LoadInt(0),
Op::LoadInt(1),
Op::NumLt,
Op::JumpIfTrue(0), ];
assert!(jit.is_trace_eligible(&ops, 0));
}
#[test]
fn trace_eligible_rejects_empty_ops() {
let jit = JitCompiler::new();
let ops: Vec<Op> = vec![];
assert!(!jit.is_trace_eligible(&ops, 0));
}
#[test]
fn trace_loop_anchors_returns_none_for_uninstalled() {
let (chunk, anchor) = build_counter_loop(10);
let jit = JitCompiler::new();
assert!(
jit.trace_loop_anchors(&chunk, anchor).is_none(),
"loop_anchors should yield None when no trace is installed"
);
}
#[test]
fn trace_metadata_serde_round_trip_preserves_fields() {
let (chunk, anchor) = build_counter_loop(180);
{
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
}
let jit = JitCompiler::new();
let original = jit.trace_export(&chunk, anchor).unwrap();
let bytes = serde_json::to_vec(&original).unwrap();
let decoded: TraceMetadata = serde_json::from_slice(&bytes).unwrap();
assert_eq!(original.chunk_op_hash, decoded.chunk_op_hash);
assert_eq!(original.anchor_ip, decoded.anchor_ip);
assert_eq!(original.fallthrough_ip, decoded.fallthrough_ip);
assert_eq!(original.ops, decoded.ops);
assert_eq!(original.recorded_ips, decoded.recorded_ips);
assert_eq!(
original.slot_kinds_at_anchor.len(),
decoded.slot_kinds_at_anchor.len()
);
}
#[test]
fn negative_ints_in_trace_arithmetic() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(100), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(3), 1);
b.emit(Op::Sub, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(-200), 1);
b.emit(Op::NumGt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
let mut x: i64 = 100;
while x > -200 {
x -= 3;
}
assert_eq!(n, x);
}
#[test]
fn nested_loops_outer_traces_inner_unchanged() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1); let outer_anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
let inner_anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::LoadInt(60), 1);
b.emit(Op::NumLt, 1);
let inner_close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(inner_close, inner_anchor);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(60), 1);
b.emit(Op::NumLt, 1);
let outer_close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(outer_close, outer_anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 2);
let result = vm.run();
let n = match result {
VMResult::Ok(Value::Int(n)) => n,
_ => panic!("expected Int"),
};
assert_eq!(n, 60);
let jit = JitCompiler::new();
assert!(
jit.trace_is_compiled(&chunk, inner_anchor),
"inner loop should compile its own trace"
);
}
#[test]
fn trace_eligible_rejects_oversized_ops() {
let jit = JitCompiler::new();
let original = jit.get_config();
jit.set_config(TraceJitConfig {
max_trace_len: 4,
..original
});
let ops: Vec<Op> = (0..20)
.map(|_| Op::Nop)
.chain(std::iter::once(Op::JumpIfTrue(0)))
.collect();
assert!(
!jit.is_trace_eligible(&ops, 0),
"trace longer than max_trace_len must reject"
);
jit.set_config(original);
}
#[test]
fn empty_loop_body_stack_balanced_trace_compiles() {
let mut b = ChunkBuilder::new();
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
let anchor = b.current_pos();
b.emit(Op::PreIncSlotVoid(0), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::LoadInt(100), 1);
b.emit(Op::NumLt, 1);
let close = b.emit(Op::JumpIfTrue(0), 1);
b.patch_jump(close, anchor);
b.emit(Op::GetSlot(0), 1);
let chunk = b.build();
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
ensure_slots(&mut vm, 1);
let _ = vm.run();
let jit = JitCompiler::new();
assert!(jit.trace_is_compiled(&chunk, anchor));
}