Merge commit 'a8385522ade6f67853edac730b5bf164ddb298fd' into simd-remove-autosplats

This commit is contained in:
Jubilee Young 2021-12-02 18:27:47 -08:00
commit 2ea27bf6ef
22 changed files with 539 additions and 524 deletions

View file

@ -15,7 +15,7 @@ SIMD can be quite complex, and even a "simple" issue can be huge. If an issue is
## CI
We currently have 2 CI matrices through Travis CI and GitHub Actions that will automatically build and test your change in order to verify that `std::simd`'s portable API is, in fact, portable. If your change builds locally, but does not build on either, this is likely due to a platform-specific concern that your code has not addressed. Please consult the build logs and address the error, or ask for help if you need it.
We currently use GitHub Actions which will automatically build and test your change in order to verify that `std::simd`'s portable API is, in fact, portable. If your change builds locally, but does not build in CI, this is likely due to a platform-specific concern that your code has not addressed. Please consult the build logs and address the error, or ask for help if you need it.
## Beyond stdsimd

View file

@ -1,5 +1,5 @@
# The Rust standard library's portable SIMD API
[![Build Status](https://travis-ci.com/rust-lang/portable-simd.svg?branch=master)](https://travis-ci.com/rust-lang/portable-simd)
![Build Status](https://github.com/rust-lang/portable-simd/actions/workflows/ci.yml/badge.svg?branch=master)
Code repository for the [Portable SIMD Project Group](https://github.com/rust-lang/project-portable-simd).
Please refer to [CONTRIBUTING.md](./CONTRIBUTING.md) for our contributing guidelines.

View file

@ -97,7 +97,7 @@ struct Body {
let sun = &mut sun[0];
for body in rest {
let m_ratio = body.mass / SOLAR_MASS;
sun.v -= body.v * m_ratio;
sun.v -= body.v * Simd::splat(m_ratio);
}
}
@ -143,14 +143,14 @@ struct Body {
let mut i = 0;
for j in 0..N_BODIES {
for k in j + 1..N_BODIES {
let f = r[i] * mag[i];
bodies[j].v -= f * bodies[k].mass;
bodies[k].v += f * bodies[j].mass;
let f = r[i] * Simd::splat(mag[i]);
bodies[j].v -= f * Simd::splat(bodies[k].mass);
bodies[k].v += f * Simd::splat(bodies[j].mass);
i += 1
}
}
for body in bodies {
body.x += dt * body.v
body.x += Simd::splat(dt) * body.v
}
}

View file

@ -8,12 +8,14 @@ impl<T, const LANES: usize> Simd<T, LANES>
{
/// 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> {
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> {
unsafe { Mask::from_int_unchecked(intrinsics::simd_ne(self, other)) }
}
@ -26,24 +28,28 @@ impl<T, const LANES: usize> Simd<T, LANES>
{
/// 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> {
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> {
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> {
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> {
unsafe { Mask::from_int_unchecked(intrinsics::simd_ge(self, other)) }
}

View file

@ -15,34 +15,28 @@ impl<const LANES: usize> LaneCount<LANES> {
pub trait SupportedLaneCount: Sealed {
#[doc(hidden)]
type BitMask: Copy + Default + AsRef<[u8]> + AsMut<[u8]>;
#[doc(hidden)]
type IntBitMask;
}
impl<const LANES: usize> Sealed for LaneCount<LANES> {}
impl SupportedLaneCount for LaneCount<1> {
type BitMask = [u8; 1];
type IntBitMask = u8;
}
impl SupportedLaneCount for LaneCount<2> {
type BitMask = [u8; 1];
type IntBitMask = u8;
}
impl SupportedLaneCount for LaneCount<4> {
type BitMask = [u8; 1];
type IntBitMask = u8;
}
impl SupportedLaneCount for LaneCount<8> {
type BitMask = [u8; 1];
type IntBitMask = u8;
}
impl SupportedLaneCount for LaneCount<16> {
type BitMask = [u8; 2];
type IntBitMask = u16;
}
impl SupportedLaneCount for LaneCount<32> {
type BitMask = [u8; 4];
type IntBitMask = u32;
}
impl SupportedLaneCount for LaneCount<64> {
type BitMask = [u8; 8];
}

View file

@ -129,6 +129,7 @@ pub fn splat(value: bool) -> Self {
/// # Safety
/// All lanes must be either 0 or -1.
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub unsafe fn from_int_unchecked(value: Simd<T, LANES>) -> Self {
unsafe { Self(mask_impl::Mask::from_int_unchecked(value)) }
}
@ -139,6 +140,7 @@ pub unsafe fn from_int_unchecked(value: Simd<T, LANES>) -> Self {
/// # Panics
/// Panics if any lane is not 0 or -1.
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn from_int(value: Simd<T, LANES>) -> Self {
assert!(T::valid(value), "all values must be either 0 or -1",);
unsafe { Self::from_int_unchecked(value) }
@ -147,6 +149,7 @@ pub fn from_int(value: Simd<T, LANES>) -> Self {
/// Converts the mask to a vector of integers, where 0 represents `false` and -1
/// represents `true`.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn to_int(self) -> Simd<T, LANES> {
self.0.to_int()
}
@ -156,6 +159,7 @@ pub fn to_int(self) -> Simd<T, LANES> {
/// # Safety
/// `lane` must be less than `LANES`.
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub unsafe fn test_unchecked(&self, lane: usize) -> bool {
unsafe { self.0.test_unchecked(lane) }
}
@ -165,6 +169,7 @@ pub unsafe fn test_unchecked(&self, lane: usize) -> bool {
/// # Panics
/// Panics if `lane` is greater than or equal to the number of lanes in the vector.
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn test(&self, lane: usize) -> bool {
assert!(lane < LANES, "lane index out of range");
unsafe { self.test_unchecked(lane) }
@ -195,24 +200,30 @@ pub fn set(&mut self, lane: usize, value: bool) {
/// Convert this mask to a bitmask, with one bit set per lane.
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new array and does not mutate the original value"]
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
self.0.to_bitmask()
}
/// Convert a bitmask to a mask.
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn from_bitmask(bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
Self(mask_impl::Mask::from_bitmask(bitmask))
}
/// Returns true if any lane is set, or false otherwise.
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn any(self) -> bool {
self.0.any()
}
/// Returns true if all lanes are set, or false otherwise.
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn all(self) -> bool {
self.0.all()
}
@ -245,6 +256,7 @@ impl<T, const LANES: usize> Default for Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a defaulted mask with all lanes set to false (0)"]
fn default() -> Self {
Self::splat(false)
}
@ -256,6 +268,7 @@ impl<T, const LANES: usize> PartialEq for Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
@ -267,6 +280,7 @@ impl<T, const LANES: usize> PartialOrd for Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new Ordering and does not mutate the original value"]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
@ -291,6 +305,7 @@ impl<T, const LANES: usize> core::ops::BitAnd for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
@ -303,6 +318,7 @@ impl<T, const LANES: usize> core::ops::BitAnd<bool> for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitand(self, rhs: bool) -> Self {
self & Self::splat(rhs)
}
@ -315,6 +331,7 @@ impl<T, const LANES: usize> core::ops::BitAnd<Mask<T, LANES>> for bool
{
type Output = Mask<T, LANES>;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitand(self, rhs: Mask<T, LANES>) -> Mask<T, LANES> {
Mask::splat(self) & rhs
}
@ -327,6 +344,7 @@ impl<T, const LANES: usize> core::ops::BitOr for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
@ -339,6 +357,7 @@ impl<T, const LANES: usize> core::ops::BitOr<bool> for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitor(self, rhs: bool) -> Self {
self | Self::splat(rhs)
}
@ -351,6 +370,7 @@ impl<T, const LANES: usize> core::ops::BitOr<Mask<T, LANES>> for bool
{
type Output = Mask<T, LANES>;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitor(self, rhs: Mask<T, LANES>) -> Mask<T, LANES> {
Mask::splat(self) | rhs
}
@ -363,6 +383,7 @@ impl<T, const LANES: usize> core::ops::BitXor for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitxor(self, rhs: Self) -> Self::Output {
Self(self.0 ^ rhs.0)
}
@ -375,6 +396,7 @@ impl<T, const LANES: usize> core::ops::BitXor<bool> for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitxor(self, rhs: bool) -> Self::Output {
self ^ Self::splat(rhs)
}
@ -387,6 +409,7 @@ impl<T, const LANES: usize> core::ops::BitXor<Mask<T, LANES>> for bool
{
type Output = Mask<T, LANES>;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitxor(self, rhs: Mask<T, LANES>) -> Self::Output {
Mask::splat(self) ^ rhs
}
@ -399,6 +422,7 @@ impl<T, const LANES: usize> core::ops::Not for Mask<T, LANES>
{
type Output = Mask<T, LANES>;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn not(self) -> Self::Output {
Self(!self.0)
}

View file

@ -1,3 +1,4 @@
#![allow(unused_imports)]
use super::MaskElement;
use crate::simd::intrinsics;
use crate::simd::{LaneCount, Simd, SupportedLaneCount};
@ -73,6 +74,7 @@ impl<T, const LANES: usize> Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn splat(value: bool) -> Self {
let mut mask = <LaneCount<LANES> as SupportedLaneCount>::BitMask::default();
if value {
@ -87,6 +89,7 @@ pub fn splat(value: bool) -> Self {
}
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub unsafe fn test_unchecked(&self, lane: usize) -> bool {
(self.0.as_ref()[lane / 8] >> (lane % 8)) & 0x1 > 0
}
@ -99,30 +102,26 @@ pub unsafe fn set_unchecked(&mut self, lane: usize, value: bool) {
}
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn to_int(self) -> Simd<T, LANES> {
unsafe {
let mask: <LaneCount<LANES> as SupportedLaneCount>::IntBitMask =
core::mem::transmute_copy(&self);
intrinsics::simd_select_bitmask(mask, Simd::splat(T::TRUE), Simd::splat(T::FALSE))
crate::intrinsics::simd_select_bitmask(
self.0,
Simd::splat(T::TRUE),
Simd::splat(T::FALSE),
)
}
}
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub unsafe fn from_int_unchecked(value: Simd<T, LANES>) -> Self {
// TODO remove the transmute when rustc is more flexible
assert_eq!(
core::mem::size_of::<<LaneCount::<LANES> as SupportedLaneCount>::BitMask>(),
core::mem::size_of::<<LaneCount::<LANES> as SupportedLaneCount>::IntBitMask>(),
);
unsafe {
let mask: <LaneCount<LANES> as SupportedLaneCount>::IntBitMask =
intrinsics::simd_bitmask(value);
Self(core::mem::transmute_copy(&mask), PhantomData)
}
unsafe { Self(crate::intrinsics::simd_bitmask(value), PhantomData) }
}
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new array and does not mutate the original value"]
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
// Safety: these are the same type and we are laundering the generic
unsafe { core::mem::transmute_copy(&self.0) }
@ -130,12 +129,14 @@ pub unsafe fn from_int_unchecked(value: Simd<T, LANES>) -> Self {
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn from_bitmask(bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
// Safety: these are the same type and we are laundering the generic
Self(unsafe { core::mem::transmute_copy(&bitmask) }, PhantomData)
}
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn convert<U>(self) -> Mask<U, LANES>
where
U: MaskElement,
@ -144,11 +145,13 @@ pub fn convert<U>(self) -> Mask<U, LANES>
}
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn any(self) -> bool {
self != Self::splat(false)
}
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn all(self) -> bool {
self == Self::splat(true)
}
@ -162,6 +165,7 @@ impl<T, const LANES: usize> core::ops::BitAnd for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitand(mut self, rhs: Self) -> Self {
for (l, r) in self.0.as_mut().iter_mut().zip(rhs.0.as_ref().iter()) {
*l &= r;
@ -178,6 +182,7 @@ impl<T, const LANES: usize> core::ops::BitOr for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitor(mut self, rhs: Self) -> Self {
for (l, r) in self.0.as_mut().iter_mut().zip(rhs.0.as_ref().iter()) {
*l |= r;
@ -193,6 +198,7 @@ impl<T, const LANES: usize> core::ops::BitXor for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitxor(mut self, rhs: Self) -> Self::Output {
for (l, r) in self.0.as_mut().iter_mut().zip(rhs.0.as_ref().iter()) {
*l ^= r;
@ -208,6 +214,7 @@ impl<T, const LANES: usize> core::ops::Not for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn not(mut self) -> Self::Output {
for x in self.0.as_mut() {
*x = !*x;

View file

@ -23,6 +23,7 @@ impl<T, const LANES: usize> Clone for Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn clone(&self) -> Self {
*self
}
@ -70,11 +71,14 @@ impl<T, const LANES: usize> Mask<T, LANES>
T: MaskElement,
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn splat(value: bool) -> Self {
Self(Simd::splat(if value { T::TRUE } else { T::FALSE }))
}
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub unsafe fn test_unchecked(&self, lane: usize) -> bool {
T::eq(self.0[lane], T::TRUE)
}
@ -85,16 +89,19 @@ pub unsafe fn set_unchecked(&mut self, lane: usize, value: bool) {
}
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn to_int(self) -> Simd<T, LANES> {
self.0
}
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub unsafe fn from_int_unchecked(value: Simd<T, LANES>) -> Self {
Self(value)
}
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn convert<U>(self) -> Mask<U, LANES>
where
U: MaskElement,
@ -104,17 +111,11 @@ pub fn convert<U>(self) -> Mask<U, LANES>
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new array and does not mutate the original value"]
pub fn to_bitmask(self) -> [u8; LaneCount::<LANES>::BITMASK_LEN] {
unsafe {
// TODO remove the transmute when rustc can use arrays of u8 as bitmasks
assert_eq!(
core::mem::size_of::<<LaneCount::<LANES> as SupportedLaneCount>::IntBitMask>(),
LaneCount::<LANES>::BITMASK_LEN,
);
let bitmask: <LaneCount<LANES> as SupportedLaneCount>::IntBitMask =
intrinsics::simd_bitmask(self.0);
let mut bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN] =
core::mem::transmute_copy(&bitmask);
crate::intrinsics::simd_bitmask(self.0);
// There is a bug where LLVM appears to implement this operation with the wrong
// bit order.
@ -131,6 +132,7 @@ pub fn convert<U>(self) -> Mask<U, LANES>
#[cfg(feature = "generic_const_exprs")]
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn from_bitmask(mut bitmask: [u8; LaneCount::<LANES>::BITMASK_LEN]) -> Self {
unsafe {
// There is a bug where LLVM appears to implement this operation with the wrong
@ -142,15 +144,7 @@ pub fn convert<U>(self) -> Mask<U, LANES>
}
}
// TODO remove the transmute when rustc can use arrays of u8 as bitmasks
assert_eq!(
core::mem::size_of::<<LaneCount::<LANES> as SupportedLaneCount>::IntBitMask>(),
LaneCount::<LANES>::BITMASK_LEN,
);
let bitmask: <LaneCount<LANES> as SupportedLaneCount>::IntBitMask =
core::mem::transmute_copy(&bitmask);
Self::from_int_unchecked(intrinsics::simd_select_bitmask(
Self::from_int_unchecked(crate::intrinsics::simd_select_bitmask(
bitmask,
Self::splat(true).to_int(),
Self::splat(false).to_int(),
@ -159,11 +153,13 @@ pub fn convert<U>(self) -> Mask<U, LANES>
}
#[inline]
#[must_use = "method returns a new bool and does not mutate the original value"]
pub fn any(self) -> bool {
unsafe { intrinsics::simd_reduce_any(self.to_int()) }
}
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn all(self) -> bool {
unsafe { intrinsics::simd_reduce_all(self.to_int()) }
}
@ -186,6 +182,7 @@ impl<T, const LANES: usize> core::ops::BitAnd for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitand(self, rhs: Self) -> Self {
unsafe { Self(intrinsics::simd_and(self.0, rhs.0)) }
}
@ -198,6 +195,7 @@ impl<T, const LANES: usize> core::ops::BitOr for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitor(self, rhs: Self) -> Self {
unsafe { Self(intrinsics::simd_or(self.0, rhs.0)) }
}
@ -210,6 +208,7 @@ impl<T, const LANES: usize> core::ops::BitXor for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn bitxor(self, rhs: Self) -> Self {
unsafe { Self(intrinsics::simd_xor(self.0, rhs.0)) }
}
@ -222,6 +221,7 @@ impl<T, const LANES: usize> core::ops::Not for Mask<T, LANES>
{
type Output = Self;
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
fn not(self) -> Self::Output {
Self::splat(true) ^ self
}

View file

@ -17,7 +17,7 @@ macro_rules! impl_uint_arith {
/// let max = Simd::splat(MAX);
/// let unsat = x + max;
/// let sat = x.saturating_add(max);
/// assert_eq!(x - 1, unsat);
/// assert_eq!(unsat, Simd::from_array([1, 0, MAX, MAX - 1]));
/// assert_eq!(sat, max);
/// ```
#[inline]
@ -37,7 +37,7 @@ pub fn saturating_add(self, second: Self) -> Self {
/// let max = Simd::splat(MAX);
/// let unsat = x - max;
/// let sat = x.saturating_sub(max);
/// assert_eq!(unsat, x + 1);
/// assert_eq!(unsat, Simd::from_array([3, 2, 1, 0]));
/// assert_eq!(sat, Simd::splat(0));
#[inline]
pub fn saturating_sub(self, second: Self) -> Self {
@ -105,7 +105,7 @@ pub fn saturating_sub(self, second: Self) -> Self {
#[inline]
pub fn abs(self) -> Self {
const SHR: $ty = <$ty>::BITS as $ty - 1;
let m = self >> SHR;
let m = self >> Simd::splat(SHR);
(self^m) - m
}
@ -128,7 +128,7 @@ pub fn abs(self) -> Self {
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;
let m = self >> Simd::splat(SHR);
(self^m).saturating_sub(m)
}

View file

@ -1,5 +1,13 @@
use crate::simd::intrinsics;
use crate::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount};
use core::ops::{Add, Mul};
use core::ops::{BitAnd, BitOr, BitXor};
use core::ops::{Div, Rem, Sub};
use core::ops::{Shl, Shr};
mod assign;
mod deref;
mod unary;
impl<I, T, const LANES: usize> core::ops::Index<I> for Simd<T, LANES>
where
@ -57,166 +65,44 @@ impl<const $lanes: usize> core::ops::$trait<$rhs> for $type
$(#[$attrs])*
fn $fn($self_tok, $rhs_arg: $rhs_arg_ty) -> Self::Output $body
}
impl<const $lanes: usize> core::ops::$trait<&'_ $rhs> for $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
type Output = <$type as core::ops::$trait<$rhs>>::Output;
$(#[$attrs])*
fn $fn($self_tok, $rhs_arg: &$rhs) -> Self::Output {
core::ops::$trait::$fn($self_tok, *$rhs_arg)
}
}
impl<const $lanes: usize> core::ops::$trait<$rhs> for &'_ $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
type Output = <$type as core::ops::$trait<$rhs>>::Output;
$(#[$attrs])*
fn $fn($self_tok, $rhs_arg: $rhs) -> Self::Output {
core::ops::$trait::$fn(*$self_tok, $rhs_arg)
}
}
impl<const $lanes: usize> core::ops::$trait<&'_ $rhs> for &'_ $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
type Output = <$type as core::ops::$trait<$rhs>>::Output;
$(#[$attrs])*
fn $fn($self_tok, $rhs_arg: &$rhs) -> Self::Output {
core::ops::$trait::$fn(*$self_tok, *$rhs_arg)
}
}
};
// binary assignment op
{
impl<const $lanes:ident: usize> core::ops::$trait:ident<$rhs:ty> for $type:ty
where
LaneCount<$lanes2:ident>: SupportedLaneCount,
{
$(#[$attrs:meta])*
fn $fn:ident(&mut $self_tok:ident, $rhs_arg:ident: $rhs_arg_ty:ty) $body:tt
}
} => {
impl<const $lanes: usize> core::ops::$trait<$rhs> for $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
$(#[$attrs])*
fn $fn(&mut $self_tok, $rhs_arg: $rhs_arg_ty) $body
}
impl<const $lanes: usize> core::ops::$trait<&'_ $rhs> for $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
$(#[$attrs])*
fn $fn(&mut $self_tok, $rhs_arg: &$rhs_arg_ty) {
core::ops::$trait::$fn($self_tok, *$rhs_arg)
}
}
};
// unary op
{
impl<const $lanes:ident: usize> core::ops::$trait:ident for $type:ty
where
LaneCount<$lanes2:ident>: SupportedLaneCount,
{
type Output = $output:ty;
fn $fn:ident($self_tok:ident) -> Self::Output $body:tt
}
} => {
impl<const $lanes: usize> core::ops::$trait for $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
type Output = $output;
fn $fn($self_tok) -> Self::Output $body
}
impl<const $lanes: usize> core::ops::$trait for &'_ $type
where
LaneCount<$lanes2>: SupportedLaneCount,
{
type Output = <$type as core::ops::$trait>::Output;
fn $fn($self_tok) -> Self::Output {
core::ops::$trait::$fn(*$self_tok)
}
}
}
}
/// Automatically implements operators over vectors and scalars for a particular vector.
macro_rules! impl_op {
{ impl Add for $scalar:ty } => {
impl_op! { @binary $scalar, Add::add, AddAssign::add_assign, simd_add }
impl_op! { @binary $scalar, Add::add, simd_add }
};
{ impl Sub for $scalar:ty } => {
impl_op! { @binary $scalar, Sub::sub, SubAssign::sub_assign, simd_sub }
impl_op! { @binary $scalar, Sub::sub, simd_sub }
};
{ impl Mul for $scalar:ty } => {
impl_op! { @binary $scalar, Mul::mul, MulAssign::mul_assign, simd_mul }
impl_op! { @binary $scalar, Mul::mul, simd_mul }
};
{ impl Div for $scalar:ty } => {
impl_op! { @binary $scalar, Div::div, DivAssign::div_assign, simd_div }
impl_op! { @binary $scalar, Div::div, simd_div }
};
{ impl Rem for $scalar:ty } => {
impl_op! { @binary $scalar, Rem::rem, RemAssign::rem_assign, simd_rem }
impl_op! { @binary $scalar, Rem::rem, simd_rem }
};
{ impl Shl for $scalar:ty } => {
impl_op! { @binary $scalar, Shl::shl, ShlAssign::shl_assign, simd_shl }
impl_op! { @binary $scalar, Shl::shl, simd_shl }
};
{ impl Shr for $scalar:ty } => {
impl_op! { @binary $scalar, Shr::shr, ShrAssign::shr_assign, simd_shr }
impl_op! { @binary $scalar, Shr::shr, simd_shr }
};
{ impl BitAnd for $scalar:ty } => {
impl_op! { @binary $scalar, BitAnd::bitand, BitAndAssign::bitand_assign, simd_and }
impl_op! { @binary $scalar, BitAnd::bitand, simd_and }
};
{ impl BitOr for $scalar:ty } => {
impl_op! { @binary $scalar, BitOr::bitor, BitOrAssign::bitor_assign, simd_or }
impl_op! { @binary $scalar, BitOr::bitor, simd_or }
};
{ impl BitXor for $scalar:ty } => {
impl_op! { @binary $scalar, BitXor::bitxor, BitXorAssign::bitxor_assign, simd_xor }
};
{ impl Not for $scalar:ty } => {
impl_ref_ops! {
impl<const LANES: usize> core::ops::Not for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
fn not(self) -> Self::Output {
self ^ Self::splat(!<$scalar>::default())
}
}
}
};
{ impl Neg for $scalar:ty } => {
impl_ref_ops! {
impl<const LANES: usize> core::ops::Neg for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
fn neg(self) -> Self::Output {
unsafe { intrinsics::simd_neg(self) }
}
}
}
impl_op! { @binary $scalar, BitXor::bitxor, simd_xor }
};
// generic binary op with assignment when output is `Self`
{ @binary $scalar:ty, $trait:ident :: $trait_fn:ident, $assign_trait:ident :: $assign_trait_fn:ident, $intrinsic:ident } => {
{ @binary $scalar:ty, $trait:ident :: $trait_fn:ident, $intrinsic:ident } => {
impl_ref_ops! {
impl<const LANES: usize> core::ops::$trait<Self> for Simd<$scalar, LANES>
where
@ -232,60 +118,6 @@ fn $trait_fn(self, rhs: Self) -> Self::Output {
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::$trait<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
fn $trait_fn(self, rhs: $scalar) -> Self::Output {
core::ops::$trait::$trait_fn(self, Self::splat(rhs))
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::$trait<Simd<$scalar, LANES>> for $scalar
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Simd<$scalar, LANES>;
#[inline]
fn $trait_fn(self, rhs: Simd<$scalar, LANES>) -> Self::Output {
core::ops::$trait::$trait_fn(Simd::splat(self), rhs)
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::$assign_trait<Self> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn $assign_trait_fn(&mut self, rhs: Self) {
unsafe {
*self = intrinsics::$intrinsic(*self, rhs);
}
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::$assign_trait<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn $assign_trait_fn(&mut self, rhs: $scalar) {
core::ops::$assign_trait::$assign_trait_fn(self, Self::splat(rhs));
}
}
}
};
}
@ -298,7 +130,6 @@ macro_rules! impl_float_ops {
impl_op! { impl Mul for $scalar }
impl_op! { impl Div for $scalar }
impl_op! { impl Rem for $scalar }
impl_op! { impl Neg for $scalar }
)*
};
}
@ -313,7 +144,6 @@ macro_rules! impl_unsigned_int_ops {
impl_op! { impl BitAnd for $scalar }
impl_op! { impl BitOr for $scalar }
impl_op! { impl BitXor for $scalar }
impl_op! { impl Not for $scalar }
// Integers panic on divide by 0
impl_ref_ops! {
@ -344,67 +174,6 @@ fn div(self, rhs: Self) -> Self::Output {
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Div<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
fn div(self, rhs: $scalar) -> Self::Output {
if rhs == 0 {
panic!("attempt to divide by zero");
}
if <$scalar>::MIN != 0 &&
self.as_array().iter().any(|x| *x == <$scalar>::MIN) &&
rhs == -1 as _ {
panic!("attempt to divide with overflow");
}
let rhs = Self::splat(rhs);
unsafe { intrinsics::simd_div(self, rhs) }
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Div<Simd<$scalar, LANES>> for $scalar
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Simd<$scalar, LANES>;
#[inline]
fn div(self, rhs: Simd<$scalar, LANES>) -> Self::Output {
Simd::splat(self) / rhs
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::DivAssign<Self> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn div_assign(&mut self, rhs: Self) {
*self = *self / rhs;
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::DivAssign<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn div_assign(&mut self, rhs: $scalar) {
*self = *self / rhs;
}
}
}
// remainder panics on zero divisor
impl_ref_ops! {
impl<const LANES: usize> core::ops::Rem<Self> for Simd<$scalar, LANES>
@ -434,67 +203,6 @@ fn rem(self, rhs: Self) -> Self::Output {
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Rem<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
fn rem(self, rhs: $scalar) -> Self::Output {
if rhs == 0 {
panic!("attempt to calculate the remainder with a divisor of zero");
}
if <$scalar>::MIN != 0 &&
self.as_array().iter().any(|x| *x == <$scalar>::MIN) &&
rhs == -1 as _ {
panic!("attempt to calculate the remainder with overflow");
}
let rhs = Self::splat(rhs);
unsafe { intrinsics::simd_rem(self, rhs) }
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Rem<Simd<$scalar, LANES>> for $scalar
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Simd<$scalar, LANES>;
#[inline]
fn rem(self, rhs: Simd<$scalar, LANES>) -> Self::Output {
Simd::splat(self) % rhs
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::RemAssign<Self> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn rem_assign(&mut self, rhs: Self) {
*self = *self % rhs;
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::RemAssign<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn rem_assign(&mut self, rhs: $scalar) {
*self = *self % rhs;
}
}
}
// shifts panic on overflow
impl_ref_ops! {
impl<const LANES: usize> core::ops::Shl<Self> for Simd<$scalar, LANES>
@ -518,49 +226,6 @@ fn shl(self, rhs: Self) -> Self::Output {
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Shl<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
fn shl(self, rhs: $scalar) -> Self::Output {
if invalid_shift_rhs(rhs) {
panic!("attempt to shift left with overflow");
}
let rhs = Self::splat(rhs);
unsafe { intrinsics::simd_shl(self, rhs) }
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::ShlAssign<Self> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn shl_assign(&mut self, rhs: Self) {
*self = *self << rhs;
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::ShlAssign<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn shl_assign(&mut self, rhs: $scalar) {
*self = *self << rhs;
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Shr<Self> for Simd<$scalar, LANES>
where
@ -582,49 +247,6 @@ fn shr(self, rhs: Self) -> Self::Output {
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::Shr<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: 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<const LANES: usize> core::ops::ShrAssign<Self> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn shr_assign(&mut self, rhs: Self) {
*self = *self >> rhs;
}
}
}
impl_ref_ops! {
impl<const LANES: usize> core::ops::ShrAssign<$scalar> for Simd<$scalar, LANES>
where
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn shr_assign(&mut self, rhs: $scalar) {
*self = *self >> rhs;
}
}
}
)*
};
}
@ -633,9 +255,6 @@ fn shr_assign(&mut self, rhs: $scalar) {
macro_rules! impl_signed_int_ops {
{ $($scalar:ty),* } => {
impl_unsigned_int_ops! { $($scalar),* }
$( // scalar
impl_op! { impl Neg for $scalar }
)*
};
}

View file

@ -0,0 +1,124 @@
//! Assignment operators
use super::*;
use core::ops::{AddAssign, MulAssign}; // commutative binary op-assignment
use core::ops::{BitAndAssign, BitOrAssign, BitXorAssign}; // commutative bit binary op-assignment
use core::ops::{DivAssign, RemAssign, SubAssign}; // non-commutative binary op-assignment
use core::ops::{ShlAssign, ShrAssign}; // non-commutative bit binary op-assignment
// Arithmetic
macro_rules! assign_ops {
($(impl<T, U, const LANES: usize> $assignTrait:ident<U> for Simd<T, LANES>
where
Self: $trait:ident,
{
fn $assign_call:ident(rhs: U) {
$call:ident
}
})*) => {
$(impl<T, U, const LANES: usize> $assignTrait<U> for Simd<T, LANES>
where
Self: $trait<U, Output = Self>,
T: SimdElement,
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
fn $assign_call(&mut self, rhs: U) {
*self = self.$call(rhs);
}
})*
}
}
assign_ops! {
// Arithmetic
impl<T, U, const LANES: usize> AddAssign<U> for Simd<T, LANES>
where
Self: Add,
{
fn add_assign(rhs: U) {
add
}
}
impl<T, U, const LANES: usize> MulAssign<U> for Simd<T, LANES>
where
Self: Mul,
{
fn mul_assign(rhs: U) {
mul
}
}
impl<T, U, const LANES: usize> SubAssign<U> for Simd<T, LANES>
where
Self: Sub,
{
fn sub_assign(rhs: U) {
sub
}
}
impl<T, U, const LANES: usize> DivAssign<U> for Simd<T, LANES>
where
Self: Div,
{
fn div_assign(rhs: U) {
div
}
}
impl<T, U, const LANES: usize> RemAssign<U> for Simd<T, LANES>
where
Self: Rem,
{
fn rem_assign(rhs: U) {
rem
}
}
// Bitops
impl<T, U, const LANES: usize> BitAndAssign<U> for Simd<T, LANES>
where
Self: BitAnd,
{
fn bitand_assign(rhs: U) {
bitand
}
}
impl<T, U, const LANES: usize> BitOrAssign<U> for Simd<T, LANES>
where
Self: BitOr,
{
fn bitor_assign(rhs: U) {
bitor
}
}
impl<T, U, const LANES: usize> BitXorAssign<U> for Simd<T, LANES>
where
Self: BitXor,
{
fn bitxor_assign(rhs: U) {
bitxor
}
}
impl<T, U, const LANES: usize> ShlAssign<U> for Simd<T, LANES>
where
Self: Shl,
{
fn shl_assign(rhs: U) {
shl
}
}
impl<T, U, const LANES: usize> ShrAssign<U> for Simd<T, LANES>
where
Self: Shr,
{
fn shr_assign(rhs: U) {
shr
}
}
}

View file

@ -0,0 +1,124 @@
//! This module hacks in "implicit deref" for Simd's operators.
//! Ideally, Rust would take care of this itself,
//! and method calls usually handle the LHS implicitly.
//! But this is not the case with arithmetic ops.
use super::*;
macro_rules! deref_lhs {
(impl<T, const LANES: usize> $trait:ident for $simd:ty {
fn $call:ident
}) => {
impl<T, const LANES: usize> $trait<$simd> for &$simd
where
T: SimdElement,
$simd: $trait<$simd, Output = $simd>,
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Simd<T, LANES>;
#[inline]
#[must_use = "operator returns a new vector without mutating the inputs"]
fn $call(self, rhs: $simd) -> Self::Output {
(*self).$call(rhs)
}
}
};
}
macro_rules! deref_rhs {
(impl<T, const LANES: usize> $trait:ident for $simd:ty {
fn $call:ident
}) => {
impl<T, const LANES: usize> $trait<&$simd> for $simd
where
T: SimdElement,
$simd: $trait<$simd, Output = $simd>,
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Simd<T, LANES>;
#[inline]
#[must_use = "operator returns a new vector without mutating the inputs"]
fn $call(self, rhs: &$simd) -> Self::Output {
self.$call(*rhs)
}
}
};
}
macro_rules! deref_ops {
($(impl<T, const LANES: usize> $trait:ident for $simd:ty {
fn $call:ident
})*) => {
$(
deref_rhs! {
impl<T, const LANES: usize> $trait for $simd {
fn $call
}
}
deref_lhs! {
impl<T, const LANES: usize> $trait for $simd {
fn $call
}
}
impl<'lhs, 'rhs, T, const LANES: usize> $trait<&'rhs $simd> for &'lhs $simd
where
T: SimdElement,
$simd: $trait<$simd, Output = $simd>,
LaneCount<LANES>: SupportedLaneCount,
{
type Output = $simd;
#[inline]
#[must_use = "operator returns a new vector without mutating the inputs"]
fn $call(self, rhs: &$simd) -> Self::Output {
(*self).$call(*rhs)
}
}
)*
}
}
deref_ops! {
// Arithmetic
impl<T, const LANES: usize> Add for Simd<T, LANES> {
fn add
}
impl<T, const LANES: usize> Mul for Simd<T, LANES> {
fn mul
}
impl<T, const LANES: usize> Sub for Simd<T, LANES> {
fn sub
}
impl<T, const LANES: usize> Div for Simd<T, LANES> {
fn div
}
impl<T, const LANES: usize> Rem for Simd<T, LANES> {
fn rem
}
// Bitops
impl<T, const LANES: usize> BitAnd for Simd<T, LANES> {
fn bitand
}
impl<T, const LANES: usize> BitOr for Simd<T, LANES> {
fn bitor
}
impl<T, const LANES: usize> BitXor for Simd<T, LANES> {
fn bitxor
}
impl<T, const LANES: usize> Shl for Simd<T, LANES> {
fn shl
}
impl<T, const LANES: usize> Shr for Simd<T, LANES> {
fn shr
}
}

View file

@ -0,0 +1,77 @@
use crate::simd::intrinsics;
use crate::simd::{LaneCount, Simd, SimdElement, SupportedLaneCount};
use core::ops::{Neg, Not}; // unary ops
macro_rules! neg {
($(impl<const LANES: usize> Neg for Simd<$scalar:ty, LANES>)*) => {
$(impl<const LANES: usize> Neg for Simd<$scalar, LANES>
where
$scalar: SimdElement,
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
#[must_use = "operator returns a new vector without mutating the input"]
fn neg(self) -> Self::Output {
unsafe { intrinsics::simd_neg(self) }
}
})*
}
}
neg! {
impl<const LANES: usize> Neg for Simd<f32, LANES>
impl<const LANES: usize> Neg for Simd<f64, LANES>
impl<const LANES: usize> Neg for Simd<i8, LANES>
impl<const LANES: usize> Neg for Simd<i16, LANES>
impl<const LANES: usize> Neg for Simd<i32, LANES>
impl<const LANES: usize> Neg for Simd<i64, LANES>
impl<const LANES: usize> Neg for Simd<isize, LANES>
}
macro_rules! not {
($(impl<const LANES: usize> Not for Simd<$scalar:ty, LANES>)*) => {
$(impl<const LANES: usize> Not for Simd<$scalar, LANES>
where
$scalar: SimdElement,
LaneCount<LANES>: SupportedLaneCount,
{
type Output = Self;
#[inline]
#[must_use = "operator returns a new vector without mutating the input"]
fn not(self) -> Self::Output {
self ^ (Simd::splat(!(0 as $scalar)))
}
})*
}
}
not! {
impl<const LANES: usize> Not for Simd<i8, LANES>
impl<const LANES: usize> Not for Simd<i16, LANES>
impl<const LANES: usize> Not for Simd<i32, LANES>
impl<const LANES: usize> Not for Simd<i64, LANES>
impl<const LANES: usize> Not for Simd<isize, LANES>
impl<const LANES: usize> Not for Simd<u8, LANES>
impl<const LANES: usize> Not for Simd<u16, LANES>
impl<const LANES: usize> Not for Simd<u32, LANES>
impl<const LANES: usize> Not for Simd<u64, LANES>
impl<const LANES: usize> Not for Simd<usize, LANES>
}

View file

@ -2,7 +2,8 @@
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::{LaneCount, Simd, SimdElement, SupportedLaneCount};
use core::ops::{BitAnd, BitOr, BitXor};
macro_rules! impl_integer_reductions {
{ $scalar:ty } => {
@ -22,27 +23,6 @@ pub 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 {
@ -121,3 +101,45 @@ pub fn horizontal_min(self) -> $scalar {
impl_float_reductions! { f32 }
impl_float_reductions! { f64 }
impl<T, const LANES: usize> Simd<T, LANES>
where
Self: BitAnd<Self, Output = Self>,
T: SimdElement + BitAnd<T, Output = T>,
LaneCount<LANES>: 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) }
}
}
impl<T, const LANES: usize> Simd<T, LANES>
where
Self: BitOr<Self, Output = Self>,
T: SimdElement + BitOr<T, Output = T>,
LaneCount<LANES>: SupportedLaneCount,
{
/// 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) }
}
}
impl<T, const LANES: usize> Simd<T, LANES>
where
Self: BitXor<Self, Output = Self>,
T: SimdElement + BitXor<T, Output = T>,
LaneCount<LANES>: SupportedLaneCount,
{
/// 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) }
}
}

View file

@ -17,6 +17,7 @@ impl<T, const LANES: usize> Sealed<Mask<T::Mask, LANES>> for Simd<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
fn select(mask: Mask<T::Mask, LANES>, true_values: Self, false_values: Self) -> Self {
unsafe { intrinsics::simd_select(mask.to_int(), true_values, false_values) }
}
@ -35,6 +36,7 @@ impl<T, const LANES: usize> Sealed<Self> for Mask<T, LANES>
LaneCount<LANES>: SupportedLaneCount,
{
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
fn select(mask: Self, true_values: Self, false_values: Self) -> Self {
mask & true_values | !mask & false_values
}
@ -80,6 +82,7 @@ impl<T, const LANES: usize> Mask<T, LANES>
/// assert_eq!(c.to_array(), [true, false, true, false]);
/// ```
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn select<S: Select<Self>>(self, true_values: S, false_values: S) -> S {
S::select(self, true_values, false_values)
}

View file

@ -87,6 +87,8 @@ pub trait Swizzle<const INPUT_LANES: usize, const OUTPUT_LANES: usize> {
/// Create a new vector from the lanes of `vector`.
///
/// Lane `i` of the output is `vector[Self::INDEX[i]]`.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
fn swizzle<T>(vector: Simd<T, INPUT_LANES>) -> Simd<T, OUTPUT_LANES>
where
T: SimdElement,
@ -106,6 +108,8 @@ pub trait Swizzle2<const INPUT_LANES: usize, const OUTPUT_LANES: usize> {
///
/// Lane `i` is `first[j]` when `Self::INDEX[i]` is `First(j)`, or `second[j]` when it is
/// `Second(j)`.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
fn swizzle2<T>(
first: Simd<T, INPUT_LANES>,
second: Simd<T, INPUT_LANES>,
@ -182,6 +186,7 @@ impl<T, const LANES: usize> Simd<T, LANES>
{
/// Reverse the order of the lanes in the vector.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn reverse(self) -> Self {
const fn reverse_index<const LANES: usize>() -> [usize; LANES] {
let mut index = [0; LANES];
@ -206,6 +211,7 @@ impl<const LANES: usize> Swizzle<LANES, LANES> for Reverse {
/// while the last `LANES - OFFSET` elements move to the front. After calling `rotate_lanes_left`,
/// the element previously in lane `OFFSET` will become the first element in the slice.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn rotate_lanes_left<const OFFSET: usize>(self) -> Self {
const fn rotate_index<const OFFSET: usize, const LANES: usize>() -> [usize; LANES] {
let offset = OFFSET % LANES;
@ -231,6 +237,7 @@ impl<const OFFSET: usize, const LANES: usize> Swizzle<LANES, LANES> for Rotate<O
/// the end while the last `OFFSET` elements move to the front. After calling `rotate_lanes_right`,
/// the element previously at index `LANES - OFFSET` will become the first element in the slice.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn rotate_lanes_right<const OFFSET: usize>(self) -> Self {
const fn rotate_index<const OFFSET: usize, const LANES: usize>() -> [usize; LANES] {
let offset = LANES - OFFSET % LANES;
@ -273,6 +280,7 @@ impl<const OFFSET: usize, const LANES: usize> Swizzle<LANES, LANES> for Rotate<O
/// assert_eq!(y.to_array(), [2, 6, 3, 7]);
/// ```
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn interleave(self, other: Self) -> (Self, Self) {
const fn lo<const LANES: usize>() -> [Which; LANES] {
let mut idx = [Which::First(0); LANES];
@ -336,6 +344,7 @@ impl<const LANES: usize> Swizzle2<LANES, LANES> for Hi {
/// assert_eq!(y.to_array(), [4, 5, 6, 7]);
/// ```
#[inline]
#[must_use = "method returns a new vector and does not mutate the original inputs"]
pub fn deinterleave(self, other: Self) -> (Self, Self) {
const fn even<const LANES: usize>() -> [Which; LANES] {
let mut idx = [Which::First(0); LANES];

View file

@ -15,6 +15,7 @@ impl<const LANES: usize> Simd<$type, LANES>
/// Raw transmutation to an unsigned integer vector type with the
/// same size and number of lanes.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn to_bits(self) -> Simd<$bits_ty, LANES> {
assert_eq!(core::mem::size_of::<Self>(), core::mem::size_of::<Simd<$bits_ty, LANES>>());
unsafe { core::mem::transmute_copy(&self) }
@ -23,6 +24,7 @@ pub fn to_bits(self) -> Simd<$bits_ty, LANES> {
/// Raw transmutation from an unsigned integer vector type with the
/// same size and number of lanes.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn from_bits(bits: Simd<$bits_ty, LANES>) -> Self {
assert_eq!(core::mem::size_of::<Self>(), core::mem::size_of::<Simd<$bits_ty, LANES>>());
unsafe { core::mem::transmute_copy(&bits) }
@ -31,6 +33,7 @@ pub fn from_bits(bits: Simd<$bits_ty, LANES>) -> Self {
/// 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) }
}
@ -44,6 +47,7 @@ pub fn abs(self) -> Self {
/// hardware in mind.
#[cfg(feature = "std")]
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn mul_add(self, a: Self, b: Self) -> Self {
unsafe { intrinsics::simd_fma(self, a, b) }
}
@ -51,6 +55,7 @@ pub fn mul_add(self, a: Self, b: Self) -> Self {
/// Produces a vector where every lane has the square root value
/// of the equivalently-indexed lane in `self`
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
#[cfg(feature = "std")]
pub fn sqrt(self) -> Self {
unsafe { intrinsics::simd_fsqrt(self) }
@ -58,12 +63,14 @@ pub fn sqrt(self) -> Self {
/// Takes the reciprocal (inverse) of each lane, `1/x`.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn recip(self) -> Self {
Self::splat(1.0) / self
}
/// Converts each lane from radians to degrees.
#[inline]
#[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.))
@ -71,6 +78,7 @@ pub fn to_degrees(self) -> Self {
/// 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.))
}
@ -78,6 +86,7 @@ pub fn to_radians(self) -> Self {
/// Returns true for each lane if it has a positive sign, including
/// `+0.0`, `NaN`s with positive sign bit and positive infinity.
#[inline]
#[must_use = "method returns a new mask and does not mutate the original value"]
pub fn is_sign_positive(self) -> Mask<$mask_ty, LANES> {
!self.is_sign_negative()
}
@ -85,6 +94,7 @@ pub fn is_sign_positive(self) -> Mask<$mask_ty, LANES> {
/// Returns true for each lane if it has a negative sign, including
/// `-0.0`, `NaN`s with negative sign bit and negative infinity.
#[inline]
#[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))
@ -92,24 +102,28 @@ pub fn is_sign_negative(self) -> Mask<$mask_ty, LANES> {
/// 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)
}
/// 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))
}
/// 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))
}
/// 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))
}
@ -117,6 +131,7 @@ pub fn is_subnormal(self) -> Mask<$mask_ty, LANES> {
/// Returns true for each lane if its value is neither neither zero, infinite,
/// subnormal, or `NaN`.
#[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())
}
@ -127,6 +142,7 @@ pub fn is_normal(self) -> Mask<$mask_ty, LANES> {
/// * `-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))
}
@ -135,6 +151,7 @@ pub fn signum(self) -> Self {
///
/// If any lane is a `NAN`, then a `NAN` with the sign of `sign` is returned.
#[inline]
#[must_use = "method returns a new vector and does not mutate the original value"]
pub fn copysign(self, sign: Self) -> Self {
let sign_bit = sign.to_bits() & Self::splat(-0.).to_bits();
let magnitude = self.to_bits() & !Self::splat(-0.).to_bits();
@ -145,6 +162,7 @@ pub fn copysign(self, sign: Self) -> Self {
///
/// 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 {
// TODO consider using an intrinsic
self.is_nan().select(
@ -157,6 +175,7 @@ pub fn min(self, other: Self) -> Self {
///
/// 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 {
// TODO consider using an intrinsic
self.is_nan().select(
@ -171,6 +190,7 @@ pub fn max(self, other: Self) -> Self {
/// 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(),

View file

@ -23,7 +23,7 @@ pub fn splat(ptr: *const T) -> Self {
pub fn wrapping_add(self, addend: Simd<usize, LANES>) -> Self {
unsafe {
let x: Simd<usize, LANES> = mem::transmute_copy(&self);
mem::transmute_copy(&{ x + (addend * mem::size_of::<T>()) })
mem::transmute_copy(&{ x + (addend * Simd::splat(mem::size_of::<T>())) })
}
}
}
@ -49,7 +49,7 @@ pub fn splat(ptr: *mut T) -> Self {
pub fn wrapping_add(self, addend: Simd<usize, LANES>) -> Self {
unsafe {
let x: Simd<usize, LANES> = mem::transmute_copy(&self);
mem::transmute_copy(&{ x + (addend * mem::size_of::<T>()) })
mem::transmute_copy(&{ x + (addend * Simd::splat(mem::size_of::<T>())) })
}
}
}

View file

@ -8,10 +8,10 @@
from_transmute! { unsafe u8x16 => __m128i }
from_transmute! { unsafe u8x32 => __m256i }
//from_transmute! { unsafe u8x64 => __m512i }
from_transmute! { unsafe u8x64 => __m512i }
from_transmute! { unsafe i8x16 => __m128i }
from_transmute! { unsafe i8x32 => __m256i }
//from_transmute! { unsafe i8x64 => __m512i }
from_transmute! { unsafe i8x64 => __m512i }
from_transmute! { unsafe u16x8 => __m128i }
from_transmute! { unsafe u16x16 => __m256i }

View file

@ -0,0 +1,22 @@
// Test that we handle all our "auto-deref" cases correctly.
#![feature(portable_simd)]
use core_simd::f32x4;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::*;
#[cfg(target_arch = "wasm32")]
wasm_bindgen_test_configure!(run_in_browser);
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn deref() {
let x = f32x4::splat(1.0);
let y = f32x4::splat(2.0);
let a = &x;
let b = &y;
assert_eq!(f32x4::splat(3.0), x + y);
assert_eq!(f32x4::splat(3.0), x + b);
assert_eq!(f32x4::splat(3.0), a + y);
assert_eq!(f32x4::splat(3.0), a + b);
}

View file

@ -38,22 +38,6 @@ fn normal<const LANES: usize>() {
);
}
fn scalar_rhs<const LANES: usize>() {
test_helpers::test_binary_scalar_rhs_elementwise(
&<Simd<$scalar, LANES> as core::ops::$trait<$scalar>>::$fn,
&$scalar_fn,
&|_, _| true,
);
}
fn scalar_lhs<const LANES: usize>() {
test_helpers::test_binary_scalar_lhs_elementwise(
&<$scalar as core::ops::$trait<Simd<$scalar, LANES>>>::$fn,
&$scalar_fn,
&|_, _| true,
);
}
fn assign<const LANES: usize>() {
test_helpers::test_binary_elementwise(
&|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign>::$fn_assign(&mut a, b); a },
@ -61,14 +45,6 @@ fn assign<const LANES: usize>() {
&|_, _| true,
);
}
fn assign_scalar_rhs<const LANES: usize>() {
test_helpers::test_binary_scalar_rhs_elementwise(
&|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign<$scalar>>::$fn_assign(&mut a, b); a },
&$scalar_fn,
&|_, _| true,
);
}
}
}
};
@ -99,22 +75,6 @@ fn normal<const LANES: usize>() {
);
}
fn scalar_rhs<const LANES: usize>() {
test_helpers::test_binary_scalar_rhs_elementwise(
&<Simd<$scalar, LANES> as core::ops::$trait<$scalar>>::$fn,
&$scalar_fn,
&|x, y| x.iter().all(|x| $check_fn(*x, y)),
);
}
fn scalar_lhs<const LANES: usize>() {
test_helpers::test_binary_scalar_lhs_elementwise(
&<$scalar as core::ops::$trait<Simd<$scalar, LANES>>>::$fn,
&$scalar_fn,
&|x, y| y.iter().all(|y| $check_fn(x, *y)),
);
}
fn assign<const LANES: usize>() {
test_helpers::test_binary_elementwise(
&|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign>::$fn_assign(&mut a, b); a },
@ -122,14 +82,6 @@ fn assign<const LANES: usize>() {
&|x, y| x.iter().zip(y.iter()).all(|(x, y)| $check_fn(*x, *y)),
)
}
fn assign_scalar_rhs<const LANES: usize>() {
test_helpers::test_binary_scalar_rhs_elementwise(
&|mut a, b| { <Simd<$scalar, LANES> as core::ops::$trait_assign<$scalar>>::$fn_assign(&mut a, b); a },
&$scalar_fn,
&|x, y| x.iter().all(|x| $check_fn(*x, y)),
)
}
}
}
};

View file

@ -376,6 +376,12 @@ fn lanes_16() {
fn lanes_32() {
implementation::<32>();
}
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
fn lanes_64() {
implementation::<64>();
}
}
)*
}
@ -431,6 +437,12 @@ fn lanes_16() {
fn lanes_32() {
implementation::<32>();
}
#[test]
#[should_panic]
fn lanes_64() {
implementation::<64>();
}
}
)*
}