[llvm] [ADT] Extract DenseMapStorage from DenseMap (NFC) (PR #226664)

Kazu Hirata via llvm-commits llvm-commits at lists.llvm.org
Sun Sep 27 19:07:05 PDT 2026


https://github.com/kazutakahirata updated https://github.com/llvm/llvm-project/pull/226664

>From 09a81de18b5449046d30bbfa2d17884bf4b51877 Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Sun, 27 Sep 2026 17:18:59 -0700
Subject: [PATCH 1/2] Rebase.

---
 llvm/include/llvm/ADT/DenseMap.h | 185 +++++++++++++++++++------------
 1 file changed, 112 insertions(+), 73 deletions(-)

diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index f1763dcfed62a..a690fa7561df8 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -192,6 +192,101 @@ 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:
+  DenseMapStorage() = default;
+
+  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 growShared(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 +640,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 +1030,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 +1079,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.growShared(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();
   }
 };
 

>From 42e3a8cc871ab8248dde18cf28d8baa58ef17d9c Mon Sep 17 00:00:00 2001
From: Kazu Hirata <kazu at google.com>
Date: Sun, 27 Sep 2026 19:06:37 -0700
Subject: [PATCH 2/2] Address comments.

---
 llvm/include/llvm/ADT/DenseMap.h         |  6 ++----
 llvm/utils/LLVMVisualizers/llvm.natvis   | 12 ++++++------
 llvm/utils/gdb-scripts/prettyprinters.py |  7 ++++---
 llvm/utils/lldbDataFormatters.py         | 13 ++++++++-----
 4 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index a690fa7561df8..cbbba782899f6 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -208,8 +208,6 @@ template <typename BucketT> class DenseMapStorage {
   unsigned NumBuckets = 0;
 
 public:
-  DenseMapStorage() = default;
-
   unsigned getNumEntries() const { return NumEntries; }
   void setNumEntries(unsigned Num) { NumEntries = Num; }
 
@@ -231,7 +229,7 @@ template <typename BucketT> class DenseMapStorage {
     NumBuckets = Num;
   }
 
-  void growShared(unsigned MinNumBuckets, BucketHasher Hasher) {
+  void grow(unsigned MinNumBuckets, BucketHasher Hasher) {
     unsigned NewNumBuckets = roundUpNumBuckets(MinNumBuckets);
     setStorage(growRelocatable(Buckets, Used, NumBuckets, NewNumBuckets,
                                sizeof(BucketT), allocAlign<BucketT>(), Hasher,
@@ -1090,7 +1088,7 @@ class DenseMap : public DenseMapBase<DenseMap<KeyT, ValueT, KeyInfoT, BucketT>,
   typename BaseT::Rep getRep() const { return Storage.getRep(); }
 
   void growShared(unsigned MinNumBuckets) {
-    Storage.growShared(MinNumBuckets, BaseT::hasher());
+    Storage.grow(MinNumBuckets, BaseT::hasher());
   }
 
   UsedT *getUsed() const { return Storage.getUsed(); }
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