about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--crates/core_simd/tests/ops_macros.rs23
-rw-r--r--crates/test_helpers/Cargo.toml1
-rw-r--r--crates/test_helpers/src/biteq.rs34
-rw-r--r--crates/test_helpers/src/lib.rs108
-rw-r--r--crates/test_helpers/src/subnormals.rs39
5 files changed, 181 insertions, 24 deletions
diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs
index 3a02f3f01e1..1f5d9488ad0 100644
--- a/crates/core_simd/tests/ops_macros.rs
+++ b/crates/core_simd/tests/ops_macros.rs
@@ -6,7 +6,7 @@ macro_rules! impl_unary_op_test {
     { $scalar:ty, $trait:ident :: $fn:ident, $scalar_fn:expr } => {
         test_helpers::test_lanes! {
             fn $fn<const LANES: usize>() {
-                test_helpers::test_unary_elementwise(
+                test_helpers::test_unary_elementwise_flush_subnormals(
                     &<core_simd::simd::Simd<$scalar, LANES> as core::ops::$trait>::$fn,
                     &$scalar_fn,
                     &|_| true,
@@ -31,7 +31,7 @@ macro_rules! impl_binary_op_test {
 
             test_helpers::test_lanes! {
                 fn normal<const LANES: usize>() {
-                    test_helpers::test_binary_elementwise(
+                    test_helpers::test_binary_elementwise_flush_subnormals(
                         &<Simd<$scalar, LANES> as core::ops::$trait>::$fn,
                         &$scalar_fn,
                         &|_, _| true,
@@ -39,7 +39,7 @@ macro_rules! impl_binary_op_test {
                 }
 
                 fn assign<const LANES: usize>() {
-                    test_helpers::test_binary_elementwise(
+                    test_helpers::test_binary_elementwise_flush_subnormals(
                         &|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign>::$fn_assign(&mut a, b); a },
                         &$scalar_fn,
                         &|_, _| true,
@@ -433,7 +433,7 @@ macro_rules! impl_float_tests {
                 }
 
                 fn to_degrees<const LANES: usize>() {
-                    test_helpers::test_unary_elementwise(
+                    test_helpers::test_unary_elementwise_flush_subnormals(
                         &Vector::<LANES>::to_degrees,
                         &Scalar::to_degrees,
                         &|_| true,
@@ -441,7 +441,7 @@ macro_rules! impl_float_tests {
                 }
 
                 fn to_radians<const LANES: usize>() {
-                    test_helpers::test_unary_elementwise(
+                    test_helpers::test_unary_elementwise_flush_subnormals(
                         &Vector::<LANES>::to_radians,
                         &Scalar::to_radians,
                         &|_| true,
@@ -512,6 +512,7 @@ macro_rules! impl_float_tests {
 
                 fn simd_clamp<const LANES: usize>() {
                     test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| {
+                        use test_helpers::subnormals::FlushSubnormals;
                         for (min, max) in min.iter_mut().zip(max.iter_mut()) {
                             if max < min {
                                 core::mem::swap(min, max);
@@ -528,8 +529,18 @@ macro_rules! impl_float_tests {
                         for i in 0..LANES {
                             result_scalar[i] = value[i].clamp(min[i], max[i]);
                         }
+                        let mut result_scalar_flush = [Scalar::default(); LANES];
+                        for i in 0..LANES {
+                            result_scalar_flush[i] = value[i];
+                            if FlushSubnormals::flush(value[i]) < FlushSubnormals::flush(min[i]) {
+                                result_scalar_flush[i] = min[i];
+                            }
+                            if FlushSubnormals::flush(value[i]) > FlushSubnormals::flush(max[i]) {
+                                result_scalar_flush[i] = max[i];
+                            }
+                        }
                         let result_vector = Vector::from_array(value).simd_clamp(min.into(), max.into()).to_array();
-                        test_helpers::prop_assert_biteq!(result_scalar, result_vector);
+                        test_helpers::prop_assert_biteq!(result_vector, result_scalar, result_scalar_flush);
                         Ok(())
                     })
                 }
diff --git a/crates/test_helpers/Cargo.toml b/crates/test_helpers/Cargo.toml
index bd481db6bac..23dae7c9338 100644
--- a/crates/test_helpers/Cargo.toml
+++ b/crates/test_helpers/Cargo.toml
@@ -5,7 +5,6 @@ edition = "2021"
 publish = false
 
 [dependencies]
-float_eq = "1.0"
 proptest = { version = "0.10", default-features = false, features = ["alloc"] }
 
 [features]
diff --git a/crates/test_helpers/src/biteq.rs b/crates/test_helpers/src/biteq.rs
index 515eaf1c048..cbc20cda0d6 100644
--- a/crates/test_helpers/src/biteq.rs
+++ b/crates/test_helpers/src/biteq.rs
@@ -40,8 +40,6 @@ macro_rules! impl_float_biteq {
             fn biteq(&self, other: &Self) -> bool {
                 if self.is_nan() && other.is_nan() {
                     true // exact nan bits don't matter
-                } else if crate::flush_subnormals::<Self>() {
-                    self.to_bits() == other.to_bits() || float_eq::float_eq!(self, other, abs <= 2. * <$type>::EPSILON)
                 } else {
                     self.to_bits() == other.to_bits()
                 }
@@ -115,6 +113,27 @@ impl<T: BitEq> core::fmt::Debug for BitEqWrapper<'_, T> {
     }
 }
 
+#[doc(hidden)]
+pub struct BitEqEitherWrapper<'a, T>(pub &'a T, pub &'a T);
+
+impl<T: BitEq> PartialEq<BitEqEitherWrapper<'_, T>> for BitEqWrapper<'_, T> {
+    fn eq(&self, other: &BitEqEitherWrapper<'_, T>) -> bool {
+        self.0.biteq(other.0) || self.0.biteq(other.1)
+    }
+}
+
+impl<T: BitEq> core::fmt::Debug for BitEqEitherWrapper<'_, T> {
+    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+        if self.0.biteq(self.1) {
+            self.0.fmt(f)
+        } else {
+            self.0.fmt(f)?;
+            write!(f, " or ")?;
+            self.1.fmt(f)
+        }
+    }
+}
+
 #[macro_export]
 macro_rules! prop_assert_biteq {
     { $a:expr, $b:expr $(,)? } => {
@@ -124,5 +143,14 @@ macro_rules! prop_assert_biteq {
             let b = $b;
             proptest::prop_assert_eq!(BitEqWrapper(&a), BitEqWrapper(&b));
         }
-    }
+    };
+    { $a:expr, $b:expr, $c:expr $(,)? } => {
+        {
+            use $crate::biteq::{BitEqWrapper, BitEqEitherWrapper};
+            let a = $a;
+            let b = $b;
+            let c = $c;
+            proptest::prop_assert_eq!(BitEqWrapper(&a), BitEqEitherWrapper(&b, &c));
+        }
+    };
 }
diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs
index 1b98bccf706..63308a2ca33 100644
--- a/crates/test_helpers/src/lib.rs
+++ b/crates/test_helpers/src/lib.rs
@@ -6,18 +6,8 @@ pub mod wasm;
 #[macro_use]
 pub mod biteq;
 
-/// Indicates if subnormal floats are flushed to zero.
-pub fn flush_subnormals<T>() -> bool {
-    let is_f32 = core::mem::size_of::<T>() == 4;
-    let ppc_flush = is_f32
-        && cfg!(all(
-            target_arch = "powerpc64",
-            target_endian = "big",
-            not(target_feature = "vsx")
-        ));
-    let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon"));
-    ppc_flush || arm_flush
-}
+pub mod subnormals;
+use subnormals::FlushSubnormals;
 
 /// Specifies the default strategy for testing a type.
 ///
@@ -164,7 +154,6 @@ pub fn test_3<
 }
 
 /// Test a unary vector function against a unary scalar function, applied elementwise.
-#[inline(never)]
 pub fn test_unary_elementwise<Scalar, ScalarResult, Vector, VectorResult, const LANES: usize>(
     fv: &dyn Fn(Vector) -> VectorResult,
     fs: &dyn Fn(Scalar) -> ScalarResult,
@@ -191,6 +180,48 @@ pub fn test_unary_elementwise<Scalar, ScalarResult, Vector, VectorResult, const
 }
 
 /// Test a unary vector function against a unary scalar function, applied elementwise.
+///
+/// Where subnormals are flushed, use approximate equality.
+pub fn test_unary_elementwise_flush_subnormals<
+    Scalar,
+    ScalarResult,
+    Vector,
+    VectorResult,
+    const LANES: usize,
+>(
+    fv: &dyn Fn(Vector) -> VectorResult,
+    fs: &dyn Fn(Scalar) -> ScalarResult,
+    check: &dyn Fn([Scalar; LANES]) -> bool,
+) where
+    Scalar: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals,
+    ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy + FlushSubnormals,
+    Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy,
+    VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy,
+{
+    let flush = |x: Scalar| FlushSubnormals::flush(fs(FlushSubnormals::flush(x)));
+    test_1(&|x: [Scalar; LANES]| {
+        proptest::prop_assume!(check(x));
+        let result_v: [ScalarResult; LANES] = fv(x.into()).into();
+        let result_s: [ScalarResult; LANES] = x
+            .iter()
+            .copied()
+            .map(fs)
+            .collect::<Vec<_>>()
+            .try_into()
+            .unwrap();
+        let result_sf: [ScalarResult; LANES] = x
+            .iter()
+            .copied()
+            .map(flush)
+            .collect::<Vec<_>>()
+            .try_into()
+            .unwrap();
+        crate::prop_assert_biteq!(result_v, result_s, result_sf);
+        Ok(())
+    });
+}
+
+/// Test a unary vector function against a unary scalar function, applied elementwise.
 #[inline(never)]
 pub fn test_unary_mask_elementwise<Scalar, Vector, Mask, const LANES: usize>(
     fv: &dyn Fn(Vector) -> Mask,
@@ -217,7 +248,6 @@ pub fn test_unary_mask_elementwise<Scalar, Vector, Mask, const LANES: usize>(
 }
 
 /// Test a binary vector function against a binary scalar function, applied elementwise.
-#[inline(never)]
 pub fn test_binary_elementwise<
     Scalar1,
     Scalar2,
@@ -254,6 +284,56 @@ pub fn test_binary_elementwise<
     });
 }
 
+/// Test a binary vector function against a binary scalar function, applied elementwise.
+///
+/// Where subnormals are flushed, use approximate equality.
+pub fn test_binary_elementwise_flush_subnormals<
+    Scalar1,
+    Scalar2,
+    ScalarResult,
+    Vector1,
+    Vector2,
+    VectorResult,
+    const LANES: usize,
+>(
+    fv: &dyn Fn(Vector1, Vector2) -> VectorResult,
+    fs: &dyn Fn(Scalar1, Scalar2) -> ScalarResult,
+    check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool,
+) where
+    Scalar1: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals,
+    Scalar2: Copy + core::fmt::Debug + DefaultStrategy + FlushSubnormals,
+    ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy + FlushSubnormals,
+    Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy,
+    Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy,
+    VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy,
+{
+    let flush = |x: Scalar1, y: Scalar2| {
+        FlushSubnormals::flush(fs(FlushSubnormals::flush(x), FlushSubnormals::flush(y)))
+    };
+    test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| {
+        proptest::prop_assume!(check(x, y));
+        let result_v: [ScalarResult; LANES] = fv(x.into(), y.into()).into();
+        let result_s: [ScalarResult; LANES] = x
+            .iter()
+            .copied()
+            .zip(y.iter().copied())
+            .map(|(x, y)| fs(x, y))
+            .collect::<Vec<_>>()
+            .try_into()
+            .unwrap();
+        let result_sf: [ScalarResult; LANES] = x
+            .iter()
+            .copied()
+            .zip(y.iter().copied())
+            .map(|(x, y)| flush(x, y))
+            .collect::<Vec<_>>()
+            .try_into()
+            .unwrap();
+        crate::prop_assert_biteq!(result_v, result_s, result_sf);
+        Ok(())
+    });
+}
+
 /// Test a binary vector-scalar function against a binary scalar function, applied elementwise.
 #[inline(never)]
 pub fn test_binary_scalar_rhs_elementwise<
diff --git a/crates/test_helpers/src/subnormals.rs b/crates/test_helpers/src/subnormals.rs
new file mode 100644
index 00000000000..122304f96db
--- /dev/null
+++ b/crates/test_helpers/src/subnormals.rs
@@ -0,0 +1,39 @@
+pub trait FlushSubnormals: Sized {
+    fn flush(self) -> Self {
+        self
+    }
+}
+
+impl<T> FlushSubnormals for *const T {}
+impl<T> FlushSubnormals for *mut T {}
+
+macro_rules! impl_float {
+    { $($ty:ty),* } => {
+        $(
+        impl FlushSubnormals for $ty {
+            fn flush(self) -> Self {
+                let is_f32 = core::mem::size_of::<Self>() == 4;
+                let ppc_flush = is_f32 && cfg!(all(target_arch = "powerpc64", target_endian = "big", not(target_feature = "vsx")));
+                let arm_flush = is_f32 && cfg!(all(target_arch = "arm", target_feature = "neon"));
+                let flush = ppc_flush || arm_flush;
+                if flush && self.is_subnormal() {
+                    <$ty>::copysign(0., self)
+                } else {
+                    self
+                }
+            }
+        }
+        )*
+    }
+}
+
+macro_rules! impl_else {
+    { $($ty:ty),* } => {
+        $(
+        impl FlushSubnormals for $ty {}
+        )*
+    }
+}
+
+impl_float! { f32, f64 }
+impl_else! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize }