openvm_circuit/arch/
vm.rs

1//! [VmExecutor] is the struct that can execute an _arbitrary_ program, provided in the form of a
2//! [VmExe], for a fixed set of OpenVM instructions corresponding to a [VmExecutionConfig].
3//! Internally once it is given a program, it will preprocess the program to rewrite it into a more
4//! optimized format for runtime execution. This **instance** of the executor will be a separate
5//! struct specialized to running a _fixed_ program on different program inputs.
6//!
7//! [VirtualMachine] will similarly be the struct that has done all the setup so it can
8//! execute+prove an arbitrary program for a fixed config - it will internally still hold VmExecutor
9use std::{
10    any::TypeId,
11    borrow::Borrow,
12    collections::{HashMap, VecDeque},
13    marker::PhantomData,
14    sync::Arc,
15};
16
17use getset::{Getters, MutGetters, Setters, WithSetters};
18use itertools::{zip_eq, Itertools};
19use openvm_circuit::system::program::trace::compute_exe_commit;
20use openvm_instructions::{
21    exe::{SparseMemoryImage, VmExe},
22    program::Program,
23};
24use openvm_stark_backend::{
25    config::{Com, StarkGenericConfig, Val},
26    engine::StarkEngine,
27    keygen::types::{MultiStarkProvingKey, MultiStarkVerifyingKey},
28    p3_field::{
29        BasedVectorSpace, InjectiveMonomial, PrimeCharacteristicRing, PrimeField32, TwoAdicField,
30    },
31    p3_util::{log2_ceil_usize, log2_strict_usize},
32    proof::Proof,
33    prover::{
34        hal::{DeviceDataTransporter, MatrixDimensions, TraceCommitter},
35        types::{CommittedTraceData, DeviceMultiStarkProvingKey, ProvingContext},
36    },
37    verifier::VerificationError,
38};
39use p3_baby_bear::BabyBear;
40use serde::{Deserialize, Serialize};
41use thiserror::Error;
42use tracing::{info_span, instrument};
43
44#[cfg(feature = "aot")]
45use super::aot::AotInstance;
46use super::{
47    execution_mode::{ExecutionCtx, MeteredCostCtx, MeteredCtx, PreflightCtx, Segment},
48    hasher::poseidon2::vm_poseidon2_hasher,
49    interpreter::InterpretedInstance,
50    interpreter_preflight::PreflightInterpretedInstance,
51    AirInventoryError, ChipInventoryError, ExecutionError, ExecutionState, Executor,
52    ExecutorInventory, ExecutorInventoryError, MemoryConfig, MeteredExecutor, PreflightExecutor,
53    StaticProgramError, SystemConfig, VmBuilder, VmChipComplex, VmCircuitConfig, VmExecState,
54    VmExecutionConfig, VmState, CONNECTOR_AIR_ID, MERKLE_AIR_ID, PROGRAM_AIR_ID,
55    PROGRAM_CACHED_TRACE_INDEX, PUBLIC_VALUES_AIR_ID,
56};
57use crate::{
58    arch::DEFAULT_RNG_SEED,
59    execute_spanned,
60    system::{
61        connector::{VmConnectorPvs, DEFAULT_SUSPEND_EXIT_CODE},
62        memory::{
63            adapter::records,
64            merkle::{
65                public_values::{UserPublicValuesProof, UserPublicValuesProofError},
66                MemoryMerklePvs,
67            },
68            online::{GuestMemory, TracingMemory},
69            AddressMap, CHUNK,
70        },
71        program::trace::{generate_cached_trace, VmCommittedExe},
72        SystemChipComplex, SystemRecords, SystemWithFixedTraceHeights,
73    },
74};
75
76/// Canonical field bound for VM execution/circuit code.
77pub const BABYBEAR_S_BOX_DEGREE: u64 = 7;
78
79pub trait VmField: PrimeField32 + InjectiveMonomial<BABYBEAR_S_BOX_DEGREE> {}
80impl<T> VmField for T where T: PrimeField32 + InjectiveMonomial<BABYBEAR_S_BOX_DEGREE> {}
81
82#[derive(Error, Debug)]
83pub enum GenerationError {
84    #[error("unexpected number of arenas: {actual} (expected num_airs={expected})")]
85    UnexpectedNumArenas { actual: usize, expected: usize },
86    #[error("trace height for air_idx={air_idx} must be fixed to {expected}, actual={actual}")]
87    ForceTraceHeightIncorrect {
88        air_idx: usize,
89        actual: usize,
90        expected: usize,
91    },
92    #[error("trace height of air {air_idx} has height {height} greater than maximum {max_height}")]
93    TraceHeightsLimitExceeded {
94        air_idx: usize,
95        height: usize,
96        max_height: usize,
97    },
98    #[error("trace heights violate linear constraint {constraint_idx} ({value} >= {threshold})")]
99    LinearTraceHeightConstraintExceeded {
100        constraint_idx: usize,
101        value: u64,
102        threshold: u32,
103    },
104}
105
106/// A trait for key-value store for `Streams`.
107pub trait KvStore: Send + Sync {
108    fn get(&self, key: &[u8]) -> Option<&[u8]>;
109}
110
111impl KvStore for HashMap<Vec<u8>, Vec<u8>> {
112    fn get(&self, key: &[u8]) -> Option<&[u8]> {
113        self.get(key).map(|v| v.as_slice())
114    }
115}
116
117#[derive(Clone)]
118pub struct Streams<F> {
119    pub input_stream: VecDeque<Vec<F>>,
120    pub hint_stream: VecDeque<F>,
121    pub hint_space: Vec<Vec<F>>,
122    /// The key-value store for hints. Both key and value are byte arrays. Executors which
123    /// read `kv_store` need to encode the key and decode the value.
124    pub kv_store: Arc<dyn KvStore>,
125}
126
127impl<F> Streams<F> {
128    pub fn new(input_stream: impl Into<VecDeque<Vec<F>>>) -> Self {
129        Self {
130            input_stream: input_stream.into(),
131            hint_stream: VecDeque::default(),
132            hint_space: Vec::default(),
133            kv_store: Arc::new(HashMap::new()),
134        }
135    }
136}
137
138impl<F> Default for Streams<F> {
139    fn default() -> Self {
140        Self::new(VecDeque::default())
141    }
142}
143
144impl<F> From<VecDeque<Vec<F>>> for Streams<F> {
145    fn from(value: VecDeque<Vec<F>>) -> Self {
146        Streams::new(value)
147    }
148}
149
150impl<F> From<Vec<Vec<F>>> for Streams<F> {
151    fn from(value: Vec<Vec<F>>) -> Self {
152        Streams::new(value)
153    }
154}
155
156/// Typedef for [PreflightInterpretedInstance] that is generic in `VC: VmExecutionConfig<F>`
157type PreflightInterpretedInstance2<F, VC> =
158    PreflightInterpretedInstance<F, <VC as VmExecutionConfig<F>>::Executor>;
159
160/// [VmExecutor] is the struct that can execute an _arbitrary_ program, provided in the form of a
161/// [VmExe], for a fixed set of OpenVM instructions corresponding to a [VmExecutionConfig].
162/// Internally once it is given a program, it will preprocess the program to rewrite it into a more
163/// optimized format for runtime execution. This **instance** of the executor will be a separate
164/// struct specialized to running a _fixed_ program on different program inputs.
165#[derive(Clone)]
166pub struct VmExecutor<F, VC>
167where
168    VC: VmExecutionConfig<F>,
169{
170    pub config: VC,
171    inventory: Arc<ExecutorInventory<VC::Executor>>,
172    phantom: PhantomData<F>,
173}
174
175#[repr(i32)]
176pub enum ExitCode {
177    Success = 0,
178    Error = 1,
179    Suspended = -1, // Continuations
180}
181
182pub struct PreflightExecutionOutput<F, RA> {
183    pub system_records: SystemRecords<F>,
184    pub record_arenas: Vec<RA>,
185    pub to_state: VmState<F, GuestMemory>,
186}
187
188impl<F, VC> VmExecutor<F, VC>
189where
190    VC: VmExecutionConfig<F>,
191{
192    /// Create a new VM executor with a given config.
193    ///
194    /// The VM will start with a single segment, which is created from the initial state.
195    pub fn new(config: VC) -> Result<Self, ExecutorInventoryError> {
196        let inventory = config.create_executors()?;
197        Ok(Self {
198            config,
199            inventory: Arc::new(inventory),
200            phantom: PhantomData,
201        })
202    }
203}
204
205impl<F, VC> VmExecutor<F, VC>
206where
207    VC: VmExecutionConfig<F> + AsRef<SystemConfig>,
208{
209    pub fn build_metered_ctx(
210        &self,
211        constant_trace_heights: &[Option<usize>],
212        air_names: &[String],
213        widths: &[usize],
214        interactions: &[usize],
215    ) -> MeteredCtx {
216        MeteredCtx::new(
217            constant_trace_heights.to_vec(),
218            air_names.to_vec(),
219            widths.to_vec(),
220            interactions.to_vec(),
221            self.config.as_ref(),
222        )
223    }
224
225    pub fn build_metered_cost_ctx(&self, widths: &[usize]) -> MeteredCostCtx {
226        MeteredCostCtx::new(widths.to_vec(), self.config.as_ref())
227    }
228}
229
230impl<F, VC> VmExecutor<F, VC>
231where
232    F: PrimeField32,
233    VC: VmExecutionConfig<F>,
234    VC::Executor: Executor<F>,
235{
236    /// Creates an instance of the interpreter specialized for pure execution, without metering, of
237    /// the given `exe`.
238    ///
239    /// For metered execution, use the [`metered_instance`](Self::metered_instance) constructor.
240    #[cfg(not(feature = "aot"))]
241    pub fn instance(
242        &self,
243        exe: &VmExe<F>,
244    ) -> Result<InterpretedInstance<F, ExecutionCtx>, StaticProgramError> {
245        InterpretedInstance::new(&self.inventory, exe)
246    }
247
248    #[cfg(feature = "aot")]
249    pub fn interpreter_instance(
250        &self,
251        exe: &VmExe<F>,
252    ) -> Result<InterpretedInstance<F, ExecutionCtx>, StaticProgramError> {
253        InterpretedInstance::new(&self.inventory, exe)
254    }
255
256    #[cfg(feature = "aot")]
257    pub fn instance(
258        &self,
259        exe: &VmExe<F>,
260    ) -> Result<AotInstance<F, ExecutionCtx>, StaticProgramError> {
261        Self::aot_instance(self, exe)
262    }
263}
264#[cfg(feature = "aot")]
265impl<F, VC> VmExecutor<F, VC>
266where
267    F: PrimeField32,
268    VC: VmExecutionConfig<F>,
269    VC::Executor: Executor<F>,
270{
271    pub fn aot_instance(
272        &self,
273        exe: &VmExe<F>,
274    ) -> Result<AotInstance<F, ExecutionCtx>, StaticProgramError> {
275        AotInstance::new(&self.inventory, exe)
276    }
277}
278
279impl<F, VC> VmExecutor<F, VC>
280where
281    F: PrimeField32,
282    VC: VmExecutionConfig<F>,
283    VC::Executor: MeteredExecutor<F>,
284{
285    /// Creates an instance of the interpreter specialized for metered execution of the given `exe`.
286    #[cfg(not(feature = "aot"))]
287    pub fn metered_instance(
288        &self,
289        exe: &VmExe<F>,
290        executor_idx_to_air_idx: &[usize],
291    ) -> Result<InterpretedInstance<F, MeteredCtx>, StaticProgramError> {
292        InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
293    }
294
295    #[cfg(feature = "aot")]
296    pub fn metered_interpreter_instance(
297        &self,
298        exe: &VmExe<F>,
299        executor_idx_to_air_idx: &[usize],
300    ) -> Result<InterpretedInstance<F, MeteredCtx>, StaticProgramError> {
301        InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
302    }
303
304    #[cfg(feature = "aot")]
305    pub fn metered_instance(
306        &self,
307        exe: &VmExe<F>,
308        executor_idx_to_air_idx: &[usize],
309    ) -> Result<AotInstance<F, MeteredCtx>, StaticProgramError> {
310        Self::metered_aot_instance(self, exe, executor_idx_to_air_idx)
311    }
312
313    // Crates an AOT instance for metered execution of the given `exe`.
314    #[cfg(feature = "aot")]
315    pub fn metered_aot_instance(
316        &self,
317        exe: &VmExe<F>,
318        executor_idx_to_air_idx: &[usize],
319    ) -> Result<AotInstance<F, MeteredCtx>, StaticProgramError> {
320        AotInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
321    }
322
323    /// Creates an instance of the interpreter specialized for cost metering execution of the given
324    /// `exe`.
325    pub fn metered_cost_instance(
326        &self,
327        exe: &VmExe<F>,
328        executor_idx_to_air_idx: &[usize],
329    ) -> Result<InterpretedInstance<F, MeteredCostCtx>, StaticProgramError> {
330        InterpretedInstance::new_metered(&self.inventory, exe, executor_idx_to_air_idx)
331    }
332}
333
334#[derive(Error, Debug)]
335pub enum VmVerificationError {
336    #[error("no proof is provided")]
337    ProofNotFound,
338
339    #[error("program commit mismatch (index of mismatch proof: {index}")]
340    ProgramCommitMismatch { index: usize },
341
342    #[error("exe commit mismatch (expected: {expected:?}, actual: {actual:?})")]
343    ExeCommitMismatch {
344        expected: [u32; CHUNK],
345        actual: [u32; CHUNK],
346    },
347
348    #[error("initial pc mismatch (initial: {initial}, prev_final: {prev_final})")]
349    InitialPcMismatch { initial: u32, prev_final: u32 },
350
351    #[error("initial memory root mismatch")]
352    InitialMemoryRootMismatch,
353
354    #[error("is terminate mismatch (expected: {expected}, actual: {actual})")]
355    IsTerminateMismatch { expected: bool, actual: bool },
356
357    #[error("exit code mismatch")]
358    ExitCodeMismatch { expected: u32, actual: u32 },
359
360    #[error("AIR has unexpected public values (expected: {expected}, actual: {actual})")]
361    UnexpectedPvs { expected: usize, actual: usize },
362
363    #[error("Invalid number of AIRs: expected at least 3, got {0}")]
364    NotEnoughAirs(usize),
365
366    #[error("missing system AIR with ID {air_id}")]
367    SystemAirMissing { air_id: usize },
368
369    #[error("stark verification error: {0}")]
370    StarkError(#[from] VerificationError),
371
372    #[error("user public values proof error: {0}")]
373    UserPublicValuesError(#[from] UserPublicValuesProofError),
374}
375
376#[derive(Error, Debug)]
377pub enum VirtualMachineError {
378    #[error("executor inventory error: {0}")]
379    ExecutorInventory(#[from] ExecutorInventoryError),
380    #[error("air inventory error: {0}")]
381    AirInventory(#[from] AirInventoryError),
382    #[error("chip inventory error: {0}")]
383    ChipInventory(#[from] ChipInventoryError),
384    #[error("static program error: {0}")]
385    StaticProgram(#[from] StaticProgramError),
386    #[error("execution error: {0}")]
387    Execution(#[from] ExecutionError),
388    #[error("trace generation error: {0}")]
389    Generation(#[from] GenerationError),
390    #[error("program committed trade data not loaded")]
391    ProgramIsNotCommitted,
392    #[error("verification error: {0}")]
393    Verification(#[from] VmVerificationError),
394}
395
396/// The [VirtualMachine] struct contains the API to generate proofs for _arbitrary_ programs for a
397/// fixed set of OpenVM instructions and a fixed VM circuit corresponding to those instructions. The
398/// API is specific to a particular [StarkEngine], which specifies a fixed [StarkGenericConfig] and
399/// [ProverBackend] via associated types. The [VmProverBuilder] also fixes the choice of
400/// `RecordArena` associated to the prover backend via an associated type.
401///
402/// In other words, this struct _is_ the zkVM.
403#[derive(Getters, MutGetters, Setters, WithSetters)]
404pub struct VirtualMachine<E, VB>
405where
406    E: StarkEngine,
407    VB: VmBuilder<E>,
408{
409    /// Proving engine
410    pub engine: E,
411    /// Runtime executor
412    #[getset(get = "pub")]
413    executor: VmExecutor<Val<E::SC>, VB::VmConfig>,
414    #[getset(get = "pub", get_mut = "pub")]
415    pk: DeviceMultiStarkProvingKey<E::PB>,
416    chip_complex: VmChipComplex<E::SC, VB::RecordArena, E::PB, VB::SystemChipInventory>,
417    #[cfg(feature = "stark-debug")]
418    pub h_pk: Option<MultiStarkProvingKey<E::SC>>,
419}
420
421impl<E, VB> VirtualMachine<E, VB>
422where
423    E: StarkEngine,
424    VB: VmBuilder<E>,
425{
426    pub fn new(
427        engine: E,
428        builder: VB,
429        config: VB::VmConfig,
430        d_pk: DeviceMultiStarkProvingKey<E::PB>,
431    ) -> Result<Self, VirtualMachineError> {
432        let circuit = config.create_airs()?;
433        let chip_complex = builder.create_chip_complex(&config, circuit)?;
434        let executor = VmExecutor::<Val<E::SC>, _>::new(config)?;
435        Ok(Self {
436            engine,
437            executor,
438            pk: d_pk,
439            chip_complex,
440            #[cfg(feature = "stark-debug")]
441            h_pk: None,
442        })
443    }
444
445    pub fn new_with_keygen(
446        engine: E,
447        builder: VB,
448        config: VB::VmConfig,
449    ) -> Result<(Self, MultiStarkProvingKey<E::SC>), VirtualMachineError> {
450        let circuit = config.create_airs()?;
451        let pk = circuit.keygen(&engine);
452        let d_pk = engine.device().transport_pk_to_device(&pk);
453        let vm = Self::new(engine, builder, config, d_pk)?;
454        Ok((vm, pk))
455    }
456
457    pub fn config(&self) -> &VB::VmConfig {
458        &self.executor.config
459    }
460
461    /// Pure interpreter.
462    #[cfg(not(feature = "aot"))]
463    pub fn interpreter(
464        &self,
465        exe: &VmExe<Val<E::SC>>,
466    ) -> Result<InterpretedInstance<Val<E::SC>, ExecutionCtx>, StaticProgramError>
467    where
468        Val<E::SC>: PrimeField32,
469        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
470    {
471        self.executor().instance(exe)
472    }
473
474    // Pure AOT execution
475    #[cfg(feature = "aot")]
476    pub fn naive_interpreter(
477        &self,
478        exe: &VmExe<Val<E::SC>>,
479    ) -> Result<InterpretedInstance<Val<E::SC>, ExecutionCtx>, StaticProgramError>
480    where
481        Val<E::SC>: PrimeField32,
482        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
483    {
484        self.executor().interpreter_instance(exe)
485    }
486
487    // Pure AOT execution
488    #[cfg(feature = "aot")]
489    pub fn interpreter(
490        &self,
491        exe: &VmExe<Val<E::SC>>,
492    ) -> Result<AotInstance<Val<E::SC>, ExecutionCtx>, StaticProgramError>
493    where
494        Val<E::SC>: PrimeField32,
495        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
496    {
497        Self::get_aot_instance(self, exe)
498    }
499
500    #[cfg(feature = "aot")]
501    pub fn get_aot_instance(
502        &self,
503        exe: &VmExe<Val<E::SC>>,
504    ) -> Result<AotInstance<Val<E::SC>, ExecutionCtx>, StaticProgramError>
505    where
506        Val<E::SC>: PrimeField32,
507        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>,
508    {
509        self.executor().aot_instance(exe)
510    }
511
512    #[cfg(not(feature = "aot"))]
513    pub fn metered_interpreter(
514        &self,
515        exe: &VmExe<Val<E::SC>>,
516    ) -> Result<InterpretedInstance<Val<E::SC>, MeteredCtx>, StaticProgramError>
517    where
518        Val<E::SC>: PrimeField32,
519        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
520    {
521        let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
522        self.executor()
523            .metered_instance(exe, &executor_idx_to_air_idx)
524    }
525
526    #[cfg(feature = "aot")]
527    pub fn metered_interpreter(
528        &self,
529        exe: &VmExe<Val<E::SC>>,
530    ) -> Result<AotInstance<Val<E::SC>, MeteredCtx>, StaticProgramError>
531    where
532        Val<E::SC>: PrimeField32,
533        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
534    {
535        let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
536        self.executor()
537            .metered_instance(exe, &executor_idx_to_air_idx)
538    }
539
540    // Metered AOT execution
541    #[cfg(feature = "aot")]
542    pub fn get_metered_aot_instance(
543        &self,
544        exe: &VmExe<Val<E::SC>>,
545    ) -> Result<AotInstance<Val<E::SC>, MeteredCtx>, StaticProgramError>
546    where
547        Val<E::SC>: PrimeField32,
548        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
549    {
550        let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
551        self.executor()
552            .metered_aot_instance(exe, &executor_idx_to_air_idx)
553    }
554
555    #[cfg(feature = "aot")]
556    pub fn naive_metered_interpreter(
557        &self,
558        exe: &VmExe<Val<E::SC>>,
559    ) -> Result<InterpretedInstance<Val<E::SC>, MeteredCtx>, StaticProgramError>
560    where
561        Val<E::SC>: PrimeField32,
562        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
563    {
564        let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
565        self.executor()
566            .metered_interpreter_instance(exe, &executor_idx_to_air_idx)
567    }
568
569    pub fn metered_cost_interpreter(
570        &self,
571        exe: &VmExe<Val<E::SC>>,
572    ) -> Result<InterpretedInstance<Val<E::SC>, MeteredCostCtx>, StaticProgramError>
573    where
574        Val<E::SC>: PrimeField32,
575        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: MeteredExecutor<Val<E::SC>>,
576    {
577        let executor_idx_to_air_idx = self.executor_idx_to_air_idx();
578        self.executor()
579            .metered_cost_instance(exe, &executor_idx_to_air_idx)
580    }
581
582    pub fn preflight_interpreter(
583        &self,
584        exe: &VmExe<Val<E::SC>>,
585    ) -> Result<PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>, StaticProgramError> {
586        PreflightInterpretedInstance::new(
587            &exe.program,
588            self.executor.inventory.clone(),
589            self.executor_idx_to_air_idx(),
590        )
591    }
592
593    /// Preflight execution for a single segment. Executes for exactly `num_insns` instructions
594    /// using an interpreter. Preflight execution must be provided with `trace_heights`
595    /// instrumentation data that was collected from a previous run of metered execution so that the
596    /// preflight execution knows how much memory to allocate for record arenas.
597    ///
598    /// This function should rarely be called on its own. Users are advised to call
599    /// [`prove`](Self::prove) directly.
600    #[instrument(name = "execute_preflight", skip_all)]
601    pub fn execute_preflight(
602        &self,
603        interpreter: &mut PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
604        state: VmState<Val<E::SC>, GuestMemory>,
605        num_insns: Option<u64>,
606        trace_heights: &[u32],
607    ) -> Result<PreflightExecutionOutput<Val<E::SC>, VB::RecordArena>, ExecutionError>
608    where
609        Val<E::SC>: PrimeField32,
610        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor:
611            PreflightExecutor<Val<E::SC>, VB::RecordArena>,
612    {
613        debug_assert!(interpreter
614            .executor_idx_to_air_idx
615            .iter()
616            .all(|&air_idx| air_idx < trace_heights.len()));
617
618        // TODO[jpw]: figure out how to compute RA specific main_widths
619        let main_widths = self
620            .pk
621            .per_air
622            .iter()
623            .map(|pk| pk.vk.params.width.main_width())
624            .collect_vec();
625        let capacities = zip_eq(trace_heights, main_widths)
626            .map(|(&h, w)| (h as usize, w))
627            .collect::<Vec<_>>();
628        let ctx = PreflightCtx::new_with_capacity(&capacities, num_insns);
629
630        let system_config: &SystemConfig = self.config().as_ref();
631        let adapter_offset = system_config.access_adapter_air_id_offset();
632        // ATTENTION: this must agree with `num_memory_airs`
633        let num_adapters = log2_strict_usize(system_config.memory_config.max_access_adapter_n);
634        assert_eq!(adapter_offset + num_adapters, system_config.num_airs());
635        let access_adapter_arena_size_bound = records::arena_size_bound(
636            &trace_heights[adapter_offset..adapter_offset + num_adapters],
637        );
638        let pc = state.pc();
639        let memory = TracingMemory::from_image(
640            state.memory,
641            system_config.initial_block_size(),
642            access_adapter_arena_size_bound,
643        );
644        let from_state = ExecutionState::new(pc, memory.timestamp());
645        let vm_state = VmState::new(
646            pc,
647            memory,
648            state.streams,
649            state.rng,
650            state.custom_pvs,
651            #[cfg(feature = "metrics")]
652            state.metrics,
653        );
654        let mut exec_state = VmExecState::new(vm_state, ctx);
655        interpreter.reset_execution_frequencies();
656        execute_spanned!("execute_preflight", interpreter, &mut exec_state)?;
657        let filtered_exec_frequencies = interpreter.filtered_execution_frequencies();
658        let touched_memory = exec_state
659            .vm_state
660            .memory
661            .finalize::<Val<E::SC>>(system_config.continuation_enabled);
662        #[cfg(feature = "perf-metrics")]
663        crate::metrics::end_segment_metrics(&mut exec_state);
664
665        let pc = exec_state.vm_state.pc();
666        let memory = exec_state.vm_state.memory;
667        let to_state = ExecutionState::new(pc, memory.timestamp());
668        let public_values = exec_state
669            .vm_state
670            .custom_pvs
671            .iter()
672            .map(|&x| x.unwrap_or(Val::<E::SC>::ZERO))
673            .collect();
674        let exit_code = exec_state.exit_code?;
675        let system_records = SystemRecords {
676            from_state,
677            to_state,
678            exit_code,
679            filtered_exec_frequencies,
680            access_adapter_records: memory.access_adapter_records,
681            touched_memory,
682            public_values,
683        };
684        let record_arenas = exec_state.ctx.arenas;
685        let to_state = VmState::new(
686            pc,
687            memory.data,
688            exec_state.vm_state.streams,
689            exec_state.vm_state.rng,
690            exec_state.vm_state.custom_pvs,
691            #[cfg(feature = "metrics")]
692            exec_state.vm_state.metrics,
693        );
694        Ok(PreflightExecutionOutput {
695            system_records,
696            record_arenas,
697            to_state,
698        })
699    }
700
701    /// Calls [`VmState::initial`] but sets more information for
702    /// performance metrics when feature "perf-metrics" is enabled.
703    #[instrument(name = "vm.create_initial_state", level = "debug", skip_all)]
704    pub fn create_initial_state(
705        &self,
706        exe: &VmExe<Val<E::SC>>,
707        inputs: impl Into<Streams<Val<E::SC>>>,
708    ) -> VmState<Val<E::SC>, GuestMemory> {
709        #[allow(unused_mut)]
710        let mut state = VmState::initial(
711            self.config().as_ref(),
712            &exe.init_memory,
713            exe.pc_start,
714            inputs,
715        );
716        // Add backtrace information for either:
717        // - debugging
718        // - performance metrics
719        #[cfg(all(feature = "metrics", any(feature = "perf-metrics", debug_assertions)))]
720        {
721            state.metrics.fn_bounds = exe.fn_bounds.clone();
722            state.metrics.debug_infos = exe.program.debug_infos();
723        }
724        #[cfg(feature = "perf-metrics")]
725        {
726            state.metrics.set_pk_info(&self.pk);
727            state.metrics.num_sys_airs = self.config().as_ref().num_airs();
728            state.metrics.access_adapter_offset =
729                self.config().as_ref().access_adapter_air_id_offset();
730        }
731        state
732    }
733
734    /// This function mutates `self` but should only depend on internal state in the sense that:
735    /// - program must already be loaded as cached trace via [`load_program`](Self::load_program).
736    /// - initial memory image was already sent to device via
737    ///   [`transport_init_memory_to_device`](Self::transport_init_memory_to_device).
738    /// - all other state should be given by `system_records` and `record_arenas`
739    #[instrument(name = "trace_gen", skip_all)]
740    pub fn generate_proving_ctx(
741        &mut self,
742        system_records: SystemRecords<Val<E::SC>>,
743        record_arenas: Vec<VB::RecordArena>,
744    ) -> Result<ProvingContext<E::PB>, GenerationError> {
745        #[cfg(feature = "metrics")]
746        let mut current_trace_heights =
747            self.get_trace_heights_from_arenas(&system_records, &record_arenas);
748        // main tracegen call:
749        let ctx = self
750            .chip_complex
751            .generate_proving_ctx(system_records, record_arenas)?;
752
753        // ==== Defensive checks that the trace heights satisfy the linear constraints: ====
754        let idx_trace_heights = ctx
755            .per_air
756            .iter()
757            .map(|(air_idx, ctx)| (*air_idx, ctx.main_trace_height()))
758            .collect_vec();
759        // 1. check max trace height isn't exceeded
760        let max_trace_height = if TypeId::of::<Val<E::SC>>() == TypeId::of::<BabyBear>() {
761            let min_log_blowup = log2_ceil_usize(self.config().as_ref().max_constraint_degree - 1);
762            1 << (BabyBear::TWO_ADICITY - min_log_blowup)
763        } else {
764            tracing::warn!(
765                "constructing VirtualMachine for unrecognized field; using max_trace_height=2^30"
766            );
767            1 << 30
768        };
769        if let Some(&(air_idx, height)) = idx_trace_heights
770            .iter()
771            .find(|(_, height)| *height > max_trace_height)
772        {
773            return Err(GenerationError::TraceHeightsLimitExceeded {
774                air_idx,
775                height,
776                max_height: max_trace_height,
777            });
778        }
779        // 2. check linear constraints on trace heights are satisfied
780        let trace_height_constraints = &self.pk.trace_height_constraints;
781        if trace_height_constraints.is_empty() {
782            tracing::warn!("generating proving context without trace height constraints");
783        }
784        for (i, constraint) in trace_height_constraints.iter().enumerate() {
785            let value = idx_trace_heights
786                .iter()
787                .map(|&(air_idx, h)| constraint.coefficients[air_idx] as u64 * h as u64)
788                .sum::<u64>();
789
790            if value >= constraint.threshold as u64 {
791                tracing::info!(
792                    "trace heights {:?} violate linear constraint {} ({} >= {})",
793                    idx_trace_heights,
794                    i,
795                    value,
796                    constraint.threshold
797                );
798                return Err(GenerationError::LinearTraceHeightConstraintExceeded {
799                    constraint_idx: i,
800                    value,
801                    threshold: constraint.threshold,
802                });
803            }
804        }
805        #[cfg(feature = "metrics")]
806        self.finalize_metrics(&mut current_trace_heights);
807        #[cfg(feature = "stark-debug")]
808        self.debug_proving_ctx(&ctx);
809
810        Ok(ctx)
811    }
812
813    /// Generates proof for zkVM execution for exactly `num_insns` instructions for a given program
814    /// and a given starting state.
815    ///
816    /// **Note**: The cached program trace must be loaded via [`load_program`](Self::load_program)
817    /// before calling this function.
818    ///
819    /// Returns:
820    /// - proof for the execution segment
821    /// - final memory state only if execution ends in successful termination (exit code 0). This
822    ///   final memory state may be used to extract user public values afterwards.
823    pub fn prove(
824        &mut self,
825        interpreter: &mut PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
826        state: VmState<Val<E::SC>, GuestMemory>,
827        num_insns: Option<u64>,
828        trace_heights: &[u32],
829    ) -> Result<(Proof<E::SC>, Option<GuestMemory>), VirtualMachineError>
830    where
831        Val<E::SC>: PrimeField32,
832        <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor:
833            PreflightExecutor<Val<E::SC>, VB::RecordArena>,
834    {
835        self.transport_init_memory_to_device(&state.memory);
836
837        let PreflightExecutionOutput {
838            system_records,
839            record_arenas,
840            to_state,
841        } = self.execute_preflight(interpreter, state, num_insns, trace_heights)?;
842        // drop final memory unless this is a terminal segment and the exit code is success
843        let final_memory =
844            (system_records.exit_code == Some(ExitCode::Success as u32)).then_some(to_state.memory);
845        let ctx = self.generate_proving_ctx(system_records, record_arenas)?;
846        let proof = self.engine.prove(&self.pk, ctx);
847
848        Ok((proof, final_memory))
849    }
850
851    /// Verify segment proofs, checking continuation boundary conditions between segments if VM
852    /// memory is persistent The behavior of this function differs depending on whether
853    /// continuations is enabled or not. We recommend to call the functions [`verify_segments`]
854    /// or [`verify_single`] directly instead.
855    pub fn verify(
856        &self,
857        vk: &MultiStarkVerifyingKey<E::SC>,
858        proofs: &[Proof<E::SC>],
859    ) -> Result<(), VmVerificationError>
860    where
861        Com<E::SC>: AsRef<[Val<E::SC>; CHUNK]> + From<[Val<E::SC>; CHUNK]>,
862        Val<E::SC>: PrimeField32,
863    {
864        if self.config().as_ref().continuation_enabled {
865            verify_segments(&self.engine, vk, proofs).map(|_| ())
866        } else {
867            assert_eq!(proofs.len(), 1);
868            verify_single(&self.engine, vk, &proofs[0]).map_err(VmVerificationError::StarkError)
869        }
870    }
871
872    /// Transforms the program into a cached trace and commits it _on device_ using the proof system
873    /// polynomial commitment scheme.
874    ///
875    /// Returns the cached program trace.
876    /// Note that [`load_program`](Self::load_program) must be called separately to load the cached
877    /// program trace into the VM itself.
878    pub fn commit_program_on_device(
879        &self,
880        program: &Program<Val<E::SC>>,
881    ) -> CommittedTraceData<E::PB> {
882        let trace = generate_cached_trace(program);
883        let d_trace = self
884            .engine
885            .device()
886            .transport_matrix_to_device(&Arc::new(trace));
887        let (commitment, data) = self.engine.device().commit(std::slice::from_ref(&d_trace));
888        CommittedTraceData {
889            commitment,
890            trace: d_trace,
891            data,
892        }
893    }
894
895    /// Convenience method to transport a host committed Exe to device. This can be used if you have
896    /// a pre-committed program and want to transport to device instead of re-committing. One should
897    /// benchmark the latency of this function versus
898    /// [`commit_program_on_device`](Self::commit_program_on_device), which directly re-commits on
899    /// device, to determine which method is more suitable.
900    pub fn transport_committed_exe_to_device(
901        &self,
902        committed_exe: &VmCommittedExe<E::SC>,
903    ) -> CommittedTraceData<E::PB> {
904        let commitment = committed_exe.get_program_commit();
905        let trace = &committed_exe.trace;
906        let prover_data = &committed_exe.prover_data;
907        self.engine
908            .device()
909            .transport_committed_trace_to_device(commitment, trace, prover_data)
910    }
911
912    /// Loads cached program trace into the VM.
913    pub fn load_program(&mut self, cached_program_trace: CommittedTraceData<E::PB>) {
914        self.chip_complex.system.load_program(cached_program_trace);
915    }
916
917    pub fn transport_init_memory_to_device(&mut self, memory: &GuestMemory) {
918        self.chip_complex
919            .system
920            .transport_init_memory_to_device(memory);
921    }
922
923    /// See [`SystemChipComplex::memory_top_tree`].
924    pub fn memory_top_tree(&self) -> Option<&[[Val<E::SC>; CHUNK]]> {
925        self.chip_complex.system.memory_top_tree()
926    }
927
928    pub fn executor_idx_to_air_idx(&self) -> Vec<usize> {
929        let ret = self.chip_complex.inventory.executor_idx_to_air_idx();
930        tracing::debug!("executor_idx_to_air_idx: {:?}", ret);
931        assert_eq!(self.executor().inventory.executors().len(), ret.len());
932        ret
933    }
934
935    /// Convenience method to construct a [MeteredCtx] using data from the stored proving key.
936    pub fn build_metered_ctx(&self, exe: &VmExe<Val<E::SC>>) -> MeteredCtx {
937        let config = self.config().as_ref();
938        let program_len = exe.program.num_defined_instructions();
939
940        let (mut constant_trace_heights, air_names, widths, interactions): (
941            Vec<_>,
942            Vec<_>,
943            Vec<_>,
944            Vec<_>,
945        ) = self
946            .pk
947            .per_air
948            .iter()
949            .map(|pk| {
950                let constant_trace_height =
951                    pk.preprocessed_data.as_ref().map(|pd| pd.trace.height());
952                let air_names = pk.air_name.clone();
953                let width = pk
954                    .vk
955                    .params
956                    .width
957                    .total_width(<<E::SC as StarkGenericConfig>::Challenge>::DIMENSION);
958                let num_interactions = pk.vk.symbolic_constraints.interactions.len();
959                (constant_trace_height, air_names, width, num_interactions)
960            })
961            .multiunzip();
962
963        // Program trace is the same for all segments
964        constant_trace_heights[PROGRAM_AIR_ID] = Some(program_len);
965        if config.has_public_values_chip() {
966            // Public values chip is only present when there's a single segment
967            constant_trace_heights[PUBLIC_VALUES_AIR_ID] = Some(config.num_public_values);
968        }
969
970        self.executor().build_metered_ctx(
971            &constant_trace_heights,
972            &air_names,
973            &widths,
974            &interactions,
975        )
976    }
977
978    /// Convenience method to construct a [MeteredCostCtx] using data from the stored proving key.
979    pub fn build_metered_cost_ctx(&self) -> MeteredCostCtx {
980        let widths: Vec<_> = self
981            .pk
982            .per_air
983            .iter()
984            .map(|pk| {
985                pk.vk
986                    .params
987                    .width
988                    .total_width(<<E::SC as StarkGenericConfig>::Challenge>::DIMENSION)
989            })
990            .collect();
991
992        self.executor().build_metered_cost_ctx(&widths)
993    }
994
995    pub fn num_airs(&self) -> usize {
996        let num_airs = self.pk.per_air.len();
997        debug_assert_eq!(num_airs, self.chip_complex.inventory.airs().num_airs());
998        num_airs
999    }
1000
1001    pub fn air_names(&self) -> impl Iterator<Item = &'_ str> {
1002        self.pk.per_air.iter().map(|pk| pk.air_name.as_str())
1003    }
1004
1005    /// See [`debug_proving_ctx`].
1006    #[cfg(feature = "stark-debug")]
1007    pub fn debug_proving_ctx(&mut self, ctx: &ProvingContext<E::PB>) {
1008        if self.h_pk.is_none() {
1009            let air_inv = self.config().create_airs().unwrap();
1010            self.h_pk = Some(air_inv.keygen(&self.engine));
1011        }
1012        let pk = self.h_pk.as_ref().unwrap();
1013        debug_proving_ctx(self, pk, ctx);
1014    }
1015}
1016
1017#[derive(Serialize, Deserialize)]
1018#[serde(bound(
1019    serialize = "Com<SC>: Serialize",
1020    deserialize = "Com<SC>: Deserialize<'de>"
1021))]
1022pub struct ContinuationVmProof<SC: StarkGenericConfig> {
1023    pub per_segment: Vec<Proof<SC>>,
1024    pub user_public_values: UserPublicValuesProof<{ CHUNK }, Val<SC>>,
1025}
1026
1027/// Prover for a specific exe in a specific continuation VM using a specific Stark config.
1028pub trait ContinuationVmProver<SC: StarkGenericConfig> {
1029    fn prove(
1030        &mut self,
1031        input: impl Into<Streams<Val<SC>>>,
1032    ) -> Result<ContinuationVmProof<SC>, VirtualMachineError>;
1033}
1034
1035/// Prover for a specific exe in a specific single-segment VM using a specific Stark config.
1036///
1037/// Does not run metered execution and directly runs preflight execution. The `prove` function must
1038/// be provided with the expected maximum `trace_heights` to use to allocate record arena
1039/// capacities.
1040pub trait SingleSegmentVmProver<SC: StarkGenericConfig> {
1041    fn prove(
1042        &mut self,
1043        input: impl Into<Streams<Val<SC>>>,
1044        trace_heights: &[u32],
1045    ) -> Result<Proof<SC>, VirtualMachineError>;
1046}
1047
1048/// Virtual machine prover instance for a fixed VM config and a fixed program. For use in proving a
1049/// program directly on bare metal.
1050///
1051/// This struct contains the [VmState] itself to avoid re-allocating guest memory. The memory is
1052/// reset with zeros before execution.
1053#[derive(Getters, MutGetters)]
1054pub struct VmInstance<E, VB>
1055where
1056    E: StarkEngine,
1057    VB: VmBuilder<E>,
1058{
1059    pub vm: VirtualMachine<E, VB>,
1060    pub interpreter: PreflightInterpretedInstance2<Val<E::SC>, VB::VmConfig>,
1061    #[getset(get = "pub")]
1062    program_commitment: Com<E::SC>,
1063    #[getset(get = "pub")]
1064    exe: Arc<VmExe<Val<E::SC>>>,
1065    #[getset(get = "pub", get_mut = "pub")]
1066    state: Option<VmState<Val<E::SC>, GuestMemory>>,
1067}
1068
1069impl<E, VB> VmInstance<E, VB>
1070where
1071    E: StarkEngine,
1072    VB: VmBuilder<E>,
1073{
1074    pub fn new(
1075        mut vm: VirtualMachine<E, VB>,
1076        exe: Arc<VmExe<Val<E::SC>>>,
1077        cached_program_trace: CommittedTraceData<E::PB>,
1078    ) -> Result<Self, StaticProgramError> {
1079        let program_commitment = cached_program_trace.commitment.clone();
1080        vm.load_program(cached_program_trace);
1081        let interpreter = vm.preflight_interpreter(&exe)?;
1082        let state = vm.create_initial_state(&exe, vec![]);
1083        Ok(Self {
1084            vm,
1085            interpreter,
1086            program_commitment,
1087            exe,
1088            state: Some(state),
1089        })
1090    }
1091
1092    #[instrument(name = "vm.reset_state", level = "debug", skip_all)]
1093    pub fn reset_state(&mut self, inputs: impl Into<Streams<Val<E::SC>>>) {
1094        let state = self.state.as_mut().unwrap();
1095        state.reset(&self.exe.init_memory, self.exe.pc_start, inputs);
1096
1097        #[cfg(all(feature = "metrics", any(feature = "perf-metrics", debug_assertions)))]
1098        {
1099            state.metrics.fn_bounds = self.exe.fn_bounds.clone();
1100            state.metrics.debug_infos = self.exe.program.debug_infos();
1101        }
1102    }
1103}
1104
1105impl<E, VB> ContinuationVmProver<E::SC> for VmInstance<E, VB>
1106where
1107    E: StarkEngine,
1108    Val<E::SC>: PrimeField32,
1109    VB: VmBuilder<E>,
1110    <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1111        + MeteredExecutor<Val<E::SC>>
1112        + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1113{
1114    /// First performs metered execution (E2) to determine segments. Then sequentially proves each
1115    /// segment. The proof for each segment uses the specified [ProverBackend], but the proof for
1116    /// the next segment does not start before the current proof finishes.
1117    fn prove(
1118        &mut self,
1119        input: impl Into<Streams<Val<E::SC>>>,
1120    ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1121        self.prove_continuations(input, |_, _| {})
1122    }
1123}
1124
1125impl<E, VB> VmInstance<E, VB>
1126where
1127    E: StarkEngine,
1128    Val<E::SC>: PrimeField32,
1129    VB: VmBuilder<E>,
1130    <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor: Executor<Val<E::SC>>
1131        + MeteredExecutor<Val<E::SC>>
1132        + PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1133{
1134    /// For internal use to resize trace matrices before proving.
1135    ///
1136    /// The closure `modify_ctx(seg_idx, &mut ctx)` is called sequentially for each segment.
1137    pub fn prove_continuations(
1138        &mut self,
1139        input: impl Into<Streams<Val<E::SC>>>,
1140        mut modify_ctx: impl FnMut(usize, &mut ProvingContext<E::PB>),
1141    ) -> Result<ContinuationVmProof<E::SC>, VirtualMachineError> {
1142        let input = input.into();
1143        self.reset_state(input.clone());
1144        let vm = &mut self.vm;
1145        let metered_ctx = vm.build_metered_ctx(&self.exe);
1146        let metered_interpreter = vm.metered_interpreter(&self.exe)?;
1147        let (segments, _) = metered_interpreter.execute_metered(input, metered_ctx)?;
1148        let mut proofs = Vec::with_capacity(segments.len());
1149        let mut state = self.state.take();
1150        for (seg_idx, segment) in segments.into_iter().enumerate() {
1151            let _segment_span = info_span!("prove_segment", segment = seg_idx).entered();
1152            // We need a separate span so the metric label includes "segment" from _segment_span
1153            let _prove_span = info_span!("total_proof").entered();
1154            let Segment {
1155                num_insns,
1156                trace_heights,
1157                ..
1158            } = segment;
1159            let from_state = Option::take(&mut state).unwrap();
1160            vm.transport_init_memory_to_device(&from_state.memory);
1161            let PreflightExecutionOutput {
1162                system_records,
1163                record_arenas,
1164                to_state,
1165            } = vm.execute_preflight(
1166                &mut self.interpreter,
1167                from_state,
1168                Some(num_insns),
1169                &trace_heights,
1170            )?;
1171            state = Some(to_state);
1172
1173            let mut ctx = vm.generate_proving_ctx(system_records, record_arenas)?;
1174            modify_ctx(seg_idx, &mut ctx);
1175            let proof = vm.engine.prove(vm.pk(), ctx);
1176            proofs.push(proof);
1177        }
1178        let to_state = state.unwrap();
1179        let final_memory = &to_state.memory.memory;
1180        let final_memory_top_tree = vm.memory_top_tree().expect("memory top tree should exist");
1181        let user_public_values = UserPublicValuesProof::compute(
1182            vm.config().as_ref().memory_config.memory_dimensions(),
1183            vm.config().as_ref().num_public_values,
1184            &vm_poseidon2_hasher(),
1185            final_memory,
1186            final_memory_top_tree,
1187        );
1188        self.state = Some(to_state);
1189        Ok(ContinuationVmProof {
1190            per_segment: proofs,
1191            user_public_values,
1192        })
1193    }
1194}
1195
1196impl<E, VB> SingleSegmentVmProver<E::SC> for VmInstance<E, VB>
1197where
1198    E: StarkEngine,
1199    Val<E::SC>: PrimeField32,
1200    VB: VmBuilder<E>,
1201    <VB::VmConfig as VmExecutionConfig<Val<E::SC>>>::Executor:
1202        PreflightExecutor<Val<E::SC>, VB::RecordArena>,
1203{
1204    #[instrument(name = "total_proof", skip_all)]
1205    fn prove(
1206        &mut self,
1207        input: impl Into<Streams<Val<E::SC>>>,
1208        trace_heights: &[u32],
1209    ) -> Result<Proof<E::SC>, VirtualMachineError> {
1210        self.reset_state(input);
1211        let vm = &mut self.vm;
1212        let exe = &self.exe;
1213        assert!(!vm.config().as_ref().continuation_enabled);
1214        let mut trace_heights = trace_heights.to_vec();
1215        trace_heights[PUBLIC_VALUES_AIR_ID] = vm.config().as_ref().num_public_values as u32;
1216        let state = self.state.take().expect("State should always be present");
1217        let num_custom_pvs = state.custom_pvs.len();
1218        let (proof, final_memory) = vm.prove(&mut self.interpreter, state, None, &trace_heights)?;
1219        let final_memory = final_memory.ok_or(ExecutionError::DidNotTerminate)?;
1220        // Put back state to avoid re-allocation
1221        self.state = Some(VmState::new_with_defaults(
1222            exe.pc_start,
1223            final_memory,
1224            vec![],
1225            DEFAULT_RNG_SEED,
1226            num_custom_pvs,
1227        ));
1228        Ok(proof)
1229    }
1230}
1231
1232/// Verifies a single proof. This should be used for proof of VM without continuations.
1233///
1234/// ## Note
1235/// This function does not check any public values or extract the starting pc or commitment
1236/// to the [VmCommittedExe].
1237pub fn verify_single<E>(
1238    engine: &E,
1239    vk: &MultiStarkVerifyingKey<E::SC>,
1240    proof: &Proof<E::SC>,
1241) -> Result<(), VerificationError>
1242where
1243    E: StarkEngine,
1244{
1245    engine.verify(vk, proof)
1246}
1247
1248/// The payload of a verified guest VM execution.
1249pub struct VerifiedExecutionPayload<F> {
1250    /// The Merklelized hash of:
1251    /// - Program code commitment (commitment of the cached trace)
1252    /// - Merkle root of the initial memory
1253    /// - Starting program counter (`pc_start`)
1254    ///
1255    /// The Merklelization uses Poseidon2 as a cryptographic hash function (for the leaves)
1256    /// and a cryptographic compression function (for internal nodes).
1257    pub exe_commit: [F; CHUNK],
1258    /// The Merkle root of the final memory state.
1259    pub final_memory_root: [F; CHUNK],
1260}
1261
1262/// Verify segment proofs with boundary condition checks for continuation between segments.
1263///
1264/// Assumption:
1265/// - `vk` is a valid verifying key of a VM circuit.
1266///
1267/// Returns:
1268/// - The commitment to the [VmCommittedExe] extracted from `proofs`. It is the responsibility of
1269///   the caller to check that the returned commitment matches the VM executable that the VM was
1270///   supposed to execute.
1271/// - The Merkle root of the final memory state.
1272///
1273/// ## Note
1274/// This function does not extract or verify any user public values from the final memory state.
1275/// This verification requires an additional Merkle proof with respect to the Merkle root of
1276/// the final memory state.
1277// @dev: This function doesn't need to be generic in `VC`.
1278pub fn verify_segments<E>(
1279    engine: &E,
1280    vk: &MultiStarkVerifyingKey<E::SC>,
1281    proofs: &[Proof<E::SC>],
1282) -> Result<VerifiedExecutionPayload<Val<E::SC>>, VmVerificationError>
1283where
1284    E: StarkEngine,
1285    Val<E::SC>: PrimeField32,
1286    Com<E::SC>: AsRef<[Val<E::SC>; CHUNK]>,
1287{
1288    if proofs.is_empty() {
1289        return Err(VmVerificationError::ProofNotFound);
1290    }
1291    let mut prev_final_memory_root = None;
1292    let mut prev_final_pc = None;
1293    let mut start_pc = None;
1294    let mut initial_memory_root = None;
1295    let mut program_commit = None;
1296
1297    for (i, proof) in proofs.iter().enumerate() {
1298        let res = engine.verify(vk, proof);
1299        match res {
1300            Ok(_) => (),
1301            Err(e) => return Err(VmVerificationError::StarkError(e)),
1302        };
1303
1304        let mut program_air_present = false;
1305        let mut connector_air_present = false;
1306        let mut merkle_air_present = false;
1307
1308        // Check public values.
1309        for air_proof_data in proof.per_air.iter() {
1310            let pvs = &air_proof_data.public_values;
1311            let air_vk = &vk.inner.per_air[air_proof_data.air_id];
1312            if air_proof_data.air_id == PROGRAM_AIR_ID {
1313                program_air_present = true;
1314                if i == 0 {
1315                    program_commit =
1316                        Some(proof.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref());
1317                } else if program_commit.unwrap()
1318                    != proof.commitments.main_trace[PROGRAM_CACHED_TRACE_INDEX].as_ref()
1319                {
1320                    return Err(VmVerificationError::ProgramCommitMismatch { index: i });
1321                }
1322            } else if air_proof_data.air_id == CONNECTOR_AIR_ID {
1323                connector_air_present = true;
1324                let pvs: &VmConnectorPvs<_> = pvs.as_slice().borrow();
1325
1326                if i != 0 {
1327                    // Check initial pc matches the previous final pc.
1328                    if pvs.initial_pc != prev_final_pc.unwrap() {
1329                        return Err(VmVerificationError::InitialPcMismatch {
1330                            initial: pvs.initial_pc.as_canonical_u32(),
1331                            prev_final: prev_final_pc.unwrap().as_canonical_u32(),
1332                        });
1333                    }
1334                } else {
1335                    start_pc = Some(pvs.initial_pc);
1336                }
1337                prev_final_pc = Some(pvs.final_pc);
1338
1339                let expected_is_terminate = i == proofs.len() - 1;
1340                if pvs.is_terminate != PrimeCharacteristicRing::from_bool(expected_is_terminate) {
1341                    return Err(VmVerificationError::IsTerminateMismatch {
1342                        expected: expected_is_terminate,
1343                        actual: pvs.is_terminate.as_canonical_u32() != 0,
1344                    });
1345                }
1346
1347                let expected_exit_code = if expected_is_terminate {
1348                    ExitCode::Success as u32
1349                } else {
1350                    DEFAULT_SUSPEND_EXIT_CODE
1351                };
1352                if pvs.exit_code != PrimeCharacteristicRing::from_u32(expected_exit_code) {
1353                    return Err(VmVerificationError::ExitCodeMismatch {
1354                        expected: expected_exit_code,
1355                        actual: pvs.exit_code.as_canonical_u32(),
1356                    });
1357                }
1358            } else if air_proof_data.air_id == MERKLE_AIR_ID {
1359                merkle_air_present = true;
1360                let pvs: &MemoryMerklePvs<_, CHUNK> = pvs.as_slice().borrow();
1361
1362                // Check that initial root matches the previous final root.
1363                if i != 0 {
1364                    if pvs.initial_root != prev_final_memory_root.unwrap() {
1365                        return Err(VmVerificationError::InitialMemoryRootMismatch);
1366                    }
1367                } else {
1368                    initial_memory_root = Some(pvs.initial_root);
1369                }
1370                prev_final_memory_root = Some(pvs.final_root);
1371            } else {
1372                if !pvs.is_empty() {
1373                    return Err(VmVerificationError::UnexpectedPvs {
1374                        expected: 0,
1375                        actual: pvs.len(),
1376                    });
1377                }
1378                // We assume the vk is valid, so this is only a debug assert.
1379                debug_assert_eq!(air_vk.params.num_public_values, 0);
1380            }
1381        }
1382        if !program_air_present {
1383            return Err(VmVerificationError::SystemAirMissing {
1384                air_id: PROGRAM_AIR_ID,
1385            });
1386        }
1387        if !connector_air_present {
1388            return Err(VmVerificationError::SystemAirMissing {
1389                air_id: CONNECTOR_AIR_ID,
1390            });
1391        }
1392        if !merkle_air_present {
1393            return Err(VmVerificationError::SystemAirMissing {
1394                air_id: MERKLE_AIR_ID,
1395            });
1396        }
1397    }
1398    let exe_commit = compute_exe_commit(
1399        &vm_poseidon2_hasher(),
1400        program_commit.unwrap(),
1401        initial_memory_root.as_ref().unwrap(),
1402        start_pc.unwrap(),
1403    );
1404    Ok(VerifiedExecutionPayload {
1405        exe_commit,
1406        final_memory_root: prev_final_memory_root.unwrap(),
1407    })
1408}
1409
1410impl<SC: StarkGenericConfig> Clone for ContinuationVmProof<SC>
1411where
1412    Com<SC>: Clone,
1413{
1414    fn clone(&self) -> Self {
1415        Self {
1416            per_segment: self.per_segment.clone(),
1417            user_public_values: self.user_public_values.clone(),
1418        }
1419    }
1420}
1421
1422pub(super) fn create_memory_image(
1423    memory_config: &MemoryConfig,
1424    init_memory: &SparseMemoryImage,
1425) -> GuestMemory {
1426    let mut inner = AddressMap::new(memory_config.addr_spaces.clone());
1427    inner.set_from_sparse(init_memory);
1428    GuestMemory::new(inner)
1429}
1430
1431impl<E, VC> VirtualMachine<E, VC>
1432where
1433    E: StarkEngine,
1434    VC: VmBuilder<E>,
1435    VC::SystemChipInventory: SystemWithFixedTraceHeights,
1436{
1437    /// Sets fixed trace heights for the system AIRs' trace matrices.
1438    pub fn override_system_trace_heights(&mut self, heights: &[u32]) {
1439        let num_sys_airs = self.config().as_ref().num_airs();
1440        assert!(heights.len() >= num_sys_airs);
1441        self.chip_complex
1442            .system
1443            .override_trace_heights(&heights[..num_sys_airs]);
1444    }
1445}
1446
1447/// Runs the STARK backend debugger to check the constraints against the trace matrices
1448/// logically, instead of cryptographically. This will panic if any constraint is violated, and
1449/// using `RUST_BACKTRACE=1` can be used to read the stack backtrace of where the constraint
1450/// failed in the code (this requires the code to be compiled with debug=true). Using lower
1451/// optimization levels like -O0 will prevent the compiler from inlining and give better
1452/// debugging information.
1453// @dev The debugger needs the host proving key.
1454//      This function is used both by VirtualMachine::debug_proving_ctx and by
1455// stark_utils::air_test_impl
1456#[cfg(any(debug_assertions, feature = "test-utils", feature = "stark-debug"))]
1457#[tracing::instrument(level = "debug", skip_all)]
1458pub fn debug_proving_ctx<E, VB>(
1459    vm: &VirtualMachine<E, VB>,
1460    pk: &MultiStarkProvingKey<E::SC>,
1461    ctx: &ProvingContext<E::PB>,
1462) where
1463    E: StarkEngine,
1464    VB: VmBuilder<E>,
1465{
1466    use itertools::multiunzip;
1467    use openvm_stark_backend::prover::types::AirProofRawInput;
1468
1469    let device = vm.engine.device();
1470    let air_inv = vm.config().create_airs().unwrap();
1471    let global_airs = air_inv.into_airs().collect_vec();
1472    let (airs, pks, proof_inputs): (Vec<_>, Vec<_>, Vec<_>) =
1473        multiunzip(ctx.per_air.iter().map(|(air_id, air_ctx)| {
1474            // Transfer from device **back** to host so the debugger can read the data.
1475            let cached_mains = air_ctx
1476                .cached_mains
1477                .iter()
1478                .map(|pre| device.transport_matrix_from_device_to_host(&pre.trace))
1479                .collect_vec();
1480            let common_main = air_ctx
1481                .common_main
1482                .as_ref()
1483                .map(|m| device.transport_matrix_from_device_to_host(m));
1484            let public_values = air_ctx.public_values.clone();
1485            let raw = AirProofRawInput {
1486                cached_mains,
1487                common_main,
1488                public_values,
1489            };
1490            (
1491                global_airs[*air_id].clone(),
1492                pk.per_air[*air_id].clone(),
1493                raw,
1494            )
1495        }));
1496    vm.engine.debug(&airs, &pks, &proof_inputs);
1497}
1498
1499#[cfg(feature = "metrics")]
1500mod vm_metrics {
1501    use std::iter::zip;
1502
1503    use metrics::counter;
1504
1505    use super::*;
1506    use crate::arch::Arena;
1507
1508    impl<E, VB> VirtualMachine<E, VB>
1509    where
1510        E: StarkEngine,
1511        VB: VmBuilder<E>,
1512    {
1513        /// Assumed that `record_arenas` has length equal to number of AIRs.
1514        ///
1515        /// Best effort calculation of the used trace heights per chip without padding to powers of
1516        /// two. This is best effort because some periphery chips may not have record arenas to
1517        /// instrument. This function includes the constant trace heights, and the used height of
1518        /// the program trace. It does not include the memory access adapter trace heights,
1519        /// which is included in `SystemChipComplex::finalize_trace_heights`.
1520        pub(crate) fn get_trace_heights_from_arenas(
1521            &self,
1522            system_records: &SystemRecords<Val<E::SC>>,
1523            record_arenas: &[VB::RecordArena],
1524        ) -> Vec<usize> {
1525            let num_airs = self.num_airs();
1526            assert_eq!(num_airs, record_arenas.len());
1527            let mut heights: Vec<usize> = record_arenas
1528                .iter()
1529                .map(|arena| arena.current_trace_height())
1530                .collect();
1531            // If there are any constant trace heights, set them
1532            for (pk, height) in zip(&self.pk.per_air, &mut heights) {
1533                if let Some(constant_height) =
1534                    pk.preprocessed_data.as_ref().map(|pd| pd.trace.height())
1535                {
1536                    *height = constant_height;
1537                }
1538            }
1539            // Program chip used height
1540            heights[PROGRAM_AIR_ID] = system_records.filtered_exec_frequencies.len();
1541
1542            heights
1543        }
1544
1545        /// Update used trace heights after tracegen is done (primarily updating memory-related
1546        /// metrics) and then emit the final metrics.
1547        pub(crate) fn finalize_metrics(&self, heights: &mut [usize]) {
1548            self.chip_complex.system.finalize_trace_heights(heights);
1549            let mut main_cells_used = 0usize;
1550            let mut total_cells_used = 0usize;
1551            for (pk, height) in zip(&self.pk.per_air, heights.iter()) {
1552                let width = &pk.vk.params.width;
1553                main_cells_used += width.main_width() * *height;
1554                total_cells_used += width
1555                    .total_width(<E::SC as StarkGenericConfig>::Challenge::DIMENSION)
1556                    * *height;
1557            }
1558            tracing::debug!(?heights);
1559            tracing::info!(main_cells_used, total_cells_used);
1560            counter!("main_cells_used").absolute(main_cells_used as u64);
1561            counter!("total_cells_used").absolute(total_cells_used as u64);
1562
1563            #[cfg(feature = "perf-metrics")]
1564            {
1565                for (name, value) in zip(self.air_names(), heights) {
1566                    let labels = [("air_name", name.to_string())];
1567                    counter!("rows_used", &labels).absolute(*value as u64);
1568                }
1569            }
1570        }
1571    }
1572}