sp1_core_machine/control_flow/jump/
air.rs

1use std::borrow::Borrow;
2
3use p3_air::{Air, AirBuilder};
4use p3_field::AbstractField;
5use p3_matrix::Matrix;
6use sp1_core_executor::{Opcode, DEFAULT_PC_INC, UNUSED_PC};
7use sp1_stark::air::{BaseAirBuilder, SP1AirBuilder};
8
9use crate::operations::BabyBearWordRangeChecker;
10
11use super::{JumpChip, JumpColumns};
12
13impl<AB> Air<AB> for JumpChip
14where
15    AB: SP1AirBuilder,
16    AB::Var: Sized,
17{
18    #[inline(never)]
19    fn eval(&self, builder: &mut AB) {
20        let main = builder.main();
21        let local = main.row_slice(0);
22        let local: &JumpColumns<AB::Var> = (*local).borrow();
23
24        // SAFETY: All selectors `is_jal`, `is_jalr` are checked to be boolean.
25        // Each "real" row has exactly one selector turned on, as `is_real = is_jal + is_jalr` is
26        // boolean. Therefore, the `opcode` matches the corresponding opcode.
27        builder.assert_bool(local.is_jal);
28        builder.assert_bool(local.is_jalr);
29        let is_real = local.is_jal + local.is_jalr;
30        builder.assert_bool(is_real.clone());
31
32        let opcode = local.is_jal * Opcode::JAL.as_field::<AB::F>() +
33            local.is_jalr * Opcode::JALR.as_field::<AB::F>();
34
35        // SAFETY: This checks the following.
36        // - `num_extra_cycles = 0`
37        // - `op_a_immutable = 0`
38        // - `is_memory = 0`
39        // - `is_syscall = 0`
40        // - `is_halt = 0`
41        // `next_pc` and `op_a_value` still has to be constrained, and this is done below.
42        builder.receive_instruction(
43            AB::Expr::zero(),
44            AB::Expr::zero(),
45            local.pc.reduce::<AB>(),
46            local.next_pc.reduce::<AB>(),
47            AB::Expr::zero(),
48            opcode,
49            local.op_a_value,
50            local.op_b_value,
51            local.op_c_value,
52            local.op_a_0,
53            AB::Expr::zero(),
54            AB::Expr::zero(),
55            AB::Expr::zero(),
56            AB::Expr::zero(),
57            is_real.clone(),
58        );
59
60        // Verify that the local.pc + 4 is saved in op_a for both jump instructions.
61        // When op_a is set to register X0, the RISC-V spec states that the jump instruction will
62        // not have a return destination address (it is effectively a GOTO command).  In this case,
63        // we shouldn't verify the return address.
64        builder.when(is_real.clone()).when_not(local.op_a_0).assert_eq(
65            local.op_a_value.reduce::<AB>(),
66            local.pc.reduce::<AB>() + AB::F::from_canonical_u32(DEFAULT_PC_INC),
67        );
68
69        // Range check op_a, pc, and next_pc.
70        // SAFETY: `is_real` is already checked to be boolean.
71        // `op_a_value` is checked to be a valid word, as it matches the one in the CpuChip.
72        // In the CpuChip's `eval_registers`, it's checked that this is a valid word.
73        // Combined with the `op_a_value = pc + 4` check above when `op_a_0 = 0`, this fully
74        // constrains `op_a_value`.
75        BabyBearWordRangeChecker::<AB::F>::range_check(
76            builder,
77            local.op_a_value,
78            local.op_a_range_checker,
79            is_real.clone(),
80        );
81        // SAFETY: `is_real` is already checked to be boolean.
82        // `local.pc`, `local.next_pc` are checked to a valid word when relevant.
83        // This is due to the ADD ALU table checking all inputs and outputs are valid words.
84        // This is done when the `AddOperation` is invoked in the ADD ALU table.
85        BabyBearWordRangeChecker::<AB::F>::range_check(
86            builder,
87            local.pc,
88            local.pc_range_checker,
89            is_real.clone(),
90        );
91        BabyBearWordRangeChecker::<AB::F>::range_check(
92            builder,
93            local.next_pc,
94            local.next_pc_range_checker,
95            is_real,
96        );
97
98        // We now constrain `next_pc`.
99
100        // Verify that the new pc is calculated correctly for JAL instructions.
101        // SAFETY: `is_jal` is boolean, and zero for padding rows.
102        builder.send_instruction(
103            AB::Expr::zero(),
104            AB::Expr::zero(),
105            AB::Expr::from_canonical_u32(UNUSED_PC),
106            AB::Expr::from_canonical_u32(UNUSED_PC + DEFAULT_PC_INC),
107            AB::Expr::zero(),
108            AB::Expr::from_canonical_u32(Opcode::ADD as u32),
109            local.next_pc,
110            local.pc,
111            local.op_b_value,
112            AB::Expr::zero(),
113            AB::Expr::zero(),
114            AB::Expr::zero(),
115            AB::Expr::zero(),
116            AB::Expr::zero(),
117            local.is_jal,
118        );
119
120        // Verify that the new pc is calculated correctly for JALR instructions.
121        // SAFETY: `is_jalr` is boolean, and zero for padding rows.
122        builder.send_instruction(
123            AB::Expr::zero(),
124            AB::Expr::zero(),
125            AB::Expr::from_canonical_u32(UNUSED_PC),
126            AB::Expr::from_canonical_u32(UNUSED_PC + DEFAULT_PC_INC),
127            AB::Expr::zero(),
128            AB::Expr::from_canonical_u32(Opcode::ADD as u32),
129            local.next_pc,
130            local.op_b_value,
131            local.op_c_value,
132            AB::Expr::zero(),
133            AB::Expr::zero(),
134            AB::Expr::zero(),
135            AB::Expr::zero(),
136            AB::Expr::zero(),
137            local.is_jalr,
138        );
139    }
140}