[clang] [Clang] Propagate elide safe context through [[clang::coro_must_await]] (PR #108474)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Sep 13 13:56:15 PDT 2024
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-coroutines
@llvm/pr-subscribers-clang
Author: Yuxuan Chen (yuxuanchen1997)
<details>
<summary>Changes</summary>
Based on discussion in https://discourse.llvm.org/t/language-extension-for-better-more-deterministic-halo-for-c-coroutines/80044/9, we are proposing a new attribute `[[clang::coro_must_await]]`. ``[[clang::coro_must_await]]`` is a function parameter attribute. It works in conjunction with ``[[clang::coro_await_elidable]]`` to propagate a safe elide context to a parameter or parameter pack if the function is called under a safe elide context.
To give a better definition, we introduce this new concept called a "safe elide context".
When a coroutine function returns a type attributed `[[clang::coro_await_elidable]]`, a direct call expression therein that returns a prvalue of a type attributed ``[[clang::coro_await_elidable]]`` is said to be under a safe elide context if one of the following is true:
- it is the immediate right-hand side operand to a co_await expression.
- it is an argument to a `[[clang::coro_must_await]]` parameter or parameter pack of another direct call expression under a safe elide context.
This attribute makes it possible to have code that composes coroutines, and the coroutines are still elidable.
```
template <typename T>
class [[clang::coro_await_elidable]] Task { ... };
template <typename... T>
class [[clang::coro_await_elidable]] WhenAll { ... };
// `when_all` is a utility function that compose coroutines. It does not need
// to be a coroutine to propagate.
template <typename... T>
WhenAll<T...> when_all([[clang::coro_must_await]] Task<T> tasks...);
Task<int> foo();
Task<int> bar();
Task<void> example1() {
// `when_all``, `foo``, and `bar` are all elide safe because `when_all` is
// under a safe elide context and propagated such contexts to foo and bar.
co_await when_all(foo(), bar());
}
```
---
Full diff: https://github.com/llvm/llvm-project/pull/108474.diff
5 Files Affected:
- (modified) clang/docs/ReleaseNotes.rst (+4-1)
- (modified) clang/include/clang/Basic/Attr.td (+8)
- (modified) clang/include/clang/Basic/AttrDocs.td (+69-8)
- (modified) clang/lib/Sema/SemaCoroutine.cpp (+28-12)
- (modified) clang/test/CodeGenCoroutines/coro-await-elidable.cpp (+31)
``````````diff
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index 3929a9fb599259..54b60ed92d8e4c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -249,7 +249,10 @@ Attribute Changes in Clang
(#GH106864)
- Introduced a new attribute ``[[clang::coro_await_elidable]]`` on coroutine return types
- to express elideability at call sites where the coroutine is co_awaited as a prvalue.
+ to express elideability at call sites where the coroutine is invoked under a safe elide context.
+
+- Introduced a new attribute ``[[clang::coro_must_await]]`` on function parameters to
+ propagate safe elide context if such function is also under a safe elide context.
Improvements to Clang's diagnostics
-----------------------------------
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 70fad60d4edbb5..40aa57115a75ff 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1258,6 +1258,14 @@ def CoroAwaitElidable : InheritableAttr {
let SimpleHandler = 1;
}
+def CoroMustAwait : InheritableAttr {
+ let Spellings = [Clang<"coro_must_await">];
+ let Subjects = SubjectList<[ParmVar]>;
+ let LangOpts = [CPlusPlus];
+ let Documentation = [CoroMustAwaitDoc];
+ let SimpleHandler = 1;
+}
+
// OSObject-based attributes.
def OSConsumed : InheritableParamAttr {
let Spellings = [Clang<"os_consumed">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 546e5100b79dd9..e8bdda8a20be70 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8261,12 +8261,19 @@ def CoroAwaitElidableDoc : Documentation {
The ``[[clang::coro_await_elidable]]`` is a class attribute which can be applied
to a coroutine return type.
-When a coroutine function that returns such a type calls another coroutine function,
-the compiler performs heap allocation elision when the call to the coroutine function
-is immediately co_awaited as a prvalue. In this case, the coroutine frame for the
-callee will be a local variable within the enclosing braces in the caller's stack
-frame. And the local variable, like other variables in coroutines, may be collected
-into the coroutine frame, which may be allocated on the heap.
+When a coroutine function returns such a type, a direct call expression therein
+that returns a prvalue of a type attributed ``[[clang::coro_await_elidable]]``
+is said to be under a safe elide context if one of the following is true:
+- it is the immediate right-hand side operand to a co_await expression.
+- it is an argument to a [[clang::coro_must_await]] parameter or parameter pack
+of another direct call expression under a safe elide context.
+
+Do note that the safe elide context applies only to the call expression itself,
+and the context does not transitively include any of its subexpressions unless
+exceptional rules of ``[[clang::coro_must_await]]`` apply.
+
+The compiler performs heap allocation elision on call expressions under a safe
+elide context, if the callee is a coroutine.
Example:
@@ -8281,8 +8288,62 @@ Example:
co_await t;
}
-The behavior is undefined if the caller coroutine is destroyed earlier than the
-callee coroutine.
+Such elision replaces the heap allocated activation frame of the callee coroutine
+with a local variable within the enclosing braces in the caller's stack frame.
+The local variable, like other variables in coroutines, may be collected into the
+coroutine frame, which may be allocated on the heap. The behavior is undefined
+if the caller coroutine is destroyed earlier than the callee coroutine.
+
+}];
+}
+
+def CoroMustAwaitDoc : Documentation {
+ let Category = DocCatDecl;
+ let Content = [{
+
+The ``[[clang::coro_must_await]]`` is a function parameter attribute. It works
+in conjunction with ``[[clang::coro_await_elidable]]`` to propagate a safe elide
+context to a parameter or parameter pack if the function is called under a safe
+elide context.
+
+This is sometimes necessary on utility functions whose role is to compose or
+modify the behavior of a callee coroutine.
+
+Example:
+
+.. code-block:: c++
+
+ template <typename T>
+ class [[clang::coro_await_elidable]] Task { ... };
+
+ template <typename... T>
+ class [[clang::coro_await_elidable]] WhenAll { ... };
+
+ // `when_all` is a utility function that compose coroutines. It does not need
+ // to be a coroutine to propagate.
+ template <typename... T>
+ WhenAll<T...> when_all([[clang::coro_must_await]] Task<T> tasks...);
+
+ Task<int> foo();
+ Task<int> bar();
+ Task<void> example1() {
+ // `when_all``, `foo``, and `bar` are all elide safe because `when_all` is
+ // under a safe elide context and propagated such contexts to foo and bar.
+ co_await when_all(foo(), bar());
+ }
+
+ Task<void> example2() {
+ // `when_all` and `bar` are elide safe. `foo` is not elide safe.
+ auto f = foo();
+ co_await when_all(f, bar());
+ }
+
+
+ Task<void> example3() {
+ // None of the calls are elide safe.
+ auto t = when_all(foo(), bar());
+ co_await t;
+ }
}];
}
diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp
index a574d56646f3a2..d0eaf469e327a2 100644
--- a/clang/lib/Sema/SemaCoroutine.cpp
+++ b/clang/lib/Sema/SemaCoroutine.cpp
@@ -849,12 +849,30 @@ static bool isAttributedCoroAwaitElidable(const QualType &QT) {
return Record && Record->hasAttr<CoroAwaitElidableAttr>();
}
-static bool isCoroAwaitElidableCall(Expr *Operand) {
- if (!Operand->isPRValue()) {
- return false;
+static void applySafeElideContext(Expr *Operand) {
+ auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit());
+ if (!Call || !Call->isPRValue())
+ return;
+
+ if (!isAttributedCoroAwaitElidable(Call->getType()))
+ return;
+
+ Call->setCoroElideSafe();
+
+ // Check parameter
+ auto *Fn = llvm::dyn_cast_if_present<FunctionDecl>(Call->getCalleeDecl());
+ if (!Fn) {
+ Call->dump();
+ return;
}
- return isAttributedCoroAwaitElidable(Operand->getType());
+ size_t ParmIdx = 0;
+ for (ParmVarDecl *PD : Fn->parameters()) {
+ if (PD->hasAttr<CoroMustAwaitAttr>())
+ applySafeElideContext(Call->getArg(ParmIdx));
+
+ ParmIdx++;
+ }
}
// Attempts to resolve and build a CoawaitExpr from "raw" inputs, bailing out to
@@ -880,14 +898,12 @@ ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
}
auto *RD = Promise->getType()->getAsCXXRecordDecl();
- bool AwaitElidable =
- isCoroAwaitElidableCall(Operand) &&
- isAttributedCoroAwaitElidable(
- getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType());
-
- if (AwaitElidable)
- if (auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit()))
- Call->setCoroElideSafe();
+
+ bool CurFnAwaitElidable = isAttributedCoroAwaitElidable(
+ getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType());
+
+ if (CurFnAwaitElidable)
+ applySafeElideContext(Operand);
Expr *Transformed = Operand;
if (lookupMember(*this, "await_transform", RD, Loc)) {
diff --git a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
index 8512995dfad45a..fe7631ba2d9189 100644
--- a/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
+++ b/clang/test/CodeGenCoroutines/coro-await-elidable.cpp
@@ -84,4 +84,35 @@ Task<int> nonelidable() {
co_return 1;
}
+// CHECK-LABEL: define{{.*}} @_Z8addTasksO4TaskIiES1_{{.*}} {
+Task<int> addTasks([[clang::coro_must_await]] Task<int> &&t1, Task<int> &&t2) {
+ int i1 = co_await t1;
+ int i2 = co_await t2;
+ co_return i1 + i2;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z10returnSamei{{.*}} {
+Task<int> returnSame(int i) {
+ co_return i;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z21elidableWithMustAwaitv{{.*}} {
+Task<int> elidableWithMustAwait() {
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]]
+ // CHECK-NOT: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]]
+ co_return co_await addTasks(returnSame(2), returnSame(3));
+}
+
+template <typename... Args>
+Task<int> sumAll([[clang::coro_must_await]] Args && ... tasks);
+
+// CHECK-LABEL: define{{.*}} @_Z16elidableWithPackv{{.*}} {
+Task<int> elidableWithPack() {
+ // CHECK-NOT: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1) #[[ELIDE_SAFE]]
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]]
+ // CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]]
+ auto t = returnSame(1);
+ co_return co_await sumAll(t, returnSame(2), returnSame(3));
+}
+
// CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe }
``````````
</details>
https://github.com/llvm/llvm-project/pull/108474
More information about the cfe-commits
mailing list