openvm_rv32im_circuit/hintstore/
execution.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    mem::size_of,
4};
5
6use openvm_circuit::{arch::*, system::memory::online::GuestMemory};
7use openvm_circuit_primitives_derive::AlignedBytesBorrow;
8use openvm_instructions::{
9    instruction::Instruction,
10    program::DEFAULT_PC_STEP,
11    riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
12    LocalOpcode,
13};
14use openvm_rv32im_transpiler::{
15    Rv32HintStoreOpcode,
16    Rv32HintStoreOpcode::{HINT_BUFFER, HINT_STOREW},
17    MAX_HINT_BUFFER_WORDS,
18};
19use openvm_stark_backend::p3_field::PrimeField32;
20
21use super::Rv32HintStoreExecutor;
22
23#[derive(AlignedBytesBorrow, Clone)]
24#[repr(C)]
25struct HintStorePreCompute {
26    c: u32,
27    a: u8,
28    b: u8,
29}
30
31impl Rv32HintStoreExecutor {
32    #[inline(always)]
33    fn pre_compute_impl<F: PrimeField32>(
34        &self,
35        pc: u32,
36        inst: &Instruction<F>,
37        data: &mut HintStorePreCompute,
38    ) -> Result<Rv32HintStoreOpcode, StaticProgramError> {
39        let &Instruction {
40            opcode,
41            a,
42            b,
43            c,
44            d,
45            e,
46            ..
47        } = inst;
48        if d.as_canonical_u32() != RV32_REGISTER_AS || e.as_canonical_u32() != RV32_MEMORY_AS {
49            return Err(StaticProgramError::InvalidInstruction(pc));
50        }
51        *data = {
52            HintStorePreCompute {
53                c: c.as_canonical_u32(),
54                a: a.as_canonical_u32() as u8,
55                b: b.as_canonical_u32() as u8,
56            }
57        };
58        Ok(Rv32HintStoreOpcode::from_usize(
59            opcode.local_opcode_idx(self.offset),
60        ))
61    }
62}
63
64macro_rules! dispatch {
65    ($execute_impl:ident, $local_opcode:ident) => {
66        match $local_opcode {
67            HINT_STOREW => Ok($execute_impl::<_, _, true>),
68            HINT_BUFFER => Ok($execute_impl::<_, _, false>),
69        }
70    };
71}
72
73impl<F> InterpreterExecutor<F> for Rv32HintStoreExecutor
74where
75    F: PrimeField32,
76{
77    #[inline(always)]
78    fn pre_compute_size(&self) -> usize {
79        size_of::<HintStorePreCompute>()
80    }
81
82    #[cfg(not(feature = "tco"))]
83    fn pre_compute<Ctx: ExecutionCtxTrait>(
84        &self,
85        pc: u32,
86        inst: &Instruction<F>,
87        data: &mut [u8],
88    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
89        let pre_compute: &mut HintStorePreCompute = data.borrow_mut();
90        let local_opcode = self.pre_compute_impl(pc, inst, pre_compute)?;
91        dispatch!(execute_e1_handler, local_opcode)
92    }
93
94    #[cfg(feature = "tco")]
95    fn handler<Ctx>(
96        &self,
97        pc: u32,
98        inst: &Instruction<F>,
99        data: &mut [u8],
100    ) -> Result<Handler<F, Ctx>, StaticProgramError>
101    where
102        Ctx: ExecutionCtxTrait,
103    {
104        let pre_compute: &mut HintStorePreCompute = data.borrow_mut();
105        let local_opcode = self.pre_compute_impl(pc, inst, pre_compute)?;
106        dispatch!(execute_e1_handler, local_opcode)
107    }
108}
109
110#[cfg(feature = "aot")]
111impl<F> AotExecutor<F> for Rv32HintStoreExecutor where F: PrimeField32 {}
112
113impl<F> InterpreterMeteredExecutor<F> for Rv32HintStoreExecutor
114where
115    F: PrimeField32,
116{
117    fn metered_pre_compute_size(&self) -> usize {
118        size_of::<E2PreCompute<HintStorePreCompute>>()
119    }
120
121    #[cfg(not(feature = "tco"))]
122    fn metered_pre_compute<Ctx>(
123        &self,
124        chip_idx: usize,
125        pc: u32,
126        inst: &Instruction<F>,
127        data: &mut [u8],
128    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
129    where
130        Ctx: MeteredExecutionCtxTrait,
131    {
132        let pre_compute: &mut E2PreCompute<HintStorePreCompute> = data.borrow_mut();
133        pre_compute.chip_idx = chip_idx as u32;
134        let local_opcode = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
135        dispatch!(execute_e2_handler, local_opcode)
136    }
137
138    #[cfg(feature = "tco")]
139    fn metered_handler<Ctx>(
140        &self,
141        chip_idx: usize,
142        pc: u32,
143        inst: &Instruction<F>,
144        data: &mut [u8],
145    ) -> Result<Handler<F, Ctx>, StaticProgramError>
146    where
147        Ctx: MeteredExecutionCtxTrait,
148    {
149        let pre_compute: &mut E2PreCompute<HintStorePreCompute> = data.borrow_mut();
150        pre_compute.chip_idx = chip_idx as u32;
151        let local_opcode = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
152        dispatch!(execute_e2_handler, local_opcode)
153    }
154}
155
156#[cfg(feature = "aot")]
157impl<F> AotMeteredExecutor<F> for Rv32HintStoreExecutor where F: PrimeField32 {}
158/// Return the number of used rows.
159#[inline(always)]
160unsafe fn execute_e12_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_HINT_STOREW: bool>(
161    pre_compute: &HintStorePreCompute,
162    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
163) -> Result<u32, ExecutionError> {
164    let pc = exec_state.pc();
165    let mem_ptr_limbs = exec_state.vm_read::<u8, 4>(RV32_REGISTER_AS, pre_compute.b as u32);
166    let mem_ptr = u32::from_le_bytes(mem_ptr_limbs);
167
168    let num_words = if IS_HINT_STOREW {
169        1
170    } else {
171        let num_words_limbs = exec_state.vm_read::<u8, 4>(RV32_REGISTER_AS, pre_compute.a as u32);
172        u32::from_le_bytes(num_words_limbs)
173    };
174    // Bounds check: num_words must be in [1, MAX_HINT_BUFFER_WORDS]
175    if num_words == 0 {
176        return Err(ExecutionError::HintBufferZeroWords { pc });
177    }
178    if num_words > MAX_HINT_BUFFER_WORDS as u32 {
179        return Err(ExecutionError::HintBufferTooLarge {
180            pc,
181            num_words,
182            max_hint_buffer_words: MAX_HINT_BUFFER_WORDS as u32,
183        });
184    }
185
186    if exec_state.streams.hint_stream.len() < RV32_REGISTER_NUM_LIMBS * num_words as usize {
187        let err = ExecutionError::HintOutOfBounds { pc };
188        return Err(err);
189    }
190
191    for word_index in 0..num_words {
192        let data: [u8; RV32_REGISTER_NUM_LIMBS] = std::array::from_fn(|_| {
193            exec_state
194                .streams
195                .hint_stream
196                .pop_front()
197                .unwrap()
198                .as_canonical_u32() as u8
199        });
200        exec_state.vm_write(
201            RV32_MEMORY_AS,
202            mem_ptr + (RV32_REGISTER_NUM_LIMBS as u32 * word_index),
203            &data,
204        );
205    }
206
207    exec_state.set_pc(pc.wrapping_add(DEFAULT_PC_STEP));
208    Ok(num_words)
209}
210
211#[create_handler]
212#[inline(always)]
213unsafe fn execute_e1_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_HINT_STOREW: bool>(
214    pre_compute: *const u8,
215    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
216) -> Result<(), ExecutionError> {
217    let pre_compute: &HintStorePreCompute =
218        std::slice::from_raw_parts(pre_compute, size_of::<HintStorePreCompute>()).borrow();
219    execute_e12_impl::<F, CTX, IS_HINT_STOREW>(pre_compute, exec_state)?;
220    Ok(())
221}
222
223#[create_handler]
224#[inline(always)]
225unsafe fn execute_e2_impl<
226    F: PrimeField32,
227    CTX: MeteredExecutionCtxTrait,
228    const IS_HINT_STOREW: bool,
229>(
230    pre_compute: *const u8,
231    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
232) -> Result<(), ExecutionError> {
233    let pre_compute: &E2PreCompute<HintStorePreCompute> =
234        std::slice::from_raw_parts(pre_compute, size_of::<E2PreCompute<HintStorePreCompute>>())
235            .borrow();
236    let height_delta = execute_e12_impl::<F, CTX, IS_HINT_STOREW>(&pre_compute.data, exec_state)?;
237    exec_state
238        .ctx
239        .on_height_change(pre_compute.chip_idx as usize, height_delta);
240    Ok(())
241}