[clang] [clang] Unique the QualType-keyed type pools in a UniquingSet. NFC (PR #221850)

Fangrui Song via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 7 16:48:55 PDT 2026


https://github.com/MaskRay created https://github.com/llvm/llvm-project/pull/221850

Eight of the ASTContext type pools key on a QualType, or on a QualType
and a bool. The `get*Type()` functions incur the FoldingSetNodeID
serialization overhead before probing the hash table.

Switch to UniquingSet. Define `QualTypeBoolInfo` for three pools that
key on a QualType and a bool, because DenseMapInfo has no bool
specialization.

After the canonical type is built, these getters re-probe to refresh the
insert token. A token is now a hash (#218190), not a bucket, so nothing
invalidates it; fold the re-probe into the assertion it feeds.

Aided by Opus 5


>From 2b611bbe1a58da2275fd62f36c1725243fbd94a1 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 7 Sep 2026 14:47:56 -0700
Subject: [PATCH] [clang] Unique the QualType-keyed type pools in a
 UniquingSet. NFC

Eight of the ASTContext type pools key on a QualType, or on a QualType
and a bool. The `get*Type()` functions incur the FoldingSetNodeID
serialization overhead before probing the hash table.

Switch to UniquingSet. Define `QualTypeBoolInfo` for three pools that
key on a QualType and a bool, because DenseMapInfo has no bool
specialization.

After the canonical type is built, these getters re-probe to refresh the
insert token. A token is now a hash (#218190), not a bucket, so nothing
invalidates it; fold the re-probe into the assertion it feeds.

Aided by Opus 5
---
 clang/include/clang/AST/ASTContext.h | 34 +++++++++---
 clang/include/clang/AST/TypeBase.h   | 67 +++++-------------------
 clang/lib/AST/ASTContext.cpp         | 78 ++++++++--------------------
 3 files changed, 59 insertions(+), 120 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index f875ae365b892..8277f1fcced95 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -218,6 +218,22 @@ struct PFPField {
   FieldDecl *Field;
 };
 
+/// UniquingSet info for pools keyed on a QualType and a bool. DenseMapInfo has
+/// no bool specialization, so we cannot use the default UniquingSetInfo.
+struct QualTypeBoolInfo {
+  using KeyTy = std::pair<QualType, bool>;
+
+  template <typename T> static KeyTy getKey(const T &N) { return N.getKey(); }
+
+  static unsigned getHashValue(const KeyTy &Key) {
+    return llvm::hash_combine(Key.first.getAsOpaquePtr(), Key.second);
+  }
+
+  template <typename T> static bool isEqual(const KeyTy &Key, const T &N) {
+    return Key == N.getKey();
+  }
+};
+
 /// Holds long-lived AST nodes (such as types and decls) that can be
 /// referred to throughout the semantic analysis of a file.
 class ASTContext : public RefCountedBase<ASTContext> {
@@ -225,12 +241,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
 
   mutable SmallVector<Type *, 0> Types;
   mutable llvm::FoldingSet<ExtQuals> ExtQualNodes;
-  mutable llvm::FoldingSet<ComplexType> ComplexTypes;
-  mutable llvm::FoldingSet<PointerType> PointerTypes{GeneralTypesLog2InitSize};
+  mutable llvm::UniquingSet<ComplexType> ComplexTypes;
+  mutable llvm::UniquingSet<PointerType> PointerTypes{GeneralTypesLog2InitSize};
   mutable llvm::FoldingSet<AdjustedType> AdjustedTypes;
-  mutable llvm::FoldingSet<BlockPointerType> BlockPointerTypes;
-  mutable llvm::FoldingSet<LValueReferenceType> LValueReferenceTypes;
-  mutable llvm::FoldingSet<RValueReferenceType> RValueReferenceTypes;
+  mutable llvm::UniquingSet<BlockPointerType> BlockPointerTypes;
+  mutable llvm::UniquingSet<LValueReferenceType, QualTypeBoolInfo>
+      LValueReferenceTypes;
+  mutable llvm::UniquingSet<RValueReferenceType, QualTypeBoolInfo>
+      RValueReferenceTypes;
   mutable llvm::FoldingSet<MemberPointerType> MemberPointerTypes;
   mutable llvm::ContextualFoldingSet<ConstantArrayType, ASTContext &>
       ConstantArrayTypes;
@@ -269,7 +287,7 @@ class ASTContext : public RefCountedBase<ASTContext> {
       SubstBuiltinTemplatePackTypes;
   mutable llvm::ContextualFoldingSet<TemplateSpecializationType, ASTContext&>
     TemplateSpecializationTypes;
-  mutable llvm::FoldingSet<ParenType> ParenTypes{GeneralTypesLog2InitSize};
+  mutable llvm::UniquingSet<ParenType> ParenTypes{GeneralTypesLog2InitSize};
   mutable llvm::FoldingSet<TagTypeFoldingSetPlaceholder> TagTypes;
   mutable llvm::FoldingSet<FoldingSetPlaceholder<UnresolvedUsingType>>
       UnresolvedUsingTypes;
@@ -289,10 +307,10 @@ class ASTContext : public RefCountedBase<ASTContext> {
   mutable llvm::DenseMap<llvm::FoldingSetNodeIDRef, AutoType *> AutoTypes;
   mutable llvm::FoldingSet<DeducedTemplateSpecializationType>
     DeducedTemplateSpecializationTypes;
-  mutable llvm::FoldingSet<AtomicType> AtomicTypes;
+  mutable llvm::UniquingSet<AtomicType> AtomicTypes;
   mutable llvm::ContextualFoldingSet<AttributedType, ASTContext &>
       AttributedTypes;
-  mutable llvm::FoldingSet<PipeType> PipeTypes;
+  mutable llvm::UniquingSet<PipeType, QualTypeBoolInfo> PipeTypes;
   mutable llvm::FoldingSet<BitIntType> BitIntTypes;
   mutable llvm::ContextualFoldingSet<DependentBitIntType, ASTContext &>
       DependentBitIntTypes;
diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h
index cdbd20b62bac5..9edf8a0ce9c68 100644
--- a/clang/include/clang/AST/TypeBase.h
+++ b/clang/include/clang/AST/TypeBase.h
@@ -3367,13 +3367,7 @@ class ComplexType : public Type, public llvm::FoldingSetNode {
   bool isSugared() const { return false; }
   QualType desugar() const { return QualType(this, 0); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getElementType());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType Element) {
-    ID.AddPointer(Element.getAsOpaquePtr());
-  }
+  QualType getKey() const { return getElementType(); }
 
   static bool classof(const Type *T) { return T->getTypeClass() == Complex; }
 };
@@ -3393,13 +3387,7 @@ class ParenType : public Type, public llvm::FoldingSetNode {
   bool isSugared() const { return true; }
   QualType desugar() const { return getInnerType(); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getInnerType());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType Inner) {
-    Inner.Profile(ID);
-  }
+  QualType getKey() const { return getInnerType(); }
 
   static bool classof(const Type *T) { return T->getTypeClass() == Paren; }
 };
@@ -3420,13 +3408,7 @@ class PointerType : public Type, public llvm::FoldingSetNode {
   bool isSugared() const { return false; }
   QualType desugar() const { return QualType(this, 0); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getPointeeType());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType Pointee) {
-    ID.AddPointer(Pointee.getAsOpaquePtr());
-  }
+  QualType getKey() const { return getPointeeType(); }
 
   static bool classof(const Type *T) { return T->getTypeClass() == Pointer; }
 };
@@ -3670,13 +3652,7 @@ class BlockPointerType : public Type, public llvm::FoldingSetNode {
   bool isSugared() const { return false; }
   QualType desugar() const { return QualType(this, 0); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-      Profile(ID, getPointeeType());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType Pointee) {
-      ID.AddPointer(Pointee.getAsOpaquePtr());
-  }
+  QualType getKey() const { return getPointeeType(); }
 
   static bool classof(const Type *T) {
     return T->getTypeClass() == BlockPointer;
@@ -3702,6 +3678,10 @@ class ReferenceType : public Type, public llvm::FoldingSetNode {
 
   QualType getPointeeTypeAsWritten() const { return PointeeType; }
 
+  std::pair<QualType, bool> getKey() const {
+    return {getPointeeTypeAsWritten(), isSpelledAsLValue()};
+  }
+
   QualType getPointeeType() const {
     // FIXME: this might strip inner qualifiers; okay?
     const ReferenceType *T = this;
@@ -3710,17 +3690,6 @@ class ReferenceType : public Type, public llvm::FoldingSetNode {
     return T->PointeeType;
   }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, PointeeType, isSpelledAsLValue());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID,
-                      QualType Referencee,
-                      bool SpelledAsLValue) {
-    ID.AddPointer(Referencee.getAsOpaquePtr());
-    ID.AddBoolean(SpelledAsLValue);
-  }
-
   static bool classof(const Type *T) {
     return T->getTypeClass() == LValueReference ||
            T->getTypeClass() == RValueReference;
@@ -8280,7 +8249,6 @@ class ObjCObjectPointerType : public Type, public llvm::FoldingSetNode {
   static void Profile(llvm::FoldingSetNodeID &ID, QualType T) {
     ID.AddPointer(T.getAsOpaquePtr());
   }
-
   static bool classof(const Type *T) {
     return T->getTypeClass() == ObjCObjectPointer;
   }
@@ -8299,17 +8267,11 @@ class AtomicType : public Type, public llvm::FoldingSetNode {
   /// the type returned by performing an atomic load of this atomic type.
   QualType getValueType() const { return ValueType; }
 
+  QualType getKey() const { return getValueType(); }
+
   bool isSugared() const { return false; }
   QualType desugar() const { return QualType(this, 0); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getValueType());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType T) {
-    ID.AddPointer(T.getAsOpaquePtr());
-  }
-
   static bool classof(const Type *T) {
     return T->getTypeClass() == Atomic;
   }
@@ -8333,13 +8295,8 @@ class PipeType : public Type, public llvm::FoldingSetNode {
 
   QualType desugar() const { return QualType(this, 0); }
 
-  void Profile(llvm::FoldingSetNodeID &ID) {
-    Profile(ID, getElementType(), isReadOnly());
-  }
-
-  static void Profile(llvm::FoldingSetNodeID &ID, QualType T, bool isRead) {
-    ID.AddPointer(T.getAsOpaquePtr());
-    ID.AddBoolean(isRead);
+  std::pair<QualType, bool> getKey() const {
+    return {getElementType(), isReadOnly()};
   }
 
   static bool classof(const Type *T) {
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 84455fb6396bd..a8b256167fd64 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -3971,11 +3971,8 @@ void ASTContext::adjustExceptionSpec(
 QualType ASTContext::getComplexType(QualType T) const {
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  ComplexType::Profile(ID, T);
-
   llvm::FoldingSetInsertToken Token;
-  if (ComplexType *CT = ComplexTypes.lookup(ID, Token))
+  if (ComplexType *CT = ComplexTypes.lookup(T, Token))
     return QualType(CT, 0);
 
   // If the pointee type isn't canonical, this won't be a canonical type either,
@@ -3984,9 +3981,7 @@ QualType ASTContext::getComplexType(QualType T) const {
   if (!T.isCanonical()) {
     Canonical = getComplexType(getCanonicalType(T));
 
-    // Get the new insert position for the node we care about.
-    ComplexType *NewIP = ComplexTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!ComplexTypes.lookup(T, Token) && "Shouldn't be in the map!");
   }
   auto *New = new (*this, alignof(ComplexType)) ComplexType(T, Canonical);
   Types.push_back(New);
@@ -3999,11 +3994,8 @@ QualType ASTContext::getComplexType(QualType T) const {
 QualType ASTContext::getPointerType(QualType T) const {
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  PointerType::Profile(ID, T);
-
   llvm::FoldingSetInsertToken Token;
-  if (PointerType *PT = PointerTypes.lookup(ID, Token))
+  if (PointerType *PT = PointerTypes.lookup(T, Token))
     return QualType(PT, 0);
 
   // If the pointee type isn't canonical, this won't be a canonical type either,
@@ -4012,9 +4004,7 @@ QualType ASTContext::getPointerType(QualType T) const {
   if (!T.isCanonical()) {
     Canonical = getPointerType(getCanonicalType(T));
 
-    // Get the new insert position for the node we care about.
-    PointerType *NewIP = PointerTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!PointerTypes.lookup(T, Token) && "Shouldn't be in the map!");
   }
   auto *New = new (*this, alignof(PointerType)) PointerType(T, Canonical);
   Types.push_back(New);
@@ -4123,11 +4113,8 @@ QualType ASTContext::getBlockPointerType(QualType T) const {
   assert(T->isFunctionType() && "block of function types only");
   // Unique pointers, to guarantee there is only one block of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  BlockPointerType::Profile(ID, T);
-
   llvm::FoldingSetInsertToken Token;
-  if (BlockPointerType *PT = BlockPointerTypes.lookup(ID, Token))
+  if (BlockPointerType *PT = BlockPointerTypes.lookup(T, Token))
     return QualType(PT, 0);
 
   // If the block pointee type isn't canonical, this won't be a canonical
@@ -4136,9 +4123,7 @@ QualType ASTContext::getBlockPointerType(QualType T) const {
   if (!T.isCanonical()) {
     Canonical = getBlockPointerType(getCanonicalType(T));
 
-    // Get the new insert position for the node we care about.
-    BlockPointerType *NewIP = BlockPointerTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!BlockPointerTypes.lookup(T, Token) && "Shouldn't be in the map!");
   }
   auto *New =
       new (*this, alignof(BlockPointerType)) BlockPointerType(T, Canonical);
@@ -4157,11 +4142,9 @@ ASTContext::getLValueReferenceType(QualType T, bool SpelledAsLValue) const {
 
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  ReferenceType::Profile(ID, T, SpelledAsLValue);
-
   llvm::FoldingSetInsertToken Token;
-  if (LValueReferenceType *RT = LValueReferenceTypes.lookup(ID, Token))
+  if (LValueReferenceType *RT =
+          LValueReferenceTypes.lookup({T, SpelledAsLValue}, Token))
     return QualType(RT, 0);
 
   const auto *InnerRef = T->getAs<ReferenceType>();
@@ -4173,9 +4156,8 @@ ASTContext::getLValueReferenceType(QualType T, bool SpelledAsLValue) const {
     QualType PointeeType = (InnerRef ? InnerRef->getPointeeType() : T);
     Canonical = getLValueReferenceType(getCanonicalType(PointeeType));
 
-    // Get the new insert position for the node we care about.
-    LValueReferenceType *NewIP = LValueReferenceTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!LValueReferenceTypes.lookup({T, SpelledAsLValue}, Token) &&
+           "Shouldn't be in the map!");
   }
 
   auto *New = new (*this, alignof(LValueReferenceType))
@@ -4195,11 +4177,8 @@ QualType ASTContext::getRValueReferenceType(QualType T) const {
 
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  ReferenceType::Profile(ID, T, false);
-
   llvm::FoldingSetInsertToken Token;
-  if (RValueReferenceType *RT = RValueReferenceTypes.lookup(ID, Token))
+  if (RValueReferenceType *RT = RValueReferenceTypes.lookup({T, false}, Token))
     return QualType(RT, 0);
 
   const auto *InnerRef = T->getAs<ReferenceType>();
@@ -4211,9 +4190,8 @@ QualType ASTContext::getRValueReferenceType(QualType T) const {
     QualType PointeeType = (InnerRef ? InnerRef->getPointeeType() : T);
     Canonical = getRValueReferenceType(getCanonicalType(PointeeType));
 
-    // Get the new insert position for the node we care about.
-    RValueReferenceType *NewIP = RValueReferenceTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!RValueReferenceTypes.lookup({T, false}, Token) &&
+           "Shouldn't be in the map!");
   }
 
   auto *New = new (*this, alignof(RValueReferenceType))
@@ -5207,11 +5185,8 @@ QualType ASTContext::getFunctionTypeInternal(
 }
 
 QualType ASTContext::getPipeType(QualType T, bool ReadOnly) const {
-  llvm::FoldingSetNodeID ID;
-  PipeType::Profile(ID, T, ReadOnly);
-
   llvm::FoldingSetInsertToken Token;
-  if (PipeType *PT = PipeTypes.lookup(ID, Token))
+  if (PipeType *PT = PipeTypes.lookup({T, ReadOnly}, Token))
     return QualType(PT, 0);
 
   // If the pipe element type isn't canonical, this won't be a canonical type
@@ -5220,10 +5195,8 @@ QualType ASTContext::getPipeType(QualType T, bool ReadOnly) const {
   if (!T.isCanonical()) {
     Canonical = getPipeType(getCanonicalType(T), ReadOnly);
 
-    // Get the new insert position for the node we care about.
-    PipeType *NewIP = PipeTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!");
-    (void)NewIP;
+    assert(!PipeTypes.lookup({T, ReadOnly}, Token) &&
+           "Shouldn't be in the map!");
   }
   auto *New = new (*this, alignof(PipeType)) PipeType(T, Canonical, ReadOnly);
   Types.push_back(New);
@@ -6197,20 +6170,16 @@ QualType ASTContext::getTemplateSpecializationType(
 
 QualType
 ASTContext::getParenType(QualType InnerType) const {
-  llvm::FoldingSetNodeID ID;
-  ParenType::Profile(ID, InnerType);
-
   llvm::FoldingSetInsertToken Token;
-  ParenType *T = ParenTypes.lookup(ID, Token);
+  ParenType *T = ParenTypes.lookup(InnerType, Token);
   if (T)
     return QualType(T, 0);
 
   QualType Canon = InnerType;
   if (!Canon.isCanonical()) {
     Canon = getCanonicalType(InnerType);
-    ParenType *CheckT = ParenTypes.lookup(ID, Token);
-    assert(!CheckT && "Paren canonical type broken");
-    (void)CheckT;
+    assert(!ParenTypes.lookup(InnerType, Token) &&
+           "Paren canonical type broken");
   }
 
   T = new (*this, alignof(ParenType)) ParenType(InnerType, Canon);
@@ -7003,11 +6972,8 @@ QualType ASTContext::getDeducedTemplateSpecializationType(
 QualType ASTContext::getAtomicType(QualType T) const {
   // Unique pointers, to guarantee there is only one pointer of a particular
   // structure.
-  llvm::FoldingSetNodeID ID;
-  AtomicType::Profile(ID, T);
-
   llvm::FoldingSetInsertToken Token;
-  if (AtomicType *AT = AtomicTypes.lookup(ID, Token))
+  if (AtomicType *AT = AtomicTypes.lookup(T, Token))
     return QualType(AT, 0);
 
   // If the atomic value type isn't canonical, this won't be a canonical type
@@ -7016,9 +6982,7 @@ QualType ASTContext::getAtomicType(QualType T) const {
   if (!T.isCanonical()) {
     Canonical = getAtomicType(getCanonicalType(T));
 
-    // Get the new insert position for the node we care about.
-    AtomicType *NewIP = AtomicTypes.lookup(ID, Token);
-    assert(!NewIP && "Shouldn't be in the map!"); (void)NewIP;
+    assert(!AtomicTypes.lookup(T, Token) && "Shouldn't be in the map!");
   }
   auto *New = new (*this, alignof(AtomicType)) AtomicType(T, Canonical);
   Types.push_back(New);



More information about the cfe-commits mailing list