miden_processor/trace/
mod.rs

1use alloc::vec::Vec;
2use core::mem;
3
4use miden_air::trace::{
5    AUX_TRACE_RAND_ELEMENTS, AUX_TRACE_WIDTH, DECODER_TRACE_OFFSET, MIN_TRACE_LEN,
6    PADDED_TRACE_WIDTH, STACK_TRACE_OFFSET, TRACE_WIDTH,
7    decoder::{NUM_USER_OP_HELPERS, USER_OP_HELPERS_OFFSET},
8    main_trace::MainTrace,
9};
10use miden_core::{ProgramInfo, StackInputs, StackOutputs, Word, ZERO, stack::MIN_STACK_DEPTH};
11use winter_prover::{EvaluationFrame, Trace, TraceInfo, crypto::RandomCoin};
12
13use super::{
14    AdviceProvider, ColMatrix, Felt, FieldElement, Process,
15    chiplets::AuxTraceBuilder as ChipletsAuxTraceBuilder, crypto::RpoRandomCoin,
16    decoder::AuxTraceBuilder as DecoderAuxTraceBuilder,
17    range::AuxTraceBuilder as RangeCheckerAuxTraceBuilder,
18    stack::AuxTraceBuilder as StackAuxTraceBuilder,
19};
20
21mod utils;
22pub use utils::{AuxColumnBuilder, ChipletsLengths, TraceFragment, TraceLenSummary};
23
24#[cfg(test)]
25mod tests;
26#[cfg(test)]
27use super::EMPTY_WORD;
28
29// CONSTANTS
30// ================================================================================================
31
32/// Number of rows at the end of an execution trace which are injected with random values.
33pub const NUM_RAND_ROWS: usize = 1;
34
35// VM EXECUTION TRACE
36// ================================================================================================
37
38#[derive(Debug)]
39pub struct AuxTraceBuilders {
40    pub(crate) decoder: DecoderAuxTraceBuilder,
41    pub(crate) stack: StackAuxTraceBuilder,
42    pub(crate) range: RangeCheckerAuxTraceBuilder,
43    pub(crate) chiplets: ChipletsAuxTraceBuilder,
44}
45
46/// Execution trace which is generated when a program is executed on the VM.
47///
48/// The trace consists of the following components:
49/// - Main traces of System, Decoder, Operand Stack, Range Checker, and Auxiliary Co-Processor
50///   components.
51/// - Hints used during auxiliary trace segment construction.
52/// - Metadata needed by the STARK prover.
53#[derive(Debug)]
54pub struct ExecutionTrace {
55    meta: Vec<u8>,
56    trace_info: TraceInfo,
57    main_trace: MainTrace,
58    aux_trace_builders: AuxTraceBuilders,
59    program_info: ProgramInfo,
60    stack_outputs: StackOutputs,
61    advice: AdviceProvider,
62    trace_len_summary: TraceLenSummary,
63}
64
65impl ExecutionTrace {
66    // CONSTANTS
67    // --------------------------------------------------------------------------------------------
68
69    /// Number of rows at the end of an execution trace which are injected with random values.
70    pub const NUM_RAND_ROWS: usize = NUM_RAND_ROWS;
71
72    // CONSTRUCTOR
73    // --------------------------------------------------------------------------------------------
74    /// Builds an execution trace for the provided process.
75    pub fn new(mut process: Process, stack_outputs: StackOutputs) -> Self {
76        // use program hash to initialize random element generator; this generator will be used
77        // to inject random values at the end of the trace; using program hash here is OK because
78        // we are using random values only to stabilize constraint degrees, and not to achieve
79        // perfect zero knowledge.
80        let program_hash = process.decoder.program_hash().into();
81        let rng = RpoRandomCoin::new(program_hash);
82
83        // create a new program info instance with the underlying kernel
84        let kernel = process.kernel().clone();
85        let program_info = ProgramInfo::new(program_hash, kernel);
86        let advice = mem::take(&mut process.advice);
87        let (main_trace, aux_trace_builders, trace_len_summary) = finalize_trace(process, rng);
88        let trace_info = TraceInfo::new_multi_segment(
89            PADDED_TRACE_WIDTH,
90            AUX_TRACE_WIDTH,
91            AUX_TRACE_RAND_ELEMENTS,
92            main_trace.num_rows(),
93            vec![],
94        );
95
96        Self {
97            meta: Vec::new(),
98            trace_info,
99            aux_trace_builders,
100            main_trace,
101            program_info,
102            stack_outputs,
103            advice,
104            trace_len_summary,
105        }
106    }
107
108    // PUBLIC ACCESSORS
109    // --------------------------------------------------------------------------------------------
110
111    /// Returns the program info of this execution trace.
112    pub fn program_info(&self) -> &ProgramInfo {
113        &self.program_info
114    }
115
116    /// Returns hash of the program execution of which resulted in this execution trace.
117    pub fn program_hash(&self) -> &Word {
118        self.program_info.program_hash()
119    }
120
121    /// Returns outputs of the program execution which resulted in this execution trace.
122    pub fn stack_outputs(&self) -> &StackOutputs {
123        &self.stack_outputs
124    }
125
126    /// Returns the initial state of the top 16 stack registers.
127    pub fn init_stack_state(&self) -> StackInputs {
128        let mut result = [ZERO; MIN_STACK_DEPTH];
129        for (i, result) in result.iter_mut().enumerate() {
130            *result = self.main_trace.get_column(i + STACK_TRACE_OFFSET)[0];
131        }
132        result.into()
133    }
134
135    /// Returns the final state of the top 16 stack registers.
136    pub fn last_stack_state(&self) -> StackOutputs {
137        let last_step = self.last_step();
138        let mut result = [ZERO; MIN_STACK_DEPTH];
139        for (i, result) in result.iter_mut().enumerate() {
140            *result = self.main_trace.get_column(i + STACK_TRACE_OFFSET)[last_step];
141        }
142        result.into()
143    }
144
145    /// Returns helper registers state at the specified `clk` of the VM
146    pub fn get_user_op_helpers_at(&self, clk: u32) -> [Felt; NUM_USER_OP_HELPERS] {
147        let mut result = [ZERO; NUM_USER_OP_HELPERS];
148        for (i, result) in result.iter_mut().enumerate() {
149            *result = self.main_trace.get_column(DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET + i)
150                [clk as usize];
151        }
152        result
153    }
154
155    /// Returns the trace length.
156    pub fn get_trace_len(&self) -> usize {
157        self.main_trace.num_rows()
158    }
159
160    /// Returns a summary of the lengths of main, range and chiplet traces.
161    pub fn trace_len_summary(&self) -> &TraceLenSummary {
162        &self.trace_len_summary
163    }
164
165    /// Returns the final advice provider state.
166    pub fn advice_provider(&self) -> &AdviceProvider {
167        &self.advice
168    }
169
170    /// Returns the trace meta data.
171    pub fn meta(&self) -> &[u8] {
172        &self.meta
173    }
174
175    /// Destructures this execution trace into the process’s final stack and advice states.
176    pub fn into_outputs(self) -> (StackOutputs, AdviceProvider) {
177        (self.stack_outputs, self.advice)
178    }
179
180    // HELPER METHODS
181    // --------------------------------------------------------------------------------------------
182
183    /// Returns the index of the last row in the trace.
184    fn last_step(&self) -> usize {
185        self.length() - NUM_RAND_ROWS - 1
186    }
187
188    // TEST HELPERS
189    // --------------------------------------------------------------------------------------------
190    #[cfg(feature = "std")]
191    #[allow(dead_code)]
192    pub fn print(&self) {
193        let mut row = [ZERO; PADDED_TRACE_WIDTH];
194        for i in 0..self.length() {
195            self.main_trace.read_row_into(i, &mut row);
196            std::println!(
197                "{:?}",
198                row.iter().take(TRACE_WIDTH).map(|v| v.as_int()).collect::<Vec<_>>()
199            );
200        }
201    }
202
203    #[cfg(test)]
204    pub fn test_finalize_trace(process: Process) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) {
205        let rng = RpoRandomCoin::new(EMPTY_WORD);
206        finalize_trace(process, rng)
207    }
208
209    pub fn build_aux_trace<E>(&self, rand_elements: &[E]) -> Option<ColMatrix<E>>
210    where
211        E: FieldElement<BaseField = Felt>,
212    {
213        // add decoder's running product columns
214        let decoder_aux_columns = self
215            .aux_trace_builders
216            .decoder
217            .build_aux_columns(&self.main_trace, rand_elements);
218
219        // add stack's running product columns
220        let stack_aux_columns =
221            self.aux_trace_builders.stack.build_aux_columns(&self.main_trace, rand_elements);
222
223        // add the range checker's running product columns
224        let range_aux_columns =
225            self.aux_trace_builders.range.build_aux_columns(&self.main_trace, rand_elements);
226
227        // add the running product columns for the chiplets
228        let chiplets = self
229            .aux_trace_builders
230            .chiplets
231            .build_aux_columns(&self.main_trace, rand_elements);
232
233        // combine all auxiliary columns into a single vector
234        let mut aux_columns = decoder_aux_columns
235            .into_iter()
236            .chain(stack_aux_columns)
237            .chain(range_aux_columns)
238            .chain(chiplets)
239            .collect::<Vec<_>>();
240
241        // inject random values into the last rows of the trace
242        let mut rng = RpoRandomCoin::new(*self.program_hash());
243        for i in self.length() - NUM_RAND_ROWS..self.length() {
244            for column in aux_columns.iter_mut() {
245                column[i] = rng.draw().expect("failed to draw a random value");
246            }
247        }
248
249        Some(ColMatrix::new(aux_columns))
250    }
251}
252
253// TRACE TRAIT IMPLEMENTATION
254// ================================================================================================
255
256impl Trace for ExecutionTrace {
257    type BaseField = Felt;
258
259    fn length(&self) -> usize {
260        self.main_trace.num_rows()
261    }
262
263    fn main_segment(&self) -> &ColMatrix<Felt> {
264        &self.main_trace
265    }
266
267    fn read_main_frame(&self, row_idx: usize, frame: &mut EvaluationFrame<Felt>) {
268        let next_row_idx = (row_idx + 1) % self.length();
269        self.main_trace.read_row_into(row_idx, frame.current_mut());
270        self.main_trace.read_row_into(next_row_idx, frame.next_mut());
271    }
272
273    fn info(&self) -> &TraceInfo {
274        &self.trace_info
275    }
276}
277
278// HELPER FUNCTIONS
279// ================================================================================================
280
281/// Converts a process into a set of execution trace columns for each component of the trace.
282///
283/// The process includes:
284/// - Determining the length of the trace required to accommodate the longest trace column.
285/// - Padding the columns to make sure all columns are of the same length.
286/// - Inserting random values in the last row of all columns. This helps ensure that there are no
287///   repeating patterns in each column and each column contains a least two distinct values. This,
288///   in turn, ensures that polynomial degrees of all columns are stable.
289fn finalize_trace(
290    process: Process,
291    mut rng: RpoRandomCoin,
292) -> (MainTrace, AuxTraceBuilders, TraceLenSummary) {
293    let (system, decoder, stack, mut range, chiplets) = process.into_parts();
294
295    let clk = system.clk();
296
297    // Trace lengths of system and stack components must be equal to the number of executed cycles
298    assert_eq!(clk.as_usize(), system.trace_len(), "inconsistent system trace lengths");
299    assert_eq!(clk.as_usize(), decoder.trace_len(), "inconsistent decoder trace length");
300    assert_eq!(clk.as_usize(), stack.trace_len(), "inconsistent stack trace lengths");
301
302    // Add the range checks required by the chiplets to the range checker.
303    chiplets.append_range_checks(&mut range);
304
305    // Generate number of rows for the range trace.
306    let range_table_len = range.get_number_range_checker_rows();
307
308    // Get the trace length required to hold all execution trace steps.
309    let max_len = range_table_len.max(clk.into()).max(chiplets.trace_len());
310
311    // Pad the trace length to the next power of two and ensure that there is space for the
312    // Rows to hold random values
313    let trace_len = (max_len + NUM_RAND_ROWS).next_power_of_two();
314    assert!(
315        trace_len >= MIN_TRACE_LEN,
316        "trace length must be at least {MIN_TRACE_LEN}, but was {trace_len}",
317    );
318
319    // Get the lengths of the traces: main, range, and chiplets
320    let trace_len_summary =
321        TraceLenSummary::new(clk.into(), range_table_len, ChipletsLengths::new(&chiplets));
322
323    // Combine all trace segments into the main trace
324    let system_trace = system.into_trace(trace_len, NUM_RAND_ROWS);
325    let decoder_trace = decoder.into_trace(trace_len, NUM_RAND_ROWS);
326    let stack_trace = stack.into_trace(trace_len, NUM_RAND_ROWS);
327    let chiplets_trace = chiplets.into_trace(trace_len, NUM_RAND_ROWS);
328
329    // Combine the range trace segment using the support lookup table
330    let range_check_trace = range.into_trace_with_table(range_table_len, trace_len, NUM_RAND_ROWS);
331
332    // Padding to make the number of columns a multiple of 8 i.e., the RPO permutation rate
333    let padding = vec![vec![ZERO; trace_len]; PADDED_TRACE_WIDTH - TRACE_WIDTH];
334
335    let mut trace = system_trace
336        .into_iter()
337        .chain(decoder_trace.trace)
338        .chain(stack_trace.trace)
339        .chain(range_check_trace.trace)
340        .chain(chiplets_trace.trace)
341        .chain(padding)
342        .collect::<Vec<_>>();
343
344    // Inject random values into the last rows of the trace
345    for i in trace_len - NUM_RAND_ROWS..trace_len {
346        for column in trace.iter_mut().take(TRACE_WIDTH) {
347            column[i] = rng.draw().expect("failed to draw a random value");
348        }
349    }
350
351    let aux_trace_hints = AuxTraceBuilders {
352        decoder: decoder_trace.aux_builder,
353        stack: StackAuxTraceBuilder,
354        range: range_check_trace.aux_builder,
355        chiplets: chiplets_trace.aux_builder,
356    };
357
358    let main_trace = MainTrace::new(ColMatrix::new(trace), clk);
359
360    (main_trace, aux_trace_hints, trace_len_summary)
361}