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)]
136#[cfg_attr(
137    all(feature = "arbitrary", test),
138    miden_test_serialization_macros::serialization_test(types(MastForestId))
139)]
140pub enum Continuation<F> {
141    /// Start processing a node in the MAST forest.
142    StartNode(MastNodeId),
143    /// Process the finish phase of a Join node.
144    FinishJoin(MastNodeId),
145    /// Process the finish phase of a Split node.
146    FinishSplit(MastNodeId),
147    /// Process the finish phase of a Loop node.
148    ///
149    /// Reached after the loop body has finished executing. Inspects the condition the body left on
150    /// top of the stack and either fires REPEAT (re-enter the body) or END (exit the loop). Loop
151    /// bodies are entered unconditionally — a `while.true` source construct is desugared into a
152    /// SPLIT that wraps the LOOP, so the LOOP itself sees a do-while body.
153    FinishLoop(MastNodeId),
154    /// Process the finish phase of a Call node.
155    FinishCall(MastNodeId),
156    /// Process the finish phase of a Dyn node.
157    FinishDyn(MastNodeId),
158    /// Resume execution at the specified operation of the specified batch in the given basic block
159    /// node.
160    ResumeBasicBlock {
161        node_id: MastNodeId,
162        batch_index: usize,
163        op_idx_in_batch: usize,
164    },
165    /// Resume execution at the RESPAN operation before the specific batch within a basic block
166    /// node.
167    Respan { node_id: MastNodeId, batch_index: usize },
168    /// Process the finish phase of a basic block node.
169    ///
170    /// This corresponds to incrementing the clock to account for the inserted END operation.
171    FinishBasicBlock(MastNodeId),
172    /// Enter a new MAST forest, where all subsequent `MastNodeId`s will be relative to this forest.
173    ///
174    /// When we encounter an `ExternalNode`, we enter the corresponding MAST forest directly, and
175    /// push an `EnterForest` continuation to restore the previous forest when done.
176    EnterForest {
177        forest: F,
178        package_debug_info: Option<Arc<PackageDebugInfo>>,
179        /// Inline-context stack depth to restore when returning to this forest.
180        inline_context_depth: usize,
181    },
182}
183
184impl<F> Continuation<F> {
185    /// Returns true if executing this continuation increments the processor clock, and false
186    /// otherwise.
187    pub fn increments_clk(&self) -> bool {
188        use Continuation::*;
189
190        // Note: we prefer naming all the variants over using a wildcard arm to ensure that if new
191        // variants are added in the future, we consciously decide whether they should increment the
192        // clock or not.
193        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// CONTINUATION STACK
229// ================================================================================================
230
231/// [ContinuationStack] reifies the call stack used by the processor when executing a program made
232/// up of possibly multiple MAST forests.
233///
234/// This allows the processor to execute a program iteratively in a loop rather than recursively
235/// traversing the nodes. It also allows the processor to pass the state of execution to another
236/// processor for further processing, which is useful for parallel execution of MAST forests.
237///
238/// Note: the binary wire format for this type is deliberately lossy (`package_debug_info` and
239/// `source_node_ids` are not serialized), so the exact-equality round-trip test generated by
240/// `serde_test` does not apply; `continuation_stack_mast_forest_id_round_trip_omits_debug_metadata`
241/// covers the lossy round trip instead.
242#[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    /// Creates a new continuation stack for a program.
282    ///
283    /// # Arguments
284    /// * `program` - The program whose execution will be managed by this continuation stack
285    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    // STATE MUTATORS
321    // --------------------------------------------------------------------------------------------
322
323    /// Pushes a continuation onto the continuation stack.
324    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    /// Pushes a continuation to enter the given MAST forest on the continuation stack.
348    ///
349    /// # Arguments
350    /// * `forest` - The MAST forest to enter
351    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    /// Pushes a join finish continuation onto the stack.
371    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    /// Pushes a split finish continuation onto the stack.
377    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    /// Pushes a loop finish continuation onto the stack.
383    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    /// Pushes a call finish continuation onto the stack.
389    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    /// Pushes a dyn finish continuation onto the stack.
395    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    /// Pushes a continuation to start processing the given node.
401    ///
402    /// # Arguments
403    /// * `node_id` - The ID of the node to process
404    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    /// Pops the next continuation from the continuation stack, and returns it along with its
410    /// associated MAST forest.
411    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    /// Consumes this stack and returns its continuations in bottom-to-top order (i.e. the order in
426    /// which they were originally pushed).
427    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    // PUBLIC ACCESSORS
476    // --------------------------------------------------------------------------------------------
477
478    /// Returns the number of continuations on the stack.
479    pub fn len(&self) -> usize {
480        self.stack.len()
481    }
482
483    /// Peeks at the next continuation to execute without removing it.
484    ///
485    /// Note that more than one continuation may execute in the same clock cycle. To get all
486    /// continuations that will execute in the next clock cycle, use
487    /// [`Self::iter_continuations_for_next_clock`].
488    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    /// Returns an iterator over the continuations on the stack that will execute in the next clock
508    /// cycle.
509    ///
510    /// This includes all coming continuations up to and including the first continuation that
511    /// increments the clock.
512    ///
513    /// Note: for this iterator to function correctly, it must be the case that executing a
514    /// continuation that doesn't increment the clock *does not* push new continuations on the
515    /// stack. This is currently the case, and is a reasonable invariant to maintain, as
516    /// continuations that don't increment the clock can be expected to be simple (e.g. enter a new
517    /// mast forest).
518    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                // We have already found the first incrementing continuation, stop here.
524                false
525            } else if continuation.increments_clk() {
526                // This is the first incrementing continuation we have found.
527                found_incrementing_cont = true;
528                true
529            } else {
530                // This continuation does not increment the clock, continue.
531                true
532            }
533        })
534    }
535
536    /// Same as [`Self::iter_continuations_for_next_clock`], but provides the set of source node
537    /// ids for each continuation.
538    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
564// SERIALIZATION
565// ================================================================================================
566
567const 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
578// NOTE: `package_debug_info` is deliberately *not* serialized: it is reconstructable from the
579// package a witness was produced from and would bloat the wire with data irrelevant to proving.
580// Round-tripping a `Continuation` (or any struct containing one, such as `ContinuationStack`)
581// therefore does not restore debug info exactly: deserialized continuations always carry
582// `package_debug_info: None`.
583impl 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
673// `source_node_ids` is deliberately *not* serialized: those indices point into a package's
674// `PackageDebugInfo`, which is itself absent from the wire (see the note above), so they would be
675// dangling references on the deserialized side. A restored stack simply does not track source
676// nodes, matching a non-debug-aware execution, which is all proving requires.
677impl 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// ARBITRARY (proptest)
695// ===============================================================================================
696
697#[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            // Every variant is any-valid on the wire. EnterForest always carries
710            // package_debug_info: None - the wire format skips it, so Some would break the
711            // exact-equality oracle (that lossiness is covered by the dedicated deterministic
712            // test instead).
713            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            // The reader omits source-node tracking, so generated stacks keep it disabled.
749            // The wire contract test below covers stacks that track source nodes.
750            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// TESTS
762// ================================================================================================
763
764#[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        // Push a continuation that increments the clock
867        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        // Push an incrementing continuation first (bottom of stack)
878        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
879        // Push a non-incrementing continuation on top
880        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        // Should return: EnterForest (non-incrementing), then StartNode (first incrementing)
888        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        // Push an incrementing continuation first (bottom of stack)
897        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
898        // Push two non-incrementing continuations on top
899        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        // Should return: EnterForest, EnterForest, StartNode
912        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        // source_node_ids is not serialized: indices without their owning package debug info
990        // would be dangling on the restored side.
991        assert_eq!(restored.source_node_ids, None);
992        assert!(restored.debug_activations.is_none());
993    }
994}
995
996// WIRE CONTRACT PIN
997// ================================================================================================
998
999#[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    /// Source-node tracking is omitted from serialization. Enabling tracking must not change the
1011    /// bytes, and decoding must leave tracking disabled.
1012    #[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}