openvm_rv32im_circuit/extension/
mod.rs

1use std::sync::Arc;
2
3use derive_more::derive::From;
4use openvm_circuit::{
5    arch::{
6        AirInventory, AirInventoryError, ChipInventory, ChipInventoryError, ExecutionBridge,
7        ExecutorInventoryBuilder, ExecutorInventoryError, RowMajorMatrixArena, VmCircuitExtension,
8        VmExecutionExtension, VmProverExtension,
9    },
10    system::{memory::SharedMemoryHelper, SystemPort},
11};
12use openvm_circuit_derive::AnyEnum;
13use openvm_circuit_primitives::{
14    bitwise_op_lookup::{
15        BitwiseOperationLookupAir, BitwiseOperationLookupBus, BitwiseOperationLookupChip,
16        SharedBitwiseOperationLookupChip,
17    },
18    range_tuple::{
19        RangeTupleCheckerAir, RangeTupleCheckerBus, RangeTupleCheckerChip,
20        SharedRangeTupleCheckerChip,
21    },
22};
23use openvm_cpu_backend::{CpuBackend, CpuDevice};
24use openvm_instructions::{program::DEFAULT_PC_STEP, LocalOpcode, PhantomDiscriminant};
25use openvm_rv32im_transpiler::{
26    BaseAluOpcode, BranchEqualOpcode, BranchLessThanOpcode, DivRemOpcode, LessThanOpcode,
27    MulHOpcode, MulOpcode, Rv32AuipcOpcode, Rv32HintStoreOpcode, Rv32JalLuiOpcode, Rv32JalrOpcode,
28    Rv32LoadStoreOpcode, Rv32Phantom, ShiftOpcode,
29};
30use openvm_stark_backend::{p3_field::PrimeField32, StarkEngine, StarkProtocolConfig, Val};
31use serde::{Deserialize, Serialize};
32use strum::IntoEnumIterator;
33
34use crate::{adapters::*, *};
35
36cfg_if::cfg_if! {
37    if #[cfg(feature = "cuda")] {
38        mod cuda;
39        pub use cuda::{
40            Rv32ImGpuProverExt as Rv32ImGpuProverExt,
41        };
42    } else {
43        pub use self::{
44            Rv32ImCpuProverExt as Rv32ImProverExt,
45        };
46    }
47}
48
49// ============ Extension Struct Definitions ============
50
51/// RISC-V 32-bit Base (RV32I) Extension
52#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
53pub struct Rv32I;
54
55/// RISC-V Extension for handling IO (not to be confused with I base extension)
56#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
57pub struct Rv32Io;
58
59/// RISC-V 32-bit Multiplication Extension (RV32M) Extension
60#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
61pub struct Rv32M {
62    #[serde(default = "default_range_tuple_checker_sizes")]
63    pub range_tuple_checker_sizes: [u32; 2],
64}
65
66impl Default for Rv32M {
67    fn default() -> Self {
68        Self {
69            range_tuple_checker_sizes: default_range_tuple_checker_sizes(),
70        }
71    }
72}
73
74fn default_range_tuple_checker_sizes() -> [u32; 2] {
75    [1 << 8, 8 * (1 << 8)]
76}
77
78// ============ Executor and Periphery Enums for Extension ============
79
80/// RISC-V 32-bit Base (RV32I) Instruction Executors
81// ITS THIS DERIVES FAULT; not supporting aot traits?
82#[derive(Clone, From, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)]
83#[cfg_attr(
84    feature = "aot",
85    derive(
86        openvm_circuit_derive::AotExecutor,
87        openvm_circuit_derive::AotMeteredExecutor
88    )
89)]
90pub enum Rv32IExecutor {
91    // Rv32 (for standard 32-bit integers):
92    BaseAlu(Rv32BaseAluExecutor),
93    LessThan(Rv32LessThanExecutor),
94    Shift(Rv32ShiftExecutor),
95    LoadStore(Rv32LoadStoreExecutor),
96    LoadSignExtend(Rv32LoadSignExtendExecutor),
97    BranchEqual(Rv32BranchEqualExecutor),
98    BranchLessThan(Rv32BranchLessThanExecutor),
99    JalLui(Rv32JalLuiExecutor),
100    Jalr(Rv32JalrExecutor),
101    Auipc(Rv32AuipcExecutor),
102}
103
104/// RISC-V 32-bit Multiplication Extension (RV32M) Instruction Executors
105#[derive(Clone, From, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)]
106#[cfg_attr(
107    feature = "aot",
108    derive(
109        openvm_circuit_derive::AotExecutor,
110        openvm_circuit_derive::AotMeteredExecutor
111    )
112)]
113pub enum Rv32MExecutor {
114    Multiplication(Rv32MultiplicationExecutor),
115    MultiplicationHigh(Rv32MulHExecutor),
116    DivRem(Rv32DivRemExecutor),
117}
118
119/// RISC-V 32-bit Io Instruction Executors
120#[derive(Clone, Copy, From, AnyEnum, Executor, MeteredExecutor, PreflightExecutor)]
121#[cfg_attr(
122    feature = "aot",
123    derive(
124        openvm_circuit_derive::AotExecutor,
125        openvm_circuit_derive::AotMeteredExecutor
126    )
127)]
128pub enum Rv32IoExecutor {
129    HintStore(Rv32HintStoreExecutor),
130}
131
132// ============ VmExtension Implementations ============
133
134impl<F: PrimeField32> VmExecutionExtension<F> for Rv32I {
135    type Executor = Rv32IExecutor;
136
137    fn extend_execution(
138        &self,
139        inventory: &mut ExecutorInventoryBuilder<F, Rv32IExecutor>,
140    ) -> Result<(), ExecutorInventoryError> {
141        let pointer_max_bits = inventory.pointer_max_bits();
142
143        let base_alu =
144            Rv32BaseAluExecutor::new(Rv32BaseAluAdapterExecutor, BaseAluOpcode::CLASS_OFFSET);
145        inventory.add_executor(base_alu, BaseAluOpcode::iter().map(|x| x.global_opcode()))?;
146
147        let lt = LessThanExecutor::new(Rv32BaseAluAdapterExecutor, LessThanOpcode::CLASS_OFFSET);
148        inventory.add_executor(lt, LessThanOpcode::iter().map(|x| x.global_opcode()))?;
149
150        let shift = ShiftExecutor::new(Rv32BaseAluAdapterExecutor, ShiftOpcode::CLASS_OFFSET);
151        inventory.add_executor(shift, ShiftOpcode::iter().map(|x| x.global_opcode()))?;
152
153        let load_store = LoadStoreExecutor::new(
154            Rv32LoadStoreAdapterExecutor::new(pointer_max_bits),
155            Rv32LoadStoreOpcode::CLASS_OFFSET,
156        );
157        inventory.add_executor(
158            load_store,
159            Rv32LoadStoreOpcode::iter()
160                .take(Rv32LoadStoreOpcode::STOREB as usize + 1)
161                .map(|x| x.global_opcode()),
162        )?;
163
164        let load_sign_extend =
165            LoadSignExtendExecutor::new(Rv32LoadStoreAdapterExecutor::new(pointer_max_bits));
166        inventory.add_executor(
167            load_sign_extend,
168            [Rv32LoadStoreOpcode::LOADB, Rv32LoadStoreOpcode::LOADH].map(|x| x.global_opcode()),
169        )?;
170
171        let beq = BranchEqualExecutor::new(
172            Rv32BranchAdapterExecutor,
173            BranchEqualOpcode::CLASS_OFFSET,
174            DEFAULT_PC_STEP,
175        );
176        inventory.add_executor(beq, BranchEqualOpcode::iter().map(|x| x.global_opcode()))?;
177
178        let blt = BranchLessThanExecutor::new(
179            Rv32BranchAdapterExecutor,
180            BranchLessThanOpcode::CLASS_OFFSET,
181        );
182        inventory.add_executor(blt, BranchLessThanOpcode::iter().map(|x| x.global_opcode()))?;
183
184        let jal_lui = Rv32JalLuiExecutor::new(Rv32CondRdWriteAdapterExecutor::new(
185            Rv32RdWriteAdapterExecutor,
186        ));
187        inventory.add_executor(jal_lui, Rv32JalLuiOpcode::iter().map(|x| x.global_opcode()))?;
188
189        let jalr = Rv32JalrExecutor::new(Rv32JalrAdapterExecutor);
190        inventory.add_executor(jalr, Rv32JalrOpcode::iter().map(|x| x.global_opcode()))?;
191
192        let auipc = Rv32AuipcExecutor::new(Rv32RdWriteAdapterExecutor);
193        inventory.add_executor(auipc, Rv32AuipcOpcode::iter().map(|x| x.global_opcode()))?;
194
195        // There is no downside to adding phantom sub-executors, so we do it in the base extension.
196        inventory.add_phantom_sub_executor(
197            phantom::Rv32HintInputSubEx,
198            PhantomDiscriminant(Rv32Phantom::HintInput as u16),
199        )?;
200        inventory.add_phantom_sub_executor(
201            phantom::Rv32HintRandomSubEx,
202            PhantomDiscriminant(Rv32Phantom::HintRandom as u16),
203        )?;
204        inventory.add_phantom_sub_executor(
205            phantom::Rv32PrintStrSubEx,
206            PhantomDiscriminant(Rv32Phantom::PrintStr as u16),
207        )?;
208
209        Ok(())
210    }
211}
212
213impl<SC: StarkProtocolConfig> VmCircuitExtension<SC> for Rv32I {
214    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
215        let SystemPort {
216            execution_bus,
217            program_bus,
218            memory_bridge,
219        } = inventory.system().port();
220
221        let exec_bridge = ExecutionBridge::new(execution_bus, program_bus);
222        let range_checker = inventory.range_checker().bus;
223        let pointer_max_bits = inventory.pointer_max_bits();
224
225        let bitwise_lu = {
226            // A trick to get around Rust's borrow rules
227            let existing_air = inventory.find_air::<BitwiseOperationLookupAir<8>>().next();
228            if let Some(air) = existing_air {
229                air.bus
230            } else {
231                let bus = BitwiseOperationLookupBus::new(inventory.new_bus_idx());
232                let air = BitwiseOperationLookupAir::<8>::new(bus);
233                inventory.add_air(air);
234                air.bus
235            }
236        };
237
238        let base_alu = Rv32BaseAluAir::new(
239            Rv32BaseAluAdapterAir::new(exec_bridge, memory_bridge, bitwise_lu),
240            BaseAluCoreAir::new(bitwise_lu, BaseAluOpcode::CLASS_OFFSET),
241        );
242        inventory.add_air(base_alu);
243
244        let lt = Rv32LessThanAir::new(
245            Rv32BaseAluAdapterAir::new(exec_bridge, memory_bridge, bitwise_lu),
246            LessThanCoreAir::new(bitwise_lu, LessThanOpcode::CLASS_OFFSET),
247        );
248        inventory.add_air(lt);
249
250        let shift = Rv32ShiftAir::new(
251            Rv32BaseAluAdapterAir::new(exec_bridge, memory_bridge, bitwise_lu),
252            ShiftCoreAir::new(bitwise_lu, range_checker, ShiftOpcode::CLASS_OFFSET),
253        );
254        inventory.add_air(shift);
255
256        let load_store = Rv32LoadStoreAir::new(
257            Rv32LoadStoreAdapterAir::new(
258                memory_bridge,
259                exec_bridge,
260                range_checker,
261                pointer_max_bits,
262            ),
263            LoadStoreCoreAir::new(Rv32LoadStoreOpcode::CLASS_OFFSET),
264        );
265        inventory.add_air(load_store);
266
267        let load_sign_extend = Rv32LoadSignExtendAir::new(
268            Rv32LoadStoreAdapterAir::new(
269                memory_bridge,
270                exec_bridge,
271                range_checker,
272                pointer_max_bits,
273            ),
274            LoadSignExtendCoreAir::new(range_checker),
275        );
276        inventory.add_air(load_sign_extend);
277
278        let beq = Rv32BranchEqualAir::new(
279            Rv32BranchAdapterAir::new(exec_bridge, memory_bridge),
280            BranchEqualCoreAir::new(BranchEqualOpcode::CLASS_OFFSET, DEFAULT_PC_STEP),
281        );
282        inventory.add_air(beq);
283
284        let blt = Rv32BranchLessThanAir::new(
285            Rv32BranchAdapterAir::new(exec_bridge, memory_bridge),
286            BranchLessThanCoreAir::new(bitwise_lu, BranchLessThanOpcode::CLASS_OFFSET),
287        );
288        inventory.add_air(blt);
289
290        let jal_lui = Rv32JalLuiAir::new(
291            Rv32CondRdWriteAdapterAir::new(Rv32RdWriteAdapterAir::new(memory_bridge, exec_bridge)),
292            Rv32JalLuiCoreAir::new(bitwise_lu),
293        );
294        inventory.add_air(jal_lui);
295
296        let jalr = Rv32JalrAir::new(
297            Rv32JalrAdapterAir::new(memory_bridge, exec_bridge),
298            Rv32JalrCoreAir::new(bitwise_lu, range_checker),
299        );
300        inventory.add_air(jalr);
301
302        let auipc = Rv32AuipcAir::new(
303            Rv32RdWriteAdapterAir::new(memory_bridge, exec_bridge),
304            Rv32AuipcCoreAir::new(bitwise_lu),
305        );
306        inventory.add_air(auipc);
307
308        Ok(())
309    }
310}
311
312pub struct Rv32ImCpuProverExt;
313// This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker,
314// BitwiseOperationLookupChip) are specific to CpuBackend.
315impl<SC, E, RA> VmProverExtension<E, RA, Rv32I> for Rv32ImCpuProverExt
316where
317    SC: StarkProtocolConfig,
318    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
319    RA: RowMajorMatrixArena<Val<SC>>,
320    Val<SC>: PrimeField32,
321    SC::EF: Ord,
322{
323    fn extend_prover(
324        &self,
325        _: &Rv32I,
326        inventory: &mut ChipInventory<SC, RA, CpuBackend<SC>>,
327    ) -> Result<(), ChipInventoryError> {
328        let range_checker = inventory.range_checker()?.clone();
329        let timestamp_max_bits = inventory.timestamp_max_bits();
330        let pointer_max_bits = inventory.airs().pointer_max_bits();
331        let mem_helper = SharedMemoryHelper::new(range_checker.clone(), timestamp_max_bits);
332
333        let bitwise_lu = {
334            let existing_chip = inventory
335                .find_chip::<SharedBitwiseOperationLookupChip<8>>()
336                .next();
337            if let Some(chip) = existing_chip {
338                chip.clone()
339            } else {
340                let air: &BitwiseOperationLookupAir<8> = inventory.next_air()?;
341                let chip = Arc::new(BitwiseOperationLookupChip::new(air.bus));
342                inventory.add_periphery_chip(chip.clone());
343                chip
344            }
345        };
346
347        // These calls to next_air are not strictly necessary to construct the chips, but provide a
348        // safeguard to ensure that chip construction matches the circuit definition
349        inventory.next_air::<Rv32BaseAluAir>()?;
350        let base_alu = Rv32BaseAluChip::new(
351            BaseAluFiller::new(
352                Rv32BaseAluAdapterFiller::new(bitwise_lu.clone()),
353                bitwise_lu.clone(),
354                BaseAluOpcode::CLASS_OFFSET,
355            ),
356            mem_helper.clone(),
357        );
358        inventory.add_executor_chip(base_alu);
359
360        inventory.next_air::<Rv32LessThanAir>()?;
361        let lt = Rv32LessThanChip::new(
362            LessThanFiller::new(
363                Rv32BaseAluAdapterFiller::new(bitwise_lu.clone()),
364                bitwise_lu.clone(),
365                LessThanOpcode::CLASS_OFFSET,
366            ),
367            mem_helper.clone(),
368        );
369        inventory.add_executor_chip(lt);
370
371        inventory.next_air::<Rv32ShiftAir>()?;
372        let shift = Rv32ShiftChip::new(
373            ShiftFiller::new(
374                Rv32BaseAluAdapterFiller::new(bitwise_lu.clone()),
375                bitwise_lu.clone(),
376                range_checker.clone(),
377                ShiftOpcode::CLASS_OFFSET,
378            ),
379            mem_helper.clone(),
380        );
381        inventory.add_executor_chip(shift);
382
383        inventory.next_air::<Rv32LoadStoreAir>()?;
384        let load_store_chip = Rv32LoadStoreChip::new(
385            LoadStoreFiller::new(
386                Rv32LoadStoreAdapterFiller::new(pointer_max_bits, range_checker.clone()),
387                Rv32LoadStoreOpcode::CLASS_OFFSET,
388            ),
389            mem_helper.clone(),
390        );
391        inventory.add_executor_chip(load_store_chip);
392
393        inventory.next_air::<Rv32LoadSignExtendAir>()?;
394        let load_sign_extend = Rv32LoadSignExtendChip::new(
395            LoadSignExtendFiller::new(
396                Rv32LoadStoreAdapterFiller::new(pointer_max_bits, range_checker.clone()),
397                range_checker.clone(),
398            ),
399            mem_helper.clone(),
400        );
401        inventory.add_executor_chip(load_sign_extend);
402
403        inventory.next_air::<Rv32BranchEqualAir>()?;
404        let beq = Rv32BranchEqualChip::new(
405            BranchEqualFiller::new(
406                Rv32BranchAdapterFiller,
407                BranchEqualOpcode::CLASS_OFFSET,
408                DEFAULT_PC_STEP,
409            ),
410            mem_helper.clone(),
411        );
412        inventory.add_executor_chip(beq);
413
414        inventory.next_air::<Rv32BranchLessThanAir>()?;
415        let blt = Rv32BranchLessThanChip::new(
416            BranchLessThanFiller::new(
417                Rv32BranchAdapterFiller,
418                bitwise_lu.clone(),
419                BranchLessThanOpcode::CLASS_OFFSET,
420            ),
421            mem_helper.clone(),
422        );
423        inventory.add_executor_chip(blt);
424
425        inventory.next_air::<Rv32JalLuiAir>()?;
426        let jal_lui = Rv32JalLuiChip::new(
427            Rv32JalLuiFiller::new(
428                Rv32CondRdWriteAdapterFiller::new(Rv32RdWriteAdapterFiller),
429                bitwise_lu.clone(),
430            ),
431            mem_helper.clone(),
432        );
433        inventory.add_executor_chip(jal_lui);
434
435        inventory.next_air::<Rv32JalrAir>()?;
436        let jalr = Rv32JalrChip::new(
437            Rv32JalrFiller::new(
438                Rv32JalrAdapterFiller,
439                bitwise_lu.clone(),
440                range_checker.clone(),
441            ),
442            mem_helper.clone(),
443        );
444        inventory.add_executor_chip(jalr);
445
446        inventory.next_air::<Rv32AuipcAir>()?;
447        let auipc = Rv32AuipcChip::new(
448            Rv32AuipcFiller::new(Rv32RdWriteAdapterFiller, bitwise_lu.clone()),
449            mem_helper.clone(),
450        );
451        inventory.add_executor_chip(auipc);
452
453        Ok(())
454    }
455}
456
457impl<F> VmExecutionExtension<F> for Rv32M {
458    type Executor = Rv32MExecutor;
459
460    fn extend_execution(
461        &self,
462        inventory: &mut ExecutorInventoryBuilder<F, Rv32MExecutor>,
463    ) -> Result<(), ExecutorInventoryError> {
464        let mult =
465            Rv32MultiplicationExecutor::new(Rv32MultAdapterExecutor, MulOpcode::CLASS_OFFSET);
466        inventory.add_executor(mult, MulOpcode::iter().map(|x| x.global_opcode()))?;
467
468        let mul_h = Rv32MulHExecutor::new(Rv32MultAdapterExecutor, MulHOpcode::CLASS_OFFSET);
469        inventory.add_executor(mul_h, MulHOpcode::iter().map(|x| x.global_opcode()))?;
470
471        let div_rem = Rv32DivRemExecutor::new(Rv32MultAdapterExecutor, DivRemOpcode::CLASS_OFFSET);
472        inventory.add_executor(div_rem, DivRemOpcode::iter().map(|x| x.global_opcode()))?;
473
474        Ok(())
475    }
476}
477
478impl<SC: StarkProtocolConfig> VmCircuitExtension<SC> for Rv32M {
479    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
480        let SystemPort {
481            execution_bus,
482            program_bus,
483            memory_bridge,
484        } = inventory.system().port();
485        let exec_bridge = ExecutionBridge::new(execution_bus, program_bus);
486
487        let bitwise_lu = {
488            let existing_air = inventory.find_air::<BitwiseOperationLookupAir<8>>().next();
489            if let Some(air) = existing_air {
490                air.bus
491            } else {
492                let bus = BitwiseOperationLookupBus::new(inventory.new_bus_idx());
493                let air = BitwiseOperationLookupAir::<8>::new(bus);
494                inventory.add_air(air);
495                air.bus
496            }
497        };
498
499        let range_tuple_checker = {
500            let existing_air = inventory.find_air::<RangeTupleCheckerAir<2>>().find(|c| {
501                c.bus.sizes[0] >= self.range_tuple_checker_sizes[0]
502                    && c.bus.sizes[1] >= self.range_tuple_checker_sizes[1]
503            });
504            if let Some(air) = existing_air {
505                air.bus
506            } else {
507                let bus = RangeTupleCheckerBus::new(
508                    inventory.new_bus_idx(),
509                    self.range_tuple_checker_sizes,
510                );
511                let air = RangeTupleCheckerAir { bus };
512                inventory.add_air(air);
513                air.bus
514            }
515        };
516
517        let mult = Rv32MultiplicationAir::new(
518            Rv32MultAdapterAir::new(exec_bridge, memory_bridge),
519            MultiplicationCoreAir::new(range_tuple_checker, MulOpcode::CLASS_OFFSET),
520        );
521        inventory.add_air(mult);
522
523        let mul_h = Rv32MulHAir::new(
524            Rv32MultAdapterAir::new(exec_bridge, memory_bridge),
525            MulHCoreAir::new(bitwise_lu, range_tuple_checker),
526        );
527        inventory.add_air(mul_h);
528
529        let div_rem = Rv32DivRemAir::new(
530            Rv32MultAdapterAir::new(exec_bridge, memory_bridge),
531            DivRemCoreAir::new(bitwise_lu, range_tuple_checker, DivRemOpcode::CLASS_OFFSET),
532        );
533        inventory.add_air(div_rem);
534
535        Ok(())
536    }
537}
538
539// This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker,
540// BitwiseOperationLookupChip) are specific to CpuBackend.
541impl<SC, E, RA> VmProverExtension<E, RA, Rv32M> for Rv32ImCpuProverExt
542where
543    SC: StarkProtocolConfig,
544    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
545    RA: RowMajorMatrixArena<Val<SC>>,
546    Val<SC>: PrimeField32,
547    SC::EF: Ord,
548{
549    fn extend_prover(
550        &self,
551        extension: &Rv32M,
552        inventory: &mut ChipInventory<SC, RA, CpuBackend<SC>>,
553    ) -> Result<(), ChipInventoryError> {
554        let range_checker = inventory.range_checker()?.clone();
555        let timestamp_max_bits = inventory.timestamp_max_bits();
556        let mem_helper = SharedMemoryHelper::new(range_checker.clone(), timestamp_max_bits);
557
558        let bitwise_lu = {
559            let existing_chip = inventory
560                .find_chip::<SharedBitwiseOperationLookupChip<8>>()
561                .next();
562            if let Some(chip) = existing_chip {
563                chip.clone()
564            } else {
565                let air: &BitwiseOperationLookupAir<8> = inventory.next_air()?;
566                let chip = Arc::new(BitwiseOperationLookupChip::new(air.bus));
567                inventory.add_periphery_chip(chip.clone());
568                chip
569            }
570        };
571
572        let range_tuple_checker = {
573            let existing_chip = inventory
574                .find_chip::<SharedRangeTupleCheckerChip<2>>()
575                .find(|c| {
576                    c.bus().sizes[0] >= extension.range_tuple_checker_sizes[0]
577                        && c.bus().sizes[1] >= extension.range_tuple_checker_sizes[1]
578                });
579            if let Some(chip) = existing_chip {
580                chip.clone()
581            } else {
582                let air: &RangeTupleCheckerAir<2> = inventory.next_air()?;
583                let chip = SharedRangeTupleCheckerChip::new(RangeTupleCheckerChip::new(air.bus));
584                inventory.add_periphery_chip(chip.clone());
585                chip
586            }
587        };
588
589        // These calls to next_air are not strictly necessary to construct the chips, but provide a
590        // safeguard to ensure that chip construction matches the circuit definition
591        inventory.next_air::<Rv32MultiplicationAir>()?;
592        let mult = Rv32MultiplicationChip::new(
593            MultiplicationFiller::new(
594                Rv32MultAdapterFiller,
595                range_tuple_checker.clone(),
596                MulOpcode::CLASS_OFFSET,
597            ),
598            mem_helper.clone(),
599        );
600        inventory.add_executor_chip(mult);
601
602        inventory.next_air::<Rv32MulHAir>()?;
603        let mul_h = Rv32MulHChip::new(
604            MulHFiller::new(
605                Rv32MultAdapterFiller,
606                bitwise_lu.clone(),
607                range_tuple_checker.clone(),
608            ),
609            mem_helper.clone(),
610        );
611        inventory.add_executor_chip(mul_h);
612
613        inventory.next_air::<Rv32DivRemAir>()?;
614        let div_rem = Rv32DivRemChip::new(
615            DivRemFiller::new(
616                Rv32MultAdapterFiller,
617                bitwise_lu.clone(),
618                range_tuple_checker.clone(),
619                DivRemOpcode::CLASS_OFFSET,
620            ),
621            mem_helper.clone(),
622        );
623        inventory.add_executor_chip(div_rem);
624
625        Ok(())
626    }
627}
628
629impl<F> VmExecutionExtension<F> for Rv32Io {
630    type Executor = Rv32IoExecutor;
631
632    fn extend_execution(
633        &self,
634        inventory: &mut ExecutorInventoryBuilder<F, Rv32IoExecutor>,
635    ) -> Result<(), ExecutorInventoryError> {
636        let pointer_max_bits = inventory.pointer_max_bits();
637        let hint_store =
638            Rv32HintStoreExecutor::new(pointer_max_bits, Rv32HintStoreOpcode::CLASS_OFFSET);
639        inventory.add_executor(
640            hint_store,
641            Rv32HintStoreOpcode::iter().map(|x| x.global_opcode()),
642        )?;
643
644        Ok(())
645    }
646}
647
648impl<SC: StarkProtocolConfig> VmCircuitExtension<SC> for Rv32Io {
649    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
650        let SystemPort {
651            execution_bus,
652            program_bus,
653            memory_bridge,
654        } = inventory.system().port();
655
656        let exec_bridge = ExecutionBridge::new(execution_bus, program_bus);
657        let pointer_max_bits = inventory.pointer_max_bits();
658
659        let bitwise_lu = {
660            let existing_air = inventory.find_air::<BitwiseOperationLookupAir<8>>().next();
661            if let Some(air) = existing_air {
662                air.bus
663            } else {
664                let bus = BitwiseOperationLookupBus::new(inventory.new_bus_idx());
665                let air = BitwiseOperationLookupAir::<8>::new(bus);
666                inventory.add_air(air);
667                air.bus
668            }
669        };
670
671        let hint_store = Rv32HintStoreAir::new(
672            exec_bridge,
673            memory_bridge,
674            bitwise_lu,
675            Rv32HintStoreOpcode::CLASS_OFFSET,
676            pointer_max_bits,
677        );
678        inventory.add_air(hint_store);
679
680        Ok(())
681    }
682}
683
684// This implementation is specific to CpuBackend because the lookup chips (VariableRangeChecker,
685// BitwiseOperationLookupChip) are specific to CpuBackend.
686impl<SC, E, RA> VmProverExtension<E, RA, Rv32Io> for Rv32ImCpuProverExt
687where
688    SC: StarkProtocolConfig,
689    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
690    RA: RowMajorMatrixArena<Val<SC>>,
691    Val<SC>: PrimeField32,
692    SC::EF: Ord,
693{
694    fn extend_prover(
695        &self,
696        _: &Rv32Io,
697        inventory: &mut ChipInventory<SC, RA, CpuBackend<SC>>,
698    ) -> Result<(), ChipInventoryError> {
699        let range_checker = inventory.range_checker()?.clone();
700        let timestamp_max_bits = inventory.timestamp_max_bits();
701        let mem_helper = SharedMemoryHelper::new(range_checker.clone(), timestamp_max_bits);
702        let pointer_max_bits = inventory.airs().pointer_max_bits();
703
704        let bitwise_lu = {
705            let existing_chip = inventory
706                .find_chip::<SharedBitwiseOperationLookupChip<8>>()
707                .next();
708            if let Some(chip) = existing_chip {
709                chip.clone()
710            } else {
711                let air: &BitwiseOperationLookupAir<8> = inventory.next_air()?;
712                let chip = Arc::new(BitwiseOperationLookupChip::new(air.bus));
713                inventory.add_periphery_chip(chip.clone());
714                chip
715            }
716        };
717
718        inventory.next_air::<Rv32HintStoreAir>()?;
719        let hint_store = Rv32HintStoreChip::new(
720            Rv32HintStoreFiller::new(pointer_max_bits, bitwise_lu.clone()),
721            mem_helper.clone(),
722        );
723        inventory.add_executor_chip(hint_store);
724
725        Ok(())
726    }
727}
728
729/// Phantom sub-executors
730mod phantom {
731    use eyre::bail;
732    use openvm_circuit::{
733        arch::{PhantomSubExecutor, Streams},
734        system::memory::online::GuestMemory,
735    };
736    use openvm_instructions::PhantomDiscriminant;
737    use openvm_stark_backend::p3_field::{Field, PrimeField32};
738    use rand::{rngs::StdRng, Rng};
739
740    use crate::adapters::{memory_read, read_rv32_register};
741
742    pub struct Rv32HintInputSubEx;
743    pub struct Rv32HintRandomSubEx;
744    pub struct Rv32PrintStrSubEx;
745
746    impl<F: Field> PhantomSubExecutor<F> for Rv32HintInputSubEx {
747        fn phantom_execute(
748            &self,
749            _: &GuestMemory,
750            streams: &mut Streams<F>,
751            _: &mut StdRng,
752            _: PhantomDiscriminant,
753            _: u32,
754            _: u32,
755            _: u16,
756        ) -> eyre::Result<()> {
757            let mut hint = match streams.input_stream.pop_front() {
758                Some(hint) => hint,
759                None => {
760                    bail!("EndOfInputStream");
761                }
762            };
763            streams.hint_stream.clear();
764            streams.hint_stream.extend(
765                (hint.len() as u32)
766                    .to_le_bytes()
767                    .iter()
768                    .map(|b| F::from_u8(*b)),
769            );
770            // Extend by 0 for 4 byte alignment
771            let capacity = hint.len().div_ceil(4) * 4;
772            hint.resize(capacity, F::ZERO);
773            streams.hint_stream.extend(hint);
774            Ok(())
775        }
776    }
777
778    impl<F: PrimeField32> PhantomSubExecutor<F> for Rv32HintRandomSubEx {
779        fn phantom_execute(
780            &self,
781            memory: &GuestMemory,
782            streams: &mut Streams<F>,
783            rng: &mut StdRng,
784            _: PhantomDiscriminant,
785            a: u32,
786            _: u32,
787            _: u16,
788        ) -> eyre::Result<()> {
789            static WARN_ONCE: std::sync::Once = std::sync::Once::new();
790            WARN_ONCE.call_once(|| {
791                eprintln!("WARNING: Using fixed-seed RNG for deterministic randomness. Consider security implications for your use case.");
792            });
793
794            let len = read_rv32_register(memory, a) as usize;
795            streams.hint_stream.clear();
796            streams
797                .hint_stream
798                .extend(std::iter::repeat_with(|| F::from_u8(rng.random::<u8>())).take(len * 4));
799            Ok(())
800        }
801    }
802
803    impl<F: PrimeField32> PhantomSubExecutor<F> for Rv32PrintStrSubEx {
804        fn phantom_execute(
805            &self,
806            memory: &GuestMemory,
807            _: &mut Streams<F>,
808            _: &mut StdRng,
809            _: PhantomDiscriminant,
810            a: u32,
811            b: u32,
812            _: u16,
813        ) -> eyre::Result<()> {
814            let rd = read_rv32_register(memory, a);
815            let rs1 = read_rv32_register(memory, b);
816            let bytes = (0..rs1)
817                .map(|i| memory_read::<1>(memory, 2, rd + i)[0])
818                .collect::<Vec<u8>>();
819            let peeked_str = String::from_utf8(bytes)?;
820            print!("{peeked_str}");
821            Ok(())
822        }
823    }
824}