[llvm] [LoopInterchange] Bail out if a PHI would be cloned into the new latch (PR #212742)
Ryotaro Kasuga via llvm-commits
llvm-commits at lists.llvm.org
Wed Jul 29 04:32:08 PDT 2026
https://github.com/kasuga-fj created https://github.com/llvm/llvm-project/pull/212742
None
>From 8e7d4acdb13461c870f4d7d7c0704b856e41a556 Mon Sep 17 00:00:00 2001
From: Ryotaro Kasuga <kasuga.ryotaro at fujitsu.com>
Date: Wed, 29 Jul 2026 20:26:05 +0900
Subject: [PATCH] [LoopInterchange] Bail out if a PHI would be cloned into the
new latch
---
.../lib/Transforms/Scalar/LoopInterchange.cpp | 56 +++++++++---------
.../LoopInterchange/inner-latch-defs-iv.ll | 58 +++++++++++++++++++
2 files changed, 84 insertions(+), 30 deletions(-)
create mode 100644 llvm/test/Transforms/LoopInterchange/inner-latch-defs-iv.ll
diff --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index c79bf04df656a..d4b8859669051 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -1481,41 +1481,37 @@ static bool areOuterLoopExitPHIsSupported(Loop *OuterLoop, Loop *InnerLoop) {
return true;
}
-/// The transform clones the inner latch's exit condition into the new latch
-/// (see MoveInstructions in LoopInterchangeTransform::transform), but it does
-/// not relocate PHI nodes. So if a PHI in the inner latch feeds that condition,
-/// a later interchange can leave the cloned PHI with a stale incoming block,
-/// producing invalid IR. Reject that case here.
+/// The transform partially clones the inner loop's latch block, but PHI nodes
+/// cannot be cloned this way. This function follows the instruction trees that
+/// would be cloned and checks whether any PHI node other than the induction
+/// PHIs feeds them. If such a PHI is found, the interchange is rejected.
///
-/// For example, %p is a PHI in the inner latch and the inner loop's exit test
-/// reads %p, so %p feeds the condition that would be cloned:
-///
-/// inner.latch:
-/// %p = phi i64 [ %v, %subloop.latch ]
-/// %ec = icmp eq i64 %iv, %p ; inner exit test reads %p
-/// br i1 %ec, label %exit, label %inner.header
-///
-/// TODO: Handle transformation of lcssa phis in the InnerLoop latch in case of
-/// multi-level loop nests.
-static bool areInnerLoopLatchPHIsSupported(Loop *InnerLoop) {
- if (InnerLoop->getSubLoops().empty())
- return true;
-
+/// TODO: This check strongly depends on the current implementation of the
+/// transform. Ideally, the transform should be able to handle such PHI nodes in
+/// the inner loop latch.
+static bool areInnerLoopLatchPHIsSupported(Loop *InnerLoop,
+ ArrayRef<PHINode *> InductionPHIs) {
BasicBlock *InnerLoopLatch = InnerLoop->getLoopLatch();
- auto *LatchBI = dyn_cast<CondBrInst>(InnerLoopLatch->getTerminator());
- if (!LatchBI)
- return true;
- auto *CondI = dyn_cast<Instruction>(LatchBI->getCondition());
- if (!CondI)
- return true;
- // Bail if a phi in the inner latch feeds the exit condition, walking operands
- // within the inner loop.
+ // Seed the worklist with the roots of the use-def chains the transform
+ // clones: the latch's exit condition and the incoming values of the induction
+ // PHIs from the latch.
SmallSetVector<Instruction *, 8> Worklist;
- Worklist.insert(CondI);
+ if (auto *LatchBI = dyn_cast<CondBrInst>(InnerLoopLatch->getTerminator()))
+ if (auto *CondI = dyn_cast<Instruction>(LatchBI->getCondition()))
+ Worklist.insert(CondI);
+ for (PHINode *InductionPHI : InductionPHIs) {
+ if (auto *IncomingI = dyn_cast<Instruction>(
+ InductionPHI->getIncomingValueForBlock(InnerLoopLatch)))
+ if (!is_contained(InductionPHIs, IncomingI))
+ Worklist.insert(IncomingI);
+ }
+
+ // Bail if a PHI node other than the induction PHIs feeds the cloned
+ // instructions, walking the operand trees within the inner loop.
for (unsigned I = 0; I < Worklist.size(); ++I) {
Instruction *Cur = Worklist[I];
- if (isa<PHINode>(Cur) && Cur->getParent() == InnerLoopLatch)
+ if (isa<PHINode>(Cur) && !is_contained(InductionPHIs, Cur))
return false;
for (Value *Op : Cur->operands())
if (auto *OpI = dyn_cast<Instruction>(Op))
@@ -1571,7 +1567,7 @@ bool LoopInterchangeLegality::canInterchangeLoops(unsigned InnerLoopId,
return false;
}
- if (!areInnerLoopLatchPHIsSupported(InnerLoop)) {
+ if (!areInnerLoopLatchPHIsSupported(InnerLoop, InnerLoopInductions)) {
LLVM_DEBUG(dbgs() << "Found unsupported PHI nodes in inner loop latch.\n");
ORE->emit([&]() {
return OptimizationRemarkMissed(DEBUG_TYPE, "UnsupportedInnerLatchPHI",
diff --git a/llvm/test/Transforms/LoopInterchange/inner-latch-defs-iv.ll b/llvm/test/Transforms/LoopInterchange/inner-latch-defs-iv.ll
new file mode 100644
index 0000000000000..80222abfd9379
--- /dev/null
+++ b/llvm/test/Transforms/LoopInterchange/inner-latch-defs-iv.ll
@@ -0,0 +1,58 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -passes=loop-interchange -loop-interchange-profitabilities=ignore -S %s | FileCheck %s
+
+; Ensure that induction variables defined in the inner latch don't cause
+; problems for loop interchange.
+
+define void @f(ptr %A) {
+; CHECK-LABEL: define void @f(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*]]:
+; CHECK-NEXT: br label %[[OUTER_HEADER:.*]]
+; CHECK: [[OUTER_HEADER]]:
+; CHECK-NEXT: [[I:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[I_NEXT:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT: br label %[[INNER_HEADER:.*]]
+; CHECK: [[INNER_HEADER]]:
+; CHECK-NEXT: [[J:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[J_NEXT_PHI:%.*]], %[[INNER_LATCH:.*]] ]
+; CHECK-NEXT: [[J_NEXT:%.*]] = add i64 [[J]], 1
+; CHECK-NEXT: [[GEP:%.*]] = getelementptr [256 x i8], ptr [[A]], i64 [[J]], i64 [[I]]
+; CHECK-NEXT: store i8 0, ptr [[GEP]], align 1
+; CHECK-NEXT: br label %[[INNER_LATCH]]
+; CHECK: [[INNER_LATCH]]:
+; CHECK-NEXT: [[J_NEXT_PHI]] = phi i64 [ [[J_NEXT]], %[[INNER_HEADER]] ]
+; CHECK-NEXT: [[EC_J:%.*]] = icmp eq i64 [[J_NEXT]], 256
+; CHECK-NEXT: br i1 [[EC_J]], label %[[OUTER_LATCH]], label %[[INNER_HEADER]]
+; CHECK: [[OUTER_LATCH]]:
+; CHECK-NEXT: [[I_NEXT]] = add i64 [[I]], 1
+; CHECK-NEXT: [[EC_I:%.*]] = icmp eq i64 [[I_NEXT]], 256
+; CHECK-NEXT: br i1 [[EC_I]], label %[[EXIT:.*]], label %[[OUTER_HEADER]]
+; CHECK: [[EXIT]]:
+; CHECK-NEXT: ret void
+;
+entry:
+ br label %outer.header
+
+outer.header:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %outer.latch ]
+ br label %inner.header
+
+inner.header:
+ %j = phi i64 [ 0, %outer.header ], [ %j.next.phi, %inner.latch ]
+ %j.next = add i64 %j, 1
+ %gep = getelementptr [256 x i8], ptr %A, i64 %j, i64 %i
+ store i8 0, ptr %gep
+ br label %inner.latch
+
+inner.latch:
+ %j.next.phi = phi i64 [ %j.next, %inner.header ]
+ %ec.j = icmp eq i64 %j.next, 256
+ br i1 %ec.j, label %outer.latch, label %inner.header
+
+outer.latch:
+ %i.next = add i64 %i, 1
+ %ec.i = icmp eq i64 %i.next, 256
+ br i1 %ec.i, label %exit, label %outer.header
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list