[llvm] [LoopPredication] Treat exits leading to deopt/unreachable as cold (PR #201860)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 5 08:06:40 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Jan Ječmen (JanJecmen)

<details>
<summary>Changes</summary>

Use getPostdominatingDeoptimizeCall() instead of
getTerminatingDeoptimizeCall() so exits that eventually reach a deoptimize are recognized.

For non-latch exits without useful profile data, treat an exit block that leads to unreachable or deoptimize as zero-probability rather than 1/NumSucc. This lets predication fire in loops where the latch exits with a low probability while there is a deoptimizing exit without profiling data (which was treated previously as 50% exit).

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


2 Files Affected:

- (modified) llvm/lib/Transforms/Scalar/LoopPredication.cpp (+19-11) 
- (modified) llvm/test/Transforms/LoopPredication/profitability.ll (+62) 


``````````diff
diff --git a/llvm/lib/Transforms/Scalar/LoopPredication.cpp b/llvm/lib/Transforms/Scalar/LoopPredication.cpp
index de5365271e233..15f1718e7ecf6 100644
--- a/llvm/lib/Transforms/Scalar/LoopPredication.cpp
+++ b/llvm/lib/Transforms/Scalar/LoopPredication.cpp
@@ -904,7 +904,7 @@ bool LoopPredication::isLoopProfitableToPredicate() {
   // predicate on that latch check.
   auto *LatchExitBlock = LatchTerm->getSuccessor(LatchBrExitIdx);
   if (isa<UnreachableInst>(LatchTerm) ||
-      LatchExitBlock->getTerminatingDeoptimizeCall())
+      LatchExitBlock->getPostdominatingDeoptimizeCall())
     return false;
 
   // Latch terminator has no valid profile data, so nothing to check
@@ -916,7 +916,6 @@ bool LoopPredication::isLoopProfitableToPredicate() {
       [&](const BasicBlock *ExitingBlock,
           const BasicBlock *ExitBlock) -> BranchProbability {
     auto *Term = ExitingBlock->getTerminator();
-    unsigned NumSucc = Term->getNumSuccessors();
     if (MDNode *ProfileData = getValidBranchWeightMDNode(*Term)) {
       SmallVector<uint32_t> Weights;
       extractBranchWeights(ProfileData, Weights);
@@ -926,16 +925,25 @@ bool LoopPredication::isLoopProfitableToPredicate() {
           Numerator += Weight;
         Denominator += Weight;
       }
-      // If all weights are zero act as if there was no profile data
-      if (Denominator == 0)
-        return BranchProbability::getBranchProbability(1, NumSucc);
-      return BranchProbability::getBranchProbability(Numerator, Denominator);
-    } else {
-      assert(LatchBlock != ExitingBlock &&
-             "Latch term should always have profile data!");
-      // No profile data, so we choose the weight as 1/num_of_succ(Src)
-      return BranchProbability::getBranchProbability(1, NumSucc);
+      if (Denominator != 0)
+        return BranchProbability::getBranchProbability(Numerator, Denominator);
+      // Fall-through: If all weights are zero, treat this as if there was
+      // no profile data.
+    }
+
+    // For non-latch exits with no useful profile data, treat an exit that
+    // leads to unreachable or to a deoptimize as cold. Does not apply
+    //  to the latch (already bailed out above).
+    if (ExitingBlock != LatchBlock) {
+      auto *ExitTerm = ExitBlock->getTerminator();
+      if (isa<UnreachableInst>(ExitTerm) ||
+          ExitBlock->getPostdominatingDeoptimizeCall())
+        return BranchProbability::getZero();
     }
+
+    // Otherwise, assume the exit edge is one of N equally likely successors.
+    unsigned NumSucc = Term->getNumSuccessors();
+    return BranchProbability::getBranchProbability(1, NumSucc);
   };
 
   BranchProbability LatchExitProbability =
diff --git a/llvm/test/Transforms/LoopPredication/profitability.ll b/llvm/test/Transforms/LoopPredication/profitability.ll
index 6fa953e5b268e..985cf693cd07e 100644
--- a/llvm/test/Transforms/LoopPredication/profitability.ll
+++ b/llvm/test/Transforms/LoopPredication/profitability.ll
@@ -172,6 +172,68 @@ exit:                                             ; preds = %Header
   %result.le = load i64, ptr %result.in3.lcssa, align 8
   ret i64 %result.le
 }
+
+; predicate loop since we have an exit block with unknown probability but
+; it leads to deopt so we assume it's cold.
+define i64 @predicate2(ptr nocapture readonly %arg, i32 %length, ptr nocapture readonly %arg2, ptr nocapture readonly %n_addr, i64 %i) !prof !21 {
+; CHECK-LABEL: @predicate2(
+; CHECK-NEXT:  entry:
+; CHECK-NEXT:    [[LENGTH_EXT:%.*]] = zext i32 [[LENGTH:%.*]] to i64
+; CHECK-NEXT:    [[N_PRE:%.*]] = load i64, ptr [[N_ADDR:%.*]], align 4
+; CHECK-NEXT:    [[TMP0:%.*]] = icmp ule i64 1048576, [[LENGTH_EXT]]
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ult i64 0, [[LENGTH_EXT]]
+; CHECK-NEXT:    [[TMP2:%.*]] = and i1 [[TMP1]], [[TMP0]]
+; CHECK-NEXT:    [[TMP3:%.*]] = freeze i1 [[TMP2]]
+; CHECK-NEXT:    br label [[HEADER:%.*]]
+; CHECK:       Header:
+; CHECK-NEXT:    [[RESULT_IN3:%.*]] = phi ptr [ [[ARG2:%.*]], [[ENTRY:%.*]] ], [ [[ARG:%.*]], [[LATCH:%.*]] ]
+; CHECK-NEXT:    [[J2:%.*]] = phi i64 [ 0, [[ENTRY]] ], [ [[J_NEXT:%.*]], [[LATCH]] ]
+; CHECK-NEXT:    [[WITHIN_BOUNDS:%.*]] = icmp ult i64 [[J2]], [[LENGTH_EXT]]
+; CHECK-NEXT:    call void (i1, ...) @llvm.experimental.guard(i1 [[TMP3]], i32 9) [ "deopt"() ]
+; CHECK-NEXT:    call void @llvm.assume(i1 [[WITHIN_BOUNDS]])
+; CHECK-NEXT:    [[INNERCMP:%.*]] = icmp eq i64 [[J2]], [[N_PRE]]
+; CHECK-NEXT:    [[J_NEXT]] = add nuw nsw i64 [[J2]], 1
+; CHECK-NEXT:    br i1 [[INNERCMP]], label [[LATCH]], label [[EVENTUAL_DEOPT:%.*]]
+; CHECK:       Latch:
+; CHECK-NEXT:    [[SPECULATE_TRIP_COUNT:%.*]] = icmp ult i64 [[J_NEXT]], 1048576
+; CHECK-NEXT:    br i1 [[SPECULATE_TRIP_COUNT]], label [[HEADER]], label [[EXITLATCH:%.*]], !prof [[PROF3]]
+; CHECK:       exitLatch:
+; CHECK-NEXT:    ret i64 1
+; CHECK:       eventual_deopt:
+; CHECK-NEXT:    br label [[DEOPT:%.*]]
+; CHECK:       deopt:
+; CHECK-NEXT:    [[COUNTED_SPECULATION_FAILED:%.*]] = call i64 (...) @llvm.experimental.deoptimize.i64(i64 30) [ "deopt"(i32 0) ]
+; CHECK-NEXT:    ret i64 [[COUNTED_SPECULATION_FAILED]]
+;
+entry:
+  %length.ext = zext i32 %length to i64
+  %n.pre = load i64, ptr %n_addr, align 4
+  br label %Header
+
+Header:                                          ; preds = %entry, %Latch
+  %result.in3 = phi ptr [ %arg2, %entry ], [ %arg, %Latch ]
+  %j2 = phi i64 [ 0, %entry ], [ %j.next, %Latch ]
+  %within.bounds = icmp ult i64 %j2, %length.ext
+  call void (i1, ...) @llvm.experimental.guard(i1 %within.bounds, i32 9) [ "deopt"() ]
+  %innercmp = icmp eq i64 %j2, %n.pre
+  %j.next = add nuw nsw i64 %j2, 1
+  br i1 %innercmp, label %Latch, label %eventual_deopt
+
+Latch:                                           ; preds = %Header
+  %speculate_trip_count = icmp ult i64 %j.next, 1048576
+  br i1 %speculate_trip_count, label %Header, label %exitLatch, !prof !2
+
+exitLatch:                                            ; preds = %Latch
+  ret i64 1
+
+eventual_deopt:                                             ; preds = %Header
+  br label %deopt
+
+deopt:                                            ; preds = %eventual_deopt_true, %eventual_deopt_false
+  %counted_speculation_failed = call i64 (...) @llvm.experimental.deoptimize.i64(i64 30) [ "deopt"(i32 0) ]
+  ret i64 %counted_speculation_failed
+}
+
 declare i64 @llvm.experimental.deoptimize.i64(...)
 declare void @llvm.experimental.guard(i1, ...)
 

``````````

</details>


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


More information about the llvm-commits mailing list