Skip to content

Commit 19aa2db

Browse files
ssbrzygoloid
authored andcommitted
[clang] Mark trivial_abi types as "trivially relocatable".
This change enables library code to skip paired move-construction and destruction for `trivial_abi` types, as if they were trivially-movable and trivially-destructible. This offers an extension to the performance fix offered by `trivial_abi`: rather than only offering trivial-type-like performance for pass-by-value, it also offers it for library code that moves values but not as arguments. For example, if we use `memcpy` for trivially relocatable types inside of vector reallocation, and mark `unique_ptr` as `trivial_abi` (via `_LIBCPP_ABI_ENABLE_UNIQUE_PTR_TRIVIAL_ABI` / `_LIBCPP_ABI_UNSTABLE` / etc.), this would speed up `vector<unique_ptr>::push_back` by 40% on my benchmarks. (Though note that in this case, the compiler could have done this anyway, but happens not to due to the inlining horizon.) If accepted, I intend to follow up with exactly such changes to library code, including and especially `std::vector`, making them use a trivial relocation operation on trivially relocatable types. **D50119 and P1144:** This change is very similar to D50119, which was rejected from Clang. (That change was an implementation of P1144, which is not yet part of the C++ standard.) The intent of this change, rather than trying to pick a winning proposal for trivial relocation operations, is to extend the behavior of `trivial_abi` in a way that could be made compatible with any such proposal. If P1144 or any similar proposal were accepted, then `trivial_abi`, `__is_trivially_relocatable`, and everything else in this change would be redefined in terms of that. **Safety:** It's worth pointing out, specifically, that `trivial_abi` already implies trivial relocatability in a narrow sense: a `trivial_abi` type, when passed by value, has its constructor run in one location, and its destructor run in another, after the type has been trivially relocated (through registers). Trivial relocatability optimizations could change the number of paired constructor/destructor calls, but this seems unlikely to matter for `trivial_abi` types. Reviewed By: rsmith Differential Revision: https://reviews.llvm.org/D114732
1 parent 42c61a5 commit 19aa2db

File tree

10 files changed

+157
-4
lines changed

10 files changed

+157
-4
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,11 @@ The following type trait primitives are supported by Clang. Those traits marked
13651365
* ``__is_trivially_constructible`` (C++, GNU, Microsoft)
13661366
* ``__is_trivially_copyable`` (C++, GNU, Microsoft)
13671367
* ``__is_trivially_destructible`` (C++, MSVC 2013)
1368+
* ``__is_trivially_relocatable`` (Clang): Returns true if moving an object
1369+
of the given type, and then destroying the source object, is known to be
1370+
functionally equivalent to copying the underlying bytes and then dropping the
1371+
source object on the floor. This is true of trivial types and types which
1372+
were made trivially relocatable via the ``clang::trivial_abi`` attribute.
13681373
* ``__is_union`` (C++, GNU, Microsoft, Embarcadero)
13691374
* ``__is_unsigned`` (C++, Embarcadero):
13701375
Returns false for enumeration types. Note, before Clang 13, returned true for

clang/include/clang/AST/Type.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -829,6 +829,8 @@ class QualType {
829829
/// Return true if this is a trivially copyable type (C++0x [basic.types]p9)
830830
bool isTriviallyCopyableType(const ASTContext &Context) const;
831831

832+
/// Return true if this is a trivially relocatable type.
833+
bool isTriviallyRelocatableType(const ASTContext &Context) const;
832834

833835
/// Returns true if it is a class and it might be dynamic.
834836
bool mayBeDynamicClass() const;

clang/include/clang/Basic/AttrDocs.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3295,6 +3295,9 @@ If a type is trivial for the purposes of calls, has a non-trivial destructor,
32953295
and is passed as an argument by value, the convention is that the callee will
32963296
destroy the object before returning.
32973297

3298+
If a type is trivial for the purpose of calls, it is assumed to be trivially
3299+
relocatable for the purpose of ``__is_trivially_relocatable``.
3300+
32983301
Attribute ``trivial_abi`` has no effect in the following cases:
32993302

33003303
- The class directly declares a virtual base or virtual methods.

clang/include/clang/Basic/TokenKinds.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,7 @@ TYPE_TRAIT_1(__has_unique_object_representations,
510510
KEYWORD(__underlying_type , KEYCXX)
511511

512512
// Clang-only C++ Type Traits
513+
TYPE_TRAIT_1(__is_trivially_relocatable, IsTriviallyRelocatable, KEYCXX)
513514
TYPE_TRAIT_2(__reference_binds_to_temporary, ReferenceBindsToTemporary, KEYCXX)
514515

515516
// Embarcadero Expression Traits

clang/lib/AST/Type.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2495,6 +2495,25 @@ bool QualType::isTriviallyCopyableType(const ASTContext &Context) const {
24952495
return false;
24962496
}
24972497

2498+
bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const {
2499+
QualType BaseElementType = Context.getBaseElementType(*this);
2500+
2501+
if (BaseElementType->isIncompleteType()) {
2502+
return false;
2503+
} else if (const auto *RD = BaseElementType->getAsRecordDecl()) {
2504+
return RD->canPassInRegisters();
2505+
} else {
2506+
switch (isNonTrivialToPrimitiveDestructiveMove()) {
2507+
case PCK_Trivial:
2508+
return !isDestructedType();
2509+
case PCK_ARCStrong:
2510+
return true;
2511+
default:
2512+
return false;
2513+
}
2514+
}
2515+
}
2516+
24982517
bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const {
24992518
return !Context.getLangOpts().ObjCAutoRefCount &&
25002519
Context.getLangOpts().ObjCWeak &&

clang/lib/Sema/SemaExprCXX.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111
///
1212
//===----------------------------------------------------------------------===//
1313

14-
#include "clang/Sema/Template.h"
15-
#include "clang/Sema/SemaInternal.h"
1614
#include "TreeTransform.h"
1715
#include "TypeLocBuilder.h"
1816
#include "clang/AST/ASTContext.h"
@@ -27,14 +25,17 @@
2725
#include "clang/Basic/AlignedAllocation.h"
2826
#include "clang/Basic/PartialDiagnostic.h"
2927
#include "clang/Basic/TargetInfo.h"
28+
#include "clang/Basic/TypeTraits.h"
3029
#include "clang/Lex/Preprocessor.h"
3130
#include "clang/Sema/DeclSpec.h"
3231
#include "clang/Sema/Initialization.h"
3332
#include "clang/Sema/Lookup.h"
3433
#include "clang/Sema/ParsedTemplate.h"
3534
#include "clang/Sema/Scope.h"
3635
#include "clang/Sema/ScopeInfo.h"
36+
#include "clang/Sema/SemaInternal.h"
3737
#include "clang/Sema/SemaLambda.h"
38+
#include "clang/Sema/Template.h"
3839
#include "clang/Sema/TemplateDeduction.h"
3940
#include "llvm/ADT/APInt.h"
4041
#include "llvm/ADT/STLExtras.h"
@@ -4746,6 +4747,8 @@ static bool CheckUnaryTypeTraitTypeCompleteness(Sema &S, TypeTrait UTT,
47464747
case UTT_IsStandardLayout:
47474748
case UTT_IsPOD:
47484749
case UTT_IsLiteral:
4750+
// By analogy, is_trivially_relocatable imposes the same constraints.
4751+
case UTT_IsTriviallyRelocatable:
47494752
// Per the GCC type traits documentation, T shall be a complete type, cv void,
47504753
// or an array of unknown bound. But GCC actually imposes the same constraints
47514754
// as above.
@@ -5210,6 +5213,8 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT,
52105213
return !T->isIncompleteType();
52115214
case UTT_HasUniqueObjectRepresentations:
52125215
return C.hasUniqueObjectRepresentations(T);
5216+
case UTT_IsTriviallyRelocatable:
5217+
return T.isTriviallyRelocatableType(C);
52135218
}
52145219
}
52155220

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

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,62 @@ 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+
#ifdef _WIN32
9+
// On Windows, 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 #ifdef _WIN32.
13+
static_assert(!__is_trivially_relocatable(a<int>), "");
14+
#else
15+
static_assert(__is_trivially_relocatable(a<int>), "");
16+
#endif
817

918
struct [[clang::trivial_abi]] S0 {
1019
int a;
1120
};
21+
static_assert(__is_trivially_relocatable(S0), "");
1222

1323
struct __attribute__((trivial_abi)) S1 {
1424
int a;
1525
};
26+
static_assert(__is_trivially_relocatable(S1), "");
1627

1728
struct __attribute__((trivial_abi)) S3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3'}} expected-note {{is polymorphic}}
1829
virtual void m();
1930
};
31+
static_assert(!__is_trivially_relocatable(S3), "");
2032

2133
struct S3_2 {
2234
virtual void m();
2335
} __attribute__((trivial_abi)); // expected-warning {{'trivial_abi' cannot be applied to 'S3_2'}} expected-note {{is polymorphic}}
36+
static_assert(!__is_trivially_relocatable(S3_2), "");
2437

2538
struct __attribute__((trivial_abi)) S3_3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3_3'}} expected-note {{has a field of a non-trivial class type}}
2639
S3_3(S3_3 &&);
2740
S3_2 s32;
2841
};
42+
static_assert(!__is_trivially_relocatable(S3_3), "");
2943

3044
// Diagnose invalid trivial_abi even when the type is templated because it has a non-trivial field.
3145
template <class T>
3246
struct __attribute__((trivial_abi)) S3_4 { // expected-warning {{'trivial_abi' cannot be applied to 'S3_4'}} expected-note {{has a field of a non-trivial class type}}
3347
S3_4(S3_4 &&);
3448
S3_2 s32;
3549
};
50+
static_assert(!__is_trivially_relocatable(S3_4<int>), "");
3651

3752
struct S4 {
3853
int a;
3954
};
55+
static_assert(__is_trivially_relocatable(S4), "");
4056

4157
struct __attribute__((trivial_abi)) S5 : public virtual S4 { // expected-warning {{'trivial_abi' cannot be applied to 'S5'}} expected-note {{has a virtual base}}
4258
};
59+
static_assert(!__is_trivially_relocatable(S5), "");
4360

4461
struct __attribute__((trivial_abi)) S9 : public S4 {
4562
};
63+
static_assert(__is_trivially_relocatable(S9), "");
4664

4765
struct __attribute__((trivial_abi(1))) S8 { // expected-error {{'trivial_abi' attribute takes no arguments}}
4866
int a;
@@ -55,6 +73,8 @@ struct __attribute__((trivial_abi)) S10 {
5573
};
5674

5775
S10<int *> p1;
76+
static_assert(__is_trivially_relocatable(S10<int>), "");
77+
static_assert(!__is_trivially_relocatable(S10<S3>), "");
5878

5979
template <class T>
6080
struct S14 {
@@ -66,11 +86,15 @@ struct __attribute__((trivial_abi)) S15 : S14<T> {
6686
};
6787

6888
S15<int> s15;
89+
static_assert(__is_trivially_relocatable(S15<int>), "");
90+
static_assert(!__is_trivially_relocatable(S15<S3>), "");
6991

7092
template <class T>
7193
struct __attribute__((trivial_abi)) S16 {
7294
S14<T> a;
7395
};
96+
static_assert(__is_trivially_relocatable(S16<int>), "");
97+
static_assert(!__is_trivially_relocatable(S16<S3>), "");
7498

7599
S16<int> s16;
76100

@@ -79,34 +103,50 @@ struct __attribute__((trivial_abi)) S17 {
79103
};
80104

81105
S17<int> s17;
106+
static_assert(__is_trivially_relocatable(S17<int>), "");
107+
static_assert(__is_trivially_relocatable(S17<S3>), "");
82108

83109
namespace deletedCopyMoveConstructor {
84110
struct __attribute__((trivial_abi)) CopyMoveDeleted { // expected-warning {{'trivial_abi' cannot be applied to 'CopyMoveDeleted'}} expected-note {{copy constructors and move constructors are all deleted}}
85111
CopyMoveDeleted(const CopyMoveDeleted &) = delete;
86112
CopyMoveDeleted(CopyMoveDeleted &&) = delete;
87113
};
114+
static_assert(!__is_trivially_relocatable(CopyMoveDeleted), "");
88115

89116
struct __attribute__((trivial_abi)) S18 { // expected-warning {{'trivial_abi' cannot be applied to 'S18'}} expected-note {{copy constructors and move constructors are all deleted}}
90117
CopyMoveDeleted a;
91118
};
119+
static_assert(!__is_trivially_relocatable(S18), "");
92120

93121
struct __attribute__((trivial_abi)) CopyDeleted {
94122
CopyDeleted(const CopyDeleted &) = delete;
95123
CopyDeleted(CopyDeleted &&) = default;
96124
};
125+
#ifdef _WIN32
126+
static_assert(!__is_trivially_relocatable(CopyDeleted), "");
127+
#else
128+
static_assert(__is_trivially_relocatable(CopyDeleted), "");
129+
#endif
97130

98131
struct __attribute__((trivial_abi)) MoveDeleted {
99132
MoveDeleted(const MoveDeleted &) = default;
100133
MoveDeleted(MoveDeleted &&) = delete;
101134
};
135+
static_assert(__is_trivially_relocatable(MoveDeleted), "");
102136

103137
struct __attribute__((trivial_abi)) S19 { // expected-warning {{'trivial_abi' cannot be applied to 'S19'}} expected-note {{copy constructors and move constructors are all deleted}}
104138
CopyDeleted a;
105139
MoveDeleted b;
106140
};
141+
static_assert(!__is_trivially_relocatable(S19), "");
107142

108143
// This is fine since the move constructor isn't deleted.
109144
struct __attribute__((trivial_abi)) S20 {
110145
int &&a; // a member of rvalue reference type deletes the copy constructor.
111146
};
147+
#ifdef _WIN32
148+
static_assert(!__is_trivially_relocatable(S20), "");
149+
#else
150+
static_assert(__is_trivially_relocatable(S20), "");
151+
#endif
112152
} // namespace deletedCopyMoveConstructor

clang/test/SemaCXX/type-traits.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2854,3 +2854,64 @@ void test() { (void) __is_constructible(int, T32768(int)); }
28542854
#undef T16384
28552855
#undef T32768
28562856
} // namespace type_trait_expr_numargs_overflow
2857+
2858+
namespace is_trivially_relocatable {
2859+
2860+
static_assert(!__is_trivially_relocatable(void), "");
2861+
static_assert(__is_trivially_relocatable(int), "");
2862+
static_assert(__is_trivially_relocatable(int[]), "");
2863+
2864+
enum Enum {};
2865+
static_assert(__is_trivially_relocatable(Enum), "");
2866+
static_assert(__is_trivially_relocatable(Enum[]), "");
2867+
2868+
union Union {int x;};
2869+
static_assert(__is_trivially_relocatable(Union), "");
2870+
static_assert(__is_trivially_relocatable(Union[]), "");
2871+
2872+
struct Trivial {};
2873+
static_assert(__is_trivially_relocatable(Trivial), "");
2874+
static_assert(__is_trivially_relocatable(Trivial[]), "");
2875+
2876+
struct Incomplete; // expected-note {{forward declaration of 'is_trivially_relocatable::Incomplete'}}
2877+
bool unused = __is_trivially_relocatable(Incomplete); // expected-error {{incomplete type}}
2878+
2879+
struct NontrivialDtor {
2880+
~NontrivialDtor() {}
2881+
};
2882+
static_assert(!__is_trivially_relocatable(NontrivialDtor), "");
2883+
static_assert(!__is_trivially_relocatable(NontrivialDtor[]), "");
2884+
2885+
struct NontrivialCopyCtor {
2886+
NontrivialCopyCtor(const NontrivialCopyCtor&) {}
2887+
};
2888+
static_assert(!__is_trivially_relocatable(NontrivialCopyCtor), "");
2889+
static_assert(!__is_trivially_relocatable(NontrivialCopyCtor[]), "");
2890+
2891+
struct NontrivialMoveCtor {
2892+
NontrivialMoveCtor(NontrivialMoveCtor&&) {}
2893+
};
2894+
static_assert(!__is_trivially_relocatable(NontrivialMoveCtor), "");
2895+
static_assert(!__is_trivially_relocatable(NontrivialMoveCtor[]), "");
2896+
2897+
struct [[clang::trivial_abi]] TrivialAbiNontrivialDtor {
2898+
~TrivialAbiNontrivialDtor() {}
2899+
};
2900+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialDtor), "");
2901+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialDtor[]), "");
2902+
2903+
struct [[clang::trivial_abi]] TrivialAbiNontrivialCopyCtor {
2904+
TrivialAbiNontrivialCopyCtor(const TrivialAbiNontrivialCopyCtor&) {}
2905+
};
2906+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialCopyCtor), "");
2907+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialCopyCtor[]), "");
2908+
2909+
// A more complete set of tests for the behavior of trivial_abi can be found in
2910+
// clang/test/SemaCXX/attr-trivial-abi.cpp
2911+
struct [[clang::trivial_abi]] TrivialAbiNontrivialMoveCtor {
2912+
TrivialAbiNontrivialMoveCtor(TrivialAbiNontrivialMoveCtor&&) {}
2913+
};
2914+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialMoveCtor), "");
2915+
static_assert(__is_trivially_relocatable(TrivialAbiNontrivialMoveCtor[]), "");
2916+
2917+
} // namespace is_trivially_relocatable

clang/test/SemaObjCXX/arc-type-traits.mm

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#define TRAIT_IS_FALSE(Trait, Type) char JOIN2(Trait,__LINE__)[Trait(Type)? -1 : 1]
1313
#define TRAIT_IS_TRUE_2(Trait, Type1, Type2) char JOIN2(Trait,__LINE__)[Trait(Type1, Type2)? 1 : -1]
1414
#define TRAIT_IS_FALSE_2(Trait, Type1, Type2) char JOIN2(Trait,__LINE__)[Trait(Type1, Type2)? -1 : 1]
15-
15+
1616
struct HasStrong { id obj; };
1717
struct HasWeak { __weak id obj; };
1818
struct HasUnsafeUnretained { __unsafe_unretained id obj; };
@@ -213,3 +213,11 @@
213213
TRAIT_IS_TRUE_2(__is_trivially_constructible, HasUnsafeUnretained, HasUnsafeUnretained);
214214
TRAIT_IS_TRUE_2(__is_trivially_constructible, HasUnsafeUnretained, HasUnsafeUnretained&&);
215215

216+
// __is_trivially_relocatable
217+
TRAIT_IS_TRUE(__is_trivially_relocatable, __strong id);
218+
TRAIT_IS_FALSE(__is_trivially_relocatable, __weak id);
219+
TRAIT_IS_TRUE(__is_trivially_relocatable, __autoreleasing id);
220+
TRAIT_IS_TRUE(__is_trivially_relocatable, __unsafe_unretained id);
221+
TRAIT_IS_TRUE(__is_trivially_relocatable, HasStrong);
222+
TRAIT_IS_FALSE(__is_trivially_relocatable, HasWeak);
223+
TRAIT_IS_TRUE(__is_trivially_relocatable, HasUnsafeUnretained);

clang/test/SemaObjCXX/objc-weak-type-traits.mm

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
#define TRAIT_IS_FALSE(Trait, Type) static_assert(!Trait(Type), "")
99
#define TRAIT_IS_TRUE_2(Trait, Type1, Type2) static_assert(Trait(Type1, Type2), "")
1010
#define TRAIT_IS_FALSE_2(Trait, Type1, Type2) static_assert(!Trait(Type1, Type2), "")
11-
11+
1212
struct HasStrong { id obj; };
1313
struct HasWeak { __weak id obj; };
1414
struct HasUnsafeUnretained { __unsafe_unretained id obj; };
@@ -208,3 +208,12 @@
208208
TRAIT_IS_FALSE_2(__is_trivially_constructible, HasWeak, HasWeak&&);
209209
TRAIT_IS_TRUE_2(__is_trivially_constructible, HasUnsafeUnretained, HasUnsafeUnretained);
210210
TRAIT_IS_TRUE_2(__is_trivially_constructible, HasUnsafeUnretained, HasUnsafeUnretained&&);
211+
212+
// __is_trivially_relocatable
213+
TRAIT_IS_TRUE(__is_trivially_relocatable, __strong id);
214+
TRAIT_IS_FALSE(__is_trivially_relocatable, __weak id);
215+
TRAIT_IS_TRUE(__is_trivially_relocatable, __autoreleasing id);
216+
TRAIT_IS_TRUE(__is_trivially_relocatable, __unsafe_unretained id);
217+
TRAIT_IS_TRUE(__is_trivially_relocatable, HasStrong);
218+
TRAIT_IS_FALSE(__is_trivially_relocatable, HasWeak);
219+
TRAIT_IS_TRUE(__is_trivially_relocatable, HasUnsafeUnretained);

0 commit comments

Comments
 (0)