Everett
Loading...
Searching...
No Matches
profile.h
Go to the documentation of this file.
1
13#pragma once
14
16
17#include <everett/policy.h>
18#include <everett/key_detail.h>
19#include <everett/elias_fano.h>
20
21#include <algorithm>
22#include <array>
23#include <bit>
24#include <cstddef>
25#include <cstdint>
26#include <cstring>
27#include <limits>
28#include <memory>
29#include <optional>
30#include <span>
31#include <stdexcept>
32#include <string_view>
33#include <utility>
34#include <vector>
35
36namespace everett {
37 namespace cola_detail { struct query_access; }
38 namespace profile_detail {
39 inline std::uint64_t add(std::uint64_t a, std::uint64_t b) {
40 if (b > std::numeric_limits<std::uint64_t>::max() - a)
41 error_detail::raise<std::overflow_error>("profile addition");
42 return a + b;
43 }
44 inline std::uint64_t multiply(std::uint64_t a, std::uint64_t b) {
45 if (b && a > std::numeric_limits<std::uint64_t>::max() / b)
46 error_detail::raise<std::overflow_error>("profile multiplication");
47 return a * b;
48 }
49 // Admitted bit extents leave room for the final byte rounding.
50 inline std::uint64_t byte_count(std::uint64_t bits) noexcept {
51 return (bits + 7) >> 3;
52 }
53 }
54
55 // MSB-first meaningful bits. A subview may begin inside its first byte.
56 struct bit_view {
57 bit_view() = default;
58 bit_view(std::span<std::byte const> bytes, std::uint64_t bits, std::uint64_t offset = 0)
59 : bytes_(bytes), offset_(offset), size_(bits) {
60 auto capacity = profile_detail::multiply(bytes.size(), 8);
61 if (offset > capacity || bits > capacity - offset)
62 error_detail::raise<std::invalid_argument>("bit view exceeds storage");
63 }
64 std::uint64_t size() const noexcept { return size_; }
65 bool empty() const noexcept { return size_ == 0; }
66 std::uint64_t offset() const noexcept { return offset_; }
67 std::span<std::byte const> storage() const noexcept { return bytes_; }
68 bool at(std::uint64_t i) const {
69 if (i >= size_) error_detail::raise<std::out_of_range>("bit position");
70 auto bit = offset_ + i;
71 return (std::to_integer<unsigned>(bytes_[bit >> 3]) >> (7 - (bit & 7))) & 1;
72 }
73 bit_view subview(std::uint64_t first, std::uint64_t count) const {
74 if (first > size_ || count > size_ - first) error_detail::raise<std::out_of_range>("bit subview");
75 auto result = *this;
76 result.offset_ += first;
77 result.size_ = count;
78 return result;
79 }
80 bit_view prefix(std::uint64_t count) const { return subview(0, std::min(count, size_)); }
81
82 private:
83 std::span<std::byte const> bytes_;
84 std::uint64_t offset_ = 0;
85 std::uint64_t size_ = 0;
86 };
87
88 namespace profile_detail {
89 inline std::uint64_t low_mask(unsigned width) noexcept {
90 return width == 64 ? ~std::uint64_t{0} : (std::uint64_t{1} << width) - 1;
91 }
92 // A bounded MSB-first field, returned in the low width bits. Width <= 64.
93 inline std::uint64_t load_bits(bit_view data, std::uint64_t first, unsigned width) noexcept {
94 if (!width) return 0;
95 auto offset = data.offset() + first;
96 auto source = data.storage().data() + (offset >> 3);
97 unsigned shift = unsigned(offset & 7), bytes = (shift + width + 7) >> 3;
98 if (bytes >= 8) {
99 auto value = key_detail::load_big(source);
100 if (shift) {
101 value <<= shift;
102 if (bytes == 9) value |= std::uint64_t(std::to_integer<unsigned>(source[8])) >> (8 - shift);
103 }
104 return width == 64 ? value : value >> (64 - width);
105 }
106 std::uint64_t value = 0;
107 for (unsigned i = 0; i != bytes; ++i) value = (value << 8) | std::to_integer<unsigned>(source[i]);
108 return (value >> ((bytes << 3) - shift - width)) & low_mask(width);
109 }
110 // Preserve both edge bytes outside the field. The caller provides capacity.
111 inline void store_bits(std::byte * target, std::uint64_t at, std::uint64_t value, unsigned width) noexcept {
112 if (!width) return;
113 if (width == 64 && (at & 7) == 0) { key_detail::store_big(target + (at >> 3), value); return; }
114 while (width) {
115 auto take = std::min(width, 8u - unsigned(at & 7));
116 auto shift = 8u - unsigned(at & 7) - take;
117 auto mask = unsigned(low_mask(take)) << shift;
118 auto bits = unsigned((value >> (width - take)) & low_mask(take)) << shift;
119 target[at >> 3] = std::byte((std::to_integer<unsigned>(target[at >> 3]) & ~mask) | bits);
120 at += take; width -= take;
121 }
122 }
123 inline void copy_bits(std::byte * target, std::uint64_t first, bit_view source) noexcept {
124 auto count = source.size();
125 if (!count) return;
126 if (((first | source.offset() | count) & 7) == 0) {
127 std::memmove(target + (first >> 3), source.storage().data() + (source.offset() >> 3),
128 static_cast<std::size_t>(count >> 3));
129 return;
130 }
131 // Detect overlapping storage without ordering unrelated C++ pointers.
132 auto destination = reinterpret_cast<std::uintptr_t>(target + (first >> 3));
133 auto origin = reinterpret_cast<std::uintptr_t>(source.storage().data() + (source.offset() >> 3));
134 auto source_bytes = byte_count((source.offset() & 7) + count);
135 bool backwards = (destination > origin || (destination == origin && (first & 7) > (source.offset() & 7))) &&
136 destination - origin < source_bytes;
137 if (backwards) {
138 while (count) {
139 auto width = unsigned(std::min<std::uint64_t>(count, 64));
140 count -= width;
141 store_bits(target, first + count, load_bits(source, count, width), width);
142 }
143 return;
144 }
145 std::uint64_t at = 0;
146 if (first & 7) {
147 auto width = unsigned(std::min<std::uint64_t>(count, 8 - (first & 7)));
148 store_bits(target, first, load_bits(source, 0, width), width); at += width;
149 }
150 if (((source.offset() + at) & 7) == 0) {
151 auto bytes = (count - at) >> 3;
152 if (bytes) std::memmove(target + ((first + at) >> 3),
153 source.storage().data() + ((source.offset() + at) >> 3), static_cast<std::size_t>(bytes));
154 at += bytes << 3;
155 } else {
156 for (; count - at >= 64; at += 64)
157 key_detail::store_big(target + ((first + at) >> 3), load_bits(source, at, 64));
158 }
159 while (at != count) {
160 auto width = unsigned(std::min<std::uint64_t>(count - at, 8));
161 store_bits(target, first + at, load_bits(source, at, width), width); at += width;
162 }
163 }
164 }
165
166 struct bit_string {
167 std::vector<std::byte> bytes;
168 std::uint64_t bit_size = 0;
169
170 void validate() const {
171 if (bit_size > std::numeric_limits<std::uint64_t>::max() - 7 ||
173 error_detail::raise<std::invalid_argument>("packed bit-string length");
174 if ((bit_size & 7) &&
175 (std::to_integer<unsigned>(bytes.back()) & ((1u << (8 - (bit_size & 7))) - 1)))
176 error_detail::raise<std::invalid_argument>("nonzero bit-string padding");
177 }
178 bit_view view() const & { validate(); return {bytes, bit_size}; }
179 bit_view view() const && = delete;
180
181 static bit_string copy(bit_view source) {
182 bit_string result;
183 result.bit_size = source.size();
184 result.bytes.resize(static_cast<std::size_t>(profile_detail::byte_count(source.size())));
185 profile_detail::copy_bits(result.bytes.data(), 0, source);
186 return result;
187 }
188 static bit_string from_bytes(std::span<std::byte const> source) {
189 return {{source.begin(), source.end()}, profile_detail::multiply(source.size(), 8)};
190 }
191 static bit_string from_bytes(std::string_view source) {
192 return from_bytes(std::as_bytes(std::span(source.data(), source.size())));
193 }
194 static bit_string from_bits(std::string_view source) {
195 if (source.size() > std::numeric_limits<std::uint64_t>::max() - 7)
196 error_detail::raise<std::length_error>("profile bit string too large");
197 bit_string result;
198 result.bit_size = source.size();
199 result.bytes.resize(static_cast<std::size_t>(profile_detail::byte_count(source.size())));
200 for (std::size_t i = 0; i != source.size(); ++i) {
201 if (source[i] != '0' && source[i] != '1') error_detail::raise<std::invalid_argument>("expected binary digits");
202 if (source[i] == '1') result.bytes[i >> 3] |= static_cast<std::byte>(1u << (7 - (i & 7)));
203 }
204 return result;
205 }
206 bool operator==(bit_string const &) const = default;
207 };
208
210 std::uint64_t common_bits = 0;
211 int order = 0;
212 };
213 template <simd::architecture Arch = simd::scalar>
214 simd_align(64)
216 auto count = std::min(a.size(), b.size());
217 // Most unrelated keys can differ immediately; do not load a whole word
218 // just to discover a mismatch in the first meaningful bit.
219 if (count) {
220 auto x = (std::to_integer<unsigned>(a.storage()[a.offset() >> 3]) >> (7 - (a.offset() & 7))) & 1;
221 auto y = (std::to_integer<unsigned>(b.storage()[b.offset() >> 3]) >> (7 - (b.offset() & 7))) & 1;
222 if (x != y) return {0, x ? 1 : -1};
223 }
224 std::uint64_t at = 0;
225 if (count >= 8 && (a.offset() & 7) == 0 && (b.offset() & 7) == 0) {
226 at = key_detail::common_bytes<Arch>(a.storage().data() + (a.offset() >> 3),
227 b.storage().data() + (b.offset() >> 3), static_cast<std::size_t>(count >> 3)) << 3;
228 if (count - at >= 8) {
229 auto x = profile_detail::load_bits(a, at, 8), y = profile_detail::load_bits(b, at, 8);
230 return {at + std::countl_zero(x ^ y) - 56, x < y ? -1 : 1};
231 }
232 }
233 while (at != count) {
234 auto width = unsigned(std::min<std::uint64_t>(count - at, 64));
235 auto x = profile_detail::load_bits(a, at, width), y = profile_detail::load_bits(b, at, width);
236 if (auto different = x ^ y)
237 return {at + std::countl_zero(different) - (64 - width), x < y ? -1 : 1};
238 at += width;
239 }
240 return {count, a.size() < b.size() ? -1 : a.size() > b.size() ? 1 : 0};
241 }
242 template <simd::architecture Arch = simd::scalar>
243 inline int compare_bits(bit_view a, bit_view b) { return compare_common_bits<Arch>(a, b).order; }
244 template <class P> std::uint64_t common_prefix_units(bit_view a, bit_view b) {
245 if ((a.size() & (P::bits_per_unit - 1)) || (b.size() & (P::bits_per_unit - 1)))
246 error_detail::raise<std::invalid_argument>("key length does not match profile unit");
247 return compare_common_bits<typename P::architecture>(a, b).common_bits >> P::unit_shift;
248 }
249
250 template <class P> struct profile_anchor {
252 std::uint64_t full_units = 0;
254 if (key.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("anchor unit mismatch");
255 return {key, (key.size() >> P::unit_shift)};
256 }
257 };
258
259 template <class P> int compare_profile_prefix(profile_anchor<P> key, bit_view query) {
260 if ((query.size() & (P::bits_per_unit - 1)) || (key.prefix.size() & (P::bits_per_unit - 1)))
261 error_detail::raise<std::invalid_argument>("query unit mismatch");
262 auto query_units = (query.size() >> P::unit_shift);
263 auto needed = profile_detail::multiply(std::min(key.full_units, query_units), P::bits_per_unit);
264 if (key.prefix.size() < needed) error_detail::raise<std::invalid_argument>("query prefix is incomplete");
265 auto order = compare_bits<typename P::architecture>(key.prefix.prefix(needed), query.prefix(needed));
266 if (order) return order;
267 return key.full_units < query_units ? -1 : key.full_units > query_units ? 1 : 0;
268 }
269
270 enum class profile_count_code : std::uint8_t { varint, exp_golomb_zero };
271 enum class profile_bit_order : std::uint8_t { msb_first };
272
293
297 // Encoding hint in complete logical-key bits, including any selector.
298 // Null means ordinary FC; a finite bound emits the full extra suffix.
299 std::optional<std::uint64_t> retained_limit_bits{};
300 };
301
302 template <class P> struct profile_item {
303 std::uint64_t ordinal = 0;
306 };
307
309 std::uint64_t retained = 0;
310 std::uint64_t key_units = 0;
311 std::uint64_t value_units = 0;
314 std::uint64_t next_offset = 0;
315 };
316
317 template <class P> struct profile_decoded_record {
319 std::uint64_t full_units = 0;
321 profile_anchor<P> anchor() const & { return {prefix.view(), full_units}; }
322 profile_anchor<P> anchor() const && = delete;
323 };
324
325 // Public comparisons share an immutable query owner. A private synchronous
326 // query may instead borrow its stack owner for the entire traversal. Agreement
327 // counts bits; record lengths/backspaces count P units. No prefix is implied.
328 template <class P> struct profile_query_context {
330 if (query.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("query unit mismatch");
331 query_ = std::make_shared<bit_string const>(bit_string::copy(query));
332 order_ = query.size() ? -1 : 0;
333 }
334 // Transfer an already encoded query without copying its allocation. Every
335 // derived comparison still shares one immutable owner, as for bit_view.
337 query.validate();
338 if (query.bit_size & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("query unit mismatch");
339 auto order = query.bit_size ? -1 : 0;
340 return profile_query_context(std::make_shared<bit_string const>(std::move(query)), order);
341 }
342 bit_view query() const {
343 if (!query_) error_detail::raise<std::logic_error>("comparison context has no query");
344 return query_->view();
345 }
346 std::uint64_t common_bits() const noexcept { return common_bits_; }
347 // A repaired lower frontier needs only order and LCP. Its full length is
348 // available after reading a frame or establishing equality with the query.
349 std::optional<std::uint64_t> full_units() const noexcept {
350 return length_known_ ? std::optional(full_units_) : std::nullopt;
351 }
352 int order() const noexcept { return order_; }
353
354 // A heterogeneous record grammar can expose its logical suffix as a few
355 // borrowed spans. Comparisons retain only order, length and agreement with
356 // the query; inherited key bytes need not be reconstructed.
357 std::uint64_t advance_parts(std::uint64_t retained_bits, std::uint64_t full_bits,
358 std::span<bit_view const> literal) {
359 if ((full_bits & (P::bits_per_unit - 1)) || retained_bits > full_bits ||
360 (length_known_ && retained_bits > (full_units_ << P::unit_shift)))
361 error_detail::raise<std::invalid_argument>("invalid inherited comparison frame");
362 std::uint64_t total = retained_bits;
363 for (auto part : literal) total = profile_detail::add(total, part.size());
364 if (total != full_bits) error_detail::raise<std::invalid_argument>("incomplete comparison literal");
365 std::uint64_t compared = 0;
366 if (common_bits_ >= retained_bits) {
367 auto at = retained_bits;
368 order_ = 0;
369 for (auto part : literal) {
370 auto available = query().size() - at;
371 auto other = query().subview(at, available);
372 auto result = compare_common_bits<typename P::architecture>(part, other.prefix(std::min(part.size(), available)));
373 compared += result.common_bits + (result.common_bits < std::min(part.size(), available));
374 at += result.common_bits;
375 if (result.order) { order_ = result.order; break; }
376 }
377 common_bits_ = at;
378 if (!order_) order_ = full_bits < query().size() ? -1 : full_bits > query().size() ? 1 : 0;
379 }
380 full_units_ = full_bits >> P::unit_shift;
381 length_known_ = true;
382 return compared;
383 }
384
386 if (key.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("boundary key unit mismatch");
387 auto result = *this;
388 auto comparison = compare_common_bits<typename P::architecture>(key, query());
389 result.common_bits_ = comparison.common_bits;
390 result.full_units_ = (key.size() >> P::unit_shift);
391 result.length_known_ = true;
392 result.order_ = comparison.order;
393 return result;
394 }
395
396 private:
397 // Only the synchronous query path may use this: all copies and predecessor
398 // contexts must die before query. An empty-owner alias needs no control
399 // block, but its nonnull stored pointer still supports the usual checks.
401 query.validate();
402 if (query.bit_size & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("query unit mismatch");
403 auto alias = std::shared_ptr<bit_string const>(std::shared_ptr<bit_string const>{}, &query);
404 return profile_query_context(std::move(alias), query.bit_size ? -1 : 0);
405 }
407 profile_query_context(std::shared_ptr<bit_string const> query, int order)
408 : query_(std::move(query)), order_(order) {}
409 template <class, stream_role> friend struct profile_view;
410 template <class> friend struct profile_blob;
411 template <class> friend struct profile_blob_view;
412 template <class, class> friend struct cola_index_view;
413 std::shared_ptr<bit_string const> query_;
414 std::uint64_t common_bits_ = 0;
415 std::uint64_t full_units_ = 0;
416 int order_ = 0;
417 bool length_known_ = true;
418
419 std::uint64_t advance(profile_encoded_record const & record) {
420 if (length_known_ && record.retained > full_units_)
421 error_detail::raise<std::invalid_argument>("comparison anchor is too short");
422 auto retained_bits = profile_detail::multiply(record.retained, P::bits_per_unit);
423 std::uint64_t compared = 0;
424 if (common_bits_ >= retained_bits) {
425 auto suffix_query = query().subview(retained_bits, query().size() - retained_bits);
426 auto comparison = compare_common_bits<typename P::architecture>(record.suffix, suffix_query);
427 common_bits_ = profile_detail::add(retained_bits, comparison.common_bits);
428 order_ = comparison.order;
429 compared = comparison.common_bits +
430 (comparison.common_bits < std::min(record.suffix.size(), suffix_query.size()));
431 }
432 full_units_ = record.key_units;
433 length_known_ = true;
434 return compared;
435 }
436
437 profile_query_context predecessor(std::uint64_t lcp_bits) const {
438 if (order_ > 0 || (length_known_ &&
439 lcp_bits > profile_detail::multiply(full_units_, P::bits_per_unit)))
440 error_detail::raise<std::invalid_argument>("invalid cut predecessor comparison");
441 auto result = *this;
442 result.common_bits_ = std::min(lcp_bits, common_bits_);
443 // C <= boundary <= query. Matching all of query therefore means C=query;
444 // a shorter match means C<query, without consulting C's full length.
445 result.length_known_ = result.common_bits_ == query().size();
446 result.full_units_ = result.length_known_ ? query().size() >> P::unit_shift : 0;
447 result.order_ = result.length_known_ ? 0 : -1;
448 return result;
449 }
450 };
451
453 std::uint64_t skipped_headers = 0;
454 std::uint64_t visited_headers = 0;
455 std::uint64_t compared_bits = 0;
456 };
457
458 template <class P> struct profile_comparison_item {
459 std::uint64_t ordinal = 0;
462 };
463
464 namespace profile_detail {
465 inline void resize(bit_string & value, std::uint64_t bits) {
466 if (bits > std::numeric_limits<std::uint64_t>::max() - 7)
467 error_detail::raise<std::length_error>("profile bit string too large");
468 auto bytes = byte_count(bits);
469 if (bytes > value.bytes.max_size()) error_detail::raise<std::length_error>("profile bit string too large");
470 value.bytes.resize(static_cast<std::size_t>(bytes));
471 value.bit_size = bits;
472 if (bits & 7) value.bytes.back() &= static_cast<std::byte>(0xffu << (8 - (bits & 7)));
473 }
474 inline void copy_into(bit_string & target, std::uint64_t first, bit_view source) {
475 if (first > target.bit_size || source.size() > target.bit_size - first)
476 error_detail::raise<std::out_of_range>("profile copy exceeds destination");
477 copy_bits(target.bytes.data(), first, source);
478 }
479
480 inline void append(bit_string & target, bit_view source) {
481 auto at = target.bit_size;
482 auto begin = reinterpret_cast<std::uintptr_t>(target.bytes.data());
483 auto input = reinterpret_cast<std::uintptr_t>(source.storage().data());
484 if (source.size() && input >= begin && input - begin < target.bytes.size()) {
485 auto saved = bit_string::copy(source);
486 resize(target, add(at, saved.bit_size));
487 copy_into(target, at, saved.view());
488 } else {
489 resize(target, add(at, source.size()));
490 copy_into(target, at, source);
491 }
492 }
493 inline void append_bit(bit_string & target, bool bit) {
494 auto at = target.bit_size;
495 resize(target, add(at, 1));
496 if (bit) target.bytes[at >> 3] |= static_cast<std::byte>(1u << (7 - (at & 7)));
497 }
498 inline void append_byte(bit_string & target, unsigned value) {
499 if ((target.bit_size & 7)) error_detail::raise<std::logic_error>("unaligned byte profile writer");
500 target.bytes.push_back(static_cast<std::byte>(value));
501 target.bit_size = add(target.bit_size, 8);
502 }
503 // Fixed-width fields are MSB-first. Width 64 is valid; no shift uses 64.
504 inline void put_fixed(bit_string & target, std::uint64_t & at, std::uint64_t value, unsigned width) {
505 if (width > 64 || at > target.bit_size || width > target.bit_size - at)
506 error_detail::raise<std::out_of_range>("profile fixed field exceeds destination");
507 store_bits(target.bytes.data(), at, value, width);
508 at += width;
509 }
510 inline std::uint64_t read_fixed(bit_view data, std::uint64_t & at, unsigned width) {
511 if (width > 64 || at > data.size() || width > data.size() - at)
512 error_detail::raise<std::invalid_argument>("truncated backspace remainder");
513 auto value = load_bits(data, at, width);
514 at += width;
515 return value;
516 }
517 inline std::uint64_t read_zero_run(bit_view data, std::uint64_t & at, std::uint64_t limit,
518 char const * truncated, char const * overflow) {
519 std::uint64_t zeros = 0;
520 for (;;) {
521 if (at >= data.size()) error_detail::raise<std::invalid_argument>(truncated);
522 auto width = unsigned(std::min<std::uint64_t>(data.size() - at, 64));
523 auto word = load_bits(data, at, width);
524 auto leading = unsigned(std::countl_zero(word)) - (64 - width);
525 if (leading > limit - zeros) {
526 at += limit - zeros + 1;
527 error_detail::raise<std::invalid_argument>(overflow);
528 }
529 zeros += leading; at += leading;
530 if (leading != width) { ++at; return zeros; }
531 }
532 }
533 template <class P> void write_count(bit_string & target, std::uint64_t value) {
534 if constexpr (P::unit == profile_unit::byte) {
535 while (value >= 128) { append_byte(target, unsigned(value & 127) | 128); value >>= 7; }
536 append_byte(target, unsigned(value));
537 } else {
538 auto maximum = value == std::numeric_limits<std::uint64_t>::max();
539 auto width = maximum ? 65u : unsigned(std::bit_width(value + 1));
540 auto at = target.bit_size;
541 resize(target, add(at, 2 * width - 1));
542 if (maximum) { at += 64; put_fixed(target, at, 1, 1); put_fixed(target, at, 0, 64); }
543 else { at += width - 1; put_fixed(target, at, value + 1, width); }
544 }
545 }
546
547 template <class P> std::uint64_t read_count(bit_view data, std::uint64_t & offset) {
548 if constexpr (P::unit == profile_unit::byte) {
549 std::uint64_t value = 0;
550 for (unsigned shift = 0; shift <= 63; shift += 7) {
551 if (offset >= (data.size() >> 3)) error_detail::raise<std::invalid_argument>("truncated profile count");
552 auto part = std::to_integer<unsigned>(data.storage()[offset++]);
553 if (shift == 63 && part > 1) error_detail::raise<std::invalid_argument>("overflowing profile count");
554 value |= std::uint64_t(part & 127) << shift;
555 if (!(part & 128)) {
556 if (shift && !(part & 127)) error_detail::raise<std::invalid_argument>("noncanonical profile count");
557 return value;
558 }
559 }
560 error_detail::raise<std::invalid_argument>("overflowing profile count");
561 } else {
562 // A complete small code fits in the first fifteen bits. Decode its
563 // prefix and suffix from one bounded field; long codes and short tails
564 // retain the general decoder's exact offset and error behavior.
565 if (offset <= data.size() && data.size() - offset >= 16) {
566 auto word = load_bits(data, offset, 16);
567 auto zeros = unsigned(std::countl_zero(word) - 48);
568 if (zeros < 8) {
569 offset += 2 * zeros + 1;
570 return (word >> (15 - 2 * zeros)) - 1;
571 }
572 }
573 auto zeros = unsigned(read_zero_run(data, offset, 64,
574 "truncated exponential-Golomb count", "overflowing exponential-Golomb count"));
575 if (zeros > data.size() - offset) {
576 offset = data.size();
577 error_detail::raise<std::invalid_argument>("truncated exponential-Golomb count");
578 }
579 auto suffix = read_fixed(data, offset, zeros);
580 if (zeros == 64) {
581 if (suffix) error_detail::raise<std::invalid_argument>("overflowing exponential-Golomb count");
582 return std::numeric_limits<std::uint64_t>::max();
583 }
584 return ((std::uint64_t{1} << zeros) - 1) + suffix;
585 }
586 }
587 template <class P> constexpr std::uint64_t golomb_cutoff() {
588 constexpr auto width = std::bit_width(P::backspace_parameter - 1);
589 if constexpr (width == 64) return std::uint64_t{0} - P::backspace_parameter;
590 else return (std::uint64_t{1} << width) - P::backspace_parameter;
591 }
592 // Golomb length includes its unary quotient: a long predecessor can make
593 // a short key's backspace expensive even at an LPFC literal restart. Charge
594 // the actual codeword length as well as encoded distance from the anchor.
595 template <class P> std::uint64_t backspace_bits(std::uint64_t value) {
596 static_assert(P::unit == profile_unit::bit);
597 if constexpr (P::backspace_code == bit_backspace_code::exponential_golomb) {
598 auto quotient = value >> P::backspace_parameter;
599 auto prefix = quotient == std::numeric_limits<std::uint64_t>::max()
600 ? 129 : 2 * std::bit_width(quotient + 1) - 1;
601 return prefix + P::backspace_parameter;
602 } else {
603 constexpr auto modulus = P::backspace_parameter;
604 constexpr auto width = std::bit_width(modulus - 1);
605 auto remainder_width = width - (value % modulus < golomb_cutoff<P>());
606 return add(add(value / modulus, 1), remainder_width);
607 }
608 }
609 template <class P> void write_backspace(bit_string & target, std::uint64_t value) {
610 if constexpr (P::unit == profile_unit::byte) write_count<P>(target, value);
611 else {
612 // Check the complete length and allocate before emitting a unary run.
613 // Fresh bytes and the existing canonical tail supply its zero bits.
614 auto at = target.bit_size;
615 resize(target, add(at, backspace_bits<P>(value)));
616 if constexpr (P::backspace_code == bit_backspace_code::exponential_golomb) {
617 auto quotient = value >> P::backspace_parameter;
618 if (quotient == std::numeric_limits<std::uint64_t>::max()) {
619 at += 64;
620 put_fixed(target, at, 1, 1);
621 put_fixed(target, at, 0, 64);
622 } else {
623 auto code = quotient + 1;
624 auto width = unsigned(std::bit_width(code));
625 at += width - 1;
626 put_fixed(target, at, code, width);
627 }
628 put_fixed(target, at, value, unsigned(P::backspace_parameter));
629 } else {
630 constexpr auto modulus = P::backspace_parameter;
631 constexpr auto width = unsigned(std::bit_width(modulus - 1));
632 constexpr auto cutoff = golomb_cutoff<P>();
633 at += value / modulus;
634 put_fixed(target, at, 1, 1);
635 auto remainder = value % modulus;
636 if (remainder < cutoff) put_fixed(target, at, remainder, width - 1);
637 else put_fixed(target, at, remainder + cutoff, width);
638 }
639 }
640 }
641 template <class P> std::uint64_t read_backspace(bit_view data, std::uint64_t & offset) {
642 if constexpr (P::unit == profile_unit::byte) return read_count<P>(data, offset);
643 else if constexpr (P::backspace_code == bit_backspace_code::exponential_golomb) {
644 auto quotient = read_count<P>(data, offset);
645 if (quotient > (std::numeric_limits<std::uint64_t>::max() >> P::backspace_parameter))
646 error_detail::raise<std::invalid_argument>("overflowing exponential-Golomb backspace");
647 auto remainder = read_fixed(data, offset, unsigned(P::backspace_parameter));
648 return (quotient << P::backspace_parameter) | remainder;
649 } else {
650 constexpr auto modulus = P::backspace_parameter;
651 constexpr auto width = unsigned(std::bit_width(modulus - 1));
652 constexpr auto cutoff = golomb_cutoff<P>();
653 constexpr auto limit = std::numeric_limits<std::uint64_t>::max() / modulus;
654 auto quotient = read_zero_run(data, offset, limit,
655 "truncated backspace remainder", "overflowing Golomb quotient");
656 std::uint64_t remainder = 0;
657 if constexpr (width != 0) {
658 remainder = read_fixed(data, offset, width - 1);
659 if (remainder >= cutoff) remainder = ((remainder << 1) | read_fixed(data, offset, 1)) - cutoff;
660 }
661 auto base = quotient * modulus;
662 if (remainder > std::numeric_limits<std::uint64_t>::max() - base)
663 error_detail::raise<std::invalid_argument>("overflowing Golomb backspace");
664 return base + remainder;
665 }
666 }
667 template <class P, stream_role Role> profile_metadata initial_metadata() {
668 profile_metadata result;
669 result.key_unit = result.value_unit = result.count_unit = result.offset_unit = P::unit;
670 result.count_code = P::unit == profile_unit::byte ? profile_count_code::varint : profile_count_code::exp_golomb_zero;
671 result.backspace_code = P::backspace_code;
672 result.backspace_parameter = P::backspace_parameter;
673 result.role = Role;
674 result.group_size = P::group_size;
675 result.codec_block_size = P::codec_block_size;
676 result.policy_fixed_values = P::fixed_width;
677 result.policy_value_width = P::value_width.value_or(0);
678 result.common_value_width = Role == stream_role::borrowed ? 0 : P::value_width.value_or(0);
679 return result;
680 }
681 }
682
683 template <class P, stream_role Role = stream_role::native> struct profile_cursor;
684 template <class P, stream_role Role = stream_role::native> struct profile_encoded_cursor;
685 template <class P> struct profile_borrowed_writer;
686 template <class P, class Native, class Output> struct index_builder;
687 namespace profile_detail { template <class P> struct index_output; template <class P> struct borrowed_sections; }
688 namespace cola_detail { template <class P, class Native, class Main> struct index_output; }
689 template <class P> struct profile_native_writer;
690 namespace profile_detail { template <class P> struct native_output; }
691
692 // A view borrows both sections. Metadata and parsed counts are checked, but
693 // complete semantic validation also requires traversing the stream. Physical
694 // offsets and all encoded lengths count P units; bit_view lengths count bits.
695 template <class P, stream_role Role = stream_role::native> struct profile_view {
696 using policy_type = P;
697 static constexpr stream_role role = Role;
698
699 profile_view(std::span<std::byte const> bytes, elias_fano_view offsets, profile_metadata metadata)
700 : profile_view(bytes, offsets, metadata, shape_only{}) {
701 validate_contents();
702 }
703
704 // Metadata/shape checks only: no stream, EF-word or sample bytes are read.
705 // Mapped owners may construct this view before touching any payload page.
706 // External metadata must also pass validate_offset_metadata() before
707 // navigation; trusted array builders establish that invariant themselves.
708 static profile_view from_sections(std::span<std::byte const> bytes,
709 elias_fano_view offsets, profile_metadata metadata) {
710 return {bytes, offsets, metadata, shape_only{}};
711 }
712
713 // The existing constructor's local content checks. This validates padding
714 // and the first/terminal offsets; it is not a complete framing, ordering or
715 // EF semantic scan. Query parsing still bounds each accessed record.
716 void validate_contents() const {
717 validate_offset_metadata();
718 auto bits = data_.size();
719 if ((bits & 7) && (std::to_integer<unsigned>(bytes_.back()) & ((1u << (8 - (bits & 7))) - 1)))
720 error_detail::raise<std::invalid_argument>("nonzero profile padding");
721 if (block_offset(0) != 0 || block_offset(block_count()) != metadata_.extent)
722 error_detail::raise<std::invalid_argument>("profile offset units or extent mismatch");
723 }
724
725 // External metadata admission checks fixed-stride representability once;
726 // it reads neither the FC payload nor any Elias–Fano word or sample.
727 // Trusted owning arrays establish this invariant while they are built.
729 auto stride = profile_detail::multiply(metadata_.record_count, metadata_.common_value_width.value_or(0));
730 if (stride > metadata_.extent || offsets_.universe() != metadata_.extent - stride)
731 error_detail::raise<std::invalid_argument>("profile offset universe or fixed stride mismatch");
732 }
733
734 std::uint64_t block_count() const noexcept {
735 return metadata_.record_count / P::codec_block_size +
736 (metadata_.record_count % P::codec_block_size != 0);
737 }
738 // The final sample is EOF at the actual record count. Fixed-stride
739 // arithmetic is unchecked here after metadata admission; the generic EF
740 // codec only selects the stored residual value.
741 std::uint64_t block_offset(std::uint64_t block) const {
742 auto blocks = block_count();
743 if (block > blocks) error_detail::raise<std::out_of_range>("profile block ordinal");
744 auto ordinal = block == blocks ? metadata_.record_count : block * P::codec_block_size;
745 return offsets_.template select<typename P::architecture>(block) + ordinal * metadata_.common_value_width.value_or(0);
746 }
747
748 std::uint64_t size() const noexcept { return metadata_.record_count; }
749 std::span<std::byte const> bytes() const noexcept { return bytes_; }
750 profile_metadata const & metadata() const noexcept { return metadata_; }
751 elias_fano_view group_offsets() const noexcept { return offsets_; }
754
755 // Physical block starts encode retained positions directly. Other records
756 // backspace from the preceding length, so only this block's controls need
757 // replaying; no inherited key bytes are reconstructed or read.
758 profile_encoded_record encoded_at(std::uint64_t ordinal) const {
759 if (ordinal >= size()) error_detail::raise<std::out_of_range>("profile record ordinal");
760 auto [at, retained] = locate(ordinal, nullptr);
761 return parse_payload(at, retained);
762 }
763
764 // Compatibility length lookup: replay the predecessor's physical block.
765 // A boundary can touch the preceding block, but never its literal bytes.
766 // At EOF terminal metadata supplies the length without replay.
767 std::uint64_t predecessor_units(std::uint64_t ordinal,
768 profile_comparison_work * work = nullptr) const {
769 if (ordinal > size()) error_detail::raise<std::out_of_range>("profile predecessor ordinal");
770 if (!ordinal) return 0;
771 if (ordinal == size()) return metadata_.terminal_key_units;
772 auto [at, retained] = locate(ordinal - 1, work);
773 auto record = parse_payload(at, retained);
774 if (work) ++work->skipped_headers;
775 return record.key_units;
776 }
777
778 // Only headers preceding the selected lane are parsed. The first candidate
779 // must share its retained prefix with the supplied query-bound context.
780 // Callback comparison references expire on the next call or return.
781 template <class F> void compare_window(std::uint64_t first, std::uint64_t last,
782 profile_query_context<P> context, F && callback, profile_comparison_work * work = nullptr,
783 std::uint64_t * predecessor = nullptr) const {
784 if (first > last || last > size() || last - first > P::group_size)
785 error_detail::raise<std::out_of_range>("profile comparison window exceeds cascade group");
786 if (first == last) {
787 if (predecessor) *predecessor = predecessor_units(first, work);
788 return;
789 }
790 auto [at, retained] = locate(first, work, predecessor);
791 auto record = parse_payload(at, retained);
792 for (auto i = first; i != last; ++i) {
793 auto compared = context.advance(record);
794 if (work) { ++work->visited_headers; work->compared_bits += compared; }
795 if (!callback(profile_comparison_item<P>{i, context, record.value})) return;
796 if (i + 1 != last) record = next_record(record, i + 1);
797 }
798 }
799
800 // The supplied anchor must share the first record's retained prefix. An
801 // anchor between its actual predecessor and that key suffices. The copied
802 // prefix is not an exact LCP with that anchor. Callback key views expire
803 // on the next call.
804 template <class F> void visit_window(std::uint64_t first, std::uint64_t last,
805 profile_anchor<P> anchor, std::uint64_t prefix_limit, F && callback) const {
806 if (first > last || last > size() || last - first > P::group_size)
807 error_detail::raise<std::out_of_range>("profile window exceeds the policy group size");
808 if (first == last) return;
809 if ((anchor.prefix.size() & (P::bits_per_unit - 1)) ||
810 (anchor.prefix.size() >> P::unit_shift) > anchor.full_units)
811 error_detail::raise<std::invalid_argument>("profile anchor units");
812 auto prefix_bits = profile_detail::multiply(
813 std::min(prefix_limit, (anchor.prefix.size() >> P::unit_shift)), P::bits_per_unit);
814 auto scratch = bit_string::copy(anchor.prefix.prefix(prefix_bits));
815 auto context = anchor.full_units;
816 auto record = encoded_at(first);
817 for (auto i = first; i != last; ++i) {
818 decode_into(record, prefix_limit, scratch, context);
819 if (!callback(profile_item<P>{i, {scratch.view(), context}, record.value})) return;
820 if (i + 1 != last) record = next_record(record, i + 1);
821 }
822 }
823
824 template <class F> void visit_all(F && callback) const {
825 if (!size()) return;
826 bit_string scratch;
827 std::uint64_t context = 0;
828 auto record = encoded_at(0);
829 for (std::uint64_t i = 0; i != size(); ++i) {
830 decode_into(record, std::numeric_limits<std::uint64_t>::max(), scratch, context);
831 if (!callback(profile_item<P>{i, {scratch.view(), context}, record.value})) return;
832 if (i + 1 != size()) record = next_record(record, i + 1);
833 }
834 if (record.next_offset != metadata_.extent) error_detail::raise<std::invalid_argument>("trailing profile data");
835 if (record.key_units != metadata_.terminal_key_units)
836 error_detail::raise<std::invalid_argument>("profile terminal length mismatch");
837 }
838
839 // Ordinary FC can traverse the entire prefix chain. LPFC bounds backward
840 // encoded distance by the full key length, not by prefix_limit; locating a
841 // record also scans at most W-1 headers before it. Values are copied in full.
843 std::uint64_t prefix_limit = std::numeric_limits<std::uint64_t>::max()) const {
844 auto record = encoded_at(ordinal);
846 result.full_units = record.key_units;
847 result.value = bit_string::copy(record.value);
848 auto need = std::min(record.key_units, prefix_limit);
849 profile_detail::resize(result.prefix, profile_detail::multiply(need, P::bits_per_unit));
850 while (need) {
851 if (record.key_units < need) error_detail::raise<std::invalid_argument>("profile predecessor prefix is too short");
852 if (record.retained < need) {
853 auto copy = profile_detail::multiply(need - record.retained, P::bits_per_unit);
854 profile_detail::copy_into(result.prefix, profile_detail::multiply(record.retained, P::bits_per_unit),
855 record.suffix.prefix(copy));
856 need = record.retained;
857 }
858 if (!need) break;
859 if (!ordinal) error_detail::raise<std::invalid_argument>("nonliteral first profile key");
860 auto retained = record.retained;
861 record = encoded_at(--ordinal);
862 if (record.key_units < retained) error_detail::raise<std::invalid_argument>("profile predecessor prefix is too short");
863 }
864 return result;
865 }
866
867 private:
868 struct shape_only {};
869 profile_view(std::span<std::byte const> bytes, elias_fano_view offsets,
871 : bytes_(bytes), offsets_(offsets), metadata_(metadata) {
872 auto expected = profile_detail::initial_metadata<P, Role>();
873 if (metadata.version != 2 || metadata.key_unit != P::unit || metadata.value_unit != P::unit ||
874 metadata.count_unit != P::unit || metadata.offset_unit != P::unit ||
875 metadata.backspace_code != P::backspace_code || metadata.backspace_parameter != P::backspace_parameter ||
876 metadata.count_code != expected.count_code || metadata.bit_order != profile_bit_order::msb_first ||
877 metadata.role != Role || metadata.group_size != P::group_size ||
878 metadata.codec_block_size != P::codec_block_size)
879 error_detail::raise<std::invalid_argument>("profile metadata does not match reader policy");
880 if (!metadata.policy_fixed_values && metadata.policy_value_width)
881 error_detail::raise<std::invalid_argument>("variable profile policy width must be zero");
882 if constexpr (Role == stream_role::borrowed) {
883 if (metadata.common_value_width != std::optional<std::uint64_t>(0))
884 error_detail::raise<std::invalid_argument>("borrowed profile must have empty values");
885 } else if (metadata.policy_fixed_values) {
886 if (metadata.common_value_width != metadata.policy_value_width)
887 error_detail::raise<std::invalid_argument>("fixed value width metadata mismatch");
888 }
889 if (!metadata.record_count && (metadata.extent || metadata.terminal_key_units))
890 error_detail::raise<std::invalid_argument>("nonempty data for empty profile");
891 auto bits = profile_detail::multiply(metadata.extent, P::bits_per_unit);
892 auto blocks = block_count();
893 if (bits > std::numeric_limits<std::uint64_t>::max() - 7 ||
894 bytes.size() != profile_detail::byte_count(bits) ||
895 blocks == std::numeric_limits<std::uint64_t>::max() || offsets.size() != blocks + 1)
896 error_detail::raise<std::invalid_argument>("profile section length mismatch");
897 data_ = {bytes, bits};
898 }
899
900 friend struct profile_cursor<P, Role>;
901 friend struct profile_encoded_cursor<P, Role>;
902 std::span<std::byte const> bytes_;
906
907 std::pair<std::uint64_t, std::uint64_t> locate(std::uint64_t ordinal, profile_comparison_work * work,
908 std::uint64_t * predecessor = nullptr) const {
909 auto group = ordinal / P::codec_block_size;
910 auto at = block_offset(group);
911 auto retained = read_absolute(at);
912 if (!group && retained) error_detail::raise<std::invalid_argument>("first profile key is not literal");
913 std::uint64_t previous = 0;
914 for (auto i = group * P::codec_block_size; i < ordinal; ++i) {
915 auto [key_units, next_offset] = parse_payload<false>(at, retained);
916 previous = key_units;
917 at = next_offset;
918 auto backspace = profile_detail::read_backspace<P>(data_, at);
919 if (backspace > previous) error_detail::raise<std::invalid_argument>("profile backspace exceeds predecessor");
920 retained = previous - backspace;
921 if (work) ++work->skipped_headers;
922 }
923 if (predecessor) {
924 if (ordinal && ordinal % P::codec_block_size == 0)
925 previous = predecessor_units(ordinal, work);
926 if (retained > previous)
927 error_detail::raise<std::invalid_argument>("profile retained prefix exceeds predecessor");
928 *predecessor = previous;
929 }
930 return {at, retained};
931 }
932
933 std::uint64_t read_absolute(std::uint64_t & at) const {
934 auto start = at;
935 auto retained = profile_detail::read_count<P>(data_, at);
936 // Every retained unit appeared in an earlier literal. Use the restored
937 // physical offset, including fixed-width values, rather than EF residuals.
938 if (retained > start)
939 error_detail::raise<std::invalid_argument>("profile retained prefix exceeds physical offset");
940 return retained;
941 }
942
943 profile_encoded_record parse_absolute(std::uint64_t at) const {
944 auto retained = read_absolute(at);
945 return parse_payload(at, retained);
946 }
947
948 profile_encoded_record parse_relative(std::uint64_t at, std::uint64_t previous) const {
949 auto backspace = profile_detail::read_backspace<P>(data_, at);
950 if (backspace > previous) error_detail::raise<std::invalid_argument>("profile backspace exceeds predecessor");
951 return parse_payload(at, previous - backspace);
952 }
953
954 template <bool Views = true>
955 auto parse_payload(std::uint64_t at, std::uint64_t retained) const {
956 auto suffix = profile_detail::read_count<P>(data_, at);
957 auto value = metadata_.common_value_width ? *metadata_.common_value_width : profile_detail::read_count<P>(data_, at);
958 if (at > metadata_.extent || suffix > metadata_.extent - at || value > metadata_.extent - at - suffix)
959 error_detail::raise<std::invalid_argument>("truncated profile payload");
960 // Absolute controls establish retained <= frame start. Relative controls
961 // inherit key_units <= the previous frame's end. Thus retained <= at and
962 // the bounded suffix gives key_units <= extent, whose bit size was admitted.
963 auto key_units = retained + suffix;
964 if constexpr (Views) {
965 auto suffix_bits = profile_detail::multiply(suffix, P::bits_per_unit);
966 auto value_bits = profile_detail::multiply(value, P::bits_per_unit);
967 auto key_data = data_.subview(profile_detail::multiply(at, P::bits_per_unit), suffix_bits);
968 at += suffix;
969 auto value_data = data_.subview(profile_detail::multiply(at, P::bits_per_unit), value_bits);
970 return profile_encoded_record{retained, key_units, value, key_data, value_data, at + value};
971 } else {
972 // Skipped lanes need only framing. Do not construct literal/value views
973 // or return an encoded record when neither payload will be inspected.
974 return std::pair<std::uint64_t, std::uint64_t>{key_units, at + suffix + value};
975 }
976 }
977
978 template <class Offset>
979 profile_encoded_record next_record(profile_encoded_record const & previous, std::uint64_t ordinal, Offset && offset) const {
980 auto at = previous.next_offset;
981 if (ordinal % P::codec_block_size == 0) {
982 if (offset(ordinal / P::codec_block_size) != at)
983 error_detail::raise<std::invalid_argument>("profile group offset mismatch");
984 auto next = parse_absolute(at);
985 if (next.retained > previous.key_units)
986 error_detail::raise<std::invalid_argument>("profile retained prefix exceeds predecessor");
987 return next;
988 }
989 return parse_relative(at, previous.key_units);
990 }
991
992 profile_encoded_record next_record(profile_encoded_record const & previous, std::uint64_t ordinal) const {
993 return next_record(previous, ordinal, [&](std::uint64_t block) { return block_offset(block); });
994 }
995
996 static void decode_into(profile_encoded_record const & record, std::uint64_t limit,
997 bit_string & scratch, std::uint64_t & context) {
998 if (record.retained > context) error_detail::raise<std::invalid_argument>("profile anchor is too short");
999 auto keep = std::min(record.retained, limit);
1000 auto keep_bits = profile_detail::multiply(keep, P::bits_per_unit);
1001 if (keep_bits > scratch.bit_size) error_detail::raise<std::invalid_argument>("profile anchor prefix is incomplete");
1002 profile_detail::resize(scratch, keep_bits);
1003 auto full = std::min(record.key_units, limit);
1004 profile_detail::append(scratch, record.suffix.prefix(profile_detail::multiply(full - keep, P::bits_per_unit)));
1005 context = record.key_units;
1006 }
1007 };
1008
1009 // Sequential framing without reconstructed keys or payload copies. The
1010 // caller retains the view's sections. A copied frame's suffix/value views
1011 // remain valid while those sections live; the peek reference expires on
1012 // advance or destruction. Parsing checks lengths and block offsets, but
1013 // sorted order and valid retention require semantic admission or a caller
1014 // that compares against the physical predecessor.
1015 template <class P, stream_role Role> struct profile_encoded_cursor {
1016 using policy_type = P;
1017 static constexpr stream_role role = Role;
1018
1019 explicit profile_encoded_cursor(profile_view<P, Role> view) : view_(view), offsets_(view.group_offsets()) {
1020 if (!done()) { record_ = view_.encoded_at(0); (void)offsets_.next(); }
1021 }
1022
1023 bool done() const noexcept { return ordinal_ == view_.size(); }
1024 std::uint64_t ordinal() const noexcept { return ordinal_; }
1025 profile_encoded_record const & peek() const & {
1026 if (done()) error_detail::raise<std::out_of_range>("encoded profile cursor at end");
1027 return record_;
1028 }
1029 profile_encoded_record const & peek() const && = delete;
1030
1031 void advance() {
1032 if (done()) error_detail::raise<std::out_of_range>("encoded profile cursor at end");
1033 if (ordinal_ + 1 == view_.size()) {
1034 if (record_.next_offset != view_.metadata_.extent)
1035 error_detail::raise<std::invalid_argument>("trailing profile data");
1036 if (record_.key_units != view_.metadata_.terminal_key_units)
1037 error_detail::raise<std::invalid_argument>("profile terminal length mismatch");
1038 } else {
1039 std::optional<elias_fano_cursor> offsets;
1040 auto next = view_.next_record(record_, ordinal_ + 1, [&](std::uint64_t block) {
1041 offsets.emplace(offsets_);
1042 return offsets->next() + block * P::codec_block_size * view_.metadata_.common_value_width.value_or(0);
1043 });
1044 record_ = next;
1045 if (offsets) offsets_ = *offsets;
1046 }
1047 ++ordinal_;
1048 }
1049
1050 private:
1054 std::uint64_t ordinal_ = 0;
1055 };
1056
1057 template <class P, stream_role Role>
1061
1062 // Resumable sequential traversal. Each encoded record is parsed once and
1063 // its value remains a view of the original payload. Only the current key is
1064 // reconstructed. The view's sections must outlive this cursor; peek's key
1065 // view additionally expires when this cursor advances or is destroyed.
1066 template <class P, stream_role Role> struct profile_cursor {
1067 using policy_type = P;
1068 static constexpr stream_role role = Role;
1069
1070 explicit profile_cursor(profile_view<P, Role> view) : profile_cursor(view, 0, {}) {}
1071
1072 // The caller supplies the true lower bound of query in this native run.
1073 // The interval between the preceding key and this key shares every
1074 // retained prefix, so query supplies the first frame's missing bits.
1075 profile_cursor(profile_view<P, Role> view, std::uint64_t ordinal, bit_view query)
1076 : view_(view), offsets_(view.group_offsets(),
1077 ordinal < view.size() ? ordinal / P::codec_block_size : view.group_offsets().size(),
1078 std::type_identity<typename P::architecture>{}), ordinal_(ordinal) {
1079 if (ordinal > view.size()) error_detail::raise<std::out_of_range>("profile cursor ordinal");
1080 if (!done()) {
1081 record_ = view_.encoded_at(ordinal);
1082 (void)offsets_.next();
1083 auto retained = record_.retained << P::unit_shift;
1084 if (retained > query.size()) error_detail::raise<std::invalid_argument>("profile cursor missing query prefix");
1085 scratch_ = bit_string::copy(query.subview(0, retained));
1086 context_ = record_.retained;
1087 profile_view<P, Role>::decode_into(record_, std::numeric_limits<std::uint64_t>::max(), scratch_, context_);
1088 }
1089 }
1090
1091 bool done() const noexcept { return ordinal_ == view_.size(); }
1092 std::uint64_t ordinal() const noexcept { return ordinal_; }
1094 if (done()) error_detail::raise<std::out_of_range>("profile cursor at end");
1095 return {ordinal_, {scratch_.view(), context_}, record_.value};
1096 }
1097 profile_item<P> peek() const && = delete;
1098 std::uint64_t retained_bits() const {
1099 if (done()) error_detail::raise<std::out_of_range>("profile cursor at end");
1100 return record_.retained << P::unit_shift;
1101 }
1102
1103 void advance() { advance_impl<false>(nullptr); }
1104
1105 // Compare the old and new keys from the retained FC prefix onward before
1106 // replacing scratch. Ordinary FC needs only its first differing unit;
1107 // redundant retained-prefix encodings remain correct. No result at EOF.
1108 std::optional<bit_comparison> advance_comparison() {
1109 bit_comparison result;
1110 advance_impl<true>(&result);
1111 return done() ? std::nullopt : std::optional<bit_comparison>{result};
1112 }
1113
1114 private:
1115 template <bool Compare> void advance_impl(bit_comparison * comparison) {
1116 if (done()) error_detail::raise<std::out_of_range>("profile cursor at end");
1117 if (ordinal_ + 1 == view_.size()) {
1118 if (record_.next_offset != view_.metadata_.extent)
1119 error_detail::raise<std::invalid_argument>("trailing profile data");
1120 if (record_.key_units != view_.metadata_.terminal_key_units)
1121 error_detail::raise<std::invalid_argument>("profile terminal length mismatch");
1122 ++ordinal_;
1123 return;
1124 }
1125 // Keep the navigation position with the committed record if parsing or
1126 // key reconstruction throws. Only block boundaries copy cursor state.
1127 std::optional<elias_fano_cursor> offsets;
1128 auto next = view_.next_record(record_, ordinal_ + 1, [&](std::uint64_t block) {
1129 offsets.emplace(offsets_);
1130 return offsets->next() + block * P::codec_block_size * view_.metadata_.common_value_width.value_or(0);
1131 });
1132 if constexpr (Compare) {
1133 auto retained_bits = profile_detail::multiply(next.retained, P::bits_per_unit);
1134 auto previous = scratch_.view();
1135 if (retained_bits == previous.size() || next.suffix.empty()) {
1136 *comparison = {retained_bits, retained_bits != previous.size() ? 1 : next.suffix.empty() ? 0 : -1};
1137 } else {
1138 auto before = profile_detail::load_bits(previous, retained_bits, P::bits_per_unit);
1139 auto after = profile_detail::load_bits(next.suffix, 0, P::bits_per_unit);
1140 if (before != after) {
1141 auto common = unsigned(std::countl_zero(before ^ after)) - (64 - P::bits_per_unit);
1142 *comparison = {retained_bits + common, before < after ? -1 : 1};
1143 } else {
1144 auto suffix = compare_common_bits<typename P::architecture>(
1145 previous.subview(retained_bits, previous.size() - retained_bits), next.suffix);
1146 *comparison = {retained_bits + suffix.common_bits, suffix.order};
1147 }
1148 }
1149 }
1150 profile_view<P, Role>::decode_into(next, std::numeric_limits<std::uint64_t>::max(), scratch_, context_);
1151 record_ = next;
1152 if (offsets) offsets_ = *offsets;
1153 ++ordinal_;
1154 }
1155
1156 private:
1160 std::uint64_t context_ = 0;
1162 std::uint64_t ordinal_ = 0;
1163 };
1164
1165 template <class P, stream_role Role>
1167
1168 template <class P, stream_role Role = stream_role::native> struct profile_array {
1169 profile_array() = default;
1170 using policy_type = P;
1171 static constexpr stream_role role = Role;
1172
1173 static profile_array build(std::span<profile_record const> records,
1174 std::span<std::uint64_t const> prefix_ceilings = {}, std::uint64_t restart_factor = 0) {
1175 if (!prefix_ceilings.empty() && prefix_ceilings.size() != records.size())
1176 error_detail::raise<std::invalid_argument>("one profile prefix ceiling is required per record");
1177 if (restart_factor && restart_factor < 3) error_detail::raise<std::invalid_argument>("LPFC factor must be at least three");
1178 profile_array result;
1179 std::optional<std::uint64_t> common;
1180 if constexpr (Role == stream_role::borrowed) common = 0;
1181 else if constexpr (P::fixed_width) common = P::value_width;
1182 else if (records.empty()) common = 0;
1183 else common = (records.front().value.view().size() >> P::unit_shift);
1184 for (auto const & record : records) {
1185 auto key_bits = record.key.view().size();
1186 auto value_bits = record.value.view().size();
1187 if ((key_bits & (P::bits_per_unit - 1)) || (value_bits & (P::bits_per_unit - 1)))
1188 error_detail::raise<std::invalid_argument>("record length does not match profile unit");
1189 auto width = (value_bits >> P::unit_shift);
1190 if constexpr (Role == stream_role::borrowed) {
1191 if (width) error_detail::raise<std::invalid_argument>("borrowed profile cannot carry values");
1192 } else if constexpr (P::fixed_width) {
1193 if (width != *P::value_width) error_detail::raise<std::invalid_argument>("value does not match fixed policy width");
1194 } else {
1195 if (common && *common != width) common.reset();
1196 }
1197 }
1198 result.metadata_.common_value_width = common;
1199 result.metadata_.record_count = records.size();
1200 bit_string data;
1201 bit_view previous;
1202 std::uint64_t previous_units = 0;
1203 std::uint64_t anchor_offset = 0;
1204 std::vector<std::uint64_t> offsets;
1205 for (std::size_t i = 0; i != records.size(); ++i) {
1206 auto key = records[i].key.view();
1207 auto value = records[i].value.view();
1208 auto comparison = compare_common_bits<typename P::architecture>(previous, key);
1209 if (i && comparison.order > 0) error_detail::raise<std::invalid_argument>("profile keys must be sorted");
1210 auto position = (data.bit_size >> P::unit_shift);
1211 if (i % P::codec_block_size == 0)
1212 offsets.push_back(position - profile_detail::multiply(i, common.value_or(0)));
1213 auto key_units = (key.size() >> P::unit_shift);
1214 auto retained = (comparison.common_bits >> P::unit_shift);
1215 if (records[i].retained_limit_bits)
1216 retained = std::min(retained, *records[i].retained_limit_bits >> P::unit_shift);
1217 if (!prefix_ceilings.empty()) retained = std::min(retained, prefix_ceilings[i]);
1218 auto start = (data.bit_size >> P::unit_shift);
1219 if (retained && restart_factor && key_units <= std::numeric_limits<std::uint64_t>::max() / restart_factor &&
1220 start - anchor_offset > restart_factor * key_units) retained = 0;
1221 if (!retained) anchor_offset = start;
1222 if (i % P::codec_block_size == 0) profile_detail::write_count<P>(data, retained);
1223 else profile_detail::write_backspace<P>(data, previous_units - retained);
1224 profile_detail::write_count<P>(data, key_units - retained);
1225 if (!common) profile_detail::write_count<P>(data, (value.size() >> P::unit_shift));
1226 profile_detail::append(data, key.subview(profile_detail::multiply(retained, P::bits_per_unit),
1227 key.size() - profile_detail::multiply(retained, P::bits_per_unit)));
1228 profile_detail::append(data, value);
1229 previous = key;
1230 previous_units = key_units;
1231 }
1232 result.metadata_.terminal_key_units = previous_units;
1233 result.metadata_.extent = (data.bit_size >> P::unit_shift);
1234 offsets.push_back(result.metadata_.extent - profile_detail::multiply(records.size(), common.value_or(0)));
1235 result.offsets_ = elias_fano::build<typename P::architecture>(offsets);
1236 result.bytes_ = std::move(data.bytes);
1237 return result;
1238 }
1239
1240 std::uint64_t size() const noexcept { return metadata_.record_count; }
1241 std::span<std::byte const> bytes() const noexcept { return bytes_; }
1242 profile_metadata const & metadata() const noexcept { return metadata_; }
1243 elias_fano const & group_offsets() const noexcept { return offsets_; }
1244 // These private sections come from the checked builders. Recheck their
1245 // shapes after copying/moving, without rereading payload or EF endpoints.
1247 return profile_view<P, Role>::from_sections(bytes_, offsets_.view(), metadata_);
1248 }
1249 profile_view<P, Role> view() const && = delete;
1250
1251 private:
1252 friend struct profile_borrowed_writer<P>;
1253 friend struct profile_native_writer<P>;
1254 friend struct profile_detail::native_output<P>;
1255 friend struct profile_detail::borrowed_sections<P>;
1256 std::vector<std::byte> bytes_;
1257 // The profile's EOF marker is explicit; the generic codec defaults empty.
1258 elias_fano offsets_ = elias_fano::build<typename P::architecture>(std::array<std::uint64_t, 1>{0});
1259 profile_metadata metadata_ = profile_detail::initial_metadata<P, Role>();
1260 profile_array(std::vector<std::byte> bytes, elias_fano offsets, profile_metadata metadata)
1261 : bytes_(std::move(bytes)), offsets_(std::move(offsets)), metadata_(metadata) {}
1262 };
1263
1264 namespace profile_detail {
1265 // Internal handoff from the same checked modified-FC encoder to an owned
1266 // array. Payload bytes and sparse navigation have already been finalized.
1267 template <class P> struct borrowed_sections {
1268 static profile_array<P, stream_role::borrowed> adopt(std::vector<std::byte> bytes,
1269 elias_fano offsets, profile_metadata metadata) {
1270 return {std::move(bytes), std::move(offsets), metadata};
1271 }
1272 };
1273 }
1274
1275 // Incremental modified-FC output. The caller supplies any boundary-dependent
1276 // prefix ceiling before appending that key. There is no all-keys staging:
1277 // retained state is the previous key, encoded bytes and one offset per group.
1278 // With the same ceilings this produces exactly the batch borrowed encoding
1279 // (restart_factor == 0), including block controls, tail padding and EF metadata.
1280 template <class P> struct profile_borrowed_writer {
1281 using policy_type = P;
1282 static constexpr stream_role role = stream_role::borrowed;
1283
1284 std::uint64_t size() const noexcept { return count_; }
1285 bool finished() const noexcept { return finished_; }
1286
1287 void append(bit_view key, std::uint64_t prefix_ceiling = std::numeric_limits<std::uint64_t>::max()) {
1288 if (finished_) error_detail::raise<std::logic_error>("borrowed profile writer is finished");
1289 if (key.size() & (P::bits_per_unit - 1)) error_detail::raise<std::invalid_argument>("key length does not match profile unit");
1290 auto previous = previous_.view();
1291 auto comparison = compare_common_bits<typename P::architecture>(previous, key);
1292 if (count_ && comparison.order > 0) error_detail::raise<std::invalid_argument>("profile keys must be sorted");
1293 append_known(key, comparison.common_bits, prefix_ceiling);
1294 }
1295
1296 // Finalizing EF takes work proportional to the staged group offsets.
1297 // This is not a bounded-byte or durable checkpoint operation.
1299 if (finished_) error_detail::raise<std::logic_error>("borrowed profile writer is finished");
1301 auto extent = (data_.bit_size >> P::unit_shift);
1302 offsets_.push_back(extent);
1303 try {
1304 result.offsets_ = elias_fano::build<typename P::architecture>(offsets_);
1305 } catch (...) {
1306 offsets_.pop_back();
1307 throw;
1308 }
1309 result.metadata_.record_count = count_;
1310 result.metadata_.extent = extent;
1311 result.metadata_.terminal_key_units = (previous_.bit_size >> P::unit_shift);
1312 result.bytes_ = std::move(data_.bytes);
1313 data_.bit_size = 0;
1314 previous_ = {};
1315 offsets_ = std::vector<std::uint64_t>{};
1316 finished_ = true;
1317 return result;
1318 }
1319
1320 private:
1321 template <class, class, class> friend struct index_builder;
1322 template <class, class, class> friend struct cola_detail::index_output;
1323 friend struct profile_detail::index_output<P>;
1324 // The index builders bypass the public comparison, using the exact
1325 // LCP already obtained when this borrowed key was accepted. Framing and
1326 // allocation rollback are shared with the public checked writer.
1327 void append_known(bit_view key, std::uint64_t exact_common_bits,
1328 std::uint64_t prefix_ceiling = std::numeric_limits<std::uint64_t>::max()) {
1329 auto next_count = profile_detail::add(count_, 1);
1330 auto retained = std::min((exact_common_bits >> P::unit_shift), prefix_ceiling);
1331 auto previous_units = (previous_.bit_size >> P::unit_shift);
1332 auto key_units = (key.size() >> P::unit_shift);
1333 auto saved_bits = data_.bit_size;
1334 auto saved_offsets = offsets_.size();
1335 // Reserve before modifying output. The private predecessor retains its
1336 // logical contents until all potentially allocating output writes finish.
1337 auto next_bytes = profile_detail::byte_count(key.size());
1338 if (next_bytes > previous_.bytes.max_size()) error_detail::raise<std::length_error>("profile bit string too large");
1339 previous_.bytes.reserve(static_cast<std::size_t>(next_bytes));
1340 try {
1341 if (count_ % P::codec_block_size == 0) {
1342 offsets_.push_back((data_.bit_size >> P::unit_shift));
1343 profile_detail::write_count<P>(data_, retained);
1344 } else profile_detail::write_backspace<P>(data_, previous_units - retained);
1345 profile_detail::write_count<P>(data_, key_units - retained);
1346 auto retained_bits = profile_detail::multiply(retained, P::bits_per_unit);
1347 profile_detail::append(data_, key.subview(retained_bits, key.size() - retained_bits));
1348 } catch (...) {
1349 profile_detail::resize(data_, saved_bits);
1350 offsets_.resize(saved_offsets);
1351 throw;
1352 }
1353 // This resize cannot allocate after reserve. Keep the actual shared
1354 // prefix even when the encoding used a smaller boundary prefix ceiling.
1355 auto common_bits = exact_common_bits & ~std::uint64_t(P::bits_per_unit - 1);
1356 profile_detail::resize(previous_, key.size());
1357 profile_detail::copy_into(previous_, common_bits,
1358 key.subview(common_bits, key.size() - common_bits));
1359 count_ = next_count;
1360 }
1361
1364 std::vector<std::uint64_t> offsets_;
1365 std::uint64_t count_ = 0;
1366 bool finished_ = false;
1367 };
1368}
Encodes and selects arbitrary nondecreasing integer sequences.
Outlines exceptional check failures while preserving their types and messages.
Provides bounded byte comparison shared by the key codecs.
std::uint64_t load_big(void const *source) noexcept
Definition key_detail.h:57
void store_big(void *target, std::uint64_t value) noexcept
Definition key_detail.h:69
void write_count(bit_string &target, std::uint64_t value)
Definition profile.h:533
void put_fixed(bit_string &target, std::uint64_t &at, std::uint64_t value, unsigned width)
Definition profile.h:504
profile_metadata initial_metadata()
Definition profile.h:667
void append_bit(bit_string &target, bool bit)
Definition profile.h:493
std::uint64_t add(std::uint64_t a, std::uint64_t b)
Definition profile.h:39
std::uint64_t multiply(std::uint64_t a, std::uint64_t b)
Definition profile.h:44
std::uint64_t byte_count(std::uint64_t bits) noexcept
Definition profile.h:50
std::uint64_t read_count(bit_view data, std::uint64_t &offset)
Definition profile.h:547
std::uint64_t read_backspace(bit_view data, std::uint64_t &offset)
Definition profile.h:641
std::uint64_t read_zero_run(bit_view data, std::uint64_t &at, std::uint64_t limit, char const *truncated, char const *overflow)
Definition profile.h:517
constexpr std::uint64_t golomb_cutoff()
Definition profile.h:587
std::uint64_t backspace_bits(std::uint64_t value)
Definition profile.h:595
void write_backspace(bit_string &target, std::uint64_t value)
Definition profile.h:609
void resize(bit_string &value, std::uint64_t bits)
Definition profile.h:465
void copy_into(bit_string &target, std::uint64_t first, bit_view source)
Definition profile.h:474
void append_byte(bit_string &target, unsigned value)
Definition profile.h:498
std::uint64_t low_mask(unsigned width) noexcept
Definition profile.h:89
void copy_bits(std::byte *target, std::uint64_t first, bit_view source) noexcept
Definition profile.h:123
void store_bits(std::byte *target, std::uint64_t at, std::uint64_t value, unsigned width) noexcept
Definition profile.h:111
std::uint64_t load_bits(bit_view data, std::uint64_t first, unsigned width) noexcept
Definition profile.h:93
void append(bit_string &target, bit_view source)
Definition profile.h:480
std::uint64_t read_fixed(bit_view data, std::uint64_t &at, unsigned width)
Definition profile.h:510
bit_string value(arrow_t< S > const &value)
Definition typed_world.h:99
bit_string key(key_t< S > const &value)
Definition typed_world.h:89
Definition active_engine.h:18
stream_role
Definition policy.h:25
profile_count_code
Definition profile.h:270
int compare_profile_prefix(profile_anchor< P > key, bit_view query)
Definition profile.h:259
bit_backspace_code
Definition policy.h:27
bit_comparison compare_common_bits(bit_view a, bit_view b)
Definition profile.h:215
std::uint64_t common_prefix_units(bit_view a, bit_view b)
Definition profile.h:244
int compare_bits(bit_view a, bit_view b)
Definition profile.h:243
profile_bit_order
Definition profile.h:271
profile_unit
Definition registry.h:29
Declares Everett's policy support.
Definition profile.h:209
std::uint64_t common_bits
Definition profile.h:210
int order
Definition profile.h:211
Definition profile.h:166
static bit_string from_bytes(std::span< std::byte const > source)
Definition profile.h:188
bit_view view() const &&=delete
static bit_string from_bits(std::string_view source)
Definition profile.h:194
static bit_string from_bytes(std::string_view source)
Definition profile.h:191
void validate() const
Definition profile.h:170
std::vector< std::byte > bytes
Definition profile.h:167
bool operator==(bit_string const &) const =default
bit_view view() const &
Definition profile.h:178
static bit_string copy(bit_view source)
Definition profile.h:181
std::uint64_t bit_size
Definition profile.h:168
Definition profile.h:56
bit_view()=default
std::span< std::byte const > storage() const noexcept
Definition profile.h:67
bool at(std::uint64_t i) const
Definition profile.h:68
std::uint64_t size() const noexcept
Definition profile.h:64
std::uint64_t size_
Definition profile.h:85
std::uint64_t offset_
Definition profile.h:84
std::uint64_t offset() const noexcept
Definition profile.h:66
bool empty() const noexcept
Definition profile.h:65
bit_view subview(std::uint64_t first, std::uint64_t count) const
Definition profile.h:73
bit_view prefix(std::uint64_t count) const
Definition profile.h:80
bit_view(std::span< std::byte const > bytes, std::uint64_t bits, std::uint64_t offset=0)
Definition profile.h:58
std::span< std::byte const > bytes_
Definition profile.h:83
Definition cola_index.h:398
Definition cola_query.h:85
Definition cola_index.h:114
Definition elias_fano.h:332
Definition elias_fano.h:212
std::uint64_t size() const noexcept
Definition elias_fano.h:256
Definition elias_fano.h:397
Definition index_builder.h:47
Definition profile.h:250
std::uint64_t full_units
Definition profile.h:252
static profile_anchor complete(bit_view key)
Definition profile.h:253
bit_view prefix
Definition profile.h:251
Definition profile.h:1168
profile_metadata metadata_
Definition profile.h:1259
elias_fano offsets_
Definition profile.h:1258
elias_fano const & group_offsets() const noexcept
Definition profile.h:1243
std::uint64_t size() const noexcept
Definition profile.h:1240
profile_metadata const & metadata() const noexcept
Definition profile.h:1242
static profile_array build(std::span< profile_record const > records, std::span< std::uint64_t const > prefix_ceilings={}, std::uint64_t restart_factor=0)
Definition profile.h:1173
profile_view< P, Role > view() const &
Definition profile.h:1246
profile_array(std::vector< std::byte > bytes, elias_fano offsets, profile_metadata metadata)
Definition profile.h:1260
P policy_type
Definition profile.h:1170
std::span< std::byte const > bytes() const noexcept
Definition profile.h:1241
std::vector< std::byte > bytes_
Definition profile.h:1256
Definition profile_blob.h:73
Definition profile_blob.h:187
Definition profile.h:1280
bit_string previous_
Definition profile.h:1363
P policy_type
Definition profile.h:1281
void append(bit_view key, std::uint64_t prefix_ceiling=std::numeric_limits< std::uint64_t >::max())
Definition profile.h:1287
profile_array< P, stream_role::borrowed > finish()
Definition profile.h:1298
bit_string data_
Definition profile.h:1362
std::uint64_t size() const noexcept
Definition profile.h:1284
std::vector< std::uint64_t > offsets_
Definition profile.h:1364
void append_known(bit_view key, std::uint64_t exact_common_bits, std::uint64_t prefix_ceiling=std::numeric_limits< std::uint64_t >::max())
Definition profile.h:1327
bool finished() const noexcept
Definition profile.h:1285
Definition profile.h:458
bit_view value
Definition profile.h:461
profile_query_context< P > const & comparison
Definition profile.h:460
Definition profile.h:452
std::uint64_t skipped_headers
Definition profile.h:453
Definition profile.h:1066
bool done() const noexcept
Definition profile.h:1091
profile_item< P > peek() const &
Definition profile.h:1093
profile_view< P, Role > view_
Definition profile.h:1157
std::uint64_t ordinal() const noexcept
Definition profile.h:1092
std::optional< bit_comparison > advance_comparison()
Definition profile.h:1108
profile_cursor(profile_view< P, Role > view, std::uint64_t ordinal, bit_view query)
Definition profile.h:1075
profile_encoded_record record_
Definition profile.h:1161
profile_cursor(profile_view< P, Role > view)
Definition profile.h:1070
P policy_type
Definition profile.h:1067
void advance()
Definition profile.h:1103
elias_fano_cursor offsets_
Definition profile.h:1158
bit_string scratch_
Definition profile.h:1159
void advance_impl(bit_comparison *comparison)
Definition profile.h:1115
profile_item< P > peek() const &&=delete
Definition profile.h:317
bit_string prefix
Definition profile.h:318
profile_anchor< P > anchor() const &
Definition profile.h:321
std::uint64_t full_units
Definition profile.h:319
profile_anchor< P > anchor() const &&=delete
bit_string value
Definition profile.h:320
static profile_array< P, stream_role::borrowed > adopt(std::vector< std::byte > bytes, elias_fano offsets, profile_metadata metadata)
Definition profile.h:1268
Definition profile_index.h:86
Definition profile.h:1015
profile_encoded_record record_
Definition profile.h:1053
profile_view< P, Role > view_
Definition profile.h:1051
bool done() const noexcept
Definition profile.h:1023
elias_fano_cursor offsets_
Definition profile.h:1052
profile_encoded_cursor(profile_view< P, Role > view)
Definition profile.h:1019
P policy_type
Definition profile.h:1016
profile_encoded_record const & peek() const &
Definition profile.h:1025
profile_encoded_record const & peek() const &&=delete
std::uint64_t ordinal() const noexcept
Definition profile.h:1024
Definition profile.h:308
bit_view value
Definition profile.h:313
std::uint64_t value_units
Definition profile.h:311
std::uint64_t next_offset
Definition profile.h:314
bit_view suffix
Definition profile.h:312
std::uint64_t retained
Definition profile.h:309
std::uint64_t key_units
Definition profile.h:310
Definition profile.h:302
std::uint64_t ordinal
Definition profile.h:303
profile_anchor< P > key
Definition profile.h:304
bit_view value
Definition profile.h:305
Definition profile.h:273
profile_unit offset_unit
Definition profile.h:278
std::uint64_t policy_value_width
Definition profile.h:285
profile_unit key_unit
Definition profile.h:275
std::uint64_t extent
Definition profile.h:291
profile_unit value_unit
Definition profile.h:276
profile_count_code count_code
Definition profile.h:279
profile_bit_order bit_order
Definition profile.h:282
std::uint64_t backspace_parameter
Definition profile.h:281
std::uint64_t terminal_key_units
Definition profile.h:289
stream_role role
Definition profile.h:283
std::uint32_t version
Definition profile.h:274
profile_unit count_unit
Definition profile.h:277
bool policy_fixed_values
Definition profile.h:284
bit_backspace_code backspace_code
Definition profile.h:280
std::uint64_t record_count
Definition profile.h:290
std::uint64_t codec_block_size
Definition profile.h:288
std::optional< std::uint64_t > common_value_width
Definition profile.h:286
std::uint64_t group_size
Definition profile.h:287
Definition native_writer.h:158
Definition profile.h:328
int order() const noexcept
Definition profile.h:352
std::uint64_t advance(profile_encoded_record const &record)
Definition profile.h:419
profile_query_context(std::shared_ptr< bit_string const > query, int order)
Definition profile.h:407
std::uint64_t advance_parts(std::uint64_t retained_bits, std::uint64_t full_bits, std::span< bit_view const > literal)
Definition profile.h:357
static profile_query_context from_borrowed(bit_string const &query)
Definition profile.h:400
std::optional< std::uint64_t > full_units() const noexcept
Definition profile.h:349
profile_query_context(bit_view query)
Definition profile.h:329
bit_view query() const
Definition profile.h:342
profile_query_context predecessor(std::uint64_t lcp_bits) const
Definition profile.h:437
std::uint64_t common_bits() const noexcept
Definition profile.h:346
static profile_query_context from_owned(bit_string query)
Definition profile.h:336
std::shared_ptr< bit_string const > query_
Definition profile.h:413
profile_query_context with_key(bit_view key) const
Definition profile.h:385
Definition profile.h:294
std::optional< std::uint64_t > retained_limit_bits
Definition profile.h:299
bit_string key
Definition profile.h:295
bit_string value
Definition profile.h:296
Definition profile.h:868
Definition profile.h:695
std::span< std::byte const > bytes() const noexcept
Definition profile.h:749
profile_encoded_record next_record(profile_encoded_record const &previous, std::uint64_t ordinal, Offset &&offset) const
Definition profile.h:979
elias_fano_view group_offsets() const noexcept
Definition profile.h:751
profile_decoded_record< P > reconstruct_at(std::uint64_t ordinal, std::uint64_t prefix_limit=std::numeric_limits< std::uint64_t >::max()) const
Definition profile.h:842
std::uint64_t block_offset(std::uint64_t block) const
Definition profile.h:741
void validate_contents() const
Definition profile.h:716
elias_fano_view offsets_
Definition profile.h:903
profile_encoded_cursor< P, Role > encoded_cursor() const
Definition profile.h:1058
profile_encoded_record next_record(profile_encoded_record const &previous, std::uint64_t ordinal) const
Definition profile.h:992
std::uint64_t block_count() const noexcept
Definition profile.h:734
std::pair< std::uint64_t, std::uint64_t > locate(std::uint64_t ordinal, profile_comparison_work *work, std::uint64_t *predecessor=nullptr) const
Definition profile.h:907
void compare_window(std::uint64_t first, std::uint64_t last, profile_query_context< P > context, F &&callback, profile_comparison_work *work=nullptr, std::uint64_t *predecessor=nullptr) const
Definition profile.h:781
static profile_view from_sections(std::span< std::byte const > bytes, elias_fano_view offsets, profile_metadata metadata)
Definition profile.h:708
profile_metadata metadata_
Definition profile.h:904
void validate_offset_metadata() const
Definition profile.h:728
profile_encoded_record parse_relative(std::uint64_t at, std::uint64_t previous) const
Definition profile.h:948
std::uint64_t size() const noexcept
Definition profile.h:748
static void decode_into(profile_encoded_record const &record, std::uint64_t limit, bit_string &scratch, std::uint64_t &context)
Definition profile.h:996
std::span< std::byte const > bytes_
Definition profile.h:902
profile_encoded_record parse_absolute(std::uint64_t at) const
Definition profile.h:943
void visit_all(F &&callback) const
Definition profile.h:824
profile_cursor< P, Role > cursor() const
Definition profile.h:1166
std::uint64_t read_absolute(std::uint64_t &at) const
Definition profile.h:933
void visit_window(std::uint64_t first, std::uint64_t last, profile_anchor< P > anchor, std::uint64_t prefix_limit, F &&callback) const
Definition profile.h:804
bit_view data_
Definition profile.h:905
std::uint64_t predecessor_units(std::uint64_t ordinal, profile_comparison_work *work=nullptr) const
Definition profile.h:767
profile_encoded_record encoded_at(std::uint64_t ordinal) const
Definition profile.h:758
P policy_type
Definition profile.h:696
auto parse_payload(std::uint64_t at, std::uint64_t retained) const
Definition profile.h:955
profile_metadata const & metadata() const noexcept
Definition profile.h:750
profile_view(std::span< std::byte const > bytes, elias_fano_view offsets, profile_metadata metadata)
Definition profile.h:699