Skip to main content

miden_processor/
lib.rs

1#![no_std]
2// Trace tests intentionally use index-based `for i in a..b` over column slices; clippy's iterator
3// suggestion is noisier than helpful there.
4#![cfg_attr(test, allow(clippy::needless_range_loop))]
5
6#[macro_use]
7extern crate alloc;
8
9#[cfg(feature = "std")]
10extern crate std;
11
12use alloc::vec::Vec;
13use core::{
14    fmt::{self, Display, LowerHex},
15    ops::ControlFlow,
16};
17
18use miden_mast_package::debug_info::DebugSourceNodeId;
19
20mod continuation_stack;
21mod errors;
22mod execution;
23mod execution_options;
24mod fast;
25mod host;
26mod processor;
27mod tracer;
28
29use miden_core::{
30    deferred::{Digest, Node, PrecompileError},
31    mast::ExecutableMastForest,
32};
33
34use crate::{
35    advice::{AdviceInputs, AdviceProvider},
36    continuation_stack::ContinuationStack,
37    errors::{MapExecErr, MapExecErrNoCtx},
38    processor::{Processor, SystemInterface},
39    trace::RowIndex,
40};
41
42#[cfg(any(test, feature = "testing"))]
43mod test_utils;
44#[cfg(any(test, feature = "testing"))]
45pub use test_utils::{ProcessorStateSnapshot, TestHost};
46
47#[cfg(test)]
48mod tests;
49
50// RE-EXPORTS
51// ================================================================================================
52
53pub use continuation_stack::Continuation;
54pub use errors::{
55    AceError, ExecutionError, HostError, MemoryError, PackageSourceDebugContext,
56    advice_error_with_package_source_context, event_error_with_package_source_context,
57    procedure_not_found_with_package_source_context,
58};
59pub use execution_options::{ExecutionOptions, ExecutionOptionsError};
60pub use fast::{BreakReason, ExecutionOutput, FastProcessor, ResumeContext};
61pub use host::{
62    BaseHost, FutureMaybeSend, Host, LoadedMastForest, MastForestStore, MemMastForestStore,
63    SyncHost,
64    debug::{StdoutWriter, format_value, write_interval, write_stack},
65    default::{DefaultHost, HostLibrary},
66};
67pub use miden_core::{
68    EMPTY_WORD, Felt, ONE, WORD_SIZE, Word, ZERO, crypto, field, mast,
69    program::{
70        InputError, KernelDescriptor, MIN_STACK_DEPTH, Program, ProgramInfo, StackInputs,
71        StackOutputs,
72    },
73    serde, utils,
74};
75pub use trace::{TraceBuildInputs, TraceGenerationContext};
76
77pub mod advice {
78    pub use miden_core::advice::{AdviceInputs, AdviceMap, AdviceStack};
79
80    pub use super::host::{
81        AdviceMutation,
82        advice::{AdviceError, AdviceProvider, MAX_ADVICE_STACK_SIZE},
83    };
84}
85
86pub mod event {
87    pub use miden_core::events::*;
88
89    pub use crate::host::handlers::{
90        EventError, EventHandler, EventHandlerRegistry, NoopEventHandler, TraceError, TraceHandler,
91        TraceHandlerRegistry,
92    };
93}
94
95pub mod operation {
96    pub use miden_core::operations::*;
97
98    pub use crate::errors::{BinaryValueErrorContext, OperationError};
99}
100
101pub mod trace;
102
103// EXECUTORS
104// ================================================================================================
105
106/// Executes the provided program against the provided inputs and returns the resulting execution
107/// output.
108///
109/// The `host` parameter is used to provide the external environment to the program being executed,
110/// such as access to the advice provider and libraries that the program depends on.
111///
112/// # Errors
113/// Returns an error if program execution fails for any reason.
114#[tracing::instrument("execute_program", skip_all)]
115pub async fn execute(
116    program: &Program,
117    stack_inputs: StackInputs,
118    advice_inputs: AdviceInputs,
119    host: &mut impl Host,
120    options: ExecutionOptions,
121) -> Result<ExecutionOutput, ExecutionError> {
122    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, options)
123        .map_exec_err_no_ctx()?;
124    processor.execute(program, host).await
125}
126
127/// Synchronous wrapper for the async `execute()` function.
128///
129/// This method is only available on non-wasm32 targets. On wasm32, use the async `execute()`
130/// method directly since wasm32 runs in the browser's event loop.
131///
132/// # Panics
133/// Panics if called from within an existing Tokio runtime. Use the async `execute()` method
134/// instead in async contexts.
135#[cfg(not(target_family = "wasm"))]
136#[tracing::instrument("execute_program_sync", skip_all)]
137pub fn execute_sync(
138    program: &Program,
139    stack_inputs: StackInputs,
140    advice_inputs: AdviceInputs,
141    host: &mut impl SyncHost,
142    options: ExecutionOptions,
143) -> Result<ExecutionOutput, ExecutionError> {
144    let processor = FastProcessor::new_with_options(stack_inputs, advice_inputs, options)
145        .map_exec_err_no_ctx()?;
146    processor.execute_sync(program, host)
147}
148
149// PROCESSOR STATE
150// ===============================================================================================
151
152/// A view into the current state of the processor.
153///
154/// This struct provides read access to the processor's state, including the stack, memory,
155/// advice provider, and execution context information.
156#[derive(Debug)]
157pub struct ProcessorState<'a> {
158    processor: &'a FastProcessor,
159}
160
161impl<'a> ProcessorState<'a> {
162    /// Returns a reference to the advice provider.
163    #[inline(always)]
164    pub fn advice_provider(&self) -> &AdviceProvider {
165        self.processor.advice_provider()
166    }
167
168    /// Returns the execution options.
169    #[inline(always)]
170    pub fn execution_options(&self) -> &ExecutionOptions {
171        self.processor.execution_options()
172    }
173
174    /// Returns the current clock cycle of a process.
175    #[inline(always)]
176    pub fn clock(&self) -> RowIndex {
177        self.processor.clock()
178    }
179
180    /// Returns the current execution context ID.
181    #[inline(always)]
182    pub fn ctx(&self) -> ContextId {
183        self.processor.ctx()
184    }
185
186    /// Returns the value located at the specified position on the stack at the current clock cycle.
187    ///
188    /// This method can access elements beyond the top 16 positions by using the overflow table.
189    #[inline(always)]
190    pub fn get_stack_item(&self, pos: usize) -> Felt {
191        self.processor.stack_get_safe(pos)
192    }
193
194    /// Returns a word starting at the specified element index on the stack.
195    ///
196    /// The word is formed by taking 4 consecutive elements starting from the specified index.
197    /// For example, start_idx=0 creates a word from stack elements 0-3, start_idx=1 creates
198    /// a word from elements 1-4, etc.
199    ///
200    /// Stack element N will be at position 0 of the word, N+1 at position 1, N+2 at position 2,
201    /// and N+3 at position 3. `word[0]` corresponds to the top of the stack.
202    ///
203    /// This method can access elements beyond the top 16 positions by using the overflow table.
204    /// Creating a word does not change the state of the stack.
205    #[inline(always)]
206    pub fn get_stack_word(&self, start_idx: usize) -> Word {
207        self.processor.stack_get_word_safe(start_idx)
208    }
209
210    /// Returns stack state at the current clock cycle. This includes the top 16 items of the
211    /// stack + overflow entries.
212    #[inline(always)]
213    pub fn get_stack_state(&self) -> Vec<Felt> {
214        self.processor.stack().iter().rev().copied().collect()
215    }
216
217    /// Returns the element located at the specified context/address, or None if the address hasn't
218    /// been accessed previously.
219    #[inline(always)]
220    pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option<Felt> {
221        self.processor.memory().read_element_impl(ctx, addr)
222    }
223
224    /// Returns the batch of elements starting at the specified context/address.
225    ///
226    /// # Errors
227    /// - If the address is not word aligned.
228    #[inline(always)]
229    pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result<Option<Word>, MemoryError> {
230        self.processor.memory().read_word_impl(ctx, addr)
231    }
232
233    /// Reads (start_addr, end_addr) tuple from the specified elements of the operand stack (
234    /// without modifying the state of the stack), and verifies that memory range is valid.
235    ///
236    /// The range is half-open `[start, end)`; both `start` and `end` must be `<= u32::MAX`.
237    pub fn get_mem_addr_range(
238        &self,
239        start_idx: usize,
240        end_idx: usize,
241    ) -> Result<core::ops::Range<u32>, MemoryError> {
242        let start_addr = self.get_stack_item(start_idx).as_canonical_u64();
243        let end_addr = self.get_stack_item(end_idx).as_canonical_u64();
244
245        if start_addr > u32::MAX as u64 {
246            return Err(MemoryError::AddressOutOfBounds { addr: start_addr });
247        }
248        if end_addr > u32::MAX as u64 {
249            return Err(MemoryError::AddressOutOfBounds { addr: end_addr });
250        }
251
252        if start_addr > end_addr {
253            return Err(MemoryError::InvalidMemoryRange { start_addr, end_addr });
254        }
255
256        Ok(start_addr as u32..end_addr as u32)
257    }
258
259    /// Returns the entire memory state for the specified execution context at the current clock
260    /// cycle.
261    ///
262    /// The state is returned as a vector of (address, value) tuples, and includes addresses which
263    /// have been accessed at least once.
264    #[inline(always)]
265    pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(MemoryAddress, Felt)> {
266        self.processor.memory().get_memory_state(ctx)
267    }
268
269    /// Returns the already-memoized canonical deferred digest for `digest`, if present.
270    ///
271    /// This is a read-only lookup: it does not evaluate `digest`, register helper nodes, or mutate
272    /// deferred state.
273    #[inline(always)]
274    pub fn get_canonical_deferred_digest(&self, digest: Digest) -> Option<Digest> {
275        self.processor.deferred_state().get_canonical_digest(digest)
276    }
277
278    /// Returns the already-memoized canonical deferred node for `digest`, if present.
279    ///
280    /// This is a read-only lookup and returns only canonical results that were already memoized in
281    /// deferred state; it never evaluates or mutates deferred state.
282    #[inline(always)]
283    pub fn get_canonical_deferred_node(&self, digest: Digest) -> Option<(Digest, &Node)> {
284        self.processor.deferred_state().get_canonical_node(digest)
285    }
286
287    /// Returns the already-memoized canonical deferred node for `digest`.
288    ///
289    /// This is a read-only lookup and never evaluates or mutates deferred state.
290    ///
291    /// # Errors
292    /// Returns [`PrecompileError::MissingNode`] if no memoized canonical node is available.
293    #[inline(always)]
294    pub fn require_canonical_deferred_node(
295        &self,
296        digest: Digest,
297    ) -> Result<(Digest, &Node), PrecompileError> {
298        self.processor.deferred_state().require_canonical_node(digest)
299    }
300}
301
302// STOPPER
303// ===============================================================================================
304
305/// A trait for types that determine whether execution should be stopped after each clock cycle.
306///
307/// This allows for flexible control over the execution process, enabling features such as stepping
308/// through execution (see [`crate::FastProcessor::step`]) or limiting execution to a certain number
309/// of clock cycles (used in parallel trace generation to fill the trace for a predetermined trace
310/// fragment).
311pub trait Stopper {
312    type Processor;
313
314    /// The forest representation used by the executor this stopper is paired with.
315    ///
316    /// For live execution this is `Arc<MastForest>`; for replay it is `Arc<SparseMastForest>`.
317    type Forest: ExecutableMastForest + Clone;
318
319    /// Determines whether execution should be stopped at the end of each clock cycle.
320    ///
321    /// This method is guaranteed to be called at the end of each clock cycle, *after* the processor
322    /// state has been updated to reflect the effects of the operations executed during that cycle
323    /// (*including* the processor clock). Hence, a processor clock of `N` indicates that clock
324    /// cycle `N - 1` has just completed.
325    ///
326    /// The `continuation_after_stop` is provided in cases where simply resuming execution from the
327    /// top of the continuation stack is not sufficient to continue execution correctly. For
328    /// example, when stopping execution in the middle of a basic block, we need to provide a
329    /// `ResumeBasicBlock` continuation to ensure that execution resumes at the correct operation
330    /// within the basic block (i.e. the operation right after the one that was last executed before
331    /// being stopped). No continuation is provided in case of error, since it is expected that
332    /// execution will not be resumed.
333    fn should_stop(
334        &self,
335        processor: &Self::Processor,
336        continuation_stack: &ContinuationStack<Self::Forest>,
337        continuation_after_stop: impl FnOnce() -> Option<(
338            Continuation<Self::Forest>,
339            Option<DebugSourceNodeId>,
340        )>,
341    ) -> ControlFlow<BreakReason<Self::Forest>>;
342}
343
344// EXECUTION CONTEXT
345// ================================================================================================
346
347/// Represents the ID of an execution context
348#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
349pub struct ContextId(u32);
350
351impl ContextId {
352    /// Returns the root context ID
353    pub fn root() -> Self {
354        Self(0)
355    }
356
357    /// Returns true if the context ID represents the root context
358    pub fn is_root(&self) -> bool {
359        self.0 == 0
360    }
361}
362
363impl From<RowIndex> for ContextId {
364    fn from(value: RowIndex) -> Self {
365        Self(value.as_u32())
366    }
367}
368
369impl From<u32> for ContextId {
370    fn from(value: u32) -> Self {
371        Self(value)
372    }
373}
374
375impl From<ContextId> for u32 {
376    fn from(context_id: ContextId) -> Self {
377        context_id.0
378    }
379}
380
381impl From<ContextId> for u64 {
382    fn from(context_id: ContextId) -> Self {
383        context_id.0.into()
384    }
385}
386
387impl From<ContextId> for Felt {
388    fn from(context_id: ContextId) -> Self {
389        Felt::from_u32(context_id.0)
390    }
391}
392
393impl Display for ContextId {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        write!(f, "{}", self.0)
396    }
397}
398
399// MEMORY ADDRESS
400// ================================================================================================
401
402#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
403pub struct MemoryAddress(u32);
404
405impl From<u32> for MemoryAddress {
406    fn from(addr: u32) -> Self {
407        MemoryAddress(addr)
408    }
409}
410
411impl From<MemoryAddress> for u32 {
412    fn from(value: MemoryAddress) -> Self {
413        value.0
414    }
415}
416
417impl Display for MemoryAddress {
418    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419        Display::fmt(&self.0, f)
420    }
421}
422
423impl LowerHex for MemoryAddress {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        LowerHex::fmt(&self.0, f)
426    }
427}
428
429impl core::ops::Add<MemoryAddress> for MemoryAddress {
430    type Output = Self;
431
432    fn add(self, rhs: MemoryAddress) -> Self::Output {
433        MemoryAddress(self.0 + rhs.0)
434    }
435}
436
437impl core::ops::Add<u32> for MemoryAddress {
438    type Output = Self;
439
440    fn add(self, rhs: u32) -> Self::Output {
441        MemoryAddress(self.0 + rhs)
442    }
443}
444
445// HELPERS
446// ===============================================================================================
447
448/// Lifts an [`Option<T>`] into a [`ControlFlow`] suitable for the execution loop, mapping `None`
449/// to a break carrying an [`ExecutionError::Internal`] with `err_msg`.
450///
451/// Intended for use with `?` at sites where a `None` represents a violated internal invariant —
452/// most commonly a missing node returned by
453/// [`ExecutableMastForest::get_node_by_id`](miden_core::mast::ExecutableMastForest::get_node_by_id).
454/// For functions returning `ControlFlow<InternalBreakReason<F>>`, chain
455/// `.map_break(InternalBreakReason::from)` before `?`.
456#[track_caller]
457fn option_map_break_reason<F, T>(
458    opt: Option<T>,
459    err_msg: &'static str,
460) -> ControlFlow<BreakReason<F>, T> {
461    match opt {
462        Some(value) => ControlFlow::Continue(value),
463        None => ControlFlow::Break(BreakReason::Err(ExecutionError::Internal(err_msg))),
464    }
465}