diff --git a/crates/core_simd/src/cast.rs b/crates/core_simd/src/cast.rs index 65a3f845ffc..1c3592f8075 100644 --- a/crates/core_simd/src/cast.rs +++ b/crates/core_simd/src/cast.rs @@ -1,55 +1,51 @@ use crate::simd::SimdElement; +mod sealed { + /// Cast vector elements to other types. + /// + /// # Safety + /// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` + /// or `simd_as` intrinsics. + pub unsafe trait Sealed {} +} +use sealed::Sealed; + /// Supporting trait for `Simd::cast`. Typically doesn't need to be used directly. -/// -/// # Safety -/// Implementing this trait asserts that the type is a valid vector element for the `simd_cast` or -/// `simd_as` intrinsics. -pub unsafe trait SimdCast: SimdElement {} +pub trait SimdCast: Sealed + SimdElement {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i8 {} +unsafe impl Sealed for i8 {} +impl SimdCast for i8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i16 {} +unsafe impl Sealed for i16 {} +impl SimdCast for i16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i32 {} +unsafe impl Sealed for i32 {} +impl SimdCast for i32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for i64 {} +unsafe impl Sealed for i64 {} +impl SimdCast for i64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for isize {} +unsafe impl Sealed for isize {} +impl SimdCast for isize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u8 {} +unsafe impl Sealed for u8 {} +impl SimdCast for u8 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u16 {} +unsafe impl Sealed for u16 {} +impl SimdCast for u16 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u32 {} +unsafe impl Sealed for u32 {} +impl SimdCast for u32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for u64 {} +unsafe impl Sealed for u64 {} +impl SimdCast for u64 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for usize {} +unsafe impl Sealed for usize {} +impl SimdCast for usize {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for f32 {} +unsafe impl Sealed for f32 {} +impl SimdCast for f32 {} // Safety: primitive number types can be cast to other primitive number types -unsafe impl SimdCast for f64 {} - -/// Supporting trait for `Simd::cast_ptr`. Typically doesn't need to be used directly. -/// -/// # Safety -/// Implementing this trait asserts that the type is a valid vector element for the `simd_cast_ptr` -/// intrinsic. -pub unsafe trait SimdCastPtr {} - -// Safety: pointers can be cast to other pointer types -unsafe impl SimdCastPtr for *const U -where - U: core::ptr::Pointee, - T: core::ptr::Pointee, -{ -} -// Safety: pointers can be cast to other pointer types -unsafe impl SimdCastPtr for *mut U -where - U: core::ptr::Pointee, - T: core::ptr::Pointee, -{ -} +unsafe impl Sealed for f64 {} +impl SimdCast for f64 {} diff --git a/crates/core_simd/src/elements/const_ptr.rs b/crates/core_simd/src/elements/const_ptr.rs index 0ef9802b5e2..f215f9a61d0 100644 --- a/crates/core_simd/src/elements/const_ptr.rs +++ b/crates/core_simd/src/elements/const_ptr.rs @@ -1,5 +1,5 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SimdUint, SupportedLaneCount}; /// Operations on SIMD vectors of constant pointers. pub trait SimdConstPtr: Copy + Sealed { @@ -9,6 +9,9 @@ pub trait SimdConstPtr: Copy + Sealed { /// Vector of `isize` with the same number of lanes. type Isize; + /// Vector of const pointers with the same number of lanes. + type CastPtr; + /// Vector of mutable pointers to the same type. type MutPtr; @@ -18,6 +21,11 @@ pub trait SimdConstPtr: Copy + Sealed { /// Returns `true` for each lane that is null. fn is_null(self) -> Self::Mask; + /// Casts to a pointer of another type. + /// + /// Equivalent to calling [`pointer::cast`] on each lane. + fn cast(self) -> Self::CastPtr; + /// Changes constness without changing the type. /// /// Equivalent to calling [`pointer::cast_mut`] on each lane. @@ -78,6 +86,7 @@ impl SimdConstPtr for Simd<*const T, LANES> { type Usize = Simd; type Isize = Simd; + type CastPtr = Simd<*const U, LANES>; type MutPtr = Simd<*mut T, LANES>; type Mask = Mask; @@ -86,9 +95,22 @@ fn is_null(self) -> Self::Mask { Simd::splat(core::ptr::null()).simd_eq(self) } + #[inline] + fn cast(self) -> Self::CastPtr { + // SimdElement currently requires zero-sized metadata, so this should never fail. + // If this ever changes, `simd_cast_ptr` should produce a post-mono error. + use core::{mem::size_of, ptr::Pointee}; + assert_eq!(size_of::<::Metadata>(), 0); + assert_eq!(size_of::<::Metadata>(), 0); + + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } + } + #[inline] fn cast_mut(self) -> Self::MutPtr { - self.cast_ptr() + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } } #[inline] @@ -106,9 +128,9 @@ fn with_addr(self, addr: Self::Usize) -> Self { // In the mean-time, this operation is defined to be "as if" it was // a wrapping_offset, so we can emulate it as such. This should properly // restore pointer provenance even under today's compiler. - self.cast_ptr::<*const u8>() + self.cast::() .wrapping_offset(addr.cast::() - self.addr().cast::()) - .cast_ptr() + .cast() } #[inline] diff --git a/crates/core_simd/src/elements/float.rs b/crates/core_simd/src/elements/float.rs index d6022327055..70b18eee0c0 100644 --- a/crates/core_simd/src/elements/float.rs +++ b/crates/core_simd/src/elements/float.rs @@ -1,6 +1,6 @@ use super::sealed::Sealed; use crate::simd::{ - intrinsics, LaneCount, Mask, Simd, SimdElement, SimdPartialEq, SimdPartialOrd, + intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialEq, SimdPartialOrd, SupportedLaneCount, }; @@ -15,6 +15,51 @@ pub trait SimdFloat: Copy + Sealed { /// Bit representation of this SIMD vector type. type Bits; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for floats (truncating or saturating + /// at the limits) for each element. + /// + /// # Example + /// ``` + /// # #![feature(portable_simd)] + /// # use core::simd::Simd; + /// let floats: Simd = Simd::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); + /// let ints = floats.cast::(); + /// assert_eq!(ints, Simd::from_array([1, -4, i32::MAX, 0])); + /// + /// // Formally equivalent, but `Simd::cast` can optimize better. + /// assert_eq!(ints, Simd::from_array(floats.to_array().map(|x| x as i32))); + /// + /// // The float conversion does not round-trip. + /// let floats_again = ints.cast(); + /// assert_ne!(floats, floats_again); + /// assert_eq!(floats_again, Simd::from_array([1.0, -4.0, 2147483647.0, 0.0])); + /// ``` + #[must_use] + fn cast(self) -> Self::Cast; + + /// Rounds toward zero and converts to the same-width integer type, assuming that + /// the value is finite and fits in that type. + /// + /// # Safety + /// The value must: + /// + /// * Not be NaN + /// * Not be infinite + /// * Be representable in the return type, after truncating off its fractional part + /// + /// If these requirements are infeasible or costly, consider using the safe function [cast], + /// which saturates on conversion. + /// + /// [cast]: Simd::cast + unsafe fn to_int_unchecked(self) -> Self::Cast + where + Self::Scalar: core::convert::FloatToInt; + /// Raw transmutation to an unsigned integer vector type with the /// same size and number of lanes. #[must_use = "method returns a new vector and does not mutate the original value"] @@ -206,6 +251,24 @@ impl SimdFloat for Simd<$ty, LANES> type Mask = Mask<<$mask_ty as SimdElement>::Mask, LANES>; type Scalar = $ty; type Bits = Simd<$bits_ty, LANES>; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast + { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } + + #[inline] + #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces + unsafe fn to_int_unchecked(self) -> Self::Cast + where + Self::Scalar: core::convert::FloatToInt, + { + // Safety: supported types are guaranteed by SimdCast, the caller is responsible for the extra invariants + unsafe { intrinsics::simd_cast(self) } + } #[inline] fn to_bits(self) -> Simd<$bits_ty, LANES> { diff --git a/crates/core_simd/src/elements/int.rs b/crates/core_simd/src/elements/int.rs index 9b8c37ed466..6db89ff9a65 100644 --- a/crates/core_simd/src/elements/int.rs +++ b/crates/core_simd/src/elements/int.rs @@ -1,6 +1,6 @@ use super::sealed::Sealed; use crate::simd::{ - intrinsics, LaneCount, Mask, Simd, SimdElement, SimdPartialOrd, SupportedLaneCount, + intrinsics, LaneCount, Mask, Simd, SimdCast, SimdElement, SimdPartialOrd, SupportedLaneCount, }; /// Operations on SIMD vectors of signed integers. @@ -11,6 +11,16 @@ pub trait SimdInt: Copy + Sealed { /// Scalar type contained by this SIMD vector type. type Scalar; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for casting integers (wrapping to + /// other integer types, and saturating to float types). + #[must_use] + fn cast(self) -> Self::Cast; + /// Lanewise saturating add. /// /// # Examples @@ -198,6 +208,13 @@ impl SimdInt for Simd<$ty, LANES> { type Mask = Mask<<$ty as SimdElement>::Mask, LANES>; type Scalar = $ty; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } #[inline] fn saturating_add(self, second: Self) -> Self { diff --git a/crates/core_simd/src/elements/mut_ptr.rs b/crates/core_simd/src/elements/mut_ptr.rs index d87986b4a09..4bdc6a14ce4 100644 --- a/crates/core_simd/src/elements/mut_ptr.rs +++ b/crates/core_simd/src/elements/mut_ptr.rs @@ -1,5 +1,5 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Mask, Simd, SimdPartialEq, SimdUint, SupportedLaneCount}; /// Operations on SIMD vectors of mutable pointers. pub trait SimdMutPtr: Copy + Sealed { @@ -9,6 +9,9 @@ pub trait SimdMutPtr: Copy + Sealed { /// Vector of `isize` with the same number of lanes. type Isize; + /// Vector of const pointers with the same number of lanes. + type CastPtr; + /// Vector of constant pointers to the same type. type ConstPtr; @@ -18,6 +21,11 @@ pub trait SimdMutPtr: Copy + Sealed { /// Returns `true` for each lane that is null. fn is_null(self) -> Self::Mask; + /// Casts to a pointer of another type. + /// + /// Equivalent to calling [`pointer::cast`] on each lane. + fn cast(self) -> Self::CastPtr; + /// Changes constness without changing the type. /// /// Equivalent to calling [`pointer::cast_const`] on each lane. @@ -73,6 +81,7 @@ impl SimdMutPtr for Simd<*mut T, LANES> { type Usize = Simd; type Isize = Simd; + type CastPtr = Simd<*mut U, LANES>; type ConstPtr = Simd<*const T, LANES>; type Mask = Mask; @@ -81,9 +90,22 @@ fn is_null(self) -> Self::Mask { Simd::splat(core::ptr::null_mut()).simd_eq(self) } + #[inline] + fn cast(self) -> Self::CastPtr { + // SimdElement currently requires zero-sized metadata, so this should never fail. + // If this ever changes, `simd_cast_ptr` should produce a post-mono error. + use core::{mem::size_of, ptr::Pointee}; + assert_eq!(size_of::<::Metadata>(), 0); + assert_eq!(size_of::<::Metadata>(), 0); + + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } + } + #[inline] fn cast_const(self) -> Self::ConstPtr { - self.cast_ptr() + // Safety: pointers can be cast + unsafe { intrinsics::simd_cast_ptr(self) } } #[inline] @@ -101,9 +123,9 @@ fn with_addr(self, addr: Self::Usize) -> Self { // In the mean-time, this operation is defined to be "as if" it was // a wrapping_offset, so we can emulate it as such. This should properly // restore pointer provenance even under today's compiler. - self.cast_ptr::<*mut u8>() + self.cast::() .wrapping_offset(addr.cast::() - self.addr().cast::()) - .cast_ptr() + .cast() } #[inline] diff --git a/crates/core_simd/src/elements/uint.rs b/crates/core_simd/src/elements/uint.rs index 21e7e76eb3d..3926c395ec9 100644 --- a/crates/core_simd/src/elements/uint.rs +++ b/crates/core_simd/src/elements/uint.rs @@ -1,11 +1,21 @@ use super::sealed::Sealed; -use crate::simd::{intrinsics, LaneCount, Simd, SupportedLaneCount}; +use crate::simd::{intrinsics, LaneCount, Simd, SimdCast, SimdElement, SupportedLaneCount}; /// Operations on SIMD vectors of unsigned integers. pub trait SimdUint: Copy + Sealed { /// Scalar type contained by this SIMD vector type. type Scalar; + /// A SIMD vector with a different element type. + type Cast; + + /// Performs elementwise conversion of this vector's elements to another SIMD-valid type. + /// + /// This follows the semantics of Rust's `as` conversion for casting integers (wrapping to + /// other integer types, and saturating to float types). + #[must_use] + fn cast(self) -> Self::Cast; + /// Lanewise saturating add. /// /// # Examples @@ -77,6 +87,13 @@ impl SimdUint for Simd<$ty, LANES> LaneCount: SupportedLaneCount, { type Scalar = $ty; + type Cast = Simd; + + #[inline] + fn cast(self) -> Self::Cast { + // Safety: supported types are guaranteed by SimdCast + unsafe { intrinsics::simd_as(self) } + } #[inline] fn saturating_add(self, second: Self) -> Self { diff --git a/crates/core_simd/src/vector.rs b/crates/core_simd/src/vector.rs index b1be2a2a114..0f9854fe9d3 100644 --- a/crates/core_simd/src/vector.rs +++ b/crates/core_simd/src/vector.rs @@ -1,6 +1,6 @@ use crate::simd::{ - intrinsics, LaneCount, Mask, MaskElement, SimdCast, SimdCastPtr, SimdConstPtr, SimdMutPtr, - SimdPartialOrd, SupportedLaneCount, Swizzle, + intrinsics, LaneCount, Mask, MaskElement, SimdConstPtr, SimdMutPtr, SimdPartialOrd, + SupportedLaneCount, Swizzle, }; use core::convert::{TryFrom, TryInto}; @@ -309,77 +309,6 @@ pub fn copy_to_slice(self, slice: &mut [T]) { unsafe { self.store(slice.as_mut_ptr().cast()) } } - /// Performs elementwise conversion of a SIMD vector's elements to another SIMD-valid type. - /// - /// This follows the semantics of Rust's `as` conversion for casting integers between - /// signed and unsigned (interpreting integers as 2s complement, so `-1` to `U::MAX` and - /// `1 << (U::BITS -1)` becoming `I::MIN` ), and from floats to integers (truncating, - /// or saturating at the limits) for each element. - /// - /// # Examples - /// ``` - /// # #![feature(portable_simd)] - /// # use core::simd::Simd; - /// let floats: Simd = Simd::from_array([1.9, -4.5, f32::INFINITY, f32::NAN]); - /// let ints = floats.cast::(); - /// assert_eq!(ints, Simd::from_array([1, -4, i32::MAX, 0])); - /// - /// // Formally equivalent, but `Simd::cast` can optimize better. - /// assert_eq!(ints, Simd::from_array(floats.to_array().map(|x| x as i32))); - /// - /// // The float conversion does not round-trip. - /// let floats_again = ints.cast(); - /// assert_ne!(floats, floats_again); - /// assert_eq!(floats_again, Simd::from_array([1.0, -4.0, 2147483647.0, 0.0])); - /// ``` - #[must_use] - #[inline] - #[cfg(not(bootstrap))] - pub fn cast(self) -> Simd - where - T: SimdCast, - { - // Safety: supported types are guaranteed by SimdCast - unsafe { intrinsics::simd_as(self) } - } - - /// Casts a vector of pointers to another pointer type. - #[must_use] - #[inline] - pub fn cast_ptr(self) -> Simd - where - T: SimdCastPtr, - U: SimdElement, - { - // Safety: supported types are guaranteed by SimdCastPtr - unsafe { intrinsics::simd_cast_ptr(self) } - } - - /// Rounds toward zero and converts to the same-width integer type, assuming that - /// the value is finite and fits in that type. - /// - /// # Safety - /// The value must: - /// - /// * Not be NaN - /// * Not be infinite - /// * Be representable in the return type, after truncating off its fractional part - /// - /// If these requirements are infeasible or costly, consider using the safe function [cast], - /// which saturates on conversion. - /// - /// [cast]: Simd::cast - #[inline] - #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces - pub unsafe fn to_int_unchecked(self) -> Simd - where - T: core::convert::FloatToInt + SimdCast, - I: SimdCast, - { - // Safety: supported types are guaranteed by SimdCast, the caller is responsible for the extra invariants - unsafe { intrinsics::simd_cast(self) } - } - /// Reads from potentially discontiguous indices in `slice` to construct a SIMD vector. /// If an index is out-of-bounds, the element is instead selected from the `or` vector. /// diff --git a/crates/core_simd/tests/cast.rs b/crates/core_simd/tests/cast.rs index ab5650f0713..00545936ea2 100644 --- a/crates/core_simd/tests/cast.rs +++ b/crates/core_simd/tests/cast.rs @@ -2,7 +2,8 @@ macro_rules! cast_types { ($start:ident, $($target:ident),*) => { mod $start { - use core_simd::simd::Simd; + #[allow(unused)] + use core_simd::simd::{Simd, SimdInt, SimdUint, SimdFloat}; type Vector = Simd<$start, N>; $( mod $target { diff --git a/crates/core_simd/tests/round.rs b/crates/core_simd/tests/round.rs index 8b9638ad466..aacf7bd3bcc 100644 --- a/crates/core_simd/tests/round.rs +++ b/crates/core_simd/tests/round.rs @@ -53,6 +53,7 @@ fn fract() { test_helpers::test_lanes! { fn to_int_unchecked() { + use core_simd::simd::SimdFloat; // The maximum integer that can be represented by the equivalently sized float has // all of the mantissa digits set to 1, pushed up to the MSB. const ALL_MANTISSA_BITS: IntScalar = ((1 << ::MANTISSA_DIGITS) - 1);