Pixie
Loading...
Searching...
No Matches
excess.h
1#pragma once
2
3#include <pixie/bits.h>
4
5#include <algorithm>
6#include <array>
7#include <bit>
8#include <cstddef>
9#include <cstdint>
10
11// clang-format off
88// clang-format on
89
91
92namespace detail {
93
94constexpr int8_t nibble_delta(uint8_t x) {
95 return static_cast<int8_t>(2 * std::popcount(x) - 4);
96}
97
98constexpr int8_t byte_delta(uint8_t x) {
99 return static_cast<int8_t>(2 * std::popcount(x) - 8);
100}
101
102constexpr int8_t min_prefix(uint8_t x, int bits) {
103 int cur = 0;
104 int best = 0;
105 for (int bit = 0; bit < bits; ++bit) {
106 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
107 if (bit == 0 || cur < best) {
108 best = cur;
109 }
110 }
111 return static_cast<int8_t>(best);
112}
113
114constexpr int8_t min_prefix_offset(uint8_t x, int bits) {
115 int cur = 0;
116 int best = 0;
117 int best_offset = 1;
118 for (int bit = 0; bit < bits; ++bit) {
119 cur += ((x >> bit) & 1u) != 0 ? 1 : -1;
120 if (bit == 0 || cur < best) {
121 best = cur;
122 best_offset = bit + 1;
123 }
124 }
125 return static_cast<int8_t>(best_offset);
126}
127
128template <size_t N, typename Fn>
129constexpr std::array<int8_t, N> make_lut(Fn fn) {
130 std::array<int8_t, N> out{};
131 for (size_t i = 0; i < N; ++i) {
132 out[i] = fn(static_cast<uint8_t>(i));
133 }
134 return out;
135}
136
137static inline constexpr std::array<int8_t, 16> kNibbleDelta =
138 make_lut<16>([](uint8_t x) { return nibble_delta(x); });
139static inline constexpr std::array<int8_t, 16> kNibbleMin =
140 make_lut<16>([](uint8_t x) { return min_prefix(x, 4); });
141static inline constexpr std::array<int8_t, 16> kNibbleMinOffset =
142 make_lut<16>([](uint8_t x) { return min_prefix_offset(x, 4); });
143static inline constexpr std::array<std::array<int8_t, 16>, 4>
144 kPartialNibbleMinOffset = [] {
145 std::array<std::array<int8_t, 16>, 4> out{};
146 for (size_t width = 1; width < out.size(); ++width) {
147 out[width] = make_lut<16>(
148 [width](uint8_t x) { return min_prefix_offset(x, width); });
149 }
150 return out;
151 }();
152static inline constexpr std::array<int8_t, 256> kByteDelta =
153 make_lut<256>([](uint8_t x) { return byte_delta(x); });
154static inline constexpr std::array<int8_t, 256> kByteMin =
155 make_lut<256>([](uint8_t x) { return min_prefix(x, 8); });
156static inline constexpr std::array<int8_t, 256> kByteMinOffset =
157 make_lut<256>([](uint8_t x) { return min_prefix_offset(x, 8); });
158
159static inline void scan_bit(const uint64_t* s,
160 size_t bit,
161 int& current,
162 int& best,
163 size_t& best_offset) noexcept {
164 current += ((s[bit >> 6] >> (bit & 63)) & 1ull) != 0 ? 1 : -1;
165 const size_t offset = bit + 1;
166 if (current < best) {
167 best = current;
168 best_offset = offset;
169 }
170}
171
172} // namespace detail
173
186static inline ExcessResult excess_min_128_scalar_bits(const uint64_t* s,
187 size_t left,
188 size_t right) noexcept {
189 if (left > right) {
190 return {};
191 }
192 left = std::min<size_t>(left, 128);
193 right = std::min<size_t>(right, 128);
194
195 int best = prefix_excess_128(s, left);
196 size_t best_offset = left;
197 if (left == right) {
198 return {best, best_offset};
199 }
200
201 int current = best;
202 for (size_t bit = left; bit < right; ++bit) {
203 detail::scan_bit(s, bit, current, best, best_offset);
204 }
205 return {best, best_offset};
206}
207
223static inline ExcessResult excess_min_128_nibble_lut(const uint64_t* s,
224 size_t left,
225 size_t right) noexcept {
226 if (left > right) {
227 return {};
228 }
229 left = std::min<size_t>(left, 128);
230 right = std::min<size_t>(right, 128);
231
232 int best = prefix_excess_128(s, left);
233 size_t best_offset = left;
234 if (left == right) {
235 return {best, best_offset};
236 }
237
238 int current = best;
239 size_t bit = left;
240 for (; bit < right && (bit & 3u) != 0; ++bit) {
241 detail::scan_bit(s, bit, current, best, best_offset);
242 }
243
244 for (; bit + 4 <= right; bit += 4) {
245 const uint8_t nibble =
246 static_cast<uint8_t>((s[bit >> 6] >> (bit & 63)) & 0xFu);
247 const int candidate = current + detail::kNibbleMin[nibble];
248 if (candidate < best) {
249 best = candidate;
250 best_offset = bit + static_cast<size_t>(detail::kNibbleMinOffset[nibble]);
251 }
252 current += detail::kNibbleDelta[nibble];
253 }
254
255 for (; bit < right; ++bit) {
256 detail::scan_bit(s, bit, current, best, best_offset);
257 }
258 return {best, best_offset};
259}
260
276static inline ExcessResult excess_min_128_byte_lut(const uint64_t* s,
277 size_t left,
278 size_t right) noexcept {
279 if (left > right) {
280 return {};
281 }
282 left = std::min<size_t>(left, 128);
283 right = std::min<size_t>(right, 128);
284
285 int best = prefix_excess_128(s, left);
286 size_t best_offset = left;
287 if (left == right) {
288 return {best, best_offset};
289 }
290
291 int current = best;
292 size_t bit = left;
293 for (; bit < right && (bit & 7u) != 0; ++bit) {
294 detail::scan_bit(s, bit, current, best, best_offset);
295 }
296
297 for (; bit + 8 <= right; bit += 8) {
298 const uint8_t byte =
299 static_cast<uint8_t>((s[bit >> 6] >> (bit & 63)) & 0xFFu);
300 const int candidate = current + detail::kByteMin[byte];
301 if (candidate < best) {
302 best = candidate;
303 best_offset = bit + static_cast<size_t>(detail::kByteMinOffset[byte]);
304 }
305 current += detail::kByteDelta[byte];
306 }
307
308 for (; bit < right; ++bit) {
309 detail::scan_bit(s, bit, current, best, best_offset);
310 }
311 return {best, best_offset};
312}
313
328static inline ExcessResult excess_min_128_hybrid_lut(const uint64_t* s,
329 size_t left,
330 size_t right) noexcept {
331 if (left > right) {
332 return {};
333 }
334 const size_t clamped_left = std::min<size_t>(left, 128);
335 const size_t clamped_right = std::min<size_t>(right, 128);
336 const size_t width = clamped_right - clamped_left;
337
338 if (width <= 2) {
339 return excess_min_128_scalar_bits(s, left, right);
340 }
341 if (width <= 64 && (clamped_left & 7u) == 0 && (clamped_right & 7u) == 0) {
342 return excess_min_128_byte_lut(s, left, right);
343 }
344 if (width <= 32) {
345 return excess_min_128_nibble_lut(s, left, right);
346 }
347 return excess_min_128(s, left, right);
348}
349
350#ifdef PIXIE_AVX2_SUPPORT
351// clang-format off
352static inline const __m128i excess_lut_delta_128 = _mm_setr_epi8(
353 -4, -2, -2, 0,
354 -2, 0, 0, 2,
355 -2, 0, 0, 2,
356 0, 2, 2, 4);
357static inline const __m128i excess_lut_min_128 = _mm_setr_epi8(
358 -4, -2, -2, 0,
359 -2, 0, -1, 1,
360 -3, -1, -1, 1,
361 -2, 0, -1, 1);
362static inline const __m128i excess_lut_nibble_index_128 = _mm_setr_epi8(
363 0, 1, 2, 3,
364 4, 5, 6, 7,
365 8, 9, 10, 11,
366 12, 13, 14, 15);
367static inline const __m128i excess_lut_low_nibble_index_128 = _mm_setr_epi8(
368 0, 2, 4, 6,
369 8, 10, 12, 14,
370 16, 18, 20, 22,
371 24, 26, 28, 30);
372static inline const __m128i excess_lut_high_nibble_index_128 = _mm_setr_epi8(
373 1, 3, 5, 7,
374 9, 11, 13, 15,
375 17, 19, 21, 23,
376 25, 27, 29, 31);
377static inline const __m128i excess_lut_nibble_mask_128 = _mm_set1_epi8(0x0F);
378// clang-format on
379
380namespace detail {
381
382static inline __m128i excess_nibbles_64_sse(uint64_t word) noexcept {
383 const __m128i word_vec = _mm_cvtsi64_si128(static_cast<int64_t>(word));
384 const __m128i lo_nibbles =
385 _mm_and_si128(word_vec, excess_lut_nibble_mask_128);
386 const __m128i hi_nibbles =
387 _mm_and_si128(_mm_srli_epi16(word_vec, 4), excess_lut_nibble_mask_128);
388 return _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
389}
390
391static inline __m128i excess_prefix_sum_16x_i8(__m128i v) noexcept {
392 __m128i x = v;
393 __m128i t = _mm_slli_si128(x, 1);
394 x = _mm_add_epi8(x, t);
395 t = _mm_slli_si128(x, 2);
396 x = _mm_add_epi8(x, t);
397 t = _mm_slli_si128(x, 4);
398 x = _mm_add_epi8(x, t);
399 t = _mm_slli_si128(x, 8);
400 return _mm_add_epi8(x, t);
401}
402
403static inline int horizontal_min_i8(__m128i v) noexcept {
404 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 8));
405 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 4));
406 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 2));
407 v = _mm_min_epi8(v, _mm_alignr_epi8(v, v, 1));
408 return static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(v, 0)));
409}
410
411static inline void scan_full_nibbles_64_sse(uint64_t word,
412 int lane_base_excess,
413 size_t lane_base_offset,
414 size_t first_nibble,
415 size_t last_nibble,
416 int& best,
417 size_t& best_offset) noexcept {
418 if (first_nibble >= last_nibble) {
419 return;
420 }
421
422 const __m128i nibbles = excess_nibbles_64_sse(word);
423 __m128i ps =
424 excess_prefix_sum_16x_i8(_mm_shuffle_epi8(excess_lut_delta_128, nibbles));
425 const __m128i excl_ps = _mm_alignr_epi8(ps, _mm_setzero_si128(), 15);
426 const __m128i candidates = _mm_add_epi8(
427 _mm_add_epi8(_mm_set1_epi8(static_cast<int8_t>(lane_base_excess)),
428 excl_ps),
429 _mm_shuffle_epi8(excess_lut_min_128, nibbles));
430
431 const __m128i idx = excess_lut_nibble_index_128;
432 const __m128i first_minus_one =
433 _mm_set1_epi8(static_cast<int8_t>(static_cast<int>(first_nibble) - 1));
434 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(last_nibble));
435 const __m128i active = _mm_and_si128(_mm_cmpgt_epi8(idx, first_minus_one),
436 _mm_cmpgt_epi8(last, idx));
437 const __m128i masked_candidates =
438 _mm_blendv_epi8(_mm_set1_epi8(127), candidates, active);
439
440 __m128i min128 = masked_candidates;
441 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 8));
442 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 4));
443 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 2));
444 min128 = _mm_min_epi8(min128, _mm_alignr_epi8(min128, min128, 1));
445
446 const int candidate_min =
447 static_cast<int>(static_cast<int8_t>(_mm_extract_epi8(min128, 0)));
448 if (candidate_min < best) {
449 const __m128i equal_min = _mm_cmpeq_epi8(
450 masked_candidates, _mm_set1_epi8(static_cast<int8_t>(candidate_min)));
451 const uint32_t equal_mask =
452 static_cast<uint32_t>(_mm_movemask_epi8(equal_min));
453 const uint32_t nibble_index = std::countr_zero(equal_mask);
454 const uint8_t nibble =
455 static_cast<uint8_t>((word >> (nibble_index * 4u)) & 0xFu);
456 best = candidate_min;
457 best_offset = lane_base_offset + static_cast<size_t>(nibble_index) * 4u +
458 static_cast<size_t>(kNibbleMinOffset[nibble]);
459 }
460}
461
462static inline size_t partial_nibble_min_offset(uint8_t nibble,
463 size_t width) noexcept {
464 return static_cast<size_t>(kPartialNibbleMinOffset[width][nibble]);
465}
466
467static inline ExcessResult excess_min_128_split64_sse_impl(
468 const uint64_t* s,
469 size_t left,
470 size_t right) noexcept {
471 if (left > right) {
472 return {};
473 }
474 left = std::min<size_t>(left, 128);
475 right = std::min<size_t>(right, 128);
476
477 int best = prefix_excess_128(s, left);
478 size_t best_offset = left;
479 if (left == right) {
480 return {best, best_offset};
481 }
482
483 int current = best;
484 size_t bit = left;
485 for (; bit < right && (bit & 3u) != 0; ++bit) {
486 scan_bit(s, bit, current, best, best_offset);
487 }
488
489 size_t first_full_nibble = bit >> 2;
490 const size_t last_full_nibble = right >> 2;
491 while (first_full_nibble < last_full_nibble) {
492 const size_t word_index = first_full_nibble >> 4;
493 const size_t lane_first = first_full_nibble & 15u;
494 const size_t lane_last =
495 std::min<size_t>(last_full_nibble - word_index * 16u, 16);
496 const size_t lane_base_offset = word_index * 64u;
497 scan_full_nibbles_64_sse(
498 s[word_index], prefix_excess_128(s, lane_base_offset), lane_base_offset,
499 lane_first, lane_last, best, best_offset);
500 first_full_nibble = word_index * 16u + lane_last;
501 }
502
503 bit = std::max(bit, first_full_nibble * 4u);
504 current = prefix_excess_128(s, bit);
505 for (; bit < right; ++bit) {
506 scan_bit(s, bit, current, best, best_offset);
507 }
508
509 return {best, best_offset};
510}
511
512} // namespace detail
513
526static inline ExcessResult excess_min_128_lane64_sse(const uint64_t* s,
527 size_t left,
528 size_t right) noexcept {
529 if (left > right) {
530 return {};
531 }
532 const size_t clamped_left = std::min<size_t>(left, 128);
533 const size_t clamped_right = std::min<size_t>(right, 128);
534 const size_t first_full_nibble = ((clamped_left + 3u) & ~size_t{3}) >> 2;
535 const size_t last_full_nibble = clamped_right >> 2;
536 if (first_full_nibble < last_full_nibble &&
537 (first_full_nibble >> 4) != ((last_full_nibble - 1u) >> 4)) {
538 return excess_min_128(s, left, right);
539 }
540 return detail::excess_min_128_split64_sse_impl(s, left, right);
541}
542
555static inline ExcessResult excess_min_128_split64_sse(const uint64_t* s,
556 size_t left,
557 size_t right) noexcept {
558 return detail::excess_min_128_split64_sse_impl(s, left, right);
559}
560
575static inline ExcessResult excess_min_128_deinterleaved_sse(
576 const uint64_t* s,
577 size_t left,
578 size_t right) noexcept {
579 if (left > right) {
580 return {};
581 }
582 left = std::min<size_t>(left, 128);
583 right = std::min<size_t>(right, 128);
584
585 int best = prefix_excess_128(s, left);
586 size_t best_offset = left;
587 if (left == right) {
588 return {best, best_offset};
589 }
590
591 int current = best;
592 size_t bit = left;
593 for (; bit < right && (bit & 3u) != 0; ++bit) {
594 detail::scan_bit(s, bit, current, best, best_offset);
595 }
596
597 const size_t first_nibble = bit >> 2;
598 const size_t last_full_nibble = right >> 2;
599 const size_t right_partial_width = bit < right ? (right & 3u) : 0;
600 const size_t end_nibble =
601 last_full_nibble + (right_partial_width == 0 ? 0 : 1);
602
603 if (first_nibble < end_nibble) {
604 const __m128i bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
605 const __m128i lo_nibbles = _mm_and_si128(bytes, excess_lut_nibble_mask_128);
606 const __m128i hi_nibbles =
607 _mm_and_si128(_mm_srli_epi16(bytes, 4), excess_lut_nibble_mask_128);
608 const __m128i lo_delta = _mm_shuffle_epi8(excess_lut_delta_128, lo_nibbles);
609 const __m128i hi_delta = _mm_shuffle_epi8(excess_lut_delta_128, hi_nibbles);
610 const __m128i byte_delta = _mm_add_epi8(lo_delta, hi_delta);
611 const __m128i byte_prefix = detail::excess_prefix_sum_16x_i8(byte_delta);
612 const __m128i byte_prefix_before = _mm_slli_si128(byte_prefix, 1);
613
614 __m128i lo_local_min = _mm_shuffle_epi8(excess_lut_min_128, lo_nibbles);
615 __m128i hi_local_min = _mm_shuffle_epi8(excess_lut_min_128, hi_nibbles);
616
617 const __m128i byte_index = excess_lut_nibble_index_128;
618 if (right_partial_width != 0) {
619 const bool partial_is_high = (last_full_nibble & 1u) != 0;
620 const size_t partial_byte = last_full_nibble >> 1;
621 const __m128i partial_source = partial_is_high ? hi_nibbles : lo_nibbles;
622 __m128i partial_min =
623 _mm_shuffle_epi8(excess_lut_pos0_sse, partial_source);
624 if (right_partial_width >= 2) {
625 partial_min = _mm_min_epi8(
626 partial_min, _mm_shuffle_epi8(excess_lut_pos1_sse, partial_source));
627 }
628 if (right_partial_width >= 3) {
629 partial_min = _mm_min_epi8(
630 partial_min, _mm_shuffle_epi8(excess_lut_pos2_sse, partial_source));
631 }
632 const __m128i partial_lane = _mm_cmpeq_epi8(
633 byte_index, _mm_set1_epi8(static_cast<int8_t>(partial_byte)));
634 if (partial_is_high) {
635 hi_local_min = _mm_blendv_epi8(hi_local_min, partial_min, partial_lane);
636 } else {
637 lo_local_min = _mm_blendv_epi8(lo_local_min, partial_min, partial_lane);
638 }
639 }
640
641 const __m128i lo_candidates =
642 _mm_add_epi8(byte_prefix_before, lo_local_min);
643 const __m128i hi_candidates =
644 _mm_add_epi8(_mm_add_epi8(byte_prefix_before, lo_delta), hi_local_min);
645
646 __m128i masked_lo = lo_candidates;
647 __m128i masked_hi = hi_candidates;
648 if (first_nibble != 0 || end_nibble != 32) {
649 const __m128i first_minus_one = _mm_set1_epi8(
650 static_cast<int8_t>(static_cast<int>(first_nibble) - 1));
651 const __m128i last = _mm_set1_epi8(static_cast<int8_t>(end_nibble));
652 const __m128i lo_active = _mm_and_si128(
653 _mm_cmpgt_epi8(excess_lut_low_nibble_index_128, first_minus_one),
654 _mm_cmpgt_epi8(last, excess_lut_low_nibble_index_128));
655 const __m128i hi_active = _mm_and_si128(
656 _mm_cmpgt_epi8(excess_lut_high_nibble_index_128, first_minus_one),
657 _mm_cmpgt_epi8(last, excess_lut_high_nibble_index_128));
658 masked_lo = _mm_blendv_epi8(_mm_set1_epi8(127), lo_candidates, lo_active);
659 masked_hi = _mm_blendv_epi8(_mm_set1_epi8(127), hi_candidates, hi_active);
660 }
661 const int candidate_min =
662 detail::horizontal_min_i8(_mm_min_epi8(masked_lo, masked_hi));
663
664 if (candidate_min < best) {
665 const __m128i min_vec = _mm_set1_epi8(static_cast<int8_t>(candidate_min));
666 const uint32_t lo_equal_mask = static_cast<uint32_t>(
667 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_lo, min_vec)));
668 const uint32_t hi_equal_mask = static_cast<uint32_t>(
669 _mm_movemask_epi8(_mm_cmpeq_epi8(masked_hi, min_vec)));
670 const uint32_t lo_nibble_index =
671 lo_equal_mask == 0
672 ? 32u
673 : static_cast<uint32_t>(std::countr_zero(lo_equal_mask)) * 2u;
674 const uint32_t hi_nibble_index =
675 hi_equal_mask == 0
676 ? 32u
677 : static_cast<uint32_t>(std::countr_zero(hi_equal_mask)) * 2u +
678 1u;
679 const uint32_t nibble_index = std::min(lo_nibble_index, hi_nibble_index);
680 const uint32_t byte_offset = nibble_index >> 1u;
681 const uint64_t byte_word = s[byte_offset >> 3u];
682 const uint8_t byte = static_cast<uint8_t>(
683 (byte_word >> ((byte_offset & 7u) * 8u)) & 0xFFu);
684 const uint8_t nibble = (nibble_index & 1u) == 0
685 ? static_cast<uint8_t>(byte & 0xFu)
686 : static_cast<uint8_t>((byte >> 4u) & 0xFu);
687 const size_t local_offset =
688 right_partial_width != 0 && nibble_index == last_full_nibble
689 ? detail::partial_nibble_min_offset(nibble, right_partial_width)
690 : static_cast<size_t>(detail::kNibbleMinOffset[nibble]);
691 best = candidate_min;
692 best_offset = static_cast<size_t>(nibble_index) * 4u + local_offset;
693 }
694 }
695
696 return {best, best_offset};
697}
698
712static inline ExcessResult excess_min_128_deinterleaved_full_sse(
713 const uint64_t* s,
714 size_t left,
715 size_t right) noexcept {
716 if (left > right) {
717 return {};
718 }
719 left = std::min<size_t>(left, 128);
720 right = std::min<size_t>(right, 128);
721 if (left != 0 || right != 128) {
722 return excess_min_128_deinterleaved_sse(s, left, right);
723 }
724
725 const __m128i bytes = _mm_loadu_si128(reinterpret_cast<const __m128i*>(s));
726 const __m128i lo_nibbles = _mm_and_si128(bytes, excess_lut_nibble_mask_128);
727 const __m128i hi_nibbles =
728 _mm_and_si128(_mm_srli_epi16(bytes, 4), excess_lut_nibble_mask_128);
729 const __m128i lo_delta = _mm_shuffle_epi8(excess_lut_delta_128, lo_nibbles);
730 const __m128i hi_delta = _mm_shuffle_epi8(excess_lut_delta_128, hi_nibbles);
731 const __m128i byte_delta = _mm_add_epi8(lo_delta, hi_delta);
732 const __m128i byte_prefix = detail::excess_prefix_sum_16x_i8(byte_delta);
733 const __m128i byte_prefix_before = _mm_slli_si128(byte_prefix, 1);
734
735 const __m128i lo_candidates = _mm_add_epi8(
736 byte_prefix_before, _mm_shuffle_epi8(excess_lut_min_128, lo_nibbles));
737 const __m128i hi_candidates =
738 _mm_add_epi8(_mm_add_epi8(byte_prefix_before, lo_delta),
739 _mm_shuffle_epi8(excess_lut_min_128, hi_nibbles));
740 const int candidate_min =
741 detail::horizontal_min_i8(_mm_min_epi8(lo_candidates, hi_candidates));
742
743 int best = 0;
744 size_t best_offset = 0;
745 if (candidate_min < best) {
746 const __m128i min_vec = _mm_set1_epi8(static_cast<int8_t>(candidate_min));
747 const uint32_t lo_equal_mask = static_cast<uint32_t>(
748 _mm_movemask_epi8(_mm_cmpeq_epi8(lo_candidates, min_vec)));
749 const uint32_t hi_equal_mask = static_cast<uint32_t>(
750 _mm_movemask_epi8(_mm_cmpeq_epi8(hi_candidates, min_vec)));
751 const uint32_t lo_nibble_index =
752 lo_equal_mask == 0
753 ? 32u
754 : static_cast<uint32_t>(std::countr_zero(lo_equal_mask)) * 2u;
755 const uint32_t hi_nibble_index =
756 hi_equal_mask == 0
757 ? 32u
758 : static_cast<uint32_t>(std::countr_zero(hi_equal_mask)) * 2u + 1u;
759 const uint32_t nibble_index = std::min(lo_nibble_index, hi_nibble_index);
760 const uint32_t byte_offset = nibble_index >> 1u;
761 const uint64_t byte_word = s[byte_offset >> 3u];
762 const uint8_t byte =
763 static_cast<uint8_t>((byte_word >> ((byte_offset & 7u) * 8u)) & 0xFFu);
764 const uint8_t nibble = (nibble_index & 1u) == 0
765 ? static_cast<uint8_t>(byte & 0xFu)
766 : static_cast<uint8_t>((byte >> 4u) & 0xFu);
767 best = candidate_min;
768 best_offset = static_cast<size_t>(nibble_index) * 4u +
769 static_cast<size_t>(detail::kNibbleMinOffset[nibble]);
770 }
771 return {best, best_offset};
772}
773
786static inline ExcessResult excess_min_128_deinterleaved_byte16_sse(
787 const uint64_t* s,
788 size_t left,
789 size_t right) noexcept {
790 if (left > right) {
791 return {};
792 }
793 const size_t clamped_left = std::min<size_t>(left, 128);
794 const size_t clamped_right = std::min<size_t>(right, 128);
795 const size_t width = clamped_right - clamped_left;
796 if (width <= 16 && (clamped_left & 7u) == 0 && (clamped_right & 7u) == 0) {
797 return excess_min_128_byte_lut(s, left, right);
798 }
799 return excess_min_128_deinterleaved_sse(s, left, right);
800}
801
816static inline ExcessResult excess_min_128_short_skip(const uint64_t* s,
817 size_t left,
818 size_t right) noexcept {
819 if (left > right) {
820 return {};
821 }
822 const size_t clamped_left = std::min<size_t>(left, 128);
823 const size_t clamped_right = std::min<size_t>(right, 128);
824 const size_t width = clamped_right - clamped_left;
825 if (width <= 2) {
826 return excess_min_128_scalar_bits(s, left, right);
827 }
828
829 const size_t first_full_nibble = ((clamped_left + 3u) & ~size_t{3}) >> 2;
830 const size_t last_full_nibble = clamped_right >> 2;
831 if (first_full_nibble < last_full_nibble &&
832 (first_full_nibble >> 4) == ((last_full_nibble - 1u) >> 4)) {
833 return excess_min_128_lane64_sse(s, left, right);
834 }
835 if (width <= 80) {
836 return excess_min_128_split64_sse(s, left, right);
837 }
838 return excess_min_128(s, left, right);
839}
840
841// clang-format off
842static inline const __m256i excess_branch_lut_em4 = _mm256_setr_epi8(
843 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
844 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
845 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
846 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
847
848static inline const __m256i excess_branch_lut_em3 = _mm256_setr_epi8(
849 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
850 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
851 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
852 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);
853
854static inline const __m256i excess_branch_lut_em2 = _mm256_setr_epi8(
855 0x02, 0x08, 0x08, 0x00, 0x0A, 0x00, 0x00, 0x00,
856 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
857 0x02, 0x08, 0x08, 0x00, 0x0A, 0x00, 0x00, 0x00,
858 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00);
859
860static inline const __m256i excess_branch_lut_em1 = _mm256_setr_epi8(
861 0x01, 0x04, 0x05, 0x00, 0x05, 0x00, 0x01, 0x00,
862 0x01, 0x04, 0x05, 0x00, 0x05, 0x00, 0x01, 0x00,
863 0x01, 0x04, 0x05, 0x00, 0x05, 0x00, 0x01, 0x00,
864 0x01, 0x04, 0x05, 0x00, 0x05, 0x00, 0x01, 0x00);
865
866static inline const __m256i excess_branch_lut_e0 = _mm256_setr_epi8(
867 0x00, 0x02, 0x02, 0x08, 0x00, 0x0A, 0x0A, 0x00,
868 0x00, 0x0A, 0x0A, 0x00, 0x08, 0x02, 0x02, 0x00,
869 0x00, 0x02, 0x02, 0x08, 0x00, 0x0A, 0x0A, 0x00,
870 0x00, 0x0A, 0x0A, 0x00, 0x08, 0x02, 0x02, 0x00);
871
872static inline const __m256i excess_branch_lut_e1 = _mm256_setr_epi8(
873 0x00, 0x01, 0x00, 0x05, 0x00, 0x05, 0x04, 0x01,
874 0x00, 0x01, 0x00, 0x05, 0x00, 0x05, 0x04, 0x01,
875 0x00, 0x01, 0x00, 0x05, 0x00, 0x05, 0x04, 0x01,
876 0x00, 0x01, 0x00, 0x05, 0x00, 0x05, 0x04, 0x01);
877
878static inline const __m256i excess_branch_lut_e2 = _mm256_setr_epi8(
879 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0A,
880 0x00, 0x00, 0x00, 0x0A, 0x00, 0x08, 0x08, 0x02,
881 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0A,
882 0x00, 0x00, 0x00, 0x0A, 0x00, 0x08, 0x08, 0x02);
883
884static inline const __m256i excess_branch_lut_e3 = _mm256_setr_epi8(
885 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
886 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
887 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
888 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04);
889
890static inline const __m256i excess_branch_lut_e4 = _mm256_setr_epi8(
891 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
892 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08,
893 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
894 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08);
895// clang-format on
896
897static inline __m256i excess_bit_masks_16x() noexcept {
898 return _mm256_setr_epi16(0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020,
899 0x0040, 0x0080, 0x0100, 0x0200, 0x0400, 0x0800,
900 0x1000, 0x2000, 0x4000, (int16_t)0x8000);
901}
902
903static inline __m256i excess_prefix_sum_16x_i16(__m256i v) noexcept {
904 __m256i x = v;
905 __m256i t = _mm256_slli_si256(x, 2);
906 x = _mm256_add_epi16(x, t);
907 t = _mm256_slli_si256(x, 4);
908 x = _mm256_add_epi16(x, t);
909 t = _mm256_slli_si256(x, 8);
910 x = _mm256_add_epi16(x, t);
911
912 __m128i lo = _mm256_extracti128_si256(x, 0);
913 __m128i hi = _mm256_extracti128_si256(x, 1);
914 const int16_t carry = (int16_t)_mm_extract_epi16(lo, 7);
915 hi = _mm_add_epi16(hi, _mm_set1_epi16(carry));
916
917 __m256i out = _mm256_castsi128_si256(lo);
918 out = _mm256_inserti128_si256(out, hi, 1);
919 return out;
920}
921
922static inline int16_t excess_last_prefix_16x_i16(__m256i pref) noexcept {
923 __m128i hi = _mm256_extracti128_si256(pref, 1);
924 return (int16_t)_mm_extract_epi16(hi, 7);
925}
926
927static inline __m256i excess_bit_masks_32x8() noexcept {
928 return _mm256_setr_epi8(0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (char)0x80,
929 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (char)0x80,
930 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (char)0x80,
931 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (char)0x80);
932}
933
934static inline __m256i excess_byte_selectors_32x8() noexcept {
935 return _mm256_setr_epi8(0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2,
936 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3);
937}
938
939static inline __m256i excess_prefix_sum_32x_i8(__m256i v) noexcept {
940 __m256i x = v;
941 __m256i t = _mm256_slli_si256(x, 1);
942 x = _mm256_add_epi8(x, t);
943 t = _mm256_slli_si256(x, 2);
944 x = _mm256_add_epi8(x, t);
945 t = _mm256_slli_si256(x, 4);
946 x = _mm256_add_epi8(x, t);
947 t = _mm256_slli_si256(x, 8);
948 x = _mm256_add_epi8(x, t);
949
950 __m128i lo = _mm256_extracti128_si256(x, 0);
951 __m128i hi = _mm256_extracti128_si256(x, 1);
952 const int8_t carry = (int8_t)_mm_extract_epi8(lo, 15);
953 hi = _mm_add_epi8(hi, _mm_set1_epi8(carry));
954
955 __m256i out = _mm256_castsi128_si256(lo);
956 out = _mm256_inserti128_si256(out, hi, 1);
957 return out;
958}
959
960static inline int8_t excess_last_prefix_32x_i8(__m256i pref) noexcept {
961 __m128i hi = _mm256_extracti128_si256(pref, 1);
962 return (int8_t)_mm_extract_epi8(hi, 15);
963}
964
977static inline ExcessResult excess_min_128_expand16_avx2(const uint64_t* s,
978 size_t left,
979 size_t right) noexcept {
980 if (left > right) {
981 return {};
982 }
983 left = std::min<size_t>(left, 128);
984 right = std::min<size_t>(right, 128);
985
986 int best = prefix_excess_128(s, left);
987 size_t best_offset = left;
988 if (left == right) {
989 return {best, best_offset};
990 }
991
992 const __m256i masks = excess_bit_masks_16x();
993 const __m256i zero = _mm256_setzero_si256();
994 const __m256i pos = _mm256_set1_epi16(1);
995 const __m256i neg = _mm256_set1_epi16(-1);
996
997 int carry = 0;
998 alignas(32) int16_t prefix_values[16];
999 for (size_t chunk = 0; chunk < 8; ++chunk) {
1000 const size_t chunk_bit = chunk * 16;
1001 const uint16_t bits =
1002 chunk < 4
1003 ? static_cast<uint16_t>((s[0] >> (chunk * 16)) & 0xFFFFu)
1004 : static_cast<uint16_t>((s[1] >> ((chunk - 4) * 16)) & 0xFFFFu);
1005 const int delta = 2 * static_cast<int>(std::popcount(bits)) - 16;
1006
1007 if (chunk_bit + 1 <= right && chunk_bit + 16 >= left) {
1008 const __m256i selected = _mm256_and_si256(
1009 _mm256_set1_epi16(static_cast<int16_t>(bits)), masks);
1010 const __m256i is_zero = _mm256_cmpeq_epi16(selected, zero);
1011 const __m256i steps = _mm256_blendv_epi8(pos, neg, is_zero);
1012 const __m256i pref =
1013 _mm256_add_epi16(excess_prefix_sum_16x_i16(steps),
1014 _mm256_set1_epi16(static_cast<int16_t>(carry)));
1015 _mm256_store_si256(reinterpret_cast<__m256i*>(prefix_values), pref);
1016
1017 for (size_t lane = 0; lane < 16; ++lane) {
1018 const size_t offset = chunk_bit + lane + 1;
1019 if (offset < left || offset > right) {
1020 continue;
1021 }
1022 const int value = prefix_values[lane];
1023 if (value < best) {
1024 best = value;
1025 best_offset = offset;
1026 }
1027 }
1028 }
1029 carry += delta;
1030 }
1031
1032 return {best, best_offset};
1033}
1034
1047static inline void excess_positions_512_branching_lut(const uint64_t* s,
1048 int target_x,
1049 uint64_t* out) noexcept {
1050 out[0] = out[1] = out[2] = out[3] = 0;
1051 out[4] = out[5] = out[6] = out[7] = 0;
1052
1053 if (target_x < -512 || target_x > 512) {
1054 return;
1055 }
1056
1057 int cur = 0;
1058 const __m256i vdelta =
1059 _mm256_setr_epi8(-4, -2, -2, 0, -2, 0, 0, 2, -2, 0, 0, 2, 0, 2, 2, 4, -4,
1060 -2, -2, 0, -2, 0, 0, 2, -2, 0, 0, 2, 0, 2, 2, 4);
1061 const __m256i vmult = _mm256_set1_epi16(0x1001);
1062 const __m128i vnibble_mask = _mm_set1_epi8(0x0F);
1063
1064 for (int k = 0; k < 4; ++k) {
1065 __m128i word_vec = _mm_loadu_si128((const __m128i*)&s[2 * k]);
1066 __m128i lo_nibbles = _mm_and_si128(word_vec, vnibble_mask);
1067 __m128i hi_nibbles =
1068 _mm_and_si128(_mm_srli_epi16(word_vec, 4), vnibble_mask);
1069
1070 __m128i unpack_lo = _mm_unpacklo_epi8(lo_nibbles, hi_nibbles);
1071 __m128i unpack_hi = _mm_unpackhi_epi8(lo_nibbles, hi_nibbles);
1072 __m256i nibbles = _mm256_inserti128_si256(_mm256_castsi128_si256(unpack_lo),
1073 unpack_hi, 1);
1074
1075 __m256i ps = _mm256_shuffle_epi8(vdelta, nibbles);
1076 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 1));
1077 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 2));
1078 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 4));
1079 ps = _mm256_add_epi8(ps, _mm256_slli_si256(ps, 8));
1080
1081 __m128i ps_lo = _mm256_castsi256_si128(ps);
1082 __m128i ps_hi = _mm256_extracti128_si256(ps, 1);
1083 __m128i carry = _mm_set1_epi8((int8_t)_mm_extract_epi8(ps_lo, 15));
1084 ps_hi = _mm_add_epi8(ps_hi, carry);
1085 ps = _mm256_inserti128_si256(_mm256_castsi128_si256(ps_lo), ps_hi, 1);
1086
1087 __m256i b = _mm256_permute2x128_si256(ps, ps, 0x08);
1088 __m256i excl_ps = _mm256_alignr_epi8(ps, b, 15);
1089
1090 int target_rel = target_x - cur;
1091 int block_delta =
1092 2 * (std::popcount(s[2 * k]) + std::popcount(s[2 * k + 1])) - 128;
1093
1094 const int d = 2 * target_rel - block_delta;
1095 if (d < -128 || d > 128) {
1096 cur += block_delta;
1097 continue;
1098 }
1099
1100 if (target_rel == 128 || target_rel == -128) {
1101 out[2 * k + 1] |= (uint64_t{1} << 63);
1102 cur += block_delta;
1103 continue;
1104 }
1105
1106 __m256i t = _mm256_sub_epi8(_mm256_set1_epi8((int8_t)target_rel), excl_ps);
1107 __m256i total_match = _mm256_setzero_si256();
1108 __m256i t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(-4));
1109 total_match = _mm256_or_si256(
1110 total_match,
1111 _mm256_and_si256(t_eq,
1112 _mm256_shuffle_epi8(excess_branch_lut_em4, nibbles)));
1113 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(-3));
1114 total_match = _mm256_or_si256(
1115 total_match,
1116 _mm256_and_si256(t_eq,
1117 _mm256_shuffle_epi8(excess_branch_lut_em3, nibbles)));
1118 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(-2));
1119 total_match = _mm256_or_si256(
1120 total_match,
1121 _mm256_and_si256(t_eq,
1122 _mm256_shuffle_epi8(excess_branch_lut_em2, nibbles)));
1123 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(-1));
1124 total_match = _mm256_or_si256(
1125 total_match,
1126 _mm256_and_si256(t_eq,
1127 _mm256_shuffle_epi8(excess_branch_lut_em1, nibbles)));
1128 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(0));
1129 total_match = _mm256_or_si256(
1130 total_match,
1131 _mm256_and_si256(t_eq,
1132 _mm256_shuffle_epi8(excess_branch_lut_e0, nibbles)));
1133 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(1));
1134 total_match = _mm256_or_si256(
1135 total_match,
1136 _mm256_and_si256(t_eq,
1137 _mm256_shuffle_epi8(excess_branch_lut_e1, nibbles)));
1138 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(2));
1139 total_match = _mm256_or_si256(
1140 total_match,
1141 _mm256_and_si256(t_eq,
1142 _mm256_shuffle_epi8(excess_branch_lut_e2, nibbles)));
1143 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(3));
1144 total_match = _mm256_or_si256(
1145 total_match,
1146 _mm256_and_si256(t_eq,
1147 _mm256_shuffle_epi8(excess_branch_lut_e3, nibbles)));
1148 t_eq = _mm256_cmpeq_epi8(t, _mm256_set1_epi8(4));
1149 total_match = _mm256_or_si256(
1150 total_match,
1151 _mm256_and_si256(t_eq,
1152 _mm256_shuffle_epi8(excess_branch_lut_e4, nibbles)));
1153
1154 __m256i res = _mm256_maddubs_epi16(total_match, vmult);
1155 __m128i packed = _mm_packus_epi16(_mm256_castsi256_si128(res),
1156 _mm256_extracti128_si256(res, 1));
1157 _mm_storeu_si128((__m128i*)&out[2 * k], packed);
1158
1159 cur += block_delta;
1160 }
1161}
1162#else
1170static inline void excess_positions_512_branching_lut(const uint64_t* s,
1171 int target_x,
1172 uint64_t* out) noexcept {
1173 excess_positions_512(s, target_x, out);
1174}
1175#endif
1176
1177#ifdef PIXIE_AVX512_SUPPORT
1178static inline __m512i excess_lut_delta_64x() noexcept {
1179 return _mm512_broadcast_i32x4(
1180 _mm_setr_epi8(-4, -2, -2, 0, -2, 0, 0, 2, -2, 0, 0, 2, 0, 2, 2, 4));
1181}
1182
1183static inline __m512i excess_lut_pos0_64x() noexcept {
1184 return _mm512_broadcast_i32x4(
1185 _mm_setr_epi8(-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1));
1186}
1187
1188static inline __m512i excess_lut_pos1_64x() noexcept {
1189 return _mm512_broadcast_i32x4(
1190 _mm_setr_epi8(-2, 0, 0, 2, -2, 0, 0, 2, -2, 0, 0, 2, -2, 0, 0, 2));
1191}
1192
1193static inline __m512i excess_lut_pos2_64x() noexcept {
1194 return _mm512_broadcast_i32x4(
1195 _mm_setr_epi8(-3, -1, -1, 1, -1, 1, 1, 3, -3, -1, -1, 1, -1, 1, 1, 3));
1196}
1197
1198static inline __m512i excess_bit_masks_64x8() noexcept {
1199 alignas(64) static constexpr int8_t masks[64] = {
1200 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1201 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1202 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1203 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1204 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1205 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1206 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80,
1207 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, (int8_t)0x80};
1208 return _mm512_load_si512((const void*)masks);
1209}
1210
1211static inline __m512i excess_byte_selectors_64x8() noexcept {
1212 alignas(64) static constexpr int8_t selectors[64] = {
1213 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,
1214 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5,
1215 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7};
1216 return _mm512_load_si512((const void*)selectors);
1217}
1218
1219static inline __m512i excess_prefix_sum_64x_i8(__m512i v) noexcept {
1220 __m512i x = v;
1221 __m512i t = _mm512_bslli_epi128(x, 1);
1222 x = _mm512_add_epi8(x, t);
1223 t = _mm512_bslli_epi128(x, 2);
1224 x = _mm512_add_epi8(x, t);
1225 t = _mm512_bslli_epi128(x, 4);
1226 x = _mm512_add_epi8(x, t);
1227 t = _mm512_bslli_epi128(x, 8);
1228 x = _mm512_add_epi8(x, t);
1229
1230 const __m512i last_byte = _mm512_set1_epi8(15);
1231 const __m512i lane_carry = _mm512_shuffle_epi8(x, last_byte);
1232 const __m512i shift1_idx =
1233 _mm512_setr_epi32(0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);
1234 const __m512i shift2_idx =
1235 _mm512_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7);
1236 const __m512i shift3_idx =
1237 _mm512_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3);
1238
1239 __m512i lane_base =
1240 _mm512_maskz_permutexvar_epi32(0xFFF0, shift1_idx, lane_carry);
1241 lane_base = _mm512_add_epi8(lane_base, _mm512_maskz_permutexvar_epi32(
1242 0xFF00, shift2_idx, lane_carry));
1243 lane_base = _mm512_add_epi8(lane_base, _mm512_maskz_permutexvar_epi32(
1244 0xF000, shift3_idx, lane_carry));
1245 return _mm512_add_epi8(x, lane_base);
1246}
1247
1248static inline __m512i excess_prefix_sum_2x32_i8(__m512i v) noexcept {
1249 __m512i x = v;
1250 __m512i t = _mm512_bslli_epi128(x, 1);
1251 x = _mm512_add_epi8(x, t);
1252 t = _mm512_bslli_epi128(x, 2);
1253 x = _mm512_add_epi8(x, t);
1254 t = _mm512_bslli_epi128(x, 4);
1255 x = _mm512_add_epi8(x, t);
1256 t = _mm512_bslli_epi128(x, 8);
1257 x = _mm512_add_epi8(x, t);
1258
1259 const __m512i last_byte = _mm512_set1_epi8(15);
1260 const __m512i lane_carry = _mm512_shuffle_epi8(x, last_byte);
1261 const __m512i prev_lane_idx =
1262 _mm512_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8);
1263 const __mmask16 carry_to_second_lane_of_each_half = 0xF0F0;
1264 const __m512i lane_base = _mm512_maskz_permutexvar_epi32(
1265 carry_to_second_lane_of_each_half, prev_lane_idx, lane_carry);
1266 return _mm512_add_epi8(x, lane_base);
1267}
1268
1269static inline __m512i excess_nibbles_64x_from_256(__m256i words) noexcept {
1270 const __m512i bytes16 = _mm512_cvtepu8_epi16(words);
1271 const __m512i low = _mm512_and_si512(bytes16, _mm512_set1_epi16(0x000F));
1272 const __m512i high = _mm512_and_si512(_mm512_srli_epi16(bytes16, 4),
1273 _mm512_set1_epi16(0x000F));
1274 return _mm512_or_si512(low, _mm512_slli_epi16(high, 8));
1275}
1276
1277static inline __m512i excess_exclusive_prefix_2x32_i8(__m512i pref) noexcept {
1278 const __m512i zero = _mm512_setzero_si512();
1279 __m512i out = _mm512_alignr_epi8(pref, zero, 15);
1280
1281 const __m512i last_byte = _mm512_set1_epi8(15);
1282 const __m512i lane_carry = _mm512_shuffle_epi8(pref, last_byte);
1283 const __m512i prev_lane_idx =
1284 _mm512_setr_epi32(0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8);
1285 const __m512i carry_dwords =
1286 _mm512_permutexvar_epi32(prev_lane_idx, lane_carry);
1287 const __mmask64 first_byte_of_second_lane_in_each_half =
1288 (uint64_t{1} << 16) | (uint64_t{1} << 48);
1289 return _mm512_or_si512(
1290 out, _mm512_maskz_mov_epi8(first_byte_of_second_lane_in_each_half,
1291 carry_dwords));
1292}
1293
1294static inline uint64_t excess_repeat_byte(int value) noexcept {
1295 return uint64_t{0x0101010101010101} *
1296 static_cast<uint8_t>(static_cast<int8_t>(value));
1297}
1298
1312static inline void excess_positions_512_lut_avx512(const uint64_t* s,
1313 int target_x,
1314 uint64_t* out) noexcept {
1315 out[0] = out[1] = out[2] = out[3] = 0;
1316 out[4] = out[5] = out[6] = out[7] = 0;
1317
1318 if (target_x < -512 || target_x > 512) {
1319 return;
1320 }
1321
1322 static const __m512i vdelta = excess_lut_delta_64x();
1323 static const __m512i vpos0 = excess_lut_pos0_64x();
1324 static const __m512i vpos1 = excess_lut_pos1_64x();
1325 static const __m512i vpos2 = excess_lut_pos2_64x();
1326 static const __m512i vbit0 = _mm512_set1_epi8(1);
1327 static const __m512i vbit1 = _mm512_set1_epi8(2);
1328 static const __m512i vbit2 = _mm512_set1_epi8(4);
1329 static const __m512i vbit3 = _mm512_set1_epi8(8);
1330 static const __m512i vmult = _mm512_set1_epi16(0x1001);
1331
1332 for (int k = 0; k < 2; ++k) {
1333 const int base_word = 4 * k;
1334 const int delta0 =
1335 2 * (std::popcount(s[base_word]) + std::popcount(s[base_word + 1])) -
1336 128;
1337 const int delta1 = 2 * (std::popcount(s[base_word + 2]) +
1338 std::popcount(s[base_word + 3])) -
1339 128;
1340 const int target0 = target_x;
1341 const int target1 = target_x - delta0;
1342 const bool reachable0 = [&] {
1343 const int d = 2 * target0 - delta0;
1344 return -128 <= d && d <= 128;
1345 }();
1346 const bool reachable1 = [&] {
1347 const int d = 2 * target1 - delta1;
1348 return -128 <= d && d <= 128;
1349 }();
1350
1351 if (!reachable0 && !reachable1) {
1352 target_x -= delta0 + delta1;
1353 continue;
1354 }
1355
1356 const __m256i words =
1357 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(&s[base_word]));
1358 const __m512i nibbles = excess_nibbles_64x_from_256(words);
1359 const __m512i ps =
1360 excess_prefix_sum_2x32_i8(_mm512_shuffle_epi8(vdelta, nibbles));
1361 const __m512i excl_ps = excess_exclusive_prefix_2x32_i8(ps);
1362 const uint64_t repeated0 = excess_repeat_byte(target0);
1363 const uint64_t repeated1 = excess_repeat_byte(target1);
1364 const __m512i vtgt =
1365 _mm512_setr_epi64(repeated0, repeated0, repeated0, repeated0, repeated1,
1366 repeated1, repeated1, repeated1);
1367 const __m512i t = _mm512_sub_epi8(vtgt, excl_ps);
1368
1369 const __mmask64 cmp0 =
1370 _mm512_cmpeq_epi8_mask(_mm512_shuffle_epi8(vpos0, nibbles), t);
1371 const __mmask64 cmp1 =
1372 _mm512_cmpeq_epi8_mask(_mm512_shuffle_epi8(vpos1, nibbles), t);
1373 const __mmask64 cmp2 =
1374 _mm512_cmpeq_epi8_mask(_mm512_shuffle_epi8(vpos2, nibbles), t);
1375 const __mmask64 cmp3 = _mm512_cmpeq_epi8_mask(ps, vtgt);
1376 __m512i total_match = _mm512_maskz_mov_epi8(cmp0, vbit0);
1377 total_match =
1378 _mm512_or_si512(total_match, _mm512_maskz_mov_epi8(cmp1, vbit1));
1379 total_match =
1380 _mm512_or_si512(total_match, _mm512_maskz_mov_epi8(cmp2, vbit2));
1381 total_match =
1382 _mm512_or_si512(total_match, _mm512_maskz_mov_epi8(cmp3, vbit3));
1383
1384 const __mmask64 active =
1385 (reachable0 ? __mmask64{0x00000000FFFFFFFFull} : __mmask64{0}) |
1386 (reachable1 ? __mmask64{0xFFFFFFFF00000000ull} : __mmask64{0});
1387 total_match = _mm512_maskz_mov_epi8(active, total_match);
1388
1389 const __m512i res = _mm512_maddubs_epi16(total_match, vmult);
1390 const __m256i packed = _mm512_cvtepi16_epi8(res);
1391 _mm256_storeu_si256(reinterpret_cast<__m256i*>(&out[base_word]), packed);
1392
1393 target_x -= delta0 + delta1;
1394 }
1395}
1396#else
1404static inline void excess_positions_512_lut_avx512(const uint64_t* s,
1405 int target_x,
1406 uint64_t* out) noexcept {
1407 excess_positions_512(s, target_x, out);
1408}
1409#endif
1410
1423static inline void excess_positions_512_expand(const uint64_t* s,
1424 int target_x,
1425 uint64_t* out) noexcept {
1426 out[0] = out[1] = out[2] = out[3] = 0;
1427 out[4] = out[5] = out[6] = out[7] = 0;
1428
1429 if (target_x < -512 || target_x > 512) {
1430 return;
1431 }
1432
1433#ifdef PIXIE_AVX2_SUPPORT
1434 static const __m256i masks = excess_bit_masks_16x();
1435 static const __m256i vzero = _mm256_setzero_si256();
1436 static const __m256i vallones = _mm256_cmpeq_epi16(vzero, vzero);
1437 static const __m256i vminus1 = _mm256_set1_epi16(-1);
1438 static const __m256i vtwo = _mm256_set1_epi16(2);
1439 const __m256i vtarget = _mm256_set1_epi16((int16_t)target_x);
1440
1441 int cur = 0;
1442 for (int block = 0; block < 4; ++block) {
1443 const int target_rel = target_x - cur;
1444 if (target_rel <= -64 || target_rel >= 64) {
1445 const int block_delta =
1446 2 * (std::popcount(s[2 * block]) + std::popcount(s[2 * block + 1])) -
1447 128;
1448 const int reachability = 2 * target_rel - block_delta;
1449 if (reachability < -128 || reachability > 128) {
1450 cur += block_delta;
1451 continue;
1452 }
1453 }
1454
1455 for (int j = 0; j < 8; ++j) {
1456 const int k = 8 * block + j;
1457 const size_t word_idx = size_t(k) >> 2;
1458 const size_t shift = size_t(k & 3) * 16;
1459 const uint16_t bits16 =
1460 static_cast<uint16_t>((s[word_idx] >> shift) & 0xFFFFull);
1461
1462 const __m256i vb = _mm256_set1_epi16((int16_t)bits16);
1463 const __m256i m = _mm256_and_si256(vb, masks);
1464 const __m256i is_zero = _mm256_cmpeq_epi16(m, vzero);
1465 const __m256i is_set = _mm256_andnot_si256(is_zero, vallones);
1466 const __m256i steps =
1467 _mm256_add_epi16(vminus1, _mm256_and_si256(is_set, vtwo));
1468
1469 const __m256i pref_rel = excess_prefix_sum_16x_i16(steps);
1470 const __m256i base = _mm256_set1_epi16((int16_t)cur);
1471 const __m256i pref_abs = _mm256_add_epi16(pref_rel, base);
1472 const __m256i cmp = _mm256_cmpeq_epi16(pref_abs, vtarget);
1473
1474 const uint32_t m32 = (uint32_t)_mm256_movemask_epi8(cmp);
1475 const uint16_t m16 = (uint16_t)_pext_u32(m32, 0xAAAAAAAAu);
1476
1477 out[word_idx] |= uint64_t(m16) << shift;
1478 cur += (int)excess_last_prefix_16x_i16(pref_rel);
1479 }
1480 }
1481#else
1482 int cur = 0;
1483 for (size_t i = 0; i < 512; ++i) {
1484 const uint64_t w = s[i >> 6];
1485 const int bit = int((w >> (i & 63)) & 1ull);
1486 cur += bit ? +1 : -1;
1487 if (cur == target_x) {
1488 out[i >> 6] |= (uint64_t{1} << (i & 63));
1489 }
1490 }
1491#endif
1492}
1493
1506static inline void excess_positions_512_expand8(const uint64_t* s,
1507 int target_x,
1508 uint64_t* out) noexcept {
1509 out[0] = out[1] = out[2] = out[3] = 0;
1510 out[4] = out[5] = out[6] = out[7] = 0;
1511
1512 if (target_x < -512 || target_x > 512) {
1513 return;
1514 }
1515
1516#ifdef PIXIE_AVX2_SUPPORT
1517 static const __m256i byte_selectors = excess_byte_selectors_32x8();
1518 static const __m256i masks = excess_bit_masks_32x8();
1519 static const __m256i vzero = _mm256_setzero_si256();
1520 static const __m256i vallones = _mm256_cmpeq_epi8(vzero, vzero);
1521 static const __m256i vminus1 = _mm256_set1_epi8(-1);
1522 static const __m256i vtwo = _mm256_set1_epi8(2);
1523
1524 int cur = 0;
1525 for (int k = 0; k < 16; ++k) {
1526 const size_t word_idx = size_t(k) >> 1;
1527 const size_t shift = size_t(k & 1) * 32;
1528 const uint32_t bits32 =
1529 static_cast<uint32_t>((s[word_idx] >> shift) & 0xFFFFFFFFull);
1530
1531 const int target_rel = target_x - cur;
1532 if (target_rel < -32 || target_rel > 32) {
1533 cur += 2 * static_cast<int>(std::popcount(bits32)) - 32;
1534 continue;
1535 }
1536
1537 const __m256i src = _mm256_set1_epi32((int)bits32);
1538 const __m256i bytes = _mm256_shuffle_epi8(src, byte_selectors);
1539 const __m256i m = _mm256_and_si256(bytes, masks);
1540 const __m256i is_zero = _mm256_cmpeq_epi8(m, vzero);
1541 const __m256i is_set = _mm256_andnot_si256(is_zero, vallones);
1542 const __m256i steps =
1543 _mm256_add_epi8(vminus1, _mm256_and_si256(is_set, vtwo));
1544
1545 const __m256i pref_rel = excess_prefix_sum_32x_i8(steps);
1546 const __m256i vtarget = _mm256_set1_epi8((int8_t)target_rel);
1547 const __m256i cmp = _mm256_cmpeq_epi8(pref_rel, vtarget);
1548 const uint32_t mask = static_cast<uint32_t>(_mm256_movemask_epi8(cmp));
1549
1550 out[word_idx] |= uint64_t(mask) << shift;
1551 cur += static_cast<int>(excess_last_prefix_32x_i8(pref_rel));
1552 }
1553#else
1554 int cur = 0;
1555 for (size_t i = 0; i < 512; ++i) {
1556 const uint64_t w = s[i >> 6];
1557 const int bit = int((w >> (i & 63)) & 1ull);
1558 cur += bit ? +1 : -1;
1559 if (cur == target_x) {
1560 out[i >> 6] |= (uint64_t{1} << (i & 63));
1561 }
1562 }
1563#endif
1564}
1565
1578static inline void excess_positions_512_expand_avx512(const uint64_t* s,
1579 int target_x,
1580 uint64_t* out) noexcept {
1581 out[0] = out[1] = out[2] = out[3] = 0;
1582 out[4] = out[5] = out[6] = out[7] = 0;
1583
1584 if (target_x < -512 || target_x > 512) {
1585 return;
1586 }
1587
1588#ifdef PIXIE_AVX512_SUPPORT
1589 static const __m512i byte_selectors = excess_byte_selectors_64x8();
1590 static const __m512i masks = excess_bit_masks_64x8();
1591 static const __m512i vzero = _mm512_setzero_si512();
1592 static const __m512i vallones = _mm512_set1_epi8(-1);
1593 static const __m512i vminus1 = _mm512_set1_epi8(-1);
1594 static const __m512i vtwo = _mm512_set1_epi8(2);
1595
1596 int cur = 0;
1597 for (int k = 0; k < 8; ++k) {
1598 const uint64_t bits64 = s[k];
1599 const int target_rel = target_x - cur;
1600 if (target_rel < -64 || target_rel > 64) {
1601 cur += 2 * static_cast<int>(std::popcount(bits64)) - 64;
1602 continue;
1603 }
1604
1605 const __m512i src = _mm512_set1_epi64(static_cast<int64_t>(bits64));
1606 const __m512i bytes = _mm512_shuffle_epi8(src, byte_selectors);
1607 const __m512i m = _mm512_and_si512(bytes, masks);
1608 const __mmask64 is_zero = _mm512_cmpeq_epi8_mask(m, vzero);
1609 const __m512i is_set = _mm512_maskz_mov_epi8(~is_zero, vallones);
1610 const __m512i steps =
1611 _mm512_add_epi8(vminus1, _mm512_and_si512(is_set, vtwo));
1612
1613 const __m512i pref_rel = excess_prefix_sum_64x_i8(steps);
1614 const __mmask64 match =
1615 _mm512_cmpeq_epi8_mask(pref_rel, _mm512_set1_epi8((int8_t)target_rel));
1616 out[k] = static_cast<uint64_t>(match);
1617 cur += 2 * static_cast<int>(std::popcount(bits64)) - 64;
1618 }
1619#else
1620 excess_positions_512_expand8(s, target_x, out);
1621#endif
1622}
1623
1624struct ExcessByteLut {
1625 uint8_t masks[256][17]; // target index: T + 8
1626 int8_t deltas[256];
1627
1628 constexpr ExcessByteLut() : masks{}, deltas{} {
1629 for (int b = 0; b < 256; ++b) {
1630 int pop = 0;
1631 for (int i = 0; i < 8; ++i) {
1632 if ((b >> i) & 1) {
1633 pop++;
1634 }
1635 }
1636 deltas[b] = static_cast<int8_t>(2 * pop - 8);
1637
1638 for (int t = -8; t <= 8; ++t) {
1639 uint8_t mask = 0;
1640 int cur_pop = 0;
1641 for (int i = 0; i < 8; ++i) {
1642 if ((b >> i) & 1) {
1643 cur_pop++;
1644 }
1645 int excess = 2 * cur_pop - (i + 1);
1646 if (excess == t) {
1647 mask |= (1 << i);
1648 }
1649 }
1650 masks[b][t + 8] = mask;
1651 }
1652 }
1653 }
1654};
1655
1656inline constexpr ExcessByteLut kExcessByteLut;
1657
1671static inline void excess_positions_512_byte_lut(const uint64_t* s,
1672 int target_x,
1673 uint64_t* out) noexcept {
1674 out[0] = out[1] = out[2] = out[3] = 0;
1675 out[4] = out[5] = out[6] = out[7] = 0;
1676
1677 if (target_x < -512 || target_x > 512) {
1678 return;
1679 }
1680
1681 const uint8_t* bytes = reinterpret_cast<const uint8_t*>(s);
1682 uint8_t* out_bytes = reinterpret_cast<uint8_t*>(out);
1683
1684 int cur = 0;
1685 for (int i = 0; i < 64; ++i) {
1686 const uint8_t b = bytes[i];
1687 const int target_rel = target_x - cur;
1688 if (target_rel >= -8 && target_rel <= 8) {
1689 out_bytes[i] = kExcessByteLut.masks[b][target_rel + 8];
1690 }
1691 cur += kExcessByteLut.deltas[b];
1692 }
1693}
1694
1695} // namespace pixie::experimental
Definition excess.h:90
Minimum prefix excess in a 128-bit bitstring range.
Definition bits.h:980
Definition excess.h:1624