Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Clang] Fix definition of layout-compatible to ignore empty classes #92103

Merged
merged 8 commits into from
Aug 1, 2024

Conversation

MitalAshok
Copy link
Contributor

Also changes the behaviour of __builtin_is_layout_compatible

None of the historic nor the current definition of layout-compatible classes mention anything about base classes (other than implicitly through being standard-layout) and are defined in terms of members, not direct members.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" llvm:adt labels May 14, 2024
@llvmbot
Copy link
Member

llvmbot commented May 14, 2024

@llvm/pr-subscribers-llvm-adt

@llvm/pr-subscribers-clang

Author: Mital Ashok (MitalAshok)

Changes

Also changes the behaviour of __builtin_is_layout_compatible

None of the historic nor the current definition of layout-compatible classes mention anything about base classes (other than implicitly through being standard-layout) and are defined in terms of members, not direct members.


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

5 Files Affected:

  • (modified) clang/include/clang/AST/DeclCXX.h (+7)
  • (modified) clang/lib/AST/DeclCXX.cpp (+36)
  • (modified) clang/lib/Sema/SemaChecking.cpp (+19-44)
  • (modified) clang/test/SemaCXX/type-traits.cpp (+11)
  • (modified) llvm/include/llvm/ADT/STLExtras.h (+6)
diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h
index fb52ac804849d..f29592e1e7520 100644
--- a/clang/include/clang/AST/DeclCXX.h
+++ b/clang/include/clang/AST/DeclCXX.h
@@ -1229,6 +1229,13 @@ class CXXRecordDecl : public RecordDecl {
   /// C++11 [class]p7, specifically using the C++11 rules without any DRs.
   bool isCXX11StandardLayout() const { return data().IsCXX11StandardLayout; }
 
+  /// If this is a standard-layout class or union per C++11 rules,
+  /// any and all data members will be declared in the same type.
+  ///
+  /// This retrieves the type if this class has any data members,
+  /// or the current class if there is no class with fields.
+  const CXXRecordDecl *getStandardLayoutBaseWithFields() const;
+
   /// Determine whether this class, or any of its class subobjects,
   /// contains a mutable field.
   bool hasMutableFields() const { return data().HasMutableFields; }
diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp
index 75c441293d62e..a101b61ad7392 100644
--- a/clang/lib/AST/DeclCXX.cpp
+++ b/clang/lib/AST/DeclCXX.cpp
@@ -561,6 +561,42 @@ void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
     data().StructuralIfLiteral = false;
 }
 
+const CXXRecordDecl *CXXRecordDecl::getStandardLayoutBaseWithFields() const {
+#ifndef NDEBUG
+  {
+    assert(
+        isCXX11StandardLayout() &&
+        "getStandardLayoutBaseWithFields called on a non-standard-layout type");
+    unsigned NumberOfBasesWithFields = 0;
+    if (!field_empty())
+      ++NumberOfBasesWithFields;
+    std::set<const CXXRecordDecl *> UniqueBases;
+    forallBases([&](const CXXRecordDecl *Base) -> bool {
+      if (!Base->field_empty())
+        ++NumberOfBasesWithFields;
+      assert(
+          UniqueBases.insert(Base->getCanonicalDecl()).second &&
+          "Standard layout struct has multiple base classes of the same type");
+      return true;
+    });
+    assert(NumberOfBasesWithFields <= 1 &&
+           "Standard layout struct has fields declared in more than one class");
+  }
+#endif
+  if (!field_empty())
+    return this;
+  const CXXRecordDecl *Result = this;
+  forallBases([&](const CXXRecordDecl *Base) -> bool {
+    if (!Base->field_empty()) {
+      // This is the base where the fields are declared; return early
+      Result = Base;
+      return false;
+    }
+    return true;
+  });
+  return Result;
+}
+
 bool CXXRecordDecl::hasConstexprDestructor() const {
   auto *Dtor = getDestructor();
   return Dtor ? Dtor->isConstexpr() : defaultedDestructorIsConstexpr();
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index ecd1821651140..acd142c1932ad 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -19084,7 +19084,8 @@ void Sema::DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
 static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2);
 
 /// Check if two enumeration types are layout-compatible.
-static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
+static bool isLayoutCompatible(ASTContext &C, const EnumDecl *ED1,
+                               const EnumDecl *ED2) {
   // C++11 [dcl.enum] p8:
   // Two enumeration types are layout-compatible if they have the same
   // underlying type.
@@ -19095,8 +19096,8 @@ static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
 /// Check if two fields are layout-compatible.
 /// Can be used on union members, which are exempt from alignment requirement
 /// of common initial sequence.
-static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
-                               FieldDecl *Field2,
+static bool isLayoutCompatible(ASTContext &C, const FieldDecl *Field1,
+                               const FieldDecl *Field2,
                                bool AreUnionMembers = false) {
   [[maybe_unused]] const Type *Field1Parent =
       Field1->getParent()->getTypeForDecl();
@@ -19139,52 +19140,26 @@ static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
 
 /// Check if two standard-layout structs are layout-compatible.
 /// (C++11 [class.mem] p17)
-static bool isLayoutCompatibleStruct(ASTContext &C, RecordDecl *RD1,
-                                     RecordDecl *RD2) {
-  // If both records are C++ classes, check that base classes match.
-  if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1)) {
-    // If one of records is a CXXRecordDecl we are in C++ mode,
-    // thus the other one is a CXXRecordDecl, too.
-    const CXXRecordDecl *D2CXX = cast<CXXRecordDecl>(RD2);
-    // Check number of base classes.
-    if (D1CXX->getNumBases() != D2CXX->getNumBases())
-      return false;
+static bool isLayoutCompatibleStruct(ASTContext &C, const RecordDecl *RD1,
+                                     const RecordDecl *RD2) {
+  // Get to the class where the fields are declared
+  if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1))
+    RD1 = D1CXX->getStandardLayoutBaseWithFields();
 
-    // Check the base classes.
-    for (CXXRecordDecl::base_class_const_iterator
-               Base1 = D1CXX->bases_begin(),
-           BaseEnd1 = D1CXX->bases_end(),
-              Base2 = D2CXX->bases_begin();
-         Base1 != BaseEnd1;
-         ++Base1, ++Base2) {
-      if (!isLayoutCompatible(C, Base1->getType(), Base2->getType()))
-        return false;
-    }
-  } else if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2)) {
-    // If only RD2 is a C++ class, it should have zero base classes.
-    if (D2CXX->getNumBases() > 0)
-      return false;
-  }
+  if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2))
+    RD2 = D2CXX->getStandardLayoutBaseWithFields();
 
   // Check the fields.
-  RecordDecl::field_iterator Field2 = RD2->field_begin(),
-                             Field2End = RD2->field_end(),
-                             Field1 = RD1->field_begin(),
-                             Field1End = RD1->field_end();
-  for ( ; Field1 != Field1End && Field2 != Field2End; ++Field1, ++Field2) {
-    if (!isLayoutCompatible(C, *Field1, *Field2))
-      return false;
-  }
-  if (Field1 != Field1End || Field2 != Field2End)
-    return false;
-
-  return true;
+  return llvm::equal(RD1->fields(), RD2->fields(),
+                     [&C](const FieldDecl *F1, const FieldDecl *F2) -> bool {
+                       return isLayoutCompatible(C, F1, F2);
+                     });
 }
 
 /// Check if two standard-layout unions are layout-compatible.
 /// (C++11 [class.mem] p18)
-static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
-                                    RecordDecl *RD2) {
+static bool isLayoutCompatibleUnion(ASTContext &C, const RecordDecl *RD1,
+                                    const RecordDecl *RD2) {
   llvm::SmallPtrSet<FieldDecl *, 8> UnmatchedFields;
   for (auto *Field2 : RD2->fields())
     UnmatchedFields.insert(Field2);
@@ -19209,8 +19184,8 @@ static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
   return UnmatchedFields.empty();
 }
 
-static bool isLayoutCompatible(ASTContext &C, RecordDecl *RD1,
-                               RecordDecl *RD2) {
+static bool isLayoutCompatible(ASTContext &C, const RecordDecl *RD1,
+                               const RecordDecl *RD2) {
   if (RD1->isUnion() != RD2->isUnion())
     return false;
 
diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp
index f2fd45762abf8..c32bb1b62828f 100644
--- a/clang/test/SemaCXX/type-traits.cpp
+++ b/clang/test/SemaCXX/type-traits.cpp
@@ -1734,6 +1734,11 @@ struct CStructWithFMA2 {
   int f[];
 };
 
+template<int N>
+struct UniqueEmpty {};
+template<typename... Bases>
+struct D : Bases... {};
+
 void is_layout_compatible(int n)
 {
   static_assert(__is_layout_compatible(void, void));
@@ -1837,6 +1842,12 @@ void is_layout_compatible(int n)
   static_assert(!__is_layout_compatible(EnumClassLayout, int));
   static_assert(!__is_layout_compatible(EnumForward, int));
   static_assert(!__is_layout_compatible(EnumClassForward, int));
+  static_assert(__is_layout_compatible(CStruct, D<CStruct>));
+  static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStruct>));
+  static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<3>>>));
+  static_assert(__is_layout_compatible(CStruct, D<CStructWithQualifiers>));
+  static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStructWithQualifiers>));
+  static_assert(__is_layout_compatible(CStructWithQualifiers, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<3>>>));
 }
 
 namespace IPIBO {
diff --git a/llvm/include/llvm/ADT/STLExtras.h b/llvm/include/llvm/ADT/STLExtras.h
index 08a708e5c5871..3592dc3b9a4eb 100644
--- a/llvm/include/llvm/ADT/STLExtras.h
+++ b/llvm/include/llvm/ADT/STLExtras.h
@@ -2027,6 +2027,12 @@ template <typename L, typename R> bool equal(L &&LRange, R &&RRange) {
                     adl_end(RRange));
 }
 
+template <typename L, typename R, typename Predicate>
+bool equal(L &&LRange, R &&RRange, Predicate P) {
+  return std::equal(adl_begin(LRange), adl_end(LRange), adl_begin(RRange),
+                    adl_end(RRange), P);
+}
+
 /// Returns true if all elements in Range are equal or when the Range is empty.
 template <typename R> bool all_equal(R &&Range) {
   auto Begin = adl_begin(Range);

@MitalAshok MitalAshok force-pushed the fix-layout-compatible branch from 147f0a7 to 74e1332 Compare May 14, 2024 11:55
@cor3ntin cor3ntin requested a review from Endilll May 14, 2024 12:41
Also changes the behaviour of __builtin_is_layout_compatible

None of the historic nor the current definition of layout-compatible classes mention anything about base classes (other than implicitly through being standard-layout) and are defined in terms of members, not direct members.
@MitalAshok MitalAshok force-pushed the fix-layout-compatible branch from 74e1332 to 5908130 Compare May 14, 2024 12:56
@dwblaikie
Copy link
Collaborator

How's this compare with other implementations clang is trying to be compatible with (gcc (in the normal clang driver mode) and msvc (in clang-cl mode))? Would this be an ABI break against them, or is this intended as an ABI fix to align better with them? Or some third option?

Copy link
Contributor

@Endilll Endilll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working on this!

clang/lib/AST/DeclCXX.cpp Outdated Show resolved Hide resolved
clang/lib/AST/DeclCXX.cpp Outdated Show resolved Hide resolved
clang/lib/AST/DeclCXX.cpp Outdated Show resolved Hide resolved
@MitalAshok
Copy link
Contributor Author

@dwblaikie This patch will bring Clang in line with GCC and MSVC: https://godbolt.org/z/nj715zbsW

@dwblaikie
Copy link
Collaborator

@dwblaikie This patch will bring Clang in line with GCC and MSVC: https://godbolt.org/z/nj715zbsW

would the change be ABI incompatible with previous versions of clang? If so, then it'll need to be versioned in Clang's ClangABI handling, so that platforms (like Sony's Playstation, and Apple's platforms) that are only interested in Clang ABI compatibility can retain the old behavior.

@rjmccall might have an interest in whether this is an ABI thing Apple cares about
@pogo59 not sure who over at Sony's the best person to check on sony ABI things

@MitalAshok
Copy link
Contributor Author

Clang only uses Sema::isLayoutCompatible in two places: to implement __is_layout_compatible and Clang type safety checking attributes, which warn if a pointer does not point to a layout compatible type. This only affects compiler warnings.

The type safety attributes were introduced by e4a5a90 and they were exposed as __is_layout_compatible by d5922cf this February, so I don't think it was available in Clang 18

@dwblaikie
Copy link
Collaborator

Ah, OK - guess this might not be an ABI issue, then - carry on :) (I'll leave it to other Clang-y folks to do the rest of the review, the ABI issues were my only concern)

@rjmccall
Copy link
Contributor

Changing a type trait is generally always an ABI problem, but yeah, if this hasn't been released yet, we're still free to fix basic bugs.

@MitalAshok
Copy link
Contributor Author

- Implemented the `__is_layout_compatible` and `__is_pointer_interconvertible_base_of`
intrinsics to support
`P0466R5: Layout-compatibility and Pointer-interconvertibility Traits <https://wg21.link/P0466R5>`_.

Should be enough for ReleaseNotes, since not much has changed since Clang 18

@MitalAshok
Copy link
Contributor Author

Ping

@MitalAshok
Copy link
Contributor Author

I am also looking to cherry-pick this for Clang 19 so there won't be a difference between Clang 19 (when __is_layout_compatible was added) and Clang 20

Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes look right to me.

@@ -160,6 +160,9 @@ Bug Fixes in This Version
Bug Fixes to Compiler Builtins
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

- ``__is_layout_compatible`` no longer requires the empty bases to be the same in two
standard-layout classes. It now only compares non-static data members.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I was under the impression that this feature had not been previously released; is that incorrect?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is correct, but the feature made into the 19 branch, whereas this fix did not (I hope we can backport it), hence a release note.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the plan to remove the release note from LLVM 20 if we're able to backport the fix?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure, but this indeed is a possible course of action.
If you have preferences how this should be handled, let us know.
Personally I'm more focused on getting this backported.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should backport these changes, drop the Clang 20 release note, and no need for a Clang 19.x release note to be added.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@Endilll
Copy link
Contributor

Endilll commented Aug 1, 2024

@MitalAshok Let us know when you apply the changes suggested by Aaron, and consider this PR ready to be merged. Unfortunately, clock is ticking on backporting.

This reverts commit dedf30c.
@MitalAshok
Copy link
Contributor Author

@Endilll @AaronBallman Removed the release note and the tests have passed again. Would appreciate if someone else could start the backport process afterwards since I don't know how to do it.

@Endilll Endilll merged commit 5d7357c into llvm:main Aug 1, 2024
7 checks passed
@Endilll
Copy link
Contributor

Endilll commented Aug 1, 2024

/cherry-pick 5d7357c

Endilll pushed a commit to Endilll/llvm-project that referenced this pull request Aug 1, 2024
…lvm#92103)

Also changes the behaviour of `__builtin_is_layout_compatible`

None of the historic nor the current definition of layout-compatible
classes mention anything about base classes (other than implicitly
through being standard-layout) and are defined in terms of members, not
direct members.
tru pushed a commit to Endilll/llvm-project that referenced this pull request Aug 2, 2024
…lvm#92103)

Also changes the behaviour of `__builtin_is_layout_compatible`

None of the historic nor the current definition of layout-compatible
classes mention anything about base classes (other than implicitly
through being standard-layout) and are defined in terms of members, not
direct members.
tru pushed a commit to Endilll/llvm-project that referenced this pull request Aug 4, 2024
…lvm#92103)

Also changes the behaviour of `__builtin_is_layout_compatible`

None of the historic nor the current definition of layout-compatible
classes mention anything about base classes (other than implicitly
through being standard-layout) and are defined in terms of members, not
direct members.
banach-space pushed a commit to banach-space/llvm-project that referenced this pull request Aug 7, 2024
…lvm#92103)

Also changes the behaviour of `__builtin_is_layout_compatible`

None of the historic nor the current definition of layout-compatible
classes mention anything about base classes (other than implicitly
through being standard-layout) and are defined in terms of members, not
direct members.
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 Clang issues not falling into any other category llvm:adt
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants