use std::sync::Arc;
use tensor_wasm_core::types::TenantId;
use tensor_wasm_exec::engine::{EngineConfig, TensorWasmEngine};
use tensor_wasm_exec::executor::{SpawnConfig, TensorWasmExecutor, WasmArg};
use tensor_wasm_jit::cache::KernelCache;
use tensor_wasm_jit::detector::DetectorConfig;
const HOT_ADD_WAT: &str = r#"
(module
(memory (export "memory") 1)
(func (export "add") (param $a i32) (param $b i32) (result i32)
(local $v v128)
(loop $L
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
(local.set $v (i32x4.add (local.get $v) (local.get $v)))
)
(i32.add (local.get $a) (local.get $b))
)
)
"#;
const PLAIN_ADD_WAT: &str = r#"
(module
(memory (export "memory") 1)
(func (export "add") (param $a i32) (param $b i32) (result i32)
(i32.add (local.get $a) (local.get $b)))
)
"#;
fn aggressive_detector() -> DetectorConfig {
DetectorConfig {
v128_ratio_threshold: 0.05,
min_trip_count: 64,
}
}
fn first_i32(v: &serde_json::Value) -> i64 {
v.as_array()
.and_then(|a| a.first())
.and_then(|n| n.as_i64())
.unwrap_or_else(|| panic!("expected a single-element i32 array, got {v:?}"))
}
#[tokio::test]
async fn auto_offload_rewrites_and_runs_hot_module() {
let cache = Arc::new(KernelCache::new());
let cfg = EngineConfig {
auto_offload: true,
auto_offload_detector: Some(aggressive_detector()),
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
let exec = TensorWasmExecutor::new(engine).with_jit_cache(cache.clone());
let wasm = wat::parse_str(HOT_ADD_WAT).expect("parse hot add");
let tenant = TenantId(11);
let id = exec
.spawn_instance(SpawnConfig::for_tenant(tenant), &wasm)
.await
.expect("spawn with auto_offload must succeed");
assert!(
!cache.is_empty(),
"auto_offload rewrite must pre-populate the JIT kernel cache",
);
#[cfg(feature = "cuda")]
{
let out = exec
.call_export_with_args(id, "add", &[WasmArg::I32(7), WasmArg::I32(5)])
.await
.expect("offloaded add must run without trapping");
assert_eq!(
first_i32(&out),
7,
"offloaded add must return the dispatch stub's echoed first arg (7), \
not the CPU sum (12) — proving the trampoline executed",
);
}
#[cfg(not(feature = "cuda"))]
{
let err = exec
.call_export_with_args(id, "add", &[WasmArg::I32(7), WasmArg::I32(5)])
.await
.unwrap_err();
let msg = format!("{err:?}");
assert!(
msg.contains("wasm trap: wasm `unreachable` instruction executed")
|| msg.contains("unreachable"),
"no-CUDA offload must trap with unreachable, got: {msg}"
);
}
exec.terminate(id).await.expect("terminate");
}
#[tokio::test]
async fn non_offloadable_module_falls_back_gracefully() {
let cache = Arc::new(KernelCache::new());
let cfg = EngineConfig {
auto_offload: true,
auto_offload_detector: Some(aggressive_detector()),
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
let exec = TensorWasmExecutor::new(engine).with_jit_cache(cache.clone());
let wasm = wat::parse_str(PLAIN_ADD_WAT).expect("parse plain add");
let id = exec
.spawn_instance(SpawnConfig::for_tenant(TenantId(22)), &wasm)
.await
.expect("spawn must succeed and fall back to the original module");
assert!(
cache.is_empty(),
"a non-offloadable module must not pre-populate the JIT cache",
);
let out = exec
.call_export_with_args(id, "add", &[WasmArg::I32(7), WasmArg::I32(5)])
.await
.expect("plain add runs on the CPU path");
assert_eq!(
first_i32(&out),
12,
"non-offloaded add must return the real CPU sum",
);
exec.terminate(id).await.expect("terminate");
}
#[tokio::test]
async fn auto_offload_disabled_runs_original_body() {
let cache = Arc::new(KernelCache::new());
let engine = Arc::new(TensorWasmEngine::new().expect("engine"));
let exec = TensorWasmExecutor::new(engine).with_jit_cache(cache.clone());
let wasm = wat::parse_str(HOT_ADD_WAT).expect("parse hot add");
let id = exec
.spawn_instance(SpawnConfig::for_tenant(TenantId(33)), &wasm)
.await
.expect("spawn with auto_offload disabled must succeed");
assert!(
cache.is_empty(),
"with auto_offload disabled the cache must stay empty (consultation-only)",
);
let out = exec
.call_export_with_args(id, "add", &[WasmArg::I32(7), WasmArg::I32(5)])
.await
.expect("original CPU body runs");
assert_eq!(
first_i32(&out),
12,
"with the swap disabled the original CPU add must return the real sum",
);
exec.terminate(id).await.expect("terminate");
}
#[tokio::test]
async fn auto_offload_without_jit_cache_falls_back() {
let cfg = EngineConfig {
auto_offload: true,
auto_offload_detector: Some(aggressive_detector()),
..EngineConfig::default()
};
let engine = Arc::new(TensorWasmEngine::with_config(cfg).expect("engine"));
let exec = TensorWasmExecutor::new(engine);
let wasm = wat::parse_str(HOT_ADD_WAT).expect("parse hot add");
let id = exec
.spawn_instance(SpawnConfig::for_tenant(TenantId(44)), &wasm)
.await
.expect("spawn must fall back and succeed without a jit cache");
let out = exec
.call_export_with_args(id, "add", &[WasmArg::I32(7), WasmArg::I32(5)])
.await
.expect("fallback CPU body runs");
assert_eq!(
first_i32(&out),
12,
"without a jit cache the original CPU add must run (no unlinkable trampoline)",
);
exec.terminate(id).await.expect("terminate");
}