1use openvm_circuit_primitives::AlignedBytesBorrow;
2use openvm_circuit_primitives_derive::AlignedBorrow;
3use openvm_instructions::{
4 instruction::Instruction, program::DEFAULT_PC_STEP, PhantomDiscriminant, VmOpcode,
5};
6use openvm_stark_backend::{
7 interaction::{BusIndex, InteractionBuilder, PermutationCheckBus},
8 p3_field::FieldAlgebra,
9};
10use rand::rngs::StdRng;
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14use super::{execution_mode::ExecutionCtxTrait, Streams, VmExecState};
15#[cfg(feature = "tco")]
16use crate::arch::interpreter::InterpretedInstance;
17#[cfg(feature = "aot")]
18use crate::arch::SystemConfig;
19#[cfg(feature = "metrics")]
20use crate::metrics::VmMetrics;
21use crate::{
22 arch::{execution_mode::MeteredExecutionCtxTrait, ExecutorInventoryError, MatrixRecordArena},
23 system::{
24 memory::online::{GuestMemory, TracingMemory},
25 program::ProgramBus,
26 },
27};
28
29#[derive(Error, Debug)]
30pub enum ExecutionError {
31 #[error("execution failed at pc {pc}, err: {msg}")]
32 Fail { pc: u32, msg: &'static str },
33 #[error("pc {0} out of bounds")]
34 PcOutOfBounds(u32),
35 #[error("unreachable instruction at pc {0}")]
36 Unreachable(u32),
37 #[error("at pc {pc}, opcode {opcode} was not enabled")]
38 DisabledOperation { pc: u32, opcode: VmOpcode },
39 #[error("at pc = {pc}")]
40 HintOutOfBounds { pc: u32 },
41 #[error("at pc {pc}, tried to publish into index {public_value_index} when num_public_values = {num_public_values}")]
42 PublicValueIndexOutOfBounds {
43 pc: u32,
44 num_public_values: usize,
45 public_value_index: usize,
46 },
47 #[error("at pc {pc}, tried to publish {new_value} into index {public_value_index} but already had {existing_value}")]
48 PublicValueNotEqual {
49 pc: u32,
50 public_value_index: usize,
51 existing_value: usize,
52 new_value: usize,
53 },
54 #[error("at pc {pc}, phantom sub-instruction not found for discriminant {}", .discriminant.0)]
55 PhantomNotFound {
56 pc: u32,
57 discriminant: PhantomDiscriminant,
58 },
59 #[error("at pc {pc}, discriminant {}, phantom error: {inner}", .discriminant.0)]
60 Phantom {
61 pc: u32,
62 discriminant: PhantomDiscriminant,
63 inner: eyre::Error,
64 },
65 #[error("program must terminate")]
66 DidNotTerminate,
67 #[error("program exit code {0}")]
68 FailedWithExitCode(u32),
69 #[error("trace buffer out of bounds: requested {requested} but capacity is {capacity}")]
70 TraceBufferOutOfBounds { requested: usize, capacity: usize },
71 #[error("instruction counter overflow: {instret} + {num_insns} > u64::MAX")]
72 InstretOverflow { instret: u64, num_insns: u64 },
73 #[error("inventory error: {0}")]
74 Inventory(#[from] ExecutorInventoryError),
75 #[error("static program error: {0}")]
76 Static(#[from] StaticProgramError),
77}
78
79#[derive(Error, Debug)]
81pub enum StaticProgramError {
82 #[error("invalid instruction at pc {0}")]
83 InvalidInstruction(u32),
84 #[error("Too many executors")]
85 TooManyExecutors,
86 #[error("at pc {pc}, opcode {opcode} was not enabled")]
87 DisabledOperation { pc: u32, opcode: VmOpcode },
88 #[error("Executor not found for opcode {opcode}")]
89 ExecutorNotFound { opcode: VmOpcode },
90 #[error("Failed to create temporary file: {err}")]
91 FailToCreateTemporaryFile { err: String },
92 #[error("Failed to write into temporary file: {err}")]
93 FailToWriteTemporaryFile { err: String },
94 #[error("Failed to generate dynamic library: {err}")]
95 FailToGenerateDynamicLibrary { err: String },
96}
97
98#[cfg(feature = "aot")]
99#[derive(Error, Debug)]
100pub enum AotError {
101 #[error("AOT compilation not supported for this opcode")]
102 NotSupported,
103
104 #[error("No executor found for opcode {0}")]
105 NoExecutorFound(VmOpcode),
106
107 #[error("Invalid instruction format")]
108 InvalidInstruction,
109
110 #[error("Other AOT error: {0}")]
111 Other(String),
112}
113
114pub type ExecuteFunc<F, CTX> =
119 unsafe fn(pre_compute: *const u8, exec_state: &mut VmExecState<F, GuestMemory, CTX>);
120
121#[cfg(feature = "tco")]
128pub type Handler<F, CTX> = unsafe fn(
129 interpreter: &InterpretedInstance<F, CTX>,
130 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
131);
132
133pub trait InterpreterExecutor<F> {
138 fn pre_compute_size(&self) -> usize;
139
140 #[cfg(not(feature = "tco"))]
141 fn pre_compute<Ctx>(
142 &self,
143 pc: u32,
144 inst: &Instruction<F>,
145 data: &mut [u8],
146 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
147 where
148 Ctx: ExecutionCtxTrait;
149
150 #[cfg(feature = "tco")]
155 fn handler<Ctx>(
156 &self,
157 pc: u32,
158 inst: &Instruction<F>,
159 data: &mut [u8],
160 ) -> Result<Handler<F, Ctx>, StaticProgramError>
161 where
162 Ctx: ExecutionCtxTrait;
163}
164
165#[cfg(feature = "aot")]
166pub trait AotExecutor<F> {
167 fn is_aot_supported(&self, _inst: &Instruction<F>) -> bool {
168 false
169 }
170
171 fn generate_x86_asm(&self, _inst: &Instruction<F>, _pc: u32) -> Result<String, AotError> {
182 unimplemented!()
183 }
184 }
186#[cfg(feature = "aot")]
187pub trait Executor<F>: InterpreterExecutor<F> + AotExecutor<F> {}
188#[cfg(feature = "aot")]
189impl<F, T> Executor<F> for T where T: InterpreterExecutor<F> + AotExecutor<F> {}
190
191#[cfg(not(feature = "aot"))]
192pub trait Executor<F>: InterpreterExecutor<F> {}
193#[cfg(not(feature = "aot"))]
194impl<F, T> Executor<F> for T where T: InterpreterExecutor<F> {}
195
196pub trait InterpreterMeteredExecutor<F> {
201 fn metered_pre_compute_size(&self) -> usize;
202
203 #[cfg(not(feature = "tco"))]
204 fn metered_pre_compute<Ctx>(
205 &self,
206 air_idx: usize,
207 pc: u32,
208 inst: &Instruction<F>,
209 data: &mut [u8],
210 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
211 where
212 Ctx: MeteredExecutionCtxTrait;
213
214 #[cfg(feature = "tco")]
220 fn metered_handler<Ctx>(
221 &self,
222 air_idx: usize,
223 pc: u32,
224 inst: &Instruction<F>,
225 data: &mut [u8],
226 ) -> Result<Handler<F, Ctx>, StaticProgramError>
227 where
228 Ctx: MeteredExecutionCtxTrait;
229}
230
231#[cfg(feature = "aot")]
232pub trait AotMeteredExecutor<F> {
233 fn is_aot_metered_supported(&self, _inst: &Instruction<F>) -> bool {
234 false
235 }
236
237 fn generate_x86_metered_asm(
238 &self,
239 _inst: &Instruction<F>,
240 _pc: u32,
241 _chip_idx: usize,
242 _config: &SystemConfig,
243 ) -> Result<String, AotError> {
244 unimplemented!()
245 }
246}
247
248#[cfg(feature = "aot")]
249pub trait MeteredExecutor<F>: InterpreterMeteredExecutor<F> + AotMeteredExecutor<F> {}
250#[cfg(feature = "aot")]
251impl<F, T> MeteredExecutor<F> for T where T: InterpreterMeteredExecutor<F> + AotMeteredExecutor<F> {}
252
253#[cfg(not(feature = "aot"))]
254pub trait MeteredExecutor<F>: InterpreterMeteredExecutor<F> {}
255#[cfg(not(feature = "aot"))]
256impl<F, T> MeteredExecutor<F> for T where T: InterpreterMeteredExecutor<F> {}
257
258pub trait PreflightExecutor<F, RA = MatrixRecordArena<F>> {
264 fn execute(
267 &self,
268 state: VmStateMut<F, TracingMemory, RA>,
269 instruction: &Instruction<F>,
270 ) -> Result<(), ExecutionError>;
271
272 fn get_opcode_name(&self, opcode: usize) -> String;
275}
276
277#[derive(derive_new::new)]
281pub struct VmStateMut<'a, F, MEM, RA> {
282 pub pc: &'a mut u32,
283 pub memory: &'a mut MEM,
284 pub streams: &'a mut Streams<F>,
285 pub rng: &'a mut StdRng,
286 pub(crate) custom_pvs: &'a mut Vec<Option<F>>,
288 pub ctx: &'a mut RA,
289 #[cfg(feature = "metrics")]
290 pub metrics: &'a mut VmMetrics,
291}
292
293#[derive(Clone, AlignedBytesBorrow)]
296#[repr(C)]
297pub struct E2PreCompute<DATA> {
298 pub chip_idx: u32,
299 pub data: DATA,
300}
301
302#[repr(C)]
303#[derive(Clone, Copy, Debug, PartialEq, Default, AlignedBorrow, Serialize, Deserialize)]
304pub struct ExecutionState<T> {
305 pub pc: T,
306 pub timestamp: T,
307}
308
309#[derive(Clone, Copy, Debug)]
310pub struct ExecutionBus {
311 pub inner: PermutationCheckBus,
312}
313
314impl ExecutionBus {
315 pub const fn new(index: BusIndex) -> Self {
316 Self {
317 inner: PermutationCheckBus::new(index),
318 }
319 }
320
321 #[inline(always)]
322 pub fn index(&self) -> BusIndex {
323 self.inner.index
324 }
325}
326
327#[derive(Copy, Clone, Debug)]
328pub struct ExecutionBridge {
329 execution_bus: ExecutionBus,
330 program_bus: ProgramBus,
331}
332
333pub struct ExecutionBridgeInteractor<AB: InteractionBuilder> {
334 execution_bus: ExecutionBus,
335 program_bus: ProgramBus,
336 opcode: AB::Expr,
337 operands: Vec<AB::Expr>,
338 from_state: ExecutionState<AB::Expr>,
339 to_state: ExecutionState<AB::Expr>,
340}
341
342pub enum PcIncOrSet<T> {
343 Inc(T),
344 Set(T),
345}
346
347impl<T> ExecutionState<T> {
348 pub fn new(pc: impl Into<T>, timestamp: impl Into<T>) -> Self {
349 Self {
350 pc: pc.into(),
351 timestamp: timestamp.into(),
352 }
353 }
354
355 #[allow(clippy::should_implement_trait)]
356 pub fn from_iter<I: Iterator<Item = T>>(iter: &mut I) -> Self {
357 let mut next = || iter.next().unwrap();
358 Self {
359 pc: next(),
360 timestamp: next(),
361 }
362 }
363
364 pub fn flatten(self) -> [T; 2] {
365 [self.pc, self.timestamp]
366 }
367
368 pub fn get_width() -> usize {
369 2
370 }
371
372 pub fn map<U: Clone, F: Fn(T) -> U>(self, function: F) -> ExecutionState<U> {
373 ExecutionState::from_iter(&mut self.flatten().map(function).into_iter())
374 }
375}
376
377impl ExecutionBus {
378 pub fn execute_and_increment_pc<AB: InteractionBuilder>(
380 &self,
381 builder: &mut AB,
382 enabled: impl Into<AB::Expr>,
383 prev_state: ExecutionState<AB::Expr>,
384 timestamp_change: impl Into<AB::Expr>,
385 ) {
386 let next_state = ExecutionState {
387 pc: prev_state.pc.clone() + AB::F::ONE,
388 timestamp: prev_state.timestamp.clone() + timestamp_change.into(),
389 };
390 self.execute(builder, enabled, prev_state, next_state);
391 }
392
393 pub fn execute<AB: InteractionBuilder>(
395 &self,
396 builder: &mut AB,
397 enabled: impl Into<AB::Expr>,
398 prev_state: ExecutionState<impl Into<AB::Expr>>,
399 next_state: ExecutionState<impl Into<AB::Expr>>,
400 ) {
401 let enabled = enabled.into();
402 self.inner.receive(
403 builder,
404 [prev_state.pc.into(), prev_state.timestamp.into()],
405 enabled.clone(),
406 );
407 self.inner.send(
408 builder,
409 [next_state.pc.into(), next_state.timestamp.into()],
410 enabled,
411 );
412 }
413}
414
415impl ExecutionBridge {
416 pub fn new(execution_bus: ExecutionBus, program_bus: ProgramBus) -> Self {
417 Self {
418 execution_bus,
419 program_bus,
420 }
421 }
422
423 pub fn execute_and_increment_or_set_pc<AB: InteractionBuilder>(
426 &self,
427 opcode: impl Into<AB::Expr>,
428 operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
429 from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
430 timestamp_change: impl Into<AB::Expr>,
431 pc_kind: impl Into<PcIncOrSet<AB::Expr>>,
432 ) -> ExecutionBridgeInteractor<AB> {
433 let to_state = ExecutionState {
434 pc: match pc_kind.into() {
435 PcIncOrSet::Set(to_pc) => to_pc,
436 PcIncOrSet::Inc(pc_inc) => from_state.pc.clone().into() + pc_inc,
437 },
438 timestamp: from_state.timestamp.clone().into() + timestamp_change.into(),
439 };
440 self.execute(opcode, operands, from_state, to_state)
441 }
442
443 pub fn execute_and_increment_pc<AB: InteractionBuilder>(
444 &self,
445 opcode: impl Into<AB::Expr>,
446 operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
447 from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
448 timestamp_change: impl Into<AB::Expr>,
449 ) -> ExecutionBridgeInteractor<AB> {
450 let to_state = ExecutionState {
451 pc: from_state.pc.clone().into() + AB::Expr::from_canonical_u32(DEFAULT_PC_STEP),
452 timestamp: from_state.timestamp.clone().into() + timestamp_change.into(),
453 };
454 self.execute(opcode, operands, from_state, to_state)
455 }
456
457 pub fn execute<AB: InteractionBuilder>(
458 &self,
459 opcode: impl Into<AB::Expr>,
460 operands: impl IntoIterator<Item = impl Into<AB::Expr>>,
461 from_state: ExecutionState<impl Into<AB::Expr> + Clone>,
462 to_state: ExecutionState<impl Into<AB::Expr>>,
463 ) -> ExecutionBridgeInteractor<AB> {
464 ExecutionBridgeInteractor {
465 execution_bus: self.execution_bus,
466 program_bus: self.program_bus,
467 opcode: opcode.into(),
468 operands: operands.into_iter().map(Into::into).collect(),
469 from_state: from_state.map(Into::into),
470 to_state: to_state.map(Into::into),
471 }
472 }
473}
474
475impl<AB: InteractionBuilder> ExecutionBridgeInteractor<AB> {
476 pub fn eval(self, builder: &mut AB, enabled: impl Into<AB::Expr>) {
478 let enabled = enabled.into();
479
480 self.program_bus.lookup_instruction(
482 builder,
483 self.from_state.pc.clone(),
484 self.opcode,
485 self.operands,
486 enabled.clone(),
487 );
488
489 self.execution_bus
490 .execute(builder, enabled, self.from_state, self.to_state);
491 }
492}
493
494impl<T: FieldAlgebra> From<(u32, Option<T>)> for PcIncOrSet<T> {
495 fn from((pc_inc, to_pc): (u32, Option<T>)) -> Self {
496 match to_pc {
497 None => PcIncOrSet::Inc(T::from_canonical_u32(pc_inc)),
498 Some(to_pc) => PcIncOrSet::Set(to_pc),
499 }
500 }
501}
502
503#[allow(clippy::too_many_arguments)]
512pub trait PhantomSubExecutor<F>: Send + Sync {
513 fn phantom_execute(
514 &self,
515 memory: &GuestMemory,
516 streams: &mut Streams<F>,
517 rng: &mut StdRng,
518 discriminant: PhantomDiscriminant,
519 a: u32,
520 b: u32,
521 c_upper: u16,
522 ) -> eyre::Result<()>;
523}