use std::mem::MaybeUninit;
use crate::elem::Elem;
use crate::simd::{Mask, Simd};
pub unsafe trait Isa: Copy {
type Bits: Simd;
type M32: Mask;
type M16: Mask;
type M8: Mask;
type F32: Simd<Elem = f32, Isa = Self, Mask = Self::M32>;
type I32: Simd<Elem = i32, Isa = Self, Mask = Self::M32>;
type I16: Simd<Elem = i16, Isa = Self, Mask = Self::M16>;
type I8: Simd<Elem = i8, Isa = Self, Mask = Self::M8>;
type U8: Simd<Elem = u8, Isa = Self, Mask = Self::M8>;
type U16: Simd<Elem = u16, Isa = Self, Mask = Self::M16>;
type U32: Simd<Elem = u32, Isa = Self, Mask = Self::M32>;
fn f32(self) -> impl FloatOps<f32, Simd = Self::F32, Int = Self::I32>;
fn i32(
self,
) -> impl SignedIntOps<i32, Simd = Self::I32>
+ NarrowSaturate<i32, i16, Output = Self::I16>
+ Concat<i32>
+ ToFloat<i32, Output = Self::F32>;
fn i16(
self,
) -> impl SignedIntOps<i16, Simd = Self::I16>
+ NarrowSaturate<i16, u8, Output = Self::U8>
+ Extend<i16, Output = Self::I32>
+ Interleave<i16>;
fn i8(
self,
) -> impl SignedIntOps<i8, Simd = Self::I8> + Extend<i8, Output = Self::I16> + Interleave<i8>;
fn u8(
self,
) -> impl IntOps<u8, Simd = Self::U8> + Extend<u8, Output = Self::U16> + Interleave<u8>;
fn u16(self) -> impl IntOps<u16, Simd = Self::U16>;
fn m32(self) -> impl MaskOps<Self::M32>;
fn m16(self) -> impl MaskOps<Self::M16>;
fn m8(self) -> impl MaskOps<Self::M8>;
}
pub trait GetNumOps
where
Self: GetSimd + 'static,
{
fn num_ops<I: Isa>(isa: I) -> impl NumOps<Self, Simd = Self::Simd<I>>;
}
macro_rules! impl_get_ops {
($trait:ty, $method:ident, $ops:ident, $type:ident) => {
impl $trait for $type {
fn $method<I: Isa>(isa: I) -> impl $ops<Self, Simd = <Self as GetSimd>::Simd<I>> {
isa.$type()
}
}
};
}
impl_get_ops!(GetNumOps, num_ops, NumOps, f32);
impl_get_ops!(GetNumOps, num_ops, NumOps, i16);
impl_get_ops!(GetNumOps, num_ops, NumOps, i32);
impl_get_ops!(GetNumOps, num_ops, NumOps, i8);
impl_get_ops!(GetNumOps, num_ops, NumOps, u16);
impl_get_ops!(GetNumOps, num_ops, NumOps, u8);
pub trait GetSimd: Elem {
type Simd<I: Isa>: Simd<Elem = Self, Isa = I>;
}
macro_rules! impl_getsimd {
($ty:ty, $simd:ident) => {
impl GetSimd for $ty {
type Simd<I: Isa> = I::$simd;
}
};
}
impl_getsimd!(f32, F32);
impl_getsimd!(i16, I16);
impl_getsimd!(i32, I32);
impl_getsimd!(i8, I8);
impl_getsimd!(u16, U16);
impl_getsimd!(u8, U8);
pub trait GetFloatOps
where
Self: GetSimd,
{
fn float_ops<I: Isa>(isa: I) -> impl FloatOps<Self, Simd = Self::Simd<I>>;
}
impl_get_ops!(GetFloatOps, float_ops, FloatOps, f32);
pub trait GetIntOps
where
Self: GetSimd,
{
fn int_ops<I: Isa>(isa: I) -> impl IntOps<Self, Simd = Self::Simd<I>>;
}
impl_get_ops!(GetIntOps, int_ops, IntOps, i16);
impl_get_ops!(GetIntOps, int_ops, IntOps, i32);
impl_get_ops!(GetIntOps, int_ops, IntOps, i8);
impl_get_ops!(GetIntOps, int_ops, IntOps, u8);
impl_get_ops!(GetIntOps, int_ops, IntOps, u16);
pub trait GetSignedIntOps
where
Self: GetSimd,
{
fn signed_int_ops<I: Isa>(isa: I) -> impl SignedIntOps<Self, Simd = Self::Simd<I>>;
}
impl_get_ops!(GetSignedIntOps, signed_int_ops, SignedIntOps, i32);
impl_get_ops!(GetSignedIntOps, signed_int_ops, SignedIntOps, i16);
impl_get_ops!(GetSignedIntOps, signed_int_ops, SignedIntOps, i8);
pub unsafe trait MaskOps<M: Mask>: Copy {
fn and(self, x: M, y: M) -> M;
fn any(self, x: M) -> bool;
fn all_false(self, x: M) -> bool {
!self.any(x)
}
fn all(self, x: M) -> bool;
}
#[allow(clippy::len_without_is_empty)]
pub unsafe trait NumOps<T: Elem>: Copy {
type Simd: Simd<Elem = T>;
#[allow(clippy::wrong_self_convention)]
fn from_bits(self, x: <<Self::Simd as Simd>::Isa as Isa>::Bits) -> Self::Simd {
Self::Simd::from_bits(x)
}
fn len(self) -> usize;
fn add(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn sub(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn mul(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn zero(self) -> Self::Simd {
self.splat(T::default())
}
fn one(self) -> Self::Simd {
self.splat(T::one())
}
fn mul_add(self, a: Self::Simd, b: Self::Simd, c: Self::Simd) -> Self::Simd {
self.add(self.mul(a, b), c)
}
fn broadcast_lane<const LANE: i32>(self, x: Self::Simd) -> Self::Simd {
let val = x.to_array()[LANE as usize];
self.splat(val)
}
#[inline]
fn poly_eval(self, x: Self::Simd, coeffs: &[Self::Simd]) -> Self::Simd {
let mut y = coeffs[coeffs.len() - 1];
for i in (0..coeffs.len() - 1).rev() {
y = self.mul_add(y, x, coeffs[i]);
}
self.mul(y, x)
}
#[inline]
fn lt(self, x: Self::Simd, y: Self::Simd) -> <Self::Simd as Simd>::Mask {
self.gt(y, x)
}
#[inline]
fn le(self, x: Self::Simd, y: Self::Simd) -> <Self::Simd as Simd>::Mask {
self.ge(y, x)
}
fn eq(self, x: Self::Simd, y: Self::Simd) -> <Self::Simd as Simd>::Mask;
fn ge(self, x: Self::Simd, y: Self::Simd) -> <Self::Simd as Simd>::Mask;
fn gt(self, x: Self::Simd, y: Self::Simd) -> <Self::Simd as Simd>::Mask;
fn min(self, x: Self::Simd, y: Self::Simd) -> Self::Simd {
self.select(x, y, self.le(x, y))
}
fn max(self, x: Self::Simd, y: Self::Simd) -> Self::Simd {
self.select(x, y, self.ge(x, y))
}
fn clamp(self, x: Self::Simd, min: Self::Simd, max: Self::Simd) -> Self::Simd {
self.min(self.max(x, min), max)
}
fn and(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn not(self, x: Self::Simd) -> Self::Simd;
fn or(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn xor(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn splat(self, x: T) -> Self::Simd;
#[inline]
fn fold_splat<F: Fn(T, T) -> T>(self, x: Self::Simd, accum: T, f: F) -> Self::Simd {
let reduced = x.to_array().into_iter().fold(accum, f);
self.splat(reduced)
}
fn first_n_mask(self, n: usize) -> <Self::Simd as Simd>::Mask;
#[inline]
#[track_caller]
fn load(self, xs: &[T]) -> Self::Simd {
assert!(
xs.len() >= self.len(),
"slice length {} too short for SIMD vector width {}",
xs.len(),
self.len()
);
unsafe { self.load_ptr(xs.as_ptr()) }
}
#[inline]
#[track_caller]
fn load_many<const N: usize>(self, xs: &[T]) -> [Self::Simd; N] {
let v_len = self.len();
assert!(
xs.len() >= v_len * N,
"slice length {} too short for {} * SIMD vector width {}",
xs.len(),
N,
v_len
);
std::array::from_fn(|i| unsafe { self.load_ptr(xs.as_ptr().add(i * v_len)) })
}
#[inline]
fn load_pad(self, xs: &[T]) -> (Self::Simd, <Self::Simd as Simd>::Mask) {
let n = xs.len().min(self.len());
let mask = self.first_n_mask(n);
let vec = unsafe { self.load_ptr_mask(xs.as_ptr(), mask) };
(vec, mask)
}
unsafe fn load_ptr(self, ptr: *const T) -> Self::Simd;
unsafe fn load_ptr_mask(self, ptr: *const T, mask: <Self::Simd as Simd>::Mask) -> Self::Simd;
fn select(self, x: Self::Simd, y: Self::Simd, mask: <Self::Simd as Simd>::Mask) -> Self::Simd;
unsafe fn store_ptr(self, x: Self::Simd, ptr: *mut T);
#[inline]
fn store(self, x: Self::Simd, xs: &mut [T]) {
assert!(xs.len() >= self.len());
unsafe { self.store_ptr(x, xs.as_mut_ptr()) }
}
#[inline]
fn store_uninit(self, x: Self::Simd, xs: &mut [MaybeUninit<T>]) -> &mut [T] {
let len = self.len();
let xs_ptr = xs.as_mut_ptr() as *mut T;
assert!(xs.len() >= len);
unsafe {
self.store_ptr(x, xs_ptr);
std::slice::from_raw_parts_mut(xs_ptr, len)
}
}
unsafe fn store_ptr_mask(self, x: Self::Simd, ptr: *mut T, mask: <Self::Simd as Simd>::Mask);
fn prefetch(self, ptr: *const T) {
let _ = ptr;
}
fn prefetch_write(self, ptr: *mut T) {
let _ = ptr;
}
fn sum(self, x: Self::Simd) -> T {
let mut sum = T::default();
for elem in x.to_array() {
sum = sum.wrapping_add(elem);
}
sum
}
}
pub trait FloatOps<T: Elem>: NumOps<T> {
type Int: Simd;
fn div(self, x: Self::Simd, y: Self::Simd) -> Self::Simd;
fn reciprocal(self, x: Self::Simd) -> Self::Simd {
self.div(self.one(), x)
}
fn neg(self, x: Self::Simd) -> Self::Simd {
self.sub(self.zero(), x)
}
fn abs(self, x: Self::Simd) -> Self::Simd {
self.select(self.neg(x), x, self.lt(x, self.zero()))
}
fn round_ties_even(self, x: Self::Simd) -> Self::Simd;
fn mul_sub_from(self, a: Self::Simd, b: Self::Simd, c: Self::Simd) -> Self::Simd {
self.sub(c, self.mul(a, b))
}
fn to_int_trunc(self, x: Self::Simd) -> Self::Int;
fn to_int_round(self, x: Self::Simd) -> Self::Int;
}
pub trait IntOps<T: Elem>: NumOps<T> {
fn shift_left<const SHIFT: i32>(self, x: Self::Simd) -> Self::Simd;
fn shift_right<const SHIFT: i32>(self, x: Self::Simd) -> Self::Simd;
}
pub trait SignedIntOps<T: Elem>: IntOps<T> {
fn abs(self, x: Self::Simd) -> Self::Simd {
self.select(self.neg(x), x, self.lt(x, self.zero()))
}
fn neg(self, x: Self::Simd) -> Self::Simd {
self.sub(self.zero(), x)
}
}
pub trait Extend<T: Elem>: NumOps<T> {
type Output;
fn extend(self, x: Self::Simd) -> (Self::Output, Self::Output);
}
pub trait Interleave<T: Elem>: NumOps<T> {
fn interleave_low(self, a: Self::Simd, b: Self::Simd) -> Self::Simd;
fn interleave_high(self, a: Self::Simd, b: Self::Simd) -> Self::Simd;
}
pub trait Concat<T: Elem>: NumOps<T> {
fn concat_low(self, a: Self::Simd, b: Self::Simd) -> Self::Simd;
fn concat_high(self, a: Self::Simd, b: Self::Simd) -> Self::Simd;
}
pub trait ToFloat<T: Elem>: NumOps<T> {
type Output;
fn to_float(self, x: Self::Simd) -> Self::Output;
}
#[cfg(target_arch = "x86_64")]
pub(crate) trait Narrow<S: Simd> {
type Output;
fn narrow_truncate(self, low: S, high: S) -> Self::Output;
}
pub trait NarrowSaturate<T: Elem, U: Elem>: NumOps<T> {
type Output: Simd<Elem = U>;
fn narrow_saturate(self, low: Self::Simd, high: Self::Simd) -> Self::Output;
}
#[cfg(test)]
mod tests {
use crate::elem::WrappingAdd;
use crate::ops::{
Concat, Extend, FloatOps, IntOps, Interleave, MaskOps, NarrowSaturate, NumOps,
SignedIntOps, ToFloat,
};
use crate::{Isa, Mask, Simd, SimdOp, assert_simd_eq, assert_simd_ne, test_simd_op};
macro_rules! test_num_ops {
($modname:ident, $elem:ident, $mask_elem:ident) => {
mod $modname {
use super::{
Isa, MaskOps, NumOps, Simd, SimdOp, WrappingAdd, assert_simd_eq,
assert_simd_ne, test_simd_op,
};
#[test]
fn test_load_store() {
test_simd_op!(isa, {
let ops = isa.$elem();
let src: Vec<_> = (0..ops.len() * 4).map(|x| x as $elem).collect();
let mut dst = vec![0 as $elem; src.len()];
for (src_chunk, dst_chunk) in
src.chunks(ops.len()).zip(dst.chunks_mut(ops.len()))
{
let x = ops.load(src_chunk);
ops.store(x, dst_chunk);
}
assert_eq!(dst, src);
})
}
#[test]
fn test_store_uninit() {
test_simd_op!(isa, {
let ops = isa.$elem();
let src: Vec<_> = (0..ops.len() + 3).map(|x| x as $elem).collect();
let mut dest = Vec::with_capacity(src.len());
let x = ops.load(&src);
let init = ops.store_uninit(x, dest.spare_capacity_mut());
assert_eq!(init, &src[0..ops.len()]);
})
}
#[test]
fn test_load_many() {
test_simd_op!(isa, {
let ops = isa.$elem();
let src: Vec<_> = (0..ops.len() * 2).map(|x| x as $elem).collect();
let xs = ops.load_many::<2>(&src);
assert_simd_eq!(xs[0], ops.load(&src));
assert_simd_eq!(xs[1], ops.load(&src[ops.len()..]));
})
}
#[test]
fn test_load_pad() {
test_simd_op!(isa, {
let ops = isa.$elem();
let src = [0, 1, 2].map(|x| x as $elem);
let (vec, _mask) = ops.load_pad(&src);
let vec_array = vec.to_array();
let vec_slice = vec_array.as_ref();
assert_eq!(&vec_slice[..src.len()], &src);
for i in ops.len()..vec_slice.len() {
assert_eq!(vec_array[i], 0 as $elem);
}
})
}
#[test]
fn test_bin_ops() {
test_simd_op!(isa, {
let ops = isa.$elem();
let a = 2 as $elem;
let b = 3 as $elem;
let x = ops.splat(a);
let y = ops.splat(b);
let expected = ops.splat(a + b);
let actual = ops.add(x, y);
assert_simd_eq!(actual, expected);
let expected = ops.splat(b - a);
let actual = ops.sub(y, x);
assert_simd_eq!(actual, expected);
let expected = ops.splat(a * b);
let actual = ops.mul(x, y);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_cmp_ops() {
test_simd_op!(isa, {
let ops = isa.$elem();
let mo = isa.$mask_elem();
let x = ops.splat(1 as $elem);
let y = ops.splat(2 as $elem);
assert!(mo.all(ops.eq(x, x)));
assert!(mo.all_false(ops.eq(x, y)));
assert!(mo.all(ops.le(x, x)));
assert!(mo.all(ops.le(x, y)));
assert!(mo.all_false(ops.le(y, x)));
assert!(mo.all(ops.ge(x, x)));
assert!(mo.all_false(ops.ge(x, y)));
assert!(mo.all_false(ops.gt(x, y)));
assert!(mo.all(ops.gt(y, x)));
})
}
#[test]
fn test_mul_add() {
test_simd_op!(isa, {
let ops = isa.$elem();
let a = ops.splat(2 as $elem);
let b = ops.splat(3 as $elem);
let c = ops.splat(4 as $elem);
let actual = ops.mul_add(a, b, c);
let expected = ops.splat(((2. * 3.) + 4.) as $elem);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_min_max() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(3 as $elem);
let y_min = ops.min(x, ops.splat(2 as $elem));
let y_min_2 = ops.min(ops.splat(2 as $elem), x);
assert_simd_eq!(y_min, y_min_2);
assert_simd_eq!(y_min, ops.splat(2 as $elem));
let y_max = ops.max(x, ops.splat(4 as $elem));
let y_max_2 = ops.max(ops.splat(4 as $elem), x);
assert_simd_eq!(y_max, y_max_2);
assert_simd_eq!(y_max, ops.splat(4 as $elem));
let y_clamped = ops.clamp(x, ops.splat(0 as $elem), ops.splat(4 as $elem));
assert_simd_eq!(y_clamped, ops.splat(3 as $elem));
})
}
#[test]
fn test_and() {
test_simd_op!(isa, {
let ops = isa.$elem();
let zeros = ops.zero();
let ones = ops.not(zeros);
assert_simd_eq!(ops.and(zeros, zeros).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.and(zeros, ones).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.and(ones, zeros).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.and(ones, ones).to_bits(), ones.to_bits());
})
}
#[test]
fn test_not() {
test_simd_op!(isa, {
let ops = isa.$elem();
let zeros = ops.zero();
let ones = ops.not(zeros);
assert_simd_ne!(zeros, ones);
let zeros_2 = ops.not(ones);
assert_simd_eq!(zeros_2, zeros);
})
}
#[test]
fn test_or() {
test_simd_op!(isa, {
let ops = isa.$elem();
let zeros = ops.zero();
let ones = ops.not(zeros);
assert_simd_eq!(ops.or(zeros, zeros).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.or(zeros, ones).to_bits(), ones.to_bits());
assert_simd_eq!(ops.or(ones, zeros).to_bits(), ones.to_bits());
assert_simd_eq!(ops.or(ones, ones).to_bits(), ones.to_bits());
})
}
#[test]
fn test_xor() {
test_simd_op!(isa, {
let ops = isa.$elem();
let zeros = ops.zero();
let ones = ops.not(zeros);
assert_simd_eq!(ops.xor(zeros, zeros).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.xor(ones, ones).to_bits(), zeros.to_bits());
assert_simd_eq!(ops.xor(zeros, ones).to_bits(), ones.to_bits());
assert_simd_eq!(ops.xor(ones, zeros).to_bits(), ones.to_bits());
})
}
#[test]
fn test_sum() {
test_simd_op!(isa, {
let ops = isa.$elem();
let vec: Vec<_> = (0..ops.len()).map(|x| x as $elem).collect();
let expected = vec
.iter()
.fold(0 as $elem, |sum, x| WrappingAdd::wrapping_add(sum, *x));
let x = ops.load(&vec);
let y = ops.sum(x);
assert_eq!(y, expected);
})
}
#[test]
fn test_poly_eval() {
test_simd_op!(isa, {
let ops = isa.$elem();
let coeffs = [2, 3, 4].map(|x| x as $elem);
let x = 2 as $elem;
let y = ops.poly_eval(ops.splat(x), &coeffs.map(|c| ops.splat(c)));
let expected =
(x * coeffs[0]) + (x * x * coeffs[1]) + (x * x * x * coeffs[2]);
assert_simd_eq!(y, ops.splat(expected));
})
}
#[test]
fn test_broadcast_lane() {
test_simd_op!(isa, {
let ops = isa.$elem();
let vec: Vec<_> = (0..ops.len()).map(|x| x as $elem).collect();
let xs = ops.load(&vec);
let ys = ops.broadcast_lane::<0>(xs);
assert_simd_eq!(ops.splat(0 as $elem), ys);
let ys = ops.broadcast_lane::<1>(xs);
assert_simd_eq!(ops.splat(1 as $elem), ys);
let ys = ops.broadcast_lane::<2>(xs);
assert_simd_eq!(ops.splat(2 as $elem), ys);
let ys = ops.broadcast_lane::<3>(xs);
assert_simd_eq!(ops.splat(3 as $elem), ys);
});
}
}
};
}
test_num_ops!(num_ops_f32, f32, m32);
test_num_ops!(num_ops_i32, i32, m32);
test_num_ops!(num_ops_i16, i16, m16);
test_num_ops!(num_ops_i8, i8, m8);
test_num_ops!(num_ops_u8, u8, m8);
test_num_ops!(num_ops_u16, u16, m16);
#[test]
fn test_i8_mul_truncate() {
test_simd_op!(isa, {
let ops = isa.i8();
let x = 17i8;
let y = 19i8;
let x_vec = ops.splat(x);
let y_vec = ops.splat(y);
let expected = ops.splat(x.wrapping_mul(y));
let actual = ops.mul(x_vec, y_vec);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_u8_mul_truncate() {
test_simd_op!(isa, {
let ops = isa.u8();
let x = 17u8;
let y = 19u8;
let x_vec = ops.splat(x);
let y_vec = ops.splat(y);
let expected = ops.splat(x.wrapping_mul(y));
let actual = ops.mul(x_vec, y_vec);
assert_simd_eq!(actual, expected);
})
}
macro_rules! test_float_ops {
($modname:ident, $elem:ident, $int_elem:ident) => {
mod $modname {
use super::{FloatOps, Isa, NumOps, Simd, SimdOp, assert_simd_eq, test_simd_op};
#[test]
fn test_div() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(1.);
let y = ops.splat(2.);
let expected = ops.splat(0.5);
let actual = ops.div(x, y);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_reciprocal() {
test_simd_op!(isa, {
let ops = isa.$elem();
let vals = [-5., -2., 2., 5.];
for v in vals {
let x = ops.splat(v);
let y = ops.reciprocal(x);
let expected = ops.splat(1. / v);
assert_simd_eq!(y, expected);
}
})
}
#[test]
fn test_abs() {
test_simd_op!(isa, {
let ops = isa.$elem();
let vals = [-1., 0., 1.];
for v in vals {
let x = ops.splat(v);
let y = ops.abs(x);
let expected = ops.splat(v.abs());
assert_simd_eq!(y, expected);
}
})
}
#[test]
fn test_neg() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(3 as $elem);
let expected = ops.splat(-3 as $elem);
let actual = ops.neg(x);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_mul_sub_from() {
test_simd_op!(isa, {
let ops = isa.$elem();
let a = ops.splat(2 as $elem);
let b = ops.splat(3 as $elem);
let c = ops.splat(4 as $elem);
let actual = ops.mul_sub_from(a, b, c);
let expected = ops.splat((-(2. * 3.) + 4.) as $elem);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_round_ties_even() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(3.5 as $elem);
let expected = ops.splat(4 as $elem);
let actual = ops.round_ties_even(x);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_to_int_trunc() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(12.345);
let y = ops.to_int_trunc(x);
let expected = isa.$int_elem().splat(12);
assert_simd_eq!(y, expected);
})
}
}
};
}
test_float_ops!(float_ops_f32, f32, i32);
macro_rules! test_unsigned_int_ops {
($modname:ident, $elem:ident) => {
mod $modname {
use super::{IntOps, Isa, NumOps, Simd, SimdOp, assert_simd_eq, test_simd_op};
#[test]
fn test_shift_left() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(42);
let y = ops.shift_left::<1>(x);
let expected = ops.splat(42 << 1);
assert_simd_eq!(y, expected);
})
}
#[test]
fn test_shift_right() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(42);
let y = ops.shift_right::<1>(x);
let expected = ops.splat(42 >> 1);
assert_simd_eq!(y, expected);
})
}
}
};
}
test_unsigned_int_ops!(uint_ops_u16, u16);
macro_rules! test_signed_int_ops {
($modname:ident, $elem:ident) => {
mod $modname {
use super::{
IntOps, Isa, NumOps, SignedIntOps, Simd, SimdOp, assert_simd_eq, test_simd_op,
};
#[test]
fn test_abs() {
test_simd_op!(isa, {
let ops = isa.$elem();
let vals = [-1, 0, 1];
for v in vals {
let x = ops.splat(v);
let y = ops.abs(x);
let expected = ops.splat(v.abs());
assert_simd_eq!(y, expected);
}
})
}
#[test]
fn test_bin_ops_neg() {
test_simd_op!(isa, {
let ops = isa.$elem();
let a = -2 as $elem;
let b = 3 as $elem;
let x = ops.splat(a);
let y = ops.splat(b);
let expected = ops.splat(a + b);
let actual = ops.add(x, y);
assert_simd_eq!(actual, expected);
let expected = ops.splat(b - a);
let actual = ops.sub(y, x);
assert_simd_eq!(actual, expected);
let expected = ops.splat(a * b);
let actual = ops.mul(x, y);
assert_simd_eq!(actual, expected);
})
}
#[test]
fn test_shift_left() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(42);
let y = ops.shift_left::<1>(x);
let expected = ops.splat(42 << 1);
assert_simd_eq!(y, expected);
})
}
#[test]
fn test_shift_right() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(42);
let y = ops.shift_right::<1>(x);
let expected = ops.splat(42 >> 1);
assert_simd_eq!(y, expected);
let x = ops.splat(-128);
let y = ops.shift_right::<1>(x);
let expected = ops.splat(-64);
assert_simd_eq!(y, expected);
})
}
#[test]
fn test_neg() {
test_simd_op!(isa, {
let ops = isa.$elem();
let x = ops.splat(3 as $elem);
let expected = ops.splat(-3 as $elem);
let actual = ops.neg(x);
assert_simd_eq!(actual, expected);
})
}
}
};
}
test_signed_int_ops!(int_ops_i32, i32);
test_signed_int_ops!(int_ops_i16, i16);
test_signed_int_ops!(int_ops_i8, i8);
#[test]
fn test_cmp_gt_ge_u16() {
test_simd_op!(isa, {
let ops = isa.u16();
let m16 = isa.m16();
let x = ops.splat(i16::MAX as u16);
let y = ops.splat(i16::MAX as u16 + 1);
assert!(m16.all(ops.gt(y, x)));
assert!(m16.all(ops.ge(y, x)));
});
}
#[test]
fn test_cmp_gt_ge_u8() {
test_simd_op!(isa, {
let ops = isa.u8();
let m8 = isa.m8();
let x = ops.splat(i8::MAX as u8);
let y = ops.splat(i8::MAX as u8 + 1);
assert!(m8.all(ops.gt(y, x)));
assert!(m8.all(ops.ge(y, x)));
});
}
macro_rules! test_mask_ops {
($elem_type:ident, $mask_type:ident) => {
test_simd_op!(isa, {
let ops = isa.$elem_type();
let mask_ops = isa.$mask_type();
let ones = ops.first_n_mask(ops.len());
let zeros = ops.first_n_mask(0);
let first = ops.first_n_mask(1);
assert_simd_eq!(mask_ops.and(ones, ones), ones);
assert_simd_eq!(mask_ops.and(first, ones), first);
assert_simd_eq!(mask_ops.and(first, zeros), zeros);
assert!(mask_ops.any(ones));
assert!(mask_ops.any(first));
assert!(!mask_ops.any(zeros));
assert!(mask_ops.all(ones));
assert!(!mask_ops.all(zeros));
assert!(!mask_ops.all(first));
assert!(mask_ops.all_false(zeros));
assert!(!mask_ops.all_false(ones));
assert!(!mask_ops.all_false(first));
});
};
}
#[test]
fn test_mask_ops_m32() {
test_mask_ops!(i32, m32);
}
#[test]
fn test_mask_ops_m16() {
test_mask_ops!(i16, m16);
}
#[test]
fn test_mask_ops_m8() {
test_mask_ops!(i8, m8);
}
macro_rules! test_narrow_saturate {
($test_name:ident, $src:ident, $dest:ident) => {
#[test]
fn $test_name() {
test_simd_op!(isa, {
let ops = isa.$src();
let src: Vec<$src> = (0..ops.len() * 2).map(|x| x as $src).collect();
let expected: Vec<$dest> = src
.iter()
.map(|&x| x.clamp($dest::MIN as $src, $dest::MAX as $src) as $dest)
.collect();
let x_low = ops.load(&src[..ops.len()]);
let x_high = ops.load(&src[ops.len()..]);
let y = ops.narrow_saturate(x_low, x_high);
assert_eq!(y.to_array().as_ref(), expected);
});
}
};
}
test_narrow_saturate!(test_narrow_i32_i16, i32, i16);
test_narrow_saturate!(test_narrow_u16_u8, i16, u8);
macro_rules! test_extend {
($test_name:ident, $src:ident, $dest:ident) => {
#[test]
fn $test_name() {
test_simd_op!(isa, {
let ops = isa.$src();
let dst_ops = isa.$dest();
let src: Vec<$src> = (0..ops.len()).map(|x| x as $src).collect();
let expected: Vec<$dest> = src.iter().map(|&x| x as $dest).collect();
let x = ops.load(&src);
let (y_low, y_high) = ops.extend(x);
assert_eq!(y_low.to_array().as_ref(), &expected[..dst_ops.len()]);
assert_eq!(y_high.to_array().as_ref(), &expected[dst_ops.len()..]);
});
}
};
}
test_extend!(test_extend_i8_i16, i8, i16);
test_extend!(test_extend_i16_i32, i16, i32);
test_extend!(test_extend_u8_u16, u8, u16);
macro_rules! test_interleave {
($test_name:ident, $elem:ident) => {
#[test]
fn $test_name() {
test_simd_op!(isa, {
let ops = isa.$elem();
let even: Vec<_> = (0..ops.len()).map(|x| x as $elem * 2).collect();
let even = ops.load(&even);
let odd: Vec<_> = (0..ops.len()).map(|x| 1 + (x as $elem * 2)).collect();
let odd = ops.load(&odd);
let expected_low: Vec<_> = (0..ops.len()).map(|x| x as $elem).collect();
let expected_high: Vec<_> = (0..ops.len())
.map(|x| ops.len() as $elem + x as $elem)
.collect();
let y_low = ops.interleave_low(even, odd);
let y_high = ops.interleave_high(even, odd);
assert_eq!(y_low.to_array().as_ref(), expected_low);
assert_eq!(y_high.to_array().as_ref(), expected_high);
});
}
};
}
test_interleave!(test_interleave_i16, i16);
test_interleave!(test_interleave_i8, i8);
test_interleave!(test_interleave_u8, u8);
#[test]
fn test_concat_i32() {
test_simd_op!(isa, {
let ops = isa.i32();
let src: Vec<_> = (0..ops.len() * 2).map(|x| x as i32).collect();
let src_a = &src[..ops.len()];
let src_b = &src[ops.len()..];
let half_len = ops.len() / 2;
let expected_low: Vec<_> = (0..ops.len())
.map(|i| {
if i < half_len {
src_a[i]
} else {
src_b[i - half_len]
}
})
.collect();
let expected_hi: Vec<_> = (0..ops.len())
.map(|i| {
if i < half_len {
src_a[half_len + i]
} else {
src_b[i]
}
})
.collect();
let a = ops.load(&src_a);
let b = ops.load(&src_b);
let ab_lo = ops.concat_low(a, b);
let ab_hi = ops.concat_high(a, b);
assert_eq!(ab_lo.to_array().as_ref(), expected_low);
assert_eq!(ab_hi.to_array().as_ref(), expected_hi);
});
}
#[test]
fn test_reinterpret_cast() {
test_simd_op!(isa, {
let x = 1.456f32;
let x_i32 = x.to_bits() as i32;
let x_vec = isa.f32().splat(x);
let y_vec: I::I32 = x_vec.reinterpret_cast();
let expected = isa.i32().splat(x_i32);
assert_simd_eq!(y_vec, expected);
})
}
#[test]
fn test_to_float_i32() {
test_simd_op!(isa, {
let x = isa.i32().splat(42);
let y = isa.i32().to_float(x);
let expected = isa.f32().splat(42.0);
assert_simd_eq!(y, expected);
});
}
}