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