| // Copyright 2022 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifndef SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_ | |
| #define SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_ | |
| #include <algorithm> | |
| #include <array> | |
| #include <functional> | |
| #include <optional> | |
| #include <tuple> | |
| #include <utility> | |
| #include "src/tint/utils/containers/vector.h" | |
| #include "src/tint/utils/ice/ice.h" | |
| #include "src/tint/utils/math/hash.h" | |
| #include "src/tint/utils/math/math.h" | |
| #include "src/tint/utils/memory/aligned_storage.h" | |
| #include "src/tint/utils/rtti/traits.h" | |
| #include "src/utils/compiler.h" | |
| // This file implements a custom STL style container & iterator in a performant manner, using | |
| // C-style data access. It is not unexpected that -Wunsafe-buffer-usage triggers in this code, since | |
| // the type of dynamic access being used cannot be guaranteed to be safe via static analysis. | |
| // Attempting to change this code in simple ways to quiet these errors either a) negatively affects | |
| // the performance by introducing unneeded copes, or b) uses typing shenanigans to work around the | |
| // warning that other linters/analyses are unhappy with. | |
| namespace tint { | |
| /// HashmapKey wraps the comparator type for a Hashmap and Hashset. | |
| /// HashmapKey acts like a read-only `T`, but can be reassigned so long as the value is equivalent. | |
| /// @tparam T the key comparator type. | |
| /// @tparam HASH the hash function for the key type. | |
| /// @tparam EQUAL the equality function for the key type. | |
| template <typename T, typename HASH = Hasher<T>, typename EQUAL = std::equal_to<T>> | |
| class HashmapKey { | |
| T value_; | |
| public: | |
| /// Key is an alias to this templated class. | |
| using Key = HashmapKey<T, HASH, EQUAL>; | |
| /// Hash is an alias to the hash function for the key type. | |
| using Hash = HASH; | |
| /// Equal is an alias to the equality function for the key type. | |
| using Equal = EQUAL; | |
| /// KeyOf() returns @p key, so a HashmapKey can be used as the entry type for a Hashset. | |
| /// @param key the HashmapKey | |
| /// @return @p key | |
| static const Key& KeyOf(const Key& key) { return key; } | |
| /// Constructor using copied value. | |
| /// @param value the key value. | |
| HashmapKey(const T& value) : value_(value), hash(HASH{}(value_)) {} // NOLINT | |
| /// Constructor using moved value. | |
| /// @param value the key value. | |
| HashmapKey(T&& value) : value_(std::forward<T>(value)), hash(HASH{}(value_)) {} // NOLINT | |
| /// Constructor using pre-computed hash and copied value. | |
| /// @param hash_ the precomputed hash of @p value | |
| /// @param value the key value | |
| HashmapKey(HashCode hash_, const T& value) : value_(value), hash(hash_) {} | |
| /// Constructor using pre-computed hash and moved value. | |
| /// @param hash_ the precomputed hash of @p value | |
| /// @param value the key value | |
| HashmapKey(HashCode hash_, T&& value) : value_(std::forward<T>(value)), hash(hash_) {} | |
| /// Copy constructor | |
| HashmapKey(const HashmapKey&) = default; | |
| /// Move constructor | |
| HashmapKey(HashmapKey&&) = default; | |
| /// Destructor | |
| ~HashmapKey() = default; | |
| /// Copy-assignment operator. | |
| /// @note As a hashmap uses the HashmapKey for indexing, the new value *must* have the same hash | |
| /// value and be equal to this key. | |
| /// @param other the key to copy to this key. | |
| /// @return this HashmapKey. | |
| HashmapKey& operator=(const HashmapKey& other) { | |
| TINT_ASSERT(*this == other); | |
| value_ = other.Value(); | |
| return *this; | |
| } | |
| /// Move-assignment operator. | |
| /// @note As a hashmap uses the HashmapKey for indexing, the new value *must* have the same hash | |
| /// value and be equal to this key. | |
| /// @param other the key to move to this key. | |
| /// @return this HashmapKey. | |
| HashmapKey& operator=(HashmapKey&& other) { | |
| TINT_ASSERT(*this == other); | |
| value_ = std::move(other.Value()); | |
| return *this; | |
| } | |
| /// Equality operator | |
| /// @param other the other key. | |
| /// @return true if the hash and value of @p other are equal to this key. | |
| bool operator==(const HashmapKey& other) const { | |
| return hash == other.hash && EQUAL{}(value_, other.Value()); | |
| } | |
| /// Equality operator | |
| /// @param other the other key. | |
| /// @return true if the hash of other and value of @p other are equal to this key. | |
| template <typename RHS> | |
| bool operator==(const RHS& other) const { | |
| return hash == HASH{}(other) && EQUAL{}(value_, other); | |
| } | |
| /// @returns the value of the key | |
| const T& Value() const { return value_; } | |
| /// @returns the value of the key | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| operator const T&() const { return value_; } | |
| /// @returns the pointer to the value, or the value itself if T is a pointer. | |
| auto operator->() const { | |
| if constexpr (std::is_pointer_v<T>) { | |
| // operator-> is useless if the T is a pointer, so automatically unwrap a pointer. | |
| return value_; | |
| } else { | |
| return &value_; | |
| } | |
| } | |
| /// The hash of value | |
| const HashCode hash; | |
| }; | |
| /// Writes the HashmapKey to the stream. | |
| /// @param out the stream to write to | |
| /// @param key the HashmapKey to write | |
| /// @returns out so calls can be chained | |
| template <typename STREAM, typename T> | |
| requires(traits::IsOStream<STREAM>) | |
| auto& operator<<(STREAM& out, const HashmapKey<T>& key) { | |
| if constexpr (traits::HasOperatorShiftLeft<STREAM, T>) { | |
| return out << key.Value(); | |
| } else { | |
| return out << "<hashmap-key>"; | |
| } | |
| } | |
| /// HashmapBase is the base class for Hashmap and Hashset. | |
| /// @tparam ENTRY is the single record in the map. The entry type must alias 'Key' to the HashmapKey | |
| /// type, and implement the method `static HashmapKey<...> KeyOf(ENTRY)` to return the key for the | |
| /// entry. | |
| template <typename ENTRY, size_t N> | |
| class HashmapBase { | |
| protected: | |
| struct Node; | |
| struct Slot; | |
| public: | |
| /// Entry is the type of a single record in the hashmap. | |
| using Entry = ENTRY; | |
| /// Key is the HashmapKey type used to find entries. | |
| using Key = typename Entry::Key; | |
| /// Hash is the | |
| using Hash = typename Key::Hash; | |
| /// Equal is the | |
| using Equal = typename Key::Equal; | |
| /// The minimum capacity of the map. | |
| static constexpr size_t kMinCapacity = std::max<size_t>(N, 8); | |
| /// The target number of slots, expressed as a fractional percentage of the map capacity. | |
| /// e.g. a kLoadFactor of 75, would mean a target slots count of (0.75 * capacity). | |
| static constexpr size_t kLoadFactor = 75; | |
| /// @param capacity the capacity of the map, as total number of entries. | |
| /// @returns the target slot vector size to hold @p capacity map entries. | |
| static constexpr size_t NumSlots(size_t capacity) { | |
| return (std::max<size_t>(capacity, kMinCapacity) * kLoadFactor) / 100; | |
| } | |
| /// Constructor. | |
| /// Constructs an empty map. | |
| HashmapBase() { | |
| slots_.Resize(slots_.Capacity()); | |
| for (auto& node : fixed_) { | |
| free_.Add(&node); | |
| } | |
| } | |
| /// Copy constructor. | |
| /// Constructs a map with a copy of @p other. | |
| /// @param other the map to copy. | |
| HashmapBase(const HashmapBase& other) : HashmapBase() { | |
| if (&other != this) { | |
| Copy(other); | |
| } | |
| } | |
| /// Move constructor. | |
| /// Constructs a map with the moved entries of @p other. | |
| /// @param other the map to move. | |
| HashmapBase(HashmapBase&& other) : HashmapBase() { | |
| if (&other != this) { | |
| Move(std::move(other)); | |
| } | |
| } | |
| /// Destructor. | |
| ~HashmapBase() { | |
| // Call the destructor on all entries in the map. | |
| for (size_t slot_idx = 0; slot_idx < slots_.Length(); slot_idx++) { | |
| auto* node = slots_[slot_idx].nodes; | |
| while (node) { | |
| auto next = node->next; | |
| node->Destroy(); | |
| node = next; | |
| } | |
| } | |
| } | |
| /// Assignment operator. | |
| /// Clears this map, and populates this map with a copy of @p other. | |
| /// @param other the map to copy. | |
| /// @returns this HashmapBase | |
| HashmapBase& operator=(const HashmapBase& other) { | |
| if (&other != this) { | |
| Clear(); | |
| Copy(other); | |
| } | |
| return *this; | |
| } | |
| /// Move-assignment operator. | |
| /// Clears this map, and populates this map with the moved entries of @p other. | |
| /// @param other the map to move. | |
| /// @returns this HashmapBase | |
| HashmapBase& operator=(HashmapBase&& other) { | |
| if (&other != this) { | |
| Clear(); | |
| Move(std::move(other)); | |
| } | |
| return *this; | |
| } | |
| /// @returns the number of entries in the map. | |
| size_t Count() const { return count_; } | |
| /// @returns true if the map holds no entries. | |
| bool IsEmpty() const { return count_ == 0; } | |
| /// Removes all the entries from the map. | |
| /// @note the map's capacity is not reduced, as it is assumed that a reused map will likely fill | |
| /// to a similar size as before. | |
| void Clear() { | |
| for (size_t slot_idx = 0; slot_idx < slots_.Length(); slot_idx++) { | |
| auto* node = slots_[slot_idx].nodes; | |
| while (node) { | |
| auto next = node->next; | |
| node->Destroy(); | |
| free_.Add(node); | |
| node = next; | |
| } | |
| slots_[slot_idx].nodes = nullptr; | |
| } | |
| count_ = 0; | |
| } | |
| /// Ensures that the map can hold @p n entries without heap reallocation or rehashing. | |
| /// @param n the number of entries to ensure can fit in the map without reallocation or | |
| /// rehashing. | |
| void Reserve(size_t n) { | |
| if (n > capacity_) { | |
| size_t count = n - capacity_; | |
| free_.Allocate(count); | |
| capacity_ += count; | |
| } | |
| } | |
| /// Looks up an entry with the given key. | |
| /// @param key the entry's key to search for. | |
| /// @returns a pointer to the matching entry, or null if no entry was found. | |
| /// @note The returned pointer is guaranteed to be valid until the owning entry is removed, | |
| /// the map is cleared, or the map is destructed. | |
| template <typename K> | |
| Entry* GetEntry(K&& key) { | |
| HashCode hash = Hash{}(key); | |
| auto& slot = slots_[hash % slots_.Length()]; | |
| return slot.Find(hash, key); | |
| } | |
| /// Looks up an entry with the given key. | |
| /// @param key the entry's key to search for. | |
| /// @returns a pointer to the matching entry, or null if no entry was found. | |
| /// @note The returned pointer is guaranteed to be valid until the owning entry is removed, | |
| /// the map is cleared, or the map is destructed. | |
| template <typename K> | |
| const Entry* GetEntry(K&& key) const { | |
| HashCode hash = Hash{}(key); | |
| auto& slot = slots_[hash % slots_.Length()]; | |
| return slot.Find(hash, key); | |
| } | |
| /// @returns true if the map contains an entry with a key that matches @p key. | |
| /// @param key the key to look for. | |
| template <typename K = Key> | |
| bool Contains(K&& key) const { | |
| return GetEntry(key) != nullptr; | |
| } | |
| /// Removes an entry from the map that has a key which matches @p key. | |
| /// @returns true if the entry was found and removed, otherwise false. | |
| /// @param key the key to look for. | |
| template <typename K = Key> | |
| bool Remove(K&& key) { | |
| HashCode hash = Hash{}(key); | |
| auto& slot = slots_[hash % slots_.Length()]; | |
| Node** edge = &slot.nodes; | |
| for (auto* node = *edge; node; node = node->next) { | |
| if (node->Equals(hash, key)) { | |
| *edge = node->next; | |
| node->Destroy(); | |
| free_.Add(node); | |
| count_--; | |
| return true; | |
| } | |
| edge = &node->next; | |
| } | |
| return false; | |
| } | |
| /// Iterator for entries in the map. | |
| template <bool IS_CONST> | |
| class IteratorT { | |
| private: | |
| using MAP = std::conditional_t<IS_CONST, const HashmapBase, HashmapBase>; | |
| using NODE = std::conditional_t<IS_CONST, const Node, Node>; | |
| public: | |
| /// @returns the entry pointed to by this iterator | |
| auto& operator->() { return node_->Entry(); } | |
| /// @returns a reference to the entry at the iterator | |
| auto& operator*() { return node_->Entry(); } | |
| /// Increments the iterator | |
| /// @returns this iterator | |
| IteratorT& operator++() { | |
| node_ = node_->next; | |
| SkipEmptySlots(); | |
| return *this; | |
| } | |
| /// Equality operator | |
| /// @param other the other iterator to compare this iterator to | |
| /// @returns true if this iterator is equal to other | |
| bool operator==(const IteratorT& other) const { return node_ == other.node_; } | |
| /// Inequality operator | |
| /// @param other the other iterator to compare this iterator to | |
| /// @returns true if this iterator is not equal to other | |
| bool operator!=(const IteratorT& other) const { return node_ != other.node_; } | |
| private: | |
| /// Friend class | |
| friend class HashmapBase; | |
| IteratorT(MAP& map, size_t slot, NODE* node) : map_(map), slot_(slot), node_(node) { | |
| SkipEmptySlots(); | |
| } | |
| void SkipEmptySlots() { | |
| while (!node_ && slot_ + 1 < map_.slots_.Length()) { | |
| node_ = map_.slots_[++slot_].nodes; | |
| } | |
| } | |
| MAP& map_; | |
| size_t slot_ = 0; | |
| NODE* node_ = nullptr; | |
| }; | |
| /// An immutable key and mutable value iterator | |
| using Iterator = IteratorT</*IS_CONST*/ false>; | |
| /// An immutable key and value iterator | |
| using ConstIterator = IteratorT</*IS_CONST*/ true>; | |
| /// @returns an immutable iterator to the start of the map. | |
| ConstIterator begin() const { return ConstIterator{*this, 0, slots_.Front().nodes}; } | |
| /// @returns an immutable iterator to the end of the map. | |
| ConstIterator end() const { return ConstIterator{*this, slots_.Length(), nullptr}; } | |
| /// @returns an iterator to the start of the map. | |
| Iterator begin() { return Iterator{*this, 0, slots_.Front().nodes}; } | |
| /// @returns an iterator to the end of the map. | |
| Iterator end() { return Iterator{*this, slots_.Length(), nullptr}; } | |
| /// STL-friendly alias to Entry. Used by gmock. | |
| using value_type = const Entry&; | |
| protected: | |
| /// Node holds an Entry in a linked list. | |
| struct Node { | |
| /// Destructs the entry. | |
| void Destroy() { Entry().~ENTRY(); } | |
| /// @returns the storage reinterpreted as an `Entry&` | |
| ENTRY& Entry() { return storage.Get(); } | |
| /// @returns the storage reinterpreted as a `const Entry&` | |
| const ENTRY& Entry() const { return storage.Get(); } | |
| /// @returns a reference to the Entry's HashmapKey | |
| const HashmapBase::Key& Key() const { return HashmapBase::Entry::KeyOf(Entry()); } | |
| /// @param hash the hash value to compare against the Entry's key hash value | |
| /// @param value the value to compare against the Entry's key | |
| /// @returns true if the Entry's hash is equal to @p hash, and the Entry's key is equal to | |
| /// @p value. | |
| template <typename T> | |
| bool Equals(HashCode hash, T&& value) const { | |
| auto& key = Key(); | |
| return key.hash == hash && HashmapBase::Equal{}(key.Value(), value); | |
| } | |
| /// storage is a buffer that has the same size and alignment as Entry. | |
| /// The storage holds a constructed Entry when linked in the slots, and is destructed when | |
| /// removed from slots. | |
| AlignedStorage<ENTRY> storage; | |
| /// next is the next Node in the slot, or in the free list. | |
| Node* next; | |
| }; | |
| /// Copies the hashmap @p other into this empty hashmap. | |
| /// @note This hashmap must be empty before calling | |
| /// @param other the hashmap to copy | |
| void Copy(const HashmapBase& other) { | |
| Reserve(other.capacity_); | |
| slots_.Resize(other.slots_.Length()); | |
| for (size_t slot_idx = 0; slot_idx < slots_.Length(); slot_idx++) { | |
| for (auto* o = other.slots_[slot_idx].nodes; o; o = o->next) { | |
| auto* node = free_.Take(); | |
| new (&node->Entry()) Entry{o->Entry()}; | |
| slots_[slot_idx].Add(node); | |
| } | |
| } | |
| count_ = other.count_; | |
| } | |
| /// Moves the hashmap @p other into this empty hashmap. | |
| /// @note This hashmap must be empty before calling | |
| /// @param other the hashmap to move | |
| void Move(HashmapBase&& other) { | |
| Reserve(other.capacity_); | |
| slots_.Resize(other.slots_.Length()); | |
| for (size_t slot_idx = 0; slot_idx < slots_.Length(); slot_idx++) { | |
| for (auto* o = other.slots_[slot_idx].nodes; o; o = o->next) { | |
| auto* node = free_.Take(); | |
| new (&node->Entry()) Entry{std::move(o->Entry())}; | |
| slots_[slot_idx].Add(node); | |
| } | |
| } | |
| count_ = other.count_; | |
| other.Clear(); | |
| } | |
| /// EditIndex is the structure returned by EditAt(), used to simplify entry replacement and | |
| /// insertion. | |
| struct EditIndex { | |
| /// The HashmapBase that created this EditIndex | |
| HashmapBase& map; | |
| /// The slot that will hold the edit. | |
| Slot& slot; | |
| /// The hash of the key, passed to EditAt(). | |
| HashCode hash = 0; | |
| /// The resolved node entry, or nullptr if EditAt() did not resolve to an existing entry. | |
| Entry* entry = nullptr; | |
| /// Replace will replace the entry with a new Entry built from @p key and @p values. | |
| /// @note #entry must not be null before calling. | |
| /// @note the new key must have equality to the old key. | |
| /// @param key the key value (inner value of a HashmapKey). | |
| /// @param values optional additional values to pass to the Entry constructor. | |
| template <typename K, typename... V> | |
| void Replace(K&& key, V&&... values) { | |
| *entry = Entry{Key{hash, std::forward<K>(key)}, std::forward<V>(values)...}; | |
| } | |
| /// Insert will create a new entry using @p key and @p values and insert it into the slot. | |
| /// The created entry will be assigned to #entry before returning. | |
| /// @note #entry must be null before calling. | |
| /// @note the key must not already exist in the map. | |
| /// @param key the key value (inner value of a HashmapKey). | |
| /// @param values optional additional values to pass to the Entry constructor. | |
| template <typename K, typename... V> | |
| void Insert(K&& key, V&&... values) { | |
| auto* node = map.free_.Take(); | |
| slot.Add(node); | |
| map.count_++; | |
| entry = &node->Entry(); | |
| new (entry) Entry{Key{hash, std::forward<K>(key)}, std::forward<V>(values)...}; | |
| } | |
| }; | |
| /// EditAt is a helper for map entry replacement and entry insertion. | |
| /// Before indexing, EditAt will ensure there's at least one free node available, potentially | |
| /// allocating and rehashing if there's no free nodes available. | |
| /// @param key the key used to compute the hash, look up the slot and search for the existing | |
| /// node. | |
| /// @returns a EditIndex used to modify or insert a new entry into the map with the given key. | |
| template <typename K> | |
| EditIndex EditAt(K&& key) { | |
| if (!free_.nodes_) { | |
| free_.Allocate(capacity_); | |
| capacity_ += capacity_; | |
| Rehash(); | |
| } | |
| HashCode hash = Hash{}(key); | |
| auto& slot = slots_[hash % slots_.Length()]; | |
| auto* entry = slot.Find(hash, key); | |
| return {*this, slot, hash, entry}; | |
| } | |
| /// Rehash resizes the slots vector proportionally to the map capacity, and then reinserts the | |
| /// nodes so they're linked in the correct slots linked lists. | |
| void Rehash() { | |
| size_t num_slots = NumSlots(capacity_); | |
| decltype(slots_) old_slots; | |
| std::swap(slots_, old_slots); | |
| slots_.Resize(num_slots); | |
| for (size_t old_slot_idx = 0; old_slot_idx < old_slots.Length(); old_slot_idx++) { | |
| auto* node = old_slots[old_slot_idx].nodes; | |
| while (node) { | |
| auto next = node->next; | |
| size_t new_slot_idx = node->Key().hash % num_slots; | |
| slots_[new_slot_idx].Add(node); | |
| node = next; | |
| } | |
| } | |
| } | |
| /// Slot holds a linked list of nodes. Nodes are assigned to the slot list by calculating the | |
| /// modulo of the entry's hash with the slot_ vector length. | |
| struct Slot { | |
| /// The linked list of nodes in this slot. | |
| Node* nodes = nullptr; | |
| /// Add adds the node @p node to this slot. | |
| /// @note The node must be unlinked from any existing list before calling. | |
| /// @param node the node to add. | |
| void Add(Node* node) { | |
| node->next = nodes; | |
| nodes = node; | |
| } | |
| /// @returns the node in the slot with the given hash and key. | |
| /// @param hash the key hash to search for. | |
| /// @param key the key value to search for. | |
| template <typename K> | |
| const Entry* Find(HashCode hash, K&& key) const { | |
| for (auto* node = nodes; node; node = node->next) { | |
| if (node->Equals(hash, key)) { | |
| return &node->Entry(); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| /// @returns the node in the slot with the given hash and key. | |
| /// @param hash the key hash to search for. | |
| /// @param key the key value to search for. | |
| template <typename K> | |
| Entry* Find(HashCode hash, K&& key) { | |
| for (auto* node = nodes; node; node = node->next) { | |
| if (node->Equals(hash, key)) { | |
| return &node->Entry(); | |
| } | |
| } | |
| return nullptr; | |
| } | |
| }; | |
| /// Free holds a linked list of nodes which are currently not used by entries in the map, and a | |
| /// linked list of node allocations. | |
| struct FreeNodes { | |
| /// Allocation is the header of a block of memory that holds Nodes. | |
| struct Allocation { | |
| /// The linked list of allocations. | |
| Allocation* next = nullptr; | |
| // Node[] array follows this structure. | |
| }; | |
| /// The linked list of free nodes. | |
| Node* nodes_ = nullptr; | |
| /// The linked list of allocations. | |
| Allocation* allocations_ = nullptr; | |
| /// Destructor. | |
| /// Frees all the allocations made. | |
| ~FreeNodes() { | |
| auto* allocation = allocations_; | |
| while (allocation) { | |
| auto* next = allocation->next; | |
| free(allocation); | |
| allocation = next; | |
| } | |
| } | |
| /// @returns the next free node in the list | |
| Node* Take() { | |
| auto* node = nodes_; | |
| nodes_ = node->next; | |
| node->next = nullptr; | |
| return node; | |
| } | |
| /// Add adds the node @p node to the list of free nodes. | |
| /// @note The node must be unlinked from any existing list before calling. | |
| /// @param node the node to add. | |
| void Add(Node* node) { | |
| node->next = nodes_; | |
| nodes_ = node; | |
| } | |
| /// Allocate allocates an additional @p count nodes and adds them to the free node list. | |
| /// @param count the number of new nodes to allocate. | |
| /// @note callers must remember to increment HashmapBase::capacity_ by the same amount. | |
| void Allocate(size_t count) { | |
| static_assert(std::is_trivial_v<Node>, | |
| "Node is not trivial, and will require construction / destruction"); | |
| constexpr size_t kAllocationSize = RoundUp(alignof(Node), sizeof(Allocation)); | |
| auto* memory = | |
| reinterpret_cast<std::byte*>(malloc(kAllocationSize + sizeof(Node) * count)); | |
| TINT_ASSERT(memory) << "out of memory"; | |
| auto* nodes_allocation = Bitcast<Allocation*>(memory); | |
| nodes_allocation->next = allocations_; | |
| allocations_ = nodes_allocation; | |
| // SAFETY: memory is allocated to be kAllocationSize + space for count nodes, so memory | |
| // + kAllocationSize is the boundary of the nodes. If there are 0 nodes it will not be | |
| // used. | |
| auto* nodes = Bitcast<Node*>(DAWN_UNSAFE_BUFFERS(memory + kAllocationSize)); | |
| for (size_t i = 0; i < count; i++) { | |
| // SAFETY: nodes has an allocated size of at least `count` elements. | |
| Add(DAWN_UNSAFE_BUFFERS(&nodes[i])); | |
| } | |
| } | |
| }; | |
| /// The fixed-size array of nodes, used for the first kMinCapacity entries of the map, before | |
| /// allocating from the heap. | |
| std::array<Node, kMinCapacity> fixed_; | |
| /// The vector of slots. Each slot holds a linked list of nodes which hold entries in the map. | |
| Vector<Slot, NumSlots(N)> slots_; | |
| /// The linked list of free nodes, and node allocations from the heap. | |
| FreeNodes free_; | |
| /// The total number of nodes, including free nodes (kMinCapacity + heap-allocated) | |
| size_t capacity_ = kMinCapacity; | |
| /// The total number of nodes that currently hold map entries. | |
| size_t count_ = 0; | |
| }; | |
| } // namespace tint | |
| #endif // SRC_TINT_UTILS_CONTAINERS_HASHMAP_BASE_H_ |