use slop_air::AirBuilder;
use slop_algebra::{AbstractField, Field, PrimeField32};
use sp1_core_executor::{
events::{ByteLookupEvent, ByteRecord, PageProtRecord},
ByteOpcode,
};
use sp1_derive::AlignedBorrow;
use sp1_hypercube::air::{BaseAirBuilder, SP1AirBuilder};
use sp1_primitives::consts::{
split_page_idx, PAGE_SIZE, PROT_FAILURE_READ, PROT_FAILURE_WRITE, PROT_READ, PROT_WRITE,
};
use struct_reflection::{StructReflection, StructReflectionHelper};
use crate::{air::MemoryAirBuilder, memory::PageProtAccessCols};
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageOperation<T> {
pub addr_4_bits: T,
pub addr_12_bits: T,
}
impl<F: Field> PageOperation<F> {
pub fn populate(&mut self, record: &mut impl ByteRecord, addr: u64) {
let addr_12_bits: u16 = (addr & 0xFFF).try_into().unwrap();
let addr_4_bits: u16 = ((addr >> 12) & 0xF).try_into().unwrap();
self.addr_12_bits = F::from_canonical_u16(addr_12_bits);
self.addr_4_bits = F::from_canonical_u16(addr_4_bits);
record.add_bit_range_check(addr_12_bits, 12);
record.add_bit_range_check(addr_4_bits, 4);
}
pub fn eval<AB: SP1AirBuilder>(
builder: &mut AB,
addr: &[AB::Expr; 3],
cols: PageOperation<AB::Var>,
is_real: AB::Expr,
) -> [AB::Expr; 3] {
builder.assert_bool(is_real.clone());
builder.when(is_real.clone()).assert_eq(
addr[0].clone(),
cols.addr_12_bits + cols.addr_4_bits * (AB::Expr::from_canonical_u32(1 << 12)),
);
builder.send_byte(
AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
cols.addr_4_bits.into(),
AB::Expr::from_canonical_u32(4),
AB::Expr::zero(),
is_real.clone(),
);
builder.send_byte(
AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
cols.addr_12_bits.into(),
AB::Expr::from_canonical_u32(12),
AB::Expr::zero(),
is_real.clone(),
);
[cols.addr_4_bits.into(), addr[1].clone(), addr[2].clone()]
}
}
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageProtOperation<T> {
pub page_op: PageOperation<T>,
pub page_prot_access: PageProtAccessCols<T>,
}
impl<F: PrimeField32> PageProtOperation<F> {
pub fn populate(
&mut self,
record: &mut impl ByteRecord,
addr: u64,
clk: u64,
previous_page_prot_access: &PageProtRecord,
) {
self.page_op.populate(record, addr);
assert!(previous_page_prot_access.timestamp < clk);
self.page_prot_access.populate(previous_page_prot_access, clk, record);
}
}
impl<F: Field> PageProtOperation<F> {
pub fn eval<AB: SP1AirBuilder>(
builder: &mut AB,
clk_high: AB::Expr,
clk_low: AB::Expr,
addr: &[AB::Expr; 3],
cols: PageProtOperation<AB::Var>,
is_real: AB::Expr,
) {
builder.assert_bool(is_real.clone());
let page_idx = PageOperation::<AB::F>::eval(builder, addr, cols.page_op, is_real.clone());
builder.eval_page_prot_access_read(
clk_high,
clk_low,
&page_idx,
cols.page_prot_access,
is_real.clone(),
);
}
}
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageIsEqualOrAdjacentOperation<T> {
pub is_overflow: T,
pub is_adjacent: T,
}
impl<F: Field> PageIsEqualOrAdjacentOperation<F> {
pub fn populate(&mut self, curr_page_idx: u64, next_page_idx: u64) {
if curr_page_idx == next_page_idx {
self.is_adjacent = F::zero();
} else if curr_page_idx + 1 == next_page_idx {
self.is_adjacent = F::one();
} else {
panic!("curr_page_idx and next_page_idx are not equal or adjacent");
}
let next_page_limbs = split_page_idx(next_page_idx);
let next_page_20_bits = next_page_limbs[0] as u64 + ((next_page_limbs[1] as u64) << 4);
self.is_overflow = F::from_bool(next_page_20_bits == 0);
}
pub fn eval<AB: SP1AirBuilder>(
builder: &mut AB,
curr_page_idx: [AB::Expr; 3],
next_page_idx: [AB::Expr; 3],
cols: PageIsEqualOrAdjacentOperation<AB::Var>,
is_real: AB::Expr,
) {
builder.assert_bool(is_real.clone());
builder.assert_bool(cols.is_adjacent);
builder.assert_bool(cols.is_overflow);
let curr_page_20_bits = curr_page_idx[0].clone()
+ curr_page_idx[1].clone() * (AB::Expr::from_canonical_u32(1 << 4));
let next_page_20_bits = next_page_idx[0].clone()
+ next_page_idx[1].clone() * (AB::Expr::from_canonical_u32(1 << 4));
builder
.when(is_real.clone())
.when_not(cols.is_adjacent)
.assert_eq(curr_page_20_bits.clone(), next_page_20_bits.clone());
builder
.when(is_real.clone())
.when_not(cols.is_adjacent)
.assert_eq(curr_page_idx[2].clone(), next_page_idx[2].clone());
builder.when(cols.is_adjacent).assert_one(is_real.clone());
let mut is_adjacent_builder = builder.when(cols.is_adjacent);
is_adjacent_builder
.when_not(cols.is_overflow)
.assert_eq(curr_page_20_bits.clone() + AB::Expr::one(), next_page_20_bits.clone());
is_adjacent_builder
.when_not(cols.is_overflow)
.assert_eq(curr_page_idx[2].clone(), next_page_idx[2].clone());
is_adjacent_builder
.when(cols.is_overflow)
.assert_eq(curr_page_20_bits, AB::Expr::from_canonical_u32((1 << 20) - 1));
is_adjacent_builder.when(cols.is_overflow).assert_eq(next_page_20_bits, AB::Expr::zero());
is_adjacent_builder
.when(cols.is_overflow)
.assert_eq(curr_page_idx[2].clone() + AB::Expr::one(), next_page_idx[2].clone());
}
}
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct TrapPageProtOperation<T> {
pub page_prot_access: PageProtAccessCols<T>,
pub is_read_fail: T,
pub is_write_fail: T,
pub is_now_trap: T,
}
#[allow(clippy::too_many_arguments)]
impl<F: PrimeField32> TrapPageProtOperation<F> {
#[allow(clippy::too_many_arguments)]
pub fn populate(
&mut self,
record: &mut impl ByteRecord,
clk: u64,
permissions: u8,
page_prot_access: PageProtRecord,
is_not_trap: &mut bool,
trap_code: &mut u8,
) {
assert!(*is_not_trap);
self.page_prot_access.populate(&page_prot_access, clk, record);
let perm = page_prot_access.page_prot;
if permissions == PROT_READ {
self.is_read_fail = F::from_bool((perm & PROT_READ) == 0);
self.is_write_fail = F::zero();
self.is_now_trap = self.is_read_fail;
if self.is_now_trap == F::one() {
*trap_code = PROT_FAILURE_READ as u8;
}
}
if permissions == PROT_WRITE {
self.is_read_fail = F::zero();
self.is_write_fail = F::from_bool((perm & PROT_WRITE) == 0);
self.is_now_trap = self.is_write_fail;
if self.is_now_trap == F::one() {
*trap_code = PROT_FAILURE_WRITE as u8;
}
}
if permissions == (PROT_READ | PROT_WRITE) {
self.is_read_fail = F::from_bool((perm & PROT_READ) == 0);
self.is_write_fail = F::from_bool((perm & PROT_WRITE) == 0);
self.is_now_trap = F::from_bool((perm & permissions) != permissions);
if self.is_read_fail == F::one() {
*trap_code = PROT_FAILURE_READ as u8;
} else if self.is_write_fail == F::one() {
*trap_code = PROT_FAILURE_WRITE as u8;
}
}
record.add_byte_lookup_event(ByteLookupEvent {
opcode: ByteOpcode::AND,
a: (perm & permissions) as u16,
b: perm,
c: permissions,
});
if self.is_now_trap == F::one() {
*is_not_trap = false;
}
}
}
impl<F: Field> TrapPageProtOperation<F> {
#[allow(clippy::too_many_arguments)]
pub fn eval<AB: SP1AirBuilder>(
builder: &mut AB,
clk_high: AB::Expr,
clk_low: AB::Expr,
page_idx: &[AB::Expr; 3],
permissions: u8,
cols: &TrapPageProtOperation<AB::Var>,
is_real: AB::Expr,
is_not_trap: &mut AB::Expr,
trap_code: &mut AB::Expr,
) {
builder.assert_bool(is_real.clone());
builder.when(is_real.clone()).assert_one(is_not_trap.clone());
builder.eval_page_prot_access_read(
clk_high.clone(),
clk_low.clone(),
page_idx,
cols.page_prot_access,
is_real.clone(),
);
let perm = cols.page_prot_access.prev_prot_bitmap;
let mut permission_and = AB::Expr::zero();
if permissions == PROT_READ {
permission_and =
(AB::Expr::one() - cols.is_read_fail) * AB::Expr::from_canonical_u8(PROT_READ);
builder.assert_zero(cols.is_write_fail);
}
if permissions == PROT_WRITE {
permission_and =
(AB::Expr::one() - cols.is_write_fail) * AB::Expr::from_canonical_u8(PROT_WRITE);
builder.assert_zero(cols.is_read_fail);
}
if permissions == (PROT_READ | PROT_WRITE) {
permission_and = permission_and.clone()
+ (AB::Expr::one() - cols.is_read_fail) * AB::Expr::from_canonical_u8(PROT_READ);
permission_and = permission_and.clone()
+ (AB::Expr::one() - cols.is_write_fail) * AB::Expr::from_canonical_u8(PROT_WRITE);
}
builder.send_byte(
AB::Expr::from_canonical_u8(ByteOpcode::AND as u8),
permission_and.clone(),
perm.into(),
AB::Expr::from_canonical_u8(permissions),
is_real.clone(),
);
builder.when_not(is_real.clone()).assert_zero(cols.is_now_trap);
builder.when_not(is_not_trap.clone()).assert_zero(cols.is_now_trap);
builder.assert_bool(cols.is_now_trap);
builder.assert_bool(cols.is_read_fail);
builder.assert_bool(cols.is_write_fail);
builder.assert_eq(
cols.is_now_trap,
cols.is_read_fail + cols.is_write_fail - cols.is_read_fail * cols.is_write_fail,
);
*is_not_trap = is_not_trap.clone() - cols.is_now_trap.into();
*trap_code = trap_code.clone()
+ cols.is_read_fail * AB::Expr::from_canonical_u64(PROT_FAILURE_READ)
+ (cols.is_now_trap - cols.is_read_fail)
* AB::Expr::from_canonical_u64(PROT_FAILURE_WRITE);
}
}
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct AddressSlicePageProtOperation<T> {
pub page_is_equal_or_adjacent: PageIsEqualOrAdjacentOperation<T>,
pub page_operations: [PageOperation<T>; 2],
pub trap_page_operations: [TrapPageProtOperation<T>; 2],
}
#[allow(clippy::too_many_arguments)]
impl<F: PrimeField32> AddressSlicePageProtOperation<F> {
#[allow(clippy::too_many_arguments)]
pub fn populate(
&mut self,
record: &mut impl ByteRecord,
start_addr: u64,
end_addr: u64,
clk: u64,
permissions: u8,
page_prot_access: &[PageProtRecord],
is_not_trap: &mut bool,
trap_code: &mut u8,
) {
if !(*is_not_trap) {
assert_eq!(page_prot_access.len(), 0);
*self = AddressSlicePageProtOperation::<F>::default();
return;
}
let start_page_idx = start_addr / (PAGE_SIZE as u64);
let end_page_idx = end_addr / (PAGE_SIZE as u64);
assert!(start_page_idx == end_page_idx || start_page_idx + 1 == end_page_idx);
self.page_operations[0].populate(record, start_addr);
self.page_operations[1].populate(record, end_addr);
assert!(!page_prot_access.is_empty());
self.trap_page_operations[0].populate(
record,
clk,
permissions,
page_prot_access[0],
is_not_trap,
trap_code,
);
if !(*is_not_trap) {
self.trap_page_operations[1] = TrapPageProtOperation::<F>::default();
self.page_is_equal_or_adjacent = PageIsEqualOrAdjacentOperation::<F>::default();
return;
}
self.page_is_equal_or_adjacent.populate(start_page_idx, end_page_idx);
if end_page_idx == start_page_idx + 1 {
assert!(page_prot_access.len() == 2);
self.trap_page_operations[1].populate(
record,
clk,
permissions,
page_prot_access[1],
is_not_trap,
trap_code,
);
} else {
self.trap_page_operations[1] = TrapPageProtOperation::<F>::default();
}
}
}
impl<F: Field> AddressSlicePageProtOperation<F> {
#[allow(clippy::too_many_arguments)]
pub fn eval<AB: SP1AirBuilder>(
builder: &mut AB,
clk_high: AB::Expr,
clk_low: AB::Expr,
start_addr: &[AB::Expr; 3],
end_addr: &[AB::Expr; 3],
permissions: u8,
cols: &AddressSlicePageProtOperation<AB::Var>,
is_not_trap: &mut AB::Expr,
trap_code: &mut AB::Expr,
) {
builder.assert_bool(is_not_trap.clone());
let start_page_idx = PageOperation::<AB::F>::eval(
builder,
start_addr,
cols.page_operations[0],
is_not_trap.clone(),
);
let end_page_idx = PageOperation::<AB::F>::eval(
builder,
&end_addr.clone(),
cols.page_operations[1],
is_not_trap.clone(),
);
TrapPageProtOperation::<AB::F>::eval(
builder,
clk_high.clone(),
clk_low.clone(),
&start_page_idx.clone(),
permissions,
&cols.trap_page_operations[0],
is_not_trap.clone(),
is_not_trap,
trap_code,
);
PageIsEqualOrAdjacentOperation::<AB::F>::eval(
builder,
start_page_idx.map(Into::into),
end_page_idx.clone().map(Into::into),
cols.page_is_equal_or_adjacent,
is_not_trap.clone(),
);
TrapPageProtOperation::<AB::F>::eval(
builder,
clk_high.clone(),
clk_low.clone(),
&end_page_idx.clone(),
permissions,
&cols.trap_page_operations[1],
cols.page_is_equal_or_adjacent.is_adjacent.into(),
is_not_trap,
trap_code,
);
}
}