miden_processor/fast/mod.rs
1use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
2use core::{cmp::min, ops::ControlFlow};
3
4use miden_air::{Felt, trace::RowIndex};
5use miden_core::{
6 EMPTY_WORD, WORD_SIZE, Word, ZERO,
7 mast::{ExecutableMastForest, MastForest},
8 precompile::PrecompileTranscript,
9 program::{MIN_STACK_DEPTH, Program, StackInputs, StackOutputs},
10 utils::range,
11};
12use miden_mast_package::Package;
13
14use crate::{
15 AdviceInputs, AdviceProvider, ContextId, ExecutionError, ExecutionOptions, ProcessorState,
16 advice::AdviceError,
17 continuation_stack::{Continuation, ContinuationStack},
18 errors::MapExecErrNoCtx,
19 tracer::{OperationHelperRegisters, Tracer},
20};
21
22mod basic_block;
23mod execution_api;
24mod external;
25mod memory;
26mod operation;
27mod step;
28
29pub use basic_block::SystemEventError;
30pub use memory::Memory;
31pub use step::{BreakReason, ResumeContext};
32
33#[cfg(test)]
34mod tests;
35
36// CONSTANTS
37// ================================================================================================
38
39/// The initial size of the stack buffer.
40///
41/// Note: This value is much larger than it needs to be for the majority of programs. However, some
42/// existing programs need it, so we're forced to push it up (though this should be double-checked).
43/// At this high a value, we're starting to see some performance degradation on benchmarks. For
44/// example, the blake3 benchmark went from 285 MHz to 250 MHz (~10% degradation). Perhaps a better
45/// solution would be to make this value much smaller (~1000), and then fallback to a `Vec` if the
46/// stack overflows.
47const INITIAL_STACK_BUFFER_SIZE: usize = 6850;
48
49/// The initial position of the top of the stack in the stack buffer.
50///
51/// We place this value close to 0 because if a program hits the limit, it's much more likely to hit
52/// the upper bound than the lower bound, since hitting the lower bound only occurs when you drop
53/// 0's that were generated automatically to keep the stack depth at 16. In practice, if this
54/// occurs, it is most likely a bug.
55const INITIAL_STACK_TOP_IDX: usize = 250;
56
57/// Default maximum operand stack depth preserving the previous fixed-buffer ceiling.
58const DEFAULT_MAX_STACK_DEPTH: usize =
59 INITIAL_STACK_BUFFER_SIZE - INITIAL_STACK_TOP_IDX - 1 + MIN_STACK_DEPTH;
60
61const _: [(); 1] =
62 [(); (ExecutionOptions::DEFAULT_MAX_STACK_DEPTH == DEFAULT_MAX_STACK_DEPTH) as usize];
63
64/// The stack buffer index where the logical operand stack starts after reset/recenter.
65const STACK_BUFFER_BASE_IDX: usize = INITIAL_STACK_TOP_IDX - MIN_STACK_DEPTH;
66
67// FAST PROCESSOR
68// ================================================================================================
69
70/// A fast processor which doesn't generate any trace.
71///
72/// This processor is designed to be as fast as possible. Hence, it only keeps track of the current
73/// state of the processor (i.e. the stack, current clock cycle, current memory context, and free
74/// memory pointer).
75///
76/// # Stack Management
77/// A few key points about how the stack was designed for maximum performance:
78///
79/// - The stack starts with a fixed buffer size defined by `INITIAL_STACK_BUFFER_SIZE`.
80/// - This was observed to increase performance by at least 2x compared to using a `Vec` with
81/// `push()` & `pop()`.
82/// - We track the stack top and bottom using indices `stack_top_idx` and `stack_bot_idx`,
83/// respectively.
84/// - Since we are using a fixed-size buffer, we need to ensure that stack buffer accesses are not
85/// out of bounds. Naively, we could check for this on every access. However, every operation
86/// alters the stack depth by a predetermined amount, allowing us to precisely determine the
87/// minimum number of operations required to reach a stack buffer boundary, whether at the top or
88/// bottom.
89/// - For example, if the stack top is 10 elements away from the top boundary, and the stack
90/// bottom is 15 elements away from the bottom boundary, then we can safely execute 10
91/// operations that modify the stack depth with no bounds check.
92/// - When switching contexts (e.g., during a call or syscall), all elements past the first 16 are
93/// stored in `stack_overflow_save_stack`, and the stack is truncated to 16 elements. They will be
94/// restored when returning from the call or syscall.
95///
96/// # Clock Cycle Management
97/// - The clock cycle (`clk`) is managed in the same way as in `Process`. That is, it is incremented
98/// by 1 for every row that `Process` adds to the main trace.
99/// - It is important to do so because the clock cycle is used to determine the context ID for
100/// new execution contexts when using `call` or `dyncall`.
101#[derive(Debug)]
102pub struct FastProcessor {
103 /// The stack is stored in reverse order, so that the last element is at the top of the stack.
104 stack: Box<[Felt]>,
105 /// The index of the top of the stack.
106 stack_top_idx: usize,
107 /// The index of the bottom of the stack.
108 stack_bot_idx: usize,
109
110 /// The current clock cycle.
111 clk: RowIndex,
112
113 /// The current context ID.
114 ctx: ContextId,
115
116 /// The hash of the function that called into the current context, or `[ZERO, ZERO, ZERO,
117 /// ZERO]` if we are in the first context (i.e. when `system_call_state_stack` is empty).
118 caller_hash: Word,
119
120 /// The advice provider to be used during execution.
121 advice: AdviceProvider,
122
123 /// A map from (context_id, word_address) to the word stored starting at that memory location.
124 memory: Memory,
125
126 /// Stack of saved system state, used when starting a new execution context (from a `call`,
127 /// `syscall` or `dyncall`) to keep track of the previous `(ctx, caller_hash)` upon return.
128 /// Pushed in lockstep with `stack_overflow_save_stack`.
129 system_call_state_stack: Vec<SystemCallState>,
130
131 /// Stack of saved operand-stack overflows, used when starting a new execution context to keep
132 /// the elements that lived past the top 16 of the previous context. Pushed in lockstep with
133 /// `system_call_state_stack`.
134 stack_overflow_save_stack: Vec<Vec<Felt>>,
135
136 /// Running total of the number of field elements currently held across all suspended overflow
137 /// segments in `stack_overflow_save_stack`. Maintained in lockstep with that stack so the
138 /// aggregate operand-stack depth (active context plus all suspended overflow) can be bounded
139 /// by `ExecutionOptions::max_stack_depth()` in O(1) without summing every saved segment on
140 /// each push. See [`Self::ensure_stack_capacity_for_push`].
141 saved_overflow_len: usize,
142
143 /// Options for execution, including cycle limits, stack limits, advice map limits, and the
144 /// size of core trace fragments during execution.
145 options: ExecutionOptions,
146
147 /// Transcript used to record commitments via `log_precompile` instruction (implemented via
148 /// Poseidon2 sponge).
149 pc_transcript: PrecompileTranscript,
150}
151
152impl FastProcessor {
153 /// Packages the processor state after successful execution into a public result type.
154 #[inline(always)]
155 fn into_execution_output(self, stack: StackOutputs) -> ExecutionOutput {
156 ExecutionOutput {
157 stack,
158 advice: self.advice,
159 memory: self.memory,
160 final_precompile_transcript: self.pc_transcript,
161 }
162 }
163
164 /// Converts the terminal result of a full execution run into [`ExecutionOutput`].
165 #[inline(always)]
166 fn execution_result_from_flow(
167 flow: ControlFlow<BreakReason<Arc<MastForest>>, StackOutputs>,
168 processor: Self,
169 ) -> Result<ExecutionOutput, ExecutionError> {
170 match flow {
171 ControlFlow::Continue(stack_outputs) => {
172 Ok(processor.into_execution_output(stack_outputs))
173 },
174 ControlFlow::Break(break_reason) => match break_reason {
175 BreakReason::Err(err) => Err(err),
176 BreakReason::Stopped(_) => {
177 unreachable!("Execution never stops prematurely with NeverStopper")
178 },
179 },
180 }
181 }
182
183 /// Converts a testing-only execution result into stack outputs.
184 #[cfg(any(test, feature = "testing"))]
185 #[inline(always)]
186 fn stack_result_from_flow(
187 flow: ControlFlow<BreakReason<Arc<MastForest>>, StackOutputs>,
188 ) -> Result<StackOutputs, ExecutionError> {
189 match flow {
190 ControlFlow::Continue(stack_outputs) => Ok(stack_outputs),
191 ControlFlow::Break(break_reason) => match break_reason {
192 BreakReason::Err(err) => Err(err),
193 BreakReason::Stopped(_) => {
194 unreachable!("Execution never stops prematurely with NeverStopper")
195 },
196 },
197 }
198 }
199
200 // CONSTRUCTORS
201 // ----------------------------------------------------------------------------------------------
202
203 /// Creates a new `FastProcessor` instance with the given stack inputs.
204 ///
205 /// By default, advice inputs are empty and execution options use their defaults.
206 ///
207 /// # Example
208 /// ```ignore
209 /// use miden_processor::FastProcessor;
210 ///
211 /// let processor = FastProcessor::new(stack_inputs)
212 /// .with_advice(advice_inputs)
213 /// .expect("advice inputs should fit advice map limits");
214 /// ```
215 ///
216 /// When using non-default advice map limits, prefer [`Self::new_with_options`] so the advice
217 /// inputs are validated against the intended execution options.
218 pub fn new(stack_inputs: StackInputs) -> Self {
219 Self::new_with_options(stack_inputs, AdviceInputs::default(), ExecutionOptions::default())
220 .expect("empty advice inputs should fit default advice map limits")
221 }
222
223 /// Sets the advice inputs for the processor.
224 ///
225 /// Advice inputs are loaded into the live advice provider immediately and are validated against
226 /// the processor's current [`ExecutionOptions`]. If the advice map needs non-default limits,
227 /// construct the processor with [`Self::new_with_options`] or call [`Self::with_options`]
228 /// before calling this method.
229 pub fn with_advice(mut self, advice_inputs: AdviceInputs) -> Result<Self, AdviceError> {
230 self.advice = AdviceProvider::new(advice_inputs, &self.options)?;
231 Ok(self)
232 }
233
234 /// Sets the execution options for the processor.
235 ///
236 /// Existing advice inputs are revalidated against the new options before they are applied. To
237 /// load advice inputs that require non-default advice map limits, call this before
238 /// [`Self::with_advice`] or use [`Self::new_with_options`].
239 pub fn with_options(mut self, options: ExecutionOptions) -> Result<Self, AdviceError> {
240 self.advice.set_options(&options)?;
241 self.memory.set_max_elements(options.max_memory_elements());
242 self.options = options;
243 Ok(self)
244 }
245
246 /// Constructor for creating a `FastProcessor` with all options specified at once.
247 ///
248 /// For a more fluent API, consider using `FastProcessor::new()` with builder methods.
249 pub fn new_with_options(
250 stack_inputs: StackInputs,
251 advice_inputs: AdviceInputs,
252 options: ExecutionOptions,
253 ) -> Result<Self, AdviceError> {
254 let stack_top_idx = INITIAL_STACK_TOP_IDX;
255 let stack = {
256 // Note: we use `Vec::into_boxed_slice()` here, since `Box::new([T; N])` first allocates
257 // the array on the stack, and then moves it to the heap. This might cause a
258 // stack overflow on some systems.
259 let mut stack = vec![ZERO; INITIAL_STACK_BUFFER_SIZE].into_boxed_slice();
260
261 // Copy inputs in reverse order so first element ends up at top of stack
262 for (i, &input) in stack_inputs.iter().enumerate() {
263 stack[stack_top_idx - 1 - i] = input;
264 }
265 stack
266 };
267
268 Ok(Self {
269 advice: AdviceProvider::new(advice_inputs, &options)?,
270 stack,
271 stack_top_idx,
272 stack_bot_idx: stack_top_idx - MIN_STACK_DEPTH,
273 clk: 0_u32.into(),
274 ctx: 0_u32.into(),
275 caller_hash: EMPTY_WORD,
276 memory: Memory::new(options.max_memory_elements()),
277 system_call_state_stack: Vec::new(),
278 stack_overflow_save_stack: Vec::new(),
279 saved_overflow_len: 0,
280 options,
281 pc_transcript: PrecompileTranscript::new(),
282 })
283 }
284
285 /// Returns the resume context to be used with the first call to `step_sync()`.
286 ///
287 /// This function asserts that `package` is not of executable type - callers should ensure that
288 /// it is before calling
289 pub fn get_initial_resume_context_for_package(
290 &mut self,
291 package: Arc<Package>,
292 ) -> Result<ResumeContext, ExecutionError> {
293 let program = package.unwrap_program();
294 let package_debug_info = package.debug_info()?.map(Arc::new);
295 let current_forest = program.mast_forest().clone();
296 self.advice.extend_map(current_forest.advice_map()).map_exec_err_no_ctx()?;
297
298 let entrypoint_source_node_id = package.entrypoint_source_node();
299 let continuation_stack = if let Some(debug_info) = package_debug_info.as_deref() {
300 Self::source_aware_continuation_stack(&program, debug_info, entrypoint_source_node_id)?
301 } else {
302 ContinuationStack::new(&program)
303 };
304
305 Ok(ResumeContext {
306 current_forest,
307 continuation_stack,
308 kernel: program.kernel().clone(),
309 package_debug_info,
310 })
311 }
312
313 /// Returns the resume context to be used with the first call to `step_sync()`.
314 pub fn get_initial_resume_context(
315 &mut self,
316 program: &Program,
317 ) -> Result<ResumeContext, ExecutionError> {
318 self.advice
319 .extend_map(program.mast_forest().advice_map())
320 .map_exec_err_no_ctx()?;
321
322 Ok(ResumeContext {
323 current_forest: program.mast_forest().clone(),
324 continuation_stack: ContinuationStack::new(program),
325 kernel: program.kernel().clone(),
326 package_debug_info: None,
327 })
328 }
329
330 // ACCESSORS
331 // -------------------------------------------------------------------------------------------
332
333 /// Returns the size of the stack.
334 #[inline(always)]
335 fn stack_size(&self) -> usize {
336 self.stack_top_idx - self.stack_bot_idx
337 }
338
339 /// Returns the stack, such that the top of the stack is at the last index of the returned
340 /// slice.
341 pub fn stack(&self) -> &[Felt] {
342 &self.stack[self.stack_bot_idx..self.stack_top_idx]
343 }
344
345 /// Returns the top 16 elements of the stack.
346 pub fn stack_top(&self) -> &[Felt] {
347 &self.stack[self.stack_top_idx - MIN_STACK_DEPTH..self.stack_top_idx]
348 }
349
350 /// Returns a mutable reference to the top 16 elements of the stack.
351 pub fn stack_top_mut(&mut self) -> &mut [Felt] {
352 &mut self.stack[self.stack_top_idx - MIN_STACK_DEPTH..self.stack_top_idx]
353 }
354
355 /// Returns the element on the stack at index `idx`.
356 ///
357 /// This method is only meant to be used to access the stack top by operation handlers, and
358 /// system event handlers.
359 ///
360 /// # Preconditions
361 /// - `idx` must be less than or equal to 15.
362 #[inline(always)]
363 pub fn stack_get(&self, idx: usize) -> Felt {
364 self.stack[self.stack_top_idx - idx - 1]
365 }
366
367 /// Same as [`Self::stack_get()`], but returns [`ZERO`] if `idx` falls below index 0 in the
368 /// stack buffer.
369 ///
370 /// Use this instead of `stack_get()` when `idx` may exceed 15.
371 #[inline(always)]
372 pub fn stack_get_safe(&self, idx: usize) -> Felt {
373 if idx < self.stack_top_idx {
374 self.stack[self.stack_top_idx - idx - 1]
375 } else {
376 ZERO
377 }
378 }
379
380 /// Mutable variant of `stack_get()`.
381 ///
382 /// This method is only meant to be used to access the stack top by operation handlers, and
383 /// system event handlers.
384 ///
385 /// # Preconditions
386 /// - `idx` must be less than or equal to 15.
387 #[inline(always)]
388 pub fn stack_get_mut(&mut self, idx: usize) -> &mut Felt {
389 &mut self.stack[self.stack_top_idx - idx - 1]
390 }
391
392 /// Returns the word on the stack starting at index `start_idx` in "stack order".
393 ///
394 /// For `start_idx=0` the top element of the stack will be at position 0 in the word.
395 ///
396 /// For example, if the stack looks like this:
397 ///
398 /// top bottom
399 /// v v
400 /// a | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p
401 ///
402 /// Then
403 /// - `stack_get_word(0)` returns `[a, b, c, d]`,
404 /// - `stack_get_word(1)` returns `[b, c, d, e]`,
405 /// - etc.
406 ///
407 /// This method is only meant to be used to access the stack top by operation handlers, and
408 /// system event handlers.
409 ///
410 /// # Preconditions
411 /// - `start_idx` must be less than or equal to 12.
412 #[inline(always)]
413 pub fn stack_get_word(&self, start_idx: usize) -> Word {
414 // Ensure we have enough elements to form a complete word
415 debug_assert!(
416 start_idx + WORD_SIZE <= self.stack_depth() as usize,
417 "Not enough elements on stack to read word starting at index {start_idx}"
418 );
419
420 let word_start_idx = self.stack_top_idx - start_idx - WORD_SIZE;
421 let mut result: [Felt; WORD_SIZE] =
422 self.stack[range(word_start_idx, WORD_SIZE)].try_into().unwrap();
423 // Reverse so top of stack (idx 0) goes to word[0]
424 result.reverse();
425 result.into()
426 }
427
428 /// Same as [`Self::stack_get_word()`], but returns [`ZERO`] for any element that falls below
429 /// index 0 in the stack buffer.
430 ///
431 /// Use this instead of `stack_get_word()` when `start_idx + WORD_SIZE` may exceed
432 /// `stack_top_idx`.
433 #[inline(always)]
434 pub fn stack_get_word_safe(&self, start_idx: usize) -> Word {
435 let buf_end = self.stack_top_idx.saturating_sub(start_idx);
436 let buf_start = self.stack_top_idx.saturating_sub(start_idx.saturating_add(WORD_SIZE));
437 let num_elements_to_read_from_buf = buf_end - buf_start;
438
439 let mut result = [ZERO; WORD_SIZE];
440 if num_elements_to_read_from_buf == WORD_SIZE {
441 result.copy_from_slice(&self.stack[range(buf_start, WORD_SIZE)]);
442 } else if num_elements_to_read_from_buf > 0 {
443 let offset = WORD_SIZE - num_elements_to_read_from_buf;
444 result[offset..]
445 .copy_from_slice(&self.stack[range(buf_start, num_elements_to_read_from_buf)]);
446 }
447 result.reverse();
448
449 result.into()
450 }
451
452 /// Returns the number of elements on the stack in the current context.
453 #[inline(always)]
454 pub fn stack_depth(&self) -> u32 {
455 (self.stack_top_idx - self.stack_bot_idx) as u32
456 }
457
458 /// Returns a reference to the processor's memory.
459 pub fn memory(&self) -> &Memory {
460 &self.memory
461 }
462
463 /// Consumes the processor and returns the advice provider, memory, and precompile
464 /// transcript.
465 pub fn into_parts(self) -> (AdviceProvider, Memory, PrecompileTranscript) {
466 (self.advice, self.memory, self.pc_transcript)
467 }
468
469 /// Returns a reference to the execution options.
470 pub fn execution_options(&self) -> &ExecutionOptions {
471 &self.options
472 }
473
474 /// Returns a narrowed interface for reading and updating the processor state.
475 #[inline(always)]
476 pub fn state(&self) -> ProcessorState<'_> {
477 ProcessorState { processor: self }
478 }
479
480 // MUTATORS
481 // -------------------------------------------------------------------------------------------
482
483 /// Writes an element to the stack at the given index.
484 #[inline(always)]
485 pub fn stack_write(&mut self, idx: usize, element: Felt) {
486 self.stack[self.stack_top_idx - idx - 1] = element
487 }
488
489 /// Writes a word to the stack starting at the given index.
490 ///
491 /// `word[0]` goes to stack position start_idx (top), `word[1]` to start_idx+1, etc.
492 #[inline(always)]
493 pub fn stack_write_word(&mut self, start_idx: usize, word: &Word) {
494 debug_assert!(start_idx <= MIN_STACK_DEPTH - WORD_SIZE);
495
496 let word_start_idx = self.stack_top_idx - start_idx - 4;
497 let mut source: [Felt; WORD_SIZE] = (*word).into();
498 // Reverse so word[0] ends up at the top of stack (highest internal index)
499 source.reverse();
500 self.stack[range(word_start_idx, WORD_SIZE)].copy_from_slice(&source)
501 }
502
503 /// Swaps the elements at the given indices on the stack.
504 #[inline(always)]
505 pub fn stack_swap(&mut self, idx1: usize, idx2: usize) {
506 let a = self.stack_get(idx1);
507 let b = self.stack_get(idx2);
508 self.stack_write(idx1, b);
509 self.stack_write(idx2, a);
510 }
511
512 /// Increments the stack top pointer by 1.
513 ///
514 /// The bottom of the stack is never affected by this operation.
515 #[inline(always)]
516 fn increment_stack_size(&mut self) {
517 self.stack_top_idx += 1;
518 }
519
520 /// Ensures the internal stack storage can accommodate one additional logical stack element.
521 ///
522 /// The operand stack depth limit is the semantic resource bound; the buffer is only an
523 /// implementation detail. We therefore check the logical depth before allocating so a program
524 /// cannot force memory growth beyond `ExecutionOptions::max_stack_depth()`. When storage does
525 /// need to grow, it grows geometrically and remains heap-allocated as a boxed slice. A
526 /// `SmallVec` would put a useful inline buffer inside `FastProcessor`, and preallocating the
527 /// full limit would penalize ordinary programs. This policy is performance-sensitive and should
528 /// be benchmarked against the fixed-buffer baseline.
529 ///
530 /// The depth that is checked is the *aggregate* operand-stack depth: the active context's depth
531 /// plus every element held in suspended overflow segments (`saved_overflow_len`). A `call`,
532 /// `dyncall`, or `syscall` context switch hides the caller's overflow in
533 /// `stack_overflow_save_stack` rather than freeing it, so checking only the active context
534 /// would let a program nest context switches to accumulate `O(call_depth *
535 /// max_stack_depth)` hidden operand-stack memory while every live frame stayed within the
536 /// limit. Because a context switch merely moves elements between the active stack and the
537 /// saved overflow (it never creates elements), the aggregate is conserved across switches
538 /// and only grows on a push, so enforcing the bound here is sufficient to cap total
539 /// operand-stack memory.
540 #[inline(always)]
541 fn ensure_stack_capacity_for_push(&mut self) -> Result<(), ExecutionError> {
542 let depth = self.stack_size() + self.saved_overflow_len + 1;
543 let max = self.options.max_stack_depth();
544 if depth > max {
545 return Err(ExecutionError::StackDepthLimitExceeded { depth, max });
546 }
547
548 if self.stack_top_idx >= self.stack.len() - 1 {
549 self.grow_stack_buffer(self.stack_top_idx + 2);
550 }
551
552 Ok(())
553 }
554
555 fn ensure_stack_capacity_for_top_idx(&mut self, top_idx: usize) {
556 if top_idx >= self.stack.len() {
557 self.grow_stack_buffer(top_idx + 1);
558 }
559 }
560
561 fn grow_stack_buffer(&mut self, requested_min_len: usize) {
562 // The maximum allocation is tied to the logical operand stack depth, not to the current
563 // buffer position. Using `stack_bot_idx` here would make the allocation ceiling drift when
564 // the live stack has moved away from the initial base.
565 let max_len = STACK_BUFFER_BASE_IDX
566 .saturating_add(self.options.max_stack_depth())
567 .saturating_add(1);
568 let live_len = self.stack_size();
569
570 // Growth also recenters the live stack at the normal base. This keeps future push/drop
571 // behavior close to the fixed-buffer layout and avoids carrying unused prefix cells into
572 // the new allocation. The extra slot is for the next checked push that triggered growth.
573 let recentered_min_len = STACK_BUFFER_BASE_IDX.saturating_add(live_len).saturating_add(2);
574 debug_assert!(recentered_min_len <= max_len);
575
576 // Allocation growth is based on the stack's post-recentered live range, not the previous
577 // buffer length. The `requested_min_len` may be beyond the allocation cap when a shallow
578 // context is still positioned near the end of the old buffer; recentering the live stack is
579 // what makes that valid. The VM-visible requirements are that the live stack is restored at
580 // `STACK_BUFFER_BASE_IDX`, the post-recentered push slot is available, and allocation stays
581 // capped by the configured stack depth. The allocation size can differ from the previous
582 // doubling policy: normal push growth may allocate a couple of extra cells because of the
583 // spare push slot, while restoring a deep caller from a shallow callee may allocate only
584 // the requested restored range instead of doubling the old buffer. That smaller
585 // restore allocation is intentional, but it means future pushes can grow again
586 // sooner and should stay covered by benchmarks.
587 let new_len = recentered_min_len.saturating_mul(2).max(requested_min_len).min(max_len);
588 debug_assert!(new_len <= max_len);
589
590 let mut new_stack = vec![ZERO; new_len].into_boxed_slice();
591 let new_stack_bot_idx = STACK_BUFFER_BASE_IDX;
592 let new_stack_top_idx = new_stack_bot_idx + live_len;
593
594 // Only the active stack range carries VM state. Prefix/suffix cells are scratch storage and
595 // stay zeroed, which keeps growth proportional to the live depth instead of the old buffer
596 // length.
597 new_stack[new_stack_bot_idx..new_stack_top_idx]
598 .copy_from_slice(&self.stack[self.stack_bot_idx..self.stack_top_idx]);
599
600 self.stack = new_stack;
601 self.stack_bot_idx = new_stack_bot_idx;
602 self.stack_top_idx = new_stack_top_idx;
603 }
604
605 /// Decrements the stack top pointer by 1.
606 ///
607 /// The bottom of the stack is only decremented in cases where the stack depth would become less
608 /// than 16.
609 #[inline(always)]
610 fn decrement_stack_size(&mut self) {
611 if self.stack_top_idx == MIN_STACK_DEPTH {
612 // We no longer have any room in the stack buffer to decrement the stack size (which
613 // would cause the `stack_bot_idx` to go below 0). We therefore reset the stack to its
614 // original position.
615 self.reset_stack_in_buffer(INITIAL_STACK_TOP_IDX);
616 }
617
618 self.stack_top_idx -= 1;
619 self.stack_bot_idx = min(self.stack_bot_idx, self.stack_top_idx - MIN_STACK_DEPTH);
620 }
621
622 /// Resets the stack in the buffer to a new position, preserving the top 16 elements of the
623 /// stack.
624 ///
625 /// # Preconditions
626 /// - The stack is expected to have exactly 16 elements.
627 #[inline(always)]
628 fn reset_stack_in_buffer(&mut self, new_stack_top_idx: usize) {
629 debug_assert_eq!(self.stack_depth(), MIN_STACK_DEPTH as u32);
630
631 let new_stack_bot_idx = new_stack_top_idx - MIN_STACK_DEPTH;
632
633 // Copy stack to its new position
634 self.stack
635 .copy_within(self.stack_bot_idx..self.stack_top_idx, new_stack_bot_idx);
636
637 // Zero out stack below the new new_stack_bot_idx, since this is where overflow values
638 // come from, and are guaranteed to be ZERO. We don't need to zero out above
639 // `stack_top_idx`, since values there are never read before being written.
640 self.stack[0..new_stack_bot_idx].fill(ZERO);
641
642 // Update indices.
643 self.stack_bot_idx = new_stack_bot_idx;
644 self.stack_top_idx = new_stack_top_idx;
645 }
646}
647
648// EXECUTION OUTPUT
649// ===============================================================================================
650
651/// The output of a program execution, containing the state of the stack, advice provider,
652/// memory, and final precompile transcript at the end of execution.
653#[derive(Debug)]
654pub struct ExecutionOutput {
655 pub stack: StackOutputs,
656 pub advice: AdviceProvider,
657 pub memory: Memory,
658 pub final_precompile_transcript: PrecompileTranscript,
659}
660
661// SYSTEM CALL STATE
662// ===============================================================================================
663
664/// The system-state half of a saved execution context.
665///
666/// Used to keep track of the `(ctx, caller_hash)` pair that needs to be restored upon return from a
667/// `call`, `syscall` or `dyncall`.
668#[derive(Debug)]
669pub(super) struct SystemCallState {
670 pub ctx: ContextId,
671 pub caller_hash: Word,
672}
673
674// NOOP TRACER
675// ================================================================================================
676
677/// A [Tracer] that does nothing.
678pub struct NoopTracer;
679
680impl Tracer for NoopTracer {
681 type Processor = FastProcessor;
682 type Forest = Arc<MastForest>;
683
684 #[inline(always)]
685 fn start_clock_cycle(
686 &mut self,
687 _processor: &FastProcessor,
688 _continuation: Continuation<Arc<MastForest>>,
689 _continuation_stack: &ContinuationStack<Arc<MastForest>>,
690 _current_forest: &Arc<MastForest>,
691 ) {
692 // do nothing
693 }
694
695 #[inline(always)]
696 fn finalize_clock_cycle(
697 &mut self,
698 _processor: &FastProcessor,
699 _op_helper_registers: OperationHelperRegisters,
700 _current_forest: &Arc<MastForest>,
701 ) {
702 // do nothing
703 }
704}