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#[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 pub eq_marker: [T; READ_LIMBS],
53
54 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 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 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 builder
170 .when(cols.is_valid - cols.is_setup)
171 .assert_bool(cols.c_lt_mark - AB::F::ONE);
172
173 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 let mut write_data = [0u8; RV32_REGISTER_NUM_LIMBS];
729 write_data[0] = (b == c) as u8;
730
731 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#[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}