Merge commit 'e0e9a4517f
' into sync-portable-simd-2023-11-19
This commit is contained in:
commit
830b387e17
55 changed files with 2119 additions and 1018 deletions
|
@ -113,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 $(,)? } => {
|
||||
|
@ -122,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));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![feature(stdsimd, powerpc_target_feature)]
|
||||
|
||||
pub mod array;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -6,6 +8,9 @@ pub mod wasm;
|
|||
#[macro_use]
|
||||
pub mod biteq;
|
||||
|
||||
pub mod subnormals;
|
||||
use subnormals::FlushSubnormals;
|
||||
|
||||
/// Specifies the default strategy for testing a type.
|
||||
///
|
||||
/// This strategy should be what "makes sense" to test.
|
||||
|
@ -151,7 +156,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,
|
||||
|
@ -177,6 +181,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| subnormals::flush(fs(subnormals::flush_in(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>(
|
||||
|
@ -204,7 +250,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,
|
||||
|
@ -241,6 +286,85 @@ 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| {
|
||||
subnormals::flush(fs(subnormals::flush_in(x), subnormals::flush_in(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 unary vector function against a unary scalar function, applied elementwise.
|
||||
#[inline(never)]
|
||||
pub fn test_binary_mask_elementwise<Scalar1, Scalar2, Vector1, Vector2, Mask, const LANES: usize>(
|
||||
fv: &dyn Fn(Vector1, Vector2) -> Mask,
|
||||
fs: &dyn Fn(Scalar1, Scalar2) -> bool,
|
||||
check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool,
|
||||
) where
|
||||
Scalar1: Copy + core::fmt::Debug + DefaultStrategy,
|
||||
Scalar2: Copy + core::fmt::Debug + DefaultStrategy,
|
||||
Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy,
|
||||
Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy,
|
||||
Mask: Into<[bool; LANES]> + From<[bool; LANES]> + Copy,
|
||||
{
|
||||
test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| {
|
||||
proptest::prop_assume!(check(x, y));
|
||||
let result_v: [bool; LANES] = fv(x.into(), y.into()).into();
|
||||
let result_s: [bool; LANES] = x
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(y.iter().copied())
|
||||
.map(|(x, y)| fs(x, y))
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
crate::prop_assert_biteq!(result_v, result_s);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
/// Test a binary vector-scalar function against a binary scalar function, applied elementwise.
|
||||
#[inline(never)]
|
||||
pub fn test_binary_scalar_rhs_elementwise<
|
||||
|
|
91
library/portable-simd/crates/test_helpers/src/subnormals.rs
Normal file
91
library/portable-simd/crates/test_helpers/src/subnormals.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
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(
|
||||
any(target_arch = "powerpc", all(target_arch = "powerpc64", target_endian = "big")),
|
||||
target_feature = "altivec",
|
||||
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 }
|
||||
|
||||
/// AltiVec should flush subnormal inputs to zero, but QEMU seems to only flush outputs.
|
||||
/// https://gitlab.com/qemu-project/qemu/-/issues/1779
|
||||
#[cfg(all(
|
||||
any(target_arch = "powerpc", target_arch = "powerpc64"),
|
||||
target_feature = "altivec"
|
||||
))]
|
||||
fn in_buggy_qemu() -> bool {
|
||||
use std::sync::OnceLock;
|
||||
static BUGGY: OnceLock<bool> = OnceLock::new();
|
||||
|
||||
fn add(x: f32, y: f32) -> f32 {
|
||||
#[cfg(target_arch = "powerpc")]
|
||||
use core::arch::powerpc::*;
|
||||
#[cfg(target_arch = "powerpc64")]
|
||||
use core::arch::powerpc64::*;
|
||||
|
||||
let array: [f32; 4] =
|
||||
unsafe { core::mem::transmute(vec_add(vec_splats(x), vec_splats(y))) };
|
||||
array[0]
|
||||
}
|
||||
|
||||
*BUGGY.get_or_init(|| add(-1.0857398e-38, 0.).is_sign_negative())
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
any(target_arch = "powerpc", target_arch = "powerpc64"),
|
||||
target_feature = "altivec"
|
||||
))]
|
||||
pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
|
||||
if in_buggy_qemu() {
|
||||
x
|
||||
} else {
|
||||
x.flush()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(
|
||||
any(target_arch = "powerpc", target_arch = "powerpc64"),
|
||||
target_feature = "altivec"
|
||||
)))]
|
||||
pub fn flush_in<T: FlushSubnormals>(x: T) -> T {
|
||||
x.flush()
|
||||
}
|
||||
|
||||
pub fn flush<T: FlushSubnormals>(x: T) -> T {
|
||||
x.flush()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue