20#include <simd/integer.h>
21#include <simd/packing.h>
34 namespace elias_fano_detail {
35 constexpr std::uint64_t
add(std::uint64_t a, std::uint64_t b)
noexcept {
return a + b; }
36 constexpr std::uint64_t
multiply(std::uint64_t a, std::uint64_t b)
noexcept {
return a * b; }
37 inline std::uint64_t
words(std::uint64_t bits)
noexcept {
38 return (bits + 63) >> 6;
41 template <simd::architecture Arch = simd::scalar>
42 inline bool monotone(std::span<std::uint64_t const> source)
noexcept {
43 if (source.size() < 2)
return true;
45 if constexpr (!std::is_same_v<Arch, simd::scalar>) {
46 using V = simd::vec<std::uint64_t, backend_detail::register_bytes<Arch> / 8, Arch>;
47 for (; source.size() - i >= V::lanes; i += V::lanes)
48 if (any(V::load(source.data() + i) < V::load(source.data() + i - 1)))
return false;
50 for (; i < source.size(); ++i)
if (source[i] < source[i - 1])
return false;
57 template <simd::architecture Arch = simd::scalar>
58 inline unsigned select_word(std::uint64_t value,
unsigned ordinal)
noexcept {
59 if constexpr (std::is_same_v<Arch, simd::avx2> || std::is_same_v<Arch, simd::avx512>)
60 return unsigned(std::countr_zero(deposit_bits(Arch{}, std::uint64_t{1} << ordinal, value)));
62 auto pairs = value - ((value >> 1) & 0x5555555555555555ull);
63 auto nibbles = (pairs & 0x3333333333333333ull) + ((pairs >> 2) & 0x3333333333333333ull);
64 auto bytes = (nibbles + (nibbles >> 4)) & 0x0f0f0f0f0f0f0f0full;
65 auto prefixes = bytes * 0x0101010101010101ull;
66 auto marked = ((prefixes | 0x8080808080808080ull) -
67 (ordinal + 1) * 0x0101010101010101ull) & 0x8080808080808080ull;
68 auto shift = unsigned(std::countr_zero(marked)) & ~7u;
69 if (shift) ordinal -= unsigned((prefixes >> (shift - 8)) & 255);
70 auto lower = unsigned((nibbles >> shift) & 15);
71 auto step = unsigned(ordinal >= lower);
73 ordinal -= step * lower;
74 auto bits = unsigned(value >> shift) & 15;
75 lower = (bits & 1) + ((bits >> 1) & 1);
76 step = unsigned(ordinal >= lower);
78 ordinal -= step * lower;
79 return shift + ordinal + unsigned(((value >> shift) & 1) == 0);
83 template <
unsigned W, std::
size_t O, std::
size_t I>
84 inline std::uint64_t
low_component(std::uint64_t
const * source)
noexcept {
85 constexpr auto mask = (std::uint64_t{1} << W) - 1;
86 constexpr auto bit = I * W;
87 constexpr auto first = O * 64;
88 if constexpr (
bit < first)
return (source[I] & mask) >> (first -
bit);
89 else return (source[I] & mask) << (
bit - first);
92 template <
unsigned W, std::size_t O, std::size_t... I>
93 inline std::uint64_t
low_word(std::uint64_t
const * source, std::index_sequence<I...>)
noexcept {
94 constexpr auto first = O * 64 / W;
95 return (low_component<W, O, first + I>(source) | ...);
98 template <
unsigned W, std::size_t... O>
99 inline void low_tile(std::uint64_t
const * source, std::uint64_t * out,
100 std::index_sequence<O...>)
noexcept {
101 ((out[O] = low_word<W, O>(source,
102 std::make_index_sequence<(O * 64 + 63) / W - O * 64 / W + 1>{})), ...);
109 template <
unsigned W, simd::architecture Arch = simd::scalar>
111 std::span<std::uint64_t> out)
noexcept {
112 static_assert(W <= 63);
114 constexpr auto divisor = std::gcd(W, 64u);
115 constexpr auto inputs = 64 / divisor;
116 constexpr auto outputs = W / divisor;
118 for (; source.size() - at >= inputs; at += inputs) {
119 auto destination = out.data() + (at / inputs) * outputs;
122 if constexpr (!std::is_same_v<Arch, simd::scalar> &&
123 std::endian::native == std::endian::little && (W == 8 || W == 16)) {
124 using V = simd::vec<std::uint64_t, 2, Arch>;
125 auto a = narrow_concat<std::uint32_t>(V::load(source.data() + at),
126 V::load(source.data() + at + 2));
127 if constexpr (W == 16) {
128 auto packed = narrow_concat<std::uint16_t>(a,
decltype(a){});
129 reinterpret_bits<std::uint64_t>(packed).store_partial(destination, 1);
131 auto b = narrow_concat<std::uint32_t>(V::load(source.data() + at + 4),
132 V::load(source.data() + at + 6));
133 auto half = narrow_concat<std::uint16_t>(a, b);
134 auto packed = narrow_concat<std::uint8_t>(half,
decltype(half){});
135 reinterpret_bits<std::uint64_t>(packed).store_partial(destination, 1);
138 low_tile<W>(source.data() + at, destination, std::make_index_sequence<outputs>{});
140 auto tail = out.subspan((at / inputs) * outputs);
141 std::fill(tail.begin(), tail.end(), 0);
142 constexpr auto mask = (std::uint64_t{1} << W) - 1;
143 for (std::size_t i = 0; i != source.size() - at; ++i) {
145 unsigned shift = unsigned(
bit & 63);
146 auto value = source[at + i] & mask;
147 tail[
bit >> 6] |= value << shift;
148 if (shift + W > 64) tail[(
bit >> 6) + 1] |= value >> (64 - shift);
153 template <simd::architecture Arch, std::size_t... W>
155 using packer = void (*)(std::span<std::uint64_t const>, std::span<std::uint64_t>)
noexcept;
156 return std::array<packer,
sizeof...(W)>{&pack_low_fixed<W, Arch>...};
162 template <simd::architecture Arch = simd::scalar>
163 inline void pack_low(std::span<std::uint64_t const> source,
164 std::span<std::uint64_t> out,
unsigned width) {
165 if (width > 63) error_detail::raise<std::invalid_argument>(
"elias_fano low width");
166 if (width && source.size() > (std::numeric_limits<std::uint64_t>::max() - 63) / width)
167 error_detail::raise<std::overflow_error>(
"elias_fano low section extent");
168 if (out.size() !=
words(source.size() * width))
169 error_detail::raise<std::invalid_argument>(
"elias_fano low output size");
170 static constexpr auto packers = low_packers<Arch>(std::make_index_sequence<64>{});
171 packers[width](source, out);
178 inline void write_high(std::span<std::uint64_t const> source,
179 std::span<std::uint64_t> out,
unsigned width)
noexcept {
180 std::uint64_t word = 0, value = 0;
181 for (std::uint64_t i = 0; i != source.size(); ++i) {
182 auto position = (source[i] >> width) + i;
183 auto next = position >> 6;
189 value |= std::uint64_t{1} << (position & 63);
191 if (!source.empty()) out[word] = value;
216 std::span<std::uint64_t const> high,
217 std::span<elias_fano_sample const>
samples,
218 std::span<std::uint64_t const> sparse,
219 std::uint64_t entry_count, std::uint64_t
universe,
224 template <
class Words,
class Samples>
225 requires (std::is_same_v<Words, word_view> && std::is_same_v<Samples, sample_view>)
230 if (
low_width > 63) error_detail::raise<std::invalid_argument>(
"elias_fano low width");
233 error_detail::raise<std::invalid_argument>(
"invalid empty Elias-Fano sections");
236 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max() - 255;
237 auto entries = entry_count;
241 error_detail::raise<std::overflow_error>(
"elias_fano section extent");
247 error_detail::raise<std::invalid_argument>(
"invalid elias_fano spans");
258 template <simd::architecture Arch = simd::scalar>
259 std::uint64_t
select(std::uint64_t ordinal)
const {
261 error_detail::raise<std::out_of_range>(
"Elias-Fano ordinal");
262 return decode(ordinal, select_high<Arch>(ordinal));
265 template <simd::architecture Arch = simd::scalar>
270 std::uint64_t
decode(std::uint64_t ordinal, std::uint64_t position)
const {
271 if (position < ordinal) error_detail::raise<std::invalid_argument>(
"invalid elias_fano high value");
272 auto hi = position - ordinal;
273 if (hi > (
universe_ >>
low_width_)) error_detail::raise<std::invalid_argument>(
"elias_fano high overflow");
274 std::uint64_t lo = 0;
277 auto word =
bit >> 6;
278 unsigned shift = unsigned(
bit & 63);
279 lo =
low_[word] >> shift;
280 if (shift +
low_width_ > 64) lo |=
low_[word + 1] << (64 - shift);
284 if (value >
universe_) error_detail::raise<std::invalid_argument>(
"elias_fano value exceeds universe");
288 template <simd::architecture Arch>
290 auto sample =
samples_[ordinal >> 8];
291 unsigned remaining = unsigned(ordinal & 255);
292 if (sample.sparse != std::numeric_limits<std::uint64_t>::max()) {
294 error_detail::raise<std::invalid_argument>(
"invalid elias_fano exception");
295 auto position =
sparse_[sample.sparse + remaining];
296 if (position >=
high_bits_ || !(
high_[position >> 6] & (std::uint64_t{1} << (position & 63))))
297 error_detail::raise<std::invalid_argument>(
"invalid elias_fano sparse position");
300 if (sample.first >=
high_bits_) error_detail::raise<std::invalid_argument>(
"invalid elias_fano sample");
301 auto word = sample.first >> 6;
302 auto value =
high_[word] & (~std::uint64_t{0} << (sample.first & 63));
303 for (
unsigned scanned = 0; scanned < 65 && word <
high_.
size(); ++scanned, ++word) {
304 if (scanned) value =
high_[word];
305 auto population = unsigned(std::popcount(value));
306 if (remaining < population) {
307 auto position = word * 64 + elias_fano_detail::select_word<Arch>(value, remaining);
308 if (position >=
high_bits_ || position - sample.first >= 4096)
309 error_detail::raise<std::invalid_argument>(
"elias_fano dense span");
312 remaining -= population;
314 error_detail::raise<std::invalid_argument>(
"elias_fano missing high bit");
333 template <simd::architecture Arch = simd::scalar>
335 std::type_identity<Arch> = {})
337 if (
ordinal > source.
size()) error_detail::raise<std::out_of_range>(
"Elias-Fano cursor ordinal");
341 if (
sample_.
sparse == std::numeric_limits<std::uint64_t>::max()) {
343 word_ = position >> 6;
350 if (
done()) [[unlikely]] error_detail::raise<std::out_of_range>(
"Elias-Fano cursor end");
354 if (
sample_.
sparse == std::numeric_limits<std::uint64_t>::max()) {
356 error_detail::raise<std::invalid_argument>(
"invalid elias_fano sample");
361 std::uint64_t position;
362 if (
sample_.
sparse != std::numeric_limits<std::uint64_t>::max()) {
365 error_detail::raise<std::invalid_argument>(
"invalid elias_fano exception");
368 error_detail::raise<std::invalid_argument>(
"invalid elias_fano sparse position");
373 error_detail::raise<std::invalid_argument>(
"elias_fano missing high bit");
378 error_detail::raise<std::invalid_argument>(
"elias_fano dense span");
392 template <simd::architecture Arch>
398 template <simd::architecture Arch = simd::scalar>
400 if (residuals.empty())
return {};
401 if (!elias_fano_detail::monotone<Arch>(residuals))
402 error_detail::raise<std::invalid_argument>(
"elias_fano nonmonotone offsets");
406 auto quotient = result.
universe / residuals.size();
407 result.
low_width = quotient ? unsigned(std::bit_width(quotient) - 1) : 0;
408 constexpr auto maximum = std::numeric_limits<std::uint64_t>::max() - 255;
409 if (residuals.size() > maximum || (result.
low_width && residuals.size() > maximum / result.
low_width) ||
411 error_detail::raise<std::overflow_error>(
"elias_fano section extent");
417 elias_fano_detail::pack_low<Arch>(residuals, result.
low, result.
low_width);
419 for (std::uint64_t begin = 0; begin < residuals.size(); begin += 256) {
420 auto end = residuals.size() - begin < 256 ? residuals.size() : begin + 256;
421 auto first = (residuals[begin] >> result.
low_width) + begin;
422 auto last = (residuals[end - 1] >> result.
low_width) + end - 1;
423 auto sparse = std::numeric_limits<std::uint64_t>::max();
424 if (last - first >= 4096) {
426 for (
auto i = begin; i < end; ++i)
442 std::vector<std::uint64_t>
low;
443 std::vector<std::uint64_t>
high;
Shares explicit SIMD architecture traits with Everett's kernels.
Outlines exceptional check failures while preserving their types and messages.
unsigned select_word(std::uint64_t value, unsigned ordinal) noexcept
Definition elias_fano.h:58
bool monotone(std::span< std::uint64_t const > source) noexcept
Definition elias_fano.h:42
void low_tile(std::uint64_t const *source, std::uint64_t *out, std::index_sequence< O... >) noexcept
Definition elias_fano.h:99
std::uint64_t low_component(std::uint64_t const *source) noexcept
Definition elias_fano.h:84
void pack_low_fixed(std::span< std::uint64_t const > source, std::span< std::uint64_t > out) noexcept
Definition elias_fano.h:110
void pack_low(std::span< std::uint64_t const > source, std::span< std::uint64_t > out, unsigned width)
Definition elias_fano.h:163
constexpr auto low_packers(std::index_sequence< W... >) noexcept
Definition elias_fano.h:154
std::uint64_t words(std::uint64_t bits) noexcept
Definition elias_fano.h:37
std::uint64_t low_word(std::uint64_t const *source, std::index_sequence< I... >) noexcept
Definition elias_fano.h:93
constexpr std::uint64_t multiply(std::uint64_t a, std::uint64_t b) noexcept
Definition elias_fano.h:36
void write_high(std::span< std::uint64_t const > source, std::span< std::uint64_t > out, unsigned width) noexcept
Definition elias_fano.h:178
constexpr std::uint64_t add(std::uint64_t a, std::uint64_t b) noexcept
Definition elias_fano.h:35
Definition active_engine.h:18
Definition elias_fano.h:332
elias_fano_sample sample_
Definition elias_fano.h:388
elias_fano_cursor(elias_fano_view source, std::uint64_t ordinal=0, std::type_identity< Arch >={})
Definition elias_fano.h:334
elias_fano_view source_
Definition elias_fano.h:387
std::uint64_t remaining_
Definition elias_fano.h:389
std::uint64_t ordinal_
Definition elias_fano.h:389
std::uint64_t next()
Definition elias_fano.h:349
std::uint64_t word_
Definition elias_fano.h:389
bool done() const noexcept
Definition elias_fano.h:347
std::uint64_t ordinal() const noexcept
Definition elias_fano.h:348
Definition elias_fano.h:195
std::uint64_t first
Definition elias_fano.h:196
std::uint64_t sparse
Definition elias_fano.h:199
Definition elias_fano.h:212
word_view sparse_
Definition elias_fano.h:320
word_view high_
Definition elias_fano.h:318
std::uint64_t select(std::uint64_t ordinal) const
Definition elias_fano.h:259
elias_fano_view(Words low, Words high, Samples samples, Words sparse, std::uint64_t entry_count, std::uint64_t universe, unsigned low_width)
Definition elias_fano.h:226
unsigned low_width_
Definition elias_fano.h:324
unsigned low_width() const noexcept
Definition elias_fano.h:255
elias_fano_view()=default
elias_fano_cursor cursor(std::uint64_t ordinal=0) const
Definition elias_fano.h:393
word_view sparse_words() const noexcept
Definition elias_fano.h:253
std::uint64_t entry_count_
Definition elias_fano.h:321
word_view low_words() const noexcept
Definition elias_fano.h:250
std::uint64_t universe_
Definition elias_fano.h:322
sample_view samples_
Definition elias_fano.h:319
std::uint64_t select_high(std::uint64_t ordinal) const
Definition elias_fano.h:289
friend struct elias_fano_cursor
Definition elias_fano.h:269
std::uint64_t size() const noexcept
Definition elias_fano.h:256
sample_view samples() const noexcept
Definition elias_fano.h:252
word_view high_words() const noexcept
Definition elias_fano.h:251
std::uint64_t universe() const noexcept
Definition elias_fano.h:254
std::uint64_t decode(std::uint64_t ordinal, std::uint64_t position) const
Definition elias_fano.h:270
elias_fano_view(std::span< std::uint64_t const > low, std::span< std::uint64_t const > high, std::span< elias_fano_sample const > samples, std::span< std::uint64_t const > sparse, std::uint64_t entry_count, std::uint64_t universe, unsigned low_width)
Definition elias_fano.h:215
word_view low_
Definition elias_fano.h:317
std::uint64_t high_bits_
Definition elias_fano.h:323
Definition elias_fano.h:397
std::vector< std::uint64_t > sparse
Definition elias_fano.h:445
std::vector< std::uint64_t > high
Definition elias_fano.h:443
std::vector< std::uint64_t > low
Definition elias_fano.h:442
std::vector< elias_fano_sample > samples
Definition elias_fano.h:444
unsigned low_width
Definition elias_fano.h:448
std::uint64_t universe
Definition elias_fano.h:447
static elias_fano build(std::span< std::uint64_t const > residuals)
Definition elias_fano.h:399
elias_fano_view view() const &
Definition elias_fano.h:434
elias_fano_view view() const &&=delete
std::uint64_t entry_count
Definition elias_fano.h:446
Definition word_view.h:82
std::size_t size() const noexcept
Definition word_view.h:97
bool empty() const noexcept
Definition word_view.h:98
Definition word_view.h:31
std::size_t size() const noexcept
Definition word_view.h:41
Borrows native or little-endian directory words and select samples.