From 8df64e940da39899c78493b9b2e330bba5ab48ee Mon Sep 17 00:00:00 2001 From: Gor Nishanov Date: Thu, 27 Oct 2016 16:28:31 +0000 Subject: [PATCH] [coroutines] Add allocation and deallocation substatements. Summary: SemaCoroutine: Add allocation / deallocation substatements. CGCoroutine/Test: Emit allocation and deallocation + test. Reviewers: rsmith Subscribers: ABataev, EricWF, llvm-commits, mehdi_amini Differential Revision: https://reviews.llvm.org/D25879 llvm-svn: 285306 --- clang/include/clang/AST/StmtCXX.h | 8 ++ clang/lib/CodeGen/CGCoroutine.cpp | 24 ++++- clang/lib/CodeGen/CGStmt.cpp | 2 + clang/lib/CodeGen/CodeGenFunction.h | 1 + clang/lib/Sema/SemaCoroutine.cpp | 155 ++++++++++++++++++++++++++-- clang/test/CodeGenCoroutines/coro-alloc.cpp | 114 ++++++++++++++++++++ 6 files changed, 289 insertions(+), 15 deletions(-) create mode 100644 clang/test/CodeGenCoroutines/coro-alloc.cpp diff --git a/clang/include/clang/AST/StmtCXX.h b/clang/include/clang/AST/StmtCXX.h index d1a91cc..8eef34c 100644 --- a/clang/include/clang/AST/StmtCXX.h +++ b/clang/include/clang/AST/StmtCXX.h @@ -304,6 +304,8 @@ class CoroutineBodyStmt : public Stmt { FinalSuspend, ///< The final suspend statement, run after the body. OnException, ///< Handler for exceptions thrown in the body. OnFallthrough, ///< Handler for control flow falling off the body. + Allocate, ///< Coroutine frame memory allocation. + Deallocate, ///< Coroutine frame memory deallocation. ReturnValue, ///< Return value for thunk function. FirstParamMove ///< First offset for move construction of parameter copies. }; @@ -313,6 +315,7 @@ class CoroutineBodyStmt : public Stmt { public: CoroutineBodyStmt(Stmt *Body, Stmt *Promise, Stmt *InitSuspend, Stmt *FinalSuspend, Stmt *OnException, Stmt *OnFallthrough, + Expr *Allocate, Stmt *Deallocate, Expr *ReturnValue, ArrayRef ParamMoves) : Stmt(CoroutineBodyStmtClass) { SubStmts[CoroutineBodyStmt::Body] = Body; @@ -321,6 +324,8 @@ public: SubStmts[CoroutineBodyStmt::FinalSuspend] = FinalSuspend; SubStmts[CoroutineBodyStmt::OnException] = OnException; SubStmts[CoroutineBodyStmt::OnFallthrough] = OnFallthrough; + SubStmts[CoroutineBodyStmt::Allocate] = Allocate; + SubStmts[CoroutineBodyStmt::Deallocate] = Deallocate; SubStmts[CoroutineBodyStmt::ReturnValue] = ReturnValue; // FIXME: Tail-allocate space for parameter move expressions and store them. assert(ParamMoves.empty() && "not implemented yet"); @@ -345,6 +350,9 @@ public: return SubStmts[SubStmt::OnFallthrough]; } + Expr *getAllocate() const { return cast(SubStmts[SubStmt::Allocate]); } + Stmt *getDeallocate() const { return SubStmts[SubStmt::Deallocate]; } + Expr *getReturnValueInit() const { return cast(SubStmts[SubStmt::ReturnValue]); } diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp index 2f5e8ab..2fdb127 100644 --- a/clang/lib/CodeGen/CGCoroutine.cpp +++ b/clang/lib/CodeGen/CGCoroutine.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "CodeGenFunction.h" +#include "clang/AST/StmtCXX.h" using namespace clang; using namespace CodeGen; @@ -36,9 +37,10 @@ struct CGCoroData { clang::CodeGen::CodeGenFunction::CGCoroInfo::CGCoroInfo() {} CodeGenFunction::CGCoroInfo::~CGCoroInfo() {} -static bool createCoroData(CodeGenFunction &CGF, +static void createCoroData(CodeGenFunction &CGF, CodeGenFunction::CGCoroInfo &CurCoro, - llvm::CallInst *CoroId, CallExpr const *CoroIdExpr) { + llvm::CallInst *CoroId, + CallExpr const *CoroIdExpr = nullptr) { if (CurCoro.Data) { if (CurCoro.Data->CoroIdExpr) CGF.CGM.Error(CoroIdExpr->getLocStart(), @@ -49,13 +51,27 @@ static bool createCoroData(CodeGenFunction &CGF, else llvm_unreachable("EmitCoroutineBodyStatement called twice?"); - return false; + return; } CurCoro.Data = std::unique_ptr(new CGCoroData); CurCoro.Data->CoroId = CoroId; CurCoro.Data->CoroIdExpr = CoroIdExpr; - return true; +} + +void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) { + auto *NullPtr = llvm::ConstantPointerNull::get(Builder.getInt8PtrTy()); + auto &TI = CGM.getContext().getTargetInfo(); + unsigned NewAlign = TI.getNewAlign() / TI.getCharWidth(); + + auto *CoroId = Builder.CreateCall( + CGM.getIntrinsic(llvm::Intrinsic::coro_id), + {Builder.getInt32(NewAlign), NullPtr, NullPtr, NullPtr}); + createCoroData(*this, CurCoro, CoroId); + + EmitScalarExpr(S.getAllocate()); + // FIXME: Emit the rest of the coroutine. + EmitStmt(S.getDeallocate()); } // Emit coroutine intrinsic and patch up arguments of the token type. diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index b4f3093..770fc96 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -142,6 +142,8 @@ void CodeGenFunction::EmitStmt(const Stmt *S) { case Stmt::GCCAsmStmtClass: // Intentional fall-through. case Stmt::MSAsmStmtClass: EmitAsmStmt(cast(*S)); break; case Stmt::CoroutineBodyStmtClass: + EmitCoroutineBody(cast(*S)); + break; case Stmt::CoreturnStmtClass: CGM.ErrorUnsupported(S, "coroutine"); break; diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 9e9b5c7..dade461 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -2311,6 +2311,7 @@ public: void EmitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt &S); void EmitObjCAutoreleasePoolStmt(const ObjCAutoreleasePoolStmt &S); + void EmitCoroutineBody(const CoroutineBodyStmt &S); RValue EmitCoroutineIntrinsic(const CallExpr *E, unsigned int IID); void EnterCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock = false); diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 5231e23..873a830 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -78,7 +78,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionProtoType *FnType, auto *Promise = R.getAsSingle(); if (!Promise) { S.Diag(Loc, diag::err_implied_std_coroutine_traits_promise_type_not_found) - << RD; + << RD; return QualType(); } @@ -92,7 +92,7 @@ static QualType lookupPromiseType(Sema &S, const FunctionProtoType *FnType, PromiseType = S.Context.getElaboratedType(ETK_None, NNS, PromiseType); S.Diag(Loc, diag::err_implied_std_coroutine_traits_promise_type_not_class) - << PromiseType; + << PromiseType; return QualType(); } @@ -121,7 +121,7 @@ checkCoroutineContext(Sema &S, SourceLocation Loc, StringRef Keyword) { // // FIXME: We assume that this really means that a coroutine cannot // be a constructor or destructor. - S.Diag(Loc, diag::err_coroutine_ctor_dtor) + S.Diag(Loc, diag::err_coroutine_ctor_dtor) << isa(FD) << Keyword; } else if (FD->isConstexpr()) { S.Diag(Loc, diag::err_coroutine_constexpr) << Keyword; @@ -130,8 +130,8 @@ checkCoroutineContext(Sema &S, SourceLocation Loc, StringRef Keyword) { } else if (FD->isMain()) { S.Diag(FD->getLocStart(), diag::err_coroutine_main); S.Diag(Loc, diag::note_declared_coroutine_here) - << (Keyword == "co_await" ? 0 : - Keyword == "co_yield" ? 1 : 2); + << (Keyword == "co_await" ? 0 : + Keyword == "co_yield" ? 1 : 2); } else { auto *ScopeInfo = S.getCurFunction(); assert(ScopeInfo && "missing function scope for function"); @@ -162,6 +162,26 @@ checkCoroutineContext(Sema &S, SourceLocation Loc, StringRef Keyword) { return nullptr; } +static Expr *buildBuiltinCall(Sema &S, SourceLocation Loc, Builtin::ID Id, + MutableArrayRef CallArgs) { + StringRef Name = S.Context.BuiltinInfo.getName(Id); + LookupResult R(S, &S.Context.Idents.get(Name), Loc, Sema::LookupOrdinaryName); + S.LookupName(R, S.TUScope, /*AllowBuiltinCreation=*/true); + + auto *BuiltInDecl = R.getAsSingle(); + assert(BuiltInDecl && "failed to find builtin declaration"); + + ExprResult DeclRef = + S.BuildDeclRefExpr(BuiltInDecl, BuiltInDecl->getType(), VK_LValue, Loc); + assert(DeclRef.isUsable() && "Builtin reference cannot fail"); + + ExprResult Call = + S.ActOnCallExpr(/*Scope=*/nullptr, DeclRef.get(), Loc, CallArgs, Loc); + + assert(!Call.isInvalid() && "Call to builtin cannot fail!"); + return Call.get(); +} + /// Build a call to 'operator co_await' if there is a suitable operator for /// the given expression. static ExprResult buildOperatorCoawaitCall(Sema &SemaRef, Scope *S, @@ -204,7 +224,7 @@ static ReadySuspendResumeResult buildCoawaitCalls(Sema &S, SourceLocation Loc, const StringRef Funcs[] = {"await_ready", "await_suspend", "await_resume"}; for (size_t I = 0, N = llvm::array_lengthof(Funcs); I != N; ++I) { Expr *Operand = new (S.Context) OpaqueValueExpr( - Loc, E->getType(), VK_LValue, E->getObjectKind(), E); + Loc, E->getType(), VK_LValue, E->getObjectKind(), E); // FIXME: Pass coroutine handle to await_suspend. ExprResult Result = buildMemberCall(S, Operand, Loc, Funcs[I], None); @@ -406,6 +426,113 @@ static ExprResult buildStdCurrentExceptionCall(Sema &S, SourceLocation Loc) { return Res; } +// Find an appropriate delete for the promise. +static FunctionDecl *findDeleteForPromise(Sema &S, SourceLocation Loc, + QualType PromiseType) { + FunctionDecl *OperatorDelete = nullptr; + + DeclarationName DeleteName = + S.Context.DeclarationNames.getCXXOperatorName(OO_Delete); + + auto *PointeeRD = PromiseType->getAsCXXRecordDecl(); + assert(PointeeRD && "PromiseType must be a CxxRecordDecl type"); + + if (S.FindDeallocationFunction(Loc, PointeeRD, DeleteName, OperatorDelete)) + return nullptr; + + 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; +} + +// Builds allocation and deallocation for the coroutine. Returns false on +// failure. +static bool buildAllocationAndDeallocation(Sema &S, SourceLocation Loc, + FunctionScopeInfo *Fn, + Expr *&Allocation, + Stmt *&Deallocation) { + TypeSourceInfo *TInfo = Fn->CoroutinePromise->getTypeSourceInfo(); + QualType PromiseType = TInfo->getType(); + if (PromiseType->isDependentType()) + return true; + + if (S.RequireCompleteType(Loc, PromiseType, diag::err_incomplete_type)) + return false; + + // FIXME: Add support for get_return_object_on_allocation failure. + // FIXME: Add support for stateful allocators. + + FunctionDecl *OperatorNew = nullptr; + FunctionDecl *OperatorDelete = nullptr; + FunctionDecl *UnusedResult = nullptr; + bool PassAlignment = false; + + S.FindAllocationFunctions(Loc, SourceRange(), + /*UseGlobal*/ false, PromiseType, + /*isArray*/ false, PassAlignment, + /*PlacementArgs*/ None, OperatorNew, UnusedResult); + + OperatorDelete = findDeleteForPromise(S, Loc, PromiseType); + + if (!OperatorDelete || !OperatorNew) + return false; + + Expr *FramePtr = + buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_frame, {}); + + Expr *FrameSize = + buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_size, {}); + + // Make new call. + + ExprResult NewRef = + S.BuildDeclRefExpr(OperatorNew, OperatorNew->getType(), VK_LValue, Loc); + if (NewRef.isInvalid()) + return false; + + ExprResult NewExpr = + S.ActOnCallExpr(S.getCurScope(), NewRef.get(), Loc, FrameSize, Loc); + if (NewExpr.isInvalid()) + return false; + + Allocation = NewExpr.get(); + + // Make delete call. + + QualType OpDeleteQualType = OperatorDelete->getType(); + + ExprResult DeleteRef = + S.BuildDeclRefExpr(OperatorDelete, OpDeleteQualType, VK_LValue, Loc); + if (DeleteRef.isInvalid()) + return false; + + Expr *CoroFree = + buildBuiltinCall(S, Loc, Builtin::BI__builtin_coro_free, {FramePtr}); + + SmallVector DeleteArgs{CoroFree}; + + // Check if we need to pass the size. + const auto *OpDeleteType = + OpDeleteQualType.getTypePtr()->getAs(); + if (OpDeleteType->getNumParams() > 1) + DeleteArgs.push_back(FrameSize); + + ExprResult DeleteExpr = + S.ActOnCallExpr(S.getCurScope(), DeleteRef.get(), Loc, DeleteArgs, Loc); + if (DeleteExpr.isInvalid()) + return false; + + Deallocation = DeleteExpr.get(); + + return true; +} + void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { FunctionScopeInfo *Fn = getCurFunction(); assert(Fn && !Fn->CoroutineStmts.empty() && "not a coroutine"); @@ -416,8 +543,8 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { Diag(Fn->FirstReturnLoc, diag::err_return_in_coroutine); auto *First = Fn->CoroutineStmts[0]; Diag(First->getLocStart(), diag::note_declared_coroutine_here) - << (isa(First) ? 0 : - isa(First) ? 1 : 2); + << (isa(First) ? 0 : + isa(First) ? 1 : 2); } bool AnyCoawaits = false; @@ -460,7 +587,12 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { if (FinalSuspend.isInvalid()) return FD->setInvalidDecl(); - // Try to form 'p.return_void();' expression statement to handle + // Form and check allocation and deallocation calls. + Expr *Allocation = nullptr; + Stmt *Deallocation = nullptr; + if (!buildAllocationAndDeallocation(*this, Loc, Fn, Allocation, Deallocation)) + return FD->setInvalidDecl(); + // control flowing off the end of the coroutine. // Also try to form 'p.set_exception(std::current_exception());' to handle // uncaught exceptions. @@ -517,7 +649,7 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { // Build implicit 'p.get_return_object()' expression and form initialization // of return type from it. ExprResult ReturnObject = - buildPromiseCall(*this, Fn, Loc, "get_return_object", None); + buildPromiseCall(*this, Fn, Loc, "get_return_object", None); if (ReturnObject.isInvalid()) return FD->setInvalidDecl(); QualType RetType = FD->getReturnType(); @@ -539,5 +671,6 @@ void Sema::CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body) { // Build body for the coroutine wrapper statement. Body = new (Context) CoroutineBodyStmt( Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(), - SetException.get(), Fallthrough.get(), ReturnObject.get(), ParamMoves); + SetException.get(), Fallthrough.get(), Allocation, Deallocation, + ReturnObject.get(), ParamMoves); } diff --git a/clang/test/CodeGenCoroutines/coro-alloc.cpp b/clang/test/CodeGenCoroutines/coro-alloc.cpp new file mode 100644 index 0000000..7dbbdd3 --- /dev/null +++ b/clang/test/CodeGenCoroutines/coro-alloc.cpp @@ -0,0 +1,114 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +namespace std { +namespace experimental { +template +struct coroutine_traits; // expected-note {{declared here}} +} +} + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend() {} + void await_resume() {} +}; + +struct global_new_delete_tag {}; + +template<> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + void return_void() {} + }; +}; + +// CHECK-LABEL: f0( +extern "C" void f0(global_new_delete_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: call void @_ZdlPv(i8* %[[MEM]]) + co_await suspend_always{}; +} + +struct promise_new_tag {}; + +template<> +struct std::experimental::coroutine_traits { + struct promise_type { + void *operator new(unsigned long); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + void return_void() {} + }; +}; + +// CHECK-LABEL: f1( +extern "C" void f1(promise_new_tag ) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv15promise_new_tagEE12promise_typenwEm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: call void @_ZdlPv(i8* %[[MEM]]) + co_await suspend_always{}; +} + +struct promise_delete_tag {}; + +template<> +struct std::experimental::coroutine_traits { + struct promise_type { + void operator delete(void*); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + void return_void() {} + }; +}; + +// CHECK-LABEL: f2( +extern "C" void f2(promise_delete_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) + co_await suspend_always{}; +} + +struct promise_sized_delete_tag {}; + +template<> +struct std::experimental::coroutine_traits { + struct promise_type { + void operator delete(void*, unsigned long); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() { return {}; } + void return_void() {} + }; +}; + +// CHECK-LABEL: f3( +extern "C" void f3(promise_sized_delete_tag) { + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call i8* @_Znwm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.frame() + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: %[[SIZE2:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv24promise_sized_delete_tagEE12promise_typedlEPvm(i8* %[[MEM]], i64 %[[SIZE2]]) + co_await suspend_always{}; +} -- 2.7.4