[llvm] [SCEV] Extend isBasicBlockEntryGuardedByCond to handle merge block (PR #190602)
Wenju He via llvm-commits
llvm-commits at lists.llvm.org
Mon Apr 6 22:55:00 PDT 2026
https://github.com/wenju-he updated https://github.com/llvm/llvm-project/pull/190602
>From 5390864a561706c43a3aa93c679078659d406e0c Mon Sep 17 00:00:00 2001
From: Wenju He <wenju.he at intel.com>
Date: Mon, 6 Apr 2026 14:08:15 +0200
Subject: [PATCH 1/2] [SCEV] Extend isBasicBlockEntryGuardedByCond to handle
merge block
In a test of two consecutive loops with `eq` compare predidate, SCEV
computes constant trip counts for them before unrolling. However, after
unrolling the first loop, the second loop's header is a merge block with
32 predecessors. isBasicBlockEntryGuardedByCond stops at the merge
block, preventing SCEV from re-computing constant trip count for the
second loop. This causes the second loop failing to fully unroll and
a perf regression from 183K to 405K cycles on an intel gpu simulator.
This PR refines the predecessor chain to handle a merge block. If all
predecessors of the merge block are proved to be guarded by the
condition, SCEV can re-compute constant trip count for the second loop.
---
llvm/lib/Analysis/ScalarEvolution.cpp | 40 ++++++++++++----
.../ScalarEvolution/exit-count-select-safe.ll | 2 +-
.../eq-upperbound-scev-merged-preheader.ll | 46 +++++++++++++++++++
.../LoopVectorize/runtime-checks-hoist.ll | 6 +--
4 files changed, 81 insertions(+), 13 deletions(-)
create mode 100644 llvm/test/Transforms/LoopUnroll/eq-upperbound-scev-merged-preheader.ll
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 2862acfedb91d..38c575e9ee56b 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -12028,15 +12028,37 @@ bool ScalarEvolution::isBasicBlockEntryGuardedByCond(const BasicBlock *BB,
PredBB = ContainingLoop->getLoopPredecessor();
else
PredBB = BB->getSinglePredecessor();
- for (std::pair<const BasicBlock *, const BasicBlock *> Pair(PredBB, BB);
- Pair.first; Pair = getPredecessorWithUniqueSuccessorForBB(Pair.first)) {
- const CondBrInst *BlockEntryPredicate =
- dyn_cast<CondBrInst>(Pair.first->getTerminator());
- if (!BlockEntryPredicate)
- continue;
-
- if (ProveViaCond(BlockEntryPredicate->getCondition(),
- BlockEntryPredicate->getSuccessor(0) != Pair.second))
+ // For simplificity, only one merge block is handled.
+ const BasicBlock *MergeBlock = nullptr;
+ unsigned NumVisits = 0;
+ auto ProveViaPredecessorChain = [&](const BasicBlock *Pred,
+ const BasicBlock *Succ,
+ unsigned MaxNumVisits) {
+ for (std::pair<const BasicBlock *, const BasicBlock *> Pair(Pred, Succ);
+ Pair.first;
+ Pair = getPredecessorWithUniqueSuccessorForBB(Pair.first)) {
+ if (NumVisits++ > MaxNumVisits)
+ return false;
+ const CondBrInst *BlockEntryPredicate =
+ dyn_cast<CondBrInst>(Pair.first->getTerminator());
+ if (BlockEntryPredicate &&
+ ProveViaCond(BlockEntryPredicate->getCondition(),
+ BlockEntryPredicate->getSuccessor(0) != Pair.second))
+ return true;
+ if (!MergeBlock && Pair.first->hasNPredecessorsOrMore(2))
+ MergeBlock = Pair.first;
+ }
+ return false;
+ };
+ unsigned MaxChainVisits = ~0u; // No limit for the first predecessor chain.
+ if (ProveViaPredecessorChain(PredBB, BB, MaxChainVisits))
+ return true;
+ if (MergeBlock) {
+ MaxChainVisits = 128;
+ auto ProveMergeBlockPredecessor = [&](const BasicBlock *Pred) {
+ return ProveViaPredecessorChain(Pred, MergeBlock, MaxChainVisits);
+ };
+ if (all_of(predecessors(MergeBlock), ProveMergeBlockPredecessor))
return true;
}
diff --git a/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll b/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
index 1043b2f6f56f6..06b9bdb2b3516 100644
--- a/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
+++ b/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
@@ -443,7 +443,7 @@ define i32 @computeSCEVAtScope(i32 %d.0) {
; CHECK-NEXT: %inc3 = add nsw i32 %e.1, 1
; CHECK-NEXT: --> {(1 + %d.0),+,1}<nw><%for.cond> U: full-set S: full-set Exits: 1 LoopDispositions: { %for.cond: Computable, %while.cond: Variant }
; CHECK-NEXT: %f.1 = phi i32 [ %inc8, %for.body5 ], [ 0, %for.cond4.preheader ]
-; CHECK-NEXT: --> {0,+,1}<nuw><nsw><%for.cond4> U: [0,1) S: [0,1) Exits: 0 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
+; CHECK-NEXT: --> {0,+,1}<%for.cond4> U: full-set S: full-set Exits: 0 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
; CHECK-NEXT: %inc8 = add i32 %f.1, 1
; CHECK-NEXT: --> {1,+,1}<nuw><nsw><%for.cond4> U: [1,2) S: [1,2) Exits: 1 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
; CHECK-NEXT: Determining loop execution counts for: @computeSCEVAtScope
diff --git a/llvm/test/Transforms/LoopUnroll/eq-upperbound-scev-merged-preheader.ll b/llvm/test/Transforms/LoopUnroll/eq-upperbound-scev-merged-preheader.ll
new file mode 100644
index 0000000000000..738641d8c97b1
--- /dev/null
+++ b/llvm/test/Transforms/LoopUnroll/eq-upperbound-scev-merged-preheader.ll
@@ -0,0 +1,46 @@
+; RUN: opt -passes='print<scalar-evolution>,loop-unroll<upperbound>,print<scalar-evolution>' -unroll-max-upperbound=32 -disable-output %s 2>&1 | FileCheck %s
+
+; Verify SCEV check all predecessors of a merge block (loop header of the
+; second loop) to compute constant trip count after unrolling the first loop.
+
+; Disable loop 2 unroll to check SCEV computation after loop 1 unroll.
+
+; CHECK: Determining loop execution counts for: @test_merge_is_loop_header
+; CHECK: Loop %for.body2: constant max backedge-taken count is i32 31
+; CHECK: Loop %for.body: constant max backedge-taken count is i32 31
+
+; CHECK: Determining loop execution counts for: @test_merge_is_loop_header
+; CHECK: Loop %for.body2: constant max backedge-taken count is i32 31
+
+declare i32 @llvm.umin.i32(i32, i32)
+
+define void @test_merge_is_loop_header(i32 %0) {
+entry:
+ %results = alloca i64, align 8
+ %trip.count = tail call i32 @llvm.umin.i32(i32 %0, i32 32)
+ %cmp0 = icmp eq i32 %trip.count, 0
+ br i1 %cmp0, label %for.end, label %for.body
+
+for.body: ; preds = %for.body, %entry
+ %i = phi i32 [ %inc, %for.body ], [ 0, %entry ]
+ store i64 0, ptr %results, align 8
+ %inc = add i32 %i, 1
+ %cmp = icmp eq i32 %inc, %trip.count
+ br i1 %cmp, label %for.body2, label %for.body
+
+for.cond: ; preds = %for.body2
+ %inc2 = add nuw nsw i32 %i2, 1
+ %cmp2 = icmp eq i32 %inc2, %trip.count
+ br i1 %cmp2, label %for.end, label %for.body2, !llvm.loop !0
+
+for.body2: ; preds = %for.cond, %for.body
+ %i2 = phi i32 [ %inc2, %for.cond ], [ 0, %for.body ]
+ %tmp = load i64, ptr %results, align 8
+ br label %for.cond
+
+for.end: ; preds = %for.cond, %entry
+ ret void
+}
+
+!0 = distinct !{!0, !1}
+!1 = !{!"llvm.loop.unroll.disable"}
diff --git a/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll b/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
index 1ac7087ae9143..7a78c13c83841 100644
--- a/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
+++ b/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
@@ -639,11 +639,11 @@ define void @triple_nested_loop_mixed_access(ptr nocapture noundef %dst, ptr noc
; CHECK-NEXT: [[WIDE_TRIP_COUNT68:%.*]] = zext i32 [[M]] to i64
; CHECK-NEXT: [[WIDE_TRIP_COUNT60:%.*]] = zext i32 [[N]] to i64
; CHECK-NEXT: [[WIDE_TRIP_COUNT:%.*]] = zext i32 [[O]] to i64
-; CHECK-NEXT: [[TMP3:%.*]] = mul i64 [[TMP1]], [[TMP2]]
+; CHECK-NEXT: [[TMP3:%.*]] = mul i64 [[WIDE_TRIP_COUNT60]], [[TMP2]]
; CHECK-NEXT: [[TMP4:%.*]] = shl i64 [[TMP3]], 2
-; CHECK-NEXT: [[TMP5:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[TMP1]]
+; CHECK-NEXT: [[TMP5:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[WIDE_TRIP_COUNT60]]
; CHECK-NEXT: [[TMP6:%.*]] = shl i64 [[TMP5]], 2
-; CHECK-NEXT: [[TMP7:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[TMP1]]
+; CHECK-NEXT: [[TMP7:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[WIDE_TRIP_COUNT60]]
; CHECK-NEXT: [[TMP8:%.*]] = shl i64 [[TMP7]], 2
; CHECK-NEXT: [[TMP9:%.*]] = shl nuw nsw i64 [[WIDE_TRIP_COUNT]], 2
; CHECK-NEXT: br label [[OUTER_OUTER_LOOP:%.*]]
>From 6b3804c40c922229ea1283bb3e8e408943c7c80a Mon Sep 17 00:00:00 2001
From: Wenju He <wenju.he at intel.com>
Date: Tue, 7 Apr 2026 07:54:12 +0200
Subject: [PATCH 2/2] skip merge block if it is in a different loop
---
llvm/lib/Analysis/ScalarEvolution.cpp | 8 ++++++--
.../Analysis/ScalarEvolution/exit-count-select-safe.ll | 2 +-
.../test/Transforms/LoopVectorize/runtime-checks-hoist.ll | 6 +++---
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 38c575e9ee56b..58fe313f0aea3 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -12054,12 +12054,16 @@ bool ScalarEvolution::isBasicBlockEntryGuardedByCond(const BasicBlock *BB,
if (ProveViaPredecessorChain(PredBB, BB, MaxChainVisits))
return true;
if (MergeBlock) {
+ // MergeBlock dominates BB. If every predecessor of MergeBlock proves the
+ // condition, then BB is guarded by the condition.
MaxChainVisits = 128;
auto ProveMergeBlockPredecessor = [&](const BasicBlock *Pred) {
return ProveViaPredecessorChain(Pred, MergeBlock, MaxChainVisits);
};
- if (all_of(predecessors(MergeBlock), ProveMergeBlockPredecessor))
- return true;
+ const Loop *MergeBlockLoop = LI.getLoopFor(MergeBlock);
+ if (!MergeBlockLoop || ContainingLoop == MergeBlockLoop)
+ if (all_of(predecessors(MergeBlock), ProveMergeBlockPredecessor))
+ return true;
}
// Check conditions due to any @llvm.assume intrinsics.
diff --git a/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll b/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
index 06b9bdb2b3516..1043b2f6f56f6 100644
--- a/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
+++ b/llvm/test/Analysis/ScalarEvolution/exit-count-select-safe.ll
@@ -443,7 +443,7 @@ define i32 @computeSCEVAtScope(i32 %d.0) {
; CHECK-NEXT: %inc3 = add nsw i32 %e.1, 1
; CHECK-NEXT: --> {(1 + %d.0),+,1}<nw><%for.cond> U: full-set S: full-set Exits: 1 LoopDispositions: { %for.cond: Computable, %while.cond: Variant }
; CHECK-NEXT: %f.1 = phi i32 [ %inc8, %for.body5 ], [ 0, %for.cond4.preheader ]
-; CHECK-NEXT: --> {0,+,1}<%for.cond4> U: full-set S: full-set Exits: 0 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
+; CHECK-NEXT: --> {0,+,1}<nuw><nsw><%for.cond4> U: [0,1) S: [0,1) Exits: 0 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
; CHECK-NEXT: %inc8 = add i32 %f.1, 1
; CHECK-NEXT: --> {1,+,1}<nuw><nsw><%for.cond4> U: [1,2) S: [1,2) Exits: 1 LoopDispositions: { %for.cond4: Computable, %while.cond: Variant }
; CHECK-NEXT: Determining loop execution counts for: @computeSCEVAtScope
diff --git a/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll b/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
index 7a78c13c83841..1ac7087ae9143 100644
--- a/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
+++ b/llvm/test/Transforms/LoopVectorize/runtime-checks-hoist.ll
@@ -639,11 +639,11 @@ define void @triple_nested_loop_mixed_access(ptr nocapture noundef %dst, ptr noc
; CHECK-NEXT: [[WIDE_TRIP_COUNT68:%.*]] = zext i32 [[M]] to i64
; CHECK-NEXT: [[WIDE_TRIP_COUNT60:%.*]] = zext i32 [[N]] to i64
; CHECK-NEXT: [[WIDE_TRIP_COUNT:%.*]] = zext i32 [[O]] to i64
-; CHECK-NEXT: [[TMP3:%.*]] = mul i64 [[WIDE_TRIP_COUNT60]], [[TMP2]]
+; CHECK-NEXT: [[TMP3:%.*]] = mul i64 [[TMP1]], [[TMP2]]
; CHECK-NEXT: [[TMP4:%.*]] = shl i64 [[TMP3]], 2
-; CHECK-NEXT: [[TMP5:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[WIDE_TRIP_COUNT60]]
+; CHECK-NEXT: [[TMP5:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[TMP1]]
; CHECK-NEXT: [[TMP6:%.*]] = shl i64 [[TMP5]], 2
-; CHECK-NEXT: [[TMP7:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[WIDE_TRIP_COUNT60]]
+; CHECK-NEXT: [[TMP7:%.*]] = mul i64 [[WIDE_TRIP_COUNT]], [[TMP1]]
; CHECK-NEXT: [[TMP8:%.*]] = shl i64 [[TMP7]], 2
; CHECK-NEXT: [[TMP9:%.*]] = shl nuw nsw i64 [[WIDE_TRIP_COUNT]], 2
; CHECK-NEXT: br label [[OUTER_OUTER_LOOP:%.*]]
More information about the llvm-commits
mailing list