use crate::{fill_and_tally, BinaryInput, BinaryLanes};
use pil2_std_lib::Std;
use proofman_common::{AirInstance, FromTrace, ProofmanResult};
use proofman_fields::PrimeField64;
use rayon::prelude::*;
use std::sync::Arc;
use zisk_core::zisk_ops::ZiskOp;
use zisk_pil::{
BinaryAddAirValues, BinaryAddHugeAirValues, BinaryAddHugeTrace, BinaryAddHugeTraceRowOps,
BinaryAddLargeAirValues, BinaryAddLargeTrace, BinaryAddLargeTraceRowOps, BinaryAddTrace,
BinaryAddTraceRowOps,
};
const MASK_U32: u64 = 0x0000_0000_FFFF_FFFF;
pub const LIMBS_X_ADD: usize = 2;
pub const CHUNKS_X_FULL_ADD: usize = LIMBS_X_ADD * 2;
pub trait BinaryAddRow<F: PrimeField64, T>: Default + Copy + Send + Sync {
const LANES_X_ROW: usize;
fn set_slot(
&mut self,
lane: usize,
a: &[u32; LIMBS_X_ADD],
b: &[u32; LIMBS_X_ADD],
c_chunks: &[u16; CHUNKS_X_FULL_ADD],
cout: &[bool; LIMBS_X_ADD],
sh3add: bool,
);
fn new_trace(trace_buffer: Vec<F>) -> ProofmanResult<T>;
fn trace_num_rows(trace: &T) -> usize;
fn trace_buffer_mut(trace: &mut T) -> &mut [Self];
fn into_air_instance(trace: &mut T, rows_used: usize, padding_size: usize) -> AirInstance<F>;
}
macro_rules! impl_binary_add_row {
($row_ops:ident, $trace:ident, $air_values:ident, $lanes:expr) => {
impl<F: PrimeField64, R: $row_ops<F>> BinaryAddRow<F, $trace<R>> for R {
const LANES_X_ROW: usize = $lanes;
#[inline(always)]
fn set_slot(
&mut self,
lane: usize,
a: &[u32; LIMBS_X_ADD],
b: &[u32; LIMBS_X_ADD],
c_chunks: &[u16; CHUNKS_X_FULL_ADD],
cout: &[bool; LIMBS_X_ADD],
sh3add: bool,
) {
for i in 0..LIMBS_X_ADD {
self.set_a(lane, i, a[i]);
self.set_b(lane, i, b[i]);
self.set_cout(lane, i, cout[i]);
}
for i in 0..CHUNKS_X_FULL_ADD {
self.set_c_chunks(lane, i, c_chunks[i]);
}
self.set_sel_sh3add(lane, sh3add);
}
fn new_trace(trace_buffer: Vec<F>) -> ProofmanResult<$trace<R>> {
$trace::<R>::new_from_vec(trace_buffer)
}
fn trace_num_rows(trace: &$trace<R>) -> usize {
trace.num_rows()
}
fn trace_buffer_mut(trace: &mut $trace<R>) -> &mut [Self] {
&mut trace.buffer
}
fn into_air_instance(
trace: &mut $trace<R>,
rows_used: usize,
padding_size: usize,
) -> AirInstance<F> {
let num_rows = trace.num_rows();
if rows_used < num_rows {
let padding_row = R::default();
trace.buffer[rows_used..num_rows]
.par_iter_mut()
.for_each(|slot| *slot = padding_row);
}
let mut air_values = $air_values::<F>::new();
air_values.padding_size = F::from_usize(padding_size);
AirInstance::new_from_trace(FromTrace::new(trace).with_air_values(&mut air_values))
}
}
};
}
impl_binary_add_row!(
BinaryAddTraceRowOps,
BinaryAddTrace,
BinaryAddAirValues,
crate::lanes_x_row::ADD
);
impl_binary_add_row!(
BinaryAddLargeTraceRowOps,
BinaryAddLargeTrace,
BinaryAddLargeAirValues,
crate::lanes_x_row::ADD_LARGE
);
impl_binary_add_row!(
BinaryAddHugeTraceRowOps,
BinaryAddHugeTrace,
BinaryAddHugeAirValues,
crate::lanes_x_row::ADD_HUGE
);
pub struct BinaryAddSM<F: PrimeField64> {
std: Arc<Std<F>>,
range_id: usize,
}
impl<F: PrimeField64> BinaryAddSM<F> {
pub fn new(std: Arc<Std<F>>) -> Arc<Self> {
let range_id = std.get_range_id(0, 0xFFFF, None).expect("Failed to get range ID");
Arc::new(Self { std, range_id })
}
#[inline(always)]
pub fn process_slice<T, R: BinaryAddRow<F, T>>(
&self,
row: &mut R,
lane: usize,
input: &BinaryInput,
) -> [u64; CHUNKS_X_FULL_ADD] {
let sh3add = input.op == ZiskOp::Sh3add.code();
let scale = if sh3add { 8u64 } else { 1u64 };
let a = input.a;
let b = input.b;
let mut cin = 0u64;
let mut a_values = [0u32; LIMBS_X_ADD];
let mut b_values = [0u32; LIMBS_X_ADD];
let mut c_chunks_values = [0u16; CHUNKS_X_FULL_ADD];
let mut cout_values = [false; LIMBS_X_ADD];
let mut range_checks = [0u64; CHUNKS_X_FULL_ADD];
for i in 0..LIMBS_X_ADD {
let _a = if i == 0 { a & MASK_U32 } else { a >> 32 };
let _b = if i == 0 { b & MASK_U32 } else { b >> 32 };
let c = scale * _a + _b + cin;
let _c = c & MASK_U32;
debug_assert!(
!sh3add || i == 0 || _a == 0,
"BinaryAdd: SH3ADD with a non-zero limb {i} of a ({:#x}); sh3add_shape should have \
kept this operation out",
input.a,
);
a_values[i] = _a as u32;
b_values[i] = _b as u32;
c_chunks_values[i * 2] = (_c & 0xFFFF) as u16;
c_chunks_values[i * 2 + 1] = (_c >> 16) as u16;
cin = c >> 32;
debug_assert!(
cin <= 1,
"BinaryAdd: carry {cin} out of limb {i} does not fit in a bit \
(op={:#x} a={a:#x} b={b:#x}); sh3add_shape should have kept this operation out",
input.op,
);
cout_values[i] = cin != 0;
range_checks[i * 2] = c_chunks_values[i * 2] as u64;
range_checks[i * 2 + 1] = c_chunks_values[i * 2 + 1] as u64;
}
row.set_slot(lane, &a_values, &b_values, &c_chunks_values, &cout_values, sh3add);
range_checks
}
pub fn compute_witness<T, R: BinaryAddRow<F, T>>(
&self,
inputs: &[Vec<BinaryInput>],
trace_buffer: Vec<F>,
) -> ProofmanResult<AirInstance<F>> {
let lanes = BinaryLanes::new(R::LANES_X_ROW);
let mut add_trace = R::new_trace(trace_buffer)?;
let num_rows = R::trace_num_rows(&add_trace);
let num_slots = lanes.slots(num_rows);
let total_inputs: usize = inputs.iter().map(|c| c.len()).sum();
debug_assert!(
total_inputs <= num_slots,
"BinaryAdd: {total_inputs} operations do not fit in {num_slots} slots",
);
tracing::debug!(
"··· Creating BinaryAdd instance [{} / {} slots filled {:.2}%]",
total_inputs,
num_slots,
total_inputs as f64 / num_slots as f64 * 100.0
);
let __t = std::time::Instant::now();
let mut flat_inputs: Vec<&BinaryInput> =
Vec::with_capacity(inputs.iter().map(|v| v.len()).sum());
flat_inputs.extend(inputs.iter().flatten());
let _report = crate::FlattenReport {
name: "BinaryAdd",
inputs: flat_inputs.len(),
flatten: __t.elapsed(),
started: std::time::Instant::now(),
};
let rows_used = lanes.rows_for(total_inputs);
let mut multiplicities = fill_and_tally(
&mut R::trace_buffer_mut(&mut add_trace)[..rows_used],
&flat_inputs,
R::LANES_X_ROW,
|trace_row, row_inputs, multiplicities| {
for (lane, input) in row_inputs.iter().enumerate() {
for chunk in self.process_slice::<T, R>(trace_row, lane, input) {
multiplicities[chunk as usize] += 1;
}
}
for lane in row_inputs.len()..R::LANES_X_ROW {
trace_row.set_slot(
lane,
&[0; LIMBS_X_ADD],
&[0; LIMBS_X_ADD],
&[0; CHUNKS_X_FULL_ADD],
&[false; LIMBS_X_ADD],
false,
);
}
},
);
let padding_size = num_slots - total_inputs;
multiplicities[0] += (CHUNKS_X_FULL_ADD * padding_size) as u32;
debug_assert_eq!(
multiplicities.iter().map(|&m| m as u64).sum::<u64>(),
CHUNKS_X_FULL_ADD as u64 * num_slots as u64,
"the multiplicities must account for the chunks of every slot",
);
self.std.range_check_ranged(self.range_id, None, &multiplicities);
Ok(R::into_air_instance(&mut add_trace, rows_used, padding_size))
}
}