//===- sanitizer_dense_map.h - Dense probed hash table ----------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This is fork of llvm/ADT/DenseMap.h class with the following changes: // * Use mmap to allocate. // * No iterators. // * Does not shrink. // //===----------------------------------------------------------------------===// #ifndef SANITIZER_DENSE_MAP_H #define SANITIZER_DENSE_MAP_H #include "sanitizer_common.h" #include "sanitizer_dense_map_info.h" #include "sanitizer_internal_defs.h" #include "sanitizer_type_traits.h" namespace __sanitizer { template class DenseMapBase { public: using size_type = unsigned; using key_type = KeyT; using mapped_type = ValueT; using value_type = BucketT; WARN_UNUSED_RESULT bool empty() const { return getNumEntries() == 0; } unsigned size() const { return getNumEntries(); } /// Grow the densemap so that it can contain at least \p NumEntries items /// before resizing again. void reserve(size_type NumEntries) { auto NumBuckets = getMinBucketToReserveForEntries(NumEntries); if (NumBuckets > getNumBuckets()) grow(NumBuckets); } void clear() { if (getNumEntries() == 0 && getNumTombstones() == 0) return; const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); if (__sanitizer::is_trivially_destructible::value) { // Use a simpler loop when values don't need destruction. for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) P->getFirst() = EmptyKey; } else { unsigned NumEntries = getNumEntries(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey)) { if (!KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) { P->getSecond().~ValueT(); --NumEntries; } P->getFirst() = EmptyKey; } } CHECK_EQ(NumEntries, 0); } setNumEntries(0); setNumTombstones(0); } /// Return 1 if the specified key is in the map, 0 otherwise. size_type count(const KeyT &Key) const { const BucketT *TheBucket; return LookupBucketFor(Key, TheBucket) ? 1 : 0; } value_type *find(const KeyT &Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return TheBucket; return nullptr; } const value_type *find(const KeyT &Key) const { const BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return TheBucket; return nullptr; } /// Alternate version of find() which allows a different, and possibly /// less expensive, key type. /// The DenseMapInfo is responsible for supplying methods /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key /// type used. template value_type *find_as(const LookupKeyT &Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return TheBucket; return nullptr; } template const value_type *find_as(const LookupKeyT &Key) const { const BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return TheBucket; return nullptr; } /// lookup - Return the entry for the specified key, or a default /// constructed value if no such entry exists. ValueT lookup(const KeyT &Key) const { const BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return TheBucket->getSecond(); return ValueT(); } // Inserts key,value pair into the map if the key isn't already in the map. // If the key is already in the map, it returns false and doesn't update the // value. detail::DenseMapPair insert(const value_type &KV) { return try_emplace(KV.first, KV.second); } // Inserts key,value pair into the map if the key isn't already in the map. // If the key is already in the map, it returns false and doesn't update the // value. detail::DenseMapPair insert(value_type &&KV) { return try_emplace(__sanitizer::move(KV.first), __sanitizer::move(KV.second)); } // Inserts key,value pair into the map if the key isn't already in the map. // The value is constructed in-place if the key is not in the map, otherwise // it is not moved. template detail::DenseMapPair try_emplace(KeyT &&Key, Ts &&...Args) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return {TheBucket, false}; // Already in map. // Otherwise, insert the new element. TheBucket = InsertIntoBucket(TheBucket, __sanitizer::move(Key), __sanitizer::forward(Args)...); return {TheBucket, true}; } // Inserts key,value pair into the map if the key isn't already in the map. // The value is constructed in-place if the key is not in the map, otherwise // it is not moved. template detail::DenseMapPair try_emplace(const KeyT &Key, Ts &&...Args) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return {TheBucket, false}; // Already in map. // Otherwise, insert the new element. TheBucket = InsertIntoBucket(TheBucket, Key, __sanitizer::forward(Args)...); return {TheBucket, true}; } /// Alternate version of insert() which allows a different, and possibly /// less expensive, key type. /// The DenseMapInfo is responsible for supplying methods /// getHashValue(LookupKeyT) and isEqual(LookupKeyT, KeyT) for each key /// type used. template detail::DenseMapPair insert_as(value_type &&KV, const LookupKeyT &Val) { BucketT *TheBucket; if (LookupBucketFor(Val, TheBucket)) return {TheBucket, false}; // Already in map. // Otherwise, insert the new element. TheBucket = InsertIntoBucketWithLookup(TheBucket, __sanitizer::move(KV.first), __sanitizer::move(KV.second), Val); return {TheBucket, true}; } bool erase(const KeyT &Val) { BucketT *TheBucket; if (!LookupBucketFor(Val, TheBucket)) return false; // not in map. TheBucket->getSecond().~ValueT(); TheBucket->getFirst() = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); return true; } void erase(value_type *I) { CHECK_NE(I, nullptr); BucketT *TheBucket = &*I; TheBucket->getSecond().~ValueT(); TheBucket->getFirst() = getTombstoneKey(); decrementNumEntries(); incrementNumTombstones(); } value_type &FindAndConstruct(const KeyT &Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return *TheBucket; return *InsertIntoBucket(TheBucket, Key); } ValueT &operator[](const KeyT &Key) { return FindAndConstruct(Key).second; } value_type &FindAndConstruct(KeyT &&Key) { BucketT *TheBucket; if (LookupBucketFor(Key, TheBucket)) return *TheBucket; return *InsertIntoBucket(TheBucket, __sanitizer::move(Key)); } ValueT &operator[](KeyT &&Key) { return FindAndConstruct(__sanitizer::move(Key)).second; } /// Iterate over active entries of the container. /// /// Function can return fast to stop the process. template void forEach(Fn fn) { const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); for (auto *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { const KeyT K = P->getFirst(); if (!KeyInfoT::isEqual(K, EmptyKey) && !KeyInfoT::isEqual(K, TombstoneKey)) { if (!fn(*P)) return; } } } template void forEach(Fn fn) const { const_cast(this)->forEach( [&](const value_type &KV) { return fn(KV); }); } protected: DenseMapBase() = default; void destroyAll() { if (getNumBuckets() == 0) // Nothing to do. return; const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) { if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) && !KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) P->getSecond().~ValueT(); P->getFirst().~KeyT(); } } void initEmpty() { setNumEntries(0); setNumTombstones(0); CHECK_EQ((getNumBuckets() & (getNumBuckets() - 1)), 0); const KeyT EmptyKey = getEmptyKey(); for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B) ::new (&B->getFirst()) KeyT(EmptyKey); } /// Returns the number of buckets to allocate to ensure that the DenseMap can /// accommodate \p NumEntries without need to grow(). unsigned getMinBucketToReserveForEntries(unsigned NumEntries) { // Ensure that "NumEntries * 4 < NumBuckets * 3" if (NumEntries == 0) return 0; // +1 is required because of the strict equality. // For example if NumEntries is 48, we need to return 401. return RoundUpToPowerOfTwo((NumEntries * 4 / 3 + 1) + /* NextPowerOf2 */ 1); } void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) { initEmpty(); // Insert all the old elements. const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) { if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey) && !KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) { // Insert the key/value into the new table. BucketT *DestBucket; bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket); (void)FoundVal; // silence warning. CHECK(!FoundVal); DestBucket->getFirst() = __sanitizer::move(B->getFirst()); ::new (&DestBucket->getSecond()) ValueT(__sanitizer::move(B->getSecond())); incrementNumEntries(); // Free the value. B->getSecond().~ValueT(); } B->getFirst().~KeyT(); } } template void copyFrom( const DenseMapBase &other) { CHECK_NE(&other, this); CHECK_EQ(getNumBuckets(), other.getNumBuckets()); setNumEntries(other.getNumEntries()); setNumTombstones(other.getNumTombstones()); if (__sanitizer::is_trivially_copyable::value && __sanitizer::is_trivially_copyable::value) internal_memcpy(reinterpret_cast(getBuckets()), other.getBuckets(), getNumBuckets() * sizeof(BucketT)); else for (uptr i = 0; i < getNumBuckets(); ++i) { ::new (&getBuckets()[i].getFirst()) KeyT(other.getBuckets()[i].getFirst()); if (!KeyInfoT::isEqual(getBuckets()[i].getFirst(), getEmptyKey()) && !KeyInfoT::isEqual(getBuckets()[i].getFirst(), getTombstoneKey())) ::new (&getBuckets()[i].getSecond()) ValueT(other.getBuckets()[i].getSecond()); } } static unsigned getHashValue(const KeyT &Val) { return KeyInfoT::getHashValue(Val); } template static unsigned getHashValue(const LookupKeyT &Val) { return KeyInfoT::getHashValue(Val); } static const KeyT getEmptyKey() { return KeyInfoT::getEmptyKey(); } static const KeyT getTombstoneKey() { return KeyInfoT::getTombstoneKey(); } private: unsigned getNumEntries() const { return static_cast(this)->getNumEntries(); } void setNumEntries(unsigned Num) { static_cast(this)->setNumEntries(Num); } void incrementNumEntries() { setNumEntries(getNumEntries() + 1); } void decrementNumEntries() { setNumEntries(getNumEntries() - 1); } unsigned getNumTombstones() const { return static_cast(this)->getNumTombstones(); } void setNumTombstones(unsigned Num) { static_cast(this)->setNumTombstones(Num); } void incrementNumTombstones() { setNumTombstones(getNumTombstones() + 1); } void decrementNumTombstones() { setNumTombstones(getNumTombstones() - 1); } const BucketT *getBuckets() const { return static_cast(this)->getBuckets(); } BucketT *getBuckets() { return static_cast(this)->getBuckets(); } unsigned getNumBuckets() const { return static_cast(this)->getNumBuckets(); } BucketT *getBucketsEnd() { return getBuckets() + getNumBuckets(); } const BucketT *getBucketsEnd() const { return getBuckets() + getNumBuckets(); } void grow(unsigned AtLeast) { static_cast(this)->grow(AtLeast); } template BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, ValueArgs &&...Values) { TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); TheBucket->getFirst() = __sanitizer::forward(Key); ::new (&TheBucket->getSecond()) ValueT(__sanitizer::forward(Values)...); return TheBucket; } template BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key, ValueT &&Value, LookupKeyT &Lookup) { TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket); TheBucket->getFirst() = __sanitizer::move(Key); ::new (&TheBucket->getSecond()) ValueT(__sanitizer::move(Value)); return TheBucket; } template BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, BucketT *TheBucket) { // If the load of the hash table is more than 3/4, or if fewer than 1/8 of // the buckets are empty (meaning that many are filled with tombstones), // grow the table. // // The later case is tricky. For example, if we had one empty bucket with // tons of tombstones, failing lookups (e.g. for insertion) would have to // probe almost the entire table until it found the empty bucket. If the // table completely filled with tombstones, no lookup would ever succeed, // causing infinite loops in lookup. unsigned NewNumEntries = getNumEntries() + 1; unsigned NumBuckets = getNumBuckets(); if (UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) { this->grow(NumBuckets * 2); LookupBucketFor(Lookup, TheBucket); NumBuckets = getNumBuckets(); } else if (UNLIKELY(NumBuckets - (NewNumEntries + getNumTombstones()) <= NumBuckets / 8)) { this->grow(NumBuckets); LookupBucketFor(Lookup, TheBucket); } CHECK(TheBucket); // Only update the state after we've grown our bucket space appropriately // so that when growing buckets we have self-consistent entry count. incrementNumEntries(); // If we are writing over a tombstone, remember this. const KeyT EmptyKey = getEmptyKey(); if (!KeyInfoT::isEqual(TheBucket->getFirst(), EmptyKey)) decrementNumTombstones(); return TheBucket; } /// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in /// FoundBucket. If the bucket contains the key and a value, this returns /// true, otherwise it returns a bucket with an empty marker or tombstone and /// returns false. template bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const { const BucketT *BucketsPtr = getBuckets(); const unsigned NumBuckets = getNumBuckets(); if (NumBuckets == 0) { FoundBucket = nullptr; return false; } // FoundTombstone - Keep track of whether we find a tombstone while probing. const BucketT *FoundTombstone = nullptr; const KeyT EmptyKey = getEmptyKey(); const KeyT TombstoneKey = getTombstoneKey(); CHECK(!KeyInfoT::isEqual(Val, EmptyKey)); CHECK(!KeyInfoT::isEqual(Val, TombstoneKey)); unsigned BucketNo = getHashValue(Val) & (NumBuckets - 1); unsigned ProbeAmt = 1; while (true) { const BucketT *ThisBucket = BucketsPtr + BucketNo; // Found Val's bucket? If so, return it. if (LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) { FoundBucket = ThisBucket; return true; } // If we found an empty bucket, the key doesn't exist in the set. // Insert it and return the default value. if (LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) { // If we've already seen a tombstone while probing, fill it in instead // of the empty bucket we eventually probed to. FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket; return false; } // If this is a tombstone, remember it. If Val ends up not in the map, we // prefer to return it than something that would require more probing. if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) && !FoundTombstone) FoundTombstone = ThisBucket; // Remember the first tombstone found. // Otherwise, it's a hash collision or a tombstone, continue quadratic // probing. BucketNo += ProbeAmt++; BucketNo &= (NumBuckets - 1); } } template bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) { const BucketT *ConstFoundBucket; bool Result = const_cast(this)->LookupBucketFor( Val, ConstFoundBucket); FoundBucket = const_cast(ConstFoundBucket); return Result; } public: /// Return the approximate size (in bytes) of the actual map. /// This is just the raw memory used by DenseMap. /// If entries are pointers to objects, the size of the referenced objects /// are not included. uptr getMemorySize() const { return RoundUpTo(getNumBuckets() * sizeof(BucketT), GetPageSizeCached()); } }; /// Equality comparison for DenseMap. /// /// Iterates over elements of LHS confirming that each (key, value) pair in LHS /// is also in RHS, and that no additional pairs are in RHS. /// Equivalent to N calls to RHS.find and N value comparisons. Amortized /// complexity is linear, worst case is O(N^2) (if every hash collides). template bool operator==( const DenseMapBase &LHS, const DenseMapBase &RHS) { if (LHS.size() != RHS.size()) return false; bool R = true; LHS.forEach( [&](const typename DenseMapBase::value_type &KV) -> bool { const auto *I = RHS.find(KV.first); if (!I || I->second != KV.second) { R = false; return false; } return true; }); return R; } /// Inequality comparison for DenseMap. /// /// Equivalent to !(LHS == RHS). See operator== for performance notes. template bool operator!=( const DenseMapBase &LHS, const DenseMapBase &RHS) { return !(LHS == RHS); } template , typename BucketT = detail::DenseMapPair> class DenseMap : public DenseMapBase, KeyT, ValueT, KeyInfoT, BucketT> { friend class DenseMapBase; // Lift some types from the dependent base class into this class for // simplicity of referring to them. using BaseT = DenseMapBase; BucketT *Buckets = nullptr; unsigned NumEntries = 0; unsigned NumTombstones = 0; unsigned NumBuckets = 0; public: /// Create a DenseMap with an optional \p InitialReserve that guarantee that /// this number of elements can be inserted in the map without grow() explicit DenseMap(unsigned InitialReserve) { init(InitialReserve); } constexpr DenseMap() = default; DenseMap(const DenseMap &other) : BaseT() { init(0); copyFrom(other); } DenseMap(DenseMap &&other) : BaseT() { init(0); swap(other); } ~DenseMap() { this->destroyAll(); deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets); } void swap(DenseMap &RHS) { Swap(Buckets, RHS.Buckets); Swap(NumEntries, RHS.NumEntries); Swap(NumTombstones, RHS.NumTombstones); Swap(NumBuckets, RHS.NumBuckets); } DenseMap &operator=(const DenseMap &other) { if (&other != this) copyFrom(other); return *this; } DenseMap &operator=(DenseMap &&other) { this->destroyAll(); deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets, alignof(BucketT)); init(0); swap(other); return *this; } void copyFrom(const DenseMap &other) { this->destroyAll(); deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets); if (allocateBuckets(other.NumBuckets)) { this->BaseT::copyFrom(other); } else { NumEntries = 0; NumTombstones = 0; } } void init(unsigned InitNumEntries) { auto InitBuckets = BaseT::getMinBucketToReserveForEntries(InitNumEntries); if (allocateBuckets(InitBuckets)) { this->BaseT::initEmpty(); } else { NumEntries = 0; NumTombstones = 0; } } void grow(unsigned AtLeast) { unsigned OldNumBuckets = NumBuckets; BucketT *OldBuckets = Buckets; allocateBuckets(RoundUpToPowerOfTwo(Max(64, AtLeast))); CHECK(Buckets); if (!OldBuckets) { this->BaseT::initEmpty(); return; } this->moveFromOldBuckets(OldBuckets, OldBuckets + OldNumBuckets); // Free the old table. deallocate_buffer(OldBuckets, sizeof(BucketT) * OldNumBuckets); } private: unsigned getNumEntries() const { return NumEntries; } void setNumEntries(unsigned Num) { NumEntries = Num; } unsigned getNumTombstones() const { return NumTombstones; } void setNumTombstones(unsigned Num) { NumTombstones = Num; } BucketT *getBuckets() const { return Buckets; } unsigned getNumBuckets() const { return NumBuckets; } bool allocateBuckets(unsigned Num) { NumBuckets = Num; if (NumBuckets == 0) { Buckets = nullptr; return false; } uptr Size = sizeof(BucketT) * NumBuckets; if (Size * 2 <= GetPageSizeCached()) { // We always allocate at least a page, so use entire space. unsigned Log2 = MostSignificantSetBitIndex(GetPageSizeCached() / Size); Size <<= Log2; NumBuckets <<= Log2; CHECK_EQ(Size, sizeof(BucketT) * NumBuckets); CHECK_GT(Size * 2, GetPageSizeCached()); } Buckets = static_cast(allocate_buffer(Size)); return true; } static void *allocate_buffer(uptr Size) { return MmapOrDie(RoundUpTo(Size, GetPageSizeCached()), "DenseMap"); } static void deallocate_buffer(void *Ptr, uptr Size) { UnmapOrDie(Ptr, RoundUpTo(Size, GetPageSizeCached())); } }; } // namespace __sanitizer #endif // SANITIZER_DENSE_MAP_H