Skip to main content

sp1_core_machine/adapter/
bump.rs

1use std::{
2    borrow::{Borrow, BorrowMut},
3    mem::{size_of, MaybeUninit},
4};
5
6use crate::{air::SP1CoreAirBuilder, utils::next_multiple_of_32};
7use hashbrown::HashMap;
8use itertools::Itertools;
9use slop_air::{Air, AirBuilder, BaseAir};
10use slop_algebra::{AbstractField, Field, PrimeField32};
11use slop_matrix::Matrix;
12use sp1_core_executor::{
13    events::{ByteLookupEvent, ByteRecord},
14    ByteOpcode, ExecutionRecord, Program, PC_INC,
15};
16use sp1_derive::AlignedBorrow;
17use sp1_hypercube::air::MachineAir;
18use struct_reflection::{StructReflection, StructReflectionHelper};
19pub(crate) const NUM_STATE_BUMP_COLS: usize = size_of::<StateBumpCols<u8>>();
20
21#[derive(AlignedBorrow, Clone, Copy, StructReflection)]
22#[repr(C)]
23pub struct StateBumpCols<T: Copy> {
24    pub next_clk_32_48: T,
25    pub next_clk_24_32: T,
26    pub next_clk_16_24: T,
27    pub next_clk_0_16: T,
28    pub clk_high: T,
29    pub clk_low: T,
30    pub next_pc: [T; 3],
31    pub pc: [T; 3],
32    pub is_clk: T,
33    pub is_real: T,
34}
35
36pub struct StateBumpChip {}
37
38impl StateBumpChip {
39    pub const fn new() -> Self {
40        Self {}
41    }
42}
43
44impl<F> BaseAir<F> for StateBumpChip {
45    fn width(&self) -> usize {
46        NUM_STATE_BUMP_COLS
47    }
48}
49
50impl<F: PrimeField32> MachineAir<F> for StateBumpChip {
51    type Record = ExecutionRecord;
52
53    type Program = Program;
54
55    fn name(&self) -> &'static str {
56        "StateBump"
57    }
58
59    fn generate_dependencies(&self, input: &Self::Record, output: &mut Self::Record) {
60        let chunk_size = 1;
61        let event_iter = input.bump_state_events.chunks(chunk_size);
62
63        let blu_batches = event_iter
64            .map(|events| {
65                let mut blu: HashMap<ByteLookupEvent, usize> = HashMap::new();
66                events.iter().for_each(|(clk, increment, _, pc)| {
67                    let next_clk = clk + increment;
68                    let next_clk_0_16 = (next_clk & 0xFFFF) as u16;
69                    let next_clk_16_24 = ((next_clk >> 16) & 0xFF) as u8;
70                    let next_clk_24_32 = ((next_clk >> 24) & 0xFF) as u8;
71                    let next_clk_32_48 = (next_clk >> 32) as u16;
72                    let pc_0 = (pc & 0xFFFF) as u16;
73                    let pc_1 = ((pc >> 16) & 0xFFFF) as u16;
74                    let pc_2 = ((pc >> 32) & 0xFFFF) as u16;
75
76                    blu.add_bit_range_check((next_clk_0_16 - 1) / 8, 13);
77                    blu.add_bit_range_check(next_clk_32_48, 16);
78                    blu.add_u8_range_checks(&[next_clk_16_24, next_clk_24_32]);
79                    blu.add_u16_range_checks(&[pc_0, pc_1, pc_2]);
80                });
81                blu
82            })
83            .collect::<Vec<_>>();
84
85        output.add_byte_lookup_events_from_maps(blu_batches.iter().collect_vec());
86    }
87
88    fn num_rows(&self, input: &Self::Record) -> Option<usize> {
89        let nb_rows = input.bump_state_events.len();
90        let size_log2 = input.fixed_log2_rows::<F, _>(self);
91        Some(next_multiple_of_32(nb_rows, size_log2))
92    }
93
94    fn generate_trace_into(
95        &self,
96        input: &Self::Record,
97        _output: &mut Self::Record,
98        buffer: &mut [MaybeUninit<F>],
99    ) {
100        let chunk_size = 1;
101        let padded_nb_rows = <StateBumpChip as MachineAir<F>>::num_rows(self, input).unwrap();
102
103        let num_event_rows = input.bump_state_events.len();
104
105        unsafe {
106            let padding_start = num_event_rows * NUM_STATE_BUMP_COLS;
107            let padding_size = (padded_nb_rows - num_event_rows) * NUM_STATE_BUMP_COLS;
108            if padding_size > 0 {
109                core::ptr::write_bytes(buffer[padding_start..].as_mut_ptr(), 0, padding_size);
110            }
111        }
112
113        let buffer_ptr = buffer.as_mut_ptr() as *mut F;
114        let values = unsafe {
115            core::slice::from_raw_parts_mut(buffer_ptr, num_event_rows * NUM_STATE_BUMP_COLS)
116        };
117
118        values.chunks_mut(chunk_size * NUM_STATE_BUMP_COLS).enumerate().for_each(|(i, rows)| {
119            rows.chunks_mut(NUM_STATE_BUMP_COLS).enumerate().for_each(|(j, row)| {
120                let idx = i * chunk_size + j;
121                let cols: &mut StateBumpCols<F> = row.borrow_mut();
122
123                if idx < input.bump_state_events.len() {
124                    let (clk, increment, bump2, pc) = input.bump_state_events[idx];
125
126                    let clk_low = ((clk & 0xFFFFFF) + increment) as u32;
127                    let clk_high = (clk >> 24) as u32;
128                    let next_clk = clk + increment;
129                    let next_clk_0_16 = (next_clk & 0xFFFF) as u16;
130                    let next_clk_16_24 = ((next_clk >> 16) & 0xFF) as u8;
131                    let next_clk_24_32 = ((next_clk >> 24) & 0xFF) as u8;
132                    let next_clk_32_48 = (next_clk >> 32) as u16;
133
134                    cols.clk_low = F::from_canonical_u32(clk_low);
135                    cols.clk_high = F::from_canonical_u32(clk_high);
136                    cols.next_clk_0_16 = F::from_canonical_u16(next_clk_0_16);
137                    cols.next_clk_16_24 = F::from_canonical_u8(next_clk_16_24);
138                    cols.next_clk_24_32 = F::from_canonical_u8(next_clk_24_32);
139                    cols.next_clk_32_48 = F::from_canonical_u16(next_clk_32_48);
140
141                    cols.next_pc = [
142                        F::from_canonical_u16((pc & 0xFFFF) as u16),
143                        F::from_canonical_u16(((pc >> 16) & 0xFFFF) as u16),
144                        F::from_canonical_u16(((pc >> 32) & 0xFFFF) as u16),
145                    ];
146
147                    if bump2 {
148                        // All the instructions that require the StateBumpChip to correct the `pc`
149                        // to its correct form increments the `pc` by the default `PC_INC`.
150                        let prev_pc = pc.wrapping_sub(PC_INC as u64);
151                        cols.pc = [
152                            F::from_canonical_u16((prev_pc & 0xFFFF) as u16)
153                                + F::from_canonical_u16(PC_INC as u16),
154                            F::from_canonical_u16(((prev_pc >> 16) & 0xFFFF) as u16),
155                            F::from_canonical_u16(((prev_pc >> 32) & 0xFFFF) as u16),
156                        ];
157                    } else {
158                        cols.pc = cols.next_pc;
159                    }
160
161                    if (next_clk >> 24) != (clk >> 24) {
162                        cols.is_clk = F::one();
163                    } else {
164                        cols.is_clk = F::zero();
165                    }
166                    cols.is_real = F::one();
167                }
168            });
169        });
170    }
171
172    fn included(&self, shard: &Self::Record) -> bool {
173        shard.cpu_event_count != 0
174    }
175
176    fn column_names(&self) -> Vec<String> {
177        StateBumpCols::<F>::struct_reflection().unwrap()
178    }
179}
180
181impl<AB> Air<AB> for StateBumpChip
182where
183    AB: SP1CoreAirBuilder,
184{
185    fn eval(&self, builder: &mut AB) {
186        let main = builder.main();
187        let local = main.row_slice(0);
188        let local: &StateBumpCols<AB::Var> = (*local).borrow();
189        // Check that `is_real` is a boolean value.
190        builder.assert_bool(local.is_real);
191
192        // Receive the state with values potentially in non-canonical forms.
193        builder.receive_state(local.clk_high, local.clk_low, local.pc, local.is_real);
194        // Send the state with `clk_high, clk_low, next_pc` being in canonical forms.
195        builder.send_state(
196            local.next_clk_24_32 + local.next_clk_32_48 * AB::F::from_canonical_u32(1 << 8),
197            local.next_clk_0_16 + local.next_clk_16_24 * AB::F::from_canonical_u32(1 << 16),
198            local.next_pc,
199            local.is_real,
200        );
201
202        // Check that the sent state's clk is in canonical form.
203        // The bottom 16 bits of the `clk` is a u16 value that is 1 (mod 8).
204        builder.send_byte(
205            AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
206            (local.next_clk_0_16 - AB::Expr::one()) * AB::F::from_canonical_u8(8).inverse(),
207            AB::Expr::from_canonical_u32(13),
208            AB::Expr::zero(),
209            local.is_real,
210        );
211        // The top 16 bits of the `clk` is a u16 value.
212        builder.send_byte(
213            AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
214            local.next_clk_32_48.into(),
215            AB::Expr::from_canonical_u32(16),
216            AB::Expr::zero(),
217            local.is_real,
218        );
219        // The two 8 bit limbs in the middle of the clk are valid u8 values.
220        builder.slice_range_check_u8(&[local.next_clk_16_24, local.next_clk_24_32], local.is_real);
221
222        // If `is_clk` is true, a carry happens from the bottom 24 bit limb to the top.
223        // First, check that `is_clk` is a boolean value. This is possible because the `clk` does
224        // not increment by more than `2^24` in a single instruction cycle.
225        builder.assert_bool(local.is_clk);
226        builder.when(local.is_real).assert_eq(
227            local.next_clk_24_32 + local.next_clk_32_48 * AB::F::from_canonical_u32(1 << 8),
228            local.clk_high + local.is_clk,
229        );
230        builder.when(local.is_real).assert_eq(
231            local.next_clk_0_16
232                + local.next_clk_16_24 * AB::F::from_canonical_u32(1 << 16)
233                + local.is_clk * AB::F::from_canonical_u32(1 << 24),
234            local.clk_low,
235        );
236
237        // The `next_pc` is the `pc` with propagated carries.
238        // The `next_pc` is checked to be canonical, three u16 limbs.
239        let mut carry = AB::Expr::zero();
240        for i in 0..3 {
241            carry = (carry.clone() + local.pc[i] - local.next_pc[i])
242                * AB::F::from_canonical_u32(1 << 16).inverse();
243            builder.assert_bool(carry.clone());
244        }
245        builder.assert_zero(carry);
246        builder.slice_range_check_u16(&local.next_pc, local.is_real);
247    }
248}