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

via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 05:30:44 PDT 2026


Author: Ryotaro Kasuga
Date: 2026-06-11T12:30:39Z
New Revision: 32ecd3e7ff64cab044716e39d14f880cf5a7367d

URL: https://github.com/llvm/llvm-project/commit/32ecd3e7ff64cab044716e39d14f880cf5a7367d
DIFF: https://github.com/llvm/llvm-project/commit/32ecd3e7ff64cab044716e39d14f880cf5a7367d.diff

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

When there are non-PHI instructions in the outer loop that use values
originating from the LCSSA PHIs of the inner loop, it becomes difficult
to adjust the wiring during the transformation. In fact, multiple issues
(#200819 and #201571) have been raised related to this pattern. #201059
tried to resolve the issue by modifying the transformation phase, but it
was insufficient.
Instead of spending effort in the transformation phase, this patch adds
an additional check in the legality check and rejects such cases. I
think the cases rejected by this additional check are not very
practical, so the impact on realistic cases should be low, and it is
simpler than adjusting the wiring in the transformation phase.
This patch also effectively reverts #201059, as it is no longer
necessary.

Fix #201571.

Added: 
    

Modified: 
    llvm/lib/Transforms/Scalar/LoopInterchange.cpp
    llvm/test/Transforms/LoopInterchange/non-phi-uses-lcssa-phi.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index f0cf9d6d4724f..660e863696a93 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -1608,6 +1608,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;
 }
 
@@ -2260,14 +2280,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