[llvm] [LoopFusion] Validate loop structure before creating LoopCandidates (PR #192280)

via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 15 08:51:55 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Madhur Amilkanthwar (madhur13490)

<details>
<summary>Changes</summary>

This patch add early check in LoopFuse.cpp
to skip loops missing preheader, header, exiting,
exit block or latch. It also makes `reportInvalidCandidate` gracefully return false instead of asserting when Preheader is null.

In other words this patch skips loops which lack the required structure.

Fixes #<!-- -->156670.

---
Full diff: https://github.com/llvm/llvm-project/pull/192280.diff


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/LoopFuse.cpp (+11-1) 
- (added) llvm/test/Transforms/LoopFusion/indirect-br.ll (+59) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index b577bf6584615..9c7500b0a290e 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -363,7 +363,9 @@ struct FusionCandidate {
 
   bool reportInvalidCandidate(Statistic &Stat) const {
     using namespace ore;
-    assert(L && Preheader && "Fusion candidate not initialized properly!");
+    assert(L && "Fusion candidate has null loop!");
+    if (!Preheader)
+      return false;
 #if LLVM_ENABLE_STATS
     ++Stat;
     ORE.emit(OptimizationRemarkAnalysis(DEBUG_TYPE, Stat.getName(),
@@ -573,6 +575,14 @@ struct LoopFuser {
   /// Flow Equivalent sets, sorted by dominance.
   void collectFusionCandidates(const LoopVector &LV) {
     for (Loop *L : LV) {
+      // Skip loops that do not have the structure required for fusion (e.g.
+      // loops with indirectbr may lack a preheader). Creating a FusionCandidate
+      // for such loops can lead to reportInvalidCandidate being called with
+      // a null Preheader.
+      if (!L->getLoopPreheader() || !L->getHeader() || !L->getExitingBlock() ||
+          !L->getExitBlock() || !L->getLoopLatch())
+        continue;
+
       TTI::PeelingPreferences PP =
           gatherPeelingPreferences(L, SE, TTI, std::nullopt, std::nullopt);
       FusionCandidate CurrCand(L, DT, &PDT, ORE, PP);
diff --git a/llvm/test/Transforms/LoopFusion/indirect-br.ll b/llvm/test/Transforms/LoopFusion/indirect-br.ll
new file mode 100644
index 0000000000000..2f95a3a6c4b6f
--- /dev/null
+++ b/llvm/test/Transforms/LoopFusion/indirect-br.ll
@@ -0,0 +1,59 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=loop-fusion < %s | FileCheck %s
+; Test that loop-fusion does not crash on functions with indirectbr and
+; loops that lack a preheader (or other required loop structure).
+
+define i32 @foo(ptr nocapture readonly %p) {
+; CHECK-LABEL: define i32 @foo(
+; CHECK-SAME: ptr readonly captures(none) [[P:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    [[TBL:%.*]] = alloca <2 x ptr>, align 16
+; CHECK-NEXT:    store <2 x ptr> <ptr blockaddress(@foo, %[[INDIRECT:.*]]), ptr blockaddress(@foo, %[[END:.*]])>, ptr [[TBL]], align 16
+; CHECK-NEXT:    br label %[[FOR_COND:.*]]
+; CHECK:       [[FOR_COND]]:
+; CHECK-NEXT:    [[CUR:%.*]] = phi ptr [ [[P]], %[[ENTRY]] ], [ [[NEXT:%.*]], %[[FOR_COND]] ]
+; CHECK-NEXT:    [[TMP0:%.*]] = load i8, ptr [[CUR]], align 1
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i8 [[TMP0]], 7
+; CHECK-NEXT:    [[NEXT]] = getelementptr inbounds i8, ptr [[CUR]], i64 1
+; CHECK-NEXT:    br i1 [[CMP]], label %[[INDIRECT_PREHEADER:.*]], label %[[FOR_COND]]
+; CHECK:       [[INDIRECT_PREHEADER]]:
+; CHECK-NEXT:    [[TMP1:%.*]] = load i8, ptr [[NEXT]], align 1
+; CHECK-NEXT:    [[IDX:%.*]] = sext i8 [[TMP1]] to i64
+; CHECK-NEXT:    [[SLOT:%.*]] = getelementptr inbounds <2 x ptr>, ptr [[TBL]], i64 0, i64 [[IDX]]
+; CHECK-NEXT:    [[TMP2:%.*]] = load ptr, ptr [[SLOT]], align 8
+; CHECK-NEXT:    br label %[[INDIRECT]]
+; CHECK:       [[INDIRECT]]:
+; CHECK-NEXT:    indirectbr ptr [[TMP2]], [label %[[INDIRECT]], label %[[END]]]
+; CHECK:       [[INDIRECT2:.*:]]
+; CHECK-NEXT:    indirectbr ptr [[TMP2]], [label %[[INDIRECT]], label %[[END]]]
+; CHECK:       [[END]]:
+; CHECK-NEXT:    ret i32 0
+;
+entry:
+  %tbl = alloca <2 x ptr>, align 16
+  store <2 x ptr> <ptr blockaddress(@foo, %indirect), ptr blockaddress(@foo, %exit)>, ptr %tbl, align 16
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.cond, %entry
+  %cur = phi ptr [ %p, %entry ], [ %next, %for.cond ]
+  %val = load i8, ptr %cur, align 1
+  %cmp = icmp eq i8 %val, 7
+  %next = getelementptr inbounds i8, ptr %cur, i64 1
+  br i1 %cmp, label %indirect.preheader, label %for.cond
+
+indirect.preheader:                               ; preds = %for.cond
+  %raw = load i8, ptr %next, align 1
+  %idx = sext i8 %raw to i64
+  %slot = getelementptr inbounds <2 x ptr>, ptr %tbl, i64 0, i64 %idx
+  %dest = load ptr, ptr %slot, align 8
+  br label %indirect
+
+indirect:
+  indirectbr ptr %dest, [label %indirect, label %exit]
+
+indirect2:
+  indirectbr ptr %dest, [label %indirect, label %exit]
+
+exit:
+  ret i32 0
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/192280


More information about the llvm-commits mailing list