miden_processor/fast/
mod.rs

1use alloc::{sync::Arc, vec::Vec};
2use core::cmp::min;
3
4use memory::Memory;
5use miden_air::RowIndex;
6use miden_core::{
7    Decorator, DecoratorIterator, EMPTY_WORD, Felt, ONE, Operation, Program, StackOutputs,
8    WORD_SIZE, Word, ZERO,
9    mast::{
10        BasicBlockNode, CallNode, ExternalNode, JoinNode, LoopNode, MastForest, MastNode,
11        MastNodeId, OP_GROUP_SIZE, OpBatch, SplitNode,
12    },
13    stack::MIN_STACK_DEPTH,
14    utils::range,
15};
16
17use crate::{
18    AdviceInputs, AdviceProvider, AsyncHost, ContextId, ErrorContext, ExecutionError, FMP_MIN,
19    ProcessState, SYSCALL_FMP_MIN,
20    chiplets::Ace,
21    continuation_stack::{Continuation, ContinuationStack},
22    err_ctx,
23};
24
25mod memory;
26
27// Ops
28mod circuit_eval;
29mod crypto_ops;
30mod field_ops;
31mod fri_ops;
32mod horner_ops;
33mod io_ops;
34mod stack_ops;
35mod sys_ops;
36mod u32_ops;
37
38#[cfg(test)]
39mod tests;
40
41/// The size of the stack buffer.
42///
43/// Note: This value is much larger than it needs to be for the majority of programs. However, some
44/// existing programs need it (e.g. `std::math::secp256k1::group::gen_mul`), so we're forced to push
45/// it up. At this high a value, we're starting to see some performance degradation on benchmarks.
46/// For example, the blake3 benchmark went from 285 MHz to 250 MHz (~10% degradation). Perhaps a
47/// better solution would be to make this value much smaller (~1000), and then fallback to a `Vec`
48/// if the stack overflows.
49const STACK_BUFFER_SIZE: usize = 6850;
50
51/// The initial position of the top of the stack in the stack buffer.
52///
53/// We place this value close to 0 because if a program hits the limit, it's much more likely to hit
54/// the upper bound than the lower bound, since hitting the lower bound only occurs when you drop
55/// 0's that were generated automatically to keep the stack depth at 16. In practice, if this
56/// occurs, it is most likely a bug.
57const INITIAL_STACK_TOP_IDX: usize = 250;
58
59/// WORD_SIZE, but as a `Felt`.
60const WORD_SIZE_FELT: Felt = Felt::new(4);
61
62/// The size of a double-word.
63const DOUBLE_WORD_SIZE: Felt = Felt::new(8);
64
65/// A fast processor which doesn't generate any trace.
66///
67/// This processor is designed to be as fast as possible. Hence, it only keeps track of the current
68/// state of the processor (i.e. the stack, current clock cycle, current memory context, and free
69/// memory pointer).
70///
71/// # Stack Management
72/// A few key points about how the stack was designed for maximum performance:
73///
74/// - The stack has a fixed buffer size defined by `STACK_BUFFER_SIZE`.
75///     - This was observed to increase performance by at least 2x compared to using a `Vec` with
76///       `push()` & `pop()`.
77///     - We track the stack top and bottom using indices `stack_top_idx` and `stack_bot_idx`,
78///       respectively.
79/// - Since we are using a fixed-size buffer, we need to ensure that stack buffer accesses are not
80///   out of bounds. Naively, we could check for this on every access. However, every operation
81///   alters the stack depth by a predetermined amount, allowing us to precisely determine the
82///   minimum number of operations required to reach a stack buffer boundary, whether at the top or
83///   bottom.
84///     - For example, if the stack top is 10 elements away from the top boundary, and the stack
85///       bottom is 15 elements away from the bottom boundary, then we can safely execute 10
86///       operations that modify the stack depth with no bounds check.
87/// - When switching contexts (e.g., during a call or syscall), all elements past the first 16 are
88///   stored in an `ExecutionContextInfo` struct, and the stack is truncated to 16 elements. This
89///   will be restored when returning from the call or syscall.
90///
91/// # Clock Cycle Management
92/// - The clock cycle (`clk`) is managed in the same way as in `Process`. That is, it is incremented
93///   by 1 for every row that `Process` adds to the main trace.
94///     - It is important to do so because the clock cycle is used to determine the context ID for
95///       new execution contexts when using `call` or `dyncall`.
96#[derive(Debug)]
97pub struct FastProcessor {
98    /// The stack is stored in reverse order, so that the last element is at the top of the stack.
99    pub(super) stack: [Felt; STACK_BUFFER_SIZE],
100    /// The index of the top of the stack.
101    stack_top_idx: usize,
102    /// The index of the bottom of the stack.
103    stack_bot_idx: usize,
104
105    /// The current clock cycle.
106    pub(super) clk: RowIndex,
107
108    /// The current context ID.
109    pub(super) ctx: ContextId,
110
111    /// The free memory pointer.
112    pub(super) fmp: Felt,
113
114    /// Whether we are currently in a syscall.
115    in_syscall: bool,
116
117    /// The hash of the function that called into the current context, or `[ZERO, ZERO, ZERO,
118    /// ZERO]` if we are in the first context (i.e. when `call_stack` is empty).
119    pub(super) caller_hash: Word,
120
121    /// The advice provider to be used during execution.
122    pub(super) advice: AdviceProvider,
123
124    /// A map from (context_id, word_address) to the word stored starting at that memory location.
125    pub(super) memory: Memory,
126
127    /// A map storing metadata per call to the ACE chiplet.
128    pub(super) ace: Ace,
129
130    /// The call stack is used when starting a new execution context (from a `call`, `syscall` or
131    /// `dyncall`) to keep track of the information needed to return to the previous context upon
132    /// return. It is a stack since calls can be nested.
133    call_stack: Vec<ExecutionContextInfo>,
134
135    /// Whether to enable debug statements and tracing.
136    in_debug_mode: bool,
137}
138
139impl FastProcessor {
140    // CONSTRUCTORS
141    // ----------------------------------------------------------------------------------------------
142
143    /// Creates a new `FastProcessor` instance with the given stack inputs.
144    ///
145    /// # Panics
146    /// - Panics if the length of `stack_inputs` is greater than `MIN_STACK_DEPTH`.
147    pub fn new(stack_inputs: &[Felt]) -> Self {
148        Self::initialize(stack_inputs, AdviceInputs::default(), false)
149    }
150
151    /// Creates a new `FastProcessor` instance with the given stack and advice inputs.
152    ///
153    /// # Panics
154    /// - Panics if the length of `stack_inputs` is greater than `MIN_STACK_DEPTH`.
155    pub fn new_with_advice_inputs(stack_inputs: &[Felt], advice_inputs: AdviceInputs) -> Self {
156        Self::initialize(stack_inputs, advice_inputs, false)
157    }
158
159    /// Creates a new `FastProcessor` instance, set to debug mode, with the given stack
160    /// and advice inputs.
161    ///
162    /// # Panics
163    /// - Panics if the length of `stack_inputs` is greater than `MIN_STACK_DEPTH`.
164    pub fn new_debug(stack_inputs: &[Felt], advice_inputs: AdviceInputs) -> Self {
165        Self::initialize(stack_inputs, advice_inputs, true)
166    }
167
168    /// Generic constructor unifying the above public ones.
169    ///
170    /// The stack inputs are expected to be stored in reverse order. For example, if `stack_inputs =
171    /// [1,2,3]`, then the stack will be initialized as `[3,2,1,0,0,...]`, with `3` being on
172    /// top.
173    fn initialize(stack_inputs: &[Felt], advice_inputs: AdviceInputs, in_debug_mode: bool) -> Self {
174        assert!(stack_inputs.len() <= MIN_STACK_DEPTH);
175
176        let stack_top_idx = INITIAL_STACK_TOP_IDX;
177        let stack = {
178            let mut stack = [ZERO; STACK_BUFFER_SIZE];
179            let bottom_idx = stack_top_idx - stack_inputs.len();
180
181            stack[bottom_idx..stack_top_idx].copy_from_slice(stack_inputs);
182            stack
183        };
184
185        Self {
186            advice: advice_inputs.into(),
187            stack,
188            stack_top_idx,
189            stack_bot_idx: stack_top_idx - MIN_STACK_DEPTH,
190            clk: 0_u32.into(),
191            ctx: 0_u32.into(),
192            fmp: Felt::new(FMP_MIN),
193            in_syscall: false,
194            caller_hash: EMPTY_WORD,
195            memory: Memory::new(),
196            call_stack: Vec::new(),
197            ace: Ace::default(),
198            in_debug_mode,
199        }
200    }
201
202    // ACCESSORS
203    // -------------------------------------------------------------------------------------------
204
205    /// Returns the stack, such that the top of the stack is at the last index of the returned
206    /// slice.
207    pub fn stack(&self) -> &[Felt] {
208        &self.stack[self.stack_bot_idx..self.stack_top_idx]
209    }
210
211    /// Returns the element on the stack at index `idx`.
212    #[inline(always)]
213    pub fn stack_get(&self, idx: usize) -> Felt {
214        self.stack[self.stack_top_idx - idx - 1]
215    }
216
217    /// Mutable variant of `stack_get()`.
218    #[inline(always)]
219    pub fn stack_get_mut(&mut self, idx: usize) -> &mut Felt {
220        &mut self.stack[self.stack_top_idx - idx - 1]
221    }
222
223    /// Returns the word on the stack starting at index `start_idx` in "stack order".
224    ///
225    /// That is, for `start_idx=0` the top element of the stack will be at the last position in the
226    /// word.
227    ///
228    /// For example, if the stack looks like this:
229    ///
230    /// top                                                       bottom
231    /// v                                                           v
232    /// a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p
233    ///
234    /// Then
235    /// - `stack_get_word(0)` returns `[d, c, b, a]`,
236    /// - `stack_get_word(1)` returns `[e, d, c ,b]`,
237    /// - etc.
238    #[inline(always)]
239    pub fn stack_get_word(&self, start_idx: usize) -> Word {
240        debug_assert!(start_idx < MIN_STACK_DEPTH);
241
242        let word_start_idx = self.stack_top_idx - start_idx - 4;
243        let result: [Felt; WORD_SIZE] =
244            self.stack[range(word_start_idx, WORD_SIZE)].try_into().unwrap();
245        result.into()
246    }
247
248    /// Returns the number of elements on the stack in the current context.
249    #[inline(always)]
250    pub fn stack_depth(&self) -> u32 {
251        (self.stack_top_idx - self.stack_bot_idx) as u32
252    }
253
254    // MUTATORS
255    // -------------------------------------------------------------------------------------------
256
257    /// Writes an element to the stack at the given index.
258    #[inline(always)]
259    pub fn stack_write(&mut self, idx: usize, element: Felt) {
260        self.stack[self.stack_top_idx - idx - 1] = element
261    }
262
263    /// Writes a word to the stack starting at the given index.
264    ///
265    /// The index is the index of the first element of the word, and the word is written in reverse
266    /// order.
267    #[inline(always)]
268    pub fn stack_write_word(&mut self, start_idx: usize, word: &Word) {
269        debug_assert!(start_idx < MIN_STACK_DEPTH);
270
271        let word_start_idx = self.stack_top_idx - start_idx - 4;
272        let source: [Felt; WORD_SIZE] = (*word).into();
273        self.stack[range(word_start_idx, WORD_SIZE)].copy_from_slice(&source)
274    }
275
276    /// Swaps the elements at the given indices on the stack.
277    #[inline(always)]
278    pub fn stack_swap(&mut self, idx1: usize, idx2: usize) {
279        let a = self.stack_get(idx1);
280        let b = self.stack_get(idx2);
281        self.stack_write(idx1, b);
282        self.stack_write(idx2, a);
283    }
284
285    // EXECUTE
286    // -------------------------------------------------------------------------------------------
287
288    /// Executes the given program and returns the stack outputs as well as the advice provider.
289    pub async fn execute(
290        mut self,
291        program: &Program,
292        host: &mut impl AsyncHost,
293    ) -> Result<(StackOutputs, AdviceProvider), ExecutionError> {
294        let stack_outputs = self.execute_impl(program, host).await?;
295
296        Ok((stack_outputs, self.advice))
297    }
298
299    async fn execute_impl(
300        &mut self,
301        program: &Program,
302        host: &mut impl AsyncHost,
303    ) -> Result<StackOutputs, ExecutionError> {
304        let mut continuation_stack = ContinuationStack::new(program);
305        let mut current_forest = program.mast_forest().clone();
306
307        while let Some(processing_step) = continuation_stack.pop_continuation() {
308            match processing_step {
309                Continuation::StartNode(node_id) => {
310                    let node = current_forest.get_node_by_id(node_id).unwrap();
311                    match node {
312                        MastNode::Block(basic_block_node) => {
313                            self.execute_basic_block_node(
314                                basic_block_node,
315                                node_id,
316                                &current_forest,
317                                host,
318                            )
319                            .await?
320                        },
321                        MastNode::Join(join_node) => self.start_join_node(
322                            join_node,
323                            node_id,
324                            &current_forest,
325                            &mut continuation_stack,
326                            host,
327                        )?,
328                        MastNode::Split(split_node) => self.start_split_node(
329                            split_node,
330                            node_id,
331                            &current_forest,
332                            &mut continuation_stack,
333                            host,
334                        )?,
335                        MastNode::Loop(loop_node) => self.start_loop_node(
336                            loop_node,
337                            node_id,
338                            &current_forest,
339                            &mut continuation_stack,
340                            host,
341                        )?,
342                        MastNode::Call(call_node) => self.start_call_node(
343                            call_node,
344                            node_id,
345                            program,
346                            &current_forest,
347                            &mut continuation_stack,
348                            host,
349                        )?,
350                        MastNode::Dyn(_dyn_node) => {
351                            self.start_dyn_node(
352                                node_id,
353                                &mut current_forest,
354                                &mut continuation_stack,
355                                host,
356                            )
357                            .await?
358                        },
359                        MastNode::External(_external_node) => {
360                            self.execute_external_node(
361                                node_id,
362                                &mut current_forest,
363                                &mut continuation_stack,
364                                host,
365                            )
366                            .await?
367                        },
368                    }
369                },
370                Continuation::FinishJoin(node_id) => {
371                    self.finish_join_node(node_id, &current_forest, host)?
372                },
373                Continuation::FinishSplit(node_id) => {
374                    self.finish_split_node(node_id, &current_forest, host)?
375                },
376                Continuation::FinishLoop(node_id) => {
377                    self.finish_loop_node(node_id, &current_forest, &mut continuation_stack, host)?
378                },
379                Continuation::FinishCall(node_id) => {
380                    self.finish_call_node(node_id, &current_forest, host)?
381                },
382                Continuation::FinishDyn(node_id) => {
383                    self.finish_dyn_node(node_id, &current_forest, host)?
384                },
385                Continuation::EnterForest(previous_forest) => {
386                    // Restore the previous forest
387                    current_forest = previous_forest;
388                },
389            }
390        }
391
392        StackOutputs::new(
393            self.stack[self.stack_bot_idx..self.stack_top_idx]
394                .iter()
395                .rev()
396                .copied()
397                .collect(),
398        )
399        .map_err(|_| {
400            ExecutionError::OutputStackOverflow(
401                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
402            )
403        })
404    }
405
406    // NODE EXECUTORS
407    // --------------------------------------------------------------------------------------------
408
409    /// Executes the start phase of a Join node.
410    #[inline(always)]
411    fn start_join_node(
412        &mut self,
413        join_node: &JoinNode,
414        node_id: MastNodeId,
415        current_forest: &MastForest,
416        continuation_stack: &mut ContinuationStack,
417        host: &mut impl AsyncHost,
418    ) -> Result<(), ExecutionError> {
419        // Execute decorators that should be executed before entering the node
420        self.execute_before_enter_decorators(node_id, current_forest, host)?;
421
422        // Corresponds to the row inserted for the JOIN operation added
423        // to the trace.
424        self.clk += 1_u32;
425
426        continuation_stack.push_finish_join(node_id);
427        continuation_stack.push_start_node(join_node.second());
428        continuation_stack.push_start_node(join_node.first());
429        Ok(())
430    }
431
432    /// Executes the finish phase of a Join node.
433    #[inline(always)]
434    fn finish_join_node(
435        &mut self,
436        node_id: MastNodeId,
437        current_forest: &MastForest,
438        host: &mut impl AsyncHost,
439    ) -> Result<(), ExecutionError> {
440        // Corresponds to the row inserted for the END operation added
441        // to the trace.
442        self.clk += 1_u32;
443
444        self.execute_after_exit_decorators(node_id, current_forest, host)
445    }
446
447    /// Executes the start phase of a Split node.
448    #[inline(always)]
449    fn start_split_node(
450        &mut self,
451        split_node: &SplitNode,
452        node_id: MastNodeId,
453        current_forest: &MastForest,
454        continuation_stack: &mut ContinuationStack,
455        host: &mut impl AsyncHost,
456    ) -> Result<(), ExecutionError> {
457        // Execute decorators that should be executed before entering the node
458        self.execute_before_enter_decorators(node_id, current_forest, host)?;
459
460        // Corresponds to the row inserted for the SPLIT operation added
461        // to the trace.
462        self.clk += 1_u32;
463
464        let condition = self.stack_get(0);
465
466        // drop the condition from the stack
467        self.decrement_stack_size();
468
469        // execute the appropriate branch
470        continuation_stack.push_finish_split(node_id);
471        if condition == ONE {
472            continuation_stack.push_start_node(split_node.on_true());
473        } else if condition == ZERO {
474            continuation_stack.push_start_node(split_node.on_false());
475        } else {
476            let err_ctx = err_ctx!(current_forest, split_node, host);
477            return Err(ExecutionError::not_binary_value_if(condition, &err_ctx));
478        };
479        Ok(())
480    }
481
482    /// Executes the finish phase of a Split node.
483    #[inline(always)]
484    fn finish_split_node(
485        &mut self,
486        node_id: MastNodeId,
487        current_forest: &MastForest,
488        host: &mut impl AsyncHost,
489    ) -> Result<(), ExecutionError> {
490        // Corresponds to the row inserted for the END operation added
491        // to the trace.
492        self.clk += 1_u32;
493
494        self.execute_after_exit_decorators(node_id, current_forest, host)
495    }
496
497    /// Executes the start phase of a Loop node.
498    #[inline(always)]
499    fn start_loop_node(
500        &mut self,
501        loop_node: &LoopNode,
502        current_node_id: MastNodeId,
503        current_forest: &MastForest,
504        continuation_stack: &mut ContinuationStack,
505        host: &mut impl AsyncHost,
506    ) -> Result<(), ExecutionError> {
507        // Execute decorators that should be executed before entering the node
508        self.execute_before_enter_decorators(current_node_id, current_forest, host)?;
509
510        // Corresponds to the row inserted for the LOOP operation added
511        // to the trace.
512        self.clk += 1_u32;
513
514        let condition = self.stack_get(0);
515
516        // drop the condition from the stack
517        self.decrement_stack_size();
518
519        // execute the loop body as long as the condition is true
520        if condition == ONE {
521            // Push the loop to check condition again after body
522            // executes
523            continuation_stack.push_finish_loop(current_node_id);
524            continuation_stack.push_start_node(loop_node.body());
525        } else if condition == ZERO {
526            // Exit the loop - add END row immediately since no body to
527            // execute
528            self.clk += 1_u32;
529        } else {
530            let err_ctx = err_ctx!(current_forest, loop_node, host);
531            return Err(ExecutionError::not_binary_value_loop(condition, &err_ctx));
532        }
533        Ok(())
534    }
535
536    /// Executes the finish phase of a Loop node.
537    #[inline(always)]
538    fn finish_loop_node(
539        &mut self,
540        current_node_id: MastNodeId,
541        current_forest: &MastForest,
542        continuation_stack: &mut ContinuationStack,
543        host: &mut impl AsyncHost,
544    ) -> Result<(), ExecutionError> {
545        // This happens after loop body execution
546        // Check condition again to see if we should continue looping
547        let condition = self.stack_get(0);
548        self.decrement_stack_size();
549
550        let loop_node = current_forest[current_node_id].unwrap_loop();
551        if condition == ONE {
552            // Add REPEAT row and continue looping
553            self.clk += 1_u32;
554            continuation_stack.push_finish_loop(current_node_id);
555            continuation_stack.push_start_node(loop_node.body());
556        } else if condition == ZERO {
557            // Exit the loop - add END row
558            self.clk += 1_u32;
559
560            self.execute_after_exit_decorators(current_node_id, current_forest, host)?;
561        } else {
562            let err_ctx = err_ctx!(current_forest, loop_node, host);
563            return Err(ExecutionError::not_binary_value_loop(condition, &err_ctx));
564        }
565        Ok(())
566    }
567
568    /// Executes the start phase of a Call node.
569    #[inline(always)]
570    fn start_call_node(
571        &mut self,
572        call_node: &CallNode,
573        current_node_id: MastNodeId,
574        program: &Program,
575        current_forest: &MastForest,
576        continuation_stack: &mut ContinuationStack,
577        host: &mut impl AsyncHost,
578    ) -> Result<(), ExecutionError> {
579        // Execute decorators that should be executed before entering the node
580        self.execute_before_enter_decorators(current_node_id, current_forest, host)?;
581
582        let err_ctx = err_ctx!(current_forest, call_node, host);
583
584        // Corresponds to the row inserted for the CALL or SYSCALL
585        // operation added to the trace.
586        self.clk += 1_u32;
587
588        // call or syscall are not allowed inside a syscall
589        if self.in_syscall {
590            let instruction = if call_node.is_syscall() { "syscall" } else { "call" };
591            return Err(ExecutionError::CallInSyscall(instruction));
592        }
593
594        let callee_hash = current_forest
595            .get_node_by_id(call_node.callee())
596            .ok_or(ExecutionError::MastNodeNotFoundInForest { node_id: call_node.callee() })?
597            .digest();
598
599        self.save_context_and_truncate_stack();
600
601        if call_node.is_syscall() {
602            // check if the callee is in the kernel
603            if !program.kernel().contains_proc(callee_hash) {
604                return Err(ExecutionError::syscall_target_not_in_kernel(callee_hash, &err_ctx));
605            }
606
607            // set the system registers to the syscall context
608            self.ctx = ContextId::root();
609            self.fmp = SYSCALL_FMP_MIN.into();
610            self.in_syscall = true;
611        } else {
612            // set the system registers to the callee context
613            self.ctx = self.clk.into();
614            self.fmp = Felt::new(FMP_MIN);
615            self.caller_hash = callee_hash;
616        }
617
618        // push the callee onto the continuation stack
619        continuation_stack.push_finish_call(current_node_id);
620        continuation_stack.push_start_node(call_node.callee());
621        Ok(())
622    }
623
624    /// Executes the finish phase of a Call node.
625    #[inline(always)]
626    fn finish_call_node(
627        &mut self,
628        node_id: MastNodeId,
629        current_forest: &MastForest,
630        host: &mut impl AsyncHost,
631    ) -> Result<(), ExecutionError> {
632        let call_node = current_forest[node_id].unwrap_call();
633        let err_ctx = err_ctx!(current_forest, call_node, host);
634        // when returning from a function call or a syscall, restore the
635        // context of the
636        // system registers and the operand stack to what it was prior
637        // to the call.
638        self.restore_context(&err_ctx)?;
639
640        // Corresponds to the row inserted for the END operation added
641        // to the trace.
642        self.clk += 1_u32;
643
644        self.execute_after_exit_decorators(node_id, current_forest, host)
645    }
646
647    /// Executes the start phase of a Dyn node.
648    #[inline(always)]
649    async fn start_dyn_node(
650        &mut self,
651        current_node_id: MastNodeId,
652        current_forest: &mut Arc<MastForest>,
653        continuation_stack: &mut ContinuationStack,
654        host: &mut impl AsyncHost,
655    ) -> Result<(), ExecutionError> {
656        // Execute decorators that should be executed before entering the node
657        self.execute_before_enter_decorators(current_node_id, current_forest, host)?;
658
659        // Corresponds to the row inserted for the DYN or DYNCALL operation
660        // added to the trace.
661        self.clk += 1_u32;
662
663        let dyn_node = current_forest[current_node_id].unwrap_dyn();
664
665        // dyn calls are not allowed inside a syscall
666        if dyn_node.is_dyncall() && self.in_syscall {
667            return Err(ExecutionError::CallInSyscall("dyncall"));
668        }
669
670        let err_ctx = err_ctx!(&current_forest, dyn_node, host);
671
672        // Retrieve callee hash from memory, using stack top as the memory
673        // address.
674        let callee_hash = {
675            let mem_addr = self.stack_get(0);
676            self.memory
677                .read_word(self.ctx, mem_addr, self.clk, &err_ctx)
678                .map_err(ExecutionError::MemoryError)?
679        };
680
681        // Drop the memory address from the stack. This needs to be done before saving the context.
682        self.decrement_stack_size();
683
684        // For dyncall, save the context and reset it.
685        if dyn_node.is_dyncall() {
686            self.save_context_and_truncate_stack();
687            self.ctx = self.clk.into();
688            self.fmp = Felt::new(FMP_MIN);
689            self.caller_hash = callee_hash;
690        };
691
692        // Update continuation stack
693        // -----------------------------
694        continuation_stack.push_finish_dyn(current_node_id);
695
696        // if the callee is not in the program's MAST forest, try to find a MAST forest for it in
697        // the host (corresponding to an external library loaded in the host); if none are found,
698        // return an error.
699        match current_forest.find_procedure_root(callee_hash) {
700            Some(callee_id) => {
701                continuation_stack.push_start_node(callee_id);
702            },
703            None => {
704                let (root_id, new_forest) = self
705                    .load_mast_forest(
706                        callee_hash,
707                        host,
708                        ExecutionError::dynamic_node_not_found,
709                        &err_ctx,
710                    )
711                    .await?;
712
713                // Push current forest to the continuation stack so that we can return to it
714                continuation_stack.push_enter_forest(current_forest.clone());
715
716                // Push the root node of the external MAST forest onto the continuation stack.
717                continuation_stack.push_start_node(root_id);
718
719                // Set the new MAST forest as current
720                *current_forest = new_forest;
721            },
722        }
723        Ok(())
724    }
725
726    /// Executes the finish phase of a Dyn node.
727    #[inline(always)]
728    fn finish_dyn_node(
729        &mut self,
730        node_id: MastNodeId,
731        current_forest: &MastForest,
732        host: &mut impl AsyncHost,
733    ) -> Result<(), ExecutionError> {
734        let dyn_node = current_forest[node_id].unwrap_dyn();
735        let err_ctx = err_ctx!(current_forest, dyn_node, host);
736        // For dyncall, restore the context.
737        if dyn_node.is_dyncall() {
738            self.restore_context(&err_ctx)?;
739        }
740
741        // Corresponds to the row inserted for the END operation added to
742        // the trace.
743        self.clk += 1_u32;
744
745        self.execute_after_exit_decorators(node_id, current_forest, host)
746    }
747
748    /// Executes an External node.
749    #[inline(always)]
750    async fn execute_external_node(
751        &mut self,
752        node_id: MastNodeId,
753        current_forest: &mut Arc<MastForest>,
754        continuation_stack: &mut ContinuationStack,
755        host: &mut impl AsyncHost,
756    ) -> Result<(), ExecutionError> {
757        // Execute decorators that should be executed before entering the node
758        self.execute_before_enter_decorators(node_id, current_forest, host)?;
759
760        let external_node = current_forest[node_id].unwrap_external();
761        let (root_id, new_mast_forest) = self.resolve_external_node(external_node, host).await?;
762
763        // Push current forest to the continuation stack so that we can return to it
764        continuation_stack.push_enter_forest(current_forest.clone());
765
766        // Push the root node of the external MAST forest onto the continuation stack.
767        continuation_stack.push_start_node(root_id);
768
769        self.execute_after_exit_decorators(node_id, current_forest, host)?;
770
771        // Update the current forest to the new MAST forest.
772        *current_forest = new_mast_forest;
773
774        Ok(())
775    }
776
777    // Note: when executing individual ops, we do not increment the clock by 1 at every iteration
778    // for performance reasons (~25% performance drop). Hence, `self.clk` cannot be used directly to
779    // determine the number of operations executed in a program.
780    #[inline(always)]
781    async fn execute_basic_block_node(
782        &mut self,
783        basic_block_node: &BasicBlockNode,
784        node_id: MastNodeId,
785        program: &MastForest,
786        host: &mut impl AsyncHost,
787    ) -> Result<(), ExecutionError> {
788        // Execute decorators that should be executed before entering the node
789        self.execute_before_enter_decorators(node_id, program, host)?;
790
791        // Corresponds to the row inserted for the SPAN operation added to the trace.
792        self.clk += 1_u32;
793
794        let mut batch_offset_in_block = 0;
795        let mut op_batches = basic_block_node.op_batches().iter();
796        let mut decorator_ids = basic_block_node.decorator_iter();
797
798        // execute first op batch
799        if let Some(first_op_batch) = op_batches.next() {
800            self.execute_op_batch(
801                basic_block_node,
802                first_op_batch,
803                &mut decorator_ids,
804                batch_offset_in_block,
805                program,
806                host,
807            )
808            .await?;
809            batch_offset_in_block += first_op_batch.ops().len();
810        }
811
812        // execute the rest of the op batches
813        for op_batch in op_batches {
814            // increment clock to account for `RESPAN`
815            self.clk += 1_u32;
816
817            self.execute_op_batch(
818                basic_block_node,
819                op_batch,
820                &mut decorator_ids,
821                batch_offset_in_block,
822                program,
823                host,
824            )
825            .await?;
826            batch_offset_in_block += op_batch.ops().len();
827        }
828
829        // Corresponds to the row inserted for the END operation added to the trace.
830        self.clk += 1_u32;
831
832        // execute any decorators which have not been executed during span ops execution; this can
833        // happen for decorators appearing after all operations in a block. these decorators are
834        // executed after SPAN block is closed to make sure the VM clock cycle advances beyond the
835        // last clock cycle of the SPAN block ops.
836        for &decorator_id in decorator_ids {
837            let decorator = program
838                .get_decorator_by_id(decorator_id)
839                .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
840            self.execute_decorator(decorator, host)?;
841        }
842
843        self.execute_after_exit_decorators(node_id, program, host)
844    }
845
846    #[inline(always)]
847    async fn execute_op_batch(
848        &mut self,
849        basic_block: &BasicBlockNode,
850        batch: &OpBatch,
851        decorators: &mut DecoratorIterator<'_>,
852        batch_offset_in_block: usize,
853        program: &MastForest,
854        host: &mut impl AsyncHost,
855    ) -> Result<(), ExecutionError> {
856        let op_counts = batch.op_counts();
857        let mut op_idx_in_group = 0;
858        let mut group_idx = 0;
859        let mut next_group_idx = 1;
860
861        // round up the number of groups to be processed to the next power of two; we do this
862        // because the processor requires the number of groups to be either 1, 2, 4, or 8; if
863        // the actual number of groups is smaller, we'll pad the batch with NOOPs at the end
864        let num_batch_groups = batch.num_groups().next_power_of_two();
865
866        // execute operations in the batch one by one
867        for (op_idx_in_batch, op) in batch.ops().iter().enumerate() {
868            while let Some(&decorator_id) =
869                decorators.next_filtered(batch_offset_in_block + op_idx_in_batch)
870            {
871                let decorator = program
872                    .get_decorator_by_id(decorator_id)
873                    .ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
874                self.execute_decorator(decorator, host)?;
875            }
876
877            // decode and execute the operation
878            let op_idx_in_block = batch_offset_in_block + op_idx_in_batch;
879            let err_ctx = err_ctx!(program, basic_block, host, op_idx_in_block);
880
881            // Execute the operation.
882            //
883            // Note: we handle the `Emit` operation separately, because it is an async operation,
884            // whereas all the other operations are synchronous (resulting in a significant
885            // performance improvement).
886            match op {
887                Operation::Emit(event_id) => self.op_emit(*event_id, host, &err_ctx).await?,
888                _ => {
889                    // if the operation is not an Emit, we execute it normally
890                    self.execute_op(op, op_idx_in_block, program, host, &err_ctx)?;
891                },
892            }
893
894            // if the operation carries an immediate value, the value is stored at the next group
895            // pointer; so, we advance the pointer to the following group
896            let has_imm = op.imm_value().is_some();
897            if has_imm {
898                next_group_idx += 1;
899            }
900
901            // determine if we've executed all non-decorator operations in a group
902            if op_idx_in_group == op_counts[group_idx] - 1 {
903                // if we are at the end of the group, first check if the operation carries an
904                // immediate value
905                if has_imm {
906                    // an operation with an immediate value cannot be the last operation in a group
907                    // so, we need execute a NOOP after it. In this processor, we increment the
908                    // clock to account for the NOOP.
909                    debug_assert!(op_idx_in_group < OP_GROUP_SIZE - 1, "invalid op index");
910                    self.clk += 1_u32;
911                }
912
913                // then, move to the next group and reset operation index
914                group_idx = next_group_idx;
915                next_group_idx += 1;
916                op_idx_in_group = 0;
917            } else {
918                op_idx_in_group += 1;
919            }
920
921            self.clk += 1_u32;
922        }
923
924        // make sure we execute the required number of operation groups; this would happen when the
925        // actual number of operation groups was not a power of two. In this processor, this
926        // corresponds to incrementing the clock by the number of empty op groups (i.e. 1 NOOP
927        // executed per missing op group).
928
929        self.clk += (num_batch_groups - group_idx) as u32;
930
931        Ok(())
932    }
933
934    /// Executes the decorators that should be executed before entering a node.
935    fn execute_before_enter_decorators(
936        &mut self,
937        node_id: MastNodeId,
938        current_forest: &MastForest,
939        host: &mut impl AsyncHost,
940    ) -> Result<(), ExecutionError> {
941        let node = current_forest
942            .get_node_by_id(node_id)
943            .expect("internal error: node id {node_id} not found in current forest");
944
945        for &decorator_id in node.before_enter() {
946            self.execute_decorator(&current_forest[decorator_id], host)?;
947        }
948
949        Ok(())
950    }
951
952    /// Executes the decorators that should be executed after exiting a node.
953    fn execute_after_exit_decorators(
954        &mut self,
955        node_id: MastNodeId,
956        current_forest: &MastForest,
957        host: &mut impl AsyncHost,
958    ) -> Result<(), ExecutionError> {
959        let node = current_forest
960            .get_node_by_id(node_id)
961            .expect("internal error: node id {node_id} not found in current forest");
962
963        for &decorator_id in node.after_exit() {
964            self.execute_decorator(&current_forest[decorator_id], host)?;
965        }
966
967        Ok(())
968    }
969
970    /// Executes the specified decorator
971    fn execute_decorator(
972        &mut self,
973        decorator: &Decorator,
974        host: &mut impl AsyncHost,
975    ) -> Result<(), ExecutionError> {
976        match decorator {
977            Decorator::Debug(options) => {
978                if self.in_debug_mode {
979                    let process = &mut self.state();
980                    host.on_debug(process, options)?;
981                }
982            },
983            Decorator::AsmOp(_assembly_op) => {
984                // do nothing
985            },
986            Decorator::Trace(id) => {
987                let process = &mut self.state();
988                host.on_trace(process, *id)?;
989            },
990        };
991        Ok(())
992    }
993
994    /// Executes the given operation.
995    ///
996    /// # Panics
997    /// - if the operation is a control flow operation, as these are never executed,
998    /// - if the operation is an `Emit` operation, as this requires async execution.
999    #[inline(always)]
1000    fn execute_op(
1001        &mut self,
1002        operation: &Operation,
1003        op_idx: usize,
1004        program: &MastForest,
1005        host: &mut impl AsyncHost,
1006        err_ctx: &impl ErrorContext,
1007    ) -> Result<(), ExecutionError> {
1008        match operation {
1009            // ----- system operations ------------------------------------------------------------
1010            Operation::Noop => {
1011                // do nothing
1012            },
1013            Operation::Assert(err_code) => self.op_assert(*err_code, host, program, err_ctx)?,
1014            Operation::FmpAdd => self.op_fmpadd(),
1015            Operation::FmpUpdate => self.op_fmpupdate()?,
1016            Operation::SDepth => self.op_sdepth()?,
1017            Operation::Caller => self.op_caller()?,
1018            Operation::Clk => self.op_clk()?,
1019            Operation::Emit(_event_id) => {
1020                panic!("emit instruction requires async, so is not supported by execute_op()")
1021            },
1022
1023            // ----- flow control operations ------------------------------------------------------
1024            // control flow operations are never executed directly
1025            Operation::Join => unreachable!("control flow operation"),
1026            Operation::Split => unreachable!("control flow operation"),
1027            Operation::Loop => unreachable!("control flow operation"),
1028            Operation::Call => unreachable!("control flow operation"),
1029            Operation::SysCall => unreachable!("control flow operation"),
1030            Operation::Dyn => unreachable!("control flow operation"),
1031            Operation::Dyncall => unreachable!("control flow operation"),
1032            Operation::Span => unreachable!("control flow operation"),
1033            Operation::Repeat => unreachable!("control flow operation"),
1034            Operation::Respan => unreachable!("control flow operation"),
1035            Operation::End => unreachable!("control flow operation"),
1036            Operation::Halt => unreachable!("control flow operation"),
1037
1038            // ----- field operations -------------------------------------------------------------
1039            Operation::Add => self.op_add()?,
1040            Operation::Neg => self.op_neg()?,
1041            Operation::Mul => self.op_mul()?,
1042            Operation::Inv => self.op_inv(err_ctx)?,
1043            Operation::Incr => self.op_incr()?,
1044            Operation::And => self.op_and(err_ctx)?,
1045            Operation::Or => self.op_or(err_ctx)?,
1046            Operation::Not => self.op_not(err_ctx)?,
1047            Operation::Eq => self.op_eq()?,
1048            Operation::Eqz => self.op_eqz()?,
1049            Operation::Expacc => self.op_expacc(),
1050            Operation::Ext2Mul => self.op_ext2mul(),
1051
1052            // ----- u32 operations ---------------------------------------------------------------
1053            Operation::U32split => self.op_u32split()?,
1054            Operation::U32add => self.op_u32add(err_ctx)?,
1055            Operation::U32add3 => self.op_u32add3(err_ctx)?,
1056            Operation::U32sub => self.op_u32sub(op_idx, err_ctx)?,
1057            Operation::U32mul => self.op_u32mul(err_ctx)?,
1058            Operation::U32madd => self.op_u32madd(err_ctx)?,
1059            Operation::U32div => self.op_u32div(err_ctx)?,
1060            Operation::U32and => self.op_u32and(err_ctx)?,
1061            Operation::U32xor => self.op_u32xor(err_ctx)?,
1062            Operation::U32assert2(err_code) => self.op_u32assert2(*err_code, err_ctx)?,
1063
1064            // ----- stack manipulation -----------------------------------------------------------
1065            Operation::Pad => self.op_pad()?,
1066            Operation::Drop => self.decrement_stack_size(),
1067            Operation::Dup0 => self.dup_nth(0)?,
1068            Operation::Dup1 => self.dup_nth(1)?,
1069            Operation::Dup2 => self.dup_nth(2)?,
1070            Operation::Dup3 => self.dup_nth(3)?,
1071            Operation::Dup4 => self.dup_nth(4)?,
1072            Operation::Dup5 => self.dup_nth(5)?,
1073            Operation::Dup6 => self.dup_nth(6)?,
1074            Operation::Dup7 => self.dup_nth(7)?,
1075            Operation::Dup9 => self.dup_nth(9)?,
1076            Operation::Dup11 => self.dup_nth(11)?,
1077            Operation::Dup13 => self.dup_nth(13)?,
1078            Operation::Dup15 => self.dup_nth(15)?,
1079            Operation::Swap => self.op_swap(),
1080            Operation::SwapW => self.swapw_nth(1),
1081            Operation::SwapW2 => self.swapw_nth(2),
1082            Operation::SwapW3 => self.swapw_nth(3),
1083            Operation::SwapDW => self.op_swap_double_word(),
1084            Operation::MovUp2 => self.rotate_left(3),
1085            Operation::MovUp3 => self.rotate_left(4),
1086            Operation::MovUp4 => self.rotate_left(5),
1087            Operation::MovUp5 => self.rotate_left(6),
1088            Operation::MovUp6 => self.rotate_left(7),
1089            Operation::MovUp7 => self.rotate_left(8),
1090            Operation::MovUp8 => self.rotate_left(9),
1091            Operation::MovDn2 => self.rotate_right(3),
1092            Operation::MovDn3 => self.rotate_right(4),
1093            Operation::MovDn4 => self.rotate_right(5),
1094            Operation::MovDn5 => self.rotate_right(6),
1095            Operation::MovDn6 => self.rotate_right(7),
1096            Operation::MovDn7 => self.rotate_right(8),
1097            Operation::MovDn8 => self.rotate_right(9),
1098            Operation::CSwap => self.op_cswap(err_ctx)?,
1099            Operation::CSwapW => self.op_cswapw(err_ctx)?,
1100
1101            // ----- input / output ---------------------------------------------------------------
1102            Operation::Push(element) => self.op_push(*element)?,
1103            Operation::AdvPop => self.op_advpop(err_ctx)?,
1104            Operation::AdvPopW => self.op_advpopw(err_ctx)?,
1105            Operation::MLoadW => self.op_mloadw(err_ctx)?,
1106            Operation::MStoreW => self.op_mstorew(err_ctx)?,
1107            Operation::MLoad => self.op_mload(err_ctx)?,
1108            Operation::MStore => self.op_mstore(err_ctx)?,
1109            Operation::MStream => self.op_mstream(err_ctx)?,
1110            Operation::Pipe => self.op_pipe(err_ctx)?,
1111
1112            // ----- cryptographic operations -----------------------------------------------------
1113            Operation::HPerm => self.op_hperm(),
1114            Operation::MpVerify(err_code) => self.op_mpverify(*err_code, program, err_ctx)?,
1115            Operation::MrUpdate => self.op_mrupdate(err_ctx)?,
1116            Operation::FriE2F4 => self.op_fri_ext2fold4()?,
1117            Operation::HornerBase => self.op_horner_eval_base(err_ctx)?,
1118            Operation::HornerExt => self.op_horner_eval_ext(err_ctx)?,
1119            Operation::EvalCircuit => self.op_eval_circuit(err_ctx)?,
1120        }
1121
1122        Ok(())
1123    }
1124
1125    // HELPERS
1126    // ----------------------------------------------------------------------------------------------
1127
1128    async fn load_mast_forest<E>(
1129        &mut self,
1130        node_digest: Word,
1131        host: &mut impl AsyncHost,
1132        get_mast_forest_failed: impl Fn(Word, &E) -> ExecutionError,
1133        err_ctx: &E,
1134    ) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError>
1135    where
1136        E: ErrorContext,
1137    {
1138        let mast_forest = host
1139            .get_mast_forest(&node_digest)
1140            .ok_or_else(|| get_mast_forest_failed(node_digest, err_ctx))?;
1141
1142        // We limit the parts of the program that can be called externally to procedure
1143        // roots, even though MAST doesn't have that restriction.
1144        let root_id = mast_forest
1145            .find_procedure_root(node_digest)
1146            .ok_or(ExecutionError::malfored_mast_forest_in_host(node_digest, err_ctx))?;
1147
1148        // Merge the advice map of this forest into the advice provider.
1149        // Note that the map may be merged multiple times if a different procedure from the same
1150        // forest is called.
1151        // For now, only compiled libraries contain non-empty advice maps, so for most cases,
1152        // this call will be cheap.
1153        self.advice
1154            .extend_map(mast_forest.advice_map())
1155            .map_err(|err| ExecutionError::advice_error(err, self.clk, err_ctx))?;
1156
1157        Ok((root_id, mast_forest))
1158    }
1159
1160    /// Analogous to [`Process::resolve_external_node`](crate::Process::resolve_external_node), but
1161    /// for asynchronous execution.
1162    async fn resolve_external_node(
1163        &mut self,
1164        external_node: &ExternalNode,
1165        host: &mut impl AsyncHost,
1166    ) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError> {
1167        let (root_id, mast_forest) = self
1168            .load_mast_forest(
1169                external_node.digest(),
1170                host,
1171                ExecutionError::no_mast_forest_with_procedure,
1172                &(),
1173            )
1174            .await?;
1175
1176        // if the node that we got by looking up an external reference is also an External
1177        // node, we are about to enter into an infinite loop - so, return an error
1178        if mast_forest[root_id].is_external() {
1179            return Err(ExecutionError::CircularExternalNode(external_node.digest()));
1180        }
1181
1182        Ok((root_id, mast_forest))
1183    }
1184
1185    /// Increments the stack top pointer by 1.
1186    ///
1187    /// The bottom of the stack is never affected by this operation.
1188    #[inline(always)]
1189    fn increment_stack_size(&mut self) -> Result<(), ExecutionError> {
1190        if self.stack_top_idx < STACK_BUFFER_SIZE - 1 {
1191            self.stack_top_idx += 1;
1192            Ok(())
1193        } else {
1194            Err(ExecutionError::FailedToExecuteProgram("stack overflow"))
1195        }
1196    }
1197
1198    /// Decrements the stack top pointer by 1.
1199    ///
1200    /// The bottom of the stack is only decremented in cases where the stack depth would become less
1201    /// than 16.
1202    #[inline(always)]
1203    fn decrement_stack_size(&mut self) {
1204        if self.stack_top_idx == MIN_STACK_DEPTH {
1205            // We no longer have any room in the stack buffer to decrement the stack size (which
1206            // would cause the `stack_bot_idx` to go below 0). We therefore reset the stack to its
1207            // original position.
1208            self.reset_stack_in_buffer(INITIAL_STACK_TOP_IDX);
1209        }
1210
1211        self.stack_top_idx -= 1;
1212        self.stack_bot_idx = min(self.stack_bot_idx, self.stack_top_idx - MIN_STACK_DEPTH);
1213    }
1214
1215    /// Returns the size of the stack.
1216    #[inline(always)]
1217    fn stack_size(&self) -> usize {
1218        self.stack_top_idx - self.stack_bot_idx
1219    }
1220
1221    /// Saves the current execution context and truncates the stack to 16 elements in preparation to
1222    /// start a new execution context.
1223    fn save_context_and_truncate_stack(&mut self) {
1224        let overflow_stack = if self.stack_size() > MIN_STACK_DEPTH {
1225            // save the overflow stack, and zero out the buffer.
1226            //
1227            // Note: we need to zero the overflow buffer, since the new context expects ZERO's to be
1228            // pulled in if they decrement the stack size (e.g. by executing a `drop`).
1229            let overflow_stack =
1230                self.stack[self.stack_bot_idx..self.stack_top_idx - MIN_STACK_DEPTH].to_vec();
1231            self.stack[self.stack_bot_idx..self.stack_top_idx - MIN_STACK_DEPTH].fill(ZERO);
1232
1233            overflow_stack
1234        } else {
1235            Vec::new()
1236        };
1237
1238        self.stack_bot_idx = self.stack_top_idx - MIN_STACK_DEPTH;
1239
1240        self.call_stack.push(ExecutionContextInfo {
1241            overflow_stack,
1242            ctx: self.ctx,
1243            fn_hash: self.caller_hash,
1244            fmp: self.fmp,
1245        });
1246    }
1247
1248    /// Restores the execution context to the state it was in before the last `call`, `syscall` or
1249    /// `dyncall`.
1250    ///
1251    /// This includes restoring the overflow stack and the system parameters.
1252    ///
1253    /// # Errors
1254    /// - Returns an error if the overflow stack is larger than the space available in the stack
1255    ///   buffer.
1256    fn restore_context(&mut self, err_ctx: &impl ErrorContext) -> Result<(), ExecutionError> {
1257        // when a call/dyncall/syscall node ends, stack depth must be exactly 16.
1258        if self.stack_size() > MIN_STACK_DEPTH {
1259            return Err(ExecutionError::invalid_stack_depth_on_return(self.stack_size(), err_ctx));
1260        }
1261
1262        let ctx_info = self
1263            .call_stack
1264            .pop()
1265            .expect("execution context stack should never be empty when restoring context");
1266
1267        // restore the overflow stack
1268        self.restore_overflow_stack(&ctx_info);
1269
1270        // restore system parameters
1271        self.ctx = ctx_info.ctx;
1272        self.fmp = ctx_info.fmp;
1273        self.in_syscall = false;
1274        self.caller_hash = ctx_info.fn_hash;
1275
1276        Ok(())
1277    }
1278
1279    /// Restores the overflow stack from a previous context.
1280    ///
1281    /// If necessary, moves the stack in the buffer to make room for the overflow stack to be
1282    /// restored.
1283    ///
1284    /// # Preconditions
1285    /// - The current stack depth is exactly `MIN_STACK_DEPTH` (16).
1286    #[inline(always)]
1287    fn restore_overflow_stack(&mut self, ctx_info: &ExecutionContextInfo) {
1288        let target_overflow_len = ctx_info.overflow_stack.len();
1289
1290        // Check if there's enough room to restore the overflow stack in the current stack buffer.
1291        if target_overflow_len > self.stack_bot_idx {
1292            // There's not enough room to restore the overflow stack, so we have to move the
1293            // location of the stack in the buffer. We reset it so that after restoring the overflow
1294            // stack, the stack_bot_idx is at its original position (i.e. INITIAL_STACK_TOP_IDX -
1295            // 16).
1296            let new_stack_top_idx =
1297                core::cmp::min(INITIAL_STACK_TOP_IDX + target_overflow_len, STACK_BUFFER_SIZE - 1);
1298
1299            self.reset_stack_in_buffer(new_stack_top_idx);
1300        }
1301
1302        // Restore the overflow
1303        self.stack[range(self.stack_bot_idx - target_overflow_len, target_overflow_len)]
1304            .copy_from_slice(&ctx_info.overflow_stack);
1305        self.stack_bot_idx -= target_overflow_len;
1306    }
1307
1308    /// Resets the stack in the buffer to a new position, preserving the top 16 elements of the
1309    /// stack.
1310    ///
1311    /// # Preconditions
1312    /// - The stack is expected to have exactly 16 elements.
1313    fn reset_stack_in_buffer(&mut self, new_stack_top_idx: usize) {
1314        debug_assert_eq!(self.stack_depth(), MIN_STACK_DEPTH as u32);
1315
1316        let new_stack_bot_idx = new_stack_top_idx - MIN_STACK_DEPTH;
1317
1318        // Copy stack to its new position
1319        self.stack
1320            .copy_within(self.stack_bot_idx..self.stack_top_idx, new_stack_bot_idx);
1321
1322        // Zero out stack below the new new_stack_bot_idx, since this is where overflow values
1323        // come from, and are guaranteed to be ZERO. We don't need to zero out above
1324        // `stack_top_idx`, since values there are never read before being written.
1325        self.stack[0..new_stack_bot_idx].fill(ZERO);
1326
1327        // Update indices.
1328        self.stack_bot_idx = new_stack_bot_idx;
1329        self.stack_top_idx = new_stack_top_idx;
1330    }
1331
1332    // TESTING
1333    // ----------------------------------------------------------------------------------------------
1334
1335    /// Convenience sync wrapper to [Self::execute] for testing purposes.
1336    #[cfg(any(test, feature = "testing"))]
1337    pub fn execute_sync(
1338        self,
1339        program: &Program,
1340        host: &mut impl AsyncHost,
1341    ) -> Result<StackOutputs, ExecutionError> {
1342        // Create a new Tokio runtime and block on the async execution
1343        let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
1344
1345        let (stack_outputs, _advice) = rt.block_on(self.execute(program, host))?;
1346
1347        Ok(stack_outputs)
1348    }
1349
1350    /// Similar to [Self::execute_sync], but allows mutable access to the processor.
1351    #[cfg(any(test, feature = "testing"))]
1352    pub fn execute_sync_mut(
1353        &mut self,
1354        program: &Program,
1355        host: &mut impl AsyncHost,
1356    ) -> Result<StackOutputs, ExecutionError> {
1357        // Create a new Tokio runtime and block on the async execution
1358        let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
1359
1360        rt.block_on(self.execute_impl(program, host))
1361    }
1362}
1363
1364#[derive(Debug)]
1365pub struct FastProcessState<'a> {
1366    pub(super) processor: &'a mut FastProcessor,
1367}
1368
1369impl FastProcessor {
1370    #[inline(always)]
1371    pub fn state(&mut self) -> ProcessState<'_> {
1372        ProcessState::Fast(FastProcessState { processor: self })
1373    }
1374}
1375
1376// EXECUTION CONTEXT INFO
1377// ===============================================================================================
1378
1379/// Information about the execution context.
1380///
1381/// This struct is used to keep track of the information needed to return to the previous context
1382/// upon return from a `call`, `syscall` or `dyncall`.
1383#[derive(Debug)]
1384struct ExecutionContextInfo {
1385    /// This stores all the elements on the stack at the call site, excluding the top 16 elements.
1386    /// This corresponds to the overflow table in [crate::Process].
1387    overflow_stack: Vec<Felt>,
1388    ctx: ContextId,
1389    fn_hash: Word,
1390    fmp: Felt,
1391}