Skip to content

Commit 093cd09

Browse files
[Clang] Introduce [[clang::structured_concurrency]]
1 parent 5b04b6f commit 093cd09

File tree

15 files changed

+288
-8
lines changed

15 files changed

+288
-8
lines changed

clang/include/clang/AST/ExprCXX.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5213,6 +5213,11 @@ class CoawaitExpr : public CoroutineSuspendExpr {
52135213
bool isImplicit() const { return CoawaitBits.IsImplicit; }
52145214
void setIsImplicit(bool value = true) { CoawaitBits.IsImplicit = value; }
52155215

5216+
bool isInplaceCall() const { return CoawaitBits.IsInplaceCall; }
5217+
void setIsInplaceCall(bool value = true) {
5218+
CoawaitBits.IsInplaceCall = value;
5219+
}
5220+
52165221
static bool classof(const Stmt *T) {
52175222
return T->getStmtClass() == CoawaitExprClass;
52185223
}

clang/include/clang/AST/Stmt.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1163,6 +1163,7 @@ class alignas(void *) Stmt {
11631163

11641164
LLVM_PREFERRED_TYPE(bool)
11651165
unsigned IsImplicit : 1;
1166+
unsigned IsInplaceCall : 1;
11661167
};
11671168

11681169
//===--- Obj-C Expression bitfields classes ---===//

clang/include/clang/Basic/Attr.td

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1212,6 +1212,14 @@ def CoroDisableLifetimeBound : InheritableAttr {
12121212
let SimpleHandler = 1;
12131213
}
12141214

1215+
def CoroInplaceTask : InheritableAttr {
1216+
let Spellings = [Clang<"coro_inplace_task">];
1217+
let Subjects = SubjectList<[CXXRecord]>;
1218+
let LangOpts = [CPlusPlus];
1219+
let Documentation = [CoroInplaceTaskDoc];
1220+
let SimpleHandler = 1;
1221+
}
1222+
12151223
// OSObject-based attributes.
12161224
def OSConsumed : InheritableParamAttr {
12171225
let Spellings = [Clang<"os_consumed">];

clang/include/clang/Basic/AttrDocs.td

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8015,6 +8015,26 @@ but do not pass them to the underlying coroutine or pass them by value.
80158015
}];
80168016
}
80178017

8018+
def CoroInplaceTaskDoc : Documentation {
8019+
let Category = DocCatDecl;
8020+
let Content = [{
8021+
The ``[[clang::coro_inplace_task]]`` is a class attribute which can be applied
8022+
to a coroutine return type.
8023+
8024+
When a coroutine function that returns such a type calls another coroutine function,
8025+
the compiler performs heap allocation elision when the following conditions are all met:
8026+
- callee coroutine function returns a type that is annotated with
8027+
``[[clang::coro_inplace_task]]``.
8028+
- The callee coroutine function is inlined.
8029+
- In caller coroutine, the return value of the callee is a prvalue or an xvalue, and
8030+
- The temporary expression containing the callee coroutine object is immediately co_awaited.
8031+
8032+
The behavior is undefined if any of the following condition was met:
8033+
- the caller coroutine is destroyed earlier than the callee coroutine.
8034+
8035+
}];
8036+
}
8037+
80188038
def CountedByDocs : Documentation {
80198039
let Category = DocCatField;
80208040
let Content = [{

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,11 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
618618
}
619619
}
620620

621-
return MakeAddrLValue(Object, M->getType(), AlignmentSource::Decl);
621+
auto Ret = MakeAddrLValue(Object, M->getType(), AlignmentSource::Decl);
622+
if (TemporaryValues.contains(M)) {
623+
TemporaryValues[M] = Ret.getPointer(*this);
624+
}
625+
return Ret;
622626
}
623627

624628
RValue

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,9 @@ class CodeGenFunction : public CodeGenTypeCache {
369369
};
370370
CGCoroInfo CurCoro;
371371

372+
llvm::SmallDenseMap<const MaterializeTemporaryExpr *, llvm::Value *>
373+
TemporaryValues;
374+
372375
bool isCoroutine() const {
373376
return CurCoro.Data != nullptr;
374377
}

clang/lib/Sema/SemaCoroutine.cpp

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,19 @@ ExprResult Sema::BuildOperatorCoawaitLookupExpr(Scope *S, SourceLocation Loc) {
825825
return CoawaitOp;
826826
}
827827

828+
static bool isAttributedCoroInplaceTask(const QualType &QT) {
829+
auto *Record = QT->getAsCXXRecordDecl();
830+
return Record && Record->hasAttr<CoroInplaceTaskAttr>();
831+
}
832+
833+
static bool isCoroInplaceCall(Expr *Operand) {
834+
if (!Operand->isPRValue()) {
835+
return false;
836+
}
837+
838+
return isAttributedCoroInplaceTask(Operand->getType());
839+
}
840+
828841
// Attempts to resolve and build a CoawaitExpr from "raw" inputs, bailing out to
829842
// DependentCoawaitExpr if needed.
830843
ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
@@ -864,7 +877,14 @@ ExprResult Sema::BuildUnresolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
864877
if (Awaiter.isInvalid())
865878
return ExprError();
866879

867-
return BuildResolvedCoawaitExpr(Loc, Operand, Awaiter.get());
880+
auto Res = BuildResolvedCoawaitExpr(Loc, Operand, Awaiter.get());
881+
if (!Res.isInvalid() && isCoroInplaceCall(Operand) &&
882+
isAttributedCoroInplaceTask(
883+
getCurFunctionDecl(/*AllowLambda=*/true)->getReturnType())) {
884+
// BuildResolvedCoawaitExpr must return a CoawaitExpr, if valid.
885+
Res.getAs<CoawaitExpr>()->setIsInplaceCall();
886+
}
887+
return Res;
868888
}
869889

870890
ExprResult Sema::BuildResolvedCoawaitExpr(SourceLocation Loc, Expr *Operand,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// This is a mock file for <utility>
2+
3+
namespace std {
4+
5+
template <typename T> struct remove_reference { using type = T; };
6+
template <typename T> struct remove_reference<T &> { using type = T; };
7+
template <typename T> struct remove_reference<T &&> { using type = T; };
8+
9+
template <typename T>
10+
constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
11+
return static_cast<typename std::remove_reference<T>::type &&>(t);
12+
}
13+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// This file tests the coro_structured_concurrency attribute semantics.
2+
// RUN: %clang_cc1 -std=c++20 -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
3+
4+
#include "Inputs/coroutine.h"
5+
#include "Inputs/utility.h"
6+
7+
template <typename T>
8+
struct [[clang::coro_inplace_task]] Task {
9+
struct promise_type {
10+
struct FinalAwaiter {
11+
bool await_ready() const noexcept { return false; }
12+
13+
template <typename P>
14+
std::coroutine_handle<> await_suspend(std::coroutine_handle<P> coro) noexcept {
15+
if (!coro)
16+
return std::noop_coroutine();
17+
return coro.promise().continuation;
18+
}
19+
void await_resume() noexcept {}
20+
};
21+
22+
Task get_return_object() noexcept {
23+
return std::coroutine_handle<promise_type>::from_promise(*this);
24+
}
25+
26+
std::suspend_always initial_suspend() noexcept { return {}; }
27+
FinalAwaiter final_suspend() noexcept { return {}; }
28+
void unhandled_exception() noexcept {}
29+
void return_value(T x) noexcept {
30+
value = x;
31+
}
32+
33+
std::coroutine_handle<> continuation;
34+
T value;
35+
};
36+
37+
Task(std::coroutine_handle<promise_type> handle) : handle(handle) {}
38+
~Task() {
39+
if (handle)
40+
handle.destroy();
41+
}
42+
43+
struct Awaiter {
44+
Awaiter(Task *t) : task(t) {}
45+
bool await_ready() const noexcept { return false; }
46+
void await_suspend(std::coroutine_handle<void> continuation) noexcept {}
47+
T await_resume() noexcept {
48+
return task->handle.promise().value;
49+
}
50+
51+
Task *task;
52+
};
53+
54+
auto operator co_await() {
55+
return Awaiter{this};
56+
}
57+
58+
private:
59+
std::coroutine_handle<promise_type> handle;
60+
};
61+
62+
// CHECK-LABEL: define{{.*}} @_Z6calleev
63+
Task<int> callee() {
64+
co_return 1;
65+
}
66+
67+
// CHECK-LABEL: define{{.*}} @_Z8elidablev
68+
Task<int> elidable() {
69+
// CHECK: %[[TARK_OBJ:.+]] = alloca %struct.Task
70+
// CHECK: call void @llvm.coro.safe.elide(ptr %[[TARK_OBJ:.+]])
71+
co_return co_await callee();
72+
}
73+
74+
// CHECK-LABEL: define{{.*}} @_Z11nonelidablev
75+
Task<int> nonelidable() {
76+
// CHECK: %[[TARK_OBJ:.+]] = alloca %struct.Task
77+
auto t = callee();
78+
// Because we aren't co_awaiting a prvalue, we cannot elide here.
79+
// CHECK-NOT: call void @llvm.coro.safe.elide(ptr %[[TARK_OBJ:.+]])
80+
co_await t;
81+
co_await std::move(t);
82+
83+
co_return 1;
84+
}

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: CoroLifetimeBound (SubjectMatchRule_record)
6363
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
6464
// CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)
65+
// CHECK-NEXT: CoroInplaceTask (SubjectMatchRule_record)
6566
// CHECK-NEXT: CoroWrapper (SubjectMatchRule_function)
6667
// CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
6768
// CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)

0 commit comments

Comments
 (0)