use alloc::vec::Vec;
use p3_binary_field::poly_basis::{LOW_STAGES, LowStageTwiddles};
use p3_binary_field::{BinaryField128, TowerLevel, poly_basis};
use p3_maybe_rayon::prelude::*;
use super::plan::{Plan, SHARED_CACHE_BYTES};
use crate::domain::domain_point;
use crate::lch::BUTTERFLY_GRAIN;
use crate::staging::{
Dispatch, StagedRuns, Store, for_each_staged_tile, for_each_staged_tile_into_cosets_stored,
prefault,
};
pub(super) struct Twiddles {
basis: [u128; usize::BITS as usize],
pub(super) deltas: [u128; usize::BITS as usize],
pub(super) shifts: [u128; usize::BITS as usize],
}
impl Twiddles {
pub(super) fn new(log_n: usize, shift: BinaryField128) -> Self {
let mut result = Self {
basis: [0; usize::BITS as usize],
deltas: [0; usize::BITS as usize],
shifts: [0; usize::BITS as usize],
};
let mut delta = 0;
let mut base = poly_basis::from_tower(shift);
for j in 0..log_n {
result.basis[j] = poly_basis::from_tower(BinaryField128::cantor_basis(j + 1));
delta ^= result.basis[j];
result.deltas[j] = delta;
result.shifts[j] = base;
base = poly_basis::square(base) ^ base;
}
result
}
pub(super) const fn at(&self, stage: usize, mut block: usize) -> u128 {
let mut t = self.shifts[stage];
while block != 0 {
t ^= self.basis[block.trailing_zeros() as usize];
block &= block - 1;
}
t
}
pub(super) fn basis(&self, log_n: usize) -> &[u128] {
&self.basis[..log_n]
}
}
#[allow(clippy::missing_const_for_fn)]
fn use_parallel(elements: usize) -> bool {
let threads = current_num_threads();
threads > 1 && elements >= 2 * BUTTERFLY_GRAIN * threads
}
pub(super) fn for_chunks(
values: &mut [u128],
chunk_len: usize,
stages: usize,
operation: impl Fn((usize, &mut [u128])) + Send + Sync,
) {
if values.len() > chunk_len && use_parallel(values.len().saturating_mul(stages.max(1))) {
values
.par_chunks_mut(chunk_len)
.enumerate()
.for_each(operation);
} else {
values.chunks_mut(chunk_len).enumerate().for_each(operation);
}
}
const COSET_COPY_GRAIN: usize = 1 << 16;
pub(super) fn copy_coset(dst: &mut [u128], src: &[u128]) {
if dst.len() > COSET_COPY_GRAIN && use_parallel(dst.len()) {
dst.par_chunks_mut(COSET_COPY_GRAIN)
.zip(src.par_chunks(COSET_COPY_GRAIN))
.for_each(|(d, s)| d.copy_from_slice(s));
} else {
dst.copy_from_slice(src);
}
}
pub(super) type Conversion = fn(&mut [u128]);
pub(crate) const INTO_POLY: Conversion = poly_basis::from_tower_slice;
pub(crate) const INTO_TOWER: Conversion = poly_basis::to_tower_slice;
pub(crate) fn convert(values: &mut [u128], conversion: Conversion) {
if use_parallel(values.len().saturating_mul(4)) {
values.par_chunks_mut(BUTTERFLY_GRAIN).for_each(conversion);
} else {
conversion(values);
}
}
#[derive(Copy, Clone, Debug)]
pub(super) struct Fold {
pub(super) entry: bool,
pub(super) exit: bool,
}
impl Fold {
pub(super) const BOTH: Self = Self {
entry: true,
exit: true,
};
pub(super) const ENTRY: Self = Self {
entry: true,
exit: false,
};
pub(super) const EXIT: Self = Self {
entry: false,
exit: true,
};
}
pub(super) fn stage(
values: &mut [u128],
half: usize,
j: usize,
twiddles: &Twiddles,
inverse: bool,
) {
if !use_parallel(values.len()) {
local_stage(values, half, j, twiddles, inverse, 0);
return;
}
let blocks_per_chunk = (BUTTERFLY_GRAIN / (half << 1)).max(1);
values
.par_chunks_mut((half << 1) * blocks_per_chunk)
.enumerate()
.for_each(|(chunk_index, chunk)| {
let first = chunk_index * blocks_per_chunk;
let mut t = twiddles.at(j, first);
for (index, block) in chunk.chunks_mut(half << 1).enumerate() {
let (lo, hi) = block.split_at_mut(half);
let butterfly = |lo: &mut [u128], hi: &mut [u128]| {
if inverse {
poly_basis::butterfly_inverse(lo, hi, t);
} else {
poly_basis::butterfly_forward(lo, hi, t);
}
};
if half <= BUTTERFLY_GRAIN {
butterfly(lo, hi);
} else {
lo.par_chunks_mut(BUTTERFLY_GRAIN)
.zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
.for_each(|(lo, hi)| butterfly(lo, hi));
}
t ^= twiddles.deltas[(first + index).trailing_ones() as usize];
}
});
}
fn local_stage(
values: &mut [u128],
half: usize,
j: usize,
twiddles: &Twiddles,
inverse: bool,
first: usize,
) {
let mut t = twiddles.at(j, first);
for (index, block) in values.chunks_mut(half << 1).enumerate() {
let (lo, hi) = block.split_at_mut(half);
if inverse {
poly_basis::butterfly_inverse(lo, hi, t);
} else {
poly_basis::butterfly_forward(lo, hi, t);
}
t ^= twiddles.deltas[(first + index).trailing_ones() as usize];
}
}
fn tile_stages(
tile: &mut [u128],
row: usize,
depth: usize,
top: usize,
twiddles: &Twiddles,
inverse: bool,
block: usize,
) {
for k in 0..depth {
let s = if inverse { depth - 1 - k } else { k };
local_stage(
tile,
(1 << (depth - 1 - s)) * row,
top - 1 - s,
twiddles,
inverse,
block << s,
);
}
}
pub(super) fn local_stages(
values: &mut [u128],
plan: Plan,
twiddles: &Twiddles,
inverse: bool,
fold: Fold,
) {
let Plan {
width,
log_n,
local,
..
} = plan;
let tile_len = (1 << local) * width;
let low = (!inverse && width == 1 && local >= LOW_STAGES)
.then(|| LowStageTwiddles::new(&twiddles.shifts, twiddles.basis(log_n)));
for_chunks(values, tile_len, local, |(index, tile)| {
if fold.entry {
INTO_POLY(tile);
}
if let Some(low) = &low {
let above = local - LOW_STAGES;
tile_stages(tile, 1 << LOW_STAGES, above, local, twiddles, false, index);
low.forward(tile, index << above);
} else {
tile_stages(tile, width, local, local, twiddles, inverse, index);
}
if fold.exit {
INTO_TOWER(tile);
}
});
}
pub(super) fn fused_stages(
values: &mut [u128],
plan: Plan,
top: usize,
depth: usize,
twiddles: &Twiddles,
inverse: bool,
convert_basis: bool,
) {
let (runs, run, dispatch) = staged_group(plan, top, depth, values.len());
for_each_staged_tile(values, runs, dispatch, |tile, block| {
if convert_basis && !inverse {
INTO_POLY(tile);
}
tile_stages(tile, run, depth, top, twiddles, inverse, block);
if convert_basis && inverse {
INTO_TOWER(tile);
}
});
}
fn staged_group(
plan: Plan,
top: usize,
depth: usize,
elements: usize,
) -> (StagedRuns, usize, Dispatch) {
let log_block = plan.log_block.min(top - depth);
let run = plan.width << log_block;
let dispatch = if use_parallel(elements.saturating_mul(depth)) {
Dispatch::Parallel { min_len: 1 }
} else {
Dispatch::Serial
};
let runs = StagedRuns::new(run, top - log_block - depth, depth);
(runs, run, dispatch)
}
pub(super) fn first_group_into_cosets(
values: &mut [u128],
source: Option<&[u128]>,
message_len: usize,
plan: Plan,
depth: usize,
twiddles: &[Twiddles],
) {
let top = plan.log_n;
let (runs, run, dispatch) = staged_group(plan, top, depth, values.len());
let store = if cfg!(all(target_arch = "x86_64", target_feature = "avx512f"))
&& size_of_val(values) > SHARED_CACHE_BYTES
{
Store::Streamed
} else {
Store::Cached
};
for_each_staged_tile_into_cosets_stored(
values,
source,
message_len,
runs,
dispatch,
store,
INTO_POLY,
|tile, block, coset| tile_stages(tile, run, depth, top, &twiddles[coset], false, block),
);
}
pub(super) fn padded_sharing_first_group(
values: &mut [u128],
source: Option<&[u128]>,
plan: Plan,
depth: usize,
log_inv_rate: usize,
) {
let log_message = plan.log_n;
let len = values.len() >> log_inv_rate;
let twiddles: Vec<Twiddles> = (0..1 << log_inv_rate)
.map(|c| Twiddles::new(log_message, domain_point(c << log_message)))
.collect();
let first_written = if source.is_some() { 0 } else { len };
prefault(&mut values[first_written..], 0);
first_group_into_cosets(values, source, len, plan, depth, &twiddles);
for_chunks(values, len, log_message, |(c, coset)| {
forward_below(coset, plan, &twiddles[c], 1, Fold::EXIT);
});
}
pub(super) fn forward(values: &mut [u128], plan: Plan, shift: BinaryField128, fold: Fold) {
forward_below(values, plan, &Twiddles::new(plan.log_n, shift), 0, fold);
}
pub(super) fn forward_below(
values: &mut [u128],
plan: Plan,
twiddles: &Twiddles,
done: usize,
fold: Fold,
) {
let Plan {
width,
log_n,
local,
..
} = plan;
debug_assert!(
done == 0 || !fold.entry,
"the entry conversion rides on the first group"
);
let mut entry = fold.entry;
let mut top = log_n;
for (group, take) in plan.group_sizes().enumerate() {
if group >= done {
fused_stages(values, plan, top, take, twiddles, false, entry);
entry = false;
}
top -= take;
}
if entry && top > local {
convert(values, INTO_POLY);
entry = false;
}
for j in (local..top).rev() {
stage(values, (1 << j) * width, j, twiddles, false);
}
local_stages(
values,
plan,
twiddles,
false,
Fold {
entry,
exit: fold.exit,
},
);
}
pub(super) fn inverse(values: &mut [u128], plan: Plan, shift: BinaryField128, fold: Fold) {
let Plan {
width,
log_n,
local,
..
} = plan;
let twiddles = Twiddles::new(log_n, shift);
let leftover = plan.leftover();
let tile_exit = fold.exit && local == log_n;
let mut exit = fold.exit && !tile_exit;
local_stages(
values,
plan,
&twiddles,
true,
Fold {
entry: fold.entry,
exit: tile_exit,
},
);
for j in local..local + leftover {
stage(values, (1 << j) * width, j, &twiddles, true);
}
let mut sizes = [0; usize::BITS as usize];
let mut count = 0;
for take in plan.group_sizes() {
sizes[count] = take;
count += 1;
}
let mut base = local + leftover;
for (index, take) in sizes[..count].iter().rev().enumerate() {
base += take;
let last = index + 1 == count;
fused_stages(values, plan, base, *take, &twiddles, true, exit && last);
exit &= !last;
}
if exit {
convert(values, INTO_TOWER);
}
}