[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