Skip to main content

sva_engine/cache/
memory.rs

1// Concern: the memory tier: every resident value and node under one cap, what it evicts and writes back, the misses it keeps | Non-concern: the disk | IO: (key) -> held, answered; offers -> kept
2
3use std::collections::HashMap;
4use std::sync::{Arc, Mutex, MutexGuard};
5
6use sva_formula::Hash;
7use sva_samples::{Buffer, Extent, Label};
8
9use super::stored::{Header, Samples};
10use super::{Entry, Expected, Payload, Stored, joined};
11
12pub const DEFAULT_CACHE_BYTES: u64 = 2 << 30;
13
14/// Samples between two states a run keeps, so a reader resumes from one at most this far back.
15pub const DEFAULT_MARK_EVERY: usize = 16_384;
16
17/// Which computed values memory keeps besides those a render offers as nodes; the rest are
18/// computed again when asked.
19#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20pub enum CachePolicy {
21    #[default]
22    All,
23    /// The values two or more nodes read, and the render target.
24    Forks,
25    Target,
26}
27
28impl CachePolicy {
29    pub const ALL: [CachePolicy; 3] = [CachePolicy::All, CachePolicy::Forks, CachePolicy::Target];
30
31    pub fn name(self) -> &'static str {
32        match self {
33            CachePolicy::All => "all",
34            CachePolicy::Forks => "forks",
35            CachePolicy::Target => "target",
36        }
37    }
38
39    pub fn named(name: &str) -> Option<CachePolicy> {
40        CachePolicy::ALL.into_iter().find(|p| p.name() == name)
41    }
42
43    fn keeps(self, fork: bool, target: bool) -> bool {
44        match self {
45            CachePolicy::All => true,
46            CachePolicy::Forks => fork || target,
47            CachePolicy::Target => target,
48        }
49    }
50}
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
53pub enum PrunePolicy {
54    /// Every entry the newest render neither stored nor read.
55    #[default]
56    Oldest,
57    /// Every entry whose node fewer than two nodes read.
58    Forks,
59}
60
61impl PrunePolicy {
62    pub const ALL: [PrunePolicy; 2] = [PrunePolicy::Oldest, PrunePolicy::Forks];
63
64    pub fn name(self) -> &'static str {
65        match self {
66            PrunePolicy::Oldest => "oldest",
67            PrunePolicy::Forks => "forks",
68        }
69    }
70
71    pub fn named(name: &str) -> Option<PrunePolicy> {
72        PrunePolicy::ALL.into_iter().find(|p| p.name() == name)
73    }
74}
75
76/// What passed between memory and the disk beneath it, and what memory let go.
77#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
78pub struct Counters {
79    pub disk_lookups: u64,
80    pub disk_reads: u64,
81    pub disk_read_bytes: u64,
82    /// Disk answers made resident: a header looked up, or samples read.
83    pub promotions: u64,
84    pub writebacks: u64,
85    /// Every entry's hits, summed: each answer of a node or a value memory held.
86    pub hits: u64,
87    pub probation_evictions: u64,
88    pub protected_evictions: u64,
89}
90
91impl Counters {
92    pub fn since(self, then: Counters) -> Counters {
93        Counters {
94            disk_lookups: self.disk_lookups - then.disk_lookups,
95            disk_reads: self.disk_reads - then.disk_reads,
96            disk_read_bytes: self.disk_read_bytes - then.disk_read_bytes,
97            promotions: self.promotions - then.promotions,
98            writebacks: self.writebacks - then.writebacks,
99            hits: self.hits - then.hits,
100            probation_evictions: self.probation_evictions - then.probation_evictions,
101            protected_evictions: self.protected_evictions - then.protected_evictions,
102        }
103    }
104
105    pub fn evictions(self) -> u64 {
106        self.probation_evictions + self.protected_evictions
107    }
108}
109
110/// A node cheaper than a priced flop per this many bytes it holds is computed, never read back.
111pub const BYTES_PER_FLOP: u128 = 64;
112
113/// What a render states of a node it offers.
114#[derive(Clone, Copy, Debug)]
115pub(crate) struct Facts {
116    pub(crate) slot: Option<Hash>,
117    pub(crate) settled: bool,
118    pub(crate) target: bool,
119    pub(crate) samples: u64,
120}
121
122/// The protected segment holds at most this share of the cap, so probation always has a
123/// quarter for a new entry to earn its first hit in.
124const PROTECTED: (u64, u64) = (3, 4);
125
126#[derive(Clone, Copy, Debug)]
127pub(crate) struct Stamp {
128    pub tree: u64,
129    pub fork: bool,
130    /// A volatile node's value replaces the last one stored under the same slot.
131    pub slot: Option<Hash>,
132}
133
134#[derive(Clone, Copy, Debug, PartialEq, Eq)]
135pub(crate) enum Kept {
136    Held,
137    Replaced,
138    Refused,
139}
140
141/// What memory answers a node's key with: a miss holds for the round it was met in and later
142/// ones begun before it, so another holder's commit is found once a new round begins.
143pub(crate) enum Known {
144    Hit(Arc<Stored>),
145    Miss,
146    Unknown,
147}
148
149/// Where an offered node's sample `n` is: sample `n + by` of the values memory holds, a value's
150/// segments or a run's, each under its key from its start on, or of another node.
151#[derive(Clone, Debug, PartialEq)]
152pub(crate) enum Offered {
153    Values {
154        keys: Vec<(i64, Hash)>,
155        by: i64,
156    },
157    Moves {
158        of: Hash,
159        by: i64,
160    },
161    /// Samples no value holds, the node's own.
162    Held(Vec<Arc<Buffer>>),
163}
164
165pub(crate) struct Writeback {
166    pub(crate) key: Hash,
167    pub(crate) head: Header,
168    pub(crate) parts: Vec<Arc<Buffer>>,
169}
170
171enum Item {
172    Value {
173        payload: Payload,
174        label: Option<Label>,
175        slot: Option<Hash>,
176    },
177    Node {
178        source: Source,
179        slot: Option<Hash>,
180        /// Holds what the disk may lack: a node a render still computes always does.
181        dirty: bool,
182        settled: bool,
183        bound: bool,
184    },
185}
186
187enum Source {
188    Disk {
189        head: Box<Header>,
190        chunks: Vec<Arc<Buffer>>,
191    },
192    Offered {
193        stored: Box<Stored>,
194        offered: Offered,
195    },
196}
197
198impl Source {
199    fn stored(&self) -> &Stored {
200        match self {
201            Source::Disk { head, .. } => head.stored(),
202            Source::Offered { stored, .. } => stored,
203        }
204    }
205}
206
207struct Held {
208    item: Item,
209    read: u64,
210    since: u64,
211    hit_round: u64,
212    protected: bool,
213    tree: u64,
214    fork: bool,
215}
216
217impl Held {
218    fn admitted(item: Item, at: u64, tree: u64, fork: bool) -> Held {
219        Held {
220            item,
221            read: at,
222            since: at,
223            hit_round: 0,
224            protected: false,
225            tree,
226            fork,
227        }
228    }
229}
230
231fn planes(b: &Buffer) -> u64 {
232    (b.len() * b.width * size_of::<f64>()) as u64
233}
234
235impl Held {
236    fn bytes(&self) -> u64 {
237        match &self.item {
238            Item::Value { payload, .. } => payload.bytes() as u64,
239            Item::Node {
240                source:
241                    Source::Disk { chunks, .. }
242                    | Source::Offered {
243                        offered: Offered::Held(chunks),
244                        ..
245                    },
246                ..
247            } => chunks.iter().map(|c| planes(c)).sum(),
248            Item::Node { .. } => 0,
249        }
250    }
251
252    fn slot(&self) -> Option<Hash> {
253        match &self.item {
254            Item::Value { slot, .. } | Item::Node { slot, .. } => *slot,
255        }
256    }
257}
258
259#[derive(Default)]
260struct State {
261    entries: HashMap<Hash, Held>,
262    slots: HashMap<Hash, Hash>,
263    misses: HashMap<Hash, u64>,
264    pending: Vec<Writeback>,
265    bytes: u64,
266    max_bytes: u64,
267    policy: CachePolicy,
268    clock: u64,
269    tree: u64,
270    round: u64,
271    mark_every: usize,
272    counters: Counters,
273    disk: bool,
274    /// Writes that failed, and why the latest did: none is tried again until a persist.
275    failed: (u64, Option<String>),
276}
277
278impl State {
279    fn tick(&mut self) -> u64 {
280        self.clock += 1;
281        self.clock
282    }
283
284    fn stands(&self, key: Hash) -> bool {
285        let bound = |at: &Hash| {
286            let held = self.entries.get(at).map(|held| &held.item);
287            matches!(held, Some(Item::Node { bound: true, .. }))
288        };
289        self.doomed(key).iter().skip(1).any(bound)
290    }
291
292    fn unsettled(&self, key: Hash) -> bool {
293        matches!(
294            self.entries.get(&key),
295            Some(Held {
296                item: Item::Node { settled: false, .. },
297                ..
298            })
299        )
300    }
301
302    /// The values and nodes reading `key`'s samples, and theirs, `key` first.
303    fn doomed(&self, key: Hash) -> Vec<Hash> {
304        let mut out = vec![key];
305        let mut k = 0;
306        while k < out.len() {
307            let gone = out[k];
308            for (at, held) in &self.entries {
309                let Item::Node {
310                    source: Source::Offered { offered, .. },
311                    ..
312                } = &held.item
313                else {
314                    continue;
315                };
316                let reads = match offered {
317                    Offered::Values { keys, .. } => keys.iter().any(|(_, key)| *key == gone),
318                    Offered::Moves { of, .. } => *of == gone,
319                    Offered::Held(_) => false,
320                };
321                if reads && !out.contains(at) {
322                    out.push(*at);
323                }
324            }
325            k += 1;
326        }
327        out
328    }
329
330    /// `key` and everything reading its samples gone, each dirty node flushed first; a node a
331    /// render still computes stays, to send the rest.
332    fn remove(&mut self, key: Hash) -> bool {
333        if !self.entries.contains_key(&key) {
334            return false;
335        }
336        let doomed = self.doomed(key);
337        for at in &doomed {
338            self.flush(*at);
339        }
340        for at in doomed {
341            if at == key || !self.unsettled(at) {
342                self.discard(at);
343            }
344        }
345        true
346    }
347
348    fn discard(&mut self, key: Hash) {
349        let Some(gone) = self.entries.remove(&key) else {
350            return;
351        };
352        self.bytes -= gone.bytes();
353        let value = matches!(gone.item, Item::Value { .. });
354        if let Some(slot) = gone.slot().filter(|_| value)
355            && self.slots.get(&slot) == Some(&key)
356        {
357            self.slots.remove(&slot);
358        }
359    }
360
361    /// A dirty node's header and what memory holds of it, on their way to the disk; a node a
362    /// render still computes stays dirty, for the samples it has yet to send.
363    fn flush(&mut self, key: Hash) {
364        let Some(Held {
365            item:
366                Item::Node {
367                    dirty: dirty @ true,
368                    settled,
369                    ..
370                },
371            ..
372        }) = self.entries.get_mut(&key)
373        else {
374            return;
375        };
376        *dirty = !*settled;
377        if let Some(back) = self.written(key) {
378            self.pending.push(back);
379        }
380    }
381
382    fn written(&self, key: Hash) -> Option<Writeback> {
383        let Item::Node {
384            source: Source::Offered { stored, offered },
385            ..
386        } = &self.entries.get(&key)?.item
387        else {
388            return None;
389        };
390        let head = |samples| Header::new((**stored).clone(), samples);
391        let referred = match offered {
392            Offered::Moves { of, by } => match self.referred(*of, *by) {
393                Some(found) => Some(found),
394                None if self.unbound(*of, 0) => None,
395                None => return None,
396            },
397            _ => None,
398        };
399        Some(match referred {
400            Some((of, by)) => Writeback {
401                key,
402                head: head(Samples::Of { key: of, by }),
403                parts: Vec::new(),
404            },
405            None => Writeback {
406                key,
407                head: head(Samples::None),
408                parts: self
409                    .offered_parts(offered, Extent::EVERYWHERE)
410                    .unwrap_or_default(),
411            },
412        })
413    }
414
415    fn unbound(&self, key: Hash, depth: usize) -> bool {
416        match self.entries.get(&key).map(|held| &held.item) {
417            Some(Item::Node { bound: false, .. }) => true,
418            Some(Item::Node {
419                source:
420                    Source::Offered {
421                        offered: Offered::Moves { of, .. },
422                        ..
423                    },
424                ..
425            }) if depth < 64 => self.unbound(*of, depth + 1),
426            _ => false,
427        }
428    }
429
430    fn referred(&self, of: Hash, by: i64) -> Option<(Hash, i64)> {
431        match &self.entries.get(&of)?.item {
432            Item::Node {
433                source: Source::Disk { head, .. },
434                ..
435            } => match head.samples() {
436                Samples::Entry { file, shift, .. } => Some((*file, by - shift)),
437                _ => None,
438            },
439            Item::Node { bound: false, .. } => None,
440            Item::Node {
441                source:
442                    Source::Offered {
443                        offered: Offered::Moves { of, by: more },
444                        ..
445                    },
446                ..
447            } => self.referred(*of, by + more),
448            Item::Node { .. } => Some((of, by)),
449            Item::Value { .. } => None,
450        }
451    }
452
453    /// Each part the values under `keys` hold, with the stretch of it that is the node's.
454    fn shares(&self, keys: &[(i64, Hash)]) -> Option<Vec<(Arc<Buffer>, Extent)>> {
455        let mut out = Vec::new();
456        for (k, (start, key)) in keys.iter().enumerate() {
457            let end = keys.get(k + 1).map_or(i64::MAX, |(next, _)| *next);
458            let within = Extent::new(*start, end);
459            let parts = match &self.entries.get(key).map(|held| &held.item) {
460                Some(Item::Value {
461                    payload: Payload::Segments(parts),
462                    ..
463                }) => parts.clone(),
464                Some(Item::Value {
465                    payload: Payload::Run(run),
466                    ..
467                }) => vec![Arc::clone(&run.samples)],
468                _ => Vec::new(),
469            };
470            let met = |part: Arc<Buffer>| {
471                let met = part.extent().intersect(within);
472                (!met.is_empty()).then_some((part, met))
473            };
474            out.extend(parts.into_iter().filter_map(met));
475        }
476        let held = keys.iter().any(|(_, key)| self.entries.contains_key(key));
477        held.then_some(out)
478    }
479
480    fn offered_parts(&self, offered: &Offered, over: Extent) -> Option<Vec<Arc<Buffer>>> {
481        let (parts, by): (Vec<Arc<Buffer>>, i64) = match offered {
482            Offered::Values { keys, by } => {
483                let within = over.shifted(*by);
484                let met = self.shares(keys)?.into_iter();
485                let met = met.filter(|(_, held)| !held.intersect(within).is_empty());
486                (met.map(|(part, held)| clipped(&part, held)).collect(), *by)
487            }
488            Offered::Moves { of, by } => (self.resident(*of, over.shifted(*by))?.0, *by),
489            Offered::Held(parts) => (parts.clone(), 0),
490        };
491        let over = over.shifted(by);
492        let meets = |b: &&Arc<Buffer>| !b.extent().intersect(over).is_empty();
493        Some(parts.iter().filter(meets).map(|p| moved(p, -by)).collect())
494    }
495
496    /// What memory holds of `key` meeting `over`, and for a node off the disk, what of `over`
497    /// the disk holds that memory lacks, with its layout.
498    fn resident(&self, key: Hash, over: Extent) -> Option<Resident> {
499        let Item::Node { source, .. } = &self.entries.get(&key)?.item else {
500            return None;
501        };
502        match source {
503            Source::Offered { offered, .. } => Some((self.offered_parts(offered, over)?, None)),
504            Source::Disk { head, chunks } => {
505                let meets = |b: &&Arc<Buffer>| !b.extent().intersect(over).is_empty();
506                let parts: Vec<Arc<Buffer>> = chunks.iter().filter(meets).cloned().collect();
507                let mut lacks = Vec::new();
508                for held in head.stored().extents() {
509                    let mut asked = vec![held.intersect(over)];
510                    for part in chunks {
511                        asked = asked
512                            .into_iter()
513                            .flat_map(|a| minus(a, part.extent()))
514                            .collect();
515                    }
516                    lacks.extend(asked.into_iter().filter(|a| !a.is_empty()));
517                }
518                let hull = lacks.iter().fold(Extent::NOWHERE, |h, e| h.hull(*e));
519                Some((parts, (!hull.is_empty()).then(|| ((**head).clone(), hull))))
520            }
521        }
522    }
523
524    fn coverage(&self, key: Hash, depth: usize) -> Option<Vec<Extent>> {
525        let Item::Node { source, .. } = &self.entries.get(&key)?.item else {
526            return None;
527        };
528        let offered = match source {
529            Source::Disk { head, .. } => return Some(head.stored().extents().to_vec()),
530            Source::Offered { offered, .. } => offered,
531        };
532        match offered {
533            Offered::Moves { of, by } if depth < 64 => {
534                let held = self.coverage(*of, depth + 1)?;
535                Some(held.into_iter().map(|e| e.shifted(-by)).collect())
536            }
537            Offered::Moves { .. } => None,
538            Offered::Values { keys, by } => {
539                let shares = self.shares(keys)?.into_iter();
540                Some(shares.map(|(_, held)| held.shifted(-by)).collect())
541            }
542            Offered::Held(parts) => Some(parts.iter().map(|p| p.extent()).collect()),
543        }
544    }
545
546    fn evict(&mut self, key: Hash) {
547        let Some(held) = self.entries.get_mut(&key) else {
548            return;
549        };
550        let protected = held.protected;
551        let gone = match &mut held.item {
552            Item::Node {
553                source: Source::Disk { chunks, .. },
554                ..
555            } => {
556                let freed: u64 = std::mem::take(chunks).iter().map(|c| planes(c)).sum();
557                self.bytes -= freed;
558                true
559            }
560            _ => self.remove(key),
561        };
562        match (gone, protected) {
563            (false, _) => {}
564            (true, false) => self.counters.probation_evictions += 1,
565            (true, true) => self.counters.protected_evictions += 1,
566        }
567    }
568
569    fn prune(&mut self, policy: PrunePolicy) {
570        let newest = self.tree;
571        let mut named: Vec<(u64, Hash)> = self
572            .entries
573            .iter()
574            .filter(|(_, held)| match policy {
575                PrunePolicy::Oldest => held.tree != newest,
576                PrunePolicy::Forks => !held.fork,
577            })
578            .map(|(key, held)| (held.read, *key))
579            .collect();
580        named.sort_unstable();
581        for (_, key) in named {
582            self.evict(key);
583        }
584        self.bounded();
585    }
586
587    /// A hit on `key` and on every entry whose samples it answers with: each moves to the
588    /// protected segment, whose least recent move back to probation past its share.
589    fn hit(&mut self, key: Hash, round: Option<u64>) {
590        let tick = self.tick();
591        let mut promoted = false;
592        for at in self.under(key) {
593            let Some(held) = self.entries.get_mut(&at) else {
594                continue;
595            };
596            held.read = tick;
597            if round.is_some_and(|round| held.hit_round == round) {
598                continue;
599            }
600            held.hit_round = round.unwrap_or(0);
601            promoted |= !std::mem::replace(&mut held.protected, true);
602            self.counters.hits += 1;
603        }
604        if promoted {
605            self.shared();
606        }
607    }
608
609    fn under(&self, key: Hash) -> Vec<Hash> {
610        let mut out = vec![key];
611        let mut k = 0;
612        while k < out.len() {
613            if let Some(Item::Node {
614                source: Source::Offered { offered, .. },
615                ..
616            }) = self.entries.get(&out[k]).map(|held| &held.item)
617            {
618                let more: Vec<Hash> = match offered {
619                    Offered::Values { keys, .. } => keys.iter().map(|(_, key)| *key).collect(),
620                    Offered::Moves { of, .. } => vec![*of],
621                    Offered::Held(_) => Vec::new(),
622                };
623                for at in more {
624                    if !out.contains(&at) {
625                        out.push(at);
626                    }
627                }
628            }
629            k += 1;
630        }
631        out
632    }
633
634    /// Protected within its share: its least recent move back to probation, as its newest.
635    fn shared(&mut self) {
636        let most =
637            (u128::from(self.max_bytes) * u128::from(PROTECTED.0) / u128::from(PROTECTED.1)) as u64;
638        let mut protected: Vec<(u64, Hash, u64)> = self
639            .entries
640            .iter()
641            .filter(|(_, held)| held.protected)
642            .map(|(key, held)| (held.read, *key, held.bytes()))
643            .collect();
644        let mut bytes: u64 = protected.iter().map(|(_, _, b)| b).sum();
645        protected.sort_unstable();
646        for (_, key, held) in protected {
647            if bytes <= most {
648                break;
649            }
650            let tick = self.tick();
651            if let Some(entry) = self.entries.get_mut(&key) {
652                entry.protected = false;
653                entry.since = tick;
654                bytes -= held;
655            }
656        }
657    }
658
659    /// Under the cap: an entry too large for it goes first, then probation oldest first,
660    /// then protected least recently read.
661    fn bounded(&mut self) {
662        if self.bytes <= self.max_bytes {
663            return;
664        }
665        self.shared();
666        let max = self.max_bytes;
667        let mut order: Vec<(u8, u64, Hash)> = self
668            .entries
669            .iter()
670            .filter(|(_, held)| held.bytes() > 0)
671            .map(|(key, held)| match (held.bytes() > max, held.protected) {
672                (true, _) => (0, held.since, *key),
673                (false, false) => (1, held.since, *key),
674                (false, true) => (2, held.read, *key),
675            })
676            .collect();
677        order.sort_unstable();
678        for (_, _, key) in order {
679            if self.bytes <= self.max_bytes {
680                break;
681            }
682            self.evict(key);
683        }
684    }
685}
686
687type Resident = (Vec<Arc<Buffer>>, Option<(Header, Extent)>);
688
689fn minus(e: Extent, cut: Extent) -> Vec<Extent> {
690    let met = e.intersect(cut);
691    if met.is_empty() {
692        return vec![e];
693    }
694    vec![Extent::new(e.start, met.start), Extent::new(met.end, e.end)]
695}
696
697fn clipped(part: &Arc<Buffer>, met: Extent) -> Arc<Buffer> {
698    let held = part.extent();
699    match met == held {
700        true => Arc::clone(part),
701        false => Arc::new(part.over(met, held)),
702    }
703}
704
705fn moved(part: &Arc<Buffer>, by: i64) -> Arc<Buffer> {
706    if by == 0 {
707        return Arc::clone(part);
708    }
709    let mut out = (**part).clone();
710    out.start += by;
711    Arc::new(out)
712}
713
714/// The memory tier: the one owner of every value and node held resident, and of what memory
715/// knows a disk beneath it holds or lacks. A clone is a handle on the same memory.
716#[derive(Clone)]
717pub(crate) struct Memory {
718    state: Arc<Mutex<State>>,
719}
720
721impl Default for Memory {
722    fn default() -> Memory {
723        Memory::holding(DEFAULT_CACHE_BYTES)
724    }
725}
726
727impl Memory {
728    pub(crate) fn holding(max_bytes: u64) -> Memory {
729        Memory {
730            state: Arc::new(Mutex::new(State {
731                max_bytes,
732                mark_every: DEFAULT_MARK_EVERY,
733                ..State::default()
734            })),
735        }
736    }
737
738    /// Over a disk: each node a render offers is written back before it goes.
739    pub(crate) fn over_disk(max_bytes: u64) -> Memory {
740        let memory = Memory::holding(max_bytes);
741        memory.locked().disk = true;
742        memory
743    }
744
745    fn locked(&self) -> MutexGuard<'_, State> {
746        self.state.lock().unwrap_or_else(|poisoned| {
747            let mut state = poisoned.into_inner();
748            state.entries.clear();
749            state.slots.clear();
750            state.bytes = 0;
751            self.state.clear_poison();
752            state
753        })
754    }
755
756    pub(crate) fn max_bytes(&self) -> u64 {
757        self.locked().max_bytes
758    }
759
760    pub(crate) fn set_max_bytes(&self, max_bytes: u64) {
761        let mut state = self.locked();
762        state.max_bytes = max_bytes;
763        state.bounded();
764    }
765
766    pub(crate) fn policy(&self) -> CachePolicy {
767        self.locked().policy
768    }
769
770    pub(crate) fn set_policy(&self, policy: CachePolicy) {
771        self.locked().policy = policy;
772    }
773
774    pub(crate) fn prune(&self, policy: PrunePolicy) {
775        self.locked().prune(policy);
776    }
777
778    /// Everything gone, each node not yet on the disk sent there first.
779    pub(crate) fn clear(&self) {
780        let mut state = self.locked();
781        let keys: Vec<Hash> = state.entries.keys().copied().collect();
782        for key in &keys {
783            state.flush(*key);
784        }
785        for key in keys {
786            state.discard(key);
787        }
788        state.misses.clear();
789    }
790
791    pub(crate) fn bytes(&self) -> u64 {
792        self.locked().bytes
793    }
794
795    pub(crate) fn entries(&self) -> usize {
796        self.locked().entries.len()
797    }
798
799    pub(crate) fn holds(&self, key: Hash) -> bool {
800        self.locked().entries.contains_key(&key)
801    }
802
803    pub(crate) fn counters(&self) -> Counters {
804        self.locked().counters
805    }
806
807    pub(crate) fn count(&self, by: impl FnOnce(&mut Counters)) {
808        by(&mut self.locked().counters);
809    }
810
811    pub(crate) fn mark_every(&self) -> usize {
812        self.locked().mark_every
813    }
814
815    pub(crate) fn set_mark_every(&self, samples: usize) {
816        self.locked().mark_every = samples.max(1);
817    }
818
819    /// Whether memory takes a value computed: as its policy says, and one offered as a node
820    /// whatever it says while a disk lies beneath, to write it there; a memory of no bytes
821    /// takes none.
822    pub(crate) fn keeps(&self, fork: bool, target: bool, offered: bool) -> bool {
823        let state = self.locked();
824        state.max_bytes > 0 && ((offered && state.disk) || state.policy.keeps(fork, target))
825    }
826
827    pub(crate) fn begin_tree(&self) -> u64 {
828        let mut state = self.locked();
829        state.tree += 1;
830        state.tree
831    }
832
833    /// A new round of lookups: what any earlier round missed is asked again.
834    pub(crate) fn begin(&self) -> u64 {
835        let mut state = self.locked();
836        state.round += 1;
837        let round = state.round;
838        state.misses.retain(|_, met| *met + 1 >= round);
839        round
840    }
841
842    pub(crate) fn answer(&self, key: Hash, round: u64) -> Known {
843        let mut state = self.locked();
844        let covered = state.coverage(key, 0);
845        if let Some(held) = covered.clone().filter(|held| !held.is_empty()) {
846            state.hit(key, Some(round));
847            let Some(Item::Node { source, .. }) = state.entries.get(&key).map(|held| &held.item)
848            else {
849                unreachable!("only a node covers");
850            };
851            return Known::Hit(Arc::new(source.stored().holding(held)));
852        }
853        let node = matches!(
854            state.entries.get(&key),
855            Some(Held {
856                item: Item::Node { .. },
857                ..
858            })
859        );
860        match (node, covered) {
861            (true, None) => {
862                state.remove(key);
863            }
864            (true, Some(_)) => return Known::Miss,
865            (false, _) => {}
866        }
867        match state.misses.get(&key) {
868            Some(met) if *met >= round => Known::Miss,
869            _ => Known::Unknown,
870        }
871    }
872
873    pub(crate) fn miss(&self, key: Hash, round: u64) {
874        let mut state = self.locked();
875        let met = state.misses.entry(key).or_insert(round);
876        *met = (*met).max(round);
877    }
878
879    /// A header the disk answered, resident from now on, unless memory took the node meanwhile.
880    pub(crate) fn promote(&self, head: Header) {
881        let mut state = self.locked();
882        let (key, read, tree) = (head.stored().key, state.tick(), state.tree);
883        if state.entries.contains_key(&key) {
884            return;
885        }
886        state.misses.remove(&key);
887        state.counters.promotions += 1;
888        let item = Item::Node {
889            source: Source::Disk {
890                head: Box::new(head),
891                chunks: Vec::new(),
892            },
893            slot: None,
894            dirty: false,
895            settled: true,
896            bound: true,
897        };
898        state
899            .entries
900            .insert(key, Held::admitted(item, read, tree, false));
901    }
902
903    pub(crate) fn promote_samples(&self, key: Hash, read: Vec<Buffer>) -> Vec<Arc<Buffer>> {
904        let read: Vec<Arc<Buffer>> = read.into_iter().map(Arc::new).collect();
905        let mut state = self.locked();
906        let tick = state.tick();
907        let Some(held) = state.entries.get_mut(&key) else {
908            return read;
909        };
910        let Item::Node {
911            source: Source::Disk { chunks, .. },
912            ..
913        } = &mut held.item
914        else {
915            return read;
916        };
917        let mut added = 0;
918        for part in &read {
919            let covered = chunks
920                .iter()
921                .any(|c| c.extent().intersect(part.extent()) == part.extent());
922            if !covered {
923                added += planes(part);
924                chunks.push(Arc::clone(part));
925            }
926        }
927        chunks.sort_by_key(|c| c.start);
928        held.read = tick;
929        state.bytes += added;
930        state.counters.promotions += 1;
931        state.bounded();
932        read
933    }
934
935    /// What memory holds of `key` over `over`, and what it lacks there that the disk holds.
936    pub(crate) fn resident(&self, key: Hash, over: Extent) -> Resident {
937        let mut state = self.locked();
938        let tick = state.tick();
939        if let Some(held) = state.entries.get_mut(&key) {
940            held.read = tick;
941        }
942        state.resident(key, over).unwrap_or_default()
943    }
944
945    pub(crate) fn forget(&self, key: Hash) {
946        self.locked().remove(key);
947    }
948
949    /// A node a render computes, standing on samples memory holds, in place of the last one
950    /// offered under `slot` or `key`, keeping the segment that one earned. Until `settled` it
951    /// holds what is computed so far, and each eviction sends that much to the disk; settled,
952    /// it goes where memory holds none of its samples and will write none to the disk.
953    pub(crate) fn offer(&self, stored: Stored, offered: Offered, facts: Facts) {
954        let mut state = self.locked();
955        let Facts { slot, settled, .. } = facts;
956        let bound = state.disk && writes(&stored, facts);
957        let key = stored.key;
958        let (read, tree) = (state.tick(), state.tree);
959        let earned = state.entries.get(&key);
960        let (since, protected) = earned.map_or((read, false), |held| (held.since, held.protected));
961        state.discard(key);
962        let slot = slot.map(|slot| super::mixed(slot, &[0x6e_6f_64_65]));
963        let item = Item::Node {
964            source: Source::Offered {
965                stored: Box::new(stored),
966                offered,
967            },
968            slot,
969            dirty: bound,
970            settled,
971            bound,
972        };
973        let held = Held {
974            since,
975            protected,
976            ..Held::admitted(item, read, tree, false)
977        };
978        state.bytes += held.bytes();
979        state.entries.insert(key, held);
980        state.misses.remove(&key);
981        let covered = state.coverage(key, 0).is_some_and(|held| !held.is_empty());
982        if settled && !covered && !bound {
983            state.discard(key);
984            return;
985        }
986        if let Some(last) = slot.and_then(|slot| state.slots.insert(slot, key))
987            && last != key
988        {
989            state.remove(last);
990        }
991        state.bounded();
992    }
993
994    /// Every node not yet on the disk, on its way there.
995    pub(crate) fn flush(&self) {
996        let mut state = self.locked();
997        let keys: Vec<Hash> = state.entries.keys().copied().collect();
998        for key in keys {
999            state.flush(key);
1000        }
1001        state.failed.1 = None;
1002    }
1003
1004    /// The nodes on their way to the disk; none while a write that failed waits for a persist.
1005    pub(crate) fn pending(&self) -> Vec<Writeback> {
1006        let mut state = self.locked();
1007        match state.failed.1 {
1008            Some(_) => Vec::new(),
1009            None => std::mem::take(&mut state.pending),
1010        }
1011    }
1012
1013    /// `left` still on its way, after a write failed for `why`.
1014    pub(crate) fn failed(&self, why: String, left: Vec<Writeback>) {
1015        let mut state = self.locked();
1016        state.failed.0 += 1;
1017        state.failed.1 = Some(why);
1018        let mut more = std::mem::take(&mut state.pending);
1019        state.pending = left;
1020        state.pending.append(&mut more);
1021    }
1022
1023    pub(crate) fn written(&self) {
1024        self.locked().counters.writebacks += 1;
1025    }
1026
1027    pub(crate) fn failures(&self) -> (u64, Option<String>) {
1028        self.locked().failed.clone()
1029    }
1030
1031    pub(crate) fn blocked(&self) -> bool {
1032        self.locked().failed.1.is_some()
1033    }
1034
1035    /// The disk committed what was staged: a header that named staged samples names nothing
1036    /// now.
1037    pub(crate) fn committed(&self) {
1038        let mut state = self.locked();
1039        let staged: Vec<Hash> = state
1040            .entries
1041            .iter()
1042            .filter(|(_, held)| {
1043                let Item::Node {
1044                    source: Source::Disk { head, .. },
1045                    ..
1046                } = &held.item
1047                else {
1048                    return false;
1049                };
1050                matches!(head.samples(), Samples::Staged { .. })
1051            })
1052            .map(|(key, _)| *key)
1053            .collect();
1054        for key in staged {
1055            state.discard(key);
1056        }
1057    }
1058
1059    /// What `key` holds, shared, never copied: a hit.
1060    pub(crate) fn load(&self, key: Hash, expected: Expected, stamp: Stamp) -> Option<Entry> {
1061        let mut state = self.locked();
1062        let held = state.entries.get_mut(&key)?;
1063        let Item::Value { payload, label, .. } = &held.item else {
1064            return None;
1065        };
1066        if !payload.answers(expected) {
1067            state.remove(key);
1068            return None;
1069        }
1070        let entry = Entry {
1071            payload: payload.clone(),
1072            label: label.clone(),
1073        };
1074        held.tree = stamp.tree;
1075        held.fork = stamp.fork;
1076        state.hit(key, None);
1077        Some(entry)
1078    }
1079
1080    /// A value's segments join those held under `key`, and a run continuing the one held
1081    /// there extends it, each in place; anything else replaces what `key` held.
1082    pub(crate) fn merge(
1083        &self,
1084        key: Hash,
1085        payload: Payload,
1086        label: Option<&Label>,
1087        stamp: Stamp,
1088    ) -> Kept {
1089        let mut state = self.locked();
1090        let tick = state.tick();
1091        let joined = match (state.entries.get_mut(&key), payload) {
1092            (Some(held), payload) if held.slot() == stamp.slot => {
1093                let before = held.bytes();
1094                let Item::Value { payload: had, .. } = &mut held.item else {
1095                    unreachable!("a value key holds a value");
1096                };
1097                let payload = match (had, payload) {
1098                    (Payload::Segments(parts), Payload::Segments(more)) => {
1099                        joined(parts, more);
1100                        None
1101                    }
1102                    (Payload::Run(run), Payload::Run(more)) if overlaps(run, &more) => {
1103                        let from = (run.end() - more.samples.start).max(0) as usize;
1104                        let run = Arc::make_mut(run);
1105                        let samples = Arc::make_mut(&mut run.samples);
1106                        for (held, more) in samples.planes.iter_mut().zip(&more.samples.planes) {
1107                            held.extend_from_slice(&more[from.min(more.len())..]);
1108                        }
1109                        run.marks
1110                            .extend(more.marks.iter().map(|(at, m)| (*at, m.clone())));
1111                        None
1112                    }
1113                    (_, payload) => Some(payload),
1114                };
1115                match payload {
1116                    None => {
1117                        held.read = tick;
1118                        held.tree = stamp.tree;
1119                        held.fork = stamp.fork;
1120                        let after = held.bytes();
1121                        Ok((before, after))
1122                    }
1123                    Some(payload) => Err(payload),
1124                }
1125            }
1126            (_, payload) => Err(payload),
1127        };
1128        match joined {
1129            Ok((before, after)) => {
1130                state.bytes = state.bytes - before + after;
1131                state.bounded();
1132                Kept::Held
1133            }
1134            Err(payload) => {
1135                drop(state);
1136                self.store(key, payload, label, stamp)
1137            }
1138        }
1139    }
1140
1141    /// A value too large to stay is refused, unless a node over the disk stands on it: then
1142    /// it is kept only to be evicted, and so written back.
1143    pub(crate) fn store(
1144        &self,
1145        key: Hash,
1146        payload: Payload,
1147        label: Option<&Label>,
1148        stamp: Stamp,
1149    ) -> Kept {
1150        let mut state = self.locked();
1151        let bytes = payload.bytes() as u64;
1152        if bytes > state.max_bytes && !state.stands(key) {
1153            return Kept::Refused;
1154        }
1155        let read = state.tick();
1156        let replaced = match stamp.slot.and_then(|slot| state.slots.insert(slot, key)) {
1157            Some(last) if last != key => state.remove(last),
1158            _ => false,
1159        };
1160        let item = Item::Value {
1161            payload,
1162            label: label.cloned(),
1163            slot: stamp.slot,
1164        };
1165        let held = Held::admitted(item, read, stamp.tree, stamp.fork);
1166        if let Some(old) = state.entries.insert(key, held) {
1167            state.bytes -= old.bytes();
1168        }
1169        state.bytes += bytes;
1170        state.bounded();
1171        match replaced {
1172            true => Kept::Replaced,
1173            false => Kept::Held,
1174        }
1175    }
1176}
1177
1178/// A node a later render is answered by, costing a flop per `BYTES_PER_FLOP` bytes or more.
1179fn writes(stored: &Stored, facts: Facts) -> bool {
1180    let bytes = u128::from(facts.samples) * u128::from(stored.width) * size_of::<f64>() as u128;
1181    (stored.readable || facts.target) && stored.priced * BYTES_PER_FLOP >= bytes
1182}
1183
1184/// A run that starts inside or at the end of the one held continues it: what it holds past
1185/// that one's end is laid on, the samples both hold being the same.
1186fn overlaps(held: &super::Run, more: &super::Run) -> bool {
1187    let (a, b) = (held.samples.start, held.end());
1188    a <= more.samples.start && more.samples.start <= b
1189}
1190
1191#[cfg(test)]
1192mod tests {
1193    use super::*;
1194
1195    /// Only a colliding key reaches this, so no render can: the entry is a miss, and goes.
1196    #[test]
1197    fn an_entry_that_does_not_answer_what_was_asked_is_a_miss_and_goes() {
1198        let memory = Memory::default();
1199        let key = Hash(7, 11);
1200        let stamp = Stamp {
1201            tree: memory.begin_tree(),
1202            fork: false,
1203            slot: None,
1204        };
1205        let four = Payload::Segments(vec![Arc::new(Buffer::mono(8_000, vec![0.25; 4]))]);
1206        for (rate, width) in [(48_000, 1), (8_000, 2)] {
1207            memory.store(key, four.clone(), None, stamp);
1208            let asked = Expected::Segments { rate, width };
1209            assert!(memory.load(key, asked, stamp).is_none());
1210            assert!(!memory.holds(key));
1211            assert_eq!(memory.bytes(), 0);
1212        }
1213    }
1214
1215    #[test]
1216    fn a_load_shares_the_samples_it_holds() {
1217        let memory = Memory::default();
1218        let key = Hash(3, 5);
1219        let stamp = Stamp {
1220            tree: memory.begin_tree(),
1221            fork: false,
1222            slot: None,
1223        };
1224        let part = Arc::new(Buffer::mono(8_000, vec![0.5; 64]));
1225        memory.store(key, Payload::Segments(vec![Arc::clone(&part)]), None, stamp);
1226        let asked = Expected::Segments {
1227            rate: 8_000,
1228            width: 1,
1229        };
1230        for _ in 0..2 {
1231            let loaded = memory.load(key, asked, stamp).expect("a hit");
1232            let Payload::Segments(parts) = loaded.payload else {
1233                panic!("segments were stored");
1234            };
1235            assert!(Arc::ptr_eq(&parts[0], &part), "the stored part itself");
1236        }
1237    }
1238}