1use 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
14fn 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
46pub 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 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
100pub 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#[derive(Clone, Debug, Default, PartialEq)]
202pub struct Schedule {
203 pub wanted: Vec<NodeId>,
205 pub compose: Vec<NodeId>,
207}
208
209pub 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 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
248fn attributed(typing: &Typing, id: NodeId, depth: usize, wanted: &mut BTreeSet<NodeId>) {
250 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
271pub(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
292pub(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
333pub(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
343pub(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
357fn 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
387fn 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}