1use std::{
2 array,
3 borrow::{Borrow, BorrowMut},
4 mem::size_of,
5};
6
7use openvm_circuit::{
8 arch::*,
9 system::memory::{online::GuestMemory, POINTER_MAX_BITS},
10};
11use openvm_circuit_primitives_derive::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,
17};
18use openvm_rv32im_transpiler::Rv32LoadStoreOpcode::{self, *};
19use openvm_stark_backend::p3_field::PrimeField32;
20
21use super::core::LoadSignExtendExecutor;
22
23#[derive(AlignedBytesBorrow, Clone)]
24#[repr(C)]
25struct LoadSignExtendPreCompute {
26 imm_extended: u32,
27 a: u8,
28 b: u8,
29 e: u8,
30}
31
32impl<A, const LIMB_BITS: usize> LoadSignExtendExecutor<A, { RV32_REGISTER_NUM_LIMBS }, LIMB_BITS> {
33 fn pre_compute_impl<F: PrimeField32>(
35 &self,
36 pc: u32,
37 inst: &Instruction<F>,
38 data: &mut LoadSignExtendPreCompute,
39 ) -> Result<(bool, 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
52 let e_u32 = e.as_canonical_u32();
53 if d.as_canonical_u32() != RV32_REGISTER_AS || e_u32 == RV32_IMM_AS {
54 return Err(StaticProgramError::InvalidInstruction(pc));
55 }
56
57 let local_opcode = Rv32LoadStoreOpcode::from_usize(
58 opcode.local_opcode_idx(Rv32LoadStoreOpcode::CLASS_OFFSET),
59 );
60 match local_opcode {
61 LOADB | LOADH => {}
62 _ => unreachable!("LoadSignExtendExecutor should only handle LOADB/LOADH opcodes"),
63 }
64
65 let imm = c.as_canonical_u32();
66 let imm_sign = g.as_canonical_u32();
67 let imm_extended = imm + imm_sign * 0xffff0000;
68
69 *data = LoadSignExtendPreCompute {
70 imm_extended,
71 a: a.as_canonical_u32() as u8,
72 b: b.as_canonical_u32() as u8,
73 e: e_u32 as u8,
74 };
75 let enabled = !f.is_zero();
76 Ok((local_opcode == LOADB, enabled))
77 }
78}
79
80macro_rules! dispatch {
81 ($execute_impl:ident, $is_loadb:ident, $enabled:ident) => {
82 match ($is_loadb, $enabled) {
83 (true, true) => Ok($execute_impl::<_, _, true, true>),
84 (true, false) => Ok($execute_impl::<_, _, true, false>),
85 (false, true) => Ok($execute_impl::<_, _, false, true>),
86 (false, false) => Ok($execute_impl::<_, _, false, false>),
87 }
88 };
89}
90
91impl<F, A, const LIMB_BITS: usize> InterpreterExecutor<F>
92 for LoadSignExtendExecutor<A, { RV32_REGISTER_NUM_LIMBS }, LIMB_BITS>
93where
94 F: PrimeField32,
95{
96 fn pre_compute_size(&self) -> usize {
97 size_of::<LoadSignExtendPreCompute>()
98 }
99
100 #[cfg(not(feature = "tco"))]
101 #[inline(always)]
102 fn pre_compute<Ctx: ExecutionCtxTrait>(
103 &self,
104 pc: u32,
105 inst: &Instruction<F>,
106 data: &mut [u8],
107 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
108 let pre_compute: &mut LoadSignExtendPreCompute = data.borrow_mut();
109 let (is_loadb, enabled) = self.pre_compute_impl(pc, inst, pre_compute)?;
110 dispatch!(execute_e1_handler, is_loadb, enabled)
111 }
112
113 #[cfg(feature = "tco")]
114 fn handler<Ctx>(
115 &self,
116 pc: u32,
117 inst: &Instruction<F>,
118 data: &mut [u8],
119 ) -> Result<Handler<F, Ctx>, StaticProgramError>
120 where
121 Ctx: ExecutionCtxTrait,
122 {
123 let pre_compute: &mut LoadSignExtendPreCompute = data.borrow_mut();
124 let (is_loadb, enabled) = self.pre_compute_impl(pc, inst, pre_compute)?;
125 dispatch!(execute_e1_handler, is_loadb, enabled)
126 }
127}
128
129impl<F, A, const LIMB_BITS: usize> InterpreterMeteredExecutor<F>
130 for LoadSignExtendExecutor<A, { RV32_REGISTER_NUM_LIMBS }, LIMB_BITS>
131where
132 F: PrimeField32,
133{
134 fn metered_pre_compute_size(&self) -> usize {
135 size_of::<E2PreCompute<LoadSignExtendPreCompute>>()
136 }
137
138 #[cfg(not(feature = "tco"))]
139 fn metered_pre_compute<Ctx>(
140 &self,
141 chip_idx: usize,
142 pc: u32,
143 inst: &Instruction<F>,
144 data: &mut [u8],
145 ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
146 where
147 Ctx: MeteredExecutionCtxTrait,
148 {
149 let pre_compute: &mut E2PreCompute<LoadSignExtendPreCompute> = data.borrow_mut();
150 pre_compute.chip_idx = chip_idx as u32;
151 let (is_loadb, enabled) = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
152 dispatch!(execute_e2_handler, is_loadb, enabled)
153 }
154
155 #[cfg(feature = "tco")]
156 fn metered_handler<Ctx>(
157 &self,
158 chip_idx: usize,
159 pc: u32,
160 inst: &Instruction<F>,
161 data: &mut [u8],
162 ) -> Result<Handler<F, Ctx>, StaticProgramError>
163 where
164 Ctx: MeteredExecutionCtxTrait,
165 {
166 let pre_compute: &mut E2PreCompute<LoadSignExtendPreCompute> = data.borrow_mut();
167 pre_compute.chip_idx = chip_idx as u32;
168 let (is_loadb, enabled) = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
169 dispatch!(execute_e2_handler, is_loadb, enabled)
170 }
171}
172
173#[inline(always)]
174unsafe fn execute_e12_impl<
175 F: PrimeField32,
176 CTX: ExecutionCtxTrait,
177 const IS_LOADB: bool,
178 const ENABLED: bool,
179>(
180 pre_compute: &LoadSignExtendPreCompute,
181 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
182) -> Result<(), ExecutionError> {
183 let pc = exec_state.pc();
184 let rs1_bytes: [u8; RV32_REGISTER_NUM_LIMBS] =
185 exec_state.vm_read(RV32_REGISTER_AS, pre_compute.b as u32);
186 let rs1_val = u32::from_le_bytes(rs1_bytes);
187 let ptr_val = rs1_val.wrapping_add(pre_compute.imm_extended);
188 debug_assert!(ptr_val < (1 << POINTER_MAX_BITS));
190 let shift_amount = ptr_val % 4;
191 let ptr_val = ptr_val - shift_amount; let read_data: [u8; RV32_REGISTER_NUM_LIMBS] =
194 exec_state.vm_read(pre_compute.e as u32, ptr_val);
195
196 let write_data = if IS_LOADB {
197 let byte = read_data[shift_amount as usize];
198 let sign_extended = (byte as i8) as i32;
199 sign_extended.to_le_bytes()
200 } else {
201 if shift_amount != 0 && shift_amount != 2 {
202 let err = ExecutionError::Fail {
203 pc,
204 msg: "LoadSignExtend invalid shift amount",
205 };
206 return Err(err);
207 }
208 let half: [u8; 2] = array::from_fn(|i| read_data[shift_amount as usize + i]);
209 (i16::from_le_bytes(half) as i32).to_le_bytes()
210 };
211
212 if ENABLED {
213 exec_state.vm_write(RV32_REGISTER_AS, pre_compute.a as u32, &write_data);
214 }
215
216 exec_state.set_pc(pc.wrapping_add(DEFAULT_PC_STEP));
217
218 Ok(())
219}
220
221#[create_handler]
222#[inline(always)]
223unsafe fn execute_e1_impl<
224 F: PrimeField32,
225 CTX: ExecutionCtxTrait,
226 const IS_LOADB: bool,
227 const ENABLED: bool,
228>(
229 pre_compute: *const u8,
230 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
231) -> Result<(), ExecutionError> {
232 let pre_compute: &LoadSignExtendPreCompute =
233 std::slice::from_raw_parts(pre_compute, size_of::<LoadSignExtendPreCompute>()).borrow();
234 execute_e12_impl::<F, CTX, IS_LOADB, ENABLED>(pre_compute, exec_state)
235}
236
237#[create_handler]
238#[inline(always)]
239unsafe fn execute_e2_impl<
240 F: PrimeField32,
241 CTX: MeteredExecutionCtxTrait,
242 const IS_LOADB: bool,
243 const ENABLED: bool,
244>(
245 pre_compute: *const u8,
246 exec_state: &mut VmExecState<F, GuestMemory, CTX>,
247) -> Result<(), ExecutionError> {
248 let pre_compute: &E2PreCompute<LoadSignExtendPreCompute> = std::slice::from_raw_parts(
249 pre_compute,
250 size_of::<E2PreCompute<LoadSignExtendPreCompute>>(),
251 )
252 .borrow();
253 exec_state
254 .ctx
255 .on_height_change(pre_compute.chip_idx as usize, 1);
256 execute_e12_impl::<F, CTX, IS_LOADB, ENABLED>(&pre_compute.data, exec_state)
257}