Skip to main content

superfluid_executor/
executor.rs

1//! `Executor<P>`.
2
3use std::collections::{HashMap, HashSet};
4
5use superfluid_abi::array::read_array;
6use superfluid_abi::{
7    cert_domain, cert_mode, exactness, strategy_cap, ArenaBounds, CapabilityReq, ExactnessCert,
8    op_state, space_kind, tier, MatchCandidate, MatchResult, OpStatus, RingRef,
9    SamplingParams, SpaceMatch, StateSpaceDesc, StrategyGrant,
10    StrategyRegistration, TickEvents, TickPlan, TokenRange, RecordArena,
11    Status,
12};
13use superfluid_engine::engine::{Engine, OpHandle, SeqHandle};
14use superfluid_engine::Rings;
15
16use crate::cache::{CacheEntry, PrefixCache, HOST_EXPORT_CLASS, RESIDENT_PREFIX_CLASS, SHARED_PREFIX_CLASS};
17use crate::grammar::{Grammars, LaneGrammar};
18use crate::primitives::{PrimError, RuntimeDescriptor, RuntimePrimitives, Seq};
19
20pub const KV_SPACE: u32 = 1;
21
22/// Ticks after memory was last short on the machine during which no cache
23/// entry is exported into host memory.
24const HOST_SHORT_TICKS: u64 = 64;
25
26#[derive(Debug, Clone)]
27pub struct ExecutorConfig {
28    pub seed_ttl_ticks: u64,
29}
30
31impl Default for ExecutorConfig {
32    fn default() -> Self {
33        ExecutorConfig { seed_ttl_ticks: 64 }
34    }
35}
36
37static FATAL_EXITS: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
38
39pub fn exit_on_fatal(on: bool) {
40    FATAL_EXITS.store(on, std::sync::atomic::Ordering::Relaxed);
41}
42
43pub(crate) fn fatal_exits() -> bool {
44    FATAL_EXITS.load(std::sync::atomic::Ordering::Relaxed)
45}
46
47pub(crate) struct Lane {
48    pub seq: Seq,
49    pub sampling: u32,
50    pub params: SamplingParams,
51    pub rng_base: u64,
52    pub rng_counter: u64,
53    pub prompt: Vec<u32>,
54    pub prefilled: u64,
55    pub prefill_end: u64,
56    pub committed: Vec<u32>,
57    pub pending_input: Option<u32>,
58    pub pending_logits: Option<RingRef>,
59    pub finished: bool,
60    pub bias: Option<Vec<(u32, f32)>>,
61    pub logprobs_top: Option<usize>,
62    pub grammar: Option<LaneGrammar>,
63    /// Drafts prompt-lookup continuations (admitted with the strategy's slot).
64    pub spec: Option<crate::speculate::LaneSpec>,
65}
66
67/// The one strategy this executor serves, once registered.
68pub(crate) struct Strategy {
69    pub slot: u32,
70    pub cert_id: u32,
71    pub cfg: crate::speculate::SpecConfig,
72}
73
74pub(crate) struct SeedLease {
75    pub prefix: Vec<u32>,
76    pub entry_seq: Seq,
77    pub determinism: u8,
78    pub expires_at_tick: u64,
79    pub adopted: Option<Seq>,
80}
81
82pub(crate) struct PendingExport {
83    pub len: u64,
84    pub encoding: u8,
85    pub gen: u64,
86    pub sealed: Vec<u8>,
87}
88
89pub(crate) struct OpRecord {
90    pub state: u8,
91    pub error: u32,
92    pub bytes_moved: u64,
93    pub output: Option<Vec<u8>>,
94}
95
96pub(crate) struct AbiStore<T> {
97    #[allow(dead_code)]
98    pub arena: RecordArena,
99    pub value: Box<T>,
100}
101
102pub struct Executor<P: RuntimePrimitives> {
103    pub(crate) prim: P,
104    pub(crate) desc: RuntimeDescriptor,
105    pub(crate) cfg: ExecutorConfig,
106    pub(crate) descs: Vec<StateSpaceDesc>,
107
108    pub(crate) lanes: HashMap<u64, Lane>,
109    pub(crate) strategy: Option<Strategy>,
110    pub(crate) spec_gate: crate::speculate::ThroughputGate,
111    /// Seconds a prompt token takes to prefill here, as ticks have run them.
112    pub(crate) prefill_secs_per_token: Option<f64>,
113    grant_store: Option<AbiStore<StrategyGrant>>,
114    pub(crate) bare: HashSet<Seq>,
115    /// Bare sequences forked from another: published, they are copies kept
116    /// for others (a cache entry marked shared).
117    pub(crate) forks: HashSet<Seq>,
118    pub(crate) adopt_leased: HashSet<Seq>,
119    pub(crate) bare_tokens: HashMap<Seq, Vec<u32>>,
120    pub(crate) seq_taint: HashMap<Seq, u32>,
121    pub(crate) seeds: HashMap<u64, SeedLease>,
122    pub(crate) cache: PrefixCache,
123    pub(crate) biases: HashMap<u32, Vec<(u32, f32)>>,
124    pub(crate) grammars: Grammars,
125    pub(crate) capabilities: std::sync::OnceLock<String>,
126
127    pub(crate) seq_gen: HashMap<Seq, u64>,
128    pub(crate) pending_export: HashMap<Seq, Vec<PendingExport>>,
129    pub(crate) ops: HashMap<OpHandle, OpRecord>,
130    pub(crate) completions_pending: Vec<superfluid_abi::OpComplete>,
131
132    pub(crate) next_seed: u64,
133    pub(crate) next_bias: u32,
134    pub(crate) next_op: OpHandle,
135    pub(crate) tick_count: u64,
136    pub(crate) last_plan_seq: Option<u64>,
137
138    pub(crate) inject_fault: HashMap<u64, u32>,
139    pub(crate) faulted: HashSet<u64>,
140    pub(crate) penalty_exempt: Option<HashSet<u32>>,
141    pub(crate) last_logprobs: Vec<superfluid_abi::LaneLogprob>,
142
143    pub(crate) match_store: Option<AbiStore<MatchResult>>,
144    pub(crate) events_store: Option<AbiStore<TickEvents>>,
145    /// The tick before which no cache entry is exported: memory was short.
146    pub(crate) exports_after_tick: u64,
147}
148
149fn build_descs(d: &RuntimeDescriptor) -> Vec<StateSpaceDesc> {
150    let mut name = [0u8; 32];
151    name[..7].copy_from_slice(b"kv.full");
152    vec![StateSpaceDesc {
153        space_id: KV_SPACE,
154        kind: space_kind::PAGED_TOKEN_KV,
155        version_tag: 1,
156        bytes_per_token: d.kv_bytes_per_token,
157        blob_bytes: 0,
158        page_size_tokens: d.page_size_tokens.max(1),
159        fork_cost_class: 0,
160        fork_cost_bytes: 0,
161        snapshot_cadence: 0,
162        snapshot_interval_tokens: 0,
163        placement: 0,
164        flags: 0,
165        name,
166    }]
167}
168
169pub(crate) fn prim_status(e: PrimError) -> Status {
170    match e {
171        PrimError::Unsupported => Status::Unsupported,
172        PrimError::UnknownSeq => Status::UnknownHandle,
173        PrimError::OutOfBoundary => Status::OutOfBoundary,
174        PrimError::Capacity => Status::NeedsReplan,
175        PrimError::Fault(_) | PrimError::Fatal => Status::Fatal,
176    }
177}
178
179impl<P: RuntimePrimitives> Executor<P> {
180    pub fn new(prim: P, cfg: ExecutorConfig) -> Executor<P> {
181        let desc = prim.describe();
182        let descs = build_descs(&desc);
183        Executor {
184            prim,
185            desc,
186            cfg,
187            descs,
188            lanes: HashMap::new(),
189            strategy: None,
190            spec_gate: Default::default(),
191            prefill_secs_per_token: None,
192            grant_store: None,
193            bare: HashSet::new(),
194            forks: HashSet::new(),
195            adopt_leased: HashSet::new(),
196            bare_tokens: HashMap::new(),
197            seq_taint: HashMap::new(),
198            seeds: HashMap::new(),
199            cache: PrefixCache::default(),
200            biases: HashMap::new(),
201            grammars: Grammars::default(),
202            capabilities: std::sync::OnceLock::new(),
203            seq_gen: HashMap::new(),
204            pending_export: HashMap::new(),
205            ops: HashMap::new(),
206            completions_pending: Vec::new(),
207            next_seed: 1,
208            next_bias: 1,
209            next_op: 1,
210            tick_count: 0,
211            last_plan_seq: None,
212            inject_fault: HashMap::new(),
213            faulted: HashSet::new(),
214            penalty_exempt: None,
215            last_logprobs: Vec::new(),
216            match_store: None,
217            events_store: None,
218            exports_after_tick: 0,
219        }
220    }
221
222    pub fn primitives(&self) -> &P {
223        &self.prim
224    }
225
226    pub fn primitives_mut(&mut self) -> &mut P {
227        &mut self.prim
228    }
229
230    pub fn descriptor(&self) -> &RuntimeDescriptor {
231        &self.desc
232    }
233
234    pub fn lane_committed(&self, lane_tag: u64) -> Option<&[u32]> {
235        self.lanes.get(&lane_tag).map(|l| l.committed.as_slice())
236    }
237
238    pub fn inject_fault(&mut self, lane_tag: u64, code: u32) {
239        self.inject_fault.insert(lane_tag, code);
240    }
241
242    /// Pool bytes the prefix cache accounts for.
243    pub fn cache_bytes(&self) -> u64 {
244        self.cache.resident_bytes()
245    }
246
247    /// Tokens of state the prefix cache holds out of the runtime's pool.
248    pub fn cache_exported_tokens(&self) -> u64 {
249        self.cache.exported_tokens()
250    }
251
252    pub fn match_lengths(&self, span: &[u32]) -> Vec<u64> {
253        self.cache.candidates(span, self.page(), self.exact_only()).into_iter().map(|(l, _)| l).collect()
254    }
255
256    /// Sequences the runtime holds for this executor: lanes, bare sequences
257    /// and the cache entries in its pool.
258    pub fn live_sequences(&self) -> u64 {
259        let in_pool = self.cache.entries.iter().filter(|e| e.exported.is_none()).count();
260        (self.lanes.len() + self.bare.len() + in_pool) as u64
261    }
262
263    pub fn last_logprobs(&self) -> Vec<superfluid_abi::LaneLogprob> {
264        self.last_logprobs.clone()
265    }
266
267    pub(crate) fn page(&self) -> u64 {
268        self.desc.page_size_tokens.max(1) as u64
269    }
270
271    pub(crate) fn exact_only(&self) -> bool {
272        !self.desc.truncate_partial
273    }
274
275    pub(crate) fn entry_bytes(&self, tokens: u64) -> u64 {
276        tokens * self.desc.kv_bytes_per_token.max(1)
277    }
278
279    fn free_evicted(&mut self, gone: Vec<(Seq, bool)>) {
280        for (s, in_pool) in gone {
281            self.forget_seq(s);
282            if in_pool {
283                self.prim.seq_free(s);
284            }
285        }
286    }
287
288    pub fn compat_identity(&self) -> [u8; 32] {
289        crate::envelope::compat_identity(
290            space_kind::PAGED_TOKEN_KV,
291            1,
292            &self.desc.runtime_id,
293            &self.desc.runtime_version,
294            &self.desc.weights_identity,
295        )
296    }
297
298    pub(crate) fn pin(&mut self, seq: Seq) {
299        if let Some(e) = self.cache.entries.iter_mut().find(|e| e.seq == seq) {
300            e.pins += 1;
301        }
302    }
303
304    pub(crate) fn unpin(&mut self, seq: Seq) {
305        if let Some(e) = self.cache.entries.iter_mut().find(|e| e.seq == seq) {
306            e.pins = e.pins.saturating_sub(1);
307        }
308    }
309
310    pub(crate) fn gen_of(&self, seq: Seq) -> u64 {
311        self.seq_gen.get(&seq).copied().unwrap_or(1)
312    }
313
314    pub(crate) fn bump(&mut self, seq: Seq) {
315        *self.seq_gen.entry(seq).or_insert(1) += 1;
316        self.pending_export.remove(&seq);
317    }
318
319    pub(crate) fn forget_seq(&mut self, seq: Seq) {
320        self.seq_gen.remove(&seq);
321        self.pending_export.remove(&seq);
322        self.seq_taint.remove(&seq);
323        self.bare_tokens.remove(&seq);
324        self.adopt_leased.remove(&seq);
325        self.forks.remove(&seq);
326    }
327
328    pub(crate) fn inherit_taint(&mut self, src: Seq, dst: Seq) {
329        if let Some(t) = self.seq_taint.get(&src).copied().filter(|t| *t != 0) {
330            self.seq_taint.insert(dst, t);
331        }
332    }
333
334    pub(crate) fn seq_is_cached(&self, seq: Seq) -> bool {
335        self.cache.entries.iter().any(|e| e.seq == seq)
336    }
337
338    pub(crate) fn tokens_of(&self, seq: Seq) -> Option<&[u32]> {
339        self.lanes
340            .values()
341            .find(|l| l.seq == seq)
342            .map(|l| l.committed.as_slice())
343            .or_else(|| self.cache.entries.iter().find(|e| e.seq == seq).map(|e| e.tokens.as_slice()))
344            .or_else(|| self.bare_tokens.get(&seq).map(|t| t.as_slice()))
345    }
346
347    pub(crate) fn seq_known(&self, seq: Seq) -> bool {
348        self.bare.contains(&seq)
349            || self.lanes.values().any(|l| l.seq == seq)
350            || self.cache.entries.iter().any(|e| e.seq == seq && e.exported.is_none())
351    }
352
353    pub(crate) fn seq_is_lane(&self, seq: Seq) -> bool {
354        self.lanes.values().any(|l| l.seq == seq)
355    }
356
357    pub(crate) fn provenance_of(&self, seq: Seq, len: u64) -> [u8; 32] {
358        match self.tokens_of(seq) {
359            Some(t) if t.len() as u64 >= len => superfluid_fingerprint::content_digest(&t[..len as usize]),
360            _ => [0u8; 32],
361        }
362    }
363
364    pub(crate) fn compat(&self) -> [u8; 32] {
365        self.compat_identity()
366    }
367
368    pub(crate) fn complete_op(&mut self, result: Result<Vec<u8>, Status>, bytes: u64) -> OpHandle {
369        let h = self.next_op;
370        self.next_op += 1;
371        let rec = match result {
372            Ok(out) => OpRecord { state: op_state::DONE, error: 0, bytes_moved: bytes, output: Some(out) },
373            Err(e) => OpRecord { state: op_state::FAILED, error: e.raw() as u32, bytes_moved: 0, output: None },
374        };
375        self.completions_pending.push(superfluid_abi::OpComplete {
376            op: h,
377            state: rec.state,
378            _pad0: [0; 3],
379            error: rec.error,
380            bytes_moved: rec.bytes_moved,
381        });
382        self.ops.insert(h, rec);
383        h
384    }
385
386    fn size_export(&mut self, seq: Seq, len: u64, encoding: u8) -> Result<(u64, u64), Status> {
387        if !self.desc.export_encodings.contains(&encoding) {
388            return Err(Status::Unsupported);
389        }
390        if len == 0 || len > self.prim.seq_len(seq) {
391            return Err(Status::RejectBounds);
392        }
393        let gen = self.gen_of(seq);
394        if let Some(pe) = self
395            .pending_export
396            .get(&seq)
397            .and_then(|v| v.iter().find(|pe| pe.len == len && pe.encoding == encoding && pe.gen == gen))
398        {
399            return Ok((pe.sealed.len() as u64, gen));
400        }
401        let payload = self.prim.seq_export(seq, len, encoding).map_err(prim_status)?;
402        let taint_bits = self.seq_taint.get(&seq).copied().unwrap_or(0)
403            | if encoding == superfluid_abi::encoding::LOSSLESS { 0 } else { superfluid_abi::taint::QUANTIZED_DEMOTION };
404        let sealed = crate::envelope::seal(crate::envelope::SealArgs {
405            kind: space_kind::PAGED_TOKEN_KV,
406            version_tag: 1,
407            compat: self.compat(),
408            provenance: self.provenance_of(seq, len),
409            taint_bits,
410            range: TokenRange { start: 0, end: len },
411            encoding,
412            payload: &payload,
413        });
414        let bytes = sealed.len() as u64;
415        let list = self.pending_export.entry(seq).or_default();
416        list.retain(|pe| pe.gen == gen);
417        list.push(PendingExport { len, encoding, gen, sealed });
418        Ok((bytes, gen))
419    }
420
421    fn take_export(&mut self, seq: Seq, len: u64, encoding: u8, dst_len: u64, sizing_gen: u64) -> Result<OpHandle, Status> {
422        if sizing_gen != self.gen_of(seq) {
423            return Err(Status::StaleSizing);
424        }
425        let list = self.pending_export.get_mut(&seq).ok_or(Status::StaleSizing)?;
426        let pos = list
427            .iter()
428            .position(|pe| pe.len == len && pe.encoding == encoding && pe.gen == sizing_gen)
429            .ok_or(Status::StaleSizing)?;
430        let sealed_len = list[pos].sealed.len() as u64;
431        if dst_len < sealed_len {
432            return Err(Status::BufferTooSmall);
433        }
434        let pe = list.remove(pos);
435        Ok(self.complete_op(Ok(pe.sealed), sealed_len))
436    }
437
438    pub(crate) fn sweep_expired_seeds(&mut self) {
439        let now = self.tick_count;
440        let expired: Vec<u64> = self
441            .seeds
442            .iter()
443            .filter(|(_, l)| l.expires_at_tick < now)
444            .map(|(h, _)| *h)
445            .collect();
446        for h in expired {
447            if let Some(lease) = self.seeds.remove(&h) {
448                match lease.adopted {
449                    Some(seq) => {
450                        self.adopt_leased.remove(&seq);
451                    }
452                    None => self.unpin(lease.entry_seq),
453                }
454            }
455        }
456    }
457
458    /// A new sequence. A runtime with a fixed number of them (`max_seqs`)
459    /// may hold them all, most in cache entries on a model whose entries no
460    /// later one covers: the entry least worth keeping then gives its up.
461    fn seq_create_making_room(&mut self) -> Result<Seq, PrimError> {
462        match self.prim.seq_create() {
463            Err(PrimError::Capacity) if self.desc.max_seqs > 0 => {
464                let (_, left, _) = self.evict_bytes(1, u64::MAX);
465                if left == 0 {
466                    return Err(PrimError::Capacity);
467                }
468                self.prim.seq_create()
469            }
470            other => other,
471        }
472    }
473
474    /// Unpinned entries leave the pool, least recently used first, until `bytes` are freed
475    /// without overshooting `cap`. Returns bytes freed, entries gone, and each eviction.
476    pub(crate) fn evict_bytes(&mut self, bytes: u64, cap: u64) -> (u64, usize, Vec<(u64, u64)>) {
477        self.evict_classes(bytes, cap, true)
478    }
479
480    fn evict_classes(&mut self, bytes: u64, cap: u64, shared_too: bool) -> (u64, usize, Vec<(u64, u64)>) {
481        let (freed, picked) = self.cache.plan_evict(bytes, cap, shared_too);
482        let seqs: Vec<Seq> = picked.iter().map(|&i| self.cache.entries[i].seq).collect();
483        let mut lost = Vec::new();
484        for &seq in &seqs {
485            self.leave_pool(seq, &mut lost);
486        }
487        (freed, seqs.len(), lost)
488    }
489
490    /// Memory is short on the machine: the exports no lease pins go, and for
491    /// a while no entry that leaves the pool is kept as one.
492    fn drop_exports(&mut self) {
493        self.exports_after_tick = self.tick_count + HOST_SHORT_TICKS;
494        let gone: Vec<Seq> = self.cache.entries.iter().filter(|e| e.exported.is_some() && e.pins == 0).map(|e| e.seq).collect();
495        for seq in gone {
496            self.cache.remove(seq);
497            self.forget_seq(seq);
498        }
499    }
500
501    /// Whether another entry serves what `seq` does (see [`CacheEntry::covered_by`]), such
502    /// as a conversation's earlier turn beside its later one.
503    fn covered(&self, seq: Seq) -> bool {
504        let Some(e) = self.cache.get(seq) else { return false };
505        if self.exact_only() || (e.tokens.len() as u64) < self.desc.seed_min_tokens {
506            return false;
507        }
508        let taint = |s: Seq| self.seq_taint.get(&s).copied().unwrap_or(0);
509        self.cache.entries.iter().any(|o| o.seq != seq && e.covered_by(o) && taint(o.seq) & !taint(seq) == 0)
510    }
511
512    /// Keep the entry `seq` as its export, evicting less valuable unpinned exports to make
513    /// room. `false`: it cannot be kept that way, and nothing moved.
514    fn export_entry(&mut self, seq: Seq, lost: &mut Vec<(u64, u64)>) -> bool {
515        const LOSSLESS: u8 = superfluid_abi::encoding::LOSSLESS;
516        let Some(e) = self.cache.get(seq) else { return false };
517        let (len, worth) = (e.tokens.len() as u64, (e.holds_shared_prefix(), e.last_used));
518        let budget = self.desc.cache_exported_cells;
519        let host_short = self.tick_count < self.exports_after_tick;
520        if host_short || len > budget || !self.desc.export_encodings.contains(&LOSSLESS) || self.prim.seq_len(seq) != len {
521            return false;
522        }
523        let mut over = (self.cache.exported_tokens() + len).saturating_sub(budget);
524        let mut lesser: Vec<&CacheEntry> = self
525            .cache
526            .entries
527            .iter()
528            .filter(|o| o.exported.is_some() && o.pins == 0 && (o.holds_shared_prefix(), o.last_used) <= worth)
529            .collect();
530        lesser.sort_by_key(|o| (o.holds_shared_prefix(), o.last_used));
531        let mut out: Vec<Seq> = Vec::new();
532        for o in lesser {
533            if over == 0 {
534                break;
535            }
536            over = over.saturating_sub(o.tokens.len() as u64);
537            out.push(o.seq);
538        }
539        if over > 0 {
540            return false;
541        }
542        let Ok(payload) = self.prim.seq_export(seq, len, LOSSLESS) else { return false };
543        for o in out {
544            if let Some(gone) = self.cache.remove(o) {
545                self.forget_seq(o);
546                lost.push((0, gone.tokens.len() as u64));
547            }
548        }
549        self.prim.seq_free(seq);
550        // The sequence is gone. Its handle stays the entry's name, and the
551        // taint kept under it is the entry's.
552        self.seq_gen.remove(&seq);
553        self.pending_export.remove(&seq);
554        if let Some(e) = self.cache.entries.iter_mut().find(|e| e.seq == seq) {
555            e.exported = Some(payload);
556        }
557        true
558    }
559
560    /// The entry `seq` leaves the pool: exported where there is room and no other entry
561    /// covers it, else evicted into `lost` as `(pool bytes freed, tokens)`.
562    pub(crate) fn leave_pool(&mut self, seq: Seq, lost: &mut Vec<(u64, u64)>) {
563        if !self.covered(seq) && self.export_entry(seq, lost) {
564            return;
565        }
566        if let Some(e) = self.cache.remove(seq) {
567            self.forget_seq(seq);
568            self.prim.seq_free(seq);
569            lost.push((e.bytes, e.tokens.len() as u64));
570        }
571    }
572
573    /// Keep the cache inside what the runtime affords in its pool
574    /// (`cache_resident_cells`): while the entries there hold more cells
575    /// between them than that (cells no lane or bare sequence shares), the
576    /// least recently used one no lease pins leaves it. One whose cells
577    /// another sequence still holds would give nothing back, and goes only
578    /// when no other would. And the exports another entry has come to cover
579    /// go: nothing would seed from them.
580    pub(crate) fn settle_cache(&mut self, lost: &mut Vec<(u64, u64)>) {
581        let limit = self.desc.cache_resident_cells;
582        while limit > 0 && self.prim.cells_released(&self.cache.resident()) > limit {
583            let mut idle: Vec<&CacheEntry> = self.cache.entries.iter().filter(|e| e.exported.is_none() && e.pins == 0).collect();
584            idle.sort_by_key(|e| (e.holds_shared_prefix(), e.last_used));
585            let gives = |e: &&CacheEntry| self.covered(e.seq) || self.prim.cells_released(&[e.seq]) > 0;
586            let Some(seq) = idle.iter().copied().find(gives).or(idle.first().copied()).map(|e| e.seq) else { break };
587            self.leave_pool(seq, lost);
588        }
589        if !std::mem::take(&mut self.cache.changed) {
590            return;
591        }
592        let covered: Vec<Seq> =
593            self.cache.entries.iter().filter(|e| e.exported.is_some() && e.pins == 0 && self.covered(e.seq)).map(|e| e.seq).collect();
594        for seq in covered {
595            if let Some(e) = self.cache.remove(seq) {
596                self.forget_seq(seq);
597                lost.push((0, e.tokens.len() as u64));
598            }
599        }
600    }
601
602    fn store_match(&mut self, candidates: Vec<MatchCandidate>) -> &MatchResult {
603        let mut arena_out = RecordArena::new();
604        let spaces = vec![SpaceMatch {
605            space_id: KV_SPACE,
606            _pad0: 0,
607            candidates: arena_out.push_records(&candidates),
608        }];
609        let result = MatchResult {
610            struct_size: std::mem::size_of::<MatchResult>() as u64,
611            spaces: arena_out.push_records(&spaces),
612        };
613        self.match_store = Some(AbiStore { arena: arena_out, value: Box::new(result) });
614        &self.match_store.as_ref().expect("just stored").value
615    }
616}
617
618impl<P: RuntimePrimitives> Engine for Executor<P> {
619    fn tick<'a>(
620        &'a mut self,
621        plan: &TickPlan,
622        arena: &RecordArena,
623        rings: &mut dyn Rings,
624    ) -> Result<&'a TickEvents, Status> {
625        self.run_tick(plan, arena, rings)?;
626        Ok(&self.events_store.as_ref().expect("stored by run_tick").value)
627    }
628
629    fn pump(&mut self) {
630    }
631
632    fn state_spaces(&self) -> &[StateSpaceDesc] {
633        &self.descs
634    }
635
636    fn strategy_register(
637        &mut self,
638        reg: &StrategyRegistration,
639    ) -> Result<&StrategyGrant, Status> {
640        // Prompt lookup verifies a draft in one pass and cuts the rejected
641        // tail off: it needs rows at every position and a partial rollback.
642        if !self.desc.verify_rows || !self.desc.truncate_partial || reg.strategy_id.is_null() {
643            return Err(Status::RegistrationRefused);
644        }
645        // SAFETY: checked non-null; registration contract: NUL-terminated.
646        let id = unsafe { std::ffi::CStr::from_ptr(reg.strategy_id) }.to_string_lossy().into_owned();
647        if id != crate::speculate::STRATEGY_ID || reg.kernel_caps_required != 0 {
648            return Err(Status::RegistrationRefused);
649        }
650        let bounds = ArenaBounds { base: 0, len: usize::MAX };
651        // SAFETY: in-process registration call; the worker materialized
652        // these arrays and they outlive the call.
653        let caps: Vec<CapabilityReq> =
654            unsafe { read_array(&reg.capabilities, &bounds) }.map_err(|e| e.status())?.collect();
655        // SAFETY: as above.
656        let archs: Vec<u64> = unsafe { read_array(&reg.target_archs, &bounds) }.map_err(|e| e.status())?.collect();
657        if !archs.is_empty() || caps.iter().any(|c| c.kind_id != strategy_cap::PROPOSAL_LINEAR) {
658            return Err(Status::RegistrationRefused);
659        }
660        let mut params = String::new();
661        for c in &caps {
662            // SAFETY: as above; the params are a byte array.
663            let bytes: Vec<u8> = unsafe { read_array(&c.params, &bounds) }.map_err(|e| e.status())?.collect();
664            params.push_str(&String::from_utf8_lossy(&bytes));
665        }
666        let cfg = crate::speculate::SpecConfig::parse(&params);
667        let (slot, cert_id) = (1, 1);
668        self.strategy = Some(Strategy { slot, cert_id, cfg });
669        self.spec_gate = Default::default();
670        let mut arena = RecordArena::new();
671        // The draft path is exact in distribution: each row is picked by the lane's own
672        // sampler at the position a plain decode would use.
673        let certificates = arena.push_records(&[ExactnessCert {
674            cert_id,
675            exactness: exactness::DISTRIBUTION_EXACT,
676            sampling_modes: cert_mode::GREEDY | cert_mode::GUMBEL,
677            grammar_allowed: 0,
678            logit_bias_allowed: 0,
679            param_domain: cert_domain::ANY,
680            verification_shape_class: 1,
681            admissible_host_identities: superfluid_abi::Array::EMPTY,
682        }]);
683        let grant = StrategyGrant {
684            struct_size: std::mem::size_of::<StrategyGrant>() as u64,
685            strategy_slot: slot,
686            _pad0: 0,
687            certificates,
688            state_spaces: superfluid_abi::Array::EMPTY,
689            reserved_bytes: 0,
690        };
691        self.grant_store = Some(AbiStore { arena, value: Box::new(grant) });
692        Ok(&self.grant_store.as_ref().expect("just stored").value)
693    }
694
695    fn space_match(
696        &mut self,
697        space_id: u32,
698        span_tokens: &[u32],
699        _media_deps: &[[u8; 32]],
700    ) -> Result<&MatchResult, Status> {
701        if space_id != KV_SPACE && space_id != superfluid_abi::ALL_SPACES {
702            return Err(Status::UnknownHandle);
703        }
704        let page = self.page();
705        let exact = self.exact_only();
706        let candidates: Vec<MatchCandidate> = self
707            .cache
708            .candidates(span_tokens, page, exact)
709            .into_iter()
710            .map(|(len, seq)| MatchCandidate {
711                prefix_len: len,
712                provenance_digest: superfluid_fingerprint::content_digest(&span_tokens[..len as usize]),
713                taint_bits: self.seq_taint.get(&seq).copied().unwrap_or(0),
714                // An entry out of the pool is served by an import.
715                resident_tier: if self.cache.get(seq).is_some_and(|e| e.exported.is_some()) { tier::HOST } else { tier::GPU },
716                _pad0: [0; 3],
717            })
718            .collect();
719        Ok(self.store_match(candidates))
720    }
721
722    fn seed_acquire(
723        &mut self,
724        span_tokens: &[u32],
725        prefix_len: u64,
726        determinism_class: u8,
727    ) -> Result<u64, Status> {
728        let page = self.page();
729        let exact = self.exact_only();
730        if prefix_len == 0 || !prefix_len.is_multiple_of(page) || prefix_len > span_tokens.len() as u64 {
731            return Err(Status::SeedUnservable);
732        }
733        if exact && prefix_len == span_tokens.len() as u64 {
734            return Err(Status::SeedUnservable);
735        }
736        let want = &span_tokens[..prefix_len as usize];
737        let entry_seq = self
738            .cache
739            .entries
740            .iter()
741            .filter(|e| {
742                let covers = e.tokens.len() as u64 >= prefix_len && e.tokens[..prefix_len as usize] == *want;
743                let whole = e.tokens.len() as u64 == prefix_len;
744                covers && (!exact || whole) && (whole || prefix_len >= self.desc.seed_min_tokens) && e.serves(prefix_len)
745            })
746            // An entry in the pool before one out of it: its seed is a copy,
747            // not an import.
748            .min_by_key(|e| e.exported.is_some())
749            .map(|e| e.seq)
750            .ok_or(Status::SeedUnservable)?;
751        self.pin(entry_seq);
752        let handle = self.next_seed;
753        self.next_seed += 1;
754        self.seeds.insert(
755            handle,
756            SeedLease {
757                prefix: want.to_vec(),
758                entry_seq,
759                determinism: determinism_class,
760                expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
761                adopted: None,
762            },
763        );
764        Ok(handle)
765    }
766
767    fn seed_lease_ticks(&self) -> Option<u64> {
768        Some(self.cfg.seed_ttl_ticks)
769    }
770
771    fn seed_release(&mut self, seed_handle: u64) -> Result<(), Status> {
772        let lease = self.seeds.remove(&seed_handle).ok_or(Status::UnknownHandle)?;
773        match lease.adopted {
774            Some(seq) => {
775                self.adopt_leased.remove(&seq);
776            }
777            None => self.unpin(lease.entry_seq),
778        }
779        Ok(())
780    }
781
782    fn seed_adopt(&mut self, seq: SeqHandle, span_tokens: &[u32]) -> Result<u64, Status> {
783        if !self.bare.contains(&seq) {
784            return Err(Status::UnknownHandle);
785        }
786        if self.adopt_leased.contains(&seq) {
787            return Err(Status::Busy);
788        }
789        if span_tokens.len() as u64 != self.prim.seq_len(seq) || span_tokens.is_empty() {
790            return Err(Status::OutOfBoundary);
791        }
792        self.adopt_leased.insert(seq);
793        self.bare_tokens.insert(seq, span_tokens.to_vec());
794        let handle = self.next_seed;
795        self.next_seed += 1;
796        self.seeds.insert(
797            handle,
798            SeedLease {
799                prefix: span_tokens.to_vec(),
800                entry_seq: 0,
801                determinism: superfluid_abi::determinism::BEST_EFFORT,
802                expires_at_tick: self.tick_count + self.cfg.seed_ttl_ticks,
803                adopted: Some(seq),
804            },
805        );
806        Ok(handle)
807    }
808
809    fn seq_fork(&mut self, parent: SeqHandle, flags: u32) -> Result<SeqHandle, Status> {
810        if !self.seq_known(parent) {
811            return Err(Status::UnknownHandle);
812        }
813        let len = self.prim.seq_len(parent);
814        let child = self.seq_create_making_room().map_err(prim_status)?;
815        if let Err(e) = self.prim.seq_copy(parent, child, len) {
816            self.prim.seq_free(child);
817            return Err(prim_status(e));
818        }
819        self.bare.insert(child);
820        if flags & superfluid_abi::fork_flags::PRIVATE == 0 {
821            self.forks.insert(child);
822        }
823        self.seq_gen.insert(child, 1);
824        if let Some(tokens) = self.tokens_of(parent).map(|t| t[..t.len().min(len as usize)].to_vec()) {
825            self.bare_tokens.insert(child, tokens);
826        }
827        self.inherit_taint(parent, child);
828        Ok(child)
829    }
830
831    fn lane_sequence(&self, lane_tag: u64) -> Option<SeqHandle> {
832        self.lanes.get(&lane_tag).map(|l| l.seq)
833    }
834
835    fn create_sequence(&mut self) -> Result<SeqHandle, Status> {
836        let seq = self.seq_create_making_room().map_err(prim_status)?;
837        self.bare.insert(seq);
838        Ok(seq)
839    }
840
841    fn free_sequence(&mut self, seq: SeqHandle) -> Result<(), Status> {
842        if !self.bare.contains(&seq) {
843            return Err(Status::UnknownHandle);
844        }
845        if self.adopt_leased.contains(&seq) {
846            return Err(Status::Busy);
847        }
848        self.bare.remove(&seq);
849        self.forget_seq(seq);
850        self.prim.seq_free(seq);
851        Ok(())
852    }
853
854    fn publish_sequence(&mut self, seq: SeqHandle, tokens: &[u32]) -> Result<(), Status> {
855        if !self.bare.contains(&seq) {
856            return Err(Status::UnknownHandle);
857        }
858        if self.adopt_leased.contains(&seq) {
859            return Err(Status::Busy);
860        }
861        if tokens.len() as u64 > self.prim.seq_len(seq) {
862            return Err(Status::OutOfBoundary);
863        }
864        let page = self.page();
865        let len = self.prim.seq_len(seq);
866        let key_len = if self.exact_only() { tokens.len() as u64 } else { tokens.len() as u64 / page * page };
867        if self.exact_only() && key_len != len {
868            return Err(Status::OutOfBoundary);
869        }
870        self.bare.remove(&seq);
871        self.pending_export.remove(&seq);
872        if key_len == 0 {
873            self.forget_seq(seq);
874            self.prim.seq_free(seq);
875            return Ok(());
876        }
877        let held = if key_len < len && self.prim.seq_truncate(seq, key_len).is_ok() { key_len } else { len };
878        let bytes = self.entry_bytes(held);
879        let now = self.tick_count;
880        let shared = self.forks.remove(&seq);
881        self.cache.insert(seq, tokens[..key_len as usize].to_vec(), 0, bytes, now);
882        if let Some(e) = self.cache.entries.last_mut() {
883            e.shared = shared;
884        }
885        self.settle_cache(&mut Vec::new());
886        Ok(())
887    }
888
889    fn space_export_size(
890        &mut self,
891        seq: SeqHandle,
892        space_id: u32,
893        range: TokenRange,
894        encoding: u8,
895    ) -> Result<(u64, u64), Status> {
896        if space_id != KV_SPACE {
897            return Err(Status::UnknownHandle);
898        }
899        if !self.seq_known(seq) {
900            return Err(Status::UnknownHandle);
901        }
902        if range.start != 0 || range.end < range.start {
903            return Err(Status::Unsupported);
904        }
905        self.size_export(seq, range.end, encoding)
906    }
907
908    fn space_snapshot(
909        &mut self,
910        seq: SeqHandle,
911        space_id: u32,
912        boundary_pos: u64,
913        dst_len: u64,
914        sizing_gen: u64,
915    ) -> Result<OpHandle, Status> {
916        if space_id != KV_SPACE || !self.seq_known(seq) {
917            return Err(Status::UnknownHandle);
918        }
919        if boundary_pos == 0 {
920            return Err(Status::RejectBounds);
921        }
922        if self.prim.seq_boundary(seq, boundary_pos) != boundary_pos {
923            return Err(Status::OutOfBoundary);
924        }
925        self.take_export(seq, boundary_pos, superfluid_abi::encoding::LOSSLESS, dst_len, sizing_gen)
926    }
927
928    fn space_snapshot_boundary(&mut self, seq: SeqHandle, space_id: u32, cap: u64) -> Result<u64, Status> {
929        if space_id != KV_SPACE || !self.seq_known(seq) {
930            return Err(Status::UnknownHandle);
931        }
932        let b = self.prim.seq_boundary(seq, cap);
933        Ok(if self.desc.truncate_partial { b / self.page() * self.page() } else { b })
934    }
935
936    fn space_restore(&mut self, seq: SeqHandle, space_id: u32, src: &[u8]) -> Result<OpHandle, Status> {
937        if space_id != KV_SPACE || !self.bare.contains(&seq) {
938            return Err(Status::UnknownHandle);
939        }
940        if self.prim.seq_len(seq) != 0 || self.adopt_leased.contains(&seq) {
941            return Err(Status::Busy);
942        }
943        let compat = self.compat();
944        let (env, payload) = crate::envelope::open(src, space_kind::PAGED_TOKEN_KV, 1, &compat)?;
945        if env.range.start != 0 || env.range.end == 0 || env.range.end > self.desc.max_seq_len {
946            return Ok(self.complete_op(Err(Status::RejectBounds), 0));
947        }
948        let result = self.prim.seq_import(seq, payload).map_err(prim_status);
949        let bytes = payload.len() as u64;
950        let failed = match result {
951            Ok(len) if len == env.range.end => None,
952            Ok(_) => Some(Status::EnvelopeMismatch),
953            Err(e) => Some(e),
954        };
955        if let Some(err) = failed {
956            let rolled_back = self.prim.seq_truncate(seq, 0).is_ok() && self.prim.seq_len(seq) == 0;
957            let err = if rolled_back { err } else { Status::Fatal };
958            return Ok(self.complete_op(Err(err), 0));
959        }
960        self.bump(seq);
961        self.bare_tokens.remove(&seq);
962        self.seq_taint.remove(&seq);
963        if env.taint_bits != 0 {
964            self.seq_taint.insert(seq, env.taint_bits);
965        }
966        Ok(self.complete_op(Ok(Vec::new()), bytes))
967    }
968
969    fn space_trim(&mut self, seq: SeqHandle, space_id: u32, new_len: u64) -> Result<(), Status> {
970        if space_id != KV_SPACE || !self.seq_known(seq) {
971            return Err(Status::UnknownHandle);
972        }
973        if self.seq_is_lane(seq) || self.seq_is_cached(seq) || self.adopt_leased.contains(&seq) {
974            return Err(Status::Busy);
975        }
976        if new_len > self.prim.seq_len(seq) {
977            return Err(Status::RejectBounds);
978        }
979        self.prim.seq_truncate(seq, new_len).map_err(prim_status)?;
980        self.bump(seq);
981        Ok(())
982    }
983
984    fn space_demote(
985        &mut self,
986        seq: SeqHandle,
987        space_id: u32,
988        range: TokenRange,
989        encoding: u8,
990        dst_len: u64,
991        sizing_gen: u64,
992    ) -> Result<OpHandle, Status> {
993        if space_id != KV_SPACE || !self.seq_known(seq) {
994            return Err(Status::UnknownHandle);
995        }
996        if range.start != 0 {
997            return Err(Status::Unsupported);
998        }
999        self.take_export(seq, range.end, encoding, dst_len, sizing_gen)
1000    }
1001
1002    fn space_promote(
1003        &mut self,
1004        _seq: SeqHandle,
1005        _space_id: u32,
1006        _range: TokenRange,
1007        _src: &[u8],
1008    ) -> Result<OpHandle, Status> {
1009        Err(Status::Unsupported)
1010    }
1011
1012    fn op_poll(&mut self, op: OpHandle) -> Result<OpStatus, Status> {
1013        let o = self.ops.get(&op).ok_or(Status::UnknownHandle)?;
1014        Ok(OpStatus {
1015            struct_size: std::mem::size_of::<OpStatus>() as u64,
1016            state: o.state,
1017            _pad0: [0; 3],
1018            error: o.error,
1019            bytes_moved: o.bytes_moved,
1020        })
1021    }
1022
1023    fn take_op_output(&mut self, op: OpHandle) -> Option<Vec<u8>> {
1024        let o = self.ops.get_mut(&op)?;
1025        if o.state != op_state::DONE {
1026            return None;
1027        }
1028        o.output.take()
1029    }
1030
1031    fn op_cancel(&mut self, op: OpHandle) -> Result<(), Status> {
1032        self.ops.get(&op).map(|_| ()).ok_or(Status::UnknownHandle)
1033    }
1034
1035    fn cache_evict(
1036        &mut self,
1037        evictable_cache_classes: u64,
1038        protected_quota_bytes: u64,
1039        max_evict_bytes: u64,
1040        bytes_target: u64,
1041    ) -> Result<u64, Status> {
1042        if evictable_cache_classes & HOST_EXPORT_CLASS != 0 {
1043            self.drop_exports();
1044        }
1045        if evictable_cache_classes & RESIDENT_PREFIX_CLASS == 0 {
1046            return Ok(0);
1047        }
1048        let cap = max_evict_bytes.min(self.cache.resident_bytes().saturating_sub(protected_quota_bytes));
1049        let want = bytes_target.min(cap);
1050        if want == 0 {
1051            return Ok(0);
1052        }
1053        let (freed, _, _) = self.evict_classes(want, cap, evictable_cache_classes & SHARED_PREFIX_CLASS != 0);
1054        Ok(freed)
1055    }
1056
1057    fn cache_evict_entries(&mut self, keys: &[([u8; 32], [u8; 32])]) -> Result<(), Status> {
1058        let chain = self.compat_identity();
1059        if keys.iter().any(|(c, _)| *c != chain) {
1060            return Err(Status::IdentityMismatch);
1061        }
1062        let digests: Vec<[u8; 32]> = keys.iter().map(|k| k.1).collect();
1063        let gone = self.cache.evict_digests(&digests);
1064        self.free_evicted(gone);
1065        Ok(())
1066    }
1067
1068    fn unpublish(&mut self, tokens: &[u32]) -> Result<(), Status> {
1069        let gone = self.cache.evict_exact(tokens);
1070        self.free_evicted(gone);
1071        Ok(())
1072    }
1073
1074    fn logit_bias_create(&mut self, tokens: &[i32], values: &[f32]) -> Result<u32, Status> {
1075        let bias: Vec<(u32, f32)> = tokens
1076            .iter()
1077            .zip(values.iter())
1078            .filter(|(t, _)| **t >= 0 && (**t as u32) < self.desc.vocab_size)
1079            .map(|(t, v)| (*t as u32, *v))
1080            .collect();
1081        let h = self.next_bias;
1082        self.next_bias += 1;
1083        self.biases.insert(h, bias);
1084        Ok(h)
1085    }
1086
1087    fn logit_bias_free(&mut self, handle: u32) -> Result<(), Status> {
1088        self.biases.remove(&handle).map(|_| ()).ok_or(Status::UnknownHandle)
1089    }
1090
1091    fn grammar_create(&mut self, json_schema: &str) -> Result<u32, Status> {
1092        let prim = &self.prim;
1093        self.grammars.create_schema(|| prim.vocabulary(), json_schema)
1094    }
1095
1096    fn grammar_create_structural(&mut self, tag_json: &str) -> Result<u32, Status> {
1097        let prim = &self.prim;
1098        self.grammars.create_structural(|| prim.vocabulary(), tag_json)
1099    }
1100
1101    fn grammar_free(&mut self, handle: u32) -> Result<(), Status> {
1102        self.grammars.free(handle)
1103    }
1104
1105    fn capability_descriptor(&self) -> Option<String> {
1106        let d = self
1107            .capabilities
1108            .get_or_init(|| crate::capabilities::descriptor(&self.desc, self.prim.vocabulary().is_some()));
1109        Some(d.clone())
1110    }
1111}
1112
1113impl<P: RuntimePrimitives> superfluid_engine::testing::EngineInspect for Executor<P> {
1114    fn lane_committed(&self, lane_tag: u64) -> Option<&[u32]> {
1115        Executor::lane_committed(self, lane_tag)
1116    }
1117    fn inject_fault(&mut self, lane_tag: u64, code: u32) {
1118        Executor::inject_fault(self, lane_tag, code)
1119    }
1120}