Skip to main content

miden_processor/fast/
mod.rs

1#[cfg(test)]
2use alloc::rc::Rc;
3use alloc::{boxed::Box, sync::Arc, vec::Vec};
4#[cfg(test)]
5use core::cell::Cell;
6use core::{cmp::min, ops::ControlFlow};
7
8use miden_air::{Felt, trace::RowIndex};
9use miden_core::{
10    EMPTY_WORD, WORD_SIZE, Word, ZERO,
11    mast::{MastForest, MastNodeExt, MastNodeId},
12    operations::Decorator,
13    precompile::PrecompileTranscript,
14    program::{Kernel, MIN_STACK_DEPTH, Program, StackInputs, StackOutputs},
15    utils::range,
16};
17use tracing::instrument;
18
19use crate::{
20    AdviceInputs, AdviceProvider, ContextId, ExecutionError, ExecutionOptions, Host,
21    ProcessorState, Stopper,
22    continuation_stack::{Continuation, ContinuationStack},
23    errors::{MapExecErr, MapExecErrNoCtx, OperationError},
24    execution::{
25        InternalBreakReason, execute_impl, finish_emit_op_execution,
26        finish_load_mast_forest_from_dyn_start, finish_load_mast_forest_from_external,
27    },
28    trace::execution_tracer::{ExecutionTracer, TraceGenerationContext},
29    tracer::{OperationHelperRegisters, Tracer},
30};
31
32mod basic_block;
33mod call_and_dyn;
34mod external;
35mod memory;
36mod operation;
37mod step;
38
39pub use basic_block::SystemEventError;
40use external::maybe_use_caller_error_context;
41pub use memory::Memory;
42pub use step::{BreakReason, ResumeContext};
43use step::{NeverStopper, StepStopper};
44
45#[cfg(test)]
46mod tests;
47
48// CONSTANTS
49// ================================================================================================
50
51/// The size of the stack buffer.
52///
53/// Note: This value is much larger than it needs to be for the majority of programs. However, some
54/// existing programs need it, so we're forced to push it up (though this should be double-checked).
55/// At this high a value, we're starting to see some performance degradation on benchmarks. For
56/// example, the blake3 benchmark went from 285 MHz to 250 MHz (~10% degradation). Perhaps a better
57/// solution would be to make this value much smaller (~1000), and then fallback to a `Vec` if the
58/// stack overflows.
59const STACK_BUFFER_SIZE: usize = 6850;
60
61/// The initial position of the top of the stack in the stack buffer.
62///
63/// We place this value close to 0 because if a program hits the limit, it's much more likely to hit
64/// the upper bound than the lower bound, since hitting the lower bound only occurs when you drop
65/// 0's that were generated automatically to keep the stack depth at 16. In practice, if this
66/// occurs, it is most likely a bug.
67const INITIAL_STACK_TOP_IDX: usize = 250;
68
69// FAST PROCESSOR
70// ================================================================================================
71
72/// A fast processor which doesn't generate any trace.
73///
74/// This processor is designed to be as fast as possible. Hence, it only keeps track of the current
75/// state of the processor (i.e. the stack, current clock cycle, current memory context, and free
76/// memory pointer).
77///
78/// # Stack Management
79/// A few key points about how the stack was designed for maximum performance:
80///
81/// - The stack has a fixed buffer size defined by `STACK_BUFFER_SIZE`.
82///     - This was observed to increase performance by at least 2x compared to using a `Vec` with
83///       `push()` & `pop()`.
84///     - We track the stack top and bottom using indices `stack_top_idx` and `stack_bot_idx`,
85///       respectively.
86/// - Since we are using a fixed-size buffer, we need to ensure that stack buffer accesses are not
87///   out of bounds. Naively, we could check for this on every access. However, every operation
88///   alters the stack depth by a predetermined amount, allowing us to precisely determine the
89///   minimum number of operations required to reach a stack buffer boundary, whether at the top or
90///   bottom.
91///     - For example, if the stack top is 10 elements away from the top boundary, and the stack
92///       bottom is 15 elements away from the bottom boundary, then we can safely execute 10
93///       operations that modify the stack depth with no bounds check.
94/// - When switching contexts (e.g., during a call or syscall), all elements past the first 16 are
95///   stored in an `ExecutionContextInfo` struct, and the stack is truncated to 16 elements. This
96///   will be restored when returning from the call or syscall.
97///
98/// # Clock Cycle Management
99/// - The clock cycle (`clk`) is managed in the same way as in `Process`. That is, it is incremented
100///   by 1 for every row that `Process` adds to the main trace.
101///     - It is important to do so because the clock cycle is used to determine the context ID for
102///       new execution contexts when using `call` or `dyncall`.
103#[derive(Debug)]
104pub struct FastProcessor {
105    /// The stack is stored in reverse order, so that the last element is at the top of the stack.
106    stack: Box<[Felt; STACK_BUFFER_SIZE]>,
107    /// The index of the top of the stack.
108    stack_top_idx: usize,
109    /// The index of the bottom of the stack.
110    stack_bot_idx: usize,
111
112    /// The current clock cycle.
113    clk: RowIndex,
114
115    /// The current context ID.
116    ctx: ContextId,
117
118    /// The hash of the function that called into the current context, or `[ZERO, ZERO, ZERO,
119    /// ZERO]` if we are in the first context (i.e. when `call_stack` is empty).
120    caller_hash: Word,
121
122    /// The advice provider to be used during execution.
123    advice: AdviceProvider,
124
125    /// A map from (context_id, word_address) to the word stored starting at that memory location.
126    memory: Memory,
127
128    /// The call stack is used when starting a new execution context (from a `call`, `syscall` or
129    /// `dyncall`) to keep track of the information needed to return to the previous context upon
130    /// return. It is a stack since calls can be nested.
131    call_stack: Vec<ExecutionContextInfo>,
132
133    /// Options for execution, including but not limited to whether debug or tracing is enabled,
134    /// the size of core trace fragments during execution, etc.
135    options: ExecutionOptions,
136
137    /// Transcript used to record commitments via `log_precompile` instruction (implemented via
138    /// Poseidon2 sponge).
139    pc_transcript: PrecompileTranscript,
140
141    /// Tracks decorator retrieval calls for testing.
142    #[cfg(test)]
143    pub decorator_retrieval_count: Rc<Cell<usize>>,
144}
145
146impl FastProcessor {
147    // CONSTRUCTORS
148    // ----------------------------------------------------------------------------------------------
149
150    /// Creates a new `FastProcessor` instance with the given stack inputs.
151    ///
152    /// By default, advice inputs are empty and execution options use their defaults
153    /// (debugging and tracing disabled).
154    ///
155    /// # Example
156    /// ```ignore
157    /// use miden_processor::FastProcessor;
158    ///
159    /// let processor = FastProcessor::new(stack_inputs)
160    ///     .with_advice(advice_inputs)
161    ///     .with_debugging(true)
162    ///     .with_tracing(true);
163    /// ```
164    pub fn new(stack_inputs: StackInputs) -> Self {
165        Self::new_with_options(stack_inputs, AdviceInputs::default(), ExecutionOptions::default())
166    }
167
168    /// Sets the advice inputs for the processor.
169    pub fn with_advice(mut self, advice_inputs: AdviceInputs) -> Self {
170        self.advice = advice_inputs.into();
171        self
172    }
173
174    /// Sets the execution options for the processor.
175    ///
176    /// This will override any previously set debugging or tracing settings.
177    pub fn with_options(mut self, options: ExecutionOptions) -> Self {
178        self.options = options;
179        self
180    }
181
182    /// Enables or disables debugging mode.
183    ///
184    /// When debugging is enabled, debug decorators will be executed during program execution.
185    pub fn with_debugging(mut self, enabled: bool) -> Self {
186        self.options = self.options.with_debugging(enabled);
187        self
188    }
189
190    /// Enables or disables tracing mode.
191    ///
192    /// When tracing is enabled, trace decorators will be executed during program execution.
193    pub fn with_tracing(mut self, enabled: bool) -> Self {
194        self.options = self.options.with_tracing(enabled);
195        self
196    }
197
198    /// Constructor for creating a `FastProcessor` with all options specified at once.
199    ///
200    /// For a more fluent API, consider using `FastProcessor::new()` with builder methods.
201    pub fn new_with_options(
202        stack_inputs: StackInputs,
203        advice_inputs: AdviceInputs,
204        options: ExecutionOptions,
205    ) -> Self {
206        let stack_top_idx = INITIAL_STACK_TOP_IDX;
207        let stack = {
208            // Note: we use `Vec::into_boxed_slice()` here, since `Box::new([T; N])` first allocates
209            // the array on the stack, and then moves it to the heap. This might cause a
210            // stack overflow on some systems.
211            let mut stack: Box<[Felt; STACK_BUFFER_SIZE]> =
212                vec![ZERO; STACK_BUFFER_SIZE].into_boxed_slice().try_into().unwrap();
213
214            // Copy inputs in reverse order so first element ends up at top of stack
215            for (i, &input) in stack_inputs.iter().enumerate() {
216                stack[stack_top_idx - 1 - i] = input;
217            }
218            stack
219        };
220
221        Self {
222            advice: advice_inputs.into(),
223            stack,
224            stack_top_idx,
225            stack_bot_idx: stack_top_idx - MIN_STACK_DEPTH,
226            clk: 0_u32.into(),
227            ctx: 0_u32.into(),
228            caller_hash: EMPTY_WORD,
229            memory: Memory::new(),
230            call_stack: Vec::new(),
231            options,
232            pc_transcript: PrecompileTranscript::new(),
233            #[cfg(test)]
234            decorator_retrieval_count: Rc::new(Cell::new(0)),
235        }
236    }
237
238    /// Returns the resume context to be used with the first call to `step()`.
239    pub fn get_initial_resume_context(
240        &mut self,
241        program: &Program,
242    ) -> Result<ResumeContext, ExecutionError> {
243        self.advice
244            .extend_map(program.mast_forest().advice_map())
245            .map_exec_err_no_ctx()?;
246
247        Ok(ResumeContext {
248            current_forest: program.mast_forest().clone(),
249            continuation_stack: ContinuationStack::new(program),
250            kernel: program.kernel().clone(),
251        })
252    }
253
254    // ACCESSORS
255    // -------------------------------------------------------------------------------------------
256
257    /// Returns whether the processor is executing in debug mode.
258    #[inline(always)]
259    pub fn in_debug_mode(&self) -> bool {
260        self.options.enable_debugging()
261    }
262
263    /// Returns true if decorators should be executed.
264    ///
265    /// This corresponds to either being in debug mode (for debug decorators) or having tracing
266    /// enabled (for trace decorators).
267    #[inline(always)]
268    fn should_execute_decorators(&self) -> bool {
269        self.in_debug_mode() || self.options.enable_tracing()
270    }
271
272    #[cfg(test)]
273    #[inline(always)]
274    fn record_decorator_retrieval(&self) {
275        self.decorator_retrieval_count.set(self.decorator_retrieval_count.get() + 1);
276    }
277
278    /// Returns the size of the stack.
279    #[inline(always)]
280    fn stack_size(&self) -> usize {
281        self.stack_top_idx - self.stack_bot_idx
282    }
283
284    /// Returns the stack, such that the top of the stack is at the last index of the returned
285    /// slice.
286    pub fn stack(&self) -> &[Felt] {
287        &self.stack[self.stack_bot_idx..self.stack_top_idx]
288    }
289
290    /// Returns the top 16 elements of the stack.
291    pub fn stack_top(&self) -> &[Felt] {
292        &self.stack[self.stack_top_idx - MIN_STACK_DEPTH..self.stack_top_idx]
293    }
294
295    /// Returns a mutable reference to the top 16 elements of the stack.
296    pub fn stack_top_mut(&mut self) -> &mut [Felt] {
297        &mut self.stack[self.stack_top_idx - MIN_STACK_DEPTH..self.stack_top_idx]
298    }
299
300    /// Returns the element on the stack at index `idx`.
301    ///
302    /// This method is only meant to be used to access the stack top by operation handlers, and
303    /// system event handlers.
304    ///
305    /// # Preconditions
306    /// - `idx` must be less than or equal to 15.
307    #[inline(always)]
308    pub fn stack_get(&self, idx: usize) -> Felt {
309        self.stack[self.stack_top_idx - idx - 1]
310    }
311
312    /// Same as [`Self::stack_get()`], but returns [`ZERO`] if `idx` falls below index 0 in the
313    /// stack buffer.
314    ///
315    /// Use this instead of `stack_get()` when `idx` may exceed 15.
316    #[inline(always)]
317    pub fn stack_get_safe(&self, idx: usize) -> Felt {
318        if idx < self.stack_top_idx {
319            self.stack[self.stack_top_idx - idx - 1]
320        } else {
321            ZERO
322        }
323    }
324
325    /// Mutable variant of `stack_get()`.
326    ///
327    /// This method is only meant to be used to access the stack top by operation handlers, and
328    /// system event handlers.
329    ///
330    /// # Preconditions
331    /// - `idx` must be less than or equal to 15.
332    #[inline(always)]
333    pub fn stack_get_mut(&mut self, idx: usize) -> &mut Felt {
334        &mut self.stack[self.stack_top_idx - idx - 1]
335    }
336
337    /// Returns the word on the stack starting at index `start_idx` in "stack order".
338    ///
339    /// For `start_idx=0` the top element of the stack will be at position 0 in the word.
340    ///
341    /// For example, if the stack looks like this:
342    ///
343    /// top                                                       bottom
344    /// v                                                           v
345    /// a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p
346    ///
347    /// Then
348    /// - `stack_get_word(0)` returns `[a, b, c, d]`,
349    /// - `stack_get_word(1)` returns `[b, c, d, e]`,
350    /// - etc.
351    ///
352    /// This method is only meant to be used to access the stack top by operation handlers, and
353    /// system event handlers.
354    ///
355    /// # Preconditions
356    /// - `start_idx` must be less than or equal to 12.
357    #[inline(always)]
358    pub fn stack_get_word(&self, start_idx: usize) -> Word {
359        // Ensure we have enough elements to form a complete word
360        debug_assert!(
361            start_idx + WORD_SIZE <= self.stack_depth() as usize,
362            "Not enough elements on stack to read word starting at index {start_idx}"
363        );
364
365        let word_start_idx = self.stack_top_idx - start_idx - WORD_SIZE;
366        let mut result: [Felt; WORD_SIZE] =
367            self.stack[range(word_start_idx, WORD_SIZE)].try_into().unwrap();
368        // Reverse so top of stack (idx 0) goes to word[0]
369        result.reverse();
370        result.into()
371    }
372
373    /// Same as [`Self::stack_get_word()`], but returns [`ZERO`] for any element that falls below
374    /// index 0 in the stack buffer.
375    ///
376    /// Use this instead of `stack_get_word()` when `start_idx + WORD_SIZE` may exceed
377    /// `stack_top_idx`.
378    #[inline(always)]
379    pub fn stack_get_word_safe(&self, start_idx: usize) -> Word {
380        let buf_end = self.stack_top_idx.saturating_sub(start_idx);
381        let buf_start = self.stack_top_idx.saturating_sub(start_idx.saturating_add(WORD_SIZE));
382        let num_elements_to_read_from_buf = buf_end - buf_start;
383
384        let mut result = [ZERO; WORD_SIZE];
385        if num_elements_to_read_from_buf == WORD_SIZE {
386            result.copy_from_slice(&self.stack[range(buf_start, WORD_SIZE)]);
387        } else if num_elements_to_read_from_buf > 0 {
388            let offset = WORD_SIZE - num_elements_to_read_from_buf;
389            result[offset..]
390                .copy_from_slice(&self.stack[range(buf_start, num_elements_to_read_from_buf)]);
391        }
392        result.reverse();
393
394        result.into()
395    }
396
397    /// Returns the number of elements on the stack in the current context.
398    #[inline(always)]
399    pub fn stack_depth(&self) -> u32 {
400        (self.stack_top_idx - self.stack_bot_idx) as u32
401    }
402
403    /// Returns a reference to the processor's memory.
404    pub fn memory(&self) -> &Memory {
405        &self.memory
406    }
407
408    /// Consumes the processor and returns the advice provider, memory, and precompile
409    /// transcript.
410    pub fn into_parts(self) -> (AdviceProvider, Memory, PrecompileTranscript) {
411        (self.advice, self.memory, self.pc_transcript)
412    }
413
414    /// Returns a reference to the execution options.
415    pub fn execution_options(&self) -> &ExecutionOptions {
416        &self.options
417    }
418
419    /// Returns a narrowed interface for reading and updating the processor state.
420    #[inline(always)]
421    pub fn state(&self) -> ProcessorState<'_> {
422        ProcessorState { processor: self }
423    }
424
425    // MUTATORS
426    // -------------------------------------------------------------------------------------------
427
428    /// Writes an element to the stack at the given index.
429    #[inline(always)]
430    pub fn stack_write(&mut self, idx: usize, element: Felt) {
431        self.stack[self.stack_top_idx - idx - 1] = element
432    }
433
434    /// Writes a word to the stack starting at the given index.
435    ///
436    /// `word[0]` goes to stack position start_idx (top), `word[1]` to start_idx+1, etc.
437    #[inline(always)]
438    pub fn stack_write_word(&mut self, start_idx: usize, word: &Word) {
439        debug_assert!(start_idx <= MIN_STACK_DEPTH - WORD_SIZE);
440
441        let word_start_idx = self.stack_top_idx - start_idx - 4;
442        let mut source: [Felt; WORD_SIZE] = (*word).into();
443        // Reverse so word[0] ends up at the top of stack (highest internal index)
444        source.reverse();
445        self.stack[range(word_start_idx, WORD_SIZE)].copy_from_slice(&source)
446    }
447
448    /// Swaps the elements at the given indices on the stack.
449    #[inline(always)]
450    pub fn stack_swap(&mut self, idx1: usize, idx2: usize) {
451        let a = self.stack_get(idx1);
452        let b = self.stack_get(idx2);
453        self.stack_write(idx1, b);
454        self.stack_write(idx2, a);
455    }
456
457    // EXECUTE
458    // -------------------------------------------------------------------------------------------
459
460    /// Executes the given program and returns the stack outputs as well as the advice provider.
461    pub async fn execute(
462        self,
463        program: &Program,
464        host: &mut impl Host,
465    ) -> Result<ExecutionOutput, ExecutionError> {
466        self.execute_with_tracer(program, host, &mut NoopTracer).await
467    }
468
469    /// Executes the given program and returns the stack outputs, the advice provider, and
470    /// context necessary to build the trace.
471    #[instrument(name = "execute_for_trace", skip_all)]
472    pub async fn execute_for_trace(
473        self,
474        program: &Program,
475        host: &mut impl Host,
476    ) -> Result<(ExecutionOutput, TraceGenerationContext), ExecutionError> {
477        let mut tracer = ExecutionTracer::new(self.options.core_trace_fragment_size());
478        let execution_output = self.execute_with_tracer(program, host, &mut tracer).await?;
479
480        let context = tracer.into_trace_generation_context();
481
482        Ok((execution_output, context))
483    }
484
485    /// Executes the given program with the provided tracer and returns the stack outputs, and the
486    /// advice provider.
487    pub async fn execute_with_tracer<T>(
488        mut self,
489        program: &Program,
490        host: &mut impl Host,
491        tracer: &mut T,
492    ) -> Result<ExecutionOutput, ExecutionError>
493    where
494        T: Tracer<Processor = Self>,
495    {
496        let mut continuation_stack = ContinuationStack::new(program);
497        let mut current_forest = program.mast_forest().clone();
498
499        // Merge the program's advice map into the advice provider
500        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
501
502        match self
503            .execute_impl(
504                &mut continuation_stack,
505                &mut current_forest,
506                program.kernel(),
507                host,
508                tracer,
509                &NeverStopper,
510            )
511            .await
512        {
513            ControlFlow::Continue(stack_outputs) => Ok(ExecutionOutput {
514                stack: stack_outputs,
515                advice: self.advice,
516                memory: self.memory,
517                final_pc_transcript: self.pc_transcript,
518            }),
519            ControlFlow::Break(break_reason) => match break_reason {
520                BreakReason::Err(err) => Err(err),
521                BreakReason::Stopped(_) => {
522                    unreachable!("Execution never stops prematurely with NeverStopper")
523                },
524            },
525        }
526    }
527
528    /// Executes a single clock cycle
529    pub async fn step(
530        &mut self,
531        host: &mut impl Host,
532        resume_ctx: ResumeContext,
533    ) -> Result<Option<ResumeContext>, ExecutionError> {
534        let ResumeContext {
535            mut current_forest,
536            mut continuation_stack,
537            kernel,
538        } = resume_ctx;
539
540        match self
541            .execute_impl(
542                &mut continuation_stack,
543                &mut current_forest,
544                &kernel,
545                host,
546                &mut NoopTracer,
547                &StepStopper,
548            )
549            .await
550        {
551            ControlFlow::Continue(_) => Ok(None),
552            ControlFlow::Break(break_reason) => match break_reason {
553                BreakReason::Err(err) => Err(err),
554                BreakReason::Stopped(maybe_continuation) => {
555                    if let Some(continuation) = maybe_continuation {
556                        continuation_stack.push_continuation(continuation);
557                    }
558
559                    Ok(Some(ResumeContext {
560                        current_forest,
561                        continuation_stack,
562                        kernel,
563                    }))
564                },
565            },
566        }
567    }
568
569    /// Executes the given program with the provided tracer and returns the stack outputs.
570    ///
571    /// This function takes a `&mut self` (compared to `self` for the public execute functions) so
572    /// that the processor state may be accessed after execution. It is incorrect to execute a
573    /// second program using the same processor. This is mainly meant to be used in tests.
574    async fn execute_impl<S, T>(
575        &mut self,
576        continuation_stack: &mut ContinuationStack,
577        current_forest: &mut Arc<MastForest>,
578        kernel: &Kernel,
579        host: &mut impl Host,
580        tracer: &mut T,
581        stopper: &S,
582    ) -> ControlFlow<BreakReason, StackOutputs>
583    where
584        S: Stopper<Processor = Self>,
585        T: Tracer<Processor = Self>,
586    {
587        while let ControlFlow::Break(internal_break_reason) =
588            execute_impl(self, continuation_stack, current_forest, kernel, host, tracer, stopper)
589        {
590            match internal_break_reason {
591                InternalBreakReason::User(break_reason) => return ControlFlow::Break(break_reason),
592                InternalBreakReason::Emit {
593                    basic_block_node_id,
594                    op_idx,
595                    continuation,
596                } => {
597                    self.op_emit(host, current_forest, basic_block_node_id, op_idx).await?;
598
599                    // Call `finish_emit_op_execution()`, as per the sans-IO contract.
600                    finish_emit_op_execution(
601                        continuation,
602                        self,
603                        continuation_stack,
604                        current_forest,
605                        tracer,
606                        stopper,
607                    )?;
608                },
609                InternalBreakReason::LoadMastForestFromDyn { dyn_node_id, callee_hash } => {
610                    // load mast forest asynchronously
611                    let (root_id, new_forest) = match self
612                        .load_mast_forest(callee_hash, host, current_forest, dyn_node_id)
613                        .await
614                    {
615                        Ok(result) => result,
616                        Err(err) => return ControlFlow::Break(BreakReason::Err(err)),
617                    };
618
619                    // Finish loading the MAST forest from the Dyn node, as per the sans-IO
620                    // contract.
621                    finish_load_mast_forest_from_dyn_start(
622                        root_id,
623                        new_forest,
624                        self,
625                        current_forest,
626                        continuation_stack,
627                        tracer,
628                        stopper,
629                    )?;
630                },
631                InternalBreakReason::LoadMastForestFromExternal {
632                    external_node_id,
633                    procedure_hash,
634                } => {
635                    // load mast forest asynchronously
636                    let (root_id, new_forest) = match self
637                        .load_mast_forest(procedure_hash, host, current_forest, external_node_id)
638                        .await
639                    {
640                        Ok(result) => result,
641                        Err(err) => {
642                            let maybe_enriched_err = maybe_use_caller_error_context(
643                                err,
644                                current_forest,
645                                continuation_stack,
646                                host,
647                            );
648
649                            return ControlFlow::Break(BreakReason::Err(maybe_enriched_err));
650                        },
651                    };
652
653                    // Finish loading the MAST forest from the External node, as per the sans-IO
654                    // contract.
655                    finish_load_mast_forest_from_external(
656                        root_id,
657                        new_forest,
658                        external_node_id,
659                        current_forest,
660                        continuation_stack,
661                        host,
662                        tracer,
663                    )?;
664                },
665            }
666        }
667
668        match StackOutputs::new(
669            &self.stack[self.stack_bot_idx..self.stack_top_idx]
670                .iter()
671                .rev()
672                .copied()
673                .collect::<Vec<_>>(),
674        ) {
675            Ok(stack_outputs) => ControlFlow::Continue(stack_outputs),
676            Err(_) => ControlFlow::Break(BreakReason::Err(ExecutionError::OutputStackOverflow(
677                self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
678            ))),
679        }
680    }
681
682    // DECORATOR EXECUTORS
683    // --------------------------------------------------------------------------------------------
684
685    /// Executes the decorators that should be executed before entering a node.
686    fn execute_before_enter_decorators(
687        &self,
688        node_id: MastNodeId,
689        current_forest: &MastForest,
690        host: &mut impl Host,
691    ) -> ControlFlow<BreakReason> {
692        if !self.should_execute_decorators() {
693            return ControlFlow::Continue(());
694        }
695
696        #[cfg(test)]
697        self.record_decorator_retrieval();
698
699        let node = current_forest
700            .get_node_by_id(node_id)
701            .expect("internal error: node id {node_id} not found in current forest");
702
703        for &decorator_id in node.before_enter(current_forest) {
704            self.execute_decorator(&current_forest[decorator_id], host)?;
705        }
706
707        ControlFlow::Continue(())
708    }
709
710    /// Executes the decorators that should be executed after exiting a node.
711    fn execute_after_exit_decorators(
712        &self,
713        node_id: MastNodeId,
714        current_forest: &MastForest,
715        host: &mut impl Host,
716    ) -> ControlFlow<BreakReason> {
717        if !self.in_debug_mode() {
718            return ControlFlow::Continue(());
719        }
720
721        #[cfg(test)]
722        self.record_decorator_retrieval();
723
724        let node = current_forest
725            .get_node_by_id(node_id)
726            .expect("internal error: node id {node_id} not found in current forest");
727
728        for &decorator_id in node.after_exit(current_forest) {
729            self.execute_decorator(&current_forest[decorator_id], host)?;
730        }
731
732        ControlFlow::Continue(())
733    }
734
735    /// Executes the specified decorator
736    fn execute_decorator(
737        &self,
738        decorator: &Decorator,
739        host: &mut impl Host,
740    ) -> ControlFlow<BreakReason> {
741        match decorator {
742            Decorator::Debug(options) => {
743                if self.in_debug_mode() {
744                    let processor_state = self.state();
745                    if let Err(err) = host.on_debug(&processor_state, options) {
746                        return ControlFlow::Break(BreakReason::Err(
747                            crate::errors::HostError::DebugHandlerError { err }.into(),
748                        ));
749                    }
750                }
751            },
752            Decorator::Trace(id) => {
753                if self.options.enable_tracing() {
754                    let processor_state = self.state();
755                    if let Err(err) = host.on_trace(&processor_state, *id) {
756                        return ControlFlow::Break(BreakReason::Err(
757                            crate::errors::HostError::TraceHandlerError { trace_id: *id, err }
758                                .into(),
759                        ));
760                    }
761                }
762            },
763        };
764        ControlFlow::Continue(())
765    }
766
767    // HELPERS
768    // ----------------------------------------------------------------------------------------------
769
770    async fn load_mast_forest(
771        &mut self,
772        node_digest: Word,
773        host: &mut impl Host,
774        current_forest: &MastForest,
775        node_id: MastNodeId,
776    ) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError> {
777        let mast_forest = host.get_mast_forest(&node_digest).await.ok_or_else(|| {
778            crate::errors::procedure_not_found_with_context(
779                node_digest,
780                current_forest,
781                node_id,
782                host,
783            )
784        })?;
785
786        // We limit the parts of the program that can be called externally to procedure
787        // roots, even though MAST doesn't have that restriction.
788        let root_id = mast_forest.find_procedure_root(node_digest).ok_or_else(|| {
789            Err::<(), _>(OperationError::MalformedMastForestInHost { root_digest: node_digest })
790                .map_exec_err(current_forest, node_id, host)
791                .unwrap_err()
792        })?;
793
794        // Merge the advice map of this forest into the advice provider.
795        // Note that the map may be merged multiple times if a different procedure from the same
796        // forest is called.
797        // For now, only compiled libraries contain non-empty advice maps, so for most cases,
798        // this call will be cheap.
799        self.advice.extend_map(mast_forest.advice_map()).map_exec_err(
800            current_forest,
801            node_id,
802            host,
803        )?;
804
805        Ok((root_id, mast_forest))
806    }
807
808    /// Increments the stack top pointer by 1.
809    ///
810    /// The bottom of the stack is never affected by this operation.
811    #[inline(always)]
812    fn increment_stack_size(&mut self) {
813        self.stack_top_idx += 1;
814    }
815
816    /// Decrements the stack top pointer by 1.
817    ///
818    /// The bottom of the stack is only decremented in cases where the stack depth would become less
819    /// than 16.
820    #[inline(always)]
821    fn decrement_stack_size(&mut self) {
822        if self.stack_top_idx == MIN_STACK_DEPTH {
823            // We no longer have any room in the stack buffer to decrement the stack size (which
824            // would cause the `stack_bot_idx` to go below 0). We therefore reset the stack to its
825            // original position.
826            self.reset_stack_in_buffer(INITIAL_STACK_TOP_IDX);
827        }
828
829        self.stack_top_idx -= 1;
830        self.stack_bot_idx = min(self.stack_bot_idx, self.stack_top_idx - MIN_STACK_DEPTH);
831    }
832
833    /// Resets the stack in the buffer to a new position, preserving the top 16 elements of the
834    /// stack.
835    ///
836    /// # Preconditions
837    /// - The stack is expected to have exactly 16 elements.
838    #[inline(always)]
839    fn reset_stack_in_buffer(&mut self, new_stack_top_idx: usize) {
840        debug_assert_eq!(self.stack_depth(), MIN_STACK_DEPTH as u32);
841
842        let new_stack_bot_idx = new_stack_top_idx - MIN_STACK_DEPTH;
843
844        // Copy stack to its new position
845        self.stack
846            .copy_within(self.stack_bot_idx..self.stack_top_idx, new_stack_bot_idx);
847
848        // Zero out stack below the new new_stack_bot_idx, since this is where overflow values
849        // come from, and are guaranteed to be ZERO. We don't need to zero out above
850        // `stack_top_idx`, since values there are never read before being written.
851        self.stack[0..new_stack_bot_idx].fill(ZERO);
852
853        // Update indices.
854        self.stack_bot_idx = new_stack_bot_idx;
855        self.stack_top_idx = new_stack_top_idx;
856    }
857
858    // SYNC WRAPPERS
859    // ----------------------------------------------------------------------------------------------
860
861    /// Convenience sync wrapper to [Self::step].
862    pub fn step_sync(
863        &mut self,
864        host: &mut impl Host,
865        resume_ctx: ResumeContext,
866    ) -> Result<Option<ResumeContext>, ExecutionError> {
867        // Create a new Tokio runtime and block on the async execution
868        let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
869
870        let execution_output = rt.block_on(self.step(host, resume_ctx))?;
871
872        Ok(execution_output)
873    }
874
875    /// Executes the given program step by step (calling [`Self::step`] repeatedly) and returns the
876    /// stack outputs.
877    pub fn execute_by_step_sync(
878        mut self,
879        program: &Program,
880        host: &mut impl Host,
881    ) -> Result<StackOutputs, ExecutionError> {
882        // Create a new Tokio runtime and block on the async execution
883        let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
884        let mut current_resume_ctx = self.get_initial_resume_context(program).unwrap();
885
886        rt.block_on(async {
887            loop {
888                match self.step(host, current_resume_ctx).await {
889                    Ok(maybe_resume_ctx) => match maybe_resume_ctx {
890                        Some(next_resume_ctx) => {
891                            current_resume_ctx = next_resume_ctx;
892                        },
893                        None => {
894                            // End of program was reached
895                            break Ok(StackOutputs::new(
896                                &self.stack[self.stack_bot_idx..self.stack_top_idx]
897                                    .iter()
898                                    .rev()
899                                    .copied()
900                                    .collect::<Vec<_>>(),
901                            )
902                            .unwrap());
903                        },
904                    },
905                    Err(err) => {
906                        break Err(err);
907                    },
908                }
909            }
910        })
911    }
912
913    /// Convenience sync wrapper to [Self::execute].
914    ///
915    /// This method is only available on non-wasm32 targets. On wasm32, use the
916    /// async `execute()` method directly since wasm32 runs in the browser's event loop.
917    ///
918    /// # Panics
919    /// Panics if called from within an existing Tokio runtime. Use the async `execute()`
920    /// method instead in async contexts.
921    #[cfg(not(target_family = "wasm"))]
922    pub fn execute_sync(
923        self,
924        program: &Program,
925        host: &mut impl Host,
926    ) -> Result<ExecutionOutput, ExecutionError> {
927        match tokio::runtime::Handle::try_current() {
928            Ok(_handle) => {
929                // We're already inside a Tokio runtime - this is not supported
930                // because we cannot safely create a nested runtime or move the
931                // non-Send host reference to another thread
932                panic!(
933                    "Cannot call execute_sync from within a Tokio runtime. \
934                     Use the async execute() method instead."
935                )
936            },
937            Err(_) => {
938                // No runtime exists - create one and use it
939                let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
940                rt.block_on(self.execute(program, host))
941            },
942        }
943    }
944
945    /// Convenience sync wrapper to [Self::execute_for_trace].
946    ///
947    /// This method is only available on non-wasm32 targets. On wasm32, use the
948    /// async `execute_for_trace()` method directly since wasm32 runs in the browser's event loop.
949    ///
950    /// # Panics
951    /// Panics if called from within an existing Tokio runtime. Use the async `execute_for_trace()`
952    /// method instead in async contexts.
953    #[cfg(not(target_family = "wasm"))]
954    #[instrument(name = "execute_for_trace_sync", skip_all)]
955    pub fn execute_for_trace_sync(
956        self,
957        program: &Program,
958        host: &mut impl Host,
959    ) -> Result<(ExecutionOutput, TraceGenerationContext), ExecutionError> {
960        match tokio::runtime::Handle::try_current() {
961            Ok(_handle) => {
962                // We're already inside a Tokio runtime - this is not supported
963                // because we cannot safely create a nested runtime or move the
964                // non-Send host reference to another thread
965                panic!(
966                    "Cannot call execute_for_trace_sync from within a Tokio runtime. \
967                     Use the async execute_for_trace() method instead."
968                )
969            },
970            Err(_) => {
971                // No runtime exists - create one and use it
972                let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
973                rt.block_on(self.execute_for_trace(program, host))
974            },
975        }
976    }
977
978    /// Similar to [Self::execute_sync], but allows mutable access to the processor.
979    ///
980    /// This method is only available on non-wasm32 targets for testing. On wasm32, use
981    /// async execution methods directly since wasm32 runs in the browser's event loop.
982    ///
983    /// # Panics
984    /// Panics if called from within an existing Tokio runtime. Use async execution
985    /// methods instead in async contexts.
986    #[cfg(all(any(test, feature = "testing"), not(target_family = "wasm")))]
987    pub fn execute_sync_mut(
988        &mut self,
989        program: &Program,
990        host: &mut impl Host,
991    ) -> Result<StackOutputs, ExecutionError> {
992        let mut continuation_stack = ContinuationStack::new(program);
993        let mut current_forest = program.mast_forest().clone();
994
995        // Merge the program's advice map into the advice provider
996        self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
997
998        let execute_fut = async {
999            match self
1000                .execute_impl(
1001                    &mut continuation_stack,
1002                    &mut current_forest,
1003                    program.kernel(),
1004                    host,
1005                    &mut NoopTracer,
1006                    &NeverStopper,
1007                )
1008                .await
1009            {
1010                ControlFlow::Continue(stack_outputs) => Ok(stack_outputs),
1011                ControlFlow::Break(break_reason) => match break_reason {
1012                    BreakReason::Err(err) => Err(err),
1013                    BreakReason::Stopped(_) => {
1014                        unreachable!("Execution never stops prematurely with NeverStopper")
1015                    },
1016                },
1017            }
1018        };
1019
1020        match tokio::runtime::Handle::try_current() {
1021            Ok(_handle) => {
1022                // We're already inside a Tokio runtime - this is not supported
1023                // because we cannot safely create a nested runtime or move the
1024                // non-Send host reference to another thread
1025                panic!(
1026                    "Cannot call execute_sync_mut from within a Tokio runtime. \
1027                     Use async execution methods instead."
1028                )
1029            },
1030            Err(_) => {
1031                // No runtime exists - create one and use it
1032                let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
1033                rt.block_on(execute_fut)
1034            },
1035        }
1036    }
1037}
1038
1039// EXECUTION OUTPUT
1040// ===============================================================================================
1041
1042/// The output of a program execution, containing the state of the stack, advice provider,
1043/// memory, and final precompile transcript at the end of execution.
1044#[derive(Debug)]
1045pub struct ExecutionOutput {
1046    pub stack: StackOutputs,
1047    pub advice: AdviceProvider,
1048    pub memory: Memory,
1049    pub final_pc_transcript: PrecompileTranscript,
1050}
1051
1052// EXECUTION CONTEXT INFO
1053// ===============================================================================================
1054
1055/// Information about the execution context.
1056///
1057/// This struct is used to keep track of the information needed to return to the previous context
1058/// upon return from a `call`, `syscall` or `dyncall`.
1059#[derive(Debug)]
1060struct ExecutionContextInfo {
1061    /// This stores all the elements on the stack at the call site, excluding the top 16 elements.
1062    /// This corresponds to the overflow table in [crate::Process].
1063    overflow_stack: Vec<Felt>,
1064    ctx: ContextId,
1065    fn_hash: Word,
1066}
1067
1068// NOOP TRACER
1069// ================================================================================================
1070
1071/// A [Tracer] that does nothing.
1072pub struct NoopTracer;
1073
1074impl Tracer for NoopTracer {
1075    type Processor = FastProcessor;
1076
1077    #[inline(always)]
1078    fn start_clock_cycle(
1079        &mut self,
1080        _processor: &FastProcessor,
1081        _continuation: Continuation,
1082        _continuation_stack: &ContinuationStack,
1083        _current_forest: &Arc<MastForest>,
1084    ) {
1085        // do nothing
1086    }
1087
1088    #[inline(always)]
1089    fn finalize_clock_cycle(
1090        &mut self,
1091        _processor: &FastProcessor,
1092        _op_helper_registers: OperationHelperRegisters,
1093        _current_forest: &Arc<MastForest>,
1094    ) {
1095        // do nothing
1096    }
1097}