Skip to main content

miden_debug_engine/debug/
stacktrace.rs

1use alloc::{
2    borrow::Cow,
3    boxed::Box,
4    collections::{BTreeMap, BTreeSet, VecDeque},
5    string::{String, ToString},
6    sync::Arc,
7    vec::Vec,
8};
9use core::{cell::OnceCell, fmt};
10#[cfg(feature = "std")]
11use std::path::{Path, PathBuf};
12
13use miden_core::operations::AssemblyOp;
14use miden_debug_types::{Location, SourceFile, SourceManager, SourceSpan, Uri};
15use miden_mast_package::debug_info::{DebugSourceInlineCall, DebugSourceNodeId, PackageDebugInfo};
16use miden_processor::{ContextId, SourceInlineCallContext, operation::Operation, trace::RowIndex};
17use miden_utils_sync::RwLock;
18
19use crate::Event;
20
21#[derive(Copy, Clone, Debug, Eq, PartialEq)]
22pub enum ControlFlowOp {
23    Span,
24    Respan,
25    Join,
26    Split,
27    End,
28}
29
30pub struct StepInfo<'a> {
31    pub op: Option<Operation>,
32    pub control: Option<ControlFlowOp>,
33    pub asmop: Option<&'a AssemblyOp>,
34    pub clk: RowIndex,
35    pub ctx: ContextId,
36    pub inline_frames: &'a [InlineCallFrame],
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct InlineCallFrame {
41    name: Arc<str>,
42    call_site: Location,
43}
44
45impl InlineCallFrame {
46    pub fn name(&self) -> &str {
47        &self.name
48    }
49
50    pub fn call_site(&self) -> &Location {
51        &self.call_site
52    }
53
54    pub fn display_name(&self) -> String {
55        demangle(&self.name)
56    }
57}
58
59#[derive(Debug, Copy, Clone, PartialEq, Eq)]
60pub enum LogicalFrameKind {
61    Physical,
62    Inline,
63}
64
65#[derive(Debug, Clone)]
66enum LogicalFrameLocation {
67    Assembly(Location),
68    Resolved(ResolvedLocation),
69}
70
71#[derive(Debug, Clone)]
72pub struct LogicalStackFrame {
73    name: Arc<str>,
74    kind: LogicalFrameKind,
75    location: Option<LogicalFrameLocation>,
76    physical_index: usize,
77}
78
79impl LogicalStackFrame {
80    pub fn name(&self) -> &str {
81        &self.name
82    }
83
84    pub fn kind(&self) -> LogicalFrameKind {
85        self.kind
86    }
87
88    pub fn physical_index(&self) -> usize {
89        self.physical_index
90    }
91
92    pub fn display_name(&self) -> String {
93        match self.kind {
94            LogicalFrameKind::Physical => self.name.to_string(),
95            LogicalFrameKind::Inline => format!("[inlined] {}", self.name),
96        }
97    }
98
99    pub fn resolved(&self, source_manager: &dyn SourceManager) -> Option<ResolvedLocation> {
100        match self.location.as_ref()? {
101            LogicalFrameLocation::Assembly(location) => {
102                resolve_assembly_location(source_manager, location)
103            }
104            LogicalFrameLocation::Resolved(resolved) => Some(resolved.clone()),
105        }
106    }
107}
108
109/// Resolves the inline frames active for an operation.
110///
111/// Rows owned by the current package come first. Contexts inherited across dynamic/external
112/// package boundaries follow in the VM-provided innermost-to-outermost order.
113pub fn inline_frames_for_operation<'a>(
114    current: Option<(&PackageDebugInfo, DebugSourceNodeId, u32)>,
115    inherited: impl IntoIterator<Item = &'a SourceInlineCallContext>,
116) -> Vec<InlineCallFrame> {
117    let mut frames = Vec::new();
118    if let Some((debug_info, source_node, op_idx)) = current {
119        append_inline_frames(
120            &mut frames,
121            debug_info,
122            debug_info.inline_calls_for_operation(source_node, op_idx),
123        );
124    }
125    for context in inherited {
126        append_inline_frames(&mut frames, context.debug_info(), context.inline_calls());
127    }
128    frames
129}
130
131fn append_inline_frames<'a>(
132    frames: &mut Vec<InlineCallFrame>,
133    debug_info: &PackageDebugInfo,
134    rows: impl IntoIterator<Item = &'a DebugSourceInlineCall>,
135) {
136    frames.extend(rows.into_iter().filter_map(|row| {
137        let function = debug_info.get_function(row.callee_idx)?;
138        let name = debug_info.get_string(function.name_idx)?;
139        let call_site = debug_info.get_location(row.loc_idx)?;
140        Some(InlineCallFrame { name, call_site })
141    }));
142}
143
144#[derive(Debug, Clone)]
145struct SpanContext {
146    frame_index: usize,
147    location: Option<Location>,
148}
149
150pub struct CallStack {
151    events: Arc<RwLock<BTreeMap<RowIndex, Event>>>,
152    contexts: BTreeSet<Arc<str>>,
153    frames: Vec<CallFrame>,
154    block_stack: Vec<Option<SpanContext>>,
155}
156impl CallStack {
157    pub fn new(events: Arc<RwLock<BTreeMap<RowIndex, Event>>>) -> Self {
158        Self {
159            events,
160            contexts: BTreeSet::default(),
161            frames: vec![],
162            block_stack: vec![],
163        }
164    }
165
166    /// Build a [CallStack] from pre-built frames — used in DAP client mode.
167    #[cfg(feature = "dap")]
168    pub fn from_remote_frames(frames: Vec<CallFrame>) -> Self {
169        Self {
170            events: Arc::new(Default::default()),
171            contexts: BTreeSet::default(),
172            frames,
173            block_stack: vec![],
174        }
175    }
176
177    pub fn stacktrace<'a>(
178        &'a self,
179        recent: &'a VecDeque<Operation>,
180        source_manager: &'a dyn SourceManager,
181    ) -> StackTrace<'a> {
182        StackTrace::new(self, recent, source_manager)
183    }
184
185    pub fn current_frame(&self) -> Option<&CallFrame> {
186        self.frames.last()
187    }
188
189    pub fn current_frame_mut(&mut self) -> Option<&mut CallFrame> {
190        self.frames.last_mut()
191    }
192
193    pub fn frames(&self) -> &[CallFrame] {
194        self.frames.as_slice()
195    }
196
197    pub fn logical_frames(&self, strip_prefix: &str) -> Vec<LogicalStackFrame> {
198        let mut logical = Vec::new();
199        for (physical_index, frame) in self.frames.iter().enumerate() {
200            let current_location = frame.last_logical_location();
201            let location = frame
202                .inline_frames
203                .last()
204                .map(|inline| LogicalFrameLocation::Assembly(inline.call_site.clone()))
205                .or_else(|| current_location.clone());
206            logical.push(LogicalStackFrame {
207                name: frame.procedure(strip_prefix).unwrap_or_else(|| Arc::from("<unknown>")),
208                kind: LogicalFrameKind::Physical,
209                location,
210                physical_index,
211            });
212
213            for inline_index in (0..frame.inline_frames.len()).rev() {
214                let inline = &frame.inline_frames[inline_index];
215                let location = if inline_index == 0 {
216                    current_location.clone()
217                } else {
218                    Some(LogicalFrameLocation::Assembly(
219                        frame.inline_frames[inline_index - 1].call_site.clone(),
220                    ))
221                };
222                logical.push(LogicalStackFrame {
223                    name: Arc::from(inline.display_name().into_boxed_str()),
224                    kind: LogicalFrameKind::Inline,
225                    location,
226                    physical_index,
227                });
228            }
229        }
230        logical
231    }
232
233    /// Updates the call stack from `info`
234    ///
235    /// Returns the call frame exited this cycle, if any
236    pub fn next(&mut self, info: &StepInfo<'_>) -> Option<CallFrame> {
237        let procedure = info.asmop.map(|op| self.cache_procedure_name(op.context_name()));
238
239        let event = {
240            let mut events = self.events.write();
241            match events.first_key_value() {
242                Some((clk, _)) if *clk <= info.clk => events.pop_first().map(|(_, event)| event),
243                _ => None,
244            }
245        };
246        log::trace!("handling {:?}/{:?} at cycle {}: {:?}", info.control, info.op, info.clk, event);
247        let is_frame_start = event.as_ref().is_some_and(|event| event.is_frame_start());
248        let is_frame_end = event.as_ref().is_some_and(|event| event.is_frame_end());
249        let popped_frame = self.handle_event(event, procedure.clone(), info.op, info.asmop);
250
251        match info.control {
252            Some(ControlFlowOp::Span) => {
253                if let Some(asmop) = info.asmop {
254                    log::debug!("{asmop:#?}");
255                    self.block_stack.push(Some(SpanContext {
256                        frame_index: self.frames.len().saturating_sub(1),
257                        location: asmop.location().cloned(),
258                    }));
259                } else {
260                    self.block_stack.push(None);
261                }
262            }
263            Some(ControlFlowOp::Join | ControlFlowOp::Split) => {
264                self.block_stack.push(None);
265            }
266            Some(ControlFlowOp::End) => {
267                self.block_stack.pop();
268            }
269            Some(ControlFlowOp::Respan) | None => {}
270        }
271
272        if !is_frame_end {
273            if self.frames.is_empty() {
274                self.frames.push(CallFrame::new(procedure.clone()));
275            }
276            self.frames.last_mut().unwrap().inline_frames = info.inline_frames.to_vec();
277            self.update_current_procedure(procedure.clone());
278        }
279
280        if is_frame_start || is_frame_end {
281            return popped_frame;
282        }
283
284        let Some(op) = info.op else {
285            return popped_frame;
286        };
287
288        // Attempt to supply procedure context from the current span context, if needed +
289        // available
290        let (procedure, asmop) = match procedure {
291            proc @ Some(_) => (proc, info.asmop.map(Cow::Borrowed)),
292            None => match self.block_stack.last() {
293                Some(Some(span_ctx)) => {
294                    let proc =
295                        self.frames.get(span_ctx.frame_index).and_then(|f| f.procedure.clone());
296                    let asmop_cow = info.asmop.map(Cow::Borrowed).or_else(|| {
297                        let context_name = proc.as_deref().unwrap_or("<unknown>").to_string();
298                        let raw_asmop = AssemblyOp::new(
299                            span_ctx.location.clone(),
300                            context_name,
301                            1,
302                            op.to_string(),
303                        );
304                        Some(Cow::Owned(raw_asmop))
305                    });
306                    (proc, asmop_cow)
307                }
308                _ => (None, info.asmop.map(Cow::Borrowed)),
309            },
310        };
311
312        // Use the current frame's procedure context, if no other more precise context is
313        // available
314        let procedure = procedure.or_else(|| self.frames.last().and_then(|f| f.procedure.clone()));
315
316        // `exec` changes procedure context without creating a physical frame. Keep the physical
317        // frame synchronized with the best context available for the current operation.
318        self.update_current_procedure(procedure);
319        let current_frame = self.frames.last_mut().unwrap();
320
321        // Push op into call frame if this is any op other than `nop` or frame setup
322        if !matches!(op, Operation::Noop) {
323            let cycle_idx = info.asmop.map(|a| a.num_cycles()).unwrap_or(1);
324            current_frame.push(op, cycle_idx, asmop.as_deref());
325        }
326
327        popped_frame
328    }
329
330    fn update_current_procedure(&mut self, procedure: Option<Arc<str>>) {
331        let context_initialized = self
332            .frames
333            .last_mut()
334            .is_some_and(|frame| frame.update_procedure(procedure.clone()));
335        let num_frames = self.frames.len();
336        if context_initialized && num_frames > 1 {
337            let caller_frame = &mut self.frames[num_frames - 2];
338            if let Some(OpDetail::Exec { callee }) = caller_frame.context.back_mut()
339                && callee.is_none()
340            {
341                *callee = procedure;
342            }
343        }
344    }
345
346    // Get or cache procedure name/context as `Arc<str>`
347    fn cache_procedure_name(&mut self, context_name: &str) -> Arc<str> {
348        match self.contexts.get(context_name) {
349            Some(name) => Arc::clone(name),
350            None => {
351                let name = Arc::from(context_name.to_string().into_boxed_str());
352                self.contexts.insert(Arc::clone(&name));
353                name
354            }
355        }
356    }
357
358    fn handle_event(
359        &mut self,
360        event: Option<Event>,
361        procedure: Option<Arc<str>>,
362        op: Option<Operation>,
363        asmop: Option<&AssemblyOp>,
364    ) -> Option<CallFrame> {
365        // Do we need to handle any frame events?
366        match event? {
367            Event::FrameStart => {
368                // Record the fact that we exec'd a new procedure in the op context
369                if let Some(current_frame) = self.frames.last_mut() {
370                    current_frame.push_exec(procedure.clone());
371                }
372                // The event is emitted at the start of the callee.
373                let mut frame = CallFrame::new(procedure);
374                if let Some(op) = op {
375                    frame.push(op, 0, asmop);
376                }
377                self.frames.push(frame);
378            }
379            Event::Unknown(code) => log::debug!("unknown trace event: {code}"),
380            Event::FrameEnd => {
381                return self.frames.pop();
382            }
383            _ => (),
384        }
385        None
386    }
387}
388
389pub struct CallFrame {
390    procedure: Option<Arc<str>>,
391    context: VecDeque<OpDetail>,
392    display_name: OnceCell<Arc<str>>,
393    finishing: bool,
394    inline_frames: Vec<InlineCallFrame>,
395}
396impl CallFrame {
397    pub fn new(procedure: Option<Arc<str>>) -> Self {
398        Self {
399            procedure,
400            context: Default::default(),
401            display_name: Default::default(),
402            finishing: false,
403            inline_frames: Vec::new(),
404        }
405    }
406
407    /// Build a frame from remote (DAP) data — used in DAP client mode.
408    ///
409    /// The frame stores the procedure name and an optional [ResolvedLocation]
410    /// as a pre-resolved `OpDetail::Full` entry so that `last_resolved()` and
411    /// `recent()` work correctly for pane rendering.
412    #[cfg(feature = "dap")]
413    pub fn from_remote(procedure: Option<Arc<str>>, resolved: Option<ResolvedLocation>) -> Self {
414        let mut context = VecDeque::new();
415        if let Some(loc) = resolved {
416            let cell = OnceCell::new();
417            cell.set(Some(loc)).ok();
418            context.push_back(OpDetail::Full {
419                op: miden_processor::operation::Operation::Noop,
420                location: None,
421                resolved: cell,
422            });
423        }
424        Self {
425            procedure,
426            context,
427            display_name: Default::default(),
428            finishing: false,
429            inline_frames: Vec::new(),
430        }
431    }
432
433    pub fn procedure(&self, strip_prefix: &str) -> Option<Arc<str>> {
434        self.procedure.as_ref()?;
435        let name = self.display_name.get_or_init(|| {
436            let name = self.procedure.as_deref().unwrap();
437            let name = match name.split_once("::") {
438                Some((module, rest)) if module == strip_prefix => demangle(rest),
439                _ => demangle(name),
440            };
441            Arc::<str>::from(name.into_boxed_str())
442        });
443        Some(Arc::clone(name))
444    }
445
446    /// Update this physical frame's procedure, returning true only when the context was first
447    /// initialized. Later changes arise from `exec` and must invalidate the cached display name,
448    /// but must not rewrite the caller's recorded callee.
449    fn update_procedure(&mut self, procedure: Option<Arc<str>>) -> bool {
450        let Some(procedure) = procedure else {
451            return false;
452        };
453        if self.procedure.as_ref() == Some(&procedure) {
454            return false;
455        }
456
457        let initialized = self.procedure.is_none();
458        self.procedure = Some(procedure);
459        self.display_name.take();
460        initialized
461    }
462
463    pub fn push_exec(&mut self, callee: Option<Arc<str>>) {
464        if self.context.len() == 5 {
465            self.context.pop_front();
466        }
467
468        self.context.push_back(OpDetail::Exec { callee });
469    }
470
471    pub fn push(&mut self, opcode: Operation, cycle_idx: u8, op: Option<&AssemblyOp>) {
472        if cycle_idx > 1 {
473            // Should we ignore this op?
474            let skip = self.context.back().map(|detail| matches!(detail, OpDetail::Full { op, .. } | OpDetail::Basic { op } if op == &opcode)).unwrap_or(false);
475            if skip {
476                return;
477            }
478        }
479
480        if self.context.len() == 5 {
481            self.context.pop_front();
482        }
483
484        match op {
485            Some(op) => {
486                let location = op.location().cloned();
487                self.context.push_back(OpDetail::Full {
488                    op: opcode,
489                    location,
490                    resolved: Default::default(),
491                });
492            }
493            None => {
494                // If this instruction does not have a location, inherit the location
495                // of the previous op in the frame, if one is present
496                if let Some(loc) = self.context.back().map(|op| op.location().cloned()) {
497                    self.context.push_back(OpDetail::Full {
498                        op: opcode,
499                        location: loc,
500                        resolved: Default::default(),
501                    });
502                } else {
503                    self.context.push_back(OpDetail::Basic { op: opcode });
504                }
505            }
506        }
507    }
508
509    pub fn last_location(&self) -> Option<&Location> {
510        self.context.iter().rev().find_map(OpDetail::location)
511    }
512
513    fn last_logical_location(&self) -> Option<LogicalFrameLocation> {
514        self.context.iter().rev().find_map(|detail| {
515            detail
516                .location()
517                .cloned()
518                .map(LogicalFrameLocation::Assembly)
519                .or_else(|| detail.cached_resolved().cloned().map(LogicalFrameLocation::Resolved))
520        })
521    }
522
523    pub fn last_resolved(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
524        // Search through context in reverse order to find the most recent op with a resolvable
525        // location.
526        for op in self.context.iter().rev() {
527            if let Some(resolved) = op.resolve(source_manager) {
528                return Some(resolved);
529            }
530        }
531        None
532    }
533
534    pub fn recent(&self) -> &VecDeque<OpDetail> {
535        &self.context
536    }
537
538    #[inline(always)]
539    pub fn should_break_on_exit(&self) -> bool {
540        self.finishing
541    }
542
543    #[inline(always)]
544    pub fn break_on_exit(&mut self) {
545        self.finishing = true;
546    }
547}
548
549#[derive(Debug, Clone)]
550pub enum OpDetail {
551    Full {
552        op: Operation,
553        location: Option<Location>,
554        resolved: OnceCell<Option<ResolvedLocation>>,
555    },
556    Exec {
557        callee: Option<Arc<str>>,
558    },
559    Basic {
560        op: Operation,
561    },
562}
563impl OpDetail {
564    pub fn callee(&self, strip_prefix: &str) -> Option<Box<str>> {
565        match self {
566            Self::Exec { callee: None } => Some(Box::from("<unknown>")),
567            Self::Exec {
568                callee: Some(callee),
569            } => {
570                let name = match callee.split_once("::") {
571                    Some((module, rest)) if module == strip_prefix => demangle(rest),
572                    _ => demangle(callee),
573                };
574                Some(name.into_boxed_str())
575            }
576            _ => None,
577        }
578    }
579
580    pub fn display(&self) -> String {
581        match self {
582            Self::Full { op, .. } | Self::Basic { op } => format!("{op}"),
583            Self::Exec {
584                callee: Some(callee),
585            } => format!("exec.{callee}"),
586            Self::Exec { callee: None } => "exec.<unavailable>".to_string(),
587        }
588    }
589
590    pub fn opcode(&self) -> Operation {
591        match self {
592            Self::Full { op, .. } | Self::Basic { op } => *op,
593            Self::Exec { .. } => panic!("no opcode associated with execs"),
594        }
595    }
596
597    pub fn location(&self) -> Option<&Location> {
598        match self {
599            Self::Full { location, .. } => location.as_ref(),
600            Self::Basic { .. } | Self::Exec { .. } => None,
601        }
602    }
603
604    pub fn resolve(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
605        match self {
606            Self::Full {
607                location, resolved, ..
608            } => {
609                if let Some(cached) = resolved.get() {
610                    return cached.as_ref();
611                }
612                let loc = location.as_ref()?;
613                resolved
614                    .get_or_init(|| {
615                        let source_file = resolve_source_file_for_location(source_manager, loc)?;
616                        let span = SourceSpan::new(source_file.id(), loc.start..loc.end);
617                        let file_line_col = source_file.location(span);
618                        Some(ResolvedLocation {
619                            source_file,
620                            line: file_line_col.line.to_u32(),
621                            col: file_line_col.column.to_u32(),
622                            span,
623                        })
624                    })
625                    .as_ref()
626            }
627            _ => None,
628        }
629    }
630
631    fn cached_resolved(&self) -> Option<&ResolvedLocation> {
632        match self {
633            Self::Full { resolved, .. } => resolved.get().and_then(Option::as_ref),
634            Self::Exec { .. } | Self::Basic { .. } => None,
635        }
636    }
637}
638
639/// Resolve a source file for `location`.
640///
641/// Compiled packages may contain remapped paths such as `src/lib.rs`, while sources loaded by the
642/// VM host may be keyed by an absolute path, or may not be loaded yet at all. Prefer the source
643/// manager's existing URI table, then fall back to loading the file from disk.
644#[cfg(feature = "std")]
645pub fn resolve_source_file_for_location(
646    source_manager: &dyn SourceManager,
647    location: &Location,
648) -> Option<Arc<SourceFile>> {
649    use miden_assembly_syntax::debuginfo::SourceManagerExt;
650    source_manager.get_by_uri(location.uri()).or_else(|| {
651        resolve_source_path(location.uri()).and_then(|path| source_manager.load_file(&path).ok())
652    })
653}
654
655#[cfg(not(feature = "std"))]
656pub fn resolve_source_file_for_location(
657    source_manager: &dyn SourceManager,
658    location: &Location,
659) -> Option<Arc<SourceFile>> {
660    source_manager.get_by_uri(location.uri())
661}
662
663/// Resolve a source URI to an existing local filesystem path.
664///
665/// Non-file URI schemes are left to the source manager. Relative paths are resolved against the
666/// debugger process' current directory, which DAP clients set to the launch `cwd`.
667#[cfg(feature = "std")]
668pub fn resolve_source_path(uri: &Uri) -> Option<PathBuf> {
669    let path = match uri.scheme() {
670        None | Some("file") => uri.to_path()?,
671        Some(_) => return None,
672    };
673
674    fn existing_path(path: &Path) -> Option<PathBuf> {
675        path.exists()
676            .then(|| path.canonicalize().unwrap_or_else(|_| path.to_path_buf()))
677    }
678
679    existing_path(&path).or_else(|| {
680        if path.is_relative() {
681            std::env::current_dir().ok().and_then(|cwd| existing_path(&cwd.join(path)))
682        } else {
683            None
684        }
685    })
686}
687
688/// Resolve a source location directly from the filesystem, returning the resolved path and line.
689#[cfg(feature = "std")]
690pub fn resolve_location_from_filesystem(location: &Location) -> Option<(PathBuf, u32)> {
691    let path = resolve_source_path(location.uri())?;
692    let bytes = std::fs::read(&path).ok()?;
693    let start = location.start.to_usize().min(bytes.len());
694    let line = bytes[..start].iter().filter(|byte| **byte == b'\n').count() as u32 + 1;
695    Some((path, line))
696}
697
698/// Returns true for source paths emitted by compiler/runtime internals rather than user code.
699pub fn is_internal_source_uri(uri: &Uri) -> bool {
700    let path = uri.as_str().replace('\\', "/");
701    path.contains("/codegen/masm/intrinsics/") || path.contains("/rustlib/src/rust/library/")
702}
703
704#[derive(Debug, Clone)]
705pub struct ResolvedLocation {
706    pub source_file: Arc<SourceFile>,
707    // TODO(fabrio): Use LineNumber and ColumnNumber instead of raw `u32`.
708    pub line: u32,
709    pub col: u32,
710    pub span: SourceSpan,
711}
712impl fmt::Display for ResolvedLocation {
713    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
714        write!(f, "{}:{}:{}", self.source_file.uri().as_str(), self.line, self.col)
715    }
716}
717
718pub struct CurrentFrame {
719    pub procedure: Option<Arc<str>>,
720    pub location: Option<ResolvedLocation>,
721}
722
723pub struct StackTrace<'a> {
724    callstack: &'a CallStack,
725    recent: &'a VecDeque<Operation>,
726    source_manager: &'a dyn SourceManager,
727    current_frame: Option<CurrentFrame>,
728}
729
730impl<'a> StackTrace<'a> {
731    pub fn new(
732        callstack: &'a CallStack,
733        recent: &'a VecDeque<Operation>,
734        source_manager: &'a dyn SourceManager,
735    ) -> Self {
736        let current_frame = callstack.logical_frames("").last().map(|frame| {
737            let location = frame.resolved(source_manager);
738            let procedure = Some(Arc::from(frame.display_name().into_boxed_str()));
739            CurrentFrame {
740                procedure,
741                location,
742            }
743        });
744        Self {
745            callstack,
746            recent,
747            source_manager,
748            current_frame,
749        }
750    }
751
752    pub fn current_frame(&self) -> Option<&CurrentFrame> {
753        self.current_frame.as_ref()
754    }
755}
756
757impl fmt::Display for StackTrace<'_> {
758    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
759        use core::fmt::Write;
760
761        let frames = self.callstack.logical_frames("");
762        let num_frames = frames.len();
763
764        writeln!(f, "\nStack Trace:")?;
765
766        for (i, frame) in frames.iter().enumerate() {
767            let is_top = i + 1 == num_frames;
768            let name = frame.display_name();
769            if is_top {
770                write!(f, " `-> {name}")?;
771            } else {
772                write!(f, " |-> {name}")?;
773            }
774            if let Some(resolved) = frame.resolved(self.source_manager) {
775                write!(f, " in {resolved}")?;
776            } else {
777                write!(f, " in <unavailable>")?;
778            }
779            if is_top {
780                let physical_frame = &self.callstack.frames[frame.physical_index()];
781                // Print op context
782                let context_size = physical_frame.context.len();
783                writeln!(f, ":\n\nLast {context_size} Instructions (of current frame):")?;
784                for (i, op) in physical_frame.context.iter().enumerate() {
785                    let is_last = i + 1 == context_size;
786                    if let Some(callee) = op.callee("") {
787                        write!(f, " |   exec.{callee}")?;
788                    } else {
789                        write!(f, " |   {}", op.opcode())?;
790                    }
791                    if is_last {
792                        writeln!(f, "\n `-> <error occurred here>")?;
793                    } else {
794                        f.write_char('\n')?;
795                    }
796                }
797
798                let context_size = self.recent.len();
799                writeln!(f, "\n\nLast {context_size} Instructions (any frame):")?;
800                for (i, op) in self.recent.iter().enumerate() {
801                    let is_last = i + 1 == context_size;
802                    if is_last {
803                        writeln!(f, " |   {}", op)?;
804                        writeln!(f, " `-> <error occurred here>")?;
805                    } else {
806                        writeln!(f, " |   {}", op)?;
807                    }
808                }
809            } else {
810                f.write_char('\n')?;
811            }
812        }
813
814        Ok(())
815    }
816}
817
818fn resolve_assembly_location(
819    source_manager: &dyn SourceManager,
820    location: &Location,
821) -> Option<ResolvedLocation> {
822    let source_file = resolve_source_file_for_location(source_manager, location)?;
823    let span = SourceSpan::new(source_file.id(), location.start..location.end);
824    let file_line_col = source_file.location(span);
825    Some(ResolvedLocation {
826        source_file,
827        line: file_line_col.line.to_u32(),
828        col: file_line_col.column.to_u32(),
829        span,
830    })
831}
832
833#[cfg(feature = "std")]
834fn demangle(name: &str) -> String {
835    let mut input = name.as_bytes();
836    let mut demangled = Vec::with_capacity(input.len() * 2);
837    rustc_demangle::demangle_stream(&mut input, &mut demangled, /* include_hash= */ false)
838        .expect("failed to write demangled identifier");
839    String::from_utf8(demangled).expect("demangled identifier contains invalid utf-8")
840}
841
842#[cfg(not(feature = "std"))]
843fn demangle(name: &str) -> String {
844    rustc_demangle::demangle(name).to_string()
845}
846
847#[cfg(test)]
848mod tests;