4#include <pixie/storage/aligned.h>
16namespace pixie::permutations::detail {
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);
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);
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());
57 BitBlock& operator=(
const BitBlock& other)
noexcept {
61 std::copy_n(other.bits_.begin(), n_ / 64 + (n_ % 64 != 0), bits_.begin());
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");
77 std::copy_n(words.begin(), n / 64 + (n % 64 != 0), bits_.begin());
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 {
91 throw std::out_of_range(
"BitBlock: index");
93 const auto sum = h_ + i;
94 const auto p = sum >= n_ ? sum - n_ : sum;
95 return (bits_[p / 64] >> (p % 64)) & 1;
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);
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);
125 return count == 64 ? value : value & ((std::uint64_t{1} << count) - 1);
127 const auto value = read(physical, first);
128 return first == width ? value : value | (read(0, width - first) << first);
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_);
142 return bits_[offset / 64];
144 const auto sum = h_ + offset;
145 const auto physical = sum >= n_ ? sum - n_ : sum;
146 return bits_[physical / 64];
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;
164 void write_bits(std::size_t offset,
166 std::uint64_t value)
noexcept {
167 assert(offset <= n_ && width <= 64 && width <= n_ - offset);
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);
181 Payload flatten() const noexcept {
195 void rotate_left(std::size_t left,
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) {
204 h_ = (h_ + distance) % n_;
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);
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) {
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_);
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,
248 gather(0, a, left_size);
249 gather(left_size, b, total - left_size);
253 rhs.n_ = total - left_size;
260 void normalize() noexcept {
265 copy(0, scratch, 0, n_);
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];
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_);
287 void copy(std::size_t offset,
289 std::size_t destination,
290 std::size_t count)
const noexcept {
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,
300 std::uint64_t n_ = 0;
301 std::uint64_t h_ = 0;