openvm_circuit/arch/
vm.rs

1//! [VmExecutor] is the struct that can execute an _arbitrary_ program, provided in the form of a
2//! [VmExe](openvm_instructions::exe::VmExe), for a fixed set of OpenVM instructions
3//! corresponding to a [VmExecutionConfig].
4//! Internally once it is given a program, it will preprocess the program to rewrite it into a more
5//! optimized format for runtime execution. This **instance** of the executor will be a separate
6//! struct specialized to running a _fixed_ program on different program inputs.
7//!
8//! [VirtualMachine] will similarly be the struct that has done all the setup so it can
9//! execute+prove an arbitrary program for a fixed config - it will internally still hold VmExecutor
10use 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
77/// Canonical field bound for VM execution/circuit code.
78pub 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    /// Stores cached deferred operation inputs and outputs. Each idx corresponds to a
112    /// unique function that is constrained outside the VM in its own deferral circuit.
113    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
144/// Typedef for [PreflightInterpretedInstance] that is generic in `VC: VmExecutionConfig<F>`
145type PreflightInterpretedInstance2<F, VC> =
146    PreflightInterpretedInstance<F, <VC as VmExecutionConfig<F>>::Executor>;
147
148/// [VmExecutor] is the struct that can execute an _arbitrary_ program, provided in the form of a
149/// [VmExe], for a fixed set of OpenVM instructions corresponding to a [VmExecutionConfig].
150/// Internally once it is given a program, it will preprocess the program to rewrite it into a more
151/// optimized format for runtime execution. This **instance** of the executor will be a separate
152/// struct specialized to running a _fixed_ program on different program inputs.
153#[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, // Continuations
168}
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    /// Create a new VM executor with a given config.
181    ///
182    /// The VM will start with a single segment, which is created from the initial state.
183    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    /// Creates an instance of the interpreter specialized for pure execution, without metering, of
217    /// the given `exe`.
218    ///
219    /// For metered execution, use the [`metered_instance`](Self::metered_instance) constructor.
220    #[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    /// Creates an instance of the interpreter specialized for metered execution of the given `exe`.
266    #[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    // Crates an AOT instance for metered execution of the given `exe`.
294    #[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    /// Creates an instance of the interpreter specialized for cost metering execution of the given
304    /// `exe`.
305    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/// The [VirtualMachine] struct contains the API to generate proofs for _arbitrary_ programs for a
375/// fixed set of OpenVM instructions and a fixed VM circuit corresponding to those instructions. The
376/// API is specific to a particular [StarkEngine], which specifies a fixed [StarkProtocolConfig] and
377/// [ProverBackend] via associated types. The [VmBuilder] also fixes the choice of
378/// `RecordArena` associated to the prover backend via an associated type.
379///
380/// In other words, this struct _is_ the zkVM.
381#[derive(Getters, MutGetters, Setters, WithSetters)]
382pub struct VirtualMachine<E, VB>
383where
384    E: StarkEngine,
385    VB: VmBuilder<E>,
386{
387    /// Proving engine
388    pub engine: E,
389    /// Runtime executor
390    #[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    /// Pure interpreter.
438    #[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    // Pure AOT execution
451    #[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    // Pure AOT execution
464    #[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    // Metered AOT execution
517    #[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    /// Preflight execution for a single segment. Executes for exactly `num_insns` instructions
570    /// using an interpreter. Preflight execution must be provided with `trace_heights`
571    /// instrumentation data that was collected from a previous run of metered execution so that the
572    /// preflight execution knows how much memory to allocate for record arenas.
573    ///
574    /// This function should rarely be called on its own. Users are advised to call
575    /// [`prove`](Self::prove) directly.
576    #[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        // TODO[jpw]: figure out how to compute RA specific main_widths
595        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    /// Calls [`VmState::initial`] but sets more information for
658    /// performance metrics when feature "perf-metrics" is enabled.
659    #[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        // Add backtrace information for either:
673        // - debugging
674        // - performance metrics
675        #[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    /// This function mutates `self` but should only depend on internal state in the sense that:
693    /// - program must already be loaded as cached trace via [`load_program`](Self::load_program).
694    /// - initial memory image was already sent to device via
695    ///   [`transport_init_memory_to_device`](Self::transport_init_memory_to_device).
696    /// - all other state should be given by `system_records` and `record_arenas`
697    #[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        // main tracegen call:
704        let ctx = self
705            .chip_complex
706            .generate_proving_ctx(system_records, record_arenas)?;
707
708        // ==== Defensive checks that the trace heights satisfy the linear constraints: ====
709        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        // 1. check max trace height isn't exceeded
715        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        // 2. check linear constraints on trace heights are satisfied
735        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    /// Generates proof for zkVM execution for exactly `num_insns` instructions for a given program
767    /// and a given starting state.
768    ///
769    /// **Note**: The cached program trace must be loaded via [`load_program`](Self::load_program)
770    /// before calling this function.
771    ///
772    /// Returns:
773    /// - proof for the execution segment
774    /// - final memory state only if execution ends in successful termination (exit code 0). This
775    ///   final memory state may be used to extract user public values afterwards.
776    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        // drop final memory unless this is a terminal segment and the exit code is success
796        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    /// Transforms the program into a cached trace and commits it _on device_ using the proof system
805    /// polynomial commitment scheme.
806    ///
807    /// Returns the cached program trace.
808    /// Note that [`load_program`](Self::load_program) must be called separately to load the cached
809    /// program trace into the VM itself.
810    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    /// Loads cached program trace into the VM.
830    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    /// See [`SystemChipComplex::memory_top_tree`].
842    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    /// Convenience method to construct a [MeteredCtx] using data from the stored proving key.
854    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        // Program trace is the same for all segments
887        constant_trace_heights[PROGRAM_AIR_ID] = Some(program_len);
888        // VmConnectorAir always has a constant trace height of 2
889        constant_trace_heights[CONNECTOR_AIR_ID] = Some(2);
890        // Merge in constant heights reported by chips (e.g., lookup table chips).
891        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    /// Convenience method to construct a [MeteredCostCtx] using data from the stored proving key.
927    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    /// See [`debug_proving_ctx`].
949    #[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
986/// Prover for a specific exe in a specific continuation VM using a specific Stark config.
987pub 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/// Virtual machine prover instance for a fixed VM config and a fixed program. For use in proving a
995/// program directly on bare metal.
996///
997/// This struct contains the [VmState] itself to avoid re-allocating guest memory. The memory is
998/// reset with zeros before execution.
999#[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    /// First performs metered execution (E2) to determine segments. Then sequentially proves each
1061    /// segment. The proof for each segment uses the specified [ProverBackend], but the proof for
1062    /// the next segment does not start before the current proof finishes.
1063    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    /// For internal use to resize trace matrices before proving.
1081    ///
1082    /// The closure `modify_ctx(seg_idx, &mut ctx)` is called sequentially for each segment.
1083    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            // We need a separate span so the metric label includes "segment" from _segment_span
1099            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
1142/// The payload of a verified guest VM execution.
1143pub struct VerifiedExecutionPayload<F> {
1144    /// The Merklelized hash of:
1145    /// - Program code commitment (commitment of the cached trace)
1146    /// - Merkle root of the initial memory
1147    /// - Starting program counter (`pc_start`)
1148    ///
1149    /// The Merklelization uses Poseidon2 as a cryptographic hash function (for the leaves)
1150    /// and a cryptographic compression function (for internal nodes).
1151    pub exe_commit: [F; CHUNK],
1152    /// The Merkle root of the final memory state.
1153    pub final_memory_root: [F; CHUNK],
1154}
1155
1156/// Verify segment proofs with boundary condition checks for continuation between segments.
1157///
1158/// Assumption:
1159/// - `vk` is a valid verifying key of a VM circuit.
1160///
1161/// Returns:
1162/// - The commitment to the VM executable extracted from `proofs`. It is the responsibility of the
1163///   caller to check that the returned commitment matches the VM executable that the VM was
1164///   supposed to execute.
1165/// - The Merkle root of the final memory state.
1166///
1167/// ## Note
1168/// This function does not extract or verify any user public values from the final memory state.
1169/// This verification requires an additional Merkle proof with respect to the Merkle root of
1170/// the final memory state.
1171// @dev: This function doesn't need to be generic in `VC`.
1172pub 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        // Check public values.
1204        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                    // Check initial pc matches the previous final pc.
1227                    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                // Check that initial root matches the previous final root.
1270                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                // We assume the vk is valid, so this is only a debug assert.
1286                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    /// Sets fixed trace heights for the system AIRs' trace matrices.
1350    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/// Runs the STARK backend debugger to check the constraints against the trace matrices
1360/// logically, instead of cryptographically. This will panic if any constraint is violated, and
1361/// using `RUST_BACKTRACE=1` can be used to read the stack backtrace of where the constraint
1362/// failed in the code (this requires the code to be compiled with debug=true). Using lower
1363/// optimization levels like -O0 will prevent the compiler from inlining and give better
1364/// debugging information.
1365// @dev The debugger needs the host proving key.
1366//      This function is used both by VirtualMachine::debug_proving_ctx and by
1367// stark_utils::air_test_impl
1368#[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}