Skip to content

Commit 74e1332

Browse files
committed
[Clang] Fix definition of layout-compatible to ignore empty classes
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.
1 parent c7c5666 commit 74e1332

File tree

5 files changed

+79
-44
lines changed

5 files changed

+79
-44
lines changed

clang/include/clang/AST/DeclCXX.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,6 +1229,13 @@ class CXXRecordDecl : public RecordDecl {
12291229
/// C++11 [class]p7, specifically using the C++11 rules without any DRs.
12301230
bool isCXX11StandardLayout() const { return data().IsCXX11StandardLayout; }
12311231

1232+
/// If this is a standard-layout class or union per C++11 rules,
1233+
/// any and all data members will be declared in the same type.
1234+
///
1235+
/// This retrieves the type if this class has any data members,
1236+
/// or the current class if there is no class with fields.
1237+
const CXXRecordDecl *getStandardLayoutBaseWithFields() const;
1238+
12321239
/// Determine whether this class, or any of its class subobjects,
12331240
/// contains a mutable field.
12341241
bool hasMutableFields() const { return data().HasMutableFields; }

clang/lib/AST/DeclCXX.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,42 @@ void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
561561
data().StructuralIfLiteral = false;
562562
}
563563

564+
const CXXRecordDecl *CXXRecordDecl::getStandardLayoutBaseWithFields() const {
565+
#ifndef NDEBUG
566+
{
567+
assert(
568+
isCXX11StandardLayout() &&
569+
"getStandardLayoutBaseWithFields called on a non-standard-layout type");
570+
unsigned NumberOfBasesWithFields = 0;
571+
if (!field_empty())
572+
++NumberOfBasesWithFields;
573+
std::set<const CXXRecordDecl *> UniqueBases;
574+
forallBases([&](const CXXRecordDecl *Base) -> bool {
575+
if (!Base->field_empty())
576+
++NumberOfBasesWithFields;
577+
assert(
578+
UniqueBases.insert(Base->getCanonicalDecl()).second &&
579+
"Standard layout struct has multiple base classes of the same type");
580+
return true;
581+
});
582+
assert(NumberOfBasesWithFields <= 1 &&
583+
"Standard layout struct has fields declared in more than one class");
584+
}
585+
#endif
586+
if (!field_empty())
587+
return this;
588+
const CXXRecordDecl *Result = this;
589+
forallBases([&](const CXXRecordDecl *Base) -> bool {
590+
if (!Base->field_empty()) {
591+
// This is the base where the fields are declared; return early
592+
Result = Base;
593+
return false;
594+
}
595+
return true;
596+
});
597+
return Result;
598+
}
599+
564600
bool CXXRecordDecl::hasConstexprDestructor() const {
565601
auto *Dtor = getDestructor();
566602
return Dtor ? Dtor->isConstexpr() : defaultedDestructorIsConstexpr();

clang/lib/Sema/SemaChecking.cpp

Lines changed: 19 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -19084,7 +19084,8 @@ void Sema::DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
1908419084
static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2);
1908519085

1908619086
/// Check if two enumeration types are layout-compatible.
19087-
static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
19087+
static bool isLayoutCompatible(ASTContext &C, const EnumDecl *ED1,
19088+
const EnumDecl *ED2) {
1908819089
// C++11 [dcl.enum] p8:
1908919090
// Two enumeration types are layout-compatible if they have the same
1909019091
// underlying type.
@@ -19095,8 +19096,8 @@ static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
1909519096
/// Check if two fields are layout-compatible.
1909619097
/// Can be used on union members, which are exempt from alignment requirement
1909719098
/// of common initial sequence.
19098-
static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
19099-
FieldDecl *Field2,
19099+
static bool isLayoutCompatible(ASTContext &C, const FieldDecl *Field1,
19100+
const FieldDecl *Field2,
1910019101
bool AreUnionMembers = false) {
1910119102
[[maybe_unused]] const Type *Field1Parent =
1910219103
Field1->getParent()->getTypeForDecl();
@@ -19139,52 +19140,26 @@ static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
1913919140

1914019141
/// Check if two standard-layout structs are layout-compatible.
1914119142
/// (C++11 [class.mem] p17)
19142-
static bool isLayoutCompatibleStruct(ASTContext &C, RecordDecl *RD1,
19143-
RecordDecl *RD2) {
19144-
// If both records are C++ classes, check that base classes match.
19145-
if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1)) {
19146-
// If one of records is a CXXRecordDecl we are in C++ mode,
19147-
// thus the other one is a CXXRecordDecl, too.
19148-
const CXXRecordDecl *D2CXX = cast<CXXRecordDecl>(RD2);
19149-
// Check number of base classes.
19150-
if (D1CXX->getNumBases() != D2CXX->getNumBases())
19151-
return false;
19143+
static bool isLayoutCompatibleStruct(ASTContext &C, const RecordDecl *RD1,
19144+
const RecordDecl *RD2) {
19145+
// Get to the class where the fields are declared
19146+
if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1))
19147+
RD1 = D1CXX->getStandardLayoutBaseWithFields();
1915219148

19153-
// Check the base classes.
19154-
for (CXXRecordDecl::base_class_const_iterator
19155-
Base1 = D1CXX->bases_begin(),
19156-
BaseEnd1 = D1CXX->bases_end(),
19157-
Base2 = D2CXX->bases_begin();
19158-
Base1 != BaseEnd1;
19159-
++Base1, ++Base2) {
19160-
if (!isLayoutCompatible(C, Base1->getType(), Base2->getType()))
19161-
return false;
19162-
}
19163-
} else if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2)) {
19164-
// If only RD2 is a C++ class, it should have zero base classes.
19165-
if (D2CXX->getNumBases() > 0)
19166-
return false;
19167-
}
19149+
if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2))
19150+
RD2 = D2CXX->getStandardLayoutBaseWithFields();
1916819151

1916919152
// Check the fields.
19170-
RecordDecl::field_iterator Field2 = RD2->field_begin(),
19171-
Field2End = RD2->field_end(),
19172-
Field1 = RD1->field_begin(),
19173-
Field1End = RD1->field_end();
19174-
for ( ; Field1 != Field1End && Field2 != Field2End; ++Field1, ++Field2) {
19175-
if (!isLayoutCompatible(C, *Field1, *Field2))
19176-
return false;
19177-
}
19178-
if (Field1 != Field1End || Field2 != Field2End)
19179-
return false;
19180-
19181-
return true;
19153+
return llvm::equal(RD1->fields(), RD2->fields(),
19154+
[&C](const FieldDecl *F1, const FieldDecl *F2) -> bool {
19155+
return isLayoutCompatible(C, F1, F2);
19156+
});
1918219157
}
1918319158

1918419159
/// Check if two standard-layout unions are layout-compatible.
1918519160
/// (C++11 [class.mem] p18)
19186-
static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
19187-
RecordDecl *RD2) {
19161+
static bool isLayoutCompatibleUnion(ASTContext &C, const RecordDecl *RD1,
19162+
const RecordDecl *RD2) {
1918819163
llvm::SmallPtrSet<FieldDecl *, 8> UnmatchedFields;
1918919164
for (auto *Field2 : RD2->fields())
1919019165
UnmatchedFields.insert(Field2);
@@ -19209,8 +19184,8 @@ static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
1920919184
return UnmatchedFields.empty();
1921019185
}
1921119186

19212-
static bool isLayoutCompatible(ASTContext &C, RecordDecl *RD1,
19213-
RecordDecl *RD2) {
19187+
static bool isLayoutCompatible(ASTContext &C, const RecordDecl *RD1,
19188+
const RecordDecl *RD2) {
1921419189
if (RD1->isUnion() != RD2->isUnion())
1921519190
return false;
1921619191

clang/test/SemaCXX/type-traits.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,11 @@ struct CStructWithFMA2 {
17341734
int f[];
17351735
};
17361736

1737+
template<int N>
1738+
struct UniqueEmpty {};
1739+
template<typename... Bases>
1740+
struct D : Bases... {};
1741+
17371742
void is_layout_compatible(int n)
17381743
{
17391744
static_assert(__is_layout_compatible(void, void));
@@ -1837,6 +1842,12 @@ void is_layout_compatible(int n)
18371842
static_assert(!__is_layout_compatible(EnumClassLayout, int));
18381843
static_assert(!__is_layout_compatible(EnumForward, int));
18391844
static_assert(!__is_layout_compatible(EnumClassForward, int));
1845+
static_assert(__is_layout_compatible(CStruct, D<CStruct>));
1846+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStruct>));
1847+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<3>>>));
1848+
static_assert(__is_layout_compatible(CStruct, D<CStructWithQualifiers>));
1849+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStructWithQualifiers>));
1850+
static_assert(__is_layout_compatible(CStructWithQualifiers, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<2>>>));
18401851
}
18411852

18421853
namespace IPIBO {

llvm/include/llvm/ADT/STLExtras.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,12 @@ template <typename L, typename R> bool equal(L &&LRange, R &&RRange) {
20272027
adl_end(RRange));
20282028
}
20292029

2030+
template <typename L, typename R, typename BinaryPredicate>
2031+
bool equal(L &&LRange, R &&RRange, BinaryPredicate P) {
2032+
return std::equal(adl_begin(LRange), adl_end(LRange), adl_begin(RRange),
2033+
adl_end(RRange), P);
2034+
}
2035+
20302036
/// Returns true if all elements in Range are equal or when the Range is empty.
20312037
template <typename R> bool all_equal(R &&Range) {
20322038
auto Begin = adl_begin(Range);

0 commit comments

Comments
 (0)