native 0.0.1
Vectors, masks and wide register packs for C++26
Loading...
Searching...
No Matches
integer_body.h
1// SPDX-FileCopyrightText: 2026 Edward Kmett <ekmett@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR Apache-2.0
3
4
5namespace native {
9 template <simd_integer_element To, simd_integer_element From, std::size_t N, ::native::isa<> Arch>
10 requires NATIVE_ARCH_REQUIRES(Arch) && (sizeof(From) * N % sizeof(To) == 0 && requires {
12 typename simd<To,sizeof(From)*N/sizeof(To),Arch>::native_type;
13 } && sizeof(typename simd<From,N,Arch>::native_type) ==
14 sizeof(typename simd<To,sizeof(From)*N/sizeof(To),Arch>::native_type) &&
15 (sizeof(typename simd<From,N,Arch>::native_type) <= 16
16#if NATIVE_HAS_AVX2
17 || sizeof(typename simd<From,N,Arch>::native_type) == 32
18#endif
19#if NATIVE_HAS_AVX512F
20 || sizeof(typename simd<From,N,Arch>::native_type) == 64
21#endif
22 ))
24 -> simd<To,sizeof(From)*N/sizeof(To),Arch> {
25 using result = simd<To,sizeof(From)*N/sizeof(To),Arch>;
26 // Keep native vectors in this target scope: the standard-library wrapper
27 // can otherwise impose its baseline vector return ABI on SysV hosts.
28 return result::from_native(__builtin_bit_cast(typename result::native_type,value.to_native()));
29 }
30
33 template <simd_integer_element T, std::size_t N, ::native::isa<> Arch>
34 requires NATIVE_ARCH_REQUIRES(Arch) && (std::is_unsigned_v<T> && sizeof(T) <= 4 && N > 1 && N % 2 == 0 &&
35 detail::integer_arithmetic<T,N,Arch>)
37 using U = std::conditional_t<sizeof(T)==1,std::uint16_t,
38 std::conditional_t<sizeof(T)==2,std::uint32_t,std::uint64_t>>;
39 using result = simd<U,N/2,Arch>;
40 if consteval {
41 std::array<T,N> source{};
42 value.store(source.data());
43 std::array<U,N/2> lanes{};
44 for(std::size_t i=0;i<N/2;++i) lanes[i]=U(source[2*i])+source[2*i+1];
45 return result(lanes);
46 }
47 if constexpr (sizeof(T)==4 && N==2) {
48 // A logical two-lane input has four physical lanes. Only the live pair
49 // contributes to the scalar result, regardless of padding bits.
50 auto lanes = value.to_native();
51 return result(std::uint64_t(lanes[0]) + lanes[1]);
52 } else
53#if NATIVE_HAS_ARM_NEON
54 if constexpr (sizeof(T)==1)
55 return result::from_native(vreinterpretq_u8_u16(vpaddlq_u8(value.to_native())));
56 else if constexpr (sizeof(T)==2)
57 return result::from_native(vreinterpretq_u8_u32(vpaddlq_u16(vreinterpretq_u16_u8(value.to_native()))));
58 else return result::from_native(vreinterpretq_u8_u64(vpaddlq_u32(vreinterpretq_u32_u8(value.to_native()))));
59#elif NATIVE_HAS_WASM_SIMD128
60 if constexpr (sizeof(T) == 1) {
61 return result::from_native(wasm_u16x8_extadd_pairwise_u8x16(value.to_native()));
62 } else if constexpr (sizeof(T) == 2) {
63 return result::from_native(wasm_u32x4_extadd_pairwise_u16x8(value.to_native()));
64 } else {
65 auto even = wasm_i32x4_shuffle(value.to_native(), value.to_native(), 0, 2, 0, 0);
66 auto odd = wasm_i32x4_shuffle(value.to_native(), value.to_native(), 1, 3, 0, 0);
67 return result::from_native(wasm_i64x2_add(
68 wasm_u64x2_extend_low_u32x4(even), wasm_u64x2_extend_low_u32x4(odd)));
69 }
70#else
71 // Two unsigned byte lanes sum to at most 510, well inside PMADDUBSW's
72 // signed saturation bound. Wider lanes use exact register shifts/adds.
73#if NATIVE_HAS_AVX2
74 if constexpr (sizeof(T)==1 && sizeof(T)*N==16)
75 return result::from_native(_mm_maddubs_epi16(value.to_native(),_mm_set1_epi8(1)));
76 else if constexpr (sizeof(T)==1 && sizeof(T)*N==32)
77 return result::from_native(_mm256_maddubs_epi16(value.to_native(),_mm256_set1_epi8(1)));
78 else
79#endif
80#if NATIVE_HAS_AVX512BW
81 if constexpr (sizeof(T)==1 && sizeof(T)*N==64)
82 return result::from_native(_mm512_maddubs_epi16(value.to_native(),_mm512_set1_epi8(1)));
83 else
84#endif
85 {
86 auto words = reinterpret_bits<U>(value);
87 return (words & result(U(std::numeric_limits<T>::max()))) + words.template right<8*sizeof(T)>();
88 }
89#endif
90 }
91
94 template <simd_integer_element T, std::size_t N, ::native::isa<> Arch>
95 requires NATIVE_ARCH_REQUIRES(Arch) && (std::is_unsigned_v<T> && detail::integer_arithmetic<T,N,Arch>)
97 using result = simd<T,N,Arch>;
98 if consteval {
99 std::array<T,N> lanes{};
100 value.store(lanes.data());
101 for(auto &lane:lanes) lane=T(std::popcount(lane));
102 return result(lanes);
103 }
104 if constexpr (N==1) return result(T(std::popcount(value.to_native())));
105 else if constexpr (sizeof(T)==4 && (N==2 || N==3))
106 return result::from_storage(popcount(value.to_storage()));
107#if NATIVE_HAS_AVX512F && !NATIVE_HAS_AVX512BW
108 else if constexpr (sizeof(T)*N==64) {
109 // F/DQ supports arithmetic on these lanes, while its byte/word
110 // repartitions provide storage only.
111 // Accumulate bit populations inside each original lane instead.
112 constexpr T all = std::numeric_limits<T>::max();
113 value = value - (value.template right<1>() & result(T(all/3)));
114 value = (value & result(T(all/5))) + (value.template right<2>() & result(T(all/5)));
115 value = (value + value.template right<4>()) & result(T(all/17));
116 value = value + value.template right<8>();
117 value = value + value.template right<16>();
118 if constexpr (sizeof(T)==8) value = value + value.template right<32>();
119 return value & result(T(127));
120 }
121#endif
122 else if constexpr (sizeof(T)>1)
124 std::conditional_t<sizeof(T)==2,std::uint8_t,
125 std::conditional_t<sizeof(T)==4,std::uint16_t,std::uint32_t>>>(value)));
126 else {
127#if NATIVE_HAS_WASM_SIMD128
128 return result::from_native(wasm_i8x16_popcnt(value.to_native()));
129#endif
130#if NATIVE_HAS_ARM_NEON
131 return result::from_native(vcntq_u8(value.to_native()));
132#endif
133#if NATIVE_HAS_AVX2
134 if constexpr (N==16) {
135 auto table = _mm_setr_epi8(0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4);
136 auto mask = _mm_set1_epi8(15);
137 auto data = value.to_native();
138 return result::from_native(_mm_add_epi8(
139 _mm_shuffle_epi8(table,_mm_and_si128(data,mask)),
140 _mm_shuffle_epi8(table,_mm_and_si128(_mm_srli_epi16(data,4),mask))));
141 } else if constexpr (N==32) {
142 auto table = _mm256_broadcastsi128_si256(_mm_setr_epi8(0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4));
143 auto mask = _mm256_set1_epi8(15);
144 auto data = value.to_native();
145 return result::from_native(_mm256_add_epi8(
146 _mm256_shuffle_epi8(table,_mm256_and_si256(data,mask)),
147 _mm256_shuffle_epi8(table,_mm256_and_si256(_mm256_srli_epi16(data,4),mask))));
148 }
149#endif
150#if NATIVE_HAS_AVX512BW
151 if constexpr (N==64) {
152 auto table = _mm512_broadcast_i32x4(_mm_setr_epi8(0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4));
153 auto mask = _mm512_set1_epi8(15);
154 auto data = value.to_native();
155 return result::from_native(_mm512_add_epi8(
156 _mm512_shuffle_epi8(table,_mm512_and_si512(data,mask)),
157 _mm512_shuffle_epi8(table,_mm512_and_si512(_mm512_srli_epi16(data,4),mask))));
158 }
159#endif
160 }
161 }
162
165 template <simd_integer_element T, std::size_t N, ::native::isa<> Arch>
166 requires NATIVE_ARCH_REQUIRES(Arch) && (std::is_unsigned_v<T> && sizeof(T)<=4 &&
167 detail::integer_arithmetic<T,N,Arch>)
169 if consteval {
170 std::array<T,N> lanes{};
171 value.store(lanes.data());
172 std::uint64_t sum=0;
173 for(auto lane:lanes) sum+=lane;
174 return sum;
175 }
176 if constexpr (N==1) return value.to_native();
177 else if constexpr (sizeof(T)==4 && (N==2 || N==3)) {
178 auto lanes = value.to_native();
179 auto sum = std::uint64_t(lanes[0]) + lanes[1];
180 if constexpr (N==3) sum += lanes[2];
181 return sum;
182 }
183 else {
184#if NATIVE_HAS_WASM_SIMD128
185 auto sums = [&] {
186 if constexpr (sizeof(T) == 1) {
188 } else if constexpr (sizeof(T) == 2) {
190 } else {
191 return pairwise_add_widened(value);
192 }
193 }();
194 return sums.template get<0>() + sums.template get<1>();
195#endif
196#if NATIVE_HAS_ARM_NEON
197 if constexpr (sizeof(T)==1) return vaddlvq_u8(value.to_native());
198 else if constexpr (sizeof(T)==2) return vaddlvq_u16(vreinterpretq_u16_u8(value.to_native()));
199 else return vaddlvq_u32(vreinterpretq_u32_u8(value.to_native()));
200#endif
201#if NATIVE_HAS_AVX2
202 auto sums = [&] {
203 if constexpr (sizeof(T)==1) {
204 if constexpr (N==16) return _mm_sad_epu8(value.to_native(),_mm_setzero_si128());
205 else if constexpr (N==32) return _mm256_sad_epu8(value.to_native(),_mm256_setzero_si256());
206#if NATIVE_HAS_AVX512BW
207 else return _mm512_sad_epu8(value.to_native(),_mm512_setzero_si512());
208#endif
209 } else if constexpr (sizeof(T)==2)
210 return pairwise_add_widened(pairwise_add_widened(value)).to_native();
211 else return pairwise_add_widened(value).to_native();
212 }();
213 if constexpr (sizeof(T)*N==16)
214 return std::uint64_t(_mm_cvtsi128_si64(_mm_add_epi64(sums,_mm_srli_si128(sums,8))));
215 else if constexpr (sizeof(T)*N==32) {
216 auto halves = _mm_add_epi64(_mm256_castsi256_si128(sums),_mm256_extracti128_si256(sums,1));
217 return std::uint64_t(_mm_cvtsi128_si64(_mm_add_epi64(halves,_mm_srli_si128(halves,8))));
218 }
219#if NATIVE_HAS_AVX512F
220 else return std::uint64_t(_mm512_reduce_add_epi64(sums));
221#endif
222#endif
223 }
224 }
225}
#define native_inline
inline [[always_inline]]
Definition attributes.h:212
#define native_nodiscard
C++17 [[nodiscard]].
Definition attributes.h:189
#define native_const
[[const]] is not const
Definition attributes.h:108
typename mask_traits< std::remove_cvref_t< T > >::type mask
Definition mask_traits.h:22
Architecture-tagged vectors, register packs and supporting value types. Native arithmetic follows its...
constexpr bool all(simd< T, N, A > a) noexcept
Test whether every integer lane is nonzero.
constexpr auto reinterpret_bits(simd< From, N, Arch > value) noexcept -> simd< To, sizeof(From) *N/sizeof(To), Arch >
constexpr simd< T, N, Arch > popcount(simd< T, N, Arch > value) noexcept
constexpr auto pairwise_add_widened(simd< T, N, Arch > value) noexcept
constexpr std::uint64_t reduce_add_widened(simd< T, N, Arch > value) noexcept
Omitted architecture arguments use the native.simd provider's baseline.