openvm_rv32_adapters/
vec_heap.rs

1use std::{
2    array::from_fn,
3    borrow::{Borrow, BorrowMut},
4    iter::{once, zip},
5};
6
7use itertools::izip;
8use openvm_circuit::{
9    arch::{
10        get_record_from_slice, AdapterAirContext, AdapterTraceExecutor, AdapterTraceFiller,
11        ExecutionBridge, ExecutionState, VecHeapAdapterInterface, VmAdapterAir,
12    },
13    system::memory::{
14        offline_checker::{
15            MemoryBridge, MemoryReadAuxCols, MemoryReadAuxRecord, MemoryWriteAuxCols,
16            MemoryWriteBytesAuxRecord,
17        },
18        online::TracingMemory,
19        MemoryAddress, MemoryAuxColsFactory,
20    },
21};
22use openvm_circuit_primitives::{
23    bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
24    AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
25};
26use openvm_circuit_primitives_derive::AlignedBorrow;
27use openvm_instructions::{
28    instruction::Instruction,
29    program::DEFAULT_PC_STEP,
30    riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS},
31};
32use openvm_rv32im_circuit::adapters::{
33    abstract_compose, tracing_read, tracing_write, RV32_CELL_BITS, RV32_REGISTER_NUM_LIMBS,
34};
35use openvm_stark_backend::{
36    interaction::InteractionBuilder,
37    p3_air::BaseAir,
38    p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
39};
40
41/// This adapter reads from R (R <= 2) pointers and writes to 1 pointer.
42/// * The data is read from the heap (address space 2), and the pointers are read from registers
43///   (address space 1).
44/// * Reads take the form of `BLOCKS_PER_READ` consecutive reads of size `READ_SIZE` from the heap,
45///   starting from the addresses in `rs[0]` (and `rs[1]` if `R = 2`).
46/// * Writes take the form of `BLOCKS_PER_WRITE` consecutive writes of size `WRITE_SIZE` to the
47///   heap, starting from the address in `rd`.
48#[repr(C)]
49#[derive(AlignedBorrow, StructReflection, Debug)]
50pub struct Rv32VecHeapAdapterCols<
51    T,
52    const NUM_READS: usize,
53    const BLOCKS_PER_READ: usize,
54    const BLOCKS_PER_WRITE: usize,
55    const READ_SIZE: usize,
56    const WRITE_SIZE: usize,
57> {
58    pub from_state: ExecutionState<T>,
59
60    pub rs_ptr: [T; NUM_READS],
61    pub rd_ptr: T,
62
63    pub rs_val: [[T; RV32_REGISTER_NUM_LIMBS]; NUM_READS],
64    pub rd_val: [T; RV32_REGISTER_NUM_LIMBS],
65
66    pub rs_read_aux: [MemoryReadAuxCols<T>; NUM_READS],
67    pub rd_read_aux: MemoryReadAuxCols<T>,
68
69    pub reads_aux: [[MemoryReadAuxCols<T>; BLOCKS_PER_READ]; NUM_READS],
70    pub writes_aux: [MemoryWriteAuxCols<T, WRITE_SIZE>; BLOCKS_PER_WRITE],
71}
72
73#[allow(dead_code)]
74#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
75#[columns_via(Rv32VecHeapAdapterCols<u8, NUM_READS, BLOCKS_PER_READ, BLOCKS_PER_WRITE, READ_SIZE, WRITE_SIZE>)]
76pub struct Rv32VecHeapAdapterAir<
77    const NUM_READS: usize,
78    const BLOCKS_PER_READ: usize,
79    const BLOCKS_PER_WRITE: usize,
80    const READ_SIZE: usize,
81    const WRITE_SIZE: usize,
82> {
83    pub(super) execution_bridge: ExecutionBridge,
84    pub(super) memory_bridge: MemoryBridge,
85    pub bus: BitwiseOperationLookupBus,
86    /// The max number of bits for an address in memory
87    address_bits: usize,
88}
89
90impl<
91        F: Field,
92        const NUM_READS: usize,
93        const BLOCKS_PER_READ: usize,
94        const BLOCKS_PER_WRITE: usize,
95        const READ_SIZE: usize,
96        const WRITE_SIZE: usize,
97    > BaseAir<F>
98    for Rv32VecHeapAdapterAir<NUM_READS, BLOCKS_PER_READ, BLOCKS_PER_WRITE, READ_SIZE, WRITE_SIZE>
99{
100    fn width(&self) -> usize {
101        Rv32VecHeapAdapterCols::<
102            F,
103            NUM_READS,
104            BLOCKS_PER_READ,
105            BLOCKS_PER_WRITE,
106            READ_SIZE,
107            WRITE_SIZE,
108        >::width()
109    }
110}
111
112impl<
113        AB: InteractionBuilder,
114        const NUM_READS: usize,
115        const BLOCKS_PER_READ: usize,
116        const BLOCKS_PER_WRITE: usize,
117        const READ_SIZE: usize,
118        const WRITE_SIZE: usize,
119    > VmAdapterAir<AB>
120    for Rv32VecHeapAdapterAir<NUM_READS, BLOCKS_PER_READ, BLOCKS_PER_WRITE, READ_SIZE, WRITE_SIZE>
121{
122    type Interface = VecHeapAdapterInterface<
123        AB::Expr,
124        NUM_READS,
125        BLOCKS_PER_READ,
126        BLOCKS_PER_WRITE,
127        READ_SIZE,
128        WRITE_SIZE,
129    >;
130
131    fn eval(
132        &self,
133        builder: &mut AB,
134        local: &[AB::Var],
135        ctx: AdapterAirContext<AB::Expr, Self::Interface>,
136    ) {
137        let cols: &Rv32VecHeapAdapterCols<
138            _,
139            NUM_READS,
140            BLOCKS_PER_READ,
141            BLOCKS_PER_WRITE,
142            READ_SIZE,
143            WRITE_SIZE,
144        > = local.borrow();
145        let timestamp = cols.from_state.timestamp;
146        let mut timestamp_delta: usize = 0;
147        let mut timestamp_pp = || {
148            timestamp_delta += 1;
149            timestamp + AB::F::from_usize(timestamp_delta - 1)
150        };
151
152        // Read register values for rs, rd
153        for (ptr, val, aux) in izip!(cols.rs_ptr, cols.rs_val, &cols.rs_read_aux).chain(once((
154            cols.rd_ptr,
155            cols.rd_val,
156            &cols.rd_read_aux,
157        ))) {
158            self.memory_bridge
159                .read(
160                    MemoryAddress::new(AB::F::from_u32(RV32_REGISTER_AS), ptr),
161                    val,
162                    timestamp_pp(),
163                    aux,
164                )
165                .eval(builder, ctx.instruction.is_valid.clone());
166        }
167
168        // We constrain the highest limbs of heap pointers to be less than 2^(addr_bits -
169        // (RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1))). This ensures that no overflow
170        // occurs when computing memory pointers. Since the number of cells accessed with each
171        // address will be small enough, and combined with the memory argument, it ensures
172        // that all the cells accessed in the memory are less than 2^addr_bits.
173        let need_range_check: Vec<AB::Var> = cols
174            .rs_val
175            .iter()
176            .chain(std::iter::repeat_n(&cols.rd_val, 2))
177            .map(|val| val[RV32_REGISTER_NUM_LIMBS - 1])
178            .collect();
179
180        // range checks constrain to RV32_CELL_BITS bits, so we need to shift the limbs to constrain
181        // the correct amount of bits
182        let limb_shift =
183            AB::F::from_usize(1 << (RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits));
184
185        // Note: since limbs are read from memory we already know that limb[i] < 2^RV32_CELL_BITS
186        //       thus range checking limb[i] * shift < 2^RV32_CELL_BITS, gives us that
187        //       limb[i] < 2^(addr_bits - (RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1)))
188        for pair in need_range_check.chunks_exact(2) {
189            self.bus
190                .send_range(pair[0] * limb_shift, pair[1] * limb_shift)
191                .eval(builder, ctx.instruction.is_valid.clone());
192        }
193
194        // Compose the u32 register value into single field element, with `abstract_compose`
195        let rd_val_f: AB::Expr = abstract_compose(cols.rd_val);
196        let rs_val_f: [AB::Expr; NUM_READS] = cols.rs_val.map(abstract_compose);
197
198        let e = AB::F::from_u32(RV32_MEMORY_AS);
199        // Reads from heap
200        for (address, reads, reads_aux) in izip!(rs_val_f, ctx.reads, &cols.reads_aux,) {
201            for (i, (read, aux)) in zip(reads, reads_aux).enumerate() {
202                self.memory_bridge
203                    .read(
204                        MemoryAddress::new(
205                            e,
206                            address.clone() + AB::Expr::from_usize(i * READ_SIZE),
207                        ),
208                        read,
209                        timestamp_pp(),
210                        aux,
211                    )
212                    .eval(builder, ctx.instruction.is_valid.clone());
213            }
214        }
215
216        // Writes to heap
217        for (i, (write, aux)) in zip(ctx.writes, &cols.writes_aux).enumerate() {
218            self.memory_bridge
219                .write(
220                    MemoryAddress::new(e, rd_val_f.clone() + AB::Expr::from_usize(i * WRITE_SIZE)),
221                    write,
222                    timestamp_pp(),
223                    aux,
224                )
225                .eval(builder, ctx.instruction.is_valid.clone());
226        }
227
228        self.execution_bridge
229            .execute_and_increment_or_set_pc(
230                ctx.instruction.opcode,
231                [
232                    cols.rd_ptr.into(),
233                    cols.rs_ptr
234                        .first()
235                        .map(|&x| x.into())
236                        .unwrap_or(AB::Expr::ZERO),
237                    cols.rs_ptr
238                        .get(1)
239                        .map(|&x| x.into())
240                        .unwrap_or(AB::Expr::ZERO),
241                    AB::Expr::from_u32(RV32_REGISTER_AS),
242                    e.into(),
243                ],
244                cols.from_state,
245                AB::F::from_usize(timestamp_delta),
246                (DEFAULT_PC_STEP, ctx.to_pc),
247            )
248            .eval(builder, ctx.instruction.is_valid.clone());
249    }
250
251    fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
252        let cols: &Rv32VecHeapAdapterCols<
253            _,
254            NUM_READS,
255            BLOCKS_PER_READ,
256            BLOCKS_PER_WRITE,
257            READ_SIZE,
258            WRITE_SIZE,
259        > = local.borrow();
260        cols.from_state.pc
261    }
262}
263
264// Intermediate type that should not be copied or cloned and should be directly written to
265#[repr(C)]
266#[derive(AlignedBytesBorrow, Debug)]
267pub struct Rv32VecHeapAdapterRecord<
268    const NUM_READS: usize,
269    const BLOCKS_PER_READ: usize,
270    const BLOCKS_PER_WRITE: usize,
271    const READ_SIZE: usize,
272    const WRITE_SIZE: usize,
273> {
274    pub from_pc: u32,
275    pub from_timestamp: u32,
276
277    pub rs_ptrs: [u32; NUM_READS],
278    pub rd_ptr: u32,
279
280    pub rs_vals: [u32; NUM_READS],
281    pub rd_val: u32,
282
283    pub rs_read_aux: [MemoryReadAuxRecord; NUM_READS],
284    pub rd_read_aux: MemoryReadAuxRecord,
285
286    pub reads_aux: [[MemoryReadAuxRecord; BLOCKS_PER_READ]; NUM_READS],
287    pub writes_aux: [MemoryWriteBytesAuxRecord<WRITE_SIZE>; BLOCKS_PER_WRITE],
288}
289
290#[derive(derive_new::new, Clone, Copy)]
291pub struct Rv32VecHeapAdapterExecutor<
292    const NUM_READS: usize,
293    const BLOCKS_PER_READ: usize,
294    const BLOCKS_PER_WRITE: usize,
295    const READ_SIZE: usize,
296    const WRITE_SIZE: usize,
297> {
298    pointer_max_bits: usize,
299}
300
301#[derive(derive_new::new)]
302pub struct Rv32VecHeapAdapterFiller<
303    const NUM_READS: usize,
304    const BLOCKS_PER_READ: usize,
305    const BLOCKS_PER_WRITE: usize,
306    const READ_SIZE: usize,
307    const WRITE_SIZE: usize,
308> {
309    pointer_max_bits: usize,
310    pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<RV32_CELL_BITS>,
311}
312
313impl<
314        F: PrimeField32,
315        const NUM_READS: usize,
316        const BLOCKS_PER_READ: usize,
317        const BLOCKS_PER_WRITE: usize,
318        const READ_SIZE: usize,
319        const WRITE_SIZE: usize,
320    > AdapterTraceExecutor<F>
321    for Rv32VecHeapAdapterExecutor<
322        NUM_READS,
323        BLOCKS_PER_READ,
324        BLOCKS_PER_WRITE,
325        READ_SIZE,
326        WRITE_SIZE,
327    >
328{
329    const WIDTH: usize = Rv32VecHeapAdapterCols::<
330        F,
331        NUM_READS,
332        BLOCKS_PER_READ,
333        BLOCKS_PER_WRITE,
334        READ_SIZE,
335        WRITE_SIZE,
336    >::width();
337    type ReadData = [[[u8; READ_SIZE]; BLOCKS_PER_READ]; NUM_READS];
338    type WriteData = [[u8; WRITE_SIZE]; BLOCKS_PER_WRITE];
339    type RecordMut<'a> = &'a mut Rv32VecHeapAdapterRecord<
340        NUM_READS,
341        BLOCKS_PER_READ,
342        BLOCKS_PER_WRITE,
343        READ_SIZE,
344        WRITE_SIZE,
345    >;
346
347    #[inline(always)]
348    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
349        record.from_pc = pc;
350        record.from_timestamp = memory.timestamp;
351    }
352
353    fn read(
354        &self,
355        memory: &mut TracingMemory,
356        instruction: &Instruction<F>,
357        record: &mut &mut Rv32VecHeapAdapterRecord<
358            NUM_READS,
359            BLOCKS_PER_READ,
360            BLOCKS_PER_WRITE,
361            READ_SIZE,
362            WRITE_SIZE,
363        >,
364    ) -> Self::ReadData {
365        let &Instruction { a, b, c, d, e, .. } = instruction;
366
367        debug_assert_eq!(d.as_canonical_u32(), RV32_REGISTER_AS);
368        debug_assert_eq!(e.as_canonical_u32(), RV32_MEMORY_AS);
369
370        // Read register values
371        record.rs_vals = from_fn(|i| {
372            record.rs_ptrs[i] = if i == 0 { b } else { c }.as_canonical_u32();
373            u32::from_le_bytes(tracing_read(
374                memory,
375                RV32_REGISTER_AS,
376                record.rs_ptrs[i],
377                &mut record.rs_read_aux[i].prev_timestamp,
378            ))
379        });
380
381        record.rd_ptr = a.as_canonical_u32();
382        record.rd_val = u32::from_le_bytes(tracing_read(
383            memory,
384            RV32_REGISTER_AS,
385            a.as_canonical_u32(),
386            &mut record.rd_read_aux.prev_timestamp,
387        ));
388
389        // Read memory values
390        from_fn(|i| {
391            debug_assert!(
392                (record.rs_vals[i] + (READ_SIZE * BLOCKS_PER_READ - 1) as u32)
393                    < (1 << self.pointer_max_bits) as u32
394            );
395            from_fn(|j| {
396                tracing_read(
397                    memory,
398                    RV32_MEMORY_AS,
399                    record.rs_vals[i] + (j * READ_SIZE) as u32,
400                    &mut record.reads_aux[i][j].prev_timestamp,
401                )
402            })
403        })
404    }
405
406    fn write(
407        &self,
408        memory: &mut TracingMemory,
409        instruction: &Instruction<F>,
410        data: Self::WriteData,
411        record: &mut &mut Rv32VecHeapAdapterRecord<
412            NUM_READS,
413            BLOCKS_PER_READ,
414            BLOCKS_PER_WRITE,
415            READ_SIZE,
416            WRITE_SIZE,
417        >,
418    ) {
419        debug_assert_eq!(instruction.e.as_canonical_u32(), RV32_MEMORY_AS);
420
421        debug_assert!(
422            record.rd_val as usize + WRITE_SIZE * BLOCKS_PER_WRITE - 1
423                < (1 << self.pointer_max_bits)
424        );
425
426        #[allow(clippy::needless_range_loop)]
427        for i in 0..BLOCKS_PER_WRITE {
428            tracing_write(
429                memory,
430                RV32_MEMORY_AS,
431                record.rd_val + (i * WRITE_SIZE) as u32,
432                data[i],
433                &mut record.writes_aux[i].prev_timestamp,
434                &mut record.writes_aux[i].prev_data,
435            );
436        }
437    }
438}
439
440impl<
441        F: PrimeField32,
442        const NUM_READS: usize,
443        const BLOCKS_PER_READ: usize,
444        const BLOCKS_PER_WRITE: usize,
445        const READ_SIZE: usize,
446        const WRITE_SIZE: usize,
447    > AdapterTraceFiller<F>
448    for Rv32VecHeapAdapterFiller<
449        NUM_READS,
450        BLOCKS_PER_READ,
451        BLOCKS_PER_WRITE,
452        READ_SIZE,
453        WRITE_SIZE,
454    >
455{
456    const WIDTH: usize = Rv32VecHeapAdapterCols::<
457        F,
458        NUM_READS,
459        BLOCKS_PER_READ,
460        BLOCKS_PER_WRITE,
461        READ_SIZE,
462        WRITE_SIZE,
463    >::width();
464
465    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, mut adapter_row: &mut [F]) {
466        // SAFETY:
467        // - caller ensures `adapter_row` contains a valid record representation that was previously
468        //   written by the executor
469        let record: &Rv32VecHeapAdapterRecord<
470            NUM_READS,
471            BLOCKS_PER_READ,
472            BLOCKS_PER_WRITE,
473            READ_SIZE,
474            WRITE_SIZE,
475        > = unsafe { get_record_from_slice(&mut adapter_row, ()) };
476
477        let cols: &mut Rv32VecHeapAdapterCols<
478            F,
479            NUM_READS,
480            BLOCKS_PER_READ,
481            BLOCKS_PER_WRITE,
482            READ_SIZE,
483            WRITE_SIZE,
484        > = adapter_row.borrow_mut();
485
486        // Range checks:
487        // **NOTE**: Must do the range checks before overwriting the records
488        debug_assert!(self.pointer_max_bits <= RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS);
489        let limb_shift_bits = RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.pointer_max_bits;
490        const MSL_SHIFT: usize = RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1);
491        if NUM_READS > 1 {
492            self.bitwise_lookup_chip.request_range(
493                (record.rs_vals[0] >> MSL_SHIFT) << limb_shift_bits,
494                (record.rs_vals[1] >> MSL_SHIFT) << limb_shift_bits,
495            );
496            self.bitwise_lookup_chip.request_range(
497                (record.rd_val >> MSL_SHIFT) << limb_shift_bits,
498                (record.rd_val >> MSL_SHIFT) << limb_shift_bits,
499            );
500        } else {
501            self.bitwise_lookup_chip.request_range(
502                (record.rs_vals[0] >> MSL_SHIFT) << limb_shift_bits,
503                (record.rd_val >> MSL_SHIFT) << limb_shift_bits,
504            );
505        }
506
507        let timestamp_delta = NUM_READS + 1 + NUM_READS * BLOCKS_PER_READ + BLOCKS_PER_WRITE;
508        let mut timestamp = record.from_timestamp + timestamp_delta as u32;
509        let mut timestamp_mm = || {
510            timestamp -= 1;
511            timestamp
512        };
513
514        // **NOTE**: Must iterate everything in reverse order to avoid overwriting the records
515        record
516            .writes_aux
517            .iter()
518            .rev()
519            .zip(cols.writes_aux.iter_mut().rev())
520            .for_each(|(write, cols_write)| {
521                cols_write.set_prev_data(write.prev_data.map(F::from_u8));
522                mem_helper.fill(write.prev_timestamp, timestamp_mm(), cols_write.as_mut());
523            });
524
525        record
526            .reads_aux
527            .iter()
528            .zip(cols.reads_aux.iter_mut())
529            .rev()
530            .for_each(|(reads, cols_reads)| {
531                reads
532                    .iter()
533                    .zip(cols_reads.iter_mut())
534                    .rev()
535                    .for_each(|(read, cols_read)| {
536                        mem_helper.fill(read.prev_timestamp, timestamp_mm(), cols_read.as_mut());
537                    });
538            });
539
540        mem_helper.fill(
541            record.rd_read_aux.prev_timestamp,
542            timestamp_mm(),
543            cols.rd_read_aux.as_mut(),
544        );
545
546        record
547            .rs_read_aux
548            .iter()
549            .zip(cols.rs_read_aux.iter_mut())
550            .rev()
551            .for_each(|(aux, cols_aux)| {
552                mem_helper.fill(aux.prev_timestamp, timestamp_mm(), cols_aux.as_mut());
553            });
554
555        cols.rd_val = record.rd_val.to_le_bytes().map(F::from_u8);
556        cols.rs_val
557            .iter_mut()
558            .rev()
559            .zip(record.rs_vals.iter().rev())
560            .for_each(|(cols_val, val)| {
561                *cols_val = val.to_le_bytes().map(F::from_u8);
562            });
563        cols.rd_ptr = F::from_u32(record.rd_ptr);
564        cols.rs_ptr
565            .iter_mut()
566            .rev()
567            .zip(record.rs_ptrs.iter().rev())
568            .for_each(|(cols_ptr, ptr)| {
569                *cols_ptr = F::from_u32(*ptr);
570            });
571        cols.from_state.timestamp = F::from_u32(record.from_timestamp);
572        cols.from_state.pc = F::from_u32(record.from_pc);
573    }
574}