From a37bb4a65d85ea453670288d07b76b9ce664466e Mon Sep 17 00:00:00 2001 From: Sanjoy Das Date: Sun, 26 Jun 2016 05:10:45 +0000 Subject: [PATCH] [LoopUnswitch] Unswitch on conditions feeding into guards Summary: This is a straightforward extension of what LoopUnswitch does to branches to guards. That is, we unswitch ``` for (;;) { ... guard(loop_invariant_cond); ... } ``` into ``` if (loop_invariant_cond) { for (;;) { ... // There is no need to emit guard(true) ... } } else { for (;;) { ... guard(false); // SimplifyCFG will clean this up by adding an // unreachable after the guard(false) ... } } ``` Reviewers: majnemer Subscribers: mcrosier, llvm-commits, mzolotukhin Differential Revision: http://reviews.llvm.org/D21725 llvm-svn: 273801 --- llvm/lib/Transforms/Scalar/LoopUnswitch.cpp | 40 +++++++-- llvm/test/Transforms/LoopUnswitch/guards.ll | 97 +++++++++++++++++++++ 2 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 llvm/test/Transforms/LoopUnswitch/guards.ll diff --git a/llvm/lib/Transforms/Scalar/LoopUnswitch.cpp b/llvm/lib/Transforms/Scalar/LoopUnswitch.cpp index 7aa730474d76..0b128a16183a 100644 --- a/llvm/lib/Transforms/Scalar/LoopUnswitch.cpp +++ b/llvm/lib/Transforms/Scalar/LoopUnswitch.cpp @@ -65,6 +65,7 @@ using namespace llvm; STATISTIC(NumBranches, "Number of branches unswitched"); STATISTIC(NumSwitches, "Number of switches unswitched"); +STATISTIC(NumGuards, "Number of guards unswitched"); STATISTIC(NumSelects , "Number of selects unswitched"); STATISTIC(NumTrivial , "Number of unswitches that are trivial"); STATISTIC(NumSimplify, "Number of simplifications of unswitched code"); @@ -514,22 +515,34 @@ bool LoopUnswitch::processCurrentLoop() { return true; } - // Do not unswitch loops containing convergent operations, as we might be - // making them control dependent on the unswitch value when they were not - // before. - // FIXME: This could be refined to only bail if the convergent operation is - // not already control-dependent on the unswitch value. + // Run through the instructions in the loop, keeping track of three things: + // + // - That we do not unswitch loops containing convergent operations, as we + // might be making them control dependent on the unswitch value when they + // were not before. + // FIXME: This could be refined to only bail if the convergent operation is + // not already control-dependent on the unswitch value. + // + // - That basic blocks in the loop contain invokes whose predecessor edges we + // cannot split. + // + // - The set of guard intrinsics encountered (these are non terminator + // instructions that are also profitable to be unswitched). + + SmallVector Guards; + for (const auto BB : currentLoop->blocks()) { for (auto &I : *BB) { auto CS = CallSite(&I); if (!CS) continue; if (CS.hasFnAttr(Attribute::Convergent)) return false; - // Return false if any loop blocks contain invokes whose predecessor edges - // we cannot split. if (auto *II = dyn_cast(&I)) if (!II->getUnwindDest()->canSplitPredecessors()) return false; + if (auto *II = dyn_cast(&I)) + if (II->getIntrinsicID() == Intrinsic::experimental_guard) + Guards.push_back(II); } } @@ -549,6 +562,19 @@ bool LoopUnswitch::processCurrentLoop() { return false; } + for (IntrinsicInst *Guard : Guards) { + Value *LoopCond = + FindLIVLoopCondition(Guard->getOperand(0), currentLoop, Changed); + if (LoopCond && + UnswitchIfProfitable(LoopCond, ConstantInt::getTrue(Context))) { + // NB! Unswitching (if successful) could have erased some of the + // instructions in Guards leaving dangling pointers there. This is fine + // because we're returning now, and won't look at Guards again. + ++NumGuards; + return true; + } + } + // Loop over all of the basic blocks in the loop. If we find an interior // block that is branching on a loop-invariant condition, we can unswitch this // loop. diff --git a/llvm/test/Transforms/LoopUnswitch/guards.ll b/llvm/test/Transforms/LoopUnswitch/guards.ll new file mode 100644 index 000000000000..558853389602 --- /dev/null +++ b/llvm/test/Transforms/LoopUnswitch/guards.ll @@ -0,0 +1,97 @@ +; RUN: opt -S -loop-unswitch < %s | FileCheck %s + +declare void @llvm.experimental.guard(i1, ...) + +define void @f_0(i32 %n, i32* %ptr, i1 %c) { +; CHECK-LABEL: @f_0( +; CHECK: loop.us: +; CHECK-NOT: guard +; CHECK: loop: +; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"() ] +entry: + br label %loop + +loop: + %iv = phi i32 [ 0, %entry ], [ %iv.inc, %loop ] + %iv.inc = add i32 %iv, 1 + call void(i1, ...) @llvm.experimental.guard(i1 %c) [ "deopt"() ] + store volatile i32 0, i32* %ptr + %becond = icmp ult i32 %iv.inc, %n + br i1 %becond, label %leave, label %loop + +leave: + ret void +} + +define void @f_1(i32 %n, i32* %ptr, i1 %c_0, i1 %c_1) { +; CHECK-LABEL: @f_1( +; CHECK: loop.us.us: +; CHECK-NOT: guard +; CHECK: loop.us: +; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"(i32 2) ] +; CHECK-NOT: guard +; CHECK: loop.us1: +; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"(i32 1) ] +; CHECK-NOT: guard +; CHECK: loop: +; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"(i32 1) ] +; CHECK: call void (i1, ...) @llvm.experimental.guard(i1 false) [ "deopt"(i32 2) ] +entry: + br label %loop + +loop: + %iv = phi i32 [ 0, %entry ], [ %iv.inc, %loop ] + %iv.inc = add i32 %iv, 1 + call void(i1, ...) @llvm.experimental.guard(i1 %c_0) [ "deopt"(i32 1) ] + store volatile i32 0, i32* %ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c_1) [ "deopt"(i32 2) ] + %becond = icmp ult i32 %iv.inc, %n + br i1 %becond, label %leave, label %loop + +leave: + ret void +} + +; Basic negative test + +define void @f_3(i32 %n, i32* %ptr, i1* %c_ptr) { +; CHECK-LABEL: @f_3( +; CHECK-NOT: loop.us: +entry: + br label %loop + +loop: + %iv = phi i32 [ 0, %entry ], [ %iv.inc, %loop ] + %iv.inc = add i32 %iv, 1 + %c = load volatile i1, i1* %c_ptr + call void(i1, ...) @llvm.experimental.guard(i1 %c) [ "deopt"() ] + store volatile i32 0, i32* %ptr + %becond = icmp ult i32 %iv.inc, %n + br i1 %becond, label %leave, label %loop + +leave: + ret void +} + +define void @f_4(i32 %n, i32* %ptr, i1 %c) { +; CHECK-LABEL: @f_4( +; +; Demonstrate that unswitching on one guard can cause another guard to +; be erased (this has implications on what guards we can keep raw +; pointers to). +entry: + br label %loop + +loop: + %iv = phi i32 [ 0, %entry ], [ %iv.inc, %loop ] + %iv.inc = add i32 %iv, 1 + call void(i1, ...) @llvm.experimental.guard(i1 %c) [ "deopt"(i32 1) ] + store volatile i32 0, i32* %ptr + %neg = xor i1 %c, 1 + call void(i1, ...) @llvm.experimental.guard(i1 %neg) [ "deopt"(i32 2) ] + %becond = icmp ult i32 %iv.inc, %n + br i1 %becond, label %leave, label %loop + +leave: + ret void +}