1use std::{
2 array,
3 borrow::{Borrow, BorrowMut},
4 fmt::Debug,
5};
6
7use openvm_circuit::{
8 arch::*,
9 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
10};
11use openvm_circuit_primitives::{
12 AlignedBorrow, AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
13};
14use openvm_instructions::{
15 instruction::Instruction, program::DEFAULT_PC_STEP, riscv::RV32_REGISTER_NUM_LIMBS, LocalOpcode,
16};
17use openvm_rv32im_transpiler::Rv32LoadStoreOpcode::{self, *};
18use openvm_stark_backend::{
19 interaction::InteractionBuilder,
20 p3_air::{AirBuilder, BaseAir},
21 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
22 BaseAirWithPublicValues,
23};
24
25use crate::adapters::{LoadStoreInstruction, Rv32LoadStoreAdapterFiller};
26
27#[derive(Debug, Clone, Copy)]
28enum InstructionOpcode {
29 LoadW0,
30 LoadHu0,
31 LoadHu2,
32 LoadBu0,
33 LoadBu1,
34 LoadBu2,
35 LoadBu3,
36 StoreW0,
37 StoreH0,
38 StoreH2,
39 StoreB0,
40 StoreB1,
41 StoreB2,
42 StoreB3,
43}
44
45use InstructionOpcode::*;
46
47#[repr(C)]
52#[derive(Debug, Clone, AlignedBorrow, StructReflection)]
53pub struct LoadStoreCoreCols<T, const NUM_CELLS: usize> {
54 pub flags: [T; 4],
55 pub is_valid: T,
57 pub is_load: T,
58
59 pub read_data: [T; NUM_CELLS],
60 pub prev_data: [T; NUM_CELLS],
61 pub write_data: [T; NUM_CELLS],
64}
65
66#[derive(Debug, Clone, derive_new::new, ColumnsAir)]
67#[columns_via(LoadStoreCoreCols<u8, NUM_CELLS>)]
68pub struct LoadStoreCoreAir<const NUM_CELLS: usize> {
69 pub offset: usize,
70}
71
72impl<F: Field, const NUM_CELLS: usize> BaseAir<F> for LoadStoreCoreAir<NUM_CELLS> {
73 fn width(&self) -> usize {
74 LoadStoreCoreCols::<F, NUM_CELLS>::width()
75 }
76}
77
78impl<F: Field, const NUM_CELLS: usize> BaseAirWithPublicValues<F> for LoadStoreCoreAir<NUM_CELLS> {}
79
80impl<AB, I, const NUM_CELLS: usize> VmCoreAir<AB, I> for LoadStoreCoreAir<NUM_CELLS>
81where
82 AB: InteractionBuilder,
83 I: VmAdapterInterface<AB::Expr>,
84 I::Reads: From<([AB::Var; NUM_CELLS], [AB::Expr; NUM_CELLS])>,
85 I::Writes: From<[[AB::Expr; NUM_CELLS]; 1]>,
86 I::ProcessedInstruction: From<LoadStoreInstruction<AB::Expr>>,
87{
88 fn eval(
89 &self,
90 builder: &mut AB,
91 local_core: &[AB::Var],
92 _from_pc: AB::Var,
93 ) -> AdapterAirContext<AB::Expr, I> {
94 let cols: &LoadStoreCoreCols<AB::Var, NUM_CELLS> = (*local_core).borrow();
95 let LoadStoreCoreCols::<AB::Var, NUM_CELLS> {
96 read_data,
97 prev_data,
98 write_data,
99 flags,
100 is_valid,
101 is_load,
102 } = *cols;
103
104 let get_expr_12 = |x: &AB::Expr| (x.clone() - AB::Expr::ONE) * (x.clone() - AB::Expr::TWO);
105
106 builder.assert_bool(is_valid);
107 let sum = flags.iter().fold(AB::Expr::ZERO, |acc, &flag| {
108 builder.assert_zero(flag * get_expr_12(&flag.into()));
109 acc + flag
110 });
111 builder.assert_zero(sum.clone() * get_expr_12(&sum));
112 builder.when(get_expr_12(&sum)).assert_zero(is_valid);
114
115 let inv_2 = AB::F::from_u32(2).inverse();
118 let mut opcode_flags = vec![];
119 for flag in flags {
120 opcode_flags.push(flag * (flag - AB::F::ONE) * inv_2);
121 }
122 for flag in flags {
123 opcode_flags.push(flag * (sum.clone() - AB::F::TWO) * AB::F::NEG_ONE);
124 }
125 (0..4).for_each(|i| {
126 ((i + 1)..4).for_each(|j| opcode_flags.push(flags[i] * flags[j]));
127 });
128
129 let opcode_when = |idxs: &[InstructionOpcode]| -> AB::Expr {
130 idxs.iter().fold(AB::Expr::ZERO, |acc, &idx| {
131 acc + opcode_flags[idx as usize].clone()
132 })
133 };
134
135 builder.assert_eq(
137 is_load,
138 opcode_when(&[LoadW0, LoadHu0, LoadHu2, LoadBu0, LoadBu1, LoadBu2, LoadBu3]),
139 );
140 builder.when(is_load).assert_one(is_valid);
141
142 for (i, cell) in write_data.iter().enumerate() {
155 let expected_load_val = if i == 0 {
157 opcode_when(&[LoadW0, LoadHu0, LoadBu0]) * read_data[0]
158 + opcode_when(&[LoadBu1]) * read_data[1]
159 + opcode_when(&[LoadHu2, LoadBu2]) * read_data[2]
160 + opcode_when(&[LoadBu3]) * read_data[3]
161 } else if i < NUM_CELLS / 2 {
162 opcode_when(&[LoadW0, LoadHu0]) * read_data[i]
163 + opcode_when(&[LoadHu2]) * read_data[i + 2]
164 } else {
165 opcode_when(&[LoadW0]) * read_data[i]
166 };
167
168 let expected_store_val = if i == 0 {
170 opcode_when(&[StoreW0, StoreH0, StoreB0]) * read_data[i]
171 + opcode_when(&[StoreH2, StoreB1, StoreB2, StoreB3]) * prev_data[i]
172 } else if i == 1 {
173 opcode_when(&[StoreB1]) * read_data[i - 1]
174 + opcode_when(&[StoreW0, StoreH0]) * read_data[i]
175 + opcode_when(&[StoreH2, StoreB0, StoreB2, StoreB3]) * prev_data[i]
176 } else if i == 2 {
177 opcode_when(&[StoreH2, StoreB2]) * read_data[i - 2]
178 + opcode_when(&[StoreW0]) * read_data[i]
179 + opcode_when(&[StoreH0, StoreB0, StoreB1, StoreB3]) * prev_data[i]
180 } else if i == 3 {
181 opcode_when(&[StoreB3]) * read_data[i - 3]
182 + opcode_when(&[StoreH2]) * read_data[i - 2]
183 + opcode_when(&[StoreW0]) * read_data[i]
184 + opcode_when(&[StoreH0, StoreB0, StoreB1, StoreB2]) * prev_data[i]
185 } else {
186 opcode_when(&[StoreW0]) * read_data[i]
187 + opcode_when(&[StoreB0, StoreB1, StoreB2, StoreB3]) * prev_data[i]
188 + opcode_when(&[StoreH0])
189 * if i < NUM_CELLS / 2 {
190 read_data[i]
191 } else {
192 prev_data[i]
193 }
194 + opcode_when(&[StoreH2])
195 * if i - 2 < NUM_CELLS / 2 {
196 read_data[i - 2]
197 } else {
198 prev_data[i]
199 }
200 };
201 let expected_val = expected_load_val + expected_store_val;
202 builder.assert_eq(*cell, expected_val);
203 }
204
205 let expected_opcode = opcode_when(&[LoadW0]) * AB::Expr::from_u8(LOADW as u8)
206 + opcode_when(&[LoadHu0, LoadHu2]) * AB::Expr::from_u8(LOADHU as u8)
207 + opcode_when(&[LoadBu0, LoadBu1, LoadBu2, LoadBu3]) * AB::Expr::from_u8(LOADBU as u8)
208 + opcode_when(&[StoreW0]) * AB::Expr::from_u8(STOREW as u8)
209 + opcode_when(&[StoreH0, StoreH2]) * AB::Expr::from_u8(STOREH as u8)
210 + opcode_when(&[StoreB0, StoreB1, StoreB2, StoreB3]) * AB::Expr::from_u8(STOREB as u8);
211 let expected_opcode = VmCoreAir::<AB, I>::expr_to_global_expr(self, expected_opcode);
212
213 let load_shift_amount = opcode_when(&[LoadBu1]) * AB::Expr::ONE
214 + opcode_when(&[LoadHu2, LoadBu2]) * AB::Expr::TWO
215 + opcode_when(&[LoadBu3]) * AB::Expr::from_u32(3);
216
217 let store_shift_amount = opcode_when(&[StoreB1]) * AB::Expr::ONE
218 + opcode_when(&[StoreH2, StoreB2]) * AB::Expr::TWO
219 + opcode_when(&[StoreB3]) * AB::Expr::from_u32(3);
220
221 AdapterAirContext {
222 to_pc: None,
223 reads: (prev_data, read_data.map(|x| x.into())).into(),
224 writes: [write_data.map(|x| x.into())].into(),
225 instruction: LoadStoreInstruction {
226 is_valid: is_valid.into(),
227 opcode: expected_opcode,
228 is_load: is_load.into(),
229 load_shift_amount,
230 store_shift_amount,
231 }
232 .into(),
233 }
234 }
235
236 fn start_offset(&self) -> usize {
237 self.offset
238 }
239}
240
241#[repr(C)]
242#[derive(AlignedBytesBorrow, Debug)]
243pub struct LoadStoreCoreRecord<const NUM_CELLS: usize> {
244 pub local_opcode: u8,
245 pub shift_amount: u8,
246 pub read_data: [u8; NUM_CELLS],
247 pub prev_data: [u32; NUM_CELLS],
249}
250
251#[derive(Clone, Copy, derive_new::new)]
252pub struct LoadStoreExecutor<A, const NUM_CELLS: usize> {
253 adapter: A,
254 pub offset: usize,
255}
256
257#[derive(Clone, derive_new::new)]
258pub struct LoadStoreFiller<
259 A = Rv32LoadStoreAdapterFiller,
260 const NUM_CELLS: usize = RV32_REGISTER_NUM_LIMBS,
261> {
262 adapter: A,
263 pub offset: usize,
264}
265
266impl<F, A, RA, const NUM_CELLS: usize> PreflightExecutor<F, RA> for LoadStoreExecutor<A, NUM_CELLS>
267where
268 F: PrimeField32,
269 A: 'static
270 + AdapterTraceExecutor<
271 F,
272 ReadData = (([u32; NUM_CELLS], [u8; NUM_CELLS]), u8),
273 WriteData = [u32; NUM_CELLS],
274 >,
275 for<'buf> RA: RecordArena<
276 'buf,
277 EmptyAdapterCoreLayout<F, A>,
278 (A::RecordMut<'buf>, &'buf mut LoadStoreCoreRecord<NUM_CELLS>),
279 >,
280{
281 fn get_opcode_name(&self, opcode: usize) -> String {
282 format!(
283 "{:?}",
284 Rv32LoadStoreOpcode::from_usize(opcode - self.offset)
285 )
286 }
287
288 fn execute(
289 &self,
290 state: VmStateMut<F, TracingMemory, RA>,
291 instruction: &Instruction<F>,
292 ) -> Result<(), ExecutionError> {
293 let Instruction { opcode, .. } = instruction;
294
295 let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
296
297 A::start(*state.pc, state.memory, &mut adapter_record);
298
299 (
300 (core_record.prev_data, core_record.read_data),
301 core_record.shift_amount,
302 ) = self
303 .adapter
304 .read(state.memory, instruction, &mut adapter_record);
305
306 let local_opcode = Rv32LoadStoreOpcode::from_usize(opcode.local_opcode_idx(self.offset));
307 core_record.local_opcode = local_opcode as u8;
308
309 let write_data = run_write_data(
310 local_opcode,
311 core_record.read_data,
312 core_record.prev_data,
313 core_record.shift_amount as usize,
314 );
315 self.adapter
316 .write(state.memory, instruction, write_data, &mut adapter_record);
317
318 *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
319
320 Ok(())
321 }
322}
323
324impl<F, A, const NUM_CELLS: usize> TraceFiller<F> for LoadStoreFiller<A, NUM_CELLS>
325where
326 F: PrimeField32,
327 A: 'static + AdapterTraceFiller<F>,
328{
329 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
330 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
333 self.adapter.fill_trace_row(mem_helper, adapter_row);
334 let record: &LoadStoreCoreRecord<NUM_CELLS> =
337 unsafe { get_record_from_slice(&mut core_row, ()) };
338 let core_row: &mut LoadStoreCoreCols<F, NUM_CELLS> = core_row.borrow_mut();
339
340 let opcode = Rv32LoadStoreOpcode::from_usize(record.local_opcode as usize);
341 let shift = record.shift_amount;
342
343 let write_data = run_write_data(opcode, record.read_data, record.prev_data, shift as usize);
344 core_row.write_data = write_data.map(F::from_u32);
346 core_row.prev_data = record.prev_data.map(F::from_u32);
347 core_row.read_data = record.read_data.map(F::from_u8);
348 core_row.is_load = F::from_bool([LOADW, LOADHU, LOADBU].contains(&opcode));
349 core_row.is_valid = F::ONE;
350 let flags = &mut core_row.flags;
351 *flags = [F::ZERO; 4];
352 match (opcode, shift) {
353 (LOADW, 0) => flags[0] = F::TWO,
354 (LOADHU, 0) => flags[1] = F::TWO,
355 (LOADHU, 2) => flags[2] = F::TWO,
356 (LOADBU, 0) => flags[3] = F::TWO,
357
358 (LOADBU, 1) => flags[0] = F::ONE,
359 (LOADBU, 2) => flags[1] = F::ONE,
360 (LOADBU, 3) => flags[2] = F::ONE,
361 (STOREW, 0) => flags[3] = F::ONE,
362
363 (STOREH, 0) => (flags[0], flags[1]) = (F::ONE, F::ONE),
364 (STOREH, 2) => (flags[0], flags[2]) = (F::ONE, F::ONE),
365 (STOREB, 0) => (flags[0], flags[3]) = (F::ONE, F::ONE),
366 (STOREB, 1) => (flags[1], flags[2]) = (F::ONE, F::ONE),
367 (STOREB, 2) => (flags[1], flags[3]) = (F::ONE, F::ONE),
368 (STOREB, 3) => (flags[2], flags[3]) = (F::ONE, F::ONE),
369 _ => unreachable!(),
370 };
371 }
372}
373
374#[inline(always)]
376pub(super) fn run_write_data<const NUM_CELLS: usize>(
377 opcode: Rv32LoadStoreOpcode,
378 read_data: [u8; NUM_CELLS],
379 prev_data: [u32; NUM_CELLS],
380 shift: usize,
381) -> [u32; NUM_CELLS] {
382 match (opcode, shift) {
383 (LOADW, 0) => {
384 read_data.map(|x| x as u32)
385 },
386 (LOADBU, 0) | (LOADBU, 1) | (LOADBU, 2) | (LOADBU, 3) => {
387 let mut wrie_data = [0; NUM_CELLS];
388 wrie_data[0] = read_data[shift] as u32;
389 wrie_data
390 }
391 (LOADHU, 0) | (LOADHU, 2) => {
392 let mut write_data = [0; NUM_CELLS];
393 for (i, cell) in write_data.iter_mut().take(NUM_CELLS / 2).enumerate() {
394 *cell = read_data[i + shift] as u32;
395 }
396 write_data
397 }
398 (STOREW, 0) => {
399 read_data.map(|x| x as u32)
400 },
401 (STOREB, 0) | (STOREB, 1) | (STOREB, 2) | (STOREB, 3) => {
402 let mut write_data = prev_data;
403 write_data[shift] = read_data[0] as u32;
404 write_data
405 }
406 (STOREH, 0) | (STOREH, 2) => {
407 array::from_fn(|i| {
408 if i >= shift && i < (NUM_CELLS / 2 + shift){
409 read_data[i - shift] as u32
410 } else {
411 prev_data[i]
412 }
413 })
414 }
415 _ => unreachable!(
419 "unaligned memory access not supported by this execution environment: {opcode:?}, shift: {shift}"
420 ),
421 }
422}