#![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 fast::FastProcessor;
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};
use utils::resolve_external_node;
pub use vm_core::{
AssemblyOp, EMPTY_WORD, Felt, Kernel, ONE, Operation, Program, ProgramInfo, QuadExtension,
StackInputs, StackOutputs, Word, ZERO,
chiplets::hasher::Digest,
crypto::merkle::SMT_DEPTH,
debuginfo::{DefaultSourceManager, SourceManager, SourceSpan},
errors::InputError,
mast::{MastForest, MastNode, MastNodeId},
sys_events::SystemEvent,
utils::{DeserializationError, collections::KvMap},
};
use vm_core::{
Decorator, DecoratorIterator, FieldElement, WORD_SIZE,
mast::{
BasicBlockNode, CallNode, DynNode, JoinNode, LoopNode, MastNodeExt, OP_GROUP_SIZE, OpBatch,
SplitNode,
},
};
pub use winter_prover::matrix::ColMatrix;
pub mod fast;
mod operations;
mod system;
use system::System;
pub use system::{ContextId, FMP_MIN, SYSCALL_FMP_MIN};
mod decoder;
use decoder::Decoder;
mod stack;
use stack::Stack;
mod range;
use range::RangeChecker;
mod host;
pub use host::{
DefaultHost, Host, MastForestStore, MemMastForestStore,
advice::{AdviceInputs, AdviceProvider, AdviceSource, MemAdviceProvider, RecAdviceProvider},
};
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, ExecutionError, Ext2InttError};
pub mod utils;
#[cfg(test)]
mod tests;
mod debug;
pub use debug::{AsmOpInfo, VmState, VmStateIterator};
pub mod math {
pub use vm_core::{Felt, FieldElement, StarkField};
pub use winter_prover::math::fft;
}
pub mod crypto {
pub use vm_core::crypto::{
hash::{
Blake3_192, Blake3_256, ElementHasher, Hasher, Rpo256, RpoDigest, Rpx256, RpxDigest,
},
merkle::{
MerkleError, MerklePath, MerkleStore, MerkleTree, NodeIndex, PartialMerkleTree,
SimpleSmt,
},
random::{RandomCoin, RpoRandomCoin, RpxRandomCoin, WinterRandomCoin},
};
}
type QuadFelt = QuadExtension<Felt>;
#[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,
host: &mut impl Host,
options: ExecutionOptions,
source_manager: Arc<dyn SourceManager>,
) -> Result<ExecutionTrace, ExecutionError> {
let mut process = Process::new(program.kernel().clone(), stack_inputs, options)
.with_source_manager(source_manager);
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,
host: &mut impl Host,
source_manager: Arc<dyn SourceManager>,
) -> VmStateIterator {
let mut process = Process::new_debug(program.kernel().clone(), stack_inputs)
.with_source_manager(source_manager);
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 {
system: System,
decoder: Decoder,
stack: Stack,
range: RangeChecker,
chiplets: Chiplets,
max_cycles: u32,
enable_tracing: bool,
source_manager: Arc<dyn SourceManager>,
}
#[cfg(any(test, feature = "testing"))]
pub struct Process {
pub system: System,
pub decoder: Decoder,
pub stack: Stack,
pub range: RangeChecker,
pub chiplets: Chiplets,
pub max_cycles: u32,
pub enable_tracing: bool,
pub source_manager: Arc<dyn SourceManager>,
}
impl Process {
pub fn new(
kernel: Kernel,
stack_inputs: StackInputs,
execution_options: ExecutionOptions,
) -> Self {
Self::initialize(kernel, stack_inputs, execution_options)
}
pub fn new_debug(kernel: Kernel, stack_inputs: StackInputs) -> Self {
Self::initialize(
kernel,
stack_inputs,
ExecutionOptions::default().with_tracing().with_debugging(true),
)
}
fn initialize(kernel: Kernel, stack: StackInputs, execution_options: ExecutionOptions) -> Self {
let in_debug_mode = execution_options.enable_debugging();
let source_manager = Arc::new(DefaultSourceManager::default());
Self {
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(),
source_manager,
}
}
pub fn with_source_manager(mut self, source_manager: Arc<dyn SourceManager>) -> Self {
self.source_manager = source_manager;
self
}
pub fn execute(
&mut self,
program: &Program,
host: &mut impl Host,
) -> Result<StackOutputs, ExecutionError> {
if self.system.clk() != 0 {
return Err(ExecutionError::ProgramAlreadyExecuted);
}
for (digest, values) in program.mast_forest().advice_map().iter() {
if let Some(stored_values) = host.advice_provider().get_mapped_values(digest) {
if stored_values != values {
return Err(ExecutionError::AdviceMapKeyAlreadyPresent {
key: digest.into(),
prev_values: stored_values.to_vec(),
new_values: values.clone(),
});
}
} else {
host.advice_provider_mut().insert_into_map(digest.into(), values.clone());
}
}
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 Host,
) -> 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 = ErrorContext::new(program, node, self.source_manager.clone());
add_error_ctx_to_external_error(
self.execute_call_node(node, program, host),
err_ctx,
)?
},
MastNode::Dyn(node) => {
let err_ctx = ErrorContext::new(program, node, self.source_manager.clone());
add_error_ctx_to_external_error(
self.execute_dyn_node(node, program, host),
err_ctx,
)?
},
MastNode::External(external_node) => {
let (root_id, mast_forest) = 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 Host,
) -> 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 Host,
) -> 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 = ErrorContext::new(program, node, self.source_manager.clone());
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 Host,
) -> 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 = ErrorContext::new(program, node, self.source_manager.clone());
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 = ErrorContext::new(program, node, self.source_manager.clone());
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 Host,
) -> Result<(), ExecutionError> {
if self.system.in_syscall() {
let instruction = if call_node.is_syscall() { "syscall" } else { "call" };
return Err(ExecutionError::CallInSyscall(instruction));
}
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 = ErrorContext::new(program, call_node, self.source_manager.clone());
self.chiplets.kernel_rom.access_proc(callee.digest(), &err_ctx)?;
}
let err_ctx = ErrorContext::new(program, call_node, self.source_manager.clone());
self.start_call_node(call_node, program, host)?;
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 Host,
) -> Result<(), ExecutionError> {
if node.is_dyncall() && self.system.in_syscall() {
return Err(ExecutionError::CallInSyscall("dyncall"));
}
let error_ctx = ErrorContext::new(program, node, self.source_manager.clone());
let callee_hash = if node.is_dyncall() {
self.start_dyncall_node(node, &error_ctx)?
} else {
self.start_dyn_node(node, program, host, &error_ctx)?
};
match program.find_procedure_root(callee_hash.into()) {
Some(callee_id) => self.execute_mast_node(callee_id, program, host)?,
None => {
let mast_forest = host.get_mast_forest(&callee_hash.into()).ok_or_else(|| {
ExecutionError::dynamic_node_not_found(callee_hash.into(), &error_ctx)
})?;
let root_id = mast_forest.find_procedure_root(callee_hash.into()).ok_or(
ExecutionError::malfored_mast_forest_in_host(
callee_hash.into(),
&ErrorContext::default(),
),
)?;
self.execute_mast_node(root_id, &mast_forest, host)?
},
}
if node.is_dyncall() {
self.end_dyncall_node(node, program, host, &error_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 Host,
) -> Result<(), ExecutionError> {
self.start_basic_block_node(basic_block, program, host)?;
let mut op_offset = 0;
let mut decorator_ids = basic_block.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 DecoratorIterator,
op_offset: usize,
program: &MastForest,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
let op_counts = batch.op_counts();
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 error_ctx = ErrorContext::new_with_op_idx(
program,
basic_block,
self.source_manager.clone(),
i + op_offset,
);
self.decoder.execute_user_op(op, op_idx);
self.execute_op_with_error_ctx(op, program, host, &error_ctx)?;
let has_imm = op.imm_value().is_some();
if has_imm {
next_group_idx += 1;
}
if op_idx == op_counts[group_idx] - 1 {
if has_imm {
debug_assert!(op_idx < OP_GROUP_SIZE - 1, "invalid op index");
self.decoder.execute_user_op(Operation::Noop, op_idx + 1);
self.execute_op(Operation::Noop, program, host)?;
}
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;
}
}
for group_idx in group_idx..num_batch_groups {
self.decoder.execute_user_op(Operation::Noop, 0);
self.execute_op(Operation::Noop, program, host)?;
if group_idx < num_batch_groups - 1 {
self.decoder.start_op_group(ZERO);
}
}
Ok(())
}
fn execute_decorator(
&mut self,
decorator: &Decorator,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
match decorator {
Decorator::Debug(options) => {
if self.decoder.in_debug_mode() {
host.on_debug(self.into(), 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 {
host.on_trace(self.into(), *id)?;
}
},
};
Ok(())
}
pub const fn kernel(&self) -> &Kernel {
self.chiplets.kernel_rom.kernel()
}
pub fn into_parts(self) -> (System, Decoder, Stack, RangeChecker, Chiplets) {
(self.system, self.decoder, self.stack, self.range, self.chiplets)
}
}
#[derive(Debug, Clone, Copy)]
pub struct SlowProcessState<'a> {
system: &'a System,
stack: &'a Stack,
chiplets: &'a Chiplets,
}
#[derive(Debug, Clone, Copy)]
pub struct FastProcessState<'a> {
processor: &'a FastProcessor,
op_idx: usize,
}
#[derive(Debug, Clone, Copy)]
pub enum ProcessState<'a> {
Slow(SlowProcessState<'a>),
Fast(FastProcessState<'a>),
}
impl<'a> ProcessState<'a> {
pub fn new_fast(processor: &'a FastProcessor, op_idx: usize) -> Self {
Self::Fast(FastProcessState { processor, op_idx })
}
#[inline(always)]
pub fn clk(&self) -> RowIndex {
match self {
ProcessState::Slow(state) => state.system.clk(),
ProcessState::Fast(state) => state.processor.clk + state.op_idx,
}
}
#[inline(always)]
pub fn ctx(&self) -> ContextId {
match self {
ProcessState::Slow(state) => state.system.ctx(),
ProcessState::Fast(state) => state.processor.ctx,
}
}
#[inline(always)]
pub fn fmp(&self) -> u64 {
match self {
ProcessState::Slow(state) => state.system.fmp().as_int(),
ProcessState::Fast(state) => state.processor.fmp.as_int(),
}
}
#[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),
}
}
#[inline(always)]
pub fn get_stack_word(&self, word_idx: usize) -> Word {
match self {
ProcessState::Slow(state) => state.stack.get_word(word_idx),
ProcessState::Fast(state) => state.processor.stack_get_word(word_idx * WORD_SIZE),
}
}
#[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(),
}
}
#[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),
}
}
#[inline(always)]
pub fn get_mem_word(&self, ctx: ContextId, addr: u32) -> Result<Option<Word>, ExecutionError> {
match self {
ProcessState::Slow(state) => {
state.chiplets.memory.get_word(ctx, addr).map_err(ExecutionError::MemoryError)
},
ProcessState::Fast(state) => {
Ok(state.processor.memory.read_word_impl(ctx, addr, None)?.copied())
},
}
}
#[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),
}
}
}
impl<'a> From<&'a Process> for ProcessState<'a> {
fn from(process: &'a Process) -> Self {
Self::Slow(SlowProcessState {
system: &process.system,
stack: &process.stack,
chiplets: &process.chiplets,
})
}
}
impl<'a> From<&'a mut Process> for ProcessState<'a> {
fn from(process: &'a mut Process) -> Self {
Self::Slow(SlowProcessState {
system: &process.system,
stack: &process.stack,
chiplets: &process.chiplets,
})
}
}
fn add_error_ctx_to_external_error(
result: Result<(), ExecutionError>,
err_ctx: ErrorContext<impl MastNodeExt>,
) -> 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)
},
},
}
}