1 // Copyright 2021 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef SRC_GN_POINTER_SET_H_ 6 #define SRC_GN_POINTER_SET_H_ 7 8 #include <functional> 9 #include "gn/hash_table_base.h" 10 11 // PointerSet<T> is a fast implemention of a set of non-owning and non-null 12 // typed pointer values (of type T*). 13 // 14 // Note that this intentionally does not support a find() method 15 // for performance reasons, instead callers must use contains(), add() 16 // or erase() directly to perform lookups or conditional insertion/removal. 17 // 18 // Only constant iterators are provided. Moreover, the slightly faster 19 // iteration form can be used for performance as in: 20 // 21 // for (auto it = my_set.begin(); it.valid(); ++it) { 22 // T* ptr = *it; 23 // ... 24 // } 25 // 26 // Instead of: 27 // 28 // for (T* ptr : my_set) { 29 // ... 30 // } 31 // 32 // The PointerSetNode type implements the methods required by HashTableBase<> 33 // to store and hash a pointer directly in the buckets array. The special 34 // address 1 is used as the tombstone value to support removal. 35 // 36 // Null nodes are marked with an empty pointer, which means that nullptr 37 // itself cannot be stored in the set. 38 struct PointerSetNode { 39 const void* ptr_; is_nullPointerSetNode40 bool is_null() const { return !ptr_; } is_tombstonePointerSetNode41 bool is_tombstone() const { return ptr_ == MakeTombstone(); } is_validPointerSetNode42 bool is_valid() const { return !is_null() && !is_tombstone(); } hash_valuePointerSetNode43 size_t hash_value() const { return MakeHash(ptr_); } 44 45 // Return the tomebstone value. This could be a static constexpr value 46 // if C++ didn't complain about reinterpret_cast<> here. MakeTombstonePointerSetNode47 static const void* MakeTombstone() { 48 return reinterpret_cast<const void*>(1u); 49 } 50 51 // Return the hash corresponding to a given pointer. MakeHashPointerSetNode52 static size_t MakeHash(const void* ptr) { 53 return std::hash<const void*>()(ptr); 54 } 55 }; 56 57 template <typename T> 58 class PointerSet : public HashTableBase<PointerSetNode> { 59 public: 60 using NodeType = PointerSetNode; 61 using BaseType = HashTableBase<NodeType>; 62 63 PointerSet() = default; 64 65 // Allow copying pointer sets. PointerSet(const PointerSet & other)66 PointerSet(const PointerSet& other) : BaseType() { insert(other); } 67 PointerSet& operator=(const PointerSet& other) { 68 if (this != &other) { 69 this->~PointerSet(); 70 new (this) PointerSet(other); 71 } 72 return *this; 73 } 74 75 // Redefine move operators since the copy constructor above 76 // masks the base ones by default. PointerSet(PointerSet && other)77 PointerSet(PointerSet&& other) noexcept : BaseType(std::move(other)) {} 78 PointerSet& operator=(PointerSet&& other) noexcept { 79 if (this != &other) { 80 this->~PointerSet(); 81 new (this) PointerSet(std::move(other)); 82 } 83 return *this; 84 } 85 86 // Range constructor. 87 template <typename InputIter> PointerSet(InputIter first,InputIter last)88 PointerSet(InputIter first, InputIter last) { 89 for (; first != last; ++first) 90 add(*first); 91 } 92 93 // Clear the set. clear()94 void clear() { NodeClear(); } 95 96 // Add one pointer to the set. Return true if the pointer was 97 // added, or false if its was already in the set. add(T * ptr)98 bool add(T* ptr) { 99 NodeType* node = Lookup(ptr); 100 if (node->is_valid()) 101 return false; 102 103 node->ptr_ = ptr; 104 UpdateAfterInsert(); 105 return true; 106 } 107 108 // Return true if a given pointer is already in the set. contains(T * ptr)109 bool contains(T* ptr) const { return Lookup(ptr)->is_valid(); } 110 111 // Try to remove a pointer from the set. Return true in case of 112 // removal, or false if the pointer was not in the set. erase(T * ptr)113 bool erase(T* ptr) { 114 NodeType* node = Lookup(ptr); 115 if (!node->is_valid()) 116 return false; 117 node->ptr_ = node->MakeTombstone(); 118 UpdateAfterRemoval(); 119 return true; 120 } 121 122 // Same as add() but does not return a boolean. 123 // This minimizes code changes when PointerSet<> replaces 124 // other standard set types in the code. insert(T * ptr)125 void insert(T* ptr) { add(ptr); } 126 127 // Range insertion. 128 template <typename InputIter> insert(InputIter first,InputIter last)129 void insert(InputIter first, InputIter last) { 130 for (; first != last; ++first) 131 add(*first); 132 } 133 134 // Insert of aitems of |other| into the current set. This 135 // is slightly more efficient than using range insertion 136 // with insert(other.begin(), other.end()). insert(const PointerSet & other)137 void insert(const PointerSet& other) { 138 for (const_iterator iter = other.begin(); iter.valid(); ++iter) 139 add(*iter); 140 } 141 142 // Return a new set that is the intersection of the current one 143 // and |other|. intersection_with(const PointerSet & other)144 PointerSet intersection_with(const PointerSet& other) const { 145 PointerSet result; 146 for (const_iterator iter = other.begin(); iter.valid(); ++iter) { 147 if (contains(*iter)) 148 result.add(*iter); 149 } 150 return result; 151 } 152 153 // Only provide const interators for pointer sets. 154 struct const_iterator : public NodeIterator { 155 T* const* operator->() const { 156 return &const_cast<T*>(static_cast<const T*>(node_->ptr_)); 157 } 158 T* operator*() const { 159 return const_cast<T*>(static_cast<const T*>(node_->ptr_)); 160 } 161 162 // The following allows: 163 // std::vector<T*> vector(set.begin(), set.end()); 164 using iterator_category = std::forward_iterator_tag; 165 using difference_type = std::ptrdiff_t; 166 using value_type = T*; 167 using pointer = T**; 168 using reference = T*&; 169 }; 170 begin()171 const_iterator begin() const { return {NodeBegin()}; } end()172 const_iterator end() const { return {NodeEnd()}; } 173 174 // Only used for unit-tests so performance is not critical. 175 bool operator==(const PointerSet& other) const { 176 if (size() != other.size()) 177 return false; 178 179 for (const_iterator iter = begin(); iter.valid(); ++iter) 180 if (!other.contains(*iter)) 181 return false; 182 183 for (const_iterator iter = other.begin(); iter.valid(); ++iter) 184 if (!contains(*iter)) 185 return false; 186 187 return true; 188 } 189 190 private: 191 // Lookup node matching a given pointer. If result->valid() is true 192 // then the pointer was found, otherwise, this is the location of 193 // the best place to insert it. Lookup(T * ptr)194 NodeType* Lookup(T* ptr) const { 195 size_t hash = NodeType::MakeHash(ptr); 196 return NodeLookup(hash, [&](NodeType* node) { return node->ptr_ == ptr; }); 197 } 198 }; 199 200 #endif // SRC_GN_POINTER_SET_H_ 201