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,30 @@ +//===- 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 +// the resume and destroy parts of the coroutine into separate functions. +// +//===----------------------------------------------------------------------===// + +#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 @@ -111,6 +111,7 @@ CGSCC_PASS("attributor-cgscc", AttributorCGSCCPass()) CGSCC_PASS("inline", InlinerPass()) CGSCC_PASS("openmpopt", OpenMPOptPass()) +CGSCC_PASS("coro-split", CoroSplitPass()) CGSCC_PASS("no-op-cgscc", NoOpCGSCCPass()) #undef CGSCC_PASS 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 @@ -18,6 +18,7 @@ // coroutine. //===----------------------------------------------------------------------===// +#include "llvm/Transforms/Coroutines/CoroSplit.h" #include "CoroInstr.h" #include "CoroInternal.h" #include "llvm/ADT/DenseMap.h" @@ -59,6 +60,7 @@ #include "llvm/Support/raw_ostream.h" #include "llvm/Transforms/Scalar.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/Transforms/Utils/CallGraphUpdater.h" #include "llvm/Transforms/Utils/Cloning.h" #include "llvm/Transforms/Utils/Local.h" #include "llvm/Transforms/Utils/ValueMapper.h" @@ -1342,19 +1344,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 @@ -1363,26 +1354,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); @@ -1390,6 +1397,43 @@ coro::updateCallGraph(F, Clones, CG, SCC); } +static void updateCallGraphAfterCoroutineSplit( + LazyCallGraph::Node &N, const coro::Shape &Shape, + const SmallVectorImpl &Clones, LazyCallGraph::SCC &C, + LazyCallGraph &CG, CGSCCAnalysisManager &AM, CGSCCUpdateResult &UR) { + if (!Shape.CoroBegin) + return; + + for (llvm::CoroEndInst *End : Shape.CoroEnds) { + auto &Context = End->getContext(); + End->replaceAllUsesWith(ConstantInt::getFalse(Context)); + End->eraseFromParent(); + } + + postSplitCleanup(N.getFunction()); + + // To insert the newly created coroutine funclets 'f.resume', 'f.destroy', and + // 'f.cleanup' into the same SCC as the coroutine 'f' they were outlined from, + // we make use of the CallGraphUpdater class, which can modify the internal + // state of the LazyCallGraph. + for (Function *Clone : Clones) + CG.addNewFunctionIntoRefSCC(*Clone, C.getOuterRefSCC()); + + // We've inserted instructions into coroutine 'f' that reference the three new + // coroutine funclets. We must now update the call graph so that reference + // edges between 'f' and its funclets are added to it. LazyCallGraph only + // allows CGSCC passes to insert "trivial" reference edges. We've ensured + // above, by inserting the funclets into the same SCC as the corutine, that + // the edges are trivial. + // + // N.B.: If we didn't update the call graph here, a CGSCCToFunctionPassAdaptor + // later in this CGSCC pass pipeline may be run, triggering a call graph + // update of its own. Function passes run by the adaptor are not permitted to + // add new edges of any kind to the graph, and the new edges inserted by this + // pass would be misattributed to that unrelated function pass. + updateCGAndAnalysisManagerForCGSCCPass(CG, C, N, AM, UR); +} + // 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. @@ -1521,12 +1565,86 @@ 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 (LazyCallGraph::Node &N : C) + if (N.getFunction().hasFnAttribute(CORO_PRESPLIT_ATTR)) + Coroutines.push_back(&N); + + if (Coroutines.empty() && !PrepareFn) + return PreservedAnalyses::all(); + + if (Coroutines.empty()) + llvm_unreachable("new pass manager cannot yet handle " + "'llvm.coro.prepare.retcon'"); + + // Split all the coroutines. + for (LazyCallGraph::Node *N : Coroutines) { + Function &F = N->getFunction(); + 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) { + // Enqueue a second iteration of the CGSCC pipeline. + // N.B.: + // The CoroSplitLegacy pass "triggers" a restart of the CGSCC pass + // pipeline by inserting an indirect function call that the + // CoroElideLegacy pass then replaces with a direct function call. The + // legacy CGSCC pipeline's implicit behavior was as if wrapped in the new + // pass manager abstraction DevirtSCCRepeatedPass. + // + // This pass does not need to "trigger" another run of the pipeline. + // Instead, it simply enqueues the same RefSCC onto the pipeline's + // worklist. + UR.CWorklist.insert(&C); + F.addFnAttr(CORO_PRESPLIT_ATTR, PREPARED_FOR_SPLIT); + continue; + } + F.removeFnAttr(CORO_PRESPLIT_ATTR); + + SmallVector Clones; + const coro::Shape Shape = splitCoroutine(F, Clones); + updateCallGraphAfterCoroutineSplit(*N, Shape, Clones, C, CG, AM, UR); + } + + if (PrepareFn) + llvm_unreachable("new pass manager cannot yet handle " + "'llvm.coro.prepare.retcon'"); + + return PreservedAnalyses::none(); +} namespace { +// 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. struct CoroSplitLegacy : public CallGraphSCCPass { static char ID; // Pass identification, replacement for typeid @@ -1539,9 +1657,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); } @@ -1583,7 +1699,10 @@ 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) 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,28 +30,15 @@ } ; 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 ; CHECK: call void @print2(i64 %this.reload) 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 @@ -102,20 +52,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) ; CHECK: to label %CoroEnd unwind label %lpad @@ -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() #0 { entry: diff --git a/llvm/test/Transforms/Coroutines/coro-split-musttail1.ll b/llvm/test/Transforms/Coroutines/coro-split-musttail1.ll --- a/llvm/test/Transforms/Coroutines/coro-split-musttail1.ll +++ b/llvm/test/Transforms/Coroutines/coro-split-musttail1.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() #0 { 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: ; diff --git a/llvm/test/Transforms/Coroutines/restart-trigger.ll b/llvm/test/Transforms/Coroutines/restart-trigger.ll --- a/llvm/test/Transforms/Coroutines/restart-trigger.ll +++ b/llvm/test/Transforms/Coroutines/restart-trigger.ll @@ -1,5 +1,6 @@ -; Verifies that restart trigger forces IPO pipelines restart and the same -; coroutine is looked at by CoroSplit pass twice. +; Verifies that the restart trigger that is used by legacy coroutine passes +; forces the legacy pass manager to restart IPO pipelines, thereby causing the +; same coroutine to be looked at by CoroSplit pass twice. ; REQUIRES: asserts ; RUN: opt < %s -S -O0 -enable-coroutines -debug-only=coro-split 2>&1 | FileCheck %s ; RUN: opt < %s -S -O1 -enable-coroutines -debug-only=coro-split 2>&1 | FileCheck %s