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 (
861 mut constant_trace_heights,
862 air_names,
863 widths,
864 interactions,
865 need_rot,
866 constraint_eval_buffers,
867 ): (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = self
868 .pk
869 .per_air
870 .iter()
871 .map(|pk| {
872 let constant_trace_height = pk.preprocessed_data.as_ref().map(|cd| cd.height());
873 let air_names = pk.air_name.clone();
874 let width = pk.vk.params.width.total_width();
875 let num_interactions = pk.vk.symbolic_constraints.interactions.len();
876 let need_rot = pk.vk.params.need_rot;
877 let constraint_eval_buffer = E::PB::constraint_eval_buffer_size(pk);
878 (
879 constant_trace_height,
880 air_names,
881 width,
882 num_interactions,
883 need_rot,
884 constraint_eval_buffer,
885 )
886 })
887 .multiunzip();
888
889 constant_trace_heights[PROGRAM_AIR_ID] = Some(program_len);
891 constant_trace_heights[CONNECTOR_AIR_ID] = Some(2);
893 for (air_id, chip_height) in self
895 .chip_complex
896 .inventory
897 .constant_trace_heights()
898 .into_iter()
899 .enumerate()
900 {
901 if constant_trace_heights[air_id].is_none() {
902 constant_trace_heights[air_id] = chip_height;
903 }
904 }
905
906 let log_stacked_height = self
907 .engine
908 .params()
909 .log_stacked_height()
910 .try_into()
911 .expect("log_stacked_height must fit in u8");
912 self.executor().build_metered_ctx(
913 MeteredCtxInputs {
914 constant_trace_heights: &constant_trace_heights,
915 air_names: &air_names,
916 widths: &widths,
917 interactions: &interactions,
918 need_rot: &need_rot,
919 constraint_eval_buffers: &constraint_eval_buffers,
920 segmentation_limits: SegmentationLimits {
921 max_trace_height_bits: log_stacked_height,
922 max_memory: self.config().as_ref().segmentation_max_memory,
923 max_interactions: <Val<E::SC> as PrimeField32>::ORDER_U32,
924 },
925 },
926 self.engine.proving_memory_config(),
927 )
928 }
929
930 pub fn build_metered_cost_ctx(&self) -> MeteredCostCtx {
932 let widths: Vec<_> = self
933 .pk
934 .per_air
935 .iter()
936 .map(|pk| pk.vk.params.width.total_width())
937 .collect();
938
939 self.executor().build_metered_cost_ctx(&widths)
940 }
941
942 pub fn num_airs(&self) -> usize {
943 let num_airs = self.pk.per_air.len();
944 debug_assert_eq!(num_airs, self.chip_complex.inventory.airs().num_airs());
945 num_airs
946 }
947
948 pub fn air_names(&self) -> impl Iterator<Item = &'_ str> {
949 self.pk.per_air.iter().map(|pk| pk.air_name.as_str())
950 }
951
952 #[cfg(feature = "stark-debug")]
954 pub fn debug_proving_ctx(&mut self, ctx: &ProvingContext<E::PB>) {
955 debug_proving_ctx(self, ctx);
956 }
957}
958
959#[cfg(test)]
960mod tests {
961 use super::{SystemConfig, VirtualMachine, CONNECTOR_AIR_ID, PROGRAM_AIR_ID};
962 use crate::{system::SystemCpuBuilder, utils::test_cpu_engine};
963
964 #[test]
965 fn keygen_marks_required_airs_for_continuations() {
966 let engine = test_cpu_engine();
967 let config = SystemConfig::default();
968 let merkle_air_id = config.memory_merkle_air_id();
969 let boundary_air_id = config.memory_boundary_air_id();
970
971 let (_vm, pk) = VirtualMachine::new_with_keygen(engine, SystemCpuBuilder, config).unwrap();
972
973 assert!(pk.per_air[PROGRAM_AIR_ID].vk.is_required);
974 assert!(pk.per_air[CONNECTOR_AIR_ID].vk.is_required);
975 assert!(pk.per_air[merkle_air_id].vk.is_required);
976 assert!(pk.per_air[boundary_air_id].vk.is_required);
977 }
978}
979
980#[derive(Serialize, Deserialize)]
981#[serde(bound(
982 serialize = "Com<SC>: Serialize",
983 deserialize = "Com<SC>: Deserialize<'de>"
984))]
985pub struct ContinuationVmProof<SC: StarkProtocolConfig> {
986 pub per_segment: Vec<Proof<SC>>,
987 pub user_public_values: UserPublicValuesProof<{ CHUNK }, Val<SC>>,
988}
989
990pub trait ContinuationVmProver<SC: StarkProtocolConfig> {
992 fn prove(
993 &mut self,
994 input: impl Into<Streams<Val<SC>>>,
995 ) -> Result<ContinuationVmProof<SC>, VirtualMachineError>;
996}
997
998#[derive(Getters, MutGetters)]
1004pub struct VmInstance<E, VB>
1005where
1006 E: StarkEngine,
1007 VB: VmBuilder<E>,
1008{
1009 pub vm: VirtualMachine<E, VB>,
1010 pub interpreter: PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
1011 #[getset(get = "pub")]
1012 program_commitment: <E::PB as ProverBackend>::Commitment,
1013 #[getset(get = "pub")]
1014 exe: Arc<VmExe<Val<E::SC>>>,
1015 #[getset(get = "pub", get_mut = "pub")]
1016 state: Option<VmState<Val<E::SC>, GuestMemory>>,
1017}
1018
1019impl<E, VB> VmInstance<E, VB>
1020where
1021 E: StarkEngine,
1022 VB: VmBuilder<E>,
1023{
1024 pub fn new(
1025 mut vm: VirtualMachine<E, VB>,
1026 exe: Arc<VmExe<Val<E::SC>>>,
1027 cached_program_trace: CommittedTraceData<E::PB>,
1028 ) -> Result<Self, StaticProgramError> {
1029 let program_commitment = cached_program_trace.commitment;
1030 vm.load_program(cached_program_trace);
1031 let interpreter = vm.preflight_interpreter(&exe)?;
1032 let state = vm.create_initial_state(&exe, vec![]);
1033 Ok(Self {
1034 vm,
1035 interpreter,
1036 program_commitment,
1037 exe,
1038 state: Some(state),
1039 })
1040 }
1041
1042 #[instrument(name = "vm.reset_state", level = "debug", skip_all)]
1043 pub fn reset_state(&mut self, inputs: impl Into<Streams<Val<E::SC>>>) {
1044 let state = self.state.as_mut().unwrap();
1045 state.reset(&self.exe.init_memory, self.exe.pc_start, inputs);
1046
1047 #[cfg(all(feature = "metrics", any(feature = "perf-metrics", debug_assertions)))]
1048 {
1049 state.metrics.fn_bounds = self.exe.fn_bounds.clone();
1050 state.metrics.debug_infos = self.exe.program.debug_infos();
1051 }
1052 }
1053}
1054
1055impl<E, VB> ContinuationVmProver<E::SC> for VmInstance<E, VB>
1056where
1057 E: StarkEngine,
1058 Val<E::SC>: PrimeField32,
1059 VB: VmBuilder<E>,
1060 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1061 + MeteredExecutor<Val<E::SC>>
1062 + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1063{
1064 fn prove(
1068 &mut self,
1069 input: impl Into<Streams<Val<E::SC>>>,
1070 ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1071 self.prove_continuations(input, |_, _| {})
1072 }
1073}
1074
1075impl<E, VB> VmInstance<E, VB>
1076where
1077 E: StarkEngine,
1078 Val<E::SC>: PrimeField32,
1079 VB: VmBuilder<E>,
1080 <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1081 + MeteredExecutor<Val<E::SC>>
1082 + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1083{
1084 pub fn prove_continuations(
1088 &mut self,
1089 input: impl Into<Streams<Val<E::SC>>>,
1090 mut modify_ctx: impl FnMut(usize, &mut ProvingContext<E::PB>),
1091 ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1092 let input = input.into();
1093 self.reset_state(input.clone());
1094 let vm = &mut self.vm;
1095 let metered_ctx = vm.build_metered_ctx(&self.exe);
1096 let metered_interpreter = vm.metered_interpreter(&self.exe)?;
1097 let (segments, _) = metered_interpreter.execute_metered(input, metered_ctx)?;
1098 let mut proofs = Vec::with_capacity(segments.len());
1099 let mut state = self.state.take();
1100 for (seg_idx, segment) in segments.into_iter().enumerate() {
1101 let _segment_span = info_span!("prove_segment", segment = seg_idx).entered();
1102 let _prove_span = info_span!("total_proof").entered();
1104 let Segment {
1105 num_insns,
1106 trace_heights,
1107 ..
1108 } = segment;
1109 let from_state = Option::take(&mut state).unwrap();
1110 vm.transport_init_memory_to_device(&from_state.memory);
1111 let PreflightExecutionOutput {
1112 system_records,
1113 record_arenas,
1114 to_state,
1115 } = vm.execute_preflight(
1116 &mut self.interpreter,
1117 from_state,
1118 Some(num_insns),
1119 &trace_heights,
1120 )?;
1121 state = Some(to_state);
1122
1123 let mut ctx = vm.generate_proving_ctx(system_records, record_arenas)?;
1124 modify_ctx(seg_idx, &mut ctx);
1125 let proof = vm.engine.prove(vm.pk(), ctx).unwrap();
1126 proofs.push(proof);
1127 }
1128 let to_state = state.unwrap();
1129 let final_memory = &to_state.memory.memory;
1130 let final_memory_top_tree = vm.memory_top_tree().expect("memory top tree should exist");
1131 let user_public_values = UserPublicValuesProof::compute(
1132 vm.config().as_ref().memory_config.memory_dimensions(),
1133 vm.config().as_ref().num_public_values,
1134 &vm_poseidon2_hasher(),
1135 final_memory,
1136 final_memory_top_tree,
1137 );
1138 self.state = Some(to_state);
1139 Ok(ContinuationVmProof {
1140 per_segment: proofs,
1141 user_public_values,
1142 })
1143 }
1144}
1145
1146pub struct VerifiedExecutionPayload<F> {
1148 pub exe_commit: [F; CHUNK],
1156 pub final_memory_root: [F; CHUNK],
1158}
1159
1160pub fn verify_segments<E>(
1177 engine: &E,
1178 vk: &MultiStarkVerifyingKey<E::SC>,
1179 proofs: &[Proof<E::SC>],
1180) -> Result<VerifiedExecutionPayload<Val<E::SC>>, VmVerificationError<E::SC>>
1181where
1182 E: StarkEngine,
1183 Val<E::SC>: PrimeField32,
1184 Com<E::SC>: Into<[Val<E::SC>; CHUNK]>,
1185{
1186 if proofs.is_empty() {
1187 return Err(VmVerificationError::ProofNotFound);
1188 }
1189 let mut prev_final_memory_root = None;
1190 let mut prev_final_pc = None;
1191 let mut start_pc = None;
1192 let mut initial_memory_root = None;
1193 let mut program_commit = None;
1194
1195 for (i, proof) in proofs.iter().enumerate() {
1196 let res = engine.verify(vk, proof);
1197 match res {
1198 Ok(_) => (),
1199 Err(e) => return Err(VmVerificationError::StarkError(e)),
1200 };
1201
1202 let mut program_air_present = false;
1203 let mut connector_air_present = false;
1204 let mut boundary_air_present = false;
1205 let mut merkle_air_present = false;
1206
1207 for (air_idx, (vdata, pvs)) in proof
1209 .trace_vdata
1210 .iter()
1211 .zip(proof.public_values.iter())
1212 .enumerate()
1213 {
1214 let air_vk = &vk.inner.per_air[air_idx];
1215 if air_idx == PROGRAM_AIR_ID {
1216 program_air_present = true;
1217 let vdata = vdata.as_ref().unwrap();
1218 if i == 0 {
1219 program_commit = Some(vdata.cached_commitments[PROGRAM_CACHED_TRACE_INDEX]);
1220 } else if program_commit.unwrap()
1221 != vdata.cached_commitments[PROGRAM_CACHED_TRACE_INDEX]
1222 {
1223 return Err(VmVerificationError::ProgramCommitMismatch { index: i });
1224 }
1225 } else if air_idx == CONNECTOR_AIR_ID {
1226 connector_air_present = true;
1227 let pvs: &VmConnectorPvs<_> = pvs.as_slice().borrow();
1228
1229 if i != 0 {
1230 if pvs.initial_pc != prev_final_pc.unwrap() {
1232 return Err(VmVerificationError::InitialPcMismatch {
1233 initial: pvs.initial_pc.as_canonical_u32(),
1234 prev_final: prev_final_pc.unwrap().as_canonical_u32(),
1235 });
1236 }
1237 } else {
1238 start_pc = Some(pvs.initial_pc);
1239 }
1240 prev_final_pc = Some(pvs.final_pc);
1241
1242 let expected_is_terminate = i == proofs.len() - 1;
1243 if pvs.is_terminate != PrimeCharacteristicRing::from_bool(expected_is_terminate) {
1244 return Err(VmVerificationError::IsTerminateMismatch {
1245 expected: expected_is_terminate,
1246 actual: pvs.is_terminate.as_canonical_u32() != 0,
1247 });
1248 }
1249
1250 let expected_exit_code = if expected_is_terminate {
1251 ExitCode::Success as u32
1252 } else {
1253 DEFAULT_SUSPEND_EXIT_CODE
1254 };
1255 if pvs.exit_code != PrimeCharacteristicRing::from_u32(expected_exit_code) {
1256 return Err(VmVerificationError::ExitCodeMismatch {
1257 expected: expected_exit_code,
1258 actual: pvs.exit_code.as_canonical_u32(),
1259 });
1260 }
1261 } else if air_idx == BOUNDARY_AIR_ID {
1262 boundary_air_present = vdata.is_some();
1263 if !pvs.is_empty() {
1264 return Err(VmVerificationError::UnexpectedPvs {
1265 expected: 0,
1266 actual: pvs.len(),
1267 });
1268 }
1269 } else if air_idx == MERKLE_AIR_ID {
1270 merkle_air_present = true;
1271 let pvs: &MemoryMerklePvs<_, CHUNK> = pvs.as_slice().borrow();
1272
1273 if i != 0 {
1275 if pvs.initial_root != prev_final_memory_root.unwrap() {
1276 return Err(VmVerificationError::InitialMemoryRootMismatch);
1277 }
1278 } else {
1279 initial_memory_root = Some(pvs.initial_root);
1280 }
1281 prev_final_memory_root = Some(pvs.final_root);
1282 } else {
1283 if !pvs.is_empty() {
1284 return Err(VmVerificationError::UnexpectedPvs {
1285 expected: 0,
1286 actual: pvs.len(),
1287 });
1288 }
1289 debug_assert_eq!(air_vk.params.num_public_values, 0);
1291 }
1292 }
1293 if !program_air_present {
1294 return Err(VmVerificationError::SystemAirMissing {
1295 air_id: PROGRAM_AIR_ID,
1296 });
1297 }
1298 if !connector_air_present {
1299 return Err(VmVerificationError::SystemAirMissing {
1300 air_id: CONNECTOR_AIR_ID,
1301 });
1302 }
1303 if !boundary_air_present {
1304 return Err(VmVerificationError::SystemAirMissing {
1305 air_id: BOUNDARY_AIR_ID,
1306 });
1307 }
1308 if !merkle_air_present {
1309 return Err(VmVerificationError::SystemAirMissing {
1310 air_id: MERKLE_AIR_ID,
1311 });
1312 }
1313 }
1314 let exe_commit = compute_exe_commit(
1315 &vm_poseidon2_hasher(),
1316 &program_commit.unwrap().into(),
1317 initial_memory_root.as_ref().unwrap(),
1318 start_pc.unwrap(),
1319 );
1320 Ok(VerifiedExecutionPayload {
1321 exe_commit,
1322 final_memory_root: prev_final_memory_root.unwrap(),
1323 })
1324}
1325
1326impl<SC: StarkProtocolConfig> Clone for ContinuationVmProof<SC>
1327where
1328 Com<SC>: Clone,
1329{
1330 fn clone(&self) -> Self {
1331 Self {
1332 per_segment: self.per_segment.clone(),
1333 user_public_values: self.user_public_values.clone(),
1334 }
1335 }
1336}
1337
1338pub(super) fn create_memory_image(
1339 memory_config: &MemoryConfig,
1340 init_memory: &SparseMemoryImage,
1341) -> GuestMemory {
1342 let mut inner = AddressMap::new(memory_config.addr_spaces.clone());
1343 inner.set_from_sparse(init_memory);
1344 GuestMemory::new(inner)
1345}
1346
1347impl<E, VC> VirtualMachine<E, VC>
1348where
1349 E: StarkEngine,
1350 VC: VmBuilder<E>,
1351 VC::SystemChipInventory: SystemWithFixedTraceHeights,
1352{
1353 pub fn override_system_trace_heights(&mut self, heights: &[u32]) {
1355 let num_sys_airs = self.config().as_ref().num_airs();
1356 assert!(heights.len() >= num_sys_airs);
1357 self.chip_complex
1358 .system
1359 .override_trace_heights(&heights[..num_sys_airs]);
1360 }
1361}
1362
1363#[cfg(any(debug_assertions, feature = "test-utils", feature = "stark-debug"))]
1373#[tracing::instrument(level = "debug", skip_all)]
1374pub fn debug_proving_ctx<E, VB>(vm: &VirtualMachine<E, VB>, ctx: &ProvingContext<E::PB>)
1375where
1376 E: StarkEngine,
1377 VB: VmBuilder<E>,
1378{
1379 let air_inv = vm.config().create_airs().unwrap();
1380 let global_airs: Vec<AirRef<E::SC>> = air_inv.into_airs().map(|a| a as AirRef<_>).collect();
1381 vm.engine.debug(&global_airs, ctx);
1382}