Skip to main content

sva_engine/
schedule.rs

1// Concern: orders nodes dependencies-first and picks which a reading materializes | Non-concern: what a held node contains (render.rs), reading one (refs.rs) | IO: (Instances, asks) -> Schedule
2
3use std::collections::{BTreeMap, BTreeSet, HashMap};
4
5use sva_ast::{Arg, Expr};
6use sva_formula::{Held, NodeId, Var};
7
8use crate::cast::Cast;
9use crate::error::EngineError;
10use crate::instantiate::{Cx, Instances, Node};
11use crate::query::Ask;
12use crate::typing::{Typing, Value, When};
13
14/// One-hop instance names. `self(...)` is a same-node read, never an edge.
15fn direct_refs(inst: &Instances, e: &Expr, cx: Cx, out: &mut Vec<String>) {
16    if inst
17        .follow(e, cx, |e2, cx2| direct_refs(inst, e2, cx2, out))
18        .is_some()
19    {
20        return;
21    }
22    match inst.node(e, cx) {
23        Node::Lit(_) | Node::Name(_) => {}
24        Node::Bin(_, l, r) => {
25            direct_refs(inst, l, cx, out);
26            direct_refs(inst, r, cx, out);
27        }
28        Node::Call { args, .. } => {
29            for a in args {
30                let (Arg::Pos(x) | Arg::Named(_, x)) = a;
31                direct_refs(inst, x, cx, out);
32            }
33        }
34        Node::Read { path, arg, .. } => {
35            out.push(path.to_string());
36            direct_refs(inst, arg, cx, out);
37        }
38        Node::Own { arg, .. } => direct_refs(inst, arg, cx, out),
39        Node::Signal { of, arg, .. } => {
40            direct_refs(inst, of.expr, inst.signal(of, cx), out);
41            direct_refs(inst, arg, cx, out);
42        }
43    }
44}
45
46/// One dependencies-first walk: the groups, what each node reads, and which are loops.
47pub struct Order {
48    pub groups: Vec<Vec<String>>,
49    deps: BTreeMap<String, Vec<String>>,
50}
51
52impl Order {
53    pub fn deps(&self, path: &str) -> &[String] {
54        self.deps.get(path).map_or(&[], Vec::as_slice)
55    }
56
57    pub(crate) fn within(&self, kept: &BTreeSet<String>) -> Order {
58        Order {
59            groups: self
60                .groups
61                .iter()
62                .filter(|group| group.iter().all(|path| kept.contains(path)))
63                .cloned()
64                .collect(),
65            deps: self.deps.clone(),
66        }
67    }
68
69    /// A group of one whose node does not ref itself is an ordinary node; anything else is a loop.
70    pub fn is_loop(&self, group: &[String]) -> bool {
71        match group {
72            [only] => self.deps(only).iter().any(|d| d == only),
73            _ => true,
74        }
75    }
76}
77
78pub fn direct_deps(inst: &Instances, path: &str) -> Result<Vec<String>, EngineError> {
79    let (e, cx) = inst
80        .at(path)
81        .ok_or_else(|| EngineError::UnknownNode(path.to_string()))?;
82    let mut out = Vec::new();
83    direct_refs(inst, e, cx, &mut out);
84    out.sort();
85    out.dedup();
86    for target in &out {
87        if !inst.holds(target) {
88            return Err(EngineError::UnknownNode(target.clone()));
89        }
90    }
91    Ok(out)
92}
93
94struct Frame {
95    node: String,
96    refs: Vec<String>,
97    idx: usize,
98}
99
100/// A node two roots reach is grouped once, whichever reached it first.
101pub fn schedule_from(inst: &Instances, roots: &[String]) -> Result<Order, EngineError> {
102    let mut walk = Walk {
103        inst,
104        deps: BTreeMap::new(),
105        index: HashMap::new(),
106        low: HashMap::new(),
107        open: Vec::new(),
108        next: 0,
109        groups: Vec::new(),
110    };
111    for root in roots {
112        walk.from(root)?;
113    }
114    Ok(Order {
115        groups: walk.groups,
116        deps: walk.deps,
117    })
118}
119
120struct Walk<'a> {
121    inst: &'a Instances<'a>,
122    deps: BTreeMap<String, Vec<String>>,
123    index: HashMap<String, usize>,
124    low: HashMap<String, usize>,
125    open: Vec<String>,
126    next: usize,
127    groups: Vec<Vec<String>>,
128}
129
130impl Walk<'_> {
131    fn from(&mut self, root: &str) -> Result<(), EngineError> {
132        if !self.inst.holds(root) {
133            return Err(EngineError::UnknownNode(root.to_string()));
134        }
135        if self.index.contains_key(root) {
136            return Ok(());
137        }
138        self.index.insert(root.to_string(), self.next);
139        self.low.insert(root.to_string(), self.next);
140        self.next += 1;
141        self.open.push(root.to_string());
142        let seed = direct_deps(self.inst, root)?;
143        self.deps.insert(root.to_string(), seed.clone());
144        let mut stack = vec![Frame {
145            node: root.to_string(),
146            refs: seed,
147            idx: 0,
148        }];
149
150        while let Some(frame) = stack.last_mut() {
151            if frame.idx < frame.refs.len() {
152                let target = frame.refs[frame.idx].clone();
153                let node = frame.node.clone();
154                frame.idx += 1;
155                match self.index.get(&target).copied() {
156                    None => {
157                        self.index.insert(target.clone(), self.next);
158                        self.low.insert(target.clone(), self.next);
159                        self.next += 1;
160                        self.open.push(target.clone());
161                        let refs = direct_deps(self.inst, &target)?;
162                        self.deps.insert(target.clone(), refs.clone());
163                        stack.push(Frame {
164                            node: target,
165                            refs,
166                            idx: 0,
167                        });
168                    }
169                    Some(at) if self.open.contains(&target) => {
170                        let mine = self.low[&node];
171                        self.low.insert(node, mine.min(at));
172                    }
173                    Some(_) => {}
174                }
175                continue;
176            }
177
178            let node = frame.node.clone();
179            let mine = self.low[&node];
180            stack.pop();
181            if let Some(parent) = stack.last() {
182                let above = self.low[&parent.node];
183                self.low.insert(parent.node.clone(), above.min(mine));
184            }
185            if mine == self.index[&node] {
186                let at = self
187                    .open
188                    .iter()
189                    .rposition(|n| *n == node)
190                    .expect("a root of its group is still open");
191                let mut group = self.open.split_off(at);
192                group.sort();
193                self.groups.push(group);
194            }
195        }
196        Ok(())
197    }
198}
199
200/// What a render has to hold, and what it can leave as a closed form.
201#[derive(Clone, Debug, Default, PartialEq)]
202pub struct Schedule {
203    /// What a reading holds for itself, the root among them.
204    pub wanted: Vec<NodeId>,
205    /// The nodes a reading asked a closed form of, none already held.
206    pub compose: Vec<NodeId>,
207}
208
209/// A closed form is held as samples only under a buffer reading, a ledger, or the root.
210pub fn plan(typing: &Typing, root: NodeId, asks: &[Ask]) -> Schedule {
211    let mut wanted: BTreeSet<NodeId> = BTreeSet::new();
212    let mut compose: Vec<NodeId> = Vec::new();
213    let audio = asks.is_empty();
214    for ask in asks {
215        let Some(id) = typing.id(&ask.node) else {
216            continue;
217        };
218        // FORMAT 14.2: `bindings` and `arguments` are structural and `flops` counts; none composes.
219        if matches!(
220            ask.representation,
221            crate::query::Representation::Bindings
222                | crate::query::Representation::Arguments
223                | crate::query::Representation::Flops
224        ) {
225            continue;
226        }
227        match ask.representation.consumes(typing.ty(id).is_closed_form()) {
228            sva_samples::Consumes::ClosedForm if !compose.contains(&id) => compose.push(id),
229            sva_samples::Consumes::ClosedForm => {}
230            _ => {
231                wanted.insert(id);
232            }
233        }
234        if let crate::query::Representation::Ledger { depth } = ask.representation {
235            attributed(typing, id, depth, &mut wanted);
236        }
237    }
238    if audio || !wanted.is_empty() {
239        wanted.insert(root);
240    }
241    compose.retain(|id| !wanted.contains(id));
242    Schedule {
243        wanted: wanted.into_iter().collect(),
244        compose,
245    }
246}
247
248/// A ledger names every ref under its target, so each is a buffer of its own.
249fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
250    // Level by level, as the reading walks: a node is held at its shortest chain's depth.
251    let mut seen = BTreeSet::from([id]);
252    let mut level = vec![id];
253    for _ in 0..depth {
254        let mut next = Vec::new();
255        while let Some(held) = level.pop() {
256            for operand in read_operands(typing, held) {
257                if !seen.insert(operand) {
258                    continue;
259                }
260                wanted.insert(operand);
261                match typing.name(operand) == typing.name(held) {
262                    true => level.push(operand),
263                    false => next.push(operand),
264                }
265            }
266        }
267        level = next;
268    }
269}
270
271/// A node reading its own output is one program, whatever its width: a component lowered on
272/// its own would read the loop at that component's width instead of the node's.
273pub(crate) fn holds_self(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>) -> bool {
274    if !seen.insert(id) {
275        return false;
276    }
277    match typing.value(id) {
278        Value::SelfAt { .. } => true,
279        Value::Cast(Cast::Sample, _) | Value::Read { .. } => false,
280        Value::Cast(_, source) => holds_self(typing, *source, seen),
281        Value::Op { args, .. } => args.iter().any(|a| holds_self(typing, *a, seen)),
282        Value::Filter {
283            x, cutoff, q, gain, ..
284        } => [x, cutoff, q, gain]
285            .into_iter()
286            .any(|operand| holds_self(typing, *operand, seen)),
287        Value::Solver { varying, .. } => varying.iter().any(|(_, a)| holds_self(typing, *a, seen)),
288        Value::ClosedForm(_) | Value::Noise(_) | Value::Stored(_) => false,
289    }
290}
291
292/// Which nodes under `id` a render has to hold before it can hold `id` itself: the buffers its
293/// program reads, an operation, filter or read under it being part of that program.
294pub(crate) fn materialized_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
295    let sampled = |set: Vec<NodeId>| -> Vec<NodeId> {
296        let mut out = Vec::new();
297        for op in set {
298            if typing.ty(op).is_closed_form() {
299                continue;
300            }
301            let inlined = matches!(
302                typing.value(op),
303                Value::Op { .. } | Value::Filter { .. } | Value::Read { .. }
304            );
305            match inlined || holds_self(typing, op, &mut BTreeSet::new()) {
306                true => out.extend(materialized_operands(typing, op)),
307                false => out.push(op),
308            }
309        }
310        out
311    };
312    match typing.value(id) {
313        Value::ClosedForm(_) | Value::Noise(_) | Value::Stored(_) => Vec::new(),
314        Value::SelfAt { at, .. } => sampled(at.moving()),
315        Value::Solver { varying, .. } => sampled(varying.iter().map(|(_, a)| *a).collect()),
316        Value::Cast(Cast::Sample, source) => vec![*source],
317        Value::Read { source, at, .. } => {
318            let mut out = match (at, anywhere(typing, *source)) {
319                (When::Moving(_) | When::Step(_), true) => Vec::new(),
320                _ => vec![*source],
321            };
322            out.extend(sampled(at.moving()));
323            out
324        }
325        Value::Cast(_, source) => sampled(vec![*source]),
326        Value::Op { args, .. } => sampled(args.clone()),
327        Value::Filter {
328            x, cutoff, q, gain, ..
329        } => sampled(vec![*x, *cutoff, *q, *gain]),
330    }
331}
332
333/// Whether a read of `id` has a value at any instant, stored or not.
334pub(crate) fn anywhere(typing: &Typing, id: NodeId) -> bool {
335    match typing.value(id) {
336        Value::Noise(_) => true,
337        Value::Cast(Cast::Sample, of) => typing.ty(*of).held == Held::Form(Var::T),
338        Value::ClosedForm(form) => form.var == Var::T,
339        _ => false,
340    }
341}
342
343/// Every ref one node reads; `materialized_operands` answers a narrower one.
344pub(crate) fn read_operands(typing: &Typing, id: NodeId) -> Vec<NodeId> {
345    match typing.value(id) {
346        Value::ClosedForm(form) => crate::refs::nodes_in(&form.body),
347        _ if typing.ty(id).is_closed_form() => {
348            let mut out = Vec::new();
349            reads_under(typing, id, &mut BTreeSet::new(), &mut out);
350            out.dedup();
351            out
352        }
353        _ => materialized_operands(typing, id),
354    }
355}
356
357/// A closed-form-typed node that holds no written one still reads refs through its operands.
358fn reads_under(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
359    if !seen.insert(id) {
360        return;
361    }
362    match typing.value(id) {
363        Value::ClosedForm(form) => out.extend(crate::refs::nodes_in(&form.body)),
364        Value::Read { source, .. } => out.push(*source),
365        Value::Cast(_, source) => read_through(typing, *source, seen, out),
366        Value::Op { args, .. } => {
367            for arg in args {
368                read_through(typing, *arg, seen, out);
369            }
370        }
371        Value::Filter {
372            x, cutoff, q, gain, ..
373        } => {
374            for operand in [x, cutoff, q, gain] {
375                read_through(typing, *operand, seen, out);
376            }
377        }
378        Value::Solver { varying, .. } => {
379            for (_, arg) in varying {
380                read_through(typing, *arg, seen, out);
381            }
382        }
383        Value::SelfAt { .. } | Value::Noise(_) | Value::Stored(_) => {}
384    }
385}
386
387/// A written form under an operand is the term read, whatever transform sits between.
388fn read_through(typing: &Typing, id: NodeId, seen: &mut BTreeSet<NodeId>, out: &mut Vec<NodeId>) {
389    let Value::ClosedForm(_) = typing.value(id) else {
390        return reads_under(typing, id, seen, out);
391    };
392    if seen.insert(id) {
393        out.push(id);
394    }
395}