[llvm] a98dcad - [ADT] Extract DenseMapStorage from DenseMap (NFC) (#226664)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 27 21:03:00 PDT 2026
Author: Kazu Hirata
Date: 2026-09-27T21:02:54-07:00
New Revision: a98dcad4fed01b7d1d2f3a5306a30d658059c431
URL: https://github.com/llvm/llvm-project/commit/a98dcad4fed01b7d1d2f3a5306a30d658059c431
DIFF: https://github.com/llvm/llvm-project/commit/a98dcad4fed01b7d1d2f3a5306a30d658059c431.diff
LOG: [ADT] Extract DenseMapStorage from DenseMap (NFC) (#226664)
This patch extracts the bucket and occupancy storage management from
DenseMap into a new helper class, densemap::detail::DenseMapStorage,
and updates DenseMap to use it via composition.
DenseMapBase::Rep is also moved to densemap::detail::StorageRep so
that DenseMapStorage::getRep() can return it directly.
This is part of the effort to replace CRTP in DenseMapBase with
composition (see #168255). I'm planning to post a similar patch for
SmallDenseMap before actually removing CRTP.
Added:
Modified:
llvm/include/llvm/ADT/DenseMap.h
llvm/utils/LLVMVisualizers/llvm.natvis
llvm/utils/gdb-scripts/prettyprinters.py
llvm/utils/lldbDataFormatters.py
Removed:
################################################################################
diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index f1763dcfed62a..cbbba782899f6 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -192,6 +192,99 @@ LLVM_ABI void *growRelocatable(void *OldBuckets, const UsedT *OldUsed,
size_t BucketSize, size_t Align,
BucketHasher Hasher, bool FreeOld);
+// 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.
+template <typename BucketT> struct StorageRep {
+ const BucketT *Buckets;
+ const UsedT *Used;
+ unsigned NumBuckets;
+};
+
+template <typename BucketT> class DenseMapStorage {
+ BucketT *Buckets = nullptr;
+ UsedT *Used = nullptr;
+ unsigned NumEntries = 0;
+ unsigned NumBuckets = 0;
+
+public:
+ unsigned getNumEntries() const { return NumEntries; }
+ void setNumEntries(unsigned Num) { NumEntries = Num; }
+
+ BucketT *getBuckets() const { return Buckets; }
+ UsedT *getUsed() const { return Used; }
+ unsigned getNumBuckets() const { return NumBuckets; }
+ StorageRep<BucketT> getRep() const { return {Buckets, Used, NumBuckets}; }
+
+ void swap(DenseMapStorage &RHS) {
+ std::swap(Buckets, RHS.Buckets);
+ std::swap(Used, RHS.Used);
+ std::swap(NumEntries, RHS.NumEntries);
+ std::swap(NumBuckets, RHS.NumBuckets);
+ }
+
+ void setStorage(void *Storage, unsigned Num) {
+ Buckets = static_cast<BucketT *>(Storage);
+ Used = usedFor(Storage, sizeof(BucketT), Num);
+ NumBuckets = Num;
+ }
+
+ void grow(unsigned MinNumBuckets, BucketHasher Hasher) {
+ unsigned NewNumBuckets = roundUpNumBuckets(MinNumBuckets);
+ setStorage(growRelocatable(Buckets, Used, NumBuckets, NewNumBuckets,
+ sizeof(BucketT), allocAlign<BucketT>(), Hasher,
+ /*FreeOld=*/true),
+ NewNumBuckets);
+ }
+
+ void deallocateBuckets() {
+ if (NumBuckets == 0)
+ return;
+ deallocate_buffer(Buckets, allocBytes<BucketT>(NumBuckets),
+ allocAlign<BucketT>());
+ Buckets = nullptr;
+ Used = nullptr;
+ NumBuckets = 0;
+ }
+
+ bool allocateBuckets(unsigned Num) {
+ if (Num == 0) {
+ Buckets = nullptr;
+ Used = nullptr;
+ NumBuckets = 0;
+ return false;
+ }
+ setStorage(allocate_buffer(allocBytes<BucketT>(Num), allocAlign<BucketT>()),
+ Num);
+ return true;
+ }
+
+ // Put the zombie instance in a known good state after a move.
+ // deallocateBuckets() already resets to the empty state.
+ void kill() { deallocateBuckets(); }
+
+ static unsigned roundUpNumBuckets(unsigned MinNumBuckets) {
+ return std::max(64u, MinNumBuckets);
+ }
+
+ // Plan how to shrink the bucket table. Return:
+ // - {false, 0} to reuse the existing bucket table
+ // - {true, N} to reallocate a bucket table with N entries
+ std::pair<bool, unsigned> planShrinkAndClear() const {
+ unsigned NewNumBuckets = 0;
+ if (NumEntries)
+ NewNumBuckets = std::max(64u, 1u << (Log2_32_Ceil(NumEntries) + 1));
+ if (NewNumBuckets == NumBuckets)
+ return {false, 0}; // Reuse.
+ return {true, NewNumBuckets}; // Reallocate.
+ }
+
+ bool maybeMoveFast(DenseMapStorage &&Other) {
+ swap(Other);
+ return true;
+ }
+};
+
} // namespace densemap::detail
// Befriended below so DenseMapBase can expose its bucket-relocation callback
@@ -545,14 +638,7 @@ 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 UsedT *Used;
- unsigned NumBuckets;
- };
+ using Rep = llvm::densemap::detail::StorageRep<BucketT>;
void initWithExactBucketCount(unsigned NewNumBuckets) {
if (derived().allocateBuckets(NewNumBuckets))
@@ -942,10 +1028,7 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
using BaseT = DenseMapBase<DenseMap, KeyT, ValueT, KeyInfoT, BucketT>;
using UsedT = llvm::densemap::detail::UsedT;
- BucketT *Buckets = nullptr;
- UsedT *Used = nullptr;
- unsigned NumEntries = 0;
- unsigned NumBuckets = 0;
+ densemap::detail::DenseMapStorage<BucketT> Storage;
explicit DenseMap(unsigned NumBuckets, typename BaseT::ExactBucketCount) {
this->initWithExactBucketCount(NumBuckets);
@@ -994,87 +1077,41 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
}
private:
- void swapImpl(DenseMap &RHS) {
- std::swap(Buckets, RHS.Buckets);
- std::swap(Used, RHS.Used);
- std::swap(NumEntries, RHS.NumEntries);
- std::swap(NumBuckets, RHS.NumBuckets);
- }
+ void swapImpl(DenseMap &RHS) { Storage.swap(RHS.Storage); }
- unsigned getNumEntries() const { return NumEntries; }
-
- void setNumEntries(unsigned Num) { NumEntries = Num; }
+ unsigned getNumEntries() const { return Storage.getNumEntries(); }
- BucketT *getBuckets() const { return Buckets; }
+ void setNumEntries(unsigned Num) { Storage.setNumEntries(Num); }
- typename BaseT::Rep getRep() const { return {Buckets, Used, NumBuckets}; }
+ BucketT *getBuckets() const { return Storage.getBuckets(); }
- void setStorage(void *Storage, unsigned Num) {
- Buckets = static_cast<BucketT *>(Storage);
- Used = llvm::densemap::detail::usedFor(Storage, sizeof(BucketT), Num);
- NumBuckets = Num;
- }
+ typename BaseT::Rep getRep() const { return Storage.getRep(); }
void growShared(unsigned MinNumBuckets) {
- unsigned NewNumBuckets = roundUpNumBuckets(MinNumBuckets);
- setStorage(llvm::densemap::detail::growRelocatable(
- Buckets, Used, NumBuckets, NewNumBuckets, sizeof(BucketT),
- llvm::densemap::detail::allocAlign<BucketT>(),
- BaseT::hasher(), /*FreeOld=*/true),
- NewNumBuckets);
+ Storage.grow(MinNumBuckets, BaseT::hasher());
}
- UsedT *getUsed() const { return Used; }
+ UsedT *getUsed() const { return Storage.getUsed(); }
- unsigned getNumBuckets() const { return NumBuckets; }
+ unsigned getNumBuckets() const { return Storage.getNumBuckets(); }
- void deallocateBuckets() {
- if (NumBuckets == 0)
- return;
- deallocate_buffer(Buckets,
- llvm::densemap::detail::allocBytes<BucketT>(NumBuckets),
- llvm::densemap::detail::allocAlign<BucketT>());
- Buckets = nullptr;
- Used = nullptr;
- NumBuckets = 0;
- }
+ void deallocateBuckets() { Storage.deallocateBuckets(); }
- bool allocateBuckets(unsigned Num) {
- if (Num == 0) {
- Buckets = nullptr;
- Used = nullptr;
- NumBuckets = 0;
- return false;
- }
- setStorage(allocate_buffer(llvm::densemap::detail::allocBytes<BucketT>(Num),
- llvm::densemap::detail::allocAlign<BucketT>()),
- Num);
- return true;
- }
+ bool allocateBuckets(unsigned Num) { return Storage.allocateBuckets(Num); }
- // Put the zombie instance in a known good state after a move.
- // deallocateBuckets() already resets to the empty state.
- void kill() { deallocateBuckets(); }
+ void kill() { Storage.kill(); }
static unsigned roundUpNumBuckets(unsigned MinNumBuckets) {
- return std::max(64u, MinNumBuckets);
+ return densemap::detail::DenseMapStorage<BucketT>::roundUpNumBuckets(
+ MinNumBuckets);
}
bool maybeMoveFast(DenseMap &&Other) {
- swapImpl(Other);
- return true;
+ return Storage.maybeMoveFast(std::move(Other.Storage));
}
- // Plan how to shrink the bucket table. Return:
- // - {false, 0} to reuse the existing bucket table
- // - {true, N} to reallocate a bucket table with N entries
std::pair<bool, unsigned> planShrinkAndClear() const {
- unsigned NewNumBuckets = 0;
- if (NumEntries)
- NewNumBuckets = std::max(64u, 1u << (Log2_32_Ceil(NumEntries) + 1));
- if (NewNumBuckets == NumBuckets)
- return {false, 0}; // Reuse.
- return {true, NewNumBuckets}; // Reallocate.
+ return Storage.planShrinkAndClear();
}
};
diff --git a/llvm/utils/LLVMVisualizers/llvm.natvis b/llvm/utils/LLVMVisualizers/llvm.natvis
index bd9ca40ac3638..70ed4e1f8cf24 100644
--- a/llvm/utils/LLVMVisualizers/llvm.natvis
+++ b/llvm/utils/LLVMVisualizers/llvm.natvis
@@ -156,14 +156,14 @@ For later versions of Visual Studio, no setup is required.
</Type>
<Type Name="llvm::DenseMap<*,*,*>">
- <DisplayString Condition="NumEntries == 0">empty</DisplayString>
- <DisplayString Condition="NumEntries != 0">{{ size={NumEntries}, buckets={NumBuckets} }}</DisplayString>
+ <DisplayString Condition="Storage.NumEntries == 0">empty</DisplayString>
+ <DisplayString Condition="Storage.NumEntries != 0">{{ size={Storage.NumEntries}, buckets={Storage.NumBuckets} }}</DisplayString>
<Expand>
- <Item Name="[size]">NumEntries</Item>
- <Item Name="[buckets]">NumBuckets</Item>
+ <Item Name="[size]">Storage.NumEntries</Item>
+ <Item Name="[buckets]">Storage.NumBuckets</Item>
<ArrayItems>
- <Size>NumBuckets</Size>
- <ValuePointer>Buckets</ValuePointer>
+ <Size>Storage.NumBuckets</Size>
+ <ValuePointer>Storage.Buckets</ValuePointer>
</ArrayItems>
</Expand>
</Type>
diff --git a/llvm/utils/gdb-scripts/prettyprinters.py b/llvm/utils/gdb-scripts/prettyprinters.py
index d37481cb19433..d1bb795df39fa 100644
--- a/llvm/utils/gdb-scripts/prettyprinters.py
+++ b/llvm/utils/gdb-scripts/prettyprinters.py
@@ -188,11 +188,12 @@ def __init__(self, val):
def children(self):
t = self.val.type.template_argument(3).pointer()
- buckets = self.val["Buckets"].cast(t)
- return self._iterator(buckets, self.val["Used"], int(self.val["NumBuckets"]))
+ storage = self.val["Storage"]
+ buckets = storage["Buckets"].cast(t)
+ return self._iterator(buckets, storage["Used"], int(storage["NumBuckets"]))
def to_string(self):
- return "llvm::DenseMap with %d elements" % (self.val["NumEntries"])
+ return "llvm::DenseMap with %d elements" % (self.val["Storage"]["NumEntries"])
def display_hint(self):
return "map"
diff --git a/llvm/utils/lldbDataFormatters.py b/llvm/utils/lldbDataFormatters.py
index d1765f87b52f4..d0ec1b8ce3258 100644
--- a/llvm/utils/lldbDataFormatters.py
+++ b/llvm/utils/lldbDataFormatters.py
@@ -446,12 +446,14 @@ def _set_raw_pointer(self, raw_value, min_low_bits):
def DenseMapSummary(valobj: lldb.SBValue, _) -> str:
raw_value = valobj.GetNonSyntheticValue()
- num_entries = raw_value.GetChildMemberWithName("NumEntries").unsigned
+ storage = raw_value.GetChildMemberWithName("Storage")
+ num_entries = storage.GetChildMemberWithName("NumEntries").unsigned
return f"size={num_entries}"
class DenseMapSynthetic:
valobj: lldb.SBValue
+ storage: lldb.SBValue
# The indexes into `Buckets` that contain valid map entries.
child_buckets: list[int]
@@ -464,18 +466,19 @@ def num_children(self) -> int:
def get_child_at_index(self, child_index: int) -> lldb.SBValue:
bucket_index = self.child_buckets[child_index]
- entry = self.valobj.GetValueForExpressionPath(f".Buckets[{bucket_index}]")
+ entry = self.storage.GetValueForExpressionPath(f".Buckets[{bucket_index}]")
return entry.Clone(f"[{child_index}]")
def update(self):
self.child_buckets = []
+ self.storage = self.valobj.GetChildMemberWithName("Storage")
- num_entries = self.valobj.GetChildMemberWithName("NumEntries").unsigned
+ num_entries = self.storage.GetChildMemberWithName("NumEntries").unsigned
if num_entries == 0:
return
- num_buckets = self.valobj.GetChildMemberWithName("NumBuckets").unsigned
- used = self.valobj.GetChildMemberWithName("Used")
+ num_buckets = self.storage.GetChildMemberWithName("NumBuckets").unsigned
+ used = self.storage.GetChildMemberWithName("Used")
# Occupancy is tracked in a packed 1-bit-per-bucket "used" array of
# uint32_t words. A bucket holds a valid entry iff its bit is set;
More information about the llvm-commits
mailing list