openvm_rv32im_circuit/load_sign_extend/
execution.rs

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    /// Return (is_loadb, enabled)
34    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> Executor<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> MeteredExecutor<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    instret: &mut u64,
182    pc: &mut u32,
183    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
184) -> Result<(), ExecutionError> {
185    let rs1_bytes: [u8; RV32_REGISTER_NUM_LIMBS] =
186        exec_state.vm_read(RV32_REGISTER_AS, pre_compute.b as u32);
187    let rs1_val = u32::from_le_bytes(rs1_bytes);
188    let ptr_val = rs1_val.wrapping_add(pre_compute.imm_extended);
189    // sign_extend([r32{c,g}(b):2]_e)`
190    debug_assert!(ptr_val < (1 << POINTER_MAX_BITS));
191    let shift_amount = ptr_val % 4;
192    let ptr_val = ptr_val - shift_amount; // aligned ptr
193
194    let read_data: [u8; RV32_REGISTER_NUM_LIMBS] =
195        exec_state.vm_read(pre_compute.e as u32, ptr_val);
196
197    let write_data = if IS_LOADB {
198        let byte = read_data[shift_amount as usize];
199        let sign_extended = (byte as i8) as i32;
200        sign_extended.to_le_bytes()
201    } else {
202        if shift_amount != 0 && shift_amount != 2 {
203            let err = ExecutionError::Fail {
204                pc: *pc,
205                msg: "LoadSignExtend invalid shift amount",
206            };
207            return Err(err);
208        }
209        let half: [u8; 2] = array::from_fn(|i| read_data[shift_amount as usize + i]);
210        (i16::from_le_bytes(half) as i32).to_le_bytes()
211    };
212
213    if ENABLED {
214        exec_state.vm_write(RV32_REGISTER_AS, pre_compute.a as u32, &write_data);
215    }
216
217    *pc += DEFAULT_PC_STEP;
218    *instret += 1;
219
220    Ok(())
221}
222
223#[create_handler]
224#[inline(always)]
225unsafe fn execute_e1_impl<
226    F: PrimeField32,
227    CTX: ExecutionCtxTrait,
228    const IS_LOADB: bool,
229    const ENABLED: bool,
230>(
231    pre_compute: &[u8],
232    instret: &mut u64,
233    pc: &mut u32,
234    _instret_end: u64,
235    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
236) -> Result<(), ExecutionError> {
237    let pre_compute: &LoadSignExtendPreCompute = pre_compute.borrow();
238    execute_e12_impl::<F, CTX, IS_LOADB, ENABLED>(pre_compute, instret, pc, exec_state)
239}
240
241#[create_handler]
242#[inline(always)]
243unsafe fn execute_e2_impl<
244    F: PrimeField32,
245    CTX: MeteredExecutionCtxTrait,
246    const IS_LOADB: bool,
247    const ENABLED: bool,
248>(
249    pre_compute: &[u8],
250    instret: &mut u64,
251    pc: &mut u32,
252    _arg: u64,
253    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
254) -> Result<(), ExecutionError> {
255    let pre_compute: &E2PreCompute<LoadSignExtendPreCompute> = pre_compute.borrow();
256    exec_state
257        .ctx
258        .on_height_change(pre_compute.chip_idx as usize, 1);
259    execute_e12_impl::<F, CTX, IS_LOADB, ENABLED>(&pre_compute.data, instret, pc, exec_state)
260}