sparta/include/PatriciaTreeMap.h (960 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <algorithm> #include <atomic> #include <cstdint> #include <functional> #include <iterator> #include <limits> #include <ostream> #include <stack> #include <type_traits> #include <utility> #include <boost/intrusive_ptr.hpp> #include "AbstractDomain.h" #include "PatriciaTreeUtil.h" // Forward declarations namespace sparta { template <typename Key, typename ValueType, typename Value> class PatriciaTreeMap; } // namespace sparta template <typename Key, typename ValueType, typename Value> std::ostream& operator<<( std::ostream&, const typename sparta::PatriciaTreeMap<Key, ValueType, Value>&); namespace sparta { // Forward declarations. namespace ptmap_impl { template <typename IntegerType, typename Value> class PatriciaTree; template <typename IntegerType, typename Value> class PatriciaTreeLeaf; template <typename IntegerType, typename Value> class PatriciaTreeIterator; template <typename T> using CombiningFunction = std::function<T(const T&, const T&)>; template <typename Value> using MappingFunction = std::function<Value(const Value&)>; template <typename IntegerType, typename Value> inline const typename Value::type* find_value( IntegerType key, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree); template <typename IntegerType, typename Value> inline bool leq( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree1, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree2); template <typename IntegerType, typename Value> inline bool equals( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree1, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree2); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> combine_new_leaf( const CombiningFunction<typename Value::type>& combine, IntegerType key, const typename Value::type& value); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> update( const CombiningFunction<typename Value::type>& combine, IntegerType key, const typename Value::type& value, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> map( const MappingFunction<typename Value::type>& f, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> erase_all_matching( IntegerType key_mask, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> merge( const CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> intersect( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t); template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> diff( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t); template <typename T> T snd(const T&, const T& second) { return second; } /* * Convenience interface that makes it easy to define maps for value types that * are default-constructible and equality-comparable. */ template <typename T> struct SimpleValue { using type = T; static T default_value() { return T(); } static bool is_default_value(const T& t) { return t == T(); } static bool equals(const T& a, const T& b) { return a == b; } }; } // namespace ptmap_impl /* * This structure implements a map of integer/pointer keys and AbstractDomain * values. It's based on the following paper: * * C. Okasaki, A. Gill. Fast Mergeable Integer Maps. In Workshop on ML (1998). * * See PatriciaTreeSet.h for more details about Patricia trees. * * This implementation differs from the paper in that we allow for a special * default value, which is never explicitly represented in the map. When using * Patricia tree maps with AbstractDomain values, this allows us to better * optimize operations like meet, join, and leq. It also makes it easy for us to * save space by implicitly mapping all unbound keys to the default value. As a * consequence, the default value must be either Top or Bottom. * * Value is a structure that should contain the following components: * * struct Value { * // The type of elements used as values in the map. * using type = ...; * * // Returns the default value. * static type default_value(); * * // Tests whether a value is the default value. * static bool is_default_value(const type& x); * * // The equality predicate for values. * static bool equals(const type& x, const type& y); * * // A partial order relation over values. In order to use the lifted * // partial order relation over maps PatriciaTreeMap::leq(), this method * // must be implemented. Additionally, value::type must be an * // implementation of an AbstractDomain. * static bool leq(const type& x, const type& y); * } * * Patricia trees can only handle unsigned integers. Arbitrary objects can be * accommodated as long as they are represented as pointers. Our implementation * of Patricia-tree maps can transparently operate on keys that are either * unsigned integers or pointers to objects. */ template <typename Key, typename ValueType, typename Value = ptmap_impl::SimpleValue<ValueType>> class PatriciaTreeMap final { public: // C++ container concept member types using key_type = Key; using mapped_type = typename Value::type; using value_type = std::pair<const Key, mapped_type>; using iterator = ptmap_impl::PatriciaTreeIterator<Key, Value>; using const_iterator = iterator; using difference_type = std::ptrdiff_t; using size_type = size_t; using const_reference = const mapped_type&; using const_pointer = const mapped_type*; using IntegerType = typename std::conditional_t<std::is_pointer<Key>::value, uintptr_t, Key>; using combining_function = ptmap_impl::CombiningFunction<mapped_type>; using mapping_function = ptmap_impl::MappingFunction<mapped_type>; ~PatriciaTreeMap() { // The destructor is the only method that is guaranteed to be created when a // class template is instantiated. This is a good place to perform all the // sanity checks on the template parameters. static_assert(std::is_same<ValueType, typename Value::type>::value, "ValueType must be equal to Value::type"); static_assert( std::is_same<decltype(Value::default_value()), mapped_type>::value, "Value::default_value() does not exist"); static_assert(std::is_same<decltype(Value::is_default_value( std::declval<mapped_type>())), bool>::value, "Value::is_default_value() does not exist"); static_assert( std::is_same<decltype(Value::equals(std::declval<mapped_type>(), std::declval<mapped_type>())), bool>::value, "Value::equals() does not exist"); } bool empty() const { return m_tree == nullptr; } size_t size() const { size_t s = 0; std::for_each(begin(), end(), [&s](const auto&) { ++s; }); return s; } size_t max_size() const { return std::numeric_limits<IntegerType>::max(); } iterator begin() const { return iterator(m_tree); } iterator end() const { return iterator(); } const mapped_type& at(Key key) const { const mapped_type* value = ptmap_impl::find_value(encode(key), m_tree); if (value == nullptr) { static const mapped_type default_value = Value::default_value(); return default_value; } return *value; } bool leq(const PatriciaTreeMap& other) const { static_assert(!std::is_same<decltype(Value::leq( std::declval<typename Value::type>(), std::declval<typename Value::type>())), bool>::value || std::is_base_of<AbstractDomain<typename Value::type>, typename Value::type>::value, "Value::leq() is defined, but Value::type is not an " "implementation of AbstractDomain"); return ptmap_impl::leq<IntegerType>(m_tree, other.m_tree); } bool equals(const PatriciaTreeMap& other) const { return ptmap_impl::equals<IntegerType>(m_tree, other.m_tree); } friend bool operator==(const PatriciaTreeMap& m1, const PatriciaTreeMap& m2) { return m1.equals(m2); } friend bool operator!=(const PatriciaTreeMap& m1, const PatriciaTreeMap& m2) { return !m1.equals(m2); } /* * This faster equality predicate can be used to check whether a sequence of * in-place modifications leaves a Patricia-tree map unchanged. For comparing * two arbitrary Patricia-tree maps, one needs to use the `equals()` * predicate. * * Example: * * PatriciaTreeMap<...> m1, m2; * ... * m2 = m1; * m2.union_with(...); * m2.update(...); * m2.intersection_with(...); * if (m2.reference_equals(m1)) { // This is equivalent to m2.equals(m1) * ... * } */ bool reference_equals(const PatriciaTreeMap& other) const { return m_tree == other.m_tree; } PatriciaTreeMap& update( const std::function<mapped_type(const mapped_type&)>& operation, Key key) { m_tree = ptmap_impl::update<IntegerType, Value>( [&operation](const mapped_type& x, const mapped_type&) { return operation(x); }, encode(key), Value::default_value(), m_tree); return *this; } bool map(const mapping_function& f) { auto new_tree = ptmap_impl::map<IntegerType, Value>(f, m_tree); bool res = new_tree != m_tree; m_tree = new_tree; return res; } bool erase_all_matching(Key key_mask) { auto new_tree = ptmap_impl::erase_all_matching<IntegerType, Value>( encode(key_mask), m_tree); bool res = new_tree != m_tree; m_tree = new_tree; return res; } PatriciaTreeMap& insert_or_assign(Key key, const mapped_type& value) { m_tree = ptmap_impl::update<IntegerType, Value>( ptmap_impl::snd<mapped_type>, encode(key), value, m_tree); return *this; } PatriciaTreeMap& union_with(const combining_function& combine, const PatriciaTreeMap& other) { m_tree = ptmap_impl::merge<IntegerType, Value>(combine, m_tree, other.m_tree); return *this; } PatriciaTreeMap& intersection_with(const combining_function& combine, const PatriciaTreeMap& other) { m_tree = ptmap_impl::intersect<IntegerType, Value>( combine, m_tree, other.m_tree); return *this; } // Requires that `combine(bottom, ...) = bottom`. PatriciaTreeMap& difference_with(const combining_function& combine, const PatriciaTreeMap& other) { m_tree = ptmap_impl::diff<IntegerType, Value>(combine, m_tree, other.m_tree); return *this; } PatriciaTreeMap get_union_with(const combining_function& combine, const PatriciaTreeMap& other) const { auto result = *this; result.union_with(combine, other); return result; } PatriciaTreeMap get_intersection_with(const combining_function& combine, const PatriciaTreeMap& other) const { auto result = *this; result.intersection_with(combine, other); return result; } PatriciaTreeMap get_difference_with(const combining_function& combine, const PatriciaTreeMap& other) const { auto result = *this; result.difference_with(combine, other); return result; } void clear() { m_tree.reset(); } private: // These functions are used to handle the type conversions required when // manipulating maps with pointer keys. The first parameter is necessary to // make template deduction work. template <typename T = Key, typename std::enable_if_t<std::is_pointer<T>::value, int> = 0> static uintptr_t encode(Key x) { return reinterpret_cast<uintptr_t>(x); } template <typename T = Key, typename std::enable_if_t<!std::is_pointer<T>::value, int> = 0> static Key encode(Key x) { return x; } template <typename T = Key, typename std::enable_if_t<std::is_pointer<T>::value, int> = 0> static Key decode(uintptr_t x) { return reinterpret_cast<Key>(x); } template <typename T = Key, typename std::enable_if_t<!std::is_pointer<T>::value, int> = 0> static Key decode(Key x) { return x; } template <typename T = Key, typename std::enable_if_t<std::is_pointer<T>::value, int> = 0> static const typename std::remove_pointer<T>::type& deref(Key x) { return *x; } template <typename T = Key, typename std::enable_if_t<!std::is_pointer<T>::value, int> = 0> static Key deref(Key x) { return x; } boost::intrusive_ptr<ptmap_impl::PatriciaTree<IntegerType, Value>> m_tree; template <typename T, typename VT, typename V> friend std::ostream& ::operator<<(std::ostream&, const PatriciaTreeMap<T, VT, V>&); template <typename T, typename V> friend class ptmap_impl::PatriciaTreeIterator; }; } // namespace sparta template <typename Key, typename ValueType, typename Value> inline std::ostream& operator<<( std::ostream& o, const typename sparta::PatriciaTreeMap<Key, ValueType, Value>& s) { using namespace sparta; o << "{"; for (auto it = s.begin(); it != s.end(); ++it) { o << PatriciaTreeMap<Key, ValueType, Value>::deref(it->first) << " -> " << it->second; if (std::next(it) != s.end()) { o << ", "; } } o << "}"; return o; } namespace sparta { namespace ptmap_impl { using namespace pt_util; template <typename IntegerType, typename Value> class PatriciaTree { public: // A Patricia tree is an immutable structure. PatriciaTree& operator=(const PatriciaTree& other) = delete; virtual ~PatriciaTree() { // The destructor is the only method that is guaranteed to be created when // a class template is instantiated. This is a good place to perform all // the sanity checks on the template parameters. static_assert(std::is_unsigned<IntegerType>::value, "IntegerType is not an unsigned arihmetic type"); } virtual bool is_leaf() const = 0; bool is_branch() const { return !is_leaf(); } friend void intrusive_ptr_add_ref(const PatriciaTree<IntegerType, Value>* p) { p->m_reference_count.fetch_add(1, std::memory_order_relaxed); } friend void intrusive_ptr_release(const PatriciaTree<IntegerType, Value>* p) { if (p->m_reference_count.fetch_sub(1, std::memory_order_release) == 1) { std::atomic_thread_fence(std::memory_order_acquire); delete p; } } private: mutable std::atomic<size_t> m_reference_count{0}; }; template <typename IntegerType, typename Value> class PatriciaTreeBranch final : public PatriciaTree<IntegerType, Value> { public: PatriciaTreeBranch( IntegerType prefix, IntegerType branching_bit, boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> left_tree, boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> right_tree) : m_prefix(prefix), m_stacking_bit(branching_bit), m_left_tree(std::move(left_tree)), m_right_tree(std::move(right_tree)) {} bool is_leaf() const override { return false; } IntegerType prefix() const { return m_prefix; } IntegerType branching_bit() const { return m_stacking_bit; } const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& left_tree() const { return m_left_tree; } const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& right_tree() const { return m_right_tree; } static boost::intrusive_ptr<PatriciaTreeBranch<IntegerType, Value>> make( IntegerType prefix, IntegerType branching_bit, boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> left_tree, boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> right_tree) { return new PatriciaTreeBranch<IntegerType, Value>( prefix, branching_bit, std::move(left_tree), std::move(right_tree)); } private: IntegerType m_prefix; IntegerType m_stacking_bit; boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> m_left_tree; boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> m_right_tree; }; template <typename IntegerType, typename Value> class PatriciaTreeLeaf final : public PatriciaTree<IntegerType, Value> { public: using mapped_type = typename Value::type; explicit PatriciaTreeLeaf(IntegerType key, const mapped_type& value) : m_pair(key, value) {} bool is_leaf() const override { return true; } const IntegerType& key() const { return m_pair.first; } const mapped_type& value() const { return m_pair.second; } static boost::intrusive_ptr<PatriciaTreeLeaf<IntegerType, Value>> make( IntegerType key, const mapped_type& value) { return new PatriciaTreeLeaf<IntegerType, Value>(key, value); } private: std::pair<IntegerType, mapped_type> m_pair; template <typename T, typename V> friend class ptmap_impl::PatriciaTreeIterator; }; template <typename IntegerType, typename Value> boost::intrusive_ptr<PatriciaTreeBranch<IntegerType, Value>> join( IntegerType prefix0, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree0, IntegerType prefix1, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree1) { IntegerType m = get_branching_bit(prefix0, prefix1); if (is_zero_bit(prefix0, m)) { return PatriciaTreeBranch<IntegerType, Value>::make( mask(prefix0, m), m, tree0, tree1); } else { return PatriciaTreeBranch<IntegerType, Value>::make( mask(prefix0, m), m, tree1, tree0); } } // This function is used to prevent the creation of branch nodes with only one // child. template <typename IntegerType, typename Value> boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> make_branch( IntegerType prefix, IntegerType branching_bit, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& left_tree, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& right_tree) { if (left_tree == nullptr) { return right_tree; } if (right_tree == nullptr) { return left_tree; } return PatriciaTreeBranch<IntegerType, Value>::make( prefix, branching_bit, left_tree, right_tree); } // Tries to find the value corresponding to :key. Returns null if the key is // not present in :tree. template <typename IntegerType, typename Value> inline const typename Value::type* find_value( IntegerType key, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { if (tree == nullptr) { return nullptr; } if (tree->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree); if (key == leaf->key()) { return &leaf->value(); } return nullptr; } const auto& branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree); if (is_zero_bit(key, branch->branching_bit())) { return find_value(key, branch->left_tree()); } else { return find_value(key, branch->right_tree()); } } /* Assumes Value::default_value() is either Top or Bottom */ template <typename IntegerType, typename Value> inline bool leq( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t) { RUNTIME_CHECK(Value::default_value().is_top() || Value::default_value().is_bottom(), undefined_operation()); if (s == t) { // This condition allows the leq operation to run in sublinear time when // comparing Patricia trees that share some structure. return true; } if (s == nullptr) { return Value::default_value().is_bottom(); } if (t == nullptr) { return Value::default_value().is_top(); } if (s->is_leaf()) { const auto& s_leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(s); if (t->is_branch()) { // t has at least one non-default binding that s doesn't have. if (Value::default_value().is_top()) { // The non-default binding in t can never be <= Top. return false; } // Otherwise, find if t contains s. // The missing bindings in s are bound to Bottom in this case. Even if we // know t contains strictly more bindings than s, they all satisfy the leq // condition. For each key k in t but not in s, s[k] == Bottom <= t[k] // always hold. auto* t_value = find_value(s_leaf->key(), t); if (t_value == nullptr) { // Always false if default_value is Bottom, which we already assume. return false; } return Value::leq(s_leaf->value(), *t_value); } // Both nodes are leaves. s leq to t iff // key(s) == key(t) && value(s) <= value(t). const auto& t_leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); return s_leaf->key() == t_leaf->key() && Value::leq(s_leaf->value(), t_leaf->value()); } else if (t->is_leaf()) { // s has at least one non-default binding that t doesn't have. if (Value::default_value().is_bottom()) { // There exists a key such that s[key] != Bottom and t[key] == Bottom. return false; } const auto& t_leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); auto* s_value = find_value(t_leaf->key(), s); if (s_value == nullptr) { // Always false if default_value is Top, which we already assume. return false; } return Value::leq(*s_value, t_leaf->value()); } // Neither s nor t is a leaf. const auto& s_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(s); const auto& t_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(t); IntegerType m = s_branch->branching_bit(); IntegerType n = t_branch->branching_bit(); IntegerType p = s_branch->prefix(); IntegerType q = t_branch->prefix(); const auto& s0 = s_branch->left_tree(); const auto& s1 = s_branch->right_tree(); const auto& t0 = t_branch->left_tree(); const auto& t1 = t_branch->right_tree(); if (m == n && p == q) { // The two trees have the same prefix, compare each subtrees. return leq(s0, t0) && leq(s1, t1); } if (m < n && match_prefix(q, p, m)) { // The tree t only contains bindings present in a subtree of s, and s has // bindings not present in t. return Value::default_value().is_top() && leq(is_zero_bit(q, m) ? s0 : s1, t); } if (m > n && match_prefix(p, q, n)) { // The tree s only contains bindings present in a subtree of t, and t has // bindings not present in s. return Value::default_value().is_bottom() && leq(s, is_zero_bit(p, n) ? t0 : t1); } // s and t both have bindings that are not present in the other tree. return false; } // A Patricia tree is a canonical representation of the set of keys it contains. // Hence, set equality is equivalent to structural equality of Patricia trees. template <typename IntegerType, typename Value> inline bool equals( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree1, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree2) { if (tree1 == tree2) { // This conditions allows the equality test to run in sublinear time when // comparing Patricia trees that share some structure. return true; } if (tree1 == nullptr) { return tree2 == nullptr; } if (tree2 == nullptr) { return false; } if (tree1->is_leaf()) { if (tree2->is_branch()) { return false; } const auto& leaf1 = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree1); const auto& leaf2 = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree2); return leaf1->key() == leaf2->key() && Value::equals(leaf1->value(), leaf2->value()); } if (tree2->is_leaf()) { return false; } const auto& branch1 = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree1); const auto& branch2 = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree2); return branch1->prefix() == branch2->prefix() && branch1->branching_bit() == branch2->branching_bit() && equals(branch1->left_tree(), branch2->left_tree()) && equals(branch1->right_tree(), branch2->right_tree()); } // Finds the value corresponding to :key in the tree and replaces its bound // value with combine(bound_value, :value). Note that the existing value is // always the first parameter to :combine and the new value is the second. template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> update( const ptmap_impl::CombiningFunction<typename Value::type>& combine, IntegerType key, const typename Value::type& value, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { if (tree == nullptr) { return combine_new_leaf<IntegerType, Value>(combine, key, value); } if (tree->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree); if (key == leaf->key()) { return combine_leaf(combine, value, leaf); } auto new_leaf = combine_new_leaf<IntegerType, Value>(combine, key, value); if (new_leaf == nullptr) { return leaf; } return ptmap_impl::join<IntegerType, Value>( key, new_leaf, leaf->key(), leaf); } const auto& branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree); if (match_prefix(key, branch->prefix(), branch->branching_bit())) { if (is_zero_bit(key, branch->branching_bit())) { auto new_left_tree = update(combine, key, value, branch->left_tree()); if (new_left_tree == branch->left_tree()) { return branch; } return make_branch(branch->prefix(), branch->branching_bit(), new_left_tree, branch->right_tree()); } else { auto new_right_tree = update(combine, key, value, branch->right_tree()); if (new_right_tree == branch->right_tree()) { return branch; } return make_branch(branch->prefix(), branch->branching_bit(), branch->left_tree(), new_right_tree); } } auto new_leaf = combine_new_leaf<IntegerType, Value>(combine, key, value); if (new_leaf == nullptr) { return branch; } return ptmap_impl::join<IntegerType, Value>( key, new_leaf, branch->prefix(), branch); } // Maps all entries with non-default values, applying a given function. template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> map( const MappingFunction<typename Value::type>& f, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { if (tree == nullptr) { return nullptr; } if (tree->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree); auto new_value = f(leaf->value()); return combine_leaf(ptmap_impl::snd<typename Value::type>, new_value, leaf); } const auto& branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree); auto new_left_tree = map(f, branch->left_tree()); auto new_right_tree = map(f, branch->right_tree()); if (new_left_tree == branch->left_tree() && new_right_tree == branch->right_tree()) { return branch; } return make_branch( branch->prefix(), branch->branching_bit(), new_left_tree, new_right_tree); } // Erases all entries where keys and :key_mask share common bits. template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> erase_all_matching( IntegerType key_mask, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { if (tree == nullptr) { return nullptr; } if (tree->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(tree); if (key_mask & leaf->key()) { return nullptr; } return tree; } const auto& branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(tree); if (key_mask & branch->prefix()) { return nullptr; } if (key_mask < branch->branching_bit()) { return branch; } auto new_left_tree = erase_all_matching(key_mask, branch->left_tree()); auto new_right_tree = erase_all_matching(key_mask, branch->right_tree()); if (new_left_tree == branch->left_tree() && new_right_tree == branch->right_tree()) { return branch; } return make_branch( branch->prefix(), branch->branching_bit(), new_left_tree, new_right_tree); } // We keep the notations of the paper so as to make the implementation easier // to follow. template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> merge( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t) { if (s == t) { // This conditional is what allows the union operation to complete in // sublinear time when the operands share some structure. return s; } if (s == nullptr) { return t; } if (t == nullptr) { return s; } if (s->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(s); return update(combine, leaf->key(), leaf->value(), t); } if (t->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); return update(combine, leaf->key(), leaf->value(), s); } const auto& s_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(s); const auto& t_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(t); IntegerType m = s_branch->branching_bit(); IntegerType n = t_branch->branching_bit(); IntegerType p = s_branch->prefix(); IntegerType q = t_branch->prefix(); const auto& s0 = s_branch->left_tree(); const auto& s1 = s_branch->right_tree(); const auto& t0 = t_branch->left_tree(); const auto& t1 = t_branch->right_tree(); if (m == n && p == q) { // The two trees have the same prefix. We just merge the subtrees. auto new_left = merge(combine, s0, t0); auto new_right = merge(combine, s1, t1); if (new_left == s0 && new_right == s1) { return s; } if (new_left == t0 && new_right == t1) { return t; } return PatriciaTreeBranch<IntegerType, Value>::make( p, m, new_left, new_right); } if (m < n && match_prefix(q, p, m)) { // q contains p. Merge t with a subtree of s. if (is_zero_bit(q, m)) { auto new_left = merge(combine, s0, t); if (s0 == new_left) { return s; } return PatriciaTreeBranch<IntegerType, Value>::make(p, m, new_left, s1); } else { auto new_right = merge(combine, s1, t); if (s1 == new_right) { return s; } return PatriciaTreeBranch<IntegerType, Value>::make(p, m, s0, new_right); } } if (m > n && match_prefix(p, q, n)) { // p contains q. Merge s with a subtree of t. if (is_zero_bit(p, n)) { auto new_left = merge(combine, s, t0); if (t0 == new_left) { return t; } return PatriciaTreeBranch<IntegerType, Value>::make(q, n, new_left, t1); } else { auto new_right = merge(combine, s, t1); if (t1 == new_right) { return t; } return PatriciaTreeBranch<IntegerType, Value>::make(q, n, t0, new_right); } } // The prefixes disagree. return ptmap_impl::join(p, s, q, t); } // Combine :value with the value in :leaf with combine(:leaf, :value). template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> combine_leaf( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const typename Value::type& value, const boost::intrusive_ptr<PatriciaTreeLeaf<IntegerType, Value>>& leaf) { auto combined_value = combine(leaf->value(), value); if (Value::is_default_value(combined_value)) { return nullptr; } if (!Value::equals(combined_value, leaf->value())) { return PatriciaTreeLeaf<IntegerType, Value>::make(leaf->key(), combined_value); } return leaf; } // Create a new leaf with the default value and combine :value into it. template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> combine_new_leaf( const ptmap_impl::CombiningFunction<typename Value::type>& combine, IntegerType key, const typename Value::type& value) { auto new_leaf = boost::intrusive_ptr<PatriciaTreeLeaf<IntegerType, Value>>( PatriciaTreeLeaf<IntegerType, Value>::make(key, Value::default_value())); return combine_leaf(combine, value, new_leaf); } template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> intersect( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t) { if (s == t) { // This conditional is what allows the intersection operation to complete in // sublinear time when the operands share some structure. return s; } if (s == nullptr || t == nullptr) { return nullptr; } if (s->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(s); auto* value = find_value(leaf->key(), t); if (value == nullptr) { return nullptr; } return combine_leaf(combine, *value, leaf); } if (t->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); auto* value = find_value(leaf->key(), s); if (value == nullptr) { return nullptr; } return combine_leaf(combine, *value, leaf); } const auto& s_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(s); const auto& t_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(t); IntegerType m = s_branch->branching_bit(); IntegerType n = t_branch->branching_bit(); IntegerType p = s_branch->prefix(); IntegerType q = t_branch->prefix(); const auto& s0 = s_branch->left_tree(); const auto& s1 = s_branch->right_tree(); const auto& t0 = t_branch->left_tree(); const auto& t1 = t_branch->right_tree(); if (m == n && p == q) { // The two trees have the same prefix. We merge the intersection of the // corresponding subtrees. // // The subtrees don't have overlapping explicit values, but the combining // function will still be called to merge the elements in one tree with the // implicit default values in the other. return merge<IntegerType, Value>( [](const typename Value::type& x, const typename Value::type& y) -> typename Value::type { if (Value::is_default_value(x)) { return y; } if (Value::is_default_value(y)) { return x; } BOOST_THROW_EXCEPTION(internal_error() << error_msg("Malformed Patricia tree")); }, intersect(combine, s0, t0), intersect(combine, s1, t1)); } if (m < n && match_prefix(q, p, m)) { // q contains p. Intersect t with a subtree of s. return intersect(combine, is_zero_bit(q, m) ? s0 : s1, t); } if (m > n && match_prefix(p, q, n)) { // p contains q. Intersect s with a subtree of t. return intersect(combine, s, is_zero_bit(p, n) ? t0 : t1); } // The prefixes disagree. return nullptr; } template <typename IntegerType, typename Value> inline boost::intrusive_ptr<PatriciaTree<IntegerType, Value>> diff( const ptmap_impl::CombiningFunction<typename Value::type>& combine, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& s, const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& t) { if (s == t) { // This conditional is what allows the intersection operation to complete in // sublinear time when the operands share some structure. return nullptr; } if (s == nullptr) { return nullptr; } if (t == nullptr) { return s; } if (s->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(s); auto* value = find_value(leaf->key(), t); if (value == nullptr) { return s; } return combine_leaf(combine, *value, leaf); } if (t->is_leaf()) { const auto& leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); return update(combine, leaf->key(), leaf->value(), s); } const auto& s_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(s); const auto& t_branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(t); IntegerType m = s_branch->branching_bit(); IntegerType n = t_branch->branching_bit(); IntegerType p = s_branch->prefix(); IntegerType q = t_branch->prefix(); const auto& s0 = s_branch->left_tree(); const auto& s1 = s_branch->right_tree(); const auto& t0 = t_branch->left_tree(); const auto& t1 = t_branch->right_tree(); auto combine_separate_trees = [](const typename Value::type& x, const typename Value::type& y) -> typename Value::type { if (Value::is_default_value(x)) { return y; } if (Value::is_default_value(y)) { return x; } BOOST_THROW_EXCEPTION(internal_error() << error_msg("Malformed Patricia tree")); }; if (m == n && p == q) { // The two trees have the same prefix. We merge the difference of the // corresponding subtrees. auto new_left = diff(combine, s0, t0); auto new_right = diff(combine, s1, t1); if (new_left == s0 && new_right == s1) { return s; } return merge<IntegerType, Value>( combine_separate_trees, new_left, new_right); } if (m < n && match_prefix(q, p, m)) { // q contains p. Diff t with a subtree of s. if (is_zero_bit(q, m)) { auto new_left = diff(combine, s0, t); if (new_left == s0) { return s; } return merge<IntegerType, Value>(combine_separate_trees, new_left, s1); } else { auto new_right = diff(combine, s1, t); if (new_right == s1) { return s; } return merge<IntegerType, Value>(combine_separate_trees, s0, new_right); } } if (m > n && match_prefix(p, q, n)) { // p contains q. Diff s with a subtree of t. if (is_zero_bit(p, n)) { return diff(combine, s, t0); } else { return diff(combine, s, t1); } } // The prefixes disagree. return s; } // The iterator basically performs a post-order traversal of the tree, pausing // at each leaf. template <typename Key, typename Value> class PatriciaTreeIterator final { public: // C++ iterator concept member types using iterator_category = std::forward_iterator_tag; using mapped_type = typename Value::type; using value_type = std::pair<Key, mapped_type>; using difference_type = std::ptrdiff_t; using pointer = value_type*; using reference = const value_type&; using IntegerType = typename std::conditional_t<std::is_pointer<Key>::value, uintptr_t, Key>; PatriciaTreeIterator() {} explicit PatriciaTreeIterator( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { if (tree == nullptr) { return; } go_to_next_leaf(tree); } PatriciaTreeIterator& operator++() { // We disallow incrementing the end iterator. RUNTIME_CHECK(m_leaf != nullptr, undefined_operation()); if (m_stack.empty()) { // This means that we were on the rightmost leaf. We've reached the end of // the iteration. m_leaf = nullptr; return *this; } // Otherwise, we pop out a branch from the stack and move to the leftmost // leaf in its right-hand subtree. auto branch = m_stack.top(); m_stack.pop(); go_to_next_leaf(branch->right_tree()); return *this; } PatriciaTreeIterator operator++(int) { PatriciaTreeIterator retval = *this; ++(*this); return retval; } bool operator==(const PatriciaTreeIterator& other) const { // Note that there's no need to check the stack (it's just used to traverse // the tree). return m_leaf == other.m_leaf; } bool operator!=(const PatriciaTreeIterator& other) const { return !(*this == other); } const std::pair<Key, mapped_type>& operator*() const { return *reinterpret_cast<const std::pair<Key, mapped_type>*>( &m_leaf->m_pair); } const std::pair<Key, mapped_type>* operator->() const { return reinterpret_cast<const std::pair<Key, mapped_type>*>( &m_leaf->m_pair); } private: // The argument is never null. void go_to_next_leaf( const boost::intrusive_ptr<PatriciaTree<IntegerType, Value>>& tree) { auto t = tree; // We go to the leftmost leaf, storing the branches that we're traversing // on the stack. By definition of a Patricia tree, a branch node always // has two children, hence the leftmost leaf always exists. while (t->is_branch()) { auto branch = boost::static_pointer_cast<PatriciaTreeBranch<IntegerType, Value>>(t); m_stack.push(branch); t = branch->left_tree(); // A branch node always has two children. RUNTIME_CHECK(t != nullptr, internal_error()); } m_leaf = boost::static_pointer_cast<PatriciaTreeLeaf<IntegerType, Value>>(t); } std::stack<boost::intrusive_ptr<PatriciaTreeBranch<IntegerType, Value>>> m_stack; boost::intrusive_ptr<PatriciaTreeLeaf<IntegerType, Value>> m_leaf; }; } // namespace ptmap_impl } // namespace sparta