sp1_core_machine/operations/
slt.rs1use 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 pub u16_compare_operation: U16CompareOperation<T>,
32 pub u16_flags: [T; WORD_SIZE],
34 pub not_eq_inv: T,
36 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 pub result: LtOperationUnsigned<T>,
56 pub b_msb: U16MSBOperation<T>,
58 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 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 builder.when_not(is_real.clone()).assert_zero(is_signed.clone());
106
107 <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 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 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 <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 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 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 let mut is_inequality_visited = AB::Expr::zero();
226
227 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 is_inequality_visited = is_inequality_visited.clone() + flag.into();
237
238 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 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 <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}