diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 12:02:58 +0000 |
commit | 698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch) | |
tree | 173a775858bd501c378080a10dca74132f05bc50 /library/portable-simd/crates/core_simd/tests/ops_macros.rs | |
parent | Initial commit. (diff) | |
download | rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip |
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | library/portable-simd/crates/core_simd/tests/ops_macros.rs | 607 |
1 files changed, 607 insertions, 0 deletions
diff --git a/library/portable-simd/crates/core_simd/tests/ops_macros.rs b/library/portable-simd/crates/core_simd/tests/ops_macros.rs new file mode 100644 index 000000000..f759394d0 --- /dev/null +++ b/library/portable-simd/crates/core_simd/tests/ops_macros.rs @@ -0,0 +1,607 @@ +/// Implements a test on a unary operation using proptest. +/// +/// Compares the vector operation to the equivalent scalar operation. +#[macro_export] +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( + &<core_simd::Simd<$scalar, LANES> as core::ops::$trait>::$fn, + &$scalar_fn, + &|_| true, + ); + } + } + }; + { $scalar:ty, $trait:ident :: $fn:ident } => { + impl_unary_op_test! { $scalar, $trait::$fn, <$scalar as core::ops::$trait>::$fn } + }; +} + +/// Implements a test on a binary operation using proptest. +/// +/// Compares the vector operation to the equivalent scalar operation. +#[macro_export] +macro_rules! impl_binary_op_test { + { $scalar:ty, $trait:ident :: $fn:ident, $trait_assign:ident :: $fn_assign:ident, $scalar_fn:expr } => { + mod $fn { + use super::*; + use core_simd::Simd; + + test_helpers::test_lanes! { + fn normal<const LANES: usize>() { + test_helpers::test_binary_elementwise( + &<Simd<$scalar, LANES> as core::ops::$trait>::$fn, + &$scalar_fn, + &|_, _| true, + ); + } + + fn assign<const LANES: usize>() { + test_helpers::test_binary_elementwise( + &|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign>::$fn_assign(&mut a, b); a }, + &$scalar_fn, + &|_, _| true, + ); + } + } + } + }; + { $scalar:ty, $trait:ident :: $fn:ident, $trait_assign:ident :: $fn_assign:ident } => { + impl_binary_op_test! { $scalar, $trait::$fn, $trait_assign::$fn_assign, <$scalar as core::ops::$trait>::$fn } + }; +} + +/// Implements a test on a binary operation using proptest. +/// +/// Like `impl_binary_op_test`, but allows providing a function for rejecting particular inputs +/// (like the `proptest_assume` macro). +/// +/// Compares the vector operation to the equivalent scalar operation. +#[macro_export] +macro_rules! impl_binary_checked_op_test { + { $scalar:ty, $trait:ident :: $fn:ident, $trait_assign:ident :: $fn_assign:ident, $scalar_fn:expr, $check_fn:expr } => { + mod $fn { + use super::*; + use core_simd::Simd; + + test_helpers::test_lanes! { + fn normal<const LANES: usize>() { + test_helpers::test_binary_elementwise( + &<Simd<$scalar, LANES> as core::ops::$trait>::$fn, + &$scalar_fn, + &|x, y| x.iter().zip(y.iter()).all(|(x, y)| $check_fn(*x, *y)), + ); + } + + fn assign<const LANES: usize>() { + test_helpers::test_binary_elementwise( + &|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign>::$fn_assign(&mut a, b); a }, + &$scalar_fn, + &|x, y| x.iter().zip(y.iter()).all(|(x, y)| $check_fn(*x, *y)), + ) + } + } + } + }; + { $scalar:ty, $trait:ident :: $fn:ident, $trait_assign:ident :: $fn_assign:ident, $check_fn:expr } => { + impl_binary_checked_op_test! { $scalar, $trait::$fn, $trait_assign::$fn_assign, <$scalar as core::ops::$trait>::$fn, $check_fn } + }; +} + +#[macro_export] +macro_rules! impl_common_integer_tests { + { $vector:ident, $scalar:ident } => { + test_helpers::test_lanes! { + fn reduce_sum<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_sum(), + x.iter().copied().fold(0 as $scalar, $scalar::wrapping_add), + ); + Ok(()) + }); + } + + fn reduce_product<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_product(), + x.iter().copied().fold(1 as $scalar, $scalar::wrapping_mul), + ); + Ok(()) + }); + } + + fn reduce_and<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_and(), + x.iter().copied().fold(-1i8 as $scalar, <$scalar as core::ops::BitAnd>::bitand), + ); + Ok(()) + }); + } + + fn reduce_or<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_or(), + x.iter().copied().fold(0 as $scalar, <$scalar as core::ops::BitOr>::bitor), + ); + Ok(()) + }); + } + + fn reduce_xor<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_xor(), + x.iter().copied().fold(0 as $scalar, <$scalar as core::ops::BitXor>::bitxor), + ); + Ok(()) + }); + } + + fn reduce_max<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_max(), + x.iter().copied().max().unwrap(), + ); + Ok(()) + }); + } + + fn reduce_min<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + $vector::<LANES>::from_array(x).reduce_min(), + x.iter().copied().min().unwrap(), + ); + Ok(()) + }); + } + } + } +} + +/// Implement tests for signed integers. +#[macro_export] +macro_rules! impl_signed_tests { + { $scalar:tt } => { + mod $scalar { + use core_simd::simd::SimdInt; + type Vector<const LANES: usize> = core_simd::Simd<Scalar, LANES>; + type Scalar = $scalar; + + impl_common_integer_tests! { Vector, Scalar } + + test_helpers::test_lanes! { + fn neg<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &<Vector::<LANES> as core::ops::Neg>::neg, + &<Scalar as core::ops::Neg>::neg, + &|x| !x.contains(&Scalar::MIN), + ); + } + + fn is_positive<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_positive, + &Scalar::is_positive, + &|_| true, + ); + } + + fn is_negative<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_negative, + &Scalar::is_negative, + &|_| true, + ); + } + + fn signum<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::signum, + &Scalar::signum, + &|_| true, + ) + } + + fn div_min_may_overflow<const LANES: usize>() { + let a = Vector::<LANES>::splat(Scalar::MIN); + let b = Vector::<LANES>::splat(-1); + assert_eq!(a / b, a); + } + + fn rem_min_may_overflow<const LANES: usize>() { + let a = Vector::<LANES>::splat(Scalar::MIN); + let b = Vector::<LANES>::splat(-1); + assert_eq!(a % b, Vector::<LANES>::splat(0)); + } + + fn simd_min<const LANES: usize>() { + use core_simd::simd::SimdOrd; + let a = Vector::<LANES>::splat(Scalar::MIN); + let b = Vector::<LANES>::splat(0); + assert_eq!(a.simd_min(b), a); + let a = Vector::<LANES>::splat(Scalar::MAX); + let b = Vector::<LANES>::splat(0); + assert_eq!(a.simd_min(b), b); + } + + fn simd_max<const LANES: usize>() { + use core_simd::simd::SimdOrd; + let a = Vector::<LANES>::splat(Scalar::MIN); + let b = Vector::<LANES>::splat(0); + assert_eq!(a.simd_max(b), b); + let a = Vector::<LANES>::splat(Scalar::MAX); + let b = Vector::<LANES>::splat(0); + assert_eq!(a.simd_max(b), a); + } + + fn simd_clamp<const LANES: usize>() { + use core_simd::simd::SimdOrd; + let min = Vector::<LANES>::splat(Scalar::MIN); + let max = Vector::<LANES>::splat(Scalar::MAX); + let zero = Vector::<LANES>::splat(0); + let one = Vector::<LANES>::splat(1); + let negone = Vector::<LANES>::splat(-1); + assert_eq!(zero.simd_clamp(min, max), zero); + assert_eq!(zero.simd_clamp(min, one), zero); + assert_eq!(zero.simd_clamp(one, max), one); + assert_eq!(zero.simd_clamp(min, negone), negone); + } + } + + test_helpers::test_lanes_panic! { + fn div_by_all_zeros_panics<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let b = Vector::<LANES>::splat(0); + let _ = a / b; + } + + fn div_by_one_zero_panics<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let mut b = Vector::<LANES>::splat(21); + b[0] = 0 as _; + let _ = a / b; + } + + fn rem_zero_panic<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let b = Vector::<LANES>::splat(0); + let _ = a % b; + } + } + + test_helpers::test_lanes! { + fn div_neg_one_no_panic<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let b = Vector::<LANES>::splat(-1); + let _ = a / b; + } + + fn rem_neg_one_no_panic<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let b = Vector::<LANES>::splat(-1); + let _ = a % b; + } + } + + impl_binary_op_test!(Scalar, Add::add, AddAssign::add_assign, Scalar::wrapping_add); + impl_binary_op_test!(Scalar, Sub::sub, SubAssign::sub_assign, Scalar::wrapping_sub); + impl_binary_op_test!(Scalar, Mul::mul, MulAssign::mul_assign, Scalar::wrapping_mul); + + // Exclude Div and Rem panicking cases + impl_binary_checked_op_test!(Scalar, Div::div, DivAssign::div_assign, Scalar::wrapping_div, |x, y| y != 0 && !(x == Scalar::MIN && y == -1)); + impl_binary_checked_op_test!(Scalar, Rem::rem, RemAssign::rem_assign, Scalar::wrapping_rem, |x, y| y != 0 && !(x == Scalar::MIN && y == -1)); + + impl_unary_op_test!(Scalar, Not::not); + impl_binary_op_test!(Scalar, BitAnd::bitand, BitAndAssign::bitand_assign); + impl_binary_op_test!(Scalar, BitOr::bitor, BitOrAssign::bitor_assign); + impl_binary_op_test!(Scalar, BitXor::bitxor, BitXorAssign::bitxor_assign); + } + } +} + +/// Implement tests for unsigned integers. +#[macro_export] +macro_rules! impl_unsigned_tests { + { $scalar:tt } => { + mod $scalar { + use core_simd::simd::SimdUint; + type Vector<const LANES: usize> = core_simd::Simd<Scalar, LANES>; + type Scalar = $scalar; + + impl_common_integer_tests! { Vector, Scalar } + + test_helpers::test_lanes_panic! { + fn rem_zero_panic<const LANES: usize>() { + let a = Vector::<LANES>::splat(42); + let b = Vector::<LANES>::splat(0); + let _ = a % b; + } + } + + impl_binary_op_test!(Scalar, Add::add, AddAssign::add_assign, Scalar::wrapping_add); + impl_binary_op_test!(Scalar, Sub::sub, SubAssign::sub_assign, Scalar::wrapping_sub); + impl_binary_op_test!(Scalar, Mul::mul, MulAssign::mul_assign, Scalar::wrapping_mul); + + // Exclude Div and Rem panicking cases + impl_binary_checked_op_test!(Scalar, Div::div, DivAssign::div_assign, Scalar::wrapping_div, |_, y| y != 0); + impl_binary_checked_op_test!(Scalar, Rem::rem, RemAssign::rem_assign, Scalar::wrapping_rem, |_, y| y != 0); + + impl_unary_op_test!(Scalar, Not::not); + impl_binary_op_test!(Scalar, BitAnd::bitand, BitAndAssign::bitand_assign); + impl_binary_op_test!(Scalar, BitOr::bitor, BitOrAssign::bitor_assign); + impl_binary_op_test!(Scalar, BitXor::bitxor, BitXorAssign::bitxor_assign); + } + } +} + +/// Implement tests for floating point numbers. +#[macro_export] +macro_rules! impl_float_tests { + { $scalar:tt, $int_scalar:tt } => { + mod $scalar { + use core_simd::SimdFloat; + type Vector<const LANES: usize> = core_simd::Simd<Scalar, LANES>; + type Scalar = $scalar; + + impl_unary_op_test!(Scalar, Neg::neg); + impl_binary_op_test!(Scalar, Add::add, AddAssign::add_assign); + impl_binary_op_test!(Scalar, Sub::sub, SubAssign::sub_assign); + impl_binary_op_test!(Scalar, Mul::mul, MulAssign::mul_assign); + impl_binary_op_test!(Scalar, Div::div, DivAssign::div_assign); + impl_binary_op_test!(Scalar, Rem::rem, RemAssign::rem_assign); + + test_helpers::test_lanes! { + fn is_sign_positive<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_sign_positive, + &Scalar::is_sign_positive, + &|_| true, + ); + } + + fn is_sign_negative<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_sign_negative, + &Scalar::is_sign_negative, + &|_| true, + ); + } + + fn is_finite<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_finite, + &Scalar::is_finite, + &|_| true, + ); + } + + fn is_infinite<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_infinite, + &Scalar::is_infinite, + &|_| true, + ); + } + + fn is_nan<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_nan, + &Scalar::is_nan, + &|_| true, + ); + } + + fn is_normal<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_normal, + &Scalar::is_normal, + &|_| true, + ); + } + + fn is_subnormal<const LANES: usize>() { + test_helpers::test_unary_mask_elementwise( + &Vector::<LANES>::is_subnormal, + &Scalar::is_subnormal, + &|_| true, + ); + } + + fn abs<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::abs, + &Scalar::abs, + &|_| true, + ) + } + + fn recip<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::recip, + &Scalar::recip, + &|_| true, + ) + } + + fn to_degrees<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::to_degrees, + &Scalar::to_degrees, + &|_| true, + ) + } + + fn to_radians<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::to_radians, + &Scalar::to_radians, + &|_| true, + ) + } + + fn signum<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::signum, + &Scalar::signum, + &|_| true, + ) + } + + fn copysign<const LANES: usize>() { + test_helpers::test_binary_elementwise( + &Vector::<LANES>::copysign, + &Scalar::copysign, + &|_, _| true, + ) + } + + fn simd_min<const LANES: usize>() { + // Regular conditions (both values aren't zero) + test_helpers::test_binary_elementwise( + &Vector::<LANES>::simd_min, + &Scalar::min, + // Reject the case where both values are zero with different signs + &|a, b| { + for (a, b) in a.iter().zip(b.iter()) { + if *a == 0. && *b == 0. && a.signum() != b.signum() { + return false; + } + } + true + } + ); + + // Special case where both values are zero + let p_zero = Vector::<LANES>::splat(0.); + let n_zero = Vector::<LANES>::splat(-0.); + assert!(p_zero.simd_min(n_zero).to_array().iter().all(|x| *x == 0.)); + assert!(n_zero.simd_min(p_zero).to_array().iter().all(|x| *x == 0.)); + } + + fn simd_max<const LANES: usize>() { + // Regular conditions (both values aren't zero) + test_helpers::test_binary_elementwise( + &Vector::<LANES>::simd_max, + &Scalar::max, + // Reject the case where both values are zero with different signs + &|a, b| { + for (a, b) in a.iter().zip(b.iter()) { + if *a == 0. && *b == 0. && a.signum() != b.signum() { + return false; + } + } + true + } + ); + + // Special case where both values are zero + let p_zero = Vector::<LANES>::splat(0.); + let n_zero = Vector::<LANES>::splat(-0.); + assert!(p_zero.simd_max(n_zero).to_array().iter().all(|x| *x == 0.)); + assert!(n_zero.simd_max(p_zero).to_array().iter().all(|x| *x == 0.)); + } + + fn simd_clamp<const LANES: usize>() { + test_helpers::test_3(&|value: [Scalar; LANES], mut min: [Scalar; LANES], mut max: [Scalar; LANES]| { + for (min, max) in min.iter_mut().zip(max.iter_mut()) { + if max < min { + core::mem::swap(min, max); + } + if min.is_nan() { + *min = Scalar::NEG_INFINITY; + } + if max.is_nan() { + *max = Scalar::INFINITY; + } + } + + let mut result_scalar = [Scalar::default(); LANES]; + for i in 0..LANES { + result_scalar[i] = value[i].clamp(min[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); + Ok(()) + }) + } + + fn reduce_sum<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + Vector::<LANES>::from_array(x).reduce_sum(), + x.iter().sum(), + ); + Ok(()) + }); + } + + fn reduce_product<const LANES: usize>() { + test_helpers::test_1(&|x| { + test_helpers::prop_assert_biteq! ( + Vector::<LANES>::from_array(x).reduce_product(), + x.iter().product(), + ); + Ok(()) + }); + } + + fn reduce_max<const LANES: usize>() { + test_helpers::test_1(&|x| { + let vmax = Vector::<LANES>::from_array(x).reduce_max(); + let smax = x.iter().copied().fold(Scalar::NAN, Scalar::max); + // 0 and -0 are treated the same + if !(x.contains(&0.) && x.contains(&-0.) && vmax.abs() == 0. && smax.abs() == 0.) { + test_helpers::prop_assert_biteq!(vmax, smax); + } + Ok(()) + }); + } + + fn reduce_min<const LANES: usize>() { + test_helpers::test_1(&|x| { + let vmax = Vector::<LANES>::from_array(x).reduce_min(); + let smax = x.iter().copied().fold(Scalar::NAN, Scalar::min); + // 0 and -0 are treated the same + if !(x.contains(&0.) && x.contains(&-0.) && vmax.abs() == 0. && smax.abs() == 0.) { + test_helpers::prop_assert_biteq!(vmax, smax); + } + Ok(()) + }); + } + } + + #[cfg(feature = "std")] + mod std { + use std_float::StdFloat; + + use super::*; + test_helpers::test_lanes! { + fn sqrt<const LANES: usize>() { + test_helpers::test_unary_elementwise( + &Vector::<LANES>::sqrt, + &Scalar::sqrt, + &|_| true, + ) + } + + fn mul_add<const LANES: usize>() { + test_helpers::test_ternary_elementwise( + &Vector::<LANES>::mul_add, + &Scalar::mul_add, + &|_, _, _| true, + ) + } + } + } + } + } +} |