openvm_native_circuit/
extension.rs

1use air::VerifyBatchBus;
2use alu_native_adapter::AluNativeAdapterChip;
3use branch_native_adapter::BranchNativeAdapterChip;
4use derive_more::derive::From;
5use loadstore_native_adapter::NativeLoadStoreAdapterChip;
6use native_vectorized_adapter::NativeVectorizedAdapterChip;
7use openvm_circuit::{
8    arch::{
9        ExecutionBridge, MemoryConfig, SystemConfig, SystemPort, VmExtension, VmInventory,
10        VmInventoryBuilder, VmInventoryError,
11    },
12    system::phantom::PhantomChip,
13};
14use openvm_circuit_derive::{AnyEnum, InstructionExecutor, VmConfig};
15use openvm_circuit_primitives_derive::{Chip, ChipUsageGetter};
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::{
24    BranchEqualCoreChip, Rv32I, Rv32IExecutor, Rv32IPeriphery, Rv32Io, Rv32IoExecutor,
25    Rv32IoPeriphery, Rv32M, Rv32MExecutor, Rv32MPeriphery,
26};
27use openvm_stark_backend::p3_field::PrimeField32;
28use serde::{Deserialize, Serialize};
29use strum::IntoEnumIterator;
30
31use crate::{
32    adapters::{convert_adapter::ConvertAdapterChip, *},
33    chip::NativePoseidon2Chip,
34    phantom::*,
35    *,
36};
37
38#[derive(Clone, Debug, Serialize, Deserialize, VmConfig, derive_new::new)]
39pub struct NativeConfig {
40    #[system]
41    pub system: SystemConfig,
42    #[extension]
43    pub native: Native,
44}
45
46impl NativeConfig {
47    pub fn aggregation(num_public_values: usize, max_constraint_degree: usize) -> Self {
48        Self {
49            system: SystemConfig::new(
50                max_constraint_degree,
51                MemoryConfig {
52                    max_access_adapter_n: 8,
53                    ..Default::default()
54                },
55                num_public_values,
56            )
57            .with_max_segment_len((1 << 24) - 100),
58            native: Default::default(),
59        }
60    }
61}
62
63#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
64pub struct Native;
65
66#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
67pub enum NativeExecutor<F: PrimeField32> {
68    LoadStore(NativeLoadStoreChip<F, 1>),
69    BlockLoadStore(NativeLoadStoreChip<F, 4>),
70    BranchEqual(NativeBranchEqChip<F>),
71    Jal(JalRangeCheckChip<F>),
72    FieldArithmetic(FieldArithmeticChip<F>),
73    FieldExtension(FieldExtensionChip<F>),
74    FriReducedOpening(FriReducedOpeningChip<F>),
75    VerifyBatch(NativePoseidon2Chip<F, 1>),
76}
77
78#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
79pub enum NativePeriphery<F: PrimeField32> {
80    Phantom(PhantomChip<F>),
81}
82
83impl<F: PrimeField32> VmExtension<F> for Native {
84    type Executor = NativeExecutor<F>;
85    type Periphery = NativePeriphery<F>;
86
87    fn build(
88        &self,
89        builder: &mut VmInventoryBuilder<F>,
90    ) -> Result<VmInventory<NativeExecutor<F>, NativePeriphery<F>>, VmInventoryError> {
91        let mut inventory = VmInventory::new();
92        let SystemPort {
93            execution_bus,
94            program_bus,
95            memory_bridge,
96        } = builder.system_port();
97        let offline_memory = builder.system_base().offline_memory();
98
99        let mut load_store_chip = NativeLoadStoreChip::<F, 1>::new(
100            NativeLoadStoreAdapterChip::new(
101                execution_bus,
102                program_bus,
103                memory_bridge,
104                NativeLoadStoreOpcode::CLASS_OFFSET,
105            ),
106            NativeLoadStoreCoreChip::new(NativeLoadStoreOpcode::CLASS_OFFSET),
107            offline_memory.clone(),
108        );
109        load_store_chip.core.set_streams(builder.streams().clone());
110
111        inventory.add_executor(
112            load_store_chip,
113            NativeLoadStoreOpcode::iter().map(|x| x.global_opcode()),
114        )?;
115
116        let mut block_load_store_chip = NativeLoadStoreChip::<F, BLOCK_LOAD_STORE_SIZE>::new(
117            NativeLoadStoreAdapterChip::new(
118                execution_bus,
119                program_bus,
120                memory_bridge,
121                NativeLoadStore4Opcode::CLASS_OFFSET,
122            ),
123            NativeLoadStoreCoreChip::new(NativeLoadStore4Opcode::CLASS_OFFSET),
124            offline_memory.clone(),
125        );
126        block_load_store_chip
127            .core
128            .set_streams(builder.streams().clone());
129
130        inventory.add_executor(
131            block_load_store_chip,
132            NativeLoadStore4Opcode::iter().map(|x| x.global_opcode()),
133        )?;
134
135        let branch_equal_chip = NativeBranchEqChip::new(
136            BranchNativeAdapterChip::<_>::new(execution_bus, program_bus, memory_bridge),
137            BranchEqualCoreChip::new(NativeBranchEqualOpcode::CLASS_OFFSET, DEFAULT_PC_STEP),
138            offline_memory.clone(),
139        );
140        inventory.add_executor(
141            branch_equal_chip,
142            NativeBranchEqualOpcode::iter().map(|x| x.global_opcode()),
143        )?;
144
145        let jal_chip = JalRangeCheckChip::new(
146            ExecutionBridge::new(execution_bus, program_bus),
147            offline_memory.clone(),
148            builder.system_base().range_checker_chip.clone(),
149        );
150        inventory.add_executor(
151            jal_chip,
152            [
153                NativeJalOpcode::JAL.global_opcode(),
154                NativeRangeCheckOpcode::RANGE_CHECK.global_opcode(),
155            ],
156        )?;
157
158        let field_arithmetic_chip = FieldArithmeticChip::new(
159            AluNativeAdapterChip::<F>::new(execution_bus, program_bus, memory_bridge),
160            FieldArithmeticCoreChip::new(),
161            offline_memory.clone(),
162        );
163        inventory.add_executor(
164            field_arithmetic_chip,
165            FieldArithmeticOpcode::iter().map(|x| x.global_opcode()),
166        )?;
167
168        let field_extension_chip = FieldExtensionChip::new(
169            NativeVectorizedAdapterChip::new(execution_bus, program_bus, memory_bridge),
170            FieldExtensionCoreChip::new(),
171            offline_memory.clone(),
172        );
173        inventory.add_executor(
174            field_extension_chip,
175            FieldExtensionOpcode::iter().map(|x| x.global_opcode()),
176        )?;
177
178        let fri_reduced_opening_chip = FriReducedOpeningChip::new(
179            execution_bus,
180            program_bus,
181            memory_bridge,
182            offline_memory.clone(),
183            builder.streams().clone(),
184        );
185        inventory.add_executor(
186            fri_reduced_opening_chip,
187            FriOpcode::iter().map(|x| x.global_opcode()),
188        )?;
189
190        let poseidon2_chip = NativePoseidon2Chip::new(
191            builder.system_port(),
192            offline_memory.clone(),
193            Poseidon2Config::default(),
194            VerifyBatchBus::new(builder.new_bus_idx()),
195            builder.streams().clone(),
196        );
197        inventory.add_executor(
198            poseidon2_chip,
199            [
200                VerifyBatchOpcode::VERIFY_BATCH.global_opcode(),
201                Poseidon2Opcode::PERM_POS2.global_opcode(),
202                Poseidon2Opcode::COMP_POS2.global_opcode(),
203            ],
204        )?;
205
206        builder.add_phantom_sub_executor(
207            NativeHintInputSubEx,
208            PhantomDiscriminant(NativePhantom::HintInput as u16),
209        )?;
210
211        builder.add_phantom_sub_executor(
212            NativeHintSliceSubEx::<1>,
213            PhantomDiscriminant(NativePhantom::HintFelt as u16),
214        )?;
215
216        builder.add_phantom_sub_executor(
217            NativeHintBitsSubEx,
218            PhantomDiscriminant(NativePhantom::HintBits as u16),
219        )?;
220
221        builder.add_phantom_sub_executor(
222            NativePrintSubEx,
223            PhantomDiscriminant(NativePhantom::Print as u16),
224        )?;
225
226        builder.add_phantom_sub_executor(
227            NativeHintLoadSubEx,
228            PhantomDiscriminant(NativePhantom::HintLoad as u16),
229        )?;
230
231        Ok(inventory)
232    }
233}
234
235pub(crate) mod phantom {
236    use eyre::bail;
237    use openvm_circuit::{
238        arch::{PhantomSubExecutor, Streams},
239        system::memory::MemoryController,
240    };
241    use openvm_instructions::PhantomDiscriminant;
242    use openvm_stark_backend::p3_field::{Field, PrimeField32};
243
244    pub struct NativeHintInputSubEx;
245    pub struct NativeHintSliceSubEx<const N: usize>;
246    pub struct NativePrintSubEx;
247    pub struct NativeHintBitsSubEx;
248    pub struct NativeHintLoadSubEx;
249
250    impl<F: Field> PhantomSubExecutor<F> for NativeHintInputSubEx {
251        fn phantom_execute(
252            &mut self,
253            _: &MemoryController<F>,
254            streams: &mut Streams<F>,
255            _: PhantomDiscriminant,
256            _: F,
257            _: F,
258            _: u16,
259        ) -> eyre::Result<()> {
260            let hint = match streams.input_stream.pop_front() {
261                Some(hint) => hint,
262                None => {
263                    bail!("EndOfInputStream");
264                }
265            };
266            assert!(streams.hint_stream.is_empty());
267            streams
268                .hint_stream
269                .push_back(F::from_canonical_usize(hint.len()));
270            streams.hint_stream.extend(hint);
271            Ok(())
272        }
273    }
274
275    impl<F: Field, const N: usize> PhantomSubExecutor<F> for NativeHintSliceSubEx<N> {
276        fn phantom_execute(
277            &mut self,
278            _: &MemoryController<F>,
279            streams: &mut Streams<F>,
280            _: PhantomDiscriminant,
281            _: F,
282            _: F,
283            _: u16,
284        ) -> eyre::Result<()> {
285            let hint = match streams.input_stream.pop_front() {
286                Some(hint) => hint,
287                None => {
288                    bail!("EndOfInputStream");
289                }
290            };
291            assert!(streams.hint_stream.is_empty());
292            assert_eq!(hint.len(), N);
293            streams.hint_stream = hint.into();
294            Ok(())
295        }
296    }
297
298    impl<F: PrimeField32> PhantomSubExecutor<F> for NativePrintSubEx {
299        fn phantom_execute(
300            &mut self,
301            memory: &MemoryController<F>,
302            _: &mut Streams<F>,
303            _: PhantomDiscriminant,
304            a: F,
305            _: F,
306            c_upper: u16,
307        ) -> eyre::Result<()> {
308            let addr_space = F::from_canonical_u16(c_upper);
309            let value = memory.unsafe_read_cell(addr_space, a);
310            println!("{}", value);
311            Ok(())
312        }
313    }
314
315    impl<F: PrimeField32> PhantomSubExecutor<F> for NativeHintBitsSubEx {
316        fn phantom_execute(
317            &mut self,
318            memory: &MemoryController<F>,
319            streams: &mut Streams<F>,
320            _: PhantomDiscriminant,
321            a: F,
322            b: F,
323            c_upper: u16,
324        ) -> eyre::Result<()> {
325            let addr_space = F::from_canonical_u16(c_upper);
326            let val = memory.unsafe_read_cell(addr_space, a);
327            let mut val = val.as_canonical_u32();
328
329            let len = b.as_canonical_u32();
330            assert!(streams.hint_stream.is_empty());
331            for _ in 0..len {
332                streams
333                    .hint_stream
334                    .push_back(F::from_canonical_u32(val & 1));
335                val >>= 1;
336            }
337            Ok(())
338        }
339    }
340
341    impl<F: PrimeField32> PhantomSubExecutor<F> for NativeHintLoadSubEx {
342        fn phantom_execute(
343            &mut self,
344            _: &MemoryController<F>,
345            streams: &mut Streams<F>,
346            _: PhantomDiscriminant,
347            _: F,
348            _: F,
349            _: u16,
350        ) -> eyre::Result<()> {
351            let payload = match streams.input_stream.pop_front() {
352                Some(hint) => hint,
353                None => {
354                    bail!("EndOfInputStream");
355                }
356            };
357            let id = streams.hint_space.len();
358            streams.hint_space.push(payload);
359            // Hint stream should have already been consumed.
360            assert!(streams.hint_stream.is_empty());
361            streams.hint_stream.push_back(F::from_canonical_usize(id));
362            Ok(())
363        }
364    }
365}
366
367#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
368pub struct CastFExtension;
369
370#[derive(ChipUsageGetter, Chip, InstructionExecutor, From, AnyEnum)]
371pub enum CastFExtensionExecutor<F: PrimeField32> {
372    CastF(CastFChip<F>),
373}
374
375#[derive(From, ChipUsageGetter, Chip, AnyEnum)]
376pub enum CastFExtensionPeriphery<F: PrimeField32> {
377    Placeholder(CastFChip<F>),
378}
379
380impl<F: PrimeField32> VmExtension<F> for CastFExtension {
381    type Executor = CastFExtensionExecutor<F>;
382    type Periphery = CastFExtensionPeriphery<F>;
383
384    fn build(
385        &self,
386        builder: &mut VmInventoryBuilder<F>,
387    ) -> Result<VmInventory<Self::Executor, Self::Periphery>, VmInventoryError> {
388        let mut inventory = VmInventory::new();
389        let SystemPort {
390            execution_bus,
391            program_bus,
392            memory_bridge,
393        } = builder.system_port();
394        let offline_memory = builder.system_base().offline_memory();
395        let range_checker = builder.system_base().range_checker_chip.clone();
396
397        let castf_chip = CastFChip::new(
398            ConvertAdapterChip::new(execution_bus, program_bus, memory_bridge),
399            CastFCoreChip::new(range_checker.clone()),
400            offline_memory.clone(),
401        );
402        inventory.add_executor(castf_chip, [CastfOpcode::CASTF.global_opcode()])?;
403
404        Ok(inventory)
405    }
406}
407
408#[derive(Clone, Debug, VmConfig, derive_new::new, Serialize, Deserialize)]
409pub struct Rv32WithKernelsConfig {
410    #[system]
411    pub system: SystemConfig,
412    #[extension]
413    pub rv32i: Rv32I,
414    #[extension]
415    pub rv32m: Rv32M,
416    #[extension]
417    pub io: Rv32Io,
418    #[extension]
419    pub native: Native,
420    #[extension]
421    pub castf: CastFExtension,
422}
423
424impl Default for Rv32WithKernelsConfig {
425    fn default() -> Self {
426        Self {
427            system: SystemConfig::default().with_continuations(),
428            rv32i: Rv32I,
429            rv32m: Rv32M::default(),
430            io: Rv32Io,
431            native: Native,
432            castf: CastFExtension,
433        }
434    }
435}