sp1_core_machine/memory/instructions/
air.rs

1use std::borrow::Borrow;
2
3use p3_air::{Air, AirBuilder};
4use p3_field::AbstractField;
5use p3_matrix::Matrix;
6use sp1_stark::{air::SP1AirBuilder, Word};
7
8use crate::{
9    air::{SP1CoreAirBuilder, WordAirBuilder},
10    memory::MemoryCols,
11    operations::{BabyBearWordRangeChecker, IsZeroOperation},
12};
13use sp1_core_executor::{
14    events::MemoryAccessPosition, ByteOpcode, Opcode, DEFAULT_PC_INC, UNUSED_PC,
15};
16
17use super::{columns::MemoryInstructionsColumns, MemoryInstructionsChip};
18
19impl<AB> Air<AB> for MemoryInstructionsChip
20where
21    AB: SP1AirBuilder,
22    AB::Var: Sized,
23{
24    #[inline(never)]
25    fn eval(&self, builder: &mut AB) {
26        let main = builder.main();
27        let local = main.row_slice(0);
28        let local: &MemoryInstructionsColumns<AB::Var> = (*local).borrow();
29
30        // SAFETY: All selectors `is_lb`, `is_lbu`, `is_lh`, `is_lhu`, `is_lw`, `is_sb`, `is_sh`,
31        // `is_sw` are checked to be boolean. Each "real" row has exactly one selector
32        // turned on, as `is_real`, the sum of the eight selectors, is boolean. Therefore,
33        // the `opcode` matches the corresponding opcode.
34
35        let is_real = local.is_lb +
36            local.is_lbu +
37            local.is_lh +
38            local.is_lhu +
39            local.is_lw +
40            local.is_sb +
41            local.is_sh +
42            local.is_sw;
43
44        builder.assert_bool(local.is_lb);
45        builder.assert_bool(local.is_lbu);
46        builder.assert_bool(local.is_lh);
47        builder.assert_bool(local.is_lhu);
48        builder.assert_bool(local.is_lw);
49        builder.assert_bool(local.is_sb);
50        builder.assert_bool(local.is_sh);
51        builder.assert_bool(local.is_sw);
52        builder.assert_bool(is_real.clone());
53        builder.assert_bool(local.op_a_0);
54
55        self.eval_memory_address_and_access::<AB>(builder, local, is_real.clone());
56        self.eval_memory_load::<AB>(builder, local);
57        self.eval_memory_store::<AB>(builder, local);
58
59        let opcode = self.compute_opcode::<AB>(local);
60
61        // SAFETY: This checks the following.
62        // - `shard`, `clk` are correctly received from the CpuChip
63        // - `next_pc = pc + 4`
64        // - `num_extra_cycles = 0`
65        // - `op_a_immutable = is_sb + is_sh + is_sw`, as store instruction keeps `op_a` immutable
66        // - `is_memory = 1`
67        // - `is_syscall = 0`
68        // - `is_halt = 0`
69        // `op_a_value` when the instruction is load still has to be constrained, as well as memory
70        // opcode behavior.
71        builder.receive_instruction(
72            local.shard,
73            local.clk,
74            local.pc,
75            local.pc + AB::Expr::from_canonical_u32(DEFAULT_PC_INC),
76            AB::Expr::zero(),
77            opcode,
78            local.op_a_value,
79            local.op_b_value,
80            local.op_c_value,
81            local.op_a_0,
82            local.is_sb + local.is_sh + local.is_sw,
83            AB::Expr::one(),
84            AB::Expr::zero(),
85            AB::Expr::zero(),
86            is_real,
87        );
88    }
89}
90
91impl MemoryInstructionsChip {
92    /// Computes the opcode based on the instruction selectors.
93    pub(crate) fn compute_opcode<AB: SP1AirBuilder>(
94        &self,
95        local: &MemoryInstructionsColumns<AB::Var>,
96    ) -> AB::Expr {
97        local.is_lb * Opcode::LB.as_field::<AB::F>() +
98            local.is_lbu * Opcode::LBU.as_field::<AB::F>() +
99            local.is_lh * Opcode::LH.as_field::<AB::F>() +
100            local.is_lhu * Opcode::LHU.as_field::<AB::F>() +
101            local.is_lw * Opcode::LW.as_field::<AB::F>() +
102            local.is_sb * Opcode::SB.as_field::<AB::F>() +
103            local.is_sh * Opcode::SH.as_field::<AB::F>() +
104            local.is_sw * Opcode::SW.as_field::<AB::F>()
105    }
106
107    /// Constrains the addr_aligned, addr_offset, and addr_word memory columns.
108    ///
109    /// This method will do the following:
110    /// 1. Calculate that the unaligned address is correctly computed to be op_b.value + op_c.value.
111    /// 2. Calculate that the address offset is address % 4.
112    /// 3. Assert the validity of the aligned address given the address offset and the unaligned
113    ///    address.
114    pub(crate) fn eval_memory_address_and_access<AB: SP1CoreAirBuilder>(
115        &self,
116        builder: &mut AB,
117        local: &MemoryInstructionsColumns<AB::Var>,
118        is_real: AB::Expr,
119    ) {
120        // Send to the ALU table to verify correct calculation of addr_word.
121        builder.send_instruction(
122            AB::Expr::zero(),
123            AB::Expr::zero(),
124            AB::Expr::from_canonical_u32(UNUSED_PC),
125            AB::Expr::from_canonical_u32(UNUSED_PC + DEFAULT_PC_INC),
126            AB::Expr::zero(),
127            AB::Expr::from_canonical_u32(Opcode::ADD as u32),
128            local.addr_word,
129            local.op_b_value,
130            local.op_c_value,
131            AB::Expr::zero(),
132            AB::Expr::zero(),
133            AB::Expr::zero(),
134            AB::Expr::zero(),
135            AB::Expr::zero(),
136            is_real.clone(),
137        );
138
139        // Range check the addr_word to be a valid babybear word. Note that this will also
140        // implicitly do a byte range check on the most significant byte.
141        BabyBearWordRangeChecker::<AB::F>::range_check(
142            builder,
143            local.addr_word,
144            local.addr_word_range_checker,
145            is_real.clone(),
146        );
147
148        // Check that the 2nd and 3rd addr_word elements are bytes. We already check the most sig
149        // byte in the BabyBearWordRangeChecker, and the least sig one in the AND byte lookup below.
150        builder.slice_range_check_u8(&local.addr_word.0[1..3], is_real.clone());
151
152        // We check that `addr_word >= 32`, or `addr_word > 31` to avoid registers.
153        // Check that if the most significant bytes are zero, then the least significant byte is at
154        // least 32.
155        builder.send_byte(
156            ByteOpcode::LTU.as_field::<AB::F>(),
157            AB::Expr::one(),
158            AB::Expr::from_canonical_u8(31),
159            local.addr_word[0],
160            local.most_sig_bytes_zero.result,
161        );
162
163        // SAFETY: Check that the above interaction is only sent if one of the opcode flags is set.
164        // If `is_real = 0`, then `local.most_sig_bytes_zero.result = 0`, leading to no interaction.
165        // Note that when `is_real = 1`, due to `IsZeroOperation`,
166        // `local.most_sig_bytes_zero.result` is boolean.
167        builder.when(local.most_sig_bytes_zero.result).assert_one(is_real.clone());
168
169        // Check the most_sig_byte_zero flag.  Note that we can simply add up the three most
170        // significant bytes and check if the sum is zero.  Those bytes are going to be byte
171        // range checked, so the only way the sum is zero is if all bytes are 0.
172        IsZeroOperation::<AB::F>::eval(
173            builder,
174            local.addr_word[1] + local.addr_word[2] + local.addr_word[3],
175            local.most_sig_bytes_zero,
176            is_real.clone(),
177        );
178
179        // Evaluate the addr_offset column and offset flags.
180        self.eval_offset_value_flags(builder, local);
181
182        // Assert that reduce(addr_word) == addr_aligned + addr_ls_two_bits.
183        builder.when(is_real.clone()).assert_eq::<AB::Expr, AB::Expr>(
184            local.addr_aligned + local.addr_ls_two_bits,
185            local.addr_word.reduce::<AB>(),
186        );
187
188        // Check the correct value of addr_ls_two_bits. Note that this lookup will implicitly do a
189        // byte range check on the least sig addr byte.
190        builder.send_byte(
191            ByteOpcode::AND.as_field::<AB::F>(),
192            local.addr_ls_two_bits,
193            local.addr_word[0],
194            AB::Expr::from_canonical_u8(0b11),
195            is_real.clone(),
196        );
197
198        // For operations that require reading from memory (not registers), we need to read the
199        // value into the memory columns.
200        builder.eval_memory_access(
201            local.shard,
202            local.clk + AB::F::from_canonical_u32(MemoryAccessPosition::Memory as u32),
203            local.addr_aligned,
204            &local.memory_access,
205            is_real.clone(),
206        );
207
208        // On memory load instructions, make sure that the memory value is not changed.
209        builder
210            .when(local.is_lb + local.is_lbu + local.is_lh + local.is_lhu + local.is_lw)
211            .assert_word_eq(*local.memory_access.value(), *local.memory_access.prev_value());
212    }
213
214    /// Evaluates constraints related to loading from memory.
215    pub(crate) fn eval_memory_load<AB: SP1AirBuilder>(
216        &self,
217        builder: &mut AB,
218        local: &MemoryInstructionsColumns<AB::Var>,
219    ) {
220        // Verify the unsigned_mem_value column.
221        self.eval_unsigned_mem_value(builder, local);
222
223        // Assert that correct value of `mem_value_is_neg_not_x0`.
224        // SAFETY: If the opcode is not `lb` or `lh`, then `is_lb + is_lh = 0`, so
225        // `mem_value_is_neg_not_x0 = 0`. In the other case, `is_lb + is_lh = 1` (at most
226        // one selector is on), so `most_sig_byte` and `most_sig_bit` are correct.
227        // Since `op_a_0` is known to be correct, we can conclude that `mem_value_is_neg_not_x0` is
228        // correct for all cases, including padding rows.
229        builder.assert_eq(
230            local.mem_value_is_neg_not_x0,
231            (local.is_lb + local.is_lh) * local.most_sig_bit * (AB::Expr::one() - local.op_a_0),
232        );
233
234        // SAFETY: `is_lb + is_lh` is already constrained to be boolean.
235        // This is because at most one opcode selector can be turned on.
236        builder.send_byte(
237            ByteOpcode::MSB.as_field::<AB::F>(),
238            local.most_sig_bit,
239            local.most_sig_byte,
240            AB::Expr::zero(),
241            local.is_lb + local.is_lh,
242        );
243        builder.assert_eq(
244            local.most_sig_byte,
245            local.is_lb * local.unsigned_mem_val[0] + local.is_lh * local.unsigned_mem_val[1],
246        );
247
248        // When the memory value is negative and not writing to x0, use the SUB opcode to compute
249        // the signed value of the memory value and verify that the op_a value is correct.
250        let signed_value = Word([
251            AB::Expr::zero(),
252            AB::Expr::one() * local.is_lb,
253            AB::Expr::one() * local.is_lh,
254            AB::Expr::zero(),
255        ]);
256
257        // SAFETY: As we mentioned before, `mem_value_is_neg_not_x0` is correct in all cases and
258        // boolean in all cases.
259        builder.send_instruction(
260            AB::Expr::zero(),
261            AB::Expr::zero(),
262            AB::Expr::from_canonical_u32(UNUSED_PC),
263            AB::Expr::from_canonical_u32(UNUSED_PC + DEFAULT_PC_INC),
264            AB::Expr::zero(),
265            Opcode::SUB.as_field::<AB::F>(),
266            local.op_a_value,
267            local.unsigned_mem_val,
268            signed_value,
269            AB::Expr::zero(),
270            AB::Expr::zero(),
271            AB::Expr::zero(),
272            AB::Expr::zero(),
273            AB::Expr::zero(),
274            local.mem_value_is_neg_not_x0,
275        );
276
277        // Assert that correct value of `mem_value_is_pos_not_x0`.
278        // SAFETY: If it's a store instruction or a padding row, `mem_value_is_pos = 0`.
279        // If it's an unsigned instruction (LBU, LHU, LW), then `mem_value_is_pos = 1`.
280        // If it's signed instruction (LB, LH), then `most_sig_bit` will be constrained correctly,
281        // and same for `mem_value_is_pos`.
282        let mem_value_is_pos = (local.is_lb + local.is_lh) * (AB::Expr::one() - local.most_sig_bit) +
283            local.is_lbu +
284            local.is_lhu +
285            local.is_lw;
286        builder.assert_eq(
287            local.mem_value_is_pos_not_x0,
288            mem_value_is_pos * (AB::Expr::one() - local.op_a_0),
289        );
290
291        // When the memory value is not positive and not writing to x0, assert that op_a value is
292        // equal to the unsigned memory value.
293        builder
294            .when(local.mem_value_is_pos_not_x0)
295            .assert_word_eq(local.unsigned_mem_val, local.op_a_value);
296
297        // These two cases combine for all cases where it's a load instruction and `op_a_0 == 0`.
298        // Since the store instructions have `op_a_immutable = 1`, this completely constrains the
299        // `op_a`'s value.
300    }
301
302    /// Evaluates constraints related to storing to memory.
303    pub(crate) fn eval_memory_store<AB: SP1AirBuilder>(
304        &self,
305        builder: &mut AB,
306        local: &MemoryInstructionsColumns<AB::Var>,
307    ) {
308        // Get the memory offset flags.
309        self.eval_offset_value_flags(builder, local);
310        // Compute the offset_is_zero flag.  The other offset flags are already constrained by the
311        // method `eval_memory_address_and_access`, which is called in
312        // `eval_memory_address_and_access`.
313        let offset_is_zero =
314            AB::Expr::one() - local.ls_bits_is_one - local.ls_bits_is_two - local.ls_bits_is_three;
315
316        // Compute the expected stored value for a SB instruction.
317        let one = AB::Expr::one();
318        let a_val = local.op_a_value;
319        let mem_val = *local.memory_access.value();
320        let prev_mem_val = *local.memory_access.prev_value();
321        let sb_expected_stored_value = Word([
322            a_val[0] * offset_is_zero.clone() +
323                (one.clone() - offset_is_zero.clone()) * prev_mem_val[0],
324            a_val[0] * local.ls_bits_is_one +
325                (one.clone() - local.ls_bits_is_one) * prev_mem_val[1],
326            a_val[0] * local.ls_bits_is_two +
327                (one.clone() - local.ls_bits_is_two) * prev_mem_val[2],
328            a_val[0] * local.ls_bits_is_three +
329                (one.clone() - local.ls_bits_is_three) * prev_mem_val[3],
330        ]);
331        builder
332            .when(local.is_sb)
333            .assert_word_eq(mem_val.map(|x| x.into()), sb_expected_stored_value);
334
335        // When the instruction is SH, make sure both offset one and three are off.
336        builder.when(local.is_sh).assert_zero(local.ls_bits_is_one + local.ls_bits_is_three);
337
338        // When the instruction is SW, ensure that the offset is 0.
339        builder.when(local.is_sw).assert_one(offset_is_zero.clone());
340
341        // Compute the expected stored value for a SH instruction.
342        let a_is_lower_half = offset_is_zero;
343        let a_is_upper_half = local.ls_bits_is_two;
344        let sh_expected_stored_value = Word([
345            a_val[0] * a_is_lower_half.clone() +
346                (one.clone() - a_is_lower_half.clone()) * prev_mem_val[0],
347            a_val[1] * a_is_lower_half.clone() + (one.clone() - a_is_lower_half) * prev_mem_val[1],
348            a_val[0] * a_is_upper_half + (one.clone() - a_is_upper_half) * prev_mem_val[2],
349            a_val[1] * a_is_upper_half + (one.clone() - a_is_upper_half) * prev_mem_val[3],
350        ]);
351        builder
352            .when(local.is_sh)
353            .assert_word_eq(mem_val.map(|x| x.into()), sh_expected_stored_value);
354
355        // When the instruction is SW, just use the word without masking.
356        builder
357            .when(local.is_sw)
358            .assert_word_eq(mem_val.map(|x| x.into()), a_val.map(|x| x.into()));
359    }
360
361    /// This function is used to evaluate the unsigned memory value for the load memory
362    /// instructions.
363    pub(crate) fn eval_unsigned_mem_value<AB: SP1AirBuilder>(
364        &self,
365        builder: &mut AB,
366        local: &MemoryInstructionsColumns<AB::Var>,
367    ) {
368        let mem_val = *local.memory_access.value();
369
370        // Compute the offset_is_zero flag.  The other offset flags are already constrained by the
371        // method `eval_memory_address_and_access`, which is called in
372        // `eval_memory_address_and_access`.
373        let offset_is_zero =
374            AB::Expr::one() - local.ls_bits_is_one - local.ls_bits_is_two - local.ls_bits_is_three;
375
376        // Compute the byte value.
377        let mem_byte = mem_val[0] * offset_is_zero.clone() +
378            mem_val[1] * local.ls_bits_is_one +
379            mem_val[2] * local.ls_bits_is_two +
380            mem_val[3] * local.ls_bits_is_three;
381        let byte_value = Word::extend_expr::<AB>(mem_byte.clone());
382
383        // When the instruction is LB or LBU, just use the lower byte.
384        builder
385            .when(local.is_lb + local.is_lbu)
386            .assert_word_eq(byte_value, local.unsigned_mem_val.map(|x| x.into()));
387
388        // When the instruction is LH or LHU, ensure that offset is either zero or two.
389        builder
390            .when(local.is_lh + local.is_lhu)
391            .assert_zero(local.ls_bits_is_one + local.ls_bits_is_three);
392
393        // When the instruction is LW, ensure that the offset is zero.
394        builder.when(local.is_lw).assert_one(offset_is_zero.clone());
395
396        let use_lower_half = offset_is_zero;
397        let use_upper_half = local.ls_bits_is_two;
398        let half_value = Word([
399            use_lower_half.clone() * mem_val[0] + use_upper_half * mem_val[2],
400            use_lower_half * mem_val[1] + use_upper_half * mem_val[3],
401            AB::Expr::zero(),
402            AB::Expr::zero(),
403        ]);
404        builder
405            .when(local.is_lh + local.is_lhu)
406            .assert_word_eq(half_value, local.unsigned_mem_val.map(|x| x.into()));
407
408        // When the instruction is LW, just use the word.
409        builder.when(local.is_lw).assert_word_eq(mem_val, local.unsigned_mem_val);
410    }
411
412    /// Evaluates the offset value flags.
413    pub(crate) fn eval_offset_value_flags<AB: SP1AirBuilder>(
414        &self,
415        builder: &mut AB,
416        local: &MemoryInstructionsColumns<AB::Var>,
417    ) {
418        let offset_is_zero =
419            AB::Expr::one() - local.ls_bits_is_one - local.ls_bits_is_two - local.ls_bits_is_three;
420
421        // Assert that the value flags are boolean
422        builder.assert_bool(local.ls_bits_is_one);
423        builder.assert_bool(local.ls_bits_is_two);
424        builder.assert_bool(local.ls_bits_is_three);
425
426        // Assert that only one of the value flags is true
427        builder.assert_one(
428            offset_is_zero.clone() +
429                local.ls_bits_is_one +
430                local.ls_bits_is_two +
431                local.ls_bits_is_three,
432        );
433
434        // Assert that the correct value flag is set
435        // SAFETY: Due to the constraints here, at most one of the four flags can be turned on
436        // (non-zero). As their sum is constrained to be 1, the only possibility is that
437        // exactly one flag is on, with value 1.
438        builder.when(offset_is_zero).assert_zero(local.addr_ls_two_bits);
439        builder.when(local.ls_bits_is_one).assert_one(local.addr_ls_two_bits);
440        builder.when(local.ls_bits_is_two).assert_eq(local.addr_ls_two_bits, AB::Expr::two());
441        builder
442            .when(local.ls_bits_is_three)
443            .assert_eq(local.addr_ls_two_bits, AB::Expr::from_canonical_u8(3));
444    }
445}