1use std::{
2 borrow::Cow,
3 cell::{OnceCell, RefCell},
4 collections::{BTreeMap, BTreeSet, VecDeque},
5 fmt,
6 path::Path,
7 rc::Rc,
8 sync::Arc,
9};
10
11use miden_core::operations::AssemblyOp;
12use miden_debug_types::{Location, SourceFile, SourceManager, SourceManagerExt, SourceSpan};
13use miden_processor::{ContextId, operation::Operation, trace::RowIndex};
14
15use crate::exec::TraceEvent;
16
17pub struct StepInfo<'a> {
18 pub op: Option<Operation>,
19 pub asmop: Option<&'a AssemblyOp>,
20 pub clk: RowIndex,
21 pub ctx: ContextId,
22}
23
24#[derive(Debug, Clone)]
25struct SpanContext {
26 frame_index: usize,
27 location: Option<Location>,
28}
29
30pub struct CallStack {
31 trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>>,
32 contexts: BTreeSet<Rc<str>>,
33 frames: Vec<CallFrame>,
34 block_stack: Vec<Option<SpanContext>>,
35}
36impl CallStack {
37 pub fn new(trace_events: Rc<RefCell<BTreeMap<RowIndex, TraceEvent>>>) -> Self {
38 Self {
39 trace_events,
40 contexts: BTreeSet::default(),
41 frames: vec![],
42 block_stack: vec![],
43 }
44 }
45
46 pub fn stacktrace<'a>(
47 &'a self,
48 recent: &'a VecDeque<Operation>,
49 source_manager: &'a dyn SourceManager,
50 ) -> StackTrace<'a> {
51 StackTrace::new(self, recent, source_manager)
52 }
53
54 pub fn current_frame(&self) -> Option<&CallFrame> {
55 self.frames.last()
56 }
57
58 pub fn current_frame_mut(&mut self) -> Option<&mut CallFrame> {
59 self.frames.last_mut()
60 }
61
62 pub fn frames(&self) -> &[CallFrame] {
63 self.frames.as_slice()
64 }
65
66 pub fn next(&mut self, info: &StepInfo<'_>) -> Option<CallFrame> {
70 if let Some(op) = info.op {
71 let procedure = info.asmop.map(|op| self.cache_procedure_name(op.context_name()));
73
74 let event = self.trace_events.borrow().get(&info.clk).copied();
76 log::trace!("handling {op} at cycle {}: {:?}", info.clk, &event);
77 let popped_frame = self.handle_trace_event(event, procedure.as_ref());
78 let is_frame_end = popped_frame.is_some();
79
80 let ignore = false;
81
82 if ignore || is_frame_end {
83 return popped_frame;
84 }
85
86 let (procedure, asmop) = match procedure {
89 proc @ Some(_) => (proc, info.asmop.map(Cow::Borrowed)),
90 None => match self.block_stack.last() {
91 Some(Some(span_ctx)) => {
92 let proc =
93 self.frames.get(span_ctx.frame_index).and_then(|f| f.procedure.clone());
94 let asmop_cow = info.asmop.map(Cow::Borrowed).or_else(|| {
95 let context_name = proc.as_deref().unwrap_or("<unknown>").to_string();
96 let raw_asmop = AssemblyOp::new(
97 span_ctx.location.clone(),
98 context_name,
99 1,
100 op.to_string(),
101 );
102 Some(Cow::Owned(raw_asmop))
103 });
104 (proc, asmop_cow)
105 }
106 _ => (None, info.asmop.map(Cow::Borrowed)),
107 },
108 };
109
110 let procedure =
113 procedure.or_else(|| self.frames.last().and_then(|f| f.procedure.clone()));
114
115 if self.frames.is_empty() {
117 self.frames.push(CallFrame::new(procedure.clone()));
118 }
119
120 let current_frame = self.frames.last_mut().unwrap();
121
122 let procedure_context_updated =
125 current_frame.procedure.is_none() && procedure.is_some();
126 if procedure_context_updated {
127 current_frame.procedure.clone_from(&procedure);
128 }
129
130 if !matches!(op, Operation::Noop) {
132 let cycle_idx = info.asmop.map(|a| a.num_cycles()).unwrap_or(1);
133 current_frame.push(op, cycle_idx, asmop.as_deref());
134 }
135
136 let num_frames = self.frames.len();
138 if procedure_context_updated && num_frames > 1 {
139 let caller_frame = &mut self.frames[num_frames - 2];
140 if let Some(OpDetail::Exec { callee }) = caller_frame.context.back_mut()
141 && callee.is_none()
142 {
143 *callee = procedure;
144 }
145 }
146 }
147
148 None
149 }
150
151 fn cache_procedure_name(&mut self, context_name: &str) -> Rc<str> {
153 match self.contexts.get(context_name) {
154 Some(name) => Rc::clone(name),
155 None => {
156 let name = Rc::from(context_name.to_string().into_boxed_str());
157 self.contexts.insert(Rc::clone(&name));
158 name
159 }
160 }
161 }
162
163 fn handle_trace_event(
164 &mut self,
165 event: Option<TraceEvent>,
166 procedure: Option<&Rc<str>>,
167 ) -> Option<CallFrame> {
168 if let Some(event) = event {
170 match event {
171 TraceEvent::FrameStart => {
172 if let Some(current_frame) = self.frames.last_mut() {
174 current_frame.push_exec(procedure.cloned());
175 }
176 self.frames.push(CallFrame::new(procedure.cloned()));
178 }
179 TraceEvent::Unknown(code) => log::debug!("unknown trace event: {code}"),
180 TraceEvent::FrameEnd => {
181 return self.frames.pop();
182 }
183 _ => (),
184 }
185 }
186 None
187 }
188}
189
190pub struct CallFrame {
191 procedure: Option<Rc<str>>,
192 context: VecDeque<OpDetail>,
193 display_name: std::cell::OnceCell<Rc<str>>,
194 finishing: bool,
195}
196impl CallFrame {
197 pub fn new(procedure: Option<Rc<str>>) -> Self {
198 Self {
199 procedure,
200 context: Default::default(),
201 display_name: Default::default(),
202 finishing: false,
203 }
204 }
205
206 pub fn procedure(&self, strip_prefix: &str) -> Option<Rc<str>> {
207 self.procedure.as_ref()?;
208 let name = self.display_name.get_or_init(|| {
209 let name = self.procedure.as_deref().unwrap();
210 let name = match name.split_once("::") {
211 Some((module, rest)) if module == strip_prefix => demangle(rest),
212 _ => demangle(name),
213 };
214 Rc::from(name.into_boxed_str())
215 });
216 Some(Rc::clone(name))
217 }
218
219 pub fn push_exec(&mut self, callee: Option<Rc<str>>) {
220 if self.context.len() == 5 {
221 self.context.pop_front();
222 }
223
224 self.context.push_back(OpDetail::Exec { callee });
225 }
226
227 pub fn push(&mut self, opcode: Operation, cycle_idx: u8, op: Option<&AssemblyOp>) {
228 if cycle_idx > 1 {
229 let skip = self.context.back().map(|detail| matches!(detail, OpDetail::Full { op, .. } | OpDetail::Basic { op } if op == &opcode)).unwrap_or(false);
231 if skip {
232 return;
233 }
234 }
235
236 if self.context.len() == 5 {
237 self.context.pop_front();
238 }
239
240 match op {
241 Some(op) => {
242 let location = op.location().cloned();
243 self.context.push_back(OpDetail::Full {
244 op: opcode,
245 location,
246 resolved: Default::default(),
247 });
248 }
249 None => {
250 if let Some(loc) = self.context.back().map(|op| op.location().cloned()) {
253 self.context.push_back(OpDetail::Full {
254 op: opcode,
255 location: loc,
256 resolved: Default::default(),
257 });
258 } else {
259 self.context.push_back(OpDetail::Basic { op: opcode });
260 }
261 }
262 }
263 }
264
265 pub fn last_location(&self) -> Option<&Location> {
266 match self.context.back() {
267 Some(OpDetail::Full { location, .. }) => {
268 let loc = location.as_ref();
269 if loc.is_none() {
270 dbg!(&self.context);
271 }
272 loc
273 }
274 Some(OpDetail::Basic { .. }) => None,
275 Some(OpDetail::Exec { .. }) => {
276 let op = self.context.iter().rev().nth(1)?;
277 op.location()
278 }
279 None => None,
280 }
281 }
282
283 pub fn last_resolved(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
284 for op in self.context.iter().rev() {
287 if let Some(resolved) = op.resolve(source_manager) {
288 return Some(resolved);
289 }
290 }
291 None
292 }
293
294 pub fn recent(&self) -> &VecDeque<OpDetail> {
295 &self.context
296 }
297
298 #[inline(always)]
299 pub fn should_break_on_exit(&self) -> bool {
300 self.finishing
301 }
302
303 #[inline(always)]
304 pub fn break_on_exit(&mut self) {
305 self.finishing = true;
306 }
307}
308
309#[derive(Debug, Clone)]
310pub enum OpDetail {
311 Full {
312 op: Operation,
313 location: Option<Location>,
314 resolved: OnceCell<Option<ResolvedLocation>>,
315 },
316 Exec {
317 callee: Option<Rc<str>>,
318 },
319 Basic {
320 op: Operation,
321 },
322}
323impl OpDetail {
324 pub fn callee(&self, strip_prefix: &str) -> Option<Box<str>> {
325 match self {
326 Self::Exec { callee: None } => Some(Box::from("<unknown>")),
327 Self::Exec {
328 callee: Some(callee),
329 } => {
330 let name = match callee.split_once("::") {
331 Some((module, rest)) if module == strip_prefix => demangle(rest),
332 _ => demangle(callee),
333 };
334 Some(name.into_boxed_str())
335 }
336 _ => None,
337 }
338 }
339
340 pub fn display(&self) -> String {
341 match self {
342 Self::Full { op, .. } | Self::Basic { op } => format!("{op}"),
343 Self::Exec {
344 callee: Some(callee),
345 } => format!("exec.{callee}"),
346 Self::Exec { callee: None } => "exec.<unavailable>".to_string(),
347 }
348 }
349
350 pub fn opcode(&self) -> Operation {
351 match self {
352 Self::Full { op, .. } | Self::Basic { op } => *op,
353 Self::Exec { .. } => panic!("no opcode associated with execs"),
354 }
355 }
356
357 pub fn location(&self) -> Option<&Location> {
358 match self {
359 Self::Full { location, .. } => location.as_ref(),
360 Self::Basic { .. } | Self::Exec { .. } => None,
361 }
362 }
363
364 pub fn resolve(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
365 match self {
366 Self::Full {
367 location: Some(loc),
368 resolved,
369 ..
370 } => resolved
371 .get_or_init(|| {
372 let path = Path::new(loc.uri().as_str());
373 let source_file = if path.exists() {
374 source_manager.load_file(path).ok()?
375 } else {
376 source_manager.get_by_uri(loc.uri())?
377 };
378 let span = SourceSpan::new(source_file.id(), loc.start..loc.end);
379 let file_line_col = source_file.location(span);
380 Some(ResolvedLocation {
381 source_file,
382 line: file_line_col.line.to_u32(),
383 col: file_line_col.column.to_u32(),
384 span,
385 })
386 })
387 .as_ref(),
388 _ => None,
389 }
390 }
391}
392
393#[derive(Debug, Clone)]
394pub struct ResolvedLocation {
395 pub source_file: Arc<SourceFile>,
396 pub line: u32,
398 pub col: u32,
399 pub span: SourceSpan,
400}
401impl fmt::Display for ResolvedLocation {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 write!(f, "{}:{}:{}", self.source_file.uri().as_str(), self.line, self.col)
404 }
405}
406
407pub struct CurrentFrame {
408 pub procedure: Option<Rc<str>>,
409 pub location: Option<ResolvedLocation>,
410}
411
412pub struct StackTrace<'a> {
413 callstack: &'a CallStack,
414 recent: &'a VecDeque<Operation>,
415 source_manager: &'a dyn SourceManager,
416 current_frame: Option<CurrentFrame>,
417}
418
419impl<'a> StackTrace<'a> {
420 pub fn new(
421 callstack: &'a CallStack,
422 recent: &'a VecDeque<Operation>,
423 source_manager: &'a dyn SourceManager,
424 ) -> Self {
425 let current_frame = callstack.current_frame().map(|frame| {
426 let location = frame.last_resolved(source_manager).cloned();
427 let procedure = frame.procedure("");
428 CurrentFrame {
429 procedure,
430 location,
431 }
432 });
433 Self {
434 callstack,
435 recent,
436 source_manager,
437 current_frame,
438 }
439 }
440
441 pub fn current_frame(&self) -> Option<&CurrentFrame> {
442 self.current_frame.as_ref()
443 }
444}
445
446impl fmt::Display for StackTrace<'_> {
447 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
448 use std::fmt::Write;
449
450 let num_frames = self.callstack.frames.len();
451
452 writeln!(f, "\nStack Trace:")?;
453
454 for (i, frame) in self.callstack.frames.iter().enumerate() {
455 let is_top = i + 1 == num_frames;
456 let name = frame.procedure("");
457 let name = name.as_deref().unwrap_or("<unknown>");
458 if is_top {
459 write!(f, " `-> {name}")?;
460 } else {
461 write!(f, " |-> {name}")?;
462 }
463 if let Some(resolved) = frame.last_resolved(self.source_manager) {
464 write!(f, " in {resolved}")?;
465 } else {
466 write!(f, " in <unavailable>")?;
467 }
468 if is_top {
469 let context_size = frame.context.len();
471 writeln!(f, ":\n\nLast {context_size} Instructions (of current frame):")?;
472 for (i, op) in frame.context.iter().enumerate() {
473 let is_last = i + 1 == context_size;
474 if let Some(callee) = op.callee("") {
475 write!(f, " | exec.{callee}")?;
476 } else {
477 write!(f, " | {}", &op.opcode())?;
478 }
479 if is_last {
480 writeln!(f, "\n `-> <error occured here>")?;
481 } else {
482 f.write_char('\n')?;
483 }
484 }
485
486 let context_size = self.recent.len();
487 writeln!(f, "\n\nLast {context_size} Instructions (any frame):")?;
488 for (i, op) in self.recent.iter().enumerate() {
489 let is_last = i + 1 == context_size;
490 if is_last {
491 writeln!(f, " | {}", &op)?;
492 writeln!(f, " `-> <error occured here>")?;
493 } else {
494 writeln!(f, " | {}", &op)?;
495 }
496 }
497 } else {
498 f.write_char('\n')?;
499 }
500 }
501
502 Ok(())
503 }
504}
505
506fn demangle(name: &str) -> String {
507 let mut input = name.as_bytes();
508 let mut demangled = Vec::with_capacity(input.len() * 2);
509 rustc_demangle::demangle_stream(&mut input, &mut demangled, false)
510 .expect("failed to write demangled identifier");
511 String::from_utf8(demangled).expect("demangled identifier contains invalid utf-8")
512}