1use std::{
2 array,
3 borrow::{Borrow, BorrowMut},
4 iter::zip,
5};
6
7use openvm_circuit::{
8 arch::*,
9 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
10};
11use openvm_circuit_primitives::{
12 bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
13 utils::not,
14 AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
15};
16use openvm_circuit_primitives_derive::AlignedBorrow;
17use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP, LocalOpcode};
18use openvm_rv32im_transpiler::BaseAluOpcode;
19use openvm_stark_backend::{
20 interaction::InteractionBuilder,
21 p3_air::{AirBuilder, BaseAir},
22 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
23 BaseAirWithPublicValues,
24};
25use strum::IntoEnumIterator;
26
27#[repr(C)]
28#[derive(AlignedBorrow, StructReflection, Debug)]
29pub struct BaseAluCoreCols<T, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
30 pub a: [T; NUM_LIMBS],
31 pub b: [T; NUM_LIMBS],
32 pub c: [T; NUM_LIMBS],
33
34 pub opcode_add_flag: T,
35 pub opcode_sub_flag: T,
36 pub opcode_xor_flag: T,
37 pub opcode_or_flag: T,
38 pub opcode_and_flag: T,
39}
40
41#[derive(Copy, Clone, Debug, derive_new::new, ColumnsAir)]
42#[columns_via(BaseAluCoreCols<u8, NUM_LIMBS, LIMB_BITS>)]
43pub struct BaseAluCoreAir<const NUM_LIMBS: usize, const LIMB_BITS: usize> {
44 pub bus: BitwiseOperationLookupBus,
45 pub offset: usize,
46}
47
48impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAir<F>
49 for BaseAluCoreAir<NUM_LIMBS, LIMB_BITS>
50{
51 fn width(&self) -> usize {
52 BaseAluCoreCols::<F, NUM_LIMBS, LIMB_BITS>::width()
53 }
54}
55impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAirWithPublicValues<F>
56 for BaseAluCoreAir<NUM_LIMBS, LIMB_BITS>
57{
58}
59
60impl<AB, I, const NUM_LIMBS: usize, const LIMB_BITS: usize> VmCoreAir<AB, I>
61 for BaseAluCoreAir<NUM_LIMBS, LIMB_BITS>
62where
63 AB: InteractionBuilder,
64 I: VmAdapterInterface<AB::Expr>,
65 I::Reads: From<[[AB::Expr; NUM_LIMBS]; 2]>,
66 I::Writes: From<[[AB::Expr; NUM_LIMBS]; 1]>,
67 I::ProcessedInstruction: From<MinimalInstruction<AB::Expr>>,
68{
69 fn eval(
70 &self,
71 builder: &mut AB,
72 local_core: &[AB::Var],
73 _from_pc: AB::Var,
74 ) -> AdapterAirContext<AB::Expr, I> {
75 let cols: &BaseAluCoreCols<_, NUM_LIMBS, LIMB_BITS> = local_core.borrow();
76 let flags = [
77 cols.opcode_add_flag,
78 cols.opcode_sub_flag,
79 cols.opcode_xor_flag,
80 cols.opcode_or_flag,
81 cols.opcode_and_flag,
82 ];
83
84 let is_valid = flags.iter().fold(AB::Expr::ZERO, |acc, &flag| {
85 builder.assert_bool(flag);
86 acc + flag.into()
87 });
88 builder.assert_bool(is_valid.clone());
89
90 let a = &cols.a;
91 let b = &cols.b;
92 let c = &cols.c;
93
94 let mut carry_add: [AB::Expr; NUM_LIMBS] = array::from_fn(|_| AB::Expr::ZERO);
99 let mut carry_sub: [AB::Expr; NUM_LIMBS] = array::from_fn(|_| AB::Expr::ZERO);
100 let carry_divide = AB::F::from_usize(1 << LIMB_BITS).inverse();
101
102 for i in 0..NUM_LIMBS {
103 carry_add[i] = AB::Expr::from(carry_divide)
107 * (b[i] + c[i] - a[i]
108 + if i > 0 {
109 carry_add[i - 1].clone()
110 } else {
111 AB::Expr::ZERO
112 });
113 builder
114 .when(cols.opcode_add_flag)
115 .assert_bool(carry_add[i].clone());
116 carry_sub[i] = AB::Expr::from(carry_divide)
117 * (a[i] + c[i] - b[i]
118 + if i > 0 {
119 carry_sub[i - 1].clone()
120 } else {
121 AB::Expr::ZERO
122 });
123 builder
124 .when(cols.opcode_sub_flag)
125 .assert_bool(carry_sub[i].clone());
126 }
127
128 let bitwise = cols.opcode_xor_flag + cols.opcode_or_flag + cols.opcode_and_flag;
131 for i in 0..NUM_LIMBS {
132 let x = not::<AB::Expr>(bitwise.clone()) * a[i] + bitwise.clone() * b[i];
133 let y = not::<AB::Expr>(bitwise.clone()) * a[i] + bitwise.clone() * c[i];
134 let x_xor_y = cols.opcode_xor_flag * a[i]
135 + cols.opcode_or_flag * ((AB::Expr::from_u32(2) * a[i]) - b[i] - c[i])
136 + cols.opcode_and_flag * (b[i] + c[i] - (AB::Expr::from_u32(2) * a[i]));
137 self.bus
138 .send_xor(x, y, x_xor_y)
139 .eval(builder, is_valid.clone());
140 }
141
142 let expected_opcode = VmCoreAir::<AB, I>::expr_to_global_expr(
143 self,
144 flags.iter().zip(BaseAluOpcode::iter()).fold(
145 AB::Expr::ZERO,
146 |acc, (flag, local_opcode)| {
147 acc + (*flag).into() * AB::Expr::from_u8(local_opcode as u8)
148 },
149 ),
150 );
151
152 AdapterAirContext {
153 to_pc: None,
154 reads: [cols.b.map(Into::into), cols.c.map(Into::into)].into(),
155 writes: [cols.a.map(Into::into)].into(),
156 instruction: MinimalInstruction {
157 is_valid,
158 opcode: expected_opcode,
159 }
160 .into(),
161 }
162 }
163
164 fn start_offset(&self) -> usize {
165 self.offset
166 }
167}
168
169#[repr(C, align(4))]
170#[derive(AlignedBytesBorrow, Debug)]
171pub struct BaseAluCoreRecord<const NUM_LIMBS: usize> {
172 pub b: [u8; NUM_LIMBS],
173 pub c: [u8; NUM_LIMBS],
174 pub local_opcode: u8,
176}
177
178#[derive(Clone, Copy, derive_new::new)]
179pub struct BaseAluExecutor<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
180 adapter: A,
181 pub offset: usize,
182}
183
184#[derive(derive_new::new)]
185pub struct BaseAluFiller<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
186 adapter: A,
187 pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<LIMB_BITS>,
188 pub offset: usize,
189}
190
191impl<F, A, RA, const NUM_LIMBS: usize, const LIMB_BITS: usize> PreflightExecutor<F, RA>
192 for BaseAluExecutor<A, NUM_LIMBS, LIMB_BITS>
193where
194 F: PrimeField32,
195 A: 'static
196 + AdapterTraceExecutor<
197 F,
198 ReadData: Into<[[u8; NUM_LIMBS]; 2]>,
199 WriteData: From<[[u8; NUM_LIMBS]; 1]>,
200 >,
201 for<'buf> RA: RecordArena<
202 'buf,
203 EmptyAdapterCoreLayout<F, A>,
204 (A::RecordMut<'buf>, &'buf mut BaseAluCoreRecord<NUM_LIMBS>),
205 >,
206{
207 fn get_opcode_name(&self, opcode: usize) -> String {
208 format!("{:?}", BaseAluOpcode::from_usize(opcode - self.offset))
209 }
210
211 fn execute(
212 &self,
213 state: VmStateMut<F, TracingMemory, RA>,
214 instruction: &Instruction<F>,
215 ) -> Result<(), ExecutionError> {
216 let Instruction { opcode, .. } = instruction;
217
218 let local_opcode = BaseAluOpcode::from_usize(opcode.local_opcode_idx(self.offset));
219 let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
220
221 A::start(*state.pc, state.memory, &mut adapter_record);
222
223 [core_record.b, core_record.c] = self
224 .adapter
225 .read(state.memory, instruction, &mut adapter_record)
226 .into();
227
228 let rd = run_alu::<NUM_LIMBS, LIMB_BITS>(local_opcode, &core_record.b, &core_record.c);
229
230 core_record.local_opcode = local_opcode as u8;
231
232 self.adapter
233 .write(state.memory, instruction, [rd].into(), &mut adapter_record);
234
235 *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
236
237 Ok(())
238 }
239}
240
241impl<F, A, const NUM_LIMBS: usize, const LIMB_BITS: usize> TraceFiller<F>
242 for BaseAluFiller<A, NUM_LIMBS, LIMB_BITS>
243where
244 F: PrimeField32,
245 A: 'static + AdapterTraceFiller<F>,
246{
247 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
248 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
251 self.adapter.fill_trace_row(mem_helper, adapter_row);
252 let record: &BaseAluCoreRecord<NUM_LIMBS> =
255 unsafe { get_record_from_slice(&mut core_row, ()) };
256 let core_row: &mut BaseAluCoreCols<F, NUM_LIMBS, LIMB_BITS> = core_row.borrow_mut();
257 let local_opcode = BaseAluOpcode::from_usize(record.local_opcode as usize);
267 let a = run_alu::<NUM_LIMBS, LIMB_BITS>(local_opcode, &record.b, &record.c);
268 core_row.opcode_and_flag = F::from_bool(local_opcode == BaseAluOpcode::AND);
270 core_row.opcode_or_flag = F::from_bool(local_opcode == BaseAluOpcode::OR);
271 core_row.opcode_xor_flag = F::from_bool(local_opcode == BaseAluOpcode::XOR);
272 core_row.opcode_sub_flag = F::from_bool(local_opcode == BaseAluOpcode::SUB);
273 core_row.opcode_add_flag = F::from_bool(local_opcode == BaseAluOpcode::ADD);
274
275 if local_opcode == BaseAluOpcode::ADD || local_opcode == BaseAluOpcode::SUB {
276 for a_val in a {
277 self.bitwise_lookup_chip
278 .request_xor(a_val as u32, a_val as u32);
279 }
280 } else {
281 for (b_val, c_val) in zip(record.b, record.c) {
282 self.bitwise_lookup_chip
283 .request_xor(b_val as u32, c_val as u32);
284 }
285 }
286 core_row.c = record.c.map(F::from_u8);
287 core_row.b = record.b.map(F::from_u8);
288 core_row.a = a.map(F::from_u8);
289 }
290}
291
292#[inline(always)]
293pub(super) fn run_alu<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
294 opcode: BaseAluOpcode,
295 x: &[u8; NUM_LIMBS],
296 y: &[u8; NUM_LIMBS],
297) -> [u8; NUM_LIMBS] {
298 debug_assert!(LIMB_BITS <= 8, "specialize for bytes");
299 match opcode {
300 BaseAluOpcode::ADD => run_add::<NUM_LIMBS, LIMB_BITS>(x, y),
301 BaseAluOpcode::SUB => run_subtract::<NUM_LIMBS, LIMB_BITS>(x, y),
302 BaseAluOpcode::XOR => run_xor::<NUM_LIMBS>(x, y),
303 BaseAluOpcode::OR => run_or::<NUM_LIMBS>(x, y),
304 BaseAluOpcode::AND => run_and::<NUM_LIMBS>(x, y),
305 }
306}
307
308#[inline(always)]
309fn run_add<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
310 x: &[u8; NUM_LIMBS],
311 y: &[u8; NUM_LIMBS],
312) -> [u8; NUM_LIMBS] {
313 let mut z = [0u8; NUM_LIMBS];
314 let mut carry = [0u8; NUM_LIMBS];
315 for i in 0..NUM_LIMBS {
316 let mut overflow =
317 (x[i] as u16) + (y[i] as u16) + if i > 0 { carry[i - 1] as u16 } else { 0 };
318 carry[i] = (overflow >> LIMB_BITS) as u8;
319 overflow &= (1u16 << LIMB_BITS) - 1;
320 z[i] = overflow as u8;
321 }
322 z
323}
324
325#[inline(always)]
326fn run_subtract<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
327 x: &[u8; NUM_LIMBS],
328 y: &[u8; NUM_LIMBS],
329) -> [u8; NUM_LIMBS] {
330 let mut z = [0u8; NUM_LIMBS];
331 let mut carry = [0u8; NUM_LIMBS];
332 for i in 0..NUM_LIMBS {
333 let rhs = y[i] as u16 + if i > 0 { carry[i - 1] as u16 } else { 0 };
334 if x[i] as u16 >= rhs {
335 z[i] = x[i] - rhs as u8;
336 carry[i] = 0;
337 } else {
338 z[i] = (x[i] as u16 + (1u16 << LIMB_BITS) - rhs) as u8;
339 carry[i] = 1;
340 }
341 }
342 z
343}
344
345#[inline(always)]
346fn run_xor<const NUM_LIMBS: usize>(x: &[u8; NUM_LIMBS], y: &[u8; NUM_LIMBS]) -> [u8; NUM_LIMBS] {
347 array::from_fn(|i| x[i] ^ y[i])
348}
349
350#[inline(always)]
351fn run_or<const NUM_LIMBS: usize>(x: &[u8; NUM_LIMBS], y: &[u8; NUM_LIMBS]) -> [u8; NUM_LIMBS] {
352 array::from_fn(|i| x[i] | y[i])
353}
354
355#[inline(always)]
356fn run_and<const NUM_LIMBS: usize>(x: &[u8; NUM_LIMBS], y: &[u8; NUM_LIMBS]) -> [u8; NUM_LIMBS] {
357 array::from_fn(|i| x[i] & y[i])
358}