Skip to main content

miden_processor/
continuation_stack.rs

1use alloc::{sync::Arc, vec::Vec};
2
3use miden_core::{
4    mast::{MastForest, MastNodeId},
5    program::Program,
6};
7
8/// A hint for the initial size of the continuation stack.
9const CONTINUATION_STACK_SIZE_HINT: usize = 64;
10
11// CONTINUATION
12// ================================================================================================
13
14/// Represents a unit of work in the continuation stack.
15///
16/// This enum defines the different types of continuations that can be performed on MAST nodes
17/// during program execution.
18#[derive(Debug, Clone)]
19pub enum Continuation {
20    /// Start processing a node in the MAST forest.
21    StartNode(MastNodeId),
22    /// Process the finish phase of a Join node.
23    FinishJoin(MastNodeId),
24    /// Process the finish phase of a Split node.
25    FinishSplit(MastNodeId),
26    /// Process the finish phase of a Loop node.
27    ///
28    /// The `was_entered` field indicates whether the loop body was entered at least once. When
29    /// `was_entered == false`, the loop condition was `ZERO` and the loop body was never executed.
30    FinishLoop { node_id: MastNodeId, was_entered: bool },
31    /// Process the finish phase of a Call node.
32    FinishCall(MastNodeId),
33    /// Process the finish phase of a Dyn node.
34    FinishDyn(MastNodeId),
35    /// Process the finish phase of an External node (execute after_exit decorators).
36    FinishExternal(MastNodeId),
37    /// Resume execution at the specified operation of the specified batch in the given basic block
38    /// node.
39    ResumeBasicBlock {
40        node_id: MastNodeId,
41        batch_index: usize,
42        op_idx_in_batch: usize,
43    },
44    /// Resume execution at the RESPAN operation before the specific batch within a basic block
45    /// node.
46    Respan { node_id: MastNodeId, batch_index: usize },
47    /// Process the finish phase of a basic block node.
48    ///
49    /// This corresponds to incrementing the clock to account for the inserted END operation, and
50    /// then executing `AfterExitDecorators`.
51    FinishBasicBlock(MastNodeId),
52    /// Enter a new MAST forest, where all subsequent `MastNodeId`s will be relative to this forest.
53    ///
54    /// When we encounter an `ExternalNode`, we enter the corresponding MAST forest directly, and
55    /// push an `EnterForest` continuation to restore the previous forest when done.
56    EnterForest(Arc<MastForest>),
57    /// Process the `after_exit` decorators of the given node.
58    AfterExitDecorators(MastNodeId),
59}
60
61impl Continuation {
62    /// Returns true if executing this continuation increments the processor clock, and false
63    /// otherwise.
64    pub fn increments_clk(&self) -> bool {
65        use Continuation::*;
66
67        // Note: we prefer naming all the variants over using a wildcard arm to ensure that if new
68        // variants are added in the future, we consciously decide whether they should increment the
69        // clock or not.
70        match self {
71            StartNode(_)
72            | FinishJoin(_)
73            | FinishSplit(_)
74            | FinishLoop { node_id: _, was_entered: _ }
75            | FinishCall(_)
76            | FinishDyn(_)
77            | ResumeBasicBlock {
78                node_id: _,
79                batch_index: _,
80                op_idx_in_batch: _,
81            }
82            | Respan { node_id: _, batch_index: _ }
83            | FinishBasicBlock(_) => true,
84
85            FinishExternal(_) | EnterForest(_) | AfterExitDecorators(_) => false,
86        }
87    }
88}
89
90// CONTINUATION STACK
91// ================================================================================================
92
93/// [ContinuationStack] reifies the call stack used by the processor when executing a program made
94/// up of possibly multiple MAST forests.
95///
96/// This allows the processor to execute a program iteratively in a loop rather than recursively
97/// traversing the nodes. It also allows the processor to pass the state of execution to another
98/// processor for further processing, which is useful for parallel execution of MAST forests.
99#[derive(Debug, Default, Clone)]
100pub struct ContinuationStack {
101    stack: Vec<Continuation>,
102}
103
104impl ContinuationStack {
105    /// Creates a new continuation stack for a program.
106    ///
107    /// # Arguments
108    /// * `program` - The program whose execution will be managed by this continuation stack
109    pub fn new(program: &Program) -> Self {
110        let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
111        stack.push(Continuation::StartNode(program.entrypoint()));
112
113        Self { stack }
114    }
115
116    // STATE MUTATORS
117    // --------------------------------------------------------------------------------------------
118
119    /// Pushes a continuation onto the continuation stack.
120    pub fn push_continuation(&mut self, continuation: Continuation) {
121        self.stack.push(continuation);
122    }
123
124    /// Pushes a continuation to enter the given MAST forest on the continuation stack.
125    ///
126    /// # Arguments
127    /// * `forest` - The MAST forest to enter
128    pub fn push_enter_forest(&mut self, forest: Arc<MastForest>) {
129        self.stack.push(Continuation::EnterForest(forest));
130    }
131
132    /// Pushes a join finish continuation onto the stack.
133    pub fn push_finish_join(&mut self, node_id: MastNodeId) {
134        self.stack.push(Continuation::FinishJoin(node_id));
135    }
136
137    /// Pushes a split finish continuation onto the stack.
138    pub fn push_finish_split(&mut self, node_id: MastNodeId) {
139        self.stack.push(Continuation::FinishSplit(node_id));
140    }
141
142    /// Pushes a loop finish continuation onto the stack, for which the loop was entered.
143    pub fn push_finish_loop_entered(&mut self, node_id: MastNodeId) {
144        self.stack.push(Continuation::FinishLoop { node_id, was_entered: true });
145    }
146
147    /// Pushes a call finish continuation onto the stack.
148    pub fn push_finish_call(&mut self, node_id: MastNodeId) {
149        self.stack.push(Continuation::FinishCall(node_id));
150    }
151
152    /// Pushes a dyn finish continuation onto the stack.
153    pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
154        self.stack.push(Continuation::FinishDyn(node_id));
155    }
156
157    /// Pushes an external finish continuation onto the stack.
158    pub fn push_finish_external(&mut self, node_id: MastNodeId) {
159        self.stack.push(Continuation::FinishExternal(node_id));
160    }
161
162    /// Pushes a continuation to start processing the given node.
163    ///
164    /// # Arguments
165    /// * `node_id` - The ID of the node to process
166    pub fn push_start_node(&mut self, node_id: MastNodeId) {
167        self.stack.push(Continuation::StartNode(node_id));
168    }
169
170    /// Pops the next continuation from the continuation stack, and returns it along with its
171    /// associated MAST forest.
172    pub fn pop_continuation(&mut self) -> Option<Continuation> {
173        self.stack.pop()
174    }
175
176    // PUBLIC ACCESSORS
177    // --------------------------------------------------------------------------------------------
178
179    /// Returns the number of continuations on the stack.
180    pub fn len(&self) -> usize {
181        self.stack.len()
182    }
183
184    /// Peeks at the next continuation to execute without removing it.
185    ///
186    /// Note that more than one continuation may execute in the same clock cycle. To get all
187    /// continuations that will execute in the next clock cycle, use
188    /// [`Self::iter_continuations_for_next_clock`].
189    pub fn peek_continuation(&self) -> Option<&Continuation> {
190        self.stack.last()
191    }
192
193    /// Returns an iterator over the continuations on the stack that will execute in the next clock
194    /// cycle.
195    ///
196    /// This includes all coming continuations up to and including the first continuation that
197    /// increments the clock.
198    ///
199    /// Note: for this iterator to function correctly, it must be the case that that executing a
200    /// continuation that doesn't increment the clock *does not* push new continuations on the
201    /// stack. This is currently the case, and is a reasonable invariant to maintain, as
202    /// continuations that don't increment the clock can be expected to be simple (e.g. run some
203    /// decorators, or enter a new mast forest).
204    pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation> {
205        let mut found_incrementing_cont = false;
206
207        self.stack.iter().rev().take_while(move |continuation| {
208            if found_incrementing_cont {
209                // We have already found the first incrementing continuation, stop here.
210                false
211            } else if continuation.increments_clk() {
212                // This is the first incrementing continuation we have found.
213                found_incrementing_cont = true;
214                true
215            } else {
216                // This continuation does not increment the clock, continue.
217                true
218            }
219        })
220    }
221}
222
223// TESTS
224// ================================================================================================
225
226#[cfg(test)]
227mod tests {
228    use super::*;
229
230    #[test]
231    fn get_next_clock_cycle_increment_empty_stack() {
232        let stack = ContinuationStack::default();
233        assert!(stack.iter_continuations_for_next_clock().next().is_none());
234    }
235
236    #[test]
237    fn get_next_clock_cycle_increment_ends_with_incrementing() {
238        let mut stack = ContinuationStack::default();
239        // Push a continuation that increments the clock
240        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
241
242        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
243        assert_eq!(result.len(), 1);
244        assert!(matches!(result[0], Continuation::StartNode(_)));
245    }
246
247    #[test]
248    fn get_next_clock_cycle_increment_non_incrementing_after_incrementing() {
249        let mut stack = ContinuationStack::default();
250        // Push an incrementing continuation first (bottom of stack)
251        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
252        // Push a non-incrementing continuation on top
253        stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
254
255        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
256        // Should return: AfterExitDecorators (non-incrementing), then StartNode (first
257        // incrementing)
258        assert_eq!(result.len(), 2);
259        assert!(matches!(result[0], Continuation::AfterExitDecorators(_)));
260        assert!(matches!(result[1], Continuation::StartNode(_)));
261    }
262
263    #[test]
264    fn get_next_clock_cycle_increment_two_non_incrementing_after_incrementing() {
265        let mut stack = ContinuationStack::default();
266        // Push an incrementing continuation first (bottom of stack)
267        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
268        // Push two non-incrementing continuations on top
269        stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
270        stack.push_continuation(Continuation::EnterForest(Arc::new(MastForest::new())));
271
272        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
273        // Should return: EnterForest, AfterExitDecorators, StartNode
274        assert_eq!(result.len(), 3);
275        assert!(matches!(result[0], Continuation::EnterForest(_)));
276        assert!(matches!(result[1], Continuation::AfterExitDecorators(_)));
277        assert!(matches!(result[2], Continuation::StartNode(_)));
278    }
279}