openvm_circuit/arch/
extensions.rs

1//! Traits and builders to compose collections of chips into a virtual machine.
2//!
3//! A full VM extension consists of three components, represented by sub-traits:
4//! - [VmExecutionExtension]
5//! - [VmCircuitExtension]
6//! - [VmProverExtension]: there may be multiple implementations of `VmProverExtension` for the same
7//!   `VmCircuitExtension` for different prover backends.
8//!
9//! It is intended that `VmExecutionExtension` and `VmCircuitExtension` are implemented on the
10//! same struct and `VmProverExtension` is implemented on a separate struct (usually a ZST) to
11//! get around Rust orphan rules.
12use std::{
13    any::{type_name, Any},
14    iter::{self, zip},
15    sync::Arc,
16};
17
18use getset::{CopyGetters, Getters};
19use openvm_circuit_primitives::var_range::{
20    SharedVariableRangeCheckerChip, VariableRangeCheckerAir,
21};
22use openvm_instructions::{PhantomDiscriminant, VmOpcode};
23use openvm_stark_backend::{
24    config::{StarkGenericConfig, Val},
25    engine::StarkEngine,
26    interaction::BusIndex,
27    keygen::types::MultiStarkProvingKey,
28    prover::{
29        cpu::CpuBackend,
30        hal::ProverBackend,
31        types::{AirProvingContext, ProvingContext},
32    },
33    rap::AnyRap,
34    AirRef, AnyChip, Chip,
35};
36use rustc_hash::FxHashMap;
37use tracing::info_span;
38
39use super::{GenerationError, PhantomSubExecutor, SystemConfig};
40use crate::{
41    arch::Arena,
42    system::{
43        memory::{BOUNDARY_AIR_OFFSET, MERKLE_AIR_OFFSET},
44        phantom::PhantomExecutor,
45        SystemAirInventory, SystemChipComplex, SystemRecords,
46    },
47};
48
49/// Global AIR ID in the VM circuit verifying key.
50pub const PROGRAM_AIR_ID: usize = 0;
51/// ProgramAir is the first AIR so its cached trace should be the first main trace.
52pub const PROGRAM_CACHED_TRACE_INDEX: usize = 0;
53pub const CONNECTOR_AIR_ID: usize = 1;
54/// If PublicValuesAir is **enabled**, its AIR ID is 2. PublicValuesAir is always disabled when
55/// continuations is enabled.
56pub const PUBLIC_VALUES_AIR_ID: usize = 2;
57/// AIR ID of the Memory Boundary AIR.
58pub const BOUNDARY_AIR_ID: usize = PUBLIC_VALUES_AIR_ID + 1 + BOUNDARY_AIR_OFFSET;
59/// If VM has continuations enabled, all AIRs of MemoryController are added after ConnectorChip.
60/// Merkle AIR commits start/final memory states.
61pub const MERKLE_AIR_ID: usize = CONNECTOR_AIR_ID + 1 + MERKLE_AIR_OFFSET;
62
63pub type ExecutorId = u32;
64
65// ======================= VM Extension Traits =============================
66
67/// Extension of VM execution. Allows registration of custom execution of new instructions by
68/// opcode.
69pub trait VmExecutionExtension<F> {
70    /// Enum of executor variants
71    type Executor: AnyEnum;
72
73    fn extend_execution(
74        &self,
75        inventory: &mut ExecutorInventoryBuilder<F, Self::Executor>,
76    ) -> Result<(), ExecutorInventoryError>;
77}
78
79/// Extension of the VM circuit. Allows _in-order_ addition of new AIRs with interactions.
80pub trait VmCircuitExtension<SC: StarkGenericConfig> {
81    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError>;
82}
83
84/// Extension of VM trace generation. The generics are `E` for [StarkEngine], `RA` for record arena,
85/// and `EXT` for execution and circuit extension.
86///
87/// Note that this trait differs from [VmExecutionExtension] and [VmCircuitExtension]. This trait is
88/// meant to be implemented on a separate ZST which may be different for different [ProverBackend]s.
89/// This is done to get around Rust orphan rules.
90pub trait VmProverExtension<E, RA, EXT>
91where
92    E: StarkEngine,
93    EXT: VmExecutionExtension<Val<E::SC>> + VmCircuitExtension<E::SC>,
94{
95    /// The chips added to `inventory` should exactly match the order of AIRs in the
96    /// [VmCircuitExtension] implementation of `EXT`.
97    ///
98    /// We do not provide access to the [ExecutorInventory] because the process to find an executor
99    /// from the inventory seems more cumbersome than to simply re-construct any necessary executors
100    /// directly within this function implementation.
101    fn extend_prover(
102        &self,
103        extension: &EXT,
104        inventory: &mut ChipInventory<E::SC, RA, E::PB>,
105    ) -> Result<(), ChipInventoryError>;
106}
107
108// ======================= Different Inventory Struct Definitions =============================
109
110pub struct ExecutorInventory<E> {
111    config: SystemConfig,
112    /// Lookup table to executor ID.
113    /// This is stored in a hashmap because it is _not_ expected to be used in the hot path.
114    /// A direct opcode -> executor mapping should be generated before runtime execution.
115    pub instruction_lookup: FxHashMap<VmOpcode, ExecutorId>,
116    pub executors: Vec<E>,
117    /// `ext_start[i]` will have the starting index in `executors` for extension `i`
118    ext_start: Vec<usize>,
119}
120
121// @dev: We need ExecutorInventoryBuilder separate from ExecutorInventory because of how
122// ExecutorInventory::extend works: we want to build an inventory with some big E3 enum that
123// includes both enum types E1, E2. However the interface for an ExecutionExtension will only know
124// about the enum E2. In order to be able to allow access to the old executors with type E1 without
125// referring to the type E1, we need to create this separate builder struct.
126pub struct ExecutorInventoryBuilder<'a, F, E> {
127    /// Chips that are already included in the chipset and may be used
128    /// as dependencies. The order should be that depended-on chips are ordered
129    /// **before** their dependents.
130    old_executors: Vec<&'a dyn AnyEnum>,
131    new_inventory: ExecutorInventory<E>,
132    phantom_executors: FxHashMap<PhantomDiscriminant, Arc<dyn PhantomSubExecutor<F>>>,
133}
134
135#[derive(Clone, Getters, CopyGetters)]
136pub struct AirInventory<SC: StarkGenericConfig> {
137    #[get = "pub"]
138    config: SystemConfig,
139    /// The system AIRs required by the circuit architecture.
140    #[get = "pub"]
141    system: SystemAirInventory<SC>,
142    /// List of all non-system AIRs in the circuit, in insertion order, which is the **reverse** of
143    /// the order they appear in the verifying key.
144    ///
145    /// Note that the system will ensure that the first AIR in the list is always the
146    /// [VariableRangeCheckerAir].
147    #[get = "pub"]
148    ext_airs: Vec<AirRef<SC>>,
149    /// `ext_start[i]` will have the starting index in `ext_airs` for extension `i`
150    ext_start: Vec<usize>,
151
152    bus_idx_mgr: BusIndexManager,
153}
154
155#[derive(Clone, Copy, Debug, Default)]
156pub struct BusIndexManager {
157    /// All existing buses use indices in [0, bus_idx_max)
158    bus_idx_max: BusIndex,
159}
160
161// @dev: ChipInventory does not have the SystemChipComplex because that is custom depending on `PB`.
162// The full struct with SystemChipComplex is VmChipComplex
163#[derive(Getters)]
164pub struct ChipInventory<SC, RA, PB>
165where
166    SC: StarkGenericConfig,
167    PB: ProverBackend,
168{
169    /// Read-only view of AIRs, as constructed via the [VmCircuitExtension] trait.
170    #[get = "pub"]
171    airs: AirInventory<SC>,
172    /// Chips that are being built.
173    #[get = "pub"]
174    chips: Vec<Box<dyn AnyChip<RA, PB>>>,
175
176    /// Number of extensions that have chips added, including the current one that is still being
177    /// built.
178    cur_num_exts: usize,
179    /// Mapping from executor index to chip insertion index. Chips must be added in order so the
180    /// chip insertion index matches the AIR insertion index. Reminder: this is in **reverse**
181    /// order of the verifying key AIR ordering.
182    ///
183    /// Note: if public values chip exists, then it will be the first entry and point to
184    /// `usize::MAX`. This entry should never be used.
185    pub executor_idx_to_insertion_idx: Vec<usize>,
186}
187
188/// The collection of all chips in the VM. The chips should correspond 1-to-1 with the associated
189/// [AirInventory]. The [VmChipComplex] coordinates the trace generation for all chips in the VM
190/// after construction.
191#[derive(Getters)]
192pub struct VmChipComplex<SC, RA, PB, SCC>
193where
194    SC: StarkGenericConfig,
195    PB: ProverBackend,
196{
197    /// System chip complex responsible for trace generation of [SystemAirInventory]
198    pub system: SCC,
199    pub inventory: ChipInventory<SC, RA, PB>,
200}
201
202// ======================= Inventory Function Definitions =============================
203
204impl<E> ExecutorInventory<E> {
205    /// Empty inventory should be created at the start of the declaration of a new extension.
206    #[allow(clippy::new_without_default)]
207    pub fn new(config: SystemConfig) -> Self {
208        Self {
209            config,
210            instruction_lookup: Default::default(),
211            executors: Default::default(),
212            ext_start: vec![0],
213        }
214    }
215
216    /// Inserts an executor with the collection of opcodes that it handles.
217    /// If some executor already owns one of the opcodes, an error is returned with the existing
218    /// executor.
219    pub fn add_executor(
220        &mut self,
221        executor: impl Into<E>,
222        opcodes: impl IntoIterator<Item = VmOpcode>,
223    ) -> Result<(), ExecutorInventoryError> {
224        let opcodes: Vec<_> = opcodes.into_iter().collect();
225        for opcode in &opcodes {
226            if let Some(id) = self.instruction_lookup.get(opcode) {
227                return Err(ExecutorInventoryError::ExecutorExists {
228                    opcode: *opcode,
229                    id: *id,
230                });
231            }
232        }
233        let id = self.executors.len();
234        self.executors.push(executor.into());
235        for opcode in opcodes {
236            self.instruction_lookup
237                .insert(opcode, id.try_into().unwrap());
238        }
239        Ok(())
240    }
241
242    /// Extend the inventory with a new extension.
243    /// A new inventory with different type generics is returned with the combined inventory.
244    pub fn extend<F, E3, EXT>(
245        self,
246        other: &EXT,
247    ) -> Result<ExecutorInventory<E3>, ExecutorInventoryError>
248    where
249        F: 'static,
250        E: Into<E3> + AnyEnum,
251        E3: AnyEnum,
252        EXT: VmExecutionExtension<F>,
253        EXT::Executor: Into<E3>,
254    {
255        let mut builder: ExecutorInventoryBuilder<F, EXT::Executor> = self.builder();
256        other.extend_execution(&mut builder)?;
257        let other_inventory = builder.new_inventory;
258        let other_phantom_executors = builder.phantom_executors;
259        let mut inventory_ext = self.transmute();
260        inventory_ext.append(other_inventory.transmute())?;
261        let phantom_chip: &mut PhantomExecutor<F> = inventory_ext
262            .find_executor_mut()
263            .next()
264            .expect("system always has phantom chip");
265        let phantom_executors = &mut phantom_chip.phantom_executors;
266        for (discriminant, sub_executor) in other_phantom_executors {
267            if phantom_executors
268                .insert(discriminant, sub_executor)
269                .is_some()
270            {
271                return Err(ExecutorInventoryError::PhantomSubExecutorExists { discriminant });
272            }
273        }
274
275        Ok(inventory_ext)
276    }
277
278    pub fn builder<F, E2>(&self) -> ExecutorInventoryBuilder<'_, F, E2>
279    where
280        F: 'static,
281        E: AnyEnum,
282    {
283        let old_executors = self.executors.iter().map(|e| e as &dyn AnyEnum).collect();
284        ExecutorInventoryBuilder {
285            old_executors,
286            new_inventory: ExecutorInventory::new(self.config.clone()),
287            phantom_executors: Default::default(),
288        }
289    }
290
291    pub fn transmute<E2>(self) -> ExecutorInventory<E2>
292    where
293        E: Into<E2>,
294    {
295        ExecutorInventory {
296            config: self.config,
297            instruction_lookup: self.instruction_lookup,
298            executors: self.executors.into_iter().map(|e| e.into()).collect(),
299            ext_start: self.ext_start,
300        }
301    }
302
303    /// Append `other` to current inventory. This means `self` comes earlier in the dependency
304    /// chain.
305    fn append(&mut self, mut other: ExecutorInventory<E>) -> Result<(), ExecutorInventoryError> {
306        let num_executors = self.executors.len();
307        for (opcode, mut id) in other.instruction_lookup.into_iter() {
308            id = id.checked_add(num_executors.try_into().unwrap()).unwrap();
309            if let Some(old_id) = self.instruction_lookup.insert(opcode, id) {
310                return Err(ExecutorInventoryError::ExecutorExists { opcode, id: old_id });
311            }
312        }
313        for id in &mut other.ext_start {
314            *id = id.checked_add(num_executors).unwrap();
315        }
316        self.executors.append(&mut other.executors);
317        self.ext_start.append(&mut other.ext_start);
318        Ok(())
319    }
320
321    pub fn get_executor(&self, opcode: VmOpcode) -> Option<&E> {
322        let id = self.instruction_lookup.get(&opcode)?;
323        self.executors.get(*id as usize)
324    }
325
326    pub fn get_mut_executor(&mut self, opcode: &VmOpcode) -> Option<&mut E> {
327        let id = self.instruction_lookup.get(opcode)?;
328        self.executors.get_mut(*id as usize)
329    }
330
331    pub fn executors(&self) -> &[E] {
332        &self.executors
333    }
334
335    pub fn find_executor<EX: 'static>(&self) -> impl Iterator<Item = &'_ EX>
336    where
337        E: AnyEnum,
338    {
339        self.executors
340            .iter()
341            .filter_map(|e| e.as_any_kind().downcast_ref())
342    }
343
344    pub fn find_executor_mut<EX: 'static>(&mut self) -> impl Iterator<Item = &'_ mut EX>
345    where
346        E: AnyEnum,
347    {
348        self.executors
349            .iter_mut()
350            .filter_map(|e| e.as_any_kind_mut().downcast_mut())
351    }
352
353    /// Returns the system config of the inventory.
354    pub fn config(&self) -> &SystemConfig {
355        &self.config
356    }
357}
358
359impl<F, E> ExecutorInventoryBuilder<'_, F, E> {
360    pub fn add_executor(
361        &mut self,
362        executor: impl Into<E>,
363        opcodes: impl IntoIterator<Item = VmOpcode>,
364    ) -> Result<(), ExecutorInventoryError> {
365        self.new_inventory.add_executor(executor, opcodes)
366    }
367
368    pub fn add_phantom_sub_executor<PE>(
369        &mut self,
370        phantom_sub: PE,
371        discriminant: PhantomDiscriminant,
372    ) -> Result<(), ExecutorInventoryError>
373    where
374        E: AnyEnum,
375        F: 'static,
376        PE: PhantomSubExecutor<F> + 'static,
377    {
378        let existing = self
379            .phantom_executors
380            .insert(discriminant, Arc::new(phantom_sub));
381        if existing.is_some() {
382            return Err(ExecutorInventoryError::PhantomSubExecutorExists { discriminant });
383        }
384        Ok(())
385    }
386
387    pub fn find_executor<EX: 'static>(&self) -> impl Iterator<Item = &'_ EX>
388    where
389        E: AnyEnum,
390    {
391        self.old_executors
392            .iter()
393            .filter_map(|e| e.as_any_kind().downcast_ref())
394    }
395
396    /// Returns the maximum number of bits used to represent addresses in memory
397    pub fn pointer_max_bits(&self) -> usize {
398        self.new_inventory.config().memory_config.pointer_max_bits
399    }
400}
401
402impl<SC: StarkGenericConfig> AirInventory<SC> {
403    /// Outside of this crate, [AirInventory] must be constructed via [SystemConfig].
404    pub(crate) fn new(
405        config: SystemConfig,
406        system: SystemAirInventory<SC>,
407        bus_idx_mgr: BusIndexManager,
408    ) -> Self {
409        Self {
410            config,
411            system,
412            ext_start: Vec::new(),
413            ext_airs: Vec::new(),
414            bus_idx_mgr,
415        }
416    }
417
418    /// This should be called **exactly once** at the start of the declaration of a new extension.
419    pub fn start_new_extension(&mut self) {
420        self.ext_start.push(self.ext_airs.len());
421    }
422
423    pub fn new_bus_idx(&mut self) -> BusIndex {
424        self.bus_idx_mgr.new_bus_idx()
425    }
426
427    /// Looks through already-defined AIRs to see if there exists any of type `A` by downcasting.
428    /// Returns all chips of type `A` in the circuit.
429    ///
430    /// This should not be used to look for system AIRs.
431    pub fn find_air<A: 'static>(&self) -> impl Iterator<Item = &'_ A> {
432        self.ext_airs
433            .iter()
434            .filter_map(|air| air.as_any().downcast_ref())
435    }
436
437    pub fn add_air<A: AnyRap<SC> + 'static>(&mut self, air: A) {
438        self.add_air_ref(Arc::new(air));
439    }
440
441    pub fn add_air_ref(&mut self, air: AirRef<SC>) {
442        self.ext_airs.push(air);
443    }
444
445    pub fn range_checker(&self) -> &VariableRangeCheckerAir {
446        self.find_air()
447            .next()
448            .expect("system always has range checker AIR")
449    }
450
451    /// The AIRs in the order they appear in the verifying key.
452    /// This is the system AIRs, followed by the other AIRs in the **reverse** of the order they
453    /// were added in the VM extension definitions. In particular, the AIRs that have dependencies
454    /// appear later. The system guarantees that the last AIR is the [VariableRangeCheckerAir].
455    pub fn into_airs(self) -> impl Iterator<Item = AirRef<SC>> {
456        self.system
457            .into_airs()
458            .into_iter()
459            .chain(self.ext_airs.into_iter().rev())
460    }
461
462    /// This is O(1). Returns the total number of AIRs and equals the length of [`Self::into_airs`].
463    pub fn num_airs(&self) -> usize {
464        self.config.num_airs() + self.ext_airs.len()
465    }
466
467    /// Standalone function to generate proving key and verifying key for this circuit.
468    pub fn keygen<E: StarkEngine<SC = SC>>(self, engine: &E) -> MultiStarkProvingKey<SC> {
469        let mut builder = engine.keygen_builder();
470        for air in self.into_airs() {
471            builder.add_air(air);
472        }
473        builder.generate_pk()
474    }
475
476    /// Returns the maximum number of bits used to represent addresses in memory
477    pub fn pointer_max_bits(&self) -> usize {
478        self.config.memory_config.pointer_max_bits
479    }
480}
481
482impl BusIndexManager {
483    pub fn new() -> Self {
484        Self { bus_idx_max: 0 }
485    }
486
487    pub fn new_bus_idx(&mut self) -> BusIndex {
488        let idx = self.bus_idx_max;
489        self.bus_idx_max = self.bus_idx_max.checked_add(1).unwrap();
490        idx
491    }
492}
493
494impl<SC, RA, PB> ChipInventory<SC, RA, PB>
495where
496    SC: StarkGenericConfig,
497    PB: ProverBackend,
498{
499    pub fn new(airs: AirInventory<SC>) -> Self {
500        Self {
501            airs,
502            chips: Vec::new(),
503            cur_num_exts: 0,
504            executor_idx_to_insertion_idx: Vec::new(),
505        }
506    }
507
508    pub fn config(&self) -> &SystemConfig {
509        &self.airs.config
510    }
511
512    // NOTE[jpw]: this is currently unused, it is for debugging purposes
513    pub fn start_new_extension(&mut self) -> Result<(), ChipInventoryError> {
514        if self.cur_num_exts >= self.airs.ext_start.len() {
515            return Err(ChipInventoryError::MissingCircuitExtension(
516                self.airs.ext_start.len(),
517            ));
518        }
519        if self.chips.len() != self.airs.ext_start[self.cur_num_exts] {
520            return Err(ChipInventoryError::MissingChip {
521                actual: self.chips.len(),
522                expected: self.airs.ext_start[self.cur_num_exts],
523            });
524        }
525
526        self.cur_num_exts += 1;
527        Ok(())
528    }
529
530    /// Gets the next AIR from the pre-existing AIR inventory according to the index of the next
531    /// chip to be built.
532    pub fn next_air<A: 'static>(&self) -> Result<&A, ChipInventoryError> {
533        let cur_idx = self.chips.len();
534        self.airs
535            .ext_airs
536            .get(cur_idx)
537            .and_then(|air| air.as_any().downcast_ref())
538            .ok_or_else(|| ChipInventoryError::AirNotFound {
539                name: type_name::<A>().to_string(),
540            })
541    }
542
543    /// Looks through built chips to see if there exists any of type `C` by downcasting.
544    /// Returns all chips of type `C` in the chipset.
545    ///
546    /// Note: the type `C` will usually be a smart pointer to a chip.
547    pub fn find_chip<C: 'static>(&self) -> impl Iterator<Item = &'_ C> {
548        self.chips.iter().filter_map(|c| c.as_any().downcast_ref())
549    }
550
551    /// Adds a chip that is not associated with any executor, as defined by the
552    /// [VmExecutionExtension] trait.
553    pub fn add_periphery_chip<C: Chip<RA, PB> + 'static>(&mut self, chip: C) {
554        self.chips.push(Box::new(chip));
555    }
556
557    /// Adds a chip and associates it to the next executor.
558    /// **Caution:** you must add chips in the order matching the order that executors were added in
559    /// the [VmExecutionExtension] implementation.
560    pub fn add_executor_chip<C: Chip<RA, PB> + 'static>(&mut self, chip: C) {
561        tracing::debug!("add_executor_chip: {}", type_name::<C>());
562        self.executor_idx_to_insertion_idx.push(self.chips.len());
563        self.chips.push(Box::new(chip));
564    }
565
566    /// Returns the mapping from executor index to the AIR index, where AIR index is the index of
567    /// the AIR within the verifying key.
568    ///
569    /// This should only be called after the `ChipInventory` is fully built.
570    pub fn executor_idx_to_air_idx(&self) -> Vec<usize> {
571        let num_airs = self.airs.num_airs();
572        assert_eq!(
573            num_airs,
574            self.config().num_airs() + self.chips.len(),
575            "Number of chips does not match number of AIRs"
576        );
577        // system AIRs are at the front of vkey, and then insertion index is the reverse ordering of
578        // AIR index
579        self.executor_idx_to_insertion_idx
580            .iter()
581            .map(|insertion_idx| {
582                num_airs
583                    .checked_sub(insertion_idx.checked_add(1).unwrap())
584                    .unwrap_or_else(|| {
585                        panic!(
586                            "Attempt to subtract num_airs={num_airs} by {}",
587                            insertion_idx + 1
588                        )
589                    })
590            })
591            .collect()
592    }
593
594    pub fn timestamp_max_bits(&self) -> usize {
595        self.airs.config().memory_config.timestamp_max_bits
596    }
597}
598
599// SharedVariableRangeCheckerChip is only used by the CPU backend.
600impl<SC, RA> ChipInventory<SC, RA, CpuBackend<SC>>
601where
602    SC: StarkGenericConfig,
603{
604    pub fn range_checker(&self) -> Result<&SharedVariableRangeCheckerChip, ChipInventoryError> {
605        self.find_chip::<SharedVariableRangeCheckerChip>()
606            .next()
607            .ok_or_else(|| ChipInventoryError::ChipNotFound {
608                name: "VariableRangeCheckerChip".to_string(),
609            })
610    }
611}
612
613// ================================== Error Types =====================================
614
615#[derive(thiserror::Error, Debug)]
616pub enum ExecutorInventoryError {
617    #[error("Opcode {opcode} already owned by executor id {id}")]
618    ExecutorExists { opcode: VmOpcode, id: ExecutorId },
619    #[error("Phantom discriminant {} already has sub-executor", .discriminant.0)]
620    PhantomSubExecutorExists { discriminant: PhantomDiscriminant },
621}
622
623#[derive(thiserror::Error, Debug)]
624pub enum AirInventoryError {
625    #[error("AIR {name} not found")]
626    AirNotFound { name: String },
627}
628
629#[derive(thiserror::Error, Debug)]
630pub enum ChipInventoryError {
631    #[error("Air {name} not found")]
632    AirNotFound { name: String },
633    #[error("Chip {name} not found")]
634    ChipNotFound { name: String },
635    #[error("Adding prover extension without execution extension. Number of execution extensions is {0}")]
636    MissingExecutionExtension(usize),
637    #[error(
638        "Adding prover extension without circuit extension. Number of circuit extensions is {0}"
639    )]
640    MissingCircuitExtension(usize),
641    #[error("Missing chip. Number of chips is {actual}, expected number is {expected}")]
642    MissingChip { actual: usize, expected: usize },
643    #[error("Missing executor chip. Number of executors with associated chips is {actual}, expected number is {expected}")]
644    MissingExecutor { actual: usize, expected: usize },
645}
646
647// ======================= VM Chip Complex Implementation =============================
648
649impl<SC, RA, PB, SCC> VmChipComplex<SC, RA, PB, SCC>
650where
651    SC: StarkGenericConfig,
652    RA: Arena,
653    PB: ProverBackend,
654    SCC: SystemChipComplex<RA, PB>,
655{
656    pub fn system_config(&self) -> &SystemConfig {
657        self.inventory.config()
658    }
659
660    /// `record_arenas` is expected to have length equal to the number of AIRs in the verifying key
661    /// and in the same order as the AIRs appearing in the verifying key, even though some chips may
662    /// not require a record arena.
663    pub(crate) fn generate_proving_ctx(
664        &mut self,
665        system_records: SystemRecords<PB::Val>,
666        record_arenas: Vec<RA>,
667        // trace_height_constraints: &[LinearConstraint],
668    ) -> Result<ProvingContext<PB>, GenerationError> {
669        // ATTENTION: The order of AIR proving context generation MUST be consistent with
670        // `AirInventory::into_airs`.
671
672        // Execution has finished at this point.
673        // ASSUMPTION WHICH MUST HOLD: non-system chips do not have a dependency on the system chips
674        // during trace generation. Given this assumption, we can generate trace on the system chips
675        // first.
676        let num_sys_airs = self.system_config().num_airs();
677        let num_airs = num_sys_airs + self.inventory.chips.len();
678        if num_airs != record_arenas.len() {
679            return Err(GenerationError::UnexpectedNumArenas {
680                actual: record_arenas.len(),
681                expected: num_airs,
682            });
683        }
684        let mut _record_arenas = record_arenas;
685        let record_arenas = _record_arenas.split_off(num_sys_airs);
686        let sys_record_arenas = _record_arenas;
687
688        // First go through all system chips
689        // Then go through all other chips in inventory in **reverse** order they were added (to
690        // resolve dependencies)
691        //
692        // Perf[jpw]: currently we call tracegen on each chip **serially** (although tracegen per
693        // chip is parallelized). We could introduce more parallelism, while potentially increasing
694        // the peak memory usage, by keeping a dependency tree and generating traces at the same
695        // layer of the tree in parallel.
696        let ctx_without_empties: Vec<(usize, AirProvingContext<_>)> = iter::empty()
697            .chain(info_span!("system_trace_gen").in_scope(|| {
698                self.system
699                    .generate_proving_ctx(system_records, sys_record_arenas)
700            }))
701            .chain(
702                zip(self.inventory.chips.iter().enumerate().rev(), record_arenas).map(
703                    |((insertion_idx, chip), records)| {
704                        // Only create a span if record is not empty:
705                        let _span = (!records.is_empty()).then(|| {
706                            let air_name = self.inventory.airs.ext_airs[insertion_idx].name();
707                            info_span!("single_trace_gen", air = air_name).entered()
708                        });
709                        chip.generate_proving_ctx(records)
710                    },
711                ),
712            )
713            .enumerate()
714            .filter(|(_air_id, ctx)| {
715                (!ctx.cached_mains.is_empty() || ctx.common_main.is_some())
716                    && ctx.main_trace_height() > 0
717            })
718            .collect();
719
720        Ok(ProvingContext {
721            per_air: ctx_without_empties,
722        })
723    }
724}
725
726// ============ Blanket implementation of VM extension traits for Option<E> ===========
727
728impl<F, EXT: VmExecutionExtension<F>> VmExecutionExtension<F> for Option<EXT> {
729    type Executor = EXT::Executor;
730
731    fn extend_execution(
732        &self,
733        inventory: &mut ExecutorInventoryBuilder<F, Self::Executor>,
734    ) -> Result<(), ExecutorInventoryError> {
735        if let Some(extension) = self {
736            extension.extend_execution(inventory)
737        } else {
738            Ok(())
739        }
740    }
741}
742
743impl<SC: StarkGenericConfig, EXT: VmCircuitExtension<SC>> VmCircuitExtension<SC> for Option<EXT> {
744    fn extend_circuit(&self, inventory: &mut AirInventory<SC>) -> Result<(), AirInventoryError> {
745        if let Some(extension) = self {
746            extension.extend_circuit(inventory)
747        } else {
748            Ok(())
749        }
750    }
751}
752
753/// A helper trait for downcasting types that may be enums.
754pub trait AnyEnum {
755    /// Recursively "unwraps" enum and casts to `Any` for downcasting.
756    fn as_any_kind(&self) -> &dyn Any;
757
758    /// Recursively "unwraps" enum and casts to `Any` for downcasting.
759    fn as_any_kind_mut(&mut self) -> &mut dyn Any;
760}
761
762impl AnyEnum for () {
763    fn as_any_kind(&self) -> &dyn Any {
764        self
765    }
766    fn as_any_kind_mut(&mut self) -> &mut dyn Any {
767        self
768    }
769}
770
771#[cfg(test)]
772mod tests {
773    use openvm_circuit_derive::AnyEnum;
774    use openvm_stark_sdk::config::baby_bear_poseidon2::BabyBearPoseidon2Config;
775
776    use super::*;
777    use crate::{arch::VmCircuitConfig, system::memory::interface::MemoryInterfaceAirs};
778
779    #[allow(dead_code)]
780    #[derive(Copy, Clone)]
781    enum EnumA {
782        A(u8),
783        B(u32),
784    }
785
786    enum EnumB {
787        C(u64),
788        D(EnumA),
789    }
790
791    #[derive(AnyEnum)]
792    enum EnumC {
793        C(u64),
794        #[any_enum]
795        D(EnumA),
796    }
797
798    impl AnyEnum for EnumA {
799        fn as_any_kind(&self) -> &dyn Any {
800            match self {
801                EnumA::A(a) => a,
802                EnumA::B(b) => b,
803            }
804        }
805
806        fn as_any_kind_mut(&mut self) -> &mut dyn Any {
807            match self {
808                EnumA::A(a) => a,
809                EnumA::B(b) => b,
810            }
811        }
812    }
813
814    impl AnyEnum for EnumB {
815        fn as_any_kind(&self) -> &dyn Any {
816            match self {
817                EnumB::C(c) => c,
818                EnumB::D(d) => d.as_any_kind(),
819            }
820        }
821
822        fn as_any_kind_mut(&mut self) -> &mut dyn Any {
823            match self {
824                EnumB::C(c) => c,
825                EnumB::D(d) => d.as_any_kind_mut(),
826            }
827        }
828    }
829
830    #[test]
831    fn test_any_enum_downcast() {
832        let a = EnumA::A(1);
833        assert_eq!(a.as_any_kind().downcast_ref::<u8>(), Some(&1));
834        let b = EnumB::D(a);
835        assert!(b.as_any_kind().downcast_ref::<u64>().is_none());
836        assert!(b.as_any_kind().downcast_ref::<EnumA>().is_none());
837        assert_eq!(b.as_any_kind().downcast_ref::<u8>(), Some(&1));
838        let c = EnumB::C(3);
839        assert_eq!(c.as_any_kind().downcast_ref::<u64>(), Some(&3));
840        let d = EnumC::D(a);
841        assert!(d.as_any_kind().downcast_ref::<u64>().is_none());
842        assert!(d.as_any_kind().downcast_ref::<EnumA>().is_none());
843        assert_eq!(d.as_any_kind().downcast_ref::<u8>(), Some(&1));
844        let e = EnumC::C(3);
845        assert_eq!(e.as_any_kind().downcast_ref::<u64>(), Some(&3));
846    }
847
848    #[test]
849    fn test_system_bus_indices() {
850        let config = SystemConfig::default();
851        let inventory: AirInventory<BabyBearPoseidon2Config> = config.create_airs().unwrap();
852        let system = inventory.system();
853        let port = system.port();
854        assert_eq!(port.execution_bus.index(), 0);
855        assert_eq!(port.memory_bridge.memory_bus().index(), 1);
856        assert_eq!(port.program_bus.index(), 2);
857        assert_eq!(port.memory_bridge.range_bus().index(), 3);
858        match &system.memory.interface {
859            MemoryInterfaceAirs::Persistent { boundary, .. } => {
860                assert_eq!(boundary.merkle_bus.index, 4);
861                assert_eq!(boundary.compression_bus.index, 5);
862            }
863            _ => unreachable!(),
864        };
865    }
866}