1 #![cfg_attr(feature = "as_crate", no_std)] // We are std! 2 #![cfg_attr(feature = "as_crate", feature(platform_intrinsics), feature(portable_simd))] 3 #[cfg(not(feature = "as_crate"))] 4 use core::simd; 5 #[cfg(feature = "as_crate")] 6 use core_simd::simd; 7 8 use simd::{LaneCount, Simd, SupportedLaneCount}; 9 10 #[cfg(feature = "as_crate")] 11 mod experimental { 12 pub trait Sealed {} 13 } 14 15 #[cfg(feature = "as_crate")] 16 use experimental as sealed; 17 18 use crate::sealed::Sealed; 19 20 // "platform intrinsics" are essentially "codegen intrinsics" 21 // each of these may be scalarized and lowered to a libm call 22 extern "platform-intrinsic" { 23 // ceil simd_ceil<T>(x: T) -> T24 fn simd_ceil<T>(x: T) -> T; 25 26 // floor simd_floor<T>(x: T) -> T27 fn simd_floor<T>(x: T) -> T; 28 29 // round simd_round<T>(x: T) -> T30 fn simd_round<T>(x: T) -> T; 31 32 // trunc simd_trunc<T>(x: T) -> T33 fn simd_trunc<T>(x: T) -> T; 34 35 // fsqrt simd_fsqrt<T>(x: T) -> T36 fn simd_fsqrt<T>(x: T) -> T; 37 38 // fma simd_fma<T>(x: T, y: T, z: T) -> T39 fn simd_fma<T>(x: T, y: T, z: T) -> T; 40 } 41 42 /// This trait provides a possibly-temporary implementation of float functions 43 /// that may, in the absence of hardware support, canonicalize to calling an 44 /// operating system's `math.h` dynamically-loaded library (also known as a 45 /// shared object). As these conditionally require runtime support, they 46 /// should only appear in binaries built assuming OS support: `std`. 47 /// 48 /// However, there is no reason SIMD types, in general, need OS support, 49 /// as for many architectures an embedded binary may simply configure that 50 /// support itself. This means these types must be visible in `core` 51 /// but have these functions available in `std`. 52 /// 53 /// [`f32`] and [`f64`] achieve a similar trick by using "lang items", but 54 /// due to compiler limitations, it is harder to implement this approach for 55 /// abstract data types like [`Simd`]. From that need, this trait is born. 56 /// 57 /// It is possible this trait will be replaced in some manner in the future, 58 /// when either the compiler or its supporting runtime functions are improved. 59 /// For now this trait is available to permit experimentation with SIMD float 60 /// operations that may lack hardware support, such as `mul_add`. 61 pub trait StdFloat: Sealed + Sized { 62 /// Fused multiply-add. Computes `(self * a) + b` with only one rounding error, 63 /// yielding a more accurate result than an unfused multiply-add. 64 /// 65 /// Using `mul_add` *may* be more performant than an unfused multiply-add if the target 66 /// architecture has a dedicated `fma` CPU instruction. However, this is not always 67 /// true, and will be heavily dependent on designing algorithms with specific target 68 /// hardware in mind. 69 #[inline] 70 #[must_use = "method returns a new vector and does not mutate the original value"] mul_add(self, a: Self, b: Self) -> Self71 fn mul_add(self, a: Self, b: Self) -> Self { 72 unsafe { simd_fma(self, a, b) } 73 } 74 75 /// Produces a vector where every lane has the square root value 76 /// of the equivalently-indexed lane in `self` 77 #[inline] 78 #[must_use = "method returns a new vector and does not mutate the original value"] sqrt(self) -> Self79 fn sqrt(self) -> Self { 80 unsafe { simd_fsqrt(self) } 81 } 82 83 /// Returns the smallest integer greater than or equal to each lane. 84 #[must_use = "method returns a new vector and does not mutate the original value"] 85 #[inline] ceil(self) -> Self86 fn ceil(self) -> Self { 87 unsafe { simd_ceil(self) } 88 } 89 90 /// Returns the largest integer value less than or equal to each lane. 91 #[must_use = "method returns a new vector and does not mutate the original value"] 92 #[inline] floor(self) -> Self93 fn floor(self) -> Self { 94 unsafe { simd_floor(self) } 95 } 96 97 /// Rounds to the nearest integer value. Ties round toward zero. 98 #[must_use = "method returns a new vector and does not mutate the original value"] 99 #[inline] round(self) -> Self100 fn round(self) -> Self { 101 unsafe { simd_round(self) } 102 } 103 104 /// Returns the floating point's integer value, with its fractional part removed. 105 #[must_use = "method returns a new vector and does not mutate the original value"] 106 #[inline] trunc(self) -> Self107 fn trunc(self) -> Self { 108 unsafe { simd_trunc(self) } 109 } 110 111 /// Returns the floating point's fractional value, with its integer part removed. 112 #[must_use = "method returns a new vector and does not mutate the original value"] fract(self) -> Self113 fn fract(self) -> Self; 114 } 115 116 impl<const N: usize> Sealed for Simd<f32, N> where LaneCount<N>: SupportedLaneCount {} 117 impl<const N: usize> Sealed for Simd<f64, N> where LaneCount<N>: SupportedLaneCount {} 118 119 // We can safely just use all the defaults. 120 impl<const N: usize> StdFloat for Simd<f32, N> 121 where 122 LaneCount<N>: SupportedLaneCount, 123 { 124 /// Returns the floating point's fractional value, with its integer part removed. 125 #[must_use = "method returns a new vector and does not mutate the original value"] 126 #[inline] fract(self) -> Self127 fn fract(self) -> Self { 128 self - self.trunc() 129 } 130 } 131 132 impl<const N: usize> StdFloat for Simd<f64, N> 133 where 134 LaneCount<N>: SupportedLaneCount, 135 { 136 /// Returns the floating point's fractional value, with its integer part removed. 137 #[must_use = "method returns a new vector and does not mutate the original value"] 138 #[inline] fract(self) -> Self139 fn fract(self) -> Self { 140 self - self.trunc() 141 } 142 } 143 144 #[cfg(test)] 145 mod tests { 146 use super::*; 147 use simd::*; 148 149 #[test] everything_works()150 fn everything_works() { 151 let x = f32x4::from_array([0.1, 0.5, 0.6, -1.5]); 152 let x2 = x + x; 153 let _xc = x.ceil(); 154 let _xf = x.floor(); 155 let _xr = x.round(); 156 let _xt = x.trunc(); 157 let _xfma = x.mul_add(x, x); 158 let _xsqrt = x.sqrt(); 159 let _ = x2.abs() * x2; 160 } 161 } 162