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