Pixie
Loading...
Searching...
No Matches
bits.h
1#pragma once
2
3#include <immintrin.h>
4
5#include <algorithm>
6#include <array>
7#include <bit>
8#include <cstddef>
9#include <cstdint>
10#include <limits>
11#include <numeric>
12
13#if defined(__AVX512VPOPCNTDQ__) && defined(__AVX512F__) && \
14 defined(__AVX512BW__)
15#define PIXIE_AVX512_SUPPORT
16#endif
17
18#if defined(__BMI2__) && !defined(PIXIE_DISABLE_BMI2)
19#define PIXIE_BMI2_SUPPORT
20#endif
21
22#ifdef __AVX2__
23#define PIXIE_AVX2_SUPPORT
24// Lookup table for 4-bit popcount
25// This table maps each 4-bit value (0-15) to its population count
26// clang-format off
27static inline const __m256i lookup_popcount_4 = _mm256_setr_epi8(
28 0, 1, 1, 2, // 0000, 0001, 0010, 0011
29 1, 2, 2, 3, // 0100, 0101, 0110, 0111
30 1, 2, 2, 3, // 1000, 1001, 1010, 1011
31 2, 3, 3, 4, // 1100, 1101, 1110, 1111
32
33 // Same table repeated for high 128 bits
34 0, 1, 1, 2, // 0000, 0001, 0010, 0011
35 1, 2, 2, 3, // 0100, 0101, 0110, 0111
36 1, 2, 2, 3, // 1000, 1001, 1010, 1011
37 2, 3, 3, 4 // 1100, 1101, 1110, 1111
38);
39
40static inline const __m256i mask_first_half = _mm256_setr_epi8(
41 0xFF, 0xFF, 0xFF, 0xFF,
42 0xFF, 0xFF, 0xFF, 0xFF,
43 0xFF, 0xFF, 0xFF, 0xFF,
44 0xFF, 0xFF, 0xFF, 0xFF,
45 0, 0, 0, 0,
46 0, 0, 0, 0,
47 0, 0, 0, 0,
48 0, 0, 0, 0
49);
50
51// clang-format on
52#endif
53
54static inline constexpr int8_t excess_nibble_min_offset[16] = {
55 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 1, 1, 2, 2, 1, 1};
56
57#if defined(__SSSE3__) && defined(__SSE4_1__)
58#define PIXIE_SSE41_SUPPORT
59// clang-format off
60static inline const __m128i excess_lut_delta_sse = _mm_setr_epi8(
61 -4, -2, -2, 0,
62 -2, 0, 0, 2,
63 -2, 0, 0, 2,
64 0, 2, 2, 4);
65static inline const __m128i excess_lut_pos0_sse = _mm_setr_epi8(
66 -1, 1, -1, 1,
67 -1, 1, -1, 1,
68 -1, 1, -1, 1,
69 -1, 1, -1, 1);
70static inline const __m128i excess_lut_pos1_sse = _mm_setr_epi8(
71 -2, 0, 0, 2,
72 -2, 0, 0, 2,
73 -2, 0, 0, 2,
74 -2, 0, 0, 2);
75static inline const __m128i excess_lut_pos2_sse = _mm_setr_epi8(
76 -3, -1, -1, 1,
77 -1, 1, 1, 3,
78 -3, -1, -1, 1,
79 -1, 1, 1, 3);
80static inline const __m128i excess_lut_min_sse = _mm_setr_epi8(
81 -4, -2, -2, 0,
82 -2, 0, -1, 1,
83 -3, -1, -1, 1,
84 -2, 0, -1, 1);
85static inline const __m128i excess_lut_nibble_index_sse = _mm_setr_epi8(
86 0, 1, 2, 3,
87 4, 5, 6, 7,
88 8, 9, 10, 11,
89 12, 13, 14, 15);
90static inline const __m128i excess_lut_low_nibble_index_sse = _mm_setr_epi8(
91 0, 2, 4, 6,
92 8, 10, 12, 14,
93 16, 18, 20, 22,
94 24, 26, 28, 30);
95static inline const __m128i excess_lut_high_nibble_index_sse = _mm_setr_epi8(
96 1, 3, 5, 7,
97 9, 11, 13, 15,
98 17, 19, 21, 23,
99 25, 27, 29, 31);
100static inline const __m128i excess_lut_nibble_mask_sse = _mm_set1_epi8(0x0F);
101// clang-format on
102#endif
103
104#ifdef PIXIE_SSE41_SUPPORT
105static inline __m128i excess_nibbles_64_sse(const uint64_t* s) noexcept {
106 const __m128i word_vec = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(s));
107 const __m128i lo_nibbles =
108 _mm_and_si128(word_vec, excess_lut_nibble_mask_sse);
109 const __m128i hi_nibbles =
110 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask_sse);
111 return _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
112}
113
114static inline __m128i excess_prefix_sum_16x_i8(__m128i v) noexcept {
115 __m128i x = v;
116 __m128i t = _mm_slli_si128(x, 1);
117 x = _mm_add_epi8(x, t);
118 t = _mm_slli_si128(x, 2);
119 x = _mm_add_epi8(x, t);
120 t = _mm_slli_si128(x, 4);
121 x = _mm_add_epi8(x, t);
122 t = _mm_slli_si128(x, 8);
123 return _mm_add_epi8(x, t);
124}
125
126static inline int excess_horizontal_min_i8(__m128i v) noexcept {
127 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 8));
128 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 4));
129 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 2));
130 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 1));
131 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(v, 0)));
132}
133#endif
134
149static inline uint32_t rmm_btree_match_mask_i16x16(const int16_t* prefix_before,
150 const int16_t* min_excess,
151 const int16_t* max_excess,
152 int16_t target,
153 bool include_zero_boundary) {
154#ifdef PIXIE_AVX2_SUPPORT
155 const __m256i vtarget = _mm256_set1_epi16(target);
156 const __m256i vprefix =
157 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(prefix_before));
158 const __m256i vmin =
159 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(min_excess));
160 const __m256i vmax =
161 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(max_excess));
162
163 const __m256i lower = _mm256_adds_epi16(vprefix, vmin);
164 const __m256i upper = _mm256_adds_epi16(vprefix, vmax);
165 const __m256i ge_lower = _mm256_or_si256(_mm256_cmpgt_epi16(vtarget, lower),
166 _mm256_cmpeq_epi16(vtarget, lower));
167 const __m256i le_upper = _mm256_or_si256(_mm256_cmpgt_epi16(upper, vtarget),
168 _mm256_cmpeq_epi16(upper, vtarget));
169 __m256i matched = _mm256_and_si256(ge_lower, le_upper);
170 if (include_zero_boundary) {
171 matched = _mm256_or_si256(matched, _mm256_cmpeq_epi16(vtarget, vprefix));
172 }
173
174 const uint32_t byte_mask =
175 static_cast<uint32_t>(_mm256_movemask_epi8(matched));
176 uint32_t result = 0;
177 for (size_t lane = 0; lane < 16; ++lane) {
178 const uint32_t lane_mask = 0x3u << (lane * 2);
179 if ((byte_mask & lane_mask) == lane_mask) {
180 result |= uint32_t{1} << lane;
181 }
182 }
183 return result;
184#else
185 uint32_t result = 0;
186 for (size_t lane = 0; lane < 16; ++lane) {
187 const int lower = prefix_before[lane] + min_excess[lane];
188 const int upper = prefix_before[lane] + max_excess[lane];
189 const bool found = (lower <= target && target <= upper) ||
190 (include_zero_boundary && target == prefix_before[lane]);
191 if (found) {
192 result |= uint32_t{1} << lane;
193 }
194 }
195 return result;
196#endif
197}
198
213static inline uint32_t rmm_btree_match_mask_i64x4(const int64_t* prefix_before,
214 const int64_t* min_excess,
215 const int64_t* max_excess,
216 int64_t target,
217 bool include_zero_boundary) {
218#ifdef PIXIE_AVX2_SUPPORT
219 const __m256i vtarget = _mm256_set1_epi64x(target);
220 const __m256i vprefix =
221 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(prefix_before));
222 const __m256i vmin =
223 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(min_excess));
224 const __m256i vmax =
225 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(max_excess));
226
227 const __m256i relative = _mm256_sub_epi64(vtarget, vprefix);
228 const __m256i ge_min = _mm256_or_si256(_mm256_cmpgt_epi64(relative, vmin),
229 _mm256_cmpeq_epi64(relative, vmin));
230 const __m256i le_max = _mm256_or_si256(_mm256_cmpgt_epi64(vmax, relative),
231 _mm256_cmpeq_epi64(vmax, relative));
232 __m256i matched = _mm256_and_si256(ge_min, le_max);
233 if (include_zero_boundary) {
234 matched = _mm256_or_si256(matched, _mm256_cmpeq_epi64(vtarget, vprefix));
235 }
236
237 const uint32_t byte_mask =
238 static_cast<uint32_t>(_mm256_movemask_epi8(matched));
239 uint32_t result = 0;
240 for (size_t lane = 0; lane < 4; ++lane) {
241 const uint32_t lane_mask = 0xffu << (lane * 8);
242 if ((byte_mask & lane_mask) == lane_mask) {
243 result |= uint32_t{1} << lane;
244 }
245 }
246 return result;
247#else
248 uint32_t result = 0;
249 for (size_t lane = 0; lane < 4; ++lane) {
250 const int64_t relative = target - prefix_before[lane];
251 const bool found =
252 (min_excess[lane] <= relative && relative <= max_excess[lane]) ||
253 (include_zero_boundary && relative == 0);
254 if (found) {
255 result |= uint32_t{1} << lane;
256 }
257 }
258 return result;
259#endif
260}
261
269static inline uint64_t first_bits_mask(size_t num) {
270 return num >= 64 ? UINT64_MAX : ((1llu << num) - 1);
271}
272
293static inline uint64_t rank_512(const uint64_t* x, uint64_t count) {
294#ifdef PIXIE_AVX512_SUPPORT
295
296 __m512i a = _mm512_maskz_set1_epi64((1ull << ((count >> 6))) - 1,
297 std::numeric_limits<uint64_t>::max());
298 __m512i b = _mm512_maskz_set1_epi64((1ull << ((count >> 6) + 1)) - 1,
299 std::numeric_limits<uint64_t>::max());
300 __m512i mask = _mm512_shldv_epi64(a, b, _mm512_set1_epi64(count % 64));
301
302 __m512i res = _mm512_loadu_epi64(x);
303 res = _mm512_and_epi64(res, mask);
304 __m512i cnt = _mm512_popcnt_epi64(res);
305 return _mm512_reduce_add_epi64(cnt);
306
307#else
308
309 size_t last_uint = count < 512 ? count >> 6 : 8;
310
311 uint64_t pop_val = 0;
312
313 for (size_t i = 0; i < last_uint; i++) {
314 pop_val += std::popcount(x[i]);
315 }
316
317 pop_val += count < 512
318 ? std::popcount(x[last_uint] & first_bits_mask(count & 63))
319 : 0;
320 return pop_val;
321
322#endif
323}
324
325struct PixieSelectByteLut {
326 uint8_t popcounts[256];
327 uint8_t select[256][8];
328
329 constexpr PixieSelectByteLut() : popcounts{}, select{} {
330 for (int byte = 0; byte < 256; ++byte) {
331 for (int rank = 0; rank < 8; ++rank) {
332 select[byte][rank] = 8;
333 }
334
335 int count = 0;
336 for (int bit = 0; bit < 8; ++bit) {
337 if (((byte >> bit) & 1) != 0) {
338 select[byte][count++] = static_cast<uint8_t>(bit);
339 }
340 }
341 popcounts[byte] = static_cast<uint8_t>(count);
342 }
343 }
344};
345
346static inline constexpr PixieSelectByteLut pixie_select_byte_lut;
347
348static inline uint64_t select_64_no_bmi2(uint64_t x, uint64_t rank) {
349 uint64_t offset = 0;
350
351 uint64_t count = std::popcount(static_cast<uint32_t>(x));
352 if (rank >= count) {
353 rank -= count;
354 x >>= 32;
355 offset += 32;
356 }
357
358 count = std::popcount(static_cast<uint16_t>(x));
359 if (rank >= count) {
360 rank -= count;
361 x >>= 16;
362 offset += 16;
363 }
364
365 const auto low_byte = static_cast<uint8_t>(x);
366 count = pixie_select_byte_lut.popcounts[low_byte];
367 if (rank >= count) {
368 rank -= count;
369 x >>= 8;
370 offset += 8;
371 }
372
373 return offset + pixie_select_byte_lut.select[static_cast<uint8_t>(x)][rank];
374}
375
385static inline uint64_t select_64(uint64_t x, uint64_t rank) {
386#ifdef PIXIE_BMI2_SUPPORT
387 return std::countr_zero(_pdep_u64(1ull << rank, x));
388#else
389 return select_64_no_bmi2(x, rank);
390#endif
391}
392
393template <bool Invert>
394static inline uint64_t select_512_word_count(uint64_t word) {
395 if constexpr (Invert) {
396 return std::popcount(~word);
397 } else {
398 return std::popcount(word);
399 }
400}
401
402template <bool Invert>
403static inline uint64_t select_512_selected_word(uint64_t word) {
404 if constexpr (Invert) {
405 return ~word;
406 } else {
407 return word;
408 }
409}
410
411template <bool Invert>
412static inline uint64_t select_512_scalar_impl(const uint64_t* x,
413 uint64_t rank) {
414 size_t word = 0;
415 uint64_t count;
416 if constexpr (Invert) {
417 count = std::popcount(~x[0]);
418 } else {
419 count = std::popcount(x[0]);
420 }
421 while (word < 7 && count <= rank) {
422 rank -= count;
423 ++word;
424 if constexpr (Invert) {
425 count = std::popcount(~x[word]);
426 } else {
427 count = std::popcount(x[word]);
428 }
429 }
430
431 if constexpr (Invert) {
432 return word * 64 + select_64(~x[word], rank);
433 } else {
434 return word * 64 + select_64(x[word], rank);
435 }
436}
437
438#ifdef PIXIE_AVX2_SUPPORT
439template <bool Invert>
440static inline void select_512_avx2_counts(const uint64_t* x, uint64_t* counts) {
441 const __m256i low_mask = _mm256_set1_epi8(0x0F);
442 const __m256i zero = _mm256_setzero_si256();
443 const __m256i sixty_four = _mm256_set1_epi64x(64);
444
445 for (int half = 0; half < 2; ++half) {
446 const __m256i words =
447 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 4 * half));
448
449 const __m256i low_nibbles = _mm256_and_si256(words, low_mask);
450 const __m256i high_nibbles =
451 _mm256_and_si256(_mm256_srli_epi16(words, 4), low_mask);
452 const __m256i byte_counts =
453 _mm256_add_epi8(_mm256_shuffle_epi8(lookup_popcount_4, low_nibbles),
454 _mm256_shuffle_epi8(lookup_popcount_4, high_nibbles));
455 __m256i word_counts = _mm256_sad_epu8(byte_counts, zero);
456 if constexpr (Invert) {
457 word_counts = _mm256_sub_epi64(sixty_four, word_counts);
458 }
459 _mm256_store_si256(reinterpret_cast<__m256i*>(counts + 4 * half),
460 word_counts);
461 }
462}
463
464template <bool Invert>
465static inline uint64_t select_512_avx2_impl(const uint64_t* x, uint64_t rank) {
466 alignas(32) uint64_t counts[8];
467 select_512_avx2_counts<Invert>(x, counts);
468
469 for (size_t i = 0; i < 8; ++i) {
470 if (rank < counts[i]) {
471 return i * 64 + select_64(select_512_selected_word<Invert>(x[i]), rank);
472 }
473 rank -= counts[i];
474 }
475 return 512;
476}
477#endif
478
479#ifdef PIXIE_AVX512_SUPPORT
480static inline __m512i select_512_avx512_prefix_sum_u64(__m512i prefix) {
481 const __m512i idx_shift1 = _mm512_set_epi64(6, 5, 4, 3, 2, 1, 0, 0);
482 const __m512i idx_shift2 = _mm512_set_epi64(5, 4, 3, 2, 1, 0, 0, 0);
483 const __m512i idx_shift4 = _mm512_set_epi64(3, 2, 1, 0, 0, 0, 0, 0);
484
485 __m512i tmp = _mm512_maskz_permutexvar_epi64(0xFE, idx_shift1, prefix);
486 prefix = _mm512_add_epi64(prefix, tmp);
487 tmp = _mm512_maskz_permutexvar_epi64(0xFC, idx_shift2, prefix);
488 prefix = _mm512_add_epi64(prefix, tmp);
489 tmp = _mm512_maskz_permutexvar_epi64(0xF0, idx_shift4, prefix);
490 return _mm512_add_epi64(prefix, tmp);
491}
492
493static inline uint64_t select_512_avx512_previous_prefix(__m512i prefix,
494 uint32_t lane) {
495 if (lane == 0) {
496 return 0;
497 }
498 const __m512i idx_previous =
499 _mm512_set1_epi64(static_cast<int64_t>(lane - 1));
500 const __m512i previous_vec = _mm512_permutexvar_epi64(idx_previous, prefix);
501 return static_cast<uint64_t>(
502 _mm_cvtsi128_si64(_mm512_castsi512_si128(previous_vec)));
503}
504
505template <bool Invert>
506static inline uint64_t select_512_avx512_impl(const uint64_t* x,
507 uint64_t rank) {
508 const __m512i words = _mm512_loadu_epi64(x);
509 __m512i prefix = _mm512_popcnt_epi64(words);
510 if constexpr (Invert) {
511 prefix = _mm512_sub_epi64(_mm512_set1_epi64(64), prefix);
512 }
513 prefix = select_512_avx512_prefix_sum_u64(prefix);
514
515 const __mmask8 mask = _mm512_cmpgt_epu64_mask(
516 prefix, _mm512_set1_epi64(static_cast<int64_t>(rank)));
517 const uint32_t lane = _tzcnt_u32(static_cast<uint32_t>(mask));
518 const uint64_t previous = select_512_avx512_previous_prefix(prefix, lane);
519 return lane * 64 +
520 select_64(select_512_selected_word<Invert>(x[lane]), rank - previous);
521}
522#endif
523
536static inline uint64_t select_512(const uint64_t* x, uint64_t rank) {
537#ifdef PIXIE_AVX512_SUPPORT
538 return select_512_avx512_impl<false>(x, rank);
539#else
540 // The AVX2 implementation is kept for explicit benchmarking, but production
541 // intentionally falls back to scalar here: on tested machines, the scalar
542 // word scan is faster than AVX2 setup for this 8-word block.
543 return select_512_scalar_impl<false>(x, rank);
544#endif
545}
546
557static inline uint64_t select0_512(const uint64_t* x, uint64_t rank0) {
558#ifdef PIXIE_AVX512_SUPPORT
559 return select_512_avx512_impl<true>(x, rank0);
560#else
561 // See select_512: AVX2 is deliberately not used in the default path because
562 // the scalar scan benchmarked faster for 512-bit select blocks.
563 return select_512_scalar_impl<true>(x, rank0);
564#endif
565}
566
571static inline uint16_t lower_bound_4x64(const uint64_t* x, uint64_t y) {
572#ifdef PIXIE_AVX512_SUPPORT
573
574 auto y_4 = _mm256_set1_epi64x(y);
575 auto reg_256 = _mm256_loadu_epi64(x);
576 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
577
578 return _tzcnt_u16(cmp);
579
580#else
581#ifdef PIXIE_AVX2_SUPPORT
582
583 auto y_4 = _mm256_set1_epi64x(y);
584 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
585
586 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
587 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
588 __m256i y_offset = _mm256_xor_si256(y_4, offset);
589 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
590 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
591
592 return _tzcnt_u32(mask) >> 3;
593
594#else
595
596 for (uint16_t i = 0; i < 4; ++i) {
597 if (x[i] >= y) {
598 return i;
599 }
600 }
601 return 4;
602
603#endif
604#endif
605}
606
620static inline uint16_t lower_bound_delta_4x64(const uint64_t* x,
621 uint64_t y,
622 const uint64_t* delta_array,
623 uint64_t delta_scalar) {
624#ifdef PIXIE_AVX512_SUPPORT
625
626 const __m256i dlt_256 = _mm256_loadu_epi64(delta_array);
627 auto x_256 = _mm256_loadu_epi64(x);
628 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
629 auto y_4 = _mm256_set1_epi64x(y);
630
631 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
632 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
633 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
634
635 return _tzcnt_u16(cmp);
636
637#else
638#ifdef PIXIE_AVX2_SUPPORT
639
640 const __m256i dlt_256 =
641 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
642 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
643 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
644 auto y_4 = _mm256_set1_epi64x(y);
645
646 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
647 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
648
649 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
650 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
651 __m256i y_offset = _mm256_xor_si256(y_4, offset);
652 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
653 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
654
655 return _tzcnt_u32(mask) >> 3;
656
657#else
658
659 for (uint16_t i = 0; i < 4; ++i) {
660 if (delta_array[i] + delta_scalar - x[i] >= y) {
661 return i;
662 }
663 }
664 return 4;
665
666#endif
667#endif
668}
669
674static inline uint16_t lower_bound_8x64(const uint64_t* x, uint64_t y) {
675#ifdef PIXIE_AVX512_SUPPORT
676
677 auto y_8 = _mm512_set1_epi64(y);
678 auto reg_512 = _mm512_loadu_epi64(x);
679 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
680
681 return _tzcnt_u16(cmp);
682
683#else
684#ifdef PIXIE_AVX2_SUPPORT
685
686 uint16_t len = lower_bound_4x64(x, y);
687
688 if (len < 4) {
689 return len;
690 }
691
692 return len + lower_bound_4x64(x + 4, y);
693
694#else
695
696 for (uint16_t i = 0; i < 8; ++i) {
697 if (x[i] >= y) {
698 return i;
699 }
700 }
701 return 8;
702
703#endif
704#endif
705}
706
720static inline uint16_t lower_bound_delta_8x64(const uint64_t* x,
721 uint64_t y,
722 const uint64_t* delta_array,
723 uint64_t delta_scalar) {
724#ifdef PIXIE_AVX512_SUPPORT
725
726 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
727 auto x_512 = _mm512_loadu_epi64(x);
728 auto dlt_8 = _mm512_set1_epi64(delta_scalar);
729 auto y_8 = _mm512_set1_epi64(y);
730
731 auto tmp = _mm512_add_epi64(dlt_8, dlt_512);
732 auto reg_512 = _mm512_sub_epi64(tmp, x_512);
733 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
734
735 return _tzcnt_u16(cmp);
736
737#else
738#ifdef PIXIE_AVX2_SUPPORT
739
740 uint16_t len = lower_bound_delta_4x64(x, y, delta_array, delta_scalar);
741
742 if (len < 4) {
743 return len;
744 }
745
746 return len + lower_bound_delta_4x64(x + 4, y, delta_array + 4, delta_scalar);
747
748#else
749
750 for (uint16_t i = 0; i < 8; ++i) {
751 if (delta_array[i] + delta_scalar - x[i] >= y) {
752 return i;
753 }
754 }
755 return 8;
756
757#endif
758#endif
759}
760
765static inline uint16_t lower_bound_32x16(const uint16_t* x, uint16_t y) {
766#ifdef PIXIE_AVX512_SUPPORT
767
768 auto y_32 = _mm512_set1_epi16(y);
769 auto reg_512 = _mm512_loadu_epi16(x);
770 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
771 return std::popcount(cmp);
772
773#else
774#ifdef PIXIE_AVX2_SUPPORT
775
776 auto y_16 = _mm256_set1_epi16(y);
777 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
778
779 const __m256i offset = _mm256_set1_epi16(0x8000);
780 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
781 __m256i y_offset = _mm256_xor_si256(y_16, offset);
782 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
783
784 uint16_t count = std::popcount(mask) >> 1;
785
786 reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
787
788 x_offset = _mm256_xor_si256(reg_256, offset);
789 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
790
791 return count + (std::popcount(mask) >> 1);
792
793#else
794
795 uint16_t cnt = 0;
796 for (uint16_t i = 0; i < 32; ++i) {
797 if (x[i] < y) {
798 cnt++;
799 }
800 }
801 return cnt;
802
803#endif
804#endif
805}
806
820static inline uint16_t lower_bound_delta_32x16(const uint16_t* x,
821 uint16_t y,
822 const uint16_t* delta_array,
823 uint16_t delta_scalar) {
824#ifdef PIXIE_AVX512_SUPPORT
825
826 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
827 auto x_512 = _mm512_loadu_epi64(x);
828 auto dlt_32 = _mm512_set1_epi16(delta_scalar);
829 auto y_32 = _mm512_set1_epi16(y);
830
831 auto tmp = _mm512_add_epi16(dlt_32, dlt_512);
832 auto reg_512 = _mm512_sub_epi16(tmp, x_512);
833 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
834 return std::popcount(cmp);
835
836#else
837#ifdef PIXIE_AVX2_SUPPORT
838
839 auto dlt_256 =
840 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
841 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
842 auto dlt_16 = _mm256_set1_epi16(delta_scalar);
843 auto y_16 = _mm256_set1_epi16(y);
844
845 auto tmp = _mm256_add_epi16(dlt_16, dlt_256);
846 auto reg_256 = _mm256_sub_epi16(tmp, x_256);
847
848 const __m256i offset = _mm256_set1_epi16(0x8000);
849 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
850 __m256i y_offset = _mm256_xor_si256(y_16, offset);
851 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
852
853 uint16_t count = std::popcount(mask) >> 1;
854
855 dlt_256 =
856 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array + 16));
857 x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
858
859 tmp = _mm256_add_epi16(dlt_16, dlt_256);
860 reg_256 = _mm256_sub_epi16(tmp, x_256);
861
862 x_offset = _mm256_xor_si256(reg_256, offset);
863 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
864
865 return count + (std::popcount(mask) >> 1);
866
867#else
868
869 uint16_t cnt = 0;
870 for (uint16_t i = 0; i < 32; ++i) {
871 if (delta_array[i] + delta_scalar - x[i] < y) {
872 cnt++;
873 }
874 }
875 return cnt;
876
877#endif
878#endif
879}
880
892static inline void popcount_64x4(const uint8_t* x, uint8_t* result) {
893#ifdef PIXIE_AVX512_SUPPORT
894 __m256i data = _mm256_loadu_si256((__m256i const*)x);
895
896 // Masks for extracting the lower and upper nibbles
897 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
898
899 // Count bits in the lower half
900 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
901 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
902
903 // Count bits in the upper half
904 __m256i high_bits = _mm256_srli_epi16(data, 4);
905 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
906 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
907
908 // Pack the results into a single output vector
909 __m256i result_vec =
910 _mm256_or_si256(low_count, _mm256_slli_epi16(high_count, 4));
911 _mm256_storeu_epi8(result, result_vec);
912#else
913 // Fallback implementation for non-AVX2 platforms
914 for (size_t i = 0; i < 32; i++) {
915 // Count bits in the lower half
916 uint8_t a = x[i] & 0x0F;
917 uint8_t low_count = std::popcount(a);
918 // Count bits in the upper half
919 a = (x[i] >> 4) & 0x0F;
920 uint8_t high_count = std::popcount(a);
921
922 // Pack the counts into the output byte
923 result[i] = low_count | (high_count << 4);
924 }
925#endif
926}
927
939static inline void popcount_32x8(const uint8_t* x, uint8_t* result) {
940#ifdef PIXIE_AVX512_SUPPORT
941 // Load 64 4-bit integers (256 bits total)
942 __m256i data = _mm256_loadu_si256((__m256i const*)x);
943 auto popcount_8 = _mm256_popcnt_epi8(data);
944 _mm256_storeu_si256((__m256i*)result, popcount_8);
945#else
946#ifdef PIXIE_AVX2_SUPPORT
947 // Load 64 4-bit integers (256 bits total)
948 __m256i data = _mm256_loadu_si256((__m256i const*)x);
949
950 // Masks for extracting the lower and upper nibbles
951 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
952
953 // Count bits in lower half
954 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
955 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
956
957 // Count bits upper half
958 __m256i high_bits = _mm256_srli_epi16(data, 4);
959 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
960 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
961
962 __m256i result_vec = _mm256_add_epi8(low_count, high_count);
963 _mm256_storeu_si256((__m256i*)result, result_vec);
964#else
965 // Fallback implementation for non-AVX2 platforms
966 for (size_t i = 0; i < 32; i++) {
967 result[i] = std::popcount(x[i]);
968 }
969#endif
970#endif
971}
972
973#ifdef PIXIE_AVX2_SUPPORT
974// clang-format off
975// LUT for total excess change across a 4-bit nibble
976static inline const __m256i excess_lut_delta = _mm256_setr_epi8(
977 -4, -2, -2, 0,
978 -2, 0, 0, 2,
979 -2, 0, 0, 2,
980 0, 2, 2, 4,
981 -4, -2, -2, 0,
982 -2, 0, 0, 2,
983 -2, 0, 0, 2,
984 0, 2, 2, 4);
985
986// LUTs for target relative excess positions
987static inline const __m256i excess_lut_pos0 = _mm256_setr_epi8(
988 -1, 1, -1, 1,
989 -1, 1, -1, 1,
990 -1, 1, -1, 1,
991 -1, 1, -1, 1,
992 -1, 1, -1, 1,
993 -1, 1, -1, 1,
994 -1, 1, -1, 1,
995 -1, 1, -1, 1);
996
997static inline const __m256i excess_lut_pos1 = _mm256_setr_epi8(
998 -2, 0, 0, 2,
999 -2, 0, 0, 2,
1000 -2, 0, 0, 2,
1001 -2, 0, 0, 2,
1002 -2, 0, 0, 2,
1003 -2, 0, 0, 2,
1004 -2, 0, 0, 2,
1005 -2, 0, 0, 2);
1006
1007static inline const __m256i excess_lut_pos2 = _mm256_setr_epi8(
1008 -3, -1, -1, 1,
1009 -1, 1, 1, 3,
1010 -3, -1, -1, 1,
1011 -1, 1, 1, 3,
1012 -3, -1, -1, 1,
1013 -1, 1, 1, 3,
1014 -3, -1, -1, 1,
1015 -1, 1, 1, 3);
1016static inline const __m256i excess_lut_min = _mm256_setr_epi8(
1017 -4, -2, -2, 0,
1018 -2, 0, -1, 1,
1019 -3, -1, -1, 1,
1020 -2, 0, -1, 1,
1021 -4, -2, -2, 0,
1022 -2, 0, -1, 1,
1023 -3, -1, -1, 1,
1024 -2, 0, -1, 1);
1025static inline constexpr int8_t excess_lut_min_offset[16] = {
1026 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 1, 1, 2, 2, 1, 1};
1027static inline const __m256i excess_lut_pack_multiplier =
1028 _mm256_set1_epi16(0x1001);
1029static inline const __m256i excess_lut_bit0 = _mm256_set1_epi8(1);
1030static inline const __m256i excess_lut_bit1 = _mm256_set1_epi8(2);
1031static inline const __m256i excess_lut_bit2 = _mm256_set1_epi8(4);
1032static inline const __m256i excess_lut_bit3 = _mm256_set1_epi8(8);
1033static inline const __m256i excess_lut_nibble_index = _mm256_setr_epi8(
1034 0, 1, 2, 3,
1035 4, 5, 6, 7,
1036 8, 9, 10, 11,
1037 12, 13, 14, 15,
1038 16, 17, 18, 19,
1039 20, 21, 22, 23,
1040 24, 25, 26, 27,
1041 28, 29, 30, 31);
1042static inline const __m128i excess_lut_nibble_mask = _mm_set1_epi8(0x0F);
1043// clang-format on
1044
1045static inline __m256i excess_nibbles_128_avx2(const uint64_t* s) noexcept {
1046 __m128i word_vec = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
1047 __m128i lo_nibbles = _mm_and_si128(word_vec, excess_lut_nibble_mask);
1048 __m128i hi_nibbles =
1049 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask);
1050
1051 __m128i unpack_lo = _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
1052 __m128i unpack_hi = _mm_unpackhi_epi8(lo_nibbles, hi_nibbles);
1053
1054 return _mm256_inserti128_si256(_mm256_castsi128_si256(unpack_lo), unpack_hi,
1055 1);
1056}
1057
1058static inline __m256i excess_bit_masks_16x_i16() noexcept {
1059 return _mm256_setr_epi16(0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
1060 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
1061 0x1000, 0x2000, 0x4000,
1062 static_cast<int16_t>(0x8000));
1063}
1064
1065static inline __m256i excess_prefix_sum_16x_i16(__m256i v) noexcept {
1066 __m256i x = v;
1067 __m256i t = _mm256_slli_si256(x, 2);
1068 x = _mm256_add_epi16(x, t);
1069 t = _mm256_slli_si256(x, 4);
1070 x = _mm256_add_epi16(x, t);
1071 t = _mm256_slli_si256(x, 8);
1072 x = _mm256_add_epi16(x, t);
1073
1074 __m128i lo = _mm256_extracti128_si256(x, 0);
1075 __m128i hi = _mm256_extracti128_si256(x, 1);
1076 const int16_t carry = static_cast<int16_t>(_mm_extract_epi16(lo, 7));
1077 hi = _mm_add_epi16(hi, _mm_set1_epi16(carry));
1078
1079 __m256i out = _mm256_castsi128_si256(lo);
1080 return _mm256_inserti128_si256(out, hi, 1);
1081}
1082#endif
1083
1093 int min_excess = 0;
1094 size_t offset = 128;
1095};
1096
1104 ExcessResult suffix;
1105 ExcessResult prefix;
1106};
1107
1108constexpr int8_t excess_byte_delta_value(uint8_t x) {
1109 return static_cast<int8_t>(2 * std::popcount(x) - 8);
1110}
1111
1112constexpr int8_t excess_byte_min_prefix_value(uint8_t x) {
1113 int cur = 0;
1114 int best = 0;
1115 for (int bit = 0; bit < 8; ++bit) {
1116 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1117 if (bit == 0 || cur < best) {
1118 best = cur;
1119 }
1120 }
1121 return static_cast<int8_t>(best);
1122}
1123
1124constexpr int8_t excess_byte_min_prefix_offset_value(uint8_t x) {
1125 int cur = 0;
1126 int best = 0;
1127 int best_offset = 1;
1128 for (int bit = 0; bit < 8; ++bit) {
1129 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1130 if (bit == 0 || cur < best) {
1131 best = cur;
1132 best_offset = bit + 1;
1133 }
1134 }
1135 return static_cast<int8_t>(best_offset);
1136}
1137
1138constexpr int8_t excess_nibble_min_prefix_offset_value(uint8_t x, int bits) {
1139 int cur = 0;
1140 int best = 0;
1141 int best_offset = 1;
1142 for (int bit = 0; bit < bits; ++bit) {
1143 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1144 if (bit == 0 || cur < best) {
1145 best = cur;
1146 best_offset = bit + 1;
1147 }
1148 }
1149 return static_cast<int8_t>(best_offset);
1150}
1151
1152template <typename Fn>
1153constexpr std::array<int8_t, 256> excess_make_byte_lut(Fn fn) {
1154 std::array<int8_t, 256> out{};
1155 for (size_t i = 0; i < out.size(); ++i) {
1156 out[i] = fn(static_cast<uint8_t>(i));
1157 }
1158 return out;
1159}
1160
1161static inline constexpr std::array<int8_t, 256> excess_byte_delta_lut =
1162 excess_make_byte_lut([](uint8_t x) { return excess_byte_delta_value(x); });
1163static inline constexpr std::array<int8_t, 256> excess_byte_min_lut =
1164 excess_make_byte_lut(
1165 [](uint8_t x) { return excess_byte_min_prefix_value(x); });
1166static inline constexpr std::array<int8_t, 256> excess_byte_min_offset_lut =
1167 excess_make_byte_lut(
1168 [](uint8_t x) { return excess_byte_min_prefix_offset_value(x); });
1169static inline constexpr std::array<std::array<int8_t, 16>, 4>
1170 excess_partial_nibble_min_offset_lut = [] {
1171 std::array<std::array<int8_t, 16>, 4> out{};
1172 for (size_t width = 1; width < out.size(); ++width) {
1173 for (size_t nibble = 0; nibble < out[width].size(); ++nibble) {
1174 out[width][nibble] = excess_nibble_min_prefix_offset_value(
1175 static_cast<uint8_t>(nibble), static_cast<int>(width));
1176 }
1177 }
1178 return out;
1179 }();
1180
1188constexpr uint8_t excess_byte_record_lows_mask(uint8_t byte, int threshold) {
1189 int cur = 0;
1190 uint8_t mask = 0;
1191 for (int bit = 0; bit < 8; ++bit) {
1192 cur += ((byte >> bit) & 1u) ? 1 : -1;
1193 if (cur < threshold) {
1194 mask |= static_cast<uint8_t>(1u << bit);
1195 }
1196 }
1197 return mask;
1198}
1199
1208static inline constexpr std::array<std::array<uint8_t, 8>, 256>
1209 excess_byte_record_lows_lut = [] {
1210 std::array<std::array<uint8_t, 8>, 256> out{};
1211 for (size_t byte = 0; byte < 256; ++byte) {
1212 for (int g = 0; g < 8; ++g) {
1213 out[byte][g] =
1214 excess_byte_record_lows_mask(static_cast<uint8_t>(byte), -g);
1215 }
1216 }
1217 return out;
1218 }();
1219
1233static inline int excess_positions_128(const uint64_t* s,
1234 int target_x,
1235 uint64_t* out) noexcept {
1236 out[0] = out[1] = 0;
1237 const int block_delta = 2 * (std::popcount(s[0]) + std::popcount(s[1])) - 128;
1238
1239 if (target_x < -128 || target_x > 128) {
1240 return block_delta;
1241 }
1242
1243#ifdef PIXIE_AVX2_SUPPORT
1244 const __m256i vdelta = excess_lut_delta;
1245 const __m256i vpos0 = excess_lut_pos0;
1246 const __m256i vpos1 = excess_lut_pos1;
1247 const __m256i vpos2 = excess_lut_pos2;
1248 const __m256i vmult = excess_lut_pack_multiplier;
1249 const __m256i vbit0 = excess_lut_bit0;
1250 const __m256i vbit1 = excess_lut_bit1;
1251 const __m256i vbit2 = excess_lut_bit2;
1252 const __m256i vbit3 = excess_lut_bit3;
1253
1254 const int d = 2 * target_x - block_delta;
1255 if (d < -128 || d > 128) {
1256 return block_delta;
1257 }
1258
1259 __m256i nibbles = excess_nibbles_128_avx2(s);
1260
1261 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
1262 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1263 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1264 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1265 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1266
1267 __m128i ps_lo = _mm256_castsi256_si128(ps);
1268 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1269 __m128i carry = _mm_set1_epi8((int8_t)_mm_extract_epi8(ps_lo, 15));
1270 ps_hi = _mm_add_epi8(ps_hi, carry);
1271 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1272
1273 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1274 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1275
1276 __m256i vtgt = _mm256_set1_epi8((int8_t)target_x);
1277 __m256i t = _mm256_sub_epi8(vtgt, excl_ps);
1278
1279 __m256i cmp0 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos0, nibbles), t);
1280 __m256i cmp1 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos1, nibbles), t);
1281 __m256i cmp2 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos2, nibbles), t);
1282 __m256i cmp3 = _mm256_cmpeq_epi8(ps, vtgt);
1283
1284 __m256i bit0 = _mm256_and_si256(cmp0, vbit0);
1285 __m256i bit1 = _mm256_and_si256(cmp1, vbit1);
1286 __m256i bit2 = _mm256_and_si256(cmp2, vbit2);
1287 __m256i bit3 = _mm256_and_si256(cmp3, vbit3);
1288
1289 __m256i total_match =
1290 _mm256_or_si256(_mm256_or_si256(bit0, bit1), _mm256_or_si256(bit2, bit3));
1291
1292 __m256i res = _mm256_maddubs_epi16(total_match, vmult);
1293 __m128i res_lo = _mm256_castsi256_si128(res);
1294 __m128i res_hi = _mm256_extracti128_si256(res, 1);
1295 __m128i packed = _mm_packus_epi16(res_lo, res_hi);
1296
1297 _mm_storeu_si128((__m128i*)out, packed);
1298#else
1299 int cur = 0;
1300 for (size_t i = 0; i < 128; ++i) {
1301 const uint64_t w = s[i >> 6];
1302 const int bit = int((w >> (i & 63)) & 1ull);
1303 cur += bit ? +1 : -1;
1304 if (cur == target_x) {
1305 out[i >> 6] |= (uint64_t{1} << (i & 63));
1306 }
1307 }
1308#endif
1309 return block_delta;
1310}
1311
1321static inline int prefix_excess_128(const uint64_t* s,
1322 size_t end_offset) noexcept {
1323 end_offset = end_offset > 128 ? 128 : end_offset;
1324 if (end_offset == 0) {
1325 return 0;
1326 }
1327 if (end_offset <= 64) {
1328 const int ones = static_cast<int>(std::popcount(
1329 s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1330 return 2 * ones - static_cast<int>(end_offset);
1331 }
1332 const int ones = static_cast<int>(
1333 std::popcount(s[0]) +
1334 std::popcount(s[1] &
1335 first_bits_mask(static_cast<uint32_t>(end_offset - 64))));
1336 return 2 * ones - static_cast<int>(end_offset);
1337}
1338
1348static inline int prefix_excess_64(const uint64_t* s,
1349 size_t end_offset) noexcept {
1350 end_offset = end_offset > 64 ? 64 : end_offset;
1351 if (end_offset == 0) {
1352 return 0;
1353 }
1354 const int ones = static_cast<int>(
1355 std::popcount(s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1356 return 2 * ones - static_cast<int>(end_offset);
1357}
1358
1359static inline ExcessResult excess_min_128_byte_lut_short(
1360 const uint64_t* s,
1361 size_t left,
1362 size_t right) noexcept {
1363 int best = prefix_excess_128(s, left);
1364 size_t best_offset = left;
1365 if (left == right) {
1366 return {best, best_offset};
1367 }
1368
1369 int current = best;
1370 size_t bit = left;
1371 for (; bit < right && (bit & 7u) != 0; ++bit) {
1372 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1373 const size_t offset = bit + 1;
1374 if (current < best) {
1375 best = current;
1376 best_offset = offset;
1377 }
1378 }
1379
1380 for (; bit + 8 <= right; bit += 8) {
1381 const uint8_t byte =
1382 static_cast<uint8_t>((s[bit >> 6] >> (bit & 63)) & 0xFFu);
1383 const int candidate = current + excess_byte_min_lut[byte];
1384 if (candidate < best) {
1385 best = candidate;
1386 best_offset = bit + static_cast<size_t>(excess_byte_min_offset_lut[byte]);
1387 }
1388 current += excess_byte_delta_lut[byte];
1389 }
1390
1391 for (; bit < right; ++bit) {
1392 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1393 const size_t offset = bit + 1;
1394 if (current < best) {
1395 best = current;
1396 best_offset = offset;
1397 }
1398 }
1399
1400 return {best, best_offset};
1401}
1402
1417static inline ExcessResult excess_min_64(const uint64_t* s,
1418 size_t left,
1419 size_t right) noexcept {
1420 if (left > right) {
1421 return {};
1422 }
1423 left = std::min<size_t>(left, 64);
1424 right = std::min<size_t>(right, 64);
1425
1426 int best = prefix_excess_64(s, left);
1427 size_t best_offset = left;
1428 if (left == right) {
1429 return {best, best_offset};
1430 }
1431
1432#ifdef PIXIE_SSE41_SUPPORT
1433 int current = best;
1434 size_t bit = left;
1435 for (; bit < right && (bit & 3u) != 0; ++bit) {
1436 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1437 const size_t offset = bit + 1;
1438 if (current < best) {
1439 best = current;
1440 best_offset = offset;
1441 }
1442 }
1443
1444 const size_t first_full_nibble = bit >> 2;
1445 const size_t last_full_nibble = right >> 2;
1446 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1447 const size_t end_nibble =
1448 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1449 if (first_full_nibble < end_nibble) {
1450 const __m128i nibbles = excess_nibbles_64_sse(s);
1451
1452 __m128i ps = _mm_shuffle_epi8(excess_lut_delta_sse, nibbles);
1453 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 1));
1454 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 2));
1455 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 4));
1456 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 8));
1457
1458 const __m128i excl_ps = _mm_slli_si128(ps, 1);
1459 __m128i local_min = _mm_shuffle_epi8(excess_lut_min_sse, nibbles);
1460 if (right_partial_width != 0) {
1461 __m128i partial_min = _mm_shuffle_epi8(excess_lut_pos0_sse, nibbles);
1462 if (right_partial_width >= 2) {
1463 partial_min = _mm_min_epi8(
1464 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, nibbles));
1465 }
1466 if (right_partial_width >= 3) {
1467 partial_min = _mm_min_epi8(
1468 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, nibbles));
1469 }
1470 local_min = _mm_blendv_epi8(
1471 local_min, partial_min,
1472 _mm_cmpeq_epi8(excess_lut_nibble_index_sse,
1473 _mm_set1_epi8(static_cast<int8_t>(last_full_nibble))));
1474 }
1475 const __m128i partial_candidates = _mm_add_epi8(excl_ps, local_min);
1476
1477 const __m128i idx = excess_lut_nibble_index_sse;
1478 const int first_minus_one_value = static_cast<int>(first_full_nibble) - 1;
1479 const __m128i first_minus_one =
1480 _mm_set1_epi8(static_cast<int8_t>(first_minus_one_value));
1481 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1482 const __m128i active = _mm_and_si128(_mm_cmpgt_epi8(idx, first_minus_one),
1483 _mm_cmpgt_epi8(last, idx));
1484 const __m128i masked_candidates =
1485 _mm_blendv_epi8(_mm_set1_epi8(127), partial_candidates, active);
1486
1487 __m128i min128 = masked_candidates;
1488 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1489 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1490 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1491 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1492
1493 const int candidate_min =
1494 static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1495 if (candidate_min < best) {
1496 const __m128i equal_min = _mm_cmpeq_epi8(
1497 masked_candidates, _mm_set1_epi8(static_cast<int8_t>(candidate_min)));
1498 const uint32_t equal_mask =
1499 static_cast<uint32_t>(_mm_movemask_epi8(equal_min));
1500 const uint32_t nibble_index = std::countr_zero(equal_mask);
1501 const uint8_t nibble =
1502 static_cast<uint8_t>((s[0] >> (nibble_index * 4u)) & 0xFu);
1503 best = candidate_min;
1504 if (right_partial_width != 0 && nibble_index == last_full_nibble) {
1505 int local = 0;
1506 int local_best = 0;
1507 size_t local_offset = 1;
1508 for (size_t i = 0; i < right_partial_width; ++i) {
1509 local += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1510 if (i == 0 || local < local_best) {
1511 local_best = local;
1512 local_offset = i + 1;
1513 }
1514 }
1515 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1516 } else {
1517 best_offset = static_cast<size_t>(nibble_index) * 4u +
1518 static_cast<size_t>(excess_nibble_min_offset[nibble]);
1519 }
1520 }
1521
1522 bit = end_nibble * 4;
1523 }
1524
1525 for (; bit < right; ++bit) {
1526 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1527 const size_t offset = bit + 1;
1528 if (current < best) {
1529 best = current;
1530 best_offset = offset;
1531 }
1532 }
1533#else
1534 int current = best;
1535 for (size_t bit = left; bit < right; ++bit) {
1536 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1537 const size_t offset = bit + 1;
1538 if (current < best) {
1539 best = current;
1540 best_offset = offset;
1541 }
1542 }
1543#endif
1544
1545 return {best, best_offset};
1546}
1547
1554static inline ExcessResult excess_min_128(const uint64_t* s,
1555 size_t left,
1556 size_t right) noexcept {
1557 if (left > right) {
1558 return {};
1559 }
1560 left = std::min<size_t>(left, 128);
1561 right = std::min<size_t>(right, 128);
1562
1563 if (right - left <= 32 && (left & 7u) == 0 && (right & 7u) == 0)
1564 [[unlikely]] {
1565 return excess_min_128_byte_lut_short(s, left, right);
1566 }
1567
1568 int best = prefix_excess_128(s, left);
1569 size_t best_offset = left;
1570 if (left == right) {
1571 return {best, best_offset};
1572 }
1573
1574#ifdef PIXIE_AVX2_SUPPORT
1575 int current = best;
1576 size_t bit = left;
1577 for (; bit < right && (bit & 3u) != 0; ++bit) {
1578 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1579 const size_t offset = bit + 1;
1580 if (current < best) {
1581 best = current;
1582 best_offset = offset;
1583 }
1584 }
1585
1586 const size_t first_nibble = bit >> 2;
1587 const size_t last_full_nibble = right >> 2;
1588 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1589 const size_t end_nibble =
1590 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1591 if (first_nibble < end_nibble) {
1592 const __m128i bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
1593 const __m128i lo_nibbles = _mm_and_si128(bytes, excess_lut_nibble_mask_sse);
1594 const __m128i hi_nibbles =
1595 _mm_and_si128(_mm_srli_epi16(bytes, 4), excess_lut_nibble_mask_sse);
1596 const __m128i lo_delta = _mm_shuffle_epi8(excess_lut_delta_sse, lo_nibbles);
1597 const __m128i hi_delta = _mm_shuffle_epi8(excess_lut_delta_sse, hi_nibbles);
1598 const __m128i byte_delta = _mm_add_epi8(lo_delta, hi_delta);
1599 const __m128i byte_prefix = excess_prefix_sum_16x_i8(byte_delta);
1600 const __m128i byte_prefix_before = _mm_slli_si128(byte_prefix, 1);
1601
1602 __m128i lo_local_min = _mm_shuffle_epi8(excess_lut_min_sse, lo_nibbles);
1603 __m128i hi_local_min = _mm_shuffle_epi8(excess_lut_min_sse, hi_nibbles);
1604
1605 const __m128i byte_index = excess_lut_nibble_index_sse;
1606 if (right_partial_width != 0) {
1607 const bool partial_is_high = (last_full_nibble & 1u) != 0;
1608 const size_t partial_byte = last_full_nibble >> 1;
1609 const __m128i partial_source = partial_is_high ? hi_nibbles : lo_nibbles;
1610 __m128i partial_min =
1611 _mm_shuffle_epi8(excess_lut_pos0_sse, partial_source);
1612 if (right_partial_width >= 2) {
1613 partial_min = _mm_min_epi8(
1614 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, partial_source));
1615 }
1616 if (right_partial_width >= 3) {
1617 partial_min = _mm_min_epi8(
1618 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, partial_source));
1619 }
1620 const __m128i partial_lane = _mm_cmpeq_epi8(
1621 byte_index, _mm_set1_epi8(static_cast<int8_t>(partial_byte)));
1622 if (partial_is_high) {
1623 hi_local_min = _mm_blendv_epi8(hi_local_min, partial_min, partial_lane);
1624 } else {
1625 lo_local_min = _mm_blendv_epi8(lo_local_min, partial_min, partial_lane);
1626 }
1627 }
1628
1629 const __m128i lo_candidates =
1630 _mm_add_epi8(byte_prefix_before, lo_local_min);
1631 const __m128i hi_candidates =
1632 _mm_add_epi8(_mm_add_epi8(byte_prefix_before, lo_delta), hi_local_min);
1633
1634 __m128i masked_lo = lo_candidates;
1635 __m128i masked_hi = hi_candidates;
1636 if (first_nibble != 0 || end_nibble != 32) {
1637 const __m128i first_minus_one = _mm_set1_epi8(
1638 static_cast<int8_t>(static_cast<int>(first_nibble) - 1));
1639 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1640 const __m128i lo_active = _mm_and_si128(
1641 _mm_cmpgt_epi8(excess_lut_low_nibble_index_sse, first_minus_one),
1642 _mm_cmpgt_epi8(last, excess_lut_low_nibble_index_sse));
1643 const __m128i hi_active = _mm_and_si128(
1644 _mm_cmpgt_epi8(excess_lut_high_nibble_index_sse, first_minus_one),
1645 _mm_cmpgt_epi8(last, excess_lut_high_nibble_index_sse));
1646 masked_lo = _mm_blendv_epi8(_mm_set1_epi8(127), lo_candidates, lo_active);
1647 masked_hi = _mm_blendv_epi8(_mm_set1_epi8(127), hi_candidates, hi_active);
1648 }
1649
1650 const int candidate_min =
1651 excess_horizontal_min_i8(_mm_min_epi8(masked_lo, masked_hi));
1652 if (candidate_min < best) {
1653 const __m128i min_vec = _mm_set1_epi8(static_cast<int8_t>(candidate_min));
1654 const uint32_t lo_equal_mask = static_cast<uint32_t>(
1655 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_lo, min_vec)));
1656 const uint32_t hi_equal_mask = static_cast<uint32_t>(
1657 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_hi, min_vec)));
1658 const uint32_t lo_nibble_index =
1659 lo_equal_mask == 0
1660 ? 32u
1661 : static_cast<uint32_t>(std::countr_zero(lo_equal_mask)) * 2u;
1662 const uint32_t hi_nibble_index =
1663 hi_equal_mask == 0
1664 ? 32u
1665 : static_cast<uint32_t>(std::countr_zero(hi_equal_mask)) * 2u +
1666 1u;
1667 const uint32_t nibble_index = std::min(lo_nibble_index, hi_nibble_index);
1668 const uint32_t byte_offset = nibble_index >> 1u;
1669 const uint64_t byte_word = s[byte_offset >> 3u];
1670 const uint8_t byte = static_cast<uint8_t>(
1671 (byte_word >> ((byte_offset & 7u) * 8u)) & 0xFFu);
1672 const uint8_t nibble = (nibble_index & 1u) == 0
1673 ? static_cast<uint8_t>(byte & 0xFu)
1674 : static_cast<uint8_t>((byte >> 4u) & 0xFu);
1675 const size_t local_offset =
1676 right_partial_width != 0 && nibble_index == last_full_nibble
1677 ? static_cast<size_t>(
1678 excess_partial_nibble_min_offset_lut[right_partial_width]
1679 [nibble])
1680 : static_cast<size_t>(excess_lut_min_offset[nibble]);
1681 best = candidate_min;
1682 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1683 }
1684 }
1685#else
1686 int current = 0;
1687 for (size_t bit = 0; bit < right; ++bit) {
1688 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1689 const size_t offset = bit + 1;
1690 if (offset >= left && current < best) {
1691 best = current;
1692 best_offset = offset;
1693 }
1694 }
1695#endif
1696
1697 return {best, best_offset};
1698}
1699
1713static inline ExcessBoundaryPairResult excess_min_64_disjoint_suffix_prefix(
1714 const uint64_t* suffix_s,
1715 size_t suffix_left,
1716 const uint64_t* prefix_s,
1717 size_t prefix_right) noexcept {
1718 suffix_left = std::min<size_t>(suffix_left, 64);
1719 prefix_right = std::min<size_t>(prefix_right, 64);
1720 return {excess_min_64(suffix_s, suffix_left, 63),
1721 excess_min_64(prefix_s, 0, prefix_right)};
1722}
1723
1742static inline ExcessBoundaryPairResult excess_min_128_disjoint_suffix_prefix(
1743 const uint64_t* suffix_s,
1744 size_t suffix_left,
1745 const uint64_t* prefix_s,
1746 size_t prefix_right) noexcept {
1747 suffix_left = std::min<size_t>(suffix_left, 128);
1748 prefix_right = std::min<size_t>(prefix_right, 128);
1749 if (suffix_left <= prefix_right || suffix_left > 127 || prefix_right > 127) {
1750 return {excess_min_128(suffix_s, suffix_left, 127),
1751 excess_min_128(prefix_s, 0, prefix_right)};
1752 }
1753
1754#ifdef PIXIE_AVX2_SUPPORT
1755 ExcessResult prefix{0, 0};
1756
1757 int suffix_best = prefix_excess_128(suffix_s, suffix_left);
1758 ExcessResult suffix{suffix_best, suffix_left};
1759 size_t suffix_bit = suffix_left;
1760 int suffix_current = suffix_best;
1761 for (; suffix_bit < 127 && (suffix_bit & 3u) != 0; ++suffix_bit) {
1762 suffix_current +=
1763 ((suffix_s[suffix_bit >> 6] >> (suffix_bit & 63)) & 1ull) != 0 ? 1 : -1;
1764 const size_t offset = suffix_bit + 1;
1765 if (suffix_current < suffix.min_excess) {
1766 suffix = {suffix_current, offset};
1767 }
1768 }
1769
1770 const size_t prefix_last_nibble = prefix_right >> 2;
1771 const size_t prefix_partial_width = prefix_right & 3u;
1772 const size_t prefix_end_nibble =
1773 prefix_last_nibble + (prefix_partial_width == 0 ? 0 : 1);
1774 const size_t suffix_first_nibble = suffix_bit < 127 ? suffix_bit >> 2 : 32;
1775 const int prefix_artificial_delta =
1776 prefix_excess_128(prefix_s, prefix_end_nibble * 4);
1777
1778 const __m256i idx = excess_lut_nibble_index;
1779 const __m256i prefix_active = _mm256_cmpgt_epi8(
1780 _mm256_set1_epi8(static_cast<int8_t>(prefix_end_nibble)), idx);
1781 const __m256i suffix_active = _mm256_cmpgt_epi8(
1782 idx, _mm256_set1_epi8(static_cast<int8_t>(suffix_first_nibble) - 1));
1783
1784 const __m256i prefix_nibbles = excess_nibbles_128_avx2(prefix_s);
1785 const __m256i suffix_nibbles = excess_nibbles_128_avx2(suffix_s);
1786 __m256i nibbles =
1787 _mm256_blendv_epi8(_mm256_set1_epi8(3), prefix_nibbles, prefix_active);
1788 nibbles = _mm256_blendv_epi8(nibbles, suffix_nibbles, suffix_active);
1789
1790 __m256i ps = _mm256_shuffle_epi8(excess_lut_delta, nibbles);
1791 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1792 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1793 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1794 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1795
1796 __m128i ps_lo = _mm256_castsi256_si128(ps);
1797 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1798 __m128i carry =
1799 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
1800 ps_hi = _mm_add_epi8(ps_hi, carry);
1801 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1802
1803 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1804 const __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1805
1806 __m256i local_min = _mm256_shuffle_epi8(excess_lut_min, nibbles);
1807 if (prefix_partial_width != 0) {
1808 __m256i partial_min = _mm256_shuffle_epi8(excess_lut_pos0, nibbles);
1809 if (prefix_partial_width >= 2) {
1810 partial_min = _mm256_min_epi8(
1811 partial_min, _mm256_shuffle_epi8(excess_lut_pos1, nibbles));
1812 }
1813 if (prefix_partial_width >= 3) {
1814 partial_min = _mm256_min_epi8(
1815 partial_min, _mm256_shuffle_epi8(excess_lut_pos2, nibbles));
1816 }
1817 local_min = _mm256_blendv_epi8(
1818 local_min, partial_min,
1819 _mm256_cmpeq_epi8(
1820 idx, _mm256_set1_epi8(static_cast<int8_t>(prefix_last_nibble))));
1821 }
1822 __m256i suffix_partial_min = _mm256_min_epi8(
1823 _mm256_shuffle_epi8(excess_lut_pos0, nibbles),
1824 _mm256_min_epi8(_mm256_shuffle_epi8(excess_lut_pos1, nibbles),
1825 _mm256_shuffle_epi8(excess_lut_pos2, nibbles)));
1826 const __m256i suffix_tail_active = _mm256_and_si256(
1827 suffix_active, _mm256_cmpeq_epi8(idx, _mm256_set1_epi8(31)));
1828 local_min =
1829 _mm256_blendv_epi8(local_min, suffix_partial_min, suffix_tail_active);
1830
1831 const __m256i base_candidates = _mm256_add_epi8(excl_ps, local_min);
1832 const __m256i sentinel = _mm256_set1_epi8(127);
1833
1834 const __m256i prefix_candidates =
1835 _mm256_blendv_epi8(sentinel, base_candidates, prefix_active);
1836 const __m256i suffix_candidates = _mm256_blendv_epi8(
1837 sentinel,
1838 _mm256_add_epi8(base_candidates,
1839 _mm256_set1_epi8(static_cast<int8_t>(
1840 suffix_current - prefix_artificial_delta))),
1841 suffix_active);
1842
1843 auto reduce_min = [](__m256i values) {
1844 __m128i min128 = _mm_min_epi8(_mm256_castsi256_si128(values),
1845 _mm256_extracti128_si256(values, 1));
1846 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1847 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1848 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1849 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1850 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1851 };
1852
1853 auto local_offset = [](uint8_t nibble, size_t width_value) {
1854 if (width_value == 0 || width_value == 4) {
1855 return static_cast<size_t>(excess_lut_min_offset[nibble]);
1856 }
1857 int current = 0;
1858 int best = 0;
1859 size_t best_offset = 1;
1860 for (size_t i = 0; i < width_value; ++i) {
1861 current += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1862 if (i == 0 || current < best) {
1863 best = current;
1864 best_offset = i + 1;
1865 }
1866 }
1867 return best_offset;
1868 };
1869
1870 const int prefix_min = reduce_min(prefix_candidates);
1871 if (prefix_min < prefix.min_excess) {
1872 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1873 _mm256_cmpeq_epi8(prefix_candidates,
1874 _mm256_set1_epi8(static_cast<int8_t>(prefix_min)))));
1875 const uint32_t prefix_lane = std::countr_zero(mask);
1876 const uint64_t word = prefix_s[prefix_lane >> 4];
1877 const uint8_t nibble =
1878 static_cast<uint8_t>((word >> ((prefix_lane & 15u) * 4u)) & 0xFu);
1879 prefix.min_excess = prefix_min;
1880 const size_t width =
1881 prefix_partial_width != 0 && prefix_lane == prefix_last_nibble
1882 ? prefix_partial_width
1883 : 4;
1884 prefix.offset =
1885 static_cast<size_t>(prefix_lane) * 4u + local_offset(nibble, width);
1886 }
1887
1888 const int suffix_min = reduce_min(suffix_candidates);
1889 if (suffix_min < suffix.min_excess) {
1890 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1891 _mm256_cmpeq_epi8(suffix_candidates,
1892 _mm256_set1_epi8(static_cast<int8_t>(suffix_min)))));
1893 const uint32_t suffix_lane = std::countr_zero(mask);
1894 const uint64_t word = suffix_s[suffix_lane >> 4];
1895 const uint8_t nibble =
1896 static_cast<uint8_t>((word >> ((suffix_lane & 15u) * 4u)) & 0xFu);
1897 suffix.min_excess = suffix_min;
1898 const size_t width = suffix_lane == 31 ? 3 : 4;
1899 suffix.offset =
1900 static_cast<size_t>(suffix_lane) * 4u + local_offset(nibble, width);
1901 }
1902
1903 return {suffix, prefix};
1904#else
1905 return {excess_min_128(suffix_s, suffix_left, 127),
1906 excess_min_128(prefix_s, 0, prefix_right)};
1907#endif
1908}
1909
1925static inline size_t forward_search_128(const uint64_t* s,
1926 int target_x,
1927 size_t start_offset,
1928 int* block_excess = nullptr) noexcept {
1929 uint64_t out[2];
1930 const int delta = excess_positions_128(s, target_x, out);
1931 if (block_excess != nullptr) {
1932 *block_excess = delta;
1933 }
1934 if (start_offset >= 128) {
1935 return 128;
1936 }
1937
1938 const size_t first_word = start_offset >> 6;
1939 const size_t first_bit = start_offset & 63;
1940 for (size_t word = first_word; word < 2; ++word) {
1941 uint64_t mask = out[word];
1942 if (word == first_word && first_bit != 0) {
1943 mask &= ~first_bits_mask(first_bit);
1944 }
1945 if (mask != 0) {
1946 return word * 64 + std::countr_zero(mask);
1947 }
1948 }
1949 return 128;
1950}
1951
1969static inline size_t backward_search_128(const uint64_t* s,
1970 int target_x,
1971 size_t end_offset,
1972 int* block_excess = nullptr) noexcept {
1973 uint64_t out[2];
1974 const int delta = excess_positions_128(s, target_x, out);
1975 if (block_excess != nullptr) {
1976 *block_excess = delta;
1977 }
1978 if (end_offset == 0) {
1979 return 128;
1980 }
1981
1982 const size_t max_prefix_length = end_offset - 1;
1983 if (max_prefix_length > 0) {
1984 const size_t last_bit_index = max_prefix_length - 1;
1985 size_t word = last_bit_index >> 6;
1986 const size_t bit_in_word = last_bit_index & 63;
1987 uint64_t mask = out[word] & first_bits_mask(bit_in_word + 1);
1988 while (true) {
1989 if (mask != 0) {
1990 return word * 64 + (63 - std::countl_zero(mask)) + 1;
1991 }
1992 if (word == 0) {
1993 break;
1994 }
1995 --word;
1996 mask = out[word];
1997 }
1998 }
1999 return target_x == 0 ? 0 : 128;
2000}
2001
2014static inline void excess_positions_512(const uint64_t* s,
2015 int target_x,
2016 uint64_t* out) noexcept {
2017 if (target_x < -512 || target_x > 512) {
2018 out[0] = out[1] = out[2] = out[3] = 0;
2019 out[4] = out[5] = out[6] = out[7] = 0;
2020 return;
2021 }
2022
2023 for (int k = 0; k < 4; ++k) {
2024 target_x -= excess_positions_128(s + 2 * k, target_x, out + 2 * k);
2025 }
2026}
2027
2041static inline void excess_record_lows_128(const uint64_t* s,
2042 uint64_t* out) noexcept {
2043 out[0] = out[1] = 0;
2044 int cur = 0;
2045 int best = 0;
2046 for (size_t i = 0; i < 128; ++i) {
2047 const uint64_t w = s[i >> 6];
2048 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
2049 cur += bit ? +1 : -1;
2050 if (cur < best) {
2051 best = cur;
2052 out[i >> 6] |= (uint64_t{1} << (i & 63));
2053 }
2054 }
2055}
2056
2065static inline void excess_record_lows_128_byte_lut(const uint64_t* s,
2066 uint64_t* out) noexcept {
2067 out[0] = out[1] = 0;
2068 int cur = 0;
2069 int best = 0;
2070 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
2071 const size_t bit_base = byte_idx * 8;
2072 const uint8_t byte =
2073 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
2074 const int byte_min = excess_byte_min_lut[byte];
2075 if (cur + byte_min < best) {
2076 for (size_t i = 0; i < 8; ++i) {
2077 const int bit = static_cast<int>((byte >> i) & 1u);
2078 cur += bit ? +1 : -1;
2079 if (cur < best) {
2080 best = cur;
2081 const size_t pos = bit_base + i;
2082 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
2083 }
2084 }
2085 } else {
2086 cur += excess_byte_delta_lut[byte];
2087 }
2088 }
2089}
2090
2099static inline void excess_record_lows_128_lut(const uint64_t* s,
2100 uint64_t* out) noexcept {
2101 out[0] = out[1] = 0;
2102 int cur = 0;
2103 int best = 0;
2104 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
2105 const size_t bit_base = byte_idx * 8;
2106 const uint8_t byte =
2107 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
2108 const int gap = cur - best;
2109 const int idx = gap > 7 ? 7 : (gap < 0 ? 0 : gap);
2110 const uint8_t mask =
2111 excess_byte_record_lows_lut[byte][static_cast<size_t>(idx)];
2112 if (mask != 0) {
2113 // Recompute absolute excesses for masked positions to update cur/best.
2114 int local = 0;
2115 int local_best = 0;
2116 uint8_t local_mask = 0;
2117 for (int bit = 0; bit < 8; ++bit) {
2118 local += ((byte >> bit) & 1u) ? 1 : -1;
2119 if (local < local_best) {
2120 local_best = local;
2121 local_mask |= static_cast<uint8_t>(1u << bit);
2122 }
2123 }
2124 // Only output positions whose absolute excess is < best.
2125 uint8_t out_mask = 0;
2126 local = 0;
2127 for (int bit = 0; bit < 8; ++bit) {
2128 local += ((byte >> bit) & 1u) ? 1 : -1;
2129 if (cur + local < best) {
2130 out_mask |= static_cast<uint8_t>(1u << bit);
2131 best = cur + local;
2132 }
2133 }
2134 if (out_mask != 0) {
2135 const uint64_t word = static_cast<uint64_t>(out_mask)
2136 << (bit_base & 63);
2137 out[bit_base >> 6] |= word;
2138 }
2139 }
2140 cur += excess_byte_delta_lut[byte];
2141 }
2142}
2143
2144#ifdef PIXIE_AVX2_SUPPORT
2152static inline void excess_record_lows_128_avx2(const uint64_t* s,
2153 uint64_t* out) noexcept {
2154 out[0] = out[1] = 0;
2155 int cur = 0;
2156 int best = 0;
2157
2158 const __m256i masks = excess_bit_masks_16x_i16();
2159 const __m256i zero = _mm256_setzero_si256();
2160 const __m256i pos = _mm256_set1_epi16(1);
2161 const __m256i neg = _mm256_set1_epi16(-1);
2162
2163 for (size_t chunk = 0; chunk < 8; ++chunk) {
2164 const size_t chunk_bit = chunk * 16;
2165 const uint16_t bits =
2166 chunk < 4
2167 ? static_cast<uint16_t>((s[0] >> (chunk * 16)) & 0xFFFFu)
2168 : static_cast<uint16_t>((s[1] >> ((chunk - 4) * 16)) & 0xFFFFu);
2169
2170 const __m256i vb = _mm256_set1_epi16(static_cast<int16_t>(bits));
2171 const __m256i m = _mm256_and_si256(vb, masks);
2172 const __m256i is_zero = _mm256_cmpeq_epi16(m, zero);
2173 const __m256i steps = _mm256_blendv_epi8(pos, neg, is_zero);
2174 const __m256i pref_rel = excess_prefix_sum_16x_i16(steps);
2175 const __m256i pref_abs = _mm256_add_epi16(
2176 pref_rel, _mm256_set1_epi16(static_cast<int16_t>(cur)));
2177
2178 alignas(32) int16_t vals[16];
2179 _mm256_store_si256(reinterpret_cast<__m256i*>(vals), pref_abs);
2180
2181 for (size_t lane = 0; lane < 16; ++lane) {
2182 const int val = vals[lane];
2183 if (val < best) {
2184 best = val;
2185 const size_t pos_idx = chunk_bit + lane;
2186 out[pos_idx >> 6] |= (uint64_t{1} << (pos_idx & 63));
2187 }
2188 }
2189
2190 cur += 2 * static_cast<int>(std::popcount(bits)) - 16;
2191 }
2192}
2193
2203static inline void excess_record_lows_128_nibble_lut(const uint64_t* s,
2204 uint64_t* out) noexcept {
2205 out[0] = out[1] = 0;
2206 int cur = 0;
2207 int best = 0;
2208
2209 const __m256i vdelta = excess_lut_delta;
2210 const __m256i vmin = excess_lut_min;
2211
2212 __m256i nibbles = excess_nibbles_128_avx2(s);
2213
2214 // Compute inclusive prefix sums of per-nibble total excess changes.
2215 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
2216 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
2217 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
2218 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
2219 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
2220
2221 __m128i ps_lo = _mm256_castsi256_si128(ps);
2222 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
2223 __m128i carry =
2224 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
2225 ps_hi = _mm_add_epi8(ps_hi, carry);
2226 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
2227
2228 // Exclusive prefix: shift in zero at start.
2229 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
2230 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
2231
2232 // Local minima relative to each nibble start.
2233 __m256i local_min = _mm256_shuffle_epi8(vmin, nibbles);
2234
2235 alignas(32) int8_t excl[32];
2236 _mm256_store_si256(reinterpret_cast<__m256i*>(excl), excl_ps);
2237
2238 alignas(32) int8_t nibble_min[32];
2239 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_min), local_min);
2240
2241 alignas(32) int8_t nibble_vals[32];
2242 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_vals), nibbles);
2243
2244 for (int n = 0; n < 32; ++n) {
2245 const int nibble_base = cur + excl[n];
2246 const int nibble_best = nibble_base + nibble_min[n];
2247 if (nibble_best < best) {
2248 // This nibble contains at least one record low — scan bit-by-bit.
2249 const uint8_t nibble = static_cast<uint8_t>(nibble_vals[n]);
2250 int local = 0;
2251 for (int bit = 0; bit < 4; ++bit) {
2252 local += ((nibble >> bit) & 1u) ? 1 : -1;
2253 const int val = nibble_base + local;
2254 if (val < best) {
2255 best = val;
2256 const size_t pos = static_cast<size_t>(n) * 4 + bit;
2257 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
2258 }
2259 }
2260 }
2261 }
2262}
2263#endif
2264
2277static inline void excess_record_lows_512(const uint64_t* s,
2278 uint64_t* out) noexcept {
2279 out[0] = out[1] = out[2] = out[3] = 0;
2280 out[4] = out[5] = out[6] = out[7] = 0;
2281 int best = 0;
2282 int global = 0;
2283 for (int k = 0; k < 4; ++k) {
2284 const uint64_t* block = s + 2 * k;
2285 uint64_t* block_out = out + 2 * k;
2286 for (size_t i = 0; i < 128; ++i) {
2287 const uint64_t w = block[i >> 6];
2288 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
2289 global += bit ? +1 : -1;
2290 if (global < best) {
2291 best = global;
2292 block_out[i >> 6] |= (uint64_t{1} << (i & 63));
2293 }
2294 }
2295 }
2296}
2297
2309static inline void rank_32x8(const uint8_t* x, uint8_t* result) {
2310#ifdef PIXIE_AVX512_SUPPORT
2311 // Step 1: Calculate popcount of each byte
2312 popcount_32x8(x, result);
2313 __m256i prefix_sums = _mm256_loadu_si256((__m256i const*)result);
2314 const __m256i zero = _mm256_setzero_si256();
2315
2316 prefix_sums = _mm256_add_epi8(prefix_sums,
2317 _mm256_alignr_epi8(prefix_sums, zero, 16 - 1));
2318 prefix_sums = _mm256_add_epi8(prefix_sums,
2319 _mm256_alignr_epi8(prefix_sums, zero, 16 - 2));
2320 prefix_sums = _mm256_add_epi8(prefix_sums,
2321 _mm256_alignr_epi8(prefix_sums, zero, 16 - 4));
2322 prefix_sums = _mm256_add_epi8(prefix_sums,
2323 _mm256_alignr_epi8(prefix_sums, zero, 16 - 8));
2324
2325 // At this point we have prefix sums for two halfs, the last step is to
2326 // extract 16-th value and add it to the whole second half
2327 __m128i low_lane = _mm256_extracti128_si256(prefix_sums, 0);
2328 __m128i high_lane = _mm256_extracti128_si256(prefix_sums, 1);
2329 auto last_val_low = _mm_extract_epi8(low_lane, 15);
2330 __m128i add_to_high = _mm_set1_epi8(last_val_low);
2331 high_lane = _mm_add_epi8(high_lane, add_to_high);
2332 prefix_sums = _mm256_set_m128i(high_lane, low_lane);
2333 _mm256_storeu_epi8(result, prefix_sums);
2334#else
2335 // Scalar fallback implementation
2336 result[0] = std::popcount(x[0]);
2337 for (size_t i = 1; i < 32; ++i) {
2338 result[i] = std::popcount(x[i]) + result[i - 1];
2339 }
2340#endif
2341}
Pair of boundary minimum results for adjacent BP query blocks.
Definition bits.h:1103
Minimum prefix excess in a 128-bit bitstring range.
Definition bits.h:1092
Definition bits.h:325