From 80469c66d650735a0520ae471c5fe5ebaf8971b5 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 13 Mar 2022 19:07:36 +0000 Subject: [PATCH 1/3] Move comparisons to SimdPartialOrd and SimdOrd traits --- crates/core_simd/src/comparisons.rs | 120 --------------- crates/core_simd/src/eq.rs | 73 +++++++++ crates/core_simd/src/masks.rs | 4 +- crates/core_simd/src/mod.rs | 5 +- crates/core_simd/src/ops.rs | 10 +- crates/core_simd/src/ord.rs | 222 +++++++++++++++++++++++++++ crates/core_simd/src/vector.rs | 16 +- crates/core_simd/src/vector/float.rs | 26 ++-- crates/core_simd/src/vector/int.rs | 6 +- crates/core_simd/tests/i16_ops.rs | 27 ---- crates/core_simd/tests/ops_macros.rs | 45 +++--- 11 files changed, 354 insertions(+), 200 deletions(-) delete mode 100644 crates/core_simd/src/comparisons.rs create mode 100644 crates/core_simd/src/eq.rs create mode 100644 crates/core_simd/src/ord.rs diff --git a/crates/core_simd/src/comparisons.rs b/crates/core_simd/src/comparisons.rs deleted file mode 100644 index 7b0d0a6864b..00000000000 --- a/crates/core_simd/src/comparisons.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::simd::intrinsics; -use crate::simd::{LaneCount, Mask, Simd, SimdElement, SupportedLaneCount}; - -impl Simd -where - T: SimdElement + PartialEq, - LaneCount: SupportedLaneCount, -{ - /// Test if each lane is equal to the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_eq(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_eq(self, other)) } - } - - /// Test if each lane is not equal to the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_ne(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_ne(self, other)) } - } -} - -impl Simd -where - T: SimdElement + PartialOrd, - LaneCount: SupportedLaneCount, -{ - /// Test if each lane is less than the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_lt(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_lt(self, other)) } - } - - /// Test if each lane is greater than the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_gt(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_gt(self, other)) } - } - - /// Test if each lane is less than or equal to the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_le(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_le(self, other)) } - } - - /// Test if each lane is greater than or equal to the corresponding lane in `other`. - #[inline] - #[must_use = "method returns a new mask and does not mutate the original value"] - pub fn lanes_ge(self, other: Self) -> Mask { - // Safety: `self` is a vector, and the result of the comparison - // is always a valid mask. - unsafe { Mask::from_int_unchecked(intrinsics::simd_ge(self, other)) } - } -} - -macro_rules! impl_ord_methods_vector { - { $type:ty } => { - impl Simd<$type, LANES> - where - LaneCount: SupportedLaneCount, - { - /// Returns the lane-wise minimum with `other`. - #[must_use = "method returns a new vector and does not mutate the original value"] - #[inline] - pub fn min(self, other: Self) -> Self { - self.lanes_gt(other).select(other, self) - } - - /// Returns the lane-wise maximum with `other`. - #[must_use = "method returns a new vector and does not mutate the original value"] - #[inline] - pub fn max(self, other: Self) -> Self { - self.lanes_lt(other).select(other, self) - } - - /// Restrict each lane to a certain interval. - /// - /// For each lane, returns `max` if `self` is greater than `max`, and `min` if `self` is - /// less than `min`. Otherwise returns `self`. - /// - /// # Panics - /// - /// Panics if `min > max` on any lane. - #[must_use = "method returns a new vector and does not mutate the original value"] - #[inline] - pub fn clamp(self, min: Self, max: Self) -> Self { - assert!( - min.lanes_le(max).all(), - "each lane in `min` must be less than or equal to the corresponding lane in `max`", - ); - self.max(min).min(max) - } - } - } -} - -impl_ord_methods_vector!(i8); -impl_ord_methods_vector!(i16); -impl_ord_methods_vector!(i32); -impl_ord_methods_vector!(i64); -impl_ord_methods_vector!(isize); -impl_ord_methods_vector!(u8); -impl_ord_methods_vector!(u16); -impl_ord_methods_vector!(u32); -impl_ord_methods_vector!(u64); -impl_ord_methods_vector!(usize); diff --git a/crates/core_simd/src/eq.rs b/crates/core_simd/src/eq.rs new file mode 100644 index 00000000000..c7111f720a8 --- /dev/null +++ b/crates/core_simd/src/eq.rs @@ -0,0 +1,73 @@ +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdElement, SupportedLaneCount}; + +/// Parallel `PartialEq`. +pub trait SimdPartialEq { + /// The mask type returned by each comparison. + type Mask; + + /// Test if each lane is equal to the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_eq(self, other: Self) -> Self::Mask; + + /// Test if each lane is equal to the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_ne(self, other: Self) -> Self::Mask; +} + +macro_rules! impl_number { + { $($number:ty),* } => { + $( + impl SimdPartialEq for Simd<$number, LANES> + where + LaneCount: SupportedLaneCount, + { + type Mask = Mask<<$number as SimdElement>::Mask, LANES>; + + #[inline] + fn simd_eq(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_eq(self, other)) } + } + + #[inline] + fn simd_ne(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_ne(self, other)) } + } + } + )* + } +} + +impl_number! { f32, f64, u8, u16, u32, u64, usize, i8, i16, i32, i64, isize } + +macro_rules! impl_mask { + { $($integer:ty),* } => { + $( + impl SimdPartialEq for Mask<$integer, LANES> + where + LaneCount: SupportedLaneCount, + { + type Mask = Self; + + #[inline] + fn simd_eq(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_eq(self.to_int(), other.to_int())) } + } + + #[inline] + fn simd_ne(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_ne(self.to_int(), other.to_int())) } + } + } + )* + } +} + +impl_mask! { i8, i16, i32, i64, isize } diff --git a/crates/core_simd/src/masks.rs b/crates/core_simd/src/masks.rs index e1cd7930450..d4e57ed90bf 100644 --- a/crates/core_simd/src/masks.rs +++ b/crates/core_simd/src/masks.rs @@ -15,7 +15,7 @@ mod mask_impl; mod to_bitmask; pub use to_bitmask::ToBitMask; -use crate::simd::{intrinsics, LaneCount, Simd, SimdElement, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Simd, SimdElement, SimdPartialEq, SupportedLaneCount}; use core::cmp::Ordering; use core::{fmt, mem}; @@ -56,7 +56,7 @@ macro_rules! impl_element { where LaneCount: SupportedLaneCount, { - (value.lanes_eq(Simd::splat(0)) | value.lanes_eq(Simd::splat(-1))).all() + (value.simd_eq(Simd::splat(0 as _)) | value.simd_eq(Simd::splat(-1 as _))).all() } fn eq(self, other: Self) -> bool { self == other } diff --git a/crates/core_simd/src/mod.rs b/crates/core_simd/src/mod.rs index 85026265956..42257f4e119 100644 --- a/crates/core_simd/src/mod.rs +++ b/crates/core_simd/src/mod.rs @@ -9,13 +9,14 @@ pub(crate) mod intrinsics; #[cfg(feature = "generic_const_exprs")] mod to_bytes; -mod comparisons; +mod eq; mod fmt; mod iter; mod lane_count; mod masks; mod math; mod ops; +mod ord; mod round; mod select; mod vector; @@ -25,8 +26,10 @@ mod vendor; pub mod simd { pub(crate) use crate::core_simd::intrinsics; + pub use crate::core_simd::eq::*; pub use crate::core_simd::lane_count::{LaneCount, SupportedLaneCount}; pub use crate::core_simd::masks::*; + pub use crate::core_simd::ord::*; pub use crate::core_simd::swizzle::*; pub use crate::core_simd::vector::*; } diff --git a/crates/core_simd/src/ops.rs b/crates/core_simd/src/ops.rs index 1b35b3e717a..d39b4091df9 100644 --- a/crates/core_simd/src/ops.rs +++ b/crates/core_simd/src/ops.rs @@ -1,4 +1,4 @@ -use crate::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount}; +use crate::simd::{LaneCount, Simd, SimdElement, SimdPartialEq, SupportedLaneCount}; use core::ops::{Add, Mul}; use core::ops::{BitAnd, BitOr, BitXor}; use core::ops::{Div, Rem, Sub}; @@ -74,7 +74,7 @@ macro_rules! int_divrem_guard { $simd_call:ident }, $int:ident ) => { - if $rhs.lanes_eq(Simd::splat(0)).any() { + if $rhs.simd_eq(Simd::splat(0 as _)).any() { panic!($zero); } else { // Prevent otherwise-UB overflow on the MIN / -1 case. @@ -82,10 +82,10 @@ macro_rules! int_divrem_guard { // This should, at worst, optimize to a few branchless logical ops // Ideally, this entire conditional should evaporate // Fire LLVM and implement those manually if it doesn't get the hint - ($lhs.lanes_eq(Simd::splat(<$int>::MIN)) + ($lhs.simd_eq(Simd::splat(<$int>::MIN)) // type inference can break here, so cut an SInt to size - & $rhs.lanes_eq(Simd::splat(-1i64 as _))) - .select(Simd::splat(1), $rhs) + & $rhs.simd_eq(Simd::splat(-1i64 as _))) + .select(Simd::splat(1 as _), $rhs) } else { // Nice base case to make it easy to const-fold away the other branch. $rhs diff --git a/crates/core_simd/src/ord.rs b/crates/core_simd/src/ord.rs new file mode 100644 index 00000000000..befa4594595 --- /dev/null +++ b/crates/core_simd/src/ord.rs @@ -0,0 +1,222 @@ +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdElement, SupportedLaneCount}; + +/// Parallel `PartialOrd`. +pub trait SimdPartialOrd { + /// The mask type returned by each comparison. + type Mask; + + /// Test if each lane is less than the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_lt(self, other: Self) -> Self::Mask; + + /// Test if each lane is less than or equal to the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_le(self, other: Self) -> Self::Mask; + + /// Test if each lane is greater than the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_gt(self, other: Self) -> Self::Mask; + + /// Test if each lane is greater than or equal to the corresponding lane in `other`. + #[must_use = "method returns a new mask and does not mutate the original value"] + fn simd_ge(self, other: Self) -> Self::Mask; +} + +/// Parallel `Ord`. +pub trait SimdOrd: SimdPartialOrd { + /// Returns the lane-wise maximum with `other`. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_max(self, other: Self) -> Self; + + /// Returns the lane-wise minimum with `other`. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_min(self, other: Self) -> Self; + + /// Restrict each lane to a certain interval. + /// + /// For each lane, returns `max` if `self` is greater than `max`, and `min` if `self` is + /// less than `min`. Otherwise returns `self`. + /// + /// # Panics + /// + /// Panics if `min > max` on any lane. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_clamp(self, min: Self, max: Self) -> Self; +} + +macro_rules! impl_integer { + { $($integer:ty),* } => { + $( + impl SimdPartialOrd for Simd<$integer, LANES> + where + LaneCount: SupportedLaneCount, + { + type Mask = Mask<<$integer as SimdElement>::Mask, LANES>; + + #[inline] + fn simd_lt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_lt(self, other)) } + } + + #[inline] + fn simd_le(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_le(self, other)) } + } + + #[inline] + fn simd_gt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_gt(self, other)) } + } + + #[inline] + fn simd_ge(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_ge(self, other)) } + } + } + + impl SimdOrd for Simd<$integer, LANES> + where + LaneCount: SupportedLaneCount, + { + #[inline] + fn simd_max(self, other: Self) -> Self { + self.simd_lt(other).select(other, self) + } + + #[inline] + fn simd_min(self, other: Self) -> Self { + self.simd_gt(other).select(other, self) + } + + #[inline] + fn simd_clamp(self, min: Self, max: Self) -> Self { + assert!( + min.simd_le(max).all(), + "each lane in `min` must be less than or equal to the corresponding lane in `max`", + ); + self.simd_max(min).simd_min(max) + } + } + )* + } +} + +impl_integer! { u8, u16, u32, u64, usize, i8, i16, i32, i64, isize } + +macro_rules! impl_float { + { $($float:ty),* } => { + $( + impl SimdPartialOrd for Simd<$float, LANES> + where + LaneCount: SupportedLaneCount, + { + type Mask = Mask<<$float as SimdElement>::Mask, LANES>; + + #[inline] + fn simd_lt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_lt(self, other)) } + } + + #[inline] + fn simd_le(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_le(self, other)) } + } + + #[inline] + fn simd_gt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_gt(self, other)) } + } + + #[inline] + fn simd_ge(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Mask::from_int_unchecked(intrinsics::simd_ge(self, other)) } + } + } + )* + } +} + +impl_float! { f32, f64 } + +macro_rules! impl_mask { + { $($integer:ty),* } => { + $( + impl SimdPartialOrd for Mask<$integer, LANES> + where + LaneCount: SupportedLaneCount, + { + type Mask = Self; + + #[inline] + fn simd_lt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_lt(self.to_int(), other.to_int())) } + } + + #[inline] + fn simd_le(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_le(self.to_int(), other.to_int())) } + } + + #[inline] + fn simd_gt(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_gt(self.to_int(), other.to_int())) } + } + + #[inline] + fn simd_ge(self, other: Self) -> Self::Mask { + // Safety: `self` is a vector, and the result of the comparison + // is always a valid mask. + unsafe { Self::from_int_unchecked(intrinsics::simd_ge(self.to_int(), other.to_int())) } + } + } + + impl SimdOrd for Mask<$integer, LANES> + where + LaneCount: SupportedLaneCount, + { + #[inline] + fn simd_max(self, other: Self) -> Self { + self.simd_gt(other).select_mask(other, self) + } + + #[inline] + fn simd_min(self, other: Self) -> Self { + self.simd_lt(other).select_mask(other, self) + } + + #[inline] + fn simd_clamp(self, min: Self, max: Self) -> Self { + assert!( + min.simd_le(max).all(), + "each lane in `min` must be less than or equal to the corresponding lane in `max`", + ); + self.simd_max(min).simd_min(max) + } + } + )* + } +} + +impl_mask! { i8, i16, i32, i64, isize } diff --git a/crates/core_simd/src/vector.rs b/crates/core_simd/src/vector.rs index 2405c1429b3..13e35ecfa49 100644 --- a/crates/core_simd/src/vector.rs +++ b/crates/core_simd/src/vector.rs @@ -10,7 +10,7 @@ pub use uint::*; pub(crate) mod ptr; use crate::simd::intrinsics; -use crate::simd::{LaneCount, Mask, MaskElement, SupportedLaneCount}; +use crate::simd::{LaneCount, Mask, MaskElement, SimdPartialOrd, SupportedLaneCount}; /// A SIMD vector of `LANES` elements of type `T`. `Simd` has the same shape as [`[T; N]`](array), but operates like `T`. /// @@ -243,7 +243,7 @@ where idxs: Simd, or: Self, ) -> Self { - let enable: Mask = enable & idxs.lanes_lt(Simd::splat(slice.len())); + let enable: Mask = enable & idxs.simd_lt(Simd::splat(slice.len())); // Safety: We have masked-off out-of-bounds lanes. unsafe { Self::gather_select_unchecked(slice, enable, idxs, or) } } @@ -260,13 +260,13 @@ where /// # Examples /// ``` /// # #![feature(portable_simd)] - /// # use core::simd::{Simd, Mask}; + /// # use core_simd::simd::{Simd, SimdPartialOrd, Mask}; /// let vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; /// let idxs = Simd::from_array([9, 3, 0, 5]); /// let alt = Simd::from_array([-5, -4, -3, -2]); /// let enable = Mask::from_array([true, true, true, false]); // Note the final mask lane. /// // If this mask was used to gather, it would be unsound. Let's fix that. - /// let enable = enable & idxs.lanes_lt(Simd::splat(vec.len())); + /// let enable = enable & idxs.simd_lt(Simd::splat(vec.len())); /// /// // We have masked the OOB lane, so it's safe to gather now. /// let result = unsafe { Simd::gather_select_unchecked(&vec, enable, idxs, alt) }; @@ -317,7 +317,7 @@ where /// # Examples /// ``` /// # #![feature(portable_simd)] - /// # use core::simd::{Simd, Mask}; + /// # use core_simd::simd::{Simd, Mask}; /// let mut vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; /// let idxs = Simd::from_array([9, 3, 0, 0]); /// let vals = Simd::from_array([-27, 82, -41, 124]); @@ -333,7 +333,7 @@ where enable: Mask, idxs: Simd, ) { - let enable: Mask = enable & idxs.lanes_lt(Simd::splat(slice.len())); + let enable: Mask = enable & idxs.simd_lt(Simd::splat(slice.len())); // Safety: We have masked-off out-of-bounds lanes. unsafe { self.scatter_select_unchecked(slice, enable, idxs) } } @@ -351,13 +351,13 @@ where /// # Examples /// ``` /// # #![feature(portable_simd)] - /// # use core::simd::{Simd, Mask}; + /// # use core_simd::simd::{Simd, SimdPartialOrd, Mask}; /// let mut vec: Vec = vec![10, 11, 12, 13, 14, 15, 16, 17, 18]; /// let idxs = Simd::from_array([9, 3, 0, 0]); /// let vals = Simd::from_array([-27, 82, -41, 124]); /// let enable = Mask::from_array([true, true, true, false]); // Note the mask of the last lane. /// // If this mask was used to scatter, it would be unsound. Let's fix that. - /// let enable = enable & idxs.lanes_lt(Simd::splat(vec.len())); + /// let enable = enable & idxs.simd_lt(Simd::splat(vec.len())); /// /// // We have masked the OOB lane, so it's safe to scatter now. /// unsafe { vals.scatter_select_unchecked(&mut vec, enable, idxs); } diff --git a/crates/core_simd/src/vector/float.rs b/crates/core_simd/src/vector/float.rs index fcc7f6d8d1c..ebe4fbcb6fa 100644 --- a/crates/core_simd/src/vector/float.rs +++ b/crates/core_simd/src/vector/float.rs @@ -1,7 +1,7 @@ #![allow(non_camel_case_types)] use crate::simd::intrinsics; -use crate::simd::{LaneCount, Mask, Simd, SupportedLaneCount}; +use crate::simd::{LaneCount, Mask, Simd, SimdPartialEq, SimdPartialOrd, SupportedLaneCount}; /// Implements inherent methods for a float vector containing multiple /// `$lanes` of float `$type`, which uses `$bits_ty` as its binary @@ -74,35 +74,35 @@ macro_rules! impl_float_vector { #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_sign_negative(self) -> Mask<$mask_ty, LANES> { let sign_bits = self.to_bits() & Simd::splat((!0 >> 1) + 1); - sign_bits.lanes_gt(Simd::splat(0)) + sign_bits.simd_gt(Simd::splat(0)) } /// Returns true for each lane if its value is `NaN`. #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_nan(self) -> Mask<$mask_ty, LANES> { - self.lanes_ne(self) + self.simd_ne(self) } /// Returns true for each lane if its value is positive infinity or negative infinity. #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_infinite(self) -> Mask<$mask_ty, LANES> { - self.abs().lanes_eq(Self::splat(<$type>::INFINITY)) + self.abs().simd_eq(Self::splat(<$type>::INFINITY)) } /// Returns true for each lane if its value is neither infinite nor `NaN`. #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_finite(self) -> Mask<$mask_ty, LANES> { - self.abs().lanes_lt(Self::splat(<$type>::INFINITY)) + self.abs().simd_lt(Self::splat(<$type>::INFINITY)) } /// Returns true for each lane if its value is subnormal. #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_subnormal(self) -> Mask<$mask_ty, LANES> { - self.abs().lanes_ne(Self::splat(0.0)) & (self.to_bits() & Self::splat(<$type>::INFINITY).to_bits()).lanes_eq(Simd::splat(0)) + self.abs().simd_ne(Self::splat(0.0)) & (self.to_bits() & Self::splat(<$type>::INFINITY).to_bits()).simd_eq(Simd::splat(0)) } /// Returns true for each lane if its value is neither zero, infinite, @@ -110,7 +110,7 @@ macro_rules! impl_float_vector { #[inline] #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_normal(self) -> Mask<$mask_ty, LANES> { - !(self.abs().lanes_eq(Self::splat(0.0)) | self.is_nan() | self.is_subnormal() | self.is_infinite()) + !(self.abs().simd_eq(Self::splat(0.0)) | self.is_nan() | self.is_subnormal() | self.is_infinite()) } /// Replaces each lane with a number that represents its sign. @@ -140,7 +140,7 @@ macro_rules! impl_float_vector { /// If one of the values is `NAN`, then the other value is returned. #[inline] #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn min(self, other: Self) -> Self { + pub fn simd_min(self, other: Self) -> Self { unsafe { intrinsics::simd_fmin(self, other) } } @@ -149,7 +149,7 @@ macro_rules! impl_float_vector { /// If one of the values is `NAN`, then the other value is returned. #[inline] #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn max(self, other: Self) -> Self { + pub fn simd_max(self, other: Self) -> Self { unsafe { intrinsics::simd_fmax(self, other) } } @@ -160,14 +160,14 @@ macro_rules! impl_float_vector { /// than `min`. Otherwise returns the lane in `self`. #[inline] #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn clamp(self, min: Self, max: Self) -> Self { + pub fn simd_clamp(self, min: Self, max: Self) -> Self { assert!( - min.lanes_le(max).all(), + min.simd_le(max).all(), "each lane in `min` must be less than or equal to the corresponding lane in `max`", ); let mut x = self; - x = x.lanes_lt(min).select(min, x); - x = x.lanes_gt(max).select(max, x); + x = x.simd_lt(min).select(min, x); + x = x.simd_gt(max).select(max, x); x } } diff --git a/crates/core_simd/src/vector/int.rs b/crates/core_simd/src/vector/int.rs index 3eac02a2761..85fabdc4e00 100644 --- a/crates/core_simd/src/vector/int.rs +++ b/crates/core_simd/src/vector/int.rs @@ -1,6 +1,6 @@ #![allow(non_camel_case_types)] -use crate::simd::{LaneCount, Mask, Simd, SupportedLaneCount}; +use crate::simd::{LaneCount, Mask, Simd, SimdPartialOrd, SupportedLaneCount}; /// Implements additional integer traits (Eq, Ord, Hash) on the specified vector `$name`, holding multiple `$lanes` of `$type`. macro_rules! impl_integer_vector { @@ -12,13 +12,13 @@ macro_rules! impl_integer_vector { /// Returns true for each positive lane and false if it is zero or negative. #[inline] pub fn is_positive(self) -> Mask<$type, LANES> { - self.lanes_gt(Self::splat(0)) + self.simd_gt(Self::splat(0)) } /// Returns true for each negative lane and false if it is zero or positive. #[inline] pub fn is_negative(self) -> Mask<$type, LANES> { - self.lanes_lt(Self::splat(0)) + self.simd_lt(Self::splat(0)) } /// Returns numbers representing the sign of each lane. diff --git a/crates/core_simd/tests/i16_ops.rs b/crates/core_simd/tests/i16_ops.rs index 171e5b472fa..f6c5d74fbbc 100644 --- a/crates/core_simd/tests/i16_ops.rs +++ b/crates/core_simd/tests/i16_ops.rs @@ -1,32 +1,5 @@ #![feature(portable_simd)] -use core_simd::i16x2; #[macro_use] mod ops_macros; impl_signed_tests! { i16 } - -#[test] -fn max_is_not_lexicographic() { - let a = i16x2::splat(10); - let b = i16x2::from_array([-4, 12]); - assert_eq!(a.max(b), i16x2::from_array([10, 12])); -} - -#[test] -fn min_is_not_lexicographic() { - let a = i16x2::splat(10); - let b = i16x2::from_array([12, -4]); - assert_eq!(a.min(b), i16x2::from_array([10, -4])); -} - -#[test] -fn clamp_is_not_lexicographic() { - let a = i16x2::splat(10); - let lo = i16x2::from_array([-12, -4]); - let up = i16x2::from_array([-4, 12]); - assert_eq!(a.clamp(lo, up), i16x2::from_array([-4, 10])); - - let x = i16x2::from_array([1, 10]); - let y = x.clamp(i16x2::splat(0), i16x2::splat(9)); - assert_eq!(y, i16x2::from_array([1, 9])); -} diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index 7c9b17673ef..f8389c910c6 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -222,34 +222,37 @@ macro_rules! impl_signed_tests { assert_eq!(a % b, Vector::::splat(0)); } - fn min() { + fn simd_min() { + use core_simd::simd::SimdOrd; let a = Vector::::splat(Scalar::MIN); let b = Vector::::splat(0); - assert_eq!(a.min(b), a); + assert_eq!(a.simd_min(b), a); let a = Vector::::splat(Scalar::MAX); let b = Vector::::splat(0); - assert_eq!(a.min(b), b); + assert_eq!(a.simd_min(b), b); } - fn max() { + fn simd_max() { + use core_simd::simd::SimdOrd; let a = Vector::::splat(Scalar::MIN); let b = Vector::::splat(0); - assert_eq!(a.max(b), b); + assert_eq!(a.simd_max(b), b); let a = Vector::::splat(Scalar::MAX); let b = Vector::::splat(0); - assert_eq!(a.max(b), a); + assert_eq!(a.simd_max(b), a); } - fn clamp() { + fn simd_clamp() { + use core_simd::simd::SimdOrd; let min = Vector::::splat(Scalar::MIN); let max = Vector::::splat(Scalar::MAX); let zero = Vector::::splat(0); let one = Vector::::splat(1); let negone = Vector::::splat(-1); - assert_eq!(zero.clamp(min, max), zero); - assert_eq!(zero.clamp(min, one), zero); - assert_eq!(zero.clamp(one, max), one); - assert_eq!(zero.clamp(min, negone), negone); + 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); } } @@ -458,10 +461,10 @@ macro_rules! impl_float_tests { ) } - fn min() { + fn simd_min() { // Regular conditions (both values aren't zero) test_helpers::test_binary_elementwise( - &Vector::::min, + &Vector::::simd_min, &Scalar::min, // Reject the case where both values are zero with different signs &|a, b| { @@ -477,14 +480,14 @@ macro_rules! impl_float_tests { // Special case where both values are zero let p_zero = Vector::::splat(0.); let n_zero = Vector::::splat(-0.); - assert!(p_zero.min(n_zero).to_array().iter().all(|x| *x == 0.)); - assert!(n_zero.min(p_zero).to_array().iter().all(|x| *x == 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 max() { + fn simd_max() { // Regular conditions (both values aren't zero) test_helpers::test_binary_elementwise( - &Vector::::max, + &Vector::::simd_max, &Scalar::max, // Reject the case where both values are zero with different signs &|a, b| { @@ -500,11 +503,11 @@ macro_rules! impl_float_tests { // Special case where both values are zero let p_zero = Vector::::splat(0.); let n_zero = Vector::::splat(-0.); - assert!(p_zero.max(n_zero).to_array().iter().all(|x| *x == 0.)); - assert!(n_zero.max(p_zero).to_array().iter().all(|x| *x == 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 clamp() { + fn simd_clamp() { 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 { @@ -522,7 +525,7 @@ macro_rules! impl_float_tests { for i in 0..LANES { result_scalar[i] = value[i].clamp(min[i], max[i]); } - let result_vector = Vector::from_array(value).clamp(min.into(), max.into()).to_array(); + 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(()) }) From 2a02c4d9cbee120915535f6fc8379849e10e8373 Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Sun, 13 Mar 2022 19:57:06 +0000 Subject: [PATCH 2/3] Create SimdFloat trait --- crates/core_simd/src/vector/float.rs | 106 ++++++++++++++++++--------- crates/core_simd/tests/ops_macros.rs | 3 + 2 files changed, 73 insertions(+), 36 deletions(-) diff --git a/crates/core_simd/src/vector/float.rs b/crates/core_simd/src/vector/float.rs index ebe4fbcb6fa..f422d161178 100644 --- a/crates/core_simd/src/vector/float.rs +++ b/crates/core_simd/src/vector/float.rs @@ -134,42 +134,6 @@ macro_rules! impl_float_vector { let magnitude = self.to_bits() & !Self::splat(-0.).to_bits(); Self::from_bits(sign_bit | magnitude) } - - /// Returns the minimum of each lane. - /// - /// If one of the values is `NAN`, then the other value is returned. - #[inline] - #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn simd_min(self, other: Self) -> Self { - unsafe { intrinsics::simd_fmin(self, other) } - } - - /// Returns the maximum of each lane. - /// - /// If one of the values is `NAN`, then the other value is returned. - #[inline] - #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn simd_max(self, other: Self) -> Self { - unsafe { intrinsics::simd_fmax(self, other) } - } - - /// Restrict each lane to a certain interval unless it is NaN. - /// - /// For each lane in `self`, returns the corresponding lane in `max` if the lane is - /// greater than `max`, and the corresponding lane in `min` if the lane is less - /// than `min`. Otherwise returns the lane in `self`. - #[inline] - #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn simd_clamp(self, min: Self, max: Self) -> Self { - assert!( - min.simd_le(max).all(), - "each lane in `min` must be less than or equal to the corresponding lane in `max`", - ); - let mut x = self; - x = x.simd_lt(min).select(min, x); - x = x.simd_gt(max).select(max, x); - x - } } }; } @@ -197,3 +161,73 @@ pub type f64x4 = Simd; /// Vector of eight `f64` values pub type f64x8 = Simd; + +mod sealed { + pub trait Sealed {} +} +use sealed::Sealed; + +/// SIMD operations on vectors of floating point numbers. +pub trait SimdFloat: Sized + Sealed { + /// Returns the minimum of each lane. + /// + /// If one of the values is `NAN`, then the other value is returned. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_min(self, other: Self) -> Self; + + /// Returns the maximum of each lane. + /// + /// If one of the values is `NAN`, then the other value is returned. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_max(self, other: Self) -> Self; + + /// Restrict each lane to a certain interval unless it is NaN. + /// + /// For each lane in `self`, returns the corresponding lane in `max` if the lane is + /// greater than `max`, and the corresponding lane in `min` if the lane is less + /// than `min`. Otherwise returns the lane in `self`. + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_clamp(self, min: Self, max: Self) -> Self; +} + +macro_rules! impl_simd_float { + { $($float:ty),* } => { + $( + impl Sealed for Simd<$float, LANES> + where + LaneCount: SupportedLaneCount, + { + } + + impl SimdFloat for Simd<$float, LANES> + where + LaneCount: SupportedLaneCount, + { + #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] + fn simd_min(self, other: Self) -> Self { + unsafe { intrinsics::simd_fmin(self, other) } + } + + #[inline] + fn simd_max(self, other: Self) -> Self { + unsafe { intrinsics::simd_fmax(self, other) } + } + + #[inline] + fn simd_clamp(self, min: Self, max: Self) -> Self { + assert!( + min.simd_le(max).all(), + "each lane in `min` must be less than or equal to the corresponding lane in `max`", + ); + let mut x = self; + x = x.simd_lt(min).select(min, x); + x = x.simd_gt(max).select(max, x); + x + } + } + )* + } +} + +impl_simd_float! { f32, f64 } diff --git a/crates/core_simd/tests/ops_macros.rs b/crates/core_simd/tests/ops_macros.rs index f8389c910c6..47fe49b0982 100644 --- a/crates/core_simd/tests/ops_macros.rs +++ b/crates/core_simd/tests/ops_macros.rs @@ -462,6 +462,7 @@ macro_rules! impl_float_tests { } fn simd_min() { + use core_simd::simd::SimdFloat; // Regular conditions (both values aren't zero) test_helpers::test_binary_elementwise( &Vector::::simd_min, @@ -485,6 +486,7 @@ macro_rules! impl_float_tests { } fn simd_max() { + use core_simd::simd::SimdFloat; // Regular conditions (both values aren't zero) test_helpers::test_binary_elementwise( &Vector::::simd_max, @@ -508,6 +510,7 @@ macro_rules! impl_float_tests { } fn simd_clamp() { + use core_simd::simd::SimdFloat; 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 { From 60486e08ed58698c7b6c2b5cd62a9fbd9080bc2f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Tue, 15 Mar 2022 00:17:14 +0000 Subject: [PATCH 3/3] SimdPartialOrd implies SimdPartialEq --- crates/core_simd/src/ord.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/core_simd/src/ord.rs b/crates/core_simd/src/ord.rs index befa4594595..9a87bc2e344 100644 --- a/crates/core_simd/src/ord.rs +++ b/crates/core_simd/src/ord.rs @@ -1,10 +1,7 @@ -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdElement, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; /// Parallel `PartialOrd`. -pub trait SimdPartialOrd { - /// The mask type returned by each comparison. - type Mask; - +pub trait SimdPartialOrd: SimdPartialEq { /// Test if each lane is less than the corresponding lane in `other`. #[must_use = "method returns a new mask and does not mutate the original value"] fn simd_lt(self, other: Self) -> Self::Mask; @@ -51,8 +48,6 @@ macro_rules! impl_integer { where LaneCount: SupportedLaneCount, { - type Mask = Mask<<$integer as SimdElement>::Mask, LANES>; - #[inline] fn simd_lt(self, other: Self) -> Self::Mask { // Safety: `self` is a vector, and the result of the comparison @@ -118,8 +113,6 @@ macro_rules! impl_float { where LaneCount: SupportedLaneCount, { - type Mask = Mask<<$float as SimdElement>::Mask, LANES>; - #[inline] fn simd_lt(self, other: Self) -> Self::Mask { // Safety: `self` is a vector, and the result of the comparison @@ -161,8 +154,6 @@ macro_rules! impl_mask { where LaneCount: SupportedLaneCount, { - type Mask = Self; - #[inline] fn simd_lt(self, other: Self) -> Self::Mask { // Safety: `self` is a vector, and the result of the comparison