sp1-core-machine 6.2.0

SP1 core machine definitions
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
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};

/// A set of columns needed to compute the page_idx from an address.
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageOperation<T> {
    /// Split that least significant limb into a 4 bit limb and a 12 bit limb.
    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);
    }

    /// Evaluate the calculation of the page idx from the address.
    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());

        // Check that the least significant address limb is correctly decomposed to the 4 bit limb
        // and the 12 bit limb.
        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)),
        );
        // Range check the limbs.
        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()]
    }
}

/// A set of columns needed to retrieve the page permissions from an address.
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageProtOperation<T> {
    /// The page operation to calculate page idx from address.
    pub page_op: PageOperation<T>,

    /// The page prot access columns.
    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(),
        );
    }
}

/// A set of columns needed to check if two page indices are equal or adjacent.
#[derive(AlignedBorrow, Default, Debug, Clone, Copy, StructReflection)]
#[repr(C)]
pub struct PageIsEqualOrAdjacentOperation<T> {
    pub is_overflow: T,

    // Bool flag that is set to 0 if equal, 1 if adjacent.
    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");
        }

        // Check that the bottom 20 bits of the next page are 0, if so we know there's an overflow
        // into the third limb
        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);

        // Check for overflow.
        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);

        // Combine the 1st and 2nd limbs.  The 1st limb is 4 bits and the 2nd limb is 16 bits.
        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));

        // First check for the case when they are equal.
        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());

        // Now check if they are adjacent.

        // First check to see is_adjacent == 1, then is_real == 1.
        // This is so that we don't need to check for is_real when is_adjacent == 1.
        builder.when(cols.is_adjacent).assert_one(is_real.clone());

        let mut is_adjacent_builder = builder.when(cols.is_adjacent);

        // Find out what each limb's relationship should be.
        // If !is_overflow -> (20bit limbs are adjacent, 3rd limb is equal).
        // if is_overflow -> (20bit limbs are at boundary, 3rd limb is adjacent).

        // Check that first page bottom 20 bits are adjacent to second page bottom 20 bits
        is_adjacent_builder
            .when_not(cols.is_overflow)
            .assert_eq(curr_page_20_bits.clone() + AB::Expr::one(), next_page_20_bits.clone());

        // Check that top limbs are equal
        is_adjacent_builder
            .when_not(cols.is_overflow)
            .assert_eq(curr_page_idx[2].clone(), next_page_idx[2].clone());

        // Check that first page bottom 20 bits are maxed out
        is_adjacent_builder
            .when(cols.is_overflow)
            .assert_eq(curr_page_20_bits, AB::Expr::from_canonical_u32((1 << 20) - 1));

        // Check that second page bottom 20 bits are 0
        is_adjacent_builder.when(cols.is_overflow).assert_eq(next_page_20_bits, AB::Expr::zero());

        // Check that top limb (top 16 bits) of second page is 1 more than top limb of first page
        is_adjacent_builder
            .when(cols.is_overflow)
            .assert_eq(curr_page_idx[2].clone() + AB::Expr::one(), next_page_idx[2].clone());
    }
}

/// A set of columns needed to check the page prot permissions and return the trap code.
#[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);
    }
}

/// A set of columns needed to check the page prot permissions for a range of addrs.
/// This operation only supports an addr range that spans at most 2 pages.
#[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());

        // Populate the first slice.
        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,
        );
    }
}