Skip to main content

sva_engine/
cache.rs

1// Concern: declares what memory and a disk hold under a content hash and how a value's key is built | Non-concern: what memory keeps and evicts (memory.rs) | IO: (Hash) -> a payload
2
3mod codec;
4mod index;
5pub(crate) mod log;
6mod memory;
7mod persist;
8mod stats;
9mod stored;
10mod tier;
11
12pub use codec::STORE_FORMAT;
13pub use memory::{
14    BYTES_PER_FLOP, CachePolicy, Counters, DEFAULT_CACHE_BYTES, DEFAULT_MARK_EVERY, PrunePolicy,
15};
16pub(crate) use memory::{Facts, Known, Memory, Offered};
17pub use persist::{Backend, DEFAULT_STORE_BYTES, INDEX_NAME, Persisted, Store};
18pub(crate) use stats::Recording;
19pub use stats::{CacheStats, Lookup, Outcome};
20pub use stored::Stored;
21pub(crate) use stored::node_key;
22pub use sva_formula::Hash;
23pub(crate) use tier::now;
24pub use tier::{FETCH_READS, Nothing, Tier};
25
26use std::collections::BTreeMap;
27use std::sync::Arc;
28
29use sva_samples::{Buffer, Frames, Label, MachineState};
30
31/// A value's segments, a stateful value's run, or one analysis of a value, each shared: a clone
32/// hands out the same samples, never a copy of them.
33#[derive(Clone, Debug, PartialEq)]
34pub enum Payload {
35    Segments(Vec<Arc<Buffer>>),
36    Frames(Arc<Frames>),
37    Run(Arc<Run>),
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub enum PayloadKind {
42    Segments,
43    Frames,
44    Run,
45}
46
47/// One segment of a run: samples, marked states, and the segment before it.
48#[derive(Clone)]
49pub struct Run {
50    pub samples: Arc<Buffer>,
51    pub marks: BTreeMap<i64, MachineState>,
52    pub parent: Option<Hash>,
53}
54
55impl Run {
56    pub fn end(&self) -> i64 {
57        self.samples.extent().end
58    }
59
60    pub fn bytes(&self) -> usize {
61        let marks: usize = self.marks.values().map(MachineState::bytes).sum();
62        self.samples.len() * self.samples.width * size_of::<f64>() + marks
63    }
64}
65
66/// The state is the node's own where its samples end, so the samples decide.
67impl PartialEq for Run {
68    fn eq(&self, other: &Run) -> bool {
69        self.samples == other.samples
70    }
71}
72
73impl std::fmt::Debug for Run {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        f.debug_struct("Run")
76            .field("samples", &self.samples.extent())
77            .finish()
78    }
79}
80
81/// An entry not matching this is a miss, never a coercion.
82#[derive(Clone, Copy, Debug, PartialEq, Eq)]
83pub enum Expected {
84    Segments { rate: u32, width: usize },
85    Frames,
86    Run { rate: u32, width: usize },
87}
88
89#[derive(Clone, Debug, PartialEq)]
90pub struct Entry {
91    pub payload: Payload,
92    /// FORMAT 9.3: the label is part of the value, so a hit answers with the cold run's.
93    pub label: Option<Label>,
94}
95
96impl Payload {
97    pub fn run(self) -> Option<Arc<Run>> {
98        match self {
99            Payload::Run(run) => Some(run),
100            _ => None,
101        }
102    }
103
104    pub fn bytes(&self) -> usize {
105        match self {
106            Payload::Segments(parts) => parts
107                .iter()
108                .map(|b| b.len() * b.width * size_of::<f64>())
109                .sum(),
110            Payload::Frames(f) => f.width * f.frames * f.bins * 2 * size_of::<f64>(),
111            Payload::Run(run) => run.bytes(),
112        }
113    }
114
115    pub fn answers(&self, expected: Expected) -> bool {
116        match (self, expected) {
117            (Payload::Segments(parts), Expected::Segments { rate, width }) => {
118                parts.iter().all(|b| b.rate == rate && b.width == width)
119            }
120            (Payload::Frames(_), Expected::Frames) => true,
121            (Payload::Run(run), Expected::Run { rate, width }) => {
122                run.samples.rate == rate && run.samples.width == width
123            }
124            _ => false,
125        }
126    }
127}
128
129pub fn frames_key(value: Hash, window: usize, hop: usize) -> Hash {
130    mixed(
131        value,
132        &[window as u64, hop as u64, 0x66_72_61_6d_65_73_00_01],
133    )
134}
135
136/// One value at one rate, width and profile, never where a reader places it.
137pub fn value_key(
138    identity: Hash,
139    step: (i128, i128),
140    rate: u32,
141    width: usize,
142    profile: &sva_samples::Profile,
143) -> Hash {
144    mixed(
145        identity,
146        &[
147            step.0 as u64,
148            step.1 as u64,
149            u64::from(rate),
150            width as u64,
151            profile.precision_bits as u64,
152            profile.ceiling_hz.to_bits(),
153            profile.prune_db.to_bits(),
154            0x76_61_6c_75_65_00_00_01,
155        ],
156    )
157}
158
159const ADDRESS_ROTATE: u32 = 17;
160
161pub(crate) fn mixed(seed: Hash, parts: &[u64]) -> Hash {
162    let mut lanes = sva_formula::Lanes::<ADDRESS_ROTATE>::from(seed);
163    for part in parts {
164        lanes.word(*part);
165    }
166    lanes.finish()
167}
168
169/// `more` laid among `parts`, each touching pair joined into one; a shared part is copied only
170/// to grow.
171pub(crate) fn joined(parts: &mut Vec<Arc<Buffer>>, more: Vec<Arc<Buffer>>) {
172    for part in more {
173        parts.push(part);
174    }
175    parts.sort_by_key(|b| b.start);
176    let mut out: Vec<Arc<Buffer>> = Vec::with_capacity(parts.len());
177    for part in parts.drain(..) {
178        match out.last_mut() {
179            Some(last) if last.extent().end >= part.start => {
180                let from = (last.extent().end - part.start) as usize;
181                if from < part.len() {
182                    let last = Arc::make_mut(last);
183                    for (held, more) in last.planes.iter_mut().zip(&part.planes) {
184                        held.extend_from_slice(&more[from.min(more.len())..]);
185                    }
186                }
187            }
188            _ => out.push(part),
189        }
190    }
191    *parts = out;
192}