use alloc::{boxed::Box, sync::Arc, vec::Vec};
use core::cmp::min;
use memory::Memory;
use miden_air::{Felt, RowIndex};
use miden_core::{
Decorator, EMPTY_WORD, Program, StackOutputs, WORD_SIZE, Word, ZERO,
mast::{MastForest, MastNode, MastNodeExt, MastNodeId},
precompile::PrecompileTranscript,
stack::MIN_STACK_DEPTH,
utils::range,
};
use crate::{
AdviceInputs, AdviceProvider, AsyncHost, ContextId, ErrorContext, ExecutionError, ProcessState,
chiplets::Ace,
continuation_stack::{Continuation, ContinuationStack},
fast::execution_tracer::{ExecutionTracer, TraceGenerationContext},
};
pub mod execution_tracer;
mod memory;
mod operation;
pub mod trace_state;
mod tracer;
pub use tracer::{NoopTracer, Tracer};
mod basic_block;
mod call_and_dyn;
mod external;
mod join;
mod r#loop;
mod split;
#[cfg(test)]
mod tests;
const STACK_BUFFER_SIZE: usize = 6850;
const INITIAL_STACK_TOP_IDX: usize = 250;
#[derive(Debug)]
pub struct FastProcessor {
pub(super) stack: Box<[Felt; STACK_BUFFER_SIZE]>,
stack_top_idx: usize,
stack_bot_idx: usize,
pub(super) clk: RowIndex,
pub(super) ctx: ContextId,
pub(super) caller_hash: Word,
pub(super) advice: AdviceProvider,
pub(super) memory: Memory,
pub(super) ace: Ace,
call_stack: Vec<ExecutionContextInfo>,
in_debug_mode: bool,
pc_transcript: PrecompileTranscript,
}
impl FastProcessor {
pub fn new(stack_inputs: &[Felt]) -> Self {
Self::initialize(stack_inputs, AdviceInputs::default(), false)
}
pub fn new_with_advice_inputs(stack_inputs: &[Felt], advice_inputs: AdviceInputs) -> Self {
Self::initialize(stack_inputs, advice_inputs, false)
}
pub fn new_debug(stack_inputs: &[Felt], advice_inputs: AdviceInputs) -> Self {
Self::initialize(stack_inputs, advice_inputs, true)
}
fn initialize(stack_inputs: &[Felt], advice_inputs: AdviceInputs, 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: Box<[Felt; STACK_BUFFER_SIZE]> =
vec![ZERO; STACK_BUFFER_SIZE].into_boxed_slice().try_into().unwrap();
let bottom_idx = stack_top_idx - stack_inputs.len();
stack[bottom_idx..stack_top_idx].copy_from_slice(stack_inputs);
stack
};
Self {
advice: advice_inputs.into(),
stack,
stack_top_idx,
stack_bot_idx: stack_top_idx - MIN_STACK_DEPTH,
clk: 0_u32.into(),
ctx: 0_u32.into(),
caller_hash: EMPTY_WORD,
memory: Memory::new(),
call_stack: Vec::new(),
ace: Ace::default(),
in_debug_mode,
pc_transcript: PrecompileTranscript::new(),
}
}
#[inline(always)]
fn stack_size(&self) -> usize {
self.stack_top_idx - self.stack_bot_idx
}
pub fn stack(&self) -> &[Felt] {
&self.stack[self.stack_bot_idx..self.stack_top_idx]
}
pub fn stack_top(&self) -> &[Felt] {
&self.stack[self.stack_top_idx - MIN_STACK_DEPTH..self.stack_top_idx]
}
pub fn stack_top_mut(&mut self) -> &mut [Felt] {
&mut self.stack[self.stack_top_idx - MIN_STACK_DEPTH..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 + WORD_SIZE <= self.stack_depth() as usize,
"Not enough elements on stack to read word starting at index {start_idx}"
);
let word_start_idx = self.stack_top_idx - start_idx - 4;
let result: [Felt; WORD_SIZE] =
self.stack[range(word_start_idx, WORD_SIZE)].try_into().unwrap();
result.into()
}
#[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;
let source: [Felt; WORD_SIZE] = (*word).into();
self.stack[range(word_start_idx, WORD_SIZE)].copy_from_slice(&source)
}
#[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 async fn execute(
self,
program: &Program,
host: &mut impl AsyncHost,
) -> Result<ExecutionOutput, ExecutionError> {
self.execute_with_tracer(program, host, &mut NoopTracer).await
}
pub async fn execute_for_trace(
self,
program: &Program,
host: &mut impl AsyncHost,
fragment_size: usize,
) -> Result<(ExecutionOutput, TraceGenerationContext), ExecutionError> {
let mut tracer = ExecutionTracer::new(fragment_size);
let execution_output = self.execute_with_tracer(program, host, &mut tracer).await?;
let context = tracer.into_trace_generation_context(execution_output.final_pc_transcript);
Ok((execution_output, context))
}
pub async fn execute_with_tracer(
mut self,
program: &Program,
host: &mut impl AsyncHost,
tracer: &mut impl Tracer,
) -> Result<ExecutionOutput, ExecutionError> {
let stack_outputs = self.execute_impl(program, host, tracer).await?;
Ok(ExecutionOutput {
stack: stack_outputs,
advice: self.advice,
memory: self.memory,
final_pc_transcript: self.pc_transcript,
})
}
async fn execute_impl(
&mut self,
program: &Program,
host: &mut impl AsyncHost,
tracer: &mut impl Tracer,
) -> Result<StackOutputs, ExecutionError> {
let mut continuation_stack = ContinuationStack::new(program);
let mut current_forest = program.mast_forest().clone();
self.advice
.extend_map(current_forest.advice_map())
.map_err(|err| ExecutionError::advice_error(err, self.clk, &()))?;
while let Some(continuation) = continuation_stack.pop_continuation() {
match continuation {
Continuation::StartNode(node_id) => {
let node = current_forest.get_node_by_id(node_id).unwrap();
match node {
MastNode::Block(basic_block_node) => {
self.execute_basic_block_node(
basic_block_node,
node_id,
¤t_forest,
host,
&mut continuation_stack,
¤t_forest,
tracer,
)
.await?
},
MastNode::Join(join_node) => self.start_join_node(
join_node,
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
MastNode::Split(split_node) => self.start_split_node(
split_node,
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
MastNode::Loop(loop_node) => self.start_loop_node(
loop_node,
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
MastNode::Call(call_node) => self.start_call_node(
call_node,
node_id,
program,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
MastNode::Dyn(_) => {
self.start_dyn_node(
node_id,
&mut current_forest,
&mut continuation_stack,
host,
tracer,
)
.await?
},
MastNode::External(_external_node) => {
self.execute_external_node(
node_id,
&mut current_forest,
&mut continuation_stack,
host,
tracer,
)
.await?
},
}
},
Continuation::FinishJoin(node_id) => self.finish_join_node(
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
Continuation::FinishSplit(node_id) => self.finish_split_node(
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
Continuation::FinishLoop(node_id) => self.finish_loop_node(
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
Continuation::FinishCall(node_id) => self.finish_call_node(
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
Continuation::FinishDyn(node_id) => self.finish_dyn_node(
node_id,
¤t_forest,
&mut continuation_stack,
host,
tracer,
)?,
Continuation::FinishExternal(node_id) => {
self.execute_after_exit_decorators(node_id, ¤t_forest, host)?;
},
Continuation::EnterForest(previous_forest) => {
current_forest = previous_forest;
},
}
}
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_before_enter_decorators(
&mut self,
node_id: MastNodeId,
current_forest: &MastForest,
host: &mut impl AsyncHost,
) -> Result<(), ExecutionError> {
let node = current_forest
.get_node_by_id(node_id)
.expect("internal error: node id {node_id} not found in current forest");
for &decorator_id in node.before_enter() {
self.execute_decorator(¤t_forest[decorator_id], host)?;
}
Ok(())
}
fn execute_after_exit_decorators(
&mut self,
node_id: MastNodeId,
current_forest: &MastForest,
host: &mut impl AsyncHost,
) -> Result<(), ExecutionError> {
let node = current_forest
.get_node_by_id(node_id)
.expect("internal error: node id {node_id} not found in current forest");
for &decorator_id in node.after_exit() {
self.execute_decorator(¤t_forest[decorator_id], host)?;
}
Ok(())
}
fn execute_decorator(
&mut self,
decorator: &Decorator,
host: &mut impl AsyncHost,
) -> Result<(), ExecutionError> {
match decorator {
Decorator::Debug(options) => {
if self.in_debug_mode {
let process = &mut self.state();
host.on_debug(process, options)?;
}
},
Decorator::AsmOp(_assembly_op) => {
},
Decorator::Trace(id) => {
let process = &mut self.state();
host.on_trace(process, *id)?;
},
};
Ok(())
}
#[inline(always)]
fn increment_clk(&mut self, tracer: &mut impl Tracer) {
self.clk += 1_u32;
tracer.increment_clk();
}
async fn load_mast_forest<E>(
&mut self,
node_digest: Word,
host: &mut impl AsyncHost,
get_mast_forest_failed: impl Fn(Word, &E) -> ExecutionError,
err_ctx: &E,
) -> Result<(MastNodeId, Arc<MastForest>), ExecutionError>
where
E: ErrorContext,
{
let mast_forest = host
.get_mast_forest(&node_digest)
.await
.ok_or_else(|| get_mast_forest_failed(node_digest, err_ctx))?;
let root_id = mast_forest
.find_procedure_root(node_digest)
.ok_or(ExecutionError::malfored_mast_forest_in_host(node_digest, err_ctx))?;
self.advice
.extend_map(mast_forest.advice_map())
.map_err(|err| ExecutionError::advice_error(err, self.clk, err_ctx))?;
Ok((root_id, mast_forest))
}
#[inline(always)]
fn increment_stack_size(&mut self, tracer: &mut impl Tracer) {
tracer.increment_stack_size(self);
self.stack_top_idx += 1;
}
#[inline(always)]
fn decrement_stack_size(&mut self, tracer: &mut impl Tracer) {
if self.stack_top_idx == MIN_STACK_DEPTH {
self.reset_stack_in_buffer(INITIAL_STACK_TOP_IDX);
}
self.stack_top_idx -= 1;
self.stack_bot_idx = min(self.stack_bot_idx, self.stack_top_idx - MIN_STACK_DEPTH);
tracer.decrement_stack_size();
}
#[inline(always)]
fn reset_stack_in_buffer(&mut self, new_stack_top_idx: usize) {
debug_assert_eq!(self.stack_depth(), MIN_STACK_DEPTH as u32);
let new_stack_bot_idx = new_stack_top_idx - MIN_STACK_DEPTH;
self.stack
.copy_within(self.stack_bot_idx..self.stack_top_idx, new_stack_bot_idx);
self.stack[0..new_stack_bot_idx].fill(ZERO);
self.stack_bot_idx = new_stack_bot_idx;
self.stack_top_idx = new_stack_top_idx;
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_sync(
self,
program: &Program,
host: &mut impl AsyncHost,
) -> Result<StackOutputs, ExecutionError> {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
let execution_output = rt.block_on(self.execute(program, host))?;
Ok(execution_output.stack)
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_for_trace_sync(
self,
program: &Program,
host: &mut impl AsyncHost,
fragment_size: usize,
) -> Result<(ExecutionOutput, TraceGenerationContext), ExecutionError> {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(self.execute_for_trace(program, host, fragment_size))
}
#[cfg(any(test, feature = "testing"))]
pub fn execute_sync_mut(
&mut self,
program: &Program,
host: &mut impl AsyncHost,
) -> Result<StackOutputs, ExecutionError> {
let rt = tokio::runtime::Builder::new_current_thread().build().unwrap();
rt.block_on(self.execute_impl(program, host, &mut NoopTracer))
}
}
#[derive(Debug)]
pub struct ExecutionOutput {
pub stack: StackOutputs,
pub advice: AdviceProvider,
pub memory: Memory,
pub final_pc_transcript: PrecompileTranscript,
}
#[derive(Debug)]
pub struct FastProcessState<'a> {
pub(super) processor: &'a mut FastProcessor,
}
impl FastProcessor {
#[inline(always)]
pub fn state(&mut self) -> ProcessState<'_> {
ProcessState::Fast(FastProcessState { processor: self })
}
}
#[derive(Debug)]
struct ExecutionContextInfo {
overflow_stack: Vec<Felt>,
ctx: ContextId,
fn_hash: Word,
}