1use std::{
2 array::from_fn,
3 borrow::{Borrow, BorrowMut},
4};
5
6use itertools::izip;
7use openvm_circuit::{
8 arch::{
9 get_record_from_slice, AdapterAirContext, AdapterTraceExecutor, AdapterTraceFiller,
10 BasicAdapterInterface, ExecutionBridge, ExecutionState, MinimalInstruction, VmAdapterAir,
11 },
12 system::memory::{
13 offline_checker::{
14 MemoryBridge, MemoryReadAuxCols, MemoryReadAuxRecord, MemoryWriteAuxCols,
15 MemoryWriteBytesAuxRecord,
16 },
17 online::TracingMemory,
18 MemoryAddress, MemoryAuxColsFactory,
19 },
20};
21use openvm_circuit_primitives::{
22 bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
23 AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
24};
25use openvm_circuit_primitives_derive::AlignedBorrow;
26use openvm_instructions::{
27 instruction::Instruction,
28 program::DEFAULT_PC_STEP,
29 riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS},
30};
31use openvm_rv32im_circuit::adapters::{
32 tracing_read, tracing_write, RV32_CELL_BITS, RV32_REGISTER_NUM_LIMBS,
33};
34use openvm_stark_backend::{
35 interaction::InteractionBuilder,
36 p3_air::BaseAir,
37 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
38};
39
40#[repr(C)]
47#[derive(AlignedBorrow, StructReflection, Debug)]
48pub struct Rv32IsEqualModAdapterCols<
49 T,
50 const NUM_READS: usize,
51 const BLOCKS_PER_READ: usize,
52 const BLOCK_SIZE: usize,
53> {
54 pub from_state: ExecutionState<T>,
55
56 pub rs_ptr: [T; NUM_READS],
57 pub rs_val: [[T; RV32_REGISTER_NUM_LIMBS]; NUM_READS],
58 pub rs_read_aux: [MemoryReadAuxCols<T>; NUM_READS],
59 pub heap_read_aux: [[MemoryReadAuxCols<T>; BLOCKS_PER_READ]; NUM_READS],
60
61 pub rd_ptr: T,
62 pub writes_aux: MemoryWriteAuxCols<T, RV32_REGISTER_NUM_LIMBS>,
63}
64
65#[allow(dead_code)]
66#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
67#[columns_via(Rv32IsEqualModAdapterCols<u8, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>)]
68pub struct Rv32IsEqualModAdapterAir<
69 const NUM_READS: usize,
70 const BLOCKS_PER_READ: usize,
71 const BLOCK_SIZE: usize,
72 const TOTAL_READ_SIZE: usize,
73> {
74 pub(super) execution_bridge: ExecutionBridge,
75 pub(super) memory_bridge: MemoryBridge,
76 pub bus: BitwiseOperationLookupBus,
77 address_bits: usize,
78}
79
80impl<
81 F: Field,
82 const NUM_READS: usize,
83 const BLOCKS_PER_READ: usize,
84 const BLOCK_SIZE: usize,
85 const TOTAL_READ_SIZE: usize,
86 > BaseAir<F>
87 for Rv32IsEqualModAdapterAir<NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
88{
89 fn width(&self) -> usize {
90 Rv32IsEqualModAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>::width()
91 }
92}
93
94impl<
95 AB: InteractionBuilder,
96 const NUM_READS: usize,
97 const BLOCKS_PER_READ: usize,
98 const BLOCK_SIZE: usize,
99 const TOTAL_READ_SIZE: usize,
100 > VmAdapterAir<AB>
101 for Rv32IsEqualModAdapterAir<NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
102{
103 type Interface = BasicAdapterInterface<
104 AB::Expr,
105 MinimalInstruction<AB::Expr>,
106 NUM_READS,
107 1,
108 TOTAL_READ_SIZE,
109 RV32_REGISTER_NUM_LIMBS,
110 >;
111
112 fn eval(
113 &self,
114 builder: &mut AB,
115 local: &[AB::Var],
116 ctx: AdapterAirContext<AB::Expr, Self::Interface>,
117 ) {
118 let cols: &Rv32IsEqualModAdapterCols<_, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE> =
119 local.borrow();
120 let timestamp = cols.from_state.timestamp;
121 let mut timestamp_delta: usize = 0;
122 let mut timestamp_pp = || {
123 timestamp_delta += 1;
124 timestamp + AB::F::from_usize(timestamp_delta - 1)
125 };
126
127 let d = AB::F::from_u32(RV32_REGISTER_AS);
129 let e = AB::F::from_u32(RV32_MEMORY_AS);
130
131 for (ptr, val, aux) in izip!(cols.rs_ptr, cols.rs_val, &cols.rs_read_aux) {
133 self.memory_bridge
134 .read(MemoryAddress::new(d, ptr), val, timestamp_pp(), aux)
135 .eval(builder, ctx.instruction.is_valid.clone());
136 }
137
138 let rs_val_f = cols.rs_val.map(|decomp| {
141 decomp.iter().rev().fold(AB::Expr::ZERO, |acc, &limb| {
142 acc * AB::Expr::from_usize(1 << RV32_CELL_BITS) + limb
143 })
144 });
145
146 let need_range_check: [_; 2] = from_fn(|i| {
147 if i < NUM_READS {
148 cols.rs_val[i][RV32_REGISTER_NUM_LIMBS - 1].into()
149 } else {
150 AB::Expr::ZERO
151 }
152 });
153
154 let limb_shift =
155 AB::F::from_usize(1 << (RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits));
156
157 self.bus
158 .send_range(
159 need_range_check[0].clone() * limb_shift,
160 need_range_check[1].clone() * limb_shift,
161 )
162 .eval(builder, ctx.instruction.is_valid.clone());
163
164 assert_eq!(TOTAL_READ_SIZE, BLOCKS_PER_READ * BLOCK_SIZE);
166 let read_block_data: [[[_; BLOCK_SIZE]; BLOCKS_PER_READ]; NUM_READS] =
167 ctx.reads.map(|r: [AB::Expr; TOTAL_READ_SIZE]| {
168 let mut r_it = r.into_iter();
169 from_fn(|_| from_fn(|_| r_it.next().unwrap()))
170 });
171 let block_ptr_offset: [_; BLOCKS_PER_READ] = from_fn(|i| AB::F::from_usize(i * BLOCK_SIZE));
172
173 for (ptr, block_data, block_aux) in izip!(rs_val_f, read_block_data, &cols.heap_read_aux) {
174 for (offset, data, aux) in izip!(block_ptr_offset, block_data, block_aux) {
175 self.memory_bridge
176 .read(
177 MemoryAddress::new(e, ptr.clone() + offset),
178 data,
179 timestamp_pp(),
180 aux,
181 )
182 .eval(builder, ctx.instruction.is_valid.clone());
183 }
184 }
185
186 self.memory_bridge
188 .write(
189 MemoryAddress::new(d, cols.rd_ptr),
190 ctx.writes[0].clone(),
191 timestamp_pp(),
192 &cols.writes_aux,
193 )
194 .eval(builder, ctx.instruction.is_valid.clone());
195
196 self.execution_bridge
197 .execute_and_increment_or_set_pc(
198 ctx.instruction.opcode,
199 [
200 cols.rd_ptr.into(),
201 cols.rs_ptr
202 .first()
203 .map(|&x| x.into())
204 .unwrap_or(AB::Expr::ZERO),
205 cols.rs_ptr
206 .get(1)
207 .map(|&x| x.into())
208 .unwrap_or(AB::Expr::ZERO),
209 d.into(),
210 e.into(),
211 ],
212 cols.from_state,
213 AB::F::from_usize(timestamp_delta),
214 (DEFAULT_PC_STEP, ctx.to_pc),
215 )
216 .eval(builder, ctx.instruction.is_valid.clone());
217 }
218
219 fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
220 let cols: &Rv32IsEqualModAdapterCols<_, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE> =
221 local.borrow();
222 cols.from_state.pc
223 }
224}
225
226#[repr(C)]
227#[derive(AlignedBytesBorrow, Debug)]
228pub struct Rv32IsEqualModAdapterRecord<
229 const NUM_READS: usize,
230 const BLOCKS_PER_READ: usize,
231 const BLOCK_SIZE: usize,
232 const TOTAL_READ_SIZE: usize,
233> {
234 pub from_pc: u32,
235 pub timestamp: u32,
236
237 pub rs_ptr: [u32; NUM_READS],
238 pub rs_val: [u32; NUM_READS],
239 pub rs_read_aux: [MemoryReadAuxRecord; NUM_READS],
240 pub heap_read_aux: [[MemoryReadAuxRecord; BLOCKS_PER_READ]; NUM_READS],
241
242 pub rd_ptr: u32,
243 pub writes_aux: MemoryWriteBytesAuxRecord<RV32_REGISTER_NUM_LIMBS>,
244}
245
246#[derive(Clone, Copy)]
247pub struct Rv32IsEqualModAdapterExecutor<
248 const NUM_READS: usize,
249 const BLOCKS_PER_READ: usize,
250 const BLOCK_SIZE: usize,
251 const TOTAL_READ_SIZE: usize,
252> {
253 pointer_max_bits: usize,
254}
255
256#[derive(derive_new::new)]
257pub struct Rv32IsEqualModAdapterFiller<
258 const NUM_READS: usize,
259 const BLOCKS_PER_READ: usize,
260 const BLOCK_SIZE: usize,
261 const TOTAL_READ_SIZE: usize,
262> {
263 pointer_max_bits: usize,
264 pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<RV32_CELL_BITS>,
265}
266
267impl<
268 const NUM_READS: usize,
269 const BLOCKS_PER_READ: usize,
270 const BLOCK_SIZE: usize,
271 const TOTAL_READ_SIZE: usize,
272 > Rv32IsEqualModAdapterExecutor<NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
273{
274 pub fn new(pointer_max_bits: usize) -> Self {
275 assert!(NUM_READS <= 2);
276 assert_eq!(TOTAL_READ_SIZE, BLOCKS_PER_READ * BLOCK_SIZE);
277 assert!(
278 RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - pointer_max_bits < RV32_CELL_BITS,
279 "pointer_max_bits={pointer_max_bits} needs to be large enough for high limb range check"
280 );
281 Self { pointer_max_bits }
282 }
283}
284
285impl<
286 F: PrimeField32,
287 const NUM_READS: usize,
288 const BLOCKS_PER_READ: usize,
289 const BLOCK_SIZE: usize,
290 const TOTAL_READ_SIZE: usize,
291 > AdapterTraceExecutor<F>
292 for Rv32IsEqualModAdapterExecutor<NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
293where
294 F: PrimeField32,
295{
296 const WIDTH: usize =
297 Rv32IsEqualModAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>::width();
298 type ReadData = [[u8; TOTAL_READ_SIZE]; NUM_READS];
299 type WriteData = [u8; RV32_REGISTER_NUM_LIMBS];
300 type RecordMut<'a> = &'a mut Rv32IsEqualModAdapterRecord<
301 NUM_READS,
302 BLOCKS_PER_READ,
303 BLOCK_SIZE,
304 TOTAL_READ_SIZE,
305 >;
306
307 fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
308 record.from_pc = pc;
309 record.timestamp = memory.timestamp;
310 }
311
312 fn read(
313 &self,
314 memory: &mut TracingMemory,
315 instruction: &Instruction<F>,
316 record: &mut Self::RecordMut<'_>,
317 ) -> Self::ReadData {
318 let Instruction { b, c, d, e, .. } = *instruction;
319
320 debug_assert_eq!(d.as_canonical_u32(), RV32_REGISTER_AS);
321 debug_assert_eq!(e.as_canonical_u32(), RV32_MEMORY_AS);
322
323 record.rs_val = from_fn(|i| {
325 record.rs_ptr[i] = if i == 0 { b } else { c }.as_canonical_u32();
326
327 u32::from_le_bytes(tracing_read(
328 memory,
329 RV32_REGISTER_AS,
330 record.rs_ptr[i],
331 &mut record.rs_read_aux[i].prev_timestamp,
332 ))
333 });
334
335 from_fn(|i| {
337 debug_assert!(
338 record.rs_val[i] as usize + TOTAL_READ_SIZE - 1 < (1 << self.pointer_max_bits)
339 );
340 from_fn::<_, BLOCKS_PER_READ, _>(|j| {
341 tracing_read::<BLOCK_SIZE>(
342 memory,
343 RV32_MEMORY_AS,
344 record.rs_val[i] + (j * BLOCK_SIZE) as u32,
345 &mut record.heap_read_aux[i][j].prev_timestamp,
346 )
347 })
348 .concat()
349 .try_into()
350 .unwrap()
351 })
352 }
353
354 fn write(
355 &self,
356 memory: &mut TracingMemory,
357 instruction: &Instruction<F>,
358 data: Self::WriteData,
359 record: &mut Self::RecordMut<'_>,
360 ) {
361 let Instruction { a, .. } = *instruction;
362 record.rd_ptr = a.as_canonical_u32();
363 tracing_write(
364 memory,
365 RV32_REGISTER_AS,
366 record.rd_ptr,
367 data,
368 &mut record.writes_aux.prev_timestamp,
369 &mut record.writes_aux.prev_data,
370 );
371 }
372}
373
374impl<
375 F: PrimeField32,
376 const NUM_READS: usize,
377 const BLOCKS_PER_READ: usize,
378 const BLOCK_SIZE: usize,
379 const TOTAL_READ_SIZE: usize,
380 > AdapterTraceFiller<F>
381 for Rv32IsEqualModAdapterFiller<NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
382{
383 const WIDTH: usize =
384 Rv32IsEqualModAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>::width();
385
386 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, mut adapter_row: &mut [F]) {
387 let record: &Rv32IsEqualModAdapterRecord<
391 NUM_READS,
392 BLOCKS_PER_READ,
393 BLOCK_SIZE,
394 TOTAL_READ_SIZE,
395 > = unsafe { get_record_from_slice(&mut adapter_row, ()) };
396
397 let cols: &mut Rv32IsEqualModAdapterCols<F, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE> =
398 adapter_row.borrow_mut();
399
400 let mut timestamp = record.timestamp + (NUM_READS + NUM_READS * BLOCKS_PER_READ) as u32 + 1;
401 let mut timestamp_mm = || {
402 timestamp -= 1;
403 timestamp
404 };
405 debug_assert!(self.pointer_max_bits <= RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS);
407 let limb_shift_bits = RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.pointer_max_bits;
408 const MSL_SHIFT: usize = RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1);
409 self.bitwise_lookup_chip.request_range(
410 (record.rs_val[0] >> MSL_SHIFT) << limb_shift_bits,
411 if NUM_READS > 1 {
412 (record.rs_val[1] >> MSL_SHIFT) << limb_shift_bits
413 } else {
414 0
415 },
416 );
417 cols.writes_aux
419 .set_prev_data(record.writes_aux.prev_data.map(F::from_u8));
420 mem_helper.fill(
421 record.writes_aux.prev_timestamp,
422 timestamp_mm(),
423 cols.writes_aux.as_mut(),
424 );
425 cols.rd_ptr = F::from_u32(record.rd_ptr);
426
427 cols.heap_read_aux
429 .iter_mut()
430 .rev()
431 .zip(record.heap_read_aux.iter().rev())
432 .for_each(|(col_reads, record_reads)| {
433 col_reads
434 .iter_mut()
435 .rev()
436 .zip(record_reads.iter().rev())
437 .for_each(|(col, record)| {
438 mem_helper.fill(record.prev_timestamp, timestamp_mm(), col.as_mut());
439 });
440 });
441
442 cols.rs_read_aux
443 .iter_mut()
444 .rev()
445 .zip(record.rs_read_aux.iter().rev())
446 .for_each(|(col, record)| {
447 mem_helper.fill(record.prev_timestamp, timestamp_mm(), col.as_mut());
448 });
449
450 cols.rs_val = record.rs_val.map(|val| val.to_le_bytes().map(F::from_u8));
451 cols.rs_ptr = record.rs_ptr.map(|ptr| F::from_u32(ptr));
452
453 cols.from_state.timestamp = F::from_u32(record.timestamp);
454 cols.from_state.pc = F::from_u32(record.from_pc);
455 }
456}