openvm_circuit/arch/
interpreter.rs

1use std::{
2    alloc::{alloc, dealloc, handle_alloc_error, Layout},
3    borrow::{Borrow, BorrowMut},
4    iter::repeat_n,
5    ptr::NonNull,
6};
7
8use itertools::Itertools;
9use openvm_circuit_primitives_derive::AlignedBytesBorrow;
10use openvm_instructions::{
11    exe::{SparseMemoryImage, VmExe},
12    instruction::Instruction,
13    program::{Program, DEFAULT_PC_STEP},
14    LocalOpcode, SystemOpcode,
15};
16use openvm_stark_backend::p3_field::PrimeField32;
17
18#[cfg(feature = "tco")]
19use crate::arch::Handler;
20use crate::{
21    arch::{
22        execution_mode::{
23            ExecutionCtx, ExecutionCtxTrait, MeteredCostCtx, MeteredCtx, MeteredExecutionCtxTrait,
24            Segment,
25        },
26        ExecuteFunc, ExecutionError, Executor, ExecutorInventory, ExitCode, MeteredExecutor,
27        StaticProgramError, Streams, SystemConfig, VmExecState, VmState,
28    },
29    system::memory::online::GuestMemory,
30};
31
32/// VM pure executor(E1/E2 executor) which doesn't consider trace generation.
33/// Note: This executor doesn't hold any VM state and can be used for multiple execution.
34///
35/// The generic `Ctx` and constructor determine whether this supported pure execution or metered
36/// execution.
37// NOTE: the lifetime 'a represents the lifetime of borrowed ExecutorInventory, which must outlive
38// the InterpretedInstance because `pre_compute_buf` may contain pointers to references held by
39// executors.
40pub struct InterpretedInstance<'a, F, Ctx> {
41    system_config: &'a SystemConfig,
42    // SAFETY: this is not actually dead code, but `pre_compute_insns` contains raw pointer refers
43    // to this buffer.
44    #[allow(dead_code)]
45    pre_compute_buf: AlignedBuf,
46    /// Instruction table of function pointers and pointers to the pre-computed buffer. Indexed by
47    /// `pc_index = pc / DEFAULT_PC_STEP`.
48    /// SAFETY: The first `pc_base / DEFAULT_PC_STEP` entries will be unreachable. We do this to
49    /// avoid needing to subtract `pc_base` during runtime.
50    #[cfg(not(feature = "tco"))]
51    pre_compute_insns: Vec<PreComputeInstruction<F, Ctx>>,
52    #[cfg(feature = "tco")]
53    pre_compute_max_size: usize,
54    /// Handler function pointers for tail call optimization.
55    #[cfg(feature = "tco")]
56    handlers: Vec<Handler<F, Ctx>>,
57
58    pc_start: u32,
59
60    init_memory: SparseMemoryImage,
61}
62
63#[repr(C)]
64#[cfg_attr(feature = "tco", allow(dead_code))]
65pub(crate) struct PreComputeInstruction<F, Ctx> {
66    pub(crate) handler: ExecuteFunc<F, Ctx>,
67    pub(crate) pre_compute: *const u8,
68}
69
70unsafe impl<F, Ctx> Send for PreComputeInstruction<F, Ctx> {}
71unsafe impl<F, Ctx> Sync for PreComputeInstruction<F, Ctx> {}
72
73#[derive(AlignedBytesBorrow, Clone)]
74#[repr(C)]
75struct TerminatePreCompute {
76    exit_code: u32,
77}
78
79macro_rules! run {
80    ($span:literal, $interpreter:ident, $exec_state:ident, $ctx:ident) => {{
81        tracing::info_span!($span).in_scope(|| -> Result<(), ExecutionError> {
82            // SAFETY:
83            // - it is the responsibility of each Executor to ensure that pre_compute_insts contains
84            //   valid function pointers and pre-computed data
85            #[cfg(not(feature = "tco"))]
86            {
87                unsafe {
88                    execute_trampoline(&mut $exec_state, &$interpreter.pre_compute_insns);
89                }
90            }
91            #[cfg(feature = "tco")]
92            {
93                if $ctx::should_suspend(&mut $exec_state) {
94                    return Ok(());
95                }
96
97                let handler = $interpreter
98                    .get_handler($exec_state.pc())
99                    .ok_or(ExecutionError::PcOutOfBounds($exec_state.pc()))?;
100                // SAFETY:
101                // - handler is generated by Executor, MeteredExecutor traits
102                // - it is the responsibility of each Executor to ensure handler is safe given a
103                //   valid VM state
104                unsafe {
105                    handler($interpreter, &mut $exec_state);
106                }
107            }
108            Ok(())
109        })?;
110    }};
111}
112
113// Constructors for E1 and E2 respectively, which generate pre-computed buffers and function
114// pointers
115// - Generic in `Ctx`
116
117impl<'a, F, Ctx> InterpretedInstance<'a, F, Ctx>
118where
119    F: PrimeField32,
120    Ctx: ExecutionCtxTrait,
121{
122    /// Creates a new interpreter instance for pure execution.
123    // (E1 execution)
124    pub fn new<E>(
125        inventory: &'a ExecutorInventory<E>,
126        exe: &VmExe<F>,
127    ) -> Result<Self, StaticProgramError>
128    where
129        E: Executor<F>,
130    {
131        let program = &exe.program;
132        let pre_compute_max_size = get_pre_compute_max_size(program, inventory);
133        let mut pre_compute_buf = alloc_pre_compute_buf(program, pre_compute_max_size);
134        let mut split_pre_compute_buf =
135            split_pre_compute_buf(program, &mut pre_compute_buf, pre_compute_max_size);
136        #[cfg(not(feature = "tco"))]
137        let pre_compute_insns = get_pre_compute_instructions::<F, Ctx, E>(
138            program,
139            inventory,
140            &mut split_pre_compute_buf,
141        )?;
142        let pc_start = exe.pc_start;
143        let init_memory = exe.init_memory.clone();
144        #[cfg(feature = "tco")]
145        let handlers = repeat_n(&None, get_pc_index(program.pc_base))
146            .chain(program.instructions_and_debug_infos.iter())
147            .zip_eq(split_pre_compute_buf.iter_mut())
148            .enumerate()
149            .map(
150                |(pc_idx, (inst_opt, pre_compute))| -> Result<Handler<F, Ctx>, StaticProgramError> {
151                    if let Some((inst, _)) = inst_opt {
152                        let pc = pc_idx as u32 * DEFAULT_PC_STEP;
153                        if get_system_opcode_handler::<F, Ctx>(inst, pre_compute).is_some() {
154                            Ok(terminate_execute_e12_tco_handler)
155                        } else {
156                            // unwrap because get_pre_compute_instructions would have errored
157                            // already on DisabledOperation
158                            let executor = inventory.get_executor(inst.opcode).unwrap();
159                            executor.handler(pc, inst, pre_compute)
160                        }
161                    } else {
162                        Ok(unreachable_tco_handler)
163                    }
164                },
165            )
166            .collect::<Result<Vec<_>, _>>()?;
167
168        Ok(Self {
169            system_config: inventory.config(),
170            pre_compute_buf,
171            #[cfg(not(feature = "tco"))]
172            pre_compute_insns,
173            pc_start,
174            init_memory,
175            #[cfg(feature = "tco")]
176            pre_compute_max_size,
177            #[cfg(feature = "tco")]
178            handlers,
179        })
180    }
181
182    pub fn create_initial_vm_state(&self, inputs: impl Into<Streams<F>>) -> VmState<F> {
183        VmState::initial(self.system_config, &self.init_memory, self.pc_start, inputs)
184    }
185
186    /// # Safety
187    /// - This function assumes that the `pc` is within program bounds - this should be the case if
188    ///   the pc is checked to be in bounds before jumping to it.
189    /// - The returned slice may not be entirely initialized, but it is the job of each Executor to
190    ///   initialize the parts of the buffer that the instruction handler will use.
191    #[cfg(feature = "tco")]
192    #[inline(always)]
193    pub fn get_pre_compute(&self, pc: u32) -> *const u8 {
194        let pc_idx = get_pc_index(pc);
195        // SAFETY:
196        // - we assume that pc is in bounds
197        // - pre_compute_buf is allocated for pre_compute_max_size * program_len bytes, with each
198        //   instruction getting pre_compute_max_size bytes
199        // - self.pre_compute_buf.ptr is non-null
200        // - initialization of the contents of the slice is the responsibility of each Executor
201        debug_assert!(
202            (pc_idx + 1) * self.pre_compute_max_size <= self.pre_compute_buf.layout.size()
203        );
204        unsafe {
205            let ptr = self
206                .pre_compute_buf
207                .ptr
208                .add(pc_idx * self.pre_compute_max_size);
209            ptr
210        }
211    }
212
213    #[cfg(feature = "tco")]
214    #[inline(always)]
215    pub fn get_handler(&self, pc: u32) -> Option<Handler<F, Ctx>> {
216        let pc_idx = get_pc_index(pc);
217        self.handlers.get(pc_idx).copied()
218    }
219}
220
221impl<'a, F, Ctx> InterpretedInstance<'a, F, Ctx>
222where
223    F: PrimeField32,
224    Ctx: MeteredExecutionCtxTrait,
225{
226    /// Creates a new interpreter instance for pure execution.
227    // (E1 execution)
228    pub fn new_metered<E>(
229        inventory: &'a ExecutorInventory<E>,
230        exe: &VmExe<F>,
231        executor_idx_to_air_idx: &[usize],
232    ) -> Result<Self, StaticProgramError>
233    where
234        E: MeteredExecutor<F>,
235    {
236        let program = &exe.program;
237        let pre_compute_max_size = get_metered_pre_compute_max_size(program, inventory);
238        let mut pre_compute_buf = alloc_pre_compute_buf(program, pre_compute_max_size);
239        let mut split_pre_compute_buf =
240            split_pre_compute_buf(program, &mut pre_compute_buf, pre_compute_max_size);
241        #[cfg(not(feature = "tco"))]
242        let pre_compute_insns = get_metered_pre_compute_instructions::<F, Ctx, E>(
243            program,
244            inventory,
245            executor_idx_to_air_idx,
246            &mut split_pre_compute_buf,
247        )?;
248
249        let pc_start = exe.pc_start;
250        let init_memory = exe.init_memory.clone();
251        #[cfg(feature = "tco")]
252        let handlers = repeat_n(&None, get_pc_index(program.pc_base))
253            .chain(program.instructions_and_debug_infos.iter())
254            .zip_eq(split_pre_compute_buf.iter_mut())
255            .enumerate()
256            .map(
257                |(pc_idx, (inst_opt, pre_compute))| -> Result<Handler<F, Ctx>, StaticProgramError> {
258                    if let Some((inst, _)) = inst_opt {
259                        let pc = pc_idx as u32 * DEFAULT_PC_STEP;
260                        if get_system_opcode_handler::<F, Ctx>(inst, pre_compute).is_some() {
261                            Ok(terminate_execute_e12_tco_handler)
262                        } else {
263                            // unwrap because get_pre_compute_instructions would have errored
264                            // already on DisabledOperation
265                            let executor_idx = inventory.instruction_lookup[&inst.opcode] as usize;
266                            let executor = &inventory.executors[executor_idx];
267                            let air_idx = executor_idx_to_air_idx[executor_idx];
268                            executor.metered_handler(air_idx, pc, inst, pre_compute)
269                        }
270                    } else {
271                        Ok(unreachable_tco_handler)
272                    }
273                },
274            )
275            .collect::<Result<Vec<_>, _>>()?;
276
277        Ok(Self {
278            system_config: inventory.config(),
279            pre_compute_buf,
280            #[cfg(not(feature = "tco"))]
281            pre_compute_insns,
282            pc_start,
283            init_memory,
284            #[cfg(feature = "tco")]
285            pre_compute_max_size,
286            #[cfg(feature = "tco")]
287            handlers,
288        })
289    }
290}
291
292// Execute functions specialize to relevant Ctx types to provide more streamlines APIs
293
294impl<'a, F> InterpretedInstance<'a, F, ExecutionCtx>
295where
296    F: PrimeField32,
297{
298    /// Pure execution, without metering, for the given `inputs`. Execution begins from the initial
299    /// state specified by the `VmExe`. This function executes the program until either termination
300    /// if `num_insns` is `None` or for exactly `num_insns` instructions if `num_insns` is `Some`.
301    ///
302    /// Returns the final VM state when execution stops.
303    pub fn execute(
304        &self,
305        inputs: impl Into<Streams<F>>,
306        num_insns: Option<u64>,
307    ) -> Result<VmState<F, GuestMemory>, ExecutionError> {
308        let vm_state =
309            VmState::initial(self.system_config, &self.init_memory, self.pc_start, inputs);
310        self.execute_from_state(vm_state, num_insns)
311    }
312
313    /// Pure execution, without metering, from the given `VmState`. This function executes the
314    /// program until either termination if `num_insns` is `None` or for exactly `num_insns`
315    /// instructions if `num_insns` is `Some`.
316    ///
317    /// Returns the final VM state when execution stops.
318    pub fn execute_from_state(
319        &self,
320        from_state: VmState<F, GuestMemory>,
321        num_insns: Option<u64>,
322    ) -> Result<VmState<F, GuestMemory>, ExecutionError> {
323        let ctx = ExecutionCtx::new(num_insns);
324        let mut exec_state = VmExecState::new(from_state, ctx);
325
326        #[cfg(feature = "metrics")]
327        let start = std::time::Instant::now();
328        #[cfg(feature = "metrics")]
329        let start_instret_left = exec_state.ctx.instret_left;
330
331        run!("execute_e1", self, exec_state, ExecutionCtx);
332
333        #[cfg(feature = "metrics")]
334        {
335            let elapsed = start.elapsed();
336            let insns = start_instret_left - exec_state.ctx.instret_left;
337            tracing::info!("instructions_executed={insns}");
338            metrics::counter!("execute_e1_insns").absolute(insns);
339            metrics::gauge!("execute_e1_insn_mi/s").set(insns as f64 / elapsed.as_micros() as f64);
340        }
341        tracing::debug!("pc: {}", exec_state.vm_state.pc());
342        tracing::debug!("interpreter exit code {:?}", exec_state.exit_code);
343        tracing::debug!("num_insns {:?}", num_insns);
344
345        if num_insns.is_some() {
346            check_exit_code(exec_state.exit_code)?;
347        } else {
348            check_termination(exec_state.exit_code)?;
349        }
350        Ok(exec_state.vm_state)
351    }
352}
353
354impl<'a, F> InterpretedInstance<'a, F, MeteredCtx>
355where
356    F: PrimeField32,
357{
358    /// Metered execution for the given `inputs`. Execution begins from the initial
359    /// state specified by the `VmExe`. This function executes the program until termination.
360    ///
361    /// Returns the segmentation boundary data and the final VM state when execution stops.
362    pub fn execute_metered(
363        &self,
364        inputs: impl Into<Streams<F>>,
365        ctx: MeteredCtx,
366    ) -> Result<(Vec<Segment>, VmState<F, GuestMemory>), ExecutionError> {
367        let vm_state = self.create_initial_vm_state(inputs);
368        self.execute_metered_from_state(vm_state, ctx)
369    }
370
371    /// Metered execution for the given `VmState`. This function executes the program until
372    /// termination.
373    ///
374    /// Returns the segmentation boundary data and the final VM state when execution stops.
375    ///
376    /// The [MeteredCtx] can be constructed using either
377    /// [VmExecutor::build_metered_ctx](super::VmExecutor::build_metered_ctx) or
378    /// [VirtualMachine::build_metered_ctx](super::VirtualMachine::build_metered_ctx).
379    pub fn execute_metered_from_state(
380        &self,
381        from_state: VmState<F, GuestMemory>,
382        ctx: MeteredCtx,
383    ) -> Result<(Vec<Segment>, VmState<F, GuestMemory>), ExecutionError> {
384        let mut exec_state = VmExecState::new(from_state, ctx);
385
386        loop {
387            exec_state = self.execute_metered_until_suspend(exec_state)?;
388            // The execution has terminated.
389            if exec_state.exit_code.is_ok() && exec_state.exit_code.as_ref().unwrap().is_some() {
390                break;
391            }
392            if exec_state.exit_code.is_err() {
393                return Err(exec_state.exit_code.unwrap_err());
394            }
395        }
396        check_termination(exec_state.exit_code)?;
397        let VmExecState { vm_state, ctx, .. } = exec_state;
398        Ok((ctx.into_segments(), vm_state))
399    }
400    /// Executes a metered virtual machine operation starting from a given execution state until
401    /// suspension.
402    ///
403    /// This function resumes and continues execution of a guest virtual machine until either it:
404    /// - Hits a suspension trigger (e.g. out of gas or a specific halt condition). ATTENTION: when
405    ///   a suspension is triggered, the VM state is not at the boundary of the last segment.
406    ///   Instead, the VM state is slightly after the segment boundary.
407    /// - Completes its run based on the instructions or context provided.
408    ///
409    /// # Parameters
410    /// - `self`: The reference to the current executor or VM context.
411    /// - `exec_state`: A mutable `VmExecState<F, GuestMemory, MeteredCtx>` which represents the
412    ///   execution state of the virtual machine, including its program counter (`pc`), instruction
413    ///   retirement (`instret`), and execution context (`MeteredCtx`).
414    ///
415    /// # Returns
416    /// - `Ok(VmExecState<F, GuestMemory, MeteredCtx>)`: The execution state after suspension or
417    ///   normal completion.
418    /// - `Err(ExecutionError)`: If there is an error during execution, such as an invalid state or
419    ///   run-time error.
420    pub fn execute_metered_until_suspend(
421        &self,
422        mut exec_state: VmExecState<F, GuestMemory, MeteredCtx>,
423    ) -> Result<VmExecState<F, GuestMemory, MeteredCtx>, ExecutionError> {
424        #[cfg(feature = "metrics")]
425        let start = std::time::Instant::now();
426        #[cfg(feature = "metrics")]
427        let start_instret = exec_state.ctx.segmentation_ctx.instret;
428
429        // Start execution
430        run!("execute_metered", self, exec_state, MeteredCtx);
431
432        #[cfg(feature = "metrics")]
433        {
434            let elapsed = start.elapsed();
435            let insns = exec_state.ctx.segmentation_ctx.instret - start_instret;
436            tracing::info!("instructions_executed={insns}");
437            metrics::counter!("execute_metered_insns").absolute(insns);
438            metrics::gauge!("execute_metered_insn_mi/s")
439                .set(insns as f64 / elapsed.as_micros() as f64);
440        }
441        Ok(exec_state)
442    }
443}
444
445impl<'a, F> InterpretedInstance<'a, F, MeteredCostCtx>
446where
447    F: PrimeField32,
448{
449    /// Metered cost execution for the given `inputs`. Execution begins from the initial
450    /// state specified by the `VmExe`. This function executes the program until termination.
451    ///
452    /// Returns the trace cost and final VM state when execution stops.
453    pub fn execute_metered_cost(
454        &self,
455        inputs: impl Into<Streams<F>>,
456        ctx: MeteredCostCtx,
457    ) -> Result<(MeteredCostCtx, VmState<F, GuestMemory>), ExecutionError> {
458        let vm_state = self.create_initial_vm_state(inputs);
459        self.execute_metered_cost_from_state(vm_state, ctx)
460    }
461
462    /// Metered cost execution for the given `VmState`. This function executes the program until
463    /// termination.
464    ///
465    /// Returns the trace cost and final VM state when execution stops.
466    pub fn execute_metered_cost_from_state(
467        &self,
468        from_state: VmState<F, GuestMemory>,
469        ctx: MeteredCostCtx,
470    ) -> Result<(MeteredCostCtx, VmState<F, GuestMemory>), ExecutionError> {
471        let mut exec_state = VmExecState::new(from_state, ctx);
472
473        #[cfg(feature = "metrics")]
474        let start = std::time::Instant::now();
475        #[cfg(feature = "metrics")]
476        let start_instret = exec_state.ctx.instret;
477
478        // Start execution
479        run!("execute_metered_cost", self, exec_state, MeteredCostCtx);
480
481        #[cfg(feature = "metrics")]
482        {
483            let elapsed = start.elapsed();
484            let insns = exec_state.ctx.instret - start_instret;
485            tracing::info!("instructions_executed={insns}");
486            metrics::counter!("execute_metered_cost_insns").absolute(insns);
487            metrics::gauge!("execute_metered_cost_insn_mi/s")
488                .set(insns as f64 / elapsed.as_micros() as f64);
489        }
490
491        check_exit_code(exec_state.exit_code)?;
492        let VmExecState { ctx, vm_state, .. } = exec_state;
493        Ok((ctx, vm_state))
494    }
495}
496
497pub(crate) fn alloc_pre_compute_buf<F>(
498    program: &Program<F>,
499    pre_compute_max_size: usize,
500) -> AlignedBuf {
501    let base_idx = get_pc_index(program.pc_base);
502    let padded_program_len = base_idx + program.instructions_and_debug_infos.len();
503    let buf_len = padded_program_len * pre_compute_max_size;
504    AlignedBuf::uninit(buf_len, pre_compute_max_size)
505}
506
507pub(crate) fn split_pre_compute_buf<'a, F>(
508    program: &Program<F>,
509    pre_compute_buf: &'a mut AlignedBuf,
510    pre_compute_max_size: usize,
511) -> Vec<&'a mut [u8]> {
512    let base_idx = get_pc_index(program.pc_base);
513    let padded_program_len = base_idx + program.instructions_and_debug_infos.len();
514    let buf_len = padded_program_len * pre_compute_max_size;
515    // SAFETY:
516    // - pre_compute_buf.ptr was allocated with exactly buf_len bytes
517    // - lifetime 'a ensures the returned slices don't outlive the AlignedBuf
518    let pre_compute_buf = unsafe { std::slice::from_raw_parts_mut(pre_compute_buf.ptr, buf_len) };
519    pre_compute_buf
520        .chunks_exact_mut(pre_compute_max_size)
521        .collect()
522}
523
524/// Executes using function pointers with the trampoline (loop) approach.
525///
526/// # Safety
527/// The `fn_ptrs` pointer to pre-computed buffers that outlive this function.
528#[cfg(not(feature = "tco"))]
529#[inline(always)]
530unsafe fn execute_trampoline<F: PrimeField32, Ctx: ExecutionCtxTrait>(
531    exec_state: &mut VmExecState<F, GuestMemory, Ctx>,
532    fn_ptrs: &[PreComputeInstruction<F, Ctx>],
533) {
534    while exec_state
535        .exit_code
536        .as_ref()
537        .is_ok_and(|exit_code| exit_code.is_none())
538    {
539        if Ctx::should_suspend(exec_state) {
540            tracing::debug!("stop because of should_suspend");
541            break;
542        }
543        let pc = exec_state.pc();
544        let pc_index = get_pc_index(pc);
545
546        if let Some(inst) = fn_ptrs.get(pc_index) {
547            // SAFETY: pre_compute assumed to live long enough
548            unsafe { (inst.handler)(inst.pre_compute, exec_state) };
549        } else {
550            exec_state.exit_code = Err(ExecutionError::PcOutOfBounds(pc));
551        }
552    }
553}
554
555#[inline(always)]
556pub fn get_pc_index(pc: u32) -> usize {
557    (pc / DEFAULT_PC_STEP) as usize
558}
559
560/// Bytes allocated according to the given Layout.
561/// Careful: this struct implements Send and Sync unsafely. Don't change the underlying data after
562/// initialization.git
563// @dev: This is duplicate from the openvm crate, but it doesn't seem worth importing `openvm` here
564// just for this.
565pub(crate) struct AlignedBuf {
566    pub ptr: *mut u8,
567    pub layout: Layout,
568}
569
570unsafe impl Send for AlignedBuf {}
571unsafe impl Sync for AlignedBuf {}
572
573impl AlignedBuf {
574    /// Allocate a new buffer whose start address is aligned to `align` bytes.
575    /// *NOTE* if `len` is zero then a creates new `NonNull` that is dangling and 16-byte aligned.
576    pub fn uninit(len: usize, align: usize) -> Self {
577        let layout = Layout::from_size_align(len, align).unwrap();
578        if layout.size() == 0 {
579            return Self {
580                ptr: NonNull::<u128>::dangling().as_ptr() as *mut u8,
581                layout,
582            };
583        }
584        // SAFETY: `len` is nonzero
585        let ptr = unsafe { alloc(layout) };
586        if ptr.is_null() {
587            handle_alloc_error(layout);
588        }
589        AlignedBuf { ptr, layout }
590    }
591}
592
593impl Drop for AlignedBuf {
594    fn drop(&mut self) {
595        if self.layout.size() != 0 {
596            // SAFETY: self.ptr was allocated with self.layout in AlignedBuf::uninit
597            unsafe {
598                dealloc(self.ptr, self.layout);
599            }
600        }
601    }
602}
603
604#[inline(always)]
605unsafe fn terminate_execute_e12_impl<F: PrimeField32, CTX: ExecutionCtxTrait>(
606    pre_compute: *const u8,
607    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
608) {
609    let pre_compute: &TerminatePreCompute =
610        std::slice::from_raw_parts(pre_compute, size_of::<TerminatePreCompute>()).borrow();
611    exec_state.exit_code = Ok(Some(pre_compute.exit_code));
612    CTX::on_terminate(exec_state);
613}
614
615#[cfg(feature = "tco")]
616unsafe fn terminate_execute_e12_tco_handler<F: PrimeField32, CTX: ExecutionCtxTrait>(
617    interpreter: &InterpretedInstance<'_, F, CTX>,
618    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
619) {
620    let pre_compute = interpreter.get_pre_compute(exec_state.vm_state.pc());
621    terminate_execute_e12_impl(pre_compute, exec_state);
622}
623
624#[cfg(feature = "tco")]
625unsafe fn unreachable_tco_handler<F: PrimeField32, CTX>(
626    _: &InterpretedInstance<'_, F, CTX>,
627    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
628) {
629    exec_state.exit_code = Err(ExecutionError::Unreachable(exec_state.vm_state.pc()));
630}
631
632pub(crate) fn get_pre_compute_max_size<F, E: Executor<F>>(
633    program: &Program<F>,
634    inventory: &ExecutorInventory<E>,
635) -> usize {
636    program
637        .instructions_and_debug_infos
638        .iter()
639        .map(|inst_opt| {
640            if let Some((inst, _)) = inst_opt {
641                if let Some(size) = system_opcode_pre_compute_size(inst) {
642                    size
643                } else {
644                    inventory
645                        .get_executor(inst.opcode)
646                        .map(|executor| executor.pre_compute_size())
647                        .unwrap()
648                }
649            } else {
650                0
651            }
652        })
653        .max()
654        .unwrap()
655        .next_power_of_two()
656}
657
658pub(crate) fn get_metered_pre_compute_max_size<F, E: MeteredExecutor<F>>(
659    program: &Program<F>,
660    inventory: &ExecutorInventory<E>,
661) -> usize {
662    program
663        .instructions_and_debug_infos
664        .iter()
665        .map(|inst_opt| {
666            if let Some((inst, _)) = inst_opt {
667                if let Some(size) = system_opcode_pre_compute_size(inst) {
668                    size
669                } else {
670                    inventory
671                        .get_executor(inst.opcode)
672                        .map(|executor| executor.metered_pre_compute_size())
673                        .unwrap()
674                }
675            } else {
676                0
677            }
678        })
679        .max()
680        .unwrap()
681        .next_power_of_two()
682}
683
684fn system_opcode_pre_compute_size<F>(inst: &Instruction<F>) -> Option<usize> {
685    if inst.opcode == SystemOpcode::TERMINATE.global_opcode() {
686        return Some(size_of::<TerminatePreCompute>());
687    }
688    None
689}
690
691#[cfg(not(feature = "tco"))]
692pub(crate) fn get_pre_compute_instructions<F, Ctx, E>(
693    program: &Program<F>,
694    inventory: &ExecutorInventory<E>,
695    pre_compute: &mut [&mut [u8]],
696) -> Result<Vec<PreComputeInstruction<F, Ctx>>, StaticProgramError>
697where
698    F: PrimeField32,
699    Ctx: ExecutionCtxTrait,
700    E: Executor<F>,
701{
702    let unreachable_handler: ExecuteFunc<F, Ctx> = |_, exec_state| {
703        exec_state.exit_code = Err(ExecutionError::Unreachable(exec_state.pc()));
704    };
705
706    repeat_n(&None, get_pc_index(program.pc_base))
707        .chain(program.instructions_and_debug_infos.iter())
708        .zip_eq(pre_compute.iter_mut())
709        .enumerate()
710        .map(|(i, (inst_opt, buf))| {
711            // SAFETY: we cast to raw pointer and then borrow to remove the lifetime. This
712            // is safe only in the current context because `buf` comes
713            // from `pre_compute_buf` which will outlive the returned
714            // `PreComputeInstruction`s.
715            let buf: &mut [u8] = unsafe { &mut *(*buf as *mut [u8]) };
716            let pre_inst = if let Some((inst, _)) = inst_opt {
717                tracing::trace!("get_pre_compute_instruction {inst:?}");
718                let pc = i as u32 * DEFAULT_PC_STEP;
719                if let Some(handler) = get_system_opcode_handler(inst, buf) {
720                    PreComputeInstruction {
721                        handler,
722                        pre_compute: buf.as_ptr(),
723                    }
724                } else if let Some(executor) = inventory.get_executor(inst.opcode) {
725                    PreComputeInstruction {
726                        handler: executor.pre_compute(pc, inst, buf)?,
727                        pre_compute: buf.as_ptr(),
728                    }
729                } else {
730                    return Err(StaticProgramError::DisabledOperation {
731                        pc,
732                        opcode: inst.opcode,
733                    });
734                }
735            } else {
736                // Dead instruction at this pc
737                PreComputeInstruction {
738                    handler: unreachable_handler,
739                    pre_compute: buf.as_ptr(),
740                }
741            };
742            Ok(pre_inst)
743        })
744        .collect::<Result<Vec<_>, _>>()
745}
746
747#[cfg(not(feature = "tco"))]
748pub(crate) fn get_metered_pre_compute_instructions<F, Ctx, E>(
749    program: &Program<F>,
750    inventory: &ExecutorInventory<E>,
751    executor_idx_to_air_idx: &[usize],
752    pre_compute: &mut [&mut [u8]],
753) -> Result<Vec<PreComputeInstruction<F, Ctx>>, StaticProgramError>
754where
755    F: PrimeField32,
756    Ctx: MeteredExecutionCtxTrait,
757    E: MeteredExecutor<F>,
758{
759    let unreachable_handler: ExecuteFunc<F, Ctx> = |_, exec_state| {
760        exec_state.exit_code = Err(ExecutionError::Unreachable(exec_state.pc()));
761    };
762    repeat_n(&None, get_pc_index(program.pc_base))
763        .chain(program.instructions_and_debug_infos.iter())
764        .zip_eq(pre_compute.iter_mut())
765        .enumerate()
766        .map(|(i, (inst_opt, buf))| {
767            // SAFETY: we cast to raw pointer and then borrow to remove the lifetime. This
768            // is safe only in the current context because `buf` comes
769            // from `pre_compute_buf` which will outlive the returned
770            // `PreComputeInstruction`s.
771            let buf: &mut [u8] = unsafe { &mut *(*buf as *mut [u8]) };
772            let pre_inst = if let Some((inst, _)) = inst_opt {
773                tracing::trace!("get_metered_pre_compute_instruction {inst:?}");
774                let pc = program.pc_base + i as u32 * DEFAULT_PC_STEP;
775                if let Some(handler) = get_system_opcode_handler(inst, buf) {
776                    PreComputeInstruction {
777                        handler,
778                        pre_compute: buf.as_ptr(),
779                    }
780                } else if let Some(&executor_idx) = inventory.instruction_lookup.get(&inst.opcode) {
781                    let executor_idx = executor_idx as usize;
782                    let executor = inventory
783                        .executors
784                        .get(executor_idx)
785                        .expect("ExecutorInventory ensures executor_idx is in bounds");
786                    let air_idx = executor_idx_to_air_idx[executor_idx];
787                    PreComputeInstruction {
788                        handler: executor.metered_pre_compute(air_idx, pc, inst, buf)?,
789                        pre_compute: buf.as_ptr(),
790                    }
791                } else {
792                    return Err(StaticProgramError::DisabledOperation {
793                        pc,
794                        opcode: inst.opcode,
795                    });
796                }
797            } else {
798                PreComputeInstruction {
799                    handler: unreachable_handler,
800                    pre_compute: buf.as_ptr(),
801                }
802            };
803            Ok(pre_inst)
804        })
805        .collect::<Result<Vec<_>, _>>()
806}
807
808fn get_system_opcode_handler<F: PrimeField32, Ctx: ExecutionCtxTrait>(
809    inst: &Instruction<F>,
810    buf: &mut [u8],
811) -> Option<ExecuteFunc<F, Ctx>> {
812    if inst.opcode == SystemOpcode::TERMINATE.global_opcode() {
813        let pre_compute: &mut TerminatePreCompute = buf.borrow_mut();
814        pre_compute.exit_code = inst.c.as_canonical_u32();
815        return Some(terminate_execute_e12_impl);
816    }
817    None
818}
819
820/// Errors if exit code is either error or terminated with non-successful exit code.
821fn check_exit_code(exit_code: Result<Option<u32>, ExecutionError>) -> Result<(), ExecutionError> {
822    let exit_code = exit_code?;
823    if let Some(exit_code) = exit_code {
824        // This means execution did terminate
825        if exit_code != ExitCode::Success as u32 {
826            return Err(ExecutionError::FailedWithExitCode(exit_code));
827        }
828    }
829    Ok(())
830}
831
832/// Same as [check_exit_code] but errors if program did not terminate.
833pub(super) fn check_termination(
834    exit_code: Result<Option<u32>, ExecutionError>,
835) -> Result<(), ExecutionError> {
836    let did_terminate = matches!(exit_code.as_ref(), Ok(Some(_)));
837    check_exit_code(exit_code)?;
838    match did_terminate {
839        true => Ok(()),
840        false => Err(ExecutionError::DidNotTerminate),
841    }
842}