1use std::{any::TypeId, borrow::Borrow, collections::VecDeque, marker::PhantomData, sync::Arc};
11
12use getset::{Getters, MutGetters, Setters, WithSetters};
13use itertools::{zip_eq, Itertools};
14use openvm_circuit::system::program::trace::compute_exe_commit;
15use openvm_instructions::{
16 exe::{SparseMemoryImage, VmExe},
17 program::Program,
18};
19#[cfg(any(debug_assertions, feature = "test-utils", feature = "stark-debug"))]
20use openvm_stark_backend::AirRef;
21use openvm_stark_backend::{
22 keygen::types::{MultiStarkProvingKey, MultiStarkVerifyingKey},
23 memory_metering::ProvingMemoryConfig,
24 p3_field::{InjectiveMonomial, PrimeCharacteristicRing, PrimeField32, TwoAdicField},
25 p3_util::log2_ceil_usize,
26 proof::Proof,
27 prover::{
28 ColMajorMatrix, CommittedTraceData, DeviceDataTransporter, DeviceMultiStarkProvingKey,
29 MatrixDimensions, ProverBackend, ProverDevice, ProvingContext, TraceCommitter,
30 },
31 verifier::VerifierError,
32 Com, StarkEngine, StarkProtocolConfig, Val,
33};
34use p3_baby_bear::BabyBear;
35use serde::{Deserialize, Serialize};
36use thiserror::Error;
37use tracing::{info_span, instrument};
38
39#[cfg(feature = "aot")]
40use super::aot::AotInstance;
41use super::{
42 execution_mode::{
43 ExecutionCtx, MeteredCostCtx, MeteredCtx, MeteredCtxInputs, PreflightCtx, Segment,
44 SegmentationLimits,
45 },
46 hasher::poseidon2::vm_poseidon2_hasher,
47 interpreter::InterpretedInstance,
48 interpreter_preflight::PreflightInterpretedInstance,
49 AirInventoryError, ChipInventoryError, ExecutionError, ExecutionState, Executor,
50 ExecutorInventory, ExecutorInventoryError, MemoryConfig, MeteredExecutor, PreflightExecutor,
51 StaticProgramError, SystemConfig, VmBuilder, VmChipComplex, VmCircuitConfig, VmExecState,
52 VmExecutionConfig, VmState, BOUNDARY_AIR_ID, CONNECTOR_AIR_ID, MERKLE_AIR_ID, PROGRAM_AIR_ID,
53 PROGRAM_CACHED_TRACE_INDEX,
54};
55#[cfg(feature = "metrics")]
56use crate::metrics::emit_opcode_counts;
57#[cfg(feature = "perf-metrics")]
58use crate::metrics::end_segment_metrics;
59use crate::{
60 arch::deferral::DeferralState,
61 execute_spanned,
62 system::{
63 connector::{VmConnectorPvs, DEFAULT_SUSPEND_EXIT_CODE},
64 memory::{
65 merkle::{
66 public_values::{UserPublicValuesProof, UserPublicValuesProofError},
67 MemoryMerklePvs,
68 },
69 online::{GuestMemory, TracingMemory},
70 AddressMap, CHUNK,
71 },
72 program::trace::generate_cached_trace,
73 SystemChipComplex, SystemRecords, SystemWithFixedTraceHeights,
74 },
75};
76
77pub const BABYBEAR_S_BOX_DEGREE: u64 = 7;
79
80pub trait VmField: PrimeField32 + InjectiveMonomial<BABYBEAR_S_BOX_DEGREE> {}
81impl<T> VmField for T where T: PrimeField32 + InjectiveMonomial<BABYBEAR_S_BOX_DEGREE> {}
82
83#[derive(Error, Debug)]
84pub enum GenerationError {
85 #[error("unexpected number of arenas: {actual} (expected num_airs={expected})")]
86 UnexpectedNumArenas { actual: usize, expected: usize },
87 #[error("trace height for air_idx={air_idx} must be fixed to {expected}, actual={actual}")]
88 ForceTraceHeightIncorrect {
89 air_idx: usize,
90 actual: usize,
91 expected: usize,
92 },
93 #[error("trace height of air {air_idx} has height {height} greater than maximum {max_height}")]
94 TraceHeightsLimitExceeded {
95 air_idx: usize,
96 height: usize,
97 max_height: usize,
98 },
99 #[error("trace heights violate linear constraint {constraint_idx} ({value} >= {threshold})")]
100 LinearTraceHeightConstraintExceeded {
101 constraint_idx: usize,
102 value: u64,
103 threshold: u32,
104 },
105}
106
107#[derive(Clone)]
108pub struct Streams<F> {
109 pub input_stream: VecDeque<Vec<F>>,
110 pub hint_stream: VecDeque<F>,
111 pub deferrals: Vec<DeferralState>,
114}
115
116impl<F> Streams<F> {
117 pub fn new(input_stream: impl Into<VecDeque<Vec<F>>>) -> Self {
118 Self {
119 input_stream: input_stream.into(),
120 hint_stream: VecDeque::default(),
121 deferrals: Vec::default(),
122 }
123 }
124}
125
126impl<F> Default for Streams<F> {
127 fn default() -> Self {
128 Self::new(VecDeque::default())
129 }
130}
131
132impl<F> From<VecDeque<Vec<F>>> for Streams<F> {
133 fn from(value: VecDeque<Vec<F>>) -> Self {
134 Streams::new(value)
135 }
136}
137
138impl<F> From<Vec<Vec<F>>> for Streams<F> {
139 fn from(value: Vec<Vec<F>>) -> Self {
140 Streams::new(value)
141 }
142}
143
144type PreflightInterpretedInstance2<F, VC> =
146 PreflightInterpretedInstance<F, <VC as VmExecutionConfig<F>>::Executor>;
147
148#[derive(Clone)]
154pub struct VmExecutor<F, VC>
155where
156 VC: VmExecutionConfig<F>,
157{
158 pub config: VC,
159 inventory: Arc<ExecutorInventory<VC::Executor>>,
160 phantom: PhantomData<F>,
161}
162
163#[repr(i32)]
164pub enum ExitCode {
165 Success = 0,
166 Error = 1,
167 Suspended = -1, }
169
170pub struct PreflightExecutionOutput<F, RA> {
171 pub system_records: SystemRecords<F>,
172 pub record_arenas: Vec<RA>,
173 pub to_state: VmState<F, GuestMemory>,
174}
175
176impl<F, VC> VmExecutor<F, VC>
177where
178 VC: VmExecutionConfig<F>,
179{
180 pub fn new(config: VC) -> Result<Self, ExecutorInventoryError> {
184 let inventory = config.create_executors()?;
185 Ok(Self {
186 config,
187 inventory: Arc::new(inventory),
188 phantom: PhantomData,
189 })
190 }
191}
192
193impl<F, VC> VmExecutor<F, VC>
194where
195 VC: VmExecutionConfig<F> + AsRef<SystemConfig>,
196{
197 pub fn build_metered_ctx(
198 &self,
199 inputs: MeteredCtxInputs<'_>,
200 memory_config: ProvingMemoryConfig,
201 ) -> MeteredCtx {
202 MeteredCtx::new(inputs, self.config.as_ref(), memory_config)
203 }
204
205 pub fn build_metered_cost_ctx(&self, widths: &[usize]) -> MeteredCostCtx {
206 MeteredCostCtx::new(widths.to_vec())
207 }
208}
209
210impl<F, VC> VmExecutor<F, VC>
211where
212 F: PrimeField32,
213 VC: VmExecutionConfig<F>,
214 VC::Executor: Executor<F>,
215{
216 #[cfg(not(feature = "aot"))]
221 pub fn instance(
222 &self,
223 exe: &VmExe<F>,
224 ) -> Result<InterpretedInstance<'_, F, ExecutionCtx>, StaticProgramError> {
225 InterpretedInstance::new(&self.inventory, exe)
226 }
227
228 #[cfg(feature = "aot")]
229 pub fn interpreter_instance(
230 &self,
231 exe: &VmExe<F>,
232 ) -> Result<InterpretedInstance<'_, F, ExecutionCtx>, StaticProgramError> {
233 InterpretedInstance::new(&self.inventory, exe)
234 }
235
236 #[cfg(feature = "aot")]
237 pub fn instance(
238 &self,
239 exe: &VmExe<F>,
240 ) -> Result<AotInstance<'_, F, ExecutionCtx>, StaticProgramError> {
241 Self::aot_instance(self, exe)
242 }
243}
244#[cfg(feature = "aot")]
245impl<F, VC> VmExecutor<F, VC>
246where
247 F: PrimeField32,
248 VC: VmExecutionConfig<F>,
249 VC::Executor: Executor<F>,
250{
251 pub fn aot_instance(
252 &self,
253 exe: &VmExe<F>,
254 ) -> Result<AotInstance<'_, F, ExecutionCtx>, StaticProgramError> {
255 AotInstance::new(&self.inventory, exe)
256 }
257}
258
259impl<F, VC> VmExecutor<F, VC>
260where
261 F: PrimeField32,
262 VC: VmExecutionConfig<F>,
263 VC::Executor: MeteredExecutor<F>,
264{
265 #[cfg(not(feature = "aot"))]
267 pub fn metered_instance(
268 &self,
269 exe: &VmExe<F>,
270 executor_idx_to_air_idx: &[usize],
271 ) -> Result<InterpretedInstance<'_, F, MeteredCtx>, StaticProgramError> {
272 InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
273 }
274
275 #[cfg(feature = "aot")]
276 pub fn metered_interpreter_instance(
277 &self,
278 exe: &VmExe<F>,
279 executor_idx_to_air_idx: &[usize],
280 ) -> Result<InterpretedInstance<'_, F, MeteredCtx>, StaticProgramError> {
281 InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
282 }
283
284 #[cfg(feature = "aot")]
285 pub fn metered_instance(
286 &self,
287 exe: &VmExe<F>,
288 executor_idx_to_air_idx: &[usize],
289 ) -> Result<AotInstance<'_, F, MeteredCtx>, StaticProgramError> {
290 Self::metered_aot_instance(self, exe, executor_idx_to_air_idx)
291 }
292
293 #[cfg(feature = "aot")]
295 pub fn metered_aot_instance(
296 &self,
297 exe: &VmExe<F>,
298 executor_idx_to_air_idx: &[usize],
299 ) -> Result<AotInstance<'_, F, MeteredCtx>, StaticProgramError> {
300 AotInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
301 }
302
303 pub fn metered_cost_instance(
306 &self,
307 exe: &VmExe<F>,
308 executor_idx_to_air_idx: &[usize],
309 ) -> Result<InterpretedInstance<'_, F, MeteredCostCtx>, StaticProgramError> {
310 InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
311 }
312}
313
314#[derive(Error, Debug)]
315pub enum VmVerificationError<SC: StarkProtocolConfig> {
316 #[error("no proof is provided")]
317 ProofNotFound,
318
319 #[error("program commit mismatch (index of mismatch proof: {index}")]
320 ProgramCommitMismatch { index: usize },
321
322 #[error("exe commit mismatch (expected: {expected:?}, actual: {actual:?})")]
323 ExeCommitMismatch {
324 expected: [u32; CHUNK],
325 actual: [u32; CHUNK],
326 },
327
328 #[error("initial pc mismatch (initial: {initial}, prev_final: {prev_final})")]
329 InitialPcMismatch { initial: u32, prev_final: u32 },
330
331 #[error("initial memory root mismatch")]
332 InitialMemoryRootMismatch,
333
334 #[error("is terminate mismatch (expected: {expected}, actual: {actual})")]
335 IsTerminateMismatch { expected: bool, actual: bool },
336
337 #[error("exit code mismatch")]
338 ExitCodeMismatch { expected: u32, actual: u32 },
339
340 #[error("AIR has unexpected public values (expected: {expected}, actual: {actual})")]
341 UnexpectedPvs { expected: usize, actual: usize },
342
343 #[error("Invalid number of AIRs: expected at least 3, got {0}")]
344 NotEnoughAirs(usize),
345
346 #[error("missing system AIR with ID {air_id}")]
347 SystemAirMissing { air_id: usize },
348
349 #[error("stark verification error: {0}")]
350 StarkError(#[from] VerifierError<SC::EF>),
351
352 #[error("user public values proof error: {0}")]
353 UserPublicValuesError(#[from] UserPublicValuesProofError),
354}
355
356#[derive(Error, Debug)]
357pub enum VirtualMachineError {
358 #[error("executor inventory error: {0}")]
359 ExecutorInventory(#[from] ExecutorInventoryError),
360 #[error("air inventory error: {0}")]
361 AirInventory(#[from] AirInventoryError),
362 #[error("chip inventory error: {0}")]
363 ChipInventory(#[from] ChipInventoryError),
364 #[error("static program error: {0}")]
365 StaticProgram(#[from] StaticProgramError),
366 #[error("execution error: {0}")]
367 Execution(#[from] ExecutionError),
368 #[error("trace generation error: {0}")]
369 Generation(#[from] GenerationError),
370 #[error("program committed trade data not loaded")]
371 ProgramIsNotCommitted,
372}
373
374#[derive(Getters, MutGetters, Setters, WithSetters)]
382pub struct VirtualMachine<E, VB>
383where
384 E: StarkEngine,
385 VB: VmBuilder<E>,
386{
387 pub engine: E,
389 #[getset(get = "pub")]
391 executor: VmExecutor<Val<E::SC>, VB::VmConfig>,
392 #[getset(get = "pub", get_mut = "pub")]
393 pk: DeviceMultiStarkProvingKey<E::PB>,
394 chip_complex: VmChipComplex<E::SC, VB::RecordArena, E::PB, VB::SystemChipInventory>,
395}
396
397impl<E, VB> VirtualMachine<E, VB>
398where
399 E: StarkEngine,
400 VB: VmBuilder<E>,
401{
402 pub fn new(
403 engine: E,
404 builder: VB,
405 config: VB::VmConfig,
406 d_pk: DeviceMultiStarkProvingKey<E::PB>,
407 ) -> Result<Self, VirtualMachineError> {
408 let circuit = config.create_airs()?;
409 let chip_complex =
410 builder.create_chip_complex(&config, circuit, engine.device().device_ctx())?;
411 let executor = VmExecutor::<Val<E::SC>, _>::new(config)?;
412 Ok(Self {
413 engine,
414 executor,
415 pk: d_pk,
416 chip_complex,
417 })
418 }
419
420 pub fn new_with_keygen(
421 engine: E,
422 builder: VB,
423 config: VB::VmConfig,
424 ) -> Result<(Self, MultiStarkProvingKey<E::SC>), VirtualMachineError> {
425 let circuit = config.create_airs()?;
426 let pk = circuit.keygen(engine.config());
427 let _vk = pk.get_vk();
428 let d_pk = engine.device().transport_pk_to_device(&pk);
429 let vm = Self::new(engine, builder, config, d_pk)?;
430 Ok((vm, pk))
431 }
432
433 pub fn config(&self) -> &VB::VmConfig {
434 &self.executor.config
435 }
436
437 #[cfg(not(feature = "aot"))]
439 pub fn interpreter(
440 &self,
441 exe: &VmExe<Val<E::SC>>,
442 ) -> Result<InterpretedInstance<'_, Val<E::SC>, ExecutionCtx>, StaticProgramError>
443 where
444 Val<E::SC>: PrimeField32,
445 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
446 {
447 self.executor().instance(exe)
448 }
449
450 #[cfg(feature = "aot")]
452 pub fn naive_interpreter(
453 &self,
454 exe: &VmExe<Val<E::SC>>,
455 ) -> Result<InterpretedInstance<'_, Val<E::SC>, ExecutionCtx>, StaticProgramError>
456 where
457 Val<E::SC>: PrimeField32,
458 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
459 {
460 self.executor().interpreter_instance(exe)
461 }
462
463 #[cfg(feature = "aot")]
465 pub fn interpreter(
466 &self,
467 exe: &VmExe<Val<E::SC>>,
468 ) -> Result<AotInstance<'_, Val<E::SC>, ExecutionCtx>, StaticProgramError>
469 where
470 Val<E::SC>: PrimeField32,
471 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
472 {
473 Self::get_aot_instance(self, exe)
474 }
475
476 #[cfg(feature = "aot")]
477 pub fn get_aot_instance(
478 &self,
479 exe: &VmExe<Val<E::SC>>,
480 ) -> Result<AotInstance<'_, Val<E::SC>, ExecutionCtx>, StaticProgramError>
481 where
482 Val<E::SC>: PrimeField32,
483 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
484 {
485 self.executor().aot_instance(exe)
486 }
487
488 #[cfg(not(feature = "aot"))]
489 pub fn metered_interpreter(
490 &self,
491 exe: &VmExe<Val<E::SC>>,
492 ) -> Result<InterpretedInstance<'_, Val<E::SC>, MeteredCtx>, StaticProgramError>
493 where
494 Val<E::SC>: PrimeField32,
495 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
496 {
497 let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
498 self.executor()
499 .metered_instance(exe, &executor_idx_to_air_idx)
500 }
501
502 #[cfg(feature = "aot")]
503 pub fn metered_interpreter(
504 &self,
505 exe: &VmExe<Val<E::SC>>,
506 ) -> Result<AotInstance<'_, Val<E::SC>, MeteredCtx>, StaticProgramError>
507 where
508 Val<E::SC>: PrimeField32,
509 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
510 {
511 let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
512 self.executor()
513 .metered_instance(exe, &executor_idx_to_air_idx)
514 }
515
516 #[cfg(feature = "aot")]
518 pub fn get_metered_aot_instance(
519 &self,
520 exe: &VmExe<Val<E::SC>>,
521 ) -> Result<AotInstance<'_, Val<E::SC>, MeteredCtx>, StaticProgramError>
522 where
523 Val<E::SC>: PrimeField32,
524 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
525 {
526 let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
527 self.executor()
528 .metered_aot_instance(exe, &executor_idx_to_air_idx)
529 }
530
531 #[cfg(feature = "aot")]
532 pub fn naive_metered_interpreter(
533 &self,
534 exe: &VmExe<Val<E::SC>>,
535 ) -> Result<InterpretedInstance<'_, Val<E::SC>, MeteredCtx>, StaticProgramError>
536 where
537 Val<E::SC>: PrimeField32,
538 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
539 {
540 let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
541 self.executor()
542 .metered_interpreter_instance(exe, &executor_idx_to_air_idx)
543 }
544
545 pub fn metered_cost_interpreter(
546 &self,
547 exe: &VmExe<Val<E::SC>>,
548 ) -> Result<InterpretedInstance<'_, Val<E::SC>, MeteredCostCtx>, StaticProgramError>
549 where
550 Val<E::SC>: PrimeField32,
551 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
552 {
553 let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
554 self.executor()
555 .metered_cost_instance(exe, &executor_idx_to_air_idx)
556 }
557
558 pub fn preflight_interpreter(
559 &self,
560 exe: &VmExe<Val<E::SC>>,
561 ) -> Result<PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>, StaticProgramError> {
562 PreflightInterpretedInstance::new(
563 &exe.program,
564 self.executor.inventory.clone(),
565 self.executor_idx_to_air_idx(),
566 )
567 }
568
569 #[instrument(name = "execute_preflight", skip_all)]
577 pub fn execute_preflight(
578 &self,
579 interpreter: &mut PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
580 state: VmState<Val<E::SC>, GuestMemory>,
581 num_insns: Option<u64>,
582 trace_heights: &[u32],
583 ) -> Result<PreflightExecutionOutput<Val<E::SC>, VB::RecordArena>, ExecutionError>
584 where
585 Val<E::SC>: PrimeField32,
586 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor:
587 PreflightExecutor<Val<E::SC>, VB::RecordArena>,
588 {
589 debug_assert!(interpreter
590 .executor_idx_to_air_idx
591 .iter()
592 .all(|&air_idx| air_idx < trace_heights.len()));
593
594 let main_widths = self
596 .pk
597 .per_air
598 .iter()
599 .map(|pk| pk.vk.params.width.main_width())
600 .collect_vec();
601 let capacities = zip_eq(trace_heights, main_widths)
602 .map(|(&h, w)| (h as usize, w))
603 .collect::<Vec<_>>();
604 let ctx = PreflightCtx::new_with_capacity(&capacities, num_insns);
605
606 let pc = state.pc();
607 let memory = TracingMemory::from_image(state.memory);
608 let from_state = ExecutionState::new(pc, memory.timestamp());
609 let vm_state = VmState::new(
610 pc,
611 memory,
612 state.streams,
613 state.rng,
614 #[cfg(feature = "metrics")]
615 state.metrics,
616 );
617 let mut exec_state = VmExecState::new(vm_state, ctx);
618 interpreter.reset_execution_frequencies();
619 execute_spanned!("execute_preflight", interpreter, &mut exec_state)?;
620 let filtered_exec_frequencies = interpreter.filtered_execution_frequencies();
621 #[cfg(feature = "metrics")]
622 emit_opcode_counts(
623 &exec_state.vm_state.metrics,
624 interpreter.opcode_counts_by_air::<VB::RecordArena>(),
625 );
626 let touched_memory = exec_state.vm_state.memory.finalize::<Val<E::SC>>();
627 #[cfg(feature = "perf-metrics")]
628 end_segment_metrics(&mut exec_state);
629
630 let pc = exec_state.vm_state.pc();
631 let memory = exec_state.vm_state.memory;
632 let to_state = ExecutionState::new(pc, memory.timestamp());
633 let exit_code = exec_state.exit_code?;
634 let system_records = SystemRecords {
635 from_state,
636 to_state,
637 exit_code,
638 filtered_exec_frequencies,
639 touched_memory,
640 };
641 let record_arenas = exec_state.ctx.arenas;
642 let to_state = VmState::new(
643 pc,
644 memory.data,
645 exec_state.vm_state.streams,
646 exec_state.vm_state.rng,
647 #[cfg(feature = "metrics")]
648 exec_state.vm_state.metrics,
649 );
650 Ok(PreflightExecutionOutput {
651 system_records,
652 record_arenas,
653 to_state,
654 })
655 }
656
657 #[instrument(name = "vm.create_initial_state", level = "debug", skip_all)]
660 pub fn create_initial_state(
661 &self,
662 exe: &VmExe<Val<E::SC>>,
663 inputs: impl Into<Streams<Val<E::SC>>>,
664 ) -> VmState<Val<E::SC>, GuestMemory> {
665 #[allow(unused_mut)]
666 let mut state = VmState::initial(
667 self.config().as_ref(),
668 &exe.init_memory,
669 exe.pc_start,
670 inputs,
671 );
672 #[cfg(all(feature = "metrics", any(feature = "perf-metrics", debug_assertions)))]
676 {
677 state.metrics.fn_bounds = exe.fn_bounds.clone();
678 state.metrics.debug_infos = exe.program.debug_infos();
679 }
680 #[cfg(feature = "metrics")]
681 {
682 state.metrics.set_pk_air_names(&self.pk);
683 }
684 #[cfg(feature = "perf-metrics")]
685 {
686 state.metrics.set_pk_trace_info(&self.pk);
687 state.metrics.num_sys_airs = self.config().as_ref().num_airs();
688 }
689 state
690 }
691
692 #[instrument(name = "trace_gen", skip_all)]
698 pub fn generate_proving_ctx(
699 &mut self,
700 system_records: SystemRecords<Val<E::SC>>,
701 record_arenas: Vec<VB::RecordArena>,
702 ) -> Result<ProvingContext<E::PB>, GenerationError> {
703 let ctx = self
705 .chip_complex
706 .generate_proving_ctx(system_records, record_arenas)?;
707
708 let idx_trace_heights = ctx
710 .per_trace
711 .iter()
712 .map(|(air_idx, ctx)| (*air_idx, ctx.common_main.height()))
713 .collect_vec();
714 let max_trace_height = if TypeId::of::<Val<E::SC>>() == TypeId::of::<BabyBear>() {
716 let min_log_blowup = log2_ceil_usize(self.config().as_ref().max_constraint_degree - 1);
717 1 << (BabyBear::TWO_ADICITY - min_log_blowup)
718 } else {
719 tracing::warn!(
720 "constructing VirtualMachine for unrecognized field; using max_trace_height=2^30"
721 );
722 1 << 30
723 };
724 if let Some(&(air_idx, height)) = idx_trace_heights
725 .iter()
726 .find(|(_, height)| *height > max_trace_height)
727 {
728 return Err(GenerationError::TraceHeightsLimitExceeded {
729 air_idx,
730 height,
731 max_height: max_trace_height,
732 });
733 }
734 let trace_height_constraints = &self.pk.trace_height_constraints;
736 if trace_height_constraints.is_empty() {
737 tracing::warn!("generating proving context without trace height constraints");
738 }
739 for (i, constraint) in trace_height_constraints.iter().enumerate() {
740 let value = idx_trace_heights
741 .iter()
742 .map(|&(air_idx, h)| constraint.coefficients[air_idx] as u64 * h as u64)
743 .sum::<u64>();
744
745 if value >= constraint.threshold as u64 {
746 tracing::info!(
747 "trace heights {:?} violate linear constraint {} ({} >= {})",
748 idx_trace_heights,
749 i,
750 value,
751 constraint.threshold
752 );
753 return Err(GenerationError::LinearTraceHeightConstraintExceeded {
754 constraint_idx: i,
755 value,
756 threshold: constraint.threshold,
757 });
758 }
759 }
760 #[cfg(feature = "stark-debug")]
761 self.debug_proving_ctx(&ctx);
762
763 Ok(ctx)
764 }
765
766 pub fn prove(
777 &mut self,
778 interpreter: &mut PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
779 state: VmState<Val<E::SC>, GuestMemory>,
780 num_insns: Option<u64>,
781 trace_heights: &[u32],
782 ) -> Result<(Proof<E::SC>, Option<GuestMemory>), VirtualMachineError>
783 where
784 Val<E::SC>: PrimeField32,
785 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor:
786 PreflightExecutor<Val<E::SC>, VB::RecordArena>,
787 {
788 self.transport_init_memory_to_device(&state.memory);
789
790 let PreflightExecutionOutput {
791 system_records,
792 record_arenas,
793 to_state,
794 } = self.execute_preflight(interpreter, state, num_insns, trace_heights)?;
795 let final_memory =
797 (system_records.exit_code == Some(ExitCode::Success as u32)).then_some(to_state.memory);
798 let ctx = self.generate_proving_ctx(system_records, record_arenas)?;
799 let proof = self.engine.prove(&self.pk, ctx).unwrap();
800
801 Ok((proof, final_memory))
802 }
803
804 pub fn commit_program_on_device(
811 &self,
812 program: &Program<Val<E::SC>>,
813 ) -> CommittedTraceData<E::PB> {
814 let rm_trace = generate_cached_trace(program);
815 let cm_trace = ColMajorMatrix::from_row_major(&rm_trace);
816 let d_trace = self.engine.device().transport_matrix_to_device(&cm_trace);
817 let (commitment, pcs) = self
818 .engine
819 .device()
820 .commit(std::slice::from_ref(&&d_trace))
821 .unwrap();
822 CommittedTraceData {
823 commitment,
824 trace: d_trace,
825 data: Arc::new(pcs),
826 }
827 }
828
829 pub fn load_program(&mut self, cached_program_trace: CommittedTraceData<E::PB>) {
831 self.chip_complex.system.load_program(cached_program_trace);
832 }
833
834 #[instrument(name = "vm.transport_init_memory", skip_all)]
835 pub fn transport_init_memory_to_device(&mut self, memory: &GuestMemory) {
836 self.chip_complex
837 .system
838 .transport_init_memory_to_device(memory);
839 }
840
841 pub fn memory_top_tree(&self) -> Option<&[[Val<E::SC>; CHUNK]]> {
843 self.chip_complex.system.memory_top_tree()
844 }
845
846 pub fn executor_idx_to_air_idx(&self) -> Vec<usize> {
847 let ret = self.chip_complex.inventory.executor_idx_to_air_idx();
848 tracing::debug!("executor_idx_to_air_idx: {:?}", ret);
849 assert_eq!(self.executor().inventory.executors().len(), ret.len());
850 ret
851 }
852
853 pub fn build_metered_ctx(&self, exe: &VmExe<Val<E::SC>>) -> MeteredCtx
855 where
856 Val<E::SC>: PrimeField32,
857 {
858 let program_len = exe.program.num_defined_instructions();
859
860 let (mut constant_trace_heights, air_names, widths, interactions, need_rot): (
861 Vec<_>,
862 Vec<_>,
863 Vec<_>,
864 Vec<_>,
865 Vec<_>,
866 ) = self
867 .pk
868 .per_air
869 .iter()
870 .map(|pk| {
871 let constant_trace_height = pk.preprocessed_data.as_ref().map(|cd| cd.height());
872 let air_names = pk.air_name.clone();
873 let width = pk.vk.params.width.total_width();
874 let num_interactions = pk.vk.symbolic_constraints.interactions.len();
875 let need_rot = pk.vk.params.need_rot;
876 (
877 constant_trace_height,
878 air_names,
879 width,
880 num_interactions,
881 need_rot,
882 )
883 })
884 .multiunzip();
885
886 constant_trace_heights[PROGRAM_AIR_ID] = Some(program_len);
888 constant_trace_heights[CONNECTOR_AIR_ID] = Some(2);
890 for (air_id, chip_height) in self
892 .chip_complex
893 .inventory
894 .constant_trace_heights()
895 .into_iter()
896 .enumerate()
897 {
898 if constant_trace_heights[air_id].is_none() {
899 constant_trace_heights[air_id] = chip_height;
900 }
901 }
902
903 let log_stacked_height = self
904 .engine
905 .params()
906 .log_stacked_height()
907 .try_into()
908 .expect("log_stacked_height must fit in u8");
909 self.executor().build_metered_ctx(
910 MeteredCtxInputs {
911 constant_trace_heights: &constant_trace_heights,
912 air_names: &air_names,
913 widths: &widths,
914 interactions: &interactions,
915 need_rot: &need_rot,
916 segmentation_limits: SegmentationLimits {
917 max_trace_height_bits: log_stacked_height,
918 max_memory: self.config().as_ref().segmentation_max_memory,
919 max_interactions: <Val<E::SC> as PrimeField32>::ORDER_U32,
920 },
921 },
922 self.engine.proving_memory_config(),
923 )
924 }
925
926 pub fn build_metered_cost_ctx(&self) -> MeteredCostCtx {
928 let widths: Vec<_> = self
929 .pk
930 .per_air
931 .iter()
932 .map(|pk| pk.vk.params.width.total_width())
933 .collect();
934
935 self.executor().build_metered_cost_ctx(&widths)
936 }
937
938 pub fn num_airs(&self) -> usize {
939 let num_airs = self.pk.per_air.len();
940 debug_assert_eq!(num_airs, self.chip_complex.inventory.airs().num_airs());
941 num_airs
942 }
943
944 pub fn air_names(&self) -> impl Iterator<Item = &'_ str> {
945 self.pk.per_air.iter().map(|pk| pk.air_name.as_str())
946 }
947
948 #[cfg(feature = "stark-debug")]
950 pub fn debug_proving_ctx(&mut self, ctx: &ProvingContext<E::PB>) {
951 debug_proving_ctx(self, ctx);
952 }
953}
954
955#[cfg(test)]
956mod tests {
957 use super::{SystemConfig, VirtualMachine, CONNECTOR_AIR_ID, PROGRAM_AIR_ID};
958 use crate::{system::SystemCpuBuilder, utils::test_cpu_engine};
959
960 #[test]
961 fn keygen_marks_required_airs_for_continuations() {
962 let engine = test_cpu_engine();
963 let config = SystemConfig::default();
964 let merkle_air_id = config.memory_merkle_air_id();
965 let boundary_air_id = config.memory_boundary_air_id();
966
967 let (_vm, pk) = VirtualMachine::new_with_keygen(engine, SystemCpuBuilder, config).unwrap();
968
969 assert!(pk.per_air[PROGRAM_AIR_ID].vk.is_required);
970 assert!(pk.per_air[CONNECTOR_AIR_ID].vk.is_required);
971 assert!(pk.per_air[merkle_air_id].vk.is_required);
972 assert!(pk.per_air[boundary_air_id].vk.is_required);
973 }
974}
975
976#[derive(Serialize, Deserialize)]
977#[serde(bound(
978 serialize = "Com<SC>: Serialize",
979 deserialize = "Com<SC>: Deserialize<'de>"
980))]
981pub struct ContinuationVmProof<SC: StarkProtocolConfig> {
982 pub per_segment: Vec<Proof<SC>>,
983 pub user_public_values: UserPublicValuesProof<{ CHUNK }, Val<SC>>,
984}
985
986pub trait ContinuationVmProver<SC: StarkProtocolConfig> {
988 fn prove(
989 &mut self,
990 input: impl Into<Streams<Val<SC>>>,
991 ) -> Result<ContinuationVmProof<SC>, VirtualMachineError>;
992}
993
994#[derive(Getters, MutGetters)]
1000pub struct VmInstance<E, VB>
1001where
1002 E: StarkEngine,
1003 VB: VmBuilder<E>,
1004{
1005 pub vm: VirtualMachine<E, VB>,
1006 pub interpreter: PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
1007 #[getset(get = "pub")]
1008 program_commitment: <E::PB as ProverBackend>::Commitment,
1009 #[getset(get = "pub")]
1010 exe: Arc<VmExe<Val<E::SC>>>,
1011 #[getset(get = "pub", get_mut = "pub")]
1012 state: Option<VmState<Val<E::SC>, GuestMemory>>,
1013}
1014
1015impl<E, VB> VmInstance<E, VB>
1016where
1017 E: StarkEngine,
1018 VB: VmBuilder<E>,
1019{
1020 pub fn new(
1021 mut vm: VirtualMachine<E, VB>,
1022 exe: Arc<VmExe<Val<E::SC>>>,
1023 cached_program_trace: CommittedTraceData<E::PB>,
1024 ) -> Result<Self, StaticProgramError> {
1025 let program_commitment = cached_program_trace.commitment;
1026 vm.load_program(cached_program_trace);
1027 let interpreter = vm.preflight_interpreter(&exe)?;
1028 let state = vm.create_initial_state(&exe, vec![]);
1029 Ok(Self {
1030 vm,
1031 interpreter,
1032 program_commitment,
1033 exe,
1034 state: Some(state),
1035 })
1036 }
1037
1038 #[instrument(name = "vm.reset_state", level = "debug", skip_all)]
1039 pub fn reset_state(&mut self, inputs: impl Into<Streams<Val<E::SC>>>) {
1040 let state = self.state.as_mut().unwrap();
1041 state.reset(&self.exe.init_memory, self.exe.pc_start, inputs);
1042
1043 #[cfg(all(feature = "metrics", any(feature = "perf-metrics", debug_assertions)))]
1044 {
1045 state.metrics.fn_bounds = self.exe.fn_bounds.clone();
1046 state.metrics.debug_infos = self.exe.program.debug_infos();
1047 }
1048 }
1049}
1050
1051impl<E, VB> ContinuationVmProver<E::SC> for VmInstance<E, VB>
1052where
1053 E: StarkEngine,
1054 Val<E::SC>: PrimeField32,
1055 VB: VmBuilder<E>,
1056 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1057 + MeteredExecutor<Val<E::SC>>
1058 + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1059{
1060 fn prove(
1064 &mut self,
1065 input: impl Into<Streams<Val<E::SC>>>,
1066 ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1067 self.prove_continuations(input, |_, _| {})
1068 }
1069}
1070
1071impl<E, VB> VmInstance<E, VB>
1072where
1073 E: StarkEngine,
1074 Val<E::SC>: PrimeField32,
1075 VB: VmBuilder<E>,
1076 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1077 + MeteredExecutor<Val<E::SC>>
1078 + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1079{
1080 pub fn prove_continuations(
1084 &mut self,
1085 input: impl Into<Streams<Val<E::SC>>>,
1086 mut modify_ctx: impl FnMut(usize, &mut ProvingContext<E::PB>),
1087 ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1088 let input = input.into();
1089 self.reset_state(input.clone());
1090 let vm = &mut self.vm;
1091 let metered_ctx = vm.build_metered_ctx(&self.exe);
1092 let metered_interpreter = vm.metered_interpreter(&self.exe)?;
1093 let (segments, _) = metered_interpreter.execute_metered(input, metered_ctx)?;
1094 let mut proofs = Vec::with_capacity(segments.len());
1095 let mut state = self.state.take();
1096 for (seg_idx, segment) in segments.into_iter().enumerate() {
1097 let _segment_span = info_span!("prove_segment", segment = seg_idx).entered();
1098 let _prove_span = info_span!("total_proof").entered();
1100 let Segment {
1101 num_insns,
1102 trace_heights,
1103 ..
1104 } = segment;
1105 let from_state = Option::take(&mut state).unwrap();
1106 vm.transport_init_memory_to_device(&from_state.memory);
1107 let PreflightExecutionOutput {
1108 system_records,
1109 record_arenas,
1110 to_state,
1111 } = vm.execute_preflight(
1112 &mut self.interpreter,
1113 from_state,
1114 Some(num_insns),
1115 &trace_heights,
1116 )?;
1117 state = Some(to_state);
1118
1119 let mut ctx = vm.generate_proving_ctx(system_records, record_arenas)?;
1120 modify_ctx(seg_idx, &mut ctx);
1121 let proof = vm.engine.prove(vm.pk(), ctx).unwrap();
1122 proofs.push(proof);
1123 }
1124 let to_state = state.unwrap();
1125 let final_memory = &to_state.memory.memory;
1126 let final_memory_top_tree = vm.memory_top_tree().expect("memory top tree should exist");
1127 let user_public_values = UserPublicValuesProof::compute(
1128 vm.config().as_ref().memory_config.memory_dimensions(),
1129 vm.config().as_ref().num_public_values,
1130 &vm_poseidon2_hasher(),
1131 final_memory,
1132 final_memory_top_tree,
1133 );
1134 self.state = Some(to_state);
1135 Ok(ContinuationVmProof {
1136 per_segment: proofs,
1137 user_public_values,
1138 })
1139 }
1140}
1141
1142pub struct VerifiedExecutionPayload<F> {
1144 pub exe_commit: [F; CHUNK],
1152 pub final_memory_root: [F; CHUNK],
1154}
1155
1156pub fn verify_segments<E>(
1173 engine: &E,
1174 vk: &MultiStarkVerifyingKey<E::SC>,
1175 proofs: &[Proof<E::SC>],
1176) -> Result<VerifiedExecutionPayload<Val<E::SC>>, VmVerificationError<E::SC>>
1177where
1178 E: StarkEngine,
1179 Val<E::SC>: PrimeField32,
1180 Com<E::SC>: Into<[Val<E::SC>; CHUNK]>,
1181{
1182 if proofs.is_empty() {
1183 return Err(VmVerificationError::ProofNotFound);
1184 }
1185 let mut prev_final_memory_root = None;
1186 let mut prev_final_pc = None;
1187 let mut start_pc = None;
1188 let mut initial_memory_root = None;
1189 let mut program_commit = None;
1190
1191 for (i, proof) in proofs.iter().enumerate() {
1192 let res = engine.verify(vk, proof);
1193 match res {
1194 Ok(_) => (),
1195 Err(e) => return Err(VmVerificationError::StarkError(e)),
1196 };
1197
1198 let mut program_air_present = false;
1199 let mut connector_air_present = false;
1200 let mut boundary_air_present = false;
1201 let mut merkle_air_present = false;
1202
1203 for (air_idx, (vdata, pvs)) in proof
1205 .trace_vdata
1206 .iter()
1207 .zip(proof.public_values.iter())
1208 .enumerate()
1209 {
1210 let air_vk = &vk.inner.per_air[air_idx];
1211 if air_idx == PROGRAM_AIR_ID {
1212 program_air_present = true;
1213 let vdata = vdata.as_ref().unwrap();
1214 if i == 0 {
1215 program_commit = Some(vdata.cached_commitments[PROGRAM_CACHED_TRACE_INDEX]);
1216 } else if program_commit.unwrap()
1217 != vdata.cached_commitments[PROGRAM_CACHED_TRACE_INDEX]
1218 {
1219 return Err(VmVerificationError::ProgramCommitMismatch { index: i });
1220 }
1221 } else if air_idx == CONNECTOR_AIR_ID {
1222 connector_air_present = true;
1223 let pvs: &VmConnectorPvs<_> = pvs.as_slice().borrow();
1224
1225 if i != 0 {
1226 if pvs.initial_pc != prev_final_pc.unwrap() {
1228 return Err(VmVerificationError::InitialPcMismatch {
1229 initial: pvs.initial_pc.as_canonical_u32(),
1230 prev_final: prev_final_pc.unwrap().as_canonical_u32(),
1231 });
1232 }
1233 } else {
1234 start_pc = Some(pvs.initial_pc);
1235 }
1236 prev_final_pc = Some(pvs.final_pc);
1237
1238 let expected_is_terminate = i == proofs.len() - 1;
1239 if pvs.is_terminate != PrimeCharacteristicRing::from_bool(expected_is_terminate) {
1240 return Err(VmVerificationError::IsTerminateMismatch {
1241 expected: expected_is_terminate,
1242 actual: pvs.is_terminate.as_canonical_u32() != 0,
1243 });
1244 }
1245
1246 let expected_exit_code = if expected_is_terminate {
1247 ExitCode::Success as u32
1248 } else {
1249 DEFAULT_SUSPEND_EXIT_CODE
1250 };
1251 if pvs.exit_code != PrimeCharacteristicRing::from_u32(expected_exit_code) {
1252 return Err(VmVerificationError::ExitCodeMismatch {
1253 expected: expected_exit_code,
1254 actual: pvs.exit_code.as_canonical_u32(),
1255 });
1256 }
1257 } else if air_idx == BOUNDARY_AIR_ID {
1258 boundary_air_present = vdata.is_some();
1259 if !pvs.is_empty() {
1260 return Err(VmVerificationError::UnexpectedPvs {
1261 expected: 0,
1262 actual: pvs.len(),
1263 });
1264 }
1265 } else if air_idx == MERKLE_AIR_ID {
1266 merkle_air_present = true;
1267 let pvs: &MemoryMerklePvs<_, CHUNK> = pvs.as_slice().borrow();
1268
1269 if i != 0 {
1271 if pvs.initial_root != prev_final_memory_root.unwrap() {
1272 return Err(VmVerificationError::InitialMemoryRootMismatch);
1273 }
1274 } else {
1275 initial_memory_root = Some(pvs.initial_root);
1276 }
1277 prev_final_memory_root = Some(pvs.final_root);
1278 } else {
1279 if !pvs.is_empty() {
1280 return Err(VmVerificationError::UnexpectedPvs {
1281 expected: 0,
1282 actual: pvs.len(),
1283 });
1284 }
1285 debug_assert_eq!(air_vk.params.num_public_values, 0);
1287 }
1288 }
1289 if !program_air_present {
1290 return Err(VmVerificationError::SystemAirMissing {
1291 air_id: PROGRAM_AIR_ID,
1292 });
1293 }
1294 if !connector_air_present {
1295 return Err(VmVerificationError::SystemAirMissing {
1296 air_id: CONNECTOR_AIR_ID,
1297 });
1298 }
1299 if !boundary_air_present {
1300 return Err(VmVerificationError::SystemAirMissing {
1301 air_id: BOUNDARY_AIR_ID,
1302 });
1303 }
1304 if !merkle_air_present {
1305 return Err(VmVerificationError::SystemAirMissing {
1306 air_id: MERKLE_AIR_ID,
1307 });
1308 }
1309 }
1310 let exe_commit = compute_exe_commit(
1311 &vm_poseidon2_hasher(),
1312 &program_commit.unwrap().into(),
1313 initial_memory_root.as_ref().unwrap(),
1314 start_pc.unwrap(),
1315 );
1316 Ok(VerifiedExecutionPayload {
1317 exe_commit,
1318 final_memory_root: prev_final_memory_root.unwrap(),
1319 })
1320}
1321
1322impl<SC: StarkProtocolConfig> Clone for ContinuationVmProof<SC>
1323where
1324 Com<SC>: Clone,
1325{
1326 fn clone(&self) -> Self {
1327 Self {
1328 per_segment: self.per_segment.clone(),
1329 user_public_values: self.user_public_values.clone(),
1330 }
1331 }
1332}
1333
1334pub(super) fn create_memory_image(
1335 memory_config: &MemoryConfig,
1336 init_memory: &SparseMemoryImage,
1337) -> GuestMemory {
1338 let mut inner = AddressMap::new(memory_config.addr_spaces.clone());
1339 inner.set_from_sparse(init_memory);
1340 GuestMemory::new(inner)
1341}
1342
1343impl<E, VC> VirtualMachine<E, VC>
1344where
1345 E: StarkEngine,
1346 VC: VmBuilder<E>,
1347 VC::SystemChipInventory: SystemWithFixedTraceHeights,
1348{
1349 pub fn override_system_trace_heights(&mut self, heights: &[u32]) {
1351 let num_sys_airs = self.config().as_ref().num_airs();
1352 assert!(heights.len() >= num_sys_airs);
1353 self.chip_complex
1354 .system
1355 .override_trace_heights(&heights[..num_sys_airs]);
1356 }
1357}
1358
1359#[cfg(any(debug_assertions, feature = "test-utils", feature = "stark-debug"))]
1369#[tracing::instrument(level = "debug", skip_all)]
1370pub fn debug_proving_ctx<E, VB>(vm: &VirtualMachine<E, VB>, ctx: &ProvingContext<E::PB>)
1371where
1372 E: StarkEngine,
1373 VB: VmBuilder<E>,
1374{
1375 let air_inv = vm.config().create_airs().unwrap();
1376 let global_airs: Vec<AirRef<E::SC>> = air_inv.into_airs().map(|a| a as AirRef<_>).collect();
1377 vm.engine.debug(&global_airs, ctx);
1378}