openvm_rv32_adapters/
eq_mod.rs

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/// This adapter reads from NUM_READS <= 2 pointers and writes to a register.
41/// * The data is read from the heap (address space 2), and the pointers are read from registers
42///   (address space 1).
43/// * Reads take the form of `BLOCKS_PER_READ` consecutive reads of size `BLOCK_SIZE` from the heap,
44///   starting from the addresses in `rs[0]` (and `rs[1]` if `R = 2`).
45/// * Writes are to 32-bit register rd.
46#[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        // Address spaces
128        let d = AB::F::from_u32(RV32_REGISTER_AS);
129        let e = AB::F::from_u32(RV32_MEMORY_AS);
130
131        // Read register values for rs
132        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        // Compose the u32 register value into single field element, with
139        // a range check on the highest limb.
140        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        // Reads from heap
165        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        // Write to rd register
187        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        // Read register values
324        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        // Read memory values
336        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        // SAFETY:
388        // - caller ensures `adapter_row` contains a valid record representation that was previously
389        //   written by the executor
390        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        // Do range checks before writing anything:
406        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        // Writing in reverse order
418        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        // **NOTE**: Must iterate everything in reverse order to avoid overwriting the records
428        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}