[llvm] [ADT][TableGen] Add UniquingSet, a FoldingSet with typed keys (PR #219630)

via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 28 22:27:55 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-tablegen

Author: Fangrui Song (MaskRay)

<details>
<summary>Changes</summary>

FoldingSet serializes a key into a FoldingSetNodeID to look a node up and
rebuilds the stored node's profile to compare against it. Where a key can be
read out of a node, neither is necessary.

UniquingSet reuses FoldingSetBase's storage, growth, removal and
insert-position protocol and replaces only the key: the node's `getKey()`
supplies it, the key type's `operator==` compares it, and DenseMapInfo
hashes it inline. An Info parameter overrides the key type or its hash.
The hash cached on each node keeps growth and erasure from calling
`getKey()`, which a DenseSet cannot avoid.

find hands back a typed FoldingSetInsertPos rather than FoldingSet's
void *; FoldingSet's own API is unchanged.

Prefer `UniquingSet` where a key can be read out of a node in O(1) and
the lookup key is built beside `getKey()`; keep FoldingSet for keys that
are wide, polymorphic or assembled at many call sites, where one Profile
helper keeps both sides consistent. insert asserts that a node hashes as
its lookup did.

TableGen's list and unary/binary/ternary operator Init pools are the first user:
each Profile and its ProfileXxxInit helper become one getKey.

Instructions retired by llvm-tblgen:

```
                gen-dag-isel  asm-matcher  subtarget  register-info
X86                   -1.01%       -2.81%     -1.01%         -3.92%
AArch64               -0.85%       -2.90%     -0.26%         -3.90%
RISCV                 -1.30%       -6.96%     -3.29%         -7.86%
AMDGPU                -1.65%       -1.40%     -3.66%         -1.87%
```

A FoldingSetTrait::Equals that matches the serialized ID against the
node's fields, keeping FoldingSet, captures 31% of this in every cell;
the rest is serializing and hashing the key.

LLM-aided

---

Patch is 23.12 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/219630.diff


5 Files Affected:

- (modified) llvm/docs/ProgrammersManual.md (+32) 
- (modified) llvm/include/llvm/ADT/FoldingSet.h (+108) 
- (modified) llvm/include/llvm/TableGen/Record.h (+19-8) 
- (modified) llvm/lib/TableGen/Record.cpp (+16-81) 
- (modified) llvm/unittests/ADT/FoldingSet.cpp (+174) 


``````````diff
diff --git a/llvm/docs/ProgrammersManual.md b/llvm/docs/ProgrammersManual.md
index e4c07a389891a..818a70f3048e7 100644
--- a/llvm/docs/ProgrammersManual.md
+++ b/llvm/docs/ProgrammersManual.md
@@ -2083,6 +2083,38 @@ stable: inserting or removing elements does not invalidate any pointers to other
 elements.  The iterators, however, are invalidated whenever an insertion or
 erasure occurs, as in `DenseMap` and `StringMap`.
 
+See {ref}`UniquingSet <dss_uniquingset>` for a variant keyed on a typed key.
+
+(dss_uniquingset)=
+
+#### UniquingSet (llvm/ADT/FoldingSet.h)
+
+`UniquingSet` is a {ref}`FoldingSet <dss_FoldingSet>` whose nodes are compared
+against a typed key instead of a serialized `FoldingSetNodeID`.  Each node
+supplies its key through `getKey()`; a lookup builds the same key from what it
+already holds and hashes it inline with `DenseMapInfo`, and `find` returns the
+matching node or an insertion token for `insert`.  Growth and removal use the
+hash cached in each node and never call `getKey`.  An `Info` template argument
+can override the key type or the hash.
+
+```cpp
+std::tuple<unsigned, const Value *, const Value *> FooNode::getKey() const {
+  return {Opcode, LHS, RHS};
+}
+
+UniquingSet<FooNode> Pool;
+FoldingSetInsertPos IP;
+if (FooNode *N = Pool.find({Opcode, LHS, RHS}, IP))
+  return N;
+Pool.insert(new (Allocator) FooNode(Opcode, LHS, RHS), IP);
+```
+
+Prefer `UniquingSet` when a key can be read out of a node in O(1) and the lookup
+key is built next to `getKey`.  Keep `FoldingSet` for keys that are wide,
+polymorphic, or assembled at many call sites: one `Profile` helper then keeps
+both sides consistent, whereas `getKey` and a lookup site can silently disagree.
+`insert` asserts that a node hashes as its lookup did.
+
 (dss_set)=
 
 #### `<set>`
diff --git a/llvm/include/llvm/ADT/FoldingSet.h b/llvm/include/llvm/ADT/FoldingSet.h
index c984c00b86fd1..42cdc3098e716 100644
--- a/llvm/include/llvm/ADT/FoldingSet.h
+++ b/llvm/include/llvm/ADT/FoldingSet.h
@@ -16,6 +16,7 @@
 #ifndef LLVM_ADT_FOLDINGSET_H
 #define LLVM_ADT_FOLDINGSET_H
 
+#include "llvm/ADT/DenseMapInfo.h"
 #include "llvm/ADT/EpochTracker.h"
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/STLForwardCompat.h"
@@ -159,6 +160,19 @@ template <typename T, typename Ctx> struct DefaultContextualFoldingSetTrait {
 template <typename T, typename Ctx>
 struct ContextualFoldingSetTrait : DefaultContextualFoldingSetTrait<T, Ctx> {};
 
+class FoldingSetInsertPos {
+  uint64_t Value = 0;
+
+  explicit FoldingSetInsertPos(uint32_t Hash) : Value(uint64_t(Hash) + 1) {}
+  uint32_t hash() const { return uint32_t(Value - 1); }
+
+  template <typename, typename> friend class UniquingSet;
+
+public:
+  FoldingSetInsertPos() = default;
+  explicit operator bool() const { return Value != 0; }
+};
+
 //===--------------------------------------------------------------------===//
 /// This class describes a reference to an interned FoldingSetNodeID, which can
 /// be a useful to store node id data rather than using plain FoldingSetNodeIDs,
@@ -389,6 +403,11 @@ class FoldingSetBase : public DebugEpochBase {
   /// it is not already in the folding set.  InsertPos must be obtained from
   /// FindNodeOrInsertPos for an ID that \p N profiles identically to.
   LLVM_ABI void InsertNode(Node *N, void *InsertPos);
+
+  /// Encode \p Hash, which must not be NotAHash, as the token InsertNode takes.
+  static void *encodeInsertPos(uint32_t Hash) {
+    return reinterpret_cast<void *>(static_cast<uintptr_t>(Hash));
+  }
 };
 
 // Convenience type to hide the implementation of the folding set.
@@ -663,6 +682,95 @@ template <typename T> class FoldingSetNodeWrapper : public FoldingSetNode {
   operator const T &() const { return data; }
 };
 
+//===----------------------------------------------------------------------===//
+/// The default UniquingSet Info: \p T supplies its own key.
+template <typename T> struct UniquingSetInfo {
+  using KeyTy = remove_cvref_t<decltype(std::declval<const T &>().getKey())>;
+  static KeyTy getKey(const T &N) { return N.getKey(); }
+  static unsigned getHashValue(const KeyTy &Key) {
+    return DenseMapInfo<KeyTy>::getHashValue(Key);
+  }
+};
+
+/// A uniquing set that compares nodes against a typed key rather than a
+/// serialized FoldingSetNodeID.
+///
+/// \p T must derive from FoldingSetNode and provide a getKey() whose result is
+/// comparable with == and for which DenseMapInfo<KeyTy>::getHashValue exists.
+/// \p Info overrides that:
+///
+/// \code
+///   using KeyTy = ...;
+///   static KeyTy getKey(const T &N);
+///   static unsigned getHashValue(const KeyTy &K);
+/// \endcode
+///
+/// Derive \p Info from UniquingSetInfo<T> to override only the hash.  The
+/// default Info needs \p T complete wherever UniquingSet<T> is instantiated;
+/// FoldingSet does not.  A key may alias storage owned by the node; it is only
+/// used within a single find().
+///
+/// Prefer FoldingSet when a key cannot be read cheaply out of a node: a
+/// FoldingSetNodeID cannot disagree with itself, whereas getKey and the code
+/// that builds a key to look up must be kept in step by hand.
+template <typename T, typename Info = UniquingSetInfo<T>>
+class UniquingSet : public FoldingSetBase {
+public:
+  using KeyTy = typename Info::KeyTy;
+
+  explicit UniquingSet(unsigned Log2InitSize = 6)
+      : FoldingSetBase(Log2InitSize) {}
+
+  using iterator = FoldingSetIterator<T>;
+  iterator begin() { return iterator(Buckets, Buckets + NumBuckets, this); }
+  iterator end() {
+    return iterator(Buckets + NumBuckets, Buckets + NumBuckets, this);
+  }
+
+  using const_iterator = FoldingSetIterator<const T>;
+  const_iterator begin() const {
+    return const_iterator(Buckets, Buckets + NumBuckets, this);
+  }
+  const_iterator end() const {
+    return const_iterator(Buckets + NumBuckets, Buckets + NumBuckets, this);
+  }
+
+  /// Look up \p Key.  On a hit \p InsertPos is cleared; on a miss it receives a
+  /// token for insert().
+  T *find(const KeyTy &Key, FoldingSetInsertPos &InsertPos) {
+    uint32_t Hash = hashKey(Key);
+    unsigned Mask = NumBuckets - 1;
+    for (unsigned I = Hash & Mask; Buckets[I]; I = (I + 1) & Mask) {
+      T *N = static_cast<T *>(static_cast<Node *>(Buckets[I]));
+      if (N->getFoldingSetHash() == Hash && Key == Info::getKey(*N)) {
+        InsertPos = {};
+        return N;
+      }
+    }
+    InsertPos = FoldingSetInsertPos(Hash);
+    return nullptr;
+  }
+
+  /// Insert \p N, which must key identically to the lookup that produced
+  /// \p InsertPos.
+  void insert(T *N, FoldingSetInsertPos InsertPos) {
+    assert(InsertPos && "Invalid InsertPos!");
+    assert(hashKey(Info::getKey(*N)) == InsertPos.hash() &&
+           "N does not key as the lookup that produced InsertPos did");
+    FoldingSetBase::InsertNode(N, encodeInsertPos(InsertPos.hash()));
+  }
+
+  /// Remove \p N, returning whether it was present.
+  bool erase(T *N) { return FoldingSetBase::RemoveNode(N); }
+
+private:
+  // Never NotAHash, for the reason FoldingSetNodeIDRef::ComputeHash gives.
+  static uint32_t hashKey(const KeyTy &Key) {
+    uint32_t Hash = Info::getHashValue(Key);
+    return Hash == FoldingSetNodeIDRef::NotAHash ? 1 : Hash;
+  }
+};
+
 //===----------------------------------------------------------------------===//
 /// This is a subclass of FoldingSetNode which stores a FoldingSetNodeID value
 /// rather than requiring the node to recompute it each time it is needed. This
diff --git a/llvm/include/llvm/TableGen/Record.h b/llvm/include/llvm/TableGen/Record.h
index 80e3b17364e37..ca9c813b8a0ca 100644
--- a/llvm/include/llvm/TableGen/Record.h
+++ b/llvm/include/llvm/TableGen/Record.h
@@ -36,6 +36,7 @@
 #include <memory>
 #include <optional>
 #include <string>
+#include <tuple>
 #include <utility>
 #include <variant>
 #include <vector>
@@ -770,8 +771,6 @@ class ListInit final : public TypedInit,
   }
   static const ListInit *get(ArrayRef<const Init *> Range, const RecTy *EltTy);
 
-  void Profile(FoldingSetNodeID &ID) const;
-
   ArrayRef<const Init *> getElements() const {
     return ArrayRef(getTrailingObjects(), NumElements);
   }
@@ -781,6 +780,10 @@ class ListInit final : public TypedInit,
 
   const Init *getElement(unsigned Idx) const { return getElements()[Idx]; }
 
+  std::pair<ArrayRef<const Init *>, const RecTy *> getKey() const {
+    return {getElements(), getElementType()};
+  }
+
   const RecTy *getElementType() const {
     return cast<ListRecTy>(getType())->getElementType();
   }
@@ -867,11 +870,13 @@ class UnOpInit final : public OpInit, public FoldingSetNode {
 
   static const UnOpInit *get(UnaryOp opc, const Init *lhs, const RecTy *Type);
 
-  void Profile(FoldingSetNodeID &ID) const;
-
   UnaryOp getOpcode() const { return (UnaryOp)Opc; }
   const Init *getOperand() const { return LHS; }
 
+  std::tuple<UnaryOp, const Init *, const RecTy *> getKey() const {
+    return {getOpcode(), LHS, getType()};
+  }
+
   // Fold - If possible, fold this to a simpler init. Return this if not
   // possible to fold.
   const Init *Fold(const Record *CurRec, bool IsFinal = false) const;
@@ -936,12 +941,15 @@ class BinOpInit final : public OpInit, public FoldingSetNode {
   static const Init *getStrConcat(const Init *lhs, const Init *rhs);
   static const Init *getListConcat(const TypedInit *lhs, const Init *rhs);
 
-  void Profile(FoldingSetNodeID &ID) const;
-
   BinaryOp getOpcode() const { return (BinaryOp)Opc; }
   const Init *getLHS() const { return LHS; }
   const Init *getRHS() const { return RHS; }
 
+  std::tuple<BinaryOp, const Init *, const Init *, const RecTy *>
+  getKey() const {
+    return {getOpcode(), LHS, RHS, getType()};
+  }
+
   std::optional<bool> CompareInit(unsigned Opc, const Init *LHS,
                                   const Init *RHS) const;
 
@@ -989,13 +997,16 @@ class TernOpInit final : public OpInit, public FoldingSetNode {
   static const TernOpInit *get(TernaryOp opc, const Init *lhs, const Init *mhs,
                                const Init *rhs, const RecTy *Type);
 
-  void Profile(FoldingSetNodeID &ID) const;
-
   TernaryOp getOpcode() const { return (TernaryOp)Opc; }
   const Init *getLHS() const { return LHS; }
   const Init *getMHS() const { return MHS; }
   const Init *getRHS() const { return RHS; }
 
+  std::tuple<TernaryOp, const Init *, const Init *, const Init *, const RecTy *>
+  getKey() const {
+    return {getOpcode(), LHS, MHS, RHS, getType()};
+  }
+
   // Fold - If possible, fold this to a simpler init. Return this if not
   // possible to fold.
   const Init *Fold(const Record *CurRec) const;
diff --git a/llvm/lib/TableGen/Record.cpp b/llvm/lib/TableGen/Record.cpp
index ac62e2f5785ae..bf9824a3c5428 100644
--- a/llvm/lib/TableGen/Record.cpp
+++ b/llvm/lib/TableGen/Record.cpp
@@ -75,10 +75,10 @@ struct detail::RecordKeeperImpl {
   std::map<int64_t, IntInit *> TheIntInitPool;
   StringMap<const StringInit *, BumpPtrAllocator &> StringInitStringPool;
   StringMap<const StringInit *, BumpPtrAllocator &> StringInitCodePool;
-  FoldingSet<ListInit> TheListInitPool;
-  FoldingSet<UnOpInit> TheUnOpInitPool;
-  FoldingSet<BinOpInit> TheBinOpInitPool;
-  FoldingSet<TernOpInit> TheTernOpInitPool;
+  UniquingSet<ListInit> TheListInitPool;
+  UniquingSet<UnOpInit> TheUnOpInitPool;
+  UniquingSet<BinOpInit> TheBinOpInitPool;
+  UniquingSet<TernOpInit> TheTernOpInitPool;
   FoldingSet<FoldOpInit> TheFoldOpInitPool;
   FoldingSet<IsAOpInit> TheIsAOpInitPool;
   FoldingSet<ExistsOpInit> TheExistsOpInitPool;
@@ -694,16 +694,6 @@ const Init *StringInit::convertInitializerTo(const RecTy *Ty) const {
   return nullptr;
 }
 
-static void ProfileListInit(FoldingSetNodeID &ID,
-                            ArrayRef<const Init *> Elements,
-                            const RecTy *EltTy) {
-  ID.AddInteger(Elements.size());
-  ID.AddPointer(EltTy);
-
-  for (const Init *E : Elements)
-    ID.AddPointer(E);
-}
-
 ListInit::ListInit(ArrayRef<const Init *> Elements, const RecTy *EltTy)
     : TypedInit(IK_ListInit, ListRecTy::get(EltTy)),
       NumElements(Elements.size()) {
@@ -712,12 +702,9 @@ ListInit::ListInit(ArrayRef<const Init *> Elements, const RecTy *EltTy)
 
 const ListInit *ListInit::get(ArrayRef<const Init *> Elements,
                               const RecTy *EltTy) {
-  FoldingSetNodeID ID;
-  ProfileListInit(ID, Elements, EltTy);
-
   detail::RecordKeeperImpl &RK = EltTy->getRecordKeeper().getImpl();
-  void *IP = nullptr;
-  if (const ListInit *I = RK.TheListInitPool.FindNodeOrInsertPos(ID, IP))
+  FoldingSetInsertPos IP;
+  if (const ListInit *I = RK.TheListInitPool.find({Elements, EltTy}, IP))
     return I;
 
   assert(Elements.empty() || !isa<TypedInit>(Elements[0]) ||
@@ -726,15 +713,10 @@ const ListInit *ListInit::get(ArrayRef<const Init *> Elements,
   void *Mem = RK.Allocator.Allocate(
       totalSizeToAlloc<const Init *>(Elements.size()), alignof(ListInit));
   ListInit *I = new (Mem) ListInit(Elements, EltTy);
-  RK.TheListInitPool.InsertNode(I, IP);
+  RK.TheListInitPool.insert(I, IP);
   return I;
 }
 
-void ListInit::Profile(FoldingSetNodeID &ID) const {
-  const RecTy *EltTy = cast<ListRecTy>(getType())->getElementType();
-  ProfileListInit(ID, getElements(), EltTy);
-}
-
 const Init *ListInit::convertInitializerTo(const RecTy *Ty) const {
   if (getType() == Ty)
     return this;
@@ -814,31 +796,17 @@ const Init *OpInit::getBit(unsigned Bit) const {
   return VarBitInit::get(this, Bit);
 }
 
-static void ProfileUnOpInit(FoldingSetNodeID &ID, unsigned Opcode,
-                            const Init *Op, const RecTy *Type) {
-  ID.AddInteger(Opcode);
-  ID.AddPointer(Op);
-  ID.AddPointer(Type);
-}
-
 const UnOpInit *UnOpInit::get(UnaryOp Opc, const Init *LHS, const RecTy *Type) {
-  FoldingSetNodeID ID;
-  ProfileUnOpInit(ID, Opc, LHS, Type);
-
   detail::RecordKeeperImpl &RK = Type->getRecordKeeper().getImpl();
-  void *IP = nullptr;
-  if (const UnOpInit *I = RK.TheUnOpInitPool.FindNodeOrInsertPos(ID, IP))
+  FoldingSetInsertPos IP;
+  if (const UnOpInit *I = RK.TheUnOpInitPool.find({Opc, LHS, Type}, IP))
     return I;
 
   UnOpInit *I = new (RK.Allocator) UnOpInit(Opc, LHS, Type);
-  RK.TheUnOpInitPool.InsertNode(I, IP);
+  RK.TheUnOpInitPool.insert(I, IP);
   return I;
 }
 
-void UnOpInit::Profile(FoldingSetNodeID &ID) const {
-  ProfileUnOpInit(ID, getOpcode(), getOperand(), getType());
-}
-
 const Init *UnOpInit::Fold(const Record *CurRec, bool IsFinal) const {
   RecordKeeper &RK = getRecordKeeper();
   switch (getOpcode()) {
@@ -1083,34 +1051,18 @@ std::string UnOpInit::getAsString() const {
   return Result + "(" + LHS->getAsString() + ")";
 }
 
-static void ProfileBinOpInit(FoldingSetNodeID &ID, unsigned Opcode,
-                             const Init *LHS, const Init *RHS,
-                             const RecTy *Type) {
-  ID.AddInteger(Opcode);
-  ID.AddPointer(LHS);
-  ID.AddPointer(RHS);
-  ID.AddPointer(Type);
-}
-
 const BinOpInit *BinOpInit::get(BinaryOp Opc, const Init *LHS, const Init *RHS,
                                 const RecTy *Type) {
-  FoldingSetNodeID ID;
-  ProfileBinOpInit(ID, Opc, LHS, RHS, Type);
-
   detail::RecordKeeperImpl &RK = LHS->getRecordKeeper().getImpl();
-  void *IP = nullptr;
-  if (const BinOpInit *I = RK.TheBinOpInitPool.FindNodeOrInsertPos(ID, IP))
+  FoldingSetInsertPos IP;
+  if (const BinOpInit *I = RK.TheBinOpInitPool.find({Opc, LHS, RHS, Type}, IP))
     return I;
 
   BinOpInit *I = new (RK.Allocator) BinOpInit(Opc, LHS, RHS, Type);
-  RK.TheBinOpInitPool.InsertNode(I, IP);
+  RK.TheBinOpInitPool.insert(I, IP);
   return I;
 }
 
-void BinOpInit::Profile(FoldingSetNodeID &ID) const {
-  ProfileBinOpInit(ID, getOpcode(), getLHS(), getRHS(), getType());
-}
-
 static const StringInit *ConcatStringInits(const StringInit *I0,
                                            const StringInit *I1) {
   SmallString<80> Concat(I0->getValue());
@@ -1676,36 +1628,19 @@ std::string BinOpInit::getAsString() const {
   return Result + "(" + LHS->getAsString() + ", " + RHS->getAsString() + ")";
 }
 
-static void ProfileTernOpInit(FoldingSetNodeID &ID, unsigned Opcode,
-                              const Init *LHS, const Init *MHS, const Init *RHS,
-                              const RecTy *Type) {
-  ID.AddInteger(Opcode);
-  ID.AddPointer(LHS);
-  ID.AddPointer(MHS);
-  ID.AddPointer(RHS);
-  ID.AddPointer(Type);
-}
-
 const TernOpInit *TernOpInit::get(TernaryOp Opc, const Init *LHS,
                                   const Init *MHS, const Init *RHS,
                                   const RecTy *Type) {
-  FoldingSetNodeID ID;
-  ProfileTernOpInit(ID, Opc, LHS, MHS, RHS, Type);
-
   detail::RecordKeeperImpl &RK = LHS->getRecordKeeper().getImpl();
-  void *IP = nullptr;
-  if (TernOpInit *I = RK.TheTernOpInitPool.FindNodeOrInsertPos(ID, IP))
+  FoldingSetInsertPos IP;
+  if (TernOpInit *I = RK.TheTernOpInitPool.find({Opc, LHS, MHS, RHS, Type}, IP))
     return I;
 
   TernOpInit *I = new (RK.Allocator) TernOpInit(Opc, LHS, MHS, RHS, Type);
-  RK.TheTernOpInitPool.InsertNode(I, IP);
+  RK.TheTernOpInitPool.insert(I, IP);
   return I;
 }
 
-void TernOpInit::Profile(FoldingSetNodeID &ID) const {
-  ProfileTernOpInit(ID, getOpcode(), getLHS(), getMHS(), getRHS(), getType());
-}
-
 static const Init *ItemApply(const Init *LHS, const Init *MHSe, const Init *RHS,
                              const Record *CurRec) {
   MapResolver R(CurRec);
diff --git a/llvm/unittests/ADT/FoldingSet.cpp b/llvm/unittests/ADT/FoldingSet.cpp
index 2556d64bae7a3..534b532c01419 100644
--- a/llvm/unittests/ADT/FoldingSet.cpp
+++ b/llvm/unittests/ADT/FoldingSet.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ArrayRef.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <map>
@@ -515,4 +516,177 @@ TEST(FoldingSetTest, InsertPosSurvivesGrowth) {
   EXPECT_EQ(201u, Set.size());
 }
 
+// FoldingSetNode is a non-first base, so find()'s two-step cast must adjust.
+struct KeyedPair : NonEmptyBase, FoldingSetNode {
+  unsigned A, B;
+  KeyedPair(unsigned A, unsigned B) : A(A), B(B) {}
+  std::pair<unsigned, unsigned> getKey() const { return {A, B}; }
+};
+
+TEST(UniquingSetTest, Basic) {
+  UniquingSet<KeyedPair> Set;
+  FoldingSetInsertPos InsertPos;
+  EXPECT_EQ(nullptr, Set.find({1, 2}, InsertPos));
+  EXPECT_TRUE(bool(InsertPos));
+
+  KeyedPair A(1, 2);
+  Set.insert(&A, InsertPos);
+  EXPECT_EQ(1u, Set.size());
+
+  // A hit clears the token; seed it with the one the miss produced.
+  FoldingSetInsertPos Token = InsertPos;
+  EXPECT_EQ(&A, Set.find({1, 2}, Token));
+  EXPECT_FALSE(bool(Token));
+  EXPECT_EQ(nullptr, Set.find({2, 1}, Token));
+  EXPECT_TRUE(bool(Token));
+
+  KeyedPair B(2, 1);
+  Set.insert(&B, Token);
+
+  std::vector<KeyedPair *> Visited;
+  for (KeyedPair &N : Set)
+    Visited.push_back(&N);
+  EXPECT_THAT(Visited, UnorderedElementsAre(&A, &B));
+
+  std::vector<const KeyedPair *> ConstVisited;
+  for (const KeyedPair &N : std::as_const(Set))
+    ConstVisited.push_back(&N);
+  EXPECT_THAT(ConstVisited, UnorderedElementsAre(&A, &B));
+
+  EXPECT_TRUE(Set.erase(&A));
+  EXPECT_FALSE(Set.erase(&A));
+  KeyedPair NeverInserted(3, 4);
+  EXPECT_FALSE(Set.erase(&NeverInserted));
+  EXPECT_EQ(1u, Set.size());
+  EXPECT_EQ(nullptr, Set.find({1, 2}, Token));
+}
+
+// Every key hashes to NotAHash, which must be remapped so that erase() does not
+// read a live node as never-inserted.
+struct ZeroHashNode : FoldingSetNode {
+  unsigned Key;
+  explicit ZeroHashNode(unsigned Key) : Key(Key) {}
+  unsigned getKey() const { return Key; }
+};
+
+struct ZeroHashInfo : UniquingSetInfo<ZeroHashNode> {
+  static unsigned getHashValue(const KeyTy &) {
+    return FoldingSetNodeIDRef::NotAHash;
+  }
+};
+
+TEST(UniquingSetTest, KeyHashingToNotAHash) {
+  UniquingSet<ZeroHashNode, ZeroHashInfo> Set;
+  ZeroHashNode A(1), B(2);
+
+  FoldingSetInsertPos P;
+  ASSERT_EQ(nullptr, Set.find(1, P));
+  ASSERT_TRUE(bool(P));
+  Set.insert(&A, P);
+  // Same bucket, different key: the probe must walk past A.
+  ASSERT_EQ(nullptr, Set.find(2, P));
+  ASSERT_TRUE(bool(P));
+  Set.insert(&B, P);
+
+  FoldingSetInsertPos Unused;
+  EXPECT_EQ(&A, Set.find(1, Unused));
+  EXPECT_EQ(&B, Set.find(2, Unused));
+  EXPECT_EQ(2u, Set.size());
+
+  EXPECT_TRUE(Set.erase(&A));
+  EXPECT_FALSE(Set.erase(&A));
+  EXPE...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/219630


More information about the llvm-commits mailing list