1use slop_air::AirBuilder;
2use slop_algebra::{AbstractField, Field, PrimeField32};
3use sp1_core_executor::{
4 events::{ByteLookupEvent, ByteRecord, PageProtRecord},
5 ByteOpcode,
6};
7use sp1_derive::AlignedBorrow;
8use sp1_hypercube::air::{BaseAirBuilder, SP1AirBuilder};
9use sp1_primitives::consts::{
10 split_page_idx, PAGE_SIZE, PROT_FAILURE_READ, PROT_FAILURE_WRITE, PROT_READ, PROT_WRITE,
11};
12use struct_reflection::{StructReflection, StructReflectionHelper};
13
14use crate::{air::MemoryAirBuilder, memory::PageProtAccessCols};
15
16#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
18#[repr(C)]
19pub struct PageOperation<T> {
20 pub addr_4_bits: T,
22 pub addr_12_bits: T,
23}
24
25impl<F: Field> PageOperation<F> {
26 pub fn populate(&mut self, record: &mut impl ByteRecord, addr: u64) {
27 let addr_12_bits: u16 = (addr & 0xFFF).try_into().unwrap();
28 let addr_4_bits: u16 = ((addr >> 12) & 0xF).try_into().unwrap();
29
30 self.addr_12_bits = F::from_canonical_u16(addr_12_bits);
31 self.addr_4_bits = F::from_canonical_u16(addr_4_bits);
32
33 record.add_bit_range_check(addr_12_bits, 12);
34 record.add_bit_range_check(addr_4_bits, 4);
35 }
36
37 pub fn eval<AB: SP1AirBuilder>(
39 builder: &mut AB,
40 addr: &[AB::Expr; 3],
41 cols: PageOperation<AB::Var>,
42 is_real: AB::Expr,
43 ) -> [AB::Expr; 3] {
44 builder.assert_bool(is_real.clone());
45
46 builder.when(is_real.clone()).assert_eq(
49 addr[0].clone(),
50 cols.addr_12_bits + cols.addr_4_bits * (AB::Expr::from_canonical_u32(1 << 12)),
51 );
52 builder.send_byte(
54 AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
55 cols.addr_4_bits.into(),
56 AB::Expr::from_canonical_u32(4),
57 AB::Expr::zero(),
58 is_real.clone(),
59 );
60 builder.send_byte(
61 AB::Expr::from_canonical_u32(ByteOpcode::Range as u32),
62 cols.addr_12_bits.into(),
63 AB::Expr::from_canonical_u32(12),
64 AB::Expr::zero(),
65 is_real.clone(),
66 );
67
68 [cols.addr_4_bits.into(), addr[1].clone(), addr[2].clone()]
69 }
70}
71
72#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
74#[repr(C)]
75pub struct PageProtOperation<T> {
76 pub page_op: PageOperation<T>,
78
79 pub page_prot_access: PageProtAccessCols<T>,
81}
82
83impl<F: PrimeField32> PageProtOperation<F> {
84 pub fn populate(
85 &mut self,
86 record: &mut impl ByteRecord,
87 addr: u64,
88 clk: u64,
89 previous_page_prot_access: &PageProtRecord,
90 ) {
91 self.page_op.populate(record, addr);
92
93 assert!(previous_page_prot_access.timestamp < clk);
94 self.page_prot_access.populate(previous_page_prot_access, clk, record);
95 }
96}
97
98impl<F: Field> PageProtOperation<F> {
99 pub fn eval<AB: SP1AirBuilder>(
100 builder: &mut AB,
101 clk_high: AB::Expr,
102 clk_low: AB::Expr,
103 addr: &[AB::Expr; 3],
104 cols: PageProtOperation<AB::Var>,
105 is_real: AB::Expr,
106 ) {
107 builder.assert_bool(is_real.clone());
108
109 let page_idx = PageOperation::<AB::F>::eval(builder, addr, cols.page_op, is_real.clone());
110
111 builder.eval_page_prot_access_read(
112 clk_high,
113 clk_low,
114 &page_idx,
115 cols.page_prot_access,
116 is_real.clone(),
117 );
118 }
119}
120
121#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
123#[repr(C)]
124pub struct PageIsEqualOrAdjacentOperation<T> {
125 pub is_overflow: T,
126
127 pub is_adjacent: T,
129}
130
131impl<F: Field> PageIsEqualOrAdjacentOperation<F> {
132 pub fn populate(&mut self, curr_page_idx: u64, next_page_idx: u64) {
133 if curr_page_idx == next_page_idx {
134 self.is_adjacent = F::zero();
135 } else if curr_page_idx + 1 == next_page_idx {
136 self.is_adjacent = F::one();
137 } else {
138 panic!("curr_page_idx and next_page_idx are not equal or adjacent");
139 }
140
141 let next_page_limbs = split_page_idx(next_page_idx);
144 let next_page_20_bits = next_page_limbs[0] as u64 + ((next_page_limbs[1] as u64) << 4);
145
146 self.is_overflow = F::from_bool(next_page_20_bits == 0);
148 }
149
150 pub fn eval<AB: SP1AirBuilder>(
151 builder: &mut AB,
152 curr_page_idx: [AB::Expr; 3],
153 next_page_idx: [AB::Expr; 3],
154 cols: PageIsEqualOrAdjacentOperation<AB::Var>,
155 is_real: AB::Expr,
156 ) {
157 builder.assert_bool(is_real.clone());
158 builder.assert_bool(cols.is_adjacent);
159 builder.assert_bool(cols.is_overflow);
160
161 let curr_page_20_bits = curr_page_idx[0].clone()
163 + curr_page_idx[1].clone() * (AB::Expr::from_canonical_u32(1 << 4));
164 let next_page_20_bits = next_page_idx[0].clone()
165 + next_page_idx[1].clone() * (AB::Expr::from_canonical_u32(1 << 4));
166
167 builder
169 .when(is_real.clone())
170 .when_not(cols.is_adjacent)
171 .assert_eq(curr_page_20_bits.clone(), next_page_20_bits.clone());
172 builder
173 .when(is_real.clone())
174 .when_not(cols.is_adjacent)
175 .assert_eq(curr_page_idx[2].clone(), next_page_idx[2].clone());
176
177 builder.when(cols.is_adjacent).assert_one(is_real.clone());
182
183 let mut is_adjacent_builder = builder.when(cols.is_adjacent);
184
185 is_adjacent_builder
191 .when_not(cols.is_overflow)
192 .assert_eq(curr_page_20_bits.clone() + AB::Expr::one(), next_page_20_bits.clone());
193
194 is_adjacent_builder
196 .when_not(cols.is_overflow)
197 .assert_eq(curr_page_idx[2].clone(), next_page_idx[2].clone());
198
199 is_adjacent_builder
201 .when(cols.is_overflow)
202 .assert_eq(curr_page_20_bits, AB::Expr::from_canonical_u32((1 << 20) - 1));
203
204 is_adjacent_builder.when(cols.is_overflow).assert_eq(next_page_20_bits, AB::Expr::zero());
206
207 is_adjacent_builder
209 .when(cols.is_overflow)
210 .assert_eq(curr_page_idx[2].clone() + AB::Expr::one(), next_page_idx[2].clone());
211 }
212}
213
214#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
216#[repr(C)]
217pub struct TrapPageProtOperation<T> {
218 pub page_prot_access: PageProtAccessCols<T>,
219 pub is_read_fail: T,
220 pub is_write_fail: T,
221 pub is_now_trap: T,
222}
223
224#[allow(clippy::too_many_arguments)]
225impl<F: PrimeField32> TrapPageProtOperation<F> {
226 #[allow(clippy::too_many_arguments)]
227 pub fn populate(
228 &mut self,
229 record: &mut impl ByteRecord,
230 clk: u64,
231 permissions: u8,
232 page_prot_access: PageProtRecord,
233 is_not_trap: &mut bool,
234 trap_code: &mut u8,
235 ) {
236 assert!(*is_not_trap);
237 self.page_prot_access.populate(&page_prot_access, clk, record);
238 let perm = page_prot_access.page_prot;
239 if permissions == PROT_READ {
240 self.is_read_fail = F::from_bool((perm & PROT_READ) == 0);
241 self.is_write_fail = F::zero();
242 self.is_now_trap = self.is_read_fail;
243 if self.is_now_trap == F::one() {
244 *trap_code = PROT_FAILURE_READ as u8;
245 }
246 }
247 if permissions == PROT_WRITE {
248 self.is_read_fail = F::zero();
249 self.is_write_fail = F::from_bool((perm & PROT_WRITE) == 0);
250 self.is_now_trap = self.is_write_fail;
251 if self.is_now_trap == F::one() {
252 *trap_code = PROT_FAILURE_WRITE as u8;
253 }
254 }
255 if permissions == (PROT_READ | PROT_WRITE) {
256 self.is_read_fail = F::from_bool((perm & PROT_READ) == 0);
257 self.is_write_fail = F::from_bool((perm & PROT_WRITE) == 0);
258 self.is_now_trap = F::from_bool((perm & permissions) != permissions);
259 if self.is_read_fail == F::one() {
260 *trap_code = PROT_FAILURE_READ as u8;
261 } else if self.is_write_fail == F::one() {
262 *trap_code = PROT_FAILURE_WRITE as u8;
263 }
264 }
265 record.add_byte_lookup_event(ByteLookupEvent {
266 opcode: ByteOpcode::AND,
267 a: (perm & permissions) as u16,
268 b: perm,
269 c: permissions,
270 });
271 if self.is_now_trap == F::one() {
272 *is_not_trap = false;
273 }
274 }
275}
276
277impl<F: Field> TrapPageProtOperation<F> {
278 #[allow(clippy::too_many_arguments)]
279 pub fn eval<AB: SP1AirBuilder>(
280 builder: &mut AB,
281 clk_high: AB::Expr,
282 clk_low: AB::Expr,
283 page_idx: &[AB::Expr; 3],
284 permissions: u8,
285 cols: &TrapPageProtOperation<AB::Var>,
286 is_real: AB::Expr,
287 is_not_trap: &mut AB::Expr,
288 trap_code: &mut AB::Expr,
289 ) {
290 builder.assert_bool(is_real.clone());
291 builder.when(is_real.clone()).assert_one(is_not_trap.clone());
292 builder.eval_page_prot_access_read(
293 clk_high.clone(),
294 clk_low.clone(),
295 page_idx,
296 cols.page_prot_access,
297 is_real.clone(),
298 );
299
300 let perm = cols.page_prot_access.prev_prot_bitmap;
301
302 let mut permission_and = AB::Expr::zero();
303
304 if permissions == PROT_READ {
305 permission_and =
306 (AB::Expr::one() - cols.is_read_fail) * AB::Expr::from_canonical_u8(PROT_READ);
307 builder.assert_zero(cols.is_write_fail);
308 }
309 if permissions == PROT_WRITE {
310 permission_and =
311 (AB::Expr::one() - cols.is_write_fail) * AB::Expr::from_canonical_u8(PROT_WRITE);
312 builder.assert_zero(cols.is_read_fail);
313 }
314 if permissions == (PROT_READ | PROT_WRITE) {
315 permission_and = permission_and.clone()
316 + (AB::Expr::one() - cols.is_read_fail) * AB::Expr::from_canonical_u8(PROT_READ);
317 permission_and = permission_and.clone()
318 + (AB::Expr::one() - cols.is_write_fail) * AB::Expr::from_canonical_u8(PROT_WRITE);
319 }
320
321 builder.send_byte(
322 AB::Expr::from_canonical_u8(ByteOpcode::AND as u8),
323 permission_and.clone(),
324 perm.into(),
325 AB::Expr::from_canonical_u8(permissions),
326 is_real.clone(),
327 );
328
329 builder.when_not(is_real.clone()).assert_zero(cols.is_now_trap);
330 builder.when_not(is_not_trap.clone()).assert_zero(cols.is_now_trap);
331 builder.assert_bool(cols.is_now_trap);
332 builder.assert_bool(cols.is_read_fail);
333 builder.assert_bool(cols.is_write_fail);
334 builder.assert_eq(
335 cols.is_now_trap,
336 cols.is_read_fail + cols.is_write_fail - cols.is_read_fail * cols.is_write_fail,
337 );
338
339 *is_not_trap = is_not_trap.clone() - cols.is_now_trap.into();
340 *trap_code = trap_code.clone()
341 + cols.is_read_fail * AB::Expr::from_canonical_u64(PROT_FAILURE_READ)
342 + (cols.is_now_trap - cols.is_read_fail)
343 * AB::Expr::from_canonical_u64(PROT_FAILURE_WRITE);
344 }
345}
346
347#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
350#[repr(C)]
351pub struct AddressSlicePageProtOperation<T> {
352 pub page_is_equal_or_adjacent: PageIsEqualOrAdjacentOperation<T>,
353 pub page_operations: [PageOperation<T>; 2],
354 pub trap_page_operations: [TrapPageProtOperation<T>; 2],
355}
356
357#[allow(clippy::too_many_arguments)]
358impl<F: PrimeField32> AddressSlicePageProtOperation<F> {
359 #[allow(clippy::too_many_arguments)]
360 pub fn populate(
361 &mut self,
362 record: &mut impl ByteRecord,
363 start_addr: u64,
364 end_addr: u64,
365 clk: u64,
366 permissions: u8,
367 page_prot_access: &[PageProtRecord],
368 is_not_trap: &mut bool,
369 trap_code: &mut u8,
370 ) {
371 if !(*is_not_trap) {
372 assert_eq!(page_prot_access.len(), 0);
373 *self = AddressSlicePageProtOperation::<F>::default();
374 return;
375 }
376
377 let start_page_idx = start_addr / (PAGE_SIZE as u64);
378 let end_page_idx = end_addr / (PAGE_SIZE as u64);
379 assert!(start_page_idx == end_page_idx || start_page_idx + 1 == end_page_idx);
380
381 self.page_operations[0].populate(record, start_addr);
382 self.page_operations[1].populate(record, end_addr);
383
384 assert!(!page_prot_access.is_empty());
385
386 self.trap_page_operations[0].populate(
388 record,
389 clk,
390 permissions,
391 page_prot_access[0],
392 is_not_trap,
393 trap_code,
394 );
395
396 if !(*is_not_trap) {
397 self.trap_page_operations[1] = TrapPageProtOperation::<F>::default();
398 self.page_is_equal_or_adjacent = PageIsEqualOrAdjacentOperation::<F>::default();
399 return;
400 }
401
402 self.page_is_equal_or_adjacent.populate(start_page_idx, end_page_idx);
403
404 if end_page_idx == start_page_idx + 1 {
405 assert!(page_prot_access.len() == 2);
406 self.trap_page_operations[1].populate(
407 record,
408 clk,
409 permissions,
410 page_prot_access[1],
411 is_not_trap,
412 trap_code,
413 );
414 } else {
415 self.trap_page_operations[1] = TrapPageProtOperation::<F>::default();
416 }
417 }
418}
419
420impl<F: Field> AddressSlicePageProtOperation<F> {
421 #[allow(clippy::too_many_arguments)]
422 pub fn eval<AB: SP1AirBuilder>(
423 builder: &mut AB,
424 clk_high: AB::Expr,
425 clk_low: AB::Expr,
426 start_addr: &[AB::Expr; 3],
427 end_addr: &[AB::Expr; 3],
428 permissions: u8,
429 cols: &AddressSlicePageProtOperation<AB::Var>,
430 is_not_trap: &mut AB::Expr,
431 trap_code: &mut AB::Expr,
432 ) {
433 builder.assert_bool(is_not_trap.clone());
434
435 let start_page_idx = PageOperation::<AB::F>::eval(
436 builder,
437 start_addr,
438 cols.page_operations[0],
439 is_not_trap.clone(),
440 );
441
442 let end_page_idx = PageOperation::<AB::F>::eval(
443 builder,
444 &end_addr.clone(),
445 cols.page_operations[1],
446 is_not_trap.clone(),
447 );
448
449 TrapPageProtOperation::<AB::F>::eval(
450 builder,
451 clk_high.clone(),
452 clk_low.clone(),
453 &start_page_idx.clone(),
454 permissions,
455 &cols.trap_page_operations[0],
456 is_not_trap.clone(),
457 is_not_trap,
458 trap_code,
459 );
460
461 PageIsEqualOrAdjacentOperation::<AB::F>::eval(
462 builder,
463 start_page_idx.map(Into::into),
464 end_page_idx.clone().map(Into::into),
465 cols.page_is_equal_or_adjacent,
466 is_not_trap.clone(),
467 );
468
469 TrapPageProtOperation::<AB::F>::eval(
470 builder,
471 clk_high.clone(),
472 clk_low.clone(),
473 &end_page_idx.clone(),
474 permissions,
475 &cols.trap_page_operations[1],
476 cols.page_is_equal_or_adjacent.is_adjacent.into(),
477 is_not_trap,
478 trap_code,
479 );
480 }
481}