#![allow(clippy::approx_constant)]
#![cfg(feature = "jit-disk-cache")]
use std::path::PathBuf;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard, OnceLock};
use fusevm::{ChunkBuilder, JitCompiler, Op, VMResult, Value, VM};
fn serial() -> MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(|e| e.into_inner())
}
fn fresh_dir(tag: &str) -> PathBuf {
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
let dir = std::env::temp_dir().join(format!(
"fusevm_jit_cache_{}_{}_{}_{}",
tag,
std::process::id(),
n,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
let _ = std::fs::remove_dir_all(&dir);
dir
}
fn build(ops: &[(Op, u32)]) -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
for (op, line) in ops {
b.emit(op.clone(), *line);
}
b.build()
}
fn run_with_cache(chunk: &fusevm::Chunk, dir: &PathBuf, slots: &[i64]) -> Option<Value> {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
let r = jit.try_run_linear(chunk, slots);
jit.set_jit_cache_dir(None);
r
}
#[test]
fn disk_cache_leaf_matches_interp() {
let _g = serial();
let dir = fresh_dir("leaf");
let chunk = build(&[
(Op::LoadInt(2), 1),
(Op::LoadInt(3), 1),
(Op::Add, 1),
(Op::LoadInt(4), 1),
(Op::Mul, 1),
]);
assert_eq!(run_with_cache(&chunk, &dir, &[]), Some(Value::Int(20)));
let entries: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().map_or(false, |x| x == "fjit"))
.collect();
assert_eq!(entries.len(), 1, "expected exactly one cached .fjit file");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_pow_uses_host_reloc() {
let _g = serial();
let dir = fresh_dir("pow");
let chunk = build(&[(Op::LoadInt(2), 1), (Op::LoadInt(10), 1), (Op::Pow, 1)]);
assert_eq!(run_with_cache(&chunk, &dir, &[]), Some(Value::Int(1024)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_float() {
let _g = serial();
let dir = fresh_dir("float");
let chunk = build(&[
(Op::LoadFloat(1.5), 1),
(Op::LoadFloat(2.5), 1),
(Op::Add, 1),
]);
match run_with_cache(&chunk, &dir, &[]) {
Some(Value::Float(f)) => assert!((f - 4.0).abs() < 1e-10),
other => panic!("expected Float(4.0), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_slots() {
let _g = serial();
let dir = fresh_dir("slots");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::GetSlot(1), 1), (Op::Add, 1)]);
assert_eq!(
run_with_cache(&chunk, &dir, &[100, 200]),
Some(Value::Int(300))
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_roundtrip_second_load_from_disk() {
let _g = serial();
let dir = fresh_dir("roundtrip");
let chunk = build(&[(Op::LoadInt(7), 1), (Op::LoadInt(6), 1), (Op::Mul, 1)]);
let first = run_with_cache(&chunk, &dir, &[]);
assert_eq!(first, Some(Value::Int(42)));
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let second = std::thread::spawn(move || run_with_cache(&chunk2, &dir2, &[]))
.join()
.unwrap();
assert_eq!(second, Some(Value::Int(42)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_corrupted_file_rejected() {
let _g = serial();
let dir = fresh_dir("corrupt");
let chunk = build(&[(Op::LoadInt(40), 1), (Op::LoadInt(2), 1), (Op::Add, 1)]);
std::fs::create_dir_all(&dir).unwrap();
assert_eq!(run_with_cache(&chunk, &dir, &[]), Some(Value::Int(42)));
for e in std::fs::read_dir(&dir).unwrap().flatten() {
if e.path().extension().map_or(false, |x| x == "fjit") {
std::fs::write(e.path(), b"not a real blob").unwrap();
}
}
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let r = std::thread::spawn(move || run_with_cache(&chunk2, &dir2, &[]))
.join()
.unwrap();
assert_eq!(r, Some(Value::Int(42)));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_on_by_default_and_opt_out() {
let _g = serial();
let jit = JitCompiler::new();
jit.set_jit_cache_dir(None);
let saved = std::env::var_os("FUSEVM_JIT_CACHE_DIR");
std::env::remove_var("FUSEVM_JIT_CACHE_DIR");
let def = jit.jit_cache_dir();
assert!(
def.as_ref().map_or(false, |p| p.ends_with("fusevm-jit")),
"expected default dir ending in fusevm-jit, got {def:?}"
);
std::env::set_var("FUSEVM_JIT_CACHE_DIR", "off");
assert_eq!(jit.jit_cache_dir(), None);
match saved {
Some(v) => std::env::set_var("FUSEVM_JIT_CACHE_DIR", v),
None => std::env::remove_var("FUSEVM_JIT_CACHE_DIR"),
}
}
#[test]
fn disk_cache_in_memory_still_correct_when_disabled() {
let _g = serial();
let jit = JitCompiler::new();
let saved = std::env::var_os("FUSEVM_JIT_CACHE_DIR");
std::env::set_var("FUSEVM_JIT_CACHE_DIR", "off");
jit.set_jit_cache_dir(None);
assert_eq!(jit.jit_cache_dir(), None);
let chunk = build(&[(Op::LoadInt(21), 1), (Op::Dup, 1), (Op::Add, 1)]);
assert_eq!(jit.try_run_linear(&chunk, &[]), Some(Value::Int(42)));
match saved {
Some(v) => std::env::set_var("FUSEVM_JIT_CACHE_DIR", v),
None => std::env::remove_var("FUSEVM_JIT_CACHE_DIR"),
}
}
#[test]
fn disk_cache_set_and_get_dir() {
let _g = serial();
let jit = JitCompiler::new();
let dir = fresh_dir("api");
jit.set_jit_cache_dir(Some(dir.clone()));
assert_eq!(jit.jit_cache_dir(), Some(dir));
jit.set_jit_cache_dir(None);
}
fn block_sum_loop() -> fusevm::Chunk {
let mut b = ChunkBuilder::new();
b.emit(Op::PushFrame, 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::LoadInt(0), 1);
b.emit(Op::SetSlot(1), 1);
b.emit(Op::GetSlot(0), 1);
b.emit(Op::GetSlot(1), 1);
b.emit(Op::Add, 1);
b.emit(Op::SetSlot(0), 1);
b.emit(Op::PreIncSlotVoid(1), 1);
b.emit(Op::SlotLtIntJumpIfFalse(1, 100, 12), 1);
b.emit(Op::Jump(5), 1);
b.emit(Op::GetSlot(0), 1);
b.build()
}
fn has_tagged_file(dir: &PathBuf, tag: &str) -> bool {
let needle = format!(".{tag}.fjit");
std::fs::read_dir(dir)
.map(|rd| {
rd.flatten()
.any(|e| e.file_name().to_string_lossy().ends_with(needle.as_str()))
})
.unwrap_or(false)
}
fn run_block_with_cache(chunk: &fusevm::Chunk, dir: &PathBuf) -> Option<i64> {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
let mut slots = vec![0i64; 4];
let r = jit.try_run_block_eager(chunk, &mut slots);
jit.set_jit_cache_dir(None);
r
}
#[test]
fn disk_cache_block_matches_and_persists() {
let _g = serial();
let dir = fresh_dir("block");
let chunk = block_sum_loop();
assert_eq!(run_block_with_cache(&chunk, &dir), Some(4950));
assert!(
has_tagged_file(&dir, "blk"),
"expected a .blk.fjit block cache file to be written"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_block_roundtrip_second_thread() {
let _g = serial();
let dir = fresh_dir("block_rt");
let chunk = block_sum_loop();
assert_eq!(run_block_with_cache(&chunk, &dir), Some(4950));
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let second = std::thread::spawn(move || run_block_with_cache(&chunk2, &dir2))
.join()
.unwrap();
assert_eq!(second, Some(4950));
let _ = std::fs::remove_dir_all(&dir);
}
fn 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 run_trace_vm(chunk: &fusevm::Chunk) -> i64 {
let mut vm = VM::new(chunk.clone());
vm.enable_tracing_jit();
{
let frame = vm.frames.last_mut().unwrap();
while frame.slots.len() < 1 {
frame.slots.push(Value::Int(0));
}
}
match vm.run() {
VMResult::Ok(Value::Int(n)) => n,
other => panic!("expected Int result, got {other:?}"),
}
}
#[test]
fn disk_cache_trace_matches_and_persists() {
let _g = serial();
let dir = fresh_dir("trace");
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
let (chunk, _anchor) = counter_loop(200);
assert_eq!(run_trace_vm(&chunk), 200);
assert!(
has_tagged_file(&dir, "trc"),
"expected a .trc.fjit trace cache file to be written"
);
jit.set_jit_cache_dir(None);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_trace_roundtrip_second_thread() {
let _g = serial();
let dir = fresh_dir("trace_rt");
let (chunk, _anchor) = counter_loop(200);
let dir_a = dir.clone();
let chunk_a = chunk.clone();
let first = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir_a.clone()));
let r = run_trace_vm(&chunk_a);
jit.set_jit_cache_dir(None);
r
})
.join()
.unwrap();
assert_eq!(first, 200);
assert!(has_tagged_file(&dir, "trc"));
let dir_b = dir.clone();
let chunk_b = chunk.clone();
let second = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir_b.clone()));
let r = run_trace_vm(&chunk_b);
jit.set_jit_cache_dir(None);
r
})
.join()
.unwrap();
assert_eq!(second, 200);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn cache_size_clear_and_max_bytes_api() {
let _g = serial();
let dir = fresh_dir("size_api");
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
for k in 0..6 {
let chunk = build(&[(Op::LoadInt(k), 1), (Op::LoadInt(1), 1), (Op::Add, 1)]);
let _ = jit.try_run_linear(&chunk, &[]);
}
let size = jit.jit_cache_size_bytes().expect("caching enabled");
assert!(size > 0, "cache should have grown after compiling chunks");
jit.set_jit_cache_max_bytes(Some(200));
let freed = jit.prune_jit_cache();
assert!(
freed > 0,
"prune should have evicted blobs to meet the 200B cap"
);
let after = jit.jit_cache_size_bytes().unwrap();
assert!(after <= 200, "expected ≤200B after prune, got {after}");
let removed = jit.clear_jit_cache();
assert!(removed > 0);
assert_eq!(jit.jit_cache_size_bytes().unwrap(), 0);
jit.set_jit_cache_max_bytes(None);
jit.set_jit_cache_dir(None);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn new_slot_ops_block_jit_matches_interp() {
use fusevm::Op::*;
let ops = vec![
(PushFrame, 1),
(LoadInt(0), 1),
(SetSlot(1), 1), (LoadInt(0), 1),
(SetSlot(0), 1), (PostIncSlot(0), 1), (GetSlot(1), 1),
(Add, 1),
(SetSlot(1), 1), (SlotLtIntJumpIfFalse(0, 5, 14), 1), (Jump(5), 1),
(Nop, 1),
(Nop, 1),
(Nop, 1),
(LoadInt(10), 1),
(SetSlot(2), 1),
(PreDecSlot(2), 1), (GetSlot(1), 1),
(Add, 1),
(SetSlot(1), 1),
(PostDecSlot(2), 1),
(GetSlot(1), 1),
(Add, 1),
(SetSlot(1), 1),
(GetSlot(1), 1), ];
let chunk = build(&ops);
let mut vm = VM::new(chunk.clone());
let interp = match vm.run() {
VMResult::Ok(v) => v.to_int(),
other => panic!("interp failed: {:?}", other),
};
let jit = JitCompiler::new();
let mut slots = vec![0i64; 4];
let native = jit
.try_run_block(&chunk, &mut slots)
.or_else(|| jit.try_run_block(&chunk, &mut slots))
.expect("block jit should compile new slot ops");
assert_eq!(interp, 28, "interp value");
assert_eq!(native, interp, "block jit must match interpreter");
}
#[test]
fn disk_cache_awk_div_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("awkdiv");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::GetSlot(1), 1), (Op::AwkDivJit, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![7i64, 2i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 3.5, "7.0 / 2.0 must be exactly 3.5");
}
other => panic!("expected Float(3.5) from AwkDivJit block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the AwkDivJit chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![7i64, 2i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => assert_eq!(f, 3.5, "reloaded blob must yield 3.5"),
other => panic!("expected reloaded Float(3.5), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_pow_float_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("powfloat");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::GetSlot(1), 1), (Op::PowFloat, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![2i64, 10i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 1024.0, "2.0 ** 10.0 must be exactly 1024.0");
}
other => panic!("expected Float(1024.0) from PowFloat block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the PowFloat chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![2i64, 10i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 1024.0, "reloaded blob must yield 1024.0")
}
other => panic!("expected reloaded Float(1024.0), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_sqrt_float_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("sqrtfloat");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::SqrtFloat, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![9i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 3.0, "sqrt(9.0) must be exactly 3.0");
}
other => panic!("expected Float(3.0) from SqrtFloat block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the SqrtFloat chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![2i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 2.0_f64.sqrt(), "reloaded blob must yield sqrt(2.0)")
}
other => panic!("expected reloaded Float(sqrt(2.0)), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_sin_float_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("sinfloat");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::SinFloat, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![0i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 0.0, "sin(0.0) must be exactly 0.0");
}
other => panic!("expected Float(0.0) from SinFloat block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the SinFloat chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![1i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 1.0_f64.sin(), "reloaded blob must yield sin(1.0)")
}
other => panic!("expected reloaded Float(sin(1.0)), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_log_float_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("logfloat");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::LogFloat, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![1i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 0.0, "log(1.0) must be exactly 0.0");
}
other => panic!("expected Float(0.0) from LogFloat block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the LogFloat chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![8i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 8.0_f64.ln(), "reloaded blob must yield log(8.0)")
}
other => panic!("expected reloaded Float(log(8.0)), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_abs_float_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("absfloat");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::AbsFloat, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![0i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 0.0, "abs(0.0) must be exactly 0.0");
}
other => panic!("expected Float(0.0) from AbsFloat block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the AbsFloat chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![-7i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Float(f)) => {
assert_eq!(f, 7.0, "reloaded blob must yield abs(-7.0) == 7.0")
}
other => panic!("expected reloaded Float(7.0), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_trunc_int_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("truncint");
let chunk = build(&[(Op::GetSlot(0), 1), (Op::SqrtFloat, 1), (Op::TruncInt, 1)]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![16i64];
assert_eq!(
jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]),
None,
"below threshold"
);
match jit.try_run_block_typed_kinded(&chunk, &mut slots, &[]) {
Some(fusevm::BlockNum::Int(n)) => {
assert_eq!(n, 4, "int(sqrt(16)) must be 4");
}
other => panic!("expected Int(4) from TruncInt block, got {other:?}"),
}
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the TruncInt chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let dir2 = dir.clone();
let chunk2 = chunk.clone();
let reloaded = std::thread::spawn(move || {
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir2));
jit.set_config(TraceJitConfig {
block_threshold: 0,
..TraceJitConfig::defaults()
});
let mut slots = vec![81i64];
jit.try_run_block_eager_typed_kinded(&chunk2, &mut slots, &[])
})
.join()
.unwrap();
match reloaded {
Some(fusevm::BlockNum::Int(n)) => {
assert_eq!(n, 9, "reloaded blob must yield int(sqrt(81)) == 9")
}
other => panic!("expected reloaded Int(9), got {other:?}"),
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_awk_int_block_persists() {
use fusevm::TraceJitConfig;
let _g = serial();
let dir = fresh_dir("awkint");
let chunk = build(&[
(Op::PushFrame, 1),
(Op::LoadFloat(0.0), 1),
(Op::SetSlot(0), 1),
(Op::GetSlot(0), 1),
(Op::LoadFloat(1.9), 1),
(Op::Add, 1),
(Op::AwkInt, 1),
(Op::Dup, 1),
(Op::SetSlot(0), 1),
(Op::Pop, 1),
]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let mut slots = vec![0i64; 1];
assert_eq!(
jit.try_run_block(&chunk, &mut slots),
None,
"below threshold"
);
let _native = jit
.try_run_block(&chunk, &mut slots)
.expect("AwkInt chunk must block-JIT compile");
assert_eq!(slots[0], 1, "slot 0 should be int(1.9) == 1");
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the AwkInt chunk, found {:?}",
std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.file_name())
.collect::<Vec<_>>()
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_awk_int_float_slot_block_persists() {
use fusevm::{SlotKind, TraceJitConfig};
let _g = serial();
let dir = fresh_dir("awkint_float");
let chunk = build(&[
(Op::GetSlot(0), 1),
(Op::LoadFloat(1.9), 1),
(Op::Add, 1),
(Op::AwkInt, 1),
(Op::Dup, 1),
(Op::SetSlot(0), 1),
(Op::Pop, 1),
]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let kinds = [SlotKind::Float];
let mut slots = vec![0i64; 1];
assert_eq!(
jit.try_run_block_kinded(&chunk, &mut slots, &kinds),
None,
"below threshold"
);
let _native = jit
.try_run_block_kinded(&chunk, &mut slots, &kinds)
.expect("AwkInt float-slot chunk must block-JIT compile");
assert_eq!(
f64::from_bits(slots[0] as u64),
1.0,
"slot 0 should hold f64 1.0 == int(1.9)"
);
let _ = jit.try_run_block_kinded(&chunk, &mut slots, &kinds);
assert_eq!(
f64::from_bits(slots[0] as u64),
2.0,
"second pass must be real f64 arithmetic: int(2.9) == 2"
);
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the float-slot AwkInt chunk"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn disk_cache_awk_sin_float_slot_block_persists() {
use fusevm::{SlotKind, TraceJitConfig};
let _g = serial();
let dir = fresh_dir("awksin_float");
let chunk = build(&[
(Op::GetSlot(0), 1),
(Op::AwkSin, 1),
(Op::LoadFloat(1.0), 1),
(Op::Add, 1),
(Op::Dup, 1),
(Op::SetSlot(0), 1),
(Op::Pop, 1),
]);
let jit = JitCompiler::new();
jit.set_jit_cache_dir(Some(dir.clone()));
jit.set_config(TraceJitConfig {
block_threshold: 1,
..TraceJitConfig::defaults()
});
let kinds = [SlotKind::Float];
let mut slots = vec![0i64; 1];
assert_eq!(
jit.try_run_block_kinded(&chunk, &mut slots, &kinds),
None,
"below threshold"
);
let _native = jit
.try_run_block_kinded(&chunk, &mut slots, &kinds)
.expect("AwkSin float-slot chunk must block-JIT compile");
assert_eq!(
f64::from_bits(slots[0] as u64),
1.0,
"slot 0 should hold f64 sin(0)+1 == 1.0"
);
let _ = jit.try_run_block_kinded(&chunk, &mut slots, &kinds);
let expected = 1.0f64.sin() + 1.0;
assert_eq!(
f64::from_bits(slots[0] as u64),
expected,
"second pass must call the sin libcall: sin(1)+1"
);
jit.set_jit_cache_dir(None);
let blk: Vec<_> = std::fs::read_dir(&dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".blk.") && n.ends_with(".fjit"))
})
.collect();
assert_eq!(
blk.len(),
1,
"expected one persisted blk.fjit for the float-slot AwkSin chunk"
);
let _ = std::fs::remove_dir_all(&dir);
}