use std::borrow::Borrow;
use itertools::Itertools;
use slop_air::{Air, AirBuilder};
use slop_algebra::AbstractField;
use slop_matrix::Matrix;
use sp1_core_executor::{Opcode, SyscallCode, CLK_INC, HALT_PC};
use sp1_hypercube::{
air::{
BaseAirBuilder, InteractionScope, PublicValues, SP1AirBuilder, POSEIDON_NUM_WORDS,
PV_DIGEST_NUM_WORDS, SP1_PROOF_NUM_PV_ELTS,
},
Word,
};
use crate::{
adapter::{register::r_type::RTypeReader, state::CPUState},
air::{SP1CoreAirBuilder, SP1Operation, WordAirBuilder},
operations::{
IsZeroOperation, IsZeroOperationInput, SP1FieldWordRangeChecker, U16toU8OperationSafe,
U16toU8OperationSafeInput,
},
};
use super::{columns::SyscallInstrColumns, SyscallInstrsChip};
impl<AB> Air<AB> for SyscallInstrsChip
where
AB: SP1CoreAirBuilder,
AB::Var: Sized,
{
#[inline(never)]
fn eval(&self, builder: &mut AB) {
let main = builder.main();
let local = main.row_slice(0);
let local: &SyscallInstrColumns<AB::Var> = (*local).borrow();
let public_values_slice: [AB::PublicVar; SP1_PROOF_NUM_PV_ELTS] =
core::array::from_fn(|i| builder.public_values()[i]);
let public_values: &PublicValues<
[AB::PublicVar; 4],
[AB::PublicVar; 3],
[AB::PublicVar; 4],
AB::PublicVar,
> = public_values_slice.as_slice().borrow();
let a_input = U16toU8OperationSafeInput::new(
local.adapter.prev_a().0.map(Into::into),
local.a_low_bytes,
local.is_real.into(),
);
let a = U16toU8OperationSafe::eval(builder, a_input);
builder.assert_bool(local.is_real);
self.eval_is_halt_syscall(builder, &a, local);
CPUState::<AB::F>::eval(
builder,
local.state,
local.next_pc.map(Into::into),
AB::Expr::from_canonical_u32(CLK_INC + 256),
local.is_real.into(),
);
let funct3 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct3().unwrap_or(0));
let funct7 = AB::Expr::from_canonical_u8(Opcode::ECALL.funct7().unwrap_or(0));
let base_opcode = AB::Expr::from_canonical_u32(Opcode::ECALL.base_opcode().0);
let instr_type = AB::Expr::from_canonical_u32(Opcode::ECALL.instruction_type().0 as u32);
RTypeReader::<AB::F>::eval(
builder,
local.state.clk_high::<AB>(),
local.state.clk_low::<AB>(),
local.state.pc,
AB::Expr::from_canonical_u32(Opcode::ECALL as u32),
[instr_type, base_opcode, funct3, funct7],
local.op_a_value,
local.adapter,
local.is_real.into(),
);
builder.when(local.is_real).assert_zero(local.adapter.op_a_0);
builder
.when(local.is_real)
.when(AB::Expr::one() - local.is_halt)
.assert_eq(local.next_pc[0], local.state.pc[0] + AB::Expr::from_canonical_u32(4));
builder
.when(local.is_real)
.when(AB::Expr::one() - local.is_halt)
.assert_eq(local.next_pc[1], local.state.pc[1]);
builder
.when(local.is_real)
.when(AB::Expr::one() - local.is_halt)
.assert_eq(local.next_pc[2], local.state.pc[2]);
self.eval_ecall(builder, &a, local);
self.eval_commit(
builder,
&a,
local,
public_values.commit_syscall,
public_values.commit_deferred_syscall,
public_values.committed_value_digest,
public_values.deferred_proofs_digest,
);
self.eval_halt_unimpl(builder, local, public_values);
}
}
impl SyscallInstrsChip {
pub(crate) fn eval_ecall<AB: SP1CoreAirBuilder>(
&self,
builder: &mut AB,
prev_a_byte: &[AB::Expr; 8],
local: &SyscallInstrColumns<AB::Var>,
) {
let syscall_id = prev_a_byte[0].clone();
let send_to_table = prev_a_byte[1].clone();
builder.when_not(local.is_real).assert_zero(send_to_table.clone());
builder.when_not(local.is_real).assert_zero(local.is_halt);
builder.when_not(local.is_real).assert_zero(local.is_commit_deferred_proofs.result);
builder.when(send_to_table.clone()).assert_zero(local.adapter.b()[3]);
builder.when(send_to_table.clone()).assert_zero(local.adapter.c()[3]);
builder.assert_bool(send_to_table.clone());
let b_address = [local.adapter.b()[0], local.adapter.b()[1], local.adapter.b()[2]];
let c_address = [local.adapter.c()[0], local.adapter.c()[1], local.adapter.c()[2]];
builder.send_syscall(
local.state.clk_high::<AB>(),
local.state.clk_low::<AB>(),
syscall_id.clone(),
b_address,
c_address,
send_to_table.clone(),
InteractionScope::Local,
);
SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
builder,
local.adapter.b().map(Into::into),
local.op_b_range_check,
local.is_halt.into(),
);
SP1FieldWordRangeChecker::<AB::F>::range_check::<AB>(
builder,
local.adapter.c().map(Into::into),
local.op_c_range_check,
local.is_commit_deferred_proofs.result.into(),
);
let is_enter_unconstrained = {
IsZeroOperation::<AB::F>::eval(
builder,
IsZeroOperationInput::new(
syscall_id.clone()
- AB::Expr::from_canonical_u32(
SyscallCode::ENTER_UNCONSTRAINED.syscall_id(),
),
local.is_enter_unconstrained,
local.is_real.into(),
),
);
local.is_enter_unconstrained.result
};
let is_hint_len = {
IsZeroOperation::<AB::F>::eval(
builder,
IsZeroOperationInput::new(
syscall_id.clone()
- AB::Expr::from_canonical_u32(SyscallCode::HINT_LEN.syscall_id()),
local.is_hint_len,
local.is_real.into(),
),
);
local.is_hint_len.result
};
let zero_word = Word::<AB::F>::from(0u64);
builder
.when(local.is_real)
.when(is_enter_unconstrained)
.assert_word_eq(local.op_a_value, zero_word);
builder
.when(local.is_real)
.when_not(is_enter_unconstrained + is_hint_len)
.assert_word_eq(local.op_a_value, *local.adapter.prev_a());
builder.slice_range_check_u16(&local.op_a_value.0, local.is_real);
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn eval_commit<AB: SP1CoreAirBuilder>(
&self,
builder: &mut AB,
prev_a_byte: &[AB::Expr; 8],
local: &SyscallInstrColumns<AB::Var>,
commit_syscall: AB::PublicVar,
commit_deferred_syscall: AB::PublicVar,
commit_digest: [[AB::PublicVar; 4]; PV_DIGEST_NUM_WORDS],
deferred_proofs_digest: [AB::PublicVar; POSEIDON_NUM_WORDS],
) {
let (is_commit, is_commit_deferred_proofs) = self.get_is_commit_related_syscall(
builder,
prev_a_byte,
commit_syscall,
commit_deferred_syscall,
local,
);
let mut bitmap_sum = AB::Expr::zero();
for bit in local.index_bitmap.iter() {
builder.when(local.is_real).assert_bool(*bit);
bitmap_sum = bitmap_sum.clone() + (*bit).into();
}
builder
.when(local.is_real)
.when(is_commit.clone() + is_commit_deferred_proofs.clone())
.assert_one(bitmap_sum.clone());
builder
.when(local.is_real)
.when(AB::Expr::one() - (is_commit.clone() + is_commit_deferred_proofs.clone()))
.assert_zero(bitmap_sum);
for (i, bit) in local.index_bitmap.iter().enumerate() {
builder
.when(local.is_real)
.when(*bit)
.assert_eq(local.adapter.b()[0], AB::Expr::from_canonical_u32(i as u32));
}
builder
.when(local.is_real)
.when(is_commit.clone() + is_commit_deferred_proofs.clone())
.assert_zero(local.adapter.b()[1] + local.adapter.b()[2] + local.adapter.b()[3]);
let mut expected_pv_digest =
[AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero(), AB::Expr::zero()];
for i in 0..4 {
expected_pv_digest[i] = builder.index_array(
commit_digest.iter().map(|word| word[i]).collect_vec().as_slice(),
&local.index_bitmap,
);
}
let expected_pv_digest_word = Word([
expected_pv_digest[0].clone()
+ expected_pv_digest[1].clone() * AB::F::from_canonical_u32(1 << 8),
expected_pv_digest[2].clone()
+ expected_pv_digest[3].clone() * AB::F::from_canonical_u32(1 << 8),
AB::Expr::zero(),
AB::Expr::zero(),
]);
builder.assert_bool(is_commit.clone());
for i in 0..4 {
builder
.when(is_commit.clone())
.assert_eq(expected_pv_digest[i].clone(), local.expected_public_values_digest[i]);
}
builder.slice_range_check_u8(&local.expected_public_values_digest, is_commit.clone());
let digest_word: Word<AB::Expr> = local.adapter.c().map(Into::into);
builder
.when(local.is_real)
.when(is_commit.clone())
.assert_word_eq(expected_pv_digest_word, digest_word.clone());
let expected_deferred_proofs_digest_element =
builder.index_array(&deferred_proofs_digest, &local.index_bitmap);
builder
.when(local.is_real)
.when(is_commit_deferred_proofs.clone())
.assert_eq(expected_deferred_proofs_digest_element, digest_word.reduce::<AB>());
}
pub(crate) fn eval_halt_unimpl<AB: SP1AirBuilder>(
&self,
builder: &mut AB,
local: &SyscallInstrColumns<AB::Var>,
public_values: &PublicValues<
[AB::PublicVar; 4],
[AB::PublicVar; 3],
[AB::PublicVar; 4],
AB::PublicVar,
>,
) {
builder
.when(local.is_halt)
.assert_eq(local.next_pc[0], AB::Expr::from_canonical_u64(HALT_PC));
builder.when(local.is_halt).assert_zero(local.next_pc[1]);
builder.when(local.is_halt).assert_zero(local.next_pc[2]);
builder
.when(local.is_halt)
.assert_eq(local.adapter.b().map(Into::into).reduce::<AB>(), public_values.exit_code);
}
pub(crate) fn eval_is_halt_syscall<AB: SP1CoreAirBuilder>(
&self,
builder: &mut AB,
prev_a_byte: &[AB::Expr; 8],
local: &SyscallInstrColumns<AB::Var>,
) {
let syscall_id = prev_a_byte[0].clone();
let is_halt = {
IsZeroOperation::<AB::F>::eval(
builder,
IsZeroOperationInput::new(
syscall_id.clone()
- AB::Expr::from_canonical_u32(SyscallCode::HALT.syscall_id()),
local.is_halt_check,
local.is_real.into(),
),
);
local.is_halt_check.result
};
builder.assert_eq(local.is_halt, is_halt * local.is_real);
}
pub(crate) fn get_is_commit_related_syscall<AB: SP1CoreAirBuilder>(
&self,
builder: &mut AB,
prev_a_byte: &[AB::Expr; 8],
commit_syscall: AB::PublicVar,
commit_deferred_syscall: AB::PublicVar,
local: &SyscallInstrColumns<AB::Var>,
) -> (AB::Expr, AB::Expr) {
let syscall_id = prev_a_byte[0].clone();
let is_commit = {
IsZeroOperation::<AB::F>::eval(
builder,
IsZeroOperationInput::new(
syscall_id.clone()
- AB::Expr::from_canonical_u32(SyscallCode::COMMIT.syscall_id()),
local.is_commit,
local.is_real.into(),
),
);
local.is_commit.result
};
let is_commit_deferred_proofs = {
IsZeroOperation::<AB::F>::eval(
builder,
IsZeroOperationInput::new(
syscall_id.clone()
- AB::Expr::from_canonical_u32(
SyscallCode::COMMIT_DEFERRED_PROOFS.syscall_id(),
),
local.is_commit_deferred_proofs,
local.is_real.into(),
),
);
local.is_commit_deferred_proofs.result
};
builder.when(is_commit).assert_one(commit_syscall);
builder.when(is_commit_deferred_proofs).assert_one(commit_deferred_syscall);
(is_commit.into(), is_commit_deferred_proofs.into())
}
}