openvm_algebra_circuit/modular_chip/
is_eq.rs

1use std::{
2    array::{self, from_fn},
3    borrow::{Borrow, BorrowMut},
4};
5
6use num_bigint::BigUint;
7use openvm_algebra_transpiler::Rv32ModularArithmeticOpcode;
8use openvm_circuit::{
9    arch::*,
10    system::memory::{
11        online::{GuestMemory, TracingMemory},
12        MemoryAuxColsFactory, POINTER_MAX_BITS,
13    },
14};
15use openvm_circuit_primitives::{
16    bigint::utils::big_uint_to_limbs,
17    bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
18    is_equal_array::{IsEqArrayIo, IsEqArraySubAir},
19    AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper, SubAir,
20    TraceSubRowGenerator,
21};
22use openvm_circuit_primitives_derive::AlignedBorrow;
23use openvm_instructions::{
24    instruction::Instruction,
25    program::DEFAULT_PC_STEP,
26    riscv::{RV32_MEMORY_AS, RV32_REGISTER_AS, RV32_REGISTER_NUM_LIMBS},
27    LocalOpcode,
28};
29use openvm_rv32_adapters::Rv32IsEqualModAdapterExecutor;
30use openvm_stark_backend::{
31    interaction::InteractionBuilder,
32    p3_air::{AirBuilder, BaseAir},
33    p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
34    BaseAirWithPublicValues,
35};
36
37use crate::modular_chip::VmModularIsEqualExecutor;
38// Given two numbers b and c, we want to prove that a) b == c or b != c, depending on
39// result of cmp_result and b) b, c < N for some modulus N that is passed into the AIR
40// at runtime (i.e. when chip is instantiated).
41
42#[repr(C)]
43#[derive(AlignedBorrow, StructReflection, Debug)]
44pub struct ModularIsEqualCoreCols<T, const READ_LIMBS: usize> {
45    pub is_valid: T,
46    pub is_setup: T,
47    pub b: [T; READ_LIMBS],
48    pub c: [T; READ_LIMBS],
49    pub cmp_result: T,
50
51    // Auxiliary columns for subair EQ comparison between b and c.
52    pub eq_marker: [T; READ_LIMBS],
53
54    // Auxiliary columns to ensure both b and c are smaller than modulus N. Let b_diff_idx be
55    // an index such that b[b_diff_idx] < N[b_diff_idx] and b[i] = N[i] for all i > b_diff_idx,
56    // where larger indices correspond to more significant limbs. Such an index exists iff b < N.
57    // Define c_diff_idx analogously. Then let b_lt_diff = N[b_diff_idx] - b[b_diff_idx] and
58    // c_lt_diff = N[c_diff_idx] - c[c_diff_idx], where both must be in [0, 2^LIMB_BITS).
59    //
60    // To constrain the above, we will use lt_marker, which will indicate where b_diff_idx and
61    // c_diff_idx are. Set lt_marker[b_diff_idx] = 1, lt_marker[c_diff_idx] = c_lt_mark, and 0
62    // everywhere else. If b_diff_idx == c_diff_idx then c_lt_mark = 1, else c_lt_mark = 2. The
63    // purpose of c_lt_mark is to handle the edge case where b_diff_idx == c_diff_idx (because
64    // we cannot set lt_marker[b_diff_idx] to 1 and 2 at the same time).
65    pub lt_marker: [T; READ_LIMBS],
66    pub b_lt_diff: T,
67    pub c_lt_diff: T,
68    pub c_lt_mark: T,
69}
70
71#[derive(Clone, Debug, ColumnsAir)]
72#[columns_via(ModularIsEqualCoreCols<u8, READ_LIMBS>)]
73pub struct ModularIsEqualCoreAir<
74    const READ_LIMBS: usize,
75    const WRITE_LIMBS: usize,
76    const LIMB_BITS: usize,
77> {
78    pub bus: BitwiseOperationLookupBus,
79    pub subair: IsEqArraySubAir<READ_LIMBS>,
80    pub modulus_limbs: [u32; READ_LIMBS],
81    pub offset: usize,
82}
83
84impl<const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize>
85    ModularIsEqualCoreAir<READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
86{
87    pub fn new(modulus: BigUint, bus: BitwiseOperationLookupBus, offset: usize) -> Self {
88        let mod_vec = big_uint_to_limbs(&modulus, LIMB_BITS);
89        assert!(mod_vec.len() <= READ_LIMBS);
90        let modulus_limbs = array::from_fn(|i| {
91            if i < mod_vec.len() {
92                mod_vec[i] as u32
93            } else {
94                0
95            }
96        });
97        Self {
98            bus,
99            subair: IsEqArraySubAir::<READ_LIMBS>,
100            modulus_limbs,
101            offset,
102        }
103    }
104}
105
106impl<F: Field, const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize> BaseAir<F>
107    for ModularIsEqualCoreAir<READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
108{
109    fn width(&self) -> usize {
110        ModularIsEqualCoreCols::<F, READ_LIMBS>::width()
111    }
112}
113impl<F: Field, const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize>
114    BaseAirWithPublicValues<F> for ModularIsEqualCoreAir<READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
115{
116}
117
118impl<AB, I, const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize>
119    VmCoreAir<AB, I> for ModularIsEqualCoreAir<READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
120where
121    AB: InteractionBuilder,
122    I: VmAdapterInterface<AB::Expr>,
123    I::Reads: From<[[AB::Expr; READ_LIMBS]; 2]>,
124    I::Writes: From<[[AB::Expr; WRITE_LIMBS]; 1]>,
125    I::ProcessedInstruction: From<MinimalInstruction<AB::Expr>>,
126{
127    fn eval(
128        &self,
129        builder: &mut AB,
130        local_core: &[AB::Var],
131        _from_pc: AB::Var,
132    ) -> AdapterAirContext<AB::Expr, I> {
133        let cols: &ModularIsEqualCoreCols<_, READ_LIMBS> = local_core.borrow();
134
135        builder.assert_bool(cols.is_valid);
136        builder.assert_bool(cols.is_setup);
137        builder.when(cols.is_setup).assert_one(cols.is_valid);
138        builder.assert_bool(cols.cmp_result);
139
140        // Constrain that either b == c or b != c, depending on the value of cmp_result.
141        let eq_subair_io = IsEqArrayIo {
142            x: cols.b.map(Into::into),
143            y: cols.c.map(Into::into),
144            out: cols.cmp_result.into(),
145            condition: cols.is_valid - cols.is_setup,
146        };
147        self.subair.eval(builder, (eq_subair_io, cols.eq_marker));
148
149        // Constrain that auxiliary columns lt_columns and c_lt_mark are as defined above.
150        // When c_lt_mark is 1, lt_marker should have exactly one index i where lt_marker[i]
151        // is 1, and be 0 elsewhere. When c_lt_mark is 2, lt_marker[i] should have an
152        // additional index j such that lt_marker[j] is 2. To constrain this:
153        //
154        // * When c_lt_mark = 1 the sum of all lt_marker[i] must be 1
155        // * When c_lt_mark = 2 the sum of lt_marker[i] * (lt_marker[i] - 1) must be 2.
156        //   Additionally, the sum of all lt_marker[i] must be 3.
157        //
158        // All this doesn't apply when is_setup.
159        let lt_marker_sum = cols
160            .lt_marker
161            .iter()
162            .fold(AB::Expr::ZERO, |acc, x| acc + *x);
163        let lt_marker_one_check_sum = cols
164            .lt_marker
165            .iter()
166            .fold(AB::Expr::ZERO, |acc, x| acc + (*x) * (*x - AB::F::ONE));
167
168        // Constrain that c_lt_mark is either 1 or 2.
169        builder
170            .when(cols.is_valid - cols.is_setup)
171            .assert_bool(cols.c_lt_mark - AB::F::ONE);
172
173        // If c_lt_mark is 1, then lt_marker_sum is 1
174        builder
175            .when(cols.is_valid - cols.is_setup)
176            .when_ne(cols.c_lt_mark, AB::F::from_u8(2))
177            .assert_one(lt_marker_sum.clone());
178
179        // If c_lt_mark is 2, then lt_marker_sum is 3
180        builder
181            .when(cols.is_valid - cols.is_setup)
182            .when_ne(cols.c_lt_mark, AB::F::ONE)
183            .assert_eq(lt_marker_sum.clone(), AB::F::from_u8(3));
184
185        // This constraint, along with the constraint (below) that lt_marker[i] is 0, 1, or 2,
186        // ensures that lt_marker has exactly one 2.
187        builder
188            .when_ne(cols.c_lt_mark, AB::F::ONE)
189            .assert_eq(lt_marker_one_check_sum, cols.is_valid * AB::F::from_u8(2));
190
191        // Handle the setup row constraints.
192        // When is_setup = 1, constrain c_lt_mark = 2 and lt_marker_sum = 2
193        // This ensures that lt_marker has exactly one 2 and the remaining entries are 0.
194        // Since lt_marker has no 1, we will end up constraining that b[i] = N[i] for all i
195        // instead of just for i > b_diff_idx.
196        builder
197            .when(cols.is_setup)
198            .assert_eq(cols.c_lt_mark, AB::F::from_u8(2));
199        builder
200            .when(cols.is_setup)
201            .assert_eq(lt_marker_sum.clone(), AB::F::from_u8(2));
202
203        // Constrain that b, c < N (i.e. modulus).
204        let modulus = self.modulus_limbs.map(AB::F::from_u32);
205        let mut prefix_sum = AB::Expr::ZERO;
206
207        for i in (0..READ_LIMBS).rev() {
208            prefix_sum += cols.lt_marker[i].into();
209            builder.assert_zero(
210                cols.lt_marker[i]
211                    * (cols.lt_marker[i] - AB::F::ONE)
212                    * (cols.lt_marker[i] - cols.c_lt_mark),
213            );
214
215            // Constrain b < N.
216            // First, we constrain b[i] = N[i] for i > b_diff_idx.
217            // We do this by constraining that b[i] = N[i] when prefix_sum is not 1 or
218            // lt_marker_sum.
219            //  - If is_setup = 0, then lt_marker_sum is either 1 or 3. In this case, prefix_sum is
220            //    0, 1, 2, or 3. It can be verified by casework that i > b_diff_idx iff prefix_sum
221            //    is not 1 or lt_marker_sum.
222            //  - If is_setup = 1, then we want to constrain b[i] = N[i] for all i. In this case,
223            //    lt_marker_sum is 2 and prefix_sum is 0 or 2. So we constrain b[i] = N[i] when
224            //    prefix_sum is not 1, which works.
225            builder
226                .when_ne(prefix_sum.clone(), AB::F::ONE)
227                .when_ne(prefix_sum.clone(), lt_marker_sum.clone() - cols.is_setup)
228                .assert_eq(cols.b[i], modulus[i]);
229            // Note that lt_marker[i] is either 0, 1, or 2 and lt_marker[i] being 1 indicates b[i] <
230            // N[i] (i.e. i == b_diff_idx).
231            builder
232                .when_ne(cols.lt_marker[i], AB::F::ZERO)
233                .when_ne(cols.lt_marker[i], AB::F::from_u8(2))
234                .assert_eq(AB::Expr::from(modulus[i]) - cols.b[i], cols.b_lt_diff);
235
236            // Constrain c < N.
237            // First, we constrain c[i] = N[i] for i > c_diff_idx.
238            // We do this by constraining that c[i] = N[i] when prefix_sum is not c_lt_mark or
239            // lt_marker_sum. It can be verified by casework that i > c_diff_idx iff
240            // prefix_sum is not c_lt_mark or lt_marker_sum.
241            builder
242                .when_ne(prefix_sum.clone(), cols.c_lt_mark)
243                .when_ne(prefix_sum.clone(), lt_marker_sum.clone())
244                .assert_eq(cols.c[i], modulus[i]);
245            // Note that lt_marker[i] is either 0, 1, or 2 and lt_marker[i] being c_lt_mark
246            // indicates c[i] < N[i] (i.e. i == c_diff_idx). Since c_lt_mark is 1 or 2,
247            // we have {0, 1, 2} \ {0, 3 - c_lt_mark} = {c_lt_mark}.
248            builder
249                .when_ne(cols.lt_marker[i], AB::F::ZERO)
250                .when_ne(cols.lt_marker[i], AB::Expr::from_u8(3) - cols.c_lt_mark)
251                .assert_eq(AB::Expr::from(modulus[i]) - cols.c[i], cols.c_lt_diff);
252        }
253
254        // Check that b_lt_diff and c_lt_diff are positive
255        self.bus
256            .send_range(
257                cols.b_lt_diff - AB::Expr::ONE,
258                cols.c_lt_diff - AB::Expr::ONE,
259            )
260            .eval(builder, cols.is_valid - cols.is_setup);
261
262        let expected_opcode = AB::Expr::from_usize(self.offset)
263            + cols.is_setup
264                * AB::Expr::from_usize(Rv32ModularArithmeticOpcode::SETUP_ISEQ as usize)
265            + (AB::Expr::ONE - cols.is_setup)
266                * AB::Expr::from_usize(Rv32ModularArithmeticOpcode::IS_EQ as usize);
267        let mut a: [AB::Expr; WRITE_LIMBS] = array::from_fn(|_| AB::Expr::ZERO);
268        a[0] = cols.cmp_result.into();
269
270        AdapterAirContext {
271            to_pc: None,
272            reads: [cols.b.map(Into::into), cols.c.map(Into::into)].into(),
273            writes: [a].into(),
274            instruction: MinimalInstruction {
275                is_valid: cols.is_valid.into(),
276                opcode: expected_opcode,
277            }
278            .into(),
279        }
280    }
281
282    fn start_offset(&self) -> usize {
283        self.offset
284    }
285}
286
287#[repr(C)]
288#[derive(AlignedBytesBorrow, Debug)]
289pub struct ModularIsEqualRecord<const READ_LIMBS: usize> {
290    pub is_setup: bool,
291    pub b: [u8; READ_LIMBS],
292    pub c: [u8; READ_LIMBS],
293}
294
295#[derive(derive_new::new, Clone)]
296pub struct ModularIsEqualExecutor<
297    A,
298    const READ_LIMBS: usize,
299    const WRITE_LIMBS: usize,
300    const LIMB_BITS: usize,
301> {
302    adapter: A,
303    pub offset: usize,
304    pub modulus_limbs: [u8; READ_LIMBS],
305}
306
307#[derive(derive_new::new, Clone)]
308pub struct ModularIsEqualFiller<
309    A,
310    const READ_LIMBS: usize,
311    const WRITE_LIMBS: usize,
312    const LIMB_BITS: usize,
313> {
314    adapter: A,
315    pub offset: usize,
316    pub modulus_limbs: [u8; READ_LIMBS],
317    pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<LIMB_BITS>,
318}
319
320impl<F, A, RA, const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize>
321    PreflightExecutor<F, RA> for ModularIsEqualExecutor<A, READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
322where
323    F: PrimeField32,
324    A: 'static
325        + AdapterTraceExecutor<
326            F,
327            ReadData: Into<[[u8; READ_LIMBS]; 2]>,
328            WriteData: From<[u8; WRITE_LIMBS]>,
329        >,
330    for<'buf> RA: RecordArena<
331        'buf,
332        EmptyAdapterCoreLayout<F, A>,
333        (
334            A::RecordMut<'buf>,
335            &'buf mut ModularIsEqualRecord<READ_LIMBS>,
336        ),
337    >,
338{
339    fn execute(
340        &self,
341        state: VmStateMut<F, TracingMemory, RA>,
342        instruction: &Instruction<F>,
343    ) -> Result<(), ExecutionError> {
344        let Instruction { opcode, .. } = instruction;
345
346        let local_opcode =
347            Rv32ModularArithmeticOpcode::from_usize(opcode.local_opcode_idx(self.offset));
348        matches!(
349            local_opcode,
350            Rv32ModularArithmeticOpcode::IS_EQ | Rv32ModularArithmeticOpcode::SETUP_ISEQ
351        );
352
353        let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
354
355        A::start(*state.pc, state.memory, &mut adapter_record);
356        [core_record.b, core_record.c] = self
357            .adapter
358            .read(state.memory, instruction, &mut adapter_record)
359            .into();
360
361        core_record.is_setup = instruction.opcode.local_opcode_idx(self.offset)
362            == Rv32ModularArithmeticOpcode::SETUP_ISEQ as usize;
363
364        let mut write_data = [0u8; WRITE_LIMBS];
365        write_data[0] = (core_record.b == core_record.c) as u8;
366
367        self.adapter.write(
368            state.memory,
369            instruction,
370            write_data.into(),
371            &mut adapter_record,
372        );
373
374        *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
375
376        Ok(())
377    }
378
379    fn get_opcode_name(&self, opcode: usize) -> String {
380        format!(
381            "{:?}",
382            Rv32ModularArithmeticOpcode::from_usize(opcode - self.offset)
383        )
384    }
385}
386
387impl<F, A, const READ_LIMBS: usize, const WRITE_LIMBS: usize, const LIMB_BITS: usize> TraceFiller<F>
388    for ModularIsEqualFiller<A, READ_LIMBS, WRITE_LIMBS, LIMB_BITS>
389where
390    F: PrimeField32,
391    A: 'static + AdapterTraceFiller<F>,
392{
393    fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
394        let (adapter_row, mut core_row) = row_slice.split_at_mut(A::WIDTH);
395        self.adapter.fill_trace_row(mem_helper, adapter_row);
396        // SAFETY:
397        // - row_slice is guaranteed by the caller to have at least A::WIDTH +
398        //   ModularIsEqualCoreCols::width() elements
399        // - caller ensures core_row contains a valid record written by the executor during trace
400        //   generation
401        let record: &ModularIsEqualRecord<READ_LIMBS> =
402            unsafe { get_record_from_slice(&mut core_row, ()) };
403        let cols: &mut ModularIsEqualCoreCols<F, READ_LIMBS> = core_row.borrow_mut();
404        let (b_cmp, b_diff_idx) =
405            run_unsigned_less_than::<READ_LIMBS>(&record.b, &self.modulus_limbs);
406        let (c_cmp, c_diff_idx) =
407            run_unsigned_less_than::<READ_LIMBS>(&record.c, &self.modulus_limbs);
408
409        if !record.is_setup {
410            assert!(b_cmp, "{:?} >= {:?}", record.b, self.modulus_limbs);
411        }
412        assert!(c_cmp, "{:?} >= {:?}", record.c, self.modulus_limbs);
413
414        // Writing in reverse order
415        cols.c_lt_mark = if b_diff_idx == c_diff_idx {
416            F::ONE
417        } else {
418            F::TWO
419        };
420
421        cols.c_lt_diff = F::from_u8(self.modulus_limbs[c_diff_idx] - record.c[c_diff_idx]);
422        if !record.is_setup {
423            cols.b_lt_diff = F::from_u8(self.modulus_limbs[b_diff_idx] - record.b[b_diff_idx]);
424            self.bitwise_lookup_chip.request_range(
425                (self.modulus_limbs[b_diff_idx] - record.b[b_diff_idx] - 1) as u32,
426                (self.modulus_limbs[c_diff_idx] - record.c[c_diff_idx] - 1) as u32,
427            );
428        } else {
429            cols.b_lt_diff = F::ZERO;
430        }
431
432        cols.lt_marker = from_fn(|i| {
433            if i == b_diff_idx {
434                F::ONE
435            } else if i == c_diff_idx {
436                cols.c_lt_mark
437            } else {
438                F::ZERO
439            }
440        });
441
442        cols.c = record.c.map(F::from_u8);
443        cols.b = record.b.map(F::from_u8);
444        let sub_air = IsEqArraySubAir::<READ_LIMBS>;
445        sub_air.generate_subrow(
446            (&cols.b, &cols.c),
447            (&mut cols.eq_marker, &mut cols.cmp_result),
448        );
449
450        cols.is_setup = F::from_bool(record.is_setup);
451        cols.is_valid = F::ONE;
452    }
453}
454
455impl<const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_LIMBS: usize>
456    VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_LIMBS>
457{
458    pub fn new(
459        adapter: Rv32IsEqualModAdapterExecutor<2, NUM_LANES, LANE_SIZE, TOTAL_LIMBS>,
460        offset: usize,
461        modulus_limbs: [u8; TOTAL_LIMBS],
462    ) -> Self {
463        Self(ModularIsEqualExecutor::new(adapter, offset, modulus_limbs))
464    }
465}
466
467#[derive(AlignedBytesBorrow, Clone)]
468#[repr(C)]
469struct ModularIsEqualPreCompute<const READ_LIMBS: usize> {
470    a: u8,
471    rs_addrs: [u8; 2],
472    modulus_limbs: [u8; READ_LIMBS],
473}
474
475impl<const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_READ_SIZE: usize>
476    VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE>
477{
478    fn pre_compute_impl<F: PrimeField32>(
479        &self,
480        pc: u32,
481        inst: &Instruction<F>,
482        data: &mut ModularIsEqualPreCompute<TOTAL_READ_SIZE>,
483    ) -> Result<bool, StaticProgramError> {
484        let Instruction {
485            opcode,
486            a,
487            b,
488            c,
489            d,
490            e,
491            ..
492        } = inst;
493
494        let local_opcode =
495            Rv32ModularArithmeticOpcode::from_usize(opcode.local_opcode_idx(self.0.offset));
496
497        // Validate instruction format
498        let a = a.as_canonical_u32();
499        let b = b.as_canonical_u32();
500        let c = c.as_canonical_u32();
501        let d = d.as_canonical_u32();
502        let e = e.as_canonical_u32();
503        if d != RV32_REGISTER_AS || e != RV32_MEMORY_AS {
504            return Err(StaticProgramError::InvalidInstruction(pc));
505        }
506
507        if !matches!(
508            local_opcode,
509            Rv32ModularArithmeticOpcode::IS_EQ | Rv32ModularArithmeticOpcode::SETUP_ISEQ
510        ) {
511            return Err(StaticProgramError::InvalidInstruction(pc));
512        }
513
514        let rs_addrs = from_fn(|i| if i == 0 { b } else { c } as u8);
515        *data = ModularIsEqualPreCompute {
516            a: a as u8,
517            rs_addrs,
518            modulus_limbs: self.0.modulus_limbs,
519        };
520
521        let is_setup = local_opcode == Rv32ModularArithmeticOpcode::SETUP_ISEQ;
522
523        Ok(is_setup)
524    }
525}
526
527macro_rules! dispatch {
528    ($execute_impl:ident, $is_setup:ident) => {
529        Ok(if $is_setup {
530            $execute_impl::<_, _, NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE, true>
531        } else {
532            $execute_impl::<_, _, NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE, false>
533        })
534    };
535}
536
537impl<F, const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_READ_SIZE: usize>
538    InterpreterExecutor<F> for VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE>
539where
540    F: PrimeField32,
541{
542    #[inline(always)]
543    fn pre_compute_size(&self) -> usize {
544        std::mem::size_of::<ModularIsEqualPreCompute<TOTAL_READ_SIZE>>()
545    }
546
547    #[cfg(not(feature = "tco"))]
548    fn pre_compute<Ctx: ExecutionCtxTrait>(
549        &self,
550        pc: u32,
551        inst: &Instruction<F>,
552        data: &mut [u8],
553    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
554        let pre_compute: &mut ModularIsEqualPreCompute<TOTAL_READ_SIZE> = data.borrow_mut();
555        let is_setup = self.pre_compute_impl(pc, inst, pre_compute)?;
556
557        dispatch!(execute_e1_handler, is_setup)
558    }
559
560    #[cfg(feature = "tco")]
561    fn handler<Ctx>(
562        &self,
563        pc: u32,
564        inst: &Instruction<F>,
565        data: &mut [u8],
566    ) -> Result<Handler<F, Ctx>, StaticProgramError>
567    where
568        Ctx: ExecutionCtxTrait,
569    {
570        let pre_compute: &mut ModularIsEqualPreCompute<TOTAL_READ_SIZE> = data.borrow_mut();
571        let is_setup = self.pre_compute_impl(pc, inst, pre_compute)?;
572
573        dispatch!(execute_e1_handler, is_setup)
574    }
575}
576
577#[cfg(feature = "aot")]
578impl<F, const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_READ_SIZE: usize> AotExecutor<F>
579    for VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE>
580where
581    F: PrimeField32,
582{
583}
584
585impl<F, const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_READ_SIZE: usize>
586    InterpreterMeteredExecutor<F>
587    for VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE>
588where
589    F: PrimeField32,
590{
591    #[inline(always)]
592    fn metered_pre_compute_size(&self) -> usize {
593        std::mem::size_of::<E2PreCompute<ModularIsEqualPreCompute<TOTAL_READ_SIZE>>>()
594    }
595
596    #[cfg(not(feature = "tco"))]
597    fn metered_pre_compute<Ctx: MeteredExecutionCtxTrait>(
598        &self,
599        chip_idx: usize,
600        pc: u32,
601        inst: &Instruction<F>,
602        data: &mut [u8],
603    ) -> Result<ExecuteFunc<F, Ctx>, StaticProgramError> {
604        let pre_compute: &mut E2PreCompute<ModularIsEqualPreCompute<TOTAL_READ_SIZE>> =
605            data.borrow_mut();
606        pre_compute.chip_idx = chip_idx as u32;
607
608        let is_setup = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
609
610        dispatch!(execute_e2_handler, is_setup)
611    }
612
613    #[cfg(feature = "tco")]
614    fn metered_handler<Ctx: MeteredExecutionCtxTrait>(
615        &self,
616        chip_idx: usize,
617        pc: u32,
618        inst: &Instruction<F>,
619        data: &mut [u8],
620    ) -> Result<Handler<F, Ctx>, StaticProgramError> {
621        let pre_compute: &mut E2PreCompute<ModularIsEqualPreCompute<TOTAL_READ_SIZE>> =
622            data.borrow_mut();
623        pre_compute.chip_idx = chip_idx as u32;
624
625        let is_setup = self.pre_compute_impl(pc, inst, &mut pre_compute.data)?;
626
627        dispatch!(execute_e2_handler, is_setup)
628    }
629}
630
631#[cfg(feature = "aot")]
632impl<F, const NUM_LANES: usize, const LANE_SIZE: usize, const TOTAL_READ_SIZE: usize>
633    AotMeteredExecutor<F> for VmModularIsEqualExecutor<NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE>
634where
635    F: PrimeField32,
636{
637}
638#[create_handler]
639#[inline(always)]
640unsafe fn execute_e1_impl<
641    F: PrimeField32,
642    CTX: ExecutionCtxTrait,
643    const NUM_LANES: usize,
644    const LANE_SIZE: usize,
645    const TOTAL_READ_SIZE: usize,
646    const IS_SETUP: bool,
647>(
648    pre_compute: *const u8,
649    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
650) {
651    let pre_compute: &ModularIsEqualPreCompute<TOTAL_READ_SIZE> = std::slice::from_raw_parts(
652        pre_compute,
653        size_of::<ModularIsEqualPreCompute<TOTAL_READ_SIZE>>(),
654    )
655    .borrow();
656
657    execute_e12_impl::<_, _, NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE, IS_SETUP>(
658        pre_compute,
659        exec_state,
660    );
661}
662
663#[create_handler]
664#[inline(always)]
665unsafe fn execute_e2_impl<
666    F: PrimeField32,
667    CTX: MeteredExecutionCtxTrait,
668    const NUM_LANES: usize,
669    const LANE_SIZE: usize,
670    const TOTAL_READ_SIZE: usize,
671    const IS_SETUP: bool,
672>(
673    pre_compute: *const u8,
674    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
675) {
676    let pre_compute: &E2PreCompute<ModularIsEqualPreCompute<TOTAL_READ_SIZE>> =
677        std::slice::from_raw_parts(
678            pre_compute,
679            size_of::<E2PreCompute<ModularIsEqualPreCompute<TOTAL_READ_SIZE>>>(),
680        )
681        .borrow();
682    exec_state
683        .ctx
684        .on_height_change(pre_compute.chip_idx as usize, 1);
685    execute_e12_impl::<_, _, NUM_LANES, LANE_SIZE, TOTAL_READ_SIZE, IS_SETUP>(
686        &pre_compute.data,
687        exec_state,
688    );
689}
690
691#[inline(always)]
692unsafe fn execute_e12_impl<
693    F: PrimeField32,
694    CTX: ExecutionCtxTrait,
695    const NUM_LANES: usize,
696    const LANE_SIZE: usize,
697    const TOTAL_READ_SIZE: usize,
698    const IS_SETUP: bool,
699>(
700    pre_compute: &ModularIsEqualPreCompute<TOTAL_READ_SIZE>,
701    exec_state: &mut VmExecState<F, GuestMemory, CTX>,
702) {
703    // Read register values
704    let rs_vals = pre_compute
705        .rs_addrs
706        .map(|addr| u32::from_le_bytes(exec_state.vm_read(RV32_REGISTER_AS, addr as u32)));
707
708    // Read memory values
709    let [b, c]: [[u8; TOTAL_READ_SIZE]; 2] = rs_vals.map(|address| {
710        debug_assert!(address as usize + TOTAL_READ_SIZE - 1 < (1 << POINTER_MAX_BITS));
711        from_fn::<_, NUM_LANES, _>(|i| {
712            exec_state.vm_read::<_, LANE_SIZE>(RV32_MEMORY_AS, address + (i * LANE_SIZE) as u32)
713        })
714        .concat()
715        .try_into()
716        .unwrap()
717    });
718
719    if !IS_SETUP {
720        let (b_cmp, _) = run_unsigned_less_than::<TOTAL_READ_SIZE>(&b, &pre_compute.modulus_limbs);
721        debug_assert!(b_cmp, "{:?} >= {:?}", b, pre_compute.modulus_limbs);
722    }
723
724    let (c_cmp, _) = run_unsigned_less_than::<TOTAL_READ_SIZE>(&c, &pre_compute.modulus_limbs);
725    debug_assert!(c_cmp, "{:?} >= {:?}", c, pre_compute.modulus_limbs);
726
727    // Compute result
728    let mut write_data = [0u8; RV32_REGISTER_NUM_LIMBS];
729    write_data[0] = (b == c) as u8;
730
731    // Write result to register
732    exec_state.vm_write(RV32_REGISTER_AS, pre_compute.a as u32, &write_data);
733
734    let pc = exec_state.pc();
735    exec_state.set_pc(pc.wrapping_add(DEFAULT_PC_STEP));
736}
737
738// Returns (cmp_result, diff_idx)
739#[inline(always)]
740pub(super) fn run_unsigned_less_than<const NUM_LIMBS: usize>(
741    x: &[u8; NUM_LIMBS],
742    y: &[u8; NUM_LIMBS],
743) -> (bool, usize) {
744    for i in (0..NUM_LIMBS).rev() {
745        if x[i] != y[i] {
746            return (x[i] < y[i], i);
747        }
748    }
749    (false, NUM_LIMBS)
750}