[llvm] [LoopFusion] Reform LCSSA after peelFusionCandidate's peelLoop (PR #200442)

Madhur Amilkanthwar via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 4 01:12:18 PDT 2026


https://github.com/madhur13490 updated https://github.com/llvm/llvm-project/pull/200442

>From 91723626ba5f25f262e6d7db8bb1c734274cd48b Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Mon, 25 May 2026 05:52:17 -0700
Subject: [PATCH 1/2] [LoopFusion] Reform LCSSA after peelFusionCandidate's
 peelLoop

peelLoop's internal simplifyLoop call requires LCSSA to be preserved
across it, but the cloned exit edges and cloned defs that peelLoop
introduces are not reflected in the existing LCSSA phis, so the
contract cannot be honoured. Pass PreserveLCSSA=false to peelLoop here
and reform LCSSA on the affected nest immediately afterward. LCSSA is
expected before and after peel+fuse, just not during it.

Caught by yarpgen fuzzing of clang -O3 -fexperimental-loop-fusion
-mllvm -loop-fusion-peel-max-count=8 on AArch64.
---
 llvm/lib/Transforms/Scalar/LoopFuse.cpp       |  9 +++-
 .../LoopFusion/peel-preserve-lcssa.ll         | 45 +++++++++++++++++++
 2 files changed, 53 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll

diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index 0ea6da3bea1bf..f517f7a0022ff 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -63,6 +63,7 @@
 #include "llvm/Transforms/Utils/CodeMoverUtils.h"
 #include "llvm/Transforms/Utils/LoopPeel.h"
 #include "llvm/Transforms/Utils/LoopSimplify.h"
+#include "llvm/Transforms/Utils/LoopUtils.h"
 #include <list>
 
 using namespace llvm;
@@ -665,7 +666,13 @@ struct LoopFuser {
                       << " iterations of the first loop. \n");
 
     ValueToValueMapTy VMap;
-    peelLoop(FC0.L, PeelCount, false, &LI, &SE, DT, &AC, true, VMap);
+    // peelLoop can invalidate LCSSA on the surrounding nest; reform it
+    // explicitly below instead of asking peelLoop to preserve it.
+    peelLoop(FC0.L, PeelCount, /*PeelLast=*/false, &LI, &SE, DT, &AC,
+             /*PreserveLCSSA=*/false, VMap);
+    Loop *AffectedNest =
+        FC0.L->getParentLoop() ? FC0.L->getParentLoop() : FC0.L;
+    formLCSSARecursively(*AffectedNest, DT, &LI, &SE);
     FC0.Peeled = true;
     LLVM_DEBUG(dbgs() << "Done Peeling\n");
 
diff --git a/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll b/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll
new file mode 100644
index 0000000000000..ab37c68eee0ca
--- /dev/null
+++ b/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll
@@ -0,0 +1,45 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=loop-fusion -loop-fusion-peel-max-count=8 < %s | FileCheck %s
+
+define void @lcssa_peel_repro() {
+; CHECK-LABEL: define void @lcssa_peel_repro() {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br label %[[OUTER_HEADER:.*]]
+; CHECK:       [[OUTER_HEADER_LOOPEXIT:.*]]:
+; CHECK-NEXT:    br label %[[OUTER_HEADER]]
+; CHECK:       [[OUTER_HEADER]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ poison, %[[OUTER_HEADER_LOOPEXIT]] ]
+; CHECK-NEXT:    br label %[[INNER1_PEEL_BEGIN:.*]]
+; CHECK:       [[INNER1_PEEL_BEGIN]]:
+; CHECK-NEXT:    br label %[[INNER1_PEEL:.*]]
+; CHECK:       [[INNER1_PEEL]]:
+; CHECK-NEXT:    [[XOR_PEEL:%.*]] = xor i64 0, 0
+; CHECK-NEXT:    [[CMP_PEEL:%.*]] = icmp ult i64 0, 1
+; CHECK-NEXT:    br label %[[INNER1_PEEL_NEXT:.*]]
+; CHECK:       [[INNER1_PEEL_NEXT]]:
+; CHECK-NEXT:    br label %[[INNER1_PEEL_NEXT1:.*]]
+; CHECK:       [[INNER1_PEEL_NEXT1]]:
+; CHECK-NEXT:    br label %[[OUTER_HEADER_PEEL_NEWPH:.*]]
+; CHECK:       [[OUTER_HEADER_PEEL_NEWPH]]:
+; CHECK-NEXT:    br label %[[INNER1:.*]]
+; CHECK:       [[INNER1]]:
+; CHECK-NEXT:    [[XOR:%.*]] = xor i64 0, 0
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 1, 1
+; CHECK-NEXT:    br i1 false, label %[[INNER1]], label %[[OUTER_HEADER_LOOPEXIT]]
+;
+entry:
+  br label %outer.header
+
+outer.header:                                     ; preds = %inner2, %entry
+  %iv = phi i64 [ 0, %entry ], [ %xor, %inner2 ]
+  br label %inner1
+
+inner1:                                           ; preds = %inner1, %outer.header
+  %i = phi i64 [ 0, %outer.header ], [ 1, %inner1 ]
+  %xor = xor i64 0, 0
+  %cmp = icmp ult i64 %i, 1
+  br i1 %cmp, label %inner1, label %inner2
+
+inner2:                                           ; preds = %inner2, %inner1
+  br i1 false, label %inner2, label %outer.header
+}

>From 2bc32cf2eaac4daf0e0ea9f71b00c6c1f636330e Mon Sep 17 00:00:00 2001
From: Madhur Amilkanthwar <madhura at nvidia.com>
Date: Thu, 4 Jun 2026 01:09:40 -0700
Subject: [PATCH 2/2] fixup! [LoopFusion] Reform LCSSA after
 peelFusionCandidate's peelLoop

---
 llvm/lib/Transforms/Scalar/LoopFuse.cpp                | 9 +++------
 llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll | 4 ++--
 2 files changed, 5 insertions(+), 8 deletions(-)

diff --git a/llvm/lib/Transforms/Scalar/LoopFuse.cpp b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
index f517f7a0022ff..9e36c5e92806c 100644
--- a/llvm/lib/Transforms/Scalar/LoopFuse.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopFuse.cpp
@@ -63,7 +63,6 @@
 #include "llvm/Transforms/Utils/CodeMoverUtils.h"
 #include "llvm/Transforms/Utils/LoopPeel.h"
 #include "llvm/Transforms/Utils/LoopSimplify.h"
-#include "llvm/Transforms/Utils/LoopUtils.h"
 #include <list>
 
 using namespace llvm;
@@ -666,13 +665,11 @@ struct LoopFuser {
                       << " iterations of the first loop. \n");
 
     ValueToValueMapTy VMap;
-    // peelLoop can invalidate LCSSA on the surrounding nest; reform it
-    // explicitly below instead of asking peelLoop to preserve it.
+    // LoopFusion is a function pass that neither requires nor preserves
+    // LCSSA, so peelLoop need not preserve it across its internal
+    // simplifyLoop call.
     peelLoop(FC0.L, PeelCount, /*PeelLast=*/false, &LI, &SE, DT, &AC,
              /*PreserveLCSSA=*/false, VMap);
-    Loop *AffectedNest =
-        FC0.L->getParentLoop() ? FC0.L->getParentLoop() : FC0.L;
-    formLCSSARecursively(*AffectedNest, DT, &LI, &SE);
     FC0.Peeled = true;
     LLVM_DEBUG(dbgs() << "Done Peeling\n");
 
diff --git a/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll b/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll
index ab37c68eee0ca..295c07fe55f2c 100644
--- a/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll
+++ b/llvm/test/Transforms/LoopFusion/peel-preserve-lcssa.ll
@@ -8,7 +8,7 @@ define void @lcssa_peel_repro() {
 ; CHECK:       [[OUTER_HEADER_LOOPEXIT:.*]]:
 ; CHECK-NEXT:    br label %[[OUTER_HEADER]]
 ; CHECK:       [[OUTER_HEADER]]:
-; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ poison, %[[OUTER_HEADER_LOOPEXIT]] ]
+; CHECK-NEXT:    [[IV:%.*]] = phi i64 [ 0, %[[ENTRY]] ], [ [[XOR:%.*]], %[[OUTER_HEADER_LOOPEXIT]] ]
 ; CHECK-NEXT:    br label %[[INNER1_PEEL_BEGIN:.*]]
 ; CHECK:       [[INNER1_PEEL_BEGIN]]:
 ; CHECK-NEXT:    br label %[[INNER1_PEEL:.*]]
@@ -23,7 +23,7 @@ define void @lcssa_peel_repro() {
 ; CHECK:       [[OUTER_HEADER_PEEL_NEWPH]]:
 ; CHECK-NEXT:    br label %[[INNER1:.*]]
 ; CHECK:       [[INNER1]]:
-; CHECK-NEXT:    [[XOR:%.*]] = xor i64 0, 0
+; CHECK-NEXT:    [[XOR]] = xor i64 0, 0
 ; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i64 1, 1
 ; CHECK-NEXT:    br i1 false, label %[[INNER1]], label %[[OUTER_HEADER_LOOPEXIT]]
 ;



More information about the llvm-commits mailing list