Pixie
Loading...
Searching...
No Matches
slot_order.h
Go to the documentation of this file.
1#pragma once
2
14
15#include <pixie/bits.h>
16
17#include <algorithm>
18#include <array>
19#include <cassert>
20#include <cstddef>
21#include <cstdint>
22#include <limits>
23#include <type_traits>
24
25namespace pixie::experimental {
26namespace slot_order_detail {
27
28template <class Word>
29constexpr Word mask(std::size_t bits) noexcept {
30 return bits >= sizeof(Word) * 8 ? ~Word{0} : (Word{1} << bits) - 1;
31}
32
33// Rotate a bounded field stream. Unchanged bits, including padding, survive.
34template <class Word, std::size_t Words>
35void rotate_bits(std::array<Word, Words>& words,
36 std::size_t left,
37 std::size_t length,
38 std::size_t distance) noexcept {
39 constexpr auto width = sizeof(Word) * 8;
40 if constexpr (Words == 1) {
41 const auto region_mask = mask<Word>(length);
42 const auto region = (words[0] >> left) & region_mask;
43 const auto rotated = (region >> distance) | (region << (length - distance));
44 words[0] =
45 (words[0] & ~(region_mask << left)) | ((rotated & region_mask) << left);
46 }
47#ifdef __SIZEOF_INT128__
48 else if constexpr (sizeof(Word) == 8 && Words == 2) {
49 using Wide = __uint128_t;
50 const auto original = Wide(words[0]) | (Wide(words[1]) << 64);
51 const auto region_mask = mask<Wide>(length);
52 const auto region = (original >> left) & region_mask;
53 const auto rotated = (region >> distance) | (region << (length - distance));
54 const auto result =
55 (original & ~(region_mask << left)) | ((rotated & region_mask) << left);
56 words[0] = Word(result);
57 words[1] = Word(result >> 64);
58 }
59#endif
60 else {
61 const auto original = words;
62 auto copy = [&](std::size_t source, std::size_t target, std::size_t count) {
63 while (count != 0) {
64 const auto source_bit = source % width;
65 const auto target_bit = target % width;
66 const auto chunk =
67 std::min({count, width - source_bit, width - target_bit});
68 const auto bits = mask<Word>(chunk);
69 const auto value = (original[source / width] >> source_bit) & bits;
70 auto& destination = words[target / width];
71 destination =
72 (destination & ~(bits << target_bit)) | (value << target_bit);
73 source += chunk;
74 target += chunk;
75 count -= chunk;
76 }
77 };
78 copy(left + distance, left, length - distance);
79 copy(left, left + length - distance, distance);
80 }
81}
82
83template <std::size_t N>
84inline constexpr auto identity = [] {
85 std::array<std::uint8_t, N> result{};
86 for (std::size_t i = 0; i < N; ++i) {
87 result[i] = i;
88 }
89 return result;
90}();
91
92// SIMD indices select source bytes for the same half-open rotation as
93// std::rotate.
94template <std::size_t N>
95void rotate_bytes(std::array<std::uint8_t, N>& bytes,
96 std::size_t left,
97 std::size_t right,
98 std::size_t distance) noexcept {
99#if defined(PIXIE_AVX512_SUPPORT) && defined(__AVX512VBMI__)
100 if constexpr (N == 64) {
101 const auto positions = _mm512_loadu_si512(identity<N>.data());
102 auto source = _mm512_add_epi8(positions, _mm512_set1_epi8(distance));
103 source = _mm512_mask_sub_epi8(
104 source, _mm512_cmpge_epi8_mask(source, _mm512_set1_epi8(right)), source,
105 _mm512_set1_epi8(right - left));
106 const auto active =
107 _mm512_cmpge_epi8_mask(positions, _mm512_set1_epi8(left)) &
108 _mm512_cmplt_epi8_mask(positions, _mm512_set1_epi8(right));
109 source = _mm512_mask_mov_epi8(positions, active, source);
110 const auto input = _mm512_loadu_si512(bytes.data());
111 _mm512_storeu_si512(bytes.data(), _mm512_permutexvar_epi8(source, input));
112 return;
113 }
114#endif
115#ifdef PIXIE_AVX2_SUPPORT
116 const auto input0 =
117 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes.data()));
118 const auto bank0 = _mm256_permute2x128_si256(input0, input0, 0x00);
119 const auto bank1 = _mm256_permute2x128_si256(input0, input0, 0x11);
120 __m256i bank2{}, bank3{};
121 if constexpr (N == 64) {
122 const auto input1 =
123 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(bytes.data() + 32));
124 bank2 = _mm256_permute2x128_si256(input1, input1, 0x00);
125 bank3 = _mm256_permute2x128_si256(input1, input1, 0x11);
126 }
127 for (std::size_t offset = 0; offset < N; offset += 32) {
128 const auto positions = _mm256_loadu_si256(
129 reinterpret_cast<const __m256i*>(identity<N>.data() + offset));
130 auto source = _mm256_add_epi8(positions, _mm256_set1_epi8(distance));
131 source = _mm256_sub_epi8(
132 source,
133 _mm256_and_si256(_mm256_cmpgt_epi8(source, _mm256_set1_epi8(right - 1)),
134 _mm256_set1_epi8(right - left)));
135 const auto active = _mm256_and_si256(
136 _mm256_cmpgt_epi8(positions,
137 _mm256_set1_epi8(static_cast<int>(left) - 1)),
138 _mm256_cmpgt_epi8(_mm256_set1_epi8(right), positions));
139 source = _mm256_blendv_epi8(positions, source, active);
140 const auto upper = _mm256_cmpgt_epi8(
141 _mm256_and_si256(source, _mm256_set1_epi8(16)), _mm256_setzero_si256());
142 auto result = _mm256_blendv_epi8(_mm256_shuffle_epi8(bank0, source),
143 _mm256_shuffle_epi8(bank1, source), upper);
144 if constexpr (N == 64) {
145 const auto second =
146 _mm256_blendv_epi8(_mm256_shuffle_epi8(bank2, source),
147 _mm256_shuffle_epi8(bank3, source), upper);
148 result = _mm256_blendv_epi8(
149 result, second, _mm256_cmpgt_epi8(source, _mm256_set1_epi8(31)));
150 }
151 _mm256_storeu_si256(reinterpret_cast<__m256i*>(bytes.data() + offset),
152 result);
153 }
154#elif defined(PIXIE_SSE41_SUPPORT)
155 __m128i banks[N / 16];
156 for (std::size_t i = 0; i < N / 16; ++i) {
157 banks[i] = _mm_loadu_si128(
158 reinterpret_cast<const __m128i*>(bytes.data() + 16 * i));
159 }
160 for (std::size_t offset = 0; offset < N; offset += 16) {
161 const auto positions = _mm_loadu_si128(
162 reinterpret_cast<const __m128i*>(identity<N>.data() + offset));
163 auto source = _mm_add_epi8(positions, _mm_set1_epi8(distance));
164 source = _mm_sub_epi8(
165 source, _mm_and_si128(_mm_cmpgt_epi8(source, _mm_set1_epi8(right - 1)),
166 _mm_set1_epi8(right - left)));
167 const auto active = _mm_and_si128(
168 _mm_cmpgt_epi8(positions, _mm_set1_epi8(static_cast<int>(left) - 1)),
169 _mm_cmpgt_epi8(_mm_set1_epi8(right), positions));
170 source = _mm_blendv_epi8(positions, source, active);
171 auto result = _mm_shuffle_epi8(banks[0], source);
172 for (std::size_t i = 1; i < N / 16; ++i) {
173 result =
174 _mm_blendv_epi8(result, _mm_shuffle_epi8(banks[i], source),
175 _mm_cmpgt_epi8(source, _mm_set1_epi8(16 * i - 1)));
176 }
177 _mm_storeu_si128(reinterpret_cast<__m128i*>(bytes.data() + offset), result);
178 }
179#else
180 std::rotate(bytes.begin() + left, bytes.begin() + left + distance,
181 bytes.begin() + right);
182#endif
183}
184} // namespace slot_order_detail
185
194template <std::size_t N>
196 static_assert(N == 32 || N == 64);
197 static constexpr std::size_t high_bits = N == 32 ? 1 : 2;
198 using HighWord = std::conditional_t<N == 32, std::uint32_t, std::uint64_t>;
199 std::array<std::uint64_t, N / 16> low_{};
200 std::array<HighWord, N * high_bits / (sizeof(HighWord) * 8)> high_{};
201 std::uint8_t count_;
202
203 public:
205 static constexpr std::size_t capacity = N;
207 static constexpr std::size_t order_bytes = N * (4 + high_bits) / 8;
209 explicit SplitSlotOrder(std::size_t count = 0) noexcept : count_(count) {
210 assert(count <= N);
211 for (std::size_t i = 0; i < count; ++i) {
212 low_[i / 16] |= std::uint64_t(i & 15) << (4 * (i % 16));
213 constexpr auto width = sizeof(HighWord) * 8;
214 high_[i * high_bits / width] |= HighWord(i >> 4)
215 << (i * high_bits % width);
216 }
217 }
218
219 std::size_t size() const noexcept { return count_; }
221 unsigned operator[](std::size_t i) const noexcept {
222 assert(i < size());
223 constexpr auto width = sizeof(HighWord) * 8;
224 const auto high =
225 (high_[i * high_bits / width] >> (i * high_bits % width)) &
226 ((1u << high_bits) - 1);
227 return unsigned((low_[i / 16] >> (4 * (i % 16))) & 15) |
228 (unsigned(high) << 4);
229 }
230
231 bool occupied(std::size_t i) const noexcept {
232 assert(i < N);
233 return i < size();
234 }
235
236 bool valid() const noexcept {
237 std::uint64_t seen = 0;
238 for (std::size_t i = 0; i < size(); ++i) {
239 const auto value = (*this)[i];
240 if (value >= size() || (seen & (std::uint64_t{1} << value))) {
241 return false;
242 }
243 seen |= std::uint64_t{1} << value;
244 }
245 return size() <= N;
246 }
247
250 void rotate_left(std::size_t left,
251 std::size_t right,
252 std::size_t distance) noexcept {
253 assert(left <= right && right <= size());
254 const auto length = right - left;
255 if (length == 0 || (distance %= length) == 0) {
256 return;
257 }
258 slot_order_detail::rotate_bits(low_, 4 * left, 4 * length, 4 * distance);
259 slot_order_detail::rotate_bits(high_, high_bits * left, high_bits * length,
260 high_bits * distance);
261 }
262
265 std::size_t rotate_children(std::array<void*, N>&,
266 std::size_t left,
267 std::size_t right,
268 std::size_t distance) noexcept {
269 rotate_left(left, right, distance);
270 return 0;
271 }
272};
273
281template <std::size_t N, bool Simd = true>
283 static_assert(N == 32 || N == 64);
284 std::array<std::uint8_t, N> order_ = slot_order_detail::identity<N>;
285 std::uint8_t count_;
286
287 public:
289 static constexpr std::size_t capacity = N;
291 static constexpr std::size_t order_bytes = N;
293 explicit ByteSlotOrder(std::size_t count = 0) noexcept : count_(count) {
294 assert(count <= N);
295 }
296
297 std::size_t size() const noexcept { return count_; }
299 unsigned operator[](std::size_t i) const noexcept {
300 assert(i < size());
301 return order_[i];
302 }
303
304 bool occupied(std::size_t i) const noexcept {
305 assert(i < N);
306 return i < size();
307 }
308
309 bool valid() const noexcept {
310 std::uint64_t seen = 0;
311 for (std::size_t i = 0; i < size(); ++i) {
312 const auto value = (*this)[i];
313 if (value >= size() || (seen & (std::uint64_t{1} << value))) {
314 return false;
315 }
316 seen |= std::uint64_t{1} << value;
317 }
318 return size() <= N;
319 }
320
323 void rotate_left(std::size_t left,
324 std::size_t right,
325 std::size_t distance) noexcept {
326 assert(left <= right && right <= size());
327 const auto length = right - left;
328 if (length == 0 || (distance %= length) == 0) {
329 return;
330 }
331 if constexpr (Simd) {
332 slot_order_detail::rotate_bytes(order_, left, right, distance);
333 } else {
334 std::rotate(order_.begin() + left, order_.begin() + left + distance,
335 order_.begin() + right);
336 }
337 }
338
341 std::size_t rotate_children(std::array<void*, N>&,
342 std::size_t left,
343 std::size_t right,
344 std::size_t distance) noexcept {
345 rotate_left(left, right, distance);
346 return 0;
347 }
348};
349} // namespace pixie::experimental
static constexpr std::size_t order_bytes
Order bytes, excluding count and alignment padding.
Definition slot_order.h:291
static constexpr std::size_t capacity
Maximum occupied count.
Definition slot_order.h:289
bool occupied(std::size_t i) const noexcept
Whether physical slot i is occupied; requires i < N.
Definition slot_order.h:304
unsigned operator[](std::size_t i) const noexcept
Physical index; requires i < size().
Definition slot_order.h:299
bool valid() const noexcept
Check mapping uniqueness and occupancy.
Definition slot_order.h:309
ByteSlotOrder(std::size_t count=0) noexcept
Construct identity order; requires count <= N.
Definition slot_order.h:293
void rotate_left(std::size_t left, std::size_t right, std::size_t distance) noexcept
Rotate [left,right); reduce distance modulo length; empty is a no-op.
Definition slot_order.h:323
std::size_t rotate_children(std::array< void *, N > &, std::size_t left, std::size_t right, std::size_t distance) noexcept
Rotate metadata using rotate_left's contract; pointers are untouched.
Definition slot_order.h:341
std::size_t size() const noexcept
Occupied count in [0,N].
Definition slot_order.h:297
void rotate_left(std::size_t left, std::size_t right, std::size_t distance) noexcept
Rotate [left,right) by distance modulo its length; empty is a no-op.
Definition slot_order.h:250
bool valid() const noexcept
Check mapping uniqueness and occupancy.
Definition slot_order.h:236
static constexpr std::size_t order_bytes
Packed order bytes, excluding count and C++ alignment padding.
Definition slot_order.h:207
std::size_t rotate_children(std::array< void *, N > &, std::size_t left, std::size_t right, std::size_t distance) noexcept
Apply rotate_left's contract without touching caller-owned pointers.
Definition slot_order.h:265
std::size_t size() const noexcept
Occupied count, in [0,N].
Definition slot_order.h:219
static constexpr std::size_t capacity
Maximum number of occupied slots.
Definition slot_order.h:205
unsigned operator[](std::size_t i) const noexcept
Physical slot for a logical index strictly below size().
Definition slot_order.h:221
bool occupied(std::size_t i) const noexcept
Whether physical slot i is occupied; requires i < N.
Definition slot_order.h:231
SplitSlotOrder(std::size_t count=0) noexcept
Identity order for physical [0,count); requires count <= N.
Definition slot_order.h:209
Definition excess.h:90