[C++] [Coroutines] Prefer aligned (de)allocation for coroutines -

implement the option2 of P2014R0

This implements the option2 of
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2014r0.pdf.

This also fixes https://github.com/llvm/llvm-project/issues/56671.

Although wg21 didn't get consensus for the direction of the problem,
we're happy to have some implementation and user experience first. And
from issue56671, the option2 should be the pursued one.

Reviewed By: ychen

Differential Revision: https://reviews.llvm.org/D133341
This commit is contained in:
Chuanqi Xu 2022-09-22 10:59:39 +08:00
parent c932cef32a
commit 327141fb1d
15 changed files with 605 additions and 21 deletions

View File

@ -227,6 +227,18 @@ Non-comprehensive list of changes in this release
New Compiler Flags
------------------
- Implemented `-fcoro-aligned-allocation` flag. This flag implements
Option 2 of P2014R0 aligned allocation of coroutine frames
(`P2014R0 <https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2014r0.pdf>`_).
With this flag, the coroutines will try to lookup aligned allocation
function all the time. The compiler will emit an error if it fails to
find aligned allocation function. So if the user code implemented self
defined allocation function for coroutines, the existing code will be
broken. A little divergence with P2014R0 is that clang will lookup
`::operator new(size_­t, std::aligned_val_t, nothrow_­t)` if there is
`get_­return_­object_­on_­allocation_­failure`. We feel this is more consistent
with the intention.
Deprecated Compiler Flags
-------------------------

View File

@ -1635,6 +1635,7 @@ LANGBUILTIN(__builtin_coro_done, "bv*", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_promise, "v*v*IiIb", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_size, "z", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_align, "z", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_frame, "v*", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_noop, "v*", "n", COR_LANG)
LANGBUILTIN(__builtin_coro_free, "v*v*", "n", COR_LANG)

View File

@ -66,7 +66,10 @@ def DeprecatedCoroutine :
DiagGroup<"deprecated-coroutine", [DeprecatedExperimentalCoroutine]>;
def AlwaysInlineCoroutine :
DiagGroup<"always-inline-coroutine">;
def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException, DeprecatedCoroutine, AlwaysInlineCoroutine]>;
def CoroNonAlignedAllocationFunction :
DiagGroup<"coro-non-aligned-allocation-funciton">;
def Coroutine : DiagGroup<"coroutine", [CoroutineMissingUnhandledException, DeprecatedCoroutine,
AlwaysInlineCoroutine, CoroNonAlignedAllocationFunction]>;
def ObjCBoolConstantConversion : DiagGroup<"objc-bool-constant-conversion">;
def ConstantConversion : DiagGroup<"constant-conversion",
[BitFieldConstantConversion,

View File

@ -11278,7 +11278,16 @@ def err_coroutine_unusable_new : Error<
"'operator new' provided by %0 is not usable with the function signature of %1"
>;
def err_coroutine_unfound_nothrow_new : Error <
"unable to find '::operator new(size_t, nothrow_t)' for %0"
"unable to find %select{'::operator new(size_t, nothrow_t)'|"
"'::operator new(size_t, align_val_t, nothrow_t)'}1 for %0"
>;
def warn_non_aligned_allocation_function : Warning <
"under -fcoro-aligned-allocation, the non-aligned allocation function "
"for the promise type %0 has higher precedence than the global aligned "
"allocation function">,
InGroup<CoroNonAlignedAllocationFunction>;
def err_conflicting_aligned_options : Error <
"conflicting option '-fcoro-aligned-allocation' and '-fno-aligned-allocation'"
>;
} // end of coroutines issue category

View File

@ -154,6 +154,7 @@ LANGOPT(NoBuiltin , 1, 0, "disable builtin functions")
LANGOPT(NoMathBuiltin , 1, 0, "disable math builtin functions")
LANGOPT(GNUAsm , 1, 1, "GNU-style inline assembly")
LANGOPT(Coroutines , 1, 0, "C++20 coroutines")
LANGOPT(CoroAlignedAllocation, 1, 0, "prefer Aligned Allocation according to P2014 Option 2")
LANGOPT(DllExportInlines , 1, 1, "dllexported classes dllexport inline methods")
LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of template template arguments")
LANGOPT(ExperimentalLibrary, 1, 0, "enable unstable and experimental library features")

View File

@ -1189,6 +1189,11 @@ defm coroutines_ts : BoolFOption<"coroutines-ts",
PosFlag<SetTrue, [CC1Option], "Enable support for the C++ Coroutines TS">,
NegFlag<SetFalse>>;
defm coro_aligned_allocation : BoolFOption<"coro-aligned-allocation",
LangOpts<"CoroAlignedAllocation">, DefaultFalse,
PosFlag<SetTrue, [CC1Option], "Prefer aligned allocation for C++ Coroutines">,
NegFlag<SetFalse>>;
defm experimental_library : BoolFOption<"experimental-library",
LangOpts<"ExperimentalLibrary">, DefaultFalse,
PosFlag<SetTrue, [CC1Option, CoreOption], "Control whether unstable and experimental library features are enabled. "

View File

@ -6660,7 +6660,8 @@ public:
bool FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD,
DeclarationName Name, FunctionDecl *&Operator,
bool Diagnose = true, bool WantSize = false);
bool Diagnose = true, bool WantSize = false,
bool WantAligned = false);
FunctionDecl *FindUsualDeallocationFunction(SourceLocation StartLoc,
bool CanProvideSize,
bool Overaligned,

View File

@ -4675,6 +4675,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
return EmitCoroutineIntrinsic(E, Intrinsic::coro_suspend);
case Builtin::BI__builtin_coro_size:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_size);
case Builtin::BI__builtin_coro_align:
return EmitCoroutineIntrinsic(E, Intrinsic::coro_align);
// OpenCL v2.0 s6.13.16.2, Built-in pipe read and write functions
case Builtin::BIread_pipe:

View File

@ -683,6 +683,13 @@ RValue CodeGenFunction::EmitCoroutineIntrinsic(const CallExpr *E,
llvm::Function *F = CGM.getIntrinsic(llvm::Intrinsic::coro_size, T);
return RValue::get(Builder.CreateCall(F));
}
case llvm::Intrinsic::coro_align: {
auto &Context = getContext();
CanQualType SizeTy = Context.getSizeType();
llvm::IntegerType *T = Builder.getIntNTy(Context.getTypeSize(SizeTy));
llvm::Function *F = CGM.getIntrinsic(llvm::Intrinsic::coro_align, T);
return RValue::get(Builder.CreateCall(F));
}
// The following three intrinsics take a token parameter referring to a token
// returned by earlier call to @llvm.coro.id. Since we cannot represent it in
// builtins, we patch it up here.

View File

@ -6500,6 +6500,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("-fcoroutines-ts");
}
if (Args.hasFlag(options::OPT_fcoro_aligned_allocation,
options::OPT_fno_coro_aligned_allocation, false) &&
types::isCXX(InputType))
CmdArgs.push_back("-fcoro-aligned-allocation");
Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes,
options::OPT_fno_double_square_bracket_attributes);

View File

@ -1030,6 +1030,13 @@ static Expr *buildStdNoThrowDeclRef(Sema &S, SourceLocation Loc) {
return DR.get();
}
static TypeSourceInfo *getTypeSourceInfoForStdAlignValT(Sema &S,
SourceLocation Loc) {
EnumDecl *StdAlignValT = S.getStdAlignValT();
QualType StdAlignValDecl = S.Context.getTypeDeclType(StdAlignValT);
return S.Context.getTrivialTypeSourceInfo(StdAlignValDecl);
}
// Find an appropriate delete for the promise.
static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseType,
FunctionDecl *&OperatorDelete) {
@ -1039,12 +1046,15 @@ static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseTy
auto *PointeeRD = PromiseType->getAsCXXRecordDecl();
assert(PointeeRD && "PromiseType must be a CxxRecordDecl type");
const bool Overaligned = S.getLangOpts().CoroAlignedAllocation;
// [dcl.fct.def.coroutine]p12
// The deallocation function's name is looked up by searching for it in the
// scope of the promise type. If nothing is found, a search is performed in
// the global scope.
if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete,
/*Diagnose*/ true, /*WantSize*/ true))
/*Diagnose*/ true, /*WantSize*/ true,
/*WantAligned*/ Overaligned))
return false;
// [dcl.fct.def.coroutine]p12
@ -1057,7 +1067,6 @@ static bool findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseTy
// Look for a global declaration.
// Coroutines can always provide their required size.
const bool CanProvideSize = true;
const bool Overaligned = false;
// Sema::FindUsualDeallocationFunction will try to find the one with two
// parameters first. It will return the deallocation function with one
// parameter if failed.
@ -1324,7 +1333,6 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
// lvalue that denotes the parameter copy corresponding to p_i.
FunctionDecl *OperatorNew = nullptr;
bool PassAlignment = false;
SmallVector<Expr *, 1> PlacementArgs;
const bool PromiseContainsNew = [this, &PromiseType]() -> bool {
@ -1338,8 +1346,13 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
return !R.empty() && !R.isAmbiguous();
}();
// Helper function to indicate whether the last lookup found the aligned
// allocation function.
bool PassAlignment = S.getLangOpts().CoroAlignedAllocation;
auto LookupAllocationFunction = [&](Sema::AllocationFunctionScope NewScope =
Sema::AFS_Both) {
Sema::AFS_Both,
bool WithoutPlacementArgs = false,
bool ForceNonAligned = false) {
// [dcl.fct.def.coroutine]p9
// The allocation function's name is looked up by searching for it in the
// scope of the promise type.
@ -1349,10 +1362,13 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
if (NewScope == Sema::AFS_Both)
NewScope = PromiseContainsNew ? Sema::AFS_Class : Sema::AFS_Global;
PassAlignment = !ForceNonAligned && S.getLangOpts().CoroAlignedAllocation;
FunctionDecl *UnusedResult = nullptr;
S.FindAllocationFunctions(Loc, SourceRange(), NewScope,
/*DeleteScope*/ Sema::AFS_Both, PromiseType,
/*isArray*/ false, PassAlignment, PlacementArgs,
/*isArray*/ false, PassAlignment,
WithoutPlacementArgs ? MultiExprArg{}
: PlacementArgs,
OperatorNew, UnusedResult, /*Diagnose*/ false);
};
@ -1364,15 +1380,58 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
LookupAllocationFunction();
// [dcl.fct.def.coroutine]p9
// If no viable function is found ([over.match.viable]), overload resolution
// is performed again on a function call created by passing just the amount of
// space required as an argument of type std::size_t.
if (!OperatorNew && !PlacementArgs.empty() && PromiseContainsNew) {
PlacementArgs.clear();
LookupAllocationFunction();
if (PromiseContainsNew && !PlacementArgs.empty()) {
// [dcl.fct.def.coroutine]p9
// If no viable function is found ([over.match.viable]), overload
// resolution
// is performed again on a function call created by passing just the amount
// of space required as an argument of type std::size_t.
//
// Proposed Change of [dcl.fct.def.coroutine]p9 in P2014R0:
// Otherwise, overload resolution is performed again on a function call
// created
// by passing the amount of space requested as an argument of type
// std::size_t as the first argument, and the requested alignment as
// an argument of type std:align_val_t as the second argument.
if (!OperatorNew ||
(S.getLangOpts().CoroAlignedAllocation && !PassAlignment))
LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
/*WithoutPlacementArgs*/ true);
}
// Proposed Change of [dcl.fct.def.coroutine]p12 in P2014R0:
// Otherwise, overload resolution is performed again on a function call
// created
// by passing the amount of space requested as an argument of type
// std::size_t as the first argument, and the lvalues p1 ... pn as the
// succeeding arguments. Otherwise, overload resolution is performed again
// on a function call created by passing just the amount of space required as
// an argument of type std::size_t.
//
// So within the proposed change in P2014RO, the priority order of aligned
// allocation functions wiht promise_type is:
//
// void* operator new( std::size_t, std::align_val_t, placement_args... );
// void* operator new( std::size_t, std::align_val_t);
// void* operator new( std::size_t, placement_args... );
// void* operator new( std::size_t);
// Helper variable to emit warnings.
bool FoundNonAlignedInPromise = false;
if (PromiseContainsNew && S.getLangOpts().CoroAlignedAllocation)
if (!OperatorNew || !PassAlignment) {
FoundNonAlignedInPromise = OperatorNew;
LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
/*WithoutPlacementArgs*/ false,
/*ForceNonAligned*/ true);
if (!OperatorNew && !PlacementArgs.empty())
LookupAllocationFunction(/*NewScope*/ Sema::AFS_Class,
/*WithoutPlacementArgs*/ true,
/*ForceNonAligned*/ true);
}
bool IsGlobalOverload =
OperatorNew && !isa<CXXRecordDecl>(OperatorNew->getDeclContext());
// If we didn't find a class-local new declaration and non-throwing new
@ -1387,11 +1446,21 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
LookupAllocationFunction(Sema::AFS_Global);
}
// If we found a non-aligned allocation function in the promise_type,
// it indicates the user forgot to update the allocation function. Let's emit
// a warning here.
if (FoundNonAlignedInPromise) {
S.Diag(OperatorNew->getLocation(),
diag::warn_non_aligned_allocation_function)
<< &FD;
}
if (!OperatorNew) {
if (PromiseContainsNew)
S.Diag(Loc, diag::err_coroutine_unusable_new) << PromiseType << &FD;
else if (RequiresNoThrowAlloc)
S.Diag(Loc, diag::err_coroutine_unfound_nothrow_new) << &FD;
S.Diag(Loc, diag::err_coroutine_unfound_nothrow_new)
<< &FD << S.getLangOpts().CoroAlignedAllocation;
return false;
}
@ -1422,15 +1491,34 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
Expr *FrameSize =
S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_size, {});
// Make new call.
Expr *FrameAlignment = nullptr;
if (S.getLangOpts().CoroAlignedAllocation) {
FrameAlignment =
S.BuildBuiltinCallExpr(Loc, Builtin::BI__builtin_coro_align, {});
TypeSourceInfo *AlignValTy = getTypeSourceInfoForStdAlignValT(S, Loc);
if (!AlignValTy)
return false;
FrameAlignment = S.BuildCXXNamedCast(Loc, tok::kw_static_cast, AlignValTy,
FrameAlignment, SourceRange(Loc, Loc),
SourceRange(Loc, Loc))
.get();
}
// Make new call.
ExprResult NewRef =
S.BuildDeclRefExpr(OperatorNew, OperatorNew->getType(), VK_LValue, Loc);
if (NewRef.isInvalid())
return false;
SmallVector<Expr *, 2> NewArgs(1, FrameSize);
llvm::append_range(NewArgs, PlacementArgs);
if (S.getLangOpts().CoroAlignedAllocation && PassAlignment)
NewArgs.push_back(FrameAlignment);
if (OperatorNew->getNumParams() > NewArgs.size())
llvm::append_range(NewArgs, PlacementArgs);
ExprResult NewExpr =
S.BuildCallExpr(S.getCurScope(), NewRef.get(), Loc, NewArgs, Loc);
@ -1459,9 +1547,29 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() {
// used, the size of the block is passed as the corresponding argument.
const auto *OpDeleteType =
OpDeleteQualType.getTypePtr()->castAs<FunctionProtoType>();
if (OpDeleteType->getNumParams() > 1)
if (OpDeleteType->getNumParams() > DeleteArgs.size() &&
S.getASTContext().hasSameType(
OpDeleteType->getParamType(DeleteArgs.size()), FrameSize->getType()))
DeleteArgs.push_back(FrameSize);
// Proposed Change of [dcl.fct.def.coroutine]p12 in P2014R0:
// If deallocation function lookup finds a usual deallocation function with
// a pointer parameter, size parameter and alignment parameter then this
// will be the selected deallocation function, otherwise if lookup finds a
// usual deallocation function with both a pointer parameter and a size
// parameter, then this will be the selected deallocation function.
// Otherwise, if lookup finds a usual deallocation function with only a
// pointer parameter, then this will be the selected deallocation
// function.
//
// So we are not forced to pass alignment to the deallocation function.
if (S.getLangOpts().CoroAlignedAllocation &&
OpDeleteType->getNumParams() > DeleteArgs.size() &&
S.getASTContext().hasSameType(
OpDeleteType->getParamType(DeleteArgs.size()),
FrameAlignment->getType()))
DeleteArgs.push_back(FrameAlignment);
ExprResult DeleteExpr =
S.BuildCallExpr(S.getCurScope(), DeleteRef.get(), Loc, DeleteArgs, Loc);
DeleteExpr =

View File

@ -3189,7 +3189,7 @@ FunctionDecl *Sema::FindDeallocationFunctionForDestructor(SourceLocation Loc,
bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD,
DeclarationName Name,
FunctionDecl *&Operator, bool Diagnose,
bool WantSize) {
bool WantSize, bool WantAligned) {
LookupResult Found(*this, Name, StartLoc, LookupOrdinaryName);
// Try to find operator delete/operator delete[] in class scope.
LookupQualifiedName(Found, RD);
@ -3199,7 +3199,8 @@ bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD,
Found.suppressDiagnostics();
bool Overaligned = hasNewExtendedAlignment(*this, Context.getRecordType(RD));
bool Overaligned =
WantAligned || hasNewExtendedAlignment(*this, Context.getRecordType(RD));
// C++17 [expr.delete]p10:
// If the deallocation functions have class scope, the one without a

View File

@ -0,0 +1,123 @@
// Tests that the combination of -fcoro-aligned-allocation and -fsized-deallocation works well.
// Test the compiler will chose sized deallocation correctly.
// This is only enabled with `-fsized-deallocation` which is off by default.
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \
// RUN: -fcoro-aligned-allocation -S -emit-llvm %s -o - -disable-llvm-passes \
// RUN: -fsized-deallocation \
// RUN: | FileCheck %s
#include "Inputs/coroutine.h"
namespace std {
typedef __SIZE_TYPE__ size_t;
enum class align_val_t : size_t {};
}
struct task {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task{}; }
void unhandled_exception() {}
void return_value(int) {}
};
};
// CHECK: define{{.*}}@_Z1fv
// CHECK: coro.free:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}void @_ZdlPvmSt11align_val_t(ptr{{.*}}, i64{{.*}}%[[coro_size]], i64{{.*}}%[[coro_align]])
task f() {
co_return 43;
}
struct task2 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task2{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f2v
// CHECK: %[[FREE_HANDLE:.+]] = call{{.*}}ptr @llvm.coro.free(
// CHECK: coro.free:
// CHECK: call{{.*}}void @_ZN5task212promise_typedlEPv(ptr{{.*}} %[[FREE_HANDLE]])
task2 f2() {
co_return 43;
}
struct task3 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task3{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr, std::size_t);
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f3v
// CHECK: %[[FREE_HANDLE:.+]] = call{{.*}}ptr @llvm.coro.free(
// CHECK: coro.free:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: call{{.*}}void @_ZN5task312promise_typedlEPvm(ptr{{.*}} %[[FREE_HANDLE]], i64{{.*}}%[[coro_size]]
task3 f3() {
co_return 43;
}
struct task4 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task4{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr, std::size_t);
void operator delete(void *ptr, std::align_val_t);
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f4v
// CHECK: %[[FREE_HANDLE:.+]] = call{{.*}}ptr @llvm.coro.free(
// CHECK: coro.free:
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}void @_ZN5task412promise_typedlEPvSt11align_val_t(ptr{{.*}} %[[FREE_HANDLE]], i64{{.*}}%[[coro_align]])
task4 f4() {
co_return 43;
}
struct task5 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task5{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr, std::size_t);
void operator delete(void *ptr, std::size_t, std::align_val_t);
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f5v
// CHECK: %[[FREE_HANDLE:.+]] = call{{.*}}ptr @llvm.coro.free(
// CHECK: coro.free:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}void @_ZN5task512promise_typedlEPvmSt11align_val_t(ptr{{.*}} %[[FREE_HANDLE]], i64{{.*}}%[[coro_size]], i64{{.*}}%[[coro_align]])
task5 f5() {
co_return 43;
}

View File

@ -0,0 +1,190 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \
// RUN: -fcoro-aligned-allocation -S -emit-llvm %s -o - -disable-llvm-passes \
// RUN: | FileCheck %s
#include "Inputs/coroutine.h"
namespace std {
typedef __SIZE_TYPE__ size_t;
enum class align_val_t : size_t {};
}
struct task {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task{}; }
void unhandled_exception() {}
void return_value(int) {}
};
};
// CHECK: define{{.*}}@_Z1fv(
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: %[[aligned_new:.+]] = call{{.*}}@_ZnwmSt11align_val_t({{.*}}%[[coro_size]],{{.*}}%[[coro_align]])
// CHECK: coro.free:
// CHECK: %[[coro_align_for_free:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call void @_ZdlPvSt11align_val_t({{.*}}[[coro_align_for_free]]
task f() {
co_return 43;
}
struct task2 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task2{}; }
void unhandled_exception() {}
void return_value(int) {}
static task2 get_return_object_on_allocation_failure() { return task2{}; }
};
};
namespace std {
struct nothrow_t {};
constexpr nothrow_t nothrow = {};
}
void *operator new(std::size_t, std::align_val_t, std::nothrow_t) noexcept;
// CHECK: define{{.*}}@_Z2f2v(
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: %[[aligned_new:.+]] = call{{.*}}@_ZnwmSt11align_val_tSt9nothrow_t({{.*}}%[[coro_size]],{{.*}}%[[coro_align]])
// CHECK: coro.free:
// CHECK: %[[coro_align_for_free:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call void @_ZdlPvSt11align_val_t({{.*}}[[coro_align_for_free]]
task2 f2() {
co_return 43;
}
struct task3 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task3{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f3v
// CHECK: coro.free:
// CHECK: call{{.*}}void @_ZN5task312promise_typedlEPv(
task3 f3() {
co_return 43;
}
struct task4 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task4{}; }
void unhandled_exception() {}
void return_value(int) {}
void operator delete(void *ptr, std::align_val_t);
void operator delete(void *ptr);
};
};
// CHECK: define{{.*}}@_Z2f4v
// CHECK: coro.free:
// CHECK: %[[coro_align_for_free:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}void @_ZN5task412promise_typedlEPvSt11align_val_t({{.*}}, i64{{.*}}[[coro_align_for_free]]
task4 f4() {
co_return 43;
}
struct task5 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task5{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t);
};
};
// CHECK: define{{.*}}@_Z2f5v
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: call{{.*}}ptr @_ZN5task512promise_typenwEm(i64{{.*}}%[[coro_size]])
task5 f5() {
co_return 43;
}
struct task6 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task6{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t);
void *operator new(std::size_t, int i);
};
};
// CHECK: define{{.*}}@_Z2f6i
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: call{{.*}}ptr @_ZN5task612promise_typenwEmi(i64{{.*}}%[[coro_size]],
task6 f6(int i) {
co_return i;
}
struct task7 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task7{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t);
void *operator new(std::size_t, int i);
void *operator new(std::size_t, std::align_val_t);
};
};
// CHECK: define{{.*}}@_Z2f7i
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}ptr @_ZN5task712promise_typenwEmSt11align_val_t(i64{{.*}}%[[coro_size]], i64{{.*}}[[coro_align]])
task7 f7(int i) {
co_return i;
}
struct task8 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task8{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t);
void *operator new(std::size_t, int i);
void *operator new(std::size_t, std::align_val_t);
void *operator new(std::size_t, std::align_val_t, int i);
};
};
// CHECK: define{{.*}}@_Z2f8i
// CHECK: coro.alloc:
// CHECK: %[[coro_size:.+]] = call{{.*}}@llvm.coro.size
// CHECK: %[[coro_align:.+]] = call{{.*}}@llvm.coro.align
// CHECK: call{{.*}}ptr @_ZN5task812promise_typenwEmSt11align_val_ti(i64{{.*}}%[[coro_size]], i64{{.*}}[[coro_align]],
task8 f8(int i) {
co_return i;
}

View File

@ -0,0 +1,116 @@
// Tests that we'll find aligned allocation funciton properly.
// RUN: %clang_cc1 %s -std=c++20 %s -fsyntax-only -verify -fcoro-aligned-allocation
#include "Inputs/std-coroutine.h"
namespace std {
typedef __SIZE_TYPE__ size_t;
enum class align_val_t : size_t {};
}
struct task {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t); // expected-warning 1+{{under -fcoro-aligned-allocation, the non-aligned allocation function for the promise type 'f' has higher precedence than the global aligned allocation function}}
};
};
task f() {
co_return 43;
}
struct task2 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task2{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t, std::align_val_t);
};
};
// no diagnostic expected
task2 f1() {
co_return 43;
}
struct task3 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task3{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t, std::align_val_t) noexcept;
void *operator new(std::size_t) noexcept;
static auto get_return_object_on_allocation_failure() { return task3{}; }
};
};
// no diagnostic expected
task3 f2() {
co_return 43;
}
struct task4 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task4{}; }
void unhandled_exception() {}
void return_value(int) {}
void *operator new(std::size_t, std::align_val_t, int, double, int) noexcept;
};
};
// no diagnostic expected
task4 f3(int, double, int) {
co_return 43;
}
struct task5 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task5{}; }
void unhandled_exception() {}
void return_value(int) {}
};
};
// no diagnostic expected.
// The aligned allocation will be declared by the compiler.
task5 f4() {
co_return 43;
}
namespace std {
struct nothrow_t {};
constexpr nothrow_t nothrow = {};
}
struct task6 {
struct promise_type {
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto get_return_object() { return task6{}; }
void unhandled_exception() {}
void return_value(int) {}
static task6 get_return_object_on_allocation_failure() { return task6{}; }
};
};
task6 f5() { // expected-error 1+{{unable to find '::operator new(size_t, align_val_t, nothrow_t)' for 'f5'}}
co_return 43;
}
void *operator new(std::size_t, std::align_val_t, std::nothrow_t) noexcept;
task6 f6() {
co_return 43;
}