From 4076ba8a77326c70645f6c4a4351b0d84c5c898f Mon Sep 17 00:00:00 2001 From: Caleb Zulawski Date: Thu, 23 Jun 2022 01:21:58 -0400 Subject: [PATCH] Implement scatter/gather with new pointer vector and add tests --- crates/core_simd/src/cast.rs | 128 +++++++++++++++++---- crates/core_simd/src/elements/const_ptr.rs | 30 +++-- crates/core_simd/src/elements/mut_ptr.rs | 30 +++-- crates/core_simd/src/eq.rs | 20 ++-- crates/core_simd/src/ord.rs | 36 ++---- crates/core_simd/src/vector.rs | 13 +-- crates/core_simd/src/vector/ptr.rs | 51 -------- crates/core_simd/tests/pointers.rs | 43 +++++++ crates/test_helpers/src/biteq.rs | 20 ++++ crates/test_helpers/src/lib.rs | 63 ++++++---- 10 files changed, 275 insertions(+), 159 deletions(-) delete mode 100644 crates/core_simd/src/vector/ptr.rs create mode 100644 crates/core_simd/tests/pointers.rs diff --git a/crates/core_simd/src/cast.rs b/crates/core_simd/src/cast.rs index e04a9042b1bf..d62d3f6635d5 100644 --- a/crates/core_simd/src/cast.rs +++ b/crates/core_simd/src/cast.rs @@ -1,25 +1,41 @@ -use crate::simd::SimdElement; +use crate::simd::{intrinsics, LaneCount, Simd, SimdElement, SupportedLaneCount}; /// Supporting trait for `Simd::cast`. Typically doesn't need to be used directly. -pub trait SimdCast: SimdElement {} +pub trait SimdCast: SimdElement { + #[doc(hidden)] + fn cast(x: Simd) -> Simd + where + LaneCount: SupportedLaneCount; +} macro_rules! into_number { + { $from:ty, $to:ty } => { + impl SimdCast<$to> for $from { + fn cast(x: Simd) -> Simd<$to, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: simd_as can handle numeric conversions + unsafe { intrinsics::simd_as(x) } + } + } + }; { $($type:ty),* } => { $( - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} + into_number! { $type, i8 } + into_number! { $type, i16 } + into_number! { $type, i32 } + into_number! { $type, i64 } + into_number! { $type, isize } - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} - impl SimdCast for $type {} + into_number! { $type, u8 } + into_number! { $type, u16 } + into_number! { $type, u32 } + into_number! { $type, u64 } + into_number! { $type, usize } - impl SimdCast for $type {} - impl SimdCast for $type {} + into_number! { $type, f32 } + into_number! { $type, f64 } )* } } @@ -29,17 +45,85 @@ into_number! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64 } macro_rules! into_pointer { { $($type:ty),* } => { $( - impl SimdCast<$type> for *const T {} - impl SimdCast<$type> for *mut T {} - impl SimdCast<*const T> for $type {} - impl SimdCast<*mut T> for $type {} + impl SimdCast<$type> for *const T { + fn cast(x: Simd) -> Simd<$type, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting isize to pointers is safe + let x: Simd = unsafe { core::mem::transmute_copy(&x) }; + x.cast() + } + } + impl SimdCast<$type> for *mut T { + fn cast(x: Simd) -> Simd<$type, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting isize to pointers is safe + let x: Simd = unsafe { core::mem::transmute_copy(&x) }; + x.cast() + } + } + impl SimdCast<*const T> for $type { + fn cast(x: Simd<$type, LANES>) -> Simd<*const T, LANES> + where + LaneCount: SupportedLaneCount, + { + let x: Simd = x.cast(); + // Safety: transmuting isize to pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } + } + impl SimdCast<*mut T> for $type { + fn cast(x: Simd<$type, LANES>) -> Simd<*mut T, LANES> + where + LaneCount: SupportedLaneCount, + { + let x: Simd = x.cast(); + // Safety: transmuting isize to pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } + } )* } } into_pointer! { i8, i16, i32, i64, isize, u8, u16, u32, u64, usize } -impl SimdCast<*const T> for *const U {} -impl SimdCast<*const T> for *mut U {} -impl SimdCast<*mut T> for *const U {} -impl SimdCast<*mut T> for *mut U {} +impl SimdCast<*const T> for *const U { + fn cast(x: Simd<*const U, LANES>) -> Simd<*const T, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } +} +impl SimdCast<*const T> for *mut U { + fn cast(x: Simd<*mut U, LANES>) -> Simd<*const T, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } +} +impl SimdCast<*mut T> for *const U { + fn cast(x: Simd<*const U, LANES>) -> Simd<*mut T, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } +} +impl SimdCast<*mut T> for *mut U { + fn cast(x: Simd<*mut U, LANES>) -> Simd<*mut T, LANES> + where + LaneCount: SupportedLaneCount, + { + // Safety: transmuting pointers is safe + unsafe { core::mem::transmute_copy(&x) } + } +} diff --git a/crates/core_simd/src/elements/const_ptr.rs b/crates/core_simd/src/elements/const_ptr.rs index 62365eace89a..c4a254f5ab1f 100644 --- a/crates/core_simd/src/elements/const_ptr.rs +++ b/crates/core_simd/src/elements/const_ptr.rs @@ -3,8 +3,8 @@ use crate::simd::{LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; /// Operations on SIMD vectors of constant pointers. pub trait SimdConstPtr: Copy + Sealed { - /// Vector type representing the pointers as bits. - type Bits; + /// Vector of usize with the same number of lanes. + type Usize; /// Vector of mutable pointers to the same type. type MutPtr; @@ -18,11 +18,15 @@ pub trait SimdConstPtr: Copy + Sealed { /// Changes constness without changing the type. fn as_mut(self) -> Self::MutPtr; - /// Cast pointers to raw bits. - fn to_bits(self) -> Self::Bits; + /// Gets the "address" portion of the pointer. + /// + /// Equivalent to calling [`pointer::addr`] on each lane. + fn addr(self) -> Self::Usize; - /// Cast raw bits to pointers. - fn from_bits(bits: Self::Bits) -> Self; + /// Calculates the offset from a pointer using wrapping arithmetic. + /// + /// Equivalent to calling [`pointer::wrapping_add`] on each lane. + fn wrapping_add(self, count: Self::Usize) -> Self; } impl Sealed for Simd<*const T, LANES> where @@ -34,23 +38,29 @@ impl SimdConstPtr for Simd<*const T, LANES> where LaneCount: SupportedLaneCount, { - type Bits = Simd; + type Usize = Simd; type MutPtr = Simd<*mut T, LANES>; type Mask = Mask; + #[inline] fn is_null(self) -> Self::Mask { Simd::splat(core::ptr::null()).simd_eq(self) } + #[inline] fn as_mut(self) -> Self::MutPtr { self.cast() } - fn to_bits(self) -> Self::Bits { + #[inline] + fn addr(self) -> Self::Usize { self.cast() } - fn from_bits(bits: Self::Bits) -> Self { - bits.cast() + #[inline] + fn wrapping_add(self, count: Self::Usize) -> Self { + let addr = self.addr() + (count * Simd::splat(core::mem::size_of::())); + // Safety: transmuting usize to pointers is safe, even if accessing those pointers isn't. + unsafe { core::mem::transmute_copy(&addr) } } } diff --git a/crates/core_simd/src/elements/mut_ptr.rs b/crates/core_simd/src/elements/mut_ptr.rs index 8c68d42628f9..5920960c49ce 100644 --- a/crates/core_simd/src/elements/mut_ptr.rs +++ b/crates/core_simd/src/elements/mut_ptr.rs @@ -3,8 +3,8 @@ use crate::simd::{LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; /// Operations on SIMD vectors of mutable pointers. pub trait SimdMutPtr: Copy + Sealed { - /// Vector type representing the pointers as bits. - type Bits; + /// Vector of usize with the same number of lanes. + type Usize; /// Vector of constant pointers to the same type. type ConstPtr; @@ -18,11 +18,15 @@ pub trait SimdMutPtr: Copy + Sealed { /// Changes constness without changing the type. fn as_const(self) -> Self::ConstPtr; - /// Cast pointers to raw bits. - fn to_bits(self) -> Self::Bits; + /// Gets the "address" portion of the pointer. + /// + /// Equivalent to calling [`pointer::addr`] on each lane. + fn addr(self) -> Self::Usize; - /// Cast raw bits to pointers. - fn from_bits(bits: Self::Bits) -> Self; + /// Calculates the offset from a pointer using wrapping arithmetic. + /// + /// Equivalent to calling [`pointer::wrapping_add`] on each lane. + fn wrapping_add(self, count: Self::Usize) -> Self; } impl Sealed for Simd<*mut T, LANES> where LaneCount: SupportedLaneCount @@ -32,23 +36,29 @@ impl SimdMutPtr for Simd<*mut T, LANES> where LaneCount: SupportedLaneCount, { - type Bits = Simd; + type Usize = Simd; type ConstPtr = Simd<*const T, LANES>; type Mask = Mask; + #[inline] fn is_null(self) -> Self::Mask { Simd::splat(core::ptr::null_mut()).simd_eq(self) } + #[inline] fn as_const(self) -> Self::ConstPtr { self.cast() } - fn to_bits(self) -> Self::Bits { + #[inline] + fn addr(self) -> Self::Usize { self.cast() } - fn from_bits(bits: Self::Bits) -> Self { - bits.cast() + #[inline] + fn wrapping_add(self, count: Self::Usize) -> Self { + let addr = self.addr() + (count * Simd::splat(core::mem::size_of::())); + // Safety: transmuting usize to pointers is safe, even if accessing those pointers isn't. + unsafe { core::mem::transmute_copy(&addr) } } } diff --git a/crates/core_simd/src/eq.rs b/crates/core_simd/src/eq.rs index 149380746e71..80763c072727 100644 --- a/crates/core_simd/src/eq.rs +++ b/crates/core_simd/src/eq.rs @@ -1,4 +1,6 @@ -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdElement, SupportedLaneCount}; +use crate::simd::{ + intrinsics, LaneCount, Mask, Simd, SimdConstPtr, SimdElement, SimdMutPtr, SupportedLaneCount, +}; /// Parallel `PartialEq`. pub trait SimdPartialEq { @@ -80,16 +82,12 @@ where #[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)) } + self.addr().simd_eq(other.addr()) } #[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)) } + self.addr().simd_ne(other.addr()) } } @@ -101,15 +99,11 @@ where #[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)) } + self.addr().simd_eq(other.addr()) } #[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)) } + self.addr().simd_ne(other.addr()) } } diff --git a/crates/core_simd/src/ord.rs b/crates/core_simd/src/ord.rs index 95a1ecaeeda7..1ae9cd061fb2 100644 --- a/crates/core_simd/src/ord.rs +++ b/crates/core_simd/src/ord.rs @@ -1,4 +1,6 @@ -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; +use crate::simd::{ + intrinsics, LaneCount, Mask, Simd, SimdConstPtr, SimdMutPtr, SimdPartialEq, SupportedLaneCount, +}; /// Parallel `PartialOrd`. pub trait SimdPartialOrd: SimdPartialEq { @@ -218,30 +220,22 @@ where { #[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)) } + self.addr().simd_lt(other.addr()) } #[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)) } + self.addr().simd_le(other.addr()) } #[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)) } + self.addr().simd_gt(other.addr()) } #[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)) } + self.addr().simd_ge(other.addr()) } } @@ -275,30 +269,22 @@ where { #[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)) } + self.addr().simd_lt(other.addr()) } #[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)) } + self.addr().simd_le(other.addr()) } #[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)) } + self.addr().simd_gt(other.addr()) } #[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)) } + self.addr().simd_ge(other.addr()) } } diff --git a/crates/core_simd/src/vector.rs b/crates/core_simd/src/vector.rs index cbc8ced5a84e..145394a519d1 100644 --- a/crates/core_simd/src/vector.rs +++ b/crates/core_simd/src/vector.rs @@ -1,8 +1,6 @@ -// Vectors of pointers are not for public use at the current time. -pub(crate) mod ptr; - use crate::simd::{ - intrinsics, LaneCount, Mask, MaskElement, SimdCast, SimdPartialOrd, SupportedLaneCount, Swizzle, + intrinsics, LaneCount, Mask, MaskElement, SimdCast, SimdConstPtr, SimdMutPtr, SimdPartialOrd, + SupportedLaneCount, Swizzle, }; /// A SIMD vector of `LANES` elements of type `T`. `Simd` has the same shape as [`[T; N]`](array), but operates like `T`. @@ -215,8 +213,7 @@ where where T: SimdCast, { - // Safety: The input argument is a vector of a valid SIMD element type. - unsafe { intrinsics::simd_as(self) } + SimdCast::cast(self) } /// Rounds toward zero and converts to the same-width integer type, assuming that @@ -352,7 +349,7 @@ where idxs: Simd, or: Self, ) -> Self { - let base_ptr = crate::simd::ptr::SimdConstPtr::splat(slice.as_ptr()); + let base_ptr = Simd::<*const T, LANES>::splat(slice.as_ptr()); // Ferris forgive me, I have done pointer arithmetic here. let ptrs = base_ptr.wrapping_add(idxs); // Safety: The ptrs have been bounds-masked to prevent memory-unsafe reads insha'allah @@ -460,7 +457,7 @@ where // 3. &mut [T] which will become our base ptr. unsafe { // Now Entering ☢️ *mut T Zone - let base_ptr = crate::simd::ptr::SimdMutPtr::splat(slice.as_mut_ptr()); + let base_ptr = Simd::<*mut T, LANES>::splat(slice.as_mut_ptr()); // Ferris forgive me, I have done pointer arithmetic here. let ptrs = base_ptr.wrapping_add(idxs); // The ptrs have been bounds-masked to prevent memory-unsafe writes insha'allah diff --git a/crates/core_simd/src/vector/ptr.rs b/crates/core_simd/src/vector/ptr.rs deleted file mode 100644 index fa756344db91..000000000000 --- a/crates/core_simd/src/vector/ptr.rs +++ /dev/null @@ -1,51 +0,0 @@ -//! Private implementation details of public gather/scatter APIs. -use crate::simd::intrinsics; -use crate::simd::{LaneCount, Simd, SupportedLaneCount}; - -/// A vector of *const T. -#[derive(Debug, Copy, Clone)] -#[repr(simd)] -pub(crate) struct SimdConstPtr([*const T; LANES]); - -impl SimdConstPtr -where - LaneCount: SupportedLaneCount, - T: Sized, -{ - #[inline] - #[must_use] - pub fn splat(ptr: *const T) -> Self { - Self([ptr; LANES]) - } - - #[inline] - #[must_use] - pub fn wrapping_add(self, addend: Simd) -> Self { - // Safety: this intrinsic doesn't have a precondition - unsafe { intrinsics::simd_arith_offset(self, addend) } - } -} - -/// A vector of *mut T. Be very careful around potential aliasing. -#[derive(Debug, Copy, Clone)] -#[repr(simd)] -pub(crate) struct SimdMutPtr([*mut T; LANES]); - -impl SimdMutPtr -where - LaneCount: SupportedLaneCount, - T: Sized, -{ - #[inline] - #[must_use] - pub fn splat(ptr: *mut T) -> Self { - Self([ptr; LANES]) - } - - #[inline] - #[must_use] - pub fn wrapping_add(self, addend: Simd) -> Self { - // Safety: this intrinsic doesn't have a precondition - unsafe { intrinsics::simd_arith_offset(self, addend) } - } -} diff --git a/crates/core_simd/tests/pointers.rs b/crates/core_simd/tests/pointers.rs new file mode 100644 index 000000000000..df26c462f93d --- /dev/null +++ b/crates/core_simd/tests/pointers.rs @@ -0,0 +1,43 @@ +#![feature(portable_simd, strict_provenance)] + +use core_simd::{Simd, SimdConstPtr, SimdMutPtr}; + +macro_rules! common_tests { + { $constness:ident } => { + test_helpers::test_lanes! { + fn is_null() { + test_helpers::test_unary_mask_elementwise( + &Simd::<*$constness (), LANES>::is_null, + &<*$constness ()>::is_null, + &|_| true, + ); + } + + fn addr() { + test_helpers::test_unary_elementwise( + &Simd::<*$constness (), LANES>::addr, + &<*$constness ()>::addr, + &|_| true, + ); + } + + fn wrapping_add() { + test_helpers::test_binary_elementwise( + &Simd::<*$constness (), LANES>::wrapping_add, + &<*$constness ()>::wrapping_add, + &|_, _| true, + ); + } + } + } +} + +mod const_ptr { + use super::*; + common_tests! { const } +} + +mod mut_ptr { + use super::*; + common_tests! { mut } +} diff --git a/crates/test_helpers/src/biteq.rs b/crates/test_helpers/src/biteq.rs index 00350e22418d..7d91260d838a 100644 --- a/crates/test_helpers/src/biteq.rs +++ b/crates/test_helpers/src/biteq.rs @@ -55,6 +55,26 @@ macro_rules! impl_float_biteq { impl_float_biteq! { f32, f64 } +impl BitEq for *const T { + fn biteq(&self, other: &Self) -> bool { + self == other + } + + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl BitEq for *mut T { + fn biteq(&self, other: &Self) -> bool { + self == other + } + + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!(f, "{:?}", self) + } +} + impl BitEq for [T; N] { fn biteq(&self, other: &Self) -> bool { self.iter() diff --git a/crates/test_helpers/src/lib.rs b/crates/test_helpers/src/lib.rs index 650eadd12bfd..5f2a928b5e4b 100644 --- a/crates/test_helpers/src/lib.rs +++ b/crates/test_helpers/src/lib.rs @@ -38,6 +38,28 @@ impl_num! { usize } impl_num! { f32 } impl_num! { f64 } +impl DefaultStrategy for *const T { + type Strategy = proptest::strategy::Map *const T>; + fn default_strategy() -> Self::Strategy { + fn map(x: isize) -> *const T { + x as _ + } + use proptest::strategy::Strategy; + proptest::num::isize::ANY.prop_map(map) + } +} + +impl DefaultStrategy for *mut T { + type Strategy = proptest::strategy::Map *mut T>; + fn default_strategy() -> Self::Strategy { + fn map(x: isize) -> *mut T { + x as _ + } + use proptest::strategy::Strategy; + proptest::num::isize::ANY.prop_map(map) + } +} + #[cfg(not(target_arch = "wasm32"))] impl DefaultStrategy for u128 { type Strategy = proptest::num::u128::Any; @@ -135,21 +157,21 @@ pub fn test_unary_elementwise ScalarResult, check: &dyn Fn([Scalar; LANES]) -> bool, ) where - Scalar: Copy + Default + core::fmt::Debug + DefaultStrategy, - ScalarResult: Copy + Default + biteq::BitEq + core::fmt::Debug + DefaultStrategy, + Scalar: Copy + core::fmt::Debug + DefaultStrategy, + ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy, Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy, VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, { test_1(&|x: [Scalar; LANES]| { proptest::prop_assume!(check(x)); let result_1: [ScalarResult; LANES] = fv(x.into()).into(); - let result_2: [ScalarResult; LANES] = { - let mut result = [ScalarResult::default(); LANES]; - for (i, o) in x.iter().zip(result.iter_mut()) { - *o = fs(*i); - } - result - }; + let result_2: [ScalarResult; LANES] = x + .iter() + .copied() + .map(fs) + .collect::>() + .try_into() + .unwrap(); crate::prop_assert_biteq!(result_1, result_2); Ok(()) }); @@ -162,7 +184,7 @@ pub fn test_unary_mask_elementwise( fs: &dyn Fn(Scalar) -> bool, check: &dyn Fn([Scalar; LANES]) -> bool, ) where - Scalar: Copy + Default + core::fmt::Debug + DefaultStrategy, + Scalar: Copy + core::fmt::Debug + DefaultStrategy, Vector: Into<[Scalar; LANES]> + From<[Scalar; LANES]> + Copy, Mask: Into<[bool; LANES]> + From<[bool; LANES]> + Copy, { @@ -196,9 +218,9 @@ pub fn test_binary_elementwise< fs: &dyn Fn(Scalar1, Scalar2) -> ScalarResult, check: &dyn Fn([Scalar1; LANES], [Scalar2; LANES]) -> bool, ) where - Scalar1: Copy + Default + core::fmt::Debug + DefaultStrategy, - Scalar2: Copy + Default + core::fmt::Debug + DefaultStrategy, - ScalarResult: Copy + Default + biteq::BitEq + core::fmt::Debug + DefaultStrategy, + Scalar1: Copy + core::fmt::Debug + DefaultStrategy, + Scalar2: Copy + core::fmt::Debug + DefaultStrategy, + ScalarResult: Copy + biteq::BitEq + core::fmt::Debug + DefaultStrategy, Vector1: Into<[Scalar1; LANES]> + From<[Scalar1; LANES]> + Copy, Vector2: Into<[Scalar2; LANES]> + From<[Scalar2; LANES]> + Copy, VectorResult: Into<[ScalarResult; LANES]> + From<[ScalarResult; LANES]> + Copy, @@ -206,13 +228,14 @@ pub fn test_binary_elementwise< test_2(&|x: [Scalar1; LANES], y: [Scalar2; LANES]| { proptest::prop_assume!(check(x, y)); let result_1: [ScalarResult; LANES] = fv(x.into(), y.into()).into(); - let result_2: [ScalarResult; LANES] = { - let mut result = [ScalarResult::default(); LANES]; - for ((i1, i2), o) in x.iter().zip(y.iter()).zip(result.iter_mut()) { - *o = fs(*i1, *i2); - } - result - }; + let result_2: [ScalarResult; LANES] = x + .iter() + .copied() + .zip(y.iter().copied()) + .map(|(x, y)| fs(x, y)) + .collect::>() + .try_into() + .unwrap(); crate::prop_assert_biteq!(result_1, result_2); Ok(()) });