//! The store of loaded parameter sets (`int_set_cache`) under the decodes that use it, //! `int_payload::decode_int` and `declared_cost`, for every capability that decodes //! against a set through the integer operator set. //! //! - A second decode through the same set is a hit, by the store's count, and gives the //! same bytes as a decode with the store off, for every committed vector that decodes //! against a set: `tiny`, `video_nk`, `audio` and `splat` of the int-payload corpus, //! the int_synth corpus, and the `wai.neural.int_mlicpp` vector. //! - Two sets whose members have the same names and whose bytes differ in one byte never //! share an entry. //! - A set the load refuses is refused again, with the same refusal, while a set one //! byte away from it is held, and it is never kept: for int_synth, a set the bounds //! pass refuses (load step 8); for the conditional codec and the MLIC++-topology codec, //! a set whose lowering leaves a layer no admissible input range (integer-operators //! §13.2). //! - Every refusal fixture of the int-payload corpus keeps its code and its detail with //! its set held, and a payload refused while it is parsed never reaches the store. //! - Under a small budget the least recently used set goes first, a set past the budget //! is not kept, and a budget of 0 neither keeps nor looks up. //! - Threads decoding one set at once get the same bytes, and one entry is kept. //! //! The store is one per process, so each test takes a lock and starts from an empty //! store with its counts at 0. #![cfg(all(feature = "video_int", feature = "audio_int", feature = "splat_int"))] use std::collections::BTreeMap; use std::path::PathBuf; use std::sync::{Mutex, MutexGuard}; use serde_json::Value; use wai::codecs as c; use wai::int_payload::{declared_cost, decode_int, resolve, IntDecoded}; use wai::int_set_cache::{LoadedSets, SetKey, Stats}; const DEFAULT_BUDGET: u64 = LoadedSets::DEFAULT_BUDGET; use wai::pin::HeldBytes; static SERIAL: Mutex<()> = Mutex::new(()); /// The store, to this test alone: empty, its counts at 0, its budget `budget`. fn store(budget: u64) -> MutexGuard<'static, ()> { let guard = SERIAL.lock().unwrap_or_else(|e| e.into_inner()); let s = LoadedSets::global(); s.set_budget(0); s.set_budget(budget); s.reset_stats(); guard } fn stats() -> Stats { LoadedSets::global().stats() } fn wai_dir() -> PathBuf { PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("..") } fn read(rel: &str) -> Vec { let p = wai_dir().join(rel); std::fs::read(&p).unwrap_or_else(|e| panic!("read {}: {e}", p.display())) } fn index() -> Value { serde_json::from_slice(&read("int-payload-conformance/vectors.json")).expect("the index parses") } /// A set: member name → bytes. type Set = BTreeMap>; fn set_of(entry: &Value) -> Set { entry["set"].as_object().into_iter().flatten().map(|(k, v)| (k.clone(), read(v.as_str().unwrap()))).collect() } fn view(set: &Set) -> BTreeMap { set.iter().map(|(k, v)| (k.clone(), v.as_slice())).collect() } /// A decode's output as bytes, or its refusal's code and detail. type Out = Result, (String, String)>; fn bytes(d: IntDecoded) -> Vec { match d { IntDecoded::Image { rgb, .. } => rgb, IntDecoded::Video { frames, .. } => frames.concat(), IntDecoded::Pcm { samples, .. } => samples.iter().flat_map(|s| s.to_le_bytes()).collect(), IntDecoded::Attr { bytes, .. } => bytes, other => panic!("{other:?}"), } } fn decode(cap: &str, payload: &[u8], set: &Set) -> Out { let v = view(set); let params = resolve(cap, None, &[HeldBytes::Set { listing: b"", files: &v }], None).expect("an unpinned set resolves"); decode_int(cap, payload, ¶ms).map(bytes).map_err(|e| (e.code().to_string(), e.detail().to_string())) } /// The same decode with the store off, then on again with the budget it had. A budget of /// 0 empties the store, so a test takes what it needs with the store off before it fills it. fn decode_off(cap: &str, payload: &[u8], set: &Set) -> Out { let s = LoadedSets::global(); let budget = s.budget(); s.set_budget(0); let out = decode(cap, payload, set); s.set_budget(budget); out } /// The capability constant `cap` names, and the lowering its driver lowers by. fn registered(cap: &str) -> (&'static str, &'static str) { match cap { c::CAP_NEURAL_INT_HYPER => (c::CAP_NEURAL_INT_HYPER, wai::int_ops::WHM1), c::CAP_VIDEO_INT_HYPER => (c::CAP_VIDEO_INT_HYPER, wai::int_ops::WHM1), c::CAP_AUDIO_INT_CODEC => (c::CAP_AUDIO_INT_CODEC, wai::int_ops::WAM1), c::CAP_SPLAT_INT_CODEC => (c::CAP_SPLAT_INT_CODEC, wai::int_ops::WHM1), c::CAP_NEURAL_INT_SYNTH => (c::CAP_NEURAL_INT_SYNTH, wai::int_ops::INT_SYNTH), c::CAP_NEURAL_INT_MLICPP => (c::CAP_NEURAL_INT_MLICPP, wai::int_ops::WMB1), other => panic!("{other} decodes against no set"), } } /// The key the decode of `cap` keeps `set` under. fn key(cap: &str, set: &Set) -> SetKey { let (cap, lowering) = registered(cap); // Every lowering of integer-operators §13 declares acc64, which each graph loads under. SetKey::of(cap, lowering, wai_ops::Profile::Acc64, &view(set)).expect("a set's names make a listing") } /// An `int_synth` payload (integer-payloads §13): `h`, `w`, `C` channels of side /// `2^s_log2`, a reserved 0, then the latents. fn synth_payload(h: u32, w: u32, ch: u16, s_log2: u8, latents: &[i8]) -> Vec { let mut p = Vec::new(); p.extend_from_slice(&h.to_le_bytes()); p.extend_from_slice(&w.to_le_bytes()); p.extend_from_slice(&ch.to_le_bytes()); p.extend_from_slice(&[s_log2, 0]); p.extend(latents.iter().map(|&v| v as u8)); p } fn synth(payload: &[u8], spec: &[u8]) -> Out { decode(c::CAP_NEURAL_INT_SYNTH, payload, &synth_set(spec)) } fn synth_off(payload: &[u8], spec: &[u8]) -> Out { decode_off(c::CAP_NEURAL_INT_SYNTH, payload, &synth_set(spec)) } fn synth_set(spec: &[u8]) -> Set { BTreeMap::from([("int_synth.json".to_string(), spec.to_vec())]) } /// The byte offsets of each convolution's multipliers and biases in a `WHM1` network /// (integer-payloads §9), with its output channels: `(out, mult, bias)`. fn whm1_rq(b: &[u8]) -> Vec<(usize, usize, usize)> { let u = |p: usize| u32::from_le_bytes(b[p..p + 4].try_into().unwrap()) as usize; let (mut p, mut out) = (20, Vec::new()); for _ in 0..u(16) { let tag = b[p]; p += 1; match tag { 0 | 1 => { let (inc, o, k) = (u(p), u(p + 4), u(p + 8)); p += if tag == 0 { 24 } else { 20 } + 2 * inc * o * k * k; out.push((o, p, p + 8 * o)); p += 16 * o; } 2 => p += 4 + 4 * u(p) * u(p) + 8 * u(p), 3 => {} 4 => p += 12, t => panic!("layer tag {t}"), } } assert_eq!(p, b.len(), "the walk ends at the network's last byte"); out } /// The committed vectors that decode against a set, with their capability, payload and set. fn vectors() -> Vec<(String, String, Vec, Set)> { let mut v: Vec<_> = index()["vectors"].as_array().unwrap().iter().filter(|v| !set_of(v).is_empty()).map(|v| { (v["name"].as_str().unwrap().to_string(), v["capability"].as_str().unwrap().to_string(), read(v["payload"].as_str().unwrap()), set_of(v)) }).collect(); let names: Vec = v.iter().map(|x| x.0.clone()).collect(); assert_eq!(names, ["tiny", "video_nk", "audio", "splat"], "the corpus's vectors with a set"); let hashes: Value = serde_json::from_slice(&read("neural-conformance/expected_hashes.json")).unwrap(); for name in hashes.as_object().unwrap().keys() { let dir = format!("neural-conformance/vectors/{name}"); v.push((format!("int_synth {name}"), c::CAP_NEURAL_INT_SYNTH.to_string(), read(&format!("{dir}/latent.bin")), synth_set(&read(&format!("{dir}/int_synth.json"))))); } v.push(("int_mlicpp".to_string(), c::CAP_NEURAL_INT_MLICPP.to_string(), mlicpp_payload(), mlicpp_set(read(MLICPP_BUNDLE)))); v } /// The `wai.neural.int_mlicpp` vector: its bundle, and the payload of its envelope. const MLICPP_BUNDLE: &str = "wai-rs/tests/vectors/int_mlicpp/model.wmb"; fn mlicpp_payload() -> Vec { wai::container::Wai::from_bytes(&read("wai-rs/tests/vectors/int_mlicpp/stream.wai")).unwrap().payload } fn mlicpp_set(bundle: Vec) -> Set { BTreeMap::from([("model.wmb".to_string(), bundle)]) } /// The byte offset of a `WMB1` bundle's section `k` (0 `h_s`, 1 `entropy`, 2 `g_s`, 3 the /// tables), past its length. fn wmb1_section(b: &[u8], k: usize) -> usize { let mut o = 4; for _ in 0..k { o += 4 + u32::from_le_bytes(b[o..o + 4].try_into().unwrap()) as usize; } o + 4 } #[test] fn a_second_decode_through_a_set_is_a_hit_and_gives_the_same_bytes() { let _s = store(DEFAULT_BUDGET); let vectors = vectors(); let offs: Vec> = vectors.iter().map(|(name, cap, payload, set)| decode_off(cap, payload, set).unwrap_or_else(|e| panic!("{name}: {e:?}"))).collect(); let mut n = 0; for ((name, cap, payload, set), off) in vectors.into_iter().zip(offs) { let s0 = stats(); let first = decode(&cap, &payload, &set); let s1 = stats(); let second = decode(&cap, &payload, &set); let s2 = stats(); assert_eq!(first.as_ref(), Ok(&off), "{name}: the decode that loads its set gives the bytes of the decode with the store off"); assert_eq!(second.as_ref(), Ok(&off), "{name}: the decode from the store gives the same bytes"); assert_eq!((s1.misses - s0.misses, s1.hits - s0.hits, s1.kept - s0.kept), (1, 0, 1), "{name}: the first decode loads its set and keeps it"); assert_eq!((s2.misses - s1.misses, s2.hits - s1.hits, s2.kept - s1.kept), (0, 1, 0), "{name}: the second is a hit"); assert!(LoadedSets::global().contains(&key(&cap, &set)), "{name}: kept under the SHA-256 of its members"); // The declared cost reads the same entry (int_synth and int_mlicpp have none). if cap != c::CAP_NEURAL_INT_SYNTH && cap != c::CAP_NEURAL_INT_MLICPP { let v = view(&set); let params = resolve(&cap, None, &[HeldBytes::Set { listing: b"", files: &v }], None).unwrap(); declared_cost(&cap, &payload, ¶ms).unwrap(); assert_eq!((stats().hits - s2.hits, stats().misses - s2.misses), (1, 0), "{name}: the declared cost finds the set loaded"); } println!("{name}: {} bytes alike with the store off, on (miss) and on (hit)", off.len()); n += 1; } assert_eq!(n, 9); // tiny and video_nk read the same three members under two capabilities: two entries. assert_eq!(stats().entries, 9); } #[test] fn sets_one_byte_apart_never_share_an_entry() { let _s = store(DEFAULT_BUDGET); // The conditional codec: g_s's last bias, channel 0, its fourth byte flipped. The // members keep their names. let tiny = index()["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "tiny").cloned().unwrap(); let (a, payload) = (set_of(&tiny), read(tiny["payload"].as_str().unwrap())); let mut b = a.clone(); let g = b.get_mut("g_s.whm").unwrap(); let &(_, _, bias) = whm1_rq(g).last().unwrap(); g[bias + 3] ^= 0x01; let cap = c::CAP_NEURAL_INT_HYPER; // int_synth: the first digit of the last layer's first bias. let base = read("neural-conformance/vectors/facade/int_synth.json"); let latent = read("neural-conformance/vectors/facade/latent.bin"); let at = base.windows(8).rposition(|w| w == b"\"bias\":[").unwrap() + 8; let mut other = base.clone(); let digit = (at..other.len()).find(|&i| other[i].is_ascii_digit()).unwrap(); other[digit] = if other[digit] == b'9' { b'1' } else { b'9' }; // Each with the store off first. let (a_off, b_off) = (decode_off(cap, &payload, &a).unwrap(), decode_off(cap, &payload, &b).unwrap()); let (x_off, y_off) = (synth_off(&latent, &base).unwrap(), synth_off(&latent, &other).unwrap()); assert_ne!(a_off, b_off, "the byte changes the image"); assert_ne!(x_off, y_off, "the digit changes the image"); assert_eq!(decode(cap, &payload, &a).unwrap(), a_off); assert_eq!(decode(cap, &payload, &b).unwrap(), b_off, "the set one byte away is decoded with its own networks"); assert_eq!((stats().misses, stats().hits, stats().entries), (2, 0, 2), "each set is loaded and kept under its own key"); assert_eq!(decode(cap, &payload, &b).unwrap(), b_off); assert_eq!(decode(cap, &payload, &a).unwrap(), a_off); assert_eq!((stats().misses, stats().hits), (2, 2)); assert!(key(cap, &a) != key(cap, &b) && LoadedSets::global().contains(&key(cap, &a)) && LoadedSets::global().contains(&key(cap, &b))); assert_eq!(synth(&latent, &base).unwrap(), x_off); assert_eq!(synth(&latent, &other).unwrap(), y_off, "the set one byte away is decoded with its own graph"); assert_eq!(synth(&latent, &other).unwrap(), y_off); assert_eq!((stats().misses, stats().hits, stats().entries), (4, 3, 4)); let (kx, ky) = (key(c::CAP_NEURAL_INT_SYNTH, &synth_set(&base)), key(c::CAP_NEURAL_INT_SYNTH, &synth_set(&other))); assert!(kx != ky && LoadedSets::global().contains(&kx) && LoadedSets::global().contains(&ky)); // int_mlicpp: g_s's last bias, channel 0, its fourth byte flipped, inside the bundle. let (ma, mp) = (mlicpp_set(read(MLICPP_BUNDLE)), mlicpp_payload()); let mut mb = ma.clone(); let w = mb.get_mut("model.wmb").unwrap(); let at = wmb1_section(w, 2); let end = wmb1_section(w, 3) - 4; let &(_, _, bias) = whm1_rq(&w[at..end]).last().unwrap(); w[at + bias + 3] ^= 0x01; let mc = c::CAP_NEURAL_INT_MLICPP; let (ma_off, mb_off) = (decode_off(mc, &mp, &ma).unwrap(), decode_off(mc, &mp, &mb).unwrap()); assert_ne!(ma_off, mb_off, "the byte changes the image"); let s0 = stats(); assert_eq!(decode(mc, &mp, &ma).unwrap(), ma_off); assert_eq!(decode(mc, &mp, &mb).unwrap(), mb_off, "the bundle one byte away is decoded with its own networks"); assert_eq!(decode(mc, &mp, &mb).unwrap(), mb_off); assert_eq!((stats().misses - s0.misses, stats().hits - s0.hits), (2, 1)); let (ka, kb) = (key(mc, &ma), key(mc, &mb)); assert!(ka != kb && LoadedSets::global().contains(&ka) && LoadedSets::global().contains(&kb)); } #[test] fn a_set_the_load_refuses_stays_refused_beside_a_held_neighbour() { let _s = store(DEFAULT_BUDGET); // int_synth: the falsification set that passes i64 by one unit (integer-operators // §14.3), refused by the bounds pass, and its neighbour with a bias of 0 in place of // 1, which loads. let overflow = read("ops-conformance/sets/overflow_acc64/int_synth.json"); const BIAS: &[u8] = b"\"bias\":[1,0,0]"; let at = overflow.windows(BIAS.len()).position(|w| w == BIAS).expect("the overflow set's bias") + 8; let mut fits = overflow.clone(); fits[at] = b'0'; assert_eq!(overflow.iter().zip(&fits).filter(|(x, y)| x != y).count(), 1, "one byte apart"); let p = synth_payload(1, 1, 1, 0, &[1]); // The conditional codec: tiny with layer 0's first bias of g_s at 2^63 − 1, so no // input range admits that layer (P_0(0) fails, integer-operators §13.2) and g_s is // refused as it is lowered, after the plan; tiny itself is held. let tiny = index()["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "tiny").cloned().unwrap(); let (good, payload) = (set_of(&tiny), read(tiny["payload"].as_str().unwrap())); let mut bad = good.clone(); let g = bad.get_mut("g_s.whm").unwrap(); let (_, _, bias) = whm1_rq(g)[0]; g[bias..bias + 8].copy_from_slice(&i64::MAX.to_le_bytes()); let cap = c::CAP_NEURAL_INT_HYPER; // Each refusal with the store off first. let synth_refused = synth_off(&p, &overflow).expect_err("the overflow set is refused"); assert!(synth_refused.1.contains("set does not load: model_mismatch at Load(8)"), "{synth_refused:?}"); let hyper_refused = decode_off(cap, &payload, &bad).expect_err("tiny with that bias is refused"); assert_eq!(hyper_refused, ("model_mismatch".to_string(), "g_s does not lower to an integer-operator graph that loads under acc64".to_string())); assert!(synth(&p, &fits).is_ok(), "the neighbour loads and decodes"); let held = stats(); for _ in 0..2 { assert_eq!(synth(&p, &overflow), Err(synth_refused.clone()), "refused again, with the same refusal"); } let after = stats(); assert_eq!((after.misses - held.misses, after.hits - held.hits, after.kept - held.kept), (2, 0, 0), "loaded each time, never kept"); assert!(!LoadedSets::global().contains(&key(c::CAP_NEURAL_INT_SYNTH, &synth_set(&overflow)))); decode(cap, &payload, &good).expect("tiny decodes, and is kept"); let held = stats(); for _ in 0..2 { assert_eq!(decode(cap, &payload, &bad), Err(hyper_refused.clone()), "refused again, with the same refusal"); } let after = stats(); assert_eq!((after.misses - held.misses, after.hits - held.hits, after.kept - held.kept), (2, 0, 0), "loaded each time, never kept"); assert!(!LoadedSets::global().contains(&key(cap, &bad)) && LoadedSets::global().contains(&key(cap, &good))); // int_mlicpp: the vector's bundle with g_s's layer-0 first bias at 2^63 − 1, refused as // it is lowered, after the header and the networks' geometry; the vector's own bundle // is held. let (good, payload) = (mlicpp_set(read(MLICPP_BUNDLE)), mlicpp_payload()); let mut bad = good.clone(); let w = bad.get_mut("model.wmb").unwrap(); let at = wmb1_section(w, 2); let end = wmb1_section(w, 3) - 4; let (_, _, bias) = whm1_rq(&w[at..end])[0]; w[at + bias..at + bias + 8].copy_from_slice(&i64::MAX.to_le_bytes()); let cap = c::CAP_NEURAL_INT_MLICPP; let mlic_refused = decode_off(cap, &payload, &bad).expect_err("the bundle with that bias is refused"); assert_eq!(mlic_refused.0, "model_mismatch"); assert!(mlic_refused.1.starts_with("g_s does not lower to an integer-operator graph that loads under acc64"), "{mlic_refused:?}"); decode(cap, &payload, &good).expect("the vector decodes, and is kept"); let held = stats(); for _ in 0..2 { assert_eq!(decode(cap, &payload, &bad), Err(mlic_refused.clone()), "refused again, with the same refusal"); } let after = stats(); assert_eq!((after.misses - held.misses, after.hits - held.hits, after.kept - held.kept), (2, 0, 0), "loaded each time, never kept"); assert!(!LoadedSets::global().contains(&key(cap, &bad)) && LoadedSets::global().contains(&key(cap, &good))); } #[test] fn every_refusal_fixture_keeps_its_code_and_detail_with_its_set_held() { let _s = store(DEFAULT_BUDGET); let idx = index(); let vectors = idx["vectors"].as_array().unwrap(); let mut n = 0; for f in idx["refusals"].as_array().unwrap() { let set = set_of(f); if set.is_empty() { continue; } let (name, cap) = (f["name"].as_str().unwrap(), f["capability"].as_str().unwrap()); let payload = read(f["payload"].as_str().unwrap()); let off = decode_off(cap, &payload, &set); assert_eq!(off.as_ref().map_err(|e| e.0.as_str()).map(|_| ()), Err(f["code"].as_str().unwrap()), "{name}: the store off"); // Twice from an empty store: the first may load and keep the set (a refusal after // the load step), the second then finds it held. LoadedSets::global().clear(); let s0 = stats(); assert_eq!(decode(cap, &payload, &set), off, "{name}: from an empty store"); assert_eq!(decode(cap, &payload, &set), off, "{name}: again"); if f["layer"] == "parse" { assert_eq!(stats(), s0, "{name}: a payload refused while it is parsed never reaches the store"); } // With the set held by a committed vector's decode, where one decodes against it. if let Some(v) = vectors.iter().find(|v| v["capability"] == cap && set_of(v) == set) { decode(cap, &read(v["payload"].as_str().unwrap()), &set).expect("the committed vector decodes"); assert!(LoadedSets::global().contains(&key(cap, &set))); assert_eq!(decode(cap, &payload, &set), off, "{name}: with its set held"); } n += 1; } assert!(n >= 40, "{n} fixtures with a set"); } #[test] fn the_least_recently_used_set_goes_first_under_a_small_budget() { let _s = store(DEFAULT_BUDGET); // Three sets of one shape, so of one size. let spec = |w: u8| format!(r#"{{"layers":[{{"in_ch":1,"out_ch":3,"k":1,"stride":1,"pad":0,"weight":[1,2,{w}],"bias":[0,0,0]}}]}}"#).into_bytes(); let (s1, s2, s3) = (spec(3), spec(4), spec(5)); let k = |s: &[u8]| key(c::CAP_NEURAL_INT_SYNTH, &synth_set(s)); let p = synth_payload(1, 1, 1, 0, &[5]); let has = |s: &[u8]| LoadedSets::global().contains(&k(s)); assert_eq!(synth(&p, &s1).unwrap(), [5, 10, 15]); assert_eq!(synth(&p, &s2).unwrap(), [5, 10, 20]); let two = stats().resident_bytes; assert_eq!((stats().entries, two % 2), (2, 0)); let one = two / 2; // Room for two: s1 is used again, so s2 is the least recently used when s3 comes. LoadedSets::global().set_budget(two); assert_eq!(synth(&p, &s1).unwrap(), [5, 10, 15]); assert_eq!(synth(&p, &s3).unwrap(), [5, 10, 25]); assert!(has(&s1) && has(&s3) && !has(&s2), "s2 went first"); assert_eq!((stats().evicted, stats().entries, stats().resident_bytes), (1, 2, two)); assert_eq!(synth(&p, &s2).unwrap(), [5, 10, 20], "an evicted set is loaded again"); assert!(has(&s2) && has(&s3) && !has(&s1)); // A budget under one entry: everything goes, and a set past it is decoded, not kept. LoadedSets::global().set_budget(one - 1); assert_eq!(stats().entries, 0); let before = stats(); assert_eq!(synth(&p, &s1).unwrap(), [5, 10, 15]); assert_eq!((stats().too_large - before.too_large, stats().entries), (1, 0)); // A budget of 0: no lookup and nothing kept. LoadedSets::global().set_budget(0); let before = stats(); assert_eq!(synth(&p, &s1).unwrap(), [5, 10, 15]); assert_eq!((stats().hits, stats().misses, stats().entries), (before.hits, before.misses, 0)); } #[test] fn threads_decoding_one_set_at_once_keep_one_entry() { let _s = store(DEFAULT_BUDGET); let tiny = index()["vectors"].as_array().unwrap().iter().find(|v| v["name"] == "tiny").cloned().unwrap(); let (set, payload) = (set_of(&tiny), read(tiny["payload"].as_str().unwrap())); let cap = c::CAP_NEURAL_INT_HYPER; let off = decode_off(cap, &payload, &set).unwrap(); let outs: Vec = std::thread::scope(|sc| { let hs: Vec<_> = (0..8).map(|_| sc.spawn(|| decode(cap, &payload, &set))).collect(); hs.into_iter().map(|h| h.join().unwrap()).collect() }); assert!(outs.iter().all(|o| o.as_ref() == Ok(&off)), "every thread decodes the same bytes"); let s = stats(); assert_eq!((s.hits + s.misses, s.entries), (8, 1)); }