https://github.com/zygoloid created https://github.com/llvm/llvm-project/pull/210178
Add an accessor to walk the fields and indirect fields of a class without deserializing everything, and use it instead of walking over all of `decls()` in a couple of places within the implicit definition of a special member function. We already have machinery to only import those parts of the class without importing everything else, but we weren't using it here. This allows us to use classes with implicit special members from an imported PCH or module without importing all the declarations in the class. >From 2dea40bfd6c194b21d64402e18860c0941400a92 Mon Sep 17 00:00:00 2001 From: Richard Smith <[email protected]> Date: Thu, 16 Jul 2026 20:46:33 +0000 Subject: [PATCH] Don't eagerly load *all* declarations in a class when defining a special member funciton. --- clang/include/clang/AST/Decl.h | 22 +++++++++++++++++ clang/lib/AST/Decl.cpp | 24 +++++++++++++++---- clang/lib/Sema/SemaDeclCXX.cpp | 4 ++-- .../test/OpenMP/declare_target_ast_print.cpp | 14 ++++++----- clang/test/PCH/Inputs/cxx-method.h | 7 ++++++ clang/test/PCH/cxx-method.cpp | 18 ++++++++++++-- 6 files changed, 75 insertions(+), 14 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 0a6f256afa2cc..64d39caec2932 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -410,6 +410,10 @@ class NamedDecl : public Decl { /// a C++ class. bool isCXXInstanceMember() const; + // Determine whether this declaration is a direct or indirect field. For use + // with filtered_decl_iterator. + bool isDirectOrIndirectFieldDecl() const; + /// Determine if the declaration obeys the reserved identifier rules of the /// given language. ReservedIdentifierStatus isReserved(const LangOptions &LangOpts) const; @@ -4603,6 +4607,24 @@ class RecordDecl : public TagDecl { return noload_field_begin() == noload_field_end(); } + // Iterator access to direct or indirect field members. The field iterator + // only visits the non-static data members of this class, ignoring any static + // data members, functions, constructors, destructors, etc. + using direct_or_indirect_field_iterator = + filtered_decl_iterator<NamedDecl, + &NamedDecl::isDirectOrIndirectFieldDecl>; + using direct_or_indirect_field_range = + llvm::iterator_range<direct_or_indirect_field_iterator>; + + direct_or_indirect_field_range direct_or_indirect_fields() const { + return direct_or_indirect_field_range(direct_or_indirect_field_begin(), + direct_or_indirect_field_end()); + } + direct_or_indirect_field_iterator direct_or_indirect_field_begin() const; + direct_or_indirect_field_iterator direct_or_indirect_field_end() const { + return direct_or_indirect_field_iterator(decl_iterator()); + } + /// Note that the definition of this type is now complete. virtual void completeDefinition(); diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 4eaef0d87f3e5..002c0085e58a2 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -1128,6 +1128,14 @@ bool NamedDecl::isPlaceholderVar(const LangOptions &LangOpts) const { return false; } +static bool isDirectOrIndirectFieldDeclKind(Decl::Kind K) { + return FieldDecl::classofKind(K) || IndirectFieldDecl::classofKind(K); +} + +bool NamedDecl::isDirectOrIndirectFieldDecl() const { + return isDirectOrIndirectFieldDeclKind(getKind()); +} + ReservedIdentifierStatus NamedDecl::isReserved(const LangOptions &LangOpts) const { const IdentifierInfo *II = getIdentifier(); @@ -5280,6 +5288,15 @@ RecordDecl::field_iterator RecordDecl::field_begin() const { return field_iterator(decl_iterator(FirstDecl)); } +RecordDecl::direct_or_indirect_field_iterator +RecordDecl::direct_or_indirect_field_begin() const { + if (hasExternalLexicalStorage() && !hasLoadedFieldsFromExternalStorage()) + LoadFieldsFromExternalStorage(); + if (RecordDecl *D = getDefinition(); D && D != this) + return D->direct_or_indirect_field_begin(); + return direct_or_indirect_field_iterator(decl_iterator(FirstDecl)); +} + RecordDecl::field_iterator RecordDecl::noload_field_begin() const { return field_iterator(decl_iterator(getDefinitionOrSelf()->FirstDecl)); } @@ -5333,14 +5350,13 @@ void RecordDecl::LoadFieldsFromExternalStorage() const { SmallVector<Decl*, 64> Decls; setHasLoadedFieldsFromExternalStorage(true); - Source->FindExternalLexicalDecls(this, [](Decl::Kind K) { - return FieldDecl::classofKind(K) || IndirectFieldDecl::classofKind(K); - }, Decls); + Source->FindExternalLexicalDecls(this, isDirectOrIndirectFieldDeclKind, + Decls); #ifndef NDEBUG // Check that all decls we got were FieldDecls. for (unsigned i=0, e=Decls.size(); i != e; ++i) - assert(isa<FieldDecl>(Decls[i]) || isa<IndirectFieldDecl>(Decls[i])); + assert(cast<NamedDecl>(Decls[i])->isDirectOrIndirectFieldDecl()); #endif if (Decls.empty()) diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 37bb69da90b6c..738f667a74ed2 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -4127,7 +4127,7 @@ namespace { llvm::SmallPtrSet<ValueDecl*, 4> UninitializedFields; // At the beginning, all fields are uninitialized. - for (auto *I : RD->decls()) { + for (auto *I : RD->direct_or_indirect_fields()) { if (auto *FD = dyn_cast<FieldDecl>(I)) { UninitializedFields.insert(FD); } else if (auto *IFD = dyn_cast<IndirectFieldDecl>(I)) { @@ -5586,7 +5586,7 @@ bool Sema::SetCtorInitializers(CXXConstructorDecl *Constructor, bool AnyErrors, } // Fields. - for (auto *Mem : ClassDecl->decls()) { + for (auto *Mem : ClassDecl->direct_or_indirect_fields()) { if (auto *F = dyn_cast<FieldDecl>(Mem)) { // C++ [class.bit]p2: // A declaration for a bit-field that omits the identifier declares an diff --git a/clang/test/OpenMP/declare_target_ast_print.cpp b/clang/test/OpenMP/declare_target_ast_print.cpp index 7b63c15dd455e..2e5b92b4785be 100644 --- a/clang/test/OpenMP/declare_target_ast_print.cpp +++ b/clang/test/OpenMP/declare_target_ast_print.cpp @@ -215,19 +215,17 @@ struct C { // CHECK-NEXT: #pragma omp end declare target // CHECK: template<> struct C<int> - T t; -// CHECK-NEXT: int t; static T ts; -// CHECK-NEXT: #pragma omp declare target +// CHECK: #pragma omp declare target // CHECK-NEXT: static int ts; -// CHECK: #pragma omp end declare target +// CHECK-NEXT: #pragma omp end declare target C(T t) : t(t) { } // CHECK: #pragma omp declare target // CHECK-NEXT: C(int t) : t(t) { // CHECK-NEXT: } -// CHECK: #pragma omp end declare target +// CHECK-NEXT: #pragma omp end declare target T foo() { return t; @@ -236,7 +234,11 @@ struct C { // CHECK-NEXT: int foo() { // CHECK-NEXT: return this->t; // CHECK-NEXT: } -// CHECK: #pragma omp end declare target +// CHECK-NEXT: #pragma omp end declare target + +// Not inside a #pragma omp declare target. +// CHECK-NEXT: int t; + T t; }; template<class T> diff --git a/clang/test/PCH/Inputs/cxx-method.h b/clang/test/PCH/Inputs/cxx-method.h index d5d56fed0585e..e6d271c00f888 100644 --- a/clang/test/PCH/Inputs/cxx-method.h +++ b/clang/test/PCH/Inputs/cxx-method.h @@ -3,7 +3,14 @@ struct S { S(); S(const S&); + S &operator=(const S&); + + void doNotDeserialize(); operator const char*(); operator char*(); }; + +struct Trivial { + void doNotDeserialize(); +}; \ No newline at end of file diff --git a/clang/test/PCH/cxx-method.cpp b/clang/test/PCH/cxx-method.cpp index c24ad9297588a..fff09b2bc4829 100644 --- a/clang/test/PCH/cxx-method.cpp +++ b/clang/test/PCH/cxx-method.cpp @@ -1,8 +1,22 @@ // RUN: %clang_cc1 -x c++ -include %S/Inputs/cxx-method.h -verify %s // RUN: %clang_cc1 -x c++ -emit-pch %S/Inputs/cxx-method.h -o %t -// RUN: %clang_cc1 -include-pch %t -verify %s +// RUN: %clang_cc1 -include-pch %t -verify %s -error-on-deserialized-decl doNotDeserialize -ast-dump // expected-no-diagnostics +// Calling constructors should not cause doNotDeserialize to be deserialized. +S s; +S s2(s); + +// Implicitly defining special member functions should not cause +// doNotDeserialize to be deserialized. +Trivial t; +Trivial t2(t); + +void assign() { + s = s2; + t = t2; +} + void S::m(int x) { } S::operator char *() { return 0; } @@ -12,4 +26,4 @@ S::operator const char *() { return 0; } struct T : S {}; const T a = T(); -T b(a); +T b(a); \ No newline at end of file _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
