1mod ops;
4mod state;
5mod strategy;
6pub use strategy::Cert;
7mod tick;
8
9use std::collections::HashMap;
10use std::ffi::CStr;
11
12use superfluid_abi::{
13 array::read_array, encoding as enc, op_state, space_kind, ArenaBounds, MatchCandidate,
14 MatchResult, OpComplete, OpStatus, SpaceMatch, StateSpaceDesc,
15 StrategyGrant, StrategyRegistration, TickEvents, TickPlan,
16 LaneAdmit, TokenRange, RecordArena, Status,
17};
18
19use crate::engine::{Engine, EngineConfig, OpHandle, SeqHandle, SpaceConfig, StageOutput};
20use crate::rings::Rings;
21use state::{Busy, Cache, CacheEntry, Lane, OpKind, OpState, SeedLease, Sequence, SpaceState};
22use strategy::{default_certs, RegisteredStrategy, RegistrationCheck};
23
24struct AbiStore<T> {
25 _arena: RecordArena,
26 value: Box<T>,
27}
28
29const MOCK_PIPELINE_LAYERS: u32 = 24;
30
31type AdmitRefusal = (fn(&LaneAdmit) -> bool, Status);
32
33pub struct MockEngine {
34 cfg: EngineConfig,
35 bundle_fp: u64,
36 target_layers: u32,
37 target_arch: String,
38
39 descs: Vec<StateSpaceDesc>,
40 lanes: HashMap<u64, Lane>,
41 seqs: HashMap<SeqHandle, Sequence>,
42 seeds: HashMap<u64, SeedLease>,
43 media: HashMap<u64, u32>,
44 next_media: u64,
45 pending_media_binds: HashMap<u64, Vec<(u32, u64)>>,
46 ops: HashMap<OpHandle, OpState>,
47 completions_pending: Vec<OpComplete>,
48 cache: Cache,
49 strategies: Vec<RegisteredStrategy>,
50 refuse_admits: Option<AdmitRefusal>,
51 certified_combos: HashMap<String, ([u8; 32], [u8; 32])>,
52
53 next_seq: SeqHandle,
54 next_op: OpHandle,
55 next_seed: u64,
56 next_slot: u32,
57 next_cert: u32,
58 grammars: std::collections::HashSet<u32>,
59 next_grammar: u32,
60 tick_count: u64,
61 last_plan_seq: Option<u64>,
62 pool_used: u64,
63
64 grant_store: Option<AbiStore<StrategyGrant>>,
65 match_store: Option<AbiStore<MatchResult>>,
66 events_store: Option<AbiStore<TickEvents>>,
67
68 record: Option<String>,
69 pub(crate) force_finish: HashMap<u64, u32>,
70 pub(crate) inject_fault: HashMap<u64, u32>,
71 pub(crate) faulted: std::collections::HashSet<u64>,
72}
73
74impl MockEngine {
75 pub fn new(cfg: EngineConfig) -> MockEngine {
76 let descs = cfg.spaces.iter().map(build_desc).collect();
77 MockEngine {
78 cfg,
79 bundle_fp: 0xBA5E_0001,
80 target_layers: 32,
81 target_arch: "mock-arch".to_string(),
82 descs,
83 lanes: HashMap::new(),
84 seqs: HashMap::new(),
85 seeds: HashMap::new(),
86 media: HashMap::new(),
87 next_media: 1,
88 pending_media_binds: HashMap::new(),
89 ops: HashMap::new(),
90 completions_pending: Vec::new(),
91 cache: Cache::new(),
92 strategies: Vec::new(),
93 certified_combos: HashMap::new(),
94 next_seq: 1,
95 next_op: 1,
96 next_seed: 1,
97 next_slot: 1,
98 next_cert: 1,
99 grammars: std::collections::HashSet::new(),
100 next_grammar: 1,
101 tick_count: 0,
102 last_plan_seq: None,
103 pool_used: 0,
104 grant_store: None,
105 match_store: None,
106 events_store: None,
107 record: None,
108 force_finish: HashMap::new(),
109 inject_fault: HashMap::new(),
110 faulted: std::collections::HashSet::new(),
111 refuse_admits: None,
112 }
113 }
114
115 pub fn refusing_admits(mut self, refuse: fn(&LaneAdmit) -> bool, status: Status) -> MockEngine {
116 self.refuse_admits = Some((refuse, status));
117 self
118 }
119
120 pub fn with_capability_descriptor(mut self, record: impl Into<String>) -> MockEngine {
121 self.record = Some(record.into());
122 self
123 }
124
125 pub fn config(&self) -> &EngineConfig {
126 &self.cfg
127 }
128
129 pub fn bundle_fingerprint(&self) -> u64 {
130 self.bundle_fp
131 }
132
133 fn space_cfg(&self, space_id: u32) -> Option<&SpaceConfig> {
134 self.cfg.spaces.iter().find(|s| s.space_id == space_id)
135 }
136
137 fn space_ops_gate(&self, space_id: u32) -> Result<&SpaceConfig, Status> {
138 let cfg = self.space_cfg(space_id).ok_or(Status::UnknownHandle)?;
139 if cfg.flags & superfluid_abi::space_flag::OPS_UNAVAILABLE != 0 {
140 return Err(Status::Unsupported);
141 }
142 Ok(cfg)
143 }
144
145 fn has_unservable_space(&self) -> bool {
146 self.cfg
147 .spaces
148 .iter()
149 .any(|s| s.flags & superfluid_abi::space_flag::OPS_UNAVAILABLE != 0)
150 }
151
152 pub fn lane_sequence(&self, lane_tag: u64) -> Option<SeqHandle> {
153 self.lanes.get(&lane_tag).map(|l| l.seq)
154 }
155
156 pub fn lane_committed(&self, lane_tag: u64) -> Option<&[u32]> {
157 self.lanes.get(&lane_tag).map(|l| l.committed.as_slice())
158 }
159
160 pub fn create_sequence(&mut self) -> SeqHandle {
161 self.alloc_seq()
162 }
163
164 pub fn space_valid_len(&self, seq: SeqHandle, space_id: u32) -> Option<u64> {
165 self.seqs
166 .get(&seq)
167 .and_then(|q| q.spaces.get(&space_id))
168 .map(|s| s.valid_len)
169 }
170
171 pub fn space_taint(&self, seq: SeqHandle, space_id: u32) -> Option<u32> {
172 self.seqs
173 .get(&seq)
174 .and_then(|q| q.spaces.get(&space_id))
175 .map(|s| s.taint_bits)
176 }
177
178 pub fn pool_used(&self) -> u64 {
179 self.pool_used
180 }
181
182 pub fn force_finish(&mut self, lane_tag: u64, finish: u32) {
183 self.force_finish.insert(lane_tag, finish);
184 }
185
186 pub fn inject_fault(&mut self, lane_tag: u64, code: u32) {
187 self.inject_fault.insert(lane_tag, code);
188 }
189
190 pub fn grant_certs_for_test(&mut self, strategy_id: &str, certs: Vec<strategy::Cert>) {
191 if let Some(s) = self
192 .strategies
193 .iter_mut()
194 .find(|s| s.strategy_id == strategy_id)
195 {
196 s.certs = certs;
197 }
198 }
199
200 #[allow(clippy::too_many_arguments)]
201 pub fn make_cert(
202 &mut self,
203 exactness: u8,
204 sampling_modes: u8,
205 param_domain: u32,
206 grammar_allowed: bool,
207 admissible_host_identities: Vec<[u8; 32]>,
208 ) -> strategy::Cert {
209 let id = self.next_cert;
210 self.next_cert += 1;
211 Cert {
212 cert_id: id,
213 exactness,
214 sampling_modes,
215 grammar_allowed,
216 logit_bias_allowed: false,
217 param_domain,
218 verification_shape_class: 1,
219 admissible_host_identities,
220 }
221 }
222
223 pub(crate) fn alloc_seq(&mut self) -> SeqHandle {
224 let h = self.next_seq;
225 self.next_seq += 1;
226 let spaces = self
227 .cfg
228 .spaces
229 .iter()
230 .map(|s| (s.space_id, SpaceState::new(s)))
231 .collect();
232 self.seqs.insert(
233 h,
234 Sequence {
235 spaces,
236 busy: Busy::Idle,
237 },
238 );
239 h
240 }
241
242 pub(crate) fn paged_block_bytes(&self) -> u64 {
243 let page = self
244 .cfg
245 .spaces
246 .iter()
247 .find(|s| s.page_size_tokens > 0)
248 .map(|s| s.page_size_tokens as u64)
249 .unwrap_or(1);
250 self.paged_bytes_per_token() * page
251 }
252
253 pub(crate) fn paged_bytes_per_token(&self) -> u64 {
254 self.cfg
255 .spaces
256 .iter()
257 .filter(|s| {
258 s.kind == space_kind::PAGED_TOKEN_KV || s.kind == space_kind::DEPTH_PAGED_KV
259 })
260 .map(|s| s.bytes_per_token)
261 .sum()
262 }
263
264 pub(crate) fn sweep_expired_seeds(&mut self) {
265 let now = self.tick_count;
266 let expired: Vec<u64> = self
267 .seeds
268 .values()
269 .filter(|l| l.expires_at_tick < now)
270 .map(|l| l.handle)
271 .collect();
272 for h in expired {
273 if let Some(lease) = self.seeds.remove(&h) {
274 for (_, id) in lease.pinned_entries {
275 if let Some(e) = self.cache.get_mut(id) {
276 e.pinned = e.pinned.saturating_sub(1);
277 }
278 }
279 }
280 }
281 }
282
283 pub(crate) fn release_seq_pool(&mut self, seq: &Sequence) {
284 for (space_id, s) in &seq.spaces {
285 if let Some(c) = self.cfg.spaces.iter().find(|c| c.space_id == *space_id) {
286 if c.bytes_per_token > 0 {
287 let demoted: u64 = s.demoted.iter().map(|(a, b, _)| b - a).sum();
288 let resident = s.valid_len.saturating_sub(demoted);
289 self.pool_used = self.pool_used.saturating_sub(resident * c.bytes_per_token);
290 }
291 }
292 }
293 }
294
295 pub(crate) fn evict_within_envelope(
296 &mut self,
297 needed: u64,
298 evictable_classes: u64,
299 max_evict_bytes: u64,
300 protected_quota_bytes: u64,
301 ) -> Vec<(u64, u64)> {
302 let mut evicted = Vec::new();
303 let mut freed = 0u64;
304 let total: u64 = self.cache.entries.iter().map(|e| e.bytes).sum();
305 let cap = max_evict_bytes.min(total.saturating_sub(protected_quota_bytes));
306 while self.pool_used + needed > self.cfg.pool_bytes && freed < cap {
307 let victim = self
308 .cache
309 .entries
310 .iter()
311 .filter(|e| e.pinned == 0 && e.cache_class & evictable_classes != 0)
312 .min_by_key(|e| e.lru)
313 .map(|e| (e.id, e.bytes, e.space_id));
314 let Some((id, bytes, space_id)) = victim else {
315 break;
316 };
317 self.cache.entries.retain(|e| e.id != id);
318 let paged = self
319 .space_cfg(space_id)
320 .map(|c| c.bytes_per_token > 0)
321 .unwrap_or(false);
322 if paged {
323 self.pool_used = self.pool_used.saturating_sub(bytes);
324 }
325 freed += bytes;
326 evicted.push((id, bytes));
327 }
328 evicted
329 }
330}
331
332fn build_desc(s: &SpaceConfig) -> StateSpaceDesc {
333 let mut name = [0u8; 32];
334 let n = s.name.as_bytes();
335 name[..n.len().min(31)].copy_from_slice(&n[..n.len().min(31)]);
336 let paged = s.kind == space_kind::PAGED_TOKEN_KV || s.kind == space_kind::DEPTH_PAGED_KV;
337 StateSpaceDesc {
338 space_id: s.space_id,
339 kind: s.kind,
340 version_tag: s.version_tag,
341 bytes_per_token: s.bytes_per_token,
342 blob_bytes: s.blob_bytes,
343 page_size_tokens: s.page_size_tokens,
344 fork_cost_class: if paged { 1 } else { 2 },
345 fork_cost_bytes: if paged { 0 } else { s.blob_bytes },
346 snapshot_cadence: s.snapshot_cadence,
347 snapshot_interval_tokens: s.snapshot_interval_tokens,
348 placement: 0,
349 flags: s.flags,
350 name,
351 }
352}
353
354impl Engine for MockEngine {
355 fn capability_descriptor(&self) -> Option<String> {
356 if let Some(r) = &self.record {
357 return Some(r.clone());
358 }
359 Some(
360 r#"{"descriptor_version":1,"architecture":"mock","backend":"mock","workload":{"causal_generation":true,"embedding":true,"speech_to_text":true},"modalities":{"image_encode":true,"gemma_audio_encode":true,"whisper_transcribe":true,"whisper_translate":true},"state":{"recurrent_snapshot":"no recurrent state in this architecture"}}"#
361 .to_string(),
362 )
363 }
364
365 fn embed(&mut self, tokens: &[u32]) -> Result<Vec<f32>, Status> {
366 let mut v = [0.0f32; 8];
367 for (i, &t) in tokens.iter().enumerate() {
368 v[i % 8] += ((t % 97) as f32 + 1.0) / (i as f32 + 1.0);
369 }
370 let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt().max(1e-6);
371 Ok(v.iter().map(|x| x / norm).collect())
372 }
373
374 fn logit_bias_create(&mut self, tokens: &[i32], values: &[f32]) -> Result<u32, Status> {
375 if tokens.is_empty() || values.is_empty() {
376 return Err(Status::Unsupported);
377 }
378 Ok(1)
379 }
380
381 fn logit_bias_free(&mut self, _handle: u32) -> Result<(), Status> {
382 Ok(())
383 }
384
385 fn transcribe(
386 &mut self,
387 _audio_path: &str,
388 params: &crate::engine::TranscribeParams,
389 on_segment: Option<crate::engine::SegmentSink<'_>>,
390 ) -> Result<crate::engine::Transcription, Status> {
391 let base = if params.translate { "mock translation" } else { "mock transcription" };
392 let text = match params.prompt.as_deref().filter(|p| !p.is_empty()) {
393 Some(p) => format!("{p} {base}"),
394 None => base.to_string(),
395 };
396 let language = params
397 .language
398 .as_deref()
399 .filter(|l| *l != "auto")
400 .unwrap_or("en")
401 .to_string();
402 let seg = |start_ms: i32, end_ms: i32, text: String| crate::engine::TranscriptSegment {
403 start_ms,
404 end_ms,
405 text,
406 avg_logprob: -0.25,
407 no_speech_prob: 0.01,
408 compression_ratio: 1.5,
409 temperature: 0.0,
410 };
411 let Some(cb) = on_segment else {
412 return Ok(crate::engine::Transcription {
413 text: text.clone(),
414 language,
415 duration_ms: 1000,
416 segments: vec![seg(0, 1000, text)],
417 });
418 };
419 let mut segments = Vec::new();
420 let words: Vec<&str> = text.split_whitespace().collect();
421 let n = words.len().max(1) as i32;
422 for (i, w) in words.iter().enumerate() {
423 std::thread::sleep(std::time::Duration::from_millis(2));
424 let (start, end) = (i as i32 * 1000 / n, (i as i32 + 1) * 1000 / n);
425 segments.push(seg(start, end, (*w).to_string()));
426 if !cb(start, end, w) {
427 break;
428 }
429 }
430 Ok(crate::engine::Transcription {
431 text: segments.iter().map(|s| s.text.as_str()).collect::<Vec<_>>().join(" "),
432 language,
433 duration_ms: 1000,
434 segments,
435 })
436 }
437
438 fn lora_load(&mut self, _adapter_path: &str) -> Result<(), Status> {
439 Ok(())
440 }
441 fn lora_unload(&mut self) -> Result<(), Status> {
442 Ok(())
443 }
444
445 fn tick<'a>(
446 &'a mut self,
447 plan: &TickPlan,
448 arena: &RecordArena,
449 rings: &mut dyn Rings,
450 ) -> Result<&'a TickEvents, Status> {
451 if !self.cfg.tick_delay.is_zero() {
452 std::thread::sleep(self.cfg.tick_delay);
453 }
454 self.run_tick(plan, arena, rings)?;
455 Ok(&self
456 .events_store
457 .as_ref()
458 .expect("run_tick stores events")
459 .value)
460 }
461
462 fn pump(&mut self) {
463 let handles: Vec<OpHandle> = self
464 .ops
465 .values()
466 .filter(|o| o.state == op_state::RUNNING)
467 .map(|o| o.handle)
468 .collect();
469 for h in handles {
470 self.complete_op(h);
471 }
472 }
473
474 fn state_spaces(&self) -> &[StateSpaceDesc] {
475 &self.descs
476 }
477
478 fn grammar_create(&mut self, json_schema: &str) -> Result<u32, Status> {
479 let t = json_schema.trim();
480 let balanced = {
481 let mut depth = 0i32;
482 let mut in_str = false;
483 let mut esc = false;
484 for c in t.chars() {
485 match c {
486 _ if esc => esc = false,
487 '\\' if in_str => esc = true,
488 '"' => in_str = !in_str,
489 '{' if !in_str => depth += 1,
490 '}' if !in_str => depth -= 1,
491 _ => {}
492 }
493 if depth < 0 {
494 break;
495 }
496 }
497 depth == 0 && !in_str
498 };
499 if !t.starts_with('{') || !t.ends_with('}') || !balanced {
500 return Err(Status::RejectBadStruct);
501 }
502 let h = self.next_grammar;
503 self.next_grammar += 1;
504 self.grammars.insert(h);
505 Ok(h)
506 }
507
508 fn grammar_create_structural(&mut self, tag_json: &str) -> Result<u32, Status> {
509 self.grammar_create(tag_json)
510 }
511
512 fn grammar_free(&mut self, handle: u32) -> Result<(), Status> {
513 if self.grammars.remove(&handle) {
514 Ok(())
515 } else {
516 Err(Status::UnknownHandle)
517 }
518 }
519
520 fn strategy_register(
521 &mut self,
522 reg: &StrategyRegistration,
523 ) -> Result<&StrategyGrant, Status> {
524 let bounds = ArenaBounds {
525 base: 0,
526 len: usize::MAX,
527 };
528 let capabilities = unsafe { read_array(®.capabilities, &bounds) }
531 .map_err(|e| e.status())?
532 .collect();
533 let taps = unsafe { read_array(®.taps, &bounds) }
535 .map_err(|e| e.status())?
536 .collect();
537 let artifacts: Vec<superfluid_abi::Artifact> =
540 unsafe { read_array(®.artifacts, &bounds) }
541 .map_err(|e| e.status())?
542 .collect();
543 for a in &artifacts {
544 if a.load_path.is_null() {
545 return Err(Status::RegistrationRefused);
546 }
547 }
548 let arch_ptrs: Vec<u64> = unsafe { read_array(®.target_archs, &bounds) }
550 .map_err(|e| e.status())?
551 .collect();
552 let target_archs: Vec<String> = arch_ptrs
553 .into_iter()
554 .map(|p| {
555 if p == 0 {
556 return Err(Status::RegistrationRefused);
557 }
558 Ok(unsafe { CStr::from_ptr(p as *const std::ffi::c_char) }
560 .to_string_lossy()
561 .into_owned())
562 })
563 .collect::<Result<_, _>>()?;
564
565 if reg.strategy_id.is_null() {
566 return Err(Status::RegistrationRefused);
567 }
568 let strategy_id = unsafe { CStr::from_ptr(reg.strategy_id) }
570 .to_string_lossy()
571 .into_owned();
572
573 let check = RegistrationCheck {
574 reg,
575 capabilities,
576 taps,
577 target_archs,
578 };
579 let host_compatible = check.validate(self.target_layers, &self.target_arch)?;
580 if reg.kernel_caps_required & !self.cfg.kernel_caps != 0 {
581 return Err(Status::RegistrationRefused);
582 }
583
584 let slot = self.next_slot;
585 self.next_slot += 1;
586 let certs = match self.certified_combos.get(&strategy_id) {
587 None => {
588 let certs = default_certs(&strategy_id, &mut self.next_cert);
589 if !certs.is_empty() {
590 self.certified_combos
591 .insert(strategy_id.clone(), (reg.impl_hash, reg.config_hash));
592 }
593 certs
594 }
595 Some((ih, ch)) if *ih == reg.impl_hash && *ch == reg.config_hash => {
596 default_certs(&strategy_id, &mut self.next_cert)
597 }
598 Some(_) => Vec::new(),
599 };
600 let registered = RegisteredStrategy {
601 slot,
602 strategy_id,
603 impl_hash: reg.impl_hash,
604 config_hash: reg.config_hash,
605 host_compatible,
606 certs,
607 reserved_bytes: reg.est_state_bytes,
608 };
609
610 let mut arena = RecordArena::new();
611 let cert_records: Vec<_> = registered
612 .certs
613 .iter()
614 .map(|c| c.to_abi(&mut arena))
615 .collect();
616 let certificates = arena.push_records(&cert_records);
617 let grant = StrategyGrant {
618 struct_size: std::mem::size_of::<StrategyGrant>() as u64,
619 strategy_slot: slot,
620 _pad0: 0,
621 certificates,
622 state_spaces: superfluid_abi::Array::EMPTY,
623 reserved_bytes: registered.reserved_bytes,
624 };
625 self.strategies.push(registered);
626 self.grant_store = Some(AbiStore {
627 _arena: arena,
628 value: Box::new(grant),
629 });
630 Ok(&self.grant_store.as_ref().expect("just stored").value)
631 }
632
633 fn space_match(
634 &mut self,
635 space_id: u32,
636 span_tokens: &[u32],
637 _media_deps: &[[u8; 32]],
638 ) -> Result<&MatchResult, Status> {
639 let mut arena = RecordArena::new();
640 let mut space_matches = Vec::new();
641 for s in &self.cfg.spaces {
642 if space_id != superfluid_abi::ALL_SPACES && s.space_id != space_id {
643 continue;
644 }
645 let mut candidates: Vec<MatchCandidate> = Vec::new();
646 for e in self
647 .cache
648 .entries
649 .iter()
650 .filter(|e| e.space_id == s.space_id)
651 {
652 let lcp = longest_common_prefix(&e.tokens, span_tokens);
653 let candidate_len = match s.kind {
654 space_kind::PAGED_TOKEN_KV | space_kind::DEPTH_PAGED_KV => {
655 let page = s.page_size_tokens.max(1) as u64;
656 (lcp / page) * page
657 }
658 space_kind::RING_KV | space_kind::RECURRENT_BLOB => {
659 *e.boundaries
660 .iter()
661 .filter(|b| **b <= lcp)
662 .max()
663 .unwrap_or(&0)
664 }
665 _ => 0,
666 };
667 if candidate_len > 0 {
668 candidates.push(MatchCandidate {
669 prefix_len: candidate_len,
670 provenance_digest: e.provenance,
671 taint_bits: e.taint_bits,
672 resident_tier: e.tier,
673 _pad0: [0; 3],
674 });
675 }
676 }
677 space_matches.push((s.space_id, candidates));
678 }
679 if space_matches.is_empty() {
680 return Err(Status::UnknownHandle);
681 }
682 let records: Vec<SpaceMatch> = space_matches
683 .into_iter()
684 .map(|(id, cands)| SpaceMatch {
685 space_id: id,
686 _pad0: 0,
687 candidates: arena.push_records(&cands),
688 })
689 .collect();
690 let result = MatchResult {
691 struct_size: std::mem::size_of::<MatchResult>() as u64,
692 spaces: arena.push_records(&records),
693 };
694 self.match_store = Some(AbiStore {
695 _arena: arena,
696 value: Box::new(result),
697 });
698 Ok(&self.match_store.as_ref().expect("just stored").value)
699 }
700
701 fn seed_acquire(
702 &mut self,
703 span_tokens: &[u32],
704 prefix_len: u64,
705 determinism_class: u8,
706 ) -> Result<u64, Status> {
707 if prefix_len == 0 || prefix_len as usize > span_tokens.len() {
708 return Err(Status::RejectBounds);
709 }
710 self.sweep_expired_seeds();
711 if self.has_unservable_space() {
712 return Err(Status::SeedUnservable);
713 }
714 if self
715 .cfg
716 .spaces
717 .iter()
718 .any(|s| s.flags & superfluid_abi::space_flag::NO_PREFIX_CACHE != 0)
719 {
720 return Err(Status::SeedUnservable);
721 }
722 let prefix = &span_tokens[..prefix_len as usize];
723 let deterministic = determinism_class == superfluid_abi::determinism::DETERMINISTIC;
724
725 let mut pinned = Vec::new();
726 for s in &self.cfg.spaces {
727 if s.kind == space_kind::ENCODER_CACHE {
728 continue;
729 }
730 let page = s.page_size_tokens.max(1) as u64;
731 let found = self.cache.entries.iter().find(|e| {
732 if e.space_id != s.space_id || e.tokens.len() < prefix.len() {
733 return false;
734 }
735 if e.tokens[..prefix.len()] != *prefix {
736 return false;
737 }
738 if deterministic && e.taint_bits != 0 {
739 return false;
740 }
741 match s.kind {
742 space_kind::PAGED_TOKEN_KV | space_kind::DEPTH_PAGED_KV => {
743 prefix_len.is_multiple_of(page)
744 }
745 _ => e.boundaries.contains(&prefix_len),
746 }
747 });
748 match found {
749 Some(e) => pinned.push((s.space_id, e.id)),
750 None => {
751 return Err(Status::SeedUnservable);
752 }
753 }
754 }
755 for (_, id) in &pinned {
756 if let Some(e) = self.cache.get_mut(*id) {
757 e.pinned += 1;
758 }
759 self.cache.touch(*id);
760 }
761 let handle = self.next_seed;
762 self.next_seed += 1;
763 self.seeds.insert(
764 handle,
765 SeedLease {
766 handle,
767 prefix: prefix.to_vec(),
768 determinism: determinism_class,
769 pinned_entries: pinned,
770 expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
771 adopted: None,
772 },
773 );
774 Ok(handle)
775 }
776
777 fn media_probe(&mut self, _image_path: &str) -> Result<superfluid_abi::MediaInfo, Status> {
778 if self.cfg.image_token_id == 0 {
779 return Err(Status::Unsupported);
780 }
781 Ok(superfluid_abi::MediaInfo {
782 n_tokens: self.cfg.media_tokens_per_image,
783 image_token_id: self.cfg.image_token_id,
784 boi_token_id: 0,
785 eoi_token_id: 0,
786 preprocess_fp: 0x4d4f434b,
787 })
788 }
789
790 fn media_encode(&mut self, image_path: &str) -> Result<(u64, superfluid_abi::MediaInfo), Status> {
791 let info = self.media_probe(image_path)?;
792 let h = self.next_media;
793 self.next_media += 1;
794 self.media.insert(h, info.n_tokens);
795 Ok((h, info))
796 }
797
798 fn media_release(&mut self, media_handle: u64) -> Result<(), Status> {
799 self.media.remove(&media_handle).map(|_| ()).ok_or(Status::UnknownHandle)
800 }
801
802 fn media_bind(&mut self, lane_tag: u64, media_handle: u64, token_offset: u32) -> Result<(), Status> {
803 if !self.media.contains_key(&media_handle) {
804 return Err(Status::UnknownHandle);
805 }
806 let binds = match self.lanes.get_mut(&lane_tag) {
807 Some(l) => &mut l.media_binds,
808 None => self.pending_media_binds.entry(lane_tag).or_default(),
809 };
810 binds.push((token_offset, media_handle));
811 binds.sort();
812 Ok(())
813 }
814
815 fn seed_adopt(&mut self, seq: SeqHandle, span_tokens: &[u32]) -> Result<u64, Status> {
816 let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
817 if s.any_op_active() || s.busy == Busy::Ticking {
818 return Err(Status::Busy);
819 }
820 if self.lanes.values().any(|l| l.seq == seq) {
821 return Err(Status::UnknownHandle);
822 }
823 let len = span_tokens.len() as u64;
824 if len == 0
825 || s
826 .spaces
827 .iter()
828 .any(|(_, sp)| sp.kind != space_kind::ENCODER_CACHE && sp.valid_len != len)
829 {
830 return Err(Status::SeedUnservable);
831 }
832 self.sweep_expired_seeds();
833 let handle = self.next_seed;
834 self.next_seed += 1;
835 self.seeds.insert(
836 handle,
837 SeedLease {
838 handle,
839 prefix: span_tokens.to_vec(),
840 determinism: 0,
841 pinned_entries: Vec::new(),
842 expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
843 adopted: Some(seq),
844 },
845 );
846 Ok(handle)
847 }
848
849 fn seed_lease_ticks(&self) -> Option<u64> {
850 Some(self.cfg.seed_ttl_ticks)
851 }
852
853 fn seed_release(&mut self, seed_handle: u64) -> Result<(), Status> {
854 let lease = self
855 .seeds
856 .remove(&seed_handle)
857 .ok_or(Status::UnknownHandle)?;
858 for (_, id) in lease.pinned_entries {
859 if let Some(e) = self.cache.get_mut(id) {
860 e.pinned = e.pinned.saturating_sub(1);
861 }
862 }
863 Ok(())
864 }
865
866 fn seq_fork(&mut self, parent: SeqHandle, _flags: u32) -> Result<SeqHandle, Status> {
867 let p = self.seqs.get(&parent).ok_or(Status::UnknownHandle)?;
868 if p.any_op_active() || p.busy == Busy::Ticking {
869 return Err(Status::Busy);
870 }
871 let clone = Sequence {
872 spaces: p.spaces.clone(),
873 busy: Busy::Idle,
874 };
875 let h = self.next_seq;
876 self.next_seq += 1;
877 self.seqs.insert(h, clone);
878 Ok(h)
879 }
880
881 fn lane_sequence(&self, lane_tag: u64) -> Option<SeqHandle> {
882 MockEngine::lane_sequence(self, lane_tag)
883 }
884
885 fn create_sequence(&mut self) -> Result<SeqHandle, Status> {
886 Ok(self.alloc_seq())
887 }
888
889 fn free_sequence(&mut self, seq: SeqHandle) -> Result<(), Status> {
890 let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
891 if s.any_op_active() || s.busy == Busy::Ticking {
892 return Err(Status::Busy);
893 }
894 let s = self.seqs.remove(&seq).expect("checked above");
895 self.release_seq_pool(&s);
896 Ok(())
897 }
898
899 fn publish_sequence(&mut self, seq: SeqHandle, tokens: &[u32]) -> Result<(), Status> {
900 let s = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
901 if s.any_op_active() || s.busy == Busy::Ticking {
902 return Err(Status::Busy);
903 }
904 let over_claim = s.spaces.iter().any(|(id, sp)| {
905 sp.valid_len < tokens.len() as u64
906 && self
907 .cfg
908 .spaces
909 .iter()
910 .any(|c| c.space_id == *id && c.bytes_per_token > 0)
911 });
912 if over_claim {
913 return Err(Status::OutOfBoundary);
914 }
915 let entries: Vec<CacheEntry> = s
916 .spaces
917 .iter()
918 .filter(|(_, sp)| sp.kind != space_kind::ENCODER_CACHE)
919 .filter_map(|(space_id, sp)| {
920 let len = sp.valid_len.min(tokens.len() as u64);
921 if len == 0 {
922 return None;
923 }
924 let cfg = self.cfg.spaces.iter().find(|c| c.space_id == *space_id)?;
925 let bytes = if cfg.blob_bytes > 0 {
926 cfg.blob_bytes
927 } else {
928 len * cfg.bytes_per_token
929 };
930 Some(CacheEntry {
931 id: 0,
932 space_id: *space_id,
933 tokens: tokens[..len as usize].to_vec(),
934 provenance: sp.provenance,
935 taint_bits: sp.taint_bits,
936 tier: superfluid_abi::tier::GPU,
937 bytes,
938 cache_class: 1,
939 pinned: 0,
940 boundaries: sp.boundaries.clone(),
941 lru: 0,
942 })
943 })
944 .collect();
945 let mut released: u64 = 0;
946 for e in entries {
947 let space_id = e.space_id;
948 let bytes = e.bytes;
949 let paged = self
950 .space_cfg(space_id)
951 .map(|c| c.bytes_per_token > 0)
952 .unwrap_or(false);
953 let outcome = self.cache.publish(e);
954 if paged && outcome.stored.is_none() {
955 released += bytes;
956 }
957 for (victim_space, victim_bytes) in outcome.evicted {
958 if self
959 .space_cfg(victim_space)
960 .map(|c| c.bytes_per_token > 0)
961 .unwrap_or(false)
962 {
963 released += victim_bytes;
964 }
965 }
966 }
967 self.seqs.remove(&seq);
968 self.pool_used = self.pool_used.saturating_sub(released);
969 Ok(())
970 }
971
972 fn space_export_size(
973 &mut self,
974 seq: SeqHandle,
975 space_id: u32,
976 range: TokenRange,
977 encoding: u8,
978 ) -> Result<(u64, u64), Status> {
979 let cfg = self.space_ops_gate(space_id)?;
980 let (bpt, blob) = (cfg.bytes_per_token, cfg.blob_bytes);
981 let s = self
982 .seqs
983 .get(&seq)
984 .and_then(|q| q.spaces.get(&space_id))
985 .ok_or(Status::UnknownHandle)?;
986 let bytes = ops::export_size(s, bpt, blob, range, encoding)?;
987 Ok((bytes, s.content_gen))
988 }
989
990 fn space_snapshot(
991 &mut self,
992 seq: SeqHandle,
993 space_id: u32,
994 boundary_pos: u64,
995 dst_len: u64,
996 sizing_gen: u64,
997 ) -> Result<OpHandle, Status> {
998 self.submit_export(
999 seq,
1000 space_id,
1001 TokenRange {
1002 start: 0,
1003 end: boundary_pos,
1004 },
1005 enc::LOSSLESS,
1006 dst_len,
1007 sizing_gen,
1008 OpKind::Snapshot,
1009 )
1010 }
1011
1012 fn space_snapshot_boundary(
1013 &mut self,
1014 seq: SeqHandle,
1015 space_id: u32,
1016 cap: u64,
1017 ) -> Result<u64, Status> {
1018 let cfg = self.space_ops_gate(space_id)?;
1019 let (cadence, interval) = (cfg.snapshot_cadence, cfg.snapshot_interval_tokens);
1020 let q = self.seqs.get(&seq).ok_or(Status::UnknownHandle)?;
1021 let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1022 let len = cap.min(s.valid_len);
1023 if s.kind != superfluid_abi::space_kind::PAGED_TOKEN_KV {
1024 if let Some(&b) = s.boundaries.iter().filter(|&&b| b <= len).max() {
1025 return Ok(b);
1026 }
1027 if cadence == 2 && interval > 0 {
1028 return Ok(len / interval as u64 * interval as u64);
1029 }
1030 return Ok(0);
1031 }
1032 Ok(len)
1033 }
1034
1035 fn space_restore(
1036 &mut self,
1037 seq: SeqHandle,
1038 space_id: u32,
1039 src: &[u8],
1040 ) -> Result<OpHandle, Status> {
1041 self.submit_import(seq, space_id, src, OpKind::Restore)
1042 }
1043
1044 fn space_trim(&mut self, seq: SeqHandle, space_id: u32, new_len: u64) -> Result<(), Status> {
1045 let interval = self.space_ops_gate(space_id)?.snapshot_interval_tokens;
1046 let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1047 if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1048 return Err(Status::Busy);
1049 }
1050 let s = q.spaces.get_mut(&space_id).ok_or(Status::UnknownHandle)?;
1051 if s.kind == space_kind::ENCODER_CACHE {
1052 return Err(Status::Unsupported);
1053 }
1054 if new_len > s.valid_len {
1055 return Err(Status::RejectBounds);
1056 }
1057 if s.is_blob_like() && !s.boundaries.contains(&new_len) {
1058 return Err(Status::OutOfBoundary);
1059 }
1060 s.valid_len = new_len;
1061 s.boundaries.retain(|b| *b <= new_len);
1062 s.content_gen += 1;
1063 let _ = interval;
1064 Ok(())
1065 }
1066
1067 fn space_demote(
1068 &mut self,
1069 seq: SeqHandle,
1070 space_id: u32,
1071 range: TokenRange,
1072 encoding: u8,
1073 dst_len: u64,
1074 sizing_gen: u64,
1075 ) -> Result<OpHandle, Status> {
1076 self.submit_export(
1077 seq,
1078 space_id,
1079 range,
1080 encoding,
1081 dst_len,
1082 sizing_gen,
1083 OpKind::Demote,
1084 )
1085 }
1086
1087 fn space_promote(
1088 &mut self,
1089 seq: SeqHandle,
1090 space_id: u32,
1091 _range: TokenRange,
1092 src: &[u8],
1093 ) -> Result<OpHandle, Status> {
1094 self.submit_import(seq, space_id, src, OpKind::Promote)
1095 }
1096
1097 fn op_poll(&mut self, op: OpHandle) -> Result<OpStatus, Status> {
1098 let o = self.ops.get(&op).ok_or(Status::UnknownHandle)?;
1099 Ok(OpStatus {
1100 struct_size: std::mem::size_of::<OpStatus>() as u64,
1101 state: o.state,
1102 _pad0: [0; 3],
1103 error: o.error,
1104 bytes_moved: o.bytes_moved,
1105 })
1106 }
1107
1108 fn take_op_output(&mut self, op: OpHandle) -> Option<Vec<u8>> {
1109 let o = self.ops.get_mut(&op)?;
1110 if o.state != op_state::DONE {
1111 return None;
1112 }
1113 o.output.take()
1114 }
1115
1116 fn n_layers(&self) -> u32 {
1117 MOCK_PIPELINE_LAYERS
1118 }
1119
1120 fn forward_stage(
1121 &mut self,
1122 tokens: &[u32],
1123 start_layer: u32,
1124 end_layer: u32,
1125 hidden_in: Option<Vec<u8>>,
1126 ) -> Result<StageOutput, Status> {
1127 let n = MOCK_PIPELINE_LAYERS;
1128 if start_layer > end_layer || end_layer > n {
1129 return Err(Status::RejectBounds);
1130 }
1131 let mut h: u64 = match hidden_in {
1132 None => {
1133 let mut b = Vec::with_capacity(tokens.len() * 4);
1134 for t in tokens {
1135 b.extend_from_slice(&t.to_le_bytes());
1136 }
1137 xxhash_rust::xxh3::xxh3_64(&b)
1138 }
1139 Some(bytes) => {
1140 if bytes.len() != 8 {
1141 return Err(Status::RejectBadStruct);
1142 }
1143 let mut arr = [0u8; 8];
1144 arr.copy_from_slice(&bytes);
1145 u64::from_le_bytes(arr)
1146 }
1147 };
1148 for l in start_layer..end_layer {
1149 let mut b = [0u8; 12];
1150 b[..8].copy_from_slice(&h.to_le_bytes());
1151 b[8..12].copy_from_slice(&l.to_le_bytes());
1152 h = xxhash_rust::xxh3::xxh3_64(&b);
1153 }
1154 if end_layer >= n {
1155 Ok(StageOutput::Token((h % self.cfg.vocab.max(1) as u64) as u32))
1156 } else {
1157 Ok(StageOutput::Hidden(h.to_le_bytes().to_vec()))
1158 }
1159 }
1160
1161 fn op_cancel(&mut self, op: OpHandle) -> Result<(), Status> {
1162 let o = self.ops.get_mut(&op).ok_or(Status::UnknownHandle)?;
1163 match o.state {
1164 op_state::PENDING | op_state::RUNNING => {
1165 o.state = op_state::CANCELLED;
1166 o.output = None;
1167 o.staged = None;
1168 let (seq, space) = (o.seq, o.space_id);
1169 let complete = OpComplete {
1170 op,
1171 state: op_state::CANCELLED,
1172 _pad0: [0; 3],
1173 error: 0,
1174 bytes_moved: o.bytes_moved,
1175 };
1176 self.release_op_claim(seq, space);
1177 self.completions_pending.push(complete);
1178 Ok(())
1179 }
1180 _ => Ok(()),
1181 }
1182 }
1183
1184 fn cache_evict(
1185 &mut self,
1186 evictable_cache_classes: u64,
1187 protected_quota_bytes: u64,
1188 max_evict_bytes: u64,
1189 bytes_target: u64,
1190 ) -> Result<u64, Status> {
1191 let mut freed = 0u64;
1192 let total: u64 = self.cache.entries.iter().map(|e| e.bytes).sum();
1193 let cap = bytes_target
1194 .min(max_evict_bytes)
1195 .min(total.saturating_sub(protected_quota_bytes));
1196 while freed < cap {
1197 let victim = self
1198 .cache
1199 .entries
1200 .iter()
1201 .filter(|e| {
1202 e.pinned == 0
1203 && e.cache_class & evictable_cache_classes != 0
1204 && freed + e.bytes <= cap
1205 })
1206 .min_by_key(|e| e.lru)
1207 .map(|e| (e.id, e.bytes, e.space_id));
1208 let Some((id, bytes, space_id)) = victim else {
1209 break;
1210 };
1211 self.cache.entries.retain(|e| e.id != id);
1212 if self
1213 .space_cfg(space_id)
1214 .map(|c| c.bytes_per_token > 0)
1215 .unwrap_or(false)
1216 {
1217 self.pool_used = self.pool_used.saturating_sub(bytes);
1218 }
1219 freed += bytes;
1220 }
1221 Ok(freed)
1222 }
1223}
1224
1225impl MockEngine {
1226 #[allow(clippy::too_many_arguments)]
1227 fn submit_export(
1228 &mut self,
1229 seq: SeqHandle,
1230 space_id: u32,
1231 range: TokenRange,
1232 encoding: u8,
1233 dst_len: u64,
1234 sizing_gen: u64,
1235 kind: OpKind,
1236 ) -> Result<OpHandle, Status> {
1237 let cfg = self.space_ops_gate(space_id)?;
1238 let (bpt, blob) = (cfg.bytes_per_token, cfg.blob_bytes);
1239 let cadence_now = cfg.snapshot_cadence == 1;
1240 let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1241 if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1242 return Err(Status::Busy);
1243 }
1244 let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1245 if sizing_gen != s.content_gen {
1246 return Err(Status::StaleSizing);
1247 }
1248 let required = ops::export_size(s, bpt, blob, range, encoding)?;
1249 if dst_len < required {
1250 return Err(Status::BufferTooSmall);
1251 }
1252 if s.kind == space_kind::RECURRENT_BLOB
1253 && !s.boundaries.contains(&range.end)
1254 && !(cadence_now && range.end == s.valid_len)
1255 {
1256 return Err(Status::OutOfBoundary);
1257 }
1258 let range = if s.is_blob_like() {
1259 TokenRange {
1260 start: range.end,
1261 end: range.end,
1262 }
1263 } else {
1264 range
1265 };
1266 if kind == OpKind::Demote
1267 && s.demoted
1268 .iter()
1269 .any(|(a, b, _)| range.start < *b && *a < range.end)
1270 {
1271 return Err(Status::RejectBounds);
1272 }
1273
1274 let payload = mock_payload(s, range, encoding, required - ops::ENVELOPE_BYTES as u64);
1275 let sealed = ops::seal(s, self.bundle_fp, range, encoding, &payload);
1276
1277 let handle = self.next_op;
1278 self.next_op += 1;
1279 let demote_range = (kind == OpKind::Demote).then_some((range.start, range.end, encoding));
1280 match &mut q.busy {
1281 Busy::Idle => {
1282 q.busy = Busy::OpActive([space_id].into_iter().collect());
1283 }
1284 Busy::OpActive(set) => {
1285 set.insert(space_id);
1286 }
1287 Busy::Ticking => unreachable!("checked above"),
1288 }
1289 self.ops.insert(
1290 handle,
1291 OpState {
1292 handle,
1293 seq,
1294 space_id,
1295 kind,
1296 state: op_state::RUNNING,
1297 error: 0,
1298 bytes_total: sealed.len() as u64,
1299 bytes_moved: 0,
1300 output: Some(sealed),
1301 staged: None,
1302 demote_range,
1303 },
1304 );
1305 Ok(handle)
1306 }
1307
1308 fn submit_import(
1309 &mut self,
1310 seq: SeqHandle,
1311 space_id: u32,
1312 src: &[u8],
1313 kind: OpKind,
1314 ) -> Result<OpHandle, Status> {
1315 self.space_ops_gate(space_id)?;
1316 let bundle_fp = self.bundle_fp;
1317 let q = self.seqs.get_mut(&seq).ok_or(Status::UnknownHandle)?;
1318 if q.op_active_on(space_id) || q.busy == Busy::Ticking {
1319 return Err(Status::Busy);
1320 }
1321 let s = q.spaces.get(&space_id).ok_or(Status::UnknownHandle)?;
1322 let (env, _payload) = ops::open(src, s, bundle_fp)?;
1323 match kind {
1324 OpKind::Promote => {
1325 if !s
1326 .demoted
1327 .iter()
1328 .any(|(a, b, _)| *a == env.range.start && *b == env.range.end)
1329 {
1330 return Err(Status::RejectBounds);
1331 }
1332 }
1333 _ => {
1334 let blob = env.space_kind == space_kind::RECURRENT_BLOB
1335 || env.space_kind == space_kind::RING_KV;
1336 let ok = if blob {
1337 env.range.start == env.range.end
1338 } else {
1339 env.range.start == 0 && env.range.end >= env.range.start
1340 };
1341 if !ok {
1342 return Err(Status::EnvelopeMismatch);
1343 }
1344 }
1345 }
1346 let staged = ops::stage_import(&env, kind == OpKind::Promote);
1347
1348 let handle = self.next_op;
1349 self.next_op += 1;
1350 match &mut q.busy {
1351 Busy::Idle => {
1352 q.busy = Busy::OpActive([space_id].into_iter().collect());
1353 }
1354 Busy::OpActive(set) => {
1355 set.insert(space_id);
1356 }
1357 Busy::Ticking => unreachable!("checked above"),
1358 }
1359 self.ops.insert(
1360 handle,
1361 OpState {
1362 handle,
1363 seq,
1364 space_id,
1365 kind,
1366 state: op_state::RUNNING,
1367 error: 0,
1368 bytes_total: src.len() as u64,
1369 bytes_moved: 0,
1370 output: None,
1371 staged: Some(staged),
1372 demote_range: None,
1373 },
1374 );
1375 Ok(handle)
1376 }
1377
1378 fn release_op_claim(&mut self, seq: SeqHandle, space_id: u32) {
1379 if let Some(q) = self.seqs.get_mut(&seq) {
1380 if let Busy::OpActive(set) = &mut q.busy {
1381 set.remove(&space_id);
1382 if set.is_empty() {
1383 q.busy = Busy::Idle;
1384 }
1385 }
1386 }
1387 }
1388
1389 fn complete_op(&mut self, handle: OpHandle) {
1390 let Some(o) = self.ops.get_mut(&handle) else {
1391 return;
1392 };
1393 if o.state != op_state::RUNNING {
1394 return;
1395 }
1396 o.state = op_state::DONE;
1397 o.bytes_moved = o.bytes_total;
1398 let (seq, space_id) = (o.seq, o.space_id);
1399 let staged = o.staged.take();
1400 let demote_range = o.demote_range.take();
1401 let bytes_moved = o.bytes_moved;
1402
1403 let cfg_bpt = self
1404 .cfg
1405 .spaces
1406 .iter()
1407 .find(|c| c.space_id == space_id)
1408 .map(|c| c.bytes_per_token)
1409 .unwrap_or(0);
1410 let mut pool_delta: i64 = 0;
1411 if let Some(q) = self.seqs.get_mut(&seq) {
1412 if let Some(s) = q.spaces.get_mut(&space_id) {
1413 if let Some(st) = staged {
1414 if let Some((a, b)) = st.promote_range {
1415 s.demoted.retain(|(x, y, _)| !(*x == a && *y == b));
1416 pool_delta += ((b - a) * cfg_bpt) as i64;
1417 if st.taint_bits != 0 {
1418 s.taint_bits |= st.taint_bits;
1419 s.provenance =
1420 state::chain_digest(&s.provenance, state::prov_op::PROMOTE, b - a);
1421 }
1422 } else {
1423 let old_resident = s
1424 .valid_len
1425 .saturating_sub(s.demoted.iter().map(|(a, b, _)| b - a).sum());
1426 s.valid_len = st.valid_len;
1427 s.provenance = st.provenance;
1428 s.taint_bits = st.taint_bits;
1429 s.demoted.clear();
1430 if !st.boundaries.is_empty() {
1431 s.boundaries = st.boundaries;
1432 }
1433 pool_delta +=
1434 (st.valid_len * cfg_bpt) as i64 - (old_resident * cfg_bpt) as i64;
1435 }
1436 s.content_gen += 1;
1437 } else if let Some((a, b, encoding)) = demote_range {
1438 s.demoted.push((a, b, encoding));
1439 s.content_gen += 1;
1440 pool_delta -= ((b - a) * cfg_bpt) as i64;
1441 }
1442 }
1443 }
1444 if pool_delta >= 0 {
1445 self.pool_used += pool_delta as u64;
1446 } else {
1447 self.pool_used = self.pool_used.saturating_sub((-pool_delta) as u64);
1448 }
1449 self.release_op_claim(seq, space_id);
1450 self.completions_pending.push(OpComplete {
1451 op: handle,
1452 state: op_state::DONE,
1453 _pad0: [0; 3],
1454 error: 0,
1455 bytes_moved,
1456 });
1457 }
1458}
1459
1460pub(crate) fn longest_common_prefix(a: &[u32], b: &[u32]) -> u64 {
1461 a.iter().zip(b.iter()).take_while(|(x, y)| x == y).count() as u64
1462}
1463
1464fn mock_payload(
1465 s: &SpaceState,
1466 range: TokenRange,
1467 encoding: u8,
1468 payload_len: u64,
1469) -> Vec<u8> {
1470 let mut seedbuf = [0u8; 33];
1471 seedbuf[..8].copy_from_slice(&s.provenance[..8]);
1472 seedbuf[8..16].copy_from_slice(&range.start.to_le_bytes());
1473 seedbuf[16..24].copy_from_slice(&range.end.to_le_bytes());
1474 seedbuf[24..32].copy_from_slice(&s.valid_len.to_le_bytes());
1475 seedbuf[32] = encoding;
1476 let h = xxhash_rust::xxh3::xxh3_64(&seedbuf);
1477 let mut out = h
1478 .to_le_bytes()
1479 .repeat((payload_len as usize).div_ceil(8).max(1));
1480 out.truncate(payload_len as usize);
1481 out
1482}