Skip to main content

sva_engine/
trace.rs

1// Concern: states one node's position in a composition — what it is built from and everyone who reads it | Non-concern: measuring it, which needs audio | IO: (&Graph, seeds, target) -> Traced
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use sva_ast::Graph;
6
7use crate::error::{BindingFault, EngineError};
8use crate::instantiate::Instances;
9use crate::schedule::{self as refs};
10
11pub struct Up {
12    pub node: String,
13    pub expr: String,
14    pub ty: String,
15}
16
17pub struct Traced {
18    pub node: String,
19    pub expr: String,
20    /// The node's own type, spelled as FORMAT 3.1 names it, with its width.
21    pub ty: String,
22    /// What made a node discrete: `sp`, a finite-difference builtin, or a `sample(...)`.
23    pub discrete: Option<String>,
24    pub entry: Vec<String>,
25    pub file: Option<String>,
26    /// The members of the feedback loop this node sits in, if any.
27    pub cycle: Option<Vec<String>>,
28    pub down: Vec<String>,
29    pub up: Vec<Up>,
30}
31
32/// No audio: structure alone, over the instances every root reaches, under the default profile.
33pub fn trace(graph: &Graph, roots: &[String], target: &str) -> Result<Traced, EngineError> {
34    // A bare file name is the file on its own terms, as `render` and `lint` read it.
35    let seeded = (graph.defines(target) && !roots.iter().any(|r| r == target)).then(|| {
36        let mut held = roots.to_vec();
37        held.push(target.to_string());
38        held
39    });
40    let (inst, entries) = match &seeded {
41        None => crate::instantiate::from_roots(graph, roots, crate::DEFAULT_SAMPLE_RATE)?,
42        Some(held) => match crate::instantiate::from_roots(graph, held, crate::DEFAULT_SAMPLE_RATE)
43        {
44            Ok(found) => found,
45            // Own terms a caller must complete are none: the call sites' are what is left.
46            Err(EngineError::Binding {
47                fault: BindingFault::Unbound(..),
48                ..
49            }) => crate::instantiate::from_roots(graph, roots, crate::DEFAULT_SAMPLE_RATE)?,
50            Err(other) => return Err(other),
51        },
52    };
53    let node = inst.instance_of(target)?;
54
55    // The seeded root is no entry point; every other one is.
56    let named = match entries.len() > roots.len() {
57        true => entries[..entries.len() - 1].to_vec(),
58        false => entries.clone(),
59    };
60    let scheduled = refs::schedule_from(&inst, &entries)?;
61    let typing = crate::typing::infer_all(&inst, &scheduled)?;
62    let groups = scheduled.groups.clone();
63    let order: Vec<String> = groups.concat();
64
65    let cycle = groups
66        .iter()
67        .filter(|g| scheduled.is_loop(g))
68        .find(|g| g.len() > 1 && g.contains(&node))
69        .map(|g| sorted(g.clone()));
70
71    let mut down = scheduled.deps(&node).to_vec();
72    if inst.reads_self(&node) {
73        down.push(node.clone());
74    }
75
76    Ok(Traced {
77        expr: expr_of(&inst, &node),
78        ty: spelled(&typing, &node),
79        discrete: typing
80            .id(&node)
81            .and_then(|id| sampled_leaf(&typing, id, &mut Vec::new())),
82        entry: sorted(named),
83        file: inst
84            .origin(&node)
85            .filter(|f| *f != node)
86            .map(str::to_string),
87        cycle,
88        up: readers(&inst, &typing, &scheduled, &order, &node),
89        down: sorted(down),
90        node,
91    })
92}
93
94fn sorted(mut names: Vec<String>) -> Vec<String> {
95    names.sort();
96    names.dedup();
97    names
98}
99
100fn expr_of(inst: &Instances, path: &str) -> String {
101    match inst.at(path) {
102        Some((e, cx)) => inst.render(e, cx),
103        None => String::new(),
104    }
105}
106
107/// The type a trace prints beside a node, with the width where the value is wide.
108fn spelled(typing: &crate::typing::Typing, path: &str) -> String {
109    match typing.id(path) {
110        Some(id) => crate::overload::describe(typing.ty(id)),
111        None => String::new(),
112    }
113}
114
115/// The leaf a sampled node owes its grid to: the one thing a type alone does not say.
116fn sampled_leaf(
117    typing: &crate::typing::Typing,
118    id: sva_formula::NodeId,
119    open: &mut Vec<sva_formula::NodeId>,
120) -> Option<String> {
121    if typing.ty(id).is_closed_form() || open.contains(&id) {
122        return None;
123    }
124    open.push(id);
125    let under = |set: Vec<sva_formula::NodeId>, open: &mut Vec<sva_formula::NodeId>| {
126        set.into_iter()
127            .find_map(|op| sampled_leaf(typing, op, open))
128    };
129    match typing.value(id) {
130        crate::typing::Value::SelfAt { .. } => {
131            Some("self, a loop stepped at the rate in use".to_string())
132        }
133        crate::typing::Value::Noise(_) => {
134            Some("rand, noise drawn per step of the rate in use".to_string())
135        }
136        crate::typing::Value::Solver { .. } => Some("a finite-difference builtin".to_string()),
137        crate::typing::Value::Stored(_) => Some("samples the store answered".to_string()),
138        crate::typing::Value::Cast(crate::cast::Cast::Sample, source) => {
139            Some(format!("sample({})", typing.name(*source)))
140        }
141        crate::typing::Value::Cast(cast, source) => {
142            under(vec![*source], open).or_else(|| Some(cast.name().to_string()))
143        }
144        crate::typing::Value::Read { source, at, .. } => under(vec![*source], open).or_else(|| {
145            Some(match at {
146                crate::typing::When::At(time) => {
147                    format!(
148                        "a read at {}*t{:+}s",
149                        time.scale.to_f64(),
150                        time.shift.to_f64()
151                    )
152                }
153                crate::typing::When::Moving(_) => "a read at a moving time".to_string(),
154                crate::typing::When::Index(_) | crate::typing::When::Step(_) => {
155                    "a read by sample index".to_string()
156                }
157            })
158        }),
159        crate::typing::Value::Op { args, .. } => under(args.clone(), open),
160        crate::typing::Value::Filter { x, .. } => under(vec![*x], open),
161        crate::typing::Value::ClosedForm(_) => None,
162    }
163}
164
165fn readers(
166    inst: &Instances,
167    typing: &crate::typing::Typing,
168    scheduled: &refs::Order,
169    order: &[String],
170    node: &str,
171) -> Vec<Up> {
172    let mut above: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
173    for path in order {
174        for target in scheduled.deps(path) {
175            if target != path {
176                above
177                    .entry(target.as_str())
178                    .or_default()
179                    .push(path.as_str());
180            }
181        }
182    }
183    let mut seen: BTreeSet<&str> = BTreeSet::from([node]);
184    let mut work: Vec<&str> = vec![node];
185    while let Some(at) = work.pop() {
186        for reader in above.get(at).map(Vec::as_slice).unwrap_or(&[]) {
187            if seen.insert(reader) {
188                work.push(reader);
189            }
190        }
191    }
192    seen.into_iter()
193        .filter(|p| *p != node)
194        .map(|p| Up {
195            node: p.to_string(),
196            expr: expr_of(inst, p),
197            ty: spelled(typing, p),
198        })
199        .collect()
200}