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::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}