Unverified Commit c601be9c authored by Utkarsh Saxena's avatar Utkarsh Saxena Committed by GitHub
Browse files

[coroutines] Introduce [[clang::coro_return_type]] and [[clang::coro_wrapper]] (#71945)

First step in the implementation of
[RFC](https://discourse.llvm.org/t/rfc-lifetime-bound-check-for-parameters-of-coroutines/74253)
([final approved
doc](https://docs.google.com/document/d/1hkfXHuvIW1Yv5LI-EIkpWzdWgIoUlzO6Zv_KJpknQzM/edit)

).

This introduces the concepts of a **coroutine return type** and explicit
**coroutine wrapper** functions.

---------

Co-authored-by: default avatarChuanqi Xu <yedeng.yd@linux.alibaba.com>
parent 2310066f
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -306,6 +306,11 @@ Attribute Changes in Clang
  to reduce the size of the destroy functions for coroutines which are known to
  be destroyed after having reached the final suspend point.

- Clang now introduced ``[[clang::coro_return_type]]`` and ``[[clang::coro_wrapper]]``
  attributes. A function returning a type marked with ``[[clang::coro_return_type]]``
  should be a coroutine. A non-coroutine function marked with ``[[clang::coro_wrapper]]``
  is still allowed to return the such a type. This is helpful for analyzers to recognize coroutines from the function signatures.

Improvements to Clang's diagnostics
-----------------------------------
- Clang constexpr evaluator now prints template arguments when displaying
+16 −0
Original line number Diff line number Diff line
@@ -1094,6 +1094,22 @@ def CoroOnlyDestroyWhenComplete : InheritableAttr {
  let SimpleHandler = 1;
}

def CoroReturnType : InheritableAttr {
  let Spellings = [Clang<"coro_return_type">];
  let Subjects = SubjectList<[CXXRecord]>;
  let LangOpts = [CPlusPlus];
  let Documentation = [CoroReturnTypeAndWrapperDoc];
  let SimpleHandler = 1;
}

def CoroWrapper : InheritableAttr {
  let Spellings = [Clang<"coro_wrapper">];
  let Subjects = SubjectList<[Function]>;
  let LangOpts = [CPlusPlus];
  let Documentation = [CoroReturnTypeAndWrapperDoc];
  let SimpleHandler = 1;
}

// OSObject-based attributes.
def OSConsumed : InheritableParamAttr {
  let Spellings = [Clang<"os_consumed">];
+58 −0
Original line number Diff line number Diff line
@@ -7482,3 +7482,61 @@ generation of the other destruction cases, optimizing the above `foo.destroy` to

  }];
}


def CoroReturnTypeAndWrapperDoc : Documentation {
  let Category = DocCatDecl;
  let Content = [{
The ``[[clang::coro_return_type]]`` attribute is used to help static analyzers to recognize
coroutines from the function signatures.

The ``coro_return_type`` attribute should be marked on a C++ class to mark it as
a **coroutine return type (CRT)**.

A function ``R func(P1, .., PN)`` has a coroutine return type (CRT) ``R`` if ``R``
is marked by ``[[clang::coro_return_type]]`` and  ``R`` has a promise type associated to it
(i.e., std​::​coroutine_traits<R, P1, .., PN>​::​promise_type is a valid promise type).

If the return type of a function is a ``CRT`` then the function must be a coroutine.
Otherwise the program is invalid. It is allowed for a non-coroutine to return a ``CRT``
if the function is marked with ``[[clang::coro_wrapper]]``.

The ``[[clang::coro_wrapper]]`` attribute should be marked on a C++ function to mark it as
a **coroutine wrapper**. A coroutine wrapper is a function which returns a ``CRT``,
is not a coroutine itself and is marked with ``[[clang::coro_wrapper]]``.

Clang will enforce that all functions that return a ``CRT`` are either coroutines or marked
with ``[[clang::coro_wrapper]]``. Clang will enforce this with an error.

From a language perspective, it is not possible to differentiate between a coroutine and a
function returning a CRT by merely looking at the function signature.

Coroutine wrappers, in particular, are susceptible to capturing
references to temporaries and other lifetime issues. This allows to avoid such lifetime
issues with coroutine wrappers.

For example,

.. code-block:: c++

  // This is a CRT.
  template <typename T> struct [[clang::coro_return_type]] Task {
    using promise_type = some_promise_type;
  };

  Task<int> increment(int a) { co_return a + 1; } // Fine. This is a coroutine.
  Task<int> foo() { return increment(1); } // Error. foo is not a coroutine.

  // Fine for a coroutine wrapper to return a CRT.
  Task<int> [[clang::coro_wrapper]] foo() { return increment(1); }

  void bar() {
    // Invalid. This intantiates a function which returns a CRT but is not marked as
    // a coroutine wrapper.
    std::function<Task<int>(int)> f = increment;
  }

Note: ``a_promise_type::get_return_object`` is exempted from this analysis as it is a necessary
implementation detail of any coroutine library.
}];
}
+4 −0
Original line number Diff line number Diff line
@@ -11591,6 +11591,10 @@ def err_conflicting_aligned_options : Error <
def err_coro_invalid_addr_of_label : Error<
  "the GNU address of label extension is not allowed in coroutines."
>;
def err_coroutine_return_type : Error<
  "function returns a type %0 makred with [[clang::coro_return_type]] but is neither a coroutine nor a coroutine wrapper; "
  "non-coroutines should be marked with [[clang::coro_wrapper]] to allow returning coroutine return type"
>;
} // end of coroutines issue category
let CategoryName = "Documentation Issue" in {
+6 −0
Original line number Diff line number Diff line
@@ -11189,6 +11189,12 @@ public:
  bool buildCoroutineParameterMoves(SourceLocation Loc);
  VarDecl *buildCoroutinePromise(SourceLocation Loc);
  void CheckCompletedCoroutineBody(FunctionDecl *FD, Stmt *&Body);
  // As a clang extension, enforces that a non-coroutine function must be marked
  // with [[clang::coro_wrapper]] if it returns a type marked with
  // [[clang::coro_return_type]].
  // Expects that FD is not a coroutine.
  void CheckCoroutineWrapper(FunctionDecl *FD);
  /// Lookup 'coroutine_traits' in std namespace and std::experimental
  /// namespace. The namespace found is recorded in Namespace.
  ClassTemplateDecl *lookupCoroutineTraits(SourceLocation KwLoc,
Loading