openvm_circuit/system/
mod.rs

1use std::sync::Arc;
2
3use derive_more::derive::From;
4use openvm_circuit_derive::{AnyEnum, Executor, MeteredExecutor, PreflightExecutor};
5#[cfg(feature = "aot")]
6use openvm_circuit_derive::{AotExecutor, AotMeteredExecutor};
7use openvm_circuit_primitives::var_range::{
8    SharedVariableRangeCheckerChip, VariableRangeCheckerAir, VariableRangeCheckerBus,
9    VariableRangeCheckerChip,
10};
11use openvm_instructions::{
12    LocalOpcode, PhantomDiscriminant, PublishOpcode, SysPhantom, SystemOpcode,
13};
14use openvm_stark_backend::{
15    config::{StarkGenericConfig, Val},
16    engine::StarkEngine,
17    interaction::{LookupBus, PermutationCheckBus},
18    p3_field::{Field, PrimeField32},
19    prover::{
20        cpu::{CpuBackend, CpuDevice},
21        hal::{MatrixDimensions, ProverBackend},
22        types::{AirProvingContext, CommittedTraceData},
23    },
24    AirRef, Chip,
25};
26use rustc_hash::FxHashMap;
27
28use self::{connector::VmConnectorAir, program::ProgramAir, public_values::PublicValuesAir};
29use crate::{
30    arch::{
31        vm_poseidon2_config, AirInventory, AirInventoryError, BusIndexManager, ChipInventory,
32        ChipInventoryError, DenseRecordArena, ExecutionBridge, ExecutionBus, ExecutionState,
33        ExecutorInventory, ExecutorInventoryError, MatrixRecordArena, PhantomSubExecutor,
34        RowMajorMatrixArena, SystemConfig, VmAirWrapper, VmBuilder, VmChipComplex, VmChipWrapper,
35        VmCircuitConfig, VmExecutionConfig, VmField, CONNECTOR_AIR_ID, PROGRAM_AIR_ID,
36        PUBLIC_VALUES_AIR_ID,
37    },
38    system::{
39        connector::VmConnectorChip,
40        memory::{
41            interface::{MemoryInterface, MemoryInterfaceAirs},
42            offline_checker::{MemoryBridge, MemoryBus},
43            online::GuestMemory,
44            MemoryAirInventory, MemoryController, TimestampedEquipartition, CHUNK,
45        },
46        native_adapter::{NativeAdapterAir, NativeAdapterExecutor},
47        phantom::{
48            CycleEndPhantomExecutor, CycleStartPhantomExecutor, NopPhantomExecutor, PhantomAir,
49            PhantomChip, PhantomExecutor, PhantomFiller,
50        },
51        poseidon2::{
52            air::Poseidon2PeripheryAir, new_poseidon2_periphery_air, Poseidon2PeripheryChip,
53        },
54        program::{ProgramBus, ProgramChip},
55        public_values::{
56            PublicValuesChip, PublicValuesCoreAir, PublicValuesExecutor, PublicValuesFiller,
57        },
58    },
59};
60
61pub mod connector;
62#[cfg(feature = "cuda")]
63pub mod cuda;
64pub mod memory;
65// Necessary for the PublicValuesChip
66pub mod native_adapter;
67pub mod phantom;
68pub mod poseidon2;
69pub mod program;
70pub mod public_values;
71
72/// **If** internal poseidon2 chip exists, then its insertion index is 1.
73const POSEIDON2_INSERTION_IDX: usize = 1;
74/// **If** public values chip exists, then its executor index is 0.
75pub(crate) const PV_EXECUTOR_IDX: usize = 0;
76
77/// Trait for trace generation of all system AIRs. The system chip complex is special because we may
78/// not exactly following the exact matching between `Air` and `Chip`. Moreover we may require more
79/// flexibility than what is provided through the trait object [`AnyChip`].
80///
81/// The [SystemChipComplex] is meant to be constructible once the VM configuration is known, and it
82/// can be loaded with arbitrary programs supported by the instruction set available to its
83/// configuration. The [SystemChipComplex] is meant to persistent between instances of proof
84/// generation.
85pub trait SystemChipComplex<RA, PB: ProverBackend> {
86    /// Loads the program in the form of a cached trace with prover data.
87    fn load_program(&mut self, cached_program_trace: CommittedTraceData<PB>);
88
89    /// Transport the initial memory state to device. This may be called before preflight execution
90    /// begins and start async device processes in parallel to execution.
91    fn transport_init_memory_to_device(&mut self, memory: &GuestMemory);
92
93    /// The caller must guarantee that `record_arenas` has length equal to the number of system
94    /// AIRs, although some arenas may be empty if they are unused.
95    fn generate_proving_ctx(
96        &mut self,
97        system_records: SystemRecords<PB::Val>,
98        record_arenas: Vec<RA>,
99    ) -> Vec<AirProvingContext<PB>>;
100
101    /// This function only returns `Some` when continuations is enabled.
102    /// When continuations is enabled, it returns the top merkle sub-tree of the memory merkle tree
103    /// as a segment tree with `2 * (2^addr_space_height) - 1` nodes, representing the Merkle
104    /// tree formed from the roots of the sub-trees for each address space.
105    ///
106    /// This function **must** return `Some` if called after
107    /// [`generate_proving_ctx`](Self::generate_proving_ctx) and may return `None` if called before
108    /// that.
109    fn memory_top_tree(&self) -> Option<&[[PB::Val; CHUNK]]>;
110
111    /// This function is only used for metric collection purposes and custom implementations are
112    /// free to ignore it.
113    ///
114    /// Since system chips (primarily memory) will only have all information needed to compute the
115    /// true used trace heights after `generate_proving_ctx` is called, this method will be called
116    /// after `generate_proving_ctx` on the trace `heights` of all AIRs (including non-system AIRs)
117    /// in the AIR ID order.
118    ///
119    /// The default implementation does nothing.
120    #[cfg(feature = "metrics")]
121    fn finalize_trace_heights(&self, _heights: &mut [usize]) {}
122}
123
124/// Trait meant to be implemented on a SystemChipComplex.
125pub trait SystemWithFixedTraceHeights {
126    /// `heights` will have length equal to number of system AIRs, in AIR ID order. This function
127    /// must guarantee that the system trace matrices generated have the required heights.
128    fn override_trace_heights(&mut self, heights: &[u32]);
129}
130
131pub struct SystemRecords<F> {
132    pub from_state: ExecutionState<u32>,
133    pub to_state: ExecutionState<u32>,
134    pub exit_code: Option<u32>,
135    /// `i` -> frequency of instruction in `i`th row of trace matrix. This requires filtering
136    /// `program.instructions_and_debug_infos` to remove gaps.
137    pub filtered_exec_frequencies: Vec<u32>,
138    // We always use a [DenseRecordArena] here, regardless of the generic `RA` used for other
139    // execution records.
140    pub access_adapter_records: DenseRecordArena,
141    // Perf[jpw]: this should be computed on-device and changed to just touched blocks
142    pub touched_memory: TouchedMemory<F>,
143    /// The public values of the [PublicValuesChip]. These should only be non-empty if
144    /// continuations are disabled.
145    pub public_values: Vec<F>,
146}
147
148pub enum TouchedMemory<F> {
149    Persistent(TimestampedEquipartition<F, CHUNK>),
150    Volatile(TimestampedEquipartition<F, 1>),
151}
152
153#[derive(Clone, AnyEnum, Executor, MeteredExecutor, PreflightExecutor, From)]
154#[cfg_attr(feature = "aot", derive(AotExecutor, AotMeteredExecutor))]
155pub enum SystemExecutor<F: Field> {
156    PublicValues(PublicValuesExecutor<F>),
157    Phantom(PhantomExecutor<F>),
158}
159
160/// SystemPort combines system resources needed by most extensions
161#[derive(Clone, Copy)]
162pub struct SystemPort {
163    pub execution_bus: ExecutionBus,
164    pub program_bus: ProgramBus,
165    pub memory_bridge: MemoryBridge,
166}
167
168#[derive(Clone)]
169pub struct SystemAirInventory<SC: StarkGenericConfig> {
170    pub program: ProgramAir,
171    pub connector: VmConnectorAir,
172    pub memory: MemoryAirInventory<SC>,
173    /// Public values AIR exists if and only if continuations is disabled and `num_public_values`
174    /// is greater than 0.
175    pub public_values: Option<PublicValuesAir>,
176}
177
178impl<SC: StarkGenericConfig> SystemAirInventory<SC> {
179    pub fn new(
180        config: &SystemConfig,
181        port: SystemPort,
182        merkle_compression_buses: Option<(PermutationCheckBus, PermutationCheckBus)>,
183    ) -> Self {
184        let SystemPort {
185            execution_bus,
186            program_bus,
187            memory_bridge,
188        } = port;
189        let range_bus = memory_bridge.range_bus();
190        let program = ProgramAir::new(program_bus);
191        let connector = VmConnectorAir::new(
192            execution_bus,
193            program_bus,
194            range_bus,
195            config.memory_config.timestamp_max_bits,
196        );
197        assert_eq!(
198            config.continuation_enabled,
199            merkle_compression_buses.is_some()
200        );
201
202        let memory = MemoryAirInventory::new(
203            memory_bridge,
204            &config.memory_config,
205            range_bus,
206            merkle_compression_buses,
207        );
208
209        let public_values = if config.has_public_values_chip() {
210            let air = VmAirWrapper::new(
211                NativeAdapterAir::new(
212                    ExecutionBridge::new(execution_bus, program_bus),
213                    memory_bridge,
214                ),
215                PublicValuesCoreAir::new(
216                    config.num_public_values,
217                    config.max_constraint_degree as u32 - 1,
218                ),
219            );
220            Some(air)
221        } else {
222            None
223        };
224
225        Self {
226            program,
227            connector,
228            memory,
229            public_values,
230        }
231    }
232
233    pub fn port(&self) -> SystemPort {
234        SystemPort {
235            memory_bridge: self.memory.bridge,
236            program_bus: self.program.bus,
237            execution_bus: self.connector.execution_bus,
238        }
239    }
240
241    pub fn into_airs(self) -> Vec<AirRef<SC>> {
242        let mut airs: Vec<AirRef<SC>> = Vec::new();
243        airs.push(Arc::new(self.program));
244        airs.push(Arc::new(self.connector));
245        if let Some(public_values) = self.public_values {
246            airs.push(Arc::new(public_values));
247        }
248        airs.extend(self.memory.into_airs());
249        airs
250    }
251}
252
253impl<F: PrimeField32> VmExecutionConfig<F> for SystemConfig {
254    type Executor = SystemExecutor<F>;
255
256    /// The only way to create an [ExecutorInventory] is from a [SystemConfig]. This will add an
257    /// executor for [PublicValuesExecutor] if continuations is disabled. It will always add an
258    /// executor for [PhantomChip], which handles all phantom sub-executors.
259    fn create_executors(
260        &self,
261    ) -> Result<ExecutorInventory<Self::Executor>, ExecutorInventoryError> {
262        let mut inventory = ExecutorInventory::new(self.clone());
263        // PublicValuesChip is required when num_public_values > 0 in single segment mode.
264        if self.has_public_values_chip() {
265            assert_eq!(inventory.executors().len(), PV_EXECUTOR_IDX);
266
267            let public_values = PublicValuesExecutor::new(NativeAdapterExecutor::default());
268            inventory.add_executor(public_values, [PublishOpcode::PUBLISH.global_opcode()])?;
269        }
270        let phantom_opcode = SystemOpcode::PHANTOM.global_opcode();
271        let mut phantom_executors: FxHashMap<PhantomDiscriminant, Arc<dyn PhantomSubExecutor<F>>> =
272            FxHashMap::default();
273        // Use NopPhantomExecutor so the discriminant is set but `DebugPanic` is handled specially.
274        phantom_executors.insert(
275            PhantomDiscriminant(SysPhantom::DebugPanic as u16),
276            Arc::new(NopPhantomExecutor),
277        );
278        phantom_executors.insert(
279            PhantomDiscriminant(SysPhantom::Nop as u16),
280            Arc::new(NopPhantomExecutor),
281        );
282        phantom_executors.insert(
283            PhantomDiscriminant(SysPhantom::CtStart as u16),
284            Arc::new(CycleStartPhantomExecutor),
285        );
286        phantom_executors.insert(
287            PhantomDiscriminant(SysPhantom::CtEnd as u16),
288            Arc::new(CycleEndPhantomExecutor),
289        );
290        let phantom = PhantomExecutor::new(phantom_executors, phantom_opcode);
291        inventory.add_executor(phantom, [phantom_opcode])?;
292
293        Ok(inventory)
294    }
295}
296
297impl<SC> VmCircuitConfig<SC> for SystemConfig
298where
299    SC: StarkGenericConfig,
300    Val<SC>: VmField,
301{
302    /// Every VM circuit within the OpenVM circuit architecture **must** be initialized from the
303    /// [SystemConfig].
304    fn create_airs(&self) -> Result<AirInventory<SC>, AirInventoryError> {
305        let mut bus_idx_mgr = BusIndexManager::new();
306        let execution_bus = ExecutionBus::new(bus_idx_mgr.new_bus_idx());
307        let memory_bus = MemoryBus::new(bus_idx_mgr.new_bus_idx());
308        let program_bus = ProgramBus::new(bus_idx_mgr.new_bus_idx());
309        let range_bus =
310            VariableRangeCheckerBus::new(bus_idx_mgr.new_bus_idx(), self.memory_config.decomp);
311
312        let merkle_compression_buses = if self.continuation_enabled {
313            let merkle_bus = PermutationCheckBus::new(bus_idx_mgr.new_bus_idx());
314            let compression_bus = PermutationCheckBus::new(bus_idx_mgr.new_bus_idx());
315            Some((merkle_bus, compression_bus))
316        } else {
317            None
318        };
319        let memory_bridge =
320            MemoryBridge::new(memory_bus, self.memory_config.timestamp_max_bits, range_bus);
321        let system_port = SystemPort {
322            execution_bus,
323            program_bus,
324            memory_bridge,
325        };
326        let system = SystemAirInventory::new(self, system_port, merkle_compression_buses);
327
328        let mut inventory = AirInventory::new(self.clone(), system, bus_idx_mgr);
329
330        let range_checker = VariableRangeCheckerAir::new(range_bus);
331        // Range checker is always the first AIR in the inventory
332        inventory.add_air(range_checker);
333
334        if self.continuation_enabled {
335            assert_eq!(inventory.ext_airs().len(), POSEIDON2_INSERTION_IDX);
336            // Add direct poseidon2 AIR for persistent memory.
337            // Currently we never use poseidon2 opcodes when continuations is enabled: we will need
338            // special handling when that happens
339            let (_, compression_bus) = merkle_compression_buses.unwrap();
340            let direct_bus_idx = compression_bus.index;
341            let air = new_poseidon2_periphery_air(
342                vm_poseidon2_config(),
343                LookupBus::new(direct_bus_idx),
344                self.max_constraint_degree,
345            );
346            inventory.add_air_ref(air);
347        }
348        let execution_bridge = ExecutionBridge::new(execution_bus, program_bus);
349        let phantom = PhantomAir {
350            execution_bridge,
351            phantom_opcode: SystemOpcode::PHANTOM.global_opcode(),
352        };
353        inventory.add_air(phantom);
354
355        Ok(inventory)
356    }
357}
358
359// =================== CPU Backend Specific System Chip Complex Constructor ==================
360
361/// Base system chips for CPU backend. These chips must exactly correspond to the AIRs in
362/// [SystemAirInventory].
363pub struct SystemChipInventory<SC: StarkGenericConfig>
364where
365    Val<SC>: VmField,
366{
367    pub program_chip: ProgramChip<SC>,
368    pub connector_chip: VmConnectorChip<Val<SC>>,
369    /// Contains all memory chips
370    pub memory_controller: MemoryController<Val<SC>>,
371    pub public_values_chip: Option<PublicValuesChip<Val<SC>>>,
372}
373
374// Note[jpw]: We could get rid of the `mem_inventory` input because `MemoryController` doesn't need
375// the buses for tracegen. We leave it to use old interfaces.
376impl<SC: StarkGenericConfig> SystemChipInventory<SC>
377where
378    Val<SC>: VmField,
379{
380    pub fn new(
381        config: &SystemConfig,
382        mem_inventory: &MemoryAirInventory<SC>,
383        range_checker: SharedVariableRangeCheckerChip,
384        hasher_chip: Option<Arc<Poseidon2PeripheryChip<Val<SC>>>>,
385    ) -> Self {
386        // We create an empty program chip: the program should be loaded later (and can be swapped
387        // out). The execution frequencies are supplied only after execution.
388        let program_chip = ProgramChip::unloaded();
389        let connector_chip = VmConnectorChip::<Val<SC>>::new(
390            range_checker.clone(),
391            config.memory_config.timestamp_max_bits,
392        );
393        let memory_bus = mem_inventory.bridge.memory_bus();
394        let memory_controller = match &mem_inventory.interface {
395            MemoryInterfaceAirs::Persistent {
396                boundary: _,
397                merkle,
398            } => {
399                assert!(config.continuation_enabled);
400                MemoryController::<Val<SC>>::with_persistent_memory(
401                    memory_bus,
402                    config.memory_config.clone(),
403                    range_checker.clone(),
404                    merkle.merkle_bus,
405                    merkle.compression_bus,
406                    hasher_chip.unwrap(),
407                )
408            }
409            MemoryInterfaceAirs::Volatile { boundary: _ } => {
410                assert!(!config.continuation_enabled);
411                MemoryController::with_volatile_memory(
412                    memory_bus,
413                    config.memory_config.clone(),
414                    range_checker.clone(),
415                )
416            }
417        };
418
419        let public_values_chip = config.has_public_values_chip().then(|| {
420            VmChipWrapper::new(
421                PublicValuesFiller::new(
422                    NativeAdapterExecutor::default(),
423                    config.num_public_values,
424                    (config.max_constraint_degree as u32)
425                        .checked_sub(1)
426                        .unwrap(),
427                ),
428                memory_controller.helper(),
429            )
430        });
431
432        Self {
433            program_chip,
434            connector_chip,
435            memory_controller,
436            public_values_chip,
437        }
438    }
439}
440
441impl<RA, SC> SystemChipComplex<RA, CpuBackend<SC>> for SystemChipInventory<SC>
442where
443    RA: RowMajorMatrixArena<Val<SC>>,
444    SC: StarkGenericConfig,
445    Val<SC>: VmField,
446{
447    fn load_program(&mut self, cached_program_trace: CommittedTraceData<CpuBackend<SC>>) {
448        let _ = self.program_chip.cached.replace(cached_program_trace);
449    }
450
451    fn transport_init_memory_to_device(&mut self, memory: &GuestMemory) {
452        self.memory_controller
453            .set_initial_memory(memory.memory.clone());
454    }
455
456    fn generate_proving_ctx(
457        &mut self,
458        system_records: SystemRecords<Val<SC>>,
459        mut record_arenas: Vec<RA>,
460    ) -> Vec<AirProvingContext<CpuBackend<SC>>> {
461        let SystemRecords {
462            from_state,
463            to_state,
464            exit_code,
465            filtered_exec_frequencies,
466            access_adapter_records,
467            touched_memory,
468            public_values,
469        } = system_records;
470
471        if let Some(chip) = &mut self.public_values_chip {
472            chip.inner.set_public_values(public_values);
473        }
474        self.program_chip.filtered_exec_frequencies = filtered_exec_frequencies;
475        let program_ctx = self.program_chip.generate_proving_ctx(());
476        self.connector_chip.begin(from_state);
477        self.connector_chip.end(to_state, exit_code);
478        let connector_ctx = self.connector_chip.generate_proving_ctx(());
479
480        let pv_ctx = self.public_values_chip.as_ref().map(|chip| {
481            let arena = record_arenas.remove(PUBLIC_VALUES_AIR_ID);
482            chip.generate_proving_ctx(arena)
483        });
484
485        let memory_ctxs = self
486            .memory_controller
487            .generate_proving_ctx(access_adapter_records, touched_memory);
488
489        [program_ctx, connector_ctx]
490            .into_iter()
491            .chain(pv_ctx)
492            .chain(memory_ctxs)
493            .collect()
494    }
495
496    fn memory_top_tree(&self) -> Option<&[[Val<SC>; CHUNK]]> {
497        match &self.memory_controller.interface_chip {
498            MemoryInterface::Persistent { merkle_chip, .. } => {
499                let top_tree = &merkle_chip.top_tree;
500                (!top_tree.is_empty()).then_some(top_tree.as_slice())
501            }
502            MemoryInterface::Volatile { .. } => None,
503        }
504    }
505
506    #[cfg(feature = "metrics")]
507    fn finalize_trace_heights(&self, heights: &mut [usize]) {
508        use openvm_stark_backend::ChipUsageGetter;
509
510        use crate::system::memory::interface::MemoryInterface;
511
512        let boundary_idx = PUBLIC_VALUES_AIR_ID + usize::from(self.public_values_chip.is_some());
513        let mut access_adapter_offset = boundary_idx + 1;
514        match &self.memory_controller.interface_chip {
515            MemoryInterface::Volatile { boundary_chip } => {
516                let boundary_height = boundary_chip
517                    .final_memory
518                    .as_ref()
519                    .map(|m| m.len())
520                    .unwrap_or(0);
521                heights[boundary_idx] = boundary_height;
522            }
523            MemoryInterface::Persistent {
524                boundary_chip,
525                merkle_chip,
526                ..
527            } => {
528                let boundary_height = 2 * boundary_chip.touched_labels.len();
529                heights[boundary_idx] = boundary_height;
530                heights[boundary_idx + 1] = merkle_chip.current_height;
531                access_adapter_offset += 1;
532
533                // Poseidon2Periphery height also varies based on memory, so set it now even though
534                // it's not a system chip:
535                let poseidon_chip = self.memory_controller.hasher_chip.as_ref().unwrap();
536                let poseidon_height = poseidon_chip.current_trace_height();
537                // We know the chip insertion index, which starts from *the end* of the the AIR
538                // ordering
539                let poseidon_idx = heights.len() - 1 - POSEIDON2_INSERTION_IDX;
540                heights[poseidon_idx] = poseidon_height;
541            }
542        }
543        let access_heights = &self
544            .memory_controller
545            .access_adapter_inventory
546            .trace_heights;
547        heights[access_adapter_offset..access_adapter_offset + access_heights.len()]
548            .copy_from_slice(access_heights);
549    }
550}
551
552#[derive(Clone)]
553pub struct SystemCpuBuilder;
554
555impl<SC, E> VmBuilder<E> for SystemCpuBuilder
556where
557    SC: StarkGenericConfig,
558    E: StarkEngine<SC = SC, PB = CpuBackend<SC>, PD = CpuDevice<SC>>,
559    Val<SC>: VmField,
560{
561    type VmConfig = SystemConfig;
562    type RecordArena = MatrixRecordArena<Val<SC>>;
563    type SystemChipInventory = SystemChipInventory<SC>;
564
565    fn create_chip_complex(
566        &self,
567        config: &SystemConfig,
568        airs: AirInventory<SC>,
569    ) -> Result<
570        VmChipComplex<SC, MatrixRecordArena<Val<SC>>, CpuBackend<SC>, SystemChipInventory<SC>>,
571        ChipInventoryError,
572    > {
573        let range_bus = airs.range_checker().bus;
574        let range_checker = Arc::new(VariableRangeCheckerChip::new(range_bus));
575
576        let mut inventory = ChipInventory::new(airs);
577        // PublicValuesChip is required when num_public_values > 0 in single segment mode.
578        if config.has_public_values_chip() {
579            assert_eq!(
580                inventory.executor_idx_to_insertion_idx.len(),
581                PV_EXECUTOR_IDX
582            );
583            // We set insertion_idx so that air_idx = num_airs - (insertion_idx + 1) =
584            // PUBLIC_VALUES_AIR_ID in `VmChipComplex::executor_idx_to_air_idx`. We need to do this
585            // because this chip is special and not part of the normal inventory.
586            let insertion_idx = inventory
587                .airs()
588                .num_airs()
589                .checked_sub(1 + PUBLIC_VALUES_AIR_ID)
590                .unwrap();
591            inventory.executor_idx_to_insertion_idx.push(insertion_idx);
592        }
593        inventory.next_air::<VariableRangeCheckerAir>()?;
594        inventory.add_periphery_chip(range_checker.clone());
595
596        let hasher_chip = if config.continuation_enabled {
597            assert_eq!(inventory.chips().len(), POSEIDON2_INSERTION_IDX);
598            // ATTENTION: The threshold 7 here must match the one in `new_poseidon2_periphery_air`
599            let direct_bus = if config.max_constraint_degree >= 7 {
600                inventory
601                    .next_air::<Poseidon2PeripheryAir<Val<SC>, 0>>()?
602                    .bus
603            } else {
604                inventory
605                    .next_air::<Poseidon2PeripheryAir<Val<SC>, 1>>()?
606                    .bus
607            };
608            let chip = Arc::new(Poseidon2PeripheryChip::new(
609                vm_poseidon2_config(),
610                direct_bus.index,
611                config.max_constraint_degree,
612            ));
613            inventory.add_periphery_chip(chip.clone());
614            Some(chip)
615        } else {
616            None
617        };
618        let system = SystemChipInventory::new(
619            config,
620            &inventory.airs().system().memory,
621            range_checker,
622            hasher_chip,
623        );
624
625        let phantom_chip = PhantomChip::new(PhantomFiller, system.memory_controller.helper());
626        inventory.add_executor_chip(phantom_chip);
627
628        Ok(VmChipComplex { system, inventory })
629    }
630}
631
632impl<SC: StarkGenericConfig> SystemWithFixedTraceHeights for SystemChipInventory<SC>
633where
634    Val<SC>: VmField,
635{
636    /// Warning: this does not set the override for the PublicValuesChip. The PublicValuesChip
637    /// override must be set via the RecordArena.
638    fn override_trace_heights(&mut self, heights: &[u32]) {
639        assert_eq!(
640            heights[PROGRAM_AIR_ID] as usize,
641            self.program_chip
642                .cached
643                .as_ref()
644                .expect("program not loaded")
645                .trace
646                .height()
647        );
648        assert_eq!(heights[CONNECTOR_AIR_ID], 2);
649        let mut memory_start_idx = PUBLIC_VALUES_AIR_ID;
650        if self.public_values_chip.is_some() {
651            memory_start_idx += 1;
652        }
653        self.memory_controller
654            .set_override_trace_heights(&heights[memory_start_idx..]);
655    }
656}