diff --git a/fearless_simd/src/generated/avx2.rs b/fearless_simd/src/generated/avx2.rs index d244b2c2..91f94da8 100644 --- a/fearless_simd/src/generated/avx2.rs +++ b/fearless_simd/src/generated/avx2.rs @@ -5152,6 +5152,22 @@ impl Simd for Avx2 { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5636,6 +5652,10 @@ impl Simd for Avx2 { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6106,6 +6126,10 @@ impl Simd for Avx2 { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { crate::kernel!( #[inline(always)] @@ -11754,6 +11778,46 @@ impl Simd for Avx2 { self.narrow_f64x4(a, b) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + [ + a[0usize] as u64, + a[1usize] as u64, + a[2usize] as u64, + a[3usize] as u64, + ] + .simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + [ + a[0usize] as u64, + a[1usize] as u64, + a[2usize] as u64, + a[3usize] as u64, + ] + .simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + [ + a[0usize] as i64, + a[1usize] as i64, + a[2usize] as i64, + a[3usize] as i64, + ] + .simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + [ + a[0usize] as i64, + a[1usize] as i64, + a[2usize] as i64, + a[3usize] as i64, + ] + .simd_into(self) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { crate::kernel!( #[inline(always)] @@ -12261,6 +12325,16 @@ impl Simd for Avx2 { self.narrow_i64x4(a, b) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + [ + a[0usize] as f64, + a[1usize] as f64, + a[2usize] as f64, + a[3usize] as f64, + ] + .simd_into(self) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { crate::kernel!( #[inline(always)] @@ -12752,6 +12826,16 @@ impl Simd for Avx2 { self.narrow_u64x4(a, b) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + [ + a[0usize] as f64, + a[1usize] as f64, + a[2usize] as f64, + a[3usize] as f64, + ] + .simd_into(self) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { crate::kernel!( #[inline(always)] @@ -16945,6 +17029,32 @@ impl Simd for Avx2 { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -17291,6 +17401,11 @@ impl Simd for Avx2 { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -17632,6 +17747,11 @@ impl Simd for Avx2 { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd/src/generated/avx512.rs b/fearless_simd/src/generated/avx512.rs index abb259ef..4020ea38 100644 --- a/fearless_simd/src/generated/avx512.rs +++ b/fearless_simd/src/generated/avx512.rs @@ -947,15 +947,9 @@ impl Simd for Avx512 { crate::kernel!( #[inline(always)] fn kernel(token: Avx512, a: f32x4) -> u32x4 { - let a = _mm_max_ps(a.into(), _mm_setzero_ps()); - let mut converted = _mm_cvttps_epu32(a); - let exceeds_unsigned_range = _mm_cmp_ps_mask::<17i32>(_mm_set1_ps(4294967040.0), a); - converted = _mm_mask_blend_epi32( - exceeds_unsigned_range, - converted, - _mm_set1_epi32(u32::MAX.cast_signed()), - ); - converted.simd_into(token) + let a = a.into(); + let positive = _mm_cmp_ps_mask::<17i32>(_mm_setzero_ps(), a); + _mm_maskz_cvttps_epu32(positive, a).simd_into(token) } ); kernel(self, a) @@ -5211,6 +5205,53 @@ impl Simd for Avx512 { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x2) -> u64x2 { + _mm_cvttpd_epu64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x2) -> u64x2 { + let a = a.into(); + let positive = _mm_cmp_pd_mask::<17i32>(_mm_setzero_pd(), a); + _mm_maskz_cvttpd_epu64(positive, a).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x2) -> i64x2 { + _mm_cvttpd_epi64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x2) -> i64x2 { + let a = a.into(); + let in_range = _mm_cmp_pd_mask::<17i32>(a, _mm_set1_pd(9223372036854775808.0)); + let mut converted = _mm_mask_cvttpd_epi64(_mm_set1_epi64x(i64::MAX), in_range, a); + let is_not_nan = _mm_cmp_pd_mask::<7i32>(a, a); + converted = _mm_mask_blend_epi64(is_not_nan, _mm_setzero_si128(), converted); + converted.simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5706,6 +5747,16 @@ impl Simd for Avx512 { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x2) -> f64x2 { + _mm_cvtepi64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6191,6 +6242,16 @@ impl Simd for Avx512 { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x2) -> f64x2 { + _mm_cvtepu64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { mask64x2 { val: (if val { 3u64 } else { 0 }) as _, @@ -6925,16 +6986,9 @@ impl Simd for Avx512 { crate::kernel!( #[inline(always)] fn kernel(token: Avx512, a: f32x8) -> u32x8 { - let a = _mm256_max_ps(a.into(), _mm256_setzero_ps()); - let mut converted = _mm256_cvttps_epu32(a); - let exceeds_unsigned_range = - _mm256_cmp_ps_mask::<17i32>(_mm256_set1_ps(4294967040.0), a); - converted = _mm256_mask_blend_epi32( - exceeds_unsigned_range, - converted, - _mm256_set1_epi32(u32::MAX.cast_signed()), - ); - converted.simd_into(token) + let a = a.into(); + let positive = _mm256_cmp_ps_mask::<17i32>(_mm256_setzero_ps(), a); + _mm256_maskz_cvttps_epu32(positive, a).simd_into(token) } ); kernel(self, a) @@ -11716,6 +11770,55 @@ impl Simd for Avx512 { self.narrow_f64x4(a, b) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x4) -> u64x4 { + _mm256_cvttpd_epu64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x4) -> u64x4 { + let a = a.into(); + let positive = _mm256_cmp_pd_mask::<17i32>(_mm256_setzero_pd(), a); + _mm256_maskz_cvttpd_epu64(positive, a).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x4) -> i64x4 { + _mm256_cvttpd_epi64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x4) -> i64x4 { + let a = a.into(); + let in_range = + _mm256_cmp_pd_mask::<17i32>(a, _mm256_set1_pd(9223372036854775808.0)); + let mut converted = + _mm256_mask_cvttpd_epi64(_mm256_set1_epi64x(i64::MAX), in_range, a); + let is_not_nan = _mm256_cmp_pd_mask::<7i32>(a, a); + converted = _mm256_mask_blend_epi64(is_not_nan, _mm256_setzero_si256(), converted); + converted.simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { crate::kernel!( #[inline(always)] @@ -12236,6 +12339,16 @@ impl Simd for Avx512 { self.narrow_i64x4(a, b) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x4) -> f64x4 { + _mm256_cvtepi64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { crate::kernel!( #[inline(always)] @@ -12746,6 +12859,16 @@ impl Simd for Avx512 { self.narrow_u64x4(a, b) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x4) -> f64x4 { + _mm256_cvtepu64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { mask64x4 { val: (if val { 15u64 } else { 0 }) as _, @@ -13543,16 +13666,9 @@ impl Simd for Avx512 { crate::kernel!( #[inline(always)] fn kernel(token: Avx512, a: f32x16) -> u32x16 { - let a = _mm512_max_ps(a.into(), _mm512_setzero_ps()); - let mut converted = _mm512_cvttps_epu32(a); - let exceeds_unsigned_range = - _mm512_cmp_ps_mask::<17i32>(_mm512_set1_ps(4294967040.0), a); - converted = _mm512_mask_blend_epi32( - exceeds_unsigned_range, - converted, - _mm512_set1_epi32(u32::MAX.cast_signed()), - ); - converted.simd_into(token) + let a = a.into(); + let positive = _mm512_cmp_ps_mask::<17i32>(_mm512_setzero_ps(), a); + _mm512_maskz_cvttps_epu32(positive, a).simd_into(token) } ); kernel(self, a) @@ -18851,6 +18967,55 @@ impl Simd for Avx512 { self.narrow_f64x8(a, b) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x8) -> u64x8 { + _mm512_cvttpd_epu64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x8) -> u64x8 { + let a = a.into(); + let positive = _mm512_cmp_pd_mask::<17i32>(_mm512_setzero_pd(), a); + _mm512_maskz_cvttpd_epu64(positive, a).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x8) -> i64x8 { + _mm512_cvttpd_epi64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: f64x8) -> i64x8 { + let a = a.into(); + let in_range = + _mm512_cmp_pd_mask::<17i32>(a, _mm512_set1_pd(9223372036854775808.0)); + let mut converted = + _mm512_mask_cvttpd_epi64(_mm512_set1_epi64(i64::MAX), in_range, a); + let is_not_nan = _mm512_cmp_pd_mask::<7i32>(a, a); + converted = _mm512_mask_blend_epi64(is_not_nan, _mm512_setzero_si512(), converted); + converted.simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { crate::kernel!( #[inline(always)] @@ -19394,6 +19559,16 @@ impl Simd for Avx512 { self.narrow_i64x8(a, b) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x8) -> f64x8 { + _mm512_cvtepi64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { crate::kernel!( #[inline(always)] @@ -19927,6 +20102,16 @@ impl Simd for Avx512 { self.narrow_u64x8(a, b) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x8) -> f64x8 { + _mm512_cvtepu64_pd(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { mask64x8 { val: (if val { 255u64 } else { 0 }) as _, diff --git a/fearless_simd/src/generated/fallback.rs b/fearless_simd/src/generated/fallback.rs index e8d23c3c..ffcd35cc 100644 --- a/fearless_simd/src/generated/fallback.rs +++ b/fearless_simd/src/generated/fallback.rs @@ -6069,6 +6069,22 @@ impl Simd for Fallback { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { [val; 2usize].simd_into(self) } @@ -6475,6 +6491,10 @@ impl Simd for Fallback { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { [val; 2usize].simd_into(self) } @@ -6877,6 +6897,10 @@ impl Simd for Fallback { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { let val: i64 = if val { !0 } else { 0 }; [val; 2usize].simd_into(self) @@ -10314,6 +10338,32 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2(self.cvt_u64_f64x2(a0), self.cvt_u64_f64x2(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2( + self.cvt_u64_precise_f64x2(a0), + self.cvt_u64_precise_f64x2(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2(self.cvt_i64_f64x2(a0), self.cvt_i64_f64x2(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2( + self.cvt_i64_precise_f64x2(a0), + self.cvt_i64_precise_f64x2(a1), + ) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { let half = self.splat_i64x2(val); self.combine_i64x2(half, half) @@ -10629,6 +10679,11 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + let (a0, a1) = self.split_i64x4(a); + self.combine_f64x2(self.cvt_f64_i64x2(a0), self.cvt_f64_i64x2(a1)) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { let half = self.splat_u64x2(val); self.combine_u64x2(half, half) @@ -10939,6 +10994,11 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + let (a0, a1) = self.split_u64x4(a); + self.combine_f64x2(self.cvt_f64_u64x2(a0), self.cvt_f64_u64x2(a1)) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { let half = self.splat_mask64x2(val); self.combine_mask64x2(half, half) @@ -14813,6 +14873,32 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -15137,6 +15223,11 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -15456,6 +15547,11 @@ impl Simd for Fallback { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd/src/generated/neon.rs b/fearless_simd/src/generated/neon.rs index 7802cbda..77eed386 100644 --- a/fearless_simd/src/generated/neon.rs +++ b/fearless_simd/src/generated/neon.rs @@ -4393,6 +4393,34 @@ impl Simd for Neon { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: f64x2) -> u64x2 { + vcvtq_u64_f64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + self.cvt_u64_f64x2(a) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: f64x2) -> i64x2 { + vcvtq_s64_f64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + self.cvt_i64_f64x2(a) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { crate::kernel!( #[inline(always)] @@ -4820,6 +4848,16 @@ impl Simd for Neon { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i64x2) -> f64x2 { + vcvtq_f64_s64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { crate::kernel!( #[inline(always)] @@ -5237,6 +5275,16 @@ impl Simd for Neon { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u64x2) -> f64x2 { + vcvtq_f64_u64(a.into()).simd_into(token) + } + ); + kernel(self, a) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { crate::kernel!( #[inline(always)] @@ -9024,6 +9072,32 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2(self.cvt_u64_f64x2(a0), self.cvt_u64_f64x2(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2( + self.cvt_u64_precise_f64x2(a0), + self.cvt_u64_precise_f64x2(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2(self.cvt_i64_f64x2(a0), self.cvt_i64_f64x2(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2( + self.cvt_i64_precise_f64x2(a0), + self.cvt_i64_precise_f64x2(a1), + ) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { let half = self.splat_i64x2(val); self.combine_i64x2(half, half) @@ -9374,6 +9448,11 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + let (a0, a1) = self.split_i64x4(a); + self.combine_f64x2(self.cvt_f64_i64x2(a0), self.cvt_f64_i64x2(a1)) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { let half = self.splat_u64x2(val); self.combine_u64x2(half, half) @@ -9719,6 +9798,11 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + let (a0, a1) = self.split_u64x4(a); + self.combine_f64x2(self.cvt_f64_u64x2(a0), self.cvt_f64_u64x2(a1)) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { let half = self.splat_mask64x2(val); self.combine_mask64x2(half, half) @@ -14055,6 +14139,32 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -14434,6 +14544,11 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -14808,6 +14923,11 @@ impl Simd for Neon { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd/src/generated/simd_trait.rs b/fearless_simd/src/generated/simd_trait.rs index db4fba63..9c2b2e30 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -69,7 +69,9 @@ pub trait Simd: Block = f64x2, Mask = Self::mask64s, ByteVector = Self::u8s, - > + SimdNarrow; + > + SimdCvtFloat + + SimdCvtFloat + + SimdNarrow; #[doc = r" A native-width SIMD vector of [`u8`]s."] type u8s: SimdInt< Self, @@ -134,7 +136,8 @@ pub trait Simd: Block = u64x2, Mask = Self::mask64s, ByteVector = Self::u8s, - > + SimdNarrow; + > + SimdCvtTruncate + + SimdNarrow; #[doc = r" A native-width SIMD vector of [`i64`]s."] type i64s: SimdInt< Self, @@ -142,7 +145,8 @@ pub trait Simd: Block = i64x2, Mask = Self::mask64s, ByteVector = Self::u8s, - > + SimdNarrow + > + SimdCvtTruncate + + SimdNarrow + core::ops::Neg; #[doc = r" A native-width SIMD mask with 8-bit lanes."] type mask8s: SimdMask @@ -1156,6 +1160,14 @@ pub trait Simd: fn saturating_narrow_f64x2(self, a: f64x2, b: f64x2) -> f32x4; #[doc = "Convert the lanes of two `f64` vectors to `f32` and concatenate them into one same-width vector.\n\nFor floating-point vectors this is identical to `narrow`, including its rounding and overflow behavior.\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_f64x2(self, a: f64x2, b: f64x2) -> f32x4; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_i64x2(self, val: i64) -> i64x2; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -1258,6 +1270,8 @@ pub trait Simd: fn saturating_narrow_i64x2(self, a: i64x2, b: i64x2) -> i32x4; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_i64x2(self, a: i64x2, b: i64x2) -> i32x4; + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_u64x2(self, val: u64) -> u64x2; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -1358,6 +1372,8 @@ pub trait Simd: fn saturating_narrow_u64x2(self, a: u64x2, b: u64x2) -> u32x4; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_u64x2(self, a: u64x2, b: u64x2) -> u32x4; + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2; #[doc = "Create a SIMD mask with all lanes set from the given boolean value."] fn splat_mask64x2(self, val: bool) -> mask64x2; #[doc = "Create a SIMD mask from a compact bitmask.\n\nBit `i` maps to lane `i`, with lane 0 in the least significant bit. Bits above the number of lanes in this mask are ignored."] @@ -2290,6 +2306,14 @@ pub trait Simd: fn saturating_narrow_f64x4(self, a: f64x4, b: f64x4) -> f32x8; #[doc = "Convert the lanes of two `f64` vectors to `f32` and concatenate them into one same-width vector.\n\nFor floating-point vectors this is identical to `narrow`, including its rounding and overflow behavior.\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_f64x4(self, a: f64x4, b: f64x4) -> f32x8; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_i64x4(self, val: i64) -> i64x4; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -2384,6 +2408,8 @@ pub trait Simd: fn saturating_narrow_i64x4(self, a: i64x4, b: i64x4) -> i32x8; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_i64x4(self, a: i64x4, b: i64x4) -> i32x8; + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_u64x4(self, val: u64) -> u64x4; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -2476,6 +2502,8 @@ pub trait Simd: fn saturating_narrow_u64x4(self, a: u64x4, b: u64x4) -> u32x8; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_u64x4(self, a: u64x4, b: u64x4) -> u32x8; + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4; #[doc = "Create a SIMD mask with all lanes set from the given boolean value."] fn splat_mask64x4(self, val: bool) -> mask64x4; #[doc = "Create a SIMD mask from a compact bitmask.\n\nBit `i` maps to lane `i`, with lane 0 in the least significant bit. Bits above the number of lanes in this mask are ignored."] @@ -3400,6 +3428,14 @@ pub trait Simd: fn saturating_narrow_f64x8(self, a: f64x8, b: f64x8) -> f32x16; #[doc = "Convert the lanes of two `f64` vectors to `f32` and concatenate them into one same-width vector.\n\nFor floating-point vectors this is identical to `narrow`, including its rounding and overflow behavior.\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_f64x8(self, a: f64x8, b: f64x8) -> f32x16; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8; + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8; + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_i64x8(self, val: i64) -> i64x8; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -3492,6 +3528,8 @@ pub trait Simd: fn saturating_narrow_i64x8(self, a: i64x8, b: i64x8) -> i32x16; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_i64x8(self, a: i64x8, b: i64x8) -> i32x16; + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8; #[doc = "Create a SIMD vector with all elements set to the given value."] fn splat_u64x8(self, val: u64) -> u64x8; #[doc = "Concatenate `[self, rhs]` and extract `Self::N` elements starting at index `SHIFT`.\n\n`SHIFT` must be within [0, `Self::N`].\n\nThis can be used to implement a \"shift items\" operation by providing all zeroes as one operand. For a left shift, the right-hand side should be all zeroes. For a right shift by `M` items, the left-hand side should be all zeroes, and the shift amount will be `Self::N - M`.\n\nThis can also be used to rotate items within a vector by providing the same vector as both operands.\n\n```text\n\nslide::<1>([a b c d], [e f g h]) == [b c d e]\n\n```"] @@ -3582,6 +3620,8 @@ pub trait Simd: fn saturating_narrow_u64x8(self, a: u64x8, b: u64x8) -> u32x16; #[doc = "Narrow the lanes of two vectors using the cheapest operation for the active SIMD backend and concatenate them into one same-width vector.\n\nInputs must fit in the destination type; in debug mode this function will panic if any of the inputs do not fit. Out-of-range results in release builds produce arbitrary values (but remain memory-safe).\n\n`a` provides the lower result lanes and `b` provides the upper result lanes."] fn relaxed_narrow_u64x8(self, a: u64x8, b: u64x8) -> u32x16; + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8; #[doc = "Create a SIMD mask with all lanes set from the given boolean value."] fn splat_mask64x8(self, val: bool) -> mask64x8; #[doc = "Create a SIMD mask from a compact bitmask.\n\nBit `i` maps to lane `i`, with lane 0 in the least significant bit. Bits above the number of lanes in this mask are ignored."] @@ -3929,8 +3969,8 @@ pub trait SimdFloat: { #[doc = r" Convert this floating-point type to an integer. This is a convenience method that"] #[doc = r" delegates to [`SimdCvtTruncate::truncate_from`], and can only be called if there"] - #[doc = r" actually exists a target type of the same bit width (currently, only `u32` and"] - #[doc = r" `i32`)."] + #[doc = r" actually exists a target type of the same bit width (`u32`/`i32` for `f32`, or"] + #[doc = r" `u64`/`i64` for `f64`)."] #[doc = r""] #[doc = r" For more information about the semantics of this specific conversion, see the"] #[doc = r" concrete `SimdCvtTruncate` implementations for integer types."] @@ -3941,7 +3981,8 @@ pub trait SimdFloat: #[doc = r" Convert this floating-point type to an integer, saturating on overflow and returning"] #[doc = r" 0 for NaN. This is a convenience method that delegates to"] #[doc = r" [`SimdCvtTruncate::truncate_from_precise`], and can only be called if there actually"] - #[doc = r" exists a target type of the same bit width (currently, only `u32` and `i32`)."] + #[doc = r" exists a target type of the same bit width (`u32`/`i32` for `f32`, or `u64`/`i64`"] + #[doc = r" for `f64`)."] #[doc = r""] #[doc = r" For more information about the semantics of this specific conversion, see the"] #[doc = r" concrete `SimdCvtTruncate` implementations for integer types."] @@ -4008,7 +4049,7 @@ pub trait SimdInt: { #[doc = r" Convert this integer type to a floating-point type. This is a convenience method"] #[doc = r" that delegates to [`SimdCvtFloat::float_from`], and can only be called if there"] - #[doc = r" actually exists a target type of the same bit width (currently, only `f32`)."] + #[doc = r" actually exists a target type of the same bit width (`f32` or `f64`)."] #[inline(always)] fn to_float>(self) -> T { T::float_from(self) diff --git a/fearless_simd/src/generated/simd_types.rs b/fearless_simd/src/generated/simd_types.rs index 1d6a23f7..4fb1e16c 100644 --- a/fearless_simd/src/generated/simd_types.rs +++ b/fearless_simd/src/generated/simd_types.rs @@ -2409,6 +2409,20 @@ impl crate::SimdFloat for f64x2 { self.simd.trunc_f64x2(self) } } +impl SimdCvtFloat> for f64x2 { + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: u64x2) -> Self { + x.simd.cvt_f64_u64x2(x) + } +} +impl SimdCvtFloat> for f64x2 { + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: i64x2) -> Self { + x.simd.cvt_f64_i64x2(x) + } +} impl SimdNarrow for f64x2 { type Narrowed = f32x4; #[inline(always)] @@ -2650,6 +2664,18 @@ impl crate::SimdInt for i64x2 { self.simd.max_i64x2(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for i64x2 { + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x2) -> Self { + x.simd.cvt_i64_f64x2(x) + } + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x2) -> Self { + x.simd.cvt_i64_precise_f64x2(x) + } +} impl SimdNarrow for i64x2 { type Narrowed = i32x4; #[inline(always)] @@ -2891,6 +2917,18 @@ impl crate::SimdInt for u64x2 { self.simd.max_u64x2(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for u64x2 { + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x2) -> Self { + x.simd.cvt_u64_f64x2(x) + } + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x2) -> Self { + x.simd.cvt_u64_precise_f64x2(x) + } +} impl SimdNarrow for u64x2 { type Narrowed = u32x4; #[inline(always)] @@ -5546,6 +5584,20 @@ impl crate::SimdFloat for f64x4 { self.simd.trunc_f64x4(self) } } +impl SimdCvtFloat> for f64x4 { + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: u64x4) -> Self { + x.simd.cvt_f64_u64x4(x) + } +} +impl SimdCvtFloat> for f64x4 { + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: i64x4) -> Self { + x.simd.cvt_f64_i64x4(x) + } +} impl SimdNarrow for f64x4 { type Narrowed = f32x8; #[inline(always)] @@ -5794,6 +5846,18 @@ impl crate::SimdInt for i64x4 { self.simd.max_i64x4(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for i64x4 { + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x4) -> Self { + x.simd.cvt_i64_f64x4(x) + } + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x4) -> Self { + x.simd.cvt_i64_precise_f64x4(x) + } +} impl SimdNarrow for i64x4 { type Narrowed = i32x8; #[inline(always)] @@ -6042,6 +6106,18 @@ impl crate::SimdInt for u64x4 { self.simd.max_u64x4(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for u64x4 { + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x4) -> Self { + x.simd.cvt_u64_f64x4(x) + } + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x4) -> Self { + x.simd.cvt_u64_precise_f64x4(x) + } +} impl SimdNarrow for u64x4 { type Narrowed = u32x8; #[inline(always)] @@ -8801,6 +8877,20 @@ impl crate::SimdFloat for f64x8 { self.simd.trunc_f64x8(self) } } +impl SimdCvtFloat> for f64x8 { + #[doc = "Convert each unsigned 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: u64x8) -> Self { + x.simd.cvt_f64_u64x8(x) + } +} +impl SimdCvtFloat> for f64x8 { + #[doc = "Convert each signed 64-bit integer element to a floating-point value.\n\nValues that cannot be exactly represented are rounded to the nearest representable value."] + #[inline(always)] + fn float_from(x: i64x8) -> Self { + x.simd.cvt_f64_i64x8(x) + } +} impl SimdNarrow for f64x8 { type Narrowed = f32x16; #[inline(always)] @@ -9055,6 +9145,18 @@ impl crate::SimdInt for i64x8 { self.simd.max_i64x8(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for i64x8 { + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x8) -> Self { + x.simd.cvt_i64_f64x8(x) + } + #[doc = "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x8) -> Self { + x.simd.cvt_i64_precise_f64x8(x) + } +} impl SimdNarrow for i64x8 { type Narrowed = i32x16; #[inline(always)] @@ -9309,6 +9411,18 @@ impl crate::SimdInt for u64x8 { self.simd.max_u64x8(self, rhs.simd_into(self.simd)) } } +impl SimdCvtTruncate> for u64x8 { + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] + #[inline(always)] + fn truncate_from(x: f64x8) -> Self { + x.simd.cvt_u64_f64x8(x) + } + #[doc = "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\nOut-of-range values are saturated to the closest in-range value. NaN becomes 0."] + #[inline(always)] + fn truncate_from_precise(x: f64x8) -> Self { + x.simd.cvt_u64_precise_f64x8(x) + } +} impl SimdNarrow for u64x8 { type Narrowed = u32x16; #[inline(always)] diff --git a/fearless_simd/src/generated/sse2.rs b/fearless_simd/src/generated/sse2.rs index b28164b0..a7b05e27 100644 --- a/fearless_simd/src/generated/sse2.rs +++ b/fearless_simd/src/generated/sse2.rs @@ -5814,6 +5814,22 @@ impl Simd for Sse2 { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { crate::kernel!( #[inline(always)] @@ -6336,6 +6352,10 @@ impl Simd for Sse2 { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6850,6 +6870,10 @@ impl Simd for Sse2 { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { crate::kernel!( #[inline(always)] @@ -10463,6 +10487,32 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2(self.cvt_u64_f64x2(a0), self.cvt_u64_f64x2(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2( + self.cvt_u64_precise_f64x2(a0), + self.cvt_u64_precise_f64x2(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2(self.cvt_i64_f64x2(a0), self.cvt_i64_f64x2(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2( + self.cvt_i64_precise_f64x2(a0), + self.cvt_i64_precise_f64x2(a1), + ) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { let half = self.splat_i64x2(val); self.combine_i64x2(half, half) @@ -10792,6 +10842,11 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + let (a0, a1) = self.split_i64x4(a); + self.combine_f64x2(self.cvt_f64_i64x2(a0), self.cvt_f64_i64x2(a1)) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { let half = self.splat_u64x2(val); self.combine_u64x2(half, half) @@ -11116,6 +11171,11 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + let (a0, a1) = self.split_u64x4(a); + self.combine_f64x2(self.cvt_f64_u64x2(a0), self.cvt_f64_u64x2(a1)) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { let half = self.splat_mask64x2(val); self.combine_mask64x2(half, half) @@ -15131,6 +15191,32 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -15469,6 +15555,11 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -15802,6 +15893,11 @@ impl Simd for Sse2 { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd/src/generated/sse4_2.rs b/fearless_simd/src/generated/sse4_2.rs index 5dd51bb1..3d1cb881 100644 --- a/fearless_simd/src/generated/sse4_2.rs +++ b/fearless_simd/src/generated/sse4_2.rs @@ -5111,6 +5111,22 @@ impl Simd for Sse4_2 { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5593,6 +5609,10 @@ impl Simd for Sse4_2 { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6066,6 +6086,10 @@ impl Simd for Sse4_2 { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { crate::kernel!( #[inline(always)] @@ -9741,6 +9765,32 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2(self.cvt_u64_f64x2(a0), self.cvt_u64_f64x2(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2( + self.cvt_u64_precise_f64x2(a0), + self.cvt_u64_precise_f64x2(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2(self.cvt_i64_f64x2(a0), self.cvt_i64_f64x2(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2( + self.cvt_i64_precise_f64x2(a0), + self.cvt_i64_precise_f64x2(a1), + ) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { let half = self.splat_i64x2(val); self.combine_i64x2(half, half) @@ -10078,6 +10128,11 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + let (a0, a1) = self.split_i64x4(a); + self.combine_f64x2(self.cvt_f64_i64x2(a0), self.cvt_f64_i64x2(a1)) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { let half = self.splat_u64x2(val); self.combine_u64x2(half, half) @@ -10410,6 +10465,11 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + let (a0, a1) = self.split_u64x4(a); + self.combine_f64x2(self.cvt_f64_u64x2(a0), self.cvt_f64_u64x2(a1)) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { let half = self.splat_mask64x2(val); self.combine_mask64x2(half, half) @@ -14466,6 +14526,32 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -14804,6 +14890,11 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -15137,6 +15228,11 @@ impl Simd for Sse4_2 { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd/src/generated/wasm.rs b/fearless_simd/src/generated/wasm.rs index fdebce01..0774253b 100644 --- a/fearless_simd/src/generated/wasm.rs +++ b/fearless_simd/src/generated/wasm.rs @@ -3106,6 +3106,22 @@ impl Simd for WasmSimd128 { self.narrow_f64x2(a, b) } #[inline(always)] + fn cvt_u64_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_u64_precise_f64x2(self, a: f64x2) -> u64x2 { + [a[0usize] as u64, a[1usize] as u64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] + fn cvt_i64_precise_f64x2(self, a: f64x2) -> i64x2 { + [a[0usize] as i64, a[1usize] as i64].simd_into(self) + } + #[inline(always)] fn splat_i64x2(self, val: i64) -> i64x2 { i64x2_splat(val).simd_into(self) } @@ -3402,6 +3418,10 @@ impl Simd for WasmSimd128 { self.narrow_i64x2(a, b) } #[inline(always)] + fn cvt_f64_i64x2(self, a: i64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_u64x2(self, val: u64) -> u64x2 { u64x2_splat(val).simd_into(self) } @@ -3714,6 +3734,10 @@ impl Simd for WasmSimd128 { self.narrow_u64x2(a, b) } #[inline(always)] + fn cvt_f64_u64x2(self, a: u64x2) -> f64x2 { + [a[0usize] as f64, a[1usize] as f64].simd_into(self) + } + #[inline(always)] fn splat_mask64x2(self, val: bool) -> mask64x2 { let val: i64 = if val { !0 } else { 0 }; i64x2_splat(val).simd_into(self) @@ -7217,6 +7241,32 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_u64_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2(self.cvt_u64_f64x2(a0), self.cvt_u64_f64x2(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x4(self, a: f64x4) -> u64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_u64x2( + self.cvt_u64_precise_f64x2(a0), + self.cvt_u64_precise_f64x2(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2(self.cvt_i64_f64x2(a0), self.cvt_i64_f64x2(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x4(self, a: f64x4) -> i64x4 { + let (a0, a1) = self.split_f64x4(a); + self.combine_i64x2( + self.cvt_i64_precise_f64x2(a0), + self.cvt_i64_precise_f64x2(a1), + ) + } + #[inline(always)] fn splat_i64x4(self, val: i64) -> i64x4 { let half = self.splat_i64x2(val); self.combine_i64x2(half, half) @@ -7543,6 +7593,11 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_f64_i64x4(self, a: i64x4) -> f64x4 { + let (a0, a1) = self.split_i64x4(a); + self.combine_f64x2(self.cvt_f64_i64x2(a0), self.cvt_f64_i64x2(a1)) + } + #[inline(always)] fn splat_u64x4(self, val: u64) -> u64x4 { let half = self.splat_u64x2(val); self.combine_u64x2(half, half) @@ -7864,6 +7919,11 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_f64_u64x4(self, a: u64x4) -> f64x4 { + let (a0, a1) = self.split_u64x4(a); + self.combine_f64x2(self.cvt_f64_u64x2(a0), self.cvt_f64_u64x2(a1)) + } + #[inline(always)] fn splat_mask64x4(self, val: bool) -> mask64x4 { let half = self.splat_mask64x2(val); self.combine_mask64x2(half, half) @@ -11800,6 +11860,32 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_u64_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4(self.cvt_u64_f64x4(a0), self.cvt_u64_f64x4(a1)) + } + #[inline(always)] + fn cvt_u64_precise_f64x8(self, a: f64x8) -> u64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_u64x4( + self.cvt_u64_precise_f64x4(a0), + self.cvt_u64_precise_f64x4(a1), + ) + } + #[inline(always)] + fn cvt_i64_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4(self.cvt_i64_f64x4(a0), self.cvt_i64_f64x4(a1)) + } + #[inline(always)] + fn cvt_i64_precise_f64x8(self, a: f64x8) -> i64x8 { + let (a0, a1) = self.split_f64x8(a); + self.combine_i64x4( + self.cvt_i64_precise_f64x4(a0), + self.cvt_i64_precise_f64x4(a1), + ) + } + #[inline(always)] fn splat_i64x8(self, val: i64) -> i64x8 { let half = self.splat_i64x4(val); self.combine_i64x4(half, half) @@ -12130,6 +12216,11 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_f64_i64x8(self, a: i64x8) -> f64x8 { + let (a0, a1) = self.split_i64x8(a); + self.combine_f64x4(self.cvt_f64_i64x4(a0), self.cvt_f64_i64x4(a1)) + } + #[inline(always)] fn splat_u64x8(self, val: u64) -> u64x8 { let half = self.splat_u64x4(val); self.combine_u64x4(half, half) @@ -12455,6 +12546,11 @@ impl Simd for WasmSimd128 { ) } #[inline(always)] + fn cvt_f64_u64x8(self, a: u64x8) -> f64x8 { + let (a0, a1) = self.split_u64x8(a); + self.combine_f64x4(self.cvt_f64_u64x4(a0), self.cvt_f64_u64x4(a1)) + } + #[inline(always)] fn splat_mask64x8(self, val: bool) -> mask64x8 { let half = self.splat_mask64x4(val); self.combine_mask64x4(half, half) diff --git a/fearless_simd_gen/src/mk_simd_trait.rs b/fearless_simd_gen/src/mk_simd_trait.rs index 625ad3cb..2d0286b1 100644 --- a/fearless_simd_gen/src/mk_simd_trait.rs +++ b/fearless_simd_gen/src/mk_simd_trait.rs @@ -75,6 +75,7 @@ pub(crate) fn mk_simd_trait() -> TokenStream { + SimdWiden; /// A native-width SIMD vector of [`f64`]s. type f64s: SimdFloat, Mask = Self::mask64s, ByteVector = Self::u8s> + + SimdCvtFloat + SimdCvtFloat + SimdNarrow; /// A native-width SIMD vector of [`u8`]s. type u8s: SimdInt, Mask = Self::mask8s, ByteVector = Self::u8s> @@ -99,9 +100,11 @@ pub(crate) fn mk_simd_trait() -> TokenStream { + core::ops::Neg; /// A native-width SIMD vector of [`u64`]s. type u64s: SimdInt, Mask = Self::mask64s, ByteVector = Self::u8s> + + SimdCvtTruncate + SimdNarrow; /// A native-width SIMD vector of [`i64`]s. type i64s: SimdInt, Mask = Self::mask64s, ByteVector = Self::u8s> + + SimdCvtTruncate + SimdNarrow + core::ops::Neg; /// A native-width SIMD mask with 8-bit lanes. @@ -342,8 +345,8 @@ fn mk_simd_float() -> TokenStream { { /// Convert this floating-point type to an integer. This is a convenience method that /// delegates to [`SimdCvtTruncate::truncate_from`], and can only be called if there - /// actually exists a target type of the same bit width (currently, only `u32` and - /// `i32`). + /// actually exists a target type of the same bit width (`u32`/`i32` for `f32`, or + /// `u64`/`i64` for `f64`). /// /// For more information about the semantics of this specific conversion, see the /// concrete `SimdCvtTruncate` implementations for integer types. @@ -353,7 +356,8 @@ fn mk_simd_float() -> TokenStream { /// Convert this floating-point type to an integer, saturating on overflow and returning /// 0 for NaN. This is a convenience method that delegates to /// [`SimdCvtTruncate::truncate_from_precise`], and can only be called if there actually - /// exists a target type of the same bit width (currently, only `u32` and `i32`). + /// exists a target type of the same bit width (`u32`/`i32` for `f32`, or `u64`/`i64` + /// for `f64`). /// /// For more information about the semantics of this specific conversion, see the /// concrete `SimdCvtTruncate` implementations for integer types. @@ -383,7 +387,7 @@ fn mk_simd_int() -> TokenStream { { /// Convert this integer type to a floating-point type. This is a convenience method /// that delegates to [`SimdCvtFloat::float_from`], and can only be called if there - /// actually exists a target type of the same bit width (currently, only `f32`). + /// actually exists a target type of the same bit width (`f32` or `f64`). #[inline(always)] fn to_float>(self) -> T { T::float_from(self) } diff --git a/fearless_simd_gen/src/mk_simd_types.rs b/fearless_simd_gen/src/mk_simd_types.rs index ed0a7727..34d83ec8 100644 --- a/fearless_simd_gen/src/mk_simd_types.rs +++ b/fearless_simd_gen/src/mk_simd_types.rs @@ -7,8 +7,9 @@ use quote::{format_ident, quote}; use crate::{ generic::{generic_op_name, unrolled_array}, ops::{ - F32_TO_I32, F32_TO_I32_PRECISE, F32_TO_U32, F32_TO_U32_PRECISE, I32_TO_F32, Op, OpSig, - TyFlavor, U32_TO_F32, base_trait_ops, vec_trait_ops_for, + F32_TO_I32, F32_TO_I32_PRECISE, F32_TO_U32, F32_TO_U32_PRECISE, F64_TO_I64, + F64_TO_I64_PRECISE, F64_TO_U64, F64_TO_U64_PRECISE, I32_TO_F32, I64_TO_F64, Op, OpSig, + TyFlavor, U32_TO_F32, U64_TO_F64, base_trait_ops, vec_trait_ops_for, }, types::{SIMD_TYPES, ScalarType, VecType}, }; @@ -118,9 +119,8 @@ pub(crate) fn mk_simd_types() -> TokenStream { }; let impl_block = simd_vec_impl(ty); let mut conditional_impls = Vec::new(); - // TODO: Relax `if` clauses once 64-bit integer or 16-bit floats vectors are implemented match ty.scalar { - ScalarType::Float if ty.scalar_bits == 32 => { + ScalarType::Float if matches!(ty.scalar_bits, 32 | 64) => { for src_scalar in [ScalarType::Unsigned, ScalarType::Int] { let src_ty = ty.cast(src_scalar); let method = format_ident!( @@ -129,9 +129,11 @@ pub(crate) fn mk_simd_types() -> TokenStream { src_ty.rust_name() ); let src_ty = src_ty.rust(); - let op = match src_scalar { - ScalarType::Unsigned => U32_TO_F32, - ScalarType::Int => I32_TO_F32, + let op = match (src_scalar, ty.scalar_bits) { + (ScalarType::Unsigned, 32) => U32_TO_F32, + (ScalarType::Int, 32) => I32_TO_F32, + (ScalarType::Unsigned, 64) => U64_TO_F64, + (ScalarType::Int, 64) => I64_TO_F64, _ => unreachable!(), }; let doc = op.format_docstring(TyFlavor::VecImpl); @@ -146,16 +148,18 @@ pub(crate) fn mk_simd_types() -> TokenStream { }); } } - ScalarType::Int | ScalarType::Unsigned if ty.scalar_bits == 32 => { + ScalarType::Int | ScalarType::Unsigned if matches!(ty.scalar_bits, 32 | 64) => { let src_ty = ty.cast(ScalarType::Float); let method = format_ident!( "cvt_{}_{}", ty.scalar.rust_name(ty.scalar_bits), src_ty.rust_name() ); - let op = match ty.scalar { - ScalarType::Unsigned => F32_TO_U32, - ScalarType::Int => F32_TO_I32, + let op = match (ty.scalar, ty.scalar_bits) { + (ScalarType::Unsigned, 32) => F32_TO_U32, + (ScalarType::Int, 32) => F32_TO_I32, + (ScalarType::Unsigned, 64) => F64_TO_U64, + (ScalarType::Int, 64) => F64_TO_I64, _ => unreachable!(), }; let doc = op.format_docstring(TyFlavor::VecImpl); @@ -164,9 +168,11 @@ pub(crate) fn mk_simd_types() -> TokenStream { ty.scalar.rust_name(ty.scalar_bits), src_ty.rust_name() ); - let op_precise = match ty.scalar { - ScalarType::Unsigned => F32_TO_U32_PRECISE, - ScalarType::Int => F32_TO_I32_PRECISE, + let op_precise = match (ty.scalar, ty.scalar_bits) { + (ScalarType::Unsigned, 32) => F32_TO_U32_PRECISE, + (ScalarType::Int, 32) => F32_TO_I32_PRECISE, + (ScalarType::Unsigned, 64) => F64_TO_U64_PRECISE, + (ScalarType::Int, 64) => F64_TO_I64_PRECISE, _ => unreachable!(), }; let doc_precise = op_precise.format_docstring(TyFlavor::VecImpl); diff --git a/fearless_simd_gen/src/mk_wasm.rs b/fearless_simd_gen/src/mk_wasm.rs index 0427b2a3..07b4d4a3 100644 --- a/fearless_simd_gen/src/mk_wasm.rs +++ b/fearless_simd_gen/src/mk_wasm.rs @@ -686,6 +686,9 @@ impl Level for WasmSimd128 { scalar_bits, precise, } => { + if scalar_bits == 64 { + return fallback_method(op, vec_ty); + } let (op, uses_relaxed) = match (vec_ty.scalar, target_ty, precise) { (ScalarType::Float, ScalarType::Int | ScalarType::Unsigned, false) => { ("relaxed_trunc", true) diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index 727a8e8e..bed8cf22 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -25,6 +25,12 @@ pub(crate) enum X86 { Avx512, } +#[derive(Clone, Copy, PartialEq, Eq)] +enum Precision { + Approx, + Precise, +} + pub(crate) const SSE2_FEATURES: &str = "fxsr,sse,sse2"; pub(crate) const SSE4_2_FEATURES: &str = "fxsr,sse4.2,cmpxchg16b,popcnt"; pub(crate) const AVX2_FEATURES: &str = @@ -3234,132 +3240,217 @@ impl X86 { target_scalar_bits: usize, precise: bool, ) -> TokenStream { + use Precision::{Approx, Precise}; + use ScalarType::{Float, Int, Unsigned}; + + // Conversion methods preserve the number of lanes, so their source and destination + // scalars must have the same width. Widening and narrowing conversions are generated by + // separate handlers. assert_eq!( vec_ty.scalar_bits, target_scalar_bits, "we currently only support converting between types of the same width" ); - if *self == Self::Sse2 - && (precise - || vec_ty.scalar == ScalarType::Unsigned - || target_scalar == ScalarType::Unsigned) - { - return fallback_method(op, vec_ty); - } + // `Precise` float-to-integer conversions reproduce Rust's saturating `as` semantics, + // including zero for NaN. `Approx` conversions may return the hardware-defined result for + // NaN and out-of-range inputs. Precision does not affect integer-to-float conversions. + let precision = if precise { Precise } else { Approx }; - if *self == Self::Avx512 - && vec_ty.scalar == ScalarType::Float - && target_scalar == ScalarType::Unsigned - { - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); - let convert = intrinsic_ident("cvttps", "epu32", vec_ty.n_bits()); - return self.kernel_method(op, vec_ty, |token| { - if precise { - let max = simple_intrinsic("max", vec_ty); - let cmp = intrinsic_ident("cmp", "ps_mask", vec_ty.n_bits()); - let blend = avx512_mask_blend_intrinsic(&target_ty); - let set1_float = set1_intrinsic(vec_ty); - let set1_int = set1_intrinsic(&target_ty); - let set0_float = - intrinsic_ident("setzero", coarse_type(vec_ty), vec_ty.n_bits()); - let lt = avx512_float_compare_predicate("simd_lt"); - quote! { - let a = #max(a.into(), #set0_float()); - let mut converted = #convert(a); - let exceeds_unsigned_range = #cmp::<#lt>(#set1_float(4294967040.0), a); - converted = #blend( - exceeds_unsigned_range, - converted, - #set1_int(u32::MAX.cast_signed()), - ); - converted.simd_into(#token) - } - } else { - quote! { - #convert(a.into()).simd_into(#token) - } - } - }); - } + // Keep every property that affects instruction selection in one key. The exhaustive match + // below then makes additions to the backend or type matrix fail loudly until handled. + let conversion = ( + *self, + vec_ty.scalar, + target_scalar, + vec_ty.scalar_bits, + vec_ty.n_bits(), + precision, + ); - if *self == Self::Avx512 - && vec_ty.scalar == ScalarType::Float - && target_scalar == ScalarType::Int - && vec_ty.scalar_bits == 32 - { - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); - let convert = intrinsic_ident("cvttps", "epi32", vec_ty.n_bits()); - return self.kernel_method(op, vec_ty, |token| { - if precise { - let masked_convert = intrinsic_ident("mask_cvttps", "epi32", vec_ty.n_bits()); - let cmp = intrinsic_ident("cmp", "ps_mask", vec_ty.n_bits()); - let blend = avx512_mask_blend_intrinsic(&target_ty); - let set1_float = set1_intrinsic(vec_ty); - let set1_int = set1_intrinsic(&target_ty); - let set0_int = - intrinsic_ident("setzero", coarse_type(&target_ty), target_ty.n_bits()); - let lt = avx512_float_compare_predicate("simd_lt"); - let ord = avx512_float_compare_predicate("ord"); - quote! { - let a = a.into(); - let in_range = #cmp::<#lt>(a, #set1_float(2147483648.0)); - let mut converted = #masked_convert(#set1_int(i32::MAX), in_range, a); - let is_not_nan = #cmp::<#ord>(a, a); - converted = #blend(is_not_nan, #set0_int(), converted); - converted.simd_into(#token) - } - } else { - quote! { - #convert(a.into()).simd_into(#token) - } - } - }); + // Deal with whole-method scalar fallbacks before constructing an intrinsic-backed kernel. + // Pre-AVX-512 x86 has no packed 64-bit conversion instructions. Some SSE2 32-bit cases are + // also absent or slower than converting the lanes individually. + if matches!( + conversion, + (Self::Sse2 | Self::Sse4_2, _, _, 64, 128, _) + | (Self::Avx2, _, _, 64, 128 | 256, _) + | (Self::Sse2, _, _, 32, 128, Precise) + | (Self::Sse2, Unsigned, _, 32, 128, _) + | (Self::Sse2, _, Unsigned, 32, 128, _) + ) { + // These conversions have no hardware support, or their native implementation is + // slower than the scalar fallback. + return fallback_method(op, vec_ty); } - if *self == Self::Avx512 - && matches!(vec_ty.n_bits(), 128 | 256) - && vec_ty.scalar == ScalarType::Unsigned - && target_scalar == ScalarType::Float - && vec_ty.scalar_bits == 32 - { - // We cannot emit the intrinsics for the conversion instructions - // because the required intrinsics are mysteriously absent from stdarch: - // https://github.com/rust-lang/rust/issues/158196 - // Fortunately LLVM optimizes this sequence into the single instruction we're after. - // TODO: switch to intrinsics once they're added, stabilized, and our MSRV is high enough. - let bits = vec_ty.n_bits(); - let zext = format_ident!("_mm512_zextsi{bits}_si512"); - let convert = intrinsic_ident("cvtepu32", "ps", 512); - let cast = format_ident!("_mm512_castps512_ps{bits}"); - return self.kernel_method(op, vec_ty, |token| { + self.kernel_method(op, vec_ty, |token| match conversion { + (Self::Avx512, Unsigned, Float, 32, bits @ (128 | 256), _) => { + // AVX-512 unsigned integer -> float for narrow vectors. + // We cannot emit the intrinsics for the conversion instructions + // because the required intrinsics are mysteriously absent from stdarch: + // https://github.com/rust-lang/rust/issues/158196 + // Fortunately LLVM optimizes this sequence into the single instruction we're after. + // TODO: switch to intrinsics once they're added, stabilized, and our MSRV is high enough. + let zext = format_ident!("_mm512_zextsi{bits}_si512"); + let convert = intrinsic_ident("cvtepu32", "ps", 512); + let cast = format_ident!("_mm512_castps512_ps{bits}"); quote! { #cast(#convert(#zext(a.into()))).simd_into(#token) } - }); - } - - if *self == Self::Avx512 && vec_ty.n_bits() == 512 { - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); - return self.kernel_method(op, vec_ty, |token| match (vec_ty.scalar, target_scalar) { - (ScalarType::Int, ScalarType::Float) => { - let intrinsic = simple_intrinsic("cvtepi32", &target_ty); - quote! { - #intrinsic(a.into()).simd_into(#token) - } + } + ( + Self::Avx512, + source @ (Int | Unsigned), + Float, + scalar_bits @ (32 | 64), + 128 | 256 | 512, + _, + ) => { + // native AVX-512 conversions with available intrinsics + let target_ty = vec_ty.cast(target_scalar); + let convert = simple_intrinsic( + &format!("cvtep{}{scalar_bits}", source.prefix()), + &target_ty, + ); + quote! { + #convert(a.into()).simd_into(#token) } - (ScalarType::Unsigned, ScalarType::Float) => { - let intrinsic = simple_intrinsic("cvtepu32", &target_ty); - quote! { - #intrinsic(a.into()).simd_into(#token) - } + } + ( + Self::Avx512, + Float, + Int, + scalar_bits @ (32 | 64), + 128 | 256 | 512, + Precise, + ) => { + // The truncating signed conversion already returns iN::MIN for negative overflow. + // Mask positive overflow to iN::MAX, then replace NaN with zero to complete Rust's + // float-to-integer cast semantics. + let target_ty = vec_ty.cast(target_scalar); + let float_suffix = op_suffix(Float, scalar_bits, true); + let int_suffix = op_suffix(Int, scalar_bits, true); + let masked_convert = intrinsic_ident( + &format!("mask_cvtt{float_suffix}"), + int_suffix, + vec_ty.n_bits(), + ); + let cmp = intrinsic_ident( + "cmp", + &format!("{float_suffix}_mask"), + vec_ty.n_bits(), + ); + let blend = avx512_mask_blend_intrinsic(&target_ty); + let set1_float = set1_intrinsic(vec_ty); + let set1_int = set1_intrinsic(&target_ty); + let set0_int = + intrinsic_ident("setzero", coarse_type(&target_ty), target_ty.n_bits()); + // `2^(N - 1)` is exactly representable in both f32 and f64, and is the first + // positive value outside the corresponding signed integer range. + // Lanes at or above this threshold retain the merge value `iN::MAX`. + // Negative overflow can use the truncating instruction's signed indefinite result, `iN::MIN`; + // NaN is replaced with zero below. + let upper_bound = match scalar_bits { + 32 => quote! { 2147483648.0 }, // 2^31 + 64 => quote! { 9223372036854775808.0 }, // 2^63 + _ => unreachable!(), + }; + let int_max = match scalar_bits { + 32 => quote! { i32::MAX }, + 64 => quote! { i64::MAX }, + _ => unreachable!(), + }; + let lt = avx512_float_compare_predicate("simd_lt"); + let ord = avx512_float_compare_predicate("ord"); + quote! { + let a = a.into(); + let in_range = #cmp::<#lt>(a, #set1_float(#upper_bound)); + let mut converted = #masked_convert(#set1_int(#int_max), in_range, a); + let is_not_nan = #cmp::<#ord>(a, a); + converted = #blend(is_not_nan, #set0_int(), converted); + converted.simd_into(#token) } - _ => unimplemented!(), - }); - } - - self.kernel_method(op, vec_ty, |token| match (vec_ty.scalar, target_scalar) { - (ScalarType::Float, ScalarType::Int | ScalarType::Unsigned) => { - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); + } + ( + Self::Avx512, + Float, + Int, + scalar_bits @ (32 | 64), + 128 | 256 | 512, + Approx, + ) => { + let target_ty = vec_ty.cast(target_scalar); + let float_suffix = op_suffix(Float, scalar_bits, true); + let convert = simple_intrinsic(&format!("cvtt{float_suffix}"), &target_ty); + quote! { + #convert(a.into()).simd_into(#token) + } + } + ( + Self::Avx512, + Float, + Unsigned, + scalar_bits @ (32 | 64), + 128 | 256 | 512, + Precise, + ) => { + // AVX-512's unsigned indefinite result is already uN::MAX, so positive overflow + // needs no correction. Masking the conversion with an ordered positive comparison + // makes every negative, zero, and NaN lane zero as required by Rust casts. + let float_suffix = op_suffix(Float, scalar_bits, true); + let int_suffix = op_suffix(Unsigned, scalar_bits, true); + let cmp = intrinsic_ident( + "cmp", + &format!("{float_suffix}_mask"), + vec_ty.n_bits(), + ); + let convert = intrinsic_ident( + &format!("maskz_cvtt{float_suffix}"), + int_suffix, + vec_ty.n_bits(), + ); + let set0_float = intrinsic_ident("setzero", coarse_type(vec_ty), vec_ty.n_bits()); + let lt = avx512_float_compare_predicate("simd_lt"); + quote! { + let a = a.into(); + let positive = #cmp::<#lt>(#set0_float(), a); + #convert(positive, a).simd_into(#token) + } + } + ( + Self::Avx512, + Float, + Unsigned, + scalar_bits @ (32 | 64), + 128 | 256 | 512, + Approx, + ) => { + let target_ty = vec_ty.cast(target_scalar); + let float_suffix = op_suffix(Float, scalar_bits, true); + let convert = simple_intrinsic(&format!("cvtt{float_suffix}"), &target_ty); + quote! { + #convert(a.into()).simd_into(#token) + } + } + ( + Self::Sse2 | Self::Sse4_2, + Float, + target @ (Int | Unsigned), + 32, + 128, + precision, + ) + | ( + Self::Avx2, + Float, + target @ (Int | Unsigned), + 32, + 128 | 256, + precision, + ) => { + // SSE and AVX2 only provide packed f32 -> i32 conversion. + // Build unsigned and precise semantics around that signed primitive. + let target_ty = vec_ty.cast(target); let max = simple_intrinsic("max", vec_ty); let set0 = intrinsic_ident("setzero", coarse_type(vec_ty), vec_ty.n_bits()); let cmplt = float_compare_method("simd_lt", vec_ty); @@ -3386,13 +3477,13 @@ impl X86 { let add_int = simple_sign_unaware_intrinsic("add", &target_ty); let sub_float = simple_intrinsic("sub", vec_ty); - match (target_scalar, precise) { - (ScalarType::Int, false) => { + match (target, precision) { + (Int, Approx) => { quote! { #convert(a.into()).simd_into(#token) } } - (ScalarType::Unsigned, false) => { + (Unsigned, Approx) => { quote! { let mut converted = #convert(a.into()); @@ -3410,7 +3501,7 @@ impl X86 { converted.simd_into(#token) } } - (ScalarType::Int, true) => { + (Int, Precise) => { quote! { let a = a.into(); @@ -3433,7 +3524,7 @@ impl X86 { converted.simd_into(#token) } } - (ScalarType::Unsigned, true) => { + (Unsigned, Precise) => { quote! { // Clamp out-of-range values (and NaN) to 0. Intel's `_mm_max_ps` always takes the second // operand if the first is NaN. @@ -3461,24 +3552,17 @@ impl X86 { _ => unreachable!(), } } - (ScalarType::Int, ScalarType::Float) => { - assert_eq!( - vec_ty.scalar_bits, 32, - "i64 to f64 conversions do not exist until AVX-512 and require special consideration" - ); - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); + (Self::Sse2 | Self::Sse4_2, Int, Float, 32, 128, _) + | (Self::Avx2, Int, Float, 32, 128 | 256, _) => { + let target_ty = vec_ty.cast(target_scalar); let intrinsic = simple_intrinsic("cvtepi32", &target_ty); quote! { #intrinsic(a.into()).simd_into(#token) } } - (ScalarType::Unsigned, ScalarType::Float) => { - assert_eq!( - vec_ty.scalar_bits, 32, - "u64 to f64 conversions do not exist until AVX-512 and require special consideration" - ); - - let target_ty = vec_ty.reinterpret(target_scalar, target_scalar_bits); + (Self::Sse4_2, Unsigned, Float, 32, 128, _) + | (Self::Avx2, Unsigned, Float, 32, 128 | 256, _) => { + let target_ty = vec_ty.cast(target_scalar); let set1_int = set1_intrinsic(vec_ty); let set1_float = set1_intrinsic(&target_ty); let add_float = simple_intrinsic("add", &target_ty); @@ -3507,7 +3591,7 @@ impl X86 { result.simd_into(#token) } } - _ => unimplemented!(), + _ => unreachable!(), }) } diff --git a/fearless_simd_gen/src/ops.rs b/fearless_simd_gen/src/ops.rs index 5738e6cc..9233ff1d 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -1217,6 +1217,72 @@ pub(crate) const I32_TO_F32: Op = Op::new( "Convert each signed 32-bit integer element to a floating-point value.\n\n\ Values that cannot be exactly represented are rounded to the nearest representable value.", ); +pub(crate) const F64_TO_U64: Op = Op::new( + "cvt_u64", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Unsigned, + scalar_bits: 64, + precise: false, + }, + "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\n\ + Out-of-range values or NaN will produce implementation-defined results.", +); +pub(crate) const F64_TO_U64_PRECISE: Op = Op::new( + "cvt_u64_precise", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Unsigned, + scalar_bits: 64, + precise: true, + }, + "Convert each floating-point element to an unsigned 64-bit integer, truncating towards zero.\n\n\ + Out-of-range values are saturated to the closest in-range value. NaN becomes 0.", +); +pub(crate) const F64_TO_I64: Op = Op::new( + "cvt_i64", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Int, + scalar_bits: 64, + precise: false, + }, + "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\n\ + Out-of-range values or NaN will produce implementation-defined results.", +); +pub(crate) const F64_TO_I64_PRECISE: Op = Op::new( + "cvt_i64_precise", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Int, + scalar_bits: 64, + precise: true, + }, + "Convert each floating-point element to a signed 64-bit integer, truncating towards zero.\n\n\ + Out-of-range values are saturated to the closest in-range value. NaN becomes 0.", +); +pub(crate) const U64_TO_F64: Op = Op::new( + "cvt_f64", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Float, + scalar_bits: 64, + precise: false, + }, + "Convert each unsigned 64-bit integer element to a floating-point value.\n\n\ + Values that cannot be exactly represented are rounded to the nearest representable value.", +); +pub(crate) const I64_TO_F64: Op = Op::new( + "cvt_f64", + OpKind::OwnTrait, + OpSig::Cvt { + target_ty: ScalarType::Float, + scalar_bits: 64, + precise: false, + }, + "Convert each signed 64-bit integer element to a floating-point value.\n\n\ + Values that cannot be exactly represented are rounded to the nearest representable value.", +); pub(crate) fn ops_for_type(ty: &VecType) -> Vec { let base = match ty.scalar { @@ -1302,7 +1368,8 @@ pub(crate) fn ops_for_type(ty: &VecType) -> Vec { )); } - if ty.scalar == ScalarType::Float && ty.scalar_bits == 64 { + let is_f64 = ty.scalar == ScalarType::Float && ty.scalar_bits == 64; + if is_f64 { let target_ty = ty.narrowed().expect("f64 vectors support narrowing"); ops.push(Op::new( "narrow", @@ -1338,7 +1405,6 @@ pub(crate) fn ops_for_type(ty: &VecType) -> Vec { For floating-point vectors this is identical to `narrow`, including its rounding and overflow behavior.\n\n\ `{arg0}` provides the lower result lanes and `{arg1}` provides the upper result lanes.", )); - return ops; } if let Some(target_ty) = ty.widened() { @@ -1355,7 +1421,7 @@ pub(crate) fn ops_for_type(ty: &VecType) -> Vec { )); } - if let Some(target_ty) = ty.narrowed() { + if !is_f64 && let Some(target_ty) = ty.narrowed() { ops.push(Op::new( "narrow", OpKind::OwnTrait, @@ -1396,6 +1462,14 @@ pub(crate) fn ops_for_type(ty: &VecType) -> Vec { } (ScalarType::Unsigned, 32) => ops.push(U32_TO_F32), (ScalarType::Int, 32) => ops.push(I32_TO_F32), + (ScalarType::Float, 64) => { + ops.push(F64_TO_U64); + ops.push(F64_TO_U64_PRECISE); + ops.push(F64_TO_I64); + ops.push(F64_TO_I64_PRECISE); + } + (ScalarType::Unsigned, 64) => ops.push(U64_TO_F64), + (ScalarType::Int, 64) => ops.push(I64_TO_F64), _ => (), } diff --git a/fearless_simd_tests/tests/generics.rs b/fearless_simd_tests/tests/generics.rs index f2908588..861a5666 100644 --- a/fearless_simd_tests/tests/generics.rs +++ b/fearless_simd_tests/tests/generics.rs @@ -12,6 +12,30 @@ fn generic_cast(x: S::f32s) -> S::u32s { x.to_int() } +fn generic_f64_to_u64(x: S::f64s) -> S::u64s { + x.to_int() +} + +fn generic_f64_to_i64(x: S::f64s) -> S::i64s { + x.to_int() +} + +fn generic_f64_to_u64_precise(x: S::f64s) -> S::u64s { + x.to_int_precise() +} + +fn generic_f64_to_i64_precise(x: S::f64s) -> S::i64s { + x.to_int_precise() +} + +fn generic_u64_to_f64(x: S::u64s) -> S::f64s { + x.to_float() +} + +fn generic_i64_to_f64(x: S::i64s) -> S::f64s { + x.to_float() +} + // Ensure that a generic vector's byte representation is itself a same-token // byte vector whose byte representation is idempotent. fn generic_bytes>(value: V) -> V { diff --git a/fearless_simd_tests/tests/harness/ops/cvt_f64.rs b/fearless_simd_tests/tests/harness/ops/cvt_f64.rs new file mode 100644 index 00000000..acc2bf28 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/cvt_f64.rs @@ -0,0 +1,74 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +#[simd_test] +fn cvt_f64_i64x2(simd: S) { + let values = [i64::MIN, i64::MAX]; + let a = i64x2::from_slice(simd, &values); + assert_eq!(*a.to_float::>(), values.map(|x| x as f64)); +} + +#[simd_test] +fn cvt_f64_u64x2(simd: S) { + let values = [0, u64::MAX]; + let a = u64x2::from_slice(simd, &values); + assert_eq!(*a.to_float::>(), values.map(|x| x as f64)); +} + +#[simd_test] +fn cvt_f64_i64x4(simd: S) { + let values = [ + i64::MIN, + -9_007_199_254_740_993, + 9_007_199_254_740_993, + i64::MAX, + ]; + let a = i64x4::from_slice(simd, &values); + assert_eq!(*a.to_float::>(), values.map(|x| x as f64)); +} + +#[simd_test] +fn cvt_f64_u64x4(simd: S) { + let values = [0, 9_007_199_254_740_993, 1 << 63, u64::MAX]; + let a = u64x4::from_slice(simd, &values); + assert_eq!(*a.to_float::>(), values.map(|x| x as f64)); +} + +#[simd_test] +fn cvt_f64_i64x8(simd: S) { + use fearless_simd::SimdCvtFloat; + let values = [ + i64::MIN, + -9_007_199_254_740_993, + -42, + -1, + 0, + 1, + 9_007_199_254_740_993, + i64::MAX, + ]; + let a = i64x8::from_slice(simd, &values); + let result = f64x8::float_from(a); + assert_eq!(*result, values.map(|x| x as f64)); +} + +#[simd_test] +fn cvt_f64_u64x8(simd: S) { + use fearless_simd::SimdCvtFloat; + let values = [ + 0, + 1, + 42, + 1 << 53, + (1 << 53) + 1, + 1 << 63, + u64::MAX - 1, + u64::MAX, + ]; + let a = u64x8::from_slice(simd, &values); + let result = f64x8::float_from(a); + assert_eq!(*result, values.map(|x| x as f64)); +} diff --git a/fearless_simd_tests/tests/harness/ops/cvt_i64.rs b/fearless_simd_tests/tests/harness/ops/cvt_i64.rs new file mode 100644 index 00000000..8cd7e186 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/cvt_i64.rs @@ -0,0 +1,33 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +#[simd_test] +fn cvt_i64_f64x2(simd: S) { + let values = [-42.9, 42.9]; + let a = f64x2::from_slice(simd, &values); + assert_eq!(*a.to_int::>(), values.map(|x| x as i64)); +} + +#[simd_test] +fn cvt_i64_f64x4(simd: S) { + let values = [ + -0.0, + 0.0, + -9_223_372_036_854_775_808.0, + 9_223_372_036_854_774_784.0, + ]; + let a = f64x4::from_slice(simd, &values); + assert_eq!(*a.to_int::>(), values.map(|x| x as i64)); +} + +#[simd_test] +fn cvt_i64_f64x8(simd: S) { + use fearless_simd::SimdCvtTruncate; + let values = [-1234.75, -1.99, -0.99, -0.0, 0.0, 0.99, 1.99, 1234.75]; + let a = f64x8::from_slice(simd, &values); + let result = i64x8::truncate_from(a); + assert_eq!(*result, values.map(|x| x as i64)); +} diff --git a/fearless_simd_tests/tests/harness/ops/cvt_i64_precise.rs b/fearless_simd_tests/tests/harness/ops/cvt_i64_precise.rs new file mode 100644 index 00000000..14fa4687 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/cvt_i64_precise.rs @@ -0,0 +1,42 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +#[simd_test] +fn cvt_i64_precise_f64x2(simd: S) { + let values = [f64::NAN, f64::INFINITY]; + let a = f64x2::from_slice(simd, &values); + assert_eq!(*a.to_int_precise::>(), values.map(|x| x as i64)); +} + +#[simd_test] +fn cvt_i64_precise_f64x4(simd: S) { + let values = [ + f64::NEG_INFINITY, + -9_223_372_036_854_775_808.0, + 9_223_372_036_854_774_784.0, + 9_223_372_036_854_775_808.0, + ]; + let a = f64x4::from_slice(simd, &values); + assert_eq!(*a.to_int_precise::>(), values.map(|x| x as i64)); +} + +#[simd_test] +fn cvt_i64_precise_f64x8(simd: S) { + use fearless_simd::SimdCvtTruncate; + let values = [ + f64::NAN, + f64::NEG_INFINITY, + f64::INFINITY, + -1e300, + 1e300, + -42.9, + -0.0, + 42.9, + ]; + let a = f64x8::from_slice(simd, &values); + let result = i64x8::truncate_from_precise(a); + assert_eq!(*result, values.map(|x| x as i64)); +} diff --git a/fearless_simd_tests/tests/harness/ops/cvt_u64.rs b/fearless_simd_tests/tests/harness/ops/cvt_u64.rs new file mode 100644 index 00000000..1ec82910 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/cvt_u64.rs @@ -0,0 +1,42 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +#[simd_test] +fn cvt_u64_f64x2(simd: S) { + let values = [0.0, 42.9]; + let a = f64x2::from_slice(simd, &values); + assert_eq!(*a.to_int::>(), values.map(|x| x as u64)); +} + +#[simd_test] +fn cvt_u64_f64x4(simd: S) { + let values = [ + -0.0, + 0.99, + 9_223_372_036_854_775_808.0, + 18_446_744_073_709_549_568.0, + ]; + let a = f64x4::from_slice(simd, &values); + assert_eq!(*a.to_int::>(), values.map(|x| x as u64)); +} + +#[simd_test] +fn cvt_u64_f64x8(simd: S) { + use fearless_simd::SimdCvtTruncate; + let values = [ + 0.0, + -0.0, + 0.99, + 1.99, + 42.9, + 1e9, + 1e15, + 18_446_744_073_709_549_568.0, + ]; + let a = f64x8::from_slice(simd, &values); + let result = u64x8::truncate_from(a); + assert_eq!(*result, values.map(|x| x as u64)); +} diff --git a/fearless_simd_tests/tests/harness/ops/cvt_u64_precise.rs b/fearless_simd_tests/tests/harness/ops/cvt_u64_precise.rs new file mode 100644 index 00000000..71dd6b80 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/cvt_u64_precise.rs @@ -0,0 +1,42 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +#[simd_test] +fn cvt_u64_precise_f64x2(simd: S) { + let values = [f64::NAN, f64::INFINITY]; + let a = f64x2::from_slice(simd, &values); + assert_eq!(*a.to_int_precise::>(), values.map(|x| x as u64)); +} + +#[simd_test] +fn cvt_u64_precise_f64x4(simd: S) { + let values = [ + f64::NEG_INFINITY, + -1.0, + 18_446_744_073_709_549_568.0, + 18_446_744_073_709_551_616.0, + ]; + let a = f64x4::from_slice(simd, &values); + assert_eq!(*a.to_int_precise::>(), values.map(|x| x as u64)); +} + +#[simd_test] +fn cvt_u64_precise_f64x8(simd: S) { + use fearless_simd::SimdCvtTruncate; + let values = [ + f64::NAN, + f64::NEG_INFINITY, + f64::INFINITY, + -1e300, + 1e300, + -0.0, + 42.9, + 18_446_744_073_709_549_568.0, + ]; + let a = f64x8::from_slice(simd, &values); + let result = u64x8::truncate_from_precise(a); + assert_eq!(*result, values.map(|x| x as u64)); +} diff --git a/fearless_simd_tests/tests/harness/ops/mod.rs b/fearless_simd_tests/tests/harness/ops/mod.rs index ec845fdd..d0837b94 100644 --- a/fearless_simd_tests/tests/harness/ops/mod.rs +++ b/fearless_simd_tests/tests/harness/ops/mod.rs @@ -20,10 +20,15 @@ mod ceil; mod combine; mod copysign; mod cvt_f32; +mod cvt_f64; mod cvt_i32; mod cvt_i32_precise; +mod cvt_i64; +mod cvt_i64_precise; mod cvt_u32; mod cvt_u32_precise; +mod cvt_u64; +mod cvt_u64_precise; mod deinterleave; mod div; mod floor;