[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