use alloc::vec::Vec;
use core::cmp::min;
use memory::Memory;
use miden_air::RowIndex;
use vm_core::{
Decorator, DecoratorIterator, EMPTY_WORD, Felt, Kernel, ONE, Operation, Program, StackOutputs,
WORD_SIZE, Word, ZERO,
mast::{
BasicBlockNode, CallNode, DynNode, ExternalNode, JoinNode, LoopNode, MastForest, MastNode,
MastNodeId, OP_GROUP_SIZE, OpBatch, SplitNode,
},
stack::MIN_STACK_DEPTH,
utils::range,
};
use crate::{
ContextId, ExecutionError, FMP_MIN, Host, ProcessState, SYSCALL_FMP_MIN, chiplets::Ace,
errors::ErrorContext, utils::resolve_external_node,
};
mod memory;
mod circuit_eval;
mod crypto_ops;
mod field_ops;
mod fri_ops;
mod horner_ops;
mod io_ops;
mod stack_ops;
mod sys_ops;
mod u32_ops;
#[cfg(test)]
mod tests;
const STACK_BUFFER_SIZE: usize = 6650;
const INITIAL_STACK_TOP_IDX: usize = 50;
const WORD_SIZE_FELT: Felt = Felt::new(4);
const DOUBLE_WORD_SIZE: Felt = Felt::new(8);
#[derive(Debug)]
pub struct FastProcessor {
pub(super) stack: [Felt; STACK_BUFFER_SIZE],
stack_top_idx: usize,
stack_bot_idx: usize,
bounds_check_counter: usize,
pub(super) clk: RowIndex,
pub(super) ctx: ContextId,
pub(super) fmp: Felt,
in_syscall: bool,
pub(super) caller_hash: Word,
pub(super) memory: Memory,
pub(super) ace: Ace,
call_stack: Vec<ExecutionContextInfo>,
in_debug_mode: bool,
}
impl FastProcessor {
pub fn new(stack_inputs: &[Felt]) -> Self {
Self::initialize(stack_inputs, false)
}
pub fn new_debug(stack_inputs: &[Felt]) -> Self {
Self::initialize(stack_inputs, true)
}
fn initialize(stack_inputs: &[Felt], in_debug_mode: bool) -> Self {
assert!(stack_inputs.len() <= MIN_STACK_DEPTH);
let stack_top_idx = INITIAL_STACK_TOP_IDX;
let stack = {
let mut stack = [ZERO; STACK_BUFFER_SIZE];
let bottom_idx = stack_top_idx - stack_inputs.len();
stack[bottom_idx..stack_top_idx].copy_from_slice(stack_inputs);
stack
};
let stack_bot_idx = stack_top_idx - MIN_STACK_DEPTH;
let bounds_check_counter = stack_bot_idx;
FastProcessor {
stack,
stack_top_idx,
stack_bot_idx,
bounds_check_counter,
clk: 0_u32.into(),
ctx: 0_u32.into(),
fmp: Felt::new(FMP_MIN),
in_syscall: false,
caller_hash: EMPTY_WORD,
memory: Memory::new(),
call_stack: Vec::new(),
ace: Ace::default(),
in_debug_mode,
}
}
pub fn stack(&self) -> &[Felt] {
&self.stack[self.stack_bot_idx..self.stack_top_idx]
}
#[inline(always)]
pub fn stack_get(&self, idx: usize) -> Felt {
self.stack[self.stack_top_idx - idx - 1]
}
#[inline(always)]
pub fn stack_get_mut(&mut self, idx: usize) -> &mut Felt {
&mut self.stack[self.stack_top_idx - idx - 1]
}
#[inline(always)]
pub fn stack_get_word(&self, start_idx: usize) -> Word {
debug_assert!(start_idx < MIN_STACK_DEPTH);
let word_start_idx = self.stack_top_idx - start_idx - 4;
self.stack[range(word_start_idx, WORD_SIZE)].try_into().unwrap()
}
#[inline(always)]
pub fn stack_depth(&self) -> u32 {
(self.stack_top_idx - self.stack_bot_idx) as u32
}
#[inline(always)]
pub fn stack_write(&mut self, idx: usize, element: Felt) {
self.stack[self.stack_top_idx - idx - 1] = element
}
#[inline(always)]
pub fn stack_write_word(&mut self, start_idx: usize, word: &Word) {
debug_assert!(start_idx < MIN_STACK_DEPTH);
let word_start_idx = self.stack_top_idx - start_idx - 4;
self.stack[range(word_start_idx, WORD_SIZE)].copy_from_slice(word)
}
#[inline(always)]
pub fn stack_swap(&mut self, idx1: usize, idx2: usize) {
let a = self.stack_get(idx1);
let b = self.stack_get(idx2);
self.stack_write(idx1, b);
self.stack_write(idx2, a);
}
pub fn execute(
mut self,
program: &Program,
host: &mut impl Host,
) -> Result<StackOutputs, ExecutionError> {
self.execute_impl(program, host)
}
fn execute_impl(
&mut self,
program: &Program,
host: &mut impl Host,
) -> Result<StackOutputs, ExecutionError> {
self.execute_mast_node(
program.entrypoint(),
program.mast_forest(),
program.kernel(),
host,
)?;
StackOutputs::new(
self.stack[self.stack_bot_idx..self.stack_top_idx]
.iter()
.rev()
.copied()
.collect(),
)
.map_err(|_| {
ExecutionError::OutputStackOverflow(
self.stack_top_idx - self.stack_bot_idx - MIN_STACK_DEPTH,
)
})
}
fn execute_mast_node(
&mut self,
node_id: MastNodeId,
program: &MastForest,
kernel: &Kernel,
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], 0, host)?;
}
match node {
MastNode::Block(basic_block_node) => {
self.execute_basic_block_node(basic_block_node, program, host)?
},
MastNode::Join(join_node) => {
self.execute_join_node(join_node, program, kernel, host)?
},
MastNode::Split(split_node) => {
self.execute_split_node(split_node, program, kernel, host)?
},
MastNode::Loop(loop_node) => {
self.execute_loop_node(loop_node, program, kernel, host)?
},
MastNode::Call(call_node) => {
self.execute_call_node(call_node, program, kernel, host)?
},
MastNode::Dyn(dyn_node) => self.execute_dyn_node(dyn_node, program, kernel, host)?,
MastNode::External(external_node) => {
self.execute_external_node(external_node, kernel, host)?
},
}
for &decorator_id in node.after_exit() {
self.execute_decorator(&program[decorator_id], 0, host)?;
}
Ok(())
}
fn execute_join_node(
&mut self,
join_node: &JoinNode,
program: &MastForest,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
self.execute_mast_node(join_node.first(), program, kernel, host)?;
self.execute_mast_node(join_node.second(), program, kernel, host)?;
self.clk += 1_u32;
Ok(())
}
fn execute_split_node(
&mut self,
split_node: &SplitNode,
program: &MastForest,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
let condition = self.stack_get(0);
self.decrement_stack_size();
let ret = if condition == ONE {
self.execute_mast_node(split_node.on_true(), program, kernel, host)
} else if condition == ZERO {
self.execute_mast_node(split_node.on_false(), program, kernel, host)
} else {
Err(ExecutionError::not_binary_value_if(condition, &ErrorContext::default()))
};
self.clk += 1_u32;
ret
}
fn execute_loop_node(
&mut self,
loop_node: &LoopNode,
program: &MastForest,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
let mut condition = self.stack_get(0);
self.decrement_stack_size();
while condition == ONE {
self.execute_mast_node(loop_node.body(), program, kernel, host)?;
condition = self.stack_get(0);
self.decrement_stack_size();
if condition == ONE {
self.clk += 1_u32;
}
}
self.clk += 1_u32;
if condition == ZERO {
Ok(())
} else {
Err(ExecutionError::not_binary_value_loop(condition, &ErrorContext::default()))
}
}
fn execute_call_node(
&mut self,
call_node: &CallNode,
program: &MastForest,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
if self.in_syscall {
let instruction = if call_node.is_syscall() { "syscall" } else { "call" };
return Err(ExecutionError::CallInSyscall(instruction));
}
let callee_hash = program
.get_node_by_id(call_node.callee())
.ok_or(ExecutionError::MastNodeNotFoundInForest { node_id: call_node.callee() })?
.digest();
self.save_context_and_truncate_stack();
if call_node.is_syscall() {
if !kernel.contains_proc(callee_hash) {
return Err(ExecutionError::syscall_target_not_in_kernel(
callee_hash,
&ErrorContext::default(),
));
}
self.ctx = ContextId::root();
self.fmp = SYSCALL_FMP_MIN.into();
self.in_syscall = true;
} else {
self.ctx = self.clk.into();
self.fmp = Felt::new(FMP_MIN);
self.caller_hash = callee_hash.into();
}
self.execute_mast_node(call_node.callee(), program, kernel, host)?;
self.restore_context()?;
self.clk += 1_u32;
Ok(())
}
fn execute_dyn_node(
&mut self,
dyn_node: &DynNode,
program: &MastForest,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
if dyn_node.is_dyncall() && self.in_syscall {
return Err(ExecutionError::CallInSyscall("dyncall"));
}
let callee_hash = {
let mem_addr = self.stack_get(0);
self.memory.read_word(self.ctx, mem_addr, self.clk).copied()?
};
self.decrement_stack_size();
if dyn_node.is_dyncall() {
self.save_context_and_truncate_stack();
self.ctx = self.clk.into();
self.fmp = Felt::new(FMP_MIN);
self.caller_hash = callee_hash;
};
match program.find_procedure_root(callee_hash.into()) {
Some(callee_id) => self.execute_mast_node(callee_id, program, kernel, host)?,
None => {
let mast_forest = host.get_mast_forest(&callee_hash.into()).ok_or_else(|| {
ExecutionError::dynamic_node_not_found(
callee_hash.into(),
&ErrorContext::default(),
)
})?;
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, kernel, host)?
},
}
if dyn_node.is_dyncall() {
self.restore_context()?;
}
self.clk += 1_u32;
Ok(())
}
fn execute_external_node(
&mut self,
external_node: &ExternalNode,
kernel: &Kernel,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
let (root_id, mast_forest) = resolve_external_node(external_node, host)?;
self.execute_mast_node(root_id, &mast_forest, kernel, host)
}
fn execute_basic_block_node(
&mut self,
basic_block_node: &BasicBlockNode,
program: &MastForest,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
self.clk += 1_u32;
let mut batch_offset_in_block = 0;
let mut op_batches = basic_block_node.op_batches().iter();
let mut decorator_ids = basic_block_node.decorator_iter();
if let Some(first_op_batch) = op_batches.next() {
self.execute_op_batch(
first_op_batch,
&mut decorator_ids,
batch_offset_in_block,
program,
host,
)?;
batch_offset_in_block += first_op_batch.ops().len();
}
for op_batch in op_batches {
self.clk += 1_u32;
self.execute_op_batch(
op_batch,
&mut decorator_ids,
batch_offset_in_block,
program,
host,
)?;
batch_offset_in_block += op_batch.ops().len();
}
self.clk += batch_offset_in_block as u32;
self.clk += 1_u32;
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, 0, host)?;
}
Ok(())
}
#[inline(always)]
fn execute_op_batch(
&mut self,
batch: &OpBatch,
decorators: &mut DecoratorIterator,
batch_offset_in_block: usize,
program: &MastForest,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
let op_counts = batch.op_counts();
let mut op_idx_in_group = 0;
let mut group_idx = 0;
let mut next_group_idx = 1;
let num_batch_groups = batch.num_groups().next_power_of_two();
for (op_idx_in_batch, op) in batch.ops().iter().enumerate() {
while let Some(&decorator_id) =
decorators.next_filtered(batch_offset_in_block + op_idx_in_batch)
{
let decorator = program
.get_decorator_by_id(decorator_id)
.ok_or(ExecutionError::DecoratorNotFoundInForest { decorator_id })?;
self.execute_decorator(decorator, op_idx_in_batch, host)?;
}
self.execute_op(op, batch_offset_in_block + op_idx_in_batch, program, host)?;
let has_imm = op.imm_value().is_some();
if has_imm {
next_group_idx += 1;
}
if op_idx_in_group == op_counts[group_idx] - 1 {
if has_imm {
debug_assert!(op_idx_in_group < OP_GROUP_SIZE - 1, "invalid op index");
self.clk += 1_u32;
}
group_idx = next_group_idx;
next_group_idx += 1;
op_idx_in_group = 0;
} else {
op_idx_in_group += 1;
}
}
self.clk += (num_batch_groups - group_idx) as u32;
Ok(())
}
fn execute_decorator(
&mut self,
decorator: &Decorator,
op_idx_in_batch: usize,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
match decorator {
Decorator::Debug(options) => {
if self.in_debug_mode {
host.on_debug(ProcessState::new_fast(self, op_idx_in_batch), options)?;
}
},
Decorator::AsmOp(_assembly_op) => {
},
Decorator::Trace(id) => {
host.on_trace(ProcessState::new_fast(self, op_idx_in_batch), *id)?;
},
};
Ok(())
}
fn execute_op(
&mut self,
operation: &Operation,
op_idx: usize,
program: &MastForest,
host: &mut impl Host,
) -> Result<(), ExecutionError> {
if self.bounds_check_counter == 0 {
let err_str = if self.stack_top_idx - MIN_STACK_DEPTH == 0 {
"stack underflow"
} else {
"stack overflow"
};
return Err(ExecutionError::FailedToExecuteProgram(err_str));
}
match operation {
Operation::Noop => {
},
Operation::Assert(err_code) => self.op_assert(*err_code, op_idx, host, program)?,
Operation::FmpAdd => self.op_fmpadd(),
Operation::FmpUpdate => self.op_fmpupdate()?,
Operation::SDepth => self.op_sdepth(),
Operation::Caller => self.op_caller()?,
Operation::Clk => self.op_clk(op_idx)?,
Operation::Emit(event_id) => self.op_emit(*event_id, op_idx, host)?,
Operation::Join => unreachable!("control flow operation"),
Operation::Split => unreachable!("control flow operation"),
Operation::Loop => unreachable!("control flow operation"),
Operation::Call => unreachable!("control flow operation"),
Operation::SysCall => unreachable!("control flow operation"),
Operation::Dyn => unreachable!("control flow operation"),
Operation::Dyncall => unreachable!("control flow operation"),
Operation::Span => unreachable!("control flow operation"),
Operation::Repeat => unreachable!("control flow operation"),
Operation::Respan => unreachable!("control flow operation"),
Operation::End => unreachable!("control flow operation"),
Operation::Halt => unreachable!("control flow operation"),
Operation::Add => self.op_add()?,
Operation::Neg => self.op_neg()?,
Operation::Mul => self.op_mul()?,
Operation::Inv => self.op_inv(op_idx)?,
Operation::Incr => self.op_incr()?,
Operation::And => self.op_and()?,
Operation::Or => self.op_or()?,
Operation::Not => self.op_not()?,
Operation::Eq => self.op_eq()?,
Operation::Eqz => self.op_eqz()?,
Operation::Expacc => self.op_expacc(),
Operation::Ext2Mul => self.op_ext2mul(),
Operation::U32split => self.op_u32split()?,
Operation::U32add => self.op_u32add()?,
Operation::U32add3 => self.op_u32add3()?,
Operation::U32sub => self.op_u32sub(op_idx)?,
Operation::U32mul => self.op_u32mul()?,
Operation::U32madd => self.op_u32madd()?,
Operation::U32div => self.op_u32div(op_idx)?,
Operation::U32and => self.op_u32and()?,
Operation::U32xor => self.op_u32xor()?,
Operation::U32assert2(err_code) => self.op_u32assert2(*err_code)?,
Operation::Pad => self.op_pad(),
Operation::Drop => self.decrement_stack_size(),
Operation::Dup0 => self.dup_nth(0),
Operation::Dup1 => self.dup_nth(1),
Operation::Dup2 => self.dup_nth(2),
Operation::Dup3 => self.dup_nth(3),
Operation::Dup4 => self.dup_nth(4),
Operation::Dup5 => self.dup_nth(5),
Operation::Dup6 => self.dup_nth(6),
Operation::Dup7 => self.dup_nth(7),
Operation::Dup9 => self.dup_nth(9),
Operation::Dup11 => self.dup_nth(11),
Operation::Dup13 => self.dup_nth(13),
Operation::Dup15 => self.dup_nth(15),
Operation::Swap => self.op_swap(),
Operation::SwapW => self.swapw_nth(1),
Operation::SwapW2 => self.swapw_nth(2),
Operation::SwapW3 => self.swapw_nth(3),
Operation::SwapDW => self.op_swap_double_word(),
Operation::MovUp2 => self.rotate_left(3),
Operation::MovUp3 => self.rotate_left(4),
Operation::MovUp4 => self.rotate_left(5),
Operation::MovUp5 => self.rotate_left(6),
Operation::MovUp6 => self.rotate_left(7),
Operation::MovUp7 => self.rotate_left(8),
Operation::MovUp8 => self.rotate_left(9),
Operation::MovDn2 => self.rotate_right(3),
Operation::MovDn3 => self.rotate_right(4),
Operation::MovDn4 => self.rotate_right(5),
Operation::MovDn5 => self.rotate_right(6),
Operation::MovDn6 => self.rotate_right(7),
Operation::MovDn7 => self.rotate_right(8),
Operation::MovDn8 => self.rotate_right(9),
Operation::CSwap => self.op_cswap()?,
Operation::CSwapW => self.op_cswapw()?,
Operation::Push(element) => self.op_push(*element),
Operation::AdvPop => self.op_advpop(op_idx, host)?,
Operation::AdvPopW => self.op_advpopw(op_idx, host)?,
Operation::MLoadW => self.op_mloadw(op_idx)?,
Operation::MStoreW => self.op_mstorew(op_idx)?,
Operation::MLoad => self.op_mload()?,
Operation::MStore => self.op_mstore()?,
Operation::MStream => self.op_mstream(op_idx)?,
Operation::Pipe => self.op_pipe(op_idx, host)?,
Operation::HPerm => self.op_hperm(),
Operation::MpVerify(err_code) => self.op_mpverify(*err_code, host, program)?,
Operation::MrUpdate => self.op_mrupdate(host)?,
Operation::FriE2F4 => self.op_fri_ext2fold4()?,
Operation::HornerBase => self.op_horner_eval_base(op_idx)?,
Operation::HornerExt => self.op_horner_eval_ext(op_idx)?,
Operation::ArithmeticCircuitEval => self.arithmetic_circuit_eval(op_idx)?,
}
Ok(())
}
#[inline(always)]
fn increment_stack_size(&mut self) {
self.stack_top_idx += 1;
self.update_bounds_check_counter();
}
#[inline(always)]
fn decrement_stack_size(&mut self) {
self.stack_top_idx -= 1;
self.stack_bot_idx = min(self.stack_bot_idx, self.stack_top_idx - MIN_STACK_DEPTH);
self.update_bounds_check_counter();
}
#[inline(always)]
fn stack_size(&self) -> usize {
self.stack_top_idx - self.stack_bot_idx
}
#[inline(always)]
fn update_bounds_check_counter(&mut self) {
self.bounds_check_counter -= 1;
if self.bounds_check_counter == 0 {
self.bounds_check_counter =
min(self.stack_top_idx - MIN_STACK_DEPTH, STACK_BUFFER_SIZE - self.stack_top_idx);
}
}
fn save_context_and_truncate_stack(&mut self) {
let overflow_stack = if self.stack_size() > MIN_STACK_DEPTH {
let overflow_stack =
self.stack[self.stack_bot_idx..self.stack_top_idx - MIN_STACK_DEPTH].to_vec();
self.stack[self.stack_bot_idx..self.stack_top_idx - MIN_STACK_DEPTH].fill(ZERO);
overflow_stack
} else {
Vec::new()
};
self.stack_bot_idx = self.stack_top_idx - MIN_STACK_DEPTH;
self.call_stack.push(ExecutionContextInfo {
overflow_stack,
ctx: self.ctx,
fn_hash: self.caller_hash,
fmp: self.fmp,
});
}
fn restore_context(&mut self) -> Result<(), ExecutionError> {
if self.stack_size() > MIN_STACK_DEPTH {
return Err(ExecutionError::invalid_stack_depth_on_return(
self.stack_size(),
&ErrorContext::default(),
));
}
let ctx_info = self
.call_stack
.pop()
.expect("execution context stack should never be empty when restoring context");
{
let overflow_len = ctx_info.overflow_stack.len();
if overflow_len > self.stack_bot_idx {
return Err(ExecutionError::FailedToExecuteProgram(
"stack underflow when restoring context",
));
}
self.stack[range(self.stack_bot_idx - overflow_len, overflow_len)]
.copy_from_slice(&ctx_info.overflow_stack);
self.stack_bot_idx -= overflow_len;
}
self.ctx = ctx_info.ctx;
self.fmp = ctx_info.fmp;
self.in_syscall = false;
self.caller_hash = ctx_info.fn_hash;
Ok(())
}
}
#[derive(Debug)]
struct ExecutionContextInfo {
overflow_stack: Vec<Felt>,
ctx: ContextId,
fn_hash: Word,
fmp: Felt,
}