Index: clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-identifier-naming/system/coroutines.h =================================================================== --- clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-identifier-naming/system/coroutines.h +++ clang-tools-extra/test/clang-tidy/checkers/Inputs/readability-identifier-naming/system/coroutines.h @@ -1,7 +1,6 @@ #pragma once namespace std { -namespace experimental { template struct coroutine_traits { @@ -13,7 +12,6 @@ static constexpr coroutine_handle from_address(void *addr) noexcept { return {}; }; }; -} // namespace experimental } // namespace std struct never_suspend { Index: clang/docs/LanguageExtensions.rst =================================================================== --- clang/docs/LanguageExtensions.rst +++ clang/docs/LanguageExtensions.rst @@ -2869,7 +2869,7 @@ Clang provides experimental builtins to support C++ Coroutines as defined by https://wg21.link/P0057. The following four are intended to be used by the -standard library to implement `std::experimental::coroutine_handle` type. +standard library to implement the ``std::coroutine_handle`` type. **Syntax**: Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -10988,19 +10988,25 @@ "|a function with a deduced return type|a varargs function" "|a consteval function}0">; def err_implied_coroutine_type_not_found : Error< - "%0 type was not found; include before defining " - "a coroutine">; + "%0 type was not found; include before defining " + "a coroutine; include if your version " + "of libcxx is less than 14.0">; +def err_mixed_use_std_and_experimental_namespace_for_coroutine : Error < + "Found mixed use of std namespace and std::experimental namespace for " + "coroutine, which is disallowed. The coroutine components in " + "std::experimental namespace is deprecated. Please use coroutine components " + "under std namespace.">; def err_implicit_coroutine_std_nothrow_type_not_found : Error< "std::nothrow was not found; include before defining a coroutine which " "uses get_return_object_on_allocation_failure()">; def err_malformed_std_nothrow : Error< "std::nothrow must be a valid variable declaration">; def err_malformed_std_coroutine_handle : Error< - "std::experimental::coroutine_handle must be a class template">; + "std::coroutine_handle isn't a class template">; def err_coroutine_handle_missing_member : Error< - "std::experimental::coroutine_handle missing a member named '%0'">; + "std::coroutine_handle must have a member named '%0'">; def err_malformed_std_coroutine_traits : Error< - "'std::experimental::coroutine_traits' must be a class template">; + "std::coroutine_traits isn't a class template">; def err_implied_std_coroutine_traits_promise_type_not_found : Error< "this function cannot be a coroutine: %q0 has no member named 'promise_type'">; def err_implied_std_coroutine_traits_promise_type_not_class : Error< Index: clang/include/clang/Sema/Sema.h =================================================================== --- clang/include/clang/Sema/Sema.h +++ clang/include/clang/Sema/Sema.h @@ -1124,6 +1124,10 @@ /// The C++ "std::coroutine_traits" template, which is defined in /// \ ClassTemplateDecl *StdCoroutineTraitsCache; + /// The namespace where coroutine components are defined. In standard, + /// they are defined in std namespace. And in the previous implementation, + /// they are defined in std::experimental namespace. + NamespaceDecl *CoroTraitsNamespaceCache; /// The C++ "type_info" declaration, which is defined in \. RecordDecl *CXXTypeInfoDecl; @@ -5696,6 +5700,7 @@ NamespaceDecl *getOrCreateStdNamespace(); NamespaceDecl *lookupStdExperimentalNamespace(); + NamespaceDecl *getCachedCoroNamespace() { return CoroTraitsNamespaceCache; } CXXRecordDecl *getStdBadAlloc() const; EnumDecl *getStdAlignValT() const; @@ -10265,8 +10270,11 @@ bool buildCoroutineParameterMoves(SourceLocation Loc); VarDecl *buildCoroutinePromise(SourceLocation Loc); void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body); + /// Lookup 'coroutine_traits' in std namespace and std::experimental + /// namespace. The namespace found is recorded in Namespace. ClassTemplateDecl *lookupCoroutineTraits(SourceLocation KwLoc, - SourceLocation FuncLoc); + SourceLocation FuncLoc, + NamespaceDecl *&Namespace); /// Check that the expression co_await promise.final_suspend() shall not be /// potentially-throwing. bool checkFinalSuspendNoThrow(const Stmt *FinalSuspend); Index: clang/lib/Frontend/InitPreprocessor.cpp =================================================================== --- clang/lib/Frontend/InitPreprocessor.cpp +++ clang/lib/Frontend/InitPreprocessor.cpp @@ -600,7 +600,7 @@ //Builder.defineMacro("__cpp_consteval", "201811L"); Builder.defineMacro("__cpp_constexpr_dynamic_alloc", "201907L"); Builder.defineMacro("__cpp_constinit", "201907L"); - //Builder.defineMacro("__cpp_coroutines", "201902L"); + Builder.defineMacro("__cpp_impl_coroutine", "201902L"); Builder.defineMacro("__cpp_designated_initializers", "201707L"); Builder.defineMacro("__cpp_impl_three_way_comparison", "201907L"); //Builder.defineMacro("__cpp_modules", "201907L"); Index: clang/lib/Sema/SemaCoroutine.cpp =================================================================== --- clang/lib/Sema/SemaCoroutine.cpp +++ clang/lib/Sema/SemaCoroutine.cpp @@ -53,15 +53,10 @@ SourceLocation KwLoc) { const FunctionProtoType *FnType = FD->getType()->castAs(); const SourceLocation FuncLoc = FD->getLocation(); - // FIXME: Cache std::coroutine_traits once we've found it. - NamespaceDecl *StdExp = S.lookupStdExperimentalNamespace(); - if (!StdExp) { - S.Diag(KwLoc, diag::err_implied_coroutine_type_not_found) - << "std::experimental::coroutine_traits"; - return QualType(); - } - ClassTemplateDecl *CoroTraits = S.lookupCoroutineTraits(KwLoc, FuncLoc); + NamespaceDecl *CoroNamespace = nullptr; + ClassTemplateDecl *CoroTraits = + S.lookupCoroutineTraits(KwLoc, FuncLoc, CoroNamespace); if (!CoroTraits) { return QualType(); } @@ -122,7 +117,7 @@ QualType PromiseType = S.Context.getTypeDeclType(Promise); auto buildElaboratedType = [&]() { - auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, StdExp); + auto *NNS = NestedNameSpecifier::Create(S.Context, nullptr, CoroNamespace); NNS = NestedNameSpecifier::Create(S.Context, NNS, false, CoroTrait.getTypePtr()); return S.Context.getElaboratedType(ETK_None, NNS, PromiseType); @@ -141,20 +136,20 @@ return PromiseType; } -/// Look up the std::experimental::coroutine_handle. +/// Look up the std::coroutine_handle. static QualType lookupCoroutineHandleType(Sema &S, QualType PromiseType, SourceLocation Loc) { if (PromiseType.isNull()) return QualType(); - NamespaceDecl *StdExp = S.lookupStdExperimentalNamespace(); - assert(StdExp && "Should already be diagnosed"); + NamespaceDecl *CoroNamespace = S.getCachedCoroNamespace(); + assert(CoroNamespace && "Should already be diagnosed"); LookupResult Result(S, &S.PP.getIdentifierTable().get("coroutine_handle"), Loc, Sema::LookupOrdinaryName); - if (!S.LookupQualifiedName(Result, StdExp)) { + if (!S.LookupQualifiedName(Result, CoroNamespace)) { S.Diag(Loc, diag::err_implied_coroutine_type_not_found) - << "std::experimental::coroutine_handle"; + << "std::coroutine_handle"; return QualType(); } @@ -1000,7 +995,7 @@ LookupResult Result(S, &S.PP.getIdentifierTable().get("nothrow"), Loc, Sema::LookupOrdinaryName); if (!S.LookupQualifiedName(Result, Std)) { - // FIXME: should have been included already. + // FIXME: should have been included already. // If we require it to include then this diagnostic is no longer // needed. S.Diag(Loc, diag::err_implicit_coroutine_std_nothrow_type_not_found); @@ -1663,25 +1658,47 @@ } ClassTemplateDecl *Sema::lookupCoroutineTraits(SourceLocation KwLoc, - SourceLocation FuncLoc) { + SourceLocation FuncLoc, + NamespaceDecl *&Namespace) { if (!StdCoroutineTraitsCache) { - if (auto StdExp = lookupStdExperimentalNamespace()) { - LookupResult Result(*this, - &PP.getIdentifierTable().get("coroutine_traits"), - FuncLoc, LookupOrdinaryName); - if (!LookupQualifiedName(Result, StdExp)) { + NamespaceDecl *CoroNamespace = getStdNamespace(); + LookupResult Result(*this, &PP.getIdentifierTable().get("coroutine_traits"), + FuncLoc, LookupOrdinaryName); + + if (!CoroNamespace || !LookupQualifiedName(Result, CoroNamespace)) { + /// Look up in namespace std::experimental, for compatibility. + /// TODO: Remove this extra lookup when is + /// removed. + CoroNamespace = lookupStdExperimentalNamespace(); + if (!CoroNamespace || !LookupQualifiedName(Result, CoroNamespace)) { Diag(KwLoc, diag::err_implied_coroutine_type_not_found) - << "std::experimental::coroutine_traits"; + << "std::coroutine_traits"; return nullptr; } - if (!(StdCoroutineTraitsCache = - Result.getAsSingle())) { - Result.suppressDiagnostics(); - NamedDecl *Found = *Result.begin(); - Diag(Found->getLocation(), diag::err_malformed_std_coroutine_traits); + /// TODO: Add a warning about not including + /// once we update libcxx. + } else { + /// When we found coroutine_traits in std namespace. Make sure there is no + /// misleading definition in std::experimental namespace. + NamespaceDecl *ExpNamespace = lookupStdExperimentalNamespace(); + LookupResult ExpResult(*this, + &PP.getIdentifierTable().get("coroutine_traits"), + FuncLoc, LookupOrdinaryName); + if (ExpNamespace && LookupQualifiedName(ExpResult, ExpNamespace)) { + Diag(KwLoc, + diag::err_mixed_use_std_and_experimental_namespace_for_coroutine); return nullptr; } } + + if (!(StdCoroutineTraitsCache = Result.getAsSingle())) { + Result.suppressDiagnostics(); + NamedDecl *Found = *Result.begin(); + Diag(Found->getLocation(), diag::err_malformed_std_coroutine_traits); + return nullptr; + } + CoroTraitsNamespaceCache = CoroNamespace; } + Namespace = CoroTraitsNamespaceCache; return StdCoroutineTraitsCache; } Index: clang/test/AST/Inputs/std-coroutine-exp-namespace.h =================================================================== --- /dev/null +++ clang/test/AST/Inputs/std-coroutine-exp-namespace.h @@ -0,0 +1,71 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value +#ifndef STD_COROUTINE_H +#define STD_COROUTINE_H + +namespace std { +namespace experimental { +template struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +template struct coroutine_handle; + +template <> struct coroutine_handle { + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + void operator()() { resume(); } + void *address() const noexcept { return ptr; } + void resume() const { __builtin_coro_resume(ptr); } + void destroy() const { __builtin_coro_destroy(ptr); } + bool done() const { return __builtin_coro_done(ptr); } + coroutine_handle &operator=(decltype(nullptr)) { + ptr = nullptr; + return *this; + } + coroutine_handle(decltype(nullptr)) : ptr(nullptr) {} + coroutine_handle() : ptr(nullptr) {} + // void reset() { ptr = nullptr; } // add to P0057? + explicit operator bool() const { return ptr; } + +protected: + void *ptr; +}; + +template struct coroutine_handle : coroutine_handle<> { + using coroutine_handle<>::operator=; + + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + + Promise &promise() const { + return *reinterpret_cast( + __builtin_coro_promise(ptr, alignof(Promise), false)); + } + static coroutine_handle from_promise(Promise &promise) { + coroutine_handle p; + p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true); + return p; + } +}; + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; +} // namespace experimental +} // namespace std + +#endif // STD_COROUTINE_H Index: clang/test/AST/Inputs/std-coroutine.h =================================================================== --- clang/test/AST/Inputs/std-coroutine.h +++ clang/test/AST/Inputs/std-coroutine.h @@ -1,9 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value #ifndef STD_COROUTINE_H #define STD_COROUTINE_H namespace std { -namespace experimental { template struct coroutine_traits { using promise_type = typename R::promise_type; @@ -67,7 +66,6 @@ void await_resume() noexcept {} }; -} // namespace experimental } // namespace std #endif // STD_COROUTINE_H Index: clang/test/AST/coroutine-locals-cleanup-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/AST/coroutine-locals-cleanup-exp-namespace.cpp @@ -0,0 +1,107 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -fsyntax-only -ast-dump %s | FileCheck %s + +#include "Inputs/std-coroutine-exp-namespace.h" + +using namespace std::experimental; + +struct Task { + struct promise_type { + Task get_return_object() noexcept { + return Task{coroutine_handle::from_promise(*this)}; + } + + void return_void() noexcept {} + + struct final_awaiter { + bool await_ready() noexcept { return false; } + coroutine_handle<> await_suspend(coroutine_handle h) noexcept { + h.destroy(); + return {}; + } + void await_resume() noexcept {} + }; + + void unhandled_exception() noexcept {} + + final_awaiter final_suspend() noexcept { return {}; } + + suspend_always initial_suspend() noexcept { return {}; } + + template + auto await_transform(Awaitable &&awaitable) { + return awaitable.co_viaIfAsync(); + } + }; + + using handle_t = coroutine_handle; + + class Awaiter { + public: + explicit Awaiter(handle_t coro) noexcept; + Awaiter(Awaiter &&other) noexcept; + Awaiter(const Awaiter &) = delete; + ~Awaiter(); + + bool await_ready() noexcept { return false; } + handle_t await_suspend(coroutine_handle<> continuation) noexcept; + void await_resume(); + + private: + handle_t coro_; + }; + + Task(handle_t coro) noexcept : coro_(coro) {} + + handle_t coro_; + + Task(const Task &t) = delete; + Task(Task &&t) noexcept; + ~Task(); + Task &operator=(Task t) noexcept; + + Awaiter co_viaIfAsync(); +}; + +static Task foo() { + co_return; +} + +Task bar() { + auto mode = 2; + switch (mode) { + case 1: + co_await foo(); + break; + case 2: + co_await foo(); + break; + default: + break; + } +} + +// CHECK-LABEL: FunctionDecl {{.*}} bar 'Task ()' +// CHECK: SwitchStmt +// CHECK: CaseStmt +// CHECK: ExprWithCleanups {{.*}} 'void' +// CHECK-NEXT: CoawaitExpr +// CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'Task::Awaiter':'Task::Awaiter' +// CHECK: ExprWithCleanups {{.*}} 'bool' +// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'bool' +// CHECK-NEXT: MemberExpr {{.*}} .await_ready +// CHECK: CallExpr {{.*}} 'void' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(void *)' +// CHECK-NEXT: DeclRefExpr {{.*}} '__builtin_coro_resume' 'void (void *)' +// CHECK-NEXT: ExprWithCleanups {{.*}} 'void *' + +// CHECK: CaseStmt +// CHECK: ExprWithCleanups {{.*}} 'void' +// CHECK-NEXT: CoawaitExpr +// CHECK-NEXT: MaterializeTemporaryExpr {{.*}} 'Task::Awaiter':'Task::Awaiter' +// CHECK: ExprWithCleanups {{.*}} 'bool' +// CHECK-NEXT: CXXMemberCallExpr {{.*}} 'bool' +// CHECK-NEXT: MemberExpr {{.*}} .await_ready +// CHECK: CallExpr {{.*}} 'void' +// CHECK-NEXT: ImplicitCastExpr {{.*}} 'void (*)(void *)' +// CHECK-NEXT: DeclRefExpr {{.*}} '__builtin_coro_resume' 'void (void *)' +// CHECK-NEXT: ExprWithCleanups {{.*}} 'void *' Index: clang/test/AST/coroutine-locals-cleanup.cpp =================================================================== --- clang/test/AST/coroutine-locals-cleanup.cpp +++ clang/test/AST/coroutine-locals-cleanup.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -fsyntax-only -ast-dump %s | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -fsyntax-only -ast-dump %s | FileCheck %s #include "Inputs/std-coroutine.h" -using namespace std::experimental; +using namespace std; struct Task { struct promise_type { Index: clang/test/AST/coroutine-source-location-crash-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/AST/coroutine-source-location-crash-exp-namespace.cpp @@ -0,0 +1,42 @@ +// Test without serialization: +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: -fsyntax-only -ast-dump | FileCheck %s +// +// Test with serialization: +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -std=c++14 -fcoroutines-ts -emit-pch -o %t %s +// RUN: %clang_cc1 -x c++ -triple x86_64-apple-darwin9 -std=c++14 -fcoroutines-ts -include-pch %t \ +// RUN: -ast-dump-all /dev/null \ +// RUN: | sed -e "s/ //" -e "s/ imported//" \ +// RUN: | FileCheck %s + +#include "Inputs/std-coroutine-exp-namespace.h" + +using namespace std::experimental; + +struct A { + bool await_ready(); + void await_resume(); + template + void await_suspend(F); +}; + +struct coro_t { + struct promise_type { + coro_t get_return_object(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + void return_void(); + static void unhandled_exception(); + }; +}; + +// {{0x[0-9a-fA-F]+}} +// CHECK-LABEL: FunctionDecl {{.*}} f 'coro_t (int)' +coro_t f(int n) { + A a{}; + // CHECK: CoawaitExpr {{0x[0-9a-fA-F]+}} + // CHECK-NEXT: DeclRefExpr {{0x[0-9a-fA-F]+}} + // CHECK-NEXT: CXXMemberCallExpr {{0x[0-9a-fA-F]+}} + // CHECK-NEXT: MemberExpr {{0x[0-9a-fA-F]+}} + co_await a; +} Index: clang/test/AST/coroutine-source-location-crash.cpp =================================================================== --- clang/test/AST/coroutine-source-location-crash.cpp +++ clang/test/AST/coroutine-source-location-crash.cpp @@ -1,17 +1,17 @@ // Test without serialization: -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 \ // RUN: -fsyntax-only -ast-dump | FileCheck %s // // Test with serialization: -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -std=c++14 -fcoroutines-ts -emit-pch -o %t %s -// RUN: %clang_cc1 -x c++ -triple x86_64-apple-darwin9 -std=c++14 -fcoroutines-ts -include-pch %t \ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 -std=c++20 -emit-pch -o %t %s +// RUN: %clang_cc1 -x c++ -triple x86_64-apple-darwin9 -std=c++20 -include-pch %t \ // RUN: -ast-dump-all /dev/null \ // RUN: | sed -e "s/ //" -e "s/ imported//" \ // RUN: | FileCheck %s #include "Inputs/std-coroutine.h" -using namespace std::experimental; +using namespace std; struct A { bool await_ready(); Index: clang/test/Analysis/more-dtors-cfg-output.cpp =================================================================== --- clang/test/Analysis/more-dtors-cfg-output.cpp +++ clang/test/Analysis/more-dtors-cfg-output.cpp @@ -275,32 +275,32 @@ #if CXX2A // Boilerplate needed to test co_return: -namespace std::experimental { - template - struct coroutine_handle { - static coroutine_handle from_address(void *) noexcept; - }; -} +namespace std { +template +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; +}; +} // namespace std struct TestPromise { TestPromise initial_suspend(); TestPromise final_suspend() noexcept; bool await_ready() noexcept; - void await_suspend(const std::experimental::coroutine_handle &) noexcept; + void await_suspend(const std::coroutine_handle &) noexcept; void await_resume() noexcept; Foo return_value(const Bar &); Bar get_return_object(); void unhandled_exception(); }; -namespace std::experimental { - template - struct coroutine_traits; - template <> - struct coroutine_traits { - using promise_type = TestPromise; - }; -} +namespace std { +template +struct coroutine_traits; +template <> +struct coroutine_traits { + using promise_type = TestPromise; +}; +} // namespace std Bar coreturn() { co_return get_bar(); Index: clang/test/CodeGenCXX/ubsan-coroutines.cpp =================================================================== --- clang/test/CodeGenCXX/ubsan-coroutines.cpp +++ clang/test/CodeGenCXX/ubsan-coroutines.cpp @@ -2,7 +2,7 @@ // crash when the LLVM coroutines passes are run. // RUN: %clang_cc1 -emit-obj -std=c++2a -fsanitize=null %s -o %t.o -namespace std::experimental { +namespace std { template struct coroutine_traits { using promise_type = typename R::promise_type; }; @@ -18,11 +18,11 @@ coroutine_handle() = default; static coroutine_handle from_address(void *) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; @@ -39,7 +39,7 @@ struct awaitable { task await() { (void)co_await *this; } bool await_ready() { return false; } - bool await_suspend(std::experimental::coroutine_handle<> awaiter) { return false; } + bool await_suspend(std::coroutine_handle<> awaiter) { return false; } bool await_resume() { return false; } }; Index: clang/test/CodeGenCoroutines/Inputs/coroutine-exp-namespace.h =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/Inputs/coroutine-exp-namespace.h @@ -0,0 +1,82 @@ +#pragma once + +namespace std { +namespace experimental { +inline namespace coroutines_v1 { + +template struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +template struct coroutine_handle; + +template <> struct coroutine_handle { + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + void operator()() { resume(); } + void *address() const noexcept { return ptr; } + void resume() const { __builtin_coro_resume(ptr); } + void destroy() const { __builtin_coro_destroy(ptr); } + bool done() const { return __builtin_coro_done(ptr); } + coroutine_handle &operator=(decltype(nullptr)) { + ptr = nullptr; + return *this; + } + coroutine_handle(decltype(nullptr)) : ptr(nullptr) {} + coroutine_handle() : ptr(nullptr) {} + // void reset() { ptr = nullptr; } // add to P0057? + explicit operator bool() const { return ptr; } + +protected: + void *ptr; +}; + +template struct coroutine_handle : coroutine_handle<> { + using coroutine_handle<>::operator=; + + static coroutine_handle from_address(void *addr) noexcept { + coroutine_handle me; + me.ptr = addr; + return me; + } + + Promise &promise() const { + return *reinterpret_cast( + __builtin_coro_promise(ptr, alignof(Promise), false)); + } + static coroutine_handle from_promise(Promise &promise) { + coroutine_handle p; + p.ptr = __builtin_coro_promise(&promise, alignof(Promise), true); + return p; + } +}; + +template +bool operator==(coroutine_handle<_PromiseT> const &_Left, + coroutine_handle<_PromiseT> const &_Right) noexcept { + return _Left.address() == _Right.address(); +} + +template +bool operator!=(coroutine_handle<_PromiseT> const &_Left, + coroutine_handle<_PromiseT> const &_Right) noexcept { + return !(_Left == _Right); +} + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} +}; +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +} // namespace coroutines_v1 +} // namespace experimental +} // namespace std Index: clang/test/CodeGenCoroutines/Inputs/coroutine.h =================================================================== --- clang/test/CodeGenCoroutines/Inputs/coroutine.h +++ clang/test/CodeGenCoroutines/Inputs/coroutine.h @@ -1,6 +1,6 @@ #pragma once -namespace std { namespace experimental { inline namespace coroutines_v1 { +namespace std { template struct coroutine_traits { using promise_type = typename R::promise_type; @@ -77,4 +77,4 @@ void await_resume() noexcept {} }; -}}} +} // namespace std Index: clang/test/CodeGenCoroutines/coro-alloc-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-alloc-exp-namespace.cpp @@ -0,0 +1,255 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 \ +// RUN: -Wno-coroutine-missing-unhandled-exception -emit-llvm %s -o - -disable-llvm-passes \ +// RUN: | FileCheck %s + +namespace std { +namespace experimental { +template +struct coroutine_traits; // expected-note {{declared here}} + +template +struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept { return {}; } +}; + +template <> +struct coroutine_handle { + static coroutine_handle from_address(void *) { return {}; } + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept {} +}; + +} // end namespace experimental + +struct nothrow_t {}; +constexpr nothrow_t nothrow = {}; + +} // end namespace std + +// Required when get_return_object_on_allocation_failure() is defined by +// the promise. +using SizeT = decltype(sizeof(int)); +void *operator new(SizeT __sz, const std::nothrow_t &) noexcept; +void operator delete(void *__p, const std::nothrow_t &)noexcept; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(std::experimental::coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +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() noexcept { 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: %[[NeedAlloc:.+]] = call i1 @llvm.coro.alloc(token %[[ID]]) + // CHECK: br i1 %[[NeedAlloc]], label %[[AllocBB:.+]], label %[[InitBB:.+]] + + // CHECK: [[AllocBB]]: + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: %[[MEM:.+]] = call noalias nonnull i8* @_Znwm(i64 %[[SIZE]]) + // CHECK: br label %[[InitBB]] + + // CHECK: [[InitBB]]: + // CHECK: %[[PHI:.+]] = phi i8* [ null, %{{.+}} ], [ %call, %[[AllocBB]] ] + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin(token %[[ID]], i8* %[[PHI]]) + + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: %[[NeedDealloc:.+]] = icmp ne i8* %[[MEM]], null + // CHECK: br i1 %[[NeedDealloc]], label %[[FreeBB:.+]], label %[[Afterwards:.+]] + + // CHECK: [[FreeBB]]: + // CHECK: call void @_ZdlPv(i8* %[[MEM]]) + // CHECK: br label %[[Afterwards]] + + // CHECK: [[Afterwards]]: + // CHECK: ret void + co_return; +} + +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() noexcept { 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.begin( + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: call void @_ZdlPv(i8* %[[MEM]]) + co_return; +} + +struct promise_matching_placement_new_tag {}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void *operator new(unsigned long, promise_matching_placement_new_tag, + int, float, double); + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + }; +}; + +// CHECK-LABEL: f1a( +extern "C" void f1a(promise_matching_placement_new_tag, int x, float y, double z) { + // CHECK: store i32 %x, i32* %x.addr, align 4 + // CHECK: store float %y, float* %y.addr, align 4 + // CHECK: store double %z, double* %z.addr, align 8 + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: %[[INT:.+]] = load i32, i32* %x.addr, align 4 + // CHECK: %[[FLOAT:.+]] = load float, float* %y.addr, align 4 + // CHECK: %[[DOUBLE:.+]] = load double, double* %z.addr, align 8 + // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagifdEE12promise_typenwEmS1_ifd(i64 %[[SIZE]], i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) + co_return; +} + +// Declare a placement form operator new, such as the one described in +// C++ 18.6.1.3.1, which takes a void* argument. +void *operator new(SizeT __sz, void *__p) noexcept; + +struct promise_matching_global_placement_new_tag {}; +struct dummy {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + }; +}; + +// A coroutine that takes a single pointer argument should not invoke this +// placement form operator. [dcl.fct.def.coroutine]/7 dictates that lookup for +// allocation functions matching the coroutine function's signature be done +// within the scope of the promise type's class. +// CHECK-LABEL: f1b( +extern "C" void f1b(promise_matching_global_placement_new_tag, dummy *) { + // CHECK: call noalias nonnull i8* @_Znwm(i64 + co_return; +} + +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() noexcept { 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 noalias nonnull i8* @_Znwm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) + co_return; +} + +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() noexcept { 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 noalias nonnull i8* @_Znwm(i64 %[[SIZE]]) + + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + // 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_return; +} + +struct promise_on_alloc_failure_tag {}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + int get_return_object() { return 0; } + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + static int get_return_object_on_allocation_failure() { return -1; } + }; +}; + +// CHECK-LABEL: f4( +extern "C" int f4(promise_on_alloc_failure_tag) { + // CHECK: %[[RetVal:.+]] = alloca i32 + // CHECK: %[[Gro:.+]] = alloca i32 + // CHECK: %[[ID:.+]] = call token @llvm.coro.id(i32 16 + // CHECK: %[[SIZE:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: %[[MEM:.+]] = call noalias i8* @_ZnwmRKSt9nothrow_t(i64 %[[SIZE]], %"struct.std::nothrow_t"* nonnull align 1 dereferenceable(1) @_ZStL7nothrow) + // CHECK: %[[OK:.+]] = icmp ne i8* %[[MEM]], null + // CHECK: br i1 %[[OK]], label %[[OKBB:.+]], label %[[ERRBB:.+]] + + // CHECK: [[ERRBB]]: + // CHECK: %[[FailRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type39get_return_object_on_allocation_failureEv( + // CHECK: store i32 %[[FailRet]], i32* %[[RetVal]] + // CHECK: br label %[[RetBB:.+]] + + // CHECK: [[OKBB]]: + // CHECK: %[[OkRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv( + // CHECK: store i32 %[[OkRet]], i32* %[[Gro]] + + // CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]] + // CHECK-NEXT: store i32 %[[Tmp1]], i32* %[[RetVal]] + // CHECK-NEXT: %[[Gro_CAST:.+]] = bitcast i32* %[[Gro]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 4, i8* %[[Gro_CAST]]) #2 + // CHECK-NEXT: br label %[[RetBB]] + + // CHECK: [[RetBB]]: + // CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]], align 4 + // CHECK: ret i32 %[[LoadRet]] + co_return; +} Index: clang/test/CodeGenCoroutines/coro-alloc.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-alloc.cpp +++ clang/test/CodeGenCoroutines/coro-alloc.cpp @@ -1,9 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \ // RUN: -Wno-coroutine-missing-unhandled-exception -emit-llvm %s -o - -disable-llvm-passes \ // RUN: | FileCheck %s namespace std { -namespace experimental { template struct coroutine_traits; // expected-note {{declared here}} @@ -21,8 +20,6 @@ coroutine_handle(coroutine_handle) noexcept {} }; -} // end namespace experimental - struct nothrow_t {}; constexpr nothrow_t nothrow = {}; @@ -37,14 +34,14 @@ struct suspend_always { bool await_ready() noexcept { return false; } - void await_suspend(std::experimental::coroutine_handle<>) noexcept {} + void await_suspend(std::coroutine_handle<>) noexcept {} void await_resume() noexcept {} }; struct global_new_delete_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object() {} suspend_always initial_suspend() { return {}; } @@ -83,8 +80,8 @@ struct promise_new_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void *operator new(unsigned long); void get_return_object() {} @@ -98,7 +95,7 @@ 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: call i8* @_ZNSt16coroutine_traitsIJv15promise_new_tagEE12promise_typenwEm(i64 %[[SIZE]]) // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) @@ -108,8 +105,8 @@ struct promise_matching_placement_new_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void *operator new(unsigned long, promise_matching_placement_new_tag, int, float, double); @@ -130,7 +127,7 @@ // CHECK: %[[INT:.+]] = load i32, i32* %x.addr, align 4 // CHECK: %[[FLOAT:.+]] = load float, float* %y.addr, align 4 // CHECK: %[[DOUBLE:.+]] = load double, double* %z.addr, align 8 - // CHECK: call i8* @_ZNSt12experimental16coroutine_traitsIJv34promise_matching_placement_new_tagifdEE12promise_typenwEmS1_ifd(i64 %[[SIZE]], i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) + // CHECK: call i8* @_ZNSt16coroutine_traitsIJv34promise_matching_placement_new_tagifdEE12promise_typenwEmS0_ifd(i64 %[[SIZE]], i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) co_return; } @@ -140,8 +137,8 @@ struct promise_matching_global_placement_new_tag {}; struct dummy {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object() {} suspend_always initial_suspend() { return {}; } @@ -162,8 +159,8 @@ struct promise_delete_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void operator delete(void*); void get_return_object() {} @@ -181,14 +178,14 @@ // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // CHECK: %[[MEM:.+]] = call i8* @llvm.coro.free(token %[[ID]], i8* %[[FRAME]]) - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) + // CHECK: call void @_ZNSt16coroutine_traitsIJv18promise_delete_tagEE12promise_typedlEPv(i8* %[[MEM]]) co_return; } struct promise_sized_delete_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void operator delete(void*, unsigned long); void get_return_object() {} @@ -207,14 +204,14 @@ // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( // 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]]) + // CHECK: call void @_ZNSt16coroutine_traitsIJv24promise_sized_delete_tagEE12promise_typedlEPvm(i8* %[[MEM]], i64 %[[SIZE2]]) co_return; } struct promise_on_alloc_failure_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { int get_return_object() { return 0; } suspend_always initial_suspend() { return {}; } @@ -235,12 +232,12 @@ // CHECK: br i1 %[[OK]], label %[[OKBB:.+]], label %[[ERRBB:.+]] // CHECK: [[ERRBB]]: - // CHECK: %[[FailRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type39get_return_object_on_allocation_failureEv( + // CHECK: %[[FailRet:.+]] = call i32 @_ZNSt16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type39get_return_object_on_allocation_failureEv( // CHECK: store i32 %[[FailRet]], i32* %[[RetVal]] // CHECK: br label %[[RetBB:.+]] // CHECK: [[OKBB]]: - // CHECK: %[[OkRet:.+]] = call i32 @_ZNSt12experimental16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv( + // CHECK: %[[OkRet:.+]] = call i32 @_ZNSt16coroutine_traitsIJi28promise_on_alloc_failure_tagEE12promise_type17get_return_objectEv( // CHECK: store i32 %[[OkRet]], i32* %[[Gro]] // CHECK: %[[Tmp1:.*]] = load i32, i32* %[[Gro]] Index: clang/test/CodeGenCoroutines/coro-always-inline-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-always-inline-exp-namespace.cpp @@ -0,0 +1,52 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: -fexperimental-new-pass-manager -O0 %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: -fexperimental-new-pass-manager -fno-inline -O0 %s -o - | FileCheck %s + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: -O0 %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: -fno-inline -O0 %s -o - | FileCheck %s + +namespace std { +namespace experimental { + +struct handle {}; + +struct awaitable { + bool await_ready() noexcept { return true; } + // CHECK-NOT: await_suspend + inline void __attribute__((__always_inline__)) await_suspend(handle) noexcept {} + bool await_resume() noexcept { return true; } +}; + +template +struct coroutine_handle { + static handle from_address(void *address) noexcept { return {}; } +}; + +template +struct coroutine_traits { + struct promise_type { + awaitable initial_suspend() { return {}; } + awaitable final_suspend() noexcept { return {}; } + void return_void() {} + T get_return_object() { return T(); } + void unhandled_exception() {} + }; +}; +} // namespace experimental +} // namespace std + +// CHECK-LABEL: @_Z3foov +// CHECK-LABEL: entry: +// CHECK: [[CAST0:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* [[CAST0]]) +// CHECK: [[CAST1:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* [[CAST1]]) + +// CHECK: [[CAST2:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* [[CAST2]]) +// CHECK: [[CAST3:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* [[CAST3]]) +void foo() { co_return; } Index: clang/test/CodeGenCoroutines/coro-always-inline.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-always-inline.cpp +++ clang/test/CodeGenCoroutines/coro-always-inline.cpp @@ -1,15 +1,14 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -std=c++20 \ // RUN: -fexperimental-new-pass-manager -O0 %s -o - | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -std=c++20 \ // RUN: -fexperimental-new-pass-manager -fno-inline -O0 %s -o - | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -std=c++20 \ // RUN: -O0 %s -o - | FileCheck %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -std=c++20 \ // RUN: -fno-inline -O0 %s -o - | FileCheck %s namespace std { -namespace experimental { struct handle {}; @@ -35,18 +34,17 @@ void unhandled_exception() {} }; }; -} // namespace experimental } // namespace std // CHECK-LABEL: @_Z3foov // CHECK-LABEL: entry: -// CHECK: [[CAST0:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK: [[CAST0:%[0-9]+]] = bitcast %"struct.std::awaitable"* %ref.tmp{{.*}} to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* [[CAST0]]) -// CHECK: [[CAST1:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK: [[CAST1:%[0-9]+]] = bitcast %"struct.std::awaitable"* %ref.tmp{{.*}} to i8* // CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* [[CAST1]]) -// CHECK: [[CAST2:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK: [[CAST2:%[0-9]+]] = bitcast %"struct.std::awaitable"* %ref.tmp{{.*}} to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 1, i8* [[CAST2]]) -// CHECK: [[CAST3:%[0-9]+]] = bitcast %"struct.std::experimental::awaitable"* %ref.tmp{{.*}} to i8* +// CHECK: [[CAST3:%[0-9]+]] = bitcast %"struct.std::awaitable"* %ref.tmp{{.*}} to i8* // CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 1, i8* [[CAST3]]) void foo() { co_return; } Index: clang/test/CodeGenCoroutines/coro-await-domination-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-await-domination-exp-namespace.cpp @@ -0,0 +1,36 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - | FileCheck %s +#include "Inputs/coroutine-exp-namespace.h" + +using namespace std::experimental; + +struct coro { + struct promise_type { + coro get_return_object(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + void return_void(); + static void unhandled_exception(); + }; +}; + +struct A { + ~A(); + bool await_ready(); + int await_resume() { return 8; } + template void await_suspend(F); +}; + +extern "C" void consume(int); + +// Verifies that domination is properly built during cleanup. +// Without CGCleanup.cpp fix verifier was reporting: +// Instruction does not dominate all uses! +// %tmp.exprcleanup = alloca i32*, align 8 +// store i32* %x, i32** %tmp.exprcleanup, align 8 + +// CHECK-LABEL: f( +extern "C" coro f(int) { + int x = 42; + x = co_await A{}; + consume(x); +} Index: clang/test/CodeGenCoroutines/coro-await-domination.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-await-domination.cpp +++ clang/test/CodeGenCoroutines/coro-await-domination.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o - | FileCheck %s #include "Inputs/coroutine.h" -using namespace std::experimental; +using namespace std; struct coro { struct promise_type { Index: clang/test/CodeGenCoroutines/coro-await-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-await-exp-namespace.cpp @@ -0,0 +1,355 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 \ +// RUN: -emit-llvm %s -o - -disable-llvm-passes -Wno-coroutine -Wno-unused | FileCheck %s + +namespace std { +namespace experimental { +template +struct coroutine_traits; + +template struct coroutine_handle; + +template <> +struct coroutine_handle { + void *ptr; + static coroutine_handle from_address(void *); + void *address(); +}; + +template +struct coroutine_handle : coroutine_handle<> { + static coroutine_handle from_address(void *) noexcept; +}; + +} // namespace experimental +} // namespace std + +struct init_susp { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +struct final_susp { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct suspend_always { + int stuff; + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object(); + init_susp initial_suspend(); + final_susp final_suspend() noexcept; + void return_void(); + }; +}; + +// CHECK-LABEL: f0( +extern "C" void f0() { + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + + // See if initial_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* + // CHECK: %[[INITSP_ID:.+]] = call token @llvm.coro.save( + // CHECK: call i8 @llvm.coro.suspend(token %[[INITSP_ID]], i1 false) + + co_await suspend_always{}; + // See if we need to suspend: + // -------------------------- + // CHECK: %[[READY:.+]] = call zeroext i1 @_ZN14suspend_always11await_readyEv(%struct.suspend_always* {{[^,]*}} %[[AWAITABLE:.+]]) + // CHECK: br i1 %[[READY]], label %[[READY_BB:.+]], label %[[SUSPEND_BB:.+]] + + // If we are suspending: + // --------------------- + // CHECK: [[SUSPEND_BB]]: + // CHECK: %[[SUSPEND_ID:.+]] = call token @llvm.coro.save( + // --------------------------- + // Build the coroutine handle and pass it to await_suspend + // --------------------------- + // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJvEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) + // ... many lines of code to coerce coroutine_handle into an i8* scalar + // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}} + // CHECK: call void @_ZN14suspend_always13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_always* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) + // ------------------------- + // Generate a suspend point: + // ------------------------- + // CHECK: %[[OUTCOME:.+]] = call i8 @llvm.coro.suspend(token %[[SUSPEND_ID]], i1 false) + // CHECK: switch i8 %[[OUTCOME]], label %[[RET_BB:.+]] [ + // CHECK: i8 0, label %[[READY_BB]] + // CHECK: i8 1, label %[[CLEANUP_BB:.+]] + // CHECK: ] + + // Cleanup code goes here: + // ----------------------- + // CHECK: [[CLEANUP_BB]]: + + // When coroutine is resumed, call await_resume + // -------------------------- + // CHECK: [[READY_BB]]: + // CHECK: call void @_ZN14suspend_always12await_resumeEv(%struct.suspend_always* {{[^,]*}} %[[AWAITABLE]]) + + // See if final_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* + // CHECK: %[[FINALSP_ID:.+]] = call token @llvm.coro.save( + // CHECK: call i8 @llvm.coro.suspend(token %[[FINALSP_ID]], i1 true) +} + +struct suspend_maybe { + float stuff; + ~suspend_maybe(); + bool await_ready(); + bool await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object(); + init_susp initial_suspend(); + final_susp final_suspend() noexcept; + void return_void(); + suspend_maybe yield_value(int); + }; +}; + +// CHECK-LABEL: f1( +extern "C" void f1(int) { + // CHECK: %[[PROMISE:.+]] = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + co_yield 42; + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJviEE12promise_type11yield_valueEi(%struct.suspend_maybe* sret(%struct.suspend_maybe) align 4 %[[AWAITER:.+]], %"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %[[PROMISE]], i32 42) + + // See if we need to suspend: + // -------------------------- + // CHECK: %[[READY:.+]] = call zeroext i1 @_ZN13suspend_maybe11await_readyEv(%struct.suspend_maybe* {{[^,]*}} %[[AWAITABLE]]) + // CHECK: br i1 %[[READY]], label %[[READY_BB:.+]], label %[[SUSPEND_BB:.+]] + + // If we are suspending: + // --------------------- + // CHECK: [[SUSPEND_BB]]: + // CHECK: %[[SUSPEND_ID:.+]] = call token @llvm.coro.save( + // --------------------------- + // Build the coroutine handle and pass it to await_suspend + // --------------------------- + // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJviEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) + // ... many lines of code to coerce coroutine_handle into an i8* scalar + // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}} + // CHECK: %[[YES:.+]] = call zeroext i1 @_ZN13suspend_maybe13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_maybe* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) + // ------------------------------------------- + // See if await_suspend decided not to suspend + // ------------------------------------------- + // CHECK: br i1 %[[YES]], label %[[SUSPEND_PLEASE:.+]], label %[[READY_BB]] + + // CHECK: [[SUSPEND_PLEASE]]: + // CHECK: call i8 @llvm.coro.suspend(token %[[SUSPEND_ID]], i1 false) + + // CHECK: [[READY_BB]]: + // CHECK: call void @_ZN13suspend_maybe12await_resumeEv(%struct.suspend_maybe* {{[^,]*}} %[[AWAITABLE]]) +} + +struct ComplexAwaiter { + template void await_suspend(F); + bool await_ready(); + _Complex float await_resume(); +}; +extern "C" void UseComplex(_Complex float); + +// CHECK-LABEL: @TestComplex( +extern "C" void TestComplex() { + UseComplex(co_await ComplexAwaiter{}); + // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter* + // CHECK: call void @UseComplex(<2 x float> %{{.+}}) + + co_await ComplexAwaiter{}; + // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter* + + _Complex float Val = co_await ComplexAwaiter{}; + // CHECK: call <2 x float> @_ZN14ComplexAwaiter12await_resumeEv(%struct.ComplexAwaiter* +} + +struct Aggr { + int X, Y, Z; + ~Aggr(); +}; +struct AggrAwaiter { + template void await_suspend(F); + bool await_ready(); + Aggr await_resume(); +}; + +extern "C" void Whatever(); +extern "C" void UseAggr(Aggr &&); + +// FIXME: Once the cleanup code is in, add testing that destructors for Aggr +// are invoked properly on the cleanup branches. + +// CHECK-LABEL: @TestAggr( +extern "C" void TestAggr() { + UseAggr(co_await AggrAwaiter{}); + Whatever(); + // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret(%struct.Aggr) align 4 %[[AwaitResume:.+]], + // CHECK: call void @UseAggr(%struct.Aggr* nonnull align 4 dereferenceable(12) %[[AwaitResume]]) + // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* {{[^,]*}} %[[AwaitResume]]) + // CHECK: call void @Whatever() + + co_await AggrAwaiter{}; + Whatever(); + // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret(%struct.Aggr) align 4 %[[AwaitResume2:.+]], + // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* {{[^,]*}} %[[AwaitResume2]]) + // CHECK: call void @Whatever() + + Aggr Val = co_await AggrAwaiter{}; + Whatever(); + // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret(%struct.Aggr) align 4 %[[AwaitResume3:.+]], + // CHECK: call void @Whatever() + // CHECK: call void @_ZN4AggrD1Ev(%struct.Aggr* {{[^,]*}} %[[AwaitResume3]]) +} + +struct ScalarAwaiter { + template void await_suspend(F); + bool await_ready(); + int await_resume(); +}; + +extern "C" void UseScalar(int); + +// CHECK-LABEL: @TestScalar( +extern "C" void TestScalar() { + UseScalar(co_await ScalarAwaiter{}); + // CHECK: %[[Result:.+]] = call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter* + // CHECK: call void @UseScalar(i32 %[[Result]]) + + int Val = co_await ScalarAwaiter{}; + // CHECK: %[[Result2:.+]] = call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter* + // CHECK: store i32 %[[Result2]], i32* %[[TMP_EXPRCLEANUP:.+]], + // CHECK: %[[TMP:.+]] = load i32, i32* %[[TMP_EXPRCLEANUP]], + // CHECK: store i32 %[[TMP]], i32* %Val, + + co_await ScalarAwaiter{}; + // CHECK: call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter* +} + +// Test operator co_await codegen. +enum class MyInt : int {}; +ScalarAwaiter operator co_await(MyInt); + +struct MyAgg { + AggrAwaiter operator co_await(); +}; + +// CHECK-LABEL: @TestOpAwait( +extern "C" void TestOpAwait() { + co_await MyInt(42); + // CHECK: call void @_Zaw5MyInt(i32 42) + // CHECK: call i32 @_ZN13ScalarAwaiter12await_resumeEv(%struct.ScalarAwaiter* {{[^,]*}} % + + co_await MyAgg{}; + // CHECK: call void @_ZN5MyAggawEv(%struct.MyAgg* {{[^,]*}} % + // CHECK: call void @_ZN11AggrAwaiter12await_resumeEv(%struct.Aggr* sret(%struct.Aggr) align 4 % +} + +// CHECK-LABEL: EndlessLoop( +extern "C" void EndlessLoop() { + // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( + + // See if initial_suspend was issued: + // ---------------------------------- + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* + + for (;;) + co_await suspend_always{}; + + // Verify that final_suspend was NOT issued: + // ---------------------------------- + // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK-NOT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* +} + +// Verifies that we don't crash when awaiting on an lvalue. +// CHECK-LABEL: @_Z11AwaitLValuev( +void AwaitLValue() { + suspend_always lval; + co_await lval; +} + +struct RefTag {}; + +struct AwaitResumeReturnsLValue { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + RefTag &await_resume(); +}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object(); + init_susp initial_suspend(); + final_susp final_suspend() noexcept; + void return_void(); + AwaitResumeReturnsLValue yield_value(int); + }; +}; + +// Verifies that we don't crash when returning an lvalue from an await_resume() +// expression. +// CHECK-LABEL: define{{.*}} void @_Z18AwaitReturnsLValued(double %0) +void AwaitReturnsLValue(double) { + AwaitResumeReturnsLValue a; + // CHECK: %[[AVAR:.+]] = alloca %struct.AwaitResumeReturnsLValue, + // CHECK: %[[XVAR:.+]] = alloca %struct.RefTag*, + + // CHECK: %[[YVAR:.+]] = alloca %struct.RefTag*, + // CHECK-NEXT: %[[TMP1:.+]] = alloca %struct.AwaitResumeReturnsLValue, + + // CHECK: %[[TMP_EXPRCLEANUP1:.+]] = alloca %struct.RefTag*, + // CHECK: %[[ZVAR:.+]] = alloca %struct.RefTag*, + // CHECK-NEXT: %[[TMP2:.+]] = alloca %struct.AwaitResumeReturnsLValue, + // CHECK: %[[TMP_EXPRCLEANUP2:.+]] = alloca %struct.RefTag*, + + // CHECK: %[[RES1:.+]] = call nonnull align 1 dereferenceable({{.*}}) %struct.RefTag* @_ZN24AwaitResumeReturnsLValue12await_resumeEv(%struct.AwaitResumeReturnsLValue* {{[^,]*}} %[[AVAR]]) + // CHECK-NEXT: store %struct.RefTag* %[[RES1]], %struct.RefTag** %[[XVAR]], + RefTag &x = co_await a; + + // CHECK: %[[RES2:.+]] = call nonnull align 1 dereferenceable({{.*}}) %struct.RefTag* @_ZN24AwaitResumeReturnsLValue12await_resumeEv(%struct.AwaitResumeReturnsLValue* {{[^,]*}} %[[TMP1]]) + // CHECK-NEXT: store %struct.RefTag* %[[RES2]], %struct.RefTag** %[[TMP_EXPRCLEANUP1]], + // CHECK: %[[LOAD_TMP1:.+]] = load %struct.RefTag*, %struct.RefTag** %[[TMP_EXPRCLEANUP1]], + // CHECK: store %struct.RefTag* %[[LOAD_TMP1]], %struct.RefTag** %[[YVAR]], + + RefTag &y = co_await AwaitResumeReturnsLValue{}; + // CHECK: %[[RES3:.+]] = call nonnull align 1 dereferenceable({{.*}}) %struct.RefTag* @_ZN24AwaitResumeReturnsLValue12await_resumeEv(%struct.AwaitResumeReturnsLValue* {{[^,]*}} %[[TMP2]]) + // CHECK-NEXT: store %struct.RefTag* %[[RES3]], %struct.RefTag** %[[TMP_EXPRCLEANUP2]], + // CHECK: %[[LOAD_TMP2:.+]] = load %struct.RefTag*, %struct.RefTag** %[[TMP_EXPRCLEANUP2]], + // CHECK: store %struct.RefTag* %[[LOAD_TMP2]], %struct.RefTag** %[[ZVAR]], + RefTag &z = co_yield 42; +} + +struct TailCallAwait { + bool await_ready(); + std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; + +// CHECK-LABEL: @TestTailcall( +extern "C" void TestTailcall() { + co_await TailCallAwait{}; + + // CHECK: %[[RESULT:.+]] = call i8* @_ZN13TailCallAwait13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.TailCallAwait* + // CHECK: %[[COERCE:.+]] = getelementptr inbounds %"struct.std::experimental::coroutine_handle", %"struct.std::experimental::coroutine_handle"* %[[TMP:.+]], i32 0, i32 0 + // CHECK: store i8* %[[RESULT]], i8** %[[COERCE]] + // CHECK: %[[ADDR:.+]] = call i8* @_ZNSt12experimental16coroutine_handleIvE7addressEv(%"struct.std::experimental::coroutine_handle"* {{[^,]*}} %[[TMP]]) + // CHECK: call void @llvm.coro.resume(i8* %[[ADDR]]) +} Index: clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-await-resume-eh-exp-namespace.cpp @@ -0,0 +1,114 @@ +// Test the behavior of http://wg21.link/P0664, a proposal to catch any +// exceptions thrown after the initial suspend point of a coroutine by +// executing the handler specified by the promise type's 'unhandled_exception' +// member function. +// +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts \ +// RUN: -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s \ +// RUN: -fexceptions -fcxx-exceptions -disable-llvm-passes \ +// RUN: | FileCheck %s + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +struct throwing_awaitable { + bool await_ready() { return true; } + void await_suspend(coro::coroutine_handle<>) {} + void await_resume() { throw 42; } +}; + +struct throwing_task { + struct promise_type { + auto get_return_object() { return throwing_task{}; } + auto initial_suspend() { return throwing_awaitable{}; } + auto final_suspend() noexcept { return coro::suspend_never{}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +// CHECK-LABEL: define{{.*}} void @_Z1fv() +throwing_task f() { + // A variable RESUMETHREW is used to keep track of whether the body + // of 'await_resume' threw an exception. Exceptions thrown in + // 'await_resume' are unwound to RESUMELPAD. + // CHECK: init.ready: + // CHECK-NEXT: store i1 true, i1* %[[RESUMETHREW:.+]], align 1 + // CHECK-NEXT: invoke void @_ZN18throwing_awaitable12await_resumeEv + // CHECK-NEXT: to label %[[RESUMECONT:.+]] unwind label %[[RESUMELPAD:.+]] + + // If 'await_resume' does not throw an exception, 'false' is stored in + // variable RESUMETHREW. + // CHECK: [[RESUMECONT]]: + // CHECK-NEXT: store i1 false, i1* %[[RESUMETHREW]] + // CHECK-NEXT: br label %[[RESUMETRYCONT:.+]] + + // 'unhandled_exception' is called for the exception thrown in + // 'await_resume'. The variable RESUMETHREW is never set to false, + // and a jump is made to RESUMETRYCONT. + // CHECK: [[RESUMELPAD]]: + // CHECK: br label %[[RESUMECATCH:.+]] + // CHECK: [[RESUMECATCH]]: + // CHECK: invoke void @_ZN13throwing_task12promise_type19unhandled_exceptionEv + // CHECK-NEXT: to label %[[RESUMEENDCATCH:.+]] unwind label + // CHECK: [[RESUMEENDCATCH]]: + // CHECK-NEXT: invoke void @__cxa_end_catch() + // CHECK-NEXT: to label %[[RESUMEENDCATCHCONT:.+]] unwind label + // CHECK: [[RESUMEENDCATCHCONT]]: + // CHECK-NEXT: br label %[[RESUMETRYCONT]] + // CHECK: [[RESUMETRYCONT]]: + // CHECK-NEXT: br label %[[CLEANUP:.+]] + // CHECK: [[CLEANUP]]: + // CHECK: switch i32 %{{.+}}, label %{{.+}} [ + // CHECK-NEXT: i32 0, label %[[CLEANUPCONT:.+]] + // CHECK-NEXT: ] + + // The variable RESUMETHREW is loaded and if true, then 'await_resume' + // threw an exception and the coroutine body is skipped, and the final + // suspend is executed immediately. Otherwise, the coroutine body is + // executed, and then the final suspend. + // CHECK: [[CLEANUPCONT]]: + // CHECK-NEXT: %[[RESUMETHREWLOAD:.+]] = load i1, i1* %[[RESUMETHREW]] + // CHECK-NEXT: br i1 %[[RESUMETHREWLOAD]], label %[[RESUMEDCONT:.+]], label %[[RESUMEDBODY:.+]] + + // CHECK: [[RESUMEDBODY]]: + // CHECK: invoke void @_ZN13throwing_task12promise_type11return_voidEv + // CHECK-NEXT: to label %[[REDUMEDBODYCONT:.+]] unwind label + // CHECK: [[REDUMEDBODYCONT]]: + // CHECK-NEXT: br label %[[COROFINAL:.+]] + + // CHECK: [[RESUMEDCONT]]: + // CHECK-NEXT: br label %[[COROFINAL]] + + // CHECK: [[COROFINAL]]: + // CHECK: call void @_ZN13throwing_task12promise_type13final_suspendEv + co_return; +} + +struct noexcept_awaitable { + bool await_ready() { return true; } + void await_suspend(coro::coroutine_handle<>) {} + void await_resume() noexcept {} +}; + +struct noexcept_task { + struct promise_type { + auto get_return_object() { return noexcept_task{}; } + auto initial_suspend() { return noexcept_awaitable{}; } + auto final_suspend() noexcept { return coro::suspend_never{}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +// CHECK-LABEL: define{{.*}} void @_Z1gv() +noexcept_task g() { + // If the await_resume function is marked as noexcept, none of the additional + // conditions that are present in f() above are added to the IR. + // This means that no i1 are stored before or after calling await_resume: + // CHECK: init.ready: + // CHECK-NEXT: call void @_ZN18noexcept_awaitable12await_resumeEv + // CHECK-NOT: store i1 false, i1* + co_return; +} Index: clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp +++ clang/test/CodeGenCoroutines/coro-await-resume-eh.cpp @@ -3,18 +3,16 @@ // executing the handler specified by the promise type's 'unhandled_exception' // member function. // -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts \ +// RUN: %clang_cc1 -std=c++20 \ // RUN: -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s \ // RUN: -fexceptions -fcxx-exceptions -disable-llvm-passes \ // RUN: | FileCheck %s #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - struct throwing_awaitable { bool await_ready() { return true; } - void await_suspend(coro::coroutine_handle<>) {} + void await_suspend(std::coroutine_handle<>) {} void await_resume() { throw 42; } }; @@ -22,7 +20,7 @@ struct promise_type { auto get_return_object() { return throwing_task{}; } auto initial_suspend() { return throwing_awaitable{}; } - auto final_suspend() noexcept { return coro::suspend_never{}; } + auto final_suspend() noexcept { return std::suspend_never{}; } void return_void() {} void unhandled_exception() {} }; @@ -88,7 +86,7 @@ struct noexcept_awaitable { bool await_ready() { return true; } - void await_suspend(coro::coroutine_handle<>) {} + void await_suspend(std::coroutine_handle<>) {} void await_resume() noexcept {} }; @@ -96,7 +94,7 @@ struct promise_type { auto get_return_object() { return noexcept_task{}; } auto initial_suspend() { return noexcept_awaitable{}; } - auto final_suspend() noexcept { return coro::suspend_never{}; } + auto final_suspend() noexcept { return std::suspend_never{}; } void return_void() {} void unhandled_exception() {} }; Index: clang/test/CodeGenCoroutines/coro-await.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-await.cpp +++ clang/test/CodeGenCoroutines/coro-await.cpp @@ -1,8 +1,7 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 \ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 \ // RUN: -emit-llvm %s -o - -disable-llvm-passes -Wno-coroutine -Wno-unused | FileCheck %s namespace std { -namespace experimental { template struct coroutine_traits; @@ -20,29 +19,28 @@ static coroutine_handle from_address(void *) noexcept; }; -} -} +} // namespace std struct init_susp { bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); void await_resume(); }; struct final_susp { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; struct suspend_always { int stuff; bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); void await_resume(); }; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object(); init_susp initial_suspend(); @@ -57,7 +55,7 @@ // See if initial_suspend was issued: // ---------------------------------- - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type15initial_suspendEv( // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* // CHECK: %[[INITSP_ID:.+]] = call token @llvm.coro.save( // CHECK: call i8 @llvm.coro.suspend(token %[[INITSP_ID]], i1 false) @@ -75,10 +73,10 @@ // --------------------------- // Build the coroutine handle and pass it to await_suspend // --------------------------- - // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJvEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) + // CHECK: call i8* @_ZNSt16coroutine_handleINSt16coroutine_traitsIJvEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) // ... many lines of code to coerce coroutine_handle into an i8* scalar // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}} - // CHECK: call void @_ZN14suspend_always13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_always* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) + // CHECK: call void @_ZN14suspend_always13await_suspendESt16coroutine_handleIvE(%struct.suspend_always* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) // ------------------------- // Generate a suspend point: // ------------------------- @@ -99,7 +97,7 @@ // See if final_suspend was issued: // ---------------------------------- - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type13final_suspendEv( // CHECK-NEXT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* // CHECK: %[[FINALSP_ID:.+]] = call token @llvm.coro.save( // CHECK: call i8 @llvm.coro.suspend(token %[[FINALSP_ID]], i1 true) @@ -109,13 +107,12 @@ float stuff; ~suspend_maybe(); bool await_ready(); - bool await_suspend(std::experimental::coroutine_handle<>); + bool await_suspend(std::coroutine_handle<>); void await_resume(); }; - -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object(); init_susp initial_suspend(); @@ -127,10 +124,10 @@ // CHECK-LABEL: f1( extern "C" void f1(int) { - // CHECK: %[[PROMISE:.+]] = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %[[PROMISE:.+]] = alloca %"struct.std::coroutine_traits::promise_type" // CHECK: %[[FRAME:.+]] = call i8* @llvm.coro.begin( co_yield 42; - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJviEE12promise_type11yield_valueEi(%struct.suspend_maybe* sret(%struct.suspend_maybe) align 4 %[[AWAITER:.+]], %"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %[[PROMISE]], i32 42) + // CHECK: call void @_ZNSt16coroutine_traitsIJviEE12promise_type11yield_valueEi(%struct.suspend_maybe* sret(%struct.suspend_maybe) align 4 %[[AWAITER:.+]], %"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %[[PROMISE]], i32 42) // See if we need to suspend: // -------------------------- @@ -144,10 +141,10 @@ // --------------------------- // Build the coroutine handle and pass it to await_suspend // --------------------------- - // CHECK: call i8* @_ZNSt12experimental16coroutine_handleINS_16coroutine_traitsIJviEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) + // CHECK: call i8* @_ZNSt16coroutine_handleINSt16coroutine_traitsIJviEE12promise_typeEE12from_addressEPv(i8* %[[FRAME]]) // ... many lines of code to coerce coroutine_handle into an i8* scalar // CHECK: %[[CH:.+]] = load i8*, i8** %{{.+}} - // CHECK: %[[YES:.+]] = call zeroext i1 @_ZN13suspend_maybe13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.suspend_maybe* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) + // CHECK: %[[YES:.+]] = call zeroext i1 @_ZN13suspend_maybe13await_suspendESt16coroutine_handleIvE(%struct.suspend_maybe* {{[^,]*}} %[[AWAITABLE]], i8* %[[CH]]) // ------------------------------------------- // See if await_suspend decided not to suspend // ------------------------------------------- @@ -264,7 +261,7 @@ // See if initial_suspend was issued: // ---------------------------------- - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type15initial_suspendEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type15initial_suspendEv( // CHECK-NEXT: call zeroext i1 @_ZN9init_susp11await_readyEv(%struct.init_susp* for (;;) @@ -272,7 +269,7 @@ // Verify that final_suspend was NOT issued: // ---------------------------------- - // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type13final_suspendEv( + // CHECK-NOT: call void @_ZNSt16coroutine_traitsIJvEE12promise_type13final_suspendEv( // CHECK-NOT: call zeroext i1 @_ZN10final_susp11await_readyEv(%struct.final_susp* } @@ -287,13 +284,12 @@ struct AwaitResumeReturnsLValue { bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); RefTag& await_resume(); }; - -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object(); init_susp initial_suspend(); @@ -338,7 +334,7 @@ struct TailCallAwait { bool await_ready(); - std::experimental::coroutine_handle<> await_suspend(std::experimental::coroutine_handle<>); + std::coroutine_handle<> await_suspend(std::coroutine_handle<>); void await_resume(); }; @@ -346,9 +342,9 @@ extern "C" void TestTailcall() { co_await TailCallAwait{}; - // CHECK: %[[RESULT:.+]] = call i8* @_ZN13TailCallAwait13await_suspendENSt12experimental16coroutine_handleIvEE(%struct.TailCallAwait* - // CHECK: %[[COERCE:.+]] = getelementptr inbounds %"struct.std::experimental::coroutine_handle", %"struct.std::experimental::coroutine_handle"* %[[TMP:.+]], i32 0, i32 0 + // CHECK: %[[RESULT:.+]] = call i8* @_ZN13TailCallAwait13await_suspendESt16coroutine_handleIvE(%struct.TailCallAwait* + // CHECK: %[[COERCE:.+]] = getelementptr inbounds %"struct.std::coroutine_handle", %"struct.std::coroutine_handle"* %[[TMP:.+]], i32 0, i32 0 // CHECK: store i8* %[[RESULT]], i8** %[[COERCE]] - // CHECK: %[[ADDR:.+]] = call i8* @_ZNSt12experimental16coroutine_handleIvE7addressEv(%"struct.std::experimental::coroutine_handle"* {{[^,]*}} %[[TMP]]) + // CHECK: %[[ADDR:.+]] = call i8* @_ZNSt16coroutine_handleIvE7addressEv(%"struct.std::coroutine_handle"* {{[^,]*}} %[[TMP]]) // CHECK: call void @llvm.coro.resume(i8* %[[ADDR]]) } Index: clang/test/CodeGenCoroutines/coro-cleanup-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-cleanup-exp-namespace.cpp @@ -0,0 +1,99 @@ +// Verify that coroutine promise and allocated memory are freed up on exception. +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s + +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template <> struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type(); + void unhandled_exception() noexcept; + }; +}; + +struct Cleanup { + ~Cleanup(); +}; +void may_throw(); + +// CHECK-LABEL: define{{.*}} void @_Z1fv( +void f() { + // CHECK: call noalias nonnull i8* @_Znwm(i64 + + // If promise constructor throws, check that we free the memory. + + // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeC1Ev( + // CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]] + + // CHECK: [[DeallocPad]]: + // CHECK-NEXT: landingpad + // CHECK-NEXT: cleanup + // CHECK: br label %[[Dealloc:.+]] + + Cleanup cleanup; + may_throw(); + + // if may_throw throws, check that we destroy the promise and free the memory. + + // CHECK: invoke void @_Z9may_throwv( + // CHECK-NEXT: to label %{{.+}} unwind label %[[CatchPad:.+]] + + // CHECK: [[CatchPad]]: + // CHECK-NEXT: landingpad + // CHECK-NEXT: catch i8* null + // CHECK: call void @_ZN7CleanupD1Ev( + // CHECK: br label %[[Catch:.+]] + + // CHECK: [[Catch]]: + // CHECK: call i8* @__cxa_begin_catch( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv( + // CHECK: invoke void @__cxa_end_catch() + // CHECK-NEXT: to label %[[Cont:.+]] unwind + + // CHECK: [[Cont]]: + // CHECK-NEXT: br label %[[Cont2:.+]] + // CHECK: [[Cont2]]: + // CHECK-NEXT: br label %[[Cleanup:.+]] + + // CHECK: [[Cleanup]]: + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeD1Ev( + // CHECK: %[[Mem0:.+]] = call i8* @llvm.coro.free( + // CHECK: call void @_ZdlPv(i8* %[[Mem0]] + + // CHECK: [[Dealloc]]: + // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free( + // CHECK: call void @_ZdlPv(i8* %[[Mem]]) + + co_return; +} + +// CHECK-LABEL: define{{.*}} void @_Z1gv( +void g() { + for (;;) + co_await suspend_always{}; + // Since this is the endless loop there should be no fallthrough handler (call to 'return_void'). + // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv +} Index: clang/test/CodeGenCoroutines/coro-cleanup.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-cleanup.cpp +++ clang/test/CodeGenCoroutines/coro-cleanup.cpp @@ -1,7 +1,7 @@ // Verify that coroutine promise and allocated memory are freed up on exception. -// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -14,15 +14,15 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template <> struct std::experimental::coroutine_traits { +template <> struct std::coroutine_traits { struct promise_type { void get_return_object() noexcept; suspend_always initial_suspend() noexcept; @@ -43,7 +43,7 @@ // If promise constructor throws, check that we free the memory. - // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeC1Ev( + // CHECK: invoke void @_ZNSt16coroutine_traitsIJvEE12promise_typeC1Ev( // CHECK-NEXT: to label %{{.+}} unwind label %[[DeallocPad:.+]] // CHECK: [[DeallocPad]]: @@ -67,7 +67,7 @@ // CHECK: [[Catch]]: // CHECK: call i8* @__cxa_begin_catch( - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type19unhandled_exceptionEv( // CHECK: invoke void @__cxa_end_catch() // CHECK-NEXT: to label %[[Cont:.+]] unwind @@ -77,7 +77,7 @@ // CHECK-NEXT: br label %[[Cleanup:.+]] // CHECK: [[Cleanup]]: - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_typeD1Ev( + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_typeD1Ev( // CHECK: %[[Mem0:.+]] = call i8* @llvm.coro.free( // CHECK: call void @_ZdlPv(i8* %[[Mem0]] @@ -93,5 +93,5 @@ for (;;) co_await suspend_always{}; // Since this is the endless loop there should be no fallthrough handler (call to 'return_void'). - // CHECK-NOT: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv + // CHECK-NOT: call void @_ZNSt16coroutine_traitsIJvEE12promise_type11return_voidEv } Index: clang/test/CodeGenCoroutines/coro-dest-slot-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-dest-slot-exp-namespace.cpp @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine-exp-namespace.h" + +using namespace std::experimental; + +struct coro { + struct promise_type { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_never final_suspend() noexcept; + void return_void(); + static void unhandled_exception(); + }; +}; + +extern "C" coro f(int) { co_return; } +// Verify that cleanup.dest.slot is eliminated in a coroutine. +// CHECK-LABEL: f( +// CHECK: %[[INIT_SUSPEND:.+]] = call i8 @llvm.coro.suspend( +// CHECK-NEXT: switch i8 %[[INIT_SUSPEND]], label +// CHECK-NEXT: i8 0, label %[[INIT_READY:.+]] +// CHECK-NEXT: i8 1, label %[[INIT_CLEANUP:.+]] +// CHECK-NEXT: ] +// CHECK: %[[CLEANUP_DEST0:.+]] = phi i32 [ 0, %[[INIT_READY]] ], [ 2, %[[INIT_CLEANUP]] ] + +// CHECK: %[[FINAL_SUSPEND:.+]] = call i8 @llvm.coro.suspend( +// CHECK-NEXT: switch i8 %{{.*}}, label %coro.ret [ +// CHECK-NEXT: i8 0, label %[[FINAL_READY:.+]] +// CHECK-NEXT: i8 1, label %[[FINAL_CLEANUP:.+]] +// CHECK-NEXT: ] + +// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv( +// CHECK: %[[CLEANUP_DEST1:.+]] = phi i32 [ 0, %[[FINAL_READY]] ], [ 2, %[[FINAL_CLEANUP]] ] +// CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ %[[CLEANUP_DEST1]], %{{.+}} ], [ 0, %{{.+}} ] +// CHECK: call i8* @llvm.coro.free( +// CHECK: switch i32 %[[CLEANUP_DEST2]], label %{{.+}} [ +// CHECK-NEXT: i32 0 +// CHECK-NEXT: i32 2 +// CHECK-NEXT: ] Index: clang/test/CodeGenCoroutines/coro-dest-slot.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-dest-slot.cpp +++ clang/test/CodeGenCoroutines/coro-dest-slot.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s #include "Inputs/coroutine.h" -using namespace std::experimental; +using namespace std; struct coro { struct promise_type { @@ -30,7 +30,7 @@ // CHECK-NEXT: i8 1, label %[[FINAL_CLEANUP:.+]] // CHECK-NEXT: ] -// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv( +// CHECK: call void @_ZNSt13suspend_never12await_resumeEv( // CHECK: %[[CLEANUP_DEST1:.+]] = phi i32 [ 0, %[[FINAL_READY]] ], [ 2, %[[FINAL_CLEANUP]] ] // CHECK: %[[CLEANUP_DEST2:.+]] = phi i32 [ %[[CLEANUP_DEST0]], %{{.+}} ], [ %[[CLEANUP_DEST1]], %{{.+}} ], [ 0, %{{.+}} ] // CHECK: call i8* @llvm.coro.free( Index: clang/test/CodeGenCoroutines/coro-dwarf-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-dwarf-exp-namespace.cpp @@ -0,0 +1,72 @@ +// RUN: %clang_cc1 -disable-llvm-optzns -std=c++2a -fcoroutines-ts \ +// RUN: -triple=x86_64 -dwarf-version=4 -debug-info-kind=limited \ +// RUN: -emit-llvm -o - %s | \ +// RUN: FileCheck %s --implicit-check-not=DILocalVariable + +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type() noexcept; + void unhandled_exception() noexcept; + }; +}; + +// TODO: Not supported yet +struct CopyOnly { + int val; + CopyOnly(const CopyOnly &) noexcept; + CopyOnly(CopyOnly &&) = delete; + ~CopyOnly(); +}; + +struct MoveOnly { + int val; + MoveOnly(const MoveOnly &) = delete; + MoveOnly(MoveOnly &&) noexcept; + ~MoveOnly(); +}; + +struct MoveAndCopy { + int val; + MoveAndCopy(const MoveAndCopy &) noexcept; + MoveAndCopy(MoveAndCopy &&) noexcept; + ~MoveAndCopy(); +}; + +void consume(int, int, int) noexcept; + +void f_coro(int val, MoveOnly moParam, MoveAndCopy mcParam) { + consume(val, moParam.val, mcParam.val); + co_return; +} + +// CHECK: ![[SP:[0-9]+]] = distinct !DISubprogram(name: "f_coro", linkageName: "_Z6f_coroi8MoveOnly11MoveAndCopy" +// CHECK: !{{[0-9]+}} = !DILocalVariable(name: "val", arg: 1, scope: ![[SP]], file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) +// CHECK: !{{[0-9]+}} = !DILocalVariable(name: "moParam", arg: 2, scope: ![[SP]], file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) +// CHECK: !{{[0-9]+}} = !DILocalVariable(name: "mcParam", arg: 3, scope: ![[SP]], file: !{{[0-9]+}}, line: {{[0-9]+}}, type: !{{[0-9]+}}) +// CHECK: !{{[0-9]+}} = !DILocalVariable(name: "__promise", Index: clang/test/CodeGenCoroutines/coro-dwarf.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-dwarf.cpp +++ clang/test/CodeGenCoroutines/coro-dwarf.cpp @@ -1,9 +1,9 @@ -// RUN: %clang_cc1 -disable-llvm-optzns -std=c++2a -fcoroutines-ts \ +// RUN: %clang_cc1 -disable-llvm-optzns -std=c++20 \ // RUN: -triple=x86_64 -dwarf-version=4 -debug-info-kind=limited \ // RUN: -emit-llvm -o - %s | \ // RUN: FileCheck %s --implicit-check-not=DILocalVariable -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -16,15 +16,15 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} // namespace std::experimental +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template struct std::experimental::coroutine_traits { +template struct std::coroutine_traits { struct promise_type { void get_return_object() noexcept; suspend_always initial_suspend() noexcept; Index: clang/test/CodeGenCoroutines/coro-eh-cleanup-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-eh-cleanup-exp-namespace.cpp @@ -0,0 +1,88 @@ +// RUN: %clang_cc1 -std=c++1z -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++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s + +namespace std::experimental { +template struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +template struct coroutine_handle; + +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +template struct coroutine_handle : coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct coro_t { + struct promise_type { + coro_t get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + void unhandled_exception() noexcept; + }; +}; + +struct Cleanup { + ~Cleanup(); +}; +void may_throw(); + +coro_t f() { + Cleanup x; + may_throw(); + co_return; +} + +// CHECK: @"?f@@YA?AUcoro_t@@XZ"( +// CHECK: invoke void @"?may_throw@@YAXXZ"() +// CHECK: to label %[[CONT:.+]] unwind label %[[EHCLEANUP:.+]] +// CHECK: [[EHCLEANUP]]: +// CHECK: %[[INNERPAD:.+]] = cleanuppad within none [] +// CHECK: call void @"??1Cleanup@@QEAA@XZ"( +// CHECK: cleanupret from %{{.+}} unwind label %[[CATCHDISPATCH:.+]] + +// CHECK: [[CATCHDISPATCH]]: +// CHECK: catchswitch within none [label %[[CATCHPAD:.+]]] unwind label %[[COROENDBB:.+]] +// CHECK: [[CATCHPAD]]: +// CHECK: call void @"?unhandled_exception@promise_type@coro_t@@QEAAXXZ" + +// CHECK: [[COROENDBB]]: +// CHECK-NEXT: %[[CLPAD:.+]] = cleanuppad within none +// CHECK-NEXT: call i1 @llvm.coro.end(i8* null, i1 true) [ "funclet"(token %[[CLPAD]]) ] +// CHECK-NEXT: cleanupret from %[[CLPAD]] unwind label + +// CHECK-LPAD: @_Z1fv( +// CHECK-LPAD: invoke void @_Z9may_throwv() +// CHECK-LPAD: to label %[[CONT:.+]] unwind label %[[EHCLEANUP:.+]] +// CHECK-LPAD: [[EHCLEANUP]]: +// CHECK-LPAD: landingpad { i8*, i32 } +// CHECK-LPAD: catch +// CHECK-LPAD: call void @_ZN7CleanupD1Ev( +// CHECK-LPAD: call i8* @__cxa_begin_catch +// CHECK-LPAD: call void @_ZN6coro_t12promise_type19unhandled_exceptionEv +// CHECK-LPAD: invoke void @__cxa_end_catch() +// CHECK-LPAD: to label %{{.+}} unwind label %[[UNWINDBB:.+]] + +// CHECK-LPAD: [[UNWINDBB]]: +// CHECK-LPAD: %[[I1RESUME:.+]] = call i1 @llvm.coro.end(i8* null, i1 true) +// CHECK-LPAD: br i1 %[[I1RESUME]], label %[[EHRESUME:.+]], label +// CHECK-LPAD: [[EHRESUME]]: +// CHECK-LPAD-NEXT: %[[exn:.+]] = load i8*, i8** %exn.slot, align 8 +// CHECK-LPAD-NEXT: %[[sel:.+]] = load i32, i32* %ehselector.slot, align 4 +// CHECK-LPAD-NEXT: %[[val1:.+]] = insertvalue { i8*, i32 } undef, i8* %[[exn]], 0 +// CHECK-LPAD-NEXT: %[[val2:.+]] = insertvalue { i8*, i32 } %[[val1]], i32 %[[sel]], 1 +// CHECK-LPAD-NEXT: resume { i8*, i32 } %[[val2]] Index: clang/test/CodeGenCoroutines/coro-eh-cleanup.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-eh-cleanup.cpp +++ clang/test/CodeGenCoroutines/coro-eh-cleanup.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_cc1 -std=c++1z -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++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s +// RUN: %clang_cc1 -std=c++20 -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++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s -namespace std::experimental { +namespace std { template struct coroutine_traits { using promise_type = typename R::promise_type; }; @@ -18,11 +18,11 @@ coroutine_handle() = default; static coroutine_handle from_address(void *) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; Index: clang/test/CodeGenCoroutines/coro-gro-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-gro-exp-namespace.cpp @@ -0,0 +1,88 @@ +// Verifies lifetime of __gro local variable +// Verify that coroutine promise and allocated memory are freed up on exception. +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s + +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct GroType { + ~GroType(); + operator int() noexcept; +}; + +template <> struct std::experimental::coroutine_traits { + struct promise_type { + GroType get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type(); + void unhandled_exception() noexcept; + }; +}; + +struct Cleanup { + ~Cleanup(); +}; +void doSomething() noexcept; + +// CHECK: define{{.*}} i32 @_Z1fv( +int f() { + // CHECK: %[[RetVal:.+]] = alloca i32 + // CHECK: %[[GroActive:.+]] = alloca i1 + + // CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64() + // CHECK: call noalias nonnull i8* @_Znwm(i64 %[[Size]]) + // CHECK: store i1 false, i1* %[[GroActive]] + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeC1Ev( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv( + // CHECK: store i1 true, i1* %[[GroActive]] + + Cleanup cleanup; + doSomething(); + co_return; + + // CHECK: call void @_Z11doSomethingv( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type11return_voidEv( + // CHECK: call void @_ZN7CleanupD1Ev( + + // Destroy promise and free the memory. + + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeD1Ev( + // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free( + // CHECK: call void @_ZdlPv(i8* %[[Mem]]) + + // Initialize retval from Gro and destroy Gro + + // CHECK: %[[Conv:.+]] = call i32 @_ZN7GroTypecviEv( + // CHECK: store i32 %[[Conv]], i32* %[[RetVal]] + // CHECK: %[[IsActive:.+]] = load i1, i1* %[[GroActive]] + // CHECK: br i1 %[[IsActive]], label %[[CleanupGro:.+]], label %[[Done:.+]] + + // CHECK: [[CleanupGro]]: + // CHECK: call void @_ZN7GroTypeD1Ev( + // CHECK: br label %[[Done]] + + // CHECK: [[Done]]: + // CHECK: %[[LoadRet:.+]] = load i32, i32* %[[RetVal]] + // CHECK: ret i32 %[[LoadRet]] +} Index: clang/test/CodeGenCoroutines/coro-gro-nrvo-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-gro-nrvo-exp-namespace.cpp @@ -0,0 +1,85 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine-exp-namespace.h" + +using namespace std::experimental; + +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; + +template +struct promise_type { + RetObject get_return_object(); + suspend_always initial_suspend(); + suspend_never final_suspend() noexcept; + void return_void(); + static void unhandled_exception(); +}; + +struct coro { + using promise_type = promise_type; + coro(coro const &); + struct Impl; + Impl *impl; +}; + +// Verify that the NRVO is applied to the Gro object. +// CHECK-LABEL: define{{.*}} void @_Z1fi(%struct.coro* noalias sret(%struct.coro) align 8 %agg.result, i32 %0) +coro f(int) { + // CHECK: %call = call noalias nonnull i8* @_Znwm( + // CHECK-NEXT: br label %[[CoroInit:.*]] + + // CHECK: {{.*}}[[CoroInit]]: + // CHECK: store i1 false, i1* %gro.active + // CHECK: call void @{{.*get_return_objectEv}}(%struct.coro* sret(%struct.coro) align 8 %agg.result + // CHECK-NEXT: store i1 true, i1* %gro.active + co_return; +} + +template +struct promise_type_with_on_alloc_failure { + static RetObject get_return_object_on_allocation_failure(); + RetObject get_return_object(); + suspend_always initial_suspend(); + suspend_never final_suspend() noexcept; + void return_void(); + static void unhandled_exception(); +}; + +struct coro_two { + using promise_type = promise_type_with_on_alloc_failure; + coro_two(coro_two const &); + struct Impl; + Impl *impl; +}; + +// Verify that the NRVO is applied to the Gro object. +// CHECK-LABEL: define{{.*}} void @_Z1hi(%struct.coro_two* noalias sret(%struct.coro_two) align 8 %agg.result, i32 %0) +coro_two h(int) { + + // CHECK: %call = call noalias i8* @_ZnwmRKSt9nothrow_t + // CHECK-NEXT: %[[CheckNull:.*]] = icmp ne i8* %call, null + // CHECK-NEXT: br i1 %[[CheckNull]], label %[[InitOnSuccess:.*]], label %[[InitOnFailure:.*]] + + // CHECK: {{.*}}[[InitOnFailure]]: + // CHECK-NEXT: call void @{{.*get_return_object_on_allocation_failureEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result + // CHECK-NEXT: br label %[[RetLabel:.*]] + + // CHECK: {{.*}}[[InitOnSuccess]]: + // CHECK: store i1 false, i1* %gro.active + // CHECK: call void @{{.*get_return_objectEv}}(%struct.coro_two* sret(%struct.coro_two) align 8 %agg.result + // CHECK-NEXT: store i1 true, i1* %gro.active + + // CHECK: [[RetLabel]]: + // CHECK-NEXT: ret void + co_return; +} Index: clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp +++ clang/test/CodeGenCoroutines/coro-gro-nrvo.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++20 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s #include "Inputs/coroutine.h" -using namespace std::experimental; +using namespace std; namespace std { Index: clang/test/CodeGenCoroutines/coro-gro.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-gro.cpp +++ clang/test/CodeGenCoroutines/coro-gro.cpp @@ -1,8 +1,8 @@ // Verifies lifetime of __gro local variable // Verify that coroutine promise and allocated memory are freed up on exception. -// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -15,11 +15,11 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; @@ -28,7 +28,7 @@ operator int() noexcept; }; -template <> struct std::experimental::coroutine_traits { +template <> struct std::coroutine_traits { struct promise_type { GroType get_return_object() noexcept; suspend_always initial_suspend() noexcept; @@ -51,8 +51,8 @@ // CHECK: %[[Size:.+]] = call i64 @llvm.coro.size.i64() // CHECK: call noalias nonnull i8* @_Znwm(i64 %[[Size]]) // CHECK: store i1 false, i1* %[[GroActive]] - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeC1Ev( - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type17get_return_objectEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_typeC1Ev( + // CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type17get_return_objectEv( // CHECK: store i1 true, i1* %[[GroActive]] Cleanup cleanup; @@ -60,12 +60,12 @@ co_return; // CHECK: call void @_Z11doSomethingv( - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type11return_voidEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type11return_voidEv( // CHECK: call void @_ZN7CleanupD1Ev( // Destroy promise and free the memory. - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_typeD1Ev( + // CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_typeD1Ev( // CHECK: %[[Mem:.+]] = call i8* @llvm.coro.free( // CHECK: call void @_ZdlPv(i8* %[[Mem]]) Index: clang/test/CodeGenCoroutines/coro-lambda-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-lambda-exp-namespace.cpp @@ -0,0 +1,61 @@ +// Verify that we synthesized the coroutine for a lambda inside of a function template. +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s + +namespace std::experimental { +template struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +template struct coroutine_handle; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +template struct coroutine_handle : coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct Task { + struct promise_type { + Task get_return_object(); + void return_void() {} + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void unhandled_exception() noexcept; + }; +}; + +template auto SyncAwait(_AwrT &&A) { + if (!A.await_ready()) { + auto AwaitAsync = [&]() -> Task { + try { + (void)(co_await A); + } catch (...) { + } + }; + Task t = AwaitAsync(); + } + return A.await_resume(); +} + +void f() { + suspend_always test; + SyncAwait(test); +} + +// Verify that we synthesized the coroutine for a lambda inside SyncAwait +// CHECK-LABEL: define linkonce_odr void @_ZZ9SyncAwaitIR14suspend_alwaysEDaOT_ENKUlvE_clEv( +// CHECK: alloca %"struct.Task::promise_type" +// CHECK: call token @llvm.coro.id( +// CHECK: call i8 @llvm.coro.suspend( +// CHECK: call i1 @llvm.coro.end( Index: clang/test/CodeGenCoroutines/coro-lambda.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-lambda.cpp +++ clang/test/CodeGenCoroutines/coro-lambda.cpp @@ -1,7 +1,7 @@ // Verify that we synthesized the coroutine for a lambda inside of a function template. -// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits { using promise_type = typename R::promise_type; }; @@ -17,11 +17,11 @@ coroutine_handle() = default; static coroutine_handle from_address(void *) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; Index: clang/test/CodeGenCoroutines/coro-newpm-pipeline-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-newpm-pipeline-exp-namespace.cpp @@ -0,0 +1,45 @@ +// Tests that coroutine passes are added to and run by the new pass manager +// pipeline, at -O0 and above. + +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -o /dev/null \ +// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -fcoroutines-ts \ +// RUN: -O0 %s 2>&1 | FileCheck %s --check-prefixes=CHECK-ALL +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -o /dev/null \ +// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -fcoroutines-ts \ +// RUN: -O1 %s 2>&1 | FileCheck %s --check-prefixes=CHECK-ALL,CHECK-OPT +// +// CHECK-ALL: Running pass:{{.*}}CoroEarlyPass +// +// CHECK-ALL: Running pass: CoroSplitPass on (_Z3foov) +// CHECK-OPT: Running pass:{{.*}}CoroElidePass{{.*}} on {{.*}}_Z3foov{{.*}} +// +// CHECK-ALL: Running pass:{{.*}}CoroCleanupPass + +namespace std { +namespace experimental { + +struct handle {}; + +struct awaitable { + bool await_ready() noexcept { return false; } + void await_suspend(handle) noexcept {} + bool await_resume() noexcept { return true; } +}; + +template struct coroutine_handle { + static handle from_address(void *address) noexcept { return {}; } +}; + +template struct coroutine_traits { + struct promise_type { + awaitable initial_suspend() { return {}; } + awaitable final_suspend() noexcept { return {}; } + void return_void() {} + T get_return_object() { return T(); } + void unhandled_exception() {} + }; +}; +} // namespace experimental +} // namespace std + +void foo() { co_return; } Index: clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp +++ clang/test/CodeGenCoroutines/coro-newpm-pipeline.cpp @@ -2,10 +2,10 @@ // pipeline, at -O0 and above. // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -o /dev/null \ -// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -fcoroutines-ts \ +// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -std=c++20 \ // RUN: -O0 %s 2>&1 | FileCheck %s --check-prefixes=CHECK-ALL // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm-bc -o /dev/null \ -// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -fcoroutines-ts \ +// RUN: -fexperimental-new-pass-manager -fdebug-pass-manager -std=c++20 \ // RUN: -O1 %s 2>&1 | FileCheck %s --check-prefixes=CHECK-ALL,CHECK-OPT // // CHECK-ALL: Running pass:{{.*}}CoroEarlyPass @@ -16,7 +16,6 @@ // CHECK-ALL: Running pass:{{.*}}CoroCleanupPass namespace std { -namespace experimental { struct handle {}; @@ -39,7 +38,6 @@ void unhandled_exception() {} }; }; -} // namespace experimental } // namespace std void foo() { co_return; } Index: clang/test/CodeGenCoroutines/coro-params-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-params-exp-namespace.cpp @@ -0,0 +1,205 @@ +// Verifies that parameters are copied with move constructors +// Verifies that parameter copies are destroyed +// Vefifies that parameter copies are used in the body of the coroutine +// Verifies that parameter copies are used to construct the promise type, if that type has a matching constructor +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s + +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + promise_type(); + ~promise_type() noexcept; + void unhandled_exception() noexcept; + }; +}; + +// TODO: Not supported yet +struct CopyOnly { + int val; + CopyOnly(const CopyOnly &) noexcept; + CopyOnly(CopyOnly &&) = delete; + ~CopyOnly(); +}; + +struct MoveOnly { + int val; + MoveOnly(const MoveOnly &) = delete; + MoveOnly(MoveOnly &&) noexcept; + ~MoveOnly(); +}; + +struct MoveAndCopy { + int val; + MoveAndCopy(const MoveAndCopy &) noexcept; + MoveAndCopy(MoveAndCopy &&) noexcept; + ~MoveAndCopy(); +}; + +void consume(int, int, int) noexcept; + +// TODO: Add support for CopyOnly params +// CHECK: define{{.*}} void @_Z1fi8MoveOnly11MoveAndCopy(i32 %val, %struct.MoveOnly* %[[MoParam:.+]], %struct.MoveAndCopy* %[[McParam:.+]]) #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8* +void f(int val, MoveOnly moParam, MoveAndCopy mcParam) { + // CHECK: %[[MoCopy:.+]] = alloca %struct.MoveOnly + // CHECK: %[[McCopy:.+]] = alloca %struct.MoveAndCopy + // CHECK: store i32 %val, i32* %[[ValAddr:.+]] + + // CHECK: call i8* @llvm.coro.begin( + // CHECK: call void @_ZN8MoveOnlyC1EOS_(%struct.MoveOnly* {{[^,]*}} %[[MoCopy]], %struct.MoveOnly* nonnull align 4 dereferenceable(4) %[[MoParam]]) + // CHECK-NEXT: bitcast %struct.MoveAndCopy* %[[McCopy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(%struct.MoveAndCopy* {{[^,]*}} %[[McCopy]], %struct.MoveAndCopy* nonnull align 4 dereferenceable(4) %[[McParam]]) # + // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev( + + // CHECK: call void @_ZN14suspend_always12await_resumeEv( + // CHECK: %[[IntParam:.+]] = load i32, i32* %{{.*}} + // CHECK: %[[MoGep:.+]] = getelementptr inbounds %struct.MoveOnly, %struct.MoveOnly* %[[MoCopy]], i32 0, i32 0 + // CHECK: %[[MoVal:.+]] = load i32, i32* %[[MoGep]] + // CHECK: %[[McGep:.+]] = getelementptr inbounds %struct.MoveAndCopy, %struct.MoveAndCopy* %[[McCopy]], i32 0, i32 0 + // CHECK: %[[McVal:.+]] = load i32, i32* %[[McGep]] + // CHECK: call void @_Z7consumeiii(i32 %[[IntParam]], i32 %[[MoVal]], i32 %[[McVal]]) + + consume(val, moParam.val, mcParam.val); + co_return; + + // Skip to final suspend: + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_type13final_suspendEv( + // CHECK: call void @_ZN14suspend_always12await_resumeEv( + + // Destroy promise, then parameter copies: + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeD1Ev(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise) + // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK-NEXT: call void @llvm.lifetime.end.p0i8( + // CHECK-NEXT: call void @_ZN11MoveAndCopyD1Ev(%struct.MoveAndCopy* {{[^,]*}} %[[McCopy]]) + // CHECK-NEXT: bitcast %struct.MoveAndCopy* %[[McCopy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end.p0i8( + // CHECK-NEXT: call void @_ZN8MoveOnlyD1Ev(%struct.MoveOnly* {{[^,]*}} %[[MoCopy]] + // CHECK-NEXT: bitcast %struct.MoveOnly* %[[MoCopy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.end.p0i8( + // CHECK-NEXT: bitcast i32* %{{.+}} to i8* + // CHECK-NEXT: call void @llvm.lifetime.end.p0i8( + // CHECK-NEXT: call i8* @llvm.coro.free( +} + +// CHECK-LABEL: void @_Z16dependent_paramsI1A1BEvT_T0_S3_(%struct.A* %x, %struct.B* %0, %struct.B* %y) +template +void dependent_params(T x, U, U y) { + // CHECK: %[[x_copy:.+]] = alloca %struct.A + // CHECK-NEXT: %[[unnamed_copy:.+]] = alloca %struct.B + // CHECK-NEXT: %[[y_copy:.+]] = alloca %struct.B + + // CHECK: call i8* @llvm.coro.begin + // CHECK-NEXT: bitcast %struct.A* %[[x_copy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: call void @_ZN1AC1EOS_(%struct.A* {{[^,]*}} %[[x_copy]], %struct.A* nonnull align 4 dereferenceable(512) %x) + // CHECK-NEXT: bitcast %struct.B* %[[unnamed_copy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: call void @_ZN1BC1EOS_(%struct.B* {{[^,]*}} %[[unnamed_copy]], %struct.B* nonnull align 4 dereferenceable(512) %0) + // CHECK-NEXT: bitcast %struct.B* %[[y_copy]] to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: call void @_ZN1BC1EOS_(%struct.B* {{[^,]*}} %[[y_copy]], %struct.B* nonnull align 4 dereferenceable(512) %y) + // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( + // CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJv1A1BS2_EE12promise_typeC1Ev( + + co_return; +} + +struct A { + int WontFitIntoRegisterForSure[128]; + A(); + A(A &&) + noexcept; + ~A(); +}; + +struct B { + int WontFitIntoRegisterForSure[128]; + B(); + B(B &&) + noexcept; + ~B(); +}; + +void call_dependent_params() { + dependent_params(A{}, B{}, B{}); +} + +// Test that, when the promise type has a constructor whose signature matches +// that of the coroutine function, that constructor is used. This is an +// experimental feature that will be proposed for the Coroutines TS. + +struct promise_matching_constructor {}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + promise_type(promise_matching_constructor, int, float, double) {} + promise_type() = delete; + void get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() {} + }; +}; + +// CHECK-LABEL: void @_Z38coroutine_matching_promise_constructor28promise_matching_constructorifd(i32 %0, float %1, double %2) +void coroutine_matching_promise_constructor(promise_matching_constructor, int, float, double) { + // CHECK: %[[INT:.+]] = load i32, i32* %5, align 4 + // CHECK: %[[FLOAT:.+]] = load float, float* %6, align 4 + // CHECK: %[[DOUBLE:.+]] = load double, double* %7, align 8 + // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJv28promise_matching_constructorifdEE12promise_typeC1ES1_ifd(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) + co_return; +} + +struct some_class; + +struct method {}; + +template struct std::experimental::coroutine_traits { + struct promise_type { + promise_type(some_class &, float); + method get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + }; +}; + +struct some_class { + method good_coroutine_calls_custom_constructor(float); +}; + +// CHECK-LABEL: define{{.*}} void @_ZN10some_class39good_coroutine_calls_custom_constructorEf(%struct.some_class* +method some_class::good_coroutine_calls_custom_constructor(float) { + // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJ6methodR10some_classfEE12promise_typeC1ES3_f(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, %struct.some_class* nonnull align 1 dereferenceable(1) %{{.+}}, float + co_return; +} Index: clang/test/CodeGenCoroutines/coro-params.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-params.cpp +++ clang/test/CodeGenCoroutines/coro-params.cpp @@ -2,9 +2,9 @@ // Verifies that parameter copies are destroyed // Vefifies that parameter copies are used in the body of the coroutine // Verifies that parameter copies are used to construct the promise type, if that type has a matching constructor -// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes -fexceptions | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -17,15 +17,15 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template struct std::experimental::coroutine_traits { +template struct std::coroutine_traits { struct promise_type { void get_return_object() noexcept; suspend_always initial_suspend() noexcept; @@ -73,9 +73,9 @@ // CHECK-NEXT: bitcast %struct.MoveAndCopy* %[[McCopy]] to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( // CHECK-NEXT: call void @_ZN11MoveAndCopyC1EOS_(%struct.MoveAndCopy* {{[^,]*}} %[[McCopy]], %struct.MoveAndCopy* nonnull align 4 dereferenceable(4) %[[McParam]]) # - // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK-NEXT: bitcast %"struct.std::coroutine_traits::promise_type"* %__promise to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( - // CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev( + // CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeC1Ev( // CHECK: call void @_ZN14suspend_always12await_resumeEv( // CHECK: %[[IntParam:.+]] = load i32, i32* %{{.*}} @@ -89,12 +89,12 @@ co_return; // Skip to final suspend: - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_type13final_suspendEv( + // CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_type13final_suspendEv( // CHECK: call void @_ZN14suspend_always12await_resumeEv( // Destroy promise, then parameter copies: - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeD1Ev(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise) - // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK: call void @_ZNSt16coroutine_traitsIJvi8MoveOnly11MoveAndCopyEE12promise_typeD1Ev(%"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %__promise) + // CHECK-NEXT: bitcast %"struct.std::coroutine_traits::promise_type"* %__promise to i8* // CHECK-NEXT: call void @llvm.lifetime.end.p0i8( // CHECK-NEXT: call void @_ZN11MoveAndCopyD1Ev(%struct.MoveAndCopy* {{[^,]*}} %[[McCopy]]) // CHECK-NEXT: bitcast %struct.MoveAndCopy* %[[McCopy]] to i8* @@ -124,9 +124,9 @@ // CHECK-NEXT: bitcast %struct.B* %[[y_copy]] to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( // CHECK-NEXT: call void @_ZN1BC1EOS_(%struct.B* {{[^,]*}} %[[y_copy]], %struct.B* nonnull align 4 dereferenceable(512) %y) - // CHECK-NEXT: bitcast %"struct.std::experimental::coroutine_traits::promise_type"* %__promise to i8* + // CHECK-NEXT: bitcast %"struct.std::coroutine_traits::promise_type"* %__promise to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( - // CHECK-NEXT: invoke void @_ZNSt12experimental16coroutine_traitsIJv1A1BS2_EE12promise_typeC1Ev( + // CHECK-NEXT: invoke void @_ZNSt16coroutine_traitsIJv1A1BS1_EE12promise_typeC1Ev( co_return; } @@ -155,8 +155,8 @@ struct promise_matching_constructor {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { promise_type(promise_matching_constructor, int, float, double) {} promise_type() = delete; @@ -173,7 +173,7 @@ // CHECK: %[[INT:.+]] = load i32, i32* %5, align 4 // CHECK: %[[FLOAT:.+]] = load float, float* %6, align 4 // CHECK: %[[DOUBLE:.+]] = load double, double* %7, align 8 - // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJv28promise_matching_constructorifdEE12promise_typeC1ES1_ifd(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) + // CHECK: invoke void @_ZNSt16coroutine_traitsIJv28promise_matching_constructorifdEE12promise_typeC1ES0_ifd(%"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 %[[INT]], float %[[FLOAT]], double %[[DOUBLE]]) co_return; } @@ -181,7 +181,7 @@ struct method {}; -template struct std::experimental::coroutine_traits { +template struct std::coroutine_traits { struct promise_type { promise_type(some_class&, float); method get_return_object(); @@ -198,6 +198,6 @@ // CHECK-LABEL: define{{.*}} void @_ZN10some_class39good_coroutine_calls_custom_constructorEf(%struct.some_class* method some_class::good_coroutine_calls_custom_constructor(float) { - // CHECK: invoke void @_ZNSt12experimental16coroutine_traitsIJ6methodR10some_classfEE12promise_typeC1ES3_f(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, %struct.some_class* nonnull align 1 dereferenceable(1) %{{.+}}, float + // CHECK: invoke void @_ZNSt16coroutine_traitsIJ6methodR10some_classfEE12promise_typeC1ES2_f(%"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %__promise, %struct.some_class* nonnull align 1 dereferenceable(1) %{{.+}}, float co_return; } Index: clang/test/CodeGenCoroutines/coro-promise-dtor-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-promise-dtor-exp-namespace.cpp @@ -0,0 +1,49 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// -triple=x86_64-unknown-linux-gnu + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +struct coro_t { + void *p; + ~coro_t(); + struct promise_type { + coro_t get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend() noexcept; + void return_void(); + promise_type(); + ~promise_type(); + void unhandled_exception(); + }; +}; + +struct Cleanup { + ~Cleanup(); +}; +void may_throw(); + +coro_t f() { + Cleanup cleanup; + may_throw(); + co_return; +} + +// CHECK-LABEL: define dso_local void @"?f@@YA?AUcoro_t@@XZ"( +// CHECK: %gro.active = alloca i1 +// CHECK: store i1 false, i1* %gro.active + +// CHECK: invoke %"struct.coro_t::promise_type"* @"??0promise_type@coro_t@@QEAA@XZ"( +// CHECK: invoke void @"?get_return_object@promise_type@coro_t@@QEAA?AU2@XZ"( +// CHECK: store i1 true, i1* %gro.active + +// CHECK: %[[IS_ACTIVE:.+]] = load i1, i1* %gro.active +// CHECK: br i1 %[[IS_ACTIVE]], label %[[CLEANUP1:.+]], label + +// CHECK: [[CLEANUP1]]: +// CHECK: %[[NRVO:.+]] = load i1, i1* %nrvo +// CHECK: br i1 %[[NRVO]], label %{{.+}}, label %[[DTOR:.+]] + +// CHECK: [[DTOR]]: +// CHECK: call void @"??1coro_t@@QEAA@XZ"( Index: clang/test/CodeGenCoroutines/coro-promise-dtor.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-promise-dtor.cpp +++ clang/test/CodeGenCoroutines/coro-promise-dtor.cpp @@ -1,17 +1,15 @@ -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-pc-windows-msvc18.0.0 -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck %s // -triple=x86_64-unknown-linux-gnu #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - struct coro_t { void* p; ~coro_t(); struct promise_type { coro_t get_return_object(); - coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend() noexcept; + std::suspend_never initial_suspend(); + std::suspend_never final_suspend() noexcept; void return_void(); promise_type(); ~promise_type(); Index: clang/test/CodeGenCoroutines/coro-ret-void-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-ret-void-exp-namespace.cpp @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +struct coro1 { + struct promise_type { + coro1 get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend() noexcept; + void return_void(); + }; +}; + +coro1 f() { + co_await coro::suspend_never{}; +} + +// CHECK-LABEL: define{{.*}} void @_Z1fv( +// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZN5coro112promise_type11return_voidEv(%"struct.coro1::promise_type"* {{[^,]*}} %__promise) + +struct A { + A(); + ~A(); +}; + +coro1 f2() { + co_return(void) A{}; +} + +// CHECK-LABEL: define{{.*}} void @_Z2f2v( +// CHECK: call void @_ZN1AC1Ev(%struct.A* {{[^,]*}} %[[AVar:.*]]) +// CHECK-NEXT: call void @_ZN1AD1Ev(%struct.A* {{[^,]*}} %[[AVar]]) +// CHECK-NEXT: call void @_ZN5coro112promise_type11return_voidEv(%"struct.coro1::promise_type"* + +struct coro2 { + struct promise_type { + coro2 get_return_object(); + coro::suspend_never initial_suspend(); + coro::suspend_never final_suspend() noexcept; + void return_value(int); + }; +}; + +coro2 g() { + co_return 42; +} + +// CHECK-LABEL: define{{.*}} void @_Z1gv( +// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZN5coro212promise_type12return_valueEi(%"struct.coro2::promise_type"* {{[^,]*}} %__promise, i32 42) Index: clang/test/CodeGenCoroutines/coro-ret-void.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-ret-void.cpp +++ clang/test/CodeGenCoroutines/coro-ret-void.cpp @@ -1,24 +1,22 @@ -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -triple=x86_64-unknown-linux-gnu -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - struct coro1 { struct promise_type { coro1 get_return_object(); - coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend() noexcept; + std::suspend_never initial_suspend(); + std::suspend_never final_suspend() noexcept; void return_void(); }; }; coro1 f() { - co_await coro::suspend_never{}; + co_await std::suspend_never{}; } // CHECK-LABEL: define{{.*}} void @_Z1fv( -// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZNSt13suspend_never12await_resumeEv(%"struct.std::suspend_never"* // CHECK: call void @_ZN5coro112promise_type11return_voidEv(%"struct.coro1::promise_type"* {{[^,]*}} %__promise) struct A { @@ -38,8 +36,8 @@ struct coro2 { struct promise_type { coro2 get_return_object(); - coro::suspend_never initial_suspend(); - coro::suspend_never final_suspend() noexcept; + std::suspend_never initial_suspend(); + std::suspend_never final_suspend() noexcept; void return_value(int); }; }; @@ -49,5 +47,5 @@ } // CHECK-LABEL: define{{.*}} void @_Z1gv( -// CHECK: call void @_ZNSt12experimental13coroutines_v113suspend_never12await_resumeEv(%"struct.std::experimental::coroutines_v1::suspend_never"* +// CHECK: call void @_ZNSt13suspend_never12await_resumeEv(%"struct.std::suspend_never"* // CHECK: call void @_ZN5coro212promise_type12return_valueEi(%"struct.coro2::promise_type"* {{[^,]*}} %__promise, i32 42) Index: clang/test/CodeGenCoroutines/coro-return-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-return-exp-namespace.cpp @@ -0,0 +1,59 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept { return {}; } +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) { return {}; } + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept {} +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template <> struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + }; +}; + +// CHECK-LABEL: f0( +extern "C" void f0() { + // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %call = call noalias nonnull i8* @_Znwm( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise) + // CHECK: call void @_ZdlPv + co_return; +} + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + int get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_value(int); + }; +}; + +// CHECK-LABEL: f1( +extern "C" int f1() { + // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %call = call noalias nonnull i8* @_Znwm( + // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type12return_valueEi(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 42) + // CHECK: call void @_ZdlPv + co_return 42; +} Index: clang/test/CodeGenCoroutines/coro-return-voidtype-initlist-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-return-voidtype-initlist-exp-namespace.cpp @@ -0,0 +1,81 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +namespace std { +template +struct b { b(int, a); }; +template +struct c {}; +namespace experimental { +template +struct coroutine_traits : d {}; +template +struct coroutine_handle; +template <> +struct coroutine_handle<> {}; +template +struct coroutine_handle : coroutine_handle<> { + static coroutine_handle from_address(void *) noexcept; +}; +struct e { + int await_ready(); + void await_suspend(coroutine_handle<>); + void await_resume(); +}; +} // namespace experimental +} // namespace std +template +auto ah(ag) { return ag().ah(0); } +template +struct f; +struct g { + struct h { + int await_ready() noexcept; + template + void await_suspend(std::experimental::coroutine_handle) noexcept; + void await_resume() noexcept; + }; + std::experimental::e initial_suspend(); + h final_suspend() noexcept; + template + auto await_transform(ag) { return ah(ag()); } +}; +struct j : g { + f>> get_return_object(); + void return_value(std::b>); + void unhandled_exception(); +}; +struct k { + k(std::experimental::coroutine_handle<>); + int await_ready(); +}; +template +struct f { + using promise_type = j; + std::experimental::coroutine_handle<> ar; + struct l : k { + using at = k; + l(std::experimental::coroutine_handle<> m) : at(m) {} + void await_suspend(std::experimental::coroutine_handle<>); + }; + struct n : l { + n(std::experimental::coroutine_handle<> m) : l(m) {} + am await_resume(); + }; + auto ah(int) { return n(ar); } +}; +template +auto ax(std::c, aw) -> f>; +template +struct J { static f>> bo(); }; +// CHECK-LABEL: _ZN1JIiE2boEv( +template +f>> J::bo() { + std::c bu; + int bw(0); + // CHECK: void @_ZN1j12return_valueESt1bISt1cIiiEE(%struct.j* {{[^,]*}} %__promise) + co_return {0, co_await ax(bu, bw)}; +} +void bh() { + auto cn = [] { J::bo; }; + cn(); +} Index: clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp +++ clang/test/CodeGenCoroutines/coro-return-voidtype-initlist.cpp @@ -1,11 +1,10 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s namespace std { template struct b { b(int, a); }; template struct c {}; -namespace experimental { template struct coroutine_traits : d {}; template @@ -21,7 +20,6 @@ void await_suspend(coroutine_handle<>); void await_resume(); }; -} // namespace experimental } // namespace std template auto ah(ag) { return ag().ah(0); } @@ -31,10 +29,10 @@ struct h { int await_ready() noexcept; template - void await_suspend(std::experimental::coroutine_handle) noexcept; + void await_suspend(std::coroutine_handle) noexcept; void await_resume() noexcept; }; - std::experimental::e initial_suspend(); + std::e initial_suspend(); h final_suspend() noexcept; template auto await_transform(ag) { return ah(ag()); } @@ -45,20 +43,20 @@ void unhandled_exception(); }; struct k { - k(std::experimental::coroutine_handle<>); + k(std::coroutine_handle<>); int await_ready(); }; template struct f { using promise_type = j; - std::experimental::coroutine_handle<> ar; + std::coroutine_handle<> ar; struct l : k { using at = k; - l(std::experimental::coroutine_handle<> m) : at(m) {} - void await_suspend(std::experimental::coroutine_handle<>); + l(std::coroutine_handle<> m) : at(m) {} + void await_suspend(std::coroutine_handle<>); }; struct n : l { - n(std::experimental::coroutine_handle<> m) : l(m) {} + n(std::coroutine_handle<> m) : l(m) {} am await_resume(); }; auto ah(int) { return n(ar); } Index: clang/test/CodeGenCoroutines/coro-return.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-return.cpp +++ clang/test/CodeGenCoroutines/coro-return.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++1z -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -13,15 +13,15 @@ template coroutine_handle(coroutine_handle) noexcept {} }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template <> struct std::experimental::coroutine_traits { +template <> struct std::coroutine_traits { struct promise_type { void get_return_object(); suspend_always initial_suspend(); @@ -32,15 +32,15 @@ // CHECK-LABEL: f0( extern "C" void f0() { - // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %__promise = alloca %"struct.std::coroutine_traits::promise_type" // CHECK: %call = call noalias nonnull i8* @_Znwm( - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJvEE12promise_type11return_voidEv(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise) + // CHECK: call void @_ZNSt16coroutine_traitsIJvEE12promise_type11return_voidEv(%"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %__promise) // CHECK: call void @_ZdlPv co_return; } -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { int get_return_object(); suspend_always initial_suspend(); @@ -51,9 +51,9 @@ // CHECK-LABEL: f1( extern "C" int f1() { - // CHECK: %__promise = alloca %"struct.std::experimental::coroutine_traits::promise_type" + // CHECK: %__promise = alloca %"struct.std::coroutine_traits::promise_type" // CHECK: %call = call noalias nonnull i8* @_Znwm( - // CHECK: call void @_ZNSt12experimental16coroutine_traitsIJiEE12promise_type12return_valueEi(%"struct.std::experimental::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 42) + // CHECK: call void @_ZNSt16coroutine_traitsIJiEE12promise_type12return_valueEi(%"struct.std::coroutine_traits::promise_type"* {{[^,]*}} %__promise, i32 42) // CHECK: call void @_ZdlPv co_return 42; } Index: clang/test/CodeGenCoroutines/coro-symmetric-transfer-01-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-symmetric-transfer-01-exp-namespace.cpp @@ -0,0 +1,63 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -O0 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang -fcoroutines-ts -std=c++14 -O0 -emit-llvm -c %s -o %t -Xclang -disable-llvm-passes && %clang -c %t + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +struct detached_task { + struct promise_type { + detached_task get_return_object() noexcept { + return detached_task{coro::coroutine_handle::from_promise(*this)}; + } + + void return_void() noexcept {} + + struct final_awaiter { + bool await_ready() noexcept { return false; } + coro::coroutine_handle<> await_suspend(coro::coroutine_handle h) noexcept { + h.destroy(); + return {}; + } + void await_resume() noexcept {} + }; + + void unhandled_exception() noexcept {} + + final_awaiter final_suspend() noexcept { return {}; } + + coro::suspend_always initial_suspend() noexcept { return {}; } + }; + + ~detached_task() { + if (coro_) { + coro_.destroy(); + coro_ = {}; + } + } + + void start() && { + auto tmp = coro_; + coro_ = {}; + tmp.resume(); + } + + coro::coroutine_handle coro_; +}; + +detached_task foo() { + co_return; +} + +// check that the lifetime of the coroutine handle used to obtain the address is contained within single basic block, and hence does not live across suspension points. +// CHECK-LABEL: final.suspend: +// CHECK: %{{.+}} = call token @llvm.coro.save(i8* null) +// CHECK: %[[HDL_CAST1:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL:.+]] to i8* +// CHECK: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HDL_CAST1]]) +// CHECK: %[[CALL:.+]] = call i8* @_ZN13detached_task12promise_type13final_awaiter13await_suspendENSt12experimental13coroutines_v116coroutine_handleIS0_EE( +// CHECK: %[[HDL_CAST2:.+]] = getelementptr inbounds %"struct.std::experimental::coroutines_v1::coroutine_handle.0", %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL]], i32 0, i32 0 +// CHECK: store i8* %[[CALL]], i8** %[[HDL_CAST2]], align 8 +// CHECK: %[[HDL_TRANSFER:.+]] = call i8* @_ZNKSt12experimental13coroutines_v116coroutine_handleIvE7addressEv(%"struct.std::experimental::coroutines_v1::coroutine_handle.0"* nonnull align 8 dereferenceable(8) %[[HDL]]) +// CHECK: %[[HDL_CAST3:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL]] to i8* +// CHECK: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HDL_CAST3]]) +// CHECK: call void @llvm.coro.resume(i8* %[[HDL_TRANSFER]]) Index: clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp +++ clang/test/CodeGenCoroutines/coro-symmetric-transfer-01.cpp @@ -1,21 +1,19 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -O0 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s -// RUN: %clang -fcoroutines-ts -std=c++14 -O0 -emit-llvm -c %s -o %t -Xclang -disable-llvm-passes && %clang -c %t +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O0 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang -std=c++20 -O0 -emit-llvm -c %s -o %t -Xclang -disable-llvm-passes && %clang -c %t #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - struct detached_task { struct promise_type { detached_task get_return_object() noexcept { - return detached_task{coro::coroutine_handle::from_promise(*this)}; + return detached_task{std::coroutine_handle::from_promise(*this)}; } void return_void() noexcept {} struct final_awaiter { bool await_ready() noexcept { return false; } - coro::coroutine_handle<> await_suspend(coro::coroutine_handle h) noexcept { + std::coroutine_handle<> await_suspend(std::coroutine_handle h) noexcept { h.destroy(); return {}; } @@ -26,7 +24,7 @@ final_awaiter final_suspend() noexcept { return {}; } - coro::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always initial_suspend() noexcept { return {}; } }; ~detached_task() { @@ -42,7 +40,7 @@ tmp.resume(); } - coro::coroutine_handle coro_; + std::coroutine_handle coro_; }; detached_task foo() { @@ -52,12 +50,12 @@ // check that the lifetime of the coroutine handle used to obtain the address is contained within single basic block, and hence does not live across suspension points. // CHECK-LABEL: final.suspend: // CHECK: %{{.+}} = call token @llvm.coro.save(i8* null) -// CHECK: %[[HDL_CAST1:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL:.+]] to i8* +// CHECK: %[[HDL_CAST1:.+]] = bitcast %"struct.std::coroutine_handle.0"* %[[HDL:.+]] to i8* // CHECK: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HDL_CAST1]]) -// CHECK: %[[CALL:.+]] = call i8* @_ZN13detached_task12promise_type13final_awaiter13await_suspendENSt12experimental13coroutines_v116coroutine_handleIS0_EE( -// CHECK: %[[HDL_CAST2:.+]] = getelementptr inbounds %"struct.std::experimental::coroutines_v1::coroutine_handle.0", %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL]], i32 0, i32 0 +// CHECK: %[[CALL:.+]] = call i8* @_ZN13detached_task12promise_type13final_awaiter13await_suspendESt16coroutine_handleIS0_E( +// CHECK: %[[HDL_CAST2:.+]] = getelementptr inbounds %"struct.std::coroutine_handle.0", %"struct.std::coroutine_handle.0"* %[[HDL]], i32 0, i32 0 // CHECK: store i8* %[[CALL]], i8** %[[HDL_CAST2]], align 8 -// CHECK: %[[HDL_TRANSFER:.+]] = call i8* @_ZNKSt12experimental13coroutines_v116coroutine_handleIvE7addressEv(%"struct.std::experimental::coroutines_v1::coroutine_handle.0"* nonnull align 8 dereferenceable(8) %[[HDL]]) -// CHECK: %[[HDL_CAST3:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle.0"* %[[HDL]] to i8* +// CHECK: %[[HDL_TRANSFER:.+]] = call i8* @_ZNKSt16coroutine_handleIvE7addressEv(%"struct.std::coroutine_handle.0"* {{.*}}%[[HDL]]) +// CHECK: %[[HDL_CAST3:.+]] = bitcast %"struct.std::coroutine_handle.0"* %[[HDL]] to i8* // CHECK: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HDL_CAST3]]) // CHECK: call void @llvm.coro.resume(i8* %[[HDL_TRANSFER]]) Index: clang/test/CodeGenCoroutines/coro-symmetric-transfer-02-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-symmetric-transfer-02-exp-namespace.cpp @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -O1 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +struct Task { + struct promise_type { + Task get_return_object() noexcept { + return Task{coro::coroutine_handle::from_promise(*this)}; + } + + void return_void() noexcept {} + + struct final_awaiter { + bool await_ready() noexcept { return false; } + coro::coroutine_handle<> await_suspend(coro::coroutine_handle h) noexcept { + h.destroy(); + return {}; + } + void await_resume() noexcept {} + }; + + void unhandled_exception() noexcept {} + + final_awaiter final_suspend() noexcept { return {}; } + + coro::suspend_always initial_suspend() noexcept { return {}; } + + template + auto await_transform(Awaitable &&awaitable) { + return awaitable.co_viaIfAsync(); + } + }; + + using handle_t = coro::coroutine_handle; + + class Awaiter { + public: + explicit Awaiter(handle_t coro) noexcept; + Awaiter(Awaiter &&other) noexcept; + Awaiter(const Awaiter &) = delete; + ~Awaiter(); + + bool await_ready() noexcept { return false; } + handle_t await_suspend(coro::coroutine_handle<> continuation) noexcept; + void await_resume(); + + private: + handle_t coro_; + }; + + Task(handle_t coro) noexcept : coro_(coro) {} + + handle_t coro_; + + Task(const Task &t) = delete; + Task(Task &&t) noexcept; + ~Task(); + Task &operator=(Task t) noexcept; + + Awaiter co_viaIfAsync(); +}; + +static Task foo() { + co_return; +} + +Task bar() { + auto mode = 2; + switch (mode) { + case 1: + co_await foo(); + break; + case 2: + co_await foo(); + break; + default: + break; + } +} + +// CHECK-LABEL: define{{.*}} void @_Z3barv +// CHECK: %[[MODE:.+]] = load i32, i32* %mode +// CHECK-NEXT: switch i32 %[[MODE]], label %{{.+}} [ +// CHECK-NEXT: i32 1, label %[[CASE1:.+]] +// CHECK-NEXT: i32 2, label %[[CASE2:.+]] +// CHECK-NEXT: ] + +// CHECK: [[CASE1]]: +// CHECK: br i1 %{{.+}}, label %[[CASE1_AWAIT_READY:.+]], label %[[CASE1_AWAIT_SUSPEND:.+]] +// CHECK: [[CASE1_AWAIT_SUSPEND]]: +// CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(i8* null) +// CHECK-NEXT: %[[HANDLE11:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1:.+]] to i8* +// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE11]]) + +// CHECK: %[[HANDLE12:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1]] to i8* +// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE12]]) +// CHECK-NEXT: call void @llvm.coro.resume +// CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend +// CHECK-NEXT: switch i8 %{{.+}}, label %coro.ret [ +// CHECK-NEXT: i8 0, label %[[CASE1_AWAIT_READY]] +// CHECK-NEXT: i8 1, label %[[CASE1_AWAIT_CLEANUP:.+]] +// CHECK-NEXT: ] +// CHECK: [[CASE1_AWAIT_CLEANUP]]: +// make sure that the awaiter eventually gets cleaned up. +// CHECK: call void @{{.+Awaiter.+}} + +// CHECK: [[CASE2]]: +// CHECK: br i1 %{{.+}}, label %[[CASE2_AWAIT_READY:.+]], label %[[CASE2_AWAIT_SUSPEND:.+]] +// CHECK: [[CASE2_AWAIT_SUSPEND]]: +// CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(i8* null) +// CHECK-NEXT: %[[HANDLE21:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2:.+]] to i8* +// CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE21]]) + +// CHECK: %[[HANDLE22:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2]] to i8* +// CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE22]]) +// CHECK-NEXT: call void @llvm.coro.resume +// CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend +// CHECK-NEXT: switch i8 %{{.+}}, label %coro.ret [ +// CHECK-NEXT: i8 0, label %[[CASE2_AWAIT_READY]] +// CHECK-NEXT: i8 1, label %[[CASE2_AWAIT_CLEANUP:.+]] +// CHECK-NEXT: ] +// CHECK: [[CASE2_AWAIT_CLEANUP]]: +// make sure that the awaiter eventually gets cleaned up. +// CHECK: call void @{{.+Awaiter.+}} Index: clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp +++ clang/test/CodeGenCoroutines/coro-symmetric-transfer-02.cpp @@ -1,20 +1,18 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -O1 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O1 -emit-llvm %s -o - -disable-llvm-passes | FileCheck %s #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - struct Task { struct promise_type { Task get_return_object() noexcept { - return Task{coro::coroutine_handle::from_promise(*this)}; + return Task{std::coroutine_handle::from_promise(*this)}; } void return_void() noexcept {} struct final_awaiter { bool await_ready() noexcept { return false; } - coro::coroutine_handle<> await_suspend(coro::coroutine_handle h) noexcept { + std::coroutine_handle<> await_suspend(std::coroutine_handle h) noexcept { h.destroy(); return {}; } @@ -25,7 +23,7 @@ final_awaiter final_suspend() noexcept { return {}; } - coro::suspend_always initial_suspend() noexcept { return {}; } + std::suspend_always initial_suspend() noexcept { return {}; } template auto await_transform(Awaitable &&awaitable) { @@ -33,7 +31,7 @@ } }; - using handle_t = coro::coroutine_handle; + using handle_t = std::coroutine_handle; class Awaiter { public: @@ -43,7 +41,7 @@ ~Awaiter(); bool await_ready() noexcept { return false; } - handle_t await_suspend(coro::coroutine_handle<> continuation) noexcept; + handle_t await_suspend(std::coroutine_handle<> continuation) noexcept; void await_resume(); private: @@ -91,10 +89,10 @@ // CHECK: br i1 %{{.+}}, label %[[CASE1_AWAIT_READY:.+]], label %[[CASE1_AWAIT_SUSPEND:.+]] // CHECK: [[CASE1_AWAIT_SUSPEND]]: // CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(i8* null) -// CHECK-NEXT: %[[HANDLE11:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1:.+]] to i8* +// CHECK-NEXT: %[[HANDLE11:.+]] = bitcast %"struct.std::coroutine_handle"* %[[TMP1:.+]] to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE11]]) -// CHECK: %[[HANDLE12:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP1]] to i8* +// CHECK: %[[HANDLE12:.+]] = bitcast %"struct.std::coroutine_handle"* %[[TMP1]] to i8* // CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE12]]) // CHECK-NEXT: call void @llvm.coro.resume // CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend @@ -110,10 +108,10 @@ // CHECK: br i1 %{{.+}}, label %[[CASE2_AWAIT_READY:.+]], label %[[CASE2_AWAIT_SUSPEND:.+]] // CHECK: [[CASE2_AWAIT_SUSPEND]]: // CHECK-NEXT: %{{.+}} = call token @llvm.coro.save(i8* null) -// CHECK-NEXT: %[[HANDLE21:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2:.+]] to i8* +// CHECK-NEXT: %[[HANDLE21:.+]] = bitcast %"struct.std::coroutine_handle"* %[[TMP2:.+]] to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8(i64 8, i8* %[[HANDLE21]]) -// CHECK: %[[HANDLE22:.+]] = bitcast %"struct.std::experimental::coroutines_v1::coroutine_handle"* %[[TMP2]] to i8* +// CHECK: %[[HANDLE22:.+]] = bitcast %"struct.std::coroutine_handle"* %[[TMP2]] to i8* // CHECK-NEXT: call void @llvm.lifetime.end.p0i8(i64 8, i8* %[[HANDLE22]]) // CHECK-NEXT: call void @llvm.coro.resume // CHECK-NEXT: %{{.+}} = call i8 @llvm.coro.suspend Index: clang/test/CodeGenCoroutines/coro-unhandled-exception-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-unhandled-exception-exp-namespace.cpp @@ -0,0 +1,78 @@ +// 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-LPAD %s + +#include "Inputs/coroutine-exp-namespace.h" + +namespace coro = std::experimental::coroutines_v1; + +namespace std { +using exception_ptr = int; +exception_ptr current_exception(); +} // namespace std + +struct coro_t { + struct promise_type { + coro_t get_return_object() { + coro::coroutine_handle{}; + return {}; + } + coro::suspend_never initial_suspend() { return {}; } + coro::suspend_never final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception() noexcept; + }; +}; + +struct Cleanup { + ~Cleanup(); +}; +void may_throw(); + +coro_t f() { + Cleanup x; + may_throw(); + co_return; +} + +// CHECK: @"?f@@YA?AUcoro_t@@XZ"( +// CHECK: invoke void @"?may_throw@@YAXXZ"() +// CHECK: to label %{{.+}} unwind label %[[EHCLEANUP:.+]] +// CHECK: [[EHCLEANUP]]: +// CHECK: %[[INNERPAD:.+]] = cleanuppad within none [] +// CHECK: call void @"??1Cleanup@@QEAA@XZ"( +// CHECK: cleanupret from %[[INNERPAD]] unwind label %[[CATCHSW:.+]] +// CHECK: [[CATCHSW]]: +// CHECK: %[[CATCHSWTOK:.+]] = catchswitch within none [label %[[CATCH:.+]]] unwind label +// CHECK: [[CATCH]]: +// CHECK: %[[CATCHTOK:.+]] = catchpad within [[CATCHSWTOK:.+]] +// CHECK: call void @"?unhandled_exception@promise_type@coro_t@@QEAAXXZ" +// CHECK: catchret from %[[CATCHTOK]] to label %[[CATCHRETDEST:.+]] +// CHECK: [[CATCHRETDEST]]: +// CHECK-NEXT: br label %[[TRYCONT:.+]] +// CHECK: [[TRYCONT]]: +// CHECK-NEXT: br label %[[COROFIN:.+]] +// CHECK: [[COROFIN]]: +// CHECK-NEXT: bitcast %"struct.std::experimental::coroutines_v1::suspend_never"* %{{.+}} to i8* +// CHECK-NEXT: call void @llvm.lifetime.start.p0i8( +// CHECK-NEXT: call void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"( + +// CHECK-LPAD: @_Z1fv( +// CHECK-LPAD: invoke void @_Z9may_throwv() +// CHECK-LPAD: to label %[[CONT:.+]] unwind label %[[CLEANUP:.+]] +// CHECK-LPAD: [[CLEANUP]]: +// CHECK-LPAD: call void @_ZN7CleanupD1Ev(%struct.Cleanup* {{[^,]*}} %x) #2 +// CHECK-LPAD: br label %[[CATCH:.+]] + +// CHECK-LPAD: [[CATCH]]: +// CHECK-LPAD: call i8* @__cxa_begin_catch +// CHECK-LPAD: call void @_ZN6coro_t12promise_type19unhandled_exceptionEv(%"struct.coro_t::promise_type"* {{[^,]*}} %__promise) #2 +// CHECK-LPAD: invoke void @__cxa_end_catch() +// CHECK-LPAD-NEXT: to label %[[CATCHRETDEST:.+]] unwind label +// CHECK-LPAD: [[CATCHRETDEST]]: +// CHECK-LPAD-NEXT: br label %[[TRYCONT:.+]] +// CHECK-LPAD: [[TRYCONT]]: +// CHECK-LPAD: br label %[[COROFIN:.+]] +// CHECK-LPAD: [[COROFIN]]: +// CHECK-LPAD-NEXT: bitcast %"struct.std::experimental::coroutines_v1::suspend_never"* %{{.+}} to i8* +// CHECK-LPAD-NEXT: call void @llvm.lifetime.start.p0i8( +// CHECK-LPAD-NEXT: call void @_ZN6coro_t12promise_type13final_suspendEv( Index: clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp =================================================================== --- clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp +++ clang/test/CodeGenCoroutines/coro-unhandled-exception.cpp @@ -1,10 +1,8 @@ -// 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-LPAD %s +// RUN: %clang_cc1 -std=c++20 -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++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -fexceptions -fcxx-exceptions -disable-llvm-passes | FileCheck --check-prefix=CHECK-LPAD %s #include "Inputs/coroutine.h" -namespace coro = std::experimental::coroutines_v1; - namespace std { using exception_ptr = int; exception_ptr current_exception(); @@ -13,11 +11,11 @@ struct coro_t { struct promise_type { coro_t get_return_object() { - coro::coroutine_handle{}; + std::coroutine_handle{}; return {}; } - coro::suspend_never initial_suspend() { return {}; } - coro::suspend_never final_suspend() noexcept { return {}; } + std::suspend_never initial_suspend() { return {}; } + std::suspend_never final_suspend() noexcept { return {}; } void return_void(){} void unhandled_exception() noexcept; }; @@ -50,9 +48,9 @@ // CHECK: [[TRYCONT]]: // CHECK-NEXT: br label %[[COROFIN:.+]] // CHECK: [[COROFIN]]: -// CHECK-NEXT: bitcast %"struct.std::experimental::coroutines_v1::suspend_never"* %{{.+}} to i8* +// CHECK-NEXT: bitcast %"struct.std::suspend_never"* %{{.+}} to i8* // CHECK-NEXT: call void @llvm.lifetime.start.p0i8( -// CHECK-NEXT: call void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@coroutines_v1@experimental@std@@XZ"( +// CHECK-NEXT: call void @"?final_suspend@promise_type@coro_t@@QEAA?AUsuspend_never@std@@XZ"( // CHECK-LPAD: @_Z1fv( // CHECK-LPAD: invoke void @_Z9may_throwv() @@ -71,6 +69,6 @@ // CHECK-LPAD: [[TRYCONT]]: // CHECK-LPAD: br label %[[COROFIN:.+]] // CHECK-LPAD: [[COROFIN]]: -// CHECK-LPAD-NEXT: bitcast %"struct.std::experimental::coroutines_v1::suspend_never"* %{{.+}} to i8* +// CHECK-LPAD-NEXT: bitcast %"struct.std::suspend_never"* %{{.+}} to i8* // CHECK-LPAD-NEXT: call void @llvm.lifetime.start.p0i8( // CHECK-LPAD-NEXT: call void @_ZN6coro_t12promise_type13final_suspendEv( Index: clang/test/CodeGenCoroutines/microsoft-abi-operator-coawait-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/microsoft-abi-operator-coawait-exp-namespace.cpp @@ -0,0 +1,24 @@ +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc18.0.0 -fcoroutines-ts -emit-llvm %s -o - -std=c++14 -disable-llvm-passes | FileCheck %s +struct no_suspend { + bool await_ready() { return true; } + template void await_suspend(F) {} + void await_resume() {} +}; + +struct A { + no_suspend operator co_await() { return {}; } +}; + +struct B {}; + +no_suspend operator co_await(B const &) { return {}; } + +// CHECK-LABEL: f( +extern "C" void f() { + A a; + B b; + // CHECK: call void @"??__LA@@QEAA?AUno_suspend@@XZ"( + a.operator co_await(); + // CHECK-NEXT: call i8 @"??__L@YA?AUno_suspend@@AEBUB@@@Z"( + operator co_await(b); +} Index: clang/test/CodeGenCoroutines/microsoft-abi-operator-coawait.cpp =================================================================== --- clang/test/CodeGenCoroutines/microsoft-abi-operator-coawait.cpp +++ clang/test/CodeGenCoroutines/microsoft-abi-operator-coawait.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc18.0.0 -fcoroutines-ts -emit-llvm %s -o - -std=c++14 -disable-llvm-passes | FileCheck %s +// RUN: %clang_cc1 -triple x86_64-pc-windows-msvc18.0.0 -emit-llvm %s -o - -std=c++20 -disable-llvm-passes | FileCheck %s struct no_suspend { bool await_ready() { return true; } template void await_suspend(F) {} Index: clang/test/CoverageMapping/coroutine.cpp =================================================================== --- clang/test/CoverageMapping/coroutine.cpp +++ clang/test/CoverageMapping/coroutine.cpp @@ -1,6 +1,6 @@ // RUN: %clang_cc1 -mllvm -emptyline-comment-coverage=false -triple x86_64-unknown-linux-gnu -fcoroutines-ts -std=c++14 -emit-llvm -fprofile-instrument=clang -fcoverage-mapping -dump-coverage-mapping %s -o - | FileCheck %s -namespace std::experimental { +namespace std { template struct coroutine_traits; @@ -16,16 +16,16 @@ template coroutine_handle(coroutine_handle) noexcept {} }; -} // namespace std::experimental +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; template <> -struct std::experimental::coroutine_traits { +struct std::coroutine_traits { struct promise_type { int get_return_object(); suspend_always initial_suspend(); Index: clang/test/Index/coroutines.cpp =================================================================== --- clang/test/Index/coroutines.cpp +++ clang/test/Index/coroutines.cpp @@ -1,8 +1,8 @@ // RUN: c-index-test -test-load-source all -c %s -fsyntax-only -target x86_64-apple-darwin9 -fcoroutines-ts -std=c++1z -I%S/../SemaCXX/Inputs | FileCheck %s #include "std-coroutine.h" -using std::experimental::suspend_always; -using std::experimental::suspend_never; +using std::suspend_always; +using std::suspend_never; struct promise_void { void get_return_object(); @@ -13,7 +13,7 @@ }; template <> -struct std::experimental::coroutine_traits { using promise_type = promise_void; }; +struct std::coroutine_traits { using promise_type = promise_void; }; void CoroutineTestRet() { co_return; Index: clang/test/Lexer/cxx-features.cpp =================================================================== --- clang/test/Lexer/cxx-features.cpp +++ clang/test/Lexer/cxx-features.cpp @@ -95,6 +95,10 @@ #error "wrong value for __cpp_impl_three_way_comparison" #endif +#if check(impl_coroutine, 0, 0, 0, 0, 201902L, 201902L) +#error "wrong value for __cpp_impl_coroutine" +#endif + // init_captures checked below #if check(modules, 0, 0, 0, 0, 0, 0) Index: clang/test/PCH/coroutines.cpp =================================================================== --- clang/test/PCH/coroutines.cpp +++ clang/test/PCH/coroutines.cpp @@ -8,7 +8,7 @@ #ifndef HEADER #define HEADER -namespace std::experimental { +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -21,15 +21,15 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template struct std::experimental::coroutine_traits { +template struct std::coroutine_traits { struct promise_type { void get_return_object() noexcept; suspend_always initial_suspend() noexcept; @@ -42,7 +42,7 @@ }; }; -template struct std::experimental::coroutine_traits { +template struct std::coroutine_traits { struct promise_type { int get_return_object() noexcept; suspend_always initial_suspend() noexcept; Index: clang/test/SemaCXX/Inputs/std-coroutine-exp-namespace.h =================================================================== --- /dev/null +++ clang/test/SemaCXX/Inputs/std-coroutine-exp-namespace.h @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value +#ifndef STD_COROUTINE_EXPERIMENTAL_H +#define STD_COROUTINE_EXPERIMENTAL_H + +namespace std { +namespace experimental { +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle) noexcept; + static coroutine_handle from_address(void *); +}; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + void await_suspend(coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; +} // namespace experimental +} // namespace std + +#endif // STD_COROUTINE_EXPERIMENTAL_H Index: clang/test/SemaCXX/Inputs/std-coroutine.h =================================================================== --- clang/test/SemaCXX/Inputs/std-coroutine.h +++ clang/test/SemaCXX/Inputs/std-coroutine.h @@ -1,9 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wno-unreachable-code -Wno-unused-value #ifndef STD_COROUTINE_H #define STD_COROUTINE_H namespace std { -namespace experimental { template struct coroutine_traits { using promise_type = typename Ret::promise_type; }; @@ -31,7 +30,6 @@ void await_resume() noexcept {} }; -} // namespace experimental } // namespace std #endif // STD_COROUTINE_H Index: clang/test/SemaCXX/co_await-range-for-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/co_await-range-for-exp-namespace.cpp @@ -0,0 +1,171 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ +// RUN: -fblocks +#include "Inputs/std-coroutine-exp-namespace.h" + +using namespace std::experimental; + +template +struct Awaiter { + bool await_ready(); + void await_suspend(coroutine_handle<>); + Begin await_resume(); +}; + +template struct BeginTag { BeginTag() = delete; }; +template struct IncTag { IncTag() = delete; }; + +template +struct CoawaitTag { CoawaitTag() = delete; }; + +template +struct Iter { + using value_type = T; + using reference = T &; + using pointer = T *; + + IncTag operator++(); + reference operator*(); + pointer operator->(); +}; +template bool operator==(Iter, Iter); +template bool operator!=(Iter, Iter); + +template +struct Range { + BeginTag> begin(); + Iter end(); +}; + +struct MyForLoopArrayAwaiter { + struct promise_type { + MyForLoopArrayAwaiter get_return_object() { return {}; } + void return_void(); + void unhandled_exception(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + template + Awaiter await_transform(T *) = delete; // expected-note {{explicitly deleted}} + }; +}; +MyForLoopArrayAwaiter g() { + int arr[10] = {0}; + for + co_await(auto i : arr) {} + // expected-error@-1 {{call to deleted member function 'await_transform'}} + // expected-note@-2 {{'await_transform' implicitly required by 'co_await' here}} +} + +struct ForLoopAwaiterBadBeginTransform { + struct promise_type { + ForLoopAwaiterBadBeginTransform get_return_object(); + void return_void(); + void unhandled_exception(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + + template + Awaiter await_transform(BeginTag) = delete; // expected-note 1+ {{explicitly deleted}} + + template + CoawaitTag await_transform(IncTag); // expected-note 1+ {{candidate}} + }; +}; +ForLoopAwaiterBadBeginTransform bad_begin() { + Range R; + for + co_await(auto i : R) {} + // expected-error@-1 {{call to deleted member function 'await_transform'}} + // expected-note@-2 {{'await_transform' implicitly required by 'co_await' here}} +} +template +ForLoopAwaiterBadBeginTransform bad_begin_template(Dummy) { + Range R; + for + co_await(auto i : R) {} + // expected-error@-1 {{call to deleted member function 'await_transform'}} + // expected-note@-2 {{'await_transform' implicitly required by 'co_await' here}} +} +template ForLoopAwaiterBadBeginTransform bad_begin_template(int); // expected-note {{requested here}} + +template +Awaiter operator co_await(CoawaitTag) = delete; +// expected-note@-1 1+ {{explicitly deleted}} + +struct ForLoopAwaiterBadIncTransform { + struct promise_type { + ForLoopAwaiterBadIncTransform get_return_object(); + void return_void(); + void unhandled_exception(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + + template + Awaiter await_transform(BeginTag e); + + template + CoawaitTag await_transform(IncTag); + }; +}; +ForLoopAwaiterBadIncTransform bad_inc_transform() { + Range R; + for + co_await(auto i : R) {} + // expected-error@-1 {{overload resolution selected deleted operator 'co_await'}} + // expected-note@-2 {{in implicit call to 'operator++' for iterator of type 'Range'}} +} + +template +ForLoopAwaiterBadIncTransform bad_inc_transform_template(Dummy) { + Range R; + for + co_await(auto i : R) {} + // expected-error@-1 {{overload resolution selected deleted operator 'co_await'}} + // expected-note@-2 {{in implicit call to 'operator++' for iterator of type 'Range'}} +} +template ForLoopAwaiterBadIncTransform bad_inc_transform_template(long); // expected-note {{requested here}} + +// Ensure we mark and check the function as a coroutine even if it's +// never instantiated. +template +constexpr void never_instant(T) { + static_assert(sizeof(T) != sizeof(T), "function should not be instantiated"); + for + co_await(auto i : foo(T{})) {} + // expected-error@-1 {{'co_await' cannot be used in a constexpr function}} +} + +namespace NS { +struct ForLoopAwaiterCoawaitLookup { + struct promise_type { + ForLoopAwaiterCoawaitLookup get_return_object(); + void return_void(); + void unhandled_exception(); + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + template + CoawaitTag await_transform(BeginTag e); + template + Awaiter await_transform(IncTag); + }; +}; +} // namespace NS +using NS::ForLoopAwaiterCoawaitLookup; + +template +ForLoopAwaiterCoawaitLookup test_coawait_lookup(T) { + Range R; + for + co_await(auto i : R) {} + // expected-error@-1 {{no member named 'await_ready' in 'CoawaitTag, false>'}} +} +template ForLoopAwaiterCoawaitLookup test_coawait_lookup(int); // expected-note {{requested here}} + +// FIXME: This test should fail as well since the newly declared operator co_await +// should not be found by lookup. +namespace NS2 { +template +Awaiter operator co_await(CoawaitTag); +} +using NS2::operator co_await; +template ForLoopAwaiterCoawaitLookup test_coawait_lookup(long); Index: clang/test/SemaCXX/co_await-range-for.cpp =================================================================== --- clang/test/SemaCXX/co_await-range-for.cpp +++ clang/test/SemaCXX/co_await-range-for.cpp @@ -1,10 +1,9 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 \ // RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ // RUN: -fblocks #include "Inputs/std-coroutine.h" -using namespace std::experimental; - +using namespace std; template struct Awaiter { Index: clang/test/SemaCXX/coreturn-eh-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coreturn-eh-exp-namespace.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fcxx-exceptions -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code +// expected-no-diagnostics + +#include "Inputs/std-coroutine-exp-namespace.h" + +using std::experimental::suspend_always; +using std::experimental::suspend_never; + +struct awaitable { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); // FIXME: coroutine_handle + void await_resume(); +} a; + +struct object { + ~object() {} +}; + +struct promise_void_return_value { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(object); +}; + +struct VoidTagReturnValue { + struct promise_type { + VoidTagReturnValue get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(object); + }; +}; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_void_return_value; }; + +VoidTagReturnValue test() { + object x = {}; + try { + co_return {}; + } catch (...) { + throw; + } +} Index: clang/test/SemaCXX/coreturn-eh.cpp =================================================================== --- clang/test/SemaCXX/coreturn-eh.cpp +++ clang/test/SemaCXX/coreturn-eh.cpp @@ -1,14 +1,14 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fcxx-exceptions -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fcxx-exceptions -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code // expected-no-diagnostics #include "Inputs/std-coroutine.h" -using std::experimental::suspend_always; -using std::experimental::suspend_never; +using std::suspend_always; +using std::suspend_never; struct awaitable { bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); // FIXME: coroutine_handle + void await_suspend(std::coroutine_handle<>); // FIXME: coroutine_handle void await_resume(); } a; @@ -33,7 +33,7 @@ }; template -struct std::experimental::coroutine_traits { using promise_type = promise_void_return_value; }; +struct std::coroutine_traits { using promise_type = promise_void_return_value; }; VoidTagReturnValue test() { object x = {}; Index: clang/test/SemaCXX/coreturn-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coreturn-exp-namespace.cpp @@ -0,0 +1,140 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code +#include "Inputs/std-coroutine-exp-namespace.h" + +using std::experimental::suspend_always; +using std::experimental::suspend_never; + +struct awaitable { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); // FIXME: coroutine_handle + void await_resume(); +} a; + +struct promise_void { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +struct promise_void_return_value { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(int); +}; + +struct VoidTagNoReturn { + struct promise_type { + VoidTagNoReturn get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + }; +}; + +struct VoidTagReturnValue { + struct promise_type { + VoidTagReturnValue get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_value(int); + }; +}; + +struct VoidTagReturnVoid { + struct promise_type { + VoidTagReturnVoid get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); + }; +}; + +struct promise_float { + float get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +struct promise_int { + int get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_value(int); + void unhandled_exception(); +}; + +template <> +struct std::experimental::coroutine_traits { using promise_type = promise_void; }; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_void_return_value; }; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_float; }; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_int; }; + +void test0() { co_await a; } +float test1() { co_await a; } + +int test2() { + co_await a; +} // expected-warning {{non-void coroutine does not return a value}} + +int test2a(bool b) { + if (b) + co_return 42; +} // expected-warning {{non-void coroutine does not return a value in all control paths}} + +int test3() { + co_await a; +b: + goto b; +} + +int test4() { + co_return 42; +} + +void test5(int) { + co_await a; +} // expected-warning {{non-void coroutine does not return a value}} + +void test6(int x) { + if (x) + co_return 42; +} // expected-warning {{non-void coroutine does not return a value in all control paths}} + +void test7(int y) { + if (y) + co_return 42; + else + co_return 101; +} + +VoidTagReturnVoid test8() { + co_await a; +} + +VoidTagReturnVoid test9(bool b) { + if (b) + co_return; +} + +VoidTagReturnValue test10() { + co_await a; +} // expected-warning {{non-void coroutine does not return a value}} + +VoidTagReturnValue test11(bool b) { + if (b) + co_return 42; +} // expected-warning {{non-void coroutine does not return a value in all control paths}} Index: clang/test/SemaCXX/coreturn.cpp =================================================================== --- clang/test/SemaCXX/coreturn.cpp +++ clang/test/SemaCXX/coreturn.cpp @@ -1,12 +1,12 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify -fblocks -Wall -Wextra -Wno-error=unreachable-code #include "Inputs/std-coroutine.h" -using std::experimental::suspend_always; -using std::experimental::suspend_never; +using std::suspend_always; +using std::suspend_never; struct awaitable { bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); // FIXME: coroutine_handle + void await_suspend(std::coroutine_handle<>); // FIXME: coroutine_handle void await_resume(); } a; @@ -72,16 +72,16 @@ }; template <> -struct std::experimental::coroutine_traits { using promise_type = promise_void; }; +struct std::coroutine_traits { using promise_type = promise_void; }; template -struct std::experimental::coroutine_traits { using promise_type = promise_void_return_value; }; +struct std::coroutine_traits { using promise_type = promise_void_return_value; }; template -struct std::experimental::coroutine_traits { using promise_type = promise_float; }; +struct std::coroutine_traits { using promise_type = promise_float; }; template -struct std::experimental::coroutine_traits { using promise_type = promise_int; }; +struct std::coroutine_traits { using promise_type = promise_int; }; void test0() { co_await a; } float test1() { co_await a; } Index: clang/test/SemaCXX/coroutine-builtins.cpp =================================================================== --- clang/test/SemaCXX/coroutine-builtins.cpp +++ clang/test/SemaCXX/coroutine-builtins.cpp @@ -1,7 +1,8 @@ // RUN: %clang_cc1 -fsyntax-only -verify -fcoroutines-ts %s +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 %s // RUN: %clang_cc1 -fsyntax-only -verify -DERRORS %s -// Check that we don't crash when using __builtin_coro_* without the fcoroutine-ts option +// Check that we don't crash when using __builtin_coro_* without the fcoroutine-ts or -std=c++20 option #ifdef ERRORS // expected-error@#A{{use of undeclared identifier '__builtin_coro_done'}} Index: clang/test/SemaCXX/coroutine-final-suspend-noexcept-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-final-suspend-noexcept-exp-namespace.cpp @@ -0,0 +1,70 @@ +// This file contains references to sections of the Coroutines TS, which can be +// found at http://wg21.link/coroutines. + +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -Wunused-result + +namespace std { +namespace experimental { +template +struct coroutine_traits { using promise_type = typename Ret::promise_type; }; + +template +struct coroutine_handle { + static coroutine_handle from_address(void *); // expected-note 2 {{must be declared with 'noexcept'}} +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle); // expected-note 2 {{must be declared with 'noexcept'}} +}; + +struct suspend_never { + bool await_ready() { return true; } // expected-note 2 {{must be declared with 'noexcept'}} + void await_suspend(coroutine_handle<>) {} // expected-note 2 {{must be declared with 'noexcept'}} + void await_resume() {} // expected-note 2 {{must be declared with 'noexcept'}} + ~suspend_never() noexcept(false); // expected-note 2 {{must be declared with 'noexcept'}} +}; + +struct suspend_always { + bool await_ready() { return false; } + void await_suspend(coroutine_handle<>) {} + void await_resume() {} + suspend_never operator co_await(); // expected-note 2 {{must be declared with 'noexcept'}} + ~suspend_always() noexcept(false); // expected-note 2 {{must be declared with 'noexcept'}} +}; +} // namespace experimental +} // namespace std + +using namespace std; + +struct A { + bool await_ready(); + void await_resume(); + template + void await_suspend(F); +}; + +struct coro_t { + struct promise_type { + coro_t get_return_object(); + std::experimental::suspend_never initial_suspend(); + std::experimental::suspend_always final_suspend(); // expected-note 2 {{must be declared with 'noexcept'}} + void return_void(); + static void unhandled_exception(); + }; +}; + +coro_t f(int n) { // expected-error {{the expression 'co_await __promise.final_suspend()' is required to be non-throwing}} + A a{}; + co_await a; +} + +template +coro_t f_dep(T n) { // expected-error {{the expression 'co_await __promise.final_suspend()' is required to be non-throwing}} + A a{}; + co_await a; +} + +void foo() { + f_dep(5); // expected-note {{in instantiation of function template specialization 'f_dep' requested here}} +} Index: clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp =================================================================== --- clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp +++ clang/test/SemaCXX/coroutine-final-suspend-noexcept.cpp @@ -1,10 +1,9 @@ // This file contains references to sections of the Coroutines TS, which can be // found at http://wg21.link/coroutines. -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++20 -verify %s -fcxx-exceptions -fexceptions -Wunused-result namespace std { -namespace experimental { template struct coroutine_traits { using promise_type = typename Ret::promise_type; }; @@ -34,10 +33,9 @@ ~suspend_always() noexcept(false); // expected-note 2 {{must be declared with 'noexcept'}} }; -} // namespace experimental } // namespace std -using namespace std::experimental; +using namespace std; struct A { bool await_ready(); Index: clang/test/SemaCXX/coroutine-mixed-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-mixed-exp-namespace.cpp @@ -0,0 +1,27 @@ +// This file is to test the mixed use of `std::experimental::coroutine*` and `std::coroutine*` +// wouldn't make the compliler to crash and emit the diagnostic message correctly. +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s + +#include "Inputs/std-coroutine-exp-namespace.h" +#include "Inputs/std-coroutine.h" + +struct my_awaitable { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<> coro) noexcept; + void await_resume() noexcept; +}; + +struct promise_void { + void get_return_object(); + my_awaitable initial_suspend(); + my_awaitable final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +template <> +struct std::coroutine_traits { using promise_type = promise_void; }; + +void test() { + co_return; // expected-error {{Found mixed use of std namespace and std::experimental namespace for coroutine, which is disallowed. The coroutine components in std::experimental namespace is deprecated. Please use coroutine components under std namespace.}} +} Index: clang/test/SemaCXX/coroutine-mixed2-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-mixed2-exp-namespace.cpp @@ -0,0 +1,28 @@ +// This file is to test the mixed use of `std::experimental::coroutine*` and `std::coroutine*` +// which is similar to coroutine-mixed-exp-namesapce. This file tests the relative order of +// included header wouldn't affect the diagnostic messages. +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s + +#include "Inputs/std-coroutine-exp-namespace.h" +#include "Inputs/std-coroutine.h" + +struct my_awaitable { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<> coro) noexcept; + void await_resume() noexcept; +}; + +struct promise_void { + void get_return_object(); + my_awaitable initial_suspend(); + my_awaitable final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +template <> +struct std::coroutine_traits { using promise_type = promise_void; }; + +void test() { + co_return; // expected-error {{Found mixed use of std namespace and std::experimental namespace for coroutine, which is disallowed. The coroutine components in std::experimental namespace is deprecated. Please use coroutine components under std namespace.}} +} Index: clang/test/SemaCXX/coroutine-rvo-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-rvo-exp-namespace.cpp @@ -0,0 +1,148 @@ +// RUN: %clang_cc1 -verify -std=c++17 -fcoroutines-ts -fsyntax-only %s + +namespace std::experimental { +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; + +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; + +template +struct void_t_imp { + using type = void; +}; +template +using void_t = typename void_t_imp::type; + +template +struct traits_sfinae_base {}; + +template +struct traits_sfinae_base> { + using promise_type = typename T::promise_type; +}; + +template +struct coroutine_traits : public traits_sfinae_base {}; +} // namespace std::experimental + +struct suspend_never { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct MoveOnly { + MoveOnly() = default; + MoveOnly(const MoveOnly &) = delete; + MoveOnly(MoveOnly &&) = default; +}; + +struct NoCopyNoMove { + NoCopyNoMove() = default; + NoCopyNoMove(const NoCopyNoMove &) = delete; +}; + +template +struct task { + struct promise_type { + auto initial_suspend() { return suspend_never{}; } + auto final_suspend() noexcept { return suspend_never{}; } + auto get_return_object() { return task{}; } + static void unhandled_exception() {} + void return_value(T &&value) {} // expected-note 4{{passing argument}} + }; +}; + +task local2val() { + NoCopyNoMove value; + co_return value; +} + +task local2ref() { + NoCopyNoMove value; + co_return value; // expected-error {{non-const lvalue reference to type 'NoCopyNoMove' cannot bind to a temporary of type 'NoCopyNoMove'}} +} + +// We need the move constructor for construction of the coroutine. +task param2val(MoveOnly value) { + co_return value; +} + +task lvalue2val(NoCopyNoMove &value) { + co_return value; // expected-error {{rvalue reference to type 'NoCopyNoMove' cannot bind to lvalue of type 'NoCopyNoMove'}} +} + +task rvalue2val(NoCopyNoMove &&value) { + co_return value; +} + +task lvalue2ref(NoCopyNoMove &value) { + co_return value; +} + +task rvalue2ref(NoCopyNoMove &&value) { + co_return value; // expected-error {{non-const lvalue reference to type 'NoCopyNoMove' cannot bind to a temporary of type 'NoCopyNoMove'}} +} + +struct To { + operator MoveOnly() &&; +}; +task conversion_operator() { + To t; + co_return t; +} + +struct Construct { + Construct(MoveOnly); +}; +task converting_constructor() { + MoveOnly w; + co_return w; +} + +struct Derived : MoveOnly {}; +task derived2base() { + Derived result; + co_return result; +} + +struct RetThis { + task foo() && { + co_return *this; // expected-error {{rvalue reference to type 'RetThis' cannot bind to lvalue of type 'RetThis'}} + } +}; + +template +struct is_same { static constexpr bool value = false; }; + +template +struct is_same { static constexpr bool value = true; }; + +template +struct generic_task { + struct promise_type { + auto initial_suspend() { return suspend_never{}; } + auto final_suspend() noexcept { return suspend_never{}; } + auto get_return_object() { return generic_task{}; } + static void unhandled_exception(); + template + void return_value(U &&value) { + static_assert(is_same::value); + } + }; +}; + +generic_task param2template(MoveOnly value) { + co_return value; // We should deduce U = MoveOnly. +} + +generic_task lvalue2template(NoCopyNoMove &value) { + co_return value; // We should deduce U = NoCopyNoMove&. +} Index: clang/test/SemaCXX/coroutine-rvo.cpp =================================================================== --- clang/test/SemaCXX/coroutine-rvo.cpp +++ clang/test/SemaCXX/coroutine-rvo.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -verify -std=c++17 -fcoroutines-ts -fsyntax-only %s +// RUN: %clang_cc1 -verify -std=c++20 -fsyntax-only %s -namespace std::experimental { +namespace std { template struct coroutine_handle { coroutine_handle() = default; static coroutine_handle from_address(void *) noexcept; @@ -30,11 +30,11 @@ template struct coroutine_traits : public traits_sfinae_base {}; -} +} // namespace std struct suspend_never { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; Index: clang/test/SemaCXX/coroutine-seh-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-seh-exp-namespace.cpp @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -triple x86_64-windows-msvc -fms-extensions +namespace std::experimental { +template struct coroutine_traits; + +template struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; +}; +template <> struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; + coroutine_handle() = default; + template + coroutine_handle(coroutine_handle) noexcept; +}; +} // namespace std::experimental + +struct suspend_always { + bool await_ready() noexcept; + void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +template <> struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() noexcept; + suspend_always initial_suspend() noexcept; + suspend_always final_suspend() noexcept; + void return_void() noexcept; + void unhandled_exception() noexcept; + }; +}; + +void SEH_used() { + __try { // expected-error {{cannot use SEH '__try' in a coroutine when C++ exceptions are enabled}} + co_return; // expected-note {{function is a coroutine due to use of 'co_return' here}} + } __except (0) { + } +} Index: clang/test/SemaCXX/coroutine-seh.cpp =================================================================== --- clang/test/SemaCXX/coroutine-seh.cpp +++ clang/test/SemaCXX/coroutine-seh.cpp @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -std=c++1z -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -triple x86_64-windows-msvc -fms-extensions -namespace std::experimental { +// RUN: %clang_cc1 -std=c++20 -verify %s -fcxx-exceptions -fexceptions -triple x86_64-windows-msvc -fms-extensions +namespace std { template struct coroutine_traits; template struct coroutine_handle { @@ -12,15 +12,15 @@ template coroutine_handle(coroutine_handle) noexcept; }; -} +} // namespace std struct suspend_always { bool await_ready() noexcept; - void await_suspend(std::experimental::coroutine_handle<>) noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; void await_resume() noexcept; }; -template <> struct std::experimental::coroutine_traits { +template <> struct std::coroutine_traits { struct promise_type { void get_return_object() noexcept; suspend_always initial_suspend() noexcept; Index: clang/test/SemaCXX/coroutine-traits-undefined-template-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-traits-undefined-template-exp-namespace.cpp @@ -0,0 +1,19 @@ +// This file contains references to sections of the Coroutines TS, which can be +// found at http://wg21.link/coroutines. + +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -Wunused-result + +namespace std { +namespace experimental { +template +struct coroutine_traits { + struct promise_type {}; +}; + +template <> struct coroutine_traits; // expected-note {{forward declaration of 'std::experimental::coroutine_traits'}} +} // namespace experimental +} // namespace std + +void uses_forward_declaration() { + co_return; // expected-error {{this function cannot be a coroutine: missing definition of specialization 'coroutine_traits'}} +} Index: clang/test/SemaCXX/coroutine-traits-undefined-template.cpp =================================================================== --- clang/test/SemaCXX/coroutine-traits-undefined-template.cpp +++ clang/test/SemaCXX/coroutine-traits-undefined-template.cpp @@ -1,18 +1,17 @@ // This file contains references to sections of the Coroutines TS, which can be // found at http://wg21.link/coroutines. -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -verify %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++20 -verify %s -fcxx-exceptions -fexceptions -Wunused-result namespace std { -namespace experimental { template struct coroutine_traits { struct promise_type {}; }; -template<> struct coroutine_traits; // expected-note {{forward declaration of 'std::experimental::coroutine_traits'}} -}} // namespace std::experimental +template <> struct coroutine_traits; // expected-note {{forward declaration of 'std::coroutine_traits'}} +} // namespace std void uses_forward_declaration() { co_return; // expected-error {{this function cannot be a coroutine: missing definition of specialization 'coroutine_traits'}} Index: clang/test/SemaCXX/coroutine-unhandled_exception-warning-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-unhandled_exception-warning-exp-namespace.cpp @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ +// RUN: -fblocks -Wno-unreachable-code -Wno-unused-value + +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ +// RUN: -fblocks -Wno-unreachable-code -Wno-unused-value \ +// RUN: -DDISABLE_WARNING -Wno-coroutine-missing-unhandled-exception + +#if __has_feature(cxx_exceptions) +#error This test requires exceptions be disabled +#endif + +#include "Inputs/std-coroutine-exp-namespace.h" + +using std::experimental::suspend_always; +using std::experimental::suspend_never; + +#ifndef DISABLE_WARNING +struct promise_void { // expected-note {{defined here}} +#else +struct promise_void { +#endif + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); +}; + +template +struct std::experimental::coroutine_traits { using promise_type = promise_void; }; + +#ifndef DISABLE_WARNING +void test0() { // expected-warning {{'promise_void' is required to declare the member 'unhandled_exception()' when exceptions are enabled}} + co_return; +} +#else +void test0() { // expected-no-diagnostics + co_return; +} +#endif Index: clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp =================================================================== --- clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp +++ clang/test/SemaCXX/coroutine-unhandled_exception-warning.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 \ // RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ // RUN: -fblocks -Wno-unreachable-code -Wno-unused-value -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts \ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 \ // RUN: -fsyntax-only -Wignored-qualifiers -Wno-error=return-type -verify \ // RUN: -fblocks -Wno-unreachable-code -Wno-unused-value \ // RUN: -DDISABLE_WARNING -Wno-coroutine-missing-unhandled-exception @@ -13,8 +13,8 @@ #include "Inputs/std-coroutine.h" -using std::experimental::suspend_always; -using std::experimental::suspend_never; +using std::suspend_always; +using std::suspend_never; #ifndef DISABLE_WARNING struct promise_void { // expected-note {{defined here}} @@ -28,7 +28,7 @@ }; template -struct std::experimental::coroutine_traits { using promise_type = promise_void; }; +struct std::coroutine_traits { using promise_type = promise_void; }; #ifndef DISABLE_WARNING void test0() { // expected-warning {{'promise_void' is required to declare the member 'unhandled_exception()' when exceptions are enabled}} Index: clang/test/SemaCXX/coroutine-uninitialized-warning-crash-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine-uninitialized-warning-crash-exp-namespace.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wall -Wextra -Wuninitialized -fblocks +#include "Inputs/std-coroutine-exp-namespace.h" + +using namespace std::experimental; + +struct A { + bool await_ready() { return true; } + int await_resume() { return 42; } + template + void await_suspend(F) {} +}; + +struct coro_t { + struct promise_type { + coro_t get_return_object() { return {}; } + suspend_never initial_suspend() { return {}; } + suspend_never final_suspend() noexcept { return {}; } + A yield_value(int) { return {}; } + void return_void() {} + static void unhandled_exception() {} + }; +}; + +coro_t f(int n) { + if (n == 0) + co_return; + co_yield 42; + int x = co_await A{}; +} + +template +coro_t g(int n) { + if (n == 0) + co_return; + co_yield 42; + int x = co_await Await{}; +} + +int main() { + f(0); + g(0); +} Index: clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp =================================================================== --- clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp +++ clang/test/SemaCXX/coroutine-uninitialized-warning-crash.cpp @@ -1,8 +1,7 @@ -// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++14 -fcoroutines-ts -fsyntax-only -Wall -Wextra -Wuninitialized -fblocks +// RUN: %clang_cc1 -triple x86_64-apple-darwin9 %s -std=c++20 -fsyntax-only -Wall -Wextra -Wuninitialized -fblocks #include "Inputs/std-coroutine.h" -using namespace std::experimental; - +using namespace std; struct A { bool await_ready() { return true; } Index: clang/test/SemaCXX/coroutine_handle-address-return-type.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutine_handle-address-return-type.cpp @@ -0,0 +1,75 @@ +// RUN: %clang_cc1 -verify %s -stdlib=libc++ -std=c++20 -fsyntax-only + +namespace std { +template +struct coroutine_handle; + +template <> +struct coroutine_handle { + coroutine_handle() = default; + static coroutine_handle from_address(void *) noexcept; + void *address() const; +}; + +template +struct coroutine_handle : public coroutine_handle<> { +}; + +template +struct void_t_imp { + using type = void; +}; +template +using void_t = typename void_t_imp::type; + +template +struct traits_sfinae_base {}; + +template +struct traits_sfinae_base> { + using promise_type = typename T::promise_type; +}; + +template +struct coroutine_traits : public traits_sfinae_base {}; +} // namespace std + +struct suspend_never { + bool await_ready() noexcept; + void await_suspend(std::coroutine_handle<>) noexcept; + void await_resume() noexcept; +}; + +struct task { + struct promise_type { + auto initial_suspend() { return suspend_never{}; } + auto final_suspend() noexcept { return suspend_never{}; } + auto get_return_object() { return task{}; } + static void unhandled_exception() {} + void return_void() {} + }; +}; + +namespace std { +template <> +struct coroutine_handle : public coroutine_handle<> { + coroutine_handle *address() const; // expected-warning {{return type of 'coroutine_handle<>::address should be 'void*'}} +}; +} // namespace std + +struct awaitable { + bool await_ready(); + + std::coroutine_handle + await_suspend(std::coroutine_handle<> handle); + void await_resume(); +} a; + +task f() { + co_await a; +} + +int main() { + f(); + return 0; +} Index: clang/test/SemaCXX/coroutines-exp-namespace.cpp =================================================================== --- /dev/null +++ clang/test/SemaCXX/coroutines-exp-namespace.cpp @@ -0,0 +1,1450 @@ +// This file is the same as coroutines.cpp, except the components are defined in namespace std::experimental. +// The intent of this test is to make sure the std::experimental implementation still works. +// TODO: Remove this test once we drop support for . + +// RUN: %clang_cc1 -std=c++2b -fsyntax-only -verify=expected,cxx20_2b,cxx2b %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=expected,cxx14_20,cxx20_2b %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -fsyntax-only -verify=expected,cxx14_20 %s -fcxx-exceptions -fexceptions -Wunused-result + +void no_coroutine_traits_bad_arg_await() { + co_await a; // expected-error {{include }} + // expected-error@-1 {{use of undeclared identifier 'a'}} +} + +void no_coroutine_traits_bad_arg_yield() { + co_yield a; // expected-error {{include }} + // expected-error@-1 {{use of undeclared identifier 'a'}} +} + +void no_coroutine_traits_bad_arg_return() { + co_return a; // expected-error {{include }} + // expected-error@-1 {{use of undeclared identifier 'a'}} +} + +void no_coroutine_traits() { + co_await 4; // expected-error {{std::coroutine_traits type was not found; include before defining a coroutine; include if your version of libcxx is less than 14.0}} +} + +namespace std { +namespace experimental { + +template +struct void_t_imp { + using type = void; +}; +template +using void_t = typename void_t_imp::type; + +template +struct traits_sfinae_base {}; + +template +struct traits_sfinae_base> { + using promise_type = typename T::promise_type; +}; + +template +struct coroutine_traits : public traits_sfinae_base {}; +} // namespace experimental +} // namespace std + +template struct coro {}; +template +struct std::experimental::coroutine_traits, Ps...> { + using promise_type = Promise; +}; + +struct awaitable { + bool await_ready() noexcept; + template + void await_suspend(F) noexcept; + void await_resume() noexcept; +} a; + +struct suspend_always { + bool await_ready() noexcept { return false; } + template + void await_suspend(F) noexcept; + void await_resume() noexcept {} +}; + +struct suspend_never { + bool await_ready() noexcept { return true; } + template + void await_suspend(F) noexcept; + void await_resume() noexcept {} +}; + +struct auto_await_suspend { + bool await_ready(); + template auto await_suspend(F) {} + void await_resume(); +}; + +struct DummyVoidTag {}; +DummyVoidTag no_specialization() { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_await a; +} + +template +struct std::experimental::coroutine_traits {}; + +int no_promise_type() { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_await a; +} + +int no_promise_type_multiple_awaits(int) { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_await a; + co_await a; +} + +template <> +struct std::experimental::coroutine_traits { typedef int promise_type; }; +double bad_promise_type(double) { // expected-error {{this function cannot be a coroutine: 'experimental::coroutine_traits::promise_type' (aka 'int') is not a class}} + co_await a; +} + +template <> +struct std::experimental::coroutine_traits { + struct promise_type {}; +}; +double bad_promise_type_2(int) { // expected-error {{no member named 'initial_suspend'}} + co_yield 0; // expected-error {{no member named 'yield_value' in 'std::experimental::coroutine_traits::promise_type'}} +} + +struct promise; // expected-note {{forward declaration}} +struct promise_void; +struct void_tag {}; +template +struct std::experimental::coroutine_traits { using promise_type = promise; }; +template +struct std::experimental::coroutine_traits { using promise_type = promise_void; }; + +// FIXME: This diagnostic is terrible. +void undefined_promise() { // expected-error {{this function cannot be a coroutine: 'experimental::coroutine_traits::promise_type' (aka 'promise') is an incomplete type}} + co_await a; +} + +struct yielded_thing { + const char *p; + short a, b; +}; + +struct not_awaitable {}; + +struct promise { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + awaitable yield_value(int); // expected-note 2{{candidate}} + awaitable yield_value(yielded_thing); // expected-note 2{{candidate}} + not_awaitable yield_value(void()); // expected-note 2{{candidate}} + void return_value(int); // expected-note 2{{here}} + void unhandled_exception(); +}; + +struct promise_void { + void get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +void no_coroutine_handle() { // expected-error {{std::coroutine_handle type was not found; include before defining a coroutine; include if your version of libcxx is less than 14.0}} + //expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} + co_return 5; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +namespace std { +namespace experimental { +template +struct coroutine_handle { + static coroutine_handle from_address(void *) noexcept; +}; +template <> +struct coroutine_handle { + template + coroutine_handle(coroutine_handle) noexcept; + static coroutine_handle from_address(void *) noexcept; +}; +} // namespace experimental +} // namespace std + +void yield() { + co_yield 0; + co_yield {"foo", 1, 2}; + co_yield {1e100}; // expected-error {{cannot be narrowed}} expected-note {{explicit cast}} expected-warning {{implicit conversion}} expected-warning {{braces around scalar}} + co_yield {"foo", __LONG_LONG_MAX__}; // expected-error {{cannot be narrowed}} expected-note {{explicit cast}} expected-warning {{changes value}} + co_yield {"foo"}; + co_yield "foo"; // expected-error {{no matching}} + co_yield 1.0; + co_yield yield; // expected-error {{no member named 'await_ready' in 'not_awaitable'}} +} + +void check_auto_await_suspend() { + co_await auto_await_suspend{}; // Should compile successfully. +} + +void coreturn(int n) { + co_await a; + if (n == 0) + co_return 3; + if (n == 1) + co_return {4}; // expected-warning {{braces around scalar initializer}} + if (n == 2) + co_return "foo"; // expected-error {{cannot initialize a parameter of type 'int' with an lvalue of type 'const char[4]'}} + co_return 42; +} + +template +void co_await_non_dependent_arg(T) { + co_await a; +} +template void co_await_non_dependent_arg(int); + +void mixed_yield() { + co_yield 0; // expected-note {{use of 'co_yield'}} + return; // expected-error {{not allowed in coroutine}} +} + +void mixed_yield_invalid() { + co_yield blah; // expected-error {{use of undeclared identifier}} + // expected-note@-1 {{function is a coroutine due to use of 'co_yield'}} + return; // expected-error {{return statement not allowed in coroutine}} +} + +template +void mixed_yield_template(T) { + co_yield blah; // expected-error {{use of undeclared identifier}} + // expected-note@-1 {{function is a coroutine due to use of 'co_yield'}} + return; // expected-error {{return statement not allowed in coroutine}} +} + +template +void mixed_yield_template2(T) { + co_yield 42; + // expected-note@-1 {{function is a coroutine due to use of 'co_yield'}} + return; // expected-error {{return statement not allowed in coroutine}} +} + +template +void mixed_yield_template3(T v) { + co_yield blah(v); + // expected-note@-1 {{function is a coroutine due to use of 'co_yield'}} + return; // expected-error {{return statement not allowed in coroutine}} +} + +void mixed_await() { + co_await a; // expected-note {{use of 'co_await'}} + return; // expected-error {{not allowed in coroutine}} +} + +void mixed_await_invalid() { + co_await 42; // expected-error {{'int' is not a structure or union}} + // expected-note@-1 {{function is a coroutine due to use of 'co_await'}} + return; // expected-error {{not allowed in coroutine}} +} + +template +void mixed_await_template(T) { + co_await 42; + // expected-note@-1 {{function is a coroutine due to use of 'co_await'}} + return; // expected-error {{not allowed in coroutine}} +} + +template +void mixed_await_template2(T v) { + co_await v; // expected-error {{'long' is not a structure or union}} + // expected-note@-1 {{function is a coroutine due to use of 'co_await'}} + return; // expected-error {{not allowed in coroutine}} +} +template void mixed_await_template2(long); // expected-note {{requested here}} + +void only_coreturn(void_tag) { + co_return; // OK +} + +void mixed_coreturn(void_tag, bool b) { + if (b) + co_return; // expected-note {{use of 'co_return'}} + else + return; // expected-error {{not allowed in coroutine}} +} + +void mixed_coreturn_invalid(bool b) { + if (b) + co_return; // expected-note {{use of 'co_return'}} + // expected-error@-1 {{no member named 'return_void' in 'promise'}} + else + return; // expected-error {{not allowed in coroutine}} +} + +template +void mixed_coreturn_template(void_tag, bool b, T v) { + if (b) + co_return v; // expected-note {{use of 'co_return'}} + // expected-error@-1 {{no member named 'return_value' in 'promise_void'}} + else + return; // expected-error {{not allowed in coroutine}} +} +template void mixed_coreturn_template(void_tag, bool, int); // expected-note {{requested here}} + +template +void mixed_coreturn_template2(bool b, T) { + if (b) + co_return v; // expected-note {{use of 'co_return'}} + // expected-error@-1 {{use of undeclared identifier 'v'}} + else + return; // expected-error {{not allowed in coroutine}} +} + +struct CtorDtor { + CtorDtor() { + co_yield 0; // expected-error {{'co_yield' cannot be used in a constructor}} + } + CtorDtor(awaitable a) { + // The spec doesn't say this is ill-formed, but it must be. + co_await a; // expected-error {{'co_await' cannot be used in a constructor}} + } + ~CtorDtor() { + co_return 0; // expected-error {{'co_return' cannot be used in a destructor}} + } + void operator=(CtorDtor &) { + co_yield 0; // OK. + } + void operator=(CtorDtor const &) { + co_yield 0; // OK. + } + void operator=(CtorDtor &&) { + co_await a; // OK. + } + void operator=(CtorDtor const &&) { + co_await a; // OK. + } + void operator=(int) { + co_await a; // OK. Not a special member + } +}; + +namespace std { +class type_info; +} + +void unevaluated() { + decltype(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}} + // expected-warning@-1 {{declaration does not declare anything}} + sizeof(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}} + // expected-error@-1 {{invalid application of 'sizeof' to an incomplete type 'void'}} + // expected-warning@-2 {{expression with side effects has no effect in an unevaluated context}} + typeid(co_await a); // expected-error {{'co_await' cannot be used in an unevaluated context}} + // expected-warning@-1 {{expression with side effects has no effect in an unevaluated context}} + // expected-warning@-2 {{expression result unused}} + decltype(co_yield 1); // expected-error {{'co_yield' cannot be used in an unevaluated context}} + // expected-warning@-1 {{declaration does not declare anything}} + sizeof(co_yield 2); // expected-error {{'co_yield' cannot be used in an unevaluated context}} + // expected-error@-1 {{invalid application of 'sizeof' to an incomplete type 'void'}} + // expected-warning@-2 {{expression with side effects has no effect in an unevaluated context}} + typeid(co_yield 3); // expected-error {{'co_yield' cannot be used in an unevaluated context}} + // expected-warning@-1 {{expression with side effects has no effect in an unevaluated context}} + // expected-warning@-2 {{expression result unused}} +} + +// [expr.await]p2: "An await-expression shall not appear in a default argument." +// FIXME: A better diagnostic would explicitly state that default arguments are +// not allowed. A user may not understand that this is "outside a function." +void default_argument(int arg = co_await 0) {} // expected-error {{'co_await' cannot be used outside a function}} + +void await_in_catch_coroutine() { + try { + } catch (...) { // FIXME: Emit a note diagnostic pointing out the try handler on this line. + []() -> void { co_await a; }(); // OK + co_await a; // expected-error {{'co_await' cannot be used in the handler of a try block}} + } +} + +void await_nested_in_catch_coroutine() { + try { + } catch (...) { // FIXME: Emit a note diagnostic pointing out the try handler on this line. + try { + co_await a; // expected-error {{'co_await' cannot be used in the handler of a try block}} + []() -> void { co_await a; }(); // OK + } catch (...) { + co_return 123; + } + } +} + +void await_in_lambda_in_catch_coroutine() { + try { + } catch (...) { + []() -> void { co_await a; }(); // OK + } +} + +void yield_in_catch_coroutine() { + try { + } catch (...) { + co_yield 1; // expected-error {{'co_yield' cannot be used in the handler of a try block}} + } +} + +void return_in_catch_coroutine() { + try { + } catch (...) { + co_return 123; // OK + } +} + +constexpr auto constexpr_deduced_return_coroutine() { + co_yield 0; // expected-error {{'co_yield' cannot be used in a constexpr function}} + // expected-error@-1 {{'co_yield' cannot be used in a function with a deduced return type}} +} + +void varargs_coroutine(const char *, ...) { + co_await a; // expected-error {{'co_await' cannot be used in a varargs function}} +} + +auto deduced_return_coroutine() { + co_await a; // expected-error {{'co_await' cannot be used in a function with a deduced return type}} +} + +struct outer {}; +struct await_arg_1 {}; +struct await_arg_2 {}; + +namespace adl_ns { +struct coawait_arg_type {}; +awaitable operator co_await(coawait_arg_type) noexcept; +} // namespace adl_ns + +namespace dependent_operator_co_await_lookup { +template void await_template(T t) { + // no unqualified lookup results + co_await t; // expected-error {{no member named 'await_ready' in 'dependent_operator_co_await_lookup::not_awaitable'}} + // expected-error@-1 {{call to function 'operator co_await' that is neither visible in the template definition nor found by argument-dependent lookup}} +}; +template void await_template(awaitable); + +struct indirectly_awaitable { + indirectly_awaitable(outer); +}; +awaitable operator co_await(indirectly_awaitable); // expected-note {{should be declared prior to}} +template void await_template(indirectly_awaitable); + +struct not_awaitable {}; +template void await_template(not_awaitable); // expected-note {{instantiation}} + +template void await_template_2(T t) { + // one unqualified lookup result + co_await t; +}; +template void await_template(outer); // expected-note {{instantiation}} +template void await_template_2(outer); + +struct transform_awaitable {}; +struct transformed {}; + +struct transform_promise { + typedef transform_awaitable await_arg; + coro get_return_object(); + transformed initial_suspend(); + ::adl_ns::coawait_arg_type final_suspend() noexcept; + transformed await_transform(transform_awaitable); + void unhandled_exception(); + void return_void(); +}; +template +struct basic_promise { + typedef AwaitArg await_arg; + coro get_return_object(); + awaitable initial_suspend(); + awaitable final_suspend() noexcept; + void unhandled_exception(); + void return_void(); +}; + +awaitable operator co_await(await_arg_1); + +template +coro await_template_3(U t) { + co_await t; +} + +template coro> await_template_3>(await_arg_1); + +template +struct dependent_member { + coro mem_fn() const { + co_await typename T::await_arg{}; // expected-error {{call to function 'operator co_await'}}} + } + template + coro dep_mem_fn(U t) { + co_await t; + } +}; + +template <> +struct dependent_member { + // FIXME this diagnostic is terrible + coro mem_fn() const { // expected-error {{no member named 'await_ready' in 'dependent_operator_co_await_lookup::transformed'}} + // expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} + // expected-note@+1 {{function is a coroutine due to use of 'co_await' here}} + co_await transform_awaitable{}; + // expected-error@-1 {{no member named 'await_ready'}} + } + template + coro dep_mem_fn(U u) { co_await u; } +}; + +awaitable operator co_await(await_arg_2); // expected-note {{'operator co_await' should be declared prior to the call site}} + +template struct dependent_member, 0>; +template struct dependent_member, 0>; // expected-note {{in instantiation}} + +template <> +coro +// FIXME this diagnostic is terrible +dependent_member::dep_mem_fn(int) { // expected-error {{no member named 'await_ready' in 'dependent_operator_co_await_lookup::transformed'}} + //expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} + //expected-note@+1 {{function is a coroutine due to use of 'co_await' here}} + co_await transform_awaitable{}; + // expected-error@-1 {{no member named 'await_ready'}} +} + +void operator co_await(transform_awaitable) = delete; +awaitable operator co_await(transformed); + +template coro + dependent_member::dep_mem_fn(transform_awaitable); + +template <> +coro dependent_member::dep_mem_fn(long) { + co_await transform_awaitable{}; +} + +template <> +struct dependent_member { + coro mem_fn() const { + co_await transform_awaitable{}; + } +}; + +template coro await_template_3(transform_awaitable); +template struct dependent_member; +template coro dependent_member::dep_mem_fn(transform_awaitable); +} // namespace dependent_operator_co_await_lookup + +struct yield_fn_tag {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + // FIXME: add an await_transform overload for functions + awaitable yield_value(int()); + void return_value(int()); + + suspend_never initial_suspend(); + suspend_never final_suspend() noexcept; + void get_return_object(); + void unhandled_exception(); + }; +}; + +namespace placeholder { +awaitable f(), f(int); // expected-note 4{{possible target}} +int g(), g(int); // expected-note 2{{candidate}} +void x() { + co_await f; // expected-error {{reference to overloaded function}} +} +void y() { + co_yield g; // expected-error {{no matching member function for call to 'yield_value'}} +} +void z() { + co_await a; + co_return g; // expected-error {{address of overloaded function 'g' does not match required type 'int'}} +} + +void x(yield_fn_tag) { + co_await f; // expected-error {{reference to overloaded function}} +} +void y(yield_fn_tag) { + co_yield g; +} +void z(yield_fn_tag) { + co_await a; + co_return g; +} +} // namespace placeholder + +struct bad_promise_1 { + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); +}; +coro missing_get_return_object() { // expected-error {{no member named 'get_return_object' in 'bad_promise_1'}} + co_await a; +} + +struct bad_promise_2 { + coro get_return_object(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); +}; +// FIXME: This shouldn't happen twice +coro missing_initial_suspend() { // expected-error {{no member named 'initial_suspend' in 'bad_promise_2'}} + co_await a; +} + +struct bad_promise_3 { + coro get_return_object(); + suspend_always initial_suspend(); + void unhandled_exception(); + void return_void(); +}; +coro missing_final_suspend() noexcept { // expected-error {{no member named 'final_suspend' in 'bad_promise_3'}} + co_await a; +} + +struct bad_promise_4 { + coro get_return_object(); + not_awaitable initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); +}; +// FIXME: This diagnostic is terrible. +coro bad_initial_suspend() { // expected-error {{no member named 'await_ready' in 'not_awaitable'}} + // expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} + co_await a; // expected-note {{function is a coroutine due to use of 'co_await' here}} +} + +struct bad_promise_5 { + coro get_return_object(); + suspend_always initial_suspend(); + not_awaitable final_suspend() noexcept; + void return_void(); +}; +// FIXME: This diagnostic is terrible. +coro bad_final_suspend() { // expected-error {{no member named 'await_ready' in 'not_awaitable'}} + // expected-note@-1 {{call to 'final_suspend' implicitly required by the final suspend point}} + co_await a; // expected-note {{function is a coroutine due to use of 'co_await' here}} +} + +struct bad_promise_6 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); // expected-note 2 {{member 'return_void' first declared here}} + void return_value(int) const; // expected-note 2 {{member 'return_value' first declared here}} + void return_value(int); +}; +coro bad_implicit_return() { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}} + co_await a; +} + +template +coro bad_implicit_return_dependent(T) { // expected-error {{'bad_promise_6' declares both 'return_value' and 'return_void'}} + co_await a; +} +template coro bad_implicit_return_dependent(bad_promise_6); // expected-note {{in instantiation}} + +struct bad_promise_7 { // expected-note 2 {{defined here}} + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); +}; +coro no_unhandled_exception() { // expected-error {{'bad_promise_7' is required to declare the member 'unhandled_exception()'}} + co_await a; +} + +template +coro no_unhandled_exception_dependent(T) { // expected-error {{'bad_promise_7' is required to declare the member 'unhandled_exception()'}} + co_await a; +} +template coro no_unhandled_exception_dependent(bad_promise_7); // expected-note {{in instantiation}} + +struct bad_promise_base { +private: + void return_void(); // expected-note 2 {{declared private here}} +}; +struct bad_promise_8 : bad_promise_base { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception() __attribute__((unavailable)); // expected-note 2 {{marked unavailable here}} + void unhandled_exception() const; + void unhandled_exception(void *) const; +}; +coro calls_unhandled_exception() { + // expected-error@-1 {{'unhandled_exception' is unavailable}} + // expected-error@-2 {{'return_void' is a private member}} + co_await a; +} + +template +coro calls_unhandled_exception_dependent(T) { + // expected-error@-1 {{'unhandled_exception' is unavailable}} + // expected-error@-2 {{'return_void' is a private member}} + co_await a; +} +template coro calls_unhandled_exception_dependent(bad_promise_8); // expected-note {{in instantiation}} + +struct bad_promise_9 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void await_transform(void *); + awaitable await_transform(int) __attribute__((unavailable)); // expected-note {{explicitly marked unavailable}} + void return_void(); + void unhandled_exception(); +}; +coro calls_await_transform() { + co_await 42; // expected-error {{'await_transform' is unavailable}} +} + +struct bad_promise_10 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + int await_transform; + void return_void(); + void unhandled_exception(); +}; +coro bad_coawait() { + // FIXME this diagnostic is terrible + co_await 42; // expected-error {{called object type 'int' is not a function or function pointer}} + // expected-note@-1 {{call to 'await_transform' implicitly required by 'co_await' here}} +} + +struct call_operator { + template + awaitable operator()(Args...) const { return a; } +}; +void ret_void(); +struct good_promise_1 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + static const call_operator await_transform; + using Fn = void (*)(); + Fn return_void = ret_void; +}; +const call_operator good_promise_1::await_transform; +coro ok_static_coawait() { + // FIXME this diagnostic is terrible + co_await 42; +} + +template void ok_generic_lambda_coawait_PR41909() { + [](auto &arg) -> coro { // expected-warning {{expression result unused}} + co_await 12; + }; + [](auto &arg) -> coro { + co_await 24; + }("argument"); + [](auto &arg) -> coro { // expected-warning {{expression result unused}} + []() -> coro { + co_await 36; + }; + co_await 48; + }; +} +template void ok_generic_lambda_coawait_PR41909(); // expected-note {{in instantiation of function template specialization 'ok_generic_lambda_coawait_PR41909' requested here}} + +template <> struct std::experimental::coroutine_traits { using promise_type = promise; }; + +int main(int, const char **) { + co_await a; // expected-error {{'co_await' cannot be used in the 'main' function}} +} + +struct good_promise_2 { + float get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; +template <> struct std::experimental::coroutine_handle {}; + +template <> struct std::experimental::coroutine_traits { using promise_type = good_promise_2; }; + +float badly_specialized_coro_handle() { // expected-error {{std::coroutine_handle must have a member named 'from_address'}} + //expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +namespace std { +struct nothrow_t {}; +constexpr nothrow_t nothrow = {}; +} // namespace std + +using SizeT = decltype(sizeof(int)); + +void *operator new(SizeT __sz, const std::nothrow_t &) noexcept; +void operator delete(void *__p, const std::nothrow_t &)noexcept; + +struct promise_on_alloc_failure_tag {}; + +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + int get_return_object() {} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + int get_return_object_on_allocation_failure(); // expected-error{{'promise_type': 'get_return_object_on_allocation_failure()' must be a static member function}} + void unhandled_exception(); + }; +}; + +extern "C" int f(promise_on_alloc_failure_tag) { + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +struct bad_promise_11 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); + +private: + static coro get_return_object_on_allocation_failure(); // expected-note 2 {{declared private here}} +}; +coro private_alloc_failure_handler() { + // expected-error@-1 {{'get_return_object_on_allocation_failure' is a private member of 'bad_promise_11'}} + co_return; // FIXME: Add a "declared coroutine here" note. +} + +template +coro dependent_private_alloc_failure_handler(T) { + // expected-error@-1 {{'get_return_object_on_allocation_failure' is a private member of 'bad_promise_11'}} + co_return; // FIXME: Add a "declared coroutine here" note. +} +template coro dependent_private_alloc_failure_handler(bad_promise_11); +// expected-note@-1 {{requested here}} + +struct bad_promise_12 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); + static coro get_return_object_on_allocation_failure(); + + static void *operator new(SizeT); + // expected-error@-1 2 {{'operator new' is required to have a non-throwing noexcept specification when the promise type declares 'get_return_object_on_allocation_failure()'}} +}; +coro throwing_in_class_new() { // expected-note {{call to 'operator new' implicitly required by coroutine function here}} + co_return; +} + +template +coro dependent_throwing_in_class_new(T) { // expected-note {{call to 'operator new' implicitly required by coroutine function here}} + co_return; +} +template coro dependent_throwing_in_class_new(bad_promise_12); // expected-note {{requested here}} + +struct good_promise_13 { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); + void return_void(); + static coro get_return_object_on_allocation_failure(); +}; +coro uses_nothrow_new() { + co_return; +} + +template +coro dependent_uses_nothrow_new(T) { + co_return; +} +template coro dependent_uses_nothrow_new(good_promise_13); + +struct good_promise_custom_new_operator { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + void *operator new(SizeT, double, float, int); +}; + +coro +good_coroutine_calls_custom_new_operator(double, float, int) { + co_return; +} + +struct coroutine_nonstatic_member_struct; + +struct good_promise_nonstatic_member_custom_new_operator { + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + void *operator new(SizeT, coroutine_nonstatic_member_struct &, double); +}; + +struct good_promise_noexcept_custom_new_operator { + static coro get_return_object_on_allocation_failure(); + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); + void *operator new(SizeT, double, float, int) noexcept; +}; + +coro +good_coroutine_calls_noexcept_custom_new_operator(double, float, int) { + co_return; +} + +struct mismatch_gro_type_tag1 {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void get_return_object() {} //expected-note {{member 'get_return_object' declared here}} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception(); + }; +}; + +extern "C" int f(mismatch_gro_type_tag1) { + // expected-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void'}} + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +struct mismatch_gro_type_tag2 {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + void *get_return_object() {} //expected-note {{member 'get_return_object' declared here}} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception(); + }; +}; + +extern "C" int f(mismatch_gro_type_tag2) { + // cxx2b-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void *'}} + // cxx14_20-error@-2 {{cannot initialize return object of type 'int' with an lvalue of type 'void *'}} + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +struct mismatch_gro_type_tag3 {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + int get_return_object() {} + static void get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared here}} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception(); + }; +}; + +extern "C" int f(mismatch_gro_type_tag3) { + // expected-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'void'}} + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +struct mismatch_gro_type_tag4 {}; +template <> +struct std::experimental::coroutine_traits { + struct promise_type { + int get_return_object() {} + static char *get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared}} + suspend_always initial_suspend() { return {}; } + suspend_always final_suspend() noexcept { return {}; } + void return_void() {} + void unhandled_exception(); + }; +}; + +extern "C" int f(mismatch_gro_type_tag4) { + // expected-error@-1 {{cannot initialize return object of type 'int' with an rvalue of type 'char *'}} + co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} +} + +struct bad_promise_no_return_func { // expected-note {{'bad_promise_no_return_func' defined here}} + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void unhandled_exception(); +}; +// FIXME: The PDTS currently specifies this as UB, technically forbidding a +// diagnostic. +coro no_return_value_or_return_void() { + // expected-error@-1 {{'bad_promise_no_return_func' must declare either 'return_value' or 'return_void'}} + co_await a; +} + +struct bad_await_suspend_return { + bool await_ready(); + // expected-error@+1 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'char')}} + char await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +struct bad_await_ready_return { + // expected-note@+1 {{return type of 'await_ready' is required to be contextually convertible to 'bool'}} + void await_ready(); + bool await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +struct await_ready_explicit_bool { + struct BoolT { + explicit operator bool() const; + }; + BoolT await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +template +struct await_suspend_type_test { + bool await_ready(); + // expected-error@+2 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'bool &')}} + // expected-error@+1 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'bool &&')}} + SuspendTy await_suspend(std::experimental::coroutine_handle<>); + // cxx20_2b-warning@-1 {{volatile-qualified return type 'const volatile bool' is deprecated}} + void await_resume(); +}; +void test_bad_suspend() { + { + // FIXME: The actual error emitted here is terrible, and no number of notes can save it. + bad_await_ready_return a; + // expected-error@+1 {{value of type 'void' is not contextually convertible to 'bool'}} + co_await a; // expected-note {{call to 'await_ready' implicitly required by coroutine function here}} + } + { + bad_await_suspend_return b; + co_await b; // expected-note {{call to 'await_suspend' implicitly required by coroutine function here}} + } + { + await_ready_explicit_bool c; + co_await c; // OK + } + { + await_suspend_type_test a; + await_suspend_type_test b; + await_suspend_type_test c; + await_suspend_type_test d; // cxx20_2b-note {{in instantiation of template class}} + co_await a; // expected-note {{call to 'await_suspend' implicitly required by coroutine function here}} + co_await b; // expected-note {{call to 'await_suspend' implicitly required by coroutine function here}} + co_await c; // OK + co_await d; // OK + } +} + +template +struct NoCopy { + NoCopy(NoCopy const &) = delete; // expected-note 2 {{deleted here}} +}; +template +void test_dependent_param(T t, U) { + // expected-error@-1 {{call to deleted constructor of 'NoCopy<0>'}} + // expected-error@-2 {{call to deleted constructor of 'NoCopy<1>'}} + ((void)t); + co_return 42; +} +template void test_dependent_param(NoCopy<0>, NoCopy<1>); // expected-note {{requested here}} + +namespace CoroHandleMemberFunctionTest { +struct CoroMemberTag {}; +struct BadCoroMemberTag {}; + +template +constexpr bool IsSameV = false; +template +constexpr bool IsSameV = true; + +template +struct TypeTest { + template + static constexpr bool IsSame = IsSameV; + + template + static constexpr bool MatchesArgs = IsSameV>; +}; + +template +struct AwaitReturnsType { + bool await_ready() const; + void await_suspend(...) const; + T await_resume() const; +}; + +template +struct CoroMemberPromise { + using TraitsT = std::experimental::coroutine_traits; + using TypeTestT = TypeTest; + using AwaitTestT = AwaitReturnsType; + + CoroMemberTag get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + + AwaitTestT yield_value(int); + + void return_void(); + void unhandled_exception(); +}; + +} // namespace CoroHandleMemberFunctionTest + +template +struct ::std::experimental::coroutine_traits { + using promise_type = CoroHandleMemberFunctionTest::CoroMemberPromise; +}; + +namespace CoroHandleMemberFunctionTest { +struct TestType { + + CoroMemberTag test_qual() { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + static_assert(!TC.MatchesArgs, ""); + static_assert(!TC.MatchesArgs, ""); + } + + CoroMemberTag test_sanity(int *) const { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); // expected-error {{static_assert failed}} + static_assert(TC.MatchesArgs, ""); // expected-error {{static_assert failed}} + static_assert(TC.MatchesArgs, ""); + } + + CoroMemberTag test_qual(int *, const float &&, volatile void *volatile) const { + // cxx20_2b-warning@-1 {{volatile-qualified parameter type}} + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + + CoroMemberTag test_qual() const volatile { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + + CoroMemberTag test_ref_qual() & { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + CoroMemberTag test_ref_qual() const & { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + CoroMemberTag test_ref_qual() && { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + CoroMemberTag test_ref_qual(const char *&) const volatile && { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + + CoroMemberTag test_args(int) { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + CoroMemberTag test_args(int, long &, void *) const { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + + template + CoroMemberTag test_member_template(Args...) const && { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + static CoroMemberTag test_static() { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs<>, ""); + static_assert(!TC.MatchesArgs, ""); + static_assert(!TC.MatchesArgs, ""); + static_assert(!TC.MatchesArgs, ""); + } + + static CoroMemberTag test_static(volatile void *const, char &&) { + auto TC = co_yield 0; + static_assert(TC.MatchesArgs, ""); + } + + template + static CoroMemberTag test_static_template(const char *volatile &, unsigned) { + auto TC = co_yield 0; + using TCT = decltype(TC); + static_assert(TCT::MatchesArgs, ""); + static_assert(!TCT::MatchesArgs, ""); + } + + BadCoroMemberTag test_diagnostics() { + // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_return; + } + BadCoroMemberTag test_diagnostics(int) const && { + // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_return; + } + + static BadCoroMemberTag test_static_diagnostics(long *) { + // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + co_return; + } +}; + +template CoroMemberTag TestType::test_member_template(long, const char *) const &&; +template CoroMemberTag TestType::test_static_template(const char *volatile &, unsigned); + +template +struct DepTestType { + + CoroMemberTag test_sanity(int *) const { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); // expected-error {{static_assert failed}} + static_assert(TC.template MatchesArgs<>, ""); // expected-error {{static_assert failed}} + static_assert(TC.template MatchesArgs, ""); + } + + CoroMemberTag test_qual() { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + static_assert(!TC.template MatchesArgs, ""); + static_assert(!TC.template MatchesArgs, ""); + } + + CoroMemberTag test_qual(int *, const float &&, volatile void *volatile) const { + // cxx20_2b-warning@-1 {{volatile-qualified parameter type}} + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + CoroMemberTag test_qual() const volatile { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + CoroMemberTag test_ref_qual() & { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + CoroMemberTag test_ref_qual() const & { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + CoroMemberTag test_ref_qual() && { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + CoroMemberTag test_ref_qual(const char *&) const volatile && { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + CoroMemberTag test_args(int) { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + CoroMemberTag test_args(int, long &, void *) const { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + template + CoroMemberTag test_member_template(UArgs...) const && { + auto TC = co_yield 0; + static_assert(TC.template MatchesArgs, ""); + } + + static CoroMemberTag test_static() { + auto TC = co_yield 0; + using TCT = decltype(TC); + static_assert(TCT::MatchesArgs<>, ""); + static_assert(!TCT::MatchesArgs, ""); + static_assert(!TCT::MatchesArgs, ""); + static_assert(!TCT::MatchesArgs, ""); + + // Ensure diagnostics are actually being generated here + static_assert(TCT::MatchesArgs, ""); // expected-error {{static_assert failed}} + } + + static CoroMemberTag test_static(volatile void *const, char &&) { + auto TC = co_yield 0; + using TCT = decltype(TC); + static_assert(TCT::MatchesArgs, ""); + } + + template + static CoroMemberTag test_static_template(const char *volatile &, unsigned) { + auto TC = co_yield 0; + using TCT = decltype(TC); + static_assert(TCT::MatchesArgs, ""); + static_assert(!TCT::MatchesArgs, ""); + } +}; + +template struct DepTestType; // expected-note {{requested here}} +template CoroMemberTag DepTestType::test_member_template(long, const char *) const &&; + +template CoroMemberTag DepTestType::test_static_template(const char *volatile &, unsigned); + +struct bad_promise_deleted_constructor { + // expected-note@+1 {{'bad_promise_deleted_constructor' has been explicitly marked deleted here}} + bad_promise_deleted_constructor() = delete; + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +coro +bad_coroutine_calls_deleted_promise_constructor() { + // expected-error@-1 {{call to deleted constructor of 'std::experimental::coroutine_traits>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_deleted_constructor')}} + co_return; +} + +// Test that, when the promise type has a constructor whose signature matches +// that of the coroutine function, that constructor is used. If no matching +// constructor exists, the default constructor is used as a fallback. If no +// matching constructors exist at all, an error is emitted. This is an +// experimental feature that will be proposed for the Coroutines TS. + +struct good_promise_default_constructor { + good_promise_default_constructor(double, float, int); + good_promise_default_constructor() = default; + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +coro +good_coroutine_calls_default_constructor() { + co_return; +} + +struct some_class; + +struct good_promise_custom_constructor { + good_promise_custom_constructor(some_class &, float, int); + good_promise_custom_constructor(double, float, int); + good_promise_custom_constructor() = delete; + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +coro +good_coroutine_calls_custom_constructor(double, float, int) { + co_return; +} + +struct some_class { + coro + good_coroutine_calls_custom_constructor(float, int) { + co_return; + } + coro static good_coroutine_calls_custom_constructor(double, float, int) { + co_return; + } +}; + +struct bad_promise_no_matching_constructor { + bad_promise_no_matching_constructor(int, int, int); + // expected-note@+1 2 {{'bad_promise_no_matching_constructor' has been explicitly marked deleted here}} + bad_promise_no_matching_constructor() = delete; + coro get_return_object(); + suspend_always initial_suspend(); + suspend_always final_suspend() noexcept; + void return_void(); + void unhandled_exception(); +}; + +coro +bad_coroutine_calls_with_no_matching_constructor(int, int) { + // expected-error@-1 {{call to deleted constructor of 'std::experimental::coroutine_traits, int, int>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_no_matching_constructor')}} + co_return; +} + +struct some_class2 { + coro + bad_coroutine_calls_with_no_matching_constructor(int, int, int) { + // expected-error@-1 {{call to deleted constructor}} + co_return; + } +}; + +} // namespace CoroHandleMemberFunctionTest + +class awaitable_no_unused_warn { +public: + using handle_type = std::experimental::coroutine_handle<>; + constexpr bool await_ready() noexcept { return false; } + void await_suspend(handle_type) noexcept {} + int await_resume() noexcept { return 1; } +}; + +class awaitable_unused_warn { +public: + using handle_type = std::experimental::coroutine_handle<>; + constexpr bool await_ready() noexcept { return false; } + void await_suspend(handle_type) noexcept {} + [[nodiscard]] int await_resume() noexcept { return 1; } +}; + +template +struct check_warning_promise { + coro get_return_object(); + Await initial_suspend(); + Await final_suspend() noexcept; + Await yield_value(int); + void return_void(); + void unhandled_exception(); +}; + +coro> +test_no_unused_warning() { + co_await awaitable_no_unused_warn(); + co_yield 42; +} + +coro> +test_unused_warning() { + co_await awaitable_unused_warn(); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} + co_yield 42; // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}} +} + +struct missing_await_ready { + void await_suspend(std::experimental::coroutine_handle<>); + void await_resume(); +}; +struct missing_await_suspend { + bool await_ready(); + void await_resume(); +}; +struct missing_await_resume { + bool await_ready(); + void await_suspend(std::experimental::coroutine_handle<>); +}; + +void test_missing_awaitable_members() { + co_await missing_await_ready{}; // expected-error {{no member named 'await_ready' in 'missing_await_ready'}} + co_await missing_await_suspend{}; // expected-error {{no member named 'await_suspend' in 'missing_await_suspend'}} + co_await missing_await_resume{}; // expected-error {{no member named 'await_resume' in 'missing_await_resume'}} +} Index: clang/test/SemaCXX/coroutines.cpp =================================================================== --- clang/test/SemaCXX/coroutines.cpp +++ clang/test/SemaCXX/coroutines.cpp @@ -1,32 +1,30 @@ // This file contains references to sections of the Coroutines TS, which can be // found at http://wg21.link/coroutines. -// RUN: %clang_cc1 -std=c++2b -fsyntax-only -verify=expected,cxx20_2b,cxx2b %s -fcxx-exceptions -fexceptions -Wunused-result -// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=expected,cxx14_20,cxx20_2b %s -fcxx-exceptions -fexceptions -Wunused-result -// RUN: %clang_cc1 -std=c++14 -fcoroutines-ts -fsyntax-only -verify=expected,cxx14_20 %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++2b -fsyntax-only -verify=expected,cxx20_2b,cxx2b %s -fcxx-exceptions -fexceptions -Wunused-result +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify=expected,cxx14_20,cxx20_2b %s -fcxx-exceptions -fexceptions -Wunused-result void no_coroutine_traits_bad_arg_await() { - co_await a; // expected-error {{include }} + co_await a; // expected-error {{include }} // expected-error@-1 {{use of undeclared identifier 'a'}} } void no_coroutine_traits_bad_arg_yield() { - co_yield a; // expected-error {{include }} + co_yield a; // expected-error {{include }} // expected-error@-1 {{use of undeclared identifier 'a'}} } void no_coroutine_traits_bad_arg_return() { - co_return a; // expected-error {{include }} + co_return a; // expected-error {{include }} // expected-error@-1 {{use of undeclared identifier 'a'}} } void no_coroutine_traits() { - co_await 4; // expected-error {{std::experimental::coroutine_traits type was not found; include }} + co_await 4; // expected-error {{std::coroutine_traits type was not found; include }} } namespace std { -namespace experimental { template struct void_t_imp { @@ -45,11 +43,11 @@ template struct coroutine_traits : public traits_sfinae_base {}; -}} // namespace std::experimental +} // end of namespace std template struct coro {}; template -struct std::experimental::coroutine_traits, Ps...> { +struct std::coroutine_traits, Ps...> { using promise_type = Promise; }; @@ -81,47 +79,46 @@ }; struct DummyVoidTag {}; -DummyVoidTag no_specialization() { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} +DummyVoidTag no_specialization() { // expected-error {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_await a; } template -struct std::experimental::coroutine_traits {}; +struct std::coroutine_traits {}; -int no_promise_type() { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} +int no_promise_type() { // expected-error {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_await a; } -int no_promise_type_multiple_awaits(int) { // expected-error {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} +int no_promise_type_multiple_awaits(int) { // expected-error {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_await a; co_await a; } template <> -struct std::experimental::coroutine_traits { typedef int promise_type; }; -double bad_promise_type(double) { // expected-error {{this function cannot be a coroutine: 'experimental::coroutine_traits::promise_type' (aka 'int') is not a class}} +struct std::coroutine_traits { typedef int promise_type; }; +double bad_promise_type(double) { // expected-error {{this function cannot be a coroutine: 'std::coroutine_traits::promise_type' (aka 'int') is not a class}} co_await a; } template <> -struct std::experimental::coroutine_traits { +struct std::coroutine_traits { struct promise_type {}; }; double bad_promise_type_2(int) { // expected-error {{no member named 'initial_suspend'}} - co_yield 0; // expected-error {{no member named 'yield_value' in 'std::experimental::coroutine_traits::promise_type'}} + co_yield 0; // expected-error {{no member named 'yield_value' in 'std::coroutine_traits::promise_type'}} } struct promise; // expected-note {{forward declaration}} struct promise_void; struct void_tag {}; template -struct std::experimental::coroutine_traits { using promise_type = promise; }; +struct std::coroutine_traits { using promise_type = promise; }; template -struct std::experimental::coroutine_traits -{ using promise_type = promise_void; }; +struct std::coroutine_traits { using promise_type = promise_void; }; // FIXME: This diagnostic is terrible. -void undefined_promise() { // expected-error {{this function cannot be a coroutine: 'experimental::coroutine_traits::promise_type' (aka 'promise') is an incomplete type}} +void undefined_promise() { // expected-error {{this function cannot be a coroutine: 'std::coroutine_traits::promise_type' (aka 'promise') is an incomplete type}} co_await a; } @@ -148,13 +145,12 @@ void unhandled_exception(); }; -void no_coroutine_handle() { // expected-error {{std::experimental::coroutine_handle type was not found; include before defining a coroutine}} +void no_coroutine_handle() { // expected-error {{std::coroutine_handle type was not found; include before defining a coroutine}} //expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} co_return 5; //expected-note {{function is a coroutine due to use of 'co_return' here}} } namespace std { -namespace experimental { template struct coroutine_handle { static coroutine_handle from_address(void *) noexcept; @@ -165,7 +161,7 @@ coroutine_handle(coroutine_handle) noexcept; static coroutine_handle from_address(void *) noexcept; }; -}} // namespace std::experimental +} // namespace std void yield() { co_yield 0; @@ -529,7 +525,7 @@ struct yield_fn_tag {}; template <> -struct std::experimental::coroutine_traits { +struct std::coroutine_traits { struct promise_type { // FIXME: add an await_transform overload for functions awaitable yield_value(int()); @@ -747,8 +743,7 @@ } template void ok_generic_lambda_coawait_PR41909(); // expected-note {{in instantiation of function template specialization 'ok_generic_lambda_coawait_PR41909' requested here}} -template<> struct std::experimental::coroutine_traits -{ using promise_type = promise; }; +template <> struct std::coroutine_traits { using promise_type = promise; }; int main(int, const char**) { co_await a; // expected-error {{'co_await' cannot be used in the 'main' function}} @@ -761,12 +756,11 @@ void return_void(); void unhandled_exception(); }; -template<> struct std::experimental::coroutine_handle {}; +template <> struct std::coroutine_handle {}; -template<> struct std::experimental::coroutine_traits -{ using promise_type = good_promise_2; }; +template <> struct std::coroutine_traits { using promise_type = good_promise_2; }; -float badly_specialized_coro_handle() { // expected-error {{std::experimental::coroutine_handle missing a member named 'from_address'}} +float badly_specialized_coro_handle() { // expected-error {{std::coroutine_handle must have a member named 'from_address'}} //expected-note@-1 {{call to 'initial_suspend' implicitly required by the initial suspend point}} co_return; //expected-note {{function is a coroutine due to use of 'co_return' here}} } @@ -785,8 +779,8 @@ struct promise_on_alloc_failure_tag {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { int get_return_object() {} suspend_always initial_suspend() { return {}; } @@ -905,8 +899,8 @@ } struct mismatch_gro_type_tag1 {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } @@ -922,8 +916,8 @@ } struct mismatch_gro_type_tag2 {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { void *get_return_object() {} //expected-note {{member 'get_return_object' declared here}} suspend_always initial_suspend() { return {}; } @@ -940,8 +934,8 @@ } struct mismatch_gro_type_tag3 {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { int get_return_object() {} static void get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared here}} @@ -959,8 +953,8 @@ struct mismatch_gro_type_tag4 {}; -template<> -struct std::experimental::coroutine_traits { +template <> +struct std::coroutine_traits { struct promise_type { int get_return_object() {} static char *get_return_object_on_allocation_failure() {} //expected-note {{member 'get_return_object_on_allocation_failure' declared}} @@ -992,13 +986,13 @@ struct bad_await_suspend_return { bool await_ready(); // expected-error@+1 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'char')}} - char await_suspend(std::experimental::coroutine_handle<>); + char await_suspend(std::coroutine_handle<>); void await_resume(); }; struct bad_await_ready_return { // expected-note@+1 {{return type of 'await_ready' is required to be contextually convertible to 'bool'}} void await_ready(); - bool await_suspend(std::experimental::coroutine_handle<>); + bool await_suspend(std::coroutine_handle<>); void await_resume(); }; struct await_ready_explicit_bool { @@ -1006,7 +1000,7 @@ explicit operator bool() const; }; BoolT await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); void await_resume(); }; template @@ -1014,7 +1008,7 @@ bool await_ready(); // expected-error@+2 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'bool &')}} // expected-error@+1 {{return type of 'await_suspend' is required to be 'void' or 'bool' (have 'bool &&')}} - SuspendTy await_suspend(std::experimental::coroutine_handle<>); + SuspendTy await_suspend(std::coroutine_handle<>); // cxx20_2b-warning@-1 {{volatile-qualified return type 'const volatile bool' is deprecated}} void await_resume(); }; @@ -1074,7 +1068,7 @@ template static constexpr bool MatchesArgs = IsSameV>; + std::coroutine_traits>; }; template @@ -1086,7 +1080,7 @@ template struct CoroMemberPromise { - using TraitsT = std::experimental::coroutine_traits; + using TraitsT = std::coroutine_traits; using TypeTestT = TypeTest; using AwaitTestT = AwaitReturnsType; @@ -1103,7 +1097,7 @@ } // namespace CoroHandleMemberFunctionTest template -struct ::std::experimental::coroutine_traits { +struct ::std::coroutine_traits { using promise_type = CoroHandleMemberFunctionTest::CoroMemberPromise; }; @@ -1189,16 +1183,16 @@ } BadCoroMemberTag test_diagnostics() { - // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + // expected-error@-1 {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_return; } BadCoroMemberTag test_diagnostics(int) const && { - // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + // expected-error@-1 {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_return; } static BadCoroMemberTag test_static_diagnostics(long *) { - // expected-error@-1 {{this function cannot be a coroutine: 'std::experimental::coroutine_traits' has no member named 'promise_type'}} + // expected-error@-1 {{this function cannot be a coroutine: 'std::coroutine_traits' has no member named 'promise_type'}} co_return; } }; @@ -1310,7 +1304,7 @@ coro bad_coroutine_calls_deleted_promise_constructor() { - // expected-error@-1 {{call to deleted constructor of 'std::experimental::coroutine_traits>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_deleted_constructor')}} + // expected-error@-1 {{call to deleted constructor of 'std::coroutine_traits>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_deleted_constructor')}} co_return; } @@ -1377,7 +1371,7 @@ coro bad_coroutine_calls_with_no_matching_constructor(int, int) { - // expected-error@-1 {{call to deleted constructor of 'std::experimental::coroutine_traits, int, int>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_no_matching_constructor')}} + // expected-error@-1 {{call to deleted constructor of 'std::coroutine_traits, int, int>::promise_type' (aka 'CoroHandleMemberFunctionTest::bad_promise_no_matching_constructor')}} co_return; } @@ -1393,7 +1387,7 @@ class awaitable_no_unused_warn { public: - using handle_type = std::experimental::coroutine_handle<>; + using handle_type = std::coroutine_handle<>; constexpr bool await_ready() noexcept { return false; } void await_suspend(handle_type) noexcept {} int await_resume() noexcept { return 1; } @@ -1402,7 +1396,7 @@ class awaitable_unused_warn { public: - using handle_type = std::experimental::coroutine_handle<>; + using handle_type = std::coroutine_handle<>; constexpr bool await_ready() noexcept { return false; } void await_suspend(handle_type) noexcept {} [[nodiscard]] int await_resume() noexcept { return 1; } @@ -1432,7 +1426,7 @@ } struct missing_await_ready { - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); void await_resume(); }; struct missing_await_suspend { @@ -1441,7 +1435,7 @@ }; struct missing_await_resume { bool await_ready(); - void await_suspend(std::experimental::coroutine_handle<>); + void await_suspend(std::coroutine_handle<>); }; void test_missing_awaitable_members() { Index: clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp =================================================================== --- clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp +++ clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp @@ -623,7 +623,6 @@ FileContentMappings M; M.push_back(std::make_pair("/coro_header", R"cpp( namespace std { -namespace experimental { template struct void_t_imp { @@ -642,7 +641,7 @@ template struct coroutine_traits : public traits_sfinae_base {}; -}} // namespace std::experimental +} // namespace std struct awaitable { bool await_ready() noexcept; template @@ -658,14 +657,13 @@ void unhandled_exception(); }; template -struct std::experimental::coroutine_traits { using promise_type = promise; }; +struct std::coroutine_traits { using promise_type = promise; }; namespace std { -namespace experimental { template struct coroutine_handle { static coroutine_handle from_address(void *) noexcept; }; -}} // namespace std::experimental +} // namespace std )cpp")); StringRef CoReturnCode = R"cpp( #include