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}