Skip to content

Commit 9907bd2

Browse files
[Clang] Introduce [[clang::structured_concurrency]]
1 parent f078548 commit 9907bd2

File tree

10 files changed

+173
-12
lines changed

10 files changed

+173
-12
lines changed

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 CoroStructuredConcurrencyType : InheritableAttr {
1216+
let Spellings = [Clang<"coro_structured_concurrency">];
1217+
let Subjects = SubjectList<[CXXRecord]>;
1218+
let LangOpts = [CPlusPlus];
1219+
let Documentation = [CoroStructuredConcurrencyDoc];
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 CoroStructuredConcurrencyDoc : Documentation {
8019+
let Category = DocCatDecl;
8020+
let Content = [{
8021+
The ``[[clang::coro_structured_concurrency]]`` 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_structured_concurrency]]``.
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/CGCoroutine.cpp

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212

1313
#include "CGCleanup.h"
1414
#include "CodeGenFunction.h"
15-
#include "llvm/ADT/ScopeExit.h"
15+
#include "clang/AST/ExprCXX.h"
1616
#include "clang/AST/StmtCXX.h"
1717
#include "clang/AST/StmtVisitor.h"
18+
#include "llvm/ADT/ArrayRef.h"
19+
#include "llvm/ADT/ScopeExit.h"
20+
#include "llvm/IR/Intrinsics.h"
1821

1922
using namespace clang;
2023
using namespace CodeGen;
@@ -219,17 +222,81 @@ namespace {
219222
RValue RV;
220223
};
221224
}
225+
226+
static MaterializeTemporaryExpr *
227+
getStructuredConcurrencyOperand(ASTContext &Ctx,
228+
CoroutineSuspendExpr const &S) {
229+
auto *E = S.getCommonExpr();
230+
auto *Temporary = dyn_cast_or_null<MaterializeTemporaryExpr>(E);
231+
if (!Temporary)
232+
return nullptr;
233+
234+
auto *Operator =
235+
dyn_cast_or_null<CXXOperatorCallExpr>(Temporary->getSubExpr());
236+
237+
if (!Operator ||
238+
Operator->getOperator() != OverloadedOperatorKind::OO_Coawait ||
239+
Operator->getNumArgs() != 1)
240+
return nullptr;
241+
242+
Expr *Arg = Operator->getArg(0);
243+
assert(Arg && "Arg to operator co_await should not be null");
244+
auto *CalleeRetClass = Arg->getType()->getAsCXXRecordDecl();
245+
246+
if (!CalleeRetClass ||
247+
!CalleeRetClass->hasAttr<CoroStructuredConcurrencyTypeAttr>())
248+
return nullptr;
249+
250+
if (!Arg->isTemporaryObject(Ctx, CalleeRetClass)) {
251+
return nullptr;
252+
}
253+
254+
return dyn_cast<MaterializeTemporaryExpr>(Arg);
255+
}
256+
222257
static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Coro,
223258
CoroutineSuspendExpr const &S,
224259
AwaitKind Kind, AggValueSlot aggSlot,
225260
bool ignoreResult, bool forLValue) {
226261
auto *E = S.getCommonExpr();
227262

263+
auto &Builder = CGF.Builder;
264+
bool MarkOperandSafeIntrinsic = false;
265+
266+
auto *TemporaryOperand = [&]() -> MaterializeTemporaryExpr * {
267+
bool CurFnRetTyHasAttr = false;
268+
if (auto *RetTyPtr = CGF.FnRetTy.getTypePtrOrNull()) {
269+
if (auto *CxxRecord = RetTyPtr->getAsCXXRecordDecl()) {
270+
CurFnRetTyHasAttr =
271+
CxxRecord->hasAttr<CoroStructuredConcurrencyTypeAttr>();
272+
}
273+
}
274+
275+
if (CurFnRetTyHasAttr) {
276+
return getStructuredConcurrencyOperand(CGF.getContext(), S);
277+
}
278+
return nullptr;
279+
}();
280+
281+
if (TemporaryOperand) {
282+
CGF.TemporaryValues[TemporaryOperand] = nullptr;
283+
MarkOperandSafeIntrinsic = true;
284+
}
285+
228286
auto CommonBinder =
229287
CodeGenFunction::OpaqueValueMappingData::bind(CGF, S.getOpaqueValue(), E);
230288
auto UnbindCommonOnExit =
231289
llvm::make_scope_exit([&] { CommonBinder.unbind(CGF); });
232290

291+
if (MarkOperandSafeIntrinsic) {
292+
auto It = CGF.TemporaryValues.find(TemporaryOperand);
293+
assert(It != CGF.TemporaryValues.end());
294+
if (auto Value = It->second)
295+
Builder.CreateCall(CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_safe_elide),
296+
{Value});
297+
CGF.TemporaryValues.erase(TemporaryOperand);
298+
}
299+
233300
auto Prefix = buildSuspendPrefixStr(Coro, Kind);
234301
BasicBlock *ReadyBlock = CGF.createBasicBlock(Prefix + Twine(".ready"));
235302
BasicBlock *SuspendBlock = CGF.createBasicBlock(Prefix + Twine(".suspend"));
@@ -241,7 +308,6 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
241308
// Otherwise, emit suspend logic.
242309
CGF.EmitBlock(SuspendBlock);
243310

244-
auto &Builder = CGF.Builder;
245311
llvm::Function *CoroSave = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_save);
246312
auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy);
247313
auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr});
@@ -255,9 +321,9 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
255321
"expected to be called in coroutine context");
256322

257323
SmallVector<llvm::Value *, 3> SuspendIntrinsicCallArgs;
258-
SuspendIntrinsicCallArgs.push_back(
259-
CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF));
260-
324+
auto *BoundAwaiterValue =
325+
CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF);
326+
SuspendIntrinsicCallArgs.push_back(BoundAwaiterValue);
261327
SuspendIntrinsicCallArgs.push_back(CGF.CurCoro.Data->CoroBegin);
262328
SuspendIntrinsicCallArgs.push_back(SuspendWrapper);
263329

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
@@ -371,6 +371,9 @@ class CodeGenFunction : public CodeGenTypeCache {
371371
};
372372
CGCoroInfo CurCoro;
373373

374+
llvm::SmallDenseMap<const MaterializeTemporaryExpr *, llvm::Value *>
375+
TemporaryValues;
376+
374377
bool isCoroutine() const {
375378
return CurCoro.Data != nullptr;
376379
}

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: CoroStructuredConcurrencyType (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)

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1729,6 +1729,9 @@ def int_coro_subfn_addr : DefaultAttrsIntrinsic<
17291729
[IntrReadMem, IntrArgMemOnly, ReadOnly<ArgIndex<0>>,
17301730
NoCapture<ArgIndex<0>>]>;
17311731

1732+
def int_coro_safe_elide : DefaultAttrsIntrinsic<
1733+
[], [llvm_ptr_ty], []>;
1734+
17321735
///===-------------------------- Other Intrinsics --------------------------===//
17331736
//
17341737
// TODO: We should introduce a new memory kind fo traps (and other side effects

llvm/lib/Transforms/Coroutines/CoroCleanup.cpp

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88

99
#include "llvm/Transforms/Coroutines/CoroCleanup.h"
1010
#include "CoroInternal.h"
11+
#include "llvm/IR/Function.h"
1112
#include "llvm/IR/IRBuilder.h"
1213
#include "llvm/IR/InstIterator.h"
14+
#include "llvm/IR/Intrinsics.h"
1315
#include "llvm/IR/PassManager.h"
14-
#include "llvm/IR/Function.h"
1516
#include "llvm/Transforms/Scalar/SimplifyCFG.h"
1617

1718
using namespace llvm;
@@ -80,7 +81,7 @@ bool Lowerer::lower(Function &F) {
8081
} else
8182
continue;
8283
break;
83-
case Intrinsic::coro_async_size_replace:
84+
case Intrinsic::coro_async_size_replace: {
8485
auto *Target = cast<ConstantStruct>(
8586
cast<GlobalVariable>(II->getArgOperand(0)->stripPointerCasts())
8687
->getInitializer());
@@ -98,6 +99,9 @@ bool Lowerer::lower(Function &F) {
9899
Target->replaceAllUsesWith(NewFuncPtrStruct);
99100
break;
100101
}
102+
case Intrinsic::coro_safe_elide:
103+
break;
104+
}
101105
II->eraseFromParent();
102106
Changed = true;
103107
}
@@ -111,7 +115,8 @@ static bool declaresCoroCleanupIntrinsics(const Module &M) {
111115
M, {"llvm.coro.alloc", "llvm.coro.begin", "llvm.coro.subfn.addr",
112116
"llvm.coro.free", "llvm.coro.id", "llvm.coro.id.retcon",
113117
"llvm.coro.id.async", "llvm.coro.id.retcon.once",
114-
"llvm.coro.async.size.replace", "llvm.coro.async.resume"});
118+
"llvm.coro.async.size.replace", "llvm.coro.async.resume",
119+
"llvm.coro.safe.elide"});
115120
}
116121

117122
PreservedAnalyses CoroCleanupPass::run(Module &M,

llvm/lib/Transforms/Coroutines/CoroElide.cpp

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
//===----------------------------------------------------------------------===//
88

99
#include "llvm/Transforms/Coroutines/CoroElide.h"
10+
#include "CoroInstr.h"
1011
#include "CoroInternal.h"
1112
#include "llvm/ADT/DenseMap.h"
1213
#include "llvm/ADT/Statistic.h"
1314
#include "llvm/Analysis/AliasAnalysis.h"
1415
#include "llvm/Analysis/InstructionSimplify.h"
1516
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
17+
#include "llvm/Analysis/PostDominators.h"
1618
#include "llvm/IR/Dominators.h"
1719
#include "llvm/IR/InstIterator.h"
1820
#include "llvm/Support/ErrorHandling.h"
@@ -56,7 +58,8 @@ class FunctionElideInfo {
5658
class CoroIdElider {
5759
public:
5860
CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI, AAResults &AA,
59-
DominatorTree &DT, OptimizationRemarkEmitter &ORE);
61+
DominatorTree &DT, PostDominatorTree &PDT,
62+
OptimizationRemarkEmitter &ORE);
6063
void elideHeapAllocations(uint64_t FrameSize, Align FrameAlign);
6164
bool lifetimeEligibleForElide() const;
6265
bool attemptElide();
@@ -68,6 +71,7 @@ class CoroIdElider {
6871
FunctionElideInfo &FEI;
6972
AAResults &AA;
7073
DominatorTree &DT;
74+
PostDominatorTree &PDT;
7175
OptimizationRemarkEmitter &ORE;
7276

7377
SmallVector<CoroBeginInst *, 1> CoroBegins;
@@ -183,8 +187,9 @@ void FunctionElideInfo::collectPostSplitCoroIds() {
183187

184188
CoroIdElider::CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI,
185189
AAResults &AA, DominatorTree &DT,
190+
PostDominatorTree &PDT,
186191
OptimizationRemarkEmitter &ORE)
187-
: CoroId(CoroId), FEI(FEI), AA(AA), DT(DT), ORE(ORE) {
192+
: CoroId(CoroId), FEI(FEI), AA(AA), DT(DT), PDT(PDT), ORE(ORE) {
188193
// Collect all coro.begin and coro.allocs associated with this coro.id.
189194
for (User *U : CoroId->users()) {
190195
if (auto *CB = dyn_cast<CoroBeginInst>(U))
@@ -336,6 +341,41 @@ bool CoroIdElider::canCoroBeginEscape(
336341
return false;
337342
}
338343

344+
// FIXME: This is not accounting for the stores to tasks whose handle is not
345+
// zero offset.
346+
static const StoreInst *getPostDominatingStoreToTask(const CoroBeginInst *CB,
347+
PostDominatorTree &PDT) {
348+
const StoreInst *OnlyStore = nullptr;
349+
350+
for (auto *U : CB->users()) {
351+
auto *Store = dyn_cast<StoreInst>(U);
352+
if (Store && Store->getValueOperand() == CB) {
353+
if (OnlyStore) {
354+
// Store must be unique. one coro begin getting stored to multiple
355+
// stores is not accepted.
356+
return nullptr;
357+
}
358+
OnlyStore = Store;
359+
}
360+
}
361+
362+
if (!OnlyStore || !PDT.dominates(OnlyStore, CB)) {
363+
return nullptr;
364+
}
365+
366+
return OnlyStore;
367+
}
368+
369+
static bool isMarkedSafeElide(const llvm::Value *V) {
370+
for (auto *U : V->users()) {
371+
auto *II = dyn_cast<IntrinsicInst>(U);
372+
if (II && (II->getIntrinsicID() == Intrinsic::coro_safe_elide)) {
373+
return true;
374+
}
375+
}
376+
return false;
377+
}
378+
339379
bool CoroIdElider::lifetimeEligibleForElide() const {
340380
// If no CoroAllocs, we cannot suppress allocation, so elision is not
341381
// possible.
@@ -364,6 +404,15 @@ bool CoroIdElider::lifetimeEligibleForElide() const {
364404

365405
// Filter out the coro.destroy that lie along exceptional paths.
366406
for (const auto *CB : CoroBegins) {
407+
// This might be too strong of a condition but should be very safe.
408+
// If the CB is unconditionally stored into a "Task Like Object",
409+
// and such object is "safe elide".
410+
if (auto *MaybeStoreToTask = getPostDominatingStoreToTask(CB, PDT)) {
411+
auto Dest = MaybeStoreToTask->getPointerOperand();
412+
if (isMarkedSafeElide(Dest))
413+
continue;
414+
}
415+
367416
auto It = DestroyAddr.find(CB);
368417

369418
// FIXME: If we have not found any destroys for this coro.begin, we
@@ -476,11 +525,12 @@ PreservedAnalyses CoroElidePass::run(Function &F, FunctionAnalysisManager &AM) {
476525

477526
AAResults &AA = AM.getResult<AAManager>(F);
478527
DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
528+
PostDominatorTree &PDT = AM.getResult<PostDominatorTreeAnalysis>(F);
479529
auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
480530

481531
bool Changed = false;
482532
for (auto *CII : FEI.getCoroIds()) {
483-
CoroIdElider CIE(CII, FEI, AA, DT, ORE);
533+
CoroIdElider CIE(CII, FEI, AA, DT, PDT, ORE);
484534
Changed |= CIE.attemptElide();
485535
}
486536

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ static const char *const CoroIntrinsics[] = {
8686
"llvm.coro.prepare.retcon",
8787
"llvm.coro.promise",
8888
"llvm.coro.resume",
89+
"llvm.coro.safe.elide",
8990
"llvm.coro.save",
9091
"llvm.coro.size",
9192
"llvm.coro.subfn.addr",

0 commit comments

Comments
 (0)