sp1_core_machine/syscall/
chip.rs

1use crate::utils::next_power_of_two;
2use core::fmt;
3use itertools::Itertools;
4use p3_air::{Air, BaseAir};
5use p3_field::{AbstractField, PrimeField32};
6use p3_matrix::{dense::RowMajorMatrix, Matrix};
7use p3_maybe_rayon::prelude::{IntoParallelRefIterator, ParallelBridge, ParallelIterator};
8use sp1_core_executor::{
9    events::{GlobalInteractionEvent, SyscallEvent},
10    ExecutionRecord, Program,
11};
12use sp1_derive::AlignedBorrow;
13use sp1_stark::{
14    air::{AirInteraction, InteractionScope, MachineAir, SP1AirBuilder},
15    InteractionKind,
16};
17use std::{
18    borrow::{Borrow, BorrowMut},
19    mem::size_of,
20};
21/// The number of main trace columns for `SyscallChip`.
22pub const NUM_SYSCALL_COLS: usize = size_of::<SyscallCols<u8>>();
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
25pub enum SyscallShardKind {
26    Core,
27    Precompile,
28}
29
30/// A chip that stores the syscall invocations.
31pub struct SyscallChip {
32    shard_kind: SyscallShardKind,
33}
34
35impl SyscallChip {
36    pub const fn new(shard_kind: SyscallShardKind) -> Self {
37        Self { shard_kind }
38    }
39
40    pub const fn core() -> Self {
41        Self::new(SyscallShardKind::Core)
42    }
43
44    pub const fn precompile() -> Self {
45        Self::new(SyscallShardKind::Precompile)
46    }
47
48    pub fn shard_kind(&self) -> SyscallShardKind {
49        self.shard_kind
50    }
51}
52
53/// The column layout for the chip.
54#[derive(AlignedBorrow, Clone, Copy)]
55#[repr(C)]
56pub struct SyscallCols<T: Copy> {
57    /// The shard number of the syscall.
58    pub shard: T,
59
60    /// The clk of the syscall.
61    pub clk: T,
62
63    /// The syscall_id of the syscall.
64    pub syscall_id: T,
65
66    /// The arg1.
67    pub arg1: T,
68
69    /// The arg2.
70    pub arg2: T,
71
72    pub is_real: T,
73}
74
75impl<F: PrimeField32> MachineAir<F> for SyscallChip {
76    type Record = ExecutionRecord;
77
78    type Program = Program;
79
80    fn name(&self) -> String {
81        format!("Syscall{}", self.shard_kind).to_string()
82    }
83
84    fn generate_dependencies(&self, input: &ExecutionRecord, output: &mut ExecutionRecord) {
85        let events = match self.shard_kind {
86            SyscallShardKind::Core => &input
87                .syscall_events
88                .iter()
89                .filter(|e| e.syscall_code.should_send() == 1)
90                .copied()
91                .collect::<Vec<_>>(),
92            SyscallShardKind::Precompile => &input
93                .precompile_events
94                .all_events()
95                .map(|(event, _)| event.to_owned())
96                .collect::<Vec<_>>(),
97        };
98
99        let events = events
100            .iter()
101            .filter(|e| e.syscall_code.should_send() == 1)
102            .map(|event| GlobalInteractionEvent {
103                message: [event.shard, event.clk, event.syscall_id, event.arg1, event.arg2, 0, 0],
104                is_receive: self.shard_kind == SyscallShardKind::Precompile,
105                kind: InteractionKind::Syscall as u8,
106            })
107            .collect_vec();
108        output.global_interaction_events.extend(events);
109    }
110
111    fn num_rows(&self, input: &Self::Record) -> Option<usize> {
112        let events = match self.shard_kind() {
113            SyscallShardKind::Core => &input.syscall_events,
114            SyscallShardKind::Precompile => &input
115                .precompile_events
116                .all_events()
117                .map(|(event, _)| event.to_owned())
118                .collect::<Vec<_>>(),
119        };
120        let nb_rows = events.len();
121        let size_log2 = input.fixed_log2_rows::<F, _>(self);
122        let padded_nb_rows = next_power_of_two(nb_rows, size_log2);
123        Some(padded_nb_rows)
124    }
125
126    fn generate_trace(
127        &self,
128        input: &ExecutionRecord,
129        _output: &mut ExecutionRecord,
130    ) -> RowMajorMatrix<F> {
131        let row_fn = |syscall_event: &SyscallEvent, _: bool| {
132            let mut row = [F::zero(); NUM_SYSCALL_COLS];
133            let cols: &mut SyscallCols<F> = row.as_mut_slice().borrow_mut();
134
135            cols.shard = F::from_canonical_u32(syscall_event.shard);
136            cols.clk = F::from_canonical_u32(syscall_event.clk);
137            cols.syscall_id = F::from_canonical_u32(syscall_event.syscall_code.syscall_id());
138            cols.arg1 = F::from_canonical_u32(syscall_event.arg1);
139            cols.arg2 = F::from_canonical_u32(syscall_event.arg2);
140            cols.is_real = F::one();
141            row
142        };
143
144        let mut rows = match self.shard_kind {
145            SyscallShardKind::Core => input
146                .syscall_events
147                .par_iter()
148                .filter(|event| event.syscall_code.should_send() == 1)
149                .map(|event| row_fn(event, false))
150                .collect::<Vec<_>>(),
151            SyscallShardKind::Precompile => input
152                .precompile_events
153                .all_events()
154                .map(|(event, _)| event)
155                .par_bridge()
156                .map(|event| row_fn(event, true))
157                .collect::<Vec<_>>(),
158        };
159
160        // Pad the trace to a power of two depending on the proof shape in `input`.
161        rows.resize(
162            <SyscallChip as MachineAir<F>>::num_rows(self, input).unwrap(),
163            [F::zero(); NUM_SYSCALL_COLS],
164        );
165
166        RowMajorMatrix::new(rows.into_iter().flatten().collect::<Vec<_>>(), NUM_SYSCALL_COLS)
167    }
168
169    fn included(&self, shard: &Self::Record) -> bool {
170        if let Some(shape) = shard.shape.as_ref() {
171            shape.included::<F, _>(self)
172        } else {
173            match self.shard_kind {
174                SyscallShardKind::Core => {
175                    shard
176                        .syscall_events
177                        .iter()
178                        .filter(|e| e.syscall_code.should_send() == 1)
179                        .take(1)
180                        .count() >
181                        0
182                }
183                SyscallShardKind::Precompile => {
184                    !shard.precompile_events.is_empty() &&
185                        shard.cpu_events.is_empty() &&
186                        shard.global_memory_initialize_events.is_empty() &&
187                        shard.global_memory_finalize_events.is_empty()
188                }
189            }
190        }
191    }
192
193    fn commit_scope(&self) -> InteractionScope {
194        InteractionScope::Local
195    }
196}
197
198impl<AB> Air<AB> for SyscallChip
199where
200    AB: SP1AirBuilder,
201{
202    fn eval(&self, builder: &mut AB) {
203        let main = builder.main();
204        let local = main.row_slice(0);
205        let local: &SyscallCols<AB::Var> = (*local).borrow();
206
207        // Constrain that `local.is_real` is boolean.
208        builder.assert_bool(local.is_real);
209
210        builder.assert_eq(
211            local.is_real * local.is_real * local.is_real,
212            local.is_real * local.is_real * local.is_real,
213        );
214
215        match self.shard_kind {
216            SyscallShardKind::Core => {
217                builder.receive_syscall(
218                    local.shard,
219                    local.clk,
220                    local.syscall_id,
221                    local.arg1,
222                    local.arg2,
223                    local.is_real,
224                    InteractionScope::Local,
225                );
226
227                // Send the "send interaction" to the global table.
228                builder.send(
229                    AirInteraction::new(
230                        vec![
231                            local.shard.into(),
232                            local.clk.into(),
233                            local.syscall_id.into(),
234                            local.arg1.into(),
235                            local.arg2.into(),
236                            AB::Expr::zero(),
237                            AB::Expr::zero(),
238                            AB::Expr::one(),
239                            AB::Expr::zero(),
240                            AB::Expr::from_canonical_u8(InteractionKind::Syscall as u8),
241                        ],
242                        local.is_real.into(),
243                        InteractionKind::Global,
244                    ),
245                    InteractionScope::Local,
246                );
247            }
248            SyscallShardKind::Precompile => {
249                builder.send_syscall(
250                    local.shard,
251                    local.clk,
252                    local.syscall_id,
253                    local.arg1,
254                    local.arg2,
255                    local.is_real,
256                    InteractionScope::Local,
257                );
258
259                // Send the "receive interaction" to the global table.
260                builder.send(
261                    AirInteraction::new(
262                        vec![
263                            local.shard.into(),
264                            local.clk.into(),
265                            local.syscall_id.into(),
266                            local.arg1.into(),
267                            local.arg2.into(),
268                            AB::Expr::zero(),
269                            AB::Expr::zero(),
270                            AB::Expr::zero(),
271                            AB::Expr::one(),
272                            AB::Expr::from_canonical_u8(InteractionKind::Syscall as u8),
273                        ],
274                        local.is_real.into(),
275                        InteractionKind::Global,
276                    ),
277                    InteractionScope::Local,
278                );
279            }
280        }
281    }
282}
283
284impl<F> BaseAir<F> for SyscallChip {
285    fn width(&self) -> usize {
286        NUM_SYSCALL_COLS
287    }
288}
289
290impl fmt::Display for SyscallShardKind {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        match self {
293            SyscallShardKind::Core => write!(f, "Core"),
294            SyscallShardKind::Precompile => write!(f, "Precompile"),
295        }
296    }
297}