openvm_recursion_circuit/primitives/exp_bits_len/
trace.rs1use core::borrow::BorrowMut;
2use std::sync::Mutex;
3
4use openvm_stark_backend::poly_common::Squarable;
5use openvm_stark_sdk::config::baby_bear_poseidon2::F;
6use p3_field::{PrimeCharacteristicRing, PrimeField32};
7use p3_matrix::dense::RowMajorMatrix;
8use p3_maybe_rayon::prelude::*;
9
10use super::air::ExpBitsLenCols;
11
12pub(crate) const NUM_BITS_MAX_PLUS_ONE: usize = 32;
13pub(crate) const LOW_BITS_COUNT: usize = 27;
14
15#[repr(C)]
16#[derive(Clone, Debug)]
17pub struct ExpBitsLenRecord {
18 pub num_bits: u8,
19 pub base: F,
20 pub bit_src: F,
21 pub row_offset: u32,
22 pub shift_bits: u8,
23 pub shift_mult: u32,
24}
25
26impl ExpBitsLenRecord {
27 pub(crate) fn new(
28 base: F,
29 bit_src: F,
30 num_bits: usize,
31 row_offset: u32,
32 shift_bits: usize,
33 shift_mult: u32,
34 ) -> Self {
35 debug_assert!(num_bits < NUM_BITS_MAX_PLUS_ONE);
36 Self {
37 base,
38 bit_src,
39 num_bits: u8::try_from(num_bits)
40 .expect("num_bits fits in NUM_BITS_MAX_PLUS_ONE (< 256)"),
41 row_offset,
42 shift_bits: u8::try_from(shift_bits)
43 .expect("shift_bits fits in NUM_BITS_MAX_PLUS_ONE (< 256)"),
44 shift_mult,
45 }
46 }
47
48 pub(crate) fn num_rows(&self) -> usize {
49 NUM_BITS_MAX_PLUS_ONE
50 }
51
52 pub(crate) fn end_row(&self) -> usize {
53 self.row_offset as usize + self.num_rows()
54 }
55}
56
57#[derive(Debug, Default)]
58pub struct ExpBitsLenCpuTraceGenerator {
59 pub requests: Mutex<Vec<ExpBitsLenRecord>>,
60}
61
62impl ExpBitsLenCpuTraceGenerator {
63 pub fn add_request(&self, base: F, bit_src: F, num_bits: usize) {
64 self.add_requests([(base, bit_src, num_bits)]);
65 }
66
67 pub fn add_requests<I>(&self, batch: I)
68 where
69 I: IntoIterator<Item = (F, F, usize)>,
70 {
71 self.add_requests_with_shift(batch.into_iter().map(|(x, y, z)| (x, y, z, 0, 0)));
72 }
73
74 pub fn add_requests_with_shift<I>(&self, batch: I)
75 where
76 I: IntoIterator<Item = (F, F, usize, usize, u32)>,
77 {
78 let mut records = self.requests.lock().unwrap();
79 let mut next_row_offset = records.last().map(|record| record.end_row()).unwrap_or(0);
80 for (base, bit_src, num_bits, shift_bits, shift_mult) in batch {
81 let row_offset = u32::try_from(next_row_offset).expect("row offset should fit in u32");
82 let record =
83 ExpBitsLenRecord::new(base, bit_src, num_bits, row_offset, shift_bits, shift_mult);
84 next_row_offset += record.num_rows();
85 records.push(record);
86 }
87 }
88
89 #[tracing::instrument(name = "generate_trace", level = "trace", skip_all)]
90 pub fn generate_trace_row_major(
91 self,
92 required_height: Option<usize>,
93 ) -> Option<RowMajorMatrix<F>> {
94 let records = self.requests.into_inner().unwrap();
95 let num_valid_rows = records.last().map(|record| record.end_row()).unwrap_or(0);
96 let width = ExpBitsLenCols::<F>::width();
97
98 let padded_rows = if let Some(height) = required_height {
99 if height < num_valid_rows {
100 return None;
101 }
102 height
103 } else {
104 num_valid_rows.next_power_of_two()
105 };
106 let mut trace = vec![F::ZERO; padded_rows * width];
107
108 let (data_slice, padding_slice) = trace.split_at_mut(num_valid_rows * width);
109 let mut trace_slices: Vec<&mut [F]> = Vec::with_capacity(records.len());
110 let mut remaining = data_slice;
111
112 for record in &records {
113 let chunk_size = record.num_rows() * width;
114 let (chunk, rest) = remaining.split_at_mut(chunk_size);
115 trace_slices.push(chunk);
116 remaining = rest;
117 }
118
119 tracing::info_span!("fill_valid_rows").in_scope(|| {
120 trace_slices
121 .par_iter_mut()
122 .zip(records.par_iter())
123 .for_each(|(trace_slice, request)| {
124 fill_valid_rows(
125 request.base,
126 request.bit_src.as_canonical_u32(),
127 request.num_bits,
128 request.shift_bits,
129 request.shift_mult,
130 trace_slice,
131 width,
132 );
133 });
134 });
135
136 tracing::info_span!("fill_padding_rows").in_scope(|| {
137 padding_slice.par_chunks_exact_mut(width).for_each(|row| {
138 let cols: &mut ExpBitsLenCols<F> = row.borrow_mut();
139 cols.result = F::ONE;
140 cols.result_multiplier = F::ONE;
141 });
142 });
143
144 Some(RowMajorMatrix::new(trace, width))
145 }
146}
147
148pub(crate) fn fill_valid_rows(
149 base: F,
150 bit_src: u32,
151 n: u8,
152 shift_bits: u8,
153 shift_mult: u32,
154 trace_slice: &mut [F],
155 width: usize,
156) {
157 fill_valid_rows_with_decomp_src(base, bit_src, n, shift_bits, shift_mult, trace_slice, width);
158}
159
160pub(crate) fn fill_valid_rows_with_decomp_src(
161 base: F,
162 decomp_src: u32,
163 n: u8,
164 shift_bits: u8,
165 shift_mult: u32,
166 trace_slice: &mut [F],
167 width: usize,
168) {
169 debug_assert!(n < NUM_BITS_MAX_PLUS_ONE as u8);
170 debug_assert_eq!(trace_slice.len(), NUM_BITS_MAX_PLUS_ONE * width);
171 debug_assert!(decomp_src < (1u32 << (NUM_BITS_MAX_PLUS_ONE - 1)));
172
173 let bases: Vec<_> = base.exp_powers_of_2().take(NUM_BITS_MAX_PLUS_ONE).collect();
174 let mut results = [F::ONE; NUM_BITS_MAX_PLUS_ONE];
175 let mut acc = F::ONE;
176 for step in (0..NUM_BITS_MAX_PLUS_ONE - 1).rev() {
177 if step < n as usize && ((decomp_src >> step) & 1) == 1 {
178 acc *= bases[step];
179 }
180 results[step] = acc;
181 }
182
183 let mut low_bits_are_zero = true;
184 let mut high_bits_all_one = false;
185 for step in 0..NUM_BITS_MAX_PLUS_ONE {
186 if step == LOW_BITS_COUNT {
187 high_bits_all_one = true;
188 }
189
190 let shifted = decomp_src >> step;
191 let num_bits = (n as usize).saturating_sub(step);
192 let low_bits_left = LOW_BITS_COUNT.saturating_sub(step);
193
194 let row_offset = step * width;
195 let row = &mut trace_slice[row_offset..row_offset + width];
196 let cols: &mut ExpBitsLenCols<F> = row.borrow_mut();
197 cols.is_valid = F::ONE;
198 cols.is_first = F::from_bool(step == 0);
199 cols.bit_idx = F::from_usize(step);
200 cols.base = bases[step];
201 cols.bit_src = F::from_u32(shifted);
202 cols.num_bits = F::from_usize(num_bits);
203 cols.apply_bit = F::from_bool(num_bits != 0);
204 cols.low_bits_left = F::from_usize(low_bits_left);
205 cols.in_low_region = F::from_bool(low_bits_left != 0);
206 cols.result = results[step];
207 cols.result_multiplier = if num_bits != 0 && (shifted & 1) == 1 {
208 bases[step]
209 } else {
210 F::ONE
211 };
212 cols.bit_src_mod_2 = F::from_bool((shifted & 1) == 1);
213 cols.low_bits_are_zero = F::from_bool(low_bits_are_zero);
214 cols.high_bits_all_one = F::from_bool(high_bits_all_one);
215 cols.bit_src_original = F::from_u32(decomp_src);
216 cols.shift_mult = if shift_bits as usize == step {
217 F::from_u32(shift_mult)
218 } else {
219 F::ZERO
220 };
221
222 if step < LOW_BITS_COUNT {
223 low_bits_are_zero &= (shifted & 1) == 0;
224 } else if step + 1 < NUM_BITS_MAX_PLUS_ONE {
225 high_bits_all_one &= (shifted & 1) == 1;
226 }
227 }
228}