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