Skip to content

Commit 73e3d27

Browse files
[Clang] Propagate elide safe context through [[clang::coro_must_await]]
1 parent 2a9208b commit 73e3d27

File tree

5 files changed

+140
-21
lines changed

5 files changed

+140
-21
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ Attribute Changes in Clang
247247
(#GH106864)
248248

249249
- Introduced a new attribute ``[[clang::coro_await_elidable]]`` on coroutine return types
250-
to express elideability at call sites where the coroutine is co_awaited as a prvalue.
250+
to express elideability at call sites where the coroutine is invoked under a safe elide context.
251+
252+
- Introduced a new attribute ``[[clang::coro_must_await]]`` on function parameters to
253+
propagate safe elide context if such function is also under a safe elide context.
251254

252255
Improvements to Clang's diagnostics
253256
-----------------------------------

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,14 @@ def CoroAwaitElidable : InheritableAttr {
12581258
let SimpleHandler = 1;
12591259
}
12601260

1261+
def CoroMustAwait : InheritableAttr {
1262+
let Spellings = [Clang<"coro_must_await">];
1263+
let Subjects = SubjectList<[ParmVar]>;
1264+
let LangOpts = [CPlusPlus];
1265+
let Documentation = [CoroMustAwaitDoc];
1266+
let SimpleHandler = 1;
1267+
}
1268+
12611269
// OSObject-based attributes.
12621270
def OSConsumed : InheritableParamAttr {
12631271
let Spellings = [Clang<"os_consumed">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8275,12 +8275,19 @@ def CoroAwaitElidableDoc : Documentation {
82758275
The ``[[clang::coro_await_elidable]]`` is a class attribute which can be applied
82768276
to a coroutine return type.
82778277

8278-
When a coroutine function that returns such a type calls another coroutine function,
8279-
the compiler performs heap allocation elision when the call to the coroutine function
8280-
is immediately co_awaited as a prvalue. In this case, the coroutine frame for the
8281-
callee will be a local variable within the enclosing braces in the caller's stack
8282-
frame. And the local variable, like other variables in coroutines, may be collected
8283-
into the coroutine frame, which may be allocated on the heap.
8278+
When a coroutine function return such a type, a direct call expression therein
8279+
that returns a prvalue of a type attributed ``[[clang::coro_await_elidable]]``
8280+
is said to be under a safe elide context if one of the following is true:
8281+
- it is the immediate right-hand side operand to a co_await expression.
8282+
- it is an argument to a [[clang::coro_must_await]] parameter or parameter
8283+
pack of another direct call expression under a safe elide context.
8284+
8285+
Do note that the safe elide context applies only to the call expression itself,
8286+
and the context does not transitively include any of its subexpressions unless
8287+
exceptional rules of ``[[clang::coro_must_await]]`` apply.
8288+
8289+
The compiler performs heap allocation elision on call expressions under a safe
8290+
elide context, if the callee is a coroutine.
82848291

82858292
Example:
82868293

@@ -8295,8 +8302,62 @@ Example:
82958302
co_await t;
82968303
}
82978304

8298-
The behavior is undefined if the caller coroutine is destroyed earlier than the
8299-
callee coroutine.
8305+
Such elision replaces the heap allocated activation frame of the callee coroutine
8306+
with a local variable within the enclosing braces in the caller's stack frame.
8307+
The local variable, like other variables in coroutines, may be collected into the
8308+
coroutine frame, which may be allocated on the heap. The behavior is undefined
8309+
if the caller coroutine is destroyed earlier than the callee coroutine.
8310+
8311+
}];
8312+
}
8313+
8314+
def CoroMustAwaitDoc : Documentation {
8315+
let Category = DocCatDecl;
8316+
let Content = [{
8317+
8318+
The ``[[clang::coro_must_await]]`` is a function parameter attribute. It works
8319+
in conjunction with ``[[clang::coro_await_elidable]]`` to propagate a safe elide
8320+
context to a parameter or parameter pack if the function is called under a safe
8321+
elide context.
8322+
8323+
This is sometimes necessary on utility functions whose role is to compose or
8324+
modify the behavior of a callee coroutine.
8325+
8326+
Example:
8327+
8328+
.. code-block:: c++
8329+
8330+
template <typename T>
8331+
class [[clang::coro_await_elidable]] Task { ... };
8332+
8333+
template <typename... T>
8334+
class [[clang::coro_await_elidable]] WhenAll { ... };
8335+
8336+
// `when_all` is a utility function that compose coroutines. It does not need
8337+
// to be a coroutine to propagate.
8338+
template <typename... T>
8339+
WhenAll<T...> when_all([[clang::coro_must_await]] Task<T> tasks...);
8340+
8341+
Task<int> foo();
8342+
Task<int> bar();
8343+
Task<void> example1() {
8344+
// `when_all``, `foo``, and `bar` are all elide safe because `when_all` is
8345+
// under a safe elide context and propagated such contexts to foo and bar.
8346+
co_await when_all(foo(), bar());
8347+
}
8348+
8349+
Task<void> example2() {
8350+
// `when_all` and `bar` are elide safe. `foo` is not elide safe.
8351+
auto f = foo();
8352+
co_await when_all(f, bar());
8353+
}
8354+
8355+
8356+
Task<void> example3() {
8357+
// None of the calls are elide safe.
8358+
auto t = when_all(foo(), bar());
8359+
co_await t;
8360+
}
83008361

83018362
}];
83028363
}

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -849,12 +849,30 @@ static bool isAttributedCoroAwaitElidable(const QualType &QT) {
849849
return Record && Record->hasAttr<CoroAwaitElidableAttr>();
850850
}
851851

852-
static bool isCoroAwaitElidableCall(Expr *Operand) {
853-
if (!Operand->isPRValue()) {
854-
return false;
852+
static void applySafeElideContext(Expr *Operand) {
853+
auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit());
854+
if (!Call || !Call->isPRValue())
855+
return;
856+
857+
if (!isAttributedCoroAwaitElidable(Call->getType()))
858+
return;
859+
860+
Call->setCoroElideSafe();
861+
862+
// Check parameter
863+
auto *Fn = llvm::dyn_cast_if_present<FunctionDecl>(Call->getCalleeDecl());
864+
if (!Fn) {
865+
Call->dump();
866+
return;
855867
}
856868

857-
return isAttributedCoroAwaitElidable(Operand->getType());
869+
size_t ParmIdx = 0;
870+
for (ParmVarDecl *PD : Fn->parameters()) {
871+
if (PD->hasAttr<CoroMustAwaitAttr>())
872+
applySafeElideContext(Call->getArg(ParmIdx));
873+
874+
ParmIdx++;
875+
}
858876
}
859877

860878
// Attempts to resolve and build a CoawaitExpr from "raw" inputs, bailing out to
@@ -880,14 +898,12 @@ ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
880898
}
881899

882900
auto *RD = Promise->getType()->getAsCXXRecordDecl();
883-
bool AwaitElidable =
884-
isCoroAwaitElidableCall(Operand) &&
885-
isAttributedCoroAwaitElidable(
886-
getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType());
887-
888-
if (AwaitElidable)
889-
if (auto *Call = dyn_cast<CallExpr>(Operand->IgnoreImplicit()))
890-
Call->setCoroElideSafe();
901+
902+
bool CurFnAwaitElidable = isAttributedCoroAwaitElidable(
903+
getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType());
904+
905+
if (CurFnAwaitElidable)
906+
applySafeElideContext(Operand);
891907

892908
Expr *Transformed = Operand;
893909
if (lookupMember(*this, "await_transform", RD, Loc)) {

clang/test/CodeGenCoroutines/coro-await-elidable.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,35 @@ Task<int> nonelidable() {
8484
co_return 1;
8585
}
8686

87+
// CHECK-LABEL: define{{.*}} @_Z8addTasksO4TaskIiES1_{{.*}} {
88+
Task<int> addTasks([[clang::coro_must_await]] Task<int> &&t1, Task<int> &&t2) {
89+
int i1 = co_await t1;
90+
int i2 = co_await t2;
91+
co_return i1 + i2;
92+
}
93+
94+
// CHECK-LABEL: define{{.*}} @_Z10returnSamei{{.*}} {
95+
Task<int> returnSame(int i) {
96+
co_return i;
97+
}
98+
99+
// CHECK-LABEL: define{{.*}} @_Z21elidableWithMustAwaitv{{.*}} {
100+
Task<int> elidableWithMustAwait() {
101+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]]
102+
// CHECK-NOT: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]]
103+
co_return co_await addTasks(returnSame(2), returnSame(3));
104+
}
105+
106+
template <typename... Args>
107+
Task<int> sumAll([[clang::coro_must_await]] Args && ... tasks);
108+
109+
// CHECK-LABEL: define{{.*}} @_Z16elidableWithPackv{{.*}} {
110+
Task<int> elidableWithPack() {
111+
// CHECK-NOT: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1) #[[ELIDE_SAFE]]
112+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2) #[[ELIDE_SAFE]]
113+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]]
114+
auto t = returnSame(1);
115+
co_return co_await sumAll(t, returnSame(2), returnSame(3));
116+
}
117+
87118
// CHECK: attributes #[[ELIDE_SAFE]] = { coro_elide_safe }

0 commit comments

Comments
 (0)