miden_debug_engine/debug/
calltrace.rs1use alloc::{string::String, vec::Vec};
2
3use super::{InvocationId, LogicalStackFrame};
4
5#[derive(Debug, Default)]
7pub struct CallTrace {
8 pub roots: Vec<CallFrameRecord>,
9}
10
11#[derive(Debug)]
13pub struct CallFrameRecord {
14 pub callee: Option<String>,
15 pub enter_clk: usize,
16 pub exit_clk: Option<usize>,
17 pub args: Vec<TracedArg>,
18 pub results: Option<Vec<u64>>,
20 pub children: Vec<CallFrameRecord>,
21}
22
23#[derive(Debug)]
25pub struct TracedArg {
26 pub index: u32,
27 pub name: String,
28 pub felt_count: Option<usize>,
30 pub values: Option<Vec<u64>>,
32}
33
34impl CallFrameRecord {
35 pub fn cycles(&self) -> Option<usize> {
37 self.exit_clk.map(|exit| exit.saturating_sub(self.enter_clk))
38 }
39}
40
41#[derive(Default)]
43pub struct CallTraceRecorder {
44 open: Vec<OpenFrame>,
46 roots: Vec<CallFrameRecord>,
48}
49
50struct OpenFrame {
51 record: CallFrameRecord,
52 caller: Option<Vec<LogicalStackFrame>>,
54 output_felt_count: Option<usize>,
56 argument_owner: Option<InvocationId>,
57 args_sealed: bool,
59}
60
61impl CallTraceRecorder {
62 pub fn enter(&mut self, clk: usize, caller: Vec<LogicalStackFrame>) {
64 if let Some(parent) = self.open.last_mut() {
65 parent.args_sealed = true;
66 }
67 self.open.push(OpenFrame {
68 record: CallFrameRecord {
69 callee: None,
70 enter_clk: clk,
71 exit_clk: None,
72 args: Vec::new(),
73 results: None,
74 children: Vec::new(),
75 },
76 caller: (!caller.is_empty()).then_some(caller),
77 output_felt_count: None,
78 argument_owner: None,
79 args_sealed: false,
80 });
81 }
82
83 pub fn needs_callee(&self) -> bool {
85 self.open.last().is_some_and(|frame| frame.caller.is_some())
86 }
87
88 pub fn stop_callee_search(&mut self) {
91 if let Some(frame) = self.open.last_mut() {
92 frame.caller = None;
93 }
94 }
95
96 pub fn pending_callee<'a>(
99 &self,
100 frames: &'a [LogicalStackFrame],
101 ) -> Option<&'a LogicalStackFrame> {
102 let caller = self.open.last()?.caller.as_ref()?;
103 if !caller
104 .iter()
105 .zip(frames)
106 .all(|(caller, current)| caller.is_same_source_frame(current))
107 {
108 return None;
109 }
110 frames.get(caller.len())
111 }
112
113 pub fn observe_callee(
115 &mut self,
116 name: &str,
117 output_felt_count: Option<usize>,
118 argument_owner: Option<InvocationId>,
119 ) {
120 if let Some(frame) = self.open.last_mut()
121 && frame.record.callee.is_none()
122 {
123 frame.caller = None;
124 frame.record.callee = Some(String::from(name));
125 frame.output_felt_count = output_felt_count;
126 frame.argument_owner = argument_owner;
127 }
128 }
129
130 pub fn argument_owner(&self) -> Option<InvocationId> {
131 self.open.last()?.argument_owner
132 }
133
134 pub fn accepting_args(&self) -> Option<usize> {
138 self.open
139 .last()
140 .filter(|frame| !frame.args_sealed)
141 .map(|frame| frame.record.enter_clk)
142 }
143
144 pub fn has_arg(&self, index: u32) -> bool {
149 self.open
150 .last()
151 .is_some_and(|frame| frame.record.args.iter().any(|arg| arg.index == index))
152 }
153
154 pub fn observe_arg(
164 &mut self,
165 index: u32,
166 name: &str,
167 felt_count: Option<usize>,
168 values: Option<Vec<u64>>,
169 ) {
170 let Some(frame) = self.open.last_mut() else {
171 return;
172 };
173 if frame.args_sealed || frame.record.args.iter().any(|arg| arg.index == index) {
174 return;
175 }
176 frame.record.args.push(TracedArg {
177 index,
178 name: String::from(name),
179 felt_count,
180 values,
181 });
182 }
183
184 pub fn exit(&mut self, clk: usize, stack: &[u64]) {
193 let Some(frame) = self.open.pop() else {
194 return;
195 };
196 let mut record = frame.record;
197 record.exit_clk = Some(clk);
198
199 record.results = frame
201 .output_felt_count
202 .and_then(|count| (stack.len() >= count).then(|| stack[..count].to_vec()));
203
204 self.attach(record);
205 }
206
207 pub fn finish(mut self) -> CallTrace {
209 while let Some(frame) = self.open.pop() {
210 self.attach(frame.record);
211 }
212 CallTrace { roots: self.roots }
213 }
214
215 fn attach(&mut self, mut frame: CallFrameRecord) {
217 frame.args.sort_by_key(|arg| arg.index);
218 match self.open.last_mut() {
219 Some(parent) => parent.record.children.push(frame),
220 None => self.roots.push(frame),
221 }
222 }
223}
224
225#[cfg(test)]
226mod tests;