summaryrefslogtreecommitdiffstats
path: root/llvm/lib
diff options
context:
space:
mode:
authorGor Nishanov <GorNishanov@gmail.com>2016-09-26 15:49:28 +0000
committerGor Nishanov <GorNishanov@gmail.com>2016-09-26 15:49:28 +0000
commitbc0ebb383c54e8ada847c6a83e0dfc490e2f2aa1 (patch)
tree69ab6cd735eb7ef12d5a7b7d0bc1a4afae13e3af /llvm/lib
parent256fcf975f60f51fce9fe7f5992f9e409e60db29 (diff)
downloadbcm5719-llvm-bc0ebb383c54e8ada847c6a83e0dfc490e2f2aa1.tar.gz
bcm5719-llvm-bc0ebb383c54e8ada847c6a83e0dfc490e2f2aa1.zip
[Coroutines] Part14: Handle coroutines with no suspend points.
Summary: If coroutine has no suspend points, remove heap allocation and turn a coroutine into a normal function. Also, if a pattern is detected that coroutine resumes or destroys itself prior to coro.suspend call, turn the suspend point into a simple jump to resume or cleanup label. This pattern occurs when coroutines are used to propagate errors in functions that return expected<T>. Reviewers: majnemer Subscribers: mehdi_amini, llvm-commits Differential Revision: https://reviews.llvm.org/D24408 llvm-svn: 282414
Diffstat (limited to 'llvm/lib')
-rw-r--r--llvm/lib/Transforms/Coroutines/CoroInstr.h8
-rw-r--r--llvm/lib/Transforms/Coroutines/CoroSplit.cpp112
2 files changed, 120 insertions, 0 deletions
diff --git a/llvm/lib/Transforms/Coroutines/CoroInstr.h b/llvm/lib/Transforms/Coroutines/CoroInstr.h
index 06571dd1e5c..91d7427c379 100644
--- a/llvm/lib/Transforms/Coroutines/CoroInstr.h
+++ b/llvm/lib/Transforms/Coroutines/CoroInstr.h
@@ -80,6 +80,14 @@ class LLVM_LIBRARY_VISIBILITY CoroIdInst : public IntrinsicInst {
enum { AlignArg, PromiseArg, CoroutineArg, InfoArg };
public:
+ IntrinsicInst *getCoroAlloc() {
+ for (User *U : users())
+ if (auto *II = dyn_cast<IntrinsicInst>(U))
+ if (II->getIntrinsicID() == Intrinsic::coro_alloc)
+ return II;
+ return nullptr;
+ }
+
IntrinsicInst *getCoroBegin() {
for (User *U : users())
if (auto *II = dyn_cast<IntrinsicInst>(U))
diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
index 6813afdcabb..7233e5301b1 100644
--- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
+++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp
@@ -361,14 +361,126 @@ static void postSplitCleanup(Function &F) {
FPM.doFinalization();
}
+// Coroutine has no suspend points. Remove heap allocation for the coroutine
+// frame if possible.
+static void handleNoSuspendCoroutine(CoroBeginInst *CoroBegin, Type *FrameTy) {
+ auto *CoroId = CoroBegin->getId();
+ auto *AllocInst = CoroId->getCoroAlloc();
+ coro::replaceCoroFree(CoroId, /*Elide=*/AllocInst != nullptr);
+ if (AllocInst) {
+ IRBuilder<> Builder(AllocInst);
+ // FIXME: Need to handle overaligned members.
+ auto *Frame = Builder.CreateAlloca(FrameTy);
+ auto *VFrame = Builder.CreateBitCast(Frame, Builder.getInt8PtrTy());
+ AllocInst->replaceAllUsesWith(Builder.getFalse());
+ AllocInst->eraseFromParent();
+ CoroBegin->replaceAllUsesWith(VFrame);
+ } else {
+ CoroBegin->replaceAllUsesWith(CoroBegin->getMem());
+ }
+ CoroBegin->eraseFromParent();
+}
+
+// look for a very simple pattern
+// coro.save
+// no other calls
+// resume or destroy call
+// coro.suspend
+//
+// If there are other calls between coro.save and coro.suspend, they can
+// potentially resume or destroy the coroutine, so it is unsafe to eliminate a
+// suspend point.
+static bool simplifySuspendPoint(CoroSuspendInst *Suspend,
+ CoroBeginInst *CoroBegin) {
+ auto *Save = Suspend->getCoroSave();
+ auto *BB = Suspend->getParent();
+ if (BB != Save->getParent())
+ return false;
+
+ CallSite SingleCallSite;
+
+ // Check that we have only one CallSite.
+ for (Instruction *I = Save->getNextNode(); I != Suspend;
+ I = I->getNextNode()) {
+ if (isa<CoroFrameInst>(I))
+ continue;
+ if (isa<CoroSubFnInst>(I))
+ continue;
+ if (CallSite CS = CallSite(I)) {
+ if (SingleCallSite)
+ return false;
+ else
+ SingleCallSite = CS;
+ }
+ }
+ auto *CallInstr = SingleCallSite.getInstruction();
+ if (!CallInstr)
+ return false;
+
+ auto *Callee = SingleCallSite.getCalledValue()->stripPointerCasts();
+
+ // See if the callsite is for resumption or destruction of the coroutine.
+ auto *SubFn = dyn_cast<CoroSubFnInst>(Callee);
+ if (!SubFn)
+ return false;
+
+ // Does not refer to the current coroutine, we cannot do anything with it.
+ if (SubFn->getFrame() != CoroBegin)
+ return false;
+
+ // Replace llvm.coro.suspend with the value that results in resumption over
+ // the resume or cleanup path.
+ Suspend->replaceAllUsesWith(SubFn->getRawIndex());
+ Suspend->eraseFromParent();
+ Save->eraseFromParent();
+
+ // No longer need a call to coro.resume or coro.destroy.
+ CallInstr->eraseFromParent();
+
+ if (SubFn->user_empty())
+ SubFn->eraseFromParent();
+
+ return true;
+}
+
+// Remove suspend points that are simplified.
+static void simplifySuspendPoints(coro::Shape &Shape) {
+ auto &S = Shape.CoroSuspends;
+ size_t I = 0, N = S.size();
+ if (N == 0)
+ return;
+ for (;;) {
+ if (simplifySuspendPoint(S[I], Shape.CoroBegin)) {
+ if (--N == I)
+ break;
+ std::swap(S[I], S[N]);
+ continue;
+ }
+ if (++I == N)
+ break;
+ }
+ S.resize(N);
+}
+
static void splitCoroutine(Function &F, CallGraph &CG, CallGraphSCC &SCC) {
coro::Shape Shape(F);
if (!Shape.CoroBegin)
return;
+ simplifySuspendPoints(Shape);
buildCoroutineFrame(F, Shape);
replaceFrameSize(Shape);
+ // If there are no suspend points, no split required, just remove
+ // the allocation and deallocation blocks, they are not needed.
+ if (Shape.CoroSuspends.empty()) {
+ handleNoSuspendCoroutine(Shape.CoroBegin, Shape.FrameTy);
+ removeCoroEnds(Shape);
+ postSplitCleanup(F);
+ coro::updateCallGraph(F, {}, CG, SCC);
+ return;
+ }
+
auto *ResumeEntry = createResumeEntryBlock(F, Shape);
auto ResumeClone = createClone(F, ".resume", Shape, ResumeEntry, 0);
auto DestroyClone = createClone(F, ".destroy", Shape, ResumeEntry, 1);
OpenPOWER on IntegriCloud