Skip to content

Commit 7b10ba4

Browse files
ojhuntvar-const
authored andcommitted
[RFC] Initial implementation of P2719 (llvm#113510)
This is a basic implementation of P2719: "Type-aware allocation and deallocation functions" described at http://wg21.link/P2719 The proposal includes some more details but the basic change in functionality is the addition of support for an additional implicit parameter in operators `new` and `delete` to act as a type tag. Tag is of type `std::type_identity<T>` where T is the concrete type being allocated. So for example, a custom type specific allocator for `int` say can be provided by the declaration of void *operator new(std::type_identity<int>, size_t, std::align_val_t); void operator delete(std::type_identity<int>, void*, size_t, std::align_val_t); However this becomes more powerful by specifying templated declarations, for example template <typename T> void *operator new(std::type_identity<T>, size_t, std::align_val_t); template <typename T> void operator delete(std::type_identity<T>, void*, size_t, std::align_val_t);); Where the operators being resolved will be the concrete type being operated over (NB. A completely unconstrained global definition as above is not recommended as it triggers many problems similar to a general override of the global operators). These type aware operators can be declared as either free functions or in class, and can be specified with or without the other implicit parameters, with overload resolution performed according to the existing standard parameter prioritisation, only with type parameterised operators having higher precedence than non-type aware operators. The only exception is destroying_delete which for reasons discussed in the paper we do not support type-aware variants by default.
1 parent 5751173 commit 7b10ba4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+3629
-458
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ C++2c Feature Support
9393

9494
- Implemented `P0963R3 Structured binding declaration as a condition <https://wg21.link/P0963R3>`_.
9595

96+
- Implemented `P2719R4 Type-aware allocation and deallocation functions <https://wg21.link/P2719>`_.
97+
9698
C++23 Feature Support
9799
^^^^^^^^^^^^^^^^^^^^^
98100

clang/include/clang/AST/ASTContext.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
329329
/// This is lazily created. This is intentionally not serialized.
330330
mutable llvm::StringMap<StringLiteral *> StringLiteralCache;
331331

332+
mutable llvm::DenseSet<const FunctionDecl *> DestroyingOperatorDeletes;
333+
mutable llvm::DenseSet<const FunctionDecl *> TypeAwareOperatorNewAndDeletes;
334+
332335
/// The next string literal "version" to allocate during constant evaluation.
333336
/// This is used to distinguish between repeated evaluations of the same
334337
/// string literal.
@@ -3356,6 +3359,15 @@ class ASTContext : public RefCountedBase<ASTContext> {
33563359
void setStaticLocalNumber(const VarDecl *VD, unsigned Number);
33573360
unsigned getStaticLocalNumber(const VarDecl *VD) const;
33583361

3362+
bool hasSeenTypeAwareOperatorNewOrDelete() const {
3363+
return !TypeAwareOperatorNewAndDeletes.empty();
3364+
}
3365+
void setIsDestroyingOperatorDelete(const FunctionDecl *FD, bool IsDestroying);
3366+
bool isDestroyingOperatorDelete(const FunctionDecl *FD) const;
3367+
void setIsTypeAwareOperatorNewOrDelete(const FunctionDecl *FD,
3368+
bool IsTypeAware);
3369+
bool isTypeAwareOperatorNewOrDelete(const FunctionDecl *FD) const;
3370+
33593371
/// Retrieve the context for computing mangling numbers in the given
33603372
/// DeclContext.
33613373
MangleNumberingContext &getManglingNumberContext(const DeclContext *DC);

clang/include/clang/AST/Decl.h

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2540,6 +2540,38 @@ class FunctionDecl : public DeclaratorDecl,
25402540
/// If this function is an allocation/deallocation function that takes
25412541
/// the `std::nothrow_t` tag, return true through IsNothrow,
25422542
bool isReplaceableGlobalAllocationFunction(
2543+
UnsignedOrNone *AlignmentParam = nullptr,
2544+
bool *IsNothrow = nullptr) const {
2545+
if (isTypeAwareOperatorNewOrDelete())
2546+
return false;
2547+
return isUsableAsGlobalAllocationFunctionInConstantEvaluation(
2548+
AlignmentParam, IsNothrow);
2549+
}
2550+
2551+
/// Determines whether this function is one of the replaceable global
2552+
/// allocation functions described in isReplaceableGlobalAllocationFunction,
2553+
/// or is a function that may be treated as such during constant evaluation.
2554+
/// This adds support for potentially templated type aware global allocation
2555+
/// functions of the form:
2556+
/// void *operator new(type-identity, std::size_t, std::align_val_t)
2557+
/// void *operator new(type-identity, std::size_t, std::align_val_t,
2558+
/// const std::nothrow_t &) noexcept;
2559+
/// void *operator new[](type-identity, std::size_t, std::align_val_t)
2560+
/// void *operator new[](type-identity, std::size_t, std::align_val_t,
2561+
/// const std::nothrow_t &) noexcept;
2562+
/// void operator delete(type-identity, void*, std::size_t,
2563+
/// std::align_val_t) noexcept;
2564+
/// void operator delete(type-identity, void*, std::size_t,
2565+
/// std::align_val_t, const std::nothrow_t&) noexcept;
2566+
/// void operator delete[](type-identity, void*, std::size_t,
2567+
/// std::align_val_t) noexcept;
2568+
/// void operator delete[](type-identity, void*, std::size_t,
2569+
/// std::align_val_t, const std::nothrow_t&) noexcept;
2570+
/// Where `type-identity` is a specialization of std::type_identity. If the
2571+
/// declaration is a templated function, it may not include a parameter pack
2572+
/// in the argument list, the type-identity parameter is required to be
2573+
/// dependent, and is the only permitted dependent parameter.
2574+
bool isUsableAsGlobalAllocationFunctionInConstantEvaluation(
25432575
UnsignedOrNone *AlignmentParam = nullptr,
25442576
bool *IsNothrow = nullptr) const;
25452577

@@ -2548,6 +2580,20 @@ class FunctionDecl : public DeclaratorDecl,
25482580

25492581
/// Determine whether this is a destroying operator delete.
25502582
bool isDestroyingOperatorDelete() const;
2583+
void setIsDestroyingOperatorDelete(bool IsDestroyingDelete);
2584+
2585+
/// Count of mandatory parameters for type aware operator new
2586+
static constexpr unsigned RequiredTypeAwareNewParameterCount =
2587+
/* type-identity */ 1 + /* size */ 1 + /* alignment */ 1;
2588+
2589+
/// Count of mandatory parameters for type aware operator delete
2590+
static constexpr unsigned RequiredTypeAwareDeleteParameterCount =
2591+
/* type-identity */ 1 + /* address */ 1 + /* size */ 1 +
2592+
/* alignment */ 1;
2593+
2594+
/// Determine whether this is a type aware operator new or delete.
2595+
bool isTypeAwareOperatorNewOrDelete() const;
2596+
void setIsTypeAwareOperatorNewOrDelete(bool IsTypeAwareOperator = true);
25512597

25522598
/// Compute the language linkage.
25532599
LanguageLinkage getLanguageLinkage() const;

clang/include/clang/AST/DeclarationName.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,34 @@ class DeclarationName {
477477
return OO_None;
478478
}
479479

480+
bool isAnyOperatorNew() const {
481+
if (getNameKind() != DeclarationName::CXXOperatorName)
482+
return false;
483+
switch (getCXXOverloadedOperator()) {
484+
case OO_New:
485+
case OO_Array_New:
486+
return true;
487+
default:
488+
return false;
489+
}
490+
}
491+
492+
bool isAnyOperatorDelete() const {
493+
if (getNameKind() != DeclarationName::CXXOperatorName)
494+
return false;
495+
switch (getCXXOverloadedOperator()) {
496+
case OO_Delete:
497+
case OO_Array_Delete:
498+
return true;
499+
default:
500+
return false;
501+
}
502+
}
503+
504+
bool isAnyOperatorNewOrDelete() const {
505+
return isAnyOperatorNew() || isAnyOperatorDelete();
506+
}
507+
480508
/// If this name is the name of a literal operator,
481509
/// retrieve the identifier associated with it.
482510
const IdentifierInfo *getCXXLiteralIdentifier() const {

clang/include/clang/AST/ExprCXX.h

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2235,6 +2235,98 @@ enum class CXXNewInitializationStyle {
22352235
Braces
22362236
};
22372237

2238+
enum class TypeAwareAllocationMode : unsigned { No, Yes };
2239+
2240+
inline bool isTypeAwareAllocation(TypeAwareAllocationMode Mode) {
2241+
return Mode == TypeAwareAllocationMode::Yes;
2242+
}
2243+
2244+
inline TypeAwareAllocationMode
2245+
typeAwareAllocationModeFromBool(bool IsTypeAwareAllocation) {
2246+
return IsTypeAwareAllocation ? TypeAwareAllocationMode::Yes
2247+
: TypeAwareAllocationMode::No;
2248+
}
2249+
2250+
enum class AlignedAllocationMode : unsigned { No, Yes };
2251+
2252+
inline bool isAlignedAllocation(AlignedAllocationMode Mode) {
2253+
return Mode == AlignedAllocationMode::Yes;
2254+
}
2255+
2256+
inline AlignedAllocationMode alignedAllocationModeFromBool(bool IsAligned) {
2257+
return IsAligned ? AlignedAllocationMode::Yes : AlignedAllocationMode::No;
2258+
}
2259+
2260+
enum class SizedDeallocationMode : unsigned { No, Yes };
2261+
2262+
inline bool isSizedDeallocation(SizedDeallocationMode Mode) {
2263+
return Mode == SizedDeallocationMode::Yes;
2264+
}
2265+
2266+
inline SizedDeallocationMode sizedDeallocationModeFromBool(bool IsSized) {
2267+
return IsSized ? SizedDeallocationMode::Yes : SizedDeallocationMode::No;
2268+
}
2269+
2270+
struct ImplicitAllocationParameters {
2271+
ImplicitAllocationParameters(QualType AllocType,
2272+
TypeAwareAllocationMode PassTypeIdentity,
2273+
AlignedAllocationMode PassAlignment)
2274+
: Type(AllocType), PassTypeIdentity(PassTypeIdentity),
2275+
PassAlignment(PassAlignment) {
2276+
if (!Type.isNull())
2277+
Type = Type.getUnqualifiedType();
2278+
}
2279+
explicit ImplicitAllocationParameters(AlignedAllocationMode PassAlignment)
2280+
: PassTypeIdentity(TypeAwareAllocationMode::No),
2281+
PassAlignment(PassAlignment) {}
2282+
2283+
unsigned getNumImplicitArgs() const {
2284+
unsigned Count = 1; // Size
2285+
if (isTypeAwareAllocation(PassTypeIdentity))
2286+
++Count;
2287+
if (isAlignedAllocation(PassAlignment))
2288+
++Count;
2289+
return Count;
2290+
}
2291+
2292+
QualType Type;
2293+
TypeAwareAllocationMode PassTypeIdentity;
2294+
AlignedAllocationMode PassAlignment;
2295+
};
2296+
2297+
struct ImplicitDeallocationParameters {
2298+
ImplicitDeallocationParameters(QualType DeallocType,
2299+
TypeAwareAllocationMode PassTypeIdentity,
2300+
AlignedAllocationMode PassAlignment,
2301+
SizedDeallocationMode PassSize)
2302+
: Type(DeallocType), PassTypeIdentity(PassTypeIdentity),
2303+
PassAlignment(PassAlignment), PassSize(PassSize) {
2304+
if (!Type.isNull())
2305+
Type = Type.getUnqualifiedType();
2306+
}
2307+
2308+
ImplicitDeallocationParameters(AlignedAllocationMode PassAlignment,
2309+
SizedDeallocationMode PassSize)
2310+
: PassTypeIdentity(TypeAwareAllocationMode::No),
2311+
PassAlignment(PassAlignment), PassSize(PassSize) {}
2312+
2313+
unsigned getNumImplicitArgs() const {
2314+
unsigned Count = 1; // Size
2315+
if (isTypeAwareAllocation(PassTypeIdentity))
2316+
++Count;
2317+
if (isAlignedAllocation(PassAlignment))
2318+
++Count;
2319+
if (isSizedDeallocation(PassSize))
2320+
++Count;
2321+
return Count;
2322+
}
2323+
2324+
QualType Type;
2325+
TypeAwareAllocationMode PassTypeIdentity;
2326+
AlignedAllocationMode PassAlignment;
2327+
SizedDeallocationMode PassSize;
2328+
};
2329+
22382330
/// Represents a new-expression for memory allocation and constructor
22392331
/// calls, e.g: "new CXXNewExpr(foo)".
22402332
class CXXNewExpr final
@@ -2290,7 +2382,8 @@ class CXXNewExpr final
22902382

22912383
/// Build a c++ new expression.
22922384
CXXNewExpr(bool IsGlobalNew, FunctionDecl *OperatorNew,
2293-
FunctionDecl *OperatorDelete, bool ShouldPassAlignment,
2385+
FunctionDecl *OperatorDelete,
2386+
const ImplicitAllocationParameters &IAP,
22942387
bool UsualArrayDeleteWantsSize, ArrayRef<Expr *> PlacementArgs,
22952388
SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
22962389
CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
@@ -2305,7 +2398,7 @@ class CXXNewExpr final
23052398
/// Create a c++ new expression.
23062399
static CXXNewExpr *
23072400
Create(const ASTContext &Ctx, bool IsGlobalNew, FunctionDecl *OperatorNew,
2308-
FunctionDecl *OperatorDelete, bool ShouldPassAlignment,
2401+
FunctionDecl *OperatorDelete, const ImplicitAllocationParameters &IAP,
23092402
bool UsualArrayDeleteWantsSize, ArrayRef<Expr *> PlacementArgs,
23102403
SourceRange TypeIdParens, std::optional<Expr *> ArraySize,
23112404
CXXNewInitializationStyle InitializationStyle, Expr *Initializer,
@@ -2394,6 +2487,10 @@ class CXXNewExpr final
23942487
return const_cast<CXXNewExpr *>(this)->getPlacementArg(I);
23952488
}
23962489

2490+
unsigned getNumImplicitArgs() const {
2491+
return implicitAllocationParameters().getNumImplicitArgs();
2492+
}
2493+
23972494
bool isParenTypeId() const { return CXXNewExprBits.IsParenTypeId; }
23982495
SourceRange getTypeIdParens() const {
23992496
return isParenTypeId() ? getTrailingObjects<SourceRange>()[0]
@@ -2439,6 +2536,15 @@ class CXXNewExpr final
24392536
return CXXNewExprBits.UsualArrayDeleteWantsSize;
24402537
}
24412538

2539+
/// Provides the full set of information about expected implicit
2540+
/// parameters in this call
2541+
ImplicitAllocationParameters implicitAllocationParameters() const {
2542+
return ImplicitAllocationParameters{
2543+
getAllocatedType(),
2544+
typeAwareAllocationModeFromBool(CXXNewExprBits.ShouldPassTypeIdentity),
2545+
alignedAllocationModeFromBool(CXXNewExprBits.ShouldPassAlignment)};
2546+
}
2547+
24422548
using arg_iterator = ExprIterator;
24432549
using const_arg_iterator = ConstExprIterator;
24442550

clang/include/clang/AST/Stmt.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,10 @@ class alignas(void *) Stmt {
891891
LLVM_PREFERRED_TYPE(bool)
892892
unsigned ShouldPassAlignment : 1;
893893

894+
/// Should the type identity be passed to the allocation function?
895+
LLVM_PREFERRED_TYPE(bool)
896+
unsigned ShouldPassTypeIdentity : 1;
897+
894898
/// If this is an array allocation, does the usual deallocation
895899
/// function for the allocated type want to know the allocated size?
896900
LLVM_PREFERRED_TYPE(bool)

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,11 @@ def AlwaysInlineCoroutine :
6464
DiagGroup<"always-inline-coroutine">;
6565
def CoroNonAlignedAllocationFunction :
6666
DiagGroup<"coro-non-aligned-allocation-function">;
67+
def CoroTypeAwareAllocationFunction :
68+
DiagGroup<"coro-type-aware-allocation-function">;
6769
def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException, DeprecatedCoroutine,
68-
AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction]>;
70+
AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction,
71+
CoroTypeAwareAllocationFunction]>;
6972
def ObjCBoolConstantConversion : DiagGroup<"objc-bool-constant-conversion">;
7073
def ConstantConversion : DiagGroup<"constant-conversion",
7174
[BitFieldConstantConversion,

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2595,8 +2595,8 @@ def err_auto_non_deduced_not_alone : Error<
25952595
def err_implied_std_initializer_list_not_found : Error<
25962596
"cannot deduce type of initializer list because std::initializer_list was "
25972597
"not found; include <initializer_list>">;
2598-
def err_malformed_std_initializer_list : Error<
2599-
"std::initializer_list must be a class template with a single type parameter">;
2598+
def err_malformed_std_class_template : Error<
2599+
"std::%0 must be a class template with a single type parameter">;
26002600
def err_auto_init_list_from_c : Error<
26012601
"cannot use %select{'auto'|<ERROR>|'__auto_type'}0 with "
26022602
"%select{initializer list|array}1 in C">;
@@ -9757,27 +9757,46 @@ def err_operator_new_delete_invalid_result_type : Error<
97579757
def err_operator_new_delete_dependent_result_type : Error<
97589758
"%0 cannot have a dependent return type; use %1 instead">;
97599759
def err_operator_new_delete_too_few_parameters : Error<
9760-
"%0 must have at least one parameter">;
9760+
"%select{|type aware }0%select{|destroying }1%2 must have at least %select{|one|two|three|four|five}3 parameter%s3">;
97619761
def err_operator_new_delete_template_too_few_parameters : Error<
97629762
"%0 template must have at least two parameters">;
97639763
def warn_operator_new_returns_null : Warning<
97649764
"%0 should not return a null pointer unless it is declared 'throw()'"
97659765
"%select{| or 'noexcept'}1">, InGroup<OperatorNewReturnsNull>;
97669766

97679767
def err_operator_new_dependent_param_type : Error<
9768-
"%0 cannot take a dependent type as first parameter; "
9769-
"use size_t (%1) instead">;
9768+
"%select{|type aware }0%select{|destroying }1%2 cannot take a dependent type as its %ordinal3 parameter; "
9769+
"use %5 (%4) instead">;
97709770
def err_operator_new_param_type : Error<
9771-
"%0 takes type size_t (%1) as first parameter">;
9771+
"%select{|type aware }0%select{|destroying }1%2 takes type %5 (%4) as %ordinal3 parameter">;
97729772
def err_operator_new_default_arg: Error<
97739773
"parameter of %0 cannot have a default argument">;
97749774
def err_operator_delete_dependent_param_type : Error<
9775-
"%0 cannot take a dependent type as first parameter; use %1 instead">;
9775+
"%select{|type aware }0%select{|destroying }1%2 cannot take a dependent type as its %ordinal3 parameter; "
9776+
"use %4 instead">;
97769777
def err_operator_delete_param_type : Error<
9777-
"first parameter of %0 must have type %1">;
9778+
"%ordinal3 parameter of%select{| type aware}0%select{| destroying}1 %2 must have type %4">;
97789779
def err_destroying_operator_delete_not_usual : Error<
97799780
"destroying operator delete can have only an optional size and optional "
97809781
"alignment parameter">;
9782+
9783+
def err_type_aware_destroying_operator_delete : Error<
9784+
"destroying delete is not permitted to be type aware">;
9785+
9786+
def ext_cxx26_type_aware_allocators : ExtWarn<
9787+
"type aware allocators are a C++2c extension">, InGroup<CXX26>;
9788+
def warn_cxx26_type_aware_allocators : Warning<
9789+
"type aware allocators are incompatible with C++ standards before C++2c">,
9790+
DefaultIgnore, InGroup<CXXPre26Compat>;
9791+
def err_type_aware_allocator_missing_matching_operator : Error<
9792+
"declaration of type aware %0 in %1 must have matching type aware %2"
9793+
>;
9794+
def note_unmatched_type_aware_allocator_declared : Note<
9795+
"unmatched type aware %0 declared here">;
9796+
def err_mismatching_type_aware_cleanup_deallocator : Error<
9797+
"type aware %0 requires a matching type aware %select{|placement }1%2 to be declared in the same scope">;
9798+
def note_type_aware_operator_declared : Note<
9799+
"%select{non-|}0type aware %1 declared here in %2">;
97819800
def note_implicit_delete_this_in_destructor_here : Note<
97829801
"while checking implicit 'delete this' for virtual destructor">;
97839802
def err_builtin_operator_new_delete_not_usual : Error<
@@ -12149,6 +12168,9 @@ def warn_always_inline_coroutine : Warning<
1214912168
def err_coroutine_unusable_new : Error<
1215012169
"'operator new' provided by %0 is not usable with the function signature of %1"
1215112170
>;
12171+
def note_coroutine_unusable_type_aware_allocators : Note<
12172+
"type aware %0 will not be used for coroutine allocation"
12173+
>;
1215212174
def err_coroutine_unfound_nothrow_new : Error <
1215312175
"unable to find %select{'::operator new(size_t, nothrow_t)'|"
1215412176
"'::operator new(size_t, align_val_t, nothrow_t)'}1 for %0"
@@ -12168,6 +12190,9 @@ def err_coroutine_return_type : Error<
1216812190
"function returns a type %0 marked with [[clang::coro_return_type]] but is neither a coroutine nor a coroutine wrapper; "
1216912191
"non-coroutines should be marked with [[clang::coro_wrapper]] to allow returning coroutine return type"
1217012192
>;
12193+
def warn_coroutine_type_aware_allocator_ignored : Warning <
12194+
"type aware %0 will not be used for coroutine allocation">,
12195+
InGroup<CoroTypeAwareAllocationFunction>;
1217112196
} // end of coroutines issue category
1217212197

1217312198
let CategoryName = "Documentation Issue" in {

clang/include/clang/Driver/Options.td

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ defvar cpp14 = LangOpts<"CPlusPlus14">;
624624
defvar cpp17 = LangOpts<"CPlusPlus17">;
625625
defvar cpp20 = LangOpts<"CPlusPlus20">;
626626
defvar cpp23 = LangOpts<"CPlusPlus23">;
627+
defvar cpp26 = LangOpts<"CPlusPlus26">;
627628
defvar c99 = LangOpts<"C99">;
628629
defvar c23 = LangOpts<"C23">;
629630
defvar lang_std = LangOpts<"LangStd">;

0 commit comments

Comments
 (0)