1use alloc::{sync::Arc, vec::Vec};
2use core::sync::atomic::{AtomicU64, Ordering};
3
4use miden_core::{
5 mast::{MastForestId, MastNodeId},
6 program::Program,
7 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
8};
9use miden_mast_package::debug_info::{DebugSourceInlineCall, DebugSourceNodeId, PackageDebugInfo};
10
11const CONTINUATION_STACK_SIZE_HINT: usize = 64;
13
14#[derive(Clone, Debug)]
15pub(crate) struct DebugActivationId {
16 execution: Arc<AtomicU64>,
17 generation: u64,
18}
19
20impl PartialEq for DebugActivationId {
21 fn eq(&self, other: &Self) -> bool {
22 Arc::ptr_eq(&self.execution, &other.execution) && self.generation == other.generation
23 }
24}
25
26impl Eq for DebugActivationId {}
27
28#[derive(Clone, Debug, PartialEq, Eq)]
29struct DebugActivations {
30 active: DebugActivationId,
31 pending: Vec<u64>,
32}
33
34impl DebugActivations {
35 fn new(count: usize) -> Self {
36 let mut activations = Self {
37 active: DebugActivationId {
38 execution: Arc::new(AtomicU64::new(1)),
39 generation: 0,
40 },
41 pending: Vec::with_capacity(count),
42 };
43 for _ in 0..count {
44 let generation = activations.next_generation();
45 activations.pending.push(generation);
46 }
47 activations
48 }
49
50 fn next_generation(&self) -> u64 {
51 let execution = &self.active.execution;
52 let mut generation = execution.load(Ordering::Relaxed);
53 loop {
54 let next = generation.checked_add(1).expect("debug activation generation exhausted");
55 match execution.compare_exchange_weak(
56 generation,
57 next,
58 Ordering::Relaxed,
59 Ordering::Relaxed,
60 ) {
61 Ok(previous) => return previous,
62 Err(current) => generation = current,
63 }
64 }
65 }
66
67 fn activation_at(&self, position: usize) -> Option<DebugActivationId> {
68 Some(DebugActivationId {
69 execution: self.active.execution.clone(),
70 generation: *self.pending.get(position)?,
71 })
72 }
73}
74
75#[derive(Debug, Clone)]
77pub struct SourceInlineCallContext {
78 package_debug_info: Arc<PackageDebugInfo>,
79 source_node_id: DebugSourceNodeId,
80 op_idx: u32,
81}
82
83impl SourceInlineCallContext {
84 pub(crate) fn new(
85 package_debug_info: Arc<PackageDebugInfo>,
86 source_node_id: DebugSourceNodeId,
87 op_idx: u32,
88 ) -> Self {
89 Self {
90 package_debug_info,
91 source_node_id,
92 op_idx,
93 }
94 }
95
96 pub(crate) fn for_source_boundary(
97 package_debug_info: Arc<PackageDebugInfo>,
98 source_node_id: Option<DebugSourceNodeId>,
99 ) -> Option<Self> {
100 let source_node_id = source_node_id?;
101 let op_idx = package_debug_info.source_node(source_node_id)?.op_start;
102 package_debug_info.inline_calls_for_operation(source_node_id, op_idx).next()?;
103 Some(Self::new(package_debug_info, source_node_id, op_idx))
104 }
105
106 pub fn debug_info(&self) -> &Arc<PackageDebugInfo> {
108 &self.package_debug_info
109 }
110
111 pub fn source_node_id(&self) -> DebugSourceNodeId {
113 self.source_node_id
114 }
115
116 pub fn inline_calls(&self) -> impl Iterator<Item = &DebugSourceInlineCall> {
118 self.package_debug_info
119 .inline_calls_for_operation(self.source_node_id, self.op_idx)
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum Continuation<F> {
137 StartNode(MastNodeId),
139 FinishJoin(MastNodeId),
141 FinishSplit(MastNodeId),
143 FinishLoop(MastNodeId),
150 FinishCall(MastNodeId),
152 FinishDyn(MastNodeId),
154 ResumeBasicBlock {
157 node_id: MastNodeId,
158 batch_index: usize,
159 op_idx_in_batch: usize,
160 },
161 Respan { node_id: MastNodeId, batch_index: usize },
164 FinishBasicBlock(MastNodeId),
168 EnterForest {
173 forest: F,
174 package_debug_info: Option<Arc<PackageDebugInfo>>,
175 inline_context_depth: usize,
177 },
178}
179
180impl<F> Continuation<F> {
181 pub fn increments_clk(&self) -> bool {
184 use Continuation::*;
185
186 match self {
190 StartNode(_)
191 | FinishJoin(_)
192 | FinishSplit(_)
193 | FinishLoop(_)
194 | FinishCall(_)
195 | FinishDyn(_)
196 | ResumeBasicBlock {
197 node_id: _,
198 batch_index: _,
199 op_idx_in_batch: _,
200 }
201 | Respan { node_id: _, batch_index: _ }
202 | FinishBasicBlock(_) => true,
203
204 EnterForest { .. } => false,
205 }
206 }
207
208 pub fn exec_node(&self) -> Option<MastNodeId> {
209 match self {
210 Self::StartNode(node_id)
211 | Self::FinishJoin(node_id)
212 | Self::FinishSplit(node_id)
213 | Self::FinishLoop(node_id)
214 | Self::FinishCall(node_id)
215 | Self::FinishDyn(node_id)
216 | Self::ResumeBasicBlock { node_id, .. }
217 | Self::Respan { node_id, .. }
218 | Self::FinishBasicBlock(node_id) => Some(*node_id),
219 Self::EnterForest { .. } => None,
220 }
221 }
222}
223
224#[derive(Debug, Clone, PartialEq, Eq)]
239pub struct ContinuationStack<F> {
240 stack: Vec<Continuation<F>>,
241 source_node_ids: Option<Vec<Option<DebugSourceNodeId>>>,
242 debug_activations: Option<DebugActivations>,
243}
244
245impl<F> Default for ContinuationStack<F> {
246 fn default() -> Self {
247 Self {
248 stack: Vec::new(),
249 source_node_ids: None,
250 debug_activations: None,
251 }
252 }
253}
254
255impl<F> ContinuationStack<F> {
256 pub(crate) fn debug_activation_at(&self, position: usize) -> Option<DebugActivationId> {
257 self.debug_activations.as_ref()?.activation_at(position)
258 }
259
260 pub(crate) fn iter_with_source_node_ids(
261 &self,
262 ) -> impl DoubleEndedIterator<Item = (&Continuation<F>, Option<DebugSourceNodeId>)> {
263 self.stack.iter().enumerate().map(|(index, continuation)| {
264 let source_node_id = self
265 .source_node_ids
266 .as_deref()
267 .and_then(|ids| ids.get(index).copied())
268 .flatten();
269 (continuation, source_node_id)
270 })
271 }
272
273 pub fn new(program: &Program) -> Self {
278 let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
279 stack.push(Continuation::StartNode(program.entrypoint()));
280
281 Self {
282 stack,
283 source_node_ids: None,
284 debug_activations: None,
285 }
286 }
287
288 pub(crate) fn new_with_source_node_id(
289 program: &Program,
290 source_node_id: DebugSourceNodeId,
291 ) -> Self {
292 Self::new_with_optional_source_node_id(program, Some(source_node_id))
293 }
294
295 pub(crate) fn new_with_optional_source_node_id(
296 program: &Program,
297 source_node_id: Option<DebugSourceNodeId>,
298 ) -> Self {
299 let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
300 stack.push(Continuation::StartNode(program.entrypoint()));
301
302 let mut source_node_ids = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
303 source_node_ids.push(source_node_id);
304
305 Self {
306 stack,
307 source_node_ids: Some(source_node_ids),
308 debug_activations: Some(DebugActivations::new(1)),
309 }
310 }
311
312 pub fn push_continuation(&mut self, continuation: Continuation<F>) {
317 self.stack.push(continuation);
318 self.push_source_node_id(None);
319 }
320
321 pub(crate) fn push_with_source_node_id(
322 &mut self,
323 continuation: Continuation<F>,
324 source_node_id: Option<DebugSourceNodeId>,
325 ) {
326 self.stack.push(continuation);
327 self.push_source_node_id(source_node_id);
328 }
329
330 pub(crate) fn push_resumed_with_source_node_id(
331 &mut self,
332 continuation: Continuation<F>,
333 source_node_id: Option<DebugSourceNodeId>,
334 ) {
335 self.stack.push(continuation);
336 self.push_source_node_id_with_resumption(source_node_id, true);
337 }
338
339 pub fn push_enter_forest(&mut self, forest: F) {
344 self.push_enter_forest_with_package_debug_info(forest, None, 0, None);
345 }
346
347 pub(crate) fn push_enter_forest_with_package_debug_info(
348 &mut self,
349 forest: F,
350 package_debug_info: Option<Arc<PackageDebugInfo>>,
351 inline_context_depth: usize,
352 source_node_id: Option<DebugSourceNodeId>,
353 ) {
354 self.stack.push(Continuation::EnterForest {
355 forest,
356 package_debug_info,
357 inline_context_depth,
358 });
359 self.push_source_node_id(source_node_id);
360 }
361
362 pub fn push_finish_join(&mut self, node_id: MastNodeId) {
364 self.stack.push(Continuation::FinishJoin(node_id));
365 self.push_source_node_id(None);
366 }
367
368 pub fn push_finish_split(&mut self, node_id: MastNodeId) {
370 self.stack.push(Continuation::FinishSplit(node_id));
371 self.push_source_node_id(None);
372 }
373
374 pub fn push_finish_loop(&mut self, node_id: MastNodeId) {
376 self.stack.push(Continuation::FinishLoop(node_id));
377 self.push_source_node_id(None);
378 }
379
380 pub fn push_finish_call(&mut self, node_id: MastNodeId) {
382 self.stack.push(Continuation::FinishCall(node_id));
383 self.push_source_node_id(None);
384 }
385
386 pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
388 self.stack.push(Continuation::FinishDyn(node_id));
389 self.push_source_node_id(None);
390 }
391
392 pub fn push_start_node(&mut self, node_id: MastNodeId) {
397 self.stack.push(Continuation::StartNode(node_id));
398 self.push_source_node_id(None);
399 }
400
401 pub fn pop_continuation(&mut self) -> Option<Continuation<F>> {
404 let continuation = self.stack.pop()?;
405 self.pop_source_node_id();
406 Some(continuation)
407 }
408
409 pub(crate) fn pop_continuation_with_source_node_id(
410 &mut self,
411 ) -> Option<(Continuation<F>, Option<DebugSourceNodeId>)> {
412 let continuation = self.stack.pop()?;
413 let source_node_id = self.pop_source_node_id();
414 Some((continuation, source_node_id))
415 }
416
417 pub fn into_inner(self) -> Vec<Continuation<F>> {
420 self.stack
421 }
422
423 fn push_source_node_id(&mut self, source_node_id: Option<DebugSourceNodeId>) {
424 self.push_source_node_id_with_resumption(source_node_id, false);
425 }
426
427 fn push_source_node_id_with_resumption(
428 &mut self,
429 source_node_id: Option<DebugSourceNodeId>,
430 resumed: bool,
431 ) {
432 if let Some(source_node_ids) = &mut self.source_node_ids {
433 source_node_ids.push(source_node_id);
434 if let Some(activations) = &mut self.debug_activations {
435 let generation =
436 if !resumed && matches!(self.stack.last(), Some(Continuation::StartNode(_))) {
437 activations.next_generation()
438 } else {
439 activations.active.generation
440 };
441 activations.pending.push(generation);
442 }
443 }
444 }
445
446 fn pop_source_node_id(&mut self) -> Option<DebugSourceNodeId> {
447 let source_node_ids = self.source_node_ids.as_mut()?;
448 if let Some(activations) = &mut self.debug_activations {
449 activations.active.generation = activations.pending.pop().unwrap();
450 }
451 source_node_ids.pop().flatten()
452 }
453
454 pub(crate) fn start_tracking_source_nodes(
455 &mut self,
456 next_source_node_id: Option<DebugSourceNodeId>,
457 ) {
458 let mut source_node_ids = Vec::with_capacity(self.stack.len());
459 source_node_ids.resize(self.stack.len(), None);
460 if let Some(source_node_id) = source_node_ids.last_mut() {
461 *source_node_id = next_source_node_id;
462 }
463 self.source_node_ids = Some(source_node_ids);
464 self.debug_activations = Some(DebugActivations::new(self.stack.len()));
465 }
466
467 pub fn len(&self) -> usize {
472 self.stack.len()
473 }
474
475 pub fn peek_continuation(&self) -> Option<&Continuation<F>> {
481 self.stack.last()
482 }
483
484 pub(crate) fn peek_continuation_with_source_node_id(
485 &self,
486 ) -> Option<(&Continuation<F>, Option<DebugSourceNodeId>)> {
487 let continuation = self.stack.last()?;
488 let source_node_id = self
489 .source_node_ids
490 .as_ref()
491 .and_then(|source_node_ids| source_node_ids.last().copied().flatten());
492 Some((continuation, source_node_id))
493 }
494
495 pub(crate) fn tracks_source_nodes(&self) -> bool {
496 self.source_node_ids.is_some()
497 }
498
499 pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation<F>> {
511 let mut found_incrementing_cont = false;
512
513 self.stack.iter().rev().take_while(move |continuation| {
514 if found_incrementing_cont {
515 false
517 } else if continuation.increments_clk() {
518 found_incrementing_cont = true;
520 true
521 } else {
522 true
524 }
525 })
526 }
527
528 pub fn iter_continuations_for_next_clock_with_source_node_ids(
531 &self,
532 ) -> impl Iterator<Item = (&Continuation<F>, Option<DebugSourceNodeId>)> {
533 let mut stack_index = self.stack.len().saturating_sub(1);
534
535 self.iter_continuations_for_next_clock().map(move |cont| {
536 let source_node_id = self
537 .source_node_ids
538 .as_deref()
539 .and_then(|ids| ids.get(stack_index).copied())
540 .flatten();
541 stack_index = stack_index.saturating_sub(1);
542 (cont, source_node_id)
543 })
544 }
545}
546
547impl ContinuationStack<MastForestId> {
548 pub(crate) fn iter_enter_forest_ids(&self) -> impl Iterator<Item = MastForestId> + '_ {
549 self.stack.iter().filter_map(|continuation| match continuation {
550 Continuation::EnterForest { forest, .. } => Some(*forest),
551 _ => None,
552 })
553 }
554}
555
556const TAG_START_NODE: u8 = 0;
560const TAG_FINISH_JOIN: u8 = 1;
561const TAG_FINISH_SPLIT: u8 = 2;
562const TAG_FINISH_LOOP: u8 = 3;
563const TAG_FINISH_CALL: u8 = 4;
564const TAG_FINISH_DYN: u8 = 5;
565const TAG_RESUME_BASIC_BLOCK: u8 = 6;
566const TAG_RESPAN: u8 = 7;
567const TAG_FINISH_BASIC_BLOCK: u8 = 8;
568const TAG_ENTER_FOREST: u8 = 9;
569
570impl Serializable for Continuation<MastForestId> {
576 fn write_into<W: ByteWriter>(&self, target: &mut W) {
577 match self {
578 Self::StartNode(node_id) => {
579 TAG_START_NODE.write_into(target);
580 node_id.write_into(target);
581 },
582 Self::FinishJoin(node_id) => {
583 TAG_FINISH_JOIN.write_into(target);
584 node_id.write_into(target);
585 },
586 Self::FinishSplit(node_id) => {
587 TAG_FINISH_SPLIT.write_into(target);
588 node_id.write_into(target);
589 },
590 Self::FinishLoop(node_id) => {
591 TAG_FINISH_LOOP.write_into(target);
592 node_id.write_into(target);
593 },
594 Self::FinishCall(node_id) => {
595 TAG_FINISH_CALL.write_into(target);
596 node_id.write_into(target);
597 },
598 Self::FinishDyn(node_id) => {
599 TAG_FINISH_DYN.write_into(target);
600 node_id.write_into(target);
601 },
602 Self::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
603 TAG_RESUME_BASIC_BLOCK.write_into(target);
604 node_id.write_into(target);
605 batch_index.write_into(target);
606 op_idx_in_batch.write_into(target);
607 },
608 Self::Respan { node_id, batch_index } => {
609 TAG_RESPAN.write_into(target);
610 node_id.write_into(target);
611 batch_index.write_into(target);
612 },
613 Self::FinishBasicBlock(node_id) => {
614 TAG_FINISH_BASIC_BLOCK.write_into(target);
615 node_id.write_into(target);
616 },
617 Self::EnterForest {
618 forest,
619 package_debug_info: _,
620 inline_context_depth,
621 } => {
622 TAG_ENTER_FOREST.write_into(target);
623 forest.write_into(target);
624 inline_context_depth.write_into(target);
625 },
626 }
627 }
628}
629
630impl Deserializable for Continuation<MastForestId> {
631 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
632 match u8::read_from(source)? {
633 TAG_START_NODE => Ok(Self::StartNode(MastNodeId::read_from(source)?)),
634 TAG_FINISH_JOIN => Ok(Self::FinishJoin(MastNodeId::read_from(source)?)),
635 TAG_FINISH_SPLIT => Ok(Self::FinishSplit(MastNodeId::read_from(source)?)),
636 TAG_FINISH_LOOP => Ok(Self::FinishLoop(MastNodeId::read_from(source)?)),
637 TAG_FINISH_CALL => Ok(Self::FinishCall(MastNodeId::read_from(source)?)),
638 TAG_FINISH_DYN => Ok(Self::FinishDyn(MastNodeId::read_from(source)?)),
639 TAG_RESUME_BASIC_BLOCK => Ok(Self::ResumeBasicBlock {
640 node_id: MastNodeId::read_from(source)?,
641 batch_index: usize::read_from(source)?,
642 op_idx_in_batch: usize::read_from(source)?,
643 }),
644 TAG_RESPAN => Ok(Self::Respan {
645 node_id: MastNodeId::read_from(source)?,
646 batch_index: usize::read_from(source)?,
647 }),
648 TAG_FINISH_BASIC_BLOCK => Ok(Self::FinishBasicBlock(MastNodeId::read_from(source)?)),
649 TAG_ENTER_FOREST => Ok(Self::EnterForest {
650 forest: MastForestId::read_from(source)?,
651 package_debug_info: None,
652 inline_context_depth: usize::read_from(source)?,
653 }),
654 tag => {
655 Err(DeserializationError::InvalidValue(format!("invalid continuation tag {tag}")))
656 },
657 }
658 }
659
660 fn min_serialized_size() -> usize {
661 u8::min_serialized_size() + MastNodeId::min_serialized_size()
662 }
663}
664
665impl Serializable for ContinuationStack<MastForestId> {
670 fn write_into<W: ByteWriter>(&self, target: &mut W) {
671 self.stack.write_into(target);
672 }
673}
674
675impl Deserializable for ContinuationStack<MastForestId> {
676 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
677 let stack = Vec::<Continuation<MastForestId>>::read_from(source)?;
678 Ok(Self {
679 stack,
680 source_node_ids: None,
681 debug_activations: None,
682 })
683 }
684}
685
686#[cfg(test)]
690mod tests {
691 use alloc::sync::Arc;
692
693 use miden_core::mast::MastForest;
694 use miden_mast_package::debug_info::{
695 DebugFunctionIdx, DebugLocIdx, DebugSourceInlineCall, DebugSourceNode,
696 PackageDebugInfoBuilder,
697 };
698
699 use super::*;
700
701 #[test]
702 fn debug_activation_identity_survives_resumption_but_not_reentry() {
703 let node_id = MastNodeId::from(1);
704 let source_id = Some(DebugSourceNodeId::from(2));
705 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
706 stack.push_start_node(node_id);
707 stack.start_tracking_source_nodes(source_id);
708 let initial = stack.debug_activation_at(0).unwrap();
709
710 let (continuation, source_id) = stack.pop_continuation_with_source_node_id().unwrap();
711 stack.push_resumed_with_source_node_id(continuation, source_id);
712 assert_eq!(stack.debug_activation_at(0).unwrap(), initial);
713
714 stack.pop_continuation_with_source_node_id().unwrap();
715 stack.push_with_source_node_id(Continuation::FinishJoin(node_id), source_id);
716 assert_eq!(stack.debug_activation_at(0).unwrap(), initial);
717
718 stack.pop_continuation_with_source_node_id().unwrap();
719 stack.push_with_source_node_id(Continuation::StartNode(node_id), source_id);
720 assert_ne!(stack.debug_activation_at(0).unwrap(), initial);
721 }
722
723 #[test]
724 fn debug_activation_generations_are_unique_across_cloned_stacks() {
725 let node_id = MastNodeId::from(1);
726 let mut first: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
727 first.push_start_node(node_id);
728 first.start_tracking_source_nodes(Some(DebugSourceNodeId::from(2)));
729 let mut second = first.clone();
730 assert_eq!(first.debug_activation_at(0), second.debug_activation_at(0));
731
732 first.pop_continuation().unwrap();
733 second.pop_continuation().unwrap();
734 first.push_start_node(node_id);
735 second.push_start_node(node_id);
736 assert_ne!(first.debug_activation_at(0), second.debug_activation_at(0));
737 }
738
739 #[test]
740 fn source_unaware_continuations_do_not_track_activations() {
741 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
742 stack.push_start_node(MastNodeId::from(1));
743 assert!(stack.debug_activations.is_none());
744 assert!(stack.debug_activation_at(0).is_none());
745 stack.pop_continuation().unwrap();
746 assert!(stack.debug_activations.is_none());
747 }
748
749 #[test]
750 fn debug_activation_generations_return_the_previous_counter_value() {
751 let activations = DebugActivations::new(0);
752 activations.active.execution.store(u64::MAX - 1, Ordering::Relaxed);
753 assert_eq!(activations.next_generation(), u64::MAX - 1);
754 assert_eq!(activations.active.execution.load(Ordering::Relaxed), u64::MAX);
755 }
756
757 #[cfg(feature = "std")]
758 #[test]
759 fn debug_activation_generations_are_unique_under_contention() {
760 let activations = DebugActivations::new(0);
761 let mut generations = std::thread::scope(|scope| {
762 let handles: [_; 4] = core::array::from_fn(|_| {
763 scope.spawn(|| (0..128).map(|_| activations.next_generation()).collect::<Vec<_>>())
764 });
765 handles
766 .into_iter()
767 .flat_map(|handle| handle.join().expect("activation worker panicked"))
768 .collect::<Vec<_>>()
769 });
770 generations.sort_unstable();
771 assert_eq!(generations, (1..=512).collect::<Vec<_>>());
772 }
773
774 #[test]
775 #[should_panic(expected = "debug activation generation exhausted")]
776 fn debug_activation_generations_reject_overflow() {
777 let activations = DebugActivations::new(0);
778 activations.active.execution.store(u64::MAX, Ordering::Relaxed);
779 activations.next_generation();
780 }
781
782 #[test]
783 fn get_next_clock_cycle_increment_empty_stack() {
784 let stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
785 assert!(stack.iter_continuations_for_next_clock().next().is_none());
786 }
787
788 #[test]
789 fn get_next_clock_cycle_increment_ends_with_incrementing() {
790 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
791 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
793
794 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
795 assert_eq!(result.len(), 1);
796 assert!(matches!(result[0], Continuation::StartNode(_)));
797 }
798
799 #[test]
800 fn get_next_clock_cycle_increment_enter_forest_after_incrementing() {
801 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
802 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
804 stack.push_continuation(Continuation::EnterForest {
806 forest: Arc::new(MastForest::new()),
807 package_debug_info: None,
808 inline_context_depth: 0,
809 });
810
811 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
812 assert_eq!(result.len(), 2);
814 assert!(matches!(result[0], Continuation::EnterForest { .. }));
815 assert!(matches!(result[1], Continuation::StartNode(_)));
816 }
817
818 #[test]
819 fn get_next_clock_cycle_increment_multiple_enter_forest_after_incrementing() {
820 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
821 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
823 stack.push_continuation(Continuation::EnterForest {
825 forest: Arc::new(MastForest::new()),
826 package_debug_info: None,
827 inline_context_depth: 0,
828 });
829 stack.push_continuation(Continuation::EnterForest {
830 forest: Arc::new(MastForest::new()),
831 package_debug_info: None,
832 inline_context_depth: 0,
833 });
834
835 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
836 assert_eq!(result.len(), 3);
838 assert!(matches!(result[0], Continuation::EnterForest { .. }));
839 assert!(matches!(result[1], Continuation::EnterForest { .. }));
840 assert!(matches!(result[2], Continuation::StartNode(_)));
841 }
842
843 #[test]
844 fn inline_call_context_uses_the_source_boundary_index() {
845 let mut builder = PackageDebugInfoBuilder::default();
846 let source_node_id = builder
847 .add_node(DebugSourceNode {
848 exec_node: MastNodeId::new_unchecked(0),
849 children: Vec::new(),
850 op_start: 7,
851 op_end: 7,
852 asm_ops: Vec::new(),
853 debug_vars: Vec::new(),
854 inline_calls: vec![DebugSourceInlineCall {
855 op_idx: 7,
856 op_end: 7,
857 callee_idx: DebugFunctionIdx::from(0),
858 loc_idx: DebugLocIdx::from(0),
859 }],
860 })
861 .unwrap();
862 let debug_info = Arc::from(builder.build());
863
864 let context =
865 SourceInlineCallContext::for_source_boundary(debug_info, Some(source_node_id))
866 .expect("boundary row should create inherited inline context");
867
868 assert_eq!(context.inline_calls().map(|row| row.op_idx).collect::<Vec<_>>(), [7]);
869 }
870
871 #[test]
872 fn continuation_stack_mast_forest_id_round_trip_omits_debug_metadata() {
873 let mut stack: ContinuationStack<MastForestId> = ContinuationStack::default();
874 stack.push_continuation(Continuation::StartNode(MastNodeId::from(1)));
875 stack.push_continuation(Continuation::EnterForest {
876 forest: MastForestId::from(2),
877 package_debug_info: None,
878 inline_context_depth: 0,
879 });
880 stack.push_continuation(Continuation::ResumeBasicBlock {
881 node_id: MastNodeId::from(3),
882 batch_index: 4,
883 op_idx_in_batch: 5,
884 });
885 stack.start_tracking_source_nodes(None);
886 stack.source_node_ids =
887 Some(vec![Some(DebugSourceNodeId::from(10)), None, Some(DebugSourceNodeId::from(11))]);
888 assert!(stack.debug_activations.is_some());
889
890 let bytes = stack.to_bytes();
891 let restored = ContinuationStack::<MastForestId>::read_from_bytes(&bytes).unwrap();
892
893 assert_eq!(restored.stack.len(), 3);
894 assert!(matches!(
895 restored.stack[0],
896 Continuation::StartNode(node_id) if node_id == MastNodeId::from(1)
897 ));
898 assert!(matches!(
899 restored.stack[1],
900 Continuation::EnterForest {
901 forest,
902 package_debug_info: None,
903 inline_context_depth: 0,
904 } if forest == MastForestId::from(2)
905 ));
906 assert!(matches!(
907 restored.stack[2],
908 Continuation::ResumeBasicBlock {
909 node_id,
910 batch_index: 4,
911 op_idx_in_batch: 5,
912 } if node_id == MastNodeId::from(3)
913 ));
914 assert_eq!(restored.source_node_ids, None);
917 assert!(restored.debug_activations.is_none());
918 }
919}