[llvm] [LoopInterchange] Bail out when outer loop latch PHI has non-PHI user (PR #201923)

Ryotaro Kasuga via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 04:56:32 PDT 2026


https://github.com/kasuga-fj updated https://github.com/llvm/llvm-project/pull/201923

>From 0661044cd85ca3ed94665a33f34b416da460fc82 Mon Sep 17 00:00:00 2001
From: Ryotaro Kasuga <kasuga.ryotaro at fujitsu.com>
Date: Sat, 6 Jun 2026 04:17:32 +0900
Subject: [PATCH] [LoopInterchange] Bail out when outer loop latch PHI has
 non-PHI user

---
 .../lib/Transforms/Scalar/LoopInterchange.cpp | 26 ++++-
 .../LoopInterchange/non-phi-uses-lcssa-phi.ll | 98 ++++++++++++++-----
 2 files changed, 96 insertions(+), 28 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index b2e3bbe516ed8..336446e1cd4fc 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -1593,6 +1593,26 @@ bool LoopInterchangeLegality::canInterchangeLoops(unsigned InnerLoopId,
     return false;
   }
 
+  // Regarding def-use chains that begin at an LCSSA PHI in the inner loop exit
+  // and end at any instruction in the outer loop latch, we currently support
+  // only the case where the chain contains only PHI nodes. Since we already
+  // call `tightlyNested()`, we know that if there is a def-use chain that we
+  // don't support (i.e., a chain that contains a non-PHI user), then the
+  // non-PHI user must be in the outer loop latch.
+  if (InnerLoop->getExitBlock() != OuterLoop->getLoopLatch())
+    for (PHINode &PHI : OuterLoop->getLoopLatch()->phis())
+      if (any_of(PHI.users(), [](const User *U) { return !isa<PHINode>(U); })) {
+        LLVM_DEBUG(dbgs() << "Outer loop latch PHI has a non-PHI user.\n");
+        ORE->emit([&]() {
+          return OptimizationRemarkMissed(DEBUG_TYPE, "UnsupportedLatchPHI",
+                                          OuterLoop->getStartLoc(),
+                                          OuterLoop->getHeader())
+                 << "Cannot interchange loops because an outer loop latch PHI "
+                    "node has a non-PHI user.";
+        });
+        return false;
+      }
+
   return true;
 }
 
@@ -2245,14 +2265,12 @@ static void moveLCSSAPhis(BasicBlock *InnerExit, BasicBlock *InnerHeader,
 
     assert(all_of(P.users(),
                   [OuterHeader, OuterExit, IncI, InnerHeader](User *U) {
-                    if (!isa<PHINode>(U))
-                      return true;
                     return (cast<PHINode>(U)->getParent() == OuterHeader &&
                             IncI->getParent() == InnerHeader) ||
                            cast<PHINode>(U)->getParent() == OuterExit;
                   }) &&
-           "Can only replace phis iff the phi-uses are in the loop nest exit "
-           "or the incoming value is defined in the inner header (it will "
+           "Can only replace phis iff the uses are in the loop nest exit or "
+           "the incoming value is defined in the inner header (it will "
            "dominate all loop blocks after interchanging)");
     P.replaceAllUsesWith(IncI);
     P.eraseFromParent();
diff --git a/llvm/test/Transforms/LoopInterchange/non-phi-uses-lcssa-phi.ll b/llvm/test/Transforms/LoopInterchange/non-phi-uses-lcssa-phi.ll
index 60d920013d8b8..0b445823c666b 100644
--- a/llvm/test/Transforms/LoopInterchange/non-phi-uses-lcssa-phi.ll
+++ b/llvm/test/Transforms/LoopInterchange/non-phi-uses-lcssa-phi.ll
@@ -1,46 +1,35 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; RUN: opt < %s -passes=loop-interchange -loop-interchange-profitabilities=ignore -S | FileCheck %s
 
-; This is a test for the assert that checks that we only replace phis iff the
-; phi-uses are in the loop nest exit or the incoming value is defined in the
-; inner header. We only want to check this when the user is a phi, which does
-; not always need to be the case.
+; Test cases that contain non-phi users of LCSSA phis (or phis that refer to
+; LCSSA phis). At the moment we don't support such cases.
 
 define i64 @non_phi_lcssa_phi_user(ptr %A) {
 ; CHECK-LABEL: define i64 @non_phi_lcssa_phi_user(
 ; CHECK-SAME: ptr [[A:%.*]]) {
-; CHECK-NEXT:  [[ENTRY:.*:]]
-; CHECK-NEXT:    br label %[[INNER_PREHEADER:.*]]
-; CHECK:       [[OUTER_HEADER_PREHEADER:.*]]:
-; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
-; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[I:%.*]] = phi i64 [ [[I_NEXT:%.*]], %[[OUTER_LATCH:.*]] ], [ 0, %[[OUTER_HEADER_PREHEADER]] ]
-; CHECK-NEXT:    br label %[[INNER_SPLIT1:.*]]
-; CHECK:       [[INNER_PREHEADER]]:
+; CHECK-NEXT:  [[INNER_PREHEADER:.*]]:
 ; CHECK-NEXT:    br label %[[INNER:.*]]
 ; CHECK:       [[INNER]]:
-; CHECK-NEXT:    [[J:%.*]] = phi i64 [ [[TMP1:%.*]], %[[INNER_SPLIT:.*]] ], [ 0, %[[INNER_PREHEADER]] ]
-; CHECK-NEXT:    br label %[[OUTER_HEADER_PREHEADER]]
-; CHECK:       [[INNER_SPLIT1]]:
+; CHECK-NEXT:    [[I:%.*]] = phi i64 [ 0, %[[INNER_PREHEADER]] ], [ [[I_NEXT:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    br label %[[OUTER_HEADER_PREHEADER:.*]]
+; CHECK:       [[OUTER_HEADER_PREHEADER]]:
+; CHECK-NEXT:    [[J:%.*]] = phi i64 [ 0, %[[INNER]] ], [ [[TMP1:%.*]], %[[OUTER_HEADER_PREHEADER]] ]
 ; CHECK-NEXT:    [[PTR:%.*]] = getelementptr inbounds [100 x i32], ptr [[A]], i64 [[J]], i64 [[I]]
 ; CHECK-NEXT:    store i32 0, ptr [[PTR]], align 4
-; CHECK-NEXT:    [[J_NEXT:%.*]] = add i64 [[J]], 1
-; CHECK-NEXT:    [[EXITCOND_INNER:%.*]] = icmp eq i64 [[J]], 99
-; CHECK-NEXT:    br label %[[INNER_EXIT:.*]]
-; CHECK:       [[INNER_SPLIT]]:
-; CHECK-NEXT:    [[TMP0:%.*]] = phi i64 [ [[USE_J:%.*]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    [[TMP1]] = add i64 [[J]], 1
 ; CHECK-NEXT:    [[TMP2:%.*]] = icmp eq i64 [[J]], 99
-; CHECK-NEXT:    br i1 [[TMP2]], label %[[EXIT:.*]], label %[[INNER]]
+; CHECK-NEXT:    br i1 [[TMP2]], label %[[INNER_EXIT:.*]], label %[[OUTER_HEADER_PREHEADER]]
 ; CHECK:       [[INNER_EXIT]]:
+; CHECK-NEXT:    [[J_LCSSA:%.*]] = phi i64 [ [[J]], %[[OUTER_HEADER_PREHEADER]] ]
 ; CHECK-NEXT:    br label %[[OUTER_LATCH]]
 ; CHECK:       [[OUTER_LATCH]]:
-; CHECK-NEXT:    [[USE_J]] = add i64 [[J]], 1
+; CHECK-NEXT:    [[J_LATCH:%.*]] = phi i64 [ [[J_LCSSA]], %[[INNER_EXIT]] ]
+; CHECK-NEXT:    [[USE_J:%.*]] = add i64 [[J_LATCH]], 1
 ; CHECK-NEXT:    [[I_NEXT]] = add i64 [[I]], 1
 ; CHECK-NEXT:    [[EXITCOND_OUTER:%.*]] = icmp eq i64 [[I]], 99
-; CHECK-NEXT:    br i1 [[EXITCOND_OUTER]], label %[[INNER_SPLIT]], label %[[OUTER_HEADER]]
+; CHECK-NEXT:    br i1 [[EXITCOND_OUTER]], label %[[EXIT:.*]], label %[[INNER]]
 ; CHECK:       [[EXIT]]:
-; CHECK-NEXT:    [[RESULT:%.*]] = phi i64 [ [[TMP0]], %[[INNER_SPLIT]] ]
+; CHECK-NEXT:    [[RESULT:%.*]] = phi i64 [ [[USE_J]], %[[OUTER_LATCH]] ]
 ; CHECK-NEXT:    ret i64 [[RESULT]]
 ;
 entry:
@@ -73,3 +62,64 @@ exit:
   %result = phi i64 [ %use.j, %outer.latch ]
   ret i64 %result
 }
+
+define void @non_phi_lcssa_phi_user2(ptr %A) {
+; CHECK-LABEL: define void @non_phi_lcssa_phi_user2(
+; CHECK-SAME: ptr [[A:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
+; CHECK:       [[OUTER_HEADER]]:
+; CHECK-NEXT:    [[I:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[I_INC:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    br label %[[INNER:.*]]
+; CHECK:       [[INNER]]:
+; CHECK-NEXT:    [[J:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[J_INC:%.*]], %[[INNER]] ]
+; CHECK-NEXT:    [[P:%.*]] = getelementptr [4 x i8], ptr [[A]], i64 [[J]], i64 [[I]]
+; CHECK-NEXT:    [[V:%.*]] = load i8, ptr [[P]], align 1
+; CHECK-NEXT:    [[ADD:%.*]] = add i8 [[V]], 1
+; CHECK-NEXT:    store i8 [[ADD]], ptr [[P]], align 1
+; CHECK-NEXT:    [[J_INC]] = add i64 [[J]], 1
+; CHECK-NEXT:    [[EC_J:%.*]] = icmp eq i64 [[J_INC]], 4
+; CHECK-NEXT:    br i1 [[EC_J]], label %[[OUTER_BODY:.*]], label %[[INNER]]
+; CHECK:       [[OUTER_BODY]]:
+; CHECK-NEXT:    [[LCSSA_ADD_0:%.*]] = phi i8 [ [[ADD]], %[[INNER]] ]
+; CHECK-NEXT:    br label %[[OUTER_LATCH]]
+; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[LCSSA_ADD_1:%.*]] = phi i8 [ [[LCSSA_ADD_0]], %[[OUTER_BODY]] ]
+; CHECK-NEXT:    [[USE:%.*]] = add i8 [[LCSSA_ADD_1]], 1
+; CHECK-NEXT:    [[I_INC]] = add i64 [[I]], 1
+; CHECK-NEXT:    [[EC_I:%.*]] = icmp eq i64 [[I_INC]], 4
+; 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.inc, %outer.latch ]
+  br label %inner
+
+inner:
+  %j = phi i64 [ 0, %outer.header ],[ %j.inc, %inner ]
+  %p = getelementptr [4 x i8], ptr %A, i64 %j, i64 %i
+  %v = load i8, ptr %p
+  %add = add i8 %v, 1
+  store i8 %add, ptr %p
+  %j.inc = add i64 %j, 1
+  %ec.j = icmp eq i64 %j.inc, 4
+  br i1 %ec.j, label %outer.body, label %inner
+
+outer.body:
+  %lcssa.add.0 = phi i8 [ %add, %inner ]
+  br label %outer.latch
+
+outer.latch:
+  %lcssa.add.1 = phi i8 [ %lcssa.add.0, %outer.body ]
+  %use = add i8 %lcssa.add.1, 1
+  %i.inc = add i64 %i, 1
+  %ec.i = icmp eq i64 %i.inc, 4
+  br i1 %ec.i, label %exit, label %outer.header
+
+exit:
+  ret void
+}



More information about the llvm-commits mailing list