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