1use std::{fmt::Display, hash::Hash, sync::Arc};
2use thousands::Separable;
3
4use slop_air::{Air, BaseAir, PairBuilder};
5use slop_algebra::{Field, PrimeField32};
6use slop_matrix::dense::RowMajorMatrix;
7use slop_uni_stark::{get_max_constraint_degree, get_symbolic_constraints, SymbolicAirBuilder};
8
9use crate::{
10 air::{MachineAir, SP1AirBuilder},
11 lookup::{Interaction, InteractionBuilder, InteractionKind},
12};
13
14use super::PROOF_MAX_NUM_PVS;
15
16pub const MAX_CONSTRAINT_DEGREE: usize = 3;
18
19pub struct Chip<F: Field, A> {
21 pub air: Arc<A>,
23 pub sends: Arc<Vec<Interaction<F>>>,
25 pub receives: Arc<Vec<Interaction<F>>>,
27 pub num_constraints: usize,
29}
30
31impl<F: Field, A: MachineAir<F>> std::fmt::Debug for Chip<F, A> {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 f.debug_struct("Chip")
34 .field("air", &self.air.name())
35 .field("sends", &self.sends.len())
36 .field("receives", &self.receives.len())
37 .field("num_constraints", &self.num_constraints)
38 .finish()
39 }
40}
41
42impl<F: Field, A> Clone for Chip<F, A> {
43 fn clone(&self) -> Self {
44 Self {
45 air: self.air.clone(),
46 sends: self.sends.clone(),
47 receives: self.receives.clone(),
48 num_constraints: self.num_constraints,
49 }
50 }
51}
52
53impl<F: Field, A> Chip<F, A> {
54 #[must_use]
56 pub fn sends(&self) -> &[Interaction<F>] {
57 &self.sends
58 }
59
60 #[must_use]
62 pub fn receives(&self) -> &[Interaction<F>] {
63 &self.receives
64 }
65
66 #[must_use]
68 pub fn into_inner(self) -> Option<A> {
69 Arc::into_inner(self.air)
70 }
71}
72
73impl<F: PrimeField32, A: MachineAir<F>> Chip<F, A> {
74 pub fn included(&self, shard: &A::Record) -> bool {
76 self.air.included(shard)
77 }
78}
79
80impl<F, A> Chip<F, A>
81where
82 F: Field,
83 A: BaseAir<F>,
84{
85 pub fn new(air: A) -> Self
87 where
88 A: MachineAir<F> + Air<InteractionBuilder<F>> + Air<SymbolicAirBuilder<F>>,
89 {
90 let mut builder = InteractionBuilder::new(air.preprocessed_width(), air.width());
91 air.eval(&mut builder);
92 let (sends, receives) = builder.interactions();
93
94 let nb_byte_sends = sends.iter().filter(|s| s.kind == InteractionKind::Byte).count();
95 let nb_byte_receives = receives.iter().filter(|r| r.kind == InteractionKind::Byte).count();
96 tracing::trace!(
97 "chip {} has {} byte interactions",
98 air.name(),
99 nb_byte_sends + nb_byte_receives
100 );
101
102 let mut max_constraint_degree =
103 get_max_constraint_degree(&air, air.preprocessed_width(), PROOF_MAX_NUM_PVS);
104
105 if !sends.is_empty() || !receives.is_empty() {
106 max_constraint_degree = std::cmp::max(max_constraint_degree, MAX_CONSTRAINT_DEGREE);
107 }
108 assert!(max_constraint_degree > 0);
109
110 assert!(max_constraint_degree <= MAX_CONSTRAINT_DEGREE);
111 let num_constraints =
114 get_symbolic_constraints(&air, air.preprocessed_width(), PROOF_MAX_NUM_PVS).len();
115
116 let sends = Arc::new(sends);
117 let receives = Arc::new(receives);
118
119 let air = Arc::new(air);
120 Self { air, sends, receives, num_constraints }
121 }
122
123 #[inline]
125 #[must_use]
126 pub fn num_interactions(&self) -> usize {
127 self.sends.len() + self.receives.len()
128 }
129
130 #[inline]
132 #[must_use]
133 pub fn num_sent_byte_lookups(&self) -> usize {
134 self.sends.iter().filter(|i| i.kind == InteractionKind::Byte).count()
135 }
136
137 #[inline]
139 #[must_use]
140 pub fn num_sends_by_kind(&self, kind: InteractionKind) -> usize {
141 self.sends.iter().filter(|i| i.kind == kind).count()
142 }
143
144 #[inline]
146 #[must_use]
147 pub fn num_receives_by_kind(&self, kind: InteractionKind) -> usize {
148 self.receives.iter().filter(|i| i.kind == kind).count()
149 }
150
151 #[inline]
153 #[must_use]
154 pub fn cost(&self) -> u64
155 where
156 A: MachineAir<F>,
157 {
158 let preprocessed_cols = self.preprocessed_width();
159 let main_cols = self.width();
160 (preprocessed_cols + main_cols) as u64
161 }
162}
163
164impl<F, A> BaseAir<F> for Chip<F, A>
165where
166 F: Field,
167 A: BaseAir<F> + Send,
168{
169 fn width(&self) -> usize {
170 self.air.width()
171 }
172
173 fn preprocessed_trace(&self) -> Option<RowMajorMatrix<F>> {
174 panic!("Chip should not use the `BaseAir` method, but the `MachineAir` method.")
175 }
176}
177
178impl<F, A> MachineAir<F> for Chip<F, A>
179where
180 F: Field,
181 A: MachineAir<F>,
182{
183 type Record = A::Record;
184
185 type Program = A::Program;
186
187 fn name(&self) -> &'static str {
188 self.air.name()
189 }
190
191 fn preprocessed_width(&self) -> usize {
192 <A as MachineAir<F>>::preprocessed_width(&self.air)
193 }
194
195 fn preprocessed_num_rows(&self, program: &Self::Program) -> Option<usize> {
196 <A as MachineAir<F>>::preprocessed_num_rows(&self.air, program)
197 }
198
199 fn preprocessed_num_rows_with_instrs_len(
200 &self,
201 program: &Self::Program,
202 instrs_len: usize,
203 ) -> Option<usize> {
204 <A as MachineAir<F>>::preprocessed_num_rows_with_instrs_len(&self.air, program, instrs_len)
205 }
206
207 fn generate_preprocessed_trace(&self, program: &A::Program) -> Option<RowMajorMatrix<F>> {
208 <A as MachineAir<F>>::generate_preprocessed_trace(&self.air, program)
209 }
210
211 fn num_rows(&self, input: &A::Record) -> Option<usize> {
212 <A as MachineAir<F>>::num_rows(&self.air, input)
213 }
214
215 fn generate_trace(&self, input: &A::Record, output: &mut A::Record) -> RowMajorMatrix<F> {
216 self.air.generate_trace(input, output)
217 }
218
219 fn generate_trace_into(
220 &self,
221 input: &A::Record,
222 output: &mut A::Record,
223 buffer: &mut [std::mem::MaybeUninit<F>],
224 ) {
225 self.air.generate_trace_into(input, output, buffer);
226 }
227
228 fn generate_dependencies(&self, input: &A::Record, output: &mut A::Record) {
229 self.air.generate_dependencies(input, output);
230 }
231
232 fn included(&self, shard: &Self::Record) -> bool {
233 self.air.included(shard)
234 }
235}
236
237impl<F, A, AB> Air<AB> for Chip<F, A>
239where
240 F: Field,
241 A: Air<AB> + MachineAir<F>,
242 AB: SP1AirBuilder<F = F> + PairBuilder,
243{
244 fn eval(&self, builder: &mut AB) {
245 self.air.eval(builder);
247 }
248}
249
250impl<F, A> PartialEq for Chip<F, A>
251where
252 F: Field,
253 A: MachineAir<F>,
254{
255 #[inline]
256 fn eq(&self, other: &Self) -> bool {
257 self.air.name() == other.air.name()
258 }
259}
260
261impl<F: Field, A: MachineAir<F>> Eq for Chip<F, A> where F: Field + Eq {}
262
263impl<F, A> Hash for Chip<F, A>
264where
265 F: Field,
266 A: MachineAir<F>,
267{
268 #[inline]
269 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
270 self.air.name().hash(state);
271 }
272}
273
274impl<F: Field, A: MachineAir<F>> PartialOrd for Chip<F, A>
275where
276 F: Field,
277 A: MachineAir<F>,
278{
279 #[inline]
280 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
281 Some(self.cmp(other))
282 }
283}
284
285impl<F: Field, A: MachineAir<F>> Ord for Chip<F, A>
286where
287 F: Field,
288 A: MachineAir<F>,
289{
290 #[inline]
291 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
292 self.name().cmp(other.name())
293 }
294}
295
296#[derive(Debug, Clone)]
298pub struct ChipStatistics<F> {
299 name: String,
301 height: usize,
303 preprocessed_cols: usize,
305 main_cols: usize,
307 _marker: std::marker::PhantomData<F>,
308}
309
310impl<F: Field> ChipStatistics<F> {
311 #[must_use]
313 pub fn new<A: MachineAir<F>>(chip: &Chip<F, A>, height: usize) -> Self {
314 let name = chip.name().to_string();
315 let preprocessed_cols = chip.preprocessed_width();
316 let main_cols = chip.width();
317 Self { name, height, preprocessed_cols, main_cols, _marker: std::marker::PhantomData }
318 }
319
320 #[must_use]
322 #[inline]
323 pub const fn total_width(&self) -> usize {
324 self.preprocessed_cols + self.main_cols
325 }
326
327 #[must_use]
329 #[inline]
330 pub const fn total_number_of_cells(&self) -> usize {
331 self.total_width() * self.height
332 }
333
334 #[must_use]
336 #[inline]
337 pub const fn total_memory_size(&self) -> usize {
338 self.total_number_of_cells() * std::mem::size_of::<F>()
339 }
340}
341
342impl<F: Field> Display for ChipStatistics<F> {
343 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
344 write!(
345 f,
346 "{:<15} | Prep Cols = {:<5} | Main Cols = {:<5} | Rows = {:<5} | Cells = {:<10}",
347 self.name,
348 self.preprocessed_cols.separate_with_underscores(),
349 self.main_cols.separate_with_underscores(),
350 self.height.separate_with_underscores(),
351 self.total_number_of_cells().separate_with_underscores()
352 )
353 }
354}