miden_processor/trace/
mod.rs

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