Skip to content

Commit 4c2c535

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 65d2177 commit 4c2c535

File tree

7 files changed

+214
-40
lines changed

7 files changed

+214
-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
@@ -1398,6 +1398,9 @@ class CXXRecordDecl : public RecordDecl {
13981398
(SMF_CopyConstructor | SMF_MoveConstructor | SMF_Destructor);
13991399
}
14001400

1401+
/// Determine whether this class is trivially relocatable
1402+
bool isTriviallyRelocatable() const;
1403+
14011404
/// Determine whether declaring a const variable with this type is ok
14021405
/// per core issue 253.
14031406
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.
@@ -611,6 +619,10 @@ bool CXXRecordDecl::hasAnyDependentBases() const {
611619
return !forallBases([](const CXXRecordDecl *) { return true; });
612620
}
613621

622+
bool CXXRecordDecl::isTriviallyRelocatable() const {
623+
return (data().IsNaturallyTriviallyRelocatable || hasAttr<TrivialABIAttr>());
624+
}
625+
614626
bool CXXRecordDecl::isTriviallyCopyable() const {
615627
// C++0x [class]p5:
616628
// A trivially copyable class is a class that:
@@ -802,6 +814,10 @@ void CXXRecordDecl::addedMember(Decl *D) {
802814
// -- has no virtual functions
803815
data().IsStandardLayout = false;
804816
data().IsCXX11StandardLayout = false;
817+
818+
// A trivially relocatable class is a class:
819+
// -- which has no virtual member functions or virtual base classes
820+
data().IsNaturallyTriviallyRelocatable = false;
805821
}
806822
}
807823

@@ -868,6 +884,14 @@ void CXXRecordDecl::addedMember(Decl *D) {
868884
? !Constructor->isImplicit()
869885
: (Constructor->isUserProvided() || Constructor->isExplicit()))
870886
data().Aggregate = false;
887+
888+
// A trivially relocatable class is a class:
889+
// -- where no eligible copy constructor, move constructor, copy
890+
// assignment operator, move assignment operator, or destructor is
891+
// user-provided,
892+
if (Constructor->isUserProvided() && (Constructor->isCopyConstructor() ||
893+
Constructor->isMoveConstructor()))
894+
data().IsNaturallyTriviallyRelocatable = false;
871895
}
872896
}
873897

@@ -897,11 +921,18 @@ void CXXRecordDecl::addedMember(Decl *D) {
897921
Method->getNonObjectParameter(0)->getType()->getAs<ReferenceType>();
898922
if (!ParamTy || ParamTy->getPointeeType().isConstQualified())
899923
data().HasDeclaredCopyAssignmentWithConstParam = true;
924+
925+
if (Method->isUserProvided())
926+
data().IsNaturallyTriviallyRelocatable = false;
900927
}
901928

902-
if (Method->isMoveAssignmentOperator())
929+
if (Method->isMoveAssignmentOperator()) {
903930
SMKind |= SMF_MoveAssignment;
904931

932+
if (Method->isUserProvided())
933+
data().IsNaturallyTriviallyRelocatable = false;
934+
}
935+
905936
// Keep the list of conversion functions up-to-date.
906937
if (auto *Conversion = dyn_cast<CXXConversionDecl>(D)) {
907938
// FIXME: We use the 'unsafe' accessor for the access specifier here,
@@ -1113,6 +1144,12 @@ void CXXRecordDecl::addedMember(Decl *D) {
11131144
} else if (!T.isCXX98PODType(Context))
11141145
data().PlainOldData = false;
11151146

1147+
// A trivially relocatable class is a class:
1148+
// -- all of whose members are either of reference type or of trivially
1149+
// relocatable type
1150+
if (!T->isReferenceType() && !T.isTriviallyRelocatableType(Context))
1151+
data().IsNaturallyTriviallyRelocatable = false;
1152+
11161153
if (T->isReferenceType()) {
11171154
if (!Field->hasInClassInitializer())
11181155
data().HasUninitializedReferenceMember = true;
@@ -1489,8 +1526,10 @@ void CXXRecordDecl::addedEligibleSpecialMemberFunction(const CXXMethodDecl *MD,
14891526
// See https://github.com/llvm/llvm-project/issues/59206
14901527

14911528
if (const auto *DD = dyn_cast<CXXDestructorDecl>(MD)) {
1492-
if (DD->isUserProvided())
1529+
if (DD->isUserProvided()) {
14931530
data().HasIrrelevantDestructor = false;
1531+
data().IsNaturallyTriviallyRelocatable = false;
1532+
}
14941533
// If the destructor is explicitly defaulted and not trivial or not public
14951534
// or if the destructor is deleted, we clear HasIrrelevantDestructor in
14961535
// finishedDefaultedOrDeletedMember.

clang/lib/AST/Type.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2848,8 +2848,8 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
28482848
return false;
28492849
} else if (!BaseElementType->isObjectType()) {
28502850
return false;
2851-
} else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
2852-
return RD->canPassInRegisters();
2851+
} else if (CXXRecordDecl *RD = BaseElementType->getAsCXXRecordDecl()) {
2852+
return RD->isTriviallyRelocatable();
28532853
} else if (BaseElementType.isTriviallyCopyableType(Context)) {
28542854
return true;
28552855
} else {

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)