[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