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}