diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -227,6 +227,18 @@ New Compiler Flags ------------------ +- Implemented `-fcoro-aligned-allocation` flag. This flag implements + Option 2 of P2014R0 aligned allocation of coroutine frames + (`P2014R0 `_). + 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 ------------------------- diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -1635,6 +1635,7 @@ 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) diff --git a/clang/include/clang/Basic/DiagnosticGroups.td b/clang/include/clang/Basic/DiagnosticGroups.td --- a/clang/include/clang/Basic/DiagnosticGroups.td +++ b/clang/include/clang/Basic/DiagnosticGroups.td @@ -66,7 +66,10 @@ 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, diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11278,7 +11278,16 @@ "'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; +def err_conflicting_aligned_options : Error < + "conflicting option '-fcoro-aligned-allocation' and '-fno-aligned-allocation'" >; } // end of coroutines issue category diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -154,6 +154,7 @@ 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") diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1189,6 +1189,11 @@ PosFlag, NegFlag>; +defm coro_aligned_allocation : BoolFOption<"coro-aligned-allocation", + LangOpts<"CoroAlignedAllocation">, DefaultFalse, + PosFlag, + NegFlag>; + defm experimental_library : BoolFOption<"experimental-library", LangOpts<"ExperimentalLibrary">, DefaultFalse, PosFlaggetAsCXXRecordDecl(); 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 @@ // 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 @@ // lvalue that denotes the parameter copy corresponding to p_i. FunctionDecl *OperatorNew = nullptr; - bool PassAlignment = false; SmallVector PlacementArgs; const bool PromiseContainsNew = [this, &PromiseType]() -> bool { @@ -1338,8 +1346,13 @@ 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 @@ 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 @@ 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(OperatorNew->getDeclContext()); // If we didn't find a class-local new declaration and non-throwing new @@ -1387,11 +1446,21 @@ 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 @@ 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 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 @@ // used, the size of the block is passed as the corresponding argument. const auto *OpDeleteType = OpDeleteQualType.getTypePtr()->castAs(); - 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 = diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -3189,7 +3189,7 @@ 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 @@ 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 diff --git a/clang/test/CodeGenCoroutines/coro-aligned-alloc-2.cpp b/clang/test/CodeGenCoroutines/coro-aligned-alloc-2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCoroutines/coro-aligned-alloc-2.cpp @@ -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; +} diff --git a/clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp b/clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp @@ -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; +} diff --git a/clang/test/SemaCXX/coroutine-alloc-4.cpp b/clang/test/SemaCXX/coroutine-alloc-4.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/coroutine-alloc-4.cpp @@ -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; +}