-
Notifications
You must be signed in to change notification settings - Fork 12.4k
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
Conversation
@llvm/pr-subscribers-llvm-adt @llvm/pr-subscribers-clang Author: Mital Ashok (MitalAshok) ChangesAlso changes the behaviour of 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:
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);
|
147f0a7
to
74e1332
Compare
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.
74e1332
to
5908130
Compare
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? |
There was a problem hiding this 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!
@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 |
Clang only uses The type safety attributes were introduced by e4a5a90 and they were exposed as |
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) |
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. |
llvm-project/clang/docs/ReleaseNotes.rst Lines 210 to 212 in b0ae923
Should be enough for ReleaseNotes, since not much has changed since Clang 18 |
Ping |
Will be ABI breaking for Clang 20
I am also looking to cherry-pick this for Clang 19 so there won't be a difference between Clang 19 (when |
There was a problem hiding this 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.
clang/docs/ReleaseNotes.rst
Outdated
@@ -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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
@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.
@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. |
/cherry-pick 5d7357c |
…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.
…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.
…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.
…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.
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.