1use std::{
2 array,
3 borrow::{Borrow, BorrowMut},
4};
5
6use openvm_circuit::{
7 arch::*,
8 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
9};
10use openvm_circuit_primitives::{
11 range_tuple::{RangeTupleCheckerBus, SharedRangeTupleCheckerChip},
12 AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
13};
14use openvm_circuit_primitives_derive::AlignedBorrow;
15use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP, LocalOpcode};
16use openvm_rv32im_transpiler::MulOpcode;
17use openvm_stark_backend::{
18 interaction::InteractionBuilder,
19 p3_air::BaseAir,
20 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
21 BaseAirWithPublicValues,
22};
23
24#[repr(C)]
25#[derive(AlignedBorrow, StructReflection)]
26pub struct MultiplicationCoreCols<T, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
27 pub a: [T; NUM_LIMBS],
28 pub b: [T; NUM_LIMBS],
29 pub c: [T; NUM_LIMBS],
30 pub is_valid: T,
31}
32
33#[derive(Copy, Clone, Debug, derive_new::new, ColumnsAir)]
34#[columns_via(MultiplicationCoreCols<u8, NUM_LIMBS, LIMB_BITS>)]
35pub struct MultiplicationCoreAir<const NUM_LIMBS: usize, const LIMB_BITS: usize> {
36 pub bus: RangeTupleCheckerBus<2>,
37 pub offset: usize,
38}
39
40impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAir<F>
41 for MultiplicationCoreAir<NUM_LIMBS, LIMB_BITS>
42{
43 fn width(&self) -> usize {
44 MultiplicationCoreCols::<F, NUM_LIMBS, LIMB_BITS>::width()
45 }
46}
47impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAirWithPublicValues<F>
48 for MultiplicationCoreAir<NUM_LIMBS, LIMB_BITS>
49{
50}
51
52impl<AB, I, const NUM_LIMBS: usize, const LIMB_BITS: usize> VmCoreAir<AB, I>
53 for MultiplicationCoreAir<NUM_LIMBS, LIMB_BITS>
54where
55 AB: InteractionBuilder,
56 I: VmAdapterInterface<AB::Expr>,
57 I::Reads: From<[[AB::Expr; NUM_LIMBS]; 2]>,
58 I::Writes: From<[[AB::Expr; NUM_LIMBS]; 1]>,
59 I::ProcessedInstruction: From<MinimalInstruction<AB::Expr>>,
60{
61 fn eval(
62 &self,
63 builder: &mut AB,
64 local_core: &[AB::Var],
65 _from_pc: AB::Var,
66 ) -> AdapterAirContext<AB::Expr, I> {
67 let cols: &MultiplicationCoreCols<_, NUM_LIMBS, LIMB_BITS> = local_core.borrow();
68 builder.assert_bool(cols.is_valid);
69
70 let a = &cols.a;
71 let b = &cols.b;
72 let c = &cols.c;
73
74 let mut carry: [AB::Expr; NUM_LIMBS] = array::from_fn(|_| AB::Expr::ZERO);
78 let carry_divide = AB::F::from_u32(1 << LIMB_BITS).inverse();
79
80 for i in 0..NUM_LIMBS {
81 let expected_limb = if i == 0 {
82 AB::Expr::ZERO
83 } else {
84 carry[i - 1].clone()
85 } + (0..=i).fold(AB::Expr::ZERO, |acc, k| acc + (b[k] * c[i - k]));
86 carry[i] = AB::Expr::from(carry_divide) * (expected_limb - a[i]);
87 }
88
89 for (a, carry) in a.iter().zip(carry.iter()) {
90 self.bus
91 .send(vec![(*a).into(), carry.clone()])
92 .eval(builder, cols.is_valid);
93 }
94
95 let expected_opcode = VmCoreAir::<AB, I>::opcode_to_global_expr(self, MulOpcode::MUL);
96
97 AdapterAirContext {
98 to_pc: None,
99 reads: [cols.b.map(Into::into), cols.c.map(Into::into)].into(),
100 writes: [cols.a.map(Into::into)].into(),
101 instruction: MinimalInstruction {
102 is_valid: cols.is_valid.into(),
103 opcode: expected_opcode,
104 }
105 .into(),
106 }
107 }
108
109 fn start_offset(&self) -> usize {
110 self.offset
111 }
112}
113
114#[repr(C)]
115#[derive(AlignedBytesBorrow, Debug)]
116pub struct MultiplicationCoreRecord<const NUM_LIMBS: usize, const LIMB_BITS: usize> {
117 pub b: [u8; NUM_LIMBS],
118 pub c: [u8; NUM_LIMBS],
119}
120
121#[derive(Clone, Copy, derive_new::new)]
122pub struct MultiplicationExecutor<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
123 adapter: A,
124 pub offset: usize,
125}
126
127#[derive(Clone, Debug)]
128pub struct MultiplicationFiller<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
129 adapter: A,
130 pub offset: usize,
131 pub range_tuple_chip: SharedRangeTupleCheckerChip<2>,
132}
133
134impl<A, const NUM_LIMBS: usize, const LIMB_BITS: usize>
135 MultiplicationFiller<A, NUM_LIMBS, LIMB_BITS>
136{
137 pub fn new(
138 adapter: A,
139 range_tuple_chip: SharedRangeTupleCheckerChip<2>,
140 offset: usize,
141 ) -> Self {
142 debug_assert!(
146 range_tuple_chip.sizes()[0] == 1 << LIMB_BITS,
147 "First element of RangeTupleChecker must have size {}",
148 1 << LIMB_BITS
149 );
150 debug_assert!(
151 range_tuple_chip.sizes()[1] >= (1 << LIMB_BITS) * NUM_LIMBS as u32,
152 "Second element of RangeTupleChecker must have size of at least {}",
153 (1 << LIMB_BITS) * NUM_LIMBS as u32
154 );
155
156 Self {
157 adapter,
158 offset,
159 range_tuple_chip,
160 }
161 }
162}
163
164impl<F, A, RA, const NUM_LIMBS: usize, const LIMB_BITS: usize> PreflightExecutor<F, RA>
165 for MultiplicationExecutor<A, NUM_LIMBS, LIMB_BITS>
166where
167 F: PrimeField32,
168 A: 'static
169 + AdapterTraceExecutor<
170 F,
171 ReadData: Into<[[u8; NUM_LIMBS]; 2]>,
172 WriteData: From<[[u8; NUM_LIMBS]; 1]>,
173 >,
174 for<'buf> RA: RecordArena<
175 'buf,
176 EmptyAdapterCoreLayout<F, A>,
177 (
178 A::RecordMut<'buf>,
179 &'buf mut MultiplicationCoreRecord<NUM_LIMBS, LIMB_BITS>,
180 ),
181 >,
182{
183 fn get_opcode_name(&self, opcode: usize) -> String {
184 format!("{:?}", MulOpcode::from_usize(opcode - self.offset))
185 }
186
187 fn execute(
188 &self,
189 state: VmStateMut<F, TracingMemory, RA>,
190 instruction: &Instruction<F>,
191 ) -> Result<(), ExecutionError> {
192 let Instruction { opcode, .. } = instruction;
193
194 debug_assert_eq!(
195 MulOpcode::from_usize(opcode.local_opcode_idx(self.offset)),
196 MulOpcode::MUL
197 );
198 let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
199
200 A::start(*state.pc, state.memory, &mut adapter_record);
201
202 let [rs1, rs2] = self
203 .adapter
204 .read(state.memory, instruction, &mut adapter_record)
205 .into();
206
207 let (a, _) = run_mul::<NUM_LIMBS, LIMB_BITS>(&rs1, &rs2);
208
209 core_record.b = rs1;
210 core_record.c = rs2;
211
212 self.adapter
213 .write(state.memory, instruction, [a].into(), &mut adapter_record);
214
215 *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
216 Ok(())
217 }
218}
219
220impl<F, A, const NUM_LIMBS: usize, const LIMB_BITS: usize> TraceFiller<F>
221 for MultiplicationFiller<A, NUM_LIMBS, LIMB_BITS>
222where
223 F: PrimeField32,
224 A: 'static + AdapterTraceFiller<F>,
225{
226 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
227 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
230 self.adapter.fill_trace_row(mem_helper, adapter_row);
231 let record: &MultiplicationCoreRecord<NUM_LIMBS, LIMB_BITS> =
234 unsafe { get_record_from_slice(&mut core_row, ()) };
235
236 let core_row: &mut MultiplicationCoreCols<F, NUM_LIMBS, LIMB_BITS> = core_row.borrow_mut();
237
238 let (a, carry) = run_mul::<NUM_LIMBS, LIMB_BITS>(&record.b, &record.c);
239
240 for (a, carry) in a.iter().zip(carry.iter()) {
241 self.range_tuple_chip.add_count(&[*a as u32, *carry]);
242 }
243
244 core_row.is_valid = F::ONE;
246 core_row.c = record.c.map(F::from_u8);
247 core_row.b = record.b.map(F::from_u8);
248 core_row.a = a.map(F::from_u8);
249 }
250}
251
252#[inline(always)]
254pub(super) fn run_mul<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
255 x: &[u8; NUM_LIMBS],
256 y: &[u8; NUM_LIMBS],
257) -> ([u8; NUM_LIMBS], [u32; NUM_LIMBS]) {
258 let mut result = [0u8; NUM_LIMBS];
259 let mut carry = [0u32; NUM_LIMBS];
260 for i in 0..NUM_LIMBS {
261 let mut res = 0u32;
262 if i > 0 {
263 res = carry[i - 1];
264 }
265 for j in 0..=i {
266 res += (x[j] as u32) * (y[i - j] as u32);
267 }
268 carry[i] = res >> LIMB_BITS;
269 res %= 1u32 << LIMB_BITS;
270 result[i] = res as u8;
271 }
272 (result, carry)
273}