Skip to main content

sva_engine/typing/
mod.rs

1// Concern: gives every node one Ty and the value it lowered to | Non-concern: the per-term judgment (sva-formula), lowering (lower/) | IO: (Instances, Order) -> Ty per node
2
3mod draft;
4mod folds;
5
6use std::collections::{BTreeMap, BTreeSet};
7use std::sync::Arc;
8
9use sva_formula::filter::Shape;
10use sva_formula::{ClosedForm, Codomain, Env, Held, NodeId, Origin, ParamId, Ty, Var, infer};
11use sva_samples::Params;
12
13use crate::arguments::{Arguments, Called, Chosen};
14use crate::cast::Cast;
15use crate::error::{Diagnostic, EngineError, Located};
16use crate::instantiate::Instances;
17use crate::lower;
18use crate::schedule::Order;
19use crate::time::Grid;
20
21use draft::{Draft, Entry, Units};
22
23/// A closed form is cast-free on one axis; every crossing is its own node.
24#[derive(Clone, Debug, PartialEq)]
25pub enum Value {
26    ClosedForm(ClosedForm),
27    Cast(Cast, NodeId),
28    Op {
29        name: String,
30        args: Vec<NodeId>,
31    },
32    SelfAt {
33        at: When,
34    },
35    Read {
36        source: NodeId,
37        at: When,
38        site: Origin,
39    },
40    Noise(u64),
41    /// The signal and its three arguments, each a node of its own.
42    Filter {
43        shape: Shape,
44        x: NodeId,
45        cutoff: NodeId,
46        q: NodeId,
47        gain: NodeId,
48    },
49    Solver {
50        params: Box<Params>,
51        varying: Vec<(&'static str, NodeId)>,
52    },
53}
54
55/// A read's instant: the exact time `k*t + s` written; a closed form of `t` held as a node;
56/// or an integer each sample evaluates.
57#[derive(Clone, Debug, PartialEq)]
58pub enum When {
59    At(crate::time::Affine),
60    Moving(NodeId),
61    Index(crate::index::Index),
62    Step(Step),
63}
64
65/// An integer no one map spells: exact indices, `idx(time, round)` of a time that moves, and
66/// sums, negations and products of them.
67#[derive(Clone, Debug, PartialEq)]
68pub enum Step {
69    Index(crate::index::Index),
70    Nearest(NodeId, crate::index::Round),
71    Add(Vec<Step>),
72    Neg(Box<Step>),
73    Mul(Vec<Step>),
74}
75
76impl Step {
77    /// Every time that moves in it.
78    pub(crate) fn times(&self, out: &mut Vec<NodeId>) {
79        match self {
80            Step::Index(_) => {}
81            Step::Nearest(time, _) => out.push(*time),
82            Step::Add(parts) | Step::Mul(parts) => parts.iter().for_each(|p| p.times(out)),
83            Step::Neg(p) => p.times(out),
84        }
85    }
86}
87
88impl When {
89    /// Every node it evaluates each sample.
90    pub(crate) fn moving(&self) -> Vec<NodeId> {
91        let mut out = Vec::new();
92        match self {
93            When::Moving(id) => out.push(*id),
94            When::Step(step) => step.times(&mut out),
95            When::At(_) | When::Index(_) => {}
96        }
97        out
98    }
99}
100
101#[derive(Clone, Debug, PartialEq)]
102pub struct Node {
103    pub name: Arc<str>,
104    pub ty: Ty,
105    pub var: Var,
106    pub value: Value,
107    pub grid: Grid,
108}
109
110/// Every node keeps one id while held; a node lowered anew takes a free one.
111#[derive(Clone, Debug, Default, PartialEq)]
112pub struct Typing {
113    nodes: Vec<Option<Node>>,
114    free: Vec<u32>,
115    arguments: BTreeMap<String, Arguments>,
116    by_path: BTreeMap<String, NodeId>,
117    /// Paths `by_path` holds for a file's one instance.
118    aliases: BTreeSet<String>,
119    copies: BTreeMap<String, BTreeMap<Grid, NodeId>>,
120    lowering: BTreeSet<String>,
121    files: BTreeMap<String, Vec<NodeId>>,
122    origins: Vec<Option<(u32, Option<sva_ast::ByteSpan>)>>,
123    free_origins: Vec<u32>,
124    /// Each node name origins mark, and how many.
125    sites: Vec<Option<(String, u32)>>,
126    free_sites: Vec<u32>,
127    site_ids: BTreeMap<String, u32>,
128    pending: BTreeSet<NodeId>,
129    indices: u32,
130    sum: Option<(NodeId, Vec<SumSlot>)>,
131    folds: folds::Folds,
132    /// Each node every held node reads, mapped to those reading it.
133    readers: BTreeMap<NodeId, BTreeSet<NodeId>>,
134    /// Every node the latest draft lowered, in order.
135    lowered: Vec<String>,
136    units: BTreeMap<String, Units>,
137    making: Vec<(Arc<str>, Grid)>,
138    draft: Draft,
139}
140
141/// One term of a stream's note sum: its node, or the hull of the supports of those it retired.
142#[derive(Clone, Copy, Debug, PartialEq)]
143pub(crate) enum SumSlot {
144    Node(NodeId),
145    Retired(sva_samples::Extent),
146}
147
148impl Typing {
149    /// A copy to change while a render still holds this one: the copy takes every fold, and
150    /// this one folds anew.
151    pub(crate) fn parted(&self) -> Typing {
152        let mut copy = self.clone();
153        copy.folds = self.folds.parted();
154        copy
155    }
156
157    /// `node` named by its terms in place.
158    pub(crate) fn name_sum(&mut self, node: NodeId, slots: Vec<SumSlot>) {
159        self.draft.sum = Some(Some((node, slots)));
160        self.forget([node]);
161    }
162
163    /// The folds of each of `changed` and of every node reading one, however far up, let go.
164    pub(crate) fn forget(&mut self, changed: impl IntoIterator<Item = NodeId>) {
165        let mut gone: BTreeSet<NodeId> = changed.into_iter().collect();
166        if gone.is_empty() {
167            return;
168        }
169        let mut open: Vec<NodeId> = gone.iter().copied().collect();
170        while let Some(at) = open.pop() {
171            let summed = self
172                .summed()
173                .filter(|(_, slots)| slots.contains(&SumSlot::Node(at)));
174            let held = self.readers.get(&at).into_iter().flatten().copied();
175            for reader in held.chain(summed.map(|(sum, _)| *sum)) {
176                if gone.insert(reader) {
177                    open.push(reader);
178                }
179            }
180        }
181        self.folds.forget(&gone);
182    }
183
184    /// `node` held at `id` in place of what was, each edge it reads recorded.
185    pub(super) fn place(&mut self, id: NodeId, node: Option<Node>) {
186        if self.nodes[id.0 as usize].is_some() {
187            for read in self.own_edges(id) {
188                if let Some(held) = self.readers.get_mut(&read)
189                    && held.remove(&id)
190                    && held.is_empty()
191                {
192                    self.readers.remove(&read);
193                }
194            }
195        }
196        self.nodes[id.0 as usize] = node;
197        if self.nodes[id.0 as usize].is_some() {
198            for read in self.own_edges(id) {
199                self.readers.entry(read).or_default().insert(id);
200            }
201        }
202    }
203
204    /// Each node whose own value reads `id` one hop up.
205    pub(crate) fn readers_of(&self, id: NodeId) -> impl Iterator<Item = NodeId> + '_ {
206        self.readers.get(&id).into_iter().flatten().copied()
207    }
208
209    fn summed(&self) -> Option<&(NodeId, Vec<SumSlot>)> {
210        match &self.draft.sum {
211            Some(staged) => staged.as_ref(),
212            None => self.sum.as_ref(),
213        }
214    }
215
216    /// The note sum, where it holds a term it retired.
217    pub(crate) fn retired_sum(&self) -> Option<NodeId> {
218        let (sum, slots) = self.summed()?;
219        let retired = slots.iter().any(|slot| matches!(slot, SumSlot::Retired(_)));
220        retired.then_some(*sum)
221    }
222
223    /// The values a node's own value is computed from.
224    pub(crate) fn operands(&self, id: NodeId) -> Vec<NodeId> {
225        match self.value(id) {
226            Value::ClosedForm(form) => crate::refs::nodes_in(&form.body),
227            Value::Cast(_, source) | Value::Read { source, .. } => vec![*source],
228            Value::Op { args, .. } => args.clone(),
229            Value::Filter {
230                x, cutoff, q, gain, ..
231            } => vec![*x, *cutoff, *q, *gain],
232            Value::Solver { varying, .. } => varying.iter().map(|(_, a)| *a).collect(),
233            Value::SelfAt { .. } | Value::Noise(_) => Vec::new(),
234        }
235    }
236
237    /// Whether `id` reads `of`, however far down.
238    pub(crate) fn reads(&self, id: NodeId, of: NodeId) -> bool {
239        let (mut open, mut seen) = (vec![id], BTreeSet::new());
240        while let Some(at) = open.pop() {
241            if at == of {
242                return true;
243            }
244            if seen.insert(at) {
245                open.extend(self.edges(at));
246            }
247        }
248        false
249    }
250
251    /// `root` and each node under it that `held` lacks, readers after what they read: a fold
252    /// asked in this order recurses one ref deep.
253    pub(crate) fn unfolded(&self, root: NodeId, held: impl Fn(NodeId) -> bool) -> Vec<NodeId> {
254        self.unfolded_over(root, |id| self.edges(id), held)
255    }
256
257    pub(crate) fn unfolded_over(
258        &self,
259        root: NodeId,
260        reads: impl Fn(NodeId) -> Vec<NodeId>,
261        held: impl Fn(NodeId) -> bool,
262    ) -> Vec<NodeId> {
263        let mut order = Vec::new();
264        let mut seen = BTreeSet::new();
265        let mut open = vec![(root, false)];
266        while let Some((at, read)) = open.pop() {
267            if read {
268                order.push(at);
269                continue;
270            }
271            if held(at) || !seen.insert(at) {
272                continue;
273            }
274            open.push((at, true));
275            open.extend(reads(at).into_iter().rev().map(|n| (n, false)));
276        }
277        order
278    }
279
280    /// Its operands and the times it reads at: the edges its own value holds.
281    fn own_edges(&self, id: NodeId) -> Vec<NodeId> {
282        let mut out = self.operands(id);
283        if let Value::Read { at, .. } | Value::SelfAt { at, .. } = self.value(id) {
284            out.extend(at.moving());
285        }
286        out
287    }
288
289    /// Every edge its identity is hashed over: its own and the note sum's terms.
290    fn edges(&self, id: NodeId) -> Vec<NodeId> {
291        let mut out = self.own_edges(id);
292        let slots = self.sum_slots(id).unwrap_or_default().iter();
293        out.extend(slots.filter_map(|slot| match slot {
294            SumSlot::Node(read) if *read != id => Some(*read),
295            _ => None,
296        }));
297        out
298    }
299
300    pub(crate) fn sum_slots(&self, node: NodeId) -> Option<&[SumSlot]> {
301        self.summed()
302            .filter(|(held, _)| *held == node)
303            .map(|(_, slots)| slots.as_slice())
304    }
305
306    pub(crate) fn lowering(&mut self, path: &str) {
307        self.lowered.push(path.to_string());
308    }
309
310    pub(crate) fn lowered(&self) -> &[String] {
311        &self.lowered
312    }
313
314    pub(crate) fn next_index(&mut self) -> sva_formula::IndexId {
315        self.indices += 1;
316        sva_formula::IndexId(self.indices)
317    }
318
319    /// A call lowered twice is noted once, at its latest lowering.
320    pub(crate) fn note(&mut self, node: &str, call: Option<Called>, chosen: Vec<Chosen>) {
321        let mut held = self.arguments(node).cloned().unwrap_or_else(|| Arguments {
322            node: node.to_string(),
323            ..Arguments::default()
324        });
325        if let Some(call) = call {
326            held.calls
327                .retain(|c| (c.at.start, &c.name) != (call.at.start, &call.name));
328            held.calls.push(call);
329        }
330        for one in chosen {
331            held.chosen.retain(|c| c.at.start != one.at.start);
332            held.chosen.push(one);
333        }
334        let old = self.draft.arguments.insert(node.to_string(), held);
335        self.draft.journal.push(Entry::Noted(node.to_string(), old));
336    }
337
338    pub fn arguments(&self, node: &str) -> Option<&Arguments> {
339        match self.draft.arguments.get(node) {
340            Some(held) => Some(held),
341            None if self.draft.hidden.contains(node) => None,
342            None => self.arguments.get(node),
343        }
344    }
345
346    pub fn ty(&self, n: NodeId) -> Ty {
347        self.at(n).ty
348    }
349
350    pub fn var(&self, n: NodeId) -> Var {
351        self.at(n).var
352    }
353
354    pub fn value(&self, n: NodeId) -> &Value {
355        &self.at(n).value
356    }
357
358    pub fn name(&self, n: NodeId) -> &str {
359        &self.at(n).name
360    }
361
362    pub fn grid(&self, n: NodeId) -> Grid {
363        self.at(n).grid
364    }
365
366    pub(crate) fn copy(&self, path: &str, grid: Grid) -> Option<NodeId> {
367        let key = (path.to_string(), grid);
368        match self.draft.copies.get(&key) {
369            Some(id) => Some(*id),
370            None if self.draft.hidden.contains(path) => None,
371            None => self
372                .copies
373                .get(path)
374                .and_then(|held| held.get(&grid))
375                .copied(),
376        }
377    }
378
379    pub(crate) fn copied(&mut self, path: &str, grid: Grid, id: NodeId) {
380        let key = (path.to_string(), grid);
381        let old = self.draft.copies.insert(key.clone(), id);
382        self.draft.journal.push(Entry::Copy(key, old));
383    }
384
385    /// `false` where a copy of `path` is already being lowered: a loop of refs.
386    pub(crate) fn opened(&mut self, path: &str) -> bool {
387        self.lowering.insert(path.to_string())
388    }
389
390    pub(crate) fn closed(&mut self, path: &str) {
391        self.lowering.remove(path);
392    }
393
394    pub fn at(&self, n: NodeId) -> &Node {
395        self.nodes[n.0 as usize]
396            .as_ref()
397            .expect("a node the typing holds")
398    }
399
400    pub fn ids(&self) -> impl Iterator<Item = NodeId> + '_ {
401        let held = self.nodes.iter().enumerate();
402        held.filter(|(_, n)| n.is_some())
403            .map(|(at, _)| NodeId(at as u32))
404    }
405
406    #[cfg(test)]
407    pub(crate) fn len(&self) -> usize {
408        self.nodes.len() - self.free.len()
409    }
410
411    pub fn id(&self, path: &str) -> Option<NodeId> {
412        match self.draft.by_path.get(path) {
413            Some(id) => Some(*id),
414            None if self.draft.hidden.contains(path) => None,
415            None => self.by_path.get(path).copied(),
416        }
417    }
418
419    /// Every path named, the draft's over the rest.
420    pub fn paths(&self) -> impl Iterator<Item = (&str, NodeId)> {
421        let drafted = self.draft.by_path.iter();
422        let kept = self.by_path.iter().filter(|(path, _)| {
423            !self.draft.hidden.contains(*path) && !self.draft.by_path.contains_key(*path)
424        });
425        let all: BTreeMap<&String, &NodeId> = drafted.chain(kept).collect();
426        all.into_iter().map(|(p, id)| (p.as_str(), *id))
427    }
428
429    /// The node a reading asks for, by instance path or by the file a composer wrote.
430    pub fn resolve(&self, path: &str) -> Result<NodeId, EngineError> {
431        if let Some(id) = self.id(path) {
432            return Ok(id);
433        }
434        let held = self.files.get(path).cloned().unwrap_or_default();
435        crate::instantiate::sole(
436            path,
437            held.into_iter()
438                .map(|id| (self.name(id).to_string(), id))
439                .collect(),
440        )
441    }
442
443    /// Every `Origin` a term carries was stamped here.
444    pub fn locate(&self, origin: Origin) -> Located {
445        let held = self.origins.get(origin.token() as usize).copied().flatten();
446        held.and_then(|(site, span)| {
447            let (name, _) = self.sites[site as usize].as_ref()?;
448            Some(Located::at(name.as_str(), span))
449        })
450        .unwrap_or_default()
451    }
452
453    pub(crate) fn mark(&mut self, node: &str, span: Option<sva_ast::ByteSpan>) -> Origin {
454        let site = match self.site_ids.get(node) {
455            Some(&site) => site,
456            None => {
457                let held = Some((node.to_string(), 0));
458                let site = match self.free_sites.pop() {
459                    Some(site) => {
460                        self.sites[site as usize] = held;
461                        site
462                    }
463                    None => {
464                        self.sites.push(held);
465                        (self.sites.len() - 1) as u32
466                    }
467                };
468                self.site_ids.insert(node.to_string(), site);
469                site
470            }
471        };
472        self.sites[site as usize].as_mut().expect("a site").1 += 1;
473        let token = match self.free_origins.pop() {
474            Some(token) => {
475                self.origins[token as usize] = Some((site, span));
476                token
477            }
478            None => {
479                self.origins.push(Some((site, span)));
480                (self.origins.len() - 1) as u32
481            }
482        };
483        let unit = self.unit();
484        self.draft.journal.push(Entry::Origin(token, unit));
485        Origin::new(token)
486    }
487
488    pub(crate) fn seed(&mut self, path: &str, held: Held, grid: Grid) -> NodeId {
489        if let Some(id) = self.id(path) {
490            return id;
491        }
492        let name = self.begin(path, grid);
493        let id = self.push(
494            Node {
495                name,
496                ty: Ty {
497                    dual: held.is_closed_form(),
498                    ..Ty::discrete(held, Codomain::Real)
499                },
500                var: Var::T,
501                value: Value::Op {
502                    name: "loop".to_string(),
503                    args: Vec::new(),
504                },
505                grid,
506            },
507            Some(path),
508        );
509        self.end();
510        self.pending.insert(id);
511        self.draft.journal.push(Entry::Pending(id));
512        id
513    }
514
515    pub(crate) fn pending(&self, id: NodeId) -> bool {
516        self.pending.contains(&id)
517    }
518
519    /// Settles a seed this draft made.
520    pub(crate) fn settle(&mut self, id: NodeId, node: Node) {
521        self.place(id, Some(node));
522        self.pending.remove(&id);
523        self.forget([id]);
524    }
525
526    pub(crate) fn folds(&self) -> &folds::Folds {
527        &self.folds
528    }
529
530    pub(crate) fn alias(&mut self, path: &str, id: NodeId) {
531        let old = self.draft.by_path.insert(path.to_string(), id);
532        self.draft.journal.push(Entry::Path(path.to_string(), old));
533    }
534
535    pub(crate) fn push(&mut self, node: Node, path: Option<&str>) -> NodeId {
536        let id = match self.free.pop() {
537            Some(at) => NodeId(at),
538            None => {
539                self.nodes.push(None);
540                NodeId((self.nodes.len() - 1) as u32)
541            }
542        };
543        self.place(id, Some(node));
544        let unit = self.unit();
545        self.draft.journal.push(Entry::Node(id, unit));
546        if let Some(path) = path {
547            self.alias(path, id);
548        }
549        id
550    }
551
552    /// What is made until `end` is `path`'s, on `grid`.
553    /// The name all it makes until `end` share.
554    pub(crate) fn begin(&mut self, path: &str, grid: Grid) -> Arc<str> {
555        let name: Arc<str> = path.into();
556        self.making.push((Arc::clone(&name), grid));
557        name
558    }
559
560    pub(crate) fn end(&mut self) {
561        self.making.pop();
562    }
563
564    fn unit(&self) -> (Arc<str>, Grid) {
565        self.making
566            .last()
567            .cloned()
568            .unwrap_or_else(|| (Arc::from(""), Grid::of(1)))
569    }
570
571    pub(crate) fn infer_closed_form(&self, form: &ClosedForm) -> Result<Ty, EngineError> {
572        let inferred = crate::refs::read_through(self, |through| {
573            infer(form, &TypedNodesEnv(&self.nodes, through))
574        });
575        inferred.map_err(|r| {
576            EngineError::of_closed_form(
577                &r,
578                self.locate(r.origin),
579                "write the subterm inside sample(...) to leave A deliberately",
580            )
581        })
582    }
583}
584
585struct TypedNodesEnv<'a>(&'a [Option<Node>], &'a dyn sva_formula::Reads);
586
587impl Env for TypedNodesEnv<'_> {
588    fn node(&self, id: NodeId) -> Ty {
589        self.0[id.0 as usize]
590            .as_ref()
591            .expect("a node the typing holds")
592            .ty
593    }
594
595    /// Substituted per instance before a closed form reaches sva-formula, so no term holds one.
596    fn param(&self, _: ParamId) -> Ty {
597        Ty::form(Var::T, true, Codomain::Real)
598    }
599
600    fn reads(&self) -> &dyn sva_formula::Reads {
601        self.1
602    }
603}
604
605/// Dependencies first, so a ref reads a type already decided.
606pub fn infer_all(inst: &Instances, order: &Order) -> Result<Typing, EngineError> {
607    let mut typing = Typing::default();
608    typing.lower(inst, &order.groups, &|_| ())?;
609    typing.commit(inst);
610    Ok(typing)
611}
612
613impl Typing {
614    /// Lowers `groups`, dependencies first, beside what is held, each path hidden first; how
615    /// many paths are lowered is told to `told` group by group.
616    pub(crate) fn lower(
617        &mut self,
618        inst: &Instances,
619        groups: &[Vec<String>],
620        told: &dyn Fn(usize),
621    ) -> Result<(), EngineError> {
622        self.lowered.clear();
623        self.hide(groups.iter().flatten().cloned());
624        let mut typed = 0;
625        for group in groups {
626            typed += group.len();
627            match crate::schedule::is_loop(inst, group) {
628                true => settle_loop(self, inst, group)?,
629                false => {
630                    for path in group {
631                        lower::node(path, inst, self)?;
632                    }
633                }
634            }
635            told(typed);
636        }
637        Ok(())
638    }
639}
640
641const SEEDS: [Held; 2] = [Held::Form(Var::T), Held::Sampled];
642
643/// A group's members type together, so the seed a pass starts from is a guess the pass
644/// either reaches again or replaces with what its members held.
645fn settle_loop(typing: &mut Typing, inst: &Instances, group: &[String]) -> Result<(), EngineError> {
646    let mut seed = SEEDS[0];
647    let mut refusal = None;
648    for _ in 0..=SEEDS.len() {
649        let mark = typing.checkpoint();
650        for path in group {
651            typing.seed(path, seed, inst.grid());
652        }
653        let walked = group
654            .iter()
655            .try_for_each(|path| lower::node(path, inst, typing).map(|_| ()));
656        match walked {
657            Ok(()) => match held_by(typing, group) {
658                reached if reached == seed => return Ok(()),
659                reached => seed = reached,
660            },
661            Err(e) => {
662                refusal = Some(e);
663                seed = elsewhere(seed);
664            }
665        }
666        typing.rollback(mark);
667    }
668    Err(refusal.unwrap_or_else(|| mixed(group)))
669}
670
671/// The seed a refused pass did not try.
672fn elsewhere(seed: Held) -> Held {
673    match seed {
674        Held::Sampled => Held::Form(Var::T),
675        _ => Held::Sampled,
676    }
677}
678
679/// One representation for the whole group: samples where any member reached them.
680fn held_by(typing: &Typing, group: &[String]) -> Held {
681    let sampled = group
682        .iter()
683        .filter_map(|path| typing.id(path))
684        .any(|id| !typing.ty(id).is_closed_form());
685    match sampled {
686        true => Held::Sampled,
687        false => Held::Form(Var::T),
688    }
689}
690
691/// A group that settles in no representation holds both.
692fn mixed(group: &[String]) -> EngineError {
693    let at = group.first().map_or("", String::as_str);
694    EngineError::refused(Diagnostic {
695        code: "type.samples_in_closed_form".to_string(),
696        message: format!(
697            "the loop over `{}` holds a closed form and samples at once.",
698            group.join("`, `")
699        ),
700        location: Located::at(at, None),
701        help: "write sample(...) on the members that are closed forms, so the whole loop runs \
702               on the grid"
703            .to_string(),
704    })
705}