Skip to main content

nichlink_run_method/runtime/trace/edges/
edges.rs

1//! Observed value edges and provenance queries.
2//! 已观测值边与来源查询。
3
4use std::collections::{BTreeSet, VecDeque};
5use std::fmt::Write as _;
6
7use super::*;
8
9/// One value transformation observed by the trace.
10/// 追踪中观察到的一次值变换。
11///
12/// `source` is optional only for adapters that cannot provide a callsite;
13/// built-in transformation APIs always fill it.
14/// `source` 仅在外部适配器无法提供调用点时为空;内置变换 API 总会填充它。
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct DataEdge {
17    /// The source local id this edge leaves.
18    /// 该边离开的源局部值 id。
19    pub from: u64,
20    /// The destination local id this edge enters.
21    /// 该边进入的目标局部值 id。
22    pub to: u64,
23    /// The transformation kind, for example `transform` or `used by f::p`.
24    /// 变换种类,例如 `transform` 或 `used by f::p`。
25    pub label: String,
26    /// The callsite that produced the edge, when one is known.
27    /// 产生该边的调用点(若可知)。
28    pub source: Option<SourceLocation>,
29}
30
31/// One end of a data edge together with the local reached through it.
32/// 数据边的一端,以及经该边到达的局部值。
33#[derive(Clone, Copy, Debug)]
34pub struct DataHop<'a> {
35    /// The edge being traversed.
36    /// 正在遍历的边。
37    pub edge: &'a DataEdge,
38    /// The local at this end, `None` when it was not recorded.
39    /// 该端的局部值;未记录时为 `None`。
40    pub value: Option<&'a LocalValue>,
41}
42
43impl CallTrace {
44    /// Record `input` transformed into a new binding; returns the new local.
45    /// 记录 `input` 变换出的新绑定,返回新局部值。
46    ///
47    /// The callsite is taken from the caller; in `Off` mode nothing is recorded
48    /// and `LocalId(0)` is returned.
49    /// 调用点取自调用方;`Off` 模式下不记录任何内容并返回 `LocalId(0)`。
50    #[track_caller]
51    pub fn transform(
52        &mut self,
53        input: LocalId,
54        name: impl Into<String>,
55        type_name: impl Into<String>,
56        value: impl std::fmt::Display,
57    ) -> LocalId {
58        let caller = std::panic::Location::caller();
59        self.transform_at_callsite(
60            input,
61            name,
62            type_name,
63            value.to_string(),
64            caller.file(),
65            caller.line(),
66            caller.column(),
67        )
68    }
69
70    /// `transform` with an explicit callsite for adapters.
71    /// 带显式调用点的 `transform`,供适配器使用。
72    #[allow(clippy::too_many_arguments)]
73    pub fn transform_at_callsite(
74        &mut self,
75        input: LocalId,
76        name: impl Into<String>,
77        type_name: impl Into<String>,
78        value: impl Into<String>,
79        file: &'static str,
80        line: u32,
81        column: u32,
82    ) -> LocalId {
83        if matches!(self.mode, TraceMode::Off) {
84            return LocalId(0);
85        }
86        let source = self.callsite_source(file, line, column);
87        let output = self.local_at(name, type_name, value, LocalKind::Binding, source);
88        self.push_edge(DataEdge {
89            from: input.0,
90            to: output.0,
91            label: "transform".to_owned(),
92            source: Some(source),
93        });
94        output
95    }
96
97    /// Record `input` as returned by the current call; returns the returned local.
98    /// 记录当前调用返回 `input`,返回代表返回值的局部值。
99    #[track_caller]
100    pub fn return_value(
101        &mut self,
102        input: LocalId,
103        name: impl Into<String>,
104        type_name: impl Into<String>,
105        value: impl std::fmt::Display,
106    ) -> LocalId {
107        let caller = std::panic::Location::caller();
108        self.return_at_callsite(
109            input,
110            name,
111            type_name,
112            value.to_string(),
113            caller.file(),
114            caller.line(),
115            caller.column(),
116        )
117    }
118
119    /// `return_value` with an explicit callsite for adapters.
120    /// 带显式调用点的 `return_value`,供适配器使用。
121    #[allow(clippy::too_many_arguments)]
122    pub fn return_at_callsite(
123        &mut self,
124        input: LocalId,
125        name: impl Into<String>,
126        type_name: impl Into<String>,
127        value: impl Into<String>,
128        file: &'static str,
129        line: u32,
130        column: u32,
131    ) -> LocalId {
132        if matches!(self.mode, TraceMode::Off) {
133            return LocalId(0);
134        }
135        let source = self.callsite_source(file, line, column);
136        let output = self.local_at(name, type_name, value, LocalKind::Output, source);
137        self.push_edge(DataEdge {
138            from: input.0,
139            to: output.0,
140            label: "return".to_owned(),
141            source: Some(source),
142        });
143        output
144    }
145
146    /// Record `input` consumed as `parameter` of `function`.
147    /// 记录 `input` 作为 `function` 的 `parameter` 被消费。
148    ///
149    /// Returns the consumer local, which is what later queries see as the
150    /// downstream end of this edge.
151    /// 返回消费者局部值,后续查询会把它当作该边的下游端点。
152    #[track_caller]
153    pub fn consume(
154        &mut self,
155        input: LocalId,
156        function: impl Into<String>,
157        parameter: impl Into<String>,
158    ) -> LocalId {
159        let caller = std::panic::Location::caller();
160        self.consume_at_callsite(
161            function,
162            parameter,
163            input,
164            caller.file(),
165            caller.line(),
166            caller.column(),
167        )
168    }
169
170    /// `consume` with an explicit callsite for adapters.
171    /// 带显式调用点的 `consume`,供适配器使用。
172    pub fn consume_at_callsite(
173        &mut self,
174        function: impl Into<String>,
175        parameter: impl Into<String>,
176        input: LocalId,
177        file: &'static str,
178        line: u32,
179        column: u32,
180    ) -> LocalId {
181        if matches!(self.mode, TraceMode::Off) {
182            return LocalId(0);
183        }
184        let source = self.callsite_source(file, line, column);
185        let function = function.into();
186        let parameter = parameter.into();
187        let output = self.local_at(
188            parameter.clone(),
189            "consumer",
190            format!("{function}::{parameter}"),
191            LocalKind::Consumer,
192            source,
193        );
194        self.push_edge(DataEdge {
195            from: input.0,
196            to: output.0,
197            label: format!("used by {function}::{parameter}"),
198            source: Some(source),
199        });
200        output
201    }
202
203    /// All recorded value edges, in insertion order.
204    /// 按写入顺序返回全部已记录的值边。
205    pub fn data_edges(&self) -> &[DataEdge] {
206        &self.edges
207    }
208
209    fn push_edge(&mut self, edge: DataEdge) {
210        let index = self.edges.len();
211        self.outgoing_index
212            .entry(edge.from)
213            .or_default()
214            .push(index);
215        self.incoming_index.entry(edge.to).or_default().push(index);
216        self.edges.push(edge);
217    }
218
219    /// Every local `id` transitively depends on, including itself, producers first.
220    /// `id` 传递依赖的全部局部值(含自身),生产者在前。
221    pub fn provenance(&self, id: LocalId) -> Vec<&LocalValue> {
222        let mut result = Vec::new();
223        self.collect_upstream(id.0, &mut BTreeSet::new(), &mut result);
224        result
225    }
226
227    /// Edges that consume `id` directly, without following them onward.
228    /// 直接消费 `id` 的边,不继续向后追踪。
229    pub fn consumers(&self, id: LocalId) -> Vec<&DataEdge> {
230        self.outgoing_index
231            .get(&id.0)
232            .into_iter()
233            .flatten()
234            .filter_map(|index| self.edges.get(*index))
235            .collect()
236    }
237
238    /// Direct consumers of `id`, each paired with the local it produced.
239    /// `id` 的直接消费者,各自与它产出的局部值配对。
240    pub fn outgoing(&self, id: LocalId) -> Vec<DataHop<'_>> {
241        self.consumers(id)
242            .into_iter()
243            .map(|edge| DataHop {
244                edge,
245                value: self.find_local(LocalId(edge.to)),
246            })
247            .collect()
248    }
249
250    /// Direct producers of `id`, each paired with the local it came from.
251    /// `id` 的直接生产者,各自与它来自的局部值配对。
252    pub fn incoming(&self, id: LocalId) -> Vec<DataHop<'_>> {
253        self.incoming_index
254            .get(&id.0)
255            .into_iter()
256            .flatten()
257            .filter_map(|index| self.edges.get(*index))
258            .map(|edge| DataHop {
259                edge,
260                value: self.find_local(LocalId(edge.from)),
261            })
262            .collect()
263    }
264
265    /// Every local reachable from `id`, nearest consumers first, de-duplicated.
266    /// 从 `id` 可到达的全部局部值,最近的消费者在前,且不重复。
267    pub fn downstream(&self, id: LocalId) -> Vec<&LocalValue> {
268        let mut result = Vec::new();
269        let mut queue = VecDeque::from([id.0]);
270        let mut visited = BTreeSet::from([id.0]);
271        while let Some(current) = queue.pop_front() {
272            for index in self.outgoing_index.get(&current).into_iter().flatten() {
273                let Some(edge) = self.edges.get(*index) else {
274                    continue;
275                };
276                if !visited.insert(edge.to) {
277                    continue;
278                }
279                if let Some(local) = self.find_local(LocalId(edge.to)) {
280                    result.push(local);
281                }
282                queue.push_back(edge.to);
283            }
284        }
285        result
286    }
287
288    /// Render `id` and its upstream/downstream neighborhood as text.
289    /// 以文本渲染 `id` 及其上下游邻域。
290    ///
291    /// An unknown id renders a single `local <id> not found` line instead of
292    /// panicking.
293    /// 未知 id 只渲染一行 `local <id> not found`,不会 panic。
294    pub fn render_provenance(&self, id: LocalId) -> String {
295        let mut output = String::new();
296        let Some(selected) = self.find_local(id) else {
297            return format!("local {} not found", id.0);
298        };
299        writeln!(
300            output,
301            "value {} {} = {} ({}) [{}] @ {}",
302            selected.id,
303            selected.name,
304            selected.value,
305            selected.type_name,
306            selected.observation.label(),
307            selected.source
308        )
309        .unwrap();
310        output.push_str("upstream:\n");
311        self.render_upstream(id.0, 1, &mut BTreeSet::new(), &mut output);
312        output.push_str("downstream:\n");
313        self.render_downstream(id.0, 1, &mut BTreeSet::new(), &mut output);
314        output
315    }
316
317    fn render_upstream(
318        &self,
319        id: u64,
320        depth: usize,
321        visited: &mut BTreeSet<u64>,
322        output: &mut String,
323    ) {
324        if !visited.insert(id) {
325            return;
326        }
327        for hop in self.incoming(LocalId(id)) {
328            let source = hop
329                .value
330                .map(|local| format!("{} {} = {}", local.id, local.name, local.value))
331                .unwrap_or_else(|| format!("local {}", hop.edge.from));
332            writeln!(
333                output,
334                "{} `-- {source} [{}]",
335                "  ".repeat(depth),
336                hop.edge.label
337            )
338            .unwrap();
339            self.render_upstream(hop.edge.from, depth + 1, visited, output);
340        }
341    }
342
343    fn render_downstream(
344        &self,
345        id: u64,
346        depth: usize,
347        visited: &mut BTreeSet<u64>,
348        output: &mut String,
349    ) {
350        if !visited.insert(id) {
351            return;
352        }
353        for hop in self.outgoing(LocalId(id)) {
354            let target = hop
355                .value
356                .map(|local| format!("{} {} = {}", local.id, local.name, local.value))
357                .unwrap_or_else(|| format!("local {}", hop.edge.to));
358            writeln!(
359                output,
360                "{} `-- {target} [{}]",
361                "  ".repeat(depth),
362                hop.edge.label
363            )
364            .unwrap();
365            self.render_downstream(hop.edge.to, depth + 1, visited, output);
366        }
367    }
368
369    fn collect_upstream<'a>(
370        &'a self,
371        id: u64,
372        visited: &mut BTreeSet<u64>,
373        result: &mut Vec<&'a LocalValue>,
374    ) {
375        if !visited.insert(id) {
376            return;
377        }
378        for index in self.incoming_index.get(&id).into_iter().flatten() {
379            if let Some(edge) = self.edges.get(*index) {
380                self.collect_upstream(edge.from, visited, result);
381            }
382        }
383        if let Some(local) = self.find_local(LocalId(id)) {
384            result.push(local);
385        }
386    }
387
388    /// Render every recorded local and edge as one text block.
389    /// 把全部已记录局部值与边渲染成一段文本。
390    pub fn render_data_flow(&self) -> String {
391        let mut output = String::new();
392        for local in &self.locals {
393            let frame = local
394                .frame_id
395                .and_then(|id| self.frame(id))
396                .map(|frame| frame.call.function)
397                .unwrap_or("<outside-call>");
398            writeln!(
399                output,
400                "{} [{}|{}] {}#{} depth={}: {} = {} @ {} ({})",
401                local.id,
402                local.kind.label(),
403                local.observation.label(),
404                frame,
405                local.frame_id.unwrap_or(0),
406                local.frame_id.map_or(0, |id| self.frame_depth(id)),
407                local.name,
408                local.value,
409                local.source,
410                local.type_name
411            )
412            .unwrap();
413        }
414        if !self.edges.is_empty() {
415            output.push_str("edges:\n");
416            for edge in &self.edges {
417                let source = edge
418                    .source
419                    .map_or_else(String::new, |location| format!(" @ {location}"));
420                writeln!(
421                    output,
422                    "  {} -> {} ({}){}",
423                    edge.from, edge.to, edge.label, source
424                )
425                .unwrap();
426            }
427        }
428        output
429    }
430}