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::{
16 DebugFunctionIdx, DebugFunctionInfo, DebugSourceInlineCall, DebugSourceNodeId, PackageDebugInfo,
17};
18use miden_processor::{ContextId, SourceInlineCallContext, operation::Operation, trace::RowIndex};
19use miden_utils_sync::RwLock;
20
21use crate::Event;
22
23mod recovered;
24pub use recovered::{InvocationId, RecoveredCallStack, ReturnTarget};
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq)]
27pub enum ControlFlowOp {
28 Span,
29 Respan,
30 Join,
31 Split,
32 End,
33}
34
35pub struct StepInfo<'a> {
36 pub op: Option<Operation>,
37 pub control: Option<ControlFlowOp>,
38 pub asmop: Option<&'a AssemblyOp>,
39 pub clk: RowIndex,
40 pub ctx: ContextId,
41 pub inline_frames: &'a [InlineCallFrame],
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct InlineCallFrame {
46 name: Arc<str>,
47 call_site: Location,
48}
49
50impl InlineCallFrame {
51 pub fn name(&self) -> &str {
52 &self.name
53 }
54
55 pub fn call_site(&self) -> &Location {
56 &self.call_site
57 }
58
59 pub fn display_name(&self) -> String {
60 demangle(&self.name)
61 }
62}
63
64#[derive(Debug, Copy, Clone, PartialEq, Eq)]
65pub enum LogicalFrameKind {
66 Physical,
67 Inline,
68}
69
70#[derive(Debug, Clone)]
71enum LogicalFrameLocation {
72 Assembly(Location),
73 Resolved(ResolvedLocation),
74}
75
76#[derive(Debug, Clone)]
77pub struct LogicalStackFrame {
78 name: Arc<str>,
79 kind: LogicalFrameKind,
80 location: Option<LogicalFrameLocation>,
81 physical_index: Option<usize>,
82 origin: Option<miden_processor::DebugFrameOrigin>,
83 invocation: Option<InvocationId>,
84 source: Option<LogicalFrameSource>,
85}
86
87#[derive(Debug, Clone)]
88enum LogicalFrameSource {
89 Physical(miden_processor::DebugCallFrame),
90 Inline {
91 owner: InvocationId,
92 info: Arc<PackageDebugInfo>,
93 function_idx: DebugFunctionIdx,
94 call_site: Location,
95 },
96}
97
98impl LogicalStackFrame {
99 pub(crate) fn is_same_source_frame(&self, other: &Self) -> bool {
101 match (&self.source, &other.source) {
102 (
103 Some(LogicalFrameSource::Physical(left)),
104 Some(LogicalFrameSource::Physical(right)),
105 ) => left.is_same_frame(right),
106 (
107 Some(LogicalFrameSource::Inline {
108 owner: left_owner,
109 info: left_info,
110 function_idx: left_function,
111 call_site: left_site,
112 }),
113 Some(LogicalFrameSource::Inline {
114 owner: right_owner,
115 info: right_info,
116 function_idx: right_function,
117 call_site: right_site,
118 }),
119 ) => {
120 left_owner == right_owner
121 && Arc::ptr_eq(left_info, right_info)
122 && left_function == right_function
123 && left_site == right_site
124 }
125 _ => false,
126 }
127 }
128
129 pub(crate) fn debug_info(&self) -> Option<&PackageDebugInfo> {
130 match self.source.as_ref()? {
131 LogicalFrameSource::Physical(frame) => Some(frame.debug_info()),
132 LogicalFrameSource::Inline { info, .. } => Some(info),
133 }
134 }
135
136 pub(crate) fn function(&self) -> Option<&DebugFunctionInfo> {
137 match self.source.as_ref()? {
138 LogicalFrameSource::Physical(frame) => Some(frame.function()),
139 LogicalFrameSource::Inline {
140 info, function_idx, ..
141 } => info.get_function(*function_idx),
142 }
143 }
144
145 pub fn invocation_id(&self) -> Option<InvocationId> {
147 self.invocation
148 }
149
150 pub fn name(&self) -> &str {
151 &self.name
152 }
153
154 pub fn kind(&self) -> LogicalFrameKind {
155 self.kind
156 }
157
158 pub fn physical_index(&self) -> Option<usize> {
159 self.physical_index
160 }
161
162 pub fn origin(&self) -> Option<miden_processor::DebugFrameOrigin> {
163 self.origin
164 }
165
166 pub fn assembly_location(&self) -> Option<&Location> {
167 match self.location.as_ref()? {
168 LogicalFrameLocation::Assembly(location) => Some(location),
169 LogicalFrameLocation::Resolved(_) => None,
170 }
171 }
172
173 pub fn display_name(&self) -> String {
174 if self
175 .origin
176 .is_some_and(|origin| origin != miden_processor::DebugFrameOrigin::Source)
177 {
178 return format!("[inferred] {}", self.name);
179 }
180 match self.kind {
181 LogicalFrameKind::Physical => self.name.to_string(),
182 LogicalFrameKind::Inline => format!("[inlined] {}", self.name),
183 }
184 }
185
186 pub fn resolved(&self, source_manager: &dyn SourceManager) -> Option<ResolvedLocation> {
187 match self.location.as_ref()? {
188 LogicalFrameLocation::Assembly(location) => {
189 resolve_assembly_location(source_manager, location)
190 }
191 LogicalFrameLocation::Resolved(resolved) => Some(resolved.clone()),
192 }
193 }
194}
195
196pub fn inline_frames_for_operation<'a>(
201 current: Option<(&PackageDebugInfo, DebugSourceNodeId, u32)>,
202 inherited: impl IntoIterator<Item = &'a SourceInlineCallContext>,
203) -> Vec<InlineCallFrame> {
204 let mut frames = Vec::new();
205 if let Some((debug_info, source_node, op_idx)) = current {
206 append_inline_frames(
207 &mut frames,
208 debug_info,
209 debug_info.inline_calls_for_operation(source_node, op_idx),
210 );
211 }
212 for context in inherited {
213 append_inline_frames(&mut frames, context.debug_info(), context.inline_calls());
214 }
215 frames
216}
217
218fn append_inline_frames<'a>(
219 frames: &mut Vec<InlineCallFrame>,
220 debug_info: &PackageDebugInfo,
221 rows: impl IntoIterator<Item = &'a DebugSourceInlineCall>,
222) {
223 frames.extend(rows.into_iter().filter_map(|row| {
224 let function = debug_info.get_function(row.callee_idx)?;
225 let name = debug_info.get_string(function.name_idx)?;
226 let call_site = debug_info.get_location(row.loc_idx)?;
227 Some(InlineCallFrame { name, call_site })
228 }));
229}
230
231#[derive(Debug, Clone)]
232struct SpanContext {
233 frame_index: usize,
234 location: Option<Location>,
235}
236
237pub struct CallStack {
238 events: Arc<RwLock<BTreeMap<RowIndex, Event>>>,
239 contexts: BTreeSet<Arc<str>>,
240 frames: Vec<CallFrame>,
241 block_stack: Vec<Option<SpanContext>>,
242 recovered: RecoveredCallStack,
243 frame_transition: Option<FrameTransition>,
244}
245
246#[derive(Debug, Clone, Copy, PartialEq, Eq)]
247pub enum FrameTransition {
248 Entered,
249 Exited,
250}
251
252impl CallStack {
253 pub fn new(events: Arc<RwLock<BTreeMap<RowIndex, Event>>>) -> Self {
254 Self {
255 events,
256 contexts: BTreeSet::default(),
257 frames: vec![],
258 block_stack: vec![],
259 recovered: RecoveredCallStack::default(),
260 frame_transition: None,
261 }
262 }
263
264 #[cfg(feature = "dap")]
266 pub fn from_remote_frames(frames: Vec<CallFrame>) -> Self {
267 Self {
268 events: Arc::new(Default::default()),
269 contexts: BTreeSet::default(),
270 frames,
271 block_stack: vec![],
272 recovered: RecoveredCallStack::default(),
273 frame_transition: None,
274 }
275 }
276
277 pub fn stacktrace<'a>(
278 &'a self,
279 recent: &'a VecDeque<Operation>,
280 source_manager: &'a dyn SourceManager,
281 ) -> StackTrace<'a> {
282 StackTrace::new(self, recent, source_manager)
283 }
284
285 pub fn current_frame(&self) -> Option<&CallFrame> {
286 self.frames.last()
287 }
288
289 pub fn current_frame_mut(&mut self) -> Option<&mut CallFrame> {
290 self.frames.last_mut()
291 }
292
293 pub fn frames(&self) -> &[CallFrame] {
294 self.frames.as_slice()
295 }
296
297 pub fn return_target(&self) -> ReturnTarget {
299 self.recovered.return_target(self.frames.len())
300 }
301
302 pub fn has_returned(&self, target: &ReturnTarget) -> bool {
303 target.has_returned(&self.recovered, self.frames.len())
304 }
305
306 pub fn current_invocation(&self) -> Option<InvocationId> {
307 self.recovered.current_invocation()
308 }
309
310 pub fn invocation_ids(&self) -> impl Iterator<Item = InvocationId> + '_ {
311 self.recovered.invocation_ids()
312 }
313
314 pub fn has_recovered_current_frame(&self) -> bool {
315 self.recovered.has_current_frame()
316 }
317
318 pub fn observe_source_context(
319 &mut self,
320 context: &miden_processor::ResumeContext,
321 current: Option<(DebugSourceNodeId, u32)>,
322 asmop: Option<&AssemblyOp>,
323 ) {
324 self.recovered.update(context, current, asmop);
325 }
326
327 pub fn logical_frames(&self, strip_prefix: &str) -> Vec<LogicalStackFrame> {
328 if self.recovered.has_current_frame() {
329 return self.recovered.logical_frames(strip_prefix);
330 }
331 let mut logical = Vec::new();
332 for (physical_index, frame) in self.frames.iter().enumerate() {
333 let current_location = frame.last_logical_location();
334 let location = frame
335 .inline_frames
336 .last()
337 .map(|inline| LogicalFrameLocation::Assembly(inline.call_site.clone()))
338 .or_else(|| current_location.clone());
339 logical.push(LogicalStackFrame {
340 name: frame.procedure(strip_prefix).unwrap_or_else(|| Arc::from("<unknown>")),
341 kind: LogicalFrameKind::Physical,
342 location,
343 physical_index: Some(physical_index),
344 origin: None,
345 invocation: None,
346 source: None,
347 });
348
349 for inline_index in (0..frame.inline_frames.len()).rev() {
350 let inline = &frame.inline_frames[inline_index];
351 let location = if inline_index == 0 {
352 current_location.clone()
353 } else {
354 Some(LogicalFrameLocation::Assembly(
355 frame.inline_frames[inline_index - 1].call_site.clone(),
356 ))
357 };
358 logical.push(LogicalStackFrame {
359 name: Arc::from(inline.display_name().into_boxed_str()),
360 kind: LogicalFrameKind::Inline,
361 location,
362 physical_index: Some(physical_index),
363 origin: None,
364 invocation: None,
365 source: None,
366 });
367 }
368 }
369 logical
370 }
371
372 pub fn take_frame_transition(&mut self) -> Option<FrameTransition> {
375 self.frame_transition.take()
376 }
377
378 pub fn next(&mut self, info: &StepInfo<'_>) -> Option<CallFrame> {
382 let procedure = info.asmop.map(|op| self.cache_procedure_name(op.context_name()));
383
384 let event = {
385 let mut events = self.events.write();
386 match events.first_key_value() {
387 Some((clk, _)) if *clk <= info.clk => events.pop_first().map(|(_, event)| event),
388 _ => None,
389 }
390 };
391 log::trace!("handling {:?}/{:?} at cycle {}: {:?}", info.control, info.op, info.clk, event);
392 let is_frame_start = event.as_ref().is_some_and(|event| event.is_frame_start());
393 let is_frame_end = event.as_ref().is_some_and(|event| event.is_frame_end());
394 self.frame_transition = match (is_frame_start, is_frame_end) {
395 (true, _) => Some(FrameTransition::Entered),
396 (_, true) => Some(FrameTransition::Exited),
397 _ => None,
398 };
399 let popped_frame = self.handle_event(event, procedure.clone(), info.op, info.asmop);
400
401 match info.control {
402 Some(ControlFlowOp::Span) => {
403 if let Some(asmop) = info.asmop {
404 log::debug!("{asmop:#?}");
405 self.block_stack.push(Some(SpanContext {
406 frame_index: self.frames.len().saturating_sub(1),
407 location: asmop.location().cloned(),
408 }));
409 } else {
410 self.block_stack.push(None);
411 }
412 }
413 Some(ControlFlowOp::Join | ControlFlowOp::Split) => {
414 self.block_stack.push(None);
415 }
416 Some(ControlFlowOp::End) => {
417 self.block_stack.pop();
418 }
419 Some(ControlFlowOp::Respan) | None => {}
420 }
421
422 if !is_frame_end {
423 if self.frames.is_empty() {
424 self.frames.push(CallFrame::new(procedure.clone()));
425 }
426 self.frames.last_mut().unwrap().inline_frames = info.inline_frames.to_vec();
427 self.update_current_procedure(procedure.clone());
428 }
429
430 if is_frame_start || is_frame_end {
431 return popped_frame;
432 }
433
434 let Some(op) = info.op else {
435 return popped_frame;
436 };
437
438 let (procedure, asmop) = match procedure {
441 proc @ Some(_) => (proc, info.asmop.map(Cow::Borrowed)),
442 None => match self.block_stack.last() {
443 Some(Some(span_ctx)) => {
444 let proc =
445 self.frames.get(span_ctx.frame_index).and_then(|f| f.procedure.clone());
446 let asmop_cow = info.asmop.map(Cow::Borrowed).or_else(|| {
447 let context_name = proc.as_deref().unwrap_or("<unknown>").to_string();
448 let raw_asmop = AssemblyOp::new(
449 span_ctx.location.clone(),
450 context_name,
451 1,
452 op.to_string(),
453 );
454 Some(Cow::Owned(raw_asmop))
455 });
456 (proc, asmop_cow)
457 }
458 _ => (None, info.asmop.map(Cow::Borrowed)),
459 },
460 };
461
462 let procedure = procedure.or_else(|| self.frames.last().and_then(|f| f.procedure.clone()));
465
466 self.update_current_procedure(procedure);
469 let current_frame = self.frames.last_mut().unwrap();
470
471 if !matches!(op, Operation::Noop) {
473 let cycle_idx = info.asmop.map(|a| a.num_cycles()).unwrap_or(1);
474 current_frame.push(op, cycle_idx, asmop.as_deref());
475 }
476
477 popped_frame
478 }
479
480 fn update_current_procedure(&mut self, procedure: Option<Arc<str>>) {
481 let context_initialized = self
482 .frames
483 .last_mut()
484 .is_some_and(|frame| frame.update_procedure(procedure.clone()));
485 let num_frames = self.frames.len();
486 if context_initialized && num_frames > 1 {
487 let caller_frame = &mut self.frames[num_frames - 2];
488 if let Some(OpDetail::Exec { callee }) = caller_frame.context.back_mut()
489 && callee.is_none()
490 {
491 *callee = procedure;
492 }
493 }
494 }
495
496 fn cache_procedure_name(&mut self, context_name: &str) -> Arc<str> {
498 match self.contexts.get(context_name) {
499 Some(name) => Arc::clone(name),
500 None => {
501 let name = Arc::from(context_name.to_string().into_boxed_str());
502 self.contexts.insert(Arc::clone(&name));
503 name
504 }
505 }
506 }
507
508 fn handle_event(
509 &mut self,
510 event: Option<Event>,
511 procedure: Option<Arc<str>>,
512 op: Option<Operation>,
513 asmop: Option<&AssemblyOp>,
514 ) -> Option<CallFrame> {
515 match event? {
517 Event::FrameStart => {
518 if let Some(current_frame) = self.frames.last_mut() {
520 current_frame.push_exec(procedure.clone());
521 }
522 let mut frame = CallFrame::new(procedure);
524 if let Some(op) = op {
525 frame.push(op, 0, asmop);
526 }
527 self.frames.push(frame);
528 }
529 Event::Unknown(code) => log::debug!("unknown trace event: {code}"),
530 Event::FrameEnd => {
531 return self.frames.pop();
532 }
533 _ => (),
534 }
535 None
536 }
537}
538
539pub struct CallFrame {
540 procedure: Option<Arc<str>>,
541 context: VecDeque<OpDetail>,
542 display_name: OnceCell<Arc<str>>,
543 finishing: bool,
544 inline_frames: Vec<InlineCallFrame>,
545}
546impl CallFrame {
547 pub fn new(procedure: Option<Arc<str>>) -> Self {
548 Self {
549 procedure,
550 context: Default::default(),
551 display_name: Default::default(),
552 finishing: false,
553 inline_frames: Vec::new(),
554 }
555 }
556
557 #[cfg(feature = "dap")]
563 pub fn from_remote(procedure: Option<Arc<str>>, resolved: Option<ResolvedLocation>) -> Self {
564 let mut context = VecDeque::new();
565 if let Some(loc) = resolved {
566 let cell = OnceCell::new();
567 cell.set(Some(loc)).ok();
568 context.push_back(OpDetail::Full {
569 op: miden_processor::operation::Operation::Noop,
570 location: None,
571 resolved: cell,
572 });
573 }
574 Self {
575 procedure,
576 context,
577 display_name: Default::default(),
578 finishing: false,
579 inline_frames: Vec::new(),
580 }
581 }
582
583 pub fn procedure(&self, strip_prefix: &str) -> Option<Arc<str>> {
584 self.procedure.as_ref()?;
585 let name = self.display_name.get_or_init(|| {
586 let name = self.procedure.as_deref().unwrap();
587 let name = match name.split_once("::") {
588 Some((module, rest)) if module == strip_prefix => demangle(rest),
589 _ => demangle(name),
590 };
591 Arc::<str>::from(name.into_boxed_str())
592 });
593 Some(Arc::clone(name))
594 }
595
596 fn update_procedure(&mut self, procedure: Option<Arc<str>>) -> bool {
600 let Some(procedure) = procedure else {
601 return false;
602 };
603 if self.procedure.as_ref() == Some(&procedure) {
604 return false;
605 }
606
607 let initialized = self.procedure.is_none();
608 self.procedure = Some(procedure);
609 self.display_name.take();
610 initialized
611 }
612
613 pub fn push_exec(&mut self, callee: Option<Arc<str>>) {
614 if self.context.len() == 5 {
615 self.context.pop_front();
616 }
617
618 self.context.push_back(OpDetail::Exec { callee });
619 }
620
621 pub fn push(&mut self, opcode: Operation, cycle_idx: u8, op: Option<&AssemblyOp>) {
622 if cycle_idx > 1 {
623 let skip = self.context.back().map(|detail| matches!(detail, OpDetail::Full { op, .. } | OpDetail::Basic { op } if op == &opcode)).unwrap_or(false);
625 if skip {
626 return;
627 }
628 }
629
630 if self.context.len() == 5 {
631 self.context.pop_front();
632 }
633
634 match op {
635 Some(op) => {
636 let location = op.location().cloned();
637 self.context.push_back(OpDetail::Full {
638 op: opcode,
639 location,
640 resolved: Default::default(),
641 });
642 }
643 None => {
644 if let Some(loc) = self.context.back().map(|op| op.location().cloned()) {
647 self.context.push_back(OpDetail::Full {
648 op: opcode,
649 location: loc,
650 resolved: Default::default(),
651 });
652 } else {
653 self.context.push_back(OpDetail::Basic { op: opcode });
654 }
655 }
656 }
657 }
658
659 pub fn last_location(&self) -> Option<&Location> {
660 self.context.iter().rev().find_map(OpDetail::location)
661 }
662
663 fn last_logical_location(&self) -> Option<LogicalFrameLocation> {
664 self.context.iter().rev().find_map(|detail| {
665 detail
666 .location()
667 .cloned()
668 .map(LogicalFrameLocation::Assembly)
669 .or_else(|| detail.cached_resolved().cloned().map(LogicalFrameLocation::Resolved))
670 })
671 }
672
673 pub fn last_resolved(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
674 for op in self.context.iter().rev() {
677 if let Some(resolved) = op.resolve(source_manager) {
678 return Some(resolved);
679 }
680 }
681 None
682 }
683
684 pub fn recent(&self) -> &VecDeque<OpDetail> {
685 &self.context
686 }
687
688 #[inline(always)]
689 pub fn should_break_on_exit(&self) -> bool {
690 self.finishing
691 }
692
693 #[inline(always)]
694 pub fn break_on_exit(&mut self) {
695 self.finishing = true;
696 }
697}
698
699#[derive(Debug, Clone)]
700pub enum OpDetail {
701 Full {
702 op: Operation,
703 location: Option<Location>,
704 resolved: OnceCell<Option<ResolvedLocation>>,
705 },
706 Exec {
707 callee: Option<Arc<str>>,
708 },
709 Basic {
710 op: Operation,
711 },
712}
713impl OpDetail {
714 pub fn callee(&self, strip_prefix: &str) -> Option<Box<str>> {
715 match self {
716 Self::Exec { callee: None } => Some(Box::from("<unknown>")),
717 Self::Exec {
718 callee: Some(callee),
719 } => {
720 let name = match callee.split_once("::") {
721 Some((module, rest)) if module == strip_prefix => demangle(rest),
722 _ => demangle(callee),
723 };
724 Some(name.into_boxed_str())
725 }
726 _ => None,
727 }
728 }
729
730 pub fn display(&self) -> String {
731 match self {
732 Self::Full { op, .. } | Self::Basic { op } => format!("{op}"),
733 Self::Exec {
734 callee: Some(callee),
735 } => format!("exec.{callee}"),
736 Self::Exec { callee: None } => "exec.<unavailable>".to_string(),
737 }
738 }
739
740 pub fn opcode(&self) -> Operation {
741 match self {
742 Self::Full { op, .. } | Self::Basic { op } => *op,
743 Self::Exec { .. } => panic!("no opcode associated with execs"),
744 }
745 }
746
747 pub fn location(&self) -> Option<&Location> {
748 match self {
749 Self::Full { location, .. } => location.as_ref(),
750 Self::Basic { .. } | Self::Exec { .. } => None,
751 }
752 }
753
754 pub fn resolve(&self, source_manager: &dyn SourceManager) -> Option<&ResolvedLocation> {
755 match self {
756 Self::Full {
757 location, resolved, ..
758 } => {
759 if let Some(cached) = resolved.get() {
760 return cached.as_ref();
761 }
762 let loc = location.as_ref()?;
763 resolved
764 .get_or_init(|| {
765 let source_file = resolve_source_file_for_location(source_manager, loc)?;
766 let span = SourceSpan::new(source_file.id(), loc.start..loc.end);
767 let file_line_col = source_file.location(span);
768 Some(ResolvedLocation {
769 source_file,
770 line: file_line_col.line.to_u32(),
771 col: file_line_col.column.to_u32(),
772 span,
773 })
774 })
775 .as_ref()
776 }
777 _ => None,
778 }
779 }
780
781 fn cached_resolved(&self) -> Option<&ResolvedLocation> {
782 match self {
783 Self::Full { resolved, .. } => resolved.get().and_then(Option::as_ref),
784 Self::Exec { .. } | Self::Basic { .. } => None,
785 }
786 }
787}
788
789#[cfg(feature = "std")]
795pub fn resolve_source_file_for_location(
796 source_manager: &dyn SourceManager,
797 location: &Location,
798) -> Option<Arc<SourceFile>> {
799 use miden_assembly_syntax::debuginfo::SourceManagerExt;
800 source_manager.get_by_uri(location.uri()).or_else(|| {
801 resolve_source_path(location.uri()).and_then(|path| source_manager.load_file(&path).ok())
802 })
803}
804
805#[cfg(not(feature = "std"))]
806pub fn resolve_source_file_for_location(
807 source_manager: &dyn SourceManager,
808 location: &Location,
809) -> Option<Arc<SourceFile>> {
810 source_manager.get_by_uri(location.uri())
811}
812
813#[cfg(feature = "std")]
818pub fn resolve_source_path(uri: &Uri) -> Option<PathBuf> {
819 let path = match uri.scheme() {
820 None | Some("file") => uri.to_path()?,
821 Some(_) => return None,
822 };
823
824 fn existing_path(path: &Path) -> Option<PathBuf> {
825 path.exists()
826 .then(|| path.canonicalize().unwrap_or_else(|_| path.to_path_buf()))
827 }
828
829 existing_path(&path).or_else(|| {
830 if path.is_relative() {
831 std::env::current_dir().ok().and_then(|cwd| existing_path(&cwd.join(path)))
832 } else {
833 None
834 }
835 })
836}
837
838#[cfg(feature = "std")]
840pub fn resolve_location_from_filesystem(location: &Location) -> Option<(PathBuf, u32)> {
841 let path = resolve_source_path(location.uri())?;
842 let bytes = std::fs::read(&path).ok()?;
843 let start = location.start.to_usize().min(bytes.len());
844 let line = bytes[..start].iter().filter(|byte| **byte == b'\n').count() as u32 + 1;
845 Some((path, line))
846}
847
848pub fn is_internal_source_uri(uri: &Uri) -> bool {
850 let path = uri.as_str().replace('\\', "/");
851 path.contains("/codegen/masm/intrinsics/") || path.contains("/rustlib/src/rust/library/")
852}
853
854#[derive(Debug, Clone)]
855pub struct ResolvedLocation {
856 pub source_file: Arc<SourceFile>,
857 pub line: u32,
859 pub col: u32,
860 pub span: SourceSpan,
861}
862impl fmt::Display for ResolvedLocation {
863 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
864 write!(f, "{}:{}:{}", self.source_file.uri().as_str(), self.line, self.col)
865 }
866}
867
868pub struct CurrentFrame {
869 pub procedure: Option<Arc<str>>,
870 pub location: Option<ResolvedLocation>,
871}
872
873pub struct StackTrace<'a> {
874 callstack: &'a CallStack,
875 recent: &'a VecDeque<Operation>,
876 source_manager: &'a dyn SourceManager,
877 current_frame: Option<CurrentFrame>,
878}
879
880impl<'a> StackTrace<'a> {
881 pub fn new(
882 callstack: &'a CallStack,
883 recent: &'a VecDeque<Operation>,
884 source_manager: &'a dyn SourceManager,
885 ) -> Self {
886 let current_frame = callstack.logical_frames("").last().map(|frame| {
887 let location = frame.resolved(source_manager);
888 let procedure = Some(Arc::from(frame.display_name().into_boxed_str()));
889 CurrentFrame {
890 procedure,
891 location,
892 }
893 });
894 Self {
895 callstack,
896 recent,
897 source_manager,
898 current_frame,
899 }
900 }
901
902 pub fn current_frame(&self) -> Option<&CurrentFrame> {
903 self.current_frame.as_ref()
904 }
905}
906
907impl fmt::Display for StackTrace<'_> {
908 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
909 use core::fmt::Write;
910
911 let frames = self.callstack.logical_frames("");
912 let num_frames = frames.len();
913
914 writeln!(f, "\nStack Trace:")?;
915
916 for (i, frame) in frames.iter().enumerate() {
917 let is_top = i + 1 == num_frames;
918 let name = frame.display_name();
919 if is_top {
920 write!(f, " `-> {name}")?;
921 } else {
922 write!(f, " |-> {name}")?;
923 }
924 if let Some(resolved) = frame.resolved(self.source_manager) {
925 write!(f, " in {resolved}")?;
926 } else {
927 write!(f, " in <unavailable>")?;
928 }
929 if is_top {
930 let context = self.callstack.current_frame().map(CallFrame::recent);
931 let context_size = context.map_or(0, VecDeque::len);
933 writeln!(f, ":\n\nLast {context_size} Instructions (of current frame):")?;
934 for (i, op) in context.into_iter().flatten().enumerate() {
935 let is_last = i + 1 == context_size;
936 if let Some(callee) = op.callee("") {
937 write!(f, " | exec.{callee}")?;
938 } else {
939 write!(f, " | {}", op.opcode())?;
940 }
941 if is_last {
942 writeln!(f, "\n `-> <error occurred here>")?;
943 } else {
944 f.write_char('\n')?;
945 }
946 }
947
948 let context_size = self.recent.len();
949 writeln!(f, "\n\nLast {context_size} Instructions (any frame):")?;
950 for (i, op) in self.recent.iter().enumerate() {
951 let is_last = i + 1 == context_size;
952 if is_last {
953 writeln!(f, " | {}", op)?;
954 writeln!(f, " `-> <error occurred here>")?;
955 } else {
956 writeln!(f, " | {}", op)?;
957 }
958 }
959 } else {
960 f.write_char('\n')?;
961 }
962 }
963
964 Ok(())
965 }
966}
967
968fn resolve_assembly_location(
969 source_manager: &dyn SourceManager,
970 location: &Location,
971) -> Option<ResolvedLocation> {
972 let source_file = resolve_source_file_for_location(source_manager, location)?;
973 let span = SourceSpan::new(source_file.id(), location.start..location.end);
974 let file_line_col = source_file.location(span);
975 Some(ResolvedLocation {
976 source_file,
977 line: file_line_col.line.to_u32(),
978 col: file_line_col.column.to_u32(),
979 span,
980 })
981}
982
983#[cfg(feature = "std")]
984pub(super) fn demangle(name: &str) -> String {
985 let mut input = name.as_bytes();
986 let mut demangled = Vec::with_capacity(input.len() * 2);
987 rustc_demangle::demangle_stream(&mut input, &mut demangled, false)
988 .expect("failed to write demangled identifier");
989 String::from_utf8(demangled).expect("demangled identifier contains invalid utf-8")
990}
991
992#[cfg(not(feature = "std"))]
993pub(super) fn demangle(name: &str) -> String {
994 match name.rsplit_once("::") {
996 Some((prefix, symbol)) => format!("{prefix}::{:#}", rustc_demangle::demangle(symbol)),
997 None => format!("{:#}", rustc_demangle::demangle(name)),
998 }
999}
1000
1001#[cfg(test)]
1002mod tests;