Skip to main content

sva_engine/cache/
codec.rs

1// Concern: one stored node value, tagged with the store's format, and one staged run, as bytes and back | Non-concern: where the bytes live or when | IO: (Stored or Buffer) <-> bytes, a bad one None
2
3use sva_formula::Codomain;
4use sva_samples::{
5    Buffer, Cost, Detail, Dropped, Extent, Grid, Label, PSYCHOACOUSTIC_V1, Rule, Source,
6};
7
8use sva_formula::Hash;
9
10use super::Stored;
11use super::stored::{Header, Laid, Samples};
12
13/// Bumped by, and only by, a change to a stored value's bytes or to the key it is stored under.
14pub const STORE_FORMAT: u32 = 30;
15
16/// Every entry opens with its format, so one another format wrote is never read as a value.
17fn entry_tag() -> Vec<u8> {
18    [&b"SVAh"[..], &STORE_FORMAT.to_le_bytes()].concat()
19}
20
21const STAGED_RUN: &[u8; 4] = b"SVAc";
22
23/// Samples per chunk of a run: what one checksum covers, and the least a read loads. Samples
24/// stay f64, as the render computes them: a stored value is read by others, and f32 would
25/// change the bits of every value that reads it.
26pub(crate) const CHUNK: usize = 4096;
27
28/// A header's length, then the header, then each run's samples chunk by chunk, every plane of
29/// a chunk together, so a stretch of a run is one read.
30pub(crate) fn entry(head: &Header, runs: &[Buffer]) -> Vec<u8> {
31    let mut laid = Vec::new();
32    let mut body = Vec::new();
33    for run in runs {
34        let mut sums = Vec::new();
35        let at = body.len() as u64;
36        for from in (0..run.len()).step_by(CHUNK) {
37            let to = (from + CHUNK).min(run.len());
38            let chunk = body.len();
39            for plane in &run.planes {
40                for sample in &plane[from..to] {
41                    word(&mut body, sample.to_bits());
42                }
43            }
44            sums.push(checksum(&body[chunk..]));
45        }
46        laid.push(Laid {
47            rate: run.rate,
48            start: run.start,
49            width: run.width,
50            len: run.len(),
51            at,
52            sums,
53        });
54    }
55    let head = sealed(header(head, &laid));
56    let mut out = Vec::with_capacity(8 + head.len() + body.len());
57    word(&mut out, head.len() as u64);
58    out.extend_from_slice(&head);
59    out.extend_from_slice(&body);
60    out
61}
62
63fn header(head: &Header, laid: &[Laid]) -> Vec<u8> {
64    let stored = head.stored();
65    let mut out = entry_tag();
66    word(&mut out, stored.key.0);
67    word(&mut out, stored.key.1);
68    word(&mut out, stored.identity.0);
69    word(&mut out, stored.identity.1);
70    labelled(&mut out, &stored.label);
71    out.push(stored.width);
72    out.push(match stored.codomain {
73        Codomain::Real => 0,
74        Codomain::Complex => 1,
75    });
76    maybe(&mut out, stored.rate.map(u64::from));
77    word(&mut out, u64::from(stored.grid.rate));
78    out.extend_from_slice(&stored.grid.a.to_le_bytes());
79    out.extend_from_slice(&stored.grid.d.to_le_bytes());
80    word(&mut out, stored.support.start as u64);
81    word(&mut out, stored.support.end as u64);
82    out.extend_from_slice(&stored.priced.to_le_bytes());
83    word(&mut out, stored.moved.to_bits());
84    out.push(u8::from(stored.readable));
85    out.push(u8::from(stored.sampled));
86    word(&mut out, stored.cuts.len() as u64);
87    for (cut, at) in &stored.cuts {
88        word(&mut out, cut.0);
89        word(&mut out, cut.1);
90        word(&mut out, *at as u64);
91    }
92    match head.samples() {
93        &Samples::Of { key, by } => {
94            out.push(1);
95            word(&mut out, key.0);
96            word(&mut out, key.1);
97            word(&mut out, by as u64);
98        }
99        _ => out.push(0),
100    }
101    word(&mut out, laid.len() as u64);
102    for run in laid {
103        word(&mut out, u64::from(run.rate));
104        word(&mut out, run.start as u64);
105        word(&mut out, run.width as u64);
106        word(&mut out, run.len as u64);
107        for sum in &run.sums {
108            word(&mut out, *sum);
109        }
110    }
111    out
112}
113
114/// How many of an entry's first bytes its header takes, off its first eight.
115pub(crate) fn head_len(first: &[u8]) -> Option<usize> {
116    let len = u64::from_le_bytes(first.get(..8)?.try_into().ok()?);
117    8usize.checked_add(usize::try_from(len).ok()?)
118}
119
120/// The meta an entry's header states, its samples laid in `file`, and the entry's length; a
121/// truncated, corrupt or foreign header is `None`, never a partial value.
122pub(crate) fn read_head(bytes: &[u8], file: Hash) -> Option<(Header, u64)> {
123    let span = head_len(bytes)?;
124    let mut r = Reader(opened(bytes.get(8..span)?, &entry_tag())?);
125    let key = Hash(r.word()?, r.word()?);
126    let identity = Hash(r.word()?, r.word()?);
127    let label = r.label()?;
128    let width = r.byte()?;
129    let codomain = match r.byte()? {
130        0 => Codomain::Real,
131        1 => Codomain::Complex,
132        _ => return None,
133    };
134    let rate = match r.maybe()? {
135        Some(rate) => Some(u32::try_from(rate).ok()?),
136        None => None,
137    };
138    let grid = Grid {
139        rate: u32::try_from(r.word()?).ok()?,
140        a: r.wide()? as i128,
141        d: r.wide()? as i128,
142    };
143    let (start, end) = (r.word()? as i64, r.word()? as i64);
144    let support = (start <= end).then(|| Extent::new(start, end))?;
145    let priced = r.wide()?;
146    let moved = f64::from_bits(r.word()?);
147    let readable = r.flag()?;
148    let sampled = r.flag()?;
149    let mut cuts = Vec::new();
150    for _ in 0..r.word()?.min(r.0.len() as u64) {
151        cuts.push((Hash(r.word()?, r.word()?), r.word()? as i64));
152    }
153    let of = match r.byte()? {
154        0 => None,
155        1 => Some(Samples::Of {
156            key: Hash(r.word()?, r.word()?),
157            by: r.word()? as i64,
158        }),
159        _ => return None,
160    };
161    let count = r.word()? as usize;
162    let mut runs = Vec::new();
163    let mut at = span as u64;
164    for _ in 0..count.min(r.0.len()) {
165        let rate = u32::try_from(r.word()?).ok()?;
166        let start = r.word()? as i64;
167        let (width, len) = (r.word()? as usize, r.word()? as usize);
168        let sums = (0..len.div_ceil(CHUNK).min(r.0.len()))
169            .map(|_| r.word())
170            .collect::<Option<Vec<u64>>>()?;
171        if sums.len() != len.div_ceil(CHUNK) {
172            return None;
173        }
174        runs.push(Laid {
175            rate,
176            start,
177            width,
178            len,
179            at,
180            sums,
181        });
182        at = at.checked_add((width.checked_mul(len)?.checked_mul(8)?) as u64)?;
183    }
184    let samples = match (of, runs.is_empty()) {
185        (Some(of), true) => of,
186        (Some(_), false) => return None,
187        (None, true) => Samples::None,
188        (None, false) => Samples::Entry {
189            file,
190            runs,
191            shift: 0,
192        },
193    };
194    let stored = Stored {
195        key,
196        identity,
197        label,
198        width,
199        codomain,
200        rate,
201        grid,
202        support,
203        priced,
204        moved,
205        readable,
206        sampled,
207        cuts,
208        held: Vec::new(),
209    };
210    let whole = runs_whole(count, &samples) && r.0.is_empty();
211    whole.then(|| (Header::new(stored, samples), at))
212}
213
214fn runs_whole(count: usize, samples: &Samples) -> bool {
215    match samples {
216        Samples::Entry { runs, .. } => runs.len() == count,
217        _ => count == 0,
218    }
219}
220
221/// Where in its file a run's chunks `from..to` lie, and how many bytes.
222pub(crate) fn span_of(run: &Laid, from: usize, to: usize) -> (u64, u64) {
223    let bytes = |chunks: usize| ((chunks * CHUNK).min(run.len) * run.width * 8) as u64;
224    (run.at + bytes(from), bytes(to) - bytes(from))
225}
226
227/// A run's chunks `from..to` off the bytes `span_of` names; `None` where a checksum fails.
228pub(crate) fn read_chunks(bytes: &[u8], run: &Laid, from: usize, to: usize) -> Option<Buffer> {
229    let first = from * CHUNK;
230    let len = (to * CHUNK).min(run.len).checked_sub(first)?;
231    if bytes.len() != len * run.width * 8 {
232        return None;
233    }
234    let mut planes = vec![Vec::with_capacity(len); run.width];
235    let mut at = 0;
236    for (k, sum) in run.sums[from..to].iter().enumerate() {
237        let n = (CHUNK).min(run.len - first - k * CHUNK);
238        let chunk = &bytes[at..at + n * run.width * 8];
239        if checksum(chunk) != *sum {
240            return None;
241        }
242        for (c, plane) in planes.iter_mut().enumerate() {
243            let own = &chunk[c * n * 8..(c + 1) * n * 8];
244            plane.extend(
245                own.chunks_exact(8).map(|b| {
246                    f64::from_bits(u64::from_le_bytes(b.try_into().expect("eight bytes")))
247                }),
248            );
249        }
250        at += chunk.len();
251    }
252    let mut out = Buffer::of_planes(run.rate, planes);
253    out.start = run.start + first as i64;
254    Some(out)
255}
256
257/// Every run an entry under `key` lays out; `None` where any is corrupt.
258pub(crate) fn read_runs(bytes: &[u8], key: Hash) -> Option<Vec<Buffer>> {
259    let (head, _) = read_head(bytes, key)?;
260    let Samples::Entry { runs, .. } = head.samples() else {
261        return Some(Vec::new());
262    };
263    let read = |run: &Laid| {
264        let n = run.len.div_ceil(CHUNK);
265        let (at, len) = span_of(run, 0, n);
266        let span = bytes.get(at as usize..(at + len) as usize)?;
267        read_chunks(span, run, 0, n)
268    };
269    runs.iter().map(read).collect()
270}
271
272pub(crate) fn chunk(samples: &Buffer) -> Vec<u8> {
273    let mut out = STAGED_RUN.to_vec();
274    segments(&mut out, std::slice::from_ref(samples));
275    sealed(out)
276}
277
278pub(crate) fn read_chunk(bytes: &[u8]) -> Option<Buffer> {
279    let mut r = Reader(opened(bytes, STAGED_RUN)?);
280    let mut parts = r.segments()?;
281    (r.0.is_empty() && parts.len() == 1).then(|| parts.remove(0))
282}
283
284fn sealed(mut out: Vec<u8>) -> Vec<u8> {
285    let sum = checksum(&out);
286    word(&mut out, sum);
287    out
288}
289
290/// The body past `magic`, where the checksum holds.
291fn opened<'b>(bytes: &'b [u8], magic: &[u8]) -> Option<&'b [u8]> {
292    let body = bytes.len().checked_sub(8)?;
293    let (body, sum) = bytes.split_at(body);
294    (checksum(body).to_le_bytes() == sum && body.starts_with(magic)).then(|| &body[magic.len()..])
295}
296
297fn segments(out: &mut Vec<u8>, parts: &[Buffer]) {
298    word(out, parts.len() as u64);
299    for part in parts {
300        word(out, u64::from(part.rate));
301        word(out, part.start as u64);
302        word(out, part.width as u64);
303        word(out, part.len() as u64);
304        for plane in &part.planes {
305            for sample in plane {
306                word(out, sample.to_bits());
307            }
308        }
309    }
310}
311
312fn checksum(bytes: &[u8]) -> u64 {
313    bytes.iter().fold(0xcbf2_9ce4_8422_2325, |h, b| {
314        (h ^ u64::from(*b)).wrapping_mul(0x0100_0000_01b3)
315    })
316}
317
318fn word(out: &mut Vec<u8>, w: u64) {
319    out.extend_from_slice(&w.to_le_bytes());
320}
321
322fn float(out: &mut Vec<u8>, v: Option<f64>) {
323    maybe(out, v.map(f64::to_bits));
324}
325
326fn maybe(out: &mut Vec<u8>, w: Option<u64>) {
327    match w {
328        None => out.push(0),
329        Some(w) => {
330            out.push(1);
331            word(out, w);
332        }
333    }
334}
335
336fn text(out: &mut Vec<u8>, s: &str) {
337    word(out, s.len() as u64);
338    out.extend_from_slice(s.as_bytes());
339}
340
341fn labelled(out: &mut Vec<u8>, label: &Label) {
342    out.push(match label.source {
343        Source::Exact => 0,
344        Source::Measured => 1,
345    });
346    text(out, label.profile);
347    word(out, u64::from(label.rate));
348    detailed(out, &label.detail);
349    match label.cost {
350        None => out.push(0),
351        Some(Cost { flops, budget }) => {
352            out.push(1);
353            out.extend_from_slice(&flops.to_le_bytes());
354            out.extend_from_slice(&budget.to_le_bytes());
355        }
356    }
357    float(out, label.moved);
358    match &label.pruned {
359        None => out.push(0),
360        Some(pruned) => {
361            out.push(1);
362            word(out, pruned.db.to_bits());
363            word(out, pruned.cuts.len() as u64);
364            for (node, at) in &pruned.cuts {
365                text(out, node);
366                word(out, *at as u64);
367            }
368        }
369    }
370}
371
372fn detailed(out: &mut Vec<u8>, detail: &Detail) {
373    let rule = |out: &mut Vec<u8>, rule: &Rule| text(out, rule.as_str());
374    match detail {
375        Detail::Lines {
376            rule: r,
377            placed,
378            summed,
379            dropped,
380            dropped_more,
381            terms,
382            tail_db,
383        } => {
384            out.push(0);
385            rule(out, r);
386            word(out, *placed as u64);
387            word(out, *summed as u64);
388            word(out, dropped.len() as u64);
389            for d in dropped {
390                word(out, d.hz.to_bits());
391                word(out, d.db.to_bits());
392            }
393            word(out, *dropped_more as u64);
394            maybe(out, terms.map(|t| t as u64));
395            float(out, *tail_db);
396        }
397        Detail::Continuous { rule: r } => {
398            out.push(1);
399            rule(out, r);
400        }
401        Detail::Cropped { rule: r, tail_db } => {
402            out.push(2);
403            rule(out, r);
404            float(out, *tail_db);
405        }
406        Detail::Point {
407            rule: r,
408            alias_db,
409            tail_db,
410        } => {
411            out.push(3);
412            rule(out, r);
413            float(out, *alias_db);
414            float(out, *tail_db);
415        }
416        Detail::Spectrum { rule: r, wrap_db } => {
417            out.push(4);
418            rule(out, r);
419            word(out, wrap_db.to_bits());
420        }
421        Detail::Roundtrip { rule: r, edited } => {
422            out.push(5);
423            rule(out, r);
424            out.push(u8::from(*edited));
425        }
426        Detail::Reading { rule: r } => {
427            out.push(6);
428            rule(out, r);
429        }
430        Detail::Added { parts } => {
431            out.push(7);
432            word(out, parts.len() as u64);
433            for part in parts {
434                detailed(out, part);
435            }
436        }
437    }
438}
439
440struct Reader<'b>(&'b [u8]);
441
442impl Reader<'_> {
443    fn take(&mut self, n: usize) -> Option<&[u8]> {
444        if self.0.len() < n {
445            return None;
446        }
447        let (head, rest) = self.0.split_at(n);
448        self.0 = rest;
449        Some(head)
450    }
451
452    fn byte(&mut self) -> Option<u8> {
453        self.take(1).map(|b| b[0])
454    }
455
456    fn flag(&mut self) -> Option<bool> {
457        match self.byte()? {
458            0 => Some(false),
459            1 => Some(true),
460            _ => None,
461        }
462    }
463
464    fn word(&mut self) -> Option<u64> {
465        Some(u64::from_le_bytes(self.take(8)?.try_into().ok()?))
466    }
467
468    fn segments(&mut self) -> Option<Vec<Buffer>> {
469        let count = self.word()? as usize;
470        let mut parts = Vec::new();
471        for _ in 0..count.min(self.0.len()) {
472            let rate = u32::try_from(self.word()?).ok()?;
473            let start = self.word()? as i64;
474            let (width, len) = (self.word()? as usize, self.word()? as usize);
475            if width.saturating_mul(len).saturating_mul(8) > self.0.len() {
476                return None;
477            }
478            let planes = (0..width)
479                .map(|_| (0..len).map(|_| self.word().map(f64::from_bits)).collect())
480                .collect::<Option<Vec<Vec<f64>>>>()?;
481            let mut part = Buffer::of_planes(rate, planes);
482            part.start = start;
483            parts.push(part);
484        }
485        (parts.len() == count).then_some(parts)
486    }
487
488    fn wide(&mut self) -> Option<u128> {
489        Some(u128::from_le_bytes(self.take(16)?.try_into().ok()?))
490    }
491
492    fn maybe(&mut self) -> Option<Option<u64>> {
493        match self.byte()? {
494            0 => Some(None),
495            1 => Some(Some(self.word()?)),
496            _ => None,
497        }
498    }
499
500    fn float(&mut self) -> Option<Option<f64>> {
501        Some(self.maybe()?.map(f64::from_bits))
502    }
503
504    fn text(&mut self) -> Option<&str> {
505        let len = self.word()? as usize;
506        std::str::from_utf8(self.take(len)?).ok()
507    }
508
509    fn rule(&mut self) -> Option<Rule> {
510        Rule::named(self.text()?)
511    }
512
513    fn label(&mut self) -> Option<Label> {
514        let source = match self.byte()? {
515            0 => Source::Exact,
516            1 => Source::Measured,
517            _ => return None,
518        };
519        let profile = match self.text()? {
520            name if name == PSYCHOACOUSTIC_V1.name => PSYCHOACOUSTIC_V1.name,
521            _ => return None,
522        };
523        let rate = u32::try_from(self.word()?).ok()?;
524        let detail = self.detail()?;
525        let cost = match self.byte()? {
526            0 => None,
527            1 => Some(Cost {
528                flops: self.wide()?,
529                budget: self.wide()?,
530            }),
531            _ => return None,
532        };
533        let moved = self.float()?;
534        let pruned = match self.byte()? {
535            0 => None,
536            1 => {
537                let db = f64::from_bits(self.word()?);
538                let count = usize::try_from(self.word()?).ok()?;
539                let cuts = (0..count)
540                    .map(|_| Some((self.text()?.to_string(), self.word()? as i64)))
541                    .collect::<Option<Vec<_>>>()?;
542                Some(sva_samples::Pruned { db, cuts })
543            }
544            _ => return None,
545        };
546        Some(Label {
547            source,
548            profile,
549            rate,
550            detail,
551            cost,
552            moved,
553            pruned,
554        })
555    }
556
557    fn detail(&mut self) -> Option<Detail> {
558        Some(match self.byte()? {
559            0 => {
560                let rule = self.rule()?;
561                let (placed, summed) = (self.word()? as usize, self.word()? as usize);
562                let count = self.word()? as usize;
563                let dropped = (0..count.min(self.0.len()))
564                    .map(|_| {
565                        Some(Dropped {
566                            hz: f64::from_bits(self.word()?),
567                            db: f64::from_bits(self.word()?),
568                        })
569                    })
570                    .collect::<Option<Vec<_>>>()?;
571                if dropped.len() != count {
572                    return None;
573                }
574                Detail::Lines {
575                    rule,
576                    placed,
577                    summed,
578                    dropped,
579                    dropped_more: self.word()? as usize,
580                    terms: self.maybe()?.map(|t| t as usize),
581                    tail_db: self.float()?,
582                }
583            }
584            1 => Detail::Continuous { rule: self.rule()? },
585            2 => Detail::Cropped {
586                rule: self.rule()?,
587                tail_db: self.float()?,
588            },
589            3 => Detail::Point {
590                rule: self.rule()?,
591                alias_db: self.float()?,
592                tail_db: self.float()?,
593            },
594            4 => Detail::Spectrum {
595                rule: self.rule()?,
596                wrap_db: f64::from_bits(self.word()?),
597            },
598            5 => Detail::Roundtrip {
599                rule: self.rule()?,
600                edited: match self.byte()? {
601                    0 => false,
602                    1 => true,
603                    _ => return None,
604                },
605            },
606            6 => Detail::Reading { rule: self.rule()? },
607            7 => {
608                let count = self.word()? as usize;
609                let parts = (0..count.min(self.0.len()))
610                    .map(|_| self.detail())
611                    .collect::<Option<Vec<_>>>()?;
612                if parts.len() != count {
613                    return None;
614                }
615                Detail::Added { parts }
616            }
617            _ => return None,
618        })
619    }
620}