openvm_circuit/system/public_values/
execution.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    mem::size_of,
4};
5
6use openvm_circuit_primitives::AlignedBytesBorrow;
7use openvm_instructions::{
8    instruction::Instruction, program::DEFAULT_PC_STEP, riscv::RV32_IMM_AS, NATIVE_AS,
9};
10use openvm_stark_backend::p3_field::PrimeField32;
11
12use super::PublicValuesExecutor;
13#[cfg(not(feature = "tco"))]
14use crate::arch::ExecuteFunc;
15#[cfg(feature = "tco")]
16use crate::arch::Handler;
17#[cfg(feature = "aot")]
18use crate::arch::{AotExecutor, AotMeteredExecutor};
19use crate::{
20    arch::{
21        create_handler,
22        execution_mode::{ExecutionCtxTrait, MeteredExecutionCtxTrait},
23        E2PreCompute, InterpreterExecutor, InterpreterMeteredExecutor, StaticProgramError,
24        VmExecState,
25    },
26    system::memory::online::GuestMemory,
27    utils::{transmute_field_to_u32, transmute_u32_to_field},
28};
29
30#[derive(AlignedBytesBorrow)]
31#[repr(C)]
32struct PublicValuesPreCompute {
33    b_or_imm: u32,
34    c_or_imm: u32,
35}
36
37impl<F, A> PublicValuesExecutor<F, A>
38where
39    F: PrimeField32,
40{
41    fn pre_compute_impl(
42        &self,
43        inst: &Instruction<F>,
44        data: &mut PublicValuesPreCompute,
45    ) -> (bool, bool) {
46        let &Instruction { b, c, e, f, .. } = inst;
47
48        let e = e.as_canonical_u32();
49        let f = f.as_canonical_u32();
50
51        let b_is_imm = e == RV32_IMM_AS;
52        let c_is_imm = f == RV32_IMM_AS;
53
54        let b_or_imm = if b_is_imm {
55            transmute_field_to_u32(&b)
56        } else {
57            b.as_canonical_u32()
58        };
59        let c_or_imm = if c_is_imm {
60            transmute_field_to_u32(&c)
61        } else {
62            c.as_canonical_u32()
63        };
64
65        *data = PublicValuesPreCompute { b_or_imm, c_or_imm };
66
67        (b_is_imm, c_is_imm)
68    }
69}
70
71macro_rules! dispatch {
72    ($execute_impl:ident, $b_is_imm:ident, $c_is_imm:ident) => {
73        match ($b_is_imm, $c_is_imm) {
74            (true, true) => Ok($execute_impl::<_, _, true, true>),
75            (true, false) => Ok($execute_impl::<_, _, true, false>),
76            (false, true) => Ok($execute_impl::<_, _, false, true>),
77            (false, false) => Ok($execute_impl::<_, _, false, false>),
78        }
79    };
80}
81
82impl<F, A> InterpreterExecutor<F> for PublicValuesExecutor<F, A>
83where
84    F: PrimeField32,
85{
86    #[inline(always)]
87    fn pre_compute_size(&self) -> usize {
88        size_of::<PublicValuesPreCompute>()
89    }
90
91    #[cfg(not(feature = "tco"))]
92    #[inline(always)]
93    fn pre_compute<Ctx>(
94        &self,
95        _pc: u32,
96        inst: &Instruction<F>,
97        data: &mut [u8],
98    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
99    where
100        Ctx: ExecutionCtxTrait,
101    {
102        let data: &mut PublicValuesPreCompute = data.borrow_mut();
103        let (b_is_imm, c_is_imm) = self.pre_compute_impl(inst, data);
104
105        dispatch!(execute_e1_handler, b_is_imm, c_is_imm)
106    }
107
108    #[cfg(feature = "tco")]
109    fn handler<Ctx>(
110        &self,
111        _pc: u32,
112        inst: &Instruction<F>,
113        data: &mut [u8],
114    ) -> Result<Handler<F, Ctx>, StaticProgramError>
115    where
116        Ctx: ExecutionCtxTrait,
117    {
118        let data: &mut PublicValuesPreCompute = data.borrow_mut();
119        let (b_is_imm, c_is_imm) = self.pre_compute_impl(inst, data);
120
121        dispatch!(execute_e1_handler, b_is_imm, c_is_imm)
122    }
123}
124
125#[cfg(feature = "aot")]
126impl<F, A> AotExecutor<F> for PublicValuesExecutor<F, A> where F: PrimeField32 {}
127
128impl<F, A> InterpreterMeteredExecutor<F> for PublicValuesExecutor<F, A>
129where
130    F: PrimeField32,
131{
132    fn metered_pre_compute_size(&self) -> usize {
133        size_of::<E2PreCompute<PublicValuesPreCompute>>()
134    }
135
136    #[cfg(not(feature = "tco"))]
137    fn metered_pre_compute<Ctx>(
138        &self,
139        chip_idx: usize,
140        _pc: u32,
141        inst: &Instruction<F>,
142        data: &mut [u8],
143    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
144    where
145        Ctx: MeteredExecutionCtxTrait,
146    {
147        let data: &mut E2PreCompute<PublicValuesPreCompute> = data.borrow_mut();
148        data.chip_idx = chip_idx as u32;
149        let (b_is_imm, c_is_imm) = self.pre_compute_impl(inst, &mut data.data);
150
151        dispatch!(execute_e2_handler, b_is_imm, c_is_imm)
152    }
153
154    #[cfg(feature = "tco")]
155    fn metered_handler<Ctx>(
156        &self,
157        chip_idx: usize,
158        _pc: u32,
159        inst: &Instruction<F>,
160        data: &mut [u8],
161    ) -> Result<Handler<F, Ctx>, StaticProgramError>
162    where
163        Ctx: MeteredExecutionCtxTrait,
164    {
165        let data: &mut E2PreCompute<PublicValuesPreCompute> = data.borrow_mut();
166        data.chip_idx = chip_idx as u32;
167        let (b_is_imm, c_is_imm) = self.pre_compute_impl(inst, &mut data.data);
168
169        dispatch!(execute_e2_handler, b_is_imm, c_is_imm)
170    }
171}
172
173#[cfg(feature = "aot")]
174impl<F, A> AotMeteredExecutor<F> for PublicValuesExecutor<F, A> where F: PrimeField32 {}
175
176#[inline(always)]
177unsafe fn execute_e12_impl<F: PrimeField32, CTX, const B_IS_IMM: bool, const C_IS_IMM: bool>(
178    pre_compute: &PublicValuesPreCompute,
179    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
180) where
181    CTX: ExecutionCtxTrait,
182{
183    let value = if B_IS_IMM {
184        transmute_u32_to_field(&pre_compute.b_or_imm)
185    } else {
186        exec_state.vm_read::<F, 1>(NATIVE_AS, pre_compute.b_or_imm)[0]
187    };
188    let index = if C_IS_IMM {
189        transmute_u32_to_field(&pre_compute.c_or_imm)
190    } else {
191        exec_state.vm_read::<F, 1>(NATIVE_AS, pre_compute.c_or_imm)[0]
192    };
193
194    let idx: usize = index.as_canonical_u32() as usize;
195    {
196        let custom_pvs = &mut exec_state.vm_state.custom_pvs;
197
198        if custom_pvs[idx].is_none() {
199            custom_pvs[idx] = Some(value);
200        } else {
201            // Not a hard constraint violation when publishing the same value twice but the
202            // program should avoid that.
203            panic!("Custom public value {idx} already set");
204        }
205    }
206    let pc = exec_state.pc();
207    exec_state.set_pc(pc.wrapping_add(DEFAULT_PC_STEP));
208}
209
210#[create_handler]
211#[inline(always)]
212unsafe fn execute_e1_impl<F: PrimeField32, CTX, const B_IS_IMM: bool, const C_IS_IMM: bool>(
213    pre_compute: *const u8,
214    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
215) where
216    CTX: ExecutionCtxTrait,
217{
218    let pre_compute: &PublicValuesPreCompute =
219        std::slice::from_raw_parts(pre_compute, size_of::<PublicValuesPreCompute>()).borrow();
220    execute_e12_impl::<_, _, B_IS_IMM, C_IS_IMM>(pre_compute, exec_state);
221}
222
223#[create_handler]
224#[inline(always)]
225unsafe fn execute_e2_impl<F: PrimeField32, CTX, const B_IS_IMM: bool, const C_IS_IMM: bool>(
226    pre_compute: *const u8,
227    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
228) where
229    CTX: MeteredExecutionCtxTrait,
230{
231    let pre_compute: &E2PreCompute<PublicValuesPreCompute> = std::slice::from_raw_parts(
232        pre_compute,
233        size_of::<E2PreCompute<PublicValuesPreCompute>>(),
234    )
235    .borrow();
236    exec_state
237        .ctx
238        .on_height_change(pre_compute.chip_idx as usize, 1);
239    execute_e12_impl::<_, _, B_IS_IMM, C_IS_IMM>(&pre_compute.data, exec_state);
240}