use p3_maybe_rayon::prelude::*;
use p3_util::{log2_ceil_usize, log2_floor_usize};
use super::twiddles::Twiddles;
use crate::butterfly::ButterflyField;
use crate::staging::{
Dispatch, StagedRuns, for_each_staged_tile, for_each_staged_tile_into_cosets, prefault,
};
pub(crate) const BUTTERFLY_GRAIN: usize = 1 << 10;
pub(super) const DEEP_TILE_BYTES: usize = 128 * 1024;
const STAGING_TILE_BYTES: usize = 64 * 1024;
pub(super) const MIN_FUSED_STAGES: usize = 2;
pub(super) const STAGED_WORKERS: usize = 16;
const CACHE_LINE_BYTES: usize = 64;
#[derive(Copy, Clone, Debug)]
pub(super) struct Schedule {
pub(super) log_tile_rows: usize,
pub(super) log_staged_rows: usize,
pub(super) log_slab_rows: usize,
}
impl Schedule {
pub(super) fn new<F>(width: usize, log_n: usize) -> Self {
Self::for_workers::<F>(width, log_n, current_num_threads())
}
pub(super) fn for_workers<F>(width: usize, log_n: usize, workers: usize) -> Self {
let row_bytes = size_of::<F>() * width;
let capacity = log2_floor_usize((DEEP_TILE_BYTES / row_bytes).max(1));
let log_tile_rows = capacity.min(log_n.saturating_sub(log2_ceil_usize(workers)));
let log_slab_rows =
log2_ceil_usize(CACHE_LINE_BYTES.div_ceil(row_bytes)).min(log_tile_rows);
let slab_bytes = row_bytes << log_slab_rows;
let log_staged_rows = if workers >= STAGED_WORKERS {
log2_floor_usize((STAGING_TILE_BYTES / slab_bytes).max(1))
} else {
0
};
Self {
log_tile_rows,
log_staged_rows,
log_slab_rows,
}
}
}
#[derive(Copy, Clone, Debug)]
struct Cut {
tile: usize,
slab: usize,
group: usize,
groups: usize,
log_n: usize,
}
impl Cut {
const fn new(schedule: &Schedule, log_n: usize) -> Self {
let tile = if schedule.log_tile_rows < log_n {
schedule.log_tile_rows
} else {
log_n
};
let slab = if schedule.log_slab_rows < tile {
schedule.log_slab_rows
} else {
tile
};
let group = if schedule.log_staged_rows >= MIN_FUSED_STAGES {
schedule.log_staged_rows
} else {
1
};
Self {
tile,
slab,
group,
groups: (log_n - tile).div_ceil(group),
log_n,
}
}
const fn bounds(&self, g: usize) -> (usize, usize) {
let low = self.tile + g * self.group;
let high = if low + self.group < self.log_n {
low + self.group
} else {
self.log_n
};
(low, high)
}
}
pub(super) fn stage_pass<F: ButterflyField, const INVERSE: bool>(
values: &mut [F],
width: usize,
j: usize,
twiddles: &Twiddles<F>,
) {
let half = (1 << j) * width;
let per_task = (BUTTERFLY_GRAIN / half).max(1);
let task_len = per_task * (half << 1);
let task = |(task, group): (usize, &mut [F])| {
let first = task * per_task;
let mut t = twiddles.at(j, first);
for (i, block) in group.chunks_mut(half << 1).enumerate() {
if i != 0 {
t += twiddles.step(first + i);
}
let (lo, hi) = block.split_at_mut(half);
if half <= BUTTERFLY_GRAIN {
F::butterfly::<INVERSE>(lo, hi, t);
} else {
lo.par_chunks_mut(BUTTERFLY_GRAIN)
.zip(hi.par_chunks_mut(BUTTERFLY_GRAIN))
.for_each(|(lo, hi)| F::butterfly::<INVERSE>(lo, hi, t));
}
}
};
if values.len() > task_len {
values.par_chunks_mut(task_len).enumerate().for_each(task);
} else {
values.chunks_mut(task_len).enumerate().for_each(task);
}
}
fn tile_stages<F: ButterflyField, const INVERSE: bool>(
tile: &mut [F],
row_len: usize,
log_rows: usize,
stage_base: usize,
index: usize,
twiddles: &Twiddles<F>,
) {
for step in 0..log_rows {
let jj = if INVERSE { step } else { log_rows - 1 - step };
let half = (1 << jj) * row_len;
let first = index << (log_rows - 1 - jj);
let mut t = twiddles.at(stage_base + jj, first);
for (i, block) in tile.chunks_mut(half << 1).enumerate() {
if i != 0 {
t += twiddles.step(first + i);
}
let (lo, hi) = block.split_at_mut(half);
F::butterfly::<INVERSE>(lo, hi, t);
}
}
}
fn deep_tiles<F: ButterflyField, const INVERSE: bool>(
values: &mut [F],
width: usize,
log_rows: usize,
twiddles: &Twiddles<F>,
) {
values
.par_chunks_mut((1 << log_rows) * width)
.enumerate()
.for_each(|(index, tile)| {
tile_stages::<F, INVERSE>(tile, width, log_rows, 0, index, twiddles);
});
}
pub(super) const fn staged_runs(
width: usize,
top: usize,
depth: usize,
log_slab: usize,
) -> StagedRuns {
StagedRuns::new((1 << log_slab) * width, top + 1 - depth - log_slab, depth)
}
fn staged_dispatch(tasks: usize) -> Dispatch {
Dispatch::Parallel {
min_len: (tasks / (4 * current_num_threads())).max(1),
}
}
fn fused_group<F: ButterflyField, const INVERSE: bool>(
values: &mut [F],
width: usize,
top: usize,
depth: usize,
log_slab: usize,
twiddles: &Twiddles<F>,
) {
debug_assert!(depth >= 1 && log_slab + depth <= top + 1);
let row_len = (1 << log_slab) * width;
let tasks = values.len() / (row_len << depth);
for_each_staged_tile(
values,
staged_runs(width, top, depth, log_slab),
staged_dispatch(tasks),
|tile, block| {
tile_stages::<F, INVERSE>(tile, row_len, depth, top + 1 - depth, block, twiddles);
},
);
}
fn group_pass<F: ButterflyField, const INVERSE: bool>(
values: &mut [F],
width: usize,
(low, high): (usize, usize),
log_slab: usize,
twiddles: &Twiddles<F>,
) {
if high - low == 1 {
stage_pass::<F, INVERSE>(values, width, low, twiddles);
} else {
fused_group::<F, INVERSE>(values, width, high - 1, high - low, log_slab, twiddles);
}
}
pub(super) fn run<F: ButterflyField, const INVERSE: bool>(
values: &mut [F],
width: usize,
log_n: usize,
twiddles: &Twiddles<F>,
schedule: &Schedule,
) {
let cut = Cut::new(schedule, log_n);
if INVERSE {
if cut.tile > 0 {
deep_tiles::<F, true>(values, width, cut.tile, twiddles);
}
for g in 0..cut.groups {
group_pass::<F, true>(values, width, cut.bounds(g), cut.slab, twiddles);
}
} else {
forward_below(values, width, cut, cut.groups, twiddles);
}
}
fn forward_below<F: ButterflyField>(
values: &mut [F],
width: usize,
cut: Cut,
groups: usize,
twiddles: &Twiddles<F>,
) {
for g in (0..groups).rev() {
group_pass::<F, false>(values, width, cut.bounds(g), cut.slab, twiddles);
}
if cut.tile > 0 {
deep_tiles::<F, false>(values, width, cut.tile, twiddles);
}
}
pub(super) fn run_cosets<F: ButterflyField>(
values: &mut [F],
width: usize,
log_message: usize,
twiddles: &[Twiddles<F>],
schedule: &Schedule,
) {
let len = width << log_message;
let cut = Cut::new(schedule, log_message);
prefault(&mut values[len..], F::ZERO);
if cut.groups == 0 {
let runs = StagedRuns::new(width, 0, cut.tile);
let tasks = len / (width << cut.tile);
for_each_staged_tile_into_cosets(
values,
None,
len,
runs,
staged_dispatch(tasks),
|_| {},
|tile, index, coset| {
tile_stages::<F, false>(tile, width, cut.tile, 0, index, &twiddles[coset]);
},
);
return;
}
let (low, high) = cut.bounds(cut.groups - 1);
let (top, depth) = (high - 1, high - low);
let row_len = (1 << cut.slab) * width;
let tasks = len / (row_len << depth);
for_each_staged_tile_into_cosets(
values,
None,
len,
staged_runs(width, top, depth, cut.slab),
staged_dispatch(tasks),
|_| {},
|tile, block, coset| {
tile_stages::<F, false>(tile, row_len, depth, low, block, &twiddles[coset]);
},
);
values
.par_chunks_mut(len)
.zip(twiddles.par_iter())
.for_each(|(coset, twiddles)| {
forward_below(coset, width, cut, cut.groups - 1, twiddles);
});
}