[llvm] [ADT] Exit doFind on an empty map, not just an unallocated one (PR #220294)

via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 1 09:48:53 PDT 2026


https://github.com/khaki3 created https://github.com/llvm/llvm-project/pull/220294

`doFind` exits early only when no buckets were ever allocated. A map that
had entries and lost them keeps its bucket array. `NumBuckets` is then not
zero, so every lookup hashes the key and probes.

For a `SmallDenseMap` in small mode, `NumBuckets` is the template parameter
`InlineBuckets`, a nonzero constant. The existing check can never fire for
those maps. An empty one hashes and probes on every lookup.

`getNumEntries() == 0` covers both cases. It also subsumes the old check.
There are no entries without buckets, so `Mask = NumBuckets - 1` is still
safe. It is one test either way, so non-empty lookups are unchanged.

| workload | instructions:u |
|---|---:|
| clang compiling 60 LLVM/Clang/MLIR translation units | **-0.133%** |
| `mlir-opt` canonicalize/cse over 1060 `mlir/test` files >4KB | **-0.277%** |

Three interleaved runs each. Ranges do not overlap. Exit codes are
identical on all 1448 candidate `mlir/test` files.

Adding `LLVM_UNLIKELY` to the branch is a regression, not a win. It takes
clang from -0.133% to -0.007%. Once this is the first check, an empty map
is the common case, not the rare one.

>From 8660322cbc20c4fcc80b6a8b674222984f27adbe Mon Sep 17 00:00:00 2001
From: Kazuaki Matsumura <kmatsumura at nvidia.com>
Date: Tue, 1 Sep 2026 09:30:31 -0700
Subject: [PATCH] [ADT] Exit doFind on an empty map, not just an unallocated
 one

doFind returns early only when no buckets were ever allocated.  A map that
had entries and lost them keeps its bucket array, so NumBuckets != 0 and
every lookup still hashes the key and probes.  SmallDenseMap in small mode
never has NumBuckets == 0 at all -- getRep() returns the constant
InlineBuckets -- so an empty one always hashes and probes.

getNumEntries() == 0 covers both and subsumes the old check: there are no
entries without buckets, so reaching Mask = NumBuckets - 1 still implies
NumBuckets != 0.  It is one test either way, so non-empty lookups are
unchanged.

instructions:u, three interleaved runs each, ranges non-overlapping:

  -0.133%  clang compiling 60 LLVM/Clang/MLIR translation units
  -0.277%  mlir-opt --verify-each=true --allow-unregistered-dialect
             -pass-pipeline='builtin.module(canonicalize,cse,canonicalize,cse,canonicalize)'
           over the 1060 files in mlir/test larger than 4KB that this
           pipeline accepts

Exit codes are identical on all 1448 candidate mlir/test files.

Marking the branch LLVM_UNLIKELY is a regression, not a win: it takes the
clang figure from -0.133% to -0.007%, because once this is the first check
an empty map is the common outcome rather than the rare one.
---
 llvm/include/llvm/ADT/DenseMap.h | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/ADT/DenseMap.h b/llvm/include/llvm/ADT/DenseMap.h
index 6d036d4a0729f..c0b5854ef09c9 100644
--- a/llvm/include/llvm/ADT/DenseMap.h
+++ b/llvm/include/llvm/ADT/DenseMap.h
@@ -722,7 +722,10 @@ class DenseMapBase : public DebugEpochBase {
   template <typename LookupKeyT>
   const BucketT *doFind(const LookupKeyT &Val) const {
     auto [BucketsPtr, U, NumBuckets] = getRep();
-    if (NumBuckets == 0)
+    // Subsumes the NumBuckets == 0 case: there are no entries without
+    // buckets, so a non-zero entry count implies NumBuckets != 0 and the
+    // Mask below is safe.
+    if (getNumEntries() == 0)
       return nullptr;
 
     const unsigned Mask = NumBuckets - 1;



More information about the llvm-commits mailing list