use std::sync::{
atomic::{AtomicBool, AtomicU64},
Arc, Mutex, RwLock,
};
use proofman_fields::PrimeField64;
use rayon::{
iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator},
slice::ParallelSliceMut,
prelude::*,
};
use proofman_witness::WitnessComponent;
use proofman_common::{AirInstance, BufferPool, ProofCtx, ProofmanResult, SetupCtx, TraceInfo};
use std::sync::atomic::Ordering;
use crate::AirComponent;
const P2_8: usize = 256;
pub struct U8Air<F: PrimeField64> {
airgroup_id: usize,
air_id: usize,
shift: usize,
mask: usize,
num_rows: usize,
num_cols: usize,
multiplicities: Vec<AtomicU64>,
table_instance_id: AtomicU64,
calculated: AtomicBool,
shared_tables: bool,
trace_buffer: Arc<Mutex<Option<Vec<F>>>>,
}
impl<F: PrimeField64> AirComponent<F> for U8Air<F> {
fn new(
pctx: &ProofCtx<F>,
_sctx: &SetupCtx<F>,
airgroup_id: usize,
air_id: usize,
shared_tables: bool,
) -> ProofmanResult<Arc<Self>> {
let num_rows = pctx.global_info.airs[airgroup_id][air_id].num_rows;
let num_cols = P2_8.div_ceil(num_rows);
let multiplicities: Vec<AtomicU64> =
(0..(num_cols * num_rows)).into_par_iter().map(|_| AtomicU64::new(0)).collect();
let trace_buffer = Arc::new(Mutex::new(Some(vec![F::ZERO; num_cols * num_rows])));
Ok(Arc::new(Self {
airgroup_id,
air_id,
shift: num_rows.trailing_zeros() as usize,
mask: num_rows - 1,
num_rows,
num_cols,
multiplicities,
table_instance_id: AtomicU64::new(0),
calculated: AtomicBool::new(false),
shared_tables,
trace_buffer,
}))
}
}
impl<F: PrimeField64> U8Air<F> {
pub const fn get_global_row(value: u8) -> u64 {
value as u64
}
pub fn get_global_rows(values: &[u8]) -> Vec<u64> {
values.iter().map(|&v| Self::get_global_row(v)).collect()
}
pub fn get_global_rows_into(values: &[u8], out: &mut Vec<u64>) {
out.clear();
out.extend(values.iter().map(|&v| Self::get_global_row(v)));
}
#[inline]
fn update(&self, iter: impl Iterator<Item = (u8, u64)>) {
if self.calculated.load(Ordering::Relaxed) {
return;
}
for (value, multiplicity) in iter {
if multiplicity == 0 {
continue;
}
let value = value as usize;
let range_idx = value >> self.shift;
let row_idx = value & self.mask;
self.multiplicities[range_idx * self.num_rows + row_idx].fetch_add(multiplicity, Ordering::Relaxed);
}
}
pub fn update_value(&self, value: u8, multiplicity: u64) {
self.update(std::iter::once((value, multiplicity)));
}
pub fn update_values(&self, values: &[u8], multiplicities: &[u64]) {
debug_assert_eq!(values.len(), multiplicities.len());
self.update(values.iter().copied().zip(multiplicities.iter().copied()));
}
pub fn update_values_same_mul(&self, values: &[u8], multiplicity: u64) {
self.update(values.iter().copied().map(|v| (v, multiplicity)));
}
pub fn update_pairs(&self, pairs: impl Iterator<Item = (u8, u64)>) {
self.update(pairs);
}
pub fn airgroup_id(&self) -> usize {
self.airgroup_id
}
pub fn air_id(&self) -> usize {
self.air_id
}
}
impl<F: PrimeField64 + Send + Sync + 'static> WitnessComponent<F> for U8Air<F> {
fn execute(
&self,
pctx: Arc<ProofCtx<F>>,
_sctx: Arc<SetupCtx<F>>,
_global_ids: &RwLock<Vec<usize>>,
) -> ProofmanResult<()> {
let (instance_found, mut table_instance_id) = pctx.dctx_find_process_table(self.airgroup_id, self.air_id)?;
if !instance_found {
if !self.shared_tables {
table_instance_id = pctx.add_table_all(self.airgroup_id, self.air_id)?;
} else {
table_instance_id = pctx.add_table(self.airgroup_id, self.air_id)?;
}
}
self.calculated.store(false, Ordering::Relaxed);
self.multiplicities.par_iter().for_each(|v| {
v.store(0, Ordering::Relaxed);
});
self.table_instance_id.store(table_instance_id as u64, Ordering::SeqCst);
Ok(())
}
fn pre_calculate_witness(
&self,
_stage: u32,
_pctx: Arc<ProofCtx<F>>,
_sctx: Arc<SetupCtx<F>>,
_instance_ids: &[usize],
_n_cores: usize,
_buffer_pool: &dyn BufferPool<F>,
) -> ProofmanResult<()> {
Ok(())
}
fn calculate_witness(
&self,
stage: u32,
pctx: Arc<ProofCtx<F>>,
sctx: Arc<SetupCtx<F>>,
_instance_ids: &[usize],
_n_cores: usize,
_buffer_pool: &dyn BufferPool<F>,
) -> ProofmanResult<()> {
if stage == 1 {
let table_instance_id = self.table_instance_id.load(Ordering::Relaxed) as usize;
let instance_id = pctx.dctx_get_table_instance_idx(table_instance_id)?;
if !_instance_ids.contains(&instance_id) {
return Ok(());
}
self.calculated.store(true, Ordering::Relaxed);
if self.shared_tables {
let owner_idx = pctx.dctx_get_process_owner_instance(instance_id)?;
pctx.mpi_ctx.distribute_multiplicities(&self.multiplicities, self.num_cols, self.num_rows, owner_idx);
}
if !self.shared_tables || pctx.dctx_is_my_process_instance(instance_id)? {
let buffer_size = self.num_cols * self.num_rows;
let mut buffer = self
.trace_buffer
.lock()
.unwrap()
.take()
.expect("U8Air trace_buffer must be populated by reclaim before calculate_witness");
debug_assert_eq!(buffer.len(), buffer_size);
let any_nonzero = AtomicBool::new(false);
let num_rows = self.num_rows;
buffer.par_chunks_mut(self.num_cols).enumerate().for_each(|(row, chunk)| {
for (col, slot) in chunk.iter_mut().enumerate() {
let v = self.multiplicities[col * num_rows + row].load(Ordering::Relaxed);
if v != 0 {
any_nonzero.store(true, Ordering::Relaxed);
}
*slot = F::from_u64(v);
}
});
if !any_nonzero.load(Ordering::Relaxed) {
tracing::info!(
"Skipping uninitialized U8 range check table (airgroup_id: {}, air_id: {})",
self.airgroup_id,
self.air_id
);
pctx.dctx_skip_process_instance(instance_id);
*self.trace_buffer.lock().unwrap() = Some(buffer);
return Ok(());
}
let setup = sctx.get_setup(self.airgroup_id, self.air_id)?;
let n_cols = setup.stark_info.map_sections_n["cm1"] as usize;
let air_instance = AirInstance::new(
TraceInfo::new(self.airgroup_id, self.air_id, n_cols, self.num_rows, buffer, false, false)
.with_reclaim_slot(self.trace_buffer.clone()),
);
pctx.add_air_instance(air_instance, instance_id);
}
}
Ok(())
}
}