3#include <pixie/permutations/detail/sequence_tree_kernels.h>
4#include <pixie/permutations/detail/tree_policy.h>
6#include <pixie/storage/aligned.h>
22#ifdef PIXIE_SEQUENCE_TREE_TESTING
23#include <unordered_set>
31 std::size_t StorageBits,
38namespace pixie::permutations::detail {
54concept SequenceBlock =
55 std::is_nothrow_default_constructible_v<B> &&
56 std::is_nothrow_move_constructible_v<B> &&
57 std::is_nothrow_move_assignable_v<B> && std::is_nothrow_destructible_v<B> &&
58 requires(B& a, B& b,
const B& c, std::size_t n) {
59 typename B::value_type;
60 requires B::capacity >= 1 &&
62 std::numeric_limits<std::size_t>::max() / 2;
63 { c.size() }
noexcept -> std::same_as<std::size_t>;
64 { c[n] } -> std::same_as<typename B::value_type>;
65 { a.rotate_left(n, n, n) }
noexcept -> std::same_as<void>;
66 { a.redistribute(b, n) }
noexcept -> std::same_as<void>;
77concept InsertableSequenceBlock =
79 requires(B& a, std::size_t n,
typename B::value_type v) {
80 { a.insert_at(n, v) }
noexcept -> std::same_as<void>;
117template <SequenceBlock Block,
118 std::size_t Fanout = 8,
120 bool IndexBias =
false,
121 class Policy = SequenceTreePolicy>
123 static_assert(Fanout >= 4 && Fanout % 2 == 0);
124 using ChildOrder =
typename Policy::template Order<Fanout>;
125 static_assert(!IndexBias || ChildOrder::supports_bias);
126 static_assert(!IndexBias ||
127 (std::is_unsigned_v<typename Block::value_type> &&
128 !std::is_same_v<typename Block::value_type, bool>));
129 static_assert(!IndexBias ||
requires(Block& block, std::uint64_t bias) {
130 { block.add_bias(bias) }
noexcept -> std::same_as<void>;
134 std::uint64_t value = 0;
136 using Bias = std::conditional_t<IndexBias, PendingBias, NoBias>;
137 struct alignas(std::max(alignof(CacheLine), alignof(Block))) Leaf {
139 [[no_unique_address]] Bias bias;
141 struct alignas(IndexBias ? alignof(CacheLine)
143 : alignof(std::max_align_t)) Node
144 : ChildOrder::Header {
148 std::array<void*, Fanout> children;
149 std::array<std::size_t, Fanout - 1> measures;
150 [[no_unique_address]] Bias bias;
151 [[no_unique_address]] ChildOrder order;
153 void*& child(std::size_t i, std::size_t height)
noexcept {
154 return children[order.slot(*
this, i, height)];
156 void* child(std::size_t i, std::size_t height)
const noexcept {
157 return children[order.slot(*
this, i, height)];
160 template <
class, std::
size_t, std::
size_t, LengthLayout>
161 friend class ::pixie::Permutation;
162 static constexpr std::size_t max_height =
163 std::numeric_limits<std::size_t>::digits;
164 static constexpr std::size_t partial_prefix_min_children = 10;
165 static constexpr std::size_t minimum_leaf_size =
166 (
static_cast<std::size_t
>(Block::capacity) + 1) / 2;
170 using block_type = Block;
172 using value_type =
typename Block::value_type;
174 static constexpr std::size_t block_capacity = Block::capacity;
176 static constexpr std::size_t block_storage_bytes =
sizeof(Leaf);
178 static constexpr std::size_t node_storage_bytes =
sizeof(Node);
180 static constexpr std::size_t block_alignment =
alignof(Leaf);
182 static constexpr std::size_t node_alignment =
alignof(Node);
184#ifdef PIXIE_SEQUENCE_TREE_TESTING
192 struct TestCounters : Policy::TestCounters {
193 std::size_t allocations = 0;
194 std::size_t node_visits = 0;
195 std::size_t child_transfers = 0;
196 std::size_t payload_mutations = 0;
197 std::size_t live_allocations = 0;
198 std::size_t peak_allocations = 0;
199 std::size_t live_bytes = 0;
200 std::size_t peak_bytes = 0;
203 inline static thread_local TestCounters test_counters{};
208 static void test_fail_after(std::ptrdiff_t n)
noexcept { failure_ = n; }
211 static void test_reset_counters() noexcept {
212 const auto live = test_counters.live_allocations;
213 const auto bytes = test_counters.live_bytes;
215 test_counters.live_allocations = test_counters.peak_allocations = live;
216 test_counters.live_bytes = test_counters.peak_bytes = bytes;
220 SequenceTree() noexcept = default;
222 SequenceTree(const SequenceTree&) = delete;
224 SequenceTree& operator=(const SequenceTree&) = delete;
226 SequenceTree(SequenceTree&&) noexcept = default;
228 SequenceTree& operator=(SequenceTree&&) noexcept = default;
230 std::
size_t size() const noexcept {
return root_.total; }
233 bool empty() const noexcept {
return !root_.p; }
236 std::size_t height() const noexcept {
return root_.height; }
243 std::size_t regular_prefix_size() const noexcept
244 requires(!IndexBias && std::has_single_bit(Fanout) &&
245 Layout == LengthLayout::cumulative)
250 const auto prefix =
static_cast<const Node*
>(root_.p)->regular_prefix;
255 const auto shift = (height() - 1) * std::countr_zero(Fanout);
256 return prefix * (block_capacity << shift);
265 value_type read_regular_unchecked(std::size_t index)
const noexcept
266 requires(!IndexBias && std::has_single_bit(Fanout) &&
267 Layout == LengthLayout::cumulative &&
268 requires(
const Block& block) {
270 block.read_unchecked(index)
271 }
noexcept -> std::same_as<value_type>;
274 assert(index < regular_prefix_size());
275 const auto block_index = index / block_capacity;
277 for (
auto h = height(); h != 0; --h) {
280 (block_index >> ((h - 1) * std::countr_zero(Fanout))) & (Fanout - 1);
281 p =
static_cast<const Node*
>(p)->child(child, h);
283 return static_cast<const Leaf*
>(p)->block.read_unchecked(index %
292 value_type operator[](std::size_t i)
const {
294 throw std::out_of_range(
"SequenceTree: index");
296 const auto position = locate<IndexBias>(root_, i);
297 const auto& block = std::as_const(position.leaf->block);
298 const auto value = [&] {
299 if constexpr (
requires {
301 block.read_unchecked(position.offset)
302 }
noexcept -> std::same_as<value_type>;
304 return block.read_unchecked(position.offset);
306 return block[position.offset];
309 if constexpr (IndexBias) {
310 return static_cast<value_type
>(value + position.bias.value);
331 template <
class Pred>
332 std::size_t lower_bound(Pred pred)
const {
336 std::size_t base = 0;
337 std::size_t total = root_.total;
340 for (
auto h = root_.height; h != 0; --h) {
341 auto& node = *
static_cast<Node*
>(p);
342 if constexpr (IndexBias) {
343 bias.value += node.bias.value;
346 std::size_t hi = node.count;
348 const auto mid = lo + (hi - lo) / 2;
349 if (pred(edge_value(node.child(mid, h), h - 1, bias,
true))) {
355 if (lo == node.count) {
359 std::size_t child_total;
360 if constexpr (Layout == LengthLayout::cumulative) {
361 prefix = lo == 0 ? 0 : node.measures[lo - 1];
362 const auto end = lo + 1 == node.count ? total : node.measures[lo];
363 child_total = end - prefix;
366 for (std::size_t i = 0; i < lo; ++i) {
367 prefix += length(node, i, total, prefix);
369 child_total = length(node, lo, total, prefix);
373 p = node.child(lo, h);
375 auto& leaf = *
static_cast<Leaf*
>(p);
376 if constexpr (IndexBias) {
377 bias.value += leaf.bias.value;
379 const auto& block = std::as_const(leaf.block);
381 std::size_t hi = block.size();
383 const auto mid = lo + (hi - lo) / 2;
384 const auto raw = block[mid];
386 if constexpr (IndexBias) {
387 return static_cast<value_type
>(raw + bias.value);
414 template <std::ranges::input_range Range>
416 constructible_from<Block, std::ranges::range_rvalue_reference_t<Range>>
417 static SequenceTree from_blocks(Range&& blocks) {
418 auto it = std::ranges::begin(blocks);
419 const auto end = std::ranges::end(blocks);
423 Block pending(std::ranges::iter_move(it));
424 std::size_t total = pending.size();
425 if (total > Block::capacity) {
426 throw std::length_error(
"SequenceTree: construction size");
433 result.root_ = Owner(allocate<Leaf>(), total, 0);
434 static_cast<Leaf*
>(result.root_.p)->block = std::move(pending);
438 return from_blocks_many(std::move(it), end, pending, total);
449 SequenceTree split_off(std::size_t p) {
451 throw std::out_of_range(
"SequenceTree: split position");
458 return std::move(*
this);
461 pool.reserve(3 * root_.height, locate(root_, p).offset != 0);
462 auto pair = split(std::move(root_), p, pool);
463 root_ = std::move(pair.left);
464 result.root_ = std::move(pair.right);
465 reserve_.trim(size(),
sizeof(value_type));
477 void merge(SequenceTree& donor) { merge_impl<false>(donor); }
494 void rotate_left(std::size_t left, std::size_t right, std::size_t distance) {
495 if (left > right || right > size()) {
496 throw std::out_of_range(
"SequenceTree: rotation range");
498 const auto n = right - left;
499 if (n == 0 || (distance %= n) == 0) {
502 if (rotate_local(left, right, distance)) {
505 if constexpr (!IndexBias &&
sizeof(value_type) <=
sizeof(std::uint64_t) &&
506 std::is_trivially_copyable_v<value_type> &&
507 std::is_default_constructible_v<value_type> &&
508 requires(Block& block, std::size_t i, value_type value) {
509 { block.set_at(i, value) }
noexcept;
514 constexpr std::size_t limit = 128;
518 std::size_t offset, count;
520 std::array<value_type, limit> values;
521 std::array<Segment, limit> segments;
522 std::size_t count = 0, done = 0;
524 const auto position = locate(root_, left + done);
525 auto& block = position.leaf->block;
526 const auto take = std::min(n - done, block.size() - position.offset);
527 segments[count++] = {&block, position.offset, take};
528 for (std::size_t i = 0; i < take; ++i) {
529 values[done + i] = std::as_const(block)[position.offset + i];
533 auto source = distance;
534 for (std::size_t j = 0; j < count; ++j) {
535 const auto& segment = segments[j];
536 for (std::size_t i = 0; i < segment.count; ++i) {
537 segment.block->set_at(segment.offset + i, values[source]);
547 Pool pool(reserve_.for_rotation(size(),
sizeof(value_type)));
548 if (left == 0 && right == size()) {
550 pool.reserve(5 * height() + 4, locate(root_, distance).offset != 0);
551 auto cut = split(std::move(root_), distance, pool);
552 root_ = concatenate(std::move(cut.right), std::move(cut.left), pool);
565 const auto leaf_spares =
566 std::size_t(left != 0 && locate(root_, left).offset != 0) +
567 (locate(root_, left + distance).offset != 0) +
568 (right != size() && locate(root_, right).offset != 0);
569 pool.reserve(15 * height() + 24, leaf_spares);
572 auto middle = split(std::move(root_), left + distance, pool);
573 auto a = split(std::move(middle.left), left, pool);
574 auto b = split(std::move(middle.right), right - left - distance, pool);
575 auto first = concatenate(std::move(a.left), std::move(b.left), pool);
576 auto second = concatenate(std::move(a.right), std::move(b.right), pool);
577 root_ = concatenate(std::move(first), std::move(second), pool);
595 void insert_at(std::size_t position, value_type value)
596 requires InsertableSequenceBlock<Block>
598 if (position > size()) {
599 throw std::out_of_range(
"SequenceTree: insert position");
602 root_ = Owner(allocate<Leaf>(), 1, 0);
603 const value_type data[] = {value};
604 static_cast<Leaf*
>(root_.p)->block =
605 Block(std::span<const value_type>(data, 1));
613 std::array<PathEntry, max_height> path;
614 std::size_t depth = 0;
615 std::uint64_t total_bias = 0;
617 auto total = root_.total;
618 auto local = position;
619 for (
auto h = root_.height; h != 0; --h) {
621 auto& node = *
static_cast<Node*
>(p);
622 if constexpr (IndexBias) {
623 total_bias += node.bias.value;
625 if constexpr (Layout == LengthLayout::cumulative) {
626 const auto i = node_select(
627 {node.measures.data(), std::size_t(node.count) - 1}, local);
628 const auto prefix = i == 0 ? 0 : node.measures[i - 1];
629 const auto end = i + 1 == node.count ? total : node.measures[i];
630 path[depth++] = {&node, i, total};
632 total = end - prefix;
633 p = node.child(i, h);
635 std::size_t prefix = 0;
636 for (std::size_t i = 0; i < node.count; ++i) {
637 const auto n = length(node, i, total, prefix);
638 if (local - prefix < n ||
639 (local - prefix == n && i + 1 == node.count)) {
640 path[depth++] = {&node, i, total};
643 p = node.child(i, h);
650 auto& leaf = *
static_cast<Leaf*
>(p);
651 if constexpr (IndexBias) {
652 total_bias += leaf.bias.value;
654 if (leaf.block.size() < block_capacity) {
655 if constexpr (IndexBias) {
656 assert(total_bias <= value);
657 leaf.block.insert_at(local,
658 static_cast<value_type
>(value - total_bias));
660 leaf.block.insert_at(local, value);
663 for (std::size_t i = depth; i > 0; --i) {
664 auto& entry = path[i - 1];
665 auto& node = *entry.node;
666 node.regular_prefix =
667 std::min(std::size_t(node.regular_prefix), entry.child);
668 if constexpr (Layout == LengthLayout::cumulative) {
669 for (std::size_t j = entry.child; j + 1 < node.count; ++j) {
672 }
else if (entry.child + 1 < node.count) {
673 ++node.measures[entry.child];
679 if constexpr (!IndexBias) {
684 std::size_t spares = 1;
685 for (std::size_t i = depth; i != 0; --i) {
686 if (path[i - 1].node->count != Fanout) {
691 pool.reserve(spares, 1);
692 auto sibling = pool.leaf();
693 const auto left_count = (block_capacity + 1) / 2;
694 const bool insert_left = local < left_count;
695 const auto old_left = left_count - std::size_t(insert_left);
696 auto& right =
static_cast<Leaf*
>(sibling.p)->block;
697 leaf.block.redistribute(right, old_left);
699 leaf.block.insert_at(local, value);
701 right.insert_at(local - old_left, value);
704 sibling.total = right.size();
706 Pair carry{Owner(&leaf, leaf.block.size(), 0), std::move(sibling)};
707 for (std::size_t level = depth; level != 0; --level) {
708 const auto& entry = path[level - 1];
709 auto* node = entry.node;
711 std::size_t count = 0, prefix = 0;
712 for (std::size_t i = 0; i < node->count; ++i) {
713 const auto n = length(*node, i, entry.total, prefix);
714 if (i == entry.child) {
715 entries[count++] = std::move(carry.left);
717 entries[count++] = std::move(carry.right);
721 Owner(node->child(i, depth - level + 1), n, depth - level);
726 carry = repack(entries, count, pool);
730 entries[0] = std::move(carry.left);
731 entries[1] = std::move(carry.right);
732 root_ = pack(pool.node(), entries, 0, 2);
734 root_ = std::move(carry.left);
740 const value_type data[] = {value};
741 if (position == size()) {
742 pool.reserve(2 * height() + 4, 1);
743 auto singleton = pool.leaf();
744 static_cast<Leaf*
>(singleton.p)->block =
745 Block(std::span<const value_type>(data, 1));
747 singleton.height = 0;
748 root_ = concatenate(std::move(root_), std::move(singleton), pool);
757 pool.reserve(7 * height() + 12, 2);
758 auto pair = split(std::move(root_), position, pool);
759 auto singleton = pool.leaf();
760 static_cast<Leaf*
>(singleton.p)->block =
761 Block(std::span<const value_type>(data, 1));
763 singleton.height = 0;
764 root_ = concatenate(std::move(pair.left), std::move(singleton), pool);
765 root_ = concatenate(std::move(root_), std::move(pair.right), pool);
771 std::size_t blocks = 0;
772 std::size_t nodes = 0;
773 std::size_t block_bytes = 0;
774 std::size_t node_bytes = 0;
775 std::size_t reserved_nodes = 0;
776 std::size_t reserved_bytes = 0;
777 std::size_t total_bytes =
sizeof(SequenceTree);
787 MemoryUsage memory_usage() const noexcept {
789 auto walk = [&](
auto&& self,
void* p, std::size_t h) ->
void {
797 const auto& node = *
static_cast<Node*
>(p);
798 for (std::size_t i = 0; i < node.count; ++i) {
799 self(self, node.child(i, h), h - 1);
803 walk(walk, root_.p, height());
804 result.block_bytes = saturated_multiply(result.blocks,
sizeof(Leaf));
805 result.node_bytes = saturated_multiply(result.nodes,
sizeof(Node));
806 result.reserved_nodes = reserve_.node_count();
807 result.reserved_bytes =
808 saturated_multiply(result.reserved_nodes,
sizeof(Node));
809 result.total_bytes = saturated_add(
811 saturated_add(result.block_bytes,
812 saturated_add(result.node_bytes, result.reserved_bytes)));
817 std::size_t memory_usage_bytes() const noexcept {
818 return memory_usage().total_bytes;
822 std::size_t block_count() const noexcept {
return memory_usage().blocks; }
827 std::size_t node_count() const noexcept {
return memory_usage().nodes; }
830 std::size_t block_allocation_bytes() const noexcept {
831 return memory_usage().block_bytes;
835 std::size_t internal_memory_bytes() const noexcept {
836 const auto usage = memory_usage();
837 return saturated_add(usage.node_bytes, usage.reserved_bytes);
844 std::size_t payload_capacity_bytes() const noexcept
845 requires requires(const Block& b) {
846 { b.payload_capacity_bytes() }
noexcept -> std::same_as<std::size_t>;
849 return saturated_multiply(block_count(), Block{}.payload_capacity_bytes());
856 std::size_t metadata_bytes() const noexcept
857 requires requires(const Block& b) {
858 { b.payload_capacity_bytes() }
noexcept -> std::same_as<std::size_t>;
861 return memory_usage_bytes() - payload_capacity_bytes();
870 template <
class Callback>
871 void for_each_block(Callback&& callback)
const
874 auto walk = [&](
auto&& self,
void* p, std::size_t h) ->
void {
879 callback(std::as_const(
static_cast<Leaf*
>(p)->block));
881 const auto& node = *
static_cast<Node*
>(p);
882 for (std::size_t i = 0; i < node.count; ++i) {
883 self(self, node.child(i, h), h - 1);
887 walk(walk, root_.p, height());
890#ifdef PIXIE_SEQUENCE_TREE_TESTING
900 std::vector<std::vector<std::size_t>> test_child_boundaries()
const {
901 std::vector<std::vector<std::size_t>> result;
902 auto walk = [&](
auto&& self,
const void* p, std::size_t total,
903 std::size_t h, std::size_t start) ->
void {
907 const auto& node = *
static_cast<const Node*
>(p);
908 std::vector<std::size_t> boundaries{start};
909 std::size_t prefix = 0;
910 for (std::size_t i = 0; i < node.count; ++i) {
911 prefix += length(node, i, total, prefix);
912 boundaries.push_back(start + prefix);
914 result.push_back(boundaries);
915 for (std::size_t i = 0; i < node.count; ++i) {
916 self(self, node.child(i, h), boundaries[i + 1] - boundaries[i], h - 1,
920 walk(walk, root_.p, root_.total, root_.height, 0);
930 bool test_validate()
const {
932 return size() == 0 && height() == 0;
934 std::unordered_set<const void*> seen;
935 auto walk = [&](
auto&& self,
void* p, std::size_t total, std::size_t h,
936 bool first,
bool last,
bool root,
937 std::uint64_t inherited) ->
bool {
938 if (!p || !total || !seen.insert(p).second) {
942 const auto& b =
static_cast<Leaf*
>(p)->block;
943 if constexpr (IndexBias) {
944 const auto pending =
static_cast<Leaf*
>(p)->bias.value;
945 const auto maximum = std::numeric_limits<value_type>::max();
946 if (inherited > maximum || pending > maximum - inherited) {
949 inherited += pending;
950 for (std::size_t i = 0; i < b.size(); ++i) {
951 if (b[i] > maximum - inherited) {
956 return reinterpret_cast<std::uintptr_t
>(p) %
alignof(Leaf) == 0 &&
957 b.size() == total && total <= Block::capacity &&
958 (first || last || total >= minimum_leaf_size);
960 const auto& node = *
static_cast<Node*
>(p);
961 if constexpr (IndexBias) {
962 const auto maximum = std::numeric_limits<value_type>::max();
963 if (inherited > maximum || node.bias.value > maximum - inherited) {
966 inherited += node.bias.value;
968 if (
reinterpret_cast<std::uintptr_t
>(p) %
alignof(Node) != 0 ||
969 node.count < (root ? 2 : Fanout / 2) || node.count > Fanout) {
972 if (node.regular_prefix > node.count) {
975 if (!node.order.valid(node, h)) {
978 if (node.regular_prefix) {
979 if constexpr (!std::has_single_bit(Fanout) ||
980 Layout != LengthLayout::cumulative) {
983 const auto shift = (h - 1) * std::countr_zero(Fanout);
984 if (shift >= std::numeric_limits<std::size_t>::digits ||
986 (std::numeric_limits<std::size_t>::max() >> shift)) {
989 const auto capacity = block_capacity << shift;
990 for (std::size_t i = 0; i < node.regular_prefix; ++i) {
991 const auto end = i + 1 == node.count ? total : node.measures[i];
992 if (end / (i + 1) != capacity || end % (i + 1) != 0) {
998 std::size_t prefix = 0;
999 for (std::size_t i = 0; i < node.count; ++i) {
1000 if (prefix >= total) {
1003 const auto n = length(node, i, total, prefix);
1004 if (n > total - prefix ||
1005 !self(self, node.child(i, h), n, h - 1, first && i == 0,
1006 last && i + 1 == node.count,
false, inherited)) {
1011 return prefix == total;
1013 return walk(walk, root_.p, size(), height(),
true,
true,
true, 0);
1017 std::vector<const void*> test_leaf_identities()
const {
1018 std::vector<const void*> result;
1019 auto walk = [&](
auto&& self,
const void* p, std::size_t h) ->
void {
1024 result.push_back(&
static_cast<const Leaf*
>(p)->block);
1026 const auto& node = *
static_cast<const Node*
>(p);
1027 for (std::size_t i = 0; i < node.count; ++i) {
1028 self(self, node.child(i, h), h - 1);
1032 walk(walk, root_.p, height());
1044 std::vector<std::pair<const void*, std::uint64_t>> test_bias_snapshot() const
1047 std::vector<std::pair<const void*, std::uint64_t>> result;
1048 auto walk = [&](
auto&& self,
const void* p, std::size_t h) ->
void {
1053 result.emplace_back(p,
static_cast<const Leaf*
>(p)->bias.value);
1055 const auto& node = *
static_cast<const Node*
>(p);
1056 result.emplace_back(p, node.bias.value);
1057 for (std::size_t i = 0; i < node.count; ++i) {
1058 self(self, node.child(i, h), h - 1);
1062 walk(walk, root_.p, height());
1070 void test_add_bias(std::uint64_t bias)
noexcept
1074 add_bias(root_.p, height(), bias);
1084 std::vector<const void*> test_internal_node_identities()
const {
1085 std::vector<const void*> result;
1086 auto walk = [&](
auto&& self,
const void* p, std::size_t h) ->
void {
1090 result.push_back(p);
1091 const auto& node = *
static_cast<const Node*
>(p);
1092 for (std::size_t i = 0; i < node.count; ++i) {
1093 self(self, node.child(i, h), h - 1);
1096 walk(walk, root_.p, height());
1102 static void allocation() {
1103#ifdef PIXIE_SEQUENCE_TREE_TESTING
1104 if (failure_ == 0) {
1105 throw std::bad_alloc();
1113 static T* allocate() {
1116#ifdef PIXIE_SEQUENCE_TREE_TESTING
1117 ++test_counters.allocations;
1118 ++test_counters.live_allocations;
1119 test_counters.peak_allocations = std::max(test_counters.peak_allocations,
1120 test_counters.live_allocations);
1121 test_counters.live_bytes +=
sizeof(T);
1122 test_counters.peak_bytes =
1123 std::max(test_counters.peak_bytes, test_counters.live_bytes);
1128 static void dispose(T* p)
noexcept {
1130#ifdef PIXIE_SEQUENCE_TREE_TESTING
1131 --test_counters.live_allocations;
1132 test_counters.live_bytes -=
sizeof(T);
1135 static void visit() noexcept {
1136#ifdef PIXIE_SEQUENCE_TREE_TESTING
1137 ++test_counters.node_visits;
1140 static void transfer() noexcept {
1141#ifdef PIXIE_SEQUENCE_TREE_TESTING
1142 ++test_counters.child_transfers;
1145 static void mutate() noexcept {
1146#ifdef PIXIE_SEQUENCE_TREE_TESTING
1147 ++test_counters.payload_mutations;
1150 static void destroy(
void* p, std::size_t height)
noexcept {
1155 dispose(
static_cast<Leaf*
>(p));
1157 auto* node =
static_cast<Node*
>(p);
1158 for (std::size_t i = 0; i < node->count; ++i) {
1159 destroy(node->child(i, height), height - 1);
1166 std::size_t total = 0;
1167 std::size_t height = 0;
1168 Owner() noexcept = default;
1169 Owner(
void* pointer, std::
size_t n, std::
size_t h) noexcept
1170 : p(pointer), total(n), height(h) {}
1171 Owner(
const Owner&) =
delete;
1172 Owner& operator=(
const Owner&) =
delete;
1173 Owner(Owner&& other) noexcept
1174 : p(std::exchange(other.p,
nullptr)),
1175 total(std::exchange(other.total, 0)),
1176 height(std::exchange(other.height, 0)) {}
1177 Owner& operator=(Owner&& other)
noexcept {
1178 if (
this != &other) {
1180 p = std::exchange(other.p,
nullptr);
1181 total = std::exchange(other.total, 0);
1182 height = std::exchange(other.height, 0);
1186 ~Owner() { destroy(p, height); }
1187 void* release() noexcept {
return std::exchange(p,
nullptr); }
1190 struct PoolAllocator {
1191 static Node* node() {
return allocate<Node>(); }
1192 static Owner leaf() {
return Owner(allocate<Leaf>(), 0, 0); }
1193 static void dispose(Node* node)
noexcept { SequenceTree::dispose(node); }
1194 static void reset(Node* node)
noexcept {
1196 node->regular_prefix = 0;
1197 if constexpr (IndexBias) {
1198 node->bias.value = 0;
1202 using Reserve =
typename Policy::template Reserve<Node, PoolAllocator>;
1203 using Pool =
typename Policy::template Pool<Node, Owner, PoolAllocator>;
1204 void clear_reserve() noexcept {
1213 std::array<Slot, 2 * Fanout> slots_;
1214 std::size_t count_ = 0;
1217 Entries() noexcept = default;
1218 Entries(const Entries&) = delete;
1219 Entries& operator=(const Entries&) = delete;
1221 while (count_ != 0) {
1222 std::destroy_at(&slots_[--count_].owner);
1225 Owner& operator[](std::size_t index)
noexcept {
1226 assert(index < slots_.size());
1227 while (count_ <= index) {
1228 std::construct_at(&slots_[count_++].owner);
1230 return slots_[index].owner;
1232 void append(
void* pointer, std::size_t total, std::size_t height)
noexcept {
1233 assert(count_ < slots_.size());
1234 std::construct_at(&slots_[count_++].owner, pointer, total, height);
1241 static void add_bias(
void* p, std::size_t height, std::uint64_t bias)
noexcept
1244 auto& pending = height == 0 ?
static_cast<Leaf*
>(p)->bias.value
1245 : static_cast<Node*>(p)->bias.value;
1246 assert(bias <= std::numeric_limits<value_type>::max() - pending);
1249 static void normalize(Leaf& leaf)
noexcept {
1250 if constexpr (IndexBias) {
1251 if (leaf.bias.value != 0) {
1252 leaf.block.add_bias(leaf.bias.value);
1253 leaf.bias.value = 0;
1258 static void redistribute(Leaf& a, Leaf& b, std::size_t left)
noexcept {
1261 a.block.redistribute(b.block, left);
1265 static std::size_t length(
const Node& node,
1268 std::size_t prefix)
noexcept {
1269 if (i + 1 == node.count) {
1270 return total - prefix;
1272 if constexpr (Layout == LengthLayout::cumulative) {
1273 return node.measures[i] - prefix;
1275 return node.measures[i];
1278 static std::size_t unpack(Owner tree, Entries& entries, Pool& pool)
noexcept {
1280 auto* node =
static_cast<Node*
>(tree.p);
1281 const auto count = node->count;
1282 std::size_t prefix = 0;
1283 for (std::size_t i = 0; i < count; ++i) {
1284 const auto n = length(*node, i, tree.total, prefix);
1285 if constexpr (IndexBias) {
1286 add_bias(node->child(i, tree.height), tree.height - 1,
1289 entries.append(node->child(i, tree.height), n, tree.height - 1);
1297 static Owner pack(Node* node,
1300 std::size_t count)
noexcept {
1301 assert(count >= 2 && count <= Fanout);
1302 node->count = count;
1303 const auto height = entries[begin].height + 1;
1304 node->order.reset(*node);
1305 std::size_t child_capacity = 0;
1306 if constexpr (std::has_single_bit(Fanout) &&
1307 Layout == LengthLayout::cumulative) {
1308 const auto shift = (height - 1) * std::countr_zero(Fanout);
1309 if (shift < std::numeric_limits<std::size_t>::digits &&
1311 (std::numeric_limits<std::size_t>::max() >> shift)) {
1312 child_capacity = block_capacity << shift;
1315 node->regular_prefix = 0;
1316 std::size_t total = 0;
1317 for (std::size_t i = 0; i < count; ++i) {
1318 auto& entry = entries[begin + i];
1319 assert(entry.p && entry.height + 1 == height);
1320 total += entry.total;
1321 node->child(i, height) = entry.release();
1322 if (child_capacity != 0 && i == node->regular_prefix &&
1323 entry.total == child_capacity) {
1324 ++node->regular_prefix;
1326 if (i + 1 < count) {
1328 Layout == LengthLayout::cumulative ? total : entry.total;
1335 if (std::size_t(node->regular_prefix) + 1 < count &&
1336 (count < partial_prefix_min_children ||
1337 std::size_t(node->regular_prefix) < count - count / 4)) {
1338 node->regular_prefix = 0;
1340 return Owner(node, total, height);
1342 static Owner group(Entries& entries,
1345 Pool& pool)
noexcept {
1350 return std::move(entries[begin]);
1352 return pack(pool.node(), entries, begin, count);
1354 static Pair repack(Entries& entries, std::size_t count, Pool& pool)
noexcept {
1355 if (count <= Fanout) {
1356 return {group(entries, 0, count, pool), {}};
1358 const auto half = count / 2;
1359 return {group(entries, 0, half, pool),
1360 group(entries, half, count - half, pool)};
1369 static Pair join_level(Owner a, Owner b, Pool& pool)
noexcept {
1370 if (a.height == b.height) {
1371 if (a.height == 0) {
1372 return {std::move(a), std::move(b)};
1374 const auto left_count =
static_cast<const Node*
>(a.p)->count;
1375 const auto right_count =
static_cast<const Node*
>(b.p)->count;
1376 if (left_count + right_count > Fanout && left_count >= Fanout / 2 &&
1377 right_count >= Fanout / 2) {
1382 return {std::move(a), std::move(b)};
1385 const auto n = unpack(std::move(a), entries, pool);
1386 const auto m = unpack(std::move(b), entries, pool);
1387 return repack(entries, n + m, pool);
1390 if (a.height > b.height) {
1391 const auto count = unpack(std::move(a), entries, pool);
1392 auto seam = join_level(std::move(entries[count - 1]), std::move(b), pool);
1393 entries[count - 1] = std::move(seam.left);
1394 const bool extra = seam.right.p !=
nullptr;
1395 entries[count] = std::move(seam.right);
1396 return repack(entries, count + extra, pool);
1398 const auto count = unpack(std::move(b), entries, pool);
1399 auto seam = join_level(std::move(a), std::move(entries[0]), pool);
1400 const bool extra = seam.right.p !=
nullptr;
1402 for (std::size_t i = count; i > 1; --i) {
1403 entries[i] = std::move(entries[i - 1]);
1405 entries[1] = std::move(seam.right);
1407 entries[0] = std::move(seam.left);
1408 return repack(entries, count + extra, pool);
1410 static Owner join(Owner a, Owner b, Pool& pool)
noexcept {
1417 auto pair = join_level(std::move(a), std::move(b), pool);
1418 if (!pair.right.p) {
1419 return std::move(pair.left);
1422 entries[0] = std::move(pair.left);
1423 entries[1] = std::move(pair.right);
1424 return group(entries, 0, 2, pool);
1451 static Pair split(Owner tree, std::size_t p, Pool& pool)
noexcept {
1453 return {{}, std::move(tree)};
1455 if (p == tree.total) {
1456 return {std::move(tree), {}};
1458 if (tree.height == 0) {
1459 auto right = pool.leaf();
1460 redistribute(*
static_cast<Leaf*
>(tree.p), *
static_cast<Leaf*
>(right.p),
1462 right.total = tree.total - p;
1464 return {std::move(tree), std::move(right)};
1467 const auto count = unpack(std::move(tree), entries, pool);
1469 while (p > entries[i].total) {
1470 p -= entries[i++].total;
1472 const auto child_height = entries[i].height;
1473 auto cut = split(std::move(entries[i]), p, pool);
1474 const auto fits = [child_height](
const Owner& boundary)
noexcept {
1475 return boundary.p && boundary.height == child_height &&
1476 (child_height == 0 ||
1477 static_cast<const Node*
>(boundary.p)->count >= Fanout / 2);
1480 if (fits(cut.left)) {
1484 entries[i] = std::move(cut.left);
1485 left = group(entries, 0, i + 1, pool);
1487 left = join(group(entries, 0, i, pool), std::move(cut.left), pool);
1490 if (fits(cut.right)) {
1491 entries[i] = std::move(cut.right);
1492 right = group(entries, i, count - i, pool);
1494 right = join(std::move(cut.right),
1495 group(entries, i + 1, count - i - 1, pool), pool);
1497 return {std::move(left), std::move(right)};
1503 [[no_unique_address]] Bias bias{};
1520 bool rotate_local(std::size_t left,
1522 std::size_t distance)
noexcept {
1523 const bool includes_first = left == 0;
1524 const bool includes_last = right == size();
1526 auto total = root_.total;
1527 for (
auto h = root_.height; h != 0; --h) {
1529 auto& node = *
static_cast<Node*
>(p);
1530 std::array<std::size_t, Fanout> lengths;
1531 std::size_t prefix = 0;
1532 std::size_t begin = Fanout, middle = Fanout, end = Fanout + 1;
1533 bool descend =
false;
1534 for (std::size_t i = 0; i < node.count; ++i) {
1535 const auto n = length(node, i, total, prefix);
1536 if (left >= prefix && right <= prefix + n) {
1540 p = node.child(i, h);
1545 if (prefix == left) {
1548 if (prefix == left + distance) {
1552 if (prefix == right) {
1559 if (begin == Fanout || middle == Fanout || end == Fanout + 1) {
1564 const auto underfull = [h](
void* child,
bool back)
noexcept {
1565 for (
auto level = h - 1; level != 0; --level) {
1567 const auto& edge = *
static_cast<Node*
>(child);
1568 child = edge.child(back ? edge.count - 1 : 0, level);
1570 return static_cast<Leaf*
>(child)->block.size() < minimum_leaf_size;
1572 if ((includes_first && underfull(node.child(begin, h),
false)) ||
1573 (includes_last && underfull(node.child(end - 1, h),
true))) {
1577 node.order.template rotate<SequenceTree>(node, begin, middle, end, h);
1578 for (std::size_t i = 0; i < writes; ++i) {
1581 const bool equal_lengths =
1582 end <= node.regular_prefix ||
1583 std::all_of(lengths.begin() + begin + 1, lengths.begin() + end,
1584 [&](
auto n) { return n == lengths[begin]; });
1585 if (!equal_lengths) {
1586 std::rotate(lengths.begin() + begin, lengths.begin() + middle,
1587 lengths.begin() + end);
1589 node.regular_prefix = 0;
1590 for (std::size_t i = 0; i + 1 < node.count; ++i) {
1591 prefix += lengths[i];
1593 Layout == LengthLayout::cumulative ? prefix : lengths[i];
1598 static_cast<Leaf*
>(p)->block.rotate_left(left, right, distance);
1602 template <
bool Accumulate = false>
1603 static Location locate(
const Owner& root, std::size_t index)
noexcept {
1605 auto total = root.total;
1606 [[maybe_unused]] Bias bias{};
1607 for (
auto h = root.height; h != 0; --h) {
1609 const auto& node = *
static_cast<Node*
>(p);
1610 if constexpr (Accumulate && IndexBias) {
1611 bias.value += node.bias.value;
1613 if constexpr (Layout == LengthLayout::cumulative) {
1615 if constexpr (std::has_single_bit(Fanout)) {
1616 if (node.regular_prefix) {
1617 const auto shift = (h - 1) * std::countr_zero(Fanout);
1618 const auto block_index = index / block_capacity;
1619 i = block_index >> shift;
1620 if constexpr (!IndexBias) {
1621 if (i < node.regular_prefix) {
1626 p = node.child(i, h);
1627 for (
auto depth = h - 1; depth != 0; --depth) {
1631 ((depth - 1) * std::countr_zero(Fanout))) &
1633 p =
static_cast<Node*
>(p)->child(child, depth);
1635 return {
static_cast<Leaf*
>(p), index % block_capacity, bias};
1638 if (Fanout < partial_prefix_min_children ||
1639 i < node.regular_prefix ||
1640 node.regular_prefix + 1 == node.count) {
1641 const auto capacity = block_capacity << shift;
1642 const auto prefix = i * capacity;
1644 total = i + 1 == node.count ? total - prefix : capacity;
1645 p = node.child(i, h);
1652 std::size_t first = 0;
1653 if constexpr (Fanout >= 32) {
1656 const auto shift = (h - 1) * std::countr_zero(Fanout);
1657 if (shift < std::numeric_limits<std::size_t>::digits) {
1658 first = (index / block_capacity) >> shift;
1661 i = first + node_select({node.measures.data() + first,
1662 std::size_t(node.count) - 1 - first},
1665 i = node_select({node.measures.data(), std::size_t(node.count) - 1},
1668 const auto prefix = i == 0 ? 0 : node.measures[i - 1];
1669 const auto end = i + 1 == node.count ? total : node.measures[i];
1671 total = end - prefix;
1672 p = node.child(i, h);
1674 std::size_t prefix = 0;
1675 for (std::size_t i = 0; i < node.count; ++i) {
1676 const auto n = length(node, i, total, prefix);
1677 if (index - prefix < n) {
1680 p = node.child(i, h);
1687 if constexpr (Accumulate && IndexBias) {
1688 bias.value +=
static_cast<Leaf*
>(p)->bias.value;
1690 return {
static_cast<Leaf*
>(p), index, bias};
1704 static value_type edge_value(
void* p,
1707 bool back)
noexcept {
1708 for (
auto h = height; h != 0; --h) {
1709 auto& node = *
static_cast<Node*
>(p);
1710 if constexpr (IndexBias) {
1711 bias.value += node.bias.value;
1713 p = node.child(back ? node.count - 1 : 0, h);
1715 auto& leaf = *
static_cast<Leaf*
>(p);
1716 if constexpr (IndexBias) {
1717 bias.value += leaf.bias.value;
1719 const auto& block = std::as_const(leaf.block);
1720 const auto offset = back ? block.size() - 1 : 0;
1721 const auto raw = block[offset];
1722 if constexpr (IndexBias) {
1723 return static_cast<value_type
>(raw + bias.value);
1728 static Owner pop_edge(Owner& tree,
bool back, Pool& pool)
noexcept {
1736 const auto n = locate(tree, back ? tree.total - 1 : 0).leaf->block.size();
1737 const auto position = back ? tree.total - n : n;
1738 auto cut = split(std::move(tree), position, pool);
1740 tree = std::move(cut.left);
1741 return std::move(cut.right);
1743 tree = std::move(cut.right);
1744 return std::move(cut.left);
1760 static Owner concatenate(Owner a, Owner b, Pool& pool)
noexcept {
1762 return join(std::move(a), std::move(b), pool);
1764 if (locate(a, a.total - 1).leaf->block.size() >= minimum_leaf_size &&
1765 locate(b, 0).leaf->block.size() >= minimum_leaf_size) {
1766 return join(std::move(a), std::move(b), pool);
1769 seam[1] = pop_edge(a,
true, pool);
1770 seam[2] = pop_edge(b,
false, pool);
1771 if (a.p && b.p && seam[1].total + seam[2].total < minimum_leaf_size) {
1772 seam[0] = pop_edge(a,
true, pool);
1774 std::size_t count = 0;
1775 for (std::size_t i = 0; i < 3; ++i) {
1778 seam[count] = std::move(seam[i]);
1783 for (std::size_t i = 0; i + 1 < count;) {
1785 auto& y = seam[i + 1];
1786 const auto total = x.total + y.total;
1787 const auto first = std::min(block_capacity, total);
1788 redistribute(*
static_cast<Leaf*
>(x.p), *
static_cast<Leaf*
>(y.p), first);
1790 y.total = total - first;
1792 for (std::size_t j = i + 1; j + 1 < count; ++j) {
1793 seam[j] = std::move(seam[j + 1]);
1800 if (count >= 2 && seam[count - 1].total < minimum_leaf_size) {
1801 auto& x = seam[count - 2];
1802 auto& y = seam[count - 1];
1803 const auto total = x.total + y.total;
1804 redistribute(*
static_cast<Leaf*
>(x.p), *
static_cast<Leaf*
>(y.p),
1806 x.total = total / 2;
1807 y.total = total - x.total;
1809 a = join(std::move(a), group(seam, 0, count, pool), pool);
1810 return join(std::move(a), std::move(b), pool);
1814 template <
class Iterator,
class Sentinel>
1815 static SequenceTree from_blocks_many(Iterator it,
1818 std::size_t total) {
1819 std::array<Entries, max_height> levels;
1820 std::array<std::size_t, max_height> counts{};
1821 auto emit = [&](Block&& block) {
1822 Owner carry(allocate<Leaf>(), block.size(), 0);
1823 static_cast<Leaf*
>(carry.p)->block = std::move(block);
1824 std::size_t level = 0;
1826 levels[level][counts[level]++] = std::move(carry);
1827 if (counts[level] != Fanout) {
1830 auto* node = allocate<Node>();
1831 carry = pack(node, levels[level], 0, Fanout);
1832 counts[level++] = 0;
1833 assert(level < max_height);
1836 if (pending.size() == Block::capacity) {
1837 emit(std::move(pending));
1840 for (; it != end; ++it) {
1841 Block incoming(std::ranges::iter_move(it));
1842 const auto n = incoming.size();
1843 if (n > Block::capacity ||
1844 n > std::numeric_limits<std::size_t>::max() - total) {
1845 throw std::length_error(
"SequenceTree: construction size");
1851 const auto combined = pending.size() + n;
1852 pending.redistribute(incoming, std::min(block_capacity, combined));
1853 if (pending.size() == Block::capacity) {
1854 emit(std::move(pending));
1855 pending = std::move(incoming);
1858 if (pending.size()) {
1859 emit(std::move(pending));
1861 SequenceTree result;
1862 if (total <= Block::capacity) {
1863 result.root_ = std::move(levels[0][0]);
1870 std::size_t used_height = 0;
1871 for (std::size_t i = 0; i < max_height; ++i) {
1878 pool.reserve(3 * (used_height + 1), 0);
1879 for (std::size_t i = 0; i <= used_height; ++i) {
1880 auto prefix = group(levels[i], 0, counts[i], pool);
1881 result.root_ = join(std::move(prefix), std::move(result.root_), pool);
1889 void merge_rebased(SequenceTree& donor)
1892 merge_impl<true>(donor);
1894 template <
bool Rebase>
1895 void merge_impl(SequenceTree& donor) {
1896 if (
this == &donor || donor.empty()) {
1899 if (donor.size() > std::numeric_limits<std::size_t>::max() - size()) {
1900 throw std::length_error(
"SequenceTree: concatenation size");
1903 *
this = std::move(donor);
1906 if constexpr (!IndexBias) {
1907 if (donor.height() == 0 && height() != 0) {
1911 for (
auto depth = height(); depth != 0; --depth) {
1913 const auto& node = *
static_cast<Node*
>(p);
1914 p = node.child(node.count - 1, depth);
1916 auto& last = *
static_cast<Leaf*
>(p);
1917 const auto incoming = donor.size();
1918 if (incoming <= block_capacity - last.block.size()) {
1919 redistribute(last, *
static_cast<Leaf*
>(donor.root_.p),
1920 last.block.size() + incoming);
1921 root_.total += incoming;
1923 donor.clear_reserve();
1929 auto tag = [&]()
noexcept {
1930 donor.clear_reserve();
1931 if constexpr (Rebase) {
1932 add_bias(donor.root_.p, donor.height(), size());
1935 if (height() == 0 && donor.height() == 0) {
1939 const auto total = size() + donor.size();
1940 if (total <= block_capacity) {
1942 redistribute(*
static_cast<Leaf*
>(root_.p),
1943 *
static_cast<Leaf*
>(donor.root_.p), total);
1944 root_.total = total;
1949 root_ = concatenate(std::move(root_), std::move(donor.root_), pool);
1953 const auto h = std::max(height(), donor.height());
1954 if (std::as_const(locate(root_, size() - 1).leaf->block).size() >=
1955 minimum_leaf_size &&
1956 std::as_const(locate(donor.root_, 0).leaf->block).size() >=
1957 minimum_leaf_size) {
1960 const auto gap = h - std::min(height(), donor.height());
1961 pool.reserve(gap + 1, 0);
1963 root_ = join(std::move(root_), std::move(donor.root_), pool);
1966 pool.reserve(2 * h + 4, 0);
1968 root_ = concatenate(std::move(root_), std::move(donor.root_), pool);
1970 static std::size_t saturated_add(std::size_t a, std::size_t b)
noexcept {
1971 return b > std::numeric_limits<std::size_t>::max() - a
1972 ? std::numeric_limits<std::size_t>::max()
1975 static std::size_t saturated_multiply(std::size_t a, std::size_t b)
noexcept {
1976 return b != 0 && a > std::numeric_limits<std::size_t>::max() / b
1977 ? std::numeric_limits<std::size_t>::max()
1980#ifdef PIXIE_SEQUENCE_TREE_TESTING
1981 inline static thread_local std::ptrdiff_t failure_ = -1;
1984 [[no_unique_address]] Reserve reserve_;
1987}
Exclusively owned permutation of the integers in [0, size()).
Definition permutation.h:45
Shared compile-time sequence tuning options.
LengthLayout
Representation of tree child lengths.
Definition sequence_options.h:14