[llvm] [LoopInterchange] Don't consider loops with BTC=0 (PR #167113)

Sjoerd Meijer via llvm-commits llvm-commits at lists.llvm.org
Mon Nov 10 04:06:58 PST 2025


https://github.com/sjoerdmeijer updated https://github.com/llvm/llvm-project/pull/167113

>From c19c535d053c942498b0736f8ffccbdc98543b6f Mon Sep 17 00:00:00 2001
From: Sjoerd Meijer <smeijer at nvidia.com>
Date: Fri, 7 Nov 2025 06:23:51 -0800
Subject: [PATCH 1/3] [LoopInterchange] Don't consider loops with BTC=0

Do not consider loops with a zero backedge taken count as candidates for
interchange. This seems like a sensible thing to do to me, because it
suggests the loop doesn't execute and there is no point in
interchanging.

This avoids triggering an assert about phis and their uses. I have a
feeling that this fix might be hiding the issue, but I haven't yet been
able to trigger the assert with other test cases; every time the loops
are rejected for other reasons.

Since I think this is a self-contained improvement that avoids a lot
of test failures, I propose to reject this loops while I investigate
further if I can still trigger this in some way.

(Partial) fix for #163954
---
 llvm/lib/Transforms/Scalar/LoopInterchange.cpp      | 13 +++++++++++++
 .../LoopInterchange/interchanged-loop-nest-4.ll     |  2 +-
 .../LoopInterchange/lcssa-phi-outer-latch.ll        |  2 +-
 .../LoopInterchange/pr43176-move-to-new-latch.ll    |  2 +-
 llvm/test/Transforms/LoopInterchange/pr43326.ll     |  2 +-
 llvm/test/Transforms/LoopInterchange/pr57148.ll     |  2 +-
 .../reductions-across-inner-and-outer-loop.ll       |  2 +-
 7 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index 9aaf6a5aa4d6a..776560e678d8e 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -101,6 +101,12 @@ static cl::opt<unsigned int> MaxLoopNestDepth(
     "loop-interchange-max-loop-nest-depth", cl::init(10), cl::Hidden,
     cl::desc("Maximum depth of loop nest considered for the transform"));
 
+// This is mainly for testing purposes, and certain tests that rely on
+// behaviour that is more difficult to trigger otherwise.
+static cl::opt<bool> SkipLoopsWithZeroBTC(
+    "loop-interchange-skip-zero-btc", cl::init(true), cl::Hidden,
+    cl::desc("Do not consider loops with a backedge taken count of 0"));
+
 // We prefer cache cost to vectorization by default.
 static cl::list<RuleTy> Profitabilities(
     "loop-interchange-profitabilities", cl::ZeroOrMore,
@@ -428,6 +434,13 @@ static bool isComputableLoopNest(ScalarEvolution *SE,
       LLVM_DEBUG(dbgs() << "Couldn't compute backedge count\n");
       return false;
     }
+    // A loop with a backedge that isn't taken, e.g. an unconditional branch
+    // true, isn't really a loop and we don't want to consider it as a
+    // candidate.
+    if (ExitCountOuter && SkipLoopsWithZeroBTC && ExitCountOuter->isZero()) {
+      LLVM_DEBUG(dbgs() << "Single iteration loop\n");
+      return false;
+    }
     if (L->getNumBackEdges() != 1) {
       LLVM_DEBUG(dbgs() << "NumBackEdges is not equal to 1\n");
       return false;
diff --git a/llvm/test/Transforms/LoopInterchange/interchanged-loop-nest-4.ll b/llvm/test/Transforms/LoopInterchange/interchanged-loop-nest-4.ll
index 70fff161154d8..a4dbd5a005082 100644
--- a/llvm/test/Transforms/LoopInterchange/interchanged-loop-nest-4.ll
+++ b/llvm/test/Transforms/LoopInterchange/interchanged-loop-nest-4.ll
@@ -1,5 +1,5 @@
 ; REQUIRES: asserts
-; RUN: opt < %s -passes="loop(loop-interchange,loop-interchange)" -cache-line-size=8 -verify-dom-info -verify-loop-info \
+; RUN: opt < %s -passes="loop(loop-interchange,loop-interchange)" -cache-line-size=8 -verify-dom-info -verify-loop-info -loop-interchange-skip-zero-btc=false \
 ; RUN:  -debug-only=loop-interchange 2>&1 | FileCheck %s
 
 target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"
diff --git a/llvm/test/Transforms/LoopInterchange/lcssa-phi-outer-latch.ll b/llvm/test/Transforms/LoopInterchange/lcssa-phi-outer-latch.ll
index a5e3accaf8e10..ec02110c90e3b 100644
--- a/llvm/test/Transforms/LoopInterchange/lcssa-phi-outer-latch.ll
+++ b/llvm/test/Transforms/LoopInterchange/lcssa-phi-outer-latch.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
+; RUN: opt < %s -passes=loop-interchange -loop-interchange-skip-zero-btc=false -cache-line-size=64 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
 
 ; This test is checking that blocks outer.body and outer.latch, where outer.body is the exit
 ; block of the inner loop and outer.latch the latch of the outer loop, correctly
diff --git a/llvm/test/Transforms/LoopInterchange/pr43176-move-to-new-latch.ll b/llvm/test/Transforms/LoopInterchange/pr43176-move-to-new-latch.ll
index 6b25c3bc9a4ba..56140236825b7 100644
--- a/llvm/test/Transforms/LoopInterchange/pr43176-move-to-new-latch.ll
+++ b/llvm/test/Transforms/LoopInterchange/pr43176-move-to-new-latch.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 -pass-remarks-missed='loop-interchange' -pass-remarks-output=%t -S
+; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 -pass-remarks-missed='loop-interchange' -pass-remarks-output=%t -loop-interchange-skip-zero-btc=false -S
 ; RUN: FileCheck --input-file=%t %s
 
 @b = external dso_local global [5 x i32], align 16
diff --git a/llvm/test/Transforms/LoopInterchange/pr43326.ll b/llvm/test/Transforms/LoopInterchange/pr43326.ll
index c25c4fadd3042..8ce3d59e3420f 100644
--- a/llvm/test/Transforms/LoopInterchange/pr43326.ll
+++ b/llvm/test/Transforms/LoopInterchange/pr43326.ll
@@ -1,5 +1,5 @@
 ; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 -pass-remarks-missed='loop-interchange' -pass-remarks-output=%t -S \
-; RUN:     -verify-dom-info -verify-loop-info -verify-loop-lcssa -stats 2>&1
+; RUN:     -loop-interchange-skip-zero-btc=false -verify-dom-info -verify-loop-info -verify-loop-lcssa -stats 2>&1
 ; RUN: FileCheck --input-file=%t --check-prefix=REMARKS %s
 
 @a = global i32 0
diff --git a/llvm/test/Transforms/LoopInterchange/pr57148.ll b/llvm/test/Transforms/LoopInterchange/pr57148.ll
index 0d4194762a692..51cad15964ed4 100644
--- a/llvm/test/Transforms/LoopInterchange/pr57148.ll
+++ b/llvm/test/Transforms/LoopInterchange/pr57148.ll
@@ -1,5 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
-; RUN: opt < %s -passes=loop-interchange -cache-line-size=4 -loop-interchange-threshold=-100 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
+; RUN: opt < %s -passes=loop-interchange -loop-interchange-skip-zero-btc=false -cache-line-size=4 -loop-interchange-threshold=-100 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
 
 ; Make sure the loops are in LCSSA form after loop interchange,
 ; and loop interchange does not hit assertion errors and crash.
diff --git a/llvm/test/Transforms/LoopInterchange/reductions-across-inner-and-outer-loop.ll b/llvm/test/Transforms/LoopInterchange/reductions-across-inner-and-outer-loop.ll
index 27d99e05e84ee..fad6a6e70037c 100644
--- a/llvm/test/Transforms/LoopInterchange/reductions-across-inner-and-outer-loop.ll
+++ b/llvm/test/Transforms/LoopInterchange/reductions-across-inner-and-outer-loop.ll
@@ -1,5 +1,5 @@
 ; RUN: opt < %s -passes=loop-interchange -cache-line-size=64 -pass-remarks-missed='loop-interchange' -pass-remarks-output=%t -S \
-; RUN:     -verify-dom-info -verify-loop-info -verify-loop-lcssa -stats 2>&1 | FileCheck %s
+; RUN:     -loop-interchange-skip-zero-btc=false -verify-dom-info -verify-loop-info -verify-loop-lcssa -stats 2>&1 | FileCheck %s
 ; RUN: FileCheck --input-file=%t --check-prefix=REMARKS %s
 
 

>From 0e463b5c9c6dcdf947435972a6ea3c8804d0f13b Mon Sep 17 00:00:00 2001
From: Sjoerd Meijer <smeijer at nvidia.com>
Date: Sat, 8 Nov 2025 01:26:23 -0800
Subject: [PATCH 2/3] Forgto to add test-case

---
 .../Transforms/LoopInterchange/zero-btc.ll    | 56 +++++++++++++++++++
 1 file changed, 56 insertions(+)
 create mode 100644 llvm/test/Transforms/LoopInterchange/zero-btc.ll

diff --git a/llvm/test/Transforms/LoopInterchange/zero-btc.ll b/llvm/test/Transforms/LoopInterchange/zero-btc.ll
new file mode 100644
index 0000000000000..58d794e5def40
--- /dev/null
+++ b/llvm/test/Transforms/LoopInterchange/zero-btc.ll
@@ -0,0 +1,56 @@
+; 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 -cache-line-size=64 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
+
+; Test case for issue: https://github.com/llvm/llvm-project/issues/163954
+
+define void @test() {
+; CHECK-LABEL: define void @test() {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
+; CHECK:       [[OUTER_HEADER]]:
+; CHECK-NEXT:    [[I:%.*]] = phi i8 [ 0, %[[ENTRY]] ], [ [[DOTLCSSA:%.*]], %[[OUTER_LATCH:.*]] ]
+; CHECK-NEXT:    br label %[[INNER_HEADER:.*]]
+; CHECK:       [[INNER_HEADER]]:
+; CHECK-NEXT:    [[J:%.*]] = phi i64 [ 0, %[[OUTER_HEADER]] ], [ [[J_NEXT:%.*]], %[[INNER_LATCH:.*]] ]
+; CHECK-NEXT:    [[TMP0:%.*]] = phi i8 [ [[I]], %[[OUTER_HEADER]] ], [ [[TMP1:%.*]], %[[INNER_LATCH]] ]
+; CHECK-NEXT:    br label %[[INNER_BODY:.*]]
+; CHECK:       [[INNER_BODY]]:
+; CHECK-NEXT:    br i1 true, label %[[INNER_LATCH]], label %[[INNER_BODY]]
+; CHECK:       [[INNER_LATCH]]:
+; CHECK-NEXT:    [[TMP1]] = or i8 [[TMP0]], 0
+; CHECK-NEXT:    [[J_NEXT]] = add i64 [[J]], 1
+; CHECK-NEXT:    br i1 true, label %[[OUTER_LATCH]], label %[[INNER_HEADER]]
+; CHECK:       [[OUTER_LATCH]]:
+; CHECK-NEXT:    [[DOTLCSSA]] = phi i8 [ [[TMP1]], %[[INNER_LATCH]] ]
+; CHECK-NEXT:    br i1 true, label %[[EXIT:.*]], label %[[OUTER_HEADER]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br label %outer.header
+
+outer.header:
+  %i = phi i8 [ 0, %entry ], [ %1, %outer.latch ]
+  br label %inner.header
+
+inner.header:
+  %j = phi i64 [ 0, %outer.header ], [ %j.next, %inner.latch ]
+  %0 = phi i8 [ %i, %outer.header ], [ %1, %inner.latch ]
+  br label %inner.body
+
+inner.body:
+  br i1 true, label %inner.latch, label %inner.body   ; another (self) loop, but never taken
+
+inner.latch:
+  %1 = or i8 %0, 0
+  %j.next = add i64 %j, 1
+  br i1 true, label %outer.latch, label %inner.header
+
+outer.latch:
+  br i1 true, label %exit, label %outer.header
+
+exit:
+  ret void
+}

>From a6a1caf5aa5b4285e82995f3691e8a02eedbcbab Mon Sep 17 00:00:00 2001
From: Sjoerd Meijer <smeijer at nvidia.com>
Date: Mon, 10 Nov 2025 03:44:40 -0800
Subject: [PATCH 3/3] Removed datalayout from test, improved debug message, and
 clarified comment.

---
 llvm/lib/Transforms/Scalar/LoopInterchange.cpp   | 16 +++++++++-------
 llvm/test/Transforms/LoopInterchange/zero-btc.ll |  2 --
 2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
index 776560e678d8e..353de3aacd4be 100644
--- a/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopInterchange.cpp
@@ -101,12 +101,6 @@ static cl::opt<unsigned int> MaxLoopNestDepth(
     "loop-interchange-max-loop-nest-depth", cl::init(10), cl::Hidden,
     cl::desc("Maximum depth of loop nest considered for the transform"));
 
-// This is mainly for testing purposes, and certain tests that rely on
-// behaviour that is more difficult to trigger otherwise.
-static cl::opt<bool> SkipLoopsWithZeroBTC(
-    "loop-interchange-skip-zero-btc", cl::init(true), cl::Hidden,
-    cl::desc("Do not consider loops with a backedge taken count of 0"));
-
 // We prefer cache cost to vectorization by default.
 static cl::list<RuleTy> Profitabilities(
     "loop-interchange-profitabilities", cl::ZeroOrMore,
@@ -126,6 +120,13 @@ static cl::list<RuleTy> Profitabilities(
                           "Ignore profitability, force interchange (does not "
                           "work with other options)")));
 
+// FIXME: this option exists mainly for a couple of tests that check some
+// corner cases that is more difficult to trigger otherwise; these tests should
+// be rewritten and this option removed if possible.
+static cl::opt<bool> SkipLoopsWithZeroBTC(
+    "loop-interchange-skip-zero-btc", cl::init(true), cl::Hidden,
+    cl::desc("Do not consider loops with a backedge taken count of 0"));
+
 #ifndef NDEBUG
 static bool noDuplicateRulesAndIgnore(ArrayRef<RuleTy> Rules) {
   SmallSet<RuleTy, 4> Set;
@@ -438,7 +439,8 @@ static bool isComputableLoopNest(ScalarEvolution *SE,
     // true, isn't really a loop and we don't want to consider it as a
     // candidate.
     if (ExitCountOuter && SkipLoopsWithZeroBTC && ExitCountOuter->isZero()) {
-      LLVM_DEBUG(dbgs() << "Single iteration loop\n");
+      LLVM_DEBUG(dbgs() << "The loop back-edge isn't taken, rejecting single "
+                           "iteration loop\n");
       return false;
     }
     if (L->getNumBackEdges() != 1) {
diff --git a/llvm/test/Transforms/LoopInterchange/zero-btc.ll b/llvm/test/Transforms/LoopInterchange/zero-btc.ll
index 58d794e5def40..af2aa72adcb94 100644
--- a/llvm/test/Transforms/LoopInterchange/zero-btc.ll
+++ b/llvm/test/Transforms/LoopInterchange/zero-btc.ll
@@ -1,8 +1,6 @@
 ; 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 -cache-line-size=64 -verify-dom-info -verify-loop-info -verify-scev -verify-loop-lcssa -S | FileCheck %s
 
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32"
-
 ; Test case for issue: https://github.com/llvm/llvm-project/issues/163954
 
 define void @test() {



More information about the llvm-commits mailing list