1use std::borrow::Borrow;
2
3use itertools::Itertools;
4use slop_air::{Air, AirBuilder};
5use slop_algebra::AbstractField;
6use slop_matrix::Matrix;
7use sp1_core_executor::{Opcode, SyscallCode, CLK_INC, HALT_PC};
8use sp1_hypercube::{
9 air::{
10 BaseAirBuilder, InteractionScope, PublicValues, SP1AirBuilder, POSEIDON_NUM_WORDS,
11 PV_DIGEST_NUM_WORDS, SP1_PROOF_NUM_PV_ELTS,
12 },
13 Word,
14};
15
16use crate::{
17 adapter::{register::r_type::RTypeReader, state::CPUState},
18 air::{SP1CoreAirBuilder, SP1Operation, WordAirBuilder},
19 operations::{
20 IsZeroOperation, IsZeroOperationInput, SP1FieldWordRangeChecker, TrapOperation,
21 U16toU8OperationSafe, U16toU8OperationSafeInput,
22 },
23 TrustMode, UserMode,
24};
25
26use super::{columns::SyscallInstrColumns, SyscallInstrsChip};
27
28impl<AB, M> Air<AB> for SyscallInstrsChip<M>
29where
30 AB: SP1CoreAirBuilder,
31 AB::Var: Sized,
32 M: TrustMode,
33{
34 #[inline(never)]
35 fn eval(&self, builder: &mut AB) {
36 let main = builder.main();
37 let local = main.row_slice(0);
38 let local: &SyscallInstrColumns<AB::Var, M> = (*local).borrow();
39
40 let public_values_slice: [AB::PublicVar; SP1_PROOF_NUM_PV_ELTS] =
41 core::array::from_fn(|i| builder.public_values()[i]);
42 let public_values: &PublicValues<
43 [AB::PublicVar; 4],
44 [AB::PublicVar; 3],
45 [AB::PublicVar; 4],
46 AB::PublicVar,
47 > = public_values_slice.as_slice().borrow();
48
49 let a_input = U16toU8OperationSafeInput::new(
51 local.adapter.prev_a().0.map(Into::into),
52 local.a_low_bytes,
53 local.is_real.into(),
54 );
55 let a = U16toU8OperationSafe::eval(builder, a_input);
56
57 builder.assert_bool(local.is_real);
60
61 self.eval_is_halt_syscall(builder, &a, local);
63
64 CPUState::<AB::F>::eval(
68 builder,
69 local.state,
70 local.next_pc.map(Into::into),
71 AB::Expr::from_canonical_u32(CLK_INC + 256),
72 local.is_real.into(),
73 );
74
75 #[allow(unused_variables)]
76 {
77 let funct3 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct3().unwrap_or(0));
78 let funct7 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct7().unwrap_or(0));
79 let base_opcode = AB::Expr::from_canonical_u32(Opcode::ECALL.base_opcode().0);
80 let instr_type =
81 AB::Expr::from_canonical_u32(Opcode::ECALL.instruction_type().0 as u32);
82 }
83
84 RTypeReader::<AB::F>::eval(
86 builder,
87 local.state.clk_high::<AB>(),
88 local.state.clk_low::<AB>(),
89 local.state.pc,
90 AB::Expr::from_canonical_u32(Opcode::ECALL as u32),
91 local.op_a_value,
92 local.adapter,
93 local.is_real.into(),
94 local.is_real.into(), );
96 builder.when(local.is_real).assert_zero(local.adapter.op_a_0);
97
98 #[allow(unused_variables)]
99 let (is_sig_return, is_trap, trap_code) = if !M::IS_TRUSTED {
100 let local = main.row_slice(0);
101 let local: &SyscallInstrColumns<AB::Var, UserMode> = (*local).borrow();
102 #[cfg(not(feature = "mprotect"))]
103 builder.assert_zero(local.is_real);
104 let is_sig_return = self.eval_sig_return(builder, &a, local);
105 let (is_trap, trap_code) = self.eval_trap(builder, local);
106 builder.assert_bool(local.is_halt + is_sig_return.clone() + is_trap.clone());
107 self.eval_page_protect(builder, local, &a, public_values.is_untrusted_programs_enabled);
109 (is_sig_return, is_trap, trap_code)
110 } else {
111 (AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero())
112 };
113
114 #[cfg(feature = "mprotect")]
115 {
116 builder.assert_eq(
117 public_values.is_untrusted_programs_enabled,
118 AB::Expr::from_bool(!M::IS_TRUSTED),
119 );
120 }
121
122 #[cfg(not(feature = "mprotect"))]
123 let jump: AB::Expr = local.is_halt.into();
124 #[cfg(feature = "mprotect")]
125 let jump: AB::Expr = local.is_halt.into() + is_sig_return.clone() + is_trap.clone();
126
127 builder
130 .when(local.is_real)
131 .when(AB::Expr::one() - jump.clone())
132 .assert_eq(local.next_pc[0], local.state.pc[0] + AB::Expr::from_canonical_u32(4));
133 builder
134 .when(local.is_real)
135 .when(AB::Expr::one() - jump.clone())
136 .assert_eq(local.next_pc[1], local.state.pc[1]);
137 builder
138 .when(local.is_real)
139 .when(AB::Expr::one() - jump.clone())
140 .assert_eq(local.next_pc[2], local.state.pc[2]);
141
142 self.eval_ecall(builder, &a, local, trap_code);
144
145 self.eval_commit(
147 builder,
148 &a,
149 local,
150 public_values.commit_syscall,
151 public_values.commit_deferred_syscall,
152 public_values.committed_value_digest,
153 public_values.deferred_proofs_digest,
154 );
155
156 self.eval_halt_unimpl(builder, local, public_values);
158 }
159}
160
161impl<M: TrustMode> SyscallInstrsChip<M> {
162 pub(crate) fn eval_ecall<AB: SP1CoreAirBuilder>(
168 &self,
169 builder: &mut AB,
170 prev_a_byte: &[AB::Expr; 8],
171 local: &SyscallInstrColumns<AB::Var, M>,
172 trap_code: AB::Expr,
173 ) {
174 let syscall_id = prev_a_byte[0].clone();
177 let send_to_table = prev_a_byte[1].clone();
178
179 builder.when_not(local.is_real).assert_zero(send_to_table.clone());
182 builder.when_not(local.is_real).assert_zero(local.is_halt);
183 builder.when_not(local.is_real).assert_zero(local.is_commit_deferred_proofs.result);
184 builder.when(send_to_table.clone()).assert_zero(local.adapter.b()[3]);
185 builder.when(send_to_table.clone()).assert_zero(local.adapter.c()[3]);
186 builder.assert_bool(send_to_table.clone());
187 if !M::IS_TRUSTED {
188 builder.when_not(send_to_table.clone()).assert_zero(trap_code.clone());
189 }
190
191 let b_address = [local.adapter.b()[0], local.adapter.b()[1], local.adapter.b()[2]];
192 let c_address = [local.adapter.c()[0], local.adapter.c()[1], local.adapter.c()[2]];
193
194 builder.send_syscall(
195 local.state.clk_high::<AB>(),
196 local.state.clk_low::<AB>(),
197 syscall_id.clone(),
198 trap_code.clone(),
199 b_address,
200 c_address,
201 send_to_table.clone(),
202 InteractionScope::Local,
203 );
204
205 SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
209 builder,
210 local.adapter.b().map(Into::into),
211 local.op_b_range_check,
212 local.is_halt.into(),
213 );
214
215 SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
218 builder,
219 local.adapter.c().map(Into::into),
220 local.op_c_range_check,
221 local.is_commit_deferred_proofs.result.into(),
222 );
223
224 let is_enter_unconstrained = {
226 IsZeroOperation::<AB::F>::eval(
227 builder,
228 IsZeroOperationInput::new(
229 syscall_id.clone()
230 - AB::Expr::from_canonical_u32(
231 SyscallCode::ENTER_UNCONSTRAINED.syscall_id(),
232 ),
233 local.is_enter_unconstrained,
234 local.is_real.into(),
235 ),
236 );
237 local.is_enter_unconstrained.result
238 };
239
240 let is_hint_len = {
242 IsZeroOperation::<AB::F>::eval(
243 builder,
244 IsZeroOperationInput::new(
245 syscall_id.clone()
246 - AB::Expr::from_canonical_u32(SyscallCode::HINT_LEN.syscall_id()),
247 local.is_hint_len,
248 local.is_real.into(),
249 ),
250 );
251 local.is_hint_len.result
252 };
253
254 let zero_word = Word::<AB::F>::from(0u64);
257 builder
258 .when(local.is_real)
259 .when(is_enter_unconstrained)
260 .assert_word_eq(local.op_a_value, zero_word);
261
262 builder
264 .when(local.is_real)
265 .when_not(is_enter_unconstrained + is_hint_len)
266 .assert_word_eq(local.op_a_value, *local.adapter.prev_a());
267
268 builder.slice_range_check_u16(&local.op_a_value.0, local.is_real);
273 }
274
275 #[allow(clippy::too_many_arguments)]
277 pub(crate) fn eval_commit<AB: SP1CoreAirBuilder>(
278 &self,
279 builder: &mut AB,
280 prev_a_byte: &[AB::Expr; 8],
281 local: &SyscallInstrColumns<AB::Var, M>,
282 commit_syscall: AB::PublicVar,
283 commit_deferred_syscall: AB::PublicVar,
284 commit_digest: [[AB::PublicVar; 4]; PV_DIGEST_NUM_WORDS],
285 deferred_proofs_digest: [AB::PublicVar; POSEIDON_NUM_WORDS],
286 ) {
287 let (is_commit, is_commit_deferred_proofs) = self.get_is_commit_related_syscall(
288 builder,
289 prev_a_byte,
290 commit_syscall,
291 commit_deferred_syscall,
292 local,
293 );
294
295 let mut bitmap_sum = AB::Expr::zero();
297 for bit in local.index_bitmap.iter() {
299 builder.when(local.is_real).assert_bool(*bit);
300 bitmap_sum = bitmap_sum.clone() + (*bit).into();
301 }
302 builder
304 .when(local.is_real)
305 .when(is_commit.clone() + is_commit_deferred_proofs.clone())
306 .assert_one(bitmap_sum.clone());
307 builder
309 .when(local.is_real)
310 .when(AB::Expr::one() - (is_commit.clone() + is_commit_deferred_proofs.clone()))
311 .assert_zero(bitmap_sum);
312
313 for (i, bit) in local.index_bitmap.iter().enumerate() {
315 builder
316 .when(local.is_real)
317 .when(*bit)
318 .assert_eq(local.adapter.b()[0], AB::Expr::from_canonical_u32(i as u32));
319 }
320 builder
323 .when(local.is_real)
324 .when(is_commit.clone() + is_commit_deferred_proofs.clone())
325 .assert_zero(local.adapter.b()[1] + local.adapter.b()[2] + local.adapter.b()[3]);
326
327 let mut expected_pv_digest =
335 [AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero()];
336 for i in 0..4 {
337 expected_pv_digest[i] = builder.index_array(
338 commit_digest.iter().map(|word| word[i]).collect_vec().as_slice(),
339 &local.index_bitmap,
340 );
341 }
342 let expected_pv_digest_word = Word([
344 expected_pv_digest[0].clone()
345 + expected_pv_digest[1].clone() * AB::F::from_canonical_u32(1 << 8),
346 expected_pv_digest[2].clone()
347 + expected_pv_digest[3].clone() * AB::F::from_canonical_u32(1 << 8),
348 AB::Expr::zero(),
349 AB::Expr::zero(),
350 ]);
351
352 builder.assert_bool(is_commit.clone());
354 for i in 0..4 {
355 builder
356 .when(is_commit.clone())
357 .assert_eq(expected_pv_digest[i].clone(), local.expected_public_values_digest[i]);
358 }
359 builder.slice_range_check_u8(&local.expected_public_values_digest, is_commit.clone());
360
361 let digest_word: Word<AB::Expr> = local.adapter.c().map(Into::into);
362
363 builder
365 .when(local.is_real)
366 .when(is_commit.clone())
367 .assert_word_eq(expected_pv_digest_word, digest_word.clone());
368
369 let expected_deferred_proofs_digest_element =
370 builder.index_array(&deferred_proofs_digest, &local.index_bitmap);
371
372 builder
373 .when(local.is_real)
374 .when(is_commit_deferred_proofs.clone())
375 .assert_eq(expected_deferred_proofs_digest_element, digest_word.reduce::<AB>());
376 }
377
378 pub(crate) fn eval_sig_return<AB: SP1CoreAirBuilder>(
380 &self,
381 builder: &mut AB,
382 prev_a_byte: &[AB::Expr; 8],
383 local: &SyscallInstrColumns<AB::Var, UserMode>,
384 ) -> AB::Expr {
385 let syscall_id = prev_a_byte[0].clone();
386 let is_sig_return = {
387 IsZeroOperation::<AB::F>::eval(
388 builder,
389 IsZeroOperationInput::new(
390 syscall_id.clone()
391 - AB::Expr::from_canonical_u32(SyscallCode::SIG_RETURN.syscall_id()),
392 local.user_mode_cols.is_sig_return,
393 local.is_real.into(),
394 ),
395 );
396 local.user_mode_cols.is_sig_return.result
397 };
398
399 builder.assert_bool(is_sig_return);
400 builder.when_not(local.is_real).assert_zero(is_sig_return);
401
402 builder.eval_memory_access_read(
403 local.state.clk_high::<AB>(),
404 local.state.clk_low::<AB>(),
405 &[
406 local.adapter.b()[0].into(),
407 local.adapter.b()[1].into(),
408 local.adapter.b()[2].into(),
409 ],
410 local.user_mode_cols.next_pc_record,
411 is_sig_return,
412 );
413
414 let next_pc = local.user_mode_cols.next_pc_record.prev_value;
415 builder.when(is_sig_return).assert_eq(local.next_pc[0], next_pc[0]);
416 builder.when(is_sig_return).assert_eq(local.next_pc[1], next_pc[1]);
417 builder.when(is_sig_return).assert_eq(local.next_pc[2], next_pc[2]);
418 builder.when(is_sig_return).assert_zero(next_pc[3]);
419
420 is_sig_return.into()
421 }
422
423 pub(crate) fn eval_trap<AB: SP1CoreAirBuilder>(
425 &self,
426 builder: &mut AB,
427 local: &SyscallInstrColumns<AB::Var, UserMode>,
428 ) -> (AB::Expr, AB::Expr) {
429 let is_not_trap = {
430 IsZeroOperation::<AB::F>::eval(
431 builder,
432 IsZeroOperationInput::new(
433 local.user_mode_cols.trap_code.into(),
434 local.user_mode_cols.is_not_trap,
435 local.is_real.into(),
436 ),
437 );
438 local.user_mode_cols.is_not_trap.result
439 };
440
441 builder.assert_bool(is_not_trap);
442 builder.when_not(local.is_real).assert_zero(is_not_trap);
443
444 let is_trap = local.is_real.into() - is_not_trap.into();
445 builder.assert_bool(is_trap.clone());
446
447 let next_pc = TrapOperation::<AB::F>::eval(
448 builder,
449 local.user_mode_cols.trap_operation,
450 local.state.clk_high::<AB>(),
451 local.state.clk_low::<AB>(),
452 local.user_mode_cols.trap_code.into(),
453 local.state.pc.map(Into::into),
454 local.user_mode_cols.addresses,
455 is_trap.clone(),
456 );
457
458 builder.when(is_trap.clone()).assert_eq(local.next_pc[0], next_pc[0]);
459 builder.when(is_trap.clone()).assert_eq(local.next_pc[1], next_pc[1]);
460 builder.when(is_trap.clone()).assert_eq(local.next_pc[2], next_pc[2]);
461
462 (is_trap, local.user_mode_cols.trap_code.into())
463 }
464
465 pub(crate) fn eval_halt_unimpl<AB: SP1AirBuilder>(
467 &self,
468 builder: &mut AB,
469 local: &SyscallInstrColumns<AB::Var, M>,
470 public_values: &PublicValues<
471 [AB::PublicVar; 4],
472 [AB::PublicVar; 3],
473 [AB::PublicVar; 4],
474 AB::PublicVar,
475 >,
476 ) {
477 builder
479 .when(local.is_halt)
480 .assert_eq(local.next_pc[0], AB::Expr::from_canonical_u64(HALT_PC));
481 builder.when(local.is_halt).assert_zero(local.next_pc[1]);
482 builder.when(local.is_halt).assert_zero(local.next_pc[2]);
483
484 builder
486 .when(local.is_halt)
487 .assert_eq(local.adapter.b().map(Into::into).reduce::<AB>(), public_values.exit_code);
488 }
489
490 pub(crate) fn eval_page_protect<AB: SP1CoreAirBuilder>(
491 &self,
492 builder: &mut AB,
493 local: &SyscallInstrColumns<AB::Var, UserMode>,
494 prev_a_byte: &[AB::Expr; 8],
495 is_page_protect_active: AB::PublicVar,
496 ) {
497 let syscall_id = prev_a_byte[0].clone();
498
499 let is_mprotect = {
501 IsZeroOperation::<AB::F>::eval(
502 builder,
503 IsZeroOperationInput::new(
504 syscall_id.clone()
505 - AB::Expr::from_canonical_u32(SyscallCode::MPROTECT.syscall_id()),
506 local.user_mode_cols.is_page_protect,
507 local.is_real.into(),
508 ),
509 );
510 local.user_mode_cols.is_page_protect.result
511 };
512
513 builder.when(is_mprotect).assert_one(is_page_protect_active);
514 }
515
516 pub(crate) fn eval_is_halt_syscall<AB: SP1CoreAirBuilder>(
518 &self,
519 builder: &mut AB,
520 prev_a_byte: &[AB::Expr; 8],
521 local: &SyscallInstrColumns<AB::Var, M>,
522 ) {
523 let syscall_id = prev_a_byte[0].clone();
525
526 let is_halt = {
528 IsZeroOperation::<AB::F>::eval(
529 builder,
530 IsZeroOperationInput::new(
531 syscall_id.clone()
532 - AB::Expr::from_canonical_u32(SyscallCode::HALT.syscall_id()),
533 local.is_halt_check,
534 local.is_real.into(),
535 ),
536 );
537 local.is_halt_check.result
538 };
539
540 builder.assert_eq(local.is_halt, is_halt * local.is_real);
544 }
545
546 pub(crate) fn get_is_commit_related_syscall<AB: SP1CoreAirBuilder>(
549 &self,
550 builder: &mut AB,
551 prev_a_byte: &[AB::Expr; 8],
552 commit_syscall: AB::PublicVar,
553 commit_deferred_syscall: AB::PublicVar,
554 local: &SyscallInstrColumns<AB::Var, M>,
555 ) -> (AB::Expr, AB::Expr) {
556 let syscall_id = prev_a_byte[0].clone();
557
558 let is_commit = {
560 IsZeroOperation::<AB::F>::eval(
561 builder,
562 IsZeroOperationInput::new(
563 syscall_id.clone()
564 - AB::Expr::from_canonical_u32(SyscallCode::COMMIT.syscall_id()),
565 local.is_commit,
566 local.is_real.into(),
567 ),
568 );
569 local.is_commit.result
570 };
571
572 let is_commit_deferred_proofs = {
574 IsZeroOperation::<AB::F>::eval(
575 builder,
576 IsZeroOperationInput::new(
577 syscall_id.clone()
578 - AB::Expr::from_canonical_u32(
579 SyscallCode::COMMIT_DEFERRED_PROOFS.syscall_id(),
580 ),
581 local.is_commit_deferred_proofs,
582 local.is_real.into(),
583 ),
584 );
585 local.is_commit_deferred_proofs.result
586 };
587
588 builder.when(is_commit).assert_one(commit_syscall);
590 builder.when(is_commit_deferred_proofs).assert_one(commit_deferred_syscall);
592
593 (is_commit.into(), is_commit_deferred_proofs.into())
594 }
595}