1use 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 pub ty: String,
22 pub discrete: Option<String>,
24 pub entry: Vec<String>,
25 pub file: Option<String>,
26 pub cycle: Option<Vec<String>>,
28 pub down: Vec<String>,
29 pub up: Vec<Up>,
30}
31
32pub fn trace(graph: &Graph, roots: &[String], target: &str) -> Result<Traced, EngineError> {
34 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 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 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
107fn 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
115fn 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::Cast(crate::cast::Cast::Sample, source) => {
138 Some(format!("sample({})", typing.name(*source)))
139 }
140 crate::typing::Value::Cast(cast, source) => {
141 under(vec![*source], open).or_else(|| Some(cast.name().to_string()))
142 }
143 crate::typing::Value::Read { source, at, .. } => under(vec![*source], open).or_else(|| {
144 Some(match at {
145 crate::typing::When::At(time) => {
146 format!(
147 "a read at {}*t{:+}s",
148 time.scale.to_f64(),
149 time.shift.to_f64()
150 )
151 }
152 crate::typing::When::Moving(_) => "a read at a moving time".to_string(),
153 crate::typing::When::Index(_) | crate::typing::When::Step(_) => {
154 "a read by sample index".to_string()
155 }
156 })
157 }),
158 crate::typing::Value::Op { args, .. } => under(args.clone(), open),
159 crate::typing::Value::Filter { x, .. } => under(vec![*x], open),
160 crate::typing::Value::ClosedForm(_) => None,
161 }
162}
163
164fn readers(
165 inst: &Instances,
166 typing: &crate::typing::Typing,
167 scheduled: &refs::Order,
168 order: &[String],
169 node: &str,
170) -> Vec<Up> {
171 let mut above: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
172 for path in order {
173 for target in scheduled.deps(path) {
174 if target != path {
175 above
176 .entry(target.as_str())
177 .or_default()
178 .push(path.as_str());
179 }
180 }
181 }
182 let mut seen: BTreeSet<&str> = BTreeSet::from([node]);
183 let mut work: Vec<&str> = vec![node];
184 while let Some(at) = work.pop() {
185 for reader in above.get(at).map(Vec::as_slice).unwrap_or(&[]) {
186 if seen.insert(reader) {
187 work.push(reader);
188 }
189 }
190 }
191 seen.into_iter()
192 .filter(|p| *p != node)
193 .map(|p| Up {
194 node: p.to_string(),
195 expr: expr_of(inst, p),
196 ty: spelled(typing, p),
197 })
198 .collect()
199}