openvm_rv32_adapters/
vec_heap_branch.rs

1use std::{
2    array::from_fn,
3    borrow::{Borrow, BorrowMut},
4    iter::zip,
5};
6
7use itertools::izip;
8use openvm_circuit::{
9    arch::{
10        get_record_from_slice, AdapterAirContext, AdapterTraceExecutor, AdapterTraceFiller,
11        ExecutionBridge, ExecutionState, VecHeapBranchAdapterInterface, VmAdapterAir,
12    },
13    system::memory::{
14        offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryReadAuxRecord},
15        online::TracingMemory,
16        MemoryAddress, MemoryAuxColsFactory,
17    },
18};
19use openvm_circuit_primitives::{
20    bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
21    AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
22};
23use openvm_circuit_primitives_derive::AlignedBorrow;
24use openvm_instructions::{
25    instruction::Instruction,
26    program::DEFAULT_PC_STEP,
27    riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS},
28};
29use openvm_rv32im_circuit::adapters::{
30    abstract_compose, tracing_read, RV32_CELL_BITS, RV32_REGISTER_NUM_LIMBS,
31};
32use openvm_stark_backend::{
33    interaction::InteractionBuilder,
34    p3_air::BaseAir,
35    p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
36};
37
38/// This adapter reads from NUM_READS <= 2 pointers (for branch operations).
39/// * The data is read from the heap (address space 2), and the pointers are read from registers
40///   (address space 1).
41/// * Reads take the form of `BLOCKS_PER_READ` consecutive reads of size `READ_SIZE` from the heap,
42///   starting from the addresses in `rs[0]` (and `rs[1]` if `NUM_READS = 2`).
43/// * No writes are performed (branch operations only compare values).
44#[repr(C)]
45#[derive(AlignedBorrow, StructReflection, Debug)]
46pub struct Rv32VecHeapBranchAdapterCols<
47    T,
48    const NUM_READS: usize,
49    const BLOCKS_PER_READ: usize,
50    const READ_SIZE: usize,
51> {
52    pub from_state: ExecutionState<T>,
53
54    pub rs_ptr: [T; NUM_READS],
55    pub rs_val: [[T; RV32_REGISTER_NUM_LIMBS]; NUM_READS],
56    pub rs_read_aux: [MemoryReadAuxCols<T>; NUM_READS],
57
58    pub reads_aux: [[MemoryReadAuxCols<T>; BLOCKS_PER_READ]; NUM_READS],
59}
60
61#[allow(dead_code)]
62#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
63#[columns_via(Rv32VecHeapBranchAdapterCols<u8, NUM_READS, BLOCKS_PER_READ, READ_SIZE>)]
64pub struct Rv32VecHeapBranchAdapterAir<
65    const NUM_READS: usize,
66    const BLOCKS_PER_READ: usize,
67    const READ_SIZE: usize,
68> {
69    pub(super) execution_bridge: ExecutionBridge,
70    pub(super) memory_bridge: MemoryBridge,
71    pub bus: BitwiseOperationLookupBus,
72    /// The max number of bits for an address in memory
73    address_bits: usize,
74}
75
76impl<F: Field, const NUM_READS: usize, const BLOCKS_PER_READ: usize, const READ_SIZE: usize>
77    BaseAir<F> for Rv32VecHeapBranchAdapterAir<NUM_READS, BLOCKS_PER_READ, READ_SIZE>
78{
79    fn width(&self) -> usize {
80        Rv32VecHeapBranchAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, READ_SIZE>::width()
81    }
82}
83
84impl<
85        AB: InteractionBuilder,
86        const NUM_READS: usize,
87        const BLOCKS_PER_READ: usize,
88        const READ_SIZE: usize,
89    > VmAdapterAir<AB> for Rv32VecHeapBranchAdapterAir<NUM_READS, BLOCKS_PER_READ, READ_SIZE>
90{
91    type Interface = VecHeapBranchAdapterInterface<AB::Expr, NUM_READS, BLOCKS_PER_READ, READ_SIZE>;
92
93    fn eval(
94        &self,
95        builder: &mut AB,
96        local: &[AB::Var],
97        ctx: AdapterAirContext<AB::Expr, Self::Interface>,
98    ) {
99        let cols: &Rv32VecHeapBranchAdapterCols<_, NUM_READS, BLOCKS_PER_READ, READ_SIZE> =
100            local.borrow();
101        let timestamp = cols.from_state.timestamp;
102        let mut timestamp_delta: usize = 0;
103        let mut timestamp_pp = || {
104            timestamp_delta += 1;
105            timestamp + AB::F::from_usize(timestamp_delta - 1)
106        };
107
108        // Read register values for rs
109        for (ptr, val, aux) in izip!(cols.rs_ptr, cols.rs_val, &cols.rs_read_aux) {
110            self.memory_bridge
111                .read(
112                    MemoryAddress::new(AB::F::from_u32(RV32_REGISTER_AS), ptr),
113                    val,
114                    timestamp_pp(),
115                    aux,
116                )
117                .eval(builder, ctx.instruction.is_valid.clone());
118        }
119
120        // We constrain the highest limbs of heap pointers to be less than 2^(addr_bits -
121        // (RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1))). This ensures that no overflow
122        // occurs when computing memory pointers.
123        let need_range_check: Vec<AB::Var> = cols
124            .rs_val
125            .iter()
126            .map(|val| val[RV32_REGISTER_NUM_LIMBS - 1])
127            .collect();
128
129        // range checks constrain to RV32_CELL_BITS bits, so we need to shift the limbs to constrain
130        // the correct amount of bits
131        let limb_shift =
132            AB::F::from_usize(1 << (RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits));
133
134        for pair in need_range_check.chunks(2) {
135            self.bus
136                .send_range(
137                    pair[0] * limb_shift,
138                    pair.get(1).map(|x| (*x).into()).unwrap_or(AB::Expr::ZERO) * limb_shift,
139                )
140                .eval(builder, ctx.instruction.is_valid.clone());
141        }
142
143        // Compose the u32 register value into single field element
144        let rs_val_f: [AB::Expr; NUM_READS] = cols.rs_val.map(abstract_compose);
145
146        let e = AB::F::from_u32(RV32_MEMORY_AS);
147        // Reads from heap
148        for (address, reads, reads_aux) in izip!(rs_val_f, ctx.reads, &cols.reads_aux) {
149            for (i, (read, aux)) in zip(reads, reads_aux).enumerate() {
150                self.memory_bridge
151                    .read(
152                        MemoryAddress::new(
153                            e,
154                            address.clone() + AB::Expr::from_usize(i * READ_SIZE),
155                        ),
156                        read,
157                        timestamp_pp(),
158                        aux,
159                    )
160                    .eval(builder, ctx.instruction.is_valid.clone());
161            }
162        }
163
164        self.execution_bridge
165            .execute_and_increment_or_set_pc(
166                ctx.instruction.opcode,
167                [
168                    cols.rs_ptr
169                        .first()
170                        .map(|&x| x.into())
171                        .unwrap_or(AB::Expr::ZERO),
172                    cols.rs_ptr
173                        .get(1)
174                        .map(|&x| x.into())
175                        .unwrap_or(AB::Expr::ZERO),
176                    ctx.instruction.immediate,
177                    AB::Expr::from_u32(RV32_REGISTER_AS),
178                    e.into(),
179                ],
180                cols.from_state,
181                AB::F::from_usize(timestamp_delta),
182                (DEFAULT_PC_STEP, ctx.to_pc),
183            )
184            .eval(builder, ctx.instruction.is_valid.clone());
185    }
186
187    fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
188        let cols: &Rv32VecHeapBranchAdapterCols<_, NUM_READS, BLOCKS_PER_READ, READ_SIZE> =
189            local.borrow();
190        cols.from_state.pc
191    }
192}
193
194// Intermediate type that should not be copied or cloned and should be directly written to
195#[repr(C)]
196#[derive(AlignedBytesBorrow, Debug)]
197pub struct Rv32VecHeapBranchAdapterRecord<
198    const NUM_READS: usize,
199    const BLOCKS_PER_READ: usize,
200    const READ_SIZE: usize,
201> {
202    pub from_pc: u32,
203    pub from_timestamp: u32,
204
205    pub rs_ptrs: [u32; NUM_READS],
206    pub rs_vals: [u32; NUM_READS],
207
208    pub rs_read_aux: [MemoryReadAuxRecord; NUM_READS],
209    pub reads_aux: [[MemoryReadAuxRecord; BLOCKS_PER_READ]; NUM_READS],
210}
211
212#[derive(derive_new::new, Clone, Copy)]
213pub struct Rv32VecHeapBranchAdapterExecutor<
214    const NUM_READS: usize,
215    const BLOCKS_PER_READ: usize,
216    const READ_SIZE: usize,
217> {
218    pointer_max_bits: usize,
219}
220
221#[derive(derive_new::new)]
222pub struct Rv32VecHeapBranchAdapterFiller<
223    const NUM_READS: usize,
224    const BLOCKS_PER_READ: usize,
225    const READ_SIZE: usize,
226> {
227    pointer_max_bits: usize,
228    pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<RV32_CELL_BITS>,
229}
230
231impl<
232        F: PrimeField32,
233        const NUM_READS: usize,
234        const BLOCKS_PER_READ: usize,
235        const READ_SIZE: usize,
236    > AdapterTraceExecutor<F>
237    for Rv32VecHeapBranchAdapterExecutor<NUM_READS, BLOCKS_PER_READ, READ_SIZE>
238{
239    const WIDTH: usize =
240        Rv32VecHeapBranchAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, READ_SIZE>::width();
241    type ReadData = [[[u8; READ_SIZE]; BLOCKS_PER_READ]; NUM_READS];
242    type WriteData = ();
243    type RecordMut<'a> =
244        &'a mut Rv32VecHeapBranchAdapterRecord<NUM_READS, BLOCKS_PER_READ, READ_SIZE>;
245
246    #[inline(always)]
247    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
248        record.from_pc = pc;
249        record.from_timestamp = memory.timestamp;
250    }
251
252    fn read(
253        &self,
254        memory: &mut TracingMemory,
255        instruction: &Instruction<F>,
256        record: &mut &mut Rv32VecHeapBranchAdapterRecord<NUM_READS, BLOCKS_PER_READ, READ_SIZE>,
257    ) -> Self::ReadData {
258        let &Instruction { a, b, d, e, .. } = instruction;
259
260        debug_assert_eq!(d.as_canonical_u32(), RV32_REGISTER_AS);
261        debug_assert_eq!(e.as_canonical_u32(), RV32_MEMORY_AS);
262
263        // Read register values
264        record.rs_vals = from_fn(|i| {
265            record.rs_ptrs[i] = if i == 0 { a } else { b }.as_canonical_u32();
266            u32::from_le_bytes(tracing_read(
267                memory,
268                RV32_REGISTER_AS,
269                record.rs_ptrs[i],
270                &mut record.rs_read_aux[i].prev_timestamp,
271            ))
272        });
273
274        // Read memory values
275        from_fn(|i| {
276            debug_assert!(
277                (record.rs_vals[i] + (READ_SIZE * BLOCKS_PER_READ - 1) as u32)
278                    < (1 << self.pointer_max_bits) as u32
279            );
280            from_fn(|j| {
281                tracing_read(
282                    memory,
283                    RV32_MEMORY_AS,
284                    record.rs_vals[i] + (j * READ_SIZE) as u32,
285                    &mut record.reads_aux[i][j].prev_timestamp,
286                )
287            })
288        })
289    }
290
291    fn write(
292        &self,
293        _memory: &mut TracingMemory,
294        _instruction: &Instruction<F>,
295        _data: Self::WriteData,
296        _record: &mut Self::RecordMut<'_>,
297    ) {
298        // Branch adapters don't write anything
299    }
300}
301
302impl<
303        F: PrimeField32,
304        const NUM_READS: usize,
305        const BLOCKS_PER_READ: usize,
306        const READ_SIZE: usize,
307    > AdapterTraceFiller<F>
308    for Rv32VecHeapBranchAdapterFiller<NUM_READS, BLOCKS_PER_READ, READ_SIZE>
309{
310    const WIDTH: usize =
311        Rv32VecHeapBranchAdapterCols::<F, NUM_READS, BLOCKS_PER_READ, READ_SIZE>::width();
312
313    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, mut adapter_row: &mut [F]) {
314        // SAFETY:
315        // - caller ensures `adapter_row` contains a valid record representation that was previously
316        //   written by the executor
317        let record: &Rv32VecHeapBranchAdapterRecord<NUM_READS, BLOCKS_PER_READ, READ_SIZE> =
318            unsafe { get_record_from_slice(&mut adapter_row, ()) };
319
320        let cols: &mut Rv32VecHeapBranchAdapterCols<F, NUM_READS, BLOCKS_PER_READ, READ_SIZE> =
321            adapter_row.borrow_mut();
322
323        // Range checks:
324        // **NOTE**: Must do the range checks before overwriting the records
325        debug_assert!(self.pointer_max_bits <= RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS);
326        let limb_shift_bits = RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.pointer_max_bits;
327        const MSL_SHIFT: usize = RV32_CELL_BITS * (RV32_REGISTER_NUM_LIMBS - 1);
328        self.bitwise_lookup_chip.request_range(
329            (record.rs_vals[0] >> MSL_SHIFT) << limb_shift_bits,
330            if NUM_READS > 1 {
331                (record.rs_vals[1] >> MSL_SHIFT) << limb_shift_bits
332            } else {
333                0
334            },
335        );
336
337        let timestamp_delta = NUM_READS + NUM_READS * BLOCKS_PER_READ;
338        let mut timestamp = record.from_timestamp + timestamp_delta as u32;
339        let mut timestamp_mm = || {
340            timestamp -= 1;
341            timestamp
342        };
343
344        // **NOTE**: Must iterate everything in reverse order to avoid overwriting the records
345        record
346            .reads_aux
347            .iter()
348            .zip(cols.reads_aux.iter_mut())
349            .rev()
350            .for_each(|(reads, cols_reads)| {
351                reads
352                    .iter()
353                    .zip(cols_reads.iter_mut())
354                    .rev()
355                    .for_each(|(read, cols_read)| {
356                        mem_helper.fill(read.prev_timestamp, timestamp_mm(), cols_read.as_mut());
357                    });
358            });
359
360        record
361            .rs_read_aux
362            .iter()
363            .zip(cols.rs_read_aux.iter_mut())
364            .rev()
365            .for_each(|(aux, cols_aux)| {
366                mem_helper.fill(aux.prev_timestamp, timestamp_mm(), cols_aux.as_mut());
367            });
368
369        cols.rs_val
370            .iter_mut()
371            .rev()
372            .zip(record.rs_vals.iter().rev())
373            .for_each(|(cols_val, val)| {
374                *cols_val = val.to_le_bytes().map(F::from_u8);
375            });
376        cols.rs_ptr
377            .iter_mut()
378            .rev()
379            .zip(record.rs_ptrs.iter().rev())
380            .for_each(|(cols_ptr, ptr)| {
381                *cols_ptr = F::from_u32(*ptr);
382            });
383        cols.from_state.timestamp = F::from_u32(record.from_timestamp);
384        cols.from_state.pc = F::from_u32(record.from_pc);
385    }
386}