openvm_circuit/arch/
interpreter_preflight.rs1#[cfg(feature = "metrics")]
2use std::collections::BTreeMap;
3use std::{iter::repeat_n, sync::Arc};
4
5#[cfg(not(feature = "parallel"))]
6use itertools::Itertools;
7use openvm_instructions::{instruction::Instruction, program::Program, LocalOpcode, SystemOpcode};
8use openvm_stark_backend::{
9 p3_field::{Field, PrimeField32},
10 p3_maybe_rayon::prelude::*,
11};
12
13use crate::{
14 arch::{
15 execution_mode::PreflightCtx, interpreter::get_pc_index, Arena, ExecutionError, ExecutorId,
16 ExecutorInventory, PreflightExecutor, StaticProgramError, VmExecState,
17 },
18 system::memory::online::TracingMemory,
19};
20
21pub struct PreflightInterpretedInstance<F, E> {
24 inventory: Arc<ExecutorInventory<E>>,
27
28 pc_handler: Vec<PcEntry<F>>,
34 execution_frequencies: Vec<u32>,
37 pc_base: u32,
38
39 pub(super) executor_idx_to_air_idx: Vec<usize>,
40}
41
42#[repr(C)]
43#[derive(Clone)]
44pub struct PcEntry<F> {
45 pub insn: Instruction<F>,
49 pub executor_idx: ExecutorId,
50}
51
52impl<F: Field, E> PreflightInterpretedInstance<F, E> {
53 pub fn new(
59 program: &Program<F>,
60 inventory: Arc<ExecutorInventory<E>>,
61 executor_idx_to_air_idx: Vec<usize>,
62 ) -> Result<Self, StaticProgramError> {
63 if inventory.executors().len() > u32::MAX as usize {
64 return Err(StaticProgramError::TooManyExecutors);
66 }
67 let len = program.instructions_and_debug_infos.len();
68 let pc_base = program.pc_base;
69 let base_idx = get_pc_index(pc_base);
70 let mut pc_handler = Vec::with_capacity(base_idx + len);
71 pc_handler.extend(repeat_n(PcEntry::undefined(), base_idx));
72 for insn_and_debug_info in &program.instructions_and_debug_infos {
73 if let Some((insn, _)) = insn_and_debug_info {
74 let insn = insn.clone();
75 let executor_idx = if insn.opcode == SystemOpcode::TERMINATE.global_opcode() {
76 0
78 } else {
79 *inventory.instruction_lookup.get(&insn.opcode).ok_or(
80 StaticProgramError::ExecutorNotFound {
81 opcode: insn.opcode,
82 },
83 )?
84 };
85 assert!(
86 (executor_idx as usize) < inventory.executors.len(),
87 "ExecutorInventory ensures executor_idx is in bounds"
88 );
89 let pc_entry = PcEntry { insn, executor_idx };
90 pc_handler.push(pc_entry);
91 } else {
92 pc_handler.push(PcEntry::undefined());
93 }
94 }
95 Ok(Self {
96 inventory,
97 execution_frequencies: vec![0u32; base_idx + len],
98 pc_base,
99 pc_handler,
100 executor_idx_to_air_idx,
101 })
102 }
103
104 pub fn executors(&self) -> &[E] {
105 &self.inventory.executors
106 }
107
108 pub fn filtered_execution_frequencies(&self) -> Vec<u32> {
109 let base_idx = get_pc_index(self.pc_base);
110 self.pc_handler
111 .par_iter()
112 .zip_eq(&self.execution_frequencies)
113 .skip(base_idx)
114 .filter_map(|(entry, freq)| entry.is_some().then_some(*freq))
115 .collect()
116 }
117
118 pub fn reset_execution_frequencies(&mut self) {
119 self.execution_frequencies.fill(0);
120 }
121}
122
123impl<F: PrimeField32, E> PreflightInterpretedInstance<F, E> {
124 #[cfg(feature = "metrics")]
125 pub fn opcode_counts_by_air<RA>(&self) -> BTreeMap<(usize, String), u64>
126 where
127 RA: Arena,
128 E: PreflightExecutor<F, RA>,
129 {
130 let mut counts = BTreeMap::new();
131 for (entry, &freq) in self.pc_handler.iter().zip(&self.execution_frequencies) {
132 if freq == 0
133 || !entry.is_some()
134 || entry.insn.opcode == SystemOpcode::TERMINATE.global_opcode()
135 {
136 continue;
137 }
138 let executor_idx = entry.executor_idx as usize;
139 let air_idx = unsafe {
140 *self.executor_idx_to_air_idx.get_unchecked(executor_idx)
143 };
144 let executor = unsafe {
145 self.inventory.executors.get_unchecked(executor_idx)
147 };
148 let opcode = executor.get_opcode_name(entry.insn.opcode.as_usize());
149 *counts.entry((air_idx, opcode)).or_insert(0) += freq as u64;
150 }
151 counts
152 }
153
154 pub fn execute_from_state<RA>(
156 &mut self,
157 state: &mut VmExecState<F, TracingMemory, PreflightCtx<RA>>,
158 ) -> Result<(), ExecutionError>
159 where
160 RA: Arena,
161 E: PreflightExecutor<F, RA>,
162 {
163 loop {
164 if let Ok(Some(_)) = state.exit_code {
165 break;
167 }
168 if state.ctx.instret_left == 0 {
169 break;
171 }
172
173 self.execute_instruction(state)?;
175 state.ctx.instret_left -= 1;
176 }
177
178 Ok(())
179 }
180
181 #[inline(always)]
183 fn execute_instruction<RA>(
184 &mut self,
185 state: &mut VmExecState<F, TracingMemory, PreflightCtx<RA>>,
186 ) -> Result<(), ExecutionError>
187 where
188 RA: Arena,
189 E: PreflightExecutor<F, RA>,
190 {
191 let pc = state.pc();
192 let pc_idx = get_pc_index(pc);
193 let pc_entry = self
194 .pc_handler
195 .get(pc_idx)
196 .ok_or_else(|| ExecutionError::PcOutOfBounds(pc))?;
197 unsafe {
200 *self.execution_frequencies.get_unchecked_mut(pc_idx) += 1;
201 };
202 tracing::trace!("pc: {pc:#x} | {:?}", pc_entry.insn);
203
204 if !pc_entry.is_some() {
205 return Err(ExecutionError::Unreachable(pc));
206 }
207
208 let opcode = pc_entry.insn.opcode;
209 let c = pc_entry.insn.c;
210 if opcode == SystemOpcode::TERMINATE.global_opcode() {
212 state.exit_code = Ok(Some(c.as_canonical_u32()));
213 return Ok(());
214 }
215
216 let executor = unsafe {
219 self.inventory
220 .executors
221 .get_unchecked(pc_entry.executor_idx as usize)
222 };
223
224 tracing::trace!(
226 "opcode: {} | timestamp: {}",
227 executor.get_opcode_name(pc_entry.insn.opcode.as_usize()),
228 state.memory.timestamp()
229 );
230 let arena = unsafe {
231 let air_idx = *self
233 .executor_idx_to_air_idx
234 .get_unchecked(pc_entry.executor_idx as usize);
235 state.ctx.arenas.get_unchecked_mut(air_idx)
238 };
239 let vm_state_mut = state.vm_state.into_mut(arena);
240 executor.execute(vm_state_mut, &pc_entry.insn)?;
241
242 #[cfg(feature = "metrics")]
243 {
244 crate::metrics::update_instruction_metrics(state, executor, pc, pc_entry);
245 }
246
247 Ok(())
248 }
249}
250
251impl<F> PcEntry<F> {
252 pub fn is_some(&self) -> bool {
253 self.executor_idx != u32::MAX
254 }
255}
256
257impl<F: Default> PcEntry<F> {
258 fn undefined() -> Self {
259 Self {
260 insn: Instruction::default(),
261 executor_idx: u32::MAX,
262 }
263 }
264}
265
266#[macro_export]
269macro_rules! execute_spanned {
270 ($name:literal, $executor:expr, $state:expr) => {{
271 #[cfg(feature = "metrics")]
272 let start = std::time::Instant::now();
273 #[cfg(feature = "metrics")]
274 let start_instret_left = $state.ctx.instret_left;
275
276 let result = $executor.execute_from_state($state);
277
278 #[cfg(feature = "metrics")]
279 {
280 let elapsed = start.elapsed();
281 let insns = start_instret_left - $state.ctx.instret_left;
282 tracing::info!("instructions_executed={insns}");
283 metrics::counter!(concat!($name, "_insns")).absolute(insns);
284 metrics::gauge!(concat!($name, "_insn_mi/s"))
285 .set(insns as f64 / elapsed.as_micros() as f64);
286 }
287 result
288 }};
289}