9#include <pixie/packed_bit_builder.h>
22#if defined(PIXIE_AVX2_SUPPORT) || defined(PIXIE_AVX512_SUPPORT)
26#if defined(__aarch64__) && defined(__ARM_NEON)
27#define PIXIE_WAVELET_NEON_SUPPORT
31namespace pixie::detail {
33#if defined(PIXIE_AVX2_SUPPORT)
35struct WaveletCompaction16Tables {
36 std::array<std::array<std::uint8_t, 16>, 256> low{};
37 std::array<std::array<std::uint8_t, 32>, 256> high{};
43consteval WaveletCompaction16Tables make_wavelet_compaction_16_tables() {
44 WaveletCompaction16Tables tables;
45 for (std::size_t mask = 0; mask < 256; ++mask) {
47 std::size_t right = 15;
48 for (std::size_t index = 0; index < 8; ++index) {
49 if ((mask & (std::size_t{1} << index)) == 0) {
50 tables.low[mask][left++] =
static_cast<std::uint8_t
>(index);
52 tables.low[mask][right--] =
static_cast<std::uint8_t
>(index);
58 for (std::size_t index = 0; index < 8; ++index) {
59 if ((mask & (std::size_t{1} << index)) == 0) {
60 tables.high[mask][left++] =
static_cast<std::uint8_t
>(index + 8);
62 tables.high[mask][right--] =
static_cast<std::uint8_t
>(index + 8);
69alignas(64)
inline constexpr auto kWaveletCompaction16Tables =
70 make_wavelet_compaction_16_tables();
72inline std::uint16_t wavelet_direction_mask_16(__m128i ranks,
73 __m128i first_right) {
75 _mm_cmpeq_epi8(_mm_min_epu8(ranks, first_right), first_right);
76 return static_cast<std::uint16_t
>(_mm_movemask_epi8(right));
79inline __m128i compact_wavelet_16(__m128i ranks, std::uint16_t right_mask) {
80 const std::uint8_t low_mask =
static_cast<std::uint8_t
>(right_mask);
81 const std::uint8_t high_mask =
static_cast<std::uint8_t
>(right_mask >> 8);
82 const unsigned low_right = std::popcount(low_mask);
83 const __m128i low = _mm_load_si128(
reinterpret_cast<const __m128i*
>(
84 kWaveletCompaction16Tables.low[low_mask].data()));
85 const __m128i high = _mm_loadu_si128(
reinterpret_cast<const __m128i*
>(
86 kWaveletCompaction16Tables.high[high_mask].data() + low_right));
87 return _mm_shuffle_epi8(ranks, _mm_or_si128(low, high));
92#if defined(PIXIE_WAVELET_NEON_SUPPORT)
94struct NeonWaveletCompactionTables {
95 std::array<std::array<std::uint8_t, 16>, 256> low{};
96 std::array<std::array<std::uint8_t, 32>, 256> high{};
99consteval NeonWaveletCompactionTables make_neon_wavelet_compaction_tables() {
100 NeonWaveletCompactionTables tables;
101 for (std::size_t mask = 0; mask < 256; ++mask) {
102 std::size_t left = 0;
103 std::size_t right = 15;
104 for (std::size_t index = 0; index < 8; ++index) {
105 if ((mask & (std::size_t{1} << index)) == 0) {
106 tables.low[mask][left++] =
static_cast<std::uint8_t
>(index);
108 tables.low[mask][right--] =
static_cast<std::uint8_t
>(index);
113 for (std::size_t index = 0; index < 8; ++index) {
114 if ((mask & (std::size_t{1} << index)) == 0) {
115 tables.high[mask][left++] =
static_cast<std::uint8_t
>(index + 8);
117 tables.high[mask][right--] =
static_cast<std::uint8_t
>(index + 8);
124alignas(64)
inline constexpr auto kNeonWaveletCompactionTables =
125 make_neon_wavelet_compaction_tables();
127inline std::uint16_t wavelet_direction_mask_16_neon(uint8x16_t ranks,
128 uint8x16_t first_right) {
129 static constexpr std::array<std::uint8_t, 16> kBitWeights = {
130 1, 2, 4, 8, 16, 32, 64, 128, 1, 2, 4, 8, 16, 32, 64, 128};
131 const uint8x16_t bits =
132 vandq_u8(vcgeq_u8(ranks, first_right), vld1q_u8(kBitWeights.data()));
133 const std::uint8_t low = vaddv_u8(vget_low_u8(bits));
134 const std::uint8_t high = vaddv_u8(vget_high_u8(bits));
135 return static_cast<std::uint16_t
>(low | (std::uint16_t{high} << 8));
138inline uint8x16_t compact_wavelet_16_neon(uint8x16_t ranks,
139 std::uint16_t right_mask) {
140 const std::uint8_t low_mask =
static_cast<std::uint8_t
>(right_mask);
141 const std::uint8_t high_mask =
static_cast<std::uint8_t
>(right_mask >> 8);
142 const unsigned low_right = std::popcount(low_mask);
143 const uint8x16_t low =
144 vld1q_u8(kNeonWaveletCompactionTables.low[low_mask].data());
145 const uint8x16_t high =
146 vld1q_u8(kNeonWaveletCompactionTables.high[high_mask].data() + low_right);
147 return vqtbl1q_u8(ranks, vorrq_u8(low, high));
152template <std::
unsigned_
integral Symbol>
153std::uint64_t wavelet_direction_mask(std::span<const Symbol> ranks,
155 std::uint64_t mask = 0;
156 for (std::size_t offset = 0; offset < ranks.size(); ++offset) {
157 mask |=
static_cast<std::uint64_t
>(ranks[offset] >= middle) << offset;
162template <
bool WriteLeft,
bool WriteRight, std::
unsigned_
integral Symbol>
163void partition_wavelet_ranks_scalar(std::span<const Symbol> input,
165 std::span<Symbol> output,
166 PackedBitBuilder& directions,
168 std::size_t& right) {
169 for (std::size_t offset = 0; offset < input.size(); offset += 64) {
170 const std::size_t width = std::min<std::size_t>(64, input.size() - offset);
171 const std::span<const Symbol> block = input.subspan(offset, width);
172 const std::uint64_t right_mask = wavelet_direction_mask(block, middle);
173 directions.write_bits(right_mask, width);
175 const std::uint64_t valid_mask =
176 width == 64 ? std::numeric_limits<std::uint64_t>::max()
177 : (std::uint64_t{1} << width) - 1;
178 std::uint64_t left_mask = (~right_mask) & valid_mask;
179 if constexpr (WriteLeft) {
180 while (left_mask != 0) {
181 const unsigned index = std::countr_zero(left_mask);
182 output[left++] = block[index];
183 left_mask &= left_mask - 1;
186 left += std::popcount(left_mask);
189 std::uint64_t remaining_right = right_mask & valid_mask;
190 if constexpr (WriteRight) {
191 while (remaining_right != 0) {
192 const unsigned index = std::countr_zero(remaining_right);
193 output[right++] = block[index];
194 remaining_right &= remaining_right - 1;
197 right += std::popcount(remaining_right);
202template <
bool WriteLeft,
bool WriteRight, std::
unsigned_
integral Symbol>
203void partition_wavelet_ranks_impl(std::span<const Symbol> input,
205 std::span<Symbol> output,
206 PackedBitBuilder& directions,
208 std::size_t& right) {
209 partition_wavelet_ranks_scalar<WriteLeft, WriteRight>(
210 input, middle, output, directions, left, right);
223template <std::
unsigned_
integral Symbol>
226 std::span<Symbol> output,
227 std::size_t expected_left,
231 const bool output_required = write_left || write_right;
232 if ((output_required && input.size() != output.size()) ||
233 expected_left > input.size()) {
234 throw std::invalid_argument(
"Invalid wavelet partition buffers");
237 std::size_t left = 0;
238 std::size_t right = expected_left;
241 partition_wavelet_ranks_impl<true, true>(input, middle, output,
242 directions, left, right);
244 partition_wavelet_ranks_impl<true, false>(input, middle, output,
245 directions, left, right);
247 }
else if (write_right) {
248 partition_wavelet_ranks_impl<false, true>(input, middle, output, directions,
251 partition_wavelet_ranks_impl<false, false>(input, middle, output,
252 directions, left, right);
255 if (left != expected_left || right != input.size()) {
256 throw std::invalid_argument(
257 "Wavelet partition does not match the supplied symbol counts");
263 std::span<const std::uint8_t> symbols,
264 std::span<std::uint8_t> ranks,
265 const std::array<std::uint8_t, 256>& symbol_to_rank,
266 const std::array<std::uint16_t, 256>& symbol_to_high_rank) {
267 if (symbols.size() != ranks.size()) {
268 throw std::invalid_argument(
"Invalid wavelet rank mapping buffers");
270 std::size_t offset = 0;
271#if defined(PIXIE_WAVELET_NEON_SUPPORT)
272 if (symbols.size() >= 20) {
277 for (std::size_t lane = 0; lane < 4; ++lane) {
278 table0.val[lane] = vld1q_u8(symbol_to_rank.data() + lane * 16);
279 table1.val[lane] = vld1q_u8(symbol_to_rank.data() + 64 + lane * 16);
280 table2.val[lane] = vld1q_u8(symbol_to_rank.data() + 128 + lane * 16);
281 table3.val[lane] = vld1q_u8(symbol_to_rank.data() + 192 + lane * 16);
283 const uint8x16_t offset64 = vdupq_n_u8(64);
284 const uint8x16_t offset128 = vdupq_n_u8(128);
285 const uint8x16_t offset192 = vdupq_n_u8(192);
286 for (; offset + 20 <= symbols.size(); offset += 20) {
287 const uint8x16_t input = vld1q_u8(symbols.data() + offset);
289 std::memcpy(&tail, symbols.data() + offset + 16,
sizeof(tail));
290 uint8x16_t mapped = vqtbl4q_u8(table0, input);
291 const unsigned rank0 = symbol_to_rank[
static_cast<std::uint8_t
>(tail)];
292 mapped = vqtbx4q_u8(mapped, table1, vsubq_u8(input, offset64));
293 const unsigned rank1 =
294 symbol_to_rank[
static_cast<std::uint8_t
>(tail >> 8)];
295 mapped = vqtbx4q_u8(mapped, table2, vsubq_u8(input, offset128));
296 const unsigned rank2 =
297 symbol_to_rank[
static_cast<std::uint8_t
>(tail >> 16)];
298 mapped = vqtbx4q_u8(mapped, table3, vsubq_u8(input, offset192));
299 const unsigned rank3 =
300 symbol_to_rank[
static_cast<std::uint8_t
>(tail >> 24)];
301 vst1q_u8(ranks.data() + offset, mapped);
302 const std::uint32_t mapped_tail =
303 rank0 | (rank1 << 8) | (rank2 << 16) | (rank3 << 24);
304 std::memcpy(ranks.data() + offset + 16, &mapped_tail,
305 sizeof(mapped_tail));
308 (void)symbol_to_high_rank;
310 if constexpr (std::endian::native == std::endian::little) {
311 for (; offset + 16 <= symbols.size(); offset += 16) {
312 std::uint64_t low_symbols;
313 std::uint64_t high_symbols;
314 std::memcpy(&low_symbols, symbols.data() + offset,
sizeof(low_symbols));
315 std::memcpy(&high_symbols, symbols.data() + offset + 8,
316 sizeof(high_symbols));
318 std::array<std::uint16_t, 8> pairs;
319 for (std::size_t pair = 0; pair < 4; ++pair) {
321 symbol_to_rank[
static_cast<std::uint8_t
>(low_symbols)] +
322 symbol_to_high_rank[
static_cast<std::uint8_t
>(low_symbols >> 8)];
325 symbol_to_rank[
static_cast<std::uint8_t
>(high_symbols)] +
326 symbol_to_high_rank[
static_cast<std::uint8_t
>(high_symbols >> 8)];
329 std::memcpy(ranks.data() + offset, pairs.data(), 16);
333 for (; offset < symbols.size(); ++offset) {
334 ranks[offset] = symbol_to_rank[symbols[offset]];
340 std::span<std::uint8_t> symbols,
341 const std::array<std::uint8_t, 256>& symbol_to_rank,
342 const std::array<std::uint16_t, 256>& symbol_to_high_rank) {
344 symbol_to_rank, symbol_to_high_rank);
347template <
bool WriteLeft,
bool WriteRight>
348std::size_t partition_wavelet_byte_block_scalar(
349 std::span<std::uint8_t> ranks,
350 std::uint8_t first_right,
351 std::span<std::uint8_t> right_output,
353 std::size_t left = 0;
354 std::size_t right = 0;
355 for (std::size_t offset = 0; offset < ranks.size(); offset += 64) {
356 const std::size_t width = std::min<std::size_t>(64, ranks.size() - offset);
357 std::uint64_t right_mask = 0;
358 for (std::size_t index = 0; index < width; ++index) {
359 const std::uint8_t rank = ranks[offset + index];
360 const bool goes_right = rank >= first_right;
361 right_mask |=
static_cast<std::uint64_t
>(goes_right) << index;
363 if constexpr (WriteRight) {
364 right_output[right] = rank;
368 if constexpr (WriteLeft) {
379#if defined(PIXIE_WAVELET_NEON_SUPPORT)
383template <
bool WriteLeft,
bool WriteRight>
384std::size_t partition_wavelet_byte_block_neon(
385 std::span<std::uint8_t> ranks,
386 std::uint8_t first_right,
387 std::span<std::uint8_t> right_output,
388 PackedBitBuilder& directions) {
389 static constexpr std::array<std::uint8_t, 16> kReverse = {
390 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
391 const uint8x16_t reverse = vld1q_u8(kReverse.data());
392 const uint8x16_t threshold = vdupq_n_u8(first_right);
393 std::size_t left = 0;
394 std::size_t right = 0;
395 std::size_t offset = 0;
397 for (; offset + 32 <= ranks.size(); offset += 32) {
398 const uint8x16_t low = vld1q_u8(ranks.data() + offset);
399 const uint8x16_t high = vld1q_u8(ranks.data() + offset + 16);
400 const std::uint16_t low_mask =
401 wavelet_direction_mask_16_neon(low, threshold);
402 const std::uint16_t high_mask =
403 wavelet_direction_mask_16_neon(high, threshold);
404 const std::uint32_t right_mask =
405 low_mask | (
static_cast<std::uint32_t
>(high_mask) << 16);
406 const unsigned low_right = std::popcount(low_mask);
407 const unsigned low_left = 16 - low_right;
408 const unsigned right_count = std::popcount(right_mask);
409 directions.write_bits(right_mask, 32);
411 if constexpr (WriteLeft || WriteRight) {
412 const uint8x16_t compacted_low = compact_wavelet_16_neon(low, low_mask);
413 const uint8x16_t compacted_high =
414 compact_wavelet_16_neon(high, high_mask);
415 if constexpr (WriteLeft) {
416 vst1q_u8(ranks.data() + left, compacted_low);
417 vst1q_u8(ranks.data() + left + low_left, compacted_high);
419 if constexpr (WriteRight) {
420 vst1q_u8(right_output.data() + right,
421 vqtbl1q_u8(compacted_low, reverse));
422 vst1q_u8(right_output.data() + right + low_right,
423 vqtbl1q_u8(compacted_high, reverse));
426 left += 32 - right_count;
427 right += right_count;
429 for (; offset + 16 <= ranks.size(); offset += 16) {
430 const uint8x16_t values = vld1q_u8(ranks.data() + offset);
431 const std::uint16_t right_mask =
432 wavelet_direction_mask_16_neon(values, threshold);
433 const unsigned right_count = std::popcount(right_mask);
434 directions.write_bits(right_mask, 16);
435 if constexpr (WriteLeft || WriteRight) {
436 const uint8x16_t compacted = compact_wavelet_16_neon(values, right_mask);
437 if constexpr (WriteLeft) {
438 vst1q_u8(ranks.data() + left, compacted);
440 if constexpr (WriteRight) {
441 vst1q_u8(right_output.data() + right, vqtbl1q_u8(compacted, reverse));
444 left += 16 - right_count;
445 right += right_count;
447 for (; offset < ranks.size(); ++offset) {
448 const std::uint8_t rank = ranks[offset];
449 const bool goes_right = rank >= first_right;
450 directions.write_bit(goes_right);
452 if constexpr (WriteRight) {
453 right_output[right] = rank;
457 if constexpr (WriteLeft) {
468#if defined(PIXIE_AVX2_SUPPORT)
473template <
bool WriteLeft,
bool WriteRight>
474std::size_t partition_wavelet_byte_block_x86(
475 std::span<std::uint8_t> ranks,
476 std::uint8_t first_right,
477 std::span<std::uint8_t> right_output,
478 PackedBitBuilder& directions) {
479 static constexpr std::array<std::uint8_t, 16> kReverse = {
480 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
481 const __m128i reverse =
482 _mm_loadu_si128(
reinterpret_cast<const __m128i*
>(kReverse.data()));
483 const __m128i threshold = _mm_set1_epi8(
static_cast<char>(first_right));
484 std::size_t left = 0;
485 std::size_t right = 0;
486 std::size_t offset = 0;
488#if defined(PIXIE_AVX512_SUPPORT) && defined(__AVX512VBMI2__)
489 const __m512i threshold_512 =
490 _mm512_set1_epi8(
static_cast<char>(first_right - 1));
491 for (; offset + 64 <= ranks.size(); offset += 64) {
492 const __m512i values = _mm512_loadu_si512(ranks.data() + offset);
493 const __mmask64 right_mask = _mm512_cmpgt_epu8_mask(values, threshold_512);
494 const unsigned right_count = std::popcount(right_mask);
495 directions.write_bits(right_mask, 64);
496 if constexpr (WriteLeft) {
497 _mm512_mask_compressstoreu_epi8(ranks.data() + left, ~right_mask, values);
499 if constexpr (WriteRight) {
500 _mm512_mask_compressstoreu_epi8(right_output.data() + right, right_mask,
503 left += 64 - right_count;
504 right += right_count;
508 for (; offset + 32 <= ranks.size(); offset += 32) {
509 const __m128i low = _mm_loadu_si128(
510 reinterpret_cast<const __m128i*
>(ranks.data() + offset));
511 const __m128i high = _mm_loadu_si128(
512 reinterpret_cast<const __m128i*
>(ranks.data() + offset + 16));
513 const std::uint16_t low_mask = wavelet_direction_mask_16(low, threshold);
514 const std::uint16_t high_mask = wavelet_direction_mask_16(high, threshold);
515 const std::uint32_t right_mask =
516 low_mask | (
static_cast<std::uint32_t
>(high_mask) << 16);
517 const unsigned low_right = std::popcount(low_mask);
518 const unsigned low_left = 16 - low_right;
519 const unsigned right_count = std::popcount(right_mask);
520 directions.write_bits(right_mask, 32);
522 if constexpr (WriteLeft || WriteRight) {
523 const __m128i compacted_low = compact_wavelet_16(low, low_mask);
524 const __m128i compacted_high = compact_wavelet_16(high, high_mask);
525 if constexpr (WriteLeft) {
526 _mm_storeu_si128(
reinterpret_cast<__m128i*
>(ranks.data() + left),
529 reinterpret_cast<__m128i*
>(ranks.data() + left + low_left),
532 if constexpr (WriteRight) {
534 reinterpret_cast<__m128i*
>(right_output.data() + right),
535 _mm_shuffle_epi8(compacted_low, reverse));
537 reinterpret_cast<__m128i*
>(right_output.data() + right + low_right),
538 _mm_shuffle_epi8(compacted_high, reverse));
541 left += 32 - right_count;
542 right += right_count;
545 for (; offset + 16 <= ranks.size(); offset += 16) {
546 const __m128i values = _mm_loadu_si128(
547 reinterpret_cast<const __m128i*
>(ranks.data() + offset));
548 const std::uint16_t right_mask =
549 wavelet_direction_mask_16(values, threshold);
550 const unsigned right_count = std::popcount(right_mask);
551 directions.write_bits(right_mask, 16);
552 if constexpr (WriteLeft || WriteRight) {
553 const __m128i compacted = compact_wavelet_16(values, right_mask);
554 if constexpr (WriteLeft) {
555 _mm_storeu_si128(
reinterpret_cast<__m128i*
>(ranks.data() + left),
558 if constexpr (WriteRight) {
560 reinterpret_cast<__m128i*
>(right_output.data() + right),
561 _mm_shuffle_epi8(compacted, reverse));
564 left += 16 - right_count;
565 right += right_count;
568 for (; offset < ranks.size(); ++offset) {
569 const std::uint8_t rank = ranks[offset];
570 const bool goes_right = rank >= first_right;
571 directions.write_bit(goes_right);
573 if constexpr (WriteRight) {
574 right_output[right] = rank;
578 if constexpr (WriteLeft) {
596 std::span<std::uint8_t> ranks,
597 std::uint8_t first_right,
598 std::span<std::uint8_t> right_output,
602 if (write_right && right_output.size() < ranks.size()) {
603 throw std::invalid_argument(
"Invalid wavelet block partition buffers");
606 const auto partition = [&]<
bool WriteLeft,
bool WriteRight>() {
607#if defined(PIXIE_AVX2_SUPPORT)
608 return partition_wavelet_byte_block_x86<WriteLeft, WriteRight>(
609 ranks, first_right, right_output, directions);
610#elif defined(PIXIE_WAVELET_NEON_SUPPORT)
611 return partition_wavelet_byte_block_neon<WriteLeft, WriteRight>(
612 ranks, first_right, right_output, directions);
614 return partition_wavelet_byte_block_scalar<WriteLeft, WriteRight>(
615 ranks, first_right, right_output, directions);
619 return write_right ? partition.template operator()<
true,
true>()
620 : partition.template operator()<
true,
false>();
622 return write_right ? partition.template operator()<
false,
true>()
623 : partition.template operator()<
false,
false>();
628#if defined(PIXIE_WAVELET_NEON_SUPPORT)
629#undef PIXIE_WAVELET_NEON_SUPPORT
Builder for a packed LSB-first bit sequence.
Definition packed_bit_builder.h:23
void write_bits(std::uint64_t bits, std::size_t width)
Append the low width bits of bits, least-significant bit first.
Definition packed_bit_builder.h:75
void map_wavelet_byte_ranks(std::span< const std::uint8_t > symbols, std::span< std::uint8_t > ranks, const std::array< std::uint8_t, 256 > &symbol_to_rank, const std::array< std::uint16_t, 256 > &symbol_to_high_rank)
Definition wavelet_partition.h:262
void partition_wavelet_ranks(std::span< const Symbol > input, Symbol middle, std::span< Symbol > output, std::size_t expected_left, bool write_left, bool write_right, PackedBitBuilder &directions)
Build packed node directions and stably partition ranks.
Definition wavelet_partition.h:224
std::size_t partition_wavelet_byte_block(std::span< std::uint8_t > ranks, std::uint8_t first_right, std::span< std::uint8_t > right_output, bool write_left, bool write_right, PackedBitBuilder &directions)
Emit a cache-sized byte block's directions and live child ranks.
Definition wavelet_partition.h:595