use super::{
checked_reduce_reset, compress_reduce_outer_axes, ErasedReduceScalar, ReduceInnerAxis,
ReduceInnerCursor, ReduceOuterAxis, ReduceOuterCursor,
};
use crate::{Result, StridedError};
use core::ops::Range;
pub(super) const REDUCE_LANES: usize = 8;
pub(super) const AXIS_RUN_KERNEL_MIN_LEN: usize = 2 * REDUCE_LANES;
pub(super) const AXIS_OUTPUT_BLOCK_MIN_LEN: usize = 16;
pub(super) const AXIS_OUTPUT_BLOCK_BYTES: usize = 64 * 1024;
pub(super) const AXIS_OUTPUT_BLOCK_STEPS: usize = 4;
fn axis_output_block_len<T>() -> usize {
(AXIS_OUTPUT_BLOCK_BYTES / core::mem::size_of::<T>().max(1)).max(AXIS_OUTPUT_BLOCK_MIN_LEN)
}
pub(super) trait ReduceKernel<T: ErasedReduceScalar>: 'static {
type Acc: Copy + crate::MaybeSendSync;
type Fallback: ReduceKernel<T>;
const SCALAR_ACC: bool;
fn identity() -> Self::Acc;
fn from_value(value: T) -> Self::Acc;
fn combine(lhs: Self::Acc, rhs: Self::Acc) -> Self::Acc;
fn finish(acc: Self::Acc) -> T;
#[inline(always)]
fn needs_ordered_fallback(_acc: &Self::Acc) -> bool {
false
}
#[inline(always)]
fn reassociable() -> bool {
true
}
#[inline(always)]
fn contiguous(values: &[T]) -> Self::Acc {
lanes_contiguous::<T, Self>(values)
}
#[inline(always)]
fn combine_block(acc: &mut [Self::Acc], values: &[T]) {
scalar_combine_block::<T, Self>(acc, values);
}
}
#[inline(always)]
fn scalar_combine_block<T: ErasedReduceScalar, K: ReduceKernel<T> + ?Sized>(
acc: &mut [K::Acc],
values: &[T],
) {
for (slot, &value) in acc.iter_mut().zip(values) {
*slot = K::combine(*slot, K::from_value(value));
}
}
pub(super) struct SumKernel;
pub(super) struct ReassociatedProductKernel;
pub(super) struct OrderedProductKernel;
pub(super) struct StableProductKernel;
pub(super) struct SumSquaresKernel;
pub(super) struct MaxKernel;
pub(super) struct MinKernel;
impl<T: ErasedReduceScalar> ReduceKernel<T> for SumKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::zero()
}
#[inline(always)]
fn from_value(value: T) -> T {
value
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_sum(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
#[inline(always)]
fn contiguous(values: &[T]) -> T {
T::try_simd_sum(values).unwrap_or_else(|| lanes_contiguous::<T, Self>(values))
}
#[inline(always)]
fn combine_block(acc: &mut [T], values: &[T]) {
if !T::try_simd_add_block(acc, values) {
scalar_combine_block::<T, Self>(acc, values);
}
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for ReassociatedProductKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::one()
}
#[inline(always)]
fn from_value(value: T) -> T {
value
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_product(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
#[inline(always)]
fn contiguous(values: &[T]) -> T {
T::try_simd_product(values).unwrap_or_else(|| lanes_contiguous::<T, Self>(values))
}
#[inline(always)]
fn combine_block(acc: &mut [T], values: &[T]) {
if !T::try_simd_mul_block(acc, values) {
scalar_combine_block::<T, Self>(acc, values);
}
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for OrderedProductKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::one()
}
#[inline(always)]
fn from_value(value: T) -> T {
value
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_product(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
#[inline(always)]
fn reassociable() -> bool {
false
}
#[inline(always)]
fn contiguous(values: &[T]) -> T {
values
.iter()
.fold(T::one(), |acc, &value| T::reduce_product(acc, value))
}
#[inline(always)]
fn combine_block(acc: &mut [T], values: &[T]) {
if !T::try_simd_mul_block(acc, values) {
scalar_combine_block::<T, Self>(acc, values);
}
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for StableProductKernel {
type Acc = T::StableProductAcc;
type Fallback = OrderedProductKernel;
const SCALAR_ACC: bool = T::SP_ACC_IS_SCALAR;
#[inline(always)]
fn identity() -> T::StableProductAcc {
T::sp_identity()
}
#[inline(always)]
fn from_value(value: T) -> T::StableProductAcc {
T::sp_from_value(value)
}
#[inline(always)]
fn combine(lhs: T::StableProductAcc, rhs: T::StableProductAcc) -> T::StableProductAcc {
T::sp_combine(lhs, rhs)
}
#[inline(always)]
fn finish(acc: T::StableProductAcc) -> T {
T::sp_finish(acc)
}
#[inline(always)]
fn needs_ordered_fallback(acc: &T::StableProductAcc) -> bool {
T::sp_needs_fallback(acc)
}
#[inline(always)]
fn reassociable() -> bool {
T::sp_reassociable()
}
#[inline(always)]
fn contiguous(values: &[T]) -> T::StableProductAcc {
T::sp_contiguous(values)
}
#[inline(always)]
fn combine_block(acc: &mut [T::StableProductAcc], values: &[T]) {
T::sp_combine_block(acc, values);
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for SumSquaresKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::zero()
}
#[inline(always)]
fn from_value(value: T) -> T {
T::reduce_product(value, value)
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_sum(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
#[inline(always)]
fn contiguous(values: &[T]) -> T {
T::try_simd_sum_squares(values).unwrap_or_else(|| lanes_contiguous::<T, Self>(values))
}
#[inline(always)]
fn combine_block(acc: &mut [T], values: &[T]) {
if !T::try_simd_sum_squares_block(acc, values) {
scalar_combine_block::<T, Self>(acc, values);
}
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for MaxKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::max_identity()
}
#[inline(always)]
fn from_value(value: T) -> T {
value
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_max(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
}
impl<T: ErasedReduceScalar> ReduceKernel<T> for MinKernel {
type Acc = T;
type Fallback = Self;
const SCALAR_ACC: bool = true;
#[inline(always)]
fn identity() -> T {
T::min_identity()
}
#[inline(always)]
fn from_value(value: T) -> T {
value
}
#[inline(always)]
fn combine(lhs: T, rhs: T) -> T {
T::reduce_min(lhs, rhs)
}
#[inline(always)]
fn finish(value: T) -> T {
value
}
}
pub(super) trait StableProductScalar: Copy + crate::MaybeSendSync {
type StableProductAcc: Copy + crate::MaybeSendSync;
const SP_ACC_IS_SCALAR: bool = true;
fn sp_identity() -> Self::StableProductAcc;
fn sp_from_value(value: Self) -> Self::StableProductAcc;
fn sp_combine(
lhs: Self::StableProductAcc,
rhs: Self::StableProductAcc,
) -> Self::StableProductAcc;
fn sp_finish(acc: Self::StableProductAcc) -> Self;
#[inline(always)]
fn sp_needs_fallback(_acc: &Self::StableProductAcc) -> bool {
false
}
#[inline(always)]
fn sp_reassociable() -> bool {
true
}
#[inline(always)]
fn sp_contiguous(values: &[Self]) -> Self::StableProductAcc {
stable_lanes::<Self>(values)
}
#[inline(always)]
fn sp_combine_block(acc: &mut [Self::StableProductAcc], values: &[Self]) {
for (slot, &value) in acc.iter_mut().zip(values) {
*slot = Self::sp_combine(*slot, Self::sp_from_value(value));
}
}
}
#[inline(always)]
fn stable_lanes<T: StableProductScalar>(values: &[T]) -> T::StableProductAcc {
let mut lanes = [T::sp_identity(); CONTIGUOUS_REDUCE_LANES];
let mut chunks = values.chunks_exact(CONTIGUOUS_REDUCE_LANES);
for chunk in chunks.by_ref() {
for (lane, &value) in lanes.iter_mut().zip(chunk) {
*lane = T::sp_combine(*lane, T::sp_from_value(value));
}
}
for (lane, &value) in lanes.iter_mut().zip(chunks.remainder()) {
*lane = T::sp_combine(*lane, T::sp_from_value(value));
}
lanes.into_iter().fold(T::sp_identity(), T::sp_combine)
}
#[derive(Clone, Copy, Debug)]
pub(super) struct StableProductFloatAcc {
m: f64,
e: i64,
bad: bool,
}
pub(super) trait StableProductFloatInput: Copy {
const K: usize;
const LO: f64;
const HI: f64;
fn split(self) -> (f64, i64, bool);
fn to_f64(self) -> f64;
fn from_f64(value: f64) -> Self;
}
#[inline(always)]
fn split_f64(value: f64) -> (f64, i64, bool) {
const EXP: u64 = 0x7ffu64 << 52;
const ONE: u64 = 1023u64 << 52;
let bits = value.to_bits();
let raw_exp = (bits & EXP) >> 52;
let m = f64::from_bits((bits & !EXP) | ONE);
(m, raw_exp as i64 - 1023, raw_exp == 0 || raw_exp == 0x7ff)
}
#[inline(always)]
fn split_f32(value: f32) -> (f64, i64, bool) {
const EXP: u32 = 0xffu32 << 23;
const ONE: u32 = 127u32 << 23;
let bits = value.to_bits();
let raw_exp = (bits & EXP) >> 23;
let m = f32::from_bits((bits & !EXP) | ONE) as f64;
(m, raw_exp as i64 - 127, raw_exp == 0 || raw_exp == 0xff)
}
impl StableProductFloatInput for f64 {
const K: usize = 8;
const LO: f64 = 7.52316384526264e-37; const HI: f64 = 1.329227995784916e36;
#[inline(always)]
fn split(self) -> (f64, i64, bool) {
split_f64(self)
}
#[inline(always)]
fn to_f64(self) -> f64 {
self
}
#[inline(always)]
fn from_f64(value: f64) -> f64 {
value
}
}
impl StableProductFloatInput for f32 {
const K: usize = 4;
const LO: f64 = 9.313225746154785e-10; const HI: f64 = 1.073741824e9;
#[inline(always)]
fn split(self) -> (f64, i64, bool) {
split_f32(self)
}
#[inline(always)]
fn to_f64(self) -> f64 {
self as f64
}
#[inline(always)]
fn from_f64(value: f64) -> f32 {
value as f32
}
}
fn f64_ldexp(mut m: f64, mut e: i64) -> f64 {
const TWO_POW_M1022: u64 = 1u64 << 52;
const TWO_POW_1023: u64 = 2046u64 << 52;
while e > 1023 {
m *= f64::from_bits(TWO_POW_1023);
e -= 1023;
if m.is_infinite() {
return m;
}
}
while e < -1022 {
m *= f64::from_bits(TWO_POW_M1022);
e += 1022;
if m == 0.0 {
return m;
}
}
m * f64::from_bits(((e + 1023) as u64) << 52)
}
#[inline(always)]
fn stable_renorm(am: &mut [f64], ae: &mut [i64]) {
for (m, e) in am.iter_mut().zip(ae.iter_mut()) {
let (mantissa, exponent, _) = split_f64(*m);
*m = mantissa;
*e += exponent;
}
}
#[inline(always)]
fn stable_float_combine(
lhs: StableProductFloatAcc,
rhs: StableProductFloatAcc,
) -> StableProductFloatAcc {
if lhs.bad | rhs.bad {
return StableProductFloatAcc {
m: lhs.m,
e: lhs.e,
bad: true,
};
}
let mut m = lhs.m * rhs.m;
let mut e = lhs.e + rhs.e;
if m.abs() >= 2.0 {
m *= 0.5;
e += 1;
}
StableProductFloatAcc { m, e, bad: false }
}
fn stable_float_contiguous<T: StableProductFloatInput>(values: &[T]) -> StableProductFloatAcc {
const LANES: usize = 8;
let identity = StableProductFloatAcc {
m: 1.0,
e: 0,
bad: false,
};
if values.is_empty() {
return identity;
}
let block = LANES * T::K;
let mut am = [1.0f64; LANES];
let mut ae = [0i64; LANES];
let mut bad = false;
let mut chunks = values.chunks_exact(block);
for chunk in &mut chunks {
let mut tentative = am;
let mut max = [0.0f64; LANES];
let mut min = [f64::INFINITY; LANES];
for step in 0..T::K {
for (lane, max) in max.iter_mut().enumerate() {
let value = chunk[step * LANES + lane].to_f64();
tentative[lane] *= value;
let magnitude = value.abs();
if magnitude > *max {
*max = magnitude;
}
if magnitude < min[lane] {
min[lane] = magnitude;
}
}
}
let in_range = max.iter().all(|&value| value <= T::HI)
&& min.iter().all(|&value| value >= T::LO)
&& !tentative.iter().any(|value| value.is_nan());
if in_range {
am = tentative;
} else {
for step in 0..T::K {
for lane in 0..LANES {
let (mantissa, exponent, is_bad) = chunk[step * LANES + lane].split();
am[lane] *= mantissa;
ae[lane] += exponent;
bad |= is_bad;
}
}
}
stable_renorm(&mut am, &mut ae);
}
for (index, &value) in chunks.remainder().iter().enumerate() {
let (mantissa, exponent, is_bad) = value.split();
let lane = index % LANES;
am[lane] *= mantissa;
ae[lane] += exponent;
bad |= is_bad;
}
stable_renorm(&mut am, &mut ae);
let mut m = 1.0f64;
let mut e = 0i64;
for lane in 0..LANES {
m *= am[lane];
e += ae[lane];
let (mantissa, exponent, _) = split_f64(m);
m = mantissa;
e += exponent;
}
StableProductFloatAcc { m, e, bad }
}
macro_rules! impl_stable_product_float {
($($ty:ty),* $(,)?) => {
$(
impl StableProductScalar for $ty {
type StableProductAcc = StableProductFloatAcc;
const SP_ACC_IS_SCALAR: bool = false;
#[inline(always)]
fn sp_identity() -> StableProductFloatAcc {
StableProductFloatAcc { m: 1.0, e: 0, bad: false }
}
#[inline(always)]
fn sp_from_value(value: Self) -> StableProductFloatAcc {
let (m, e, bad) = <Self as StableProductFloatInput>::split(value);
StableProductFloatAcc { m, e, bad }
}
#[inline(always)]
fn sp_combine(
lhs: StableProductFloatAcc,
rhs: StableProductFloatAcc,
) -> StableProductFloatAcc {
stable_float_combine(lhs, rhs)
}
#[inline(always)]
fn sp_finish(acc: StableProductFloatAcc) -> Self {
if acc.bad {
return <$ty>::NAN;
}
<$ty as StableProductFloatInput>::from_f64(f64_ldexp(acc.m, acc.e))
}
#[inline(always)]
fn sp_needs_fallback(acc: &StableProductFloatAcc) -> bool {
acc.bad
}
#[inline(always)]
fn sp_reassociable() -> bool {
true
}
#[inline(always)]
fn sp_contiguous(values: &[Self]) -> StableProductFloatAcc {
crate::simd::dispatch_if_large(values.len(), || {
stable_float_contiguous::<$ty>(values)
})
}
}
)*
};
}
impl_stable_product_float!(f32, f64);
impl StableProductScalar for i32 {
type StableProductAcc = i32;
#[inline(always)]
fn sp_identity() -> i32 {
1
}
#[inline(always)]
fn sp_from_value(value: i32) -> i32 {
value
}
#[inline(always)]
fn sp_combine(lhs: i32, rhs: i32) -> i32 {
lhs.wrapping_mul(rhs)
}
#[inline(always)]
fn sp_finish(acc: i32) -> i32 {
acc
}
}
impl StableProductScalar for i64 {
type StableProductAcc = i64;
#[inline(always)]
fn sp_identity() -> i64 {
1
}
#[inline(always)]
fn sp_from_value(value: i64) -> i64 {
value
}
#[inline(always)]
fn sp_combine(lhs: i64, rhs: i64) -> i64 {
lhs.wrapping_mul(rhs)
}
#[inline(always)]
fn sp_finish(acc: i64) -> i64 {
acc
}
}
macro_rules! impl_stable_product_complex {
($($ty:ty),* $(,)?) => {
$(
impl StableProductScalar for $ty {
type StableProductAcc = $ty;
#[inline(always)]
fn sp_identity() -> $ty {
<$ty>::new(1.0, 0.0)
}
#[inline(always)]
fn sp_from_value(value: $ty) -> $ty {
value
}
#[inline(always)]
fn sp_combine(lhs: $ty, rhs: $ty) -> $ty {
lhs * rhs
}
#[inline(always)]
fn sp_finish(acc: $ty) -> $ty {
acc
}
#[inline(always)]
fn sp_reassociable() -> bool {
false
}
#[inline(always)]
fn sp_contiguous(values: &[$ty]) -> $ty {
values
.iter()
.fold(Self::sp_identity(), |acc, &value| acc * value)
}
}
)*
};
}
impl_stable_product_complex!(num_complex::Complex32, num_complex::Complex64);
pub(super) const CONTIGUOUS_REDUCE_LANES: usize = 16;
#[inline(always)]
fn lanes_contiguous<T, K>(values: &[T]) -> K::Acc
where
T: ErasedReduceScalar,
K: ReduceKernel<T> + ?Sized,
{
crate::simd::dispatch_if_large(values.len(), || {
let mut lanes = [K::identity(); CONTIGUOUS_REDUCE_LANES];
let mut chunks = values.chunks_exact(CONTIGUOUS_REDUCE_LANES);
for chunk in chunks.by_ref() {
for (lane, &value) in lanes.iter_mut().zip(chunk) {
*lane = K::combine(*lane, K::from_value(value));
}
}
for (lane, &value) in lanes.iter_mut().zip(chunks.remainder()) {
*lane = K::combine(*lane, K::from_value(value));
}
lanes.into_iter().fold(K::identity(), K::combine)
})
}
#[inline(always)]
unsafe fn lanes_strided<T, K>(ptr: *const T, stride: isize, len: usize) -> K::Acc
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
let mut lanes = [K::identity(); REDUCE_LANES];
let mut cursor = ptr;
for _ in 0..len / REDUCE_LANES {
let mut lane_ptr = cursor;
for lane in &mut lanes {
*lane = K::combine(*lane, K::from_value(unsafe { *lane_ptr }));
lane_ptr = lane_ptr.wrapping_offset(stride);
}
cursor = lane_ptr;
}
for lane in lanes.iter_mut().take(len % REDUCE_LANES) {
*lane = K::combine(*lane, K::from_value(unsafe { *cursor }));
cursor = cursor.wrapping_offset(stride);
}
lanes.into_iter().fold(K::identity(), K::combine)
}
#[inline(always)]
unsafe fn fold_run_ordered<T, K>(
mut acc: K::Acc,
ptr: *const T,
stride: isize,
len: usize,
) -> K::Acc
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
let mut cursor = ptr;
for _ in 0..len {
acc = K::combine(acc, K::from_value(unsafe { *cursor }));
cursor = cursor.wrapping_offset(stride);
}
acc
}
#[inline(always)]
unsafe fn reduce_run<T, K>(ptr: *const T, stride: isize, len: usize) -> K::Acc
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
if stride == 1 {
K::contiguous(unsafe { core::slice::from_raw_parts(ptr, len) })
} else if K::reassociable() {
unsafe { lanes_strided::<T, K>(ptr, stride, len) }
} else {
unsafe { fold_run_ordered::<T, K>(K::identity(), ptr, stride, len) }
}
}
#[derive(Clone, Debug)]
pub(super) struct FullTraversal {
total: usize,
run_len: usize,
run_stride: isize,
run_axes: Vec<ReduceOuterAxis>,
}
impl FullTraversal {
pub(super) fn compile(dims: &[usize], strides: &[isize]) -> Result<Self> {
if dims.len() != strides.len() {
return Err(StridedError::StrideLengthMismatch);
}
let total = dims
.iter()
.try_fold(1usize, |total, &dim| total.checked_mul(dim))
.ok_or(StridedError::OffsetOverflow)?;
if total == 0 {
return Ok(Self {
total,
run_len: 0,
run_stride: 1,
run_axes: Vec::new(),
});
}
let mut axes: Vec<(usize, isize)> = dims
.iter()
.copied()
.zip(strides.iter().copied())
.filter(|&(extent, _)| extent != 1)
.collect();
axes.sort_by_key(|&(_, stride)| {
if stride == 0 {
usize::MAX
} else {
stride.unsigned_abs()
}
});
let mut fused = compress_reduce_outer_axes(
axes.into_iter()
.map(|(extent, stride)| {
Ok(ReduceOuterAxis {
extent,
source_step: stride,
source_reset: checked_reduce_reset(extent, stride)?,
dest_step: 0,
dest_reset: 0,
})
})
.collect::<Result<Vec<_>>>()?,
)?;
if fused.is_empty() {
return Ok(Self {
total,
run_len: 1,
run_stride: 1,
run_axes: fused,
});
}
let run = fused.remove(0);
Ok(Self {
total,
run_len: run.extent,
run_stride: run.source_step,
run_axes: fused,
})
}
#[inline]
pub(super) fn total(&self) -> usize {
self.total
}
}
pub(super) unsafe fn reduce_full_range<T, K>(
source: *const T,
source_base: isize,
traversal: &FullTraversal,
range: Range<usize>,
) -> Result<K::Acc>
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
debug_assert!(!range.is_empty() && range.end <= traversal.total);
let run_len = traversal.run_len;
let run_stride = traversal.run_stride;
let mut col = range.start % run_len;
let mut runs =
ReduceOuterCursor::decode(range.start / run_len, source_base, 0, &traversal.run_axes)?;
let mut remaining = range.len();
let mut acc = None;
loop {
let len = (run_len - col).min(remaining);
let start = runs.source_offset + col as isize * run_stride;
let partial = if K::reassociable() {
unsafe { reduce_run::<T, K>(source.offset(start), run_stride, len) }
} else {
unsafe {
fold_run_ordered::<T, K>(
acc.unwrap_or_else(K::identity),
source.offset(start),
run_stride,
len,
)
}
};
acc = Some(match acc {
Some(acc) if K::reassociable() => K::combine(acc, partial),
_ => partial,
});
remaining -= len;
if remaining == 0 {
break;
}
col = 0;
runs.advance();
}
Ok(acc.unwrap_or_else(K::identity))
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum AxesStrategy {
ContiguousRuns,
OutputBlocks,
Sequential,
}
fn axes_strategy(outer_axes: &[ReduceOuterAxis], inner_axes: &[ReduceInnerAxis]) -> AxesStrategy {
if inner_axes
.first()
.is_some_and(|axis| axis.source_step == 1 && axis.extent >= AXIS_RUN_KERNEL_MIN_LEN)
{
AxesStrategy::ContiguousRuns
} else if outer_axes
.first()
.is_some_and(|axis| axis.source_step == 1 && axis.extent >= AXIS_OUTPUT_BLOCK_MIN_LEN)
{
AxesStrategy::OutputBlocks
} else {
AxesStrategy::Sequential
}
}
pub(super) struct AxesRange<'a, T> {
pub(super) source: *const T,
pub(super) source_base: isize,
pub(super) dest: *mut T,
pub(super) dest_base: isize,
pub(super) outer_axes: &'a [ReduceOuterAxis],
pub(super) inner_axes: &'a [ReduceInnerAxis],
pub(super) reduce_total: usize,
}
#[inline(always)]
unsafe fn fallback_axes_output<T, K>(
source: *const T,
inner_axes: &[ReduceInnerAxis],
source_offset: isize,
reduce_total: usize,
) -> T
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
let mut inner = ReduceInnerCursor::new(0, inner_axes);
inner.reset(source_offset);
let acc = unsafe { sequential_fold::<T, K::Fallback>(source, &mut inner, reduce_total) };
K::Fallback::finish(acc)
}
#[inline]
fn block_output_source_offset(base: isize, step: isize, index: usize) -> Result<isize> {
let index = isize::try_from(index).map_err(|_| StridedError::OffsetOverflow)?;
let delta = step
.checked_mul(index)
.ok_or(StridedError::OffsetOverflow)?;
base.checked_add(delta).ok_or(StridedError::OffsetOverflow)
}
pub(super) unsafe fn reduce_axes_range<T, K>(
parts: AxesRange<'_, T>,
range: Range<usize>,
) -> Result<()>
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
if range.is_empty() {
return Ok(());
}
let source = parts.source;
let dest = parts.dest;
let reduce_total = parts.reduce_total;
debug_assert!(reduce_total != 0);
let mut outer = ReduceOuterCursor::decode(
range.start,
parts.source_base,
parts.dest_base,
parts.outer_axes,
)?;
match axes_strategy(parts.outer_axes, parts.inner_axes) {
AxesStrategy::ContiguousRuns => {
let run_len = parts.inner_axes[0].extent;
let run_count = reduce_total / run_len;
let mut runs = ReduceInnerCursor::new(0, &parts.inner_axes[1..]);
for output in range.clone() {
runs.reset(outer.source_offset);
let mut acc = if K::reassociable() {
K::contiguous(unsafe {
core::slice::from_raw_parts(source.offset(runs.source_offset), run_len)
})
} else {
unsafe {
fold_run_ordered::<T, K>(
K::identity(),
source.offset(runs.source_offset),
1,
run_len,
)
}
};
for _ in 1..run_count {
runs.advance();
if K::reassociable() {
let partial = K::contiguous(unsafe {
core::slice::from_raw_parts(source.offset(runs.source_offset), run_len)
});
acc = K::combine(acc, partial);
} else {
acc = unsafe {
fold_run_ordered::<T, K>(
acc,
source.offset(runs.source_offset),
1,
run_len,
)
};
}
}
let value = if K::needs_ordered_fallback(&acc) {
unsafe {
fallback_axes_output::<T, K>(
source,
parts.inner_axes,
outer.source_offset,
reduce_total,
)
}
} else {
K::finish(acc)
};
unsafe { dest.offset(outer.dest_offset).write(value) };
if output + 1 < range.end {
outer.advance();
}
}
}
AxesStrategy::OutputBlocks => {
let block_axis = parts.outer_axes[0];
let mut inner = ReduceInnerCursor::new(0, parts.inner_axes);
let block = axis_output_block_len::<T>().min(range.len());
let mut acc = vec![K::identity(); block];
let mut output = range.start;
while output < range.end {
let block_room = block_axis.extent - outer.leading_coord();
let len = block_room.min(range.end - output).min(block);
let acc = &mut acc[..len];
acc.fill(K::identity());
inner.reset(outer.source_offset);
let mut offsets = [0isize; AXIS_OUTPUT_BLOCK_STEPS];
let mut value_index = 0;
while value_index + AXIS_OUTPUT_BLOCK_STEPS <= reduce_total {
for offset in &mut offsets {
*offset = inner.source_offset;
value_index += 1;
if value_index < reduce_total {
inner.advance();
}
}
let [v0, v1, v2, v3] = offsets.map(|offset| unsafe {
core::slice::from_raw_parts(source.offset(offset), len)
});
K::combine_block(acc, v0);
K::combine_block(acc, v1);
K::combine_block(acc, v2);
K::combine_block(acc, v3);
}
while value_index < reduce_total {
let values = unsafe {
core::slice::from_raw_parts(source.offset(inner.source_offset), len)
};
K::combine_block(acc, values);
value_index += 1;
if value_index < reduce_total {
inner.advance();
}
}
let block_needs_fallback = acc.iter().any(K::needs_ordered_fallback);
if block_axis.dest_step == 1 && K::SCALAR_ACC && !block_needs_fallback {
unsafe {
core::ptr::copy_nonoverlapping(
acc.as_ptr() as *const T,
dest.offset(outer.dest_offset),
len,
)
};
} else {
for (index, &value) in acc.iter().enumerate() {
let offset = outer.dest_offset + index as isize * block_axis.dest_step;
let output_value = if K::needs_ordered_fallback(&value) {
let source_offset = block_output_source_offset(
outer.source_offset,
block_axis.source_step,
index,
)?;
unsafe {
fallback_axes_output::<T, K>(
source,
parts.inner_axes,
source_offset,
reduce_total,
)
}
} else {
K::finish(value)
};
unsafe { dest.offset(offset).write(output_value) };
}
}
for _ in 0..len {
output += 1;
if output < range.end {
outer.advance();
}
}
}
}
AxesStrategy::Sequential => {
let mut inner = ReduceInnerCursor::new(0, parts.inner_axes);
for output in range.clone() {
inner.reset(outer.source_offset);
let acc = unsafe { sequential_fold::<T, K>(source, &mut inner, reduce_total) };
let value = if K::needs_ordered_fallback(&acc) {
unsafe {
fallback_axes_output::<T, K>(
source,
parts.inner_axes,
outer.source_offset,
reduce_total,
)
}
} else {
K::finish(acc)
};
unsafe { dest.offset(outer.dest_offset).write(value) };
if output + 1 < range.end {
outer.advance();
}
}
}
}
Ok(())
}
#[inline(always)]
unsafe fn sequential_fold<T, K>(
source: *const T,
inner: &mut ReduceInnerCursor<'_>,
count: usize,
) -> K::Acc
where
T: ErasedReduceScalar,
K: ReduceKernel<T>,
{
let mut acc = K::identity();
for value_index in 0..count {
let value = unsafe { *source.offset(inner.source_offset) };
acc = K::combine(acc, K::from_value(value));
if value_index + 1 < count {
inner.advance();
}
}
acc
}