use alloc::vec::Vec;
pub(crate) const SIMD_WIDTH: usize = 8;
pub(crate) const fn bit_width_u32(mut value: u32) -> u8 {
let mut bits = 0_u8;
while value != 0 {
bits += 1;
value >>= 1;
}
bits
}
pub(crate) const fn bit_width_u64(mut value: u64) -> u8 {
let mut bits = 0_u8;
while value != 0 {
bits += 1;
value >>= 1;
}
bits
}
pub(crate) const fn ceil_log2_u32(value: u32) -> u8 {
if value <= 1 {
0
} else {
bit_width_u32(value - 1)
}
}
#[cfg(feature = "simd")]
mod inner {
use super::SIMD_WIDTH;
use core::ops::{Add, AddAssign, DivAssign, Mul, MulAssign, Sub, SubAssign};
use fearless_simd::{SimdBase, SimdFloat};
pub(crate) use fearless_simd::{dispatch, Level, Simd};
#[derive(Copy, Clone)]
#[repr(C, align(32))]
pub(crate) struct f32x8<S: Simd> {
inner: fearless_simd::f32x8<S>,
}
impl<S: Simd> f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn from_slice(simd: S, slice: &[f32]) -> Self {
Self {
inner: fearless_simd::f32x8::from_slice(simd, slice),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn splat(simd: S, value: f32) -> Self {
Self {
inner: fearless_simd::f32x8::splat(simd, value),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn mul_add(self, mul: Self, addend: Self) -> Self {
Self {
inner: self.inner.mul_add(mul.inner, addend.inner),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn floor(self) -> Self {
Self {
inner: self.inner.floor(),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn store(self, slice: &mut [f32]) {
self.inner.store_slice(&mut slice[..SIMD_WIDTH]);
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn zip_low(self, other: Self) -> Self {
Self {
inner: self.inner.zip_low(other.inner),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn zip_high(self, other: Self) -> Self {
Self {
inner: self.inner.zip_high(other.inner),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn min(self, other: Self) -> Self {
Self {
inner: self.inner.min(other.inner),
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn max(self, other: Self) -> Self {
Self {
inner: self.inner.max(other.inner),
}
}
}
impl<S: Simd> Add for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn add(self, rhs: Self) -> Self {
Self {
inner: self.inner + rhs.inner,
}
}
}
impl<S: Simd> Sub for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
Self {
inner: self.inner - rhs.inner,
}
}
}
impl<S: Simd> Mul for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
Self {
inner: self.inner * rhs.inner,
}
}
}
impl<S: Simd> Add<f32> for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn add(self, rhs: f32) -> Self {
Self {
inner: self.inner + rhs,
}
}
}
impl<S: Simd> Mul<f32> for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn mul(self, rhs: f32) -> Self {
Self {
inner: self.inner * rhs,
}
}
}
impl<S: Simd> AddAssign for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
self.inner = self.inner + rhs.inner;
}
}
impl<S: Simd> SubAssign for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn sub_assign(&mut self, rhs: Self) {
self.inner = self.inner - rhs.inner;
}
}
impl<S: Simd> MulAssign<f32> for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn mul_assign(&mut self, rhs: f32) {
self.inner = self.inner * rhs;
}
}
impl<S: Simd> DivAssign<f32> for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn div_assign(&mut self, rhs: f32) {
self.inner = self.inner / rhs;
}
}
}
#[cfg(not(feature = "simd"))]
mod inner {
use super::SIMD_WIDTH;
use core::marker::PhantomData;
use core::ops::{Add, AddAssign, DivAssign, Mul, MulAssign, Sub, SubAssign};
pub(crate) trait Simd: Copy + Clone {}
#[derive(Copy, Clone)]
pub(crate) struct ScalarSimd;
impl Simd for ScalarSimd {}
pub(crate) struct Level;
impl Level {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn new() -> Self {
Level
}
}
#[derive(Copy, Clone)]
#[repr(C, align(32))]
pub(crate) struct f32x8<S: Simd> {
val: [f32; SIMD_WIDTH],
_marker: PhantomData<S>,
}
impl<S: Simd> f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn from_slice(_simd: S, slice: &[f32]) -> Self {
let mut val = [0.0f32; SIMD_WIDTH];
val.copy_from_slice(&slice[..SIMD_WIDTH]);
Self {
val,
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn splat(_simd: S, value: f32) -> Self {
Self {
val: [value; SIMD_WIDTH],
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
pub(crate) fn mul_add(self, mul: Self, addend: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = super::mul_add(self.val[i], mul.val[i], addend.val[i]);
}
Self {
val: result,
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
pub(crate) fn floor(self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = super::floor_f32(self.val[i]);
}
Self {
val: result,
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn store(self, slice: &mut [f32]) {
slice[..SIMD_WIDTH].copy_from_slice(&self.val);
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn zip_low(self, other: Self) -> Self {
Self {
val: [
self.val[0],
other.val[0],
self.val[1],
other.val[1],
self.val[2],
other.val[2],
self.val[3],
other.val[3],
],
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn zip_high(self, other: Self) -> Self {
Self {
val: [
self.val[4],
other.val[4],
self.val[5],
other.val[5],
self.val[6],
other.val[6],
self.val[7],
other.val[7],
],
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
pub(crate) fn min(self, other: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = super::min_f32(self.val[i], other.val[i]);
}
Self {
val: result,
_marker: PhantomData,
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
pub(crate) fn max(self, other: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = super::max_f32(self.val[i], other.val[i]);
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> Add for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
fn add(self, rhs: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = self.val[i] + rhs.val[i];
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> Sub for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
fn sub(self, rhs: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = self.val[i] - rhs.val[i];
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> Mul for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
fn mul(self, rhs: Self) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = self.val[i] * rhs.val[i];
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> Add<f32> for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
fn add(self, rhs: f32) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = self.val[i] + rhs;
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> Mul<f32> for f32x8<S> {
type Output = Self;
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(clippy::needless_range_loop, reason = "fixed-width scalar SIMD lanes")]
#[inline(always)]
fn mul(self, rhs: f32) -> Self {
let mut result = [0.0f32; SIMD_WIDTH];
for i in 0..SIMD_WIDTH {
result[i] = self.val[i] * rhs;
}
Self {
val: result,
_marker: PhantomData,
}
}
}
impl<S: Simd> AddAssign for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn add_assign(&mut self, rhs: Self) {
for i in 0..SIMD_WIDTH {
self.val[i] += rhs.val[i];
}
}
}
impl<S: Simd> SubAssign for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn sub_assign(&mut self, rhs: Self) {
for i in 0..SIMD_WIDTH {
self.val[i] -= rhs.val[i];
}
}
}
impl<S: Simd> MulAssign<f32> for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn mul_assign(&mut self, rhs: f32) {
for i in 0..SIMD_WIDTH {
self.val[i] *= rhs;
}
}
}
impl<S: Simd> DivAssign<f32> for f32x8<S> {
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
fn div_assign(&mut self, rhs: f32) {
for i in 0..SIMD_WIDTH {
self.val[i] /= rhs;
}
}
}
#[macro_export]
macro_rules! simd_dispatch {
($level:expr, $simd:ident => $body:expr) => {{
let _ = $level;
let $simd = $crate::math::ScalarSimd;
$body
}};
}
pub(crate) use simd_dispatch as dispatch;
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn mul_add(a: f32, b: f32, c: f32) -> f32 {
#[cfg(all(
feature = "std",
any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "fma"
),
all(target_arch = "aarch64", target_feature = "neon")
)
))]
{
f32::mul_add(a, b, c)
}
#[cfg(not(all(
feature = "std",
any(
all(
any(target_arch = "x86", target_arch = "x86_64"),
target_feature = "fma"
),
all(target_arch = "aarch64", target_feature = "neon")
)
)))]
{
a * b + c
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[cfg_attr(
not(feature = "std"),
expect(
clippy::cast_possible_truncation,
clippy::cast_precision_loss,
reason = "the no-std floor polyfill converts through the truncated integer part"
)
)]
#[inline(always)]
pub(crate) fn floor_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.floor()
}
#[cfg(not(feature = "std"))]
{
let xi = x as i32;
let xf = xi as f32;
if x < xf {
xf - 1.0
} else {
xf
}
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn round_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.round()
}
#[cfg(not(feature = "std"))]
{
if x >= 0.0 {
floor_f32(x + 0.5)
} else {
-floor_f32(-x + 0.5)
}
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
pub(crate) fn log2_f32(x: f32) -> f32 {
#[cfg(feature = "std")]
{
x.log2()
}
#[cfg(not(feature = "std"))]
{
libm::log2f(x)
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[expect(
clippy::cast_precision_loss,
reason = "powers of two in the supported exponent range are exactly representable in f32"
)]
#[inline(always)]
pub(crate) fn pow2i(exp: i32) -> f32 {
if exp >= 0 {
(1_u32 << exp) as f32
} else {
1.0 / (1_u32 << -exp) as f32
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
#[cfg(not(feature = "simd"))]
pub(crate) fn min_f32(a: f32, b: f32) -> f32 {
#[cfg(feature = "std")]
{
a.min(b)
}
#[cfg(not(feature = "std"))]
{
if a < b {
a
} else {
b
}
}
}
#[expect(
clippy::inline_always,
reason = "tiny SIMD and scalar wrappers must disappear inside transform loops"
)]
#[inline(always)]
#[cfg(not(feature = "simd"))]
pub(crate) fn max_f32(a: f32, b: f32) -> f32 {
#[cfg(feature = "std")]
{
a.max(b)
}
#[cfg(not(feature = "std"))]
{
if a > b {
a
} else {
b
}
}
}
#[cfg(not(feature = "simd"))]
pub(crate) use inner::{dispatch, f32x8, Level, ScalarSimd, Simd};
#[cfg(feature = "simd")]
pub(crate) use inner::{dispatch, f32x8, Level, Simd};
#[derive(Debug)]
pub(crate) struct SimdBuffer<const N: usize> {
data: Vec<f32>,
original_len: usize,
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct SimdBufferAllocationError;
impl<const N: usize> SimdBuffer<N> {
pub(crate) const fn empty() -> Self {
Self {
data: Vec::new(),
original_len: 0,
}
}
#[cfg(test)]
pub(crate) fn new(mut data: Vec<f32>) -> Self {
let original_len = data.len();
let padded_len = Self::padded_len(original_len)
.expect("an allocated vector length plus SIMD padding must fit usize");
if padded_len > original_len {
data.resize(padded_len, 0.0);
}
Self { data, original_len }
}
pub(crate) fn try_zeros(
original_len: usize,
) -> core::result::Result<Self, SimdBufferAllocationError> {
let padded_len = Self::padded_len(original_len).ok_or(SimdBufferAllocationError)?;
let mut data = Vec::new();
data.try_reserve_exact(padded_len)
.map_err(|_| SimdBufferAllocationError)?;
data.resize(padded_len, 0.0);
Ok(Self { data, original_len })
}
pub(crate) fn try_reset_zeros(
&mut self,
original_len: usize,
) -> core::result::Result<(), SimdBufferAllocationError> {
let padded_len = Self::padded_len(original_len).ok_or(SimdBufferAllocationError)?;
if padded_len > self.data.capacity() {
return Err(SimdBufferAllocationError);
}
self.data.resize(padded_len, 0.0);
self.data.fill(0.0);
self.original_len = original_len;
Ok(())
}
pub(crate) fn truncated(&self) -> &[f32] {
&self.data[..self.original_len]
}
pub(crate) fn capacity(&self) -> usize {
self.data.capacity()
}
pub(crate) fn padded_len(original_len: usize) -> Option<usize> {
if N == 0 {
return None;
}
let remainder = original_len % N;
let padding = (N - remainder) % N;
original_len.checked_add(padding)
}
}
impl<const N: usize> core::ops::Deref for SimdBuffer<N> {
type Target = [f32];
#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<const N: usize> core::ops::DerefMut for SimdBuffer<N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
#[cfg(test)]
mod simd_operator_tests {
use super::{dispatch, f32x8, Level, SIMD_WIDTH};
#[test]
fn assignment_operators_preserve_each_lane_for_active_simd_level() {
let left = [1.0, -2.0, 3.5, -4.5, 5.25, -6.25, 7.75, -8.75];
let right = [8.0, 7.0, -6.0, -5.0, 4.0, 3.0, -2.0, -1.0];
let mut output = [0.0; SIMD_WIDTH];
dispatch!(Level::new(), simd => {
let mut value = f32x8::from_slice(simd, &left);
value += f32x8::from_slice(simd, &right);
value -= f32x8::splat(simd, 1.0);
value *= 0.5;
value /= 2.0;
value.store(&mut output);
});
let expected: [f32; SIMD_WIDTH] =
core::array::from_fn(|index| (left[index] + right[index] - 1.0) / 4.0);
for (index, (actual, expected)) in output.into_iter().zip(expected).enumerate() {
assert!(
(actual - expected).abs() <= f32::EPSILON,
"lane {index}: expected {expected}, got {actual}"
);
}
}
}
#[cfg(test)]
mod integer_tests {
use super::{bit_width_u32, bit_width_u64, ceil_log2_u32, SimdBuffer};
#[test]
fn integer_bit_ranges_cover_type_boundaries() {
assert_eq!(bit_width_u32(0), 0);
assert_eq!(bit_width_u32(1), 1);
assert_eq!(bit_width_u32(u32::MAX), 32);
assert_eq!(bit_width_u64(0), 0);
assert_eq!(bit_width_u64(1_u64 << 63), 64);
assert_eq!(bit_width_u64(u64::MAX), 64);
}
#[test]
fn simd_buffer_fallible_zero_allocation_uses_only_required_padding() {
let aligned = SimdBuffer::<8>::try_zeros(16).unwrap();
assert_eq!(aligned.len(), 16);
assert_eq!(aligned.truncated(), &[0.0; 16]);
let unaligned = SimdBuffer::<8>::try_zeros(17).unwrap();
assert_eq!(unaligned.len(), 24);
assert_eq!(unaligned.truncated(), &[0.0; 17]);
assert!(SimdBuffer::<8>::try_zeros(usize::MAX).is_err());
assert!(SimdBuffer::<0>::try_zeros(1).is_err());
}
#[test]
fn ceil_log2_covers_zero_and_full_u32_domain() {
assert_eq!(ceil_log2_u32(0), 0);
assert_eq!(ceil_log2_u32(1), 0);
assert_eq!(ceil_log2_u32(2), 1);
assert_eq!(ceil_log2_u32(3), 2);
assert_eq!(ceil_log2_u32(u32::MAX), 32);
}
}