Index: include/clang/AST/StmtCXX.h =================================================================== --- include/clang/AST/StmtCXX.h +++ include/clang/AST/StmtCXX.h @@ -304,6 +304,8 @@ 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 @@ 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 @@ 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 @@ 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]); } Index: lib/CodeGen/CGCoroutine.cpp =================================================================== --- lib/CodeGen/CGCoroutine.cpp +++ lib/CodeGen/CGCoroutine.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "CodeGenFunction.h" +#include "clang/AST/StmtCXX.h" using namespace clang; using namespace CodeGen; @@ -58,6 +59,24 @@ 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}); + if (!createCoroData(*this, CurCoro, CoroId, nullptr)) { + // User inserted __builtin_coro_id by hand. Should not try to emit anything. + return; + } + + EmitScalarExpr(S.getAllocate()); + // FIXME: Emit the rest of the coroutine. + EmitStmt(S.getDeallocate()); +} + // Emit coroutine intrinsic and patch up arguments of the token type. RValue CodeGenFunction::EmitCoroutineIntrinsic(const CallExpr *E, unsigned int IID) { Index: lib/CodeGen/CGStmt.cpp =================================================================== --- lib/CodeGen/CGStmt.cpp +++ lib/CodeGen/CGStmt.cpp @@ -142,6 +142,8 @@ 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; Index: lib/CodeGen/CodeGenFunction.h =================================================================== --- lib/CodeGen/CodeGenFunction.h +++ lib/CodeGen/CodeGenFunction.h @@ -2309,6 +2309,7 @@ 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); Index: lib/Sema/SemaCoroutine.cpp =================================================================== --- lib/Sema/SemaCoroutine.cpp +++ lib/Sema/SemaCoroutine.cpp @@ -162,6 +162,26 @@ 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, @@ -378,6 +398,114 @@ 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"); @@ -432,6 +560,12 @@ if (FinalSuspend.isInvalid()) return FD->setInvalidDecl(); + // Form and check allocation and deallocation calls. + Expr *Allocation = nullptr; + Stmt *Deallocation = nullptr; + if (!buildAllocationAndDeallocation(*this, Loc, Fn, Allocation, Deallocation)) + return FD->setInvalidDecl(); + // FIXME: Perform analysis of set_exception call. // FIXME: Try to form 'p.return_void();' expression statement to handle @@ -462,6 +596,6 @@ // Build body for the coroutine wrapper statement. Body = new (Context) CoroutineBodyStmt( Body, PromiseStmt.get(), InitialSuspend.get(), FinalSuspend.get(), - /*SetException*/nullptr, /*Fallthrough*/nullptr, + /*SetException*/nullptr, /*Fallthrough*/nullptr, Allocation, Deallocation, ReturnObject.get(), ParamMoves); } Index: test/CodeGenCoroutines/coro-alloc.cpp =================================================================== --- /dev/null +++ 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{}; +}