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 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 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; 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 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 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#[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}