[clang] [DebugInfo] Ignore undefined constexpr constructors in constructor homing. (PR #221971)

via cfe-commits cfe-commits at lists.llvm.org
Tue Sep 8 05:07:02 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang-codegen

Author: Clayton Knittel (ClaytonKnittel)

<details>
<summary>Changes</summary>

This is a roll-forward of https://github.com/llvm/llvm-project/pull/218165, which inadvertantly caused missing debug info in https://github.com/llvm/llvm-project/issues/221560 due to an existing issue with standard-layout unions and layout-compatible aliasing (see [[class.mem]](https://timsong-cpp.github.io/cppwp/n3337/class.mem#<!-- -->19)).

Constexpr constructors without a visible definition in the TU are not callable in a constexpr context, which means it is not possible to construct a type in constexpr without emitting debug info for the constructor itself. Although undefined constexpr constructors are still callable, they are normal out of line functions, requiring a definition to link against.

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


6 Files Affected:

- (modified) clang/include/clang/AST/CXXRecordDeclDefinitionBits.def (+3) 
- (modified) clang/include/clang/AST/DeclCXX.h (+9) 
- (modified) clang/lib/AST/DeclCXX.cpp (+15-1) 
- (modified) clang/lib/CodeGen/CGDebugInfo.cpp (+26-5) 
- (modified) clang/test/DebugInfo/CXX/limited-ctor.cpp (+22-5) 
- (modified) clang/unittests/AST/DeclTest.cpp (+256) 


``````````diff
diff --git a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
index 97e61aaec7d51..a45a57ecfff20 100644
--- a/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
+++ b/clang/include/clang/AST/CXXRecordDeclDefinitionBits.def
@@ -64,6 +64,9 @@ FIELD(Abstract, 1, NO_MERGE)
 /// language rules (including DRs).
 FIELD(IsStandardLayout, 1, NO_MERGE)
 
+/// True when this class appears as a member of some standard-layout union.
+FIELD(IsStandardLayoutUnionMember, 1, MERGE_OR)
+
 /// True when this class was standard-layout under the C++11
 /// definition.
 ///
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index afe46fae1bceb..c46ab72cb1bc5 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1233,6 +1233,15 @@ class CXXRecordDecl : public RecordDecl {
   /// C++ [class]p7.
   bool isStandardLayout() const { return data().IsStandardLayout; }
 
+  /// Determine whether this class appears as a member of a standard-layout
+  /// union.
+  bool isStandardLayoutUnionMember() const {
+    return data().IsStandardLayoutUnionMember;
+  }
+  void setIsStandardLayoutUnionMember(bool V = true) {
+    data().IsStandardLayoutUnionMember = V;
+  }
+
   /// Determine whether this class was standard-layout per
   /// C++11 [class]p7, specifically using the C++11 rules without any DRs.
   bool isCXX11StandardLayout() const { return data().IsCXX11StandardLayout; }
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index f0da56542ae7e..476eb0bc2d5f3 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -76,7 +76,8 @@ void LazyASTUnresolvedSet::getFromExternalSource(ASTContext &C) const {
 CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
     : UserDeclaredConstructor(false), UserDeclaredSpecialMembers(0),
       Aggregate(true), PlainOldData(true), Empty(true), Polymorphic(false),
-      Abstract(false), IsStandardLayout(true), IsCXX11StandardLayout(true),
+      Abstract(false), IsStandardLayout(true),
+      IsStandardLayoutUnionMember(false), IsCXX11StandardLayout(true),
       HasBasesWithFields(false), HasBasesWithNonStaticDataMembers(false),
       HasPrivateFields(false), HasProtectedFields(false),
       HasPublicFields(false), HasMutableFields(false), HasVariantMembers(false),
@@ -2321,6 +2322,19 @@ void CXXRecordDecl::completeDefinition(CXXFinalOverriderMap *FinalOverriders) {
     data().IsStandardLayout = false;
     data().IsCXX11StandardLayout = false;
   }
+
+  if (isUnion() && isStandardLayout()) {
+    for (const FieldDecl *FD : fields()) {
+      if (const auto *RT =
+              FD->getType()->getBaseElementTypeUnsafe()->getAs<RecordType>()) {
+        if (auto *CRD =
+                dyn_cast<CXXRecordDecl>(RT->getDecl()->getDefinitionOrSelf())) {
+          if (CRD->hasDefinition() && CRD->isStandardLayout())
+            CRD->setIsStandardLayoutUnionMember(true);
+        }
+      }
+    }
+  }
 }
 
 bool CXXRecordDecl::mayBeAbstract() const {
diff --git a/clang/lib/CodeGen/CGDebugInfo.cpp b/clang/lib/CodeGen/CGDebugInfo.cpp
index 02864621d60a3..38bb67543ebc6 100644
--- a/clang/lib/CodeGen/CGDebugInfo.cpp
+++ b/clang/lib/CodeGen/CGDebugInfo.cpp
@@ -3239,11 +3239,28 @@ static bool canUseCtorHoming(const CXXRecordDecl *RD) {
   if (isClassOrMethodDLLImport(RD))
     return false;
 
-  if (RD->isLambda() || RD->isAggregate() ||
-      RD->hasTrivialDefaultConstructor() ||
-      RD->hasConstexprNonCopyMoveConstructor())
+  if (RD->isLambda() || RD->isAggregate() || RD->hasTrivialDefaultConstructor())
     return false;
 
+  // Skip this optimization if the class has an implicit constexpr default
+  // constructor, since those constructors can be invoked without emitting type
+  // information for the constructor.
+  if (RD->needsImplicitDefaultConstructor() &&
+      RD->defaultedDefaultConstructorIsConstexpr())
+    return false;
+
+  // Skip this optimization if this type is standard-layout and is a member of
+  // some standard-layout union in this translation unit. Per the C++ spec, "it
+  // is permitted to inspect the common initial part of any of" the "common
+  // initial sequence" of distinct types in a standard-layout union. This
+  // exception to strict aliasing enables producing a reference to a type
+  // without ever having constructed that type.
+  //
+  // See: https://timsong-cpp.github.io/cppwp/n3337/class.mem#19
+  if (RD->isStandardLayoutUnionMember())
+    return false;
+
+  bool HasNonDeletedCtor = false;
   for (const CXXConstructorDecl *Ctor : RD->ctors()) {
     if (Ctor->isCopyOrMoveConstructor())
       continue;
@@ -3254,11 +3271,15 @@ static bool canUseCtorHoming(const CXXRecordDecl *RD) {
       // copy/move constructor, which does not enable homing.
       if (CtorDef->isDelegatingConstructor())
         continue;
+      // Skip this optimization if we see a defined constexpr constructor, which
+      // can be invoked without emitting type info.
+      if (Ctor->isConstexpr() && !Ctor->isDeleted())
+        return false;
     }
     if (!Ctor->isDeleted())
-      return true;
+      HasNonDeletedCtor = true;
   }
-  return false;
+  return HasNonDeletedCtor;
 }
 
 static bool shouldOmitDefinition(llvm::codegenoptions::DebugInfoKind DebugKind,
diff --git a/clang/test/DebugInfo/CXX/limited-ctor.cpp b/clang/test/DebugInfo/CXX/limited-ctor.cpp
index e820c0703df4f..6394c01a467c6 100644
--- a/clang/test/DebugInfo/CXX/limited-ctor.cpp
+++ b/clang/test/DebugInfo/CXX/limited-ctor.cpp
@@ -27,10 +27,9 @@ struct E {
   constexpr E(){};
 } TestE;
 
-// Restored by this revert: a constexpr constructor that is only declared keeps
-// the class exempt from constructor homing. See Aliased below for a case where
-// narrowing the exemption to defined constructors homes the type nowhere.
-// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "DeclaredConstexpr"{{.*}}DIFlagTypePassByValue
+// Declared but not defined constexpr constructor should not emit full debug
+// info.
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "DeclaredConstexpr"{{.*}}flags: DIFlagFwdDecl
 struct DeclaredConstexpr {
   constexpr DeclaredConstexpr();
 } TestDeclaredConstexpr;
@@ -53,7 +52,7 @@ struct DeclaredConstexpr {
 template <class A, class B> struct Aliased {
   A first;
   B second;
-  constexpr Aliased(const A &a, const B &b) : first(a), second(b) {}
+  Aliased(const A &a, const B &b) : first(a), second(b) {}
 };
 union AliasedSlot {
   Aliased<const int, int> value;
@@ -66,6 +65,24 @@ int ReadAliasedSlot() {
   return TestAliasedSlot.value.first;
 }
 
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "ConstexprAliased<int, int>"{{.*}}DIFlagTypePassByValue
+// CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "ConstexprAliased<const int, int>"{{.*}}DIFlagTypePassByValue
+template <class A, class B> struct ConstexprAliased {
+  A first;
+  B second;
+  constexpr ConstexprAliased(const A &a, const B &b) : first(a), second(b) {}
+};
+union ConstexprAliasedSlot {
+  ConstexprAliased<const int, int> value;
+  ConstexprAliased<int, int> mutable_value;
+  ConstexprAliasedSlot() {}
+  ~ConstexprAliasedSlot() {}
+} TestConstexprAliasedSlot;
+int ReadConstexprAliasedSlot() {
+  TestConstexprAliasedSlot.mutable_value = ConstexprAliased<int, int>(1, 2);
+  return TestConstexprAliasedSlot.value.first;
+}
+
 // Defined out-of-line constexpr constructor should emit full debug info.
 // CHECK-DAG: !DICompositeType(tag: DW_TAG_structure_type, name: "OutOfLineConstexpr"{{.*}}DIFlagTypePassByValue
 struct OutOfLineConstexpr {
diff --git a/clang/unittests/AST/DeclTest.cpp b/clang/unittests/AST/DeclTest.cpp
index 195b8ab4c4e66..6fa1da7f01f00 100644
--- a/clang/unittests/AST/DeclTest.cpp
+++ b/clang/unittests/AST/DeclTest.cpp
@@ -24,10 +24,12 @@
 #include "clang/Basic/LLVM.h"
 #include "clang/Basic/Linkage.h"
 #include "clang/Basic/TargetInfo.h"
+#include "clang/Frontend/ASTUnit.h"
 #include "clang/Lex/Lexer.h"
 #include "clang/Tooling/Tooling.h"
 #include "llvm/Support/raw_ostream.h"
 #include "llvm/Testing/Annotations/Annotations.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 #include <cassert>
 #include <memory>
@@ -36,6 +38,14 @@
 using namespace clang::ast_matchers;
 using namespace clang::tooling;
 using namespace clang;
+using ::testing::AllOf;
+using ::testing::Not;
+using ::testing::Pointee;
+
+MATCHER(IsStandardLayout, "") { return arg.isStandardLayout(); }
+MATCHER(IsStandardLayoutUnionMember, "") {
+  return arg.isStandardLayoutUnionMember();
+}
 
 TEST(Decl, CleansUpAPValues) {
   MatchFinder Finder;
@@ -1029,3 +1039,249 @@ TEST(Decl, ObjCPropertyDeclNameForDiagnostic) {
                              /*Qualified=*/true);
   EXPECT_EQ(ExtPQualifiedOS.str(), "-[MyClass extensionProp]");
 }
+
+class StandardLayoutUnionMemberTest : public ::testing::Test {
+protected:
+  std::unique_ptr<ASTUnit> AST;
+  ASTContext *Ctx = nullptr;
+
+  void buildAST(StringRef Code, ArrayRef<std::string> Args = {"-std=c++11"}) {
+    AST = tooling::buildASTFromCodeWithArgs(Code, Args);
+    ASSERT_NE(AST, nullptr);
+    Ctx = &AST->getASTContext();
+  }
+
+  const CXXRecordDecl *findRecord(StringRef Name) const {
+    assert(Ctx && "ASTContext not initialized");
+    return selectFirst<CXXRecordDecl>(
+        "d",
+        match(cxxRecordDecl(hasName(Name), isDefinition()).bind("d"), *Ctx));
+  }
+
+  const CXXRecordDecl *findTemplate(StringRef Name,
+                                    StringRef TemplateArg) const {
+    assert(Ctx && "ASTContext not initialized");
+    return selectFirst<CXXRecordDecl>(
+        "d", match(classTemplateSpecializationDecl(
+                       hasName(Name), isDefinition(),
+                       hasTemplateArgument(
+                           0, refersToType(asString(TemplateArg.str()))))
+                       .bind("d"),
+                   *Ctx));
+  }
+
+  // Matches a standard-layout union member.
+  auto IsSLUnionMember() {
+    return Pointee(AllOf(IsStandardLayout(), IsStandardLayoutUnionMember()));
+  }
+
+  // Matches a standard-layout type that is not a member of a standard-layout
+  // union.
+  auto IsStandaloneSL() {
+    return Pointee(
+        AllOf(IsStandardLayout(), Not(IsStandardLayoutUnionMember())));
+  }
+
+  // Matches a non-standard-layout type.
+  auto IsNonSL() {
+    return Pointee(
+        AllOf(Not(IsStandardLayout()), Not(IsStandardLayoutUnionMember())));
+  }
+};
+
+TEST_F(StandardLayoutUnionMemberTest, StandaloneStruct) {
+  buildAST(R"cc(
+    struct StandaloneSL {
+      int x;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("StandaloneSL"), IsStandaloneSL());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, NonStandardLayoutStruct) {
+  buildAST(R"cc(
+    struct NonSL {
+      virtual void foo();
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("NonSL"), IsNonSL());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, StandardLayoutUnion) {
+  buildAST(R"cc(
+    struct SLInUnion {
+      int x;
+    };
+
+    union SLUnion {
+      SLInUnion u;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("SLUnion"), IsStandaloneSL());
+  EXPECT_THAT(findRecord("SLInUnion"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, TemplatedMember) {
+  buildAST(R"cc(
+    template <typename T>
+    struct TemplatedSL {
+      T x;
+    };
+
+    union TemplatedUnion {
+      TemplatedSL<int> a;
+      TemplatedSL<float> b;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("TemplatedUnion"), IsStandaloneSL());
+  EXPECT_THAT(findTemplate("TemplatedSL", "int"), IsSLUnionMember());
+  EXPECT_THAT(findTemplate("TemplatedSL", "float"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, NonStandardLayoutUnion) {
+  buildAST(R"cc(
+    struct NonSL {
+      virtual void foo();
+    };
+
+    struct SLInNonSLUnion {
+      int x;
+    };
+
+    union NonSLUnion {
+      SLInNonSLUnion s;
+      NonSL n;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("NonSLUnion"), Pointee(Not(IsStandardLayout())));
+  EXPECT_THAT(findRecord("NonSL"), IsNonSL());
+  EXPECT_THAT(findRecord("SLInNonSLUnion"), IsStandaloneSL());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, NestedStruct) {
+  buildAST(R"cc(
+    union NestedUnion {
+      struct NestedSL {
+        int a;
+      } n;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("NestedUnion"), IsStandaloneSL());
+  EXPECT_THAT(findRecord("NestedSL"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, Array) {
+  buildAST(R"cc(
+    struct SLInArray {
+      int x;
+    };
+    struct SLInMultiArray {
+      int y;
+    };
+    union ArrayUnion {
+      SLInArray arr[3];
+      SLInMultiArray multi_arr[2][4];
+      int raw;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("ArrayUnion"), IsStandaloneSL());
+  EXPECT_THAT(findRecord("SLInArray"), IsSLUnionMember());
+  EXPECT_THAT(findRecord("SLInMultiArray"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, CVQualified) {
+  buildAST(R"cc(
+    struct SLConst {
+      int x;
+    };
+    struct SLVolatile {
+      int y;
+    };
+    union CVUnion {
+      const SLConst c;
+      volatile SLVolatile v;
+      int raw;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("CVUnion"), IsStandaloneSL());
+  EXPECT_THAT(findRecord("SLConst"), IsSLUnionMember());
+  EXPECT_THAT(findRecord("SLVolatile"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, GenericUnionTemplate) {
+  buildAST(R"cc(
+    template <typename T>
+    union GenericUnion {
+      T val;
+      int raw;
+    };
+    struct SLInGenericUnion {
+      int x;
+    };
+    GenericUnion<SLInGenericUnion> u;
+  )cc");
+
+  EXPECT_THAT(findRecord("SLInGenericUnion"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, AnonymousUnion) {
+  buildAST(R"cc(
+    struct SLInAnonUnion {
+      int x;
+    };
+    struct EnclosingStruct {
+      union {
+        SLInAnonUnion a;
+        int b;
+      };
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("SLInAnonUnion"), IsSLUnionMember());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, Inheritance) {
+  buildAST(R"cc(
+    struct EmptyBase {};
+    struct SLDerived : EmptyBase {
+      int x;
+    };
+    union DerivedUnion {
+      SLDerived d;
+      int raw;
+    };
+  )cc");
+
+  EXPECT_THAT(findRecord("DerivedUnion"), IsStandaloneSL());
+  EXPECT_THAT(findRecord("SLDerived"), IsSLUnionMember());
+  EXPECT_THAT(findRecord("EmptyBase"), IsStandaloneSL());
+}
+
+TEST_F(StandardLayoutUnionMemberTest, AnonymousTypedefStruct) {
+  buildAST(R"cc(
+    typedef struct {
+      int x;
+    } AnonTypedefSL;
+
+    union AnonTypedefUnion {
+      AnonTypedefSL s;
+      int raw;
+    };
+  )cc");
+
+  const auto *TD = selectFirst<TypedefDecl>(
+      "d", match(typedefDecl(hasName("AnonTypedefSL")).bind("d"), *Ctx));
+  ASSERT_NE(TD, nullptr);
+  const auto *AnonRecord = TD->getUnderlyingType()->getAsCXXRecordDecl();
+
+  EXPECT_THAT(findRecord("AnonTypedefUnion"), IsStandaloneSL());
+  EXPECT_THAT(AnonRecord, IsSLUnionMember());
+}

``````````

</details>


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


More information about the cfe-commits mailing list