[llvm] [DenseMap] Store occupancy in a packed used-bit array (PR #201281)

via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 3 00:36:24 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-adt

Author: Fangrui Song (MaskRay)

<details>
<summary>Changes</summary>

Track bucket occupancy in a packed 1-bit-per-bucket "used" array
(uint32_t words) instead of an `Empty` sentinel key. The buckets and the
used array share one allocation. The probing scheme is unchanged.
(uint64_t words lead to slightly larger clang binary.)

Empty buckets are now raw storage: insert placement-news the key/value
and erase destroys them. Any key value is therefore storable --
`DenseMap<unsigned, X>` can use all keys -- which lets `DenseMapInfo`
drop `getEmptyKey` in a follow-up.

This helps find-miss and iteration (the empty terminus and the empty
buckets become a bit test, not a bucket load; for large keys it also
skips the structural compare against the empty key) and large-bucket
insert. It costs find-hit (the matched bucket is loaded either way, so
the bit is pure overhead) and, for small buckets, fill/insert (the
per-insert used-bit write).

The net is a slight stage2 compile-time regression (instructions:u
+0.10% -O3, +0.24% -O0-g); the find-miss and iteration gains are cache
effects an instruction-count metric does not see.

The regression concentrates in hot (pointer key, sizeof(bucket) <= 16)
instantiations; a follow-up may move those to a separate pointer-keyed
container that keeps the in-band sentinel, reclaiming the loss.

getMemorySize now also counts the used array; the InitSize tests are updated and
the BitVectorTest DenseSet test no longer expects inserting a default key to
abort.

Aided by Claude Opus 4.8


---

Patch is 43.15 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/201281.diff


4 Files Affected:

- (modified) llvm/docs/ProgrammersManual.rst (+2-2) 
- (modified) llvm/include/llvm/ADT/DenseMap.h (+358-220) 
- (modified) llvm/unittests/ADT/BitVectorTest.cpp (+6-3) 
- (modified) llvm/unittests/ADT/DenseMapTest.cpp (+18-12) 


``````````diff
diff --git a/llvm/docs/ProgrammersManual.rst b/llvm/docs/ProgrammersManual.rst
index ce786a5b7b681..ddb9c57ad4e2a 100644
--- a/llvm/docs/ProgrammersManual.rst
+++ b/llvm/docs/ProgrammersManual.rst
@@ -2170,7 +2170,7 @@ copy-construction, which :ref:`SmallSet <dss_smallset>` and :ref:`SmallPtrSet
 llvm/ADT/DenseSet.h
 ^^^^^^^^^^^^^^^^^^^
 
-``DenseSet`` is a simple quadratically probed hash table.  It excels at supporting
+``DenseSet`` is a simple linearly probed hash table.  It excels at supporting
 small values: it uses a single allocation to hold all of the pairs that are
 currently inserted in the set.  ``DenseSet`` is a great way to unique small values
 that are not simple pointers (use :ref:`SmallPtrSet <dss_smallptrset>` for
@@ -2416,7 +2416,7 @@ virtual register ID).
 llvm/ADT/DenseMap.h
 ^^^^^^^^^^^^^^^^^^^
 
-``DenseMap`` is a simple quadratically probed hash table.  It excels at supporting
+``DenseMap`` is a simple linearly probed hash table.  It excels at supporting
 small keys and values: it uses a single allocation to hold all of the pairs
 that are currently inserted in the map.  ``DenseMap`` is a great way to map
 pointers to pointers, or map other small types to each other.
diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index b52906a215a7c..5759982d33dff 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -9,6 +9,13 @@
 /// \file
 /// This file defines the DenseMap class.
 ///
+/// The hash table is linear-probing open addressing with tombstone-free
+/// deletion (Knuth TAOCP 6.4 Algorithm R), power-of-two capacity, and a 0.75
+/// maximum load factor. No sentinel key. Occupancy is stored in a packed
+/// 1-bit-per-bucket "used" array.
+///
+/// `SmallDenseMap` adds an inline small buffer optimization.
+///
 //===----------------------------------------------------------------------===//
 
 #ifndef LLVM_ADT_DENSEMAP_H
@@ -28,6 +35,7 @@
 #include <algorithm>
 #include <cassert>
 #include <cstddef>
+#include <cstdint>
 #include <cstring>
 #include <initializer_list>
 #include <iterator>
@@ -53,6 +61,53 @@ struct DenseMapPair : std::pair<KeyT, ValueT> {
 
 } // end namespace detail
 
+namespace densemap::detail {
+using used_t = uint32_t;
+
+// Number of used words backing N buckets where N is zero or a power of two.
+constexpr size_t usedWords(size_t N) {
+  assert((N == 0 || isPowerOf2_64(N)) &&
+         "bucket count must be zero or a power of two");
+  return (N + 31) / 32;
+}
+
+inline bool used(const used_t *U, size_t I) {
+  return (U[I >> 5] >> (I & 31)) & 1;
+}
+inline void setUsed(used_t *U, size_t I) { U[I >> 5] |= used_t(1) << (I & 31); }
+inline void unsetUsed(used_t *U, size_t I) {
+  U[I >> 5] &= ~(used_t(1) << (I & 31));
+}
+
+// Invoke Func(I) for each occupied bucket index I in [0, N). Set always_inline;
+// otherwise, for a heavy caller such as moveFrom's rehash, the inliner can
+// leave it out of line and the per-element call dwarfs the work.
+template <typename Fn>
+LLVM_ATTRIBUTE_ALWAYS_INLINE void forEachUsed(const used_t *U, unsigned N,
+                                              Fn Func) {
+  const unsigned NW = usedWords(N);
+  for (unsigned W = 0; W != NW; ++W) {
+    used_t Bits = U[W];
+    while (Bits) {
+      Func((W << 5) + llvm::countr_zero(Bits));
+      Bits &= Bits - 1;
+    }
+  }
+}
+
+// Buckets and the used array share one allocation: the bucket array first, then
+// the used words.  NumBuckets is a power of two >= 4, so the bucket region size
+// is a multiple of sizeof(used_t) and the trailing used words are aligned.
+template <typename BucketT> constexpr size_t allocAlign() {
+  return std::max(alignof(BucketT), alignof(used_t));
+}
+template <typename BucketT> size_t allocBytes(unsigned Num) {
+  return sizeof(BucketT) * static_cast<size_t>(Num) +
+         usedWords(Num) * sizeof(used_t);
+}
+
+} // namespace densemap::detail
+
 // Befriended below so DenseMapBase can expose its bucket-relocation callback
 // erase to ValueHandleBase, the only caller that caches bucket pointers.
 class ValueHandleBase;
@@ -69,6 +124,8 @@ class DenseMapBase : public DebugEpochBase {
   template <typename T>
   using const_arg_type_t = typename const_pointer_or_const_ref<T>::type;
 
+  using used_t = llvm::densemap::detail::used_t;
+
 public:
   using size_type = unsigned;
   using key_type = KeyT;
@@ -80,16 +137,19 @@ class DenseMapBase : public DebugEpochBase {
       DenseMapIterator<KeyT, ValueT, KeyInfoT, BucketT, true>;
 
   [[nodiscard]] inline iterator begin() {
-    return iterator::makeBegin(buckets(), empty(), *this);
+    return iterator::makeBegin(getBuckets(), getUsed(), getNumBuckets(),
+                               empty(), *this);
   }
   [[nodiscard]] inline iterator end() {
-    return iterator::makeEnd(buckets(), *this);
+    return iterator::makeEnd(getBuckets(), getUsed(), getNumBuckets(), *this);
   }
   [[nodiscard]] inline const_iterator begin() const {
-    return const_iterator::makeBegin(buckets(), empty(), *this);
+    return const_iterator::makeBegin(getBuckets(), getUsed(), getNumBuckets(),
+                                     empty(), *this);
   }
   [[nodiscard]] inline const_iterator end() const {
-    return const_iterator::makeEnd(buckets(), *this);
+    return const_iterator::makeEnd(getBuckets(), getUsed(), getNumBuckets(),
+                                   *this);
   }
 
   // Return an iterator to iterate over keys in the map.
@@ -134,23 +194,10 @@ class DenseMapBase : public DebugEpochBase {
       return;
     }
 
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    if constexpr (std::is_trivially_destructible_v<ValueT>) {
-      // Use a simpler loop when values don't need destruction.
-      for (BucketT &B : buckets())
-        B.getFirst() = EmptyKey;
-    } else {
-      unsigned NumEntries = getNumEntries();
-      for (BucketT &B : buckets()) {
-        if (!KeyInfoT::isEqual(B.getFirst(), EmptyKey)) {
-          B.getSecond().~ValueT();
-          --NumEntries;
-          B.getFirst() = EmptyKey;
-        }
-      }
-      assert(NumEntries == 0 && "Node count imbalance!");
-      (void)NumEntries;
-    }
+    destroyAll();
+    std::memset(getUsed(), 0,
+                llvm::densemap::detail::usedWords(getNumBuckets()) *
+                    sizeof(used_t));
     setNumEntries(0);
   }
 
@@ -277,7 +324,7 @@ class DenseMapBase : public DebugEpochBase {
 
     // Otherwise, insert the new element.
     TheBucket = findBucketForInsertion(Val, TheBucket);
-    TheBucket->getFirst() = std::move(KV.first);
+    ::new (&TheBucket->getFirst()) KeyT(std::move(KV.first));
     ::new (&TheBucket->getSecond()) ValueT(std::move(KV.second));
     return {makeIterator(TheBucket), true};
   }
@@ -346,15 +393,17 @@ class DenseMapBase : public DebugEpochBase {
   /// Returns whether anything was removed. If so, all iterators and references
   /// into the map are invalidated.
   template <typename Predicate> bool remove_if(Predicate Pred) {
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
+    used_t *U = getUsed();
     unsigned NumBuckets = getNumBuckets();
+    BucketT *B = getBuckets();
     bool Removed = false;
-    for (BucketT &B : buckets()) {
-      if (KeyInfoT::isEqual(B.getFirst(), EmptyKey))
+    for (unsigned I = 0; I != NumBuckets; ++I) {
+      if (!llvm::densemap::detail::used(U, I))
         continue;
-      if (Pred(B)) {
-        B.getSecond().~ValueT();
-        B.getFirst() = EmptyKey;
+      if (Pred(B[I])) {
+        B[I].getSecond().~ValueT();
+        B[I].getFirst().~KeyT();
+        llvm::densemap::detail::unsetUsed(U, I);
         decrementNumEntries();
         Removed = true;
       }
@@ -398,6 +447,15 @@ class DenseMapBase : public DebugEpochBase {
 
   struct ExactBucketCount {};
 
+  // A snapshot of the three fields the hot lookup paths need. Fetching them
+  // together lets SmallDenseMap test its Small discriminator once rather than
+  // once per accessor; for plain DenseMap it is three member loads either way.
+  struct Rep {
+    const BucketT *Buckets;
+    const used_t *Used;
+    unsigned NumBuckets;
+  };
+
   void initWithExactBucketCount(unsigned NewNumBuckets) {
     if (derived().allocateBuckets(NewNumBuckets))
       initEmpty();
@@ -415,12 +473,13 @@ class DenseMapBase : public DebugEpochBase {
     if (getNumBuckets() == 0) // Nothing to do.
       return;
 
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    for (BucketT &B : buckets()) {
-      if (!KeyInfoT::isEqual(B.getFirst(), EmptyKey))
-        B.getSecond().~ValueT();
-      B.getFirst().~KeyT();
-    }
+    BucketT *B = getBuckets();
+    const used_t *U = getUsed();
+    const unsigned E = getNumBuckets();
+    llvm::densemap::detail::forEachUsed(U, E, [&](unsigned I) {
+      B[I].getSecond().~ValueT();
+      B[I].getFirst().~KeyT();
+    });
   }
 
   void initEmpty() {
@@ -430,9 +489,9 @@ class DenseMapBase : public DebugEpochBase {
 
     assert((getNumBuckets() & (getNumBuckets() - 1)) == 0 &&
            "# initial buckets must be a power of two!");
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    for (BucketT &B : buckets())
-      ::new (&B.getFirst()) KeyT(EmptyKey);
+    std::memset(getUsed(), 0,
+                llvm::densemap::detail::usedWords(getNumBuckets()) *
+                    sizeof(used_t));
   }
 
   /// Returns the number of buckets to allocate to ensure that the DenseMap can
@@ -448,29 +507,34 @@ class DenseMapBase : public DebugEpochBase {
 
   // Move key/value from Other to *this.
   // Other is left in a valid but empty state.
-  void moveFrom(DerivedT &Other) {
-    // Insert all the old elements.
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    for (BucketT &B : Other.buckets()) {
-      if (!KeyInfoT::isEqual(B.getFirst(), EmptyKey)) {
-        // 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();
-    }
+  LLVM_ATTRIBUTE_NOINLINE void moveFrom(DerivedT &Other) {
+    assert(getNumEntries() == 0 && "moveFrom requires an empty destination");
+    BucketT *OtherB = Other.getBuckets();
+    used_t *OtherU = Other.getUsed();
+    const unsigned E = Other.getNumBuckets();
+    used_t *U = getUsed();
+    BucketT *B = getBuckets();
+    const unsigned Mask = getNumBuckets() - 1;
+    llvm::densemap::detail::forEachUsed(OtherU, E, [&](unsigned I) {
+      // Find the first empty slot on this key's probe chain; there is no equal
+      // key in the destination, so nothing to compare against.
+      unsigned BucketNo = KeyInfoT::getHashValue(OtherB[I].getFirst()) & Mask;
+      while (llvm::densemap::detail::used(U, BucketNo))
+        BucketNo = (BucketNo + 1) & Mask;
+      BucketT *DestBucket = B + BucketNo;
+      ::new (&DestBucket->getFirst()) KeyT(std::move(OtherB[I].getFirst()));
+      ::new (&DestBucket->getSecond()) ValueT(std::move(OtherB[I].getSecond()));
+      llvm::densemap::detail::setUsed(U, BucketNo);
+
+      // Free the moved-out key/value.
+      OtherB[I].getSecond().~ValueT();
+      OtherB[I].getFirst().~KeyT();
+    });
+    setNumEntries(Other.getNumEntries());
     Other.derived().kill();
   }
 
-  void copyFrom(const DerivedT &other) {
+  LLVM_ATTRIBUTE_NOINLINE void copyFrom(const DerivedT &other) {
     this->destroyAll();
     derived().deallocateBuckets();
     setNumEntries(0);
@@ -486,18 +550,20 @@ class DenseMapBase : public DebugEpochBase {
 
     BucketT *Buckets = getBuckets();
     const BucketT *OtherBuckets = other.getBuckets();
-    const size_t NumBuckets = getNumBuckets();
+    const unsigned NumBuckets = getNumBuckets();
+    used_t *U = getUsed();
+    const used_t *OtherU = other.getUsed();
+    std::memcpy(U, OtherU,
+                llvm::densemap::detail::usedWords(NumBuckets) * sizeof(used_t));
     if constexpr (std::is_trivially_copyable_v<KeyT> &&
                   std::is_trivially_copyable_v<ValueT>) {
       memcpy(reinterpret_cast<void *>(Buckets), OtherBuckets,
              NumBuckets * sizeof(BucketT));
     } else {
-      const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-      for (size_t I = 0; I < NumBuckets; ++I) {
+      llvm::densemap::detail::forEachUsed(U, NumBuckets, [&](unsigned I) {
         ::new (&Buckets[I].getFirst()) KeyT(OtherBuckets[I].getFirst());
-        if (!KeyInfoT::isEqual(Buckets[I].getFirst(), EmptyKey))
-          ::new (&Buckets[I].getSecond()) ValueT(OtherBuckets[I].getSecond());
-      }
+        ::new (&Buckets[I].getSecond()) ValueT(OtherBuckets[I].getSecond());
+      });
     }
   }
 
@@ -511,35 +577,37 @@ class DenseMapBase : public DebugEpochBase {
   /// TAOCP 6.4 Algorithm R. For callers that cache pointers into the bucket
   /// array, call \p OnMoved per shifted bucket.
   template <typename OnMovedT>
-  void eraseFromFilledBucket(BucketT *TheBucket, OnMovedT &&OnMoved) {
+  LLVM_ATTRIBUTE_NOINLINE void eraseFromFilledBucket(BucketT *TheBucket,
+                                                     OnMovedT &&OnMoved) {
     incrementEpoch();
     TheBucket->getSecond().~ValueT();
+    TheBucket->getFirst().~KeyT();
     decrementNumEntries();
 
     BucketT *BucketsPtr = getBuckets();
-    const unsigned NumBuckets = getNumBuckets();
-    const unsigned Mask = NumBuckets - 1;
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    unsigned I = static_cast<unsigned>(TheBucket - BucketsPtr);
+    used_t *U = getUsed();
+    const unsigned Mask = getNumBuckets() - 1;
+    unsigned I = TheBucket - BucketsPtr;
     unsigned J = I;
     while (true) {
       J = (J + 1) & Mask;
       BucketT &BJ = BucketsPtr[J];
-      if (KeyInfoT::isEqual(BJ.getFirst(), EmptyKey))
+      if (!llvm::densemap::detail::used(U, J))
         break;
       auto Ideal = KeyInfoT::getHashValue(BJ.getFirst());
       // If the hole (I) lies on the linear-probe chain from the home bucket
       // (Ideal) to J, shift J into the hole and make J the new hole.
       if (((I - Ideal) & Mask) < ((J - Ideal) & Mask)) {
         BucketT &BI = BucketsPtr[I];
-        BI.getFirst() = std::move(BJ.getFirst());
+        ::new (&BI.getFirst()) KeyT(std::move(BJ.getFirst()));
         ::new (&BI.getSecond()) ValueT(std::move(BJ.getSecond()));
         BJ.getSecond().~ValueT();
+        BJ.getFirst().~KeyT();
         OnMoved(BI);
         I = J;
       }
     }
-    BucketsPtr[I].getFirst() = EmptyKey;
+    llvm::densemap::detail::unsetUsed(U, I);
   }
 
   /// Erase \p Val and close the resulting hole by potentially shifting other
@@ -567,7 +635,7 @@ class DenseMapBase : public DebugEpochBase {
 
     // Otherwise, insert the new element.
     TheBucket = findBucketForInsertion(Key, TheBucket);
-    TheBucket->getFirst() = std::forward<KeyArgT>(Key);
+    ::new (&TheBucket->getFirst()) KeyT(std::forward<KeyArgT>(Key));
     ::new (&TheBucket->getSecond()) ValueT(std::forward<Ts>(Args)...);
     return {TheBucket, true};
   }
@@ -580,11 +648,13 @@ class DenseMapBase : public DebugEpochBase {
   }
 
   iterator makeIterator(BucketT *TheBucket) {
-    return iterator::makeIterator(TheBucket, buckets(), *this);
+    return iterator::makeIterator(TheBucket, getBuckets(), getUsed(),
+                                  getNumBuckets(), *this);
   }
 
   const_iterator makeConstIterator(const BucketT *TheBucket) const {
-    return const_iterator::makeIterator(TheBucket, buckets(), *this);
+    return const_iterator::makeIterator(TheBucket, getBuckets(), getUsed(),
+                                        getNumBuckets(), *this);
   }
 
   unsigned getNumEntries() const { return derived().getNumEntries(); }
@@ -599,6 +669,12 @@ class DenseMapBase : public DebugEpochBase {
 
   BucketT *getBuckets() { return derived().getBuckets(); }
 
+  Rep getRep() const { return derived().getRep(); }
+
+  const used_t *getUsed() const { return derived().getUsed(); }
+
+  used_t *getUsed() { return derived().getUsed(); }
+
   unsigned getNumBuckets() const { return derived().getNumBuckets(); }
 
   BucketT *getBucketsEnd() { return getBuckets() + getNumBuckets(); }
@@ -607,15 +683,7 @@ class DenseMapBase : public DebugEpochBase {
     return getBuckets() + getNumBuckets();
   }
 
-  iterator_range<BucketT *> buckets() {
-    return llvm::make_range(getBuckets(), getBucketsEnd());
-  }
-
-  iterator_range<const BucketT *> buckets() const {
-    return llvm::make_range(getBuckets(), getBucketsEnd());
-  }
-
-  void grow(unsigned MinNumBuckets) {
+  LLVM_ATTRIBUTE_NOINLINE void grow(unsigned MinNumBuckets) {
     unsigned NumBuckets = DerivedT::roundUpNumBuckets(MinNumBuckets);
     DerivedT Tmp(NumBuckets, ExactBucketCount{});
     Tmp.moveFrom(derived());
@@ -642,6 +710,9 @@ class DenseMapBase : public DebugEpochBase {
     }
     assert(TheBucket);
 
+    // Mark used. The caller will placement-construct the raw key/value.
+    llvm::densemap::detail::setUsed(getUsed(), TheBucket - getBuckets());
+
     // Only update the state after we've grown our bucket space appropriately
     // so that when growing buckets we have self-consistent entry count.
     incrementNumEntries();
@@ -650,22 +721,22 @@ class DenseMapBase : public DebugEpochBase {
 
   template <typename LookupKeyT>
   const BucketT *doFind(const LookupKeyT &Val) const {
-    const BucketT *BucketsPtr = getBuckets();
-    const unsigned NumBuckets = getNumBuckets();
+    auto [BucketsPtr, U, NumBuckets] = getRep();
     if (NumBuckets == 0)
       return nullptr;
 
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    unsigned BucketNo = KeyInfoT::getHashValue(Val) & (NumBuckets - 1);
+    const unsigned Mask = NumBuckets - 1;
+    unsigned BucketNo = KeyInfoT::getHashValue(Val) & Mask;
     while (true) {
+      // An empty bucket terminates the probe: the key isn't in the map.
+      if (LLVM_LIKELY(!llvm::densemap::detail::used(U, BucketNo)))
+        return nullptr;
       const BucketT *Bucket = BucketsPtr + BucketNo;
       if (LLVM_LIKELY(KeyInfoT::isEqual(Val, Bucket->getFirst())))
         return Bucket;
-      if (LLVM_LIKELY(KeyInfoT::isEqual(Bucket->getFirst(), EmptyKey)))
-        return nullptr;
 
       // Hash collision: continue linear probing.
-      BucketNo = (BucketNo + 1) & (NumBuckets - 1);
+      BucketNo = (BucketNo + 1) & Mask;
     }
   }
 
@@ -679,36 +750,34 @@ class DenseMapBase : public DebugEpochBase {
   /// returns a bucket with an empty marker and returns false.
   template <typename LookupKeyT>
   bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
-    BucketT *BucketsPtr = getBuckets();
-    const unsigned NumBuckets = getNumBuckets();
-
+    auto [CBuckets, U, NumBuckets] = getRep();
     if (NumBuckets == 0) {
       FoundBucket = nullptr;
       return false;
     }
+    // getRep() yields const pointers; this object is non-const, so recovering
+    // a mutable bucket pointer is safe (mirrors the non-const getBuckets()).
+    BucketT *BucketsPtr = const_cast<BucketT *>(CBuckets);
 
-    const KeyT EmptyKey = KeyInfoT::getEmptyKey();
-    assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
-           "Empty value shouldn't be inserted into map!");
-
-    unsigned BucketNo = KeyInfoT::getHashValue(Val) & (NumBuckets - 1);
+    const unsigned Mask = NumBuckets - 1;
+    unsigned BucketNo = KeyInfoT::getHashValue(Val) & Mask;
     while (true) {
       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.
       // Return it as the insertion point.
-      if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
+      if (LLVM_LIKELY(!llvm::densemap::detail::used(U, BucketNo))) {
         FoundBucket = ThisBucket;
         return false;
       }
 
+      // Found Val's bucket?  If so, return it.
+      if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
+        FoundBucket = ThisBucket;
+   ...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/201281


More information about the llvm-commits mailing list