Pixie
Loading...
Searching...
No Matches
bit_block.h
1#pragma once
2
3#include <pixie/bits.h>
4#include <pixie/storage/aligned.h>
5
6#include <algorithm>
7#include <array>
8#include <cassert>
9#include <cstddef>
10#include <cstdint>
11#include <limits>
12#include <span>
13#include <stdexcept>
14
16namespace pixie::permutations::detail {
17
27template <std::size_t PayloadBits = 2048>
28class alignas(CacheLine) BitBlock {
29 static_assert(PayloadBits >= 64 && PayloadBits % 64 == 0);
30 static_assert(PayloadBits <= std::numeric_limits<std::size_t>::max() / 2);
31
32 public:
34 using value_type = bool;
36 static constexpr std::size_t capacity = PayloadBits;
38 using Payload = std::array<std::uint64_t, PayloadBits / 64>;
41 static constexpr std::size_t payload_offset_bytes = 2 * sizeof(std::uint64_t);
44 static constexpr std::size_t payload_alignment = alignof(std::uint64_t);
45
47 BitBlock() noexcept {}
51 BitBlock(const BitBlock& other) noexcept : n_(other.n_), h_(other.h_) {
52 std::copy_n(other.bits_.begin(), n_ / 64 + (n_ % 64 != 0), bits_.begin());
53 }
57 BitBlock& operator=(const BitBlock& other) noexcept {
58 if (this != &other) {
59 n_ = other.n_;
60 h_ = other.h_;
61 std::copy_n(other.bits_.begin(), n_ / 64 + (n_ % 64 != 0), bits_.begin());
62 }
63 return *this;
64 }
72 BitBlock(std::span<const std::uint64_t> words, std::size_t n) {
73 if (n > capacity || n / 64 + (n % 64 != 0) > words.size()) {
74 throw std::invalid_argument("BitBlock: input size");
75 }
76 n_ = n;
77 std::copy_n(words.begin(), n / 64 + (n % 64 != 0), bits_.begin());
78 }
80 std::size_t size() const noexcept { return n_; }
82 bool empty() const noexcept { return n_ == 0; }
89 bool operator[](std::size_t i) const {
90 if (i >= n_) {
91 throw std::out_of_range("BitBlock: index");
92 }
93 const auto sum = h_ + i;
94 const auto p = sum >= n_ ? sum - n_ : sum;
95 return (bits_[p / 64] >> (p % 64)) & 1;
96 }
108 std::uint64_t read_bits(std::size_t offset,
109 std::size_t width) const noexcept {
110 assert(offset <= n_ && width <= 64 && width <= n_ - offset);
111 if (width == 0) {
112 return 0;
113 }
114 // Both operands are below n_, and capacity <= SIZE_MAX / 2. The
115 // circular sum cannot overflow and needs at most one subtraction.
116 const auto sum = h_ + offset;
117 const auto physical = sum >= n_ ? sum - n_ : sum;
118 const auto first = std::min(width, n_ - physical);
119 auto read = [&](std::size_t p, std::size_t count) {
120 const auto shift = p % 64;
121 auto value = bits_[p / 64] >> shift;
122 if (count > 64 - shift) {
123 value |= bits_[p / 64 + 1] << (64 - shift);
124 }
125 return count == 64 ? value : value & ((std::uint64_t{1} << count) - 1);
126 };
127 const auto value = read(physical, first);
128 return first == width ? value : value | (read(0, width - first) << first);
129 }
138 std::uint64_t read_aligned_word(std::size_t offset) const noexcept {
139 assert(n_ % 64 == 0 && h_ % 64 == 0 && offset % 64 == 0 && offset < n_);
140 // The common normalized case has no metadata-dependent value address.
141 if (h_ == 0) {
142 return bits_[offset / 64];
143 }
144 const auto sum = h_ + offset;
145 const auto physical = sum >= n_ ? sum - n_ : sum;
146 return bits_[physical / 64];
147 }
153 void write_aligned_word(std::size_t offset, std::uint64_t value) noexcept {
154 assert(n_ % 64 == 0 && h_ % 64 == 0 && offset % 64 == 0 && offset < n_);
155 const auto sum = h_ + offset;
156 bits_[(sum >= n_ ? sum - n_ : sum) / 64] = value;
157 }
164 void write_bits(std::size_t offset,
165 std::size_t width,
166 std::uint64_t value) noexcept {
167 assert(offset <= n_ && width <= 64 && width <= n_ - offset);
168 if (width == 0) {
169 return;
170 }
171 const auto sum = h_ + offset;
172 const auto p = sum >= n_ ? sum - n_ : sum;
173 const auto first = std::min(width, n_ - p);
174 copy_packed_bits(&value, 0, bits_.data(), p, first);
175 copy_packed_bits(&value, first, bits_.data(), 0, width - first);
176 }
181 Payload flatten() const noexcept {
182 Payload out{};
183 copy(0, out, 0, n_);
184 return out;
185 }
195 void rotate_left(std::size_t left,
196 std::size_t right,
197 std::size_t distance) noexcept {
198 assert(left <= right && right <= n_);
199 const auto length = right - left;
200 if (length == 0 || (distance %= length) == 0) {
201 return;
202 }
203 if (length == n_) {
204 h_ = (h_ + distance) % n_;
205 return;
206 }
207 Payload scratch{};
208 copy(left + distance, scratch, 0, length - distance);
209 copy(left, scratch, length - distance, distance);
210 const auto p = (h_ + left) % n_;
211 const auto prefix = std::min(length, n_ - p);
212 copy_packed_bits(scratch.data(), 0, bits_.data(), p, prefix);
213 copy_packed_bits(scratch.data(), prefix, bits_.data(), 0, length - prefix);
214 }
224 void redistribute(BitBlock& rhs, std::size_t left_size) noexcept {
225 assert(this != &rhs);
226 const auto total = n_ + rhs.n_;
227 assert(left_size <= capacity && left_size <= total &&
228 total - left_size <= capacity);
229 if (left_size == total && h_ == 0) {
230 // A normalized prefix stays in place when absorbing the whole donor.
231 // Copy only the incoming bits, including a possibly circular donor.
232 // Partial-word writes preserve destination padding, so initialize newly
233 // exposed words before the copy kernel can read those padding bits.
234 std::fill(bits_.begin() + n_ / 64 + (n_ % 64 != 0),
235 bits_.begin() + total / 64 + (total % 64 != 0), 0);
236 rhs.copy(0, bits_, n_, rhs.n_);
237 n_ = total;
238 rhs.n_ = rhs.h_ = 0;
239 return;
240 }
241 Payload a{}, b{};
242 auto gather = [&](std::size_t start, Payload& out, std::size_t count) {
243 const auto first = start < n_ ? std::min(count, n_ - start) : 0;
244 copy(start, out, 0, first);
245 rhs.copy(start + first - std::min(start + first, n_), out, first,
246 count - first);
247 };
248 gather(0, a, left_size);
249 gather(left_size, b, total - left_size);
250 bits_ = a;
251 rhs.bits_ = b;
252 n_ = left_size;
253 rhs.n_ = total - left_size;
254 h_ = rhs.h_ = 0;
255 }
260 void normalize() noexcept {
261 if (h_ == 0) {
262 return;
263 }
264 Payload scratch{};
265 copy(0, scratch, 0, n_);
266 bits_ = scratch;
267 h_ = 0;
268 }
273 std::uint64_t read_normalized_word(std::size_t offset) const noexcept {
274 assert(h_ == 0 && offset % 64 == 0 && offset <= n_ && n_ - offset >= 64);
275 return bits_[offset / 64];
276 }
279 std::size_t payload_capacity_bytes() const noexcept { return sizeof(bits_); }
282 std::size_t metadata_bytes() const noexcept {
283 return sizeof(*this) - sizeof(bits_);
284 }
285
286 private:
287 void copy(std::size_t offset,
288 Payload& out,
289 std::size_t destination,
290 std::size_t count) const noexcept {
291 if (count == 0) {
292 return;
293 }
294 const auto p = (h_ + offset) % n_;
295 const auto prefix = std::min(count, n_ - p);
296 copy_packed_bits(bits_.data(), p, out.data(), destination, prefix);
297 copy_packed_bits(bits_.data(), 0, out.data(), destination + prefix,
298 count - prefix);
299 }
300 std::uint64_t n_ = 0;
301 std::uint64_t h_ = 0;
302 Payload bits_;
303};
304
305} // namespace pixie::permutations::detail