openvm_rv32im_circuit/loadstore/
execution.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    fmt::Debug,
4    mem::size_of,
5};
6
7use openvm_circuit::{
8    arch::*,
9    system::memory::{online::GuestMemory, POINTER_MAX_BITS},
10};
11use openvm_circuit_primitives::AlignedBytesBorrow;
12use openvm_instructions::{
13    instruction::Instruction,
14    program::DEFAULT_PC_STEP,
15    riscv::{RV32_IMM_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
16    LocalOpcode, DEFERRAL_AS,
17};
18use openvm_rv32im_transpiler::Rv32LoadStoreOpcode::{self, *};
19use openvm_stark_backend::p3_field::PrimeField32;
20
21use super::core::LoadStoreExecutor;
22
23#[derive(AlignedBytesBorrow, Clone)]
24#[repr(C)]
25struct LoadStorePreCompute {
26    imm_extended: u32,
27    a: u8,
28    b: u8,
29    e: u8,
30}
31
32impl<A, const NUM_CELLS: usize> LoadStoreExecutor<A, NUM_CELLS> {
33    /// Return (local_opcode, enabled)
34    fn pre_compute_impl<F: PrimeField32>(
35        &self,
36        pc: u32,
37        inst: &Instruction<F>,
38        data: &mut LoadStorePreCompute,
39    ) -> Result<(Rv32LoadStoreOpcode, bool), StaticProgramError> {
40        let Instruction {
41            opcode,
42            a,
43            b,
44            c,
45            d,
46            e,
47            f,
48            g,
49            ..
50        } = inst;
51        let enabled = !f.is_zero();
52
53        let e_u32 = e.as_canonical_u32();
54        if d.as_canonical_u32() != RV32_REGISTER_AS || e_u32 == RV32_IMM_AS || e_u32 == DEFERRAL_AS
55        {
56            return Err(StaticProgramError::InvalidInstruction(pc));
57        }
58
59        let local_opcode = Rv32LoadStoreOpcode::from_usize(
60            opcode.local_opcode_idx(Rv32LoadStoreOpcode::CLASS_OFFSET),
61        );
62        match local_opcode {
63            LOADW | LOADBU | LOADHU => {}
64            STOREW | STOREH | STOREB => {
65                if !enabled {
66                    return Err(StaticProgramError::InvalidInstruction(pc));
67                }
68            }
69            _ => unreachable!("LoadStoreExecutor should not handle LOADB/LOADH opcodes"),
70        }
71
72        let imm = c.as_canonical_u32();
73        let imm_sign = g.as_canonical_u32();
74        let imm_extended = imm + imm_sign * 0xffff0000;
75        *data = LoadStorePreCompute {
76            imm_extended,
77            a: a.as_canonical_u32() as u8,
78            b: b.as_canonical_u32() as u8,
79            e: e_u32 as u8,
80        };
81        Ok((local_opcode, enabled))
82    }
83}
84
85macro_rules! dispatch {
86    ($execute_impl:ident, $local_opcode:ident, $enabled:ident) => {
87        match ($local_opcode, $enabled) {
88            (LOADW, true) => Ok($execute_impl::<_, _, U8, LoadWOp, true>),
89            (LOADW, false) => Ok($execute_impl::<_, _, U8, LoadWOp, false>),
90            (LOADHU, true) => Ok($execute_impl::<_, _, U8, LoadHUOp, true>),
91            (LOADHU, false) => Ok($execute_impl::<_, _, U8, LoadHUOp, false>),
92            (LOADBU, true) => Ok($execute_impl::<_, _, U8, LoadBUOp, true>),
93            (LOADBU, false) => Ok($execute_impl::<_, _, U8, LoadBUOp, false>),
94            (STOREW, true) => Ok($execute_impl::<_, _, U8, StoreWOp, true>),
95            (STOREW, false) => Ok($execute_impl::<_, _, U8, StoreWOp, false>),
96            (STOREH, true) => Ok($execute_impl::<_, _, U8, StoreHOp, true>),
97            (STOREH, false) => Ok($execute_impl::<_, _, U8, StoreHOp, false>),
98            (STOREB, true) => Ok($execute_impl::<_, _, U8, StoreBOp, true>),
99            (STOREB, false) => Ok($execute_impl::<_, _, U8, StoreBOp, false>),
100            (_, _) => unreachable!(),
101        }
102    };
103}
104
105impl<F, A, const NUM_CELLS: usize> InterpreterExecutor<F> for LoadStoreExecutor<A, NUM_CELLS>
106where
107    F: PrimeField32,
108{
109    #[inline(always)]
110    fn pre_compute_size(&self) -> usize {
111        size_of::<LoadStorePreCompute>()
112    }
113
114    #[cfg(not(feature = "tco"))]
115    #[inline(always)]
116    fn pre_compute<Ctx: ExecutionCtxTrait>(
117        &self,
118        pc: u32,
119        inst: &Instruction<F>,
120        data: &mut [u8],
121    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
122        let pre_compute: &mut LoadStorePreCompute = data.borrow_mut();
123        let (local_opcode, enabled) = self.pre_compute_impl(pc, inst, pre_compute)?;
124        dispatch!(execute_e1_handler, local_opcode, enabled)
125    }
126
127    #[cfg(feature = "tco")]
128    fn handler<Ctx>(
129        &self,
130        pc: u32,
131        inst: &Instruction<F>,
132        data: &mut [u8],
133    ) -> Result<Handler<F, Ctx>, StaticProgramError>
134    where
135        Ctx: ExecutionCtxTrait,
136    {
137        let pre_compute: &mut LoadStorePreCompute = data.borrow_mut();
138        let (local_opcode, enabled) = self.pre_compute_impl(pc, inst, pre_compute)?;
139        dispatch!(execute_e1_handler, local_opcode, enabled)
140    }
141}
142
143impl<F, A, const NUM_CELLS: usize> InterpreterMeteredExecutor<F> for LoadStoreExecutor<A, NUM_CELLS>
144where
145    F: PrimeField32,
146{
147    fn metered_pre_compute_size(&self) -> usize {
148        size_of::<E2PreCompute<LoadStorePreCompute>>()
149    }
150
151    #[cfg(not(feature = "tco"))]
152    fn metered_pre_compute<Ctx>(
153        &self,
154        chip_idx: usize,
155        pc: u32,
156        inst: &Instruction<F>,
157        data: &mut [u8],
158    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
159    where
160        Ctx: MeteredExecutionCtxTrait,
161    {
162        let pre_compute: &mut E2PreCompute<LoadStorePreCompute> = data.borrow_mut();
163        pre_compute.chip_idx = chip_idx as u32;
164        let (local_opcode, enabled) = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
165        dispatch!(execute_e2_handler, local_opcode, enabled)
166    }
167
168    #[cfg(feature = "tco")]
169    fn metered_handler<Ctx>(
170        &self,
171        chip_idx: usize,
172        pc: u32,
173        inst: &Instruction<F>,
174        data: &mut [u8],
175    ) -> Result<Handler<F, Ctx>, StaticProgramError>
176    where
177        Ctx: MeteredExecutionCtxTrait,
178    {
179        let pre_compute: &mut E2PreCompute<LoadStorePreCompute> = data.borrow_mut();
180        pre_compute.chip_idx = chip_idx as u32;
181        let (local_opcode, enabled) = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
182        dispatch!(execute_e2_handler, local_opcode, enabled)
183    }
184}
185
186#[inline(always)]
187unsafe fn execute_e12_impl<
188    F: PrimeField32,
189    CTX: ExecutionCtxTrait,
190    T: Copy + Debug + Default,
191    OP: LoadStoreOp<T>,
192    const ENABLED: bool,
193>(
194    pre_compute: &LoadStorePreCompute,
195    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
196) -> Result<(), ExecutionError> {
197    let pc = exec_state.pc();
198    let rs1_bytes: [u8; RV32_REGISTER_NUM_LIMBS] =
199        exec_state.vm_read(RV32_REGISTER_AS, pre_compute.b as u32);
200    let rs1_val = u32::from_le_bytes(rs1_bytes);
201    let ptr_val = rs1_val.wrapping_add(pre_compute.imm_extended);
202    // sign_extend([r32{c,g}(b):2]_e)`
203    if ptr_val >= (1 << POINTER_MAX_BITS) {
204        println!(
205            "at {} ptr_val: {ptr_val} >= (1 << POINTER_MAX_BITS): {}",
206            pc,
207            1 << POINTER_MAX_BITS
208        );
209    }
210    debug_assert!(ptr_val < (1 << POINTER_MAX_BITS));
211
212    let shift_amount = ptr_val % 4;
213    let ptr_val = ptr_val - shift_amount; // aligned ptr
214
215    let read_data: [u8; RV32_REGISTER_NUM_LIMBS] = if OP::IS_LOAD {
216        exec_state.vm_read(pre_compute.e as u32, ptr_val)
217    } else {
218        exec_state.vm_read(RV32_REGISTER_AS, pre_compute.a as u32)
219    };
220
221    // We need to write 4 u32s for STORE.
222    let mut write_data: [T; RV32_REGISTER_NUM_LIMBS] = if OP::HOST_READ {
223        exec_state.host_read(pre_compute.e as u32, ptr_val)
224    } else {
225        [T::default(); RV32_REGISTER_NUM_LIMBS]
226    };
227
228    if !OP::compute_write_data(&mut write_data, read_data, shift_amount as usize) {
229        let err = ExecutionError::Fail {
230            pc,
231            msg: "Invalid LoadStoreOp",
232        };
233        return Err(err);
234    }
235
236    if ENABLED {
237        if OP::IS_LOAD {
238            exec_state.vm_write(RV32_REGISTER_AS, pre_compute.a as u32, &write_data);
239        } else {
240            exec_state.vm_write(pre_compute.e as u32, ptr_val, &write_data);
241        }
242    }
243
244    exec_state.set_pc(pc.wrapping_add(DEFAULT_PC_STEP));
245
246    Ok(())
247}
248
249#[create_handler]
250#[inline(always)]
251unsafe fn execute_e1_impl<
252    F: PrimeField32,
253    CTX: ExecutionCtxTrait,
254    T: Copy + Debug + Default,
255    OP: LoadStoreOp<T>,
256    const ENABLED: bool,
257>(
258    pre_compute: *const u8,
259    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
260) -> Result<(), ExecutionError> {
261    let pre_compute: &LoadStorePreCompute =
262        std::slice::from_raw_parts(pre_compute, size_of::<LoadStorePreCompute>()).borrow();
263    execute_e12_impl::<F, CTX, T, OP, ENABLED>(pre_compute, exec_state)
264}
265
266#[create_handler]
267#[inline(always)]
268unsafe fn execute_e2_impl<
269    F: PrimeField32,
270    CTX: MeteredExecutionCtxTrait,
271    T: Copy + Debug + Default,
272    OP: LoadStoreOp<T>,
273    const ENABLED: bool,
274>(
275    pre_compute: *const u8,
276    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
277) -> Result<(), ExecutionError> {
278    let pre_compute: &E2PreCompute<LoadStorePreCompute> =
279        std::slice::from_raw_parts(pre_compute, size_of::<E2PreCompute<LoadStorePreCompute>>())
280            .borrow();
281    exec_state
282        .ctx
283        .on_height_change(pre_compute.chip_idx as usize, 1);
284    execute_e12_impl::<F, CTX, T, OP, ENABLED>(&pre_compute.data, exec_state)
285}
286
287trait LoadStoreOp<T> {
288    const IS_LOAD: bool;
289    const HOST_READ: bool;
290
291    /// Return if the operation is valid.
292    fn compute_write_data(
293        write_data: &mut [T; RV32_REGISTER_NUM_LIMBS],
294        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
295        shift_amount: usize,
296    ) -> bool;
297}
298/// Wrapper type for u8 so we can implement `LoadStoreOp<F>` for `F: PrimeField32`.
299/// For memory read/write, this type behaves as same as `u8`.
300#[allow(dead_code)]
301#[derive(Copy, Clone, Debug, Default)]
302struct U8(u8);
303struct LoadWOp;
304struct LoadHUOp;
305struct LoadBUOp;
306struct StoreWOp;
307struct StoreHOp;
308struct StoreBOp;
309impl LoadStoreOp<U8> for LoadWOp {
310    const IS_LOAD: bool = true;
311    const HOST_READ: bool = false;
312
313    #[inline(always)]
314    fn compute_write_data(
315        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
316        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
317        _shift_amount: usize,
318    ) -> bool {
319        *write_data = read_data.map(U8);
320        true
321    }
322}
323
324impl LoadStoreOp<U8> for LoadHUOp {
325    const IS_LOAD: bool = true;
326    const HOST_READ: bool = false;
327    #[inline(always)]
328    fn compute_write_data(
329        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
330        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
331        shift_amount: usize,
332    ) -> bool {
333        if shift_amount != 0 && shift_amount != 2 {
334            return false;
335        }
336        write_data[0] = U8(read_data[shift_amount]);
337        write_data[1] = U8(read_data[shift_amount + 1]);
338        true
339    }
340}
341impl LoadStoreOp<U8> for LoadBUOp {
342    const IS_LOAD: bool = true;
343    const HOST_READ: bool = false;
344    #[inline(always)]
345    fn compute_write_data(
346        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
347        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
348        shift_amount: usize,
349    ) -> bool {
350        write_data[0] = U8(read_data[shift_amount]);
351        true
352    }
353}
354
355impl LoadStoreOp<U8> for StoreWOp {
356    const IS_LOAD: bool = false;
357    const HOST_READ: bool = false;
358    #[inline(always)]
359    fn compute_write_data(
360        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
361        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
362        _shift_amount: usize,
363    ) -> bool {
364        *write_data = read_data.map(U8);
365        true
366    }
367}
368impl LoadStoreOp<U8> for StoreHOp {
369    const IS_LOAD: bool = false;
370    const HOST_READ: bool = true;
371
372    #[inline(always)]
373    fn compute_write_data(
374        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
375        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
376        shift_amount: usize,
377    ) -> bool {
378        if shift_amount != 0 && shift_amount != 2 {
379            return false;
380        }
381        write_data[shift_amount] = U8(read_data[0]);
382        write_data[shift_amount + 1] = U8(read_data[1]);
383        true
384    }
385}
386impl LoadStoreOp<U8> for StoreBOp {
387    const IS_LOAD: bool = false;
388    const HOST_READ: bool = true;
389    #[inline(always)]
390    fn compute_write_data(
391        write_data: &mut [U8; RV32_REGISTER_NUM_LIMBS],
392        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
393        shift_amount: usize,
394    ) -> bool {
395        write_data[shift_amount] = U8(read_data[0]);
396        true
397    }
398}
399
400impl<F: PrimeField32> LoadStoreOp<F> for StoreWOp {
401    const IS_LOAD: bool = false;
402    const HOST_READ: bool = false;
403    #[inline(always)]
404    fn compute_write_data(
405        write_data: &mut [F; RV32_REGISTER_NUM_LIMBS],
406        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
407        _shift_amount: usize,
408    ) -> bool {
409        *write_data = read_data.map(F::from_u8);
410        true
411    }
412}
413impl<F: PrimeField32> LoadStoreOp<F> for StoreHOp {
414    const IS_LOAD: bool = false;
415    const HOST_READ: bool = true;
416
417    #[inline(always)]
418    fn compute_write_data(
419        write_data: &mut [F; RV32_REGISTER_NUM_LIMBS],
420        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
421        shift_amount: usize,
422    ) -> bool {
423        if shift_amount != 0 && shift_amount != 2 {
424            return false;
425        }
426        write_data[shift_amount] = F::from_u8(read_data[0]);
427        write_data[shift_amount + 1] = F::from_u8(read_data[1]);
428        true
429    }
430}
431impl<F: PrimeField32> LoadStoreOp<F> for StoreBOp {
432    const IS_LOAD: bool = false;
433    const HOST_READ: bool = true;
434    #[inline(always)]
435    fn compute_write_data(
436        write_data: &mut [F; RV32_REGISTER_NUM_LIMBS],
437        read_data: [u8; RV32_REGISTER_NUM_LIMBS],
438        shift_amount: usize,
439    ) -> bool {
440        write_data[shift_amount] = F::from_u8(read_data[0]);
441        true
442    }
443}