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