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)]
136#[cfg_attr(
137 all(feature = "arbitrary", test),
138 miden_test_serialization_macros::serialization_test(types(MastForestId))
139)]
140pub enum Continuation<F> {
141 StartNode(MastNodeId),
143 FinishJoin(MastNodeId),
145 FinishSplit(MastNodeId),
147 FinishLoop(MastNodeId),
154 FinishCall(MastNodeId),
156 FinishDyn(MastNodeId),
158 ResumeBasicBlock {
161 node_id: MastNodeId,
162 batch_index: usize,
163 op_idx_in_batch: usize,
164 },
165 Respan { node_id: MastNodeId, batch_index: usize },
168 FinishBasicBlock(MastNodeId),
172 EnterForest {
177 forest: F,
178 package_debug_info: Option<Arc<PackageDebugInfo>>,
179 inline_context_depth: usize,
181 },
182}
183
184impl<F> Continuation<F> {
185 pub fn increments_clk(&self) -> bool {
188 use Continuation::*;
189
190 match self {
194 StartNode(_)
195 | FinishJoin(_)
196 | FinishSplit(_)
197 | FinishLoop(_)
198 | FinishCall(_)
199 | FinishDyn(_)
200 | ResumeBasicBlock {
201 node_id: _,
202 batch_index: _,
203 op_idx_in_batch: _,
204 }
205 | Respan { node_id: _, batch_index: _ }
206 | FinishBasicBlock(_) => true,
207
208 EnterForest { .. } => false,
209 }
210 }
211
212 pub fn exec_node(&self) -> Option<MastNodeId> {
213 match self {
214 Self::StartNode(node_id)
215 | Self::FinishJoin(node_id)
216 | Self::FinishSplit(node_id)
217 | Self::FinishLoop(node_id)
218 | Self::FinishCall(node_id)
219 | Self::FinishDyn(node_id)
220 | Self::ResumeBasicBlock { node_id, .. }
221 | Self::Respan { node_id, .. }
222 | Self::FinishBasicBlock(node_id) => Some(*node_id),
223 Self::EnterForest { .. } => None,
224 }
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Eq)]
243#[cfg_attr(
244 all(feature = "arbitrary", test),
245 miden_test_serialization_macros::serialization_test(types(MastForestId))
246)]
247pub struct ContinuationStack<F> {
248 stack: Vec<Continuation<F>>,
249 source_node_ids: Option<Vec<Option<DebugSourceNodeId>>>,
250 debug_activations: Option<DebugActivations>,
251}
252
253impl<F> Default for ContinuationStack<F> {
254 fn default() -> Self {
255 Self {
256 stack: Vec::new(),
257 source_node_ids: None,
258 debug_activations: None,
259 }
260 }
261}
262
263impl<F> ContinuationStack<F> {
264 pub(crate) fn debug_activation_at(&self, position: usize) -> Option<DebugActivationId> {
265 self.debug_activations.as_ref()?.activation_at(position)
266 }
267
268 pub(crate) fn iter_with_source_node_ids(
269 &self,
270 ) -> impl DoubleEndedIterator<Item = (&Continuation<F>, Option<DebugSourceNodeId>)> {
271 self.stack.iter().enumerate().map(|(index, continuation)| {
272 let source_node_id = self
273 .source_node_ids
274 .as_deref()
275 .and_then(|ids| ids.get(index).copied())
276 .flatten();
277 (continuation, source_node_id)
278 })
279 }
280
281 pub fn new(program: &Program) -> Self {
286 let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
287 stack.push(Continuation::StartNode(program.entrypoint()));
288
289 Self {
290 stack,
291 source_node_ids: None,
292 debug_activations: None,
293 }
294 }
295
296 pub(crate) fn new_with_source_node_id(
297 program: &Program,
298 source_node_id: DebugSourceNodeId,
299 ) -> Self {
300 Self::new_with_optional_source_node_id(program, Some(source_node_id))
301 }
302
303 pub(crate) fn new_with_optional_source_node_id(
304 program: &Program,
305 source_node_id: Option<DebugSourceNodeId>,
306 ) -> Self {
307 let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
308 stack.push(Continuation::StartNode(program.entrypoint()));
309
310 let mut source_node_ids = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
311 source_node_ids.push(source_node_id);
312
313 Self {
314 stack,
315 source_node_ids: Some(source_node_ids),
316 debug_activations: Some(DebugActivations::new(1)),
317 }
318 }
319
320 pub fn push_continuation(&mut self, continuation: Continuation<F>) {
325 self.stack.push(continuation);
326 self.push_source_node_id(None);
327 }
328
329 pub(crate) fn push_with_source_node_id(
330 &mut self,
331 continuation: Continuation<F>,
332 source_node_id: Option<DebugSourceNodeId>,
333 ) {
334 self.stack.push(continuation);
335 self.push_source_node_id(source_node_id);
336 }
337
338 pub(crate) fn push_resumed_with_source_node_id(
339 &mut self,
340 continuation: Continuation<F>,
341 source_node_id: Option<DebugSourceNodeId>,
342 ) {
343 self.stack.push(continuation);
344 self.push_source_node_id_with_resumption(source_node_id, true);
345 }
346
347 pub fn push_enter_forest(&mut self, forest: F) {
352 self.push_enter_forest_with_package_debug_info(forest, None, 0, None);
353 }
354
355 pub(crate) fn push_enter_forest_with_package_debug_info(
356 &mut self,
357 forest: F,
358 package_debug_info: Option<Arc<PackageDebugInfo>>,
359 inline_context_depth: usize,
360 source_node_id: Option<DebugSourceNodeId>,
361 ) {
362 self.stack.push(Continuation::EnterForest {
363 forest,
364 package_debug_info,
365 inline_context_depth,
366 });
367 self.push_source_node_id(source_node_id);
368 }
369
370 pub fn push_finish_join(&mut self, node_id: MastNodeId) {
372 self.stack.push(Continuation::FinishJoin(node_id));
373 self.push_source_node_id(None);
374 }
375
376 pub fn push_finish_split(&mut self, node_id: MastNodeId) {
378 self.stack.push(Continuation::FinishSplit(node_id));
379 self.push_source_node_id(None);
380 }
381
382 pub fn push_finish_loop(&mut self, node_id: MastNodeId) {
384 self.stack.push(Continuation::FinishLoop(node_id));
385 self.push_source_node_id(None);
386 }
387
388 pub fn push_finish_call(&mut self, node_id: MastNodeId) {
390 self.stack.push(Continuation::FinishCall(node_id));
391 self.push_source_node_id(None);
392 }
393
394 pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
396 self.stack.push(Continuation::FinishDyn(node_id));
397 self.push_source_node_id(None);
398 }
399
400 pub fn push_start_node(&mut self, node_id: MastNodeId) {
405 self.stack.push(Continuation::StartNode(node_id));
406 self.push_source_node_id(None);
407 }
408
409 pub fn pop_continuation(&mut self) -> Option<Continuation<F>> {
412 let continuation = self.stack.pop()?;
413 self.pop_source_node_id();
414 Some(continuation)
415 }
416
417 pub(crate) fn pop_continuation_with_source_node_id(
418 &mut self,
419 ) -> Option<(Continuation<F>, Option<DebugSourceNodeId>)> {
420 let continuation = self.stack.pop()?;
421 let source_node_id = self.pop_source_node_id();
422 Some((continuation, source_node_id))
423 }
424
425 pub fn into_inner(self) -> Vec<Continuation<F>> {
428 self.stack
429 }
430
431 fn push_source_node_id(&mut self, source_node_id: Option<DebugSourceNodeId>) {
432 self.push_source_node_id_with_resumption(source_node_id, false);
433 }
434
435 fn push_source_node_id_with_resumption(
436 &mut self,
437 source_node_id: Option<DebugSourceNodeId>,
438 resumed: bool,
439 ) {
440 if let Some(source_node_ids) = &mut self.source_node_ids {
441 source_node_ids.push(source_node_id);
442 if let Some(activations) = &mut self.debug_activations {
443 let generation =
444 if !resumed && matches!(self.stack.last(), Some(Continuation::StartNode(_))) {
445 activations.next_generation()
446 } else {
447 activations.active.generation
448 };
449 activations.pending.push(generation);
450 }
451 }
452 }
453
454 fn pop_source_node_id(&mut self) -> Option<DebugSourceNodeId> {
455 let source_node_ids = self.source_node_ids.as_mut()?;
456 if let Some(activations) = &mut self.debug_activations {
457 activations.active.generation = activations.pending.pop().unwrap();
458 }
459 source_node_ids.pop().flatten()
460 }
461
462 pub(crate) fn start_tracking_source_nodes(
463 &mut self,
464 next_source_node_id: Option<DebugSourceNodeId>,
465 ) {
466 let mut source_node_ids = Vec::with_capacity(self.stack.len());
467 source_node_ids.resize(self.stack.len(), None);
468 if let Some(source_node_id) = source_node_ids.last_mut() {
469 *source_node_id = next_source_node_id;
470 }
471 self.source_node_ids = Some(source_node_ids);
472 self.debug_activations = Some(DebugActivations::new(self.stack.len()));
473 }
474
475 pub fn len(&self) -> usize {
480 self.stack.len()
481 }
482
483 pub fn peek_continuation(&self) -> Option<&Continuation<F>> {
489 self.stack.last()
490 }
491
492 pub(crate) fn peek_continuation_with_source_node_id(
493 &self,
494 ) -> Option<(&Continuation<F>, Option<DebugSourceNodeId>)> {
495 let continuation = self.stack.last()?;
496 let source_node_id = self
497 .source_node_ids
498 .as_ref()
499 .and_then(|source_node_ids| source_node_ids.last().copied().flatten());
500 Some((continuation, source_node_id))
501 }
502
503 pub(crate) fn tracks_source_nodes(&self) -> bool {
504 self.source_node_ids.is_some()
505 }
506
507 pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation<F>> {
519 let mut found_incrementing_cont = false;
520
521 self.stack.iter().rev().take_while(move |continuation| {
522 if found_incrementing_cont {
523 false
525 } else if continuation.increments_clk() {
526 found_incrementing_cont = true;
528 true
529 } else {
530 true
532 }
533 })
534 }
535
536 pub fn iter_continuations_for_next_clock_with_source_node_ids(
539 &self,
540 ) -> impl Iterator<Item = (&Continuation<F>, Option<DebugSourceNodeId>)> {
541 let mut stack_index = self.stack.len().saturating_sub(1);
542
543 self.iter_continuations_for_next_clock().map(move |cont| {
544 let source_node_id = self
545 .source_node_ids
546 .as_deref()
547 .and_then(|ids| ids.get(stack_index).copied())
548 .flatten();
549 stack_index = stack_index.saturating_sub(1);
550 (cont, source_node_id)
551 })
552 }
553}
554
555impl ContinuationStack<MastForestId> {
556 pub(crate) fn iter_enter_forest_ids(&self) -> impl Iterator<Item = MastForestId> + '_ {
557 self.stack.iter().filter_map(|continuation| match continuation {
558 Continuation::EnterForest { forest, .. } => Some(*forest),
559 _ => None,
560 })
561 }
562}
563
564const TAG_START_NODE: u8 = 0;
568const TAG_FINISH_JOIN: u8 = 1;
569const TAG_FINISH_SPLIT: u8 = 2;
570const TAG_FINISH_LOOP: u8 = 3;
571const TAG_FINISH_CALL: u8 = 4;
572const TAG_FINISH_DYN: u8 = 5;
573const TAG_RESUME_BASIC_BLOCK: u8 = 6;
574const TAG_RESPAN: u8 = 7;
575const TAG_FINISH_BASIC_BLOCK: u8 = 8;
576const TAG_ENTER_FOREST: u8 = 9;
577
578impl Serializable for Continuation<MastForestId> {
584 fn write_into<W: ByteWriter>(&self, target: &mut W) {
585 match self {
586 Self::StartNode(node_id) => {
587 TAG_START_NODE.write_into(target);
588 node_id.write_into(target);
589 },
590 Self::FinishJoin(node_id) => {
591 TAG_FINISH_JOIN.write_into(target);
592 node_id.write_into(target);
593 },
594 Self::FinishSplit(node_id) => {
595 TAG_FINISH_SPLIT.write_into(target);
596 node_id.write_into(target);
597 },
598 Self::FinishLoop(node_id) => {
599 TAG_FINISH_LOOP.write_into(target);
600 node_id.write_into(target);
601 },
602 Self::FinishCall(node_id) => {
603 TAG_FINISH_CALL.write_into(target);
604 node_id.write_into(target);
605 },
606 Self::FinishDyn(node_id) => {
607 TAG_FINISH_DYN.write_into(target);
608 node_id.write_into(target);
609 },
610 Self::ResumeBasicBlock { node_id, batch_index, op_idx_in_batch } => {
611 TAG_RESUME_BASIC_BLOCK.write_into(target);
612 node_id.write_into(target);
613 batch_index.write_into(target);
614 op_idx_in_batch.write_into(target);
615 },
616 Self::Respan { node_id, batch_index } => {
617 TAG_RESPAN.write_into(target);
618 node_id.write_into(target);
619 batch_index.write_into(target);
620 },
621 Self::FinishBasicBlock(node_id) => {
622 TAG_FINISH_BASIC_BLOCK.write_into(target);
623 node_id.write_into(target);
624 },
625 Self::EnterForest {
626 forest,
627 package_debug_info: _,
628 inline_context_depth,
629 } => {
630 TAG_ENTER_FOREST.write_into(target);
631 forest.write_into(target);
632 inline_context_depth.write_into(target);
633 },
634 }
635 }
636}
637
638impl Deserializable for Continuation<MastForestId> {
639 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
640 match u8::read_from(source)? {
641 TAG_START_NODE => Ok(Self::StartNode(MastNodeId::read_from(source)?)),
642 TAG_FINISH_JOIN => Ok(Self::FinishJoin(MastNodeId::read_from(source)?)),
643 TAG_FINISH_SPLIT => Ok(Self::FinishSplit(MastNodeId::read_from(source)?)),
644 TAG_FINISH_LOOP => Ok(Self::FinishLoop(MastNodeId::read_from(source)?)),
645 TAG_FINISH_CALL => Ok(Self::FinishCall(MastNodeId::read_from(source)?)),
646 TAG_FINISH_DYN => Ok(Self::FinishDyn(MastNodeId::read_from(source)?)),
647 TAG_RESUME_BASIC_BLOCK => Ok(Self::ResumeBasicBlock {
648 node_id: MastNodeId::read_from(source)?,
649 batch_index: usize::read_from(source)?,
650 op_idx_in_batch: usize::read_from(source)?,
651 }),
652 TAG_RESPAN => Ok(Self::Respan {
653 node_id: MastNodeId::read_from(source)?,
654 batch_index: usize::read_from(source)?,
655 }),
656 TAG_FINISH_BASIC_BLOCK => Ok(Self::FinishBasicBlock(MastNodeId::read_from(source)?)),
657 TAG_ENTER_FOREST => Ok(Self::EnterForest {
658 forest: MastForestId::read_from(source)?,
659 package_debug_info: None,
660 inline_context_depth: usize::read_from(source)?,
661 }),
662 tag => {
663 Err(DeserializationError::InvalidValue(format!("invalid continuation tag {tag}")))
664 },
665 }
666 }
667
668 fn min_serialized_size() -> usize {
669 u8::min_serialized_size() + MastNodeId::min_serialized_size()
670 }
671}
672
673impl Serializable for ContinuationStack<MastForestId> {
678 fn write_into<W: ByteWriter>(&self, target: &mut W) {
679 self.stack.write_into(target);
680 }
681}
682
683impl Deserializable for ContinuationStack<MastForestId> {
684 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
685 let stack = Vec::<Continuation<MastForestId>>::read_from(source)?;
686 Ok(Self {
687 stack,
688 source_node_ids: None,
689 debug_activations: None,
690 })
691 }
692}
693
694#[cfg(any(test, feature = "arbitrary"))]
698mod arbitrary {
699 use miden_core::mast::{MastForestId, MastNodeId};
700 use proptest::prelude::*;
701
702 use super::{Continuation, ContinuationStack};
703
704 impl Arbitrary for Continuation<MastForestId> {
705 type Parameters = ();
706 type Strategy = BoxedStrategy<Self>;
707
708 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
709 prop_oneof![
714 any::<MastNodeId>().prop_map(Self::StartNode).boxed(),
715 any::<MastNodeId>().prop_map(Self::FinishJoin).boxed(),
716 any::<MastNodeId>().prop_map(Self::FinishSplit).boxed(),
717 any::<MastNodeId>().prop_map(Self::FinishLoop).boxed(),
718 any::<MastNodeId>().prop_map(Self::FinishCall).boxed(),
719 any::<MastNodeId>().prop_map(Self::FinishDyn).boxed(),
720 (any::<MastNodeId>(), any::<usize>(), any::<usize>())
721 .prop_map(|(node_id, batch_index, op_idx_in_batch)| Self::ResumeBasicBlock {
722 node_id,
723 batch_index,
724 op_idx_in_batch,
725 })
726 .boxed(),
727 (any::<MastNodeId>(), any::<usize>())
728 .prop_map(|(node_id, batch_index)| Self::Respan { node_id, batch_index })
729 .boxed(),
730 any::<MastNodeId>().prop_map(Self::FinishBasicBlock).boxed(),
731 (any::<MastForestId>(), any::<usize>())
732 .prop_map(|(forest, inline_context_depth)| Self::EnterForest {
733 forest,
734 package_debug_info: None,
735 inline_context_depth,
736 })
737 .boxed(),
738 ]
739 .boxed()
740 }
741 }
742
743 impl Arbitrary for ContinuationStack<MastForestId> {
744 type Parameters = ();
745 type Strategy = BoxedStrategy<Self>;
746
747 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
748 proptest::collection::vec(any::<Continuation<MastForestId>>(), 0..=6)
751 .prop_map(|stack| Self {
752 stack,
753 source_node_ids: None,
754 debug_activations: None,
755 })
756 .boxed()
757 }
758 }
759}
760
761#[cfg(test)]
765mod tests {
766 use alloc::sync::Arc;
767
768 use miden_core::mast::MastForest;
769 use miden_mast_package::debug_info::{
770 DebugFunctionIdx, DebugLocIdx, DebugSourceInlineCall, DebugSourceNode,
771 PackageDebugInfoBuilder,
772 };
773
774 use super::*;
775
776 #[test]
777 fn debug_activation_identity_survives_resumption_but_not_reentry() {
778 let node_id = MastNodeId::from(1);
779 let source_id = Some(DebugSourceNodeId::from(2));
780 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
781 stack.push_start_node(node_id);
782 stack.start_tracking_source_nodes(source_id);
783 let initial = stack.debug_activation_at(0).unwrap();
784
785 let (continuation, source_id) = stack.pop_continuation_with_source_node_id().unwrap();
786 stack.push_resumed_with_source_node_id(continuation, source_id);
787 assert_eq!(stack.debug_activation_at(0).unwrap(), initial);
788
789 stack.pop_continuation_with_source_node_id().unwrap();
790 stack.push_with_source_node_id(Continuation::FinishJoin(node_id), source_id);
791 assert_eq!(stack.debug_activation_at(0).unwrap(), initial);
792
793 stack.pop_continuation_with_source_node_id().unwrap();
794 stack.push_with_source_node_id(Continuation::StartNode(node_id), source_id);
795 assert_ne!(stack.debug_activation_at(0).unwrap(), initial);
796 }
797
798 #[test]
799 fn debug_activation_generations_are_unique_across_cloned_stacks() {
800 let node_id = MastNodeId::from(1);
801 let mut first: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
802 first.push_start_node(node_id);
803 first.start_tracking_source_nodes(Some(DebugSourceNodeId::from(2)));
804 let mut second = first.clone();
805 assert_eq!(first.debug_activation_at(0), second.debug_activation_at(0));
806
807 first.pop_continuation().unwrap();
808 second.pop_continuation().unwrap();
809 first.push_start_node(node_id);
810 second.push_start_node(node_id);
811 assert_ne!(first.debug_activation_at(0), second.debug_activation_at(0));
812 }
813
814 #[test]
815 fn source_unaware_continuations_do_not_track_activations() {
816 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
817 stack.push_start_node(MastNodeId::from(1));
818 assert!(stack.debug_activations.is_none());
819 assert!(stack.debug_activation_at(0).is_none());
820 stack.pop_continuation().unwrap();
821 assert!(stack.debug_activations.is_none());
822 }
823
824 #[test]
825 fn debug_activation_generations_return_the_previous_counter_value() {
826 let activations = DebugActivations::new(0);
827 activations.active.execution.store(u64::MAX - 1, Ordering::Relaxed);
828 assert_eq!(activations.next_generation(), u64::MAX - 1);
829 assert_eq!(activations.active.execution.load(Ordering::Relaxed), u64::MAX);
830 }
831
832 #[cfg(feature = "std")]
833 #[test]
834 fn debug_activation_generations_are_unique_under_contention() {
835 let activations = DebugActivations::new(0);
836 let mut generations = std::thread::scope(|scope| {
837 let handles: [_; 4] = core::array::from_fn(|_| {
838 scope.spawn(|| (0..128).map(|_| activations.next_generation()).collect::<Vec<_>>())
839 });
840 handles
841 .into_iter()
842 .flat_map(|handle| handle.join().expect("activation worker panicked"))
843 .collect::<Vec<_>>()
844 });
845 generations.sort_unstable();
846 assert_eq!(generations, (1..=512).collect::<Vec<_>>());
847 }
848
849 #[test]
850 #[should_panic(expected = "debug activation generation exhausted")]
851 fn debug_activation_generations_reject_overflow() {
852 let activations = DebugActivations::new(0);
853 activations.active.execution.store(u64::MAX, Ordering::Relaxed);
854 activations.next_generation();
855 }
856
857 #[test]
858 fn get_next_clock_cycle_increment_empty_stack() {
859 let stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
860 assert!(stack.iter_continuations_for_next_clock().next().is_none());
861 }
862
863 #[test]
864 fn get_next_clock_cycle_increment_ends_with_incrementing() {
865 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
866 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
868
869 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
870 assert_eq!(result.len(), 1);
871 assert!(matches!(result[0], Continuation::StartNode(_)));
872 }
873
874 #[test]
875 fn get_next_clock_cycle_increment_enter_forest_after_incrementing() {
876 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
877 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
879 stack.push_continuation(Continuation::EnterForest {
881 forest: Arc::new(MastForest::new()),
882 package_debug_info: None,
883 inline_context_depth: 0,
884 });
885
886 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
887 assert_eq!(result.len(), 2);
889 assert!(matches!(result[0], Continuation::EnterForest { .. }));
890 assert!(matches!(result[1], Continuation::StartNode(_)));
891 }
892
893 #[test]
894 fn get_next_clock_cycle_increment_multiple_enter_forest_after_incrementing() {
895 let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
896 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
898 stack.push_continuation(Continuation::EnterForest {
900 forest: Arc::new(MastForest::new()),
901 package_debug_info: None,
902 inline_context_depth: 0,
903 });
904 stack.push_continuation(Continuation::EnterForest {
905 forest: Arc::new(MastForest::new()),
906 package_debug_info: None,
907 inline_context_depth: 0,
908 });
909
910 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
911 assert_eq!(result.len(), 3);
913 assert!(matches!(result[0], Continuation::EnterForest { .. }));
914 assert!(matches!(result[1], Continuation::EnterForest { .. }));
915 assert!(matches!(result[2], Continuation::StartNode(_)));
916 }
917
918 #[test]
919 fn inline_call_context_uses_the_source_boundary_index() {
920 let mut builder = PackageDebugInfoBuilder::default();
921 let source_node_id = builder
922 .add_node(DebugSourceNode {
923 exec_node: MastNodeId::new_unchecked(0),
924 children: Vec::new(),
925 op_start: 7,
926 op_end: 7,
927 asm_ops: Vec::new(),
928 debug_vars: Vec::new(),
929 inline_calls: vec![DebugSourceInlineCall {
930 op_idx: 7,
931 op_end: 7,
932 callee_idx: DebugFunctionIdx::from(0),
933 loc_idx: DebugLocIdx::from(0),
934 }],
935 })
936 .unwrap();
937 let debug_info = Arc::from(builder.build());
938
939 let context =
940 SourceInlineCallContext::for_source_boundary(debug_info, Some(source_node_id))
941 .expect("boundary row should create inherited inline context");
942
943 assert_eq!(context.inline_calls().map(|row| row.op_idx).collect::<Vec<_>>(), [7]);
944 }
945
946 #[test]
947 fn continuation_stack_mast_forest_id_round_trip_omits_debug_metadata() {
948 let mut stack: ContinuationStack<MastForestId> = ContinuationStack::default();
949 stack.push_continuation(Continuation::StartNode(MastNodeId::from(1)));
950 stack.push_continuation(Continuation::EnterForest {
951 forest: MastForestId::from(2),
952 package_debug_info: None,
953 inline_context_depth: 0,
954 });
955 stack.push_continuation(Continuation::ResumeBasicBlock {
956 node_id: MastNodeId::from(3),
957 batch_index: 4,
958 op_idx_in_batch: 5,
959 });
960 stack.start_tracking_source_nodes(None);
961 stack.source_node_ids =
962 Some(vec![Some(DebugSourceNodeId::from(10)), None, Some(DebugSourceNodeId::from(11))]);
963 assert!(stack.debug_activations.is_some());
964
965 let bytes = stack.to_bytes();
966 let restored = ContinuationStack::<MastForestId>::read_from_bytes(&bytes).unwrap();
967
968 assert_eq!(restored.stack.len(), 3);
969 assert!(matches!(
970 restored.stack[0],
971 Continuation::StartNode(node_id) if node_id == MastNodeId::from(1)
972 ));
973 assert!(matches!(
974 restored.stack[1],
975 Continuation::EnterForest {
976 forest,
977 package_debug_info: None,
978 inline_context_depth: 0,
979 } if forest == MastForestId::from(2)
980 ));
981 assert!(matches!(
982 restored.stack[2],
983 Continuation::ResumeBasicBlock {
984 node_id,
985 batch_index: 4,
986 op_idx_in_batch: 5,
987 } if node_id == MastNodeId::from(3)
988 ));
989 assert_eq!(restored.source_node_ids, None);
992 assert!(restored.debug_activations.is_none());
993 }
994}
995
996#[cfg(test)]
1000mod wire_contract_pin {
1001 use alloc::{vec, vec::Vec};
1002
1003 use miden_core::{
1004 mast::{MastForestId, MastNodeId},
1005 serde::{Deserializable, Serializable, SliceReader},
1006 };
1007
1008 use super::*;
1009
1010 #[test]
1013 fn tracked_and_untracked_stacks_serialize_identically() {
1014 let mut tracked = ContinuationStack::<MastForestId>::default();
1015 tracked.start_tracking_source_nodes(None);
1016 tracked.push_with_source_node_id(
1017 Continuation::FinishJoin(MastNodeId::from(0u32)),
1018 Some(DebugSourceNodeId::from(0u32)),
1019 );
1020 assert_eq!(tracked.source_node_ids, Some(vec![Some(DebugSourceNodeId::from(0u32))]));
1021
1022 let mut untracked = ContinuationStack::<MastForestId>::default();
1023 untracked.push_continuation(Continuation::FinishJoin(MastNodeId::from(0u32)));
1024
1025 let mut tracked_bytes = Vec::new();
1026 tracked.write_into(&mut tracked_bytes);
1027 let mut untracked_bytes = Vec::new();
1028 untracked.write_into(&mut untracked_bytes);
1029
1030 assert_eq!(tracked_bytes, untracked_bytes);
1031
1032 let decoded =
1033 ContinuationStack::<MastForestId>::read_from(&mut SliceReader::new(&tracked_bytes))
1034 .expect("tracked stack round-trips through the wire");
1035 assert_eq!(decoded, untracked);
1036 assert_eq!(decoded.source_node_ids, None);
1037 }
1038}