diff --git a/CHANGELOG.md b/CHANGELOG.md index 08f0e187..94f5a2fb 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` 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 9b58cdd3..312d90b0 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)] @@ -793,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)] @@ -1351,6 +1371,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)] @@ -1361,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)] @@ -2071,6 +2111,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)] @@ -2081,6 +2131,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)] @@ -2584,6 +2644,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)] @@ -2594,6 +2664,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)] @@ -3308,6 +3388,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 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(wrapped), + _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)] @@ -3318,6 +3418,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)] @@ -3798,6 +3918,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)] @@ -3808,6 +3941,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)] @@ -5091,6 +5236,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 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(wrapped), + _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)] @@ -5101,6 +5266,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]), @@ -5543,6 +5728,24 @@ 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 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(wrapped, sign_bias), + ); + _mm_or_si128(wrapped, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] @@ -5553,6 +5756,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]), @@ -6715,6 +6934,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)] @@ -6725,6 +6954,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)] @@ -7257,6 +7496,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)] @@ -7267,6 +7516,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)] @@ -7953,6 +8212,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)] @@ -7963,6 +8232,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)] @@ -8426,6 +8705,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)] @@ -8436,6 +8725,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)] @@ -9126,6 +9425,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 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>(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 sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -9136,6 +9456,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)] @@ -9569,6 +9910,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)] @@ -9579,6 +9933,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)] @@ -10772,6 +11138,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 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(wrapped), + _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)] @@ -10782,6 +11169,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,6 +11609,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 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(wrapped, sign_bias), + ); + _mm256_or_si256(wrapped, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x4(self, a: u64x4, b: u64x4) -> u64x4 { crate::kernel!( #[inline(always)] @@ -11211,6 +11637,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 2342f97a..d1be40fb 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)] @@ -1003,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)] @@ -1544,6 +1564,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)] @@ -1554,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)] @@ -2172,6 +2212,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)] @@ -2182,6 +2232,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)] @@ -2653,6 +2713,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)] @@ -2663,6 +2733,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)] @@ -3261,6 +3341,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 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, wrapped).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x4(self, a: i32x4, b: i32x4) -> i32x4 { crate::kernel!( #[inline(always)] @@ -3271,6 +3367,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)] @@ -3731,6 +3843,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)] @@ -3741,6 +3866,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)] @@ -4881,6 +5018,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 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, wrapped).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x2(self, a: i64x2, b: i64x2) -> i64x2 { crate::kernel!( #[inline(always)] @@ -4891,6 +5044,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)] @@ -5316,6 +5485,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)] @@ -5326,6 +5508,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)] @@ -6466,6 +6660,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)] @@ -6476,6 +6680,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)] @@ -7009,6 +7223,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)] @@ -7019,6 +7243,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)] @@ -7643,6 +7877,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)] @@ -7653,6 +7897,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)] @@ -8113,6 +8367,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)] @@ -8123,6 +8387,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)] @@ -8724,6 +8998,24 @@ 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 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, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x8(self, a: i32x8, b: i32x8) -> i32x8 { crate::kernel!( #[inline(always)] @@ -8734,6 +9026,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)] @@ -9182,6 +9492,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)] @@ -9192,6 +9515,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)] @@ -10303,6 +10638,24 @@ 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 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, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x4(self, a: i64x4, b: i64x4) -> i64x4 { crate::kernel!( #[inline(always)] @@ -10313,6 +10666,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)] @@ -10731,6 +11102,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)] @@ -10741,6 +11125,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)] @@ -11876,6 +12272,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)] @@ -11886,6 +12292,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)] @@ -12429,6 +12845,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)] @@ -12439,6 +12865,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)] @@ -13065,6 +13501,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)] @@ -13075,6 +13521,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)] @@ -13546,6 +14002,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)] @@ -13556,6 +14022,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)] @@ -14160,6 +14636,24 @@ 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 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, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i32x16(self, a: i32x16, b: i32x16) -> i32x16 { crate::kernel!( #[inline(always)] @@ -14170,6 +14664,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)] @@ -14633,6 +15145,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)] @@ -14643,6 +15168,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)] @@ -15773,6 +16310,24 @@ 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 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, wrapped) + .simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_i64x8(self, a: i64x8, b: i64x8) -> i64x8 { crate::kernel!( #[inline(always)] @@ -15783,6 +16338,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)] @@ -16210,6 +16783,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)] @@ -16220,6 +16806,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 d6a58aaa..c2b7011a 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]), @@ -727,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]), @@ -1630,6 +1674,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]), @@ -1652,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]), @@ -2799,6 +2887,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]), @@ -2813,6 +2915,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]), @@ -3381,6 +3497,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]), @@ -3395,6 +3525,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]), @@ -4193,6 +4337,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]), @@ -4203,6 +4357,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]), @@ -4572,6 +4736,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]), @@ -4582,6 +4756,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]), @@ -5427,6 +5611,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]), @@ -5435,6 +5627,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]), @@ -5709,6 +5909,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]), @@ -5717,6 +5925,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 7c94eca6..586b1b7d 100644 --- a/fearless_simd/src/generated/neon.rs +++ b/fearless_simd/src/generated/neon.rs @@ -623,6 +623,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)] @@ -633,6 +643,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)] @@ -1016,6 +1036,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)] @@ -1026,6 +1056,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)] @@ -1586,6 +1626,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)] @@ -1596,6 +1646,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)] @@ -1978,6 +2038,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)] @@ -1988,6 +2058,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)] @@ -2573,6 +2653,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)] @@ -2583,6 +2673,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)] @@ -2975,6 +3075,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)] @@ -2985,6 +3095,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)] @@ -4042,6 +4162,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)] @@ -4052,6 +4182,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]), @@ -4418,6 +4558,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)] @@ -4428,6 +4578,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 adeb2682..14d9906b 100644 --- a/fearless_simd/src/generated/simd_trait.rs +++ b/fearless_simd/src/generated/simd_trait.rs @@ -385,8 +385,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -488,8 +492,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -645,8 +653,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -763,8 +775,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -926,8 +942,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -1046,8 +1066,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -1351,8 +1375,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -1469,8 +1497,12 @@ 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 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, 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; #[doc = "Compute the bitwise AND of two vectors."] @@ -2041,6 +2073,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 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); + 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 { @@ -2048,6 +2090,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 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); + 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 { @@ -2314,6 +2366,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 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); + 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 { @@ -2321,6 +2383,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 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); + 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 { @@ -2703,6 +2775,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 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); + 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 { @@ -2710,6 +2792,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 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); + 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 { @@ -3008,6 +3100,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 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); + 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 { @@ -3015,6 +3117,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 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); + 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 { @@ -3420,6 +3532,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 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); + 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 { @@ -3427,6 +3549,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 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); + 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 { @@ -3727,6 +3859,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 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); + 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 { @@ -3734,6 +3876,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 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); + 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 { @@ -4547,6 +4699,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 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); + 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 { @@ -4554,6 +4716,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 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); + 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 { @@ -4846,6 +5018,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 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); + 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 { @@ -4853,6 +5035,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 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); + 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 { @@ -5651,6 +5843,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 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); + 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 { @@ -5658,6 +5860,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 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); + 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 { @@ -5922,6 +6134,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 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); + 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 { @@ -5929,6 +6151,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 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); + 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 { @@ -6307,6 +6539,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 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); + 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 { @@ -6314,6 +6556,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 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); + 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 { @@ -6616,6 +6868,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 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); + 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 { @@ -6623,6 +6885,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 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); + 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 { @@ -7037,6 +7309,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 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); + 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 { @@ -7044,6 +7326,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 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); + 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 { @@ -7346,6 +7638,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 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); + 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 { @@ -7353,6 +7655,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 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); + 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 { @@ -8160,6 +8472,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 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); + 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 { @@ -8167,6 +8489,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 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); + 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 { @@ -8457,6 +8789,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 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); + 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 { @@ -8464,6 +8806,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 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); + 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 { @@ -9505,6 +9857,10 @@ 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 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, 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."] #[doc = r""] diff --git a/fearless_simd/src/generated/simd_types.rs b/fearless_simd/src/generated/simd_types.rs index 4efe309b..f1111953 100644 --- a/fearless_simd/src/generated/simd_types.rs +++ b/fearless_simd/src/generated/simd_types.rs @@ -598,6 +598,16 @@ 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)) + } + #[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; @@ -886,6 +896,16 @@ 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)) + } + #[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; @@ -1272,6 +1292,16 @@ 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)) + } + #[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; @@ -1567,6 +1597,16 @@ 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)) + } + #[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; @@ -1956,6 +1996,16 @@ 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)) + } + #[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."] @@ -2251,6 +2301,16 @@ 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)) + } + #[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."] @@ -2990,6 +3050,16 @@ 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)) + } + #[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."] @@ -3278,6 +3348,16 @@ 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)) + } + #[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."] @@ -4045,6 +4125,16 @@ 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)) + } + #[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; @@ -4344,6 +4434,16 @@ 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)) + } + #[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; @@ -4734,6 +4834,16 @@ 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)) + } + #[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; @@ -5033,6 +5143,16 @@ 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)) + } + #[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; @@ -5429,6 +5549,16 @@ 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)) + } + #[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."] @@ -5731,6 +5861,16 @@ 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)) + } + #[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."] @@ -6460,6 +6600,16 @@ 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)) + } + #[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."] @@ -6743,6 +6893,16 @@ 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)) + } + #[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."] @@ -7541,6 +7701,16 @@ 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)) + } + #[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; @@ -7866,6 +8036,16 @@ 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)) + } + #[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; @@ -8266,6 +8446,16 @@ 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)) + } + #[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; @@ -8575,6 +8765,16 @@ 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)) + } + #[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; @@ -8974,6 +9174,16 @@ 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)) + } + #[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."] @@ -9279,6 +9489,16 @@ 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)) + } + #[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."] @@ -10020,6 +10240,16 @@ 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)) + } + #[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."] @@ -10309,6 +10539,16 @@ 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)) + } + #[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 a559bb52..62736e07 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)] @@ -896,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)] @@ -1644,6 +1664,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)] @@ -1654,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)] @@ -2430,6 +2470,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)] @@ -2440,6 +2490,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)] @@ -2928,6 +2988,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)] @@ -2938,6 +3008,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 +3805,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)] @@ -3735,6 +3825,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]), @@ -4254,6 +4354,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)] @@ -4264,6 +4374,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]), @@ -5539,6 +5659,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)] @@ -5549,6 +5677,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]), @@ -5957,6 +6093,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)] @@ -5967,6 +6111,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 72ede6f4..f68a389e 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)] @@ -943,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)] @@ -1506,6 +1526,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)] @@ -1516,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)] @@ -2228,6 +2268,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)] @@ -2238,6 +2288,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)] @@ -2742,6 +2802,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)] @@ -2752,6 +2822,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)] @@ -3464,6 +3544,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 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(wrapped), + _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)] @@ -3474,6 +3574,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)] @@ -3951,6 +4071,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)] @@ -3961,6 +4094,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)] @@ -5263,6 +5408,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 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(wrapped), + _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)] @@ -5273,6 +5439,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]), @@ -5713,6 +5900,24 @@ 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 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(wrapped, sign_bias), + ); + _mm_or_si128(wrapped, overflow).simd_into(token) + } + ); + kernel(self, a, b) + } + #[inline(always)] fn sub_u64x2(self, a: u64x2, b: u64x2) -> u64x2 { crate::kernel!( #[inline(always)] @@ -5723,6 +5928,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 5a6a9ef2..1e1f36d0 100644 --- a/fearless_simd/src/generated/wasm.rs +++ b/fearless_simd/src/generated/wasm.rs @@ -572,10 +572,18 @@ 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) } #[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()); @@ -954,10 +962,18 @@ 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) } #[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()); @@ -1435,10 +1451,18 @@ 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) } #[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) } @@ -1711,10 +1735,18 @@ 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) } #[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) } @@ -2113,10 +2145,28 @@ 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 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) } @@ -2375,10 +2425,22 @@ 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) } #[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) } @@ -3080,10 +3142,28 @@ 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 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) } @@ -3337,10 +3417,28 @@ 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 wrapped = u64x2_add(a, b); + let sign_bit = i64x2_splat(i64::MIN); + 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 36d6d72e..158e7940 100644 --- a/fearless_simd_gen/src/arch/fallback.rs +++ b/fearless_simd_gen/src/arch/fallback.rs @@ -29,6 +29,8 @@ pub(crate) fn translate_op(op: &str, is_float: bool) -> Option<&'static str> { "wrapping_add" } } + "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 cf6732cb..3d04b1d7 100644 --- a/fearless_simd_gen/src/arch/neon.rs +++ b/fearless_simd_gen/src/arch/neon.rs @@ -15,6 +15,8 @@ fn translate_op(op: &str) -> Option<&'static str> { "trunc" => "vrnd", "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 781ee132..47a67d5a 100644 --- a/fearless_simd_gen/src/arch/wasm.rs +++ b/fearless_simd_gen/src/arch/wasm.rs @@ -15,6 +15,8 @@ fn translate_op(op: &str) -> Option<&'static str> { "trunc" => "trunc", "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 ad152faf..be068a27 100644 --- a/fearless_simd_gen/src/mk_fallback.rs +++ b/fearless_simd_gen/src/mk_fallback.rs @@ -892,6 +892,8 @@ fn rhs_reference(method: &str) -> bool { | "wrapping_sub" | "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 46857113..0faf2477 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,6 +221,127 @@ fn reduce_sum(method_sig: TokenStream, 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/sub lowering only handles one native vector" + ); + assert!( + matches!(vec_ty.scalar, ScalarType::Int | ScalarType::Unsigned), + "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 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) + } + }; + } + + 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) => { + 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! { + #wrapping(#clamped, b) + } + } + (ScalarType::Unsigned, 64) => { + 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); + 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) => { + 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).try_into().unwrap()); + 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 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(wrapped, #sign_shift), + #splat(#scalar::MIN), + ); + v128_bitselect(saturation, wrapped, 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 +603,15 @@ impl Level for WasmSimd128 { mode: NarrowingMode::Relaxed, } => relaxed_narrow_method(op, vec_ty, target_ty, "narrow"), OpSig::Binary => { + 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") || (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 b487e778..d49a4cfb 100644 --- a/fearless_simd_gen/src/mk_x86.rs +++ b/fearless_simd_gen/src/mk_x86.rs @@ -14,7 +14,8 @@ use crate::generic::{ }; use crate::level::Level; use crate::ops::{ - ElementDirection, NarrowingMode, Op, OpSig, Quantifier, SlideGranularity, relaxed_narrow_method, + ElementDirection, NarrowingMode, Op, OpSig, Quantifier, SaturatingOp, SlideGranularity, + relaxed_narrow_method, }; use crate::types::{ScalarType, VecType}; use proc_macro2::{Ident, Literal, Span, TokenStream}; @@ -2339,9 +2340,259 @@ impl X86 { } } + 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), + "Saturating arithmetic 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. + (_, _, 8 | 16, _) => { + let intrinsic = simple_intrinsic( + match saturating_op { + Add => "adds", + Sub => "subs", + }, + vec_ty, + ); + self.kernel_method(op, vec_ty, |token| { + quote! { + #intrinsic(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, 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 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 wrapped = #arithmetic(a, b); + + // 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); + + // 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 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 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(); + #expression.simd_into(#token) + } + }) + } + (Self::Sse4_2 | Self::Avx2, Unsigned, 64, _bits) => { + let bits = vec_ty.n_bits(); + 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 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. + self.kernel_method(op, vec_ty, |token| { + quote! { + let a = a.into(); + let b = b.into(); + let wrapped = #arithmetic(a, b); + let sign_bias = #set1(i64::MIN); + #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); + let cmpgt = simple_sign_unaware_intrinsic("cmpgt", vec_ty); + let bound = match (*self, lane_bits) { + (_, 32) => { + let shift = intrinsic_ident("srai", "epi32", bits); + quote! { + 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 a_sign = #shift::<31>(#shuffle::<0xf5>(a)); + let bound = #xor(a_sign, #set1(i64::MAX)); + } + } + (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 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 { + 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 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(#comparison, b); + #bound + let result = #blend( + #to_float(wrapped), + #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 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 { 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 901b88bd..5762ee6d 100644 --- a/fearless_simd_gen/src/ops.rs +++ b/fearless_simd_gen/src/ops.rs @@ -47,6 +47,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. @@ -985,12 +992,32 @@ 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 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 wrapping addition on x86.", + ), Op::new( "sub", OpKind::Overloaded(CoreOpTrait::Sub), OpSig::Binary, "Subtract two vectors element-wise, wrapping on overflow.", ), + Op::new( + "saturating_sub", + OpKind::VecTraitMethod, + OpSig::Binary, + "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 wrapping subtraction on x86.", + ), Op::new( "mul", OpKind::Overloaded(CoreOpTrait::Mul), diff --git a/fearless_simd_tests/tests/generics.rs b/fearless_simd_tests/tests/generics.rs index c1f3b72c..8b087eaa 100644 --- a/fearless_simd_tests/tests/generics.rs +++ b/fearless_simd_tests/tests/generics.rs @@ -36,6 +36,15 @@ 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) +} + +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 90acb6a9..71c5706a 100644 --- a/fearless_simd_tests/tests/harness/ops/mod.rs +++ b/fearless_simd_tests/tests/harness/ops/mod.rs @@ -66,6 +66,8 @@ mod reverse; 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 00000000..ebf0acb5 --- /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 00000000..3d68b975 --- /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] + ); +}