#![no_std]
#[macro_use]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
use alloc::{sync::Arc, vec::Vec};
use core::fmt::{Display, LowerHex};
use miden_air::trace::{
CHIPLETS_WIDTH, DECODER_TRACE_WIDTH, MIN_TRACE_LEN, RANGE_CHECK_TRACE_WIDTH, STACK_TRACE_WIDTH,
SYS_TRACE_WIDTH,
};
pub use miden_air::{ExecutionOptions, ExecutionOptionsError, RowIndex};
pub use miden_core::{
AssemblyOp, EMPTY_WORD, Felt, Kernel, ONE, Operation, Program, ProgramInfo, QuadExtension,
StackInputs, StackOutputs, WORD_SIZE, Word, ZERO,
crypto::merkle::SMT_DEPTH,
errors::InputError,
mast::{MastForest, MastNode, MastNodeExt, MastNodeId},
precompile::{PrecompileRequest, PrecompileTranscriptState},
sys_events::SystemEvent,
utils::DeserializationError,
};
use miden_core::{
Decorator, FieldElement,
mast::{
BasicBlockNode, CallNode, DecoratorOpLinkIterator, DynNode, ExternalNode, JoinNode,
LoopNode, OpBatch, SplitNode,
},
};
use miden_debug_types::SourceSpan;
pub use winter_prover::matrix::ColMatrix;
pub(crate) mod continuation_stack;
pub mod fast;
use fast::FastProcessState;
pub mod parallel;
pub(crate) mod processor;
mod operations;
mod system;
pub use system::ContextId;
use system::System;
#[cfg(test)]
mod test_utils;
pub(crate) mod decoder;
use decoder::Decoder;
mod stack;
use stack::Stack;
mod range;
use range::RangeChecker;
mod host;
pub use host::{
AdviceMutation, AsyncHost, BaseHost, FutureMaybeSend, MastForestStore, MemMastForestStore,
SyncHost,
advice::{AdviceError, AdviceInputs, AdviceProvider},
debug::DefaultDebugHandler,
default::{DefaultHost, HostLibrary},
handlers::{DebugHandler, EventError, EventHandler, EventHandlerRegistry, NoopEventHandler},
};
mod chiplets;
use chiplets::Chiplets;
pub use chiplets::MemoryError;
mod trace;
use trace::TraceFragment;
pub use trace::{ChipletsLengths, ExecutionTrace, NUM_RAND_ROWS, TraceLenSummary};
mod errors;
pub use errors::{ErrorContext, ErrorContextImpl, ExecutionError};
pub mod utils;
#[cfg(all(test, not(feature = "no_err_ctx")))]
mod tests;
mod debug;
pub use debug::{AsmOpInfo, VmState, VmStateIterator};
pub mod math {
pub use miden_core::{Felt, FieldElement, StarkField};
pub use winter_prover::math::fft;
}
pub mod crypto {
pub use miden_core::crypto::{
hash::{Blake3_192, Blake3_256, ElementHasher, Hasher, Poseidon2, Rpo256, Rpx256},
merkle::{
MerkleError, MerklePath, MerkleStore, MerkleTree, NodeIndex, PartialMerkleTree,
SimpleSmt,
},
random::{RandomCoin, RpoRandomCoin, RpxRandomCoin, WinterRandomCoin},
};
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct MemoryAddress(u32);
impl From<u32> for MemoryAddress {
fn from(addr: u32) -> Self {
MemoryAddress(addr)
}
}
impl From<MemoryAddress> for u32 {
fn from(value: MemoryAddress) -> Self {
value.0
}
}
impl Display for MemoryAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
Display::fmt(&self.0, f)
}
}
impl LowerHex for MemoryAddress {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
LowerHex::fmt(&self.0, f)
}
}
impl core::ops::Add<MemoryAddress> for MemoryAddress {
type Output = Self;
fn add(self, rhs: MemoryAddress) -> Self::Output {
MemoryAddress(self.0 + rhs.0)
}
}
impl core::ops::Add<u32> for MemoryAddress {
type Output = Self;
fn add(self, rhs: u32) -> Self::Output {
MemoryAddress(self.0 + rhs)
}
}
type SysTrace = [Vec<Felt>; SYS_TRACE_WIDTH];
pub struct DecoderTrace {
trace: [Vec<Felt>; DECODER_TRACE_WIDTH],
aux_builder: decoder::AuxTraceBuilder,
}
pub struct StackTrace {
trace: [Vec<Felt>; STACK_TRACE_WIDTH],
}
pub struct RangeCheckTrace {
trace: [Vec<Felt>; RANGE_CHECK_TRACE_WIDTH],
aux_builder: range::AuxTraceBuilder,
}
pub struct ChipletsTrace {
trace: [Vec<Felt>; CHIPLETS_WIDTH],
aux_builder: chiplets::AuxTraceBuilder,
}
#[tracing::instrument("execute_program", skip_all)]
pub fn execute(
program: &Program,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
host: &mut impl SyncHost,
options: ExecutionOptions,
) -> Result<ExecutionTrace, ExecutionError> {
let mut process = Process::new(program.kernel().clone(), stack_inputs, advice_inputs, options);
let stack_outputs = process.execute(program, host)?;
let trace = ExecutionTrace::new(process, stack_outputs);
assert_eq!(&program.hash(), trace.program_hash(), "inconsistent program hash");
Ok(trace)
}
pub fn execute_iter(
program: &Program,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
host: &mut impl SyncHost,
) -> VmStateIterator {
let mut process = Process::new_debug(program.kernel().clone(), stack_inputs, advice_inputs);
let result = process.execute(program, host);
if result.is_ok() {
assert_eq!(
program.hash(),
process.decoder.program_hash().into(),
"inconsistent program hash"
);
}
VmStateIterator::new(process, result)
}
#[cfg(not(any(test, feature = "testing")))]
pub struct Process {
advice: AdviceProvider,
system: System,
decoder: Decoder,
stack: Stack,
range: RangeChecker,
chiplets: Chiplets,
max_cycles: u32,
enable_tracing: bool,
pc_transcript_state: PrecompileTranscriptState,
}
#[cfg(any(test, feature = "testing"))]
pub struct Process {
pub advice: AdviceProvider,
pub system: System,
pub decoder: Decoder,
pub stack: Stack,
pub range: RangeChecker,
pub chiplets: Chiplets,
pub max_cycles: u32,
pub enable_tracing: bool,
pub pc_transcript_state: PrecompileTranscriptState,
}
impl Process {
pub fn new(
kernel: Kernel,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
execution_options: ExecutionOptions,
) -> Self {
Self::initialize(kernel, stack_inputs, advice_inputs, execution_options)
}
pub fn new_debug(
kernel: Kernel,
stack_inputs: StackInputs,
advice_inputs: AdviceInputs,
) -> Self {
Self::initialize(
kernel,
stack_inputs,
advice_inputs,
ExecutionOptions::default().with_tracing().with_debugging(true),
)
}
fn initialize(
kernel: Kernel,
stack: StackInputs,
advice_inputs: AdviceInputs,
execution_options: ExecutionOptions,
) -> Self {
let in_debug_mode = execution_options.enable_debugging();
Self {
advice: advice_inputs.into(),
system: System::new(execution_options.expected_cycles() as usize),
decoder: Decoder::new(in_debug_mode),
stack: Stack::new(&stack, execution_options.expected_cycles() as usize, in_debug_mode),
range: RangeChecker::new(),
chiplets: Chiplets::new(kernel),
max_cycles: execution_options.max_cycles(),
enable_tracing: execution_options.enable_tracing(),
pc_transcript_state: PrecompileTranscriptState::default(),
}
}
pub fn execute(
&mut self,
program: &Program,
host: &mut impl SyncHost,
) -> Result<StackOutputs, ExecutionError> {
if self.system.clk() != 0 {
return Err(ExecutionError::ProgramAlreadyExecuted);
}
self.advice
.extend_map(program.mast_forest().advice_map())
.map_err(|err| ExecutionError::advice_error(err, RowIndex::from(0), &()))?;
self.execute_mast_node(program.entrypoint(), &program.mast_forest().clone(), host)?;
self.stack.build_stack_outputs()
}
fn execute_mast_node(
&mut self,
node_id: MastNodeId,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
let node = program
.get_node_by_id(node_id)
.ok_or(ExecutionError::MastNodeNotFoundInForest { node_id })?;
for &decorator_id in node.before_enter() {
self.execute_decorator(&program[decorator_id], host)?;
}
match node {
MastNode::Block(node) => self.execute_basic_block_node(node, program, host)?,
MastNode::Join(node) => self.execute_join_node(node, program, host)?,
MastNode::Split(node) => self.execute_split_node(node, program, host)?,
MastNode::Loop(node) => self.execute_loop_node(node, program, host)?,
MastNode::Call(node) => {
let err_ctx = err_ctx!(program, node, host);
add_error_ctx_to_external_error(
self.execute_call_node(node, program, host),
err_ctx,
)?
},
MastNode::Dyn(node) => {
let err_ctx = err_ctx!(program, node, host);
add_error_ctx_to_external_error(
self.execute_dyn_node(node, program, host),
err_ctx,
)?
},
MastNode::External(external_node) => {
let (root_id, mast_forest) = self.resolve_external_node(external_node, host)?;
self.execute_mast_node(root_id, &mast_forest, host)?;
},
}
for &decorator_id in node.after_exit() {
self.execute_decorator(&program[decorator_id], host)?;
}
Ok(())
}
#[inline(always)]
fn execute_join_node(
&mut self,
node: &JoinNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
self.start_join_node(node, program, host)?;
self.execute_mast_node(node.first(), program, host)?;
self.execute_mast_node(node.second(), program, host)?;
self.end_join_node(node, program, host)
}
#[inline(always)]
fn execute_split_node(
&mut self,
node: &SplitNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
let condition = self.start_split_node(node, program, host)?;
if condition == ONE {
self.execute_mast_node(node.on_true(), program, host)?;
} else if condition == ZERO {
self.execute_mast_node(node.on_false(), program, host)?;
} else {
let err_ctx = err_ctx!(program, node, host);
return Err(ExecutionError::not_binary_value_if(condition, &err_ctx));
}
self.end_split_node(node, program, host)
}
#[inline(always)]
fn execute_loop_node(
&mut self,
node: &LoopNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
let condition = self.start_loop_node(node, program, host)?;
if condition == ONE {
self.execute_mast_node(node.body(), program, host)?;
while self.stack.peek() == ONE {
self.decoder.repeat();
self.execute_op(Operation::Drop, program, host)?;
self.execute_mast_node(node.body(), program, host)?;
}
if self.stack.peek() != ZERO {
let err_ctx = err_ctx!(program, node, host);
return Err(ExecutionError::not_binary_value_loop(self.stack.peek(), &err_ctx));
}
self.end_loop_node(node, true, program, host)
} else if condition == ZERO {
self.end_loop_node(node, false, program, host)
} else {
let err_ctx = err_ctx!(program, node, host);
Err(ExecutionError::not_binary_value_loop(condition, &err_ctx))
}
}
#[inline(always)]
fn execute_call_node(
&mut self,
call_node: &CallNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
if call_node.is_syscall() {
let callee = program.get_node_by_id(call_node.callee()).ok_or_else(|| {
ExecutionError::MastNodeNotFoundInForest { node_id: call_node.callee() }
})?;
let err_ctx = err_ctx!(program, call_node, host);
self.chiplets.kernel_rom.access_proc(callee.digest(), &err_ctx)?;
}
let err_ctx = err_ctx!(program, call_node, host);
self.start_call_node(call_node, program, host, &err_ctx)?;
self.execute_mast_node(call_node.callee(), program, host)?;
self.end_call_node(call_node, program, host, &err_ctx)
}
#[inline(always)]
fn execute_dyn_node(
&mut self,
node: &DynNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
let err_ctx = err_ctx!(program, node, host);
let callee_hash = if node.is_dyncall() {
self.start_dyncall_node(node, &err_ctx)?
} else {
self.start_dyn_node(node, program, host, &err_ctx)?
};
match program.find_procedure_root(callee_hash) {
Some(callee_id) => self.execute_mast_node(callee_id, program, host)?,
None => {
let mast_forest = host
.get_mast_forest(&callee_hash)
.ok_or_else(|| ExecutionError::dynamic_node_not_found(callee_hash, &err_ctx))?;
let root_id = mast_forest
.find_procedure_root(callee_hash)
.ok_or(ExecutionError::malfored_mast_forest_in_host(callee_hash, &()))?;
self.advice
.extend_map(mast_forest.advice_map())
.map_err(|err| ExecutionError::advice_error(err, self.system.clk(), &()))?;
self.execute_mast_node(root_id, &mast_forest, host)?
},
}
if node.is_dyncall() {
self.end_dyncall_node(node, program, host, &err_ctx)
} else {
self.end_dyn_node(node, program, host)
}
}
#[inline(always)]
fn execute_basic_block_node(
&mut self,
basic_block: &BasicBlockNode,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
self.start_basic_block_node(basic_block, program, host)?;
let mut op_offset = 0;
let mut decorator_ids = basic_block.indexed_decorator_iter();
self.execute_op_batch(
basic_block,
&basic_block.op_batches()[0],
&mut decorator_ids,
op_offset,
program,
host,
)?;
op_offset += basic_block.op_batches()[0].ops().len();
for op_batch in basic_block.op_batches().iter().skip(1) {
self.respan(op_batch);
self.execute_op(Operation::Noop, program, host)?;
self.execute_op_batch(
basic_block,
op_batch,
&mut decorator_ids,
op_offset,
program,
host,
)?;
op_offset += op_batch.ops().len();
}
self.end_basic_block_node(basic_block, program, host)?;
for (_, decorator_id) in decorator_ids {
let decorator = program
.get_decorator_by_id(decorator_id)
.ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
self.execute_decorator(decorator, host)?;
}
Ok(())
}
#[inline(always)]
fn execute_op_batch(
&mut self,
basic_block: &BasicBlockNode,
batch: &OpBatch,
decorators: &mut DecoratorOpLinkIterator,
op_offset: usize,
program: &MastForest,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
let end_indices = batch.end_indices();
let mut op_idx = 0;
let mut group_idx = 0;
let mut next_group_idx = 1;
let num_batch_groups = batch.num_groups().next_power_of_two();
for (i, &op) in batch.ops().iter().enumerate() {
while let Some((_, decorator_id)) = decorators.next_filtered(i + op_offset) {
let decorator = program
.get_decorator_by_id(decorator_id)
.ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
self.execute_decorator(decorator, host)?;
}
let err_ctx = err_ctx!(program, basic_block, host, i + op_offset);
self.decoder.execute_user_op(op, op_idx);
self.execute_op_with_error_ctx(op, program, host, &err_ctx)?;
let has_imm = op.imm_value().is_some();
if has_imm {
next_group_idx += 1;
}
if i + 1 == end_indices[group_idx] {
group_idx = next_group_idx;
next_group_idx += 1;
op_idx = 0;
if group_idx < num_batch_groups {
self.decoder.start_op_group(batch.groups()[group_idx]);
}
} else {
op_idx += 1;
}
}
Ok(())
}
fn execute_decorator(
&mut self,
decorator: &Decorator,
host: &mut impl SyncHost,
) -> Result<(), ExecutionError> {
match decorator {
Decorator::Debug(options) => {
if self.decoder.in_debug_mode() {
let process = &mut self.state();
host.on_debug(process, options)?;
}
},
Decorator::AsmOp(assembly_op) => {
if self.decoder.in_debug_mode() {
self.decoder.append_asmop(self.system.clk(), assembly_op.clone());
}
},
Decorator::Trace(id) => {
if self.enable_tracing {
let process = &mut self.state();
host.on_trace(process, *id)?;
}
},
};
Ok(())
}
fn resolve_external_node(
&mut self,
external_node: &ExternalNode,
host: &impl SyncHost,
) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError> {
let node_digest = external_node.digest();
let mast_forest = host
.get_mast_forest(&node_digest)
.ok_or(ExecutionError::no_mast_forest_with_procedure(node_digest, &()))?;
let root_id = mast_forest
.find_procedure_root(node_digest)
.ok_or(ExecutionError::malfored_mast_forest_in_host(node_digest, &()))?;
if mast_forest[root_id].is_external() {
return Err(ExecutionError::CircularExternalNode(node_digest));
}
self.advice
.extend_map(mast_forest.advice_map())
.map_err(|err| ExecutionError::advice_error(err, self.system.clk(), &()))?;
Ok((root_id, mast_forest))
}
pub const fn kernel(&self) -> &Kernel {
self.chiplets.kernel_rom.kernel()
}
pub fn into_parts(
self,
) -> (System, Decoder, Stack, RangeChecker, Chiplets, PrecompileTranscriptState) {
(
self.system,
self.decoder,
self.stack,
self.range,
self.chiplets,
self.pc_transcript_state,
)
}
}
#[derive(Debug)]
pub struct SlowProcessState<'a> {
advice: &'a mut AdviceProvider,
system: &'a System,
stack: &'a Stack,
chiplets: &'a Chiplets,
}
#[derive(Debug)]
pub enum ProcessState<'a> {
Slow(SlowProcessState<'a>),
Fast(FastProcessState<'a>),
Noop(()),
}
impl Process {
#[inline(always)]
pub fn state(&mut self) -> ProcessState<'_> {
ProcessState::Slow(SlowProcessState {
advice: &mut self.advice,
system: &self.system,
stack: &self.stack,
chiplets: &self.chiplets,
})
}
}
impl<'a> ProcessState<'a> {
#[inline(always)]
pub fn advice_provider(&self) -> &AdviceProvider {
match self {
ProcessState::Slow(state) => state.advice,
ProcessState::Fast(state) => &state.processor.advice,
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn advice_provider_mut(&mut self) -> &mut AdviceProvider {
match self {
ProcessState::Slow(state) => state.advice,
ProcessState::Fast(state) => &mut state.processor.advice,
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn clk(&self) -> RowIndex {
match self {
ProcessState::Slow(state) => state.system.clk(),
ProcessState::Fast(state) => state.processor.clk,
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn ctx(&self) -> ContextId {
match self {
ProcessState::Slow(state) => state.system.ctx(),
ProcessState::Fast(state) => state.processor.ctx,
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn get_stack_item(&self, pos: usize) -> Felt {
match self {
ProcessState::Slow(state) => state.stack.get(pos),
ProcessState::Fast(state) => state.processor.stack_get(pos),
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn get_stack_word_be(&self, start_idx: usize) -> Word {
match self {
ProcessState::Slow(state) => state.stack.get_word(start_idx),
ProcessState::Fast(state) => state.processor.stack_get_word(start_idx),
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn get_stack_word_le(&self, start_idx: usize) -> Word {
let mut word = self.get_stack_word_be(start_idx);
word.reverse();
word
}
#[deprecated(
since = "0.19.0",
note = "Use `get_stack_word_be()` or `get_stack_word_le()` to make endianness explicit"
)]
#[inline(always)]
pub fn get_stack_word(&self, start_idx: usize) -> Word {
self.get_stack_word_be(start_idx)
}
#[inline(always)]
pub fn get_stack_state(&self) -> Vec<Felt> {
match self {
ProcessState::Slow(state) => state.stack.get_state_at(state.system.clk()),
ProcessState::Fast(state) => state.processor.stack().iter().rev().copied().collect(),
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn get_mem_value(&self, ctx: ContextId, addr: u32) -> Option<Felt> {
match self {
ProcessState::Slow(state) => state.chiplets.memory.get_value(ctx, addr),
ProcessState::Fast(state) => state.processor.memory.read_element_impl(ctx, addr),
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
#[inline(always)]
pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result<Option<Word>, MemoryError> {
match self {
ProcessState::Slow(state) => state.chiplets.memory.get_word(ctx, addr),
ProcessState::Fast(state) => {
state.processor.memory.read_word_impl(ctx, addr, None, &())
},
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
pub fn get_mem_addr_range(
&self,
start_idx: usize,
end_idx: usize,
) -> Result<core::ops::Range<u32>, MemoryError> {
let start_addr = self.get_stack_item(start_idx).as_int();
let end_addr = self.get_stack_item(end_idx).as_int();
if start_addr > u32::MAX as u64 {
return Err(MemoryError::address_out_of_bounds(start_addr, &()));
}
if end_addr > u32::MAX as u64 {
return Err(MemoryError::address_out_of_bounds(end_addr, &()));
}
if start_addr > end_addr {
return Err(MemoryError::InvalidMemoryRange { start_addr, end_addr });
}
Ok(start_addr as u32..end_addr as u32)
}
#[inline(always)]
pub fn get_mem_state(&self, ctx: ContextId) -> Vec<(MemoryAddress, Felt)> {
match self {
ProcessState::Slow(state) => {
state.chiplets.memory.get_state_at(ctx, state.system.clk())
},
ProcessState::Fast(state) => state.processor.memory.get_memory_state(ctx),
ProcessState::Noop(()) => panic!("attempted to access Noop process state"),
}
}
}
impl<'a> From<&'a mut Process> for ProcessState<'a> {
fn from(process: &'a mut Process) -> Self {
process.state()
}
}
pub(crate) fn add_error_ctx_to_external_error(
result: Result<(), ExecutionError>,
err_ctx: impl ErrorContext,
) -> Result<(), ExecutionError> {
match result {
Ok(_) => Ok(()),
Err(err) => match err {
ExecutionError::NoMastForestWithProcedure { label, source_file: _, root_digest }
| ExecutionError::MalformedMastForestInHost { label, source_file: _, root_digest } => {
if label == SourceSpan::UNKNOWN {
let err_with_ctx =
ExecutionError::no_mast_forest_with_procedure(root_digest, &err_ctx);
Err(err_with_ctx)
} else {
Err(err)
}
},
_ => {
Err(err)
},
},
}
}