openvm_rv32im_circuit/branch_eq/
core.rs1use std::borrow::{Borrow, BorrowMut};
2
3use openvm_circuit::{
4 arch::*,
5 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
6};
7use openvm_circuit_primitives::{utils::not, ColumnsAir, StructReflection, StructReflectionHelper};
8use openvm_circuit_primitives_derive::{AlignedBorrow, AlignedBytesBorrow};
9use openvm_instructions::{instruction::Instruction, LocalOpcode};
10use openvm_rv32im_transpiler::BranchEqualOpcode;
11use openvm_stark_backend::{
12 interaction::InteractionBuilder,
13 p3_air::{AirBuilder, BaseAir},
14 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
15 BaseAirWithPublicValues,
16};
17use strum::IntoEnumIterator;
18
19#[repr(C)]
20#[derive(AlignedBorrow, StructReflection)]
21pub struct BranchEqualCoreCols<T, const NUM_LIMBS: usize> {
22 pub a: [T; NUM_LIMBS],
23 pub b: [T; NUM_LIMBS],
24
25 pub cmp_result: T,
27 pub imm: T,
28
29 pub opcode_beq_flag: T,
30 pub opcode_bne_flag: T,
31
32 pub diff_inv_marker: [T; NUM_LIMBS],
33}
34
35#[derive(Copy, Clone, Debug, derive_new::new, ColumnsAir)]
36#[columns_via(BranchEqualCoreCols<u8, NUM_LIMBS>)]
37pub struct BranchEqualCoreAir<const NUM_LIMBS: usize> {
38 offset: usize,
39 pc_step: u32,
40}
41
42impl<F: Field, const NUM_LIMBS: usize> BaseAir<F> for BranchEqualCoreAir<NUM_LIMBS> {
43 fn width(&self) -> usize {
44 BranchEqualCoreCols::<F, NUM_LIMBS>::width()
45 }
46}
47impl<F: Field, const NUM_LIMBS: usize> BaseAirWithPublicValues<F>
48 for BranchEqualCoreAir<NUM_LIMBS>
49{
50}
51
52impl<AB, I, const NUM_LIMBS: usize> VmCoreAir<AB, I> for BranchEqualCoreAir<NUM_LIMBS>
53where
54 AB: InteractionBuilder,
55 I: VmAdapterInterface<AB::Expr>,
56 I::Reads: From<[[AB::Expr; NUM_LIMBS]; 2]>,
57 I::Writes: Default,
58 I::ProcessedInstruction: From<ImmInstruction<AB::Expr>>,
59{
60 fn eval(
61 &self,
62 builder: &mut AB,
63 local: &[AB::Var],
64 from_pc: AB::Var,
65 ) -> AdapterAirContext<AB::Expr, I> {
66 let cols: &BranchEqualCoreCols<_, NUM_LIMBS> = local.borrow();
67 let flags = [cols.opcode_beq_flag, cols.opcode_bne_flag];
68
69 let is_valid = flags.iter().fold(AB::Expr::ZERO, |acc, &flag| {
70 builder.assert_bool(flag);
71 acc + flag.into()
72 });
73 builder.assert_bool(is_valid.clone());
74 builder.assert_bool(cols.cmp_result);
75
76 let a = &cols.a;
77 let b = &cols.b;
78 let inv_marker = &cols.diff_inv_marker;
79
80 let cmp_eq =
82 cols.cmp_result * cols.opcode_beq_flag + not(cols.cmp_result) * cols.opcode_bne_flag;
83 let mut sum = cmp_eq.clone();
84
85 for i in 0..NUM_LIMBS {
98 sum += (a[i] - b[i]) * inv_marker[i];
99 builder.assert_zero(cmp_eq.clone() * (a[i] - b[i]));
100 }
101 builder.when(is_valid.clone()).assert_one(sum);
102
103 let expected_opcode = flags
104 .iter()
105 .zip(BranchEqualOpcode::iter())
106 .fold(AB::Expr::ZERO, |acc, (flag, opcode)| {
107 acc + (*flag).into() * AB::Expr::from_u8(opcode as u8)
108 })
109 + AB::Expr::from_usize(self.offset);
110
111 let to_pc = from_pc
112 + cols.cmp_result * cols.imm
113 + not(cols.cmp_result) * AB::Expr::from_u32(self.pc_step);
114
115 AdapterAirContext {
116 to_pc: Some(to_pc),
117 reads: [cols.a.map(Into::into), cols.b.map(Into::into)].into(),
118 writes: Default::default(),
119 instruction: ImmInstruction {
120 is_valid,
121 opcode: expected_opcode,
122 immediate: cols.imm.into(),
123 }
124 .into(),
125 }
126 }
127
128 fn start_offset(&self) -> usize {
129 self.offset
130 }
131}
132
133#[repr(C)]
134#[derive(AlignedBytesBorrow, Debug)]
135pub struct BranchEqualCoreRecord<const NUM_LIMBS: usize> {
136 pub a: [u8; NUM_LIMBS],
137 pub b: [u8; NUM_LIMBS],
138 pub imm: u32,
139 pub local_opcode: u8,
140}
141
142#[derive(Clone, Copy, derive_new::new)]
143pub struct BranchEqualExecutor<A, const NUM_LIMBS: usize> {
144 adapter: A,
145 pub offset: usize,
146 pub pc_step: u32,
147}
148
149#[derive(Clone, Copy, derive_new::new)]
150pub struct BranchEqualFiller<A, const NUM_LIMBS: usize> {
151 adapter: A,
152 pub offset: usize,
153 pub pc_step: u32,
154}
155
156impl<F, A, RA, const NUM_LIMBS: usize> PreflightExecutor<F, RA>
157 for BranchEqualExecutor<A, NUM_LIMBS>
158where
159 F: PrimeField32,
160 A: 'static + AdapterTraceExecutor<F, ReadData: Into<[[u8; NUM_LIMBS]; 2]>, WriteData = ()>,
161 for<'buf> RA: RecordArena<
162 'buf,
163 EmptyAdapterCoreLayout<F, A>,
164 (
165 A::RecordMut<'buf>,
166 &'buf mut BranchEqualCoreRecord<NUM_LIMBS>,
167 ),
168 >,
169{
170 fn get_opcode_name(&self, opcode: usize) -> String {
171 format!("{:?}", BranchEqualOpcode::from_usize(opcode - self.offset))
172 }
173
174 fn execute(
175 &self,
176 state: VmStateMut<F, TracingMemory, RA>,
177 instruction: &Instruction<F>,
178 ) -> Result<(), ExecutionError> {
179 let &Instruction { opcode, c: imm, .. } = instruction;
180
181 let branch_eq_opcode = BranchEqualOpcode::from_usize(opcode.local_opcode_idx(self.offset));
182
183 let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
184
185 A::start(*state.pc, state.memory, &mut adapter_record);
186
187 let [rs1, rs2] = self
188 .adapter
189 .read(state.memory, instruction, &mut adapter_record)
190 .into();
191
192 core_record.a = rs1;
193 core_record.b = rs2;
194 core_record.imm = imm.as_canonical_u32();
195 core_record.local_opcode = branch_eq_opcode as u8;
196
197 if fast_run_eq(branch_eq_opcode, &rs1, &rs2) {
198 *state.pc = (F::from_u32(*state.pc) + imm).as_canonical_u32();
199 } else {
200 *state.pc = state.pc.wrapping_add(self.pc_step);
201 }
202
203 Ok(())
204 }
205}
206
207impl<F, A, const NUM_LIMBS: usize> TraceFiller<F> for BranchEqualFiller<A, NUM_LIMBS>
208where
209 F: PrimeField32,
210 A: 'static + AdapterTraceFiller<F>,
211{
212 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
213 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
216 self.adapter.fill_trace_row(mem_helper, adapter_row);
217 let record: &BranchEqualCoreRecord<NUM_LIMBS> =
220 unsafe { get_record_from_slice(&mut core_row, ()) };
221 let core_row: &mut BranchEqualCoreCols<F, NUM_LIMBS> = core_row.borrow_mut();
222
223 let (cmp_result, diff_idx, diff_inv_val) = run_eq::<F, NUM_LIMBS>(
224 record.local_opcode == BranchEqualOpcode::BEQ as u8,
225 &record.a,
226 &record.b,
227 );
228 core_row.diff_inv_marker = [F::ZERO; NUM_LIMBS];
229 core_row.diff_inv_marker[diff_idx] = diff_inv_val;
230
231 core_row.opcode_bne_flag =
232 F::from_bool(record.local_opcode == BranchEqualOpcode::BNE as u8);
233 core_row.opcode_beq_flag =
234 F::from_bool(record.local_opcode == BranchEqualOpcode::BEQ as u8);
235
236 core_row.imm = F::from_u32(record.imm);
237 core_row.cmp_result = F::from_bool(cmp_result);
238
239 core_row.b = record.b.map(F::from_u8);
240 core_row.a = record.a.map(F::from_u8);
241 }
242}
243
244#[inline(always)]
246pub(super) fn fast_run_eq<const NUM_LIMBS: usize>(
247 local_opcode: BranchEqualOpcode,
248 x: &[u8; NUM_LIMBS],
249 y: &[u8; NUM_LIMBS],
250) -> bool {
251 match local_opcode {
252 BranchEqualOpcode::BEQ => x == y,
253 BranchEqualOpcode::BNE => x != y,
254 }
255}
256
257#[inline(always)]
259pub(super) fn run_eq<F, const NUM_LIMBS: usize>(
260 is_beq: bool,
261 x: &[u8; NUM_LIMBS],
262 y: &[u8; NUM_LIMBS],
263) -> (bool, usize, F)
264where
265 F: PrimeField32,
266{
267 for i in 0..NUM_LIMBS {
268 if x[i] != y[i] {
269 return (!is_beq, i, (F::from_u8(x[i]) - F::from_u8(y[i])).inverse());
270 }
271 }
272 (is_beq, 0, F::ZERO)
273}