Skip to main content

miden_processor/
continuation_stack.rs

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
11/// A hint for the initial size of the continuation stack.
12const 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/// Package-owned source context whose inline-call rows remain active across a dynamic target.
76#[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    /// Returns the package debug information which owns this source occurrence.
107    pub fn debug_info(&self) -> &Arc<PackageDebugInfo> {
108        &self.package_debug_info
109    }
110
111    /// Returns the source occurrence whose boundary inline rows form this context.
112    pub fn source_node_id(&self) -> DebugSourceNodeId {
113        self.source_node_id
114    }
115
116    /// Returns the inline calls inherited at this source boundary.
117    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// CONTINUATION
124// ================================================================================================
125
126/// Represents a unit of work in the continuation stack.
127///
128/// This enum defines the different types of continuations that can be performed on MAST nodes
129/// during program execution.
130///
131/// The type parameter `F` is the representation of a MAST forest carried by the
132/// [`Continuation::EnterForest`] variant. For live execution this is `Arc<MastForest>`; for the
133/// snapshotted continuation stack inside a trace fragment it is a `usize` index into the
134/// `mast_forest_store` of the trace generation context.
135#[derive(Debug, Clone, PartialEq, Eq)]
136pub enum Continuation<F> {
137    /// Start processing a node in the MAST forest.
138    StartNode(MastNodeId),
139    /// Process the finish phase of a Join node.
140    FinishJoin(MastNodeId),
141    /// Process the finish phase of a Split node.
142    FinishSplit(MastNodeId),
143    /// Process the finish phase of a Loop node.
144    ///
145    /// Reached after the loop body has finished executing. Inspects the condition the body left on
146    /// top of the stack and either fires REPEAT (re-enter the body) or END (exit the loop). Loop
147    /// bodies are entered unconditionally — a `while.true` source construct is desugared into a
148    /// SPLIT that wraps the LOOP, so the LOOP itself sees a do-while body.
149    FinishLoop(MastNodeId),
150    /// Process the finish phase of a Call node.
151    FinishCall(MastNodeId),
152    /// Process the finish phase of a Dyn node.
153    FinishDyn(MastNodeId),
154    /// Resume execution at the specified operation of the specified batch in the given basic block
155    /// node.
156    ResumeBasicBlock {
157        node_id: MastNodeId,
158        batch_index: usize,
159        op_idx_in_batch: usize,
160    },
161    /// Resume execution at the RESPAN operation before the specific batch within a basic block
162    /// node.
163    Respan { node_id: MastNodeId, batch_index: usize },
164    /// Process the finish phase of a basic block node.
165    ///
166    /// This corresponds to incrementing the clock to account for the inserted END operation.
167    FinishBasicBlock(MastNodeId),
168    /// Enter a new MAST forest, where all subsequent `MastNodeId`s will be relative to this forest.
169    ///
170    /// When we encounter an `ExternalNode`, we enter the corresponding MAST forest directly, and
171    /// push an `EnterForest` continuation to restore the previous forest when done.
172    EnterForest {
173        forest: F,
174        package_debug_info: Option<Arc<PackageDebugInfo>>,
175        /// Inline-context stack depth to restore when returning to this forest.
176        inline_context_depth: usize,
177    },
178}
179
180impl<F> Continuation<F> {
181    /// Returns true if executing this continuation increments the processor clock, and false
182    /// otherwise.
183    pub fn increments_clk(&self) -> bool {
184        use Continuation::*;
185
186        // Note: we prefer naming all the variants over using a wildcard arm to ensure that if new
187        // variants are added in the future, we consciously decide whether they should increment the
188        // clock or not.
189        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// CONTINUATION STACK
225// ================================================================================================
226
227/// [ContinuationStack] reifies the call stack used by the processor when executing a program made
228/// up of possibly multiple MAST forests.
229///
230/// This allows the processor to execute a program iteratively in a loop rather than recursively
231/// traversing the nodes. It also allows the processor to pass the state of execution to another
232/// processor for further processing, which is useful for parallel execution of MAST forests.
233///
234/// Note: the binary wire format for this type is deliberately lossy (`package_debug_info` and
235/// `source_node_ids` are not serialized), so the exact-equality round-trip test generated by
236/// `serde_test` does not apply; `continuation_stack_mast_forest_id_round_trip_omits_debug_metadata`
237/// covers the lossy round trip instead.
238#[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    /// Creates a new continuation stack for a program.
274    ///
275    /// # Arguments
276    /// * `program` - The program whose execution will be managed by this continuation stack
277    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    // STATE MUTATORS
313    // --------------------------------------------------------------------------------------------
314
315    /// Pushes a continuation onto the continuation stack.
316    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    /// Pushes a continuation to enter the given MAST forest on the continuation stack.
340    ///
341    /// # Arguments
342    /// * `forest` - The MAST forest to enter
343    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    /// Pushes a join finish continuation onto the stack.
363    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    /// Pushes a split finish continuation onto the stack.
369    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    /// Pushes a loop finish continuation onto the stack.
375    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    /// Pushes a call finish continuation onto the stack.
381    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    /// Pushes a dyn finish continuation onto the stack.
387    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    /// Pushes a continuation to start processing the given node.
393    ///
394    /// # Arguments
395    /// * `node_id` - The ID of the node to process
396    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    /// Pops the next continuation from the continuation stack, and returns it along with its
402    /// associated MAST forest.
403    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    /// Consumes this stack and returns its continuations in bottom-to-top order (i.e. the order in
418    /// which they were originally pushed).
419    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    // PUBLIC ACCESSORS
468    // --------------------------------------------------------------------------------------------
469
470    /// Returns the number of continuations on the stack.
471    pub fn len(&self) -> usize {
472        self.stack.len()
473    }
474
475    /// Peeks at the next continuation to execute without removing it.
476    ///
477    /// Note that more than one continuation may execute in the same clock cycle. To get all
478    /// continuations that will execute in the next clock cycle, use
479    /// [`Self::iter_continuations_for_next_clock`].
480    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    /// Returns an iterator over the continuations on the stack that will execute in the next clock
500    /// cycle.
501    ///
502    /// This includes all coming continuations up to and including the first continuation that
503    /// increments the clock.
504    ///
505    /// Note: for this iterator to function correctly, it must be the case that executing a
506    /// continuation that doesn't increment the clock *does not* push new continuations on the
507    /// stack. This is currently the case, and is a reasonable invariant to maintain, as
508    /// continuations that don't increment the clock can be expected to be simple (e.g. enter a new
509    /// mast forest).
510    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                // We have already found the first incrementing continuation, stop here.
516                false
517            } else if continuation.increments_clk() {
518                // This is the first incrementing continuation we have found.
519                found_incrementing_cont = true;
520                true
521            } else {
522                // This continuation does not increment the clock, continue.
523                true
524            }
525        })
526    }
527
528    /// Same as [`Self::iter_continuations_for_next_clock`], but provides the set of source node
529    /// ids for each continuation.
530    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
556// SERIALIZATION
557// ================================================================================================
558
559const 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
570// NOTE: `package_debug_info` is deliberately *not* serialized: it is reconstructable from the
571// package a witness was produced from and would bloat the wire with data irrelevant to proving.
572// Round-tripping a `Continuation` (or any struct containing one, such as `ContinuationStack`)
573// therefore does not restore debug info exactly: deserialized continuations always carry
574// `package_debug_info: None`.
575impl 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
665// `source_node_ids` is deliberately *not* serialized: those indices point into a package's
666// `PackageDebugInfo`, which is itself absent from the wire (see the note above), so they would be
667// dangling references on the deserialized side. A restored stack simply does not track source
668// nodes, matching a non-debug-aware execution, which is all proving requires.
669impl 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// TESTS
687// ================================================================================================
688
689#[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        // Push a continuation that increments the clock
792        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        // Push an incrementing continuation first (bottom of stack)
803        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
804        // Push a non-incrementing continuation on top
805        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        // Should return: EnterForest (non-incrementing), then StartNode (first incrementing)
813        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        // Push an incrementing continuation first (bottom of stack)
822        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
823        // Push two non-incrementing continuations on top
824        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        // Should return: EnterForest, EnterForest, StartNode
837        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        // source_node_ids is not serialized: indices without their owning package debug info
915        // would be dangling on the restored side.
916        assert_eq!(restored.source_node_ids, None);
917        assert!(restored.debug_activations.is_none());
918    }
919}