winter_prover/trace/
trace_table.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6use alloc::vec::Vec;
7
8use air::{EvaluationFrame, TraceInfo};
9use math::StarkField;
10use utils::uninit_vector;
11#[cfg(feature = "concurrent")]
12use utils::{iterators::*, rayon};
13
14use super::{ColMatrix, Trace};
15
16// CONSTANTS
17// ================================================================================================
18
19const MIN_FRAGMENT_LENGTH: usize = 2;
20
21// TRACE TABLE
22// ================================================================================================
23/// A concrete implementation of the [Trace] trait.
24///
25/// This implementation supports concurrent trace generation and should be sufficient for most use
26/// cases. There are two ways to create a trace table trace.
27///
28/// First, you can use the [TraceTable::init()] function which takes a set of vectors as a
29/// parameter, where each vector contains values for a given column of the trace. This approach
30/// allows you to build an execution trace as you see fit, as long as it meets a basic set of
31/// requirements. These requirements are:
32///
33/// 1. Lengths of all columns in the execution trace must be the same.
34/// 2. The length of the columns must be some power of two.
35///
36/// The other approach is to use [TraceTable::new()] function, which takes trace width and
37/// length as parameters. This function will allocate memory for the trace, but will not fill it
38/// with data. To fill the execution trace, you can use the [fill()](TraceTable::fill) method,
39/// which takes two closures as parameters:
40///
41/// 1. The first closure is responsible for initializing the first state of the computation (the
42///    first row of the execution trace).
43/// 2. The second closure receives the previous state of the execution trace as input, and must
44///    update it to the next state of the computation.
45///
46/// You can also use [TraceTable::with_meta()] function to create a blank execution trace.
47/// This function work just like [TraceTable::new()] function, but also takes a metadata
48/// parameter which can be an arbitrary sequence of bytes up to 64KB in size.
49///
50/// # Concurrent trace generation
51/// For computations which consist of many small independent computations, we can generate the
52/// execution trace of the entire computation by building fragments of the trace in parallel,
53/// and then joining these fragments together.
54///
55/// For this purpose, `TraceTable` struct exposes [fragments()](TraceTable::fragments)
56/// method, which takes fragment length as a parameter, breaks the execution trace into equally
57/// sized fragments, and returns an iterator over these fragments. You can then use fragment's
58/// [fill()](TraceTableFragment::fill) method to fill all fragments with data in parallel.
59/// The semantics of the fragment's [TraceTableFragment::fill()] method are identical to the
60/// semantics of the [TraceTable::fill()] method.
61#[derive(Debug, Clone)]
62pub struct TraceTable<B: StarkField> {
63    info: TraceInfo,
64    trace: ColMatrix<B>,
65}
66
67impl<B: StarkField> TraceTable<B> {
68    // CONSTRUCTORS
69    // --------------------------------------------------------------------------------------------
70
71    /// Creates a new execution trace of the specified width and length.
72    ///
73    /// This allocates all the required memory for the trace, but does not initialize it. It is
74    /// expected that the trace will be filled using one of the data mutator methods.
75    ///
76    /// # Panics
77    /// Panics if:
78    /// * `width` is zero or greater than 255.
79    /// * `length` is smaller than 8, greater than biggest multiplicative subgroup in the field `B`,
80    ///   or is not a power of two.
81    pub fn new(width: usize, length: usize) -> Self {
82        Self::with_meta(width, length, vec![])
83    }
84
85    /// Creates a new execution trace of the specified width and length, and with the specified
86    /// metadata.
87    ///
88    /// This allocates all the required memory for the trace, but does not initialize it. It is
89    /// expected that the trace will be filled using one of the data mutator methods.
90    ///
91    /// # Panics
92    /// Panics if:
93    /// * `width` is zero or greater than 255.
94    /// * `length` is smaller than 8, greater than the biggest multiplicative subgroup in the field
95    ///   `B`, or is not a power of two.
96    /// * Length of `meta` is greater than 65535;
97    pub fn with_meta(width: usize, length: usize, meta: Vec<u8>) -> Self {
98        let info = TraceInfo::with_meta(width, length, meta);
99        assert!(
100            length.ilog2() <= B::TWO_ADICITY,
101            "execution trace length cannot exceed 2^{} steps, but was 2^{}",
102            B::TWO_ADICITY,
103            length.ilog2()
104        );
105
106        let columns = unsafe { (0..width).map(|_| uninit_vector(length)).collect() };
107
108        Self { info, trace: ColMatrix::new(columns) }
109    }
110
111    /// Creates a new execution trace from a list of provided trace columns.
112    ///
113    /// # Panics
114    /// Panics if:
115    /// * The `columns` vector is empty or has over 255 columns.
116    /// * Number of elements in any of the columns is smaller than 8, greater than the biggest
117    ///   multiplicative subgroup in the field `B`, or is not a power of two.
118    /// * Number of elements is not identical for all columns.
119    pub fn init(columns: Vec<Vec<B>>) -> Self {
120        assert!(!columns.is_empty(), "execution trace must consist of at least one column");
121
122        let trace_length = columns[0].len();
123        let info = TraceInfo::with_meta(columns.len(), trace_length, Vec::new());
124
125        assert!(
126            trace_length.ilog2() <= B::TWO_ADICITY,
127            "execution trace length cannot exceed 2^{} steps, but was 2^{}",
128            B::TWO_ADICITY,
129            trace_length.ilog2()
130        );
131
132        for column in columns.iter().skip(1) {
133            assert_eq!(column.len(), trace_length, "all columns traces must have the same length");
134        }
135
136        Self { info, trace: ColMatrix::new(columns) }
137    }
138
139    // DATA MUTATORS
140    // --------------------------------------------------------------------------------------------
141
142    /// Updates a value in a single cell of the execution trace.
143    ///
144    /// Specifically, the value in the specified `column` and the specified `step` is set to the
145    /// provide `value`.
146    ///
147    /// # Panics
148    /// Panics if either `column` or `step` are out of bounds for this execution trace.
149    pub fn set(&mut self, column: usize, step: usize, value: B) {
150        self.trace.set(column, step, value)
151    }
152
153    /// Fill all rows in the execution trace.
154    ///
155    /// The rows are filled by executing the provided closures as follows:
156    /// - `init` closure is used to initialize the first row of the trace; it receives a mutable
157    ///   reference to the first state initialized to all zeros. The contents of the state are
158    ///   copied into the first row of the trace after the closure returns.
159    /// - `update` closure is used to populate all subsequent rows of the trace; it receives two
160    ///   parameters:
161    ///   - index of the last updated row (starting with 0).
162    ///   - a mutable reference to the last updated state; the contents of the state are copied into
163    ///     the next row of the trace after the closure returns.
164    pub fn fill<I, U>(&mut self, init: I, mut update: U)
165    where
166        I: FnOnce(&mut [B]),
167        U: FnMut(usize, &mut [B]),
168    {
169        let mut state = vec![B::ZERO; self.info.main_trace_width()];
170        init(&mut state);
171        self.update_row(0, &state);
172
173        for i in 0..self.info.length() - 1 {
174            update(i, &mut state);
175            self.update_row(i + 1, &state);
176        }
177    }
178
179    /// Updates a single row in the execution trace with provided data.
180    pub fn update_row(&mut self, step: usize, state: &[B]) {
181        self.trace.update_row(step, state);
182    }
183
184    // FRAGMENTS
185    // --------------------------------------------------------------------------------------------
186
187    /// Breaks the execution trace into mutable fragments.
188    ///
189    /// The number of rows in each fragment will be equal to `fragment_length` parameter. The
190    /// returned fragments can be used to update data in the trace from multiple threads.
191    ///
192    /// # Panics
193    /// Panics if `fragment_length` is smaller than 2, greater than the length of the trace,
194    /// or is not a power of two.
195    #[cfg(not(feature = "concurrent"))]
196    pub fn fragments(
197        &mut self,
198        fragment_length: usize,
199    ) -> alloc::vec::IntoIter<TraceTableFragment<B>> {
200        self.build_fragments(fragment_length).into_iter()
201    }
202
203    /// Breaks the execution trace into mutable fragments.
204    ///
205    /// The number of rows in each fragment will be equal to `fragment_length` parameter. The
206    /// returned fragments can be used to update data in the trace from multiple threads.
207    ///
208    /// # Panics
209    /// Panics if `fragment_length` is smaller than 2, greater than the length of the trace,
210    /// or is not a power of two.
211    #[cfg(feature = "concurrent")]
212    pub fn fragments(
213        &mut self,
214        fragment_length: usize,
215    ) -> rayon::vec::IntoIter<TraceTableFragment<'_, B>> {
216        self.build_fragments(fragment_length).into_par_iter()
217    }
218
219    /// Returns a vector of trace fragments each covering the number of steps specified by the
220    /// `fragment_length` parameter.
221    fn build_fragments(&mut self, fragment_length: usize) -> Vec<TraceTableFragment<'_, B>> {
222        assert!(
223            fragment_length >= MIN_FRAGMENT_LENGTH,
224            "fragment length must be at least {MIN_FRAGMENT_LENGTH}, but was {fragment_length}"
225        );
226        assert!(
227            fragment_length <= self.info.length(),
228            "length of a fragment cannot exceed {}, but was {}",
229            self.info.length(),
230            fragment_length
231        );
232        assert!(fragment_length.is_power_of_two(), "fragment length must be a power of 2");
233        let num_fragments = self.info.length() / fragment_length;
234
235        let mut fragment_data = (0..num_fragments).map(|_| Vec::new()).collect::<Vec<_>>();
236        self.trace.columns_mut().for_each(|column| {
237            for (i, fragment) in column.chunks_mut(fragment_length).enumerate() {
238                fragment_data[i].push(fragment);
239            }
240        });
241
242        fragment_data
243            .into_iter()
244            .enumerate()
245            .map(|(i, data)| TraceTableFragment {
246                index: i,
247                offset: i * fragment_length,
248                data,
249            })
250            .collect()
251    }
252
253    // PUBLIC ACCESSORS
254    // --------------------------------------------------------------------------------------------
255
256    /// Returns the number of columns in this execution trace.
257    pub fn width(&self) -> usize {
258        self.info.main_trace_width()
259    }
260
261    /// Returns the entire trace column at the specified index.
262    pub fn get_column(&self, col_idx: usize) -> &[B] {
263        self.trace.get_column(col_idx)
264    }
265
266    /// Returns value of the cell in the specified column at the specified row of this trace.
267    pub fn get(&self, column: usize, step: usize) -> B {
268        self.trace.get(column, step)
269    }
270
271    /// Reads a single row from this execution trace into the provided target.
272    pub fn read_row_into(&self, step: usize, target: &mut [B]) {
273        self.trace.read_row_into(step, target);
274    }
275}
276
277// TRACE TRAIT IMPLEMENTATION
278// ================================================================================================
279
280impl<B: StarkField> Trace for TraceTable<B> {
281    type BaseField = B;
282
283    fn info(&self) -> &TraceInfo {
284        &self.info
285    }
286
287    fn read_main_frame(&self, row_idx: usize, frame: &mut EvaluationFrame<Self::BaseField>) {
288        let next_row_idx = (row_idx + 1) % self.info.length();
289        self.trace.read_row_into(row_idx, frame.current_mut());
290        self.trace.read_row_into(next_row_idx, frame.next_mut());
291    }
292
293    fn main_segment(&self) -> &ColMatrix<B> {
294        &self.trace
295    }
296}
297
298// TRACE FRAGMENTS
299// ================================================================================================
300/// A set of consecutive rows of an execution trace.
301///
302/// An execution trace fragment is a "view" into the specific execution trace. Updating data in
303/// the fragment, directly updates the data in the underlying execution trace.
304///
305/// A fragment cannot be instantiated directly but is created by executing
306/// [TraceTable::fragments()] method.
307///
308/// A fragment always contains contiguous rows, and the number of rows is guaranteed to be a power
309/// of two.
310pub struct TraceTableFragment<'a, B: StarkField> {
311    index: usize,
312    offset: usize,
313    data: Vec<&'a mut [B]>,
314}
315
316impl<B: StarkField> TraceTableFragment<'_, B> {
317    // PUBLIC ACCESSORS
318    // --------------------------------------------------------------------------------------------
319
320    /// Returns the index of this fragment.
321    pub fn index(&self) -> usize {
322        self.index
323    }
324
325    /// Returns the step at which the fragment starts in the context of the original execution
326    /// trace.
327    pub fn offset(&self) -> usize {
328        self.offset
329    }
330
331    /// Returns the number of rows in this execution trace fragment.
332    pub fn length(&self) -> usize {
333        self.data[0].len()
334    }
335
336    /// Returns the width of the fragment (same as the width of the underlying execution trace).
337    pub fn width(&self) -> usize {
338        self.data.len()
339    }
340
341    // DATA MUTATORS
342    // --------------------------------------------------------------------------------------------
343
344    /// Fills all rows in the fragment.
345    ///
346    /// The rows are filled by executing the provided closures as follows:
347    /// - `init` closure is used to initialize the first row of the fragment; it receives a mutable
348    ///   reference to the first state initialized to all zeros. Contents of the state are copied
349    ///   into the first row of the fragment after the closure returns.
350    /// - `update` closure is used to populate all subsequent rows of the fragment; it receives two
351    ///   parameters:
352    ///   - index of the last updated row (starting with 0).
353    ///   - a mutable reference to the last updated state; the contents of the state are copied into
354    ///     the next row of the fragment after the closure returns.
355    pub fn fill<I, T>(&mut self, init_state: I, mut update_state: T)
356    where
357        I: FnOnce(&mut [B]),
358        T: FnMut(usize, &mut [B]),
359    {
360        let mut state = vec![B::ZERO; self.width()];
361        init_state(&mut state);
362        self.update_row(0, &state);
363
364        for i in 0..self.length() - 1 {
365            update_state(i, &mut state);
366            self.update_row(i + 1, &state);
367        }
368    }
369
370    /// Updates a single row in the fragment with provided data.
371    pub fn update_row(&mut self, row_idx: usize, row_data: &[B]) {
372        for (column, &value) in self.data.iter_mut().zip(row_data) {
373            column[row_idx] = value;
374        }
375    }
376}