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 `AfterExitDecoratorsBasicBlock`.
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    /// Process the `after_exit` decorators of the basic block node.
60    ///
61    /// Similar to `AfterExitDecorators`, but also executes all operation-level decorators that
62    /// refer to after the last operation in the basic block. See
63    /// [`miden_core::mast::BasicBlockNode`] for more details.
64    AfterExitDecoratorsBasicBlock(MastNodeId),
65}
66
67impl Continuation {
68    /// Returns true if executing this continuation increments the processor clock, and false
69    /// otherwise.
70    pub fn increments_clk(&self) -> bool {
71        use Continuation::*;
72
73        // Note: we prefer naming all the variants over using a wildcard arm to ensure that if new
74        // variants are added in the future, we consciously decide whether they should increment the
75        // clock or not.
76        match self {
77            StartNode(_)
78            | FinishJoin(_)
79            | FinishSplit(_)
80            | FinishLoop { node_id: _, was_entered: _ }
81            | FinishCall(_)
82            | FinishDyn(_)
83            | ResumeBasicBlock {
84                node_id: _,
85                batch_index: _,
86                op_idx_in_batch: _,
87            }
88            | Respan { node_id: _, batch_index: _ }
89            | FinishBasicBlock(_) => true,
90
91            FinishExternal(_)
92            | EnterForest(_)
93            | AfterExitDecorators(_)
94            | AfterExitDecoratorsBasicBlock(_) => false,
95        }
96    }
97}
98
99// CONTINUATION STACK
100// ================================================================================================
101
102/// [ContinuationStack] reifies the call stack used by the processor when executing a program made
103/// up of possibly multiple MAST forests.
104///
105/// This allows the processor to execute a program iteratively in a loop rather than recursively
106/// traversing the nodes. It also allows the processor to pass the state of execution to another
107/// processor for further processing, which is useful for parallel execution of MAST forests.
108#[derive(Debug, Default, Clone)]
109pub struct ContinuationStack {
110    stack: Vec<Continuation>,
111}
112
113impl ContinuationStack {
114    /// Creates a new continuation stack for a program.
115    ///
116    /// # Arguments
117    /// * `program` - The program whose execution will be managed by this continuation stack
118    pub fn new(program: &Program) -> Self {
119        let mut stack = Vec::with_capacity(CONTINUATION_STACK_SIZE_HINT);
120        stack.push(Continuation::StartNode(program.entrypoint()));
121
122        Self { stack }
123    }
124
125    // STATE MUTATORS
126    // --------------------------------------------------------------------------------------------
127
128    /// Pushes a continuation onto the continuation stack.
129    pub fn push_continuation(&mut self, continuation: Continuation) {
130        self.stack.push(continuation);
131    }
132
133    /// Pushes a continuation to enter the given MAST forest on the continuation stack.
134    ///
135    /// # Arguments
136    /// * `forest` - The MAST forest to enter
137    pub fn push_enter_forest(&mut self, forest: Arc<MastForest>) {
138        self.stack.push(Continuation::EnterForest(forest));
139    }
140
141    /// Pushes a join finish continuation onto the stack.
142    pub fn push_finish_join(&mut self, node_id: MastNodeId) {
143        self.stack.push(Continuation::FinishJoin(node_id));
144    }
145
146    /// Pushes a split finish continuation onto the stack.
147    pub fn push_finish_split(&mut self, node_id: MastNodeId) {
148        self.stack.push(Continuation::FinishSplit(node_id));
149    }
150
151    /// Pushes a loop finish continuation onto the stack, for which the loop was entered.
152    pub fn push_finish_loop_entered(&mut self, node_id: MastNodeId) {
153        self.stack.push(Continuation::FinishLoop { node_id, was_entered: true });
154    }
155
156    /// Pushes a call finish continuation onto the stack.
157    pub fn push_finish_call(&mut self, node_id: MastNodeId) {
158        self.stack.push(Continuation::FinishCall(node_id));
159    }
160
161    /// Pushes a dyn finish continuation onto the stack.
162    pub fn push_finish_dyn(&mut self, node_id: MastNodeId) {
163        self.stack.push(Continuation::FinishDyn(node_id));
164    }
165
166    /// Pushes an external finish continuation onto the stack.
167    pub fn push_finish_external(&mut self, node_id: MastNodeId) {
168        self.stack.push(Continuation::FinishExternal(node_id));
169    }
170
171    /// Pushes a continuation to start processing the given node.
172    ///
173    /// # Arguments
174    /// * `node_id` - The ID of the node to process
175    pub fn push_start_node(&mut self, node_id: MastNodeId) {
176        self.stack.push(Continuation::StartNode(node_id));
177    }
178
179    /// Pops the next continuation from the continuation stack, and returns it along with its
180    /// associated MAST forest.
181    pub fn pop_continuation(&mut self) -> Option<Continuation> {
182        self.stack.pop()
183    }
184
185    // PUBLIC ACCESSORS
186    // --------------------------------------------------------------------------------------------
187
188    /// Peeks at the next continuation to execute without removing it.
189    ///
190    /// Note that more than one continuation may execute in the same clock cycle. To get all
191    /// continuations that will execute in the next clock cycle, use
192    /// [`Self::iter_continuations_for_next_clock`].
193    pub fn peek_continuation(&self) -> Option<&Continuation> {
194        self.stack.last()
195    }
196
197    /// Returns an iterator over the continuations on the stack that will execute in the next clock
198    /// cycle.
199    ///
200    /// This includes all coming continuations up to and including the first continuation that
201    /// increments the clock.
202    ///
203    /// Note: for this iterator to function correctly, it must be the case that that executing a
204    /// continuation that doesn't increment the clock *does not* push new continuations on the
205    /// stack. This is currently the case, and is a reasonable invariant to maintain, as
206    /// continuations that don't increment the clock can be expected to be simple (e.g. run some
207    /// decorators, or enter a new mast forest).
208    pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation> {
209        let mut found_incrementing_cont = false;
210
211        self.stack.iter().rev().take_while(move |continuation| {
212            if found_incrementing_cont {
213                // We have already found the first incrementing continuation, stop here.
214                false
215            } else if continuation.increments_clk() {
216                // This is the first incrementing continuation we have found.
217                found_incrementing_cont = true;
218                true
219            } else {
220                // This continuation does not increment the clock, continue.
221                true
222            }
223        })
224    }
225}
226
227// TESTS
228// ================================================================================================
229
230#[cfg(test)]
231mod tests {
232    use super::*;
233
234    #[test]
235    fn get_next_clock_cycle_increment_empty_stack() {
236        let stack = ContinuationStack::default();
237        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
238        assert!(result.is_empty());
239    }
240
241    #[test]
242    fn get_next_clock_cycle_increment_ends_with_incrementing() {
243        let mut stack = ContinuationStack::default();
244        // Push a continuation that increments the clock
245        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
246
247        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
248        assert_eq!(result.len(), 1);
249        assert!(matches!(result[0], Continuation::StartNode(_)));
250    }
251
252    #[test]
253    fn get_next_clock_cycle_increment_non_incrementing_after_incrementing() {
254        let mut stack = ContinuationStack::default();
255        // Push an incrementing continuation first (bottom of stack)
256        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
257        // Push a non-incrementing continuation on top
258        stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
259
260        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
261        // Should return: AfterExitDecorators (non-incrementing), then StartNode (first
262        // incrementing)
263        assert_eq!(result.len(), 2);
264        assert!(matches!(result[0], Continuation::AfterExitDecorators(_)));
265        assert!(matches!(result[1], Continuation::StartNode(_)));
266    }
267
268    #[test]
269    fn get_next_clock_cycle_increment_two_non_incrementing_after_incrementing() {
270        let mut stack = ContinuationStack::default();
271        // Push an incrementing continuation first (bottom of stack)
272        stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
273        // Push two non-incrementing continuations on top
274        stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
275        stack.push_continuation(Continuation::EnterForest(Arc::new(MastForest::new())));
276
277        let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
278        // Should return: EnterForest, AfterExitDecorators, StartNode
279        assert_eq!(result.len(), 3);
280        assert!(matches!(result[0], Continuation::EnterForest(_)));
281        assert!(matches!(result[1], Continuation::AfterExitDecorators(_)));
282        assert!(matches!(result[2], Continuation::StartNode(_)));
283    }
284}