Skip to content

Commit 191a98e

Browse files
committed
[clang][Sema] Track trivial-relocatability as a type trait
To resolve #69394, this patch separates trivial-relocatability's logic from `canPassInRegisters` to decide if a type is trivial-relocatable. A type passed in registers doesn't necessarily mean trivial-relocatability of that type(e.g. on Windows) i.e. it gives us an unintended false positive. This change would be beneficial for Abseil since they rely upon these semantics. By these changes now: User-provided special members prevent natural trivial-relocatabilitiy. It's important because Abseil and maybe others assume the assignment operator doesn't have an impact on the trivial-relocatability of a type. In fact, it does have an effect, and with a user-provided assignment operator, the compiler should only accept it as trivial-relocatable if it's implied by the `[[clang::trivial_abi]]` attribute. Just because a type can pass in registers doesn't necessarily mean it's trivial-relocatable. The `[[clang::trivial_abi]]` attribute always implies trivial-relocatability, even if it can't pass in registers. The trait has extensive tests for both old and new behaviors. Test aggregation of both kinds of types as data members; inheritance; virtual member functions and virtual bases; const and reference data members; and reference types. Fixes #69394
1 parent 2fcfc97 commit 191a98e

File tree

7 files changed

+228
-40
lines changed

7 files changed

+228
-40
lines changed

clang/include/clang/AST/CXXRecordDeclDefinitionBits.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,11 @@ FIELD(DeclaredNonTrivialSpecialMembers, 6, MERGE_OR)
189189
/// SMF_MoveConstructor, and SMF_Destructor are meaningful here.
190190
FIELD(DeclaredNonTrivialSpecialMembersForCall, 6, MERGE_OR)
191191

192+
/// True when this class's bases and fields are all trivially relocatable
193+
/// or references, and the class itself has no user-provided special
194+
/// member functions.
195+
FIELD(IsNaturallyTriviallyRelocatable, 1, NO_MERGE)
196+
192197
/// True when this class has a destructor with no semantic effect.
193198
FIELD(HasIrrelevantDestructor, 1, NO_MERGE)
194199

clang/include/clang/AST/DeclCXX.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1386,6 +1386,9 @@ class CXXRecordDecl : public RecordDecl {
13861386
(SMF_CopyConstructor | SMF_MoveConstructor | SMF_Destructor);
13871387
}
13881388

1389+
/// Determine whether this class is trivially relocatable
1390+
bool isTriviallyRelocatable() const;
1391+
13891392
/// Determine whether declaring a const variable with this type is ok
13901393
/// per core issue 253.
13911394
bool allowConstDefaultInit() const {

clang/lib/AST/DeclCXX.cpp

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D)
9595
DefaultedDestructorIsDeleted(false), HasTrivialSpecialMembers(SMF_All),
9696
HasTrivialSpecialMembersForCall(SMF_All),
9797
DeclaredNonTrivialSpecialMembers(0),
98-
DeclaredNonTrivialSpecialMembersForCall(0), HasIrrelevantDestructor(true),
98+
DeclaredNonTrivialSpecialMembersForCall(0),
99+
IsNaturallyTriviallyRelocatable(true), HasIrrelevantDestructor(true),
99100
HasConstexprNonCopyMoveConstructor(false),
100101
HasDefaultedDefaultConstructor(false),
101102
DefaultedDefaultConstructorIsConstexpr(true),
@@ -279,6 +280,10 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
279280

280281
// An aggregate is a class with [...] no virtual functions.
281282
data().Aggregate = false;
283+
284+
// A trivially relocatable class is a class:
285+
// -- which has no virtual member functions or virtual base classes
286+
data().IsNaturallyTriviallyRelocatable = false;
282287
}
283288

284289
// C++0x [class]p7:
@@ -293,6 +298,9 @@ CXXRecordDecl::setBases(CXXBaseSpecifier const * const *Bases,
293298
if (!hasNonLiteralTypeFieldsOrBases() && !BaseType->isLiteralType(C))
294299
data().HasNonLiteralTypeFieldsOrBases = true;
295300

301+
if (Base->isVirtual() || !BaseClassDecl->isTriviallyRelocatable())
302+
data().IsNaturallyTriviallyRelocatable = false;
303+
296304
// Now go through all virtual bases of this base and add them.
297305
for (const auto &VBase : BaseClassDecl->vbases()) {
298306
// Add this base if it's not already in the list.
@@ -570,6 +578,10 @@ bool CXXRecordDecl::hasAnyDependentBases() const {
570578
return !forallBases([](const CXXRecordDecl *) { return true; });
571579
}
572580

581+
bool CXXRecordDecl::isTriviallyRelocatable() const {
582+
return (data().IsNaturallyTriviallyRelocatable || hasAttr<TrivialABIAttr>());
583+
}
584+
573585
bool CXXRecordDecl::isTriviallyCopyable() const {
574586
// C++0x [class]p5:
575587
// A trivially copyable class is a class that:
@@ -758,6 +770,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
758770
// -- has no virtual functions
759771
data().IsStandardLayout = false;
760772
data().IsCXX11StandardLayout = false;
773+
774+
// A trivially relocatable class is a class:
775+
// -- which has no virtual member functions or virtual base classes
776+
data().IsNaturallyTriviallyRelocatable = false;
761777
}
762778
}
763779

@@ -824,6 +840,14 @@ void CXXRecordDecl::addedMember(Decl *D) {
824840
? !Constructor->isImplicit()
825841
: (Constructor->isUserProvided() || Constructor->isExplicit()))
826842
data().Aggregate = false;
843+
844+
// A trivially relocatable class is a class:
845+
// -- where no eligible copy constructor, move constructor, copy
846+
// assignment operator, move assignment operator, or destructor is
847+
// user-provided,
848+
if (Constructor->isUserProvided() && (Constructor->isCopyConstructor() ||
849+
Constructor->isMoveConstructor()))
850+
data().IsNaturallyTriviallyRelocatable = false;
827851
}
828852
}
829853

@@ -853,11 +877,18 @@ void CXXRecordDecl::addedMember(Decl *D) {
853877
Method->getNonObjectParameter(0)->getType()->getAs<ReferenceType>();
854878
if (!ParamTy || ParamTy->getPointeeType().isConstQualified())
855879
data().HasDeclaredCopyAssignmentWithConstParam = true;
880+
881+
if (Method->isUserProvided())
882+
data().IsNaturallyTriviallyRelocatable = false;
856883
}
857884

858-
if (Method->isMoveAssignmentOperator())
885+
if (Method->isMoveAssignmentOperator()) {
859886
SMKind |= SMF_MoveAssignment;
860887

888+
if (Method->isUserProvided())
889+
data().IsNaturallyTriviallyRelocatable = false;
890+
}
891+
861892
// Keep the list of conversion functions up-to-date.
862893
if (auto *Conversion = dyn_cast<CXXConversionDecl>(D)) {
863894
// FIXME: We use the 'unsafe' accessor for the access specifier here,
@@ -1066,6 +1097,12 @@ void CXXRecordDecl::addedMember(Decl *D) {
10661097
} else if (!T.isCXX98PODType(Context))
10671098
data().PlainOldData = false;
10681099

1100+
// A trivially relocatable class is a class:
1101+
// -- all of whose members are either of reference type or of trivially
1102+
// relocatable type
1103+
if (!T->isReferenceType() && !T.isTriviallyRelocatableType(Context))
1104+
data().IsNaturallyTriviallyRelocatable = false;
1105+
10691106
if (T->isReferenceType()) {
10701107
if (!Field->hasInClassInitializer())
10711108
data().HasUninitializedReferenceMember = true;
@@ -1420,8 +1457,10 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
14201457
// See https://github.com/llvm/llvm-project/issues/59206
14211458

14221459
if (const auto *DD = dyn_cast<CXXDestructorDecl>(MD)) {
1423-
if (DD->isUserProvided())
1460+
if (DD->isUserProvided()) {
14241461
data().HasIrrelevantDestructor = false;
1462+
data().IsNaturallyTriviallyRelocatable = false;
1463+
}
14251464
// If the destructor is explicitly defaulted and not trivial or not public
14261465
// or if the destructor is deleted, we clear HasIrrelevantDestructor in
14271466
// finishedDefaultedOrDeletedMember.

clang/lib/AST/Type.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2680,8 +2680,10 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
26802680
return false;
26812681
} else if (!BaseElementType->isObjectType()) {
26822682
return false;
2683-
} else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
2684-
return RD->canPassInRegisters();
2683+
} else if (CXXRecordDecl *RD = BaseElementType->getAsCXXRecordDecl()) {
2684+
return RD->isTriviallyRelocatable();
2685+
} else if (BaseElementType.isTriviallyCopyableType(Context)) {
2686+
return true;
26852687
} else {
26862688
switch (isNonTrivialToPrimitiveDestructiveMove()) {
26872689
case PCK_Trivial:

clang/test/SemaCXX/attr-trivial-abi.cpp

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,7 @@ void __attribute__((trivial_abi)) foo(); // expected-warning {{'trivial_abi' att
55
// Should not crash.
66
template <class>
77
class __attribute__((trivial_abi)) a { a(a &&); };
8-
#if defined(_WIN64) && !defined(__MINGW32__)
9-
// On Windows/MSVC, to be trivial-for-calls, an object must be trivially copyable.
10-
// (And it is only trivially relocatable, currently, if it is trivial for calls.)
11-
// In this case, it is suppressed by an explicitly defined move constructor.
12-
// Similar concerns apply to later tests that have #if defined(_WIN64) && !defined(__MINGW32__)
13-
static_assert(!__is_trivially_relocatable(a<int>), "");
14-
#else
158
static_assert(__is_trivially_relocatable(a<int>), "");
16-
#endif
179

1810
struct [[clang::trivial_abi]] S0 {
1911
int a;
@@ -39,14 +31,7 @@ struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' c
3931
S3_3(S3_3 &&);
4032
S3_2 s32;
4133
};
42-
#ifdef __ORBIS__
43-
// The ClangABI4OrPS4 calling convention kind passes classes in registers if the
44-
// copy constructor is trivial for calls *or deleted*, while other platforms do
45-
// not accept deleted constructors.
46-
static_assert(__is_trivially_relocatable(S3_3), "");
47-
#else
4834
static_assert(!__is_trivially_relocatable(S3_3), "");
49-
#endif
5035

5136
// Diagnose invalid trivial_abi even when the type is templated because it has a non-trivial field.
5237
template <class T>
@@ -118,30 +103,18 @@ struct __attribute__((trivial_abi)) CopyMoveDeleted { // expected-warning {{'tri
118103
CopyMoveDeleted(const CopyMoveDeleted &) = delete;
119104
CopyMoveDeleted(CopyMoveDeleted &&) = delete;
120105
};
121-
#ifdef __ORBIS__
122106
static_assert(__is_trivially_relocatable(CopyMoveDeleted), "");
123-
#else
124-
static_assert(!__is_trivially_relocatable(CopyMoveDeleted), "");
125-
#endif
126107

127108
struct __attribute__((trivial_abi)) S18 { // expected-warning {{'trivial_abi' cannot be applied to 'S18'}} expected-note {{copy constructors and move constructors are all deleted}}
128109
CopyMoveDeleted a;
129110
};
130-
#ifdef __ORBIS__
131111
static_assert(__is_trivially_relocatable(S18), "");
132-
#else
133-
static_assert(!__is_trivially_relocatable(S18), "");
134-
#endif
135112

136113
struct __attribute__((trivial_abi)) CopyDeleted {
137114
CopyDeleted(const CopyDeleted &) = delete;
138115
CopyDeleted(CopyDeleted &&) = default;
139116
};
140-
#if defined(_WIN64) && !defined(__MINGW32__)
141-
static_assert(!__is_trivially_relocatable(CopyDeleted), "");
142-
#else
143117
static_assert(__is_trivially_relocatable(CopyDeleted), "");
144-
#endif
145118

146119
struct __attribute__((trivial_abi)) MoveDeleted {
147120
MoveDeleted(const MoveDeleted &) = default;
@@ -153,19 +126,11 @@ struct __attribute__((trivial_abi)) S19 { // expected-warning {{'trivial_abi' ca
153126
CopyDeleted a;
154127
MoveDeleted b;
155128
};
156-
#ifdef __ORBIS__
157129
static_assert(__is_trivially_relocatable(S19), "");
158-
#else
159-
static_assert(!__is_trivially_relocatable(S19), "");
160-
#endif
161130

162131
// This is fine since the move constructor isn't deleted.
163132
struct __attribute__((trivial_abi)) S20 {
164133
int &&a; // a member of rvalue reference type deletes the copy constructor.
165134
};
166-
#if defined(_WIN64) && !defined(__MINGW32__)
167-
static_assert(!__is_trivially_relocatable(S20), "");
168-
#else
169135
static_assert(__is_trivially_relocatable(S20), "");
170-
#endif
171136
} // namespace deletedCopyMoveConstructor
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// RUN: %clang_cc1 -std=c++03 -fsyntax-only -verify %s -triple x86_64-windows-msvc
2+
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s -triple x86_64-windows-msvc
3+
// RUN: %clang_cc1 -std=c++03 -fsyntax-only -verify %s -triple x86_64-apple-darwin10
4+
// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s -triple x86_64-apple-darwin10
5+
6+
// expected-no-diagnostics
7+
8+
#if __cplusplus < 201103L
9+
#define static_assert(...) __extension__ _Static_assert(__VA_ARGS__, "")
10+
// cxx98-error@-1 {{variadic macros are a C99 feature}}
11+
#endif
12+
13+
template <class T>
14+
struct Agg {
15+
T t_;
16+
};
17+
18+
template <class T>
19+
struct Der : T {
20+
};
21+
22+
template <class T>
23+
struct Mut {
24+
mutable T t_;
25+
};
26+
27+
template <class T>
28+
struct Non {
29+
Non(); // make it a non-aggregate
30+
T t_;
31+
};
32+
33+
struct CompletelyTrivial {
34+
};
35+
static_assert(__is_trivially_relocatable(CompletelyTrivial));
36+
static_assert(__is_trivially_relocatable(Agg<CompletelyTrivial>));
37+
static_assert(__is_trivially_relocatable(Der<CompletelyTrivial>));
38+
static_assert(__is_trivially_relocatable(Mut<CompletelyTrivial>));
39+
static_assert(__is_trivially_relocatable(Non<CompletelyTrivial>));
40+
41+
struct NonTrivialDtor {
42+
~NonTrivialDtor();
43+
};
44+
static_assert(!__is_trivially_relocatable(NonTrivialDtor));
45+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialDtor>));
46+
static_assert(!__is_trivially_relocatable(Der<NonTrivialDtor>));
47+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialDtor>));
48+
static_assert(!__is_trivially_relocatable(Non<NonTrivialDtor>));
49+
50+
struct NonTrivialCopyCtor {
51+
NonTrivialCopyCtor(const NonTrivialCopyCtor&);
52+
};
53+
static_assert(!__is_trivially_relocatable(NonTrivialCopyCtor));
54+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialCopyCtor>));
55+
static_assert(!__is_trivially_relocatable(Der<NonTrivialCopyCtor>));
56+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialCopyCtor>));
57+
static_assert(!__is_trivially_relocatable(Non<NonTrivialCopyCtor>));
58+
59+
struct NonTrivialMutableCopyCtor {
60+
NonTrivialMutableCopyCtor(NonTrivialMutableCopyCtor&);
61+
};
62+
static_assert(!__is_trivially_relocatable(NonTrivialMutableCopyCtor));
63+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialMutableCopyCtor>));
64+
static_assert(!__is_trivially_relocatable(Der<NonTrivialMutableCopyCtor>));
65+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialMutableCopyCtor>));
66+
static_assert(!__is_trivially_relocatable(Non<NonTrivialMutableCopyCtor>));
67+
68+
#if __cplusplus >= 201103L
69+
struct NonTrivialMoveCtor {
70+
NonTrivialMoveCtor(NonTrivialMoveCtor&&);
71+
};
72+
static_assert(!__is_trivially_relocatable(NonTrivialMoveCtor));
73+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialMoveCtor>));
74+
static_assert(!__is_trivially_relocatable(Der<NonTrivialMoveCtor>));
75+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialMoveCtor>));
76+
static_assert(!__is_trivially_relocatable(Non<NonTrivialMoveCtor>));
77+
#endif
78+
79+
struct NonTrivialCopyAssign {
80+
NonTrivialCopyAssign& operator=(const NonTrivialCopyAssign&);
81+
};
82+
static_assert(!__is_trivially_relocatable(NonTrivialCopyAssign));
83+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialCopyAssign>));
84+
static_assert(!__is_trivially_relocatable(Der<NonTrivialCopyAssign>));
85+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialCopyAssign>));
86+
static_assert(!__is_trivially_relocatable(Non<NonTrivialCopyAssign>));
87+
88+
struct NonTrivialMutableCopyAssign {
89+
NonTrivialMutableCopyAssign& operator=(NonTrivialMutableCopyAssign&);
90+
};
91+
static_assert(!__is_trivially_relocatable(NonTrivialMutableCopyAssign));
92+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialMutableCopyAssign>));
93+
static_assert(!__is_trivially_relocatable(Der<NonTrivialMutableCopyAssign>));
94+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialMutableCopyAssign>));
95+
static_assert(!__is_trivially_relocatable(Non<NonTrivialMutableCopyAssign>));
96+
97+
#if __cplusplus >= 201103L
98+
struct NonTrivialMoveAssign {
99+
NonTrivialMoveAssign& operator=(NonTrivialMoveAssign&&);
100+
};
101+
static_assert(!__is_trivially_relocatable(NonTrivialMoveAssign));
102+
static_assert(!__is_trivially_relocatable(Agg<NonTrivialMoveAssign>));
103+
static_assert(!__is_trivially_relocatable(Der<NonTrivialMoveAssign>));
104+
static_assert(!__is_trivially_relocatable(Mut<NonTrivialMoveAssign>));
105+
static_assert(!__is_trivially_relocatable(Non<NonTrivialMoveAssign>));
106+
#endif

0 commit comments

Comments
 (0)