From 38db25d011a65dfbfc9c876df2725fadbd778887 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 18:10:52 +0100 Subject: [PATCH 1/9] Initial pass at the saturating add. The codegen is good but the generator code could use tidying up. --- CHANGELOG.md | 1 + fearless_simd/src/generated/avx2.rs | 222 ++++++++++++++ fearless_simd/src/generated/avx512.rs | 298 +++++++++++++++++++ fearless_simd/src/generated/fallback.rs | 108 +++++++ fearless_simd/src/generated/neon.rs | 80 +++++ fearless_simd/src/generated/simd_trait.rs | 178 +++++++++++ fearless_simd/src/generated/simd_types.rs | 120 ++++++++ fearless_simd/src/generated/sse2.rs | 76 +++++ fearless_simd/src/generated/sse4_2.rs | 110 +++++++ fearless_simd/src/generated/wasm.rs | 51 ++++ fearless_simd_gen/src/arch/fallback.rs | 1 + fearless_simd_gen/src/arch/neon.rs | 1 + fearless_simd_gen/src/arch/wasm.rs | 1 + fearless_simd_gen/src/mk_fallback.rs | 1 + fearless_simd_gen/src/mk_wasm.rs | 88 ++++++ fearless_simd_gen/src/mk_x86.rs | 183 ++++++++++++ fearless_simd_gen/src/ops.rs | 6 + fearless_simd_tests/tests/generics.rs | 5 + fearless_simd_tests/tests/harness/ops/mod.rs | 1 + 19 files changed, 1531 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f0e1879..fd7cc5584 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ You can find its changes [documented below](#070-2026-08-11). ### Added - Added `reverse` for all SIMD vector and mask types. +- Added lane-wise `saturating_add` for all integer vector types and backends. - Added lane-wise `count_ones` and `count_zeros` operations for all integer vector types and backends. - Added `mul_add_precise` and `mul_sub_precise` for floating-point vectors. They guarantee the infinite-precision product-plus-add rounded once, including on SIMD levels without hardware fused multiply-add instructions. They are not susceptible to the [bug](https://github.com/rust-lang/compiler-builtins/issues/1262) in Rust standard library, `std::simd` and musl libc that causes incorrect rounding for subnormal results. SSE4.2 gets SIMD emulation of these operations for better performance. ([#323][], [#324][] by [@Shnatsel][]) - Documented the storage representation of the SIMD vector types. The documented representation will not change without a semver major version change. diff --git a/fearless_simd/src/generated/avx2.rs b/fearless_simd/src/generated/avx2.rs index 1f2328d81..6178db6d5 100644 --- a/fearless_simd/src/generated/avx2.rs +++ b/fearless_simd/src/generated/avx2.rs @@ -783,6 +783,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i8x16, b: i8x16) -> i8x16 { + _mm_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1351,6 +1361,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u8x16, b: u8x16) -> u8x16 { + _mm_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2041,6 +2061,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i16x8, b: i16x8) -> i16x8 { + _mm_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2554,6 +2584,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u16x8, b: u16x8) -> u16x8 { + _mm_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3248,6 +3288,26 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, sum), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(sum), _mm_set1_epi32(i32::MIN)); + let result = _mm_blendv_ps( + _mm_castsi128_ps(sum), + _mm_castsi128_ps(bound), + _mm_castsi128_ps(overflow), + ); + _mm_castps_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3738,6 +3798,19 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + let threshold = _mm_xor_si128(b, _mm_set1_epi32(-1)); + _mm_add_epi32(_mm_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -5001,6 +5074,26 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, sum), b); + let bound = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); + let result = _mm_blendv_pd( + _mm_castsi128_pd(sum), + _mm_castsi128_pd(bound), + _mm_castsi128_pd(overflow), + ); + _mm_castpd_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5453,6 +5546,22 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi64(a, b); + let sign_bias = _mm_set1_epi64x(i64::MIN); + let overflow = + _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(sum, sign_bias)); + _mm_or_si128(sum, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6595,6 +6704,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i8x32, b: i8x32) -> i8x32 { + _mm256_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { crate::kernel!( #[inline(always)] @@ -7137,6 +7256,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u8x32, b: u8x32) -> u8x32 { + _mm256_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { crate::kernel!( #[inline(always)] @@ -7803,6 +7932,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i16x16, b: i16x16) -> i16x16 { + _mm256_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { crate::kernel!( #[inline(always)] @@ -8276,6 +8415,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u16x16, b: u16x16) -> u16x16 { + _mm256_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { crate::kernel!( #[inline(always)] @@ -8946,6 +9095,27 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i32x8, b: i32x8) -> i32x8 { + let a = a.into(); + let b = b.into(); + let sum = _mm256_add_epi32(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi32(a, sum), b); + let bound = + _mm256_xor_si256(_mm256_srai_epi32::<31>(sum), _mm256_set1_epi32(i32::MIN)); + let result = _mm256_blendv_ps( + _mm256_castsi256_ps(sum), + _mm256_castsi256_ps(bound), + _mm256_castsi256_ps(overflow), + ); + _mm256_castps_si256(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -9389,6 +9559,19 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u32x8, b: u32x8) -> u32x8 { + let a = a.into(); + let b = b.into(); + let threshold = _mm256_xor_si256(b, _mm256_set1_epi32(-1)); + _mm256_add_epi32(_mm256_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { crate::kernel!( #[inline(always)] @@ -10562,6 +10745,27 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i64x4, b: i64x4) -> i64x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm256_add_epi64(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi64(a, sum), b); + let bound = + _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); + let result = _mm256_blendv_pd( + _mm256_castsi256_pd(sum), + _mm256_castsi256_pd(bound), + _mm256_castsi256_pd(overflow), + ); + _mm256_castpd_si256(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { crate::kernel!( #[inline(always)] @@ -10991,6 +11195,24 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u64x4, b: u64x4) -> u64x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm256_add_epi64(a, b); + let sign_bias = _mm256_set1_epi64x(i64::MIN); + let overflow = _mm256_cmpgt_epi64( + _mm256_xor_si256(a, sign_bias), + _mm256_xor_si256(sum, sign_bias), + ); + _mm256_or_si256(sum, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/avx512.rs b/fearless_simd/src/generated/avx512.rs index 348c9fb2f..7f3c12290 100644 --- a/fearless_simd/src/generated/avx512.rs +++ b/fearless_simd/src/generated/avx512.rs @@ -993,6 +993,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x16, b: i8x16) -> i8x16 { + _mm_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1544,6 +1554,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x16, b: u8x16) -> u8x16 { + _mm_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2152,6 +2172,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x8, b: i16x8) -> i16x8 { + _mm_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2633,6 +2663,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x8, b: u16x8) -> u16x8 { + _mm_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3221,6 +3261,22 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi32(a, b); + let overflow_bits = _mm_ternarylogic_epi32::<0x42>(a, b, sum); + let overflow_mask = _mm_srai_epi32::<31>(overflow_bits); + let direction = _mm_add_epi32(_mm_srli_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); + _mm_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3691,6 +3747,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + let threshold = _mm_xor_si128(b, _mm_set1_epi32(-1)); + _mm_add_epi32(_mm_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -4807,6 +4876,22 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi64(a, b); + let overflow_bits = _mm_ternarylogic_epi64::<0x42>(a, b, sum); + let overflow_mask = _mm_srai_epi64::<63>(overflow_bits); + let direction = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); + _mm_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5242,6 +5327,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + let threshold = _mm_xor_si128(b, _mm_set1_epi64x(-1)); + _mm_add_epi64(_mm_min_epu64(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6358,6 +6456,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x32, b: i8x32) -> i8x32 { + _mm256_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { crate::kernel!( #[inline(always)] @@ -6901,6 +7009,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x32, b: u8x32) -> u8x32 { + _mm256_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { crate::kernel!( #[inline(always)] @@ -7515,6 +7633,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x16, b: i16x16) -> i16x16 { + _mm256_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { crate::kernel!( #[inline(always)] @@ -7985,6 +8113,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x16, b: u16x16) -> u16x16 { + _mm256_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { crate::kernel!( #[inline(always)] @@ -8576,6 +8714,23 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x8, b: i32x8) -> i32x8 { + let a = a.into(); + let b = b.into(); + let sum = _mm256_add_epi32(a, b); + let overflow_bits = _mm256_ternarylogic_epi32::<0x42>(a, b, sum); + let overflow_mask = _mm256_srai_epi32::<31>(overflow_bits); + let direction = + _mm256_add_epi32(_mm256_srli_epi32::<31>(a), _mm256_set1_epi32(i32::MAX)); + _mm256_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -9034,6 +9189,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x8, b: u32x8) -> u32x8 { + let a = a.into(); + let b = b.into(); + let threshold = _mm256_xor_si256(b, _mm256_set1_epi32(-1)); + _mm256_add_epi32(_mm256_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { crate::kernel!( #[inline(always)] @@ -10135,6 +10303,23 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x4, b: i64x4) -> i64x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm256_add_epi64(a, b); + let overflow_bits = _mm256_ternarylogic_epi64::<0x42>(a, b, sum); + let overflow_mask = _mm256_srai_epi64::<63>(overflow_bits); + let direction = + _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); + _mm256_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { crate::kernel!( #[inline(always)] @@ -10563,6 +10748,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x4, b: u64x4) -> u64x4 { + let a = a.into(); + let b = b.into(); + let threshold = _mm256_xor_si256(b, _mm256_set1_epi64x(-1)); + _mm256_add_epi64(_mm256_min_epu64(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { crate::kernel!( #[inline(always)] @@ -11674,6 +11872,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x64, b: i8x64) -> i8x64 { + _mm512_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { crate::kernel!( #[inline(always)] @@ -12227,6 +12435,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x64, b: u8x64) -> u8x64 { + _mm512_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { crate::kernel!( #[inline(always)] @@ -12843,6 +13061,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x32, b: i16x32) -> i16x32 { + _mm512_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { crate::kernel!( #[inline(always)] @@ -13324,6 +13552,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x32, b: u16x32) -> u16x32 { + _mm512_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { crate::kernel!( #[inline(always)] @@ -13918,6 +14156,23 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x16, b: i32x16) -> i32x16 { + let a = a.into(); + let b = b.into(); + let sum = _mm512_add_epi32(a, b); + let overflow_bits = _mm512_ternarylogic_epi32::<0x42>(a, b, sum); + let overflow_mask = _mm512_srai_epi32::<31>(overflow_bits); + let direction = + _mm512_add_epi32(_mm512_srli_epi32::<31>(a), _mm512_set1_epi32(i32::MAX)); + _mm512_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { crate::kernel!( #[inline(always)] @@ -14391,6 +14646,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x16, b: u32x16) -> u32x16 { + let a = a.into(); + let b = b.into(); + let threshold = _mm512_xor_si512(b, _mm512_set1_epi32(-1)); + _mm512_add_epi32(_mm512_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { crate::kernel!( #[inline(always)] @@ -15511,6 +15779,23 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x8, b: i64x8) -> i64x8 { + let a = a.into(); + let b = b.into(); + let sum = _mm512_add_epi64(a, b); + let overflow_bits = _mm512_ternarylogic_epi64::<0x42>(a, b, sum); + let overflow_mask = _mm512_srai_epi64::<63>(overflow_bits); + let direction = + _mm512_add_epi64(_mm512_srli_epi64::<63>(a), _mm512_set1_epi64(i64::MAX)); + _mm512_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { crate::kernel!( #[inline(always)] @@ -15948,6 +16233,19 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x8, b: u64x8) -> u64x8 { + let a = a.into(); + let b = b.into(); + let threshold = _mm512_xor_si512(b, _mm512_set1_epi64(-1)); + _mm512_add_epi64(_mm512_min_epu64(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/fallback.rs b/fearless_simd/src/generated/fallback.rs index 3e6766077..c63844ff2 100644 --- a/fearless_simd/src/generated/fallback.rs +++ b/fearless_simd/src/generated/fallback.rs @@ -705,6 +705,28 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + [ + i8::saturating_add(a[0usize], b[0usize]), + i8::saturating_add(a[1usize], b[1usize]), + i8::saturating_add(a[2usize], b[2usize]), + i8::saturating_add(a[3usize], b[3usize]), + i8::saturating_add(a[4usize], b[4usize]), + i8::saturating_add(a[5usize], b[5usize]), + i8::saturating_add(a[6usize], b[6usize]), + i8::saturating_add(a[7usize], b[7usize]), + i8::saturating_add(a[8usize], b[8usize]), + i8::saturating_add(a[9usize], b[9usize]), + i8::saturating_add(a[10usize], b[10usize]), + i8::saturating_add(a[11usize], b[11usize]), + i8::saturating_add(a[12usize], b[12usize]), + i8::saturating_add(a[13usize], b[13usize]), + i8::saturating_add(a[14usize], b[14usize]), + i8::saturating_add(a[15usize], b[15usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { [ i8::wrapping_sub(a[0usize], b[0usize]), @@ -1630,6 +1652,28 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + [ + u8::saturating_add(a[0usize], b[0usize]), + u8::saturating_add(a[1usize], b[1usize]), + u8::saturating_add(a[2usize], b[2usize]), + u8::saturating_add(a[3usize], b[3usize]), + u8::saturating_add(a[4usize], b[4usize]), + u8::saturating_add(a[5usize], b[5usize]), + u8::saturating_add(a[6usize], b[6usize]), + u8::saturating_add(a[7usize], b[7usize]), + u8::saturating_add(a[8usize], b[8usize]), + u8::saturating_add(a[9usize], b[9usize]), + u8::saturating_add(a[10usize], b[10usize]), + u8::saturating_add(a[11usize], b[11usize]), + u8::saturating_add(a[12usize], b[12usize]), + u8::saturating_add(a[13usize], b[13usize]), + u8::saturating_add(a[14usize], b[14usize]), + u8::saturating_add(a[15usize], b[15usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { [ u8::wrapping_sub(a[0usize], b[0usize]), @@ -2769,6 +2813,20 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + [ + i16::saturating_add(a[0usize], b[0usize]), + i16::saturating_add(a[1usize], b[1usize]), + i16::saturating_add(a[2usize], b[2usize]), + i16::saturating_add(a[3usize], b[3usize]), + i16::saturating_add(a[4usize], b[4usize]), + i16::saturating_add(a[5usize], b[5usize]), + i16::saturating_add(a[6usize], b[6usize]), + i16::saturating_add(a[7usize], b[7usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { [ i16::wrapping_sub(a[0usize], b[0usize]), @@ -3351,6 +3409,20 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + [ + u16::saturating_add(a[0usize], b[0usize]), + u16::saturating_add(a[1usize], b[1usize]), + u16::saturating_add(a[2usize], b[2usize]), + u16::saturating_add(a[3usize], b[3usize]), + u16::saturating_add(a[4usize], b[4usize]), + u16::saturating_add(a[5usize], b[5usize]), + u16::saturating_add(a[6usize], b[6usize]), + u16::saturating_add(a[7usize], b[7usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { [ u16::wrapping_sub(a[0usize], b[0usize]), @@ -4133,6 +4205,16 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + [ + i32::saturating_add(a[0usize], b[0usize]), + i32::saturating_add(a[1usize], b[1usize]), + i32::saturating_add(a[2usize], b[2usize]), + i32::saturating_add(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { [ i32::wrapping_sub(a[0usize], b[0usize]), @@ -4512,6 +4594,16 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + [ + u32::saturating_add(a[0usize], b[0usize]), + u32::saturating_add(a[1usize], b[1usize]), + u32::saturating_add(a[2usize], b[2usize]), + u32::saturating_add(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { [ u32::wrapping_sub(a[0usize], b[0usize]), @@ -5337,6 +5429,14 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + [ + i64::saturating_add(a[0usize], b[0usize]), + i64::saturating_add(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_sub(a[0usize], b[0usize]), @@ -5619,6 +5719,14 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + [ + u64::saturating_add(a[0usize], b[0usize]), + u64::saturating_add(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_sub(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/neon.rs b/fearless_simd/src/generated/neon.rs index 096cbef2b..00b378119 100644 --- a/fearless_simd/src/generated/neon.rs +++ b/fearless_simd/src/generated/neon.rs @@ -625,6 +625,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i8x16, b: i8x16) -> i8x16 { + vqaddq_s8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1018,6 +1028,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u8x16, b: u8x16) -> u8x16 { + vqaddq_u8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -1558,6 +1578,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i16x8, b: i16x8) -> i16x8 { + vqaddq_s16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -1950,6 +1980,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u16x8, b: u16x8) -> u16x8 { + vqaddq_u16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -2515,6 +2555,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i32x4, b: i32x4) -> i32x4 { + vqaddq_s32(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -2917,6 +2967,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u32x4, b: u32x4) -> u32x4 { + vqaddq_u32(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -3956,6 +4016,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i64x2, b: i64x2) -> i64x2 { + vqaddq_s64(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -4332,6 +4402,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u64x2, b: u64x2) -> u64x2 { + vqaddq_u64(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/simd_trait.rs b/fearless_simd/src/generated/simd_trait.rs index f1ce7214f..f4352b4e5 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -385,6 +385,8 @@ pub trait Simd: fn count_zeros_i8x16(self, a: i8x16) -> i8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -488,6 +490,8 @@ pub trait Simd: fn count_zeros_u8x16(self, a: u8x16) -> u8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -635,6 +639,8 @@ pub trait Simd: fn count_zeros_i16x8(self, a: i16x8) -> i16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -753,6 +759,8 @@ pub trait Simd: fn count_zeros_u16x8(self, a: u16x8) -> u16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -906,6 +914,8 @@ pub trait Simd: fn count_zeros_i32x4(self, a: i32x4) -> i32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -1026,6 +1036,8 @@ pub trait Simd: fn count_zeros_u32x4(self, a: u32x4) -> u32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -1321,6 +1333,8 @@ pub trait Simd: fn count_zeros_i64x2(self, a: i64x2) -> i64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -1439,6 +1453,8 @@ pub trait Simd: fn count_zeros_u64x2(self, a: u64x2) -> u64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] @@ -2001,6 +2017,16 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.add_i8x16(a0, b0), self.add_i8x16(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + let (a0, a1) = self.split_i8x32(a); + let (b0, b1) = self.split_i8x32(b); + self.combine_i8x16( + self.saturating_add_i8x16(a0, b0), + self.saturating_add_i8x16(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { @@ -2274,6 +2300,16 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.add_u8x16(a0, b0), self.add_u8x16(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + let (a0, a1) = self.split_u8x32(a); + let (b0, b1) = self.split_u8x32(b); + self.combine_u8x16( + self.saturating_add_u8x16(a0, b0), + self.saturating_add_u8x16(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { @@ -2653,6 +2689,16 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.add_i16x8(a0, b0), self.add_i16x8(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + let (a0, a1) = self.split_i16x16(a); + let (b0, b1) = self.split_i16x16(b); + self.combine_i16x8( + self.saturating_add_i16x8(a0, b0), + self.saturating_add_i16x8(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { @@ -2958,6 +3004,16 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.add_u16x8(a0, b0), self.add_u16x8(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + let (a0, a1) = self.split_u16x16(a); + let (b0, b1) = self.split_u16x16(b); + self.combine_u16x8( + self.saturating_add_u16x8(a0, b0), + self.saturating_add_u16x8(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { @@ -3360,6 +3416,16 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.add_i32x4(a0, b0), self.add_i32x4(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + let (a0, a1) = self.split_i32x8(a); + let (b0, b1) = self.split_i32x8(b); + self.combine_i32x4( + self.saturating_add_i32x4(a0, b0), + self.saturating_add_i32x4(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { @@ -3667,6 +3733,16 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.add_u32x4(a0, b0), self.add_u32x4(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + let (a0, a1) = self.split_u32x8(a); + let (b0, b1) = self.split_u32x8(b); + self.combine_u32x4( + self.saturating_add_u32x4(a0, b0), + self.saturating_add_u32x4(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { @@ -4477,6 +4553,16 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.add_i64x2(a0, b0), self.add_i64x2(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + let (a0, a1) = self.split_i64x4(a); + let (b0, b1) = self.split_i64x4(b); + self.combine_i64x2( + self.saturating_add_i64x2(a0, b0), + self.saturating_add_i64x2(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { @@ -4776,6 +4862,16 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.add_u64x2(a0, b0), self.add_u64x2(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + let (a0, a1) = self.split_u64x4(a); + let (b0, b1) = self.split_u64x4(b); + self.combine_u64x2( + self.saturating_add_u64x2(a0, b0), + self.saturating_add_u64x2(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { @@ -5571,6 +5667,16 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.add_i8x32(a0, b0), self.add_i8x32(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { + let (a0, a1) = self.split_i8x64(a); + let (b0, b1) = self.split_i8x64(b); + self.combine_i8x32( + self.saturating_add_i8x32(a0, b0), + self.saturating_add_i8x32(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { @@ -5842,6 +5948,16 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.add_u8x32(a0, b0), self.add_u8x32(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { + let (a0, a1) = self.split_u8x64(a); + let (b0, b1) = self.split_u8x64(b); + self.combine_u8x32( + self.saturating_add_u8x32(a0, b0), + self.saturating_add_u8x32(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { @@ -6217,6 +6333,16 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.add_i16x16(a0, b0), self.add_i16x16(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { + let (a0, a1) = self.split_i16x32(a); + let (b0, b1) = self.split_i16x32(b); + self.combine_i16x16( + self.saturating_add_i16x16(a0, b0), + self.saturating_add_i16x16(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { @@ -6526,6 +6652,16 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.add_u16x16(a0, b0), self.add_u16x16(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { + let (a0, a1) = self.split_u16x32(a); + let (b0, b1) = self.split_u16x32(b); + self.combine_u16x16( + self.saturating_add_u16x16(a0, b0), + self.saturating_add_u16x16(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { @@ -6937,6 +7073,16 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.add_i32x8(a0, b0), self.add_i32x8(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { + let (a0, a1) = self.split_i32x16(a); + let (b0, b1) = self.split_i32x16(b); + self.combine_i32x8( + self.saturating_add_i32x8(a0, b0), + self.saturating_add_i32x8(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { @@ -7246,6 +7392,16 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.add_u32x8(a0, b0), self.add_u32x8(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { + let (a0, a1) = self.split_u32x16(a); + let (b0, b1) = self.split_u32x16(b); + self.combine_u32x8( + self.saturating_add_u32x8(a0, b0), + self.saturating_add_u32x8(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { @@ -8050,6 +8206,16 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.add_i64x4(a0, b0), self.add_i64x4(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { + let (a0, a1) = self.split_i64x8(a); + let (b0, b1) = self.split_i64x8(b); + self.combine_i64x4( + self.saturating_add_i64x4(a0, b0), + self.saturating_add_i64x4(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { @@ -8347,6 +8513,16 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.add_u64x4(a0, b0), self.add_u64x4(a1, b1)) } + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_add_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { + let (a0, a1) = self.split_u64x8(a); + let (b0, b1) = self.split_u64x8(b); + self.combine_u64x4( + self.saturating_add_u64x4(a0, b0), + self.saturating_add_u64x4(a1, b1), + ) + } #[doc = "Subtract two vectors element-wise, wrapping on overflow."] #[inline(always)] fn sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { @@ -9385,6 +9561,8 @@ pub trait SimdInt: fn count_ones(self) -> Self; #[doc = "Return the number of zeros in the binary representation of each element."] fn count_zeros(self) -> Self; + #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_add(self, rhs: impl SimdInto) -> Self; } #[doc = r" Functionality implemented by SIMD masks."] #[doc = r""] diff --git a/fearless_simd/src/generated/simd_types.rs b/fearless_simd/src/generated/simd_types.rs index 3c69e51ae..74e3ea52a 100644 --- a/fearless_simd/src/generated/simd_types.rs +++ b/fearless_simd/src/generated/simd_types.rs @@ -598,6 +598,11 @@ impl crate::SimdInt for i8x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_i8x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i8x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x16 { type Widened = i16x8; @@ -886,6 +891,11 @@ impl crate::SimdInt for u8x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_u8x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u8x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x16 { type Widened = u16x8; @@ -1264,6 +1274,11 @@ impl crate::SimdInt for i16x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_i16x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i16x8(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x8 { type Widened = i32x4; @@ -1559,6 +1574,11 @@ impl crate::SimdInt for u16x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_u16x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u16x8(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x8 { type Widened = u32x4; @@ -1940,6 +1960,11 @@ impl crate::SimdInt for i32x4 { fn count_zeros(self) -> Self { self.simd.count_zeros_i32x4(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i32x4(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x4 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -2235,6 +2260,11 @@ impl crate::SimdInt for u32x4 { fn count_zeros(self) -> Self { self.simd.count_zeros_u32x4(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u32x4(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x4 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -2966,6 +2996,11 @@ impl crate::SimdInt for i64x2 { fn count_zeros(self) -> Self { self.simd.count_zeros_i64x2(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] @@ -3254,6 +3289,11 @@ impl crate::SimdInt for u64x2 { fn count_zeros(self) -> Self { self.simd.count_zeros_u64x2(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] @@ -4013,6 +4053,11 @@ impl crate::SimdInt for i8x32 { fn count_zeros(self) -> Self { self.simd.count_zeros_i8x32(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i8x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x32 { type Widened = i16x16; @@ -4312,6 +4357,11 @@ impl crate::SimdInt for u8x32 { fn count_zeros(self) -> Self { self.simd.count_zeros_u8x32(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u8x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x32 { type Widened = u16x16; @@ -4694,6 +4744,11 @@ impl crate::SimdInt for i16x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_i16x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i16x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x16 { type Widened = i32x8; @@ -4993,6 +5048,11 @@ impl crate::SimdInt for u16x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_u16x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u16x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x16 { type Widened = u32x8; @@ -5381,6 +5441,11 @@ impl crate::SimdInt for i32x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_i32x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i32x8(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x8 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -5683,6 +5748,11 @@ impl crate::SimdInt for u32x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_u32x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u32x8(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x8 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -6404,6 +6474,11 @@ impl crate::SimdInt for i64x4 { fn count_zeros(self) -> Self { self.simd.count_zeros_i64x4(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] @@ -6687,6 +6762,11 @@ impl crate::SimdInt for u64x4 { fn count_zeros(self) -> Self { self.simd.count_zeros_u64x4(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] @@ -7477,6 +7557,11 @@ impl crate::SimdInt for i8x64 { fn count_zeros(self) -> Self { self.simd.count_zeros_i8x64(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i8x64(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x64 { type Widened = i16x32; @@ -7802,6 +7887,11 @@ impl crate::SimdInt for u8x64 { fn count_zeros(self) -> Self { self.simd.count_zeros_u8x64(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u8x64(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x64 { type Widened = u16x32; @@ -8194,6 +8284,11 @@ impl crate::SimdInt for i16x32 { fn count_zeros(self) -> Self { self.simd.count_zeros_i16x32(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i16x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x32 { type Widened = i32x16; @@ -8503,6 +8598,11 @@ impl crate::SimdInt for u16x32 { fn count_zeros(self) -> Self { self.simd.count_zeros_u16x32(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u16x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x32 { type Widened = u32x16; @@ -8894,6 +8994,11 @@ impl crate::SimdInt for i32x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_i32x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_i32x16(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x16 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -9199,6 +9304,11 @@ impl crate::SimdInt for u32x16 { fn count_zeros(self) -> Self { self.simd.count_zeros_u32x16(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_u32x16(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x16 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -9932,6 +10042,11 @@ impl crate::SimdInt for i64x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_i64x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] @@ -10221,6 +10336,11 @@ impl crate::SimdInt for u64x8 { fn count_zeros(self) -> Self { self.simd.count_zeros_u64x8(self) } + #[inline(always)] + fn saturating_add(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_add_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."] diff --git a/fearless_simd/src/generated/sse2.rs b/fearless_simd/src/generated/sse2.rs index ed0c46c26..b87f36794 100644 --- a/fearless_simd/src/generated/sse2.rs +++ b/fearless_simd/src/generated/sse2.rs @@ -886,6 +886,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: i8x16, b: i8x16) -> i8x16 { + _mm_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1644,6 +1654,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: u8x16, b: u8x16) -> u8x16 { + _mm_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2400,6 +2420,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: i16x8, b: i16x8) -> i16x8 { + _mm_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2898,6 +2928,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: u16x8, b: u16x8) -> u16x8 { + _mm_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3665,6 +3705,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + [ + i32::saturating_add(a[0usize], b[0usize]), + i32::saturating_add(a[1usize], b[1usize]), + i32::saturating_add(a[2usize], b[2usize]), + i32::saturating_add(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -4194,6 +4244,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + [ + u32::saturating_add(a[0usize], b[0usize]), + u32::saturating_add(a[1usize], b[1usize]), + u32::saturating_add(a[2usize], b[2usize]), + u32::saturating_add(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -5449,6 +5509,14 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + [ + i64::saturating_add(a[0usize], b[0usize]), + i64::saturating_add(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5867,6 +5935,14 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + [ + u64::saturating_add(a[0usize], b[0usize]), + u64::saturating_add(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/sse4_2.rs b/fearless_simd/src/generated/sse4_2.rs index 21870c53b..184b10d4c 100644 --- a/fearless_simd/src/generated/sse4_2.rs +++ b/fearless_simd/src/generated/sse4_2.rs @@ -933,6 +933,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i8x16, b: i8x16) -> i8x16 { + _mm_adds_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1506,6 +1516,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u8x16, b: u8x16) -> u8x16 { + _mm_adds_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2198,6 +2218,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i16x8, b: i16x8) -> i16x8 { + _mm_adds_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2712,6 +2742,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u16x8, b: u16x8) -> u16x8 { + _mm_adds_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3404,6 +3444,26 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, sum), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(sum), _mm_set1_epi32(i32::MIN)); + let result = _mm_blendv_ps( + _mm_castsi128_ps(sum), + _mm_castsi128_ps(bound), + _mm_castsi128_ps(overflow), + ); + _mm_castps_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3891,6 +3951,19 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + let threshold = _mm_xor_si128(b, _mm_set1_epi32(-1)); + _mm_add_epi32(_mm_min_epu32(a, threshold), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -5173,6 +5246,27 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, sum), b); + let sum_sign = _mm_srai_epi32::<31>(_mm_shuffle_epi32::<0xf5>(sum)); + let bound = _mm_xor_si128(sum_sign, _mm_set1_epi64x(i64::MIN)); + let result = _mm_blendv_pd( + _mm_castsi128_pd(sum), + _mm_castsi128_pd(bound), + _mm_castsi128_pd(overflow), + ); + _mm_castpd_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5623,6 +5717,22 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + let sum = _mm_add_epi64(a, b); + let sign_bias = _mm_set1_epi64x(i64::MIN); + let overflow = + _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(sum, sign_bias)); + _mm_or_si128(sum, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/wasm.rs b/fearless_simd/src/generated/wasm.rs index ba07c50e9..ad41737a0 100644 --- a/fearless_simd/src/generated/wasm.rs +++ b/fearless_simd/src/generated/wasm.rs @@ -572,6 +572,10 @@ impl Simd for WasmSimd128 { i8x16_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + i8x16_add_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { i8x16_sub(a.into(), b.into()).simd_into(self) } @@ -954,6 +958,10 @@ impl Simd for WasmSimd128 { u8x16_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + u8x16_add_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { u8x16_sub(a.into(), b.into()).simd_into(self) } @@ -1405,6 +1413,10 @@ impl Simd for WasmSimd128 { i16x8_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + i16x8_add_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { i16x8_sub(a.into(), b.into()).simd_into(self) } @@ -1681,6 +1693,10 @@ impl Simd for WasmSimd128 { u16x8_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + u16x8_add_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { u16x8_sub(a.into(), b.into()).simd_into(self) } @@ -2053,6 +2069,16 @@ impl Simd for WasmSimd128 { i32x4_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let sum = i32x4_add(a, b); + let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); + let overflow_mask = i32x4_shr(overflow_bits, 31); + let saturation = v128_xor(i32x4_shr(sum, 31), i32x4_splat(i32::MIN)); + v128_bitselect(saturation, sum, overflow_mask).simd_into(self) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { i32x4_sub(a.into(), b.into()).simd_into(self) } @@ -2315,6 +2341,12 @@ impl Simd for WasmSimd128 { u32x4_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + let a: v128 = a.into(); + let b: v128 = b.into(); + u32x4_add(u32x4_min(a, v128_not(b)), b).simd_into(self) + } + #[inline(always)] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { u32x4_sub(a.into(), b.into()).simd_into(self) } @@ -2990,6 +3022,16 @@ impl Simd for WasmSimd128 { i64x2_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let sum = i64x2_add(a, b); + let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); + let overflow_mask = i64x2_shr(overflow_bits, 63); + let saturation = v128_xor(i64x2_shr(sum, 63), i64x2_splat(i64::MIN)); + v128_bitselect(saturation, sum, overflow_mask).simd_into(self) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { i64x2_sub(a.into(), b.into()).simd_into(self) } @@ -3247,6 +3289,15 @@ impl Simd for WasmSimd128 { u64x2_add(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let sum = u64x2_add(a, b); + let sign_bit = i64x2_splat(i64::MIN); + let overflow_mask = i64x2_gt(v128_xor(a, sign_bit), v128_xor(sum, sign_bit)); + v128_or(sum, overflow_mask).simd_into(self) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { u64x2_sub(a.into(), b.into()).simd_into(self) } diff --git a/fearless_simd_gen/src/arch/fallback.rs b/fearless_simd_gen/src/arch/fallback.rs index 36d6d72e4..dcba5e1d1 100644 --- a/fearless_simd_gen/src/arch/fallback.rs +++ b/fearless_simd_gen/src/arch/fallback.rs @@ -29,6 +29,7 @@ pub(crate) fn translate_op(op: &str, is_float: bool) -> Option<&'static str> { "wrapping_add" } } + "saturating_add" => "saturating_add", "sub" => { if is_float { "sub" diff --git a/fearless_simd_gen/src/arch/neon.rs b/fearless_simd_gen/src/arch/neon.rs index aa7d23e41..0bceb0199 100644 --- a/fearless_simd_gen/src/arch/neon.rs +++ b/fearless_simd_gen/src/arch/neon.rs @@ -15,6 +15,7 @@ fn translate_op(op: &str) -> Option<&'static str> { "trunc" => "vrnd", "sqrt" => "vsqrt", "add" => "vadd", + "saturating_add" => "vqadd", "sub" => "vsub", "mul" => "vmul", "div" => "vdiv", diff --git a/fearless_simd_gen/src/arch/wasm.rs b/fearless_simd_gen/src/arch/wasm.rs index 781ee132a..42449730f 100644 --- a/fearless_simd_gen/src/arch/wasm.rs +++ b/fearless_simd_gen/src/arch/wasm.rs @@ -15,6 +15,7 @@ fn translate_op(op: &str) -> Option<&'static str> { "trunc" => "trunc", "sqrt" => "sqrt", "add" => "add", + "saturating_add" => "add_sat", // TODO: Is wrapping sub same on WASM? "sub" => "sub", "mul" => "mul", diff --git a/fearless_simd_gen/src/mk_fallback.rs b/fearless_simd_gen/src/mk_fallback.rs index d4d88412a..8c9aee102 100644 --- a/fearless_simd_gen/src/mk_fallback.rs +++ b/fearless_simd_gen/src/mk_fallback.rs @@ -891,6 +891,7 @@ fn rhs_reference(method: &str) -> bool { | "wrapping_sub" | "wrapping_mul" | "wrapping_add" + | "saturating_add" | "wrapping_shl" | "wrapping_shr" ) diff --git a/fearless_simd_gen/src/mk_wasm.rs b/fearless_simd_gen/src/mk_wasm.rs index 21052fc17..4803f1409 100644 --- a/fearless_simd_gen/src/mk_wasm.rs +++ b/fearless_simd_gen/src/mk_wasm.rs @@ -219,6 +219,90 @@ fn reduce_sum(method_sig: TokenStream, vec_ty: &VecType) -> TokenStream { } } +fn saturating_add_method(op: Op, vec_ty: &VecType) -> TokenStream { + assert_eq!( + vec_ty.n_bits(), + 128, + "WASM saturating-add lowering only handles one native vector" + ); + assert!( + matches!(vec_ty.scalar, ScalarType::Int | ScalarType::Unsigned), + "saturating_add is only defined for integers" + ); + + let method_sig = op.simd_trait_method_sig(vec_ty); + if matches!(vec_ty.scalar_bits, 8 | 16) { + let expr = wasm::expr( + "saturating_add", + vec_ty, + &[quote! { a.into() }, quote! { b.into() }], + ); + return quote! { + #method_sig { + #expr.simd_into(self) + } + }; + } + + let add = simple_intrinsic("add", vec_ty); + let body = match (vec_ty.scalar, vec_ty.scalar_bits) { + (ScalarType::Unsigned, 32) => { + // Clamp `a` to the greatest value that can be added to `b`, then add. + // `!b` is `u32::MAX - b` lane-wise. + let min = simple_intrinsic("min", vec_ty); + quote! { + #add(#min(a, v128_not(b)), b) + } + } + (ScalarType::Unsigned, 64) => { + // WebAssembly has no unsigned i64x2 comparison. Flip the sign bit so + // the signed ordering matches unsigned ordering, then detect carry by + // checking whether the wrapping sum is less than the first addend. + let signed_ty = vec_ty.cast(ScalarType::Int); + let signed_gt = simple_intrinsic("gt", &signed_ty); + let signed_splat = simple_intrinsic("splat", &signed_ty); + let signed_scalar = signed_ty.scalar.rust(signed_ty.scalar_bits); + quote! { + let sum = #add(a, b); + let sign_bit = #signed_splat(#signed_scalar::MIN); + let overflow_mask = #signed_gt( + v128_xor(a, sign_bit), + v128_xor(sum, sign_bit), + ); + v128_or(sum, overflow_mask) + } + } + (ScalarType::Int, 32 | 64) => { + // Signed overflow has the same sign in `(sum ^ a)` and `(sum ^ b)`. + // Expand that sign bit into a lane mask, then select the appropriate + // endpoint. A wrapped negative sum means positive overflow and vice versa. + let shr = simple_intrinsic("shr", vec_ty); + let splat = simple_intrinsic("splat", vec_ty); + let scalar = vec_ty.scalar.rust(vec_ty.scalar_bits); + let sign_shift = Literal::u32_unsuffixed((vec_ty.scalar_bits - 1) as u32); + quote! { + let sum = #add(a, b); + let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); + let overflow_mask = #shr(overflow_bits, #sign_shift); + let saturation = v128_xor( + #shr(sum, #sign_shift), + #splat(#scalar::MIN), + ); + v128_bitselect(saturation, sum, overflow_mask) + } + } + _ => unreachable!(), + }; + + quote! { + #method_sig { + let a: v128 = a.into(); + let b: v128 = b.into(); + #body.simd_into(self) + } + } +} + impl Level for WasmSimd128 { fn name(&self) -> &'static str { "WasmSimd128" @@ -480,6 +564,10 @@ impl Level for WasmSimd128 { mode: NarrowingMode::Relaxed, } => relaxed_narrow_method(op, vec_ty, target_ty, "narrow"), OpSig::Binary => { + if method == "saturating_add" { + return saturating_add_method(op, vec_ty); + } + if matches!(method, "shlv" | "shrv") || (matches!(method, "min" | "max") && vec_ty.scalar_bits == 64 diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index 1bc6838ab..ba5a0f6ab 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2275,9 +2275,192 @@ impl X86 { } } + fn handle_avx512_signed_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { + assert!(*self == Self::Avx512); + assert_eq!(vec_ty.scalar, ScalarType::Int); + assert!(matches!(vec_ty.scalar_bits, 32 | 64)); + + let bits = vec_ty.n_bits(); + let lane_bits = vec_ty.scalar_bits; + let shift = Literal::usize_unsuffixed(lane_bits - 1); + let max = match lane_bits { + 32 => quote! { i32::MAX }, + 64 => quote! { i64::MAX }, + _ => unreachable!(), + }; + let suffix = format!("epi{lane_bits}"); + let add = intrinsic_ident("add", &suffix, bits); + let shift_right_logical = intrinsic_ident("srli", &suffix, bits); + let shift_right_arithmetic = intrinsic_ident("srai", &suffix, bits); + let set1 = set1_intrinsic(vec_ty); + let ternary = intrinsic_ident("ternarylogic", &suffix, bits); + + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + + // The 0x42 truth table computes `(a ^ sum) & (b ^ sum)`, whose + // sign bit is set exactly when signed addition overflows. + let overflow_bits = #ternary::<0x42>(a, b, sum); + let overflow_mask = #shift_right_arithmetic::<#shift>(overflow_bits); + + // The top bit of `a` selects the saturation direction. Logical + // shift produces 0 or 1; adding that to MAX gives MAX or MIN. + let direction = #add( + #shift_right_logical::<#shift>(a), + #set1(#max), + ); + + // 0xca is a bitwise select: use `direction` where overflowed, + // and the wrapped sum everywhere else. + #ternary::<0xca>(overflow_mask, direction, sum).simd_into(#token) + } + }) + } + + fn handle_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { + assert!(matches!( + vec_ty.scalar, + ScalarType::Int | ScalarType::Unsigned + )); + + if matches!(vec_ty.scalar_bits, 8 | 16) { + let adds = simple_intrinsic("adds", vec_ty); + return self.kernel_method(op, vec_ty, |token| { + quote! { + #adds(a.into(), b.into()).simd_into(#token) + } + }); + } + + // SSE2 is a correctness target, not an optimization target. Its 32/64-bit + // packed emulations are substantially more complex than the scalar fallback. + if *self == Self::Sse2 { + return fallback_method(op, vec_ty); + } + + if *self == Self::Avx512 && vec_ty.scalar == ScalarType::Int { + return self.handle_avx512_signed_saturating_add(op, vec_ty); + } + + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + + if vec_ty.scalar == ScalarType::Unsigned + && (vec_ty.scalar_bits == 32 || *self == Self::Avx512) + { + // Unsigned saturation can be expressed without detecting carry: + // clamp `a` to the greatest value that can be added to `b`, then add. + let min = simple_intrinsic("min", vec_ty); + return self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let threshold = #xor(b, #set1(-1)); + #add(#min(a, threshold), b).simd_into(#token) + } + }); + } + + if vec_ty.scalar == ScalarType::Unsigned && vec_ty.scalar_bits == 64 { + // SSE4.2 and AVX2 have signed, but not unsigned, qword comparisons. + // Flipping the sign bit maps unsigned order onto signed order. + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + let or = intrinsic_ident("or", coarse_type(vec_ty), bits); + return self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + let sign_bias = #set1(i64::MIN); + let overflow = #cmpgt( + #xor(a, sign_bias), + #xor(sum, sign_bias), + ); + #or(sum, overflow).simd_into(#token) + } + }); + } + + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + match vec_ty.scalar_bits { + 32 => { + let shift = intrinsic_ident("srai", "epi32", bits); + let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 32, 32, bits); + let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 32, 32, bits); + let blend = intrinsic_ident("blendv", "ps", bits); + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + + // Only the sign bit of each lane is meaningful here, so use + // BLENDVPS rather than the byte-granularity integer blend. + let overflow = #xor(#cmpgt(a, sum), b); + let bound = #xor(#shift::<31>(sum), #set1(i32::MIN)); + let result = #blend( + #to_float(sum), + #to_float(bound), + #to_float(overflow), + ); + #to_int(result).simd_into(#token) + } + }) + } + 64 => { + let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 64, 64, bits); + let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 64, 64, bits); + let blend = intrinsic_ident("blendv", "pd", bits); + let bound = if *self == Self::Avx2 { + let shift = intrinsic_ident("srli", "epi64", bits); + quote! { + // AVX2 can construct the endpoint directly from `a`'s sign + // without the high-dword shuffle required by SSE4.2. + let bound = #add(#shift::<63>(a), #set1(i64::MAX)); + } + } else { + let shuffle = intrinsic_ident("shuffle", "epi32", bits); + let shift = intrinsic_ident("srai", "epi32", bits); + quote! { + let sum_sign = #shift::<31>(#shuffle::<0xf5>(sum)); + let bound = #xor(sum_sign, #set1(i64::MIN)); + } + }; + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + + // BLENDVPD reads one sign bit per qword, which is exactly the + // meaningful part of this non-canonical overflow mask. + let overflow = #xor(#cmpgt(a, sum), b); + #bound + let result = #blend( + #to_float(sum), + #to_float(bound), + #to_float(overflow), + ); + #to_int(result).simd_into(#token) + } + }) + } + _ => unreachable!(), + } + } + pub(crate) fn handle_binary(&self, op: Op, method: &str, vec_ty: &VecType) -> TokenStream { let method_sig = op.simd_trait_method_sig(vec_ty); + if method == "saturating_add" { + return self.handle_saturating_add(op, vec_ty); + } + if *self == Self::Avx512 && vec_ty.scalar == ScalarType::Mask { let lane_mask = avx512_mask_lane_bits(vec_ty); let a_bits = avx512_mask_bits_expr(quote! { a }); diff --git a/fearless_simd_gen/src/ops.rs b/fearless_simd_gen/src/ops.rs index 9ee913ee0..616259bfc 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -971,6 +971,12 @@ const INT_OPS: &[Op] = &[ OpSig::Binary, "Add two vectors element-wise, wrapping on overflow.", ), + Op::new( + "saturating_add", + OpKind::VecTraitMethod, + OpSig::Binary, + "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing.", + ), Op::new( "sub", OpKind::Overloaded(CoreOpTrait::Sub), diff --git a/fearless_simd_tests/tests/generics.rs b/fearless_simd_tests/tests/generics.rs index c1f3b72c1..f540ae636 100644 --- a/fearless_simd_tests/tests/generics.rs +++ b/fearless_simd_tests/tests/generics.rs @@ -36,6 +36,11 @@ fn generic_i64_to_f64(x: S::i64s) -> S::f64s { x.to_float() } +// Ensure that integer operations exposed through `SimdInt` are available to generic code. +fn generic_saturating_add>(lhs: V, rhs: V) -> V { + lhs.saturating_add(rhs) +} + // 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/mod.rs b/fearless_simd_tests/tests/harness/ops/mod.rs index 90acb6a91..7a33fa2aa 100644 --- a/fearless_simd_tests/tests/harness/ops/mod.rs +++ b/fearless_simd_tests/tests/harness/ops/mod.rs @@ -66,6 +66,7 @@ mod reverse; mod rotate_elements_left; mod rotate_elements_right; mod round_ties_even; +mod saturating_add; mod select; mod set; mod shift_elements_left; From 72d7e948613f90209ab4216c5ea16c8695edc616 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 18:27:13 +0100 Subject: [PATCH 2/9] Refactor x86 saturating add generator to use a single large toplevel match --- fearless_simd_gen/src/mk_x86.rs | 250 +++++++++++++++++--------------- 1 file changed, 133 insertions(+), 117 deletions(-) diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index ba5a0f6ab..92f761454 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2275,120 +2275,112 @@ impl X86 { } } - fn handle_avx512_signed_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { - assert!(*self == Self::Avx512); - assert_eq!(vec_ty.scalar, ScalarType::Int); - assert!(matches!(vec_ty.scalar_bits, 32 | 64)); - - let bits = vec_ty.n_bits(); - let lane_bits = vec_ty.scalar_bits; - let shift = Literal::usize_unsuffixed(lane_bits - 1); - let max = match lane_bits { - 32 => quote! { i32::MAX }, - 64 => quote! { i64::MAX }, - _ => unreachable!(), - }; - let suffix = format!("epi{lane_bits}"); - let add = intrinsic_ident("add", &suffix, bits); - let shift_right_logical = intrinsic_ident("srli", &suffix, bits); - let shift_right_arithmetic = intrinsic_ident("srai", &suffix, bits); - let set1 = set1_intrinsic(vec_ty); - let ternary = intrinsic_ident("ternarylogic", &suffix, bits); - - self.kernel_method(op, vec_ty, |token| { - quote! { - let a = a.into(); - let b = b.into(); - let sum = #add(a, b); - - // The 0x42 truth table computes `(a ^ sum) & (b ^ sum)`, whose - // sign bit is set exactly when signed addition overflows. - let overflow_bits = #ternary::<0x42>(a, b, sum); - let overflow_mask = #shift_right_arithmetic::<#shift>(overflow_bits); - - // The top bit of `a` selects the saturation direction. Logical - // shift produces 0 or 1; adding that to MAX gives MAX or MIN. - let direction = #add( - #shift_right_logical::<#shift>(a), - #set1(#max), - ); - - // 0xca is a bitwise select: use `direction` where overflowed, - // and the wrapped sum everywhere else. - #ternary::<0xca>(overflow_mask, direction, sum).simd_into(#token) - } - }) - } - fn handle_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { assert!(matches!( vec_ty.scalar, ScalarType::Int | ScalarType::Unsigned )); + match (*self, vec_ty.scalar, vec_ty.scalar_bits, vec_ty.n_bits()) { + // x86 has native instructions for 8-bit and 16-bit elements only. + (_, _, 8 | 16, _) => { + let adds = simple_intrinsic("adds", vec_ty); + self.kernel_method(op, vec_ty, |token| { + quote! { + #adds(a.into(), b.into()).simd_into(#token) + } + }) + } + // SSE2 emulations are possible but complex so we don't bother, SSE2 is too rare. + (Self::Sse2, _, 32 | 64, _) => { + fallback_method(op, vec_ty) + } + (Self::Avx512, ScalarType::Int, lane_bits @ (32 | 64), bits) => { + let shift = Literal::usize_unsuffixed(lane_bits - 1); + let max = match lane_bits { + 32 => quote! { i32::MAX }, + 64 => quote! { i64::MAX }, + _ => unreachable!(), + }; + let suffix = format!("epi{lane_bits}"); + let add = intrinsic_ident("add", &suffix, bits); + let shift_right_logical = intrinsic_ident("srli", &suffix, bits); + let shift_right_arithmetic = intrinsic_ident("srai", &suffix, bits); + let set1 = set1_intrinsic(vec_ty); + let ternary = intrinsic_ident("ternarylogic", &suffix, bits); - if matches!(vec_ty.scalar_bits, 8 | 16) { - let adds = simple_intrinsic("adds", vec_ty); - return self.kernel_method(op, vec_ty, |token| { - quote! { - #adds(a.into(), b.into()).simd_into(#token) - } - }); - } - - // SSE2 is a correctness target, not an optimization target. Its 32/64-bit - // packed emulations are substantially more complex than the scalar fallback. - if *self == Self::Sse2 { - return fallback_method(op, vec_ty); - } - - if *self == Self::Avx512 && vec_ty.scalar == ScalarType::Int { - return self.handle_avx512_signed_saturating_add(op, vec_ty); - } + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); - let bits = vec_ty.n_bits(); - let add = simple_sign_unaware_intrinsic("add", vec_ty); - let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); - let set1 = set1_intrinsic(vec_ty); + // The 0x42 truth table computes `(a ^ sum) & (b ^ sum)`, whose + // sign bit is set exactly when signed addition overflows. + let overflow_bits = #ternary::<0x42>(a, b, sum); + let overflow_mask = + #shift_right_arithmetic::<#shift>(overflow_bits); + + // The top bit of `a` selects the saturation direction. Logical + // shift produces 0 or 1; adding that to MAX gives MAX or MIN. + let direction = #add( + #shift_right_logical::<#shift>(a), + #set1(#max), + ); - if vec_ty.scalar == ScalarType::Unsigned - && (vec_ty.scalar_bits == 32 || *self == Self::Avx512) - { - // Unsigned saturation can be expressed without detecting carry: - // clamp `a` to the greatest value that can be added to `b`, then add. - let min = simple_intrinsic("min", vec_ty); - return self.kernel_method(op, vec_ty, |token| { - quote! { - let a = a.into(); - let b = b.into(); - let threshold = #xor(b, #set1(-1)); - #add(#min(a, threshold), b).simd_into(#token) - } - }); - } + // 0xca is a bitwise select: use `direction` where overflowed, + // and the wrapped sum everywhere else. + #ternary::<0xca>(overflow_mask, direction, sum).simd_into(#token) + } + }) + } + (Self::Sse4_2 | Self::Avx2 | Self::Avx512, ScalarType::Unsigned, 32, _bits) => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let min = simple_intrinsic("min", vec_ty); - if vec_ty.scalar == ScalarType::Unsigned && vec_ty.scalar_bits == 64 { - // SSE4.2 and AVX2 have signed, but not unsigned, qword comparisons. - // Flipping the sign bit maps unsigned order onto signed order. - let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - let or = intrinsic_ident("or", coarse_type(vec_ty), bits); - return self.kernel_method(op, vec_ty, |token| { - quote! { - let a = a.into(); - let b = b.into(); - let sum = #add(a, b); - let sign_bias = #set1(i64::MIN); - let overflow = #cmpgt( - #xor(a, sign_bias), - #xor(sum, sign_bias), - ); - #or(sum, overflow).simd_into(#token) - } - }); - } + // Unsigned saturation can be expressed without detecting carry: + // clamp `a` to the greatest value that can be added to `b`, then add. + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let threshold = #xor(b, #set1(-1)); + #add(#min(a, threshold), b).simd_into(#token) + } + }) + } + (Self::Sse4_2 | Self::Avx2, ScalarType::Unsigned, 64, _bits) => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + let or = intrinsic_ident("or", coarse_type(vec_ty), bits); - let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - match vec_ty.scalar_bits { - 32 => { + // SSE4.2 and AVX2 have signed, but not unsigned, qword comparisons. + // Flipping the sign bit maps unsigned order onto signed order. + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + let sign_bias = #set1(i64::MIN); + let overflow = #cmpgt( + #xor(a, sign_bias), + #xor(sum, sign_bias), + ); + #or(sum, overflow).simd_into(#token) + } + }) + } + (Self::Sse4_2 | Self::Avx2, ScalarType::Int, 32, _bits) => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); let shift = intrinsic_ident("srai", "epi32", bits); let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 32, 32, bits); let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 32, 32, bits); @@ -2412,25 +2404,47 @@ impl X86 { } }) } - 64 => { + (Self::Sse4_2, ScalarType::Int, 64, 128) => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + let shuffle = intrinsic_ident("shuffle", "epi32", bits); + let shift = intrinsic_ident("srai", "epi32", bits); let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 64, 64, bits); let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 64, 64, bits); let blend = intrinsic_ident("blendv", "pd", bits); - let bound = if *self == Self::Avx2 { - let shift = intrinsic_ident("srli", "epi64", bits); - quote! { - // AVX2 can construct the endpoint directly from `a`'s sign - // without the high-dword shuffle required by SSE4.2. - let bound = #add(#shift::<63>(a), #set1(i64::MAX)); - } - } else { - let shuffle = intrinsic_ident("shuffle", "epi32", bits); - let shift = intrinsic_ident("srai", "epi32", bits); + self.kernel_method(op, vec_ty, |token| { quote! { + let a = a.into(); + let b = b.into(); + let sum = #add(a, b); + + // BLENDVPD reads one sign bit per qword, which is exactly the + // meaningful part of this non-canonical overflow mask. + let overflow = #xor(#cmpgt(a, sum), b); let sum_sign = #shift::<31>(#shuffle::<0xf5>(sum)); let bound = #xor(sum_sign, #set1(i64::MIN)); + let result = #blend( + #to_float(sum), + #to_float(bound), + #to_float(overflow), + ); + #to_int(result).simd_into(#token) } - }; + }) + } + (Self::Avx2, ScalarType::Int, 64, 128 | 256) => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + let shift = intrinsic_ident("srli", "epi64", bits); + let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 64, 64, bits); + let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 64, 64, bits); + let blend = intrinsic_ident("blendv", "pd", bits); self.kernel_method(op, vec_ty, |token| { quote! { let a = a.into(); @@ -2440,7 +2454,9 @@ impl X86 { // BLENDVPD reads one sign bit per qword, which is exactly the // meaningful part of this non-canonical overflow mask. let overflow = #xor(#cmpgt(a, sum), b); - #bound + // AVX2 can construct the endpoint directly from `a`'s sign + // without the high-dword shuffle required by SSE4.2. + let bound = #add(#shift::<63>(a), #set1(i64::MAX)); let result = #blend( #to_float(sum), #to_float(bound), From 17d2d5cdd2f903d5bdb9ea894aaa0ff871f3d2f9 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 18:38:24 +0100 Subject: [PATCH 3/9] Collapse three toplevel branches into one with shared structure --- fearless_simd_gen/src/mk_x86.rs | 123 +++++++++++++------------------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index 92f761454..ad4b0b587 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2291,9 +2291,7 @@ impl X86 { }) } // SSE2 emulations are possible but complex so we don't bother, SSE2 is too rare. - (Self::Sse2, _, 32 | 64, _) => { - fallback_method(op, vec_ty) - } + (Self::Sse2, _, 32 | 64, _) => fallback_method(op, vec_ty), (Self::Avx512, ScalarType::Int, lane_bits @ (32 | 64), bits) => { let shift = Literal::usize_unsuffixed(lane_bits - 1); let max = match lane_bits { @@ -2333,7 +2331,8 @@ impl X86 { } }) } - (Self::Sse4_2 | Self::Avx2 | Self::Avx512, ScalarType::Unsigned, 32, _bits) => { + (Self::Sse4_2 | Self::Avx2, ScalarType::Unsigned, 32, _bits) + | (Self::Avx512, ScalarType::Unsigned, 32 | 64, _bits) => { let bits = vec_ty.n_bits(); let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); @@ -2375,88 +2374,66 @@ impl X86 { } }) } - (Self::Sse4_2 | Self::Avx2, ScalarType::Int, 32, _bits) => { - let bits = vec_ty.n_bits(); + (Self::Sse4_2 | Self::Avx2, ScalarType::Int, lane_bits @ (32 | 64), bits) => { let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); let set1 = set1_intrinsic(vec_ty); let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - let shift = intrinsic_ident("srai", "epi32", bits); - let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 32, 32, bits); - let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 32, 32, bits); - let blend = intrinsic_ident("blendv", "ps", bits); - self.kernel_method(op, vec_ty, |token| { - quote! { - let a = a.into(); - let b = b.into(); - let sum = #add(a, b); - - // Only the sign bit of each lane is meaningful here, so use - // BLENDVPS rather than the byte-granularity integer blend. - let overflow = #xor(#cmpgt(a, sum), b); - let bound = #xor(#shift::<31>(sum), #set1(i32::MIN)); - let result = #blend( - #to_float(sum), - #to_float(bound), - #to_float(overflow), - ); - #to_int(result).simd_into(#token) + let bound = match (*self, lane_bits) { + (_, 32) => { + let shift = intrinsic_ident("srai", "epi32", bits); + quote! { + let bound = #xor(#shift::<31>(sum), #set1(i32::MIN)); + } } - }) - } - (Self::Sse4_2, ScalarType::Int, 64, 128) => { - let bits = vec_ty.n_bits(); - let add = simple_sign_unaware_intrinsic("add", vec_ty); - let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); - let set1 = set1_intrinsic(vec_ty); - let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - let shuffle = intrinsic_ident("shuffle", "epi32", bits); - let shift = intrinsic_ident("srai", "epi32", bits); - let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 64, 64, bits); - let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 64, 64, bits); - let blend = intrinsic_ident("blendv", "pd", bits); - self.kernel_method(op, vec_ty, |token| { - quote! { - let a = a.into(); - let b = b.into(); - let sum = #add(a, b); - - // BLENDVPD reads one sign bit per qword, which is exactly the - // meaningful part of this non-canonical overflow mask. - let overflow = #xor(#cmpgt(a, sum), b); - let sum_sign = #shift::<31>(#shuffle::<0xf5>(sum)); - let bound = #xor(sum_sign, #set1(i64::MIN)); - let result = #blend( - #to_float(sum), - #to_float(bound), - #to_float(overflow), - ); - #to_int(result).simd_into(#token) + (Self::Sse4_2, 64) => { + let shuffle = intrinsic_ident("shuffle", "epi32", bits); + let shift = intrinsic_ident("srai", "epi32", bits); + quote! { + let sum_sign = #shift::<31>(#shuffle::<0xf5>(sum)); + let bound = #xor(sum_sign, #set1(i64::MIN)); + } } - }) - } - (Self::Avx2, ScalarType::Int, 64, 128 | 256) => { - let bits = vec_ty.n_bits(); - let add = simple_sign_unaware_intrinsic("add", vec_ty); - let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); - let set1 = set1_intrinsic(vec_ty); - let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - let shift = intrinsic_ident("srli", "epi64", bits); - let to_float = cast_ident(ScalarType::Int, ScalarType::Float, 64, 64, bits); - let to_int = cast_ident(ScalarType::Float, ScalarType::Int, 64, 64, bits); - let blend = intrinsic_ident("blendv", "pd", bits); + (Self::Avx2, 64) => { + let shift = intrinsic_ident("srli", "epi64", bits); + quote! { + // AVX2 can construct the endpoint directly from `a`'s sign + // without the high-dword shuffle required by SSE4.2. + let bound = #add(#shift::<63>(a), #set1(i64::MAX)); + } + } + _ => unreachable!(), + }; + let to_float = cast_ident( + ScalarType::Int, + ScalarType::Float, + lane_bits, + lane_bits, + bits, + ); + let to_int = cast_ident( + ScalarType::Float, + ScalarType::Int, + lane_bits, + lane_bits, + bits, + ); + let blend_suffix = match lane_bits { + 32 => "ps", + 64 => "pd", + _ => unreachable!(), + }; + let blend = intrinsic_ident("blendv", blend_suffix, bits); self.kernel_method(op, vec_ty, |token| { quote! { let a = a.into(); let b = b.into(); let sum = #add(a, b); - // BLENDVPD reads one sign bit per qword, which is exactly the - // meaningful part of this non-canonical overflow mask. + // Only the sign bit of each lane is meaningful here, so use a + // lane-granularity floating-point blend instead of BLENDV_EPI8. let overflow = #xor(#cmpgt(a, sum), b); - // AVX2 can construct the endpoint directly from `a`'s sign - // without the high-dword shuffle required by SSE4.2. - let bound = #add(#shift::<63>(a), #set1(i64::MAX)); + #bound let result = #blend( #to_float(sum), #to_float(bound), From 4ea5a4a9650a2c993f627f7fde732fa9d712b167 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 18:41:19 +0100 Subject: [PATCH 4/9] Use a use statement for nicer formatting --- fearless_simd_gen/src/mk_x86.rs | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index ad4b0b587..a4c9a6989 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2276,10 +2276,8 @@ impl X86 { } fn handle_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { - assert!(matches!( - vec_ty.scalar, - ScalarType::Int | ScalarType::Unsigned - )); + use ScalarType::{Float, Int, Unsigned}; + assert!(matches!(vec_ty.scalar, Int | Unsigned)); match (*self, vec_ty.scalar, vec_ty.scalar_bits, vec_ty.n_bits()) { // x86 has native instructions for 8-bit and 16-bit elements only. (_, _, 8 | 16, _) => { @@ -2292,7 +2290,7 @@ impl X86 { } // SSE2 emulations are possible but complex so we don't bother, SSE2 is too rare. (Self::Sse2, _, 32 | 64, _) => fallback_method(op, vec_ty), - (Self::Avx512, ScalarType::Int, lane_bits @ (32 | 64), bits) => { + (Self::Avx512, Int, lane_bits @ (32 | 64), bits) => { let shift = Literal::usize_unsuffixed(lane_bits - 1); let max = match lane_bits { 32 => quote! { i32::MAX }, @@ -2331,8 +2329,8 @@ impl X86 { } }) } - (Self::Sse4_2 | Self::Avx2, ScalarType::Unsigned, 32, _bits) - | (Self::Avx512, ScalarType::Unsigned, 32 | 64, _bits) => { + (Self::Sse4_2 | Self::Avx2, Unsigned, 32, _bits) + | (Self::Avx512, Unsigned, 32 | 64, _bits) => { let bits = vec_ty.n_bits(); let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); @@ -2350,7 +2348,7 @@ impl X86 { } }) } - (Self::Sse4_2 | Self::Avx2, ScalarType::Unsigned, 64, _bits) => { + (Self::Sse4_2 | Self::Avx2, Unsigned, 64, _bits) => { let bits = vec_ty.n_bits(); let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); @@ -2374,7 +2372,7 @@ impl X86 { } }) } - (Self::Sse4_2 | Self::Avx2, ScalarType::Int, lane_bits @ (32 | 64), bits) => { + (Self::Sse4_2 | Self::Avx2, Int, lane_bits @ (32 | 64), bits) => { let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); let set1 = set1_intrinsic(vec_ty); @@ -2404,20 +2402,8 @@ impl X86 { } _ => unreachable!(), }; - let to_float = cast_ident( - ScalarType::Int, - ScalarType::Float, - lane_bits, - lane_bits, - bits, - ); - let to_int = cast_ident( - ScalarType::Float, - ScalarType::Int, - lane_bits, - lane_bits, - bits, - ); + let to_float = cast_ident(Int, Float, lane_bits, lane_bits, bits); + let to_int = cast_ident(Float, Int, lane_bits, lane_bits, bits); let blend_suffix = match lane_bits { 32 => "ps", 64 => "pd", From dea8646ccc567396281fa4ce1ccece3cfdafd94a Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 19:37:17 +0100 Subject: [PATCH 5/9] Add saturating_sub --- CHANGELOG.md | 2 +- fearless_simd/src/generated/avx2.rs | 264 ++- fearless_simd/src/generated/avx512.rs | 336 +++- fearless_simd/src/generated/fallback.rs | 108 ++ fearless_simd/src/generated/neon.rs | 80 + fearless_simd/src/generated/simd_trait.rs | 178 ++ fearless_simd/src/generated/simd_types.rs | 120 ++ fearless_simd/src/generated/sse2.rs | 76 + fearless_simd/src/generated/sse4_2.rs | 137 +- fearless_simd/src/generated/wasm.rs | 73 +- fearless_simd_gen/src/arch/fallback.rs | 1 + fearless_simd_gen/src/arch/neon.rs | 1 + fearless_simd_gen/src/arch/wasm.rs | 1 + fearless_simd_gen/src/mk_fallback.rs | 1 + fearless_simd_gen/src/mk_wasm.rs | 114 +- fearless_simd_gen/src/mk_x86.rs | 159 +- fearless_simd_gen/src/ops.rs | 13 + fearless_simd_tests/tests/generics.rs | 4 + fearless_simd_tests/tests/harness/ops/mod.rs | 1 + .../tests/harness/ops/saturating_add.rs | 1539 +++++++++++++++++ .../tests/harness/ops/saturating_sub.rs | 1386 +++++++++++++++ 21 files changed, 4457 insertions(+), 137 deletions(-) create mode 100644 fearless_simd_tests/tests/harness/ops/saturating_add.rs create mode 100644 fearless_simd_tests/tests/harness/ops/saturating_sub.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fd7cc5584..94f5a2fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ You can find its changes [documented below](#070-2026-08-11). ### Added - Added `reverse` for all SIMD vector and mask types. -- Added lane-wise `saturating_add` for all integer vector types and backends. +- Added lane-wise `saturating_add` and `saturating_sub` for all integer vector types and backends. - Added lane-wise `count_ones` and `count_zeros` operations for all integer vector types and backends. - Added `mul_add_precise` and `mul_sub_precise` for floating-point vectors. They guarantee the infinite-precision product-plus-add rounded once, including on SIMD levels without hardware fused multiply-add instructions. They are not susceptible to the [bug](https://github.com/rust-lang/compiler-builtins/issues/1262) in Rust standard library, `std::simd` and musl libc that causes incorrect rounding for subnormal results. SSE4.2 gets SIMD emulation of these operations for better performance. ([#323][], [#324][] by [@Shnatsel][]) - Documented the storage representation of the SIMD vector types. The documented representation will not change without a semver major version change. diff --git a/fearless_simd/src/generated/avx2.rs b/fearless_simd/src/generated/avx2.rs index 6178db6d5..3bc9ad6cf 100644 --- a/fearless_simd/src/generated/avx2.rs +++ b/fearless_simd/src/generated/avx2.rs @@ -803,6 +803,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i8x16, b: i8x16) -> i8x16 { + _mm_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1381,6 +1391,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u8x16, b: u8x16) -> u8x16 { + _mm_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2081,6 +2101,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i16x8, b: i16x8) -> i16x8 { + _mm_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2604,6 +2634,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u16x8, b: u16x8) -> u16x8 { + _mm_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3294,11 +3334,11 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: i32x4, b: i32x4) -> i32x4 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi32(a, b); - let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, sum), b); - let bound = _mm_xor_si128(_mm_srai_epi32::<31>(sum), _mm_set1_epi32(i32::MIN)); + let wrapped = _mm_add_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, wrapped), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); let result = _mm_blendv_ps( - _mm_castsi128_ps(sum), + _mm_castsi128_ps(wrapped), _mm_castsi128_ps(bound), _mm_castsi128_ps(overflow), ); @@ -3318,6 +3358,26 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(wrapped, a), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); + let result = _mm_blendv_ps( + _mm_castsi128_ps(wrapped), + _mm_castsi128_ps(bound), + _mm_castsi128_ps(overflow), + ); + _mm_castps_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3821,6 +3881,18 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + _mm_sub_epi32(_mm_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -5080,11 +5152,11 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: i64x2, b: i64x2) -> i64x2 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi64(a, b); - let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, sum), b); + let wrapped = _mm_add_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, wrapped), b); let bound = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); let result = _mm_blendv_pd( - _mm_castsi128_pd(sum), + _mm_castsi128_pd(wrapped), _mm_castsi128_pd(bound), _mm_castsi128_pd(overflow), ); @@ -5104,6 +5176,26 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(wrapped, a), b); + let bound = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); + let result = _mm_blendv_pd( + _mm_castsi128_pd(wrapped), + _mm_castsi128_pd(bound), + _mm_castsi128_pd(overflow), + ); + _mm_castpd_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -5552,11 +5644,13 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: u64x2, b: u64x2) -> u64x2 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi64(a, b); + let wrapped = _mm_add_epi64(a, b); let sign_bias = _mm_set1_epi64x(i64::MIN); - let overflow = - _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(sum, sign_bias)); - _mm_or_si128(sum, overflow).simd_into(token) + let overflow = _mm_cmpgt_epi64( + _mm_xor_si128(a, sign_bias), + _mm_xor_si128(wrapped, sign_bias), + ); + _mm_or_si128(wrapped, overflow).simd_into(token) } ); kernel(self, a, b) @@ -5572,6 +5666,22 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi64(a, b); + let sign_bias = _mm_set1_epi64x(i64::MIN); + let no_borrow = + _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(b, sign_bias)); + _mm_and_si128(wrapped, no_borrow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_mul(a[0usize], b[0usize]), @@ -6724,6 +6834,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i8x32, b: i8x32) -> i8x32 { + _mm256_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { crate::kernel!( #[inline(always)] @@ -7276,6 +7396,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u8x32, b: u8x32) -> u8x32 { + _mm256_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { crate::kernel!( #[inline(always)] @@ -7952,6 +8082,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i16x16, b: i16x16) -> i16x16 { + _mm256_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { crate::kernel!( #[inline(always)] @@ -8435,6 +8575,16 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u16x16, b: u16x16) -> u16x16 { + _mm256_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { crate::kernel!( #[inline(always)] @@ -9101,12 +9251,12 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: i32x8, b: i32x8) -> i32x8 { let a = a.into(); let b = b.into(); - let sum = _mm256_add_epi32(a, b); - let overflow = _mm256_xor_si256(_mm256_cmpgt_epi32(a, sum), b); + let wrapped = _mm256_add_epi32(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi32(a, wrapped), b); let bound = - _mm256_xor_si256(_mm256_srai_epi32::<31>(sum), _mm256_set1_epi32(i32::MIN)); + _mm256_xor_si256(_mm256_srai_epi32::<31>(a), _mm256_set1_epi32(i32::MAX)); let result = _mm256_blendv_ps( - _mm256_castsi256_ps(sum), + _mm256_castsi256_ps(wrapped), _mm256_castsi256_ps(bound), _mm256_castsi256_ps(overflow), ); @@ -9126,6 +9276,27 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i32x8, b: i32x8) -> i32x8 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm256_sub_epi32(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi32(wrapped, a), b); + let bound = + _mm256_xor_si256(_mm256_srai_epi32::<31>(a), _mm256_set1_epi32(i32::MAX)); + let result = _mm256_blendv_ps( + _mm256_castsi256_ps(wrapped), + _mm256_castsi256_ps(bound), + _mm256_castsi256_ps(overflow), + ); + _mm256_castps_si256(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -9582,6 +9753,18 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u32x8, b: u32x8) -> u32x8 { + let a = a.into(); + let b = b.into(); + _mm256_sub_epi32(_mm256_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { crate::kernel!( #[inline(always)] @@ -10751,12 +10934,12 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: i64x4, b: i64x4) -> i64x4 { let a = a.into(); let b = b.into(); - let sum = _mm256_add_epi64(a, b); - let overflow = _mm256_xor_si256(_mm256_cmpgt_epi64(a, sum), b); + let wrapped = _mm256_add_epi64(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi64(a, wrapped), b); let bound = _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); let result = _mm256_blendv_pd( - _mm256_castsi256_pd(sum), + _mm256_castsi256_pd(wrapped), _mm256_castsi256_pd(bound), _mm256_castsi256_pd(overflow), ); @@ -10776,6 +10959,27 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: i64x4, b: i64x4) -> i64x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm256_sub_epi64(a, b); + let overflow = _mm256_xor_si256(_mm256_cmpgt_epi64(wrapped, a), b); + let bound = + _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); + let result = _mm256_blendv_pd( + _mm256_castsi256_pd(wrapped), + _mm256_castsi256_pd(bound), + _mm256_castsi256_pd(overflow), + ); + _mm256_castpd_si256(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -11201,13 +11405,13 @@ impl Simd for Avx2 { fn kernel(token: Avx2, a: u64x4, b: u64x4) -> u64x4 { let a = a.into(); let b = b.into(); - let sum = _mm256_add_epi64(a, b); + let wrapped = _mm256_add_epi64(a, b); let sign_bias = _mm256_set1_epi64x(i64::MIN); let overflow = _mm256_cmpgt_epi64( _mm256_xor_si256(a, sign_bias), - _mm256_xor_si256(sum, sign_bias), + _mm256_xor_si256(wrapped, sign_bias), ); - _mm256_or_si256(sum, overflow).simd_into(token) + _mm256_or_si256(wrapped, overflow).simd_into(token) } ); kernel(self, a, b) @@ -11223,6 +11427,24 @@ impl Simd for Avx2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx2, a: u64x4, b: u64x4) -> u64x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm256_sub_epi64(a, b); + let sign_bias = _mm256_set1_epi64x(i64::MIN); + let no_borrow = _mm256_cmpgt_epi64( + _mm256_xor_si256(a, sign_bias), + _mm256_xor_si256(b, sign_bias), + ); + _mm256_and_si256(wrapped, no_borrow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { [ u64::wrapping_mul(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/avx512.rs b/fearless_simd/src/generated/avx512.rs index 7f3c12290..42cee2117 100644 --- a/fearless_simd/src/generated/avx512.rs +++ b/fearless_simd/src/generated/avx512.rs @@ -1013,6 +1013,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x16, b: i8x16) -> i8x16 { + _mm_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1574,6 +1584,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x16, b: u8x16) -> u8x16 { + _mm_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2192,6 +2212,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x8, b: i16x8) -> i16x8 { + _mm_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2683,6 +2713,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x8, b: u16x8) -> u16x8 { + _mm_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3267,11 +3307,11 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i32x4, b: i32x4) -> i32x4 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi32(a, b); - let overflow_bits = _mm_ternarylogic_epi32::<0x42>(a, b, sum); + let wrapped = _mm_add_epi32(a, b); + let overflow_bits = _mm_ternarylogic_epi32::<0x42>(a, b, wrapped); let overflow_mask = _mm_srai_epi32::<31>(overflow_bits); let direction = _mm_add_epi32(_mm_srli_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); - _mm_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped).simd_into(token) } ); kernel(self, a, b) @@ -3287,6 +3327,22 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi32(a, b); + let overflow_bits = _mm_ternarylogic_epi32::<0x18>(a, b, wrapped); + let overflow_mask = _mm_srai_epi32::<31>(overflow_bits); + let direction = _mm_add_epi32(_mm_srli_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); + _mm_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3770,6 +3826,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + _mm_sub_epi32(_mm_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -4882,11 +4950,11 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i64x2, b: i64x2) -> i64x2 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi64(a, b); - let overflow_bits = _mm_ternarylogic_epi64::<0x42>(a, b, sum); + let wrapped = _mm_add_epi64(a, b); + let overflow_bits = _mm_ternarylogic_epi64::<0x42>(a, b, wrapped); let overflow_mask = _mm_srai_epi64::<63>(overflow_bits); let direction = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); - _mm_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped).simd_into(token) } ); kernel(self, a, b) @@ -4902,6 +4970,22 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi64(a, b); + let overflow_bits = _mm_ternarylogic_epi64::<0x18>(a, b, wrapped); + let overflow_mask = _mm_srai_epi64::<63>(overflow_bits); + let direction = _mm_add_epi64(_mm_srli_epi64::<63>(a), _mm_set1_epi64x(i64::MAX)); + _mm_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -5350,6 +5434,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + _mm_sub_epi64(_mm_max_epu64(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] @@ -6476,6 +6572,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x32, b: i8x32) -> i8x32 { + _mm256_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { crate::kernel!( #[inline(always)] @@ -7029,6 +7135,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x32, b: u8x32) -> u8x32 { + _mm256_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { crate::kernel!( #[inline(always)] @@ -7653,6 +7769,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x16, b: i16x16) -> i16x16 { + _mm256_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { crate::kernel!( #[inline(always)] @@ -8133,6 +8259,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x16, b: u16x16) -> u16x16 { + _mm256_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { crate::kernel!( #[inline(always)] @@ -8720,12 +8856,13 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i32x8, b: i32x8) -> i32x8 { let a = a.into(); let b = b.into(); - let sum = _mm256_add_epi32(a, b); - let overflow_bits = _mm256_ternarylogic_epi32::<0x42>(a, b, sum); + let wrapped = _mm256_add_epi32(a, b); + let overflow_bits = _mm256_ternarylogic_epi32::<0x42>(a, b, wrapped); let overflow_mask = _mm256_srai_epi32::<31>(overflow_bits); let direction = _mm256_add_epi32(_mm256_srli_epi32::<31>(a), _mm256_set1_epi32(i32::MAX)); - _mm256_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm256_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) } ); kernel(self, a, b) @@ -8741,6 +8878,24 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x8, b: i32x8) -> i32x8 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm256_sub_epi32(a, b); + let overflow_bits = _mm256_ternarylogic_epi32::<0x18>(a, b, wrapped); + let overflow_mask = _mm256_srai_epi32::<31>(overflow_bits); + let direction = + _mm256_add_epi32(_mm256_srli_epi32::<31>(a), _mm256_set1_epi32(i32::MAX)); + _mm256_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -9212,6 +9367,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x8, b: u32x8) -> u32x8 { + let a = a.into(); + let b = b.into(); + _mm256_sub_epi32(_mm256_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { crate::kernel!( #[inline(always)] @@ -10309,12 +10476,13 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i64x4, b: i64x4) -> i64x4 { let a = a.into(); let b = b.into(); - let sum = _mm256_add_epi64(a, b); - let overflow_bits = _mm256_ternarylogic_epi64::<0x42>(a, b, sum); + let wrapped = _mm256_add_epi64(a, b); + let overflow_bits = _mm256_ternarylogic_epi64::<0x42>(a, b, wrapped); let overflow_mask = _mm256_srai_epi64::<63>(overflow_bits); let direction = _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); - _mm256_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm256_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) } ); kernel(self, a, b) @@ -10330,6 +10498,24 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x4, b: i64x4) -> i64x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm256_sub_epi64(a, b); + let overflow_bits = _mm256_ternarylogic_epi64::<0x18>(a, b, wrapped); + let overflow_mask = _mm256_srai_epi64::<63>(overflow_bits); + let direction = + _mm256_add_epi64(_mm256_srli_epi64::<63>(a), _mm256_set1_epi64x(i64::MAX)); + _mm256_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { crate::kernel!( #[inline(always)] @@ -10771,6 +10957,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x4, b: u64x4) -> u64x4 { + let a = a.into(); + let b = b.into(); + _mm256_sub_epi64(_mm256_max_epu64(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { crate::kernel!( #[inline(always)] @@ -11892,6 +12090,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i8x64, b: i8x64) -> i8x64 { + _mm512_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { crate::kernel!( #[inline(always)] @@ -12455,6 +12663,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u8x64, b: u8x64) -> u8x64 { + _mm512_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { crate::kernel!( #[inline(always)] @@ -13081,6 +13299,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i16x32, b: i16x32) -> i16x32 { + _mm512_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { crate::kernel!( #[inline(always)] @@ -13572,6 +13800,16 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u16x32, b: u16x32) -> u16x32 { + _mm512_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { crate::kernel!( #[inline(always)] @@ -14162,12 +14400,13 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i32x16, b: i32x16) -> i32x16 { let a = a.into(); let b = b.into(); - let sum = _mm512_add_epi32(a, b); - let overflow_bits = _mm512_ternarylogic_epi32::<0x42>(a, b, sum); + let wrapped = _mm512_add_epi32(a, b); + let overflow_bits = _mm512_ternarylogic_epi32::<0x42>(a, b, wrapped); let overflow_mask = _mm512_srai_epi32::<31>(overflow_bits); let direction = _mm512_add_epi32(_mm512_srli_epi32::<31>(a), _mm512_set1_epi32(i32::MAX)); - _mm512_ternarylogic_epi32::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm512_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) } ); kernel(self, a, b) @@ -14183,6 +14422,24 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i32x16, b: i32x16) -> i32x16 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm512_sub_epi32(a, b); + let overflow_bits = _mm512_ternarylogic_epi32::<0x18>(a, b, wrapped); + let overflow_mask = _mm512_srai_epi32::<31>(overflow_bits); + let direction = + _mm512_add_epi32(_mm512_srli_epi32::<31>(a), _mm512_set1_epi32(i32::MAX)); + _mm512_ternarylogic_epi32::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { crate::kernel!( #[inline(always)] @@ -14669,6 +14926,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u32x16, b: u32x16) -> u32x16 { + let a = a.into(); + let b = b.into(); + _mm512_sub_epi32(_mm512_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { crate::kernel!( #[inline(always)] @@ -15785,12 +16054,13 @@ impl Simd for Avx512 { fn kernel(token: Avx512, a: i64x8, b: i64x8) -> i64x8 { let a = a.into(); let b = b.into(); - let sum = _mm512_add_epi64(a, b); - let overflow_bits = _mm512_ternarylogic_epi64::<0x42>(a, b, sum); + let wrapped = _mm512_add_epi64(a, b); + let overflow_bits = _mm512_ternarylogic_epi64::<0x42>(a, b, wrapped); let overflow_mask = _mm512_srai_epi64::<63>(overflow_bits); let direction = _mm512_add_epi64(_mm512_srli_epi64::<63>(a), _mm512_set1_epi64(i64::MAX)); - _mm512_ternarylogic_epi64::<0xca>(overflow_mask, direction, sum).simd_into(token) + _mm512_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) } ); kernel(self, a, b) @@ -15806,6 +16076,24 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: i64x8, b: i64x8) -> i64x8 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm512_sub_epi64(a, b); + let overflow_bits = _mm512_ternarylogic_epi64::<0x18>(a, b, wrapped); + let overflow_mask = _mm512_srai_epi64::<63>(overflow_bits); + let direction = + _mm512_add_epi64(_mm512_srli_epi64::<63>(a), _mm512_set1_epi64(i64::MAX)); + _mm512_ternarylogic_epi64::<0xca>(overflow_mask, direction, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { crate::kernel!( #[inline(always)] @@ -16256,6 +16544,18 @@ impl Simd for Avx512 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Avx512, a: u64x8, b: u64x8) -> u64x8 { + let a = a.into(); + let b = b.into(); + _mm512_sub_epi64(_mm512_max_epu64(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { crate::kernel!( #[inline(always)] diff --git a/fearless_simd/src/generated/fallback.rs b/fearless_simd/src/generated/fallback.rs index c63844ff2..742012995 100644 --- a/fearless_simd/src/generated/fallback.rs +++ b/fearless_simd/src/generated/fallback.rs @@ -749,6 +749,28 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + [ + i8::saturating_sub(a[0usize], b[0usize]), + i8::saturating_sub(a[1usize], b[1usize]), + i8::saturating_sub(a[2usize], b[2usize]), + i8::saturating_sub(a[3usize], b[3usize]), + i8::saturating_sub(a[4usize], b[4usize]), + i8::saturating_sub(a[5usize], b[5usize]), + i8::saturating_sub(a[6usize], b[6usize]), + i8::saturating_sub(a[7usize], b[7usize]), + i8::saturating_sub(a[8usize], b[8usize]), + i8::saturating_sub(a[9usize], b[9usize]), + i8::saturating_sub(a[10usize], b[10usize]), + i8::saturating_sub(a[11usize], b[11usize]), + i8::saturating_sub(a[12usize], b[12usize]), + i8::saturating_sub(a[13usize], b[13usize]), + i8::saturating_sub(a[14usize], b[14usize]), + i8::saturating_sub(a[15usize], b[15usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { [ i8::wrapping_mul(a[0usize], b[0usize]), @@ -1696,6 +1718,28 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + [ + u8::saturating_sub(a[0usize], b[0usize]), + u8::saturating_sub(a[1usize], b[1usize]), + u8::saturating_sub(a[2usize], b[2usize]), + u8::saturating_sub(a[3usize], b[3usize]), + u8::saturating_sub(a[4usize], b[4usize]), + u8::saturating_sub(a[5usize], b[5usize]), + u8::saturating_sub(a[6usize], b[6usize]), + u8::saturating_sub(a[7usize], b[7usize]), + u8::saturating_sub(a[8usize], b[8usize]), + u8::saturating_sub(a[9usize], b[9usize]), + u8::saturating_sub(a[10usize], b[10usize]), + u8::saturating_sub(a[11usize], b[11usize]), + u8::saturating_sub(a[12usize], b[12usize]), + u8::saturating_sub(a[13usize], b[13usize]), + u8::saturating_sub(a[14usize], b[14usize]), + u8::saturating_sub(a[15usize], b[15usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { [ u8::wrapping_mul(a[0usize], b[0usize]), @@ -2841,6 +2885,20 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + [ + i16::saturating_sub(a[0usize], b[0usize]), + i16::saturating_sub(a[1usize], b[1usize]), + i16::saturating_sub(a[2usize], b[2usize]), + i16::saturating_sub(a[3usize], b[3usize]), + i16::saturating_sub(a[4usize], b[4usize]), + i16::saturating_sub(a[5usize], b[5usize]), + i16::saturating_sub(a[6usize], b[6usize]), + i16::saturating_sub(a[7usize], b[7usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { [ i16::wrapping_mul(a[0usize], b[0usize]), @@ -3437,6 +3495,20 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + [ + u16::saturating_sub(a[0usize], b[0usize]), + u16::saturating_sub(a[1usize], b[1usize]), + u16::saturating_sub(a[2usize], b[2usize]), + u16::saturating_sub(a[3usize], b[3usize]), + u16::saturating_sub(a[4usize], b[4usize]), + u16::saturating_sub(a[5usize], b[5usize]), + u16::saturating_sub(a[6usize], b[6usize]), + u16::saturating_sub(a[7usize], b[7usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { [ u16::wrapping_mul(a[0usize], b[0usize]), @@ -4225,6 +4297,16 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + [ + i32::saturating_sub(a[0usize], b[0usize]), + i32::saturating_sub(a[1usize], b[1usize]), + i32::saturating_sub(a[2usize], b[2usize]), + i32::saturating_sub(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { [ i32::wrapping_mul(a[0usize], b[0usize]), @@ -4614,6 +4696,16 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + [ + u32::saturating_sub(a[0usize], b[0usize]), + u32::saturating_sub(a[1usize], b[1usize]), + u32::saturating_sub(a[2usize], b[2usize]), + u32::saturating_sub(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { [ u32::wrapping_mul(a[0usize], b[0usize]), @@ -5445,6 +5537,14 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + [ + i64::saturating_sub(a[0usize], b[0usize]), + i64::saturating_sub(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -5735,6 +5835,14 @@ impl Simd for Fallback { .simd_into(self) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + [ + u64::saturating_sub(a[0usize], b[0usize]), + u64::saturating_sub(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_mul(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/neon.rs b/fearless_simd/src/generated/neon.rs index 00b378119..3664ece51 100644 --- a/fearless_simd/src/generated/neon.rs +++ b/fearless_simd/src/generated/neon.rs @@ -645,6 +645,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i8x16, b: i8x16) -> i8x16 { + vqsubq_s8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1048,6 +1058,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u8x16, b: u8x16) -> u8x16 { + vqsubq_u8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -1598,6 +1618,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i16x8, b: i16x8) -> i16x8 { + vqsubq_s16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2000,6 +2030,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u16x8, b: u16x8) -> u16x8 { + vqsubq_u16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -2575,6 +2615,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i32x4, b: i32x4) -> i32x4 { + vqsubq_s32(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -2987,6 +3037,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u32x4, b: u32x4) -> u32x4 { + vqsubq_u32(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -4036,6 +4096,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: i64x2, b: i64x2) -> i64x2 { + vqsubq_s64(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -4422,6 +4492,16 @@ impl Simd for Neon { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Neon, a: u64x2, b: u64x2) -> u64x2 { + vqsubq_u64(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_mul(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/simd_trait.rs b/fearless_simd/src/generated/simd_trait.rs index f4352b4e5..f09e6c66e 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -389,6 +389,8 @@ pub trait Simd: fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Compute the bitwise AND of two vectors."] @@ -494,6 +496,8 @@ pub trait Simd: fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Compute the bitwise AND of two vectors."] @@ -643,6 +647,8 @@ pub trait Simd: fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Compute the bitwise AND of two vectors."] @@ -763,6 +769,8 @@ pub trait Simd: fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Compute the bitwise AND of two vectors."] @@ -918,6 +926,8 @@ pub trait Simd: fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Compute the bitwise AND of two vectors."] @@ -1040,6 +1050,8 @@ pub trait Simd: fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Compute the bitwise AND of two vectors."] @@ -1337,6 +1349,8 @@ pub trait Simd: fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Compute the bitwise AND of two vectors."] @@ -1457,6 +1471,8 @@ pub trait Simd: fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Compute the bitwise AND of two vectors."] @@ -2034,6 +2050,16 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.sub_i8x16(a0, b0), self.sub_i8x16(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { + let (a0, a1) = self.split_i8x32(a); + let (b0, b1) = self.split_i8x32(b); + self.combine_i8x16( + self.saturating_sub_i8x16(a0, b0), + self.saturating_sub_i8x16(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { @@ -2317,6 +2343,16 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.sub_u8x16(a0, b0), self.sub_u8x16(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { + let (a0, a1) = self.split_u8x32(a); + let (b0, b1) = self.split_u8x32(b); + self.combine_u8x16( + self.saturating_sub_u8x16(a0, b0), + self.saturating_sub_u8x16(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { @@ -2706,6 +2742,16 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.sub_i16x8(a0, b0), self.sub_i16x8(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { + let (a0, a1) = self.split_i16x16(a); + let (b0, b1) = self.split_i16x16(b); + self.combine_i16x8( + self.saturating_sub_i16x8(a0, b0), + self.saturating_sub_i16x8(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { @@ -3021,6 +3067,16 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.sub_u16x8(a0, b0), self.sub_u16x8(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { + let (a0, a1) = self.split_u16x16(a); + let (b0, b1) = self.split_u16x16(b); + self.combine_u16x8( + self.saturating_sub_u16x8(a0, b0), + self.saturating_sub_u16x8(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { @@ -3433,6 +3489,16 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.sub_i32x4(a0, b0), self.sub_i32x4(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { + let (a0, a1) = self.split_i32x8(a); + let (b0, b1) = self.split_i32x8(b); + self.combine_i32x4( + self.saturating_sub_i32x4(a0, b0), + self.saturating_sub_i32x4(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { @@ -3750,6 +3816,16 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.sub_u32x4(a0, b0), self.sub_u32x4(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { + let (a0, a1) = self.split_u32x8(a); + let (b0, b1) = self.split_u32x8(b); + self.combine_u32x4( + self.saturating_sub_u32x4(a0, b0), + self.saturating_sub_u32x4(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { @@ -4570,6 +4646,16 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.sub_i64x2(a0, b0), self.sub_i64x2(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { + let (a0, a1) = self.split_i64x4(a); + let (b0, b1) = self.split_i64x4(b); + self.combine_i64x2( + self.saturating_sub_i64x2(a0, b0), + self.saturating_sub_i64x2(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { @@ -4879,6 +4965,16 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.sub_u64x2(a0, b0), self.sub_u64x2(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { + let (a0, a1) = self.split_u64x4(a); + let (b0, b1) = self.split_u64x4(b); + self.combine_u64x2( + self.saturating_sub_u64x2(a0, b0), + self.saturating_sub_u64x2(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { @@ -5684,6 +5780,16 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.sub_i8x32(a0, b0), self.sub_i8x32(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { + let (a0, a1) = self.split_i8x64(a); + let (b0, b1) = self.split_i8x64(b); + self.combine_i8x32( + self.saturating_sub_i8x32(a0, b0), + self.saturating_sub_i8x32(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { @@ -5965,6 +6071,16 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.sub_u8x32(a0, b0), self.sub_u8x32(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { + let (a0, a1) = self.split_u8x64(a); + let (b0, b1) = self.split_u8x64(b); + self.combine_u8x32( + self.saturating_sub_u8x32(a0, b0), + self.saturating_sub_u8x32(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { @@ -6350,6 +6466,16 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.sub_i16x16(a0, b0), self.sub_i16x16(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { + let (a0, a1) = self.split_i16x32(a); + let (b0, b1) = self.split_i16x32(b); + self.combine_i16x16( + self.saturating_sub_i16x16(a0, b0), + self.saturating_sub_i16x16(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { @@ -6669,6 +6795,16 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.sub_u16x16(a0, b0), self.sub_u16x16(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { + let (a0, a1) = self.split_u16x32(a); + let (b0, b1) = self.split_u16x32(b); + self.combine_u16x16( + self.saturating_sub_u16x16(a0, b0), + self.saturating_sub_u16x16(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { @@ -7090,6 +7226,16 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.sub_i32x8(a0, b0), self.sub_i32x8(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { + let (a0, a1) = self.split_i32x16(a); + let (b0, b1) = self.split_i32x16(b); + self.combine_i32x8( + self.saturating_sub_i32x8(a0, b0), + self.saturating_sub_i32x8(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { @@ -7409,6 +7555,16 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.sub_u32x8(a0, b0), self.sub_u32x8(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { + let (a0, a1) = self.split_u32x16(a); + let (b0, b1) = self.split_u32x16(b); + self.combine_u32x8( + self.saturating_sub_u32x8(a0, b0), + self.saturating_sub_u32x8(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { @@ -8223,6 +8379,16 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.sub_i64x4(a0, b0), self.sub_i64x4(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { + let (a0, a1) = self.split_i64x8(a); + let (b0, b1) = self.split_i64x8(b); + self.combine_i64x4( + self.saturating_sub_i64x4(a0, b0), + self.saturating_sub_i64x4(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { @@ -8530,6 +8696,16 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.sub_u64x4(a0, b0), self.sub_u64x4(a1, b1)) } + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[inline(always)] + fn saturating_sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { + let (a0, a1) = self.split_u64x8(a); + let (b0, b1) = self.split_u64x8(b); + self.combine_u64x4( + self.saturating_sub_u64x4(a0, b0), + self.saturating_sub_u64x4(a1, b1), + ) + } #[doc = "Multiply two vectors element-wise, wrapping on overflow."] #[inline(always)] fn mul_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { @@ -9563,6 +9739,8 @@ pub trait SimdInt: fn count_zeros(self) -> Self; #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] fn saturating_add(self, rhs: impl SimdInto) -> Self; + #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + fn saturating_sub(self, rhs: impl SimdInto) -> Self; } #[doc = r" Functionality implemented by SIMD masks."] #[doc = r""] diff --git a/fearless_simd/src/generated/simd_types.rs b/fearless_simd/src/generated/simd_types.rs index 74e3ea52a..e2d4059b9 100644 --- a/fearless_simd/src/generated/simd_types.rs +++ b/fearless_simd/src/generated/simd_types.rs @@ -603,6 +603,11 @@ impl crate::SimdInt for i8x16 { self.simd .saturating_add_i8x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i8x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x16 { type Widened = i16x8; @@ -896,6 +901,11 @@ impl crate::SimdInt for u8x16 { self.simd .saturating_add_u8x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u8x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x16 { type Widened = u16x8; @@ -1279,6 +1289,11 @@ impl crate::SimdInt for i16x8 { self.simd .saturating_add_i16x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i16x8(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x8 { type Widened = i32x4; @@ -1579,6 +1594,11 @@ impl crate::SimdInt for u16x8 { self.simd .saturating_add_u16x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u16x8(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x8 { type Widened = u32x4; @@ -1965,6 +1985,11 @@ impl crate::SimdInt for i32x4 { self.simd .saturating_add_i32x4(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i32x4(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x4 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -2265,6 +2290,11 @@ impl crate::SimdInt for u32x4 { self.simd .saturating_add_u32x4(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u32x4(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x4 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -3001,6 +3031,11 @@ impl crate::SimdInt for i64x2 { self.simd .saturating_add_i64x2(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] @@ -3294,6 +3329,11 @@ impl crate::SimdInt for u64x2 { self.simd .saturating_add_u64x2(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] @@ -4058,6 +4098,11 @@ impl crate::SimdInt for i8x32 { self.simd .saturating_add_i8x32(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i8x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x32 { type Widened = i16x16; @@ -4362,6 +4407,11 @@ impl crate::SimdInt for u8x32 { self.simd .saturating_add_u8x32(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u8x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x32 { type Widened = u16x16; @@ -4749,6 +4799,11 @@ impl crate::SimdInt for i16x16 { self.simd .saturating_add_i16x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i16x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x16 { type Widened = i32x8; @@ -5053,6 +5108,11 @@ impl crate::SimdInt for u16x16 { self.simd .saturating_add_u16x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u16x16(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x16 { type Widened = u32x8; @@ -5446,6 +5506,11 @@ impl crate::SimdInt for i32x8 { self.simd .saturating_add_i32x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i32x8(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x8 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -5753,6 +5818,11 @@ impl crate::SimdInt for u32x8 { self.simd .saturating_add_u32x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u32x8(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x8 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -6479,6 +6549,11 @@ impl crate::SimdInt for i64x4 { self.simd .saturating_add_i64x4(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] @@ -6767,6 +6842,11 @@ impl crate::SimdInt for u64x4 { self.simd .saturating_add_u64x4(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] @@ -7562,6 +7642,11 @@ impl crate::SimdInt for i8x64 { self.simd .saturating_add_i8x64(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i8x64(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i8x64 { type Widened = i16x32; @@ -7892,6 +7977,11 @@ impl crate::SimdInt for u8x64 { self.simd .saturating_add_u8x64(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u8x64(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u8x64 { type Widened = u16x32; @@ -8289,6 +8379,11 @@ impl crate::SimdInt for i16x32 { self.simd .saturating_add_i16x32(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i16x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for i16x32 { type Widened = i32x16; @@ -8603,6 +8698,11 @@ impl crate::SimdInt for u16x32 { self.simd .saturating_add_u16x32(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u16x32(self, rhs.simd_into(self.simd)) + } } impl SimdWiden for u16x32 { type Widened = u32x16; @@ -8999,6 +9099,11 @@ impl crate::SimdInt for i32x16 { self.simd .saturating_add_i32x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_i32x16(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for i32x16 { #[doc = "Convert each floating-point element to a signed 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results."] @@ -9309,6 +9414,11 @@ impl crate::SimdInt for u32x16 { self.simd .saturating_add_u32x16(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_u32x16(self, rhs.simd_into(self.simd)) + } } impl SimdCvtTruncate> for u32x16 { #[doc = "Convert each floating-point element to an unsigned 32-bit integer, truncating towards zero.\n\nOut-of-range values or NaN will produce implementation-defined results.\n\nOn x86 platforms below AVX-512, this operation will still be slower than converting to `i32`, because there is no native instruction for converting to `u32`.\nIf you know your values fit within range of an `i32`, you should convert to an `i32` and cast to your desired datatype afterwards."] @@ -10047,6 +10157,11 @@ impl crate::SimdInt for i64x8 { self.simd .saturating_add_i64x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] @@ -10341,6 +10456,11 @@ impl crate::SimdInt for u64x8 { self.simd .saturating_add_u64x8(self, rhs.simd_into(self.simd)) } + #[inline(always)] + fn saturating_sub(self, rhs: impl SimdInto) -> Self { + self.simd + .saturating_sub_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."] diff --git a/fearless_simd/src/generated/sse2.rs b/fearless_simd/src/generated/sse2.rs index b87f36794..7af74a278 100644 --- a/fearless_simd/src/generated/sse2.rs +++ b/fearless_simd/src/generated/sse2.rs @@ -906,6 +906,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: i8x16, b: i8x16) -> i8x16 { + _mm_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1674,6 +1684,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: u8x16, b: u8x16) -> u8x16 { + _mm_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2440,6 +2460,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: i16x8, b: i16x8) -> i16x8 { + _mm_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2948,6 +2978,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse2, a: u16x8, b: u16x8) -> u16x8 { + _mm_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3725,6 +3765,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + [ + i32::saturating_sub(a[0usize], b[0usize]), + i32::saturating_sub(a[1usize], b[1usize]), + i32::saturating_sub(a[2usize], b[2usize]), + i32::saturating_sub(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { [ i32::wrapping_mul(a[0usize], b[0usize]), @@ -4264,6 +4314,16 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + [ + u32::saturating_sub(a[0usize], b[0usize]), + u32::saturating_sub(a[1usize], b[1usize]), + u32::saturating_sub(a[2usize], b[2usize]), + u32::saturating_sub(a[3usize], b[3usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { [ u32::wrapping_mul(a[0usize], b[0usize]), @@ -5527,6 +5587,14 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + [ + i64::saturating_sub(a[0usize], b[0usize]), + i64::saturating_sub(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -5953,6 +6021,14 @@ impl Simd for Sse2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + [ + u64::saturating_sub(a[0usize], b[0usize]), + u64::saturating_sub(a[1usize], b[1usize]), + ] + .simd_into(self) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_mul(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/sse4_2.rs b/fearless_simd/src/generated/sse4_2.rs index 184b10d4c..dc189489d 100644 --- a/fearless_simd/src/generated/sse4_2.rs +++ b/fearless_simd/src/generated/sse4_2.rs @@ -953,6 +953,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i8x16, b: i8x16) -> i8x16 { + _mm_subs_epi8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { crate::kernel!( #[inline(always)] @@ -1536,6 +1546,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u8x16, b: u8x16) -> u8x16 { + _mm_subs_epu8(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { crate::kernel!( #[inline(always)] @@ -2238,6 +2258,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i16x8, b: i16x8) -> i16x8 { + _mm_subs_epi16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { crate::kernel!( #[inline(always)] @@ -2762,6 +2792,16 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u16x8, b: u16x8) -> u16x8 { + _mm_subs_epu16(a.into(), b.into()).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { crate::kernel!( #[inline(always)] @@ -3450,11 +3490,11 @@ impl Simd for Sse4_2 { fn kernel(token: Sse4_2, a: i32x4, b: i32x4) -> i32x4 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi32(a, b); - let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, sum), b); - let bound = _mm_xor_si128(_mm_srai_epi32::<31>(sum), _mm_set1_epi32(i32::MIN)); + let wrapped = _mm_add_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(a, wrapped), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); let result = _mm_blendv_ps( - _mm_castsi128_ps(sum), + _mm_castsi128_ps(wrapped), _mm_castsi128_ps(bound), _mm_castsi128_ps(overflow), ); @@ -3474,6 +3514,26 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i32x4, b: i32x4) -> i32x4 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi32(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi32(wrapped, a), b); + let bound = _mm_xor_si128(_mm_srai_epi32::<31>(a), _mm_set1_epi32(i32::MAX)); + let result = _mm_blendv_ps( + _mm_castsi128_ps(wrapped), + _mm_castsi128_ps(bound), + _mm_castsi128_ps(overflow), + ); + _mm_castps_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3974,6 +4034,18 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u32x4, b: u32x4) -> u32x4 { + let a = a.into(); + let b = b.into(); + _mm_sub_epi32(_mm_max_epu32(a, b), b).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { crate::kernel!( #[inline(always)] @@ -5252,12 +5324,12 @@ impl Simd for Sse4_2 { fn kernel(token: Sse4_2, a: i64x2, b: i64x2) -> i64x2 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi64(a, b); - let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, sum), b); - let sum_sign = _mm_srai_epi32::<31>(_mm_shuffle_epi32::<0xf5>(sum)); - let bound = _mm_xor_si128(sum_sign, _mm_set1_epi64x(i64::MIN)); + let wrapped = _mm_add_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(a, wrapped), b); + let a_sign = _mm_srai_epi32::<31>(_mm_shuffle_epi32::<0xf5>(a)); + let bound = _mm_xor_si128(a_sign, _mm_set1_epi64x(i64::MAX)); let result = _mm_blendv_pd( - _mm_castsi128_pd(sum), + _mm_castsi128_pd(wrapped), _mm_castsi128_pd(bound), _mm_castsi128_pd(overflow), ); @@ -5277,6 +5349,27 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: i64x2, b: i64x2) -> i64x2 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi64(a, b); + let overflow = _mm_xor_si128(_mm_cmpgt_epi64(wrapped, a), b); + let a_sign = _mm_srai_epi32::<31>(_mm_shuffle_epi32::<0xf5>(a)); + let bound = _mm_xor_si128(a_sign, _mm_set1_epi64x(i64::MAX)); + let result = _mm_blendv_pd( + _mm_castsi128_pd(wrapped), + _mm_castsi128_pd(bound), + _mm_castsi128_pd(overflow), + ); + _mm_castpd_si128(result).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { [ i64::wrapping_mul(a[0usize], b[0usize]), @@ -5723,11 +5816,13 @@ impl Simd for Sse4_2 { fn kernel(token: Sse4_2, a: u64x2, b: u64x2) -> u64x2 { let a = a.into(); let b = b.into(); - let sum = _mm_add_epi64(a, b); + let wrapped = _mm_add_epi64(a, b); let sign_bias = _mm_set1_epi64x(i64::MIN); - let overflow = - _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(sum, sign_bias)); - _mm_or_si128(sum, overflow).simd_into(token) + let overflow = _mm_cmpgt_epi64( + _mm_xor_si128(a, sign_bias), + _mm_xor_si128(wrapped, sign_bias), + ); + _mm_or_si128(wrapped, overflow).simd_into(token) } ); kernel(self, a, b) @@ -5743,6 +5838,22 @@ impl Simd for Sse4_2 { kernel(self, a, b) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + crate::kernel!( + #[inline(always)] + fn kernel(token: Sse4_2, a: u64x2, b: u64x2) -> u64x2 { + let a = a.into(); + let b = b.into(); + let wrapped = _mm_sub_epi64(a, b); + let sign_bias = _mm_set1_epi64x(i64::MIN); + let no_borrow = + _mm_cmpgt_epi64(_mm_xor_si128(a, sign_bias), _mm_xor_si128(b, sign_bias)); + _mm_and_si128(wrapped, no_borrow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { [ u64::wrapping_mul(a[0usize], b[0usize]), diff --git a/fearless_simd/src/generated/wasm.rs b/fearless_simd/src/generated/wasm.rs index ad41737a0..d836385d4 100644 --- a/fearless_simd/src/generated/wasm.rs +++ b/fearless_simd/src/generated/wasm.rs @@ -580,6 +580,10 @@ impl Simd for WasmSimd128 { i8x16_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { + i8x16_sub_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16 { let low = i16x8_extmul_low_i8x16(a.into(), b.into()); let high = i16x8_extmul_high_i8x16(a.into(), b.into()); @@ -966,6 +970,10 @@ impl Simd for WasmSimd128 { u8x16_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { + u8x16_sub_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16 { let low = u16x8_extmul_low_u8x16(a.into(), b.into()); let high = u16x8_extmul_high_u8x16(a.into(), b.into()); @@ -1421,6 +1429,10 @@ impl Simd for WasmSimd128 { i16x8_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { + i16x8_sub_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8 { i16x8_mul(a.into(), b.into()).simd_into(self) } @@ -1701,6 +1713,10 @@ impl Simd for WasmSimd128 { u16x8_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { + u16x8_sub_sat(a.into(), b.into()).simd_into(self) + } + #[inline(always)] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8 { u16x8_mul(a.into(), b.into()).simd_into(self) } @@ -2072,17 +2088,25 @@ impl Simd for WasmSimd128 { fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { let a: v128 = a.into(); let b: v128 = b.into(); - let sum = i32x4_add(a, b); - let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); - let overflow_mask = i32x4_shr(overflow_bits, 31); - let saturation = v128_xor(i32x4_shr(sum, 31), i32x4_splat(i32::MIN)); - v128_bitselect(saturation, sum, overflow_mask).simd_into(self) + let wrapped = i32x4_add(a, b); + let overflow_mask = i32x4_shr(v128_and(v128_xor(a, wrapped), v128_xor(b, wrapped)), 31); + let saturation = v128_xor(i32x4_shr(wrapped, 31), i32x4_splat(i32::MIN)); + v128_bitselect(saturation, wrapped, overflow_mask).simd_into(self) } #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { i32x4_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let wrapped = i32x4_sub(a, b); + let overflow_mask = i32x4_shr(v128_and(v128_xor(a, b), v128_xor(a, wrapped)), 31); + let saturation = v128_xor(i32x4_shr(wrapped, 31), i32x4_splat(i32::MIN)); + v128_bitselect(saturation, wrapped, overflow_mask).simd_into(self) + } + #[inline(always)] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { i32x4_mul(a.into(), b.into()).simd_into(self) } @@ -2351,6 +2375,12 @@ impl Simd for WasmSimd128 { u32x4_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { + let a: v128 = a.into(); + let b: v128 = b.into(); + u32x4_sub(u32x4_max(a, b), b).simd_into(self) + } + #[inline(always)] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4 { u32x4_mul(a.into(), b.into()).simd_into(self) } @@ -3025,17 +3055,25 @@ impl Simd for WasmSimd128 { fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { let a: v128 = a.into(); let b: v128 = b.into(); - let sum = i64x2_add(a, b); - let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); - let overflow_mask = i64x2_shr(overflow_bits, 63); - let saturation = v128_xor(i64x2_shr(sum, 63), i64x2_splat(i64::MIN)); - v128_bitselect(saturation, sum, overflow_mask).simd_into(self) + let wrapped = i64x2_add(a, b); + let overflow_mask = i64x2_shr(v128_and(v128_xor(a, wrapped), v128_xor(b, wrapped)), 63); + let saturation = v128_xor(i64x2_shr(wrapped, 63), i64x2_splat(i64::MIN)); + v128_bitselect(saturation, wrapped, overflow_mask).simd_into(self) } #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { i64x2_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let wrapped = i64x2_sub(a, b); + let overflow_mask = i64x2_shr(v128_and(v128_xor(a, b), v128_xor(a, wrapped)), 63); + let saturation = v128_xor(i64x2_shr(wrapped, 63), i64x2_splat(i64::MIN)); + v128_bitselect(saturation, wrapped, overflow_mask).simd_into(self) + } + #[inline(always)] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { i64x2_mul(a.into(), b.into()).simd_into(self) } @@ -3292,16 +3330,25 @@ impl Simd for WasmSimd128 { fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { let a: v128 = a.into(); let b: v128 = b.into(); - let sum = u64x2_add(a, b); + let wrapped = u64x2_add(a, b); let sign_bit = i64x2_splat(i64::MIN); - let overflow_mask = i64x2_gt(v128_xor(a, sign_bit), v128_xor(sum, sign_bit)); - v128_or(sum, overflow_mask).simd_into(self) + let saturation_mask = i64x2_gt(v128_xor(a, sign_bit), v128_xor(wrapped, sign_bit)); + v128_or(wrapped, saturation_mask).simd_into(self) } #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { u64x2_sub(a.into(), b.into()).simd_into(self) } #[inline(always)] + fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { + let a: v128 = a.into(); + let b: v128 = b.into(); + let wrapped = u64x2_sub(a, b); + let sign_bit = i64x2_splat(i64::MIN); + let saturation_mask = i64x2_gt(v128_xor(b, sign_bit), v128_xor(a, sign_bit)); + v128_andnot(wrapped, saturation_mask).simd_into(self) + } + #[inline(always)] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { u64x2_mul(a.into(), b.into()).simd_into(self) } diff --git a/fearless_simd_gen/src/arch/fallback.rs b/fearless_simd_gen/src/arch/fallback.rs index dcba5e1d1..158e7940f 100644 --- a/fearless_simd_gen/src/arch/fallback.rs +++ b/fearless_simd_gen/src/arch/fallback.rs @@ -30,6 +30,7 @@ pub(crate) fn translate_op(op: &str, is_float: bool) -> Option<&'static str> { } } "saturating_add" => "saturating_add", + "saturating_sub" => "saturating_sub", "sub" => { if is_float { "sub" diff --git a/fearless_simd_gen/src/arch/neon.rs b/fearless_simd_gen/src/arch/neon.rs index 0bceb0199..e632627f7 100644 --- a/fearless_simd_gen/src/arch/neon.rs +++ b/fearless_simd_gen/src/arch/neon.rs @@ -16,6 +16,7 @@ fn translate_op(op: &str) -> Option<&'static str> { "sqrt" => "vsqrt", "add" => "vadd", "saturating_add" => "vqadd", + "saturating_sub" => "vqsub", "sub" => "vsub", "mul" => "vmul", "div" => "vdiv", diff --git a/fearless_simd_gen/src/arch/wasm.rs b/fearless_simd_gen/src/arch/wasm.rs index 42449730f..47a67d5a2 100644 --- a/fearless_simd_gen/src/arch/wasm.rs +++ b/fearless_simd_gen/src/arch/wasm.rs @@ -16,6 +16,7 @@ fn translate_op(op: &str) -> Option<&'static str> { "sqrt" => "sqrt", "add" => "add", "saturating_add" => "add_sat", + "saturating_sub" => "sub_sat", // TODO: Is wrapping sub same on WASM? "sub" => "sub", "mul" => "mul", diff --git a/fearless_simd_gen/src/mk_fallback.rs b/fearless_simd_gen/src/mk_fallback.rs index 8c9aee102..89e5faf0f 100644 --- a/fearless_simd_gen/src/mk_fallback.rs +++ b/fearless_simd_gen/src/mk_fallback.rs @@ -892,6 +892,7 @@ fn rhs_reference(method: &str) -> bool { | "wrapping_mul" | "wrapping_add" | "saturating_add" + | "saturating_sub" | "wrapping_shl" | "wrapping_shr" ) diff --git a/fearless_simd_gen/src/mk_wasm.rs b/fearless_simd_gen/src/mk_wasm.rs index 4803f1409..dd9085d9b 100644 --- a/fearless_simd_gen/src/mk_wasm.rs +++ b/fearless_simd_gen/src/mk_wasm.rs @@ -11,7 +11,9 @@ use crate::generic::{ recursive_swizzle_dyn_precise_body, reverse_method, reverse_vector_mask_method, }; use crate::level::Level; -use crate::ops::{NarrowingMode, Op, Quantifier, SlideGranularity, relaxed_narrow_method}; +use crate::ops::{ + NarrowingMode, Op, Quantifier, SaturatingOp, SlideGranularity, relaxed_narrow_method, +}; use crate::{ arch::wasm::{self, simple_intrinsic}, ops::OpSig, @@ -219,24 +221,26 @@ fn reduce_sum(method_sig: TokenStream, vec_ty: &VecType) -> TokenStream { } } -fn saturating_add_method(op: Op, vec_ty: &VecType) -> TokenStream { +fn saturating_add_sub_method(op: Op, vec_ty: &VecType, arithmetic: SaturatingOp) -> TokenStream { + use SaturatingOp::{Add, Sub}; + assert_eq!( vec_ty.n_bits(), 128, - "WASM saturating-add lowering only handles one native vector" + "WASM saturating add/sub lowering only handles one native vector" ); assert!( matches!(vec_ty.scalar, ScalarType::Int | ScalarType::Unsigned), - "saturating_add is only defined for integers" + "saturating add/sub is only defined for integers" ); let method_sig = op.simd_trait_method_sig(vec_ty); if matches!(vec_ty.scalar_bits, 8 | 16) { - let expr = wasm::expr( - "saturating_add", - vec_ty, - &[quote! { a.into() }, quote! { b.into() }], - ); + let method = match arithmetic { + Add => "saturating_add", + Sub => "saturating_sub", + }; + let expr = wasm::expr(method, vec_ty, &[quote! { a.into() }, quote! { b.into() }]); return quote! { #method_sig { #expr.simd_into(self) @@ -244,51 +248,86 @@ fn saturating_add_method(op: Op, vec_ty: &VecType) -> TokenStream { }; } - let add = simple_intrinsic("add", vec_ty); + let wrapping = simple_intrinsic( + match arithmetic { + Add => "add", + Sub => "sub", + }, + vec_ty, + ); let body = match (vec_ty.scalar, vec_ty.scalar_bits) { (ScalarType::Unsigned, 32) => { - // Clamp `a` to the greatest value that can be added to `b`, then add. - // `!b` is `u32::MAX - b` lane-wise. - let min = simple_intrinsic("min", vec_ty); + let clamped = match arithmetic { + Add => { + // Clamp `a` to the greatest value that can be added to `b`. + // `!b` is `u32::MAX - b` lane-wise. + let min = simple_intrinsic("min", vec_ty); + quote! { #min(a, v128_not(b)) } + } + Sub => { + // Raising `a` to at least `b` makes an underflowing difference zero. + let max = simple_intrinsic("max", vec_ty); + quote! { #max(a, b) } + } + }; quote! { - #add(#min(a, v128_not(b)), b) + #wrapping(#clamped, b) } } (ScalarType::Unsigned, 64) => { - // WebAssembly has no unsigned i64x2 comparison. Flip the sign bit so - // the signed ordering matches unsigned ordering, then detect carry by - // checking whether the wrapping sum is less than the first addend. let signed_ty = vec_ty.cast(ScalarType::Int); let signed_gt = simple_intrinsic("gt", &signed_ty); let signed_splat = simple_intrinsic("splat", &signed_ty); let signed_scalar = signed_ty.scalar.rust(signed_ty.scalar_bits); - quote! { - let sum = #add(a, b); - let sign_bit = #signed_splat(#signed_scalar::MIN); - let overflow_mask = #signed_gt( - v128_xor(a, sign_bit), - v128_xor(sum, sign_bit), - ); - v128_or(sum, overflow_mask) + match arithmetic { + Add => quote! { + // WebAssembly has no unsigned i64x2 comparison. Flip the sign + // bit so signed ordering matches unsigned ordering, then detect + // carry by checking whether the sum is less than `a`. + let wrapped = #wrapping(a, b); + let sign_bit = #signed_splat(#signed_scalar::MIN); + let saturation_mask = #signed_gt( + v128_xor(a, sign_bit), + v128_xor(wrapped, sign_bit), + ); + v128_or(wrapped, saturation_mask) + }, + Sub => quote! { + // In sign-biased unsigned ordering, `b > a` identifies the lanes + // whose wrapping difference must be replaced with zero. + let wrapped = #wrapping(a, b); + let sign_bit = #signed_splat(#signed_scalar::MIN); + let saturation_mask = #signed_gt( + v128_xor(b, sign_bit), + v128_xor(a, sign_bit), + ); + v128_andnot(wrapped, saturation_mask) + }, } } (ScalarType::Int, 32 | 64) => { - // Signed overflow has the same sign in `(sum ^ a)` and `(sum ^ b)`. - // Expand that sign bit into a lane mask, then select the appropriate - // endpoint. A wrapped negative sum means positive overflow and vice versa. let shr = simple_intrinsic("shr", vec_ty); let splat = simple_intrinsic("splat", vec_ty); let scalar = vec_ty.scalar.rust(vec_ty.scalar_bits); let sign_shift = Literal::u32_unsuffixed((vec_ty.scalar_bits - 1) as u32); + let overflow_bits = match arithmetic { + // `(a ^ wrapped) & (b ^ wrapped)` has its sign bit set exactly + // when signed addition overflows. + Add => quote! { v128_and(v128_xor(a, wrapped), v128_xor(b, wrapped)) }, + // `(a ^ b) & (a ^ wrapped)` has its sign bit set exactly when + // signed subtraction overflows. + Sub => quote! { v128_and(v128_xor(a, b), v128_xor(a, wrapped)) }, + }; quote! { - let sum = #add(a, b); - let overflow_bits = v128_and(v128_xor(sum, a), v128_xor(sum, b)); - let overflow_mask = #shr(overflow_bits, #sign_shift); + let wrapped = #wrapping(a, b); + let overflow_mask = #shr(#overflow_bits, #sign_shift); + // On overflow, the wrapped result has the opposite sign from the + // saturation endpoint. This bound works for both add and subtract. let saturation = v128_xor( - #shr(sum, #sign_shift), + #shr(wrapped, #sign_shift), #splat(#scalar::MIN), ); - v128_bitselect(saturation, sum, overflow_mask) + v128_bitselect(saturation, wrapped, overflow_mask) } } _ => unreachable!(), @@ -564,8 +603,13 @@ impl Level for WasmSimd128 { mode: NarrowingMode::Relaxed, } => relaxed_narrow_method(op, vec_ty, target_ty, "narrow"), OpSig::Binary => { - if method == "saturating_add" { - return saturating_add_method(op, vec_ty); + let saturating_op = match method { + "saturating_add" => Some(SaturatingOp::Add), + "saturating_sub" => Some(SaturatingOp::Sub), + _ => None, + }; + if let Some(arithmetic) = saturating_op { + return saturating_add_sub_method(op, vec_ty, arithmetic); } if matches!(method, "shlv" | "shrv") diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index a4c9a6989..547ddcabf 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -12,7 +12,9 @@ use crate::generic::{ recursive_swizzle_dyn_precise_body, reverse_method, reverse_vector_mask_method, }; use crate::level::Level; -use crate::ops::{NarrowingMode, Op, OpSig, Quantifier, SlideGranularity, relaxed_narrow_method}; +use crate::ops::{ + NarrowingMode, Op, OpSig, Quantifier, SaturatingOp, SlideGranularity, relaxed_narrow_method, +}; use crate::types::{ScalarType, VecType}; use proc_macro2::{Ident, Literal, Span, TokenStream}; use quote::{ToTokens as _, format_ident, quote}; @@ -2275,16 +2277,30 @@ impl X86 { } } - fn handle_saturating_add(&self, op: Op, vec_ty: &VecType) -> TokenStream { + fn handle_saturating_add_sub( + &self, + op: Op, + saturating_op: SaturatingOp, + vec_ty: &VecType, + ) -> TokenStream { + use SaturatingOp::{Add, Sub}; use ScalarType::{Float, Int, Unsigned}; + assert!(matches!(vec_ty.scalar, Int | Unsigned)); + match (*self, vec_ty.scalar, vec_ty.scalar_bits, vec_ty.n_bits()) { // x86 has native instructions for 8-bit and 16-bit elements only. (_, _, 8 | 16, _) => { - let adds = simple_intrinsic("adds", vec_ty); + let intrinsic = simple_intrinsic( + match saturating_op { + Add => "adds", + Sub => "subs", + }, + vec_ty, + ); self.kernel_method(op, vec_ty, |token| { quote! { - #adds(a.into(), b.into()).simd_into(#token) + #intrinsic(a.into(), b.into()).simd_into(#token) } }) } @@ -2298,21 +2314,39 @@ impl X86 { _ => unreachable!(), }; let suffix = format!("epi{lane_bits}"); + let arithmetic = intrinsic_ident( + match saturating_op { + Add => "add", + Sub => "sub", + }, + &suffix, + bits, + ); let add = intrinsic_ident("add", &suffix, bits); let shift_right_logical = intrinsic_ident("srli", &suffix, bits); let shift_right_arithmetic = intrinsic_ident("srai", &suffix, bits); let set1 = set1_intrinsic(vec_ty); let ternary = intrinsic_ident("ternarylogic", &suffix, bits); + let overflow_bits = match saturating_op { + Add => quote! { + // 0x42 computes `(a ^ wrapped) & (b ^ wrapped)`. + let overflow_bits = #ternary::<0x42>(a, b, wrapped); + }, + Sub => quote! { + // 0x18 computes `(a ^ b) & (a ^ wrapped)`. + let overflow_bits = #ternary::<0x18>(a, b, wrapped); + }, + }; self.kernel_method(op, vec_ty, |token| { quote! { let a = a.into(); let b = b.into(); - let sum = #add(a, b); + let wrapped = #arithmetic(a, b); - // The 0x42 truth table computes `(a ^ sum) & (b ^ sum)`, whose - // sign bit is set exactly when signed addition overflows. - let overflow_bits = #ternary::<0x42>(a, b, sum); + // The sign bit of this expression is set exactly when the + // signed arithmetic operation overflows. + #overflow_bits let overflow_mask = #shift_right_arithmetic::<#shift>(overflow_bits); @@ -2324,37 +2358,79 @@ impl X86 { ); // 0xca is a bitwise select: use `direction` where overflowed, - // and the wrapped sum everywhere else. - #ternary::<0xca>(overflow_mask, direction, sum).simd_into(#token) + // and the wrapped result everywhere else. + #ternary::<0xca>(overflow_mask, direction, wrapped).simd_into(#token) } }) } (Self::Sse4_2 | Self::Avx2, Unsigned, 32, _bits) | (Self::Avx512, Unsigned, 32 | 64, _bits) => { - let bits = vec_ty.n_bits(); - let add = simple_sign_unaware_intrinsic("add", vec_ty); - let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); - let set1 = set1_intrinsic(vec_ty); - let min = simple_intrinsic("min", vec_ty); - - // Unsigned saturation can be expressed without detecting carry: - // clamp `a` to the greatest value that can be added to `b`, then add. + let expression = match saturating_op { + Add => { + let bits = vec_ty.n_bits(); + let add = simple_sign_unaware_intrinsic("add", vec_ty); + let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); + let set1 = set1_intrinsic(vec_ty); + let min = simple_intrinsic("min", vec_ty); + quote! { + // Clamp `a` to the greatest value that can be added to `b`. + let threshold = #xor(b, #set1(-1)); + #add(#min(a, threshold), b) + } + } + Sub => { + let sub = simple_sign_unaware_intrinsic("sub", vec_ty); + let max = simple_intrinsic("max", vec_ty); + quote! { + // Clamping `a` upward to `b` makes underflow produce zero. + #sub(#max(a, b), b) + } + } + }; self.kernel_method(op, vec_ty, |token| { quote! { let a = a.into(); let b = b.into(); - let threshold = #xor(b, #set1(-1)); - #add(#min(a, threshold), b).simd_into(#token) + #expression.simd_into(#token) } }) } (Self::Sse4_2 | Self::Avx2, Unsigned, 64, _bits) => { let bits = vec_ty.n_bits(); - let add = simple_sign_unaware_intrinsic("add", vec_ty); + let arithmetic = simple_sign_unaware_intrinsic( + match saturating_op { + Add => "add", + Sub => "sub", + }, + vec_ty, + ); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); let set1 = set1_intrinsic(vec_ty); let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); - let or = intrinsic_ident("or", coarse_type(vec_ty), bits); + let finish = match saturating_op { + Add => { + let or = intrinsic_ident("or", coarse_type(vec_ty), bits); + quote! { + let overflow = #cmpgt( + #xor(a, sign_bias), + #xor(wrapped, sign_bias), + ); + #or(wrapped, overflow) + } + } + Sub => { + let and = intrinsic_ident("and", coarse_type(vec_ty), bits); + quote! { + // A strict comparison is sufficient: when `a == b`, the + // wrapped difference is already zero. + let no_borrow = #cmpgt( + #xor(a, sign_bias), + #xor(b, sign_bias), + ); + #and(wrapped, no_borrow) + } + } + }; // SSE4.2 and AVX2 have signed, but not unsigned, qword comparisons. // Flipping the sign bit maps unsigned order onto signed order. @@ -2362,17 +2438,20 @@ impl X86 { quote! { let a = a.into(); let b = b.into(); - let sum = #add(a, b); + let wrapped = #arithmetic(a, b); let sign_bias = #set1(i64::MIN); - let overflow = #cmpgt( - #xor(a, sign_bias), - #xor(sum, sign_bias), - ); - #or(sum, overflow).simd_into(#token) + #finish.simd_into(#token) } }) } (Self::Sse4_2 | Self::Avx2, Int, lane_bits @ (32 | 64), bits) => { + let arithmetic = simple_sign_unaware_intrinsic( + match saturating_op { + Add => "add", + Sub => "sub", + }, + vec_ty, + ); let add = simple_sign_unaware_intrinsic("add", vec_ty); let xor = intrinsic_ident("xor", coarse_type(vec_ty), bits); let set1 = set1_intrinsic(vec_ty); @@ -2381,15 +2460,15 @@ impl X86 { (_, 32) => { let shift = intrinsic_ident("srai", "epi32", bits); quote! { - let bound = #xor(#shift::<31>(sum), #set1(i32::MIN)); + let bound = #xor(#shift::<31>(a), #set1(i32::MAX)); } } (Self::Sse4_2, 64) => { let shuffle = intrinsic_ident("shuffle", "epi32", bits); let shift = intrinsic_ident("srai", "epi32", bits); quote! { - let sum_sign = #shift::<31>(#shuffle::<0xf5>(sum)); - let bound = #xor(sum_sign, #set1(i64::MIN)); + let a_sign = #shift::<31>(#shuffle::<0xf5>(a)); + let bound = #xor(a_sign, #set1(i64::MAX)); } } (Self::Avx2, 64) => { @@ -2402,6 +2481,10 @@ impl X86 { } _ => unreachable!(), }; + let comparison = match saturating_op { + Add => quote! { #cmpgt(a, wrapped) }, + Sub => quote! { #cmpgt(wrapped, a) }, + }; let to_float = cast_ident(Int, Float, lane_bits, lane_bits, bits); let to_int = cast_ident(Float, Int, lane_bits, lane_bits, bits); let blend_suffix = match lane_bits { @@ -2414,14 +2497,14 @@ impl X86 { quote! { let a = a.into(); let b = b.into(); - let sum = #add(a, b); + let wrapped = #arithmetic(a, b); // Only the sign bit of each lane is meaningful here, so use a // lane-granularity floating-point blend instead of BLENDV_EPI8. - let overflow = #xor(#cmpgt(a, sum), b); + let overflow = #xor(#comparison, b); #bound let result = #blend( - #to_float(sum), + #to_float(wrapped), #to_float(bound), #to_float(overflow), ); @@ -2436,8 +2519,12 @@ impl X86 { pub(crate) fn handle_binary(&self, op: Op, method: &str, vec_ty: &VecType) -> TokenStream { let method_sig = op.simd_trait_method_sig(vec_ty); - if method == "saturating_add" { - return self.handle_saturating_add(op, vec_ty); + if let Some(saturating_op) = match method { + "saturating_add" => Some(SaturatingOp::Add), + "saturating_sub" => Some(SaturatingOp::Sub), + _ => None, + } { + return self.handle_saturating_add_sub(op, saturating_op, vec_ty); } if *self == Self::Avx512 && vec_ty.scalar == ScalarType::Mask { diff --git a/fearless_simd_gen/src/ops.rs b/fearless_simd_gen/src/ops.rs index 616259bfc..392a0549f 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -41,6 +41,13 @@ pub(crate) enum NarrowingMode { Saturate, Relaxed, } + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) enum SaturatingOp { + Add, + Sub, +} + #[derive(Clone, Copy)] pub(crate) enum OpSig { /// Takes a single scalar argument, and returns the corresponding vector type. @@ -983,6 +990,12 @@ const INT_OPS: &[Op] = &[ OpSig::Binary, "Subtract two vectors element-wise, wrapping on overflow.", ), + Op::new( + "saturating_sub", + OpKind::VecTraitMethod, + OpSig::Binary, + "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing.", + ), Op::new( "mul", OpKind::Overloaded(CoreOpTrait::Mul), diff --git a/fearless_simd_tests/tests/generics.rs b/fearless_simd_tests/tests/generics.rs index f540ae636..8b087eaa9 100644 --- a/fearless_simd_tests/tests/generics.rs +++ b/fearless_simd_tests/tests/generics.rs @@ -41,6 +41,10 @@ fn generic_saturating_add>(lhs: V, rhs: V) -> V { lhs.saturating_add(rhs) } +fn generic_saturating_sub>(lhs: V, rhs: V) -> V { + lhs.saturating_sub(rhs) +} + // 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/mod.rs b/fearless_simd_tests/tests/harness/ops/mod.rs index 7a33fa2aa..71c5706a3 100644 --- a/fearless_simd_tests/tests/harness/ops/mod.rs +++ b/fearless_simd_tests/tests/harness/ops/mod.rs @@ -67,6 +67,7 @@ mod rotate_elements_left; mod rotate_elements_right; mod round_ties_even; mod saturating_add; +mod saturating_sub; mod select; mod set; mod shift_elements_left; diff --git a/fearless_simd_tests/tests/harness/ops/saturating_add.rs b/fearless_simd_tests/tests/harness/ops/saturating_add.rs new file mode 100644 index 000000000..ebf0acb54 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/saturating_add.rs @@ -0,0 +1,1539 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +// One concrete test row per supported integer vector type. + +#[simd_test] +fn saturating_add_i8x16(simd: S) { + let a = i8x16::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + ], + ); + let b = i8x16::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_i8x32(simd: S) { + let a = i8x32::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + ], + ); + let b = i8x32::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_i8x64(simd: S) { + let a = i8x64::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + ], + ); + let b = i8x64::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i8::MAX, + i8::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + -25, + 25, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_u8x16(simd: S) { + let a = u8x16::from_slice( + simd, + &[ + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + ], + ); + let b = u8x16::from_slice( + simd, + &[ + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_u8x32(simd: S) { + let a = u8x32::from_slice( + simd, + &[ + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + ], + ); + let b = u8x32::from_slice( + simd, + &[ + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_u8x64(simd: S) { + let a = u8x64::from_slice( + simd, + &[ + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + u8::MAX, + u8::MAX - 1, + u8::MAX - 1, + 0, + 40, + 100, + u8::MAX - 10, + 10, + 0, + 1, + 0, + u8::MAX, + u8::MAX / 2, + u8::MAX / 2, + u8::MAX / 2 + 1, + 17, + ], + ); + let b = u8x64::from_slice( + simd, + &[ + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + 1, + 2, + 1, + u8::MAX, + 20, + 20, + 9, + u8::MAX - 5, + 0, + 0, + 1, + 0, + u8::MAX / 2, + u8::MAX / 2 + 1, + u8::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + u8::MAX, + u8::MAX, + u8::MAX, + u8::MAX, + 60, + 120, + u8::MAX - 1, + u8::MAX, + 0, + 1, + 1, + u8::MAX, + u8::MAX - 1, + u8::MAX, + u8::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_i16x8(simd: S) { + let a = i16x8::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 40, + -40, + 50, + -50, + ], + ); + let b = i16x8::from_slice(simd, &[1, -1, 1, -1, 20, -20, -75, 75]); + assert_eq!( + *a.saturating_add(b), + [i16::MAX, i16::MIN, i16::MAX, i16::MIN, 60, -60, -25, 25] + ); +} + +#[simd_test] +fn saturating_add_i16x16(simd: S) { + let a = i16x16::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 10, + i16::MIN + 10, + 1, + -1, + ], + ); + let b = i16x16::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i16::MAX, + i16::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -25, + 25, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_i16x32(simd: S) { + let a = i16x32::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 10, + i16::MIN + 10, + 1, + -1, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 10, + i16::MIN + 10, + 1, + -1, + ], + ); + let b = i16x32::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i16::MAX, + i16::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i16::MAX, + i16::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -25, + 25, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -25, + 25, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_u16x8(simd: S) { + let a = u16x8::from_slice( + simd, + &[ + u16::MAX, + u16::MAX - 1, + u16::MAX - 1, + 0, + 40, + 100, + u16::MAX - 10, + 10, + ], + ); + let b = u16x8::from_slice(simd, &[1, 2, 1, u16::MAX, 20, 20, 9, u16::MAX - 5]); + assert_eq!( + *a.saturating_add(b), + [ + u16::MAX, + u16::MAX, + u16::MAX, + u16::MAX, + 60, + 120, + u16::MAX - 1, + u16::MAX + ] + ); +} + +#[simd_test] +fn saturating_add_u16x16(simd: S) { + let a = u16x16::from_slice( + simd, + &[ + u16::MAX, + u16::MAX - 1, + u16::MAX - 1, + 0, + 40, + 100, + u16::MAX - 10, + 10, + 0, + 1, + 0, + u16::MAX, + u16::MAX / 2, + u16::MAX / 2, + u16::MAX / 2 + 1, + 17, + ], + ); + let b = u16x16::from_slice( + simd, + &[ + 1, + 2, + 1, + u16::MAX, + 20, + 20, + 9, + u16::MAX - 5, + 0, + 0, + 1, + 0, + u16::MAX / 2, + u16::MAX / 2 + 1, + u16::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u16::MAX, + u16::MAX, + u16::MAX, + u16::MAX, + 60, + 120, + u16::MAX - 1, + u16::MAX, + 0, + 1, + 1, + u16::MAX, + u16::MAX - 1, + u16::MAX, + u16::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_u16x32(simd: S) { + let a = u16x32::from_slice( + simd, + &[ + u16::MAX, + u16::MAX - 1, + u16::MAX - 1, + 0, + 40, + 100, + u16::MAX - 10, + 10, + 0, + 1, + 0, + u16::MAX, + u16::MAX / 2, + u16::MAX / 2, + u16::MAX / 2 + 1, + 17, + u16::MAX, + u16::MAX - 1, + u16::MAX - 1, + 0, + 40, + 100, + u16::MAX - 10, + 10, + 0, + 1, + 0, + u16::MAX, + u16::MAX / 2, + u16::MAX / 2, + u16::MAX / 2 + 1, + 17, + ], + ); + let b = u16x32::from_slice( + simd, + &[ + 1, + 2, + 1, + u16::MAX, + 20, + 20, + 9, + u16::MAX - 5, + 0, + 0, + 1, + 0, + u16::MAX / 2, + u16::MAX / 2 + 1, + u16::MAX / 2 + 1, + 23, + 1, + 2, + 1, + u16::MAX, + 20, + 20, + 9, + u16::MAX - 5, + 0, + 0, + 1, + 0, + u16::MAX / 2, + u16::MAX / 2 + 1, + u16::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u16::MAX, + u16::MAX, + u16::MAX, + u16::MAX, + 60, + 120, + u16::MAX - 1, + u16::MAX, + 0, + 1, + 1, + u16::MAX, + u16::MAX - 1, + u16::MAX, + u16::MAX, + 40, + u16::MAX, + u16::MAX, + u16::MAX, + u16::MAX, + 60, + 120, + u16::MAX - 1, + u16::MAX, + 0, + 1, + 1, + u16::MAX, + u16::MAX - 1, + u16::MAX, + u16::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_i32x4(simd: S) { + let a = i32x4::from_slice(simd, &[i32::MAX, i32::MIN, i32::MAX - 1, i32::MIN + 1]); + let b = i32x4::from_slice(simd, &[1, -1, 1, -1]); + assert_eq!( + *a.saturating_add(b), + [i32::MAX, i32::MIN, i32::MAX, i32::MIN] + ); + + let scalar_rhs = i32x4::from_slice(simd, &[i32::MAX, i32::MIN, 40, -40]); + assert_eq!( + *scalar_rhs.saturating_add(20), + [i32::MAX, i32::MIN + 20, 60, -20] + ); +} + +#[simd_test] +fn saturating_add_i32x8(simd: S) { + let a = i32x8::from_slice( + simd, + &[ + i32::MAX, + i32::MIN, + i32::MAX - 1, + i32::MIN + 1, + 40, + -40, + 50, + -50, + ], + ); + let b = i32x8::from_slice(simd, &[1, -1, 1, -1, 20, -20, -75, 75]); + assert_eq!( + *a.saturating_add(b), + [i32::MAX, i32::MIN, i32::MAX, i32::MIN, 60, -60, -25, 25] + ); +} + +#[simd_test] +fn saturating_add_i32x16(simd: S) { + let a = i32x16::from_slice( + simd, + &[ + i32::MAX, + i32::MIN, + i32::MAX - 1, + i32::MIN + 1, + 40, + -40, + 50, + -50, + 0, + 0, + i32::MAX, + i32::MIN, + i32::MAX - 10, + i32::MIN + 10, + 1, + -1, + ], + ); + let b = i32x16::from_slice( + simd, + &[ + 1, + -1, + 1, + -1, + 20, + -20, + -75, + 75, + i32::MAX, + i32::MIN, + 0, + 0, + 9, + -9, + -1, + 1, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + i32::MAX, + i32::MIN, + i32::MAX, + i32::MIN, + 60, + -60, + -25, + 25, + i32::MAX, + i32::MIN, + i32::MAX, + i32::MIN, + i32::MAX - 1, + i32::MIN + 1, + 0, + 0, + ] + ); +} + +#[simd_test] +fn saturating_add_u32x4(simd: S) { + let a = u32x4::from_slice(simd, &[u32::MAX, u32::MAX - 1, 40, 0]); + let b = u32x4::from_slice(simd, &[1, 2, 20, u32::MAX]); + assert_eq!(*a.saturating_add(b), [u32::MAX, u32::MAX, 60, u32::MAX]); +} + +#[simd_test] +fn saturating_add_u32x8(simd: S) { + let a = u32x8::from_slice( + simd, + &[ + u32::MAX, + u32::MAX - 1, + u32::MAX - 1, + 0, + 40, + 100, + u32::MAX - 10, + 10, + ], + ); + let b = u32x8::from_slice(simd, &[1, 2, 1, u32::MAX, 20, 20, 9, u32::MAX - 5]); + assert_eq!( + *a.saturating_add(b), + [ + u32::MAX, + u32::MAX, + u32::MAX, + u32::MAX, + 60, + 120, + u32::MAX - 1, + u32::MAX + ] + ); +} + +#[simd_test] +fn saturating_add_u32x16(simd: S) { + let a = u32x16::from_slice( + simd, + &[ + u32::MAX, + u32::MAX - 1, + u32::MAX - 1, + 0, + 40, + 100, + u32::MAX - 10, + 10, + 0, + 1, + 0, + u32::MAX, + u32::MAX / 2, + u32::MAX / 2, + u32::MAX / 2 + 1, + 17, + ], + ); + let b = u32x16::from_slice( + simd, + &[ + 1, + 2, + 1, + u32::MAX, + 20, + 20, + 9, + u32::MAX - 5, + 0, + 0, + 1, + 0, + u32::MAX / 2, + u32::MAX / 2 + 1, + u32::MAX / 2 + 1, + 23, + ], + ); + assert_eq!( + *a.saturating_add(b), + [ + u32::MAX, + u32::MAX, + u32::MAX, + u32::MAX, + 60, + 120, + u32::MAX - 1, + u32::MAX, + 0, + 1, + 1, + u32::MAX, + u32::MAX - 1, + u32::MAX, + u32::MAX, + 40, + ] + ); +} + +#[simd_test] +fn saturating_add_i64x2(simd: S) { + let overflow = i64x2::from_slice(simd, &[i64::MAX, i64::MIN]); + let overflow_rhs = i64x2::from_slice(simd, &[1, -1]); + assert_eq!(*overflow.saturating_add(overflow_rhs), [i64::MAX, i64::MIN]); + + let boundary = i64x2::from_slice(simd, &[i64::MAX - 1, i64::MIN + 1]); + let boundary_rhs = i64x2::from_slice(simd, &[1, -1]); + assert_eq!(*boundary.saturating_add(boundary_rhs), [i64::MAX, i64::MIN]); + + let ordinary = i64x2::from_slice(simd, &[40, -40]); + let ordinary_rhs = i64x2::from_slice(simd, &[20, -20]); + assert_eq!(*ordinary.saturating_add(ordinary_rhs), [60, -60]); + + let mixed = i64x2::from_slice(simd, &[50, -50]); + let mixed_rhs = i64x2::from_slice(simd, &[-75, 75]); + assert_eq!(*mixed.saturating_add(mixed_rhs), [-25, 25]); +} + +#[simd_test] +fn saturating_add_i64x4(simd: S) { + let a = i64x4::from_slice(simd, &[i64::MAX, i64::MIN, 40, -40]); + let b = i64x4::from_slice(simd, &[1, -1, 20, -20]); + assert_eq!(*a.saturating_add(b), [i64::MAX, i64::MIN, 60, -60]); + + let boundary = i64x4::from_slice(simd, &[i64::MAX - 1, i64::MIN + 1, 50, -50]); + let boundary_rhs = i64x4::from_slice(simd, &[1, -1, -75, 75]); + assert_eq!( + *boundary.saturating_add(boundary_rhs), + [i64::MAX, i64::MIN, -25, 25] + ); +} + +#[simd_test] +fn saturating_add_i64x8(simd: S) { + let a = i64x8::from_slice( + simd, + &[ + i64::MAX, + i64::MIN, + i64::MAX - 1, + i64::MIN + 1, + 40, + -40, + 50, + -50, + ], + ); + let b = i64x8::from_slice(simd, &[1, -1, 1, -1, 20, -20, -75, 75]); + assert_eq!( + *a.saturating_add(b), + [i64::MAX, i64::MIN, i64::MAX, i64::MIN, 60, -60, -25, 25] + ); +} + +#[simd_test] +fn saturating_add_u64x2(simd: S) { + let overflow = u64x2::from_slice(simd, &[u64::MAX, u64::MAX - 1]); + let overflow_rhs = u64x2::from_slice(simd, &[1, 2]); + assert_eq!(*overflow.saturating_add(overflow_rhs), [u64::MAX, u64::MAX]); + + let boundary = u64x2::from_slice(simd, &[u64::MAX - 1, 0]); + let boundary_rhs = u64x2::from_slice(simd, &[1, u64::MAX]); + assert_eq!(*boundary.saturating_add(boundary_rhs), [u64::MAX, u64::MAX]); + + let ordinary = u64x2::from_slice(simd, &[40, u64::MAX - 10]); + let ordinary_rhs = u64x2::from_slice(simd, &[20, 9]); + assert_eq!(*ordinary.saturating_add(ordinary_rhs), [60, u64::MAX - 1]); + + let mixed = u64x2::from_slice(simd, &[10, u64::MAX / 2 + 1]); + let mixed_rhs = u64x2::from_slice(simd, &[u64::MAX - 5, u64::MAX / 2 + 1]); + assert_eq!(*mixed.saturating_add(mixed_rhs), [u64::MAX, u64::MAX]); +} + +#[simd_test] +fn saturating_add_u64x4(simd: S) { + let a = u64x4::from_slice(simd, &[u64::MAX, u64::MAX - 1, 40, 0]); + let b = u64x4::from_slice(simd, &[1, 2, 20, u64::MAX]); + assert_eq!(*a.saturating_add(b), [u64::MAX, u64::MAX, 60, u64::MAX]); + + let boundary = u64x4::from_slice(simd, &[u64::MAX - 1, u64::MAX - 10, u64::MAX / 2, 17]); + let boundary_rhs = u64x4::from_slice(simd, &[1, 9, u64::MAX / 2, 23]); + assert_eq!( + *boundary.saturating_add(boundary_rhs), + [u64::MAX, u64::MAX - 1, u64::MAX - 1, 40] + ); +} + +#[simd_test] +fn saturating_add_u64x8(simd: S) { + let a = u64x8::from_slice( + simd, + &[ + u64::MAX, + u64::MAX - 1, + u64::MAX - 1, + 0, + 40, + 100, + u64::MAX - 10, + 10, + ], + ); + let b = u64x8::from_slice(simd, &[1, 2, 1, u64::MAX, 20, 20, 9, u64::MAX - 5]); + assert_eq!( + *a.saturating_add(b), + [ + u64::MAX, + u64::MAX, + u64::MAX, + u64::MAX, + 60, + 120, + u64::MAX - 1, + u64::MAX + ] + ); +} diff --git a/fearless_simd_tests/tests/harness/ops/saturating_sub.rs b/fearless_simd_tests/tests/harness/ops/saturating_sub.rs new file mode 100644 index 000000000..3d68b9755 --- /dev/null +++ b/fearless_simd_tests/tests/harness/ops/saturating_sub.rs @@ -0,0 +1,1386 @@ +// Copyright 2026 the Fearless_SIMD Authors +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use fearless_simd::*; +use fearless_simd_dev_macros::simd_test; + +// One concrete test row per supported integer vector type. + +#[simd_test] +fn saturating_sub_i8x16(simd: S) { + let a = i8x16::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + ], + ); + let b = i8x16::from_slice( + simd, + &[ + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60 + ] + ); +} + +#[simd_test] +fn saturating_sub_i8x32(simd: S) { + let a = i8x32::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + ], + ); + let b = i8x32::from_slice( + simd, + &[ + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60 + ] + ); +} + +#[simd_test] +fn saturating_sub_i8x64(simd: S) { + let a = i8x64::from_slice( + simd, + &[ + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + i8::MAX, + i8::MIN, + i8::MAX - 1, + i8::MIN + 1, + 100, + -100, + 50, + -50, + 0, + 0, + i8::MAX - 10, + i8::MIN + 10, + 1, + -1, + 40, + -40, + ], + ); + let b = i8x64::from_slice( + simd, + &[ + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + -1, + 1, + -1, + 1, + 40, + -40, + -75, + 75, + i8::MAX, + i8::MIN, + -9, + 9, + 1, + -1, + -20, + 20, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60, + i8::MAX, + i8::MIN, + i8::MAX, + i8::MIN, + 60, + -60, + 125, + -125, + -i8::MAX, + i8::MAX, + i8::MAX - 1, + i8::MIN + 1, + 0, + 0, + 60, + -60 + ] + ); +} + +#[simd_test] +fn saturating_sub_u8x16(simd: S) { + let a = u8x16::from_slice( + simd, + &[ + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + ], + ); + let b = u8x16::from_slice( + simd, + &[ + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0 + ] + ); +} + +#[simd_test] +fn saturating_sub_u8x32(simd: S) { + let a = u8x32::from_slice( + simd, + &[ + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + ], + ); + let b = u8x32::from_slice( + simd, + &[ + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0 + ] + ); +} + +#[simd_test] +fn saturating_sub_u8x64(simd: S) { + let a = u8x64::from_slice( + simd, + &[ + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + 100, + 5, + 50, + 0, + u8::MAX, + u8::MAX, + 1, + 20, + 200, + 10, + 0, + u8::MAX - 1, + 60, + 40, + u8::MAX, + 128, + ], + ); + let b = u8x64::from_slice( + simd, + &[ + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + 40, + 10, + 50, + 0, + 1, + 0, + 1, + 30, + 100, + 10, + 1, + u8::MAX, + 20, + 60, + u8::MAX, + 128, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0, + 60, + 0, + 0, + 0, + u8::MAX - 1, + u8::MAX, + 0, + 0, + 100, + 0, + 0, + 0, + 40, + 0, + 0, + 0 + ] + ); +} + +#[simd_test] +fn saturating_sub_i16x8(simd: S) { + let a = i16x8::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + ], + ); + let b = i16x8::from_slice(simd, &[-1, 1, -1, 1, 40, -40, i16::MAX, i16::MIN]); + assert_eq!( + *a.saturating_sub(b), + [ + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_i16x16(simd: S) { + let a = i16x16::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + ], + ); + let b = i16x16::from_slice( + simd, + &[ + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_i16x32(simd: S) { + let a = i16x32::from_slice( + simd, + &[ + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + i16::MAX, + i16::MIN, + i16::MAX - 1, + i16::MIN + 1, + 100, + -100, + 0, + 0, + ], + ); + let b = i16x32::from_slice( + simd, + &[ + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + -1, + 1, + -1, + 1, + 40, + -40, + i16::MAX, + i16::MIN, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX, + i16::MAX, + i16::MIN, + i16::MAX, + i16::MIN, + 60, + -60, + -i16::MAX, + i16::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_u16x8(simd: S) { + let a = u16x8::from_slice(simd, &[100, 5, 50, 0, u16::MAX, u16::MAX, 1, 20]); + let b = u16x8::from_slice(simd, &[40, 10, 50, 0, 1, 0, 1, 30]); + assert_eq!( + *a.saturating_sub(b), + [60, 0, 0, 0, u16::MAX - 1, u16::MAX, 0, 0] + ); +} + +#[simd_test] +fn saturating_sub_u16x16(simd: S) { + let a = u16x16::from_slice( + simd, + &[ + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + ], + ); + let b = u16x16::from_slice( + simd, + &[40, 10, 50, 0, 1, 0, 1, 30, 40, 10, 50, 0, 1, 0, 1, 30], + ); + assert_eq!( + *a.saturating_sub(b), + [ + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0, + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0 + ] + ); +} + +#[simd_test] +fn saturating_sub_u16x32(simd: S) { + let a = u16x32::from_slice( + simd, + &[ + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + 100, + 5, + 50, + 0, + u16::MAX, + u16::MAX, + 1, + 20, + ], + ); + let b = u16x32::from_slice( + simd, + &[ + 40, 10, 50, 0, 1, 0, 1, 30, 40, 10, 50, 0, 1, 0, 1, 30, 40, 10, 50, 0, 1, 0, 1, 30, 40, + 10, 50, 0, 1, 0, 1, 30, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0, + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0, + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0, + 60, + 0, + 0, + 0, + u16::MAX - 1, + u16::MAX, + 0, + 0 + ] + ); +} + +#[simd_test] +fn saturating_sub_i32x4(simd: S) { + let a = i32x4::from_slice(simd, &[i32::MAX, i32::MIN, 100, 0]); + let b = i32x4::from_slice(simd, &[-1, 1, 40, i32::MIN]); + assert_eq!(*a.saturating_sub(b), [i32::MAX, i32::MIN, 60, i32::MAX]); + let scalar_rhs = i32x4::from_slice(simd, &[i32::MAX, i32::MIN, 40, -40]); + assert_eq!( + *scalar_rhs.saturating_sub(20), + [i32::MAX - 20, i32::MIN, 20, -60] + ); +} + +#[simd_test] +fn saturating_sub_i32x8(simd: S) { + let a = i32x8::from_slice( + simd, + &[i32::MAX, i32::MIN, 100, 0, i32::MAX, i32::MIN, 100, 0], + ); + let b = i32x8::from_slice(simd, &[-1, 1, 40, i32::MIN, -1, 1, 40, i32::MIN]); + assert_eq!( + *a.saturating_sub(b), + [ + i32::MAX, + i32::MIN, + 60, + i32::MAX, + i32::MAX, + i32::MIN, + 60, + i32::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_i32x16(simd: S) { + let a = i32x16::from_slice( + simd, + &[ + i32::MAX, + i32::MIN, + 100, + 0, + i32::MAX, + i32::MIN, + 100, + 0, + i32::MAX, + i32::MIN, + 100, + 0, + i32::MAX, + i32::MIN, + 100, + 0, + ], + ); + let b = i32x16::from_slice( + simd, + &[ + -1, + 1, + 40, + i32::MIN, + -1, + 1, + 40, + i32::MIN, + -1, + 1, + 40, + i32::MIN, + -1, + 1, + 40, + i32::MIN, + ], + ); + assert_eq!( + *a.saturating_sub(b), + [ + i32::MAX, + i32::MIN, + 60, + i32::MAX, + i32::MAX, + i32::MIN, + 60, + i32::MAX, + i32::MAX, + i32::MIN, + 60, + i32::MAX, + i32::MAX, + i32::MIN, + 60, + i32::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_u32x4(simd: S) { + let a = u32x4::from_slice(simd, &[100, 5, 50, 0]); + let b = u32x4::from_slice(simd, &[40, 10, 50, 1]); + assert_eq!(*a.saturating_sub(b), [60, 0, 0, 0]); +} + +#[simd_test] +fn saturating_sub_u32x8(simd: S) { + let a = u32x8::from_slice(simd, &[100, 5, 50, 0, 100, 5, 50, 0]); + let b = u32x8::from_slice(simd, &[40, 10, 50, 1, 40, 10, 50, 1]); + assert_eq!(*a.saturating_sub(b), [60, 0, 0, 0, 60, 0, 0, 0]); +} + +#[simd_test] +fn saturating_sub_u32x16(simd: S) { + let a = u32x16::from_slice( + simd, + &[100, 5, 50, 0, 100, 5, 50, 0, 100, 5, 50, 0, 100, 5, 50, 0], + ); + let b = u32x16::from_slice( + simd, + &[40, 10, 50, 1, 40, 10, 50, 1, 40, 10, 50, 1, 40, 10, 50, 1], + ); + assert_eq!( + *a.saturating_sub(b), + [60, 0, 0, 0, 60, 0, 0, 0, 60, 0, 0, 0, 60, 0, 0, 0] + ); +} + +#[simd_test] +fn saturating_sub_i64x2(simd: S) { + let a = i64x2::from_slice(simd, &[i64::MAX, i64::MIN]); + let b = i64x2::from_slice(simd, &[-1, 1]); + assert_eq!(*a.saturating_sub(b), [i64::MAX, i64::MIN]); + let boundary = i64x2::from_slice(simd, &[i64::MAX - 1, i64::MIN + 1]); + let boundary_rhs = i64x2::from_slice(simd, &[-1, 1]); + assert_eq!(*boundary.saturating_sub(boundary_rhs), [i64::MAX, i64::MIN]); + + let ordinary = i64x2::from_slice(simd, &[100, -100]); + let ordinary_rhs = i64x2::from_slice(simd, &[40, -40]); + assert_eq!(*ordinary.saturating_sub(ordinary_rhs), [60, -60]); + + let mixed = i64x2::from_slice(simd, &[50, -50]); + let mixed_rhs = i64x2::from_slice(simd, &[-75, 75]); + assert_eq!(*mixed.saturating_sub(mixed_rhs), [125, -125]); + + let zero = i64x2::from_slice(simd, &[0, 0]); + let zero_rhs = i64x2::from_slice(simd, &[i64::MAX, i64::MIN]); + assert_eq!(*zero.saturating_sub(zero_rhs), [-i64::MAX, i64::MAX]); +} + +#[simd_test] +fn saturating_sub_i64x4(simd: S) { + let a = i64x4::from_slice(simd, &[i64::MAX, i64::MIN, 100, -100]); + let b = i64x4::from_slice(simd, &[-1, 1, 40, -40]); + assert_eq!(*a.saturating_sub(b), [i64::MAX, i64::MIN, 60, -60]); + let boundary = i64x4::from_slice(simd, &[i64::MAX - 1, i64::MIN + 1, 50, -50]); + let boundary_rhs = i64x4::from_slice(simd, &[-1, 1, -75, 75]); + assert_eq!( + *boundary.saturating_sub(boundary_rhs), + [i64::MAX, i64::MIN, 125, -125] + ); +} + +#[simd_test] +fn saturating_sub_i64x8(simd: S) { + let a = i64x8::from_slice( + simd, + &[ + i64::MAX, + i64::MIN, + i64::MAX - 1, + i64::MIN + 1, + 100, + -100, + 0, + 0, + ], + ); + let b = i64x8::from_slice(simd, &[-1, 1, -1, 1, 40, -40, i64::MAX, i64::MIN]); + assert_eq!( + *a.saturating_sub(b), + [ + i64::MAX, + i64::MIN, + i64::MAX, + i64::MIN, + 60, + -60, + -i64::MAX, + i64::MAX + ] + ); +} + +#[simd_test] +fn saturating_sub_u64x2(simd: S) { + let a = u64x2::from_slice(simd, &[100, 5]); + let b = u64x2::from_slice(simd, &[40, 10]); + assert_eq!(*a.saturating_sub(b), [60, 0]); + let boundary = u64x2::from_slice(simd, &[u64::MAX, u64::MAX]); + let boundary_rhs = u64x2::from_slice(simd, &[1, 0]); + assert_eq!( + *boundary.saturating_sub(boundary_rhs), + [u64::MAX - 1, u64::MAX] + ); + + let exact = u64x2::from_slice(simd, &[50, 0]); + let exact_rhs = u64x2::from_slice(simd, &[50, 0]); + assert_eq!(*exact.saturating_sub(exact_rhs), [0, 0]); + + let floor = u64x2::from_slice(simd, &[0, 1]); + let floor_rhs = u64x2::from_slice(simd, &[1, 1]); + assert_eq!(*floor.saturating_sub(floor_rhs), [0, 0]); +} + +#[simd_test] +fn saturating_sub_u64x4(simd: S) { + let a = u64x4::from_slice(simd, &[100, 5, u64::MAX, 0]); + let b = u64x4::from_slice(simd, &[40, 10, 1, 1]); + assert_eq!(*a.saturating_sub(b), [60, 0, u64::MAX - 1, 0]); +} + +#[simd_test] +fn saturating_sub_u64x8(simd: S) { + let a = u64x8::from_slice(simd, &[100, 5, 50, 0, u64::MAX, u64::MAX, 1, 20]); + let b = u64x8::from_slice(simd, &[40, 10, 50, 0, 1, 0, 1, 30]); + assert_eq!( + *a.saturating_sub(b), + [60, 0, 0, 0, u64::MAX - 1, u64::MAX, 0, 0] + ); +} From cc301a0258cb6030e0a097ac5823b399ac6de5c6 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 19:53:40 +0100 Subject: [PATCH 6/9] improve docs --- fearless_simd/src/generated/simd_trait.rs | 100 +++++++++++----------- fearless_simd_gen/src/ops.rs | 8 +- 2 files changed, 56 insertions(+), 52 deletions(-) diff --git a/fearless_simd/src/generated/simd_trait.rs b/fearless_simd/src/generated/simd_trait.rs index f09e6c66e..7a30d696c 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -385,11 +385,11 @@ pub trait Simd: fn count_zeros_i8x16(self, a: i8x16) -> i8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16; @@ -492,11 +492,11 @@ pub trait Simd: fn count_zeros_u8x16(self, a: u8x16) -> u8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16; @@ -643,11 +643,11 @@ pub trait Simd: fn count_zeros_i16x8(self, a: i16x8) -> i16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8; @@ -765,11 +765,11 @@ pub trait Simd: fn count_zeros_u16x8(self, a: u16x8) -> u16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8; @@ -922,11 +922,11 @@ pub trait Simd: fn count_zeros_i32x4(self, a: i32x4) -> i32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4; @@ -1046,11 +1046,11 @@ pub trait Simd: fn count_zeros_u32x4(self, a: u32x4) -> u32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4; @@ -1345,11 +1345,11 @@ pub trait Simd: fn count_zeros_i64x2(self, a: i64x2) -> i64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2; @@ -1467,11 +1467,11 @@ pub trait Simd: fn count_zeros_u64x2(self, a: u64x2) -> u64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2; @@ -2033,7 +2033,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.add_i8x16(a0, b0), self.add_i8x16(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { let (a0, a1) = self.split_i8x32(a); @@ -2050,7 +2050,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.sub_i8x16(a0, b0), self.sub_i8x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { let (a0, a1) = self.split_i8x32(a); @@ -2326,7 +2326,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.add_u8x16(a0, b0), self.add_u8x16(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { let (a0, a1) = self.split_u8x32(a); @@ -2343,7 +2343,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.sub_u8x16(a0, b0), self.sub_u8x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { let (a0, a1) = self.split_u8x32(a); @@ -2725,7 +2725,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.add_i16x8(a0, b0), self.add_i16x8(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { let (a0, a1) = self.split_i16x16(a); @@ -2742,7 +2742,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.sub_i16x8(a0, b0), self.sub_i16x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { let (a0, a1) = self.split_i16x16(a); @@ -3050,7 +3050,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.add_u16x8(a0, b0), self.add_u16x8(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { let (a0, a1) = self.split_u16x16(a); @@ -3067,7 +3067,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.sub_u16x8(a0, b0), self.sub_u16x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { let (a0, a1) = self.split_u16x16(a); @@ -3472,7 +3472,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.add_i32x4(a0, b0), self.add_i32x4(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { let (a0, a1) = self.split_i32x8(a); @@ -3489,7 +3489,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.sub_i32x4(a0, b0), self.sub_i32x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { let (a0, a1) = self.split_i32x8(a); @@ -3799,7 +3799,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.add_u32x4(a0, b0), self.add_u32x4(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { let (a0, a1) = self.split_u32x8(a); @@ -3816,7 +3816,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.sub_u32x4(a0, b0), self.sub_u32x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { let (a0, a1) = self.split_u32x8(a); @@ -4629,7 +4629,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.add_i64x2(a0, b0), self.add_i64x2(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { let (a0, a1) = self.split_i64x4(a); @@ -4646,7 +4646,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.sub_i64x2(a0, b0), self.sub_i64x2(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { let (a0, a1) = self.split_i64x4(a); @@ -4948,7 +4948,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.add_u64x2(a0, b0), self.add_u64x2(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { let (a0, a1) = self.split_u64x4(a); @@ -4965,7 +4965,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.sub_u64x2(a0, b0), self.sub_u64x2(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { let (a0, a1) = self.split_u64x4(a); @@ -5763,7 +5763,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.add_i8x32(a0, b0), self.add_i8x32(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { let (a0, a1) = self.split_i8x64(a); @@ -5780,7 +5780,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.sub_i8x32(a0, b0), self.sub_i8x32(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { let (a0, a1) = self.split_i8x64(a); @@ -6054,7 +6054,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.add_u8x32(a0, b0), self.add_u8x32(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { let (a0, a1) = self.split_u8x64(a); @@ -6071,7 +6071,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.sub_u8x32(a0, b0), self.sub_u8x32(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { let (a0, a1) = self.split_u8x64(a); @@ -6449,7 +6449,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.add_i16x16(a0, b0), self.add_i16x16(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { let (a0, a1) = self.split_i16x32(a); @@ -6466,7 +6466,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.sub_i16x16(a0, b0), self.sub_i16x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { let (a0, a1) = self.split_i16x32(a); @@ -6778,7 +6778,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.add_u16x16(a0, b0), self.add_u16x16(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { let (a0, a1) = self.split_u16x32(a); @@ -6795,7 +6795,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.sub_u16x16(a0, b0), self.sub_u16x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { let (a0, a1) = self.split_u16x32(a); @@ -7209,7 +7209,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.add_i32x8(a0, b0), self.add_i32x8(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { let (a0, a1) = self.split_i32x16(a); @@ -7226,7 +7226,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.sub_i32x8(a0, b0), self.sub_i32x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { let (a0, a1) = self.split_i32x16(a); @@ -7538,7 +7538,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.add_u32x8(a0, b0), self.add_u32x8(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { let (a0, a1) = self.split_u32x16(a); @@ -7555,7 +7555,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.sub_u32x8(a0, b0), self.sub_u32x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { let (a0, a1) = self.split_u32x16(a); @@ -8362,7 +8362,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.add_i64x4(a0, b0), self.add_i64x4(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { let (a0, a1) = self.split_i64x8(a); @@ -8379,7 +8379,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.sub_i64x4(a0, b0), self.sub_i64x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { let (a0, a1) = self.split_i64x8(a); @@ -8679,7 +8679,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.add_u64x4(a0, b0), self.add_u64x4(a1, b1)) } - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] #[inline(always)] fn saturating_add_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { let (a0, a1) = self.split_u64x8(a); @@ -8696,7 +8696,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.sub_u64x4(a0, b0), self.sub_u64x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] #[inline(always)] fn saturating_sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { let (a0, a1) = self.split_u64x8(a); @@ -9737,9 +9737,9 @@ pub trait SimdInt: fn count_ones(self) -> Self; #[doc = "Return the number of zeros in the binary representation of each element."] fn count_zeros(self) -> Self; - #[doc = "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] fn saturating_add(self, rhs: impl SimdInto) -> Self; - #[doc = "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing."] + #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] fn saturating_sub(self, rhs: impl SimdInto) -> Self; } #[doc = r" Functionality implemented by SIMD masks."] diff --git a/fearless_simd_gen/src/ops.rs b/fearless_simd_gen/src/ops.rs index 392a0549f..7a0741e2a 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -982,7 +982,9 @@ const INT_OPS: &[Op] = &[ "saturating_add", OpKind::VecTraitMethod, OpSig::Binary, - "Add two vectors element-wise, saturating at the numeric bounds instead of overflowing.", + "Add two vectors element-wise, returning the maximum value on overflow.\n\n\ + On x86 it is implemented in hardware only for 8-bit and 16-bit elements. \ + For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86.", ), Op::new( "sub", @@ -994,7 +996,9 @@ const INT_OPS: &[Op] = &[ "saturating_sub", OpKind::VecTraitMethod, OpSig::Binary, - "Subtract two vectors element-wise, saturating at the numeric bounds instead of overflowing.", + "Subtract two vectors element-wise, returning the maximum value on overflow.\n\n\ + On x86 it is implemented in hardware only for 8-bit and 16-bit elements. \ + For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86.", ), Op::new( "mul", From 711a5961dadf01add0026ecb66ec3af0871ff5ee Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 21:57:39 +0100 Subject: [PATCH 7/9] address clippy lints --- fearless_simd_gen/src/mk_wasm.rs | 2 +- fearless_simd_gen/src/mk_x86.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/fearless_simd_gen/src/mk_wasm.rs b/fearless_simd_gen/src/mk_wasm.rs index dd9085d9b..91b79962d 100644 --- a/fearless_simd_gen/src/mk_wasm.rs +++ b/fearless_simd_gen/src/mk_wasm.rs @@ -309,7 +309,7 @@ fn saturating_add_sub_method(op: Op, vec_ty: &VecType, arithmetic: SaturatingOp) let shr = simple_intrinsic("shr", vec_ty); let splat = simple_intrinsic("splat", vec_ty); let scalar = vec_ty.scalar.rust(vec_ty.scalar_bits); - let sign_shift = Literal::u32_unsuffixed((vec_ty.scalar_bits - 1) as u32); + let sign_shift = Literal::u32_unsuffixed((vec_ty.scalar_bits - 1).try_into().unwrap()); let overflow_bits = match arithmetic { // `(a ^ wrapped) & (b ^ wrapped)` has its sign bit set exactly // when signed addition overflows. diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index 547ddcabf..6154f41b7 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2286,7 +2286,10 @@ impl X86 { use SaturatingOp::{Add, Sub}; use ScalarType::{Float, Int, Unsigned}; - assert!(matches!(vec_ty.scalar, Int | Unsigned)); + assert!( + matches!(vec_ty.scalar, Int | Unsigned), + "Saturating artihmetic is not implementable for floats" + ); match (*self, vec_ty.scalar, vec_ty.scalar_bits, vec_ty.n_bits()) { // x86 has native instructions for 8-bit and 16-bit elements only. From 7b249a180a1ce5187332848abfe0c73f5ed29146 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Wed, 26 Aug 2026 21:59:31 +0100 Subject: [PATCH 8/9] fix typo --- fearless_simd_gen/src/mk_x86.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fearless_simd_gen/src/mk_x86.rs b/fearless_simd_gen/src/mk_x86.rs index 6154f41b7..46612b62e 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -2288,7 +2288,7 @@ impl X86 { assert!( matches!(vec_ty.scalar, Int | Unsigned), - "Saturating artihmetic is not implementable for floats" + "Saturating arithmetic is not implementable for floats" ); match (*self, vec_ty.scalar, vec_ty.scalar_bits, vec_ty.n_bits()) { From 78e910012193c40fb5611a7e2f4e8f2bb45f49d0 Mon Sep 17 00:00:00 2001 From: "Sergey \"Shnatsel\" Davidoff" Date: Thu, 3 Sep 2026 21:19:25 +0100 Subject: [PATCH 9/9] Improve doc string --- fearless_simd/src/generated/simd_trait.rs | 100 +++++++++++----------- fearless_simd_gen/src/ops.rs | 12 ++- 2 files changed, 58 insertions(+), 54 deletions(-) diff --git a/fearless_simd/src/generated/simd_trait.rs b/fearless_simd/src/generated/simd_trait.rs index 7f9cfdfa7..14d9906bc 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -385,11 +385,11 @@ pub trait Simd: fn count_zeros_i8x16(self, a: i8x16) -> i8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_i8x16(self, a: i8x16, b: i8x16) -> i8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i8x16(self, a: i8x16, b: i8x16) -> i8x16; @@ -492,11 +492,11 @@ pub trait Simd: fn count_zeros_u8x16(self, a: u8x16) -> u8x16; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_u8x16(self, a: u8x16, b: u8x16) -> u8x16; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u8x16(self, a: u8x16, b: u8x16) -> u8x16; @@ -653,11 +653,11 @@ pub trait Simd: fn count_zeros_i16x8(self, a: i16x8) -> i16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_i16x8(self, a: i16x8, b: i16x8) -> i16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i16x8(self, a: i16x8, b: i16x8) -> i16x8; @@ -775,11 +775,11 @@ pub trait Simd: fn count_zeros_u16x8(self, a: u16x8) -> u16x8; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_u16x8(self, a: u16x8, b: u16x8) -> u16x8; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u16x8(self, a: u16x8, b: u16x8) -> u16x8; @@ -942,11 +942,11 @@ pub trait Simd: fn count_zeros_i32x4(self, a: i32x4) -> i32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i32x4(self, a: i32x4, b: i32x4) -> i32x4; @@ -1066,11 +1066,11 @@ pub trait Simd: fn count_zeros_u32x4(self, a: u32x4) -> u32x4; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_u32x4(self, a: u32x4, b: u32x4) -> u32x4; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u32x4(self, a: u32x4, b: u32x4) -> u32x4; @@ -1375,11 +1375,11 @@ pub trait Simd: fn count_zeros_i64x2(self, a: i64x2) -> i64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_i64x2(self, a: i64x2, b: i64x2) -> i64x2; @@ -1497,11 +1497,11 @@ pub trait Simd: fn count_zeros_u64x2(self, a: u64x2) -> u64x2; #[doc = "Add two vectors element-wise, wrapping on overflow."] fn add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Subtract two vectors element-wise, wrapping on overflow."] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2; #[doc = "Multiply two vectors element-wise, wrapping on overflow."] fn mul_u64x2(self, a: u64x2, b: u64x2) -> u64x2; @@ -2073,7 +2073,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.add_i8x16(a0, b0), self.add_i8x16(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { let (a0, a1) = self.split_i8x32(a); @@ -2090,7 +2090,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x32(b); self.combine_i8x16(self.sub_i8x16(a0, b0), self.sub_i8x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i8x32(self, a: i8x32, b: i8x32) -> i8x32 { let (a0, a1) = self.split_i8x32(a); @@ -2366,7 +2366,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.add_u8x16(a0, b0), self.add_u8x16(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { let (a0, a1) = self.split_u8x32(a); @@ -2383,7 +2383,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x32(b); self.combine_u8x16(self.sub_u8x16(a0, b0), self.sub_u8x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u8x32(self, a: u8x32, b: u8x32) -> u8x32 { let (a0, a1) = self.split_u8x32(a); @@ -2775,7 +2775,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.add_i16x8(a0, b0), self.add_i16x8(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { let (a0, a1) = self.split_i16x16(a); @@ -2792,7 +2792,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x16(b); self.combine_i16x8(self.sub_i16x8(a0, b0), self.sub_i16x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i16x16(self, a: i16x16, b: i16x16) -> i16x16 { let (a0, a1) = self.split_i16x16(a); @@ -3100,7 +3100,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.add_u16x8(a0, b0), self.add_u16x8(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { let (a0, a1) = self.split_u16x16(a); @@ -3117,7 +3117,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x16(b); self.combine_u16x8(self.sub_u16x8(a0, b0), self.sub_u16x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u16x16(self, a: u16x16, b: u16x16) -> u16x16 { let (a0, a1) = self.split_u16x16(a); @@ -3532,7 +3532,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.add_i32x4(a0, b0), self.add_i32x4(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { let (a0, a1) = self.split_i32x8(a); @@ -3549,7 +3549,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x8(b); self.combine_i32x4(self.sub_i32x4(a0, b0), self.sub_i32x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { let (a0, a1) = self.split_i32x8(a); @@ -3859,7 +3859,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.add_u32x4(a0, b0), self.add_u32x4(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { let (a0, a1) = self.split_u32x8(a); @@ -3876,7 +3876,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x8(b); self.combine_u32x4(self.sub_u32x4(a0, b0), self.sub_u32x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u32x8(self, a: u32x8, b: u32x8) -> u32x8 { let (a0, a1) = self.split_u32x8(a); @@ -4699,7 +4699,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.add_i64x2(a0, b0), self.add_i64x2(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { let (a0, a1) = self.split_i64x4(a); @@ -4716,7 +4716,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x4(b); self.combine_i64x2(self.sub_i64x2(a0, b0), self.sub_i64x2(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { let (a0, a1) = self.split_i64x4(a); @@ -5018,7 +5018,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.add_u64x2(a0, b0), self.add_u64x2(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { let (a0, a1) = self.split_u64x4(a); @@ -5035,7 +5035,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x4(b); self.combine_u64x2(self.sub_u64x2(a0, b0), self.sub_u64x2(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { let (a0, a1) = self.split_u64x4(a); @@ -5843,7 +5843,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.add_i8x32(a0, b0), self.add_i8x32(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { let (a0, a1) = self.split_i8x64(a); @@ -5860,7 +5860,7 @@ pub trait Simd: let (b0, b1) = self.split_i8x64(b); self.combine_i8x32(self.sub_i8x32(a0, b0), self.sub_i8x32(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i8x64(self, a: i8x64, b: i8x64) -> i8x64 { let (a0, a1) = self.split_i8x64(a); @@ -6134,7 +6134,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.add_u8x32(a0, b0), self.add_u8x32(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { let (a0, a1) = self.split_u8x64(a); @@ -6151,7 +6151,7 @@ pub trait Simd: let (b0, b1) = self.split_u8x64(b); self.combine_u8x32(self.sub_u8x32(a0, b0), self.sub_u8x32(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u8x64(self, a: u8x64, b: u8x64) -> u8x64 { let (a0, a1) = self.split_u8x64(a); @@ -6539,7 +6539,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.add_i16x16(a0, b0), self.add_i16x16(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { let (a0, a1) = self.split_i16x32(a); @@ -6556,7 +6556,7 @@ pub trait Simd: let (b0, b1) = self.split_i16x32(b); self.combine_i16x16(self.sub_i16x16(a0, b0), self.sub_i16x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i16x32(self, a: i16x32, b: i16x32) -> i16x32 { let (a0, a1) = self.split_i16x32(a); @@ -6868,7 +6868,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.add_u16x16(a0, b0), self.add_u16x16(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { let (a0, a1) = self.split_u16x32(a); @@ -6885,7 +6885,7 @@ pub trait Simd: let (b0, b1) = self.split_u16x32(b); self.combine_u16x16(self.sub_u16x16(a0, b0), self.sub_u16x16(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u16x32(self, a: u16x32, b: u16x32) -> u16x32 { let (a0, a1) = self.split_u16x32(a); @@ -7309,7 +7309,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.add_i32x8(a0, b0), self.add_i32x8(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { let (a0, a1) = self.split_i32x16(a); @@ -7326,7 +7326,7 @@ pub trait Simd: let (b0, b1) = self.split_i32x16(b); self.combine_i32x8(self.sub_i32x8(a0, b0), self.sub_i32x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { let (a0, a1) = self.split_i32x16(a); @@ -7638,7 +7638,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.add_u32x8(a0, b0), self.add_u32x8(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { let (a0, a1) = self.split_u32x16(a); @@ -7655,7 +7655,7 @@ pub trait Simd: let (b0, b1) = self.split_u32x16(b); self.combine_u32x8(self.sub_u32x8(a0, b0), self.sub_u32x8(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u32x16(self, a: u32x16, b: u32x16) -> u32x16 { let (a0, a1) = self.split_u32x16(a); @@ -8472,7 +8472,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.add_i64x4(a0, b0), self.add_i64x4(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { let (a0, a1) = self.split_i64x8(a); @@ -8489,7 +8489,7 @@ pub trait Simd: let (b0, b1) = self.split_i64x8(b); self.combine_i64x4(self.sub_i64x4(a0, b0), self.sub_i64x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { let (a0, a1) = self.split_i64x8(a); @@ -8789,7 +8789,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.add_u64x4(a0, b0), self.add_u64x4(a1, b1)) } - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] #[inline(always)] fn saturating_add_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { let (a0, a1) = self.split_u64x8(a); @@ -8806,7 +8806,7 @@ pub trait Simd: let (b0, b1) = self.split_u64x8(b); self.combine_u64x4(self.sub_u64x4(a0, b0), self.sub_u64x4(a1, b1)) } - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] #[inline(always)] fn saturating_sub_u64x8(self, a: u64x8, b: u64x8) -> u64x8 { let (a0, a1) = self.split_u64x8(a); @@ -9857,9 +9857,9 @@ pub trait SimdInt: fn count_ones(self) -> Self; #[doc = "Return the number of zeros in the binary representation of each element."] fn count_zeros(self) -> Self; - #[doc = "Add two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86."] + #[doc = "Add two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86."] fn saturating_add(self, rhs: impl SimdInto) -> Self; - #[doc = "Subtract two vectors element-wise, returning the maximum value on overflow.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86."] + #[doc = "Subtract two vectors element-wise, saturating on overflow.\n\n\"Saturating\" means that if the result is not representable, the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\nOn x86 it is implemented in hardware only for 8-bit and 16-bit elements. For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86."] fn saturating_sub(self, rhs: impl SimdInto) -> Self; } #[doc = r" Functionality implemented by SIMD masks."] diff --git a/fearless_simd_gen/src/ops.rs b/fearless_simd_gen/src/ops.rs index db5ee9745..5762ee6d9 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -996,9 +996,11 @@ const INT_OPS: &[Op] = &[ "saturating_add", OpKind::VecTraitMethod, OpSig::Binary, - "Add two vectors element-wise, returning the maximum value on overflow.\n\n\ + "Add two vectors element-wise, saturating on overflow.\n\n\ + \"Saturating\" means that if the result is not representable, \ + the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\n\ On x86 it is implemented in hardware only for 8-bit and 16-bit elements. \ - For 32-bit and 64-bit vectors this operation is slower than overflowing addition on x86.", + For 32-bit and 64-bit vectors this operation is slower than wrapping addition on x86.", ), Op::new( "sub", @@ -1010,9 +1012,11 @@ const INT_OPS: &[Op] = &[ "saturating_sub", OpKind::VecTraitMethod, OpSig::Binary, - "Subtract two vectors element-wise, returning the maximum value on overflow.\n\n\ + "Subtract two vectors element-wise, saturating on overflow.\n\n\ + \"Saturating\" means that if the result is not representable, \ + the closest representable value (either `Element::MAX` or `Element::MIN`) is returned.\n\n\ On x86 it is implemented in hardware only for 8-bit and 16-bit elements. \ - For 32-bit and 64-bit vectors this operation is slower than overflowing subtraction on x86.", + For 32-bit and 64-bit vectors this operation is slower than wrapping subtraction on x86.", ), Op::new( "mul",