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