[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