openvm_circuit/arch/execution_mode/
metered_cost.rs

1use getset::WithSetters;
2use openvm_instructions::riscv::RV32_IMM_AS;
3
4use crate::{
5    arch::{
6        execution_mode::metered::segment_ctx::DEFAULT_MAX_MEMORY as DEFAULT_SEGMENT_MAX_MEMORY,
7        ExecutionCtxTrait, MeteredExecutionCtxTrait, VmExecState,
8    },
9    system::memory::online::GuestMemory,
10};
11
12const DEFAULT_MAX_SEGMENTS: u64 = 100;
13pub const DEFAULT_MAX_COST: u64 = DEFAULT_MAX_SEGMENTS * DEFAULT_SEGMENT_MAX_MEMORY as u64;
14
15#[derive(Clone, Debug, WithSetters)]
16pub struct MeteredCostCtx {
17    pub widths: Vec<usize>,
18    #[getset(set_with = "pub")]
19    pub max_execution_cost: u64,
20    // Cost is number of trace cells (height * width)
21    pub cost: u64,
22    /// To measure instructions/s
23    pub instret: u64,
24}
25
26impl MeteredCostCtx {
27    pub fn new(widths: Vec<usize>) -> Self {
28        Self {
29            widths,
30            max_execution_cost: DEFAULT_MAX_COST,
31            cost: 0,
32            instret: 0,
33        }
34    }
35
36    #[cold]
37    fn panic_cost_exceeded(&self) -> ! {
38        panic!(
39            "Execution cost {} exceeded maximum allowed cost of {}",
40            self.cost,
41            2 * DEFAULT_MAX_COST
42        );
43    }
44}
45
46impl ExecutionCtxTrait for MeteredCostCtx {
47    #[inline(always)]
48    fn on_memory_operation(&mut self, address_space: u32, _ptr: u32, size: u32) {
49        debug_assert!(
50            address_space != RV32_IMM_AS,
51            "address space must not be immediate"
52        );
53        debug_assert!(size > 0, "size must be greater than 0, got {size}");
54        debug_assert!(
55            size.is_power_of_two(),
56            "size must be a power of 2, got {size}"
57        );
58        // Prevent unbounded memory accesses per instruction
59        if self.cost > 2 * std::cmp::max(self.max_execution_cost, DEFAULT_MAX_COST) {
60            self.panic_cost_exceeded();
61        }
62    }
63
64    #[inline(always)]
65    fn should_suspend<F>(exec_state: &mut VmExecState<F, GuestMemory, Self>) -> bool {
66        if exec_state.ctx.cost > exec_state.ctx.max_execution_cost {
67            true
68        } else {
69            exec_state.ctx.instret += 1;
70            false
71        }
72    }
73}
74
75impl MeteredExecutionCtxTrait for MeteredCostCtx {
76    #[inline(always)]
77    fn on_height_change(&mut self, chip_idx: usize, height_delta: u32) {
78        debug_assert!(chip_idx < self.widths.len(), "chip_idx out of bounds");
79        // SAFETY: chip_idx is created in executor_idx_to_air_idx and is always within bounds
80        let width = unsafe { *self.widths.get_unchecked(chip_idx) };
81        self.cost += (height_delta as u64) * (width as u64);
82    }
83}