[clang] [llvm] [Clang][Coroutines] Improve CoroElide with [[clang::coro_structured_concurrency]] attribute for C++ (PR #94693)
Yuxuan Chen via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 14 13:23:02 PDT 2024
https://github.com/yuxuanchen1997 updated https://github.com/llvm/llvm-project/pull/94693
>From 049c29d85a06c58ed8afa689fe359e83c11dc314 Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <yuxuanchen1997 at outlook.com>
Date: Tue, 4 Jun 2024 23:22:00 -0700
Subject: [PATCH] [Clang] Introduce [[clang::structured_concurrency]]
---
clang/include/clang/Basic/Attr.td | 8 ++
clang/include/clang/Basic/AttrDocs.td | 20 +++++
clang/lib/CodeGen/CGCoroutine.cpp | 76 +++++++++++++++--
clang/lib/CodeGen/CGExpr.cpp | 6 +-
clang/lib/CodeGen/CodeGenFunction.h | 3 +
clang/test/CodeGenCoroutines/Inputs/utility.h | 13 +++
.../coro-structured-concurrency.cpp | 84 +++++++++++++++++++
...a-attribute-supported-attributes-list.test | 1 +
llvm/include/llvm/IR/Intrinsics.td | 3 +
.../lib/Transforms/Coroutines/CoroCleanup.cpp | 11 ++-
llvm/lib/Transforms/Coroutines/CoroElide.cpp | 56 ++++++++++++-
llvm/lib/Transforms/Coroutines/Coroutines.cpp | 1 +
.../coro-elide-structured-concurrency.ll | 62 ++++++++++++++
13 files changed, 332 insertions(+), 12 deletions(-)
create mode 100644 clang/test/CodeGenCoroutines/Inputs/utility.h
create mode 100644 clang/test/CodeGenCoroutines/coro-structured-concurrency.cpp
create mode 100644 llvm/test/Transforms/Coroutines/coro-elide-structured-concurrency.ll
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 6032b934279dd..d468e08d6f427 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -1212,6 +1212,14 @@ def CoroDisableLifetimeBound : InheritableAttr {
let SimpleHandler = 1;
}
+def CoroStructuredConcurrencyType : InheritableAttr {
+ let Spellings = [Clang<"coro_structured_concurrency">];
+ let Subjects = SubjectList<[CXXRecord]>;
+ let LangOpts = [CPlusPlus];
+ let Documentation = [CoroStructuredConcurrencyDoc];
+ let SimpleHandler = 1;
+}
+
// OSObject-based attributes.
def OSConsumed : InheritableParamAttr {
let Spellings = [Clang<"os_consumed">];
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 70d5dfa8aaf86..50fc0fc2d16f6 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -8015,6 +8015,26 @@ but do not pass them to the underlying coroutine or pass them by value.
}];
}
+def CoroStructuredConcurrencyDoc : Documentation {
+ let Category = DocCatDecl;
+ let Content = [{
+The ``[[clang::coro_structured_concurrency]]`` is a class attribute which can be applied
+to a coroutine return type.
+
+When a coroutine function that returns such a type calls another coroutine function,
+the compiler performs heap allocation elision when the following conditions are all met:
+- callee coroutine function returns a type that is annotated with
+ ``[[clang::coro_structured_concurrency]]``.
+- The callee coroutine function is inlined.
+- In caller coroutine, the return value of the callee is a prvalue or an xvalue, and
+- The temporary expression containing the callee coroutine object is immediately co_awaited.
+
+The behavior is undefined if any of the following condition was met:
+- the caller coroutine is destroyed earlier than the callee coroutine.
+
+ }];
+}
+
def CountedByDocs : Documentation {
let Category = DocCatField;
let Content = [{
diff --git a/clang/lib/CodeGen/CGCoroutine.cpp b/clang/lib/CodeGen/CGCoroutine.cpp
index b4c724422c14a..78ae04982cba0 100644
--- a/clang/lib/CodeGen/CGCoroutine.cpp
+++ b/clang/lib/CodeGen/CGCoroutine.cpp
@@ -12,9 +12,12 @@
#include "CGCleanup.h"
#include "CodeGenFunction.h"
-#include "llvm/ADT/ScopeExit.h"
+#include "clang/AST/ExprCXX.h"
#include "clang/AST/StmtCXX.h"
#include "clang/AST/StmtVisitor.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/ScopeExit.h"
+#include "llvm/IR/Intrinsics.h"
using namespace clang;
using namespace CodeGen;
@@ -219,17 +222,81 @@ namespace {
RValue RV;
};
}
+
+static MaterializeTemporaryExpr *
+getStructuredConcurrencyOperand(ASTContext &Ctx,
+ CoroutineSuspendExpr const &S) {
+ auto *E = S.getCommonExpr();
+ auto *Temporary = dyn_cast_or_null<MaterializeTemporaryExpr>(E);
+ if (!Temporary)
+ return nullptr;
+
+ auto *Operator =
+ dyn_cast_or_null<CXXOperatorCallExpr>(Temporary->getSubExpr());
+
+ if (!Operator ||
+ Operator->getOperator() != OverloadedOperatorKind::OO_Coawait ||
+ Operator->getNumArgs() != 1)
+ return nullptr;
+
+ Expr *Arg = Operator->getArg(0);
+ assert(Arg && "Arg to operator co_await should not be null");
+ auto *CalleeRetClass = Arg->getType()->getAsCXXRecordDecl();
+
+ if (!CalleeRetClass ||
+ !CalleeRetClass->hasAttr<CoroStructuredConcurrencyTypeAttr>())
+ return nullptr;
+
+ if (!Arg->isTemporaryObject(Ctx, CalleeRetClass)) {
+ return nullptr;
+ }
+
+ return dyn_cast<MaterializeTemporaryExpr>(Arg);
+}
+
static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Coro,
CoroutineSuspendExpr const &S,
AwaitKind Kind, AggValueSlot aggSlot,
bool ignoreResult, bool forLValue) {
auto *E = S.getCommonExpr();
+ auto &Builder = CGF.Builder;
+ bool MarkOperandSafeIntrinsic = false;
+
+ auto *TemporaryOperand = [&]() -> MaterializeTemporaryExpr * {
+ bool CurFnRetTyHasAttr = false;
+ if (auto *RetTyPtr = CGF.FnRetTy.getTypePtrOrNull()) {
+ if (auto *CxxRecord = RetTyPtr->getAsCXXRecordDecl()) {
+ CurFnRetTyHasAttr =
+ CxxRecord->hasAttr<CoroStructuredConcurrencyTypeAttr>();
+ }
+ }
+
+ if (CurFnRetTyHasAttr) {
+ return getStructuredConcurrencyOperand(CGF.getContext(), S);
+ }
+ return nullptr;
+ }();
+
+ if (TemporaryOperand) {
+ CGF.TemporaryValues[TemporaryOperand] = nullptr;
+ MarkOperandSafeIntrinsic = true;
+ }
+
auto CommonBinder =
CodeGenFunction::OpaqueValueMappingData::bind(CGF, S.getOpaqueValue(), E);
auto UnbindCommonOnExit =
llvm::make_scope_exit([&] { CommonBinder.unbind(CGF); });
+ if (MarkOperandSafeIntrinsic) {
+ auto It = CGF.TemporaryValues.find(TemporaryOperand);
+ assert(It != CGF.TemporaryValues.end());
+ if (auto Value = It->second)
+ Builder.CreateCall(CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_safe_elide),
+ {Value});
+ CGF.TemporaryValues.erase(TemporaryOperand);
+ }
+
auto Prefix = buildSuspendPrefixStr(Coro, Kind);
BasicBlock *ReadyBlock = CGF.createBasicBlock(Prefix + Twine(".ready"));
BasicBlock *SuspendBlock = CGF.createBasicBlock(Prefix + Twine(".suspend"));
@@ -241,7 +308,6 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
// Otherwise, emit suspend logic.
CGF.EmitBlock(SuspendBlock);
- auto &Builder = CGF.Builder;
llvm::Function *CoroSave = CGF.CGM.getIntrinsic(llvm::Intrinsic::coro_save);
auto *NullPtr = llvm::ConstantPointerNull::get(CGF.CGM.Int8PtrTy);
auto *SaveCall = Builder.CreateCall(CoroSave, {NullPtr});
@@ -255,9 +321,9 @@ static LValueOrRValue emitSuspendExpression(CodeGenFunction &CGF, CGCoroData &Co
"expected to be called in coroutine context");
SmallVector<llvm::Value *, 3> SuspendIntrinsicCallArgs;
- SuspendIntrinsicCallArgs.push_back(
- CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF));
-
+ auto *BoundAwaiterValue =
+ CGF.getOrCreateOpaqueLValueMapping(S.getOpaqueValue()).getPointer(CGF);
+ SuspendIntrinsicCallArgs.push_back(BoundAwaiterValue);
SuspendIntrinsicCallArgs.push_back(CGF.CurCoro.Data->CoroBegin);
SuspendIntrinsicCallArgs.push_back(SuspendWrapper);
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 48d8ca3478862..f682c994a9b41 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -618,7 +618,11 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
}
}
- return MakeAddrLValue(Object, M->getType(), AlignmentSource::Decl);
+ auto Ret = MakeAddrLValue(Object, M->getType(), AlignmentSource::Decl);
+ if (TemporaryValues.contains(M)) {
+ TemporaryValues[M] = Ret.getPointer(*this);
+ }
+ return Ret;
}
RValue
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 06fc7259b5901..8f53ac4663b2c 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -369,6 +369,9 @@ class CodeGenFunction : public CodeGenTypeCache {
};
CGCoroInfo CurCoro;
+ llvm::SmallDenseMap<const MaterializeTemporaryExpr *, llvm::Value *>
+ TemporaryValues;
+
bool isCoroutine() const {
return CurCoro.Data != nullptr;
}
diff --git a/clang/test/CodeGenCoroutines/Inputs/utility.h b/clang/test/CodeGenCoroutines/Inputs/utility.h
new file mode 100644
index 0000000000000..43c6d27823bd4
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/Inputs/utility.h
@@ -0,0 +1,13 @@
+// This is a mock file for <utility>
+
+namespace std {
+
+template <typename T> struct remove_reference { using type = T; };
+template <typename T> struct remove_reference<T &> { using type = T; };
+template <typename T> struct remove_reference<T &&> { using type = T; };
+
+template <typename T>
+constexpr typename std::remove_reference<T>::type&& move(T &&t) noexcept {
+ return static_cast<typename std::remove_reference<T>::type &&>(t);
+}
+}
diff --git a/clang/test/CodeGenCoroutines/coro-structured-concurrency.cpp b/clang/test/CodeGenCoroutines/coro-structured-concurrency.cpp
new file mode 100644
index 0000000000000..b1892069d3d88
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-structured-concurrency.cpp
@@ -0,0 +1,84 @@
+// This file tests the coro_structured_concurrency attribute semantics.
+// RUN: %clang_cc1 -std=c++20 -disable-llvm-passes -emit-llvm %s -o - | FileCheck %s
+
+#include "Inputs/coroutine.h"
+#include "Inputs/utility.h"
+
+template <typename T>
+struct [[clang::coro_structured_concurrency]] Task {
+ struct promise_type {
+ struct FinalAwaiter {
+ bool await_ready() const noexcept { return false; }
+
+ template <typename P>
+ std::coroutine_handle<> await_suspend(std::coroutine_handle<P> coro) noexcept {
+ if (!coro)
+ return std::noop_coroutine();
+ return coro.promise().continuation;
+ }
+ void await_resume() noexcept {}
+ };
+
+ Task get_return_object() noexcept {
+ return std::coroutine_handle<promise_type>::from_promise(*this);
+ }
+
+ std::suspend_always initial_suspend() noexcept { return {}; }
+ FinalAwaiter final_suspend() noexcept { return {}; }
+ void unhandled_exception() noexcept {}
+ void return_value(T x) noexcept {
+ value = x;
+ }
+
+ std::coroutine_handle<> continuation;
+ T value;
+ };
+
+ Task(std::coroutine_handle<promise_type> handle) : handle(handle) {}
+ ~Task() {
+ if (handle)
+ handle.destroy();
+ }
+
+ struct Awaiter {
+ Awaiter(Task *t) : task(t) {}
+ bool await_ready() const noexcept { return false; }
+ void await_suspend(std::coroutine_handle<void> continuation) noexcept {}
+ T await_resume() noexcept {
+ return task->handle.promise().value;
+ }
+
+ Task *task;
+ };
+
+ auto operator co_await() {
+ return Awaiter{this};
+ }
+
+private:
+ std::coroutine_handle<promise_type> handle;
+};
+
+// CHECK-LABEL: define{{.*}} @_Z6calleev
+Task<int> callee() {
+ co_return 1;
+}
+
+// CHECK-LABEL: define{{.*}} @_Z8elidablev
+Task<int> elidable() {
+ // CHECK: %[[TARK_OBJ:.+]] = alloca %struct.Task
+ // CHECK: call void @llvm.coro.safe.elide(ptr %[[TARK_OBJ:.+]])
+ co_return co_await callee();
+}
+
+// CHECK-LABEL: define{{.*}} @_Z11nonelidablev
+Task<int> nonelidable() {
+ // CHECK: %[[TARK_OBJ:.+]] = alloca %struct.Task
+ auto t = callee();
+ // Because we aren't co_awaiting a prvalue, we cannot elide here.
+ // CHECK-NOT: call void @llvm.coro.safe.elide(ptr %[[TARK_OBJ:.+]])
+ co_await t;
+ co_await std::move(t);
+
+ co_return 1;
+}
diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
index 99732694f72a5..0369906d7c275 100644
--- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test
+++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test
@@ -62,6 +62,7 @@
// CHECK-NEXT: CoroLifetimeBound (SubjectMatchRule_record)
// CHECK-NEXT: CoroOnlyDestroyWhenComplete (SubjectMatchRule_record)
// CHECK-NEXT: CoroReturnType (SubjectMatchRule_record)
+// CHECK-NEXT: CoroStructuredConcurrencyType (SubjectMatchRule_record)
// CHECK-NEXT: CoroWrapper (SubjectMatchRule_function)
// CHECK-NEXT: DLLExport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
// CHECK-NEXT: DLLImport (SubjectMatchRule_function, SubjectMatchRule_variable, SubjectMatchRule_record, SubjectMatchRule_objc_interface)
diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td
index 4c506a6ace23e..20299750951ff 100644
--- a/llvm/include/llvm/IR/Intrinsics.td
+++ b/llvm/include/llvm/IR/Intrinsics.td
@@ -1733,6 +1733,9 @@ def int_coro_subfn_addr : DefaultAttrsIntrinsic<
[IntrReadMem, IntrArgMemOnly, ReadOnly<ArgIndex<0>>,
NoCapture<ArgIndex<0>>]>;
+def int_coro_safe_elide : DefaultAttrsIntrinsic<
+ [], [llvm_ptr_ty], []>;
+
///===-------------------------- Other Intrinsics --------------------------===//
//
// TODO: We should introduce a new memory kind fo traps (and other side effects
diff --git a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
index 3e3825fcd50e2..71229eae5cb47 100644
--- a/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroCleanup.cpp
@@ -8,10 +8,11 @@
#include "llvm/Transforms/Coroutines/CoroCleanup.h"
#include "CoroInternal.h"
+#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstIterator.h"
+#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/PassManager.h"
-#include "llvm/IR/Function.h"
#include "llvm/Transforms/Scalar/SimplifyCFG.h"
using namespace llvm;
@@ -80,7 +81,7 @@ bool Lowerer::lower(Function &F) {
} else
continue;
break;
- case Intrinsic::coro_async_size_replace:
+ case Intrinsic::coro_async_size_replace: {
auto *Target = cast<ConstantStruct>(
cast<GlobalVariable>(II->getArgOperand(0)->stripPointerCasts())
->getInitializer());
@@ -98,6 +99,9 @@ bool Lowerer::lower(Function &F) {
Target->replaceAllUsesWith(NewFuncPtrStruct);
break;
}
+ case Intrinsic::coro_safe_elide:
+ break;
+ }
II->eraseFromParent();
Changed = true;
}
@@ -111,7 +115,8 @@ static bool declaresCoroCleanupIntrinsics(const Module &M) {
M, {"llvm.coro.alloc", "llvm.coro.begin", "llvm.coro.subfn.addr",
"llvm.coro.free", "llvm.coro.id", "llvm.coro.id.retcon",
"llvm.coro.id.async", "llvm.coro.id.retcon.once",
- "llvm.coro.async.size.replace", "llvm.coro.async.resume"});
+ "llvm.coro.async.size.replace", "llvm.coro.async.resume",
+ "llvm.coro.safe.elide"});
}
PreservedAnalyses CoroCleanupPass::run(Module &M,
diff --git a/llvm/lib/Transforms/Coroutines/CoroElide.cpp b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
index 74b5ccb7b9b71..dd2f72410c931 100644
--- a/llvm/lib/Transforms/Coroutines/CoroElide.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroElide.cpp
@@ -7,12 +7,14 @@
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/Coroutines/CoroElide.h"
+#include "CoroInstr.h"
#include "CoroInternal.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Analysis/AliasAnalysis.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/Analysis/OptimizationRemarkEmitter.h"
+#include "llvm/Analysis/PostDominators.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/InstIterator.h"
#include "llvm/Support/ErrorHandling.h"
@@ -56,7 +58,8 @@ class FunctionElideInfo {
class CoroIdElider {
public:
CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI, AAResults &AA,
- DominatorTree &DT, OptimizationRemarkEmitter &ORE);
+ DominatorTree &DT, PostDominatorTree &PDT,
+ OptimizationRemarkEmitter &ORE);
void elideHeapAllocations(uint64_t FrameSize, Align FrameAlign);
bool lifetimeEligibleForElide() const;
bool attemptElide();
@@ -68,6 +71,7 @@ class CoroIdElider {
FunctionElideInfo &FEI;
AAResults &AA;
DominatorTree &DT;
+ PostDominatorTree &PDT;
OptimizationRemarkEmitter &ORE;
SmallVector<CoroBeginInst *, 1> CoroBegins;
@@ -183,8 +187,9 @@ void FunctionElideInfo::collectPostSplitCoroIds() {
CoroIdElider::CoroIdElider(CoroIdInst *CoroId, FunctionElideInfo &FEI,
AAResults &AA, DominatorTree &DT,
+ PostDominatorTree &PDT,
OptimizationRemarkEmitter &ORE)
- : CoroId(CoroId), FEI(FEI), AA(AA), DT(DT), ORE(ORE) {
+ : CoroId(CoroId), FEI(FEI), AA(AA), DT(DT), PDT(PDT), ORE(ORE) {
// Collect all coro.begin and coro.allocs associated with this coro.id.
for (User *U : CoroId->users()) {
if (auto *CB = dyn_cast<CoroBeginInst>(U))
@@ -336,6 +341,41 @@ bool CoroIdElider::canCoroBeginEscape(
return false;
}
+// FIXME: This is not accounting for the stores to tasks whose handle is not
+// zero offset.
+static const StoreInst *getPostDominatingStoreToTask(const CoroBeginInst *CB,
+ PostDominatorTree &PDT) {
+ const StoreInst *OnlyStore = nullptr;
+
+ for (auto *U : CB->users()) {
+ auto *Store = dyn_cast<StoreInst>(U);
+ if (Store && Store->getValueOperand() == CB) {
+ if (OnlyStore) {
+ // Store must be unique. one coro begin getting stored to multiple
+ // stores is not accepted.
+ return nullptr;
+ }
+ OnlyStore = Store;
+ }
+ }
+
+ if (!OnlyStore || !PDT.dominates(OnlyStore, CB)) {
+ return nullptr;
+ }
+
+ return OnlyStore;
+}
+
+static bool isMarkedSafeElide(const llvm::Value *V) {
+ for (auto *U : V->users()) {
+ auto *II = dyn_cast<IntrinsicInst>(U);
+ if (II && (II->getIntrinsicID() == Intrinsic::coro_safe_elide)) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool CoroIdElider::lifetimeEligibleForElide() const {
// If no CoroAllocs, we cannot suppress allocation, so elision is not
// possible.
@@ -364,6 +404,15 @@ bool CoroIdElider::lifetimeEligibleForElide() const {
// Filter out the coro.destroy that lie along exceptional paths.
for (const auto *CB : CoroBegins) {
+ // This might be too strong of a condition but should be very safe.
+ // If the CB is unconditionally stored into a "Task Like Object",
+ // and such object is "safe elide".
+ if (auto *MaybeStoreToTask = getPostDominatingStoreToTask(CB, PDT)) {
+ auto Dest = MaybeStoreToTask->getPointerOperand();
+ if (isMarkedSafeElide(Dest))
+ continue;
+ }
+
auto It = DestroyAddr.find(CB);
// FIXME: If we have not found any destroys for this coro.begin, we
@@ -476,11 +525,12 @@ PreservedAnalyses CoroElidePass::run(Function &F, FunctionAnalysisManager &AM) {
AAResults &AA = AM.getResult<AAManager>(F);
DominatorTree &DT = AM.getResult<DominatorTreeAnalysis>(F);
+ PostDominatorTree &PDT = AM.getResult<PostDominatorTreeAnalysis>(F);
auto &ORE = AM.getResult<OptimizationRemarkEmitterAnalysis>(F);
bool Changed = false;
for (auto *CII : FEI.getCoroIds()) {
- CoroIdElider CIE(CII, FEI, AA, DT, ORE);
+ CoroIdElider CIE(CII, FEI, AA, DT, PDT, ORE);
Changed |= CIE.attemptElide();
}
diff --git a/llvm/lib/Transforms/Coroutines/Coroutines.cpp b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
index 1a92bc1636257..48c02e5406b75 100644
--- a/llvm/lib/Transforms/Coroutines/Coroutines.cpp
+++ b/llvm/lib/Transforms/Coroutines/Coroutines.cpp
@@ -86,6 +86,7 @@ static const char *const CoroIntrinsics[] = {
"llvm.coro.prepare.retcon",
"llvm.coro.promise",
"llvm.coro.resume",
+ "llvm.coro.safe.elide",
"llvm.coro.save",
"llvm.coro.size",
"llvm.coro.subfn.addr",
diff --git a/llvm/test/Transforms/Coroutines/coro-elide-structured-concurrency.ll b/llvm/test/Transforms/Coroutines/coro-elide-structured-concurrency.ll
new file mode 100644
index 0000000000000..97c615d00d238
--- /dev/null
+++ b/llvm/test/Transforms/Coroutines/coro-elide-structured-concurrency.ll
@@ -0,0 +1,62 @@
+; Tests that the coro.destroy and coro.resume are devirtualized where possible,
+; SCC pipeline restarts and inlines the direct calls.
+; RUN: opt < %s -S -passes='inline,coro-elide' | FileCheck %s
+
+%struct.Task = type { ptr }
+
+declare void @print(i32) nounwind
+
+; resume part of the coroutine
+define fastcc void @callee.resume(ptr dereferenceable(1)) {
+ tail call void @print(i32 0)
+ ret void
+}
+
+; destroy part of the coroutine
+define fastcc void @callee.destroy(ptr) {
+ tail call void @print(i32 1)
+ ret void
+}
+
+; cleanup part of the coroutine
+define fastcc void @callee.cleanup(ptr) {
+ tail call void @print(i32 2)
+ ret void
+}
+
+ at callee.resumers = internal constant [3 x ptr] [
+ ptr @callee.resume, ptr @callee.destroy, ptr @callee.cleanup]
+
+declare void @alloc(i1) nounwind
+
+; CHECK: define ptr @callee()
+define ptr @callee() {
+entry:
+ %task = alloca %struct.Task, align 8
+ %id = call token @llvm.coro.id(i32 0, ptr null,
+ ptr @callee,
+ ptr @callee.resumers)
+ %alloc = call i1 @llvm.coro.alloc(token %id)
+ %hdl = call ptr @llvm.coro.begin(token %id, ptr null)
+ store ptr %hdl, ptr %task
+ ret ptr %task
+}
+
+; CHECK: define ptr @caller()
+define ptr @caller() {
+entry:
+ %task = call ptr @callee()
+
+ ; CHECK: %[[id:.+]] = call token @llvm.coro.id(i32 0, ptr null, ptr @callee, ptr @callee.resumers)
+ ; CHECK-NOT: call i1 @llvm.coro.alloc(token %[[id]])
+ call void @llvm.coro.safe.elide(ptr %task)
+
+ ret ptr %task
+}
+
+declare token @llvm.coro.id(i32, ptr, ptr, ptr)
+declare ptr @llvm.coro.begin(token, ptr)
+declare ptr @llvm.coro.frame()
+declare ptr @llvm.coro.subfn.addr(ptr, i8)
+declare i1 @llvm.coro.alloc(token)
+declare void @llvm.coro.safe.elide(ptr)
More information about the cfe-commits
mailing list