Skip to content

Conversation

kazutakahirata
Copy link
Contributor

DenseMapIterator has two tasks:

  • iterate the buckets in the requested direction
  • skip the empty and tombstone buckets

These tasks are intertwined in the current implementation.

This patch cleans up DenseMapIterator by separating the two tasks.
Specifically, we introduce a private middleman iterator type called
BucketItTy. This is the same as the pointer-based iterator in the
forward direction, but it becomes std::reverse_iterator
otherwise. Now, the user-facing iterator iterates over BucketItTy
while skipping the empty and tombstone buckets. This way,
AdvancePastEmptyBuckets always calls BucketItTy::operator++. If the
reverse iteration is requested, the underlying raw pointer gets
decremented, but that logic is hidden behind
std::reverse_iterator::operator++.

As a result, we can remove RetreatPastEmptyBuckets and a couple of
calls to shouldReverseIterate.

Here is a data point. A couple of months ago, we were calling
shouldReverseIterate from 18 places in DenseMap.h. That's down to 5.
This patch reduces it further down to 3.

DenseMapIterator has two tasks:

- iterate the buckets in the requested direction
- skip the empty and tombstone buckets

These tasks are intertwined in the current implementation.

This patch cleans up DenseMapIterator by separating the two tasks.
Specifically, we introduce a private middleman iterator type called
BucketItTy.  This is the same as the pointer-based iterator in the
forward direction, but it becomes std::reverse_iterator<pointer>
otherwise.  Now, the user-facing iterator iterates over BucketItTy
while skipping the empty and tombstone buckets.  This way,
AdvancePastEmptyBuckets always calls BucketItTy::operator++.  If the
reverse iteration is requested, the underlying raw pointer gets
decremented, but that logic is hidden behind
std::reverse_iterator<pointer>::operator++.

As a result, we can remove RetreatPastEmptyBuckets and a couple of
calls to shouldReverseIterate.

Here is a data point.  A couple of months ago, we were calling
shouldReverseIterate from 18 places in DenseMap.h.  That's down to 5.
This patch reduces it further down to 3.
@llvmbot
Copy link
Member

llvmbot commented Sep 8, 2025

@llvm/pr-subscribers-llvm-adt

Author: Kazu Hirata (kazutakahirata)

Changes

DenseMapIterator has two tasks:

  • iterate the buckets in the requested direction
  • skip the empty and tombstone buckets

These tasks are intertwined in the current implementation.

This patch cleans up DenseMapIterator by separating the two tasks.
Specifically, we introduce a private middleman iterator type called
BucketItTy. This is the same as the pointer-based iterator in the
forward direction, but it becomes std::reverse_iterator<pointer>
otherwise. Now, the user-facing iterator iterates over BucketItTy
while skipping the empty and tombstone buckets. This way,
AdvancePastEmptyBuckets always calls BucketItTy::operator++. If the
reverse iteration is requested, the underlying raw pointer gets
decremented, but that logic is hidden behind
std::reverse_iterator<pointer>::operator++.

As a result, we can remove RetreatPastEmptyBuckets and a couple of
calls to shouldReverseIterate.

Here is a data point. A couple of months ago, we were calling
shouldReverseIterate from 18 places in DenseMap.h. That's down to 5.
This patch reduces it further down to 3.


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

1 Files Affected:

  • (modified) llvm/include/llvm/ADT/DenseMap.h (+23-32)
diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index 848e76feb20c6..18dd7f30c5616 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -1184,10 +1184,14 @@ class DenseMapIterator : DebugEpochBase::HandleBase {
   using iterator_category = std::forward_iterator_tag;
 
 private:
-  pointer Ptr = nullptr;
-  pointer End = nullptr;
+  using BucketItTy =
+      std::conditional_t<shouldReverseIterate<KeyT>(),
+                         std::reverse_iterator<pointer>, pointer>;
 
-  DenseMapIterator(pointer Pos, pointer E, const DebugEpochBase &Epoch)
+  BucketItTy Ptr = {};
+  BucketItTy End = {};
+
+  DenseMapIterator(BucketItTy Pos, BucketItTy E, const DebugEpochBase &Epoch)
       : DebugEpochBase::HandleBase(&Epoch), Ptr(Pos), End(E) {
     assert(isHandleInSync() && "invalid construction!");
   }
@@ -1201,29 +1205,24 @@ class DenseMapIterator : DebugEpochBase::HandleBase {
     // empty buckets.
     if (IsEmpty)
       return makeEnd(Buckets, Epoch);
-    if (shouldReverseIterate<KeyT>()) {
-      DenseMapIterator Iter(Buckets.end(), Buckets.begin(), Epoch);
-      Iter.RetreatPastEmptyBuckets();
-      return Iter;
-    }
-    DenseMapIterator Iter(Buckets.begin(), Buckets.end(), Epoch);
+    auto R = maybeReverse(Buckets);
+    DenseMapIterator Iter(R.begin(), R.end(), Epoch);
     Iter.AdvancePastEmptyBuckets();
     return Iter;
   }
 
   static DenseMapIterator makeEnd(iterator_range<pointer> Buckets,
                                   const DebugEpochBase &Epoch) {
-    if (shouldReverseIterate<KeyT>())
-      return DenseMapIterator(Buckets.begin(), Buckets.begin(), Epoch);
-    return DenseMapIterator(Buckets.end(), Buckets.end(), Epoch);
+    auto R = maybeReverse(Buckets);
+    return DenseMapIterator(R.end(), R.end(), Epoch);
   }
 
   static DenseMapIterator makeIterator(pointer P,
                                        iterator_range<pointer> Buckets,
                                        const DebugEpochBase &Epoch) {
-    if (shouldReverseIterate<KeyT>())
-      return DenseMapIterator(P + 1, Buckets.begin(), Epoch);
-    return DenseMapIterator(P, Buckets.end(), Epoch);
+    auto R = maybeReverse(Buckets);
+    constexpr int Offset = shouldReverseIterate<KeyT>() ? 1 : 0;
+    return DenseMapIterator(BucketItTy(P + Offset), R.end(), Epoch);
   }
 
   // Converting ctor from non-const iterators to const iterators. SFINAE'd out
@@ -1238,16 +1237,16 @@ class DenseMapIterator : DebugEpochBase::HandleBase {
   reference operator*() const {
     assert(isHandleInSync() && "invalid iterator access!");
     assert(Ptr != End && "dereferencing end() iterator");
-    if (shouldReverseIterate<KeyT>())
-      return Ptr[-1];
     return *Ptr;
   }
   pointer operator->() const { return &operator*(); }
 
   friend bool operator==(const DenseMapIterator &LHS,
                          const DenseMapIterator &RHS) {
-    assert((!LHS.Ptr || LHS.isHandleInSync()) && "handle not in sync!");
-    assert((!RHS.Ptr || RHS.isHandleInSync()) && "handle not in sync!");
+    assert((!LHS.getEpochAddress() || LHS.isHandleInSync()) &&
+           "handle not in sync!");
+    assert((!RHS.getEpochAddress() || RHS.isHandleInSync()) &&
+           "handle not in sync!");
     assert(LHS.getEpochAddress() == RHS.getEpochAddress() &&
            "comparing incomparable iterators!");
     return LHS.Ptr == RHS.Ptr;
@@ -1261,11 +1260,6 @@ class DenseMapIterator : DebugEpochBase::HandleBase {
   inline DenseMapIterator &operator++() { // Preincrement
     assert(isHandleInSync() && "invalid iterator access!");
     assert(Ptr != End && "incrementing end() iterator");
-    if (shouldReverseIterate<KeyT>()) {
-      --Ptr;
-      RetreatPastEmptyBuckets();
-      return *this;
-    }
     ++Ptr;
     AdvancePastEmptyBuckets();
     return *this;
@@ -1288,14 +1282,11 @@ class DenseMapIterator : DebugEpochBase::HandleBase {
       ++Ptr;
   }
 
-  void RetreatPastEmptyBuckets() {
-    assert(Ptr >= End);
-    const KeyT Empty = KeyInfoT::getEmptyKey();
-    const KeyT Tombstone = KeyInfoT::getTombstoneKey();
-
-    while (Ptr != End && (KeyInfoT::isEqual(Ptr[-1].getFirst(), Empty) ||
-                          KeyInfoT::isEqual(Ptr[-1].getFirst(), Tombstone)))
-      --Ptr;
+  static auto maybeReverse(iterator_range<pointer> Range) {
+    if constexpr (shouldReverseIterate<KeyT>())
+      return reverse(Range);
+    else
+      return Range;
   }
 };
 

Copy link
Member

@kuhar kuhar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks OK as long as it doesn't make compilation times worse -- could you check that?

@kazutakahirata
Copy link
Contributor Author

This looks OK as long as it doesn't make compilation times worse -- could you check that?

@kuhar I can only get noise while compiling preprocessed X86ISelLowering.cpp 10 times each, which is a good thing.

        Before   After     Notes
Average 41120 ms 41147 ms  10 runs
st-dev     98 ms   110 ms
st-dev%  0.24%    0.27%
p-value           0.54

@kazutakahirata kazutakahirata merged commit 71a28f3 into llvm:main Sep 10, 2025
11 checks passed
@kazutakahirata kazutakahirata deleted the cleanup_20250907_ADT_DenseMapIterator_reverse_iterator branch September 10, 2025 01:06
@llvm-ci
Copy link
Collaborator

llvm-ci commented Sep 10, 2025

LLVM Buildbot has detected a new failure on builder lldb-aarch64-windows running on linaro-armv8-windows-msvc-05 while building llvm at step 6 "test".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/141/builds/11414

Here is the relevant piece of the build log for the reference
Step 6 (test) failure: build (failure)
...
UNSUPPORTED: lldb-api :: tools/lldb-dap/disassemble/TestDAP_disassemble.py (1208 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/disconnect/TestDAP_disconnect.py (1209 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/evaluate/TestDAP_evaluate.py (1210 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/exception/TestDAP_exception.py (1211 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/exception/cpp/TestDAP_exception_cpp.py (1212 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/exception/objc/TestDAP_exception_objc.py (1213 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/extendedStackTrace/TestDAP_extendedStackTrace.py (1214 of 2303)
UNSUPPORTED: lldb-api :: tools/lldb-dap/instruction-breakpoint/TestDAP_instruction_breakpoint.py (1215 of 2303)
PASS: lldb-api :: tools/lldb-dap/io/TestDAP_io.py (1216 of 2303)
UNRESOLVED: lldb-api :: tools/lldb-dap/console/TestDAP_console.py (1217 of 2303)
******************** TEST 'lldb-api :: tools/lldb-dap/console/TestDAP_console.py' FAILED ********************
Script:
--
C:/Users/tcwg/scoop/apps/python/current/python.exe C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/llvm-project/lldb\test\API\dotest.py -u CXXFLAGS -u CFLAGS --env LLVM_LIBS_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./lib --env LLVM_INCLUDE_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/include --env LLVM_TOOLS_DIR=C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin --arch aarch64 --build-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex --lldb-module-cache-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/module-cache-lldb\lldb-api --clang-module-cache-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/module-cache-clang\lldb-api --executable C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/lldb.exe --compiler C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/clang.exe --dsymutil C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin/dsymutil.exe --make C:/Users/tcwg/scoop/shims/make.exe --llvm-tools-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./bin --lldb-obj-root C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/tools/lldb --lldb-libs-dir C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/./lib --cmake-build-type Release --skip-category=watchpoint C:\Users\tcwg\llvm-worker\lldb-aarch64-windows\llvm-project\lldb\test\API\tools\lldb-dap\console -p TestDAP_console.py
--
Exit Code: 1

Command Output (stdout):
--
lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c)
  clang revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c
  llvm revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c
Skipping the following test categories: ['watchpoint', 'libc++', 'libstdcxx', 'dwo', 'dsym', 'gmodules', 'debugserver', 'objc', 'fork', 'pexpect']


--
Command Output (stderr):
--
========= DEBUG ADAPTER PROTOCOL LOGS =========

1757476191.633264065 (stdio) --> {"command":"initialize","type":"request","arguments":{"adapterID":"lldb-native","clientID":"vscode","columnsStartAt1":true,"linesStartAt1":true,"locale":"en-us","pathFormat":"path","supportsRunInTerminalRequest":true,"supportsVariablePaging":true,"supportsVariableType":true,"supportsStartDebuggingRequest":true,"supportsProgressReporting":true,"$__lldb_sourceInitFile":false},"seq":1}

1757476191.633461475 (stdio) queued (command=initialize seq=1)

1757476191.643313408 (stdio) <-- {"body":{"$__lldb_version":"lldb version 22.0.0git (https://github.com/llvm/llvm-project.git revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c)\n  clang revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c\n  llvm revision 71a28f3d1e71af2acee9b9c83e012edbcfef159c","completionTriggerCharacters":["."," ","\t"],"exceptionBreakpointFilters":[{"description":"C++ Catch","filter":"cpp_catch","label":"C++ Catch","supportsCondition":true},{"description":"C++ Throw","filter":"cpp_throw","label":"C++ Throw","supportsCondition":true},{"description":"Objective-C Catch","filter":"objc_catch","label":"Objective-C Catch","supportsCondition":true},{"description":"Objective-C Throw","filter":"objc_throw","label":"Objective-C Throw","supportsCondition":true}],"supportTerminateDebuggee":true,"supportsBreakpointLocationsRequest":true,"supportsCancelRequest":true,"supportsCompletionsRequest":true,"supportsConditionalBreakpoints":true,"supportsConfigurationDoneRequest":true,"supportsDataBreakpoints":true,"supportsDelayedStackTraceLoading":true,"supportsDisassembleRequest":true,"supportsEvaluateForHovers":true,"supportsExceptionFilterOptions":true,"supportsExceptionInfoRequest":true,"supportsFunctionBreakpoints":true,"supportsHitConditionalBreakpoints":true,"supportsInstructionBreakpoints":true,"supportsLogPoints":true,"supportsModuleSymbolsRequest":true,"supportsModulesRequest":true,"supportsReadMemoryRequest":true,"supportsSetVariable":true,"supportsSteppingGranularity":true,"supportsValueFormattingOptions":true,"supportsWriteMemoryRequest":true},"command":"initialize","request_seq":1,"seq":0,"success":true,"type":"response"}

1757476191.644104481 (stdio) --> {"command":"launch","type":"request","arguments":{"program":"C:\\Users\\tcwg\\llvm-worker\\lldb-aarch64-windows\\build\\lldb-test-build.noindex\\tools\\lldb-dap\\console\\TestDAP_console.test_custom_escape_prefix\\a.out","initCommands":["settings clear --all","settings set symbols.enable-external-lookup false","settings set target.inherit-tcc true","settings set target.disable-aslr false","settings set target.detach-on-error false","settings set target.auto-apply-fixits false","settings set plugin.process.gdb-remote.packet-timeout 60","settings set symbols.clang-modules-cache-path \"C:/Users/tcwg/llvm-worker/lldb-aarch64-windows/build/lldb-test-build.noindex/module-cache-lldb\\lldb-api\"","settings set use-color false","settings set show-statusline false","settings set target.env-vars PATH="],"disableASLR":false,"enableAutoVariableSummaries":false,"enableSyntheticChildDebugging":false,"displayExtendedBacktrace":false,"commandEscapePrefix":"::"},"seq":2}

1757476191.644159794 (stdio) queued (command=launch seq=2)

1757476191.644660711 (stdio) <-- {"body":{"category":"console","output":"Running initCommands:\n"},"event":"output","seq":0,"type":"event"}

1757476191.644711971 (stdio) <-- {"body":{"category":"console","output":"(lldb) settings clear --all\n"},"event":"output","seq":0,"type":"event"}

1757476191.644740343 (stdio) <-- {"body":{"category":"console","output":"(lldb) settings set symbols.enable-external-lookup false\n"},"event":"output","seq":0,"type":"event"}

1757476191.644764423 (stdio) <-- {"body":{"category":"console","output":"(lldb) settings set target.inherit-tcc true\n"},"event":"output","seq":0,"type":"event"}

1757476191.644786596 (stdio) <-- {"body":{"category":"console","output":"(lldb) settings set target.disable-aslr false\n"},"event":"output","seq":0,"type":"event"}

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.

4 participants