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 /// Returns the number of continuations on the stack.
189 pub fn len(&self) -> usize {
190 self.stack.len()
191 }
192
193 /// Peeks at the next continuation to execute without removing it.
194 ///
195 /// Note that more than one continuation may execute in the same clock cycle. To get all
196 /// continuations that will execute in the next clock cycle, use
197 /// [`Self::iter_continuations_for_next_clock`].
198 pub fn peek_continuation(&self) -> Option<&Continuation> {
199 self.stack.last()
200 }
201
202 /// Returns an iterator over the continuations on the stack that will execute in the next clock
203 /// cycle.
204 ///
205 /// This includes all coming continuations up to and including the first continuation that
206 /// increments the clock.
207 ///
208 /// Note: for this iterator to function correctly, it must be the case that that executing a
209 /// continuation that doesn't increment the clock *does not* push new continuations on the
210 /// stack. This is currently the case, and is a reasonable invariant to maintain, as
211 /// continuations that don't increment the clock can be expected to be simple (e.g. run some
212 /// decorators, or enter a new mast forest).
213 pub fn iter_continuations_for_next_clock(&self) -> impl Iterator<Item = &Continuation> {
214 let mut found_incrementing_cont = false;
215
216 self.stack.iter().rev().take_while(move |continuation| {
217 if found_incrementing_cont {
218 // We have already found the first incrementing continuation, stop here.
219 false
220 } else if continuation.increments_clk() {
221 // This is the first incrementing continuation we have found.
222 found_incrementing_cont = true;
223 true
224 } else {
225 // This continuation does not increment the clock, continue.
226 true
227 }
228 })
229 }
230}
231
232// TESTS
233// ================================================================================================
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn get_next_clock_cycle_increment_empty_stack() {
241 let stack = ContinuationStack::default();
242 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
243 assert!(result.is_empty());
244 }
245
246 #[test]
247 fn get_next_clock_cycle_increment_ends_with_incrementing() {
248 let mut stack = ContinuationStack::default();
249 // Push a continuation that increments the clock
250 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
251
252 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
253 assert_eq!(result.len(), 1);
254 assert!(matches!(result[0], Continuation::StartNode(_)));
255 }
256
257 #[test]
258 fn get_next_clock_cycle_increment_non_incrementing_after_incrementing() {
259 let mut stack = ContinuationStack::default();
260 // Push an incrementing continuation first (bottom of stack)
261 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
262 // Push a non-incrementing continuation on top
263 stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
264
265 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
266 // Should return: AfterExitDecorators (non-incrementing), then StartNode (first
267 // incrementing)
268 assert_eq!(result.len(), 2);
269 assert!(matches!(result[0], Continuation::AfterExitDecorators(_)));
270 assert!(matches!(result[1], Continuation::StartNode(_)));
271 }
272
273 #[test]
274 fn get_next_clock_cycle_increment_two_non_incrementing_after_incrementing() {
275 let mut stack = ContinuationStack::default();
276 // Push an incrementing continuation first (bottom of stack)
277 stack.push_continuation(Continuation::StartNode(MastNodeId::new_unchecked(0)));
278 // Push two non-incrementing continuations on top
279 stack.push_continuation(Continuation::AfterExitDecorators(MastNodeId::new_unchecked(0)));
280 stack.push_continuation(Continuation::EnterForest(Arc::new(MastForest::new())));
281
282 let result: Vec<_> = stack.iter_continuations_for_next_clock().collect();
283 // Should return: EnterForest, AfterExitDecorators, StartNode
284 assert_eq!(result.len(), 3);
285 assert!(matches!(result[0], Continuation::EnterForest(_)));
286 assert!(matches!(result[1], Continuation::AfterExitDecorators(_)));
287 assert!(matches!(result[2], Continuation::StartNode(_)));
288 }
289}