[llvm-branch-commits] [llvm] [SCEV] Prove signed/unsigned implications in either operand order (PR #234208)
Dmitry Makogon via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Oct 22 04:52:35 PDT 2026
https://github.com/d-makogon updated https://github.com/llvm/llvm-project/pull/234208
>From e037d7d51c3fe8569bc3a438445c9b9a50e9e020 Mon Sep 17 00:00:00 2001
From: Dmitrii Makogon <dmakogon at azul.com>
Date: Thu, 22 Oct 2026 11:25:52 +0000
Subject: [PATCH 1/3] [SCEV] Try other implications when sign-flipped ones fail
SCEV's isImpliedCondBalancedTypes tries to prove an implication
through several rules. There are rules for when a predicate to prove
is sign-flipped from the found predicate, and also there are other
rules, which, for example, try to prove the implication through
ranges.
Before, if a proof for a sign-flipped rule implication returned false,
we wouldn't check other rules and just bail out with false result too.
Now make those rules fall-through - if one rule couldn't be proven,
try other rules in the function.
An example query is x - 5 <s 5 with x <u 10 known.
The implication "a <u b and b >=s 0 --> a <s b" matches it,
but x - 5 <u 5 with x <u 10 known does not hold (e.g. x = 0),
so SCEV gave up.
Now it goes on to trying to prove the query from ranges.
x <u 10 gives x in [0, 10), so x - 5 is in [-5, 5), and x - 5 <s 5
holds.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
llvm/lib/Analysis/ScalarEvolution.cpp | 23 +++++++++++--------
.../implied-cond-different-signedness.ll | 4 +---
2 files changed, 14 insertions(+), 13 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index fed69ea33fab74..0b53f7906e6ae9 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -12242,8 +12242,9 @@ bool ScalarEvolution::isImpliedCondBalancedTypes(
if (IsSignFlippedPredicate(Pred, FoundPred)) {
// Unsigned comparison is the same as signed comparison when both the
// operands are non-negative or negative.
- if (haveSameSign(FoundLHS, FoundRHS))
- return isImpliedCondOperands(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI);
+ if (haveSameSign(FoundLHS, FoundRHS) &&
+ isImpliedCondOperands(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI))
+ return true;
// Create local copies that we can freely swap and canonicalize our
// conditions to "le/lt".
CmpPredicate CanonicalPred = Pred, CanonicalFoundPred = FoundPred;
@@ -12260,20 +12261,22 @@ bool ScalarEvolution::isImpliedCondBalancedTypes(
assert((ICmpInst::isLT(CanonicalFoundPred) ||
ICmpInst::isLE(CanonicalFoundPred)) &&
"Must be!");
- if (ICmpInst::isSigned(CanonicalPred) && isKnownNonNegative(CanonicalRHS))
+ if (ICmpInst::isSigned(CanonicalPred) && isKnownNonNegative(CanonicalRHS)) {
// Use implication:
// x <u y && y >=s 0 --> x <s y.
// If we can prove the left part, the right part is also proven.
- return isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS,
- CanonicalRHS, CanonicalFoundLHS,
- CanonicalFoundRHS);
- if (ICmpInst::isUnsigned(CanonicalPred) && isKnownNegative(CanonicalRHS))
+ if (isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS, CanonicalRHS,
+ CanonicalFoundLHS, CanonicalFoundRHS))
+ return true;
+ }
+ if (ICmpInst::isUnsigned(CanonicalPred) && isKnownNegative(CanonicalRHS)) {
// Use implication:
// x <s y && y <s 0 --> x <u y.
// If we can prove the left part, the right part is also proven.
- return isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS,
- CanonicalRHS, CanonicalFoundLHS,
- CanonicalFoundRHS);
+ if (isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS, CanonicalRHS,
+ CanonicalFoundLHS, CanonicalFoundRHS))
+ return true;
+ }
}
// Check if we can make progress by sharpening ranges.
diff --git a/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll b/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
index ebbec2d0fdf94b..fda6bc18dd78b0 100644
--- a/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
+++ b/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
@@ -159,9 +159,7 @@ define void @ult_const_query_slt_offset(i32 %x) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_NEXT:%.*]], %[[LATCH:.*]] ], [ 0, %[[LOOP_PREHEADER]] ]
-; CHECK-NEXT: [[X_M5:%.*]] = add i32 [[X]], -5
-; CHECK-NEXT: [[Q:%.*]] = icmp sge i32 [[X_M5]], 5
-; CHECK-NEXT: br i1 [[Q]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK-NEXT: br i1 false, label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
; CHECK: [[LATCH]]:
; CHECK-NEXT: call void @side()
; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1
>From 79776da3790015d9c966cbe652a23a61e1784029 Mon Sep 17 00:00:00 2001
From: Dmitrii Makogon <dmakogon at azul.com>
Date: Thu, 22 Oct 2026 09:20:43 +0000
Subject: [PATCH 2/3] [SCEV][NFC] Factor isImpliedCondViaSignFlippedPred out of
isImpliedCondBalancedTypes
Move the code that proves a condition from a found condition with the
other signedness into a new private function.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
llvm/include/llvm/Analysis/ScalarEvolution.h | 8 ++
llvm/lib/Analysis/ScalarEvolution.cpp | 78 ++++++++++----------
2 files changed, 47 insertions(+), 39 deletions(-)
diff --git a/llvm/include/llvm/Analysis/ScalarEvolution.h b/llvm/include/llvm/Analysis/ScalarEvolution.h
index 17bfec22d65ca7..dc74802a639398 100644
--- a/llvm/include/llvm/Analysis/ScalarEvolution.h
+++ b/llvm/include/llvm/Analysis/ScalarEvolution.h
@@ -2323,6 +2323,14 @@ class ScalarEvolution {
const SCEV *FoundLHS,
const SCEV *FoundRHS);
+ /// Test whether the condition described by Pred, LHS, and RHS is true
+ /// whenever the condition described by FoundLHS, FoundRHS, and Pred with the
+ /// other signedness is true.
+ bool isImpliedCondViaSignFlippedPred(CmpPredicate Pred, const SCEV *LHS,
+ const SCEV *RHS, const SCEV *FoundLHS,
+ const SCEV *FoundRHS,
+ const Instruction *CtxI);
+
/// Return true if the condition denoted by \p LHS \p Pred \p RHS is implied
/// by a call to @llvm.experimental.guard in \p BB.
bool isImpliedViaGuard(const BasicBlock *BB, CmpPredicate Pred,
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 0b53f7906e6ae9..732fc89e3875e9 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -12239,45 +12239,9 @@ bool ScalarEvolution::isImpliedCondBalancedTypes(
return CmpInst::isRelational(P2) &&
P1 == ICmpInst::getFlippedSignednessPredicate(P2);
};
- if (IsSignFlippedPredicate(Pred, FoundPred)) {
- // Unsigned comparison is the same as signed comparison when both the
- // operands are non-negative or negative.
- if (haveSameSign(FoundLHS, FoundRHS) &&
- isImpliedCondOperands(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI))
- return true;
- // Create local copies that we can freely swap and canonicalize our
- // conditions to "le/lt".
- CmpPredicate CanonicalPred = Pred, CanonicalFoundPred = FoundPred;
- const SCEV *CanonicalLHS = LHS, *CanonicalRHS = RHS,
- *CanonicalFoundLHS = FoundLHS, *CanonicalFoundRHS = FoundRHS;
- if (ICmpInst::isGT(CanonicalPred) || ICmpInst::isGE(CanonicalPred)) {
- CanonicalPred = ICmpInst::getSwappedCmpPredicate(CanonicalPred);
- CanonicalFoundPred = ICmpInst::getSwappedCmpPredicate(CanonicalFoundPred);
- std::swap(CanonicalLHS, CanonicalRHS);
- std::swap(CanonicalFoundLHS, CanonicalFoundRHS);
- }
- assert((ICmpInst::isLT(CanonicalPred) || ICmpInst::isLE(CanonicalPred)) &&
- "Must be!");
- assert((ICmpInst::isLT(CanonicalFoundPred) ||
- ICmpInst::isLE(CanonicalFoundPred)) &&
- "Must be!");
- if (ICmpInst::isSigned(CanonicalPred) && isKnownNonNegative(CanonicalRHS)) {
- // Use implication:
- // x <u y && y >=s 0 --> x <s y.
- // If we can prove the left part, the right part is also proven.
- if (isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS, CanonicalRHS,
- CanonicalFoundLHS, CanonicalFoundRHS))
- return true;
- }
- if (ICmpInst::isUnsigned(CanonicalPred) && isKnownNegative(CanonicalRHS)) {
- // Use implication:
- // x <s y && y <s 0 --> x <u y.
- // If we can prove the left part, the right part is also proven.
- if (isImpliedCondOperands(CanonicalFoundPred, CanonicalLHS, CanonicalRHS,
- CanonicalFoundLHS, CanonicalFoundRHS))
- return true;
- }
- }
+ if (IsSignFlippedPredicate(Pred, FoundPred) &&
+ isImpliedCondViaSignFlippedPred(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI))
+ return true;
// Check if we can make progress by sharpening ranges.
if (FoundPred == ICmpInst::ICMP_NE &&
@@ -12373,6 +12337,42 @@ bool ScalarEvolution::isImpliedCondBalancedTypes(
return false;
}
+bool ScalarEvolution::isImpliedCondViaSignFlippedPred(
+ CmpPredicate Pred, const SCEV *LHS, const SCEV *RHS, const SCEV *FoundLHS,
+ const SCEV *FoundRHS, const Instruction *CtxI) {
+ CmpPredicate FoundPred = ICmpInst::getFlippedSignednessPredicate(Pred);
+ // Unsigned comparison is the same as signed comparison when both the
+ // operands are non-negative or negative.
+ if (haveSameSign(FoundLHS, FoundRHS) &&
+ isImpliedCondOperands(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI))
+ return true;
+ // Canonicalize both conditions to "le/lt".
+ if (ICmpInst::isGT(Pred) || ICmpInst::isGE(Pred)) {
+ Pred = ICmpInst::getSwappedCmpPredicate(Pred);
+ FoundPred = ICmpInst::getSwappedCmpPredicate(FoundPred);
+ std::swap(LHS, RHS);
+ std::swap(FoundLHS, FoundRHS);
+ }
+ assert((ICmpInst::isLT(Pred) || ICmpInst::isLE(Pred)) && "Must be!");
+ assert((ICmpInst::isLT(FoundPred) || ICmpInst::isLE(FoundPred)) &&
+ "Must be!");
+ if (ICmpInst::isSigned(Pred) && isKnownNonNegative(RHS)) {
+ // Use implication:
+ // x <u y && y >=s 0 --> x <s y.
+ // If we can prove the left part, the right part is also proven.
+ if (isImpliedCondOperands(FoundPred, LHS, RHS, FoundLHS, FoundRHS))
+ return true;
+ }
+ if (ICmpInst::isUnsigned(Pred) && isKnownNegative(RHS)) {
+ // Use implication:
+ // x <s y && y <s 0 --> x <u y.
+ // If we can prove the left part, the right part is also proven.
+ if (isImpliedCondOperands(FoundPred, LHS, RHS, FoundLHS, FoundRHS))
+ return true;
+ }
+ return false;
+}
+
bool ScalarEvolution::splitBinaryAdd(SCEVUse Expr, SCEVUse &L, SCEVUse &R,
SCEVFlags &Flags) {
if (!match(Expr, m_scev_Add(m_SCEV(L), m_SCEV(R))))
>From 4f83cc898ce868b937efe5ac5231a33823a4b26b Mon Sep 17 00:00:00 2001
From: Dmitrii Makogon <dmakogon at azul.com>
Date: Thu, 22 Oct 2026 09:26:01 +0000
Subject: [PATCH 3/3] [SCEV] Prove signed/unsigned implications in either
operand order
SCEV could prove these implications only with the operands in the same
order:
- If a <u b and a, b have the same sign, then a <s b.
- If a <s b and a, b have the same sign, then a <u b.
- If a <u b and b >=s 0, then a <s b.
- If a <s b and b <s 0, then a <u b.
For example, it proved a <s b from a <u b and b >=s 0, but not b >s a.
Now it proves them in either order.
Co-Authored-By: Claude Opus 5.5 <noreply at anthropic.com>
---
llvm/lib/Analysis/ScalarEvolution.cpp | 8 +++++++-
.../implied-cond-different-signedness.ll | 17 +++--------------
2 files changed, 10 insertions(+), 15 deletions(-)
diff --git a/llvm/lib/Analysis/ScalarEvolution.cpp b/llvm/lib/Analysis/ScalarEvolution.cpp
index 732fc89e3875e9..0d632b857158dc 100644
--- a/llvm/lib/Analysis/ScalarEvolution.cpp
+++ b/llvm/lib/Analysis/ScalarEvolution.cpp
@@ -12233,15 +12233,21 @@ bool ScalarEvolution::isImpliedCondBalancedTypes(
return false;
}
+ // Try the query as is and with its operands swapped. E.g. "x >s -1" is
+ // tried as "-1 <s x" too, to match the found condition "x <u y".
auto IsSignFlippedPredicate = [](CmpInst::Predicate P1,
CmpInst::Predicate P2) {
- assert(P1 != P2 && "Handled earlier!");
return CmpInst::isRelational(P2) &&
P1 == ICmpInst::getFlippedSignednessPredicate(P2);
};
if (IsSignFlippedPredicate(Pred, FoundPred) &&
isImpliedCondViaSignFlippedPred(Pred, LHS, RHS, FoundLHS, FoundRHS, CtxI))
return true;
+ CmpPredicate SwappedPred = ICmpInst::getSwappedCmpPredicate(Pred);
+ if (IsSignFlippedPredicate(SwappedPred, FoundPred) &&
+ isImpliedCondViaSignFlippedPred(SwappedPred, RHS, LHS, FoundLHS, FoundRHS,
+ CtxI))
+ return true;
// Check if we can make progress by sharpening ranges.
if (FoundPred == ICmpInst::ICMP_NE &&
diff --git a/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll b/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
index fda6bc18dd78b0..710840c0f2bd0b 100644
--- a/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
+++ b/llvm/test/Transforms/IndVarSimplify/implied-cond-different-signedness.ll
@@ -3,8 +3,6 @@
; Check that an exit condition is proven from a guard with a predicate of
; different signedness.
-;
-; FIXME: SCEV cannot prove these yet, fold the exits to false.
target datalayout = "e-m:e-i64:64-n8:16:32:64-S128"
@@ -22,10 +20,7 @@ define void @same_sign(ptr %px, ptr %py, i32 %w) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_NEXT:%.*]], %[[LATCH:.*]] ], [ 0, %[[LOOP_PREHEADER]] ]
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[W]])
-; CHECK-NEXT: [[Y_1:%.*]] = add nsw i32 [[Y]], 1
-; CHECK-NEXT: [[Q:%.*]] = icmp sle i32 [[Y_1]], [[M]]
-; CHECK-NEXT: br i1 [[Q]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK-NEXT: br i1 false, label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
; CHECK: [[LATCH]]:
; CHECK-NEXT: call void @side()
; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1
@@ -69,10 +64,7 @@ define void @ult_nonneg(i32 %x, ptr %py, i32 %w) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_NEXT:%.*]], %[[LATCH:.*]] ], [ 0, %[[LOOP_PREHEADER]] ]
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.umin.i32(i32 [[X]], i32 [[W]])
-; CHECK-NEXT: [[Y_1:%.*]] = add nuw nsw i32 [[Y]], 1
-; CHECK-NEXT: [[Q:%.*]] = icmp sle i32 [[Y_1]], [[M]]
-; CHECK-NEXT: br i1 [[Q]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK-NEXT: br i1 false, label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
; CHECK: [[LATCH]]:
; CHECK-NEXT: call void @side()
; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1
@@ -115,10 +107,7 @@ define void @slt_neg(i32 %x, ptr %py, i32 %w) {
; CHECK-NEXT: br label %[[LOOP:.*]]
; CHECK: [[LOOP]]:
; CHECK-NEXT: [[I:%.*]] = phi i32 [ [[I_NEXT:%.*]], %[[LATCH:.*]] ], [ 0, %[[LOOP_PREHEADER]] ]
-; CHECK-NEXT: [[M:%.*]] = call i32 @llvm.smin.i32(i32 [[X]], i32 [[W]])
-; CHECK-NEXT: [[Y_1:%.*]] = add nsw i32 [[Y]], 1
-; CHECK-NEXT: [[Q:%.*]] = icmp ule i32 [[Y_1]], [[M]]
-; CHECK-NEXT: br i1 [[Q]], label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
+; CHECK-NEXT: br i1 false, label %[[EXIT_LOOPEXIT:.*]], label %[[LATCH]]
; CHECK: [[LATCH]]:
; CHECK-NEXT: call void @side()
; CHECK-NEXT: [[I_NEXT]] = add nuw nsw i32 [[I]], 1
More information about the llvm-branch-commits
mailing list