miden_debug_engine/debug/stacktrace/
recovered.rs1use miden_processor::{DebugCallFrame, DebugCallFrameResolver, ResumeContext};
2
3use super::*;
4
5#[derive(Default)]
7pub struct RecoveredCallStack {
8 resolver: DebugCallFrameResolver,
9 frames: Vec<RecoveredFrame>,
10 current_frame_recovered: bool,
11 next_invocation: u64,
12}
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct InvocationId(pub(crate) u64);
17
18struct RecoveredFrame {
19 invocation: InvocationId,
20 identity: DebugCallFrame,
21 location: Option<Location>,
22 inline_frames: Vec<RecoveredInlineFrame>,
23}
24
25struct RecoveredInlineFrame {
26 display: InlineCallFrame,
27 info: Arc<PackageDebugInfo>,
28 function_idx: DebugFunctionIdx,
29}
30
31#[derive(Clone)]
36pub struct ReturnTarget {
37 invocation: Option<DebugCallFrame>,
38 event_depth: usize,
39}
40
41impl ReturnTarget {
42 pub fn is_recovered(&self) -> bool {
43 self.invocation.is_some()
44 }
45
46 pub fn has_returned(&self, stack: &RecoveredCallStack, event_depth: usize) -> bool {
47 match self.invocation.as_ref() {
48 Some(invocation) => {
49 !stack.frames.iter().any(|frame| frame.identity.is_same_frame(invocation))
50 }
51 None => event_depth < self.event_depth,
52 }
53 }
54}
55
56impl RecoveredCallStack {
57 pub fn current_invocation(&self) -> Option<InvocationId> {
58 self.frames
59 .last()
60 .filter(|_| self.current_frame_recovered)
61 .map(|frame| frame.invocation)
62 }
63
64 pub fn invocation_ids(&self) -> impl Iterator<Item = InvocationId> + '_ {
65 self.frames.iter().map(|frame| frame.invocation)
66 }
67
68 pub fn return_target(&self, event_depth: usize) -> ReturnTarget {
69 ReturnTarget {
70 invocation: self
71 .frames
72 .last()
73 .filter(|_| self.current_frame_recovered)
74 .map(|frame| frame.identity.clone()),
75 event_depth,
76 }
77 }
78
79 pub fn has_current_frame(&self) -> bool {
82 self.current_frame_recovered
83 }
84
85 pub fn is_empty(&self) -> bool {
86 self.frames.is_empty()
87 }
88
89 pub fn update(
91 &mut self,
92 context: &ResumeContext,
93 current: Option<(DebugSourceNodeId, u32)>,
94 asmop: Option<&AssemblyOp>,
95 ) {
96 let identities = self.resolver.resolve(context);
97 let common = self
98 .frames
99 .iter()
100 .zip(&identities)
101 .take_while(|(old, new)| old.identity.is_same_frame(new))
102 .count();
103 self.frames.truncate(common);
104 for (index, identity) in identities.into_iter().enumerate() {
105 if let Some(frame) = self.frames.get_mut(index) {
106 frame.identity = identity;
107 continue;
108 }
109 let invocation = InvocationId(self.next_invocation);
110 self.next_invocation =
111 self.next_invocation.checked_add(1).expect("invocation identity exhausted");
112 self.frames.push(RecoveredFrame {
113 invocation,
114 identity,
115 location: None,
116 inline_frames: Vec::new(),
117 });
118 }
119
120 let debug_info = context.debug_info();
121 self.current_frame_recovered = self
122 .frames
123 .last()
124 .zip(debug_info.as_deref())
125 .is_some_and(|(frame, info)| core::ptr::eq(frame.identity.debug_info(), info));
126 let mut rows = Vec::new();
127 if let Some((info, (source, operation))) = debug_info.as_ref().zip(current) {
128 rows.extend(info.inline_calls_for_operation(source, operation).map(|row| (info, row)));
129 }
130 for inherited in context.inherited_inline_call_contexts() {
131 rows.extend(inherited.inline_calls().map(|row| (inherited.debug_info(), row)));
132 }
133 rows.reverse();
134
135 for index in 0..self.frames.len() {
136 let start = self.frames[index].identity.inherited_inline_calls().min(rows.len());
137 let next = self.frames.get(index + 1);
138 let end = next
139 .map_or(rows.len(), |next| next.identity.inherited_inline_calls().min(rows.len()));
140 let boundary = next.and_then(|next| {
145 let (info, row) = *rows.get(end.checked_sub(1)?)?;
146 let same_function = if core::ptr::eq(info.as_ref(), next.identity.debug_info()) {
147 row.callee_idx == next.identity.function_idx()
148 } else {
149 info.get_function(row.callee_idx)?.mast_root
150 == next.identity.function().mast_root
151 };
152 (end > start && same_function).then_some((info, row))
153 });
154 let top = index + 1 == self.frames.len();
155 let frame = &mut self.frames[index];
156 if top || end > start {
157 let own_end = end - usize::from(boundary.is_some());
158 frame.inline_frames.clear();
159 for (info, row) in rows[start..own_end.max(start)].iter().rev() {
160 let Some(function) = info.get_function(row.callee_idx) else {
161 continue;
162 };
163 let Some(name) = info.get_string(function.name_idx) else {
164 continue;
165 };
166 let Some(call_site) = info.get_location(row.loc_idx) else {
167 continue;
168 };
169 frame.inline_frames.push(RecoveredInlineFrame {
170 display: InlineCallFrame { name, call_site },
171 info: Arc::clone(info),
172 function_idx: row.callee_idx,
173 });
174 }
175 }
176 if let Some((info, row)) = boundary {
177 frame.location = info.get_location(row.loc_idx);
178 } else if top && let Some(location) = asmop.and_then(AssemblyOp::location) {
179 frame.location = Some(location.clone());
180 }
181 }
182 }
183
184 pub fn logical_frames(&self, strip_prefix: &str) -> Vec<LogicalStackFrame> {
185 let mut logical = Vec::new();
186 for frame in &self.frames {
187 let name = &frame.identity.debug_info()[frame.identity.function().name_idx];
188 let name = name.trim_start_matches("::");
189 let name = match name.split_once("::") {
190 Some((module, rest)) if module == strip_prefix => rest,
191 _ => name,
192 };
193 let location = frame
194 .inline_frames
195 .last()
196 .map(|inline| &inline.display.call_site)
197 .or(frame.location.as_ref())
198 .cloned()
199 .map(LogicalFrameLocation::Assembly);
200 logical.push(LogicalStackFrame {
201 name: Arc::from(demangle(name)),
202 kind: LogicalFrameKind::Physical,
203 location,
204 physical_index: None,
205 origin: Some(frame.identity.origin()),
206 invocation: Some(frame.invocation),
207 source: Some(LogicalFrameSource::Physical(frame.identity.clone())),
208 });
209 for index in (0..frame.inline_frames.len()).rev() {
210 let inline = &frame.inline_frames[index];
211 let location = if index == 0 {
212 frame.location.clone()
213 } else {
214 Some(frame.inline_frames[index - 1].display.call_site.clone())
215 };
216 logical.push(LogicalStackFrame {
217 name: Arc::from(inline.display.display_name()),
218 kind: LogicalFrameKind::Inline,
219 location: location.map(LogicalFrameLocation::Assembly),
220 physical_index: None,
221 origin: None,
222 invocation: None,
223 source: Some(LogicalFrameSource::Inline {
224 owner: frame.invocation,
225 info: Arc::clone(&inline.info),
226 function_idx: inline.function_idx,
227 call_site: inline.display.call_site.clone(),
228 }),
229 });
230 }
231 }
232 logical
233 }
234}
235
236#[cfg(test)]
237mod tests;