use openvm_circuit_primitives::{AlignedBytesBorrow, StructReflection, StructReflectionHelper};
use openvm_circuit_primitives_derive::AlignedBorrow;
use openvm_instructions::{
instruction::Instruction, program::DEFAULT_PC_STEP, PhantomDiscriminant, VmOpcode,
};
use openvm_stark_backend::{
interaction::{BusIndex, InteractionBuilder, PermutationCheckBus},
p3_field::PrimeCharacteristicRing,
};
use rand::rngs::StdRng;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use super::{execution_mode::ExecutionCtxTrait, Streams, VmExecState};
#[cfg(feature = "tco")]
use crate::arch::interpreter::InterpretedInstance;
#[cfg(feature = "aot")]
use crate::arch::SystemConfig;
#[cfg(feature = "metrics")]
use crate::metrics::VmMetrics;
use crate::{
arch::{execution_mode::MeteredExecutionCtxTrait, ExecutorInventoryError, MatrixRecordArena},
system::{
memory::online::{GuestMemory, TracingMemory},
program::ProgramBus,
},
};
#[derive(Error, Debug)]
pub enum ExecutionError {
#[error("execution failed at pc {pc}, err: {msg}")]
Fail { pc: u32, msg: &'static str },
#[error("pc {0} out of bounds")]
PcOutOfBounds(u32),
#[error("unreachable instruction at pc {0}")]
Unreachable(u32),
#[error("at pc {pc}, opcode {opcode} was not enabled")]
DisabledOperation { pc: u32, opcode: VmOpcode },
#[error("at pc = {pc}")]
HintOutOfBounds { pc: u32 },
#[error("at pc {pc}, hint buffer num_words is zero")]
HintBufferZeroWords { pc: u32 },
#[error("at pc {pc}, hint buffer num_words {num_words} exceeds MAX_HINT_BUFFER_WORDS {max_hint_buffer_words}")]
HintBufferTooLarge {
pc: u32,
num_words: u32,
max_hint_buffer_words: u32,
},
#[error("at pc {pc}, tried to publish into index {public_value_index} when num_public_values = {num_public_values}")]
PublicValueIndexOutOfBounds {
pc: u32,
num_public_values: usize,
public_value_index: usize,
},
#[error("at pc {pc}, tried to publish {new_value} into index {public_value_index} but already had {existing_value}")]
PublicValueNotEqual {
pc: u32,
public_value_index: usize,
existing_value: usize,
new_value: usize,
},
#[error("at pc {pc}, phantom sub-instruction not found for discriminant {}", .discriminant.0)]
PhantomNotFound {
pc: u32,
discriminant: PhantomDiscriminant,
},
#[error("at pc {pc}, discriminant {}, phantom error: {inner}", .discriminant.0)]
Phantom {
pc: u32,
discriminant: PhantomDiscriminant,
inner: eyre::Error,
},
#[error("program must terminate")]
DidNotTerminate,
#[error("program exit code {0}")]
FailedWithExitCode(u32),
#[error("trace buffer out of bounds: requested {requested} but capacity is {capacity}")]
TraceBufferOutOfBounds { requested: usize, capacity: usize },
#[error("instruction counter overflow: {instret} + {num_insns} > u64::MAX")]
InstretOverflow { instret: u64, num_insns: u64 },
#[error("inventory error: {0}")]
Inventory(#[from] ExecutorInventoryError),
#[error("static program error: {0}")]
Static(#[from] StaticProgramError),
}
#[derive(Error, Debug)]
pub enum StaticProgramError {
#[error("invalid instruction at pc {0}")]
InvalidInstruction(u32),
#[error("Too many executors")]
TooManyExecutors,
#[error("at pc {pc}, opcode {opcode} was not enabled")]
DisabledOperation { pc: u32, opcode: VmOpcode },
#[error("Executor not found for opcode {opcode}")]
ExecutorNotFound { opcode: VmOpcode },
#[error("Failed to create temporary file: {err}")]
FailToCreateTemporaryFile { err: String },
#[error("Failed to write into temporary file: {err}")]
FailToWriteTemporaryFile { err: String },
#[error("Failed to generate dynamic library: {err}")]
FailToGenerateDynamicLibrary { err: String },
}
#[cfg(feature = "aot")]
#[derive(Error, Debug)]
pub enum AotError {
#[error("AOT compilation not supported for this opcode")]
NotSupported,
#[error("No executor found for opcode {0}")]
NoExecutorFound(VmOpcode),
#[error("Invalid instruction format")]
InvalidInstruction,
#[error("Other AOT error: {0}")]
Other(String),
}
pub type ExecuteFunc<F, CTX> =
unsafe fn(pre_compute: *const u8, exec_state: &mut VmExecState<F, GuestMemory, CTX>);
#[cfg(feature = "tco")]
pub type Handler<F, CTX> = unsafe fn(
interpreter: &InterpretedInstance<'_, F, CTX>,
exec_state: &mut VmExecState<F, GuestMemory, CTX>,
);
pub trait InterpreterExecutor<F> {
fn pre_compute_size(&self) -> usize;
#[cfg(not(feature = "tco"))]
fn pre_compute<Ctx>(
&self,
pc: u32,
inst: &Instruction<F>,
data: &mut [u8],
) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
where
Ctx: ExecutionCtxTrait;
#[cfg(feature = "tco")]
fn handler<Ctx>(
&self,
pc: u32,
inst: &Instruction<F>,
data: &mut [u8],
) -> Result<Handler<F, Ctx>, StaticProgramError>
where
Ctx: ExecutionCtxTrait;
}
#[cfg(feature = "aot")]
pub trait AotExecutor<F> {
fn is_aot_supported(&self, _inst: &Instruction<F>) -> bool {
false
}
fn generate_x86_asm(&self, _inst: &Instruction<F>, _pc: u32) -> Result<String, AotError> {
unimplemented!()
}
}
#[cfg(feature = "aot")]
pub trait Executor<F>: InterpreterExecutor<F> + AotExecutor<F> {}
#[cfg(feature = "aot")]
impl<F, T> Executor<F> for T where T: InterpreterExecutor<F> + AotExecutor<F> {}
#[cfg(not(feature = "aot"))]
pub trait Executor<F>: InterpreterExecutor<F> {}
#[cfg(not(feature = "aot"))]
impl<F, T> Executor<F> for T where T: InterpreterExecutor<F> {}
pub trait InterpreterMeteredExecutor<F> {
fn metered_pre_compute_size(&self) -> usize;
#[cfg(not(feature = "tco"))]
fn metered_pre_compute<Ctx>(
&self,
air_idx: usize,
pc: u32,
inst: &Instruction<F>,
data: &mut [u8],
) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
where
Ctx: MeteredExecutionCtxTrait;
#[cfg(feature = "tco")]
fn metered_handler<Ctx>(
&self,
air_idx: usize,
pc: u32,
inst: &Instruction<F>,
data: &mut [u8],
) -> Result<Handler<F, Ctx>, StaticProgramError>
where
Ctx: MeteredExecutionCtxTrait;
}
#[cfg(feature = "aot")]
pub trait AotMeteredExecutor<F> {
fn is_aot_metered_supported(&self, _inst: &Instruction<F>) -> bool {
false
}
fn generate_x86_metered_asm(
&self,
_inst: &Instruction<F>,
_pc: u32,
_chip_idx: usize,
_config: &SystemConfig,
) -> Result<String, AotError> {
unimplemented!()
}
}
#[cfg(feature = "aot")]
pub trait MeteredExecutor<F>: InterpreterMeteredExecutor<F> + AotMeteredExecutor<F> {}
#[cfg(feature = "aot")]
impl<F, T> MeteredExecutor<F> for T where T: InterpreterMeteredExecutor<F> + AotMeteredExecutor<F> {}
#[cfg(not(feature = "aot"))]
pub trait MeteredExecutor<F>: InterpreterMeteredExecutor<F> {}
#[cfg(not(feature = "aot"))]
impl<F, T> MeteredExecutor<F> for T where T: InterpreterMeteredExecutor<F> {}
pub trait PreflightExecutor<F, RA = MatrixRecordArena<F>> {
fn execute(
&self,
state: VmStateMut<F, TracingMemory, RA>,
instruction: &Instruction<F>,
) -> Result<(), ExecutionError>;
fn get_opcode_name(&self, opcode: usize) -> String;
}
#[derive(derive_new::new)]
pub struct VmStateMut<'a, F, MEM, RA> {
pub pc: &'a mut u32,
pub memory: &'a mut MEM,
pub streams: &'a mut Streams<F>,
pub rng: &'a mut StdRng,
pub ctx: &'a mut RA,
#[cfg(feature = "metrics")]
pub metrics: &'a mut VmMetrics,
}
#[derive(Clone, AlignedBytesBorrow)]
#[repr(C)]
pub struct E2PreCompute<DATA> {
pub chip_idx: u32,
pub data: DATA,
}
#[repr(C)]
#[derive(
Clone, Copy, Debug, PartialEq, Default, AlignedBorrow, StructReflection, Serialize, Deserialize,
)]
pub struct ExecutionState<T> {
pub pc: T,
pub timestamp: T,
}
#[derive(Clone, Copy, Debug)]
pub struct ExecutionBus {
pub inner: PermutationCheckBus,
}
impl ExecutionBus {
pub const fn new(index: BusIndex) -> Self {
Self {
inner: PermutationCheckBus::new(index),
}
}
#[inline(always)]
pub fn index(&self) -> BusIndex {
self.inner.index
}
}
#[derive(Copy, Clone, Debug)]
pub struct ExecutionBridge {
execution_bus: ExecutionBus,
program_bus: ProgramBus,
}
pub struct ExecutionBridgeInteractor<AB: InteractionBuilder> {
execution_bus: ExecutionBus,
program_bus: ProgramBus,
opcode: AB::Expr,
operands: Vec<AB::Expr>,
from_state: ExecutionState<AB::Expr>,
to_state: ExecutionState<AB::Expr>,
}
pub enum PcIncOrSet<T> {
Inc(T),
Set(T),
}
impl<T> ExecutionState<T> {
pub fn new(pc: impl Into<T>, timestamp: impl Into<T>) -> Self {
Self {
pc: pc.into(),
timestamp: timestamp.into(),
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_iter<I: Iterator<Item = T>>(iter: &mut I) -> Self {
let mut next = || iter.next().unwrap();
Self {
pc: next(),
timestamp: next(),
}
}
pub fn flatten(self) -> [T; 2] {
[self.pc, self.timestamp]
}
pub fn get_width() -> usize {
2
}
pub fn map<U: Clone, F: Fn(T) -> U>(self, function: F) -> ExecutionState<U> {
ExecutionState::from_iter(&mut self.flatten().map(function).into_iter())
}
}
impl ExecutionBus {
pub fn execute_and_increment_pc<AB: InteractionBuilder>(
&self,
builder: &mut AB,
enabled: impl Into<AB::Expr>,
prev_state: ExecutionState<AB::Expr>,
timestamp_change: impl Into<AB::Expr>,
) {
let next_state = ExecutionState {
pc: prev_state.pc.clone() + AB::F::ONE,
timestamp: prev_state.timestamp.clone() + timestamp_change.into(),
};
self.execute(builder, enabled, prev_state, next_state);
}
pub fn execute<AB: InteractionBuilder>(
&self,
builder: &mut AB,
enabled: impl Into<AB::Expr>,
prev_state: ExecutionState<impl Into<AB::Expr>>,
next_state: ExecutionState<impl Into<AB::Expr>>,
) {
let enabled = enabled.into();
self.inner.receive(
builder,
[prev_state.pc.into(), prev_state.timestamp.into()],
enabled.clone(),
);
self.inner.send(
builder,
[next_state.pc.into(), next_state.timestamp.into()],
enabled,
);
}
}
impl ExecutionBridge {
pub fn new(execution_bus: ExecutionBus, program_bus: ProgramBus) -> Self {
Self {
execution_bus,
program_bus,
}
}
pub fn execute_and_increment_or_set_pc<AB: InteractionBuilder>(
&self,
opcode: impl Into<AB::Expr>,
operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
timestamp_change: impl Into<AB::Expr>,
pc_kind: impl Into<PcIncOrSet<AB::Expr>>,
) -> ExecutionBridgeInteractor<AB> {
let to_state = ExecutionState {
pc: match pc_kind.into() {
PcIncOrSet::Set(to_pc) => to_pc,
PcIncOrSet::Inc(pc_inc) => from_state.pc.clone().into() + pc_inc,
},
timestamp: from_state.timestamp.clone().into() + timestamp_change.into(),
};
self.execute(opcode, operands, from_state, to_state)
}
pub fn execute_and_increment_pc<AB: InteractionBuilder>(
&self,
opcode: impl Into<AB::Expr>,
operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
timestamp_change: impl Into<AB::Expr>,
) -> ExecutionBridgeInteractor<AB> {
let to_state = ExecutionState {
pc: from_state.pc.clone().into() + AB::Expr::from_u32(DEFAULT_PC_STEP),
timestamp: from_state.timestamp.clone().into() + timestamp_change.into(),
};
self.execute(opcode, operands, from_state, to_state)
}
pub fn execute<AB: InteractionBuilder>(
&self,
opcode: impl Into<AB::Expr>,
operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
to_state: ExecutionState<impl Into<AB::Expr>>,
) -> ExecutionBridgeInteractor<AB> {
ExecutionBridgeInteractor {
execution_bus: self.execution_bus,
program_bus: self.program_bus,
opcode: opcode.into(),
operands: operands.into_iter().map(Into::into).collect(),
from_state: from_state.map(Into::into),
to_state: to_state.map(Into::into),
}
}
}
impl<AB: InteractionBuilder> ExecutionBridgeInteractor<AB> {
pub fn eval(self, builder: &mut AB, enabled: impl Into<AB::Expr>) {
let enabled = enabled.into();
self.program_bus.lookup_instruction(
builder,
self.from_state.pc.clone(),
self.opcode,
self.operands,
enabled.clone(),
);
self.execution_bus
.execute(builder, enabled, self.from_state, self.to_state);
}
}
impl<T: PrimeCharacteristicRing> From<(u32, Option<T>)> for PcIncOrSet<T> {
fn from((pc_inc, to_pc): (u32, Option<T>)) -> Self {
match to_pc {
None => PcIncOrSet::Inc(T::from_u32(pc_inc)),
Some(to_pc) => PcIncOrSet::Set(to_pc),
}
}
}
#[allow(clippy::too_many_arguments)]
pub trait PhantomSubExecutor<F>: Send + Sync {
fn phantom_execute(
&self,
memory: &GuestMemory,
streams: &mut Streams<F>,
rng: &mut StdRng,
discriminant: PhantomDiscriminant,
a: u32,
b: u32,
c_upper: u16,
) -> eyre::Result<()>;
}