ychen created this revision. ychen added reviewers: rjmccall, EricWF, GorNishanov. Herald added subscribers: ChuanqiXu, dexonsmith, lxfind, dang. ychen requested review of this revision. Herald added a project: clang. Herald added a subscriber: cfe-commits.
Default off. The allocation/deallocation function overload resolutions are following the `Issue #4` of http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p2014r0.pdf. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D102147 Files: clang/docs/ClangCommandLineReference.rst clang/include/clang/Basic/Builtins.def clang/include/clang/Basic/LangOptions.def clang/include/clang/Driver/Options.td clang/include/clang/Sema/Sema.h clang/lib/CodeGen/CGBuiltin.cpp clang/lib/Driver/ToolChains/Clang.cpp clang/lib/Sema/SemaCoroutine.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/lib/Sema/SemaExprCXX.cpp clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp
Index: clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-aligned-alloc.cpp @@ -0,0 +1,199 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -fcoroutines-aligned-alloc -std=c++17 \ +// RUN: -Wno-coroutine-missing-unhandled-exception -emit-llvm %s -o - -disable-llvm-passes \ +// RUN: | FileCheck %s + +namespace std { +namespace experimental { +template <typename... T> +struct coroutine_traits; // expected-note {{declared here}} + +template <class Promise = void> +struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept { return {}; } +}; + +template <> +struct coroutine_handle<void> { + static coroutine_handle from_address(void *) { return {}; } + coroutine_handle() = default; + template <class PromiseType> + coroutine_handle(coroutine_handle<PromiseType>) noexcept {} +}; + +} // end namespace experimental + +using size_t = decltype(sizeof(0)); +enum class align_val_t : size_t; + +} // end namespace std + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(std::experimental::coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct global_new_delete_tag {}; + +template<> +struct std::experimental::coroutine_traits<void, global_new_delete_tag> { + struct promise_type { + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + }; +}; + +// If class scope allocator/deallocator are not defined, use the default global +// ones. +// CHECK-LABEL: f0( +extern "C" void f0(global_new_delete_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[NeedAlloc:.+]] = call i1 @llvm.coro.alloc(token %[[ID]]) + // CHECK: br i1 %[[NeedAlloc]], label %[[CheckAlignBB:.+]], label %[[InitBB:.+]] + + // CHECK: [[CheckAlignBB]]: + // CHECK: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK: %[[CMP:.+]] = icmp ugt i64 %[[ALIGN]], 16 + // CHECK: br i1 %[[CMP]], label %[[AlignAllocBB:.+]], label %[[AllocBB:.+]] + + // CHECK: [[AllocBB]]: + // CHECK-NEXT: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK-NEXT: %[[MEM:.+]] = call noalias nonnull i8* @_Znwm(i64 %[[SIZE]]) + // CHECK-NEXT: br label %[[InitBB:.+]] + + // CHECK: [[AlignAllocBB]]: + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK: %[[MEM2:.+]] = call noalias nonnull i8* @_ZnwmSt11align_val_t(i64 %[[SIZE]], i64 %[[ALIGN]]) + // CHECK: call void @llvm.assume(i1 true) [ "align"(i8* %[[MEM2]], i64 %[[ALIGN]]) ] + // CHECK: br label %[[InitBB]] + + // CHECK: [[InitBB]]: + // CHECK: %[[PHI:.+]] = phi i8* [ null, %{{.+}} ], [ %[[MEM]], %[[AllocBB]] ], [ %[[MEM2]], %[[AlignAllocBB]] ] + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin(token %[[ID]], i8* %[[PHI]]) + + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: %[[NeedDealloc:.+]] = icmp ne i8* %[[MEM]], null + // CHECK: br i1 %[[NeedDealloc]], label %[[CheckAlignBB:.+]], label %[[Afterwards:.+]] + + // CHECK: [[CheckAlignBB]]: + // CHECK: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK: %[[CMP:.+]] = icmp ugt i64 %[[ALIGN]], 16 + // CHECK: br i1 %[[CMP]], label %[[AlignedFreeBB:.+]], label %[[FreeBB:.+]] + + // CHECK: [[FreeBB]]: + // CHECK-NEXT: call void @_ZdlPv(i8* %[[MEM]]) + // CHECK-NEXT: br label %[[Afterwards]] + + // CHECK: [[AlignedFreeBB]]: + // CHECK-NEXT: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK-NEXT: call void @_ZdlPvSt11align_val_t(i8* %[[MEM]], i64 %[[ALIGN]]) + // CHECK-NEXT: br label %[[Afterwards]] + + // CHECK: [[Afterwards]]: + // CHECK: ret void + co_return; +} + +struct promise_new_delete_tag {}; + +template<> +struct std::experimental::coroutine_traits<void, promise_new_delete_tag> { + struct promise_type { + void *operator new(std::size_t); + void operator delete(void *); + void operator delete(void *, std::align_val_t); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + }; +}; + +// - If class-specific allocator/deallocator have precedence over global +// allocator/deallocator. +// - Aligned deallocator is preferred over non-aligned deallocator for +// overaligned coroutine frame; Non-aligned deallocator is preferred over +// aligned deallocator for non-overaligned coroutine frame. +// - Dynamically adjust alignment for alloc and dealloc if the selected +// allocator do not have std::align_val_t argument. +// CHECK-LABEL: f1( +extern "C" void f1(promise_new_delete_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv22promise_new_delete_tagEE12promise_typenwEm(i64 %[[SIZE]]) + // CHECK: call i64 @llvm.coro.align.i64() + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv22promise_new_delete_tagEE12promise_typenwEm(i64 + // CHECK: call i32 @llvm.coro.raw.frame.ptr.offset.i32() + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: %[[NeedDealloc:.+]] = icmp ne i8* %[[MEM]], null + // CHECK: br i1 %[[NeedDealloc]], label %[[CheckAlignBB:.+]], label %[[Afterwards:.+]] + + // CHECK: [[CheckAlignBB]]: + // CHECK: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK: %[[CMP:.+]] = icmp ugt i64 %[[ALIGN]], 16 + // CHECK: br i1 %[[CMP]], label %[[AlignedFreeBB:.+]], label %[[FreeBB:.+]] + + // CHECK: [[FreeBB]]: + // CHECK-NEXT: call void @_ZNSt12experimental16coroutine_traitsIJv22promise_new_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) + // CHECK-NEXT: br label %[[Afterwards]] + + // CHECK: [[AlignedFreeBB]]: + // CHECK-NEXT: %[[OFFSET:.+]] = call i32 @llvm.coro.raw.frame.ptr.offset.i32() + // CHECK-NEXT: %[[ADDR:.+]] = getelementptr inbounds i8, i8* %[[MEM]], i32 %[[OFFSET]] + // CHECK-NEXT: %[[ADDR2:.+]] = bitcast i8* %[[ADDR]] to i8** + // CHECK-NEXT: %[[MEM:.+]] = load i8*, i8** %[[ADDR2]], align 8 + // CHECK-NEXT: %[[ALIGN:.+]] = call i64 @llvm.coro.align.i64() + // CHECK-NEXT: call void @_ZNSt12experimental16coroutine_traitsIJv22promise_new_delete_tagEE12promise_typedlEPvSt11align_val_t(i8* %[[MEM]], i64 %[[ALIGN]]) + // CHECK-NEXT: br label %[[Afterwards]] + + // CHECK: [[Afterwards]]: + // CHECK: ret void + co_return; +} + +struct promise_matching_placement_new_tag {}; + +template <> +struct std::experimental::coroutine_traits<void, promise_matching_placement_new_tag> { + struct promise_type { + void *operator new(std::size_t); + void *operator new(std::size_t, promise_matching_placement_new_tag, + int, float, double); + void *operator new(std::size_t, std::align_val_t); + void *operator new(std::size_t, std::align_val_t, + promise_matching_placement_new_tag, + int, float, double); + + void operator delete(void *, std::size_t); + void operator delete(void *, std::size_t, std::align_val_t); + void operator delete(void *); + void operator delete(void *, std::align_val_t); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + }; +}; + +// Class-specific placement allocators have precedence over class-specific +// allocators. +// Sized class-specific deallocators have precedence over non-sized +// class-specific deallocators. +// CHECK-LABEL: f2( +extern "C" void f2(promise_matching_placement_new_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagEE12promise_typenwEm(i64 %[[SIZE]]) + // CHECK: call i64 @llvm.coro.align.i64() + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagEE12promise_typenwEmSt11align_val_t(i64 + + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagEE12promise_typedlEPvm(i8* + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagEE12promise_typedlEPvmSt11align_val_t(i8* + co_return; +} Index: clang/lib/Sema/SemaExprCXX.cpp =================================================================== --- clang/lib/Sema/SemaExprCXX.cpp +++ clang/lib/Sema/SemaExprCXX.cpp @@ -1577,7 +1577,7 @@ /// Determine whether the given function is a non-placement /// deallocation function. -static bool isNonPlacementDeallocationFunction(Sema &S, FunctionDecl *FD) { +bool Sema::isNonPlacementDeallocationFunction(Sema &S, FunctionDecl *FD) { if (CXXMethodDecl *Method = dyn_cast<CXXMethodDecl>(FD)) return S.isUsualDeallocationFunction(Method); @@ -1602,71 +1602,61 @@ return UsualParams == FD->getNumParams(); } -namespace { - struct UsualDeallocFnInfo { - UsualDeallocFnInfo() : Found(), FD(nullptr) {} - UsualDeallocFnInfo(Sema &S, DeclAccessPair Found) - : Found(Found), FD(dyn_cast<FunctionDecl>(Found->getUnderlyingDecl())), - Destroying(false), HasSizeT(false), HasAlignValT(false), - CUDAPref(Sema::CFP_Native) { - // A function template declaration is never a usual deallocation function. - if (!FD) - return; - unsigned NumBaseParams = 1; - if (FD->isDestroyingOperatorDelete()) { - Destroying = true; - ++NumBaseParams; - } - - if (NumBaseParams < FD->getNumParams() && - S.Context.hasSameUnqualifiedType( - FD->getParamDecl(NumBaseParams)->getType(), - S.Context.getSizeType())) { - ++NumBaseParams; - HasSizeT = true; - } - - if (NumBaseParams < FD->getNumParams() && - FD->getParamDecl(NumBaseParams)->getType()->isAlignValT()) { - ++NumBaseParams; - HasAlignValT = true; - } +Sema::UsualDeallocFnInfo::UsualDeallocFnInfo() : Found(), FD(nullptr) {} +Sema::UsualDeallocFnInfo::UsualDeallocFnInfo(Sema &S, DeclAccessPair Found) + : Found(Found), FD(dyn_cast<FunctionDecl>(Found->getUnderlyingDecl())), + Destroying(false), HasSizeT(false), HasAlignValT(false), + CUDAPref(Sema::CFP_Native) { + // A function template declaration is never a usual deallocation function. + if (!FD) + return; + unsigned NumBaseParams = 1; + if (FD->isDestroyingOperatorDelete()) { + Destroying = true; + ++NumBaseParams; + } - // In CUDA, determine how much we'd like / dislike to call this. - if (S.getLangOpts().CUDA) - if (auto *Caller = dyn_cast<FunctionDecl>(S.CurContext)) - CUDAPref = S.IdentifyCUDAPreference(Caller, FD); - } + if (NumBaseParams < FD->getNumParams() && + S.Context.hasSameUnqualifiedType( + FD->getParamDecl(NumBaseParams)->getType(), + S.Context.getSizeType())) { + ++NumBaseParams; + HasSizeT = true; + } - explicit operator bool() const { return FD; } + if (NumBaseParams < FD->getNumParams() && + FD->getParamDecl(NumBaseParams)->getType()->isAlignValT()) { + ++NumBaseParams; + HasAlignValT = true; + } - bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize, - bool WantAlign) const { - // C++ P0722: - // A destroying operator delete is preferred over a non-destroying - // operator delete. - if (Destroying != Other.Destroying) - return Destroying; + // In CUDA, determine how much we'd like / dislike to call this. + if (S.getLangOpts().CUDA) + if (auto *Caller = dyn_cast<FunctionDecl>(S.CurContext)) + CUDAPref = S.IdentifyCUDAPreference(Caller, FD); +} - // C++17 [expr.delete]p10: - // If the type has new-extended alignment, a function with a parameter - // of type std::align_val_t is preferred; otherwise a function without - // such a parameter is preferred - if (HasAlignValT != Other.HasAlignValT) - return HasAlignValT == WantAlign; +bool Sema::UsualDeallocFnInfo::isBetterThan(const UsualDeallocFnInfo &Other, + bool WantSize, + bool WantAlign) const { + // C++ P0722: + // A destroying operator delete is preferred over a non-destroying + // operator delete. + if (Destroying != Other.Destroying) + return Destroying; - if (HasSizeT != Other.HasSizeT) - return HasSizeT == WantSize; + // C++17 [expr.delete]p10: + // If the type has new-extended alignment, a function with a parameter + // of type std::align_val_t is preferred; otherwise a function without + // such a parameter is preferred + if (HasAlignValT != Other.HasAlignValT) + return HasAlignValT == WantAlign; - // Use CUDA call preference as a tiebreaker. - return CUDAPref > Other.CUDAPref; - } + if (HasSizeT != Other.HasSizeT) + return HasSizeT == WantSize; - DeclAccessPair Found; - FunctionDecl *FD; - bool Destroying, HasSizeT, HasAlignValT; - Sema::CUDAFunctionPreference CUDAPref; - }; + // Use CUDA call preference as a tiebreaker. + return CUDAPref > Other.CUDAPref; } /// Determine whether a type has new-extended alignment. This may be called when @@ -1681,14 +1671,14 @@ /// Select the correct "usual" deallocation function to use from a selection of /// deallocation functions (either global or class-scope). -static UsualDeallocFnInfo resolveDeallocationOverload( +static Sema::UsualDeallocFnInfo resolveDeallocationOverload( Sema &S, LookupResult &R, bool WantSize, bool WantAlign, - llvm::SmallVectorImpl<UsualDeallocFnInfo> *BestFns = nullptr) { - UsualDeallocFnInfo Best; + llvm::SmallVectorImpl<Sema::UsualDeallocFnInfo> *BestFns = nullptr) { + Sema::UsualDeallocFnInfo Best; for (auto I = R.begin(), E = R.end(); I != E; ++I) { - UsualDeallocFnInfo Info(S, I.getPair()); - if (!Info || !isNonPlacementDeallocationFunction(S, Info.FD) || + Sema::UsualDeallocFnInfo Info(S, I.getPair()); + if (!Info || !Sema::isNonPlacementDeallocationFunction(S, Info.FD) || Info.CUDAPref == Sema::CFP_Never) continue; @@ -3075,10 +3065,10 @@ } } -FunctionDecl *Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, - bool CanProvideSize, - bool Overaligned, - DeclarationName Name) { +Sema::UsualDeallocFnInfo +Sema::FindUsualDeallocationFunction(SourceLocation StartLoc, + bool CanProvideSize, bool Overaligned, + DeclarationName Name) { DeclareGlobalNewDelete(); LookupResult FoundDelete(*this, Name, StartLoc, LookupOrdinaryName); @@ -3091,7 +3081,7 @@ auto Result = resolveDeallocationOverload(*this, FoundDelete, CanProvideSize, Overaligned); assert(Result.FD && "operator delete missing from global scope?"); - return Result.FD; + return Result; } FunctionDecl *Sema::FindDeallocationFunctionForDestructor(SourceLocation Loc, @@ -3107,13 +3097,16 @@ // If there's no class-specific operator delete, look up the global // non-array delete. return FindUsualDeallocationFunction( - Loc, true, hasNewExtendedAlignment(*this, Context.getRecordType(RD)), - Name); + Loc, true, + hasNewExtendedAlignment(*this, Context.getRecordType(RD)), Name) + .FD; } bool Sema::FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, DeclarationName Name, - FunctionDecl *&Operator, bool Diagnose) { + FunctionDecl *&Operator, + const bool *WantSize, const bool *WantAlign, + bool Diagnose) { LookupResult Found(*this, Name, StartLoc, LookupOrdinaryName); // Try to find operator delete/operator delete[] in class scope. LookupQualifiedName(Found, RD); @@ -3123,13 +3116,16 @@ Found.suppressDiagnostics(); - bool Overaligned = hasNewExtendedAlignment(*this, Context.getRecordType(RD)); + bool Overaligned = + WantAlign ? *WantAlign + : hasNewExtendedAlignment(*this, Context.getRecordType(RD)); // C++17 [expr.delete]p10: // If the deallocation functions have class scope, the one without a // parameter of type std::size_t is selected. llvm::SmallVector<UsualDeallocFnInfo, 4> Matches; - resolveDeallocationOverload(*this, Found, /*WantSize*/ false, + resolveDeallocationOverload(*this, Found, + /*WantSize*/ WantSize ? *WantSize : false, /*WantAlign*/ Overaligned, &Matches); // If we could find an overload, use it. @@ -3621,7 +3617,8 @@ // Look for a global declaration. OperatorDelete = FindUsualDeallocationFunction(StartLoc, CanProvideSize, - Overaligned, DeleteName); + Overaligned, DeleteName) + .FD; } MarkFunctionReferenced(StartLoc, OperatorDelete); Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -9210,9 +9210,11 @@ if (CSM == CXXDestructor && MD->isVirtual()) { FunctionDecl *OperatorDelete = nullptr; DeclarationName Name = - Context.DeclarationNames.getCXXOperatorName(OO_Delete); + Context.DeclarationNames.getCXXOperatorName(OO_Delete); if (FindDeallocationFunction(MD->getLocation(), MD->getParent(), Name, - OperatorDelete, /*Diagnose*/false)) { + OperatorDelete, /*WantSize*/ nullptr, + /*WantAlign*/ nullptr, + /*Diagnose*/ false)) { if (Diagnose) Diag(RD->getLocation(), diag::note_deleted_dtor_no_operator_delete); return true; Index: clang/lib/Sema/SemaCoroutine.cpp =================================================================== --- clang/lib/Sema/SemaCoroutine.cpp +++ clang/lib/Sema/SemaCoroutine.cpp @@ -25,6 +25,7 @@ #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaInternal.h" #include "llvm/ADT/SmallSet.h" +#include "llvm/ADT/SmallVector.h" using namespace clang; using namespace sema; @@ -1060,30 +1061,71 @@ } // Find an appropriate delete for the promise. -static FunctionDecl *findDeleteForPromise(Sema &S, SourceLocation Loc, - QualType PromiseType) { - FunctionDecl *OperatorDelete = nullptr; - +static void +findDeleteForPromise(Sema &S, SourceLocation Loc, QualType PromiseType, + bool UseAlignedAlloc, + Sema::UsualDeallocFnInfo &OperatorDelete, + Sema::UsualDeallocFnInfo &AlignedOperatorDelete) { DeclarationName DeleteName = S.Context.DeclarationNames.getCXXOperatorName(OO_Delete); + // Try to find operator delete in class scope. + auto *PointeeRD = PromiseType->getAsCXXRecordDecl(); assert(PointeeRD && "PromiseType must be a CxxRecordDecl type"); + LookupResult Found(S, DeleteName, Loc, S.LookupOrdinaryName); + S.LookupQualifiedName(Found, PointeeRD); + if (Found.isAmbiguous()) + return; - if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete)) - return nullptr; + Found.suppressDiagnostics(); + + for (auto I = Found.begin(), E = Found.end(); I != E; ++I) { + Sema::UsualDeallocFnInfo Info(S, I.getPair()); + if (!Info || !Sema::isNonPlacementDeallocationFunction(S, Info.FD) || + Info.CUDAPref == Sema::CFP_Never || Info.Destroying) + continue; + + if (!OperatorDelete) { + OperatorDelete = Info; + if (UseAlignedAlloc) + AlignedOperatorDelete = Info; + continue; + } + + if (Info.isBetterThan(OperatorDelete, /*WantSize*/ true, + /*WantAlign*/ false)) + OperatorDelete = Info; + + if (UseAlignedAlloc && + Info.isBetterThan(AlignedOperatorDelete, /*WantSize*/ true, + /*WantAlign*/ true)) + AlignedOperatorDelete = Info; + } + + const bool CanProvideSize = S.isCompleteType(Loc, PromiseType); if (!OperatorDelete) { // Look for a global declaration. - const bool CanProvideSize = S.isCompleteType(Loc, PromiseType); const bool Overaligned = false; OperatorDelete = S.FindUsualDeallocationFunction(Loc, CanProvideSize, Overaligned, DeleteName); } - S.MarkFunctionReferenced(Loc, OperatorDelete); - return OperatorDelete; -} + S.MarkFunctionReferenced(Loc, OperatorDelete.FD); + if (!UseAlignedAlloc) { + AlignedOperatorDelete = OperatorDelete; + return; + } + + if (!AlignedOperatorDelete) { + // Look for a global declaration. + const bool Overaligned = true; + AlignedOperatorDelete = S.FindUsualDeallocationFunction( + Loc, CanProvideSize, Overaligned, DeleteName); + } + S.MarkFunctionReferenced(Loc, AlignedOperatorDelete.FD); +} void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { FunctionScopeInfo *Fn = getCurFunction(); @@ -1254,22 +1296,21 @@ if (S.RequireCompleteType(Loc, PromiseType, diag::err_incomplete_type)) return false; - const bool RequiresNoThrowAlloc = ReturnStmtOnAllocFailure != nullptr; - - // [dcl.fct.def.coroutine]/7 + // [dcl.fct.def.coroutine]/9 // Lookup allocation functions using a parameter list composed of the // requested size of the coroutine state being allocated, followed by // the coroutine function's arguments. If a matching allocation function // exists, use it. Otherwise, use an allocation function that just takes // the requested size. - FunctionDecl *OperatorNew = nullptr; - FunctionDecl *OperatorDelete = nullptr; FunctionDecl *UnusedResult = nullptr; bool PassAlignment = false; + FunctionDecl *OperatorNew = nullptr; + FunctionDecl *AlignedOperatorNew = nullptr; SmallVector<Expr *, 1> PlacementArgs; + SmallVector<Expr *, 1> AlignedPlacementArgs; - // [dcl.fct.def.coroutine]/7 + // [dcl.fct.def.coroutine]/9 // "The allocation functionâs name is looked up in the scope of P. // [...] If the lookup finds an allocation function in the scope of P, // overload resolution is performed on a function call created by assembling @@ -1309,123 +1350,230 @@ PlacementArgs.push_back(PDRefExpr.get()); } + + // Tentatively implement P2014R0 - "Proposed resolution for US061+US062 - + // aligned allocation of coroutine frames" Option 1. + const bool UseAlignedAlloc = S.getLangOpts().AlignedAllocation && + S.getLangOpts().CoroutinesAlignedAlloc; + + if (UseAlignedAlloc) { + AlignedPlacementArgs = PlacementArgs; + PassAlignment = true; + S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class, + /*DeleteScope*/ Sema::AFS_Both, PromiseType, + /*isArray*/ false, PassAlignment, + AlignedPlacementArgs, AlignedOperatorNew, + UnusedResult, /*Diagnose*/ false); + } + + // [dcl.fct.def.coroutine]/9 + // "If no matching function is found, 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 (UseAlignedAlloc && !AlignedOperatorNew && !AlignedPlacementArgs.empty()) { + AlignedPlacementArgs.clear(); + PassAlignment = true; + S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class, + /*DeleteScope*/ Sema::AFS_Both, PromiseType, + /*isArray*/ false, PassAlignment, + AlignedPlacementArgs, AlignedOperatorNew, + UnusedResult, /*Diagnose*/ false); + } + + PassAlignment = false; S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class, /*DeleteScope*/ Sema::AFS_Both, PromiseType, /*isArray*/ false, PassAlignment, PlacementArgs, OperatorNew, UnusedResult, /*Diagnose*/ false); - // [dcl.fct.def.coroutine]/7 - // "If no matching function is found, 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()) { PlacementArgs.clear(); + PassAlignment = false; S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Class, /*DeleteScope*/ Sema::AFS_Both, PromiseType, /*isArray*/ false, PassAlignment, PlacementArgs, OperatorNew, UnusedResult, /*Diagnose*/ false); } - // [dcl.fct.def.coroutine]/7 + if (UseAlignedAlloc && !AlignedOperatorNew && OperatorNew) { + AlignedOperatorNew = OperatorNew; + AlignedPlacementArgs = PlacementArgs; + } + + // [dcl.fct.def.coroutine]/9 // "The allocation functionâs name is looked up in the scope of P. If this // lookup fails, the allocation functionâs name is looked up in the global // scope." + if (UseAlignedAlloc && !AlignedOperatorNew) { + PassAlignment = true; + S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Global, + /*DeleteScope*/ Sema::AFS_Both, PromiseType, + /*isArray*/ false, PassAlignment, + AlignedPlacementArgs, AlignedOperatorNew, + UnusedResult); + } + if (!OperatorNew) { + PassAlignment = false; S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Global, /*DeleteScope*/ Sema::AFS_Both, PromiseType, /*isArray*/ false, PassAlignment, PlacementArgs, OperatorNew, UnusedResult); } - bool IsGlobalOverload = - OperatorNew && !isa<CXXRecordDecl>(OperatorNew->getDeclContext()); - // If we didn't find a class-local new declaration and non-throwing new - // was is required then we need to lookup the non-throwing global operator - // instead. - if (RequiresNoThrowAlloc && (!OperatorNew || IsGlobalOverload)) { + assert(OperatorNew); + assert(!UseAlignedAlloc || AlignedOperatorNew); + + // [dcl.fct.def.coroutine]/10 + // "The unqualified-id get_return_object_on_allocation_failure is looked up in + // the scope of class P by class member access lookup. If a declaration is + // found, then the result of a call to an allocation function used to obtain + // storage for the coroutine state is assumed to return nullptr if it fails to + // obtain storage, and if a global allocation function is selected, the + // ::operator new(size_t, nothrow_t) form shall be used." + auto FindNoThrowAlloc = [&](FunctionDecl *&Operator, + SmallVector<Expr *, 1> &PlaceArgs, + bool PassAlignment) { + // Check if the operator is globally overloaded. + if (isa<CXXRecordDecl>(Operator->getDeclContext())) + return true; + + // If we didn't find a class-local new declaration and non-throwing new + // was is required then we need to lookup the non-throwing global operator + // instead. auto *StdNoThrow = buildStdNoThrowDeclRef(S, Loc); if (!StdNoThrow) return false; - PlacementArgs = {StdNoThrow}; - OperatorNew = nullptr; + PlaceArgs = {StdNoThrow}; + Operator = nullptr; + S.FindAllocationFunctions(Loc, SourceRange(), /*NewScope*/ Sema::AFS_Both, /*DeleteScope*/ Sema::AFS_Both, PromiseType, - /*isArray*/ false, PassAlignment, PlacementArgs, - OperatorNew, UnusedResult); - } + /*isArray*/ false, PassAlignment, PlaceArgs, + Operator, UnusedResult); + return true; + }; - if (!OperatorNew) - return false; + if (ReturnStmtOnAllocFailure) { + if (!FindNoThrowAlloc(OperatorNew, PlacementArgs, false)) + return false; + if (UseAlignedAlloc && + !FindNoThrowAlloc(AlignedOperatorNew, AlignedPlacementArgs, true)) + return false; - if (RequiresNoThrowAlloc) { - const auto *FT = OperatorNew->getType()->castAs<FunctionProtoType>(); - if (!FT->isNothrow(/*ResultIfDependent*/ false)) { - S.Diag(OperatorNew->getLocation(), + auto DiagNoThrow = [&](FunctionDecl *Operator) { + if (!Operator) + return true; + const auto *FT = Operator->getType()->castAs<FunctionProtoType>(); + if (FT->isNothrow(/*ResultIfDependent*/ false)) + return true; + S.Diag(Operator->getLocation(), diag::err_coroutine_promise_new_requires_nothrow) - << OperatorNew; + << Operator; S.Diag(Loc, diag::note_coroutine_promise_call_implicitly_required) - << OperatorNew; + << Operator; + return false; + }; + + bool NoThrow = DiagNoThrow(OperatorNew); + NoThrow = DiagNoThrow(AlignedOperatorNew) || NoThrow; + if (!NoThrow) return false; - } } - if ((OperatorDelete = findDeleteForPromise(S, Loc, PromiseType)) == nullptr) - return false; + Sema::UsualDeallocFnInfo OperatorDelete; + Sema::UsualDeallocFnInfo AlignedOperatorDelete; + findDeleteForPromise(S, Loc, PromiseType, UseAlignedAlloc, OperatorDelete, + AlignedOperatorDelete); - Expr *FramePtr = - buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_frame, {}); + // Make new call. Expr *FrameSize = buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_size, {}); + Expr *FrameAlign = + buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_align, {}); + + // Cast __builtin_coro_align return value to std::align_val_t. + if (UseAlignedAlloc) { + QualType AlignValT = S.Context.getTypeDeclType(S.getStdAlignValT()); + FrameAlign = + S.BuildCStyleCastExpr(FrameAlign->getExprLoc(), + S.Context.getTrivialTypeSourceInfo(AlignValT), + FrameAlign->getExprLoc(), FrameAlign) + .get(); + } - // Make new call. + auto MakeNewCall = [&](FunctionDecl *Operator, + SmallVector<Expr *, 1> &PlaceArgs, Expr *&Allocate) { + ExprResult NewRef = + S.BuildDeclRefExpr(Operator, Operator->getType(), VK_LValue, Loc); + if (NewRef.isInvalid()) + return false; - ExprResult NewRef = - S.BuildDeclRefExpr(OperatorNew, OperatorNew->getType(), VK_LValue, Loc); - if (NewRef.isInvalid()) - return false; + SmallVector<Expr *, 2> NewArgs(1, FrameSize); + if (Operator->getNumParams() > 1 && + Operator->getParamDecl(1)->getType()->isAlignValT()) + NewArgs.insert(NewArgs.begin() + 1, FrameAlign); + NewArgs.append(PlaceArgs); - SmallVector<Expr *, 2> NewArgs(1, FrameSize); - for (auto Arg : PlacementArgs) - NewArgs.push_back(Arg); + ExprResult NewExpr = + S.BuildCallExpr(S.getCurScope(), NewRef.get(), Loc, NewArgs, Loc); + NewExpr = S.ActOnFinishFullExpr(NewExpr.get(), /*DiscardedValue*/ false); + if (NewExpr.isInvalid()) + return false; + Allocate = NewExpr.get(); + return true; + }; - ExprResult NewExpr = - S.BuildCallExpr(S.getCurScope(), NewRef.get(), Loc, NewArgs, Loc); - NewExpr = S.ActOnFinishFullExpr(NewExpr.get(), /*DiscardedValue*/ false); - if (NewExpr.isInvalid()) + if (!MakeNewCall(OperatorNew, PlacementArgs, this->Allocate)) return false; - // Make delete call. - - QualType OpDeleteQualType = OperatorDelete->getType(); + if (UseAlignedAlloc) { + if (!MakeNewCall(AlignedOperatorNew, AlignedPlacementArgs, + this->AlignedAllocate)) + return false; + } else { + this->AlignedAllocate = this->Allocate; + } - ExprResult DeleteRef = - S.BuildDeclRefExpr(OperatorDelete, OpDeleteQualType, VK_LValue, Loc); - if (DeleteRef.isInvalid()) - return false; + // Make delete call. + Expr *FramePtr = + buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_frame, {}); Expr *CoroFree = buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_free, {FramePtr}); - SmallVector<Expr *, 2> DeleteArgs{CoroFree}; + auto MakeDeleteCall = [&](const Sema::UsualDeallocFnInfo &Operator, + Expr *&Deallocate) { + ExprResult DeleteRef = + S.BuildDeclRefExpr(Operator.FD, Operator.FD->getType(), VK_LValue, Loc); + if (DeleteRef.isInvalid()) + return false; - // Check if we need to pass the size. - const auto *OpDeleteType = - OpDeleteQualType.getTypePtr()->castAs<FunctionProtoType>(); - if (OpDeleteType->getNumParams() > 1) - DeleteArgs.push_back(FrameSize); + SmallVector<Expr *, 2> DeleteArgs{CoroFree}; + if (Operator.HasSizeT) + DeleteArgs.push_back(FrameSize); + if (Operator.HasAlignValT) + DeleteArgs.push_back(FrameAlign); + + ExprResult DeleteExpr = + S.BuildCallExpr(S.getCurScope(), DeleteRef.get(), Loc, DeleteArgs, Loc); + DeleteExpr = + S.ActOnFinishFullExpr(DeleteExpr.get(), /*DiscardedValue*/ false); + if (DeleteExpr.isInvalid()) + return false; + Deallocate = DeleteExpr.get(); + return true; + }; - ExprResult DeleteExpr = - S.BuildCallExpr(S.getCurScope(), DeleteRef.get(), Loc, DeleteArgs, Loc); - DeleteExpr = - S.ActOnFinishFullExpr(DeleteExpr.get(), /*DiscardedValue*/ false); - if (DeleteExpr.isInvalid()) + if (!MakeDeleteCall(OperatorDelete, this->Deallocate)) return false; - - this->Allocate = NewExpr.get(); - this->AlignedAllocate = this->Allocate; - this->Deallocate = DeleteExpr.get(); - this->AlignedDeallocate = this->Deallocate; + if (UseAlignedAlloc) { + if (!MakeDeleteCall(AlignedOperatorDelete, this->AlignedDeallocate)) + return false; + } else { + this->AlignedDeallocate = this->Deallocate; + } return true; } Index: clang/lib/Driver/ToolChains/Clang.cpp =================================================================== --- clang/lib/Driver/ToolChains/Clang.cpp +++ clang/lib/Driver/ToolChains/Clang.cpp @@ -5895,6 +5895,12 @@ CmdArgs.push_back("-fcoroutines-ts"); } + if (Args.hasFlag(options::OPT_fcoroutines_aligned_alloc, + options::OPT_fno_coroutines_aligned_alloc, false) && + types::isCXX(InputType)) { + CmdArgs.push_back("-fcoroutines-aligned-alloc"); + } + Args.AddLastArg(CmdArgs, options::OPT_fdouble_square_bracket_attributes, options::OPT_fno_double_square_bracket_attributes); Index: clang/lib/CodeGen/CGBuiltin.cpp =================================================================== --- clang/lib/CodeGen/CGBuiltin.cpp +++ clang/lib/CodeGen/CGBuiltin.cpp @@ -4486,6 +4486,14 @@ return RValue::get(Builder.CreateCall(F)); } + case Builtin::BI__builtin_coro_align: { + auto &Context = getContext(); + auto SizeTy = Context.getSizeType(); + auto *T = Builder.getIntNTy(Context.getTypeSize(SizeTy)); + Function *F = CGM.getIntrinsic(Intrinsic::coro_align, T); + return RValue::get(Builder.CreateCall(F)); + } + case Builtin::BI__builtin_coro_id: return EmitCoroutineIntrinsic(E, Intrinsic::coro_id); case Builtin::BI__builtin_coro_promise: Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -6256,13 +6256,42 @@ void DeclareGlobalAllocationFunction(DeclarationName Name, QualType Return, ArrayRef<QualType> Params); + // CUDA function call preference. Must be ordered numerically from + // worst to best. + enum CUDAFunctionPreference { + CFP_Never, // Invalid caller/callee combination. + CFP_WrongSide, // Calls from host-device to host or device + // function that do not match current compilation + // mode. + CFP_HostDevice, // Any calls to host/device functions. + CFP_SameSide, // Calls from host-device to host or device + // function matching current compilation mode. + CFP_Native, // host-to-host or device-to-device calls. + }; + + struct UsualDeallocFnInfo { + UsualDeallocFnInfo(); + UsualDeallocFnInfo(Sema &S, DeclAccessPair Found); + explicit operator bool() const { return FD; } + bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize, + bool WantAlign) const; + + DeclAccessPair Found; + FunctionDecl *FD; + bool Destroying, HasSizeT, HasAlignValT; + Sema::CUDAFunctionPreference CUDAPref; + }; + + static bool isNonPlacementDeallocationFunction(Sema &S, FunctionDecl *FD); bool FindDeallocationFunction(SourceLocation StartLoc, CXXRecordDecl *RD, - DeclarationName Name, FunctionDecl* &Operator, + DeclarationName Name, FunctionDecl *&Operator, + const bool *WantSize = nullptr, + const bool *WantAlign = nullptr, bool Diagnose = true); - FunctionDecl *FindUsualDeallocationFunction(SourceLocation StartLoc, - bool CanProvideSize, - bool Overaligned, - DeclarationName Name); + UsualDeallocFnInfo FindUsualDeallocationFunction(SourceLocation StartLoc, + bool CanProvideSize, + bool Overaligned, + DeclarationName Name); FunctionDecl *FindDeallocationFunctionForDestructor(SourceLocation StartLoc, CXXRecordDecl *RD); @@ -12146,19 +12175,6 @@ static bool isCUDAImplicitHostDeviceFunction(const FunctionDecl *D); - // CUDA function call preference. Must be ordered numerically from - // worst to best. - enum CUDAFunctionPreference { - CFP_Never, // Invalid caller/callee combination. - CFP_WrongSide, // Calls from host-device to host or device - // function that do not match current compilation - // mode. - CFP_HostDevice, // Any calls to host/device functions. - CFP_SameSide, // Calls from host-device to host or device - // function matching current compilation mode. - CFP_Native, // host-to-host or device-to-device calls. - }; - /// Identifies relative preference of a given Caller/Callee /// combination, based on their host/device attributes. /// \param Caller function which needs address of \p Callee. Index: clang/include/clang/Driver/Options.td =================================================================== --- clang/include/clang/Driver/Options.td +++ clang/include/clang/Driver/Options.td @@ -1105,6 +1105,10 @@ LangOpts<"Coroutines">, Default<cpp20.KeyPath>, PosFlag<SetTrue, [CC1Option], "Enable support for the C++ Coroutines TS">, NegFlag<SetFalse>>; +defm coroutines_aligned_alloc : BoolFOption<"coroutines-aligned-alloc", + LangOpts<"CoroutinesAlignedAlloc">, DefaultFalse, + PosFlag<SetTrue, [CC1Option], "Enable support for using aligned allocation/deallocation for the C++ Coroutines TS">, + NegFlag<SetFalse>>; def fembed_bitcode_EQ : Joined<["-"], "fembed-bitcode=">, Group<f_Group>, Flags<[NoXarchOption, CC1Option, CC1AsOption]>, MetaVarName<"<option>">, Index: clang/include/clang/Basic/LangOptions.def =================================================================== --- clang/include/clang/Basic/LangOptions.def +++ clang/include/clang/Basic/LangOptions.def @@ -145,6 +145,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(CoroutinesAlignedAlloc, 1, 0, "C++20 coroutines aligned allocation") LANGOPT(DllExportInlines , 1, 1, "dllexported classes dllexport inline methods") LANGOPT(RelaxedTemplateTemplateArgs, 1, 0, "C++17 relaxed matching of template template arguments") Index: clang/include/clang/Basic/Builtins.def =================================================================== --- clang/include/clang/Basic/Builtins.def +++ clang/include/clang/Basic/Builtins.def @@ -1583,6 +1583,7 @@ BUILTIN(__builtin_coro_promise, "v*v*IiIb", "n") BUILTIN(__builtin_coro_size, "z", "n") +BUILTIN(__builtin_coro_align, "z", "n") BUILTIN(__builtin_coro_frame, "v*", "n") BUILTIN(__builtin_coro_noop, "v*", "n") BUILTIN(__builtin_coro_free, "v*v*", "n") Index: clang/docs/ClangCommandLineReference.rst =================================================================== --- clang/docs/ClangCommandLineReference.rst +++ clang/docs/ClangCommandLineReference.rst @@ -1418,6 +1418,10 @@ Enable support for the C++ Coroutines TS +.. option:: -fcoroutines-aligned-alloc, -fno-coroutines-aligned-alloc + +Enable support for using aligned allocation/deallocation for the C++ Coroutines TS + .. option:: -fcoverage-mapping, -fno-coverage-mapping Generate coverage mapping to enable code coverage analysis
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits