From b92de4d9762b648bd06ff1f64326fb3bd617bef7 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 23 Mar 2021 23:23:58 +0100 Subject: [PATCH 01/25] [Consensus] Reject special txes before V6 enforcement --- src/evo/specialtx.cpp | 37 +++++++++++++++++++++++++++---------- src/evo/specialtx.h | 2 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index d9c82dc61680..89ee973edeae 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -1,4 +1,3 @@ - // Copyright (c) 2017 The Dash Core developers // Copyright (c) 2020 The PIVX Core developers // Distributed under the MIT software license, see the accompanying @@ -12,7 +11,8 @@ #include "consensus/validation.h" #include "primitives/block.h" -bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) +// Basic non-contextual checks for all tx types +static bool CheckSpecialTxBasic(const CTransaction& tx, CValidationState& state) { bool hasExtraPayload = tx.hasExtraPayload(); @@ -32,8 +32,6 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) REJECT_INVALID, "bad-txns-type-version"); } - // --- From here on, tx has nVersion>=2 and nType!=0 - // Cannot be coinbase/coinstake tx if (tx.IsCoinBase() || tx.IsCoinStake()) { return state.DoS(10, error("%s: Special tx is coinbase or coinstake", __func__), @@ -52,8 +50,23 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) REJECT_INVALID, "bad-txns-payload-oversize"); } + return true; +} + +bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) +{ + if (!CheckSpecialTxBasic(tx, state)) { + // pass the state returned by the function above + return false; + } + + // non-contextual per-type checks switch (tx.nType) { - /* per-tx-type non-contextual checking */ + case CTransaction::TxType::NORMAL: { + // nothing to check + return true; + } + // !TODO } return state.DoS(10, error("%s: special tx %s with invalid type %d", __func__, tx.GetHash().ToString(), tx.nType), @@ -62,9 +75,10 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) { + // This function is not called when connecting the genesis block assert(pindexPrev != nullptr); - if (!CheckSpecialTxNoContext(tx, state)) { + if (!CheckSpecialTxBasic(tx, state)) { // pass the state returned by the function above return false; } @@ -74,13 +88,16 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali return true; } - // !TODO: Add enforcement-height check + if (!Params().GetConsensus().NetworkUpgradeActive(pindexPrev->nHeight + 1, Consensus::UPGRADE_V6_0)) { + return state.DoS(100, error("%s: Special tx when v6 upgrade not enforced yet", __func__), + REJECT_INVALID, "bad-txns-v6-not-active"); + } + // contextual and non-contextual per-type checks switch (tx.nType) { - /* per-tx-type contextual checking */ + // !TODO } - // should never get here, as we already checked the type in CheckSpecialTxNoContext return state.DoS(10, error("%s: special tx %s with invalid type %d", __func__, tx.GetHash().ToString(), tx.nType), REJECT_INVALID, "bad-tx-type"); } @@ -98,7 +115,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV return true; } -bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindexPrev) +bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) { /* undo special txes in batches */ return true; diff --git a/src/evo/specialtx.h b/src/evo/specialtx.h index 6c8373043c35..14c5123fcc8c 100644 --- a/src/evo/specialtx.h +++ b/src/evo/specialtx.h @@ -29,6 +29,6 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state); // Update internal tiertwo data when blocks containing special txes get connected/disconnected bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state, bool fJustCheck); -bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindexPrev); +bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex); #endif // PIVX_SPECIALTX_H From e067443b187e16c275935942a5a157dc3d5e43f8 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 26 Jan 2021 15:03:06 +0100 Subject: [PATCH 02/25] [Build] Add "immer" functional/immutable containers library Coming from https://github.com/arximboldi/immer Commit 0a718d2d76bab6ebdcf43de943bd6c7d2dbfe2f9 --- src/Makefile.am | 3 + src/immer/algorithm.hpp | 214 ++ src/immer/array.hpp | 310 +++ src/immer/array_transient.hpp | 187 ++ src/immer/atom.hpp | 259 ++ src/immer/box.hpp | 172 ++ src/immer/config.hpp | 66 + src/immer/detail/arrays/no_capacity.hpp | 197 ++ src/immer/detail/arrays/node.hpp | 132 + src/immer/detail/arrays/with_capacity.hpp | 301 +++ src/immer/detail/combine_standard_layout.hpp | 196 ++ src/immer/detail/hamts/bits.hpp | 101 + src/immer/detail/hamts/champ.hpp | 476 ++++ src/immer/detail/hamts/champ_iterator.hpp | 153 ++ src/immer/detail/hamts/node.hpp | 722 +++++ src/immer/detail/iterator_facade.hpp | 202 ++ src/immer/detail/rbts/bits.hpp | 33 + src/immer/detail/rbts/node.hpp | 942 +++++++ src/immer/detail/rbts/operations.hpp | 2322 +++++++++++++++++ src/immer/detail/rbts/position.hpp | 1846 +++++++++++++ src/immer/detail/rbts/rbtree.hpp | 533 ++++ src/immer/detail/rbts/rbtree_iterator.hpp | 102 + src/immer/detail/rbts/rrbtree.hpp | 1282 +++++++++ src/immer/detail/rbts/rrbtree_iterator.hpp | 102 + src/immer/detail/rbts/visitor.hpp | 56 + src/immer/detail/ref_count_base.hpp | 36 + src/immer/detail/type_traits.hpp | 191 ++ src/immer/detail/util.hpp | 225 ++ .../experimental/detail/dvektor_impl.hpp | 512 ++++ src/immer/experimental/dvektor.hpp | 63 + src/immer/flex_vector.hpp | 502 ++++ src/immer/flex_vector_transient.hpp | 232 ++ src/immer/heap/cpp_heap.hpp | 41 + src/immer/heap/debug_size_heap.hpp | 54 + src/immer/heap/free_list_heap.hpp | 83 + src/immer/heap/free_list_node.hpp | 25 + src/immer/heap/gc_heap.hpp | 130 + src/immer/heap/heap_policy.hpp | 148 ++ src/immer/heap/identity_heap.hpp | 34 + src/immer/heap/malloc_heap.hpp | 47 + src/immer/heap/split_heap.hpp | 41 + src/immer/heap/tags.hpp | 15 + .../heap/thread_local_free_list_heap.hpp | 55 + src/immer/heap/unsafe_free_list_heap.hpp | 108 + src/immer/heap/with_data.hpp | 43 + src/immer/map.hpp | 311 +++ src/immer/map_transient.hpp | 29 + src/immer/memory_policy.hpp | 138 + src/immer/refcount/enable_intrusive_ptr.hpp | 37 + src/immer/refcount/no_refcount_policy.hpp | 44 + src/immer/refcount/refcount_policy.hpp | 111 + src/immer/refcount/unsafe_refcount_policy.hpp | 37 + src/immer/set.hpp | 161 ++ src/immer/set_transient.hpp | 28 + src/immer/transience/gc_transience_policy.hpp | 110 + src/immer/transience/no_transience_policy.hpp | 48 + src/immer/vector.hpp | 354 +++ src/immer/vector_transient.hpp | 189 ++ 58 files changed, 15091 insertions(+) create mode 100644 src/immer/algorithm.hpp create mode 100644 src/immer/array.hpp create mode 100644 src/immer/array_transient.hpp create mode 100644 src/immer/atom.hpp create mode 100644 src/immer/box.hpp create mode 100644 src/immer/config.hpp create mode 100644 src/immer/detail/arrays/no_capacity.hpp create mode 100644 src/immer/detail/arrays/node.hpp create mode 100644 src/immer/detail/arrays/with_capacity.hpp create mode 100644 src/immer/detail/combine_standard_layout.hpp create mode 100644 src/immer/detail/hamts/bits.hpp create mode 100644 src/immer/detail/hamts/champ.hpp create mode 100644 src/immer/detail/hamts/champ_iterator.hpp create mode 100644 src/immer/detail/hamts/node.hpp create mode 100644 src/immer/detail/iterator_facade.hpp create mode 100644 src/immer/detail/rbts/bits.hpp create mode 100644 src/immer/detail/rbts/node.hpp create mode 100644 src/immer/detail/rbts/operations.hpp create mode 100644 src/immer/detail/rbts/position.hpp create mode 100644 src/immer/detail/rbts/rbtree.hpp create mode 100644 src/immer/detail/rbts/rbtree_iterator.hpp create mode 100644 src/immer/detail/rbts/rrbtree.hpp create mode 100644 src/immer/detail/rbts/rrbtree_iterator.hpp create mode 100644 src/immer/detail/rbts/visitor.hpp create mode 100644 src/immer/detail/ref_count_base.hpp create mode 100644 src/immer/detail/type_traits.hpp create mode 100644 src/immer/detail/util.hpp create mode 100644 src/immer/experimental/detail/dvektor_impl.hpp create mode 100644 src/immer/experimental/dvektor.hpp create mode 100644 src/immer/flex_vector.hpp create mode 100644 src/immer/flex_vector_transient.hpp create mode 100644 src/immer/heap/cpp_heap.hpp create mode 100644 src/immer/heap/debug_size_heap.hpp create mode 100644 src/immer/heap/free_list_heap.hpp create mode 100644 src/immer/heap/free_list_node.hpp create mode 100644 src/immer/heap/gc_heap.hpp create mode 100644 src/immer/heap/heap_policy.hpp create mode 100644 src/immer/heap/identity_heap.hpp create mode 100644 src/immer/heap/malloc_heap.hpp create mode 100644 src/immer/heap/split_heap.hpp create mode 100644 src/immer/heap/tags.hpp create mode 100644 src/immer/heap/thread_local_free_list_heap.hpp create mode 100644 src/immer/heap/unsafe_free_list_heap.hpp create mode 100644 src/immer/heap/with_data.hpp create mode 100644 src/immer/map.hpp create mode 100644 src/immer/map_transient.hpp create mode 100644 src/immer/memory_policy.hpp create mode 100644 src/immer/refcount/enable_intrusive_ptr.hpp create mode 100644 src/immer/refcount/no_refcount_policy.hpp create mode 100644 src/immer/refcount/refcount_policy.hpp create mode 100644 src/immer/refcount/unsafe_refcount_policy.hpp create mode 100644 src/immer/set.hpp create mode 100644 src/immer/set_transient.hpp create mode 100644 src/immer/transience/gc_transience_policy.hpp create mode 100644 src/immer/transience/no_transience_policy.hpp create mode 100644 src/immer/vector.hpp create mode 100644 src/immer/vector_transient.hpp diff --git a/src/Makefile.am b/src/Makefile.am index 8d248316fd7b..7f77117f40e5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -735,7 +735,10 @@ CTAES_DIST += crypto/ctaes/ctaes.h CTAES_DIST += crypto/ctaes/README.md CTAES_DIST += crypto/ctaes/test.c +IMMER_DIST = immer + EXTRA_DIST = $(CTAES_DIST) rust +EXTRA_DIST += $(IMMER_DIST) config/pivx-config.h: config/stamp-h1 diff --git a/src/immer/algorithm.hpp b/src/immer/algorithm.hpp new file mode 100644 index 000000000000..df9ff28a8314 --- /dev/null +++ b/src/immer/algorithm.hpp @@ -0,0 +1,214 @@ +// +// 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 + +namespace immer { + +/** + * @defgroup algorithm + * @{ + */ + +/*@{*/ +// Right now these algorithms dispatch directly to the vector +// implementations unconditionally. This will be changed in the +// future to support other kinds of containers. + +/*! + * Apply operation `fn` for every contiguous *chunk* of data in the + * range sequentially. Each time, `Fn` is passed two `value_type` + * pointers describing a range over a part of the vector. This allows + * iterating over the elements in the most efficient way. + * + * @rst + * + * .. tip:: This is a low level method. Most of the time, :doc:`other + * wrapper algorithms ` should be used instead. + * + * @endrst + */ +template +void for_each_chunk(const Range& r, Fn&& fn) +{ + r.impl().for_each_chunk(std::forward(fn)); +} + +template +void for_each_chunk(const Iterator& first, const Iterator& last, Fn&& fn) +{ + assert(&first.impl() == &last.impl()); + first.impl().for_each_chunk(first.index(), last.index(), + std::forward(fn)); +} + +template +void for_each_chunk(const T* first, const T* last, Fn&& fn) +{ + std::forward(fn)(first, last); +} + +/*! + * Apply operation `fn` for every contiguous *chunk* of data in the + * range sequentially, until `fn` returns `false`. Each time, `Fn` is + * passed two `value_type` pointers describing a range over a part of + * the vector. This allows iterating over the elements in the most + * efficient way. + * + * @rst + * + * .. tip:: This is a low level method. Most of the time, :doc:`other + * wrapper algorithms ` should be used instead. + * + * @endrst + */ +template +bool for_each_chunk_p(const Range& r, Fn&& fn) +{ + return r.impl().for_each_chunk_p(std::forward(fn)); +} + +template +bool for_each_chunk_p(const Iterator& first, const Iterator& last, Fn&& fn) +{ + assert(&first.impl() == &last.impl()); + return first.impl().for_each_chunk_p(first.index(), last.index(), + std::forward(fn)); +} + +template +bool for_each_chunk_p(const T* first, const T* last, Fn&& fn) +{ + return std::forward(fn)(first, last); +} + +/*! + * Equivalent of `std::accumulate` applied to the range `r`. + */ +template +T accumulate(Range&& r, T init) +{ + for_each_chunk(r, [&] (auto first, auto last) { + init = std::accumulate(first, last, init); + }); + return init; +} + +template +T accumulate(Range&& r, T init, Fn fn) +{ + for_each_chunk(r, [&] (auto first, auto last) { + init = std::accumulate(first, last, init, fn); + }); + return init; +} + +/*! + * Equivalent of `std::accumulate` applied to the range @f$ [first, + * last) @f$. + */ +template +T accumulate(Iterator first, Iterator last, T init) +{ + for_each_chunk(first, last, [&] (auto first, auto last) { + init = std::accumulate(first, last, init); + }); + return init; +} + +template +T accumulate(Iterator first, Iterator last, T init, Fn fn) +{ + for_each_chunk(first, last, [&] (auto first, auto last) { + init = std::accumulate(first, last, init, fn); + }); + return init; +} + +/*! + * Equivalent of `std::for_each` applied to the range `r`. + */ +template +Fn&& for_each(Range&& r, Fn&& fn) +{ + for_each_chunk(r, [&] (auto first, auto last) { + for (; first != last; ++first) + fn(*first); + }); + return std::forward(fn); +} + +/*! + * Equivalent of `std::for_each` applied to the range @f$ [first, + * last) @f$. + */ +template +Fn&& for_each(Iterator first, Iterator last, Fn&& fn) +{ + for_each_chunk(first, last, [&] (auto first, auto last) { + for (; first != last; ++first) + fn(*first); + }); + return std::forward(fn); +} + +/*! + * Equivalent of `std::copy` applied to the range `r`. + */ +template +OutIter copy(Range&& r, OutIter out) +{ + for_each_chunk(r, [&] (auto first, auto last) { + out = std::copy(first, last, out); + }); + return out; +} + +/*! + * Equivalent of `std::copy` applied to the range @f$ [first, + * last) @f$. + */ +template +OutIter copy(InIter first, InIter last, OutIter out) +{ + for_each_chunk(first, last, [&] (auto first, auto last) { + out = std::copy(first, last, out); + }); + return out; +} + +/*! + * Equivalent of `std::all_of` applied to the range `r`. + */ +template +bool all_of(Range&& r, Pred p) +{ + return for_each_chunk_p(r, [&] (auto first, auto last) { + return std::all_of(first, last, p); + }); +} + +/*! + * Equivalent of `std::all_of` applied to the range @f$ [first, last) + * @f$. + */ +template +bool all_of(Iter first, Iter last, Pred p) +{ + return for_each_chunk_p(first, last, [&] (auto first, auto last) { + return std::all_of(first, last, p); + }); +} + +/** @} */ // group: algorithm + +} // namespace immer diff --git a/src/immer/array.hpp b/src/immer/array.hpp new file mode 100644 index 000000000000..0f73649fb3ac --- /dev/null +++ b/src/immer/array.hpp @@ -0,0 +1,310 @@ +// +// 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 + +namespace immer { + +template +class array_transient; + +/*! + * Immutable container that stores a sequence of elements in + * contiguous memory. + * + * @tparam T The type of the values to be stored in the container. + * + * @rst + * + * It supports the most efficient iteration and random access, + * equivalent to a ``std::vector`` or ``std::array``, but all + * manipulations are :math:`O(size)`. + * + * .. tip:: Don't be fooled by the bad complexity of this data + * structure. It is a great choice for short sequence or when it + * is seldom or never changed. This depends on the ``sizeof(T)`` + * and the expensiveness of its ``T``'s copy constructor, in case + * of doubt, measure. For basic types, using an `array` when + * :math:`n < 100` is a good heuristic. + * + * @endrst + */ +template +class array +{ + using impl_t = std::conditional_t< + MemoryPolicy::use_transient_rvalues, + detail::arrays::with_capacity, + detail::arrays::no_capacity>; + + using move_t = + std::integral_constant; + +public: + using value_type = T; + using reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = const T*; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using memory_policy = MemoryPolicy; + using transient_type = array_transient; + + /*! + * Default constructor. It creates an array of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + array() = default; + + /*! + * Constructs an array containing the elements in `values`. + */ + array(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a array containing the elements in the range + * defined by the forward iterator `first` and range sentinel `last`. + */ + template + && detail::is_forward_iterator_v, bool> = true> + array(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + + /*! + * Constructs a array containing the element `val` repeated `n` + * times. + */ + array(size_type n, T v = {}) + : impl_{impl_t::from_fill(n, v)} + {} + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return impl_.data(); } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return impl_.data() + impl_.size; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + std::size_t size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.d->empty(); } + + /*! + * Access the raw data. + */ + const T* data() const { return impl_.data(); } + + /*! + * Access the last element. + */ + const T& back() const { return data()[size() - 1]; } + + /*! + * Access the first element. + */ + const T& front() const { return data()[0]; } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Returns whether the vectors are equal. + */ + bool operator==(const array& other) const + { return impl_.equals(other.impl_); } + bool operator!=(const array& other) const + { return !(*this == other); } + + /*! + * Returns an array with `value` inserted at the end. It may + * allocate memory and its complexity is @f$ O(size) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/array/array.cpp + * :language: c++ + * :dedent: 8 + * :start-after: push-back/start + * :end-before: push-back/end + * + * @endrst + */ + array push_back(value_type value) const& + { return impl_.push_back(std::move(value)); } + + decltype(auto) push_back(value_type value) && + { return push_back_move(move_t{}, std::move(value)); } + + /*! + * Returns an array containing value `value` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is @f$ O(size) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/array/array.cpp + * :language: c++ + * :dedent: 8 + * :start-after: set/start + * :end-before: set/end + * + * @endrst + */ + array set(std::size_t index, value_type value) const& + { return impl_.assoc(index, std::move(value)); } + + decltype(auto) set(size_type index, value_type value) && + { return set_move(move_t{}, index, std::move(value)); } + + /*! + * Returns an array containing the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is @f$ O(size) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/array/array.cpp + * :language: c++ + * :dedent: 8 + * :start-after: update/start + * :end-before: update/end + * + * @endrst + */ + template + array update(std::size_t index, FnT&& fn) const& + { return impl_.update(index, std::forward(fn)); } + + template + decltype(auto) update(size_type index, FnT&& fn) && + { return update_move(move_t{}, index, std::forward(fn)); } + + /*! + * Returns a array containing only the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/array/array.cpp + * :language: c++ + * :dedent: 8 + * :start-after: take/start + * :end-before: take/end + * + * @endrst + */ + array take(size_type elems) const& + { return impl_.take(elems); } + + decltype(auto) take(size_type elems) && + { return take_move(move_t{}, elems); } + + /*! + * Returns an @a transient form of this container, an + * `immer::array_transient`. + */ + transient_type transient() const& + { return transient_type{ impl_ }; } + transient_type transient() && + { return transient_type{ std::move(impl_) }; } + + // Semi-private + const impl_t& impl() const { return impl_; } + +private: + friend transient_type; + + array(impl_t impl) : impl_(std::move(impl)) {} + + array&& push_back_move(std::true_type, value_type value) + { impl_.push_back_mut({}, std::move(value)); return std::move(*this); } + array push_back_move(std::false_type, value_type value) + { return impl_.push_back(std::move(value)); } + + array&& set_move(std::true_type, size_type index, value_type value) + { impl_.assoc_mut({}, index, std::move(value)); return std::move(*this); } + array set_move(std::false_type, size_type index, value_type value) + { return impl_.assoc(index, std::move(value)); } + + template + array&& update_move(std::true_type, size_type index, Fn&& fn) + { impl_.update_mut({}, index, std::forward(fn)); return std::move(*this); } + template + array update_move(std::false_type, size_type index, Fn&& fn) + { return impl_.update(index, std::forward(fn)); } + + array&& take_move(std::true_type, size_type elems) + { impl_.take_mut({}, elems); return std::move(*this); } + array take_move(std::false_type, size_type elems) + { return impl_.take(elems); } + + impl_t impl_ = impl_t::empty(); +}; + +} /* namespace immer */ diff --git a/src/immer/array_transient.hpp b/src/immer/array_transient.hpp new file mode 100644 index 000000000000..0084e47dddc8 --- /dev/null +++ b/src/immer/array_transient.hpp @@ -0,0 +1,187 @@ +// +// 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 + +namespace immer { + +template +class array; + +/*! + * Mutable version of `immer::array`. + * + * @rst + * + * Refer to :doc:`transients` to learn more about when and how to use + * the mutable versions of immutable containers. + * + * @endrst + */ +template +class array_transient + : MemoryPolicy::transience_t::owner +{ + using impl_t = detail::arrays::with_capacity; + using impl_no_capacity_t = detail::arrays::no_capacity; + using owner_t = typename MemoryPolicy::transience_t::owner; + +public: + using value_type = T; + using reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = const T*; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using memory_policy = MemoryPolicy; + using persistent_type = array; + + /*! + * Default constructor. It creates a mutable array of `size() == + * 0`. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + array_transient() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return impl_.data(); } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return impl_.data() + impl_.size; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + std::size_t size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.d->empty(); } + + /*! + * Access the raw data. + */ + const T* data() const { return impl_.data(); } + + /*! + * Access the last element. + */ + const T& back() const { return data()[size() - 1]; } + + /*! + * Access the first element. + */ + const T& front() const { return data()[0]; } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Inserts `value` at the end. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + void push_back(value_type value) + { impl_.push_back_mut(*this, std::move(value)); } + + /*! + * Sets to the value `value` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void set(size_type index, value_type value) + { impl_.assoc_mut(*this, index, std::move(value)); } + + /*! + * Updates the array to contain the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `0 >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + void update(size_type index, FnT&& fn) + { impl_.update_mut(*this, index, std::forward(fn)); } + + /*! + * Resizes the array to only contain the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void take(size_type elems) + { impl_.take_mut(*this, elems); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::array`. + */ + persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return persistent_type{ impl_ }; + } + persistent_type persistent() && + { return persistent_type{ std::move(impl_) }; } + +private: + friend persistent_type; + + array_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty; +}; + +} // namespace immer diff --git a/src/immer/atom.hpp b/src/immer/atom.hpp new file mode 100644 index 000000000000..206f3c497a09 --- /dev/null +++ b/src/immer/atom.hpp @@ -0,0 +1,259 @@ +// +// 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 + +namespace immer { + +namespace detail { + +template +struct refcount_atom_impl +{ + using box_type = box; + using value_type = T; + using memory_policy = MemoryPolicy; + using spinlock_t = typename MemoryPolicy::refcount::spinlock_type; + using scoped_lock_t = typename spinlock_t::scoped_lock; + + refcount_atom_impl(const refcount_atom_impl&) = delete; + refcount_atom_impl(refcount_atom_impl&&) = delete; + refcount_atom_impl& operator=(const refcount_atom_impl&) = delete; + refcount_atom_impl& operator=(refcount_atom_impl&&) = delete; + + refcount_atom_impl(box_type b) + : impl_{std::move(b)} + {} + + box_type load() const + { + scoped_lock_t lock{lock_}; + return impl_; + } + + void store(box_type b) + { + scoped_lock_t lock{lock_}; + impl_ = std::move(b); + } + + box_type exchange(box_type b) + { + { + scoped_lock_t lock{lock_}; + swap(b, impl_); + } + return std::move(b); + } + + template + box_type update(Fn&& fn) + { + while (true) { + auto oldv = load(); + auto newv = oldv.update(fn); + { + scoped_lock_t lock{lock_}; + if (oldv.impl_ == impl_.impl_) { + impl_ = newv; + return { newv }; + } + } + } + } + +private: + mutable spinlock_t lock_; + box_type impl_; +}; + +template +struct gc_atom_impl +{ + using box_type = box; + using value_type = T; + using memory_policy = MemoryPolicy; + + static_assert( + std::is_same::value, + "gc_atom_impl can only be used when there is no refcount!"); + + gc_atom_impl(const gc_atom_impl&) = delete; + gc_atom_impl(gc_atom_impl&&) = delete; + gc_atom_impl& operator=(const gc_atom_impl&) = delete; + gc_atom_impl& operator=(gc_atom_impl&&) = delete; + + gc_atom_impl(box_type b) + : impl_{b.impl_} + {} + + box_type load() const + { return {impl_.load()}; } + + void store(box_type b) + { impl_.store(b.impl_); } + + box_type exchange(box_type b) + { return {impl_.exchange(b.impl_)}; } + + template + box_type update(Fn&& fn) + { + while (true) { + auto oldv = box_type{impl_.load()}; + auto newv = oldv.update(fn); + if (impl_.compare_exchange_weak(oldv.impl_, newv.impl_)) + return { newv }; + } + } + +private: + std::atomic impl_; +}; + +} // namespace detail + +/*! + * Stores for boxed values of type `T` in a thread-safe manner. + * + * @see box + * + * @rst + * + * .. warning:: If memory policy used includes thread unsafe reference counting, + * no no thread safety is assumed, and the atom becomes thread unsafe too! + * + * .. note:: ``box`` provides a value based box of type ``T``, this is, we can + * think about it as a value-based version of ``std::shared_ptr``. In a + * similar fashion, ``atom`` is in spirit the value-based equivalent of + * C++20 ``std::atomic_shared_ptr``. However, the API does not follow + * ``std::atomic`` interface closely, since it attempts to be a higher level + * construction, most similar to Clojure's ``(atom)``. It is remarkable in + * particular that, since ``box`` underlying object is immutable, using + * ``atom`` is fully thread-safe in ways that ``std::atmic_shared_ptr`` is + * not. This is so because dereferencing the underlying pointer in a + * ``std::atomic_share_ptr`` may require further synchronization, in particular + * when invoking non-const methods. + * + * @endrst + */ +template +class atom +{ +public: + using box_type = box; + using value_type = T; + using memory_policy = MemoryPolicy; + + atom(const atom&) = delete; + atom(atom&&) = delete; + void operator=(const atom&) = delete; + void operator=(atom&&) = delete; + + /*! + * Constructs an atom holding a value `b`; + */ + atom(box_type v={}) + : impl_{std::move(v)} + {} + + /*! + * Sets a new value in the atom. + */ + atom& operator=(box_type b) + { + impl_.store(std::move(b)); + return *this; + } + + /*! + * Reads the currently stored value in a thread-safe manner. + */ + operator box_type() const + { return impl_.load(); } + + /*! + * Reads the currently stored value in a thread-safe manner. + */ + operator value_type() const + { return *impl_.load(); } + + /*! + * Reads the currently stored value in a thread-safe manner. + */ + box_type load() const + { return impl_.load(); } + + /*! + * Stores a new value in a thread-safe manner. + */ + void store(box_type b) + { impl_.store(std::move(b)); } + + /*! + * Stores a new value and returns the old value, in a thread-safe manner. + */ + box_type exchange(box_type b) + { return impl_.exchange(std::move(b)); } + + /*! + * Stores the result of applying `fn` to the current value atomically and + * returns the new resulting value. + * + * @rst + * + * .. warning:: ``fn`` must be a pure function and have no side effects! The + * function might be evaluated multiple times when multiple threads + * content to update the value. + * + * @endrst + */ + template + box_type update(Fn&& fn) + { return impl_.update(std::forward(fn)); } + +private: + struct get_refcount_atom_impl + { + template + struct apply + { + using type = detail::refcount_atom_impl; + }; + }; + + struct get_gc_atom_impl + { + template + struct apply + { + using type = detail::gc_atom_impl; + }; + }; + + // If we are using "real" garbage collection (we assume this when we use + // `no_refcount_policy`), we just store the pointer in an atomic. If we use + // reference counting, we rely on the reference counting spinlock. + using impl_t = typename std::conditional_t< + std::is_same::value, + get_gc_atom_impl, + get_refcount_atom_impl + >::template apply::type; + + impl_t impl_; +}; + +} diff --git a/src/immer/box.hpp b/src/immer/box.hpp new file mode 100644 index 000000000000..b8ad19e1a499 --- /dev/null +++ b/src/immer/box.hpp @@ -0,0 +1,172 @@ +// +// 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 + +namespace immer { + +namespace detail { + +template +struct gc_atom_impl; + +template +struct refcount_atom_impl; + +} // namespace detail + +/*! + * Immutable box for a single value of type `T`. + * + * The box is always copiable and movable. The `T` copy or move + * operations are never called. Since a box is immutable, copying or + * moving just copy the underlying pointers. + */ +template +class box +{ + friend struct detail::gc_atom_impl; + friend struct detail::refcount_atom_impl; + + struct holder : MemoryPolicy::refcount + { + T value; + + template + holder(Args&&... args) : value{std::forward(args)...} {} + }; + + using heap = typename MemoryPolicy::heap::type; + + holder* impl_ = nullptr; + + box(holder* impl) : impl_{impl} {} + +public: + using value_type = T; + using memory_policy = MemoryPolicy; + + /*! + * Constructs a box holding `T{}`. + */ + box() : impl_{detail::make()} {} + + /*! + * Constructs a box holding `T{arg}` + */ + template >::value && + std::is_constructible::value>> + box(Arg&& arg) + : impl_{detail::make(std::forward(arg))} {} + + /*! + * Constructs a box holding `T{arg1, arg2, args...}` + */ + template + box(Arg1&& arg1, Arg2&& arg2, Args&& ...args) + : impl_{detail::make( + std::forward(arg1), + std::forward(arg2), + std::forward(args)...)} + {} + + friend void swap(box& a, box& b) + { using std::swap; swap(a.impl_, b.impl_); } + + box(box&& other) { swap(*this, other); } + box(const box& other) : impl_(other.impl_) { impl_->inc(); } + box& operator=(box&& other) { swap(*this, other); return *this; } + box& operator=(const box& other) + { + auto aux = other; + swap(*this, aux); + return *this; + } + ~box() + { + if (impl_ && impl_->dec()) { + impl_->~holder(); + heap::deallocate(sizeof(holder), impl_); + } + } + + /*! Query the current value. */ + const T& get() const { return impl_->value; } + + /*! Conversion to the boxed type. */ + operator const T&() const { return get(); } + + /*! Access via dereference */ + const T& operator* () const { return get(); } + + /*! Access via pointer member access */ + const T* operator-> () const { return &get(); } + + /*! Comparison. */ + bool operator==(detail::exact_t other) const + { return impl_ == other.value.impl_ || get() == other.value.get(); } + // Note that the `exact_t` disambiguates comparisons against `T{}` + // directly. In that case we want to use `operator T&` and + // compare directly. We definitely never want to convert a value + // to a box (which causes an allocation) just to compare it. + bool operator!=(detail::exact_t other) const + { return !(*this == other.value); } + bool operator<(detail::exact_t other) const + { return get() < other.value.get(); } + + /*! + * Returns a new box built by applying the `fn` to the underlying + * value. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/box/box.cpp + * :language: c++ + * :dedent: 8 + * :start-after: update/start + * :end-before: update/end + * + * @endrst + */ + template + box update(Fn&& fn) const& + { + return std::forward(fn)(get()); + } + template + box&& update(Fn&& fn) && + { + if (impl_->unique()) + impl_->value = std::forward(fn)(std::move(impl_->value)); + else + *this = std::forward(fn)(impl_->value); + return std::move(*this); + } +}; + +} // namespace immer + +namespace std { + +template +struct hash> +{ + std::size_t operator() (const immer::box& x) const + { + return std::hash{}(*x); + } +}; + +} // namespace std diff --git a/src/immer/config.hpp b/src/immer/config.hpp new file mode 100644 index 000000000000..67697ef36b07 --- /dev/null +++ b/src/immer/config.hpp @@ -0,0 +1,66 @@ +// +// 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 + +#ifndef IMMER_DEBUG_TRACES +#define IMMER_DEBUG_TRACES 0 +#endif + +#ifndef IMMER_DEBUG_PRINT +#define IMMER_DEBUG_PRINT 0 +#endif + +#ifndef IMMER_DEBUG_DEEP_CHECK +#define IMMER_DEBUG_DEEP_CHECK 0 +#endif + +#if IMMER_DEBUG_TRACES || IMMER_DEBUG_PRINT +#include +#include +#endif + +#if IMMER_DEBUG_TRACES +#define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl +#else +#define IMMER_TRACE(...) +#endif +#define IMMER_TRACE_F(...) \ + IMMER_TRACE(__FILE__ << ":" << __LINE__ << ": " << __VA_ARGS__) +#define IMMER_TRACE_E(expr) \ + IMMER_TRACE(" " << #expr << " = " << (expr)) + +#if defined(_MSC_VER) +#define IMMER_UNREACHABLE __assume(false) +#define IMMER_LIKELY(cond) cond +#define IMMER_UNLIKELY(cond) cond +#define IMMER_FORCEINLINE __forceinline +#define IMMER_PREFETCH(p) +#else +#define IMMER_UNREACHABLE __builtin_unreachable() +#define IMMER_LIKELY(cond) __builtin_expect(!!(cond), 1) +#define IMMER_UNLIKELY(cond) __builtin_expect(!!(cond), 0) +#define IMMER_FORCEINLINE inline __attribute__ ((always_inline)) +#define IMMER_PREFETCH(p) +// #define IMMER_PREFETCH(p) __builtin_prefetch(p) +#endif + +#define IMMER_DESCENT_DEEP 0 + +#ifdef NDEBUG +#define IMMER_ENABLE_DEBUG_SIZE_HEAP 0 +#else +#define IMMER_ENABLE_DEBUG_SIZE_HEAP 1 +#endif + +namespace immer { + +const auto default_bits = 5; +const auto default_free_list_size = 1 << 10; + +} // namespace immer diff --git a/src/immer/detail/arrays/no_capacity.hpp b/src/immer/detail/arrays/no_capacity.hpp new file mode 100644 index 000000000000..7f8096a7b670 --- /dev/null +++ b/src/immer/detail/arrays/no_capacity.hpp @@ -0,0 +1,197 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace arrays { + +template +struct no_capacity +{ + using node_t = node; + using edit_t = typename MemoryPolicy::transience_t::edit; + using size_t = std::size_t; + + node_t* ptr; + size_t size; + + static const no_capacity& empty() + { + static const no_capacity empty_ { + node_t::make_n(0), + 0, + }; + return empty_; + } + + no_capacity(node_t* p, size_t s) + : ptr{p}, size{s} + {} + + no_capacity(const no_capacity& other) + : no_capacity{other.ptr, other.size} + { + inc(); + } + + no_capacity(no_capacity&& other) + : no_capacity{empty()} + { + swap(*this, other); + } + + no_capacity& operator=(const no_capacity& other) + { + auto next = other; + swap(*this, next); + return *this; + } + + no_capacity& operator=(no_capacity&& other) + { + swap(*this, other); + return *this; + } + + friend void swap(no_capacity& x, no_capacity& y) + { + using std::swap; + swap(x.ptr, y.ptr); + swap(x.size, y.size); + } + + ~no_capacity() + { + dec(); + } + + void inc() + { + using immer::detail::get; + ptr->refs().inc(); + } + + void dec() + { + using immer::detail::get; + if (ptr->refs().dec()) + node_t::delete_n(ptr, size, size); + } + + T* data() { return ptr->data(); } + const T* data() const { return ptr->data(); } + + template + && compatible_sentinel_v, bool> = true> + static no_capacity from_range(Iter first, Sent last) + { + auto count = static_cast(distance(first, last)); + return { + node_t::copy_n(count, first, last), + count, + }; + } + + static no_capacity from_fill(size_t n, T v) + { + return { node_t::fill_n(n, v), n }; + } + + template + static no_capacity from_initializer_list(std::initializer_list values) + { + using namespace std; + return from_range(begin(values), end(values)); + } + + template + void for_each_chunk(Fn&& fn) const + { + std::forward(fn)(data(), data() + size); + } + + template + bool for_each_chunk_p(Fn&& fn) const + { + return std::forward(fn)(data(), data() + size); + } + + const T& get(std::size_t index) const + { + return data()[index]; + } + + const T& get_check(std::size_t index) const + { + if (index >= size) + throw std::out_of_range{"out of range"}; + return data()[index]; + } + + bool equals(const no_capacity& other) const + { + return ptr == other.ptr || + (size == other.size && + std::equal(data(), data() + size, other.data())); + } + + no_capacity push_back(T value) const + { + auto p = node_t::copy_n(size + 1, ptr, size); + try { + new (p->data() + size) T{std::move(value)}; + return { p, size + 1 }; + } catch (...) { + node_t::delete_n(p, size, size + 1); + throw; + } + } + + no_capacity assoc(std::size_t idx, T value) const + { + auto p = node_t::copy_n(size, ptr, size); + try { + p->data()[idx] = std::move(value); + return { p, size }; + } catch (...) { + node_t::delete_n(p, size, size); + throw; + } + } + + template + no_capacity update(std::size_t idx, Fn&& op) const + { + auto p = node_t::copy_n(size, ptr, size); + try { + auto& elem = p->data()[idx]; + elem = std::forward(op)(std::move(elem)); + return { p, size }; + } catch (...) { + node_t::delete_n(p, size, size); + throw; + } + } + + no_capacity take(std::size_t sz) const + { + auto p = node_t::copy_n(sz, ptr, sz); + return { p, sz }; + } +}; + +} // namespace arrays +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/arrays/node.hpp b/src/immer/detail/arrays/node.hpp new file mode 100644 index 000000000000..7883d167f2f4 --- /dev/null +++ b/src/immer/detail/arrays/node.hpp @@ -0,0 +1,132 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace arrays { + +template +struct node +{ + using memory = MemoryPolicy; + using heap = typename MemoryPolicy::heap::type; + using transience = typename memory::transience_t; + using refs_t = typename memory::refcount; + using ownee_t = typename transience::ownee; + using node_t = node; + using edit_t = typename transience::edit; + + struct data_t + { + aligned_storage_for buffer; + }; + + using impl_t = combine_standard_layout_t; + + impl_t impl; + + constexpr static std::size_t sizeof_n(size_t count) + { + return immer_offsetof(impl_t, d.buffer) + sizeof(T) * count; + } + + refs_t& refs() const + { + return auto_const_cast(get(impl)); + } + + const ownee_t& ownee() const { return get(impl); } + ownee_t& ownee() { return get(impl); } + + const T* data() const { return reinterpret_cast(&impl.d.buffer); } + T* data() { return reinterpret_cast(&impl.d.buffer); } + + bool can_mutate(edit_t e) const + { + return refs().unique() + || ownee().can_mutate(e); + } + + static void delete_n(node_t* p, size_t sz, size_t cap) + { + destroy_n(p->data(), sz); + heap::deallocate(sizeof_n(cap), p); + } + + + static node_t* make_n(size_t n) + { + return new (heap::allocate(sizeof_n(n))) node_t{}; + } + + static node_t* make_e(edit_t e, size_t n) + { + auto p = make_n(n); + p->ownee() = e; + return p; + } + + static node_t* fill_n(size_t n, T v) + { + auto p = make_n(n); + try { + std::uninitialized_fill_n(p->data(), n, v); + return p; + } catch (...) { + heap::deallocate(sizeof_n(n), p); + throw; + } + } + + template , bool> = true> + static node_t* copy_n(size_t n, Iter first, Sent last) + { + auto p = make_n(n); + try { + uninitialized_copy(first, last, p->data()); + return p; + } catch (...) { + heap::deallocate(sizeof_n(n), p); + throw; + } + } + + static node_t* copy_n(size_t n, node_t* p, size_t count) + { + return copy_n(n, p->data(), p->data() + count); + } + + template + static node_t* copy_e(edit_t e, size_t n, Iter first, Iter last) + { + auto p = copy_n(n, first, last); + p->ownee() = e; + return p; + } + + static node_t* copy_e(edit_t e, size_t n, node_t* p, size_t count) + { + return copy_e(e, n, p->data(), p->data() + count); + } +}; + +} // namespace arrays +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/arrays/with_capacity.hpp b/src/immer/detail/arrays/with_capacity.hpp new file mode 100644 index 000000000000..d408b38aa3a5 --- /dev/null +++ b/src/immer/detail/arrays/with_capacity.hpp @@ -0,0 +1,301 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace arrays { + +template +struct with_capacity +{ + using no_capacity_t = no_capacity; + + using node_t = node; + using edit_t = typename MemoryPolicy::transience_t::edit; + using size_t = std::size_t; + + node_t* ptr; + size_t size; + size_t capacity; + + static const with_capacity& empty() + { + static const with_capacity empty_ { + node_t::make_n(1), + 0, + 1 + }; + return empty_; + } + + with_capacity(node_t* p, size_t s, size_t c) + : ptr{p}, size{s}, capacity{c} + {} + + with_capacity(const with_capacity& other) + : with_capacity{other.ptr, other.size, other.capacity} + { + inc(); + } + + with_capacity(const no_capacity_t& other) + : with_capacity{other.ptr, other.size, other.size} + { + inc(); + } + + with_capacity(with_capacity&& other) + : with_capacity{empty()} + { + swap(*this, other); + } + + with_capacity& operator=(const with_capacity& other) + { + auto next = other; + swap(*this, next); + return *this; + } + + with_capacity& operator=(with_capacity&& other) + { + swap(*this, other); + return *this; + } + + friend void swap(with_capacity& x, with_capacity& y) + { + using std::swap; + swap(x.ptr, y.ptr); + swap(x.size, y.size); + swap(x.capacity, y.capacity); + } + + ~with_capacity() + { + dec(); + } + + void inc() + { + using immer::detail::get; + ptr->refs().inc(); + } + + void dec() + { + using immer::detail::get; + if (ptr->refs().dec()) + node_t::delete_n(ptr, size, capacity); + } + + const T* data() const { return ptr->data(); } + T* data() { return ptr->data(); } + + operator no_capacity_t() const + { + if (size == capacity) { + ptr->refs().inc(); + return { ptr, size }; + } else { + return { node_t::copy_n(size, ptr, size), size }; + } + } + + template + && compatible_sentinel_v, bool> = true> + static with_capacity from_range(Iter first, Sent last) + { + auto count = static_cast(distance(first, last)); + return { + node_t::copy_n(count, first, last), + count, + count + }; + } + + template + static with_capacity from_initializer_list(std::initializer_list values) + { + using namespace std; + return from_range(begin(values), end(values)); + } + + static with_capacity from_fill(size_t n, T v) + { + return { node_t::fill_n(n, v), n, n }; + } + + template + void for_each_chunk(Fn&& fn) const + { + std::forward(fn)(data(), data() + size); + } + + template + bool for_each_chunk_p(Fn&& fn) const + { + return std::forward(fn)(data(), data() + size); + } + + const T& get(std::size_t index) const + { + return data()[index]; + } + + const T& get_check(std::size_t index) const + { + if (index >= size) + throw std::out_of_range{"out of range"}; + return data()[index]; + } + + bool equals(const with_capacity& other) const + { + return ptr == other.ptr || + (size == other.size && + std::equal(data(), data() + size, other.data())); + } + + static size_t recommend_up(size_t sz, size_t cap) + { + auto max = std::numeric_limits::max(); + return + sz <= cap ? cap : + cap >= max / 2 ? max + /* otherwise */ : std::max(2 * cap, sz); + } + + static size_t recommend_down(size_t sz, size_t cap) + { + return sz == 0 ? 1 : + sz < cap / 2 ? sz * 2 : + /* otherwise */ cap; + } + + with_capacity push_back(T value) const + { + auto cap = recommend_up(size + 1, capacity); + auto p = node_t::copy_n(cap, ptr, size); + try { + new (p->data() + size) T{std::move(value)}; + return { p, size + 1, cap }; + } catch (...) { + node_t::delete_n(p, size, cap); + throw; + } + } + + void push_back_mut(edit_t e, T value) + { + if (ptr->can_mutate(e) && capacity > size) { + new (data() + size) T{std::move(value)}; + ++size; + } else { + auto cap = recommend_up(size + 1, capacity); + auto p = node_t::copy_e(e, cap, ptr, size); + try { + new (p->data() + size) T{std::move(value)}; + *this = { p, size + 1, cap }; + } catch (...) { + node_t::delete_n(p, size, cap); + throw; + } + } + } + + with_capacity assoc(std::size_t idx, T value) const + { + auto p = node_t::copy_n(capacity, ptr, size); + try { + p->data()[idx] = std::move(value); + return { p, size, capacity }; + } catch (...) { + node_t::delete_n(p, size, capacity); + throw; + } + } + + void assoc_mut(edit_t e, std::size_t idx, T value) + { + if (ptr->can_mutate(e)) { + data()[idx] = std::move(value); + } else { + auto p = node_t::copy_n(capacity, ptr, size); + try { + p->data()[idx] = std::move(value); + *this = { p, size, capacity }; + } catch (...) { + node_t::delete_n(p, size, capacity); + throw; + } + } + } + + template + with_capacity update(std::size_t idx, Fn&& op) const + { + auto p = node_t::copy_n(capacity, ptr, size); + try { + auto& elem = p->data()[idx]; + elem = std::forward(op)(std::move(elem)); + return { p, size, capacity }; + } catch (...) { + node_t::delete_n(p, size, capacity); + throw; + } + } + + template + void update_mut(edit_t e, std::size_t idx, Fn&& op) + { + if (ptr->can_mutate(e)) { + auto& elem = data()[idx]; + elem = std::forward(op)(std::move(elem)); + } else { + auto p = node_t::copy_e(e, capacity, ptr, size); + try { + auto& elem = p->data()[idx]; + elem = std::forward(op)(std::move(elem)); + *this = { p, size, capacity }; + } catch (...) { + node_t::delete_n(p, size, capacity); + throw; + } + } + } + + with_capacity take(std::size_t sz) const + { + auto cap = recommend_down(sz, capacity); + auto p = node_t::copy_n(cap, ptr, sz); + return { p, sz, cap }; + } + + void take_mut(edit_t e, std::size_t sz) + { + if (ptr->can_mutate(e)) { + destroy_n(data() + size, size - sz); + size = sz; + } else { + auto cap = recommend_down(sz, capacity); + auto p = node_t::copy_e(e, cap, ptr, sz); + *this = { p, sz, cap }; + } + } +}; + +} // namespace arrays +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/combine_standard_layout.hpp b/src/immer/detail/combine_standard_layout.hpp new file mode 100644 index 000000000000..be8e698accb2 --- /dev/null +++ b/src/immer/detail/combine_standard_layout.hpp @@ -0,0 +1,196 @@ +// +// 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 + +#if __GNUC__ == 7 || __GNUC_MINOR__ == 1 +#define IMMER_BROKEN_STANDARD_LAYOUT_DETECTION 1 +#define immer_offsetof(st, m) ((std::size_t) &(((st*)0)->m)) +#else +#define IMMER_BROKEN_STANDARD_LAYOUT_DETECTION 0 +#define immer_offsetof offsetof +#endif + +namespace immer { +namespace detail { + +// +// Metafunction that returns a standard layout struct that combines +// all the standard layout types in `Ts...`, while making sure that +// empty base optimizations are used. +// +// To query a part of the type do `get(x)`; +// +// This is useful when putting together a type that merges various +// types coming from different policies. Some of them might be empty, +// so we shall enable empty base optimizations. But if we just +// inherit from all of them, we would break the "standard layout" +// rules, preventing us from using `offseof(...)`. So metafunction +// will generate the type by sometimes inheriting, sometimes adding as +// member. +// +// Note that the types are added to the combined type from right to +// left! +// +template +struct combine_standard_layout; + +template +using combine_standard_layout_t = typename combine_standard_layout::type; + +namespace csl { + +template +struct type_t {}; + +template +U& get(T& x); + +template +const U& get(const T& x); + +template +struct inherit +{ + struct type : T, Next + { + using Next::get_; + + template + friend decltype(auto) get(type& x) { return x.get_(type_t{}); } + template + friend decltype(auto) get(const type& x) { return x.get_(type_t{}); } + + T& get_(type_t) { return *this; } + const T& get_(type_t) const { return *this; } + }; +}; + +template +struct inherit +{ + struct type : T + { + template + friend decltype(auto) get(type& x) { return x.get_(type_t{}); } + template + friend decltype(auto) get(const type& x) { return x.get_(type_t{}); } + + T& get_(type_t) { return *this; } + const T& get_(type_t) const { return *this; } + }; +}; + +template +struct member +{ + struct type : Next + { + T d; + + using Next::get_; + + template + friend decltype(auto) get(type& x) { return x.get_(type_t{}); } + template + friend decltype(auto) get(const type& x) { return x.get_(type_t{}); } + + T& get_(type_t) { return d; } + const T& get_(type_t) const { return d; } + }; +}; + +template +struct member +{ + struct type + { + T d; + + template + friend decltype(auto) get(type& x) { return x.get_(type_t{}); } + template + friend decltype(auto) get(const type& x) { return x.get_(type_t{}); } + + T& get_(type_t) { return d; } + const T& get_(type_t) const { return d; } + }; +}; + +template +struct member_two +{ + struct type + { + Next n; + T d; + + template + friend decltype(auto) get(type& x) { return x.get_(type_t{}); } + template + friend decltype(auto) get(const type& x) { return x.get_(type_t{}); } + + T& get_(type_t) { return d; } + const T& get_(type_t) const { return d; } + + template + auto get_(type_t t) -> decltype(auto) { return n.get_(t); } + template + auto get_(type_t t) const -> decltype(auto) { return n.get_(t); } + }; +}; + +template +struct combine_standard_layout_aux; + +template +struct combine_standard_layout_aux +{ + static_assert(std::is_standard_layout::value, ""); + + using type = typename std::conditional_t< + std::is_empty::value, + csl::inherit, + csl::member>::type; +}; + +template +struct combine_standard_layout_aux +{ + static_assert(std::is_standard_layout::value, ""); + + using this_t = T; + using next_t = typename combine_standard_layout_aux::type; + + static constexpr auto empty_this = std::is_empty::value; + static constexpr auto empty_next = std::is_empty::value; + + using type = typename std::conditional_t< + empty_this, inherit, + std::conditional_t< + empty_next, member, + member_two>>::type; +}; + +} // namespace csl + +using csl::get; + +template +struct combine_standard_layout +{ + using type = typename csl::combine_standard_layout_aux::type; +#if !IMMER_BROKEN_STANDARD_LAYOUT_DETECTION + static_assert(std::is_standard_layout::value, ""); +#endif +}; + +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/hamts/bits.hpp b/src/immer/detail/hamts/bits.hpp new file mode 100644 index 000000000000..92c7e45cf2ef --- /dev/null +++ b/src/immer/detail/hamts/bits.hpp @@ -0,0 +1,101 @@ +// +// 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 + +#if defined(_MSC_VER) +#include // __popcnt +#endif + +namespace immer { +namespace detail { +namespace hamts { + +using size_t = std::size_t; +using hash_t = std::size_t; +using bits_t = std::uint32_t; +using count_t = std::uint32_t; +using shift_t = std::uint32_t; + +template +struct get_bitmap_type +{ + static_assert(B < 6u, "B > 6 is not supported."); + + using type = std::uint32_t; +}; + +template <> +struct get_bitmap_type<6u> +{ + using type = std::uint64_t; +}; + +template +constexpr T branches = T{1u} << B; + +template +constexpr T mask = branches - 1u; + +template +constexpr T max_depth = (sizeof(hash_t) * 8u + B - 1u) / B; + +template +constexpr T max_shift = max_depth * B; + +#define IMMER_HAS_BUILTIN_POPCOUNT 1 + +inline auto popcount_fallback(std::uint32_t x) +{ + // More alternatives: + // https://en.wikipedia.org/wiki/Hamming_weight + // http://wm.ite.pl/articles/sse-popcount.html + // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetParallel + x = x - ((x >> 1) & 0x55555555u); + x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u); + return (((x + (x >> 4u)) & 0xF0F0F0Fu) * 0x1010101u) >> 24u; +} + +inline auto popcount_fallback(std::uint64_t x) +{ + x = x - ((x >> 1) & 0x5555555555555555u); + x = (x & 0x3333333333333333u) + ((x >> 2u) & 0x3333333333333333u); + return (((x + (x >> 4)) & 0x0F0F0F0F0F0F0F0Fu) * 0x0101010101010101u) >> 56u; +} + +inline count_t popcount(std::uint32_t x) +{ +#if IMMER_HAS_BUILTIN_POPCOUNT +# if defined(_MSC_VER) + return __popcnt(x); +# else + return __builtin_popcount(x); +# endif +#else + return popcount_fallback(x); +#endif +} + +inline count_t popcount(std::uint64_t x) +{ +#if IMMER_HAS_BUILTIN_POPCOUNT +# if defined(_MSC_VER) + return __popcnt64(x); +# else + return __builtin_popcountll(x); +# endif +#else + return popcount_fallback(x); +#endif +} + +} // namespace hamts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/hamts/champ.hpp b/src/immer/detail/hamts/champ.hpp new file mode 100644 index 000000000000..d3687ffbee18 --- /dev/null +++ b/src/immer/detail/hamts/champ.hpp @@ -0,0 +1,476 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace hamts { + +template +struct champ +{ + static constexpr auto bits = B; + + using node_t = node; + using bitmap_t = typename get_bitmap_type::type; + + static_assert(branches <= sizeof(bitmap_t) * 8, ""); + + node_t* root; + size_t size; + + static const champ& empty() + { + static const champ empty_ { + node_t::make_inner_n(0), + 0, + }; + return empty_; + } + + champ(node_t* r, size_t sz) + : root{r}, size{sz} + { + } + + champ(const champ& other) + : champ{other.root, other.size} + { + inc(); + } + + champ(champ&& other) + : champ{empty()} + { + swap(*this, other); + } + + champ& operator=(const champ& other) + { + auto next = other; + swap(*this, next); + return *this; + } + + champ& operator=(champ&& other) + { + swap(*this, other); + return *this; + } + + friend void swap(champ& x, champ& y) + { + using std::swap; + swap(x.root, y.root); + swap(x.size, y.size); + } + + ~champ() + { + dec(); + } + + void inc() const + { + root->inc(); + } + + void dec() const + { + if (root->dec()) + node_t::delete_deep(root, 0); + } + + template + void for_each_chunk(Fn&& fn) const + { + for_each_chunk_traversal(root, 0, fn); + } + + template + void for_each_chunk_traversal(node_t* node, count_t depth, Fn&& fn) const + { + if (depth < max_depth) { + auto datamap = node->datamap(); + if (datamap) + fn(node->values(), node->values() + popcount(datamap)); + auto nodemap = node->nodemap(); + if (nodemap) { + auto fst = node->children(); + auto lst = fst + popcount(nodemap); + for (; fst != lst; ++fst) + for_each_chunk_traversal(*fst, depth + 1, fn); + } + } else { + fn(node->collisions(), node->collisions() + node->collision_count()); + } + } + + template + decltype(auto) get(const K& k) const + { + auto node = root; + auto hash = Hash{}(k); + for (auto i = count_t{}; i < max_depth; ++i) { + auto bit = bitmap_t{1u} << (hash & mask); + if (node->nodemap() & bit) { + auto offset = popcount(node->nodemap() & (bit - 1)); + node = node->children() [offset]; + hash = hash >> B; + } else if (node->datamap() & bit) { + auto offset = popcount(node->datamap() & (bit - 1)); + auto val = node->values() + offset; + if (Equal{}(*val, k)) + return Project{}(*val); + else + return Default{}(); + } else { + return Default{}(); + } + } + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) + return Project{}(*fst); + return Default{}(); + } + + std::pair + do_add(node_t* node, T v, hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, v)) + return { + node_t::copy_collision_replace(node, fst, std::move(v)), + false + }; + return { + node_t::copy_collision_insert(node, std::move(v)), + true + }; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = popcount(node->nodemap() & (bit - 1)); + auto result = do_add(node->children() [offset], + std::move(v), hash, + shift + B); + try { + result.first = node_t::copy_inner_replace( + node, offset, result.first); + return result; + } catch (...) { + node_t::delete_deep_shift(result.first, shift + B); + throw; + } + } else if (node->datamap() & bit) { + auto offset = popcount(node->datamap() & (bit - 1)); + auto val = node->values() + offset; + if (Equal{}(*val, v)) + return { + node_t::copy_inner_replace_value( + node, offset, std::move(v)), + false + }; + else { + auto child = node_t::make_merged(shift + B, + std::move(v), hash, + *val, Hash{}(*val)); + try { + return { + node_t::copy_inner_replace_merged( + node, bit, offset, child), + true + }; + } catch (...) { + node_t::delete_deep_shift(child, shift + B); + throw; + } + } + } else { + return { + node_t::copy_inner_insert_value(node, bit, std::move(v)), + true + }; + } + } + } + + champ add(T v) const + { + auto hash = Hash{}(v); + auto res = do_add(root, std::move(v), hash, 0); + auto new_size = size + (res.second ? 1 : 0); + return { res.first, new_size }; + } + + template + std::pair + do_update(node_t* node, K&& k, Fn&& fn, + hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) + return { + node_t::copy_collision_replace( + node, fst, Combine{}(std::forward(k), + std::forward(fn)( + Project{}(*fst)))), + false + }; + return { + node_t::copy_collision_insert( + node, Combine{}(std::forward(k), + std::forward(fn)( + Default{}()))), + true + }; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = popcount(node->nodemap() & (bit - 1)); + auto result = do_update( + node->children() [offset], k, std::forward(fn), + hash, shift + B); + try { + result.first = node_t::copy_inner_replace( + node, offset, result.first); + return result; + } catch (...) { + node_t::delete_deep_shift(result.first, shift + B); + throw; + } + } else if (node->datamap() & bit) { + auto offset = popcount(node->datamap() & (bit - 1)); + auto val = node->values() + offset; + if (Equal{}(*val, k)) + return { + node_t::copy_inner_replace_value( + node, offset, Combine{}(std::forward(k), + std::forward(fn)( + Project{}(*val)))), + false + }; + else { + auto child = node_t::make_merged( + shift + B, Combine{}(std::forward(k), + std::forward(fn)( + Default{}())), + hash, *val, Hash{}(*val)); + try { + return { + node_t::copy_inner_replace_merged( + node, bit, offset, child), + true + }; + } catch (...) { + node_t::delete_deep_shift(child, shift + B); + throw; + } + } + } else { + return { + node_t::copy_inner_insert_value( + node, bit, Combine{}(std::forward(k), + std::forward(fn)( + Default{}()))), + true + }; + } + } + } + + template + champ update(const K& k, Fn&& fn) const + { + auto hash = Hash{}(k); + auto res = do_update( + root, k, std::forward(fn), hash, 0); + auto new_size = size + (res.second ? 1 : 0); + return { res.first, new_size }; + } + + // basically: + // variant + // boo bad we are not using... C++17 :'( + struct sub_result + { + enum kind_t + { + nothing, + singleton, + tree + }; + + union data_t + { + T* singleton; + node_t* tree; + }; + + kind_t kind; + data_t data; + + sub_result() : kind{nothing} {}; + sub_result(T* x) : kind{singleton} { data.singleton = x; }; + sub_result(node_t* x) : kind{tree} { data.tree = x; }; + }; + + template + sub_result do_sub(node_t* node, const K& k, hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (auto cur = fst; cur != lst; ++cur) + if (Equal{}(*cur, k)) + return node->collision_count() > 2 + ? node_t::copy_collision_remove(node, cur) + : sub_result{fst + (cur == fst)}; + return {}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = popcount(node->nodemap() & (bit - 1)); + auto result = do_sub(node->children() [offset], + k, hash, shift + B); + switch (result.kind) { + case sub_result::nothing: + return {}; + case sub_result::singleton: + return node->datamap() == 0 && + popcount(node->nodemap()) == 1 && + shift > 0 + ? result + : node_t::copy_inner_replace_inline( + node, bit, offset, *result.data.singleton); + case sub_result::tree: + try { + return node_t::copy_inner_replace(node, offset, + result.data.tree); + } catch (...) { + node_t::delete_deep_shift(result.data.tree, shift + B); + throw; + } + } + } else if (node->datamap() & bit) { + auto offset = popcount(node->datamap() & (bit - 1)); + auto val = node->values() + offset; + if (Equal{}(*val, k)) { + auto nv = popcount(node->datamap()); + if (node->nodemap() || nv > 2) + return node_t::copy_inner_remove_value(node, bit, offset); + else if (nv == 2) { + return shift > 0 + ? sub_result{node->values() + !offset} + : node_t::make_inner_n(0, + node->datamap() & ~bit, + node->values()[!offset]); + } else { + assert(shift == 0); + return empty().root->inc(); + } + } + } + return {}; + } + } + + template + champ sub(const K& k) const + { + auto hash = Hash{}(k); + auto res = do_sub(root, k, hash, 0); + switch (res.kind) { + case sub_result::nothing: + return *this; + case sub_result::tree: + return { + res.data.tree, + size - 1 + }; + default: + IMMER_UNREACHABLE; + } + } + + template + bool equals(const champ& other) const + { + return size == other.size && equals_tree(root, other.root, 0); + } + + template + static bool equals_tree(const node_t* a, const node_t* b, count_t depth) + { + if (a == b) + return true; + else if (depth == max_depth) { + auto nv = a->collision_count(); + return nv == b->collision_count() && + equals_collisions(a->collisions(), b->collisions(), nv); + } else { + if (a->nodemap() != b->nodemap() || + a->datamap() != b->datamap()) + return false; + auto n = popcount(a->nodemap()); + for (auto i = count_t{}; i < n; ++i) + if (!equals_tree(a->children()[i], b->children()[i], depth + 1)) + return false; + auto nv = popcount(a->datamap()); + return !nv || equals_values(a->values(), b->values(), nv); + } + } + + template + static bool equals_values(const T* a, const T* b, count_t n) + { + return std::equal(a, a + n, b, Eq{}); + } + + template + static bool equals_collisions(const T* a, const T* b, count_t n) + { + auto ae = a + n; + auto be = b + n; + for (; a != ae; ++a) { + for (auto fst = b; fst != be; ++fst) + if (Eq{}(*a, *fst)) + goto good; + return false; + good: continue; + } + return true; + } +}; + +} // namespace hamts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/hamts/champ_iterator.hpp b/src/immer/detail/hamts/champ_iterator.hpp new file mode 100644 index 000000000000..07d552dacab2 --- /dev/null +++ b/src/immer/detail/hamts/champ_iterator.hpp @@ -0,0 +1,153 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace hamts { + +template +struct champ_iterator + : iterator_facade, + std::forward_iterator_tag, + T, + const T&, + std::ptrdiff_t, + const T*> +{ + using tree_t = champ; + using node_t = typename tree_t::node_t; + + struct end_t {}; + + champ_iterator() = default; + + champ_iterator(const tree_t& v) + : depth_ { 0 } + { + if (v.root->datamap()) { + cur_ = v.root->values(); + end_ = v.root->values() + popcount(v.root->datamap()); + } else { + cur_ = end_ = nullptr; + } + path_[0] = &v.root; + ensure_valid_(); + } + + champ_iterator(const tree_t& v, end_t) + : cur_ { nullptr } + , end_ { nullptr } + , depth_ { 0 } + { + path_[0] = &v.root; + } + + champ_iterator(const champ_iterator& other) + : cur_ { other.cur_ } + , end_ { other.end_ } + , depth_ { other.depth_ } + { + std::copy(other.path_, other.path_ + depth_ + 1, path_); + } + +private: + friend iterator_core_access; + + T* cur_; + T* end_; + count_t depth_; + node_t* const* path_[max_depth + 1]; + + void increment() + { + ++cur_; + ensure_valid_(); + } + + bool step_down() + { + if (depth_ < max_depth) { + auto parent = *path_[depth_]; + if (parent->nodemap()) { + ++depth_; + path_[depth_] = parent->children(); + auto child = *path_[depth_]; + if (depth_ < max_depth) { + if (child->datamap()) { + cur_ = child->values(); + end_ = cur_ + popcount(child->datamap()); + } + } else { + cur_ = child->collisions(); + end_ = cur_ + child->collision_count(); + } + return true; + } + } + return false; + } + + bool step_right() + { + while (depth_ > 0) { + auto parent = *path_[depth_ - 1]; + auto last = parent->children() + popcount(parent->nodemap()); + auto next = path_[depth_] + 1; + if (next < last) { + path_[depth_] = next; + auto child = *path_[depth_]; + if (depth_ < max_depth) { + if (child->datamap()) { + cur_ = child->values(); + end_ = cur_ + popcount(child->datamap()); + } + } else { + cur_ = child->collisions(); + end_ = cur_ + child->collision_count(); + } + return true; + } + -- depth_; + } + return false; + } + + void ensure_valid_() + { + while (cur_ == end_) { + while (step_down()) + if (cur_ != end_) + return; + if (!step_right()) { + // end of sequence + assert(depth_ == 0); + cur_ = end_ = nullptr; + return; + } + } + } + + bool equal(const champ_iterator& other) const + { + return cur_ == other.cur_; + } + + const T& dereference() const + { + return *cur_; + } +}; + +} // namespace hamts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/hamts/node.hpp b/src/immer/detail/hamts/node.hpp new file mode 100644 index 000000000000..61aa381336f0 --- /dev/null +++ b/src/immer/detail/hamts/node.hpp @@ -0,0 +1,722 @@ +// +// 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 + +#ifdef NDEBUG +#define IMMER_HAMTS_TAGGED_NODE 0 +#else +#define IMMER_HAMTS_TAGGED_NODE 1 +#endif + +namespace immer { +namespace detail { +namespace hamts { + +template +struct node +{ + using node_t = node; + + using memory = MemoryPolicy; + using heap_policy = typename memory::heap; + using heap = typename heap_policy::type; + using transience = typename memory::transience_t; + using refs_t = typename memory::refcount; + using ownee_t = typename transience::ownee; + using edit_t = typename transience::edit; + using value_t = T; + using bitmap_t = typename get_bitmap_type::type; + + enum class kind_t + { + collision, + inner + }; + + struct collision_t + { + count_t count; + aligned_storage_for buffer; + }; + + struct values_data_t + { + aligned_storage_for buffer; + }; + + using values_t = combine_standard_layout_t< + values_data_t, refs_t>; + + struct inner_t + { + bitmap_t nodemap; + bitmap_t datamap; + values_t* values; + aligned_storage_for buffer; + }; + + union data_t + { + inner_t inner; + collision_t collision; + }; + + struct impl_data_t + { +#if IMMER_HAMTS_TAGGED_NODE + kind_t kind; +#endif + data_t data; + }; + + using impl_t = combine_standard_layout_t< + impl_data_t, refs_t>; + + impl_t impl; + + constexpr static std::size_t sizeof_values_n(count_t count) + { + return immer_offsetof(values_t, d.buffer) + + sizeof(values_data_t::buffer) * count; + } + + constexpr static std::size_t sizeof_collision_n(count_t count) + { + return immer_offsetof(impl_t, d.data.collision.buffer) + + sizeof(collision_t::buffer) * count; + } + + constexpr static std::size_t sizeof_inner_n(count_t count) + { + return immer_offsetof(impl_t, d.data.inner.buffer) + + sizeof(inner_t::buffer) * count; + } + +#if IMMER_HAMTS_TAGGED_NODE + kind_t kind() const + { + return impl.d.kind; + } +#endif + + auto values() + { + assert(kind() == kind_t::inner); + assert(impl.d.data.inner.values); + return (T*) &impl.d.data.inner.values->d.buffer; + } + + auto values() const + { + assert(kind() == kind_t::inner); + assert(impl.d.data.inner.values); + return (const T*) &impl.d.data.inner.values->d.buffer; + } + + auto children() + { + assert(kind() == kind_t::inner); + return (node_t**) &impl.d.data.inner.buffer; + } + + auto children() const + { + assert(kind() == kind_t::inner); + return (const node_t* const*) &impl.d.data.inner.buffer; + } + + auto datamap() const + { + assert(kind() == kind_t::inner); + return impl.d.data.inner.datamap; + } + + auto nodemap() const + { + assert(kind() == kind_t::inner); + return impl.d.data.inner.nodemap; + } + + auto collision_count() const + { + assert(kind() == kind_t::collision); + return impl.d.data.collision.count; + } + + T* collisions() + { + assert(kind() == kind_t::collision); + return (T*)&impl.d.data.collision.buffer; + } + + const T* collisions() const + { + assert(kind() == kind_t::collision); + return (const T*)&impl.d.data.collision.buffer; + } + + static refs_t& refs(const values_t* x) { return auto_const_cast(get(*x)); } + static const ownee_t& ownee(const values_t* x) { return get(*x); } + static ownee_t& ownee(values_t* x) { return get(*x); } + + static refs_t& refs(const node_t* x) { return auto_const_cast(get(x->impl)); } + static const ownee_t& ownee(const node_t* x) { return get(x->impl); } + static ownee_t& ownee(node_t* x) { return get(x->impl); } + + static node_t* make_inner_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_inner_n(n)); + auto p = new (m) node_t; +#if IMMER_HAMTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + p->impl.d.data.inner.nodemap = 0; + p->impl.d.data.inner.datamap = 0; + p->impl.d.data.inner.values = nullptr; + return p; + } + + static node_t* make_inner_n(count_t n, values_t* values) + { + auto p = make_inner_n(n); + if (values) { + p->impl.d.data.inner.values = values; + refs(values).inc(); + } + return p; + } + + static node_t* make_inner_n(count_t n, count_t nv) + { + assert(nv <= branches); + auto p = make_inner_n(n); + if (nv) { + try { + p->impl.d.data.inner.values = + new (heap::allocate(sizeof_values_n(nv))) values_t{}; + } catch (...) { + deallocate_inner(p, n); + throw; + } + } + return p; + } + + static node_t* make_inner_n(count_t n, count_t idx, node_t* child) + { + assert(n >= 1); + auto p = make_inner_n(n); + p->impl.d.data.inner.nodemap = bitmap_t{1u} << idx; + p->children()[0] = child; + return p; + } + + static node_t* make_inner_n(count_t n, + bitmap_t bitmap, + T x) + { + auto p = make_inner_n(n, 1); + p->impl.d.data.inner.datamap = bitmap; + try { + new (p->values()) T{std::move(x)}; + } catch (...) { + deallocate_inner(p, n, 1); + throw; + } + return p; + } + + static node_t* make_inner_n(count_t n, + count_t idx1, T x1, + count_t idx2, T x2) + { + assert(idx1 != idx2); + auto p = make_inner_n(n, 2); + p->impl.d.data.inner.datamap = (bitmap_t{1u} << idx1) | (bitmap_t{1u} << idx2); + auto assign = [&] (auto&& x1, auto&& x2) { + auto vp = p->values(); + try { + new (vp) T{std::move(x1)}; + try { + new (vp + 1) T{std::move(x2)}; + } catch (...) { + vp->~T(); + throw; + } + } catch (...) { + deallocate_inner(p, n, 2); + throw; + } + }; + if (idx1 < idx2) + assign(x1, x2); + else + assign(x2, x1); + return p; + } + + static node_t* make_collision_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_collision_n(n)); + auto p = new (m) node_t; +#if IMMER_HAMTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::collision; +#endif + p->impl.d.data.collision.count = n; + return p; + } + + static node_t* make_collision(T v1, T v2) + { + auto m = heap::allocate(sizeof_collision_n(2)); + auto p = new (m) node_t; +#if IMMER_HAMTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::collision; +#endif + p->impl.d.data.collision.count = 2; + auto cols = p->collisions(); + try { + new (cols) T{std::move(v1)}; + try { + new (cols + 1) T{std::move(v2)}; + } catch (...) { + cols->~T(); + throw; + } + } catch (...) { + deallocate_collision(p, 2); + throw; + } + return p; + } + + static node_t* copy_collision_insert(node_t* src, T v) + { + assert(src->kind() == kind_t::collision); + auto n = src->collision_count(); + auto dst = make_collision_n(n + 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + try { + new (dstp) T{std::move(v)}; + try { + std::uninitialized_copy(srcp, srcp + n, dstp + 1); + } catch (...) { + dstp->~T(); + throw; + } + } catch (...) { + deallocate_collision(dst, n + 1); + throw; + } + return dst; + } + + static node_t* copy_collision_remove(node_t* src, T* v) + { + assert(src->kind() == kind_t::collision); + assert(src->collision_count() > 1); + auto n = src->collision_count(); + auto dst = make_collision_n(n - 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + try { + dstp = std::uninitialized_copy(srcp, v, dstp); + try { + std::uninitialized_copy(v + 1, srcp + n, dstp); + } catch (...) { + destroy(dst->collisions(), dstp); + throw; + } + } catch (...) { + deallocate_collision(dst, n - 1); + throw; + } + return dst; + } + + static node_t* copy_collision_replace(node_t* src, T* pos, T v) + { + assert(src->kind() == kind_t::collision); + auto n = src->collision_count(); + auto dst = make_collision_n(n); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + assert(pos >= srcp && pos < srcp + n); + try { + new (dstp) T{std::move(v)}; + try { + dstp = std::uninitialized_copy(srcp, pos, dstp + 1); + try { + std::uninitialized_copy(pos + 1, srcp + n, dstp); + } catch (...) { + destroy(dst->collisions(), dstp); + throw; + } + } catch (...) { + dst->collisions()->~T(); + throw; + } + } catch (...) { + deallocate_collision(dst, n); + throw; + } + return dst; + } + + static node_t* copy_inner_replace(node_t* src, + count_t offset, node_t* child) + { + assert(src->kind() == kind_t::inner); + auto n = popcount(src->nodemap()); + auto dst = make_inner_n(n, src->impl.d.data.inner.values); + auto srcp = src->children(); + auto dstp = dst->children(); + dst->impl.d.data.inner.datamap = src->datamap(); + dst->impl.d.data.inner.nodemap = src->nodemap(); + std::uninitialized_copy(srcp, srcp + n, dstp); + inc_nodes(srcp, n); + srcp[offset]->dec_unsafe(); + dstp[offset] = child; + return dst; + } + + static node_t* copy_inner_replace_value(node_t* src, + count_t offset, T v) + { + assert(src->kind() == kind_t::inner); + assert(offset < popcount(src->datamap())); + auto n = popcount(src->nodemap()); + auto nv = popcount(src->datamap()); + auto dst = make_inner_n(n, nv); + dst->impl.d.data.inner.datamap = src->datamap(); + dst->impl.d.data.inner.nodemap = src->nodemap(); + try { + std::uninitialized_copy( + src->values(), src->values() + nv, dst->values()); + try { + dst->values()[offset] = std::move(v); + } catch (...) { + destroy_n(dst->values(), nv); + throw; + } + } catch (...) { + deallocate_inner(dst, n, nv); + throw; + } + inc_nodes(src->children(), n); + std::uninitialized_copy( + src->children(), src->children() + n, dst->children()); + return dst; + } + + static node_t* copy_inner_replace_merged( + node_t* src, bitmap_t bit, count_t voffset, node_t* node) + { + assert(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == popcount(src->datamap() & (bit - 1))); + auto n = popcount(src->nodemap()); + auto nv = popcount(src->datamap()); + auto dst = make_inner_n(n + 1, nv - 1); + auto noffset = popcount(src->nodemap() & (bit - 1)); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap() | bit; + if (nv > 1) { + try { + std::uninitialized_copy( + src->values(), src->values() + voffset, + dst->values()); + try { + std::uninitialized_copy( + src->values() + voffset + 1, src->values() + nv, + dst->values() + voffset); + } catch (...) { + destroy_n(dst->values(), voffset); + throw; + } + } catch (...) { + deallocate_inner(dst, n + 1, nv - 1); + throw; + } + } + inc_nodes(src->children(), n); + std::uninitialized_copy( + src->children(), src->children() + noffset, + dst->children()); + std::uninitialized_copy( + src->children() + noffset, src->children() + n, + dst->children() + noffset + 1); + dst->children()[noffset] = node; + return dst; + } + + static node_t* copy_inner_replace_inline( + node_t* src, bitmap_t bit, count_t noffset, T value) + { + assert(src->kind() == kind_t::inner); + assert(!(src->datamap() & bit)); + assert(src->nodemap() & bit); + assert(noffset == popcount(src->nodemap() & (bit - 1))); + auto n = popcount(src->nodemap()); + auto nv = popcount(src->datamap()); + auto dst = make_inner_n(n - 1, nv + 1); + auto voffset = popcount(src->datamap() & (bit - 1)); + dst->impl.d.data.inner.nodemap = src->nodemap() & ~bit; + dst->impl.d.data.inner.datamap = src->datamap() | bit; + try { + if (nv) + std::uninitialized_copy( + src->values(), src->values() + voffset, + dst->values()); + try { + new (dst->values() + voffset) T{std::move(value)}; + try { + if (nv) + std::uninitialized_copy( + src->values() + voffset, src->values() + nv, + dst->values() + voffset + 1); + } catch (...) { + dst->values()[voffset].~T(); + throw; + } + } catch (...) { + destroy_n(dst->values(), voffset); + throw; + } + } catch (...) { + deallocate_inner(dst, n - 1, nv + 1); + throw; + } + inc_nodes(src->children(), n); + src->children()[noffset]->dec_unsafe(); + std::uninitialized_copy( + src->children(), src->children() + noffset, + dst->children()); + std::uninitialized_copy( + src->children() + noffset + 1, src->children() + n, + dst->children() + noffset); + return dst; + } + + static node_t* copy_inner_remove_value( + node_t* src, bitmap_t bit, count_t voffset) + { + assert(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == popcount(src->datamap() & (bit - 1))); + auto n = popcount(src->nodemap()); + auto nv = popcount(src->datamap()); + auto dst = make_inner_n(n, nv - 1); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + if (nv > 1) { + try { + std::uninitialized_copy( + src->values(), src->values() + voffset, + dst->values()); + try { + std::uninitialized_copy( + src->values() + voffset + 1, src->values() + nv, + dst->values() + voffset); + } catch (...) { + destroy_n(dst->values(), voffset); + throw; + } + } catch (...) { + deallocate_inner(dst, n, nv - 1); + throw; + } + } + inc_nodes(src->children(), n); + std::uninitialized_copy( + src->children(), src->children() + n, dst->children()); + return dst; + } + + static node_t* copy_inner_insert_value(node_t* src, bitmap_t bit, T v) + { + assert(src->kind() == kind_t::inner); + auto n = popcount(src->nodemap()); + auto nv = popcount(src->datamap()); + auto offset = popcount(src->datamap() & (bit - 1)); + auto dst = make_inner_n(n, nv + 1); + dst->impl.d.data.inner.datamap = src->datamap() | bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + try { + if (nv) + std::uninitialized_copy( + src->values(), src->values() + offset, dst->values()); + try { + new (dst->values() + offset) T{std::move(v)}; + try { + if (nv) + std::uninitialized_copy( + src->values() + offset, src->values() + nv, + dst->values() + offset + 1); + } catch (...) { + dst->values()[offset].~T(); + throw; + } + } catch (...) { + destroy_n(dst->values(), offset); + throw; + } + } catch (...) { + deallocate_inner(dst, n, nv + 1); + throw; + } + inc_nodes(src->children(), n); + std::uninitialized_copy( + src->children(), src->children() + n, dst->children()); + return dst; + } + + static node_t* make_merged(shift_t shift, + T v1, hash_t hash1, + T v2, hash_t hash2) + { + if (shift < max_shift) { + auto idx1 = hash1 & (mask << shift); + auto idx2 = hash2 & (mask << shift); + if (idx1 == idx2) { + auto merged = make_merged(shift + B, + std::move(v1), hash1, + std::move(v2), hash2); + try { + return make_inner_n(1, idx1 >> shift, merged); + } catch (...) { + delete_deep_shift(merged, shift + B); + throw; + } + } else { + return make_inner_n(0, + idx1 >> shift, std::move(v1), + idx2 >> shift, std::move(v2)); + } + } else { + return make_collision(std::move(v1), std::move(v2)); + } + } + + node_t* inc() + { + refs(this).inc(); + return this; + } + + const node_t* inc() const + { + refs(this).inc(); + return this; + } + + bool dec() const { return refs(this).dec(); } + void dec_unsafe() const { refs(this).dec_unsafe(); } + + static void inc_nodes(node_t** p, count_t n) + { + for (auto i = p, e = i + n; i != e; ++i) + refs(*i).inc(); + } + + static void delete_values(values_t* p, count_t n) + { + assert(p); + deallocate_values(p, n); + } + + static void delete_inner(node_t* p) + { + assert(p); + assert(p->kind() == kind_t::inner); + auto vp = p->impl.d.data.inner.values; + if (vp && refs(vp).dec()) + delete_values(vp, popcount(p->datamap())); + deallocate_inner(p, popcount(p->nodemap())); + } + + static void delete_collision(node_t* p) + { + assert(p); + assert(p->kind() == kind_t::collision); + auto n = p->collision_count(); + deallocate_collision(p, n); + } + + static void delete_deep(node_t* p, shift_t s) + { + if (s == max_depth) + delete_collision(p); + else { + auto fst = p->children(); + auto lst = fst + popcount(p->nodemap()); + for (; fst != lst; ++fst) + if ((*fst)->dec()) + delete_deep(*fst, s + 1); + delete_inner(p); + } + } + + static void delete_deep_shift(node_t* p, shift_t s) + { + if (s == max_shift) + delete_collision(p); + else { + auto fst = p->children(); + auto lst = fst + popcount(p->nodemap()); + for (; fst != lst; ++fst) + if ((*fst)->dec()) + delete_deep_shift(*fst, s + B); + delete_inner(p); + } + } + + static void deallocate_values(values_t* p, count_t n) + { + destroy_n((T*) &p->d.buffer, n); + heap::deallocate(node_t::sizeof_values_n(n), p); + } + + static void deallocate_collision(node_t* p, count_t n) + { + destroy_n(p->collisions(), n); + heap::deallocate(node_t::sizeof_collision_n(n), p); + } + + static void deallocate_inner(node_t* p, count_t n) + { + heap::deallocate(node_t::sizeof_inner_n(n), p); + } + + static void deallocate_inner(node_t* p, count_t n, count_t nv) + { + assert(nv); + heap::deallocate(node_t::sizeof_values_n(nv), p->impl.d.data.inner.values); + heap::deallocate(node_t::sizeof_inner_n(n), p); + } +}; + +} // namespace hamts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/iterator_facade.hpp b/src/immer/detail/iterator_facade.hpp new file mode 100644 index 000000000000..985b2f17ae65 --- /dev/null +++ b/src/immer/detail/iterator_facade.hpp @@ -0,0 +1,202 @@ +// +// 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 + +namespace immer { +namespace detail { + +struct iterator_core_access +{ + template + static decltype(auto) dereference(T&& x) + { return x.dereference(); } + + template + static decltype(auto) increment(T&& x) + { return x.increment(); } + + template + static decltype(auto) decrement(T&& x) + { return x.decrement(); } + + template + static decltype(auto) equal(T1&& x1, T2&& x2) + { return x1.equal(x2); } + + template + static decltype(auto) advance(T&& x, D d) + { return x.advance(d); } + + template + static decltype(auto) distance_to(T1&& x1, T2&& x2) + { return x1.distance_to(x2); } +}; + +/*! + * Minimalistic reimplementation of boost::iterator_facade + */ +template +class iterator_facade + : public std::iterator +{ +protected: + using access_t = iterator_core_access; + + constexpr static auto is_random_access = + std::is_base_of::value; + constexpr static auto is_bidirectional = + std::is_base_of::value; + + class reference_proxy + { + friend iterator_facade; + DerivedT iter_; + + reference_proxy(DerivedT iter) + : iter_{std::move(iter)} {} + public: + operator ReferenceT() const { return *iter_; } + }; + + const DerivedT& derived() const + { + static_assert(std::is_base_of::value, + "must pass a derived thing"); + return *static_cast(this); + } + DerivedT& derived() + { + static_assert(std::is_base_of::value, + "must pass a derived thing"); + return *static_cast(this); + } + +public: + ReferenceT operator*() const + { + return access_t::dereference(derived()); + } + PointerT operator->() const + { + return &access_t::dereference(derived()); + } + reference_proxy operator[](DifferenceTypeT n) const + { + static_assert(is_random_access, ""); + return derived() + n; + } + + bool operator==(const DerivedT& rhs) const + { + return access_t::equal(derived(), rhs); + } + bool operator!=(const DerivedT& rhs) const + { + return !access_t::equal(derived(), rhs); + } + + DerivedT& operator++() + { + access_t::increment(derived()); + return derived(); + } + DerivedT operator++(int) + { + auto tmp = derived(); + access_t::increment(derived()); + return tmp; + } + + DerivedT& operator--() + { + static_assert(is_bidirectional || is_random_access, ""); + access_t::decrement(derived()); + return derived(); + } + DerivedT operator--(int) + { + static_assert(is_bidirectional || is_random_access, ""); + auto tmp = derived(); + access_t::decrement(derived()); + return tmp; + } + + DerivedT& operator+=(DifferenceTypeT n) + { + access_t::advance(derived(), n); + return derived(); + } + DerivedT& operator-=(DifferenceTypeT n) + { + access_t::advance(derived(), -n); + return derived(); + } + + DerivedT operator+(DifferenceTypeT n) const + { + static_assert(is_random_access, ""); + auto tmp = derived(); + return tmp += n; + } + friend DerivedT operator+(DifferenceTypeT n, const DerivedT& i) + { + static_assert(is_random_access, ""); + return i + n; + } + DerivedT operator-(DifferenceTypeT n) const + { + static_assert(is_random_access, ""); + auto tmp = derived(); + return tmp -= n; + } + DifferenceTypeT operator-(const DerivedT& rhs) const + { + static_assert(is_random_access, ""); + return access_t::distance_to(rhs, derived()); + } + + bool operator<(const DerivedT& rhs) const + { + static_assert(is_random_access, ""); + return access_t::distance_to(derived(), rhs) > 0; + } + bool operator<=(const DerivedT& rhs) const + { + static_assert(is_random_access, ""); + return access_t::distance_to(derived(), rhs) >= 0; + } + bool operator>(const DerivedT& rhs) const + { + static_assert(is_random_access, ""); + return access_t::distance_to(derived(), rhs) < 0; + } + bool operator>=(const DerivedT& rhs) const + { + static_assert(is_random_access, ""); + return access_t::distance_to(derived(), rhs) <= 0; + } +}; + +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/bits.hpp b/src/immer/detail/rbts/bits.hpp new file mode 100644 index 000000000000..549319ae793a --- /dev/null +++ b/src/immer/detail/rbts/bits.hpp @@ -0,0 +1,33 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +using bits_t = std::uint32_t; +using shift_t = std::uint32_t; +using count_t = std::uint32_t; +using size_t = std::size_t; + +template +constexpr T branches = T{1} << B; + +template +constexpr T mask = branches - 1; + +template +constexpr shift_t endshift = shift_t{BL} - shift_t{B}; + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/node.hpp b/src/immer/detail/rbts/node.hpp new file mode 100644 index 000000000000..229b1d9d48d7 --- /dev/null +++ b/src/immer/detail/rbts/node.hpp @@ -0,0 +1,942 @@ +// +// 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 + +#ifdef NDEBUG +#define IMMER_RBTS_TAGGED_NODE 0 +#else +#define IMMER_RBTS_TAGGED_NODE 1 +#endif + +namespace immer { +namespace detail { +namespace rbts { + +template +struct node +{ + static constexpr auto bits = B; + static constexpr auto bits_leaf = BL; + + using node_t = node; + using memory = MemoryPolicy; + using heap_policy = typename memory::heap; + using transience = typename memory::transience_t; + using refs_t = typename memory::refcount; + using ownee_t = typename transience::ownee; + using edit_t = typename transience::edit; + using value_t = T; + + static constexpr bool embed_relaxed = memory::prefer_fewer_bigger_objects; + + enum class kind_t + { + leaf, + inner + }; + + struct relaxed_data_t + { + count_t count; + size_t sizes[branches]; + }; + + using relaxed_data_with_meta_t = + combine_standard_layout_t; + + using relaxed_data_no_meta_t = + combine_standard_layout_t; + + using relaxed_t = std::conditional_t; + + struct leaf_t + { + aligned_storage_for buffer; + }; + + struct inner_t + { + relaxed_t* relaxed; + aligned_storage_for buffer; + }; + + union data_t + { + inner_t inner; + leaf_t leaf; + }; + + struct impl_data_t + { +#if IMMER_RBTS_TAGGED_NODE + kind_t kind; +#endif + data_t data; + }; + + using impl_t = combine_standard_layout_t< + impl_data_t, refs_t, ownee_t>; + + impl_t impl; + + // assume that we need to keep headroom space in the node when we + // are doing reference counting, since any node may become + // transient when it has only one reference + constexpr static bool keep_headroom = !std::is_empty{}; + + constexpr static std::size_t sizeof_packed_leaf_n(count_t count) + { + return immer_offsetof(impl_t, d.data.leaf.buffer) + + sizeof(leaf_t::buffer) * count; + } + + constexpr static std::size_t sizeof_packed_inner_n(count_t count) + { + return immer_offsetof(impl_t, d.data.inner.buffer) + + sizeof(inner_t::buffer) * count; + } + + constexpr static std::size_t sizeof_packed_relaxed_n(count_t count) + { + return immer_offsetof(relaxed_t, d.sizes) + + sizeof(size_t) * count; + } + + constexpr static std::size_t sizeof_packed_inner_r_n(count_t count) + { + return embed_relaxed + ? sizeof_packed_inner_n(count) + sizeof_packed_relaxed_n(count) + : sizeof_packed_inner_n(count); + } + + constexpr static std::size_t max_sizeof_leaf = + sizeof_packed_leaf_n(branches); + + constexpr static std::size_t max_sizeof_inner = + sizeof_packed_inner_n(branches); + + constexpr static std::size_t max_sizeof_relaxed = + sizeof_packed_relaxed_n(branches); + + constexpr static std::size_t max_sizeof_inner_r = + sizeof_packed_inner_r_n(branches); + + constexpr static std::size_t sizeof_inner_n(count_t n) + { return keep_headroom ? max_sizeof_inner : sizeof_packed_inner_n(n); } + + constexpr static std::size_t sizeof_inner_r_n(count_t n) + { return keep_headroom ? max_sizeof_inner_r : sizeof_packed_inner_r_n(n); } + + constexpr static std::size_t sizeof_relaxed_n(count_t n) + { return keep_headroom ? max_sizeof_relaxed : sizeof_packed_relaxed_n(n); } + + constexpr static std::size_t sizeof_leaf_n(count_t n) + { return keep_headroom ? max_sizeof_leaf : sizeof_packed_leaf_n(n); } + + using heap = typename heap_policy::template + optimized::type; + +#if IMMER_RBTS_TAGGED_NODE + kind_t kind() const + { + return impl.d.kind; + } +#endif + + relaxed_t* relaxed() + { + assert(kind() == kind_t::inner); + return impl.d.data.inner.relaxed; + } + + const relaxed_t* relaxed() const + { + assert(kind() == kind_t::inner); + return impl.d.data.inner.relaxed; + } + + node_t** inner() + { + assert(kind() == kind_t::inner); + return reinterpret_cast(&impl.d.data.inner.buffer); + } + + T* leaf() + { + assert(kind() == kind_t::leaf); + return reinterpret_cast(&impl.d.data.leaf.buffer); + } + + static refs_t& refs(const relaxed_t* x) { return auto_const_cast(get(*x)); } + static const ownee_t& ownee(const relaxed_t* x) { return get(*x); } + static ownee_t& ownee(relaxed_t* x) { return get(*x); } + + static refs_t& refs(const node_t* x) { return auto_const_cast(get(x->impl)); } + static const ownee_t& ownee(const node_t* x) { return get(x->impl); } + static ownee_t& ownee(node_t* x) { return get(x->impl); } + + static node_t* make_inner_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_inner_n(n)); + auto p = new (m) node_t; + p->impl.d.data.inner.relaxed = nullptr; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + } + + static node_t* make_inner_e(edit_t e) + { + auto m = heap::allocate(max_sizeof_inner); + auto p = new (m) node_t; + ownee(p) = e; + p->impl.d.data.inner.relaxed = nullptr; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + } + + static node_t* make_inner_r_n(count_t n) + { + assert(n <= branches); + auto mp = heap::allocate(sizeof_inner_r_n(n)); + auto mr = static_cast(nullptr); + if (embed_relaxed) { + mr = reinterpret_cast(mp) + sizeof_inner_n(n); + } else { + try { + mr = heap::allocate(sizeof_relaxed_n(n), norefs_tag{}); + } catch (...) { + heap::deallocate(sizeof_inner_r_n(n), mp); + throw; + } + } + auto p = new (mp) node_t; + auto r = new (mr) relaxed_t; + r->d.count = 0; + p->impl.d.data.inner.relaxed = r; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + } + + static node_t* make_inner_sr_n(count_t n, relaxed_t* r) + { + return static_if( + [&] (auto) { + return node_t::make_inner_r_n(n); + }, + [&] (auto) { + auto p = new (heap::allocate(node_t::sizeof_inner_r_n(n))) node_t; + assert(r->d.count >= n); + node_t::refs(r).inc(); + p->impl.d.data.inner.relaxed = r; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + }); + } + + static node_t* make_inner_r_e(edit_t e) + { + auto mp = heap::allocate(max_sizeof_inner_r); + auto mr = static_cast(nullptr); + if (embed_relaxed) { + mr = reinterpret_cast(mp) + max_sizeof_inner; + } else { + try { + mr = heap::allocate(max_sizeof_relaxed, norefs_tag{}); + } catch (...) { + heap::deallocate(max_sizeof_inner_r, mp); + throw; + } + } + auto p = new (mp) node_t; + auto r = new (mr) relaxed_t; + ownee(p) = e; + static_if([&](auto){ node_t::ownee(r) = e; }); + r->d.count = 0; + p->impl.d.data.inner.relaxed = r; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + } + + static node_t* make_inner_sr_e(edit_t e, relaxed_t* r) + { + return static_if( + [&] (auto) { + return node_t::make_inner_r_e(e); + }, + [&] (auto) { + auto p = new (heap::allocate(node_t::max_sizeof_inner_r)) node_t; + node_t::refs(r).inc(); + p->impl.d.data.inner.relaxed = r; + node_t::ownee(p) = e; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::inner; +#endif + return p; + }); + } + + static node_t* make_leaf_n(count_t n) + { + assert(n <= branches); + auto p = new (heap::allocate(sizeof_leaf_n(n))) node_t; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::leaf; +#endif + return p; + } + + static node_t* make_leaf_e(edit_t e) + { + auto p = new (heap::allocate(max_sizeof_leaf)) node_t; + ownee(p) = e; +#if IMMER_RBTS_TAGGED_NODE + p->impl.d.kind = node_t::kind_t::leaf; +#endif + return p; + } + + static node_t* make_inner_n(count_t n, node_t* x) + { + assert(n >= 1); + auto p = make_inner_n(n); + p->inner() [0] = x; + return p; + } + + static node_t* make_inner_n(edit_t n, node_t* x) + { + assert(n >= 1); + auto p = make_inner_n(n); + p->inner() [0] = x; + return p; + } + + static node_t* make_inner_n(count_t n, node_t* x, node_t* y) + { + assert(n >= 2); + auto p = make_inner_n(n); + p->inner() [0] = x; + p->inner() [1] = y; + return p; + } + + static node_t* make_inner_r_n(count_t n, node_t* x) + { + assert(n >= 1); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + r->d.count = 1; + return p; + } + + static node_t* make_inner_r_n(count_t n, node_t* x, size_t xs) + { + assert(n >= 1); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + r->d.sizes [0] = xs; + r->d.count = 1; + return p; + } + + static node_t* make_inner_r_n(count_t n, node_t* x, node_t* y) + { + assert(n >= 2); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + p->inner() [1] = y; + r->d.count = 2; + return p; + } + + static node_t* make_inner_r_n(count_t n, + node_t* x, size_t xs, + node_t* y) + { + assert(n >= 2); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + p->inner() [1] = y; + r->d.sizes [0] = xs; + r->d.count = 2; + return p; + } + + static node_t* make_inner_r_n(count_t n, + node_t* x, size_t xs, + node_t* y, size_t ys) + { + assert(n >= 2); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + p->inner() [1] = y; + r->d.sizes [0] = xs; + r->d.sizes [1] = xs + ys; + r->d.count = 2; + return p; + } + + static node_t* make_inner_r_n(count_t n, + node_t* x, size_t xs, + node_t* y, size_t ys, + node_t* z, size_t zs) + { + assert(n >= 3); + auto p = make_inner_r_n(n); + auto r = p->relaxed(); + p->inner() [0] = x; + p->inner() [1] = y; + p->inner() [2] = z; + r->d.sizes [0] = xs; + r->d.sizes [1] = xs + ys; + r->d.sizes [2] = xs + ys + zs; + r->d.count = 3; + return p; + } + + template + static node_t* make_leaf_n(count_t n, U&& x) + { + assert(n >= 1); + auto p = make_leaf_n(n); + try { + new (p->leaf()) T{ std::forward(x) }; + } catch (...) { + heap::deallocate(node_t::sizeof_leaf_n(n), p); + throw; + } + return p; + } + + template + static node_t* make_leaf_e(edit_t e, U&& x) + { + auto p = make_leaf_e(e); + try { + new (p->leaf()) T{ std::forward(x) }; + } catch (...) { + heap::deallocate(node_t::max_sizeof_leaf, p); + throw; + } + return p; + } + + static node_t* make_path(shift_t shift, node_t* node) + { + assert(node->kind() == kind_t::leaf); + if (shift == endshift) + return node; + else { + auto n = node_t::make_inner_n(1); + try { + n->inner() [0] = make_path(shift - B, node); + } catch (...) { + heap::deallocate(node_t::sizeof_inner_n(1), n); + throw; + } + return n; + } + } + + static node_t* make_path_e(edit_t e, shift_t shift, node_t* node) + { + assert(node->kind() == kind_t::leaf); + if (shift == endshift) + return node; + else { + auto n = node_t::make_inner_e(e); + try { + n->inner() [0] = make_path_e(e, shift - B, node); + } catch (...) { + heap::deallocate(node_t::max_sizeof_inner, n); + throw; + } + return n; + } + } + + static node_t* copy_inner(node_t* src, count_t n) + { + assert(src->kind() == kind_t::inner); + auto dst = make_inner_n(n); + inc_nodes(src->inner(), n); + std::uninitialized_copy(src->inner(), src->inner() + n, dst->inner()); + return dst; + } + + static node_t* copy_inner_n(count_t allocn, node_t* src, count_t n) + { + assert(allocn >= n); + assert(src->kind() == kind_t::inner); + auto dst = make_inner_n(allocn); + return do_copy_inner(dst, src, n); + } + + static node_t* copy_inner_e(edit_t e, node_t* src, count_t n) + { + assert(src->kind() == kind_t::inner); + auto dst = make_inner_e(e); + return do_copy_inner(dst, src, n); + } + + static node_t* do_copy_inner(node_t* dst, node_t* src, count_t n) + { + assert(dst->kind() == kind_t::inner); + assert(src->kind() == kind_t::inner); + auto p = src->inner(); + inc_nodes(p, n); + std::uninitialized_copy(p, p + n, dst->inner()); + return dst; + } + + static node_t* copy_inner_r(node_t* src, count_t n) + { + assert(src->kind() == kind_t::inner); + auto dst = make_inner_r_n(n); + return do_copy_inner_r(dst, src, n); + } + + static node_t* copy_inner_r_n(count_t allocn, node_t* src, count_t n) + { + assert(allocn >= n); + assert(src->kind() == kind_t::inner); + auto dst = make_inner_r_n(allocn); + return do_copy_inner_r(dst, src, n); + } + + static node_t* copy_inner_r_e(edit_t e, node_t* src, count_t n) + { + assert(src->kind() == kind_t::inner); + auto dst = make_inner_r_e(e); + return do_copy_inner_r(dst, src, n); + } + + static node_t* copy_inner_sr_e(edit_t e, node_t* src, count_t n) + { + assert(src->kind() == kind_t::inner); + auto dst = make_inner_sr_e(e, src->relaxed()); + return do_copy_inner_sr(dst, src, n); + } + + static node_t* do_copy_inner_r(node_t* dst, node_t* src, count_t n) + { + assert(dst->kind() == kind_t::inner); + assert(src->kind() == kind_t::inner); + auto src_r = src->relaxed(); + auto dst_r = dst->relaxed(); + inc_nodes(src->inner(), n); + std::copy(src->inner(), src->inner() + n, dst->inner()); + std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + dst_r->d.count = n; + return dst; + } + + static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n) + { + if (embed_relaxed) + return do_copy_inner_r(dst, src, n); + else { + inc_nodes(src->inner(), n); + std::copy(src->inner(), src->inner() + n, dst->inner()); + return dst; + } + } + + static node_t* copy_leaf(node_t* src, count_t n) + { + assert(src->kind() == kind_t::leaf); + auto dst = make_leaf_n(n); + try { + std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + } catch (...) { + heap::deallocate(node_t::sizeof_leaf_n(n), dst); + throw; + } + return dst; + } + + static node_t* copy_leaf_e(edit_t e, node_t* src, count_t n) + { + assert(src->kind() == kind_t::leaf); + auto dst = make_leaf_e(e); + try { + std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + } catch (...) { + heap::deallocate(node_t::max_sizeof_leaf, dst); + throw; + } + return dst; + } + + static node_t* copy_leaf_n(count_t allocn, node_t* src, count_t n) + { + assert(allocn >= n); + assert(src->kind() == kind_t::leaf); + auto dst = make_leaf_n(allocn); + try { + std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + } catch (...) { + heap::deallocate(node_t::sizeof_leaf_n(allocn), dst); + throw; + } + return dst; + } + + static node_t* copy_leaf(node_t* src1, count_t n1, + node_t* src2, count_t n2) + { + assert(src1->kind() == kind_t::leaf); + assert(src2->kind() == kind_t::leaf); + auto dst = make_leaf_n(n1 + n2); + try { + std::uninitialized_copy( + src1->leaf(), src1->leaf() + n1, dst->leaf()); + } catch (...) { + heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); + throw; + } + try { + std::uninitialized_copy( + src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); + } catch (...) { + destroy_n(dst->leaf(), n1); + heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); + throw; + } + return dst; + } + + static node_t* copy_leaf_e(edit_t e, + node_t* src1, count_t n1, + node_t* src2, count_t n2) + { + assert(src1->kind() == kind_t::leaf); + assert(src2->kind() == kind_t::leaf); + auto dst = make_leaf_e(e); + try { + std::uninitialized_copy( + src1->leaf(), src1->leaf() + n1, dst->leaf()); + } catch (...) { + heap::deallocate(max_sizeof_leaf, dst); + throw; + } + try { + std::uninitialized_copy( + src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); + } catch (...) { + destroy_n(dst->leaf(), n1); + heap::deallocate(max_sizeof_leaf, dst); + throw; + } + return dst; + } + + static node_t* copy_leaf_e(edit_t e, node_t* src, count_t idx, count_t last) + { + assert(src->kind() == kind_t::leaf); + auto dst = make_leaf_e(e); + try { + std::uninitialized_copy( + src->leaf() + idx, src->leaf() + last, dst->leaf()); + } catch (...) { + heap::deallocate(max_sizeof_leaf, dst); + throw; + } + return dst; + } + + static node_t* copy_leaf(node_t* src, count_t idx, count_t last) + { + assert(src->kind() == kind_t::leaf); + auto dst = make_leaf_n(last - idx); + try { + std::uninitialized_copy( + src->leaf() + idx, src->leaf() + last, dst->leaf()); + } catch (...) { + heap::deallocate(node_t::sizeof_leaf_n(last - idx), dst); + throw; + } + return dst; + } + + template + static node_t* copy_leaf_emplace(node_t* src, count_t n, U&& x) + { + auto dst = copy_leaf_n(n + 1, src, n); + try { + new (dst->leaf() + n) T{std::forward(x)}; + } catch (...) { + destroy_n(dst->leaf(), n); + heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst); + throw; + } + return dst; + } + + static void delete_inner(node_t* p, count_t n) + { + assert(p->kind() == kind_t::inner); + assert(!p->relaxed()); + heap::deallocate(ownee(p).owned() + ? node_t::max_sizeof_inner + : node_t::sizeof_inner_n(n), p); + } + + static void delete_inner_e(node_t* p) + { + assert(p->kind() == kind_t::inner); + assert(!p->relaxed()); + heap::deallocate(node_t::max_sizeof_inner, p); + } + + static void delete_inner_any(node_t* p, count_t n) + { + if (p->relaxed()) + delete_inner_r(p, n); + else + delete_inner(p, n); + } + + static void delete_inner_r(node_t* p, count_t n) + { + assert(p->kind() == kind_t::inner); + auto r = p->relaxed(); + assert(r); + static_if([&] (auto) { + if (node_t::refs(r).dec()) + heap::deallocate(node_t::ownee(r).owned() + ? node_t::max_sizeof_relaxed + : node_t::sizeof_relaxed_n(n), r); + }); + heap::deallocate(ownee(p).owned() + ? node_t::max_sizeof_inner_r + : node_t::sizeof_inner_r_n(n), p); + } + + static void delete_inner_r_e(node_t* p) + { + assert(p->kind() == kind_t::inner); + auto r = p->relaxed(); + assert(r); + static_if([&] (auto) { + if (node_t::refs(r).dec()) + heap::deallocate(node_t::max_sizeof_relaxed, r); + }); + heap::deallocate(node_t::max_sizeof_inner_r, p); + } + + static void delete_leaf(node_t* p, count_t n) + { + assert(p->kind() == kind_t::leaf); + destroy_n(p->leaf(), n); + heap::deallocate(ownee(p).owned() + ? node_t::max_sizeof_leaf + : node_t::sizeof_leaf_n(n), p); + } + + bool can_mutate(edit_t e) const + { + return refs(this).unique() + || ownee(this).can_mutate(e); + } + + bool can_relax() const + { + return !embed_relaxed || relaxed(); + } + + relaxed_t* ensure_mutable_relaxed(edit_t e) + { + auto src_r = relaxed(); + return static_if( + [&] (auto) { return src_r; }, + [&] (auto) { + if (node_t::refs(src_r).unique() || + node_t::ownee(src_r).can_mutate(e)) + return src_r; + else { + if (src_r) + node_t::refs(src_r).dec_unsafe(); + auto dst_r = impl.d.data.inner.relaxed = + new (heap::allocate(max_sizeof_relaxed)) relaxed_t; + node_t::ownee(dst_r) = e; + return dst_r; + } + }); + } + + relaxed_t* ensure_mutable_relaxed_e(edit_t e, edit_t ec) + { + auto src_r = relaxed(); + return static_if( + [&] (auto) { return src_r; }, + [&] (auto) { + if (src_r && (node_t::refs(src_r).unique() || + node_t::ownee(src_r).can_mutate(e))) { + node_t::ownee(src_r) = ec; + return src_r; + } else { + if (src_r) + node_t::refs(src_r).dec_unsafe(); + auto dst_r = impl.d.data.inner.relaxed = + new (heap::allocate(max_sizeof_relaxed)) relaxed_t; + node_t::ownee(dst_r) = ec; + return dst_r; + } + }); + } + + relaxed_t* ensure_mutable_relaxed_n(edit_t e, count_t n) + { + auto src_r = relaxed(); + return static_if( + [&] (auto) { return src_r; }, + [&] (auto) { + if (node_t::refs(src_r).unique() || + node_t::ownee(src_r).can_mutate(e)) + return src_r; + else { + if (src_r) + node_t::refs(src_r).dec_unsafe(); + auto dst_r = + new (heap::allocate(max_sizeof_relaxed)) relaxed_t; + std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + node_t::ownee(dst_r) = e; + return impl.d.data.inner.relaxed = dst_r; + } + }); + } + + node_t* inc() + { + refs(this).inc(); + return this; + } + + const node_t* inc() const + { + refs(this).inc(); + return this; + } + + bool dec() const { return refs(this).dec(); } + void dec_unsafe() const { refs(this).dec_unsafe(); } + + static void inc_nodes(node_t** p, count_t n) + { + for (auto i = p, e = i + n; i != e; ++i) + refs(*i).inc(); + } + +#if IMMER_RBTS_TAGGED_NODE + shift_t compute_shift() + { + if (kind() == kind_t::leaf) + return endshift; + else + return B + inner() [0]->compute_shift(); + } +#endif + + bool check(shift_t shift, size_t size) + { +#if IMMER_DEBUG_DEEP_CHECK + assert(size > 0); + if (shift == endshift) { + assert(kind() == kind_t::leaf); + assert(size <= branches); + } else if (auto r = relaxed()) { + auto count = r->d.count; + assert(count > 0); + assert(count <= branches); + if (r->d.sizes[count - 1] != size) { + IMMER_TRACE_F("check"); + IMMER_TRACE_E(r->d.sizes[count - 1]); + IMMER_TRACE_E(size); + } + assert(r->d.sizes[count - 1] == size); + for (auto i = 1; i < count; ++i) + assert(r->d.sizes[i - 1] < r->d.sizes[i]); + auto last_size = size_t{}; + for (auto i = 0; i < count; ++i) { + assert(inner()[i]->check( + shift - B, + r->d.sizes[i] - last_size)); + last_size = r->d.sizes[i]; + } + } else { + assert(size <= branches << shift); + auto count = (size >> shift) + + (size - ((size >> shift) << shift) > 0); + assert(count <= branches); + if (count) { + for (auto i = 1; i < count - 1; ++i) + assert(inner()[i]->check( + shift - B, + 1 << shift)); + assert(inner()[count - 1]->check( + shift - B, + size - ((count - 1) << shift))); + } + } +#endif // IMMER_DEBUG_DEEP_CHECK + return true; + } +}; + +template +constexpr bits_t derive_bits_leaf_aux() +{ + using node_t = node; + constexpr auto sizeof_elem = sizeof(T); + constexpr auto space = node_t::max_sizeof_inner - node_t::sizeof_packed_leaf_n(0); + constexpr auto full_elems = space / sizeof_elem; + constexpr auto BL = log2(full_elems); + return BL; +} + +template +constexpr bits_t derive_bits_leaf = derive_bits_leaf_aux(); + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/operations.hpp b/src/immer/detail/rbts/operations.hpp new file mode 100644 index 000000000000..17ec9911ef1c --- /dev/null +++ b/src/immer/detail/rbts/operations.hpp @@ -0,0 +1,2322 @@ +// +// 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 +#include + +namespace immer { +namespace detail { +namespace rbts { + +template +struct array_for_visitor : visitor_base> +{ + using this_t = array_for_visitor; + + template + static T* visit_inner(PosT&& pos, size_t idx) + { return pos.descend(this_t{}, idx); } + + template + static T* visit_leaf(PosT&& pos, size_t) + { return pos.node()->leaf(); } +}; + +template +struct region_for_visitor : visitor_base> +{ + using this_t = region_for_visitor; + using result_t = std::tuple; + + template + static result_t visit_inner(PosT&& pos, size_t idx) + { return pos.towards(this_t{}, idx); } + + template + static result_t visit_leaf(PosT&& pos, size_t idx) + { return { pos.node()->leaf(), pos.index(idx), pos.count() }; } +}; + +template +struct get_visitor : visitor_base> +{ + using this_t = get_visitor; + + template + static const T& visit_inner(PosT&& pos, size_t idx) + { return pos.descend(this_t{}, idx); } + + template + static const T& visit_leaf(PosT&& pos, size_t idx) + { return pos.node()->leaf() [pos.index(idx)]; } +}; + +struct for_each_chunk_visitor : visitor_base +{ + using this_t = for_each_chunk_visitor; + + template + static void visit_inner(Pos&& pos, Fn&& fn) + { pos.each(this_t{}, fn); } + + template + static void visit_leaf(Pos&& pos, Fn&& fn) + { + auto data = pos.node()->leaf(); + fn(data, data + pos.count()); + } +}; + +struct for_each_chunk_p_visitor : visitor_base +{ + using this_t = for_each_chunk_p_visitor; + + template + static bool visit_inner(Pos&& pos, Fn&& fn) + { return pos.each_pred(this_t{}, fn); } + + template + static bool visit_leaf(Pos&& pos, Fn&& fn) + { + auto data = pos.node()->leaf(); + return fn(data, data + pos.count()); + } +}; + +struct for_each_chunk_left_visitor : visitor_base +{ + using this_t = for_each_chunk_left_visitor; + + template + static void visit_inner(Pos&& pos, + size_t last, Fn&& fn) + { + auto l = pos.index(last); + pos.each_left(for_each_chunk_visitor{}, l, fn); + pos.towards_oh(this_t{}, last, l, fn); + } + + template + static void visit_leaf(Pos&& pos, + size_t last, + Fn&& fn) + { + auto data = pos.node()->leaf(); + auto l = pos.index(last); + fn(data, data + l + 1); + } +}; + +struct for_each_chunk_right_visitor : visitor_base +{ + using this_t = for_each_chunk_right_visitor; + + template + static void visit_inner(Pos&& pos, + size_t first, Fn&& fn) + { + auto f = pos.index(first); + pos.towards_oh(this_t{}, first, f, fn); + pos.each_right(for_each_chunk_visitor{}, f + 1, fn); + } + + template + static void visit_leaf(Pos&& pos, + size_t first, + Fn&& fn) + { + auto data = pos.node()->leaf(); + auto f = pos.index(first); + fn(data + f, data + pos.count()); + } +}; + +struct for_each_chunk_i_visitor : visitor_base +{ + using this_t = for_each_chunk_i_visitor; + + template + static void visit_relaxed(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + // we are going towards *two* indices, so we need to do the + // relaxed as a special case to correct the second index + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + if (f == l) { + auto sbh = pos.size_before(f); + pos.towards_oh_sbh(this_t{}, first, f, sbh, last - sbh, fn); + } else { + assert(f < l); + pos.towards_oh(for_each_chunk_right_visitor{}, first, f, fn); + pos.each_i(for_each_chunk_visitor{}, f + 1, l, fn); + pos.towards_oh(for_each_chunk_left_visitor{}, last - 1, l, fn); + } + } + } + + template + static void visit_regular(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + if (f == l) + pos.towards_oh(this_t{}, first, f, last, fn); + else { + assert(f < l); + pos.towards_oh(for_each_chunk_right_visitor{}, first, f, fn); + pos.each_i(for_each_chunk_visitor{}, f + 1, l, fn); + pos.towards_oh(for_each_chunk_left_visitor{}, last - 1, l, fn); + } + } + } + + template + static void visit_leaf(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + auto data = pos.node()->leaf(); + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + fn(data + f, data + l + 1); + } + } +}; + +struct for_each_chunk_p_left_visitor + : visitor_base +{ + using this_t = for_each_chunk_p_left_visitor; + + template + static bool visit_inner(Pos&& pos, + size_t last, Fn&& fn) + { + auto l = pos.index(last); + return pos.each_pred_left(for_each_chunk_p_visitor{}, l, fn) + && pos.towards_oh(this_t{}, last, l, fn); + } + + template + static bool visit_leaf(Pos&& pos, + size_t last, + Fn&& fn) + { + auto data = pos.node()->leaf(); + auto l = pos.index(last); + return fn(data, data + l + 1); + } +}; + +struct for_each_chunk_p_right_visitor + : visitor_base +{ + using this_t = for_each_chunk_p_right_visitor; + + template + static bool visit_inner(Pos&& pos, + size_t first, Fn&& fn) + { + auto f = pos.index(first); + return pos.towards_oh(this_t{}, first, f, fn) + && pos.each_pred_right(for_each_chunk_p_visitor{}, f + 1, fn); + } + + template + static bool visit_leaf(Pos&& pos, + size_t first, + Fn&& fn) + { + auto data = pos.node()->leaf(); + auto f = pos.index(first); + return fn(data + f, data + pos.count()); + } +}; + +struct for_each_chunk_p_i_visitor : visitor_base +{ + using this_t = for_each_chunk_p_i_visitor; + + template + static bool visit_relaxed(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + // we are going towards *two* indices, so we need to do the + // relaxed as a special case to correct the second index + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + if (f == l) { + auto sbh = pos.size_before(f); + return pos.towards_oh_sbh(this_t{}, first, f, sbh, last - sbh, fn); + } else { + assert(f < l); + return pos.towards_oh(for_each_chunk_p_right_visitor{}, first, f, fn) + && pos.each_pred_i(for_each_chunk_p_visitor{}, f + 1, l, fn) + && pos.towards_oh(for_each_chunk_p_left_visitor{}, last - 1, l, fn); + } + } + return true; + } + + template + static bool visit_regular(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + if (f == l) + return pos.towards_oh(this_t{}, first, f, last, fn); + else { + assert(f < l); + return pos.towards_oh(for_each_chunk_p_right_visitor{}, first, f, fn) + && pos.each_pred_i(for_each_chunk_p_visitor{}, f + 1, l, fn) + && pos.towards_oh(for_each_chunk_p_left_visitor{}, last - 1, l, fn); + } + } + return true; + } + + template + static bool visit_leaf(Pos&& pos, + size_t first, size_t last, + Fn&& fn) + { + auto data = pos.node()->leaf(); + if (first < last) { + auto f = pos.index(first); + auto l = pos.index(last - 1); + return fn(data + f, data + l + 1); + } + return true; + } +}; + +struct equals_visitor : visitor_base +{ + using this_t = equals_visitor; + + struct this_aux_t : visitor_base + { + template + static bool visit_inner(PosR&& posr, + count_t i, PosL&& posl, + Iter&& first, size_t idx) + { return posl.nth_sub(i, this_t{}, posr, first, idx); } + + template + static bool visit_leaf(PosR&& posr, + count_t i, PosL&& posl, + Iter&& first, size_t idx) + { return posl.nth_sub_leaf(i, this_t{}, posr, first, idx); } + }; + + struct rrb : visitor_base + { + template + static bool visit_node(PosR&& posr, Iter&& first, + Node* rootl, shift_t shiftl, size_t sizel) + { + assert(shiftl <= posr.shift()); + return shiftl == posr.shift() + ? visit_maybe_relaxed_sub(rootl, shiftl, sizel, + this_t{}, posr, first, size_t{}) + : posr.first_sub_inner(rrb{}, first, rootl, shiftl, sizel); + } + }; + + template + static auto equal_chunk_p(Iter&& iter) + { + return [iter] (auto f, auto e) mutable { + if (f == &*iter) { + iter += e - f; + return true; + } + for (; f != e; ++f, ++iter) + if (*f != *iter) + return false; + return true; + }; + } + + template + static bool visit_relaxed(PosL&& posl, PosR&& posr, + Iter&& first, size_t idx) + { + auto nl = posl.node(); + auto nr = posr.node(); + if (nl == nr) + return true; + auto cl = posl.count(); + auto cr = posr.count(); + assert(cr > 0); + auto sbr = size_t{}; + auto i = count_t{}; + auto j = count_t{}; + for (; i < cl; ++i) { + auto sbl = posl.size_before(i); + for (; j + 1 < cr && (sbr = posr.size_before(j)) < sbl; ++j); + auto res = sbl == sbr + ? posr.nth_sub(j, this_aux_t{}, i, posl, first, idx + sbl) + : posl.nth_sub(i, for_each_chunk_p_visitor{}, + this_t::equal_chunk_p(first + (idx + sbl))); + if (!res) return false; + } + return true; + } + + template + static std::enable_if_t, bool> + visit_regular(PosL&& posl, PosR&& posr, Iter&& first, size_t idx) + { + return this_t::visit_relaxed(posl, posr, first, idx); + } + + template + static std::enable_if_t, bool> + visit_regular(PosL&& posl, PosR&& posr, Iter&& first, size_t idx) + { + return posl.count() >= posr.count() + ? this_t::visit_regular(posl, posr.node()) + : this_t::visit_regular(posr, posl.node()); + } + + template + static bool visit_leaf(PosL&& posl, + PosR&& posr, Iter&& first, size_t idx) + { + if (posl.node() == posr.node()) + return true; + auto cl = posl.count(); + auto cr = posr.count(); + auto mp = std::min(cl, cr); + return + std::equal(posl.node()->leaf(), + posl.node()->leaf() + mp, + posr.node()->leaf()) && + std::equal(posl.node()->leaf() + mp, + posl.node()->leaf() + posl.count(), + first + (idx + mp)); + } + + template + static bool visit_regular(Pos&& pos, NodeT* other) + { + auto node = pos.node(); + return node == other + || pos.each_pred_zip(this_t{}, other); + } + + template + static bool visit_leaf(Pos&& pos, NodeT* other) + { + auto node = pos.node(); + return node == other + || std::equal(node->leaf(), node->leaf() + pos.count(), + other->leaf()); + } +}; + +template +struct update_visitor : visitor_base> +{ + using node_t = NodeT; + using this_t = update_visitor; + + template + static node_t* visit_relaxed(Pos&& pos, size_t idx, Fn&& fn) + { + auto offset = pos.index(idx); + auto count = pos.count(); + auto node = node_t::make_inner_sr_n(count, pos.relaxed()); + try { + auto child = pos.towards_oh(this_t{}, idx, offset, fn); + node_t::do_copy_inner_sr(node, pos.node(), count); + node->inner()[offset]->dec_unsafe(); + node->inner()[offset] = child; + return node; + } catch (...) { + node_t::delete_inner_r(node, count); + throw; + } + } + + template + static node_t* visit_regular(Pos&& pos, size_t idx, Fn&& fn) + { + auto offset = pos.index(idx); + auto count = pos.count(); + auto node = node_t::make_inner_n(count); + try { + auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn); + node_t::do_copy_inner(node, pos.node(), count); + node->inner()[offset]->dec_unsafe(); + node->inner()[offset] = child; + return node; + } catch (...) { + node_t::delete_inner(node, count); + throw; + } + } + + template + static node_t* visit_leaf(Pos&& pos, size_t idx, Fn&& fn) + { + auto offset = pos.index(idx); + auto node = node_t::copy_leaf(pos.node(), pos.count()); + try { + node->leaf()[offset] = std::forward(fn) ( + std::move(node->leaf()[offset])); + return node; + } catch (...) { + node_t::delete_leaf(node, pos.count()); + throw; + } + } +}; + +struct dec_visitor : visitor_base +{ + using this_t = dec_visitor; + + template + static void visit_relaxed(Pos&& p) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each(this_t{}); + node_t::delete_inner_r(node, p.count()); + } + } + + template + static void visit_regular(Pos&& p) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each(this_t{}); + node_t::delete_inner(node, p.count()); + } + } + + template + static void visit_leaf(Pos&& p) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + node_t::delete_leaf(node, p.count()); + } + } +}; + +template +void dec_leaf(NodeT* node, count_t n) +{ + make_leaf_sub_pos(node, n).visit(dec_visitor{}); +} + +template +void dec_inner(NodeT* node, shift_t shift, size_t size) +{ + visit_maybe_relaxed_sub(node, shift, size, dec_visitor()); +} + +template +void dec_relaxed(NodeT* node, shift_t shift) +{ + make_relaxed_pos(node, shift, node->relaxed()).visit(dec_visitor()); +} + +template +void dec_regular(NodeT* node, shift_t shift, size_t size) +{ + make_regular_pos(node, shift, size).visit(dec_visitor()); +} + +template +void dec_empty_regular(NodeT* node) +{ + make_empty_regular_pos(node).visit(dec_visitor()); +} + +template +struct get_mut_visitor : visitor_base> +{ + using node_t = NodeT; + using this_t = get_mut_visitor; + using value_t = typename NodeT::value_t; + using edit_t = typename NodeT::edit_t; + + template + static value_t& visit_relaxed(Pos&& pos, size_t idx, + edit_t e, node_t** location) + { + auto offset = pos.index(idx); + auto count = pos.count(); + auto node = pos.node(); + if (node->can_mutate(e)) { + return pos.towards_oh(this_t{}, idx, offset, + e, &node->inner()[offset]); + } else { + auto new_node = node_t::copy_inner_sr_e(e, node, count); + try { + auto& res = pos.towards_oh(this_t{}, idx, offset, + e, &new_node->inner()[offset]); + pos.visit(dec_visitor{}); + *location = new_node; + return res; + } catch (...) { + dec_relaxed(new_node, pos.shift()); + throw; + } + } + } + + template + static value_t& visit_regular(Pos&& pos, size_t idx, + edit_t e, node_t** location) + { + assert(pos.node() == *location); + auto offset = pos.index(idx); + auto count = pos.count(); + auto node = pos.node(); + if (node->can_mutate(e)) { + return pos.towards_oh_ch(this_t{}, idx, offset, count, + e, &node->inner()[offset]); + } else { + auto new_node = node_t::copy_inner_e(e, node, count); + try { + auto& res = pos.towards_oh_ch(this_t{}, idx, offset, count, + e, &new_node->inner()[offset]); + pos.visit(dec_visitor{}); + *location = new_node; + return res; + } catch (...) { + dec_regular(new_node, pos.shift(), pos.size()); + throw; + } + } + } + + template + static value_t& visit_leaf(Pos&& pos, size_t idx, + edit_t e, node_t** location) + { + assert(pos.node() == *location); + auto node = pos.node(); + if (node->can_mutate(e)) { + return node->leaf() [pos.index(idx)]; + } else { + auto new_node = node_t::copy_leaf_e(e, pos.node(), pos.count()); + pos.visit(dec_visitor{}); + *location = new_node; + return new_node->leaf() [pos.index(idx)]; + } + } +}; + +template +struct push_tail_mut_visitor + : visitor_base> +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using this_t = push_tail_mut_visitor; + using this_no_mut_t = push_tail_mut_visitor; + using node_t = NodeT; + using edit_t = typename NodeT::edit_t; + + template + static node_t* visit_relaxed(Pos&& pos, edit_t e, node_t* tail, count_t ts) + { + auto node = pos.node(); + auto level = pos.shift(); + auto idx = pos.count() - 1; + auto children = pos.size(idx); + auto new_idx = children == size_t{1} << level || level == BL + ? idx + 1 : idx; + auto new_child = static_cast(nullptr); + auto mutate = Mutating && node->can_mutate(e); + + if (new_idx >= branches) + return nullptr; + else if (idx == new_idx) { + new_child = mutate + ? pos.last_oh_csh(this_t{}, idx, children, e, tail, ts) + : pos.last_oh_csh(this_no_mut_t{}, idx, children, e, tail, ts); + if (!new_child) { + if (++new_idx < branches) + new_child = node_t::make_path_e(e, level - B, tail); + else + return nullptr; + } + } else + new_child = node_t::make_path_e(e, level - B, tail); + + if (mutate) { + auto count = new_idx + 1; + auto relaxed = node->ensure_mutable_relaxed_n(e, new_idx); + node->inner()[new_idx] = new_child; + relaxed->d.sizes[new_idx] = pos.size() + ts; + relaxed->d.count = count; + return node; + } else { + try { + auto count = new_idx + 1; + auto new_node = node_t::copy_inner_r_e(e, pos.node(), new_idx); + auto relaxed = new_node->relaxed(); + new_node->inner()[new_idx] = new_child; + relaxed->d.sizes[new_idx] = pos.size() + ts; + relaxed->d.count = count; + if (Mutating) pos.visit(dec_visitor{}); + return new_node; + } catch (...) { + auto shift = pos.shift(); + auto size = new_idx == idx ? children + ts : ts; + if (shift > BL) { + tail->inc(); + dec_inner(new_child, shift - B, size); + } + throw; + } + } + } + + template + static node_t* visit_regular(Pos&& pos, edit_t e, node_t* tail, Args&&...) + { + assert((pos.size() & mask) == 0); + auto node = pos.node(); + auto idx = pos.index(pos.size() - 1); + auto new_idx = pos.index(pos.size() + branches - 1); + auto mutate = Mutating && node->can_mutate(e); + if (mutate) { + node->inner()[new_idx] = + idx == new_idx ? pos.last_oh(this_t{}, idx, e, tail) + /* otherwise */ : node_t::make_path_e(e, pos.shift() - B, tail); + return node; + } else { + auto new_parent = node_t::make_inner_e(e); + try { + new_parent->inner()[new_idx] = + idx == new_idx ? pos.last_oh(this_no_mut_t{}, idx, e, tail) + /* otherwise */ : node_t::make_path_e(e, pos.shift() - B, tail); + node_t::do_copy_inner(new_parent, node, new_idx); + if (Mutating) pos.visit(dec_visitor{}); + return new_parent; + } catch (...) { + node_t::delete_inner_e(new_parent); + throw; + } + } + } + + template + static node_t* visit_leaf(Pos&& pos, edit_t e, node_t* tail, Args&&...) + { IMMER_UNREACHABLE; } +}; + +template +struct push_tail_visitor : visitor_base> +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using this_t = push_tail_visitor; + using node_t = NodeT; + + template + static node_t* visit_relaxed(Pos&& pos, node_t* tail, count_t ts) + { + auto level = pos.shift(); + auto idx = pos.count() - 1; + auto children = pos.size(idx); + auto new_idx = children == size_t{1} << level || level == BL + ? idx + 1 : idx; + auto new_child = static_cast(nullptr); + if (new_idx >= branches) + return nullptr; + else if (idx == new_idx) { + new_child = pos.last_oh_csh(this_t{}, idx, children, tail, ts); + if (!new_child) { + if (++new_idx < branches) + new_child = node_t::make_path(level - B, tail); + else + return nullptr; + } + } else + new_child = node_t::make_path(level - B, tail); + try { + auto count = new_idx + 1; + auto new_parent = node_t::copy_inner_r_n(count, pos.node(), new_idx); + auto new_relaxed = new_parent->relaxed(); + new_parent->inner()[new_idx] = new_child; + new_relaxed->d.sizes[new_idx] = pos.size() + ts; + new_relaxed->d.count = count; + return new_parent; + } catch (...) { + auto shift = pos.shift(); + auto size = new_idx == idx ? children + ts : ts; + if (shift > BL) { + tail->inc(); + dec_inner(new_child, shift - B, size); + } + throw; + } + } + + template + static node_t* visit_regular(Pos&& pos, node_t* tail, Args&&...) + { + assert((pos.size() & mask) == 0); + auto idx = pos.index(pos.size() - 1); + auto new_idx = pos.index(pos.size() + branches - 1); + auto count = new_idx + 1; + auto new_parent = node_t::make_inner_n(count); + try { + new_parent->inner()[new_idx] = + idx == new_idx ? pos.last_oh(this_t{}, idx, tail) + /* otherwise */ : node_t::make_path(pos.shift() - B, tail); + } catch (...) { + node_t::delete_inner(new_parent, count); + throw; + } + return node_t::do_copy_inner(new_parent, pos.node(), new_idx); + } + + template + static node_t* visit_leaf(Pos&& pos, node_t* tail, Args&&...) + { IMMER_UNREACHABLE; } +}; + +struct dec_right_visitor : visitor_base +{ + using this_t = dec_right_visitor; + using dec_t = dec_visitor; + + template + static void visit_relaxed(Pos&& p, count_t idx) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each_right(dec_t{}, idx); + node_t::delete_inner_r(node, p.count()); + } + } + + template + static void visit_regular(Pos&& p, count_t idx) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each_right(dec_t{}, idx); + node_t::delete_inner(node, p.count()); + } + } + + template + static void visit_leaf(Pos&& p, count_t idx) + { IMMER_UNREACHABLE; } +}; + +template +struct slice_right_mut_visitor + : visitor_base> +{ + using node_t = NodeT; + using this_t = slice_right_mut_visitor; + using edit_t = typename NodeT::edit_t; + + // returns a new shift, new root, the new tail size and the new tail + using result_t = std::tuple; + using no_collapse_t = slice_right_mut_visitor; + using no_collapse_no_mut_t = slice_right_mut_visitor; + using no_mut_t = slice_right_mut_visitor; + + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + template + static result_t visit_relaxed(PosT&& pos, size_t last, edit_t e) + { + auto idx = pos.index(last); + auto node = pos.node(); + auto mutate = Mutating && node->can_mutate(e); + if (Collapse && idx == 0) { + auto res = mutate + ? pos.towards_oh(this_t{}, last, idx, e) + : pos.towards_oh(no_mut_t{}, last, idx, e); + if (Mutating) pos.visit(dec_right_visitor{}, count_t{1}); + return res; + } else { + using std::get; + auto subs = mutate + ? pos.towards_oh(no_collapse_t{}, last, idx, e) + : pos.towards_oh(no_collapse_no_mut_t{}, last, idx, e); + auto next = get<1>(subs); + auto ts = get<2>(subs); + auto tail = get<3>(subs); + try { + if (next) { + if (mutate) { + auto nodr = node->ensure_mutable_relaxed_n(e, idx); + pos.each_right(dec_visitor{}, idx + 1); + node->inner()[idx] = next; + nodr->d.sizes[idx] = last + 1 - ts; + nodr->d.count = idx + 1; + return { pos.shift(), node, ts, tail }; + } else { + auto newn = node_t::copy_inner_r_e(e, node, idx); + auto newr = newn->relaxed(); + newn->inner()[idx] = next; + newr->d.sizes[idx] = last + 1 - ts; + newr->d.count = idx + 1; + if (Mutating) pos.visit(dec_visitor{}); + return { pos.shift(), newn, ts, tail }; + } + } else if (idx == 0) { + if (Mutating) pos.visit(dec_right_visitor{}, count_t{1}); + return { pos.shift(), nullptr, ts, tail }; + } else if (Collapse && idx == 1 && pos.shift() > BL) { + auto newn = pos.node()->inner()[0]; + if (!mutate) newn->inc(); + if (Mutating) pos.visit(dec_right_visitor{}, count_t{2}); + return { pos.shift() - B, newn, ts, tail }; + } else { + if (mutate) { + pos.each_right(dec_visitor{}, idx + 1); + node->ensure_mutable_relaxed_n(e, idx)->d.count = idx; + return { pos.shift(), node, ts, tail }; + } else { + auto newn = node_t::copy_inner_r_e(e, node, idx); + if (Mutating) pos.visit(dec_visitor{}); + return { pos.shift(), newn, ts, tail }; + } + } + } catch (...) { + assert(!mutate); + assert(!next || pos.shift() > BL); + if (next) + dec_inner(next, pos.shift() - B, + last + 1 - ts - pos.size_before(idx)); + dec_leaf(tail, ts); + throw; + } + } + } + + template + static result_t visit_regular(PosT&& pos, size_t last, edit_t e) + { + auto idx = pos.index(last); + auto node = pos.node(); + auto mutate = Mutating && node->can_mutate(e); + if (Collapse && idx == 0) { + auto res = mutate + ? pos.towards_oh(this_t{}, last, idx, e) + : pos.towards_oh(no_mut_t{}, last, idx, e); + if (Mutating) pos.visit(dec_right_visitor{}, count_t{1}); + return res; + } else { + using std::get; + auto subs = mutate + ? pos.towards_oh(no_collapse_t{}, last, idx, e) + : pos.towards_oh(no_collapse_no_mut_t{}, last, idx, e); + auto next = get<1>(subs); + auto ts = get<2>(subs); + auto tail = get<3>(subs); + try { + if (next) { + if (mutate) { + node->inner()[idx] = next; + pos.each_right(dec_visitor{}, idx + 1); + return { pos.shift(), node, ts, tail }; + } else { + auto newn = node_t::copy_inner_e(e, node, idx); + newn->inner()[idx] = next; + if (Mutating) pos.visit(dec_visitor{}); + return { pos.shift(), newn, ts, tail }; + } + } else if (idx == 0) { + if (Mutating) pos.visit(dec_right_visitor{}, count_t{1}); + return { pos.shift(), nullptr, ts, tail }; + } else if (Collapse && idx == 1 && pos.shift() > BL) { + auto newn = pos.node()->inner()[0]; + if (!mutate) newn->inc(); + if (Mutating) pos.visit(dec_right_visitor{}, count_t{2}); + return { pos.shift() - B, newn, ts, tail }; + } else { + if (mutate) { + pos.each_right(dec_visitor{}, idx + 1); + return { pos.shift(), node, ts, tail }; + } else { + auto newn = node_t::copy_inner_e(e, node, idx); + if (Mutating) pos.visit(dec_visitor{}); + return { pos.shift(), newn, ts, tail }; + } + } + } catch (...) { + assert(!mutate); + assert(!next || pos.shift() > BL); + assert(tail); + if (next) dec_regular(next, pos.shift() - B, last + 1 - ts); + dec_leaf(tail, ts); + throw; + } + } + } + + template + static result_t visit_leaf(PosT&& pos, size_t last, edit_t e) + { + auto old_tail_size = pos.count(); + auto new_tail_size = pos.index(last) + 1; + auto node = pos.node(); + auto mutate = Mutating && node->can_mutate(e); + if (new_tail_size == old_tail_size) { + if (!Mutating) node->inc(); + return { 0, nullptr, new_tail_size, node }; + } else if (mutate) { + destroy_n(node->leaf() + new_tail_size, + old_tail_size - new_tail_size); + return { 0, nullptr, new_tail_size, node }; + } else { + auto new_tail = node_t::copy_leaf_e(e, node, new_tail_size); + if (Mutating) pos.visit(dec_visitor{}); + return { 0, nullptr, new_tail_size, new_tail }; + } + } +}; + +template +struct slice_right_visitor : visitor_base> +{ + using node_t = NodeT; + using this_t = slice_right_visitor; + + // returns a new shift, new root, the new tail size and the new tail + using result_t = std::tuple; + using no_collapse_t = slice_right_visitor; + + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + template + static result_t visit_relaxed(PosT&& pos, size_t last) + { + auto idx = pos.index(last); + if (Collapse && idx == 0) { + return pos.towards_oh(this_t{}, last, idx); + } else { + using std::get; + auto subs = pos.towards_oh(no_collapse_t{}, last, idx); + auto next = get<1>(subs); + auto ts = get<2>(subs); + auto tail = get<3>(subs); + try { + if (next) { + auto count = idx + 1; + auto newn = node_t::copy_inner_r_n(count, pos.node(), idx); + auto newr = newn->relaxed(); + newn->inner()[idx] = next; + newr->d.sizes[idx] = last + 1 - ts; + newr->d.count = count; + return { pos.shift(), newn, ts, tail }; + } else if (idx == 0) { + return { pos.shift(), nullptr, ts, tail }; + } else if (Collapse && idx == 1 && pos.shift() > BL) { + auto newn = pos.node()->inner()[0]; + return { pos.shift() - B, newn->inc(), ts, tail }; + } else { + auto newn = node_t::copy_inner_r(pos.node(), idx); + return { pos.shift(), newn, ts, tail }; + } + } catch (...) { + assert(!next || pos.shift() > BL); + if (next) dec_inner(next, pos.shift() - B, + last + 1 - ts - pos.size_before(idx)); + if (tail) dec_leaf(tail, ts); + throw; + } + } + } + + template + static result_t visit_regular(PosT&& pos, size_t last) + { + auto idx = pos.index(last); + if (Collapse && idx == 0) { + return pos.towards_oh(this_t{}, last, idx); + } else { + using std::get; + auto subs = pos.towards_oh(no_collapse_t{}, last, idx); + auto next = get<1>(subs); + auto ts = get<2>(subs); + auto tail = get<3>(subs); + try { + if (next) { + auto newn = node_t::copy_inner_n(idx + 1, pos.node(), idx); + newn->inner()[idx] = next; + return { pos.shift(), newn, ts, tail }; + } else if (idx == 0) { + return { pos.shift(), nullptr, ts, tail }; + } else if (Collapse && idx == 1 && pos.shift() > BL) { + auto newn = pos.node()->inner()[0]; + return { pos.shift() - B, newn->inc(), ts, tail }; + } else { + auto newn = node_t::copy_inner_n(idx, pos.node(), idx); + return { pos.shift(), newn, ts, tail }; + } + } catch (...) { + assert(!next || pos.shift() > BL); + assert(tail); + if (next) dec_regular(next, pos.shift() - B, last + 1 - ts); + dec_leaf(tail, ts); + throw; + } + } + } + + template + static result_t visit_leaf(PosT&& pos, size_t last) + { + auto old_tail_size = pos.count(); + auto new_tail_size = pos.index(last) + 1; + auto new_tail = new_tail_size == old_tail_size + ? pos.node()->inc() + : node_t::copy_leaf(pos.node(), new_tail_size); + return { 0, nullptr, new_tail_size, new_tail }; + } +}; + +struct dec_left_visitor : visitor_base +{ + using this_t = dec_left_visitor; + using dec_t = dec_visitor; + + template + static void visit_relaxed(Pos&& p, count_t idx) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each_left(dec_t{}, idx); + node_t::delete_inner_r(node, p.count()); + } + } + + template + static void visit_regular(Pos&& p, count_t idx) + { + using node_t = node_type; + auto node = p.node(); + if (node->dec()) { + p.each_left(dec_t{}, idx); + node_t::delete_inner(node, p.count()); + } + } + + template + static void visit_leaf(Pos&& p, count_t idx) + { IMMER_UNREACHABLE; } +}; + +template +struct slice_left_mut_visitor + : visitor_base> +{ + using node_t = NodeT; + using this_t = slice_left_mut_visitor; + using edit_t = typename NodeT::edit_t; + using value_t = typename NodeT::value_t; + using relaxed_t = typename NodeT::relaxed_t; + // returns a new shift and new root + using result_t = std::tuple; + + using no_collapse_t = slice_left_mut_visitor; + using no_collapse_no_mut_t = slice_left_mut_visitor; + using no_mut_t = slice_left_mut_visitor; + + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + template + static result_t visit_relaxed(PosT&& pos, size_t first, edit_t e) + { + auto idx = pos.subindex(first); + auto count = pos.count(); + auto node = pos.node(); + auto mutate = Mutating && node->can_mutate(e); + auto left_size = pos.size_before(idx); + auto child_size = pos.size_sbh(idx, left_size); + auto dropped_size = first; + auto child_dropped_size = dropped_size - left_size; + if (Collapse && pos.shift() > BL && idx == pos.count() - 1) { + auto r = mutate + ? pos.towards_sub_oh(this_t{}, first, idx, e) + : pos.towards_sub_oh(no_mut_t{}, first, idx, e); + if (Mutating) pos.visit(dec_left_visitor{}, idx); + return r; + } else { + using std::get; + auto newn = mutate + ? (node->ensure_mutable_relaxed(e), node) + : node_t::make_inner_r_e(e); + auto newr = newn->relaxed(); + auto newcount = count - idx; + auto new_child_size = child_size - child_dropped_size; + try { + auto subs = mutate + ? pos.towards_sub_oh(no_collapse_t{}, first, idx, e) + : pos.towards_sub_oh(no_collapse_no_mut_t{}, first, idx, e); + if (mutate) pos.each_left(dec_visitor{}, idx); + pos.copy_sizes(idx + 1, newcount - 1, + new_child_size, newr->d.sizes + 1); + std::uninitialized_copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); + newn->inner()[0] = get<1>(subs); + newr->d.sizes[0] = new_child_size; + newr->d.count = newcount; + if (!mutate) { + node_t::inc_nodes(newn->inner() + 1, newcount - 1); + if (Mutating) pos.visit(dec_visitor{}); + } + return { pos.shift(), newn }; + } catch (...) { + if (!mutate) node_t::delete_inner_r_e(newn); + throw; + } + } + } + + template + static result_t visit_regular(PosT&& pos, size_t first, edit_t e) + { + auto idx = pos.subindex(first); + auto count = pos.count(); + auto node = pos.node(); + auto mutate = Mutating + // this is more restrictive than actually needed because + // it causes the algorithm to also avoid mutating the leaf + // in place + && !node_t::embed_relaxed + && node->can_mutate(e); + auto left_size = pos.size_before(idx); + auto child_size = pos.size_sbh(idx, left_size); + auto dropped_size = first; + auto child_dropped_size = dropped_size - left_size; + if (Collapse && pos.shift() > BL && idx == pos.count() - 1) { + auto r = mutate + ? pos.towards_sub_oh(this_t{}, first, idx, e) + : pos.towards_sub_oh(no_mut_t{}, first, idx, e); + if (Mutating) pos.visit(dec_left_visitor{}, idx); + return r; + } else { + using std::get; + // if possible, we convert the node to a relaxed one + // simply by allocating a `relaxed_t` size table for + // it... maybe some of this magic should be moved as a + // `node<...>` static method... + auto newcount = count - idx; + auto newn = mutate + ? (node->impl.d.data.inner.relaxed = new ( + node_t::heap::allocate( + node_t::max_sizeof_relaxed, + norefs_tag{})) relaxed_t, + node) + : node_t::make_inner_r_e(e); + auto newr = newn->relaxed(); + try { + auto subs = mutate + ? pos.towards_sub_oh(no_collapse_t{}, first, idx, e) + : pos.towards_sub_oh(no_collapse_no_mut_t{}, first, idx, e); + if (mutate) pos.each_left(dec_visitor{}, idx); + newr->d.sizes[0] = child_size - child_dropped_size; + pos.copy_sizes(idx + 1, newcount - 1, + newr->d.sizes[0], newr->d.sizes + 1); + newr->d.count = newcount; + newn->inner()[0] = get<1>(subs); + std::uninitialized_copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); + if (!mutate) { + node_t::inc_nodes(newn->inner() + 1, newcount - 1); + if (Mutating) pos.visit(dec_visitor{}); + } + return { pos.shift(), newn }; + } catch (...) { + if (!mutate) node_t::delete_inner_r_e(newn); + else { + // restore the regular node that we were + // attempting to relax... + node_t::heap::deallocate(node_t::max_sizeof_relaxed, + node->impl.d.data.inner.relaxed); + node->impl.d.data.inner.relaxed = nullptr; + } + throw; + } + } + } + + template + static result_t visit_leaf(PosT&& pos, size_t first, edit_t e) + { + auto node = pos.node(); + auto idx = pos.index(first); + auto count = pos.count(); + auto mutate = Mutating + && std::is_nothrow_move_constructible::value + && node->can_mutate(e); + if (mutate) { + auto data = node->leaf(); + auto newcount = count - idx; + std::move(data + idx, data + count, data); + destroy_n(data + newcount, idx); + return { 0, node }; + } else { + auto newn = node_t::copy_leaf_e(e, node, idx, count); + if (Mutating) pos.visit(dec_visitor{}); + return { 0, newn }; + } + } +}; + +template +struct slice_left_visitor : visitor_base> +{ + using node_t = NodeT; + using this_t = slice_left_visitor; + + // returns a new shift and new root + using result_t = std::tuple; + using no_collapse_t = slice_left_visitor; + + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + template + static result_t visit_inner(PosT&& pos, size_t first) + { + auto idx = pos.subindex(first); + auto count = pos.count(); + auto left_size = pos.size_before(idx); + auto child_size = pos.size_sbh(idx, left_size); + auto dropped_size = first; + auto child_dropped_size = dropped_size - left_size; + if (Collapse && pos.shift() > BL && idx == pos.count() - 1) { + return pos.towards_sub_oh(this_t{}, first, idx); + } else { + using std::get; + auto n = pos.node(); + auto newc = count - idx; + auto newn = node_t::make_inner_r_n(newc); + try { + auto subs = pos.towards_sub_oh(no_collapse_t{}, first, idx); + auto newr = newn->relaxed(); + newr->d.count = count - idx; + newr->d.sizes[0] = child_size - child_dropped_size; + pos.copy_sizes(idx + 1, newr->d.count - 1, + newr->d.sizes[0], newr->d.sizes + 1); + assert(newr->d.sizes[newr->d.count - 1] == pos.size() - dropped_size); + newn->inner()[0] = get<1>(subs); + std::uninitialized_copy(n->inner() + idx + 1, + n->inner() + count, + newn->inner() + 1); + node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1); + return { pos.shift(), newn }; + } catch (...) { + node_t::delete_inner_r(newn, newc); + throw; + } + } + } + + template + static result_t visit_leaf(PosT&& pos, size_t first) + { + auto n = node_t::copy_leaf(pos.node(), pos.index(first), pos.count()); + return { 0, n }; + } +}; + +template +struct concat_center_pos +{ + static constexpr auto B = Node::bits; + static constexpr auto BL = Node::bits_leaf; + + static constexpr count_t max_children = 3; + + using node_t = Node; + using edit_t = typename Node::edit_t; + + shift_t shift_ = 0u; + count_t count_ = 0u; + node_t* nodes_[max_children]; + size_t sizes_[max_children]; + + auto shift() const { return shift_; } + + concat_center_pos(shift_t s, + Node* n0, size_t s0) + : shift_{s}, count_{1}, nodes_{n0}, sizes_{s0} {} + + concat_center_pos(shift_t s, + Node* n0, size_t s0, + Node* n1, size_t s1) + : shift_{s}, count_{2}, nodes_{n0, n1}, sizes_{s0, s1} {} + + concat_center_pos(shift_t s, + Node* n0, size_t s0, + Node* n1, size_t s1, + Node* n2, size_t s2) + : shift_{s}, count_{3}, nodes_{n0, n1, n2}, sizes_{s0, s1, s2} {} + + template + void each_sub(Visitor v, Args&& ...args) + { + if (shift_ == BL) { + for (auto i = count_t{0}; i < count_; ++i) + make_leaf_sub_pos(nodes_[i], sizes_[i]).visit(v, args...); + } else { + for (auto i = count_t{0}; i < count_; ++i) + make_relaxed_pos(nodes_[i], shift_ - B, nodes_[i]->relaxed()).visit(v, args...); + } + } + + relaxed_pos realize() && + { + if (count_ > 1) { + try { + auto result = node_t::make_inner_r_n(count_); + auto r = result->relaxed(); + r->d.count = count_; + std::copy(nodes_, nodes_ + count_, result->inner()); + std::copy(sizes_, sizes_ + count_, r->d.sizes); + return { result, shift_, r }; + } catch (...) { + each_sub(dec_visitor{}); + throw; + } + } else { + assert(shift_ >= B + BL); + return { nodes_[0], shift_ - B, nodes_[0]->relaxed() }; + } + } + + relaxed_pos realize_e(edit_t e) + { + if (count_ > 1) { + auto result = node_t::make_inner_r_e(e); + auto r = result->relaxed(); + r->d.count = count_; + std::copy(nodes_, nodes_ + count_, result->inner()); + std::copy(sizes_, sizes_ + count_, r->d.sizes); + return { result, shift_, r }; + } else { + assert(shift_ >= B + BL); + return { nodes_[0], shift_ - B, nodes_[0]->relaxed() }; + } + } +}; + +template +struct concat_merger +{ + using node_t = Node; + static constexpr auto B = Node::bits; + static constexpr auto BL = Node::bits_leaf; + + using result_t = concat_center_pos; + + count_t* curr_; + count_t n_; + result_t result_; + + concat_merger(shift_t shift, count_t* counts, count_t n) + : curr_{counts} + , n_{n} + , result_{shift + B, node_t::make_inner_r_n(std::min(n_, branches)), 0} + {} + + node_t* to_ = {}; + count_t to_offset_ = {}; + size_t to_size_ = {}; + + void add_child(node_t* p, size_t size) + { + ++curr_; + auto parent = result_.nodes_[result_.count_ - 1]; + auto relaxed = parent->relaxed(); + if (relaxed->d.count == branches) { + assert(result_.count_ < result_t::max_children); + n_ -= branches; + parent = node_t::make_inner_r_n(std::min(n_, branches)); + relaxed = parent->relaxed(); + result_.nodes_[result_.count_] = parent; + result_.sizes_[result_.count_] = result_.sizes_[result_.count_ - 1]; + ++result_.count_; + } + auto idx = relaxed->d.count++; + result_.sizes_[result_.count_ - 1] += size; + relaxed->d.sizes[idx] = size + (idx ? relaxed->d.sizes[idx - 1] : 0); + parent->inner() [idx] = p; + }; + + template + void merge_leaf(Pos&& p) + { + auto from = p.node(); + auto from_size = p.size(); + auto from_count = p.count(); + assert(from_size); + if (!to_ && *curr_ == from_count) { + add_child(from, from_size); + from->inc(); + } else { + auto from_offset = count_t{}; + auto from_data = from->leaf(); + do { + if (!to_) { + to_ = node_t::make_leaf_n(*curr_); + to_offset_ = 0; + } + auto data = to_->leaf(); + auto to_copy = std::min(from_count - from_offset, + *curr_ - to_offset_); + std::uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + to_offset_ += to_copy; + from_offset += to_copy; + if (*curr_ == to_offset_) { + add_child(to_, to_offset_); + to_ = nullptr; + } + } while (from_offset != from_count); + } + } + + template + void merge_inner(Pos&& p) + { + auto from = p.node(); + auto from_size = p.size(); + auto from_count = p.count(); + assert(from_size); + if (!to_ && *curr_ == from_count) { + add_child(from, from_size); + from->inc(); + } else { + auto from_offset = count_t{}; + auto from_data = from->inner(); + do { + if (!to_) { + to_ = node_t::make_inner_r_n(*curr_); + to_offset_ = 0; + to_size_ = 0; + } + auto data = to_->inner(); + auto to_copy = std::min(from_count - from_offset, + *curr_ - to_offset_); + std::uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + node_t::inc_nodes(from_data + from_offset, to_copy); + auto sizes = to_->relaxed()->d.sizes; + p.copy_sizes(from_offset, to_copy, + to_size_, sizes + to_offset_); + to_offset_ += to_copy; + from_offset += to_copy; + to_size_ = sizes[to_offset_ - 1]; + if (*curr_ == to_offset_) { + to_->relaxed()->d.count = to_offset_; + add_child(to_, to_size_); + to_ = nullptr; + } + } while (from_offset != from_count); + } + } + + concat_center_pos finish() const + { + assert(!to_); + return result_; + } + + void abort() + { + auto shift = result_.shift_ - B; + if (to_) { + if (shift == BL) + node_t::delete_leaf(to_, to_offset_); + else { + to_->relaxed()->d.count = to_offset_; + dec_relaxed(to_, shift - B); + } + } + result_.each_sub(dec_visitor()); + } +}; + +struct concat_merger_visitor : visitor_base +{ + using this_t = concat_merger_visitor; + + template + static void visit_inner(Pos&& p, Merger& merger) + { merger.merge_inner(p); } + + template + static void visit_leaf(Pos&& p, Merger& merger) + { merger.merge_leaf(p); } +}; + +struct concat_rebalance_plan_fill_visitor + : visitor_base +{ + using this_t = concat_rebalance_plan_fill_visitor; + + template + static void visit_node(Pos&& p, Plan& plan) + { + auto count = p.count(); + assert(plan.n < Plan::max_children); + plan.counts[plan.n++] = count; + plan.total += count; + } +}; + +template +struct concat_rebalance_plan +{ + static constexpr auto max_children = 2 * branches + 1; + + count_t counts [max_children]; + count_t n = 0u; + count_t total = 0u; + + template + void fill(LPos&& lpos, CPos&& cpos, RPos&& rpos) + { + assert(n == 0u); + assert(total == 0u); + using visitor_t = concat_rebalance_plan_fill_visitor; + lpos.each_left_sub(visitor_t{}, *this); + cpos.each_sub(visitor_t{}, *this); + rpos.each_right_sub(visitor_t{}, *this); + } + + void shuffle(shift_t shift) + { + // gcc seems to not really understand this code... :( +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + constexpr count_t rrb_extras = 2; + constexpr count_t rrb_invariant = 1; + const auto bits = shift == BL ? BL : B; + const auto branches = count_t{1} << bits; + const auto optimal = ((total - 1) >> bits) + 1; + count_t i = 0; + while (n >= optimal + rrb_extras) { + // skip ok nodes + while (counts[i] > branches - rrb_invariant) i++; + // short node, redistribute + auto remaining = counts[i]; + do { + auto count = std::min(remaining + counts[i+1], branches); + counts[i] = count; + remaining += counts[i + 1] - count; + ++i; + } while (remaining > 0); + // remove node + std::move(counts + i + 1, counts + n, counts + i); + --n; + --i; + } +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + } + + template + concat_center_pos> + merge(LPos&& lpos, CPos&& cpos, RPos&& rpos) + { + using node_t = node_type; + using merger_t = concat_merger; + using visitor_t = concat_merger_visitor; + auto merger = merger_t{cpos.shift(), counts, n}; + try { + lpos.each_left_sub(visitor_t{}, merger); + cpos.each_sub(visitor_t{}, merger); + rpos.each_right_sub(visitor_t{}, merger); + cpos.each_sub(dec_visitor{}); + return merger.finish(); + } catch (...) { + merger.abort(); + throw; + } + } +}; + +template +concat_center_pos +concat_rebalance(LPos&& lpos, CPos&& cpos, RPos&& rpos) +{ + auto plan = concat_rebalance_plan{}; + plan.fill(lpos, cpos, rpos); + plan.shuffle(cpos.shift()); + try { + return plan.merge(lpos, cpos, rpos); + } catch (...) { + cpos.each_sub(dec_visitor{}); + throw; + } +} + +template +concat_center_pos +concat_leafs(LPos&& lpos, TPos&& tpos, RPos&& rpos) +{ + static_assert(Node::bits >= 2, ""); + assert(lpos.shift() == tpos.shift()); + assert(lpos.shift() == rpos.shift()); + assert(lpos.shift() == 0); + if (tpos.count() > 0) + return { + Node::bits_leaf, + lpos.node()->inc(), lpos.count(), + tpos.node()->inc(), tpos.count(), + rpos.node()->inc(), rpos.count(), + }; + else + return { + Node::bits_leaf, + lpos.node()->inc(), lpos.count(), + rpos.node()->inc(), rpos.count(), + }; +} + +template +struct concat_left_visitor; +template +struct concat_right_visitor; +template +struct concat_both_visitor; + +template +concat_center_pos +concat_inners(LPos&& lpos, TPos&& tpos, RPos&& rpos) +{ + auto lshift = lpos.shift(); + auto rshift = rpos.shift(); + if (lshift > rshift) { + auto cpos = lpos.last_sub(concat_left_visitor{}, tpos, rpos); + return concat_rebalance(lpos, cpos, null_sub_pos{}); + } else if (lshift < rshift) { + auto cpos = rpos.first_sub(concat_right_visitor{}, lpos, tpos); + return concat_rebalance(null_sub_pos{}, cpos, rpos); + } else { + assert(lshift == rshift); + assert(Node::bits_leaf == 0u || lshift > 0); + auto cpos = lpos.last_sub(concat_both_visitor{}, tpos, rpos); + return concat_rebalance(lpos, cpos, rpos); + } +} + +template +struct concat_left_visitor : visitor_base> +{ + using this_t = concat_left_visitor; + + template + static concat_center_pos + visit_inner(LPos&& lpos, TPos&& tpos, RPos&& rpos) + { return concat_inners(lpos, tpos, rpos); } + + template + static concat_center_pos + visit_leaf(LPos&& lpos, TPos&& tpos, RPos&& rpos) + { IMMER_UNREACHABLE; } +}; + +template +struct concat_right_visitor : visitor_base> +{ + using this_t = concat_right_visitor; + + template + static concat_center_pos + visit_inner(RPos&& rpos, LPos&& lpos, TPos&& tpos) + { return concat_inners(lpos, tpos, rpos); } + + template + static concat_center_pos + visit_leaf(RPos&& rpos, LPos&& lpos, TPos&& tpos) + { return concat_leafs(lpos, tpos, rpos); } +}; + +template +struct concat_both_visitor + : visitor_base> +{ + using this_t = concat_both_visitor; + + template + static concat_center_pos + visit_inner(LPos&& lpos, TPos&& tpos, RPos&& rpos) + { return rpos.first_sub(concat_right_visitor{}, lpos, tpos); } + + template + static concat_center_pos + visit_leaf(LPos&& lpos, TPos&& tpos, RPos&& rpos) + { return rpos.first_sub_leaf(concat_right_visitor{}, lpos, tpos); } +}; + +template +struct concat_trees_right_visitor + : visitor_base> +{ + using this_t = concat_trees_right_visitor; + + template + static concat_center_pos + visit_node(RPos&& rpos, LPos&& lpos, TPos&& tpos) + { return concat_inners(lpos, tpos, rpos); } +}; + +template +struct concat_trees_left_visitor + : visitor_base> +{ + using this_t = concat_trees_left_visitor; + + template + static concat_center_pos + visit_node(LPos&& lpos, TPos&& tpos, Args&& ...args) + { return visit_maybe_relaxed_sub( + args..., + concat_trees_right_visitor{}, + lpos, tpos); } +}; + +template +relaxed_pos +concat_trees(Node* lroot, shift_t lshift, size_t lsize, + Node* ltail, count_t ltcount, + Node* rroot, shift_t rshift, size_t rsize) +{ + return visit_maybe_relaxed_sub( + lroot, lshift, lsize, + concat_trees_left_visitor{}, + make_leaf_pos(ltail, ltcount), + rroot, rshift, rsize) + .realize(); +} + +template +relaxed_pos +concat_trees(Node* ltail, count_t ltcount, + Node* rroot, shift_t rshift, size_t rsize) +{ + return make_singleton_regular_sub_pos(ltail, ltcount).visit( + concat_trees_left_visitor{}, + empty_leaf_pos{}, + rroot, rshift, rsize) + .realize(); +} + +template +using concat_center_mut_pos = concat_center_pos; + +template +struct concat_merger_mut +{ + using node_t = Node; + using edit_t = typename Node::edit_t; + + static constexpr auto B = Node::bits; + static constexpr auto BL = Node::bits_leaf; + + using result_t = concat_center_pos; + + edit_t ec_ = {}; + + count_t* curr_; + count_t n_; + result_t result_; + count_t count_ = 0; + node_t* candidate_ = nullptr; + edit_t candidate_e_ = Node::memory::transience_t::noone; + + concat_merger_mut(edit_t ec, shift_t shift, + count_t* counts, count_t n, + edit_t candidate_e, node_t* candidate) + : ec_{ec} + , curr_{counts} + , n_{n} + , result_{shift + B, nullptr, 0} + { + if (candidate) { + candidate->ensure_mutable_relaxed_e(candidate_e, ec); + result_.nodes_[0] = candidate; + } else { + result_.nodes_[0] = node_t::make_inner_r_e(ec); + } + } + + node_t* to_ = {}; + count_t to_offset_ = {}; + size_t to_size_ = {}; + + void set_candidate(edit_t candidate_e, node_t* candidate) + { candidate_ = candidate; candidate_e_ = candidate_e; } + + void add_child(node_t* p, size_t size) + { + ++curr_; + auto parent = result_.nodes_[result_.count_ - 1]; + auto relaxed = parent->relaxed(); + if (count_ == branches) { + parent->relaxed()->d.count = count_; + assert(result_.count_ < result_t::max_children); + n_ -= branches; + if (candidate_) { + parent = candidate_; + parent->ensure_mutable_relaxed_e(candidate_e_, ec_); + candidate_ = nullptr; + } else + parent = node_t::make_inner_r_e(ec_); + count_ = 0; + relaxed = parent->relaxed(); + result_.nodes_[result_.count_] = parent; + result_.sizes_[result_.count_] = result_.sizes_[result_.count_ - 1]; + ++result_.count_; + } + auto idx = count_++; + result_.sizes_[result_.count_ - 1] += size; + relaxed->d.sizes[idx] = size + (idx ? relaxed->d.sizes[idx - 1] : 0); + parent->inner() [idx] = p; + }; + + template + void merge_leaf(Pos&& p, edit_t e) + { + auto from = p.node(); + auto from_size = p.size(); + auto from_count = p.count(); + assert(from_size); + if (!to_ && *curr_ == from_count) { + add_child(from, from_size); + } else { + auto from_offset = count_t{}; + auto from_data = from->leaf(); + auto from_mutate = from->can_mutate(e); + do { + if (!to_) { + if (from_mutate) { + node_t::ownee(from) = ec_; + to_ = from->inc(); + assert(from_count); + } else { + to_ = node_t::make_leaf_e(ec_); + } + to_offset_ = 0; + } + auto data = to_->leaf(); + auto to_copy = std::min(from_count - from_offset, + *curr_ - to_offset_); + if (from == to_) { + if (from_offset != to_offset_) + std::move(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + } else { + if (!from_mutate) + std::uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + else + detail::uninitialized_move(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + } + to_offset_ += to_copy; + from_offset += to_copy; + if (*curr_ == to_offset_) { + add_child(to_, to_offset_); + to_ = nullptr; + } + } while (from_offset != from_count); + } + } + + template + void merge_inner(Pos&& p, edit_t e) + { + auto from = p.node(); + auto from_size = p.size(); + auto from_count = p.count(); + assert(from_size); + if (!to_ && *curr_ == from_count) { + add_child(from, from_size); + } else { + auto from_offset = count_t{}; + auto from_data = from->inner(); + auto from_mutate = from->can_relax() && from->can_mutate(e); + do { + if (!to_) { + if (from_mutate) { + node_t::ownee(from) = ec_; + from->ensure_mutable_relaxed_e(e, ec_); + to_ = from; + } else { + to_ = node_t::make_inner_r_e(ec_); + } + to_offset_ = 0; + to_size_ = 0; + } + auto data = to_->inner(); + auto to_copy = std::min(from_count - from_offset, + *curr_ - to_offset_); + auto sizes = to_->relaxed()->d.sizes; + if (from != to_ || from_offset != to_offset_) { + std::copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); + p.copy_sizes(from_offset, to_copy, + to_size_, sizes + to_offset_); + } + to_offset_ += to_copy; + from_offset += to_copy; + to_size_ = sizes[to_offset_ - 1]; + if (*curr_ == to_offset_) { + to_->relaxed()->d.count = to_offset_; + add_child(to_, to_size_); + to_ = nullptr; + } + } while (from_offset != from_count); + } + } + + concat_center_pos finish() const + { + assert(!to_); + result_.nodes_[result_.count_ - 1]->relaxed()->d.count = count_; + return result_; + } + + void abort() + { + // We may have mutated stuff the tree in place, leaving + // everything in a corrupted state... It should be possible + // to define cleanup properly, but that is a task for some + // other day... ;) + std::terminate(); + } +}; + +struct concat_merger_mut_visitor : visitor_base +{ + using this_t = concat_merger_mut_visitor; + + template + static void visit_inner(Pos&& p, + Merger& merger, edit_type e) + { merger.merge_inner(p, e); } + + template + static void visit_leaf(Pos&& p, + Merger& merger, edit_type e) + { merger.merge_leaf(p, e); } +}; + +template +struct concat_rebalance_plan_mut : concat_rebalance_plan +{ + using this_t = concat_rebalance_plan_mut; + + template + concat_center_mut_pos> + merge(edit_type ec, + edit_type el, LPos&& lpos, CPos&& cpos, + edit_type er, RPos&& rpos) + { + using node_t = node_type; + using merger_t = concat_merger_mut; + using visitor_t = concat_merger_mut_visitor; + auto lnode = ((node_t*)lpos.node()); + auto rnode = ((node_t*)rpos.node()); + auto lmut2 = lnode && lnode->can_relax() && lnode->can_mutate(el); + auto rmut2 = rnode && rnode->can_relax() && rnode->can_mutate(er); + auto merger = merger_t{ + ec, cpos.shift(), this->counts, this->n, + el, lmut2 ? lnode : nullptr + }; + try { + lpos.each_left_sub(visitor_t{}, merger, el); + cpos.each_sub(visitor_t{}, merger, ec); + if (rmut2) merger.set_candidate(er, rnode); + rpos.each_right_sub(visitor_t{}, merger, er); + return merger.finish(); + } catch (...) { + merger.abort(); + throw; + } + } +}; + +template +concat_center_pos +concat_rebalance_mut(edit_type ec, + edit_type el, LPos&& lpos, CPos&& cpos, + edit_type er, RPos&& rpos) +{ + auto plan = concat_rebalance_plan_mut{}; + plan.fill(lpos, cpos, rpos); + plan.shuffle(cpos.shift()); + return plan.merge(ec, el, lpos, cpos, er, rpos); +} + +template +concat_center_mut_pos +concat_leafs_mut(edit_type ec, + edit_type el, LPos&& lpos, TPos&& tpos, + edit_type er, RPos&& rpos) +{ + static_assert(Node::bits >= 2, ""); + assert(lpos.shift() == tpos.shift()); + assert(lpos.shift() == rpos.shift()); + assert(lpos.shift() == 0); + if (tpos.count() > 0) + return { + Node::bits_leaf, + lpos.node(), lpos.count(), + tpos.node(), tpos.count(), + rpos.node(), rpos.count(), + }; + else + return { + Node::bits_leaf, + lpos.node(), lpos.count(), + rpos.node(), rpos.count(), + }; +} + +template +struct concat_left_mut_visitor; +template +struct concat_right_mut_visitor; +template +struct concat_both_mut_visitor; + +template +concat_center_mut_pos +concat_inners_mut(edit_type ec, + edit_type el, LPos&& lpos, TPos&& tpos, + edit_type er, RPos&& rpos) +{ + auto lshift = lpos.shift(); + auto rshift = rpos.shift(); + // lpos.node() can be null it is a singleton_regular_sub_pos<...>, + // this is, when the tree is just a tail... + if (lshift > rshift) { + auto cpos = lpos.last_sub(concat_left_mut_visitor{}, + ec, el, tpos, er, rpos); + return concat_rebalance_mut(ec, + el, lpos, cpos, + er, null_sub_pos{}); + } else if (lshift < rshift) { + auto cpos = rpos.first_sub(concat_right_mut_visitor{}, + ec, el, lpos, tpos, er); + return concat_rebalance_mut(ec, + el, null_sub_pos{}, cpos, + er, rpos); + } else { + assert(lshift == rshift); + assert(Node::bits_leaf == 0u || lshift > 0); + auto cpos = lpos.last_sub(concat_both_mut_visitor{}, + ec, el, tpos, er, rpos); + return concat_rebalance_mut(ec, + el, lpos, cpos, + er, rpos); + } +} + +template +struct concat_left_mut_visitor : visitor_base> +{ + using this_t = concat_left_mut_visitor; + using edit_t = typename Node::edit_t; + + template + static concat_center_mut_pos + visit_inner(LPos&& lpos, edit_t ec, + edit_t el, TPos&& tpos, + edit_t er, RPos&& rpos) + { return concat_inners_mut( + ec, el, lpos, tpos, er, rpos); } + + template + static concat_center_mut_pos + visit_leaf(LPos&& lpos, edit_t ec, + edit_t el, TPos&& tpos, + edit_t er, RPos&& rpos) + { IMMER_UNREACHABLE; } +}; + +template +struct concat_right_mut_visitor + : visitor_base> +{ + using this_t = concat_right_mut_visitor; + using edit_t = typename Node::edit_t; + + template + static concat_center_mut_pos + visit_inner(RPos&& rpos, edit_t ec, + edit_t el, LPos&& lpos, TPos&& tpos, + edit_t er) + { return concat_inners_mut( + ec, el, lpos, tpos, er, rpos); } + + template + static concat_center_mut_pos + visit_leaf(RPos&& rpos, edit_t ec, + edit_t el, LPos&& lpos, TPos&& tpos, + edit_t er) + { return concat_leafs_mut( + ec, el, lpos, tpos, er, rpos); } +}; + +template +struct concat_both_mut_visitor + : visitor_base> +{ + using this_t = concat_both_mut_visitor; + using edit_t = typename Node::edit_t; + + template + static concat_center_mut_pos + visit_inner(LPos&& lpos, edit_t ec, + edit_t el, TPos&& tpos, + edit_t er, RPos&& rpos) + { return rpos.first_sub(concat_right_mut_visitor{}, + ec, el, lpos, tpos, er); } + + template + static concat_center_mut_pos + visit_leaf(LPos&& lpos, edit_t ec, + edit_t el, TPos&& tpos, + edit_t er, RPos&& rpos) + { return rpos.first_sub_leaf(concat_right_mut_visitor{}, + ec, el, lpos, tpos, er); } +}; + +template +struct concat_trees_right_mut_visitor + : visitor_base> +{ + using this_t = concat_trees_right_mut_visitor; + using edit_t = typename Node::edit_t; + + template + static concat_center_mut_pos + visit_node(RPos&& rpos, edit_t ec, + edit_t el, LPos&& lpos, TPos&& tpos, + edit_t er) + { return concat_inners_mut( + ec, el, lpos, tpos, er, rpos); } +}; + +template +struct concat_trees_left_mut_visitor + : visitor_base> +{ + using this_t = concat_trees_left_mut_visitor; + using edit_t = typename Node::edit_t; + + template + static concat_center_mut_pos + visit_node(LPos&& lpos, edit_t ec, + edit_t el, TPos&& tpos, + edit_t er, Args&& ...args) + { return visit_maybe_relaxed_sub( + args..., + concat_trees_right_mut_visitor{}, + ec, el, lpos, tpos, er); } +}; + +template +relaxed_pos +concat_trees_mut(edit_type ec, + edit_type el, + Node* lroot, shift_t lshift, size_t lsize, + Node* ltail, count_t ltcount, + edit_type er, + Node* rroot, shift_t rshift, size_t rsize) +{ + return visit_maybe_relaxed_sub( + lroot, lshift, lsize, + concat_trees_left_mut_visitor{}, + ec, + el, make_leaf_pos(ltail, ltcount), + er, rroot, rshift, rsize) + .realize_e(ec); +} + +template +relaxed_pos +concat_trees_mut(edit_type ec, + edit_type el, + Node* ltail, count_t ltcount, + edit_type er, + Node* rroot, shift_t rshift, size_t rsize) +{ + return make_singleton_regular_sub_pos(ltail, ltcount).visit( + concat_trees_left_mut_visitor{}, + ec, + el, empty_leaf_pos{}, + er, rroot, rshift, rsize) + .realize_e(ec); +} + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/position.hpp b/src/immer/detail/rbts/position.hpp new file mode 100644 index 000000000000..4ff579f8f90b --- /dev/null +++ b/src/immer/detail/rbts/position.hpp @@ -0,0 +1,1846 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +template +constexpr auto bits = std::decay_t::node_t::bits; + +template +constexpr auto bits_leaf = std::decay_t::node_t::bits_leaf; + +template +using node_type = typename std::decay::type::node_t; + +template +using edit_type = typename std::decay::type::node_t::edit_t; + +template +struct empty_regular_pos +{ + using node_t = NodeT; + node_t* node_; + + count_t count() const { return 0; } + node_t* node() const { return node_; } + shift_t shift() const { return 0; } + size_t size() const { return 0; } + + template + void each(Visitor, Args&&...) {} + template + bool each_pred(Visitor, Args&&...) { return true; } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +empty_regular_pos make_empty_regular_pos(NodeT* node) +{ + return {node}; +} + +template +struct empty_leaf_pos +{ + using node_t = NodeT; + node_t* node_; + + count_t count() const { return 0; } + node_t* node() const { return node_; } + shift_t shift() const { return 0; } + size_t size() const { return 0; } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_leaf(*this, std::forward(args)...); + } +}; + +template +empty_leaf_pos make_empty_leaf_pos(NodeT* node) +{ + assert(node); + return {node}; +} + +template +struct leaf_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + size_t size_; + + count_t count() const { return index(size_ - 1) + 1; } + node_t* node() const { return node_; } + size_t size() const { return size_; } + shift_t shift() const { return 0; } + count_t index(size_t idx) const { return idx & mask; } + count_t subindex(size_t idx) const { return idx; } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_leaf(*this, std::forward(args)...); + } +}; + +template +leaf_pos make_leaf_pos(NodeT* node, size_t size) +{ + assert(node); + assert(size > 0); + return {node, size}; +} + +template +struct leaf_sub_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + count_t count_; + + count_t count() const { return count_; } + node_t* node() const { return node_; } + size_t size() const { return count_; } + shift_t shift() const { return 0; } + count_t index(size_t idx) const { return idx & mask; } + count_t subindex(size_t idx) const { return idx; } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_leaf(*this, std::forward(args)...); + } +}; + +template +leaf_sub_pos make_leaf_sub_pos(NodeT* node, count_t count) +{ + assert(node); + assert(count <= branches); + return {node, count}; +} + +template +struct leaf_descent_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + + node_t* node() const { return node_; } + shift_t shift() const { return 0; } + count_t index(size_t idx) const { return idx & mask; } + + template + decltype(auto) descend(Args&&...) {} + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_leaf(*this, std::forward(args)...); + } +}; + +template +leaf_descent_pos make_leaf_descent_pos(NodeT* node) +{ + assert(node); + return {node}; +} + +template +struct full_leaf_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + + count_t count() const { return branches; } + node_t* node() const { return node_; } + size_t size() const { return branches; } + shift_t shift() const { return 0; } + count_t index(size_t idx) const { return idx & mask; } + count_t subindex(size_t idx) const { return idx; } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_leaf(*this, std::forward(args)...); + } +}; + +template +full_leaf_pos make_full_leaf_pos(NodeT* node) +{ + assert(node); + return {node}; +} + +template +struct regular_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + shift_t shift_; + size_t size_; + + count_t count() const { return index(size_ - 1) + 1; } + node_t* node() const { return node_; } + size_t size() const { return size_; } + shift_t shift() const { return shift_; } + count_t index(size_t idx) const { return (idx >> shift_) & mask; } + count_t subindex(size_t idx) const { return idx >> shift_; } + size_t this_size() const { return ((size_ - 1) & ~(~size_t{} << (shift_ + B))) + 1; } + + template + void each(Visitor v, Args&&... args) + { return each_regular(*this, v, args...); } + + template + bool each_pred(Visitor v, Args&&... args) + { return each_pred_regular(*this, v, args...); } + + template + bool each_pred_zip(Visitor v, node_t* other, Args&&... args) + { return each_pred_zip_regular(*this, v, other, args...); } + + template + bool each_pred_i(Visitor v, count_t i, count_t n, Args&&... args) + { return each_pred_i_regular(*this, v, i, n, args...); } + + template + bool each_pred_right(Visitor v, count_t start, Args&&... args) + { return each_pred_right_regular(*this, v, start, args...); } + + template + bool each_pred_left(Visitor v, count_t n, Args&&... args) + { return each_pred_left_regular(*this, v, n, args...); } + + template + void each_i(Visitor v, count_t i, count_t n, Args&&... args) + { return each_i_regular(*this, v, i, n, args...); } + + template + void each_right(Visitor v, count_t start, Args&&... args) + { return each_right_regular(*this, v, start, args...); } + + template + void each_left(Visitor v, count_t n, Args&&... args) + { return each_left_regular(*this, v, n, args...); } + + template + decltype(auto) towards(Visitor v, size_t idx, Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, index(idx), count(), args...); } + + template + decltype(auto) towards_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, offset_hint, count(), args...); } + + template + decltype(auto) towards_oh_ch(Visitor v, size_t idx, + count_t offset_hint, + count_t count_hint, + Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, offset_hint, count(), args...); } + + template + decltype(auto) towards_sub_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { return towards_sub_oh_regular(*this, v, idx, offset_hint, args...); } + + template + decltype(auto) last_oh(Visitor v, count_t offset_hint, Args&&... args) + { return last_oh_regular(*this, v, offset_hint, args...); } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +void each_regular(Pos&& p, Visitor v, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + auto n = p.node()->inner(); + auto last = p.count() - 1; + auto e = n + last; + if (p.shift() == BL) { + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + make_leaf_pos(*n, p.size()).visit(v, args...); + } else { + auto ss = p.shift() - B; + for (; n != e; ++n) + make_full_pos(*n, ss).visit(v, args...); + make_regular_pos(*n, ss, p.size()).visit(v, args...); + } +} + +template +bool each_pred_regular(Pos&& p, Visitor v, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + auto n = p.node()->inner(); + auto last = p.count() - 1; + auto e = n + last; + if (p.shift() == BL) { + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + if (!make_full_leaf_pos(*n).visit(v, args...)) + return false; + } + return make_leaf_pos(*n, p.size()).visit(v, args...); + } else { + auto ss = p.shift() - B; + for (; n != e; ++n) + if (!make_full_pos(*n, ss).visit(v, args...)) + return false; + return make_regular_pos(*n, ss, p.size()).visit(v, args...); + } +} + +template +bool each_pred_zip_regular(Pos&& p, Visitor v, node_type* other, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + + auto n = p.node()->inner(); + auto n2 = other->inner(); + auto last = p.count() - 1; + auto e = n + last; + if (p.shift() == BL) { + for (; n != e; ++n, ++n2) { + IMMER_PREFETCH(n + 1); + IMMER_PREFETCH(n2 + 1); + if (!make_full_leaf_pos(*n).visit(v, *n2, args...)) + return false; + } + return make_leaf_pos(*n, p.size()).visit(v, *n2, args...); + } else { + auto ss = p.shift() - B; + for (; n != e; ++n, ++n2) + if (!make_full_pos(*n, ss).visit(v, *n2, args...)) + return false; + return make_regular_pos(*n, ss, p.size()).visit(v, *n2, args...); + } +} + +template +bool each_pred_i_regular(Pos&& p, Visitor v, count_t f, count_t l, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + + if (p.shift() == BL) { + if (l > f) { + if (l < p.count()) { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l; + for (; n < e; ++n) { + IMMER_PREFETCH(n + 1); + if (!make_full_leaf_pos(*n).visit(v, args...)) + return false; + } + } else { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l - 1; + for (; n < e; ++n) { + IMMER_PREFETCH(n + 1); + if (!make_full_leaf_pos(*n).visit(v, args...)) + return false; + } + if (!make_leaf_pos(*n, p.size()).visit(v, args...)) + return false; + } + } + } else { + if (l > f) { + auto ss = p.shift() - B; + if (l < p.count()) { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l; + for (; n < e; ++n) + if (!make_full_pos(*n, ss).visit(v, args...)) + return false; + } else { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l - 1; + for (; n < e; ++n) + if (!make_full_pos(*n, ss).visit(v, args...)) + return false; + if (!make_regular_pos(*n, ss, p.size()).visit(v, args...)) + return false; + } + } + } + return true; +} + +template +bool each_pred_left_regular(Pos&& p, Visitor v, count_t last, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + assert(last < p.count()); + if (p.shift() == BL) { + auto n = p.node()->inner(); + auto e = n + last; + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + if (!make_full_leaf_pos(*n).visit(v, args...)) + return false; + } + } else { + auto n = p.node()->inner(); + auto e = n + last; + auto ss = p.shift() - B; + for (; n != e; ++n) + if (!make_full_pos(*n, ss).visit(v, args...)) + return false; + } + return true; +} + +template +bool each_pred_right_regular(Pos&& p, Visitor v, count_t start, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + + if (p.shift() == BL) { + auto n = p.node()->inner() + start; + auto last = p.count() - 1; + auto e = p.node()->inner() + last; + if (n <= e) { + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + if (!make_full_leaf_pos(*n).visit(v, args...)) + return false; + } + if (!make_leaf_pos(*n, p.size()).visit(v, args...)) + return false; + } + } else { + auto n = p.node()->inner() + start; + auto last = p.count() - 1; + auto e = p.node()->inner() + last; + auto ss = p.shift() - B; + if (n <= e) { + for (; n != e; ++n) + if (!make_full_pos(*n, ss).visit(v, args...)) + return false; + if (!make_regular_pos(*n, ss, p.size()).visit(v, args...)) + return false; + } + } + return true; +} + +template +void each_i_regular(Pos&& p, Visitor v, count_t f, count_t l, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + + if (p.shift() == BL) { + if (l > f) { + if (l < p.count()) { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l; + for (; n < e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + } else { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l - 1; + for (; n < e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + make_leaf_pos(*n, p.size()).visit(v, args...); + } + } + } else { + if (l > f) { + auto ss = p.shift() - B; + if (l < p.count()) { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l; + for (; n < e; ++n) + make_full_pos(*n, ss).visit(v, args...); + } else { + auto n = p.node()->inner() + f; + auto e = p.node()->inner() + l - 1; + for (; n < e; ++n) + make_full_pos(*n, ss).visit(v, args...); + make_regular_pos(*n, ss, p.size()).visit(v, args...); + } + } + } +} + +template +void each_left_regular(Pos&& p, Visitor v, count_t last, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + assert(last < p.count()); + if (p.shift() == BL) { + auto n = p.node()->inner(); + auto e = n + last; + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + } else { + auto n = p.node()->inner(); + auto e = n + last; + auto ss = p.shift() - B; + for (; n != e; ++n) + make_full_pos(*n, ss).visit(v, args...); + } +} + +template +void each_right_regular(Pos&& p, Visitor v, count_t start, Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + + if (p.shift() == BL) { + auto n = p.node()->inner() + start; + auto last = p.count() - 1; + auto e = p.node()->inner() + last; + if (n <= e) { + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + make_leaf_pos(*n, p.size()).visit(v, args...); + } + } else { + auto n = p.node()->inner() + start; + auto last = p.count() - 1; + auto e = p.node()->inner() + last; + auto ss = p.shift() - B; + if (n <= e) { + for (; n != e; ++n) + make_full_pos(*n, ss).visit(v, args...); + make_regular_pos(*n, ss, p.size()).visit(v, args...); + } + } +} + +template +decltype(auto) towards_oh_ch_regular(Pos&& p, Visitor v, size_t idx, + count_t offset_hint, + count_t count_hint, + Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + assert(offset_hint == p.index(idx)); + assert(count_hint == p.count()); + auto is_leaf = p.shift() == BL; + auto child = p.node()->inner() [offset_hint]; + auto is_full = offset_hint + 1 != count_hint; + return is_full + ? (is_leaf + ? make_full_leaf_pos(child).visit(v, idx, args...) + : make_full_pos(child, p.shift() - B).visit(v, idx, args...)) + : (is_leaf + ? make_leaf_pos(child, p.size()).visit(v, idx, args...) + : make_regular_pos(child, p.shift() - B, p.size()).visit(v, idx, args...)); +} + +template +decltype(auto) towards_sub_oh_regular(Pos&& p, Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) +{ + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + assert(offset_hint == p.index(idx)); + auto is_leaf = p.shift() == BL; + auto child = p.node()->inner() [offset_hint]; + auto lsize = offset_hint << p.shift(); + auto size = p.this_size(); + auto is_full = (size - lsize) >= (size_t{1} << p.shift()); + return is_full + ? (is_leaf + ? make_full_leaf_pos(child).visit( + v, idx - lsize, args...) + : make_full_pos(child, p.shift() - B).visit( + v, idx - lsize, args...)) + : (is_leaf + ? make_leaf_sub_pos(child, size - lsize).visit( + v, idx - lsize, args...) + : make_regular_sub_pos(child, p.shift() - B, size - lsize).visit( + v, idx - lsize, args...)); +} + +template +decltype(auto) last_oh_regular(Pos&& p, Visitor v, + count_t offset_hint, + Args&&... args) +{ + assert(offset_hint == p.count() - 1); + constexpr auto B = bits; + constexpr auto BL = bits_leaf; + auto child = p.node()->inner() [offset_hint]; + auto is_leaf = p.shift() == BL; + return is_leaf + ? make_leaf_pos(child, p.size()).visit(v, args...) + : make_regular_pos(child, p.shift() - B, p.size()).visit(v, args...); +} + +template +regular_pos make_regular_pos(NodeT* node, + shift_t shift, + size_t size) +{ + assert(node); + assert(shift >= NodeT::bits_leaf); + assert(size > 0); + return {node, shift, size}; +} + +struct null_sub_pos +{ + auto node() const { return nullptr; } + + template + void each_sub(Visitor, Args&&...) {} + template + void each_right_sub(Visitor, Args&&...) {} + template + void each_left_sub(Visitor, Args&&...) {} + template + void visit(Visitor, Args&&...) {} +}; + +template +struct singleton_regular_sub_pos +{ + // this is a fake regular pos made out of a single child... useful + // to treat a single leaf node as a whole tree + + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* leaf_; + count_t count_; + + count_t count() const { return 1; } + node_t* node() const { return nullptr; } + size_t size() const { return count_; } + shift_t shift() const { return BL; } + count_t index(size_t idx) const { return 0; } + count_t subindex(size_t idx) const { return 0; } + size_t size_before(count_t offset) const { return 0; } + size_t this_size() const { return count_; } + size_t size(count_t offset) { return count_; } + + template + void each_left_sub(Visitor v, Args&&... args) {} + template + void each(Visitor v, Args&&... args) {} + + template + decltype(auto) last_sub(Visitor v, Args&&... args) + { + return make_leaf_sub_pos(leaf_, count_).visit(v, args...); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +auto make_singleton_regular_sub_pos(NodeT* leaf, count_t count) +{ + assert(leaf); + assert(leaf->kind() == NodeT::kind_t::leaf); + assert(count > 0); + return singleton_regular_sub_pos{leaf, count}; +} + +template +struct regular_sub_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + shift_t shift_; + size_t size_; + + count_t count() const { return subindex(size_ - 1) + 1; } + node_t* node() const { return node_; } + size_t size() const { return size_; } + shift_t shift() const { return shift_; } + count_t index(size_t idx) const { return (idx >> shift_) & mask; } + count_t subindex(size_t idx) const { return idx >> shift_; } + size_t size_before(count_t offset) const { return offset << shift_; } + size_t this_size() const { return size_; } + + auto size(count_t offset) + { + return offset == subindex(size_ - 1) + ? size_ - size_before(offset) + : 1 << shift_; + } + + auto size_sbh(count_t offset, size_t size_before_hint) + { + assert(size_before_hint == size_before(offset)); + return offset == subindex(size_ - 1) + ? size_ - size_before_hint + : 1 << shift_; + } + + void copy_sizes(count_t offset, + count_t n, + size_t init, + size_t* sizes) + { + if (n) { + auto last = offset + n - 1; + auto e = sizes + n - 1; + for (; sizes != e; ++sizes) + init = *sizes = init + (1 << shift_); + *sizes = init + size(last); + } + } + + template + void each(Visitor v, Args&& ...args) + { return each_regular(*this, v, args...); } + + template + bool each_pred(Visitor v, Args&& ...args) + { return each_pred_regular(*this, v, args...); } + + template + bool each_pred_zip(Visitor v, node_t* other, Args&&... args) + { return each_pred_zip_regular(*this, v, other, args...); } + + template + bool each_pred_i(Visitor v, count_t i, count_t n, Args&& ...args) + { return each_pred_i_regular(*this, v, i, n, args...); } + + template + bool each_pred_right(Visitor v, count_t start, Args&& ...args) + { return each_pred_right_regular(*this, v, start, args...); } + + template + bool each_pred_left(Visitor v, count_t last, Args&& ...args) + { return each_pred_left_regular(*this, v, last, args...); } + + template + void each_i(Visitor v, count_t i, count_t n, Args&& ...args) + { return each_i_regular(*this, v, i, n, args...); } + + template + void each_right(Visitor v, count_t start, Args&& ...args) + { return each_right_regular(*this, v, start, args...); } + + template + void each_left(Visitor v, count_t last, Args&& ...args) + { return each_left_regular(*this, v, last, args...); } + + template + void each_right_sub_(Visitor v, count_t i, Args&& ...args) + { + auto last = count() - 1; + auto lsize = size_ - (last << shift_); + auto n = node()->inner() + i; + auto e = node()->inner() + last; + if (shift() == BL) { + for (; n != e; ++n) { + IMMER_PREFETCH(n + 1); + make_full_leaf_pos(*n).visit(v, args...); + } + make_leaf_sub_pos(*n, lsize).visit(v, args...); + } else { + auto ss = shift_ - B; + for (; n != e; ++n) + make_full_pos(*n, ss).visit(v, args...); + make_regular_sub_pos(*n, ss, lsize).visit(v, args...); + } + } + + template + void each_sub(Visitor v, Args&& ...args) + { each_right_sub_(v, 0, args...); } + + template + void each_right_sub(Visitor v, Args&& ...args) + { if (count() > 1) each_right_sub_(v, 1, args...); } + + template + void each_left_sub(Visitor v, Args&& ...args) + { each_left(v, count() - 1, args...); } + + template + decltype(auto) towards(Visitor v, size_t idx, Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, index(idx), count(), args...); } + + template + decltype(auto) towards_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, offset_hint, count(), args...); } + + template + decltype(auto) towards_oh_ch(Visitor v, size_t idx, + count_t offset_hint, + count_t count_hint, + Args&&... args) + { return towards_oh_ch_regular(*this, v, idx, offset_hint, count(), args...); } + + template + decltype(auto) towards_sub_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&& ...args) + { return towards_sub_oh_regular(*this, v, idx, offset_hint, args...); } + + template + decltype(auto) last_oh(Visitor v, count_t offset_hint, Args&&... args) + { return last_oh_regular(*this, v, offset_hint, args...); } + + template + decltype(auto) last_sub(Visitor v, Args&&... args) + { + auto offset = count() - 1; + auto child = node_->inner() [offset]; + auto is_leaf = shift_ == BL; + auto lsize = size_ - (offset << shift_); + return is_leaf + ? make_leaf_sub_pos(child, lsize).visit(v, args...) + : make_regular_sub_pos(child, shift_ - B, lsize).visit(v, args...); + } + + template + decltype(auto) first_sub(Visitor v, Args&&... args) + { + auto is_leaf = shift_ == BL; + auto child = node_->inner() [0]; + auto is_full = size_ >= (size_t{1} << shift_); + return is_full + ? (is_leaf + ? make_full_leaf_pos(child).visit(v, args...) + : make_full_pos(child, shift_ - B).visit(v, args...)) + : (is_leaf + ? make_leaf_sub_pos(child, size_).visit(v, args...) + : make_regular_sub_pos(child, shift_ - B, size_).visit(v, args...)); + } + + template + decltype(auto) first_sub_leaf(Visitor v, Args&&... args) + { + assert(shift_ == BL); + auto child = node_->inner() [0]; + auto is_full = size_ >= branches; + return is_full + ? make_full_leaf_pos(child).visit(v, args...) + : make_leaf_sub_pos(child, size_).visit(v, args...); + } + + template + decltype(auto) first_sub_inner(Visitor v, Args&&... args) + { + assert(shift_ >= BL); + auto child = node_->inner() [0]; + auto is_full = size_ >= branches; + return is_full + ? make_full_pos(child, shift_ - B).visit(v, args...) + : make_regular_sub_pos(child, shift_ - B, size_).visit(v, args...); + } + + template + decltype(auto) nth_sub(count_t idx, Visitor v, Args&&... args) + { + assert(idx < count()); + auto is_leaf = shift_ == BL; + auto child = node_->inner() [idx]; + auto lsize = size(idx); + auto is_full = idx + 1 < count(); + return is_full + ? (is_leaf + ? make_full_leaf_pos(child).visit(v, args...) + : make_full_pos(child, shift_ - B).visit(v, args...)) + : (is_leaf + ? make_leaf_sub_pos(child, lsize).visit(v, args...) + : make_regular_sub_pos(child, shift_ - B, lsize).visit(v, args...)); + } + + template + decltype(auto) nth_sub_leaf(count_t idx, Visitor v, Args&&... args) + { + assert(shift_ == BL); + auto child = node_->inner() [idx]; + auto lsize = size(idx); + auto is_full = idx + 1 < count(); + return is_full + ? make_full_leaf_pos(child).visit(v, args...) + : make_leaf_sub_pos(child, lsize).visit(v, args...); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +regular_sub_pos make_regular_sub_pos(NodeT* node, + shift_t shift, + size_t size) +{ + assert(node); + assert(shift >= NodeT::bits_leaf); + assert(size > 0); + assert(size <= (branches << shift)); + return {node, shift, size}; +} + +template +struct regular_descent_pos +{ + static_assert(Shift > 0, "not leaf..."); + + using node_t = NodeT; + node_t* node_; + + node_t* node() const { return node_; } + shift_t shift() const { return Shift; } + count_t index(size_t idx) const { +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#endif + return (idx >> Shift) & mask; +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + } + + template + decltype(auto) descend(Visitor v, size_t idx) + { + auto offset = index(idx); + auto child = node_->inner()[offset]; + return regular_descent_pos{child}.visit(v, idx); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +struct regular_descent_pos +{ + using node_t = NodeT; + node_t* node_; + + node_t* node() const { return node_; } + shift_t shift() const { return BL; } + count_t index(size_t idx) const { return (idx >> BL) & mask; } + + template + decltype(auto) descend(Visitor v, size_t idx) + { + auto offset = index(idx); + auto child = node_->inner()[offset]; + return make_leaf_descent_pos(child).visit(v, idx); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +decltype(auto) visit_regular_descent(NodeT* node, shift_t shift, Visitor v, + size_t idx) +{ + constexpr auto B = NodeT::bits; + constexpr auto BL = NodeT::bits_leaf; + assert(node); + assert(shift >= BL); + switch (shift) { + case BL + B * 0: return regular_descent_pos{node}.visit(v, idx); + case BL + B * 1: return regular_descent_pos{node}.visit(v, idx); + case BL + B * 2: return regular_descent_pos{node}.visit(v, idx); + case BL + B * 3: return regular_descent_pos{node}.visit(v, idx); + case BL + B * 4: return regular_descent_pos{node}.visit(v, idx); + case BL + B * 5: return regular_descent_pos{node}.visit(v, idx); +#if IMMER_DESCENT_DEEP + default: + for (auto level = shift; level != endshift; level -= B) + node = node->inner() [(idx >> level) & mask]; + return make_leaf_descent_pos(node).visit(v, idx); +#endif // IMMER_DEEP_DESCENT + } + IMMER_UNREACHABLE; +} + +template +struct full_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + node_t* node_; + shift_t shift_; + + count_t count() const { return branches; } + node_t* node() const { return node_; } + size_t size() const { return branches << shift_; } + shift_t shift() const { return shift_; } + count_t index(size_t idx) const { return (idx >> shift_) & mask; } + count_t subindex(size_t idx) const { return idx >> shift_; } + size_t size(count_t offset) const { return 1 << shift_; } + size_t size_sbh(count_t offset, size_t) const { return 1 << shift_; } + size_t size_before(count_t offset) const { return offset << shift_; } + + void copy_sizes(count_t offset, + count_t n, + size_t init, + size_t* sizes) + { + auto e = sizes + n; + for (; sizes != e; ++sizes) + init = *sizes = init + (1 << shift_); + } + + template + void each(Visitor v, Args&&... args) + { + auto p = node_->inner(); + auto e = p + branches; + if (shift_ == BL) { + for (; p != e; ++p) { + IMMER_PREFETCH(p + 1); + make_full_leaf_pos(*p).visit(v, args...); + } + } else { + auto ss = shift_ - B; + for (; p != e; ++p) + make_full_pos(*p, ss).visit(v, args...); + } + } + + template + bool each_pred(Visitor v, Args&&... args) + { + auto p = node_->inner(); + auto e = p + branches; + if (shift_ == BL) { + for (; p != e; ++p) { + IMMER_PREFETCH(p + 1); + if (!make_full_leaf_pos(*p).visit(v, args...)) + return false; + } + } else { + auto ss = shift_ - B; + for (; p != e; ++p) + if (!make_full_pos(*p, ss).visit(v, args...)) + return false; + } + return true; + } + + template + bool each_pred_zip(Visitor v, node_t* other, Args&&... args) + { + auto p = node_->inner(); + auto p2 = other->inner(); + auto e = p + branches; + if (shift_ == BL) { + for (; p != e; ++p, ++p2) { + IMMER_PREFETCH(p + 1); + if (!make_full_leaf_pos(*p).visit(v, *p2, args...)) + return false; + } + } else { + auto ss = shift_ - B; + for (; p != e; ++p, ++p2) + if (!make_full_pos(*p, ss).visit(v, *p2, args...)) + return false; + } + return true; + } + + template + bool each_pred_i(Visitor v, count_t i, count_t n, Args&&... args) + { + auto p = node_->inner() + i; + auto e = node_->inner() + n; + if (shift_ == BL) { + for (; p != e; ++p) { + IMMER_PREFETCH(p + 1); + if (!make_full_leaf_pos(*p).visit(v, args...)) + return false; + } + } else { + auto ss = shift_ - B; + for (; p != e; ++p) + if (!make_full_pos(*p, ss).visit(v, args...)) + return false; + } + return true; + } + + template + void each_i(Visitor v, count_t i, count_t n, Args&&... args) + { + auto p = node_->inner() + i; + auto e = node_->inner() + n; + if (shift_ == BL) { + for (; p != e; ++p) { + IMMER_PREFETCH(p + 1); + make_full_leaf_pos(*p).visit(v, args...); + } + } else { + auto ss = shift_ - B; + for (; p != e; ++p) + make_full_pos(*p, ss).visit(v, args...); + } + } + + template + bool each_pred_right(Visitor v, count_t start, Args&&... args) + { return each_pred_i(v, start, branches, args...); } + + template + bool each_pred_left(Visitor v, count_t last, Args&&... args) + { return each_pred_i(v, 0, last, args...); } + + template + void each_sub(Visitor v, Args&&... args) + { each(v, args...); } + + template + void each_left_sub(Visitor v, Args&&... args) + { each_i(v, 0, branches - 1, args...); } + + template + void each_right_sub(Visitor v, Args&&... args) + { each_i(v, 1, branches, args...); } + + template + void each_right(Visitor v, count_t start, Args&&... args) + { each_i(v, start, branches, args...); } + + template + void each_left(Visitor v, count_t last, Args&&... args) + { each_i(v, 0, last, args...); } + + template + decltype(auto) towards(Visitor v, size_t idx, Args&&... args) + { return towards_oh(v, idx, index(idx), args...); } + + template + decltype(auto) towards_oh_ch(Visitor v, size_t idx, + count_t offset_hint, count_t, + Args&&... args) + { return towards_oh(v, idx, offset_hint, args...); } + + template + decltype(auto) towards_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { + assert(offset_hint == index(idx)); + auto is_leaf = shift_ == BL; + auto child = node_->inner() [offset_hint]; + return is_leaf + ? make_full_leaf_pos(child).visit(v, idx, args...) + : make_full_pos(child, shift_ - B).visit(v, idx, args...); + } + + template + decltype(auto) towards_sub_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { + assert(offset_hint == index(idx)); + auto is_leaf = shift_ == BL; + auto child = node_->inner() [offset_hint]; + auto lsize = offset_hint << shift_; + return is_leaf + ? make_full_leaf_pos(child).visit(v, idx - lsize, args...) + : make_full_pos(child, shift_ - B).visit(v, idx - lsize, args...); + } + + template + decltype(auto) first_sub(Visitor v, Args&&... args) + { + auto is_leaf = shift_ == BL; + auto child = node_->inner() [0]; + return is_leaf + ? make_full_leaf_pos(child).visit(v, args...) + : make_full_pos(child, shift_ - B).visit(v, args...); + } + + template + decltype(auto) first_sub_leaf(Visitor v, Args&&... args) + { + assert(shift_ == BL); + auto child = node_->inner() [0]; + return make_full_leaf_pos(child).visit(v, args...); + } + + template + decltype(auto) first_sub_inner(Visitor v, Args&&... args) + { + assert(shift_ >= BL); + auto child = node_->inner() [0]; + return make_full_pos(child, shift_ - B).visit(v, args...); + } + + template + decltype(auto) nth_sub(count_t idx, Visitor v, Args&&... args) + { + assert(idx < count()); + auto is_leaf = shift_ == BL; + auto child = node_->inner() [idx]; + return is_leaf + ? make_full_leaf_pos(child).visit(v, args...) + : make_full_pos(child, shift_ - B).visit(v, args...); + } + + template + decltype(auto) nth_sub_leaf(count_t idx, Visitor v, Args&&... args) + { + assert(shift_ == BL); + assert(idx < count()); + auto child = node_->inner() [idx]; + return make_full_leaf_pos(child).visit(v, args...); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_regular(*this, std::forward(args)...); + } +}; + +template +full_pos make_full_pos(NodeT* node, shift_t shift) +{ + assert(node); + assert(shift >= NodeT::bits_leaf); + return {node, shift}; +} + +template +struct relaxed_pos +{ + static constexpr auto B = NodeT::bits; + static constexpr auto BL = NodeT::bits_leaf; + + using node_t = NodeT; + using relaxed_t = typename NodeT::relaxed_t; + node_t* node_; + shift_t shift_; + relaxed_t* relaxed_; + + count_t count() const { return relaxed_->d.count; } + node_t* node() const { return node_; } + size_t size() const { return relaxed_->d.sizes[relaxed_->d.count - 1]; } + shift_t shift() const { return shift_; } + count_t subindex(size_t idx) const { return index(idx); } + relaxed_t* relaxed() const { return relaxed_; } + + size_t size_before(count_t offset) const + { return offset ? relaxed_->d.sizes[offset - 1] : 0; } + + size_t size(count_t offset) const + { return size_sbh(offset, size_before(offset)); } + + size_t size_sbh(count_t offset, size_t size_before_hint) const + { + assert(size_before_hint == size_before(offset)); + return relaxed_->d.sizes[offset] - size_before_hint; + } + + count_t index(size_t idx) const + { + auto offset = idx >> shift_; + while (relaxed_->d.sizes[offset] <= idx) ++offset; + return offset; + } + + void copy_sizes(count_t offset, + count_t n, + size_t init, + size_t* sizes) + { + auto e = sizes + n; + auto prev = size_before(offset); + auto these = relaxed_->d.sizes + offset; + for (; sizes != e; ++sizes, ++these) { + auto this_size = *these; + init = *sizes = init + (this_size - prev); + prev = this_size; + } + } + + template + void each(Visitor v, Args&&... args) + { each_left(v, relaxed_->d.count, args...); } + + template + bool each_pred(Visitor v, Args&&... args) + { + auto p = node_->inner(); + auto s = size_t{}; + auto n = count(); + if (shift_ == BL) { + for (auto i = count_t{0}; i < n; ++i) { + IMMER_PREFETCH(p + i + 1); + if (!make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } else { + auto ss = shift_ - B; + for (auto i = count_t{0}; i < n; ++i) { + if (!visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } + return true; + } + + template + bool each_pred_i(Visitor v, count_t i, count_t n, Args&&... args) + { + if (shift_ == BL) { + auto p = node_->inner(); + auto s = i > 0 ? relaxed_->d.sizes[i - 1] : 0; + for (; i < n; ++i) { + IMMER_PREFETCH(p + i + 1); + if (!make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } else { + auto p = node_->inner(); + auto s = i > 0 ? relaxed_->d.sizes[i - 1] : 0; + auto ss = shift_ - B; + for (; i < n; ++i) { + if (!visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } + return true; + } + + template + bool each_pred_left(Visitor v, count_t n, Args&&... args) + { + auto p = node_->inner(); + auto s = size_t{}; + if (shift_ == BL) { + for (auto i = count_t{0}; i < n; ++i) { + IMMER_PREFETCH(p + i + 1); + if (!make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } else { + auto ss = shift_ - B; + for (auto i = count_t{0}; i < n; ++i) { + if (!visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } + return true; + } + + template + bool each_pred_right(Visitor v, count_t start, Args&&... args) + { + assert(start > 0); + assert(start <= relaxed_->d.count); + auto s = relaxed_->d.sizes[start - 1]; + auto p = node_->inner(); + if (shift_ == BL) { + for (auto i = start; i < relaxed_->d.count; ++i) { + IMMER_PREFETCH(p + i + 1); + if (!make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } else { + auto ss = shift_ - B; + for (auto i = start; i < relaxed_->d.count; ++i) { + if (!visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...)) + return false; + s = relaxed_->d.sizes[i]; + } + } + return true; + } + + template + void each_i(Visitor v, count_t i, count_t n, Args&&... args) + { + if (shift_ == BL) { + auto p = node_->inner(); + auto s = i > 0 ? relaxed_->d.sizes[i - 1] : 0; + for (; i < n; ++i) { + IMMER_PREFETCH(p + i + 1); + make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...); + s = relaxed_->d.sizes[i]; + } + } else { + auto p = node_->inner(); + auto s = i > 0 ? relaxed_->d.sizes[i - 1] : 0; + auto ss = shift_ - B; + for (; i < n; ++i) { + visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...); + s = relaxed_->d.sizes[i]; + } + } + } + + template + void each_sub(Visitor v, Args&&... args) + { each_left(v, relaxed_->d.count, args...); } + + template + void each_left_sub(Visitor v, Args&&... args) + { each_left(v, relaxed_->d.count - 1, args...); } + + template + void each_left(Visitor v, count_t n, Args&&... args) + { + auto p = node_->inner(); + auto s = size_t{}; + if (shift_ == BL) { + for (auto i = count_t{0}; i < n; ++i) { + IMMER_PREFETCH(p + i + 1); + make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...); + s = relaxed_->d.sizes[i]; + } + } else { + auto ss = shift_ - B; + for (auto i = count_t{0}; i < n; ++i) { + visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...); + s = relaxed_->d.sizes[i]; + } + } + } + + template + void each_right_sub(Visitor v, Args&&... args) + { each_right(v, 1, std::forward(args)...); } + + template + void each_right(Visitor v, count_t start, Args&&... args) + { + assert(start > 0); + assert(start <= relaxed_->d.count); + auto s = relaxed_->d.sizes[start - 1]; + auto p = node_->inner(); + if (shift_ == BL) { + for (auto i = start; i < relaxed_->d.count; ++i) { + IMMER_PREFETCH(p + i + 1); + make_leaf_sub_pos(p[i], relaxed_->d.sizes[i] - s) + .visit(v, args...); + s = relaxed_->d.sizes[i]; + } + } else { + auto ss = shift_ - B; + for (auto i = start; i < relaxed_->d.count; ++i) { + visit_maybe_relaxed_sub(p[i], ss, relaxed_->d.sizes[i] - s, + v, args...); + s = relaxed_->d.sizes[i]; + } + } + } + + template + decltype(auto) towards(Visitor v, size_t idx, Args&&... args) + { return towards_oh(v, idx, subindex(idx), args...); } + + template + decltype(auto) towards_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { + assert(offset_hint == index(idx)); + auto left_size = offset_hint ? relaxed_->d.sizes[offset_hint - 1] : 0; + return towards_oh_sbh(v, idx, offset_hint, left_size, args...); + } + + template + decltype(auto) towards_oh_sbh(Visitor v, size_t idx, + count_t offset_hint, + size_t left_size_hint, + Args&&... args) + { return towards_sub_oh_sbh(v, idx, offset_hint, left_size_hint, args...); } + + template + decltype(auto) towards_sub_oh(Visitor v, size_t idx, + count_t offset_hint, + Args&&... args) + { + assert(offset_hint == index(idx)); + auto left_size = offset_hint ? relaxed_->d.sizes[offset_hint - 1] : 0; + return towards_sub_oh_sbh(v, idx, offset_hint, left_size, args...); + } + + template + decltype(auto) towards_sub_oh_sbh(Visitor v, size_t idx, + count_t offset_hint, + size_t left_size_hint, + Args&&... args) + { + assert(offset_hint == index(idx)); + assert(left_size_hint == + (offset_hint ? relaxed_->d.sizes[offset_hint - 1] : 0)); + auto child = node_->inner() [offset_hint]; + auto is_leaf = shift_ == BL; + auto next_size = relaxed_->d.sizes[offset_hint] - left_size_hint; + auto next_idx = idx - left_size_hint; + return is_leaf + ? make_leaf_sub_pos(child, next_size).visit( + v, next_idx, args...) + : visit_maybe_relaxed_sub(child, shift_ - B, next_size, + v, next_idx, args...); + } + + template + decltype(auto) last_oh_csh(Visitor v, + count_t offset_hint, + size_t child_size_hint, + Args&&... args) + { + assert(offset_hint == count() - 1); + assert(child_size_hint == size(offset_hint)); + auto child = node_->inner() [offset_hint]; + auto is_leaf = shift_ == BL; + return is_leaf + ? make_leaf_sub_pos(child, child_size_hint).visit(v, args...) + : visit_maybe_relaxed_sub(child, shift_ - B, child_size_hint, + v, args...); + } + + template + decltype(auto) last_sub(Visitor v, Args&&... args) + { + auto offset = relaxed_->d.count - 1; + auto child = node_->inner() [offset]; + auto child_size = size(offset); + auto is_leaf = shift_ == BL; + return is_leaf + ? make_leaf_sub_pos(child, child_size).visit(v, args...) + : visit_maybe_relaxed_sub(child, shift_ - B, child_size, v, args...); + } + + template + decltype(auto) first_sub(Visitor v, Args&&... args) + { + auto child = node_->inner() [0]; + auto child_size = relaxed_->d.sizes[0]; + auto is_leaf = shift_ == BL; + return is_leaf + ? make_leaf_sub_pos(child, child_size).visit(v, args...) + : visit_maybe_relaxed_sub(child, shift_ - B, child_size, v, args...); + } + + template + decltype(auto) first_sub_leaf(Visitor v, Args&&... args) + { + assert(shift_ == BL); + auto child = node_->inner() [0]; + auto child_size = relaxed_->d.sizes[0]; + return make_leaf_sub_pos(child, child_size).visit(v, args...); + } + + template + decltype(auto) first_sub_inner(Visitor v, Args&&... args) + { + assert(shift_ > BL); + auto child = node_->inner() [0]; + auto child_size = relaxed_->d.sizes[0]; + return visit_maybe_relaxed_sub(child, shift_ - B, child_size, v, args...); + } + + template + decltype(auto) nth_sub(count_t offset, Visitor v, Args&&... args) + { + auto child = node_->inner() [offset]; + auto child_size = size(offset); + auto is_leaf = shift_ == BL; + return is_leaf + ? make_leaf_sub_pos(child, child_size).visit(v, args...) + : visit_maybe_relaxed_sub(child, shift_ - B, child_size, v, args...); + } + + template + decltype(auto) nth_sub_leaf(count_t offset, Visitor v, Args&&... args) + { + assert(shift_ == BL); + auto child = node_->inner() [offset]; + auto child_size = size(offset); + return make_leaf_sub_pos(child, child_size).visit(v, args...); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_relaxed(*this, std::forward(args)...); + } +}; + +template +using is_relaxed = std::is_same::node_t>, + std::decay_t>; + +template +constexpr auto is_relaxed_v = is_relaxed::value; + +template +relaxed_pos make_relaxed_pos(NodeT* node, + shift_t shift, + typename NodeT::relaxed_t* relaxed) +{ + assert(node); + assert(relaxed); + assert(shift >= NodeT::bits_leaf); + return {node, shift, relaxed}; +} + +template +decltype(auto) visit_maybe_relaxed_sub(NodeT* node, shift_t shift, size_t size, + Visitor v, Args&& ...args) +{ + assert(node); + auto relaxed = node->relaxed(); + if (relaxed) { + assert(size == relaxed->d.sizes[relaxed->d.count - 1]); + return make_relaxed_pos(node, shift, relaxed) + .visit(v, std::forward(args)...); + } else { + return make_regular_sub_pos(node, shift, size) + .visit(v, std::forward(args)...); + } +} + +template +struct relaxed_descent_pos +{ + static_assert(Shift > 0, "not leaf..."); + + using node_t = NodeT; + using relaxed_t = typename NodeT::relaxed_t; + node_t* node_; + relaxed_t* relaxed_; + + count_t count() const { return relaxed_->d.count; } + node_t* node() const { return node_; } + shift_t shift() const { return Shift; } + size_t size() const { return relaxed_->d.sizes[relaxed_->d.count - 1]; } + + count_t index(size_t idx) const + { + // make gcc happy +#if !defined(_MSC_VER) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshift-count-overflow" +#endif + auto offset = idx >> Shift; +#if !defined(_MSC_VER) +#pragma GCC diagnostic pop +#endif + while (relaxed_->d.sizes[offset] <= idx) ++offset; + return offset; + } + + template + decltype(auto) descend(Visitor v, size_t idx) + { + auto offset = index(idx); + auto child = node_->inner() [offset]; + auto left_size = offset ? relaxed_->d.sizes[offset - 1] : 0; + auto next_idx = idx - left_size; + auto r = child->relaxed(); + return r + ? relaxed_descent_pos{child, r}.visit(v, next_idx) + : regular_descent_pos{child}.visit(v, next_idx); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_relaxed(*this, std::forward(args)...); + } +}; + +template +struct relaxed_descent_pos +{ + using node_t = NodeT; + using relaxed_t = typename NodeT::relaxed_t; + node_t* node_; + relaxed_t* relaxed_; + + count_t count() const { return relaxed_->d.count; } + node_t* node() const { return node_; } + shift_t shift() const { return BL; } + size_t size() const { return relaxed_->d.sizes[relaxed_->d.count - 1]; } + + count_t index(size_t idx) const + { + auto offset = (idx >> BL) & mask; + while (relaxed_->d.sizes[offset] <= idx) ++offset; + return offset; + } + + template + decltype(auto) descend(Visitor v, size_t idx) + { + auto offset = index(idx); + auto child = node_->inner() [offset]; + auto left_size = offset ? relaxed_->d.sizes[offset - 1] : 0; + auto next_idx = idx - left_size; + return leaf_descent_pos{child}.visit(v, next_idx); + } + + template + decltype(auto) visit(Visitor v, Args&& ...args) + { + return Visitor::visit_relaxed(*this, std::forward(args)...); + } +}; + +template +decltype(auto) visit_maybe_relaxed_descent(NodeT* node, shift_t shift, + Visitor v, size_t idx) +{ + constexpr auto B = NodeT::bits; + constexpr auto BL = NodeT::bits_leaf; + assert(node); + assert(shift >= BL); + auto r = node->relaxed(); + if (r) { + switch (shift) { + case BL + B * 0: return relaxed_descent_pos{node, r}.visit(v, idx); + case BL + B * 1: return relaxed_descent_pos{node, r}.visit(v, idx); + case BL + B * 2: return relaxed_descent_pos{node, r}.visit(v, idx); + case BL + B * 3: return relaxed_descent_pos{node, r}.visit(v, idx); + case BL + B * 4: return relaxed_descent_pos{node, r}.visit(v, idx); + case BL + B * 5: return relaxed_descent_pos{node, r}.visit(v, idx); +#if IMMER_DESCENT_DEEP + default: + for (auto level = shift; level != endshift; level -= B) { + auto r = node->relaxed(); + if (r) { + auto node_idx = (idx >> level) & mask; + while (r->d.sizes[node_idx] <= idx) ++node_idx; + if (node_idx) idx -= r->d.sizes[node_idx - 1]; + node = node->inner() [node_idx]; + } else { + do { + node = node->inner() [(idx >> level) & mask]; + } while ((level -= B) != endshift); + return make_leaf_descent_pos(node).visit(v, idx); + } + } + return make_leaf_descent_pos(node).visit(v, idx); +#endif // IMMER_DESCENT_DEEP + } + IMMER_UNREACHABLE; + } else { + return visit_regular_descent(node, shift, v, idx); + } +} + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/rbtree.hpp b/src/immer/detail/rbts/rbtree.hpp new file mode 100644 index 000000000000..aab5bd9fe38a --- /dev/null +++ b/src/immer/detail/rbts/rbtree.hpp @@ -0,0 +1,533 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +template +struct rbtree +{ + 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 rbtree& empty() + { + static const rbtree 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 = rbtree{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 = rbtree{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 = rbtree{empty()}; + while (n --> 0) + result.push_back_mut(e, v); + return result; + } + + rbtree(size_t sz, shift_t sh, node_t* r, node_t* t) + : size{sz}, shift{sh}, root{r}, tail{t} + { + assert(check_tree()); + } + + rbtree(const rbtree& other) + : rbtree{other.size, other.shift, other.root, other.tail} + { + inc(); + } + + rbtree(rbtree&& other) + : rbtree{empty()} + { + swap(*this, other); + } + + rbtree& operator=(const rbtree& other) + { + auto next = other; + swap(*this, next); + return *this; + } + + rbtree& operator=(rbtree&& other) + { + swap(*this, other); + return *this; + } + + friend void swap(rbtree& x, rbtree& y) + { + using std::swap; + swap(x.size, y.size); + swap(x.shift, y.shift); + swap(x.root, y.root); + swap(x.tail, y.tail); + } + + ~rbtree() + { + dec(); + } + + void inc() const + { + root->inc(); + tail->inc(); + } + + void dec() const + { + traverse(dec_visitor()); + } + + auto tail_size() const + { + return size ? ((size - 1) & mask) + 1 : 0; + } + + auto tail_offset() const + { + return size ? (size - 1) & ~mask : 0; + } + + template + void traverse(Visitor v, Args&&... args) const + { + auto tail_off = tail_offset(); + auto tail_size = size - tail_off; + + if (tail_off) make_regular_sub_pos(root, shift, tail_off).visit(v, args...); + else make_empty_regular_pos(root).visit(v, args...); + + make_leaf_sub_pos(tail, tail_size).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) + make_regular_sub_pos(root, shift, tail_off).visit( + 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 + ? make_regular_sub_pos(root, shift, tail_off).visit(v, args...) + : make_empty_regular_pos(root).visit(v, args...)) + && make_leaf_sub_pos(tail, tail_size).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 + ? make_regular_sub_pos(root, shift, tail_off).visit( + 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) + : visit_regular_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 rbtree& other) const + { + if (size != other.size) return false; + if (size == 0) return true; + return (size <= branches + || make_regular_sub_pos(root, shift, tail_offset()).visit( + equals_visitor{}, other.root)) + && make_leaf_sub_pos(tail, tail_size()).visit( + equals_visitor{}, other.tail); + } + + 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 tail_off = tail_offset(); + auto ts = size - tail_off; + if (ts < branches) { + ensure_mutable_tail(e, ts); + new (&tail->leaf()[ts]) T{std::move(value)}; + } else { + auto new_tail = node_t::make_leaf_e(e, std::move(value)); + try { + if (tail_off == size_t{branches} << shift) { + auto new_root = node_t::make_inner_e(e); + try { + auto path = node_t::make_path_e(e, shift, tail); + new_root->inner() [0] = root; + new_root->inner() [1] = path; + root = new_root; + tail = new_tail; + 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; + tail = new_tail; + } else { + auto new_root = node_t::make_path_e(e, shift, tail); + assert(tail_off == 0); + dec_empty_regular(root); + root = new_root; + tail = new_tail; + } + } catch (...) { + node_t::delete_leaf(new_tail, 1); + throw; + } + } + ++size; + } + + rbtree push_back(T value) const + { + auto tail_off = tail_offset(); + auto ts = size - tail_off; + 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 { + auto new_tail = node_t::make_leaf_n(1, std::move(value)); + try { + if (tail_off == size_t{branches} << shift) { + auto new_root = node_t::make_inner_n(2); + try { + auto path = node_t::make_path(shift, tail); + new_root->inner() [0] = root; + new_root->inner() [1] = path; + root->inc(); + tail->inc(); + return { size + 1, shift + B, new_root, new_tail }; + } catch (...) { + node_t::delete_inner(new_root, 2); + throw; + } + } else if (tail_off) { + auto new_root = make_regular_sub_pos(root, shift, tail_off) + .visit(push_tail_visitor{}, tail); + tail->inc(); + return { size + 1, shift, new_root, new_tail }; + } else { + auto new_root = node_t::make_path(shift, tail); + tail->inc(); + return { size + 1, shift, new_root, new_tail }; + } + } catch (...) { + node_t::delete_leaf(new_tail, 1); + throw; + } + } + } + + const T* array_for(size_t index) const + { + return descend(array_for_visitor(), index); + } + + 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 & mask]; + } else { + return make_regular_sub_pos(root, shift, tail_off) + .visit(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{"index out of range"}; + return descend(get_visitor(), index); + } + + const T& front() const + { + return get(0); + } + + const T& back() const + { + return tail->leaf()[(size - 1) & mask]; + } + + 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 + rbtree 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 = make_regular_sub_pos(root, shift, tail_off) + .visit(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); + }); + } + + rbtree assoc(size_t idx, T value) const + { + return update(idx, [&] (auto&&) { + return std::move(value); + }); + } + + rbtree 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 = make_regular_sub_pos(root, shift, tail_off).visit(v, l); + auto new_shift = get<0>(r); + auto new_root = get<1>(r); + auto new_tail = get<3>(r); + if (new_root) { + assert(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 take_mut(edit_t e, size_t new_size) + { + auto tail_off = tail_offset(); + if (new_size == 0) { + // todo: more efficient? + *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 = make_regular_sub_pos(root, shift, tail_off).visit(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; + } + } + + bool check_tree() const + { +#if IMMER_DEBUG_DEEP_CHECK + assert(shift >= BL); + assert(tail_offset() <= size); + 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(0, 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 { + assert(root->kind() == node_t::kind_t::inner); + assert(shift == BL); + } +#endif + return true; + } +}; + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/rbtree_iterator.hpp b/src/immer/detail/rbts/rbtree_iterator.hpp new file mode 100644 index 000000000000..672484eae3ea --- /dev/null +++ b/src/immer/detail/rbts/rbtree_iterator.hpp @@ -0,0 +1,102 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +template +struct rbtree_iterator + : iterator_facade, + std::random_access_iterator_tag, + T, + const T&, + std::ptrdiff_t, + const T*> +{ + using tree_t = rbtree; + + struct end_t {}; + + rbtree_iterator() = default; + + rbtree_iterator(const tree_t& v) + : v_ { &v } + , i_ { 0 } + , base_ { ~size_t{} } + , curr_ { nullptr } + {} + + rbtree_iterator(const tree_t& v, end_t) + : v_ { &v } + , i_ { v.size } + , base_ { ~size_t{} } + , curr_ { nullptr } + {} + + const tree_t& impl() const { return *v_; } + size_t index() const { return i_; } + +private: + friend iterator_core_access; + + const tree_t* v_; + size_t i_; + mutable size_t base_; + mutable const T* curr_ = nullptr; + + void increment() + { + assert(i_ < v_->size); + ++i_; + } + + void decrement() + { + assert(i_ > 0); + --i_; + } + + void advance(std::ptrdiff_t n) + { + assert(n <= 0 || i_ + static_cast(n) <= v_->size); + assert(n >= 0 || static_cast(-n) <= i_); + i_ += n; + } + + bool equal(const rbtree_iterator& other) const + { + return i_ == other.i_; + } + + std::ptrdiff_t distance_to(const rbtree_iterator& other) const + { + return other.i_ > i_ + ? static_cast(other.i_ - i_) + : - static_cast(i_ - other.i_); + } + + const T& dereference() const + { + auto base = i_ & ~mask; + if (base_ != base) { + base_ = base; + curr_ = v_->array_for(i_); + } + return curr_[i_ & mask]; + } +}; + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/rrbtree.hpp b/src/immer/detail/rbts/rrbtree.hpp new file mode 100644 index 000000000000..7d26e50a66f6 --- /dev/null +++ b/src/immer/detail/rbts/rrbtree.hpp @@ -0,0 +1,1282 @@ +// +// 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 { 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 { 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 { shift + B, new_root }; + } else if (size) { + auto new_root = make_regular_sub_pos(root, shift, size) + .visit(push_tail_visitor{}, tail); + return { shift, new_root }; + } else { + return { 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 { 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 { 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) { + assert(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(); + assert(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(); + assert(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()); + assert(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()); + assert(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()); + assert(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()); + assert(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()); + assert(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()); + assert(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()); + assert(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()); + assert(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 { + assert(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 diff --git a/src/immer/detail/rbts/rrbtree_iterator.hpp b/src/immer/detail/rbts/rrbtree_iterator.hpp new file mode 100644 index 000000000000..a40f7f486de0 --- /dev/null +++ b/src/immer/detail/rbts/rrbtree_iterator.hpp @@ -0,0 +1,102 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +template +struct rrbtree_iterator + : iterator_facade, + std::random_access_iterator_tag, + T, + const T&, + std::ptrdiff_t, + const T*> +{ + using tree_t = rrbtree; + using region_t = std::tuple; + + struct end_t {}; + + const tree_t& impl() const { return *v_; } + size_t index() const { return i_; } + + rrbtree_iterator() = default; + + rrbtree_iterator(const tree_t& v) + : v_ { &v } + , i_ { 0 } + , curr_ { nullptr, ~size_t{}, ~size_t{} } + { + } + + rrbtree_iterator(const tree_t& v, end_t) + : v_ { &v } + , i_ { v.size } + , curr_ { nullptr, ~size_t{}, ~size_t{} } + {} + +private: + friend iterator_core_access; + + const tree_t* v_; + size_t i_; + mutable region_t curr_; + + void increment() + { + using std::get; + assert(i_ < v_->size); + ++i_; + } + + void decrement() + { + using std::get; + assert(i_ > 0); + --i_; + } + + void advance(std::ptrdiff_t n) + { + using std::get; + assert(n <= 0 || i_ + static_cast(n) <= v_->size); + assert(n >= 0 || static_cast(-n) <= i_); + i_ += n; + } + + bool equal(const rrbtree_iterator& other) const + { + return i_ == other.i_; + } + + std::ptrdiff_t distance_to(const rrbtree_iterator& other) const + { + return other.i_ > i_ + ? static_cast(other.i_ - i_) + : - static_cast(i_ - other.i_); + } + + const T& dereference() const + { + using std::get; + if (i_ < get<1>(curr_) || i_ >= get<2>(curr_)) + curr_ = v_->region_for(i_); + return get<0>(curr_)[i_ - get<1>(curr_)]; + } +}; + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/rbts/visitor.hpp b/src/immer/detail/rbts/visitor.hpp new file mode 100644 index 000000000000..eaccfcdca474 --- /dev/null +++ b/src/immer/detail/rbts/visitor.hpp @@ -0,0 +1,56 @@ +// +// 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 + +namespace immer { +namespace detail { +namespace rbts { + +template +struct visitor_base +{ + template + static decltype(auto) visit_node(Args&& ...args) + { + IMMER_UNREACHABLE; + } + + template + static decltype(auto) visit_relaxed(Args&& ...args) + { + return Deriv::visit_inner(std::forward(args)...); + } + + template + static decltype(auto) visit_regular(Args&& ...args) + { + return Deriv::visit_inner(std::forward(args)...); + } + + template + static decltype(auto) visit_inner(Args&& ...args) + { + return Deriv::visit_node(std::forward(args)...); + } + + template + static decltype(auto) visit_leaf(Args&& ...args) + { + return Deriv::visit_node(std::forward(args)...); + } +}; + +} // namespace rbts +} // namespace detail +} // namespace immer diff --git a/src/immer/detail/ref_count_base.hpp b/src/immer/detail/ref_count_base.hpp new file mode 100644 index 000000000000..a20428b13f70 --- /dev/null +++ b/src/immer/detail/ref_count_base.hpp @@ -0,0 +1,36 @@ +// +// 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 + +namespace immer { +namespace detail { + +template +struct ref_count_base +{ + mutable std::atomic ref_count { 0 }; + + friend void intrusive_ptr_add_ref(const Deriv* x) + { + x->ref_count.fetch_add(1, std::memory_order_relaxed); + } + + friend void intrusive_ptr_release(const Deriv* x) + { + if (x->ref_count.fetch_sub(1, std::memory_order_release) == 1) { + std::atomic_thread_fence(std::memory_order_acquire); + delete x; + } + } +}; + +} /* namespace detail */ +} /* namespace immer */ diff --git a/src/immer/detail/type_traits.hpp b/src/immer/detail/type_traits.hpp new file mode 100644 index 000000000000..668f1d826f60 --- /dev/null +++ b/src/immer/detail/type_traits.hpp @@ -0,0 +1,191 @@ +// +// 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 + +namespace immer { +namespace detail { + +template +struct make_void { using type = void; }; + +template +using void_t = typename make_void::type; + +template +struct is_dereferenceable : std::false_type {}; + +template +struct is_dereferenceable()))>> : + std::true_type {}; + +template +constexpr bool is_dereferenceable_v = is_dereferenceable::value; + +template +struct is_equality_comparable : std::false_type {}; + +template +struct is_equality_comparable +() == std::declval())>::value>> : + std::true_type {}; + +template +constexpr bool is_equality_comparable_v = is_equality_comparable::value; + +template +struct is_inequality_comparable : std::false_type {}; + +template +struct is_inequality_comparable +() != std::declval())>::value>> : + std::true_type {}; + +template +constexpr bool is_inequality_comparable_v = + is_inequality_comparable::value; + +template +struct is_preincrementable : std::false_type {}; + +template +struct is_preincrementable +()))>::value>> : + std::true_type {}; + +template +constexpr bool is_preincrementable_v = is_preincrementable::value; + +template +struct is_subtractable : std::false_type {}; + +template +struct is_subtractable +() - std::declval())>> : + std::true_type {}; + +template +constexpr bool is_subtractable_v = is_subtractable::value; + +namespace swappable { + +using std::swap; + +template +struct with : std::false_type {}; + +// Does not account for non-referenceable types +template +struct with +(), std::declval())), + decltype(swap(std::declval(), std::declval()))>> : + std::true_type {}; + +} + +template +using is_swappable_with = swappable::with; + +template +using is_swappable = is_swappable_with; + +template +constexpr bool is_swappable_v = is_swappable_with::value; + +template +struct is_iterator : std::false_type {}; + +// See http://en.cppreference.com/w/cpp/concept/Iterator +template +struct is_iterator + + && is_dereferenceable_v + // accounts for non-referenceable types + && std::is_copy_constructible::value + && std::is_copy_assignable::value + && std::is_destructible::value + && is_swappable_v>, + typename std::iterator_traits::value_type, + typename std::iterator_traits::difference_type, + typename std::iterator_traits::reference, + typename std::iterator_traits::pointer, + typename std::iterator_traits::iterator_category>> : + std::true_type {}; + +template +constexpr bool is_iterator_v = is_iterator::value; + +template +struct compatible_sentinel : std::false_type {}; + +template +struct compatible_sentinel + + && is_equality_comparable_v + && is_inequality_comparable_v>> : + std::true_type {}; + +template +constexpr bool compatible_sentinel_v = compatible_sentinel::value; + +template +struct is_forward_iterator : std::false_type {}; + +template +struct is_forward_iterator + && + std::is_base_of + ::iterator_category>::value>> : + std::true_type {}; + +template +constexpr bool is_forward_iterator_v = is_forward_iterator::value; + +template +struct std_distance_supports : std::false_type {}; + +template +struct std_distance_supports +(), std::declval()))>> : + std::true_type {}; + +template +constexpr bool std_distance_supports_v = std_distance_supports::value; + +template +struct std_uninitialized_copy_supports : std::false_type {}; + +template +struct std_uninitialized_copy_supports +(), + std::declval(), + std::declval()))>> : + std::true_type {}; + +template +constexpr bool std_uninitialized_copy_supports_v = + std_uninitialized_copy_supports::value; + +} +} diff --git a/src/immer/detail/util.hpp b/src/immer/detail/util.hpp new file mode 100644 index 000000000000..854a263d9e68 --- /dev/null +++ b/src/immer/detail/util.hpp @@ -0,0 +1,225 @@ +// +// 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 + +#if defined(_MSC_VER) +#include // for __lzcnt* +#endif + +namespace immer { +namespace detail { + +template +using aligned_storage_for = + typename std::aligned_storage::type; + +template +T& auto_const_cast(const T& x) { return const_cast(x); } +template +T&& auto_const_cast(const T&& x) { return const_cast(std::move(x)); } + +template +auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out) +{ + return std::uninitialized_copy(std::make_move_iterator(in1), + std::make_move_iterator(in2), + out); +} + +template +void destroy(T* first, T* last) +{ + for (; first != last; ++first) + first->~T(); +} + +template +void destroy_n(T* p, Size n) +{ + auto e = p + n; + for (; p != e; ++p) + p->~T(); +} + +template +T* make(Args&& ...args) +{ + auto ptr = Heap::allocate(sizeof(T)); + try { + return new (ptr) T{std::forward(args)...}; + } catch (...) { + Heap::deallocate(sizeof(T), ptr); + throw; + } +} + +struct not_supported_t {}; +struct empty_t {}; + +template +struct exact_t +{ + T value; + exact_t(T v) : value{v} {}; +}; + +template +inline constexpr auto clz_(T) -> not_supported_t { IMMER_UNREACHABLE; return {}; } +#if defined(_MSC_VER) +// inline auto clz_(unsigned short x) { return __lzcnt16(x); } +// inline auto clz_(unsigned int x) { return __lzcnt(x); } +// inline auto clz_(unsigned __int64 x) { return __lzcnt64(x); } +#else +inline constexpr auto clz_(unsigned int x) { return __builtin_clz(x); } +inline constexpr auto clz_(unsigned long x) { return __builtin_clzl(x); } +inline constexpr auto clz_(unsigned long long x) { return __builtin_clzll(x); } +#endif + +template +inline constexpr T log2_aux(T x, T r = 0) +{ + return x <= 1 ? r : log2_aux(x >> 1, r + 1); +} + +template +inline constexpr auto log2(T x) + -> std::enable_if_t::value, T> +{ + return x == 0 ? 0 : sizeof(std::size_t) * 8 - 1 - clz_(x); +} + +template +inline constexpr auto log2(T x) + -> std::enable_if_t::value, T> +{ + return log2_aux(x); +} + +template +auto static_if(F&& f) -> std::enable_if_t +{ std::forward(f)(empty_t{}); } +template +auto static_if(F&& f) -> std::enable_if_t +{} + +template +auto static_if(F1&& f1, F2&& f2) -> std::enable_if_t +{ return std::forward(f1)(empty_t{}); } +template +auto static_if(F1&& f1, F2&& f2) -> std::enable_if_t +{ return std::forward(f2)(empty_t{}); } + +template +struct constantly +{ + template + T operator() (Args&&...) const { return value; } +}; + +/*! + * An alias to `std::distance` + */ +template , bool> = true> +typename std::iterator_traits::difference_type +distance(Iterator first, Sentinel last) +{ + return std::distance(first, last); +} + +/*! + * Equivalent of the `std::distance` applied to the sentinel-delimited + * forward range @f$ [first, last) @f$ + */ +template ) + && detail::is_forward_iterator_v + && detail::compatible_sentinel_v + && (!detail::is_subtractable_v), bool> = true> +typename std::iterator_traits::difference_type +distance(Iterator first, Sentinel last) +{ + std::size_t result = 0; + while (first != last) { + ++first; + ++result; + } + return result; +} + +/*! + * Equivalent of the `std::distance` applied to the sentinel-delimited + * random access range @f$ [first, last) @f$ + */ +template ) + && detail::is_forward_iterator_v + && detail::compatible_sentinel_v + && detail::is_subtractable_v, bool> = true> +typename std::iterator_traits::difference_type +distance(Iterator first, Sentinel last) +{ + return last - first; +} + + + +/*! + * An alias to `std::uninitialized_copy` + */ +template , bool> = true> +SinkIter uninitialized_copy(Iterator first, Sentinel last, SinkIter d_first) +{ + return std::uninitialized_copy(first, last, d_first); +} + +/*! + * Equivalent of the `std::uninitialized_copy` applied to the + * sentinel-delimited forward range @f$ [first, last) @f$ + */ +template ) + && detail::compatible_sentinel_v + && detail::is_forward_iterator_v, bool> = true> +SinkIter uninitialized_copy(SourceIter first, Sent last, SinkIter d_first) +{ + auto current = d_first; + try { + while (first != last) { + *current++ = *first; + ++first; + } + } catch (...) { + using Value = typename std::iterator_traits::value_type; + for (;d_first != current; ++d_first){ + d_first->~Value(); + } + throw; + } + return current; +} + +} // namespace detail +} // namespace immer diff --git a/src/immer/experimental/detail/dvektor_impl.hpp b/src/immer/experimental/detail/dvektor_impl.hpp new file mode 100644 index 000000000000..df273aab71c9 --- /dev/null +++ b/src/immer/experimental/detail/dvektor_impl.hpp @@ -0,0 +1,512 @@ +// +// 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 dvektor { + +constexpr auto fast_log2(std::size_t x) +{ + return x == 0 ? 0 : sizeof(std::size_t) * 8 - 1 - __builtin_clzl(x); +} + +template +constexpr T branches = T{1} << B; + +template +constexpr T mask = branches - 1; + +template +constexpr auto max_depth = + fast_log2(std::numeric_limits::max()) / B; + +template +struct node; + +template +using node_ptr = boost::intrusive_ptr >; + +template +using leaf_node = std::array; + +template +using inner_node = std::array, 1 << B>; + +template +struct node : enable_intrusive_ptr, typename MP::refcount> + , enable_optimized_heap_policy, typename MP::heap> +{ + using leaf_node_t = leaf_node; + using inner_node_t = inner_node; + + enum + { + leaf_kind, + inner_kind + } kind; + + union data_t + { + leaf_node_t leaf; + inner_node_t inner; + data_t(leaf_node_t n) : leaf(std::move(n)) {} + data_t(inner_node_t n) : inner(std::move(n)) {} + ~data_t() {} + } data; + + ~node() + { + switch (kind) { + case leaf_kind: + data.leaf.~leaf_node_t(); + break; + case inner_kind: + data.inner.~inner_node_t(); + break; + } + } + + node(leaf_node n) + : kind{leaf_kind} + , data{std::move(n)} + {} + + node(inner_node n) + : kind{inner_kind} + , data{std::move(n)} + {} + + inner_node_t& inner() & { + assert(kind == inner_kind); + return data.inner; + } + const inner_node_t& inner() const& { + assert(kind == inner_kind); + return data.inner; + } + inner_node_t&& inner() && { + assert(kind == inner_kind); + return std::move(data.inner); + } + + leaf_node_t& leaf() & { + assert(kind == leaf_kind); + return data.leaf; + } + const leaf_node_t& leaf() const& { + assert(kind == leaf_kind); + return data.leaf; + } + leaf_node_t&& leaf() && { + assert(kind == leaf_kind); + return std::move(data.leaf); + } +}; + +template +auto make_node(Ts&& ...xs) + -> boost::intrusive_ptr> +{ + return new node(std::forward(xs)...); +} + +template +struct ref +{ + using inner_t = inner_node; + using leaf_t = leaf_node; + using node_t = node; + using node_ptr_t = node_ptr; + + unsigned depth; + std::array> display; + + template + static auto make_node(Ts&& ...xs) + { + return dvektor::make_node(std::forward(xs)...); + } + + const T& get_elem(std::size_t index, std::size_t xr) const + { + auto display_idx = fast_log2(xr) / B; + auto node = display[display_idx].get(); + auto shift = display_idx * B; + while (display_idx--) { + node = node->inner() [(index >> shift) & mask].get(); + shift -= B; + } + return node->leaf() [index & mask]; + } + + node_ptr_t null_slot_and_copy_inner(node_ptr_t& node, std::size_t idx) + { + auto& n = node->inner(); + auto x = node_ptr_t{}; + x.swap(n[idx]); + return copy_of_inner(x); + } + + node_ptr_t null_slot_and_copy_leaf(node_ptr_t& node, std::size_t idx) + { + auto& n = node->inner(); + auto x = node_ptr_t{}; + x.swap(n[idx]); + return copy_of_leaf(x); + } + + node_ptr_t copy_of_inner(const node_ptr_t& n) + { + return make_node(n->inner()); + } + + node_ptr_t copy_of_leaf(const node_ptr_t& n) + { + return make_node(n->leaf()); + } + + void stabilize(std::size_t index) + { + auto shift = B; + for (auto i = 1u; i < depth; ++i) + { + display[i] = copy_of_inner(display[i]); + display[i]->inner() [(index >> shift) & mask] + = display[i - 1]; + shift += B; + } + } + + void goto_pos_writable_from_clean(std::size_t old_index, + std::size_t index, + std::size_t xr) + { + assert(depth); + auto d = depth - 1; + if (d == 0) { + display[0] = copy_of_leaf(display[0]); + } else { + IMMER_UNREACHABLE; + display[d] = copy_of_inner(display[d]); + auto shift = B * d; + while (--d) { + display[d] = null_slot_and_copy_inner( + display[d + 1], + (index >> shift) & mask); + shift -= B; + } + display[0] = null_slot_and_copy_leaf( + display[1], + (index >> B) & mask); + } + } + + void goto_pos_writable_from_dirty(std::size_t old_index, + std::size_t new_index, + std::size_t xr) + { + assert(depth); + if (xr < (1 << B)) { + display[0] = copy_of_leaf(display[0]); + } else { + auto display_idx = fast_log2(xr) / B; + auto shift = B; + for (auto i = 1u; i <= display_idx; ++i) { + display[i] = copy_of_inner(display[i]); + display[i]->inner() [(old_index >> shift) & mask] + = display[i - 1]; + shift += B; + } + for (auto i = display_idx - 1; i > 0; --i) { + shift -= B; + display[i] = null_slot_and_copy_inner( + display[i + 1], + (new_index >> shift) & mask); + } + display[0] = null_slot_and_copy_leaf( + display[1], + (new_index >> B) & mask); + } + } + + void goto_fresh_pos_writable_from_clean(std::size_t old_index, + std::size_t new_index, + std::size_t xr) + { + auto display_idx = fast_log2(xr) / B; + if (display_idx > 0) { + auto shift = display_idx * B; + if (display_idx == depth) { + display[display_idx] = make_node(inner_t{}); + display[display_idx]->inner() + [(old_index >> shift) & mask] = + display[display_idx - 1]; + ++depth; + } + while (--display_idx) { + auto node = display[display_idx + 1]->inner() + [(new_index >> shift) & mask]; + display[display_idx] = node + ? std::move(node) + : make_node(inner_t{}); + + } + display[0] = make_node(leaf_t{}); + } + } + + void goto_fresh_pos_writable_from_dirty(std::size_t old_index, + std::size_t new_index, + std::size_t xr) + { + stabilize(old_index); + goto_fresh_pos_writable_from_clean(old_index, new_index, xr); + } + + void goto_next_block_start(std::size_t index, std::size_t xr) + { + auto display_idx = fast_log2(xr) / B; + auto shift = display_idx * B; + if (display_idx > 0) { + display[display_idx - 1] = display[display_idx]->inner() + [(index >> shift) & mask]; + while (--display_idx) + display[display_idx - 1] = display[display_idx]->inner()[0]; + } + } + + void goto_pos(std::size_t index, std::size_t xr) + { + auto display_idx = fast_log2(xr) / B; + auto shift = display_idx * B; + if (display_idx) { + do { + display[display_idx - 1] = display[display_idx]->inner() + [(index >> shift) & mask]; + shift -= B; + } while (--display_idx); + } + } +}; + +template +struct impl +{ + using inner_t = inner_node; + using leaf_t = leaf_node; + using node_t = node; + using node_ptr_t = node_ptr; + using ref_t = ref; + + std::size_t size; + std::size_t focus; + bool dirty; + ref_t p; + + template + static auto make_node(Ts&& ...xs) + { + return dvektor::make_node(std::forward(xs)...); + } + + void goto_pos_writable(std::size_t old_index, + std::size_t new_index, + std::size_t xr) + { + if (dirty) { + p.goto_pos_writable_from_dirty(old_index, new_index, xr); + } else { + p.goto_pos_writable_from_clean(old_index, new_index, xr); + dirty = true; + } + } + + void goto_fresh_pos_writable(std::size_t old_index, + std::size_t new_index, + std::size_t xr) + { + if (dirty) { + p.goto_fresh_pos_writable_from_dirty(old_index, new_index, xr); + } else { + p.goto_fresh_pos_writable_from_clean(old_index, new_index, xr); + dirty = true; + } + } + + impl push_back(T value) const + { + if (size) { + auto block_index = size & ~mask; + auto lo = size & mask; + if (size != block_index) { + auto s = impl{ size + 1, block_index, dirty, p }; + s.goto_pos_writable(focus, block_index, focus ^ block_index); + s.p.display[0]->leaf() [lo] = std::move(value); + return s; + } else { + auto s = impl{ size + 1, block_index, dirty, p }; + s.goto_fresh_pos_writable(focus, block_index, focus ^ block_index); + s.p.display[0]->leaf() [lo] = std::move(value); + return s; + } + } else { + return impl{ + 1, 0, false, + { 1, {{ make_node(leaf_t{{std::move(value)}}) }} } + }; + } + } + + const T& get(std::size_t index) const + { + return p.get_elem(index, index ^ focus); + } + + template + impl update(std::size_t idx, FnT&& fn) const + { + auto s = impl{ size, idx, dirty, p }; + s.goto_pos_writable(focus, idx, focus ^ idx); + auto& v = s.p.display[0]->leaf() [idx & mask]; + v = fn(std::move(v)); + return s; + } + + impl assoc(std::size_t idx, T value) const + { + return update(idx, [&] (auto&&) { + return std::move(value); + }); + } +}; + +template +const impl empty = { + 0, + 0, + false, + ref {1, {}} +}; + +template +struct iterator : boost::iterator_facade< + iterator, + T, + boost::random_access_traversal_tag, + const T&> +{ + struct end_t {}; + + iterator() = default; + + iterator(const impl& v) + : p_{ v.p } + , i_{ 0 } + , base_{ 0 } + { + if (v.dirty) + p_.stabilize(v.focus); + p_.goto_pos(0, 0 ^ v.focus); + curr_ = p_.display[0]->leaf().begin(); + } + + iterator(const impl& v, end_t) + : p_{ v.p } + , i_{ v.size } + , base_{ (v.size-1) & ~mask } + { + if (v.dirty) + p_.stabilize(v.focus); + p_.goto_pos(base_, base_ ^ v.focus); + curr_ = p_.display[0]->leaf().begin() + (i_ - base_); + } + +private: + friend class boost::iterator_core_access; + using leaf_iterator = typename leaf_node::const_iterator; + + ref p_; + std::size_t i_; + std::size_t base_; + leaf_iterator curr_; + + void increment() + { + ++i_; + if (i_ - base_ < branches) { + ++curr_; + } else { + auto new_base = base_ + branches; + p_.goto_next_block_start(new_base, base_ ^ new_base); + base_ = new_base; + curr_ = p_.display[0]->leaf().begin(); + } + } + + void decrement() + { + assert(i_ > 0); + --i_; + if (i_ >= base_) { + --curr_; + } else { + auto new_base = base_ - branches; + p_.goto_pos(new_base, base_ ^ new_base); + base_ = new_base; + curr_ = std::prev(p_.display[0]->leaf().end()); + } + } + + void advance(std::ptrdiff_t n) + { + i_ += n; + if (i_ <= base_ && i_ - base_ < branches) { + curr_ += n; + } else { + auto new_base = i_ & ~mask; + p_.goto_pos(new_base, base_ ^ new_base); + base_ = new_base; + curr_ = p_.display[0]->leaf().begin() + (i_ - base_); + } + } + + bool equal(const iterator& other) const + { + return i_ == other.i_; + } + + std::ptrdiff_t distance_to(const iterator& other) const + { + return other.i_ > i_ + ? static_cast(other.i_ - i_) + : - static_cast(i_ - other.i_); + } + + const T& dereference() const + { + return *curr_; + } +}; + +} /* namespace dvektor */ +} /* namespace detail */ +} /* namespace immer */ diff --git a/src/immer/experimental/dvektor.hpp b/src/immer/experimental/dvektor.hpp new file mode 100644 index 000000000000..7aa0bde7b3da --- /dev/null +++ b/src/immer/experimental/dvektor.hpp @@ -0,0 +1,63 @@ +// +// 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 + +namespace immer { + +template +class dvektor +{ + using impl_t = detail::dvektor::impl; + +public: + using value_type = T; + using reference = const T&; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = detail::dvektor::iterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + dvektor() = default; + + iterator begin() const { return {impl_}; } + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + std::size_t size() const { return impl_.size; } + bool empty() const { return impl_.size == 0; } + + reference operator[] (size_type index) const + { return impl_.get(index); } + + dvektor push_back(value_type value) const + { return { impl_.push_back(std::move(value)) }; } + + dvektor assoc(std::size_t idx, value_type value) const + { return { impl_.assoc(idx, std::move(value)) }; } + + template + dvektor update(std::size_t idx, FnT&& fn) const + { return { impl_.update(idx, std::forward(fn)) }; } + +private: + dvektor(impl_t impl) : impl_(std::move(impl)) {} + impl_t impl_ = detail::dvektor::empty; +}; + +} // namespace immer diff --git a/src/immer/flex_vector.hpp b/src/immer/flex_vector.hpp new file mode 100644 index 000000000000..7ab8fcd31b16 --- /dev/null +++ b/src/immer/flex_vector.hpp @@ -0,0 +1,502 @@ +// +// 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 + +namespace immer { + +template +class vector; + +template +class flex_vector_transient; + +/*! + * Immutable sequential container supporting both random access, + * structural sharing and efficient concatenation and slicing. + * + * @tparam T The type of the values to be stored in the container. + * @tparam MemoryPolicy Memory management policy. See @ref + * memory_policy. + * + * @rst + * + * This container is very similar to `vector`_ but also supports + * :math:`O(log(size))` *concatenation*, *slicing* and *insertion* at + * any point. Its performance characteristics are almost identical + * until one of these operations is performed. After that, + * performance is degraded by a constant factor that usually oscilates + * in the range :math:`[1, 2)` depending on the operation and the + * amount of flexible operations that have been performed. + * + * .. tip:: A `vector`_ can be converted to a `flex_vector`_ in + * constant time without any allocation. This is so because the + * internal structure of a *vector* is a strict subset of the + * internal structure of a *flexible vector*. You can take + * advantage of this property by creating normal vectors as long as + * the flexible operations are not needed, and convert later in + * your processing pipeline once and if these are needed. + * + * @endrst + */ +template > +class flex_vector +{ + using impl_t = detail::rbts::rrbtree; + + using move_t = + std::integral_constant; + +public: + static constexpr auto bits = B; + static constexpr auto bits_leaf = BL; + using memory_policy = MemoryPolicy; + + using value_type = T; + using reference = const T&; + using size_type = detail::rbts::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = detail::rbts::rrbtree_iterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using transient_type = flex_vector_transient; + + /*! + * Default constructor. It creates a flex_vector of `size() == 0`. + * It does not allocate memory and its complexity is @f$ O(1) @f$. + */ + flex_vector() = default; + + /*! + * Constructs a flex_vector containing the elements in `values`. + */ + flex_vector(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a flex_vector containing the elements in the range + * defined by the input iterator `first` and range sentinel `last`. + */ + template , bool> = true> + flex_vector(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + + /*! + * Constructs a vector containing the element `val` repeated `n` + * times. + */ + flex_vector(size_type n, T v = {}) + : impl_{impl_t::from_fill(n, v)} + {} + + /*! + * Default constructor. It creates a flex_vector with the same + * contents as `v`. It does not allocate memory and is + * @f$ O(1) @f$. + */ + flex_vector(vector v) + : impl_ { v.impl_.size, v.impl_.shift, + v.impl_.root->inc(), v.impl_.tail->inc() } + {} + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.size == 0; } + + /*! + * Access the last element. + */ + const T& back() const { return impl_.back(); } + + /*! + * Access the first element. + */ + const T& front() const { return impl_.front(); } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Returns whether the vectors are equal. + */ + bool operator==(const flex_vector& other) const + { return impl_.equals(other.impl_); } + bool operator!=(const flex_vector& other) const + { return !(*this == other); } + + /*! + * Returns a flex_vector with `value` inserted at the end. It may + * allocate memory and its complexity is *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: push-back/start + * :end-before: push-back/end + * + * @endrst + */ + flex_vector push_back(value_type value) const& + { return impl_.push_back(std::move(value)); } + + decltype(auto) push_back(value_type value) && + { return push_back_move(move_t{}, std::move(value)); } + + /*! + * Returns a flex_vector with `value` inserted at the frony. It may + * allocate memory and its complexity is @f$ O(log(size)) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: push-front/start + * :end-before: push-front/end + * + * @endrst + */ + flex_vector push_front(value_type value) const + { return flex_vector{}.push_back(value) + *this; } + + /*! + * Returns a flex_vector containing value `value` at position `index`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: set/start + * :end-before: set/end + * + * @endrst + */ + flex_vector set(size_type index, value_type value) const& + { return impl_.assoc(index, std::move(value)); } + + decltype(auto) set(size_type index, value_type value) && + { return set_move(move_t{}, index, std::move(value)); } + + /*! + * Returns a vector containing the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: update/start + * :end-before: update/end + * + * @endrst + + */ + template + flex_vector update(size_type index, FnT&& fn) const& + { return impl_.update(index, std::forward(fn)); } + + template + decltype(auto) update(size_type index, FnT&& fn) && + { return update_move(move_t{}, index, std::forward(fn)); } + + /*! + * Returns a vector containing only the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: take/start + * :end-before: take/end + * + * @endrst + */ + flex_vector take(size_type elems) const& + { return impl_.take(elems); } + + decltype(auto) take(size_type elems) && + { return take_move(move_t{}, elems); } + + /*! + * Returns a vector without the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: drop/start + * :end-before: drop/end + * + * @endrst + */ + flex_vector drop(size_type elems) const& + { return impl_.drop(elems); } + + decltype(auto) drop(size_type elems) && + { return drop_move(move_t{}, elems); } + + /*! + * Concatenation operator. Returns a flex_vector with the contents + * of `l` followed by those of `r`. It may allocate memory + * and its complexity is @f$ O(log(max(size_r, size_l))) @f$ + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: concat/start + * :end-before: concat/end + * + * @endrst + */ + friend flex_vector operator+ (const flex_vector& l, const flex_vector& r) + { return l.impl_.concat(r.impl_); } + + friend decltype(auto) operator+ (flex_vector&& l, const flex_vector& r) + { return concat_move(move_t{}, std::move(l), r); } + + friend decltype(auto) operator+ (const flex_vector& l, flex_vector&& r) + { return concat_move(move_t{}, l, std::move(r)); } + + friend decltype(auto) operator+ (flex_vector&& l, flex_vector&& r) + { return concat_move(move_t{}, std::move(l), std::move(r)); } + + /*! + * Returns a flex_vector with the `value` inserted at index + * `pos`. It may allocate memory and its complexity is @f$ + * O(log(size)) @f$ + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: insert/start + * :end-before: insert/end + * + * @endrst + */ + flex_vector insert(size_type pos, T value) const& + { return take(pos).push_back(std::move(value)) + drop(pos); } + decltype(auto) insert(size_type pos, T value) && + { + using std::move; + auto rs = drop(pos); + return std::move(*this).take(pos).push_back( + std::move(value)) + std::move(rs); + } + + flex_vector insert(size_type pos, flex_vector value) const& + { return take(pos) + std::move(value) + drop(pos); } + decltype(auto) insert(size_type pos, flex_vector value) && + { + using std::move; + auto rs = drop(pos); + return std::move(*this).take(pos) + std::move(value) + std::move(rs); + } + + /*! + * Returns a flex_vector without the element at index `pos`. It + * may allocate memory and its complexity is @f$ O(log(size)) @f$ + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/flex-vector/flex-vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: erase/start + * :end-before: erase/end + * + * @endrst + */ + flex_vector erase(size_type pos) const& + { return take(pos) + drop(pos + 1); } + decltype(auto) erase(size_type pos) && + { + auto rs = drop(pos + 1); + return std::move(*this).take(pos) + std::move(rs); + } + + flex_vector erase(size_type pos, size_type lpos) const& + { return lpos > pos ? take(pos) + drop(lpos) : *this; } + decltype(auto) erase(size_type pos, size_type lpos) && + { + if (lpos > pos) { + auto rs = drop(lpos); + return std::move(*this).take(pos) + std::move(rs); + } else { + return std::move(*this); + } + } + + /*! + * Returns an @a transient form of this container, an + * `immer::flex_vector_transient`. + */ + transient_type transient() const& + { return transient_type{ impl_ }; } + transient_type transient() && + { return transient_type{ std::move(impl_) }; } + + // Semi-private + const impl_t& impl() const { return impl_; } + +#if IMMER_DEBUG_PRINT + void debug_print(std::ostream& out=std::cerr) const + { impl_.debug_print(out); } +#endif + +private: + friend transient_type; + + flex_vector(impl_t impl) + : impl_(std::move(impl)) + { +#if IMMER_DEBUG_PRINT + // force the compiler to generate debug_print, so we can call + // it from a debugger + [](volatile auto){}(&flex_vector::debug_print); +#endif + } + + flex_vector&& push_back_move(std::true_type, value_type value) + { impl_.push_back_mut({}, std::move(value)); return std::move(*this); } + flex_vector push_back_move(std::false_type, value_type value) + { return impl_.push_back(std::move(value)); } + + flex_vector&& set_move(std::true_type, size_type index, value_type value) + { impl_.assoc_mut({}, index, std::move(value)); return std::move(*this); } + flex_vector set_move(std::false_type, size_type index, value_type value) + { return impl_.assoc(index, std::move(value)); } + + template + flex_vector&& update_move(std::true_type, size_type index, Fn&& fn) + { impl_.update_mut({}, index, std::forward(fn)); return std::move(*this); } + template + flex_vector update_move(std::false_type, size_type index, Fn&& fn) + { return impl_.update(index, std::forward(fn)); } + + flex_vector&& take_move(std::true_type, size_type elems) + { impl_.take_mut({}, elems); return std::move(*this); } + flex_vector take_move(std::false_type, size_type elems) + { return impl_.take(elems); } + + flex_vector&& drop_move(std::true_type, size_type elems) + { impl_.drop_mut({}, elems); return std::move(*this); } + flex_vector drop_move(std::false_type, size_type elems) + { return impl_.drop(elems); } + + static flex_vector&& concat_move(std::true_type, flex_vector&& l, const flex_vector& r) + { concat_mut_l(l.impl_, {}, r.impl_); return std::move(l); } + static flex_vector&& concat_move(std::true_type, const flex_vector& l, flex_vector&& r) + { concat_mut_r(l.impl_, r.impl_, {}); return std::move(r); } + static flex_vector&& concat_move(std::true_type, flex_vector&& l, flex_vector&& r) + { concat_mut_lr_l(l.impl_, {}, r.impl_, {}); return std::move(l); } + static flex_vector concat_move(std::false_type, const flex_vector& l, const flex_vector& r) + { return l.impl_.concat(r.impl_); } + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/src/immer/flex_vector_transient.hpp b/src/immer/flex_vector_transient.hpp new file mode 100644 index 000000000000..0d6e50aae1ef --- /dev/null +++ b/src/immer/flex_vector_transient.hpp @@ -0,0 +1,232 @@ +// +// 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 + +namespace immer { + +template +class flex_vector; + +template +class vector_transient; + +/*! + * Mutable version of `immer::flex_vector`. + * + * @rst + * + * Refer to :doc:`transients` to learn more about when and how to use + * the mutable versions of immutable containers. + * + * @endrst + */ +template > +class flex_vector_transient + : MemoryPolicy::transience_t::owner +{ + using impl_t = detail::rbts::rrbtree; + using base_t = typename MemoryPolicy::transience_t::owner; + using owner_t = typename MemoryPolicy::transience_t::owner; + +public: + static constexpr auto bits = B; + static constexpr auto bits_leaf = BL; + using memory_policy = MemoryPolicy; + + using value_type = T; + using reference = const T&; + using size_type = detail::rbts::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = detail::rbts::rrbtree_iterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using persistent_type = flex_vector; + + /*! + * Default constructor. It creates a flex_vector of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + flex_vector_transient() = default; + + /*! + * Default constructor. It creates a flex_vector with the same + * contents as `v`. It does not allocate memory and is + * @f$ O(1) @f$. + */ + flex_vector_transient(vector_transient v) + : base_t { std::move(static_cast(v)) } + , impl_ { v.impl_.size, v.impl_.shift, + v.impl_.root->inc(), v.impl_.tail->inc() } + {} + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.size == 0; } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Inserts `value` at the end. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + void push_back(value_type value) + { impl_.push_back_mut(*this, std::move(value)); } + + /*! + * Sets to the value `value` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void set(size_type index, value_type value) + { impl_.assoc_mut(*this, index, std::move(value)); } + + /*! + * Updates the vector to contain the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `0 >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + void update(size_type index, FnT&& fn) + { impl_.update_mut(*this, index, std::forward(fn)); } + + /*! + * Resizes the vector to only contain the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void take(size_type elems) + { impl_.take_mut(*this, elems); } + + /*! + * Removes the first the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void drop(size_type elems) + { impl_.drop_mut(*this, elems); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::flex_vector`. + */ + persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return persistent_type{ impl_ }; + } + persistent_type persistent() && + { return persistent_type{ std::move(impl_) }; } + + /*! + * Appends the contents of the `r` at the end. It may allocate + * memory and its complexity is: + * @f$ O(log(max(size_r, size_l))) @f$ + */ + void append(flex_vector_transient& r) + { + r.owner_t::operator=(owner_t{}); + concat_mut_l(impl_, *this, r.impl_); + } + void append(flex_vector_transient&& r) + { concat_mut_lr_l(impl_, *this, r.impl_, r); } + + /*! + * Prepends the contents of the `l` at the beginning. It may + * allocate memory and its complexity is: + * @f$ O(log(max(size_r, size_l))) @f$ + */ + void prepend(flex_vector_transient& l) + { + l.owner_t::operator=(owner_t{}); + concat_mut_r(l.impl_, impl_, *this); + } + void prepend(flex_vector_transient&& l) + { concat_mut_lr_r(l.impl_, l, impl_, *this); } + +private: + friend persistent_type; + + flex_vector_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/src/immer/heap/cpp_heap.hpp b/src/immer/heap/cpp_heap.hpp new file mode 100644 index 000000000000..cd129b406bea --- /dev/null +++ b/src/immer/heap/cpp_heap.hpp @@ -0,0 +1,41 @@ +// +// 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 + +namespace immer { + +/*! + * A heap that uses `operator new` and `operator delete`. + */ +struct cpp_heap +{ + /*! + * Returns a pointer to a memory region of size `size`, if the + * allocation was successful, and throws otherwise. + */ + template + static void* allocate(std::size_t size, Tags...) + { + return ::operator new(size); + } + + /*! + * Releases a memory region `data` that was previously returned by + * `allocate`. One must not use nor deallocate again a memory + * region that once it has been deallocated. + */ + static void deallocate(std::size_t size, void* data) + { + ::operator delete(data); + } +}; + +} // namespace immer diff --git a/src/immer/heap/debug_size_heap.hpp b/src/immer/heap/debug_size_heap.hpp new file mode 100644 index 000000000000..5cfb74ca8f65 --- /dev/null +++ b/src/immer/heap/debug_size_heap.hpp @@ -0,0 +1,54 @@ +// +// 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 + +namespace immer { + +#if IMMER_ENABLE_DEBUG_SIZE_HEAP + +/*! + * A heap that in debug mode ensures that the sizes for allocation and + * deallocation do match. + */ +template +struct debug_size_heap +{ + // temporary fix until https://github.com/arximboldi/immer/issues/78 is fixed + constexpr static auto extra_size = sizeof(void*) * 2; //alignof(std::max_align_t); + + template + static void* allocate(std::size_t size, Tags... tags) + { + auto p = (std::size_t*) Base::allocate(size + extra_size, tags...); + *p = size; + return ((char*)p) + extra_size; + } + + template + static void deallocate(std::size_t size, void* data, Tags... tags) + { + auto p = (std::size_t*) (((char*) data) - extra_size); + assert(*p == size); + Base::deallocate(size + extra_size, p, tags...); + } +}; + +#else // IMMER_ENABLE_DEBUG_SIZE_HEAP + +template +using debug_size_heap = identity_heap; + +#endif // !IMMER_ENABLE_DEBUG_SIZE_HEAP + +} // namespace immer diff --git a/src/immer/heap/free_list_heap.hpp b/src/immer/heap/free_list_heap.hpp new file mode 100644 index 000000000000..d82d0967b00c --- /dev/null +++ b/src/immer/heap/free_list_heap.hpp @@ -0,0 +1,83 @@ +// +// 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 + +namespace immer { + +/*! + * Adaptor that does not release the memory to the parent heap but + * instead it keeps the memory in a thread-safe global free list. Must + * be preceded by a `with_data` heap adaptor. + * + * @tparam Size Maximum size of the objects to be allocated. + * @tparam Base Type of the parent heap. + */ +template +struct free_list_heap : Base +{ + using base_t = Base; + + template + static void* allocate(std::size_t size, Tags...) + { + assert(size <= sizeof(free_list_node) + Size); + assert(size >= sizeof(free_list_node)); + + free_list_node* n; + do { + n = head().data; + if (!n) { + auto p = base_t::allocate(Size + sizeof(free_list_node)); + return static_cast(p); + } + } while (!head().data.compare_exchange_weak(n, n->next)); + head().count.fetch_sub(1u, std::memory_order_relaxed); + return n; + } + + template + static void deallocate(std::size_t size, void* data, Tags...) + { + assert(size <= sizeof(free_list_node) + Size); + assert(size >= sizeof(free_list_node)); + + // we use relaxed, because we are fine with temporarily having + // a few more/less buffers in free list + if (head().count.load(std::memory_order_relaxed) >= Limit) { + base_t::deallocate(Size + sizeof(free_list_node), data); + } else { + auto n = static_cast(data); + do { + n->next = head().data; + } while (!head().data.compare_exchange_weak(n->next, n)); + head().count.fetch_add(1u, std::memory_order_relaxed); + } + } + +private: + struct head_t + { + std::atomic data; + std::atomic count; + }; + + static head_t& head() + { + static head_t head_{{nullptr}, {0}}; + return head_; + } +}; + +} // namespace immer diff --git a/src/immer/heap/free_list_node.hpp b/src/immer/heap/free_list_node.hpp new file mode 100644 index 000000000000..5c2f5b7529d9 --- /dev/null +++ b/src/immer/heap/free_list_node.hpp @@ -0,0 +1,25 @@ +// +// 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 + +namespace immer { + +struct free_list_node +{ + free_list_node* next; +}; + +template +struct with_free_list_node + : with_data +{}; + +} // namespace immer diff --git a/src/immer/heap/gc_heap.hpp b/src/immer/heap/gc_heap.hpp new file mode 100644 index 000000000000..f37da2f6e3dd --- /dev/null +++ b/src/immer/heap/gc_heap.hpp @@ -0,0 +1,130 @@ +// +// 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 + +#if IMMER_HAS_LIBGC +#include +#else +#error "Using garbage collection requires libgc" +#endif + +#include +#include + +namespace immer { + +#ifdef __APPLE__ +#define IMMER_GC_REQUIRE_INIT 1 +#else +#define IMMER_GC_REQUIRE_INIT 0 +#endif + +#if IMMER_GC_REQUIRE_INIT + +namespace detail { + +template +struct gc_initializer +{ + gc_initializer() { GC_init(); } + static gc_initializer init; +}; + +template +gc_initializer gc_initializer::init {}; + +inline void gc_initializer_guard() +{ + static gc_initializer<> init_ = gc_initializer<>::init; + (void) init_; +} + +} // namespace detail + +#define IMMER_GC_INIT_GUARD_ ::immer::detail::gc_initializer_guard() + +#else + +#define IMMER_GC_INIT_GUARD_ + +#endif // IMMER_GC_REQUIRE_INIT + +/*! + * Heap that uses a tracing garbage collector. + * + * @rst + * + * This heap uses the `Boehm's conservative garbage collector`_ under + * the hood. This is a tracing garbage collector that automatically + * reclaims unused memory. Thus, it is not needed to call + * ``deallocate()`` in order to release memory. + * + * .. admonition:: Dependencies + * :class: tip + * + * In order to use this header file, you need to make sure that + * Boehm's ``libgc`` is your include path and link to its binary + * library. + * + * .. caution:: Memory that is allocated with the standard ``malloc`` + * and ``free`` is not visible to ``libgc`` when it is looking for + * references. This means that if, let's say, you store a + * :cpp:class:`immer::vector` using a ``gc_heap`` inside a + * ``std::vector`` that uses a standard allocator, the memory of + * the former might be released automatically at unexpected times + * causing crashes. + * + * .. caution:: When using a ``gc_heap`` in combination with immutable + * containers, the destructors of the contained objects will never + * be called. It is ok to store containers inside containers as + * long as all of them use a ``gc_heap`` consistently, but storing + * other kinds of objects with relevant destructors + * (e.g. containers with reference counting or other kinds of + * *resource handles*) might cause memory leaks and other problems. + * + * .. _boehm's conservative garbage collector: https://github.com/ivmai/bdwgc + * + * @endrst + */ +class gc_heap +{ +public: + static void* allocate(std::size_t n) + { + IMMER_GC_INIT_GUARD_; + auto p = GC_malloc(n); + if (IMMER_UNLIKELY(!p)) + throw std::bad_alloc{}; + return p; + } + + static void* allocate(std::size_t n, norefs_tag) + { + IMMER_GC_INIT_GUARD_; + auto p = GC_malloc_atomic(n); + if (IMMER_UNLIKELY(!p)) + throw std::bad_alloc{}; + return p; + } + + static void deallocate(std::size_t, void* data) + { + GC_free(data); + } + + static void deallocate(std::size_t, void* data, norefs_tag) + { + GC_free(data); + } +}; + +} // namespace immer diff --git a/src/immer/heap/heap_policy.hpp b/src/immer/heap/heap_policy.hpp new file mode 100644 index 000000000000..a0723cbcf2b9 --- /dev/null +++ b/src/immer/heap/heap_policy.hpp @@ -0,0 +1,148 @@ +// +// 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 + +namespace immer { + +/*! + * Heap policy that unconditionally uses its `Heap` argument. + */ +template +struct heap_policy +{ + using type = Heap; + + template + struct optimized + { + using type = Heap; + }; +}; + +template +struct enable_optimized_heap_policy +{ + static void* operator new (std::size_t size) + { + using heap_type = typename HeapPolicy + ::template optimized::type; + + return heap_type::allocate(size); + } + + static void operator delete (void* data, std::size_t size) + { + using heap_type = typename HeapPolicy + ::template optimized::type; + + heap_type::deallocate(size, data); + } +}; + +/*! + * Heap policy that returns a heap with a free list of objects + * of `max_size = max(Sizes...)` on top an underlying `Heap`. Note + * these two properties of the resulting heap: + * + * - Allocating an object that is bigger than `max_size` may trigger + * *undefined behavior*. + * + * - Allocating an object of size less than `max_size` still + * returns an object of `max_size`. + * + * Basically, this heap will always return objects of `max_size`. + * When an object is freed, it does not directly invoke `std::free`, + * but it keeps the object in a global linked list instead. When a + * new object is requested, it does not need to call `std::malloc` but + * it can directly pop and return the other object from the global + * list, a much faster operation. + * + * This actually creates a hierarchy with two free lists: + * + * - A `thread_local` free list is used first. It does not need any + * kind of synchronization and is very fast. When the thread + * finishes, its contents are returned to the next free list. + * + * - A global free list using lock-free access via atomics. + * + * @tparam Heap Heap to be used when the free list is empty. + * + * @rst + * + * .. tip:: For many applications that use immutable data structures + * significantly, this is actually the best heap policy, and it + * might become the default in the future. + * + * Note that most our data structures internally use trees with the + * same big branching factors. This means that all *vectors*, + * *maps*, etc. can just allocate elements from the same free-list + * optimized heap. Not only does this lowers the allocation time, + * but also makes up for more efficient *cache utilization*. When + * a new node is needed, there are high chances the allocator will + * return a node that was just accessed. When batches of immutable + * updates are made, this can make a significant difference. + * + * @endrst + */ +template +struct free_list_heap_policy +{ + using type = debug_size_heap; + + template + struct optimized + { + using type = split_heap< + Size, + with_free_list_node< + thread_local_free_list_heap< + Size, + Limit, + free_list_heap< + Size, Limit, + debug_size_heap>>>, + debug_size_heap>; + }; +}; + +/*! + * Similar to @ref free_list_heap_policy, but it assumes no + * multi-threading, so a single global free list with no concurrency + * checks is used. + */ +template +struct unsafe_free_list_heap_policy +{ + using type = Heap; + + template + struct optimized + { + using type = split_heap< + Size, + with_free_list_node< + unsafe_free_list_heap< + Size, Limit, + debug_size_heap>>, + debug_size_heap>; + }; +}; + +} // namespace immer diff --git a/src/immer/heap/identity_heap.hpp b/src/immer/heap/identity_heap.hpp new file mode 100644 index 000000000000..032cb3f221d0 --- /dev/null +++ b/src/immer/heap/identity_heap.hpp @@ -0,0 +1,34 @@ +// +// 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 + +namespace immer { + +/*! + * A heap that simply passes on to the parent heap. + */ +template +struct identity_heap : Base +{ + template + static void* allocate(std::size_t size, Tags... tags) + { + return Base::allocate(size, tags...); + } + + template + static void deallocate(std::size_t size, void* data, Tags... tags) + { + Base::deallocate(size, data, tags...); + } +}; + +} // namespace immer diff --git a/src/immer/heap/malloc_heap.hpp b/src/immer/heap/malloc_heap.hpp new file mode 100644 index 000000000000..73909058dec3 --- /dev/null +++ b/src/immer/heap/malloc_heap.hpp @@ -0,0 +1,47 @@ +// +// 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 + +namespace immer { + +/*! + * A heap that uses `std::malloc` and `std::free` to manage memory. + */ +struct malloc_heap +{ + /*! + * Returns a pointer to a memory region of size `size`, if the + * allocation was successful and throws `std::bad_alloc` otherwise. + */ + template + static void* allocate(std::size_t size, Tags...) + { + auto p = std::malloc(size); + if (IMMER_UNLIKELY(!p)) + throw std::bad_alloc{}; + return p; + } + + /*! + * Releases a memory region `data` that was previously returned by + * `allocate`. One must not use nor deallocate again a memory + * region that once it has been deallocated. + */ + static void deallocate(std::size_t, void* data) + { + std::free(data); + } +}; + +} // namespace immer diff --git a/src/immer/heap/split_heap.hpp b/src/immer/heap/split_heap.hpp new file mode 100644 index 000000000000..8ce210815f4c --- /dev/null +++ b/src/immer/heap/split_heap.hpp @@ -0,0 +1,41 @@ +// +// 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 + +namespace immer { + +/*! + * Adaptor that uses `SmallHeap` for allocations that are smaller or + * equal to `Size` and `BigHeap` otherwise. + */ +template +struct split_heap +{ + template + static void* allocate(std::size_t size, Tags... tags) + { + return size <= Size + ? SmallHeap::allocate(size, tags...) + : BigHeap::allocate(size, tags...); + } + + template + static void deallocate(std::size_t size, void* data, Tags... tags) + { + if (size <= Size) + SmallHeap::deallocate(size, data, tags...); + else + BigHeap::deallocate(size, data, tags...); + } +}; + +} // namespace immer diff --git a/src/immer/heap/tags.hpp b/src/immer/heap/tags.hpp new file mode 100644 index 000000000000..a3012bd3ef35 --- /dev/null +++ b/src/immer/heap/tags.hpp @@ -0,0 +1,15 @@ +// +// 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 + +namespace immer { + +struct norefs_tag {}; + +} // namespace immer diff --git a/src/immer/heap/thread_local_free_list_heap.hpp b/src/immer/heap/thread_local_free_list_heap.hpp new file mode 100644 index 000000000000..2539ce73c102 --- /dev/null +++ b/src/immer/heap/thread_local_free_list_heap.hpp @@ -0,0 +1,55 @@ +// +// 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 + +namespace immer { +namespace detail { + +template +struct thread_local_free_list_storage +{ + struct head_t + { + free_list_node* data; + std::size_t count; + + ~head_t() { Heap::clear(); } + }; + + static head_t& head() + { + thread_local static head_t head_{nullptr, 0}; + return head_; + } +}; + +} // namespace detail + +/*! + * Adaptor that does not release the memory to the parent heap but + * instead it keeps the memory in a `thread_local` global free + * list. Must be preceded by a `with_data` heap + * adaptor. When the current thread finishes, the memory is returned + * to the parent heap. + * + * @tparam Size Maximum size of the objects to be allocated. + * @tparam Limit Maximum number of elements to keep in the free list. + * @tparam Base Type of the parent heap. + */ +template +struct thread_local_free_list_heap : detail::unsafe_free_list_heap_impl< + detail::thread_local_free_list_storage, + Size, + Limit, + Base> +{}; + +} // namespace immer diff --git a/src/immer/heap/unsafe_free_list_heap.hpp b/src/immer/heap/unsafe_free_list_heap.hpp new file mode 100644 index 000000000000..9a1fdd73e44d --- /dev/null +++ b/src/immer/heap/unsafe_free_list_heap.hpp @@ -0,0 +1,108 @@ +// +// 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 + +namespace immer { +namespace detail { + +template +struct unsafe_free_list_storage +{ + struct head_t + { + free_list_node* data; + std::size_t count; + }; + + static head_t& head() + { + static head_t head_ {nullptr, 0}; + return head_; + } +}; + +template class Storage, + std::size_t Size, + std::size_t Limit, + typename Base> +class unsafe_free_list_heap_impl : Base +{ + using storage = Storage; + +public: + using base_t = Base; + + template + static void* allocate(std::size_t size, Tags...) + { + assert(size <= sizeof(free_list_node) + Size); + assert(size >= sizeof(free_list_node)); + + auto n = storage::head().data; + if (!n) { + auto p = base_t::allocate(Size + sizeof(free_list_node)); + return static_cast(p); + } + --storage::head().count; + storage::head().data = n->next; + return n; + } + + template + static void deallocate(std::size_t size, void* data, Tags...) + { + assert(size <= sizeof(free_list_node) + Size); + assert(size >= sizeof(free_list_node)); + + if (storage::head().count >= Limit) + base_t::deallocate(Size + sizeof(free_list_node), data); + else { + auto n = static_cast(data); + n->next = storage::head().data; + storage::head().data = n; + ++storage::head().count; + } + } + + static void clear() + { + while (storage::head().data) { + auto n = storage::head().data->next; + base_t::deallocate(Size + sizeof(free_list_node), storage::head().data); + storage::head().data = n; + --storage::head().count; + } + } +}; + +} // namespace detail + +/*! + * Adaptor that does not release the memory to the parent heap but + * instead it keeps the memory in a global free list that **is not + * thread-safe**. Must be preceded by a `with_data` heap adaptor. + * + * @tparam Size Maximum size of the objects to be allocated. + * @tparam Limit Maximum number of elements to keep in the free list. + * @tparam Base Type of the parent heap. + */ +template +struct unsafe_free_list_heap : detail::unsafe_free_list_heap_impl< + detail::unsafe_free_list_storage, + Size, + Limit, + Base> +{}; + +} // namespace immer diff --git a/src/immer/heap/with_data.hpp b/src/immer/heap/with_data.hpp new file mode 100644 index 000000000000..1e8c2abb082e --- /dev/null +++ b/src/immer/heap/with_data.hpp @@ -0,0 +1,43 @@ +// +// 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 + +namespace immer { + +/*! + * Appends a default constructed extra object of type `T` at the + * *before* the requested region. + * + * @tparam T Type of the appended data. + * @tparam Base Type of the parent heap. + */ +template +struct with_data : Base +{ + using base_t = Base; + + template + static void* allocate(std::size_t size, Tags... tags) + { + auto p = base_t::allocate(size + sizeof(T), tags...); + return new (p) T{} + 1; + } + + template + static void deallocate(std::size_t size, void* p, Tags... tags) + { + auto dp = static_cast(p) - 1; + dp->~T(); + base_t::deallocate(size + sizeof(T), dp, tags...); + } +}; + +} // namespace immer diff --git a/src/immer/map.hpp b/src/immer/map.hpp new file mode 100644 index 000000000000..99e483a2d40e --- /dev/null +++ b/src/immer/map.hpp @@ -0,0 +1,311 @@ +// +// 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 + +namespace immer { + +template +class map_transient; + +/*! + * Immutable unordered mapping of values from type `K` to type `T`. + * + * @tparam K The type of the keys. + * @tparam T The type of the values to be stored in the container. + * @tparam Hash The type of a function object capable of hashing + * values of type `T`. + * @tparam Equal The type of a function object capable of comparing + * values of type `T`. + * @tparam MemoryPolicy Memory management policy. See @ref + * memory_policy. + * + * @rst + * + * This cotainer provides a good trade-off between cache locality, + * search, update performance and structural sharing. It does so by + * storing the data in contiguous chunks of :math:`2^{B}` elements. + * When storing big objects, the size of these contiguous chunks can + * become too big, damaging performance. If this is measured to be + * problematic for a specific use-case, it can be solved by using a + * `immer::box` to wrap the type `T`. + * + * **Example** + * .. literalinclude:: ../example/map/intro.cpp + * :language: c++ + * :start-after: intro/start + * :end-before: intro/end + * + * @endrst + * + */ +template , + typename Equal = std::equal_to, + typename MemoryPolicy = default_memory_policy, + detail::hamts::bits_t B = default_bits> +class map +{ + using value_t = std::pair; + + struct project_value + { + const T& operator() (const value_t& v) const noexcept + { + return v.second; + } + }; + + struct project_value_ptr + { + const T* operator() (const value_t& v) const noexcept + { + return &v.second; + } + }; + + struct combine_value + { + template + value_t operator() (Kf&& k, Tf&& v) const + { + return { std::forward(k), std::forward(v) }; + } + }; + + struct default_value + { + const T& operator() () const + { + static T v{}; + return v; + } + }; + + struct error_value + { + const T& operator() () const + { + throw std::out_of_range{"key not found"}; + } + }; + + struct hash_key + { + auto operator() (const value_t& v) + { return Hash{}(v.first); } + + auto operator() (const K& v) + { return Hash{}(v); } + }; + + struct equal_key + { + auto operator() (const value_t& a, const value_t& b) + { return Equal{}(a.first, b.first); } + + auto operator() (const value_t& a, const K& b) + { return Equal{}(a.first, b); } + }; + + struct equal_value + { + auto operator() (const value_t& a, const value_t& b) + { return Equal{}(a.first, b.first) && a.second == b.second; } + }; + + using impl_t = detail::hamts::champ< + value_t, hash_key, equal_key, MemoryPolicy, B>; + +public: + using key_type = K; + using mapped_type = T; + using value_type = std::pair; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const value_type&; + using const_reference = const value_type&; + + using iterator = detail::hamts::champ_iterator< + value_t, hash_key, equal_key, MemoryPolicy, B>; + using const_iterator = iterator; + + using transient_type = map_transient; + + /*! + * Default constructor. It creates a set of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + map() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `1` when the key `k` is contained in the map or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + size_type count(const K& k) const + { return impl_.template get, + detail::constantly>(k); } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + const T& operator[] (const K& k) const + { return impl_.template get(k); } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + const T& at(const K& k) const + { return impl_.template get(k); } + + + /*! + * Returns a pointer to the value associated with the key `k`. If + * the key is not contained in the map, a `nullptr` is returned. + * It does not allocate memory and its complexity is *effectively* + * @f$ O(1) @f$. + * + * @rst + * + * .. admonition:: Why doesn't this function return an iterator? + * + * Associative containers from the C++ standard library provide a + * ``find`` method that returns an iterator pointing to the + * element in the container or ``end()`` when the key is missing. + * In the case of an unordered container, the only meaningful + * thing one may do with it is to compare it with the end, to + * test if the find was succesfull, and dereference it. This + * comparison is cumbersome compared to testing for a non-empty + * optional value. Furthermore, for an immutable container, + * returning an iterator would have some additional performance + * cost, with no benefits otherwise. + * + * In our opinion, this function should return a + * ``std::optional`` but this construction is not valid + * in any current standard. As a compromise we return a + * pointer, which has similar syntactic properties yet it is + * unfortunatelly unnecessarily unrestricted. + * + * @endrst + */ + const T* find(const K& k) const + { return impl_.template get>(k); } + + /*! + * Returns whether the sets are equal. + */ + bool operator==(const map& other) const + { return impl_.template equals(other.impl_); } + bool operator!=(const map& other) const + { return !(*this == other); } + + /*! + * Returns a map containing the association `value`. If the key is + * already in the map, it replaces its association in the map. + * It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + map insert(value_type value) const + { return impl_.add(std::move(value)); } + + /*! + * Returns a map containing the association `(k, v)`. If the key + * is already in the map, it replaces its association in the map. + * It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + map set(key_type k, mapped_type v) const + { return impl_.add({std::move(k), std::move(v)}); } + + /*! + * Returns a map replacing the association `(k, v)` by the + * association new association `(k, fn(v))`, where `v` is the + * currently associated value for `k` in the map or a default + * constructed value otherwise. It may allocate memory + * and its complexity is *effectively* @f$ O(1) @f$. + */ + template + map update(key_type k, Fn&& fn) const + { + return impl_ + .template update( + std::move(k), std::forward(fn)); + } + + /*! + * Returns a map without the key `k`. If the key is not + * associated in the map it returns the same map. It may allocate + * memory and its complexity is *effectively* @f$ O(1) @f$. + */ + map erase(const K& k) const + { return impl_.sub(k); } + + /*! + * Returns an @a transient form of this container, a + * `immer::map_transient`. + */ + transient_type transient() const& + { return transient_type{ impl_ }; } + transient_type transient() && + { return transient_type{ std::move(impl_) }; } + + // Semi-private + const impl_t& impl() const { return impl_; } + +private: + friend transient_type; + + map(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/src/immer/map_transient.hpp b/src/immer/map_transient.hpp new file mode 100644 index 000000000000..542dac1d5deb --- /dev/null +++ b/src/immer/map_transient.hpp @@ -0,0 +1,29 @@ +// +// 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 + +namespace immer { + +/*! + * **WORK IN PROGRESS** + */ +template , + typename Equal = std::equal_to, + typename MemoryPolicy = default_memory_policy, + detail::hamts::bits_t B = default_bits> +class map_transient; + +} // namespace immer diff --git a/src/immer/memory_policy.hpp b/src/immer/memory_policy.hpp new file mode 100644 index 000000000000..9d95f56a0e48 --- /dev/null +++ b/src/immer/memory_policy.hpp @@ -0,0 +1,138 @@ +// +// 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 { + +/*! + * Metafunction that returns the best *transience policy* to use for a + * given *refcount policy*. + */ +template +struct get_transience_policy + : std::conditional::value, + gc_transience_policy, + no_transience_policy> +{}; + +template +using get_transience_policy_t = typename get_transience_policy::type; + +/*! + * Metafunction that returns wether to *prefer fewer bigger objects* + * to use for a given *heap policy*. + */ +template +struct get_prefer_fewer_bigger_objects + : std::integral_constant + >::value> +{}; + +template +constexpr auto get_prefer_fewer_bigger_objects_v = + get_prefer_fewer_bigger_objects::value; + +/*! + * Metafunction that returns wether to use *transient R-Values* + * for a given *refcount policy*. + */ +template +struct get_use_transient_rvalues + : std::integral_constant::value> +{}; + +template +constexpr auto get_use_transient_rvalues_v = get_use_transient_rvalues::value; + +/*! + * This is a default implementation of a *memory policy*. A memory + * policy is just a bag of other policies plus some flags with hints + * to the user about the best way to use these strategies. + * + * @tparam HeapPolicy A *heap policy*, for example, @ref heap_policy. + * @tparam RefcountPolicy A *reference counting policy*, for example, + * @ref refcount_policy. + * @tparam TransiencePolicy A *transience policy*, for example, + * @ref no_transience_policy. + * @tparam PreferFewerBiggerObjects Boolean flag indicating whether + * the user should prefer to allocate memory in bigger chungs + * --e.g. by putting various objects in the same memory + * region-- or not. + * @tparam UseTransientRValues Boolean flag indicating whether + * immutable containers should try to modify contents in-place + * when manipulating an r-value reference. + */ +template , + bool PreferFewerBiggerObjects = get_prefer_fewer_bigger_objects_v, + bool UseTransientRValues = get_use_transient_rvalues_v> +struct memory_policy +{ + using heap = HeapPolicy; + using refcount = RefcountPolicy; + using transience = TransiencePolicy; + + static constexpr bool prefer_fewer_bigger_objects = + PreferFewerBiggerObjects; + + static constexpr bool use_transient_rvalues = + UseTransientRValues; + + using transience_t = typename transience::template apply::type; +}; + +/*! + * The default *heap policy* just uses the standard heap with a + * @ref free_list_heap_policy. If `IMMER_NO_FREE_LIST` is defined to `1` + * then it just uses the standard heap. + */ +#if IMMER_NO_FREE_LIST +using default_heap_policy = heap_policy>; +#else +#if IMMER_NO_THREAD_SAFETY +using default_heap_policy = unsafe_free_list_heap_policy; +#else +using default_heap_policy = free_list_heap_policy; +#endif +#endif + +/*! + * By default we use thread safe reference counting. + */ +#if IMMER_NO_THREAD_SAFETY +using default_refcount_policy = unsafe_refcount_policy; +#else +using default_refcount_policy = refcount_policy; +#endif + +/*! + * The default memory policy. + */ +using default_memory_policy = memory_policy< + default_heap_policy, + default_refcount_policy>; + +} // namespace immer diff --git a/src/immer/refcount/enable_intrusive_ptr.hpp b/src/immer/refcount/enable_intrusive_ptr.hpp new file mode 100644 index 000000000000..1185a219fd9b --- /dev/null +++ b/src/immer/refcount/enable_intrusive_ptr.hpp @@ -0,0 +1,37 @@ +// +// 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 + +namespace immer { + +template +class enable_intrusive_ptr +{ + mutable RefcountPolicy refcount_data_; + +public: + enable_intrusive_ptr() + : refcount_data_{disowned{}} + {} + + friend void intrusive_ptr_add_ref(const Deriv* x) + { + x->refcount_data_.inc(); + } + + friend void intrusive_ptr_release(const Deriv* x) + { + if (x->refcount_data_.dec()) + delete x; + } +}; + +} // namespace immer diff --git a/src/immer/refcount/no_refcount_policy.hpp b/src/immer/refcount/no_refcount_policy.hpp new file mode 100644 index 000000000000..95663368f112 --- /dev/null +++ b/src/immer/refcount/no_refcount_policy.hpp @@ -0,0 +1,44 @@ +// +// 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 + +namespace immer { + +struct disowned {}; + +struct no_spinlock +{ + bool try_lock() { return true; } + void lock() {} + void unlock() {} + + struct scoped_lock + { + scoped_lock(no_spinlock&) {} + }; +}; + +/*! + * Disables reference counting, to be used with an alternative garbage + * collection strategy like a `gc_heap`. + */ +struct no_refcount_policy +{ + using spinlock_type = no_spinlock; + + no_refcount_policy() {}; + no_refcount_policy(disowned) {} + + void inc() {} + bool dec() { return false; } + void dec_unsafe() {} + bool unique() { return false; } +}; + +} // namespace immer diff --git a/src/immer/refcount/refcount_policy.hpp b/src/immer/refcount/refcount_policy.hpp new file mode 100644 index 000000000000..68ba209c8c76 --- /dev/null +++ b/src/immer/refcount/refcount_policy.hpp @@ -0,0 +1,111 @@ +// +// 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 + +// This has been shamelessly copied from boost... +#if defined(_MSC_VER) && _MSC_VER >= 1310 && (defined(_M_IX86) || defined(_M_X64)) && !defined(__c2__) +extern "C" void _mm_pause(); +# define IMMER_SMT_PAUSE _mm_pause() +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) +# define IMMER_SMT_PAUSE __asm__ __volatile__( "rep; nop" : : : "memory" ) +#endif + +namespace immer { + +// This is an atomic spinlock similar to the one used by boost to provide +// "atomic" shared_ptr operations. It also does not differ much from the one +// from libc++ or libstdc++... +struct spinlock +{ + std::atomic_flag v_{}; + + bool try_lock() + { + return !v_.test_and_set(std::memory_order_acquire); + } + + void lock() + { + for (auto k = 0u; !try_lock(); ++k) { + if (k < 4) + continue; +#ifdef IMMER_SMT_PAUSE + else if (k < 16) + IMMER_SMT_PAUSE; +#endif + else + std::this_thread::yield(); + } + } + + void unlock() + { + v_.clear(std::memory_order_release); + } + + struct scoped_lock + { + scoped_lock(const scoped_lock&) = delete; + scoped_lock& operator=(const scoped_lock& ) = delete; + + explicit scoped_lock(spinlock& sp) + : sp_{sp} + { sp.lock(); } + + ~scoped_lock() + { sp_.unlock();} + + private: + spinlock& sp_; + }; +}; + +/*! + * A reference counting policy implemented using an *atomic* `int` + * count. It is **thread-safe**. + */ +struct refcount_policy +{ + using spinlock_type = spinlock; + + mutable std::atomic refcount; + + refcount_policy() : refcount{1} {}; + refcount_policy(disowned) : refcount{0} {} + + void inc() + { + refcount.fetch_add(1, std::memory_order_relaxed); + } + + bool dec() + { + return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); + } + + void dec_unsafe() + { + assert(refcount.load() > 1); + refcount.fetch_sub(1, std::memory_order_relaxed); + } + + bool unique() + { + return refcount == 1; + } +}; + +} // namespace immer diff --git a/src/immer/refcount/unsafe_refcount_policy.hpp b/src/immer/refcount/unsafe_refcount_policy.hpp new file mode 100644 index 000000000000..2a170b7573cc --- /dev/null +++ b/src/immer/refcount/unsafe_refcount_policy.hpp @@ -0,0 +1,37 @@ +// +// 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 + +namespace immer { + +/*! + * A reference counting policy implemented using a raw `int` count. + * It is **not thread-safe**. + */ +struct unsafe_refcount_policy +{ + using spinlock_type = no_spinlock; + + mutable int refcount; + + unsafe_refcount_policy() : refcount{1} {}; + unsafe_refcount_policy(disowned) : refcount{0} {} + + void inc() { ++refcount; } + bool dec() { return --refcount == 0; } + void dec_unsafe() { --refcount; } + bool unique() { return refcount == 1; } +}; + +} // namespace immer diff --git a/src/immer/set.hpp b/src/immer/set.hpp new file mode 100644 index 000000000000..5d9eed8ca6d3 --- /dev/null +++ b/src/immer/set.hpp @@ -0,0 +1,161 @@ +// +// 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 + +namespace immer { + +template +class set_transient; + +/*! + * Immutable set representing an unordered bag of values. + * + * @tparam T The type of the values to be stored in the container. + * @tparam Hash The type of a function object capable of hashing + * values of type `T`. + * @tparam Equal The type of a function object capable of comparing + * values of type `T`. + * @tparam MemoryPolicy Memory management policy. See @ref + * memory_policy. + * + * @rst + * + * This cotainer provides a good trade-off between cache locality, + * membership checks, update performance and structural sharing. It + * does so by storing the data in contiguous chunks of :math:`2^{B}` + * elements. When storing big objects, the size of these contiguous + * chunks can become too big, damaging performance. If this is + * measured to be problematic for a specific use-case, it can be + * solved by using a `immer::box` to wrap the type `T`. + * + * **Example** + * .. literalinclude:: ../example/set/intro.cpp + * :language: c++ + * :start-after: intro/start + * :end-before: intro/end + * + * @endrst + * + */ +template , + typename Equal = std::equal_to, + typename MemoryPolicy = default_memory_policy, + detail::hamts::bits_t B = default_bits> +class set +{ + using impl_t = detail::hamts::champ; + +public: + using key_type = T; + using value_type = T; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const T&; + using const_reference = const T&; + + using iterator = detail::hamts::champ_iterator; + using const_iterator = iterator; + + using transient_type = set_transient; + + /*! + * Default constructor. It creates a set of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + set() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `1` when `value` is contained in the set or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + size_type count(const T& value) const + { return impl_.template get, + detail::constantly>(value); } + + /*! + * Returns whether the sets are equal. + */ + bool operator==(const set& other) const + { return impl_.equals(other.impl_); } + bool operator!=(const set& other) const + { return !(*this == other); } + + /*! + * Returns a set containing `value`. If the `value` is already in + * the set, it returns the same set. It may allocate memory and + * its complexity is *effectively* @f$ O(1) @f$. + */ + set insert(T value) const + { return impl_.add(std::move(value)); } + + /*! + * Returns a set without `value`. If the `value` is not in the + * set it returns the same set. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + set erase(const T& value) const + { return impl_.sub(value); } + + /*! + * Returns an @a transient form of this container, a + * `immer::set_transient`. + */ + transient_type transient() const& + { return transient_type{ impl_ }; } + transient_type transient() && + { return transient_type{ std::move(impl_) }; } + + // Semi-private + const impl_t& impl() const { return impl_; } + +private: + friend transient_type; + + set(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/src/immer/set_transient.hpp b/src/immer/set_transient.hpp new file mode 100644 index 000000000000..cba41601c694 --- /dev/null +++ b/src/immer/set_transient.hpp @@ -0,0 +1,28 @@ +// +// 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 + +namespace immer { + +/*! + * **WORK IN PROGRESS** + */ +template , + typename Equal = std::equal_to, + typename MemoryPolicy = default_memory_policy, + detail::hamts::bits_t B = default_bits> +class set_transient; + +} // namespace immer diff --git a/src/immer/transience/gc_transience_policy.hpp b/src/immer/transience/gc_transience_policy.hpp new file mode 100644 index 000000000000..7137bed83952 --- /dev/null +++ b/src/immer/transience/gc_transience_policy.hpp @@ -0,0 +1,110 @@ +// +// 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 + +namespace immer { + +/*! + * Provides transience ownership tracking when a *tracing garbage + * collector* is used instead of reference counting. + * + * @rst + * + * .. warning:: Using this policy without an allocation scheme that + * includes automatic tracing garbage collection may cause memory + * leaks. + * + * @endrst + */ +struct gc_transience_policy +{ + template + struct apply + { + struct type + { + using heap_ = typename HeapPolicy::type; + + struct edit + { + void* v; + edit() = delete; + bool operator==(edit x) const { return v == x.v; } + bool operator!=(edit x) const { return v != x.v; } + }; + + struct owner + { + void* make_token_() + { + return heap_::allocate(1, norefs_tag{}); + }; + + mutable std::atomic token_; + + operator edit () { return { token_ }; } + + owner() + : token_{make_token_()} + {} + owner(const owner& o) + : token_{make_token_()} + { + o.token_ = make_token_(); + } + owner(owner&& o) noexcept + : token_{o.token_.load()} + {} + owner& operator=(const owner& o) + { + o.token_ = make_token_(); + token_ = make_token_(); + return *this; + } + owner& operator=(owner&& o) noexcept + { + token_ = o.token_.load(); + return *this; + } + }; + + struct ownee + { + edit token_ {nullptr}; + + ownee& operator=(edit e) + { + assert(e != noone); + // This would be a nice safety plug but it sadly + // does not hold during transient concatenation. + // assert(token_ == e || token_ == edit{nullptr}); + token_ = e; + return *this; + } + + bool can_mutate(edit t) const { return token_ == t; } + bool owned() const { return token_ != edit{nullptr}; } + }; + + static owner noone; + }; + }; +}; + +template +typename gc_transience_policy::apply::type::owner +gc_transience_policy::apply::type::noone = {}; + +} // namespace immer diff --git a/src/immer/transience/no_transience_policy.hpp b/src/immer/transience/no_transience_policy.hpp new file mode 100644 index 000000000000..aa3d44ed4287 --- /dev/null +++ b/src/immer/transience/no_transience_policy.hpp @@ -0,0 +1,48 @@ +// +// 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 + +namespace immer { + +/*! + * Disables any special *transience* tracking. To be used when + * *reference counting* is available instead. + */ +struct no_transience_policy +{ + template + struct apply + { + struct type + { + struct edit {}; + + struct owner + { + operator edit () const { return {}; } + owner& operator=(const owner&) { return *this; }; + }; + + struct ownee + { + ownee& operator=(edit) { return *this; }; + bool can_mutate(edit) const { return false; } + bool owned() const { return false; } + }; + + static owner noone; + }; + }; +}; + +template +typename no_transience_policy::apply::type::owner +no_transience_policy::apply::type::noone = {}; + +} // namespace immer diff --git a/src/immer/vector.hpp b/src/immer/vector.hpp new file mode 100644 index 000000000000..b9c9ef48f6c8 --- /dev/null +++ b/src/immer/vector.hpp @@ -0,0 +1,354 @@ +// +// 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 + +#if IMMER_DEBUG_PRINT +#include +#endif + +namespace immer { + +template +class flex_vector; + +template +class vector_transient; + +/*! + * Immutable sequential container supporting both random access and + * structural sharing. + * + * @tparam T The type of the values to be stored in the container. + * @tparam MemoryPolicy Memory management policy. See @ref + * memory_policy. + * + * @rst + * + * This cotainer provides a good trade-off between cache locality, + * random access, update performance and structural sharing. It does + * so by storing the data in contiguous chunks of :math:`2^{BL}` + * elements. By default, when ``sizeof(T) == sizeof(void*)`` then + * :math:`B=BL=5`, such that data would be stored in contiguous + * chunks of :math:`32` elements. + * + * You may learn more about the meaning and implications of ``B`` and + * ``BL`` parameters in the :doc:`implementation` section. + * + * .. note:: In several methods we say that their complexity is + * *effectively* :math:`O(...)`. Do not confuse this with the word + * *amortized*, which has a very different meaning. In this + * context, *effective* means that while the + * mathematically rigurous + * complexity might be higher, for all practical matters the + * provided complexity is more useful to think about the actual + * cost of the operation. + * + * **Example** + * .. literalinclude:: ../example/vector/intro.cpp + * :language: c++ + * :start-after: intro/start + * :end-before: intro/end + * + * @endrst + */ +template > +class vector +{ + using impl_t = detail::rbts::rbtree; + using flex_t = flex_vector; + + using move_t = + std::integral_constant; + +public: + static constexpr auto bits = B; + static constexpr auto bits_leaf = BL; + using memory_policy = MemoryPolicy; + + using value_type = T; + using reference = const T&; + using size_type = detail::rbts::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = detail::rbts::rbtree_iterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using transient_type = vector_transient; + + /*! + * Default constructor. It creates a vector of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + vector() = default; + + /*! + * Constructs a vector containing the elements in `values`. + */ + vector(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a vector containing the elements in the range + * defined by the input iterator `first` and range sentinel `last`. + */ + template , bool> = true> + vector(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + + /*! + * Constructs a vector containing the element `val` repeated `n` + * times. + */ + vector(size_type n, T v = {}) + : impl_{impl_t::from_fill(n, v)} + {} + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.size == 0; } + + /*! + * Access the last element. + */ + const T& back() const { return impl_.back(); } + + /*! + * Access the first element. + */ + const T& front() const { return impl_.front(); } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Returns whether the vectors are equal. + */ + bool operator==(const vector& other) const + { return impl_.equals(other.impl_); } + bool operator!=(const vector& other) const + { return !(*this == other); } + + /*! + * Returns a vector with `value` inserted at the end. It may + * allocate memory and its complexity is *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/vector/vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: push-back/start + * :end-before: push-back/end + * + * @endrst + */ + vector push_back(value_type value) const& + { return impl_.push_back(std::move(value)); } + + decltype(auto) push_back(value_type value) && + { return push_back_move(move_t{}, std::move(value)); } + + /*! + * Returns a vector containing value `value` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/vector/vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: set/start + * :end-before: set/end + * + * @endrst + */ + vector set(size_type index, value_type value) const& + { return impl_.assoc(index, std::move(value)); } + + decltype(auto) set(size_type index, value_type value) && + { return set_move(move_t{}, index, std::move(value)); } + + /*! + * Returns a vector containing the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `0 >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/vector/vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: update/start + * :end-before: update/end + * + * @endrst + */ + template + vector update(size_type index, FnT&& fn) const& + { return impl_.update(index, std::forward(fn)); } + + template + decltype(auto) update(size_type index, FnT&& fn) && + { return update_move(move_t{}, index, std::forward(fn)); } + + /*! + * Returns a vector containing only the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * @rst + * + * **Example** + * .. literalinclude:: ../example/vector/vector.cpp + * :language: c++ + * :dedent: 8 + * :start-after: take/start + * :end-before: take/end + * + * @endrst + */ + vector take(size_type elems) const& + { return impl_.take(elems); } + + decltype(auto) take(size_type elems) && + { return take_move(move_t{}, elems); } + + /*! + * Returns an @a transient form of this container, an + * `immer::vector_transient`. + */ + transient_type transient() const& + { return transient_type{ impl_ }; } + transient_type transient() && + { return transient_type{ std::move(impl_) }; } + + // Semi-private + const impl_t& impl() const { return impl_; } + +#if IMMER_DEBUG_PRINT + void debug_print(std::ostream& out=std::cerr) const + { flex_t{*this}.debug_print(out); } +#endif + +private: + friend flex_t; + friend transient_type; + + vector(impl_t impl) + : impl_(std::move(impl)) + { +#if IMMER_DEBUG_PRINT + // force the compiler to generate debug_print, so we can call + // it from a debugger + [](volatile auto){}(&vector::debug_print); +#endif + } + + vector&& push_back_move(std::true_type, value_type value) + { impl_.push_back_mut({}, std::move(value)); return std::move(*this); } + vector push_back_move(std::false_type, value_type value) + { return impl_.push_back(std::move(value)); } + + vector&& set_move(std::true_type, size_type index, value_type value) + { impl_.assoc_mut({}, index, std::move(value)); return std::move(*this); } + vector set_move(std::false_type, size_type index, value_type value) + { return impl_.assoc(index, std::move(value)); } + + template + vector&& update_move(std::true_type, size_type index, Fn&& fn) + { impl_.update_mut({}, index, std::forward(fn)); return std::move(*this); } + template + vector update_move(std::false_type, size_type index, Fn&& fn) + { return impl_.update(index, std::forward(fn)); } + + vector&& take_move(std::true_type, size_type elems) + { impl_.take_mut({}, elems); return std::move(*this); } + vector take_move(std::false_type, size_type elems) + { return impl_.take(elems); } + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/src/immer/vector_transient.hpp b/src/immer/vector_transient.hpp new file mode 100644 index 000000000000..4cf81f07114a --- /dev/null +++ b/src/immer/vector_transient.hpp @@ -0,0 +1,189 @@ +// +// 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 + +namespace immer { + +template +class vector; + +template +class flex_vector_transient; + +/*! + * Mutable version of `immer::vector`. + * + * @rst + * + * Refer to :doc:`transients` to learn more about when and how to use + * the mutable versions of immutable containers. + * + * @endrst + */ +template > +class vector_transient + : MemoryPolicy::transience_t::owner +{ + using impl_t = detail::rbts::rbtree; + using flex_t = flex_vector_transient; + using owner_t = typename MemoryPolicy::transience_t::owner; + +public: + static constexpr auto bits = B; + static constexpr auto bits_leaf = BL; + using memory_policy = MemoryPolicy; + + using value_type = T; + using reference = const T&; + using size_type = detail::rbts::size_t; + using difference_type = std::ptrdiff_t; + using const_reference = const T&; + + using iterator = detail::rbts::rbtree_iterator; + using const_iterator = iterator; + using reverse_iterator = std::reverse_iterator; + + using persistent_type = vector; + + /*! + * Default constructor. It creates a mutable vector of `size() == + * 0`. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + vector_transient() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + iterator end() const { return {impl_, typename iterator::end_t{}}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing at the first element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rbegin() const { return reverse_iterator{end()}; } + + /*! + * Returns an iterator that traverses the collection backwards, + * pointing after the last element of the reversed collection. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + reverse_iterator rend() const { return reverse_iterator{begin()}; } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + bool empty() const { return impl_.size == 0; } + + /*! + * Returns a `const` reference to the element at position `index`. + * It is undefined when @f$ 0 index \geq size() @f$. It does not + * allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + reference operator[] (size_type index) const + { return impl_.get(index); } + + /*! + * Returns a `const` reference to the element at position + * `index`. It throws an `std::out_of_range` exception when @f$ + * index \geq size() @f$. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + reference at(size_type index) const + { return impl_.get_check(index); } + + /*! + * Inserts `value` at the end. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + void push_back(value_type value) + { impl_.push_back_mut(*this, std::move(value)); } + + /*! + * Sets to the value `value` at position `idx`. + * Undefined for `index >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void set(size_type index, value_type value) + { impl_.assoc_mut(*this, index, std::move(value)); } + + /*! + * Updates the vector to contain the result of the expression + * `fn((*this)[idx])` at position `idx`. + * Undefined for `0 >= size()`. + * It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + void update(size_type index, FnT&& fn) + { impl_.update_mut(*this, index, std::forward(fn)); } + + /*! + * Resizes the vector to only contain the first `min(elems, size())` + * elements. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void take(size_type elems) + { impl_.take_mut(*this, elems); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::vector`. + */ + persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return persistent_type{ impl_ }; + } + persistent_type persistent() && + { return persistent_type{ std::move(impl_) }; } + +private: + friend flex_t; + friend persistent_type; + + vector_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer From 3f74c88c0526b65f8f855ec12be52103313c2737 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 26 Mar 2021 17:12:47 +0100 Subject: [PATCH 03/25] [Build] CMake: add immer headers --- CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae6ce0555fcc..ce7af246c25a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -173,6 +173,7 @@ file(GLOB CONSENSUS_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/consensus/*.h) file(GLOB CTAES_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/crypto/ctaes/*.h) file(GLOB ZRUST_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/rust/include/*.h) file(GLOB SAPLING_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/sapling/*.h) +file(GLOB IMMER_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/immer/*.h) file(GLOB EVO_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/src/evo/*.h) source_group("BitcoinHeaders" FILES @@ -187,6 +188,7 @@ source_group("BitcoinHeaders" FILES ${CTAES_HEADERS} ${ZRUST_HEADERS} ${SAPLING_HEADERS} + ${IMMER_HEADERS} ${EVO_HEADERS} ./src/support/cleanse.h ) From 6fb2496b8e6afb14b426a30d68ce86784d7fd938 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 26 Jan 2021 15:20:00 +0100 Subject: [PATCH 04/25] Define hash function for uint256 to be used in STL-like containers >>> backported from dash@aa31acb110b82773ea135a484567bfd6fd2ac1de --- src/uint256.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/uint256.h b/src/uint256.h index 564dd7a3751a..ed92ba987685 100644 --- a/src/uint256.h +++ b/src/uint256.h @@ -141,4 +141,15 @@ const uint256 UINT256_ZERO = uint256(); const uint256 UINT256_ONE = uint256("0000000000000000000000000000000000000000000000000000000000000001"); const uint256 UINT256_MAX = uint256("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); +namespace std { + template <> + struct hash + { + std::size_t operator()(const uint256& k) const + { + return (std::size_t)k.GetCheapHash(); + } + }; +} + #endif // PIVX_UINT256_H From 693772aebb77cba4c1ca3d2be572041ec70f2a39 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sun, 24 Jan 2021 13:44:01 +0100 Subject: [PATCH 05/25] [Core] Introduce ProRegTx payload --- CMakeLists.txt | 1 + src/Makefile.am | 2 + src/evo/providertx.cpp | 242 ++++++++++++++++++++++++++++++++++ src/evo/providertx.h | 73 ++++++++++ src/evo/specialtx.cpp | 20 ++- src/evo/specialtx.h | 32 +++++ src/primitives/transaction.h | 6 + src/test/validation_tests.cpp | 13 +- 8 files changed, 381 insertions(+), 8 deletions(-) create mode 100644 src/evo/providertx.cpp create mode 100644 src/evo/providertx.h diff --git a/CMakeLists.txt b/CMakeLists.txt index ce7af246c25a..49837838eb1c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,6 +385,7 @@ set(COMMON_SOURCES ./src/key_io.cpp ./src/compressor.cpp ./src/evo/evodb.cpp + ./src/evo/providertx.cpp ./src/evo/specialtx.cpp ./src/consensus/merkle.cpp ./src/consensus/zerocoin_verify.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 7f77117f40e5..8b7bc3c5f95f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -183,6 +183,7 @@ BITCOIN_CORE_H = \ crypter.h \ cyclingvector.h \ evo/evodb.h \ + evo/providertx.h \ evo/specialtx.h \ pairresult.h \ addressbook.h \ @@ -326,6 +327,7 @@ libbitcoin_server_a_SOURCES = \ consensus/tx_verify.cpp \ consensus/zerocoin_verify.cpp \ evo/evodb.cpp \ + evo/providertx.cpp \ evo/specialtx.cpp \ httprpc.cpp \ httpserver.cpp \ diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp new file mode 100644 index 000000000000..27b3d6fe9a31 --- /dev/null +++ b/src/evo/providertx.cpp @@ -0,0 +1,242 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "evo/providertx.h" + +#include "base58.h" +#include "core_io.h" +#include "masternode.h" // MN_COLL_AMT +#include "messagesigner.h" +#include "evo/specialtx.h" +#include "tinyformat.h" +#include "utilstrencodings.h" +#include "validation.h" + +/* -- Helper static functions -- */ + +static bool CheckService(const CService& addr, CValidationState& state) +{ + if (!addr.IsValid()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); + } + if (!Params().IsRegTestNet() && !addr.IsRoutable()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); + } + + // IP port must be the default one on main-net, which cannot be used on other nets. + static int mainnetDefaultPort = CreateChainParams(CBaseChainParams::MAIN)->GetDefaultPort(); + if (Params().NetworkIDString() == CBaseChainParams::MAIN) { + if (addr.GetPort() != mainnetDefaultPort) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr-port"); + } + } else if (addr.GetPort() == mainnetDefaultPort) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr-port"); + } + + // !TODO: add support for IPv6 and Tor + if (!addr.IsIPv4()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-ipaddr"); + } + + return true; +} + +template +static bool CheckStringSig(const Payload& pl, const CKeyID& keyID, CValidationState& state) +{ + std::string strError; + if (!CMessageSigner::VerifyMessage(keyID, pl.vchSig, pl.MakeSignString(), strError)) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-sig", false, strError); + } + return true; +} + +template +static bool CheckInputsHash(const CTransaction& tx, const Payload& pl, CValidationState& state) +{ + uint256 inputsHash = CalcTxInputsHash(tx); + if (inputsHash != pl.inputsHash) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-inputs-hash"); + } + + return true; +} + +// Provider Register Payload + +static bool CheckCollateralOut(const CTxOut& out, const ProRegPL& pl, CValidationState& state, CTxDestination& collateralDestRet) +{ + if (!ExtractDestination(out.scriptPubKey, collateralDestRet)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-dest"); + } + // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server) + // this check applies to internal and external collateral, but internal collaterals are not necessarely a P2PKH + if (collateralDestRet == CTxDestination(pl.keyIDOwner) || collateralDestRet == CTxDestination(pl.keyIDVoting)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-reuse"); + } + // check collateral amount + if (out.nValue != MN_COLL_AMT) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral-amount"); + } + return true; +} + +bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state) +{ + assert(tx.nType == CTransaction::TxType::PROREG); + + ProRegPL pl; + if (!GetTxPayload(tx, pl)) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); + } + + if (pl.nVersion == 0 || pl.nVersion > ProRegPL::CURRENT_VERSION) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-version"); + } + if (pl.nType != 0) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-type"); + } + if (pl.nMode != 0) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-mode"); + } + + if (pl.keyIDOwner.IsNull() || pl.keyIDOperator.IsNull() || pl.keyIDVoting.IsNull()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-key-null"); + } + // we may support other kinds of scripts later, but restrict it for now + if (!pl.scriptPayout.IsPayToPublicKeyHash()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee"); + } + if (!pl.scriptOperatorPayout.empty() && !pl.scriptOperatorPayout.IsPayToPublicKeyHash()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-payee"); + } + + CTxDestination payoutDest; + if (!ExtractDestination(pl.scriptPayout, payoutDest)) { + // should not happen as we checked script types before + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-dest"); + } + // don't allow reuse of payout key for other keys (don't allow people to put the payee key onto an online server) + if (payoutDest == CTxDestination(pl.keyIDOwner) || + payoutDest == CTxDestination(pl.keyIDVoting) || + payoutDest == CTxDestination(pl.keyIDOperator)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-payee-reuse"); + } + + // It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be issues later + // If any of both is set, it must be valid however + if (pl.addr != CService() && !CheckService(pl.addr, state)) { + // pass the state returned by the function above + return false; + } + + if (pl.nOperatorReward > 10000) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-operator-reward"); + } + + if (pl.collateralOutpoint.hash.IsNull()) { + // collateral included in the proReg tx + if (pl.collateralOutpoint.n >= tx.vout.size()) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-index"); + } + CTxDestination collateralTxDest; + if (!CheckCollateralOut(tx.vout[pl.collateralOutpoint.n], pl, state, collateralTxDest)) { + // pass the state returned by the function above + return false; + } + // collateral is part of this ProRegTx, so we know the collateral is owned by the issuer + if (!pl.vchSig.empty()) { + return state.DoS(100, false, REJECT_INVALID, "bad-protx-sig"); + } + } else if (pindexPrev != nullptr) { + // Referenced external collateral. + // This is checked only when pindexPrev is not null (thus during ConnectBlock-->CheckSpecialTx), + // because this is a contextual check: we need the updated utxo set, to verify that + // the coin exists and it is unspent. + Coin coin; + if (!GetUTXOCoin(pl.collateralOutpoint, coin)) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral"); + } + CTxDestination collateralTxDest; + if (!CheckCollateralOut(coin.out, pl, state, collateralTxDest)) { + // pass the state returned by the function above + return false; + } + // Extract key from collateral. This only works for P2PK and P2PKH collaterals and will fail for P2SH. + // Issuer of this ProRegTx must prove ownership with this key by signing the ProRegTx + const CKeyID* keyForPayloadSig = boost::get(&collateralTxDest); + if (!keyForPayloadSig) { + return state.DoS(10, false, REJECT_INVALID, "bad-protx-collateral-pkh"); + } + // collateral is not part of this ProRegTx, so we must verify ownership of the collateral + if (!CheckStringSig(pl, *keyForPayloadSig, state)) { + // pass the state returned by the function above + return false; + } + } + + // !TODO: check for duplicate IP address or keys in the dmns manager + + if (!CheckInputsHash(tx, pl, state)) { + return false; + } + + return true; +} + +std::string ProRegPL::MakeSignString() const +{ + std::ostringstream ss; + CTxDestination destPayout; + std::string strPayout; + if (ExtractDestination(scriptPayout, destPayout)) { + strPayout = EncodeDestination(destPayout); + } else { + strPayout = HexStr(scriptPayout.begin(), scriptPayout.end()); + } + + ss << strPayout << "|"; + ss << strprintf("%d", nOperatorReward) << "|"; + ss << EncodeDestination(keyIDOwner) << "|"; + ss << EncodeDestination(keyIDVoting) << "|"; + + // ... and also the full hash of the payload as a protection agains malleability and replays + ss << ::SerializeHash(*this).ToString(); + + return ss.str(); +} + +std::string ProRegPL::ToString() const +{ + CTxDestination dest; + std::string payee = ExtractDestination(scriptPayout, dest) ? + EncodeDestination(dest) : "unknown"; + return strprintf("ProRegPL(nVersion=%d, collateralOutpoint=%s, addr=%s, nOperatorReward=%f, ownerAddress=%s, operatorAddress=%s, votingAddress=%s, scriptPayout=%s)", + nVersion, collateralOutpoint.ToStringShort(), addr.ToString(), (double)nOperatorReward / 100, EncodeDestination(keyIDOwner), EncodeDestination(keyIDOperator), EncodeDestination(keyIDVoting), payee); +} + +void ProRegPL::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + obj.pushKV("version", nVersion); + obj.pushKV("collateralHash", collateralOutpoint.hash.ToString()); + obj.pushKV("collateralIndex", (int)collateralOutpoint.n); + obj.pushKV("service", addr.ToString()); + obj.pushKV("ownerAddress", EncodeDestination(keyIDOwner)); + obj.pushKV("operatorAddress", EncodeDestination(keyIDOperator)); + obj.pushKV("votingAddress", EncodeDestination(keyIDVoting)); + + CTxDestination dest1; + if (ExtractDestination(scriptPayout, dest1)) { + obj.pushKV("payoutAddress", EncodeDestination(dest1)); + } + CTxDestination dest2; + if (ExtractDestination(scriptOperatorPayout, dest2)) { + obj.pushKV("operatorPayoutAddress", EncodeDestination(dest2)); + } + obj.pushKV("operatorReward", (double)nOperatorReward / 100); + obj.pushKV("inputsHash", inputsHash.ToString()); +} diff --git a/src/evo/providertx.h b/src/evo/providertx.h new file mode 100644 index 000000000000..a8640259992a --- /dev/null +++ b/src/evo/providertx.h @@ -0,0 +1,73 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX Core developers +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_PROVIDERTX_H +#define PIVX_PROVIDERTX_H + +#include "primitives/transaction.h" +#include "consensus/validation.h" + +#include "netaddress.h" +#include "pubkey.h" + +#include + +class CBlockIndex; + +// Provider-Register tx payload + +class ProRegPL +{ +public: + static const uint16_t CURRENT_VERSION = 1; + +public: + uint16_t nVersion{CURRENT_VERSION}; // message version + uint16_t nType{0}; // only 0 supported for now + uint16_t nMode{0}; // only 0 supported for now + COutPoint collateralOutpoint{UINT256_ZERO, (uint32_t)-1}; // if hash is null, we refer to a ProRegTx output + CService addr; + CKeyID keyIDOwner; + CKeyID keyIDOperator; + CKeyID keyIDVoting; + CScript scriptPayout; + uint16_t nOperatorReward{0}; + CScript scriptOperatorPayout; + uint256 inputsHash; // replay protection + std::vector vchSig; + +public: + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nVersion); + READWRITE(nType); + READWRITE(nMode); + READWRITE(collateralOutpoint); + READWRITE(addr); + READWRITE(keyIDOwner); + READWRITE(keyIDOperator); + READWRITE(keyIDVoting); + READWRITE(scriptPayout); + READWRITE(nOperatorReward); + READWRITE(scriptOperatorPayout); + READWRITE(inputsHash); + if (!(s.GetType() & SER_GETHASH)) { + READWRITE(vchSig); + } + } + + // When signing with the collateral key, we don't sign the hash but a generated message instead + // This is needed for HW wallet support which can only sign text messages as of now + std::string MakeSignString() const; + + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; + +bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValidationState& state); + +#endif //PIVX_PROVIDERTX_H diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 89ee973edeae..66f5147cd33e 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -9,6 +9,8 @@ #include "chainparams.h" #include "clientversion.h" #include "consensus/validation.h" +#include "evo/providertx.h" +#include "primitives/transaction.h" #include "primitives/block.h" // Basic non-contextual checks for all tx types @@ -66,7 +68,10 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state) // nothing to check return true; } - // !TODO + case CTransaction::TxType::PROREG: { + // provider-register + return CheckProRegTx(tx, nullptr, state); + } } return state.DoS(10, error("%s: special tx %s with invalid type %d", __func__, tx.GetHash().ToString(), tx.nType), @@ -95,7 +100,10 @@ bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CVali // contextual and non-contextual per-type checks switch (tx.nType) { - // !TODO + case CTransaction::TxType::PROREG: { + // provider-register + return CheckProRegTx(tx, pindexPrev, state); + } } return state.DoS(10, error("%s: special tx %s with invalid type %d", __func__, tx.GetHash().ToString(), tx.nType), @@ -121,3 +129,11 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) return true; } +uint256 CalcTxInputsHash(const CTransaction& tx) +{ + CHashWriter hw(CLIENT_VERSION, SER_GETHASH); + for (const auto& in : tx.vin) { + hw << in.prevout; + } + return hw.GetHash(); +} diff --git a/src/evo/specialtx.h b/src/evo/specialtx.h index 14c5123fcc8c..ba7260477c19 100644 --- a/src/evo/specialtx.h +++ b/src/evo/specialtx.h @@ -31,4 +31,36 @@ bool CheckSpecialTxNoContext(const CTransaction& tx, CValidationState& state); bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state, bool fJustCheck); bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex); +template +inline bool GetTxPayload(const std::vector& payload, T& obj) +{ + CDataStream ds(payload, SER_NETWORK, PROTOCOL_VERSION); + try { + ds >> obj; + } catch (std::exception& e) { + return false; + } + return ds.empty(); +} +template +inline bool GetTxPayload(const CMutableTransaction& tx, T& obj) +{ + return tx.hasExtraPayload() && GetTxPayload(*tx.extraPayload, obj); +} +template +inline bool GetTxPayload(const CTransaction& tx, T& obj) +{ + return tx.hasExtraPayload() && GetTxPayload(*tx.extraPayload, obj); +} + +template +void SetTxPayload(CMutableTransaction& tx, const T& payload) +{ + CDataStream ds(SER_NETWORK, PROTOCOL_VERSION); + ds << payload; + tx.extraPayload.emplace(ds.begin(), ds.end()); +} + +uint256 CalcTxInputsHash(const CTransaction& tx); + #endif // PIVX_SPECIALTX_H diff --git a/src/primitives/transaction.h b/src/primitives/transaction.h index 5b31c69d6ead..d467be23f462 100644 --- a/src/primitives/transaction.h +++ b/src/primitives/transaction.h @@ -272,6 +272,7 @@ class CTransaction /** Transaction types */ enum TxType: int16_t { NORMAL = 0, + PROREG = 1, }; static const int16_t CURRENT_VERSION = TxVersion::LEGACY; @@ -453,6 +454,11 @@ struct CMutableTransaction */ uint256 GetHash() const; + bool hasExtraPayload() const + { + return extraPayload != nullopt && !extraPayload->empty(); + } + // Ensure that special and sapling fields are signed SigVersion GetRequiredSigVersion() const { diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index 8ad91add541f..846ef6240dd3 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -18,36 +18,37 @@ BOOST_AUTO_TEST_CASE(special_tx_validation_test) CValidationState state; // v1 can only be Type=0 - mtx.nType = 1; + mtx.nType = CTransaction::TxType::PROREG; mtx.nVersion = CTransaction::TxVersion::LEGACY; BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK(state.GetRejectReason().find("not supported with version 0")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-version"); // version >= Sapling, type = 0, payload != null. mtx.nType = CTransaction::TxType::NORMAL; mtx.extraPayload = std::vector(10, 1); mtx.nVersion = CTransaction::TxVersion::SAPLING; BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK(state.GetRejectReason().find("doesn't support extra payload")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-payload"); // version >= Sapling, type = 0, payload == null --> pass mtx.extraPayload = nullopt; BOOST_CHECK(CheckSpecialTxNoContext(CTransaction(mtx), state)); // nVersion>=2 and nType!=0 without extrapayload - mtx.nType = 1; + mtx.nType = CTransaction::TxType::PROREG; BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); BOOST_CHECK(state.GetRejectReason().find("without extra payload")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-empty"); // Size limits mtx.extraPayload = std::vector(MAX_SPECIALTX_EXTRAPAYLOAD + 1, 1); BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK(state.GetRejectReason().find("Special tx payload oversize")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-oversize"); // Remove one element, so now it passes the size check mtx.extraPayload->pop_back(); BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK(state.GetRejectReason().find("with invalid type")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-payload"); } void test_simple_sapling_invalidity(CMutableTransaction& tx) From 0c064f8fab7424cfb7736485884d4799b0a40fca Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 26 Jan 2021 01:53:09 +0100 Subject: [PATCH 06/25] [Tests] Add tests for SetTxPayload/GetTxPayload and CheckStringSig --- src/Makefile.test.include | 1 + src/test/CMakeLists.txt | 1 + src/test/evo_specialtx_tests.cpp | 92 ++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 src/test/evo_specialtx_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index a4b3bd4e3643..c675fafd523d 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -62,6 +62,7 @@ BITCOIN_TESTS =\ test/crypto_tests.cpp \ test/cuckoocache_tests.cpp \ test/DoS_tests.cpp \ + test/evo_specialtx_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ test/key_tests.cpp \ diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 10d6cdaddc0d..6775c06d76c0 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -68,6 +68,7 @@ set(BITCOIN_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/arith_uint256_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/addrman_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/allocator_tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/evo_specialtx_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/librust/libsapling_utils_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/librust/sapling_key_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/librust/pedersen_hash_tests.cpp diff --git a/src/test/evo_specialtx_tests.cpp b/src/test/evo_specialtx_tests.cpp new file mode 100644 index 000000000000..934483d9de47 --- /dev/null +++ b/src/test/evo_specialtx_tests.cpp @@ -0,0 +1,92 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "test/test_pivx.h" +#include "primitives/transaction.h" +#include "evo/providertx.h" +#include "evo/specialtx.h" +#include "messagesigner.h" +#include "netbase.h" + +#include + +BOOST_FIXTURE_TEST_SUITE(evo_specialtx_tests, TestingSetup) + +static void RandomScript(CScript &script) +{ + static const opcodetype oplist[] = {OP_FALSE, OP_1, OP_2, OP_3, OP_CHECKSIG, OP_IF, OP_VERIF, OP_RETURN, OP_CODESEPARATOR}; + script = CScript(); + int ops = (InsecureRandRange(10)); + for (int i=0; i Date: Tue, 26 Feb 2019 14:13:19 +0100 Subject: [PATCH 07/25] Implement std::unordered_map/set compatible hasher classes for salted hashes >>> cherry-pick dash@b5462f52460e7c4f982dec8396e46b5928c638a3 Allows convenient salted hashing with unordered maps and sets. Useful when there is a risk of unbalanced hash buckets slowing things down, e.g. when externally supplied hashes are used as keys into a map. --- CMakeLists.txt | 1 + src/Makefile.am | 2 ++ src/saltedhasher.cpp | 12 +++++++ src/saltedhasher.h | 75 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+) create mode 100644 src/saltedhasher.cpp create mode 100644 src/saltedhasher.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 49837838eb1c..fcbcfce7f612 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -403,6 +403,7 @@ set(COMMON_SOURCES ./src/policy/feerate.cpp ./src/protocol.cpp ./src/pubkey.cpp + ./src/saltedhasher.cpp ./src/scheduler.cpp ./src/script/interpreter.cpp ./src/script/script.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 8b7bc3c5f95f..6bbee362148d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -248,6 +248,7 @@ BITCOIN_CORE_H = \ rpc/protocol.h \ rpc/register.h \ rpc/server.h \ + saltedhasher.h \ scheduler.h \ script/interpreter.h \ script/keyorigin.h \ @@ -514,6 +515,7 @@ libbitcoin_common_a_SOURCES = \ policy/feerate.cpp \ protocol.cpp \ pubkey.cpp \ + saltedhasher.cpp \ scheduler.cpp \ script/interpreter.cpp \ script/script.cpp \ diff --git a/src/saltedhasher.cpp b/src/saltedhasher.cpp new file mode 100644 index 000000000000..ec3c819f2c21 --- /dev/null +++ b/src/saltedhasher.cpp @@ -0,0 +1,12 @@ +// Copyright (c) 2019 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "saltedhasher.h" +#include "random.h" + +#include + +SaltedHasherBase::SaltedHasherBase() : k0(GetRand(std::numeric_limits::max())), k1(GetRand(std::numeric_limits::max())) {} + +SaltedHasherBase StaticSaltedHasher::s; diff --git a/src/saltedhasher.h b/src/saltedhasher.h new file mode 100644 index 000000000000..ffd5cd6388f9 --- /dev/null +++ b/src/saltedhasher.h @@ -0,0 +1,75 @@ +// Copyright (c) 2019 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef SALTEDHASHER_H +#define SALTEDHASHER_H + +#include "hash.h" +#include "uint256.h" + +/** Helper classes for std::unordered_map and std::unordered_set hashing */ + +template struct SaltedHasherImpl; + +template +struct SaltedHasherImpl> +{ + static std::size_t CalcHash(const std::pair& v, uint64_t k0, uint64_t k1) + { + return SipHashUint256Extra(k0, k1, v.first, (uint32_t) v.second); + } +}; + +template +struct SaltedHasherImpl> +{ + static std::size_t CalcHash(const std::pair& v, uint64_t k0, uint64_t k1) + { + return SipHashUint256Extra(k0, k1, v.second, (uint32_t) v.first); + } +}; + +template<> +struct SaltedHasherImpl +{ + static std::size_t CalcHash(const uint256& v, uint64_t k0, uint64_t k1) + { + return SipHashUint256(k0, k1, v); + } +}; + +struct SaltedHasherBase +{ + /** Salt */ + const uint64_t k0, k1; + + SaltedHasherBase(); +}; + +/* Allows each instance of unordered maps/sest to have their own salt */ +template +struct SaltedHasher +{ + S s; + std::size_t operator()(const T& v) const + { + return SaltedHasherImpl::CalcHash(v, s.k0, s.k1); + } +}; + +/* Allows to use a static salt for all instances. The salt is a random value set at startup + * (through static initialization) + */ +struct StaticSaltedHasher +{ + static SaltedHasherBase s; + + template + std::size_t operator()(const T& v) const + { + return SaltedHasherImpl::CalcHash(v, s.k0, s.k1); + } +}; + +#endif//SALTEDHASHER_H From ae3397215be2026769a18eb0715bd599a776872a Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 26 Jan 2021 14:42:55 +0100 Subject: [PATCH 08/25] [Core] Deterministic Masternode List implementation --- CMakeLists.txt | 1 + src/Makefile.am | 2 + src/evo/deterministicmns.cpp | 833 +++++++++++++++++++++++++++++++++++ src/evo/deterministicmns.h | 598 +++++++++++++++++++++++++ src/init.cpp | 4 + src/test/test_pivx.cpp | 3 + src/validation.cpp | 1 + 7 files changed, 1442 insertions(+) create mode 100644 src/evo/deterministicmns.cpp create mode 100644 src/evo/deterministicmns.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fcbcfce7f612..d892edf7b181 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -384,6 +384,7 @@ set(COMMON_SOURCES ./src/coins.cpp ./src/key_io.cpp ./src/compressor.cpp + ./src/evo/deterministicmns.cpp ./src/evo/evodb.cpp ./src/evo/providertx.cpp ./src/evo/specialtx.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 6bbee362148d..94ae65df81f1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -182,6 +182,7 @@ BITCOIN_CORE_H = \ cuckoocache.h \ crypter.h \ cyclingvector.h \ + evo/deterministicmns.h \ evo/evodb.h \ evo/providertx.h \ evo/specialtx.h \ @@ -327,6 +328,7 @@ libbitcoin_server_a_SOURCES = \ consensus/params.cpp \ consensus/tx_verify.cpp \ consensus/zerocoin_verify.cpp \ + evo/deterministicmns.cpp \ evo/evodb.cpp \ evo/providertx.cpp \ evo/specialtx.cpp \ diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp new file mode 100644 index 000000000000..300a5f236d3e --- /dev/null +++ b/src/evo/deterministicmns.cpp @@ -0,0 +1,833 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "evo/deterministicmns.h" + +#include "base58.h" +#include "chainparams.h" +#include "core_io.h" +#include "evo/specialtx.h" +#include "masternode.h" // for MN_COLL_AMT, MasternodeCollateralMinConf +#include "script/standard.h" +#include "sync.h" +#include "validation.h" +#include "validationinterface.h" + +#include + +static const std::string DB_LIST_SNAPSHOT = "dmn_S"; +static const std::string DB_LIST_DIFF = "dmn_D"; + +std::unique_ptr deterministicMNManager; + +std::string CDeterministicMNState::ToString() const +{ + CTxDestination dest; + std::string payoutAddress = "unknown"; + std::string operatorPayoutAddress = "none"; + if (ExtractDestination(scriptPayout, dest)) { + payoutAddress = EncodeDestination(dest); + } + if (ExtractDestination(scriptOperatorPayout, dest)) { + operatorPayoutAddress = EncodeDestination(dest); + } + + return strprintf("CDeterministicMNState(nRegisteredHeight=%d, nLastPaidHeight=%d, nPoSePenalty=%d, nPoSeRevivedHeight=%d, nPoSeBanHeight=%d, nRevocationReason=%d, ownerAddress=%s, operatorAddress=%s, votingAddress=%s, addr=%s, payoutAddress=%s, operatorPayoutAddress=%s)", + nRegisteredHeight, nLastPaidHeight, nPoSePenalty, nPoSeRevivedHeight, nPoSeBanHeight, nRevocationReason, + EncodeDestination(keyIDOwner), EncodeDestination(keyIDOperator), EncodeDestination(keyIDVoting), addr.ToStringIPPort(), payoutAddress, operatorPayoutAddress); +} + +void CDeterministicMNState::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + obj.pushKV("service", addr.ToStringIPPort()); + obj.pushKV("registeredHeight", nRegisteredHeight); + obj.pushKV("lastPaidHeight", nLastPaidHeight); + obj.pushKV("PoSePenalty", nPoSePenalty); + obj.pushKV("PoSeRevivedHeight", nPoSeRevivedHeight); + obj.pushKV("PoSeBanHeight", nPoSeBanHeight); + obj.pushKV("revocationReason", nRevocationReason); + obj.pushKV("ownerAddress", EncodeDestination(keyIDOwner)); + obj.pushKV("operatorAddress", EncodeDestination(keyIDOperator)); + obj.pushKV("votingAddress", EncodeDestination(keyIDVoting)); + + CTxDestination dest1; + if (ExtractDestination(scriptPayout, dest1)) { + obj.pushKV("payoutAddress", EncodeDestination(dest1)); + } + CTxDestination dest2; + if (ExtractDestination(scriptOperatorPayout, dest2)) { + obj.pushKV("operatorPayoutAddress", EncodeDestination(dest2)); + } +} + +uint64_t CDeterministicMN::GetInternalId() const +{ + // can't get it if it wasn't set yet + assert(internalId != std::numeric_limits::max()); + return internalId; +} + +std::string CDeterministicMN::ToString() const +{ + return strprintf("CDeterministicMN(proTxHash=%s, collateralOutpoint=%s, nOperatorReward=%f, state=%s", proTxHash.ToString(), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, pdmnState->ToString()); +} + +void CDeterministicMN::ToJson(UniValue& obj) const +{ + obj.clear(); + obj.setObject(); + + UniValue stateObj; + pdmnState->ToJson(stateObj); + + obj.pushKV("proTxHash", proTxHash.ToString()); + obj.pushKV("collateralHash", collateralOutpoint.hash.ToString()); + obj.pushKV("collateralIndex", (int)collateralOutpoint.n); + + Coin coin; + if (GetUTXOCoin(collateralOutpoint, coin)) { + CTxDestination dest; + if (ExtractDestination(coin.out.scriptPubKey, dest)) { + obj.pushKV("collateralAddress", EncodeDestination(dest)); + } + } + + obj.pushKV("operatorReward", (double)nOperatorReward / 100); + obj.pushKV("dmnstate", stateObj); +} + +bool CDeterministicMNList::IsMNValid(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return false; + } + return IsMNValid(*p); +} + +bool CDeterministicMNList::IsMNPoSeBanned(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return false; + } + return IsMNPoSeBanned(*p); +} + +bool CDeterministicMNList::IsMNValid(const CDeterministicMNCPtr& dmn) const +{ + return !IsMNPoSeBanned(dmn); +} + +bool CDeterministicMNList::IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const +{ + assert(dmn); + const CDeterministicMNState& state = *dmn->pdmnState; + return state.nPoSeBanHeight != -1; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMN(const uint256& proTxHash) const +{ + auto p = mnMap.find(proTxHash); + if (p == nullptr) { + return nullptr; + } + return *p; +} + +CDeterministicMNCPtr CDeterministicMNList::GetValidMN(const uint256& proTxHash) const +{ + auto dmn = GetMN(proTxHash); + if (dmn && !IsMNValid(dmn)) { + return nullptr; + } + return dmn; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CKeyID& keyID) +{ + for (const auto& p : mnMap) { + if (p.second->pdmnState->keyIDOperator == keyID) { + return p.second; + } + } + return nullptr; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNByCollateral(const COutPoint& collateralOutpoint) const +{ + return GetUniquePropertyMN(collateralOutpoint); +} + +CDeterministicMNCPtr CDeterministicMNList::GetValidMNByCollateral(const COutPoint& collateralOutpoint) const +{ + auto dmn = GetMNByCollateral(collateralOutpoint); + if (dmn && !IsMNValid(dmn)) { + return nullptr; + } + return dmn; +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNByService(const CService& service) const +{ + return GetUniquePropertyMN(service); +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNByInternalId(uint64_t internalId) const +{ + auto proTxHash = mnInternalIdMap.find(internalId); + if (!proTxHash) { + return nullptr; + } + return GetMN(*proTxHash); +} + +static int CompareByLastPaid_GetHeight(const CDeterministicMN& dmn) +{ + int height = dmn.pdmnState->nLastPaidHeight; + if (dmn.pdmnState->nPoSeRevivedHeight != -1 && dmn.pdmnState->nPoSeRevivedHeight > height) { + height = dmn.pdmnState->nPoSeRevivedHeight; + } else if (height == 0) { + height = dmn.pdmnState->nRegisteredHeight; + } + return height; +} + +static bool CompareByLastPaid(const CDeterministicMN& _a, const CDeterministicMN& _b) +{ + int ah = CompareByLastPaid_GetHeight(_a); + int bh = CompareByLastPaid_GetHeight(_b); + if (ah == bh) { + return _a.proTxHash < _b.proTxHash; + } else { + return ah < bh; + } +} +static bool CompareByLastPaid(const CDeterministicMNCPtr& _a, const CDeterministicMNCPtr& _b) +{ + return CompareByLastPaid(*_a, *_b); +} + +CDeterministicMNCPtr CDeterministicMNList::GetMNPayee() const +{ + if (mnMap.size() == 0) { + return nullptr; + } + + CDeterministicMNCPtr best; + ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + if (!best || CompareByLastPaid(dmn, best)) { + best = dmn; + } + }); + + return best; +} + +std::vector CDeterministicMNList::GetProjectedMNPayees(unsigned int nCount) const +{ + if (nCount > GetValidMNsCount()) { + nCount = GetValidMNsCount(); + } + + std::vector result; + result.reserve(nCount); + + ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + result.emplace_back(dmn); + }); + std::sort(result.begin(), result.end(), [&](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) { + return CompareByLastPaid(a, b); + }); + + result.resize(nCount); + + return result; +} + +std::vector CDeterministicMNList::CalculateQuorum(size_t maxSize, const uint256& modifier) const +{ + auto scores = CalculateScores(modifier); + + // sort is descending order + std::sort(scores.rbegin(), scores.rend(), [](const std::pair& a, std::pair& b) { + if (a.first == b.first) { + // this should actually never happen, but we should stay compatible with how the non deterministic MNs did the sorting + return a.second->collateralOutpoint < b.second->collateralOutpoint; + } + return a.first < b.first; + }); + + // take top maxSize entries and return it + std::vector result; + result.resize(std::min(maxSize, scores.size())); + for (size_t i = 0; i < result.size(); i++) { + result[i] = std::move(scores[i].second); + } + return result; +} + +std::vector> CDeterministicMNList::CalculateScores(const uint256& modifier) const +{ + std::vector> scores; + scores.reserve(GetAllMNsCount()); + ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + if (dmn->pdmnState->confirmedHash.IsNull()) { + // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a + // future quorums + return; + } + // calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN + // Please note that this is not a double-sha256 but a single-sha256 + // The first part is already precalculated (confirmedHashWithProRegTxHash) + // TODO When https://github.com/bitcoin/bitcoin/pull/13191 gets backported, implement something that is similar but for single-sha256 + uint256 h; + CSHA256 sha256; + sha256.Write(dmn->pdmnState->confirmedHashWithProRegTxHash.begin(), dmn->pdmnState->confirmedHashWithProRegTxHash.size()); + sha256.Write(modifier.begin(), modifier.size()); + sha256.Finalize(h.begin()); + + scores.emplace_back(UintToArith256(h), dmn); + }); + + return scores; +} + +int CDeterministicMNList::CalcMaxPoSePenalty() const +{ + // Maximum PoSe penalty is dynamic and equals the number of registered MNs + // It's however at least 100. + // This means that the max penalty is usually equal to a full payment cycle + return std::max(100, (int)GetAllMNsCount()); +} + +int CDeterministicMNList::CalcPenalty(int percent) const +{ + assert(percent > 0); + return (CalcMaxPoSePenalty() * percent) / 100; +} + +void CDeterministicMNList::PoSePunish(const uint256& proTxHash, int penalty, bool debugLogs) +{ + assert(penalty > 0); + + auto dmn = GetMN(proTxHash); + if (!dmn) { + throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString()))); + } + + int maxPenalty = CalcMaxPoSePenalty(); + + auto newState = std::make_shared(*dmn->pdmnState); + newState->nPoSePenalty += penalty; + newState->nPoSePenalty = std::min(maxPenalty, newState->nPoSePenalty); + + if (debugLogs) { + LogPrintf("CDeterministicMNList::%s -- punished MN %s, penalty %d->%d (max=%d)\n", + __func__, proTxHash.ToString(), dmn->pdmnState->nPoSePenalty, newState->nPoSePenalty, maxPenalty); + } + + if (newState->nPoSePenalty >= maxPenalty && newState->nPoSeBanHeight == -1) { + newState->nPoSeBanHeight = nHeight; + if (debugLogs) { + LogPrintf("CDeterministicMNList::%s -- banned MN %s at height %d\n", + __func__, proTxHash.ToString(), nHeight); + } + } + UpdateMN(proTxHash, newState); +} + +void CDeterministicMNList::PoSeDecrease(const uint256& proTxHash) +{ + auto dmn = GetMN(proTxHash); + if (!dmn) { + throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString()))); + } + assert(dmn->pdmnState->nPoSePenalty > 0 && dmn->pdmnState->nPoSeBanHeight == -1); + + auto newState = std::make_shared(*dmn->pdmnState); + newState->nPoSePenalty--; + UpdateMN(proTxHash, newState); +} + +CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNList& to) const +{ + CDeterministicMNListDiff diffRet; + + to.ForEachMN(false, [&](const CDeterministicMNCPtr& toPtr) { + auto fromPtr = GetMN(toPtr->proTxHash); + if (fromPtr == nullptr) { + diffRet.addedMNs.emplace_back(toPtr); + } else if (fromPtr != toPtr || fromPtr->pdmnState != toPtr->pdmnState) { + CDeterministicMNStateDiff stateDiff(*fromPtr->pdmnState, *toPtr->pdmnState); + if (stateDiff.fields) { + diffRet.updatedMNs.emplace(toPtr->GetInternalId(), std::move(stateDiff)); + } + } + }); + ForEachMN(false, [&](const CDeterministicMNCPtr& fromPtr) { + auto toPtr = to.GetMN(fromPtr->proTxHash); + if (toPtr == nullptr) { + diffRet.removedMns.emplace(fromPtr->GetInternalId()); + } + }); + + // added MNs need to be sorted by internalId so that these are added in correct order when the diff is applied later + // otherwise internalIds will not match with the original list + std::sort(diffRet.addedMNs.begin(), diffRet.addedMNs.end(), [](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) { + return a->GetInternalId() < b->GetInternalId(); + }); + + return diffRet; +} + +CDeterministicMNList CDeterministicMNList::ApplyDiff(const CBlockIndex* pindex, const CDeterministicMNListDiff& diff) const +{ + CDeterministicMNList result = *this; + result.blockHash = pindex->GetBlockHash(); + result.nHeight = pindex->nHeight; + + for (const auto& id : diff.removedMns) { + auto dmn = result.GetMNByInternalId(id); + if (!dmn) { + throw(std::runtime_error(strprintf("%s: can't find a removed masternode, id=%d", __func__, id))); + } + result.RemoveMN(dmn->proTxHash); + } + for (const auto& dmn : diff.addedMNs) { + result.AddMN(dmn); + } + for (const auto& p : diff.updatedMNs) { + auto dmn = result.GetMNByInternalId(p.first); + result.UpdateMN(dmn, p.second); + } + + return result; +} + +void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount) +{ + assert(dmn != nullptr); + + if (mnMap.find(dmn->proTxHash)) { + throw(std::runtime_error(strprintf("%s: can't add a duplicate masternode with the same proTxHash=%s", __func__, dmn->proTxHash.ToString()))); + } + if (mnInternalIdMap.find(dmn->GetInternalId())) { + throw(std::runtime_error(strprintf("%s: can't add a duplicate masternode with the same internalId=%d", __func__, dmn->GetInternalId()))); + } + if (HasUniqueProperty(dmn->pdmnState->addr)) { + throw(std::runtime_error(strprintf("%s: can't add a masternode with a duplicate address %s", __func__, dmn->pdmnState->addr.ToStringIPPort()))); + } + if (HasUniqueProperty(dmn->pdmnState->keyIDOwner) || HasUniqueProperty(dmn->pdmnState->keyIDOperator)) { + throw(std::runtime_error(strprintf("%s: can't add a masternode with a duplicate key (%s or %s)", __func__, EncodeDestination(dmn->pdmnState->keyIDOwner), EncodeDestination(dmn->pdmnState->keyIDOperator)))); + } + + mnMap = mnMap.set(dmn->proTxHash, dmn); + mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash); + AddUniqueProperty(dmn, dmn->collateralOutpoint); + if (dmn->pdmnState->addr != CService()) { + AddUniqueProperty(dmn, dmn->pdmnState->addr); + } + AddUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); + AddUniqueProperty(dmn, dmn->pdmnState->keyIDOperator); + + if (fBumpTotalCount) { + // nTotalRegisteredCount acts more like a checkpoint, not as a limit, + nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount); + } +} + +void CDeterministicMNList::UpdateMN(const CDeterministicMNCPtr& oldDmn, const CDeterministicMNStateCPtr& pdmnState) +{ + assert(oldDmn != nullptr); + + if (HasUniqueProperty(oldDmn->pdmnState->addr) && GetUniquePropertyMN(oldDmn->pdmnState->addr)->proTxHash != oldDmn->proTxHash) { + throw(std::runtime_error(strprintf("%s: can't update a masternode with a duplicate address %s", __func__, oldDmn->pdmnState->addr.ToStringIPPort()))); + } + + auto dmn = std::make_shared(*oldDmn); + auto oldState = dmn->pdmnState; + dmn->pdmnState = pdmnState; + mnMap = mnMap.set(oldDmn->proTxHash, dmn); + + UpdateUniqueProperty(dmn, oldState->addr, pdmnState->addr); + UpdateUniqueProperty(dmn, oldState->keyIDOwner, pdmnState->keyIDOwner); + UpdateUniqueProperty(dmn, oldState->keyIDOperator, pdmnState->keyIDOperator); +} + +void CDeterministicMNList::UpdateMN(const uint256& proTxHash, const CDeterministicMNStateCPtr& pdmnState) +{ + auto oldDmn = mnMap.find(proTxHash); + if (!oldDmn) { + throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString()))); + } + UpdateMN(*oldDmn, pdmnState); +} + +void CDeterministicMNList::UpdateMN(const CDeterministicMNCPtr& oldDmn, const CDeterministicMNStateDiff& stateDiff) +{ + assert(oldDmn != nullptr); + auto oldState = oldDmn->pdmnState; + auto newState = std::make_shared(*oldState); + stateDiff.ApplyToState(*newState); + UpdateMN(oldDmn, newState); +} + +void CDeterministicMNList::RemoveMN(const uint256& proTxHash) +{ + auto dmn = GetMN(proTxHash); + if (!dmn) { + throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString()))); + } + DeleteUniqueProperty(dmn, dmn->collateralOutpoint); + if (dmn->pdmnState->addr != CService()) { + DeleteUniqueProperty(dmn, dmn->pdmnState->addr); + } + DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOwner); + DeleteUniqueProperty(dmn, dmn->pdmnState->keyIDOperator); + + mnMap = mnMap.erase(proTxHash); + mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId()); +} + +CDeterministicMNManager::CDeterministicMNManager(CEvoDB& _evoDb) : + evoDb(_evoDb) +{ +} + +bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& _state, bool fJustCheck) +{ + int nHeight = pindex->nHeight; + // !TODO: exit early if enforcement not active + + CDeterministicMNList oldList, newList; + CDeterministicMNListDiff diff; + + try { + LOCK(cs); + + if (!BuildNewListFromBlock(block, pindex->pprev, _state, newList, true)) { + // pass the state returned by the function above + return false; + } + + if (fJustCheck) { + return true; + } + + if (newList.GetHeight() == -1) { + newList.SetHeight(nHeight); + } + + newList.SetBlockHash(block.GetHash()); + + oldList = GetListForBlock(pindex->pprev); + diff = oldList.BuildDiff(newList); + + evoDb.Write(std::make_pair(DB_LIST_DIFF, newList.GetBlockHash()), diff); + if ((nHeight % DISK_SNAPSHOT_PERIOD) == 0 || oldList.GetHeight() == -1) { + evoDb.Write(std::make_pair(DB_LIST_SNAPSHOT, newList.GetBlockHash()), newList); + mnListsCache.emplace(newList.GetBlockHash(), newList); + LogPrintf("CDeterministicMNManager::%s -- Wrote snapshot. nHeight=%d, mapCurMNs.allMNsCount=%d\n", + __func__, nHeight, newList.GetAllMNsCount()); + } + + diff.nHeight = pindex->nHeight; + mnListDiffsCache.emplace(pindex->GetBlockHash(), diff); + } catch (const std::exception& e) { + LogPrintf("CDeterministicMNManager::%s -- internal error: %s\n", __func__, e.what()); + return _state.DoS(100, false, REJECT_INVALID, "failed-dmn-block"); + } + + // !TODO: notify listeners that the mn list has changed + + LOCK(cs); + CleanupCache(nHeight); + + return true; +} + +bool CDeterministicMNManager::UndoBlock(const CBlock& block, const CBlockIndex* pindex) +{ + // !TODO: exit early if enforcement not active + uint256 blockHash = block.GetHash(); + + CDeterministicMNList curList; + CDeterministicMNList prevList; + CDeterministicMNListDiff diff; + { + LOCK(cs); + evoDb.Read(std::make_pair(DB_LIST_DIFF, blockHash), diff); + + if (diff.HasChanges()) { + // need to call this before erasing + curList = GetListForBlock(pindex); + prevList = GetListForBlock(pindex->pprev); + } + + mnListsCache.erase(blockHash); + mnListDiffsCache.erase(blockHash); + } + + if (diff.HasChanges()) { + auto inversedDiff = curList.BuildDiff(prevList); + // !TODO: notify listeners that the masternode list has changed + } + + return true; +} + +void CDeterministicMNManager::UpdatedBlockTip(const CBlockIndex* pindex) +{ + LOCK(cs); + tipIndex = pindex; +} + +bool CDeterministicMNManager::BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& _state, CDeterministicMNList& mnListRet, bool debugLogs) +{ + AssertLockHeld(cs); + + int nHeight = pindexPrev->nHeight + 1; + + CDeterministicMNList oldList = GetListForBlock(pindexPrev); + CDeterministicMNList newList = oldList; + newList.SetBlockHash(UINT256_ZERO); // we can't know the final block hash, so better not return a (invalid) block hash + newList.SetHeight(nHeight); + + auto payee = oldList.GetMNPayee(); + + // we iterate the oldList here and update the newList + // this is only valid as long these have not diverged at this point, which is the case as long as we don't add + // code above this loop that modifies newList + oldList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { + if (!dmn->pdmnState->confirmedHash.IsNull()) { + // already confirmed + return; + } + // this works on the previous block, so confirmation will happen one block after nMasternodeMinimumConfirmations + // has been reached, but the block hash will then point to the block at nMasternodeMinimumConfirmations + int nConfirmations = pindexPrev->nHeight - dmn->pdmnState->nRegisteredHeight; + if (nConfirmations >= MasternodeCollateralMinConf()) { + auto newState = std::make_shared(*dmn->pdmnState); + newState->UpdateConfirmedHash(dmn->proTxHash, pindexPrev->GetBlockHash()); + newList.UpdateMN(dmn->proTxHash, newState); + } + }); + + DecreasePoSePenalties(newList); + + // we skip the coinbase + for (int i = 1; i < (int)block.vtx.size(); i++) { + const CTransaction& tx = *block.vtx[i]; + + if (tx.nType == CTransaction::TxType::PROREG) { + ProRegPL pl; + if (!GetTxPayload(tx, pl)) { + return _state.DoS(100, false, REJECT_INVALID, "bad-protx-payload"); + } + + auto dmn = std::make_shared(newList.GetTotalRegisteredCount()); + dmn->proTxHash = tx.GetHash(); + + // collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself + dmn->collateralOutpoint = pl.collateralOutpoint.hash.IsNull() ? COutPoint(tx.GetHash(), pl.collateralOutpoint.n) + : pl.collateralOutpoint; + + Coin coin; + if (!pl.collateralOutpoint.hash.IsNull() && (!GetUTXOCoin(pl.collateralOutpoint, coin) || coin.out.nValue != MN_COLL_AMT)) { + // should actually never get to this point as CheckProRegTx should have handled this case. + // We do this additional check nevertheless to be 100% sure + return _state.DoS(100, false, REJECT_INVALID, "bad-protx-collateral"); + } + + auto replacedDmn = newList.GetMNByCollateral(dmn->collateralOutpoint); + if (replacedDmn != nullptr) { + // This might only happen with a ProRegTx that refers an external collateral + // In that case the new ProRegTx will replace the old one. This means the old one is removed + // and the new one is added like a completely fresh one, which is also at the bottom of the payment list + newList.RemoveMN(replacedDmn->proTxHash); + if (debugLogs) { + LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was used for a new ProRegTx. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", + __func__, replacedDmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, newList.GetAllMNsCount()); + } + } + + if (newList.HasUniqueProperty(pl.addr)) { + return _state.DoS(100, false, REJECT_DUPLICATE, "bad-protx-dup-IP-address"); + } + if (newList.HasUniqueProperty(pl.keyIDOwner)) { + return _state.DoS(100, false, REJECT_DUPLICATE, "bad-protx-dup-owner-key"); + } + if (newList.HasUniqueProperty(pl.keyIDOperator)) { + return _state.DoS(100, false, REJECT_DUPLICATE, "bad-protx-dup-operator-key"); + } + + dmn->nOperatorReward = pl.nOperatorReward; + + auto dmnState = std::make_shared(pl); + dmnState->nRegisteredHeight = nHeight; + if (pl.addr == CService()) { + // start in banned pdmnState as we need to wait for a ProUpServTx + dmnState->nPoSeBanHeight = nHeight; + } + dmn->pdmnState = dmnState; + + newList.AddMN(dmn); + + if (debugLogs) { + LogPrintf("CDeterministicMNManager::%s -- MN %s added at height %d: %s\n", + __func__, tx.GetHash().ToString(), nHeight, pl.ToString()); + } + } + + // !TODO: manage other special txes + } + + // check if any existing MN collateral is spent by this transaction + // we skip the coinbase + for (int i = 1; i < (int)block.vtx.size(); i++) { + const CTransaction& tx = *block.vtx[i]; + for (const auto& in : tx.vin) { + auto dmn = newList.GetMNByCollateral(in.prevout); + if (dmn && dmn->collateralOutpoint == in.prevout) { + newList.RemoveMN(dmn->proTxHash); + if (debugLogs) { + LogPrintf("CDeterministicMNManager::%s -- MN %s removed from list because collateral was spent. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n", + __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight, newList.GetAllMNsCount()); + } + } + } + } + + // The payee for the current block was determined by the previous block's list but it might have disappeared in the + // current block. We still pay that MN one last time however. + if (payee && newList.HasMN(payee->proTxHash)) { + auto newState = std::make_shared(*newList.GetMN(payee->proTxHash)->pdmnState); + newState->nLastPaidHeight = nHeight; + newList.UpdateMN(payee->proTxHash, newState); + } + + mnListRet = std::move(newList); + + return true; +} + +void CDeterministicMNManager::DecreasePoSePenalties(CDeterministicMNList& mnList) +{ + std::vector toDecrease; + toDecrease.reserve(mnList.GetValidMNsCount() / 10); + // only iterate and decrease for valid ones (not PoSe banned yet) + // if a MN ever reaches the maximum, it stays in PoSe banned state until revived + mnList.ForEachMN(true, [&](const CDeterministicMNCPtr& dmn) { + if (dmn->pdmnState->nPoSePenalty > 0 && dmn->pdmnState->nPoSeBanHeight == -1) { + toDecrease.emplace_back(dmn->proTxHash); + } + }); + + for (const auto& proTxHash : toDecrease) { + mnList.PoSeDecrease(proTxHash); + } +} + +CDeterministicMNList CDeterministicMNManager::GetListForBlock(const CBlockIndex* pindex) +{ + LOCK(cs); + + CDeterministicMNList snapshot; + std::list listDiffIndexes; + + while (true) { + // try using cache before reading from disk + auto itLists = mnListsCache.find(pindex->GetBlockHash()); + if (itLists != mnListsCache.end()) { + snapshot = itLists->second; + break; + } + + if (evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, pindex->GetBlockHash()), snapshot)) { + mnListsCache.emplace(pindex->GetBlockHash(), snapshot); + break; + } + + // no snapshot found yet, check diffs + auto itDiffs = mnListDiffsCache.find(pindex->GetBlockHash()); + if (itDiffs != mnListDiffsCache.end()) { + listDiffIndexes.emplace_front(pindex); + pindex = pindex->pprev; + continue; + } + + CDeterministicMNListDiff diff; + if (!evoDb.Read(std::make_pair(DB_LIST_DIFF, pindex->GetBlockHash()), diff)) { + // no snapshot and no diff on disk means that it's the initial snapshot + snapshot = CDeterministicMNList(pindex->GetBlockHash(), -1, 0); + mnListsCache.emplace(pindex->GetBlockHash(), snapshot); + break; + } + + diff.nHeight = pindex->nHeight; + mnListDiffsCache.emplace(pindex->GetBlockHash(), std::move(diff)); + listDiffIndexes.emplace_front(pindex); + pindex = pindex->pprev; + } + + for (const auto& diffIndex : listDiffIndexes) { + const auto& diff = mnListDiffsCache.at(diffIndex->GetBlockHash()); + if (diff.HasChanges()) { + snapshot = snapshot.ApplyDiff(diffIndex, diff); + } else { + snapshot.SetBlockHash(diffIndex->GetBlockHash()); + snapshot.SetHeight(diffIndex->nHeight); + } + } + + if (tipIndex) { + // always keep a snapshot for the tip + if (snapshot.GetBlockHash() == tipIndex->GetBlockHash()) { + mnListsCache.emplace(snapshot.GetBlockHash(), snapshot); + } else { + // !TODO: keep snapshots for yet alive quorums + } + } + + return snapshot; +} + +CDeterministicMNList CDeterministicMNManager::GetListAtChainTip() +{ + LOCK(cs); + if (!tipIndex) { + return {}; + } + return GetListForBlock(tipIndex); +} + +void CDeterministicMNManager::CleanupCache(int nHeight) +{ + AssertLockHeld(cs); + + std::vector toDeleteLists; + std::vector toDeleteDiffs; + for (const auto& p : mnListsCache) { + if (p.second.GetHeight() + LIST_DIFFS_CACHE_SIZE < nHeight) { + toDeleteLists.emplace_back(p.first); + continue; + } + // !TODO: llmq cache cleanup + } + for (const auto& h : toDeleteLists) { + mnListsCache.erase(h); + } + for (const auto& p : mnListDiffsCache) { + if (p.second.nHeight + LIST_DIFFS_CACHE_SIZE < nHeight) { + toDeleteDiffs.emplace_back(p.first); + } + } + for (const auto& h : toDeleteDiffs) { + mnListDiffsCache.erase(h); + } +} diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h new file mode 100644 index 000000000000..6ba1720cb614 --- /dev/null +++ b/src/evo/deterministicmns.h @@ -0,0 +1,598 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_DETERMINISTICMNS_H +#define PIVX_DETERMINISTICMNS_H + +#include "arith_uint256.h" +#include "dbwrapper.h" +#include "evo/evodb.h" +#include "evo/providertx.h" +#include "saltedhasher.h" +#include "sync.h" + +#include +#include + +#include + +class CBlock; +class CBlockIndex; +class CValidationState; + +class CDeterministicMNState +{ +public: + int nRegisteredHeight{-1}; + int nLastPaidHeight{0}; + int nPoSePenalty{0}; + int nPoSeRevivedHeight{-1}; + int nPoSeBanHeight{-1}; + uint16_t nRevocationReason{0}; + + // the block hash X blocks after registration, used in quorum calculations + uint256 confirmedHash; + // sha256(proTxHash, confirmedHash) to speed up quorum calculations + // please note that this is NOT a double-sha256 hash + uint256 confirmedHashWithProRegTxHash; + + CKeyID keyIDOwner; + CKeyID keyIDOperator; + CKeyID keyIDVoting; + CService addr; + CScript scriptPayout; + CScript scriptOperatorPayout; + +public: + CDeterministicMNState() {} + explicit CDeterministicMNState(const ProRegPL& pl) + { + keyIDOwner = pl.keyIDOwner; + keyIDOperator = pl.keyIDOperator; + keyIDVoting = pl.keyIDVoting; + addr = pl.addr; + scriptPayout = pl.scriptPayout; + scriptOperatorPayout = pl.scriptOperatorPayout; + } + template + CDeterministicMNState(deserialize_type, Stream& s) + { + s >> *this; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(nRegisteredHeight); + READWRITE(nLastPaidHeight); + READWRITE(nPoSePenalty); + READWRITE(nPoSeRevivedHeight); + READWRITE(nPoSeBanHeight); + READWRITE(nRevocationReason); + READWRITE(confirmedHash); + READWRITE(confirmedHashWithProRegTxHash); + READWRITE(keyIDOwner); + READWRITE(keyIDOperator); + READWRITE(keyIDVoting); + READWRITE(addr); + READWRITE(scriptPayout); + READWRITE(scriptOperatorPayout); + } + + void UpdateConfirmedHash(const uint256& _proTxHash, const uint256& _confirmedHash) + { + confirmedHash = _confirmedHash; + CSHA256 h; + h.Write(_proTxHash.begin(), _proTxHash.size()); + h.Write(_confirmedHash.begin(), _confirmedHash.size()); + h.Finalize(confirmedHashWithProRegTxHash.begin()); + } + +public: + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; +typedef std::shared_ptr CDeterministicMNStatePtr; +typedef std::shared_ptr CDeterministicMNStateCPtr; + +class CDeterministicMNStateDiff +{ +public: + enum Field : uint32_t { + Field_nRegisteredHeight = 0x0001, + Field_nLastPaidHeight = 0x0002, + Field_nPoSePenalty = 0x0004, + Field_nPoSeRevivedHeight = 0x0008, + Field_nPoSeBanHeight = 0x0010, + Field_nRevocationReason = 0x0020, + Field_confirmedHash = 0x0040, + Field_confirmedHashWithProRegTxHash = 0x0080, + Field_keyIDOwner = 0x0100, + Field_keyIDOperator = 0x0200, + Field_keyIDVoting = 0x0400, + Field_addr = 0x0800, + Field_scriptPayout = 0x1000, + Field_scriptOperatorPayout = 0x2000, + }; + +#define DMN_STATE_DIFF_ALL_FIELDS \ + DMN_STATE_DIFF_LINE(nRegisteredHeight) \ + DMN_STATE_DIFF_LINE(nLastPaidHeight) \ + DMN_STATE_DIFF_LINE(nPoSePenalty) \ + DMN_STATE_DIFF_LINE(nPoSeRevivedHeight) \ + DMN_STATE_DIFF_LINE(nPoSeBanHeight) \ + DMN_STATE_DIFF_LINE(nRevocationReason) \ + DMN_STATE_DIFF_LINE(confirmedHash) \ + DMN_STATE_DIFF_LINE(confirmedHashWithProRegTxHash) \ + DMN_STATE_DIFF_LINE(keyIDOwner) \ + DMN_STATE_DIFF_LINE(keyIDOperator) \ + DMN_STATE_DIFF_LINE(keyIDVoting) \ + DMN_STATE_DIFF_LINE(addr) \ + DMN_STATE_DIFF_LINE(scriptPayout) \ + DMN_STATE_DIFF_LINE(scriptOperatorPayout) + +public: + uint32_t fields{0}; + // we reuse the state class, but only the members as noted by fields are valid + CDeterministicMNState state; + +public: + CDeterministicMNStateDiff() {} + CDeterministicMNStateDiff(const CDeterministicMNState& a, const CDeterministicMNState& b) + { +#define DMN_STATE_DIFF_LINE(f) if (a.f != b.f) { state.f = b.f; fields |= Field_##f; } + DMN_STATE_DIFF_ALL_FIELDS +#undef DMN_STATE_DIFF_LINE + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(VARINT(fields)); +#define DMN_STATE_DIFF_LINE(f) if (fields & Field_##f) READWRITE(state.f); + DMN_STATE_DIFF_ALL_FIELDS +#undef DMN_STATE_DIFF_LINE + } + + void ApplyToState(CDeterministicMNState& target) const + { +#define DMN_STATE_DIFF_LINE(f) if (fields & Field_##f) target.f = state.f; + DMN_STATE_DIFF_ALL_FIELDS +#undef DMN_STATE_DIFF_LINE + } +}; + +class CDeterministicMN +{ +private: + uint64_t internalId{std::numeric_limits::max()}; + +public: + CDeterministicMN() = delete; // no default constructor, must specify internalId + CDeterministicMN(uint64_t _internalId) : internalId(_internalId) + { + // only non-initial values + assert(_internalId != std::numeric_limits::max()); + } + // TODO: can be removed in a future version + CDeterministicMN(const CDeterministicMN& mn, uint64_t _internalId) : CDeterministicMN(mn) { + // only non-initial values + assert(_internalId != std::numeric_limits::max()); + internalId = _internalId; + } + + template + CDeterministicMN(deserialize_type, Stream& s) + { + s >> *this; + } + + uint256 proTxHash; + COutPoint collateralOutpoint; + uint16_t nOperatorReward; + CDeterministicMNStateCPtr pdmnState; + +public: + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(proTxHash); + READWRITE(VARINT(internalId)); + READWRITE(collateralOutpoint); + READWRITE(nOperatorReward); + READWRITE(pdmnState); + } + + template + void Serialize(Stream& s) const { NCONST_PTR(this)->SerializationOp(s, CSerActionSerialize()); } + + template + void Unserialize(Stream& s) { SerializationOp(s, CSerActionUnserialize()); } + + uint64_t GetInternalId() const; + + std::string ToString() const; + void ToJson(UniValue& obj) const; +}; + +typedef std::shared_ptr CDeterministicMNCPtr; + +class CDeterministicMNListDiff; + +template +void SerializeImmerMap(Stream& os, const immer::map& m) +{ + WriteCompactSize(os, m.size()); + for (typename immer::map::const_iterator mi = m.begin(); mi != m.end(); ++mi) + Serialize(os, (*mi)); +} + +template +void UnserializeImmerMap(Stream& is, immer::map& m) +{ + m = immer::map(); + unsigned int nSize = ReadCompactSize(is); + for (unsigned int i = 0; i < nSize; i++) { + std::pair item; + Unserialize(is, item); + m = m.set(item.first, item.second); + } +} + +// For some reason the compiler is not able to choose the correct Serialize/Deserialize methods without a specialized +// version of SerReadWrite. It otherwise always chooses the version that calls a.Serialize() +template +inline void SerReadWrite(Stream& s, const immer::map& m, CSerActionSerialize ser_action) +{ + ::SerializeImmerMap(s, m); +} + +template +inline void SerReadWrite(Stream& s, immer::map& obj, CSerActionUnserialize ser_action) +{ + ::UnserializeImmerMap(s, obj); +} + +class CDeterministicMNList +{ +public: + typedef immer::map MnMap; + typedef immer::map MnInternalIdMap; + typedef immer::map > MnUniquePropertyMap; + +private: + uint256 blockHash; + int nHeight{-1}; + uint32_t nTotalRegisteredCount{0}; + MnMap mnMap; + MnInternalIdMap mnInternalIdMap; + + // map of unique properties like address and keys + // we keep track of this as checking for duplicates would otherwise be painfully slow + MnUniquePropertyMap mnUniquePropertyMap; + +public: + CDeterministicMNList() {} + explicit CDeterministicMNList(const uint256& _blockHash, int _height, uint32_t _totalRegisteredCount) : + blockHash(_blockHash), + nHeight(_height), + nTotalRegisteredCount(_totalRegisteredCount) + { + } + + template + inline void SerializationOpBase(Stream& s, Operation ser_action) + { + READWRITE(blockHash); + READWRITE(nHeight); + READWRITE(nTotalRegisteredCount); + } + + template + void Serialize(Stream& s) const + { + NCONST_PTR(this)->SerializationOpBase(s, CSerActionSerialize()); + // Serialize the map as a vector + WriteCompactSize(s, mnMap.size()); + for (const auto& p : mnMap) { + s << *p.second; + } + } + + template + void Unserialize(Stream& s) { + mnMap = MnMap(); + mnUniquePropertyMap = MnUniquePropertyMap(); + mnInternalIdMap = MnInternalIdMap(); + + SerializationOpBase(s, CSerActionUnserialize()); + + size_t cnt = ReadCompactSize(s); + for (size_t i = 0; i < cnt; i++) { + AddMN(std::make_shared(deserialize, s), false); + } + } + +public: + size_t GetAllMNsCount() const + { + return mnMap.size(); + } + + size_t GetValidMNsCount() const + { + size_t count = 0; + for (const auto& p : mnMap) { + if (IsMNValid(p.second)) { + count++; + } + } + return count; + } + + template + void ForEachMN(bool onlyValid, Callback&& cb) const + { + for (const auto& p : mnMap) { + if (!onlyValid || IsMNValid(p.second)) { + cb(p.second); + } + } + } + +public: + const uint256& GetBlockHash() const { return blockHash; } + int GetHeight() const { return nHeight; } + uint32_t GetTotalRegisteredCount() const { return nTotalRegisteredCount; } + void SetHeight(int _height) { nHeight = _height; } + void SetBlockHash(const uint256& _blockHash) { blockHash = _blockHash; } + + bool IsMNValid(const uint256& proTxHash) const; + bool IsMNPoSeBanned(const uint256& proTxHash) const; + bool IsMNValid(const CDeterministicMNCPtr& dmn) const; + bool IsMNPoSeBanned(const CDeterministicMNCPtr& dmn) const; + + bool HasMN(const uint256& proTxHash) const + { + return GetMN(proTxHash) != nullptr; + } + bool HasMNByCollateral(const COutPoint& collateralOutpoint) const + { + return GetMNByCollateral(collateralOutpoint) != nullptr; + } + bool HasValidMNByCollateral(const COutPoint& collateralOutpoint) const + { + return GetValidMNByCollateral(collateralOutpoint) != nullptr; + } + CDeterministicMNCPtr GetMN(const uint256& proTxHash) const; + CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const; + CDeterministicMNCPtr GetMNByOperatorKey(const CKeyID& keyID); + CDeterministicMNCPtr GetMNByCollateral(const COutPoint& collateralOutpoint) const; + CDeterministicMNCPtr GetValidMNByCollateral(const COutPoint& collateralOutpoint) const; + CDeterministicMNCPtr GetMNByService(const CService& service) const; + CDeterministicMNCPtr GetMNByInternalId(uint64_t internalId) const; + CDeterministicMNCPtr GetMNPayee() const; + + /** + * Calculates the projected MN payees for the next *count* blocks. The result is not guaranteed to be correct + * as PoSe banning might occur later + * @param count + * @return + */ + std::vector GetProjectedMNPayees(unsigned int nCount) const; + + /** + * Calculate a quorum based on the modifier. The resulting list is deterministically sorted by score + * @param maxSize + * @param modifier + * @return + */ + std::vector CalculateQuorum(size_t maxSize, const uint256& modifier) const; + std::vector> CalculateScores(const uint256& modifier) const; + + /** + * Calculates the maximum penalty which is allowed at the height of this MN list. It is dynamic and might change + * for every block. + * @return + */ + int CalcMaxPoSePenalty() const; + + /** + * Returns a the given percentage from the max penalty for this MN list. Always use this method to calculate the + * value later passed to PoSePunish. The percentage should be high enough to take per-block penalty decreasing for MNs + * into account. This means, if you want to accept 2 failures per payment cycle, you should choose a percentage that + * is higher then 50%, e.g. 66%. + * @param percent + * @return + */ + int CalcPenalty(int percent) const; + + /** + * Punishes a MN for misbehavior. If the resulting penalty score of the MN reaches the max penalty, it is banned. + * Penalty scores are only increased when the MN is not already banned, which means that after banning the penalty + * might appear lower then the current max penalty, while the MN is still banned. + * @param proTxHash + * @param penalty + */ + void PoSePunish(const uint256& proTxHash, int penalty, bool debugLogs); + + /** + * Decrease penalty score of MN by 1. + * Only allowed on non-banned MNs. + * @param proTxHash + */ + void PoSeDecrease(const uint256& proTxHash); + + CDeterministicMNListDiff BuildDiff(const CDeterministicMNList& to) const; + CDeterministicMNList ApplyDiff(const CBlockIndex* pindex, const CDeterministicMNListDiff& diff) const; + + void AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount = true); + void UpdateMN(const CDeterministicMNCPtr& oldDmn, const CDeterministicMNStateCPtr& pdmnState); + void UpdateMN(const uint256& proTxHash, const CDeterministicMNStateCPtr& pdmnState); + void UpdateMN(const CDeterministicMNCPtr& oldDmn, const CDeterministicMNStateDiff& stateDiff); + void RemoveMN(const uint256& proTxHash); + + template + bool HasUniqueProperty(const T& v) const + { + return mnUniquePropertyMap.count(::SerializeHash(v)) != 0; + } + template + CDeterministicMNCPtr GetUniquePropertyMN(const T& v) const + { + auto p = mnUniquePropertyMap.find(::SerializeHash(v)); + if (!p) { + return nullptr; + } + return GetMN(p->first); + } + +private: + template + void AddUniqueProperty(const CDeterministicMNCPtr& dmn, const T& v) + { + static const T nullValue; + assert(v != nullValue); + + auto hash = ::SerializeHash(v); + auto oldEntry = mnUniquePropertyMap.find(hash); + assert(!oldEntry || oldEntry->first == dmn->proTxHash); + std::pair newEntry(dmn->proTxHash, 1); + if (oldEntry) { + newEntry.second = oldEntry->second + 1; + } + mnUniquePropertyMap = mnUniquePropertyMap.set(hash, newEntry); + } + template + void DeleteUniqueProperty(const CDeterministicMNCPtr& dmn, const T& oldValue) + { + static const T nullValue; + assert(oldValue != nullValue); + + auto oldHash = ::SerializeHash(oldValue); + auto p = mnUniquePropertyMap.find(oldHash); + assert(p && p->first == dmn->proTxHash); + if (p->second == 1) { + mnUniquePropertyMap = mnUniquePropertyMap.erase(oldHash); + } else { + mnUniquePropertyMap = mnUniquePropertyMap.set(oldHash, std::make_pair(dmn->proTxHash, p->second - 1)); + } + } + template + void UpdateUniqueProperty(const CDeterministicMNCPtr& dmn, const T& oldValue, const T& newValue) + { + if (oldValue == newValue) { + return; + } + static const T nullValue; + + if (oldValue != nullValue) { + DeleteUniqueProperty(dmn, oldValue); + } + + if (newValue != nullValue) { + AddUniqueProperty(dmn, newValue); + } + } +}; + +class CDeterministicMNListDiff +{ +public: + int nHeight{-1}; //memory only + + std::vector addedMNs; + // keys are all relating to the internalId of MNs + std::map updatedMNs; + std::set removedMns; + +public: + template + void Serialize(Stream& s) const + { + s << addedMNs; + WriteCompactSize(s, updatedMNs.size()); + for (const auto& p : updatedMNs) { + s << VARINT(p.first); + s << p.second; + } + WriteCompactSize(s, removedMns.size()); + for (const auto& p : removedMns) { + s << VARINT(p); + } + } + + template + void Unserialize(Stream& s) + { + updatedMNs.clear(); + removedMns.clear(); + + size_t tmp; + uint64_t tmp2; + s >> addedMNs; + tmp = ReadCompactSize(s); + for (size_t i = 0; i < tmp; i++) { + CDeterministicMNStateDiff diff; + s >> VARINT(tmp2); + s >> diff; + updatedMNs.emplace(tmp2, std::move(diff)); + } + tmp = ReadCompactSize(s); + for (size_t i = 0; i < tmp; i++) { + s >> VARINT(tmp2); + removedMns.emplace(tmp2); + } + } + +public: + bool HasChanges() const + { + return !addedMNs.empty() || !updatedMNs.empty() || !removedMns.empty(); + } +}; + +class CDeterministicMNManager +{ + static const int DISK_SNAPSHOT_PERIOD = 1440; // once per day + static const int DISK_SNAPSHOTS = 3; // keep cache for 3 disk snapshots to have 2 full days covered + static const int LIST_DIFFS_CACHE_SIZE = DISK_SNAPSHOT_PERIOD * DISK_SNAPSHOTS; + +public: + mutable RecursiveMutex cs; + +private: + CEvoDB& evoDb; + + std::unordered_map mnListsCache; + std::unordered_map mnListDiffsCache; + const CBlockIndex* tipIndex{nullptr}; + +public: + explicit CDeterministicMNManager(CEvoDB& _evoDb); + + bool ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& state, bool fJustCheck); + bool UndoBlock(const CBlock& block, const CBlockIndex* pindex); + + void UpdatedBlockTip(const CBlockIndex* pindex); + + // the returned list will not contain the correct block hash (we can't know it yet as the coinbase TX is not updated yet) + bool BuildNewListFromBlock(const CBlock& block, const CBlockIndex* pindexPrev, CValidationState& state, CDeterministicMNList& mnListRet, bool debugLogs); + void DecreasePoSePenalties(CDeterministicMNList& mnList); + + CDeterministicMNList GetListForBlock(const CBlockIndex* pindex); + CDeterministicMNList GetListAtChainTip(); + +private: + void CleanupCache(int nHeight); +}; + +extern std::unique_ptr deterministicMNManager; + +#endif //PIVX_DETERMINISTICMNS_H diff --git a/src/init.cpp b/src/init.cpp index 1301b25ea140..3ff99e42fe20 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -44,6 +44,7 @@ #include "scheduler.h" #include "spork.h" #include "sporkdb.h" +#include "evo/deterministicmns.h" #include "evo/evodb.h" #include "txdb.h" #include "torcontrol.h" @@ -298,6 +299,7 @@ void PrepareShutdown() zerocoinDB = NULL; delete pSporkDB; pSporkDB = NULL; + deterministicMNManager.reset(); evoDb.reset(); } #ifdef ENABLE_WALLET @@ -1582,8 +1584,10 @@ bool AppInitMain() zerocoinDB = new CZerocoinDB(0, false, fReindex); pSporkDB = new CSporkDB(0, false, false); + deterministicMNManager.reset(); evoDb.reset(); evoDb.reset(new CEvoDB(nEvoDbCache, false, fReindex)); + deterministicMNManager.reset(new CDeterministicMNManager(*evoDb)); pblocktree = new CBlockTreeDB(nBlockTreeDBCache, false, fReindex); diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index e15348900b95..080bc5083448 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -9,6 +9,7 @@ #include "blockassembler.h" #include "guiinterface.h" +#include "evo/deterministicmns.h" #include "evo/evodb.h" #include "miner.h" #include "net_processing.h" @@ -45,11 +46,13 @@ BasicTestingSetup::BasicTestingSetup() fCheckBlockIndex = true; SelectParams(CBaseChainParams::MAIN); evoDb.reset(new CEvoDB(1 << 20, true, true)); + deterministicMNManager.reset(new CDeterministicMNManager(*evoDb)); } BasicTestingSetup::~BasicTestingSetup() { ECC_Stop(); g_connman.reset(); + deterministicMNManager.reset(); evoDb.reset(); } diff --git a/src/validation.cpp b/src/validation.cpp index 8dd9e4660f86..35731b1ee665 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -23,6 +23,7 @@ #include "consensus/validation.h" #include "consensus/zerocoin_verify.h" #include "evo/specialtx.h" +#include "evo/deterministicmns.h" #include "fs.h" #include "guiinterface.h" #include "init.h" From 48c66dc01e33ba8f05fe830d7fd32d6a5674d1a4 Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Wed, 14 Feb 2018 14:42:06 +0100 Subject: [PATCH 09/25] Conflict handling for ProRegTx in mempool >>> adapted from dash@cdd723ede6bf6b13ff19142e9f0e210c0bb4a139 with more recent changes merged in --- src/txmempool.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++++- src/txmempool.h | 12 ++++ src/validation.cpp | 4 ++ 3 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index feb663acfb9a..42c8ff5bb93f 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -7,6 +7,9 @@ #include "txmempool.h" #include "clientversion.h" +#include "evo/deterministicmns.h" +#include "evo/specialtx.h" +#include "evo/providertx.h" #include "policy/fees.h" #include "reverse_iterate.h" #include "streams.h" @@ -19,7 +22,6 @@ #include "validationinterface.h" - CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFee, int64_t _nTime, double _entryPriority, unsigned int _entryHeight, bool poolHasNoInputsOf, CAmount _inChainInputValue, @@ -429,6 +431,23 @@ bool CTxMemPool::addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, totalTxSize += entry.GetTxSize(); minerPolicyEstimator->processTransaction(entry, fCurrentEstimate); + // Invalid ProTxes should never get this far because transactions should be + // fully checked by AcceptToMemoryPool() at this point, so we just assume that + // everything is fine here. + if (tx.nType == CTransaction::TxType::PROREG) { + const uint256& txid = tx.GetHash(); + ProRegPL pl; + bool ok = GetTxPayload(tx, pl); + assert(ok); + if (!pl.collateralOutpoint.hash.IsNull()) { + mapProTxRefs.emplace(txid, pl.collateralOutpoint.hash); + mapProTxCollaterals.emplace(pl.collateralOutpoint, txid); + } + mapProTxAddresses.emplace(pl.addr, txid); + mapProTxPubKeyIDs.emplace(pl.keyIDOwner, txid); + mapProTxPubKeyIDs.emplace(pl.keyIDOperator, txid); + } + return true; } @@ -452,6 +471,32 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) mapSaplingNullifiers.erase(sd.nullifier); } } + + auto eraseProTxRef = [&](const uint256& proTxHash, const uint256& txHash) { + auto its = mapProTxRefs.equal_range(proTxHash); + for (auto it = its.first; it != its.second;) { + if (it->second == txHash) { + it = mapProTxRefs.erase(it); + } else { + ++it; + } + } + }; + + if (tx.nType == CTransaction::TxType::PROREG) { + const uint256& txid = tx.GetHash(); + ProRegPL pl; + bool ok = GetTxPayload(tx, pl); + assert(ok); + if (!pl.collateralOutpoint.IsNull()) { + eraseProTxRef(txid, pl.collateralOutpoint.hash); + } + mapProTxCollaterals.erase(pl.collateralOutpoint); + mapProTxAddresses.erase(pl.addr); + mapProTxPubKeyIDs.erase(pl.keyIDOwner); + mapProTxPubKeyIDs.erase(pl.keyIDOperator); + } + totalTxSize -= it->GetTxSize(); cachedInnerUsage -= it->DynamicMemoryUsage(); cachedInnerUsage -= memusage::DynamicUsage(mapLinks[it].parents) + memusage::DynamicUsage(mapLinks[it].children); @@ -607,6 +652,89 @@ void CTxMemPool::removeConflicts(const CTransaction& tx) } } +void CTxMemPool::removeProTxPubKeyConflicts(const CTransaction& tx, const CKeyID& keyId) +{ + if (mapProTxPubKeyIDs.count(keyId)) { + const uint256& conflictHash = mapProTxPubKeyIDs.at(keyId); + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } +} + +void CTxMemPool::removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint) +{ + if (mapProTxCollaterals.count(collateralOutpoint)) { + const uint256& conflictHash = mapProTxCollaterals.at(collateralOutpoint); + if (conflictHash != tx.GetHash() && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } +} + +void CTxMemPool::removeProTxSpentCollateralConflicts(const CTransaction &tx) +{ + // Remove TXs that refer to a MN for which the collateral was spent + auto removeSpentCollateralConflict = [&](const uint256& proTxHash) { + // Can't use equal_range here as every call to removeRecursive might invalidate iterators + while (true) { + auto it = mapProTxRefs.find(proTxHash); + if (it == mapProTxRefs.end()) { + break; + } + auto conflictIt = mapTx.find(it->second); + if (conflictIt != mapTx.end()) { + removeRecursive(conflictIt->GetTx(), MemPoolRemovalReason::CONFLICT); + } else { + // Should not happen as we track referencing TXs in addUnchecked/removeUnchecked. + // But lets be on the safe side and not run into an endless loop... + LogPrint(BCLog::MEMPOOL, "%s: ERROR: found invalid TX ref in mapProTxRefs, proTxHash=%s, txHash=%s\n", __func__, proTxHash.ToString(), it->second.ToString()); + mapProTxRefs.erase(it); + } + } + }; + auto mnList = deterministicMNManager->GetListAtChainTip(); + for (const auto& in : tx.vin) { + auto collateralIt = mapProTxCollaterals.find(in.prevout); + if (collateralIt != mapProTxCollaterals.end()) { + // These are not yet mined ProRegTxs + removeSpentCollateralConflict(collateralIt->second); + } + auto dmn = mnList.GetMNByCollateral(in.prevout); + if (dmn) { + // These are updates refering to a mined ProRegTx + removeSpentCollateralConflict(dmn->proTxHash); + } + } +} + +void CTxMemPool::removeProTxConflicts(const CTransaction &tx) +{ + removeProTxSpentCollateralConflicts(tx); + + if (tx.nType == CTransaction::TxType::PROREG) { + ProRegPL pl; + if (!GetTxPayload(tx, pl)) { + LogPrint(BCLog::MEMPOOL, "%s: ERROR: Invalid transaction payload, tx: %s", __func__, tx.ToString()); + return; + } + + const uint256& txid = tx.GetHash(); + + if (mapProTxAddresses.count(pl.addr)) { + const uint256& conflictHash = mapProTxAddresses.at(pl.addr); + if (conflictHash != txid && mapTx.count(conflictHash)) { + removeRecursive(mapTx.find(conflictHash)->GetTx(), MemPoolRemovalReason::CONFLICT); + } + } + removeProTxPubKeyConflicts(tx, pl.keyIDOwner); + removeProTxPubKeyConflicts(tx, pl.keyIDOperator); + if (!pl.collateralOutpoint.hash.IsNull()) { + removeProTxCollateralConflicts(tx, pl.collateralOutpoint); + } + } +} + /** * Called when a block is connected. Removes from mempool and updates the miner fee estimator. */ @@ -629,6 +757,7 @@ void CTxMemPool::removeForBlock(const std::vector& vtx, unsigne RemoveStaged(stage, true, MemPoolRemovalReason::BLOCK); } removeConflicts(*tx); + removeProTxConflicts(*tx); ClearPrioritisation(tx->GetHash()); } // After the txs in the new block have been removed from the mempool, update policy estimates @@ -643,6 +772,8 @@ void CTxMemPool::_clear() mapLinks.clear(); mapTx.clear(); mapNextTx.clear(); + mapProTxAddresses.clear(); + mapProTxPubKeyIDs.clear(); totalTxSize = 0; cachedInnerUsage = 0; lastRollingFeeUpdate = GetTime(); @@ -918,6 +1049,35 @@ TxMempoolInfo CTxMemPool::info(const uint256& hash) const return GetInfo(i); } +bool CTxMemPool::existsProviderTxConflict(const CTransaction &tx) const { + LOCK(cs); + + if (tx.nType != CTransaction::TxType::PROREG) + return false; + + ProRegPL pl; + if (!GetTxPayload(tx, pl)) { + LogPrint(BCLog::MEMPOOL, "%s: ERROR: Invalid transaction payload, tx: %s", __func__, tx.ToString()); + return true; // i.e. can't decode payload == conflict + } + + if (mapProTxAddresses.count(pl.addr) || mapProTxPubKeyIDs.count(pl.keyIDOwner) || mapProTxPubKeyIDs.count(pl.keyIDOperator)) { + return true; + } + + if (!pl.collateralOutpoint.hash.IsNull()) { + if (mapProTxCollaterals.count(pl.collateralOutpoint)) { + // there is another ProRegTx that refers to the same collateral + return true; + } + if (mapNextTx.count(pl.collateralOutpoint)) { + // there is another tx that spends the collateral + return true; + } + } + return false; +} + CFeeRate CTxMemPool::estimateFee(int nBlocks) const { LOCK(cs); diff --git a/src/txmempool.h b/src/txmempool.h index f4355cdbff53..440b8754a67b 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -18,6 +18,7 @@ #include "primitives/transaction.h" #include "sync.h" #include "random.h" +#include "netaddress.h" #include "boost/multi_index_container.hpp" #include "boost/multi_index/ordered_index.hpp" @@ -511,6 +512,11 @@ class CTxMemPool typedef std::map txlinksMap; txlinksMap mapLinks; + std::multimap mapProTxRefs; // proTxHash -> transaction (all TXs that refer to an existing proTx) + std::map mapProTxAddresses; + std::map mapProTxPubKeyIDs; + std::map mapProTxCollaterals; + void UpdateParent(txiter entry, txiter parent, bool add); void UpdateChild(txiter entry, txiter child, bool add); @@ -551,6 +557,10 @@ class CTxMemPool void removeForReorg(const CCoinsViewCache* pcoins, unsigned int nMemPoolHeight, int flags); void removeWithAnchor(const uint256& invalidRoot); void removeConflicts(const CTransaction& tx); + void removeProTxPubKeyConflicts(const CTransaction &tx, const CKeyID &keyId); + void removeProTxCollateralConflicts(const CTransaction &tx, const COutPoint &collateralOutpoint); + void removeProTxSpentCollateralConflicts(const CTransaction &tx); + void removeProTxConflicts(const CTransaction &tx); void removeForBlock(const std::vector& vtx, unsigned int nBlockHeight, bool fCurrentEstimate = true); @@ -658,6 +668,8 @@ class CTxMemPool TxMempoolInfo info(const uint256& hash) const; std::vector infoAll() const; + bool existsProviderTxConflict(const CTransaction &tx) const; + /** Estimate fee rate needed to get into the next nBlocks * If no answer can be given at nBlocks, return an estimate * at the lowest number of blocks where one can be given diff --git a/src/validation.cpp b/src/validation.cpp index 35731b1ee665..f66280c9cab4 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -433,6 +433,10 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState &state, const C if (tx.IsCoinStake()) return state.DoS(100, false, REJECT_INVALID, "coinstake"); + if (pool.existsProviderTxConflict(tx)) { + return state.DoS(0, false, REJECT_DUPLICATE, "protx-dup"); + } + // Only accept nLockTime-using transactions that can be mined in the next // block; we don't want our mempool filled up with transactions that can't // be mined yet. From 9ac0f37ab110336ad02d7a50c4ebe900eae5afa4 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 27 Jan 2021 18:44:26 +0100 Subject: [PATCH 10/25] [Tests] Introduce evo_deterministicmns_tests.cpp --- src/Makefile.test.include | 1 + src/test/CMakeLists.txt | 2 + src/test/evo_deterministicmns_tests.cpp | 216 ++++++++++++++++++++++++ 3 files changed, 219 insertions(+) create mode 100644 src/test/evo_deterministicmns_tests.cpp diff --git a/src/Makefile.test.include b/src/Makefile.test.include index c675fafd523d..bb3222c55298 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -62,6 +62,7 @@ BITCOIN_TESTS =\ test/crypto_tests.cpp \ test/cuckoocache_tests.cpp \ test/DoS_tests.cpp \ + test/evo_deterministicmns_tests.cpp \ test/evo_specialtx_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 6775c06d76c0..40eacc538493 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -94,6 +94,8 @@ set(BITCOIN_TESTS ${CMAKE_CURRENT_SOURCE_DIR}/crypto_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cuckoocache_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/DoS_tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/evo_deterministicmns_tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/evo_specialtx_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/getarg_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/hash_tests.cpp ${CMAKE_CURRENT_SOURCE_DIR}/key_tests.cpp diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp new file mode 100644 index 000000000000..9f4cfb0b6bc9 --- /dev/null +++ b/src/test/evo_deterministicmns_tests.cpp @@ -0,0 +1,216 @@ +// Copyright (c) 2018-2021 The Dash Core developers +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "test/test_pivx.h" + +#include "evo/specialtx.h" +#include "evo/providertx.h" +#include "evo/deterministicmns.h" +#include "masternode.h" +#include "messagesigner.h" +#include "netbase.h" +#include "policy/policy.h" +#include "primitives/transaction.h" +#include "script/sign.h" +#include "validation.h" + +#include + +typedef std::map> SimpleUTXOMap; + +// static 0.1 PIV fee used for the special txes in these tests +static const CAmount fee = 10000000; + +static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) +{ + SimpleUTXOMap utxos; + for (size_t i = 0; i < txs.size(); i++) { + auto& tx = txs[i]; + for (size_t j = 0; j < tx.vout.size(); j++) { + utxos.emplace(std::piecewise_construct, + std::forward_as_tuple(tx.GetHash(), j), + std::forward_as_tuple((int)i + 1, tx.vout[j].nValue)); + } + } + return utxos; +} + +static std::vector SelectUTXOs(SimpleUTXOMap& utoxs, CAmount amount, CAmount& changeRet) +{ + changeRet = 0; + amount += fee; + + std::vector selectedUtxos; + CAmount selectedAmount = 0; + int chainHeight = chainActive.Height(); + while (!utoxs.empty()) { + bool found = false; + for (auto it = utoxs.begin(); it != utoxs.end(); ++it) { + if (chainHeight - it->second.first < 100) { + continue; + } + + found = true; + selectedAmount += it->second.second; + selectedUtxos.emplace_back(it->first); + utoxs.erase(it); + break; + } + BOOST_ASSERT(found); + if (selectedAmount >= amount) { + changeRet = selectedAmount - amount; + break; + } + } + + return selectedUtxos; +} + +static void FundTransaction(CMutableTransaction& tx, SimpleUTXOMap& utoxs, const CScript& scriptPayout, const CScript& scriptChange, CAmount amount) +{ + CAmount change; + auto inputs = SelectUTXOs(utoxs, amount, change); + for (size_t i = 0; i < inputs.size(); i++) { + tx.vin.emplace_back(inputs[i]); + } + tx.vout.emplace_back(CTxOut(amount, scriptPayout)); + if (change != 0) { + tx.vout.emplace_back(change, scriptChange); + } +} + +static void SignTransaction(CMutableTransaction& tx, const CKey& coinbaseKey) +{ + CBasicKeyStore tempKeystore; + tempKeystore.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); + + for (size_t i = 0; i < tx.vin.size(); i++) { + CTransactionRef txFrom; + uint256 hashBlock; + BOOST_ASSERT(GetTransaction(tx.vin[i].prevout.hash, txFrom, hashBlock)); + BOOST_ASSERT(SignSignature(tempKeystore, *txFrom, tx, i, SIGHASH_ALL)); + } +} + +static CKey GetRandomKey() +{ + CKey keyRet; + keyRet.MakeNewKey(true); + return keyRet; +} + +// Creates a ProRegTx. +// - if optCollateralOut is nullopt, generate a new collateral in the first output of the tx +// - otherwise reference *optCollateralOut as external collateral +static CMutableTransaction CreateProRegTx(Optional optCollateralOut, SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, const CKey& ownerKey, const CKey& operatorKey) +{ + ProRegPL pl; + pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0)); + pl.addr = LookupNumeric("1.1.1.1", port); + pl.keyIDOwner = ownerKey.GetPubKey().GetID(); + pl.keyIDOperator = operatorKey.GetPubKey().GetID(); + pl.keyIDVoting = ownerKey.GetPubKey().GetID(); + pl.scriptPayout = scriptPayout; + + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; + tx.nType = CTransaction::TxType::PROREG; + FundTransaction(tx, utxos, scriptPayout, + GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), + (optCollateralOut ? 0 : MN_COLL_AMT)); + + pl.inputsHash = CalcTxInputsHash(tx); + SetTxPayload(tx, pl); + SignTransaction(tx, coinbaseKey); + + return tx; +} + +static CScript GenerateRandomAddress() +{ + CKey key; + key.MakeNewKey(false); + return GetScriptForDestination(key.GetPubKey().GetID()); +} + +template +static CMutableTransaction MalleateProTxPayout(const CMutableTransaction& tx) +{ + ProPL pl; + GetTxPayload(tx, pl); + pl.scriptPayout = GenerateRandomAddress(); + CMutableTransaction tx2 = tx; + SetTxPayload(tx2, pl); + return tx2; +} + +static bool CheckTransactionSignature(const CMutableTransaction& tx) +{ + for (unsigned int i = 0; i < tx.vin.size(); i++) { + const auto& txin = tx.vin[i]; + CTransactionRef txFrom; + uint256 hashBlock; + BOOST_ASSERT(GetTransaction(txin.prevout.hash, txFrom, hashBlock)); + + CAmount amount = txFrom->vout[txin.prevout.n].nValue; + if (!VerifyScript(txin.scriptSig, txFrom->vout[txin.prevout.n].scriptPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker(&tx, i, amount), tx.GetRequiredSigVersion())) { + return false; + } + } + return true; +} + +BOOST_AUTO_TEST_SUITE(deterministicmns_tests) + +BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) +{ + auto utxos = BuildSimpleUtxoMap(coinbaseTxns); + + int nHeight = chainActive.Height(); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight); + int port = 1; + + // these maps are only populated, but not used for now. They will be needed later on, in the next commits. + std::vector dmnHashes; + std::map ownerKeys; + std::map operatorKeys; + + // register one MN per block + for (size_t i = 0; i < 6; i++) { + const CKey& ownerKey = GetRandomKey(); + const CKey& operatorKey = GetRandomKey(); + auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey); + const uint256& txid = tx.GetHash(); + dmnHashes.emplace_back(txid); + ownerKeys.emplace(txid, ownerKey); + operatorKeys.emplace(txid, operatorKey); + + CValidationState dummyState; + BOOST_CHECK(CheckProRegTx(tx, dummyState)); + BOOST_CHECK(CheckTransactionSignature(tx)); + + // also verify that payloads are not malleable after they have been signed + // the form of ProRegTx we use here is one with a collateral included, so there is no signature inside the + // payload itself. This means, we need to rely on script verification, which takes the hash of the extra payload + // into account + auto tx2 = MalleateProTxPayout(tx); + // Technically, the payload is still valid... + BOOST_CHECK(CheckProRegTx(tx2, dummyState)); + // But the signature should not verify anymore + BOOST_CHECK(!CheckTransactionSignature(tx2)); + + CreateAndProcessBlock({tx}, coinbaseKey); + deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + + BOOST_CHECK_EQUAL(chainActive.Height(), nHeight + 1); + // check list after connecting deterministicMNManager->ProcessBlock(); + //BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid)); + + nHeight++; + } + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); +} + +BOOST_AUTO_TEST_SUITE_END() From 7f2d2b5fa90696cffb85e6764dba04956a1eb2af Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 28 Jan 2021 01:51:38 +0100 Subject: [PATCH 11/25] [MOVE-ONLY] move special_tx_validation_test inside evo_* test file --- src/test/evo_specialtx_tests.cpp | 39 +++++++++++++++++++++++++++++ src/test/validation_tests.cpp | 42 +------------------------------- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/src/test/evo_specialtx_tests.cpp b/src/test/evo_specialtx_tests.cpp index 934483d9de47..e1c133f3d2cb 100644 --- a/src/test/evo_specialtx_tests.cpp +++ b/src/test/evo_specialtx_tests.cpp @@ -51,6 +51,45 @@ static ProRegPL GetRandomProRegPayload() return pl; } +BOOST_AUTO_TEST_CASE(special_tx_validation_test) +{ + CMutableTransaction mtx; + CValidationState state; + + // v1 can only be Type=0 + mtx.nType = CTransaction::TxType::PROREG; + mtx.nVersion = CTransaction::TxVersion::LEGACY; + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-version"); + + // version >= Sapling, type = 0, payload != null. + mtx.nType = CTransaction::TxType::NORMAL; + mtx.extraPayload = std::vector(10, 1); + mtx.nVersion = CTransaction::TxVersion::SAPLING; + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-payload"); + + // version >= Sapling, type = 0, payload == null --> pass + mtx.extraPayload = nullopt; + BOOST_CHECK(CheckSpecialTxNoContext(CTransaction(mtx), state)); + + // nVersion>=2 and nType!=0 without extrapayload + mtx.nType = CTransaction::TxType::PROREG; + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK(state.GetRejectReason().find("without extra payload")); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-empty"); + + // Size limits + mtx.extraPayload = std::vector(MAX_SPECIALTX_EXTRAPAYLOAD + 1, 1); + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-oversize"); + + // Remove one element, so now it passes the size check + mtx.extraPayload->pop_back(); + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-payload"); +} + BOOST_AUTO_TEST_CASE(providertx_setpayload_test) { const ProRegPL& pl = GetRandomProRegPayload(); diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index 846ef6240dd3..c1b948ce156a 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -1,56 +1,16 @@ -// Copyright (c) 2020 The PIVX developers +// Copyright (c) 2020-2021 The PIVX developers // Distributed under the MIT software license, see the accompanying // file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "test/test_pivx.h" #include "primitives/transaction.h" #include "sapling/sapling_validation.h" -#include "evo/specialtx.h" #include "test/librust/utiltest.h" #include BOOST_FIXTURE_TEST_SUITE(validation_tests, TestingSetup) -BOOST_AUTO_TEST_CASE(special_tx_validation_test) -{ - CMutableTransaction mtx; - CValidationState state; - - // v1 can only be Type=0 - mtx.nType = CTransaction::TxType::PROREG; - mtx.nVersion = CTransaction::TxVersion::LEGACY; - BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-version"); - - // version >= Sapling, type = 0, payload != null. - mtx.nType = CTransaction::TxType::NORMAL; - mtx.extraPayload = std::vector(10, 1); - mtx.nVersion = CTransaction::TxVersion::SAPLING; - BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-type-payload"); - - // version >= Sapling, type = 0, payload == null --> pass - mtx.extraPayload = nullopt; - BOOST_CHECK(CheckSpecialTxNoContext(CTransaction(mtx), state)); - - // nVersion>=2 and nType!=0 without extrapayload - mtx.nType = CTransaction::TxType::PROREG; - BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK(state.GetRejectReason().find("without extra payload")); - BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-empty"); - - // Size limits - mtx.extraPayload = std::vector(MAX_SPECIALTX_EXTRAPAYLOAD + 1, 1); - BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-payload-oversize"); - - // Remove one element, so now it passes the size check - mtx.extraPayload->pop_back(); - BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); - BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-payload"); -} - void test_simple_sapling_invalidity(CMutableTransaction& tx) { CAmount nDummyValueOut; From 180a311387ed8b0bd6fe8c411f136feefe69d2f1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Feb 2021 14:29:24 +0100 Subject: [PATCH 12/25] [Validation] Connect deterministic manager to block processing --- src/evo/specialtx.cpp | 14 ++++++++++++-- src/test/evo_deterministicmns_tests.cpp | 3 +-- src/validation.cpp | 9 ++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 66f5147cd33e..7051bdd38268 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -9,6 +9,7 @@ #include "chainparams.h" #include "clientversion.h" #include "consensus/validation.h" +#include "evo/deterministicmns.h" #include "evo/providertx.h" #include "primitives/transaction.h" #include "primitives/block.h" @@ -119,13 +120,22 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CV return false; } } - // !TODO: Process batch of special txes in deterministic manager + + if (!deterministicMNManager->ProcessBlock(block, pindex, state, fJustCheck)) { + // pass the state returned by the function above + return false; + } + + // !TODO: ProcessBlock llmq quorum block processor return true; } bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) { - /* undo special txes in batches */ + if (!deterministicMNManager->UndoBlock(block, pindex)) { + return false; + } + // !TODO: UndoBlock llmq quorum block processor return true; } diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 9f4cfb0b6bc9..44687fdac8cc 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -205,8 +205,7 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); BOOST_CHECK_EQUAL(chainActive.Height(), nHeight + 1); - // check list after connecting deterministicMNManager->ProcessBlock(); - //BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid)); + BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid)); nHeight++; } diff --git a/src/validation.cpp b/src/validation.cpp index f66280c9cab4..4eb7357d10a7 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -22,8 +22,8 @@ #include "consensus/tx_verify.h" #include "consensus/validation.h" #include "consensus/zerocoin_verify.h" -#include "evo/specialtx.h" #include "evo/deterministicmns.h" +#include "evo/specialtx.h" #include "fs.h" #include "guiinterface.h" #include "init.h" @@ -1347,6 +1347,10 @@ DisconnectResult DisconnectBlock(CBlock& block, const CBlockIndex* pindex, CCoin return DISCONNECT_FAILED; } + if (!UndoSpecialTxsInBlock(block, pindex)) { + return DISCONNECT_FAILED; + } + // undo transactions in reverse order for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction& tx = *block.vtx[i]; @@ -2411,6 +2415,9 @@ bool ActivateBestChain(CValidationState& state, std::shared_ptr pb // Notify ValidationInterface subscribers GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, fInitialDownload); + // TODO: move to notification interface + deterministicMNManager->UpdatedBlockTip(pindexNewTip); + // Always notify the UI if a new block tip was connected uiInterface.NotifyBlockTip(fInitialDownload, pindexNewTip); } From c013bf16bdb38ef119f23668f97f33ad0ebbb7e1 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Feb 2021 17:05:23 +0100 Subject: [PATCH 13/25] [Refactoring] Add evo Notification Interface --- CMakeLists.txt | 1 + src/Makefile.am | 2 ++ src/evo/deterministicmns.cpp | 10 ++++++++-- src/evo/evonotificationinterface.cpp | 24 ++++++++++++++++++++++++ src/evo/evonotificationinterface.h | 28 ++++++++++++++++++++++++++++ src/guiinterface.h | 4 ++++ src/init.cpp | 18 +++++++++++++++++- src/validation.cpp | 3 --- src/validationinterface.cpp | 8 ++++++++ src/validationinterface.h | 5 +++++ 10 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/evo/evonotificationinterface.cpp create mode 100644 src/evo/evonotificationinterface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d892edf7b181..400eba944aad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,6 +385,7 @@ set(COMMON_SOURCES ./src/key_io.cpp ./src/compressor.cpp ./src/evo/deterministicmns.cpp + ./src/evo/evonotificationinterface.cpp ./src/evo/evodb.cpp ./src/evo/providertx.cpp ./src/evo/specialtx.cpp diff --git a/src/Makefile.am b/src/Makefile.am index 94ae65df81f1..f3eb07302acb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -184,6 +184,7 @@ BITCOIN_CORE_H = \ cyclingvector.h \ evo/deterministicmns.h \ evo/evodb.h \ + evo/evonotificationinterface.h \ evo/providertx.h \ evo/specialtx.h \ pairresult.h \ @@ -330,6 +331,7 @@ libbitcoin_server_a_SOURCES = \ consensus/zerocoin_verify.cpp \ evo/deterministicmns.cpp \ evo/evodb.cpp \ + evo/evonotificationinterface.cpp \ evo/providertx.cpp \ evo/specialtx.cpp \ httprpc.cpp \ diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 300a5f236d3e..3c9399c804f8 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -9,6 +9,7 @@ #include "chainparams.h" #include "core_io.h" #include "evo/specialtx.h" +#include "guiinterface.h" #include "masternode.h" // for MN_COLL_AMT, MasternodeCollateralMinConf #include "script/standard.h" #include "sync.h" @@ -543,7 +544,11 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockInde return _state.DoS(100, false, REJECT_INVALID, "failed-dmn-block"); } - // !TODO: notify listeners that the mn list has changed + // Don't hold cs while calling signals + if (diff.HasChanges()) { + GetMainSignals().NotifyMasternodeListChanged(false, oldList, diff); + uiInterface.NotifyMasternodeListChanged(newList); + } LOCK(cs); CleanupCache(nHeight); @@ -575,7 +580,8 @@ bool CDeterministicMNManager::UndoBlock(const CBlock& block, const CBlockIndex* if (diff.HasChanges()) { auto inversedDiff = curList.BuildDiff(prevList); - // !TODO: notify listeners that the masternode list has changed + GetMainSignals().NotifyMasternodeListChanged(true, curList, inversedDiff); + uiInterface.NotifyMasternodeListChanged(prevList); } return true; diff --git a/src/evo/evonotificationinterface.cpp b/src/evo/evonotificationinterface.cpp new file mode 100644 index 000000000000..3de92cc5be67 --- /dev/null +++ b/src/evo/evonotificationinterface.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2014-2019 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "evo/evonotificationinterface.h" + +#include "evo/deterministicmns.h" +#include "validation.h" + +void EvoNotificationInterface::InitializeCurrentBlockTip() +{ + LOCK(cs_main); + UpdatedBlockTip(chainActive.Tip(), nullptr, IsInitialBlockDownload()); +} + +void EvoNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) +{ + deterministicMNManager->UpdatedBlockTip(pindexNew); +} + +void EvoNotificationInterface::NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) +{ + // !TODO +} diff --git a/src/evo/evonotificationinterface.h b/src/evo/evonotificationinterface.h new file mode 100644 index 000000000000..1020f4473368 --- /dev/null +++ b/src/evo/evonotificationinterface.h @@ -0,0 +1,28 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef EVONOTIFICATIONINTERFACE_H +#define EVONOTIFICATIONINTERFACE_H + +#include "validationinterface.h" + +class EvoNotificationInterface : public CValidationInterface +{ +public: + explicit EvoNotificationInterface(CConnman& connmanIn): connman(connmanIn) {} + virtual ~EvoNotificationInterface() = default; + + // a small helper to initialize current block height in sub-modules on startup + void InitializeCurrentBlockTip(); + +protected: + // CValidationInterface + void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; + void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) override; + +private: + CConnman& connman; +}; + +#endif // EVONOTIFICATIONINTERFACE_H diff --git a/src/guiinterface.h b/src/guiinterface.h index 2d2053681028..be550751b3a3 100644 --- a/src/guiinterface.h +++ b/src/guiinterface.h @@ -18,6 +18,7 @@ class CBasicKeyStore; class CWallet; class uint256; class CBlockIndex; +class CDeterministicMNList; /** General change type (added, updated, removed). */ enum ChangeType { @@ -102,6 +103,9 @@ class CClientUIInterface /** Banlist did change. */ boost::signals2::signal BannedListChanged; + + /** Deterministic Masternode list has changed */ + boost::signals2::signal NotifyMasternodeListChanged; }; extern CClientUIInterface uiInterface; diff --git a/src/init.cpp b/src/init.cpp index 3ff99e42fe20..9d009d446f2b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -22,6 +22,7 @@ #include "checkpoints.h" #include "compat/sanity.h" #include "consensus/upgrades.h" +#include "evo/evonotificationinterface.h" #include "fs.h" #include "httpserver.h" #include "httprpc.h" @@ -105,6 +106,8 @@ std::unique_ptr peerLogic; static CZMQNotificationInterface* pzmqNotificationInterface = NULL; #endif +static EvoNotificationInterface* pEvoNotificationInterface = nullptr; + #ifdef WIN32 // Win32 LevelDB doesn't use filedescriptors, and the ones used for // accessing block files, don't count towards to fd_set size limit @@ -307,11 +310,17 @@ void PrepareShutdown() bitdb.Flush(true); #endif + if (pEvoNotificationInterface) { + UnregisterValidationInterface(pEvoNotificationInterface); + delete pEvoNotificationInterface; + pEvoNotificationInterface = nullptr; + } + #if ENABLE_ZMQ if (pzmqNotificationInterface) { UnregisterValidationInterface(pzmqNotificationInterface); delete pzmqNotificationInterface; - pzmqNotificationInterface = NULL; + pzmqNotificationInterface = nullptr; } #endif @@ -719,6 +728,10 @@ void ThreadImport(const std::vector& vImportFiles) StartShutdown(); } + // force UpdatedBlockTip to initialize nCachedBlockHeight for DS, MN payments and budgets + // but don't call it directly to prevent triggering of other listeners like zmq etc. + pEvoNotificationInterface->InitializeCurrentBlockTip(); + if (gArgs.GetBoolArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) { LoadMempool(::mempool); } @@ -1536,6 +1549,9 @@ bool AppInitMain() } #endif + pEvoNotificationInterface = new EvoNotificationInterface(connman); + RegisterValidationInterface(pEvoNotificationInterface); + // ********************************************************* Step 7: load block chain fReindex = gArgs.GetBoolArg("-reindex", false); diff --git a/src/validation.cpp b/src/validation.cpp index 4eb7357d10a7..bf4c01278234 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2415,9 +2415,6 @@ bool ActivateBestChain(CValidationState& state, std::shared_ptr pb // Notify ValidationInterface subscribers GetMainSignals().UpdatedBlockTip(pindexNewTip, pindexFork, fInitialDownload); - // TODO: move to notification interface - deterministicMNManager->UpdatedBlockTip(pindexNewTip); - // Always notify the UI if a new block tip was connected uiInterface.NotifyBlockTip(fInitialDownload, pindexNewTip); } diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 2b980368923a..b8925e45a2ba 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -22,6 +22,7 @@ struct ValidationInterfaceConnections { boost::signals2::scoped_connection SetBestChain; boost::signals2::scoped_connection Broadcast; boost::signals2::scoped_connection BlockChecked; + boost::signals2::scoped_connection NotifyMasternodeListChanged; }; struct MainSignalsInstance { @@ -45,6 +46,8 @@ struct MainSignalsInstance { boost::signals2::signal Broadcast; /** Notifies listeners of a block validation result */ boost::signals2::signal BlockChecked; + /** Notifies listeners of updated deterministic masternode list */ + boost::signals2::signal NotifyMasternodeListChanged; std::unordered_map m_connMainSignals; @@ -94,6 +97,7 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) conns.SetBestChain = g_signals.m_internals->SetBestChain.connect(std::bind(&CValidationInterface::SetBestChain, pwalletIn, std::placeholders::_1)); conns.Broadcast = g_signals.m_internals->Broadcast.connect(std::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, std::placeholders::_1)); conns.BlockChecked = g_signals.m_internals->BlockChecked.connect(std::bind(&CValidationInterface::BlockChecked, pwalletIn, std::placeholders::_1, std::placeholders::_2)); + conns.NotifyMasternodeListChanged = g_signals.m_internals->NotifyMasternodeListChanged.connect(std::bind(&CValidationInterface::NotifyMasternodeListChanged, pwalletIn, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); } void UnregisterValidationInterface(CValidationInterface* pwalletIn) @@ -175,3 +179,7 @@ void CMainSignals::Broadcast(CConnman* connman) { void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& state) { m_internals->BlockChecked(block, state); } + +void CMainSignals::NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) { + m_internals->NotifyMasternodeListChanged(undo, oldMNList, diff); +} diff --git a/src/validationinterface.h b/src/validationinterface.h index 4dd8c54681d2..ae32ff1bc901 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -18,6 +18,8 @@ class CBlock; struct CBlockLocator; class CBlockIndex; class CConnman; +class CDeterministicMNList; +class CDeterministicMNListDiff; class CValidationInterface; class CValidationState; class uint256; @@ -146,6 +148,8 @@ class CValidationInterface { friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterAllValidationInterfaces(); + /** Notifies listeners of updated deterministic masternode list */ + virtual void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) {} }; struct MainSignalsInstance; @@ -176,6 +180,7 @@ class CMainSignals { void SetBestChain(const CBlockLocator &); void Broadcast(CConnman* connman); void BlockChecked(const CBlock&, const CValidationState&); + void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff); }; CMainSignals& GetMainSignals(); From 41869ecfda067d863ca72741eff3f92545ed6f30 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Feb 2021 18:08:03 +0100 Subject: [PATCH 14/25] [Refactoring] Add IsDIP3Enforced/LegacyMNObsolete funcs to DMNManager --- src/evo/deterministicmns.cpp | 36 +++++++++++++++++++++++++++++++++--- src/evo/deterministicmns.h | 8 ++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 3c9399c804f8..d2ddadd06406 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -12,6 +12,7 @@ #include "guiinterface.h" #include "masternode.h" // for MN_COLL_AMT, MasternodeCollateralMinConf #include "script/standard.h" +#include "spork.h" #include "sync.h" #include "validation.h" #include "validationinterface.h" @@ -503,7 +504,10 @@ CDeterministicMNManager::CDeterministicMNManager(CEvoDB& _evoDb) : bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockIndex* pindex, CValidationState& _state, bool fJustCheck) { int nHeight = pindex->nHeight; - // !TODO: exit early if enforcement not active + if (!IsDIP3Enforced(nHeight)) { + // nothing to do + return true; + } CDeterministicMNList oldList, newList; CDeterministicMNListDiff diff; @@ -558,8 +562,12 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, const CBlockInde bool CDeterministicMNManager::UndoBlock(const CBlock& block, const CBlockIndex* pindex) { - // !TODO: exit early if enforcement not active - uint256 blockHash = block.GetHash(); + if (!IsDIP3Enforced(pindex->nHeight)) { + // nothing to do + return true; + } + + const uint256& blockHash = block.GetHash(); CDeterministicMNList curList; CDeterministicMNList prevList; @@ -812,6 +820,28 @@ CDeterministicMNList CDeterministicMNManager::GetListAtChainTip() return GetListForBlock(tipIndex); } +bool CDeterministicMNManager::IsDIP3Enforced(int nHeight) const +{ + return Params().GetConsensus().NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V6_0); +} + +bool CDeterministicMNManager::IsDIP3Enforced() const +{ + int tipHeight = WITH_LOCK(cs, return tipIndex ? tipIndex->nHeight : -1;); + return IsDIP3Enforced(tipHeight); +} + +bool CDeterministicMNManager::LegacyMNObsolete(int nHeight) const +{ + return nHeight > sporkManager.GetSporkValue(SPORK_21_LEGACY_MNS_MAX_HEIGHT); +} + +bool CDeterministicMNManager::LegacyMNObsolete() const +{ + int tipHeight = WITH_LOCK(cs, return tipIndex ? tipIndex->nHeight : -1;); + return LegacyMNObsolete(tipHeight); +} + void CDeterministicMNManager::CleanupCache(int nHeight) { AssertLockHeld(cs); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 6ba1720cb614..fd9a4fde3abc 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -589,6 +589,14 @@ class CDeterministicMNManager CDeterministicMNList GetListForBlock(const CBlockIndex* pindex); CDeterministicMNList GetListAtChainTip(); + // Whether DMNs are enforced at provided height, or at the chain-tip + bool IsDIP3Enforced(int nHeight) const; + bool IsDIP3Enforced() const; + + // Whether Legacy MNs are disabled at provided height, or at the chain-tip + bool LegacyMNObsolete(int nHeight) const; + bool LegacyMNObsolete() const; + private: void CleanupCache(int nHeight); }; From ddad010eb52545231eedebaeac984582029cfa82 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 8 Mar 2021 03:33:13 +0100 Subject: [PATCH 15/25] [Validation] Check duplicate unique-properties for masternodes --- src/evo/providertx.cpp | 18 ++++++++++++++++-- src/test/evo_deterministicmns_tests.cpp | 12 +++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 27b3d6fe9a31..0b6a7b90d7f1 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -7,6 +7,7 @@ #include "base58.h" #include "core_io.h" +#include "evo/deterministicmns.h" #include "masternode.h" // MN_COLL_AMT #include "messagesigner.h" #include "evo/specialtx.h" @@ -177,12 +178,25 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid } } - // !TODO: check for duplicate IP address or keys in the dmns manager - if (!CheckInputsHash(tx, pl, state)) { return false; } + if (pindexPrev) { + auto mnList = deterministicMNManager->GetListForBlock(pindexPrev); + // only allow reusing of addresses when it's for the same collateral (which replaces the old MN) + if (mnList.HasUniqueProperty(pl.addr) && mnList.GetUniquePropertyMN(pl.addr)->collateralOutpoint != pl.collateralOutpoint) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-IP-address"); + } + // never allow duplicate keys, even if this ProTx would replace an existing MN + if (mnList.HasUniqueProperty(pl.keyIDOwner)) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-owner-key"); + } + if (mnList.HasUniqueProperty(pl.keyIDOperator)) { + return state.DoS(10, false, REJECT_DUPLICATE, "bad-protx-dup-operator-key"); + } + } + return true; } diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 44687fdac8cc..ce1324a459e4 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -168,7 +168,8 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) { auto utxos = BuildSimpleUtxoMap(coinbaseTxns); - int nHeight = chainActive.Height(); + const CBlockIndex* chainTip = chainActive.Tip(); + int nHeight = chainTip->nHeight; UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight); int port = 1; @@ -188,7 +189,7 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) operatorKeys.emplace(txid, operatorKey); CValidationState dummyState; - BOOST_CHECK(CheckProRegTx(tx, dummyState)); + BOOST_CHECK(CheckSpecialTx(tx, chainTip, dummyState)); BOOST_CHECK(CheckTransactionSignature(tx)); // also verify that payloads are not malleable after they have been signed @@ -197,14 +198,15 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) // into account auto tx2 = MalleateProTxPayout(tx); // Technically, the payload is still valid... - BOOST_CHECK(CheckProRegTx(tx2, dummyState)); + BOOST_CHECK(CheckSpecialTx(tx2, chainTip, dummyState)); // But the signature should not verify anymore BOOST_CHECK(!CheckTransactionSignature(tx2)); CreateAndProcessBlock({tx}, coinbaseKey); - deterministicMNManager->UpdatedBlockTip(chainActive.Tip()); + chainTip = chainActive.Tip(); + BOOST_CHECK_EQUAL(chainTip->nHeight, nHeight + 1); - BOOST_CHECK_EQUAL(chainActive.Height(), nHeight + 1); + deterministicMNManager->UpdatedBlockTip(chainTip); BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid)); nHeight++; From 66302c300de5203dbbb579295a55f09ca75c3d13 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Mar 2021 23:57:51 +0100 Subject: [PATCH 16/25] [Tests] Check deterministic masternodes unique properties --- src/test/evo_deterministicmns_tests.cpp | 93 ++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 1 deletion(-) diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index ce1324a459e4..29fd95e02508 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -168,7 +168,7 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) { auto utxos = BuildSimpleUtxoMap(coinbaseTxns); - const CBlockIndex* chainTip = chainActive.Tip(); + CBlockIndex* chainTip = chainActive.Tip(); int nHeight = chainTip->nHeight; UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight); int port = 1; @@ -209,8 +209,99 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) deterministicMNManager->UpdatedBlockTip(chainTip); BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txid)); + // Add change to the utxos map + if (tx.vout.size() > 1) { + utxos.emplace(COutPoint(tx.GetHash(), 1), std::make_pair(nHeight + 1, tx.vout[1].nValue)); + } + nHeight++; } + // Mine 30 more blocks + for (size_t i = 0; i < 30; i++) { + CreateAndProcessBlock({}, coinbaseKey); + chainTip = chainActive.Tip(); + BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight); + deterministicMNManager->UpdatedBlockTip(chainTip); + } + + // Try to register used owner key + { + SimpleUTXOMap utxos_copy(utxos); + const CKey& ownerKey = ownerKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]); + auto tx = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey, GetRandomKey()); + CValidationState state; + BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key"); + } + // Try to register used operator key + { + SimpleUTXOMap utxos_copy(utxos); + const CKey& operatorKey = operatorKeys.at(dmnHashes[InsecureRandRange(dmnHashes.size())]); + auto tx = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKey); + CValidationState state; + BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key"); + } + // Try to register used IP address + { + SimpleUTXOMap utxos_copy(utxos); + auto tx = CreateProRegTx(nullopt, utxos_copy, 1 + InsecureRandRange(port-1), GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + CValidationState state; + BOOST_CHECK(!CheckSpecialTx(tx, chainTip, state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address"); + } + // Block with two ProReg txes using same owner key + { + SimpleUTXOMap utxos_copy(utxos); + const CKey& ownerKey = GetRandomKey(); + const CKey& operatorKey1 = GetRandomKey(); + const CKey& operatorKey2 = GetRandomKey(); + auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey1); + auto tx2 = CreateProRegTx(nullopt, utxos_copy, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey, operatorKey2); + CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); + CBlockIndex indexFake(block); + indexFake.nHeight = nHeight; + indexFake.pprev = chainTip; + CValidationState state; + BOOST_CHECK(!ProcessSpecialTxsInBlock(block, &indexFake, state, true)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key"); + ProcessNewBlock(state, nullptr, std::make_shared(block), nullptr); + BOOST_CHECK_EQUAL(chainActive.Height(), nHeight); // bad block not connected + } + // Block with two ProReg txes using same operator key + { + SimpleUTXOMap utxos_copy(utxos); + const CKey& ownerKey1 = GetRandomKey(); + const CKey& ownerKey2 = GetRandomKey(); + const CKey& operatorKey = GetRandomKey(); + auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, ownerKey1, operatorKey); + auto tx2 = CreateProRegTx(nullopt, utxos_copy, (port+1), GenerateRandomAddress(), coinbaseKey, ownerKey2, operatorKey); + CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); + CBlockIndex indexFake(block); + indexFake.nHeight = nHeight; + indexFake.pprev = chainTip; + CValidationState state; + BOOST_CHECK(!ProcessSpecialTxsInBlock(block, &indexFake, state, true)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key"); + ProcessNewBlock(state, nullptr, std::make_shared(block), nullptr); + BOOST_CHECK_EQUAL(chainActive.Height(), nHeight); // bad block not connected + } + // Block with two ProReg txes using ip address + { + SimpleUTXOMap utxos_copy(utxos); + auto tx1 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + auto tx2 = CreateProRegTx(nullopt, utxos_copy, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomKey()); + CBlock block = CreateBlock({tx1, tx2}, coinbaseKey); + CBlockIndex indexFake(block); + indexFake.nHeight = nHeight; + indexFake.pprev = chainTip; + CValidationState state; + BOOST_CHECK(!ProcessSpecialTxsInBlock(block, &indexFake, state, true)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address"); + ProcessNewBlock(state, nullptr, std::make_shared(block), nullptr); + BOOST_CHECK_EQUAL(chainActive.Height(), nHeight); // bad block not connected + } + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } From ca9d10cfb575d482859c9bf0685a81fbfe4b282b Mon Sep 17 00:00:00 2001 From: Alexander Block Date: Thu, 15 Feb 2018 14:33:04 +0100 Subject: [PATCH 17/25] Implement CActiveDeterministicMasternodeManager >>> adapted from dashpay@d90b1399679849665bdbce5be2560be4ea6a5140 --- src/activemasternode.cpp | 181 +++++++++++++++++++++++++++++++++++++++ src/activemasternode.h | 51 ++++++++++- src/init.cpp | 15 ++++ 3 files changed, 246 insertions(+), 1 deletion(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 863a68860f58..0a0f18d18133 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -6,6 +6,8 @@ #include "activemasternode.h" #include "addrman.h" +#include "evo/providertx.h" +#include "evo/deterministicmns.h" #include "masternode-sync.h" #include "masternode.h" #include "masternodeconfig.h" @@ -14,6 +16,185 @@ #include "netbase.h" #include "protocol.h" +// Keep track of the active Masternode +CActiveMasternodeInfo activeMasternodeInfo; +CActiveDeterministicMasternodeManager* activeMasternodeManager; + +std::string CActiveDeterministicMasternodeManager::GetStateString() const +{ + switch (state) { + case MASTERNODE_WAITING_FOR_PROTX: return "WAITING_FOR_PROTX"; + case MASTERNODE_POSE_BANNED: return "POSE_BANNED"; + case MASTERNODE_REMOVED: return "REMOVED"; + case MASTERNODE_OPERATOR_KEY_CHANGED: return "OPERATOR_KEY_CHANGED"; + case MASTERNODE_PROTX_IP_CHANGED: return "PROTX_IP_CHANGED"; + case MASTERNODE_READY: return "READY"; + case MASTERNODE_ERROR: return "ERROR"; + default: return "UNKNOWN"; + } +} + +std::string CActiveDeterministicMasternodeManager::GetStatus() const +{ + switch (state) { + case MASTERNODE_WAITING_FOR_PROTX: return "Waiting for ProTx to appear on-chain"; + case MASTERNODE_POSE_BANNED: return "Masternode was PoSe banned"; + case MASTERNODE_REMOVED: return "Masternode removed from list"; + case MASTERNODE_OPERATOR_KEY_CHANGED: return "Operator key changed or revoked"; + case MASTERNODE_PROTX_IP_CHANGED: return "IP address specified in ProTx changed"; + case MASTERNODE_READY: return "Ready"; + case MASTERNODE_ERROR: return "Error. " + strError; + default: return "Unknown"; + } +} + +void CActiveDeterministicMasternodeManager::Init() +{ + if (!fMasterNode || !deterministicMNManager->IsDIP3Enforced()) + return; + + LOCK(cs_main); + + // Check that our local network configuration is correct + if (!fListen) { + // listen option is probably overwritten by smth else, no good + state = MASTERNODE_ERROR; + strError = "Masternode must accept connections from outside. Make sure listen configuration option is not overwritten by some another parameter."; + LogPrintf("%s -- ERROR: %s\n", __func__, strError); + return; + } + + if (!GetLocalAddress(activeMasternodeInfo.service)) { + state = MASTERNODE_ERROR; + return; + } + + CDeterministicMNList mnList = deterministicMNManager->GetListAtChainTip(); + + CDeterministicMNCPtr dmn = mnList.GetMNByOperatorKey(activeMasternodeInfo.keyIDOperator); + if (!dmn) { + // MN not appeared on the chain yet + return; + } + + if (!mnList.IsMNValid(dmn->proTxHash)) { + if (mnList.IsMNPoSeBanned(dmn->proTxHash)) { + state = MASTERNODE_POSE_BANNED; + } else { + state = MASTERNODE_REMOVED; + } + return; + } + + LogPrintf("%s -- proTxHash=%s, proTx=%s\n", __func__, dmn->proTxHash.ToString(), dmn->ToString()); + + if (activeMasternodeInfo.service != dmn->pdmnState->addr) { + state = MASTERNODE_ERROR; + strError = "Local address does not match the address from ProTx"; + LogPrintf("%s -- ERROR: %s", __func__, strError); + return; + } + + if (!Params().IsRegTestNet()) { + // Check socket connectivity + const std::string& strService = activeMasternodeInfo.service.ToString(); + LogPrintf("%s -- Checking inbound connection to '%s'\n", __func__, strService); + SOCKET hSocket; + bool fConnected = ConnectSocket(activeMasternodeInfo.service, hSocket, nConnectTimeout) && IsSelectableSocket(hSocket); + CloseSocket(hSocket); + + if (!fConnected) { + state = MASTERNODE_ERROR; + LogPrintf("%s -- ERROR: Could not connect to %s\n", __func__, strService); + return; + } + } + + activeMasternodeInfo.proTxHash = dmn->proTxHash; + activeMasternodeInfo.outpoint = dmn->collateralOutpoint; + state = MASTERNODE_READY; +} + +void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +{ + if (fInitialDownload) + return; + + if (!fMasterNode || !deterministicMNManager->IsDIP3Enforced()) + return; + + LOCK(cs_main); + + if (state == MASTERNODE_READY) { + auto oldMNList = deterministicMNManager->GetListForBlock(pindexNew->pprev); + auto newMNList = deterministicMNManager->GetListForBlock(pindexNew); + if (!newMNList.IsMNValid(activeMasternodeInfo.proTxHash)) { + // MN disappeared from MN list + state = MASTERNODE_REMOVED; + activeMasternodeInfo.SetNullProTx(); + // MN might have reappeared in same block with a new ProTx + Init(); + return; + } + + auto oldDmn = oldMNList.GetMN(activeMasternodeInfo.proTxHash); + auto newDmn = newMNList.GetMN(activeMasternodeInfo.proTxHash); + if (newDmn->pdmnState->keyIDOperator != oldDmn->pdmnState->keyIDOperator) { + // MN operator key changed or revoked + state = MASTERNODE_OPERATOR_KEY_CHANGED; + activeMasternodeInfo.SetNullProTx(); + // MN might have reappeared in same block with a new ProTx + Init(); + return; + } + + if (newDmn->pdmnState->addr != oldDmn->pdmnState->addr) { + // MN IP changed + state = MASTERNODE_PROTX_IP_CHANGED; + activeMasternodeInfo.SetNullProTx(); + Init(); + return; + } + } else { + // MN might have (re)appeared with a new ProTx or we've found some peers + // and figured out our local address + Init(); + } +} + +bool CActiveDeterministicMasternodeManager::GetLocalAddress(CService& addrRet) +{ + // First try to find whatever local address is specified by externalip option + bool fFoundLocal = GetLocal(addrRet) && IsValidNetAddr(addrRet); + if (!fFoundLocal && Params().IsRegTestNet()) { + if (Lookup("127.0.0.1", addrRet, GetListenPort(), false)) { + fFoundLocal = true; + } + } + if(!fFoundLocal) { + bool empty = true; + // If we have some peers, let's try to find our local address from one of them + g_connman->ForEachNodeContinueIf([&fFoundLocal, &empty](CNode* pnode) { + empty = false; + if (pnode->addr.IsIPv4()) + fFoundLocal = GetLocal(activeMasternodeInfo.service, &pnode->addr) && IsValidNetAddr(activeMasternodeInfo.service); + return !fFoundLocal; + }); + strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; + LogPrintf("%s -- ERROR: %s\n", __func__, strError); + return false; + } + return true; +} + +bool CActiveDeterministicMasternodeManager::IsValidNetAddr(const CService& addrIn) +{ + // TODO: check IPv6 and TOR addresses + return Params().IsRegTestNet() || (addrIn.IsIPv4() && IsReachable(addrIn) && addrIn.IsRoutable()); +} + +/********* LEGACY *********/ + OperationResult initMasternode(const std::string& _strMasterNodePrivKey, const std::string& _strMasterNodeAddr, bool isFromInit) { if (!isFromInit && fMasterNode) { diff --git a/src/activemasternode.h b/src/activemasternode.h index cfa4351d3756..6c1c9104bd75 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -12,17 +12,66 @@ #include "net.h" #include "operationresult.h" #include "sync.h" +#include "validationinterface.h" #include "wallet/wallet.h" +struct CActiveMasternodeInfo; +class CActiveDeterministicMasternodeManager; + #define ACTIVE_MASTERNODE_INITIAL 0 // initial state #define ACTIVE_MASTERNODE_SYNC_IN_PROCESS 1 #define ACTIVE_MASTERNODE_NOT_CAPABLE 3 #define ACTIVE_MASTERNODE_STARTED 4 +extern CActiveMasternodeInfo activeMasternodeInfo; +extern CActiveDeterministicMasternodeManager* activeMasternodeManager; + +struct CActiveMasternodeInfo { + // Keys for the active Masternode + CKeyID keyIDOperator; + CKey keyOperator; + // Initialized while registering Masternode + uint256 proTxHash; + COutPoint outpoint; + CService service; + void SetNullProTx() { proTxHash = UINT256_ZERO; outpoint.SetNull(); } +}; + +class CActiveDeterministicMasternodeManager : public CValidationInterface +{ +public: + enum masternode_state_t { + MASTERNODE_WAITING_FOR_PROTX, + MASTERNODE_POSE_BANNED, + MASTERNODE_REMOVED, + MASTERNODE_OPERATOR_KEY_CHANGED, + MASTERNODE_PROTX_IP_CHANGED, + MASTERNODE_READY, + MASTERNODE_ERROR, + }; + +private: + masternode_state_t state{MASTERNODE_WAITING_FOR_PROTX}; + std::string strError; + +public: + virtual void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload); + + void Init(); + + std::string GetStateString() const; + std::string GetStatus() const; + + static bool IsValidNetAddr(const CService& addrIn); + +private: + bool GetLocalAddress(CService& addrRet); +}; + // Responsible for initializing the masternode OperationResult initMasternode(const std::string& strMasterNodePrivKey, const std::string& strMasterNodeAddr, bool isFromInit); -// Responsible for activating the Masternode and pinging the network +// Responsible for activating the Masternode and pinging the network (legacy MN list) class CActiveMasternode { private: diff --git a/src/init.cpp b/src/init.cpp index 9d009d446f2b..6a877850299a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -316,6 +316,10 @@ void PrepareShutdown() pEvoNotificationInterface = nullptr; } + if (fMasterNode) { + UnregisterValidationInterface(activeMasternodeManager); + } + #if ENABLE_ZMQ if (pzmqNotificationInterface) { UnregisterValidationInterface(pzmqNotificationInterface); @@ -1894,6 +1898,11 @@ bool AppInitMain() if (fMasterNode) { LogPrintf("IS MASTER NODE\n"); + + // init and register activeMasternodeManager + activeMasternodeManager = new CActiveDeterministicMasternodeManager(); + RegisterValidationInterface(activeMasternodeManager); + auto res = initMasternode(gArgs.GetArg("-masternodeprivkey", ""), gArgs.GetArg("-masternodeaddr", ""), true); if (!res) { return UIError(res.getError()); } } @@ -1932,6 +1941,11 @@ bool AppInitMain() return false; } + if (fMasterNode) { + assert(activeMasternodeManager); + activeMasternodeManager->Init(); + } + // ********************************************************* Step 11: start node if (!strErrors.str().empty()) @@ -1940,6 +1954,7 @@ bool AppInitMain() //// debug print LogPrintf("mapBlockIndex.size() = %u\n", mapBlockIndex.size()); LogPrintf("chainActive.Height() = %d\n", chainActive.Height()); + #ifdef ENABLE_WALLET { if (pwalletMain) { From 6cd5b0cd7ec61b93746277ea1a4a01fe5d992898 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sun, 14 Feb 2021 20:45:04 +0100 Subject: [PATCH 18/25] [Refactoring] encapsulate activeMNINfo inside activeMNManager providing const pointer getter --- src/activemasternode.cpp | 110 ++++++++++++++++++++------------------- src/activemasternode.h | 18 ++++--- 2 files changed, 68 insertions(+), 60 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 0a0f18d18133..f4c24a3bebdc 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -17,22 +17,7 @@ #include "protocol.h" // Keep track of the active Masternode -CActiveMasternodeInfo activeMasternodeInfo; -CActiveDeterministicMasternodeManager* activeMasternodeManager; - -std::string CActiveDeterministicMasternodeManager::GetStateString() const -{ - switch (state) { - case MASTERNODE_WAITING_FOR_PROTX: return "WAITING_FOR_PROTX"; - case MASTERNODE_POSE_BANNED: return "POSE_BANNED"; - case MASTERNODE_REMOVED: return "REMOVED"; - case MASTERNODE_OPERATOR_KEY_CHANGED: return "OPERATOR_KEY_CHANGED"; - case MASTERNODE_PROTX_IP_CHANGED: return "PROTX_IP_CHANGED"; - case MASTERNODE_READY: return "READY"; - case MASTERNODE_ERROR: return "ERROR"; - default: return "UNKNOWN"; - } -} +CActiveDeterministicMasternodeManager* activeMasternodeManager{nullptr}; std::string CActiveDeterministicMasternodeManager::GetStatus() const { @@ -48,6 +33,22 @@ std::string CActiveDeterministicMasternodeManager::GetStatus() const } } +OperationResult CActiveDeterministicMasternodeManager::SetOperatorKey(const std::string& strMNOperatorPrivKey) +{ + + LOCK(cs_main); // Lock cs_main so the node doesn't perform any action while we setup the Masternode + LogPrintf("Initializing deterministic masternode...\n"); + if (strMNOperatorPrivKey.empty()) { + return errorOut("ERROR: Masternode operator priv key cannot be empty."); + } + CPubKey pubkey; + if (!CMessageSigner::GetKeysFromSecret(strMNOperatorPrivKey, info.keyOperator, pubkey)) { + return errorOut(_("Invalid mnoperatorprivatekey. Please see the documentation.")); + } + info.keyIDOperator = pubkey.GetID(); + return OperationResult(true); +} + void CActiveDeterministicMasternodeManager::Init() { if (!fMasterNode || !deterministicMNManager->IsDIP3Enforced()) @@ -64,14 +65,14 @@ void CActiveDeterministicMasternodeManager::Init() return; } - if (!GetLocalAddress(activeMasternodeInfo.service)) { + if (!GetLocalAddress(info.service)) { state = MASTERNODE_ERROR; return; } CDeterministicMNList mnList = deterministicMNManager->GetListAtChainTip(); - CDeterministicMNCPtr dmn = mnList.GetMNByOperatorKey(activeMasternodeInfo.keyIDOperator); + CDeterministicMNCPtr dmn = mnList.GetMNByOperatorKey(info.keyIDOperator); if (!dmn) { // MN not appeared on the chain yet return; @@ -88,19 +89,20 @@ void CActiveDeterministicMasternodeManager::Init() LogPrintf("%s -- proTxHash=%s, proTx=%s\n", __func__, dmn->proTxHash.ToString(), dmn->ToString()); - if (activeMasternodeInfo.service != dmn->pdmnState->addr) { + if (info.service != dmn->pdmnState->addr) { state = MASTERNODE_ERROR; - strError = "Local address does not match the address from ProTx"; - LogPrintf("%s -- ERROR: %s", __func__, strError); + strError = strprintf("Local address %s does not match the address from ProTx (%s)", + info.service.ToStringIPPort(), dmn->pdmnState->addr.ToStringIPPort()); + LogPrintf("%s -- ERROR: %s\n", __func__, strError); return; } if (!Params().IsRegTestNet()) { // Check socket connectivity - const std::string& strService = activeMasternodeInfo.service.ToString(); + const std::string& strService = info.service.ToString(); LogPrintf("%s -- Checking inbound connection to '%s'\n", __func__, strService); SOCKET hSocket; - bool fConnected = ConnectSocket(activeMasternodeInfo.service, hSocket, nConnectTimeout) && IsSelectableSocket(hSocket); + bool fConnected = ConnectSocket(info.service, hSocket, nConnectTimeout) && IsSelectableSocket(hSocket); CloseSocket(hSocket); if (!fConnected) { @@ -110,11 +112,18 @@ void CActiveDeterministicMasternodeManager::Init() } } - activeMasternodeInfo.proTxHash = dmn->proTxHash; - activeMasternodeInfo.outpoint = dmn->collateralOutpoint; + info.proTxHash = dmn->proTxHash; state = MASTERNODE_READY; } +void CActiveDeterministicMasternodeManager::Reset(masternode_state_t _state) +{ + state = _state; + SetNullProTx(); + // MN might have reappeared in same block with a new ProTx + Init(); +} + void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) { if (fInitialDownload) @@ -123,36 +132,26 @@ void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* p if (!fMasterNode || !deterministicMNManager->IsDIP3Enforced()) return; - LOCK(cs_main); - if (state == MASTERNODE_READY) { auto oldMNList = deterministicMNManager->GetListForBlock(pindexNew->pprev); auto newMNList = deterministicMNManager->GetListForBlock(pindexNew); - if (!newMNList.IsMNValid(activeMasternodeInfo.proTxHash)) { + if (!newMNList.IsMNValid(info.proTxHash)) { // MN disappeared from MN list - state = MASTERNODE_REMOVED; - activeMasternodeInfo.SetNullProTx(); - // MN might have reappeared in same block with a new ProTx - Init(); + Reset(MASTERNODE_REMOVED); return; } - auto oldDmn = oldMNList.GetMN(activeMasternodeInfo.proTxHash); - auto newDmn = newMNList.GetMN(activeMasternodeInfo.proTxHash); + auto oldDmn = oldMNList.GetMN(info.proTxHash); + auto newDmn = newMNList.GetMN(info.proTxHash); if (newDmn->pdmnState->keyIDOperator != oldDmn->pdmnState->keyIDOperator) { // MN operator key changed or revoked - state = MASTERNODE_OPERATOR_KEY_CHANGED; - activeMasternodeInfo.SetNullProTx(); - // MN might have reappeared in same block with a new ProTx - Init(); + Reset(MASTERNODE_OPERATOR_KEY_CHANGED); return; } if (newDmn->pdmnState->addr != oldDmn->pdmnState->addr) { // MN IP changed - state = MASTERNODE_PROTX_IP_CHANGED; - activeMasternodeInfo.SetNullProTx(); - Init(); + Reset(MASTERNODE_PROTX_IP_CHANGED); return; } } else { @@ -165,24 +164,24 @@ void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* p bool CActiveDeterministicMasternodeManager::GetLocalAddress(CService& addrRet) { // First try to find whatever local address is specified by externalip option - bool fFoundLocal = GetLocal(addrRet) && IsValidNetAddr(addrRet); - if (!fFoundLocal && Params().IsRegTestNet()) { + bool fFound = GetLocal(addrRet) && IsValidNetAddr(addrRet); + if (!fFound && Params().IsRegTestNet()) { if (Lookup("127.0.0.1", addrRet, GetListenPort(), false)) { - fFoundLocal = true; + fFound = true; } } - if(!fFoundLocal) { - bool empty = true; + if(!fFound) { // If we have some peers, let's try to find our local address from one of them - g_connman->ForEachNodeContinueIf([&fFoundLocal, &empty](CNode* pnode) { - empty = false; + g_connman->ForEachNodeContinueIf([&fFound, &addrRet](CNode* pnode) { if (pnode->addr.IsIPv4()) - fFoundLocal = GetLocal(activeMasternodeInfo.service, &pnode->addr) && IsValidNetAddr(activeMasternodeInfo.service); - return !fFoundLocal; + fFound = GetLocal(addrRet, &pnode->addr) && IsValidNetAddr(addrRet); + return !fFound; }); - strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; - LogPrintf("%s -- ERROR: %s\n", __func__, strError); - return false; + if (!fFound) { + strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; + LogPrintf("%s -- ERROR: %s\n", __func__, strError); + return false; + } } return true; } @@ -193,6 +192,7 @@ bool CActiveDeterministicMasternodeManager::IsValidNetAddr(const CService& addrI return Params().IsRegTestNet() || (addrIn.IsIPv4() && IsReachable(addrIn) && addrIn.IsRoutable()); } + /********* LEGACY *********/ OperationResult initMasternode(const std::string& _strMasterNodePrivKey, const std::string& _strMasterNodeAddr, bool isFromInit) @@ -260,6 +260,10 @@ OperationResult initMasternode(const std::string& _strMasterNodePrivKey, const s void CActiveMasternode::ManageStatus() { if (!fMasterNode) return; + if (activeMasternodeManager != nullptr) { + // Deterministic masternode + return; + } LogPrint(BCLog::MASTERNODE, "CActiveMasternode::ManageStatus() - Begin\n"); diff --git a/src/activemasternode.h b/src/activemasternode.h index 6c1c9104bd75..37f9c083bd2a 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -15,7 +15,6 @@ #include "validationinterface.h" #include "wallet/wallet.h" -struct CActiveMasternodeInfo; class CActiveDeterministicMasternodeManager; #define ACTIVE_MASTERNODE_INITIAL 0 // initial state @@ -23,18 +22,16 @@ class CActiveDeterministicMasternodeManager; #define ACTIVE_MASTERNODE_NOT_CAPABLE 3 #define ACTIVE_MASTERNODE_STARTED 4 -extern CActiveMasternodeInfo activeMasternodeInfo; extern CActiveDeterministicMasternodeManager* activeMasternodeManager; -struct CActiveMasternodeInfo { +struct CActiveMasternodeInfo +{ // Keys for the active Masternode CKeyID keyIDOperator; CKey keyOperator; // Initialized while registering Masternode - uint256 proTxHash; - COutPoint outpoint; + uint256 proTxHash{UINT256_ZERO}; CService service; - void SetNullProTx() { proTxHash = UINT256_ZERO; outpoint.SetNull(); } }; class CActiveDeterministicMasternodeManager : public CValidationInterface @@ -53,14 +50,21 @@ class CActiveDeterministicMasternodeManager : public CValidationInterface private: masternode_state_t state{MASTERNODE_WAITING_FOR_PROTX}; std::string strError; + CActiveMasternodeInfo info; public: virtual void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload); void Init(); + void Reset(masternode_state_t _state); + // Sets the Deterministic Masternode Operator's private/public key + OperationResult SetOperatorKey(const std::string& strMNOperatorPrivKey); + void SetNullProTx() { info.proTxHash = UINT256_ZERO; } - std::string GetStateString() const; + const CActiveMasternodeInfo* GetInfo() const { return &info; } + masternode_state_t GetState() const { return state; } std::string GetStatus() const; + bool IsReady() const { return state == MASTERNODE_READY; } static bool IsValidNetAddr(const CService& addrIn); From dcb5df91aec2c8ba9b8f97e7fd653c711aef83bd Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 15 Feb 2021 00:46:31 +0100 Subject: [PATCH 19/25] [Refactoring] CADMM: GetLocalAddress static --- src/activemasternode.cpp | 47 +++++++++++++++++++--------------------- src/activemasternode.h | 4 +--- 2 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index f4c24a3bebdc..61df561e9ca4 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -19,6 +19,26 @@ // Keep track of the active Masternode CActiveDeterministicMasternodeManager* activeMasternodeManager{nullptr}; +static bool GetLocalAddress(CService& addrRet) +{ + // First try to find whatever local address is specified by externalip option + bool fFound = GetLocal(addrRet) && CActiveDeterministicMasternodeManager::IsValidNetAddr(addrRet); + if (!fFound && Params().IsRegTestNet()) { + if (Lookup("127.0.0.1", addrRet, GetListenPort(), false)) { + fFound = true; + } + } + if(!fFound) { + // If we have some peers, let's try to find our local address from one of them + g_connman->ForEachNodeContinueIf([&fFound, &addrRet](CNode* pnode) { + if (pnode->addr.IsIPv4()) + fFound = GetLocal(addrRet, &pnode->addr) && CActiveDeterministicMasternodeManager::IsValidNetAddr(addrRet); + return !fFound; + }); + } + return fFound; +} + std::string CActiveDeterministicMasternodeManager::GetStatus() const { switch (state) { @@ -67,6 +87,8 @@ void CActiveDeterministicMasternodeManager::Init() if (!GetLocalAddress(info.service)) { state = MASTERNODE_ERROR; + strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; + LogPrintf("%s -- ERROR: %s\n", __func__, strError); return; } @@ -161,31 +183,6 @@ void CActiveDeterministicMasternodeManager::UpdatedBlockTip(const CBlockIndex* p } } -bool CActiveDeterministicMasternodeManager::GetLocalAddress(CService& addrRet) -{ - // First try to find whatever local address is specified by externalip option - bool fFound = GetLocal(addrRet) && IsValidNetAddr(addrRet); - if (!fFound && Params().IsRegTestNet()) { - if (Lookup("127.0.0.1", addrRet, GetListenPort(), false)) { - fFound = true; - } - } - if(!fFound) { - // If we have some peers, let's try to find our local address from one of them - g_connman->ForEachNodeContinueIf([&fFound, &addrRet](CNode* pnode) { - if (pnode->addr.IsIPv4()) - fFound = GetLocal(addrRet, &pnode->addr) && IsValidNetAddr(addrRet); - return !fFound; - }); - if (!fFound) { - strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; - LogPrintf("%s -- ERROR: %s\n", __func__, strError); - return false; - } - } - return true; -} - bool CActiveDeterministicMasternodeManager::IsValidNetAddr(const CService& addrIn) { // TODO: check IPv6 and TOR addresses diff --git a/src/activemasternode.h b/src/activemasternode.h index 37f9c083bd2a..b076b4b0f880 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -67,14 +67,12 @@ class CActiveDeterministicMasternodeManager : public CValidationInterface bool IsReady() const { return state == MASTERNODE_READY; } static bool IsValidNetAddr(const CService& addrIn); - -private: - bool GetLocalAddress(CService& addrRet); }; // Responsible for initializing the masternode OperationResult initMasternode(const std::string& strMasterNodePrivKey, const std::string& strMasterNodeAddr, bool isFromInit); + // Responsible for activating the Masternode and pinging the network (legacy MN list) class CActiveMasternode { From 0520ea9a51fae966e128e291709b95d3d106d7f0 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Wed, 10 Feb 2021 22:22:23 +0100 Subject: [PATCH 20/25] [Init] Add -mnoperatorprivatekey to flag the active mn as deterministic --- src/activemasternode.cpp | 4 +--- src/init.cpp | 44 +++++++++++++++++++++++++++------------- src/messagesigner.cpp | 10 +++++++++ src/messagesigner.h | 1 + 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 61df561e9ca4..09df8d405f45 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -61,11 +61,9 @@ OperationResult CActiveDeterministicMasternodeManager::SetOperatorKey(const std: if (strMNOperatorPrivKey.empty()) { return errorOut("ERROR: Masternode operator priv key cannot be empty."); } - CPubKey pubkey; - if (!CMessageSigner::GetKeysFromSecret(strMNOperatorPrivKey, info.keyOperator, pubkey)) { + if (!CMessageSigner::GetKeysFromSecret(strMNOperatorPrivKey, info.keyOperator, info.keyIDOperator)) { return errorOut(_("Invalid mnoperatorprivatekey. Please see the documentation.")); } - info.keyIDOperator = pubkey.GetID(); return OperationResult(true); } diff --git a/src/init.cpp b/src/init.cpp index 6a877850299a..1aa4316504c1 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -316,7 +316,7 @@ void PrepareShutdown() pEvoNotificationInterface = nullptr; } - if (fMasterNode) { + if (activeMasternodeManager) { UnregisterValidationInterface(activeMasternodeManager); } @@ -571,6 +571,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-masternodeprivkey=", _("Set the masternode private key")); strUsage += HelpMessageOpt("-masternodeaddr=", strprintf(_("Set external address:port to get to this masternode (example: %s)"), "128.127.106.235:51472")); strUsage += HelpMessageOpt("-budgetvotemode=", _("Change automatic finalized budget voting behavior. mode=auto: Vote for only exact finalized budget match to my generated budget. (string, default: auto)")); + strUsage += HelpMessageOpt("-mnoperatorprivatekey=", _("Set the masternode operator private key. Only valid with -masternode=1. When set, the masternode acts as a deterministic masternode.")); strUsage += HelpMessageGroup(_("Node relay options:")); strUsage += HelpMessageOpt("-datacarrier", strprintf(_("Relay and mine data carrier transactions (default: %u)"), DEFAULT_ACCEPT_DATACARRIER)); @@ -1897,14 +1898,34 @@ bool AppInitMain() } if (fMasterNode) { - LogPrintf("IS MASTER NODE\n"); - - // init and register activeMasternodeManager - activeMasternodeManager = new CActiveDeterministicMasternodeManager(); - RegisterValidationInterface(activeMasternodeManager); - - auto res = initMasternode(gArgs.GetArg("-masternodeprivkey", ""), gArgs.GetArg("-masternodeaddr", ""), true); - if (!res) { return UIError(res.getError()); } + const std::string& mnoperatorkeyStr = gArgs.GetArg("-mnoperatorprivatekey", ""); + const bool fDeterministic = !mnoperatorkeyStr.empty(); + LogPrintf("IS %sMASTERNODE\n", (fDeterministic ? "DETERMINISTIC " : "")); + + if (fDeterministic) { + // Check enforcement + if (!deterministicMNManager->IsDIP3Enforced()) { + const std::string strError = "Cannot start deterministic masternode before enforcement. Remove -mnoperatorprivatekey to start as legacy masternode"; + LogPrintf("-- ERROR: %s\n", strError); + return UIError(_(strError.c_str())); + } + // Create and register activeMasternodeManager + activeMasternodeManager = new CActiveDeterministicMasternodeManager(); + auto res = activeMasternodeManager->SetOperatorKey(mnoperatorkeyStr); + if (!res) { return UIError(res.getError()); } + RegisterValidationInterface(activeMasternodeManager); + // Init active masternode + activeMasternodeManager->Init(); + } else { + // Check enforcement + if (deterministicMNManager->LegacyMNObsolete()) { + const std::string strError = "Legacy masternode system disabled. Use -mnoperatorprivatekey to start as deterministic masternode"; + LogPrintf("-- ERROR: %s\n", strError); + return UIError(_(strError.c_str())); + } + auto res = initMasternode(gArgs.GetArg("-masternodeprivkey", ""), gArgs.GetArg("-masternodeaddr", ""), true); + if (!res) { return UIError(res.getError()); } + } } //get the mode of budget voting for this masternode @@ -1941,11 +1962,6 @@ bool AppInitMain() return false; } - if (fMasterNode) { - assert(activeMasternodeManager); - activeMasternodeManager->Init(); - } - // ********************************************************* Step 11: start node if (!strErrors.str().empty()) diff --git a/src/messagesigner.cpp b/src/messagesigner.cpp index 8149002c8c98..386f47c86885 100644 --- a/src/messagesigner.cpp +++ b/src/messagesigner.cpp @@ -22,6 +22,16 @@ bool CMessageSigner::GetKeysFromSecret(const std::string& strSecret, CKey& keyRe return true; } +bool CMessageSigner::GetKeysFromSecret(const std::string& strSecret, CKey& keyRet, CKeyID& keyIDRet) +{ + CPubKey pubkey; + if (!GetKeysFromSecret(strSecret, keyRet, pubkey)) { + return false; + } + keyIDRet = pubkey.GetID(); + return true; +} + uint256 CMessageSigner::GetMessageHash(const std::string& strMessage) { CHashWriter ss(SER_GETHASH, 0); diff --git a/src/messagesigner.h b/src/messagesigner.h index c088b04ac383..ded8c6e6e2a2 100644 --- a/src/messagesigner.h +++ b/src/messagesigner.h @@ -23,6 +23,7 @@ class CMessageSigner public: /// Set the private/public key values, returns true if successful static bool GetKeysFromSecret(const std::string& strSecret, CKey& keyRet, CPubKey& pubkeyRet); + static bool GetKeysFromSecret(const std::string& strSecret, CKey& keyRet, CKeyID& keyIDRet); /// Get the hash based on the input message static uint256 GetMessageHash(const std::string& strMessage); /// Sign the message, returns true if successful From a1cb2276da50232d4e082d054aa8840ca5a5a3a3 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Thu, 11 Feb 2021 03:32:08 +0100 Subject: [PATCH 21/25] [BUG] Add proper virtual dtor for CActiveDeterministicMasternodeManager --- src/activemasternode.h | 1 + src/init.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/activemasternode.h b/src/activemasternode.h index b076b4b0f880..1990b94fad45 100644 --- a/src/activemasternode.h +++ b/src/activemasternode.h @@ -53,6 +53,7 @@ class CActiveDeterministicMasternodeManager : public CValidationInterface CActiveMasternodeInfo info; public: + virtual ~CActiveDeterministicMasternodeManager() = default; virtual void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload); void Init(); diff --git a/src/init.cpp b/src/init.cpp index 1aa4316504c1..dd4871514525 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -318,6 +318,8 @@ void PrepareShutdown() if (activeMasternodeManager) { UnregisterValidationInterface(activeMasternodeManager); + delete activeMasternodeManager; + activeMasternodeManager = nullptr; } #if ENABLE_ZMQ From 170ab92e4f486772de5da18b667e78711ad514e5 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Fri, 16 Apr 2021 23:17:55 +0200 Subject: [PATCH 22/25] [Tests] Check for protx inputs hash in evo_specialtx_tests --- src/test/evo_specialtx_tests.cpp | 36 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/test/evo_specialtx_tests.cpp b/src/test/evo_specialtx_tests.cpp index e1c133f3d2cb..52f0d665312a 100644 --- a/src/test/evo_specialtx_tests.cpp +++ b/src/test/evo_specialtx_tests.cpp @@ -13,15 +13,6 @@ BOOST_FIXTURE_TEST_SUITE(evo_specialtx_tests, TestingSetup) -static void RandomScript(CScript &script) -{ - static const opcodetype oplist[] = {OP_FALSE, OP_1, OP_2, OP_3, OP_CHECKSIG, OP_IF, OP_VERIF, OP_RETURN, OP_CODESEPARATOR}; - script = CScript(); - int ops = (InsecureRandRange(10)); - for (int i=0; ipop_back(); BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-payload"); + + // valid payload but invalid inputs hash + mtx.extraPayload->clear(); + ProRegPL pl = GetRandomProRegPayload(); + SetTxPayload(mtx, pl); + BOOST_CHECK(!CheckSpecialTxNoContext(CTransaction(mtx), state)); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-inputs-hash"); + + // all good. + mtx.vin.emplace_back(GetRandHash(), 0); + mtx.extraPayload->clear(); + pl.inputsHash = CalcTxInputsHash(CTransaction(mtx)); + SetTxPayload(mtx, pl); + BOOST_CHECK(CheckSpecialTxNoContext(CTransaction(mtx), state)); } BOOST_AUTO_TEST_CASE(providertx_setpayload_test) @@ -123,7 +133,7 @@ BOOST_AUTO_TEST_CASE(providertx_checkstringsig_test) // Change owner address or script payout pl.keyIDOwner = GetRandomKeyID(); BOOST_CHECK(!CMessageSigner::VerifyMessage(keyID, pl.vchSig, pl.MakeSignString(), strError)); - RandomScript(pl.scriptPayout); + pl.scriptPayout = GetRandomScript(); BOOST_CHECK(!CMessageSigner::VerifyMessage(keyID, pl.vchSig, pl.MakeSignString(), strError)); } From e659d7e708b9bdd9095e2b822248d95cfdd544b2 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Sun, 18 Apr 2021 17:51:52 +0200 Subject: [PATCH 23/25] [Trivial] Fix styling/logging nits --- src/activemasternode.cpp | 12 ++++++------ src/evo/deterministicmns.cpp | 6 +++--- src/init.cpp | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 09df8d405f45..7e2a240f3d1e 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -79,14 +79,14 @@ void CActiveDeterministicMasternodeManager::Init() // listen option is probably overwritten by smth else, no good state = MASTERNODE_ERROR; strError = "Masternode must accept connections from outside. Make sure listen configuration option is not overwritten by some another parameter."; - LogPrintf("%s -- ERROR: %s\n", __func__, strError); + LogPrintf("%s ERROR: %s\n", __func__, strError); return; } if (!GetLocalAddress(info.service)) { state = MASTERNODE_ERROR; strError = "Can't detect valid external address. Please consider using the externalip configuration option if problem persists. Make sure to use IPv4 address only."; - LogPrintf("%s -- ERROR: %s\n", __func__, strError); + LogPrintf("%s ERROR: %s\n", __func__, strError); return; } @@ -107,27 +107,27 @@ void CActiveDeterministicMasternodeManager::Init() return; } - LogPrintf("%s -- proTxHash=%s, proTx=%s\n", __func__, dmn->proTxHash.ToString(), dmn->ToString()); + LogPrintf("%s: proTxHash=%s, proTx=%s\n", __func__, dmn->proTxHash.ToString(), dmn->ToString()); if (info.service != dmn->pdmnState->addr) { state = MASTERNODE_ERROR; strError = strprintf("Local address %s does not match the address from ProTx (%s)", info.service.ToStringIPPort(), dmn->pdmnState->addr.ToStringIPPort()); - LogPrintf("%s -- ERROR: %s\n", __func__, strError); + LogPrintf("%s ERROR: %s\n", __func__, strError); return; } if (!Params().IsRegTestNet()) { // Check socket connectivity const std::string& strService = info.service.ToString(); - LogPrintf("%s -- Checking inbound connection to '%s'\n", __func__, strService); + LogPrintf("%s: Checking inbound connection to '%s'\n", __func__, strService); SOCKET hSocket; bool fConnected = ConnectSocket(info.service, hSocket, nConnectTimeout) && IsSelectableSocket(hSocket); CloseSocket(hSocket); if (!fConnected) { state = MASTERNODE_ERROR; - LogPrintf("%s -- ERROR: Could not connect to %s\n", __func__, strService); + LogPrintf("%s ERROR: Could not connect to %s\n", __func__, strService); return; } } diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index d2ddadd06406..05039e9097ae 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -188,7 +188,7 @@ CDeterministicMNCPtr CDeterministicMNList::GetMNByInternalId(uint64_t internalId return GetMN(*proTxHash); } -static int CompareByLastPaid_GetHeight(const CDeterministicMN& dmn) +static int CompareByLastPaidGetHeight(const CDeterministicMN& dmn) { int height = dmn.pdmnState->nLastPaidHeight; if (dmn.pdmnState->nPoSeRevivedHeight != -1 && dmn.pdmnState->nPoSeRevivedHeight > height) { @@ -201,8 +201,8 @@ static int CompareByLastPaid_GetHeight(const CDeterministicMN& dmn) static bool CompareByLastPaid(const CDeterministicMN& _a, const CDeterministicMN& _b) { - int ah = CompareByLastPaid_GetHeight(_a); - int bh = CompareByLastPaid_GetHeight(_b); + int ah = CompareByLastPaidGetHeight(_a); + int bh = CompareByLastPaidGetHeight(_b); if (ah == bh) { return _a.proTxHash < _b.proTxHash; } else { diff --git a/src/init.cpp b/src/init.cpp index dd4871514525..8f7f26a14eda 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1907,9 +1907,9 @@ bool AppInitMain() if (fDeterministic) { // Check enforcement if (!deterministicMNManager->IsDIP3Enforced()) { - const std::string strError = "Cannot start deterministic masternode before enforcement. Remove -mnoperatorprivatekey to start as legacy masternode"; + const std::string strError = strprintf(_("Cannot start deterministic masternode before enforcement. Remove %s to start as legacy masternode"), "-mnoperatorprivatekey"); LogPrintf("-- ERROR: %s\n", strError); - return UIError(_(strError.c_str())); + return UIError(strError); } // Create and register activeMasternodeManager activeMasternodeManager = new CActiveDeterministicMasternodeManager(); @@ -1921,9 +1921,9 @@ bool AppInitMain() } else { // Check enforcement if (deterministicMNManager->LegacyMNObsolete()) { - const std::string strError = "Legacy masternode system disabled. Use -mnoperatorprivatekey to start as deterministic masternode"; + const std::string strError = strprintf(_("Legacy masternode system disabled. Use %s to start as deterministic masternode"), "-mnoperatorprivatekey"); LogPrintf("-- ERROR: %s\n", strError); - return UIError(_(strError.c_str())); + return UIError(strError); } auto res = initMasternode(gArgs.GetArg("-masternodeprivkey", ""), gArgs.GetArg("-masternodeaddr", ""), true); if (!res) { return UIError(res.getError()); } From e0ff60fe891235f0b6a75797b8a8a49a33168007 Mon Sep 17 00:00:00 2001 From: random-zebra Date: Mon, 19 Apr 2021 23:58:03 +0200 Subject: [PATCH 24/25] [Consensus] Serialize the whole scriptPayout for ProReg sign string --- src/evo/providertx.cpp | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/evo/providertx.cpp b/src/evo/providertx.cpp index 0b6a7b90d7f1..9f3b6ff011c2 100644 --- a/src/evo/providertx.cpp +++ b/src/evo/providertx.cpp @@ -203,15 +203,8 @@ bool CheckProRegTx(const CTransaction& tx, const CBlockIndex* pindexPrev, CValid std::string ProRegPL::MakeSignString() const { std::ostringstream ss; - CTxDestination destPayout; - std::string strPayout; - if (ExtractDestination(scriptPayout, destPayout)) { - strPayout = EncodeDestination(destPayout); - } else { - strPayout = HexStr(scriptPayout.begin(), scriptPayout.end()); - } - ss << strPayout << "|"; + ss << HexStr(scriptPayout.begin(), scriptPayout.end()) << "|"; ss << strprintf("%d", nOperatorReward) << "|"; ss << EncodeDestination(keyIDOwner) << "|"; ss << EncodeDestination(keyIDVoting) << "|"; From 6dbdf3714dcd2064259a7c6b117f6333fd5e0fda Mon Sep 17 00:00:00 2001 From: random-zebra Date: Tue, 20 Apr 2021 00:07:30 +0200 Subject: [PATCH 25/25] [Consensus] Serialize also shield inputs for ProReg inputshash --- src/evo/specialtx.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/evo/specialtx.cpp b/src/evo/specialtx.cpp index 7051bdd38268..b028f4ac9870 100644 --- a/src/evo/specialtx.cpp +++ b/src/evo/specialtx.cpp @@ -142,8 +142,15 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex) uint256 CalcTxInputsHash(const CTransaction& tx) { CHashWriter hw(CLIENT_VERSION, SER_GETHASH); - for (const auto& in : tx.vin) { + // transparent inputs + for (const CTxIn& in: tx.vin) { hw << in.prevout; } + // shield inputs + if (tx.hasSaplingData()) { + for (const SpendDescription& sd: tx.sapData->vShieldedSpend) { + hw << sd.nullifier; + } + } return hw.GetHash(); }