Index: clang/include/clang/AST/Expr.h =================================================================== --- clang/include/clang/AST/Expr.h +++ clang/include/clang/AST/Expr.h @@ -672,6 +672,9 @@ /// Determine whether this expression involves a call to any function /// that is not trivial. bool hasNonTrivialCall(const ASTContext &Ctx) const; + /// Determine whether this expression involves a call to any function + /// that is possible to throw. + bool hasMayExceptCall(const ASTContext &Ctx) const; /// EvaluateKnownConstInt - Call EvaluateAsRValue and return the folded /// integer. This must be called on an expression that constant folds to an Index: clang/include/clang/AST/StmtCXX.h =================================================================== --- clang/include/clang/AST/StmtCXX.h +++ clang/include/clang/AST/StmtCXX.h @@ -386,6 +386,26 @@ return cast(cast(getPromiseDeclStmt())->getSingleDecl()); } + /// Return true if the constructor of the promise wouldn't throw, + /// return false otherwise. + /// + /// These is*Nothrow functions are helpers to detect if there is + /// possible throw from the main try-catch. + bool isPromiseCtorNothrow(const ASTContext &Ctx) const; + /// Return true if the promise_type::unhandled_exception exists and it is + /// nothrow, return false otherwise. + bool isOnExceptionNothrow(const ASTContext &Ctx) const; + /// Return true if the promise_type::initial_suspend() and corresponding + /// await_ready and await_suspend are noexcept. Return false otherwise. + bool isInitSuspendExceptAwaitResumeNothrow(const ASTContext &Ctx) const; + /// Return true if all the parameter moves statements wouldn't throw. + /// Return false otherwise. + bool isParamMovesNothrow(const ASTContext &Ctx) const; + /// Return true if promise.get_return_object() and + /// promise::get_return_object_on_allocation_failure wouldn't throw. + /// Return false otherwise. + bool isGRONothrow(const ASTContext &Ctx) const; + Stmt *getInitSuspendStmt() const { return getStoredStmts()[SubStmt::InitSuspend]; } Index: clang/lib/AST/Expr.cpp =================================================================== --- clang/lib/AST/Expr.cpp +++ clang/lib/AST/Expr.cpp @@ -3741,6 +3741,44 @@ return Finder.hasNonTrivialCall(); } +namespace { +/// Look for a call to a may-throw function within an expression. +class MayThrowCallFinder + : public ConstEvaluatedExprVisitor { + typedef ConstEvaluatedExprVisitor Inherited; + + bool MayThrow; + +public: + explicit MayThrowCallFinder(const ASTContext &Context) + : Inherited(Context), MayThrow(false) {} + + bool hasMayThrowCall() const { return MayThrow; } + + void VisitCallExpr(const CallExpr *E) { + auto *Callee = E->getDirectCallee(); + if (!Callee) { + // take indirect call as may-throw conservatively. + MayThrow = true; + return; + } + if (!isNoexceptExceptionSpec(Callee->getExceptionSpecType())) + MayThrow = true; + } + + void VisitCXXConstructExpr(const CXXConstructExpr *E) { + if (!isNoexceptExceptionSpec(E->getConstructor()->getExceptionSpecType())) + MayThrow = true; + } +}; +} // namespace + +bool Expr::hasMayExceptCall(const ASTContext &Ctx) const { + MayThrowCallFinder Finder(Ctx); + Finder.Visit(this); + return Finder.hasMayThrowCall(); +} + /// isNullPointerConstant - C99 6.3.2.3p3 - Return whether this is a null /// pointer constant or not, as well as the specific kind of constant detected. /// Null pointer constants can be integer constant expressions with the 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" @@ -125,3 +126,57 @@ std::copy(Args.ParamMoves.begin(), Args.ParamMoves.end(), const_cast(getParamMoves().data())); } + +bool CoroutineBodyStmt::isPromiseCtorNothrow(const ASTContext &Ctx) const { + return !getPromiseDecl()->getInit()->hasMayExceptCall(Ctx); +} + +bool CoroutineBodyStmt::isOnExceptionNothrow(const ASTContext &Ctx) const { + Stmt *ExceptionHandler = getExceptionHandler(); + if (!ExceptionHandler) + // If there is no ExceptionHandler, it wouldn't generate the try catch. + return false; + return !cast(ExceptionHandler)->hasMayExceptCall(Ctx); +} + +bool CoroutineBodyStmt::isInitSuspendExceptAwaitResumeNothrow( + const ASTContext &Ctx) const { + auto *InitSuspend = + isa(getInitSuspendStmt()) + ? dyn_cast( + cast(getInitSuspendStmt())->getSubExpr()) + : dyn_cast(getInitSuspendStmt()); + + if (!InitSuspend) + // Unimaged pattern. Return false conservatively. + return false; + + return !InitSuspend->getCommonExpr()->hasMayExceptCall(Ctx) && + !InitSuspend->getReadyExpr()->hasMayExceptCall(Ctx) && + !InitSuspend->getSuspendExpr()->hasMayExceptCall(Ctx); +} + +bool CoroutineBodyStmt::isParamMovesNothrow(const ASTContext &Ctx) const { + return llvm::all_of(getParamMoves(), [&](const Stmt *Move) { + if (!isa(Move)) + return false; + + return llvm::all_of(cast(Move)->decls(), [&](const Decl *D) { + if (!isa(D)) + return false; + const Expr *Init = cast(D)->getInit(); + return !Init || !Init->hasMayExceptCall(Ctx); + }); + }); +} + +bool CoroutineBodyStmt::isGRONothrow(const ASTContext &Ctx) const { + if (getReturnValueInit()->hasMayExceptCall(Ctx)) + return false; + if (auto *S = getReturnStmtOnAllocFailure()) { + assert(isa(S) && + "ReturnStmtOnAllocFailure is not a ReturnStmt!"); + return !cast(S)->getRetValue()->hasMayExceptCall(Ctx); + } + return true; +} \ No newline at end of file Index: clang/lib/CodeGen/CGCoroutine.cpp =================================================================== --- clang/lib/CodeGen/CGCoroutine.cpp +++ clang/lib/CodeGen/CGCoroutine.cpp @@ -541,8 +541,42 @@ } void CodeGenFunction::EmitCoroutineBody(const CoroutineBodyStmt &S) { + ASTContext &C = getContext(); + + // A coroutine would generate following codes: + // ``` + // 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. + // - 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.isPromiseCtorNothrow(C) && S.isOnExceptionNothrow(C) && + S.isInitSuspendExceptAwaitResumeNothrow(C) && S.isParamMovesNothrow(C) && + S.isGRONothrow(C)) + CurFn->addFnAttr(llvm::Attribute::NoUnwind); + auto *NullPtr = llvm::ConstantPointerNull::get(Builder.getInt8PtrTy()); - auto &TI = CGM.getContext().getTargetInfo(); + auto &TI = C.getTargetInfo(); unsigned NewAlign = TI.getNewAlign() / TI.getCharWidth(); auto *EntryBB = Builder.GetInsertBlock(); Index: clang/test/CodeGenCoroutines/coro-nounwind.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-nounwind.cpp @@ -0,0 +1,305 @@ +// 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 {}; + } + 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 {}; + } + 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 {}; + } + 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 {}; + } + 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 {}; + } + 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 {}; + } + 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 {}; + } + 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 {}; + } + 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:[0-9]+]] +// CHECK-LINUX: define dso_local void @_Z3f12v{{.*}}#[[FOOATTRNUM:[0-9]+]] + +coro_grooaf_nothrow_t f12() { + 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-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