Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -193,6 +193,9 @@ used ``201904L`` (the date the proposal was seen by the committee) by mistake. There were no other changes to the attribute behavior. +- Introduced a new attribute ``[[clang::coro_always_done]]`` to allow programmer + provide runtime information to compilers. See the attribute document for details. + Windows Support --------------- - For the MinGW driver, added the options ``-mguard=none``, ``-mguard=cf`` and Index: clang/include/clang/Basic/Attr.td =================================================================== --- clang/include/clang/Basic/Attr.td +++ clang/include/clang/Basic/Attr.td @@ -4093,3 +4093,9 @@ let Subjects = SubjectList<[Function]>; let Documentation = [FunctionReturnThunksDocs]; } + +def CoroAlwaysDone : InheritableAttr { + let Spellings = [Clang<"coro_always_done", /*AllowInC*/0>]; + let Subjects = SubjectList<[CXXRecord, Function], ErrorDiag>; + let Documentation = [CoroAlwaysDoneDocs]; +} Index: clang/include/clang/Basic/AttrDocs.td =================================================================== --- clang/include/clang/Basic/AttrDocs.td +++ clang/include/clang/Basic/AttrDocs.td @@ -6688,3 +6688,72 @@ As such, this function attribute is currently only supported on X86 targets. }]; } + +def CoroAlwaysDoneDocs : Documentation { + let Category = DocCatType; + let Content = [{ +The attribute ``coro_always_done`` can be applied to a class definition or +a function definiton. +We call the coroutines whose return type is ``coro_always_done`` class as +``coro_always_done`` coroutine too. +The ``coro_always_done`` coroutine are guaranteed to complete. +This is pretty helpful for compiler optimizations. +For example, + +.. code-block:: c++ + + class task {...}; + task coro() { + X x; + co_await std::suspend_always{}; + co_return; + } + +Then the generated destroy function looks like: + +.. code-block:: c++ + + task coro.destroy() { + if (suspended at the second line) { + x.~X(); + mark coroutine as done; + } + if (coroutine is done) { + delete ... + } + } + +The destroy function will call the destructors in case the coroutine are destroyed +before completed. However, in many cases, the library writers know their coroutines +are not going to be destroyed before completed. In these cases, +``coro_always_done`` is helpful. It'll tell the compiler the information that +the coroutine would be destroyed after it gets completed. +So the compiler can optimize the codes further. For the above example, + +.. code-block:: c++ + + class [[clang::coro_always_done]] task {...}; + task coro() { + X x; + co_await std::suspend_always{}; + co_return; + } + +Now the generated destroy function will look like: + +.. code-block:: c++ + + task coro.destroy() { + delete ... + } + +Note that it is an undefined behavior if a ``coro_always_done`` coroutine +doesn't complete. For example, a coroutine gets destroyed before it is +done or a suspended coroutine never gets resumed. + +Also note that if the coroutine may exit via an exception from +`promise.unhandled_­exception()`, it is an undefined behavior if we mark the coroutine +as ``coro_always_done``. Although the language standard says the coroutine +is considered to suspended at the final suspend point. + }]; +} Index: clang/lib/CodeGen/CGCoroutine.cpp =================================================================== --- clang/lib/CodeGen/CGCoroutine.cpp +++ clang/lib/CodeGen/CGCoroutine.cpp @@ -654,6 +654,14 @@ EmitStmt(Ret); } + if (auto *RD = FnRetTy->getAsCXXRecordDecl()) { + if (RD->hasAttr()) + CurFn->setAlwaysPresplitCoroutine(); + } else if (auto *FD = dyn_cast(CurFuncDecl)) { + if (FD->hasAttr()) + CurFn->setAlwaysPresplitCoroutine(); + } + // LLVM require the frontend to mark the coroutine. CurFn->setPresplitCoroutine(); } Index: clang/lib/Sema/SemaDeclAttr.cpp =================================================================== --- clang/lib/Sema/SemaDeclAttr.cpp +++ clang/lib/Sema/SemaDeclAttr.cpp @@ -9109,6 +9109,11 @@ case ParsedAttr::AT_UsingIfExists: handleSimpleAttribute(S, D, AL); break; + + // Coroutine attributes. + case ParsedAttr::AT_CoroAlwaysDone: + handleSimpleAttribute(S, D, AL); + break; } } Index: clang/test/CodeGenCoroutines/coro-coro_always_done.cpp =================================================================== --- /dev/null +++ clang/test/CodeGenCoroutines/coro-coro_always_done.cpp @@ -0,0 +1,68 @@ +// Verify the always_complete_coroutine will be emitted correctly. +// RUN: %clang_cc1 -std=c++20 -triple=x86_64-unknown-linux-gnu -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s + +namespace std { +template struct coroutine_traits { + using promise_type = typename R::promise_type; +}; + +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; +}; + +struct suspend_always { + bool await_ready() noexcept { return false; } + void await_suspend(std::coroutine_handle<>) noexcept {} + void await_resume() noexcept {} +}; +} // namespace std + +template <> struct std::coroutine_traits { + struct promise_type { + void get_return_object() noexcept {} + std::suspend_always initial_suspend() noexcept { return std::suspend_always{}; } + std::suspend_always final_suspend() noexcept { return std::suspend_always{}; } + void return_void() noexcept {} + promise_type() {} + ~promise_type() {} + void unhandled_exception() noexcept {} + }; +}; + +struct Cleanup { ~Cleanup(); }; + +[[clang::coro_always_done]] +void f() { + Cleanup cleanup; + co_await std::suspend_always{}; + co_return; +} + +// CHECK: define{{.*}}@_Z1fv{{.*}} #[[F_ATTR_NUM:[0-9]+]] + +struct [[clang::coro_always_done]] task { + struct promise_type { + auto initial_suspend() { return std::suspend_always{}; } + auto final_suspend() noexcept { return std::suspend_always{}; } + auto get_return_object() { return task{}; } + void unhandled_exception() {} + void return_value(int) {} + }; +}; + +task f1() { + Cleanup cleanup; + co_await std::suspend_always{}; + co_return 43; +} + +// CHECK: define{{.*}}@_Z2f1v{{.*}} #[[F_ATTR_NUM]] + +// CHECK: attributes #[[F_ATTR_NUM]] = {{.*}}always_complete_coroutine Index: clang/test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -55,6 +55,7 @@ // CHECK-NEXT: ConsumableAutoCast (SubjectMatchRule_record) // CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record) // CHECK-NEXT: Convergent (SubjectMatchRule_function) +// CHECK-NEXT: CoroAlwaysDone (SubjectMatchRule_record, SubjectMatchRule_function) // CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface) // CHECK-NEXT: Destructor (SubjectMatchRule_function)