use std::ffi::CStr;
use std::marker::PhantomData;
use std::path::PathBuf;
use std::ptr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{OnceLock, RwLock};
use std::time::Instant;
use mozjs::jsapi;
use mozjs::rust::wrappers2;
use crate::stencil_cache::hash_key;
static CACHE_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
const CACHE_DIR_ENV: &str = "BAO_XDR_CACHE_DIR";
fn cache_dir() -> Option<PathBuf> {
if let Some(dir) = CACHE_DIR.read().expect("cache dir lock poisoned").clone() {
return Some(dir); }
std::env::var_os(CACHE_DIR_ENV).map(PathBuf::from)
}
#[doc(hidden)]
pub fn set_cache_dir_for_tests(dir: Option<PathBuf>) {
*CACHE_DIR.write().expect("cache dir lock poisoned") = dir;
}
fn cache_file(source: &str, filename: &CStr, line: u32) -> Option<PathBuf> {
let key = hash_key(source, filename, line);
Some(cache_dir()?.join(format!("{key:016x}.xdr")))
}
static BUILD_ID: OnceLock<Option<Vec<u8>>> = OnceLock::new();
const BUILD_ID_TAG: &[u8] = b"bao-stencil-xdr-1";
unsafe extern "C" fn bao_process_build_id(build_id: *mut jsapi::BuildIdCharVector) -> bool {
unsafe {
wrappers2::SetBuildId(
build_id,
BUILD_ID_TAG.as_ptr() as *const _,
BUILD_ID_TAG.len(),
)
}
}
fn process_build_id() -> Option<&'static [u8]> {
BUILD_ID
.get_or_init(|| unsafe {
unsafe {
jsapi::SetProcessBuildIdOp(Some(bao_process_build_id));
}
let vector = unsafe { wrappers2::CreateBuildIdCharVector() };
if vector.is_null() {
return None;
}
let ok = unsafe { wrappers2::GetScriptTranscodingBuildId(vector) };
let begin = unsafe { wrappers2::BuildIdCharVectorBegin(vector) };
let len = unsafe { wrappers2::BuildIdCharVectorLength(vector) };
let id = if ok && !begin.is_null() {
Some(unsafe { std::slice::from_raw_parts(begin, len) }.to_vec())
} else {
None
};
unsafe { wrappers2::DestroyBuildIdCharVector(vector) };
id
})
.as_deref()
}
#[doc(hidden)]
#[derive(Default)]
pub struct XdrCounters {
pub hits: u64,
pub miss_absent: u64,
pub miss_corrupt: u64,
pub miss_stale: u64,
pub miss_decode_failed: u64,
pub disabled: u64,
pub stores: u64,
pub store_failed: u64,
pub decode_us: u64,
}
macro_rules! counters {
($($field:ident => $static_name:ident),+ $(,)?) => {
$(static $static_name: AtomicU64 = AtomicU64::new(0);)+
impl XdrCounters {
fn snapshot() -> Self {
Self { $($field: $static_name.load(Ordering::Relaxed),)+ }
}
}
};
}
counters!(
hits => HITS,
miss_absent => MISS_ABSENT,
miss_corrupt => MISS_CORRUPT,
miss_stale => MISS_STALE,
miss_decode_failed => MISS_DECODE_FAILED,
disabled => DISABLED,
stores => STORES,
store_failed => STORE_FAILED,
decode_us => DECODE_US,
);
#[doc(hidden)]
pub fn xdr_counters() -> XdrCounters {
XdrCounters::snapshot()
}
const MAGIC: &[u8; 8] = b"BAOXDR1\n";
struct Reader<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Reader<'a> {
fn new(bytes: &'a [u8]) -> Self {
Reader { bytes, pos: 0 }
}
fn take(&mut self, n: usize) -> Option<&'a [u8]> {
let end = self.pos.checked_add(n)?;
if end > self.bytes.len() {
return None;
}
let out = &self.bytes[self.pos..end];
self.pos = end;
Some(out)
}
fn take_u32(&mut self) -> Option<u32> {
Some(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
}
fn take_u64(&mut self) -> Option<u64> {
Some(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
}
}
fn put_u32(out: &mut Vec<u8>, v: u32) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_u64(out: &mut Vec<u8>, v: u64) {
out.extend_from_slice(&v.to_le_bytes());
}
fn put_bytes(out: &mut Vec<u8>, v: &[u8]) {
put_u64(out, v.len() as u64);
out.extend_from_slice(v);
}
fn put_bytes32(out: &mut Vec<u8>, v: &[u8]) {
put_u32(out, v.len() as u32);
out.extend_from_slice(v);
}
fn parse_entry<'a>(
bytes: &'a [u8],
build_id: &[u8],
source: &str,
filename: &CStr,
line: u32,
) -> Result<&'a [u8], &'static str> {
let mut r = Reader::new(bytes);
if r.take(MAGIC.len()) != Some(&MAGIC[..]) {
return Err("corrupt");
}
let id_len = r.take_u32().ok_or("corrupt")? as usize;
let stored_id = r.take(id_len).ok_or("corrupt")?;
if stored_id != build_id {
return Err("stale");
}
let file_len = r.take_u32().ok_or("corrupt")? as usize;
let stored_file = r.take(file_len).ok_or("corrupt")?;
if stored_file != filename.to_bytes() {
return Err("corrupt");
}
if r.take_u32().ok_or("corrupt")? != line {
return Err("corrupt");
}
let src_len = r.take_u64().ok_or("corrupt")? as usize;
let stored_src = r.take(src_len).ok_or("corrupt")?;
if stored_src != source.as_bytes() {
return Err("corrupt");
}
let xdr_len = r.take_u64().ok_or("corrupt")? as usize;
let xdr = r.take(xdr_len).ok_or("corrupt")?;
if r.pos != bytes.len() {
return Err("corrupt");
}
Ok(xdr)
}
fn transcode_range(bytes: &[u8]) -> jsapi::TranscodeRange {
let base = bytes.as_ptr() as *mut u8;
jsapi::TranscodeRange {
_phantom_0: PhantomData,
mStart: jsapi::mozilla::RangedPtr {
_phantom_0: PhantomData,
mPtr: base,
},
mEnd: jsapi::mozilla::RangedPtr {
_phantom_0: PhantomData,
mPtr: base.wrapping_add(bytes.len()),
},
}
}
fn decode_options() -> jsapi::ReadOnlyDecodeOptions {
jsapi::ReadOnlyDecodeOptions {
borrowBuffer: false,
usePinnedBytecode: false,
introducerFilename_: jsapi::ConstUTF8CharsZ { data_: ptr::null() },
introductionType: ptr::null(),
introductionLineno: 0,
introductionOffset: 0,
}
}
pub unsafe fn load(
cx: *mut jsapi::JSContext,
source: &str,
filename: &CStr,
line: u32,
) -> Option<*mut jsapi::Stencil> {
let Some(build_id) = process_build_id() else {
DISABLED.fetch_add(1, Ordering::Relaxed);
return None;
};
let Some(path) = cache_file(source, filename, line) else {
DISABLED.fetch_add(1, Ordering::Relaxed);
return None;
};
let Ok(bytes) = std::fs::read(path) else {
MISS_ABSENT.fetch_add(1, Ordering::Relaxed);
return None;
};
let xdr = match parse_entry(&bytes, build_id, source, filename, line) {
Ok(xdr) => xdr,
Err("stale") => {
MISS_STALE.fetch_add(1, Ordering::Relaxed);
return None;
}
Err(_) => {
MISS_CORRUPT.fetch_add(1, Ordering::Relaxed);
return None;
}
};
let opts = decode_options();
let range = transcode_range(xdr);
let mut decoded: *mut jsapi::Stencil = ptr::null_mut();
let t0 = Instant::now();
let result = unsafe { jsapi::DecodeStencil(cx, &opts, &range, &mut decoded) };
DECODE_US.fetch_add(t0.elapsed().as_micros() as u64, Ordering::Relaxed);
if result != jsapi::TranscodeResult::Ok || decoded.is_null() {
MISS_DECODE_FAILED.fetch_add(1, Ordering::Relaxed);
return None;
}
HITS.fetch_add(1, Ordering::Relaxed);
Some(decoded)
}
pub unsafe fn store(
cx: *mut jsapi::JSContext,
stencil: *mut jsapi::Stencil,
source: &str,
filename: &CStr,
line: u32,
) {
let Some(build_id) = process_build_id() else {
DISABLED.fetch_add(1, Ordering::Relaxed);
return;
};
let Some(path) = cache_file(source, filename, line) else {
DISABLED.fetch_add(1, Ordering::Relaxed);
return;
};
if !unsafe { jsapi::IsStencilCacheable(stencil) } {
return;
}
let buffer = unsafe { wrappers2::CreateTranscodeBuffer() };
if buffer.is_null() {
STORE_FAILED.fetch_add(1, Ordering::Relaxed);
return;
}
let result = unsafe { jsapi::EncodeStencil(cx, stencil, buffer) };
let (begin, len) = unsafe {
(
wrappers2::TranscodeBufferBegin(buffer),
wrappers2::TranscodeBufferLength(buffer),
)
};
let xdr = if result == jsapi::TranscodeResult::Ok && !begin.is_null() && len > 0 {
Some(unsafe { std::slice::from_raw_parts(begin, len) }.to_vec())
} else {
None
};
unsafe { wrappers2::DestroyTranscodeBuffer(buffer) };
let Some(xdr) = xdr else {
STORE_FAILED.fetch_add(1, Ordering::Relaxed);
return;
};
let mut entry = Vec::with_capacity(MAGIC.len() + 8 + build_id.len() + filename.to_bytes().len() + source.len() + xdr.len());
entry.extend_from_slice(MAGIC);
put_bytes32(&mut entry, build_id);
put_bytes32(&mut entry, filename.to_bytes());
put_u32(&mut entry, line);
put_bytes(&mut entry, source.as_bytes());
put_bytes(&mut entry, &xdr);
if let Some(dir) = path.parent() {
let _ = std::fs::create_dir_all(dir);
}
let tmp = path.with_extension(format!("tmp.{}", std::process::id()));
let written = std::fs::write(&tmp, &entry).and_then(|()| std::fs::rename(&tmp, &path));
match written {
Ok(()) => STORES.fetch_add(1, Ordering::Relaxed),
Err(_) => {
let _ = std::fs::remove_file(&tmp);
STORE_FAILED.fetch_add(1, Ordering::Relaxed)
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::JsContext;
use crate::stencil_cache::{clear_thread_cache, evaluate_script_cached, thread_cache_len};
use crate::value::{jsval_to_jsvalue, JsValue};
use mozjs::jsval::UndefinedValue;
use mozjs::rooted;
use std::ffi::CString;
use std::sync::atomic::AtomicU32;
static TEST_DIR_SEQ: AtomicU32 = AtomicU32::new(0);
fn fresh_test_dir(tag: &str) -> PathBuf {
let n = TEST_DIR_SEQ.fetch_add(1, Ordering::Relaxed);
let thread: String = std::thread::current()
.name()
.unwrap_or("t")
.chars()
.map(|c| if c.is_ascii_alphanumeric() || c == '_' || c == '-' { c } else { '_' })
.collect();
let dir = std::env::temp_dir().join(format!(
"bao-xdr-test-{}-{}-{tag}-{n}",
std::process::id(),
thread
));
let _ = std::fs::remove_dir_all(&dir);
set_cache_dir_for_tests(Some(dir.clone()));
dir
}
fn make_jsvalue_fresh_realm(
src: &str,
filename: &str,
) -> Result<JsValue, String> {
let mut ctx = JsContext::for_test().map_err(|e| e.message)?;
let mut cx = ctx.cx();
let global_ptr = ctx
.ensure_realm_global(&mut cx, None)
.map_err(|e| e.message)?;
rooted!(&in(cx) let global = global_ptr);
let c_filename = CString::new(filename).unwrap();
rooted!(&in(cx) let mut rval = UndefinedValue());
evaluate_script_cached(&mut cx, global.handle(), src, &c_filename, 1, rval.handle_mut())
.map_err(|_| "eval failed".to_string())?;
unsafe { Ok(jsval_to_jsvalue(cx.raw_cx_no_gc(), rval.get())) }
}
fn heavy_payload(tag: u64, functions: usize) -> String {
let mut src = String::new();
for i in 0..functions {
src.push_str(&format!(
"function f{tag}_{i}(x) {{ var s = 0; for (var j = 0; j < 8; j++) {{ s += x * {i} + j; }} return s; }}\n"
));
}
src.push_str(&format!("globalThis.__xdr_tag = {tag};\n"));
src.push_str(&format!("f{tag}_0(2) + f{tag}_1(3)\n"));
src
}
fn entry_files(dir: &PathBuf) -> Vec<PathBuf> {
let mut out: Vec<PathBuf> = std::fs::read_dir(dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| p.extension().map_or(false, |e| e == "xdr"))
.collect();
out.sort();
out
}
fn tmp_files(dir: &PathBuf) -> Vec<PathBuf> {
std::fs::read_dir(dir)
.unwrap()
.filter_map(|e| e.ok())
.map(|e| e.path())
.filter(|p| {
p.file_name()
.and_then(|n| n.to_str())
.map_or(false, |n| n.contains(".tmp."))
})
.collect()
}
fn number_value(v: &JsValue) -> f64 {
match v {
JsValue::Number(n) => *n,
other => panic!("expected number, got {other:?}"),
}
}
#[test]
fn c1_disk_hit_is_execution_equal_to_direct_compile() {
let dir = fresh_test_dir("c1");
let src = heavy_payload(1, 24);
let first = make_jsvalue_fresh_realm(&src, "<xdr-c1>").unwrap();
let dbg = xdr_counters();
eprintln!(
"[c1dbg] dir={:?} exists={} stores={} failed={} disabled={} misses_absent={}",
dir, dir.exists(), dbg.stores, dbg.store_failed, dbg.disabled, dbg.miss_absent
);
assert_eq!(entry_files(&dir).len(), 1, "store must persist one entry");
let c = xdr_counters();
assert_eq!(c.stores, 1, "exactly one store");
clear_thread_cache();
let hits_before = xdr_counters().hits;
let second = make_jsvalue_fresh_realm(&src, "<xdr-c1>").unwrap();
let c = xdr_counters();
assert_eq!(c.hits, hits_before + 1, "second eval must be a disk hit");
assert_eq!(
number_value(&first),
number_value(&second),
"C1: decoded execution must equal direct compile"
);
assert_eq!(thread_cache_len(), 1);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn c3_truncated_entry_falls_back_and_self_heals() {
let dir = fresh_test_dir("c3trunc");
let src = heavy_payload(3, 24);
make_jsvalue_fresh_realm(&src, "<xdr-c3>").unwrap();
let path = &entry_files(&dir)[0];
let full = std::fs::read(path).unwrap();
std::fs::write(path, &full[..full.len() / 2]).unwrap();
clear_thread_cache();
let c0 = xdr_counters();
let value = make_jsvalue_fresh_realm(&src, "<xdr-c3>").unwrap();
let c = xdr_counters();
assert_eq!(c.miss_corrupt, c0.miss_corrupt + 1, "truncation = corrupt miss");
assert_eq!(c.hits, c0.hits, "corrupt entry must not hit");
assert!(number_value(&value) >= 0.0);
assert_eq!(entry_files(&dir).len(), 1);
clear_thread_cache();
let hits_before = xdr_counters().hits;
let again = make_jsvalue_fresh_realm(&src, "<xdr-c3>").unwrap();
assert_eq!(xdr_counters().hits, hits_before + 1, "healed entry hits");
assert!(number_value(&again) >= 0.0);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn c3_stale_build_id_is_a_miss() {
let dir = fresh_test_dir("c3stale");
let src = heavy_payload(4, 24);
make_jsvalue_fresh_realm(&src, "<xdr-c3s>").unwrap();
let path = &entry_files(&dir)[0];
let mut bytes = std::fs::read(path).unwrap();
let id_start = 8 + 4;
bytes[id_start] ^= 0xff;
std::fs::write(path, &bytes).unwrap();
clear_thread_cache();
let c0 = xdr_counters();
let value = make_jsvalue_fresh_realm(&src, "<xdr-c3s>").unwrap();
let c = xdr_counters();
assert_eq!(c.miss_stale, c0.miss_stale + 1, "id flip = stale miss");
assert_eq!(c.hits, c0.hits, "stale entry must not hit");
assert!(number_value(&value) >= 0.0, "fallback still evaluates");
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn c4_atomic_write_leaves_no_temp_and_ignores_leftovers() {
let dir = fresh_test_dir("c4");
let src = heavy_payload(5, 24);
make_jsvalue_fresh_realm(&src, "<xdr-c4>").unwrap();
assert_eq!(entry_files(&dir).len(), 1);
assert!(
tmp_files(&dir).is_empty(),
"completed store must not leave temp files"
);
let leftover = dir.join(".deadbeef.tmp999");
std::fs::write(&leftover, b"BAOXDR1\npartial").unwrap();
clear_thread_cache();
let value = make_jsvalue_fresh_realm(&src, "<xdr-c4>").unwrap();
assert!(number_value(&value) >= 0.0);
assert!(leftover.exists(), "leftover temp is not consumed");
let c = xdr_counters();
assert!(c.stores >= 1);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn c2_cold_start_decode_beats_recompile_on_heavy_payload() {
let dir = fresh_test_dir("c2");
let src = heavy_payload(6, 240);
let t0 = Instant::now();
make_jsvalue_fresh_realm(&src, "<xdr-c2>").unwrap();
let compile_us = t0.elapsed().as_micros() as u64;
clear_thread_cache();
let hits_before = xdr_counters().hits;
let t1 = Instant::now();
make_jsvalue_fresh_realm(&src, "<xdr-c2>").unwrap();
let decode_us = t1.elapsed().as_micros() as u64;
assert_eq!(xdr_counters().hits, hits_before + 1, "cold eval = disk hit");
let c = xdr_counters();
assert!(c.decode_us > 0, "decode time must be recorded");
assert!(
decode_us < compile_us,
"C2: decode-served cold start ({decode_us}µs) must beat compile-served ({compile_us}µs)"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn store_io_failure_is_non_fatal() {
let dir = fresh_test_dir("c4io");
let src = heavy_payload(7, 24);
make_jsvalue_fresh_realm(&src, "<xdr-io>").unwrap();
let path = &entry_files(&dir)[0];
std::fs::remove_file(path).unwrap();
std::fs::create_dir(path).unwrap();
clear_thread_cache();
let c0 = xdr_counters();
let value = make_jsvalue_fresh_realm(&src, "<xdr-io>").unwrap();
let c = xdr_counters();
assert!(number_value(&value) >= 0.0, "eval succeeds despite store failure");
let failed = c.store_failed - c0.store_failed;
let absent = c.miss_absent - c0.miss_absent;
assert!(
failed + absent >= 1,
"the failure path must be observable (store_failed={failed}, absent={absent})"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn w31_default_disabled_layer_is_zero_disk_zero_drift() {
if std::env::var_os("BAO_XDR_CACHE_DIR").is_some() {
eprintln!("[w31] BAO_XDR_CACHE_DIR set in environment — skipping");
return;
}
set_cache_dir_for_tests(None);
let src = heavy_payload(11, 24);
let c0 = xdr_counters();
let first = make_jsvalue_fresh_realm(&src, "<w31-off>").unwrap();
let c1 = xdr_counters();
assert_eq!(
c1.disabled,
c0.disabled + 2,
"cold eval must exercise (and gate off) both load and store"
);
assert_eq!(c1.stores, c0.stores, "no disk store may happen");
assert_eq!(c1.miss_absent, c0.miss_absent, "no disk read may happen");
assert_eq!(thread_cache_len(), 1);
let hits_before = crate::stencil_cache::thread_cache_counters().0;
let second = make_jsvalue_fresh_realm(&src, "<w31-off>").unwrap();
let hits_after = crate::stencil_cache::thread_cache_counters().0;
assert!(hits_after > hits_before, "memory layer still hits");
assert_eq!(number_value(&first), number_value(&second));
let c2 = xdr_counters();
assert_eq!(c2.stores, c0.stores);
assert_eq!(c2.miss_absent, c0.miss_absent);
}
#[test]
fn w31_decoded_stencil_matches_plain_compile_across_multiple_realms() {
let dir = fresh_test_dir("w31fp");
let fp = r#"
(function() {
function r5(t) { return Math.round(t / 5) * 5; }
var orig = Date.now;
Date.now = function() { return r5(orig()); };
globalThis.__fp = {
d: Date.now.toString().length > 0,
c: (function() { var v = 40; return function() { return v + 2; }; })()(),
n: Object.keys(globalThis).filter(function(k) { return k.indexOf('__fp') === 0; }).sort()
};
})();
globalThis.__fp2 = 11;
JSON.stringify(globalThis.__fp)
"#;
let mut src = String::new();
while src.len() + fp.len() < crate::stencil_cache::MIN_CACHED_SOURCE_BYTES {
src.push_str("var __pad_w31 = 1;\n");
}
src.push_str(fp);
make_jsvalue_fresh_realm(&src, "<w31-fp>").unwrap();
assert_eq!(entry_files(&dir).len(), 1);
const REALMS: usize = 3;
let mut plain_prints = Vec::new();
for _ in 0..REALMS {
let mut ctx = JsContext::for_test().unwrap();
let v = ctx.eval(&src, "<w31-fp").unwrap();
match v {
JsValue::String(s) => plain_prints.push(s),
other => panic!("plain returned {other:?}"),
}
}
let mut decoded_prints = Vec::new();
for _ in 0..REALMS {
clear_thread_cache();
let hits_before = xdr_counters().hits;
let v = make_jsvalue_fresh_realm(&src, "<w31-fp>").unwrap();
assert_eq!(xdr_counters().hits, hits_before + 1, "each round = disk hit");
match v {
JsValue::String(s) => decoded_prints.push(s),
other => panic!("decoded returned {other:?}"),
}
}
assert!(
plain_prints.windows(2).all(|w| w[0] == w[1]),
"plain path must be deterministic"
);
assert_eq!(
plain_prints[0], decoded_prints[0],
"W31: decode fingerprint must equal plain-compile fingerprint"
);
assert!(
decoded_prints.windows(2).all(|w| w[0] == w[1]),
"decoded stencil must instantiate identically in every realm"
);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn xdr_bench_child() {
if std::env::var_os("BAO_XDR_BENCH_CHILD").is_none() {
return; }
set_cache_dir_for_tests(None); use mozjs::jsval::UndefinedValue;
use mozjs::rooted;
let src = heavy_payload(13, 2000); let mut ctx = JsContext::for_test().unwrap();
let mut cx = ctx.cx();
let global_ptr = ctx.ensure_realm_global(&mut cx, None).unwrap();
rooted!(&in(cx) let global = global_ptr);
let c_filename = CString::new("<w31-bench>").unwrap();
rooted!(&in(cx) let mut rval = UndefinedValue());
let c0 = xdr_counters();
let t0 = Instant::now();
evaluate_script_cached(
&mut cx,
global.handle(),
&src,
&c_filename,
1,
rval.handle_mut(),
)
.expect("bench eval");
let us = t0.elapsed().as_micros() as u64;
let c = xdr_counters();
let outcome = if c.hits > c0.hits {
"warm-hit"
} else if c.stores > c0.stores {
"cold-store"
} else {
"no-disk"
};
let v = unsafe { jsval_to_jsvalue(cx.raw_cx_no_gc(), rval.get()) };
assert!(number_value(&v) >= 0.0);
println!("XDRBENCH outcome={outcome} us={us}");
}
#[test]
fn w31_cold_warm_two_process_bench() {
let dir = fresh_test_dir("w31bench");
set_cache_dir_for_tests(None); let exe = std::env::current_exe().unwrap();
let run_child = || -> (String, u64) {
let out = std::process::Command::new(&exe)
.args(["--exact", "xdr_cache::tests::xdr_bench_child", "--nocapture"])
.env("BAO_XDR_BENCH_CHILD", "1")
.env("BAO_XDR_CACHE_DIR", &dir)
.output()
.expect("spawn bench child");
assert!(
out.status.success(),
"child failed:\n{}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8_lossy(&out.stdout);
let line = stdout
.lines()
.find(|l| l.starts_with("XDRBENCH"))
.unwrap_or_else(|| panic!("no XDRBENCH line in child output:\n{stdout}"));
let outcome = line
.split(' ')
.find_map(|p| p.strip_prefix("outcome="))
.unwrap()
.to_string();
let us: u64 = line
.split(' ')
.find_map(|p| p.strip_prefix("us="))
.unwrap()
.parse()
.unwrap();
(outcome, us)
};
let (cold_outcome, cold_us) = run_child();
let (warm_outcome, warm_us) = run_child();
eprintln!("[w31] cold={cold_us}µs ({cold_outcome}) warm={warm_us}µs ({warm_outcome})");
assert_eq!(cold_outcome, "cold-store", "first child must compile+store");
assert_eq!(warm_outcome, "warm-hit", "second child must disk-hit");
assert!(
warm_us < cold_us,
"W31: warm decode ({warm_us}µs) must beat cold compile ({cold_us}µs)"
);
let _ = std::fs::remove_dir_all(&dir);
}
}