Index: clang/include/clang/AST/StmtCXX.h =================================================================== --- clang/include/clang/AST/StmtCXX.h +++ clang/include/clang/AST/StmtCXX.h @@ -333,6 +333,7 @@ FirstParamMove ///< First offset for move construction of parameter copies. }; unsigned NumParams; + bool MayThrow; friend class ASTStmtReader; friend class ASTReader; @@ -358,6 +359,7 @@ Stmt *ReturnStmt = nullptr; Stmt *ReturnStmtOnAllocFailure = nullptr; ArrayRef ParamMoves; + bool MayThrow = true; }; private: @@ -437,6 +439,19 @@ NumParams); } + /// Helper class to determine if this is possible to throw an exception. + /// Note that the coroutine body would be wrapped by try-catch statement. + /// So the coroutine wouldn't throw if all of following are true: + /// - Promise Constructor. + // - Parameter Copy/Move Constructor. + // - Memory allocator + // - promise-type::get_return_object_on_allocation_failure(). + // - promise.get_return_object(). + // - promise.initial_suspend(), init_awaiter.await_ready() and + // init_awaiter.await_suspend(). + // - promise.unhandled_exception() + bool mayThrow() const { return MayThrow; } + static bool classof(const Stmt *T) { return T->getStmtClass() == CoroutineBodyStmtClass; } Index: clang/lib/AST/StmtCXX.cpp =================================================================== --- clang/lib/AST/StmtCXX.cpp +++ clang/lib/AST/StmtCXX.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "clang/AST/StmtCXX.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/ASTContext.h" @@ -107,7 +108,8 @@ } CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args) - : Stmt(CoroutineBodyStmtClass), NumParams(Args.ParamMoves.size()) { + : Stmt(CoroutineBodyStmtClass), NumParams(Args.ParamMoves.size()), + MayThrow(Args.MayThrow) { Stmt **SubStmts = getStoredStmts(); SubStmts[CoroutineBodyStmt::Body] = Args.Body; SubStmts[CoroutineBodyStmt::Promise] = Args.Promise; @@ -124,4 +126,4 @@ Args.ReturnStmtOnAllocFailure; std::copy(Args.ParamMoves.begin(), Args.ParamMoves.end(), const_cast(getParamMoves().data())); -} +} \ No newline at end of file Index: clang/lib/CodeGen/CGCoroutine.cpp =================================================================== --- clang/lib/CodeGen/CGCoroutine.cpp +++ clang/lib/CodeGen/CGCoroutine.cpp @@ -541,6 +541,38 @@ } void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) { + // A coroutine would generate following codes: + // ``` + // allocate memories; + // if (allocation fails) + // return promise-type::get_return_object_on_allocation_failure(); + // promise-type promise promise-constructor-arguments ; + // auto GRO = promise.get_return_object(); + // auto init_awaiter = promise.initial_suspend(); + // init_awaiter.await_ready(); + // init_awaiter.await_suspend(promise); + // try { + // init_awaiter.await_resume(); + // function-body + // } catch (...) { + // promise.unhandled_exception(); + // } + // co_await promise.final_suspend(); + // ``` + // promise_type::final_suspend is guranteed to be noexcept. + // So we could mark the coroutine as no-throw if all the followings are + // no-throw: + // - Promise Constructor. + // - Parameter Copy/Move Constructor. + // - Memory allocator + // - promise-type::get_return_object_on_allocation_failure(). + // - promise.get_return_object(). + // - promise.initial_suspend(), init_awaiter.await_ready() and + // init_awaiter.await_suspend(). + // - promise.unhandled_exception() + if (!S.mayThrow()) + CurFn->addFnAttr(llvm::Attribute::NoUnwind); + auto *NullPtr = llvm::ConstantPointerNull::get(Builder.getInt8PtrTy()); auto &TI = CGM.getContext().getTargetInfo(); unsigned NewAlign = TI.getNewAlign() / TI.getCharWidth(); Index: clang/lib/Sema/CoroutineStmtBuilder.h =================================================================== --- clang/lib/Sema/CoroutineStmtBuilder.h +++ clang/lib/Sema/CoroutineStmtBuilder.h @@ -50,6 +50,10 @@ /// name lookup. bool buildDependentStatements(); + /// Calculating if the coroutine may throw an exception. + /// See CoroutineBodyStmt::mayThrow for details. + void buildMayThrow(); + bool isInvalid() const { return !this->IsValid; } private: @@ -61,6 +65,7 @@ bool makeReturnObject(); bool makeGroDeclAndReturnStmt(); bool makeReturnOnAllocFailure(); + bool isMayThrow(); }; } // end namespace clang Index: clang/lib/Sema/SemaCoroutine.cpp =================================================================== --- clang/lib/Sema/SemaCoroutine.cpp +++ clang/lib/Sema/SemaCoroutine.cpp @@ -1080,6 +1080,7 @@ if (Builder.isInvalid() || !Builder.buildStatements()) return FD->setInvalidDecl(); + Builder.buildMayThrow(); // Build body for the coroutine wrapper statement. Body = CoroutineBodyStmt::Create(Context, Builder); } @@ -1589,6 +1590,55 @@ return true; } +void CoroutineStmtBuilder::buildMayThrow() { + if (this->Promise) + this->MayThrow = isMayThrow(); +} + +bool CoroutineStmtBuilder::isMayThrow() { + if (S.canThrow(this->Promise)) + return true; + + // For Initial Suspend, the await_resume part is in the try-catch + // with the remaining is out of the try-catch. + // So we choose not to check the await_resume. + auto *InitSuspendExpr = + isa(this->InitialSuspend) + ? dyn_cast( + cast(this->InitialSuspend)->getSubExpr()) + : dyn_cast(this->InitialSuspend); + + // Unimaged pattern. Return true conservatively. + if (!InitSuspendExpr) + return true; + + if (S.canThrow(InitSuspendExpr->getCommonExpr()) || + S.canThrow(InitSuspendExpr->getReadyExpr()) || + S.canThrow(InitSuspendExpr->getSuspendExpr())) + return true; + + if (this->OnException && S.canThrow(this->OnException)) + return true; + + if (S.canThrow(this->Allocate) || S.canThrow(this->ReturnValue)) + + if (this->ResultDecl) { + if (S.canThrow(this->ResultDecl) || S.canThrow(this->ReturnStmt)) + return true; + + if (this->ReturnStmtOnAllocFailure && + S.canThrow(this->ReturnStmtOnAllocFailure)) + return true; + } + + if (llvm::any_of(this->ParamMoves, [&](const Stmt *ParamMove) { + return S.canThrow(ParamMove); + })) + return true; + + return false; +} + // Create a static_cast\(expr). static Expr *castForMoving(Sema &S, Expr *E, QualType T = QualType()) { if (T.isNull()) Index: clang/lib/Sema/TreeTransform.h =================================================================== --- clang/lib/Sema/TreeTransform.h +++ clang/lib/Sema/TreeTransform.h @@ -7849,6 +7849,7 @@ } } + Builder.buildMayThrow(); return getDerived().RebuildCoroutineBodyStmt(Builder); } Index: clang/test/CodeGenCoroutines/coro-nounwind.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-nounwind.cpp @@ -0,0 +1,376 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm %s -o - -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LINUX %s + +#include "Inputs/coroutine.h" + +namespace coro = std::experimental::coroutines_v1; + +namespace std { + +struct nothrow_t {}; +constexpr nothrow_t nothrow = {}; + +} // end namespace std + +// Required when get_return_object_on_allocation_failure() is defined by +// the promise. +void *operator new(__SIZE_TYPE__ __sz, const std::nothrow_t &) noexcept; +void operator delete(void *__p, const std::nothrow_t &)noexcept; + +struct coro_nothrow_t { + struct promise_type { + coro_nothrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_nothrow_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?foo@@YA?AUcoro_nothrow_t{{.*}}#[[FOOATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3foov{{.*}}#[[FOOATTRNUM:[0-9]+]] + +coro_nothrow_t foo() { + co_return; +} + +struct coro_ctor_maythrow_t { + struct promise_type { + coro_ctor_maythrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_ctor_maythrow_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + promise_type(); + }; +}; + +// CHECK: define dso_local i8 @"?bar@@YA?AUcoro_ctor_maythrow_t{{.*}}#[[BARATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3barv() #[[BARATTRNUM:[0-9]+]] +coro_ctor_maythrow_t bar() { + co_return; +} + +struct coro_onexception_maythrow_t { + struct promise_type { + coro_onexception_maythrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_onexception_maythrow_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception(); + }; +}; + +// CHECK: define dso_local i8 @"?baz@@YA?AUcoro_onexception_maythrow_t{{.*}}#[[BAZATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3bazv() #[[BAZATTRNUM:[0-9]+]] +coro_onexception_maythrow_t baz() { + co_return; +} + +struct coro_initsuspend_maythrow_t { + struct promise_type { + coro_initsuspend_maythrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_initsuspend_maythrow_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f1@@YA?AUcoro_initsuspend_maythrow_t{{.*}}#[[F1ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z2f1v() #[[F1ATTRNUM:[0-9]+]] +coro_initsuspend_maythrow_t f1() { + co_return; +} + +struct coro_initsuspend_maythrow_t2 { + struct promise_type { + coro_initsuspend_maythrow_t2 get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_initsuspend_maythrow_t2 get_return_object_on_allocation_failure() noexcept; + struct init_awaiter { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; + }; + init_awaiter initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f2@@YA?AUcoro_initsuspend_maythrow_t2{{.*}}#[[F2ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z2f2v() #[[F2ATTRNUM:[0-9]+]] +coro_initsuspend_maythrow_t2 f2() { + co_return; +} + +struct coro_initsuspend_maythrow_t3 { + struct promise_type { + coro_initsuspend_maythrow_t3 get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_initsuspend_maythrow_t3 get_return_object_on_allocation_failure() noexcept; + struct init_awaiter { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume() noexcept; + }; + init_awaiter initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f3@@YA?AUcoro_initsuspend_maythrow_t3{{.*}}#[[F3ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z2f3v() #[[F3ATTRNUM:[0-9]+]] +coro_initsuspend_maythrow_t3 f3() { + co_return; +} + +struct coro_initsuspend_maythrow_t4 { + struct promise_type { + coro_initsuspend_maythrow_t4 get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_initsuspend_maythrow_t4 get_return_object_on_allocation_failure() noexcept; + struct init_awaiter { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume(); + }; + init_awaiter initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f4@@YA?AUcoro_initsuspend_maythrow_t4{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z2f4v() #[[FOOATTRNUM]] +coro_initsuspend_maythrow_t4 f4() { + co_return; +} + +class NoCopyMove { +}; + +// CHECK: define dso_local i8 @"?f5@@YA?AUcoro_nothrow_t{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z2f5{{.*}} #[[FOOATTRNUM]] +coro_nothrow_t f5(NoCopyMove) { + co_return; +} + +class MoveMayThrow { +public: + MoveMayThrow(MoveMayThrow &&); +}; + +// CHECK: define dso_local i8 @"?f6@@YA?AUcoro_nothrow_t{{.*}}#[[F6ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z2f6{{.*}} #[[F6ATTRNUM:[0-9]+]] +coro_nothrow_t f6(MoveMayThrow) { + co_return; +} + +class MoveNoThrow { +public: + MoveNoThrow(MoveNoThrow &&) noexcept; +}; + +// CHECK: define dso_local i8 @"?f7@@YA?AUcoro_nothrow_t{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z2f7{{.*}} #[[FOOATTRNUM]] +coro_nothrow_t f7(MoveNoThrow) { + co_return; +} + +class CopyMayThrow { +public: + CopyMayThrow(CopyMayThrow &&); +}; + +// CHECK: define dso_local i8 @"?f8@@YA?AUcoro_nothrow_t{{.*}}#[[F8ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z2f8{{.*}} #[[F8ATTRNUM:[0-9]+]] +coro_nothrow_t f8(CopyMayThrow) { + co_return; +} + +class CopyNoThrow { +public: + CopyNoThrow(CopyNoThrow &&) noexcept; +}; + +// CHECK: define dso_local i8 @"?f9@@YA?AUcoro_nothrow_t{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z2f9{{.*}} #[[FOOATTRNUM]] +coro_nothrow_t f9(CopyNoThrow) { + co_return; +} + +struct coro_gro_throw_t { + struct promise_type { + coro_gro_throw_t get_return_object() { + coro::coroutine_handle{}; + return {}; + } + static coro_gro_throw_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f10@@YA?AUcoro_gro_throw_t{{.*}}#[[F10ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3f10v{{.*}}#[[F10ATTRNUM:[0-9]+]] + +coro_gro_throw_t f10() { + co_return; +} + +struct coro_grooaf_throw_t { + struct promise_type { + coro_grooaf_throw_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_grooaf_throw_t get_return_object_on_allocation_failure(); + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f11@@YA?AUcoro_grooaf_throw_t{{.*}}#[[F11ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3f11v{{.*}}#[[F11ATTRNUM:[0-9]+]] + +coro_grooaf_throw_t f11() { + co_return; +} + +struct coro_grooaf_nothrow_t { + struct promise_type { + coro_grooaf_nothrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + static coro_grooaf_nothrow_t get_return_object_on_allocation_failure() noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f12@@YA?AUcoro_grooaf_nothrow_t{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z3f12v{{.*}}#[[FOOATTRNUM]] + +coro_grooaf_nothrow_t f12() { + co_return; +} + +struct coro_alloc_throw_t { + struct promise_type { + coro_alloc_throw_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f13@@YA?AUcoro_alloc_throw_t{{.*}}#[[F13ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3f13v{{.*}}#[[F13ATTRNUM:[0-9]+]] +coro_alloc_throw_t f13() { + co_return; +} + +struct coro_alloc_throw_t2 { + struct promise_type { + coro_alloc_throw_t2 get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + void *operator new(__SIZE_TYPE__); + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f14@@YA?AUcoro_alloc_throw_t2{{.*}}#[[F14ATTRNUM:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3f14v{{.*}}#[[F14ATTRNUM:[0-9]+]] +coro_alloc_throw_t2 f14() { + co_return; +} + +struct coro_alloc_nothrow_t { + struct promise_type { + coro_alloc_nothrow_t get_return_object() noexcept { + coro::coroutine_handle{}; + return {}; + } + void *operator new(__SIZE_TYPE__) noexcept; + coro::suspend_never initial_suspend() noexcept { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +// CHECK: define dso_local i8 @"?f15@@YA?AUcoro_alloc_nothrow_t{{.*}}#[[FOOATTRNUM]] +// CHECK-LINUX: define dso_local void @_Z3f15v{{.*}}#[[FOOATTRNUM]] +coro_alloc_nothrow_t f15() { + co_return; +} + +// CHECK: attributes #[[FOOATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[BARATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[BAZATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F1ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F2ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F3ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F6ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F8ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F10ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F11ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F13ATTRNUM]] = {{.*}}nounwind +// CHECK-NOT: attributes #[[F14ATTRNUM]] = {{.*}}nounwind + +// CHECK-LINUX: attributes #[[FOOATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[BARATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[BAZATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F1ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F2ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F3ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F6ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F8ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F10ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F11ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F13ATTRNUM]] = {{.*}}nounwind +// CHECK-LINUX-NOT: attributes #[[F14ATTRNUM]] = {{.*}}nounwind