miden_processor/trace/
mod.rs

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