From 65ab88738b4f6a2115e4fde30bcdde82a0d2c41f Mon Sep 17 00:00:00 2001 From: Marco Barbone Date: Wed, 12 Aug 2026 18:00:38 -0400 Subject: [PATCH 1/3] fix: declare incr_if and decr_if in the common forward header The avx512vl_128 overloads delegate to the common implementation with an unqualified call. That call is dependent, so it only resolves through ordinary lookup at the point of definition -- ADL cannot reach xsimd::kernel from arguments in namespace xsimd -- and neither name was declared before xsimd_avx512vl_128.hpp, which the ISA header includes ahead of xsimd_common.hpp. Building the test suite with -march=skylake-avx512 -mprefer-vector-width=128 therefore failed with 16 "no matching function for call to incr_if(..., xsimd::common)" errors. Assisted-by: Claude Opus 5 --- include/xsimd/arch/xsimd_common_fwd.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/xsimd/arch/xsimd_common_fwd.hpp b/include/xsimd/arch/xsimd_common_fwd.hpp index b247b2bd6..495049785 100644 --- a/include/xsimd/arch/xsimd_common_fwd.hpp +++ b/include/xsimd/arch/xsimd_common_fwd.hpp @@ -60,8 +60,12 @@ namespace xsimd XSIMD_INLINE batch bitwise_rshift(batch const& self, batch const& other, requires_arch) noexcept; template >> XSIMD_INLINE batch bitwise_rshift(batch const& self, requires_arch) noexcept; + template + XSIMD_INLINE batch decr_if(batch const& self, Mask const& mask, requires_arch) noexcept; template XSIMD_INLINE batch_bool gt(batch const& self, batch const& other, requires_arch) noexcept; + template + XSIMD_INLINE batch incr_if(batch const& self, Mask const& mask, requires_arch) noexcept; template >> XSIMD_INLINE batch mul(batch const& self, batch const& other, requires_arch) noexcept; template >> From 42011ac03e5c3bac6d8f59774c608e212866d3d3 Mon Sep 17 00:00:00 2001 From: Marco Barbone Date: Wed, 12 Aug 2026 18:45:58 -0400 Subject: [PATCH 2/3] fix: correct the tobitset shift for 4 and 2 bool blocks tobitset packs N bool bytes into a bitmask by multiplying with a magic constant that gathers the N selected bits into the top N bits of the 8 * N bit product, so the result has to be shifted down by 8 * N - N. The N == 8 case shifts by 56 and is right; N == 4 shifted by 24 instead of 28 and N == 2 by 8 instead of 14, leaving the mask multiplied by 16 and 64 respectively. Exhaustively, the old shifts are wrong for 15 of the 16 inputs of the 4-bool block and 3 of the 4 inputs of the 2-bool block. Blocks smaller than 8 only occur for batch_bool of size 4 and 2, which only exist on avx512vl_128 and avx512vl_256. Neither compiled before the preceding commit, so the wrong code was unreachable. Assisted-by: Claude Opus 5 --- include/xsimd/arch/xsimd_avx512f.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/xsimd/arch/xsimd_avx512f.hpp b/include/xsimd/arch/xsimd_avx512f.hpp index 02934de8b..271a1bc08 100644 --- a/include/xsimd/arch/xsimd_avx512f.hpp +++ b/include/xsimd/arch/xsimd_avx512f.hpp @@ -1544,6 +1544,8 @@ namespace xsimd XSIMD_INLINE unsigned char tobitset(unsigned char unpacked[N]) { static_assert(N == 8 || N == 4 || N == 2, "valid pack size"); + // The multiply gathers the N selected bits into the top N bits + // of the 8 * N bit product, so the shift is 8 * N - N. if constexpr (N == 8) { uint64_t data; @@ -1561,7 +1563,7 @@ namespace xsimd const uint32_t magic = (0x80 + 0x4000 + 0x200000 + 0x10000000); - unsigned char res = ((data * magic) >> 24) & 0xFF; + unsigned char res = ((data * magic) >> 28) & 0xFF; return res; } else if constexpr (N == 2) @@ -1571,7 +1573,7 @@ namespace xsimd const uint16_t magic = (0x80 + 0x4000); - unsigned char res = ((data * magic) >> 8) & 0xFF; + unsigned char res = ((data * magic) >> 14) & 0xFF; return res; } } From 30140f49c85c19183f9570cbe0abbd7b58c596b4 Mon Sep 17 00:00:00 2001 From: Marco Barbone Date: Wed, 12 Aug 2026 18:57:17 -0400 Subject: [PATCH 3/3] fix: derive the sign mask in avx sadd/ssub from a comparison Both took the sign of the operand with an arithmetic shift and then reinterpreted the vector as a batch_bool. That construction assumes the boolean register is a vector, which is false for avx512vl_128 and avx512vl_256: they inherit the AVX kernels but carry k-register booleans, so the batch_bool constructor does not accept a __m256i and neither arch compiles. Comparing against zero produces the same mask with the right type on every architecture. It is also shorter for 64-bit lanes, where AVX2 has no vpsraq and the arithmetic shift is emulated -- per-function instruction counts at -O3 -mavx2: sadd 24 -> 14, ssub 21 -> 11, 32-bit and unsigned unchanged. Verified against a scalar reference over the type extremes and 64 pseudo-random values for all eight integer types on avx, avx2 and avx512: no mismatches before or after. Assisted-by: Claude Opus 5 --- include/xsimd/arch/xsimd_avx.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/include/xsimd/arch/xsimd_avx.hpp b/include/xsimd/arch/xsimd_avx.hpp index 84b1ba3a0..f15c72e74 100644 --- a/include/xsimd/arch/xsimd_avx.hpp +++ b/include/xsimd/arch/xsimd_avx.hpp @@ -1477,10 +1477,10 @@ namespace xsimd { if (std::is_signed_v) { - auto mask = (other >> (8 * sizeof(T) - 1)); + auto negative = other < batch(T(0)); auto self_pos_branch = min(std::numeric_limits::max() - other, self); auto self_neg_branch = max(std::numeric_limits::min() - other, self); - return other + select(batch_bool(mask.data), self_neg_branch, self_pos_branch); + return other + select(negative, self_neg_branch, self_pos_branch); } else { @@ -1728,10 +1728,10 @@ namespace xsimd } else if (std::is_signed_v) { - auto mask = (other >> (8 * sizeof(T) - 1)); + auto negative = other < batch(T(0)); auto self_overflow_branch = min(std::numeric_limits::max() + other, self); auto self_underflow_branch = max(std::numeric_limits::min() + other, self); - return select(batch_bool(mask.data), self_overflow_branch, self_underflow_branch) - other; + return select(negative, self_overflow_branch, self_underflow_branch) - other; } else {