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