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
32pub struct InterpretedInstance<'a, F, Ctx> {
41 system_config: &'a SystemConfig,
42 #[allow(dead_code)]
45 pre_compute_buf: AlignedBuf,
46 #[cfg(not(feature = "tco"))]
51 pre_compute_insns: Vec<PreComputeInstruction<F, Ctx>>,
52 #[cfg(feature = "tco")]
53 pre_compute_max_size: usize,
54 #[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 #[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 unsafe {
105 handler($interpreter, &mut $exec_state);
106 }
107 }
108 Ok(())
109 })?;
110 }};
111}
112
113impl<'a, F, Ctx> InterpretedInstance<'a, F, Ctx>
118where
119 F: PrimeField32,
120 Ctx: ExecutionCtxTrait,
121{
122 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 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 #[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 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 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 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
292impl<'a, F> InterpretedInstance<'a, F, ExecutionCtx>
295where
296 F: PrimeField32,
297{
298 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 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 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 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 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 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 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 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 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 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 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#[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 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
560pub(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 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 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 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 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 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 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
820fn 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 if exit_code != ExitCode::Success as u32 {
826 return Err(ExecutionError::FailedWithExitCode(exit_code));
827 }
828 }
829 Ok(())
830}
831
832pub(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}