Pixie
Loading...
Searching...
No Matches
bits.h
1#pragma once
2
3#if defined(__AVX512VPOPCNTDQ__) && defined(__AVX512F__) && \
4 defined(__AVX512BW__)
5#define PIXIE_AVX512_SUPPORT
6#endif
7
8#if defined(__BMI2__) && !defined(PIXIE_DISABLE_BMI2)
9#define PIXIE_BMI2_SUPPORT
10#endif
11
12#ifdef __AVX2__
13#define PIXIE_AVX2_SUPPORT
14#endif
15
16#if defined(__SSSE3__) && defined(__SSE4_1__)
17#define PIXIE_SSE41_SUPPORT
18#endif
19
20#if defined(__AVX512F__) || defined(PIXIE_BMI2_SUPPORT) || \
21 defined(PIXIE_AVX2_SUPPORT) || defined(PIXIE_SSE41_SUPPORT)
22#include <immintrin.h>
23#endif
24
25#include <algorithm>
26#include <array>
27#include <bit>
28#include <cstddef>
29#include <cstdint>
30#include <limits>
31#include <numeric>
32
33#ifdef PIXIE_AVX2_SUPPORT
34// Lookup table for 4-bit popcount
35// This table maps each 4-bit value (0-15) to its population count
36// clang-format off
37static inline const __m256i lookup_popcount_4 = _mm256_setr_epi8(
38 0, 1, 1, 2, // 0000, 0001, 0010, 0011
39 1, 2, 2, 3, // 0100, 0101, 0110, 0111
40 1, 2, 2, 3, // 1000, 1001, 1010, 1011
41 2, 3, 3, 4, // 1100, 1101, 1110, 1111
42
43 // Same table repeated for high 128 bits
44 0, 1, 1, 2, // 0000, 0001, 0010, 0011
45 1, 2, 2, 3, // 0100, 0101, 0110, 0111
46 1, 2, 2, 3, // 1000, 1001, 1010, 1011
47 2, 3, 3, 4 // 1100, 1101, 1110, 1111
48);
49
50static inline const __m256i mask_first_half = _mm256_setr_epi8(
51 0xFF, 0xFF, 0xFF, 0xFF,
52 0xFF, 0xFF, 0xFF, 0xFF,
53 0xFF, 0xFF, 0xFF, 0xFF,
54 0xFF, 0xFF, 0xFF, 0xFF,
55 0, 0, 0, 0,
56 0, 0, 0, 0,
57 0, 0, 0, 0,
58 0, 0, 0, 0
59);
60
61// clang-format on
62#endif
63
64static inline constexpr int8_t excess_nibble_min_offset[16] = {
65 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 1, 1, 2, 2, 1, 1};
66
67#ifdef PIXIE_SSE41_SUPPORT
68// clang-format off
69static inline const __m128i excess_lut_delta_sse = _mm_setr_epi8(
70 -4, -2, -2, 0,
71 -2, 0, 0, 2,
72 -2, 0, 0, 2,
73 0, 2, 2, 4);
74static inline const __m128i excess_lut_pos0_sse = _mm_setr_epi8(
75 -1, 1, -1, 1,
76 -1, 1, -1, 1,
77 -1, 1, -1, 1,
78 -1, 1, -1, 1);
79static inline const __m128i excess_lut_pos1_sse = _mm_setr_epi8(
80 -2, 0, 0, 2,
81 -2, 0, 0, 2,
82 -2, 0, 0, 2,
83 -2, 0, 0, 2);
84static inline const __m128i excess_lut_pos2_sse = _mm_setr_epi8(
85 -3, -1, -1, 1,
86 -1, 1, 1, 3,
87 -3, -1, -1, 1,
88 -1, 1, 1, 3);
89static inline const __m128i excess_lut_min_sse = _mm_setr_epi8(
90 -4, -2, -2, 0,
91 -2, 0, -1, 1,
92 -3, -1, -1, 1,
93 -2, 0, -1, 1);
94static inline const __m128i excess_lut_nibble_index_sse = _mm_setr_epi8(
95 0, 1, 2, 3,
96 4, 5, 6, 7,
97 8, 9, 10, 11,
98 12, 13, 14, 15);
99static inline const __m128i excess_lut_low_nibble_index_sse = _mm_setr_epi8(
100 0, 2, 4, 6,
101 8, 10, 12, 14,
102 16, 18, 20, 22,
103 24, 26, 28, 30);
104static inline const __m128i excess_lut_high_nibble_index_sse = _mm_setr_epi8(
105 1, 3, 5, 7,
106 9, 11, 13, 15,
107 17, 19, 21, 23,
108 25, 27, 29, 31);
109static inline const __m128i excess_lut_nibble_mask_sse = _mm_set1_epi8(0x0F);
110// clang-format on
111#endif
112
113#ifdef PIXIE_SSE41_SUPPORT
114static inline __m128i excess_nibbles_64_sse(const uint64_t* s) noexcept {
115 const __m128i word_vec = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(s));
116 const __m128i lo_nibbles =
117 _mm_and_si128(word_vec, excess_lut_nibble_mask_sse);
118 const __m128i hi_nibbles =
119 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask_sse);
120 return _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
121}
122
123static inline __m128i excess_prefix_sum_16x_i8(__m128i v) noexcept {
124 __m128i x = v;
125 __m128i t = _mm_slli_si128(x, 1);
126 x = _mm_add_epi8(x, t);
127 t = _mm_slli_si128(x, 2);
128 x = _mm_add_epi8(x, t);
129 t = _mm_slli_si128(x, 4);
130 x = _mm_add_epi8(x, t);
131 t = _mm_slli_si128(x, 8);
132 return _mm_add_epi8(x, t);
133}
134
135static inline int excess_horizontal_min_i8(__m128i v) noexcept {
136 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 8));
137 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 4));
138 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 2));
139 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 1));
140 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(v, 0)));
141}
142#endif
143
158static inline uint32_t rmm_btree_match_mask_i16x16(const int16_t* prefix_before,
159 const int16_t* min_excess,
160 const int16_t* max_excess,
161 int16_t target,
162 bool include_zero_boundary) {
163#ifdef PIXIE_AVX2_SUPPORT
164 const __m256i vtarget = _mm256_set1_epi16(target);
165 const __m256i vprefix =
166 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(prefix_before));
167 const __m256i vmin =
168 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(min_excess));
169 const __m256i vmax =
170 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(max_excess));
171
172 const __m256i lower = _mm256_adds_epi16(vprefix, vmin);
173 const __m256i upper = _mm256_adds_epi16(vprefix, vmax);
174 const __m256i ge_lower = _mm256_or_si256(_mm256_cmpgt_epi16(vtarget, lower),
175 _mm256_cmpeq_epi16(vtarget, lower));
176 const __m256i le_upper = _mm256_or_si256(_mm256_cmpgt_epi16(upper, vtarget),
177 _mm256_cmpeq_epi16(upper, vtarget));
178 __m256i matched = _mm256_and_si256(ge_lower, le_upper);
179 if (include_zero_boundary) {
180 matched = _mm256_or_si256(matched, _mm256_cmpeq_epi16(vtarget, vprefix));
181 }
182
183 const uint32_t byte_mask =
184 static_cast<uint32_t>(_mm256_movemask_epi8(matched));
185 uint32_t result = 0;
186 for (size_t lane = 0; lane < 16; ++lane) {
187 const uint32_t lane_mask = 0x3u << (lane * 2);
188 if ((byte_mask & lane_mask) == lane_mask) {
189 result |= uint32_t{1} << lane;
190 }
191 }
192 return result;
193#else
194 uint32_t result = 0;
195 for (size_t lane = 0; lane < 16; ++lane) {
196 const int lower = prefix_before[lane] + min_excess[lane];
197 const int upper = prefix_before[lane] + max_excess[lane];
198 const bool found = (lower <= target && target <= upper) ||
199 (include_zero_boundary && target == prefix_before[lane]);
200 if (found) {
201 result |= uint32_t{1} << lane;
202 }
203 }
204 return result;
205#endif
206}
207
222static inline uint32_t rmm_btree_match_mask_i64x4(const int64_t* prefix_before,
223 const int64_t* min_excess,
224 const int64_t* max_excess,
225 int64_t target,
226 bool include_zero_boundary) {
227#ifdef PIXIE_AVX2_SUPPORT
228 const __m256i vtarget = _mm256_set1_epi64x(target);
229 const __m256i vprefix =
230 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(prefix_before));
231 const __m256i vmin =
232 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(min_excess));
233 const __m256i vmax =
234 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(max_excess));
235
236 const __m256i relative = _mm256_sub_epi64(vtarget, vprefix);
237 const __m256i ge_min = _mm256_or_si256(_mm256_cmpgt_epi64(relative, vmin),
238 _mm256_cmpeq_epi64(relative, vmin));
239 const __m256i le_max = _mm256_or_si256(_mm256_cmpgt_epi64(vmax, relative),
240 _mm256_cmpeq_epi64(vmax, relative));
241 __m256i matched = _mm256_and_si256(ge_min, le_max);
242 if (include_zero_boundary) {
243 matched = _mm256_or_si256(matched, _mm256_cmpeq_epi64(vtarget, vprefix));
244 }
245
246 const uint32_t byte_mask =
247 static_cast<uint32_t>(_mm256_movemask_epi8(matched));
248 uint32_t result = 0;
249 for (size_t lane = 0; lane < 4; ++lane) {
250 const uint32_t lane_mask = 0xffu << (lane * 8);
251 if ((byte_mask & lane_mask) == lane_mask) {
252 result |= uint32_t{1} << lane;
253 }
254 }
255 return result;
256#else
257 uint32_t result = 0;
258 for (size_t lane = 0; lane < 4; ++lane) {
259 const int64_t relative = target - prefix_before[lane];
260 const bool found =
261 (min_excess[lane] <= relative && relative <= max_excess[lane]) ||
262 (include_zero_boundary && relative == 0);
263 if (found) {
264 result |= uint32_t{1} << lane;
265 }
266 }
267 return result;
268#endif
269}
270
278static inline uint64_t first_bits_mask(size_t num) {
279 return num >= 64 ? UINT64_MAX : ((1llu << num) - 1);
280}
281
282namespace pixie {
283
302inline void copy_packed_bits(const uint64_t* source,
303 size_t source_bit,
304 uint64_t* destination,
305 size_t destination_bit,
306 size_t count) noexcept {
307 // Peel the destination's partial word, then write whole words in batches.
308 const auto copy_boundary = [&](size_t width) {
309 const auto shift = source_bit % 64;
310 uint64_t value = source[source_bit / 64] >> shift;
311 if (width > 64 - shift) {
312 value |= source[source_bit / 64 + 1] << (64 - shift);
313 }
314 const auto offset = destination_bit % 64;
315 const auto mask = first_bits_mask(width) << offset;
316 auto& word = destination[destination_bit / 64];
317 word = (word & ~mask) | ((value << offset) & mask);
318 source_bit += width;
319 destination_bit += width;
320 count -= width;
321 };
322 if (count == 0) {
323 return;
324 }
325 if (destination_bit % 64 != 0) {
326 copy_boundary(std::min(count, 64 - destination_bit % 64));
327 }
328 const auto shift = source_bit % 64;
329 const auto* input = source + source_bit / 64;
330 auto* output = destination + destination_bit / 64;
331 auto words = count / 64;
332 if (shift == 0) {
333 std::copy_n(input, words, output);
334 } else {
335#if defined(__AVX512F__)
336#if defined(__AVX512VBMI2__)
337 const auto shifts = _mm512_set1_epi64(shift);
338#else
339 const auto low_shift = _mm_cvtsi64_si128(shift);
340 const auto high_shift = _mm_cvtsi64_si128(64 - shift);
341#endif
342 for (; words >= 8; words -= 8, input += 8, output += 8) {
343 const auto low = _mm512_loadu_si512(input);
344 const auto high = _mm512_loadu_si512(input + 1);
345#if defined(__AVX512VBMI2__)
346 _mm512_storeu_si512(output, _mm512_shrdv_epi64(low, high, shifts));
347#else
348 _mm512_storeu_si512(output,
349 _mm512_or_si512(_mm512_srl_epi64(low, low_shift),
350 _mm512_sll_epi64(high, high_shift)));
351#endif
352 }
353#elif defined(PIXIE_AVX2_SUPPORT)
354 const auto low_shift = _mm_cvtsi64_si128(shift);
355 const auto high_shift = _mm_cvtsi64_si128(64 - shift);
356 for (; words >= 4; words -= 4, input += 4, output += 4) {
357 const auto low =
358 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(input));
359 const auto high =
360 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(input + 1));
361 _mm256_storeu_si256(reinterpret_cast<__m256i*>(output),
362 _mm256_or_si256(_mm256_srl_epi64(low, low_shift),
363 _mm256_sll_epi64(high, high_shift)));
364 }
365#endif
366 for (size_t i = 0; i < words; ++i) {
367 output[i] = (input[i] >> shift) | (input[i + 1] << (64 - shift));
368 }
369 }
370 source_bit += count / 64 * 64;
371 destination_bit += count / 64 * 64;
372 count %= 64;
373 if (count != 0) {
374 copy_boundary(count);
375 }
376}
377
378} // namespace pixie
379
400static inline uint64_t rank_512(const uint64_t* x, uint64_t count) {
401#if defined(PIXIE_AVX512_SUPPORT) && defined(__AVX512VBMI2__)
402
403 __m512i a = _mm512_maskz_set1_epi64((1ull << ((count >> 6))) - 1,
404 std::numeric_limits<uint64_t>::max());
405 __m512i b = _mm512_maskz_set1_epi64((1ull << ((count >> 6) + 1)) - 1,
406 std::numeric_limits<uint64_t>::max());
407 __m512i mask = _mm512_shldv_epi64(a, b, _mm512_set1_epi64(count % 64));
408
409 __m512i res = _mm512_loadu_epi64(x);
410 res = _mm512_and_epi64(res, mask);
411 __m512i cnt = _mm512_popcnt_epi64(res);
412 return _mm512_reduce_add_epi64(cnt);
413
414#else
415
416 size_t last_uint = count < 512 ? count >> 6 : 8;
417
418 uint64_t pop_val = 0;
419
420 for (size_t i = 0; i < last_uint; i++) {
421 pop_val += std::popcount(x[i]);
422 }
423
424 pop_val += count < 512
425 ? std::popcount(x[last_uint] & first_bits_mask(count & 63))
426 : 0;
427 return pop_val;
428
429#endif
430}
431
432struct PixieSelectByteLut {
433 uint8_t popcounts[256];
434 uint8_t select[256][8];
435
436 constexpr PixieSelectByteLut() : popcounts{}, select{} {
437 for (int byte = 0; byte < 256; ++byte) {
438 for (int rank = 0; rank < 8; ++rank) {
439 select[byte][rank] = 8;
440 }
441
442 int count = 0;
443 for (int bit = 0; bit < 8; ++bit) {
444 if (((byte >> bit) & 1) != 0) {
445 select[byte][count++] = static_cast<uint8_t>(bit);
446 }
447 }
448 popcounts[byte] = static_cast<uint8_t>(count);
449 }
450 }
451};
452
453static inline constexpr PixieSelectByteLut pixie_select_byte_lut;
454
455static inline uint64_t select_64_no_bmi2(uint64_t x, uint64_t rank) {
456 uint64_t offset = 0;
457
458 uint64_t count = std::popcount(static_cast<uint32_t>(x));
459 if (rank >= count) {
460 rank -= count;
461 x >>= 32;
462 offset += 32;
463 }
464
465 count = std::popcount(static_cast<uint16_t>(x));
466 if (rank >= count) {
467 rank -= count;
468 x >>= 16;
469 offset += 16;
470 }
471
472 const auto low_byte = static_cast<uint8_t>(x);
473 count = pixie_select_byte_lut.popcounts[low_byte];
474 if (rank >= count) {
475 rank -= count;
476 x >>= 8;
477 offset += 8;
478 }
479
480 return offset + pixie_select_byte_lut.select[static_cast<uint8_t>(x)][rank];
481}
482
492static inline uint64_t select_64(uint64_t x, uint64_t rank) {
493#ifdef PIXIE_BMI2_SUPPORT
494 return std::countr_zero(_pdep_u64(1ull << rank, x));
495#else
496 return select_64_no_bmi2(x, rank);
497#endif
498}
499
500template <bool Invert>
501static inline uint64_t select_512_word_count(uint64_t word) {
502 if constexpr (Invert) {
503 return std::popcount(~word);
504 } else {
505 return std::popcount(word);
506 }
507}
508
509template <bool Invert>
510static inline uint64_t select_512_selected_word(uint64_t word) {
511 if constexpr (Invert) {
512 return ~word;
513 } else {
514 return word;
515 }
516}
517
518template <bool Invert>
519static inline uint64_t select_512_scalar_impl(const uint64_t* x,
520 uint64_t rank) {
521 size_t word = 0;
522 uint64_t count;
523 if constexpr (Invert) {
524 count = std::popcount(~x[0]);
525 } else {
526 count = std::popcount(x[0]);
527 }
528 while (word < 7 && count <= rank) {
529 rank -= count;
530 ++word;
531 if constexpr (Invert) {
532 count = std::popcount(~x[word]);
533 } else {
534 count = std::popcount(x[word]);
535 }
536 }
537
538 if constexpr (Invert) {
539 return word * 64 + select_64(~x[word], rank);
540 } else {
541 return word * 64 + select_64(x[word], rank);
542 }
543}
544
545#ifdef PIXIE_AVX2_SUPPORT
546template <bool Invert>
547static inline void select_512_avx2_counts(const uint64_t* x, uint64_t* counts) {
548 const __m256i low_mask = _mm256_set1_epi8(0x0F);
549 const __m256i zero = _mm256_setzero_si256();
550 const __m256i sixty_four = _mm256_set1_epi64x(64);
551
552 for (int half = 0; half < 2; ++half) {
553 const __m256i words =
554 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 4 * half));
555
556 const __m256i low_nibbles = _mm256_and_si256(words, low_mask);
557 const __m256i high_nibbles =
558 _mm256_and_si256(_mm256_srli_epi16(words, 4), low_mask);
559 const __m256i byte_counts =
560 _mm256_add_epi8(_mm256_shuffle_epi8(lookup_popcount_4, low_nibbles),
561 _mm256_shuffle_epi8(lookup_popcount_4, high_nibbles));
562 __m256i word_counts = _mm256_sad_epu8(byte_counts, zero);
563 if constexpr (Invert) {
564 word_counts = _mm256_sub_epi64(sixty_four, word_counts);
565 }
566 _mm256_store_si256(reinterpret_cast<__m256i*>(counts + 4 * half),
567 word_counts);
568 }
569}
570
571template <bool Invert>
572static inline uint64_t select_512_avx2_impl(const uint64_t* x, uint64_t rank) {
573 alignas(32) uint64_t counts[8];
574 select_512_avx2_counts<Invert>(x, counts);
575
576 for (size_t i = 0; i < 8; ++i) {
577 if (rank < counts[i]) {
578 return i * 64 + select_64(select_512_selected_word<Invert>(x[i]), rank);
579 }
580 rank -= counts[i];
581 }
582 return 512;
583}
584#endif
585
586#ifdef PIXIE_AVX512_SUPPORT
587static inline __m512i select_512_avx512_prefix_sum_u64(__m512i prefix) {
588 const __m512i idx_shift1 = _mm512_set_epi64(6, 5, 4, 3, 2, 1, 0, 0);
589 const __m512i idx_shift2 = _mm512_set_epi64(5, 4, 3, 2, 1, 0, 0, 0);
590 const __m512i idx_shift4 = _mm512_set_epi64(3, 2, 1, 0, 0, 0, 0, 0);
591
592 __m512i tmp = _mm512_maskz_permutexvar_epi64(0xFE, idx_shift1, prefix);
593 prefix = _mm512_add_epi64(prefix, tmp);
594 tmp = _mm512_maskz_permutexvar_epi64(0xFC, idx_shift2, prefix);
595 prefix = _mm512_add_epi64(prefix, tmp);
596 tmp = _mm512_maskz_permutexvar_epi64(0xF0, idx_shift4, prefix);
597 return _mm512_add_epi64(prefix, tmp);
598}
599
600static inline uint64_t select_512_avx512_previous_prefix(__m512i prefix,
601 uint32_t lane) {
602 if (lane == 0) {
603 return 0;
604 }
605 const __m512i idx_previous =
606 _mm512_set1_epi64(static_cast<int64_t>(lane - 1));
607 const __m512i previous_vec = _mm512_permutexvar_epi64(idx_previous, prefix);
608 return static_cast<uint64_t>(
609 _mm_cvtsi128_si64(_mm512_castsi512_si128(previous_vec)));
610}
611
612template <bool Invert>
613static inline uint64_t select_512_avx512_impl(const uint64_t* x,
614 uint64_t rank) {
615 const __m512i words = _mm512_loadu_epi64(x);
616 __m512i prefix = _mm512_popcnt_epi64(words);
617 if constexpr (Invert) {
618 prefix = _mm512_sub_epi64(_mm512_set1_epi64(64), prefix);
619 }
620 prefix = select_512_avx512_prefix_sum_u64(prefix);
621
622 const __mmask8 mask = _mm512_cmpgt_epu64_mask(
623 prefix, _mm512_set1_epi64(static_cast<int64_t>(rank)));
624 const uint32_t lane = _tzcnt_u32(static_cast<uint32_t>(mask));
625 const uint64_t previous = select_512_avx512_previous_prefix(prefix, lane);
626 return lane * 64 +
627 select_64(select_512_selected_word<Invert>(x[lane]), rank - previous);
628}
629#endif
630
643static inline uint64_t select_512(const uint64_t* x, uint64_t rank) {
644#ifdef PIXIE_AVX512_SUPPORT
645 return select_512_avx512_impl<false>(x, rank);
646#else
647 // The AVX2 implementation is kept for explicit benchmarking, but production
648 // intentionally falls back to scalar here: on tested machines, the scalar
649 // word scan is faster than AVX2 setup for this 8-word block.
650 return select_512_scalar_impl<false>(x, rank);
651#endif
652}
653
664static inline uint64_t select0_512(const uint64_t* x, uint64_t rank0) {
665#ifdef PIXIE_AVX512_SUPPORT
666 return select_512_avx512_impl<true>(x, rank0);
667#else
668 // See select_512: AVX2 is deliberately not used in the default path because
669 // the scalar scan benchmarked faster for 512-bit select blocks.
670 return select_512_scalar_impl<true>(x, rank0);
671#endif
672}
673
678static inline uint16_t lower_bound_4x64(const uint64_t* x, uint64_t y) {
679#ifdef PIXIE_AVX512_SUPPORT
680
681 auto y_4 = _mm256_set1_epi64x(y);
682 auto reg_256 = _mm256_loadu_epi64(x);
683 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
684
685 return _tzcnt_u16(cmp);
686
687#else
688#ifdef PIXIE_AVX2_SUPPORT
689
690 auto y_4 = _mm256_set1_epi64x(y);
691 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
692
693 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
694 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
695 __m256i y_offset = _mm256_xor_si256(y_4, offset);
696 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
697 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
698
699 return _tzcnt_u32(mask) >> 3;
700
701#else
702
703 for (uint16_t i = 0; i < 4; ++i) {
704 if (x[i] >= y) {
705 return i;
706 }
707 }
708 return 4;
709
710#endif
711#endif
712}
713
727static inline uint16_t lower_bound_delta_4x64(const uint64_t* x,
728 uint64_t y,
729 const uint64_t* delta_array,
730 uint64_t delta_scalar) {
731#ifdef PIXIE_AVX512_SUPPORT
732
733 const __m256i dlt_256 = _mm256_loadu_epi64(delta_array);
734 auto x_256 = _mm256_loadu_epi64(x);
735 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
736 auto y_4 = _mm256_set1_epi64x(y);
737
738 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
739 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
740 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
741
742 return _tzcnt_u16(cmp);
743
744#else
745#ifdef PIXIE_AVX2_SUPPORT
746
747 const __m256i dlt_256 =
748 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
749 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
750 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
751 auto y_4 = _mm256_set1_epi64x(y);
752
753 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
754 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
755
756 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
757 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
758 __m256i y_offset = _mm256_xor_si256(y_4, offset);
759 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
760 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
761
762 return _tzcnt_u32(mask) >> 3;
763
764#else
765
766 for (uint16_t i = 0; i < 4; ++i) {
767 if (delta_array[i] + delta_scalar - x[i] >= y) {
768 return i;
769 }
770 }
771 return 4;
772
773#endif
774#endif
775}
776
781static inline uint16_t lower_bound_8x64(const uint64_t* x, uint64_t y) {
782#ifdef PIXIE_AVX512_SUPPORT
783
784 auto y_8 = _mm512_set1_epi64(y);
785 auto reg_512 = _mm512_loadu_epi64(x);
786 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
787
788 return _tzcnt_u16(cmp);
789
790#else
791#ifdef PIXIE_AVX2_SUPPORT
792
793 uint16_t len = lower_bound_4x64(x, y);
794
795 if (len < 4) {
796 return len;
797 }
798
799 return len + lower_bound_4x64(x + 4, y);
800
801#else
802
803 for (uint16_t i = 0; i < 8; ++i) {
804 if (x[i] >= y) {
805 return i;
806 }
807 }
808 return 8;
809
810#endif
811#endif
812}
813
827static inline uint16_t lower_bound_delta_8x64(const uint64_t* x,
828 uint64_t y,
829 const uint64_t* delta_array,
830 uint64_t delta_scalar) {
831#ifdef PIXIE_AVX512_SUPPORT
832
833 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
834 auto x_512 = _mm512_loadu_epi64(x);
835 auto dlt_8 = _mm512_set1_epi64(delta_scalar);
836 auto y_8 = _mm512_set1_epi64(y);
837
838 auto tmp = _mm512_add_epi64(dlt_8, dlt_512);
839 auto reg_512 = _mm512_sub_epi64(tmp, x_512);
840 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
841
842 return _tzcnt_u16(cmp);
843
844#else
845#ifdef PIXIE_AVX2_SUPPORT
846
847 uint16_t len = lower_bound_delta_4x64(x, y, delta_array, delta_scalar);
848
849 if (len < 4) {
850 return len;
851 }
852
853 return len + lower_bound_delta_4x64(x + 4, y, delta_array + 4, delta_scalar);
854
855#else
856
857 for (uint16_t i = 0; i < 8; ++i) {
858 if (delta_array[i] + delta_scalar - x[i] >= y) {
859 return i;
860 }
861 }
862 return 8;
863
864#endif
865#endif
866}
867
872static inline uint16_t lower_bound_32x16(const uint16_t* x, uint16_t y) {
873#ifdef PIXIE_AVX512_SUPPORT
874
875 auto y_32 = _mm512_set1_epi16(y);
876 auto reg_512 = _mm512_loadu_epi16(x);
877 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
878 return std::popcount(cmp);
879
880#else
881#ifdef PIXIE_AVX2_SUPPORT
882
883 auto y_16 = _mm256_set1_epi16(y);
884 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
885
886 const __m256i offset = _mm256_set1_epi16(0x8000);
887 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
888 __m256i y_offset = _mm256_xor_si256(y_16, offset);
889 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
890
891 uint16_t count = std::popcount(mask) >> 1;
892
893 reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
894
895 x_offset = _mm256_xor_si256(reg_256, offset);
896 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
897
898 return count + (std::popcount(mask) >> 1);
899
900#else
901
902 uint16_t cnt = 0;
903 for (uint16_t i = 0; i < 32; ++i) {
904 if (x[i] < y) {
905 cnt++;
906 }
907 }
908 return cnt;
909
910#endif
911#endif
912}
913
927static inline uint16_t lower_bound_delta_32x16(const uint16_t* x,
928 uint16_t y,
929 const uint16_t* delta_array,
930 uint16_t delta_scalar) {
931#ifdef PIXIE_AVX512_SUPPORT
932
933 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
934 auto x_512 = _mm512_loadu_epi64(x);
935 auto dlt_32 = _mm512_set1_epi16(delta_scalar);
936 auto y_32 = _mm512_set1_epi16(y);
937
938 auto tmp = _mm512_add_epi16(dlt_32, dlt_512);
939 auto reg_512 = _mm512_sub_epi16(tmp, x_512);
940 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
941 return std::popcount(cmp);
942
943#else
944#ifdef PIXIE_AVX2_SUPPORT
945
946 auto dlt_256 =
947 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
948 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
949 auto dlt_16 = _mm256_set1_epi16(delta_scalar);
950 auto y_16 = _mm256_set1_epi16(y);
951
952 auto tmp = _mm256_add_epi16(dlt_16, dlt_256);
953 auto reg_256 = _mm256_sub_epi16(tmp, x_256);
954
955 const __m256i offset = _mm256_set1_epi16(0x8000);
956 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
957 __m256i y_offset = _mm256_xor_si256(y_16, offset);
958 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
959
960 uint16_t count = std::popcount(mask) >> 1;
961
962 dlt_256 =
963 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array + 16));
964 x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
965
966 tmp = _mm256_add_epi16(dlt_16, dlt_256);
967 reg_256 = _mm256_sub_epi16(tmp, x_256);
968
969 x_offset = _mm256_xor_si256(reg_256, offset);
970 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
971
972 return count + (std::popcount(mask) >> 1);
973
974#else
975
976 uint16_t cnt = 0;
977 for (uint16_t i = 0; i < 32; ++i) {
978 if (delta_array[i] + delta_scalar - x[i] < y) {
979 cnt++;
980 }
981 }
982 return cnt;
983
984#endif
985#endif
986}
987
999static inline void popcount_64x4(const uint8_t* x, uint8_t* result) {
1000#ifdef PIXIE_AVX512_SUPPORT
1001 __m256i data = _mm256_loadu_si256((__m256i const*)x);
1002
1003 // Masks for extracting the lower and upper nibbles
1004 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
1005
1006 // Count bits in the lower half
1007 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
1008 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
1009
1010 // Count bits in the upper half
1011 __m256i high_bits = _mm256_srli_epi16(data, 4);
1012 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
1013 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
1014
1015 // Pack the results into a single output vector
1016 __m256i result_vec =
1017 _mm256_or_si256(low_count, _mm256_slli_epi16(high_count, 4));
1018 _mm256_storeu_epi8(result, result_vec);
1019#else
1020 // Fallback implementation for non-AVX2 platforms
1021 for (size_t i = 0; i < 32; i++) {
1022 // Count bits in the lower half
1023 uint8_t a = x[i] & 0x0F;
1024 uint8_t low_count = std::popcount(a);
1025 // Count bits in the upper half
1026 a = (x[i] >> 4) & 0x0F;
1027 uint8_t high_count = std::popcount(a);
1028
1029 // Pack the counts into the output byte
1030 result[i] = low_count | (high_count << 4);
1031 }
1032#endif
1033}
1034
1046static inline void popcount_32x8(const uint8_t* x, uint8_t* result) {
1047#ifdef PIXIE_AVX512_SUPPORT
1048 // Load 64 4-bit integers (256 bits total)
1049 __m256i data = _mm256_loadu_si256((__m256i const*)x);
1050 auto popcount_8 = _mm256_popcnt_epi8(data);
1051 _mm256_storeu_si256((__m256i*)result, popcount_8);
1052#else
1053#ifdef PIXIE_AVX2_SUPPORT
1054 // Load 64 4-bit integers (256 bits total)
1055 __m256i data = _mm256_loadu_si256((__m256i const*)x);
1056
1057 // Masks for extracting the lower and upper nibbles
1058 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
1059
1060 // Count bits in lower half
1061 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
1062 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
1063
1064 // Count bits upper half
1065 __m256i high_bits = _mm256_srli_epi16(data, 4);
1066 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
1067 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
1068
1069 __m256i result_vec = _mm256_add_epi8(low_count, high_count);
1070 _mm256_storeu_si256((__m256i*)result, result_vec);
1071#else
1072 // Fallback implementation for non-AVX2 platforms
1073 for (size_t i = 0; i < 32; i++) {
1074 result[i] = std::popcount(x[i]);
1075 }
1076#endif
1077#endif
1078}
1079
1080#ifdef PIXIE_AVX2_SUPPORT
1081// clang-format off
1082// LUT for total excess change across a 4-bit nibble
1083static inline const __m256i excess_lut_delta = _mm256_setr_epi8(
1084 -4, -2, -2, 0,
1085 -2, 0, 0, 2,
1086 -2, 0, 0, 2,
1087 0, 2, 2, 4,
1088 -4, -2, -2, 0,
1089 -2, 0, 0, 2,
1090 -2, 0, 0, 2,
1091 0, 2, 2, 4);
1092
1093// LUTs for target relative excess positions
1094static inline const __m256i excess_lut_pos0 = _mm256_setr_epi8(
1095 -1, 1, -1, 1,
1096 -1, 1, -1, 1,
1097 -1, 1, -1, 1,
1098 -1, 1, -1, 1,
1099 -1, 1, -1, 1,
1100 -1, 1, -1, 1,
1101 -1, 1, -1, 1,
1102 -1, 1, -1, 1);
1103
1104static inline const __m256i excess_lut_pos1 = _mm256_setr_epi8(
1105 -2, 0, 0, 2,
1106 -2, 0, 0, 2,
1107 -2, 0, 0, 2,
1108 -2, 0, 0, 2,
1109 -2, 0, 0, 2,
1110 -2, 0, 0, 2,
1111 -2, 0, 0, 2,
1112 -2, 0, 0, 2);
1113
1114static inline const __m256i excess_lut_pos2 = _mm256_setr_epi8(
1115 -3, -1, -1, 1,
1116 -1, 1, 1, 3,
1117 -3, -1, -1, 1,
1118 -1, 1, 1, 3,
1119 -3, -1, -1, 1,
1120 -1, 1, 1, 3,
1121 -3, -1, -1, 1,
1122 -1, 1, 1, 3);
1123static inline const __m256i excess_lut_min = _mm256_setr_epi8(
1124 -4, -2, -2, 0,
1125 -2, 0, -1, 1,
1126 -3, -1, -1, 1,
1127 -2, 0, -1, 1,
1128 -4, -2, -2, 0,
1129 -2, 0, -1, 1,
1130 -3, -1, -1, 1,
1131 -2, 0, -1, 1);
1132static inline constexpr int8_t excess_lut_min_offset[16] = {
1133 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 1, 1, 2, 2, 1, 1};
1134static inline const __m256i excess_lut_pack_multiplier =
1135 _mm256_set1_epi16(0x1001);
1136static inline const __m256i excess_lut_bit0 = _mm256_set1_epi8(1);
1137static inline const __m256i excess_lut_bit1 = _mm256_set1_epi8(2);
1138static inline const __m256i excess_lut_bit2 = _mm256_set1_epi8(4);
1139static inline const __m256i excess_lut_bit3 = _mm256_set1_epi8(8);
1140static inline const __m256i excess_lut_nibble_index = _mm256_setr_epi8(
1141 0, 1, 2, 3,
1142 4, 5, 6, 7,
1143 8, 9, 10, 11,
1144 12, 13, 14, 15,
1145 16, 17, 18, 19,
1146 20, 21, 22, 23,
1147 24, 25, 26, 27,
1148 28, 29, 30, 31);
1149static inline const __m128i excess_lut_nibble_mask = _mm_set1_epi8(0x0F);
1150// clang-format on
1151
1152static inline __m256i excess_nibbles_128_avx2(const uint64_t* s) noexcept {
1153 __m128i word_vec = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
1154 __m128i lo_nibbles = _mm_and_si128(word_vec, excess_lut_nibble_mask);
1155 __m128i hi_nibbles =
1156 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask);
1157
1158 __m128i unpack_lo = _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
1159 __m128i unpack_hi = _mm_unpackhi_epi8(lo_nibbles, hi_nibbles);
1160
1161 return _mm256_inserti128_si256(_mm256_castsi128_si256(unpack_lo), unpack_hi,
1162 1);
1163}
1164
1165static inline __m256i excess_bit_masks_16x_i16() noexcept {
1166 return _mm256_setr_epi16(0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
1167 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
1168 0x1000, 0x2000, 0x4000,
1169 static_cast<int16_t>(0x8000));
1170}
1171
1172static inline __m256i excess_prefix_sum_16x_i16(__m256i v) noexcept {
1173 __m256i x = v;
1174 __m256i t = _mm256_slli_si256(x, 2);
1175 x = _mm256_add_epi16(x, t);
1176 t = _mm256_slli_si256(x, 4);
1177 x = _mm256_add_epi16(x, t);
1178 t = _mm256_slli_si256(x, 8);
1179 x = _mm256_add_epi16(x, t);
1180
1181 __m128i lo = _mm256_extracti128_si256(x, 0);
1182 __m128i hi = _mm256_extracti128_si256(x, 1);
1183 const int16_t carry = static_cast<int16_t>(_mm_extract_epi16(lo, 7));
1184 hi = _mm_add_epi16(hi, _mm_set1_epi16(carry));
1185
1186 __m256i out = _mm256_castsi128_si256(lo);
1187 return _mm256_inserti128_si256(out, hi, 1);
1188}
1189#endif
1190
1200 int min_excess = 0;
1201 size_t offset = 128;
1202};
1203
1211 ExcessResult suffix;
1212 ExcessResult prefix;
1213};
1214
1215constexpr int8_t excess_byte_delta_value(uint8_t x) {
1216 return static_cast<int8_t>(2 * std::popcount(x) - 8);
1217}
1218
1219constexpr int8_t excess_byte_min_prefix_value(uint8_t x) {
1220 int cur = 0;
1221 int best = 0;
1222 for (int bit = 0; bit < 8; ++bit) {
1223 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1224 if (bit == 0 || cur < best) {
1225 best = cur;
1226 }
1227 }
1228 return static_cast<int8_t>(best);
1229}
1230
1231constexpr int8_t excess_byte_min_prefix_offset_value(uint8_t x) {
1232 int cur = 0;
1233 int best = 0;
1234 int best_offset = 1;
1235 for (int bit = 0; bit < 8; ++bit) {
1236 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1237 if (bit == 0 || cur < best) {
1238 best = cur;
1239 best_offset = bit + 1;
1240 }
1241 }
1242 return static_cast<int8_t>(best_offset);
1243}
1244
1245constexpr int8_t excess_nibble_min_prefix_offset_value(uint8_t x, int bits) {
1246 int cur = 0;
1247 int best = 0;
1248 int best_offset = 1;
1249 for (int bit = 0; bit < bits; ++bit) {
1250 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1251 if (bit == 0 || cur < best) {
1252 best = cur;
1253 best_offset = bit + 1;
1254 }
1255 }
1256 return static_cast<int8_t>(best_offset);
1257}
1258
1259template <typename Fn>
1260constexpr std::array<int8_t, 256> excess_make_byte_lut(Fn fn) {
1261 std::array<int8_t, 256> out{};
1262 for (size_t i = 0; i < out.size(); ++i) {
1263 out[i] = fn(static_cast<uint8_t>(i));
1264 }
1265 return out;
1266}
1267
1268static inline constexpr std::array<int8_t, 256> excess_byte_delta_lut =
1269 excess_make_byte_lut([](uint8_t x) { return excess_byte_delta_value(x); });
1270static inline constexpr std::array<int8_t, 256> excess_byte_min_lut =
1271 excess_make_byte_lut(
1272 [](uint8_t x) { return excess_byte_min_prefix_value(x); });
1273static inline constexpr std::array<int8_t, 256> excess_byte_min_offset_lut =
1274 excess_make_byte_lut(
1275 [](uint8_t x) { return excess_byte_min_prefix_offset_value(x); });
1276static inline constexpr std::array<std::array<int8_t, 16>, 4>
1277 excess_partial_nibble_min_offset_lut = [] {
1278 std::array<std::array<int8_t, 16>, 4> out{};
1279 for (size_t width = 1; width < out.size(); ++width) {
1280 for (size_t nibble = 0; nibble < out[width].size(); ++nibble) {
1281 out[width][nibble] = excess_nibble_min_prefix_offset_value(
1282 static_cast<uint8_t>(nibble), static_cast<int>(width));
1283 }
1284 }
1285 return out;
1286 }();
1287
1295constexpr uint8_t excess_byte_record_lows_mask(uint8_t byte, int threshold) {
1296 int cur = 0;
1297 uint8_t mask = 0;
1298 for (int bit = 0; bit < 8; ++bit) {
1299 cur += ((byte >> bit) & 1u) ? 1 : -1;
1300 if (cur < threshold) {
1301 mask |= static_cast<uint8_t>(1u << bit);
1302 }
1303 }
1304 return mask;
1305}
1306
1315static inline constexpr std::array<std::array<uint8_t, 8>, 256>
1316 excess_byte_record_lows_lut = [] {
1317 std::array<std::array<uint8_t, 8>, 256> out{};
1318 for (size_t byte = 0; byte < 256; ++byte) {
1319 for (int g = 0; g < 8; ++g) {
1320 out[byte][g] =
1321 excess_byte_record_lows_mask(static_cast<uint8_t>(byte), -g);
1322 }
1323 }
1324 return out;
1325 }();
1326
1340static inline int excess_positions_128(const uint64_t* s,
1341 int target_x,
1342 uint64_t* out) noexcept {
1343 out[0] = out[1] = 0;
1344 const int block_delta = 2 * (std::popcount(s[0]) + std::popcount(s[1])) - 128;
1345
1346 if (target_x < -128 || target_x > 128) {
1347 return block_delta;
1348 }
1349
1350#ifdef PIXIE_AVX2_SUPPORT
1351 const __m256i vdelta = excess_lut_delta;
1352 const __m256i vpos0 = excess_lut_pos0;
1353 const __m256i vpos1 = excess_lut_pos1;
1354 const __m256i vpos2 = excess_lut_pos2;
1355 const __m256i vmult = excess_lut_pack_multiplier;
1356 const __m256i vbit0 = excess_lut_bit0;
1357 const __m256i vbit1 = excess_lut_bit1;
1358 const __m256i vbit2 = excess_lut_bit2;
1359 const __m256i vbit3 = excess_lut_bit3;
1360
1361 const int d = 2 * target_x - block_delta;
1362 if (d < -128 || d > 128) {
1363 return block_delta;
1364 }
1365
1366 __m256i nibbles = excess_nibbles_128_avx2(s);
1367
1368 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
1369 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1370 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1371 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1372 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1373
1374 __m128i ps_lo = _mm256_castsi256_si128(ps);
1375 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1376 __m128i carry = _mm_set1_epi8((int8_t)_mm_extract_epi8(ps_lo, 15));
1377 ps_hi = _mm_add_epi8(ps_hi, carry);
1378 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1379
1380 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1381 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1382
1383 __m256i vtgt = _mm256_set1_epi8((int8_t)target_x);
1384 __m256i t = _mm256_sub_epi8(vtgt, excl_ps);
1385
1386 __m256i cmp0 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos0, nibbles), t);
1387 __m256i cmp1 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos1, nibbles), t);
1388 __m256i cmp2 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos2, nibbles), t);
1389 __m256i cmp3 = _mm256_cmpeq_epi8(ps, vtgt);
1390
1391 __m256i bit0 = _mm256_and_si256(cmp0, vbit0);
1392 __m256i bit1 = _mm256_and_si256(cmp1, vbit1);
1393 __m256i bit2 = _mm256_and_si256(cmp2, vbit2);
1394 __m256i bit3 = _mm256_and_si256(cmp3, vbit3);
1395
1396 __m256i total_match =
1397 _mm256_or_si256(_mm256_or_si256(bit0, bit1), _mm256_or_si256(bit2, bit3));
1398
1399 __m256i res = _mm256_maddubs_epi16(total_match, vmult);
1400 __m128i res_lo = _mm256_castsi256_si128(res);
1401 __m128i res_hi = _mm256_extracti128_si256(res, 1);
1402 __m128i packed = _mm_packus_epi16(res_lo, res_hi);
1403
1404 _mm_storeu_si128((__m128i*)out, packed);
1405#else
1406 int cur = 0;
1407 for (size_t i = 0; i < 128; ++i) {
1408 const uint64_t w = s[i >> 6];
1409 const int bit = int((w >> (i & 63)) & 1ull);
1410 cur += bit ? +1 : -1;
1411 if (cur == target_x) {
1412 out[i >> 6] |= (uint64_t{1} << (i & 63));
1413 }
1414 }
1415#endif
1416 return block_delta;
1417}
1418
1428static inline int prefix_excess_128(const uint64_t* s,
1429 size_t end_offset) noexcept {
1430 end_offset = end_offset > 128 ? 128 : end_offset;
1431 if (end_offset == 0) {
1432 return 0;
1433 }
1434 if (end_offset <= 64) {
1435 const int ones = static_cast<int>(std::popcount(
1436 s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1437 return 2 * ones - static_cast<int>(end_offset);
1438 }
1439 const int ones = static_cast<int>(
1440 std::popcount(s[0]) +
1441 std::popcount(s[1] &
1442 first_bits_mask(static_cast<uint32_t>(end_offset - 64))));
1443 return 2 * ones - static_cast<int>(end_offset);
1444}
1445
1455static inline int prefix_excess_64(const uint64_t* s,
1456 size_t end_offset) noexcept {
1457 end_offset = end_offset > 64 ? 64 : end_offset;
1458 if (end_offset == 0) {
1459 return 0;
1460 }
1461 const int ones = static_cast<int>(
1462 std::popcount(s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1463 return 2 * ones - static_cast<int>(end_offset);
1464}
1465
1466static inline ExcessResult excess_min_128_byte_lut_short(
1467 const uint64_t* s,
1468 size_t left,
1469 size_t right) noexcept {
1470 int best = prefix_excess_128(s, left);
1471 size_t best_offset = left;
1472 if (left == right) {
1473 return {best, best_offset};
1474 }
1475
1476 int current = best;
1477 size_t bit = left;
1478 for (; bit < right && (bit & 7u) != 0; ++bit) {
1479 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1480 const size_t offset = bit + 1;
1481 if (current < best) {
1482 best = current;
1483 best_offset = offset;
1484 }
1485 }
1486
1487 for (; bit + 8 <= right; bit += 8) {
1488 const uint8_t byte =
1489 static_cast<uint8_t>((s[bit >> 6] >> (bit & 63)) & 0xFFu);
1490 const int candidate = current + excess_byte_min_lut[byte];
1491 if (candidate < best) {
1492 best = candidate;
1493 best_offset = bit + static_cast<size_t>(excess_byte_min_offset_lut[byte]);
1494 }
1495 current += excess_byte_delta_lut[byte];
1496 }
1497
1498 for (; bit < right; ++bit) {
1499 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1500 const size_t offset = bit + 1;
1501 if (current < best) {
1502 best = current;
1503 best_offset = offset;
1504 }
1505 }
1506
1507 return {best, best_offset};
1508}
1509
1524static inline ExcessResult excess_min_64(const uint64_t* s,
1525 size_t left,
1526 size_t right) noexcept {
1527 if (left > right) {
1528 return {};
1529 }
1530 left = std::min<size_t>(left, 64);
1531 right = std::min<size_t>(right, 64);
1532
1533 int best = prefix_excess_64(s, left);
1534 size_t best_offset = left;
1535 if (left == right) {
1536 return {best, best_offset};
1537 }
1538
1539#ifdef PIXIE_SSE41_SUPPORT
1540 int current = best;
1541 size_t bit = left;
1542 for (; bit < right && (bit & 3u) != 0; ++bit) {
1543 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1544 const size_t offset = bit + 1;
1545 if (current < best) {
1546 best = current;
1547 best_offset = offset;
1548 }
1549 }
1550
1551 const size_t first_full_nibble = bit >> 2;
1552 const size_t last_full_nibble = right >> 2;
1553 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1554 const size_t end_nibble =
1555 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1556 if (first_full_nibble < end_nibble) {
1557 const __m128i nibbles = excess_nibbles_64_sse(s);
1558
1559 __m128i ps = _mm_shuffle_epi8(excess_lut_delta_sse, nibbles);
1560 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 1));
1561 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 2));
1562 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 4));
1563 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 8));
1564
1565 const __m128i excl_ps = _mm_slli_si128(ps, 1);
1566 __m128i local_min = _mm_shuffle_epi8(excess_lut_min_sse, nibbles);
1567 if (right_partial_width != 0) {
1568 __m128i partial_min = _mm_shuffle_epi8(excess_lut_pos0_sse, nibbles);
1569 if (right_partial_width >= 2) {
1570 partial_min = _mm_min_epi8(
1571 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, nibbles));
1572 }
1573 if (right_partial_width >= 3) {
1574 partial_min = _mm_min_epi8(
1575 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, nibbles));
1576 }
1577 local_min = _mm_blendv_epi8(
1578 local_min, partial_min,
1579 _mm_cmpeq_epi8(excess_lut_nibble_index_sse,
1580 _mm_set1_epi8(static_cast<int8_t>(last_full_nibble))));
1581 }
1582 const __m128i partial_candidates = _mm_add_epi8(excl_ps, local_min);
1583
1584 const __m128i idx = excess_lut_nibble_index_sse;
1585 const int first_minus_one_value = static_cast<int>(first_full_nibble) - 1;
1586 const __m128i first_minus_one =
1587 _mm_set1_epi8(static_cast<int8_t>(first_minus_one_value));
1588 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1589 const __m128i active = _mm_and_si128(_mm_cmpgt_epi8(idx, first_minus_one),
1590 _mm_cmpgt_epi8(last, idx));
1591 const __m128i masked_candidates =
1592 _mm_blendv_epi8(_mm_set1_epi8(127), partial_candidates, active);
1593
1594 __m128i min128 = masked_candidates;
1595 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1596 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1597 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1598 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1599
1600 const int candidate_min =
1601 static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1602 if (candidate_min < best) {
1603 const __m128i equal_min = _mm_cmpeq_epi8(
1604 masked_candidates, _mm_set1_epi8(static_cast<int8_t>(candidate_min)));
1605 const uint32_t equal_mask =
1606 static_cast<uint32_t>(_mm_movemask_epi8(equal_min));
1607 const uint32_t nibble_index = std::countr_zero(equal_mask);
1608 const uint8_t nibble =
1609 static_cast<uint8_t>((s[0] >> (nibble_index * 4u)) & 0xFu);
1610 best = candidate_min;
1611 if (right_partial_width != 0 && nibble_index == last_full_nibble) {
1612 int local = 0;
1613 int local_best = 0;
1614 size_t local_offset = 1;
1615 for (size_t i = 0; i < right_partial_width; ++i) {
1616 local += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1617 if (i == 0 || local < local_best) {
1618 local_best = local;
1619 local_offset = i + 1;
1620 }
1621 }
1622 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1623 } else {
1624 best_offset = static_cast<size_t>(nibble_index) * 4u +
1625 static_cast<size_t>(excess_nibble_min_offset[nibble]);
1626 }
1627 }
1628
1629 bit = end_nibble * 4;
1630 }
1631
1632 for (; bit < right; ++bit) {
1633 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1634 const size_t offset = bit + 1;
1635 if (current < best) {
1636 best = current;
1637 best_offset = offset;
1638 }
1639 }
1640#else
1641 int current = best;
1642 for (size_t bit = left; bit < right; ++bit) {
1643 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1644 const size_t offset = bit + 1;
1645 if (current < best) {
1646 best = current;
1647 best_offset = offset;
1648 }
1649 }
1650#endif
1651
1652 return {best, best_offset};
1653}
1654
1661static inline ExcessResult excess_min_128(const uint64_t* s,
1662 size_t left,
1663 size_t right) noexcept {
1664 if (left > right) {
1665 return {};
1666 }
1667 left = std::min<size_t>(left, 128);
1668 right = std::min<size_t>(right, 128);
1669
1670 if (right - left <= 32 && (left & 7u) == 0 && (right & 7u) == 0)
1671 [[unlikely]] {
1672 return excess_min_128_byte_lut_short(s, left, right);
1673 }
1674
1675 int best = prefix_excess_128(s, left);
1676 size_t best_offset = left;
1677 if (left == right) {
1678 return {best, best_offset};
1679 }
1680
1681#ifdef PIXIE_AVX2_SUPPORT
1682 int current = best;
1683 size_t bit = left;
1684 for (; bit < right && (bit & 3u) != 0; ++bit) {
1685 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1686 const size_t offset = bit + 1;
1687 if (current < best) {
1688 best = current;
1689 best_offset = offset;
1690 }
1691 }
1692
1693 const size_t first_nibble = bit >> 2;
1694 const size_t last_full_nibble = right >> 2;
1695 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1696 const size_t end_nibble =
1697 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1698 if (first_nibble < end_nibble) {
1699 const __m128i bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
1700 const __m128i lo_nibbles = _mm_and_si128(bytes, excess_lut_nibble_mask_sse);
1701 const __m128i hi_nibbles =
1702 _mm_and_si128(_mm_srli_epi16(bytes, 4), excess_lut_nibble_mask_sse);
1703 const __m128i lo_delta = _mm_shuffle_epi8(excess_lut_delta_sse, lo_nibbles);
1704 const __m128i hi_delta = _mm_shuffle_epi8(excess_lut_delta_sse, hi_nibbles);
1705 const __m128i byte_delta = _mm_add_epi8(lo_delta, hi_delta);
1706 const __m128i byte_prefix = excess_prefix_sum_16x_i8(byte_delta);
1707 const __m128i byte_prefix_before = _mm_slli_si128(byte_prefix, 1);
1708
1709 __m128i lo_local_min = _mm_shuffle_epi8(excess_lut_min_sse, lo_nibbles);
1710 __m128i hi_local_min = _mm_shuffle_epi8(excess_lut_min_sse, hi_nibbles);
1711
1712 const __m128i byte_index = excess_lut_nibble_index_sse;
1713 if (right_partial_width != 0) {
1714 const bool partial_is_high = (last_full_nibble & 1u) != 0;
1715 const size_t partial_byte = last_full_nibble >> 1;
1716 const __m128i partial_source = partial_is_high ? hi_nibbles : lo_nibbles;
1717 __m128i partial_min =
1718 _mm_shuffle_epi8(excess_lut_pos0_sse, partial_source);
1719 if (right_partial_width >= 2) {
1720 partial_min = _mm_min_epi8(
1721 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, partial_source));
1722 }
1723 if (right_partial_width >= 3) {
1724 partial_min = _mm_min_epi8(
1725 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, partial_source));
1726 }
1727 const __m128i partial_lane = _mm_cmpeq_epi8(
1728 byte_index, _mm_set1_epi8(static_cast<int8_t>(partial_byte)));
1729 if (partial_is_high) {
1730 hi_local_min = _mm_blendv_epi8(hi_local_min, partial_min, partial_lane);
1731 } else {
1732 lo_local_min = _mm_blendv_epi8(lo_local_min, partial_min, partial_lane);
1733 }
1734 }
1735
1736 const __m128i lo_candidates =
1737 _mm_add_epi8(byte_prefix_before, lo_local_min);
1738 const __m128i hi_candidates =
1739 _mm_add_epi8(_mm_add_epi8(byte_prefix_before, lo_delta), hi_local_min);
1740
1741 __m128i masked_lo = lo_candidates;
1742 __m128i masked_hi = hi_candidates;
1743 if (first_nibble != 0 || end_nibble != 32) {
1744 const __m128i first_minus_one = _mm_set1_epi8(
1745 static_cast<int8_t>(static_cast<int>(first_nibble) - 1));
1746 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1747 const __m128i lo_active = _mm_and_si128(
1748 _mm_cmpgt_epi8(excess_lut_low_nibble_index_sse, first_minus_one),
1749 _mm_cmpgt_epi8(last, excess_lut_low_nibble_index_sse));
1750 const __m128i hi_active = _mm_and_si128(
1751 _mm_cmpgt_epi8(excess_lut_high_nibble_index_sse, first_minus_one),
1752 _mm_cmpgt_epi8(last, excess_lut_high_nibble_index_sse));
1753 masked_lo = _mm_blendv_epi8(_mm_set1_epi8(127), lo_candidates, lo_active);
1754 masked_hi = _mm_blendv_epi8(_mm_set1_epi8(127), hi_candidates, hi_active);
1755 }
1756
1757 const int candidate_min =
1758 excess_horizontal_min_i8(_mm_min_epi8(masked_lo, masked_hi));
1759 if (candidate_min < best) {
1760 const __m128i min_vec = _mm_set1_epi8(static_cast<int8_t>(candidate_min));
1761 const uint32_t lo_equal_mask = static_cast<uint32_t>(
1762 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_lo, min_vec)));
1763 const uint32_t hi_equal_mask = static_cast<uint32_t>(
1764 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_hi, min_vec)));
1765 const uint32_t lo_nibble_index =
1766 lo_equal_mask == 0
1767 ? 32u
1768 : static_cast<uint32_t>(std::countr_zero(lo_equal_mask)) * 2u;
1769 const uint32_t hi_nibble_index =
1770 hi_equal_mask == 0
1771 ? 32u
1772 : static_cast<uint32_t>(std::countr_zero(hi_equal_mask)) * 2u +
1773 1u;
1774 const uint32_t nibble_index = std::min(lo_nibble_index, hi_nibble_index);
1775 const uint32_t byte_offset = nibble_index >> 1u;
1776 const uint64_t byte_word = s[byte_offset >> 3u];
1777 const uint8_t byte = static_cast<uint8_t>(
1778 (byte_word >> ((byte_offset & 7u) * 8u)) & 0xFFu);
1779 const uint8_t nibble = (nibble_index & 1u) == 0
1780 ? static_cast<uint8_t>(byte & 0xFu)
1781 : static_cast<uint8_t>((byte >> 4u) & 0xFu);
1782 const size_t local_offset =
1783 right_partial_width != 0 && nibble_index == last_full_nibble
1784 ? static_cast<size_t>(
1785 excess_partial_nibble_min_offset_lut[right_partial_width]
1786 [nibble])
1787 : static_cast<size_t>(excess_lut_min_offset[nibble]);
1788 best = candidate_min;
1789 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1790 }
1791 }
1792#else
1793 int current = 0;
1794 for (size_t bit = 0; bit < right; ++bit) {
1795 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1796 const size_t offset = bit + 1;
1797 if (offset >= left && current < best) {
1798 best = current;
1799 best_offset = offset;
1800 }
1801 }
1802#endif
1803
1804 return {best, best_offset};
1805}
1806
1820static inline ExcessBoundaryPairResult excess_min_64_disjoint_suffix_prefix(
1821 const uint64_t* suffix_s,
1822 size_t suffix_left,
1823 const uint64_t* prefix_s,
1824 size_t prefix_right) noexcept {
1825 suffix_left = std::min<size_t>(suffix_left, 64);
1826 prefix_right = std::min<size_t>(prefix_right, 64);
1827 return {excess_min_64(suffix_s, suffix_left, 63),
1828 excess_min_64(prefix_s, 0, prefix_right)};
1829}
1830
1849static inline ExcessBoundaryPairResult excess_min_128_disjoint_suffix_prefix(
1850 const uint64_t* suffix_s,
1851 size_t suffix_left,
1852 const uint64_t* prefix_s,
1853 size_t prefix_right) noexcept {
1854 suffix_left = std::min<size_t>(suffix_left, 128);
1855 prefix_right = std::min<size_t>(prefix_right, 128);
1856 if (suffix_left <= prefix_right || suffix_left > 127 || prefix_right > 127) {
1857 return {excess_min_128(suffix_s, suffix_left, 127),
1858 excess_min_128(prefix_s, 0, prefix_right)};
1859 }
1860
1861#ifdef PIXIE_AVX2_SUPPORT
1862 ExcessResult prefix{0, 0};
1863
1864 int suffix_best = prefix_excess_128(suffix_s, suffix_left);
1865 ExcessResult suffix{suffix_best, suffix_left};
1866 size_t suffix_bit = suffix_left;
1867 int suffix_current = suffix_best;
1868 for (; suffix_bit < 127 && (suffix_bit & 3u) != 0; ++suffix_bit) {
1869 suffix_current +=
1870 ((suffix_s[suffix_bit >> 6] >> (suffix_bit & 63)) & 1ull) != 0 ? 1 : -1;
1871 const size_t offset = suffix_bit + 1;
1872 if (suffix_current < suffix.min_excess) {
1873 suffix = {suffix_current, offset};
1874 }
1875 }
1876
1877 const size_t prefix_last_nibble = prefix_right >> 2;
1878 const size_t prefix_partial_width = prefix_right & 3u;
1879 const size_t prefix_end_nibble =
1880 prefix_last_nibble + (prefix_partial_width == 0 ? 0 : 1);
1881 const size_t suffix_first_nibble = suffix_bit < 127 ? suffix_bit >> 2 : 32;
1882 const int prefix_artificial_delta =
1883 prefix_excess_128(prefix_s, prefix_end_nibble * 4);
1884
1885 const __m256i idx = excess_lut_nibble_index;
1886 const __m256i prefix_active = _mm256_cmpgt_epi8(
1887 _mm256_set1_epi8(static_cast<int8_t>(prefix_end_nibble)), idx);
1888 const __m256i suffix_active = _mm256_cmpgt_epi8(
1889 idx, _mm256_set1_epi8(static_cast<int8_t>(suffix_first_nibble) - 1));
1890
1891 const __m256i prefix_nibbles = excess_nibbles_128_avx2(prefix_s);
1892 const __m256i suffix_nibbles = excess_nibbles_128_avx2(suffix_s);
1893 __m256i nibbles =
1894 _mm256_blendv_epi8(_mm256_set1_epi8(3), prefix_nibbles, prefix_active);
1895 nibbles = _mm256_blendv_epi8(nibbles, suffix_nibbles, suffix_active);
1896
1897 __m256i ps = _mm256_shuffle_epi8(excess_lut_delta, nibbles);
1898 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1899 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1900 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1901 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1902
1903 __m128i ps_lo = _mm256_castsi256_si128(ps);
1904 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1905 __m128i carry =
1906 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
1907 ps_hi = _mm_add_epi8(ps_hi, carry);
1908 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1909
1910 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1911 const __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1912
1913 __m256i local_min = _mm256_shuffle_epi8(excess_lut_min, nibbles);
1914 if (prefix_partial_width != 0) {
1915 __m256i partial_min = _mm256_shuffle_epi8(excess_lut_pos0, nibbles);
1916 if (prefix_partial_width >= 2) {
1917 partial_min = _mm256_min_epi8(
1918 partial_min, _mm256_shuffle_epi8(excess_lut_pos1, nibbles));
1919 }
1920 if (prefix_partial_width >= 3) {
1921 partial_min = _mm256_min_epi8(
1922 partial_min, _mm256_shuffle_epi8(excess_lut_pos2, nibbles));
1923 }
1924 local_min = _mm256_blendv_epi8(
1925 local_min, partial_min,
1926 _mm256_cmpeq_epi8(
1927 idx, _mm256_set1_epi8(static_cast<int8_t>(prefix_last_nibble))));
1928 }
1929 __m256i suffix_partial_min = _mm256_min_epi8(
1930 _mm256_shuffle_epi8(excess_lut_pos0, nibbles),
1931 _mm256_min_epi8(_mm256_shuffle_epi8(excess_lut_pos1, nibbles),
1932 _mm256_shuffle_epi8(excess_lut_pos2, nibbles)));
1933 const __m256i suffix_tail_active = _mm256_and_si256(
1934 suffix_active, _mm256_cmpeq_epi8(idx, _mm256_set1_epi8(31)));
1935 local_min =
1936 _mm256_blendv_epi8(local_min, suffix_partial_min, suffix_tail_active);
1937
1938 const __m256i base_candidates = _mm256_add_epi8(excl_ps, local_min);
1939 const __m256i sentinel = _mm256_set1_epi8(127);
1940
1941 const __m256i prefix_candidates =
1942 _mm256_blendv_epi8(sentinel, base_candidates, prefix_active);
1943 const __m256i suffix_candidates = _mm256_blendv_epi8(
1944 sentinel,
1945 _mm256_add_epi8(base_candidates,
1946 _mm256_set1_epi8(static_cast<int8_t>(
1947 suffix_current - prefix_artificial_delta))),
1948 suffix_active);
1949
1950 auto reduce_min = [](__m256i values) {
1951 __m128i min128 = _mm_min_epi8(_mm256_castsi256_si128(values),
1952 _mm256_extracti128_si256(values, 1));
1953 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1954 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1955 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1956 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1957 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1958 };
1959
1960 auto local_offset = [](uint8_t nibble, size_t width_value) {
1961 if (width_value == 0 || width_value == 4) {
1962 return static_cast<size_t>(excess_lut_min_offset[nibble]);
1963 }
1964 int current = 0;
1965 int best = 0;
1966 size_t best_offset = 1;
1967 for (size_t i = 0; i < width_value; ++i) {
1968 current += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1969 if (i == 0 || current < best) {
1970 best = current;
1971 best_offset = i + 1;
1972 }
1973 }
1974 return best_offset;
1975 };
1976
1977 const int prefix_min = reduce_min(prefix_candidates);
1978 if (prefix_min < prefix.min_excess) {
1979 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1980 _mm256_cmpeq_epi8(prefix_candidates,
1981 _mm256_set1_epi8(static_cast<int8_t>(prefix_min)))));
1982 const uint32_t prefix_lane = std::countr_zero(mask);
1983 const uint64_t word = prefix_s[prefix_lane >> 4];
1984 const uint8_t nibble =
1985 static_cast<uint8_t>((word >> ((prefix_lane & 15u) * 4u)) & 0xFu);
1986 prefix.min_excess = prefix_min;
1987 const size_t width =
1988 prefix_partial_width != 0 && prefix_lane == prefix_last_nibble
1989 ? prefix_partial_width
1990 : 4;
1991 prefix.offset =
1992 static_cast<size_t>(prefix_lane) * 4u + local_offset(nibble, width);
1993 }
1994
1995 const int suffix_min = reduce_min(suffix_candidates);
1996 if (suffix_min < suffix.min_excess) {
1997 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1998 _mm256_cmpeq_epi8(suffix_candidates,
1999 _mm256_set1_epi8(static_cast<int8_t>(suffix_min)))));
2000 const uint32_t suffix_lane = std::countr_zero(mask);
2001 const uint64_t word = suffix_s[suffix_lane >> 4];
2002 const uint8_t nibble =
2003 static_cast<uint8_t>((word >> ((suffix_lane & 15u) * 4u)) & 0xFu);
2004 suffix.min_excess = suffix_min;
2005 const size_t width = suffix_lane == 31 ? 3 : 4;
2006 suffix.offset =
2007 static_cast<size_t>(suffix_lane) * 4u + local_offset(nibble, width);
2008 }
2009
2010 return {suffix, prefix};
2011#else
2012 return {excess_min_128(suffix_s, suffix_left, 127),
2013 excess_min_128(prefix_s, 0, prefix_right)};
2014#endif
2015}
2016
2032static inline size_t forward_search_128(const uint64_t* s,
2033 int target_x,
2034 size_t start_offset,
2035 int* block_excess = nullptr) noexcept {
2036 uint64_t out[2];
2037 const int delta = excess_positions_128(s, target_x, out);
2038 if (block_excess != nullptr) {
2039 *block_excess = delta;
2040 }
2041 if (start_offset >= 128) {
2042 return 128;
2043 }
2044
2045 const size_t first_word = start_offset >> 6;
2046 const size_t first_bit = start_offset & 63;
2047 for (size_t word = first_word; word < 2; ++word) {
2048 uint64_t mask = out[word];
2049 if (word == first_word && first_bit != 0) {
2050 mask &= ~first_bits_mask(first_bit);
2051 }
2052 if (mask != 0) {
2053 return word * 64 + std::countr_zero(mask);
2054 }
2055 }
2056 return 128;
2057}
2058
2076static inline size_t backward_search_128(const uint64_t* s,
2077 int target_x,
2078 size_t end_offset,
2079 int* block_excess = nullptr) noexcept {
2080 uint64_t out[2];
2081 const int delta = excess_positions_128(s, target_x, out);
2082 if (block_excess != nullptr) {
2083 *block_excess = delta;
2084 }
2085 if (end_offset == 0) {
2086 return 128;
2087 }
2088
2089 const size_t max_prefix_length = end_offset - 1;
2090 if (max_prefix_length > 0) {
2091 const size_t last_bit_index = max_prefix_length - 1;
2092 size_t word = last_bit_index >> 6;
2093 const size_t bit_in_word = last_bit_index & 63;
2094 uint64_t mask = out[word] & first_bits_mask(bit_in_word + 1);
2095 while (true) {
2096 if (mask != 0) {
2097 return word * 64 + (63 - std::countl_zero(mask)) + 1;
2098 }
2099 if (word == 0) {
2100 break;
2101 }
2102 --word;
2103 mask = out[word];
2104 }
2105 }
2106 return target_x == 0 ? 0 : 128;
2107}
2108
2121static inline void excess_positions_512(const uint64_t* s,
2122 int target_x,
2123 uint64_t* out) noexcept {
2124 if (target_x < -512 || target_x > 512) {
2125 out[0] = out[1] = out[2] = out[3] = 0;
2126 out[4] = out[5] = out[6] = out[7] = 0;
2127 return;
2128 }
2129
2130 for (int k = 0; k < 4; ++k) {
2131 target_x -= excess_positions_128(s + 2 * k, target_x, out + 2 * k);
2132 }
2133}
2134
2148static inline void excess_record_lows_128(const uint64_t* s,
2149 uint64_t* out) noexcept {
2150 out[0] = out[1] = 0;
2151 int cur = 0;
2152 int best = 0;
2153 for (size_t i = 0; i < 128; ++i) {
2154 const uint64_t w = s[i >> 6];
2155 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
2156 cur += bit ? +1 : -1;
2157 if (cur < best) {
2158 best = cur;
2159 out[i >> 6] |= (uint64_t{1} << (i & 63));
2160 }
2161 }
2162}
2163
2172static inline void excess_record_lows_128_byte_lut(const uint64_t* s,
2173 uint64_t* out) noexcept {
2174 out[0] = out[1] = 0;
2175 int cur = 0;
2176 int best = 0;
2177 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
2178 const size_t bit_base = byte_idx * 8;
2179 const uint8_t byte =
2180 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
2181 const int byte_min = excess_byte_min_lut[byte];
2182 if (cur + byte_min < best) {
2183 for (size_t i = 0; i < 8; ++i) {
2184 const int bit = static_cast<int>((byte >> i) & 1u);
2185 cur += bit ? +1 : -1;
2186 if (cur < best) {
2187 best = cur;
2188 const size_t pos = bit_base + i;
2189 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
2190 }
2191 }
2192 } else {
2193 cur += excess_byte_delta_lut[byte];
2194 }
2195 }
2196}
2197
2206static inline void excess_record_lows_128_lut(const uint64_t* s,
2207 uint64_t* out) noexcept {
2208 out[0] = out[1] = 0;
2209 int cur = 0;
2210 int best = 0;
2211 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
2212 const size_t bit_base = byte_idx * 8;
2213 const uint8_t byte =
2214 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
2215 const int gap = cur - best;
2216 const int idx = gap > 7 ? 7 : (gap < 0 ? 0 : gap);
2217 const uint8_t mask =
2218 excess_byte_record_lows_lut[byte][static_cast<size_t>(idx)];
2219 if (mask != 0) {
2220 // Recompute absolute excesses for masked positions to update cur/best.
2221 int local = 0;
2222 int local_best = 0;
2223 uint8_t local_mask = 0;
2224 for (int bit = 0; bit < 8; ++bit) {
2225 local += ((byte >> bit) & 1u) ? 1 : -1;
2226 if (local < local_best) {
2227 local_best = local;
2228 local_mask |= static_cast<uint8_t>(1u << bit);
2229 }
2230 }
2231 // Only output positions whose absolute excess is < best.
2232 uint8_t out_mask = 0;
2233 local = 0;
2234 for (int bit = 0; bit < 8; ++bit) {
2235 local += ((byte >> bit) & 1u) ? 1 : -1;
2236 if (cur + local < best) {
2237 out_mask |= static_cast<uint8_t>(1u << bit);
2238 best = cur + local;
2239 }
2240 }
2241 if (out_mask != 0) {
2242 const uint64_t word = static_cast<uint64_t>(out_mask)
2243 << (bit_base & 63);
2244 out[bit_base >> 6] |= word;
2245 }
2246 }
2247 cur += excess_byte_delta_lut[byte];
2248 }
2249}
2250
2251#ifdef PIXIE_AVX2_SUPPORT
2259static inline void excess_record_lows_128_avx2(const uint64_t* s,
2260 uint64_t* out) noexcept {
2261 out[0] = out[1] = 0;
2262 int cur = 0;
2263 int best = 0;
2264
2265 const __m256i masks = excess_bit_masks_16x_i16();
2266 const __m256i zero = _mm256_setzero_si256();
2267 const __m256i pos = _mm256_set1_epi16(1);
2268 const __m256i neg = _mm256_set1_epi16(-1);
2269
2270 for (size_t chunk = 0; chunk < 8; ++chunk) {
2271 const size_t chunk_bit = chunk * 16;
2272 const uint16_t bits =
2273 chunk < 4
2274 ? static_cast<uint16_t>((s[0] >> (chunk * 16)) & 0xFFFFu)
2275 : static_cast<uint16_t>((s[1] >> ((chunk - 4) * 16)) & 0xFFFFu);
2276
2277 const __m256i vb = _mm256_set1_epi16(static_cast<int16_t>(bits));
2278 const __m256i m = _mm256_and_si256(vb, masks);
2279 const __m256i is_zero = _mm256_cmpeq_epi16(m, zero);
2280 const __m256i steps = _mm256_blendv_epi8(pos, neg, is_zero);
2281 const __m256i pref_rel = excess_prefix_sum_16x_i16(steps);
2282 const __m256i pref_abs = _mm256_add_epi16(
2283 pref_rel, _mm256_set1_epi16(static_cast<int16_t>(cur)));
2284
2285 alignas(32) int16_t vals[16];
2286 _mm256_store_si256(reinterpret_cast<__m256i*>(vals), pref_abs);
2287
2288 for (size_t lane = 0; lane < 16; ++lane) {
2289 const int val = vals[lane];
2290 if (val < best) {
2291 best = val;
2292 const size_t pos_idx = chunk_bit + lane;
2293 out[pos_idx >> 6] |= (uint64_t{1} << (pos_idx & 63));
2294 }
2295 }
2296
2297 cur += 2 * static_cast<int>(std::popcount(bits)) - 16;
2298 }
2299}
2300
2310static inline void excess_record_lows_128_nibble_lut(const uint64_t* s,
2311 uint64_t* out) noexcept {
2312 out[0] = out[1] = 0;
2313 int cur = 0;
2314 int best = 0;
2315
2316 const __m256i vdelta = excess_lut_delta;
2317 const __m256i vmin = excess_lut_min;
2318
2319 __m256i nibbles = excess_nibbles_128_avx2(s);
2320
2321 // Compute inclusive prefix sums of per-nibble total excess changes.
2322 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
2323 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
2324 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
2325 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
2326 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
2327
2328 __m128i ps_lo = _mm256_castsi256_si128(ps);
2329 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
2330 __m128i carry =
2331 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
2332 ps_hi = _mm_add_epi8(ps_hi, carry);
2333 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
2334
2335 // Exclusive prefix: shift in zero at start.
2336 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
2337 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
2338
2339 // Local minima relative to each nibble start.
2340 __m256i local_min = _mm256_shuffle_epi8(vmin, nibbles);
2341
2342 alignas(32) int8_t excl[32];
2343 _mm256_store_si256(reinterpret_cast<__m256i*>(excl), excl_ps);
2344
2345 alignas(32) int8_t nibble_min[32];
2346 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_min), local_min);
2347
2348 alignas(32) int8_t nibble_vals[32];
2349 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_vals), nibbles);
2350
2351 for (int n = 0; n < 32; ++n) {
2352 const int nibble_base = cur + excl[n];
2353 const int nibble_best = nibble_base + nibble_min[n];
2354 if (nibble_best < best) {
2355 // This nibble contains at least one record low — scan bit-by-bit.
2356 const uint8_t nibble = static_cast<uint8_t>(nibble_vals[n]);
2357 int local = 0;
2358 for (int bit = 0; bit < 4; ++bit) {
2359 local += ((nibble >> bit) & 1u) ? 1 : -1;
2360 const int val = nibble_base + local;
2361 if (val < best) {
2362 best = val;
2363 const size_t pos = static_cast<size_t>(n) * 4 + bit;
2364 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
2365 }
2366 }
2367 }
2368 }
2369}
2370#endif
2371
2384static inline void excess_record_lows_512(const uint64_t* s,
2385 uint64_t* out) noexcept {
2386 out[0] = out[1] = out[2] = out[3] = 0;
2387 out[4] = out[5] = out[6] = out[7] = 0;
2388 int best = 0;
2389 int global = 0;
2390 for (int k = 0; k < 4; ++k) {
2391 const uint64_t* block = s + 2 * k;
2392 uint64_t* block_out = out + 2 * k;
2393 for (size_t i = 0; i < 128; ++i) {
2394 const uint64_t w = block[i >> 6];
2395 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
2396 global += bit ? +1 : -1;
2397 if (global < best) {
2398 best = global;
2399 block_out[i >> 6] |= (uint64_t{1} << (i & 63));
2400 }
2401 }
2402 }
2403}
2404
2416static inline void rank_32x8(const uint8_t* x, uint8_t* result) {
2417#ifdef PIXIE_AVX512_SUPPORT
2418 // Step 1: Calculate popcount of each byte
2419 popcount_32x8(x, result);
2420 __m256i prefix_sums = _mm256_loadu_si256((__m256i const*)result);
2421 const __m256i zero = _mm256_setzero_si256();
2422
2423 prefix_sums = _mm256_add_epi8(prefix_sums,
2424 _mm256_alignr_epi8(prefix_sums, zero, 16 - 1));
2425 prefix_sums = _mm256_add_epi8(prefix_sums,
2426 _mm256_alignr_epi8(prefix_sums, zero, 16 - 2));
2427 prefix_sums = _mm256_add_epi8(prefix_sums,
2428 _mm256_alignr_epi8(prefix_sums, zero, 16 - 4));
2429 prefix_sums = _mm256_add_epi8(prefix_sums,
2430 _mm256_alignr_epi8(prefix_sums, zero, 16 - 8));
2431
2432 // At this point we have prefix sums for two halfs, the last step is to
2433 // extract 16-th value and add it to the whole second half
2434 __m128i low_lane = _mm256_extracti128_si256(prefix_sums, 0);
2435 __m128i high_lane = _mm256_extracti128_si256(prefix_sums, 1);
2436 auto last_val_low = _mm_extract_epi8(low_lane, 15);
2437 __m128i add_to_high = _mm_set1_epi8(last_val_low);
2438 high_lane = _mm_add_epi8(high_lane, add_to_high);
2439 prefix_sums = _mm256_set_m128i(high_lane, low_lane);
2440 _mm256_storeu_epi8(result, prefix_sums);
2441#else
2442 // Scalar fallback implementation
2443 result[0] = std::popcount(x[0]);
2444 for (size_t i = 1; i < 32; ++i) {
2445 result[i] = std::popcount(x[i]) + result[i - 1];
2446 }
2447#endif
2448}
Pair of boundary minimum results for adjacent BP query blocks.
Definition bits.h:1210
Minimum prefix excess in a 128-bit bitstring range.
Definition bits.h:1199
Definition bits.h:432