Index: clang/include/clang/AST/Stmt.h =================================================================== --- clang/include/clang/AST/Stmt.h +++ clang/include/clang/AST/Stmt.h @@ -281,6 +281,15 @@ SourceLocation KeywordLoc; }; + class CoroutineBodyStmtBitfields { + friend class CoroutineBodyStmt; + + unsigned : NumStmtBits; + + unsigned NumParams : 32 - NumStmtBits - 1; + unsigned MayThrow : 1; + }; + //===--- Expression bitfields classes ---===// class ExprBitfields { @@ -1016,6 +1025,7 @@ BreakStmtBitfields BreakStmtBits; ReturnStmtBitfields ReturnStmtBits; SwitchCaseBitfields SwitchCaseBits; + CoroutineBodyStmtBitfields CoroutineBodyBits; // Expressions ExprBitfields ExprBits; Index: clang/include/clang/AST/StmtCXX.h =================================================================== --- clang/include/clang/AST/StmtCXX.h +++ clang/include/clang/AST/StmtCXX.h @@ -332,7 +332,6 @@ ReturnStmtOnAllocFailure, ///< Return statement if allocation failed. FirstParamMove ///< First offset for move construction of parameter copies. }; - unsigned NumParams; friend class ASTStmtReader; friend class ASTReader; @@ -342,6 +341,9 @@ Stmt *const *getStoredStmts() const { return getTrailingObjects(); } + void setNumParams(unsigned Num) { CoroutineBodyBits.NumParams = Num; } + void setMayThrow(bool MayThrow) { CoroutineBodyBits.MayThrow = MayThrow; } + public: struct CtorArgs { @@ -358,6 +360,7 @@ Stmt *ReturnStmt = nullptr; Stmt *ReturnStmtOnAllocFailure = nullptr; ArrayRef ParamMoves; + bool MayThrow = true; }; private: @@ -415,7 +418,8 @@ return getStoredStmts()[SubStmt::ReturnStmtOnAllocFailure]; } ArrayRef getParamMoves() const { - return {getStoredStmts() + SubStmt::FirstParamMove, NumParams}; + return {getStoredStmts() + SubStmt::FirstParamMove, + CoroutineBodyBits.NumParams}; } SourceLocation getBeginLoc() const LLVM_READONLY { @@ -427,15 +431,35 @@ } child_range children() { - return child_range(getStoredStmts(), - getStoredStmts() + SubStmt::FirstParamMove + NumParams); + return child_range(getStoredStmts(), getStoredStmts() + + SubStmt::FirstParamMove + + CoroutineBodyBits.NumParams); } const_child_range children() const { return const_child_range(getStoredStmts(), getStoredStmts() + SubStmt::FirstParamMove + - NumParams); - } + CoroutineBodyBits.NumParams); + } + + unsigned getNumParams() const { return CoroutineBodyBits.NumParams; } + + /// Queries that if the coroutine 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 promise.unhandled_exception() and + /// initial "ramp" portion of the coroutine wouldn't throw. + /// In other words, coroutine wouldn't throw if all of followings are nothrow. + /// - 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() + /// - promise.final_suspend() + bool mayThrow() const { return CoroutineBodyBits.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 @@ -99,7 +99,8 @@ void *Mem = C.Allocate(Size, alignof(CoroutineBodyStmt)); auto *Result = new (Mem) CoroutineBodyStmt(CtorArgs()); - Result->NumParams = NumParams; + Result->setNumParams(NumParams); + Result->setMayThrow(true); auto *ParamBegin = Result->getStoredStmts() + SubStmt::FirstParamMove; std::uninitialized_fill(ParamBegin, ParamBegin + NumParams, static_cast(nullptr)); @@ -107,7 +108,9 @@ } CoroutineBodyStmt::CoroutineBodyStmt(CoroutineBodyStmt::CtorArgs const &Args) - : Stmt(CoroutineBodyStmtClass), NumParams(Args.ParamMoves.size()) { + : Stmt(CoroutineBodyStmtClass) { + CoroutineBodyBits.MayThrow = Args.MayThrow; + CoroutineBodyBits.NumParams = Args.ParamMoves.size(); Stmt **SubStmts = getStoredStmts(); SubStmts[CoroutineBodyStmt::Body] = Args.Body; SubStmts[CoroutineBodyStmt::Promise] = Args.Promise; @@ -124,4 +127,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,59 @@ 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)) + return true; + + 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; + + assert( + !S.canThrow(this->FinalSuspend) && + "promise_type::final_suspend should be noexcept by the language spec!"); + 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/lib/Serialization/ASTReaderStmt.cpp =================================================================== --- clang/lib/Serialization/ASTReaderStmt.cpp +++ clang/lib/Serialization/ASTReaderStmt.cpp @@ -452,11 +452,11 @@ void ASTStmtReader::VisitCoroutineBodyStmt(CoroutineBodyStmt *S) { VisitStmt(S); - assert(Record.peekInt() == S->NumParams); - Record.skipInts(1); + S->setNumParams(Record.readInt()); + S->setMayThrow(Record.readInt()); auto *StoredStmts = S->getStoredStmts(); for (unsigned i = 0; - i < CoroutineBodyStmt::SubStmt::FirstParamMove + S->NumParams; ++i) + i < CoroutineBodyStmt::SubStmt::FirstParamMove + S->getNumParams(); ++i) StoredStmts[i] = Record.readSubStmt(); } Index: clang/lib/Serialization/ASTWriterStmt.cpp =================================================================== --- clang/lib/Serialization/ASTWriterStmt.cpp +++ clang/lib/Serialization/ASTWriterStmt.cpp @@ -354,7 +354,9 @@ void ASTStmtWriter::VisitCoroutineBodyStmt(CoroutineBodyStmt *CoroStmt) { VisitStmt(CoroStmt); - Record.push_back(CoroStmt->getParamMoves().size()); + assert(CoroStmt->getParamMoves().size() == CoroStmt->getNumParams()); + Record.push_back(CoroStmt->getNumParams()); + Record.push_back(CoroStmt->mayThrow()); for (Stmt *S : CoroStmt->children()) Record.AddStmt(S); Code = serialization::STMT_COROUTINE_BODY; 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