[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