use tensor_wasm_jit::cache::KernelCache;
use tensor_wasm_jit::detector::DetectorConfig;
use tensor_wasm_jit::rewrite::{rewrite_wasm, RewriteOptions};
fn build_n_candidate_module(n: usize) -> Vec<u8> {
let mut wat = String::from("(module\n (memory 1)\n");
for i in 0..n {
wat.push_str(&format!(
" (func (export \"f{i}\") (result i32)\n (local $v v128)\n (loop $L\n",
));
for _ in 0..(16 + i) {
wat.push_str(" (local.set $v (i32x4.add (local.get $v) (local.get $v)))\n");
}
wat.push_str(" )\n (i32.const 0)\n )\n");
}
wat.push(')');
wat::parse_str(&wat).expect("synthetic module must parse")
}
fn permissive_opts() -> RewriteOptions {
RewriteOptions {
detector: DetectorConfig {
v128_ratio_threshold: 0.05,
min_trip_count: 64,
},
..RewriteOptions::default()
}
}
#[test]
fn parallel_analyse_preserves_source_order_for_16_candidates() {
let n = 16;
let wasm = build_n_candidate_module(n);
let cache = KernelCache::new();
let out = rewrite_wasm(&wasm, &permissive_opts(), &cache).expect("baseline rewrite");
assert_eq!(
out.offloaded_functions.len(),
n,
"all {n} synthetic candidates must be swapped; got {}",
out.offloaded_functions.len()
);
let indices: Vec<u32> = out
.offloaded_functions
.iter()
.map(|f| f.function_index)
.collect();
let expected_indices: Vec<u32> = (0..n as u32).collect();
assert_eq!(
indices, expected_indices,
"offloaded_functions must be in source order (ascending function_index)"
);
let mut fps: Vec<u64> = out
.offloaded_functions
.iter()
.map(|f| f.fingerprint)
.collect();
let unique_count_pre_sort = {
let mut seen = std::collections::HashSet::new();
fps.iter().filter(|fp| seen.insert(**fp)).count()
};
fps.sort_unstable();
fps.dedup();
assert_eq!(
fps.len(),
n,
"the {n} synthetic candidates must produce {n} distinct fingerprints — \
got {} unique (pre-sort count: {})",
fps.len(),
unique_count_pre_sort,
);
let baseline: Vec<(u32, u64)> = out
.offloaded_functions
.iter()
.map(|f| (f.function_index, f.fingerprint))
.collect();
for trial in 0..4 {
let cache_n = KernelCache::new();
let out_n = rewrite_wasm(&wasm, &permissive_opts(), &cache_n).expect("re-run rewrite");
let observed: Vec<(u32, u64)> = out_n
.offloaded_functions
.iter()
.map(|f| (f.function_index, f.fingerprint))
.collect();
assert_eq!(
observed, baseline,
"rewrite trial {trial} disagreed with baseline — parallel \
analyse leaked a reorder (observed = {:?}, baseline = {:?})",
observed, baseline,
);
}
}
#[test]
fn parallel_analyse_one_candidate_round_trips() {
let wasm = build_n_candidate_module(1);
let cache = KernelCache::new();
let out = rewrite_wasm(&wasm, &permissive_opts(), &cache).expect("rewrite");
assert_eq!(out.offloaded_functions.len(), 1);
assert_eq!(out.offloaded_functions[0].function_index, 0);
}
#[test]
fn parallel_analyse_zero_candidates_does_not_panic() {
let wat = r#"(module (memory 1))"#;
let wasm = wat::parse_str(wat).unwrap();
let cache = KernelCache::new();
let out = rewrite_wasm(&wasm, &permissive_opts(), &cache).expect("rewrite");
assert!(out.offloaded_functions.is_empty());
assert_eq!(out.total_defined_functions, 0);
}