// // immer: immutable data structures for C++ // Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente // // This software is distributed under the Boost Software License, Version 1.0. // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // #pragma once #include #include #include #include #include #include #include #include namespace immer { namespace detail { namespace rbts { template struct rrbtree_iterator; template struct rrbtree { using node_t = node; using edit_t = typename node_t::edit_t; using owner_t = typename MemoryPolicy::transience_t::owner; size_t size; shift_t shift; node_t* root; node_t* tail; static const rrbtree& empty() { static const rrbtree empty_{ 0, BL, node_t::make_inner_n(0u), node_t::make_leaf_n(0u)}; return empty_; } template static auto from_initializer_list(std::initializer_list values) { auto e = owner_t{}; auto result = rrbtree{empty()}; for (auto&& v : values) result.push_back_mut(e, v); return result; } template , bool> = true> static auto from_range(Iter first, Sent last) { auto e = owner_t{}; auto result = rrbtree{empty()}; for (; first != last; ++first) result.push_back_mut(e, *first); return result; } static auto from_fill(size_t n, T v) { auto e = owner_t{}; auto result = rrbtree{empty()}; while (n-- > 0) result.push_back_mut(e, v); return result; } rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) : size{sz} , shift{sh} , root{r} , tail{t} { assert(check_tree()); } rrbtree(const rrbtree& other) : rrbtree{other.size, other.shift, other.root, other.tail} { inc(); } rrbtree(rrbtree&& other) : rrbtree{empty()} { swap(*this, other); } rrbtree& operator=(const rrbtree& other) { auto next{other}; swap(*this, next); return *this; } rrbtree& operator=(rrbtree&& other) { swap(*this, other); return *this; } friend void swap(rrbtree& x, rrbtree& y) { using std::swap; swap(x.size, y.size); swap(x.shift, y.shift); swap(x.root, y.root); swap(x.tail, y.tail); } ~rrbtree() { dec(); } void inc() const { root->inc(); tail->inc(); } void dec() const { traverse(dec_visitor()); } auto tail_size() const { return size - tail_offset(); } auto tail_offset() const { auto r = root->relaxed(); assert(r == nullptr || r->d.count); return r ? r->d.sizes[r->d.count - 1] : size ? (size - 1) & ~mask /* otherwise */ : 0; } template void traverse(Visitor v, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; if (tail_off) visit_maybe_relaxed_sub(root, shift, tail_off, v, args...); else make_empty_regular_pos(root).visit(v, args...); if (tail_size) make_leaf_sub_pos(tail, tail_size).visit(v, args...); else make_empty_leaf_pos(tail).visit(v, args...); } template void traverse(Visitor v, size_t first, size_t last, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; if (first < tail_off) visit_maybe_relaxed_sub(root, shift, tail_off, v, first, last < tail_off ? last : tail_off, args...); if (last > tail_off) make_leaf_sub_pos(tail, tail_size) .visit(v, first > tail_off ? first - tail_off : 0, last - tail_off, args...); } template bool traverse_p(Visitor v, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; return (tail_off ? visit_maybe_relaxed_sub(root, shift, tail_off, v, args...) : make_empty_regular_pos(root).visit(v, args...)) && (tail_size ? make_leaf_sub_pos(tail, tail_size).visit(v, args...) : make_empty_leaf_pos(tail).visit(v, args...)); } template bool traverse_p(Visitor v, size_t first, size_t last, Args&&... args) const { auto tail_off = tail_offset(); auto tail_size = size - tail_off; return (first < tail_off ? visit_maybe_relaxed_sub(root, shift, tail_off, v, first, last < tail_off ? last : tail_off, args...) : true) && (last > tail_off ? make_leaf_sub_pos(tail, tail_size) .visit(v, first > tail_off ? first - tail_off : 0, last - tail_off, args...) : true); } template decltype(auto) descend(Visitor v, size_t idx) const { auto tail_off = tail_offset(); return idx >= tail_off ? make_leaf_descent_pos(tail).visit(v, idx - tail_off) : visit_maybe_relaxed_descent(root, shift, v, idx); } template void for_each_chunk(Fn&& fn) const { traverse(for_each_chunk_visitor{}, std::forward(fn)); } template void for_each_chunk(size_t first, size_t last, Fn&& fn) const { traverse(for_each_chunk_i_visitor{}, first, last, std::forward(fn)); } template bool for_each_chunk_p(Fn&& fn) const { return traverse_p(for_each_chunk_p_visitor{}, std::forward(fn)); } template bool for_each_chunk_p(size_t first, size_t last, Fn&& fn) const { return traverse_p( for_each_chunk_p_i_visitor{}, first, last, std::forward(fn)); } bool equals(const rrbtree& other) const { using iter_t = rrbtree_iterator; if (size != other.size) return false; if (size == 0) return true; auto tail_off = tail_offset(); auto tail_off_other = other.tail_offset(); // compare trees if (tail_off > 0 && tail_off_other > 0) { // other.shift != shift is a theoretical possibility for // relaxed trees that sadly we haven't managed to exercise // in tests yet... if (other.shift >= shift) { if (!visit_maybe_relaxed_sub(other.root, other.shift, tail_off_other, equals_visitor::rrb{}, iter_t{other}, root, shift, tail_off)) return false; } else { if (!visit_maybe_relaxed_sub(root, shift, tail_off, equals_visitor::rrb{}, iter_t{*this}, other.root, other.shift, tail_off_other)) return false; } } return tail_off == tail_off_other ? make_leaf_sub_pos(tail, tail_size()) .visit(equals_visitor{}, other.tail) : tail_off > tail_off_other ? std::equal(tail->leaf(), tail->leaf() + (size - tail_off), other.tail->leaf() + (tail_off - tail_off_other)) /* otherwise */ : std::equal(tail->leaf(), tail->leaf() + (size - tail_off), iter_t{other} + tail_off); } std::tuple push_tail(node_t* root, shift_t shift, size_t size, node_t* tail, count_t tail_size) const { if (auto r = root->relaxed()) { auto new_root = make_relaxed_pos(root, shift, r) .visit(push_tail_visitor{}, tail, tail_size); if (new_root) return std::make_tuple(shift, new_root); else { auto new_root = node_t::make_inner_r_n(2); try { auto new_path = node_t::make_path(shift, tail); new_root->inner()[0] = root->inc(); new_root->inner()[1] = new_path; new_root->relaxed()->d.sizes[0] = size; new_root->relaxed()->d.sizes[1] = size + tail_size; new_root->relaxed()->d.count = 2u; } catch (...) { node_t::delete_inner_r(new_root, 2); throw; } return std::make_tuple(shift + B, new_root); } } else if (size == size_t{branches} << shift) { auto new_root = node_t::make_inner_n(2); try { auto new_path = node_t::make_path(shift, tail); new_root->inner()[0] = root->inc(); new_root->inner()[1] = new_path; } catch (...) { node_t::delete_inner(new_root, 2); throw; } return std::make_tuple(shift + B, new_root); } else if (size) { auto new_root = make_regular_sub_pos(root, shift, size) .visit(push_tail_visitor{}, tail); return std::make_tuple(shift, new_root); } else { return std::make_tuple(shift, node_t::make_path(shift, tail)); } } void push_tail_mut(edit_t e, size_t tail_off, node_t* tail, count_t tail_size) { if (auto r = root->relaxed()) { auto new_root = make_relaxed_pos(root, shift, r) .visit(push_tail_mut_visitor{}, e, tail, tail_size); if (new_root) { root = new_root; } else { auto new_root = node_t::make_inner_r_e(e); try { auto new_path = node_t::make_path_e(e, shift, tail); new_root->inner()[0] = root; new_root->inner()[1] = new_path; new_root->relaxed()->d.sizes[0] = tail_off; new_root->relaxed()->d.sizes[1] = tail_off + tail_size; new_root->relaxed()->d.count = 2u; root = new_root; shift += B; } catch (...) { node_t::delete_inner_r_e(new_root); throw; } } } else if (tail_off == size_t{branches} << shift) { auto new_root = node_t::make_inner_e(e); try { auto new_path = node_t::make_path_e(e, shift, tail); new_root->inner()[0] = root; new_root->inner()[1] = new_path; root = new_root; shift += B; } catch (...) { node_t::delete_inner_e(new_root); throw; } } else if (tail_off) { auto new_root = make_regular_sub_pos(root, shift, tail_off) .visit(push_tail_mut_visitor{}, e, tail); root = new_root; } else { auto new_root = node_t::make_path_e(e, shift, tail); dec_empty_regular(root); root = new_root; } } void ensure_mutable_tail(edit_t e, count_t n) { if (!tail->can_mutate(e)) { auto new_tail = node_t::copy_leaf_e(e, tail, n); dec_leaf(tail, n); tail = new_tail; } } void push_back_mut(edit_t e, T value) { auto ts = tail_size(); if (ts < branches) { ensure_mutable_tail(e, ts); new (&tail->leaf()[ts]) T{std::move(value)}; } else { using std::get; auto new_tail = node_t::make_leaf_e(e, std::move(value)); auto tail_off = tail_offset(); try { push_tail_mut(e, tail_off, tail, ts); tail = new_tail; } catch (...) { node_t::delete_leaf(new_tail, 1u); throw; } } ++size; } rrbtree push_back(T value) const { auto ts = tail_size(); if (ts < branches) { auto new_tail = node_t::copy_leaf_emplace(tail, ts, std::move(value)); return {size + 1, shift, root->inc(), new_tail}; } else { using std::get; auto new_tail = node_t::make_leaf_n(1u, std::move(value)); auto tail_off = tail_offset(); try { auto new_root = push_tail(root, shift, tail_off, tail, size - tail_off); tail->inc(); return {size + 1, get<0>(new_root), get<1>(new_root), new_tail}; } catch (...) { node_t::delete_leaf(new_tail, 1u); throw; } } } std::tuple region_for(size_t idx) const { using std::get; auto tail_off = tail_offset(); if (idx >= tail_off) { return std::make_tuple(tail->leaf(), tail_off, size); } else { auto subs = visit_maybe_relaxed_sub( root, shift, tail_off, region_for_visitor(), idx); auto first = idx - get<1>(subs); auto end = first + get<2>(subs); return std::make_tuple(get<0>(subs), first, end); } } T& get_mut(edit_t e, size_t idx) { auto tail_off = tail_offset(); if (idx >= tail_off) { ensure_mutable_tail(e, size - tail_off); return tail->leaf()[(idx - tail_off) & mask]; } else { return visit_maybe_relaxed_sub(root, shift, tail_off, get_mut_visitor{}, idx, e, &root); } } const T& get(size_t index) const { return descend(get_visitor(), index); } const T& get_check(size_t index) const { if (index >= size) throw std::out_of_range{"out of range"}; return descend(get_visitor(), index); } const T& front() const { return get(0); } const T& back() const { return get(size - 1); } template void update_mut(edit_t e, size_t idx, FnT&& fn) { auto& elem = get_mut(e, idx); elem = std::forward(fn)(std::move(elem)); } template rrbtree update(size_t idx, FnT&& fn) const { auto tail_off = tail_offset(); if (idx >= tail_off) { auto tail_size = size - tail_off; auto new_tail = make_leaf_sub_pos(tail, tail_size) .visit(update_visitor{}, idx - tail_off, fn); return {size, shift, root->inc(), new_tail}; } else { auto new_root = visit_maybe_relaxed_sub( root, shift, tail_off, update_visitor{}, idx, fn); return {size, shift, new_root, tail->inc()}; } } void assoc_mut(edit_t e, size_t idx, T value) { update_mut(e, idx, [&](auto&&) { return std::move(value); }); } rrbtree assoc(size_t idx, T value) const { return update(idx, [&](auto&&) { return std::move(value); }); } void take_mut(edit_t e, size_t new_size) { auto tail_off = tail_offset(); if (new_size == 0) { *this = empty(); } else if (new_size >= size) { return; } else if (new_size > tail_off) { auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); tail = new_tail; } size = new_size; return; } else { using std::get; auto l = new_size - 1; auto v = slice_right_mut_visitor(); auto r = visit_maybe_relaxed_sub(root, shift, tail_off, v, l, e); auto new_shift = get<0>(r); auto new_root = get<1>(r); auto new_tail = get<3>(r); if (new_root) { root = new_root; shift = new_shift; } else { root = empty().root->inc(); shift = BL; } dec_leaf(tail, size - tail_off); size = new_size; tail = new_tail; return; } } rrbtree take(size_t new_size) const { auto tail_off = tail_offset(); if (new_size == 0) { return empty(); } else if (new_size >= size) { return *this; } else if (new_size > tail_off) { auto new_tail = node_t::copy_leaf(tail, new_size - tail_off); return {new_size, shift, root->inc(), new_tail}; } else { using std::get; auto l = new_size - 1; auto v = slice_right_visitor(); auto r = visit_maybe_relaxed_sub(root, shift, tail_off, v, l); auto new_shift = get<0>(r); auto new_root = get<1>(r); auto new_tail = get<3>(r); if (new_root) { IMMER_ASSERT_TAGGED(new_root->compute_shift() == get<0>(r)); assert(new_root->check(new_shift, new_size - get<2>(r))); return {new_size, new_shift, new_root, new_tail}; } else { return {new_size, BL, empty().root->inc(), new_tail}; } } } void drop_mut(edit_t e, size_t elems) { using std::get; auto tail_off = tail_offset(); if (elems == 0) { return; } else if (elems >= size) { *this = empty(); } else if (elems == tail_off) { dec_inner(root, shift, tail_off); shift = BL; root = empty().root->inc(); size -= elems; return; } else if (elems > tail_off) { auto v = slice_left_mut_visitor(); tail = get<1>(make_leaf_sub_pos(tail, size - tail_off) .visit(v, elems - tail_off, e)); if (root != empty().root) { dec_inner(root, shift, tail_off); shift = BL; root = empty().root->inc(); } size -= elems; return; } else { auto v = slice_left_mut_visitor(); auto r = visit_maybe_relaxed_sub(root, shift, tail_off, v, elems, e); shift = get<0>(r); root = get<1>(r); size -= elems; return; } } rrbtree drop(size_t elems) const { if (elems == 0) { return *this; } else if (elems >= size) { return empty(); } else if (elems == tail_offset()) { return {size - elems, BL, empty().root->inc(), tail->inc()}; } else if (elems > tail_offset()) { auto tail_off = tail_offset(); auto new_tail = node_t::copy_leaf(tail, elems - tail_off, size - tail_off); return {size - elems, BL, empty().root->inc(), new_tail}; } else { using std::get; auto v = slice_left_visitor(); auto r = visit_maybe_relaxed_sub(root, shift, tail_offset(), v, elems); auto new_root = get<1>(r); auto new_shift = get<0>(r); return {size - elems, new_shift, new_root, tail->inc()}; } return *this; } rrbtree concat(const rrbtree& r) const { assert(r.size < (std::numeric_limits::max() - size)); using std::get; if (size == 0) return r; else if (r.size == 0) return *this; else if (r.tail_offset() == 0) { // just concat the tail, similar to push_back auto tail_offst = tail_offset(); auto tail_size = size - tail_offst; if (tail_size == branches) { auto new_root = push_tail(root, shift, tail_offst, tail, tail_size); tail->inc(); return {size + r.size, get<0>(new_root), get<1>(new_root), r.tail->inc()}; } else if (tail_size + r.size <= branches) { auto new_tail = node_t::copy_leaf(tail, tail_size, r.tail, r.size); return {size + r.size, shift, root->inc(), new_tail}; } else { auto remaining = branches - tail_size; auto add_tail = node_t::copy_leaf(tail, tail_size, r.tail, remaining); try { auto new_tail = node_t::copy_leaf(r.tail, remaining, r.size); try { auto new_root = push_tail( root, shift, tail_offst, add_tail, branches); return {size + r.size, get<0>(new_root), get<1>(new_root), new_tail}; } catch (...) { node_t::delete_leaf(new_tail, r.size - remaining); throw; } } catch (...) { node_t::delete_leaf(add_tail, branches); throw; } } } else if (tail_offset() == 0) { auto tail_offst = tail_offset(); auto tail_size = size - tail_offst; auto concated = concat_trees(tail, tail_size, r.root, r.shift, r.tail_offset()); auto new_shift = concated.shift(); auto new_root = concated.node(); IMMER_ASSERT_TAGGED(new_shift == new_root->compute_shift()); assert(new_root->check(new_shift, size + r.tail_offset())); return {size + r.size, new_shift, new_root, r.tail->inc()}; } else { auto tail_offst = tail_offset(); auto tail_size = size - tail_offst; auto concated = concat_trees(root, shift, tail_offst, tail, tail_size, r.root, r.shift, r.tail_offset()); auto new_shift = concated.shift(); auto new_root = concated.node(); IMMER_ASSERT_TAGGED(new_shift == new_root->compute_shift()); assert(new_root->check(new_shift, size + r.tail_offset())); return {size + r.size, new_shift, new_root, r.tail->inc()}; } } constexpr static bool supports_transient_concat = !std::is_empty::value; friend void concat_mut_l(rrbtree& l, edit_t el, const rrbtree& r) { assert(&l != &r); assert(r.size < (std::numeric_limits::max() - l.size)); using std::get; if (l.size == 0) l = r; else if (r.size == 0) return; else if (r.tail_offset() == 0) { // just concat the tail, similar to push_back auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; if (tail_size == branches) { l.push_tail_mut(el, tail_offst, l.tail, tail_size); l.tail = r.tail->inc(); l.size += r.size; return; } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); std::uninitialized_copy(r.tail->leaf(), r.tail->leaf() + r.size, l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); std::uninitialized_copy(r.tail->leaf(), r.tail->leaf() + remaining, l.tail->leaf() + tail_size); try { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); try { l.push_tail_mut(el, tail_offst, l.tail, branches); l.tail = new_tail; l.size += r.size; return; } catch (...) { node_t::delete_leaf(new_tail, r.size - remaining); throw; } } catch (...) { destroy_n(r.tail->leaf() + tail_size, remaining); throw; } } } else if (l.tail_offset() == 0) { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(el, el, l.tail, tail_size, MemoryPolicy::transience_t::noone, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); l.size += r.size; l.shift = concated.shift(); l.root = concated.node(); l.tail = r.tail; } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees( l.tail, tail_size, r.root, r.shift, r.tail_offset()); l = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; return; } } else { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(el, el, l.root, l.shift, tail_offst, l.tail, tail_size, MemoryPolicy::transience_t::noone, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); l.size += r.size; l.shift = concated.shift(); l.root = concated.node(); l.tail = r.tail; } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees(l.root, l.shift, tail_offst, l.tail, tail_size, r.root, r.shift, r.tail_offset()); l = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; } } } friend void concat_mut_r(const rrbtree& l, rrbtree& r, edit_t er) { assert(&l != &r); assert(r.size < (std::numeric_limits::max() - l.size)); using std::get; if (r.size == 0) r = std::move(l); else if (l.size == 0) return; else if (r.tail_offset() == 0) { // just concat the tail, similar to push_back auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; if (tail_size == branches) { // this could be improved by making sure that the // newly created nodes as part of the `push_tail()` // are tagged with `er` auto res = l.push_tail(l.root, l.shift, tail_offst, l.tail, tail_size); l.tail->inc(); // note: leak if mutably concatenated // with itself, but this is forbidden // by the interface r = {l.size + r.size, get<0>(res), get<1>(res), r.tail->inc()}; return; } else if (tail_size + r.size <= branches) { // doing this in a exception way mutating way is very // tricky while potential performance gains are // minimal (we need to move every element of the right // tail anyways to make space for the left tail) // // we could however improve this by at least moving the // elements of the right tail... auto new_tail = node_t::copy_leaf(l.tail, tail_size, r.tail, r.size); r = {l.size + r.size, l.shift, l.root->inc(), new_tail}; return; } else { // like the immutable version auto remaining = branches - tail_size; auto add_tail = node_t::copy_leaf_e( er, l.tail, tail_size, r.tail, remaining); try { auto new_tail = node_t::copy_leaf_e(er, r.tail, remaining, r.size); try { // this could be improved by making sure that the // newly created nodes as part of the `push_tail()` // are tagged with `er` auto new_root = l.push_tail(l.root, l.shift, tail_offst, add_tail, branches); r = {l.size + r.size, get<0>(new_root), get<1>(new_root), new_tail}; return; } catch (...) { node_t::delete_leaf(new_tail, r.size - remaining); throw; } } catch (...) { node_t::delete_leaf(add_tail, branches); throw; } return; } } else if (l.tail_offset() == 0) { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(er, MemoryPolicy::transience_t::noone, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); r.size += l.size; r.shift = concated.shift(); r.root = concated.node(); } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees( l.tail, tail_size, r.root, r.shift, r.tail_offset()); r = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; return; } } else { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(er, MemoryPolicy::transience_t::noone, l.root, l.shift, tail_offst, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); r.size += l.size; r.shift = concated.shift(); r.root = concated.node(); return; } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees(l.root, l.shift, tail_offst, l.tail, tail_size, r.root, r.shift, r.tail_offset()); r = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; return; } } } friend void concat_mut_lr_l(rrbtree& l, edit_t el, rrbtree& r, edit_t er) { assert(&l != &r); assert(r.size < (std::numeric_limits::max() - l.size)); using std::get; if (l.size == 0) l = r; else if (r.size == 0) return; else if (r.tail_offset() == 0) { // just concat the tail, similar to push_back auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; if (tail_size == branches) { l.push_tail_mut(el, tail_offst, l.tail, tail_size); l.tail = r.tail->inc(); l.size += r.size; return; } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) detail::uninitialized_move(r.tail->leaf(), r.tail->leaf() + r.size, l.tail->leaf() + tail_size); else std::uninitialized_copy(r.tail->leaf(), r.tail->leaf() + r.size, l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); if (r.tail->can_mutate(er)) detail::uninitialized_move(r.tail->leaf(), r.tail->leaf() + remaining, l.tail->leaf() + tail_size); else std::uninitialized_copy(r.tail->leaf(), r.tail->leaf() + remaining, l.tail->leaf() + tail_size); try { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); try { l.push_tail_mut(el, tail_offst, l.tail, branches); l.tail = new_tail; l.size += r.size; return; } catch (...) { node_t::delete_leaf(new_tail, r.size - remaining); throw; } } catch (...) { destroy_n(r.tail->leaf() + tail_size, remaining); throw; } } } else if (l.tail_offset() == 0) { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(el, el, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); l.size += r.size; l.shift = concated.shift(); l.root = concated.node(); l.tail = r.tail; r.hard_reset(); } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees( l.tail, tail_size, r.root, r.shift, r.tail_offset()); l = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; return; } } else { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(el, el, l.root, l.shift, tail_offst, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); l.size += r.size; l.shift = concated.shift(); l.root = concated.node(); l.tail = r.tail; r.hard_reset(); } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees(l.root, l.shift, tail_offst, l.tail, tail_size, r.root, r.shift, r.tail_offset()); l = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; } } } friend void concat_mut_lr_r(rrbtree& l, edit_t el, rrbtree& r, edit_t er) { assert(&l != &r); assert(r.size < (std::numeric_limits::max() - l.size)); using std::get; if (r.size == 0) r = l; else if (l.size == 0) return; else if (r.tail_offset() == 0) { // just concat the tail, similar to push_back auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; if (tail_size == branches) { // this could be improved by making sure that the // newly created nodes as part of the `push_tail()` // are tagged with `er` auto res = l.push_tail(l.root, l.shift, tail_offst, l.tail, tail_size); r = {l.size + r.size, get<0>(res), get<1>(res), r.tail->inc()}; return; } else if (tail_size + r.size <= branches) { // doing this in a exception way mutating way is very // tricky while potential performance gains are // minimal (we need to move every element of the right // tail anyways to make space for the left tail) // // we could however improve this by at least moving the // elements of the mutable tails... auto new_tail = node_t::copy_leaf(l.tail, tail_size, r.tail, r.size); r = {l.size + r.size, l.shift, l.root->inc(), new_tail}; return; } else { // like the immutable version. // we could improve this also by moving elements // instead of just copying them auto remaining = branches - tail_size; auto add_tail = node_t::copy_leaf_e( er, l.tail, tail_size, r.tail, remaining); try { auto new_tail = node_t::copy_leaf_e(er, r.tail, remaining, r.size); try { // this could be improved by making sure that the // newly created nodes as part of the `push_tail()` // are tagged with `er` auto new_root = l.push_tail(l.root, l.shift, tail_offst, add_tail, branches); r = {l.size + r.size, get<0>(new_root), get<1>(new_root), new_tail}; return; } catch (...) { node_t::delete_leaf(new_tail, r.size - remaining); throw; } } catch (...) { node_t::delete_leaf(add_tail, branches); throw; } return; } } else if (l.tail_offset() == 0) { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(er, el, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); r.size += l.size; r.shift = concated.shift(); r.root = concated.node(); l.hard_reset(); } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees( l.tail, tail_size, r.root, r.shift, r.tail_offset()); r = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; return; } } else { if (supports_transient_concat) { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees_mut(er, el, l.root, l.shift, tail_offst, l.tail, tail_size, er, r.root, r.shift, r.tail_offset()); IMMER_ASSERT_TAGGED(concated.shift() == concated.node()->compute_shift()); assert(concated.node()->check(concated.shift(), l.size + r.tail_offset())); r.size += l.size; r.shift = concated.shift(); r.root = concated.node(); l.hard_reset(); } else { auto tail_offst = l.tail_offset(); auto tail_size = l.size - tail_offst; auto concated = concat_trees(l.root, l.shift, tail_offst, l.tail, tail_size, r.root, r.shift, r.tail_offset()); r = {l.size + r.size, concated.shift(), concated.node(), r.tail->inc()}; } } } void hard_reset() { assert(supports_transient_concat); auto&& empty_ = empty(); size = empty_.size; shift = empty_.shift; root = empty_.root; tail = empty_.tail; } bool check_tree() const { assert(shift <= sizeof(size_t) * 8 - BL); assert(shift >= BL); assert(tail_offset() <= size); assert(tail_size() <= branches); #if IMMER_DEBUG_DEEP_CHECK assert(check_root()); assert(check_tail()); #endif return true; } bool check_tail() const { #if IMMER_DEBUG_DEEP_CHECK if (tail_size() > 0) assert(tail->check(endshift, tail_size())); #endif return true; } bool check_root() const { #if IMMER_DEBUG_DEEP_CHECK if (tail_offset() > 0) assert(root->check(shift, tail_offset())); else { IMMER_ASSERT_TAGGED(root->kind() == node_t::kind_t::inner); assert(shift == BL); } #endif return true; } #if IMMER_DEBUG_PRINT void debug_print(std::ostream& out) const { out << "--" << std::endl << "{" << std::endl << " size = " << size << std::endl << " shift = " << shift << std::endl << " root = " << std::endl; debug_print_node(out, root, shift, tail_offset()); out << " tail = " << std::endl; debug_print_node(out, tail, endshift, tail_size()); out << "}" << std::endl; } void debug_print_indent(std::ostream& out, unsigned indent) const { while (indent-- > 0) out << ' '; } void debug_print_node(std::ostream& out, node_t* node, shift_t shift, size_t size, unsigned indent = 8) const { const auto indent_step = 4; if (shift == endshift) { debug_print_indent(out, indent); out << "- {" << size << "} " << pretty_print_array(node->leaf(), size) << std::endl; } else if (auto r = node->relaxed()) { auto count = r->d.count; debug_print_indent(out, indent); out << "# {" << size << "} " << pretty_print_array(r->d.sizes, r->d.count) << std::endl; auto last_size = size_t{}; for (auto i = count_t{}; i < count; ++i) { debug_print_node(out, node->inner()[i], shift - B, r->d.sizes[i] - last_size, indent + indent_step); last_size = r->d.sizes[i]; } } else { debug_print_indent(out, indent); out << "+ {" << size << "}" << std::endl; auto count = (size >> shift) + (size - ((size >> shift) << shift) > 0); if (count) { for (auto i = count_t{}; i < count - 1; ++i) debug_print_node(out, node->inner()[i], shift - B, 1 << shift, indent + indent_step); debug_print_node(out, node->inner()[count - 1], shift - B, size - ((count - 1) << shift), indent + indent_step); } } } #endif // IMMER_DEBUG_PRINT }; } // namespace rbts } // namespace detail } // namespace immer