openvm_rv32im_circuit/branch_eq/
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, program::DEFAULT_PC_STEP, riscv::RV32_REGISTER_AS, LocalOpcode,
10};
11use openvm_rv32im_transpiler::BranchEqualOpcode;
12use openvm_stark_backend::p3_field::PrimeField32;
13
14use super::BranchEqualExecutor;
15#[cfg(feature = "aot")]
16use crate::common::{update_height_change_asm, xmm_to_gpr, REG_A_W, REG_B_W};
17
18#[derive(AlignedBytesBorrow, Clone)]
19#[repr(C)]
20struct BranchEqualPreCompute {
21    imm: isize,
22    a: u8,
23    b: u8,
24}
25
26impl<A, const NUM_LIMBS: usize> BranchEqualExecutor<A, NUM_LIMBS> {
27    /// Return `is_bne`, true if the local opcode is BNE.
28    #[inline(always)]
29    fn pre_compute_impl<F: PrimeField32>(
30        &self,
31        pc: u32,
32        inst: &Instruction<F>,
33        data: &mut BranchEqualPreCompute,
34    ) -> Result<bool, StaticProgramError> {
35        let data: &mut BranchEqualPreCompute = data.borrow_mut();
36        let &Instruction {
37            opcode, a, b, c, d, ..
38        } = inst;
39        let local_opcode = BranchEqualOpcode::from_usize(opcode.local_opcode_idx(self.offset));
40        let c = c.as_canonical_u32();
41        let imm = if F::ORDER_U32 - c < c {
42            -((F::ORDER_U32 - c) as isize)
43        } else {
44            c as isize
45        };
46        if d.as_canonical_u32() != RV32_REGISTER_AS {
47            return Err(StaticProgramError::InvalidInstruction(pc));
48        }
49        *data = BranchEqualPreCompute {
50            imm,
51            a: a.as_canonical_u32() as u8,
52            b: b.as_canonical_u32() as u8,
53        };
54        Ok(local_opcode == BranchEqualOpcode::BNE)
55    }
56}
57
58macro_rules! dispatch {
59    ($execute_impl:ident, $is_bne:ident) => {
60        if $is_bne {
61            Ok($execute_impl::<_, _, true>)
62        } else {
63            Ok($execute_impl::<_, _, false>)
64        }
65    };
66}
67
68impl<F, A, const NUM_LIMBS: usize> InterpreterExecutor<F> for BranchEqualExecutor<A, NUM_LIMBS>
69where
70    F: PrimeField32,
71{
72    #[inline(always)]
73    fn pre_compute_size(&self) -> usize {
74        size_of::<BranchEqualPreCompute>()
75    }
76
77    #[cfg(not(feature = "tco"))]
78    #[inline(always)]
79    fn pre_compute<Ctx: ExecutionCtxTrait>(
80        &self,
81        pc: u32,
82        inst: &Instruction<F>,
83        data: &mut [u8],
84    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
85        let data: &mut BranchEqualPreCompute = data.borrow_mut();
86        let is_bne = self.pre_compute_impl(pc, inst, data)?;
87        dispatch!(execute_e1_handler, is_bne)
88    }
89
90    #[cfg(feature = "tco")]
91    fn handler<Ctx>(
92        &self,
93        pc: u32,
94        inst: &Instruction<F>,
95        data: &mut [u8],
96    ) -> Result<Handler<F, Ctx>, StaticProgramError>
97    where
98        Ctx: ExecutionCtxTrait,
99    {
100        let data: &mut BranchEqualPreCompute = data.borrow_mut();
101        let is_bne = self.pre_compute_impl(pc, inst, data)?;
102        dispatch!(execute_e1_handler, is_bne)
103    }
104}
105
106#[cfg(feature = "aot")]
107impl<F, A, const NUM_LIMBS: usize> AotExecutor<F> for BranchEqualExecutor<A, NUM_LIMBS>
108where
109    F: PrimeField32,
110{
111    fn generate_x86_asm(&self, inst: &Instruction<F>, pc: u32) -> Result<String, AotError> {
112        let &Instruction {
113            opcode, a, b, c, d, ..
114        } = inst;
115        let local_opcode = BranchEqualOpcode::from_usize(opcode.local_opcode_idx(self.offset));
116        let c = c.as_canonical_u32();
117        let imm = if F::ORDER_U32 - c < c {
118            -((F::ORDER_U32 - c) as isize)
119        } else {
120            c as isize
121        };
122        let next_pc = (pc as isize + imm) as u32;
123        if d.as_canonical_u32() != RV32_REGISTER_AS {
124            return Err(AotError::InvalidInstruction);
125        }
126        let a = a.as_canonical_u32() as u8;
127        let b = b.as_canonical_u32() as u8;
128
129        let mut asm_str = String::new();
130        let a_reg = a / 4;
131        let b_reg = b / 4;
132
133        // Calculate the result. Inputs: eax, ecx. Outputs: edx.
134        let (reg_a, delta_str_a) = &xmm_to_gpr(a_reg, REG_A_W, false);
135        asm_str += delta_str_a;
136        let (reg_b, delta_str_b) = &xmm_to_gpr(b_reg, REG_B_W, false);
137        asm_str += delta_str_b;
138        asm_str += &format!("   cmp {reg_a}, {reg_b}\n");
139        let not_jump_label = format!(".asm_execute_pc_{pc}_not_jump");
140        match local_opcode {
141            BranchEqualOpcode::BEQ => {
142                asm_str += &format!("   jne {not_jump_label}\n");
143                asm_str += &format!("   jmp asm_execute_pc_{next_pc}\n");
144            }
145            BranchEqualOpcode::BNE => {
146                asm_str += &format!("   je {not_jump_label}\n");
147                asm_str += &format!("   jmp asm_execute_pc_{next_pc}\n");
148            }
149        }
150        asm_str += &format!("{not_jump_label}:\n");
151
152        Ok(asm_str)
153    }
154
155    fn is_aot_supported(&self, _inst: &Instruction<F>) -> bool {
156        true
157    }
158}
159
160impl<F, A, const NUM_LIMBS: usize> InterpreterMeteredExecutor<F>
161    for BranchEqualExecutor<A, NUM_LIMBS>
162where
163    F: PrimeField32,
164{
165    fn metered_pre_compute_size(&self) -> usize {
166        size_of::<E2PreCompute<BranchEqualPreCompute>>()
167    }
168
169    #[cfg(not(feature = "tco"))]
170    fn metered_pre_compute<Ctx>(
171        &self,
172        chip_idx: usize,
173        pc: u32,
174        inst: &Instruction<F>,
175        data: &mut [u8],
176    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError>
177    where
178        Ctx: MeteredExecutionCtxTrait,
179    {
180        let data: &mut E2PreCompute<BranchEqualPreCompute> = data.borrow_mut();
181        data.chip_idx = chip_idx as u32;
182        let is_bne = self.pre_compute_impl(pc, inst, &mut data.data)?;
183        dispatch!(execute_e2_handler, is_bne)
184    }
185
186    #[cfg(feature = "tco")]
187    fn metered_handler<Ctx>(
188        &self,
189        chip_idx: usize,
190        pc: u32,
191        inst: &Instruction<F>,
192        data: &mut [u8],
193    ) -> Result<Handler<F, Ctx>, StaticProgramError>
194    where
195        Ctx: MeteredExecutionCtxTrait,
196    {
197        let data: &mut E2PreCompute<BranchEqualPreCompute> = data.borrow_mut();
198        data.chip_idx = chip_idx as u32;
199        let is_bne = self.pre_compute_impl(pc, inst, &mut data.data)?;
200        dispatch!(execute_e2_handler, is_bne)
201    }
202}
203#[cfg(feature = "aot")]
204impl<F, A, const NUM_LIMBS: usize> AotMeteredExecutor<F> for BranchEqualExecutor<A, NUM_LIMBS>
205where
206    F: PrimeField32,
207{
208    fn is_aot_metered_supported(&self, _inst: &Instruction<F>) -> bool {
209        true
210    }
211    fn generate_x86_metered_asm(
212        &self,
213        inst: &Instruction<F>,
214        pc: u32,
215        chip_idx: usize,
216        _config: &SystemConfig,
217    ) -> Result<String, AotError> {
218        let mut asm_str = String::from("");
219
220        asm_str += &update_height_change_asm(chip_idx, 1)?;
221
222        asm_str += &self.generate_x86_asm(inst, pc)?;
223        Ok(asm_str)
224    }
225}
226
227#[inline(always)]
228unsafe fn execute_e12_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_NE: bool>(
229    pre_compute: &BranchEqualPreCompute,
230    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
231) {
232    let mut pc = exec_state.pc();
233    let rs1 = exec_state.vm_read::<u8, 4>(RV32_REGISTER_AS, pre_compute.a as u32);
234    let rs2 = exec_state.vm_read::<u8, 4>(RV32_REGISTER_AS, pre_compute.b as u32);
235    if (rs1 == rs2) ^ IS_NE {
236        pc = (pc as isize + pre_compute.imm) as u32;
237    } else {
238        pc = pc.wrapping_add(DEFAULT_PC_STEP);
239    }
240    exec_state.set_pc(pc);
241}
242
243#[create_handler]
244#[inline(always)]
245unsafe fn execute_e1_impl<F: PrimeField32, CTX: ExecutionCtxTrait, const IS_NE: bool>(
246    pre_compute: *const u8,
247    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
248) {
249    let pre_compute: &BranchEqualPreCompute =
250        std::slice::from_raw_parts(pre_compute, size_of::<BranchEqualPreCompute>()).borrow();
251    execute_e12_impl::<F, CTX, IS_NE>(pre_compute, exec_state);
252}
253
254#[create_handler]
255#[inline(always)]
256unsafe fn execute_e2_impl<F: PrimeField32, CTX: MeteredExecutionCtxTrait, const IS_NE: bool>(
257    pre_compute: *const u8,
258    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
259) {
260    let pre_compute: &E2PreCompute<BranchEqualPreCompute> = std::slice::from_raw_parts(
261        pre_compute,
262        size_of::<E2PreCompute<BranchEqualPreCompute>>(),
263    )
264    .borrow();
265    exec_state
266        .ctx
267        .on_height_change(pre_compute.chip_idx as usize, 1);
268    execute_e12_impl::<F, CTX, IS_NE>(&pre_compute.data, exec_state);
269}