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