use std::ops::Range;
use bytemuck::Pod;
use crate::link::{OP_MAX_F64, OP_SUM_F64, OP_SUM_I64};
pub(crate) trait Reduce {
type Elem: Pod;
const OP: u32;
fn combine(acc: Self::Elem, x: Self::Elem) -> Self::Elem;
}
pub(crate) struct SumF64;
pub(crate) struct SumI64;
pub(crate) struct MaxF64;
impl Reduce for SumF64 {
type Elem = f64;
const OP: u32 = OP_SUM_F64;
#[inline]
fn combine(acc: f64, x: f64) -> f64 {
acc + x
}
}
impl Reduce for SumI64 {
type Elem = i64;
const OP: u32 = OP_SUM_I64;
#[inline]
fn combine(acc: i64, x: i64) -> i64 {
acc.wrapping_add(x)
}
}
impl Reduce for MaxF64 {
type Elem = f64;
const OP: u32 = OP_MAX_F64;
#[inline]
fn combine(acc: f64, x: f64) -> f64 {
if x > acc || x.is_nan() {
x
} else {
acc
}
}
}
pub(crate) const GATHER_BELOW_BYTES: usize = 4096;
pub(crate) fn gathers<T>(len: usize, world: usize) -> bool {
len < world || len * std::mem::size_of::<T>() < GATHER_BELOW_BYTES
}
pub(crate) fn chunk_range(len: usize, world: usize, chunk: usize) -> Range<usize> {
let base = len / world;
let extra = len % world;
let start = chunk * base + chunk.min(extra);
start..start + base + usize::from(chunk < extra)
}
pub(crate) fn fold_slots<R: Reduce>(slots: &[R::Elem], out: &mut [R::Elem], world: usize) {
let len = out.len();
debug_assert_eq!(slots.len(), len * world);
for chunk in 0..world {
let range = chunk_range(len, world, chunk);
let out = &mut out[range.clone()];
out.copy_from_slice(&slots[chunk * len..][range.clone()]);
for offset in 1..world {
let rank = (chunk + offset) % world;
for (acc, &x) in out.iter_mut().zip(&slots[rank * len..][range.clone()]) {
*acc = R::combine(*acc, x);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunks_partition_every_length() {
for world in 1..=7 {
for len in 0..=30 {
let mut next = 0;
for chunk in 0..world {
let range = chunk_range(len, world, chunk);
assert_eq!(range.start, next, "world {world} len {len} chunk {chunk}: contiguous");
assert!(
range.len() >= len / world,
"world {world} len {len} chunk {chunk}: balanced"
);
next = range.end;
}
assert_eq!(next, len, "world {world} len {len}: covers the buffer");
}
}
}
#[test]
fn fold_follows_ring_order() {
let world = 3;
let slots = [0.0, 1.0, 0.0, 0.0, 1e16, 0.0, 0.0, -1e16, 0.0];
let mut out = [0.0; 3];
fold_slots::<SumF64>(&slots, &mut out, world);
assert_eq!(out[1], 1.0, "chunk 1 folds ranks 1, 2, 0");
}
#[test]
fn max_propagates_nan() {
assert!(MaxF64::combine(f64::NAN, 1.0).is_nan(), "NaN accumulator");
assert!(MaxF64::combine(1.0, f64::NAN).is_nan(), "NaN operand");
assert_eq!(MaxF64::combine(-1.0, 2.0), 2.0, "larger operand");
}
}