Skip to main content

sva_engine/refs/
identity.rs

1// Concern: content-addresses one node, whatever representation it holds | Non-concern: composing a closed form across a ref (mod.rs) | IO: (NodeId) -> Hash
2
3use sva_formula::{
4    ClosedForm, Hash, NodeId, SpectralSum, Var, hash_closed_form, hash_spectral_sum,
5};
6
7use crate::error::{Diagnostic, EngineError};
8use crate::offset::Offset;
9use crate::typing::{Typing, Value};
10
11use super::{cyclic, nodes_in, spectral_sum_of, substituted_closed_form};
12
13/// What keys a closed form's spectral sum and every buffer collapsed from it.
14pub fn symbolic_hash(typing: &Typing, node: NodeId, want: Var) -> Result<Hash, EngineError> {
15    spectral_sum_of(typing, node, want).map(|n| hash_spectral_sum(&n))
16}
17
18/// What one node is, whatever it holds: its closed form's own form, else the tree it was built from.
19pub fn identity(typing: &Typing, node: NodeId) -> Result<Hash, EngineError> {
20    identity_of(typing, node, &mut Vec::new())
21}
22
23fn identity_of(typing: &Typing, node: NodeId, open: &mut Vec<NodeId>) -> Result<Hash, EngineError> {
24    if typing.ty(node).is_closed_form() {
25        let named = closed_form_identity(
26            &spectral_sum_of(typing, node, typing.var(node)),
27            substituted_closed_form(typing, node).as_ref(),
28        );
29        return match named {
30            Ok(hash) => Ok(hash),
31            Err(form) => built(typing, node, open).map_err(|tree| neither(form, tree)),
32        };
33    }
34    built(typing, node, open)
35}
36
37/// Neither form names the node. Each refusal alone reads as the whole reason, so the one
38/// that answers carries both.
39fn neither(form: EngineError, tree: EngineError) -> EngineError {
40    EngineError::refused(Diagnostic {
41        code: tree.code().to_string(),
42        message: format!("{form} and the tree it was built from: {tree}"),
43        location: tree.at().or_else(|| form.at()).cloned().unwrap_or_default(),
44        help: "write the construct inside the node it reads, or sample it".to_string(),
45    })
46}
47
48fn built(typing: &Typing, node: NodeId, open: &mut Vec<NodeId>) -> Result<Hash, EngineError> {
49    if open.contains(&node) {
50        return Err(cyclic(typing, node));
51    }
52    open.push(node);
53    let mut sink = Sink::new();
54    match typing.value(node) {
55        Value::ClosedForm(form) => {
56            sink.text("closed form");
57            sink.hash(hash_closed_form(form));
58            for id in nodes_in(&form.body) {
59                sink.hash(identity_of(typing, id, open)?);
60            }
61        }
62        Value::Cast(cast, source) => {
63            sink.text(cast.name());
64            sink.hash(identity_of(typing, *source, open)?);
65        }
66        Value::Read { source, at, .. } => {
67            sink.text("read");
68            sink.hash(identity_of(typing, *source, open)?);
69            match at {
70                Offset::Steps(steps) => {
71                    sink.text("sp");
72                    sink.word(*steps as u64);
73                }
74                Offset::Secs(secs) => {
75                    sink.text("s");
76                    sink.word(secs.to_bits());
77                }
78            }
79        }
80        Value::SelfAt(delay) => sink.text(&format!("self {delay:?}")),
81        Value::Grid(count) => sink.text(&format!("sp {count}")),
82        Value::Solver(params) => sink.text(&format!("{params:?}")),
83        Value::Filter {
84            shape,
85            x,
86            cutoff,
87            q,
88            gain,
89        } => {
90            sink.text(shape.name());
91            for operand in [x, cutoff, q, gain] {
92                sink.hash(identity_of(typing, *operand, open)?);
93            }
94        }
95        Value::Op { name, args } => {
96            sink.text(name);
97            for arg in args {
98                sink.hash(identity_of(typing, *arg, open)?);
99            }
100        }
101    }
102    open.pop();
103    Ok(sink.finish())
104}
105
106const IDENTITY_ROTATE: u32 = 23;
107
108struct Sink(sva_formula::Lanes<IDENTITY_ROTATE>);
109
110impl Sink {
111    fn new() -> Sink {
112        Sink(sva_formula::Lanes::default())
113    }
114
115    fn word(&mut self, part: u64) {
116        self.0.word(part);
117    }
118
119    fn text(&mut self, what: &str) {
120        self.word(what.len() as u64);
121        for byte in what.as_bytes() {
122            self.word(u64::from(*byte));
123        }
124    }
125
126    fn hash(&mut self, held: Hash) {
127        self.word(held.0);
128        self.word(held.1);
129    }
130
131    fn finish(&self) -> Hash {
132        self.0.finish()
133    }
134}
135
136/// The same identity from forms a caller already holds, so a render never walks a closed form twice.
137pub fn closed_form_identity(
138    sum: &Result<SpectralSum, EngineError>,
139    written: Option<&ClosedForm>,
140) -> Result<Hash, EngineError> {
141    match (sum, written) {
142        (Ok(sum), _) => Ok(hash_spectral_sum(sum)),
143        (Err(_), Some(form)) => Ok(hash_closed_form(form)),
144        (Err(e), None) => Err(e.clone()),
145    }
146}