Skip to content

Conversation

@ChuanqiXu9
Copy link
Member

I meant to add test but I found it is hard as now we will always load every redecls after ASTReader. So I just add an assert for it.

@ChuanqiXu9 ChuanqiXu9 added the clang:modules C++20 modules and Clang Header Modules label Dec 5, 2025
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 5, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-modules

Author: Chuanqi Xu (ChuanqiXu9)

Changes

I meant to add test but I found it is hard as now we will always load every redecls after ASTReader. So I just add an assert for it.


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

11 Files Affected:

  • (modified) clang/include/clang/AST/Decl.h (+20)
  • (modified) clang/include/clang/AST/DeclBase.h (+27-8)
  • (modified) clang/include/clang/AST/DeclCXX.h (+5)
  • (modified) clang/include/clang/AST/DeclObjC.h (+7)
  • (modified) clang/include/clang/AST/DeclTemplate.h (+3)
  • (modified) clang/include/clang/AST/Redeclarable.h (+8-2)
  • (modified) clang/include/clang/Serialization/ASTReader.h (+2)
  • (modified) clang/lib/AST/DeclCXX.cpp (+8)
  • (modified) clang/lib/AST/DeclObjC.cpp (+5)
  • (modified) clang/lib/Serialization/ASTReader.cpp (+6-3)
  • (modified) clang/lib/Serialization/ASTReaderDecl.cpp (+12-2)
diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index 2e8ceff453547..632a7326316a0 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -108,6 +108,9 @@ class TranslationUnitDecl : public Decl,
   TranslationUnitDecl *getNextRedeclarationImpl() override {
     return getNextRedeclaration();
   }
+  TranslationUnitDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
 
   TranslationUnitDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
@@ -607,6 +610,7 @@ class NamespaceDecl : public NamespaceBaseDecl,
   using redeclarable_base = Redeclarable<NamespaceDecl>;
 
   NamespaceDecl *getNextRedeclarationImpl() override;
+  NamespaceDecl *getNextRedeclarationNoUpdateImpl() override;
   NamespaceDecl *getPreviousDeclImpl() override;
   NamespaceDecl *getMostRecentDeclImpl() override;
 
@@ -1135,6 +1139,10 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
     return getNextRedeclaration();
   }
 
+  VarDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
+
   VarDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
   }
@@ -2163,6 +2171,10 @@ class FunctionDecl : public DeclaratorDecl,
     return getNextRedeclaration();
   }
 
+  FunctionDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
+
   FunctionDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
   }
@@ -3586,6 +3598,10 @@ class TypedefNameDecl : public TypeDecl, public Redeclarable<TypedefNameDecl> {
     return getNextRedeclaration();
   }
 
+  TypedefNameDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
+
   TypedefNameDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
   }
@@ -3755,6 +3771,10 @@ class TagDecl : public TypeDecl,
     return getNextRedeclaration();
   }
 
+  TagDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
+
   TagDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
   }
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index 5519787d71f88..ea30d1dfc8f37 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -989,6 +989,10 @@ class alignas(8) Decl {
   /// Decl subclasses that can be redeclared should override this method so that
   /// Decl::redecl_iterator can iterate over them.
   virtual Decl *getNextRedeclarationImpl() { return this; }
+  /// Returns the next redeclaration without loading.
+  /// FIXME: We may be able to erase such unneccesary virtual function call by
+  /// introduce CRTP.
+  virtual Decl *getNextRedeclarationNoUpdateImpl() { return this; }
 
   /// Implementation of getPreviousDecl(), to be overridden by any
   /// subclass that has a redeclaration chain.
@@ -1000,7 +1004,8 @@ class alignas(8) Decl {
 
 public:
   /// Iterates through all the redeclarations of the same decl.
-  class redecl_iterator {
+  template <bool Update = true>
+  class redecl_iterator_impl {
     /// Current - The current declaration.
     Decl *Current = nullptr;
     Decl *Starter;
@@ -1012,36 +1017,37 @@ class alignas(8) Decl {
     using iterator_category = std::forward_iterator_tag;
     using difference_type = std::ptrdiff_t;
 
-    redecl_iterator() = default;
-    explicit redecl_iterator(Decl *C) : Current(C), Starter(C) {}
+    redecl_iterator_impl() = default;
+    explicit redecl_iterator_impl(Decl *C) : Current(C), Starter(C) {}
 
     reference operator*() const { return Current; }
     value_type operator->() const { return Current; }
 
-    redecl_iterator& operator++() {
+    redecl_iterator_impl& operator++() {
       assert(Current && "Advancing while iterator has reached end");
       // Get either previous decl or latest decl.
-      Decl *Next = Current->getNextRedeclarationImpl();
+      Decl *Next = Update ? Current->getNextRedeclarationImpl() : Current->getNextRedeclarationNoUpdateImpl(); 
       assert(Next && "Should return next redeclaration or itself, never null!");
       Current = (Next != Starter) ? Next : nullptr;
       return *this;
     }
 
-    redecl_iterator operator++(int) {
+    redecl_iterator_impl operator++(int) {
       redecl_iterator tmp(*this);
       ++(*this);
       return tmp;
     }
 
-    friend bool operator==(redecl_iterator x, redecl_iterator y) {
+    friend bool operator==(redecl_iterator_impl x, redecl_iterator_impl y) {
       return x.Current == y.Current;
     }
 
-    friend bool operator!=(redecl_iterator x, redecl_iterator y) {
+    friend bool operator!=(redecl_iterator_impl x, redecl_iterator_impl y) {
       return x.Current != y.Current;
     }
   };
 
+  using redecl_iterator = redecl_iterator_impl</*update=*/true>;
   using redecl_range = llvm::iterator_range<redecl_iterator>;
 
   /// Returns an iterator range for all the redeclarations of the same
@@ -1056,6 +1062,19 @@ class alignas(8) Decl {
 
   redecl_iterator redecls_end() const { return redecl_iterator(); }
 
+  using noload_redecl_iterator = redecl_iterator_impl</*update=*/false>;
+  using noload_redecl_range = llvm::iterator_range<noload_redecl_iterator>;
+
+  noload_redecl_range noload_redecls() const {
+    return noload_redecl_range(noload_redecls_begin(), noload_redecls_end());
+  }
+
+  noload_redecl_iterator noload_redecls_begin() const {
+    return noload_redecl_iterator(const_cast<Decl *>(this));
+  }
+
+  noload_redecl_iterator noload_redecls_end() const { return noload_redecl_iterator(); }
+
   /// Retrieve the previous declaration that declares the same entity
   /// as this declaration, or NULL if there is no previous declaration.
   Decl *getPreviousDecl() { return getPreviousDeclImpl(); }
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index dfa3befb27dd0..4238885cc4678 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -3229,6 +3229,7 @@ class NamespaceAliasDecl : public NamespaceBaseDecl,
   using redeclarable_base = Redeclarable<NamespaceAliasDecl>;
 
   NamespaceAliasDecl *getNextRedeclarationImpl() override;
+  NamespaceAliasDecl *getNextRedeclarationNoUpdateImpl() override;
   NamespaceAliasDecl *getPreviousDeclImpl() override;
   NamespaceAliasDecl *getMostRecentDeclImpl() override;
 
@@ -3414,6 +3415,10 @@ class UsingShadowDecl : public NamedDecl, public Redeclarable<UsingShadowDecl> {
     return getNextRedeclaration();
   }
 
+  UsingShadowDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
+
   UsingShadowDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
   }
diff --git a/clang/include/clang/AST/DeclObjC.h b/clang/include/clang/AST/DeclObjC.h
index 2541edba83855..dfdefc6f90316 100644
--- a/clang/include/clang/AST/DeclObjC.h
+++ b/clang/include/clang/AST/DeclObjC.h
@@ -221,6 +221,7 @@ class ObjCMethodDecl : public NamedDecl, public DeclContext {
   /// An interface declaration will return its definition.
   /// Otherwise it will return itself.
   ObjCMethodDecl *getNextRedeclarationImpl() override;
+  ObjCMethodDecl *getNextRedeclarationNoUpdateImpl() override;
 
 public:
   friend class ASTDeclReader;
@@ -1265,6 +1266,9 @@ class ObjCInterfaceDecl : public ObjCContainerDecl
   ObjCInterfaceDecl *getNextRedeclarationImpl() override {
     return getNextRedeclaration();
   }
+  ObjCInterfaceDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
 
   ObjCInterfaceDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
@@ -2122,6 +2126,9 @@ class ObjCProtocolDecl : public ObjCContainerDecl,
   ObjCProtocolDecl *getNextRedeclarationImpl() override {
     return getNextRedeclaration();
   }
+  ObjCProtocolDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
 
   ObjCProtocolDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
diff --git a/clang/include/clang/AST/DeclTemplate.h b/clang/include/clang/AST/DeclTemplate.h
index a4a1bb9c13c79..f836b32314eff 100644
--- a/clang/include/clang/AST/DeclTemplate.h
+++ b/clang/include/clang/AST/DeclTemplate.h
@@ -718,6 +718,9 @@ class RedeclarableTemplateDecl : public TemplateDecl,
   RedeclarableTemplateDecl *getNextRedeclarationImpl() override {
     return getNextRedeclaration();
   }
+  RedeclarableTemplateDecl *getNextRedeclarationNoUpdateImpl() override {
+    return getNextRedeclarationNoUpdate();
+  }
 
   RedeclarableTemplateDecl *getPreviousDeclImpl() override {
     return getPreviousDecl();
diff --git a/clang/include/clang/AST/Redeclarable.h b/clang/include/clang/AST/Redeclarable.h
index 68516c66aaf65..2003d624e07db 100644
--- a/clang/include/clang/AST/Redeclarable.h
+++ b/clang/include/clang/AST/Redeclarable.h
@@ -117,6 +117,7 @@ class Redeclarable {
              isa<UninitializedLatest>(cast<NotKnownLatest>(Link));
     }
 
+    template <bool Update = true>
     decl_type *getPrevious(const decl_type *D) const {
       if (NotKnownLatest NKL = dyn_cast<NotKnownLatest>(Link)) {
         if (auto *Prev = dyn_cast<Previous>(NKL))
@@ -128,7 +129,8 @@ class Redeclarable {
                            const_cast<decl_type *>(D));
       }
 
-      return static_cast<decl_type *>(cast<KnownLatest>(Link).get(D));
+      return Update ? static_cast<decl_type *>(cast<KnownLatest>(Link).get(D))
+        : static_cast<decl_type *>(cast<KnownLatest>(Link).getNotUpdated());
     }
 
     void setPrevious(decl_type *D) {
@@ -183,7 +185,11 @@ class Redeclarable {
   decl_type *First;
 
   decl_type *getNextRedeclaration() const {
-    return RedeclLink.getPrevious(static_cast<const decl_type *>(this));
+    return RedeclLink.template getPrevious</*update=*/true>(static_cast<const decl_type *>(this));
+  }
+
+  decl_type *getNextRedeclarationNoUpdate() const {
+    return RedeclLink.template getPrevious</*update=*/false>(static_cast<const decl_type *>(this));
   }
 
 public:
diff --git a/clang/include/clang/Serialization/ASTReader.h b/clang/include/clang/Serialization/ASTReader.h
index d276f0d21b958..1ac4b2566b54c 100644
--- a/clang/include/clang/Serialization/ASTReader.h
+++ b/clang/include/clang/Serialization/ASTReader.h
@@ -2078,6 +2078,8 @@ class ASTReader
     return static_cast<unsigned>(DeclsLoaded.size());
   }
 
+  unsigned getNumDeclsLoaded() const;
+
   /// Returns the number of submodules known.
   unsigned getTotalNumSubmodules() const {
     return static_cast<unsigned>(SubmodulesLoaded.size());
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 24e4f189cbe4a..60b15244535a4 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -3276,6 +3276,10 @@ NamespaceDecl *NamespaceDecl::getNextRedeclarationImpl() {
   return getNextRedeclaration();
 }
 
+NamespaceDecl *NamespaceDecl::getNextRedeclarationNoUpdateImpl() {
+  return getNextRedeclarationNoUpdate();
+}
+
 NamespaceDecl *NamespaceDecl::getPreviousDeclImpl() {
   return getPreviousDecl();
 }
@@ -3290,6 +3294,10 @@ NamespaceAliasDecl *NamespaceAliasDecl::getNextRedeclarationImpl() {
   return getNextRedeclaration();
 }
 
+NamespaceAliasDecl *NamespaceAliasDecl::getNextRedeclarationNoUpdateImpl() {
+  return getNextRedeclarationNoUpdate();
+}
+
 NamespaceAliasDecl *NamespaceAliasDecl::getPreviousDeclImpl() {
   return getPreviousDecl();
 }
diff --git a/clang/lib/AST/DeclObjC.cpp b/clang/lib/AST/DeclObjC.cpp
index a66eb72981084..e07607b075bdd 100644
--- a/clang/lib/AST/DeclObjC.cpp
+++ b/clang/lib/AST/DeclObjC.cpp
@@ -1006,6 +1006,11 @@ ObjCMethodDecl *ObjCMethodDecl::getNextRedeclarationImpl() {
   return Redecl ? Redecl : this;
 }
 
+// FIXME: make sure ObjCMethodDecl::getNextRedeclarationImpl wont't load.
+ObjCMethodDecl *ObjCMethodDecl::getNextRedeclarationNoUpdateImpl() {
+  return getNextRedeclarationImpl();
+}
+
 ObjCMethodDecl *ObjCMethodDecl::getCanonicalDecl() {
   auto *CtxD = cast<Decl>(getDeclContext());
   const auto &Sel = getSelector();
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index aec61322fb8be..0d9edff273b6a 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8847,14 +8847,17 @@ void ASTReader::StartTranslationUnit(ASTConsumer *Consumer) {
     DeserializationListener->ReaderInitialized(this);
 }
 
+unsigned ASTReader::getNumDeclsLoaded() const {
+  return DeclsLoaded.size() -
+  llvm::count(DeclsLoaded.materialized(), (Decl *)nullptr);
+}
+
 void ASTReader::PrintStats() {
   std::fprintf(stderr, "*** AST File Statistics:\n");
 
   unsigned NumTypesLoaded =
       TypesLoaded.size() - llvm::count(TypesLoaded.materialized(), QualType());
-  unsigned NumDeclsLoaded =
-      DeclsLoaded.size() -
-      llvm::count(DeclsLoaded.materialized(), (Decl *)nullptr);
+  unsigned NumDeclsLoaded = getNumDeclsLoaded();
   unsigned NumIdentifiersLoaded =
       IdentifiersLoaded.size() -
       llvm::count(IdentifiersLoaded, (IdentifierInfo *)nullptr);
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 882d54f31280a..6e180b48e9e95 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2107,8 +2107,18 @@ void ASTDeclMerger::MergeDefinitionData(
     auto *Def = DD.Definition;
     DD = std::move(MergeDD);
     DD.Definition = Def;
-    for (auto *D : Def->redecls())
-      cast<CXXRecordDecl>(D)->DefinitionData = &DD;
+
+#ifndef NDEBUG
+    unsigned OldLoadedSize = Reader.getNumDeclsLoaded();
+#endif
+
+    for (auto *RD : Def->noload_redecls())
+      cast<CXXRecordDecl>(RD)->DefinitionData = &DD;
+
+#ifndef NDEBUG
+    assert(Reader.getNumDeclsLoaded() == OldLoadedSize && "We shouldn't load new decls during merge definition data for class");
+#endif
+
     return;
   }
 

@github-actions
Copy link

github-actions bot commented Dec 5, 2025

⚠️ C/C++ code formatter, clang-format found issues in your code. ⚠️

You can test this locally with the following command:
git-clang-format --diff origin/main HEAD --extensions h,cpp -- clang/include/clang/AST/Decl.h clang/include/clang/AST/DeclBase.h clang/include/clang/AST/DeclCXX.h clang/include/clang/AST/DeclObjC.h clang/include/clang/AST/DeclTemplate.h clang/include/clang/AST/Redeclarable.h clang/include/clang/Serialization/ASTReader.h clang/lib/AST/DeclCXX.cpp clang/lib/AST/DeclObjC.cpp clang/lib/Serialization/ASTReader.cpp clang/lib/Serialization/ASTReaderDecl.cpp --diff_from_common_commit

⚠️
The reproduction instructions above might return results for more than one PR
in a stack if you are using a stacked PR workflow. You can limit the results by
changing origin/main to the base branch/commit you want to compare against.
⚠️

View the diff from clang-format here.
diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h
index ea30d1dfc..1c471b91c 100644
--- a/clang/include/clang/AST/DeclBase.h
+++ b/clang/include/clang/AST/DeclBase.h
@@ -1004,8 +1004,7 @@ protected:
 
 public:
   /// Iterates through all the redeclarations of the same decl.
-  template <bool Update = true>
-  class redecl_iterator_impl {
+  template <bool Update = true> class redecl_iterator_impl {
     /// Current - The current declaration.
     Decl *Current = nullptr;
     Decl *Starter;
@@ -1023,10 +1022,11 @@ public:
     reference operator*() const { return Current; }
     value_type operator->() const { return Current; }
 
-    redecl_iterator_impl& operator++() {
+    redecl_iterator_impl &operator++() {
       assert(Current && "Advancing while iterator has reached end");
       // Get either previous decl or latest decl.
-      Decl *Next = Update ? Current->getNextRedeclarationImpl() : Current->getNextRedeclarationNoUpdateImpl(); 
+      Decl *Next = Update ? Current->getNextRedeclarationImpl()
+                          : Current->getNextRedeclarationNoUpdateImpl();
       assert(Next && "Should return next redeclaration or itself, never null!");
       Current = (Next != Starter) ? Next : nullptr;
       return *this;
@@ -1073,7 +1073,9 @@ public:
     return noload_redecl_iterator(const_cast<Decl *>(this));
   }
 
-  noload_redecl_iterator noload_redecls_end() const { return noload_redecl_iterator(); }
+  noload_redecl_iterator noload_redecls_end() const {
+    return noload_redecl_iterator();
+  }
 
   /// Retrieve the previous declaration that declares the same entity
   /// as this declaration, or NULL if there is no previous declaration.
diff --git a/clang/include/clang/AST/Redeclarable.h b/clang/include/clang/AST/Redeclarable.h
index 2003d624e..bde0e162a 100644
--- a/clang/include/clang/AST/Redeclarable.h
+++ b/clang/include/clang/AST/Redeclarable.h
@@ -130,7 +130,8 @@ protected:
       }
 
       return Update ? static_cast<decl_type *>(cast<KnownLatest>(Link).get(D))
-        : static_cast<decl_type *>(cast<KnownLatest>(Link).getNotUpdated());
+                    : static_cast<decl_type *>(
+                          cast<KnownLatest>(Link).getNotUpdated());
     }
 
     void setPrevious(decl_type *D) {
@@ -185,11 +186,13 @@ protected:
   decl_type *First;
 
   decl_type *getNextRedeclaration() const {
-    return RedeclLink.template getPrevious</*update=*/true>(static_cast<const decl_type *>(this));
+    return RedeclLink.template getPrevious</*update=*/true>(
+        static_cast<const decl_type *>(this));
   }
 
   decl_type *getNextRedeclarationNoUpdate() const {
-    return RedeclLink.template getPrevious</*update=*/false>(static_cast<const decl_type *>(this));
+    return RedeclLink.template getPrevious</*update=*/false>(
+        static_cast<const decl_type *>(this));
   }
 
 public:
diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp
index 0d9edff27..d18113fec 100644
--- a/clang/lib/Serialization/ASTReader.cpp
+++ b/clang/lib/Serialization/ASTReader.cpp
@@ -8849,7 +8849,7 @@ void ASTReader::StartTranslationUnit(ASTConsumer *Consumer) {
 
 unsigned ASTReader::getNumDeclsLoaded() const {
   return DeclsLoaded.size() -
-  llvm::count(DeclsLoaded.materialized(), (Decl *)nullptr);
+         llvm::count(DeclsLoaded.materialized(), (Decl *)nullptr);
 }
 
 void ASTReader::PrintStats() {
diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp
index 6e180b48e..1532e48d8 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -2116,7 +2116,9 @@ void ASTDeclMerger::MergeDefinitionData(
       cast<CXXRecordDecl>(RD)->DefinitionData = &DD;
 
 #ifndef NDEBUG
-    assert(Reader.getNumDeclsLoaded() == OldLoadedSize && "We shouldn't load new decls during merge definition data for class");
+    assert(
+        Reader.getNumDeclsLoaded() == OldLoadedSize &&
+        "We shouldn't load new decls during merge definition data for class");
 #endif
 
     return;

@alexfh
Copy link
Contributor

alexfh commented Dec 15, 2025

I ran a build of all of our code with the assertions-enabled clang. So far I'm seeing compilation timeouts on a large number of compilations. I'm trying to figure out whether this is an infinite loop or a superlinear algorithm.

@alexfh
Copy link
Contributor

alexfh commented Dec 15, 2025

I ran a build of all of our code with the assertions-enabled clang. So far I'm seeing compilation timeouts on a large number of compilations. I'm trying to figure out whether this is an infinite loop or a superlinear algorithm.

The stack trace after a few minutes of execution looks like this:

  * frame #0: 0x000055555ae13b56 clang-checked`clang::ASTDeclMerger::MergeDefinitionData(clang::CXXRecordDecl*, clang::CXXRecordDecl::DefinitionData&&) + 694
    frame #1: 0x000055555ae1734e clang-checked`clang::ASTDeclReader::VisitClassTemplateSpecializationDeclImpl(clang::ClassTemplateSpecializationDecl*) + 1006
    frame #2: 0x000055555ae079f3 clang-checked`clang::declvisitor::Base<std::__u::add_pointer, clang::ASTDeclReader, void>::Visit(clang::Decl*) + 1235
    frame #3: 0x000055555ae071d5 clang-checked`clang::ASTDeclReader::Visit(clang::Decl*) + 21
    frame #4: 0x000055555c29eb8f clang-checked`clang::StackExhaustionHandler::runWithSufficientStackSpace(clang::SourceLocation, llvm::function_ref<void ()>) + 47
    frame #5: 0x000055555ae2e911 clang-checked`clang::ASTReader::ReadDeclRecord(clang::GlobalDeclID) + 3409
    frame #6: 0x000055555adaa595 clang-checked`clang::ASTReader::GetDecl(clang::GlobalDeclID) + 213
    frame #7: 0x000055555ae37759 clang-checked`clang::DeclContext* clang::ASTReader::ReadDeclAs<clang::DeclContext>(clang::serialization::ModuleFile&, llvm::SmallVectorImpl<unsigned long> const&, unsigned int&) + 25
    frame #8: 0x000055555ae080ab clang-checked`clang::ASTDeclReader::VisitDecl(clang::Decl*) + 1099
    frame #9: 0x000055555ae09ef2 clang-checked`clang::ASTDeclReader::VisitValueDecl(clang::ValueDecl*) + 18
    frame #10: 0x000055555ae0a215 clang-checked`clang::ASTDeclReader::VisitDeclaratorDecl(clang::DeclaratorDecl*) + 21
    frame #11: 0x000055555ae0af0a clang-checked`clang::ASTDeclReader::VisitFunctionDecl(clang::FunctionDecl*) + 2666
    frame #12: 0x000055555ae153d6 clang-checked`clang::ASTDeclReader::VisitCXXMethodDecl(clang::CXXMethodDecl*) + 22
    frame #13: 0x000055555ae071d5 clang-checked`clang::ASTDeclReader::Visit(clang::Decl*) + 21
    frame #14: 0x000055555c29eb8f clang-checked`clang::StackExhaustionHandler::runWithSufficientStackSpace(clang::SourceLocation, llvm::function_ref<void ()>) + 47
    frame #15: 0x000055555ae2e911 clang-checked`clang::ASTReader::ReadDeclRecord(clang::GlobalDeclID) + 3409
    frame #16: 0x000055555adaa595 clang-checked`clang::ASTReader::GetDecl(clang::GlobalDeclID) + 213
    frame #17: 0x000055555ae4b098 clang-checked`clang::ASTStmtReader::VisitMemberExpr(clang::MemberExpr*) + 152
    frame #18: 0x000055555ae5b3e1 clang-checked`clang::ASTReader::ReadStmtFromStream(clang::serialization::ModuleFile&) + 14081
    frame #19: 0x000055555adb585d clang-checked`clang::ASTReader::GetExternalDeclStmt(unsigned long) + 413
    frame #20: 0x000055555bdf8959 clang-checked`clang::FunctionDecl::getBody(clang::FunctionDecl const*&) const + 217
    frame #21: 0x000055555be05591 clang-checked`clang::FunctionDecl::getBody() const + 17
    frame #22: 0x000055555a12493d clang-checked`clang::CodeGen::CodeGenFunction::GenerateCode(clang::GlobalDecl, llvm::Function*, clang::CodeGen::CGFunctionInfo const&) + 797
    frame #23: 0x000055555a15084c clang-checked`clang::CodeGen::CodeGenModule::EmitGlobalFunctionDefinition(clang::GlobalDecl, llvm::GlobalValue*) + 396
    frame #24: 0x000055555a147ea2 clang-checked`clang::CodeGen::CodeGenModule::EmitGlobalDefinition(clang::GlobalDecl, llvm::GlobalValue*) + 514
    frame #25: 0x000055555a138856 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 822
    frame #26: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #27: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #28: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #29: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #30: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #31: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #32: 0x000055555a138872 clang-checked`clang::CodeGen::CodeGenModule::EmitDeferred() + 850
    frame #33: 0x000055555a1351ee clang-checked`clang::CodeGen::CodeGenModule::Release() + 78
    frame #34: 0x000055555a27acee clang-checked`(anonymous namespace)::CodeGeneratorImpl::HandleTranslationUnit(clang::ASTContext&) + 46
    frame #35: 0x0000555559dd3b9a clang-checked`clang::BackendConsumer::HandleTranslationUnit(clang::ASTContext&) + 154
    frame #36: 0x000055555ac77788 clang-checked`clang::ParseAST(clang::Sema&, bool, bool) + 616
    frame #37: 0x000055555a9ad5aa clang-checked`clang::FrontendAction::Execute() + 42
    frame #38: 0x000055555a92145d clang-checked`clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) + 1117
    frame #39: 0x0000555559dd316e clang-checked`clang::ExecuteCompilerInvocation(clang::CompilerInstance*) + 526
    frame #40: 0x0000555559dc65ea clang-checked`cc1_main(llvm::ArrayRef<char const*>, char const*, void*) + 1834
    frame #41: 0x0000555559dc3619 clang-checked`ExecuteCC1Tool(llvm::SmallVectorImpl<char const*>&, llvm::ToolContext const&, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>) + 377
    frame #42: 0x0000555559dc5dcc clang-checked`int llvm::function_ref<int (llvm::SmallVectorImpl<char const*>&)>::callback_fn<clang_main(int, char**, llvm::ToolContext const&)::$_0>(long, llvm::SmallVectorImpl<char const*>&) + 44
    frame #43: 0x000055555aae291e clang-checked`void llvm::function_ref<void ()>::callback_fn<clang::driver::CC1Command::Execute(llvm::ArrayRef<std::__u::optional<llvm::StringRef>>, std::__u::basic_string<char, std::__u::char_traits<char>, std::__u::allocator<char>>*, bool*) const::$_0>(long) + 30
    frame #44: 0x000055555fc831dc clang-checked`llvm::CrashRecoveryContext::RunSafely(llvm::function_ref<void ()>) + 124
    frame #45: 0x000055555aae1e04 clang-checked`clang::driver::CC1Command::Execute(llvm::ArrayRef<std::__u::optional<llvm::StringRef>>, std::__u::basic_string<char, std::__u::char_traits<char>, std::__u::allocator<char>>*, bool*) const + 260
    frame #46: 0x000055555aa9f453 clang-checked`clang::driver::Compilation::ExecuteCommand(clang::driver::Command const&, clang::driver::Command const*&, bool) const + 595
    frame #47: 0x000055555aa9f6df clang-checked`clang::driver::Compilation::ExecuteJobs(clang::driver::JobList const&, llvm::SmallVectorImpl<std::__u::pair<int, clang::driver::Command const*>>&, bool) const + 127
    frame #48: 0x000055555aab9dc0 clang-checked`clang::driver::Driver::ExecuteCompilation(clang::driver::Compilation&, llvm::SmallVectorImpl<std::__u::pair<int, clang::driver::Command const*>>&) + 352
    frame #49: 0x0000555559dc2c1e clang-checked`clang_main(int, char**, llvm::ToolContext const&) + 6206

I can look a bit closer tomorrow.

@alexfh
Copy link
Contributor

alexfh commented Dec 15, 2025

And as usual with modules: producing a shareable test case is likely to take long, since the initial size of inputs is on the order of tens of megabytes of C++ source code.

@ChuanqiXu9
Copy link
Member Author

@alexfh if it is the case, it implies that #170090 (comment) can solve the problem for you (I remember the conclusion is the reproducer will fail on trunk before) because it loads redeclarations I think you don't need to prepare reproducer for this.

@alexfh
Copy link
Contributor

alexfh commented Dec 16, 2025

The infinite loop seems to be happening in this code:

* thread #1, name = 'clang-debug', stop reason = step over
    frame #0: 0x0000555567ade9c8 clang-debug`clang::ASTDeclMerger::MergeDefinitionData(this=0x00007ffffffe0320, D=0x000030c271c7e908, MergeDD=0x000030c271c801f0) at ASTReaderDecl.cpp:2115:19
   2112     unsigned OldLoadedSize = Reader.getNumDeclsLoaded();
   2113 #endif
   2114
-> 2115     for (auto *RD : Def->noload_redecls())
   2116       cast<CXXRecordDecl>(RD)->DefinitionData = &DD;

@ChuanqiXu9
Copy link
Member Author

Oh, thank you. Maybe the patch has problems already, I'll take a look later.

@alexfh
Copy link
Contributor

alexfh commented Dec 16, 2025

I debugged the code a bit more and found out that the infinite loop is due to getNextRedeclarationNoUpdate()returning the same value as this

@ChuanqiXu9
Copy link
Member Author

I debugged the code a bit more and found out that the infinite loop is due to getNextRedeclarationNoUpdate()returning the same value as this

Thanks. It's the default implementation. But the iterator should stop if it see "this".

@alexfh
Copy link
Contributor

alexfh commented Dec 16, 2025

I debugged the code a bit more and found out that the infinite loop is due to getNextRedeclarationNoUpdate()returning the same value as this

Thanks. It's the default implementation. But the iterator should stop if it see "this".

Maybe this will help understanding what's happening:

* thread #1, name = 'clang-debug', stop reason = step over
    frame #0: 0x0000555567b31cb5 clang-debug`clang::Decl::redecl_iterator_impl<false>::operator++(this=0x00007ffffffdeb60) at DeclBase.h:1030:14
   1027       assert(Current && "Advancing while iterator has reached end");
   1028       // Get either previous decl or latest decl.
   1029       Decl *Next = Update ? Current->getNextRedeclarationImpl() : Current->getNextRedeclarationNoUpdateImpl();
-> 1030       assert(Next && "Should return next redeclaration or itself, never null!");
   1031       Current = (Next != Starter) ? Next : nullptr;
   1032       return *this;
   1033     }
(lldb) p Next
(clang::ClassTemplateSpecializationDecl *) 0x000030c271c7e908
(lldb) p Starter
(clang::ClassTemplateSpecializationDecl *) 0x000030c271c7f1d8
(lldb) p Current
(clang::ClassTemplateSpecializationDecl *) 0x000030c271c7e908
(lldb) p Update
(void (*)(const uint64_t, const size_t, tcmalloc::tcmalloc_internal::Residency::Info &)) 0x0000555564a37830 (clang-debug`tcmalloc::tcmalloc_internal::(anonymous namespace)::Update(unsigned long, unsigned long, tcmalloc::tcmalloc_internal::Residency::Info&) at residency.cc:53)
(lldb) p Starter->dump()
ClassTemplateSpecializationDecl 0x30c271c7f1d8 prev 0x30c271c7e908 <buffer.h:57:1, line:237:1> line:58:8 imported in vector struct buffer definition
|-DefinitionData pass_in_registers literal has_user_declared_ctor has_constexpr_non_copy_move_ctor can_const_default_init
| |-DefaultConstructor exists non_trivial user_provided constexpr defaulted_is_constexpr
| |-CopyConstructor trivial user_declared has_const_param needs_overload_resolution implicit_has_const_param
| |-MoveConstructor exists non_trivial user_declared
| |-CopyAssignment trivial has_const_param user_declared needs_overload_resolution implicit_has_const_param
| |-MoveAssignment exists non_trivial user_declared
| `-Destructor non_trivial user_declared constexpr
|-TemplateArgument type 'std::string'
| `-RecordType 0x30c27932eab0 'std::string' imported canonical
|   `-ClassTemplateSpecialization 0x30c27932e9b0 'basic_string'
`-TemplateArgument type 'std::allocator<std::string> &'
  `-LValueReferenceType 0x30c272c026c0 'std::allocator<std::string> &' imported
    `-RecordType 0x30c277a8b340 'std::allocator<std::string>' imported canonical
      `-ClassTemplateSpecialization 0x30c277a8b250 'allocator'
(lldb) p Current->dump()
ClassTemplateSpecializationDecl 0x30c271c7e908 <buffer.h:57:1, line:237:1> line:58:8 imported in vector struct buffer
|-TemplateArgument type 'std::string'
| `-RecordType 0x30c27932eab0 'std::string' imported canonical
|   `-ClassTemplateSpecialization 0x30c27932e9b0 'basic_string'
`-TemplateArgument type 'std::allocator<std::string> &'
  `-LValueReferenceType 0x30c272c026c0 'std::allocator<std::string> &' imported
    `-RecordType 0x30c277a8b340 'std::allocator<std::string>' imported canonical
      `-ClassTemplateSpecialization 0x30c277a8b250 'allocator'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:modules C++20 modules and Clang Header Modules clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants