openvm_native_circuit/adapters/
alu_native_adapter.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    mem::size_of,
4};
5
6use openvm_circuit::{
7    arch::{
8        get_record_from_slice, AdapterAirContext, AdapterTraceExecutor, AdapterTraceFiller,
9        BasicAdapterInterface, ExecutionBridge, ExecutionState, MinimalInstruction, VmAdapterAir,
10    },
11    system::{
12        memory::{
13            offline_checker::{
14                MemoryBridge, MemoryReadAuxRecord, MemoryReadOrImmediateAuxCols,
15                MemoryWriteAuxCols, MemoryWriteAuxRecord,
16            },
17            online::TracingMemory,
18            MemoryAddress, MemoryAuxColsFactory,
19        },
20        native_adapter::util::{tracing_read_or_imm_native, tracing_write_native},
21    },
22};
23use openvm_circuit_primitives::AlignedBytesBorrow;
24use openvm_circuit_primitives_derive::AlignedBorrow;
25use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP};
26use openvm_native_compiler::conversion::AS;
27use openvm_stark_backend::{
28    interaction::InteractionBuilder,
29    p3_air::BaseAir,
30    p3_field::{Field, FieldAlgebra, PrimeField32},
31};
32
33#[repr(C)]
34#[derive(AlignedBorrow)]
35pub struct AluNativeAdapterCols<T> {
36    pub from_state: ExecutionState<T>,
37    pub a_pointer: T,
38    pub b_pointer: T,
39    pub c_pointer: T,
40    pub e_as: T,
41    pub f_as: T,
42    pub reads_aux: [MemoryReadOrImmediateAuxCols<T>; 2],
43    pub write_aux: MemoryWriteAuxCols<T, 1>,
44}
45
46#[derive(Clone, Copy, Debug, derive_new::new)]
47pub struct AluNativeAdapterAir {
48    pub(super) execution_bridge: ExecutionBridge,
49    pub(super) memory_bridge: MemoryBridge,
50}
51
52impl<F: Field> BaseAir<F> for AluNativeAdapterAir {
53    fn width(&self) -> usize {
54        AluNativeAdapterCols::<F>::width()
55    }
56}
57
58impl<AB: InteractionBuilder> VmAdapterAir<AB> for AluNativeAdapterAir {
59    type Interface = BasicAdapterInterface<AB::Expr, MinimalInstruction<AB::Expr>, 2, 1, 1, 1>;
60
61    fn eval(
62        &self,
63        builder: &mut AB,
64        local: &[AB::Var],
65        ctx: AdapterAirContext<AB::Expr, Self::Interface>,
66    ) {
67        let cols: &AluNativeAdapterCols<_> = local.borrow();
68        let timestamp = cols.from_state.timestamp;
69        let mut timestamp_delta = 0usize;
70        let mut timestamp_pp = || {
71            timestamp_delta += 1;
72            timestamp + AB::F::from_canonical_usize(timestamp_delta - 1)
73        };
74
75        let native_as = AB::Expr::from_canonical_u32(AS::Native as u32);
76
77        // TODO: we assume address space is either 0 or 4, should we add a
78        //       constraint for that?
79        self.memory_bridge
80            .read_or_immediate(
81                MemoryAddress::new(cols.e_as, cols.b_pointer),
82                ctx.reads[0][0].clone(),
83                timestamp_pp(),
84                &cols.reads_aux[0],
85            )
86            .eval(builder, ctx.instruction.is_valid.clone());
87
88        self.memory_bridge
89            .read_or_immediate(
90                MemoryAddress::new(cols.f_as, cols.c_pointer),
91                ctx.reads[1][0].clone(),
92                timestamp_pp(),
93                &cols.reads_aux[1],
94            )
95            .eval(builder, ctx.instruction.is_valid.clone());
96
97        self.memory_bridge
98            .write(
99                MemoryAddress::new(native_as.clone(), cols.a_pointer),
100                ctx.writes[0].clone(),
101                timestamp_pp(),
102                &cols.write_aux,
103            )
104            .eval(builder, ctx.instruction.is_valid.clone());
105
106        self.execution_bridge
107            .execute_and_increment_or_set_pc(
108                ctx.instruction.opcode,
109                [
110                    cols.a_pointer.into(),
111                    cols.b_pointer.into(),
112                    cols.c_pointer.into(),
113                    native_as.clone(),
114                    cols.e_as.into(),
115                    cols.f_as.into(),
116                ],
117                cols.from_state,
118                AB::F::from_canonical_usize(timestamp_delta),
119                (DEFAULT_PC_STEP, ctx.to_pc),
120            )
121            .eval(builder, ctx.instruction.is_valid);
122    }
123
124    fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
125        let cols: &AluNativeAdapterCols<_> = local.borrow();
126        cols.from_state.pc
127    }
128}
129
130#[repr(C)]
131#[derive(AlignedBytesBorrow, Debug)]
132pub struct AluNativeAdapterRecord<F> {
133    pub from_pc: u32,
134    pub from_timestamp: u32,
135
136    pub a_ptr: F,
137    pub b: F,
138    pub c: F,
139
140    // Will set prev_timestamp to `u32::MAX` if the read is an immediate
141    pub reads_aux: [MemoryReadAuxRecord; 2],
142    pub write_aux: MemoryWriteAuxRecord<F, 1>,
143}
144
145#[derive(derive_new::new, Clone, Copy)]
146pub struct AluNativeAdapterExecutor;
147
148#[derive(derive_new::new)]
149pub struct AluNativeAdapterFiller;
150
151impl<F: PrimeField32> AdapterTraceExecutor<F> for AluNativeAdapterExecutor {
152    const WIDTH: usize = size_of::<AluNativeAdapterCols<u8>>();
153    type ReadData = [F; 2];
154    type WriteData = [F; 1];
155    type RecordMut<'a> = &'a mut AluNativeAdapterRecord<F>;
156
157    #[inline(always)]
158    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
159        record.from_pc = pc;
160        record.from_timestamp = memory.timestamp;
161    }
162
163    #[inline(always)]
164    fn read(
165        &self,
166        memory: &mut TracingMemory,
167        instruction: &Instruction<F>,
168        record: &mut Self::RecordMut<'_>,
169    ) -> Self::ReadData {
170        let &Instruction { b, c, e, f, .. } = instruction;
171
172        record.b = b;
173        let rs1 = tracing_read_or_imm_native(memory, e, b, &mut record.reads_aux[0].prev_timestamp);
174        record.c = c;
175        let rs2 = tracing_read_or_imm_native(memory, f, c, &mut record.reads_aux[1].prev_timestamp);
176        [rs1, rs2]
177    }
178
179    #[inline(always)]
180    fn write(
181        &self,
182        memory: &mut TracingMemory,
183        instruction: &Instruction<F>,
184        data: Self::WriteData,
185        record: &mut Self::RecordMut<'_>,
186    ) {
187        let &Instruction { a, .. } = instruction;
188
189        record.a_ptr = a;
190        tracing_write_native(
191            memory,
192            a.as_canonical_u32(),
193            data,
194            &mut record.write_aux.prev_timestamp,
195            &mut record.write_aux.prev_data,
196        );
197    }
198}
199
200impl<F: PrimeField32> AdapterTraceFiller<F> for AluNativeAdapterFiller {
201    const WIDTH: usize = size_of::<AluNativeAdapterCols<u8>>();
202
203    #[inline(always)]
204    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, mut adapter_row: &mut [F]) {
205        // SAFETY:
206        // - caller ensures `adapter_row` contains a valid record representation that was previously
207        //   written by the executor
208        let record: &AluNativeAdapterRecord<F> =
209            unsafe { get_record_from_slice(&mut adapter_row, ()) };
210        let adapter_row: &mut AluNativeAdapterCols<F> = adapter_row.borrow_mut();
211
212        // Writing in reverse order to avoid overwriting the `record`
213        adapter_row
214            .write_aux
215            .set_prev_data(record.write_aux.prev_data);
216        mem_helper.fill(
217            record.write_aux.prev_timestamp,
218            record.from_timestamp + 2,
219            adapter_row.write_aux.as_mut(),
220        );
221
222        let native_as = F::from_canonical_u32(AS::Native as u32);
223        for ((i, read_record), read_cols) in record
224            .reads_aux
225            .iter()
226            .enumerate()
227            .zip(adapter_row.reads_aux.iter_mut())
228            .rev()
229        {
230            let as_col = if i == 0 {
231                &mut adapter_row.e_as
232            } else {
233                &mut adapter_row.f_as
234            };
235            // previous timestamp is u32::MAX if the read is an immediate
236            if read_record.prev_timestamp == u32::MAX {
237                read_cols.is_zero_aux = F::ZERO;
238                read_cols.is_immediate = F::ONE;
239                mem_helper.fill(0, record.from_timestamp + i as u32, read_cols.as_mut());
240                *as_col = F::ZERO;
241            } else {
242                read_cols.is_zero_aux = native_as.inverse();
243                read_cols.is_immediate = F::ZERO;
244                mem_helper.fill(
245                    read_record.prev_timestamp,
246                    record.from_timestamp + i as u32,
247                    read_cols.as_mut(),
248                );
249                *as_col = native_as;
250            }
251        }
252
253        adapter_row.c_pointer = record.c;
254        adapter_row.b_pointer = record.b;
255        adapter_row.a_pointer = record.a_ptr;
256
257        adapter_row.from_state.timestamp = F::from_canonical_u32(record.from_timestamp);
258        adapter_row.from_state.pc = F::from_canonical_u32(record.from_pc);
259    }
260}