Skip to content

Commit ca782d8

Browse files
committed
feat: add per-lane popcount on batch
Count the bits set in each element of an integer batch. The common kernel is the SWAR fold; x86 uses the PSHUFB nibble lookup from SSSE3 up, NEON uses CNT with pairwise widening adds, SVE uses svcnt_x, and WASM uses i8x16.popcnt with pairwise widening extends, falling back to the common kernel for 64-bit elements. VSX and VXE use vec_popcnt, which the compiler maps to a single VPOPCNTB/H/W/D or VPOPCT. Assisted-by: Claude Opus 5 <noreply@anthropic.com>
1 parent e3cdb6a commit ca782d8

15 files changed

Lines changed: 287 additions & 0 deletions

docs/source/api/bitwise_operators_index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ Bitwise Operators
4848
+---------------------------------------+----------------------------------------------------+
4949
| :cpp:func:`rotl` | per slot rotate left |
5050
+---------------------------------------+----------------------------------------------------+
51+
| :cpp:func:`popcount` | per slot population count |
52+
+---------------------------------------+----------------------------------------------------+
5153

5254
----
5355

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/***************************************************************************
2+
* Copyright (c) Johan Mabille, Sylvain Corlay, Wolf Vollprecht and *
3+
* Martin Renou *
4+
* Copyright (c) QuantStack *
5+
* Copyright (c) Serge Guelton *
6+
* Copyright (c) Marco Barbone *
7+
* *
8+
* Distributed under the terms of the BSD 3-Clause License. *
9+
* *
10+
* The full license is in the file LICENSE, distributed with this software. *
11+
****************************************************************************/
12+
13+
#ifndef XSIMD_COMMON_BITWISE_HPP
14+
#define XSIMD_COMMON_BITWISE_HPP
15+
16+
#include "./xsimd_common_details.hpp"
17+
18+
#include <climits>
19+
#include <cstddef>
20+
21+
namespace xsimd
22+
{
23+
24+
namespace kernel
25+
{
26+
27+
using namespace types;
28+
29+
namespace detail
30+
{
31+
// bit i is set when i / s is even: 0x55.. for s == 1, 0x33.. for
32+
// s == 2, 0x0f0f.. for s == 4, and so on up to s == bits / 2
33+
template <class U>
34+
constexpr U alternating_mask(unsigned s) noexcept
35+
{
36+
U m = 0;
37+
for (unsigned i = 0; i < sizeof(U) * CHAR_BIT; ++i)
38+
if (((i / s) & 1u) == 0u)
39+
m = U(m | U(U(1) << i));
40+
return m;
41+
}
42+
}
43+
44+
// popcount
45+
// SWAR fold, popcount64b from Hacker's Delight, listed in
46+
// https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation
47+
template <class A, class T, class /*=std::enable_if_t<std::is_integral_v<T>>*/>
48+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<common>) noexcept
49+
{
50+
using U = as_unsigned_integer_t<T>;
51+
using b_type = batch<U, A>;
52+
constexpr std::size_t bits = sizeof(T) * CHAR_BIT;
53+
54+
b_type x = bitwise_cast<U>(self);
55+
x = x - ((x >> 1) & b_type(detail::alternating_mask<U>(1)));
56+
b_type const m2(detail::alternating_mask<U>(2));
57+
x = (x & m2) + ((x >> 2) & m2);
58+
x = (x + (x >> 4)) & b_type(detail::alternating_mask<U>(4));
59+
if constexpr (bits >= 16)
60+
x = (x + (x >> 8)) & b_type(detail::alternating_mask<U>(8));
61+
if constexpr (bits >= 32)
62+
x = (x + (x >> 16)) & b_type(detail::alternating_mask<U>(16));
63+
if constexpr (bits >= 64)
64+
x = (x + (x >> 32)) & b_type(detail::alternating_mask<U>(32));
65+
return bitwise_cast<T>(x);
66+
}
67+
}
68+
}
69+
70+
#endif

include/xsimd/arch/xsimd_avx.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,6 +1411,15 @@ namespace xsimd
14111411
return _mm256_castps_si256(_mm256_xor_ps(_mm256_castsi256_ps(self.data), _mm256_castsi256_ps(other.data)));
14121412
}
14131413

1414+
// popcount
1415+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
1416+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<avx>) noexcept
1417+
{
1418+
return detail::fwd_to_sse([](__m128i s) noexcept
1419+
{ return popcount(batch<T, ssse3>(s), ssse3 {}); },
1420+
self);
1421+
}
1422+
14141423
// reciprocal
14151424
template <class A>
14161425
XSIMD_INLINE batch<float, A> reciprocal(batch<float, A> const& self,

include/xsimd/arch/xsimd_avx2.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -962,6 +962,28 @@ namespace xsimd
962962
{ return batch<uint64_t, A>(_mm256_mul_epu32(a, b)); });
963963
}
964964

965+
// popcount
966+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
967+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<avx2>) noexcept
968+
{
969+
// per-byte counts from a nibble lookup indexed by VPSHUFB, after
970+
// Wojciech Muła, http://0x80.pl/articles/sse-popcount.html
971+
__m256i const low_mask = _mm256_set1_epi8(0x0f);
972+
__m256i const lookup = _mm256_broadcastsi128_si256(_mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4));
973+
__m256i const lo = _mm256_and_si256(self, low_mask);
974+
__m256i const hi = _mm256_and_si256(_mm256_srli_epi16(self, 4), low_mask);
975+
__m256i const counts = _mm256_add_epi8(_mm256_shuffle_epi8(lookup, lo), _mm256_shuffle_epi8(lookup, hi));
976+
// wider elements sum their byte counts
977+
if constexpr (sizeof(T) == 1)
978+
return counts;
979+
else if constexpr (sizeof(T) == 2)
980+
return _mm256_maddubs_epi16(counts, _mm256_set1_epi8(1));
981+
else if constexpr (sizeof(T) == 4)
982+
return _mm256_madd_epi16(_mm256_maddubs_epi16(counts, _mm256_set1_epi8(1)), _mm256_set1_epi16(1));
983+
else
984+
return _mm256_sad_epu8(counts, _mm256_setzero_si256());
985+
}
986+
965987
// reduce_add
966988
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
967989
XSIMD_INLINE T reduce_add(batch<T, A> const& self, requires_arch<avx2>) noexcept

include/xsimd/arch/xsimd_avx512bw.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,28 @@ namespace xsimd
561561
return detail::compare_int_avx512bw<A, T, _MM_CMPINT_NE>(self, other);
562562
}
563563

564+
// popcount
565+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
566+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<avx512bw>) noexcept
567+
{
568+
// per-byte counts from a nibble lookup indexed by VPSHUFB, after
569+
// Wojciech Muła, http://0x80.pl/articles/sse-popcount.html
570+
__m512i const low_mask = _mm512_set1_epi8(0x0f);
571+
__m512i const lookup = _mm512_broadcast_i32x4(_mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4));
572+
__m512i const lo = _mm512_and_si512(self, low_mask);
573+
__m512i const hi = _mm512_and_si512(_mm512_srli_epi16(self, 4), low_mask);
574+
__m512i const counts = _mm512_add_epi8(_mm512_shuffle_epi8(lookup, lo), _mm512_shuffle_epi8(lookup, hi));
575+
// wider elements sum their byte counts
576+
if constexpr (sizeof(T) == 1)
577+
return counts;
578+
else if constexpr (sizeof(T) == 2)
579+
return _mm512_maddubs_epi16(counts, _mm512_set1_epi8(1));
580+
else if constexpr (sizeof(T) == 4)
581+
return _mm512_madd_epi16(_mm512_maddubs_epi16(counts, _mm512_set1_epi8(1)), _mm512_set1_epi16(1));
582+
else
583+
return _mm512_sad_epu8(counts, _mm512_setzero_si512());
584+
}
585+
564586
// sadd
565587
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
566588
XSIMD_INLINE batch<T, A> sadd(batch<T, A> const& self, batch<T, A> const& other, requires_arch<avx512bw>) noexcept

include/xsimd/arch/xsimd_common.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "./common/xsimd_common_arithmetic.hpp"
1616
#include "./common/xsimd_common_bit.hpp"
17+
#include "./common/xsimd_common_bitwise.hpp"
1718
#include "./common/xsimd_common_cast.hpp"
1819
#include "./common/xsimd_common_complex.hpp"
1920
#include "./common/xsimd_common_logical.hpp"

include/xsimd/arch/xsimd_common_fwd.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ namespace xsimd
8585
XSIMD_INLINE batch<T, A> rotr(batch<T, A> const& self, STy other, requires_arch<common>) noexcept;
8686
template <size_t count, class A, class T>
8787
XSIMD_INLINE batch<T, A> rotr(batch<T, A> const& self, requires_arch<common>) noexcept;
88+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
89+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<common>) noexcept;
8890
template <class A, class T>
8991
XSIMD_INLINE batch<T, A> load(T const* mem, aligned_mode, requires_arch<A>) noexcept;
9092
template <class A, class T>

include/xsimd/arch/xsimd_neon.hpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3680,6 +3680,26 @@ namespace xsimd
36803680
WRAP_MASK_OP(countr_one)
36813681

36823682
#undef WRAP_MASK_OP
3683+
3684+
/************
3685+
* popcount *
3686+
************/
3687+
3688+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
3689+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<neon>) noexcept
3690+
{
3691+
// CNT counts bytes; wider elements fold them with pairwise widening
3692+
// adds, after Wojciech Muła, http://0x80.pl/articles/sse-popcount.html
3693+
uint8x16_t counts = vcntq_u8(bitwise_cast<uint8_t>(self).data);
3694+
if constexpr (sizeof(T) == 1)
3695+
return bitwise_cast<T>(batch<uint8_t, A>(counts));
3696+
else if constexpr (sizeof(T) == 2)
3697+
return bitwise_cast<T>(batch<uint16_t, A>(vpaddlq_u8(counts)));
3698+
else if constexpr (sizeof(T) == 4)
3699+
return bitwise_cast<T>(batch<uint32_t, A>(vpaddlq_u16(vpaddlq_u8(counts))));
3700+
else
3701+
return bitwise_cast<T>(batch<uint64_t, A>(vpaddlq_u32(vpaddlq_u16(vpaddlq_u8(counts)))));
3702+
}
36833703
}
36843704

36853705
}

include/xsimd/arch/xsimd_ssse3.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,28 @@ namespace xsimd
8282
return detail::extract_pair(self, other, i, std::make_index_sequence<size>());
8383
}
8484

85+
// popcount
86+
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
87+
XSIMD_INLINE batch<T, A> popcount(batch<T, A> const& self, requires_arch<ssse3>) noexcept
88+
{
89+
// per-byte counts from a nibble lookup indexed by PSHUFB, after
90+
// Wojciech Muła, http://0x80.pl/articles/sse-popcount.html
91+
__m128i const low_mask = _mm_set1_epi8(0x0f);
92+
__m128i const lookup = _mm_setr_epi8(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
93+
__m128i const lo = _mm_and_si128(self, low_mask);
94+
__m128i const hi = _mm_and_si128(_mm_srli_epi16(self, 4), low_mask);
95+
__m128i const counts = _mm_add_epi8(_mm_shuffle_epi8(lookup, lo), _mm_shuffle_epi8(lookup, hi));
96+
// wider elements sum their byte counts
97+
if constexpr (sizeof(T) == 1)
98+
return counts;
99+
else if constexpr (sizeof(T) == 2)
100+
return _mm_maddubs_epi16(counts, _mm_set1_epi8(1));
101+
else if constexpr (sizeof(T) == 4)
102+
return _mm_madd_epi16(_mm_maddubs_epi16(counts, _mm_set1_epi8(1)), _mm_set1_epi16(1));
103+
else
104+
return _mm_sad_epu8(counts, _mm_setzero_si128());
105+
}
106+
85107
// reduce_add
86108
template <class A, class T, class = std::enable_if_t<std::is_integral_v<T>>>
87109
XSIMD_INLINE T reduce_add(batch<T, A> const& self, requires_arch<ssse3>) noexcept

include/xsimd/arch/xsimd_sve.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,14 @@ namespace xsimd
12061206
return svscale_x(detail_sve::ptrue<T>(), x, exp);
12071207
}
12081208

1209+
// popcount
1210+
template <class A, class T, detail::enable_integral_t<T> = 0>
1211+
XSIMD_INLINE batch<T, A> popcount(const batch<T, A>& self, requires_arch<sve>) noexcept
1212+
{
1213+
using U = as_unsigned_integer_t<T>;
1214+
return bitwise_cast<T>(batch<U, A>(svcnt_x(detail_sve::ptrue<T>(), self)));
1215+
}
1216+
12091217
} // namespace kernel
12101218
} // namespace xsimd
12111219

0 commit comments

Comments
 (0)