Skip to content

Commit 3b5c9cc

Browse files
[Clang] Propagate elide safe context through [[clang::coro_must_await]]
1 parent 90f077c commit 3b5c9cc

File tree

6 files changed

+141
-21
lines changed

6 files changed

+141
-21
lines changed

clang/docs/ReleaseNotes.rst

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

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

254257
Improvements to Clang's diagnostics
255258
-----------------------------------

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
@@ -8261,12 +8261,19 @@ def CoroAwaitElidableDoc : Documentation {
82618261
The ``[[clang::coro_await_elidable]]`` is a class attribute which can be applied
82628262
to a coroutine return type.
82638263

8264-
When a coroutine function that returns such a type calls another coroutine function,
8265-
the compiler performs heap allocation elision when the call to the coroutine function
8266-
is immediately co_awaited as a prvalue. In this case, the coroutine frame for the
8267-
callee will be a local variable within the enclosing braces in the caller's stack
8268-
frame. And the local variable, like other variables in coroutines, may be collected
8269-
into the coroutine frame, which may be allocated on the heap.
8264+
When a coroutine function returns such a type, a direct call expression therein
8265+
that returns a prvalue of a type attributed ``[[clang::coro_await_elidable]]``
8266+
is said to be under a safe elide context if one of the following is true:
8267+
- it is the immediate right-hand side operand to a co_await expression.
8268+
- it is an argument to a [[clang::coro_must_await]] parameter or parameter pack
8269+
of another direct call expression under a safe elide context.
8270+
8271+
Do note that the safe elide context applies only to the call expression itself,
8272+
and the context does not transitively include any of its subexpressions unless
8273+
exceptional rules of ``[[clang::coro_must_await]]`` apply.
8274+
8275+
The compiler performs heap allocation elision on call expressions under a safe
8276+
elide context, if the callee is a coroutine.
82708277

82718278
Example:
82728279

@@ -8281,8 +8288,62 @@ Example:
82818288
co_await t;
82828289
}
82838290

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

82878348
}];
82888349
}

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 }

clang/test/Misc/pragma-attribute-supported-attributes-list.test

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
// CHECK-NEXT: CoroAwaitElidable (SubjectMatchRule_record)
6363
// CHECK-NEXT: CoroDisableLifetimeBound (SubjectMatchRule_function)
6464
// CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record)
65+
// CHECK-NEXT: CoroMustAwait (SubjectMatchRule_variable_is_parameter)
6566
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
6667
// CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)
6768
// CHECK-NEXT: CoroWrapper (SubjectMatchRule_function)

0 commit comments

Comments
 (0)