diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h @@ -0,0 +1,1308 @@ +//===- llvm/ADT/DenseMap.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 file defines the DenseMap class. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_DENSEMAP_H +#define LLVM_ADT_DENSEMAP_H + +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/EpochTracker.h" +#include "llvm/Support/AlignOf.h" +#include "llvm/Support/Compiler.h" +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/MemAlloc.h" +#include "llvm/Support/ReverseIteration.h" +#include "llvm/Support/type_traits.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace llvm { + +namespace detail { + +// We extend a pair to allow users to override the bucket type with their own +// implementation without requiring two members. +template +struct DenseMapPair : public std::pair { + using std::pair::pair; + + KeyT &getFirst() { return std::pair::first; } + const KeyT &getFirst() const { return std::pair::first; } + ValueT &getSecond() { return std::pair::second; } + const ValueT &getSecond() const { return std::pair::second; } +}; + +} // end namespace detail + +template , + typename Bucket = llvm::detail::DenseMapPair, + bool IsConst = false> +class DenseMapIterator; + +template +class DenseMapBase : public DebugEpochBase { + template + using const_arg_type_t = typename const_pointer_or_const_ref::type; + +public: + using size_type = unsigned; + using key_type = KeyT; + using mapped_type = ValueT; + using value_type = BucketT; + + using iterator = DenseMapIterator; + using const_iterator = + DenseMapIterator; + + inline iterator begin() { + // When the map is empty, avoid the overhead of advancing/retreating past + // empty buckets. + if (empty()) + return end(); + if (shouldReverseIterate()) + return makeIterator(getBucketsEnd() - 1, getBuckets(), *this); + return makeIterator(getBuckets(), getBucketsEnd(), *this); + } + inline iterator end() { + return makeIterator(getBucketsEnd(), getBucketsEnd(), *this, true); + } + inline const_iterator begin() const { + if (empty()) + return end(); + if (shouldReverseIterate()) + return makeConstIterator(getBucketsEnd() - 1, getBuckets(), *this); + return makeConstIterator(getBuckets(), getBucketsEnd(), *this); + } + inline const_iterator end() const { + return makeConstIterator(getBucketsEnd(), getBucketsEnd(), *this, true); + } + + LLVM_NODISCARD 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); + incrementEpoch(); + if (NumBuckets > getNumBuckets()) + grow(NumBuckets); + } + + void clear() { + incrementEpoch(); + if (getNumEntries() == 0 && getNumTombstones() == 0) return; + + // If the capacity of the array is huge, and the # elements used is small, + // shrink the array. + if (getNumEntries() * 4 < getNumBuckets() && getNumBuckets() > 64) { + shrink_and_clear(); + return; + } + + const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey(); + if (std::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; + } + } + assert(NumEntries == 0 && "Node count imbalance!"); + } + setNumEntries(0); + setNumTombstones(0); + } + + /// Return 1 if the specified key is in the map, 0 otherwise. + size_type count(const_arg_type_t Val) const { + const BucketT *TheBucket; + return LookupBucketFor(Val, TheBucket) ? 1 : 0; + } + + iterator find(const_arg_type_t Val) { + BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return makeIterator(TheBucket, + shouldReverseIterate() ? getBuckets() + : getBucketsEnd(), + *this, true); + return end(); + } + const_iterator find(const_arg_type_t Val) const { + const BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return makeConstIterator(TheBucket, + shouldReverseIterate() ? getBuckets() + : getBucketsEnd(), + *this, true); + return end(); + } + + /// 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 + iterator find_as(const LookupKeyT &Val) { + BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return makeIterator(TheBucket, + shouldReverseIterate() ? getBuckets() + : getBucketsEnd(), + *this, true); + return end(); + } + template + const_iterator find_as(const LookupKeyT &Val) const { + const BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return makeConstIterator(TheBucket, + shouldReverseIterate() ? getBuckets() + : getBucketsEnd(), + *this, true); + return end(); + } + + /// lookup - Return the entry for the specified key, or a default + /// constructed value if no such entry exists. + ValueT lookup(const_arg_type_t Val) const { + const BucketT *TheBucket; + if (LookupBucketFor(Val, 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. + std::pair insert(const std::pair &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. + std::pair insert(std::pair &&KV) { + return try_emplace(std::move(KV.first), std::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 + std::pair try_emplace(KeyT &&Key, Ts &&... Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + false); // Already in map. + + // Otherwise, insert the new element. + TheBucket = + InsertIntoBucket(TheBucket, std::move(Key), std::forward(Args)...); + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + 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 + std::pair try_emplace(const KeyT &Key, Ts &&... Args) { + BucketT *TheBucket; + if (LookupBucketFor(Key, TheBucket)) + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + false); // Already in map. + + // Otherwise, insert the new element. + TheBucket = InsertIntoBucket(TheBucket, Key, std::forward(Args)...); + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + 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 + std::pair insert_as(std::pair &&KV, + const LookupKeyT &Val) { + BucketT *TheBucket; + if (LookupBucketFor(Val, TheBucket)) + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + false); // Already in map. + + // Otherwise, insert the new element. + TheBucket = InsertIntoBucketWithLookup(TheBucket, std::move(KV.first), + std::move(KV.second), Val); + return std::make_pair(makeIterator(TheBucket, + shouldReverseIterate() + ? getBuckets() + : getBucketsEnd(), + *this, true), + true); + } + + /// insert - Range insertion of pairs. + template + void insert(InputIt I, InputIt E) { + for (; I != E; ++I) + insert(*I); + } + + 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(iterator I) { + 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, std::move(Key)); + } + + ValueT &operator[](KeyT &&Key) { + return FindAndConstruct(std::move(Key)).second; + } + + /// isPointerIntoBucketsArray - Return true if the specified pointer points + /// somewhere into the DenseMap's array of buckets (i.e. either to a key or + /// value in the DenseMap). + bool isPointerIntoBucketsArray(const void *Ptr) const { + return Ptr >= getBuckets() && Ptr < getBucketsEnd(); + } + + /// getPointerIntoBucketsArray() - Return an opaque pointer into the buckets + /// array. In conjunction with the previous method, this can be used to + /// determine whether an insertion caused the DenseMap to reallocate. + const void *getPointerIntoBucketsArray() const { return getBuckets(); } + +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); + + assert((getNumBuckets() & (getNumBuckets()-1)) == 0 && + "# initial buckets must be a power of two!"); + 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 NextPowerOf2(NumEntries * 4 / 3 + 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. + assert(!FoundVal && "Key already in new map?"); + DestBucket->getFirst() = std::move(B->getFirst()); + ::new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond())); + incrementNumEntries(); + + // Free the value. + B->getSecond().~ValueT(); + } + B->getFirst().~KeyT(); + } + } + + template + void copyFrom( + const DenseMapBase &other) { + assert(&other != this); + assert(getNumBuckets() == other.getNumBuckets()); + + setNumEntries(other.getNumEntries()); + setNumTombstones(other.getNumTombstones()); + + if (std::is_trivially_copyable::value && + std::is_trivially_copyable::value) + memcpy(reinterpret_cast(getBuckets()), other.getBuckets(), + getNumBuckets() * sizeof(BucketT)); + else + for (size_t 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() { + static_assert(std::is_base_of::value, + "Must pass the derived type to this template!"); + return KeyInfoT::getEmptyKey(); + } + + static const KeyT getTombstoneKey() { + return KeyInfoT::getTombstoneKey(); + } + +private: + iterator makeIterator(BucketT *P, BucketT *E, + DebugEpochBase &Epoch, + bool NoAdvance=false) { + if (shouldReverseIterate()) { + BucketT *B = P == getBucketsEnd() ? getBuckets() : P + 1; + return iterator(B, E, Epoch, NoAdvance); + } + return iterator(P, E, Epoch, NoAdvance); + } + + const_iterator makeConstIterator(const BucketT *P, const BucketT *E, + const DebugEpochBase &Epoch, + const bool NoAdvance=false) const { + if (shouldReverseIterate()) { + const BucketT *B = P == getBucketsEnd() ? getBuckets() : P + 1; + return const_iterator(B, E, Epoch, NoAdvance); + } + return const_iterator(P, E, Epoch, NoAdvance); + } + + 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); + } + + void shrink_and_clear() { + static_cast(this)->shrink_and_clear(); + } + + template + BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key, + ValueArgs &&... Values) { + TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket); + + TheBucket->getFirst() = std::forward(Key); + ::new (&TheBucket->getSecond()) ValueT(std::forward(Values)...); + return TheBucket; + } + + template + BucketT *InsertIntoBucketWithLookup(BucketT *TheBucket, KeyT &&Key, + ValueT &&Value, LookupKeyT &Lookup) { + TheBucket = InsertIntoBucketImpl(Key, Lookup, TheBucket); + + TheBucket->getFirst() = std::move(Key); + ::new (&TheBucket->getSecond()) ValueT(std::move(Value)); + return TheBucket; + } + + template + BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, + BucketT *TheBucket) { + incrementEpoch(); + + // 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 (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) { + this->grow(NumBuckets * 2); + LookupBucketFor(Lookup, TheBucket); + NumBuckets = getNumBuckets(); + } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <= + NumBuckets/8)) { + this->grow(NumBuckets); + LookupBucketFor(Lookup, TheBucket); + } + assert(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(); + assert(!KeyInfoT::isEqual(Val, EmptyKey) && + !KeyInfoT::isEqual(Val, TombstoneKey) && + "Empty/Tombstone value shouldn't be inserted into map!"); + + 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 (LLVM_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 (LLVM_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. + size_t getMemorySize() const { + return getNumBuckets() * sizeof(BucketT); + } +}; + +/// 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; + + for (auto &KV : LHS) { + auto I = RHS.find(KV.first); + if (I == RHS.end() || I->second != KV.second) + return false; + } + + return true; +} + +/// 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 = llvm::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; + unsigned NumEntries; + unsigned NumTombstones; + unsigned NumBuckets; + +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 = 0) { init(InitialReserve); } + + DenseMap(const DenseMap &other) : BaseT() { + init(0); + copyFrom(other); + } + + DenseMap(DenseMap &&other) : BaseT() { + init(0); + swap(other); + } + + template + DenseMap(const InputIt &I, const InputIt &E) { + init(std::distance(I, E)); + this->insert(I, E); + } + + DenseMap(std::initializer_list Vals) { + init(Vals.size()); + this->insert(Vals.begin(), Vals.end()); + } + + ~DenseMap() { + this->destroyAll(); + deallocate_buffer(Buckets, sizeof(BucketT) * NumBuckets, alignof(BucketT)); + } + + void swap(DenseMap& RHS) { + this->incrementEpoch(); + RHS.incrementEpoch(); + std::swap(Buckets, RHS.Buckets); + std::swap(NumEntries, RHS.NumEntries); + std::swap(NumTombstones, RHS.NumTombstones); + std::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, alignof(BucketT)); + 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(std::max(64, static_cast(NextPowerOf2(AtLeast-1)))); + assert(Buckets); + if (!OldBuckets) { + this->BaseT::initEmpty(); + return; + } + + this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets); + + // Free the old table. + deallocate_buffer(OldBuckets, sizeof(BucketT) * OldNumBuckets, + alignof(BucketT)); + } + + void shrink_and_clear() { + unsigned OldNumBuckets = NumBuckets; + unsigned OldNumEntries = NumEntries; + this->destroyAll(); + + // Reduce the number of buckets. + unsigned NewNumBuckets = 0; + if (OldNumEntries) + NewNumBuckets = std::max(64, 1 << (Log2_32_Ceil(OldNumEntries) + 1)); + if (NewNumBuckets == NumBuckets) { + this->BaseT::initEmpty(); + return; + } + + deallocate_buffer(Buckets, sizeof(BucketT) * OldNumBuckets, + alignof(BucketT)); + init(NewNumBuckets); + } + +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; + } + + Buckets = static_cast( + allocate_buffer(sizeof(BucketT) * NumBuckets, alignof(BucketT))); + return true; + } +}; + +template , + typename BucketT = llvm::detail::DenseMapPair> +class SmallDenseMap + : public DenseMapBase< + SmallDenseMap, 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; + + static_assert(isPowerOf2_64(InlineBuckets), + "InlineBuckets must be a power of 2."); + + unsigned Small : 1; + unsigned NumEntries : 31; + unsigned NumTombstones; + + struct LargeRep { + BucketT *Buckets; + unsigned NumBuckets; + }; + + /// A "union" of an inline bucket array and the struct representing + /// a large bucket. This union will be discriminated by the 'Small' bit. + AlignedCharArrayUnion storage; + +public: + explicit SmallDenseMap(unsigned NumInitBuckets = 0) { + init(NumInitBuckets); + } + + SmallDenseMap(const SmallDenseMap &other) : BaseT() { + init(0); + copyFrom(other); + } + + SmallDenseMap(SmallDenseMap &&other) : BaseT() { + init(0); + swap(other); + } + + template + SmallDenseMap(const InputIt &I, const InputIt &E) { + init(NextPowerOf2(std::distance(I, E))); + this->insert(I, E); + } + + SmallDenseMap(std::initializer_list Vals) + : SmallDenseMap(Vals.begin(), Vals.end()) {} + + ~SmallDenseMap() { + this->destroyAll(); + deallocateBuckets(); + } + + void swap(SmallDenseMap& RHS) { + unsigned TmpNumEntries = RHS.NumEntries; + RHS.NumEntries = NumEntries; + NumEntries = TmpNumEntries; + std::swap(NumTombstones, RHS.NumTombstones); + + const KeyT EmptyKey = this->getEmptyKey(); + const KeyT TombstoneKey = this->getTombstoneKey(); + if (Small && RHS.Small) { + // If we're swapping inline bucket arrays, we have to cope with some of + // the tricky bits of DenseMap's storage system: the buckets are not + // fully initialized. Thus we swap every key, but we may have + // a one-directional move of the value. + for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { + BucketT *LHSB = &getInlineBuckets()[i], + *RHSB = &RHS.getInlineBuckets()[i]; + bool hasLHSValue = (!KeyInfoT::isEqual(LHSB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(LHSB->getFirst(), TombstoneKey)); + bool hasRHSValue = (!KeyInfoT::isEqual(RHSB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(RHSB->getFirst(), TombstoneKey)); + if (hasLHSValue && hasRHSValue) { + // Swap together if we can... + std::swap(*LHSB, *RHSB); + continue; + } + // Swap separately and handle any asymmetry. + std::swap(LHSB->getFirst(), RHSB->getFirst()); + if (hasLHSValue) { + ::new (&RHSB->getSecond()) ValueT(std::move(LHSB->getSecond())); + LHSB->getSecond().~ValueT(); + } else if (hasRHSValue) { + ::new (&LHSB->getSecond()) ValueT(std::move(RHSB->getSecond())); + RHSB->getSecond().~ValueT(); + } + } + return; + } + if (!Small && !RHS.Small) { + std::swap(getLargeRep()->Buckets, RHS.getLargeRep()->Buckets); + std::swap(getLargeRep()->NumBuckets, RHS.getLargeRep()->NumBuckets); + return; + } + + SmallDenseMap &SmallSide = Small ? *this : RHS; + SmallDenseMap &LargeSide = Small ? RHS : *this; + + // First stash the large side's rep and move the small side across. + LargeRep TmpRep = std::move(*LargeSide.getLargeRep()); + LargeSide.getLargeRep()->~LargeRep(); + LargeSide.Small = true; + // This is similar to the standard move-from-old-buckets, but the bucket + // count hasn't actually rotated in this case. So we have to carefully + // move construct the keys and values into their new locations, but there + // is no need to re-hash things. + for (unsigned i = 0, e = InlineBuckets; i != e; ++i) { + BucketT *NewB = &LargeSide.getInlineBuckets()[i], + *OldB = &SmallSide.getInlineBuckets()[i]; + ::new (&NewB->getFirst()) KeyT(std::move(OldB->getFirst())); + OldB->getFirst().~KeyT(); + if (!KeyInfoT::isEqual(NewB->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(NewB->getFirst(), TombstoneKey)) { + ::new (&NewB->getSecond()) ValueT(std::move(OldB->getSecond())); + OldB->getSecond().~ValueT(); + } + } + + // The hard part of moving the small buckets across is done, just move + // the TmpRep into its new home. + SmallSide.Small = false; + new (SmallSide.getLargeRep()) LargeRep(std::move(TmpRep)); + } + + SmallDenseMap& operator=(const SmallDenseMap& other) { + if (&other != this) + copyFrom(other); + return *this; + } + + SmallDenseMap& operator=(SmallDenseMap &&other) { + this->destroyAll(); + deallocateBuckets(); + init(0); + swap(other); + return *this; + } + + void copyFrom(const SmallDenseMap& other) { + this->destroyAll(); + deallocateBuckets(); + Small = true; + if (other.getNumBuckets() > InlineBuckets) { + Small = false; + new (getLargeRep()) LargeRep(allocateBuckets(other.getNumBuckets())); + } + this->BaseT::copyFrom(other); + } + + void init(unsigned InitBuckets) { + Small = true; + if (InitBuckets > InlineBuckets) { + Small = false; + new (getLargeRep()) LargeRep(allocateBuckets(InitBuckets)); + } + this->BaseT::initEmpty(); + } + + void grow(unsigned AtLeast) { + if (AtLeast > InlineBuckets) + AtLeast = std::max(64, NextPowerOf2(AtLeast-1)); + + if (Small) { + // First move the inline buckets into a temporary storage. + AlignedCharArrayUnion TmpStorage; + BucketT *TmpBegin = reinterpret_cast(&TmpStorage); + BucketT *TmpEnd = TmpBegin; + + // Loop over the buckets, moving non-empty, non-tombstones into the + // temporary storage. Have the loop move the TmpEnd forward as it goes. + const KeyT EmptyKey = this->getEmptyKey(); + const KeyT TombstoneKey = this->getTombstoneKey(); + for (BucketT *P = getBuckets(), *E = P + InlineBuckets; P != E; ++P) { + if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) && + !KeyInfoT::isEqual(P->getFirst(), TombstoneKey)) { + assert(size_t(TmpEnd - TmpBegin) < InlineBuckets && + "Too many inline buckets!"); + ::new (&TmpEnd->getFirst()) KeyT(std::move(P->getFirst())); + ::new (&TmpEnd->getSecond()) ValueT(std::move(P->getSecond())); + ++TmpEnd; + P->getSecond().~ValueT(); + } + P->getFirst().~KeyT(); + } + + // AtLeast == InlineBuckets can happen if there are many tombstones, + // and grow() is used to remove them. Usually we always switch to the + // large rep here. + if (AtLeast > InlineBuckets) { + Small = false; + new (getLargeRep()) LargeRep(allocateBuckets(AtLeast)); + } + this->moveFromOldBuckets(TmpBegin, TmpEnd); + return; + } + + LargeRep OldRep = std::move(*getLargeRep()); + getLargeRep()->~LargeRep(); + if (AtLeast <= InlineBuckets) { + Small = true; + } else { + new (getLargeRep()) LargeRep(allocateBuckets(AtLeast)); + } + + this->moveFromOldBuckets(OldRep.Buckets, OldRep.Buckets+OldRep.NumBuckets); + + // Free the old table. + deallocate_buffer(OldRep.Buckets, sizeof(BucketT) * OldRep.NumBuckets, + alignof(BucketT)); + } + + void shrink_and_clear() { + unsigned OldSize = this->size(); + this->destroyAll(); + + // Reduce the number of buckets. + unsigned NewNumBuckets = 0; + if (OldSize) { + NewNumBuckets = 1 << (Log2_32_Ceil(OldSize) + 1); + if (NewNumBuckets > InlineBuckets && NewNumBuckets < 64u) + NewNumBuckets = 64; + } + if ((Small && NewNumBuckets <= InlineBuckets) || + (!Small && NewNumBuckets == getLargeRep()->NumBuckets)) { + this->BaseT::initEmpty(); + return; + } + + deallocateBuckets(); + init(NewNumBuckets); + } + +private: + unsigned getNumEntries() const { + return NumEntries; + } + + void setNumEntries(unsigned Num) { + // NumEntries is hardcoded to be 31 bits wide. + assert(Num < (1U << 31) && "Cannot support more than 1<<31 entries"); + NumEntries = Num; + } + + unsigned getNumTombstones() const { + return NumTombstones; + } + + void setNumTombstones(unsigned Num) { + NumTombstones = Num; + } + + const BucketT *getInlineBuckets() const { + assert(Small); + // Note that this cast does not violate aliasing rules as we assert that + // the memory's dynamic type is the small, inline bucket buffer, and the + // 'storage' is a POD containing a char buffer. + return reinterpret_cast(&storage); + } + + BucketT *getInlineBuckets() { + return const_cast( + const_cast(this)->getInlineBuckets()); + } + + const LargeRep *getLargeRep() const { + assert(!Small); + // Note, same rule about aliasing as with getInlineBuckets. + return reinterpret_cast(&storage); + } + + LargeRep *getLargeRep() { + return const_cast( + const_cast(this)->getLargeRep()); + } + + const BucketT *getBuckets() const { + return Small ? getInlineBuckets() : getLargeRep()->Buckets; + } + + BucketT *getBuckets() { + return const_cast( + const_cast(this)->getBuckets()); + } + + unsigned getNumBuckets() const { + return Small ? InlineBuckets : getLargeRep()->NumBuckets; + } + + void deallocateBuckets() { + if (Small) + return; + + deallocate_buffer(getLargeRep()->Buckets, + sizeof(BucketT) * getLargeRep()->NumBuckets, + alignof(BucketT)); + getLargeRep()->~LargeRep(); + } + + LargeRep allocateBuckets(unsigned Num) { + assert(Num > InlineBuckets && "Must allocate more buckets than are inline"); + LargeRep Rep = {static_cast(allocate_buffer( + sizeof(BucketT) * Num, alignof(BucketT))), + Num}; + return Rep; + } +}; + +template +class DenseMapIterator : DebugEpochBase::HandleBase { + friend class DenseMapIterator; + friend class DenseMapIterator; + +public: + using difference_type = ptrdiff_t; + using value_type = + typename std::conditional::type; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + +private: + pointer Ptr = nullptr; + pointer End = nullptr; + +public: + DenseMapIterator() = default; + + DenseMapIterator(pointer Pos, pointer E, const DebugEpochBase &Epoch, + bool NoAdvance = false) + : DebugEpochBase::HandleBase(&Epoch), Ptr(Pos), End(E) { + assert(isHandleInSync() && "invalid construction!"); + + if (NoAdvance) return; + if (shouldReverseIterate()) { + RetreatPastEmptyBuckets(); + return; + } + AdvancePastEmptyBuckets(); + } + + // Converting ctor from non-const iterators to const iterators. SFINAE'd out + // for const iterator destinations so it doesn't end up as a user defined copy + // constructor. + template > + DenseMapIterator( + const DenseMapIterator &I) + : DebugEpochBase::HandleBase(I), Ptr(I.Ptr), End(I.End) {} + + reference operator*() const { + assert(isHandleInSync() && "invalid iterator access!"); + assert(Ptr != End && "dereferencing end() iterator"); + if (shouldReverseIterate()) + return Ptr[-1]; + return *Ptr; + } + pointer operator->() const { + assert(isHandleInSync() && "invalid iterator access!"); + assert(Ptr != End && "dereferencing end() iterator"); + if (shouldReverseIterate()) + return &(Ptr[-1]); + return Ptr; + } + + friend bool operator==(const DenseMapIterator &LHS, + const DenseMapIterator &RHS) { + assert((!LHS.Ptr || LHS.isHandleInSync()) && "handle not in sync!"); + assert((!RHS.Ptr || RHS.isHandleInSync()) && "handle not in sync!"); + assert(LHS.getEpochAddress() == RHS.getEpochAddress() && + "comparing incomparable iterators!"); + return LHS.Ptr == RHS.Ptr; + } + + friend bool operator!=(const DenseMapIterator &LHS, + const DenseMapIterator &RHS) { + return !(LHS == RHS); + } + + inline DenseMapIterator& operator++() { // Preincrement + assert(isHandleInSync() && "invalid iterator access!"); + assert(Ptr != End && "incrementing end() iterator"); + if (shouldReverseIterate()) { + --Ptr; + RetreatPastEmptyBuckets(); + return *this; + } + ++Ptr; + AdvancePastEmptyBuckets(); + return *this; + } + DenseMapIterator operator++(int) { // Postincrement + assert(isHandleInSync() && "invalid iterator access!"); + DenseMapIterator tmp = *this; ++*this; return tmp; + } + +private: + void AdvancePastEmptyBuckets() { + assert(Ptr <= End); + const KeyT Empty = KeyInfoT::getEmptyKey(); + const KeyT Tombstone = KeyInfoT::getTombstoneKey(); + + while (Ptr != End && (KeyInfoT::isEqual(Ptr->getFirst(), Empty) || + KeyInfoT::isEqual(Ptr->getFirst(), Tombstone))) + ++Ptr; + } + + void RetreatPastEmptyBuckets() { + assert(Ptr >= End); + const KeyT Empty = KeyInfoT::getEmptyKey(); + const KeyT Tombstone = KeyInfoT::getTombstoneKey(); + + while (Ptr != End && (KeyInfoT::isEqual(Ptr[-1].getFirst(), Empty) || + KeyInfoT::isEqual(Ptr[-1].getFirst(), Tombstone))) + --Ptr; + } +}; + +template +inline size_t capacity_in_bytes(const DenseMap &X) { + return X.getMemorySize(); +} + +} // end namespace llvm + +#endif // LLVM_ADT_DENSEMAP_H diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h @@ -0,0 +1,287 @@ +//===- llvm/ADT/DenseMapInfo.h - Type traits for DenseMap -------*- 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 file defines DenseMapInfo traits for DenseMap. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ADT_DENSEMAPINFO_H +#define LLVM_ADT_DENSEMAPINFO_H + +#include +#include +#include +#include +#include + +namespace llvm { + +namespace detail { + +/// Simplistic combination of 32-bit hash values into 32-bit hash values. +static inline unsigned combineHashValue(unsigned a, unsigned b) { + uint64_t key = (uint64_t)a << 32 | (uint64_t)b; + key += ~(key << 32); + key ^= (key >> 22); + key += ~(key << 13); + key ^= (key >> 8); + key += (key << 3); + key ^= (key >> 15); + key += ~(key << 27); + key ^= (key >> 31); + return (unsigned)key; +} + +} // end namespace detail + +template +struct DenseMapInfo { + //static inline T getEmptyKey(); + //static inline T getTombstoneKey(); + //static unsigned getHashValue(const T &Val); + //static bool isEqual(const T &LHS, const T &RHS); +}; + +// Provide DenseMapInfo for all pointers. Come up with sentinel pointer values +// that are aligned to alignof(T) bytes, but try to avoid requiring T to be +// complete. This allows clients to instantiate DenseMap with forward +// declared key types. Assume that no pointer key type requires more than 4096 +// bytes of alignment. +template +struct DenseMapInfo { + // The following should hold, but it would require T to be complete: + // static_assert(alignof(T) <= (1 << Log2MaxAlign), + // "DenseMap does not support pointer keys requiring more than " + // "Log2MaxAlign bits of alignment"); + static constexpr uintptr_t Log2MaxAlign = 12; + + static inline T* getEmptyKey() { + uintptr_t Val = static_cast(-1); + Val <<= Log2MaxAlign; + return reinterpret_cast(Val); + } + + static inline T* getTombstoneKey() { + uintptr_t Val = static_cast(-2); + Val <<= Log2MaxAlign; + return reinterpret_cast(Val); + } + + static unsigned getHashValue(const T *PtrVal) { + return (unsigned((uintptr_t)PtrVal) >> 4) ^ + (unsigned((uintptr_t)PtrVal) >> 9); + } + + static bool isEqual(const T *LHS, const T *RHS) { return LHS == RHS; } +}; + +// Provide DenseMapInfo for chars. +template<> struct DenseMapInfo { + static inline char getEmptyKey() { return ~0; } + static inline char getTombstoneKey() { return ~0 - 1; } + static unsigned getHashValue(const char& Val) { return Val * 37U; } + + static bool isEqual(const char &LHS, const char &RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for unsigned chars. +template <> struct DenseMapInfo { + static inline unsigned char getEmptyKey() { return ~0; } + static inline unsigned char getTombstoneKey() { return ~0 - 1; } + static unsigned getHashValue(const unsigned char &Val) { return Val * 37U; } + + static bool isEqual(const unsigned char &LHS, const unsigned char &RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for unsigned shorts. +template <> struct DenseMapInfo { + static inline unsigned short getEmptyKey() { return 0xFFFF; } + static inline unsigned short getTombstoneKey() { return 0xFFFF - 1; } + static unsigned getHashValue(const unsigned short &Val) { return Val * 37U; } + + static bool isEqual(const unsigned short &LHS, const unsigned short &RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for unsigned ints. +template<> struct DenseMapInfo { + static inline unsigned getEmptyKey() { return ~0U; } + static inline unsigned getTombstoneKey() { return ~0U - 1; } + static unsigned getHashValue(const unsigned& Val) { return Val * 37U; } + + static bool isEqual(const unsigned& LHS, const unsigned& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for unsigned longs. +template<> struct DenseMapInfo { + static inline unsigned long getEmptyKey() { return ~0UL; } + static inline unsigned long getTombstoneKey() { return ~0UL - 1L; } + + static unsigned getHashValue(const unsigned long& Val) { + return (unsigned)(Val * 37UL); + } + + static bool isEqual(const unsigned long& LHS, const unsigned long& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for unsigned long longs. +template<> struct DenseMapInfo { + static inline unsigned long long getEmptyKey() { return ~0ULL; } + static inline unsigned long long getTombstoneKey() { return ~0ULL - 1ULL; } + + static unsigned getHashValue(const unsigned long long& Val) { + return (unsigned)(Val * 37ULL); + } + + static bool isEqual(const unsigned long long& LHS, + const unsigned long long& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for shorts. +template <> struct DenseMapInfo { + static inline short getEmptyKey() { return 0x7FFF; } + static inline short getTombstoneKey() { return -0x7FFF - 1; } + static unsigned getHashValue(const short &Val) { return Val * 37U; } + static bool isEqual(const short &LHS, const short &RHS) { return LHS == RHS; } +}; + +// Provide DenseMapInfo for ints. +template<> struct DenseMapInfo { + static inline int getEmptyKey() { return 0x7fffffff; } + static inline int getTombstoneKey() { return -0x7fffffff - 1; } + static unsigned getHashValue(const int& Val) { return (unsigned)(Val * 37U); } + + static bool isEqual(const int& LHS, const int& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for longs. +template<> struct DenseMapInfo { + static inline long getEmptyKey() { + return (1UL << (sizeof(long) * 8 - 1)) - 1UL; + } + + static inline long getTombstoneKey() { return getEmptyKey() - 1L; } + + static unsigned getHashValue(const long& Val) { + return (unsigned)(Val * 37UL); + } + + static bool isEqual(const long& LHS, const long& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for long longs. +template<> struct DenseMapInfo { + static inline long long getEmptyKey() { return 0x7fffffffffffffffLL; } + static inline long long getTombstoneKey() { return -0x7fffffffffffffffLL-1; } + + static unsigned getHashValue(const long long& Val) { + return (unsigned)(Val * 37ULL); + } + + static bool isEqual(const long long& LHS, + const long long& RHS) { + return LHS == RHS; + } +}; + +// Provide DenseMapInfo for all pairs whose members have info. +template +struct DenseMapInfo> { + using Pair = std::pair; + using FirstInfo = DenseMapInfo; + using SecondInfo = DenseMapInfo; + + static inline Pair getEmptyKey() { + return std::make_pair(FirstInfo::getEmptyKey(), + SecondInfo::getEmptyKey()); + } + + static inline Pair getTombstoneKey() { + return std::make_pair(FirstInfo::getTombstoneKey(), + SecondInfo::getTombstoneKey()); + } + + static unsigned getHashValue(const Pair& PairVal) { + return detail::combineHashValue(FirstInfo::getHashValue(PairVal.first), + SecondInfo::getHashValue(PairVal.second)); + } + + static bool isEqual(const Pair &LHS, const Pair &RHS) { + return FirstInfo::isEqual(LHS.first, RHS.first) && + SecondInfo::isEqual(LHS.second, RHS.second); + } +}; + +// Provide DenseMapInfo for all tuples whose members have info. +template struct DenseMapInfo> { + using Tuple = std::tuple; + + static inline Tuple getEmptyKey() { + return Tuple(DenseMapInfo::getEmptyKey()...); + } + + static inline Tuple getTombstoneKey() { + return Tuple(DenseMapInfo::getTombstoneKey()...); + } + + template + static unsigned getHashValueImpl(const Tuple &values, std::false_type) { + using EltType = typename std::tuple_element::type; + std::integral_constant atEnd; + return detail::combineHashValue( + DenseMapInfo::getHashValue(std::get(values)), + getHashValueImpl(values, atEnd)); + } + + template + static unsigned getHashValueImpl(const Tuple &, std::true_type) { + return 0; + } + + static unsigned getHashValue(const std::tuple &values) { + std::integral_constant atEnd; + return getHashValueImpl<0>(values, atEnd); + } + + template + static bool isEqualImpl(const Tuple &lhs, const Tuple &rhs, std::false_type) { + using EltType = typename std::tuple_element::type; + std::integral_constant atEnd; + return DenseMapInfo::isEqual(std::get(lhs), std::get(rhs)) && + isEqualImpl(lhs, rhs, atEnd); + } + + template + static bool isEqualImpl(const Tuple &, const Tuple &, std::true_type) { + return true; + } + + static bool isEqual(const Tuple &lhs, const Tuple &rhs) { + std::integral_constant atEnd; + return isEqualImpl<0>(lhs, rhs, atEnd); + } +}; + +} // end namespace llvm + +#endif // LLVM_ADT_DENSEMAPINFO_H diff --git a/compiler-rt/lib/sanitizer_common/tests/sanitizer_dense_map_test.cpp b/compiler-rt/lib/sanitizer_common/tests/sanitizer_dense_map_test.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_dense_map_test.cpp @@ -0,0 +1,658 @@ +//===- llvm/unittest/ADT/DenseMapMap.cpp - DenseMap unit tests --*- 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 +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/DenseMap.h" +#include "gtest/gtest.h" +#include +#include + +using namespace llvm; + +namespace { + +uint32_t getTestKey(int i, uint32_t *) { return i; } +uint32_t getTestValue(int i, uint32_t *) { return 42 + i; } + +uint32_t *getTestKey(int i, uint32_t **) { + static uint32_t dummy_arr1[8192]; + assert(i < 8192 && "Only support 8192 dummy keys."); + return &dummy_arr1[i]; +} +uint32_t *getTestValue(int i, uint32_t **) { + static uint32_t dummy_arr1[8192]; + assert(i < 8192 && "Only support 8192 dummy keys."); + return &dummy_arr1[i]; +} + +/// A test class that tries to check that construction and destruction +/// occur correctly. +class CtorTester { + static std::set Constructed; + int Value; + +public: + explicit CtorTester(int Value = 0) : Value(Value) { + EXPECT_TRUE(Constructed.insert(this).second); + } + CtorTester(uint32_t Value) : Value(Value) { + EXPECT_TRUE(Constructed.insert(this).second); + } + CtorTester(const CtorTester &Arg) : Value(Arg.Value) { + EXPECT_TRUE(Constructed.insert(this).second); + } + CtorTester &operator=(const CtorTester &) = default; + ~CtorTester() { + EXPECT_EQ(1u, Constructed.erase(this)); + } + operator uint32_t() const { return Value; } + + int getValue() const { return Value; } + bool operator==(const CtorTester &RHS) const { return Value == RHS.Value; } +}; + +std::set CtorTester::Constructed; + +struct CtorTesterMapInfo { + static inline CtorTester getEmptyKey() { return CtorTester(-1); } + static inline CtorTester getTombstoneKey() { return CtorTester(-2); } + static unsigned getHashValue(const CtorTester &Val) { + return Val.getValue() * 37u; + } + static bool isEqual(const CtorTester &LHS, const CtorTester &RHS) { + return LHS == RHS; + } +}; + +CtorTester getTestKey(int i, CtorTester *) { return CtorTester(i); } +CtorTester getTestValue(int i, CtorTester *) { return CtorTester(42 + i); } + +// Test fixture, with helper functions implemented by forwarding to global +// function overloads selected by component types of the type parameter. This +// allows all of the map implementations to be tested with shared +// implementations of helper routines. +template +class DenseMapTest : public ::testing::Test { +protected: + T Map; + + static typename T::key_type *const dummy_key_ptr; + static typename T::mapped_type *const dummy_value_ptr; + + typename T::key_type getKey(int i = 0) { + return getTestKey(i, dummy_key_ptr); + } + typename T::mapped_type getValue(int i = 0) { + return getTestValue(i, dummy_value_ptr); + } +}; + +template +typename T::key_type *const DenseMapTest::dummy_key_ptr = nullptr; +template +typename T::mapped_type *const DenseMapTest::dummy_value_ptr = nullptr; + +// Register these types for testing. +typedef ::testing::Types, + DenseMap, + DenseMap, + SmallDenseMap, + SmallDenseMap, + SmallDenseMap + > DenseMapTestTypes; +TYPED_TEST_SUITE(DenseMapTest, DenseMapTestTypes, ); + +// Empty map tests +TYPED_TEST(DenseMapTest, EmptyIntMapTest) { + // Size tests + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + + // Iterator tests + EXPECT_TRUE(this->Map.begin() == this->Map.end()); + + // Lookup tests + EXPECT_FALSE(this->Map.count(this->getKey())); + EXPECT_TRUE(this->Map.find(this->getKey()) == this->Map.end()); + EXPECT_EQ(typename TypeParam::mapped_type(), + this->Map.lookup(this->getKey())); +} + +// Constant map tests +TYPED_TEST(DenseMapTest, ConstEmptyMapTest) { + const TypeParam &ConstMap = this->Map; + EXPECT_EQ(0u, ConstMap.size()); + EXPECT_TRUE(ConstMap.empty()); + EXPECT_TRUE(ConstMap.begin() == ConstMap.end()); +} + +// A map with a single entry +TYPED_TEST(DenseMapTest, SingleEntryMapTest) { + this->Map[this->getKey()] = this->getValue(); + + // Size tests + EXPECT_EQ(1u, this->Map.size()); + EXPECT_FALSE(this->Map.begin() == this->Map.end()); + EXPECT_FALSE(this->Map.empty()); + + // Iterator tests + typename TypeParam::iterator it = this->Map.begin(); + EXPECT_EQ(this->getKey(), it->first); + EXPECT_EQ(this->getValue(), it->second); + ++it; + EXPECT_TRUE(it == this->Map.end()); + + // Lookup tests + EXPECT_TRUE(this->Map.count(this->getKey())); + EXPECT_TRUE(this->Map.find(this->getKey()) == this->Map.begin()); + EXPECT_EQ(this->getValue(), this->Map.lookup(this->getKey())); + EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); +} + +// Test clear() method +TYPED_TEST(DenseMapTest, ClearTest) { + this->Map[this->getKey()] = this->getValue(); + this->Map.clear(); + + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + EXPECT_TRUE(this->Map.begin() == this->Map.end()); +} + +// Test erase(iterator) method +TYPED_TEST(DenseMapTest, EraseTest) { + this->Map[this->getKey()] = this->getValue(); + this->Map.erase(this->Map.begin()); + + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + EXPECT_TRUE(this->Map.begin() == this->Map.end()); +} + +// Test erase(value) method +TYPED_TEST(DenseMapTest, EraseTest2) { + this->Map[this->getKey()] = this->getValue(); + this->Map.erase(this->getKey()); + + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + EXPECT_TRUE(this->Map.begin() == this->Map.end()); +} + +// Test insert() method +TYPED_TEST(DenseMapTest, InsertTest) { + this->Map.insert(std::make_pair(this->getKey(), this->getValue())); + EXPECT_EQ(1u, this->Map.size()); + EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); +} + +// Test copy constructor method +TYPED_TEST(DenseMapTest, CopyConstructorTest) { + this->Map[this->getKey()] = this->getValue(); + TypeParam copyMap(this->Map); + + EXPECT_EQ(1u, copyMap.size()); + EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); +} + +// Test copy constructor method where SmallDenseMap isn't small. +TYPED_TEST(DenseMapTest, CopyConstructorNotSmallTest) { + for (int Key = 0; Key < 5; ++Key) + this->Map[this->getKey(Key)] = this->getValue(Key); + TypeParam copyMap(this->Map); + + EXPECT_EQ(5u, copyMap.size()); + for (int Key = 0; Key < 5; ++Key) + EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); +} + +// Test copying from a default-constructed map. +TYPED_TEST(DenseMapTest, CopyConstructorFromDefaultTest) { + TypeParam copyMap(this->Map); + + EXPECT_TRUE(copyMap.empty()); +} + +// Test copying from an empty map where SmallDenseMap isn't small. +TYPED_TEST(DenseMapTest, CopyConstructorFromEmptyTest) { + for (int Key = 0; Key < 5; ++Key) + this->Map[this->getKey(Key)] = this->getValue(Key); + this->Map.clear(); + TypeParam copyMap(this->Map); + + EXPECT_TRUE(copyMap.empty()); +} + +// Test assignment operator method +TYPED_TEST(DenseMapTest, AssignmentTest) { + this->Map[this->getKey()] = this->getValue(); + TypeParam copyMap = this->Map; + + EXPECT_EQ(1u, copyMap.size()); + EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); + + // test self-assignment. + copyMap = static_cast(copyMap); + EXPECT_EQ(1u, copyMap.size()); + EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); +} + +TYPED_TEST(DenseMapTest, AssignmentTestNotSmall) { + for (int Key = 0; Key < 5; ++Key) + this->Map[this->getKey(Key)] = this->getValue(Key); + TypeParam copyMap = this->Map; + + EXPECT_EQ(5u, copyMap.size()); + for (int Key = 0; Key < 5; ++Key) + EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); + + // test self-assignment. + copyMap = static_cast(copyMap); + EXPECT_EQ(5u, copyMap.size()); + for (int Key = 0; Key < 5; ++Key) + EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); +} + +// Test swap method +TYPED_TEST(DenseMapTest, SwapTest) { + this->Map[this->getKey()] = this->getValue(); + TypeParam otherMap; + + this->Map.swap(otherMap); + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + EXPECT_EQ(1u, otherMap.size()); + EXPECT_EQ(this->getValue(), otherMap[this->getKey()]); + + this->Map.swap(otherMap); + EXPECT_EQ(0u, otherMap.size()); + EXPECT_TRUE(otherMap.empty()); + EXPECT_EQ(1u, this->Map.size()); + EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); + + // Make this more interesting by inserting 100 numbers into the map. + for (int i = 0; i < 100; ++i) + this->Map[this->getKey(i)] = this->getValue(i); + + this->Map.swap(otherMap); + EXPECT_EQ(0u, this->Map.size()); + EXPECT_TRUE(this->Map.empty()); + EXPECT_EQ(100u, otherMap.size()); + for (int i = 0; i < 100; ++i) + EXPECT_EQ(this->getValue(i), otherMap[this->getKey(i)]); + + this->Map.swap(otherMap); + EXPECT_EQ(0u, otherMap.size()); + EXPECT_TRUE(otherMap.empty()); + EXPECT_EQ(100u, this->Map.size()); + for (int i = 0; i < 100; ++i) + EXPECT_EQ(this->getValue(i), this->Map[this->getKey(i)]); +} + +// A more complex iteration test +TYPED_TEST(DenseMapTest, IterationTest) { + bool visited[100]; + std::map visitedIndex; + + // Insert 100 numbers into the map + for (int i = 0; i < 100; ++i) { + visited[i] = false; + visitedIndex[this->getKey(i)] = i; + + this->Map[this->getKey(i)] = this->getValue(i); + } + + // Iterate over all numbers and mark each one found. + for (typename TypeParam::iterator it = this->Map.begin(); + it != this->Map.end(); ++it) + visited[visitedIndex[it->first]] = true; + + // Ensure every number was visited. + for (int i = 0; i < 100; ++i) + ASSERT_TRUE(visited[i]) << "Entry #" << i << " was never visited"; +} + +// const_iterator test +TYPED_TEST(DenseMapTest, ConstIteratorTest) { + // Check conversion from iterator to const_iterator. + typename TypeParam::iterator it = this->Map.begin(); + typename TypeParam::const_iterator cit(it); + EXPECT_TRUE(it == cit); + + // Check copying of const_iterators. + typename TypeParam::const_iterator cit2(cit); + EXPECT_TRUE(cit == cit2); +} + +namespace { +// Simple class that counts how many moves and copy happens when growing a map +struct CountCopyAndMove { + static int Move; + static int Copy; + CountCopyAndMove() {} + + CountCopyAndMove(const CountCopyAndMove &) { Copy++; } + CountCopyAndMove &operator=(const CountCopyAndMove &) { + Copy++; + return *this; + } + CountCopyAndMove(CountCopyAndMove &&) { Move++; } + CountCopyAndMove &operator=(const CountCopyAndMove &&) { + Move++; + return *this; + } +}; +int CountCopyAndMove::Copy = 0; +int CountCopyAndMove::Move = 0; + +} // anonymous namespace + +// Test initializer list construction. +TEST(DenseMapCustomTest, InitializerList) { + DenseMap M({{0, 0}, {0, 1}, {1, 2}}); + EXPECT_EQ(2u, M.size()); + EXPECT_EQ(1u, M.count(0)); + EXPECT_EQ(0, M[0]); + EXPECT_EQ(1u, M.count(1)); + EXPECT_EQ(2, M[1]); +} + +// Test initializer list construction. +TEST(DenseMapCustomTest, EqualityComparison) { + DenseMap M1({{0, 0}, {1, 2}}); + DenseMap M2({{0, 0}, {1, 2}}); + DenseMap M3({{0, 0}, {1, 3}}); + + EXPECT_EQ(M1, M2); + EXPECT_NE(M1, M3); +} + +// Test for the default minimum size of a DenseMap +TEST(DenseMapCustomTest, DefaultMinReservedSizeTest) { + // IF THIS VALUE CHANGE, please update InitialSizeTest, InitFromIterator, and + // ReserveTest as well! + const int ExpectedInitialBucketCount = 64; + // Formula from DenseMap::getMinBucketToReserveForEntries() + const int ExpectedMaxInitialEntries = ExpectedInitialBucketCount * 3 / 4 - 1; + + DenseMap Map; + // Will allocate 64 buckets + Map.reserve(1); + unsigned MemorySize = Map.getMemorySize(); + CountCopyAndMove::Copy = 0; + CountCopyAndMove::Move = 0; + for (int i = 0; i < ExpectedMaxInitialEntries; ++i) + Map.insert(std::pair(std::piecewise_construct, + std::forward_as_tuple(i), + std::forward_as_tuple())); + // Check that we didn't grow + EXPECT_EQ(MemorySize, Map.getMemorySize()); + // Check that move was called the expected number of times + EXPECT_EQ(ExpectedMaxInitialEntries, CountCopyAndMove::Move); + // Check that no copy occurred + EXPECT_EQ(0, CountCopyAndMove::Copy); + + // Adding one extra element should grow the map + Map.insert(std::pair( + std::piecewise_construct, + std::forward_as_tuple(ExpectedMaxInitialEntries), + std::forward_as_tuple())); + // Check that we grew + EXPECT_NE(MemorySize, Map.getMemorySize()); + // Check that move was called the expected number of times + // This relies on move-construction elision, and cannot be reliably tested. + // EXPECT_EQ(ExpectedMaxInitialEntries + 2, CountCopyAndMove::Move); + // Check that no copy occurred + EXPECT_EQ(0, CountCopyAndMove::Copy); +} + +// Make sure creating the map with an initial size of N actually gives us enough +// buckets to insert N items without increasing allocation size. +TEST(DenseMapCustomTest, InitialSizeTest) { + // Test a few different sizes, 48 is *not* a random choice: we need a value + // that is 2/3 of a power of two to stress the grow() condition, and the power + // of two has to be at least 64 because of minimum size allocation in the + // DenseMap (see DefaultMinReservedSizeTest). 66 is a value just above the + // 64 default init. + for (auto Size : {1, 2, 48, 66}) { + DenseMap Map(Size); + unsigned MemorySize = Map.getMemorySize(); + CountCopyAndMove::Copy = 0; + CountCopyAndMove::Move = 0; + for (int i = 0; i < Size; ++i) + Map.insert(std::pair(std::piecewise_construct, + std::forward_as_tuple(i), + std::forward_as_tuple())); + // Check that we didn't grow + EXPECT_EQ(MemorySize, Map.getMemorySize()); + // Check that move was called the expected number of times + EXPECT_EQ(Size, CountCopyAndMove::Move); + // Check that no copy occurred + EXPECT_EQ(0, CountCopyAndMove::Copy); + } +} + +// Make sure creating the map with a iterator range does not trigger grow() +TEST(DenseMapCustomTest, InitFromIterator) { + std::vector> Values; + // The size is a random value greater than 64 (hardcoded DenseMap min init) + const int Count = 65; + for (int i = 0; i < Count; i++) + Values.emplace_back(i, CountCopyAndMove()); + + CountCopyAndMove::Move = 0; + CountCopyAndMove::Copy = 0; + DenseMap Map(Values.begin(), Values.end()); + // Check that no move occurred + EXPECT_EQ(0, CountCopyAndMove::Move); + // Check that copy was called the expected number of times + EXPECT_EQ(Count, CountCopyAndMove::Copy); +} + +// Make sure reserve actually gives us enough buckets to insert N items +// without increasing allocation size. +TEST(DenseMapCustomTest, ReserveTest) { + // Test a few different size, 48 is *not* a random choice: we need a value + // that is 2/3 of a power of two to stress the grow() condition, and the power + // of two has to be at least 64 because of minimum size allocation in the + // DenseMap (see DefaultMinReservedSizeTest). 66 is a value just above the + // 64 default init. + for (auto Size : {1, 2, 48, 66}) { + DenseMap Map; + Map.reserve(Size); + unsigned MemorySize = Map.getMemorySize(); + CountCopyAndMove::Copy = 0; + CountCopyAndMove::Move = 0; + for (int i = 0; i < Size; ++i) + Map.insert(std::pair(std::piecewise_construct, + std::forward_as_tuple(i), + std::forward_as_tuple())); + // Check that we didn't grow + EXPECT_EQ(MemorySize, Map.getMemorySize()); + // Check that move was called the expected number of times + EXPECT_EQ(Size, CountCopyAndMove::Move); + // Check that no copy occurred + EXPECT_EQ(0, CountCopyAndMove::Copy); + } +} + +// Make sure DenseMap works with StringRef keys. +TEST(DenseMapCustomTest, StringRefTest) { + DenseMap M; + + M["a"] = 1; + M["b"] = 2; + M["c"] = 3; + + EXPECT_EQ(3u, M.size()); + EXPECT_EQ(1, M.lookup("a")); + EXPECT_EQ(2, M.lookup("b")); + EXPECT_EQ(3, M.lookup("c")); + + EXPECT_EQ(0, M.lookup("q")); + + // Test the empty string, spelled various ways. + EXPECT_EQ(0, M.lookup("")); + EXPECT_EQ(0, M.lookup(StringRef())); + EXPECT_EQ(0, M.lookup(StringRef("a", 0))); + M[""] = 42; + EXPECT_EQ(42, M.lookup("")); + EXPECT_EQ(42, M.lookup(StringRef())); + EXPECT_EQ(42, M.lookup(StringRef("a", 0))); +} + +// Key traits that allows lookup with either an unsigned or char* key; +// In the latter case, "a" == 0, "b" == 1 and so on. +struct TestDenseMapInfo { + static inline unsigned getEmptyKey() { return ~0; } + static inline unsigned getTombstoneKey() { return ~0U - 1; } + static unsigned getHashValue(const unsigned& Val) { return Val * 37U; } + static unsigned getHashValue(const char* Val) { + return (unsigned)(Val[0] - 'a') * 37U; + } + static bool isEqual(const unsigned& LHS, const unsigned& RHS) { + return LHS == RHS; + } + static bool isEqual(const char* LHS, const unsigned& RHS) { + return (unsigned)(LHS[0] - 'a') == RHS; + } +}; + +// find_as() tests +TEST(DenseMapCustomTest, FindAsTest) { + DenseMap map; + map[0] = 1; + map[1] = 2; + map[2] = 3; + + // Size tests + EXPECT_EQ(3u, map.size()); + + // Normal lookup tests + EXPECT_EQ(1u, map.count(1)); + EXPECT_EQ(1u, map.find(0)->second); + EXPECT_EQ(2u, map.find(1)->second); + EXPECT_EQ(3u, map.find(2)->second); + EXPECT_TRUE(map.find(3) == map.end()); + + // find_as() tests + EXPECT_EQ(1u, map.find_as("a")->second); + EXPECT_EQ(2u, map.find_as("b")->second); + EXPECT_EQ(3u, map.find_as("c")->second); + EXPECT_TRUE(map.find_as("d") == map.end()); +} + +TEST(DenseMapCustomTest, SmallDenseMapInitializerList) { + SmallDenseMap M = {{0, 0}, {0, 1}, {1, 2}}; + EXPECT_EQ(2u, M.size()); + EXPECT_EQ(1u, M.count(0)); + EXPECT_EQ(0, M[0]); + EXPECT_EQ(1u, M.count(1)); + EXPECT_EQ(2, M[1]); +} + +struct ContiguousDenseMapInfo { + static inline unsigned getEmptyKey() { return ~0; } + static inline unsigned getTombstoneKey() { return ~0U - 1; } + static unsigned getHashValue(const unsigned& Val) { return Val; } + static bool isEqual(const unsigned& LHS, const unsigned& RHS) { + return LHS == RHS; + } +}; + +// Test that filling a small dense map with exactly the number of elements in +// the map grows to have enough space for an empty bucket. +TEST(DenseMapCustomTest, SmallDenseMapGrowTest) { + SmallDenseMap map; + // Add some number of elements, then delete a few to leave us some tombstones. + // If we just filled the map with 32 elements we'd grow because of not enough + // tombstones which masks the issue here. + for (unsigned i = 0; i < 20; ++i) + map[i] = i + 1; + for (unsigned i = 0; i < 10; ++i) + map.erase(i); + for (unsigned i = 20; i < 32; ++i) + map[i] = i + 1; + + // Size tests + EXPECT_EQ(22u, map.size()); + + // Try to find an element which doesn't exist. There was a bug in + // SmallDenseMap which led to a map with num elements == small capacity not + // having an empty bucket any more. Finding an element not in the map would + // therefore never terminate. + EXPECT_TRUE(map.find(32) == map.end()); +} + +TEST(DenseMapCustomTest, LargeSmallDenseMapCompaction) { + SmallDenseMap map; + // Fill to < 3/4 load. + for (unsigned i = 0; i < 95; ++i) + map[i] = i; + // And erase, leaving behind tombstones. + for (unsigned i = 0; i < 95; ++i) + map.erase(i); + // Fill further, so that less than 1/8 are empty, but still below 3/4 load. + for (unsigned i = 95; i < 128; ++i) + map[i] = i; + + EXPECT_EQ(33u, map.size()); + // Similar to the previous test, check for a non-existing element, as an + // indirect check that tombstones have been removed. + EXPECT_TRUE(map.find(0) == map.end()); +} + +TEST(DenseMapCustomTest, TryEmplaceTest) { + DenseMap> Map; + std::unique_ptr P(new int(2)); + auto Try1 = Map.try_emplace(0, new int(1)); + EXPECT_TRUE(Try1.second); + auto Try2 = Map.try_emplace(0, std::move(P)); + EXPECT_FALSE(Try2.second); + EXPECT_EQ(Try1.first, Try2.first); + EXPECT_NE(nullptr, P); +} + +TEST(DenseMapCustomTest, ConstTest) { + // Test that const pointers work okay for count and find, even when the + // underlying map is a non-const pointer. + DenseMap Map; + int A; + int *B = &A; + const int *C = &A; + Map.insert({B, 0}); + EXPECT_EQ(Map.count(B), 1u); + EXPECT_EQ(Map.count(C), 1u); + EXPECT_NE(Map.find(B), Map.end()); + EXPECT_NE(Map.find(C), Map.end()); +} + +struct IncompleteStruct; + +TEST(DenseMapCustomTest, OpaquePointerKey) { + // Test that we can use a pointer to an incomplete type as a DenseMap key. + // This is an important build time optimization, since many classes have + // DenseMap members. + DenseMap Map; + int Keys[3] = {0, 0, 0}; + IncompleteStruct *K1 = reinterpret_cast(&Keys[0]); + IncompleteStruct *K2 = reinterpret_cast(&Keys[1]); + IncompleteStruct *K3 = reinterpret_cast(&Keys[2]); + Map.insert({K1, 1}); + Map.insert({K2, 2}); + Map.insert({K3, 3}); + EXPECT_EQ(Map.count(K1), 1u); + EXPECT_EQ(Map[K1], 1); + EXPECT_EQ(Map[K2], 2); + EXPECT_EQ(Map[K3], 3); + Map.clear(); + EXPECT_EQ(Map.find(K1), Map.end()); + EXPECT_EQ(Map.find(K2), Map.end()); + EXPECT_EQ(Map.find(K3), Map.end()); +} +}