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}