Skip to content

[IRCE] Check loop clone legality before attempting to do so #151062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions llvm/include/llvm/Analysis/LoopInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,12 @@ class LLVM_ABI Loop : public LoopBase<BasicBlock, Loop> {
/// Return true if the loop body is safe to clone in practice.
bool isSafeToClone() const;

/// Like `isSafeToClone`, but also checks whether we may form phis for all
/// values that are live-out from the loop. This is required if either of
/// the cloned loop bodies may run conditionally.
bool isSafeToCloneConditionally(const DominatorTree &DT,
bool AllowConvergent = false) const;

/// Returns true if the loop is annotated parallel.
///
/// A parallel loop can be assumed to not contain any dependencies between
Expand Down
60 changes: 45 additions & 15 deletions llvm/lib/Analysis/LoopInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,27 @@ bool Loop::isCanonical(ScalarEvolution &SE) const {
return true;
}

static bool loopContainsUser(const Loop &L, const BasicBlock &BB, const Use &U,
const DominatorTree &DT) {
const Instruction *UI = cast<Instruction>(U.getUser());
const BasicBlock *UserBB = UI->getParent();

// For practical purposes, we consider that the use in a PHI
// occurs in the respective predecessor block. For more info,
// see the `phi` doc in LangRef and the LCSSA doc.
if (const PHINode *P = dyn_cast<PHINode>(UI))
UserBB = P->getIncomingBlock(U);

// Check the current block, as a fast-path, before checking whether
// the use is anywhere in the loop. Most values are used in the same
// block they are defined in. Also, blocks not reachable from the
// entry are special; uses in them don't need to go through PHIs.
if (UserBB != &BB && !L.contains(UserBB) && DT.isReachableFromEntry(UserBB))
return false;

return true;
}

// Check that 'BB' doesn't have any uses outside of the 'L'
static bool isBlockInLCSSAForm(const Loop &L, const BasicBlock &BB,
const DominatorTree &DT, bool IgnoreTokens) {
Expand All @@ -440,21 +461,7 @@ static bool isBlockInLCSSAForm(const Loop &L, const BasicBlock &BB,
continue;

for (const Use &U : I.uses()) {
const Instruction *UI = cast<Instruction>(U.getUser());
const BasicBlock *UserBB = UI->getParent();

// For practical purposes, we consider that the use in a PHI
// occurs in the respective predecessor block. For more info,
// see the `phi` doc in LangRef and the LCSSA doc.
if (const PHINode *P = dyn_cast<PHINode>(UI))
UserBB = P->getIncomingBlock(U);

// Check the current block, as a fast-path, before checking whether
// the use is anywhere in the loop. Most values are used in the same
// block they are defined in. Also, blocks not reachable from the
// entry are special; uses in them don't need to go through PHIs.
if (UserBB != &BB && !L.contains(UserBB) &&
DT.isReachableFromEntry(UserBB))
if (!loopContainsUser(L, BB, U, DT))
return false;
}
}
Expand Down Expand Up @@ -500,6 +507,29 @@ bool Loop::isSafeToClone() const {
return true;
}

bool Loop::isSafeToCloneConditionally(const DominatorTree &DT,
bool AllowConvergent) const {
// Return false if any loop blocks contain indirectbrs, or there are any calls
// to noduplicate functions.
for (BasicBlock *BB : this->blocks()) {
if (isa<IndirectBrInst>(BB->getTerminator()))
return false;

for (Instruction &I : *BB) {
if (I.getType()->isTokenTy()) {
for (const Use &U : I.uses()) {
if (!loopContainsUser(*this, *BB, U, DT))
return false;
}
}
if (auto *CB = dyn_cast<CallBase>(&I))
if (CB->cannotDuplicate() || (AllowConvergent || CB->isConvergent()))
return false;
}
}
return true;
}

MDNode *Loop::getLoopID() const {
MDNode *LoopID = nullptr;

Expand Down
17 changes: 4 additions & 13 deletions llvm/lib/Transforms/Scalar/SimpleLoopUnswitch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3277,19 +3277,10 @@ static bool collectUnswitchCandidatesWithInjections(
return Found;
}

static bool isSafeForNoNTrivialUnswitching(Loop &L, LoopInfo &LI) {
if (!L.isSafeToClone())
static bool isSafeForNoNTrivialUnswitching(const DominatorTree &DT, Loop &L,
LoopInfo &LI) {
if (!L.isSafeToCloneConditionally(DT))
return false;
for (auto *BB : L.blocks())
for (auto &I : *BB) {
if (I.getType()->isTokenTy() && I.isUsedOutsideOfBlock(BB))
return false;
if (auto *CB = dyn_cast<CallBase>(&I)) {
assert(!CB->cannotDuplicate() && "Checked by L.isSafeToClone().");
if (CB->isConvergent())
return false;
}
}

// Check if there are irreducible CFG cycles in this loop. If so, we cannot
// easily unswitch non-trivial edges out of the loop. Doing so might turn the
Expand Down Expand Up @@ -3665,7 +3656,7 @@ static bool unswitchLoop(Loop &L, DominatorTree &DT, LoopInfo &LI,
}

// Perform legality checks.
if (!isSafeForNoNTrivialUnswitching(L, LI))
if (!isSafeForNoNTrivialUnswitching(DT, L, LI))
return false;

// For non-trivial unswitching, because it often creates new loops, we rely on
Expand Down
7 changes: 7 additions & 0 deletions llvm/lib/Transforms/Utils/LoopConstrainer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -750,6 +750,13 @@ bool LoopConstrainer::run() {
const SCEVConstant *MinusOneS =
cast<SCEVConstant>(SE.getConstant(IVTy, -1, true /* isSigned */));

if ((NeedsPreLoop || NeedsPostLoop) &&
!OriginalLoop.isSafeToCloneConditionally(DT)) {
LLVM_DEBUG(dbgs() << "cannot clone loop " << OriginalLoop.getName()
<< " because it is not safe to clone.\n");
return false;
}

if (NeedsPreLoop) {
const SCEV *ExitPreLoopAtSCEV = nullptr;

Expand Down
61 changes: 61 additions & 0 deletions llvm/test/Transforms/IRCE/pre_post_loops_clone.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
; RUN: opt -verify-loop-info -irce-print-changed-loops -passes=irce -S < %s 2>&1 | FileCheck %s
; This test is the same as pre_post_loops.ll, except that the loop body contains a token-generating
; call. We need to ensure that IRCE does not try to introduce a PHI or otherwise generate invalid IE.

declare token @llvm.source_token()
declare void @llvm.sink_token(token)

; CHECK: define void @test_01
define void @test_01(ptr %arr, ptr %a_len_ptr) {
entry:
%len = load i32, ptr %a_len_ptr, !range !0
br label %loop

loop:
%idx = phi i32 [ 0, %entry ], [ %idx.next, %in.bounds ]
%idx.next = add i32 %idx, 1
%abc = icmp slt i32 %idx, %len
%token = call token @llvm.source_token()
br i1 %abc, label %in.bounds, label %out.of.bounds

in.bounds:
%addr = getelementptr i32, ptr %arr, i32 %idx
store i32 0, ptr %addr
%next = icmp slt i32 %idx.next, 2147483647
br i1 %next, label %loop, label %exit

out.of.bounds:
ret void

exit:
call void @llvm.sink_token(token %token)
ret void
}

define void @test_02(ptr %arr, ptr %a_len_ptr) {
entry:
%len = load i32, ptr %a_len_ptr, !range !0
br label %loop

loop:
%idx = phi i32 [ 2147483647, %entry ], [ %idx.next, %in.bounds ]
%idx.next = add i32 %idx, -1
%abc = icmp slt i32 %idx, %len
%token = call token @llvm.source_token()
br i1 %abc, label %in.bounds, label %out.of.bounds

in.bounds:
%addr = getelementptr i32, ptr %arr, i32 %idx
store i32 0, ptr %addr
%next = icmp sgt i32 %idx.next, -1
br i1 %next, label %loop, label %exit

out.of.bounds:
ret void

exit:
call void @llvm.sink_token(token %token)
ret void
}

!0 = !{i32 0, i32 50}