Skip to main content

miden_processor/
continuation_stack.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use miden_core::{mast::MastNodeId, program::Program};
4use miden_mast_package::debug_info::{DebugSourceNodeId, PackageDebugInfo};
5
6/// A hint for the initial size of the continuation stack.
7const CONTINUATION_STACK_SIZE_HINT: usize = 64;
8
9// CONTINUATION
10// ================================================================================================
11
12/// Represents a unit of work in the continuation stack.
13///
14/// This enum defines the different types of continuations that can be performed on MAST nodes
15/// during program execution.
16///
17/// The type parameter `F` is the representation of a MAST forest carried by the
18/// [`Continuation::EnterForest`] variant. For live execution this is `Arc<MastForest>`; for the
19/// snapshotted continuation stack inside a trace fragment it is a `usize` index into the
20/// `mast_forest_store` of the trace generation context.
21#[derive(Debug, Clone)]
22pub enum Continuation<F> {
23    /// Start processing a node in the MAST forest.
24    StartNode(MastNodeId),
25    /// Process the finish phase of a Join node.
26    FinishJoin(MastNodeId),
27    /// Process the finish phase of a Split node.
28    FinishSplit(MastNodeId),
29    /// Process the finish phase of a Loop node.
30    ///
31    /// Reached after the loop body has finished executing. Inspects the condition the body left on
32    /// top of the stack and either fires REPEAT (re-enter the body) or END (exit the loop). Loop
33    /// bodies are entered unconditionally — a `while.true` source construct is desugared into a
34    /// SPLIT that wraps the LOOP, so the LOOP itself sees a do-while body.
35    FinishLoop(MastNodeId),
36    /// Process the finish phase of a Call node.
37    FinishCall(MastNodeId),
38    /// Process the finish phase of a Dyn node.
39    FinishDyn(MastNodeId),
40    /// Resume execution at the specified operation of the specified batch in the given basic block
41    /// node.
42    ResumeBasicBlock {
43        node_id: MastNodeId,
44        batch_index: usize,
45        op_idx_in_batch: usize,
46    },
47    /// Resume execution at the RESPAN operation before the specific batch within a basic block
48    /// node.
49    Respan { node_id: MastNodeId, batch_index: usize },
50    /// Process the finish phase of a basic block node.
51    ///
52    /// This corresponds to incrementing the clock to account for the inserted END operation.
53    FinishBasicBlock(MastNodeId),
54    /// Enter a new MAST forest, where all subsequent `MastNodeId`s will be relative to this forest.
55    ///
56    /// When we encounter an `ExternalNode`, we enter the corresponding MAST forest directly, and
57    /// push an `EnterForest` continuation to restore the previous forest when done.
58    EnterForest {
59        forest: F,
60        package_debug_info: Option<Arc<PackageDebugInfo>>,
61    },
62}
63
64impl<F> Continuation<F> {
65    /// Returns true if executing this continuation increments the processor clock, and false
66    /// otherwise.
67    pub fn increments_clk(&self) -> bool {
68        use Continuation::*;
69
70        // Note: we prefer naming all the variants over using a wildcard arm to ensure that if new
71        // variants are added in the future, we consciously decide whether they should increment the
72        // clock or not.
73        match self {
74            StartNode(_)
75            | FinishJoin(_)
76            | FinishSplit(_)
77            | FinishLoop(_)
78            | FinishCall(_)
79            | FinishDyn(_)
80            | ResumeBasicBlock {
81                node_id: _,
82                batch_index: _,
83                op_idx_in_batch: _,
84            }
85            | Respan { node_id: _, batch_index: _ }
86            | FinishBasicBlock(_) => true,
87
88            EnterForest { .. } => false,
89        }
90    }
91
92    #[cfg(any(test, feature = "testing"))]
93    pub(crate) fn exec_node(&self) -> Option<MastNodeId> {
94        match self {
95            Self::StartNode(node_id)
96            | Self::FinishJoin(node_id)
97            | Self::FinishSplit(node_id)
98            | Self::FinishLoop(node_id)
99            | Self::FinishCall(node_id)
100            | Self::FinishDyn(node_id)
101            | Self::ResumeBasicBlock { node_id, .. }
102            | Self::Respan { node_id, .. }
103            | Self::FinishBasicBlock(node_id) => Some(*node_id),
104            Self::EnterForest { .. } => None,
105        }
106    }
107}
108
109// CONTINUATION STACK
110// ================================================================================================
111
112/// [ContinuationStack] reifies the call stack used by the processor when executing a program made
113/// up of possibly multiple MAST forests.
114///
115/// This allows the processor to execute a program iteratively in a loop rather than recursively
116/// traversing the nodes. It also allows the processor to pass the state of execution to another
117/// processor for further processing, which is useful for parallel execution of MAST forests.
118#[derive(Debug, Clone)]
119pub struct ContinuationStack<F> {
120    stack: Vec<Continuation<F>>,
121    source_node_ids: Option<Vec<Option<DebugSourceNodeId>>>,
122}
123
124impl<F> Default for ContinuationStack<F> {
125    fn default() -> Self {
126        Self { stack: Vec::new(), source_node_ids: None }
127    }
128}
129
130impl<F> ContinuationStack<F> {
131    /// Creates a new continuation stack for a program.
132    ///
133    /// # Arguments
134    /// * `program` - The program whose execution will be managed by this continuation stack
135    pub fn new(program: &Program) -> Self {
136        let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
137        stack.push(Continuation::StartNode(program.entrypoint()));
138
139        Self { stack, source_node_ids: None }
140    }
141
142    pub(crate) fn new_with_source_node_id(
143        program: &Program,
144        source_node_id: DebugSourceNodeId,
145    ) -> Self {
146        Self::new_with_optional_source_node_id(program, Some(source_node_id))
147    }
148
149    pub(crate) fn new_with_optional_source_node_id(
150        program: &Program,
151        source_node_id: Option<DebugSourceNodeId>,
152    ) -> Self {
153        let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
154        stack.push(Continuation::StartNode(program.entrypoint()));
155
156        let mut source_node_ids = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
157        source_node_ids.push(source_node_id);
158
159        Self {
160            stack,
161            source_node_ids: Some(source_node_ids),
162        }
163    }
164
165    // STATE MUTATORS
166    // --------------------------------------------------------------------------------------------
167
168    /// Pushes a continuation onto the continuation stack.
169    pub fn push_continuation(&mut self, continuation: Continuation<F>) {
170        self.stack.push(continuation);
171        self.push_source_node_id(None);
172    }
173
174    pub(crate) fn push_with_source_node_id(
175        &mut self,
176        continuation: Continuation<F>,
177        source_node_id: Option<DebugSourceNodeId>,
178    ) {
179        self.stack.push(continuation);
180        self.push_source_node_id(source_node_id);
181    }
182
183    /// Pushes a continuation to enter the given MAST forest on the continuation stack.
184    ///
185    /// # Arguments
186    /// * `forest` - The MAST forest to enter
187    pub fn push_enter_forest(&mut self, forest: F) {
188        self.push_enter_forest_with_package_debug_info(forest, None);
189    }
190
191    pub(crate) fn push_enter_forest_with_package_debug_info(
192        &mut self,
193        forest: F,
194        package_debug_info: Option<Arc<PackageDebugInfo>>,
195    ) {
196        self.stack.push(Continuation::EnterForest { forest, package_debug_info });
197        self.push_source_node_id(None);
198    }
199
200    /// Pushes a join finish continuation onto the stack.
201    pub fn push_finish_join(&mut self, node_id: MastNodeId) {
202        self.stack.push(Continuation::FinishJoin(node_id));
203        self.push_source_node_id(None);
204    }
205
206    /// Pushes a split finish continuation onto the stack.
207    pub fn push_finish_split(&mut self, node_id: MastNodeId) {
208        self.stack.push(Continuation::FinishSplit(node_id));
209        self.push_source_node_id(None);
210    }
211
212    /// Pushes a loop finish continuation onto the stack.
213    pub fn push_finish_loop(&mut self, node_id: MastNodeId) {
214        self.stack.push(Continuation::FinishLoop(node_id));
215        self.push_source_node_id(None);
216    }
217
218    /// Pushes a call finish continuation onto the stack.
219    pub fn push_finish_call(&mut self, node_id: MastNodeId) {
220        self.stack.push(Continuation::FinishCall(node_id));
221        self.push_source_node_id(None);
222    }
223
224    /// Pushes a dyn finish continuation onto the stack.
225    pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
226        self.stack.push(Continuation::FinishDyn(node_id));
227        self.push_source_node_id(None);
228    }
229
230    /// Pushes a continuation to start processing the given node.
231    ///
232    /// # Arguments
233    /// * `node_id` - The ID of the node to process
234    pub fn push_start_node(&mut self, node_id: MastNodeId) {
235        self.stack.push(Continuation::StartNode(node_id));
236        self.push_source_node_id(None);
237    }
238
239    /// Pops the next continuation from the continuation stack, and returns it along with its
240    /// associated MAST forest.
241    pub fn pop_continuation(&mut self) -> Option<Continuation<F>> {
242        let continuation = self.stack.pop()?;
243        if let Some(source_node_ids) = &mut self.source_node_ids {
244            source_node_ids.pop();
245        }
246        Some(continuation)
247    }
248
249    pub(crate) fn pop_continuation_with_source_node_id(
250        &mut self,
251    ) -> Option<(Continuation<F>, Option<DebugSourceNodeId>)> {
252        let continuation = self.stack.pop()?;
253        let source_node_id = self.source_node_ids.as_mut().and_then(Vec::pop).flatten();
254        Some((continuation, source_node_id))
255    }
256
257    /// Consumes this stack and returns its continuations in bottom-to-top order (i.e. the order in
258    /// which they were originally pushed).
259    pub fn into_inner(self) -> Vec<Continuation<F>> {
260        self.stack
261    }
262
263    fn push_source_node_id(&mut self, source_node_id: Option<DebugSourceNodeId>) {
264        if let Some(source_node_ids) = &mut self.source_node_ids {
265            source_node_ids.push(source_node_id);
266        }
267    }
268
269    #[cfg(any(test, feature = "testing"))]
270    pub(crate) fn start_tracking_source_nodes(
271        &mut self,
272        next_source_node_id: Option<DebugSourceNodeId>,
273    ) {
274        let mut source_node_ids = Vec::with_capacity(self.stack.len());
275        source_node_ids.resize(self.stack.len(), None);
276        if let Some(source_node_id) = source_node_ids.last_mut() {
277            *source_node_id = next_source_node_id;
278        }
279        self.source_node_ids = Some(source_node_ids);
280    }
281
282    // PUBLIC ACCESSORS
283    // --------------------------------------------------------------------------------------------
284
285    /// Returns the number of continuations on the stack.
286    pub fn len(&self) -> usize {
287        self.stack.len()
288    }
289
290    /// Peeks at the next continuation to execute without removing it.
291    ///
292    /// Note that more than one continuation may execute in the same clock cycle. To get all
293    /// continuations that will execute in the next clock cycle, use
294    /// [`Self::iter_continuations_for_next_clock`].
295    pub fn peek_continuation(&self) -> Option<&Continuation<F>> {
296        self.stack.last()
297    }
298
299    pub(crate) fn peek_continuation_with_source_node_id(
300        &self,
301    ) -> Option<(&Continuation<F>, Option<DebugSourceNodeId>)> {
302        let continuation = self.stack.last()?;
303        let source_node_id = self
304            .source_node_ids
305            .as_ref()
306            .and_then(|source_node_ids| source_node_ids.last().copied().flatten());
307        Some((continuation, source_node_id))
308    }
309
310    pub(crate) fn tracks_source_nodes(&self) -> bool {
311        self.source_node_ids.is_some()
312    }
313
314    /// Returns an iterator over the continuations on the stack that will execute in the next clock
315    /// cycle.
316    ///
317    /// This includes all coming continuations up to and including the first continuation that
318    /// increments the clock.
319    ///
320    /// Note: for this iterator to function correctly, it must be the case that executing a
321    /// continuation that doesn't increment the clock *does not* push new continuations on the
322    /// stack. This is currently the case, and is a reasonable invariant to maintain, as
323    /// continuations that don't increment the clock can be expected to be simple (e.g. enter a new
324    /// mast forest).
325    pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation<F>> {
326        let mut found_incrementing_cont = false;
327
328        self.stack.iter().rev().take_while(move |continuation| {
329            if found_incrementing_cont {
330                // We have already found the first incrementing continuation, stop here.
331                false
332            } else if continuation.increments_clk() {
333                // This is the first incrementing continuation we have found.
334                found_incrementing_cont = true;
335                true
336            } else {
337                // This continuation does not increment the clock, continue.
338                true
339            }
340        })
341    }
342}
343
344// TESTS
345// ================================================================================================
346
347#[cfg(test)]
348mod tests {
349    use alloc::sync::Arc;
350
351    use miden_core::mast::MastForest;
352
353    use super::*;
354
355    #[test]
356    fn get_next_clock_cycle_increment_empty_stack() {
357        let stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
358        assert!(stack.iter_continuations_for_next_clock().next().is_none());
359    }
360
361    #[test]
362    fn get_next_clock_cycle_increment_ends_with_incrementing() {
363        let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
364        // Push a continuation that increments the clock
365        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
366
367        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
368        assert_eq!(result.len(), 1);
369        assert!(matches!(result[0], Continuation::StartNode(_)));
370    }
371
372    #[test]
373    fn get_next_clock_cycle_increment_enter_forest_after_incrementing() {
374        let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
375        // Push an incrementing continuation first (bottom of stack)
376        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
377        // Push a non-incrementing continuation on top
378        stack.push_continuation(Continuation::EnterForest {
379            forest: Arc::new(MastForest::new()),
380            package_debug_info: None,
381        });
382
383        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
384        // Should return: EnterForest (non-incrementing), then StartNode (first incrementing)
385        assert_eq!(result.len(), 2);
386        assert!(matches!(result[0], Continuation::EnterForest { .. }));
387        assert!(matches!(result[1], Continuation::StartNode(_)));
388    }
389
390    #[test]
391    fn get_next_clock_cycle_increment_multiple_enter_forest_after_incrementing() {
392        let mut stack: ContinuationStack<Arc<MastForest>> = ContinuationStack::default();
393        // Push an incrementing continuation first (bottom of stack)
394        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
395        // Push two non-incrementing continuations on top
396        stack.push_continuation(Continuation::EnterForest {
397            forest: Arc::new(MastForest::new()),
398            package_debug_info: None,
399        });
400        stack.push_continuation(Continuation::EnterForest {
401            forest: Arc::new(MastForest::new()),
402            package_debug_info: None,
403        });
404
405        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
406        // Should return: EnterForest, EnterForest, StartNode
407        assert_eq!(result.len(), 3);
408        assert!(matches!(result[0], Continuation::EnterForest { .. }));
409        assert!(matches!(result[1], Continuation::EnterForest { .. }));
410        assert!(matches!(result[2], Continuation::StartNode(_)));
411    }
412}