Skip to main content

sp1_core_machine/operations/
slt.rs

1use itertools::izip;
2use serde::{Deserialize, Serialize};
3use sp1_core_executor::events::ByteRecord;
4use sp1_hypercube::{air::SP1AirBuilder, Word};
5use struct_reflection::{StructReflection, StructReflectionHelper};
6
7use slop_air::AirBuilder;
8use slop_algebra::{AbstractField, Field};
9use sp1_derive::{AlignedBorrow, InputExpr, InputParams, IntoShape, SP1OperationBuilder};
10use sp1_primitives::consts::{u64_to_u16_limbs, WORD_SIZE};
11
12use crate::air::{SP1Operation, SP1OperationBuilder};
13
14use super::{U16CompareOperation, U16CompareOperationInput, U16MSBOperation, U16MSBOperationInput};
15
16#[derive(
17    AlignedBorrow,
18    StructReflection,
19    Default,
20    Debug,
21    Clone,
22    Copy,
23    Serialize,
24    Deserialize,
25    IntoShape,
26    SP1OperationBuilder,
27)]
28#[repr(C)]
29pub struct LtOperationUnsigned<T> {
30    /// Instance of the U16CompareOperation.
31    pub u16_compare_operation: U16CompareOperation<T>,
32    /// Boolean flag to indicate which limb pair differs if the operands are not equal.
33    pub u16_flags: [T; WORD_SIZE],
34    /// An inverse of differing limb if b_comp != c_comp.
35    pub not_eq_inv: T,
36    /// The comparison limbs to be looked up.
37    pub comparison_limbs: [T; 2],
38}
39
40#[derive(
41    AlignedBorrow,
42    StructReflection,
43    Default,
44    Debug,
45    Clone,
46    Copy,
47    Serialize,
48    Deserialize,
49    IntoShape,
50    SP1OperationBuilder,
51)]
52#[repr(C)]
53pub struct LtOperationSigned<T> {
54    /// The result of the SLTU operation.
55    pub result: LtOperationUnsigned<T>,
56    /// The most significant bit of operand b if `is_signed` is true.
57    pub b_msb: U16MSBOperation<T>,
58    /// The most significant bit of operand c if `is_signed` is true.
59    pub c_msb: U16MSBOperation<T>,
60}
61
62impl<F: Field> LtOperationSigned<F> {
63    pub fn populate_signed(
64        &mut self,
65        record: &mut impl ByteRecord,
66        a_u64: u64,
67        b_u64: u64,
68        c_u64: u64,
69        is_signed: bool,
70    ) {
71        let b_comp = u64_to_u16_limbs(b_u64);
72        let c_comp = u64_to_u16_limbs(c_u64);
73        if is_signed {
74            self.b_msb.populate_msb(record, b_comp[3]);
75            self.c_msb.populate_msb(record, c_comp[3]);
76            self.result.populate_unsigned(record, a_u64, b_u64 ^ (1 << 63), c_u64 ^ (1 << 63));
77        } else {
78            self.b_msb.msb = F::zero();
79            self.c_msb.msb = F::zero();
80            self.result.populate_unsigned(record, a_u64, b_u64, c_u64);
81        }
82    }
83
84    /// Evaluate the signed LT operation.
85    /// Assumes that `b`, `c` are valid `Word`s of u16 limbs.
86    /// Constrains that `is_signed` is boolean.
87    /// Constrains that `is_real` is boolean.
88    /// If `is_real` is true, constrains that the result is the signed LT of `b` and `c`.
89    pub fn eval_lt_signed<AB>(
90        builder: &mut AB,
91        b: Word<AB::Expr>,
92        c: Word<AB::Expr>,
93        cols: LtOperationSigned<AB::Var>,
94        is_signed: AB::Expr,
95        is_real: AB::Expr,
96    ) where
97        AB: SP1AirBuilder
98            + SP1OperationBuilder<U16CompareOperation<<AB as AirBuilder>::F>>
99            + SP1OperationBuilder<U16MSBOperation<<AB as AirBuilder>::F>>
100            + SP1OperationBuilder<LtOperationUnsigned<<AB as AirBuilder>::F>>,
101    {
102        builder.assert_bool(is_signed.clone());
103        builder.assert_bool(is_real.clone());
104        // If `is_real` is false, assert that `is_signed` is zero.
105        builder.when_not(is_real.clone()).assert_zero(is_signed.clone());
106
107        // Constrain the MSB of `b` and `c` if `is_signed` is true.
108        // This will be used to determine the sign of `b` and `c`.
109        <U16MSBOperation<AB::F> as SP1Operation<AB>>::eval(
110            builder,
111            U16MSBOperationInput::<AB>::new(
112                b.0[WORD_SIZE - 1].clone(),
113                cols.b_msb,
114                is_signed.clone(),
115            ),
116        );
117        <U16MSBOperation<AB::F> as SP1Operation<AB>>::eval(
118            builder,
119            U16MSBOperationInput::<AB>::new(
120                c.0[WORD_SIZE - 1].clone(),
121                cols.c_msb,
122                is_signed.clone(),
123            ),
124        );
125
126        // Constrain `b` and `c` to be considered positive if `is_signed` is false.
127        builder.when_not(is_signed.clone()).assert_zero(cols.b_msb.msb);
128        builder.when_not(is_signed.clone()).assert_zero(cols.c_msb.msb);
129
130        let mut b_compare = b;
131        let mut c_compare = c;
132
133        let base = AB::Expr::from_canonical_u32(1 << 16);
134
135        // XOR `1 << 63` to `b` and `c` if `is_signed` is true.
136        // If `is_signed` is false, the `msb` values are constrained to be zero.
137        // If `is_signed` is true, the `msb` values are constrained by `U16MSBOperation`.
138        // In both cases, `b_compare` and `c_compare` are correct.
139        b_compare[WORD_SIZE - 1] = b_compare[WORD_SIZE - 1].clone()
140            + is_signed.clone() * AB::Expr::from_canonical_u32(1 << 15)
141            - base.clone() * cols.b_msb.msb;
142        c_compare[WORD_SIZE - 1] = c_compare[WORD_SIZE - 1].clone()
143            + is_signed.clone() * AB::Expr::from_canonical_u32(1 << 15)
144            - base.clone() * cols.c_msb.msb;
145
146        // Now apply the unsigned LT operation.
147        <LtOperationUnsigned<AB::F> as SP1Operation<AB>>::eval(
148            builder,
149            LtOperationUnsignedInput::<AB>::new(b_compare, c_compare, cols.result, is_real.clone()),
150        );
151    }
152}
153
154impl<F: Field> LtOperationUnsigned<F> {
155    pub fn populate_unsigned(
156        &mut self,
157        record: &mut impl ByteRecord,
158        a_u64: u64,
159        b_u64: u64,
160        c_u64: u64,
161    ) {
162        self.comparison_limbs[0] = F::zero();
163        self.comparison_limbs[1] = F::zero();
164        self.not_eq_inv = F::zero();
165        self.u16_flags = [F::zero(), F::zero(), F::zero(), F::zero()];
166
167        let a_limbs = u64_to_u16_limbs(a_u64);
168        let b_limbs = u64_to_u16_limbs(b_u64);
169        let c_limbs = u64_to_u16_limbs(c_u64);
170
171        let a_u16 = a_limbs[0] as u16;
172
173        let mut comparison_limbs = [0u16; 2];
174        for (b_limb, c_limb, flag) in
175            izip!(b_limbs.iter().rev(), c_limbs.iter().rev(), self.u16_flags.iter_mut().rev())
176        {
177            if b_limb != c_limb {
178                *flag = F::one();
179                comparison_limbs[0] = *b_limb;
180                comparison_limbs[1] = *c_limb;
181                let b_limb = F::from_canonical_u16(*b_limb);
182                let c_limb = F::from_canonical_u16(*c_limb);
183                self.not_eq_inv = (b_limb - c_limb).inverse();
184                self.comparison_limbs = [b_limb, c_limb];
185                break;
186            }
187        }
188        self.u16_compare_operation.populate(
189            record,
190            a_u16,
191            comparison_limbs[0],
192            comparison_limbs[1],
193        );
194    }
195
196    /// Evaluate that LT operation.
197    /// Assumes that `b`, `c` are either valid `Word`s of u16 limbs.
198    /// Constrains that `is_real` is boolean.
199    /// If `is_real` is true, constrains that the result is the LT of `b` and `c`.
200    pub fn eval_lt_unsigned<AB>(
201        builder: &mut AB,
202        b: Word<AB::Expr>,
203        c: Word<AB::Expr>,
204        cols: LtOperationUnsigned<AB::Var>,
205        is_real: AB::Expr,
206    ) where
207        AB: SP1AirBuilder + SP1OperationBuilder<U16CompareOperation<<AB as AirBuilder>::F>>,
208    {
209        builder.assert_bool(is_real.clone());
210
211        // Verify that the limb equality flags are set correctly, i.e. all are boolean and only
212        // at most a single flag is set to one.
213        let sum_flags =
214            cols.u16_flags[0] + cols.u16_flags[1] + cols.u16_flags[2] + cols.u16_flags[3];
215        builder.assert_bool(cols.u16_flags[0]);
216        builder.assert_bool(cols.u16_flags[1]);
217        builder.assert_bool(cols.u16_flags[2]);
218        builder.assert_bool(cols.u16_flags[3]);
219        builder.assert_bool(sum_flags.clone());
220
221        let is_comp_eq = AB::Expr::one() - sum_flags;
222
223        // A flag to indicate whether an equality check is necessary.
224        // This is for all limbs from most significant until the first inequality.
225        let mut is_inequality_visited = AB::Expr::zero();
226
227        // Iterate over the limbs in reverse order and select the differing limbs using the limb
228        // flag columns values.
229        let mut b_comparison_limb = AB::Expr::zero();
230        let mut c_comparison_limb = AB::Expr::zero();
231        for (b_limb, c_limb, &flag) in
232            izip!(b.0.iter().rev(), c.0.iter().rev(), cols.u16_flags.iter().rev())
233        {
234            // Once the byte flag was set to one, we turn off the equality check flag.
235            // We can do this by calculating the sum of the flags since only one is set to `1`.
236            is_inequality_visited = is_inequality_visited.clone() + flag.into();
237
238            // If inequality is not visited, assert that the limbs are equal.
239            builder
240                .when(is_real.clone() - is_inequality_visited.clone())
241                .assert_eq(b_limb.clone(), c_limb.clone());
242
243            b_comparison_limb = b_comparison_limb.clone() + b_limb.clone() * flag.into();
244            c_comparison_limb = c_comparison_limb.clone() + c_limb.clone() * flag.into();
245        }
246
247        let (b_comp_limb, c_comp_limb) = (cols.comparison_limbs[0], cols.comparison_limbs[1]);
248        builder.assert_eq(b_comparison_limb, b_comp_limb);
249        builder.assert_eq(c_comparison_limb, c_comp_limb);
250
251        // Using the values above, we can constrain the `is_comp_eq` flag. We already asserted
252        // in the loop that when `is_comp_eq == 1` then all limbs are equal. It is left to
253        // verify that when `is_comp_eq == 0` the comparison limbs are indeed not equal.
254        // This is done using the inverse hint `not_eq_inv`, when `is_real` is true.
255        builder
256            .when_not(is_comp_eq)
257            .assert_eq(cols.not_eq_inv * (b_comp_limb - c_comp_limb), is_real.clone());
258
259        // Compare the two comparison limbs.
260        <U16CompareOperation<AB::F> as SP1Operation<AB>>::eval(
261            builder,
262            U16CompareOperationInput::<AB>::new(
263                b_comp_limb.into(),
264                c_comp_limb.into(),
265                cols.u16_compare_operation,
266                is_real.clone(),
267            ),
268        );
269    }
270}
271
272#[derive(Clone, InputExpr, InputParams)]
273pub struct LtOperationUnsignedInput<AB: SP1AirBuilder> {
274    pub b: Word<AB::Expr>,
275    pub c: Word<AB::Expr>,
276    pub cols: LtOperationUnsigned<AB::Var>,
277    pub is_real: AB::Expr,
278}
279
280impl<AB> SP1Operation<AB> for LtOperationUnsigned<AB::F>
281where
282    AB: SP1AirBuilder + SP1OperationBuilder<U16CompareOperation<<AB as AirBuilder>::F>>,
283{
284    type Input = LtOperationUnsignedInput<AB>;
285    type Output = ();
286
287    fn lower(builder: &mut AB, input: Self::Input) -> Self::Output {
288        Self::eval_lt_unsigned(builder, input.b, input.c, input.cols, input.is_real);
289    }
290}
291
292#[derive(Clone, InputExpr, InputParams)]
293pub struct LtOperationSignedInput<AB: SP1AirBuilder> {
294    pub b: Word<AB::Expr>,
295    pub c: Word<AB::Expr>,
296    pub cols: LtOperationSigned<AB::Var>,
297    pub is_signed: AB::Expr,
298    pub is_real: AB::Expr,
299}
300
301impl<AB> SP1Operation<AB> for LtOperationSigned<AB::F>
302where
303    AB: SP1AirBuilder
304        + SP1OperationBuilder<U16CompareOperation<<AB as AirBuilder>::F>>
305        + SP1OperationBuilder<U16MSBOperation<<AB as AirBuilder>::F>>
306        + SP1OperationBuilder<LtOperationUnsigned<<AB as AirBuilder>::F>>,
307{
308    type Input = LtOperationSignedInput<AB>;
309    type Output = ();
310
311    fn lower(builder: &mut AB, input: Self::Input) -> Self::Output {
312        Self::eval_lt_signed(builder, input.b, input.c, input.cols, input.is_signed, input.is_real);
313    }
314}