1use alloc::{
2 collections::{BTreeMap, BTreeSet},
3 sync::Arc,
4 vec,
5 vec::Vec,
6};
7
8use miden_core::{
9 Word,
10 mast::{MastForest, MastNodeExt, MastNodeId},
11};
12use miden_mast_package::debug_info::{
13 DebugFunctionIdx, DebugFunctionInfo, DebugSourceNode, DebugSourceNodeId, DebugStringIdx,
14 PackageDebugInfo,
15};
16
17use crate::{Continuation, ResumeContext, continuation_stack::DebugActivationId};
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub enum DebugFrameOrigin {
23 Source,
25 InferredRange,
27 InferredContext,
29 InferredInline,
31}
32
33#[derive(Clone, Debug)]
35pub struct DebugCallFrame {
36 debug_info: Arc<PackageDebugInfo>,
37 function_idx: DebugFunctionIdx,
38 source_node_id: DebugSourceNodeId,
39 range_start: u32,
40 activation: DebugActivationId,
41 inherited_inline_calls: usize,
42 origin: DebugFrameOrigin,
43}
44
45impl DebugCallFrame {
46 pub fn function_idx(&self) -> DebugFunctionIdx {
47 self.function_idx
48 }
49 pub fn function(&self) -> &DebugFunctionInfo {
50 &self.debug_info[self.function_idx]
51 }
52 pub fn debug_info(&self) -> &PackageDebugInfo {
53 &self.debug_info
54 }
55 pub fn inherited_inline_calls(&self) -> usize {
56 self.inherited_inline_calls
57 }
58 pub fn origin(&self) -> DebugFrameOrigin {
59 self.origin
60 }
61 pub fn is_inferred(&self) -> bool {
62 self.origin != DebugFrameOrigin::Source
63 }
64
65 pub fn is_same_frame(&self, other: &Self) -> bool {
67 Arc::ptr_eq(&self.debug_info, &other.debug_info)
68 && self.function_idx == other.function_idx
69 && self.source_node_id == other.source_node_id
70 && self.range_start == other.range_start
71 && self.activation == other.activation
72 }
73}
74
75#[derive(Clone)]
76struct FunctionRange {
77 function: DebugFunctionIdx,
78 start: u32,
79 end: u32,
80 signature: Signature,
81 explicit_boundary: bool,
83}
84
85impl FunctionRange {
86 fn matches_source(&self, node: &DebugSourceNode) -> bool {
87 let start = node.asm_ops.partition_point(|row| row.op_idx < self.start);
88 let end = node.asm_ops.partition_point(|row| row.op_idx < self.end);
89 let rows = &node.asm_ops[start..end];
90 rows.len() == self.signature.len()
91 && rows.iter().zip(&self.signature).all(
92 |(row, (offset, context, operation, cycles))| {
93 (self.explicit_boundary || row.op_idx - self.start == *offset)
94 && (row.context_name_idx, row.op_name_idx, row.num_cycles)
95 == (*context, *operation, *cycles)
96 },
97 )
98 }
99}
100
101type Signature = Vec<(u32, DebugStringIdx, DebugStringIdx, u8)>;
102
103fn signature(node: &DebugSourceNode) -> Signature {
104 node.asm_ops
105 .iter()
106 .map(|row| {
107 (
108 row.op_idx.saturating_sub(node.op_start),
109 row.context_name_idx,
110 row.op_name_idx,
111 row.num_cycles,
112 )
113 })
114 .collect()
115}
116
117fn inline_depths(node: &DebugSourceNode) -> BTreeMap<u32, (usize, usize)> {
120 let mut events = BTreeMap::<u32, (usize, usize, usize)>::new();
121 for row in &node.inline_calls {
122 if row.op_idx == row.op_end {
123 events.entry(row.op_idx).or_default().2 += 1;
124 } else {
125 events.entry(row.op_idx).or_default().0 += 1;
126 events.entry(row.op_end).or_default().1 += 1;
127 }
128 }
129 let mut active = 0;
130 events
131 .into_iter()
132 .map(|(position, (starts, ends, points))| {
133 active += starts;
134 active -= ends;
135 (position, (active, points))
136 })
137 .collect()
138}
139
140struct FunctionIndex {
141 info: Arc<PackageDebugInfo>,
142 forest: Arc<MastForest>,
143 sources: BTreeMap<DebugSourceNodeId, Vec<DebugFunctionIdx>>,
144 names: BTreeMap<Arc<str>, Vec<DebugFunctionIdx>>,
145 ranges: BTreeMap<MastNodeId, Vec<FunctionRange>>,
146 entry_inline_counts: BTreeMap<DebugFunctionIdx, usize>,
147 entry_names: BTreeMap<DebugSourceNodeId, Option<DebugStringIdx>>,
148 inline_depths: BTreeMap<DebugSourceNodeId, BTreeMap<u32, (usize, usize)>>,
149}
150
151impl FunctionIndex {
152 fn new(info: Arc<PackageDebugInfo>, forest: Arc<MastForest>) -> Self {
153 let mut index = Self {
154 info,
155 forest,
156 sources: BTreeMap::new(),
157 names: BTreeMap::new(),
158 ranges: BTreeMap::new(),
159 entry_inline_counts: BTreeMap::new(),
160 entry_names: BTreeMap::new(),
161 inline_depths: BTreeMap::new(),
162 };
163 let mut root_nodes = BTreeMap::<Word, MastNodeId>::new();
164 for root in index.forest.procedure_roots() {
165 root_nodes.entry(index.forest[*root].digest()).or_insert(*root);
166 }
167 let mut default_sources = BTreeMap::<MastNodeId, &DebugSourceNode>::new();
168 let mut named_sources = BTreeMap::<(MastNodeId, DebugStringIdx), &DebugSourceNode>::new();
169 let rank = |node: &DebugSourceNode| {
170 (node.asm_ops.len(), core::cmp::Reverse(node.inline_calls.len()))
171 };
172 for node in index.info.nodes() {
173 default_sources
174 .entry(node.exec_node)
175 .and_modify(|current| {
176 if rank(node) >= rank(current) {
177 *current = node;
178 }
179 })
180 .or_insert(node);
181 for row in &node.asm_ops {
182 named_sources
183 .entry((node.exec_node, row.context_name_idx))
184 .and_modify(|current| {
185 if rank(node) >= rank(current) {
186 *current = node;
187 }
188 })
189 .or_insert(node);
190 }
191 }
192 for position in 0..index.info.nodes().len() {
193 let mut source = DebugSourceNodeId::from(position as u32);
194 let mut path = Vec::new();
195 let name = loop {
196 if let Some(name) = index.entry_names.get(&source) {
197 break *name;
198 }
199 if path.len() >= index.info.nodes().len() {
200 break None;
201 }
202 path.push(source);
203 let node = &index.info[source];
204 if let Some(row) = node.asm_ops.first() {
205 break Some(row.context_name_idx);
206 }
207 let Some(child) = node.children.first() else {
208 break None;
209 };
210 source = *child;
211 };
212 for source in path {
213 index.entry_names.insert(source, name);
214 }
215 }
216 let mut signatures = BTreeMap::<Signature, Vec<DebugFunctionIdx>>::new();
217 let mut function_signatures = BTreeMap::<DebugFunctionIdx, Signature>::new();
218 for (position, function) in index.info.functions().iter().enumerate() {
219 let function_idx = DebugFunctionIdx::from(position as u32);
220 if let Some(source) = function.source_node.into_option() {
221 index.sources.entry(source).or_default().push(function_idx);
222 }
223 for name_idx in [Some(function.name_idx), function.linkage_name_idx.into_option()]
224 .into_iter()
225 .flatten()
226 {
227 if let Some(name) = index.info.get_string(name_idx) {
228 let names = index.names.entry(name).or_default();
229 if names.last() != Some(&function_idx) {
230 names.push(function_idx);
231 }
232 }
233 }
234 let Some(root) = root_nodes.get(&function.mast_root) else {
235 continue;
236 };
237 let canonical = [Some(function.name_idx), function.linkage_name_idx.into_option()]
238 .into_iter()
239 .flatten()
240 .filter_map(|name| named_sources.get(&(*root, name)).copied())
241 .max_by_key(|node| rank(node));
242 if let Some(node) = canonical.or_else(|| default_sources.get(root).copied()) {
243 index.entry_inline_counts.insert(
244 function_idx,
245 node.inline_calls.iter().filter(|row| row.op_idx == node.op_start).count(),
246 );
247 if canonical.is_some() && !node.asm_ops.is_empty() {
248 let signature = signature(node);
249 function_signatures.insert(function_idx, signature.clone());
250 signatures.entry(signature).or_default().push(function_idx);
251 }
252 }
253 }
254 let mut seen_ranges = BTreeSet::new();
255 for node in index.info.nodes().iter().filter(|node| !node.asm_ops.is_empty()) {
256 let Some(functions) = signatures.get(&signature(node)) else {
257 continue;
258 };
259 let [function] = functions.as_slice() else {
260 continue;
261 };
262 let range = FunctionRange {
263 function: *function,
264 start: node.op_start,
265 end: node.op_end,
266 signature: signature(node),
267 explicit_boundary: false,
268 };
269 let ranges = index.ranges.entry(node.exec_node).or_default();
270 if seen_ranges.insert((node.exec_node, range.function, range.start, range.end)) {
271 ranges.push(range);
272 }
273 }
274 for (position, node) in index.info.nodes().iter().enumerate() {
280 if !node.inline_calls.is_empty() {
281 index
282 .inline_depths
283 .insert(DebugSourceNodeId::from(position as u32), inline_depths(node));
284 }
285 for row in &node.inline_calls {
286 let Some(signature) = function_signatures.get(&row.callee_idx) else {
287 continue;
288 };
289 let range = FunctionRange {
290 function: row.callee_idx,
291 start: row.op_idx,
292 end: row.op_end,
293 signature: signature.clone(),
294 explicit_boundary: true,
295 };
296 if range.matches_source(node)
297 && seen_ranges.insert((node.exec_node, range.function, range.start, range.end))
298 {
299 index.ranges.entry(node.exec_node).or_default().push(range);
300 }
301 }
302 }
303 for ranges in index.ranges.values_mut() {
304 ranges.sort_by_key(|range| (range.start, core::cmp::Reverse(range.end)));
305 }
306 index
307 }
308
309 fn inline_depth_at(&self, source: DebugSourceNodeId, operation: u32) -> usize {
310 self.inline_depths
311 .get(&source)
312 .and_then(|depths| depths.range(..=operation).next_back())
313 .map_or(0, |(position, (active, points))| {
314 active + if *position == operation { *points } else { 0 }
315 })
316 }
317
318 fn source_function(&self, source: DebugSourceNodeId) -> Option<DebugFunctionIdx> {
319 let node = self.info.source_node(source)?;
320 let digest = self.forest[node.exec_node].digest();
321 let mut candidates = self
322 .sources
323 .get(&source)?
324 .iter()
325 .copied()
326 .filter(|function| self.info[*function].mast_root == digest);
327 let first = candidates.next()?;
328 candidates.next().is_none().then_some(first)
329 }
330
331 fn external_caller_functions<'a>(
332 &'a self,
333 node: &'a DebugSourceNode,
334 ) -> impl Iterator<Item = (DebugFunctionIdx, u32, usize)> + 'a {
335 let external_digest = self.forest[node.exec_node]
336 .is_external()
337 .then(|| self.forest[node.exec_node].digest());
338 node.inline_calls.iter().rev().filter_map(move |row| {
339 if !row.contains_operation(node.op_start) {
340 return None;
341 }
342 let function = self.info.get_function(row.callee_idx)?;
343 let canonical = self.info.source_node(function.source_node.into_option()?)?;
344 if canonical.exec_node != node.exec_node || Some(function.mast_root) != external_digest
345 {
346 return None;
347 }
348 let own_inline = canonical
349 .inline_calls
350 .iter()
351 .filter(|row| row.contains_operation(canonical.op_start))
352 .count();
353 Some((row.callee_idx, row.op_idx, own_inline))
354 })
355 }
356
357 fn context_function(&self, node: &DebugSourceNode, operation: u32) -> Option<DebugFunctionIdx> {
358 let row = node.asm_op_for_operation(operation)?;
359 if operation >= row.op_idx.saturating_add(u32::from(row.num_cycles)) {
360 return None;
361 }
362 let name = self.info.get_string(row.context_name_idx)?;
363 let [function] = self.names.get(&name)?.as_slice() else {
364 return None;
365 };
366 Some(*function)
367 }
368
369 fn entry_function(&self, source: DebugSourceNodeId) -> Option<DebugFunctionIdx> {
370 let name = self.info.get_string((*self.entry_names.get(&source)?)?)?;
371 let [function] = self.names.get(&name)?.as_slice() else {
372 return None;
373 };
374 let node = self.info.source_node(source)?;
375 (self.info[*function].mast_root == self.forest[node.exec_node].digest())
376 .then_some(*function)
377 }
378}
379
380#[derive(Default)]
383pub struct DebugCallFrameResolver {
384 indices: Vec<FunctionIndex>,
385}
386
387impl DebugCallFrameResolver {
388 pub fn new() -> Self {
389 Self::default()
390 }
391
392 fn index(&mut self, info: &Arc<PackageDebugInfo>, forest: &Arc<MastForest>) -> &FunctionIndex {
393 let position = self
394 .indices
395 .iter()
396 .position(|index| Arc::ptr_eq(&index.info, info) && Arc::ptr_eq(&index.forest, forest));
397 let position = position.unwrap_or_else(|| {
398 self.indices.push(FunctionIndex::new(info.clone(), forest.clone()));
399 self.indices.len() - 1
400 });
401 &self.indices[position]
402 }
403
404 pub fn resolve(&mut self, context: &ResumeContext) -> Vec<DebugCallFrame> {
407 let continuations =
408 context.continuation_stack.iter_with_source_node_ids().collect::<Vec<_>>();
409 let next_count = context.continuation_stack.iter_continuations_for_next_clock().count();
410 let next_start = continuations.len().saturating_sub(next_count);
411 let mut owners = vec![None; continuations.len()];
412 let mut info = context.package_debug_info.clone();
413 let mut forest = context.current_forest.clone();
414 let mut inline_depth = context.inline_call_contexts.len();
415 for (position, (continuation, _)) in continuations.iter().enumerate().rev() {
416 if let Continuation::EnterForest {
417 forest: caller_forest,
418 package_debug_info,
419 inline_context_depth,
420 ..
421 } = continuation
422 {
423 info = package_debug_info.clone();
424 forest = caller_forest.clone();
425 inline_depth = *inline_context_depth;
426 }
427 if matches!(continuation, Continuation::FinishDyn(_)) {
428 inline_depth = inline_depth.saturating_sub(1);
429 }
430 let inherited = context.inline_call_contexts
431 [..inline_depth.min(context.inline_call_contexts.len())]
432 .iter()
433 .filter_map(Option::as_ref)
434 .map(|entry| entry.inline_calls().count())
435 .sum::<usize>();
436 owners[position] = info.as_ref().map(|info| (info.clone(), forest.clone(), inherited));
437 }
438 let mut frames = Vec::<DebugCallFrame>::new();
439 for (position, (continuation, source)) in continuations.iter().enumerate() {
440 let ancestor = matches!(
441 continuation,
442 Continuation::FinishJoin(_)
443 | Continuation::FinishSplit(_)
444 | Continuation::FinishLoop(_)
445 | Continuation::FinishCall(_)
446 | Continuation::FinishDyn(_)
447 | Continuation::EnterForest { .. }
448 );
449 if position > next_start || (!ancestor && position != next_start) {
450 continue;
451 }
452 let (Some((info, forest, inherited)), Some(source)) = (&owners[position], source)
453 else {
454 continue;
455 };
456 let Some(node) = info.source_node(*source) else {
457 continue;
458 };
459 let Some(activation) = context.continuation_stack.debug_activation_at(position) else {
460 continue;
461 };
462 let operation = match continuation {
463 Continuation::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
464 let block = forest[*node_id].unwrap_basic_block();
465 (block
466 .op_batches()
467 .iter()
468 .take(*batch_index)
469 .map(|batch| batch.ops().len())
470 .sum::<usize>()
471 + op_idx_in_batch) as u32
472 },
473 Continuation::Respan { node_id, batch_index } => forest[*node_id]
474 .unwrap_basic_block()
475 .op_batches()
476 .iter()
477 .take(*batch_index)
478 .map(|batch| batch.ops().len() as u32)
479 .sum(),
480 Continuation::FinishBasicBlock(node_id) => {
481 forest[*node_id].unwrap_basic_block().num_operations().saturating_sub(1)
482 },
483 _ => node.op_start,
484 };
485 let index = self.index(info, forest);
486 let exact = index.source_function(*source);
487 if let Some(function) = exact.or_else(|| index.entry_function(*source)) {
488 let own_inline = index.entry_inline_counts.get(&function).copied().unwrap_or(0);
489 let inherited = inherited
490 + node
491 .inline_calls
492 .iter()
493 .filter(|row| row.op_idx == node.op_start)
494 .count()
495 .saturating_sub(own_inline);
496 frames.push(frame(
497 info,
498 function,
499 *source,
500 node.op_start,
501 activation.clone(),
502 inherited,
503 if exact.is_some() {
504 DebugFrameOrigin::Source
505 } else {
506 DebugFrameOrigin::InferredContext
507 },
508 ));
509 }
510 for (function, start, own_inline) in index.external_caller_functions(node) {
515 if frames.last().is_some_and(|frame| {
516 frame.function_idx == function && Arc::ptr_eq(&frame.debug_info, info)
517 }) {
518 continue;
519 }
520 let inherited = inherited
521 + node
522 .inline_calls
523 .iter()
524 .filter(|row| row.contains_operation(node.op_start))
525 .count()
526 .saturating_sub(own_inline);
527 frames.push(frame(
528 info,
529 function,
530 *source,
531 start,
532 activation.clone(),
533 inherited,
534 DebugFrameOrigin::InferredInline,
535 ));
536 }
537 if position != next_start {
538 continue;
539 }
540 if let Some(ranges) = index.ranges.get(&node.exec_node) {
541 let mut enclosing_end = node.op_end;
542 for range in
543 ranges.iter().filter(|range| range.start <= operation && operation < range.end)
544 {
545 if range.start < node.op_start
546 || range.end > enclosing_end
547 || !range.matches_source(node)
548 || frames.last().is_some_and(|frame| {
549 frame.function_idx == range.function
550 && Arc::ptr_eq(&frame.debug_info, info)
551 })
552 {
553 continue;
554 }
555 enclosing_end = range.end;
556 frames.push(frame(
557 info,
558 range.function,
559 *source,
560 range.start,
561 activation.clone(),
562 inherited
563 + index.inline_depth_at(*source, range.start).saturating_sub(
564 index
565 .entry_inline_counts
566 .get(&range.function)
567 .copied()
568 .unwrap_or(0),
569 ),
570 DebugFrameOrigin::InferredRange,
571 ));
572 }
573 }
574 if let Some(function) = index.context_function(node, operation)
575 && !frames.last().is_some_and(|frame| {
576 frame.function_idx == function && Arc::ptr_eq(&frame.debug_info, info)
577 })
578 {
579 let inline_calls =
580 node.inline_calls.iter().filter(|row| row.contains_operation(operation));
581 let own_inline =
584 inline_calls.clone().position(|row| row.callee_idx == function).unwrap_or_else(
585 || index.entry_inline_counts.get(&function).copied().unwrap_or(0),
586 );
587 let inherited = inherited + inline_calls.count().saturating_sub(own_inline);
588 frames.push(frame(
589 info,
590 function,
591 *source,
592 node.op_start,
593 activation,
594 inherited,
595 DebugFrameOrigin::InferredContext,
596 ));
597 }
598 }
599 frames
600 }
601}
602
603impl ResumeContext {
604 pub fn debug_call_frames(&self) -> Vec<DebugCallFrame> {
607 DebugCallFrameResolver::new().resolve(self)
608 }
609}
610
611fn frame(
612 info: &Arc<PackageDebugInfo>,
613 function_idx: DebugFunctionIdx,
614 source_node_id: DebugSourceNodeId,
615 range_start: u32,
616 activation: DebugActivationId,
617 inherited_inline_calls: usize,
618 origin: DebugFrameOrigin,
619) -> DebugCallFrame {
620 DebugCallFrame {
621 debug_info: info.clone(),
622 function_idx,
623 source_node_id,
624 range_start,
625 activation,
626 inherited_inline_calls,
627 origin,
628 }
629}
630
631#[cfg(test)]
632mod tests {
633 use alloc::{boxed::Box, format};
634
635 use miden_assembly::{Assembler, DefaultSourceManager};
636 use miden_core::serde::Serializable;
637 use miden_mast_package::debug_info::PackageDebugInfoBuilder;
638
639 use super::*;
640
641 #[test]
642 fn inline_depth_index_respects_adjacent_nested_and_point_ranges() {
643 use miden_mast_package::debug_info::{DebugLocIdx, DebugSourceInlineCall};
644 let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
645 .assemble_program("program", "begin push.7 drop end")
646 .unwrap();
647 let info = package.debug_info().unwrap().unwrap();
648 let mut node = info.nodes().iter().next().unwrap().clone();
649 node.inline_calls = [(0, 10), (0, 5), (5, 10), (5, 5)]
650 .into_iter()
651 .map(|(op_idx, op_end)| DebugSourceInlineCall {
652 op_idx,
653 op_end,
654 callee_idx: DebugFunctionIdx::from(0_u32),
655 loc_idx: DebugLocIdx::from(0_u32),
656 })
657 .collect();
658 let depths = inline_depths(&node);
659 assert_eq!(depths[&0], (2, 0));
660 assert_eq!(depths[&5], (2, 1));
661 assert_eq!(depths[&10], (0, 0));
662 }
663
664 #[test]
665 fn frame_index_handles_many_sources_and_functions_sharing_an_execution_node() {
666 let package = Assembler::new(Arc::new(DefaultSourceManager::default()))
667 .assemble_program("program", "begin push.7 drop end")
668 .unwrap();
669 let info = package.debug_info().unwrap().unwrap();
670 let template = info.functions()[0];
671 let node = info[template.source_node.into_option().unwrap()].clone();
672 let mut builder = PackageDebugInfoBuilder::from(Box::new(info));
673 for position in 0..50_000 {
674 let name = builder.add_string(format!("function_{position}"));
675 let mut source = node.clone();
676 for row in &mut source.asm_ops {
677 row.context_name_idx = name;
678 }
679 let source = builder.add_node(source).unwrap();
680 let mut function = template;
681 function.source_node = Some(source).into();
682 function.name_idx = name;
683 function.linkage_name_idx = Some(name).into();
684 builder.add_function(function);
685 }
686 let info = Arc::<PackageDebugInfo>::from(builder.build());
687 assert!(info.to_bytes().len() < 16 * 1024 * 1024);
688 let index = FunctionIndex::new(info.clone(), package.mast_forest().clone());
689 assert_eq!(index.entry_inline_counts.len(), 50_001);
690 assert_eq!(index.ranges[&node.exec_node].len(), 50_001);
691 for position in [1, 25_000, 50_000] {
692 let function = DebugFunctionIdx::from(position);
693 let source = info[function].source_node.into_option().unwrap();
694 assert_eq!(index.source_function(source), Some(function));
695 assert_eq!(index.entry_function(source), Some(function));
696 }
697 }
698}