[compiler-rt] [sanitizer_common] DenseMap: replace tombstone deletion with TAOCP 6.4 Algorithm R (PR #202231)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sun Jun 7 12:57:54 PDT 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/202231

sanitizer_dense_map.h is a fork of llvm/ADT/DenseMap.h, which uses
quadratic probing with lazy deletion: an erased entry becomes a
tombstone, a third bucket state alongside empty and live that every
find/insert must inspect.

Port the upstream #200595 and getTombstoneKey() removal.


>From f5565382df1cd68211cdd16ceb29b9b7a97fd328 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sun, 7 Jun 2026 12:54:09 -0700
Subject: [PATCH] [sanitizer_common] DenseMap: replace tombstone deletion with
 TAOCP 6.4 Algorithm R

sanitizer_dense_map.h is a fork of llvm/ADT/DenseMap.h, which uses
quadratic probing with lazy deletion: an erased entry becomes a
tombstone, a third bucket state alongside empty and live that every
find/insert must inspect.

Port the upstream #200595 and getTombstoneKey() removal.
---
 .../sanitizer_common/sanitizer_dense_map.h    | 152 +++++++-----------
 .../sanitizer_dense_map_info.h                |  25 ---
 .../lib/sanitizer_common/sanitizer_lzw.h      |   4 +-
 .../tests/sanitizer_dense_map_test.cpp        |  43 ++++-
 4 files changed, 99 insertions(+), 125 deletions(-)

diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h
index c63788653de75..9dc196de851f3 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map.h
@@ -44,10 +44,10 @@ class DenseMapBase {
   }
 
   void clear() {
-    if (getNumEntries() == 0 && getNumTombstones() == 0)
+    if (getNumEntries() == 0)
       return;
 
-    const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey();
+    const KeyT EmptyKey = getEmptyKey();
     if (__sanitizer::is_trivially_destructible<ValueT>::value) {
       // Use a simpler loop when values don't need destruction.
       for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P)
@@ -56,17 +56,14 @@ class DenseMapBase {
       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->getSecond().~ValueT();
+          --NumEntries;
           P->getFirst() = EmptyKey;
         }
       }
       CHECK_EQ(NumEntries, 0);
     }
     setNumEntries(0);
-    setNumTombstones(0);
   }
 
   /// Return true if the specified key is in the map, false otherwise.
@@ -171,20 +168,13 @@ class DenseMapBase {
     if (!TheBucket)
       return false;  // not in map.
 
-    TheBucket->getSecond().~ValueT();
-    TheBucket->getFirst() = getTombstoneKey();
-    decrementNumEntries();
-    incrementNumTombstones();
+    eraseFromFilledBucket(TheBucket);
     return true;
   }
 
   void erase(value_type *I) {
     CHECK_NE(I, nullptr);
-    BucketT *TheBucket = &*I;
-    TheBucket->getSecond().~ValueT();
-    TheBucket->getFirst() = getTombstoneKey();
-    decrementNumEntries();
-    incrementNumTombstones();
+    eraseFromFilledBucket(I);
   }
 
   value_type &FindAndConstruct(const KeyT &Key) {
@@ -214,11 +204,10 @@ class DenseMapBase {
   /// Function can return fast to stop the process.
   template <class Fn>
   void forEach(Fn fn) {
-    const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey();
+    const KeyT EmptyKey = getEmptyKey();
     for (auto *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) {
       const KeyT K = P->getFirst();
-      if (!KeyInfoT::isEqual(K, EmptyKey) &&
-          !KeyInfoT::isEqual(K, TombstoneKey)) {
+      if (!KeyInfoT::isEqual(K, EmptyKey)) {
         if (!fn(*P))
           return;
       }
@@ -238,10 +227,9 @@ class DenseMapBase {
     if (getNumBuckets() == 0)  // Nothing to do.
       return;
 
-    const KeyT EmptyKey = getEmptyKey(), TombstoneKey = getTombstoneKey();
+    const KeyT EmptyKey = getEmptyKey();
     for (BucketT *P = getBuckets(), *E = getBucketsEnd(); P != E; ++P) {
-      if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey) &&
-          !KeyInfoT::isEqual(P->getFirst(), TombstoneKey))
+      if (!KeyInfoT::isEqual(P->getFirst(), EmptyKey))
         P->getSecond().~ValueT();
       P->getFirst().~KeyT();
     }
@@ -249,7 +237,6 @@ class DenseMapBase {
 
   void initEmpty() {
     setNumEntries(0);
-    setNumTombstones(0);
 
     CHECK_EQ((getNumBuckets() & (getNumBuckets() - 1)), 0);
     const KeyT EmptyKey = getEmptyKey();
@@ -273,10 +260,8 @@ class DenseMapBase {
 
     // 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)) {
+      if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey)) {
         // Insert the key/value into the new table.
         BucketT *DestBucket;
         bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket);
@@ -301,7 +286,6 @@ class DenseMapBase {
     CHECK_EQ(getNumBuckets(), other.getNumBuckets());
 
     setNumEntries(other.getNumEntries());
-    setNumTombstones(other.getNumTombstones());
 
     if (__sanitizer::is_trivially_copyable<KeyT>::value &&
         __sanitizer::is_trivially_copyable<ValueT>::value)
@@ -311,8 +295,7 @@ class DenseMapBase {
       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()))
+        if (!KeyInfoT::isEqual(getBuckets()[i].getFirst(), getEmptyKey()))
           ::new (&getBuckets()[i].getSecond())
               ValueT(other.getBuckets()[i].getSecond());
       }
@@ -329,9 +312,40 @@ class DenseMapBase {
 
   static const KeyT getEmptyKey() { return KeyInfoT::getEmptyKey(); }
 
-  static const KeyT getTombstoneKey() { return KeyInfoT::getTombstoneKey(); }
-
  private:
+  /// Erase the entry at \p TheBucket and close the resulting hole via Knuth
+  /// TAOCP 6.4 Algorithm R: walk forward over the cluster, shifting back any
+  /// entry whose linear-probe chain from its home bucket passes through the
+  /// hole, until an empty bucket terminates the cluster.
+  void eraseFromFilledBucket(BucketT* TheBucket) {
+    TheBucket->getSecond().~ValueT();
+    decrementNumEntries();
+
+    BucketT* BucketsPtr = getBuckets();
+    const unsigned NumBuckets = getNumBuckets();
+    const unsigned Mask = NumBuckets - 1;
+    const KeyT EmptyKey = getEmptyKey();
+    unsigned I = static_cast<unsigned>(TheBucket - BucketsPtr);
+    unsigned J = I;
+    while (true) {
+      J = (J + 1) & Mask;
+      BucketT& BJ = BucketsPtr[J];
+      if (KeyInfoT::isEqual(BJ.getFirst(), EmptyKey))
+        break;
+      unsigned Ideal = getHashValue(BJ.getFirst()) & Mask;
+      // 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() = __sanitizer::move(BJ.getFirst());
+        ::new (&BI.getSecond()) ValueT(__sanitizer::move(BJ.getSecond()));
+        BJ.getSecond().~ValueT();
+        I = J;
+      }
+    }
+    BucketsPtr[I].getFirst() = EmptyKey;
+  }
+
   unsigned getNumEntries() const {
     return static_cast<const DerivedT *>(this)->getNumEntries();
   }
@@ -344,18 +358,6 @@ class DenseMapBase {
 
   void decrementNumEntries() { setNumEntries(getNumEntries() - 1); }
 
-  unsigned getNumTombstones() const {
-    return static_cast<const DerivedT *>(this)->getNumTombstones();
-  }
-
-  void setNumTombstones(unsigned Num) {
-    static_cast<DerivedT *>(this)->setNumTombstones(Num);
-  }
-
-  void incrementNumTombstones() { setNumTombstones(getNumTombstones() + 1); }
-
-  void decrementNumTombstones() { setNumTombstones(getNumTombstones() - 1); }
-
   const BucketT *getBuckets() const {
     return static_cast<const DerivedT *>(this)->getBuckets();
   }
@@ -398,25 +400,16 @@ class DenseMapBase {
   template <typename LookupKeyT>
   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.
+    // Grow the table if the load factor would exceed 3/4 after insertion.
+    // Linear probing with gap-closing deletion (Knuth Algorithm R) keeps every
+    // chain compact and bounded by the table's empty-bucket count, so no
+    // tombstone-driven resize is needed.
     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);
 
@@ -424,11 +417,6 @@ class DenseMapBase {
     // 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;
   }
 
@@ -441,7 +429,6 @@ class DenseMapBase {
 
     const KeyT EmptyKey = getEmptyKey();
     unsigned BucketNo = getHashValue(Val) & (NumBuckets - 1);
-    unsigned ProbeAmt = 1;
     while (true) {
       BucketT *Bucket = BucketsPtr + BucketNo;
       if (LIKELY(KeyInfoT::isEqual(Val, Bucket->getFirst())))
@@ -449,10 +436,8 @@ class DenseMapBase {
       if (LIKELY(KeyInfoT::isEqual(Bucket->getFirst(), EmptyKey)))
         return nullptr;
 
-      // Otherwise, it's a hash collision or a tombstone, continue quadratic
-      // probing.
-      BucketNo += ProbeAmt++;
-      BucketNo &= NumBuckets - 1;
+      // Hash collision: continue linear probing.
+      BucketNo = (BucketNo + 1) & (NumBuckets - 1);
     }
   }
 
@@ -463,8 +448,8 @@ class DenseMapBase {
 
   /// 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.
+  /// true, otherwise it returns a bucket with an empty marker and returns
+  /// false.
   template <typename LookupKeyT>
   bool LookupBucketFor(const LookupKeyT &Val,
                        const BucketT *&FoundBucket) const {
@@ -476,15 +461,10 @@ class DenseMapBase {
       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.
@@ -494,24 +474,14 @@ class DenseMapBase {
       }
 
       // If we found an empty bucket, the key doesn't exist in the set.
-      // Insert it and return the default value.
+      // Return it as the insertion point.
       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;
+        FoundBucket = 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);
+      // Hash collision: continue linear probing.
+      BucketNo = (BucketNo + 1) & (NumBuckets - 1);
     }
   }
 
@@ -587,7 +557,6 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
 
   BucketT *Buckets = nullptr;
   unsigned NumEntries = 0;
-  unsigned NumTombstones = 0;
   unsigned NumBuckets = 0;
 
  public:
@@ -614,7 +583,6 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
   void swap(DenseMap &RHS) {
     Swap(Buckets, RHS.Buckets);
     Swap(NumEntries, RHS.NumEntries);
-    Swap(NumTombstones, RHS.NumTombstones);
     Swap(NumBuckets, RHS.NumBuckets);
   }
 
@@ -639,7 +607,6 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
       this->BaseT::copyFrom(other);
     } else {
       NumEntries = 0;
-      NumTombstones = 0;
     }
   }
 
@@ -649,7 +616,6 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
       this->BaseT::initEmpty();
     } else {
       NumEntries = 0;
-      NumTombstones = 0;
     }
   }
 
@@ -675,10 +641,6 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
 
   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; }
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h
index f4640369ae588..a3c7b6d9571ed 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_dense_map_info.h
@@ -62,7 +62,6 @@ struct DenseMapPair {
 template <typename T>
 struct DenseMapInfo {
   // static T getEmptyKey();
-  // static T getTombstoneKey();
   // static unsigned getHashValue(const T &Val);
   // static bool isEqual(const T &LHS, const T &RHS);
 };
@@ -86,12 +85,6 @@ struct DenseMapInfo<T *> {
     return reinterpret_cast<T *>(Val);
   }
 
-  static constexpr T *getTombstoneKey() {
-    uptr Val = static_cast<uptr>(-2);
-    Val <<= Log2MaxAlign;
-    return reinterpret_cast<T *>(Val);
-  }
-
   static constexpr unsigned getHashValue(const T *PtrVal) {
     return (unsigned((uptr)PtrVal) >> 4) ^ (unsigned((uptr)PtrVal) >> 9);
   }
@@ -105,7 +98,6 @@ struct DenseMapInfo<T *> {
 template <>
 struct DenseMapInfo<char> {
   static constexpr char getEmptyKey() { return ~0; }
-  static constexpr char getTombstoneKey() { return ~0 - 1; }
   static constexpr unsigned getHashValue(const char &Val) { return Val * 37U; }
 
   static constexpr bool isEqual(const char &LHS, const char &RHS) {
@@ -117,7 +109,6 @@ struct DenseMapInfo<char> {
 template <>
 struct DenseMapInfo<unsigned char> {
   static constexpr unsigned char getEmptyKey() { return ~0; }
-  static constexpr unsigned char getTombstoneKey() { return ~0 - 1; }
   static constexpr unsigned getHashValue(const unsigned char &Val) {
     return Val * 37U;
   }
@@ -132,7 +123,6 @@ struct DenseMapInfo<unsigned char> {
 template <>
 struct DenseMapInfo<unsigned short> {
   static constexpr unsigned short getEmptyKey() { return 0xFFFF; }
-  static constexpr unsigned short getTombstoneKey() { return 0xFFFF - 1; }
   static constexpr unsigned getHashValue(const unsigned short &Val) {
     return Val * 37U;
   }
@@ -147,7 +137,6 @@ struct DenseMapInfo<unsigned short> {
 template <>
 struct DenseMapInfo<unsigned> {
   static constexpr unsigned getEmptyKey() { return ~0U; }
-  static constexpr unsigned getTombstoneKey() { return ~0U - 1; }
   static constexpr unsigned getHashValue(const unsigned &Val) {
     return Val * 37U;
   }
@@ -161,7 +150,6 @@ struct DenseMapInfo<unsigned> {
 template <>
 struct DenseMapInfo<unsigned long> {
   static constexpr unsigned long getEmptyKey() { return ~0UL; }
-  static constexpr unsigned long getTombstoneKey() { return ~0UL - 1L; }
 
   static constexpr unsigned getHashValue(const unsigned long &Val) {
     return (unsigned)(Val * 37UL);
@@ -177,7 +165,6 @@ struct DenseMapInfo<unsigned long> {
 template <>
 struct DenseMapInfo<unsigned long long> {
   static constexpr unsigned long long getEmptyKey() { return ~0ULL; }
-  static constexpr unsigned long long getTombstoneKey() { return ~0ULL - 1ULL; }
 
   static constexpr unsigned getHashValue(const unsigned long long &Val) {
     return (unsigned)(Val * 37ULL);
@@ -193,7 +180,6 @@ struct DenseMapInfo<unsigned long long> {
 template <>
 struct DenseMapInfo<short> {
   static constexpr short getEmptyKey() { return 0x7FFF; }
-  static constexpr short getTombstoneKey() { return -0x7FFF - 1; }
   static constexpr unsigned getHashValue(const short &Val) { return Val * 37U; }
   static constexpr bool isEqual(const short &LHS, const short &RHS) {
     return LHS == RHS;
@@ -204,7 +190,6 @@ struct DenseMapInfo<short> {
 template <>
 struct DenseMapInfo<int> {
   static constexpr int getEmptyKey() { return 0x7fffffff; }
-  static constexpr int getTombstoneKey() { return -0x7fffffff - 1; }
   static constexpr unsigned getHashValue(const int &Val) {
     return (unsigned)(Val * 37U);
   }
@@ -221,8 +206,6 @@ struct DenseMapInfo<long> {
     return (1UL << (sizeof(long) * 8 - 1)) - 1UL;
   }
 
-  static constexpr long getTombstoneKey() { return getEmptyKey() - 1L; }
-
   static constexpr unsigned getHashValue(const long &Val) {
     return (unsigned)(Val * 37UL);
   }
@@ -236,9 +219,6 @@ struct DenseMapInfo<long> {
 template <>
 struct DenseMapInfo<long long> {
   static constexpr long long getEmptyKey() { return 0x7fffffffffffffffLL; }
-  static constexpr long long getTombstoneKey() {
-    return -0x7fffffffffffffffLL - 1;
-  }
 
   static constexpr unsigned getHashValue(const long long &Val) {
     return (unsigned)(Val * 37ULL);
@@ -261,11 +241,6 @@ struct DenseMapInfo<detail::DenseMapPair<T, U>> {
                                       SecondInfo::getEmptyKey());
   }
 
-  static constexpr Pair getTombstoneKey() {
-    return detail::DenseMapPair<T, U>(FirstInfo::getTombstoneKey(),
-                                      SecondInfo::getTombstoneKey());
-  }
-
   static constexpr unsigned getHashValue(const Pair &PairVal) {
     return detail::combineHashValue(FirstInfo::getHashValue(PairVal.first),
                                     SecondInfo::getHashValue(PairVal.second));
diff --git a/compiler-rt/lib/sanitizer_common/sanitizer_lzw.h b/compiler-rt/lib/sanitizer_common/sanitizer_lzw.h
index 42acfbdcea092..d8e7d1f217627 100644
--- a/compiler-rt/lib/sanitizer_common/sanitizer_lzw.h
+++ b/compiler-rt/lib/sanitizer_common/sanitizer_lzw.h
@@ -26,9 +26,7 @@ ItOut LzwEncode(ItIn begin, ItIn end, ItOut out) {
 
   // Sentinel value for substrings of len 1.
   static constexpr LzwCodeType kNoPrefix =
-      Min(DenseMapInfo<Substring>::getEmptyKey().first,
-          DenseMapInfo<Substring>::getTombstoneKey().first) -
-      1;
+      DenseMapInfo<Substring>::getEmptyKey().first - 1;
   DenseMap<Substring, LzwCodeType> prefix_to_code;
   {
     // Add all substring of len 1 as initial dictionary.
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
index 1336f1d85eac7..f122c32d1124e 100644
--- a/compiler-rt/lib/sanitizer_common/tests/sanitizer_dense_map_test.cpp
+++ b/compiler-rt/lib/sanitizer_common/tests/sanitizer_dense_map_test.cpp
@@ -83,7 +83,6 @@ std::set<CtorTester *> 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;
   }
@@ -476,7 +475,6 @@ TEST(DenseMapCustomTest, ReserveTest) {
 // 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;
@@ -489,6 +487,47 @@ struct TestDenseMapInfo {
   }
 };
 
+// Hashes every key to the same home bucket, so the table degenerates into a
+// single long linear-probe cluster -- the worst case for Algorithm R
+// backward-shift erase.
+struct CollidingMapInfo {
+  static inline unsigned getEmptyKey() { return ~0U; }
+  static unsigned getHashValue(const unsigned&) { return 0; }
+  static bool isEqual(const unsigned& LHS, const unsigned& RHS) {
+    return LHS == RHS;
+  }
+};
+
+// Erase in a strided order via the pointer overload (the one LSan uses), and
+// after every erase confirm the full key set: surviving keys must be relocated
+// yet stay findable with their value, erased keys must be absent.
+TEST(DenseMapCustomTest, EraseStressColliding) {
+  DenseMap<unsigned, unsigned, CollidingMapInfo> Map;
+  const unsigned N = 200;
+  for (unsigned i = 0; i < N; ++i) Map[i] = i * 7 + 1;
+
+  std::set<unsigned> Erased;
+  unsigned Idx = 0;
+  for (unsigned Step = 0; Step < N; ++Step) {
+    Idx = (Idx + 37) % N;
+    while (Erased.count(Idx)) Idx = (Idx + 1) % N;
+    Map.erase(Map.find(Idx));
+    Erased.insert(Idx);
+    EXPECT_EQ(N - Erased.size(), Map.size());
+
+    for (unsigned K = 0; K < N; ++K) {
+      auto* B = Map.find(K);
+      if (Erased.count(K)) {
+        EXPECT_EQ(nullptr, B);
+      } else {
+        ASSERT_NE(nullptr, B);
+        EXPECT_EQ(K * 7 + 1, B->second);
+      }
+    }
+  }
+  EXPECT_TRUE(Map.empty());
+}
+
 // find_as() tests
 TEST(DenseMapCustomTest, FindAsTest) {
   DenseMap<unsigned, unsigned, TestDenseMapInfo> map;



More information about the llvm-commits mailing list