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;
64pub mod native_adapter;
66pub mod phantom;
67pub mod poseidon2;
68pub mod program;
69pub mod public_values;
70
71const POSEIDON2_INSERTION_IDX: usize = 1;
73pub(crate) const PV_EXECUTOR_IDX: usize = 0;
75
76pub trait SystemChipComplex<RA, PB: ProverBackend> {
85 fn load_program(&mut self, cached_program_trace: CommittedTraceData<PB>);
87
88 fn transport_init_memory_to_device(&mut self, memory: &GuestMemory);
91
92 fn generate_proving_ctx(
95 &mut self,
96 system_records: SystemRecords<PB::Val>,
97 record_arenas: Vec<RA>,
98 ) -> Vec<AirProvingContext<PB>>;
99
100 fn memory_top_tree(&self) -> Option<&[[PB::Val; CHUNK]]>;
109
110 #[cfg(feature = "metrics")]
120 fn finalize_trace_heights(&self, _heights: &mut [usize]) {}
121}
122
123pub trait SystemWithFixedTraceHeights {
125 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 pub filtered_exec_frequencies: Vec<u32>,
137 pub access_adapter_records: DenseRecordArena,
140 pub touched_memory: TouchedMemory<F>,
142 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#[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 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 fn create_executors(
259 &self,
260 ) -> Result<ExecutorInventory<Self::Executor>, ExecutorInventoryError> {
261 let mut inventory = ExecutorInventory::new(self.clone());
262 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 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 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 inventory.add_air(range_checker);
328
329 if self.continuation_enabled {
330 assert_eq!(inventory.ext_airs().len(), POSEIDON2_INSERTION_IDX);
331 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
354pub struct SystemChipInventory<SC: StarkGenericConfig> {
359 pub program_chip: ProgramChip<SC>,
360 pub connector_chip: VmConnectorChip<Val<SC>>,
361 pub memory_controller: MemoryController<Val<SC>>,
363 pub public_values_chip: Option<PublicValuesChip<Val<SC>>>,
364}
365
366impl<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 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 let poseidon_chip = self.memory_controller.hasher_chip.as_ref().unwrap();
528 let poseidon_height = poseidon_chip.current_trace_height();
529 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 if config.has_public_values_chip() {
571 assert_eq!(
572 inventory.executor_idx_to_insertion_idx.len(),
573 PV_EXECUTOR_IDX
574 );
575 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 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 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}