openvm_native_circuit/extension/
mod.rs

1use alu_native_adapter::{AluNativeAdapterAir, AluNativeAdapterExecutor};
2use branch_native_adapter::{BranchNativeAdapterAir, BranchNativeAdapterExecutor};
3use convert_adapter::{ConvertAdapterAir, ConvertAdapterExecutor};
4use derive_more::derive::From;
5use loadstore_native_adapter::{NativeLoadStoreAdapterAir, NativeLoadStoreAdapterExecutor};
6use native_vectorized_adapter::{NativeVectorizedAdapterAir, NativeVectorizedAdapterExecutor};
7use openvm_circuit::{
8    arch::{
9        AirInventory, AirInventoryError, ChipInventory, ChipInventoryError, ExecutionBridge,
10        ExecutorInventoryBuilder, ExecutorInventoryError, RowMajorMatrixArena, VmCircuitExtension,
11        VmExecutionExtension, VmProverExtension,
12    },
13    system::{memory::SharedMemoryHelper, SystemPort},
14};
15use openvm_circuit_derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor};
16use openvm_instructions::{program::DEFAULT_PC_STEP, LocalOpcode, PhantomDiscriminant};
17use openvm_native_compiler::{
18    CastfOpcode, FieldArithmeticOpcode, FieldExtensionOpcode, FriOpcode, NativeBranchEqualOpcode,
19    NativeJalOpcode, NativeLoadStore4Opcode, NativeLoadStoreOpcode, NativePhantom,
20    NativeRangeCheckOpcode, Poseidon2Opcode, VerifyBatchOpcode, BLOCK_LOAD_STORE_SIZE,
21};
22use openvm_poseidon2_air::Poseidon2Config;
23use openvm_rv32im_circuit::BranchEqualCoreAir;
24use openvm_stark_backend::{
25    config::{StarkGenericConfig, Val},
26    p3_field::{Field, PrimeField32},
27    prover::cpu::{CpuBackend, CpuDevice},
28};
29use openvm_stark_sdk::engine::StarkEngine;
30use serde::{Deserialize, Serialize};
31use strum::IntoEnumIterator;
32
33use crate::{
34    adapters::*,
35    branch_eq::{
36        NativeBranchEqAir, NativeBranchEqChip, NativeBranchEqExecutor, NativeBranchEqualFiller,
37    },
38    castf::{CastFAir, CastFChip, CastFCoreAir, CastFCoreFiller, CastFExecutor},
39    field_arithmetic::{
40        FieldArithmeticAir, FieldArithmeticChip, FieldArithmeticCoreAir, FieldArithmeticCoreFiller,
41        FieldArithmeticExecutor,
42    },
43    field_extension::{
44        FieldExtensionAir, FieldExtensionChip, FieldExtensionCoreAir, FieldExtensionCoreFiller,
45        FieldExtensionExecutor,
46    },
47    fri::{
48        FriReducedOpeningAir, FriReducedOpeningChip, FriReducedOpeningExecutor,
49        FriReducedOpeningFiller,
50    },
51    jal_rangecheck::{
52        JalRangeCheckAir, JalRangeCheckExecutor, JalRangeCheckFiller, NativeJalRangeCheckChip,
53    },
54    loadstore::{
55        NativeLoadStoreAir, NativeLoadStoreChip, NativeLoadStoreCoreAir, NativeLoadStoreCoreFiller,
56        NativeLoadStoreExecutor,
57    },
58    phantom::*,
59    poseidon2::{
60        air::{NativePoseidon2Air, VerifyBatchBus},
61        chip::{NativePoseidon2Executor, NativePoseidon2Filler},
62        NativePoseidon2Chip,
63    },
64};
65
66cfg_if::cfg_if! {
67    if #[cfg(feature = "cuda")] {
68        mod cuda;
69        pub use self::cuda::*;
70        pub use self::cuda::{
71            NativeGpuProverExt as NativeProverExt,
72        };
73        pub type NativeBuilder = crate::NativeGpuBuilder;
74    } else {
75        pub use self::{
76            NativeCpuProverExt as NativeProverExt,
77        };
78        pub type NativeBuilder = crate::NativeCpuBuilder;
79    }
80}
81
82// ============ VmExtension Implementations ============
83
84#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
85pub struct Native;
86
87#[derive(Clone, From, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)]
88#[cfg_attr(
89    feature = "aot",
90    derive(
91        openvm_circuit_derive::AotExecutor,
92        openvm_circuit_derive::AotMeteredExecutor
93    )
94)]
95pub enum NativeExecutor<F: Field> {
96    LoadStore(NativeLoadStoreExecutor<1>),
97    BlockLoadStore(NativeLoadStoreExecutor<BLOCK_LOAD_STORE_SIZE>),
98    BranchEqual(NativeBranchEqExecutor),
99    Jal(JalRangeCheckExecutor),
100    FieldArithmetic(FieldArithmeticExecutor),
101    FieldExtension(FieldExtensionExecutor),
102    FriReducedOpening(FriReducedOpeningExecutor),
103    VerifyBatch(NativePoseidon2Executor<F, 1>),
104}
105
106impl<F: PrimeField32> VmExecutionExtension<F> for Native {
107    type Executor = NativeExecutor<F>;
108
109    fn extend_execution(
110        &self,
111        inventory: &mut ExecutorInventoryBuilder<F, NativeExecutor<F>>,
112    ) -> Result<(), ExecutorInventoryError> {
113        let load_store = NativeLoadStoreExecutor::<1>::new(
114            NativeLoadStoreAdapterExecutor::new(NativeLoadStoreOpcode::CLASS_OFFSET),
115            NativeLoadStoreOpcode::CLASS_OFFSET,
116        );
117        inventory.add_executor(
118            load_store,
119            NativeLoadStoreOpcode::iter().map(|x| x.global_opcode()),
120        )?;
121
122        let block_load_store = NativeLoadStoreExecutor::<BLOCK_LOAD_STORE_SIZE>::new(
123            NativeLoadStoreAdapterExecutor::new(NativeLoadStore4Opcode::CLASS_OFFSET),
124            NativeLoadStore4Opcode::CLASS_OFFSET,
125        );
126        inventory.add_executor(
127            block_load_store,
128            NativeLoadStore4Opcode::iter().map(|x| x.global_opcode()),
129        )?;
130
131        let branch_equal = NativeBranchEqExecutor::new(
132            BranchNativeAdapterExecutor::new(),
133            NativeBranchEqualOpcode::CLASS_OFFSET,
134            DEFAULT_PC_STEP,
135        );
136        inventory.add_executor(
137            branch_equal,
138            NativeBranchEqualOpcode::iter().map(|x| x.global_opcode()),
139        )?;
140
141        let jal_rangecheck = JalRangeCheckExecutor;
142        inventory.add_executor(
143            jal_rangecheck,
144            [
145                NativeJalOpcode::JAL.global_opcode(),
146                NativeRangeCheckOpcode::RANGE_CHECK.global_opcode(),
147            ],
148        )?;
149
150        let field_arithmetic = FieldArithmeticExecutor::new(AluNativeAdapterExecutor::new());
151        inventory.add_executor(
152            field_arithmetic,
153            FieldArithmeticOpcode::iter().map(|x| x.global_opcode()),
154        )?;
155
156        let field_extension = FieldExtensionExecutor::new(NativeVectorizedAdapterExecutor::new());
157        inventory.add_executor(
158            field_extension,
159            FieldExtensionOpcode::iter().map(|x| x.global_opcode()),
160        )?;
161
162        let fri_reduced_opening = FriReducedOpeningExecutor::new();
163        inventory.add_executor(
164            fri_reduced_opening,
165            FriOpcode::iter().map(|x| x.global_opcode()),
166        )?;
167
168        let verify_batch = NativePoseidon2Executor::<F, 1>::new(Poseidon2Config::default());
169        inventory.add_executor(
170            verify_batch,
171            [
172                VerifyBatchOpcode::VERIFY_BATCH.global_opcode(),
173                Poseidon2Opcode::PERM_POS2.global_opcode(),
174                Poseidon2Opcode::COMP_POS2.global_opcode(),
175            ],
176        )?;
177
178        inventory.add_phantom_sub_executor(
179            NativeHintInputSubEx,
180            PhantomDiscriminant(NativePhantom::HintInput as u16),
181        )?;
182
183        inventory.add_phantom_sub_executor(
184            NativeHintSliceSubEx::<1>,
185            PhantomDiscriminant(NativePhantom::HintFelt as u16),
186        )?;
187
188        inventory.add_phantom_sub_executor(
189            NativeHintBitsSubEx,
190            PhantomDiscriminant(NativePhantom::HintBits as u16),
191        )?;
192
193        inventory.add_phantom_sub_executor(
194            NativePrintSubEx,
195            PhantomDiscriminant(NativePhantom::Print as u16),
196        )?;
197
198        inventory.add_phantom_sub_executor(
199            NativeHintLoadSubEx,
200            PhantomDiscriminant(NativePhantom::HintLoad as u16),
201        )?;
202
203        Ok(())
204    }
205}
206
207impl<SC: StarkGenericConfig> VmCircuitExtension<SC> for Native
208where
209    Val<SC>: PrimeField32,
210{
211    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
212        let SystemPort {
213            execution_bus,
214            program_bus,
215            memory_bridge,
216        } = inventory.system().port();
217        let exec_bridge = ExecutionBridge::new(execution_bus, program_bus);
218        let range_checker = inventory.range_checker().bus;
219
220        let load_store = NativeLoadStoreAir::<1>::new(
221            NativeLoadStoreAdapterAir::new(memory_bridge, exec_bridge),
222            NativeLoadStoreCoreAir::new(NativeLoadStoreOpcode::CLASS_OFFSET),
223        );
224        inventory.add_air(load_store);
225
226        let block_load_store = NativeLoadStoreAir::<BLOCK_LOAD_STORE_SIZE>::new(
227            NativeLoadStoreAdapterAir::new(memory_bridge, exec_bridge),
228            NativeLoadStoreCoreAir::new(NativeLoadStore4Opcode::CLASS_OFFSET),
229        );
230        inventory.add_air(block_load_store);
231
232        let branch_equal = NativeBranchEqAir::new(
233            BranchNativeAdapterAir::new(exec_bridge, memory_bridge),
234            BranchEqualCoreAir::new(NativeBranchEqualOpcode::CLASS_OFFSET, DEFAULT_PC_STEP),
235        );
236        inventory.add_air(branch_equal);
237
238        let jal_rangecheck = JalRangeCheckAir::new(
239            ExecutionBridge::new(execution_bus, program_bus),
240            memory_bridge,
241            range_checker,
242        );
243        inventory.add_air(jal_rangecheck);
244
245        let field_arithmetic = FieldArithmeticAir::new(
246            AluNativeAdapterAir::new(exec_bridge, memory_bridge),
247            FieldArithmeticCoreAir::new(),
248        );
249        inventory.add_air(field_arithmetic);
250
251        let field_extension = FieldExtensionAir::new(
252            NativeVectorizedAdapterAir::new(exec_bridge, memory_bridge),
253            FieldExtensionCoreAir::new(),
254        );
255        inventory.add_air(field_extension);
256
257        let fri_reduced_opening = FriReducedOpeningAir::new(
258            ExecutionBridge::new(execution_bus, program_bus),
259            memory_bridge,
260        );
261        inventory.add_air(fri_reduced_opening);
262
263        let verify_batch = NativePoseidon2Air::<_, 1>::new(
264            exec_bridge,
265            memory_bridge,
266            VerifyBatchBus::new(inventory.new_bus_idx()),
267            Poseidon2Config::default(),
268        );
269        inventory.add_air(verify_batch);
270
271        Ok(())
272    }
273}
274
275pub struct NativeCpuProverExt;
276// This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker,
277// BitwiseOperationLookupChip) are specific to CpuBackend.
278impl<E, SC, RA> VmProverExtension<E, RA, Native> for NativeCpuProverExt
279where
280    SC: StarkGenericConfig,
281    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
282    RA: RowMajorMatrixArena<Val<SC>>,
283    Val<SC>: PrimeField32,
284{
285    fn extend_prover(
286        &self,
287        _: &Native,
288        inventory: &mut ChipInventory<SC, RA, CpuBackend<SC>>,
289    ) -> Result<(), ChipInventoryError> {
290        let range_checker = inventory.range_checker()?.clone();
291        let timestamp_max_bits = inventory.timestamp_max_bits();
292        let mem_helper = SharedMemoryHelper::new(range_checker.clone(), timestamp_max_bits);
293
294        // These calls to next_air are not strictly necessary to construct the chips, but provide a
295        // safeguard to ensure that chip construction matches the circuit definition
296        inventory.next_air::<NativeLoadStoreAir<1>>()?;
297        let load_store = NativeLoadStoreChip::<_, 1>::new(
298            NativeLoadStoreCoreFiller::new(NativeLoadStoreAdapterFiller),
299            mem_helper.clone(),
300        );
301        inventory.add_executor_chip(load_store);
302
303        inventory.next_air::<NativeLoadStoreAir<BLOCK_LOAD_STORE_SIZE>>()?;
304        let block_load_store = NativeLoadStoreChip::<_, BLOCK_LOAD_STORE_SIZE>::new(
305            NativeLoadStoreCoreFiller::new(NativeLoadStoreAdapterFiller),
306            mem_helper.clone(),
307        );
308        inventory.add_executor_chip(block_load_store);
309
310        inventory.next_air::<NativeBranchEqAir>()?;
311        let branch_eq = NativeBranchEqChip::new(
312            NativeBranchEqualFiller::new(BranchNativeAdapterFiller),
313            mem_helper.clone(),
314        );
315
316        inventory.add_executor_chip(branch_eq);
317
318        inventory.next_air::<JalRangeCheckAir>()?;
319        let jal_rangecheck = NativeJalRangeCheckChip::new(
320            JalRangeCheckFiller::new(range_checker.clone()),
321            mem_helper.clone(),
322        );
323        inventory.add_executor_chip(jal_rangecheck);
324
325        inventory.next_air::<FieldArithmeticAir>()?;
326        let field_arithmetic = FieldArithmeticChip::new(
327            FieldArithmeticCoreFiller::new(AluNativeAdapterFiller),
328            mem_helper.clone(),
329        );
330        inventory.add_executor_chip(field_arithmetic);
331
332        inventory.next_air::<FieldExtensionAir>()?;
333        let field_extension = FieldExtensionChip::new(
334            FieldExtensionCoreFiller::new(NativeVectorizedAdapterFiller),
335            mem_helper.clone(),
336        );
337        inventory.add_executor_chip(field_extension);
338
339        inventory.next_air::<FriReducedOpeningAir>()?;
340        let fri_reduced_opening =
341            FriReducedOpeningChip::new(FriReducedOpeningFiller::new(), mem_helper.clone());
342        inventory.add_executor_chip(fri_reduced_opening);
343
344        inventory.next_air::<NativePoseidon2Air<Val<SC>, 1>>()?;
345        let poseidon2 = NativePoseidon2Chip::<_, 1>::new(
346            NativePoseidon2Filler::new(Poseidon2Config::default()),
347            mem_helper.clone(),
348        );
349        inventory.add_executor_chip(poseidon2);
350
351        Ok(())
352    }
353}
354
355pub(crate) mod phantom {
356    use eyre::bail;
357    use openvm_circuit::{
358        arch::{PhantomSubExecutor, Streams},
359        system::memory::online::GuestMemory,
360    };
361    use openvm_instructions::PhantomDiscriminant;
362    use openvm_stark_backend::p3_field::{Field, PrimeField32};
363    use rand::rngs::StdRng;
364
365    pub struct NativeHintInputSubEx;
366    pub struct NativeHintSliceSubEx<const N: usize>;
367    pub struct NativePrintSubEx;
368    pub struct NativeHintBitsSubEx;
369    pub struct NativeHintLoadSubEx;
370
371    impl<F: Field> PhantomSubExecutor<F> for NativeHintInputSubEx {
372        fn phantom_execute(
373            &self,
374            _: &GuestMemory,
375            streams: &mut Streams<F>,
376            _: &mut StdRng,
377            _: PhantomDiscriminant,
378            _: u32,
379            _: u32,
380            _: u16,
381        ) -> eyre::Result<()> {
382            let hint = match streams.input_stream.pop_front() {
383                Some(hint) => hint,
384                None => {
385                    bail!("EndOfInputStream");
386                }
387            };
388            assert!(streams.hint_stream.is_empty());
389            streams
390                .hint_stream
391                .push_back(F::from_canonical_usize(hint.len()));
392            streams.hint_stream.extend(hint);
393            Ok(())
394        }
395    }
396
397    impl<F: Field, const N: usize> PhantomSubExecutor<F> for NativeHintSliceSubEx<N> {
398        fn phantom_execute(
399            &self,
400            _: &GuestMemory,
401            streams: &mut Streams<F>,
402            _: &mut StdRng,
403            _: PhantomDiscriminant,
404            _: u32,
405            _: u32,
406            _: u16,
407        ) -> eyre::Result<()> {
408            let hint = match streams.input_stream.pop_front() {
409                Some(hint) => hint,
410                None => {
411                    bail!("EndOfInputStream");
412                }
413            };
414            assert!(streams.hint_stream.is_empty());
415            assert_eq!(hint.len(), N);
416            streams.hint_stream = hint.into();
417            Ok(())
418        }
419    }
420
421    impl<F: PrimeField32> PhantomSubExecutor<F> for NativePrintSubEx {
422        fn phantom_execute(
423            &self,
424            memory: &GuestMemory,
425            _: &mut Streams<F>,
426            _: &mut StdRng,
427            _: PhantomDiscriminant,
428            a: u32,
429            _: u32,
430            c_upper: u16,
431        ) -> eyre::Result<()> {
432            // TODO: this check should be performed statically on the program before execution
433            assert!(
434                (c_upper as usize) < memory.memory.config.len(),
435                "c_upper out of bounds"
436            );
437            // SAFETY:
438            // - F is stack-allocated repr(C) or repr(transparent)
439            // - `c_upper` is a valid address space
440            let [value] = unsafe { memory.read::<F, 1>(c_upper as u32, a) };
441            println!("{value}");
442            Ok(())
443        }
444    }
445
446    impl<F: PrimeField32> PhantomSubExecutor<F> for NativeHintBitsSubEx {
447        fn phantom_execute(
448            &self,
449            memory: &GuestMemory,
450            streams: &mut Streams<F>,
451            _: &mut StdRng,
452            _: PhantomDiscriminant,
453            a: u32,
454            len: u32,
455            c_upper: u16,
456        ) -> eyre::Result<()> {
457            // TODO: this check should be performed statically on the program before execution
458            assert!(
459                (c_upper as usize) < memory.memory.config.len(),
460                "c_upper out of bounds"
461            );
462            // SAFETY:
463            // - F is stack-allocated repr(C) or repr(transparent)
464            // - `c_upper` is a valid address space
465            let [val] = unsafe { memory.read::<F, 1>(c_upper as u32, a) };
466            let mut val = val.as_canonical_u32();
467
468            assert!(streams.hint_stream.is_empty());
469            for _ in 0..len {
470                streams
471                    .hint_stream
472                    .push_back(F::from_canonical_u32(val & 1));
473                val >>= 1;
474            }
475            Ok(())
476        }
477    }
478
479    impl<F: PrimeField32> PhantomSubExecutor<F> for NativeHintLoadSubEx {
480        fn phantom_execute(
481            &self,
482            _: &GuestMemory,
483            streams: &mut Streams<F>,
484            _: &mut StdRng,
485            _: PhantomDiscriminant,
486            _: u32,
487            _: u32,
488            _: u16,
489        ) -> eyre::Result<()> {
490            let payload = match streams.input_stream.pop_front() {
491                Some(hint) => hint,
492                None => {
493                    bail!("EndOfInputStream");
494                }
495            };
496            let id = streams.hint_space.len();
497            streams.hint_space.push(payload);
498            // Hint stream should have already been consumed.
499            assert!(streams.hint_stream.is_empty());
500            streams.hint_stream.push_back(F::from_canonical_usize(id));
501            Ok(())
502        }
503    }
504}
505
506#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
507pub struct CastFExtension;
508
509#[derive(Clone, From, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)]
510#[cfg_attr(
511    feature = "aot",
512    derive(
513        openvm_circuit_derive::AotExecutor,
514        openvm_circuit_derive::AotMeteredExecutor
515    )
516)]
517pub enum CastFExtensionExecutor {
518    CastF(CastFExecutor),
519}
520
521impl<F: PrimeField32> VmExecutionExtension<F> for CastFExtension {
522    type Executor = CastFExtensionExecutor;
523
524    fn extend_execution(
525        &self,
526        inventory: &mut ExecutorInventoryBuilder<F, CastFExtensionExecutor>,
527    ) -> Result<(), ExecutorInventoryError> {
528        let castf = CastFExecutor::new(ConvertAdapterExecutor::new());
529        inventory.add_executor(castf, [CastfOpcode::CASTF.global_opcode()])?;
530        Ok(())
531    }
532}
533
534impl<SC: StarkGenericConfig> VmCircuitExtension<SC> for CastFExtension {
535    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
536        let SystemPort {
537            execution_bus,
538            program_bus,
539            memory_bridge,
540        } = inventory.system().port();
541        let exec_bridge = ExecutionBridge::new(execution_bus, program_bus);
542        let range_checker = inventory.range_checker().bus;
543
544        let castf = CastFAir::new(
545            ConvertAdapterAir::new(exec_bridge, memory_bridge),
546            CastFCoreAir::new(range_checker),
547        );
548        inventory.add_air(castf);
549        Ok(())
550    }
551}
552
553impl<E, SC, RA> VmProverExtension<E, RA, CastFExtension> for NativeCpuProverExt
554where
555    SC: StarkGenericConfig,
556    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
557    RA: RowMajorMatrixArena<Val<SC>>,
558    Val<SC>: PrimeField32,
559{
560    fn extend_prover(
561        &self,
562        _: &CastFExtension,
563        inventory: &mut ChipInventory<SC, RA, CpuBackend<SC>>,
564    ) -> Result<(), ChipInventoryError> {
565        let range_checker = inventory.range_checker()?.clone();
566        let timestamp_max_bits = inventory.timestamp_max_bits();
567        let mem_helper = SharedMemoryHelper::new(range_checker.clone(), timestamp_max_bits);
568
569        inventory.next_air::<CastFAir>()?;
570        let castf = CastFChip::new(
571            CastFCoreFiller::new(ConvertAdapterFiller::new(), range_checker),
572            mem_helper.clone(),
573        );
574        inventory.add_executor_chip(castf);
575
576        Ok(())
577    }
578}
579
580// Pre-computed maximum trace heights for NativeConfig. Found by doubling
581// the actual trace heights of kitchen-sink leaf verification (except for
582// VariableRangeChecker, which has a fixed height).
583pub const NATIVE_MAX_TRACE_HEIGHTS: &[u32] = &[
584    4194304, 4, 128, 2097152, 8388608, 4194304, 262144, 2097152, 16777216, 2097152, 8388608,
585    262144, 2097152, 1048576, 4194304, 65536, 262144,
586];