From 6b93a899af4857553aeb9104fbe945b35118d7c9 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Sat, 13 Nov 2021 22:57:12 -0800 Subject: [PATCH 1/3] Introduce trait-driven arithmetic This allows collapsing 5~10 instances of a function on the Simd type into 1-3 copies, at least from the perspective of the docs. The result is far more legible to a user. --- crates/core_simd/src/math.rs | 303 +++++++++++++-------------- crates/core_simd/src/mod.rs | 3 +- crates/core_simd/src/ops.rs | 134 ++++++------ crates/core_simd/src/vector/float.rs | 74 ++++--- crates/core_simd/src/vector/int.rs | 129 +++++++++--- crates/core_simd/src/vector/uint.rs | 1 - 6 files changed, 369 insertions(+), 275 deletions(-) diff --git a/crates/core_simd/src/math.rs b/crates/core_simd/src/math.rs index 2bae414ebfb..a56b8395524 100644 --- a/crates/core_simd/src/math.rs +++ b/crates/core_simd/src/math.rs @@ -1,159 +1,156 @@ -use crate::simd::intrinsics::{simd_saturating_add, simd_saturating_sub}; -use crate::simd::{LaneCount, Simd, SupportedLaneCount}; - -macro_rules! impl_uint_arith { - ($($ty:ty),+) => { - $( impl Simd<$ty, LANES> where LaneCount: SupportedLaneCount { - - /// Lanewise saturating add. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::MAX;")] - /// let x = Simd::from_array([2, 1, 0, MAX]); - /// let max = Simd::splat(MAX); - /// let unsat = x + max; - /// let sat = x.saturating_add(max); - /// assert_eq!(x - 1, unsat); - /// assert_eq!(sat, max); - /// ``` - #[inline] - pub fn saturating_add(self, second: Self) -> Self { - unsafe { simd_saturating_add(self, second) } - } - - /// Lanewise saturating subtract. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::MAX;")] - /// let x = Simd::from_array([2, 1, 0, MAX]); - /// let max = Simd::splat(MAX); - /// let unsat = x - max; - /// let sat = x.saturating_sub(max); - /// assert_eq!(unsat, x + 1); - /// assert_eq!(sat, Simd::splat(0)); - #[inline] - pub fn saturating_sub(self, second: Self) -> Self { - unsafe { simd_saturating_sub(self, second) } - } - })+ +use crate::simd::intrinsics; +use crate::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount}; + +mod sealed { + pub trait Sealed {} +} +use sealed::Sealed; +impl Sealed for Simd +where + T: SimdElement, + LaneCount: SupportedLaneCount, +{ +} + +impl Simd +where + T: Int, + LaneCount: SupportedLaneCount, +{ + /// Lanewise saturating add. + /// + /// # Examples + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "std")] use core_simd::Simd; + /// # #[cfg(not(feature = "std"))] use core::simd::Simd; + /// let x = Simd::from_array([i32::MIN, 0, 1, i32::MAX]); + /// let max = Simd::splat(i32::MAX); + /// let unsat = x + max; + /// let sat = x.saturating_add(max); + /// assert_eq!(unsat, Simd::from_array([-1, i32::MAX, i32::MIN, -2])); + /// assert_eq!(sat, Simd::from_array([-1, i32::MAX, i32::MAX, i32::MAX])); + /// ``` + #[inline] + pub fn saturating_add(self, other: Self) -> Self { + unsafe { intrinsics::simd_saturating_add(self, other) } } + + /// Lanewise saturating subtract. + /// + /// # Examples + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "std")] use core_simd::Simd; + /// # #[cfg(not(feature = "std"))] use core::simd::Simd; + /// let x = Simd::from_array([i32::MIN, -2, -1, i32::MAX]); + /// let max = Simd::splat(i32::MAX); + /// let unsat = x - max; + /// let sat = x.saturating_sub(max); + /// assert_eq!(unsat, Simd::from_array([1, i32::MAX, i32::MIN, 0])); + /// assert_eq!(sat, Simd::from_array([i32::MIN, i32::MIN, i32::MIN, 0])); + #[inline] + pub fn saturating_sub(self, other: Self) -> Self { + unsafe { intrinsics::simd_saturating_sub(self, other) } + } +} + +pub trait Int: SimdElement + PartialOrd { + const BITS: u32; +} + +impl Int for u8 { + const BITS: u32 = 8; +} + +impl Int for i8 { + const BITS: u32 = 8; +} + +impl Int for u16 { + const BITS: u32 = 16; +} + +impl Int for i16 { + const BITS: u32 = 16; +} + +impl Int for u32 { + const BITS: u32 = 32; +} + +impl Int for i32 { + const BITS: u32 = 32; +} + +impl Int for u64 { + const BITS: u32 = 64; +} + +impl Int for i64 { + const BITS: u32 = 64; +} + +impl Int for usize { + const BITS: u32 = usize::BITS; } -macro_rules! impl_int_arith { - ($($ty:ty),+) => { - $( impl Simd<$ty, LANES> where LaneCount: SupportedLaneCount { - - /// Lanewise saturating add. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::{MIN, MAX};")] - /// let x = Simd::from_array([MIN, 0, 1, MAX]); - /// let max = Simd::splat(MAX); - /// let unsat = x + max; - /// let sat = x.saturating_add(max); - /// assert_eq!(unsat, Simd::from_array([-1, MAX, MIN, -2])); - /// assert_eq!(sat, Simd::from_array([-1, MAX, MAX, MAX])); - /// ``` - #[inline] - pub fn saturating_add(self, second: Self) -> Self { - unsafe { simd_saturating_add(self, second) } - } - - /// Lanewise saturating subtract. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::{MIN, MAX};")] - /// let x = Simd::from_array([MIN, -2, -1, MAX]); - /// let max = Simd::splat(MAX); - /// let unsat = x - max; - /// let sat = x.saturating_sub(max); - /// assert_eq!(unsat, Simd::from_array([1, MAX, MIN, 0])); - /// assert_eq!(sat, Simd::from_array([MIN, MIN, MIN, 0])); - #[inline] - pub fn saturating_sub(self, second: Self) -> Self { - unsafe { simd_saturating_sub(self, second) } - } - - /// Lanewise absolute value, implemented in Rust. - /// Every lane becomes its absolute value. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::{MIN, MAX};")] - /// let xs = Simd::from_array([MIN, MIN +1, -5, 0]); - /// assert_eq!(xs.abs(), Simd::from_array([MIN, MAX, 5, 0])); - /// ``` - #[inline] - pub fn abs(self) -> Self { - const SHR: $ty = <$ty>::BITS as $ty - 1; - let m = self >> SHR; - (self^m) - m - } - - /// Lanewise saturating absolute value, implemented in Rust. - /// As abs(), except the MIN value becomes MAX instead of itself. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::{MIN, MAX};")] - /// let xs = Simd::from_array([MIN, -2, 0, 3]); - /// let unsat = xs.abs(); - /// let sat = xs.saturating_abs(); - /// assert_eq!(unsat, Simd::from_array([MIN, 2, 0, 3])); - /// assert_eq!(sat, Simd::from_array([MAX, 2, 0, 3])); - /// ``` - #[inline] - pub fn saturating_abs(self) -> Self { - // arith shift for -1 or 0 mask based on sign bit, giving 2s complement - const SHR: $ty = <$ty>::BITS as $ty - 1; - let m = self >> SHR; - (self^m).saturating_sub(m) - } - - /// Lanewise saturating negation, implemented in Rust. - /// As neg(), except the MIN value becomes MAX instead of itself. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # #[cfg(feature = "std")] use core_simd::Simd; - /// # #[cfg(not(feature = "std"))] use core::simd::Simd; - #[doc = concat!("# use core::", stringify!($ty), "::{MIN, MAX};")] - /// let x = Simd::from_array([MIN, -2, 3, MAX]); - /// let unsat = -x; - /// let sat = x.saturating_neg(); - /// assert_eq!(unsat, Simd::from_array([MIN, 2, -3, MIN + 1])); - /// assert_eq!(sat, Simd::from_array([MAX, 2, -3, MIN + 1])); - /// ``` - #[inline] - pub fn saturating_neg(self) -> Self { - Self::splat(0).saturating_sub(self) - } - })+ +impl Int for isize { + const BITS: u32 = isize::BITS; +} + +pub trait SimdSignum: Sealed { + fn signum(self) -> Self; +} + +impl Simd +where + Self: SimdSignum, + T: SimdElement, + LaneCount: SupportedLaneCount, +{ + /// Replaces each lane with a number that represents its sign. + /// + /// For floats: + /// * `1.0` if the number is positive, `+0.0`, or `INFINITY` + /// * `-1.0` if the number is negative, `-0.0`, or `NEG_INFINITY` + /// * `NAN` if the number is `NAN` + /// + /// For signed integers: + /// * `0` if the number is zero + /// * `1` if the number is positive + /// * `-1` if the number is negative + #[inline] + pub fn signum(self) -> Self { + ::signum(self) } } -impl_uint_arith! { u8, u16, u32, u64, usize } -impl_int_arith! { i8, i16, i32, i64, isize } +pub trait SimdAbs: Sealed { + /// Returns a vector where every lane has the absolute value of the + /// equivalent index in `self`. + fn abs(self) -> Self; +} + +impl Simd +where + Self: SimdAbs, + T: SimdElement, + LaneCount: SupportedLaneCount, +{ + /// Returns a vector where every lane has the absolute value of the + /// equivalent index in `self`. + /// + /// # Examples + /// ```rust + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "std")] use core_simd::Simd; + /// # #[cfg(not(feature = "std"))] use core::simd::Simd; + /// let xs = Simd::from_array([i32::MIN, i32::MIN +1, -5, 0]); + /// assert_eq!(xs.abs(), Simd::from_array([i32::MIN, i32::MAX, 5, 0])); + /// ``` + #[inline] + pub fn abs(self) -> Self { + ::abs(self) + } +} diff --git a/crates/core_simd/src/mod.rs b/crates/core_simd/src/mod.rs index ec874a22389..4c5ebb23511 100644 --- a/crates/core_simd/src/mod.rs +++ b/crates/core_simd/src/mod.rs @@ -5,6 +5,7 @@ mod reduction; mod swizzle; pub(crate) mod intrinsics; +pub(crate) mod math; #[cfg(feature = "generic_const_exprs")] mod to_bytes; @@ -14,7 +15,6 @@ mod fmt; mod iter; mod lane_count; mod masks; -mod math; mod ops; mod round; mod select; @@ -24,6 +24,7 @@ mod vendor; #[doc = include_str!("core_simd_docs.md")] pub mod simd { pub(crate) use crate::core_simd::intrinsics; + pub(crate) use crate::core_simd::math::*; pub use crate::core_simd::lane_count::{LaneCount, SupportedLaneCount}; pub use crate::core_simd::masks::*; diff --git a/crates/core_simd/src/ops.rs b/crates/core_simd/src/ops.rs index 5d7af474caf..8e427be08b4 100644 --- a/crates/core_simd/src/ops.rs +++ b/crates/core_simd/src/ops.rs @@ -174,9 +174,9 @@ macro_rules! impl_op { { impl Shl for $scalar:ty } => { impl_op! { @binary $scalar, Shl::shl, ShlAssign::shl_assign, simd_shl } }; - { impl Shr for $scalar:ty } => { - impl_op! { @binary $scalar, Shr::shr, ShrAssign::shr_assign, simd_shr } - }; + // { impl Shr for $scalar:ty } => { + // impl_op! { @binary $scalar, Shr::shr, ShrAssign::shr_assign, simd_shr } + // }; { impl BitAnd for $scalar:ty } => { impl_op! { @binary $scalar, BitAnd::bitand, BitAndAssign::bitand_assign, simd_and } }; @@ -561,70 +561,70 @@ macro_rules! impl_unsigned_int_ops { } } - impl_ref_ops! { - impl core::ops::Shr for Simd<$scalar, LANES> - where - LaneCount: SupportedLaneCount, - { - type Output = Self; - - #[inline] - fn shr(self, rhs: Self) -> Self::Output { - // TODO there is probably a better way of doing this - if rhs.as_array() - .iter() - .copied() - .any(invalid_shift_rhs) - { - panic!("attempt to shift with overflow"); - } - unsafe { intrinsics::simd_shr(self, rhs) } - } - } - } - - impl_ref_ops! { - impl core::ops::Shr<$scalar> for Simd<$scalar, LANES> - where - LaneCount: SupportedLaneCount, - { - type Output = Self; - - #[inline] - fn shr(self, rhs: $scalar) -> Self::Output { - if invalid_shift_rhs(rhs) { - panic!("attempt to shift with overflow"); - } - let rhs = Self::splat(rhs); - unsafe { intrinsics::simd_shr(self, rhs) } - } - } - } - - - impl_ref_ops! { - impl core::ops::ShrAssign for Simd<$scalar, LANES> - where - LaneCount: SupportedLaneCount, - { - #[inline] - fn shr_assign(&mut self, rhs: Self) { - *self = *self >> rhs; - } - } - } - - impl_ref_ops! { - impl core::ops::ShrAssign<$scalar> for Simd<$scalar, LANES> - where - LaneCount: SupportedLaneCount, - { - #[inline] - fn shr_assign(&mut self, rhs: $scalar) { - *self = *self >> rhs; - } - } - } + // impl_ref_ops! { + // impl core::ops::Shr for Simd<$scalar, LANES> + // where + // LaneCount: SupportedLaneCount, + // { + // type Output = Self; + + // #[inline] + // fn shr(self, rhs: Self) -> Self::Output { + // // TODO there is probably a better way of doing this + // if rhs.as_array() + // .iter() + // .copied() + // .any(invalid_shift_rhs) + // { + // panic!("attempt to shift with overflow"); + // } + // unsafe { intrinsics::simd_shr(self, rhs) } + // } + // } + // } + + // impl_ref_ops! { + // impl core::ops::Shr<$scalar> for Simd<$scalar, LANES> + // where + // LaneCount: SupportedLaneCount, + // { + // type Output = Self; + + // #[inline] + // fn shr(self, rhs: $scalar) -> Self::Output { + // if invalid_shift_rhs(rhs) { + // panic!("attempt to shift with overflow"); + // } + // let rhs = Self::splat(rhs); + // unsafe { intrinsics::simd_shr(self, rhs) } + // } + // } + // } + + + // impl_ref_ops! { + // impl core::ops::ShrAssign for Simd<$scalar, LANES> + // where + // LaneCount: SupportedLaneCount, + // { + // #[inline] + // fn shr_assign(&mut self, rhs: Self) { + // *self = *self >> rhs; + // } + // } + // } + + // impl_ref_ops! { + // impl core::ops::ShrAssign<$scalar> for Simd<$scalar, LANES> + // where + // LaneCount: SupportedLaneCount, + // { + // #[inline] + // fn shr_assign(&mut self, rhs: $scalar) { + // *self = *self >> rhs; + // } + // } + // } )* }; } diff --git a/crates/core_simd/src/vector/float.rs b/crates/core_simd/src/vector/float.rs index 4a4b23238c4..2026669a35d 100644 --- a/crates/core_simd/src/vector/float.rs +++ b/crates/core_simd/src/vector/float.rs @@ -1,14 +1,14 @@ #![allow(non_camel_case_types)] use crate::simd::intrinsics; -use crate::simd::{LaneCount, Mask, Simd, SupportedLaneCount}; +use crate::simd::{LaneCount, Mask, Simd, SimdAbs, SimdSignum, SupportedLaneCount}; /// Implements inherent methods for a float vector containing multiple /// `$lanes` of float `$type`, which uses `$bits_ty` as its binary /// representation. macro_rules! impl_float_vector { - { $type:ty, $bits_ty:ty, $mask_ty:ty } => { - impl Simd<$type, LANES> + { $float:ty, $bits_ty:ty, $mask_ty:ty } => { + impl Simd<$float, LANES> where LaneCount: SupportedLaneCount, { @@ -30,14 +30,6 @@ macro_rules! impl_float_vector { unsafe { core::mem::transmute_copy(&bits) } } - /// Produces a vector where every lane has the absolute value of the - /// equivalently-indexed lane in `self`. - #[inline] - #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn abs(self) -> Self { - unsafe { intrinsics::simd_fabs(self) } - } - /// Fused multiply-add. Computes `(self * a) + b` with only one rounding error, /// yielding a more accurate result than an unfused multiply-add. /// @@ -73,14 +65,14 @@ macro_rules! impl_float_vector { #[must_use = "method returns a new vector and does not mutate the original value"] pub fn to_degrees(self) -> Self { // to_degrees uses a special constant for better precision, so extract that constant - self * Self::splat(<$type>::to_degrees(1.)) + self * Self::splat(<$float>::to_degrees(1.)) } /// Converts each lane from degrees to radians. #[inline] #[must_use = "method returns a new vector and does not mutate the original value"] pub fn to_radians(self) -> Self { - self * Self::splat(<$type>::to_radians(1.)) + self * Self::splat(<$float>::to_radians(1.)) } /// Returns true for each lane if it has a positive sign, including @@ -111,21 +103,21 @@ macro_rules! impl_float_vector { #[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().lanes_eq(Self::splat(<$float>::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().lanes_lt(Self::splat(<$float>::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().lanes_ne(Self::splat(0.0)) & (self.to_bits() & Self::splat(<$float>::INFINITY).to_bits()).lanes_eq(Simd::splat(0)) } /// Returns true for each lane if its value is neither neither zero, infinite, @@ -136,17 +128,6 @@ macro_rules! impl_float_vector { !(self.abs().lanes_eq(Self::splat(0.0)) | self.is_nan() | self.is_subnormal() | self.is_infinite()) } - /// Replaces each lane with a number that represents its sign. - /// - /// * `1.0` if the number is positive, `+0.0`, or `INFINITY` - /// * `-1.0` if the number is negative, `-0.0`, or `NEG_INFINITY` - /// * `NAN` if the number is `NAN` - #[inline] - #[must_use = "method returns a new vector and does not mutate the original value"] - pub fn signum(self) -> Self { - self.is_nan().select(Self::splat(<$type>::NAN), Self::splat(1.0).copysign(self)) - } - /// Returns each lane with the magnitude of `self` and the sign of `sign`. /// /// If any lane is a `NAN`, then a `NAN` with the sign of `sign` is returned. @@ -202,9 +183,48 @@ macro_rules! impl_float_vector { x } } + impl SimdSignum for Simd<$float, LANES> + where + LaneCount: SupportedLaneCount, + { + /// Replaces each lane with a number that represents its sign. + /// + /// For floats: + /// * `1.0` if the number is positive, `+0.0`, or `INFINITY` + /// * `-1.0` if the number is negative, `-0.0`, or `NEG_INFINITY` + /// * `NAN` if the number is `NAN` + #[inline] + fn signum(self) -> Self { + self.is_nan().select(Self::splat(<$float>::NAN), Self::splat(1.0).copysign(self)) + } + } }; } +impl SimdAbs for Simd +where + LaneCount: SupportedLaneCount, +{ + /// Returns a vector where every lane has the absolute value of the + /// equivalent index in `self`. + #[inline] + fn abs(self) -> Self { + unsafe { intrinsics::simd_fabs(self) } + } +} + +impl SimdAbs for Simd +where + LaneCount: SupportedLaneCount, +{ + /// Returns a vector where every lane has the absolute value of the + /// equivalent index in `self`. + #[inline] + fn abs(self) -> Self { + unsafe { intrinsics::simd_fabs(self) } + } +} + impl_float_vector! { f32, u32, i32 } impl_float_vector! { f64, u64, i64 } diff --git a/crates/core_simd/src/vector/int.rs b/crates/core_simd/src/vector/int.rs index 3eac02a2761..5f047d9f95e 100644 --- a/crates/core_simd/src/vector/int.rs +++ b/crates/core_simd/src/vector/int.rs @@ -1,46 +1,123 @@ #![allow(non_camel_case_types)] +use crate::simd::intrinsics; +use crate::simd::{Int, LaneCount, Mask, Simd, SimdAbs, SimdSignum, SupportedLaneCount}; +use core::ops::Shr; + +impl Simd +where + T: SInt, + Self: Default, + LaneCount: SupportedLaneCount, +{ + /// Returns true for each positive lane and false if it is zero or negative. + #[inline] + #[must_use = "method returns a new mask and does not mutate the original value"] + pub fn is_positive(self) -> Mask { + self.lanes_gt(Self::default()) + } -use crate::simd::{LaneCount, Mask, Simd, SupportedLaneCount}; + /// Returns true for each negative lane and false if it is zero or positive. + #[inline] + pub fn is_negative(self) -> Mask { + self.lanes_lt(Self::default()) + } -/// Implements additional integer traits (Eq, Ord, Hash) on the specified vector `$name`, holding multiple `$lanes` of `$type`. -macro_rules! impl_integer_vector { - { $type:ty } => { - impl Simd<$type, LANES> - where - LaneCount: SupportedLaneCount, - { - /// 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)) - } + /// Lanewise saturating absolute value, implemented in Rust. + /// As abs(), except the Scalar::MIN value becomes Scalar::MAX instead of itself. + /// + /// # Examples + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "std")] use core_simd::Simd; + /// # #[cfg(not(feature = "std"))] use core::simd::Simd; + /// let xs = Simd::from_array([i32::MIN, -2, 0, 3]); + /// let unsat = xs.abs(); + /// let sat = xs.saturating_abs(); + /// assert_eq!(unsat, Simd::from_array([i32::MIN, 2, 0, 3])); + /// assert_eq!(sat, Simd::from_array([i32::MAX, 2, 0, 3])); + /// ``` + #[inline] + pub fn saturating_abs(self) -> Self { + // arith shift for -1 or 0 mask based on sign bit, giving 2s complement + let shr = T::BITS - 1; + let m = self >> shr; + unsafe { intrinsics::simd_xor(self, m).saturating_sub(m) } + } - /// 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)) - } + /// Lanewise saturating negation, implemented in Rust. + /// As neg(), except the Scalar::MIN value becomes Scalar::MAX instead of itself. + /// + /// # Examples + /// ``` + /// # #![feature(portable_simd)] + /// # #[cfg(feature = "std")] use core_simd::Simd; + /// # #[cfg(not(feature = "std"))] use core::simd::Simd; + /// let x = Simd::from_array([i32::MIN, -2, 3, i32::MAX]); + /// let unsat = -x; + /// let sat = x.saturating_neg(); + /// assert_eq!(unsat, Simd::from_array([i32::MIN, 2, -3, i32::MIN + 1])); + /// assert_eq!(sat, Simd::from_array([i32::MAX, 2, -3, i32::MIN + 1])); + /// ``` + #[inline] + pub fn saturating_neg(self) -> Self { + Self::default().saturating_sub(self) + } +} + +macro_rules! impl_int_abs { + ($($ty:ty),+) => { + $( impl SimdAbs for Simd<$ty, LANES> where LaneCount: SupportedLaneCount { + #[inline] + fn abs(self) -> Self { + const SHR: u32 = <$ty>::BITS - 1; + let m = self >> SHR; + (self^m) - m + } + } + impl SimdSignum for Simd<$ty, LANES> where LaneCount: SupportedLaneCount { /// Returns numbers representing the sign of each lane. /// * `0` if the number is zero /// * `1` if the number is positive /// * `-1` if the number is negative #[inline] - pub fn signum(self) -> Self { + fn signum(self) -> Self { self.is_positive().select( Self::splat(1), - self.is_negative().select(Self::splat(-1), Self::splat(0)) + self.is_negative().select( + unsafe { intrinsics::simd_neg(Self::splat(1)) }, + Self::default(), + ), ) } - } + })+ + } +} +/// A 2s complement signed integer type. +pub trait SInt: Int {} +impl SInt for isize {} + +impl SInt for i8 {} + +impl SInt for i16 {} + +impl SInt for i32 {} + +impl SInt for i64 {} + +impl Shr for Simd +where + T: SInt, + LaneCount: SupportedLaneCount, +{ + type Output = Self; + + fn shr(self, rhs: u32) -> Self::Output { + unsafe { intrinsics::simd_shr(self, intrinsics::simd_cast(Simd::splat(rhs))) } } } -impl_integer_vector! { isize } -impl_integer_vector! { i16 } -impl_integer_vector! { i32 } -impl_integer_vector! { i64 } -impl_integer_vector! { i8 } +impl_int_abs! { i8, i16, i32, i64, isize } /// Vector of two `isize` values pub type isizex2 = Simd; diff --git a/crates/core_simd/src/vector/uint.rs b/crates/core_simd/src/vector/uint.rs index ed91fc3640e..8b3ab60ddc6 100644 --- a/crates/core_simd/src/vector/uint.rs +++ b/crates/core_simd/src/vector/uint.rs @@ -1,5 +1,4 @@ #![allow(non_camel_case_types)] - use crate::simd::Simd; /// Vector of two `usize` values From 4c99aa4753b17063eb263c5a508697b1e6f59d03 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 16 Nov 2021 18:50:33 -0800 Subject: [PATCH 2/3] Collapse reductions in docs using traits --- crates/core_simd/src/reduction.rs | 167 +++++++++++++++++++----------- 1 file changed, 105 insertions(+), 62 deletions(-) diff --git a/crates/core_simd/src/reduction.rs b/crates/core_simd/src/reduction.rs index db0640aae79..77316c903aa 100644 --- a/crates/core_simd/src/reduction.rs +++ b/crates/core_simd/src/reduction.rs @@ -2,58 +2,117 @@ use crate::simd::intrinsics::{ simd_reduce_add_ordered, simd_reduce_and, simd_reduce_max, simd_reduce_min, simd_reduce_mul_ordered, simd_reduce_or, simd_reduce_xor, }; -use crate::simd::{LaneCount, Simd, SupportedLaneCount}; +use crate::simd::{Int, LaneCount, Simd, SimdElement, SupportedLaneCount}; + +impl Simd +where + T: Int, + LaneCount: SupportedLaneCount, +{ + /// Horizontal bitwise "and". Returns the cumulative bitwise "and" across the lanes of + /// the vector. + #[inline] + pub fn horizontal_and(self) -> T { + unsafe { simd_reduce_and(self) } + } + + /// Horizontal bitwise "or". Returns the cumulative bitwise "or" across the lanes of + /// the vector. + #[inline] + pub fn horizontal_or(self) -> T { + unsafe { simd_reduce_or(self) } + } + + /// Horizontal bitwise "xor". Returns the cumulative bitwise "xor" across the lanes of + /// the vector. + #[inline] + pub fn horizontal_xor(self) -> T { + unsafe { simd_reduce_xor(self) } + } +} + +impl Simd +where + T: SimdElement, + LaneCount: SupportedLaneCount, +{ + /// Horizontal maximum. Returns the maximum lane in the vector. + /// + /// Returns values based on equality, so a vector containing both `0.` and `-0.` may + /// return either. This function will not return `NaN` unless all lanes are `NaN`. + #[inline] + pub fn horizontal_max(self) -> T { + unsafe { simd_reduce_max(self) } + } + + /// Horizontal minimum. Returns the minimum lane in the vector. + /// + /// Returns values based on equality, so a vector containing both `0.` and `-0.` may + /// return either. This function will not return `NaN` unless all lanes are `NaN`. + #[inline] + pub fn horizontal_min(self) -> T { + unsafe { simd_reduce_min(self) } + } +} + +impl Simd +where + Self: HorizontalArith, + T: SimdElement, + LaneCount: SupportedLaneCount, +{ + /// Horizontal add. Returns the sum of the lanes of the vector. + #[inline] + pub fn horizontal_sum(self) -> T { + ::horizontal_sum(self) + } + + /// Horizontal multiply. Returns the product of the lanes of the vector. + #[inline] + pub fn horizontal_product(self) -> T { + ::horizontal_product(self) + } +} + +mod sealed { + pub trait Sealed {} +} +use sealed::Sealed; +impl Sealed for Simd +where + T: SimdElement, + LaneCount: SupportedLaneCount, +{ +} + +pub trait HorizontalArith: Sealed { + type Scalar: SimdElement; + /// Horizontal add. Returns the sum of the lanes of the vector. + fn horizontal_sum(self) -> Self::Scalar; + + /// Horizontal multiply. Returns the product of the lanes of the vector. + fn horizontal_product(self) -> Self::Scalar; +} macro_rules! impl_integer_reductions { { $scalar:ty } => { - impl Simd<$scalar, LANES> + impl HorizontalArith for Simd<$scalar, LANES> where - LaneCount: SupportedLaneCount, - { + LaneCount: SupportedLaneCount, + +{ + type Scalar = $scalar; /// Horizontal wrapping add. Returns the sum of the lanes of the vector, with wrapping addition. #[inline] - pub fn horizontal_sum(self) -> $scalar { + fn horizontal_sum(self) -> $scalar { unsafe { simd_reduce_add_ordered(self, 0) } } /// Horizontal wrapping multiply. Returns the product of the lanes of the vector, with wrapping multiplication. #[inline] - pub fn horizontal_product(self) -> $scalar { + fn horizontal_product(self) -> $scalar { unsafe { simd_reduce_mul_ordered(self, 1) } } - - /// Horizontal bitwise "and". Returns the cumulative bitwise "and" across the lanes of - /// the vector. - #[inline] - pub fn horizontal_and(self) -> $scalar { - unsafe { simd_reduce_and(self) } - } - - /// Horizontal bitwise "or". Returns the cumulative bitwise "or" across the lanes of - /// the vector. - #[inline] - pub fn horizontal_or(self) -> $scalar { - unsafe { simd_reduce_or(self) } - } - - /// Horizontal bitwise "xor". Returns the cumulative bitwise "xor" across the lanes of - /// the vector. - #[inline] - pub fn horizontal_xor(self) -> $scalar { - unsafe { simd_reduce_xor(self) } - } - - /// Horizontal maximum. Returns the maximum lane in the vector. - #[inline] - pub fn horizontal_max(self) -> $scalar { - unsafe { simd_reduce_max(self) } - } - - /// Horizontal minimum. Returns the minimum lane in the vector. - #[inline] - pub fn horizontal_min(self) -> $scalar { - unsafe { simd_reduce_min(self) } - } } } } @@ -71,14 +130,16 @@ impl_integer_reductions! { usize } macro_rules! impl_float_reductions { { $scalar:ty } => { - impl Simd<$scalar, LANES> + impl HorizontalArith for Simd<$scalar, LANES> where - LaneCount: SupportedLaneCount, - { + LaneCount: SupportedLaneCount, + +{ + type Scalar = $scalar; /// Horizontal add. Returns the sum of the lanes of the vector. #[inline] - pub fn horizontal_sum(self) -> $scalar { + fn horizontal_sum(self) -> $scalar { // LLVM sum is inaccurate on i586 if cfg!(all(target_arch = "x86", not(target_feature = "sse2"))) { self.as_array().iter().sum() @@ -89,7 +150,7 @@ macro_rules! impl_float_reductions { /// Horizontal multiply. Returns the product of the lanes of the vector. #[inline] - pub fn horizontal_product(self) -> $scalar { + fn horizontal_product(self) -> $scalar { // LLVM product is inaccurate on i586 if cfg!(all(target_arch = "x86", not(target_feature = "sse2"))) { self.as_array().iter().product() @@ -97,24 +158,6 @@ macro_rules! impl_float_reductions { unsafe { simd_reduce_mul_ordered(self, 1.) } } } - - /// Horizontal maximum. Returns the maximum lane in the vector. - /// - /// Returns values based on equality, so a vector containing both `0.` and `-0.` may - /// return either. This function will not return `NaN` unless all lanes are `NaN`. - #[inline] - pub fn horizontal_max(self) -> $scalar { - unsafe { simd_reduce_max(self) } - } - - /// Horizontal minimum. Returns the minimum lane in the vector. - /// - /// Returns values based on equality, so a vector containing both `0.` and `-0.` may - /// return either. This function will not return `NaN` unless all lanes are `NaN`. - #[inline] - pub fn horizontal_min(self) -> $scalar { - unsafe { simd_reduce_min(self) } - } } } } From b5687deb449da724c7bb2f97102fc397bd61f207 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Tue, 16 Nov 2021 19:18:32 -0800 Subject: [PATCH 3/3] Add #[must_use] annotations --- crates/core_simd/src/math.rs | 4 ++++ crates/core_simd/src/reduction.rs | 7 +++++++ crates/core_simd/src/vector/int.rs | 3 +++ 3 files changed, 14 insertions(+) diff --git a/crates/core_simd/src/math.rs b/crates/core_simd/src/math.rs index a56b8395524..13827437174 100644 --- a/crates/core_simd/src/math.rs +++ b/crates/core_simd/src/math.rs @@ -32,6 +32,7 @@ where /// assert_eq!(sat, Simd::from_array([-1, i32::MAX, i32::MAX, i32::MAX])); /// ``` #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn saturating_add(self, other: Self) -> Self { unsafe { intrinsics::simd_saturating_add(self, other) } } @@ -50,6 +51,7 @@ where /// assert_eq!(unsat, Simd::from_array([1, i32::MAX, i32::MIN, 0])); /// assert_eq!(sat, Simd::from_array([i32::MIN, i32::MIN, i32::MIN, 0])); #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn saturating_sub(self, other: Self) -> Self { unsafe { intrinsics::simd_saturating_sub(self, other) } } @@ -121,6 +123,7 @@ where /// * `1` if the number is positive /// * `-1` if the number is negative #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn signum(self) -> Self { ::signum(self) } @@ -150,6 +153,7 @@ where /// assert_eq!(xs.abs(), Simd::from_array([i32::MIN, i32::MAX, 5, 0])); /// ``` #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn abs(self) -> Self { ::abs(self) } diff --git a/crates/core_simd/src/reduction.rs b/crates/core_simd/src/reduction.rs index 77316c903aa..a7e2136ba3d 100644 --- a/crates/core_simd/src/reduction.rs +++ b/crates/core_simd/src/reduction.rs @@ -12,6 +12,7 @@ where /// Horizontal bitwise "and". Returns the cumulative bitwise "and" across the lanes of /// the vector. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_and(self) -> T { unsafe { simd_reduce_and(self) } } @@ -19,6 +20,7 @@ where /// Horizontal bitwise "or". Returns the cumulative bitwise "or" across the lanes of /// the vector. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_or(self) -> T { unsafe { simd_reduce_or(self) } } @@ -26,6 +28,7 @@ where /// Horizontal bitwise "xor". Returns the cumulative bitwise "xor" across the lanes of /// the vector. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_xor(self) -> T { unsafe { simd_reduce_xor(self) } } @@ -41,6 +44,7 @@ where /// Returns values based on equality, so a vector containing both `0.` and `-0.` may /// return either. This function will not return `NaN` unless all lanes are `NaN`. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_max(self) -> T { unsafe { simd_reduce_max(self) } } @@ -50,6 +54,7 @@ where /// Returns values based on equality, so a vector containing both `0.` and `-0.` may /// return either. This function will not return `NaN` unless all lanes are `NaN`. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_min(self) -> T { unsafe { simd_reduce_min(self) } } @@ -63,12 +68,14 @@ where { /// Horizontal add. Returns the sum of the lanes of the vector. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_sum(self) -> T { ::horizontal_sum(self) } /// Horizontal multiply. Returns the product of the lanes of the vector. #[inline] + #[must_use = "method returns a new value and does not mutate the original vector"] pub fn horizontal_product(self) -> T { ::horizontal_product(self) } diff --git a/crates/core_simd/src/vector/int.rs b/crates/core_simd/src/vector/int.rs index 5f047d9f95e..f903cbd1d7d 100644 --- a/crates/core_simd/src/vector/int.rs +++ b/crates/core_simd/src/vector/int.rs @@ -18,6 +18,7 @@ where /// Returns true for each negative lane and false if it is zero or positive. #[inline] + #[must_use = "method returns a new mask and does not mutate the original value"] pub fn is_negative(self) -> Mask { self.lanes_lt(Self::default()) } @@ -37,6 +38,7 @@ where /// assert_eq!(sat, Simd::from_array([i32::MAX, 2, 0, 3])); /// ``` #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn saturating_abs(self) -> Self { // arith shift for -1 or 0 mask based on sign bit, giving 2s complement let shr = T::BITS - 1; @@ -59,6 +61,7 @@ where /// assert_eq!(sat, Simd::from_array([i32::MAX, 2, -3, i32::MIN + 1])); /// ``` #[inline] + #[must_use = "method returns a new vector and does not mutate the original value"] pub fn saturating_neg(self) -> Self { Self::default().saturating_sub(self) }