[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