Skip to content

Conversation

@kazutakahirata
Copy link
Contributor

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values. We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values.  We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.
@llvmbot
Copy link
Member

llvmbot commented Oct 25, 2025

@llvm/pr-subscribers-llvm-adt

Author: Kazu Hirata (kazutakahirata)

Changes

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values. We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.


Full diff: https://github.com/llvm/llvm-project/pull/165080.diff

2 Files Affected:

  • (modified) llvm/include/llvm/ADT/DenseMap.h (+6)
  • (modified) llvm/unittests/ADT/DenseMapTest.cpp (+70)
diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index 25b5262800a10..3d91defe2f98d 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -353,6 +353,12 @@ class DenseMapBase : public DebugEpochBase {
   DenseMapBase() = default;
 
   void destroyAll() {
+    // No need to iterate through the buckets if both KeyT and ValueT are
+    // trivially destructible.
+    if constexpr (std::is_trivially_destructible_v<KeyT> &&
+                  std::is_trivially_destructible_v<ValueT>)
+      return;
+
     if (getNumBuckets() == 0) // Nothing to do.
       return;
 
diff --git a/llvm/unittests/ADT/DenseMapTest.cpp b/llvm/unittests/ADT/DenseMapTest.cpp
index 50e9c6e138ef1..aceb4f30d878d 100644
--- a/llvm/unittests/ADT/DenseMapTest.cpp
+++ b/llvm/unittests/ADT/DenseMapTest.cpp
@@ -70,6 +70,16 @@ class CtorTester {
 
   int getValue() const { return Value; }
   bool operator==(const CtorTester &RHS) const { return Value == RHS.Value; }
+
+  // Return the number of live CtorTester objects, excluding the empty and
+  // tombstone keys.
+  static size_t getNumConstructed() {
+    return std::count_if(Constructed.begin(), Constructed.end(),
+                         [](const CtorTester *Obj) {
+                           int V = Obj->getValue();
+                           return V != -1 && V != -2;
+                         });
+  }
 };
 
 std::set<CtorTester *> CtorTester::Constructed;
@@ -1031,4 +1041,64 @@ TEST(SmallDenseMapCustomTest, InitSize) {
   }
 }
 
+TEST(DenseMapCustomTest, KeyDtor) {
+  // This test relies on CtorTester being non-trivially destructible.
+  static_assert(!std::is_trivially_destructible_v<CtorTester>,
+                "CtorTester must not be trivially destructible");
+
+  // Test that keys are destructed on scope exit.
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  {
+    DenseMap<CtorTester, int, CtorTesterMapInfo> Map;
+    Map.try_emplace(CtorTester(0), 1);
+    Map.try_emplace(CtorTester(1), 2);
+    EXPECT_EQ(2u, CtorTester::getNumConstructed());
+  }
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+
+  // Test that keys are destructed on erase and shrink_and_clear.
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  {
+    DenseMap<CtorTester, int, CtorTesterMapInfo> Map;
+    Map.try_emplace(CtorTester(0), 1);
+    Map.try_emplace(CtorTester(1), 2);
+    EXPECT_EQ(2u, CtorTester::getNumConstructed());
+    Map.erase(CtorTester(1));
+    EXPECT_EQ(1u, CtorTester::getNumConstructed());
+    Map.shrink_and_clear();
+    EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  }
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+}
+
+TEST(DenseMapCustomTest, ValueDtor) {
+  // This test relies on CtorTester being non-trivially destructible.
+  static_assert(!std::is_trivially_destructible_v<CtorTester>,
+                "CtorTester must not be trivially destructible");
+
+  // Test that values are destructed on scope exit.
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  {
+    DenseMap<int, CtorTester> Map;
+    Map.try_emplace(0, CtorTester(1));
+    Map.try_emplace(1, CtorTester(2));
+    EXPECT_EQ(2u, CtorTester::getNumConstructed());
+  }
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+
+  // Test that values are destructed on erase and shrink_and_clear.
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  {
+    DenseMap<int, CtorTester> Map;
+    Map.try_emplace(0, CtorTester(1));
+    Map.try_emplace(1, CtorTester(2));
+    EXPECT_EQ(2u, CtorTester::getNumConstructed());
+    Map.erase(1);
+    EXPECT_EQ(1u, CtorTester::getNumConstructed());
+    Map.shrink_and_clear();
+    EXPECT_EQ(0u, CtorTester::getNumConstructed());
+  }
+  EXPECT_EQ(0u, CtorTester::getNumConstructed());
+}
+
 } // namespace

@kazutakahirata kazutakahirata merged commit d461244 into llvm:main Oct 25, 2025
12 checks passed
@kazutakahirata kazutakahirata deleted the cleanup_20251024_ADT_DenseMap_skip_dtor branch October 25, 2025 13:25
dvbuka pushed a commit to dvbuka/llvm-project that referenced this pull request Oct 27, 2025
…lvm#165080)

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values.  We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.
Lukacma pushed a commit to Lukacma/llvm-project that referenced this pull request Oct 29, 2025
…lvm#165080)

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values.  We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.
aokblast pushed a commit to aokblast/llvm-project that referenced this pull request Oct 30, 2025
…lvm#165080)

DenseMap::destroyAll currently iterates through the entire bucket
array to call destructors keys and values.  We don't need to do that
if we know that both key and value types are trivially destructible,
meaning that the destructors are no-ops.

This patch introduces "constexpr if" at the beginning of destroyAll to
skip the rest of the function if both key and value types are
trivially destructible.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants