rust-lang/portable-simd#265: Move comparisons to traits
A simpler variant of rust-lang/portable-simd#206. * Comparisons are moved to `SimdPartialEq`, `SimdPartialOrd`, and `SimdOrd`. The function names are prefixed with `simd_` to disambiguate from the regular `PartialEq` etc functions. With the functions on traits instead of `Simd` directly, shadowing the function names doesn't work very well. * Floating point `Ord`-like functions are put into a `SimdFloat` trait. The intention is that eventually (some time after this PR) all floating point functions will be moved from `Simd` to `SimdFloat`, and the same goes for future `SimdInt`/`SimdUint` traits.
This commit is contained in:
commit
1ec010db2a
@ -1,120 +0,0 @@
|
||||
use crate::simd::intrinsics;
|
||||
use crate::simd::{LaneCount, Mask, Simd, SimdElement, SupportedLaneCount};
|
||||
|
||||
impl<T, const LANES: usize> Simd<T, LANES>
|
||||
where
|
||||
T: SimdElement + PartialEq,
|
||||
LaneCount<LANES>: 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<T::Mask, LANES> {
|
||||
// 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<T::Mask, LANES> {
|
||||
// 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<T, const LANES: usize> Simd<T, LANES>
|
||||
where
|
||||
T: SimdElement + PartialOrd,
|
||||
LaneCount<LANES>: 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<T::Mask, LANES> {
|
||||
// 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<T::Mask, LANES> {
|
||||
// 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<T::Mask, LANES> {
|
||||
// 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<T::Mask, LANES> {
|
||||
// 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<const LANES: usize> Simd<$type, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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);
|
73
crates/core_simd/src/eq.rs
Normal file
73
crates/core_simd/src/eq.rs
Normal file
@ -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<const LANES: usize> SimdPartialEq for Simd<$number, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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<const LANES: usize> SimdPartialEq for Mask<$integer, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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 }
|
@ -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<LANES>: 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 }
|
||||
|
@ -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::*;
|
||||
}
|
||||
|
@ -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
|
||||
|
213
crates/core_simd/src/ord.rs
Normal file
213
crates/core_simd/src/ord.rs
Normal file
@ -0,0 +1,213 @@
|
||||
use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount};
|
||||
|
||||
/// Parallel `PartialOrd`.
|
||||
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;
|
||||
|
||||
/// 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<const LANES: usize> SimdPartialOrd for Simd<$integer, LANES>
|
||||
where
|
||||
LaneCount<LANES>: SupportedLaneCount,
|
||||
{
|
||||
#[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<const LANES: usize> SimdOrd for Simd<$integer, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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<const LANES: usize> SimdPartialOrd for Simd<$float, LANES>
|
||||
where
|
||||
LaneCount<LANES>: SupportedLaneCount,
|
||||
{
|
||||
#[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<const LANES: usize> SimdPartialOrd for Mask<$integer, LANES>
|
||||
where
|
||||
LaneCount<LANES>: SupportedLaneCount,
|
||||
{
|
||||
#[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<const LANES: usize> SimdOrd for Mask<$integer, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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 }
|
@ -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<T, N>` has the same shape as [`[T; N]`](array), but operates like `T`.
|
||||
///
|
||||
@ -243,7 +243,7 @@ where
|
||||
idxs: Simd<usize, LANES>,
|
||||
or: Self,
|
||||
) -> Self {
|
||||
let enable: Mask<isize, LANES> = enable & idxs.lanes_lt(Simd::splat(slice.len()));
|
||||
let enable: Mask<isize, LANES> = 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<i32> = 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<i32> = 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<isize, LANES>,
|
||||
idxs: Simd<usize, LANES>,
|
||||
) {
|
||||
let enable: Mask<isize, LANES> = enable & idxs.lanes_lt(Simd::splat(slice.len()));
|
||||
let enable: Mask<isize, LANES> = 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<i32> = 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); }
|
||||
|
@ -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.
|
||||
@ -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 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 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 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`",
|
||||
);
|
||||
let mut x = self;
|
||||
x = x.lanes_lt(min).select(min, x);
|
||||
x = x.lanes_gt(max).select(max, x);
|
||||
x
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -197,3 +161,73 @@ pub type f64x4 = Simd<f64, 4>;
|
||||
|
||||
/// A 512-bit SIMD vector with eight elements of type `f64`.
|
||||
pub type f64x8 = Simd<f64, 8>;
|
||||
|
||||
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 <const LANES: usize> Sealed for Simd<$float, LANES>
|
||||
where
|
||||
LaneCount<LANES>: SupportedLaneCount,
|
||||
{
|
||||
}
|
||||
|
||||
impl <const LANES: usize> SimdFloat for Simd<$float, LANES>
|
||||
where
|
||||
LaneCount<LANES>: 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 }
|
||||
|
@ -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.
|
||||
|
@ -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]));
|
||||
}
|
||||
|
@ -222,34 +222,37 @@ macro_rules! impl_signed_tests {
|
||||
assert_eq!(a % b, Vector::<LANES>::splat(0));
|
||||
}
|
||||
|
||||
fn min<const LANES: usize>() {
|
||||
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.min(b), a);
|
||||
assert_eq!(a.simd_min(b), a);
|
||||
let a = Vector::<LANES>::splat(Scalar::MAX);
|
||||
let b = Vector::<LANES>::splat(0);
|
||||
assert_eq!(a.min(b), b);
|
||||
assert_eq!(a.simd_min(b), b);
|
||||
}
|
||||
|
||||
fn max<const LANES: usize>() {
|
||||
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.max(b), b);
|
||||
assert_eq!(a.simd_max(b), b);
|
||||
let a = Vector::<LANES>::splat(Scalar::MAX);
|
||||
let b = Vector::<LANES>::splat(0);
|
||||
assert_eq!(a.max(b), a);
|
||||
assert_eq!(a.simd_max(b), a);
|
||||
}
|
||||
|
||||
fn clamp<const LANES: usize>() {
|
||||
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.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,11 @@ macro_rules! impl_float_tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn min<const LANES: usize>() {
|
||||
fn simd_min<const LANES: usize>() {
|
||||
use core_simd::simd::SimdFloat;
|
||||
// Regular conditions (both values aren't zero)
|
||||
test_helpers::test_binary_elementwise(
|
||||
&Vector::<LANES>::min,
|
||||
&Vector::<LANES>::simd_min,
|
||||
&Scalar::min,
|
||||
// Reject the case where both values are zero with different signs
|
||||
&|a, b| {
|
||||
@ -477,14 +481,15 @@ macro_rules! impl_float_tests {
|
||||
// Special case where both values are zero
|
||||
let p_zero = Vector::<LANES>::splat(0.);
|
||||
let n_zero = Vector::<LANES>::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<const LANES: usize>() {
|
||||
fn simd_max<const LANES: usize>() {
|
||||
use core_simd::simd::SimdFloat;
|
||||
// Regular conditions (both values aren't zero)
|
||||
test_helpers::test_binary_elementwise(
|
||||
&Vector::<LANES>::max,
|
||||
&Vector::<LANES>::simd_max,
|
||||
&Scalar::max,
|
||||
// Reject the case where both values are zero with different signs
|
||||
&|a, b| {
|
||||
@ -500,11 +505,12 @@ macro_rules! impl_float_tests {
|
||||
// Special case where both values are zero
|
||||
let p_zero = Vector::<LANES>::splat(0.);
|
||||
let n_zero = Vector::<LANES>::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<const LANES: usize>() {
|
||||
fn simd_clamp<const LANES: usize>() {
|
||||
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 {
|
||||
@ -522,7 +528,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(())
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user