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.
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)?,
42        Some(held) => match crate::instantiate::from_roots(graph, held) {
43            Ok(found) => found,
44            // Own terms a caller must complete are none: the call sites' are what is left.
45            Err(EngineError::Binding {
46                fault: BindingFault::Unbound(..),
47                ..
48            }) => crate::instantiate::from_roots(graph, roots)?,
49            Err(other) => return Err(other),
50        },
51    };
52    let node = inst.instance_of(target)?;
53
54    // The seeded root is no entry point; every other one is.
55    let named = match entries.len() > roots.len() {
56        true => entries[..entries.len() - 1].to_vec(),
57        false => entries.clone(),
58    };
59    let scheduled = refs::schedule_from(&inst, &entries)?;
60    let typing = crate::typing::infer_all(&inst, &scheduled)?;
61    let groups = scheduled.groups.clone();
62    let order: Vec<String> = groups.concat();
63
64    let cycle = groups
65        .iter()
66        .filter(|g| scheduled.is_loop(g))
67        .find(|g| g.len() > 1 && g.contains(&node))
68        .map(|g| sorted(g.clone()));
69
70    let mut down = scheduled.deps(&node).to_vec();
71    if inst.reads_self(&node) {
72        down.push(node.clone());
73    }
74
75    Ok(Traced {
76        expr: expr_of(&inst, &node),
77        ty: spelled(&typing, &node),
78        discrete: typing
79            .id(&node)
80            .and_then(|id| sampled_leaf(&typing, id, &mut Vec::new())),
81        entry: sorted(named),
82        file: inst
83            .origin(&node)
84            .filter(|f| *f != node)
85            .map(str::to_string),
86        cycle,
87        up: readers(&inst, &typing, &scheduled, &order, &node),
88        down: sorted(down),
89        node,
90    })
91}
92
93fn sorted(mut names: Vec<String>) -> Vec<String> {
94    names.sort();
95    names.dedup();
96    names
97}
98
99fn expr_of(inst: &Instances, path: &str) -> String {
100    match inst.at(path) {
101        Some((e, cx)) => inst.render(e, cx),
102        None => String::new(),
103    }
104}
105
106/// The type a trace prints beside a node, with the width where the value is wide.
107fn spelled(typing: &crate::typing::Typing, path: &str) -> String {
108    match typing.id(path) {
109        Some(id) => crate::overload::describe(typing.ty(id)),
110        None => String::new(),
111    }
112}
113
114/// The leaf a sampled node owes its grid to: the one thing a type alone does not say.
115fn sampled_leaf(
116    typing: &crate::typing::Typing,
117    id: sva_formula::NodeId,
118    open: &mut Vec<sva_formula::NodeId>,
119) -> Option<String> {
120    if typing.ty(id).is_closed_form() || open.contains(&id) {
121        return None;
122    }
123    open.push(id);
124    let under = |set: Vec<sva_formula::NodeId>, open: &mut Vec<sva_formula::NodeId>| {
125        set.into_iter()
126            .find_map(|op| sampled_leaf(typing, op, open))
127    };
128    match typing.value(id) {
129        crate::typing::Value::SelfAt(_) => Some("sp, a self-reference on the grid".to_string()),
130        crate::typing::Value::Grid(_) => Some("sp, a duration on the grid".to_string()),
131        crate::typing::Value::Solver(_) => Some("a finite-difference builtin".to_string()),
132        crate::typing::Value::Cast(crate::cast::Cast::Sample, source) => {
133            Some(format!("sample({})", typing.name(*source)))
134        }
135        crate::typing::Value::Cast(cast, source) => {
136            under(vec![*source], open).or_else(|| Some(cast.name().to_string()))
137        }
138        crate::typing::Value::Read { source, at, .. } => under(vec![*source], open).or_else(|| {
139            Some(match at {
140                crate::offset::Offset::Steps(steps) => format!("a read {steps} samples back"),
141                crate::offset::Offset::Secs(secs) => format!("a read {secs} seconds back"),
142            })
143        }),
144        crate::typing::Value::Op { args, .. } => under(args.clone(), open),
145        crate::typing::Value::Filter { x, .. } => under(vec![*x], open),
146        crate::typing::Value::ClosedForm(_) => None,
147    }
148}
149
150fn readers(
151    inst: &Instances,
152    typing: &crate::typing::Typing,
153    scheduled: &refs::Order,
154    order: &[String],
155    node: &str,
156) -> Vec<Up> {
157    let mut above: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
158    for path in order {
159        for target in scheduled.deps(path) {
160            if target != path {
161                above
162                    .entry(target.as_str())
163                    .or_default()
164                    .push(path.as_str());
165            }
166        }
167    }
168    let mut seen: BTreeSet<&str> = BTreeSet::from([node]);
169    let mut work: Vec<&str> = vec![node];
170    while let Some(at) = work.pop() {
171        for reader in above.get(at).map(Vec::as_slice).unwrap_or(&[]) {
172            if seen.insert(reader) {
173                work.push(reader);
174            }
175        }
176    }
177    seen.into_iter()
178        .filter(|p| *p != node)
179        .map(|p| Up {
180            node: p.to_string(),
181            expr: expr_of(inst, p),
182            ty: spelled(typing, p),
183        })
184        .collect()
185}