[llvm] [ConstraintElim] Forget loop SCEV when folding an exiting condition. (PR #213945)
Uzair rehman via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 5 00:15:26 PDT 2026
https://github.com/Uzair-90 updated https://github.com/llvm/llvm-project/pull/213945
>From e02cb19edc7c0b17eeb38b9863c70a9e408975c7 Mon Sep 17 00:00:00 2001
From: Uzair <uzair.rehmann at gmail.com>
Date: Tue, 4 Aug 2026 18:34:31 +0500
Subject: [PATCH 1/2] [ConstraintElim] Forget loop SCEV when folding an exiting
condition.
ConstraintElimination preserves ScalarEvolution, but when it folds the
condition of a loop-exiting branch to a constant, the loop's cached
backedge-taken count (computed against the original exit structure,
often populated by the pass's own queries in addInfoForInductions)
becomes stale: still sound, but less precise than fresh recomputation.
Later trip-count based folds, e.g. in indvars, then fail.
Record loops whose exiting-block terminators have their condition
replaced in ReplaceCmpWithConstant and forget their SCEV afterwards.
forgetValue on the compare would not be enough, since backedge-taken
counts are expressed in terms of the compare's operands, not the
compare itself.
Fixes #213872.
---
.../Scalar/ConstraintElimination.cpp | 26 +++++++++++++--
.../ConstraintElimination/preserve-scev.ll | 32 +++++++++++++++++++
2 files changed, 56 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/Transforms/ConstraintElimination/preserve-scev.ll
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 945ee0351e2e3..5a5dbf5be4a28 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1602,12 +1602,14 @@ static bool checkAndReplaceCondition(
ConstraintInfo &Info, unsigned NumIn, unsigned NumOut,
Instruction *ContextInst, Module *ReproducerModule,
ArrayRef<ReproducerEntry> ReproducerCondStack, DominatorTree &DT,
+ LoopInfo &LI, ScalarEvolution &SE,
SmallVectorImpl<Instruction *> &ToRemove) {
auto ReplaceCmpWithConstant = [&](Instruction *CheckInst, bool IsTrue) {
generateReproducer(CheckInst, ICmpInst::isSigned(Pred), ReproducerModule,
ReproducerCondStack, Info, DT);
Constant *ConstantC = ConstantInt::getBool(
CmpInst::makeCmpResultType(CheckInst->getType()), IsTrue);
+ SmallPtrSet<const Loop *, 2> AffectedLoops;
bool Changed = CheckInst->replaceUsesWithIf(ConstantC, [&](Use &U) {
auto *UserI = getContextInstForUse(U);
auto *DTN = DT.getNode(UserI->getParent());
@@ -1620,8 +1622,27 @@ static bool checkAndReplaceCondition(
// Conditions in an assume trivially simplify to true. Skip uses
// in assume calls to not destroy the available information.
auto *II = dyn_cast<IntrinsicInst>(U.getUser());
- return !II || II->getIntrinsicID() != Intrinsic::assume;
+ if (II && II->getIntrinsicID() == Intrinsic::assume)
+ return false;
+
+ // Replacing the condition of a terminator in a loop-exiting block may
+ // change how often the loop is exited, so cached trip counts, while
+ // still correct, may be less precise than freshly computed ones. Keep
+ // track of the affected loops, so their cached info can be dropped.
+ if (auto *User = dyn_cast<Instruction>(U.getUser());
+ User && User->isTerminator()) {
+ BasicBlock *BB = User->getParent();
+ if (Loop *L = LI.getLoopFor(BB); L && L->isLoopExiting(BB))
+ AffectedLoops.insert(L);
+ }
+ return true;
});
+
+ // The exit conditions of the affected loops (and any enclosing loops the
+ // exiting blocks may also exit) changed; drop the cached trip counts so
+ // later passes re-compute them with the now more precise exit structure.
+ for (const Loop *L : AffectedLoops)
+ SE.forgetTopmostLoop(L);
NumCondsRemoved++;
// Update the debug value records that satisfy the same condition used
@@ -2059,7 +2080,8 @@ static bool eliminateConstraints(Function &F, DominatorTree &DT, LoopInfo &LI,
} else if (match(Inst, m_ICmpLike(Pred, m_Value(A), m_Value(B)))) {
bool Simplified = checkAndReplaceCondition(
Pred, A, B, Inst, Info, CB.NumIn, CB.NumOut, CB.getContextInst(),
- ReproducerModule.get(), ReproducerCondStack, S.DT, ToRemove);
+ ReproducerModule.get(), ReproducerCondStack, S.DT, S.LI, S.SE,
+ ToRemove);
if (!Simplified &&
match(CB.getContextInst(), m_LogicalOp(m_Value(), m_Value()))) {
Simplified = checkOrAndOpImpliedByOther(
diff --git a/llvm/test/Transforms/ConstraintElimination/preserve-scev.ll b/llvm/test/Transforms/ConstraintElimination/preserve-scev.ll
new file mode 100644
index 0000000000000..53f0971120dae
--- /dev/null
+++ b/llvm/test/Transforms/ConstraintElimination/preserve-scev.ll
@@ -0,0 +1,32 @@
+; RUN: opt -passes='constraint-elimination,print<scalar-evolution>' -disable-output %s 2>&1 | FileCheck %s
+
+; Make sure ScalarEvolution's cached trip counts are dropped when
+; constraint-elimination folds the condition of a loop-exiting branch. The
+; preserved analysis would otherwise keep a stale, less precise backedge-taken
+; count (umin of both original exits), blocking later trip-count based folds,
+; e.g. in indvars.
+; See https://github.com/llvm/llvm-project/issues/213872.
+
+; CHECK-LABEL: Classifying expressions for: @multiple_pow2
+; CHECK: Loop %loop: <multiple exits> backedge-taken count is ((4 * %count) /u 4)
+; CHECK-NEXT: exit count for loop: ((4 * %count) /u 4)
+; CHECK-NEXT: exit count for loop.latch: ***COULDNOTCOMPUTE***
+
+define void @multiple_pow2(i64 %count) {
+entry:
+ %end = shl i64 %count, 2
+ br label %loop
+
+loop: ; preds = %loop.latch, %entry
+ %iv = phi i64 [ %iv.next, %loop.latch ], [ 0, %entry ]
+ %cmp.i.not = icmp eq i64 %iv, %end
+ br i1 %cmp.i.not, label %exit, label %loop.latch
+
+loop.latch: ; preds = %loop
+ %iv.next = add i64 %iv, 4
+ %cmp2.i.i = icmp ult i64 %iv, %end
+ br i1 %cmp2.i.i, label %loop, label %exit
+
+exit: ; preds = %loop.latch, %loop
+ ret void
+}
>From f83da4fe3732f816b81bde1b8b979e2ae690f466 Mon Sep 17 00:00:00 2001
From: Uzair <uzair.rehmann at gmail.com>
Date: Wed, 5 Aug 2026 12:15:10 +0500
Subject: [PATCH 2/2] Fix clang-format.
---
.../Transforms/Scalar/ConstraintElimination.cpp | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
index 5a5dbf5be4a28..cf1b2158e55c0 100644
--- a/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/ConstraintElimination.cpp
@@ -1597,13 +1597,14 @@ static std::optional<bool> checkCondition(CmpInst::Predicate Pred, Value *A,
return std::nullopt;
}
-static bool checkAndReplaceCondition(
- CmpPredicate Pred, Value *A, Value *B, Instruction *CheckInst,
- ConstraintInfo &Info, unsigned NumIn, unsigned NumOut,
- Instruction *ContextInst, Module *ReproducerModule,
- ArrayRef<ReproducerEntry> ReproducerCondStack, DominatorTree &DT,
- LoopInfo &LI, ScalarEvolution &SE,
- SmallVectorImpl<Instruction *> &ToRemove) {
+static bool
+checkAndReplaceCondition(CmpPredicate Pred, Value *A, Value *B,
+ Instruction *CheckInst, ConstraintInfo &Info,
+ unsigned NumIn, unsigned NumOut,
+ Instruction *ContextInst, Module *ReproducerModule,
+ ArrayRef<ReproducerEntry> ReproducerCondStack,
+ DominatorTree &DT, LoopInfo &LI, ScalarEvolution &SE,
+ SmallVectorImpl<Instruction *> &ToRemove) {
auto ReplaceCmpWithConstant = [&](Instruction *CheckInst, bool IsTrue) {
generateReproducer(CheckInst, ICmpInst::isSigned(Pred), ReproducerModule,
ReproducerCondStack, Info, DT);
More information about the llvm-commits
mailing list