Everett
Loading...
Searching...
No Matches
rank.h
Go to the documentation of this file.
1
13#pragma once
14
15#include <everett/backend.h>
16#include <simd/integer.h>
17
18#include <array>
19#include <bit>
20#include <cstdint>
21#include <limits>
22#include <span>
23#include <stdexcept>
24#include <vector>
25
26
27namespace everett {
28 // Three ten-bit populations at bits 0, 11 and 22. The zero spacers
29 // let a multiply sum up to 1536 without carrying between lanes.
30 struct rank_block {
31 std::uint32_t before;
32 std::uint32_t runs;
33 };
34 static_assert(sizeof(rank_block) == 8);
35
36 namespace rank_detail {
37 // Prefix helpers take 0..512 bits. Vector variants require eight readable
38 // words; rank_view keeps shorter allocation tails on the bounded path.
39 // Read only words contributing to the prefix, including a masked tail.
40 inline unsigned prefix512_portable(std::uint64_t const * words, unsigned bits) noexcept {
41 unsigned result = 0;
42 for (unsigned word = 0; word < (bits >> 6); ++word)
43 result += unsigned(std::popcount(words[word]));
44 if (bits & 63)
45 result += unsigned(std::popcount(words[bits >> 6] & ((std::uint64_t{1} << (bits & 63)) - 1)));
46 return result;
47 }
48
49 // Native kernels keep every intermediate in registers. Each byte contains
50 // at most 32 population bits across the complete 512-bit input.
51 template <simd::architecture Arch, unsigned Vector>
52 simd_inline auto prefix_vector(std::uint64_t const * words, unsigned bits) noexcept {
53 constexpr auto bytes = backend_detail::register_bytes<Arch>;
54 using W = simd::vec<std::uint64_t, bytes / 8, Arch>;
55 constexpr auto positions = [=] {
56 std::array<std::uint64_t, bytes / 8> result{};
57 for (unsigned i = 0; i < result.size(); ++i) result[i] = Vector * result.size() + i;
58 return result;
59 }();
60 auto boundary = W(bits >> 6);
61 auto mask = select(W(positions) < boundary, W(~std::uint64_t{0}),
62 select(W(positions) == boundary, W((std::uint64_t{1} << (bits & 63)) - 1), W(0)));
63 return popcount(reinterpret_bits<std::uint8_t>(W::loadu(words + Vector * (bytes / 8)) & mask));
64 }
65
66 template <simd::architecture Arch, unsigned Vector = 0>
67 simd_inline auto prefix_vectors(std::uint64_t const * words, unsigned bits) noexcept {
68 auto counts = prefix_vector<Arch, Vector>(words, bits);
69 if constexpr ((Vector + 1) * backend_detail::register_bytes<Arch> == 64) return counts;
70 else return counts + prefix_vectors<Arch, Vector + 1>(words, bits);
71 }
72
73 // Exactly eight readable words, with arbitrary uint64_t alignment.
74 template <simd::architecture Arch = simd::scalar>
75 simd_inline unsigned prefix512(std::uint64_t const * words, unsigned bits) noexcept {
76 if constexpr (std::same_as<Arch, simd::scalar>) return prefix512_portable(words, bits);
77 else return unsigned(reduce_add_widened(prefix_vectors<Arch>(words, bits)));
78 }
79
80 inline unsigned popcount512_portable(std::uint64_t const * words) noexcept {
81 unsigned total = 0;
82 for (unsigned i = 0; i < 8; ++i) total += unsigned(std::popcount(words[i]));
83 return total;
84 }
85
86 template <simd::architecture Arch, unsigned Vector = 0>
87 simd_inline auto population_vectors(std::uint8_t const * bytes) noexcept {
88 constexpr auto width = backend_detail::register_bytes<Arch>;
89 using V = simd::vec<std::uint8_t, width, Arch>;
90 auto counts = popcount(V::loadu(bytes + Vector * width));
91 if constexpr ((Vector + 1) * width == 64) return counts;
92 else return counts + population_vectors<Arch, Vector + 1>(bytes);
93 }
94
95 template <simd::architecture Arch = simd::scalar>
96 simd_inline unsigned popcount512(std::uint64_t const * words) noexcept {
97 if constexpr (std::same_as<Arch, simd::scalar>) return popcount512_portable(words);
98 else return unsigned(reduce_add_widened(population_vectors<Arch>(
99 reinterpret_cast<std::uint8_t const *>(words))));
100 }
101
102 // Caller supplies 0 <= run <= 3 and independent counts in [0,512].
103 // The stored spacer bits already leave eleven-bit lanes for the sum.
104 constexpr unsigned run_prefix(std::uint32_t packed, unsigned run) noexcept {
105 std::uint64_t selected = packed & ((std::uint64_t{1} << (11 * run)) - 1);
106 return unsigned(((selected * 0x400801ull) >> 22) & 2047u);
107 }
108
109 // The builder calls this at each 2048-bit block. Kept independent of
110 // payload allocation so counter transitions can be checked at 2^32 bits.
112 static constexpr bool starts_epoch(std::uint64_t block) noexcept {
113 return (block & ((std::uint64_t{1} << 21) - 1)) == 0;
114 }
115 std::uint32_t before(std::uint64_t block, std::uint64_t total) {
116 if (starts_epoch(block)) epoch_base = total;
117 if (total < epoch_base || total - epoch_base > std::numeric_limits<std::uint32_t>::max())
118 throw std::overflow_error("rank relative count");
119 return std::uint32_t(total - epoch_base);
120 }
121 std::uint64_t epoch_base = 0;
122 };
123 }
124
125 // Borrowed, native-endian/aligned spans. A future file-format reader owns
126 // endian conversion and mapping lifetime. The directory has 64 bits per
127 // 2048 source bits, plus one 64-bit absolute rank per 2^32 source bits.
128 // This structure supports rank only; it contains no select directory.
129 // Construction checks section shapes, not directory contents. Borrowed
130 // metadata must come from the builder or an independently validated reader.
131 struct rank_view {
132 rank_view(std::span<std::uint64_t const> words,
133 std::span<rank_block const> blocks,
134 std::span<std::uint64_t const> supers,
135 std::uint64_t bit_count)
136 : words_(words), blocks_(blocks), supers_(supers),
137 bit_count_(bit_count) {
138 if (bit_count > std::numeric_limits<std::uint64_t>::max() - 0xffffffffu ||
139 words.size() != ((bit_count + 63) >> 6) ||
140 blocks.size() != ((bit_count + 2047) >> 11) ||
141 supers.size() != ((bit_count + 0xffffffffu) >> 32))
142 throw std::invalid_argument("invalid rank spans");
143 }
144
145 std::uint64_t size() const noexcept { return bit_count_; }
146 // The final bit belongs to an existing word, including a partial tail.
147 template <simd::architecture Arch = simd::scalar>
148 std::uint64_t count() const {
149 if (!bit_count_) return 0;
150 auto last = bit_count_ - 1;
151 return rank<Arch>(last) + ((words_[last >> 6] >> (last & 63)) & 1);
152 }
153
154 // Exclusive rank at an existing bit. Use count() for the total population.
155 template <simd::architecture Arch = simd::scalar>
156 std::uint64_t rank(std::uint64_t position) const {
157 if (position >= bit_count_) throw std::out_of_range("rank position");
158 auto block = blocks_[position >> 11];
159 unsigned run = unsigned((position >> 9) & 3);
160 std::uint64_t result = supers_[position >> 32] + block.before;
161 result += rank_detail::run_prefix(block.runs, run);
162 auto word = (position >> 9) << 3;
163 auto bits = unsigned(position & 511);
164 if (!bits) return result;
165 if constexpr (!std::same_as<Arch, simd::scalar>)
166 if (words_.size() - word >= 8)
167 return result + rank_detail::prefix512<Arch>(words_.data() + word, bits);
168 return result + rank_detail::prefix512_portable(words_.data() + word, bits);
169 }
170
171 private:
172 std::span<std::uint64_t const> words_;
173 std::span<rank_block const> blocks_;
174 std::span<std::uint64_t const> supers_;
175 std::uint64_t bit_count_;
176 };
177
178 struct rank_index {
179 template <simd::architecture Arch = simd::scalar>
180 static rank_index build(std::span<std::uint64_t const> source, std::uint64_t bits) {
181 if (bits > std::numeric_limits<std::uint64_t>::max() - 0xffffffffu ||
182 source.size() != ((bits + 63) >> 6))
183 throw std::invalid_argument("rank source length");
184 rank_index result;
185 result.bit_count = bits;
186 result.words.assign(source.begin(), source.end());
187 if (bits & 63) result.words.back() &= (std::uint64_t{1} << (bits & 63)) - 1;
188 result.blocks.resize((bits + 2047) >> 11);
190 std::uint64_t total = 0;
191 auto begin_block = [&](std::uint64_t block) -> rank_block & {
192 auto & entry = result.blocks[block];
193 entry.before = cursor.before(block, total);
194 if (cursor.starts_epoch(block)) result.supers.push_back(cursor.epoch_base);
195 return entry;
196 };
197 auto full_blocks = bits >> 11;
198 for (std::uint64_t block = 0; block < full_blocks; ++block) {
199 auto & entry = begin_block(block);
200 auto words = result.words.data() + block * 32;
201 auto a = rank_detail::popcount512<Arch>(words);
202 auto b = rank_detail::popcount512<Arch>(words + 8);
203 auto c = rank_detail::popcount512<Arch>(words + 16);
204 auto d = rank_detail::popcount512<Arch>(words + 24);
205 entry.runs = a | (b << 11) | (c << 22);
206 total += a + b + c + d;
207 }
208 if (full_blocks != result.blocks.size()) {
209 auto & entry = begin_block(full_blocks);
210 unsigned counts[4]{};
211 auto first = full_blocks * 32;
212 // Only the final partial block needs bounded word loads. The owning
213 // copy has already cleared unused bits in its final word.
214 for (auto word = first; word < result.words.size(); ++word)
215 counts[(word - first) >> 3] += unsigned(std::popcount(result.words[word]));
216 entry.runs = counts[0] | (counts[1] << 11) | (counts[2] << 22);
217 total += counts[0] + counts[1] + counts[2] + counts[3];
218 }
219 return result;
220 }
221
222 rank_view view() const & { return {words, blocks, supers, bit_count}; }
223
224 rank_view view() const && = delete;
225
226 // Exposed sections allow a checked reader to supply mapped spans without
227 // copying through this owning build representation.
228 std::vector<std::uint64_t> words;
229 std::vector<rank_block> blocks;
230 std::vector<std::uint64_t> supers;
231 std::uint64_t bit_count = 0;
232 };
233}
Shares explicit SIMD architecture traits with Everett's kernels.
unsigned prefix512_portable(std::uint64_t const *words, unsigned bits) noexcept
Definition rank.h:40
unsigned popcount512_portable(std::uint64_t const *words) noexcept
Definition rank.h:80
auto prefix_vectors(std::uint64_t const *words, unsigned bits) noexcept
Definition rank.h:67
constexpr unsigned run_prefix(std::uint32_t packed, unsigned run) noexcept
Definition rank.h:104
auto prefix_vector(std::uint64_t const *words, unsigned bits) noexcept
Definition rank.h:52
unsigned popcount512(std::uint64_t const *words) noexcept
Definition rank.h:96
unsigned prefix512(std::uint64_t const *words, unsigned bits) noexcept
Definition rank.h:75
auto population_vectors(std::uint8_t const *bytes) noexcept
Definition rank.h:87
Definition active_engine.h:18
Definition rank.h:30
std::uint32_t runs
Definition rank.h:32
std::uint32_t before
Definition rank.h:31
std::uint32_t before(std::uint64_t block, std::uint64_t total)
Definition rank.h:115
static constexpr bool starts_epoch(std::uint64_t block) noexcept
Definition rank.h:112
std::uint64_t epoch_base
Definition rank.h:121
Definition rank.h:178
static rank_index build(std::span< std::uint64_t const > source, std::uint64_t bits)
Definition rank.h:180
std::vector< rank_block > blocks
Definition rank.h:229
std::vector< std::uint64_t > supers
Definition rank.h:230
rank_view view() const &
Definition rank.h:222
rank_view view() const &&=delete
std::vector< std::uint64_t > words
Definition rank.h:228
std::uint64_t bit_count
Definition rank.h:231
Definition rank.h:131
std::uint64_t rank(std::uint64_t position) const
Definition rank.h:156
std::span< rank_block const > blocks_
Definition rank.h:173
rank_view(std::span< std::uint64_t const > words, std::span< rank_block const > blocks, std::span< std::uint64_t const > supers, std::uint64_t bit_count)
Definition rank.h:132
std::uint64_t count() const
Definition rank.h:148
std::span< std::uint64_t const > words_
Definition rank.h:172
std::uint64_t size() const noexcept
Definition rank.h:145
std::span< std::uint64_t const > supers_
Definition rank.h:174
std::uint64_t bit_count_
Definition rank.h:175