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(__BMI__) && defined(__BMI2__)
19#define PIXIE_BMI_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 uint64_t last_uint = count < 512 ? count >> 6 : 8;
310
311 uint64_t pop_val = 0;
312
313 for (int 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
329static inline uint64_t select_64(uint64_t x, uint64_t rank) {
330#ifdef PIXIE_BMI_SUPPORT
331 return _tzcnt_u64(_pdep_u64(1ull << rank, x));
332#else
333 while (rank != 0) {
334 x &= x - 1;
335 --rank;
336 }
337 return std::countr_zero(x);
338#endif
339}
340
358static inline uint64_t select_512(const uint64_t* x, uint64_t rank) {
359#ifdef PIXIE_AVX512_SUPPORT
360
361 __m512i res = _mm512_loadu_epi64(x);
362 __m512i counts = _mm512_popcnt_epi64(res);
363 __m512i prefix = counts;
364
365 const __m512i idx_shift1 = _mm512_set_epi64(6, 5, 4, 3, 2, 1, 0, 0);
366 const __m512i idx_shift2 = _mm512_set_epi64(5, 4, 3, 2, 1, 0, 0, 0);
367 const __m512i idx_shift4 = _mm512_set_epi64(3, 2, 1, 0, 0, 0, 0, 0);
368
369 __m512i tmp = _mm512_maskz_permutexvar_epi64(0xFE, idx_shift1, prefix);
370 prefix = _mm512_add_epi64(prefix, tmp);
371 tmp = _mm512_maskz_permutexvar_epi64(0xFC, idx_shift2, prefix);
372 prefix = _mm512_add_epi64(prefix, tmp);
373 tmp = _mm512_maskz_permutexvar_epi64(0xF0, idx_shift4, prefix);
374 prefix = _mm512_add_epi64(prefix, tmp);
375
376 __mmask8 mask = _mm512_cmpgt_epu64_mask(prefix, _mm512_set1_epi64(rank));
377 uint32_t i = _tzcnt_u32(static_cast<uint32_t>(mask));
378 uint64_t prev = 0;
379 if (i != 0) {
380 __m512i idx_prev = _mm512_set1_epi64(static_cast<int64_t>(i - 1));
381 __m512i prev_vec = _mm512_permutexvar_epi64(idx_prev, prefix);
382 prev = static_cast<uint64_t>(
383 _mm_cvtsi128_si64(_mm512_castsi512_si128(prev_vec)));
384 }
385 return i * 64 + select_64(x[i], rank - prev);
386
387#else
388
389 size_t i = 0;
390 int popcount = std::popcount(x[0]);
391 while (i < 7 && popcount <= rank) {
392 rank -= popcount;
393 popcount = std::popcount(x[++i]);
394 }
395 return i * 64 + select_64(x[i], rank);
396
397#endif
398}
399
400#ifdef PIXIE_AVX512_SUPPORT
401static inline uint64_t select0_512_from_inverted_words(const uint64_t* x,
402 uint64_t rank0,
403 __m512i res) {
404 __m512i counts = _mm512_popcnt_epi64(res);
405 __m512i prefix = counts;
406
407 const __m512i idx_shift1 = _mm512_set_epi64(6, 5, 4, 3, 2, 1, 0, 0);
408 const __m512i idx_shift2 = _mm512_set_epi64(5, 4, 3, 2, 1, 0, 0, 0);
409 const __m512i idx_shift4 = _mm512_set_epi64(3, 2, 1, 0, 0, 0, 0, 0);
410
411 __m512i tmp = _mm512_maskz_permutexvar_epi64(0xFE, idx_shift1, prefix);
412 prefix = _mm512_add_epi64(prefix, tmp);
413 tmp = _mm512_maskz_permutexvar_epi64(0xFC, idx_shift2, prefix);
414 prefix = _mm512_add_epi64(prefix, tmp);
415 tmp = _mm512_maskz_permutexvar_epi64(0xF0, idx_shift4, prefix);
416 prefix = _mm512_add_epi64(prefix, tmp);
417
418 __mmask8 mask = _mm512_cmpgt_epu64_mask(prefix, _mm512_set1_epi64(rank0));
419 uint32_t i = _tzcnt_u32(static_cast<uint32_t>(mask));
420 uint64_t prev = 0;
421 if (i != 0) {
422 __m512i idx_prev = _mm512_set1_epi64(static_cast<int64_t>(i - 1));
423 __m512i prev_vec = _mm512_permutexvar_epi64(idx_prev, prefix);
424 prev = static_cast<uint64_t>(
425 _mm_cvtsi128_si64(_mm512_castsi512_si128(prev_vec)));
426 }
427 return i * 64 + select_64(~x[i], rank0 - prev);
428}
429#endif
430
435static inline uint64_t select0_512(const uint64_t* x, uint64_t rank0) {
436#ifdef PIXIE_AVX512_SUPPORT
437
438 __m512i res = _mm512_loadu_epi64(x);
439 res = _mm512_ternarylogic_epi64(res, res, res, 0x55);
440 return select0_512_from_inverted_words(x, rank0, res);
441
442#else
443
444 size_t i = 0;
445 int popcount = std::popcount(~x[0]);
446 while (i < 7 && popcount <= rank0) {
447 rank0 -= popcount;
448 popcount = std::popcount(~x[++i]);
449 }
450 return i * 64 + select_64(~x[i], rank0);
451
452#endif
453}
454
459static inline uint16_t lower_bound_4x64(const uint64_t* x, uint64_t y) {
460#ifdef PIXIE_AVX512_SUPPORT
461
462 auto y_4 = _mm256_set1_epi64x(y);
463 auto reg_256 = _mm256_loadu_epi64(x);
464 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
465
466 return _tzcnt_u16(cmp);
467
468#else
469#ifdef PIXIE_AVX2_SUPPORT
470
471 auto y_4 = _mm256_set1_epi64x(y);
472 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
473
474 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
475 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
476 __m256i y_offset = _mm256_xor_si256(y_4, offset);
477 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
478 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
479
480 return _tzcnt_u32(mask) >> 3;
481
482#else
483
484 for (uint16_t i = 0; i < 4; ++i) {
485 if (x[i] >= y) {
486 return i;
487 }
488 }
489 return 4;
490
491#endif
492#endif
493}
494
508static inline uint16_t lower_bound_delta_4x64(const uint64_t* x,
509 uint64_t y,
510 const uint64_t* delta_array,
511 uint64_t delta_scalar) {
512#ifdef PIXIE_AVX512_SUPPORT
513
514 const __m256i dlt_256 = _mm256_loadu_epi64(delta_array);
515 auto x_256 = _mm256_loadu_epi64(x);
516 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
517 auto y_4 = _mm256_set1_epi64x(y);
518
519 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
520 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
521 auto cmp = _mm256_cmpge_epu64_mask(reg_256, y_4);
522
523 return _tzcnt_u16(cmp);
524
525#else
526#ifdef PIXIE_AVX2_SUPPORT
527
528 const __m256i dlt_256 =
529 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
530 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
531 auto dlt_4 = _mm256_set1_epi64x(delta_scalar);
532 auto y_4 = _mm256_set1_epi64x(y);
533
534 auto tmp = _mm256_add_epi64(dlt_4, dlt_256);
535 auto reg_256 = _mm256_sub_epi64(tmp, x_256);
536
537 const __m256i offset = _mm256_set1_epi64x(0x8000000000000000ULL);
538 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
539 __m256i y_offset = _mm256_xor_si256(y_4, offset);
540 auto mask = _mm256_movemask_epi8(_mm256_cmpgt_epi64(
541 x_offset, _mm256_sub_epi64(y_offset, _mm256_set1_epi64x(1))));
542
543 return _tzcnt_u32(mask) >> 3;
544
545#else
546
547 for (uint16_t i = 0; i < 4; ++i) {
548 if (delta_array[i] + delta_scalar - x[i] >= y) {
549 return i;
550 }
551 }
552 return 4;
553
554#endif
555#endif
556}
557
562static inline uint16_t lower_bound_8x64(const uint64_t* x, uint64_t y) {
563#ifdef PIXIE_AVX512_SUPPORT
564
565 auto y_8 = _mm512_set1_epi64(y);
566 auto reg_512 = _mm512_loadu_epi64(x);
567 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
568
569 return _tzcnt_u16(cmp);
570
571#else
572#ifdef PIXIE_AVX2_SUPPORT
573
574 uint16_t len = lower_bound_4x64(x, y);
575
576 if (len < 4) {
577 return len;
578 }
579
580 return len + lower_bound_4x64(x + 4, y);
581
582#else
583
584 for (uint16_t i = 0; i < 8; ++i) {
585 if (x[i] >= y) {
586 return i;
587 }
588 }
589 return 8;
590
591#endif
592#endif
593}
594
608static inline uint16_t lower_bound_delta_8x64(const uint64_t* x,
609 uint64_t y,
610 const uint64_t* delta_array,
611 uint64_t delta_scalar) {
612#ifdef PIXIE_AVX512_SUPPORT
613
614 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
615 auto x_512 = _mm512_loadu_epi64(x);
616 auto dlt_8 = _mm512_set1_epi64(delta_scalar);
617 auto y_8 = _mm512_set1_epi64(y);
618
619 auto tmp = _mm512_add_epi64(dlt_8, dlt_512);
620 auto reg_512 = _mm512_sub_epi64(tmp, x_512);
621 auto cmp = _mm512_cmpge_epu64_mask(reg_512, y_8);
622
623 return _tzcnt_u16(cmp);
624
625#else
626#ifdef PIXIE_AVX2_SUPPORT
627
628 uint16_t len = lower_bound_delta_4x64(x, y, delta_array, delta_scalar);
629
630 if (len < 4) {
631 return len;
632 }
633
634 return len + lower_bound_delta_4x64(x + 4, y, delta_array + 4, delta_scalar);
635
636#else
637
638 for (uint16_t i = 0; i < 8; ++i) {
639 if (delta_array[i] + delta_scalar - x[i] >= y) {
640 return i;
641 }
642 }
643 return 8;
644
645#endif
646#endif
647}
648
653static inline uint16_t lower_bound_32x16(const uint16_t* x, uint16_t y) {
654#ifdef PIXIE_AVX512_SUPPORT
655
656 auto y_32 = _mm512_set1_epi16(y);
657 auto reg_512 = _mm512_loadu_epi16(x);
658 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
659 return std::popcount(cmp);
660
661#else
662#ifdef PIXIE_AVX2_SUPPORT
663
664 auto y_16 = _mm256_set1_epi16(y);
665 __m256i reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
666
667 const __m256i offset = _mm256_set1_epi16(0x8000);
668 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
669 __m256i y_offset = _mm256_xor_si256(y_16, offset);
670 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
671
672 uint16_t count = std::popcount(mask) >> 1;
673
674 reg_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
675
676 x_offset = _mm256_xor_si256(reg_256, offset);
677 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
678
679 return count + (std::popcount(mask) >> 1);
680
681#else
682
683 uint16_t cnt = 0;
684 for (uint16_t i = 0; i < 32; ++i) {
685 if (x[i] < y) {
686 cnt++;
687 }
688 }
689 return cnt;
690
691#endif
692#endif
693}
694
708static inline uint16_t lower_bound_delta_32x16(const uint16_t* x,
709 uint16_t y,
710 const uint16_t* delta_array,
711 uint16_t delta_scalar) {
712#ifdef PIXIE_AVX512_SUPPORT
713
714 const __m512i dlt_512 = _mm512_loadu_epi64(delta_array);
715 auto x_512 = _mm512_loadu_epi64(x);
716 auto dlt_32 = _mm512_set1_epi16(delta_scalar);
717 auto y_32 = _mm512_set1_epi16(y);
718
719 auto tmp = _mm512_add_epi16(dlt_32, dlt_512);
720 auto reg_512 = _mm512_sub_epi16(tmp, x_512);
721 auto cmp = _mm512_cmplt_epu16_mask(reg_512, y_32);
722 return std::popcount(cmp);
723
724#else
725#ifdef PIXIE_AVX2_SUPPORT
726
727 auto dlt_256 =
728 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array));
729 auto x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x));
730 auto dlt_16 = _mm256_set1_epi16(delta_scalar);
731 auto y_16 = _mm256_set1_epi16(y);
732
733 auto tmp = _mm256_add_epi16(dlt_16, dlt_256);
734 auto reg_256 = _mm256_sub_epi16(tmp, x_256);
735
736 const __m256i offset = _mm256_set1_epi16(0x8000);
737 __m256i x_offset = _mm256_xor_si256(reg_256, offset);
738 __m256i y_offset = _mm256_xor_si256(y_16, offset);
739 uint32_t mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
740
741 uint16_t count = std::popcount(mask) >> 1;
742
743 dlt_256 =
744 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(delta_array + 16));
745 x_256 = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(x + 16));
746
747 tmp = _mm256_add_epi16(dlt_16, dlt_256);
748 reg_256 = _mm256_sub_epi16(tmp, x_256);
749
750 x_offset = _mm256_xor_si256(reg_256, offset);
751 mask = _mm256_movemask_epi8(_mm256_cmpgt_epi16(y_offset, x_offset));
752
753 return count + (std::popcount(mask) >> 1);
754
755#else
756
757 uint16_t cnt = 0;
758 for (uint16_t i = 0; i < 32; ++i) {
759 if (delta_array[i] + delta_scalar - x[i] < y) {
760 cnt++;
761 }
762 }
763 return cnt;
764
765#endif
766#endif
767}
768
780static inline void popcount_64x4(const uint8_t* x, uint8_t* result) {
781#ifdef PIXIE_AVX512_SUPPORT
782 __m256i data = _mm256_loadu_si256((__m256i const*)x);
783
784 // Masks for extracting the lower and upper nibbles
785 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
786
787 // Count bits in the lower half
788 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
789 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
790
791 // Count bits in the upper half
792 __m256i high_bits = _mm256_srli_epi16(data, 4);
793 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
794 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
795
796 // Pack the results into a single output vector
797 __m256i result_vec =
798 _mm256_or_si256(low_count, _mm256_slli_epi16(high_count, 4));
799 _mm256_storeu_epi8(result, result_vec);
800#else
801 // Fallback implementation for non-AVX2 platforms
802 for (size_t i = 0; i < 32; i++) {
803 // Count bits in the lower half
804 uint8_t a = x[i] & 0x0F;
805 uint8_t low_count = std::popcount(a);
806 // Count bits in the upper half
807 a = (x[i] >> 4) & 0x0F;
808 uint8_t high_count = std::popcount(a);
809
810 // Pack the counts into the output byte
811 result[i] = low_count | (high_count << 4);
812 }
813#endif
814}
815
827static inline void popcount_32x8(const uint8_t* x, uint8_t* result) {
828#ifdef PIXIE_AVX512_SUPPORT
829 // Load 64 4-bit integers (256 bits total)
830 __m256i data = _mm256_loadu_si256((__m256i const*)x);
831 auto popcount_8 = _mm256_popcnt_epi8(data);
832 _mm256_storeu_si256((__m256i*)result, popcount_8);
833#else
834#ifdef PIXIE_AVX2_SUPPORT
835 // Load 64 4-bit integers (256 bits total)
836 __m256i data = _mm256_loadu_si256((__m256i const*)x);
837
838 // Masks for extracting the lower and upper nibbles
839 const __m256i low_bits_mask = _mm256_set1_epi8(0x0F);
840
841 // Count bits in lower half
842 __m256i low_bits = _mm256_and_si256(data, low_bits_mask);
843 __m256i low_count = _mm256_shuffle_epi8(lookup_popcount_4, low_bits);
844
845 // Count bits upper half
846 __m256i high_bits = _mm256_srli_epi16(data, 4);
847 high_bits = _mm256_and_si256(high_bits, low_bits_mask);
848 __m256i high_count = _mm256_shuffle_epi8(lookup_popcount_4, high_bits);
849
850 __m256i result_vec = _mm256_add_epi8(low_count, high_count);
851 _mm256_storeu_si256((__m256i*)result, result_vec);
852#else
853 // Fallback implementation for non-AVX2 platforms
854 for (size_t i = 0; i < 32; i++) {
855 result[i] = std::popcount(x[i]);
856 }
857#endif
858#endif
859}
860
861#ifdef PIXIE_AVX2_SUPPORT
862// clang-format off
863// LUT for total excess change across a 4-bit nibble
864static inline const __m256i excess_lut_delta = _mm256_setr_epi8(
865 -4, -2, -2, 0,
866 -2, 0, 0, 2,
867 -2, 0, 0, 2,
868 0, 2, 2, 4,
869 -4, -2, -2, 0,
870 -2, 0, 0, 2,
871 -2, 0, 0, 2,
872 0, 2, 2, 4);
873
874// LUTs for target relative excess positions
875static inline const __m256i excess_lut_pos0 = _mm256_setr_epi8(
876 -1, 1, -1, 1,
877 -1, 1, -1, 1,
878 -1, 1, -1, 1,
879 -1, 1, -1, 1,
880 -1, 1, -1, 1,
881 -1, 1, -1, 1,
882 -1, 1, -1, 1,
883 -1, 1, -1, 1);
884
885static inline const __m256i excess_lut_pos1 = _mm256_setr_epi8(
886 -2, 0, 0, 2,
887 -2, 0, 0, 2,
888 -2, 0, 0, 2,
889 -2, 0, 0, 2,
890 -2, 0, 0, 2,
891 -2, 0, 0, 2,
892 -2, 0, 0, 2,
893 -2, 0, 0, 2);
894
895static inline const __m256i excess_lut_pos2 = _mm256_setr_epi8(
896 -3, -1, -1, 1,
897 -1, 1, 1, 3,
898 -3, -1, -1, 1,
899 -1, 1, 1, 3,
900 -3, -1, -1, 1,
901 -1, 1, 1, 3,
902 -3, -1, -1, 1,
903 -1, 1, 1, 3);
904static inline const __m256i excess_lut_min = _mm256_setr_epi8(
905 -4, -2, -2, 0,
906 -2, 0, -1, 1,
907 -3, -1, -1, 1,
908 -2, 0, -1, 1,
909 -4, -2, -2, 0,
910 -2, 0, -1, 1,
911 -3, -1, -1, 1,
912 -2, 0, -1, 1);
913static inline constexpr int8_t excess_lut_min_offset[16] = {
914 4, 4, 4, 4, 2, 2, 1, 1, 3, 3, 1, 1, 2, 2, 1, 1};
915static inline const __m256i excess_lut_pack_multiplier =
916 _mm256_set1_epi16(0x1001);
917static inline const __m256i excess_lut_bit0 = _mm256_set1_epi8(1);
918static inline const __m256i excess_lut_bit1 = _mm256_set1_epi8(2);
919static inline const __m256i excess_lut_bit2 = _mm256_set1_epi8(4);
920static inline const __m256i excess_lut_bit3 = _mm256_set1_epi8(8);
921static inline const __m256i excess_lut_nibble_index = _mm256_setr_epi8(
922 0, 1, 2, 3,
923 4, 5, 6, 7,
924 8, 9, 10, 11,
925 12, 13, 14, 15,
926 16, 17, 18, 19,
927 20, 21, 22, 23,
928 24, 25, 26, 27,
929 28, 29, 30, 31);
930static inline const __m128i excess_lut_nibble_mask = _mm_set1_epi8(0x0F);
931// clang-format on
932
933static inline __m256i excess_nibbles_128_avx2(const uint64_t* s) noexcept {
934 __m128i word_vec = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
935 __m128i lo_nibbles = _mm_and_si128(word_vec, excess_lut_nibble_mask);
936 __m128i hi_nibbles =
937 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask);
938
939 __m128i unpack_lo = _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
940 __m128i unpack_hi = _mm_unpackhi_epi8(lo_nibbles, hi_nibbles);
941
942 return _mm256_inserti128_si256(_mm256_castsi128_si256(unpack_lo), unpack_hi,
943 1);
944}
945
946static inline __m256i excess_bit_masks_16x_i16() noexcept {
947 return _mm256_setr_epi16(0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
948 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
949 0x1000, 0x2000, 0x4000,
950 static_cast<int16_t>(0x8000));
951}
952
953static inline __m256i excess_prefix_sum_16x_i16(__m256i v) noexcept {
954 __m256i x = v;
955 __m256i t = _mm256_slli_si256(x, 2);
956 x = _mm256_add_epi16(x, t);
957 t = _mm256_slli_si256(x, 4);
958 x = _mm256_add_epi16(x, t);
959 t = _mm256_slli_si256(x, 8);
960 x = _mm256_add_epi16(x, t);
961
962 __m128i lo = _mm256_extracti128_si256(x, 0);
963 __m128i hi = _mm256_extracti128_si256(x, 1);
964 const int16_t carry = static_cast<int16_t>(_mm_extract_epi16(lo, 7));
965 hi = _mm_add_epi16(hi, _mm_set1_epi16(carry));
966
967 __m256i out = _mm256_castsi128_si256(lo);
968 return _mm256_inserti128_si256(out, hi, 1);
969}
970#endif
971
981 int min_excess = 0;
982 size_t offset = 128;
983};
984
992 ExcessResult suffix;
993 ExcessResult prefix;
994};
995
996constexpr int8_t excess_byte_delta_value(uint8_t x) {
997 return static_cast<int8_t>(2 * std::popcount(x) - 8);
998}
999
1000constexpr int8_t excess_byte_min_prefix_value(uint8_t x) {
1001 int cur = 0;
1002 int best = 0;
1003 for (int bit = 0; bit < 8; ++bit) {
1004 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1005 if (bit == 0 || cur < best) {
1006 best = cur;
1007 }
1008 }
1009 return static_cast<int8_t>(best);
1010}
1011
1012constexpr int8_t excess_byte_min_prefix_offset_value(uint8_t x) {
1013 int cur = 0;
1014 int best = 0;
1015 int best_offset = 1;
1016 for (int bit = 0; bit < 8; ++bit) {
1017 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1018 if (bit == 0 || cur < best) {
1019 best = cur;
1020 best_offset = bit + 1;
1021 }
1022 }
1023 return static_cast<int8_t>(best_offset);
1024}
1025
1026constexpr int8_t excess_nibble_min_prefix_offset_value(uint8_t x, int bits) {
1027 int cur = 0;
1028 int best = 0;
1029 int best_offset = 1;
1030 for (int bit = 0; bit < bits; ++bit) {
1031 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
1032 if (bit == 0 || cur < best) {
1033 best = cur;
1034 best_offset = bit + 1;
1035 }
1036 }
1037 return static_cast<int8_t>(best_offset);
1038}
1039
1040template <typename Fn>
1041constexpr std::array<int8_t, 256> excess_make_byte_lut(Fn fn) {
1042 std::array<int8_t, 256> out{};
1043 for (size_t i = 0; i < out.size(); ++i) {
1044 out[i] = fn(static_cast<uint8_t>(i));
1045 }
1046 return out;
1047}
1048
1049static inline constexpr std::array<int8_t, 256> excess_byte_delta_lut =
1050 excess_make_byte_lut([](uint8_t x) { return excess_byte_delta_value(x); });
1051static inline constexpr std::array<int8_t, 256> excess_byte_min_lut =
1052 excess_make_byte_lut(
1053 [](uint8_t x) { return excess_byte_min_prefix_value(x); });
1054static inline constexpr std::array<int8_t, 256> excess_byte_min_offset_lut =
1055 excess_make_byte_lut(
1056 [](uint8_t x) { return excess_byte_min_prefix_offset_value(x); });
1057static inline constexpr std::array<std::array<int8_t, 16>, 4>
1058 excess_partial_nibble_min_offset_lut = [] {
1059 std::array<std::array<int8_t, 16>, 4> out{};
1060 for (size_t width = 1; width < out.size(); ++width) {
1061 for (size_t nibble = 0; nibble < out[width].size(); ++nibble) {
1062 out[width][nibble] = excess_nibble_min_prefix_offset_value(
1063 static_cast<uint8_t>(nibble), static_cast<int>(width));
1064 }
1065 }
1066 return out;
1067 }();
1068
1076constexpr uint8_t excess_byte_record_lows_mask(uint8_t byte, int threshold) {
1077 int cur = 0;
1078 uint8_t mask = 0;
1079 for (int bit = 0; bit < 8; ++bit) {
1080 cur += ((byte >> bit) & 1u) ? 1 : -1;
1081 if (cur < threshold) {
1082 mask |= static_cast<uint8_t>(1u << bit);
1083 }
1084 }
1085 return mask;
1086}
1087
1096static inline constexpr std::array<std::array<uint8_t, 8>, 256>
1097 excess_byte_record_lows_lut = [] {
1098 std::array<std::array<uint8_t, 8>, 256> out{};
1099 for (size_t byte = 0; byte < 256; ++byte) {
1100 for (int g = 0; g < 8; ++g) {
1101 out[byte][g] =
1102 excess_byte_record_lows_mask(static_cast<uint8_t>(byte), -g);
1103 }
1104 }
1105 return out;
1106 }();
1107
1121static inline int excess_positions_128(const uint64_t* s,
1122 int target_x,
1123 uint64_t* out) noexcept {
1124 out[0] = out[1] = 0;
1125 const int block_delta = 2 * (std::popcount(s[0]) + std::popcount(s[1])) - 128;
1126
1127 if (target_x < -128 || target_x > 128) {
1128 return block_delta;
1129 }
1130
1131#ifdef PIXIE_AVX2_SUPPORT
1132 const __m256i vdelta = excess_lut_delta;
1133 const __m256i vpos0 = excess_lut_pos0;
1134 const __m256i vpos1 = excess_lut_pos1;
1135 const __m256i vpos2 = excess_lut_pos2;
1136 const __m256i vmult = excess_lut_pack_multiplier;
1137 const __m256i vbit0 = excess_lut_bit0;
1138 const __m256i vbit1 = excess_lut_bit1;
1139 const __m256i vbit2 = excess_lut_bit2;
1140 const __m256i vbit3 = excess_lut_bit3;
1141
1142 const int d = 2 * target_x - block_delta;
1143 if (d < -128 || d > 128) {
1144 return block_delta;
1145 }
1146
1147 __m256i nibbles = excess_nibbles_128_avx2(s);
1148
1149 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
1150 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1151 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1152 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1153 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1154
1155 __m128i ps_lo = _mm256_castsi256_si128(ps);
1156 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1157 __m128i carry = _mm_set1_epi8((int8_t)_mm_extract_epi8(ps_lo, 15));
1158 ps_hi = _mm_add_epi8(ps_hi, carry);
1159 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1160
1161 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1162 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1163
1164 __m256i vtgt = _mm256_set1_epi8((int8_t)target_x);
1165 __m256i t = _mm256_sub_epi8(vtgt, excl_ps);
1166
1167 __m256i cmp0 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos0, nibbles), t);
1168 __m256i cmp1 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos1, nibbles), t);
1169 __m256i cmp2 = _mm256_cmpeq_epi8(_mm256_shuffle_epi8(vpos2, nibbles), t);
1170 __m256i cmp3 = _mm256_cmpeq_epi8(ps, vtgt);
1171
1172 __m256i bit0 = _mm256_and_si256(cmp0, vbit0);
1173 __m256i bit1 = _mm256_and_si256(cmp1, vbit1);
1174 __m256i bit2 = _mm256_and_si256(cmp2, vbit2);
1175 __m256i bit3 = _mm256_and_si256(cmp3, vbit3);
1176
1177 __m256i total_match =
1178 _mm256_or_si256(_mm256_or_si256(bit0, bit1), _mm256_or_si256(bit2, bit3));
1179
1180 __m256i res = _mm256_maddubs_epi16(total_match, vmult);
1181 __m128i res_lo = _mm256_castsi256_si128(res);
1182 __m128i res_hi = _mm256_extracti128_si256(res, 1);
1183 __m128i packed = _mm_packus_epi16(res_lo, res_hi);
1184
1185 _mm_storeu_si128((__m128i*)out, packed);
1186#else
1187 int cur = 0;
1188 for (size_t i = 0; i < 128; ++i) {
1189 const uint64_t w = s[i >> 6];
1190 const int bit = int((w >> (i & 63)) & 1ull);
1191 cur += bit ? +1 : -1;
1192 if (cur == target_x) {
1193 out[i >> 6] |= (uint64_t{1} << (i & 63));
1194 }
1195 }
1196#endif
1197 return block_delta;
1198}
1199
1209static inline int prefix_excess_128(const uint64_t* s,
1210 size_t end_offset) noexcept {
1211 end_offset = end_offset > 128 ? 128 : end_offset;
1212 if (end_offset == 0) {
1213 return 0;
1214 }
1215 if (end_offset <= 64) {
1216 const int ones = static_cast<int>(std::popcount(
1217 s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1218 return 2 * ones - static_cast<int>(end_offset);
1219 }
1220 const int ones = static_cast<int>(
1221 std::popcount(s[0]) +
1222 std::popcount(s[1] &
1223 first_bits_mask(static_cast<uint32_t>(end_offset - 64))));
1224 return 2 * ones - static_cast<int>(end_offset);
1225}
1226
1236static inline int prefix_excess_64(const uint64_t* s,
1237 size_t end_offset) noexcept {
1238 end_offset = end_offset > 64 ? 64 : end_offset;
1239 if (end_offset == 0) {
1240 return 0;
1241 }
1242 const int ones = static_cast<int>(
1243 std::popcount(s[0] & first_bits_mask(static_cast<uint32_t>(end_offset))));
1244 return 2 * ones - static_cast<int>(end_offset);
1245}
1246
1247static inline ExcessResult excess_min_128_byte_lut_short(
1248 const uint64_t* s,
1249 size_t left,
1250 size_t right) noexcept {
1251 int best = prefix_excess_128(s, left);
1252 size_t best_offset = left;
1253 if (left == right) {
1254 return {best, best_offset};
1255 }
1256
1257 int current = best;
1258 size_t bit = left;
1259 for (; bit < right && (bit & 7u) != 0; ++bit) {
1260 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1261 const size_t offset = bit + 1;
1262 if (current < best) {
1263 best = current;
1264 best_offset = offset;
1265 }
1266 }
1267
1268 for (; bit + 8 <= right; bit += 8) {
1269 const uint8_t byte =
1270 static_cast<uint8_t>((s[bit >> 6] >> (bit & 63)) & 0xFFu);
1271 const int candidate = current + excess_byte_min_lut[byte];
1272 if (candidate < best) {
1273 best = candidate;
1274 best_offset = bit + static_cast<size_t>(excess_byte_min_offset_lut[byte]);
1275 }
1276 current += excess_byte_delta_lut[byte];
1277 }
1278
1279 for (; bit < right; ++bit) {
1280 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1281 const size_t offset = bit + 1;
1282 if (current < best) {
1283 best = current;
1284 best_offset = offset;
1285 }
1286 }
1287
1288 return {best, best_offset};
1289}
1290
1305static inline ExcessResult excess_min_64(const uint64_t* s,
1306 size_t left,
1307 size_t right) noexcept {
1308 if (left > right) {
1309 return {};
1310 }
1311 left = std::min<size_t>(left, 64);
1312 right = std::min<size_t>(right, 64);
1313
1314 int best = prefix_excess_64(s, left);
1315 size_t best_offset = left;
1316 if (left == right) {
1317 return {best, best_offset};
1318 }
1319
1320#ifdef PIXIE_SSE41_SUPPORT
1321 int current = best;
1322 size_t bit = left;
1323 for (; bit < right && (bit & 3u) != 0; ++bit) {
1324 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1325 const size_t offset = bit + 1;
1326 if (current < best) {
1327 best = current;
1328 best_offset = offset;
1329 }
1330 }
1331
1332 const size_t first_full_nibble = bit >> 2;
1333 const size_t last_full_nibble = right >> 2;
1334 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1335 const size_t end_nibble =
1336 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1337 if (first_full_nibble < end_nibble) {
1338 const __m128i nibbles = excess_nibbles_64_sse(s);
1339
1340 __m128i ps = _mm_shuffle_epi8(excess_lut_delta_sse, nibbles);
1341 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 1));
1342 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 2));
1343 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 4));
1344 ps = _mm_add_epi8(ps, _mm_slli_si128(ps, 8));
1345
1346 const __m128i excl_ps = _mm_slli_si128(ps, 1);
1347 __m128i local_min = _mm_shuffle_epi8(excess_lut_min_sse, nibbles);
1348 if (right_partial_width != 0) {
1349 __m128i partial_min = _mm_shuffle_epi8(excess_lut_pos0_sse, nibbles);
1350 if (right_partial_width >= 2) {
1351 partial_min = _mm_min_epi8(
1352 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, nibbles));
1353 }
1354 if (right_partial_width >= 3) {
1355 partial_min = _mm_min_epi8(
1356 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, nibbles));
1357 }
1358 local_min = _mm_blendv_epi8(
1359 local_min, partial_min,
1360 _mm_cmpeq_epi8(excess_lut_nibble_index_sse,
1361 _mm_set1_epi8(static_cast<int8_t>(last_full_nibble))));
1362 }
1363 const __m128i partial_candidates = _mm_add_epi8(excl_ps, local_min);
1364
1365 const __m128i idx = excess_lut_nibble_index_sse;
1366 const int first_minus_one_value = static_cast<int>(first_full_nibble) - 1;
1367 const __m128i first_minus_one =
1368 _mm_set1_epi8(static_cast<int8_t>(first_minus_one_value));
1369 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1370 const __m128i active = _mm_and_si128(_mm_cmpgt_epi8(idx, first_minus_one),
1371 _mm_cmpgt_epi8(last, idx));
1372 const __m128i masked_candidates =
1373 _mm_blendv_epi8(_mm_set1_epi8(127), partial_candidates, active);
1374
1375 __m128i min128 = masked_candidates;
1376 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1377 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1378 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1379 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1380
1381 const int candidate_min =
1382 static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1383 if (candidate_min < best) {
1384 const __m128i equal_min = _mm_cmpeq_epi8(
1385 masked_candidates, _mm_set1_epi8(static_cast<int8_t>(candidate_min)));
1386 const uint32_t equal_mask =
1387 static_cast<uint32_t>(_mm_movemask_epi8(equal_min));
1388 const uint32_t nibble_index = std::countr_zero(equal_mask);
1389 const uint8_t nibble =
1390 static_cast<uint8_t>((s[0] >> (nibble_index * 4u)) & 0xFu);
1391 best = candidate_min;
1392 if (right_partial_width != 0 && nibble_index == last_full_nibble) {
1393 int local = 0;
1394 int local_best = 0;
1395 size_t local_offset = 1;
1396 for (size_t i = 0; i < right_partial_width; ++i) {
1397 local += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1398 if (i == 0 || local < local_best) {
1399 local_best = local;
1400 local_offset = i + 1;
1401 }
1402 }
1403 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1404 } else {
1405 best_offset = static_cast<size_t>(nibble_index) * 4u +
1406 static_cast<size_t>(excess_nibble_min_offset[nibble]);
1407 }
1408 }
1409
1410 bit = end_nibble * 4;
1411 }
1412
1413 for (; bit < right; ++bit) {
1414 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1415 const size_t offset = bit + 1;
1416 if (current < best) {
1417 best = current;
1418 best_offset = offset;
1419 }
1420 }
1421#else
1422 int current = best;
1423 for (size_t bit = left; bit < right; ++bit) {
1424 current += ((s[0] >> bit) & 1ull) != 0 ? 1 : -1;
1425 const size_t offset = bit + 1;
1426 if (current < best) {
1427 best = current;
1428 best_offset = offset;
1429 }
1430 }
1431#endif
1432
1433 return {best, best_offset};
1434}
1435
1442static inline ExcessResult excess_min_128(const uint64_t* s,
1443 size_t left,
1444 size_t right) noexcept {
1445 if (left > right) {
1446 return {};
1447 }
1448 left = std::min<size_t>(left, 128);
1449 right = std::min<size_t>(right, 128);
1450
1451 if (right - left <= 32 && (left & 7u) == 0 && (right & 7u) == 0)
1452 [[unlikely]] {
1453 return excess_min_128_byte_lut_short(s, left, right);
1454 }
1455
1456 int best = prefix_excess_128(s, left);
1457 size_t best_offset = left;
1458 if (left == right) {
1459 return {best, best_offset};
1460 }
1461
1462#ifdef PIXIE_AVX2_SUPPORT
1463 int current = best;
1464 size_t bit = left;
1465 for (; bit < right && (bit & 3u) != 0; ++bit) {
1466 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1467 const size_t offset = bit + 1;
1468 if (current < best) {
1469 best = current;
1470 best_offset = offset;
1471 }
1472 }
1473
1474 const size_t first_nibble = bit >> 2;
1475 const size_t last_full_nibble = right >> 2;
1476 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
1477 const size_t end_nibble =
1478 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
1479 if (first_nibble < end_nibble) {
1480 const __m128i bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
1481 const __m128i lo_nibbles = _mm_and_si128(bytes, excess_lut_nibble_mask_sse);
1482 const __m128i hi_nibbles =
1483 _mm_and_si128(_mm_srli_epi16(bytes, 4), excess_lut_nibble_mask_sse);
1484 const __m128i lo_delta = _mm_shuffle_epi8(excess_lut_delta_sse, lo_nibbles);
1485 const __m128i hi_delta = _mm_shuffle_epi8(excess_lut_delta_sse, hi_nibbles);
1486 const __m128i byte_delta = _mm_add_epi8(lo_delta, hi_delta);
1487 const __m128i byte_prefix = excess_prefix_sum_16x_i8(byte_delta);
1488 const __m128i byte_prefix_before = _mm_slli_si128(byte_prefix, 1);
1489
1490 __m128i lo_local_min = _mm_shuffle_epi8(excess_lut_min_sse, lo_nibbles);
1491 __m128i hi_local_min = _mm_shuffle_epi8(excess_lut_min_sse, hi_nibbles);
1492
1493 const __m128i byte_index = excess_lut_nibble_index_sse;
1494 if (right_partial_width != 0) {
1495 const bool partial_is_high = (last_full_nibble & 1u) != 0;
1496 const size_t partial_byte = last_full_nibble >> 1;
1497 const __m128i partial_source = partial_is_high ? hi_nibbles : lo_nibbles;
1498 __m128i partial_min =
1499 _mm_shuffle_epi8(excess_lut_pos0_sse, partial_source);
1500 if (right_partial_width >= 2) {
1501 partial_min = _mm_min_epi8(
1502 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, partial_source));
1503 }
1504 if (right_partial_width >= 3) {
1505 partial_min = _mm_min_epi8(
1506 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, partial_source));
1507 }
1508 const __m128i partial_lane = _mm_cmpeq_epi8(
1509 byte_index, _mm_set1_epi8(static_cast<int8_t>(partial_byte)));
1510 if (partial_is_high) {
1511 hi_local_min = _mm_blendv_epi8(hi_local_min, partial_min, partial_lane);
1512 } else {
1513 lo_local_min = _mm_blendv_epi8(lo_local_min, partial_min, partial_lane);
1514 }
1515 }
1516
1517 const __m128i lo_candidates =
1518 _mm_add_epi8(byte_prefix_before, lo_local_min);
1519 const __m128i hi_candidates =
1520 _mm_add_epi8(_mm_add_epi8(byte_prefix_before, lo_delta), hi_local_min);
1521
1522 __m128i masked_lo = lo_candidates;
1523 __m128i masked_hi = hi_candidates;
1524 if (first_nibble != 0 || end_nibble != 32) {
1525 const __m128i first_minus_one = _mm_set1_epi8(
1526 static_cast<int8_t>(static_cast<int>(first_nibble) - 1));
1527 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
1528 const __m128i lo_active = _mm_and_si128(
1529 _mm_cmpgt_epi8(excess_lut_low_nibble_index_sse, first_minus_one),
1530 _mm_cmpgt_epi8(last, excess_lut_low_nibble_index_sse));
1531 const __m128i hi_active = _mm_and_si128(
1532 _mm_cmpgt_epi8(excess_lut_high_nibble_index_sse, first_minus_one),
1533 _mm_cmpgt_epi8(last, excess_lut_high_nibble_index_sse));
1534 masked_lo = _mm_blendv_epi8(_mm_set1_epi8(127), lo_candidates, lo_active);
1535 masked_hi = _mm_blendv_epi8(_mm_set1_epi8(127), hi_candidates, hi_active);
1536 }
1537
1538 const int candidate_min =
1539 excess_horizontal_min_i8(_mm_min_epi8(masked_lo, masked_hi));
1540 if (candidate_min < best) {
1541 const __m128i min_vec = _mm_set1_epi8(static_cast<int8_t>(candidate_min));
1542 const uint32_t lo_equal_mask = static_cast<uint32_t>(
1543 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_lo, min_vec)));
1544 const uint32_t hi_equal_mask = static_cast<uint32_t>(
1545 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_hi, min_vec)));
1546 const uint32_t lo_nibble_index =
1547 lo_equal_mask == 0
1548 ? 32u
1549 : static_cast<uint32_t>(std::countr_zero(lo_equal_mask)) * 2u;
1550 const uint32_t hi_nibble_index =
1551 hi_equal_mask == 0
1552 ? 32u
1553 : static_cast<uint32_t>(std::countr_zero(hi_equal_mask)) * 2u +
1554 1u;
1555 const uint32_t nibble_index = std::min(lo_nibble_index, hi_nibble_index);
1556 const uint32_t byte_offset = nibble_index >> 1u;
1557 const uint64_t byte_word = s[byte_offset >> 3u];
1558 const uint8_t byte = static_cast<uint8_t>(
1559 (byte_word >> ((byte_offset & 7u) * 8u)) & 0xFFu);
1560 const uint8_t nibble = (nibble_index & 1u) == 0
1561 ? static_cast<uint8_t>(byte & 0xFu)
1562 : static_cast<uint8_t>((byte >> 4u) & 0xFu);
1563 const size_t local_offset =
1564 right_partial_width != 0 && nibble_index == last_full_nibble
1565 ? static_cast<size_t>(
1566 excess_partial_nibble_min_offset_lut[right_partial_width]
1567 [nibble])
1568 : static_cast<size_t>(excess_lut_min_offset[nibble]);
1569 best = candidate_min;
1570 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
1571 }
1572 }
1573#else
1574 int current = 0;
1575 for (size_t bit = 0; bit < right; ++bit) {
1576 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
1577 const size_t offset = bit + 1;
1578 if (offset >= left && current < best) {
1579 best = current;
1580 best_offset = offset;
1581 }
1582 }
1583#endif
1584
1585 return {best, best_offset};
1586}
1587
1601static inline ExcessBoundaryPairResult excess_min_64_disjoint_suffix_prefix(
1602 const uint64_t* suffix_s,
1603 size_t suffix_left,
1604 const uint64_t* prefix_s,
1605 size_t prefix_right) noexcept {
1606 suffix_left = std::min<size_t>(suffix_left, 64);
1607 prefix_right = std::min<size_t>(prefix_right, 64);
1608 return {excess_min_64(suffix_s, suffix_left, 63),
1609 excess_min_64(prefix_s, 0, prefix_right)};
1610}
1611
1630static inline ExcessBoundaryPairResult excess_min_128_disjoint_suffix_prefix(
1631 const uint64_t* suffix_s,
1632 size_t suffix_left,
1633 const uint64_t* prefix_s,
1634 size_t prefix_right) noexcept {
1635 suffix_left = std::min<size_t>(suffix_left, 128);
1636 prefix_right = std::min<size_t>(prefix_right, 128);
1637 if (suffix_left <= prefix_right || suffix_left > 127 || prefix_right > 127) {
1638 return {excess_min_128(suffix_s, suffix_left, 127),
1639 excess_min_128(prefix_s, 0, prefix_right)};
1640 }
1641
1642#ifdef PIXIE_AVX2_SUPPORT
1643 ExcessResult prefix{0, 0};
1644
1645 int suffix_best = prefix_excess_128(suffix_s, suffix_left);
1646 ExcessResult suffix{suffix_best, suffix_left};
1647 size_t suffix_bit = suffix_left;
1648 int suffix_current = suffix_best;
1649 for (; suffix_bit < 127 && (suffix_bit & 3u) != 0; ++suffix_bit) {
1650 suffix_current +=
1651 ((suffix_s[suffix_bit >> 6] >> (suffix_bit & 63)) & 1ull) != 0 ? 1 : -1;
1652 const size_t offset = suffix_bit + 1;
1653 if (suffix_current < suffix.min_excess) {
1654 suffix = {suffix_current, offset};
1655 }
1656 }
1657
1658 const size_t prefix_last_nibble = prefix_right >> 2;
1659 const size_t prefix_partial_width = prefix_right & 3u;
1660 const size_t prefix_end_nibble =
1661 prefix_last_nibble + (prefix_partial_width == 0 ? 0 : 1);
1662 const size_t suffix_first_nibble = suffix_bit < 127 ? suffix_bit >> 2 : 32;
1663 const int prefix_artificial_delta =
1664 prefix_excess_128(prefix_s, prefix_end_nibble * 4);
1665
1666 const __m256i idx = excess_lut_nibble_index;
1667 const __m256i prefix_active = _mm256_cmpgt_epi8(
1668 _mm256_set1_epi8(static_cast<int8_t>(prefix_end_nibble)), idx);
1669 const __m256i suffix_active = _mm256_cmpgt_epi8(
1670 idx, _mm256_set1_epi8(static_cast<int8_t>(suffix_first_nibble) - 1));
1671
1672 const __m256i prefix_nibbles = excess_nibbles_128_avx2(prefix_s);
1673 const __m256i suffix_nibbles = excess_nibbles_128_avx2(suffix_s);
1674 __m256i nibbles =
1675 _mm256_blendv_epi8(_mm256_set1_epi8(3), prefix_nibbles, prefix_active);
1676 nibbles = _mm256_blendv_epi8(nibbles, suffix_nibbles, suffix_active);
1677
1678 __m256i ps = _mm256_shuffle_epi8(excess_lut_delta, nibbles);
1679 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1680 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1681 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1682 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1683
1684 __m128i ps_lo = _mm256_castsi256_si128(ps);
1685 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1686 __m128i carry =
1687 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
1688 ps_hi = _mm_add_epi8(ps_hi, carry);
1689 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1690
1691 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1692 const __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1693
1694 __m256i local_min = _mm256_shuffle_epi8(excess_lut_min, nibbles);
1695 if (prefix_partial_width != 0) {
1696 __m256i partial_min = _mm256_shuffle_epi8(excess_lut_pos0, nibbles);
1697 if (prefix_partial_width >= 2) {
1698 partial_min = _mm256_min_epi8(
1699 partial_min, _mm256_shuffle_epi8(excess_lut_pos1, nibbles));
1700 }
1701 if (prefix_partial_width >= 3) {
1702 partial_min = _mm256_min_epi8(
1703 partial_min, _mm256_shuffle_epi8(excess_lut_pos2, nibbles));
1704 }
1705 local_min = _mm256_blendv_epi8(
1706 local_min, partial_min,
1707 _mm256_cmpeq_epi8(
1708 idx, _mm256_set1_epi8(static_cast<int8_t>(prefix_last_nibble))));
1709 }
1710 __m256i suffix_partial_min = _mm256_min_epi8(
1711 _mm256_shuffle_epi8(excess_lut_pos0, nibbles),
1712 _mm256_min_epi8(_mm256_shuffle_epi8(excess_lut_pos1, nibbles),
1713 _mm256_shuffle_epi8(excess_lut_pos2, nibbles)));
1714 const __m256i suffix_tail_active = _mm256_and_si256(
1715 suffix_active, _mm256_cmpeq_epi8(idx, _mm256_set1_epi8(31)));
1716 local_min =
1717 _mm256_blendv_epi8(local_min, suffix_partial_min, suffix_tail_active);
1718
1719 const __m256i base_candidates = _mm256_add_epi8(excl_ps, local_min);
1720 const __m256i sentinel = _mm256_set1_epi8(127);
1721
1722 const __m256i prefix_candidates =
1723 _mm256_blendv_epi8(sentinel, base_candidates, prefix_active);
1724 const __m256i suffix_candidates = _mm256_blendv_epi8(
1725 sentinel,
1726 _mm256_add_epi8(base_candidates,
1727 _mm256_set1_epi8(static_cast<int8_t>(
1728 suffix_current - prefix_artificial_delta))),
1729 suffix_active);
1730
1731 auto reduce_min = [](__m256i values) {
1732 __m128i min128 = _mm_min_epi8(_mm256_castsi256_si128(values),
1733 _mm256_extracti128_si256(values, 1));
1734 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
1735 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
1736 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
1737 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
1738 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
1739 };
1740
1741 auto local_offset = [](uint8_t nibble, size_t width_value) {
1742 if (width_value == 0 || width_value == 4) {
1743 return static_cast<size_t>(excess_lut_min_offset[nibble]);
1744 }
1745 int current = 0;
1746 int best = 0;
1747 size_t best_offset = 1;
1748 for (size_t i = 0; i < width_value; ++i) {
1749 current += ((nibble >> i) & 1u) != 0 ? 1 : -1;
1750 if (i == 0 || current < best) {
1751 best = current;
1752 best_offset = i + 1;
1753 }
1754 }
1755 return best_offset;
1756 };
1757
1758 const int prefix_min = reduce_min(prefix_candidates);
1759 if (prefix_min < prefix.min_excess) {
1760 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1761 _mm256_cmpeq_epi8(prefix_candidates,
1762 _mm256_set1_epi8(static_cast<int8_t>(prefix_min)))));
1763 const uint32_t prefix_lane = std::countr_zero(mask);
1764 const uint64_t word = prefix_s[prefix_lane >> 4];
1765 const uint8_t nibble =
1766 static_cast<uint8_t>((word >> ((prefix_lane & 15u) * 4u)) & 0xFu);
1767 prefix.min_excess = prefix_min;
1768 const size_t width =
1769 prefix_partial_width != 0 && prefix_lane == prefix_last_nibble
1770 ? prefix_partial_width
1771 : 4;
1772 prefix.offset =
1773 static_cast<size_t>(prefix_lane) * 4u + local_offset(nibble, width);
1774 }
1775
1776 const int suffix_min = reduce_min(suffix_candidates);
1777 if (suffix_min < suffix.min_excess) {
1778 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(
1779 _mm256_cmpeq_epi8(suffix_candidates,
1780 _mm256_set1_epi8(static_cast<int8_t>(suffix_min)))));
1781 const uint32_t suffix_lane = std::countr_zero(mask);
1782 const uint64_t word = suffix_s[suffix_lane >> 4];
1783 const uint8_t nibble =
1784 static_cast<uint8_t>((word >> ((suffix_lane & 15u) * 4u)) & 0xFu);
1785 suffix.min_excess = suffix_min;
1786 const size_t width = suffix_lane == 31 ? 3 : 4;
1787 suffix.offset =
1788 static_cast<size_t>(suffix_lane) * 4u + local_offset(nibble, width);
1789 }
1790
1791 return {suffix, prefix};
1792#else
1793 return {excess_min_128(suffix_s, suffix_left, 127),
1794 excess_min_128(prefix_s, 0, prefix_right)};
1795#endif
1796}
1797
1813static inline size_t forward_search_128(const uint64_t* s,
1814 int target_x,
1815 size_t start_offset,
1816 int* block_excess = nullptr) noexcept {
1817 uint64_t out[2];
1818 const int delta = excess_positions_128(s, target_x, out);
1819 if (block_excess != nullptr) {
1820 *block_excess = delta;
1821 }
1822 if (start_offset >= 128) {
1823 return 128;
1824 }
1825
1826 const size_t first_word = start_offset >> 6;
1827 const size_t first_bit = start_offset & 63;
1828 for (size_t word = first_word; word < 2; ++word) {
1829 uint64_t mask = out[word];
1830 if (word == first_word && first_bit != 0) {
1831 mask &= ~first_bits_mask(first_bit);
1832 }
1833 if (mask != 0) {
1834 return word * 64 + std::countr_zero(mask);
1835 }
1836 }
1837 return 128;
1838}
1839
1857static inline size_t backward_search_128(const uint64_t* s,
1858 int target_x,
1859 size_t end_offset,
1860 int* block_excess = nullptr) noexcept {
1861 uint64_t out[2];
1862 const int delta = excess_positions_128(s, target_x, out);
1863 if (block_excess != nullptr) {
1864 *block_excess = delta;
1865 }
1866 if (end_offset == 0) {
1867 return 128;
1868 }
1869
1870 const size_t max_prefix_length = end_offset - 1;
1871 if (max_prefix_length > 0) {
1872 const size_t last_bit_index = max_prefix_length - 1;
1873 size_t word = last_bit_index >> 6;
1874 const size_t bit_in_word = last_bit_index & 63;
1875 uint64_t mask = out[word] & first_bits_mask(bit_in_word + 1);
1876 while (true) {
1877 if (mask != 0) {
1878 return word * 64 + (63 - std::countl_zero(mask)) + 1;
1879 }
1880 if (word == 0) {
1881 break;
1882 }
1883 --word;
1884 mask = out[word];
1885 }
1886 }
1887 return target_x == 0 ? 0 : 128;
1888}
1889
1902static inline void excess_positions_512(const uint64_t* s,
1903 int target_x,
1904 uint64_t* out) noexcept {
1905 if (target_x < -512 || target_x > 512) {
1906 out[0] = out[1] = out[2] = out[3] = 0;
1907 out[4] = out[5] = out[6] = out[7] = 0;
1908 return;
1909 }
1910
1911 for (int k = 0; k < 4; ++k) {
1912 target_x -= excess_positions_128(s + 2 * k, target_x, out + 2 * k);
1913 }
1914}
1915
1929static inline void excess_record_lows_128(const uint64_t* s,
1930 uint64_t* out) noexcept {
1931 out[0] = out[1] = 0;
1932 int cur = 0;
1933 int best = 0;
1934 for (size_t i = 0; i < 128; ++i) {
1935 const uint64_t w = s[i >> 6];
1936 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
1937 cur += bit ? +1 : -1;
1938 if (cur < best) {
1939 best = cur;
1940 out[i >> 6] |= (uint64_t{1} << (i & 63));
1941 }
1942 }
1943}
1944
1953static inline void excess_record_lows_128_byte_lut(const uint64_t* s,
1954 uint64_t* out) noexcept {
1955 out[0] = out[1] = 0;
1956 int cur = 0;
1957 int best = 0;
1958 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
1959 const size_t bit_base = byte_idx * 8;
1960 const uint8_t byte =
1961 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
1962 const int byte_min = excess_byte_min_lut[byte];
1963 if (cur + byte_min < best) {
1964 for (size_t i = 0; i < 8; ++i) {
1965 const int bit = static_cast<int>((byte >> i) & 1u);
1966 cur += bit ? +1 : -1;
1967 if (cur < best) {
1968 best = cur;
1969 const size_t pos = bit_base + i;
1970 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
1971 }
1972 }
1973 } else {
1974 cur += excess_byte_delta_lut[byte];
1975 }
1976 }
1977}
1978
1987static inline void excess_record_lows_128_lut(const uint64_t* s,
1988 uint64_t* out) noexcept {
1989 out[0] = out[1] = 0;
1990 int cur = 0;
1991 int best = 0;
1992 for (size_t byte_idx = 0; byte_idx < 16; ++byte_idx) {
1993 const size_t bit_base = byte_idx * 8;
1994 const uint8_t byte =
1995 static_cast<uint8_t>((s[bit_base >> 6] >> (bit_base & 63)) & 0xFFu);
1996 const int gap = cur - best;
1997 const int idx = gap > 7 ? 7 : (gap < 0 ? 0 : gap);
1998 const uint8_t mask =
1999 excess_byte_record_lows_lut[byte][static_cast<size_t>(idx)];
2000 if (mask != 0) {
2001 // Recompute absolute excesses for masked positions to update cur/best.
2002 int local = 0;
2003 int local_best = 0;
2004 uint8_t local_mask = 0;
2005 for (int bit = 0; bit < 8; ++bit) {
2006 local += ((byte >> bit) & 1u) ? 1 : -1;
2007 if (local < local_best) {
2008 local_best = local;
2009 local_mask |= static_cast<uint8_t>(1u << bit);
2010 }
2011 }
2012 // Only output positions whose absolute excess is < best.
2013 uint8_t out_mask = 0;
2014 local = 0;
2015 for (int bit = 0; bit < 8; ++bit) {
2016 local += ((byte >> bit) & 1u) ? 1 : -1;
2017 if (cur + local < best) {
2018 out_mask |= static_cast<uint8_t>(1u << bit);
2019 best = cur + local;
2020 }
2021 }
2022 if (out_mask != 0) {
2023 const uint64_t word = static_cast<uint64_t>(out_mask)
2024 << (bit_base & 63);
2025 out[bit_base >> 6] |= word;
2026 }
2027 }
2028 cur += excess_byte_delta_lut[byte];
2029 }
2030}
2031
2032#ifdef PIXIE_AVX2_SUPPORT
2040static inline void excess_record_lows_128_avx2(const uint64_t* s,
2041 uint64_t* out) noexcept {
2042 out[0] = out[1] = 0;
2043 int cur = 0;
2044 int best = 0;
2045
2046 const __m256i masks = excess_bit_masks_16x_i16();
2047 const __m256i zero = _mm256_setzero_si256();
2048 const __m256i pos = _mm256_set1_epi16(1);
2049 const __m256i neg = _mm256_set1_epi16(-1);
2050
2051 for (size_t chunk = 0; chunk < 8; ++chunk) {
2052 const size_t chunk_bit = chunk * 16;
2053 const uint16_t bits =
2054 chunk < 4
2055 ? static_cast<uint16_t>((s[0] >> (chunk * 16)) & 0xFFFFu)
2056 : static_cast<uint16_t>((s[1] >> ((chunk - 4) * 16)) & 0xFFFFu);
2057
2058 const __m256i vb = _mm256_set1_epi16(static_cast<int16_t>(bits));
2059 const __m256i m = _mm256_and_si256(vb, masks);
2060 const __m256i is_zero = _mm256_cmpeq_epi16(m, zero);
2061 const __m256i steps = _mm256_blendv_epi8(pos, neg, is_zero);
2062 const __m256i pref_rel = excess_prefix_sum_16x_i16(steps);
2063 const __m256i pref_abs = _mm256_add_epi16(
2064 pref_rel, _mm256_set1_epi16(static_cast<int16_t>(cur)));
2065
2066 alignas(32) int16_t vals[16];
2067 _mm256_store_si256(reinterpret_cast<__m256i*>(vals), pref_abs);
2068
2069 for (size_t lane = 0; lane < 16; ++lane) {
2070 const int val = vals[lane];
2071 if (val < best) {
2072 best = val;
2073 const size_t pos_idx = chunk_bit + lane;
2074 out[pos_idx >> 6] |= (uint64_t{1} << (pos_idx & 63));
2075 }
2076 }
2077
2078 cur += 2 * static_cast<int>(std::popcount(bits)) - 16;
2079 }
2080}
2081
2091static inline void excess_record_lows_128_nibble_lut(const uint64_t* s,
2092 uint64_t* out) noexcept {
2093 out[0] = out[1] = 0;
2094 int cur = 0;
2095 int best = 0;
2096
2097 const __m256i vdelta = excess_lut_delta;
2098 const __m256i vmin = excess_lut_min;
2099
2100 __m256i nibbles = excess_nibbles_128_avx2(s);
2101
2102 // Compute inclusive prefix sums of per-nibble total excess changes.
2103 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
2104 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
2105 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
2106 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
2107 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
2108
2109 __m128i ps_lo = _mm256_castsi256_si128(ps);
2110 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
2111 __m128i carry =
2112 _mm_set1_epi8(static_cast<int8_t>(_mm_extract_epi8(ps_lo, 15)));
2113 ps_hi = _mm_add_epi8(ps_hi, carry);
2114 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
2115
2116 // Exclusive prefix: shift in zero at start.
2117 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
2118 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
2119
2120 // Local minima relative to each nibble start.
2121 __m256i local_min = _mm256_shuffle_epi8(vmin, nibbles);
2122
2123 alignas(32) int8_t excl[32];
2124 _mm256_store_si256(reinterpret_cast<__m256i*>(excl), excl_ps);
2125
2126 alignas(32) int8_t nibble_min[32];
2127 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_min), local_min);
2128
2129 alignas(32) int8_t nibble_vals[32];
2130 _mm256_store_si256(reinterpret_cast<__m256i*>(nibble_vals), nibbles);
2131
2132 for (int n = 0; n < 32; ++n) {
2133 const int nibble_base = cur + excl[n];
2134 const int nibble_best = nibble_base + nibble_min[n];
2135 if (nibble_best < best) {
2136 // This nibble contains at least one record low — scan bit-by-bit.
2137 const uint8_t nibble = static_cast<uint8_t>(nibble_vals[n]);
2138 int local = 0;
2139 for (int bit = 0; bit < 4; ++bit) {
2140 local += ((nibble >> bit) & 1u) ? 1 : -1;
2141 const int val = nibble_base + local;
2142 if (val < best) {
2143 best = val;
2144 const size_t pos = static_cast<size_t>(n) * 4 + bit;
2145 out[pos >> 6] |= (uint64_t{1} << (pos & 63));
2146 }
2147 }
2148 }
2149 }
2150}
2151#endif
2152
2165static inline void excess_record_lows_512(const uint64_t* s,
2166 uint64_t* out) noexcept {
2167 out[0] = out[1] = out[2] = out[3] = 0;
2168 out[4] = out[5] = out[6] = out[7] = 0;
2169 int best = 0;
2170 int global = 0;
2171 for (int k = 0; k < 4; ++k) {
2172 const uint64_t* block = s + 2 * k;
2173 uint64_t* block_out = out + 2 * k;
2174 for (size_t i = 0; i < 128; ++i) {
2175 const uint64_t w = block[i >> 6];
2176 const int bit = static_cast<int>((w >> (i & 63)) & 1ull);
2177 global += bit ? +1 : -1;
2178 if (global < best) {
2179 best = global;
2180 block_out[i >> 6] |= (uint64_t{1} << (i & 63));
2181 }
2182 }
2183 }
2184}
2185
2197static inline void rank_32x8(const uint8_t* x, uint8_t* result) {
2198#ifdef PIXIE_AVX512_SUPPORT
2199 // Step 1: Calculate popcount of each byte
2200 popcount_32x8(x, result);
2201 __m256i prefix_sums = _mm256_loadu_si256((__m256i const*)result);
2202 const __m256i zero = _mm256_setzero_si256();
2203
2204 prefix_sums = _mm256_add_epi8(prefix_sums,
2205 _mm256_alignr_epi8(prefix_sums, zero, 16 - 1));
2206 prefix_sums = _mm256_add_epi8(prefix_sums,
2207 _mm256_alignr_epi8(prefix_sums, zero, 16 - 2));
2208 prefix_sums = _mm256_add_epi8(prefix_sums,
2209 _mm256_alignr_epi8(prefix_sums, zero, 16 - 4));
2210 prefix_sums = _mm256_add_epi8(prefix_sums,
2211 _mm256_alignr_epi8(prefix_sums, zero, 16 - 8));
2212
2213 // At this point we have prefix sums for two halfs, the last step is to
2214 // extract 16-th value and add it to the whole second half
2215 __m128i low_lane = _mm256_extracti128_si256(prefix_sums, 0);
2216 __m128i high_lane = _mm256_extracti128_si256(prefix_sums, 1);
2217 auto last_val_low = _mm_extract_epi8(low_lane, 15);
2218 __m128i add_to_high = _mm_set1_epi8(last_val_low);
2219 high_lane = _mm_add_epi8(high_lane, add_to_high);
2220 prefix_sums = _mm256_set_m128i(high_lane, low_lane);
2221 _mm256_storeu_epi8(result, prefix_sums);
2222#else
2223 // Scalar fallback implementation
2224 result[0] = std::popcount(x[0]);
2225 for (size_t i = 1; i < 32; ++i) {
2226 result[i] = std::popcount(x[i]) + result[i - 1];
2227 }
2228#endif
2229}
Pair of boundary minimum results for adjacent BP query blocks.
Definition bits.h:991
Minimum prefix excess in a 128-bit bitstring range.
Definition bits.h:980