openvm_deferral_circuit/call/
trace.rs

1use std::{array::from_fn, borrow::BorrowMut, sync::Arc};
2
3use itertools::Itertools;
4use openvm_circuit::{
5    arch::{
6        get_record_from_slice, AdapterTraceExecutor, AdapterTraceFiller, EmptyAdapterCoreLayout,
7        ExecutionError, PreflightExecutor, RecordArena, TraceFiller, VmField, VmStateMut,
8        DEFAULT_BLOCK_SIZE,
9    },
10    system::memory::{
11        offline_checker::{MemoryReadAuxRecord, MemoryWriteAuxRecord, MemoryWriteBytesAuxRecord},
12        online::TracingMemory,
13        MemoryAuxColsFactory,
14    },
15};
16use openvm_circuit_primitives::{
17    bitwise_op_lookup::SharedBitwiseOperationLookupChip, AlignedBytesBorrow,
18};
19use openvm_deferral_transpiler::DeferralOpcode;
20use openvm_instructions::{
21    instruction::Instruction,
22    program::DEFAULT_PC_STEP,
23    riscv::{RV32_CELL_BITS, RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
24};
25use openvm_rv32im_circuit::adapters::{tracing_read, tracing_write};
26use openvm_stark_backend::p3_field::PrimeField32;
27use openvm_stark_sdk::config::baby_bear_poseidon2::DIGEST_SIZE;
28
29use crate::{
30    adapters::{tracing_read_deferral, tracing_write_deferral},
31    call::{DeferralCallAdapterCols, DeferralCallCoreCols, DeferralCallReads, DeferralCallWrites},
32    canonicity::CanonicityTraceGen,
33    count::DeferralCircuitCountChip,
34    poseidon2::{deferral_poseidon2_chip, DeferralPoseidon2Chip},
35    utils::{
36        byte_commit_to_f, combine_output, join_memory_ops, memory_op_chunk, COMMIT_MEMORY_OPS,
37        DIGEST_MEMORY_OPS, F_NUM_BYTES, OUTPUT_TOTAL_MEMORY_OPS,
38    },
39    DeferralFn,
40};
41
42// ========================= CORE ==============================
43
44#[repr(C)]
45#[derive(AlignedBytesBorrow, Debug)]
46pub struct DeferralCallCoreRecord<F> {
47    pub deferral_idx: F,
48    pub read_data: DeferralCallReads<u8, F>,
49    pub write_data: DeferralCallWrites<u8, F>,
50}
51
52#[derive(Clone, derive_new::new)]
53pub struct DeferralCallCoreExecutor<A> {
54    pub(in crate::call) adapter: A,
55    pub(in crate::call) deferral_fns: Vec<Arc<DeferralFn>>,
56}
57
58#[derive(Clone, derive_new::new)]
59pub struct DeferralCallCoreFiller<A, F: VmField> {
60    adapter: A,
61    count_chip: Arc<DeferralCircuitCountChip>,
62    poseidon2_chip: Arc<DeferralPoseidon2Chip<F>>,
63    bitwise_lookup_chip: SharedBitwiseOperationLookupChip<RV32_CELL_BITS>,
64    address_bits: usize,
65}
66
67impl<F, A, RA> PreflightExecutor<F, RA> for DeferralCallCoreExecutor<A>
68where
69    F: VmField,
70    A: 'static
71        + AdapterTraceExecutor<
72            F,
73            ReadData = DeferralCallReads<u8, F>,
74            WriteData = DeferralCallWrites<u8, F>,
75        >,
76    for<'buf> RA: RecordArena<
77        'buf,
78        EmptyAdapterCoreLayout<F, A>,
79        (A::RecordMut<'buf>, &'buf mut DeferralCallCoreRecord<F>),
80    >,
81{
82    fn get_opcode_name(&self, _opcode: usize) -> String {
83        format!("{:?}", DeferralOpcode::CALL)
84    }
85
86    fn execute(
87        &self,
88        state: VmStateMut<F, TracingMemory, RA>,
89        instruction: &Instruction<F>,
90    ) -> Result<(), ExecutionError> {
91        let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
92        A::start(*state.pc, state.memory, &mut adapter_record);
93        core_record.deferral_idx = instruction.c;
94
95        let read_data = self
96            .adapter
97            .read(state.memory, instruction, &mut adapter_record);
98        core_record.read_data = read_data;
99
100        let input_commit = byte_commit_to_f(&read_data.input_commit.map(F::from_u8));
101        let def_idx = instruction.c.as_canonical_u32();
102        let poseidon2_chip = deferral_poseidon2_chip();
103
104        let (output_commit, output_len) = self.deferral_fns[def_idx as usize].execute(
105            &read_data.input_commit.to_vec(),
106            &mut state.streams.deferrals[def_idx as usize],
107            def_idx,
108            &poseidon2_chip,
109        );
110
111        let output_f_commit =
112            byte_commit_to_f(&output_commit.iter().map(|v| F::from_u8(*v)).collect_vec());
113        let new_input_acc = poseidon2_chip.perm(&read_data.old_input_acc, &input_commit, true);
114        let new_output_acc = poseidon2_chip.perm(&read_data.old_output_acc, &output_f_commit, true);
115
116        let output_len_u32 =
117            u32::try_from(output_len).expect("deferral output length should fit in a u32");
118        let write_data = DeferralCallWrites {
119            output_commit: output_commit.try_into().unwrap(),
120            output_len: output_len_u32.to_le_bytes(),
121            new_input_acc,
122            new_output_acc,
123        };
124        core_record.write_data = write_data;
125        self.adapter
126            .write(state.memory, instruction, write_data, &mut adapter_record);
127
128        *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
129        Ok(())
130    }
131}
132
133impl<F, A> TraceFiller<F> for DeferralCallCoreFiller<A, F>
134where
135    F: VmField,
136    A: 'static + AdapterTraceFiller<F>,
137{
138    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
139        // SAFETY: row_slice is guaranteed by the caller to have at least A::WIDTH +
140        // DeferralCallCoreCols::width() elements
141        let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
142        self.adapter.fill_trace_row(mem_helper, adapter_row);
143
144        // SAFETY: core_row contains a valid DeferralCallCoreRecord written by the executor
145        // during trace generation
146        let record: &DeferralCallCoreRecord<F> =
147            unsafe { get_record_from_slice(&mut core_row, ()) };
148        let cols: &mut DeferralCallCoreCols<F> = core_row.borrow_mut();
149
150        let input_commit_f = record.read_data.input_commit.map(F::from_u8);
151        let output_commit_f = record.write_data.output_commit.map(F::from_u8);
152        let output_len_f = record.write_data.output_len.map(F::from_u8);
153
154        self.count_chip
155            .add_count(record.deferral_idx.as_canonical_u32());
156
157        let input_f_commit: [F; _] = byte_commit_to_f(&input_commit_f);
158        let output_f_commit: [F; _] = byte_commit_to_f(&output_commit_f);
159        self.poseidon2_chip
160            .perm_and_record(&record.read_data.old_input_acc, &input_f_commit, true);
161        self.poseidon2_chip.perm_and_record(
162            &record.read_data.old_output_acc,
163            &output_f_commit,
164            true,
165        );
166
167        for bytes in record.write_data.output_commit.chunks_exact(2) {
168            self.bitwise_lookup_chip
169                .request_range(bytes[0] as u32, bytes[1] as u32);
170        }
171        for bytes in record.write_data.output_len.chunks_exact(2) {
172            self.bitwise_lookup_chip
173                .request_range(bytes[0] as u32, bytes[1] as u32);
174        }
175
176        // NOTE: this range check is done in the adapter AIR
177        debug_assert!(RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS >= self.address_bits);
178        let limb_shift_bits = RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits;
179        self.bitwise_lookup_chip.request_range(
180            (record.write_data.output_len[RV32_REGISTER_NUM_LIMBS - 1] as u32) << limb_shift_bits,
181            0,
182        );
183
184        // Write columns in reverse order to avoid clobbering the record.
185        let input_commit_rcs = input_commit_f
186            .chunks_exact(F_NUM_BYTES)
187            .zip(cols.input_commit_lt_aux.iter_mut())
188            .map(|(bytes, aux)| {
189                let x_le = from_fn(|i| bytes[i]);
190                CanonicityTraceGen::generate_subrow(&x_le, aux)
191            })
192            .collect_vec();
193        for rc_pair in input_commit_rcs.chunks_exact(2) {
194            self.bitwise_lookup_chip
195                .request_range(rc_pair[0], rc_pair[1]);
196        }
197
198        let output_commit_rcs = output_commit_f
199            .chunks_exact(F_NUM_BYTES)
200            .zip(cols.output_commit_lt_aux.iter_mut())
201            .map(|(bytes, aux)| {
202                let x_le = from_fn(|i| bytes[i]);
203                CanonicityTraceGen::generate_subrow(&x_le, aux)
204            })
205            .collect_vec();
206        for rc_pair in output_commit_rcs.chunks_exact(2) {
207            self.bitwise_lookup_chip
208                .request_range(rc_pair[0], rc_pair[1]);
209        }
210
211        cols.writes.new_output_acc = record.write_data.new_output_acc;
212        cols.writes.new_input_acc = record.write_data.new_input_acc;
213        cols.writes.output_len = output_len_f;
214        cols.writes.output_commit = output_commit_f;
215        cols.reads.old_output_acc = record.read_data.old_output_acc;
216        cols.reads.old_input_acc = record.read_data.old_input_acc;
217        cols.reads.input_commit = input_commit_f;
218        cols.deferral_idx = record.deferral_idx;
219        cols.is_valid = F::ONE;
220    }
221}
222
223// ========================= ADAPTER ==============================
224
225#[repr(C)]
226#[derive(AlignedBytesBorrow, Debug)]
227pub struct DeferralCallAdapterRecord<F> {
228    pub from_pc: u32,
229    pub from_timestamp: u32,
230    pub rd_ptr: F,
231    pub rs_ptr: F,
232
233    // Heap pointers and auxiliary records
234    pub rd_val: [u8; RV32_REGISTER_NUM_LIMBS],
235    pub rs_val: [u8; RV32_REGISTER_NUM_LIMBS],
236    pub rd_aux: MemoryReadAuxRecord,
237    pub rs_aux: MemoryReadAuxRecord,
238
239    // Read auxiliary records
240    pub input_commit_aux: [MemoryReadAuxRecord; COMMIT_MEMORY_OPS],
241    pub old_input_acc_aux: [MemoryReadAuxRecord; DIGEST_MEMORY_OPS],
242    pub old_output_acc_aux: [MemoryReadAuxRecord; DIGEST_MEMORY_OPS],
243
244    // Write auxiliary records
245    pub output_commit_and_len_aux:
246        [MemoryWriteBytesAuxRecord<DEFAULT_BLOCK_SIZE>; OUTPUT_TOTAL_MEMORY_OPS],
247    pub new_input_acc_aux: [MemoryWriteAuxRecord<F, DEFAULT_BLOCK_SIZE>; DIGEST_MEMORY_OPS],
248    pub new_output_acc_aux: [MemoryWriteAuxRecord<F, DEFAULT_BLOCK_SIZE>; DIGEST_MEMORY_OPS],
249}
250
251#[derive(Clone, Copy)]
252pub struct DeferralCallAdapterExecutor;
253
254#[derive(Clone, derive_new::new)]
255pub struct DeferralCallAdapterFiller {
256    bitwise_lookup_chip: SharedBitwiseOperationLookupChip<RV32_CELL_BITS>,
257    address_bits: usize,
258}
259
260impl<F: PrimeField32> AdapterTraceExecutor<F> for DeferralCallAdapterExecutor {
261    const WIDTH: usize = DeferralCallAdapterCols::<u8>::width();
262    type ReadData = DeferralCallReads<u8, F>;
263    type WriteData = DeferralCallWrites<u8, F>;
264    type RecordMut<'a> = &'a mut DeferralCallAdapterRecord<F>;
265
266    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
267        record.from_pc = pc;
268        record.from_timestamp = memory.timestamp;
269    }
270
271    fn read(
272        &self,
273        memory: &mut TracingMemory,
274        instruction: &Instruction<F>,
275        record: &mut Self::RecordMut<'_>,
276    ) -> Self::ReadData {
277        let &Instruction { a, b, c, d, e, .. } = instruction;
278        debug_assert_eq!(d.as_canonical_u32(), RV32_REGISTER_AS);
279        debug_assert_eq!(e.as_canonical_u32(), RV32_MEMORY_AS);
280        record.rd_ptr = a;
281        record.rs_ptr = b;
282
283        record.rd_val = tracing_read(
284            memory,
285            d.as_canonical_u32(),
286            a.as_canonical_u32(),
287            &mut record.rd_aux.prev_timestamp,
288        );
289        record.rs_val = tracing_read(
290            memory,
291            d.as_canonical_u32(),
292            b.as_canonical_u32(),
293            &mut record.rs_aux.prev_timestamp,
294        );
295
296        let input_commit_chunks: [[u8; DEFAULT_BLOCK_SIZE]; COMMIT_MEMORY_OPS] = from_fn(|i| {
297            tracing_read(
298                memory,
299                e.as_canonical_u32(),
300                u32::from_le_bytes(record.rs_val) + (i * DEFAULT_BLOCK_SIZE) as u32,
301                &mut record.input_commit_aux[i].prev_timestamp,
302            )
303        });
304        let input_commit = join_memory_ops(input_commit_chunks);
305
306        let deferral_idx = c.as_canonical_u32();
307
308        const DIGEST_SIZE_U32: u32 = DIGEST_SIZE as u32;
309        let input_acc_ptr = 2 * deferral_idx * DIGEST_SIZE_U32;
310        let output_acc_ptr = input_acc_ptr + DIGEST_SIZE_U32;
311
312        let old_input_acc_chunks: [[F; DEFAULT_BLOCK_SIZE]; DIGEST_MEMORY_OPS] = from_fn(|i| {
313            tracing_read_deferral(
314                memory,
315                input_acc_ptr + (i * DEFAULT_BLOCK_SIZE) as u32,
316                &mut record.old_input_acc_aux[i].prev_timestamp,
317            )
318        });
319        let old_output_acc_chunks: [[F; DEFAULT_BLOCK_SIZE]; DIGEST_MEMORY_OPS] = from_fn(|i| {
320            tracing_read_deferral(
321                memory,
322                output_acc_ptr + (i * DEFAULT_BLOCK_SIZE) as u32,
323                &mut record.old_output_acc_aux[i].prev_timestamp,
324            )
325        });
326        let old_input_acc = join_memory_ops(old_input_acc_chunks);
327        let old_output_acc = join_memory_ops(old_output_acc_chunks);
328
329        DeferralCallReads {
330            input_commit,
331            old_input_acc,
332            old_output_acc,
333        }
334    }
335
336    fn write(
337        &self,
338        memory: &mut TracingMemory,
339        instruction: &Instruction<F>,
340        data: Self::WriteData,
341        record: &mut Self::RecordMut<'_>,
342    ) {
343        let &Instruction { c, e, .. } = instruction;
344        debug_assert_eq!(e.as_canonical_u32(), RV32_MEMORY_AS);
345
346        let output_len_full = from_fn(|i| {
347            if i < F_NUM_BYTES {
348                data.output_len[i]
349            } else {
350                0u8
351            }
352        });
353
354        let output_commit_and_len = combine_output(data.output_commit, output_len_full);
355        for chunk_idx in 0..OUTPUT_TOTAL_MEMORY_OPS {
356            tracing_write(
357                memory,
358                e.as_canonical_u32(),
359                u32::from_le_bytes(record.rd_val) + (chunk_idx * DEFAULT_BLOCK_SIZE) as u32,
360                memory_op_chunk(&output_commit_and_len, chunk_idx),
361                &mut record.output_commit_and_len_aux[chunk_idx].prev_timestamp,
362                &mut record.output_commit_and_len_aux[chunk_idx].prev_data,
363            );
364        }
365
366        let deferral_idx = c.as_canonical_u32();
367
368        const DIGEST_SIZE_U32: u32 = DIGEST_SIZE as u32;
369        let input_acc_ptr = 2 * deferral_idx * DIGEST_SIZE_U32;
370        let output_acc_ptr = input_acc_ptr + DIGEST_SIZE_U32;
371
372        for chunk_idx in 0..DIGEST_MEMORY_OPS {
373            tracing_write_deferral(
374                memory,
375                input_acc_ptr + (chunk_idx * DEFAULT_BLOCK_SIZE) as u32,
376                memory_op_chunk(&data.new_input_acc, chunk_idx),
377                &mut record.new_input_acc_aux[chunk_idx].prev_timestamp,
378                &mut record.new_input_acc_aux[chunk_idx].prev_data,
379            );
380        }
381
382        for chunk_idx in 0..DIGEST_MEMORY_OPS {
383            tracing_write_deferral(
384                memory,
385                output_acc_ptr + (chunk_idx * DEFAULT_BLOCK_SIZE) as u32,
386                memory_op_chunk(&data.new_output_acc, chunk_idx),
387                &mut record.new_output_acc_aux[chunk_idx].prev_timestamp,
388                &mut record.new_output_acc_aux[chunk_idx].prev_data,
389            );
390        }
391    }
392}
393
394impl<F: PrimeField32> AdapterTraceFiller<F> for DeferralCallAdapterFiller {
395    const WIDTH: usize = DeferralCallAdapterCols::<u8>::width();
396
397    #[inline(always)]
398    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, mut adapter_row: &mut [F]) {
399        // SAFETY: caller ensures `adapter_row` contains a valid record representation
400        // that was previously written by the executor
401        let record: &DeferralCallAdapterRecord<F> =
402            unsafe { get_record_from_slice(&mut adapter_row, ()) };
403        let adapter_row: &mut DeferralCallAdapterCols<F> = adapter_row.borrow_mut();
404
405        // Range checks must happen before we start writing adapter columns,
406        // since the record and columns share the same backing buffer.
407        debug_assert!(RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS >= self.address_bits);
408        let limb_shift_bits = RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits;
409
410        self.bitwise_lookup_chip.request_range(
411            (record.rd_val[RV32_REGISTER_NUM_LIMBS - 1] as u32) << limb_shift_bits,
412            (record.rs_val[RV32_REGISTER_NUM_LIMBS - 1] as u32) << limb_shift_bits,
413        );
414
415        // Timestamps in AIR are assigned in strict sequence starting from
416        // `from_state.timestamp`; mirror that exact sequence in reverse here.
417        let timestamp_delta =
418            2 + COMMIT_MEMORY_OPS + OUTPUT_TOTAL_MEMORY_OPS + 4 * DIGEST_MEMORY_OPS;
419        let mut timestamp = record.from_timestamp + timestamp_delta as u32;
420        let mut timestamp_mm = || {
421            timestamp -= 1;
422            timestamp
423        };
424
425        // Writing in reverse order to avoid overwriting the record
426        for chunk_idx in (0..DIGEST_MEMORY_OPS).rev() {
427            adapter_row.new_output_acc_aux[chunk_idx]
428                .set_prev_data(record.new_output_acc_aux[chunk_idx].prev_data);
429            mem_helper.fill(
430                record.new_output_acc_aux[chunk_idx].prev_timestamp,
431                timestamp_mm(),
432                adapter_row.new_output_acc_aux[chunk_idx].as_mut(),
433            );
434        }
435        for chunk_idx in (0..DIGEST_MEMORY_OPS).rev() {
436            adapter_row.new_input_acc_aux[chunk_idx]
437                .set_prev_data(record.new_input_acc_aux[chunk_idx].prev_data);
438            mem_helper.fill(
439                record.new_input_acc_aux[chunk_idx].prev_timestamp,
440                timestamp_mm(),
441                adapter_row.new_input_acc_aux[chunk_idx].as_mut(),
442            );
443        }
444        for chunk_idx in (0..OUTPUT_TOTAL_MEMORY_OPS).rev() {
445            adapter_row.output_commit_and_len_aux[chunk_idx].set_prev_data(
446                record.output_commit_and_len_aux[chunk_idx]
447                    .prev_data
448                    .map(F::from_u8),
449            );
450            mem_helper.fill(
451                record.output_commit_and_len_aux[chunk_idx].prev_timestamp,
452                timestamp_mm(),
453                adapter_row.output_commit_and_len_aux[chunk_idx].as_mut(),
454            );
455        }
456
457        for chunk_idx in (0..DIGEST_MEMORY_OPS).rev() {
458            mem_helper.fill(
459                record.old_output_acc_aux[chunk_idx].prev_timestamp,
460                timestamp_mm(),
461                adapter_row.old_output_acc_aux[chunk_idx].as_mut(),
462            );
463        }
464        for chunk_idx in (0..DIGEST_MEMORY_OPS).rev() {
465            mem_helper.fill(
466                record.old_input_acc_aux[chunk_idx].prev_timestamp,
467                timestamp_mm(),
468                adapter_row.old_input_acc_aux[chunk_idx].as_mut(),
469            );
470        }
471        for chunk_idx in (0..COMMIT_MEMORY_OPS).rev() {
472            mem_helper.fill(
473                record.input_commit_aux[chunk_idx].prev_timestamp,
474                timestamp_mm(),
475                adapter_row.input_commit_aux[chunk_idx].as_mut(),
476            );
477        }
478
479        mem_helper.fill(
480            record.rs_aux.prev_timestamp,
481            timestamp_mm(),
482            adapter_row.rs_aux.as_mut(),
483        );
484        mem_helper.fill(
485            record.rd_aux.prev_timestamp,
486            timestamp_mm(),
487            adapter_row.rd_aux.as_mut(),
488        );
489        adapter_row.rs_val = record.rs_val.map(F::from_u8);
490        adapter_row.rd_val = record.rd_val.map(F::from_u8);
491
492        adapter_row.rs_ptr = record.rs_ptr;
493        adapter_row.rd_ptr = record.rd_ptr;
494        adapter_row.from_state.timestamp = F::from_u32(record.from_timestamp);
495        adapter_row.from_state.pc = F::from_u32(record.from_pc);
496    }
497}