#![cfg(feature = "jit")]
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use fusevm::{Chunk, ChunkBuilder, NumOp, Op, VMResult, Value, VM};
fn narrow(n: i64, bits: u32) -> i64 {
let sh = 64 - bits;
(n << sh) >> sh
}
fn width_range(bits: u32) -> (i64, i64) {
if bits >= 64 {
return (i64::MIN, i64::MAX);
}
let hi = (1i64 << (bits - 1)) - 1;
(-(hi + 1), hi)
}
fn width_hook(bits: u32, calls: Arc<AtomicUsize>) -> fusevm::NumericHook {
Arc::new(move |op, a, b| {
calls.fetch_add(1, Ordering::Relaxed);
let (x, y) = (a.to_int(), b.to_int());
let wide = match op {
NumOp::Add => x.wrapping_add(y),
NumOp::Sub => x.wrapping_sub(y),
NumOp::Mul => x.wrapping_mul(y),
NumOp::Neg => x.wrapping_neg(),
other => return Err(format!("width hook: unexpected {other:?}")),
};
Ok(Value::Int(narrow(wide, bits)))
})
}
fn binop_chunk(a: i64, b: i64, op: Op) -> Chunk {
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(a), 0);
bd.emit(Op::LoadInt(b), 0);
bd.emit(op, 0);
bd.build()
}
fn run_width(chunk: Chunk, bits: u32, hook: fusevm::NumericHook) -> Value {
let mut vm = VM::new(chunk);
vm.enable_tracing_jit();
vm.set_numeric_hook(hook);
let (lo, hi) = width_range(bits);
vm.set_fixnum_range(lo, hi);
match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
}
}
fn wrapped_binop_chunk(a: i64, b: i64, op: Op, bits: u32) -> Chunk {
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(a), 0);
bd.emit(Op::LoadInt(b), 0);
bd.emit(op, 0);
let sh = (64 - bits) as i64;
bd.emit(Op::LoadInt(sh), 0);
bd.emit(Op::Shl, 0);
bd.emit(Op::LoadInt(sh), 0);
bd.emit(Op::Shr, 0);
bd.build()
}
fn run_plain(chunk: Chunk) -> Value {
let mut vm = VM::new(chunk);
vm.enable_tracing_jit();
match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
}
}
#[test]
fn bytecode_sign_extend_reproduces_fixed_width_wrapping() {
const I32_MAX: i64 = i32::MAX as i64;
const I32_MIN: i64 = i32::MIN as i64;
let cases: &[(u32, i64, Op, i64, i64)] = &[
(32, I32_MAX, Op::Add, 1, I32_MIN),
(32, I32_MIN, Op::Sub, 1, I32_MAX),
(32, 1_000_000, Op::Mul, 1_000_000, -727_379_968),
(32, 9_993_973, Op::Mul, -490, -602_079_474),
(8, 127, Op::Add, 1, -128),
(8, -128, Op::Sub, 1, 127),
(16, 32767, Op::Add, 1, -32768),
(16, 1000, Op::Mul, 1000, 16960),
(32, 2_000_000, Op::Add, 3_000_000, 5_000_000),
(8, 3, Op::Mul, 4, 12),
];
for (bits, a, op, b, want) in cases {
let (bits, a, b, want) = (*bits, *a, *b, *want);
let got = run_plain(wrapped_binop_chunk(a, b, op.clone(), bits));
assert_eq!(
got,
Value::Int(want),
"{bits}-bit {a} {op:?} {b} via Shl/Shr must match the reference language"
);
}
}
#[test]
fn bytecode_sign_extend_survives_jit_compilation() {
for i in 1..=40 {
let got = run_plain(wrapped_binop_chunk(i32::MAX as i64, 1, Op::Add, 32));
assert_eq!(
got,
Value::Int(i32::MIN as i64),
"run {i}: native code disagreed with the interpreter's sign-extend"
);
}
}
#[test]
fn bitand_mask_reproduces_unsigned_fixed_width_wrapping() {
let masked = |a: i64, op: Op, b: i64, mask: i64| -> Value {
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(a), 0);
bd.emit(Op::LoadInt(b), 0);
bd.emit(op, 0);
bd.emit(Op::LoadInt(mask), 0);
bd.emit(Op::BitAnd, 0);
run_plain(bd.build())
};
assert_eq!(masked(0, Op::Sub, 1, 0xFF), Value::Int(255));
assert_eq!(masked(255, Op::Add, 1, 0xFF), Value::Int(0));
assert_eq!(masked(0, Op::Sub, 1, 0xFFFF), Value::Int(65535));
assert_eq!(
masked(0, Op::Sub, 1, 0xFFFF_FFFF),
Value::Int(4_294_967_295)
);
assert_eq!(masked(200, Op::Add, 7, 0xFF), Value::Int(207));
}
#[test]
fn sign_extend_lets_int_and_long_coexist_without_any_vm_setting() {
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(i32::MAX as i64), 0);
bd.emit(Op::LoadInt(1), 0);
bd.emit(Op::Add, 0);
bd.emit(Op::LoadInt(i32::MAX as i64), 0);
bd.emit(Op::LoadInt(1), 0);
bd.emit(Op::Add, 0);
bd.emit(Op::LoadInt(32), 0);
bd.emit(Op::Shl, 0);
bd.emit(Op::LoadInt(32), 0);
bd.emit(Op::Shr, 0);
let mut vm = VM::new(bd.build());
vm.enable_tracing_jit();
let int_result = match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.pop().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
};
assert_eq!(
int_result,
Value::Int(i32::MIN as i64),
"the int site must wrap to 32 bits"
);
assert_eq!(
vm.stack.last(),
Some(&Value::Int(2_147_483_648)),
"the long site, in the same chunk, must stay exact"
);
}
#[test]
fn narrowed_range_plus_hook_reproduces_fixed_width_wrapping() {
const I32_MAX: i64 = i32::MAX as i64;
const I32_MIN: i64 = i32::MIN as i64;
let cases: &[(u32, i64, Op, i64, i64)] = &[
(32, I32_MAX, Op::Add, 1, I32_MIN),
(32, I32_MIN, Op::Sub, 1, I32_MAX),
(32, 1_000_000, Op::Mul, 1_000_000, -727_379_968),
(8, 127, Op::Add, 1, -128),
(8, -128, Op::Sub, 1, 127),
(16, 32767, Op::Add, 1, -32768),
(16, 1000, Op::Mul, 1000, 16960),
];
for (bits, a, op, b, want) in cases {
let (bits, a, b, want) = (*bits, *a, *b, *want);
let calls = Arc::new(AtomicUsize::new(0));
let hook = width_hook(bits, calls.clone());
let got = run_width(binop_chunk(a, b, op.clone()), bits, hook);
assert_eq!(
got,
Value::Int(want),
"{bits}-bit {a} {op:?} {b}: fusevm must agree with the reference language"
);
assert_eq!(
calls.load(Ordering::Relaxed),
1,
"{bits}-bit {a} {op:?} {b}: exactly one delegation"
);
}
}
#[test]
fn fixed_width_wrapping_survives_jit_compilation() {
let calls = Arc::new(AtomicUsize::new(0));
let hook = width_hook(32, calls.clone());
for i in 1..=40 {
let got = run_width(binop_chunk(i32::MAX as i64, 1, Op::Add), 32, hook.clone());
assert_eq!(
got,
Value::Int(i32::MIN as i64),
"run {i}: native code let the i64 result escape"
);
}
assert_eq!(
calls.load(Ordering::Relaxed),
40,
"every run must delegate, warm or cold"
);
}
#[test]
fn in_range_arithmetic_never_reaches_the_width_hook() {
let calls = Arc::new(AtomicUsize::new(0));
let hook = width_hook(32, calls.clone());
for _ in 0..40 {
let got = run_width(binop_chunk(2_000_000, 3_000_000, Op::Add), 32, hook.clone());
assert_eq!(got, Value::Int(5_000_000));
}
assert_eq!(
calls.load(Ordering::Relaxed),
0,
"in-range 32-bit arithmetic must stay native"
);
}
#[test]
fn sited_hook_separates_int_and_long_sites_in_one_chunk() {
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(i32::MAX as i64), 0);
bd.emit(Op::LoadInt(1), 0);
let int_site = bd.emit(Op::Add, 0);
bd.emit(Op::Pop, 0);
bd.emit(Op::LoadInt(i32::MAX as i64), 0);
bd.emit(Op::LoadInt(1), 0);
let long_site = bd.emit(Op::Add, 0);
let chunk = bd.build();
let widths: std::collections::HashMap<usize, u32> =
[(int_site, 32), (long_site, 64)].into_iter().collect();
let hook: fusevm::SitedNumericHook = Arc::new(move |call| {
let bits = *widths.get(&call.ip).unwrap_or(&64);
let (x, y) = (call.a.to_int(), call.b.to_int());
let wide = match call.op {
NumOp::Add => x.wrapping_add(y),
other => return Err(format!("unexpected {other:?}")),
};
Ok(Value::Int(narrow(wide, bits)))
});
let mut vm = VM::new(chunk);
vm.enable_tracing_jit();
vm.set_sited_numeric_hook(hook);
vm.set_fixnum_range(i32::MIN as i64, i32::MAX as i64);
let got = match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
};
assert_eq!(
got,
Value::Int(2_147_483_648),
"the long site must keep the exact i64 result"
);
let mut bd = ChunkBuilder::new();
bd.emit(Op::LoadInt(i32::MAX as i64), 0);
bd.emit(Op::LoadInt(1), 0);
let only_site = bd.emit(Op::Add, 0);
assert_eq!(only_site, int_site, "site index must match the first chunk");
let widths2: std::collections::HashMap<usize, u32> = [(int_site, 32)].into_iter().collect();
let hook2: fusevm::SitedNumericHook = Arc::new(move |call| {
let bits = *widths2.get(&call.ip).unwrap_or(&64);
Ok(Value::Int(narrow(
call.a.to_int().wrapping_add(call.b.to_int()),
bits,
)))
});
let mut vm = VM::new(bd.build());
vm.set_sited_numeric_hook(hook2);
vm.set_fixnum_range(i32::MIN as i64, i32::MAX as i64);
let got = match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
};
assert_eq!(got, Value::Int(i32::MIN as i64), "the int site must wrap");
}
#[test]
fn default_policy_still_wraps_at_i64_with_no_hook() {
let mut vm = VM::new(binop_chunk(i64::MAX, 1, Op::Add));
vm.enable_tracing_jit();
let got = match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
};
assert_eq!(got, Value::Int(i64::MIN), "default policy must wrap at i64");
let mut vm = VM::new(binop_chunk(4_000_000_000, 4_000_000_000, Op::Mul));
let got = match vm.run() {
VMResult::Ok(v) => v,
VMResult::Halted => vm.stack.last().cloned().unwrap_or(Value::Undef),
VMResult::Error(e) => panic!("vm error: {e}"),
};
assert_eq!(
got,
Value::Int(4_000_000_000_i64.wrapping_mul(4_000_000_000)),
"64-bit wrapping is the default and matches Java `long`"
);
}
#[test]
fn shifts_bypass_the_width_policy_and_must_be_lowered_by_the_frontend() {
let calls = Arc::new(AtomicUsize::new(0));
let hook = width_hook(32, calls.clone());
let got = run_width(binop_chunk(1, 31, Op::Shl), 32, hook.clone());
assert_eq!(
got,
Value::Int(2_147_483_648),
"Shl is i64-wide; the frontend, not the VM, owns int-width shifts"
);
assert_eq!(
calls.load(Ordering::Relaxed),
0,
"Shl never consults the numeric hook"
);
let got = run_width(binop_chunk(1, 32, Op::Shl), 32, hook);
assert_eq!(got, Value::Int(4_294_967_296));
}