Pixie
Loading...
Searching...
No Matches
wavelet_partition.h
Go to the documentation of this file.
1#pragma once
2
7
8#include <pixie/bits.h>
9#include <pixie/packed_bit_builder.h>
10
11#include <algorithm>
12#include <array>
13#include <bit>
14#include <concepts>
15#include <cstddef>
16#include <cstdint>
17#include <cstring>
18#include <limits>
19#include <span>
20#include <stdexcept>
21
22#if defined(PIXIE_AVX2_SUPPORT) || defined(PIXIE_AVX512_SUPPORT)
23#include <immintrin.h>
24#endif
25
26#if defined(__aarch64__) && defined(__ARM_NEON)
27#define PIXIE_WAVELET_NEON_SUPPORT
28#include <arm_neon.h>
29#endif
30
31namespace pixie::detail {
32
33#if defined(PIXIE_AVX2_SUPPORT)
34
35struct WaveletCompaction16Tables {
36 std::array<std::array<std::uint8_t, 16>, 256> low{};
37 std::array<std::array<std::uint8_t, 32>, 256> high{};
38};
39
40// Adapted from PivCo's p16rev partition tables. One shuffle places the left
41// ranks forward at the front and the right ranks in reverse at the back. A
42// fixed reverse shuffle then recovers the stable right partition.
43consteval WaveletCompaction16Tables make_wavelet_compaction_16_tables() {
44 WaveletCompaction16Tables tables;
45 for (std::size_t mask = 0; mask < 256; ++mask) {
46 std::size_t left = 0;
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);
51 } else {
52 tables.low[mask][right--] = static_cast<std::uint8_t>(index);
53 }
54 }
55
56 left = 8;
57 right = 15;
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);
61 } else {
62 tables.high[mask][right--] = static_cast<std::uint8_t>(index + 8);
63 }
64 }
65 }
66 return tables;
67}
68
69alignas(64) inline constexpr auto kWaveletCompaction16Tables =
70 make_wavelet_compaction_16_tables();
71
72inline std::uint16_t wavelet_direction_mask_16(__m128i ranks,
73 __m128i first_right) {
74 const __m128i right =
75 _mm_cmpeq_epi8(_mm_min_epu8(ranks, first_right), first_right);
76 return static_cast<std::uint16_t>(_mm_movemask_epi8(right));
77}
78
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));
88}
89
90#endif
91
92#if defined(PIXIE_WAVELET_NEON_SUPPORT)
93
94struct NeonWaveletCompactionTables {
95 std::array<std::array<std::uint8_t, 16>, 256> low{};
96 std::array<std::array<std::uint8_t, 32>, 256> high{};
97};
98
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);
107 } else {
108 tables.low[mask][right--] = static_cast<std::uint8_t>(index);
109 }
110 }
111 left = 8;
112 right = 15;
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);
116 } else {
117 tables.high[mask][right--] = static_cast<std::uint8_t>(index + 8);
118 }
119 }
120 }
121 return tables;
122}
123
124alignas(64) inline constexpr auto kNeonWaveletCompactionTables =
125 make_neon_wavelet_compaction_tables();
126
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));
136}
137
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));
148}
149
150#endif
151
152template <std::unsigned_integral Symbol>
153std::uint64_t wavelet_direction_mask(std::span<const Symbol> ranks,
154 Symbol middle) {
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;
158 }
159 return mask;
160}
161
162template <bool WriteLeft, bool WriteRight, std::unsigned_integral Symbol>
163void partition_wavelet_ranks_scalar(std::span<const Symbol> input,
164 Symbol middle,
165 std::span<Symbol> output,
166 PackedBitBuilder& directions,
167 std::size_t& left,
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);
174
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;
184 }
185 } else {
186 left += std::popcount(left_mask);
187 }
188
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;
195 }
196 } else {
197 right += std::popcount(remaining_right);
198 }
199 }
200}
201
202template <bool WriteLeft, bool WriteRight, std::unsigned_integral Symbol>
203void partition_wavelet_ranks_impl(std::span<const Symbol> input,
204 Symbol middle,
205 std::span<Symbol> output,
206 PackedBitBuilder& directions,
207 std::size_t& left,
208 std::size_t& right) {
209 partition_wavelet_ranks_scalar<WriteLeft, WriteRight>(
210 input, middle, output, directions, left, right);
211}
212
223template <std::unsigned_integral Symbol>
224void partition_wavelet_ranks(std::span<const Symbol> input,
225 Symbol middle,
226 std::span<Symbol> output,
227 std::size_t expected_left,
228 bool write_left,
229 bool write_right,
230 PackedBitBuilder& directions) {
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");
235 }
236
237 std::size_t left = 0;
238 std::size_t right = expected_left;
239 if (write_left) {
240 if (write_right) {
241 partition_wavelet_ranks_impl<true, true>(input, middle, output,
242 directions, left, right);
243 } else {
244 partition_wavelet_ranks_impl<true, false>(input, middle, output,
245 directions, left, right);
246 }
247 } else if (write_right) {
248 partition_wavelet_ranks_impl<false, true>(input, middle, output, directions,
249 left, right);
250 } else {
251 partition_wavelet_ranks_impl<false, false>(input, middle, output,
252 directions, left, right);
253 }
254
255 if (left != expected_left || right != input.size()) {
256 throw std::invalid_argument(
257 "Wavelet partition does not match the supplied symbol counts");
258 }
259}
260
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");
269 }
270 std::size_t offset = 0;
271#if defined(PIXIE_WAVELET_NEON_SUPPORT)
272 if (symbols.size() >= 20) {
273 uint8x16x4_t table0;
274 uint8x16x4_t table1;
275 uint8x16x4_t table2;
276 uint8x16x4_t table3;
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);
282 }
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);
288 std::uint32_t tail;
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));
306 }
307 }
308 (void)symbol_to_high_rank;
309#else
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));
317
318 std::array<std::uint16_t, 8> pairs;
319 for (std::size_t pair = 0; pair < 4; ++pair) {
320 pairs[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)];
323 low_symbols >>= 16;
324 pairs[pair + 4] =
325 symbol_to_rank[static_cast<std::uint8_t>(high_symbols)] +
326 symbol_to_high_rank[static_cast<std::uint8_t>(high_symbols >> 8)];
327 high_symbols >>= 16;
328 }
329 std::memcpy(ranks.data() + offset, pairs.data(), 16);
330 }
331 }
332#endif
333 for (; offset < symbols.size(); ++offset) {
334 ranks[offset] = symbol_to_rank[symbols[offset]];
335 }
336}
337
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) {
343 map_wavelet_byte_ranks(std::span<const std::uint8_t>(symbols), symbols,
344 symbol_to_rank, symbol_to_high_rank);
345}
346
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,
352 PackedBitBuilder& directions) {
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;
362 if (goes_right) {
363 if constexpr (WriteRight) {
364 right_output[right] = rank;
365 }
366 ++right;
367 } else {
368 if constexpr (WriteLeft) {
369 ranks[left] = rank;
370 }
371 ++left;
372 }
373 }
374 directions.write_bits(right_mask, width);
375 }
376 return left;
377}
378
379#if defined(PIXIE_WAVELET_NEON_SUPPORT)
380
381// PivCo's p16rev idea expressed with AArch64 TBL. The two halves are loaded
382// before either in-place store, and the template removes dead leaf scatters.
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;
396
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);
410
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);
418 }
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));
424 }
425 }
426 left += 32 - right_count;
427 right += right_count;
428 }
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);
439 }
440 if constexpr (WriteRight) {
441 vst1q_u8(right_output.data() + right, vqtbl1q_u8(compacted, reverse));
442 }
443 }
444 left += 16 - right_count;
445 right += right_count;
446 }
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);
451 if (goes_right) {
452 if constexpr (WriteRight) {
453 right_output[right] = rank;
454 }
455 ++right;
456 } else {
457 if constexpr (WriteLeft) {
458 ranks[left] = rank;
459 }
460 ++left;
461 }
462 }
463 return left;
464}
465
466#endif
467
468#if defined(PIXIE_AVX2_SUPPORT)
469
470// PivCo's non-flat p16rev kernel adapted to append direction masks directly to
471// a Pixie node stream. This deliberately keeps the full/right/none compile-time
472// specializations so leaf children do not cause dead scatter traffic.
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;
487
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);
498 }
499 if constexpr (WriteRight) {
500 _mm512_mask_compressstoreu_epi8(right_output.data() + right, right_mask,
501 values);
502 }
503 left += 64 - right_count;
504 right += right_count;
505 }
506#endif
507
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);
521
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),
527 compacted_low);
528 _mm_storeu_si128(
529 reinterpret_cast<__m128i*>(ranks.data() + left + low_left),
530 compacted_high);
531 }
532 if constexpr (WriteRight) {
533 _mm_storeu_si128(
534 reinterpret_cast<__m128i*>(right_output.data() + right),
535 _mm_shuffle_epi8(compacted_low, reverse));
536 _mm_storeu_si128(
537 reinterpret_cast<__m128i*>(right_output.data() + right + low_right),
538 _mm_shuffle_epi8(compacted_high, reverse));
539 }
540 }
541 left += 32 - right_count;
542 right += right_count;
543 }
544
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),
556 compacted);
557 }
558 if constexpr (WriteRight) {
559 _mm_storeu_si128(
560 reinterpret_cast<__m128i*>(right_output.data() + right),
561 _mm_shuffle_epi8(compacted, reverse));
562 }
563 }
564 left += 16 - right_count;
565 right += right_count;
566 }
567
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);
572 if (goes_right) {
573 if constexpr (WriteRight) {
574 right_output[right] = rank;
575 }
576 ++right;
577 } else {
578 if constexpr (WriteLeft) {
579 ranks[left] = rank;
580 }
581 ++left;
582 }
583 }
584 return left;
585}
586
587#endif
588
596 std::span<std::uint8_t> ranks,
597 std::uint8_t first_right,
598 std::span<std::uint8_t> right_output,
599 bool write_left,
600 bool write_right,
601 PackedBitBuilder& directions) {
602 if (write_right && right_output.size() < ranks.size()) {
603 throw std::invalid_argument("Invalid wavelet block partition buffers");
604 }
605
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);
613#else
614 return partition_wavelet_byte_block_scalar<WriteLeft, WriteRight>(
615 ranks, first_right, right_output, directions);
616#endif
617 };
618 if (write_left) {
619 return write_right ? partition.template operator()<true, true>()
620 : partition.template operator()<true, false>();
621 }
622 return write_right ? partition.template operator()<false, true>()
623 : partition.template operator()<false, false>();
624}
625
626} // namespace pixie::detail
627
628#if defined(PIXIE_WAVELET_NEON_SUPPORT)
629#undef PIXIE_WAVELET_NEON_SUPPORT
630#endif
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