openvm_rv32_adapters/
vec_to_flat.rs

1//! Adapter wrappers that convert between Vec/Block-based interfaces and Flat/Basic interfaces.
2//!
3//! These wrappers allow using `Rv32VecHeapAdapter*` types with cores that expect flat
4//! `BasicAdapterInterface` data formats.
5
6use openvm_circuit::{
7    arch::{
8        AdapterAirContext, AdapterTraceExecutor, BasicAdapterInterface, ImmInstruction,
9        MinimalInstruction, VecHeapAdapterInterface, VecHeapBranchAdapterInterface, VmAdapterAir,
10        VmAdapterInterface,
11    },
12    system::memory::online::TracingMemory,
13};
14use openvm_circuit_primitives::ColumnsAir;
15use openvm_instructions::instruction::Instruction;
16use openvm_stark_backend::{
17    interaction::InteractionBuilder, p3_air::BaseAir, p3_field::PrimeField32,
18    BaseAirWithPublicValues,
19};
20
21// =================================================================================================
22// ALU Adapter Wrappers (with reads and writes)
23// =================================================================================================
24
25/// Wrapper that converts a `VecHeapAdapterInterface` (block-based) to `BasicAdapterInterface`
26/// (flat).
27///
28/// This allows using `Rv32VecHeapAdapterAir` with cores that expect flat read/write data.
29///
30/// # Type Parameters
31/// - `A`: The inner adapter AIR (e.g., `Rv32VecHeapAdapterAir`)
32/// - `NUM_READS`: Number of read operands
33/// - `BLOCKS_PER_READ`: Number of blocks per read operand
34/// - `BLOCKS_PER_WRITE`: Number of blocks per write operand
35/// - `BLOCK_SIZE`: Size of each block
36/// - `TOTAL_READ_SIZE`: Total read size per operand (`BLOCKS_PER_READ * BLOCK_SIZE`)
37/// - `TOTAL_WRITE_SIZE`: Total write size per operand (`BLOCKS_PER_WRITE * BLOCK_SIZE`)
38#[derive(Clone, Copy, Debug, derive_new::new)]
39pub struct VecToFlatAluAdapterAir<
40    A,
41    const NUM_READS: usize,
42    const BLOCKS_PER_READ: usize,
43    const BLOCKS_PER_WRITE: usize,
44    const BLOCK_SIZE: usize,
45    const TOTAL_READ_SIZE: usize,
46    const TOTAL_WRITE_SIZE: usize,
47>(pub A);
48
49impl<
50        F,
51        A,
52        const NUM_READS: usize,
53        const BLOCKS_PER_READ: usize,
54        const BLOCKS_PER_WRITE: usize,
55        const BLOCK_SIZE: usize,
56        const TOTAL_READ_SIZE: usize,
57        const TOTAL_WRITE_SIZE: usize,
58    > BaseAir<F>
59    for VecToFlatAluAdapterAir<
60        A,
61        NUM_READS,
62        BLOCKS_PER_READ,
63        BLOCKS_PER_WRITE,
64        BLOCK_SIZE,
65        TOTAL_READ_SIZE,
66        TOTAL_WRITE_SIZE,
67    >
68where
69    A: BaseAir<F>,
70{
71    fn width(&self) -> usize {
72        self.0.width()
73    }
74}
75
76impl<
77        A,
78        const NUM_READS: usize,
79        const BLOCKS_PER_READ: usize,
80        const BLOCKS_PER_WRITE: usize,
81        const BLOCK_SIZE: usize,
82        const TOTAL_READ_SIZE: usize,
83        const TOTAL_WRITE_SIZE: usize,
84    > ColumnsAir
85    for VecToFlatAluAdapterAir<
86        A,
87        NUM_READS,
88        BLOCKS_PER_READ,
89        BLOCKS_PER_WRITE,
90        BLOCK_SIZE,
91        TOTAL_READ_SIZE,
92        TOTAL_WRITE_SIZE,
93    >
94where
95    A: ColumnsAir,
96{
97    fn columns(&self) -> Option<Vec<String>> {
98        self.0.columns()
99    }
100}
101
102impl<
103        AB,
104        A,
105        const NUM_READS: usize,
106        const BLOCKS_PER_READ: usize,
107        const BLOCKS_PER_WRITE: usize,
108        const BLOCK_SIZE: usize,
109        const TOTAL_READ_SIZE: usize,
110        const TOTAL_WRITE_SIZE: usize,
111    > VmAdapterAir<AB>
112    for VecToFlatAluAdapterAir<
113        A,
114        NUM_READS,
115        BLOCKS_PER_READ,
116        BLOCKS_PER_WRITE,
117        BLOCK_SIZE,
118        TOTAL_READ_SIZE,
119        TOTAL_WRITE_SIZE,
120    >
121where
122    AB: InteractionBuilder,
123    A: VmAdapterAir<
124        AB,
125        Interface = VecHeapAdapterInterface<
126            AB::Expr,
127            NUM_READS,
128            BLOCKS_PER_READ,
129            BLOCKS_PER_WRITE,
130            BLOCK_SIZE,
131            BLOCK_SIZE,
132        >,
133    >,
134{
135    type Interface = BasicAdapterInterface<
136        AB::Expr,
137        MinimalInstruction<AB::Expr>,
138        NUM_READS,
139        1,
140        TOTAL_READ_SIZE,
141        TOTAL_WRITE_SIZE,
142    >;
143
144    fn eval(
145        &self,
146        builder: &mut AB,
147        local: &[AB::Var],
148        ctx: AdapterAirContext<AB::Expr, Self::Interface>,
149    ) {
150        // Runtime assertions for const generic relationships
151        assert_eq!(
152            TOTAL_READ_SIZE,
153            BLOCKS_PER_READ * BLOCK_SIZE,
154            "TOTAL_READ_SIZE must equal BLOCKS_PER_READ * BLOCK_SIZE"
155        );
156        assert_eq!(
157            TOTAL_WRITE_SIZE,
158            BLOCKS_PER_WRITE * BLOCK_SIZE,
159            "TOTAL_WRITE_SIZE must equal BLOCKS_PER_WRITE * BLOCK_SIZE"
160        );
161
162        type InnerI<T, const NR: usize, const BPR: usize, const BPW: usize, const BS: usize> =
163            VecHeapAdapterInterface<T, NR, BPR, BPW, BS, BS>;
164
165        let inner_reads: <InnerI<
166            AB::Expr,
167            NUM_READS,
168            BLOCKS_PER_READ,
169            BLOCKS_PER_WRITE,
170            BLOCK_SIZE,
171        > as VmAdapterInterface<AB::Expr>>::Reads = core::array::from_fn(|read_i| {
172            core::array::from_fn(|block_i| {
173                core::array::from_fn(|in_block_i| {
174                    let byte_i = block_i * BLOCK_SIZE + in_block_i;
175                    ctx.reads[read_i][byte_i].clone()
176                })
177            })
178        });
179
180        let inner_writes: <InnerI<
181            AB::Expr,
182            NUM_READS,
183            BLOCKS_PER_READ,
184            BLOCKS_PER_WRITE,
185            BLOCK_SIZE,
186        > as VmAdapterInterface<AB::Expr>>::Writes = core::array::from_fn(|block_i| {
187            core::array::from_fn(|in_block_i| {
188                let byte_i = block_i * BLOCK_SIZE + in_block_i;
189                ctx.writes[0][byte_i].clone()
190            })
191        });
192
193        let inner_ctx: AdapterAirContext<
194            AB::Expr,
195            InnerI<AB::Expr, NUM_READS, BLOCKS_PER_READ, BLOCKS_PER_WRITE, BLOCK_SIZE>,
196        > = AdapterAirContext {
197            to_pc: ctx.to_pc,
198            reads: inner_reads,
199            writes: inner_writes,
200            instruction: ctx.instruction,
201        };
202
203        self.0.eval(builder, local, inner_ctx)
204    }
205
206    fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
207        self.0.get_from_pc(local)
208    }
209}
210
211impl<
212        F,
213        A,
214        const NUM_READS: usize,
215        const BLOCKS_PER_READ: usize,
216        const BLOCKS_PER_WRITE: usize,
217        const BLOCK_SIZE: usize,
218        const TOTAL_READ_SIZE: usize,
219        const TOTAL_WRITE_SIZE: usize,
220    > BaseAirWithPublicValues<F>
221    for VecToFlatAluAdapterAir<
222        A,
223        NUM_READS,
224        BLOCKS_PER_READ,
225        BLOCKS_PER_WRITE,
226        BLOCK_SIZE,
227        TOTAL_READ_SIZE,
228        TOTAL_WRITE_SIZE,
229    >
230where
231    A: BaseAirWithPublicValues<F>,
232{
233    fn num_public_values(&self) -> usize {
234        self.0.num_public_values()
235    }
236}
237
238/// Wrapper that converts block-based read/write data to flat format for ALU operations.
239#[derive(Clone, Copy, Debug, derive_new::new)]
240pub struct VecToFlatAluAdapterExecutor<
241    A,
242    const NUM_READS: usize,
243    const BLOCKS_PER_READ: usize,
244    const BLOCKS_PER_WRITE: usize,
245    const BLOCK_SIZE: usize,
246    const TOTAL_READ_SIZE: usize,
247    const TOTAL_WRITE_SIZE: usize,
248>(pub A);
249
250impl<
251        F,
252        A,
253        const NUM_READS: usize,
254        const BLOCKS_PER_READ: usize,
255        const BLOCKS_PER_WRITE: usize,
256        const BLOCK_SIZE: usize,
257        const TOTAL_READ_SIZE: usize,
258        const TOTAL_WRITE_SIZE: usize,
259    > AdapterTraceExecutor<F>
260    for VecToFlatAluAdapterExecutor<
261        A,
262        NUM_READS,
263        BLOCKS_PER_READ,
264        BLOCKS_PER_WRITE,
265        BLOCK_SIZE,
266        TOTAL_READ_SIZE,
267        TOTAL_WRITE_SIZE,
268    >
269where
270    F: PrimeField32,
271    A: AdapterTraceExecutor<
272        F,
273        ReadData = [[[u8; BLOCK_SIZE]; BLOCKS_PER_READ]; NUM_READS],
274        WriteData = [[u8; BLOCK_SIZE]; BLOCKS_PER_WRITE],
275    >,
276{
277    const WIDTH: usize = A::WIDTH;
278    type ReadData = [[u8; TOTAL_READ_SIZE]; NUM_READS];
279    type WriteData = [[u8; TOTAL_WRITE_SIZE]; 1];
280    type RecordMut<'a>
281        = A::RecordMut<'a>
282    where
283        Self: 'a;
284
285    #[inline(always)]
286    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
287        A::start(pc, memory, record);
288    }
289
290    #[inline(always)]
291    fn read(
292        &self,
293        memory: &mut TracingMemory,
294        instruction: &Instruction<F>,
295        record: &mut Self::RecordMut<'_>,
296    ) -> Self::ReadData {
297        let data_inner = <A as AdapterTraceExecutor<F>>::read(&self.0, memory, instruction, record);
298
299        core::array::from_fn(|i| {
300            let mut out = [0u8; TOTAL_READ_SIZE];
301            for (block_idx, block) in data_inner[i].iter().enumerate() {
302                let start = block_idx * BLOCK_SIZE;
303                out[start..start + BLOCK_SIZE].copy_from_slice(&block[..]);
304            }
305            out
306        })
307    }
308
309    #[inline(always)]
310    fn write(
311        &self,
312        memory: &mut TracingMemory,
313        instruction: &Instruction<F>,
314        data: Self::WriteData,
315        record: &mut Self::RecordMut<'_>,
316    ) {
317        let data_inner: <A as AdapterTraceExecutor<F>>::WriteData = core::array::from_fn(|i| {
318            let start = i * BLOCK_SIZE;
319            data[0][start..start + BLOCK_SIZE]
320                .try_into()
321                .expect("slice length matches BLOCK_SIZE")
322        });
323
324        <A as AdapterTraceExecutor<F>>::write(&self.0, memory, instruction, data_inner, record);
325    }
326}
327
328// =================================================================================================
329// Branch Adapter Wrappers (reads only, no writes)
330// =================================================================================================
331
332/// Wrapper that converts a `VecHeapBranchAdapterInterface` (block-based) to `BasicAdapterInterface`
333/// (flat).
334///
335/// This allows using `Rv32VecHeapBranchAdapterAir` with cores that expect flat read data.
336/// Branch operations have no writes.
337///
338/// # Type Parameters
339/// - `A`: The inner adapter AIR (e.g., `Rv32VecHeapBranchAdapterAir`)
340/// - `NUM_READS`: Number of read operands
341/// - `BLOCKS_PER_READ`: Number of blocks per read operand
342/// - `BLOCK_SIZE`: Size of each block
343/// - `TOTAL_READ_SIZE`: Total read size per operand (`BLOCKS_PER_READ * BLOCK_SIZE`)
344#[derive(Clone, Copy, Debug, derive_new::new)]
345pub struct VecToFlatBranchAdapterAir<
346    A,
347    const NUM_READS: usize,
348    const BLOCKS_PER_READ: usize,
349    const BLOCK_SIZE: usize,
350    const TOTAL_READ_SIZE: usize,
351>(pub A);
352
353impl<
354        F,
355        A,
356        const NUM_READS: usize,
357        const BLOCKS_PER_READ: usize,
358        const BLOCK_SIZE: usize,
359        const TOTAL_READ_SIZE: usize,
360    > BaseAir<F>
361    for VecToFlatBranchAdapterAir<A, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
362where
363    A: BaseAir<F>,
364{
365    fn width(&self) -> usize {
366        self.0.width()
367    }
368}
369
370impl<
371        A,
372        const NUM_READS: usize,
373        const BLOCKS_PER_READ: usize,
374        const BLOCK_SIZE: usize,
375        const TOTAL_READ_SIZE: usize,
376    > ColumnsAir
377    for VecToFlatBranchAdapterAir<A, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
378where
379    A: ColumnsAir,
380{
381    fn columns(&self) -> Option<Vec<String>> {
382        self.0.columns()
383    }
384}
385
386impl<
387        AB,
388        A,
389        const NUM_READS: usize,
390        const BLOCKS_PER_READ: usize,
391        const BLOCK_SIZE: usize,
392        const TOTAL_READ_SIZE: usize,
393    > VmAdapterAir<AB>
394    for VecToFlatBranchAdapterAir<A, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
395where
396    AB: InteractionBuilder,
397    A: VmAdapterAir<
398        AB,
399        Interface = VecHeapBranchAdapterInterface<AB::Expr, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>,
400    >,
401{
402    type Interface =
403        BasicAdapterInterface<AB::Expr, ImmInstruction<AB::Expr>, NUM_READS, 0, TOTAL_READ_SIZE, 0>;
404
405    fn eval(
406        &self,
407        builder: &mut AB,
408        local: &[AB::Var],
409        ctx: AdapterAirContext<AB::Expr, Self::Interface>,
410    ) {
411        // Runtime assertion for const generic relationship
412        assert_eq!(
413            TOTAL_READ_SIZE,
414            BLOCKS_PER_READ * BLOCK_SIZE,
415            "TOTAL_READ_SIZE must equal BLOCKS_PER_READ * BLOCK_SIZE"
416        );
417
418        type InnerI<T, const NR: usize, const BPR: usize, const BS: usize> =
419            VecHeapBranchAdapterInterface<T, NR, BPR, BS>;
420
421        let inner_reads: <InnerI<AB::Expr, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE> as VmAdapterInterface<AB::Expr>>::Reads =
422            core::array::from_fn(|read_i| {
423                core::array::from_fn(|block_i| {
424                    core::array::from_fn(|in_block_i| {
425                        let byte_i = block_i * BLOCK_SIZE + in_block_i;
426                        ctx.reads[read_i][byte_i].clone()
427                    })
428                })
429            });
430
431        let inner_ctx: AdapterAirContext<
432            AB::Expr,
433            InnerI<AB::Expr, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE>,
434        > = AdapterAirContext {
435            to_pc: ctx.to_pc,
436            reads: inner_reads,
437            writes: (),
438            instruction: ctx.instruction,
439        };
440
441        self.0.eval(builder, local, inner_ctx)
442    }
443
444    fn get_from_pc(&self, local: &[AB::Var]) -> AB::Var {
445        self.0.get_from_pc(local)
446    }
447}
448
449impl<
450        F,
451        A,
452        const NUM_READS: usize,
453        const BLOCKS_PER_READ: usize,
454        const BLOCK_SIZE: usize,
455        const TOTAL_READ_SIZE: usize,
456    > BaseAirWithPublicValues<F>
457    for VecToFlatBranchAdapterAir<A, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
458where
459    A: BaseAirWithPublicValues<F>,
460{
461    fn num_public_values(&self) -> usize {
462        self.0.num_public_values()
463    }
464}
465
466/// Wrapper that converts block-based read data to flat format for branch operations.
467/// Branch operations have no writes.
468#[derive(Clone, Copy, Debug, derive_new::new)]
469pub struct VecToFlatBranchAdapterExecutor<
470    A,
471    const NUM_READS: usize,
472    const BLOCKS_PER_READ: usize,
473    const BLOCK_SIZE: usize,
474    const TOTAL_READ_SIZE: usize,
475>(pub A);
476
477impl<
478        F,
479        A,
480        const NUM_READS: usize,
481        const BLOCKS_PER_READ: usize,
482        const BLOCK_SIZE: usize,
483        const TOTAL_READ_SIZE: usize,
484    > AdapterTraceExecutor<F>
485    for VecToFlatBranchAdapterExecutor<A, NUM_READS, BLOCKS_PER_READ, BLOCK_SIZE, TOTAL_READ_SIZE>
486where
487    F: PrimeField32,
488    A: AdapterTraceExecutor<
489        F,
490        ReadData = [[[u8; BLOCK_SIZE]; BLOCKS_PER_READ]; NUM_READS],
491        WriteData = (),
492    >,
493{
494    const WIDTH: usize = A::WIDTH;
495    type ReadData = [[u8; TOTAL_READ_SIZE]; NUM_READS];
496    type WriteData = ();
497    type RecordMut<'a>
498        = A::RecordMut<'a>
499    where
500        Self: 'a;
501
502    #[inline(always)]
503    fn start(pc: u32, memory: &TracingMemory, record: &mut Self::RecordMut<'_>) {
504        A::start(pc, memory, record);
505    }
506
507    #[inline(always)]
508    fn read(
509        &self,
510        memory: &mut TracingMemory,
511        instruction: &Instruction<F>,
512        record: &mut Self::RecordMut<'_>,
513    ) -> Self::ReadData {
514        let data_inner = <A as AdapterTraceExecutor<F>>::read(&self.0, memory, instruction, record);
515
516        core::array::from_fn(|i| {
517            let mut out = [0u8; TOTAL_READ_SIZE];
518            for (block_idx, block) in data_inner[i].iter().enumerate() {
519                let start = block_idx * BLOCK_SIZE;
520                out[start..start + BLOCK_SIZE].copy_from_slice(&block[..]);
521            }
522            out
523        })
524    }
525
526    #[inline(always)]
527    fn write(
528        &self,
529        _memory: &mut TracingMemory,
530        _instruction: &Instruction<F>,
531        _data: Self::WriteData,
532        _record: &mut Self::RecordMut<'_>,
533    ) {
534        // Branch adapters don't write anything
535    }
536}