[llvm] [LoopSplitUtils] Prevent erasure of non-dead latch compares (PR #214243)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 5 07:55:23 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Sean Clarke (xarkenz)

<details>
<summary>Changes</summary>

In the case where a loop latch gets rewritten after cloning but the original latch compare only has uses outside the loop, the instruction seemingly has no uses since SSA has not yet been reconstructed, and thus is erased. Since it is captured as an escaping value during the initial phase, a use-after-free then occurs in attempting to reconstruct SSA, usually resulting in a crash. Instead, defer erasing the latch compares until after SSA has been reconstructed.

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


2 Files Affected:

- (modified) llvm/lib/Transforms/Utils/LoopSplitUtils.cpp (+19-7) 
- (added) llvm/test/Transforms/LoopSplit/liveout.ll (+119) 


``````````diff
diff --git a/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp b/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
index db07e3f571f66..65474db56e829 100644
--- a/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
+++ b/llvm/lib/Transforms/Utils/LoopSplitUtils.cpp
@@ -113,6 +113,11 @@ struct LoopSplitUtils::SplitState {
   /// Values that must survive across partitions (carried and/or live-out).
   SmallVector<EscapingValue, 8> Escaping;
 
+  /// Latch compares displaced by rewriteLatch() that had no remaining use at
+  /// the time. Once SSA is reconstructed, we can know for certain which ones
+  /// are dead and erase them at that point.
+  SmallVector<ICmpInst *, 4> DanglingLatchCmps;
+
   EscapingValue &addEscaping(Value *Def) { return Escaping.emplace_back(Def); }
 };
 
@@ -319,6 +324,10 @@ bool LoopSplitUtils::split() {
   clonePartitions(S);
   chainPartitions(S);
   reconstructSSA(S);
+  // Strip out any remaining dead latch compares.
+  for (ICmpInst *Cmp : S.DanglingLatchCmps)
+    if (Cmp->use_empty())
+      Cmp->eraseFromParent();
   ExpanderCleaner.markResultUsed();
   return true;
 }
@@ -493,9 +502,11 @@ void LoopSplitUtils::clonePartitions(SplitState &S) {
 }
 
 // Replace a partition's latch test so it iterates only within [start, SelEnd].
-static void rewriteLatch(Loop *PL, Value *IndOp, Value *SelEnd,
-                         BasicBlock *Exit, bool Signed, bool Descending,
-                         bool LatchComparesPHI) {
+// Returns the old latch compare if it is now (seemingly) unused, for the caller
+// to erase if it is still dead after SSA has been rebuilt.
+static ICmpInst *rewriteLatch(Loop *PL, Value *IndOp, Value *SelEnd,
+                              BasicBlock *Exit, bool Signed, bool Descending,
+                              bool LatchComparesPHI) {
   auto *Term = cast<CondBrInst>(PL->getLoopLatch()->getTerminator());
   auto *Cmp = cast<ICmpInst>(Term->getCondition());
   IRBuilder<> B(Cmp);
@@ -518,8 +529,7 @@ static void rewriteLatch(Loop *PL, Value *IndOp, Value *SelEnd,
         /*IsExpected=*/false);
   }
   Term->eraseFromParent();
-  if (Cmp->use_empty())
-    Cmp->eraseFromParent();
+  return Cmp->use_empty() ? Cmp : nullptr;
 }
 
 // Pass 2: emit each partition's guard branch, clamp its latch, wire the
@@ -568,8 +578,10 @@ void LoopSplitUtils::chainPartitions(SplitState &S) {
     }
     GuardTerm->eraseFromParent();
 
-    rewriteLatch(P.SubLoop, P.LatchIndOp, P.SelEnd, P.Exit, InductionIsSigned,
-                 S.Descending, S.LatchComparesPHI);
+    if (ICmpInst *DanglingCmp =
+            rewriteLatch(P.SubLoop, P.LatchIndOp, P.SelEnd, P.Exit,
+                         InductionIsSigned, S.Descending, S.LatchComparesPHI))
+      S.DanglingLatchCmps.push_back(DanglingCmp);
     P.Exit->getTerminator()->setSuccessor(0, MergeAfter);
   }
 
diff --git a/llvm/test/Transforms/LoopSplit/liveout.ll b/llvm/test/Transforms/LoopSplit/liveout.ll
new file mode 100644
index 0000000000000..d77ee38a31867
--- /dev/null
+++ b/llvm/test/Transforms/LoopSplit/liveout.ll
@@ -0,0 +1,119 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; REQUIRES: asserts
+; RUN: opt -passes=loop-split-utils -loop-split-points=50 -S < %s | FileCheck %s
+
+declare void @use(i32)
+
+define i1 @latchcmp_liveout(i32 %n) {
+; CHECK-LABEL: define i1 @latchcmp_liveout(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT:  [[LS_GUARD0:.*]]:
+; CHECK-NEXT:    [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
+; CHECK-NEXT:    [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
+; CHECK-NEXT:    [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 49)
+; CHECK-NEXT:    [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
+; CHECK-NEXT:    br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
+; CHECK:       [[ENTRY]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    call void @use(i32 [[IV]])
+; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    br label %[[LS_GUARD1]]
+; CHECK:       [[LS_GUARD1]]:
+; CHECK-NEXT:    [[CMP5:%.*]] = phi i1 [ [[CMP]], %[[EXIT]] ], [ poison, %[[LS_GUARD0]] ]
+; CHECK-NEXT:    [[ITR_CHK2:%.*]] = icmp sle i32 50, [[TMP0]]
+; CHECK-NEXT:    br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
+; CHECK:       [[ENTRY_LS1]]:
+; CHECK-NEXT:    br label %[[LOOP_LS1:.*]]
+; CHECK:       [[LOOP_LS1]]:
+; CHECK-NEXT:    [[IV_LS1:%.*]] = phi i32 [ 50, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LOOP_LS1]] ]
+; CHECK-NEXT:    call void @use(i32 [[IV_LS1]])
+; CHECK-NEXT:    [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
+; CHECK-NEXT:    [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
+; CHECK-NEXT:    [[CMP_LS1:%.*]] = icmp slt i32 [[IV_NEXT_LS1]], [[N]]
+; CHECK-NEXT:    br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
+; CHECK:       [[LS_EXIT1]]:
+; CHECK-NEXT:    br label %[[LS_FINAL_EXIT]]
+; CHECK:       [[LS_FINAL_EXIT]]:
+; CHECK-NEXT:    [[CMP4:%.*]] = phi i1 [ [[CMP_LS1]], %[[LS_EXIT1]] ], [ [[CMP5]], %[[LS_GUARD1]] ]
+; CHECK-NEXT:    ret i1 [[CMP4]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+  call void @use(i32 %iv)
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp slt i32 %iv.next, %n
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  %cmp.lcssa = phi i1 [ %cmp, %loop ]
+  ret i1 %cmp.lcssa
+}
+
+define i1 @latchcmp_liveout_extra_use(i32 %n) {
+; CHECK-LABEL: define i1 @latchcmp_liveout_extra_use(
+; CHECK-SAME: i32 [[N:%.*]]) {
+; CHECK-NEXT:  [[LS_GUARD0:.*]]:
+; CHECK-NEXT:    [[SMAX:%.*]] = call i32 @llvm.smax.i32(i32 [[N]], i32 1)
+; CHECK-NEXT:    [[TMP0:%.*]] = add nsw i32 [[SMAX]], -1
+; CHECK-NEXT:    [[SMIN:%.*]] = call i32 @llvm.smin.i32(i32 [[TMP0]], i32 49)
+; CHECK-NEXT:    [[ITR_CHK:%.*]] = icmp sle i32 0, [[SMIN]]
+; CHECK-NEXT:    br i1 [[ITR_CHK]], label %[[ENTRY:.*]], label %[[LS_GUARD1:.*]]
+; CHECK:       [[ENTRY]]:
+; CHECK-NEXT:    br label %[[LOOP:.*]]
+; CHECK:       [[LOOP]]:
+; CHECK-NEXT:    [[IV:%.*]] = phi i32 [ 0, %[[ENTRY]] ], [ [[IV_NEXT:%.*]], %[[LOOP]] ]
+; CHECK-NEXT:    call void @use(i32 [[IV]])
+; CHECK-NEXT:    [[IV_NEXT]] = add i32 [[IV]], 1
+; CHECK-NEXT:    [[ITR_CHK1:%.*]] = icmp sle i32 [[IV_NEXT]], [[SMIN]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[IV_NEXT]], [[N]]
+; CHECK-NEXT:    [[CMP_EXT:%.*]] = zext i1 [[CMP]] to i32
+; CHECK-NEXT:    call void @use(i32 [[CMP_EXT]])
+; CHECK-NEXT:    br i1 [[ITR_CHK1]], label %[[LOOP]], label %[[EXIT:.*]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    br label %[[LS_GUARD1]]
+; CHECK:       [[LS_GUARD1]]:
+; CHECK-NEXT:    [[CMP5:%.*]] = phi i1 [ [[CMP]], %[[EXIT]] ], [ poison, %[[LS_GUARD0]] ]
+; CHECK-NEXT:    [[ITR_CHK2:%.*]] = icmp sle i32 50, [[TMP0]]
+; CHECK-NEXT:    br i1 [[ITR_CHK2]], label %[[ENTRY_LS1:.*]], label %[[LS_FINAL_EXIT:.*]]
+; CHECK:       [[ENTRY_LS1]]:
+; CHECK-NEXT:    br label %[[LOOP_LS1:.*]]
+; CHECK:       [[LOOP_LS1]]:
+; CHECK-NEXT:    [[IV_LS1:%.*]] = phi i32 [ 50, %[[ENTRY_LS1]] ], [ [[IV_NEXT_LS1:%.*]], %[[LOOP_LS1]] ]
+; CHECK-NEXT:    call void @use(i32 [[IV_LS1]])
+; CHECK-NEXT:    [[IV_NEXT_LS1]] = add i32 [[IV_LS1]], 1
+; CHECK-NEXT:    [[ITR_CHK3:%.*]] = icmp sle i32 [[IV_NEXT_LS1]], [[TMP0]]
+; CHECK-NEXT:    [[CMP_LS1:%.*]] = icmp slt i32 [[IV_NEXT_LS1]], [[N]]
+; CHECK-NEXT:    [[CMP_EXT_LS1:%.*]] = zext i1 [[CMP_LS1]] to i32
+; CHECK-NEXT:    call void @use(i32 [[CMP_EXT_LS1]])
+; CHECK-NEXT:    br i1 [[ITR_CHK3]], label %[[LOOP_LS1]], label %[[LS_EXIT1:.*]]
+; CHECK:       [[LS_EXIT1]]:
+; CHECK-NEXT:    br label %[[LS_FINAL_EXIT]]
+; CHECK:       [[LS_FINAL_EXIT]]:
+; CHECK-NEXT:    [[CMP4:%.*]] = phi i1 [ [[CMP_LS1]], %[[LS_EXIT1]] ], [ [[CMP5]], %[[LS_GUARD1]] ]
+; CHECK-NEXT:    ret i1 [[CMP4]]
+;
+entry:
+  br label %loop
+
+loop:
+  %iv = phi i32 [ 0, %entry ], [ %iv.next, %loop ]
+  call void @use(i32 %iv)
+  %iv.next = add i32 %iv, 1
+  %cmp = icmp slt i32 %iv.next, %n
+  %cmp.ext = zext i1 %cmp to i32
+  call void @use(i32 %cmp.ext)
+  br i1 %cmp, label %loop, label %exit
+
+exit:
+  %cmp.lcssa = phi i1 [ %cmp, %loop ]
+  ret i1 %cmp.lcssa
+}

``````````

</details>


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


More information about the llvm-commits mailing list