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(¤t_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(¤t_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}