1use std::{
2 marker::PhantomData,
3 mem::{align_of, size_of},
4 sync::Arc,
5};
6
7use itertools::Itertools;
8use num_bigint::BigUint;
9use num_traits::Zero;
10use openvm_circuit::{
11 arch::*,
12 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
13};
14use openvm_circuit_primitives::{
15 var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerChip},
16 ColumnsAir, SubAir, TraceSubRowGenerator,
17};
18use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP};
19use openvm_stark_backend::{
20 interaction::InteractionBuilder,
21 p3_air::BaseAir,
22 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
23 BaseAirWithPublicValues,
24};
25use openvm_stark_sdk::p3_baby_bear::BabyBear;
26
27use crate::builder::{FieldExpr, FieldExprCols};
28
29#[derive(Clone)]
30pub struct FieldExpressionCoreAir {
31 pub expr: FieldExpr,
32
33 pub offset: usize,
35
36 pub local_opcode_idx: Vec<usize>,
39 pub opcode_flag_idx: Vec<usize>,
42 }
53
54impl ColumnsAir for FieldExpressionCoreAir {}
56
57impl FieldExpressionCoreAir {
58 pub fn new(
59 expr: FieldExpr,
60 offset: usize,
61 local_opcode_idx: Vec<usize>,
62 opcode_flag_idx: Vec<usize>,
63 ) -> Self {
64 let opcode_flag_idx = if opcode_flag_idx.is_empty() && expr.needs_setup() {
65 vec![0]
67 } else {
68 opcode_flag_idx
70 };
71 assert_eq!(opcode_flag_idx.len(), local_opcode_idx.len() - 1);
72 Self {
73 expr,
74 offset,
75 local_opcode_idx,
76 opcode_flag_idx,
77 }
78 }
79
80 pub fn num_inputs(&self) -> usize {
81 self.expr.builder.num_input
82 }
83
84 pub fn num_vars(&self) -> usize {
85 self.expr.builder.num_variables
86 }
87
88 pub fn num_flags(&self) -> usize {
89 self.expr.builder.num_flags
90 }
91
92 pub fn output_indices(&self) -> &[usize] {
93 &self.expr.builder.output_indices
94 }
95}
96
97impl<F: Field> BaseAir<F> for FieldExpressionCoreAir {
98 fn width(&self) -> usize {
99 BaseAir::<F>::width(&self.expr)
100 }
101}
102
103impl<F: Field> BaseAirWithPublicValues<F> for FieldExpressionCoreAir {}
104
105impl<AB: InteractionBuilder, I> VmCoreAir<AB, I> for FieldExpressionCoreAir
106where
107 I: VmAdapterInterface<AB::Expr>,
108 AdapterAirContext<AB::Expr, I>:
109 From<AdapterAirContext<AB::Expr, DynAdapterInterface<AB::Expr>>>,
110{
111 fn eval(
112 &self,
113 builder: &mut AB,
114 local: &[AB::Var],
115 _from_pc: AB::Var,
116 ) -> AdapterAirContext<AB::Expr, I> {
117 assert_eq!(local.len(), BaseAir::<AB::F>::width(&self.expr));
118 self.expr.eval(builder, local);
119 let FieldExprCols {
120 is_valid,
121 inputs,
122 vars,
123 flags,
124 ..
125 } = self.expr.load_vars(local);
126 assert_eq!(inputs.len(), self.num_inputs());
127 assert_eq!(vars.len(), self.num_vars());
128 assert_eq!(flags.len(), self.num_flags());
129 let reads: Vec<AB::Expr> = inputs.concat().iter().map(|x| (*x).into()).collect();
130 let writes: Vec<AB::Expr> = self
131 .output_indices()
132 .iter()
133 .flat_map(|&i| vars[i].clone())
134 .map(Into::into)
135 .collect();
136
137 let opcode_flags_except_last = self.opcode_flag_idx.iter().map(|&i| flags[i]).collect_vec();
138 let last_opcode_flag = is_valid
139 - opcode_flags_except_last
140 .iter()
141 .map(|&v| v.into())
142 .sum::<AB::Expr>();
143 builder.assert_bool(last_opcode_flag.clone());
144 let opcode_flags = opcode_flags_except_last
145 .into_iter()
146 .map(Into::into)
147 .chain(Some(last_opcode_flag));
148 let expected_opcode = opcode_flags
149 .zip(self.local_opcode_idx.iter().map(|&i| i + self.offset))
150 .map(|(flag, global_idx)| flag * AB::Expr::from_usize(global_idx))
151 .sum();
152
153 let instruction = MinimalInstruction {
154 is_valid: is_valid.into(),
155 opcode: expected_opcode,
156 };
157
158 let ctx: AdapterAirContext<_, DynAdapterInterface<_>> = AdapterAirContext {
159 to_pc: None,
160 reads: reads.into(),
161 writes: writes.into(),
162 instruction: instruction.into(),
163 };
164 ctx.into()
165 }
166
167 fn start_offset(&self) -> usize {
168 self.offset
169 }
170}
171
172pub struct FieldExpressionMetadata<F, A> {
173 pub total_input_limbs: usize, _phantom: PhantomData<(F, A)>,
175}
176
177impl<F, A> Clone for FieldExpressionMetadata<F, A> {
178 fn clone(&self) -> Self {
179 Self {
180 total_input_limbs: self.total_input_limbs,
181 _phantom: PhantomData,
182 }
183 }
184}
185
186impl<F, A> Default for FieldExpressionMetadata<F, A> {
187 fn default() -> Self {
188 Self {
189 total_input_limbs: 0,
190 _phantom: PhantomData,
191 }
192 }
193}
194
195impl<F, A> FieldExpressionMetadata<F, A> {
196 pub fn new(total_input_limbs: usize) -> Self {
197 Self {
198 total_input_limbs,
199 _phantom: PhantomData,
200 }
201 }
202}
203
204impl<F, A> AdapterCoreMetadata for FieldExpressionMetadata<F, A>
205where
206 A: AdapterTraceExecutor<F>,
207{
208 #[inline(always)]
209 fn get_adapter_width() -> usize {
210 A::WIDTH * size_of::<F>()
211 }
212}
213
214pub type FieldExpressionRecordLayout<F, A> = AdapterCoreLayout<FieldExpressionMetadata<F, A>>;
215
216pub struct FieldExpressionCoreRecordMut<'a> {
217 pub opcode: &'a mut u8,
218 pub input_limbs: &'a mut [u8],
219}
220
221impl<'a, F, A> CustomBorrow<'a, FieldExpressionCoreRecordMut<'a>, FieldExpressionRecordLayout<F, A>>
222 for [u8]
223{
224 fn custom_borrow(
225 &'a mut self,
226 layout: FieldExpressionRecordLayout<F, A>,
227 ) -> FieldExpressionCoreRecordMut<'a> {
228 let (opcode_buf, input_limbs_buff) = unsafe { self.split_at_mut_unchecked(1) };
230
231 let opcode_buf = unsafe { opcode_buf.get_unchecked_mut(0) };
233
234 FieldExpressionCoreRecordMut {
235 opcode: opcode_buf,
236 input_limbs: &mut input_limbs_buff[..layout.metadata.total_input_limbs],
237 }
238 }
239
240 unsafe fn extract_layout(&self) -> FieldExpressionRecordLayout<F, A> {
241 panic!("Should get the Layout information from FieldExpressionExecutor");
242 }
243}
244
245impl<F, A> SizedRecord<FieldExpressionRecordLayout<F, A>> for FieldExpressionCoreRecordMut<'_> {
246 fn size(layout: &FieldExpressionRecordLayout<F, A>) -> usize {
247 layout.metadata.total_input_limbs + 1
248 }
249
250 fn alignment(_layout: &FieldExpressionRecordLayout<F, A>) -> usize {
251 align_of::<u8>()
252 }
253}
254
255impl<'a> FieldExpressionCoreRecordMut<'a> {
256 pub fn new_from_execution_data(
258 buffer: &'a mut [u8],
259 inputs: &[BigUint],
260 limbs_per_input: usize,
261 ) -> Self {
262 let record_info = FieldExpressionMetadata::<(), ()>::new(inputs.len() * limbs_per_input);
263
264 let record: Self = buffer.custom_borrow(FieldExpressionRecordLayout {
265 metadata: record_info,
266 });
267 record
268 }
269
270 #[inline(always)]
271 pub fn fill_from_execution_data(&mut self, opcode: u8, data: &[u8]) {
272 *self.opcode = opcode;
275 self.input_limbs.copy_from_slice(data);
276 }
277}
278
279#[derive(Clone)]
280pub struct FieldExpressionExecutor<A> {
281 adapter: A,
282 pub expr: FieldExpr,
283 pub offset: usize,
284 pub local_opcode_idx: Vec<usize>,
285 pub opcode_flag_idx: Vec<usize>,
286 pub name: String,
287}
288
289impl<A> FieldExpressionExecutor<A> {
290 #[allow(clippy::too_many_arguments)]
291 pub fn new(
292 adapter: A,
293 expr: FieldExpr,
294 offset: usize,
295 local_opcode_idx: Vec<usize>,
296 opcode_flag_idx: Vec<usize>,
297 name: &str,
298 ) -> Self {
299 let opcode_flag_idx = if opcode_flag_idx.is_empty() && expr.needs_setup() {
300 vec![0]
302 } else {
303 opcode_flag_idx
305 };
306 assert_eq!(opcode_flag_idx.len(), local_opcode_idx.len() - 1);
307 tracing::debug!(
308 "FieldExpressionCoreExecutor: opcode={name}, main_width={}",
309 BaseAir::<BabyBear>::width(&expr)
310 );
311 Self {
312 adapter,
313 expr,
314 offset,
315 local_opcode_idx,
316 opcode_flag_idx,
317 name: name.to_string(),
318 }
319 }
320
321 pub fn get_record_layout<F>(&self) -> FieldExpressionRecordLayout<F, A> {
322 FieldExpressionRecordLayout {
323 metadata: FieldExpressionMetadata::new(
324 self.expr.builder.num_input * self.expr.canonical_num_limbs(),
325 ),
326 }
327 }
328
329 #[inline]
331 pub fn adapter(&self) -> &A {
332 &self.adapter
333 }
334}
335
336pub struct FieldExpressionFiller<A> {
337 adapter: A,
338 pub expr: FieldExpr,
339 pub local_opcode_idx: Vec<usize>,
340 pub opcode_flag_idx: Vec<usize>,
341 pub range_checker: SharedVariableRangeCheckerChip,
342 pub should_finalize: bool,
343}
344
345impl<A> FieldExpressionFiller<A> {
346 #[allow(clippy::too_many_arguments)]
347 pub fn new(
348 adapter: A,
349 expr: FieldExpr,
350 local_opcode_idx: Vec<usize>,
351 opcode_flag_idx: Vec<usize>,
352 range_checker: SharedVariableRangeCheckerChip,
353 should_finalize: bool,
354 ) -> Self {
355 let opcode_flag_idx = if opcode_flag_idx.is_empty() && expr.needs_setup() {
356 vec![0]
358 } else {
359 opcode_flag_idx
361 };
362 assert_eq!(opcode_flag_idx.len(), local_opcode_idx.len() - 1);
363 Self {
364 adapter,
365 expr,
366 local_opcode_idx,
367 opcode_flag_idx,
368 range_checker,
369 should_finalize,
370 }
371 }
372 pub fn num_inputs(&self) -> usize {
373 self.expr.builder.num_input
374 }
375
376 pub fn num_flags(&self) -> usize {
377 self.expr.builder.num_flags
378 }
379
380 pub fn get_record_layout<F>(&self) -> FieldExpressionRecordLayout<F, A> {
381 FieldExpressionRecordLayout {
382 metadata: FieldExpressionMetadata::new(
383 self.num_inputs() * self.expr.canonical_num_limbs(),
384 ),
385 }
386 }
387}
388
389impl<F, A, RA> PreflightExecutor<F, RA> for FieldExpressionExecutor<A>
390where
391 F: PrimeField32,
392 A: 'static
393 + AdapterTraceExecutor<F, ReadData: Into<DynArray<u8>>, WriteData: From<DynArray<u8>>>,
394 for<'buf> RA: RecordArena<
395 'buf,
396 FieldExpressionRecordLayout<F, A>,
397 (A::RecordMut<'buf>, FieldExpressionCoreRecordMut<'buf>),
398 >,
399{
400 fn execute(
401 &self,
402 state: VmStateMut<F, TracingMemory, RA>,
403 instruction: &Instruction<F>,
404 ) -> Result<(), ExecutionError> {
405 let (mut adapter_record, mut core_record) = state.ctx.alloc(self.get_record_layout());
406
407 A::start(*state.pc, state.memory, &mut adapter_record);
408
409 let data: DynArray<_> = self
410 .adapter
411 .read(state.memory, instruction, &mut adapter_record)
412 .into();
413
414 core_record.fill_from_execution_data(
415 instruction.opcode.local_opcode_idx(self.offset) as u8,
416 &data.0,
417 );
418
419 let (writes, _, _) = run_field_expression(
420 &self.expr,
421 &self.local_opcode_idx,
422 &self.opcode_flag_idx,
423 core_record.input_limbs,
424 *core_record.opcode as usize,
425 );
426
427 self.adapter.write(
428 state.memory,
429 instruction,
430 writes.into(),
431 &mut adapter_record,
432 );
433
434 *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
435 Ok(())
436 }
437
438 fn get_opcode_name(&self, _opcode: usize) -> String {
439 self.name.clone()
440 }
441}
442
443#[cfg(feature = "aot")]
444impl<F: PrimeField32, A> AotExecutor<F> for FieldExpressionExecutor<A> {}
445
446impl<F, A> TraceFiller<F> for FieldExpressionFiller<A>
447where
448 F: PrimeField32 + Send + Sync + Clone,
449 A: 'static + AdapterTraceFiller<F>,
450{
451 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
452 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
455
456 self.adapter.fill_trace_row(mem_helper, adapter_row);
457
458 let record: FieldExpressionCoreRecordMut =
464 unsafe { get_record_from_slice(&mut core_row, self.get_record_layout::<F>()) };
465
466 let (_, inputs, flags) = run_field_expression(
467 &self.expr,
468 &self.local_opcode_idx,
469 &self.opcode_flag_idx,
470 record.input_limbs,
471 *record.opcode as usize,
472 );
473
474 let range_checker = self.range_checker.as_ref();
475 self.expr
476 .generate_subrow((range_checker, inputs, flags), core_row);
477 }
478
479 fn fill_dummy_trace_row(&self, row_slice: &mut [F]) {
480 if !self.should_finalize {
481 return;
482 }
483
484 let inputs: Vec<BigUint> = vec![BigUint::zero(); self.num_inputs()];
485 let flags: Vec<bool> = vec![false; self.num_flags()];
486 let core_row = &mut row_slice[A::WIDTH..];
487 let tmp_range_checker = Arc::new(VariableRangeCheckerChip::new(self.range_checker.bus()));
490 self.expr
491 .generate_subrow((&tmp_range_checker, inputs, flags), core_row);
492 core_row[0] = F::ZERO; }
494}
495
496fn run_field_expression(
497 expr: &FieldExpr,
498 local_opcode_flags: &[usize],
499 opcode_flag_idx: &[usize],
500 data: &[u8],
501 local_opcode_idx: usize,
502) -> (DynArray<u8>, Vec<BigUint>, Vec<bool>) {
503 let field_element_limbs = expr.canonical_num_limbs();
504 assert_eq!(data.len(), expr.builder.num_input * field_element_limbs);
505
506 let mut inputs = Vec::with_capacity(expr.builder.num_input);
507 for i in 0..expr.builder.num_input {
508 let start = i * field_element_limbs;
509 let end = start + field_element_limbs;
510 let limb_slice = &data[start..end];
511 let input = BigUint::from_bytes_le(limb_slice);
512 inputs.push(input);
513 }
514
515 let mut flags = vec![];
516 if expr.needs_setup() {
517 flags = vec![false; expr.builder.num_flags];
518
519 if let Some(opcode_position) = local_opcode_flags
521 .iter()
522 .position(|&idx| idx == local_opcode_idx)
523 {
524 if opcode_position < opcode_flag_idx.len() {
526 let flag_idx = opcode_flag_idx[opcode_position];
527 flags[flag_idx] = true;
528 }
529 }
532 }
533
534 let vars = expr.execute(&inputs, &flags);
535 assert_eq!(vars.len(), expr.builder.num_variables);
536
537 let num_outputs = expr.builder.output_indices.len();
539 let total_output_bytes = num_outputs * field_element_limbs;
540 let mut write_buffer = vec![0u8; total_output_bytes];
541 for (i, &var_idx) in expr.builder.output_indices.iter().enumerate() {
542 let start = i * field_element_limbs;
543 let bytes = vars[var_idx].to_bytes_le();
544 let copy_len = bytes.len().min(field_element_limbs);
545 write_buffer[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
546 }
548 let writes: DynArray<_> = write_buffer.into();
549
550 (writes, inputs, flags)
551}
552
553#[inline(always)]
554pub fn run_field_expression_precomputed<const NEEDS_SETUP: bool>(
555 expr: &FieldExpr,
556 flag_idx: usize,
557 data: &[u8],
558) -> DynArray<u8> {
559 let field_element_limbs = expr.canonical_num_limbs();
560 assert_eq!(data.len(), expr.num_inputs() * field_element_limbs);
561
562 let mut inputs = Vec::with_capacity(expr.num_inputs());
563 for i in 0..expr.num_inputs() {
564 let start = i * expr.canonical_num_limbs();
565 let end = start + expr.canonical_num_limbs();
566 let limb_slice = &data[start..end];
567 let input = BigUint::from_bytes_le(limb_slice);
568 inputs.push(input);
569 }
570
571 let flags = if NEEDS_SETUP {
572 let mut flags = vec![false; expr.num_flags()];
573 if flag_idx < expr.num_flags() {
574 flags[flag_idx] = true;
575 }
576 flags
577 } else {
578 vec![]
579 };
580
581 let vars = expr.execute(&inputs, &flags);
582 assert_eq!(vars.len(), expr.num_vars());
583
584 let num_outputs = expr.output_indices().len();
586 let total_output_bytes = num_outputs * field_element_limbs;
587 let mut write_buffer = vec![0u8; total_output_bytes];
588 for (i, &var_idx) in expr.output_indices().iter().enumerate() {
589 let start = i * field_element_limbs;
590 let bytes = vars[var_idx].to_bytes_le();
591 let copy_len = bytes.len().min(field_element_limbs);
592 write_buffer[start..start + copy_len].copy_from_slice(&bytes[..copy_len]);
593 }
594 write_buffer.into()
595}