1use std::{
2 borrow::{Borrow, BorrowMut},
3 marker::PhantomData,
4};
5
6use openvm_circuit::{
7 arch::{
8 instructions::LocalOpcode, AdapterAirContext, AdapterRuntimeContext, ExecutionBridge,
9 ExecutionBus, ExecutionState, Result, VmAdapterAir, VmAdapterChip, VmAdapterInterface,
10 },
11 system::{
12 memory::{
13 offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryWriteAuxCols},
14 MemoryAddress, MemoryController, OfflineMemory, RecordId,
15 },
16 program::ProgramBus,
17 },
18};
19use openvm_circuit_primitives_derive::AlignedBorrow;
20use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP};
21use openvm_native_compiler::{
22 conversion::AS,
23 NativeLoadStoreOpcode::{self, *},
24};
25use openvm_stark_backend::{
26 interaction::InteractionBuilder,
27 p3_air::BaseAir,
28 p3_field::{Field, FieldAlgebra, PrimeField32},
29};
30use serde::{Deserialize, Serialize};
31
32pub struct NativeLoadStoreInstruction<T> {
33 pub is_valid: T,
34 pub opcode: T,
36 pub is_loadw: T,
37 pub is_storew: T,
38 pub is_hint_storew: T,
39}
40
41pub struct NativeLoadStoreAdapterInterface<T, const NUM_CELLS: usize>(PhantomData<T>);
42
43impl<T, const NUM_CELLS: usize> VmAdapterInterface<T>
44 for NativeLoadStoreAdapterInterface<T, NUM_CELLS>
45{
46 type Reads = (T, [T; NUM_CELLS]);
47 type Writes = [T; NUM_CELLS];
48 type ProcessedInstruction = NativeLoadStoreInstruction<T>;
49}
50
51#[derive(Debug)]
52pub struct NativeLoadStoreAdapterChip<F: Field, const NUM_CELLS: usize> {
53 pub air: NativeLoadStoreAdapterAir<NUM_CELLS>,
54 offset: usize,
55 _marker: PhantomData<F>,
56}
57
58impl<F: PrimeField32, const NUM_CELLS: usize> NativeLoadStoreAdapterChip<F, NUM_CELLS> {
59 pub fn new(
60 execution_bus: ExecutionBus,
61 program_bus: ProgramBus,
62 memory_bridge: MemoryBridge,
63 offset: usize,
64 ) -> Self {
65 Self {
66 air: NativeLoadStoreAdapterAir {
67 memory_bridge,
68 execution_bridge: ExecutionBridge::new(execution_bus, program_bus),
69 },
70 offset,
71 _marker: PhantomData,
72 }
73 }
74}
75
76#[repr(C)]
77#[derive(Clone, Debug, Serialize, Deserialize)]
78#[serde(bound = "F: Field")]
79pub struct NativeLoadStoreReadRecord<F: Field, const NUM_CELLS: usize> {
80 pub pointer_read: RecordId,
81 pub data_read: Option<RecordId>,
82 pub write_as: F,
83 pub write_ptr: F,
84
85 pub a: F,
86 pub b: F,
87 pub c: F,
88 pub d: F,
89 pub e: F,
90}
91
92#[repr(C)]
93#[derive(Clone, Debug, Serialize, Deserialize)]
94#[serde(bound = "F: Field")]
95pub struct NativeLoadStoreWriteRecord<F: Field, const NUM_CELLS: usize> {
96 pub from_state: ExecutionState<F>,
97 pub write_id: RecordId,
98}
99
100#[repr(C)]
101#[derive(Clone, Debug, AlignedBorrow)]
102pub struct NativeLoadStoreAdapterCols<T, const NUM_CELLS: usize> {
103 pub from_state: ExecutionState<T>,
104 pub a: T,
105 pub b: T,
106 pub c: T,
107
108 pub data_write_pointer: T,
109
110 pub pointer_read_aux_cols: MemoryReadAuxCols<T>,
111 pub data_read_aux_cols: MemoryReadAuxCols<T>,
112 pub data_write_aux_cols: MemoryWriteAuxCols<T, NUM_CELLS>,
113}
114
115#[derive(Clone, Copy, Debug, derive_new::new)]
116pub struct NativeLoadStoreAdapterAir<const NUM_CELLS: usize> {
117 pub(super) memory_bridge: MemoryBridge,
118 pub(super) execution_bridge: ExecutionBridge,
119}
120
121impl<F: Field, const NUM_CELLS: usize> BaseAir<F> for NativeLoadStoreAdapterAir<NUM_CELLS> {
122 fn width(&self) -> usize {
123 NativeLoadStoreAdapterCols::<F, NUM_CELLS>::width()
124 }
125}
126
127impl<AB: InteractionBuilder, const NUM_CELLS: usize> VmAdapterAir<AB>
128 for NativeLoadStoreAdapterAir<NUM_CELLS>
129{
130 type Interface = NativeLoadStoreAdapterInterface<AB::Expr, NUM_CELLS>;
131
132 fn eval(
133 &self,
134 builder: &mut AB,
135 local: &[AB::Var],
136 ctx: AdapterAirContext<AB::Expr, Self::Interface>,
137 ) {
138 let cols: &NativeLoadStoreAdapterCols<_, NUM_CELLS> = local.borrow();
139 let timestamp = cols.from_state.timestamp;
140 let mut timestamp_delta = AB::Expr::from_canonical_usize(0);
141
142 let is_valid = ctx.instruction.is_valid;
143 let is_loadw = ctx.instruction.is_loadw;
144 let is_storew = ctx.instruction.is_storew;
145 let is_hint_storew = ctx.instruction.is_hint_storew;
146
147 let native_as = AB::Expr::from_canonical_u32(AS::Native as u32);
148
149 let ptr = ctx.reads.0;
150 let data = ctx.writes;
153
154 self.memory_bridge
156 .read(
157 MemoryAddress::new(native_as.clone(), cols.c),
158 [ptr.clone()],
159 timestamp + timestamp_delta.clone(),
160 &cols.pointer_read_aux_cols,
161 )
162 .eval(builder, is_valid.clone());
163 timestamp_delta += is_valid.clone();
164
165 self.memory_bridge
166 .read(
167 MemoryAddress::new(
168 native_as.clone(),
169 is_storew.clone() * cols.a + is_loadw.clone() * (ptr.clone() + cols.b),
170 ),
171 data.clone(),
172 timestamp + timestamp_delta.clone(),
173 &cols.data_read_aux_cols,
174 )
175 .eval(builder, is_valid.clone() - is_hint_storew.clone());
176 timestamp_delta += is_valid.clone() - is_hint_storew.clone();
177
178 builder.assert_eq(
179 is_valid.clone() * cols.data_write_pointer,
180 is_loadw.clone() * cols.a
181 + (is_storew.clone() + is_hint_storew.clone()) * (ptr.clone() + cols.b),
182 );
183
184 self.memory_bridge
185 .write(
186 MemoryAddress::new(native_as.clone(), cols.data_write_pointer),
187 data.clone(),
188 timestamp + timestamp_delta.clone(),
189 &cols.data_write_aux_cols,
190 )
191 .eval(builder, is_valid.clone());
192 timestamp_delta += is_valid.clone();
193
194 self.execution_bridge
195 .execute_and_increment_or_set_pc(
196 ctx.instruction.opcode,
197 [
198 cols.a.into(),
199 cols.b.into(),
200 cols.c.into(),
201 native_as.clone(),
202 native_as.clone(),
203 ],
204 cols.from_state,
205 timestamp_delta.clone(),
206 (DEFAULT_PC_STEP, ctx.to_pc),
207 )
208 .eval(builder, is_valid.clone());
209 }
210
211 fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
212 let local_cols: &NativeLoadStoreAdapterCols<_, NUM_CELLS> = local.borrow();
213 local_cols.from_state.pc
214 }
215}
216
217impl<F: PrimeField32, const NUM_CELLS: usize> VmAdapterChip<F>
218 for NativeLoadStoreAdapterChip<F, NUM_CELLS>
219{
220 type ReadRecord = NativeLoadStoreReadRecord<F, NUM_CELLS>;
221 type WriteRecord = NativeLoadStoreWriteRecord<F, NUM_CELLS>;
222 type Air = NativeLoadStoreAdapterAir<NUM_CELLS>;
223 type Interface = NativeLoadStoreAdapterInterface<F, NUM_CELLS>;
224
225 fn preprocess(
226 &mut self,
227 memory: &mut MemoryController<F>,
228 instruction: &Instruction<F>,
229 ) -> Result<(
230 <Self::Interface as VmAdapterInterface<F>>::Reads,
231 Self::ReadRecord,
232 )> {
233 let Instruction {
234 opcode,
235 a,
236 b,
237 c,
238 d,
239 e,
240 ..
241 } = *instruction;
242 let local_opcode = NativeLoadStoreOpcode::from_usize(opcode.local_opcode_idx(self.offset));
243
244 let read_as = d;
245 let read_ptr = c;
246 let read_cell = memory.read_cell(read_as, read_ptr);
247
248 let (data_read_as, data_write_as) = {
249 match local_opcode {
250 LOADW => (e, d),
251 STOREW | HINT_STOREW => (d, e),
252 }
253 };
254 let (data_read_ptr, data_write_ptr) = {
255 match local_opcode {
256 LOADW => (read_cell.1 + b, a),
257 STOREW | HINT_STOREW => (a, read_cell.1 + b),
258 }
259 };
260
261 let data_read = match local_opcode {
262 HINT_STOREW => None,
263 LOADW | STOREW => Some(memory.read::<NUM_CELLS>(data_read_as, data_read_ptr)),
264 };
265 let record = NativeLoadStoreReadRecord {
266 pointer_read: read_cell.0,
267 data_read: data_read.map(|x| x.0),
268 write_as: data_write_as,
269 write_ptr: data_write_ptr,
270 a,
271 b,
272 c,
273 d,
274 e,
275 };
276
277 Ok((
278 (read_cell.1, data_read.map_or([F::ZERO; NUM_CELLS], |x| x.1)),
279 record,
280 ))
281 }
282
283 fn postprocess(
284 &mut self,
285 memory: &mut MemoryController<F>,
286 _instruction: &Instruction<F>,
287 from_state: ExecutionState<u32>,
288 output: AdapterRuntimeContext<F, Self::Interface>,
289 read_record: &Self::ReadRecord,
290 ) -> Result<(ExecutionState<u32>, Self::WriteRecord)> {
291 let (write_id, _) =
292 memory.write::<NUM_CELLS>(read_record.write_as, read_record.write_ptr, output.writes);
293 Ok((
294 ExecutionState {
295 pc: output.to_pc.unwrap_or(from_state.pc + DEFAULT_PC_STEP),
296 timestamp: memory.timestamp(),
297 },
298 Self::WriteRecord {
299 from_state: from_state.map(F::from_canonical_u32),
300 write_id,
301 },
302 ))
303 }
304
305 fn generate_trace_row(
306 &self,
307 row_slice: &mut [F],
308 read_record: Self::ReadRecord,
309 write_record: Self::WriteRecord,
310 memory: &OfflineMemory<F>,
311 ) {
312 let aux_cols_factory = memory.aux_cols_factory();
313 let cols: &mut NativeLoadStoreAdapterCols<_, NUM_CELLS> = row_slice.borrow_mut();
314 cols.from_state = write_record.from_state;
315 cols.a = read_record.a;
316 cols.b = read_record.b;
317 cols.c = read_record.c;
318
319 let data_read = read_record.data_read.map(|read| memory.record_by_id(read));
320 if let Some(data_read) = data_read {
321 aux_cols_factory.generate_read_aux(data_read, &mut cols.data_read_aux_cols);
322 }
323
324 let write = memory.record_by_id(write_record.write_id);
325 cols.data_write_pointer = write.pointer;
326
327 aux_cols_factory.generate_read_aux(
328 memory.record_by_id(read_record.pointer_read),
329 &mut cols.pointer_read_aux_cols,
330 );
331 aux_cols_factory.generate_write_aux(write, &mut cols.data_write_aux_cols);
332 }
333
334 fn air(&self) -> &Self::Air {
335 &self.air
336 }
337}