Skip to main content

miden_processor/trace/
mod.rs

1use alloc::vec::Vec;
2#[cfg(any(test, feature = "testing"))]
3use core::ops::Range;
4
5#[cfg(feature = "std")]
6use miden_air::trace::PADDED_TRACE_WIDTH;
7use miden_air::{
8    AirWitness, AuxBuilder, ProcessorAir, PublicInputs, debug,
9    trace::{
10        DECODER_TRACE_OFFSET, MainTrace, STACK_TRACE_OFFSET, TRACE_WIDTH,
11        decoder::{NUM_USER_OP_HELPERS, USER_OP_HELPERS_OFFSET},
12    },
13};
14
15use crate::{
16    AdviceProvider, Felt, MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs, Word, ZERO,
17    fast::ExecutionOutput,
18    field::{ExtensionField, QuadFelt},
19    precompile::{PrecompileRequest, PrecompileTranscript},
20    utils::{ColMatrix, Matrix, RowMajorMatrix},
21};
22
23pub(crate) mod utils;
24use miden_air::trace::Challenges;
25use utils::{AuxColumnBuilder, TraceFragment};
26
27pub mod chiplets;
28pub mod execution_tracer;
29
30mod decoder;
31mod parallel;
32mod range;
33mod stack;
34mod trace_state;
35
36#[cfg(test)]
37mod tests;
38
39// RE-EXPORTS
40// ================================================================================================
41
42pub use miden_air::trace::RowIndex;
43pub use parallel::{CORE_TRACE_WIDTH, build_trace, build_trace_with_max_len};
44pub use utils::{ChipletsLengths, TraceLenSummary};
45
46// VM EXECUTION TRACE
47// ================================================================================================
48
49/// Execution trace which is generated when a program is executed on the VM.
50///
51/// The trace consists of the following components:
52/// - Main traces of System, Decoder, Operand Stack, Range Checker, and Chiplets.
53/// - Auxiliary trace builders.
54/// - Information about the program (program hash and the kernel).
55/// - Information about execution outputs (stack state, advice provider, and precompile transcript).
56/// - Summary of trace lengths of the main trace components.
57#[derive(Debug)]
58pub struct ExecutionTrace {
59    main_trace: MainTrace,
60    aux_trace_builders: AuxTraceBuilders,
61    program_info: ProgramInfo,
62    stack_outputs: StackOutputs,
63    advice: AdviceProvider,
64    final_pc_transcript: PrecompileTranscript,
65    trace_len_summary: TraceLenSummary,
66}
67
68impl ExecutionTrace {
69    // CONSTRUCTOR
70    // --------------------------------------------------------------------------------------------
71
72    pub fn new_from_parts(
73        program_info: ProgramInfo,
74        execution_output: ExecutionOutput,
75        main_trace: MainTrace,
76        aux_trace_builders: AuxTraceBuilders,
77        trace_len_summary: TraceLenSummary,
78    ) -> Self {
79        Self {
80            main_trace,
81            aux_trace_builders,
82            program_info,
83            stack_outputs: execution_output.stack,
84            advice: execution_output.advice,
85            final_pc_transcript: execution_output.final_pc_transcript,
86            trace_len_summary,
87        }
88    }
89
90    // PUBLIC ACCESSORS
91    // --------------------------------------------------------------------------------------------
92
93    /// Returns the program info of this execution trace.
94    pub fn program_info(&self) -> &ProgramInfo {
95        &self.program_info
96    }
97
98    /// Returns hash of the program execution of which resulted in this execution trace.
99    pub fn program_hash(&self) -> &Word {
100        self.program_info.program_hash()
101    }
102
103    /// Returns outputs of the program execution which resulted in this execution trace.
104    pub fn stack_outputs(&self) -> &StackOutputs {
105        &self.stack_outputs
106    }
107
108    /// Returns the public inputs for this execution trace.
109    pub fn public_inputs(&self) -> PublicInputs {
110        PublicInputs::new(
111            self.program_info.clone(),
112            self.init_stack_state(),
113            self.stack_outputs,
114            self.final_pc_transcript.state(),
115        )
116    }
117
118    /// Returns the public values for this execution trace.
119    pub fn to_public_values(&self) -> Vec<Felt> {
120        self.public_inputs().to_elements()
121    }
122
123    /// Returns a clone of the auxiliary trace builders.
124    pub fn aux_trace_builders(&self) -> AuxTraceBuilders {
125        self.aux_trace_builders.clone()
126    }
127
128    /// Returns a reference to the main trace.
129    pub fn main_trace(&self) -> &MainTrace {
130        &self.main_trace
131    }
132
133    /// Returns a mutable reference to the main trace.
134    pub fn main_trace_mut(&mut self) -> &mut MainTrace {
135        &mut self.main_trace
136    }
137
138    /// Returns the precompile requests generated during program execution.
139    pub fn precompile_requests(&self) -> &[PrecompileRequest] {
140        self.advice.precompile_requests()
141    }
142
143    /// Moves all accumulated precompile requests out of the trace, leaving it empty.
144    ///
145    /// Intended for proof packaging, where requests are serialized into the proof and no longer
146    /// needed in the trace after consumption.
147    pub fn take_precompile_requests(&mut self) -> Vec<PrecompileRequest> {
148        self.advice.take_precompile_requests()
149    }
150
151    /// Returns the final precompile transcript after executing all precompile requests.
152    pub fn final_precompile_transcript(&self) -> PrecompileTranscript {
153        self.final_pc_transcript
154    }
155
156    /// Returns the initial state of the top 16 stack registers.
157    pub fn init_stack_state(&self) -> StackInputs {
158        let mut result = [ZERO; MIN_STACK_DEPTH];
159        for (i, result) in result.iter_mut().enumerate() {
160            *result = self.main_trace.get_column(i + STACK_TRACE_OFFSET)[0];
161        }
162        result.into()
163    }
164
165    /// Returns the final state of the top 16 stack registers.
166    pub fn last_stack_state(&self) -> StackOutputs {
167        let last_step = self.last_step();
168        let mut result = [ZERO; MIN_STACK_DEPTH];
169        for (i, result) in result.iter_mut().enumerate() {
170            *result = self.main_trace.get_column(i + STACK_TRACE_OFFSET)[last_step];
171        }
172        result.into()
173    }
174
175    /// Returns helper registers state at the specified `clk` of the VM
176    pub fn get_user_op_helpers_at(&self, clk: u32) -> [Felt; NUM_USER_OP_HELPERS] {
177        let mut result = [ZERO; NUM_USER_OP_HELPERS];
178        for (i, result) in result.iter_mut().enumerate() {
179            *result = self.main_trace.get_column(DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET + i)
180                [clk as usize];
181        }
182        result
183    }
184
185    /// Returns the trace length.
186    pub fn get_trace_len(&self) -> usize {
187        self.main_trace.num_rows()
188    }
189
190    /// Returns the length of the trace (number of rows in the main trace).
191    pub fn length(&self) -> usize {
192        self.get_trace_len()
193    }
194
195    /// Returns a summary of the lengths of main, range and chiplet traces.
196    pub fn trace_len_summary(&self) -> &TraceLenSummary {
197        &self.trace_len_summary
198    }
199
200    /// Returns the final advice provider state.
201    pub fn advice_provider(&self) -> &AdviceProvider {
202        &self.advice
203    }
204
205    /// Destructures this execution trace into the process’s final stack and advice states.
206    pub fn into_outputs(self) -> (StackOutputs, AdviceProvider) {
207        (self.stack_outputs, self.advice)
208    }
209
210    // DEBUG CONSTRAINT CHECKING
211    // --------------------------------------------------------------------------------------------
212
213    /// Validates this execution trace against all AIR constraints without generating a STARK
214    /// proof.
215    ///
216    /// This is the recommended way to test trace correctness. It is much faster than full STARK
217    /// proving and provides better error diagnostics (panics on the first constraint violation
218    /// with the instance and row index).
219    ///
220    /// # Panics
221    ///
222    /// Panics if any AIR constraint evaluates to nonzero.
223    pub fn check_constraints(&self) {
224        let public_inputs = self.public_inputs();
225        let trace_matrix = self.to_row_major_matrix();
226
227        let (public_values, kernel_felts) = public_inputs.to_air_inputs();
228        let var_len_public_inputs: &[&[Felt]] = &[&kernel_felts];
229
230        let aux_builder = self.aux_trace_builders();
231
232        // Derive deterministic challenges by hashing public values with Poseidon2.
233        // The 4-element digest maps directly to 2 QuadFelt challenges.
234        let digest = crate::crypto::hash::Poseidon2::hash_elements(&public_values);
235        let challenges =
236            [QuadFelt::new([digest[0], digest[1]]), QuadFelt::new([digest[2], digest[3]])];
237
238        let witness = AirWitness::new(&trace_matrix, &public_values, var_len_public_inputs);
239        debug::check_constraints(&ProcessorAir, witness, &aux_builder, &challenges);
240    }
241
242    /// Converts the main trace from column-major to row-major format.
243    ///
244    /// Only includes the first [`TRACE_WIDTH`] columns (excluding padding columns added for
245    /// Poseidon2 rate alignment), which is the width expected by the AIR.
246    // TODO: the padding columns can be removed once we use the lifted-stark's virtual trace
247    // alignment, which pads to the required rate width without materializing extra columns.
248    pub fn to_row_major_matrix(&self) -> RowMajorMatrix<Felt> {
249        let trace_len = self.get_trace_len();
250        let mut col_major_data = Vec::with_capacity(TRACE_WIDTH * trace_len);
251        for col_idx in 0..TRACE_WIDTH {
252            col_major_data.extend_from_slice(self.main_trace.get_column(col_idx));
253        }
254        let col_major_matrix = RowMajorMatrix::new(col_major_data, trace_len);
255        col_major_matrix.transpose()
256    }
257
258    // HELPER METHODS
259    // --------------------------------------------------------------------------------------------
260
261    /// Returns the index of the last row in the trace.
262    fn last_step(&self) -> usize {
263        self.length() - 1
264    }
265
266    // TEST HELPERS
267    // --------------------------------------------------------------------------------------------
268    #[cfg(feature = "std")]
269    pub fn print(&self) {
270        use miden_air::trace::TRACE_WIDTH;
271
272        let mut row = [ZERO; PADDED_TRACE_WIDTH];
273        for i in 0..self.length() {
274            self.main_trace.read_row_into(i, &mut row);
275            std::println!(
276                "{:?}",
277                row.iter().take(TRACE_WIDTH).map(|v| v.as_canonical_u64()).collect::<Vec<_>>()
278            );
279        }
280    }
281
282    #[cfg(any(test, feature = "testing"))]
283    pub fn get_column_range(&self, range: Range<usize>) -> Vec<Vec<Felt>> {
284        self.main_trace.get_column_range(range)
285    }
286
287    pub fn build_aux_trace<E>(&self, rand_elements: &[E]) -> Option<ColMatrix<E>>
288    where
289        E: ExtensionField<Felt>,
290    {
291        let aux_columns =
292            self.aux_trace_builders.build_aux_columns(&self.main_trace, rand_elements);
293
294        Some(ColMatrix::new(aux_columns))
295    }
296}
297
298// AUX TRACE BUILDERS
299// ================================================================================================
300
301#[derive(Debug, Clone)]
302pub struct AuxTraceBuilders {
303    pub(crate) decoder: decoder::AuxTraceBuilder,
304    pub(crate) stack: stack::AuxTraceBuilder,
305    pub(crate) range: range::AuxTraceBuilder,
306    pub(crate) chiplets: chiplets::AuxTraceBuilder,
307}
308
309impl AuxTraceBuilders {
310    /// Builds auxiliary columns for all trace segments given the main trace and challenges.
311    ///
312    /// This is the internal column-major version used by the processor.
313    pub fn build_aux_columns<E>(&self, main_trace: &MainTrace, challenges: &[E]) -> Vec<Vec<E>>
314    where
315        E: ExtensionField<Felt>,
316    {
317        // Expand raw challenges (alpha, beta) into coefficient array once, then pass
318        // the expanded challenges to all sub-builders.
319        let challenges = Challenges::<E>::new(challenges[0], challenges[1]);
320
321        let decoder_cols = self.decoder.build_aux_columns(main_trace, &challenges);
322        let stack_cols = self.stack.build_aux_columns(main_trace, &challenges);
323        let range_cols = self.range.build_aux_columns(main_trace, &challenges);
324        let chiplets_cols = self.chiplets.build_aux_columns(main_trace, &challenges);
325
326        decoder_cols
327            .into_iter()
328            .chain(stack_cols)
329            .chain(range_cols)
330            .chain(chiplets_cols)
331            .collect()
332    }
333}
334
335// PLONKY3 AUX TRACE BUILDER
336// ================================================================================================
337//
338// Implements the upstream `AuxBuilder` trait from `p3_miden_lifted_air` directly on
339// `AuxTraceBuilders`. Plonky3 uses row-major matrices while our existing aux trace building logic
340// uses column-major format. This impl adapts between the two by converting the main trace from
341// row-major to column-major, delegating to the existing logic, and converting the result back.
342
343impl<EF: ExtensionField<Felt>> AuxBuilder<Felt, EF> for AuxTraceBuilders {
344    fn build_aux_trace(
345        &self,
346        main: &RowMajorMatrix<Felt>,
347        challenges: &[EF],
348    ) -> (RowMajorMatrix<EF>, Vec<EF>) {
349        let _span = tracing::info_span!("build_aux_trace").entered();
350
351        // Transpose the row-major main trace into column-major `MainTrace` needed by the
352        // auxiliary trace builders. The last program row is the point where the clock
353        // (column 0) stops incrementing.
354        let main_trace_col_major = {
355            let num_rows = main.height();
356            // Detect last program row from row-major layout using column 0 (clock).
357            let last_program_row = (1..num_rows)
358                .find(|&i| {
359                    main.get(i, 0).expect("valid indices")
360                        != main.get(i - 1, 0).expect("valid indices") + Felt::ONE
361                })
362                .map_or(num_rows - 1, |i| i - 1);
363            let transposed = main.transpose();
364            let columns: Vec<Vec<Felt>> = transposed.row_slices().map(|row| row.to_vec()).collect();
365            MainTrace::new(ColMatrix::new(columns), last_program_row.into())
366        };
367
368        // Build auxiliary columns in column-major format.
369        let aux_columns = self.build_aux_columns(&main_trace_col_major, challenges);
370        assert!(!aux_columns.is_empty(), "aux columns should not be empty");
371
372        // Flatten column-major aux columns into a contiguous buffer, then transpose
373        // to the row-major layout expected by the lifted prover.
374        let trace_len = main.height();
375        let num_ef_cols = aux_columns.len();
376        let mut col_major_data = Vec::with_capacity(trace_len * num_ef_cols);
377        for col in aux_columns {
378            col_major_data.extend_from_slice(&col);
379        }
380        let aux_trace = RowMajorMatrix::new(col_major_data, trace_len).transpose();
381
382        // Extract the last row from the row-major aux trace for Fiat-Shamir.
383        let last_row = aux_trace
384            .row_slice(trace_len - 1)
385            .expect("aux trace has at least one row")
386            .to_vec();
387
388        (aux_trace, last_row)
389    }
390}