use crate::{fill_and_tally, lanes_x_row::MAX_ADD_HI, BinaryInput};
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::{
BinaryAddHiAirValues, BinaryAddHiHugeAirValues, BinaryAddHiHugeTrace,
BinaryAddHiHugeTraceRowOps, BinaryAddHiLargeAirValues, BinaryAddHiLargeTrace,
BinaryAddHiLargeTraceRowOps, BinaryAddHiTrace, BinaryAddHiTraceRowOps,
};
const MASK_32: u64 = 0x0000_0000_FFFF_FFFF;
pub const CHUNKS_X_ADD: usize = 2;
pub trait BinaryAddHiRow<F: PrimeField64, T>: Default + Copy + Send + Sync {
const LANES_X_ROW: usize;
fn set_slots(
&mut self,
a: &[u32],
b: &[u32],
c_chunks: &[[u16; CHUNKS_X_ADD]],
sel: &[bool],
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_hi_row {
($row_ops:ident, $trace:ident, $air_values:ident, $adds:expr) => {
impl<F: PrimeField64, R: $row_ops<F>> BinaryAddHiRow<F, $trace<R>> for R {
const LANES_X_ROW: usize = $adds;
#[inline(always)]
fn set_slots(
&mut self,
a: &[u32],
b: &[u32],
c_chunks: &[[u16; CHUNKS_X_ADD]],
sel: &[bool],
sh3add: &[bool],
) {
self.set_all_a(a.try_into().expect("a must hold LANES_X_ROW slots"));
self.set_all_b(b.try_into().expect("b must hold LANES_X_ROW slots"));
self.set_all_c_chunks(
c_chunks.try_into().expect("c_chunks must hold LANES_X_ROW slots"),
);
self.set_all_sel_b_hi_is_ff(
sel.try_into().expect("sel must hold LANES_X_ROW slots"),
);
self.set_all_sel_sh3add(
sh3add.try_into().expect("sh3add must hold LANES_X_ROW slots"),
);
}
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_hi_row!(
BinaryAddHiTraceRowOps,
BinaryAddHiTrace,
BinaryAddHiAirValues,
crate::lanes_x_row::ADD_HI
);
impl_binary_add_hi_row!(
BinaryAddHiLargeTraceRowOps,
BinaryAddHiLargeTrace,
BinaryAddHiLargeAirValues,
crate::lanes_x_row::ADD_HI_LARGE
);
impl_binary_add_hi_row!(
BinaryAddHiHugeTraceRowOps,
BinaryAddHiHugeTrace,
BinaryAddHiHugeAirValues,
crate::lanes_x_row::ADD_HI_HUGE
);
#[inline]
pub fn rows_needed(num_ops: u64, lanes_x_row: usize) -> u64 {
num_ops.div_ceil(lanes_x_row as u64)
}
#[inline]
pub fn ops_per_instance(num_rows: u64, lanes_x_row: usize) -> u64 {
lanes_x_row as u64 * num_rows
}
pub struct BinaryAddHiSM<F: PrimeField64> {
std: Arc<Std<F>>,
range_id: usize,
}
impl<F: PrimeField64> BinaryAddHiSM<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)]
#[allow(clippy::too_many_arguments)]
fn process_slot(
input: &BinaryInput,
a_values: &mut [u32; MAX_ADD_HI],
b_values: &mut [u32; MAX_ADD_HI],
c_chunks_values: &mut [[u16; CHUNKS_X_ADD]; MAX_ADD_HI],
sel_values: &mut [bool; MAX_ADD_HI],
sh3add_values: &mut [bool; MAX_ADD_HI],
slot: usize,
) -> [u64; CHUNKS_X_ADD] {
let sh3add = input.op == ZiskOp::Sh3add.code();
let scale = if sh3add { 8u64 } else { 1u64 };
let a = input.a & MASK_32;
let b = input.b & MASK_32;
let sum = scale * a + b;
let c = sum & MASK_32;
let carry = sum >> 32;
debug_assert!(
carry <= 1,
"BinaryAddHi: carry {carry} out of the low limb does not fit in a bit \
(op={:#x} a={:#x} b={:#x}); the shape classifiers should have kept this out",
input.op,
input.a,
input.b,
);
a_values[slot] = a as u32;
b_values[slot] = b as u32;
c_chunks_values[slot][0] = (c & 0xFFFF) as u16;
c_chunks_values[slot][1] = (c >> 16) as u16;
sel_values[slot] = carry != 0;
sh3add_values[slot] = sh3add;
[c_chunks_values[slot][0] as u64, c_chunks_values[slot][1] as u64]
}
pub fn compute_witness<T, R: BinaryAddHiRow<F, T>>(
&self,
inputs: &[Vec<BinaryInput>],
trace_buffer: Vec<F>,
) -> ProofmanResult<AirInstance<F>> {
let lanes_x_row = R::LANES_X_ROW;
debug_assert!(lanes_x_row <= MAX_ADD_HI);
let mut add_trace = R::new_trace(trace_buffer)?;
let num_rows = R::trace_num_rows(&add_trace);
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: "BinaryAddHi",
inputs: flat_inputs.len(),
flatten: __t.elapsed(),
started: std::time::Instant::now(),
};
let total_inputs = flat_inputs.len();
let rows_used = rows_needed(total_inputs as u64, lanes_x_row) as usize;
debug_assert!(rows_used <= num_rows, "{} <= {}", rows_used, num_rows);
tracing::debug!(
"··· Creating BinaryAddHi instance [{} ops in {} / {} rows filled {:.2}%]",
total_inputs,
rows_used,
num_rows,
rows_used as f64 / num_rows as f64 * 100.0
);
let chunks_x_row = CHUNKS_X_ADD * lanes_x_row;
let mut multiplicities = fill_and_tally(
&mut R::trace_buffer_mut(&mut add_trace)[..rows_used],
&flat_inputs,
lanes_x_row,
|trace_row, row_inputs, multiplicities| {
let mut a_values = [0u32; MAX_ADD_HI];
let mut b_values = [0u32; MAX_ADD_HI];
let mut c_chunks_values = [[0u16; CHUNKS_X_ADD]; MAX_ADD_HI];
let mut sel_values = [false; MAX_ADD_HI];
let mut sh3add_values = [false; MAX_ADD_HI];
for (slot, input) in row_inputs.iter().enumerate() {
let chunks = Self::process_slot(
input,
&mut a_values,
&mut b_values,
&mut c_chunks_values,
&mut sel_values,
&mut sh3add_values,
slot,
);
multiplicities[chunks[0] as usize] += 1;
multiplicities[chunks[1] as usize] += 1;
}
trace_row.set_slots(
&a_values[..lanes_x_row],
&b_values[..lanes_x_row],
&c_chunks_values[..lanes_x_row],
&sel_values[..lanes_x_row],
&sh3add_values[..lanes_x_row],
);
},
);
multiplicities[0] += (chunks_x_row * num_rows - CHUNKS_X_ADD * total_inputs) as u32;
debug_assert_eq!(
multiplicities.iter().map(|&m| m as u64).sum::<u64>(),
(chunks_x_row * num_rows) as u64,
"the multiplicities must account for one chunk of every slot of every row",
);
self.std.range_check_ranged(self.range_id, None, &multiplicities);
let padding_size = lanes_x_row * num_rows - total_inputs;
Ok(R::into_air_instance(&mut add_trace, rows_used, padding_size))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lanes_x_row::{ADD_HI, ADD_HI_HUGE, ADD_HI_LARGE};
#[test]
fn rows_needed_packs_ops_per_row() {
for lanes_x_row in [ADD_HI, ADD_HI_LARGE, ADD_HI_HUGE] {
assert_eq!(rows_needed(0, lanes_x_row), 0);
assert_eq!(rows_needed(1, lanes_x_row), 1);
assert_eq!(rows_needed(lanes_x_row as u64, lanes_x_row), 1);
assert_eq!(rows_needed(lanes_x_row as u64 + 1, lanes_x_row), 2);
}
}
#[test]
fn rows_needed_is_tight() {
for lanes_x_row in [ADD_HI, ADD_HI_LARGE, ADD_HI_HUGE] {
for num_ops in 0..100u64 {
let rows = rows_needed(num_ops, lanes_x_row);
assert!(rows * lanes_x_row as u64 >= num_ops);
assert!(rows == 0 || ((rows - 1) * lanes_x_row as u64) < num_ops);
}
}
}
#[test]
fn ops_per_instance_matches_the_packing() {
for lanes_x_row in [ADD_HI, ADD_HI_LARGE, ADD_HI_HUGE] {
assert_eq!(rows_needed(ops_per_instance(10, lanes_x_row), lanes_x_row), 10);
}
}
#[test]
fn the_row_buffers_hold_the_widest_packing() {
const {
assert!(ADD_HI <= MAX_ADD_HI);
assert!(ADD_HI_LARGE <= MAX_ADD_HI);
assert!(ADD_HI_HUGE <= MAX_ADD_HI, "the fixed-size row buffers must hold every air");
}
}
}