1use std::{
2 array,
3 borrow::{Borrow, BorrowMut},
4};
5
6use openvm_circuit::{
7 arch::*,
8 system::memory::{online::TracingMemory, MemoryAuxColsFactory},
9};
10use openvm_circuit_primitives::{
11 bitwise_op_lookup::{BitwiseOperationLookupBus, SharedBitwiseOperationLookupChip},
12 utils::not,
13 var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerBus},
14 AlignedBytesBorrow, ColumnsAir, StructReflection, StructReflectionHelper,
15};
16use openvm_circuit_primitives_derive::AlignedBorrow;
17use openvm_instructions::{instruction::Instruction, program::DEFAULT_PC_STEP, LocalOpcode};
18use openvm_rv32im_transpiler::ShiftOpcode;
19use openvm_stark_backend::{
20 interaction::InteractionBuilder,
21 p3_air::{AirBuilder, BaseAir},
22 p3_field::{Field, PrimeCharacteristicRing, PrimeField32},
23 BaseAirWithPublicValues,
24};
25use strum::IntoEnumIterator;
26
27#[repr(C)]
28#[derive(AlignedBorrow, StructReflection, Clone, Copy, Debug)]
29pub struct ShiftCoreCols<T, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
30 pub a: [T; NUM_LIMBS],
31 pub b: [T; NUM_LIMBS],
32 pub c: [T; NUM_LIMBS],
33
34 pub opcode_sll_flag: T,
35 pub opcode_srl_flag: T,
36 pub opcode_sra_flag: T,
37
38 pub bit_multiplier_left: T,
40 pub bit_multiplier_right: T,
41
42 pub b_sign: T,
44
45 pub bit_shift_marker: [T; LIMB_BITS],
47 pub limb_shift_marker: [T; NUM_LIMBS],
48
49 pub bit_shift_carry: [T; NUM_LIMBS],
51}
52
53#[derive(Copy, Clone, Debug, derive_new::new, ColumnsAir)]
57#[columns_via(ShiftCoreCols<u8, NUM_LIMBS, LIMB_BITS>)]
58pub struct ShiftCoreAir<const NUM_LIMBS: usize, const LIMB_BITS: usize> {
59 pub bitwise_lookup_bus: BitwiseOperationLookupBus,
60 pub range_bus: VariableRangeCheckerBus,
61 pub offset: usize,
62}
63
64impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAir<F>
65 for ShiftCoreAir<NUM_LIMBS, LIMB_BITS>
66{
67 fn width(&self) -> usize {
68 ShiftCoreCols::<F, NUM_LIMBS, LIMB_BITS>::width()
69 }
70}
71impl<F: Field, const NUM_LIMBS: usize, const LIMB_BITS: usize> BaseAirWithPublicValues<F>
72 for ShiftCoreAir<NUM_LIMBS, LIMB_BITS>
73{
74}
75
76impl<AB, I, const NUM_LIMBS: usize, const LIMB_BITS: usize> VmCoreAir<AB, I>
77 for ShiftCoreAir<NUM_LIMBS, LIMB_BITS>
78where
79 AB: InteractionBuilder,
80 I: VmAdapterInterface<AB::Expr>,
81 I::Reads: From<[[AB::Expr; NUM_LIMBS]; 2]>,
82 I::Writes: From<[[AB::Expr; NUM_LIMBS]; 1]>,
83 I::ProcessedInstruction: From<MinimalInstruction<AB::Expr>>,
84{
85 fn eval(
86 &self,
87 builder: &mut AB,
88 local_core: &[AB::Var],
89 _from_pc: AB::Var,
90 ) -> AdapterAirContext<AB::Expr, I> {
91 let cols: &ShiftCoreCols<_, NUM_LIMBS, LIMB_BITS> = local_core.borrow();
92 let flags = [
93 cols.opcode_sll_flag,
94 cols.opcode_srl_flag,
95 cols.opcode_sra_flag,
96 ];
97
98 let is_valid = flags.iter().fold(AB::Expr::ZERO, |acc, &flag| {
99 builder.assert_bool(flag);
100 acc + flag.into()
101 });
102 builder.assert_bool(is_valid.clone());
103
104 let a = &cols.a;
105 let b = &cols.b;
106 let c = &cols.c;
107 let right_shift = cols.opcode_srl_flag + cols.opcode_sra_flag;
108
109 let mut bit_marker_sum = AB::Expr::ZERO;
113 let mut bit_shift = AB::Expr::ZERO;
114
115 for i in 0..LIMB_BITS {
116 builder.assert_bool(cols.bit_shift_marker[i]);
117 bit_marker_sum += cols.bit_shift_marker[i].into();
118 bit_shift += AB::Expr::from_usize(i) * cols.bit_shift_marker[i];
119
120 let mut when_bit_shift = builder.when(cols.bit_shift_marker[i]);
121 when_bit_shift.assert_eq(
122 cols.bit_multiplier_left,
123 AB::Expr::from_usize(1 << i) * cols.opcode_sll_flag,
124 );
125 when_bit_shift.assert_eq(
126 cols.bit_multiplier_right,
127 AB::Expr::from_usize(1 << i) * right_shift.clone(),
128 );
129 }
130 builder.when(is_valid.clone()).assert_one(bit_marker_sum);
131
132 let mut limb_marker_sum = AB::Expr::ZERO;
135 let mut limb_shift = AB::Expr::ZERO;
136 for i in 0..NUM_LIMBS {
137 builder.assert_bool(cols.limb_shift_marker[i]);
138 limb_marker_sum += cols.limb_shift_marker[i].into();
139 limb_shift += AB::Expr::from_usize(i) * cols.limb_shift_marker[i];
140
141 let mut when_limb_shift = builder.when(cols.limb_shift_marker[i]);
142
143 for j in 0..NUM_LIMBS {
144 if j < i {
146 when_limb_shift.assert_zero(a[j] * cols.opcode_sll_flag);
147 } else {
148 let expected_a_left = if j - i == 0 {
149 AB::Expr::ZERO
150 } else {
151 cols.bit_shift_carry[j - i - 1].into() * cols.opcode_sll_flag
152 } + b[j - i] * cols.bit_multiplier_left
153 - AB::Expr::from_usize(1 << LIMB_BITS)
154 * cols.bit_shift_carry[j - i]
155 * cols.opcode_sll_flag;
156 when_limb_shift.assert_eq(a[j] * cols.opcode_sll_flag, expected_a_left);
157 }
158
159 if j + i > NUM_LIMBS - 1 {
161 when_limb_shift.assert_eq(
162 a[j] * right_shift.clone(),
163 cols.b_sign * AB::F::from_usize((1 << LIMB_BITS) - 1),
164 );
165 } else {
166 let expected_a_right = if j + i == NUM_LIMBS - 1 {
167 cols.b_sign * (cols.bit_multiplier_right - AB::F::ONE)
168 } else {
169 cols.bit_shift_carry[j + i + 1].into() * right_shift.clone()
170 } * AB::F::from_usize(1 << LIMB_BITS)
171 + right_shift.clone() * (b[j + i] - cols.bit_shift_carry[j + i]);
172 when_limb_shift.assert_eq(a[j] * cols.bit_multiplier_right, expected_a_right);
173 }
174 }
175 }
176 builder.when(is_valid.clone()).assert_one(limb_marker_sum);
177
178 let num_bits = AB::F::from_usize(NUM_LIMBS * LIMB_BITS);
180 self.range_bus
181 .range_check(
182 (c[0] - limb_shift * AB::F::from_usize(LIMB_BITS) - bit_shift.clone())
183 * num_bits.inverse(),
184 LIMB_BITS - ((NUM_LIMBS * LIMB_BITS) as u32).ilog2() as usize,
185 )
186 .eval(builder, is_valid.clone());
187
188 builder.assert_bool(cols.b_sign);
190 builder
191 .when(not(cols.opcode_sra_flag))
192 .assert_zero(cols.b_sign);
193
194 let mask = AB::F::from_u32(1 << (LIMB_BITS - 1));
195 let b_sign_shifted = cols.b_sign * mask;
196 self.bitwise_lookup_bus
197 .send_xor(
198 b[NUM_LIMBS - 1],
199 mask,
200 b[NUM_LIMBS - 1] + mask - (AB::Expr::from_u32(2) * b_sign_shifted),
201 )
202 .eval(builder, cols.opcode_sra_flag);
203
204 for i in 0..(NUM_LIMBS / 2) {
205 self.bitwise_lookup_bus
206 .send_range(a[i * 2], a[i * 2 + 1])
207 .eval(builder, is_valid.clone());
208 }
209
210 for carry in cols.bit_shift_carry {
211 self.range_bus
212 .send(carry, bit_shift.clone())
213 .eval(builder, is_valid.clone());
214 }
215
216 let expected_opcode = VmCoreAir::<AB, I>::expr_to_global_expr(
217 self,
218 flags
219 .iter()
220 .zip(ShiftOpcode::iter())
221 .fold(AB::Expr::ZERO, |acc, (flag, opcode)| {
222 acc + (*flag).into() * AB::Expr::from_u8(opcode as u8)
223 }),
224 );
225
226 AdapterAirContext {
227 to_pc: None,
228 reads: [cols.b.map(Into::into), cols.c.map(Into::into)].into(),
229 writes: [cols.a.map(Into::into)].into(),
230 instruction: MinimalInstruction {
231 is_valid,
232 opcode: expected_opcode,
233 }
234 .into(),
235 }
236 }
237
238 fn start_offset(&self) -> usize {
239 self.offset
240 }
241}
242
243#[repr(C)]
244#[derive(AlignedBytesBorrow, Debug)]
245pub struct ShiftCoreRecord<const NUM_LIMBS: usize, const LIMB_BITS: usize> {
246 pub b: [u8; NUM_LIMBS],
247 pub c: [u8; NUM_LIMBS],
248 pub local_opcode: u8,
249}
250
251#[derive(Clone, Copy)]
252pub struct ShiftExecutor<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
253 adapter: A,
254 pub offset: usize,
255}
256
257#[derive(Clone)]
258pub struct ShiftFiller<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> {
259 adapter: A,
260 pub offset: usize,
261 pub bitwise_lookup_chip: SharedBitwiseOperationLookupChip<LIMB_BITS>,
262 pub range_checker_chip: SharedVariableRangeCheckerChip,
263}
264
265impl<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> ShiftExecutor<A, NUM_LIMBS, LIMB_BITS> {
266 pub fn new(adapter: A, offset: usize) -> Self {
267 assert_eq!(NUM_LIMBS % 2, 0, "Number of limbs must be divisible by 2");
268 Self { adapter, offset }
269 }
270}
271
272impl<A, const NUM_LIMBS: usize, const LIMB_BITS: usize> ShiftFiller<A, NUM_LIMBS, LIMB_BITS> {
273 pub fn new(
274 adapter: A,
275 bitwise_lookup_chip: SharedBitwiseOperationLookupChip<LIMB_BITS>,
276 range_checker_chip: SharedVariableRangeCheckerChip,
277 offset: usize,
278 ) -> Self {
279 assert_eq!(NUM_LIMBS % 2, 0, "Number of limbs must be divisible by 2");
280 Self {
281 adapter,
282 offset,
283 bitwise_lookup_chip,
284 range_checker_chip,
285 }
286 }
287}
288
289impl<F, A, RA, const NUM_LIMBS: usize, const LIMB_BITS: usize> PreflightExecutor<F, RA>
290 for ShiftExecutor<A, NUM_LIMBS, LIMB_BITS>
291where
292 F: PrimeField32,
293 A: 'static
294 + AdapterTraceExecutor<
295 F,
296 ReadData: Into<[[u8; NUM_LIMBS]; 2]>,
297 WriteData: From<[[u8; NUM_LIMBS]; 1]>,
298 >,
299 for<'buf> RA: RecordArena<
300 'buf,
301 EmptyAdapterCoreLayout<F, A>,
302 (
303 A::RecordMut<'buf>,
304 &'buf mut ShiftCoreRecord<NUM_LIMBS, LIMB_BITS>,
305 ),
306 >,
307{
308 fn get_opcode_name(&self, opcode: usize) -> String {
309 format!("{:?}", ShiftOpcode::from_usize(opcode - self.offset))
310 }
311
312 fn execute(
313 &self,
314 state: VmStateMut<F, TracingMemory, RA>,
315 instruction: &Instruction<F>,
316 ) -> Result<(), ExecutionError> {
317 let Instruction { opcode, .. } = instruction;
318
319 let local_opcode = ShiftOpcode::from_usize(opcode.local_opcode_idx(self.offset));
320
321 let (mut adapter_record, core_record) = state.ctx.alloc(EmptyAdapterCoreLayout::new());
322
323 A::start(*state.pc, state.memory, &mut adapter_record);
324
325 let [rs1, rs2] = self
326 .adapter
327 .read(state.memory, instruction, &mut adapter_record)
328 .into();
329
330 let (output, _, _) = run_shift::<NUM_LIMBS, LIMB_BITS>(local_opcode, &rs1, &rs2);
331
332 core_record.b = rs1;
333 core_record.c = rs2;
334 core_record.local_opcode = local_opcode as u8;
335
336 self.adapter.write(
337 state.memory,
338 instruction,
339 [output].into(),
340 &mut adapter_record,
341 );
342 *state.pc = state.pc.wrapping_add(DEFAULT_PC_STEP);
343
344 Ok(())
345 }
346}
347
348impl<F, A, const NUM_LIMBS: usize, const LIMB_BITS: usize> TraceFiller<F>
349 for ShiftFiller<A, NUM_LIMBS, LIMB_BITS>
350where
351 F: PrimeField32,
352 A: 'static + AdapterTraceFiller<F>,
353{
354 fn fill_trace_row(&self, mem_helper: &MemoryAuxColsFactory<F>, row_slice: &mut [F]) {
355 let (adapter_row, mut core_row) = unsafe { row_slice.split_at_mut_unchecked(A::WIDTH) };
358 self.adapter.fill_trace_row(mem_helper, adapter_row);
359 let record: &ShiftCoreRecord<NUM_LIMBS, LIMB_BITS> =
362 unsafe { get_record_from_slice(&mut core_row, ()) };
363
364 let core_row: &mut ShiftCoreCols<F, NUM_LIMBS, LIMB_BITS> = core_row.borrow_mut();
365
366 let opcode = ShiftOpcode::from_usize(record.local_opcode as usize);
367 let (a, limb_shift, bit_shift) =
368 run_shift::<NUM_LIMBS, LIMB_BITS>(opcode, &record.b, &record.c);
369
370 for pair in a.chunks_exact(2) {
371 self.bitwise_lookup_chip
372 .request_range(pair[0] as u32, pair[1] as u32);
373 }
374
375 let num_bits_log = (NUM_LIMBS * LIMB_BITS).ilog2();
376 self.range_checker_chip.add_count(
377 ((record.c[0] as usize - bit_shift - limb_shift * LIMB_BITS) >> num_bits_log) as u32,
378 LIMB_BITS - num_bits_log as usize,
379 );
380
381 core_row.bit_shift_carry = if bit_shift == 0 {
382 for _ in 0..NUM_LIMBS {
383 self.range_checker_chip.add_count(0, 0);
384 }
385 [F::ZERO; NUM_LIMBS]
386 } else {
387 array::from_fn(|i| {
388 let carry = match opcode {
389 ShiftOpcode::SLL => record.b[i] >> (LIMB_BITS - bit_shift),
390 _ => record.b[i] % (1 << bit_shift),
391 };
392 self.range_checker_chip.add_count(carry as u32, bit_shift);
393 F::from_u8(carry)
394 })
395 };
396
397 core_row.limb_shift_marker = [F::ZERO; NUM_LIMBS];
398 core_row.limb_shift_marker[limb_shift] = F::ONE;
399 core_row.bit_shift_marker = [F::ZERO; LIMB_BITS];
400 core_row.bit_shift_marker[bit_shift] = F::ONE;
401
402 core_row.b_sign = F::ZERO;
403 if opcode == ShiftOpcode::SRA {
404 core_row.b_sign = F::from_u8(record.b[NUM_LIMBS - 1] >> (LIMB_BITS - 1));
405 self.bitwise_lookup_chip
406 .request_xor(record.b[NUM_LIMBS - 1] as u32, 1 << (LIMB_BITS - 1));
407 }
408
409 core_row.bit_multiplier_right = match opcode {
410 ShiftOpcode::SLL => F::ZERO,
411 _ => F::from_usize(1 << bit_shift),
412 };
413 core_row.bit_multiplier_left = match opcode {
414 ShiftOpcode::SLL => F::from_usize(1 << bit_shift),
415 _ => F::ZERO,
416 };
417
418 core_row.opcode_sra_flag = F::from_bool(opcode == ShiftOpcode::SRA);
419 core_row.opcode_srl_flag = F::from_bool(opcode == ShiftOpcode::SRL);
420 core_row.opcode_sll_flag = F::from_bool(opcode == ShiftOpcode::SLL);
421
422 core_row.c = record.c.map(F::from_u8);
423 core_row.b = record.b.map(F::from_u8);
424 core_row.a = a.map(F::from_u8);
425 }
426}
427
428#[inline(always)]
430pub(super) fn run_shift<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
431 opcode: ShiftOpcode,
432 x: &[u8; NUM_LIMBS],
433 y: &[u8; NUM_LIMBS],
434) -> ([u8; NUM_LIMBS], usize, usize) {
435 match opcode {
436 ShiftOpcode::SLL => run_shift_left::<NUM_LIMBS, LIMB_BITS>(x, y),
437 ShiftOpcode::SRL => run_shift_right::<NUM_LIMBS, LIMB_BITS>(x, y, true),
438 ShiftOpcode::SRA => run_shift_right::<NUM_LIMBS, LIMB_BITS>(x, y, false),
439 }
440}
441
442#[inline(always)]
443fn run_shift_left<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
444 x: &[u8; NUM_LIMBS],
445 y: &[u8; NUM_LIMBS],
446) -> ([u8; NUM_LIMBS], usize, usize) {
447 let mut result = [0u8; NUM_LIMBS];
448
449 let (limb_shift, bit_shift) = get_shift::<NUM_LIMBS, LIMB_BITS>(y);
450
451 for i in limb_shift..NUM_LIMBS {
452 result[i] = if i > limb_shift {
453 (((x[i - limb_shift] as u16) << bit_shift)
454 | ((x[i - limb_shift - 1] as u16) >> (LIMB_BITS - bit_shift)))
455 % (1u16 << LIMB_BITS)
456 } else {
457 ((x[i - limb_shift] as u16) << bit_shift) % (1u16 << LIMB_BITS)
458 } as u8;
459 }
460 (result, limb_shift, bit_shift)
461}
462
463#[inline(always)]
464fn run_shift_right<const NUM_LIMBS: usize, const LIMB_BITS: usize>(
465 x: &[u8; NUM_LIMBS],
466 y: &[u8; NUM_LIMBS],
467 logical: bool,
468) -> ([u8; NUM_LIMBS], usize, usize) {
469 let fill = if logical {
470 0
471 } else {
472 (((1u16 << LIMB_BITS) - 1) as u8) * (x[NUM_LIMBS - 1] >> (LIMB_BITS - 1))
473 };
474 let mut result = [fill; NUM_LIMBS];
475
476 let (limb_shift, bit_shift) = get_shift::<NUM_LIMBS, LIMB_BITS>(y);
477
478 for i in 0..(NUM_LIMBS - limb_shift) {
479 let res = if i + limb_shift + 1 < NUM_LIMBS {
480 (((x[i + limb_shift] >> bit_shift) as u16)
481 | ((x[i + limb_shift + 1] as u16) << (LIMB_BITS - bit_shift)))
482 % (1u16 << LIMB_BITS)
483 } else {
484 (((x[i + limb_shift] >> bit_shift) as u16) | ((fill as u16) << (LIMB_BITS - bit_shift)))
485 % (1u16 << LIMB_BITS)
486 };
487 result[i] = res as u8;
488 }
489 (result, limb_shift, bit_shift)
490}
491
492#[inline(always)]
493fn get_shift<const NUM_LIMBS: usize, const LIMB_BITS: usize>(y: &[u8]) -> (usize, usize) {
494 debug_assert!(NUM_LIMBS * LIMB_BITS <= (1 << LIMB_BITS));
495 let shift = (y[0] as usize) % (NUM_LIMBS * LIMB_BITS);
498 (shift / LIMB_BITS, shift % LIMB_BITS)
499}