diff --git a/llvm/include/llvm-c/Transforms/Coroutines.h b/llvm/include/llvm-c/Transforms/Coroutines.h --- a/llvm/include/llvm-c/Transforms/Coroutines.h +++ b/llvm/include/llvm-c/Transforms/Coroutines.h @@ -34,7 +34,7 @@ /** See llvm::createCoroEarlyLegacyPass function. */ void LLVMAddCoroEarlyPass(LLVMPassManagerRef PM); -/** See llvm::createCoroSplitPass function. */ +/** See llvm::createCoroSplitLegacyPass function. */ void LLVMAddCoroSplitPass(LLVMPassManagerRef PM); /** See llvm::createCoroElidePass function. */ diff --git a/llvm/include/llvm/Transforms/Coroutines.h b/llvm/include/llvm/Transforms/Coroutines.h --- a/llvm/include/llvm/Transforms/Coroutines.h +++ b/llvm/include/llvm/Transforms/Coroutines.h @@ -23,7 +23,7 @@ Pass *createCoroEarlyLegacyPass(); /// Split up coroutines into multiple functions driving their state machines. -Pass *createCoroSplitPass(); +Pass *createCoroSplitLegacyPass(); /// Analyze coroutines use sites, devirtualize resume/destroy calls and elide /// heap allocation for coroutine frame where possible. diff --git a/llvm/include/llvm/Transforms/Coroutines/CoroSplit.h b/llvm/include/llvm/Transforms/Coroutines/CoroSplit.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/Transforms/Coroutines/CoroSplit.h @@ -0,0 +1,39 @@ +//===- CoroSplit.h - Converts a coroutine into a state machine -*- C++ -*--===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// \file +// This file declares the pass that builds the coroutine frame and outlines +// resume and destroy parts of the coroutine into separate functions. +// +// We present a coroutine to LLVM as an ordinary function with suspension +// points marked up with intrinsics. We let the optimizer party on the coroutine +// as a single function for as long as possible. Shortly before the coroutine is +// eligible to be inlined into its callers, we split up the coroutine into parts +// corresponding to initial, resume and destroy invocations of the coroutine, +// add them to the current SCC and restart the IPO pipeline to optimize the +// coroutine subfunctions we extracted before proceeding to the caller of the +// coroutine. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_COROUTINES_COROSPLIT_H +#define LLVM_TRANSFORMS_COROUTINES_COROSPLIT_H + +#include "llvm/Analysis/CGSCCPassManager.h" +#include "llvm/Analysis/LazyCallGraph.h" +#include "llvm/IR/PassManager.h" + +namespace llvm { + +struct CoroSplitPass : PassInfoMixin { + PreservedAnalyses run(LazyCallGraph::SCC &C, CGSCCAnalysisManager &AM, + LazyCallGraph &CG, CGSCCUpdateResult &UR); +}; +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_COROUTINES_COROSPLIT_H diff --git a/llvm/lib/Passes/PassBuilder.cpp b/llvm/lib/Passes/PassBuilder.cpp --- a/llvm/lib/Passes/PassBuilder.cpp +++ b/llvm/lib/Passes/PassBuilder.cpp @@ -68,6 +68,7 @@ #include "llvm/Target/TargetMachine.h" #include "llvm/Transforms/AggressiveInstCombine/AggressiveInstCombine.h" #include "llvm/Transforms/Coroutines/CoroEarly.h" +#include "llvm/Transforms/Coroutines/CoroSplit.h" #include "llvm/Transforms/IPO/AlwaysInliner.h" #include "llvm/Transforms/IPO/ArgumentPromotion.h" #include "llvm/Transforms/IPO/Attributor.h" diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def --- a/llvm/lib/Passes/PassRegistry.def +++ b/llvm/lib/Passes/PassRegistry.def @@ -108,6 +108,7 @@ CGSCC_PASS("invalidate", InvalidateAllAnalysesPass()) CGSCC_PASS("function-attrs", PostOrderFunctionAttrsPass()) CGSCC_PASS("inline", InlinerPass()) +CGSCC_PASS("coro-split", CoroSplitPass()) CGSCC_PASS("no-op-cgscc", NoOpCGSCCPass()) #undef CGSCC_PASS diff --git a/llvm/lib/Transforms/Coroutines/CoroInternal.h b/llvm/lib/Transforms/Coroutines/CoroInternal.h --- a/llvm/lib/Transforms/Coroutines/CoroInternal.h +++ b/llvm/lib/Transforms/Coroutines/CoroInternal.h @@ -22,7 +22,7 @@ class PassRegistry; void initializeCoroEarlyLegacyPass(PassRegistry &); -void initializeCoroSplitPass(PassRegistry &); +void initializeCoroSplitLegacyPass(PassRegistry &); void initializeCoroElidePass(PassRegistry &); void initializeCoroCleanupPass(PassRegistry &); diff --git a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp --- a/llvm/lib/Transforms/Coroutines/CoroSplit.cpp +++ b/llvm/lib/Transforms/Coroutines/CoroSplit.cpp @@ -5,19 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// This pass builds the coroutine frame and outlines resume and destroy parts -// of the coroutine into separate functions. -// -// We present a coroutine to an LLVM as an ordinary function with suspension -// points marked up with intrinsics. We let the optimizer party on the coroutine -// as a single function for as long as possible. Shortly before the coroutine is -// eligible to be inlined into its callers, we split up the coroutine into parts -// corresponding to an initial, resume and destroy invocations of the coroutine, -// add them to the current SCC and restart the IPO pipeline to optimize the -// coroutine subfunctions we extracted before proceeding to the caller of the -// coroutine. -//===----------------------------------------------------------------------===// +#include "llvm/Transforms/Coroutines/CoroSplit.h" #include "CoroInstr.h" #include "CoroInternal.h" #include "llvm/ADT/DenseMap.h" @@ -158,8 +147,9 @@ } // end anonymous namespace -static void maybeFreeRetconStorage(IRBuilder<> &Builder, coro::Shape &Shape, - Value *FramePtr, CallGraph *CG) { +static void maybeFreeRetconStorage(IRBuilder<> &Builder, + const coro::Shape &Shape, Value *FramePtr, + CallGraph *CG) { assert(Shape.ABI == coro::ABI::Retcon || Shape.ABI == coro::ABI::RetconOnce); if (Shape.RetconLowering.IsFrameInlineInStorage) @@ -169,9 +159,9 @@ } /// Replace a non-unwind call to llvm.coro.end. -static void replaceFallthroughCoroEnd(CoroEndInst *End, coro::Shape &Shape, - Value *FramePtr, bool InResume, - CallGraph *CG) { +static void replaceFallthroughCoroEnd(CoroEndInst *End, + const coro::Shape &Shape, Value *FramePtr, + bool InResume, CallGraph *CG) { // Start inserting right before the coro.end. IRBuilder<> Builder(End); @@ -219,7 +209,7 @@ } /// Replace an unwind call to llvm.coro.end. -static void replaceUnwindCoroEnd(CoroEndInst *End, coro::Shape &Shape, +static void replaceUnwindCoroEnd(CoroEndInst *End, const coro::Shape &Shape, Value *FramePtr, bool InResume, CallGraph *CG){ IRBuilder<> Builder(End); @@ -246,7 +236,7 @@ } } -static void replaceCoroEnd(CoroEndInst *End, coro::Shape &Shape, +static void replaceCoroEnd(CoroEndInst *End, const coro::Shape &Shape, Value *FramePtr, bool InResume, CallGraph *CG) { if (End->isUnwind()) replaceUnwindCoroEnd(End, Shape, FramePtr, InResume, CG); @@ -782,7 +772,7 @@ } /// Remove calls to llvm.coro.end in the original function. -static void removeCoroEnds(coro::Shape &Shape, CallGraph *CG) { +static void removeCoroEnds(const coro::Shape &Shape, CallGraph *CG) { for (auto End : Shape.CoroEnds) { replaceCoroEnd(End, Shape, Shape.FramePtr, /*in resume*/ false, CG); } @@ -1329,19 +1319,8 @@ }; } -static void splitCoroutine(Function &F, coro::Shape &Shape, - SmallVectorImpl &Clones) { - switch (Shape.ABI) { - case coro::ABI::Switch: - return splitSwitchCoroutine(F, Shape, Clones); - case coro::ABI::Retcon: - case coro::ABI::RetconOnce: - return splitRetconCoroutine(F, Shape, Clones); - } - llvm_unreachable("bad ABI kind"); -} - -static void splitCoroutine(Function &F, CallGraph &CG, CallGraphSCC &SCC) { +static coro::Shape splitCoroutine(Function &F, + SmallVectorImpl &Clones) { PrettyStackTraceFunction prettyStackTrace(F); // The suspend-crossing algorithm in buildCoroutineFrame get tripped @@ -1350,26 +1329,42 @@ coro::Shape Shape(F); if (!Shape.CoroBegin) - return; + return Shape; simplifySuspendPoints(Shape); buildCoroutineFrame(F, Shape); replaceFrameSize(Shape); - SmallVector Clones; - // 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); } else { - splitCoroutine(F, Shape, Clones); + switch (Shape.ABI) { + case coro::ABI::Switch: + splitSwitchCoroutine(F, Shape, Clones); + break; + case coro::ABI::Retcon: + case coro::ABI::RetconOnce: + splitRetconCoroutine(F, Shape, Clones); + break; + } } // Replace all the swifterror operations in the original function. // This invalidates SwiftErrorOps in the Shape. replaceSwiftErrorOps(F, Shape, nullptr); + return Shape; +} + +static void +updateCallGraphAfterCoroutineSplit(Function &F, const coro::Shape &Shape, + const SmallVectorImpl &Clones, + CallGraph &CG, CallGraphSCC &SCC) { + if (!Shape.CoroBegin) + return; + removeCoroEnds(Shape, &CG); postSplitCleanup(F); @@ -1377,10 +1372,32 @@ coro::updateCallGraph(F, Clones, CG, SCC); } +static void +updateCallGraphAfterCoroutineSplit(Function &F, const coro::Shape &Shape, + const SmallVectorImpl &Clones, + LazyCallGraph::SCC &C, LazyCallGraph &CG, + CGSCCUpdateResult &UR) { + if (!Shape.CoroBegin) + return; + + for (llvm::CoroEndInst *End : Shape.CoroEnds) { + auto &Context = End->getContext(); + End->replaceAllUsesWith(ConstantInt::getFalse(Context)); + End->eraseFromParent(); + } + + postSplitCleanup(F); + + for (Function *Clone : Clones) { + LazyCallGraph::Node &N = CG.get(*Clone); + N.populate(); + } +} + // When we see the coroutine the first time, we insert an indirect call to a // devirt trigger function and mark the coroutine that it is now ready for // split. -static void prepareForSplit(Function &F, CallGraph &CG) { +static llvm::CallBase *createIndirectCallToDevirtTrigger(Function &F) { Module &M = *F.getParent(); LLVMContext &Context = F.getContext(); #ifndef NDEBUG @@ -1402,19 +1419,22 @@ Lowerer.makeSubFnCall(Null, CoroSubFnInst::RestartTrigger, InsertPt); FunctionType *FnTy = FunctionType::get(Type::getVoidTy(Context), {Type::getInt8PtrTy(Context)}, false); - auto *IndirectCall = CallInst::Create(FnTy, DevirtFnAddr, Null, "", InsertPt); + return CallInst::Create(FnTy, DevirtFnAddr, Null, "", InsertPt); +} +static void addIndirectCallToCallGraph(llvm::CallBase *IndirectCall, + const Function &F, CallGraph &CG) { // Update CG graph with an indirect call we just added. CG[&F]->addCalledFunction(IndirectCall, CG.getCallsExternalNode()); } -// Make sure that there is a devirtualization trigger function that CoroSplit -// pass uses the force restart CGSCC pipeline. If devirt trigger function is not -// found, we will create one and add it to the current SCC. -static void createDevirtTriggerFunc(CallGraph &CG, CallGraphSCC &SCC) { - Module &M = CG.getModule(); +// Make sure that there is a devirtualization trigger function that the +// coro-split pass uses to force a restart of the CGSCC pipeline. If the devirt +// trigger function is not found, we create one and return it. If it is found, +// this returns null. +static Function *createDevirtTriggerFunc(Module &M) { if (M.getFunction(CORO_DEVIRT_TRIGGER_FN)) - return; + return nullptr; LLVMContext &C = M.getContext(); auto *FnTy = FunctionType::get(Type::getVoidTy(C), Type::getInt8PtrTy(C), @@ -1426,13 +1446,26 @@ auto *Entry = BasicBlock::Create(C, "entry", DevirtFn); ReturnInst::Create(C, Entry); - auto *Node = CG.getOrInsertFunction(DevirtFn); + return DevirtFn; +} +// Add the devirtualization trigger function to the call graph. +static void addFunctionToCallGraph(const Function *DevirtFn, CallGraph &CG, + CallGraphSCC &SCC) { + auto *Node = CG.getOrInsertFunction(DevirtFn); SmallVector Nodes(SCC.begin(), SCC.end()); Nodes.push_back(Node); SCC.initialize(Nodes); } +// Add the devirtualization trigger function to the (lazy) call graph. +static void addFunctionToCallGraph(Function *DevirtFn, + LazyCallGraph::SCC &C, LazyCallGraph &CG, + CGSCCUpdateResult &UR) { + LazyCallGraph::Node &N = CG.get(*DevirtFn); + N.populate(); +} + /// Replace a call to llvm.coro.prepare.retcon. static void replacePrepare(CallInst *Prepare, CallGraph &CG) { auto CastFn = Prepare->getArgOperand(0); // as an i8* @@ -1507,17 +1540,76 @@ return Changed; } -//===----------------------------------------------------------------------===// -// Top Level Driver -//===----------------------------------------------------------------------===// +static bool declaresCoroSplitIntrinsics(const Module &M) { + return coro::declaresIntrinsics( + M, {"llvm.coro.begin", "llvm.coro.prepare.retcon"}); +} + +PreservedAnalyses CoroSplitPass::run(LazyCallGraph::SCC &C, + CGSCCAnalysisManager &AM, + LazyCallGraph &CG, CGSCCUpdateResult &UR) { + // NB: One invariant of a valid LazyCallGraph::SCC is that it must contain a + // non-zero number of nodes, so we assume that here and grab the first + // node's function's module. + Module &M = *C.begin()->getFunction().getParent(); + if (!declaresCoroSplitIntrinsics(M)) + return PreservedAnalyses::all(); + + // Check for uses of llvm.coro.prepare.retcon. + const auto *PrepareFn = M.getFunction("llvm.coro.prepare.retcon"); + if (PrepareFn && PrepareFn->use_empty()) + PrepareFn = nullptr; + + // Find coroutines for processing. + SmallVector Coroutines; + for (const LazyCallGraph::Node &CGN : C) { + auto &F = CGN.getFunction(); + if (F.hasFnAttribute(CORO_PRESPLIT_ATTR)) + Coroutines.push_back(&F); + } + + if (Coroutines.empty() && !PrepareFn) + return PreservedAnalyses::all(); + + if (Coroutines.empty()) + llvm_unreachable("experimental pass manager cannot yet handle " + "'llvm.coro.prepare.retcon'"); + + Function *DevirtFn = createDevirtTriggerFunc(M); + if (DevirtFn) + addFunctionToCallGraph(DevirtFn, C, CG, UR); + + // Split all the coroutines. + for (Function *F : Coroutines) { + Attribute Attr = F->getFnAttribute(CORO_PRESPLIT_ATTR); + StringRef Value = Attr.getValueAsString(); + LLVM_DEBUG(dbgs() << "CoroSplit: Processing coroutine '" << F->getName() + << "' state: " << Value << "\n"); + if (Value == UNPREPARED_FOR_SPLIT) { + createIndirectCallToDevirtTrigger(*F); + continue; + } + F->removeFnAttr(CORO_PRESPLIT_ATTR); + + SmallVector Clones; + const coro::Shape Shape = splitCoroutine(*F, Clones); + updateCallGraphAfterCoroutineSplit(*F, Shape, Clones, C, CG, UR); + } + + if (PrepareFn) + llvm_unreachable("experimental pass manager cannot yet handle " + "'llvm.coro.prepare.retcon'"); + + return PreservedAnalyses::none(); +} namespace { -struct CoroSplit : public CallGraphSCCPass { +struct CoroSplitLegacy : public CallGraphSCCPass { static char ID; // Pass identification, replacement for typeid - CoroSplit() : CallGraphSCCPass(ID) { - initializeCoroSplitPass(*PassRegistry::getPassRegistry()); + CoroSplitLegacy() : CallGraphSCCPass(ID) { + initializeCoroSplitLegacyPass(*PassRegistry::getPassRegistry()); } bool Run = false; @@ -1525,9 +1617,7 @@ // A coroutine is identified by the presence of coro.begin intrinsic, if // we don't have any, this pass has nothing to do. bool doInitialization(CallGraph &CG) override { - Run = coro::declaresIntrinsics(CG.getModule(), - {"llvm.coro.begin", - "llvm.coro.prepare.retcon"}); + Run = declaresCoroSplitIntrinsics(CG.getModule()); return CallGraphSCCPass::doInitialization(CG); } @@ -1556,7 +1646,9 @@ if (Coroutines.empty()) return replaceAllPrepares(PrepareFn, CG); - createDevirtTriggerFunc(CG, SCC); + const Function *DevirtFn = createDevirtTriggerFunc(CG.getModule()); + if (DevirtFn) + addFunctionToCallGraph(DevirtFn, CG, SCC); // Split all the coroutines. for (Function *F : Coroutines) { @@ -1565,11 +1657,15 @@ LLVM_DEBUG(dbgs() << "CoroSplit: Processing coroutine '" << F->getName() << "' state: " << Value << "\n"); if (Value == UNPREPARED_FOR_SPLIT) { - prepareForSplit(*F, CG); + auto *IndirectCall = createIndirectCallToDevirtTrigger(*F); + addIndirectCallToCallGraph(IndirectCall, *F, CG); continue; } F->removeFnAttr(CORO_PRESPLIT_ATTR); - splitCoroutine(*F, CG, SCC); + + SmallVector Clones; + const coro::Shape Shape = splitCoroutine(*F, Clones); + updateCallGraphAfterCoroutineSplit(*F, Shape, Clones, CG, SCC); } if (PrepareFn) @@ -1587,16 +1683,16 @@ } // end anonymous namespace -char CoroSplit::ID = 0; +char CoroSplitLegacy::ID = 0; INITIALIZE_PASS_BEGIN( - CoroSplit, "coro-split", + CoroSplitLegacy, "coro-split", "Split coroutine into a set of functions driving its state machine", false, false) INITIALIZE_PASS_DEPENDENCY(CallGraphWrapperPass) INITIALIZE_PASS_END( - CoroSplit, "coro-split", + CoroSplitLegacy, "coro-split", "Split coroutine into a set of functions driving its state machine", false, false) -Pass *llvm::createCoroSplitPass() { return new CoroSplit(); } +Pass *llvm::createCoroSplitLegacyPass() { return new CoroSplitLegacy(); } diff --git a/llvm/lib/Transforms/Coroutines/Coroutines.cpp b/llvm/lib/Transforms/Coroutines/Coroutines.cpp --- a/llvm/lib/Transforms/Coroutines/Coroutines.cpp +++ b/llvm/lib/Transforms/Coroutines/Coroutines.cpp @@ -44,14 +44,14 @@ void llvm::initializeCoroutines(PassRegistry &Registry) { initializeCoroEarlyLegacyPass(Registry); - initializeCoroSplitPass(Registry); + initializeCoroSplitLegacyPass(Registry); initializeCoroElidePass(Registry); initializeCoroCleanupPass(Registry); } static void addCoroutineOpt0Passes(const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) { - PM.add(createCoroSplitPass()); + PM.add(createCoroSplitLegacyPass()); PM.add(createCoroElidePass()); PM.add(createBarrierNoopPass()); @@ -70,7 +70,7 @@ static void addCoroutineSCCPasses(const PassManagerBuilder &Builder, legacy::PassManagerBase &PM) { - PM.add(createCoroSplitPass()); + PM.add(createCoroSplitLegacyPass()); } static void addCoroutineOptimizerLastPasses(const PassManagerBuilder &Builder, @@ -639,7 +639,7 @@ } void LLVMAddCoroSplitPass(LLVMPassManagerRef PM) { - unwrap(PM)->add(createCoroSplitPass()); + unwrap(PM)->add(createCoroSplitLegacyPass()); } void LLVMAddCoroElidePass(LLVMPassManagerRef PM) { diff --git a/llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll rename from llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll rename to llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll --- a/llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll +++ b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O0.ll @@ -1,29 +1,7 @@ ; Check that we can handle the case when both alloc function and ; the user body consume the same argument. ; RUN: opt < %s -coro-split -S | FileCheck %s - -; using this directly (as it would happen under -O2) -define i8* @f_direct(i64 %this) "coroutine.presplit"="1" { -entry: - %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) - %size = call i32 @llvm.coro.size.i32() - %alloc = call i8* @myAlloc(i64 %this, i32 %size) - %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) - %0 = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %0, label %suspend [i8 0, label %resume - i8 1, label %cleanup] -resume: - call void @print2(i64 %this) - br label %cleanup - -cleanup: - %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) - call void @free(i8* %mem) - br label %suspend -suspend: - call i1 @llvm.coro.end(i8* %hdl, i1 0) - ret i8* %hdl -} +; RUN: opt < %s -passes=coro-split -S | FileCheck %s ; using copy of this (as it would happen under -O0) define i8* @f_copy(i64 %this_arg) "coroutine.presplit"="1" { @@ -52,27 +30,14 @@ } ; See if %this was added to the frame -; CHECK: %f_direct.Frame = type { void (%f_direct.Frame*)*, void (%f_direct.Frame*)*, i1, i1, i64 } ; CHECK: %f_copy.Frame = type { void (%f_copy.Frame*)*, void (%f_copy.Frame*)*, i1, i1, i64 } -; See that %this is spilled into the frame -; CHECK-LABEL: define i8* @f_direct(i64 %this) -; CHECK: %this.spill.addr = getelementptr inbounds %f_direct.Frame, %f_direct.Frame* %FramePtr, i32 0, i32 4 -; CHECK: store i64 %this, i64* %this.spill.addr -; CHECK: ret i8* %hdl - ; See that %this is spilled into the frame ; CHECK-LABEL: define i8* @f_copy(i64 %this_arg) ; CHECK: %this.spill.addr = getelementptr inbounds %f_copy.Frame, %f_copy.Frame* %FramePtr, i32 0, i32 4 ; CHECK: store i64 %this_arg, i64* %this.spill.addr ; CHECK: ret i8* %hdl -; See that %this was loaded from the frame -; CHECK-LABEL: @f_direct.resume( -; CHECK: %this.reload = load i64, i64* %this.reload.addr -; CHECK: call void @print2(i64 %this.reload) -; CHECK: ret void - ; See that %this was loaded from the frame ; CHECK-LABEL: @f_copy.resume( ; CHECK: %this.reload = load i64, i64* %this.reload.addr diff --git a/llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll rename from llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll rename to llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll --- a/llvm/test/Transforms/Coroutines/coro-alloc-with-param.ll +++ b/llvm/test/Transforms/Coroutines/coro-alloc-with-param-O2.ll @@ -1,6 +1,7 @@ ; Check that we can handle the case when both alloc function and ; the user body consume the same argument. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s ; using this directly (as it would happen under -O2) define i8* @f_direct(i64 %this) "coroutine.presplit"="1" { @@ -25,35 +26,8 @@ ret i8* %hdl } -; using copy of this (as it would happen under -O0) -define i8* @f_copy(i64 %this_arg) "coroutine.presplit"="1" { -entry: - %this.addr = alloca i64 - store i64 %this_arg, i64* %this.addr - %this = load i64, i64* %this.addr - %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) - %size = call i32 @llvm.coro.size.i32() - %alloc = call i8* @myAlloc(i64 %this, i32 %size) - %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) - %0 = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %0, label %suspend [i8 0, label %resume - i8 1, label %cleanup] -resume: - call void @print2(i64 %this) - br label %cleanup - -cleanup: - %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) - call void @free(i8* %mem) - br label %suspend -suspend: - call i1 @llvm.coro.end(i8* %hdl, i1 0) - ret i8* %hdl -} - ; See if %this was added to the frame ; CHECK: %f_direct.Frame = type { void (%f_direct.Frame*)*, void (%f_direct.Frame*)*, i1, i1, i64 } -; CHECK: %f_copy.Frame = type { void (%f_copy.Frame*)*, void (%f_copy.Frame*)*, i1, i1, i64 } ; See that %this is spilled into the frame ; CHECK-LABEL: define i8* @f_direct(i64 %this) @@ -61,24 +35,12 @@ ; CHECK: store i64 %this, i64* %this.spill.addr ; CHECK: ret i8* %hdl -; See that %this is spilled into the frame -; CHECK-LABEL: define i8* @f_copy(i64 %this_arg) -; CHECK: %this.spill.addr = getelementptr inbounds %f_copy.Frame, %f_copy.Frame* %FramePtr, i32 0, i32 4 -; CHECK: store i64 %this_arg, i64* %this.spill.addr -; CHECK: ret i8* %hdl - ; See that %this was loaded from the frame ; CHECK-LABEL: @f_direct.resume( ; CHECK: %this.reload = load i64, i64* %this.reload.addr ; CHECK: call void @print2(i64 %this.reload) ; CHECK: ret void -; See that %this was loaded from the frame -; CHECK-LABEL: @f_copy.resume( -; CHECK: %this.reload = load i64, i64* %this.reload.addr -; CHECK: call void @print2(i64 %this.reload) -; CHECK: ret void - declare i8* @llvm.coro.free(token, i8*) declare i32 @llvm.coro.size.i32() declare i8 @llvm.coro.suspend(token, i1) diff --git a/llvm/test/Transforms/Coroutines/coro-catchswitch.ll b/llvm/test/Transforms/Coroutines/coro-catchswitch.ll --- a/llvm/test/Transforms/Coroutines/coro-catchswitch.ll +++ b/llvm/test/Transforms/Coroutines/coro-catchswitch.ll @@ -1,5 +1,6 @@ ; Verifies that we can insert the spill for a PHI preceding the catchswitch ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s target datalayout = "e-m:x-p:32:32-i64:64-f80:32-n8:16:32-a:0:32-S32" target triple = "i686-pc-windows-msvc" diff --git a/llvm/test/Transforms/Coroutines/coro-debug.ll b/llvm/test/Transforms/Coroutines/coro-debug.ll --- a/llvm/test/Transforms/Coroutines/coro-debug.ll +++ b/llvm/test/Transforms/Coroutines/coro-debug.ll @@ -1,5 +1,6 @@ ; Tests that debug information is sane after coro-split ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s source_filename = "simple-repro.c" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" diff --git a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-00.ll @@ -0,0 +1,98 @@ +; Check that we can handle edge splits leading into a landingpad +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK-LABEL: define internal fastcc void @f.resume( +define void @f(i1 %cond) "coroutine.presplit"="1" personality i32 0 { +entry: + %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) + %size = tail call i64 @llvm.coro.size.i64() + %alloc = call i8* @malloc(i64 %size) + %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) + %sp = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %sp, label %coro.ret [ + i8 0, label %resume + i8 1, label %cleanup + ] + +resume: + br i1 %cond, label %invoke1, label %invoke2 + +invoke1: + invoke void @may_throw1() + to label %unreach unwind label %pad.with.phi +invoke2: + invoke void @may_throw2() + to label %unreach unwind label %pad.with.phi + +; Verify that we cloned landing pad on every edge and inserted a reload of the spilled value + +; CHECK: pad.with.phi.from.invoke2: +; CHECK: %0 = landingpad { i8*, i32 } +; CHECK: catch i8* null +; CHECK: br label %pad.with.phi + +; CHECK: pad.with.phi.from.invoke1: +; CHECK: %1 = landingpad { i8*, i32 } +; CHECK: catch i8* null +; CHECK: br label %pad.with.phi + +; CHECK: pad.with.phi: +; CHECK: %val = phi i32 [ 0, %pad.with.phi.from.invoke1 ], [ 1, %pad.with.phi.from.invoke2 ] +; CHECK: %lp = phi { i8*, i32 } [ %0, %pad.with.phi.from.invoke2 ], [ %1, %pad.with.phi.from.invoke1 ] +; CHECK: %exn = extractvalue { i8*, i32 } %lp, 0 +; CHECK: call i8* @__cxa_begin_catch(i8* %exn) +; CHECK: call void @use_val(i32 %val) +; CHECK: call void @__cxa_end_catch() +; CHECK: call void @free(i8* %vFrame) +; CHECK: ret void + +pad.with.phi: + %val = phi i32 [ 0, %invoke1 ], [ 1, %invoke2 ] + %lp = landingpad { i8*, i32 } + catch i8* null + %exn = extractvalue { i8*, i32 } %lp, 0 + call i8* @__cxa_begin_catch(i8* %exn) + call void @use_val(i32 %val) + call void @__cxa_end_catch() + br label %cleanup + +cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend + %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) + call void @free(i8* %mem) + br label %coro.ret + +coro.ret: + call i1 @llvm.coro.end(i8* null, i1 false) + ret void + +unreach: + unreachable +} + +; Function Attrs: argmemonly nounwind readonly +declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) +declare noalias i8* @malloc(i64) +declare i64 @llvm.coro.size.i64() +declare i8* @llvm.coro.begin(token, i8* writeonly) + +; Function Attrs: nounwind +declare token @llvm.coro.save(i8*) +declare i8 @llvm.coro.suspend(token, i1) + +; Function Attrs: argmemonly nounwind +declare void @may_throw1() +declare void @may_throw2() + +declare i8* @__cxa_begin_catch(i8*) + +declare void @use_val(i32) +declare void @__cxa_end_catch() + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(i8*, i1) +declare void @free(i8*) +declare i8* @llvm.coro.free(token, i8* nocapture readonly) diff --git a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-01.ll @@ -0,0 +1,92 @@ +; Check that we can handle edge splits leading into a landingpad +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK-LABEL: define internal fastcc void @g.resume( +define void @g(i1 %cond, i32 %x, i32 %y) "coroutine.presplit"="1" personality i32 0 { +entry: + %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) + %size = tail call i64 @llvm.coro.size.i64() + %alloc = call i8* @malloc(i64 %size) + %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) + %sp = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %sp, label %coro.ret [ + i8 0, label %resume + i8 1, label %cleanup + ] + +resume: + br i1 %cond, label %invoke1, label %invoke2 + +invoke1: + invoke void @may_throw1() + to label %unreach unwind label %pad.with.phi +invoke2: + invoke void @may_throw2() + to label %unreach unwind label %pad.with.phi + +; Verify that we created cleanuppads on every edge and inserted a reload of the spilled value + +; CHECK: pad.with.phi.from.invoke2: +; CHECK: %0 = cleanuppad within none [] +; CHECK: %y.reload.addr = getelementptr inbounds %g.Frame, %g.Frame* %FramePtr, i32 0, i32 6 +; CHECK: %y.reload = load i32, i32* %y.reload.addr +; CHECK: cleanupret from %0 unwind label %pad.with.phi + +; CHECK: pad.with.phi.from.invoke1: +; CHECK: %1 = cleanuppad within none [] +; CHECK: %x.reload.addr = getelementptr inbounds %g.Frame, %g.Frame* %FramePtr, i32 0, i32 5 +; CHECK: %x.reload = load i32, i32* %x.reload.addr +; CHECK: cleanupret from %1 unwind label %pad.with.phi + +; CHECK: pad.with.phi: +; CHECK: %val = phi i32 [ %x.reload, %pad.with.phi.from.invoke1 ], [ %y.reload, %pad.with.phi.from.invoke2 ] +; CHECK: %tok = cleanuppad within none [] +; CHECK: call void @use_val(i32 %val) +; CHECK: cleanupret from %tok unwind to caller + +pad.with.phi: + %val = phi i32 [ %x, %invoke1 ], [ %y, %invoke2 ] + %tok = cleanuppad within none [] + call void @use_val(i32 %val) + cleanupret from %tok unwind to caller + +cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend + %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) + call void @free(i8* %mem) + br label %coro.ret + +coro.ret: + call i1 @llvm.coro.end(i8* null, i1 false) + ret void + +unreach: + unreachable +} + +; Function Attrs: argmemonly nounwind readonly +declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) +declare noalias i8* @malloc(i64) +declare i64 @llvm.coro.size.i64() +declare i8* @llvm.coro.begin(token, i8* writeonly) + +; Function Attrs: nounwind +declare token @llvm.coro.save(i8*) +declare i8 @llvm.coro.suspend(token, i1) + +; Function Attrs: argmemonly nounwind +declare void @may_throw1() +declare void @may_throw2() + +declare i8* @__cxa_begin_catch(i8*) + +declare void @use_val(i32) +declare void @__cxa_end_catch() + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(i8*, i1) +declare void @free(i8*) +declare i8* @llvm.coro.free(token, i8* nocapture readonly) diff --git a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split-02.ll @@ -0,0 +1,89 @@ +; Check that we can handle edge splits leading into a landingpad +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s + +target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-unknown-linux-gnu" + +; CHECK-LABEL: define internal fastcc void @h.resume( +define void @h(i1 %cond, i32 %x, i32 %y) "coroutine.presplit"="1" personality i32 0 { +entry: + %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) + %size = tail call i64 @llvm.coro.size.i64() + %alloc = call i8* @malloc(i64 %size) + %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) + %sp = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %sp, label %coro.ret [ + i8 0, label %resume + i8 1, label %cleanup + ] + +resume: + br i1 %cond, label %invoke1, label %invoke2 + +invoke1: + invoke void @may_throw1() + to label %coro.ret unwind label %pad.with.phi +invoke2: + invoke void @may_throw2() + to label %coro.ret unwind label %pad.with.phi + +; Verify that we created cleanuppads on every edge and inserted a reload of the spilled value + +; CHECK: pad.with.phi.from.invoke2: +; CHECK: %0 = cleanuppad within none [] +; CHECK: %y.reload.addr = getelementptr inbounds %h.Frame, %h.Frame* %FramePtr, i32 0, i32 6 +; CHECK: %y.reload = load i32, i32* %y.reload.addr +; CHECK: cleanupret from %0 unwind label %pad.with.phi + +; CHECK: pad.with.phi.from.invoke1: +; CHECK: %1 = cleanuppad within none [] +; CHECK: %x.reload.addr = getelementptr inbounds %h.Frame, %h.Frame* %FramePtr, i32 0, i32 5 +; CHECK: %x.reload = load i32, i32* %x.reload.addr +; CHECK: cleanupret from %1 unwind label %pad.with.phi + +; CHECK: pad.with.phi: +; CHECK: %val = phi i32 [ %x.reload, %pad.with.phi.from.invoke1 ], [ %y.reload, %pad.with.phi.from.invoke2 ] +; CHECK: %switch = catchswitch within none [label %catch] unwind to caller +pad.with.phi: + %val = phi i32 [ %x, %invoke1 ], [ %y, %invoke2 ] + %switch = catchswitch within none [label %catch] unwind to caller + +catch: ; preds = %catch.dispatch + %pad = catchpad within %switch [i8* null, i32 64, i8* null] + call void @use_val(i32 %val) + catchret from %pad to label %coro.ret + +cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend + %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) + call void @free(i8* %mem) + br label %coro.ret + +coro.ret: + call i1 @llvm.coro.end(i8* null, i1 false) + ret void +} + +; Function Attrs: argmemonly nounwind readonly +declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) +declare noalias i8* @malloc(i64) +declare i64 @llvm.coro.size.i64() +declare i8* @llvm.coro.begin(token, i8* writeonly) + +; Function Attrs: nounwind +declare token @llvm.coro.save(i8*) +declare i8 @llvm.coro.suspend(token, i1) + +; Function Attrs: argmemonly nounwind +declare void @may_throw1() +declare void @may_throw2() + +declare i8* @__cxa_begin_catch(i8*) + +declare void @use_val(i32) +declare void @__cxa_end_catch() + +; Function Attrs: nounwind +declare i1 @llvm.coro.end(i8*, i1) +declare void @free(i8*) +declare i8* @llvm.coro.free(token, i8* nocapture readonly) diff --git a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split.ll b/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split.ll deleted file mode 100644 --- a/llvm/test/Transforms/Coroutines/coro-eh-aware-edge-split.ll +++ /dev/null @@ -1,218 +0,0 @@ -; Check that we can handle edge splits leading into a landingpad -; RUN: opt < %s -coro-split -S | FileCheck %s - -target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" -target triple = "x86_64-unknown-linux-gnu" - -; CHECK-LABEL: define internal fastcc void @f.resume( -define void @f(i1 %cond) "coroutine.presplit"="1" personality i32 0 { -entry: - %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) - %size = tail call i64 @llvm.coro.size.i64() - %alloc = call i8* @malloc(i64 %size) - %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) - %sp = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %sp, label %coro.ret [ - i8 0, label %resume - i8 1, label %cleanup - ] - -resume: - br i1 %cond, label %invoke1, label %invoke2 - -invoke1: - invoke void @may_throw1() - to label %unreach unwind label %pad.with.phi -invoke2: - invoke void @may_throw2() - to label %unreach unwind label %pad.with.phi - -; Verify that we cloned landing pad on every edge and inserted a reload of the spilled value - -; CHECK: pad.with.phi.from.invoke2: -; CHECK: %0 = landingpad { i8*, i32 } -; CHECK: catch i8* null -; CHECK: br label %pad.with.phi - -; CHECK: pad.with.phi.from.invoke1: -; CHECK: %1 = landingpad { i8*, i32 } -; CHECK: catch i8* null -; CHECK: br label %pad.with.phi - -; CHECK: pad.with.phi: -; CHECK: %val = phi i32 [ 0, %pad.with.phi.from.invoke1 ], [ 1, %pad.with.phi.from.invoke2 ] -; CHECK: %lp = phi { i8*, i32 } [ %0, %pad.with.phi.from.invoke2 ], [ %1, %pad.with.phi.from.invoke1 ] -; CHECK: %exn = extractvalue { i8*, i32 } %lp, 0 -; CHECK: call i8* @__cxa_begin_catch(i8* %exn) -; CHECK: call void @use_val(i32 %val) -; CHECK: call void @__cxa_end_catch() -; CHECK: call void @free(i8* %vFrame) -; CHECK: ret void - -pad.with.phi: - %val = phi i32 [ 0, %invoke1 ], [ 1, %invoke2 ] - %lp = landingpad { i8*, i32 } - catch i8* null - %exn = extractvalue { i8*, i32 } %lp, 0 - call i8* @__cxa_begin_catch(i8* %exn) - call void @use_val(i32 %val) - call void @__cxa_end_catch() - br label %cleanup - -cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend - %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) - call void @free(i8* %mem) - br label %coro.ret - -coro.ret: - call i1 @llvm.coro.end(i8* null, i1 false) - ret void - -unreach: - unreachable -} - -; CHECK-LABEL: define internal fastcc void @g.resume( -define void @g(i1 %cond, i32 %x, i32 %y) "coroutine.presplit"="1" personality i32 0 { -entry: - %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) - %size = tail call i64 @llvm.coro.size.i64() - %alloc = call i8* @malloc(i64 %size) - %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) - %sp = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %sp, label %coro.ret [ - i8 0, label %resume - i8 1, label %cleanup - ] - -resume: - br i1 %cond, label %invoke1, label %invoke2 - -invoke1: - invoke void @may_throw1() - to label %unreach unwind label %pad.with.phi -invoke2: - invoke void @may_throw2() - to label %unreach unwind label %pad.with.phi - -; Verify that we created cleanuppads on every edge and inserted a reload of the spilled value - -; CHECK: pad.with.phi.from.invoke2: -; CHECK: %0 = cleanuppad within none [] -; CHECK: %y.reload.addr = getelementptr inbounds %g.Frame, %g.Frame* %FramePtr, i32 0, i32 6 -; CHECK: %y.reload = load i32, i32* %y.reload.addr -; CHECK: cleanupret from %0 unwind label %pad.with.phi - -; CHECK: pad.with.phi.from.invoke1: -; CHECK: %1 = cleanuppad within none [] -; CHECK: %x.reload.addr = getelementptr inbounds %g.Frame, %g.Frame* %FramePtr, i32 0, i32 5 -; CHECK: %x.reload = load i32, i32* %x.reload.addr -; CHECK: cleanupret from %1 unwind label %pad.with.phi - -; CHECK: pad.with.phi: -; CHECK: %val = phi i32 [ %x.reload, %pad.with.phi.from.invoke1 ], [ %y.reload, %pad.with.phi.from.invoke2 ] -; CHECK: %tok = cleanuppad within none [] -; CHECK: call void @use_val(i32 %val) -; CHECK: cleanupret from %tok unwind to caller - -pad.with.phi: - %val = phi i32 [ %x, %invoke1 ], [ %y, %invoke2 ] - %tok = cleanuppad within none [] - call void @use_val(i32 %val) - cleanupret from %tok unwind to caller - -cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend - %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) - call void @free(i8* %mem) - br label %coro.ret - -coro.ret: - call i1 @llvm.coro.end(i8* null, i1 false) - ret void - -unreach: - unreachable -} - -; CHECK-LABEL: define internal fastcc void @h.resume( -define void @h(i1 %cond, i32 %x, i32 %y) "coroutine.presplit"="1" personality i32 0 { -entry: - %id = call token @llvm.coro.id(i32 16, i8* null, i8* null, i8* null) - %size = tail call i64 @llvm.coro.size.i64() - %alloc = call i8* @malloc(i64 %size) - %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) - %sp = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %sp, label %coro.ret [ - i8 0, label %resume - i8 1, label %cleanup - ] - -resume: - br i1 %cond, label %invoke1, label %invoke2 - -invoke1: - invoke void @may_throw1() - to label %coro.ret unwind label %pad.with.phi -invoke2: - invoke void @may_throw2() - to label %coro.ret unwind label %pad.with.phi - -; Verify that we created cleanuppads on every edge and inserted a reload of the spilled value - -; CHECK: pad.with.phi.from.invoke2: -; CHECK: %0 = cleanuppad within none [] -; CHECK: %y.reload.addr = getelementptr inbounds %h.Frame, %h.Frame* %FramePtr, i32 0, i32 6 -; CHECK: %y.reload = load i32, i32* %y.reload.addr -; CHECK: cleanupret from %0 unwind label %pad.with.phi - -; CHECK: pad.with.phi.from.invoke1: -; CHECK: %1 = cleanuppad within none [] -; CHECK: %x.reload.addr = getelementptr inbounds %h.Frame, %h.Frame* %FramePtr, i32 0, i32 5 -; CHECK: %x.reload = load i32, i32* %x.reload.addr -; CHECK: cleanupret from %1 unwind label %pad.with.phi - -; CHECK: pad.with.phi: -; CHECK: %val = phi i32 [ %x.reload, %pad.with.phi.from.invoke1 ], [ %y.reload, %pad.with.phi.from.invoke2 ] -; CHECK: %switch = catchswitch within none [label %catch] unwind to caller -pad.with.phi: - %val = phi i32 [ %x, %invoke1 ], [ %y, %invoke2 ] - %switch = catchswitch within none [label %catch] unwind to caller - -catch: ; preds = %catch.dispatch - %pad = catchpad within %switch [i8* null, i32 64, i8* null] - call void @use_val(i32 %val) - catchret from %pad to label %coro.ret - -cleanup: ; preds = %invoke.cont15, %if.else, %if.then, %ehcleanup21, %init.suspend - %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) - call void @free(i8* %mem) - br label %coro.ret - -coro.ret: - call i1 @llvm.coro.end(i8* null, i1 false) - ret void -} - -; Function Attrs: argmemonly nounwind readonly -declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) -declare noalias i8* @malloc(i64) -declare i64 @llvm.coro.size.i64() -declare i8* @llvm.coro.begin(token, i8* writeonly) - -; Function Attrs: nounwind -declare token @llvm.coro.save(i8*) -declare i8 @llvm.coro.suspend(token, i1) - -; Function Attrs: argmemonly nounwind -declare void @may_throw1() -declare void @may_throw2() - -declare i8* @__cxa_begin_catch(i8*) - -declare void @use_val(i32) -declare void @__cxa_end_catch() - -; Function Attrs: nounwind -declare i1 @llvm.coro.end(i8*, i1) -declare void @free(i8*) -declare i8* @llvm.coro.free(token, i8* nocapture readonly) diff --git a/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll b/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll --- a/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll +++ b/llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll @@ -1,5 +1,6 @@ ; Check that we can handle spills of array allocas ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s declare void @consume.double.ptr(double*) declare void @consume.i32.ptr(i32*) diff --git a/llvm/test/Transforms/Coroutines/coro-frame-unreachable.ll b/llvm/test/Transforms/Coroutines/coro-frame-unreachable.ll --- a/llvm/test/Transforms/Coroutines/coro-frame-unreachable.ll +++ b/llvm/test/Transforms/Coroutines/coro-frame-unreachable.ll @@ -1,5 +1,6 @@ ; Check that coro-split doesn't choke on intrinsics in unreachable blocks ; RUN: opt < %s -coro-split -S +; RUN: opt < %s -passes=coro-split -S define i8* @f(i1 %arg) "coroutine.presplit"="1" personality i32 0 { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-frame.ll b/llvm/test/Transforms/Coroutines/coro-frame.ll --- a/llvm/test/Transforms/Coroutines/coro-frame.ll +++ b/llvm/test/Transforms/Coroutines/coro-frame.ll @@ -1,5 +1,6 @@ ; Check that we can handle spills of the result of the invoke instruction ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f(i64 %this) "coroutine.presplit"="1" personality i32 0 { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-materialize.ll b/llvm/test/Transforms/Coroutines/coro-materialize.ll --- a/llvm/test/Transforms/Coroutines/coro-materialize.ll +++ b/llvm/test/Transforms/Coroutines/coro-materialize.ll @@ -1,5 +1,6 @@ ; Verifies that we materialize instruction across suspend points ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f(i32 %n) "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-padding.ll b/llvm/test/Transforms/Coroutines/coro-padding.ll --- a/llvm/test/Transforms/Coroutines/coro-padding.ll +++ b/llvm/test/Transforms/Coroutines/coro-padding.ll @@ -1,6 +1,7 @@ ; Check that we will insert the correct padding if natural alignment of the ; spilled data does not match the alignment specified in alloca instruction. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s %PackedStruct = type <{ i64 }> diff --git a/llvm/test/Transforms/Coroutines/coro-param-copy.ll b/llvm/test/Transforms/Coroutines/coro-param-copy.ll --- a/llvm/test/Transforms/Coroutines/coro-param-copy.ll +++ b/llvm/test/Transforms/Coroutines/coro-param-copy.ll @@ -1,6 +1,7 @@ ; Check that we create copy the data from the alloca into the coroutine ; frame slot if it was written to. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f() "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-spill-after-phi.ll b/llvm/test/Transforms/Coroutines/coro-spill-after-phi.ll --- a/llvm/test/Transforms/Coroutines/coro-spill-after-phi.ll +++ b/llvm/test/Transforms/Coroutines/coro-spill-after-phi.ll @@ -1,5 +1,6 @@ ; Verifies that we insert spills of PHI instruction _after) all PHI Nodes ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f(i1 %n) "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll b/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll --- a/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll +++ b/llvm/test/Transforms/Coroutines/coro-spill-corobegin.ll @@ -1,5 +1,6 @@ ; Check that we can spills coro.begin from an inlined inner coroutine. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s %g.Frame = type { void (%g.Frame*)*, void (%g.Frame*)*, i32, i1, i32 } diff --git a/llvm/test/Transforms/Coroutines/coro-split-00.ll b/llvm/test/Transforms/Coroutines/coro-split-00.ll --- a/llvm/test/Transforms/Coroutines/coro-split-00.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-00.ll @@ -1,5 +1,6 @@ ; Tests that coro-split pass splits the coroutine into f, f.resume and f.destroy ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f() "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-split-02.ll b/llvm/test/Transforms/Coroutines/coro-split-02.ll --- a/llvm/test/Transforms/Coroutines/coro-split-02.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-02.ll @@ -2,6 +2,7 @@ ; a value produces between coro.save and coro.suspend (%Result.i19) ; and checks whether stray coro.saves are properly removed ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s %"struct.std::coroutine_handle" = type { i8* } %"struct.std::coroutine_handle.0" = type { %"struct.std::coroutine_handle" } diff --git a/llvm/test/Transforms/Coroutines/coro-split-alloc.ll b/llvm/test/Transforms/Coroutines/coro-split-alloc.ll --- a/llvm/test/Transforms/Coroutines/coro-split-alloc.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-alloc.ll @@ -1,5 +1,6 @@ ; Tests that coro-split passes initialized values to coroutine frame allocator. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f(i32 %argument) "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-split-dbg.ll b/llvm/test/Transforms/Coroutines/coro-split-dbg.ll --- a/llvm/test/Transforms/Coroutines/coro-split-dbg.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-dbg.ll @@ -1,6 +1,7 @@ ; Make sure that coro-split correctly deals with debug information. ; The test here is simply that it does not result in bad IR that will crash opt. ; RUN: opt < %s -coro-split -disable-output +; RUN: opt < %s -passes=coro-split -disable-output source_filename = "coro.c" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-unknown-linux-gnu" diff --git a/llvm/test/Transforms/Coroutines/coro-split-eh.ll b/llvm/test/Transforms/Coroutines/coro-split-eh-00.ll rename from llvm/test/Transforms/Coroutines/coro-split-eh.ll rename to llvm/test/Transforms/Coroutines/coro-split-eh-00.ll --- a/llvm/test/Transforms/Coroutines/coro-split-eh.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-eh-00.ll @@ -1,6 +1,7 @@ ; Tests that coro-split removes cleanup code after coro.end in resume functions ; and retains it in the start function. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f(i1 %val) "coroutine.presplit"="1" personality i32 3 { entry: @@ -53,52 +54,6 @@ ; CHECK-NEXT: call void @print(i32 3) ; CHECK-NEXT: resume { i8*, i32 } %lpval -define i8* @f2(i1 %val) "coroutine.presplit"="1" personality i32 4 { -entry: - %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) - %hdl = call i8* @llvm.coro.begin(token %id, i8* null) - call void @print(i32 0) - br i1 %val, label %resume, label %susp - -susp: - %0 = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %0, label %suspend [i8 0, label %resume - i8 1, label %suspend] -resume: - invoke void @print(i32 1) to label %suspend unwind label %lpad - -suspend: - call i1 @llvm.coro.end(i8* %hdl, i1 0) - call void @print(i32 0) ; should not be present in f.resume - ret i8* %hdl - -lpad: - %tok = cleanuppad within none [] - call void @print(i32 2) - %unused = call i1 @llvm.coro.end(i8* null, i1 true) [ "funclet"(token %tok) ] - cleanupret from %tok unwind label %cleanup.cont - -cleanup.cont: - %tok2 = cleanuppad within none [] - call void @print(i32 3) ; should not be present in f.resume - cleanupret from %tok2 unwind to caller -} - -; Verify that start function contains both print calls the one before and after coro.end -; CHECK-LABEL: define i8* @f2( -; CHECK: invoke void @print(i32 1) -; CHECK: to label %AfterCoroEnd unwind label %lpad - -; CHECK: AfterCoroEnd: -; CHECK: call void @print(i32 0) -; CHECK: ret i8* %hdl - -; CHECK: lpad: -; CHECK-NEXT: %tok = cleanuppad within none [] -; CHECK-NEXT: call void @print(i32 2) -; CHECK-NEXT: call void @print(i32 3) -; CHECK-NEXT: cleanupret from %tok unwind to caller - ; VERIFY Resume Parts ; Verify that resume function does not contains both print calls appearing after coro.end @@ -115,19 +70,6 @@ ; CHECK-NEXT: call void @print(i32 2) ; CHECK-NEXT: resume { i8*, i32 } %lpval -; Verify that resume function does not contains both print calls appearing after coro.end -; CHECK-LABEL: define internal fastcc void @f2.resume -; CHECK: invoke void @print(i32 1) -; CHECK: to label %CoroEnd unwind label %lpad - -; CHECK: CoroEnd: -; CHECK-NEXT: ret void - -; CHECK: lpad: -; CHECK-NEXT: %tok = cleanuppad within none [] -; CHECK-NEXT: call void @print(i32 2) -; CHECK-NEXT: cleanupret from %tok unwind to caller - declare i8* @llvm.coro.free(token, i8*) declare i32 @llvm.coro.size.i32() declare i8 @llvm.coro.suspend(token, i1) diff --git a/llvm/test/Transforms/Coroutines/coro-split-eh.ll b/llvm/test/Transforms/Coroutines/coro-split-eh-01.ll rename from llvm/test/Transforms/Coroutines/coro-split-eh.ll rename to llvm/test/Transforms/Coroutines/coro-split-eh-01.ll --- a/llvm/test/Transforms/Coroutines/coro-split-eh.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-eh-01.ll @@ -1,57 +1,7 @@ ; Tests that coro-split removes cleanup code after coro.end in resume functions ; and retains it in the start function. ; RUN: opt < %s -coro-split -S | FileCheck %s - -define i8* @f(i1 %val) "coroutine.presplit"="1" personality i32 3 { -entry: - %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) - %hdl = call i8* @llvm.coro.begin(token %id, i8* null) - call void @print(i32 0) - br i1 %val, label %resume, label %susp - -susp: - %0 = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %0, label %suspend [i8 0, label %resume - i8 1, label %suspend] -resume: - invoke void @print(i32 1) to label %suspend unwind label %lpad - -suspend: - call i1 @llvm.coro.end(i8* %hdl, i1 0) - call void @print(i32 0) ; should not be present in f.resume - ret i8* %hdl - -lpad: - %lpval = landingpad { i8*, i32 } - cleanup - - call void @print(i32 2) - %need.resume = call i1 @llvm.coro.end(i8* null, i1 true) - br i1 %need.resume, label %eh.resume, label %cleanup.cont - -cleanup.cont: - call void @print(i32 3) ; should not be present in f.resume - br label %eh.resume - -eh.resume: - resume { i8*, i32 } %lpval -} - -; Verify that start function contains both print calls the one before and after coro.end -; CHECK-LABEL: define i8* @f( -; CHECK: invoke void @print(i32 1) -; CHECK: to label %AfterCoroEnd unwind label %lpad - -; CHECK: AfterCoroEnd: -; CHECK: call void @print(i32 0) -; CHECK: ret i8* %hdl - -; CHECK: lpad: -; CHECK-NEXT: %lpval = landingpad { i8*, i32 } -; CHECK-NEXT: cleanup -; CHECK-NEXT: call void @print(i32 2) -; CHECK-NEXT: call void @print(i32 3) -; CHECK-NEXT: resume { i8*, i32 } %lpval +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define i8* @f2(i1 %val) "coroutine.presplit"="1" personality i32 4 { entry: @@ -60,15 +10,15 @@ call void @print(i32 0) br i1 %val, label %resume, label %susp -susp: +susp: %0 = call i8 @llvm.coro.suspend(token none, i1 false) - switch i8 %0, label %suspend [i8 0, label %resume + switch i8 %0, label %suspend [i8 0, label %resume i8 1, label %suspend] resume: invoke void @print(i32 1) to label %suspend unwind label %lpad suspend: - call i1 @llvm.coro.end(i8* %hdl, i1 0) + call i1 @llvm.coro.end(i8* %hdl, i1 0) call void @print(i32 0) ; should not be present in f.resume ret i8* %hdl @@ -81,7 +31,7 @@ cleanup.cont: %tok2 = cleanuppad within none [] call void @print(i32 3) ; should not be present in f.resume - cleanupret from %tok2 unwind to caller + cleanupret from %tok2 unwind to caller } ; Verify that start function contains both print calls the one before and after coro.end @@ -101,20 +51,6 @@ ; VERIFY Resume Parts -; Verify that resume function does not contains both print calls appearing after coro.end -; CHECK-LABEL: define internal fastcc void @f.resume -; CHECK: invoke void @print(i32 1) -; CHECK: to label %CoroEnd unwind label %lpad - -; CHECK: CoroEnd: -; CHECK-NEXT: ret void - -; CHECK: lpad: -; CHECK-NEXT: %lpval = landingpad { i8*, i32 } -; CHECK-NEXT: cleanup -; CHECK-NEXT: call void @print(i32 2) -; CHECK-NEXT: resume { i8*, i32 } %lpval - ; Verify that resume function does not contains both print calls appearing after coro.end ; CHECK-LABEL: define internal fastcc void @f2.resume ; CHECK: invoke void @print(i32 1) @@ -137,7 +73,7 @@ declare token @llvm.coro.id(i32, i8*, i8*, i8*) declare i8* @llvm.coro.alloc(token) declare i8* @llvm.coro.begin(token, i8*) -declare i1 @llvm.coro.end(i8*, i1) +declare i1 @llvm.coro.end(i8*, i1) declare noalias i8* @malloc(i32) declare void @print(i32) diff --git a/llvm/test/Transforms/Coroutines/coro-split-hidden.ll b/llvm/test/Transforms/Coroutines/coro-split-hidden.ll --- a/llvm/test/Transforms/Coroutines/coro-split-hidden.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-hidden.ll @@ -2,6 +2,7 @@ ; These may be generated by a frontend such as Clang, when inlining with ; '-fvisibility-inlines-hidden'. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define hidden i8* @f() "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-split-musttail.ll b/llvm/test/Transforms/Coroutines/coro-split-musttail.ll --- a/llvm/test/Transforms/Coroutines/coro-split-musttail.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-musttail.ll @@ -1,6 +1,7 @@ ; Tests that coro-split will convert coro.resume followed by a suspend to a ; musttail call. ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s define void @f() "coroutine.presplit"="1" { entry: diff --git a/llvm/test/Transforms/Coroutines/no-suspend.ll b/llvm/test/Transforms/Coroutines/no-suspend.ll --- a/llvm/test/Transforms/Coroutines/no-suspend.ll +++ b/llvm/test/Transforms/Coroutines/no-suspend.ll @@ -1,5 +1,6 @@ ; Test no suspend coroutines ; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s ; Coroutine with no-suspends will turn into: ;