[llvm] [SCCP] Relax 2-insts range check to one-icmp check (PR #187997)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Fri May 1 02:14:59 PDT 2026


https://github.com/Camsyn updated https://github.com/llvm/llvm-project/pull/187997

>From 4b2a73fd025d96914d9358b30e86688a023885b2 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Mon, 23 Mar 2026 17:47:30 +0800
Subject: [PATCH 1/8] Pre-commit tests

---
 .../Transforms/SCCP/relax-range-checks.ll     | 123 ++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/llvm/test/Transforms/SCCP/relax-range-checks.ll b/llvm/test/Transforms/SCCP/relax-range-checks.ll
index 34e48136df37a..f7c4f6f468929 100644
--- a/llvm/test/Transforms/SCCP/relax-range-checks.ll
+++ b/llvm/test/Transforms/SCCP/relax-range-checks.ll
@@ -113,4 +113,127 @@ define i1 @range_check_to_icmp_eq2(i32 range(i32 -1, 2) %x) {
   ret i1 %cmp
 }
 
+define i1 @range_check_to_icmp_ult(i8 range(i8 2, 10) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_ult(
+; CHECK-SAME: i8 range(i8 2, 10) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], -2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 4
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, -2
+  %cmp = icmp ult i8 %off, 4
+  ret i1 %cmp
+}
+
+define i1 @range_check_to_icmp_uge(i8 range(i8 2, 6) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_uge(
+; CHECK-SAME: i8 range(i8 2, 6) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], -4
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 2
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nsw i8 %x, -4
+  %cmp = icmp ult i8 %off, 2
+  ret i1 %cmp
+}
+
+define i1 @range_check_to_icmp_slt(i8 range(i8 -56, 20) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_slt(
+; CHECK-SAME: i8 range(i8 -56, 20) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], 56
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 50
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nsw i8 %x, 56
+  %cmp = icmp ult i8 %off, 50
+  ret i1 %cmp
+}
+
+define i1 @range_check_to_icmp_sge(i8 range(i8 -56, 20) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_sge(
+; CHECK-SAME: i8 range(i8 -56, 20) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], 16
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 36
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nsw i8 %x, 16
+  %cmp = icmp ult i8 %off, 36
+  ret i1 %cmp
+}
+
+; Cover the early exit when ActiveCmpCR is already a one-icmp check.
+
+define i1 @range_check_intersection_to_icmp_eq(i32 range(i32 0, 4) %x) {
+; CHECK-LABEL: define i1 @range_check_intersection_to_icmp_eq(
+; CHECK-SAME: i32 range(i32 0, 4) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i32 [[X]], -3
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[X]], 3
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nsw i32 %x, -3
+  %cmp = icmp ult i32 %off, 2
+  ret i1 %cmp
+}
+
+define i1 @range_check_intersection_to_icmp_ult(i8 range(i8 0, 10) %x) {
+; CHECK-LABEL: define i1 @range_check_intersection_to_icmp_ult(
+; CHECK-SAME: i8 range(i8 0, 10) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nuw nsw i8 [[X]], 2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[X]], 4
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, 2
+  %cmp = icmp ult i8 %off, 6
+  ret i1 %cmp
+}
+
+define i1 @range_check_intersection_to_icmp_slt(i8 range(i8 -128, -100) %x) {
+; CHECK-LABEL: define i1 @range_check_intersection_to_icmp_slt(
+; CHECK-SAME: i8 range(i8 -128, -100) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add i8 [[X]], -120
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i8 [[X]], -118
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, -120
+  %cmp = icmp ult i8 %off, 18
+  ret i1 %cmp
+}
+
+define i1 @range_check_intersection_to_icmp_uge(i8 range(i8 -6, 0) %x) {
+; CHECK-LABEL: define i1 @range_check_intersection_to_icmp_uge(
+; CHECK-SAME: i8 range(i8 -6, 0) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], 2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i8 [[X]], -2
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, 2
+  %cmp = icmp ult i8 %off, 6
+  ret i1 %cmp
+}
+
+define i1 @range_check_intersection_to_icmp_sge(i8 range(i8 120, -128) %x) {
+; CHECK-LABEL: define i1 @range_check_intersection_to_icmp_sge(
+; CHECK-SAME: i8 range(i8 120, -128) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], -122
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i8 [[X]], 122
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, -122
+  %cmp = icmp ult i8 %off, 14
+  ret i1 %cmp
+}
+
+; Negative test: CmpCR relaxation cannot perform when x's range is nuw and nsw.
+define i1 @range_check_nsw_nuw(i8 range(i8 -20, -56) %x) {
+; CHECK-LABEL: define i1 @range_check_nsw_nuw(
+; CHECK-SAME: i8 range(i8 -20, -56) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add i8 [[X]], 20
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 14
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add i8 %x, 20
+  %cmp = icmp ult i8 %off, 14
+  ret i1 %cmp
+}
+
 declare void @use(i8)

>From 4be1c34e10a4c538047f7c64eff8a9e0e4f0fd1c Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Mon, 23 Mar 2026 18:06:10 +0800
Subject: [PATCH 2/8] Relax 2-insts range check to one-icmp check

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp      | 130 +++++++++++++++---
 .../Transforms/SCCP/relax-range-checks.ll     |   8 +-
 2 files changed, 112 insertions(+), 26 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index fd315c14df866..c3414d63ada57 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -13,6 +13,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/SCCPSolver.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/Analysis/ConstantFolding.h"
 #include "llvm/Analysis/InstructionSimplify.h"
@@ -105,6 +106,96 @@ static ConstantRange getRange(Value *Op, SCCPSolver &Solver,
                                                        /*UndefAllowed=*/false);
 }
 
+/// SCCP already proves x \in KnownCR, so only ActiveCmpCR = CmpCR ∩ KnownCR
+/// matters. Try to replace CmpCR with a simpler equivalent range NewCmpCR
+/// such that NewCmpCR ∩ KnownCR == ActiveCmpCR.
+///
+/// Prefer ranges that lower to a single canonical compare without an add:
+///   - [L, L+1)      --> X  eq L
+///   - [R+1, R)      --> X  ne R
+///   - [0, R)        --> X ult R
+///   - [L, 0)        --> X uge L
+///   - [SignMin, R)  --> X slt R
+///   - [L, SignMin)  --> X sge L
+///
+/// If no such range preserves the active semantics under KnownCR, keep CmpCR.
+static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
+                                      const ConstantRange &KnownCR) {
+  assert(!KnownCR.inverse().contains(CmpCR) &&
+         "CmpCR ∩ KnowCR should not be ∅");
+  assert((!CmpCR.isFullSet() && !CmpCR.isEmptySet()) && "Unexpected CmpCR");
+  assert((!KnownCR.isFullSet() && !KnownCR.isEmptySet()) &&
+         "Unexpected KnownCR");
+
+  // If KnownCR is both nuw and nsw, we cannot relax CmpCR at all.
+  if (KnownCR.isWrappedSet() && KnownCR.isSignWrappedSet())
+    return CmpCR;
+
+  const unsigned BW = CmpCR.getBitWidth();
+  // All reachable value satisfy CmpCR --> always true.
+  if (CmpCR.contains(KnownCR))
+    return ConstantRange::getFull(BW);
+
+  std::optional<ConstantRange> ActCmpCR = CmpCR.exactIntersectWith(KnownCR);
+  if (!ActCmpCR)
+    return CmpCR;
+
+  const APInt &CmpLo = ActCmpCR->getLower(), &CmpHi = ActCmpCR->getUpper();
+
+  // If the intersection happens to be the ONE-icmp check, just return it.
+  if (/*eq*/ ActCmpCR->isSingleElement() ||
+      /*ne*/ ActCmpCR->inverse().isSingleElement() ||
+      /*ult*/ CmpLo.isZero() ||
+      /*slt*/ CmpLo.isMinSignedValue() ||
+      /*uge*/ CmpHi.isZero() ||
+      /*sge*/ CmpHi.isMinSignedValue())
+    return *ActCmpCR;
+
+  const APInt Zero = APInt::getZero(BW);
+  const APInt SignMin = APInt::getSignedMinValue(BW);
+
+  if (CmpLo == KnownCR.getLower()) {
+    // Tie to lower:
+
+    // Try ult
+    // 0
+    // |  L------------R   : KnownCR
+    // |  L---R            : ActiveCmpCR
+    // L------R            : RelaxedCmpCR
+    if (!KnownCR.isWrappedSet())
+      return ConstantRange::getNonEmpty(Zero, CmpHi);
+
+    // Try slt
+    //       smin                                 smin
+    // -----R  |  L------- : KnownCR        ----R   |  L------- : KnownCR
+    //         |  L--R     : ActiveCmpCR    --R     |  L------- : ActiveCmpCR
+    //         L-----R     : RelaxedCmpCR   --R     L---------- : RelaxedCmpCR
+    if (!KnownCR.isSignWrappedSet())
+      return ConstantRange::getNonEmpty(SignMin, CmpHi);
+
+  } else if (CmpHi == KnownCR.getUpper()) {
+    // Tie to upper:
+
+    // Try uge
+    // 0
+    // |  L--------R       : KnownCR
+    // |       L---R       : ActiveCmpCR
+    // R       L---------- : RelaxedCmpCR
+    if (!KnownCR.isWrappedSet())
+      return ConstantRange::getNonEmpty(CmpLo, Zero);
+
+    // Try sge
+    //       smin                                 smin
+    // -----R  |  L------- : KnownCR        -----R  |  L------- : KnownCR
+    //   L--R  |           : ActiveCmpCR    -----R  |      L--- : ActiveCmpCR
+    //   L-----R           : RelaxedCmpCR   --------R      L--- : RelaxedCmpCR
+    if (!KnownCR.isSignWrappedSet())
+      return ConstantRange::getNonEmpty(CmpLo, SignMin);
+  }
+
+  return CmpCR;
+}
+
 /// Try to use \p Inst's value range from \p Solver to infer the NUW flag.
 static bool refineInstruction(SCCPSolver &Solver,
                               const SmallPtrSetImpl<Value *> &InsertedValues,
@@ -318,29 +409,24 @@ static Value *simplifyInstruction(SCCPSolver &Solver,
       // Early exit if we know nothing about X.
       if (LRange.isFullSet())
         return nullptr;
-      auto ConvertCRToICmp =
-          [&](const std::optional<ConstantRange> &NewCR) -> Value * {
-        ICmpInst::Predicate Pred;
-        APInt RHS;
-        // Check if we can represent NewCR as an icmp predicate.
-        if (NewCR && NewCR->getEquivalentICmp(Pred, RHS)) {
-          IRBuilder<NoFolder> Builder(&Inst);
-          Value *NewICmp =
-              Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), RHS));
-          InsertedValues.insert(NewICmp);
-          return NewICmp;
-        }
-        return nullptr;
-      };
       // We are allowed to refine the comparison to either true or false for out
-      // of range inputs.
-      // Here we refine the comparison to false, and check if we can narrow the
-      // range check to a simpler test.
-      if (auto *V = ConvertCRToICmp(CR->exactIntersectWith(LRange)))
-        return V;
-      // Here we refine the comparison to true, i.e. we relax the range check.
-      if (auto *V = ConvertCRToICmp(CR->exactUnionWith(LRange.inverse())))
-        return V;
+      // of range inputs. Based on this, try to simplify CmpCR as a single
+      // ult/uge/slt/sge/eq/ne.
+      // E.g., CmpCR = [3, 10), LRange = [5, 0) --> NewCmpCR = [0, 10) -> ult
+      ConstantRange NewCmpCR = simplifyCmpRange(*CR, LRange);
+
+      ICmpInst::Predicate Pred;
+      APInt RHS;
+      // If NewCmpCR is just the same as CR, no simplification happens.
+      if (NewCmpCR != *CR) {
+        bool Match [[maybe_unused]] = NewCmpCR.getEquivalentICmp(Pred, RHS);
+        assert(Match && "Incorrect simplifyCmpRange");
+        IRBuilder<NoFolder> Builder(&Inst);
+        Value *NewICmp =
+            Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), RHS));
+        InsertedValues.insert(NewICmp);
+        return NewICmp;
+      }
     }
   }
 
diff --git a/llvm/test/Transforms/SCCP/relax-range-checks.ll b/llvm/test/Transforms/SCCP/relax-range-checks.ll
index f7c4f6f468929..998271b3b24d9 100644
--- a/llvm/test/Transforms/SCCP/relax-range-checks.ll
+++ b/llvm/test/Transforms/SCCP/relax-range-checks.ll
@@ -117,7 +117,7 @@ define i1 @range_check_to_icmp_ult(i8 range(i8 2, 10) %x) {
 ; CHECK-LABEL: define i1 @range_check_to_icmp_ult(
 ; CHECK-SAME: i8 range(i8 2, 10) [[X:%.*]]) {
 ; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], -2
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 4
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[X]], 6
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %off = add i8 %x, -2
@@ -129,7 +129,7 @@ define i1 @range_check_to_icmp_uge(i8 range(i8 2, 6) %x) {
 ; CHECK-LABEL: define i1 @range_check_to_icmp_uge(
 ; CHECK-SAME: i8 range(i8 2, 6) [[X:%.*]]) {
 ; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], -4
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i8 [[X]], 4
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %off = add nsw i8 %x, -4
@@ -141,7 +141,7 @@ define i1 @range_check_to_icmp_slt(i8 range(i8 -56, 20) %x) {
 ; CHECK-LABEL: define i1 @range_check_to_icmp_slt(
 ; CHECK-SAME: i8 range(i8 -56, 20) [[X:%.*]]) {
 ; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], 56
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 50
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i8 [[X]], -6
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %off = add nsw i8 %x, 56
@@ -153,7 +153,7 @@ define i1 @range_check_to_icmp_sge(i8 range(i8 -56, 20) %x) {
 ; CHECK-LABEL: define i1 @range_check_to_icmp_sge(
 ; CHECK-SAME: i8 range(i8 -56, 20) [[X:%.*]]) {
 ; CHECK-NEXT:    [[OFF:%.*]] = add nsw i8 [[X]], 16
-; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[OFF]], 36
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i8 [[X]], -16
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %off = add nsw i8 %x, 16

>From 2c5b36424159b3dbdfe0bf0baf68626f3da6088c Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Tue, 24 Mar 2026 21:14:03 +0800
Subject: [PATCH 3/8] fix: CmpCR could be simple enough

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index c3414d63ada57..206b4dc0a7691 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -13,7 +13,6 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Transforms/Utils/SCCPSolver.h"
-#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/SetVector.h"
 #include "llvm/Analysis/ConstantFolding.h"
 #include "llvm/Analysis/InstructionSimplify.h"
@@ -417,10 +416,8 @@ static Value *simplifyInstruction(SCCPSolver &Solver,
 
       ICmpInst::Predicate Pred;
       APInt RHS;
-      // If NewCmpCR is just the same as CR, no simplification happens.
-      if (NewCmpCR != *CR) {
-        bool Match [[maybe_unused]] = NewCmpCR.getEquivalentICmp(Pred, RHS);
-        assert(Match && "Incorrect simplifyCmpRange");
+      // NewCmpCR might be CmpCR, i.e., no simplification happens.
+      if (NewCmpCR.getEquivalentICmp(Pred, RHS)) {
         IRBuilder<NoFolder> Builder(&Inst);
         Value *NewICmp =
             Builder.CreateICmp(Pred, X, ConstantInt::get(X->getType(), RHS));

>From 6554b14e65496cfbf4f416cfe3ab646dae958912 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Tue, 24 Mar 2026 23:44:23 +0800
Subject: [PATCH 4/8] fix: move a guard to its proper location

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 206b4dc0a7691..44a11fb2d745f 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -126,10 +126,6 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
   assert((!KnownCR.isFullSet() && !KnownCR.isEmptySet()) &&
          "Unexpected KnownCR");
 
-  // If KnownCR is both nuw and nsw, we cannot relax CmpCR at all.
-  if (KnownCR.isWrappedSet() && KnownCR.isSignWrappedSet())
-    return CmpCR;
-
   const unsigned BW = CmpCR.getBitWidth();
   // All reachable value satisfy CmpCR --> always true.
   if (CmpCR.contains(KnownCR))
@@ -150,6 +146,10 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
       /*sge*/ CmpHi.isMinSignedValue())
     return *ActCmpCR;
 
+  // If KnownCR is both nuw and nsw, we cannot relax CmpCR at all.
+  if (KnownCR.isWrappedSet() && KnownCR.isSignWrappedSet())
+    return CmpCR;
+
   const APInt Zero = APInt::getZero(BW);
   const APInt SignMin = APInt::getSignedMinValue(BW);
 

>From 42f845f62ff5ce8578613ddb64678fc740c03fe7 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 25 Mar 2026 00:41:38 +0800
Subject: [PATCH 5/8] fix: suport ne relaxing

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 44a11fb2d745f..62f49dd7f6a2a 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -146,16 +146,19 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
       /*sge*/ CmpHi.isMinSignedValue())
     return *ActCmpCR;
 
-  // If KnownCR is both nuw and nsw, we cannot relax CmpCR at all.
-  if (KnownCR.isWrappedSet() && KnownCR.isSignWrappedSet())
-    return CmpCR;
-
   const APInt Zero = APInt::getZero(BW);
   const APInt SignMin = APInt::getSignedMinValue(BW);
 
   if (CmpLo == KnownCR.getLower()) {
     // Tie to lower:
 
+    // Try ne
+    //    L------------R   : KnownCR
+    //    L-----------R    : ActiveCmpCR
+    // ---------------RL-- : RelaxedCmpCR
+    if (CmpHi + 1 == KnownCR.getUpper())
+      return ConstantRange::getNonEmpty(KnownCR.getUpper(), CmpHi);
+
     // Try ult
     // 0
     // |  L------------R   : KnownCR
@@ -175,6 +178,14 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
   } else if (CmpHi == KnownCR.getUpper()) {
     // Tie to upper:
 
+    // Try ne
+    //
+    //    L--------R       : KnownCR
+    //     L-------R       : ActiveCmpCR
+    // ---RL-------------- : RelaxedCmpCR
+    if (KnownCR.getLower() + 1 == CmpLo)
+      return ConstantRange::getNonEmpty(CmpLo, KnownCR.getLower());
+
     // Try uge
     // 0
     // |  L--------R       : KnownCR

>From 756d74fcf107c5df3f7d02885d3b6a62a32c7acc Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 25 Mar 2026 20:42:11 +0800
Subject: [PATCH 6/8] fix: prefer ne rather than ge/lt

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp | 45 ++++++++++++------------
 1 file changed, 23 insertions(+), 22 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index 62f49dd7f6a2a..cfbf733eb52c8 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -134,13 +134,29 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
   std::optional<ConstantRange> ActCmpCR = CmpCR.exactIntersectWith(KnownCR);
   if (!ActCmpCR)
     return CmpCR;
+  // Proof of ActCmpCR cannot be ne:
+  // 1. ActCmpCR = ne ∧ ActCmpCR ⊆ KnownCR -> KnownCR = ActCmpCR/fullset
+  // 2. KnownCR = fullset contradicts KnownCR != fullset
+  // 3. KnownCR = ActCmpCR = KnownCR ∩ CmpCR -> KnownCR ⊆ CmpCR
+  // 4. KnownCR ⊆ CmpCR contradicts KnownCR ⊈ CmpCR
+  assert(/*ne*/ !ActCmpCR->inverse().isSingleElement() && "Unexpected ne");
+
+  // We prefer eq rather than ne.
+  if (/*eq*/ ActCmpCR->isSingleElement())
+    return *ActCmpCR;
+
+  // We prefer ne rather than lt/ge.
+  //    L--------R       : KnownCR       or    L------------R   : KnownCR
+  //     L-------R       : ActiveCmpCR         L-----------R    : ActiveCmpCR
+  // ---RL-------------- : RelaxedCmpCR     ---------------RL-- : RelaxedCmpCR
+  if (const ConstantRange FalseCR = KnownCR.intersectWith(ActCmpCR->inverse());
+      FalseCR.isSingleElement())
+    return FalseCR.inverse();
 
   const APInt &CmpLo = ActCmpCR->getLower(), &CmpHi = ActCmpCR->getUpper();
 
   // If the intersection happens to be the ONE-icmp check, just return it.
-  if (/*eq*/ ActCmpCR->isSingleElement() ||
-      /*ne*/ ActCmpCR->inverse().isSingleElement() ||
-      /*ult*/ CmpLo.isZero() ||
+  if (/*ult*/ CmpLo.isZero() ||
       /*slt*/ CmpLo.isMinSignedValue() ||
       /*uge*/ CmpHi.isZero() ||
       /*sge*/ CmpHi.isMinSignedValue())
@@ -152,14 +168,7 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
   if (CmpLo == KnownCR.getLower()) {
     // Tie to lower:
 
-    // Try ne
-    //    L------------R   : KnownCR
-    //    L-----------R    : ActiveCmpCR
-    // ---------------RL-- : RelaxedCmpCR
-    if (CmpHi + 1 == KnownCR.getUpper())
-      return ConstantRange::getNonEmpty(KnownCR.getUpper(), CmpHi);
-
-    // Try ult
+    // Try ult.
     // 0
     // |  L------------R   : KnownCR
     // |  L---R            : ActiveCmpCR
@@ -167,7 +176,7 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
     if (!KnownCR.isWrappedSet())
       return ConstantRange::getNonEmpty(Zero, CmpHi);
 
-    // Try slt
+    // Try slt.
     //       smin                                 smin
     // -----R  |  L------- : KnownCR        ----R   |  L------- : KnownCR
     //         |  L--R     : ActiveCmpCR    --R     |  L------- : ActiveCmpCR
@@ -178,15 +187,7 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
   } else if (CmpHi == KnownCR.getUpper()) {
     // Tie to upper:
 
-    // Try ne
-    //
-    //    L--------R       : KnownCR
-    //     L-------R       : ActiveCmpCR
-    // ---RL-------------- : RelaxedCmpCR
-    if (KnownCR.getLower() + 1 == CmpLo)
-      return ConstantRange::getNonEmpty(CmpLo, KnownCR.getLower());
-
-    // Try uge
+    // Try uge.
     // 0
     // |  L--------R       : KnownCR
     // |       L---R       : ActiveCmpCR
@@ -194,7 +195,7 @@ static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
     if (!KnownCR.isWrappedSet())
       return ConstantRange::getNonEmpty(CmpLo, Zero);
 
-    // Try sge
+    // Try sge.
     //       smin                                 smin
     // -----R  |  L------- : KnownCR        -----R  |  L------- : KnownCR
     //   L--R  |           : ActiveCmpCR    -----R  |      L--- : ActiveCmpCR

>From a918eb2758a81a7c9fb0c565edc7bf57d8113622 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Tue, 31 Mar 2026 19:30:34 +0800
Subject: [PATCH 7/8] Fix: correct assertion

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index cfbf733eb52c8..d19135537ad85 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -120,16 +120,20 @@ static ConstantRange getRange(Value *Op, SCCPSolver &Solver,
 /// If no such range preserves the active semantics under KnownCR, keep CmpCR.
 static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
                                       const ConstantRange &KnownCR) {
-  assert(!KnownCR.inverse().contains(CmpCR) &&
-         "CmpCR ∩ KnowCR should not be ∅");
   assert((!CmpCR.isFullSet() && !CmpCR.isEmptySet()) && "Unexpected CmpCR");
-  assert((!KnownCR.isFullSet() && !KnownCR.isEmptySet()) &&
-         "Unexpected KnownCR");
+  assert(!KnownCR.isEmptySet() && "Unexpected KnownCR");
+
+  // If KnownCR is full, bail out early.
+  if (KnownCR.isFullSet())
+    return CmpCR;
 
   const unsigned BW = CmpCR.getBitWidth();
-  // All reachable value satisfy CmpCR --> always true.
+  // All reachable values satisfy CmpCR --> always true.
   if (CmpCR.contains(KnownCR))
     return ConstantRange::getFull(BW);
+  // All values in CmpCR are unreachable --> always false.
+  if (KnownCR.inverse().contains(CmpCR))
+    return ConstantRange::getEmpty(BW);
 
   std::optional<ConstantRange> ActCmpCR = CmpCR.exactIntersectWith(KnownCR);
   if (!ActCmpCR)

>From a58bcf12007c4da94e9e66644816563ecb56602f Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Fri, 1 May 2026 17:14:39 +0800
Subject: [PATCH 8/8] Handle empty/full SCCP relaxed comparison ranges

---
 llvm/lib/Transforms/Utils/SCCPSolver.cpp      |  5 +++-
 .../Transforms/SCCP/relax-range-checks.ll     | 24 +++++++++++++++++++
 2 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Transforms/Utils/SCCPSolver.cpp b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
index d19135537ad85..5b9cf1e97fcd1 100644
--- a/llvm/lib/Transforms/Utils/SCCPSolver.cpp
+++ b/llvm/lib/Transforms/Utils/SCCPSolver.cpp
@@ -120,9 +120,12 @@ static ConstantRange getRange(Value *Op, SCCPSolver &Solver,
 /// If no such range preserves the active semantics under KnownCR, keep CmpCR.
 static ConstantRange simplifyCmpRange(const ConstantRange &CmpCR,
                                       const ConstantRange &KnownCR) {
-  assert((!CmpCR.isFullSet() && !CmpCR.isEmptySet()) && "Unexpected CmpCR");
   assert(!KnownCR.isEmptySet() && "Unexpected KnownCR");
 
+  // Empty and full ranges already lower to a single icmp.
+  if (CmpCR.isEmptySet() || CmpCR.isFullSet())
+    return CmpCR;
+
   // If KnownCR is full, bail out early.
   if (KnownCR.isFullSet())
     return CmpCR;
diff --git a/llvm/test/Transforms/SCCP/relax-range-checks.ll b/llvm/test/Transforms/SCCP/relax-range-checks.ll
index 998271b3b24d9..189cde8fb6061 100644
--- a/llvm/test/Transforms/SCCP/relax-range-checks.ll
+++ b/llvm/test/Transforms/SCCP/relax-range-checks.ll
@@ -161,6 +161,30 @@ define i1 @range_check_to_icmp_sge(i8 range(i8 -56, 20) %x) {
   ret i1 %cmp
 }
 
+define i1 @range_check_to_icmp_empty(i8 range(i8 2, 10) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_empty(
+; CHECK-SAME: i8 range(i8 2, 10) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nuw nsw i8 [[X]], -2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nuw i8 %x, -2
+  %cmp = icmp ult i8 %off, 0
+  ret i1 %cmp
+}
+
+define i1 @range_check_to_icmp_full(i8 range(i8 2, 10) %x) {
+; CHECK-LABEL: define i1 @range_check_to_icmp_full(
+; CHECK-SAME: i8 range(i8 2, 10) [[X:%.*]]) {
+; CHECK-NEXT:    [[OFF:%.*]] = add nuw nsw i8 [[X]], -2
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i8 [[X]], 0
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %off = add nuw i8 %x, -2
+  %cmp = icmp uge i8 %off, 0
+  ret i1 %cmp
+}
+
 ; Cover the early exit when ActiveCmpCR is already a one-icmp check.
 
 define i1 @range_check_intersection_to_icmp_eq(i32 range(i32 0, 4) %x) {



More information about the llvm-commits mailing list