[llvm] [InstCombine] Generalise optimisation of redundant floating point comparisons with `ConstantFPRange` (PR #159315)

Rajveer Singh Bharadwaj via llvm-commits llvm-commits at lists.llvm.org
Sat Sep 20 03:54:07 PDT 2025


https://github.com/Rajveer100 updated https://github.com/llvm/llvm-project/pull/159315

>From c06a312d3ae7c035c34c5f3ed10c11c38156c5ef Mon Sep 17 00:00:00 2001
From: Rajveer <rajveer.developer at icloud.com>
Date: Wed, 17 Sep 2025 15:54:27 +0530
Subject: [PATCH 1/2] [InstCombine] Generalise optimisation of redundant
 floating point comparisons with `ConstantFPRange`

Follow up of #158097

Similar to `simplifyAndOrOfICmpsWithConstants`, we can do so for floating point
comparisons.
---
 llvm/lib/Analysis/InstructionSimplify.cpp     | 86 +++++++++++++------
 .../create-class-from-logic-fcmp.ll           | 15 ++--
 .../Transforms/InstCombine/redundant-fcmp.ll  | 21 ++---
 3 files changed, 75 insertions(+), 47 deletions(-)

diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 100fa428cb842..2e0442eb648ca 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -35,6 +35,7 @@
 #include "llvm/Analysis/TargetLibraryInfo.h"
 #include "llvm/Analysis/ValueTracking.h"
 #include "llvm/Analysis/VectorUtils.h"
+#include "llvm/IR/ConstantFPRange.h"
 #include "llvm/IR/ConstantRange.h"
 #include "llvm/IR/DataLayout.h"
 #include "llvm/IR/Dominators.h"
@@ -1812,6 +1813,61 @@ static Value *simplifyOrOfICmps(ICmpInst *Op0, ICmpInst *Op1,
   return nullptr;
 }
 
+/// Test if a pair of compares with a shared operand and 2 constants has an
+/// empty set intersection, full set union, or if one compare is a superset of
+/// the other.
+static Value *simplifyAndOrOfFCmpsWithConstants(FCmpInst *Cmp0, FCmpInst *Cmp1,
+                                                bool IsAnd) {
+  // Look for this pattern: {and/or} (fcmp X, C0), (fcmp X, C1)).
+  if (Cmp0->getOperand(0) != Cmp1->getOperand(0))
+    return nullptr;
+
+  const APFloat *C0, *C1;
+  if (!match(Cmp0->getOperand(1), m_APFloat(C0)) ||
+      !match(Cmp1->getOperand(1), m_APFloat(C1)))
+    return nullptr;
+
+  auto Range0 = ConstantFPRange::makeExactFCmpRegion(Cmp0->getPredicate(), *C0);
+  auto Range1 = ConstantFPRange::makeExactFCmpRegion(Cmp1->getPredicate(), *C1);
+
+  if (!Range0 || !Range1)
+    return nullptr;
+
+  // For and-of-compares, check if the intersection is empty:
+  // (fcmp X, C0) && (fcmp X, C1) --> empty set --> false
+  if (IsAnd && (*Range0).intersectWith(*Range1).isEmptySet())
+    return getFalse(Cmp0->getType());
+
+  // For or-of-compares, check if the union is full:
+  // (fcmp X, C0) || (fcmp X, C1) --> full set --> true
+  //
+  // TODO: `unionWith` is not precise at the moment, so
+  // we can invert the predicate and check:
+  // inv(fcmp X, C0) && inv(fcmp X, C1) --> empty set --> false
+  if (!IsAnd) {
+    auto Range0Inv = ConstantFPRange::makeExactFCmpRegion(
+        FCmpInst::getInversePredicate(Cmp0->getPredicate()), *C0);
+    auto Range1Inv = ConstantFPRange::makeExactFCmpRegion(
+        FCmpInst::getInversePredicate(Cmp1->getPredicate()), *C1);
+    if (Range0Inv && Range1Inv) {
+      if ((*Range0Inv).intersectWith(*Range1Inv).isEmptySet())
+        return getFalse(Cmp0->getType());
+    }
+  }
+
+  // Is one range a superset of the other?
+  // If this is and-of-compares, take the smaller set:
+  // (fcmp ogt X, 4) && (fcmp ogt X, 42) --> fcmp ogt X, 42
+  // If this is or-of-compares, take the larger set:
+  // (fcmp ogt X, 4) || (fcmp ogt X, 42) --> fcmp ogt X, 4
+  if ((*Range0).contains(*Range1))
+    return IsAnd ? Cmp1 : Cmp0;
+  if ((*Range1).contains(*Range0))
+    return IsAnd ? Cmp0 : Cmp1;
+
+  return nullptr;
+}
+
 static Value *simplifyAndOrOfFCmps(const SimplifyQuery &Q, FCmpInst *LHS,
                                    FCmpInst *RHS, bool IsAnd) {
   Value *LHS0 = LHS->getOperand(0), *LHS1 = LHS->getOperand(1);
@@ -1850,34 +1906,8 @@ static Value *simplifyAndOrOfFCmps(const SimplifyQuery &Q, FCmpInst *LHS,
                  : ConstantInt::getBool(LHS->getType(), !IsAnd);
   }
 
-  Value *V0;
-  const APFloat *V0Op1, *V1Op1;
-  // (fcmp olt V0, V0Op1) || (fcmp olt V0, V1Op1)
-  //                      --> fcmp olt V0, max(V0Op1, V1Op1)
-  // (fcmp ogt V0, V0Op1) || (fcmp ogt V0, V1Op1)
-  //                      --> fcmp ogt V0, max(V0Op1, V1Op1)
-  //
-  // (fcmp olt V0, V0Op1) && (fcmp olt V0, V1Op1)
-  //                      --> fcmp olt V0, min(V0Op1, V1Op1)
-  // (fcmp ogt V0, V0Op1) && (fcmp ogt V0, V1Op1)
-  //                      --> fcmp ogt V0, min(V0Op1, V1Op1)
-  if (match(LHS, m_SpecificFCmp(FCmpInst::FCMP_OLT, m_Value(V0),
-                                m_APFloat(V0Op1))) &&
-      match(RHS, m_SpecificFCmp(FCmpInst::FCMP_OLT, m_Specific(V0),
-                                m_APFloat(V1Op1)))) {
-    if (*V0Op1 > *V1Op1)
-      return IsAnd ? RHS : LHS;
-    if (*V1Op1 > *V0Op1)
-      return IsAnd ? LHS : RHS;
-  } else if (match(LHS, m_SpecificFCmp(FCmpInst::FCMP_OGT, m_Value(V0),
-                                       m_APFloat(V0Op1))) &&
-             match(RHS, m_SpecificFCmp(FCmpInst::FCMP_OGT, m_Specific(V0),
-                                       m_APFloat(V1Op1)))) {
-    if (*V0Op1 < *V1Op1)
-      return IsAnd ? RHS : LHS;
-    if (*V1Op1 < *V0Op1)
-      return IsAnd ? LHS : RHS;
-  }
+  if (auto *V = simplifyAndOrOfFCmpsWithConstants(LHS, RHS, IsAnd))
+    return V;
 
   return nullptr;
 }
diff --git a/llvm/test/Transforms/InstCombine/create-class-from-logic-fcmp.ll b/llvm/test/Transforms/InstCombine/create-class-from-logic-fcmp.ll
index 9a723e8bc89ff..625897a9996e4 100644
--- a/llvm/test/Transforms/InstCombine/create-class-from-logic-fcmp.ll
+++ b/llvm/test/Transforms/InstCombine/create-class-from-logic-fcmp.ll
@@ -567,7 +567,8 @@ define i1 @not_issubnormal_or_inf(half %x) {
 
 define i1 @issubnormal_uge_or_inf(half %x) {
 ; CHECK-LABEL: @issubnormal_uge_or_inf(
-; CHECK-NEXT:    [[CLASS:%.*]] = call i1 @llvm.is.fpclass.f16(half [[X:%.*]], i32 783)
+; CHECK-NEXT:    [[FABS:%.*]] = call half @llvm.fabs.f16(half [[X:%.*]])
+; CHECK-NEXT:    [[CLASS:%.*]] = fcmp uge half [[FABS]], 0xH0400
 ; CHECK-NEXT:    ret i1 [[CLASS]]
 ;
   %fabs = call half @llvm.fabs.f16(half %x)
@@ -609,10 +610,8 @@ define i1 @issubnormal_or_inf_neg_smallest_normal(half %x) {
 define i1 @fneg_fabs_olt_neg_smallest_normal_or_inf(half %x) {
 ; CHECK-LABEL: @fneg_fabs_olt_neg_smallest_normal_or_inf(
 ; CHECK-NEXT:    [[FABS:%.*]] = call half @llvm.fabs.f16(half [[X:%.*]])
-; CHECK-NEXT:    [[CMPINF:%.*]] = fcmp oeq half [[FABS]], 0xH7C00
 ; CHECK-NEXT:    [[CMP_SMALLEST_NORMAL:%.*]] = fcmp ogt half [[FABS]], 0xH0400
-; CHECK-NEXT:    [[CLASS:%.*]] = or i1 [[CMP_SMALLEST_NORMAL]], [[CMPINF]]
-; CHECK-NEXT:    ret i1 [[CLASS]]
+; CHECK-NEXT:    ret i1 [[CMP_SMALLEST_NORMAL]]
 ;
   %fabs = call half @llvm.fabs.f16(half %x)
   %cmpinf = fcmp oeq half %fabs, 0xH7C00
@@ -674,7 +673,8 @@ define i1 @not_zero_and_subnormal(half %x) {
 
 define i1 @fcmp_fabs_uge_inf_or_fabs_uge_smallest_norm(half %x) {
 ; CHECK-LABEL: @fcmp_fabs_uge_inf_or_fabs_uge_smallest_norm(
-; CHECK-NEXT:    [[OR:%.*]] = call i1 @llvm.is.fpclass.f16(half [[X:%.*]], i32 783)
+; CHECK-NEXT:    [[FABS:%.*]] = call half @llvm.fabs.f16(half [[X:%.*]])
+; CHECK-NEXT:    [[OR:%.*]] = fcmp uge half [[FABS]], 0xH0400
 ; CHECK-NEXT:    ret i1 [[OR]]
 ;
   %fabs = call half @llvm.fabs.f16(half %x)
@@ -868,7 +868,8 @@ define i1 @une_or_oge_smallest_normal(half %x) #0 {
 ; -> normal | inf
 define i1 @isnormalinf_or_inf(half %x) #0 {
 ; CHECK-LABEL: @isnormalinf_or_inf(
-; CHECK-NEXT:    [[OR:%.*]] = call i1 @llvm.is.fpclass.f16(half [[X:%.*]], i32 780)
+; CHECK-NEXT:    [[FABS:%.*]] = call half @llvm.fabs.f16(half [[X:%.*]])
+; CHECK-NEXT:    [[OR:%.*]] = fcmp oge half [[FABS]], 0xH0400
 ; CHECK-NEXT:    ret i1 [[OR]]
 ;
   %fabs = call half @llvm.fabs.f16(half %x)
@@ -1408,7 +1409,7 @@ define i1 @oeq_neginfinity_or_oeq_smallest_normal(half %x) #0 {
 ; -> ninf | fcZero | fcSubnormal
 define i1 @oeq_neginfinity_or_olt_smallest_normal(half %x) #0 {
 ; CHECK-LABEL: @oeq_neginfinity_or_olt_smallest_normal(
-; CHECK-NEXT:    [[CLASS:%.*]] = call i1 @llvm.is.fpclass.f16(half [[X:%.*]], i32 252)
+; CHECK-NEXT:    [[CLASS:%.*]] = fcmp olt half [[X:%.*]], 0xH0400
 ; CHECK-NEXT:    ret i1 [[CLASS]]
 ;
   %oeq.neg.infinity = fcmp oeq half %x, 0xHFC00
diff --git a/llvm/test/Transforms/InstCombine/redundant-fcmp.ll b/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
index 0f5fe9fb9a1b2..5d1529ef0b214 100644
--- a/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
+++ b/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
@@ -45,8 +45,8 @@ define i1 @or_fcmp_redundant_or4(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_or_neg1(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_or_neg1(
+define i1 @or_fcmp_redundant_or_5(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_or_5(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp olt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp ogt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = or i1 [[V1]], [[V2]]
@@ -58,8 +58,8 @@ define i1 @or_fcmp_redundant_or_neg1(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_or_neg2(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_or_neg2(
+define i1 @or_fcmp_redundant_or_6(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_or_6(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp ogt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp olt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = or i1 [[V1]], [[V2]]
@@ -115,12 +115,9 @@ define i1 @or_fcmp_redundant_and4(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_and_neg1(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_and_neg1(
-; CHECK-NEXT:    [[V1:%.*]] = fcmp olt double [[V0:%.*]], 1.000000e-02
-; CHECK-NEXT:    [[V2:%.*]] = fcmp ogt double [[V0]], 1.990000e+00
-; CHECK-NEXT:    [[V3:%.*]] = and i1 [[V1]], [[V2]]
-; CHECK-NEXT:    ret i1 [[V3]]
+define i1 @or_fcmp_redundant_and_5(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_and_5(
+; CHECK-NEXT:    ret i1 false
 ;
   %v1 = fcmp olt double %v0, 1.000000e-02
   %v2 = fcmp ogt double %v0, 1.990000e+00
@@ -128,8 +125,8 @@ define i1 @or_fcmp_redundant_and_neg1(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_and_neg2(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_and_neg2(
+define i1 @or_fcmp_redundant_and_6(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_and_6(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp ogt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp olt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = and i1 [[V1]], [[V2]]

>From dd232fca00a2de858abca1ee55a9c9630a1b6c05 Mon Sep 17 00:00:00 2001
From: Rajveer Singh Bharadwaj <rajveer.developer at icloud.com>
Date: Sat, 20 Sep 2025 16:03:24 +0530
Subject: [PATCH 2/2] Apply suggestions from code review

Co-authored-by: Yingwei Zheng <dtcxzyw at qq.com>
---
 llvm/lib/Analysis/InstructionSimplify.cpp     | 35 ++++++-------------
 .../Transforms/InstCombine/redundant-fcmp.ll  | 16 ++++-----
 2 files changed, 18 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Analysis/InstructionSimplify.cpp b/llvm/lib/Analysis/InstructionSimplify.cpp
index 2e0442eb648ca..3e4f19ce83688 100644
--- a/llvm/lib/Analysis/InstructionSimplify.cpp
+++ b/llvm/lib/Analysis/InstructionSimplify.cpp
@@ -1827,43 +1827,28 @@ static Value *simplifyAndOrOfFCmpsWithConstants(FCmpInst *Cmp0, FCmpInst *Cmp1,
       !match(Cmp1->getOperand(1), m_APFloat(C1)))
     return nullptr;
 
-  auto Range0 = ConstantFPRange::makeExactFCmpRegion(Cmp0->getPredicate(), *C0);
-  auto Range1 = ConstantFPRange::makeExactFCmpRegion(Cmp1->getPredicate(), *C1);
+  auto Range0 = ConstantFPRange::makeExactFCmpRegion(
+      IsAnd ? Cmp0->getPredicate() : Cmp0->getInversePredicate(), *C0);
+  auto Range1 = ConstantFPRange::makeExactFCmpRegion(
+      IsAnd ? Cmp1->getPredicate() : Cmp1->getInversePredicate(), *C1);
 
   if (!Range0 || !Range1)
     return nullptr;
 
   // For and-of-compares, check if the intersection is empty:
   // (fcmp X, C0) && (fcmp X, C1) --> empty set --> false
-  if (IsAnd && (*Range0).intersectWith(*Range1).isEmptySet())
-    return getFalse(Cmp0->getType());
-
-  // For or-of-compares, check if the union is full:
-  // (fcmp X, C0) || (fcmp X, C1) --> full set --> true
-  //
-  // TODO: `unionWith` is not precise at the moment, so
-  // we can invert the predicate and check:
-  // inv(fcmp X, C0) && inv(fcmp X, C1) --> empty set --> false
-  if (!IsAnd) {
-    auto Range0Inv = ConstantFPRange::makeExactFCmpRegion(
-        FCmpInst::getInversePredicate(Cmp0->getPredicate()), *C0);
-    auto Range1Inv = ConstantFPRange::makeExactFCmpRegion(
-        FCmpInst::getInversePredicate(Cmp1->getPredicate()), *C1);
-    if (Range0Inv && Range1Inv) {
-      if ((*Range0Inv).intersectWith(*Range1Inv).isEmptySet())
-        return getFalse(Cmp0->getType());
-    }
-  }
+  if (Range0->intersectWith(*Range1).isEmptySet())
+    return ConstantInt::getBool(Cmp0->getType(), !IsAnd);
 
   // Is one range a superset of the other?
   // If this is and-of-compares, take the smaller set:
   // (fcmp ogt X, 4) && (fcmp ogt X, 42) --> fcmp ogt X, 42
   // If this is or-of-compares, take the larger set:
   // (fcmp ogt X, 4) || (fcmp ogt X, 42) --> fcmp ogt X, 4
-  if ((*Range0).contains(*Range1))
-    return IsAnd ? Cmp1 : Cmp0;
-  if ((*Range1).contains(*Range0))
-    return IsAnd ? Cmp0 : Cmp1;
+  if (Range0->contains(*Range1))
+    return Cmp1;
+  if (Range1->contains(*Range0))
+    return Cmp0;
 
   return nullptr;
 }
diff --git a/llvm/test/Transforms/InstCombine/redundant-fcmp.ll b/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
index 5d1529ef0b214..7f579ed9f25c5 100644
--- a/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
+++ b/llvm/test/Transforms/InstCombine/redundant-fcmp.ll
@@ -45,8 +45,8 @@ define i1 @or_fcmp_redundant_or4(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_or_5(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_or_5(
+define i1 @or_fcmp_redundant_or_neg1(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_or_neg1(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp olt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp ogt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = or i1 [[V1]], [[V2]]
@@ -58,8 +58,8 @@ define i1 @or_fcmp_redundant_or_5(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_or_6(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_or_6(
+define i1 @or_fcmp_redundant_or_neg2(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_or_neg2(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp ogt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp olt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = or i1 [[V1]], [[V2]]
@@ -115,8 +115,8 @@ define i1 @or_fcmp_redundant_and4(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_and_5(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_and_5(
+define i1 @or_fcmp_redundant_and_neg1(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_and_neg1(
 ; CHECK-NEXT:    ret i1 false
 ;
   %v1 = fcmp olt double %v0, 1.000000e-02
@@ -125,8 +125,8 @@ define i1 @or_fcmp_redundant_and_5(double %v0) {
   ret i1 %v3
 }
 
-define i1 @or_fcmp_redundant_and_6(double %v0) {
-; CHECK-LABEL: @or_fcmp_redundant_and_6(
+define i1 @or_fcmp_redundant_and_neg2(double %v0) {
+; CHECK-LABEL: @or_fcmp_redundant_and_neg2(
 ; CHECK-NEXT:    [[V1:%.*]] = fcmp ogt double [[V0:%.*]], 1.000000e-02
 ; CHECK-NEXT:    [[V2:%.*]] = fcmp olt double [[V0]], 1.990000e+00
 ; CHECK-NEXT:    [[V3:%.*]] = and i1 [[V1]], [[V2]]



More information about the llvm-commits mailing list