Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## Unreleased
- Bump the minimum `wide` version to 1.6, and switch from the deprecated `blend`/`Cmp*` APIs to `select` and the
inherent `simd_*` comparison methods.
- The `WideF32xX`/`WideF64xX` math functions now go through `ComplexField`/`RealField` instead of the inherent `std`
methods, so they honor the `libm_force` feature. Under `libm_force`, `simd_sin`, `simd_cos` and `simd_sin_cos` are
computed per-lane with `libm` instead of `wide`'s SIMD polynomials.
- `SimdValue::splat` for the wide types no longer emits a `memset_pattern16` libc call.

## Release v0.10.0 (24 May 2026)
- Update the `rand` dependency to 0.10 (from 0.8). As a side-effect, the `Distribution<T> for StandardUniform`
implementations for SIMD types containing `isize`/`usize` now generate random values via `fill_bytes()`, because
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ num-traits = { version = "0.2.11", default-features = false }
approx = { version = "0.5", default-features = false }
decimal = { version = "2.0", default-features = false, optional = true }
num-complex = { version = "0.4", default-features = false }
wide = { version = "1", default-features = false, optional = true }
wide = { version = "1.6", default-features = false, optional = true }
fixed = { version = "1", optional = true }
cordic = { version = "0.1", optional = true }
rand = { version = "0.10", optional = true }
serde = { version = "1", default-features = false, optional = true }
rkyv = { version = "0.7", optional = true }
libm_force = { package = "libm", version = "0.2", optional = true }
libm_force = { package = "libm", version = "0.2.15", optional = true }

[package.metadata.docs.rs]
all-features = true
97 changes: 65 additions & 32 deletions src/simd/wide_simd_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

//! Traits for SIMD values.

use crate::scalar::{ComplexField, Field, SubsetOf, SupersetOf};
use crate::scalar::{ComplexField, Field, RealField, SubsetOf, SupersetOf};
use crate::simd::{
PrimitiveSimdValue, SimdBool, SimdComplexField, SimdPartialOrd, SimdRealField, SimdSigned,
SimdValue,
Expand All @@ -18,7 +18,6 @@ use std::{
RemAssign, Sub, SubAssign,
},
};
use wide::{CmpEq, CmpGe, CmpGt, CmpLe, CmpLt, CmpNe};

#[cfg(feature = "rkyv")]
macro_rules! impl_rkyv {
Expand Down Expand Up @@ -164,7 +163,9 @@ macro_rules! impl_wide_f32 (

#[inline(always)]
fn splat(val: Self::Element) -> Self {
$WideF32xX(wide::$f32xX::from(val))
// NOTE: we don’t use `wide::$f32xX::from(val)` because this results in
// an inefficient `memset_pattern16` libc call.
$WideF32xX(wide::$f32xX::new([val, $({ let _ = $ii; val }),+]))
}

#[inline(always)]
Expand Down Expand Up @@ -193,7 +194,7 @@ macro_rules! impl_wide_f32 (

#[inline(always)]
fn select(self, cond: Self::SimdBool, other: Self) -> Self {
$WideF32xX(cond.0.blend(self.0, other.0))
$WideF32xX(cond.0.select(self.0, other.0))
}
}

Expand Down Expand Up @@ -239,7 +240,7 @@ macro_rules! impl_wide_f32 (

#[inline(always)]
fn select(self, cond: Self::SimdBool, other: Self) -> Self {
$WideBoolF32xX(cond.0.blend(self.0, other.0))
$WideBoolF32xX(cond.0.select(self.0, other.0))
}
}

Expand Down Expand Up @@ -722,12 +723,12 @@ macro_rules! impl_wide_f32 (
impl SimdRealField for $WideF32xX {
#[inline(always)]
fn simd_atan2(self, other: Self) -> Self {
self.zip_map_lanes(other, |a, b| a.atan2(b))
self.zip_map_lanes(other, <$f32 as RealField>::atan2)
}

#[inline(always)]
fn simd_copysign(self, sign: Self) -> Self {
let neg_zero = wide::$f32xX::from(-0.0);
let neg_zero = <Self as SimdValue>::splat(-0.0).0;
$WideF32xX((neg_zero & sign.0) | ((!neg_zero) & self.0))
}

Expand Down Expand Up @@ -863,7 +864,7 @@ macro_rules! impl_wide_f32 (
#[inline(always)]
fn simd_to_exp(self) -> (Self::SimdRealField, Self) {
let ge = self.0.simd_ge(Self::one().0);
let exp = ge.blend(Self::one().0, -Self::one().0);
let exp = ge.select(Self::one().0, -Self::one().0);
($WideF32xX(self.0 * exp), $WideF32xX(exp))
}

Expand Down Expand Up @@ -934,12 +935,12 @@ macro_rules! impl_wide_f32 (

#[inline(always)]
fn simd_powf(self, n: Self) -> Self {
self.zip_map_lanes(n, |e, n| e.powf(n))
self.zip_map_lanes(n, <$f32 as ComplexField>::powf)
}

#[inline(always)]
fn simd_powc(self, n: Self) -> Self {
self.zip_map_lanes(n, |e, n| e.powf(n))
self.zip_map_lanes(n, <$f32 as ComplexField>::powf)
}

#[inline(always)]
Expand All @@ -949,128 +950,160 @@ macro_rules! impl_wide_f32 (

#[inline(always)]
fn simd_exp(self) -> Self {
self.map_lanes(|e| e.exp())
self.map_lanes(<$f32 as ComplexField>::exp)
}

#[inline(always)]
fn simd_exp2(self) -> Self {
self.map_lanes(|e| e.exp2())
self.map_lanes(<$f32 as ComplexField>::exp2)
}

#[inline(always)]
fn simd_exp_m1(self) -> Self {
self.map_lanes(|e| e.exp_m1())
self.map_lanes(<$f32 as ComplexField>::exp_m1)
}

#[inline(always)]
fn simd_ln_1p(self) -> Self {
self.map_lanes(|e| e.ln_1p())
self.map_lanes(<$f32 as ComplexField>::ln_1p)
}

#[inline(always)]
fn simd_ln(self) -> Self {
self.map_lanes(|e| e.ln())
self.map_lanes(<$f32 as ComplexField>::ln)
}

#[inline(always)]
fn simd_log(self, base: Self) -> Self {
self.zip_map_lanes(base, |e, b| e.log(b))
// Same formula as `std`'s `log`, per-lane.
self.zip_map_lanes(base, |e, b| <$f32 as ComplexField>::ln(e) / <$f32 as ComplexField>::ln(b))
}

#[inline(always)]
fn simd_log2(self) -> Self {
self.map_lanes(|e| e.log2())
self.map_lanes(<$f32 as ComplexField>::log2)
}

#[inline(always)]
fn simd_log10(self) -> Self {
self.map_lanes(|e| e.log10())
self.map_lanes(<$f32 as ComplexField>::log10)
}

#[inline(always)]
fn simd_cbrt(self) -> Self {
self.map_lanes(|e| e.cbrt())
self.map_lanes(<$f32 as ComplexField>::cbrt)
}

#[inline(always)]
fn simd_hypot(self, other: Self) -> Self::SimdRealField {
self.zip_map_lanes(other, |e, o| e.hypot(o))
self.zip_map_lanes(other, <$f32 as ComplexField>::hypot)
}

// sin/cos/sin_cos keep wide's SIMD polynomial by default (it internally uses
// `mul_add`, fused on NEON but not baseline x86 — fine without a determinism
// requirement) and go per-lane through libm under `libm_force`.
#[cfg(not(feature = "libm_force"))]
#[inline(always)]
fn simd_sin(self) -> Self {
$WideF32xX(self.0.sin())
}

#[cfg(feature = "libm_force")]
#[inline(always)]
fn simd_sin(self) -> Self {
self.map_lanes(<$f32 as ComplexField>::sin)
}

#[cfg(not(feature = "libm_force"))]
#[inline(always)]
fn simd_cos(self) -> Self {
$WideF32xX(self.0.cos())
}

#[cfg(feature = "libm_force")]
#[inline(always)]
fn simd_cos(self) -> Self {
self.map_lanes(<$f32 as ComplexField>::cos)
}

#[inline(always)]
fn simd_tan(self) -> Self {
self.map_lanes(|e| e.tan())
self.map_lanes(<$f32 as ComplexField>::tan)
}

#[inline(always)]
fn simd_asin(self) -> Self {
self.map_lanes(|e| e.asin())
self.map_lanes(<$f32 as ComplexField>::asin)
}

#[inline(always)]
fn simd_acos(self) -> Self {
self.map_lanes(|e| e.acos())
self.map_lanes(<$f32 as ComplexField>::acos)
}

#[inline(always)]
fn simd_atan(self) -> Self {
self.map_lanes(|e| e.atan())
self.map_lanes(<$f32 as ComplexField>::atan)
}

#[cfg(not(feature = "libm_force"))]
#[inline(always)]
fn simd_sin_cos(self) -> (Self, Self) {
let (sin, cos) = self.0.sin_cos();
($WideF32xX(sin), $WideF32xX(cos))
}

#[cfg(feature = "libm_force")]
#[inline(always)]
fn simd_sin_cos(self) -> (Self, Self) {
let mut sin = self;
let mut cos = self;
for ii in 0..$lanes {
let (s, c) = <$f32 as ComplexField>::sin_cos(self.extract(ii));
sin.replace(ii, s);
cos.replace(ii, c);
}
(sin, cos)
}

// #[inline(always]
// fn simd_exp_m1(self) -> Self {
// $libm::exp_m1(self)
// <$f32 as ComplexField>::exp_m1(self)
// }
//
// #[inline(always]
// fn simd_ln_1p(self) -> Self {
// $libm::ln_1p(self)
// <$f32 as ComplexField>::ln_1p(self)
// }
//
#[inline(always)]
fn simd_sinh(self) -> Self {
self.map_lanes(|e| e.sinh())
self.map_lanes(<$f32 as ComplexField>::sinh)
}

#[inline(always)]
fn simd_cosh(self) -> Self {
self.map_lanes(|e| e.cosh())
self.map_lanes(<$f32 as ComplexField>::cosh)
}

#[inline(always)]
fn simd_tanh(self) -> Self {
self.map_lanes(|e| e.tanh())
self.map_lanes(<$f32 as ComplexField>::tanh)
}

#[inline(always)]
fn simd_asinh(self) -> Self {
self.map_lanes(|e| e.asinh())
self.map_lanes(<$f32 as ComplexField>::asinh)
}

#[inline(always)]
fn simd_acosh(self) -> Self {
self.map_lanes(|e| e.acosh())
self.map_lanes(<$f32 as ComplexField>::acosh)
}

#[inline(always)]
fn simd_atanh(self) -> Self {
self.map_lanes(|e| e.atanh())
self.map_lanes(<$f32 as ComplexField>::atanh)
}
}

Expand Down
Loading