1use std::{array::from_fn, borrow::Borrow};
2
3use itertools::{izip, Itertools as _};
4use openvm_circuit::{
5 arch::{
6 AdapterAirContext, ExecutionBridge, ExecutionState, ImmInstruction, VmAdapterAir,
7 VmAdapterInterface, VmCoreAir, DEFAULT_BLOCK_SIZE,
8 },
9 system::memory::{
10 offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryWriteAuxCols},
11 MemoryAddress,
12 },
13};
14use openvm_circuit_primitives::{
15 bitwise_op_lookup::BitwiseOperationLookupBus, ColumnsAir, StructReflection,
16 StructReflectionHelper,
17};
18use openvm_circuit_primitives_derive::AlignedBorrow;
19use openvm_deferral_transpiler::DeferralOpcode;
20use openvm_instructions::{
21 program::DEFAULT_PC_STEP,
22 riscv::{RV32_CELL_BITS, RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
23 LocalOpcode, DEFERRAL_AS,
24};
25use openvm_stark_backend::{
26 interaction::InteractionBuilder,
27 p3_air::BaseAir,
28 p3_field::{Field, PrimeCharacteristicRing},
29 BaseAirWithPublicValues,
30};
31use openvm_stark_sdk::config::baby_bear_poseidon2::DIGEST_SIZE;
32use p3_field::PrimeField32;
33
34use crate::{
35 canonicity::{CanonicityAuxCols, CanonicitySubAir},
36 count::DeferralCircuitCountBus,
37 poseidon2::DeferralPoseidon2Bus,
38 utils::{
39 byte_commit_to_f, bytes_to_f, combine_output, split_memory_ops, COMMIT_MEMORY_OPS,
40 COMMIT_NUM_BYTES, DIGEST_MEMORY_OPS, F_NUM_BYTES, OUTPUT_TOTAL_BYTES,
41 OUTPUT_TOTAL_MEMORY_OPS,
42 },
43};
44
45#[repr(C)]
48#[derive(AlignedBorrow, StructReflection, Clone, Copy, Debug)]
49pub struct DeferralCallReads<B, F> {
50 pub input_commit: [B; COMMIT_NUM_BYTES],
52
53 pub old_input_acc: [F; DIGEST_SIZE],
55 pub old_output_acc: [F; DIGEST_SIZE],
56}
57
58#[repr(C)]
59#[derive(AlignedBorrow, StructReflection, Clone, Copy, Debug)]
60pub struct DeferralCallWrites<B, F> {
61 pub output_commit: [B; COMMIT_NUM_BYTES],
65 pub output_len: [B; F_NUM_BYTES],
66
67 pub new_input_acc: [F; DIGEST_SIZE],
69 pub new_output_acc: [F; DIGEST_SIZE],
70}
71
72#[repr(C)]
73#[derive(AlignedBorrow, StructReflection)]
74pub struct DeferralCallCoreCols<T> {
75 pub is_valid: T,
76 pub deferral_idx: T,
77 pub reads: DeferralCallReads<T, T>,
78 pub writes: DeferralCallWrites<T, T>,
79
80 pub input_commit_lt_aux: [CanonicityAuxCols<T>; DIGEST_SIZE],
81 pub output_commit_lt_aux: [CanonicityAuxCols<T>; DIGEST_SIZE],
82}
83
84#[derive(Copy, Clone, Debug, derive_new::new, ColumnsAir)]
85#[columns_via(DeferralCallCoreCols<u8>)]
86pub struct DeferralCallCoreAir {
87 pub count_bus: DeferralCircuitCountBus,
88 pub poseidon2_bus: DeferralPoseidon2Bus,
89 pub bitwise_bus: BitwiseOperationLookupBus,
90}
91
92impl<F: Field> BaseAir<F> for DeferralCallCoreAir {
93 fn width(&self) -> usize {
94 DeferralCallCoreCols::<F>::width()
95 }
96}
97impl<F: Field> BaseAirWithPublicValues<F> for DeferralCallCoreAir {}
98
99impl<AB, I> VmCoreAir<AB, I> for DeferralCallCoreAir
100where
101 AB: InteractionBuilder,
102 AB::F: PrimeField32,
103 I: VmAdapterInterface<AB::Expr>,
104 I::Reads: From<DeferralCallReads<AB::Expr, AB::Expr>>,
105 I::Writes: From<DeferralCallWrites<AB::Expr, AB::Expr>>,
106 I::ProcessedInstruction: From<ImmInstruction<AB::Expr>>,
107{
108 fn eval(
109 &self,
110 builder: &mut AB,
111 local_core: &[AB::Var],
112 _from_pc: AB::Var,
113 ) -> AdapterAirContext<AB::Expr, I> {
114 let cols: &DeferralCallCoreCols<_> = local_core.borrow();
115 builder.assert_bool(cols.is_valid);
116
117 let input_commit_rcs = izip!(
120 cols.reads.input_commit.chunks_exact(F_NUM_BYTES),
121 cols.input_commit_lt_aux
122 )
123 .map(|(bytes, aux)| {
124 CanonicitySubAir.assert_canonicity(builder, bytes, &aux, cols.is_valid.into())
125 })
126 .collect_vec();
127
128 let output_commit_rcs = izip!(
129 cols.writes.output_commit.chunks_exact(F_NUM_BYTES),
130 cols.output_commit_lt_aux
131 )
132 .map(|(bytes, aux)| {
133 CanonicitySubAir.assert_canonicity(builder, bytes, &aux, cols.is_valid.into())
134 })
135 .collect_vec();
136
137 for rc_pair in input_commit_rcs.chunks_exact(2) {
138 self.bitwise_bus
139 .send_range(rc_pair[0].clone(), rc_pair[1].clone())
140 .eval(builder, cols.is_valid);
141 }
142 for rc_pair in output_commit_rcs.chunks_exact(2) {
143 self.bitwise_bus
144 .send_range(rc_pair[0].clone(), rc_pair[1].clone())
145 .eval(builder, cols.is_valid);
146 }
147
148 for bytes in cols.writes.output_commit.chunks_exact(2) {
150 self.bitwise_bus
151 .send_range(bytes[0], bytes[1])
152 .eval(builder, cols.is_valid);
153 }
154
155 for bytes in cols.writes.output_len.chunks_exact(2) {
156 self.bitwise_bus
157 .send_range(bytes[0], bytes[1])
158 .eval(builder, cols.is_valid);
159 }
160
161 let input_f_commit = byte_commit_to_f(&cols.reads.input_commit);
163 let output_f_commit = byte_commit_to_f(&cols.writes.output_commit);
164
165 self.poseidon2_bus
166 .lookup(
167 cols.reads.old_input_acc,
168 input_f_commit,
169 cols.writes.new_input_acc,
170 AB::Expr::ONE,
171 )
172 .eval(builder, cols.is_valid);
173
174 self.poseidon2_bus
175 .lookup(
176 cols.reads.old_output_acc,
177 output_f_commit,
178 cols.writes.new_output_acc,
179 AB::Expr::ONE,
180 )
181 .eval(builder, cols.is_valid);
182
183 self.count_bus
184 .send(cols.deferral_idx)
185 .eval(builder, cols.is_valid);
186
187 AdapterAirContext {
188 to_pc: None,
189 reads: DeferralCallReads {
190 input_commit: cols.reads.input_commit.map(Into::into),
191 old_input_acc: cols.reads.old_input_acc.map(Into::into),
192 old_output_acc: cols.reads.old_output_acc.map(Into::into),
193 }
194 .into(),
195 writes: DeferralCallWrites {
196 output_commit: cols.writes.output_commit.map(Into::into),
197 output_len: cols.writes.output_len.map(Into::into),
198 new_input_acc: cols.writes.new_input_acc.map(Into::into),
199 new_output_acc: cols.writes.new_output_acc.map(Into::into),
200 }
201 .into(),
202 instruction: ImmInstruction {
203 is_valid: cols.is_valid.into(),
204 opcode: AB::Expr::from_usize(DeferralOpcode::CALL.global_opcode_usize()),
205 immediate: cols.deferral_idx.into(),
206 }
207 .into(),
208 }
209 }
210
211 fn start_offset(&self) -> usize {
212 DeferralOpcode::CLASS_OFFSET
213 }
214}
215
216pub struct DeferralCallAdapterInterface;
219
220impl<T> VmAdapterInterface<T> for DeferralCallAdapterInterface {
221 type Reads = DeferralCallReads<T, T>;
222 type Writes = DeferralCallWrites<T, T>;
223 type ProcessedInstruction = ImmInstruction<T>;
224}
225
226#[repr(C)]
227#[derive(AlignedBorrow, StructReflection)]
228pub struct DeferralCallAdapterCols<T> {
229 pub from_state: ExecutionState<T>,
230 pub rd_ptr: T,
231 pub rs_ptr: T,
232
233 pub rd_val: [T; RV32_REGISTER_NUM_LIMBS],
235 pub rs_val: [T; RV32_REGISTER_NUM_LIMBS],
236 pub rd_aux: MemoryReadAuxCols<T>,
237 pub rs_aux: MemoryReadAuxCols<T>,
238
239 pub input_commit_aux: [MemoryReadAuxCols<T>; COMMIT_MEMORY_OPS],
241 pub old_input_acc_aux: [MemoryReadAuxCols<T>; DIGEST_MEMORY_OPS],
242 pub old_output_acc_aux: [MemoryReadAuxCols<T>; DIGEST_MEMORY_OPS],
243
244 pub output_commit_and_len_aux:
246 [MemoryWriteAuxCols<T, DEFAULT_BLOCK_SIZE>; OUTPUT_TOTAL_MEMORY_OPS],
247 pub new_input_acc_aux: [MemoryWriteAuxCols<T, DEFAULT_BLOCK_SIZE>; DIGEST_MEMORY_OPS],
248 pub new_output_acc_aux: [MemoryWriteAuxCols<T, DEFAULT_BLOCK_SIZE>; DIGEST_MEMORY_OPS],
249}
250
251#[derive(Clone, Copy, Debug, derive_new::new, ColumnsAir)]
252#[columns_via(DeferralCallAdapterCols<u8>)]
253pub struct DeferralCallAdapterAir {
254 pub execution_bridge: ExecutionBridge,
255 pub memory_bridge: MemoryBridge,
256 pub bitwise_bus: BitwiseOperationLookupBus,
257 pub address_bits: usize,
258}
259
260impl<F: Field> BaseAir<F> for DeferralCallAdapterAir {
261 fn width(&self) -> usize {
262 DeferralCallAdapterCols::<F>::width()
263 }
264}
265
266impl<AB: InteractionBuilder> VmAdapterAir<AB> for DeferralCallAdapterAir {
267 type Interface = DeferralCallAdapterInterface;
268
269 fn eval(
270 &self,
271 builder: &mut AB,
272 local: &[AB::Var],
273 ctx: AdapterAirContext<AB::Expr, Self::Interface>,
274 ) {
275 let cols: &DeferralCallAdapterCols<_> = local.borrow();
276 let timestamp = cols.from_state.timestamp;
277 let mut timestamp_delta = 0usize;
278 let mut timestamp_pp = || {
279 timestamp_delta += 1;
280 timestamp + AB::F::from_usize(timestamp_delta - 1)
281 };
282
283 let d = AB::Expr::from_u32(RV32_REGISTER_AS);
286 let e = AB::Expr::from_u32(RV32_MEMORY_AS);
287
288 self.memory_bridge
290 .read(
291 MemoryAddress::new(d.clone(), cols.rd_ptr),
292 cols.rd_val,
293 timestamp_pp(),
294 &cols.rd_aux,
295 )
296 .eval(builder, ctx.instruction.is_valid.clone());
297
298 self.memory_bridge
299 .read(
300 MemoryAddress::new(d.clone(), cols.rs_ptr),
301 cols.rs_val,
302 timestamp_pp(),
303 &cols.rs_aux,
304 )
305 .eval(builder, ctx.instruction.is_valid.clone());
306
307 debug_assert!(RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS >= self.address_bits);
312 let limb_shift =
313 AB::F::from_usize(1 << (RV32_CELL_BITS * RV32_REGISTER_NUM_LIMBS - self.address_bits));
314
315 self.bitwise_bus
316 .send_range(
317 cols.rd_val[RV32_REGISTER_NUM_LIMBS - 1] * limb_shift,
318 cols.rs_val[RV32_REGISTER_NUM_LIMBS - 1] * limb_shift,
319 )
320 .eval(builder, ctx.instruction.is_valid.clone());
321
322 let input_ptr = bytes_to_f(&cols.rs_val);
326 let output_ptr = bytes_to_f(&cols.rd_val);
327
328 let deferral_idx = ctx.instruction.immediate;
329 let deferral_as = AB::Expr::from_u32(DEFERRAL_AS);
330
331 let digest_size = AB::F::from_usize(DIGEST_SIZE);
332 let input_acc_ptr = deferral_idx.clone() * AB::Expr::TWO * digest_size;
333 let output_acc_ptr = input_acc_ptr.clone() + digest_size;
334
335 let DeferralCallReads {
336 input_commit,
337 old_input_acc,
338 old_output_acc,
339 } = ctx.reads;
340 let DeferralCallWrites {
341 output_commit,
342 output_len,
343 new_input_acc,
344 new_output_acc,
345 } = ctx.writes;
346
347 self.bitwise_bus
349 .send_range(
350 output_len[RV32_REGISTER_NUM_LIMBS - 1].clone() * limb_shift,
351 AB::Expr::ZERO,
352 )
353 .eval(builder, ctx.instruction.is_valid.clone());
354
355 let output_len_full = from_fn(|i| {
356 if i < F_NUM_BYTES {
357 output_len[i].clone()
358 } else {
359 AB::Expr::ZERO
360 }
361 });
362
363 let input_commit_chunks =
364 split_memory_ops::<_, COMMIT_NUM_BYTES, COMMIT_MEMORY_OPS>(input_commit);
365 for (chunk_idx, (data, aux)) in input_commit_chunks
366 .into_iter()
367 .zip(&cols.input_commit_aux)
368 .enumerate()
369 {
370 self.memory_bridge
371 .read(
372 MemoryAddress::new(
373 e.clone(),
374 input_ptr.clone() + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
375 ),
376 data,
377 timestamp_pp(),
378 aux,
379 )
380 .eval(builder, ctx.instruction.is_valid.clone());
381 }
382
383 let old_input_acc_chunks =
384 split_memory_ops::<_, DIGEST_SIZE, DIGEST_MEMORY_OPS>(old_input_acc);
385 for (chunk_idx, (data, aux)) in old_input_acc_chunks
386 .into_iter()
387 .zip(&cols.old_input_acc_aux)
388 .enumerate()
389 {
390 self.memory_bridge
391 .read(
392 MemoryAddress::new(
393 deferral_as.clone(),
394 input_acc_ptr.clone()
395 + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
396 ),
397 data,
398 timestamp_pp(),
399 aux,
400 )
401 .eval(builder, ctx.instruction.is_valid.clone());
402 }
403
404 let old_output_acc_chunks =
405 split_memory_ops::<_, DIGEST_SIZE, DIGEST_MEMORY_OPS>(old_output_acc);
406 for (chunk_idx, (data, aux)) in old_output_acc_chunks
407 .into_iter()
408 .zip(&cols.old_output_acc_aux)
409 .enumerate()
410 {
411 self.memory_bridge
412 .read(
413 MemoryAddress::new(
414 deferral_as.clone(),
415 output_acc_ptr.clone()
416 + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
417 ),
418 data,
419 timestamp_pp(),
420 aux,
421 )
422 .eval(builder, ctx.instruction.is_valid.clone());
423 }
424
425 let output_commit_and_len = combine_output(output_commit, output_len_full);
426 let output_commit_and_len_chunks =
427 split_memory_ops::<_, OUTPUT_TOTAL_BYTES, OUTPUT_TOTAL_MEMORY_OPS>(
428 output_commit_and_len,
429 );
430 for (chunk_idx, (data, aux)) in output_commit_and_len_chunks
431 .into_iter()
432 .zip(&cols.output_commit_and_len_aux)
433 .enumerate()
434 {
435 self.memory_bridge
436 .write(
437 MemoryAddress::new(
438 e.clone(),
439 output_ptr.clone() + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
440 ),
441 data,
442 timestamp_pp(),
443 aux,
444 )
445 .eval(builder, ctx.instruction.is_valid.clone());
446 }
447
448 let new_input_acc_chunks =
449 split_memory_ops::<_, DIGEST_SIZE, DIGEST_MEMORY_OPS>(new_input_acc);
450 for (chunk_idx, (data, aux)) in new_input_acc_chunks
451 .into_iter()
452 .zip(&cols.new_input_acc_aux)
453 .enumerate()
454 {
455 self.memory_bridge
456 .write(
457 MemoryAddress::new(
458 deferral_as.clone(),
459 input_acc_ptr.clone()
460 + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
461 ),
462 data,
463 timestamp_pp(),
464 aux,
465 )
466 .eval(builder, ctx.instruction.is_valid.clone());
467 }
468
469 let new_output_acc_chunks =
470 split_memory_ops::<_, DIGEST_SIZE, DIGEST_MEMORY_OPS>(new_output_acc);
471 for (chunk_idx, (data, aux)) in new_output_acc_chunks
472 .into_iter()
473 .zip(&cols.new_output_acc_aux)
474 .enumerate()
475 {
476 self.memory_bridge
477 .write(
478 MemoryAddress::new(
479 deferral_as.clone(),
480 output_acc_ptr.clone()
481 + AB::Expr::from_usize(chunk_idx * DEFAULT_BLOCK_SIZE),
482 ),
483 data,
484 timestamp_pp(),
485 aux,
486 )
487 .eval(builder, ctx.instruction.is_valid.clone());
488 }
489
490 self.execution_bridge
491 .execute_and_increment_or_set_pc(
492 ctx.instruction.opcode,
493 [
494 cols.rd_ptr.into(),
495 cols.rs_ptr.into(),
496 deferral_idx,
497 d.clone(),
498 e.clone(),
499 ],
500 cols.from_state,
501 AB::Expr::from_usize(timestamp_delta),
502 (DEFAULT_PC_STEP, ctx.to_pc),
503 )
504 .eval(builder, ctx.instruction.is_valid);
505 }
506
507 fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
508 let cols: &DeferralCallAdapterCols<_> = local.borrow();
509 cols.from_state.pc
510 }
511}