openvm_native_circuit/fri/
mod.rs

1use core::ops::Deref;
2use std::{
3    borrow::{Borrow, BorrowMut},
4    mem::offset_of,
5    sync::{Arc, Mutex},
6};
7
8use itertools::{zip_eq, Itertools};
9use openvm_circuit::{
10    arch::{
11        ExecutionBridge, ExecutionBus, ExecutionError, ExecutionState, InstructionExecutor, Streams,
12    },
13    system::{
14        memory::{
15            offline_checker::{MemoryBridge, MemoryReadAuxCols, MemoryWriteAuxCols},
16            MemoryAddress, MemoryAuxColsFactory, MemoryController, OfflineMemory, RecordId,
17        },
18        program::ProgramBus,
19    },
20};
21use openvm_circuit_primitives::utils::next_power_of_two_or_zero;
22use openvm_circuit_primitives_derive::AlignedBorrow;
23use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP, LocalOpcode};
24use openvm_native_compiler::{conversion::AS, FriOpcode::FRI_REDUCED_OPENING};
25use openvm_stark_backend::{
26    config::{StarkGenericConfig, Val},
27    interaction::InteractionBuilder,
28    p3_air::{Air, AirBuilder, BaseAir},
29    p3_field::{Field, FieldAlgebra, PrimeField32},
30    p3_matrix::{dense::RowMajorMatrix, Matrix},
31    p3_maybe_rayon::prelude::*,
32    prover::types::AirProofInput,
33    rap::{BaseAirWithPublicValues, PartitionedBaseAir},
34    AirRef, Chip, ChipUsageGetter,
35};
36use serde::{Deserialize, Serialize};
37use static_assertions::const_assert_eq;
38
39use crate::{
40    field_extension::{FieldExtension, EXT_DEG},
41    utils::const_max,
42};
43
44#[cfg(test)]
45mod tests;
46
47#[repr(C)]
48#[derive(Debug, AlignedBorrow)]
49struct WorkloadCols<T> {
50    prefix: PrefixCols<T>,
51
52    a_aux: MemoryWriteAuxCols<T, 1>,
53    /// The value of `b` read.
54    b: [T; EXT_DEG],
55    b_aux: MemoryReadAuxCols<T>,
56}
57const WL_WIDTH: usize = WorkloadCols::<u8>::width();
58const_assert_eq!(WL_WIDTH, 27);
59
60#[repr(C)]
61#[derive(Debug, AlignedBorrow)]
62struct Instruction1Cols<T> {
63    prefix: PrefixCols<T>,
64
65    pc: T,
66
67    a_ptr_ptr: T,
68    a_ptr_aux: MemoryReadAuxCols<T>,
69
70    b_ptr_ptr: T,
71    b_ptr_aux: MemoryReadAuxCols<T>,
72
73    /// Extraneous column that is constrained to write_a * a_or_is_first but has no meaningful
74    /// effect. It can be removed along with its constraints without impacting correctness.
75    write_a_x_is_first: T,
76}
77const INS_1_WIDTH: usize = Instruction1Cols::<u8>::width();
78const_assert_eq!(INS_1_WIDTH, 26);
79const_assert_eq!(
80    offset_of!(WorkloadCols<u8>, prefix),
81    offset_of!(Instruction1Cols<u8>, prefix)
82);
83
84#[repr(C)]
85#[derive(Debug, AlignedBorrow)]
86struct Instruction2Cols<T> {
87    general: GeneralCols<T>,
88    /// Shared with `a_or_is_first` in other column types. Must be 0 for Instruction2Cols.
89    is_first: T,
90
91    length_ptr: T,
92    length_aux: MemoryReadAuxCols<T>,
93
94    alpha_ptr: T,
95    alpha_aux: MemoryReadAuxCols<T>,
96
97    result_ptr: T,
98    result_aux: MemoryWriteAuxCols<T, EXT_DEG>,
99
100    hint_id_ptr: T,
101
102    is_init_ptr: T,
103    is_init_aux: MemoryReadAuxCols<T>,
104
105    /// Extraneous column that is constrained to write_a * a_or_is_first but has no meaningful
106    /// effect. It can be removed along with its constraints without impacting correctness.
107    write_a_x_is_first: T,
108}
109const INS_2_WIDTH: usize = Instruction2Cols::<u8>::width();
110const_assert_eq!(INS_2_WIDTH, 26);
111const_assert_eq!(
112    offset_of!(WorkloadCols<u8>, prefix) + offset_of!(PrefixCols<u8>, general),
113    offset_of!(Instruction2Cols<u8>, general)
114);
115const_assert_eq!(
116    offset_of!(Instruction1Cols<u8>, prefix) + offset_of!(PrefixCols<u8>, a_or_is_first),
117    offset_of!(Instruction2Cols<u8>, is_first)
118);
119const_assert_eq!(
120    offset_of!(Instruction1Cols<u8>, write_a_x_is_first),
121    offset_of!(Instruction2Cols<u8>, write_a_x_is_first)
122);
123
124pub const OVERALL_WIDTH: usize = const_max(const_max(WL_WIDTH, INS_1_WIDTH), INS_2_WIDTH);
125const_assert_eq!(OVERALL_WIDTH, 27);
126
127/// Every row starts with these columns.
128#[repr(C)]
129#[derive(Debug, AlignedBorrow)]
130struct GeneralCols<T> {
131    /// Whether the row is a workload row.
132    is_workload_row: T,
133    /// Whether the row is an instruction row.
134    is_ins_row: T,
135    /// For Instruction1 rows, the initial timestamp of the FRI_REDUCED_OPENING instruction.
136    /// For Workload rows, the final timestamp after processing the next elements minus `INSTRUCTION_READS`.
137    /// For Instruction2 rows, unused.
138    timestamp: T,
139}
140const GENERAL_WIDTH: usize = GeneralCols::<u8>::width();
141const_assert_eq!(GENERAL_WIDTH, 3);
142
143#[repr(C)]
144#[derive(Debug, AlignedBorrow)]
145struct DataCols<T> {
146    /// For Instruction1 rows, `mem[a_ptr_ptr]`.
147    /// For Workload rows, the pointer in a-values after increment.
148    a_ptr: T,
149    /// Indicates whether to write a-value or read it.
150    /// For Instruction1 rows, `1 - mem[is_init_ptr]`.
151    /// For Workload rows, whether we are writing the a-value or reading it; fixed for entire
152    /// workload/instruction block.
153    write_a: T,
154    /// For Instruction1 rows, `mem[b_ptr_ptr]`.
155    /// For Workload rows, the pointer in b-values after increment.
156    b_ptr: T,
157    /// For Instruction1 rows, the value read from `mem[length_ptr]`.
158    /// For Workload rows, the workload row index from the top. *Not* the index into a-values and
159    /// b-values. (Note: idx increases within a workload/instruction block, while timestamp, a_ptr,
160    /// and b_ptr decrease.)
161    idx: T,
162    /// For both Instruction1 and Workload rows, equal to sum_{k=0}^{idx} alpha^{len-i} (b_i - a_i).
163    /// Instruction1 rows constrain this to be the result written to `mem[result_ptr]`.
164    result: [T; EXT_DEG],
165    /// The alpha to use in this instruction. Fixed across workload rows; Instruction1 rows read
166    /// this from `mem[alpha_ptr]`.
167    alpha: [T; EXT_DEG],
168}
169#[allow(dead_code)]
170const DATA_WIDTH: usize = DataCols::<u8>::width();
171const_assert_eq!(DATA_WIDTH, 12);
172
173/// Prefix of `WorkloadCols` and `Instruction1Cols`
174#[repr(C)]
175#[derive(Debug, AlignedBorrow)]
176struct PrefixCols<T> {
177    general: GeneralCols<T>,
178    /// WorkloadCols uses this column as the value of `a` read. Instruction1Cols uses this column as
179    /// the `is_first` flag must be set to one. Shared with Instruction2Cols `is_first`.
180    a_or_is_first: T,
181    data: DataCols<T>,
182}
183const PREFIX_WIDTH: usize = PrefixCols::<u8>::width();
184const_assert_eq!(PREFIX_WIDTH, 16);
185
186const INSTRUCTION_READS: usize = 5;
187
188/// A valid trace is a sequence of blocks, where each block has the following consecutive rows:
189/// 1. **Workload Columns**: A sequence of rows used to compute the "rolling hash" of b - a.
190/// 2. **Instruction1**: The "local" row for the instruction window.
191/// 3. **Instruction2**: The "next" row for the instruction window.
192///
193/// The row mode is determined by the following flags:
194/// * `GeneralCols.is_workload_row`: Indicator for a Workload row.
195/// * `GeneralCols.is_ins_row`: Indicator for an Instruction1 or Instruction2 row.
196/// * `PrefixCols.a_or_is_first` / `Instruction2Cols.is_first`: For Instruction1 or Instruction2
197///   rows, indicator for Instruction1 rows.
198///
199/// We impose the following flag constraints:
200/// * (F1): Every row is either a Workload row, an Instruction row, or Disabled.
201///
202/// A trace may also end in one or more Disabled rows, which emit no interactions and for which
203/// the all-zeroes row is valid.
204///
205/// The AIR enforces the following transitions, which define the block structure outlined above:
206/// * (T1): The trace must start with a Workload row or a Disabled row.
207/// * (T2): A Disabled row can only be followed by a Disabled row (except on last).
208/// * (T3): A Workload row cannot be followed by a Disabled row.
209/// * (T4): A non-Instruction must not be followed by an Instruction2 row.
210/// * (T5): An Instruction1 row must be followed by an Instruction2 row.
211/// * (T6): An Instruction2 row can only be followed by a Workload or Disabled row.
212/// * (T7): The last row is either a Disabled or an Instruction2 row.
213///
214/// Note that (T2) + (T3) + (T4) together imply that a Workload row can only be followed by an
215/// Instruction1 row, as desired.
216///
217/// Note that these transition constraints do allow for a somewhat degenerate trace consisting of
218/// only disabled rows. If the trace does not have this degenerate form, then the constraints ensure
219/// it starts with a Workload row (T1) and ends with either a Disabled or Instruction2 row (T7).
220/// The other transition constraints then ensure the proper state transitions from Workload to
221/// Instruction2.
222#[derive(Copy, Clone, Debug)]
223struct FriReducedOpeningAir {
224    execution_bridge: ExecutionBridge,
225    memory_bridge: MemoryBridge,
226}
227
228impl<F: Field> BaseAir<F> for FriReducedOpeningAir {
229    fn width(&self) -> usize {
230        OVERALL_WIDTH
231    }
232}
233
234impl<F: Field> BaseAirWithPublicValues<F> for FriReducedOpeningAir {}
235impl<F: Field> PartitionedBaseAir<F> for FriReducedOpeningAir {}
236impl<AB: InteractionBuilder> Air<AB> for FriReducedOpeningAir {
237    fn eval(&self, builder: &mut AB) {
238        let main = builder.main();
239        let local = main.row_slice(0);
240        let next = main.row_slice(1);
241        let local_slice = local.deref();
242        let next_slice = next.deref();
243        self.eval_general(builder, local_slice, next_slice);
244        self.eval_workload_row(builder, local_slice, next_slice);
245        self.eval_instruction1_row(builder, local_slice, next_slice);
246        self.eval_instruction2_row(builder, local_slice, next_slice);
247    }
248}
249
250impl FriReducedOpeningAir {
251    fn eval_general<AB: InteractionBuilder>(
252        &self,
253        builder: &mut AB,
254        local_slice: &[AB::Var],
255        next_slice: &[AB::Var],
256    ) {
257        let local: &GeneralCols<AB::Var> = local_slice[..GENERAL_WIDTH].borrow();
258        let next: &GeneralCols<AB::Var> = next_slice[..GENERAL_WIDTH].borrow();
259        // (F1): Every row is either a Workload row, an Instruction row, or Disabled.
260        {
261            builder.assert_bool(local.is_ins_row);
262            builder.assert_bool(local.is_workload_row);
263            builder.assert_bool(local.is_ins_row + local.is_workload_row);
264        }
265        //  (T2): A Disabled row can only be followed by a Disabled row (except on last).
266        {
267            let mut when_transition = builder.when_transition();
268            let mut when_disabled =
269                when_transition.when_ne(local.is_ins_row + local.is_workload_row, AB::Expr::ONE);
270            when_disabled.assert_zero(next.is_ins_row + next.is_workload_row);
271        }
272    }
273
274    fn eval_workload_row<AB: InteractionBuilder>(
275        &self,
276        builder: &mut AB,
277        local_slice: &[AB::Var],
278        next_slice: &[AB::Var],
279    ) {
280        let local: &WorkloadCols<AB::Var> = local_slice[..WL_WIDTH].borrow();
281        let next: &PrefixCols<AB::Var> = next_slice[..PREFIX_WIDTH].borrow();
282        let local_data = &local.prefix.data;
283        let start_timestamp = next.general.timestamp;
284        let multiplicity = local.prefix.general.is_workload_row;
285        // a_ptr/b_ptr/length/result
286        let ptr_reads = AB::F::from_canonical_usize(INSTRUCTION_READS);
287        let native_as = AB::Expr::from_canonical_u32(AS::Native as u32);
288        // write_a itself could be anything on non-workload row, but on workload row, it must be boolean.
289        // write_a on last workflow row will be constrained to equal write_a on instruction1 row, implying the latter is boolean.
290        builder.when(multiplicity).assert_bool(local_data.write_a);
291        // read a when write_a is 0
292        self.memory_bridge
293            .read(
294                MemoryAddress::new(native_as.clone(), next.data.a_ptr),
295                [local.prefix.a_or_is_first],
296                start_timestamp + ptr_reads,
297                local.a_aux.as_ref(),
298            )
299            .eval(builder, (AB::Expr::ONE - local_data.write_a) * multiplicity);
300        // write a when write_a is 1
301        self.memory_bridge
302            .write(
303                MemoryAddress::new(native_as.clone(), next.data.a_ptr),
304                [local.prefix.a_or_is_first],
305                start_timestamp + ptr_reads,
306                &local.a_aux,
307            )
308            .eval(builder, local_data.write_a * multiplicity);
309        // read b
310        self.memory_bridge
311            .read(
312                MemoryAddress::new(native_as.clone(), next.data.b_ptr),
313                local.b,
314                start_timestamp + ptr_reads + AB::Expr::ONE,
315                &local.b_aux,
316            )
317            .eval(builder, multiplicity);
318        {
319            let mut when_transition = builder.when_transition();
320            let mut builder = when_transition.when(local.prefix.general.is_workload_row);
321            // ATTENTION: degree of builder is 2
322            // local.timestamp = next.timestamp + 2
323            builder.assert_eq(
324                local.prefix.general.timestamp,
325                start_timestamp + AB::Expr::TWO,
326            );
327            // local.idx = next.idx + 1
328            builder.assert_eq(local_data.idx + AB::Expr::ONE, next.data.idx);
329            // local.alpha = next.alpha
330            assert_array_eq(&mut builder, local_data.alpha, next.data.alpha);
331            // local.a_ptr = next.a_ptr + 1
332            builder.assert_eq(local_data.a_ptr, next.data.a_ptr + AB::F::ONE);
333            // local.write_a = next.write_a
334            builder.assert_eq(local_data.write_a, next.data.write_a);
335            // local.b_ptr = next.b_ptr + EXT_DEG
336            builder.assert_eq(
337                local_data.b_ptr,
338                next.data.b_ptr + AB::F::from_canonical_usize(EXT_DEG),
339            );
340            // local.timestamp = next.timestamp + 2
341            builder.assert_eq(
342                local.prefix.general.timestamp,
343                next.general.timestamp + AB::Expr::TWO,
344            );
345            // local.result * local.alpha + local.b - local.a = next.result
346            let mut expected_result = FieldExtension::multiply(local_data.result, local_data.alpha);
347            expected_result
348                .iter_mut()
349                .zip(local.b.iter())
350                .for_each(|(e, b)| {
351                    *e += (*b).into();
352                });
353            expected_result[0] -= local.prefix.a_or_is_first.into();
354            assert_array_eq(&mut builder, expected_result, next.data.result);
355        }
356        {
357            let mut next_ins = builder.when(next.general.is_ins_row);
358            let mut local_non_ins =
359                next_ins.when_ne(local.prefix.general.is_ins_row, AB::Expr::ONE);
360            // (T4): A non-Instruction must not be followed by an Instruction2 row.
361            local_non_ins.assert_one(next.a_or_is_first);
362
363            // (T3): A Workload row cannot be followed by a Disabled row.
364            builder
365                .when(local.prefix.general.is_workload_row)
366                .assert_one(next.general.is_ins_row + next.general.is_workload_row);
367        }
368        {
369            let mut when_first_row = builder.when_first_row();
370            let mut when_enabled = when_first_row
371                .when(local.prefix.general.is_ins_row + local.prefix.general.is_workload_row);
372            // (T1): The trace must start with a Workload row or a Disabled row.
373            when_enabled.assert_one(local.prefix.general.is_workload_row);
374            // Workload rows must start with the first element.
375            when_enabled.assert_zero(local.prefix.data.idx);
376            // local.result is all 0s.
377            assert_array_eq(
378                &mut when_enabled,
379                local.prefix.data.result,
380                [AB::Expr::ZERO; EXT_DEG],
381            );
382        }
383    }
384
385    fn eval_instruction1_row<AB: InteractionBuilder>(
386        &self,
387        builder: &mut AB,
388        local_slice: &[AB::Var],
389        next_slice: &[AB::Var],
390    ) {
391        let local: &Instruction1Cols<AB::Var> = local_slice[..INS_1_WIDTH].borrow();
392        let next: &Instruction2Cols<AB::Var> = next_slice[..INS_2_WIDTH].borrow();
393        // `is_ins_row` already indicates enabled.
394        let mut is_ins_row = builder.when(local.prefix.general.is_ins_row);
395        // These constraints do not add anything and can be safely removed.
396        {
397            is_ins_row.assert_eq(
398                local.write_a_x_is_first,
399                local.prefix.data.write_a * local.prefix.a_or_is_first,
400            );
401            is_ins_row.assert_bool(local.write_a_x_is_first);
402        }
403        let mut is_first_ins = is_ins_row.when(local.prefix.a_or_is_first);
404        // ATTENTION: degree of is_first_ins is 2
405        // (T5): An Instruction1 row must be followed by an Instruction2 row.
406        {
407            is_first_ins.assert_one(next.general.is_ins_row);
408            is_first_ins.assert_zero(next.is_first);
409        }
410
411        let local_data = &local.prefix.data;
412        let length = local.prefix.data.idx;
413        let multiplicity = local.prefix.general.is_ins_row * local.prefix.a_or_is_first;
414        let start_timestamp = local.prefix.general.timestamp;
415        let write_timestamp = start_timestamp
416            + AB::Expr::TWO * length
417            + AB::Expr::from_canonical_usize(INSTRUCTION_READS);
418        let end_timestamp = write_timestamp.clone() + AB::Expr::ONE;
419        let native_as = AB::Expr::from_canonical_u32(AS::Native as u32);
420        self.execution_bridge
421            .execute(
422                AB::F::from_canonical_usize(FRI_REDUCED_OPENING.global_opcode().as_usize()),
423                [
424                    local.a_ptr_ptr.into(),
425                    local.b_ptr_ptr.into(),
426                    next.length_ptr.into(),
427                    next.alpha_ptr.into(),
428                    next.result_ptr.into(),
429                    next.hint_id_ptr.into(),
430                    next.is_init_ptr.into(),
431                ],
432                ExecutionState::new(local.pc, local.prefix.general.timestamp),
433                ExecutionState::<AB::Expr>::new(
434                    AB::Expr::from_canonical_u32(DEFAULT_PC_STEP) + local.pc,
435                    end_timestamp.clone(),
436                ),
437            )
438            .eval(builder, multiplicity.clone());
439        // Read alpha
440        self.memory_bridge
441            .read(
442                MemoryAddress::new(native_as.clone(), next.alpha_ptr),
443                local_data.alpha,
444                start_timestamp,
445                &next.alpha_aux,
446            )
447            .eval(builder, multiplicity.clone());
448        // Read length.
449        self.memory_bridge
450            .read(
451                MemoryAddress::new(native_as.clone(), next.length_ptr),
452                [length],
453                start_timestamp + AB::Expr::ONE,
454                &next.length_aux,
455            )
456            .eval(builder, multiplicity.clone());
457        // Read a_ptr
458        self.memory_bridge
459            .read(
460                MemoryAddress::new(native_as.clone(), local.a_ptr_ptr),
461                [local_data.a_ptr],
462                start_timestamp + AB::Expr::TWO,
463                &local.a_ptr_aux,
464            )
465            .eval(builder, multiplicity.clone());
466        // Read b_ptr
467        self.memory_bridge
468            .read(
469                MemoryAddress::new(native_as.clone(), local.b_ptr_ptr),
470                [local_data.b_ptr],
471                start_timestamp + AB::Expr::from_canonical_u32(3),
472                &local.b_ptr_aux,
473            )
474            .eval(builder, multiplicity.clone());
475        // Read write_a = 1 - is_init, it should be a boolean.
476        self.memory_bridge
477            .read(
478                MemoryAddress::new(native_as.clone(), next.is_init_ptr),
479                [AB::Expr::ONE - local_data.write_a],
480                start_timestamp + AB::Expr::from_canonical_u32(4),
481                &next.is_init_aux,
482            )
483            .eval(builder, multiplicity.clone());
484        self.memory_bridge
485            .write(
486                MemoryAddress::new(native_as.clone(), next.result_ptr),
487                local_data.result,
488                write_timestamp,
489                &next.result_aux,
490            )
491            .eval(builder, multiplicity.clone());
492    }
493
494    fn eval_instruction2_row<AB: InteractionBuilder>(
495        &self,
496        builder: &mut AB,
497        local_slice: &[AB::Var],
498        next_slice: &[AB::Var],
499    ) {
500        let local: &Instruction2Cols<AB::Var> = local_slice[..INS_2_WIDTH].borrow();
501        let next: &WorkloadCols<AB::Var> = next_slice[..WL_WIDTH].borrow();
502        // (T7): The last row is either a Disabled or an Instruction2 row.
503        {
504            let mut last_row = builder.when_last_row();
505            let mut enabled =
506                last_row.when(local.general.is_ins_row + local.general.is_workload_row);
507            enabled.assert_one(local.general.is_ins_row);
508            enabled.assert_zero(local.is_first);
509        }
510        {
511            let mut when_transition = builder.when_transition();
512            let mut is_ins_row = when_transition.when(local.general.is_ins_row);
513            let mut not_first_ins_row = is_ins_row.when_ne(local.is_first, AB::Expr::ONE);
514            // ATTENTION: degree of not_first_ins_row is 2
515            // Because all the following assert 0, we don't need to check next.enabled.
516            // (T6): An Instruction2 row must be followed by a Workload or Disabled row.
517            not_first_ins_row.assert_zero(next.prefix.general.is_ins_row);
518            // The next row must have idx = 0.
519            not_first_ins_row.assert_zero(next.prefix.data.idx);
520            // next.result is all 0s
521            assert_array_eq(
522                &mut not_first_ins_row,
523                next.prefix.data.result,
524                [AB::Expr::ZERO; EXT_DEG],
525            );
526        }
527    }
528}
529
530fn assert_array_eq<AB: AirBuilder, I1: Into<AB::Expr>, I2: Into<AB::Expr>, const N: usize>(
531    builder: &mut AB,
532    x: [I1; N],
533    y: [I2; N],
534) {
535    for (x, y) in zip_eq(x, y) {
536        builder.assert_eq(x, y);
537    }
538}
539
540fn elem_to_ext<F: Field>(elem: F) -> [F; EXT_DEG] {
541    let mut ret = [F::ZERO; EXT_DEG];
542    ret[0] = elem;
543    ret
544}
545
546#[derive(Serialize, Deserialize)]
547#[serde(bound = "F: Field")]
548pub struct FriReducedOpeningRecord<F: Field> {
549    pub pc: F,
550    pub start_timestamp: F,
551    pub instruction: Instruction<F>,
552    pub alpha_read: RecordId,
553    pub length_read: RecordId,
554    pub a_ptr_read: RecordId,
555    pub is_init_read: RecordId,
556    pub b_ptr_read: RecordId,
557    pub a_rws: Vec<RecordId>,
558    pub b_reads: Vec<RecordId>,
559    pub result_write: RecordId,
560}
561
562impl<F: Field> FriReducedOpeningRecord<F> {
563    pub fn get_height(&self) -> usize {
564        // 2 for instruction rows
565        self.a_rws.len() + 2
566    }
567}
568
569pub struct FriReducedOpeningChip<F: Field> {
570    air: FriReducedOpeningAir,
571    pub records: Vec<FriReducedOpeningRecord<F>>,
572    pub height: usize,
573    offline_memory: Arc<Mutex<OfflineMemory<F>>>,
574    streams: Arc<Mutex<Streams<F>>>,
575}
576impl<F: PrimeField32> FriReducedOpeningChip<F> {
577    pub fn new(
578        execution_bus: ExecutionBus,
579        program_bus: ProgramBus,
580        memory_bridge: MemoryBridge,
581        offline_memory: Arc<Mutex<OfflineMemory<F>>>,
582        streams: Arc<Mutex<Streams<F>>>,
583    ) -> Self {
584        let air = FriReducedOpeningAir {
585            execution_bridge: ExecutionBridge::new(execution_bus, program_bus),
586            memory_bridge,
587        };
588        Self {
589            records: vec![],
590            air,
591            height: 0,
592            offline_memory,
593            streams,
594        }
595    }
596}
597impl<F: PrimeField32> InstructionExecutor<F> for FriReducedOpeningChip<F> {
598    fn execute(
599        &mut self,
600        memory: &mut MemoryController<F>,
601        instruction: &Instruction<F>,
602        from_state: ExecutionState<u32>,
603    ) -> Result<ExecutionState<u32>, ExecutionError> {
604        let &Instruction {
605            a: a_ptr_ptr,
606            b: b_ptr_ptr,
607            c: length_ptr,
608            d: alpha_ptr,
609            e: result_ptr,
610            f: hint_id_ptr,
611            g: is_init_ptr,
612            ..
613        } = instruction;
614
615        let addr_space = F::from_canonical_u32(AS::Native as u32);
616        let alpha_read = memory.read(addr_space, alpha_ptr);
617        let length_read = memory.read_cell(addr_space, length_ptr);
618        let a_ptr_read = memory.read_cell(addr_space, a_ptr_ptr);
619        let b_ptr_read = memory.read_cell(addr_space, b_ptr_ptr);
620        let is_init_read = memory.read_cell(addr_space, is_init_ptr);
621        let is_init = is_init_read.1.as_canonical_u32();
622
623        let hint_id_f = memory.unsafe_read_cell(addr_space, hint_id_ptr);
624        let hint_id = hint_id_f.as_canonical_u32() as usize;
625
626        let alpha = alpha_read.1;
627        let length = length_read.1.as_canonical_u32() as usize;
628        let a_ptr = a_ptr_read.1;
629        let b_ptr = b_ptr_read.1;
630
631        let mut a_rws = Vec::with_capacity(length);
632        let mut b_reads = Vec::with_capacity(length);
633        let mut result = [F::ZERO; EXT_DEG];
634
635        let data = if is_init == 0 {
636            let mut streams = self.streams.lock().unwrap();
637            let hint_steam = &mut streams.hint_space[hint_id];
638            hint_steam.drain(0..length).collect()
639        } else {
640            vec![]
641        };
642        #[allow(clippy::needless_range_loop)]
643        for i in 0..length {
644            let a_rw = if is_init == 0 {
645                let (record_id, _) =
646                    memory.write_cell(addr_space, a_ptr + F::from_canonical_usize(i), data[i]);
647                (record_id, data[i])
648            } else {
649                memory.read_cell(addr_space, a_ptr + F::from_canonical_usize(i))
650            };
651            let b_read =
652                memory.read::<EXT_DEG>(addr_space, b_ptr + F::from_canonical_usize(EXT_DEG * i));
653            a_rws.push(a_rw);
654            b_reads.push(b_read);
655        }
656
657        for (a_rw, b_read) in a_rws.iter().rev().zip_eq(b_reads.iter().rev()) {
658            let a = a_rw.1;
659            let b = b_read.1;
660            // result = result * alpha + (b - a)
661            result = FieldExtension::add(
662                FieldExtension::multiply(result, alpha),
663                FieldExtension::subtract(b, elem_to_ext(a)),
664            );
665        }
666
667        let (result_write, _) = memory.write(addr_space, result_ptr, result);
668
669        let record = FriReducedOpeningRecord {
670            pc: F::from_canonical_u32(from_state.pc),
671            start_timestamp: F::from_canonical_u32(from_state.timestamp),
672            instruction: instruction.clone(),
673            alpha_read: alpha_read.0,
674            length_read: length_read.0,
675            a_ptr_read: a_ptr_read.0,
676            is_init_read: is_init_read.0,
677            b_ptr_read: b_ptr_read.0,
678            a_rws: a_rws.into_iter().map(|r| r.0).collect(),
679            b_reads: b_reads.into_iter().map(|r| r.0).collect(),
680            result_write,
681        };
682        self.height += record.get_height();
683        self.records.push(record);
684
685        Ok(ExecutionState {
686            pc: from_state.pc + DEFAULT_PC_STEP,
687            timestamp: memory.timestamp(),
688        })
689    }
690
691    fn get_opcode_name(&self, opcode: usize) -> String {
692        assert_eq!(opcode, FRI_REDUCED_OPENING.global_opcode().as_usize());
693        String::from("FRI_REDUCED_OPENING")
694    }
695}
696
697fn record_to_rows<F: PrimeField32>(
698    record: FriReducedOpeningRecord<F>,
699    aux_cols_factory: &MemoryAuxColsFactory<F>,
700    slice: &mut [F],
701    memory: &OfflineMemory<F>,
702) {
703    let Instruction {
704        a: a_ptr_ptr,
705        b: b_ptr_ptr,
706        c: length_ptr,
707        d: alpha_ptr,
708        e: result_ptr,
709        f: hint_id_ptr,
710        g: is_init_ptr,
711        ..
712    } = record.instruction;
713
714    let length_read = memory.record_by_id(record.length_read);
715    let alpha_read = memory.record_by_id(record.alpha_read);
716    let a_ptr_read = memory.record_by_id(record.a_ptr_read);
717    let b_ptr_read = memory.record_by_id(record.b_ptr_read);
718    let is_init_read = memory.record_by_id(record.is_init_read);
719    let is_init = is_init_read.data_at(0);
720    let write_a = F::ONE - is_init;
721
722    let length = length_read.data_at(0).as_canonical_u32() as usize;
723    let alpha: [F; EXT_DEG] = alpha_read.data_slice().try_into().unwrap();
724    let a_ptr = a_ptr_read.data_at(0);
725    let b_ptr = b_ptr_read.data_at(0);
726
727    let mut result = [F::ZERO; EXT_DEG];
728
729    let alpha_aux = aux_cols_factory.make_read_aux_cols(alpha_read);
730    let length_aux = aux_cols_factory.make_read_aux_cols(length_read);
731    let a_ptr_aux = aux_cols_factory.make_read_aux_cols(a_ptr_read);
732    let b_ptr_aux = aux_cols_factory.make_read_aux_cols(b_ptr_read);
733    let is_init_aux = aux_cols_factory.make_read_aux_cols(is_init_read);
734
735    let result_aux = aux_cols_factory.make_write_aux_cols(memory.record_by_id(record.result_write));
736
737    // WorkloadCols
738    for (i, (&a_record_id, &b_record_id)) in record
739        .a_rws
740        .iter()
741        .rev()
742        .zip_eq(record.b_reads.iter().rev())
743        .enumerate()
744    {
745        let a_rw = memory.record_by_id(a_record_id);
746        let b_read = memory.record_by_id(b_record_id);
747        let a = a_rw.data_at(0);
748        let b: [F; EXT_DEG] = b_read.data_slice().try_into().unwrap();
749
750        let start = i * OVERALL_WIDTH;
751        let cols: &mut WorkloadCols<F> = slice[start..start + WL_WIDTH].borrow_mut();
752        *cols = WorkloadCols {
753            prefix: PrefixCols {
754                general: GeneralCols {
755                    is_workload_row: F::ONE,
756                    is_ins_row: F::ZERO,
757                    timestamp: record.start_timestamp + F::from_canonical_usize((length - i) * 2),
758                },
759                a_or_is_first: a,
760                data: DataCols {
761                    a_ptr: a_ptr + F::from_canonical_usize(length - i),
762                    write_a,
763                    b_ptr: b_ptr + F::from_canonical_usize((length - i) * EXT_DEG),
764                    idx: F::from_canonical_usize(i),
765                    result,
766                    alpha,
767                },
768            },
769            // Generate write aux columns no matter `a` is read or written. When `a` is written,
770            // `prev_data` is not constrained.
771            a_aux: if a_rw.prev_data_slice().is_some() {
772                aux_cols_factory.make_write_aux_cols(a_rw)
773            } else {
774                let read_aux = aux_cols_factory.make_read_aux_cols(a_rw);
775                MemoryWriteAuxCols::from_base(read_aux.get_base(), [F::ZERO])
776            },
777            b,
778            b_aux: aux_cols_factory.make_read_aux_cols(b_read),
779        };
780        // result = result * alpha + (b - a)
781        result = FieldExtension::add(
782            FieldExtension::multiply(result, alpha),
783            FieldExtension::subtract(b, elem_to_ext(a)),
784        );
785    }
786    // Instruction1Cols
787    {
788        let start = length * OVERALL_WIDTH;
789        let cols: &mut Instruction1Cols<F> = slice[start..start + INS_1_WIDTH].borrow_mut();
790        *cols = Instruction1Cols {
791            prefix: PrefixCols {
792                general: GeneralCols {
793                    is_workload_row: F::ZERO,
794                    is_ins_row: F::ONE,
795                    timestamp: record.start_timestamp,
796                },
797                a_or_is_first: F::ONE,
798                data: DataCols {
799                    a_ptr,
800                    write_a,
801                    b_ptr,
802                    idx: F::from_canonical_usize(length),
803                    result,
804                    alpha,
805                },
806            },
807            pc: record.pc,
808            a_ptr_ptr,
809            a_ptr_aux,
810            b_ptr_ptr,
811            b_ptr_aux,
812            write_a_x_is_first: write_a,
813        };
814    }
815    // Instruction2Cols
816    {
817        let start = (length + 1) * OVERALL_WIDTH;
818        let cols: &mut Instruction2Cols<F> = slice[start..start + INS_2_WIDTH].borrow_mut();
819        *cols = Instruction2Cols {
820            general: GeneralCols {
821                is_workload_row: F::ZERO,
822                is_ins_row: F::ONE,
823                timestamp: record.start_timestamp,
824            },
825            is_first: F::ZERO,
826            length_ptr,
827            length_aux,
828            alpha_ptr,
829            alpha_aux,
830            result_ptr,
831            result_aux,
832            hint_id_ptr,
833            is_init_ptr,
834            is_init_aux,
835            write_a_x_is_first: F::ZERO,
836        };
837    }
838}
839
840impl<F: Field> ChipUsageGetter for FriReducedOpeningChip<F> {
841    fn air_name(&self) -> String {
842        "FriReducedOpeningAir".to_string()
843    }
844
845    fn current_trace_height(&self) -> usize {
846        self.height
847    }
848
849    fn trace_width(&self) -> usize {
850        OVERALL_WIDTH
851    }
852}
853
854impl<SC: StarkGenericConfig> Chip<SC> for FriReducedOpeningChip<Val<SC>>
855where
856    Val<SC>: PrimeField32,
857{
858    fn air(&self) -> AirRef<SC> {
859        Arc::new(self.air)
860    }
861    fn generate_air_proof_input(self) -> AirProofInput<SC> {
862        let height = next_power_of_two_or_zero(self.height);
863        let mut flat_trace = Val::<SC>::zero_vec(OVERALL_WIDTH * height);
864        let chunked_trace = {
865            let sizes: Vec<_> = self
866                .records
867                .par_iter()
868                .map(|record| OVERALL_WIDTH * record.get_height())
869                .collect();
870            variable_chunks_mut(&mut flat_trace, &sizes)
871        };
872
873        let memory = self.offline_memory.lock().unwrap();
874        let aux_cols_factory = memory.aux_cols_factory();
875
876        self.records
877            .into_par_iter()
878            .zip_eq(chunked_trace.into_par_iter())
879            .for_each(|(record, slice)| {
880                record_to_rows(record, &aux_cols_factory, slice, &memory);
881            });
882
883        let matrix = RowMajorMatrix::new(flat_trace, OVERALL_WIDTH);
884        AirProofInput::simple_no_pis(matrix)
885    }
886}
887
888fn variable_chunks_mut<'a, T>(mut slice: &'a mut [T], sizes: &[usize]) -> Vec<&'a mut [T]> {
889    let mut result = Vec::with_capacity(sizes.len());
890    for &size in sizes {
891        // split_at_mut guarantees disjoint slices
892        let (left, right) = slice.split_at_mut(size);
893        result.push(left);
894        slice = right; // move forward for the next chunk
895    }
896    result
897}