Skip to content

Commit e1b40dc

Browse files
[Clang] Propagate elide safe context through [[clang::coro_await_elidable_argument]] (#108474)
1 parent 125635e commit e1b40dc

File tree

6 files changed

+153
-24
lines changed

6 files changed

+153
-24
lines changed

clang/docs/ReleaseNotes.rst

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

254254
- Introduced a new attribute ``[[clang::coro_await_elidable]]`` on coroutine return types
255-
to express elideability at call sites where the coroutine is co_awaited as a prvalue.
255+
to express elideability at call sites where the coroutine is invoked under a safe elide context.
256+
257+
- Introduced a new attribute ``[[clang::coro_await_elidable_argument]]`` on function parameters
258+
to propagate safe elide context to arguments if such function is also under a safe elide context.
256259

257260
Improvements to Clang's diagnostics
258261
-----------------------------------

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 CoroAwaitElidableArgument : InheritableAttr {
1262+
let Spellings = [Clang<"coro_await_elidable_argument">];
1263+
let Subjects = SubjectList<[ParmVar]>;
1264+
let LangOpts = [CPlusPlus];
1265+
let Documentation = [CoroAwaitElidableArgumentDoc];
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: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8258,15 +8258,23 @@ but do not pass them to the underlying coroutine or pass them by value.
82588258
def CoroAwaitElidableDoc : Documentation {
82598259
let Category = DocCatDecl;
82608260
let Content = [{
8261-
The ``[[clang::coro_await_elidable]]`` is a class attribute which can be applied
8262-
to a coroutine return type.
8261+
The ``[[clang::coro_await_elidable]]`` is a class attribute which can be
8262+
applied to a coroutine return type. It provides a hint to the compiler to apply
8263+
Heap Allocation Elision more aggressively.
82638264

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.
8265+
When a coroutine function returns such a type, a direct call expression therein
8266+
that returns a prvalue of a type attributed ``[[clang::coro_await_elidable]]``
8267+
is said to be under a safe elide context if one of the following is true:
8268+
- it is the immediate right-hand side operand to a co_await expression.
8269+
- it is an argument to a ``[[clang::coro_await_elidable_argument]]`` parameter
8270+
or parameter pack of another direct call expression under a safe elide context.
8271+
8272+
Do note that the safe elide context applies only to the call expression itself,
8273+
and the context does not transitively include any of its subexpressions unless
8274+
exceptional rules of ``[[clang::coro_await_elidable_argument]]`` apply.
8275+
8276+
The compiler performs heap allocation elision on call expressions under a safe
8277+
elide context, if the callee is a coroutine.
82708278

82718279
Example:
82728280

@@ -8281,8 +8289,63 @@ Example:
82818289
co_await t;
82828290
}
82838291

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

82878350
}];
82888351
}

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -849,12 +849,28 @@ 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;
855-
}
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();
856861

857-
return isAttributedCoroAwaitElidable(Operand->getType());
862+
// Check parameter
863+
auto *Fn = llvm::dyn_cast_if_present<FunctionDecl>(Call->getCalleeDecl());
864+
if (!Fn)
865+
return;
866+
867+
size_t ParmIdx = 0;
868+
for (ParmVarDecl *PD : Fn->parameters()) {
869+
if (PD->hasAttr<CoroAwaitElidableArgumentAttr>())
870+
applySafeElideContext(Call->getArg(ParmIdx));
871+
872+
ParmIdx++;
873+
}
858874
}
859875

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

882898
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();
899+
900+
bool CurFnAwaitElidable = isAttributedCoroAwaitElidable(
901+
getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType());
902+
903+
if (CurFnAwaitElidable)
904+
applySafeElideContext(Operand);
891905

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

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

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

87+
// CHECK-LABEL: define{{.*}} @_Z8addTasksO4TaskIiES1_{{.*}} {
88+
Task<int> addTasks([[clang::coro_await_elidable_argument]] 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: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3){{$}}
103+
co_return co_await addTasks(returnSame(2), returnSame(3));
104+
}
105+
106+
template <typename... Args>
107+
Task<int> sumAll([[clang::coro_await_elidable_argument]] Args && ... tasks);
108+
109+
// CHECK-LABEL: define{{.*}} @_Z16elidableWithPackv{{.*}} {
110+
Task<int> elidableWithPack() {
111+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1){{$}}
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+
118+
119+
// CHECK-LABEL: define{{.*}} @_Z25elidableWithPackRecursivev{{.*}} {
120+
Task<int> elidableWithPackRecursive() {
121+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 1) #[[ELIDE_SAFE]]
122+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 2){{$}}
123+
// CHECK: call void @_Z10returnSamei(ptr {{.*}}, i32 noundef 3) #[[ELIDE_SAFE]]
124+
co_return co_await sumAll(addTasks(returnSame(1), returnSame(2)), returnSame(3));
125+
}
126+
87127
// 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
@@ -60,6 +60,7 @@
6060
// CHECK-NEXT: ConsumableSetOnRead (SubjectMatchRule_record)
6161
// CHECK-NEXT: Convergent (SubjectMatchRule_function)
6262
// CHECK-NEXT: CoroAwaitElidable (SubjectMatchRule_record)
63+
// CHECK-NEXT: CoroAwaitElidableArgument (SubjectMatchRule_variable_is_parameter)
6364
// CHECK-NEXT: CoroDisableLifetimeBound (SubjectMatchRule_function)
6465
// CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record)
6566
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)

0 commit comments

Comments
 (0)