[llvm] [InstCombine] Fold fcmp eq over min/max-like select into compare on input (PR #188226)

Nathiyaa Sengodan via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 1 02:18:10 PDT 2026


https://github.com/Nathiyaa-Sengodan updated https://github.com/llvm/llvm-project/pull/188226

>From e70e9b8d760541d10b5c7a4595623d342f0b774d Mon Sep 17 00:00:00 2001
From: Nathiyaa Sengodan <s.nathiyaa at gmail.com>
Date: Tue, 24 Mar 2026 16:21:36 +0530
Subject: [PATCH 1/2] [InstCombine] Fold fcmp eq over min/max-like select into
 compare on input
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Recognize fcmp oeq/ueq where the LHS is a min/max-like select
driven by fcmp ogt/oge, and fold it into a simpler comparison
directly on the input value.

For max-like patterns:

max(X, K) == C:

C > K  -> X == C
C == K -> X <= K

For min-like patterns:

min(X, K) == C:

C < K  -> X == C
C == K -> X >= K
---
 .../InstCombine/InstCombineCompares.cpp       |  76 +++++++
 .../Transforms/InstCombine/fcmp-select.ll     | 198 ++++++++++++++++++
 2 files changed, 274 insertions(+)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 7b6d380acffe1..8fadbbc8a7a25 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8984,6 +8984,80 @@ static Instruction *foldFCmpWithFloorAndCeil(FCmpInst &I,
   return nullptr;
 }
 
+/// Fold equality compares against OGT/OGE-driven min/max-like selects:
+///   fcmp oeq/ueq (select (fcmp ogt/oge X, K), X, K), C
+///   fcmp oeq/ueq (select (fcmp ogt/oge X, K), K, X), C
+///
+/// Where one select arm is the compare bound constant K and the other is X.
+/// This performs a local one-step simplification based on C vs K:
+///   max-like select:
+///     C > K  -> X == C
+///     C == K -> X <= K
+///   min-like select:
+///     C < K  -> X == C
+///     C == K -> X >= K
+///
+static Instruction *
+foldFCmpEqWithMinMaxLikeSelect(FCmpInst &I, Instruction *LHSI, Constant *RHSC) {
+  const FCmpInst::Predicate Pred = I.getPredicate();
+  if (Pred != FCmpInst::FCMP_OEQ && Pred != FCmpInst::FCMP_UEQ)
+    return nullptr;
+
+  auto *SI = dyn_cast<SelectInst>(LHSI);
+  const APFloat *C;
+  if (!SI || !match(RHSC, m_APFloat(C)))
+    return nullptr;
+
+  auto *InnerCmp = dyn_cast<FCmpInst>(SI->getCondition());
+  if (!InnerCmp)
+    return nullptr;
+
+  // Restrict to OGT/OGE-driven min/max-like selects.
+  const FCmpInst::Predicate InnerPred = InnerCmp->getPredicate();
+  if (InnerPred != FCmpInst::FCMP_OGT && InnerPred != FCmpInst::FCMP_OGE)
+    return nullptr;
+
+  auto *CmpBound = dyn_cast<ConstantFP>(InnerCmp->getOperand(1));
+  if (!CmpBound)
+    return nullptr;
+  Value *X = InnerCmp->getOperand(0);
+
+  Value *TV = SI->getTrueValue();
+  Value *FV = SI->getFalseValue();
+
+  // select(cmp X, K), K, X is min-like;
+  // select(cmp X, K), X, K is max-like.
+  const bool isMinPattern = (TV == CmpBound && FV == X);
+  const bool isMaxPattern = (TV == X && FV == CmpBound);
+
+  if (!(isMinPattern || isMaxPattern))
+    return nullptr;
+
+  APFloat::cmpResult CmpVsBound = C->compare(CmpBound->getValueAPF());
+  if (isMaxPattern) {
+    // max(X, K) == C:
+    //   C > K  -> X == C
+    //   C == K -> X <= K
+    if (CmpVsBound == APFloat::cmpGreaterThan)
+      return new FCmpInst(FCmpInst::FCMP_OEQ, X, RHSC, "", &I);
+    if (CmpVsBound == APFloat::cmpEqual)
+      return new FCmpInst(FCmpInst::FCMP_ULE, X, RHSC, "", &I);
+    return nullptr;
+  }
+
+  bool Ordered = FCmpInst::isOrdered(Pred);
+  // min(X, K) == C:
+  //   C < K  -> X == C
+  //   C == K -> X >= K
+  if (CmpVsBound == APFloat::cmpLessThan)
+    return new FCmpInst(Ordered ? FCmpInst::FCMP_OEQ : FCmpInst::FCMP_UEQ, X,
+                        RHSC, "", &I);
+  if (CmpVsBound == APFloat::cmpEqual)
+    return new FCmpInst(Ordered ? FCmpInst::FCMP_OGE : FCmpInst::FCMP_UGE, X,
+                        RHSC, "", &I);
+  return nullptr;
+}
+
 /// Returns true if a select that implements a min/max is redundant and
 /// select result can be replaced with its non-constant operand, e.g.,
 ///   select ( (si/ui-to-fp A) <= C ), C, (si/ui-to-fp A)
@@ -9186,6 +9260,8 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
         return replaceOperand(I, 0, X);
       if (Instruction *NV = FoldOpIntoSelect(I, cast<SelectInst>(LHSI)))
         return NV;
+      if (Instruction *NV = foldFCmpEqWithMinMaxLikeSelect(I, LHSI, RHSC))
+        return NV;
       break;
     case Instruction::FSub:
       if (LHSI->hasOneUse())
diff --git a/llvm/test/Transforms/InstCombine/fcmp-select.ll b/llvm/test/Transforms/InstCombine/fcmp-select.ll
index 2115cd7f232e2..af625526dd8f4 100644
--- a/llvm/test/Transforms/InstCombine/fcmp-select.ll
+++ b/llvm/test/Transforms/InstCombine/fcmp-select.ll
@@ -595,3 +595,201 @@ define float @test_select_fcmp_uitofp_min(i8 %x) {
   %sel = select i1 %cmp, float 2.550000e+02, float %f
   ret float %sel
 }
+
+define i1 @fold_fcmp_min_clamp(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %max_cmp = fcmp ogt double %x, 2.0
+  %clamp = select i1 %max_cmp, double %x, double 2.0
+  %cmp = fcmp oeq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_min_clamp_same_constant(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_same_constant(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ule double [[X:%.*]], 2.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %max_cmp = fcmp ogt double %x, 2.0
+  %clamp = select i1 %max_cmp, double %x, double 2.0
+  %cmp = fcmp oeq double %clamp, 2.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_max_clamp(double %x) {
+; CHECK-LABEL: @fold_fcmp_max_clamp(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %clamp = select i1 %min_cmp, double 5.0, double %x
+  %cmp = fcmp oeq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_ueq_max_clamp(double %x) {
+; CHECK-LABEL: @fold_fcmp_ueq_max_clamp(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp uge double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %clamp = select i1 %min_cmp, double 5.0, double %x
+  %cmp = fcmp ueq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_max_clamp_small_const(double %x) {
+; CHECK-LABEL: @fold_fcmp_max_clamp_small_const(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ueq double [[X:%.*]], 3.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %clamp = select i1 %min_cmp, double 5.0, double %x
+  %cmp = fcmp ueq double %clamp, 3.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_min_clamp_oge_cond(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_oge_cond(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 7.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %max_cmp = fcmp oge double %x, 2.0
+  %clamp = select i1 %max_cmp, double %x, double 2.0
+  %cmp = fcmp oeq double %clamp, 7.0
+  ret i1 %cmp
+}
+
+; Full clamp (OGT/OGE-driven nested min/max shapes)
+define i1 @fold_fcmp_full_clamp_oeq_hi(double %x) {
+; CHECK-LABEL: @fold_fcmp_full_clamp_oeq_hi(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %min = select i1 %min_cmp, double 5.0, double %x
+  %max_cmp = fcmp ogt double %min, 2.0
+  %clamp = select i1 %max_cmp, double %min, double 2.0
+  %cmp = fcmp oeq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_full_clamp_oeq_lo(double %x) {
+; CHECK-LABEL: @fold_fcmp_full_clamp_oeq_lo(
+; CHECK-NEXT:    [[MIN_CMP:%.*]] = fcmp ogt double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    [[MIN:%.*]] = select i1 [[MIN_CMP]], double 5.000000e+00, double [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ule double [[MIN]], 2.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %min = select i1 %min_cmp, double 5.0, double %x
+  %max_cmp = fcmp ogt double %min, 2.0
+  %clamp = select i1 %max_cmp, double %min, double 2.0
+  %cmp = fcmp oeq double %clamp, 2.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_full_clamp_mid_const(double %x) {
+; CHECK-LABEL: @fold_fcmp_full_clamp_mid_const(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 4.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %min = select i1 %min_cmp, double 5.0, double %x
+  %max_cmp = fcmp ogt double %min, 2.0
+  %clamp = select i1 %max_cmp, double %min, double 2.0
+  %cmp = fcmp oeq double %clamp, 4.0
+  ret i1 %cmp
+}
+define i1 @fold_fcmp_clamp_minmax_alt_oeq_hi(double %x) {
+; CHECK-LABEL: @fold_fcmp_clamp_minmax_alt_oeq_hi(
+; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
+; CHECK-NEXT:    [[MAX:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[MAX]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  ; clamp as min(max(X, 2), 5) instead of max(min(X, 5), 2) pattern
+  %max_cmp = fcmp ogt double %x, 2.0
+  %max = select i1 %max_cmp, double %x, double 2.0
+  %min_cmp = fcmp ogt double %max, 5.0
+  %clamp = select i1 %min_cmp, double 5.0, double %max
+  %cmp = fcmp oeq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_two_clamp_hi(float %arg0, float %arg1) {
+; CHECK-LABEL: @fold_two_clamp_hi(
+; CHECK-DAG:    [[CMP0:%.*]] = fcmp oge float [[ARG0:%.*]], 1.000000e+00
+; CHECK-DAG:    [[CMP1:%.*]] = fcmp oge float [[ARG1:%.*]], 1.000000e+00
+; CHECK:        [[RES:%.*]] = and i1
+; CHECK-NEXT:   ret i1 [[RES]]
+;
+  %v0 = fcmp ogt float %arg1, 1.000000e+00
+  %v1 = select i1 %v0, float 1.000000e+00, float %arg1
+  %v2 = fcmp ogt float %v1, 0.000000e+00
+  %v3 = select i1 %v2, float %v1, float 0.000000e+00
+  %v4 = fcmp ogt float %arg0, 1.000000e+00
+  %v5 = select i1 %v4, float 1.000000e+00, float %arg0
+  %v6 = fcmp ogt float %v5, 0.000000e+00
+  %v7 = select i1 %v6, float %v5, float 0.000000e+00
+  %v8 = fcmp oeq float %v3, 1.000000e+00
+  %v9 = fcmp oeq float %v7, 1.000000e+00
+  %v10 = and i1 %v8, %v9
+  ret i1 %v10
+}
+
+; Negative test cases (including non-OGT/OGE predicate shapes)
+
+define i1 @fold_fcmp_non_minmax_shape(double %x, double %y) {
+; CHECK-LABEL: @fold_fcmp_non_minmax_shape(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
+; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT:    [[RES:%.*]] = fcmp oeq double [[CLAMP]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[RES]]
+;
+  %cmp = fcmp ogt double %x, 2.0
+  %clamp = select i1 %cmp, double %x, double %y
+  %res = fcmp oeq double %clamp, 5.0
+  ret i1 %res
+}
+
+define i1 @fold_fcmp_min_clamp_non_og(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_non_og(
+; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp olt double [[X:%.*]], 2.000000e+00
+; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 5.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %max_cmp = fcmp olt double %x, 2.0
+  %clamp = select i1 %max_cmp, double %x, double 2.0
+  %cmp = fcmp oeq double %clamp, 5.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_max_clamp_bigger_const_no_fold(double %x) {
+; CHECK-LABEL: @fold_fcmp_max_clamp_bigger_const_no_fold(
+; CHECK-NEXT:    [[MIN_CMP:%.*]] = fcmp ogt double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MIN_CMP]], double 5.000000e+00, double [[X]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 6.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %min_cmp = fcmp ogt double %x, 5.0
+  %clamp = select i1 %min_cmp, double 5.0, double %x
+  %cmp = fcmp oeq double %clamp, 6.0
+  ret i1 %cmp
+}
+
+define i1 @fold_fcmp_min_clamp_smaller_const_no_fold(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_smaller_const_no_fold(
+; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
+; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 1.000000e+00
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %max_cmp = fcmp ogt double %x, 2.0
+  %clamp = select i1 %max_cmp, double %x, double 2.0
+  %cmp = fcmp oeq double %clamp, 1.0
+  ret i1 %cmp
+}

>From 14cba35d593761341399857e9f30982ad7d3dd06 Mon Sep 17 00:00:00 2001
From: Nathiyaa Sengodan <s.nathiyaa at gmail.com>
Date: Fri, 8 May 2026 00:10:01 +0530
Subject: [PATCH 2/2] Address review comments

- Add support for vector inputs
- Expand the inner compare predicate support to both olt and ogt
- Take care of FMF in the target fcmp instruction
- Use matchSelectPattern instead of matching fmin/fmax manually
- Update tests - add new tests along with vector tests
---
 .../InstCombine/InstCombineCompares.cpp       | 155 +++++++++-----
 .../Transforms/InstCombine/fcmp-select.ll     | 196 ++++++++++--------
 2 files changed, 209 insertions(+), 142 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 8fadbbc8a7a25..69dd072a7a6eb 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8984,78 +8984,129 @@ static Instruction *foldFCmpWithFloorAndCeil(FCmpInst &I,
   return nullptr;
 }
 
-/// Fold equality compares against OGT/OGE-driven min/max-like selects:
-///   fcmp oeq/ueq (select (fcmp ogt/oge X, K), X, K), C
-///   fcmp oeq/ueq (select (fcmp ogt/oge X, K), K, X), C
+/// Fold equality/inequality compares against floating min/max-like select
+/// patterns.
 ///
-/// Where one select arm is the compare bound constant K and the other is X.
-/// This performs a local one-step simplification based on C vs K:
-///   max-like select:
+/// This handles:
+///   fcmp oeq/ueq/one/une (select (fcmp Pred X, K), X, K), C
+///   fcmp oeq/ueq/one/une (select (fcmp Pred X, K), K, X), C
+///
+/// when the select is recognized as an fmaxnum/fminnum-like operation and one
+/// operand is the constant bound K. The result can be expressed as a
+/// direct comparison against X based on the relation between C and K:
+///
+///   fmaxnum-like:
 ///     C > K  -> X == C
 ///     C == K -> X <= K
-///   min-like select:
+///
+///   fminnum-like:
 ///     C < K  -> X == C
 ///     C == K -> X >= K
 ///
-static Instruction *
-foldFCmpEqWithMinMaxLikeSelect(FCmpInst &I, Instruction *LHSI, Constant *RHSC) {
-  const FCmpInst::Predicate Pred = I.getPredicate();
-  if (Pred != FCmpInst::FCMP_OEQ && Pred != FCmpInst::FCMP_UEQ)
+static Instruction *foldFCmpEqWithMinMaxLikeSelect(FCmpInst &I,
+                                                   Instruction *LHSI,
+                                                   Constant *RHSC,
+                                                   InstCombinerImpl &CI) {
+  FCmpInst::Predicate Pred = I.getPredicate();
+
+  // Currently handles only equality predicates.
+  if (!I.isEquality())
     return nullptr;
 
-  auto *SI = dyn_cast<SelectInst>(LHSI);
+  bool Invert = false;
+  if (Pred != FCmpInst::FCMP_OEQ && Pred != FCmpInst::FCMP_UEQ) {
+    Invert = true;
+    Pred = I.getInversePredicate();
+  }
+
   const APFloat *C;
-  if (!SI || !match(RHSC, m_APFloat(C)))
+  if (!match(RHSC, m_APFloat(C)))
     return nullptr;
 
-  auto *InnerCmp = dyn_cast<FCmpInst>(SI->getCondition());
-  if (!InnerCmp)
-    return nullptr;
+  Value *X;
+  Constant *CmpBoundC;
+  bool IsMax;
 
-  // Restrict to OGT/OGE-driven min/max-like selects.
-  const FCmpInst::Predicate InnerPred = InnerCmp->getPredicate();
-  if (InnerPred != FCmpInst::FCMP_OGT && InnerPred != FCmpInst::FCMP_OGE)
+  if (match(LHSI, m_OrdOrUnordFMax(m_Value(X), m_Constant(CmpBoundC))))
+    IsMax = true;
+  else if (match(LHSI, m_OrdOrUnordFMin(m_Value(X), m_Constant(CmpBoundC))))
+    IsMax = false;
+  else
     return nullptr;
 
-  auto *CmpBound = dyn_cast<ConstantFP>(InnerCmp->getOperand(1));
-  if (!CmpBound)
-    return nullptr;
-  Value *X = InnerCmp->getOperand(0);
+  auto *SI = cast<SelectInst>(LHSI);
 
-  Value *TV = SI->getTrueValue();
-  Value *FV = SI->getFalseValue();
+  // Handling both scalar ConstantFP and splat vector constants.
+  const APFloat *CmpBoundAP;
+  if (!match(CmpBoundC, m_APFloat(CmpBoundAP)))
+    return nullptr;
 
-  // select(cmp X, K), K, X is min-like;
-  // select(cmp X, K), X, K is max-like.
-  const bool isMinPattern = (TV == CmpBound && FV == X);
-  const bool isMaxPattern = (TV == X && FV == CmpBound);
+  // Determine NaN behavior from the IR structure of the select.
+  //
+  // For an ordered inner predicate (NaN -> false -> selects FalseVal):
+  //   NaN(X) returns the constant iff the constant is the FalseVal.
+  // For an unordered inner predicate (NaN -> true -> selects TrueVal):
+  //   NaN(X) returns the constant iff the constant is the TrueVal.
+  //
+  // In both cases: NaNReturnsConst = (PredIsOrdered != ConstIsTV).
+  auto *FcmpCond = cast<FCmpInst>(SI->getCondition());
+  bool PredIsOrdered = CmpInst::isOrdered(FcmpCond->getPredicate());
+  bool ConstIsTV = SI->getTrueValue() == CmpBoundC;
+  bool NaNReturnsConst = PredIsOrdered != ConstIsTV;
 
-  if (!(isMinPattern || isMaxPattern))
-    return nullptr;
+  const SimplifyQuery Q = CI.getSimplifyQuery().getWithInstruction(&I);
+  bool Ordered = FCmpInst::isOrdered(Pred);
+  FCmpInst::Predicate NewPred;
+  APFloat::cmpResult CmpVsBound = C->compare(*CmpBoundAP);
 
-  APFloat::cmpResult CmpVsBound = C->compare(CmpBound->getValueAPF());
-  if (isMaxPattern) {
+  if (IsMax) {
     // max(X, K) == C:
-    //   C > K  -> X == C
-    //   C == K -> X <= K
+    //
+    // C > K  -> X == C
+    // C == K -> X <= K
+
     if (CmpVsBound == APFloat::cmpGreaterThan)
-      return new FCmpInst(FCmpInst::FCMP_OEQ, X, RHSC, "", &I);
+      NewPred = Ordered ? FCmpInst::FCMP_OEQ : FCmpInst::FCMP_UEQ;
+    else if (CmpVsBound == APFloat::cmpEqual)
+      NewPred = Ordered ? FCmpInst::FCMP_OLE : FCmpInst::FCMP_ULE;
+    else
+      return nullptr;
+  } else {
+    // min(X, K) == C:
+    //
+    // C < K  -> X == C
+    // C == K -> X >= K
+
+    if (CmpVsBound == APFloat::cmpLessThan)
+      NewPred = Ordered ? FCmpInst::FCMP_OEQ : FCmpInst::FCMP_UEQ;
+    else if (CmpVsBound == APFloat::cmpEqual)
+      NewPred = Ordered ? FCmpInst::FCMP_OGE : FCmpInst::FCMP_UGE;
+    else
+      return nullptr;
+  }
+
+  // When the select maps NaN to the bound constant or X is known non-NaN,
+  // choose the appropriate ordered/unordered predicate variant that preserves
+  // the original comparison semantics.
+  if (NaNReturnsConst || SI->getFastMathFlags().noNaNs() ||
+      FcmpCond->hasNoNaNs() || isKnownNeverNaN(X, Q)) {
     if (CmpVsBound == APFloat::cmpEqual)
-      return new FCmpInst(FCmpInst::FCMP_ULE, X, RHSC, "", &I);
-    return nullptr;
+      NewPred = FCmpInst::getUnorderedPredicate(NewPred);
+    else
+      NewPred = FCmpInst::getOrderedPredicate(NewPred);
   }
 
-  bool Ordered = FCmpInst::isOrdered(Pred);
-  // min(X, K) == C:
-  //   C < K  -> X == C
-  //   C == K -> X >= K
-  if (CmpVsBound == APFloat::cmpLessThan)
-    return new FCmpInst(Ordered ? FCmpInst::FCMP_OEQ : FCmpInst::FCMP_UEQ, X,
-                        RHSC, "", &I);
-  if (CmpVsBound == APFloat::cmpEqual)
-    return new FCmpInst(Ordered ? FCmpInst::FCMP_OGE : FCmpInst::FCMP_UGE, X,
-                        RHSC, "", &I);
-  return nullptr;
+  if (Invert)
+    NewPred = FCmpInst::getInversePredicate(NewPred);
+
+  auto *NewCmp = new FCmpInst(NewPred, X, RHSC);
+  FastMathFlags FMF = FcmpCond->getFastMathFlags();
+  if (C->isNaN())
+    FMF.setNoNaNs(false);
+  if (C->isInfinity())
+    FMF.setNoInfs(false);
+  NewCmp->setFastMathFlags(FMF);
+  return NewCmp;
 }
 
 /// Returns true if a select that implements a min/max is redundant and
@@ -9260,8 +9311,10 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
         return replaceOperand(I, 0, X);
       if (Instruction *NV = FoldOpIntoSelect(I, cast<SelectInst>(LHSI)))
         return NV;
-      if (Instruction *NV = foldFCmpEqWithMinMaxLikeSelect(I, LHSI, RHSC))
-        return NV;
+      if (LHSI->hasOneUse())
+        if (Instruction *NV =
+                foldFCmpEqWithMinMaxLikeSelect(I, LHSI, RHSC, *this))
+          return NV;
       break;
     case Instruction::FSub:
       if (LHSI->hasOneUse())
diff --git a/llvm/test/Transforms/InstCombine/fcmp-select.ll b/llvm/test/Transforms/InstCombine/fcmp-select.ll
index af625526dd8f4..7978c4b88303a 100644
--- a/llvm/test/Transforms/InstCombine/fcmp-select.ll
+++ b/llvm/test/Transforms/InstCombine/fcmp-select.ll
@@ -596,83 +596,72 @@ define float @test_select_fcmp_uitofp_min(i8 %x) {
   ret float %sel
 }
 
-define i1 @fold_fcmp_min_clamp(double %x) {
-; CHECK-LABEL: @fold_fcmp_min_clamp(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 5.000000e+00
+define i1 @fold_fcmp_min_clamp_big_const(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_big_const(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ninf oeq double [[X:%.*]], 5.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %max_cmp = fcmp ogt double %x, 2.0
+  %max_cmp = fcmp ninf ogt double %x, 2.0
   %clamp = select i1 %max_cmp, double %x, double 2.0
   %cmp = fcmp oeq double %clamp, 5.0
   ret i1 %cmp
 }
 
-define i1 @fold_fcmp_min_clamp_same_constant(double %x) {
-; CHECK-LABEL: @fold_fcmp_min_clamp_same_constant(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp ule double [[X:%.*]], 2.000000e+00
+define i1 @fold_fcmp_min_clamp_big_const_inf(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_big_const_inf(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp une double [[X:%.*]], +inf
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %max_cmp = fcmp ogt double %x, 2.0
-  %clamp = select i1 %max_cmp, double %x, double 2.0
-  %cmp = fcmp oeq double %clamp, 2.0
+  %max_cmp = fcmp ninf ogt double %x, 3.0
+  %clamp = select i1 %max_cmp, double %x, double 3.0
+  %cmp = fcmp one double %clamp, 0x7FF0000000000000
   ret i1 %cmp
 }
 
-define i1 @fold_fcmp_max_clamp(double %x) {
-; CHECK-LABEL: @fold_fcmp_max_clamp(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[X:%.*]], 5.000000e+00
+define i1 @fold_fcmp_min_clamp_same_constant(double %x) {
+; CHECK-LABEL: @fold_fcmp_min_clamp_same_constant(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ule double [[X:%.*]], 2.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %min_cmp = fcmp ogt double %x, 5.0
-  %clamp = select i1 %min_cmp, double 5.0, double %x
-  %cmp = fcmp oeq double %clamp, 5.0
+  %max_cmp = fcmp ult double %x, 2.0
+  %clamp = select nnan i1 %max_cmp, double 2.0, double %x
+  %cmp = fcmp ueq double %clamp, 2.0
   ret i1 %cmp
 }
 
-define i1 @fold_fcmp_ueq_max_clamp(double %x) {
-; CHECK-LABEL: @fold_fcmp_ueq_max_clamp(
+define i1 @fold_fcmp_max_clamp_same_constant(double %x) {
+; CHECK-LABEL: @fold_fcmp_max_clamp_same_constant(
 ; CHECK-NEXT:    [[CMP:%.*]] = fcmp uge double [[X:%.*]], 5.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %min_cmp = fcmp ogt double %x, 5.0
-  %clamp = select i1 %min_cmp, double 5.0, double %x
-  %cmp = fcmp ueq double %clamp, 5.0
+  %clamp = select nnan i1 %min_cmp, double 5.0, double %x
+  %cmp = fcmp nnan ninf oeq double %clamp, 5.0
   ret i1 %cmp
 }
 
 define i1 @fold_fcmp_max_clamp_small_const(double %x) {
 ; CHECK-LABEL: @fold_fcmp_max_clamp_small_const(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp ueq double [[X:%.*]], 3.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ninf une double [[X:%.*]], 3.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %min_cmp = fcmp ogt double %x, 5.0
-  %clamp = select i1 %min_cmp, double 5.0, double %x
-  %cmp = fcmp ueq double %clamp, 3.0
+  %min_cmp = fcmp ninf olt double %x, 5.0
+  %clamp = select nnan i1 %min_cmp, double %x, double 5.0
+  %cmp = fcmp une double %clamp, 3.0
   ret i1 %cmp
 }
 
-define i1 @fold_fcmp_min_clamp_oge_cond(double %x) {
-; CHECK-LABEL: @fold_fcmp_min_clamp_oge_cond(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 7.000000e+00
-; CHECK-NEXT:    ret i1 [[CMP]]
-;
-  %max_cmp = fcmp oge double %x, 2.0
-  %clamp = select i1 %max_cmp, double %x, double 2.0
-  %cmp = fcmp oeq double %clamp, 7.0
-  ret i1 %cmp
-}
-
-; Full clamp (OGT/OGE-driven nested min/max shapes)
+; Full clamp (nested min/max shapes)
 define i1 @fold_fcmp_full_clamp_oeq_hi(double %x) {
 ; CHECK-LABEL: @fold_fcmp_full_clamp_oeq_hi(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp uge double [[X:%.*]], 5.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %min_cmp = fcmp ogt double %x, 5.0
-  %min = select i1 %min_cmp, double 5.0, double %x
-  %max_cmp = fcmp ogt double %min, 2.0
-  %clamp = select i1 %max_cmp, double %min, double 2.0
-  %cmp = fcmp oeq double %clamp, 5.0
+  %min = select nnan i1 %min_cmp, double 5.0, double %x
+  %max_cmp = fcmp olt double %min, 2.0
+  %clamp = select nnan i1 %max_cmp, double 2.0, double %min
+  %cmp = fcmp nnan ueq double %clamp, 5.0
   ret i1 %cmp
 }
 
@@ -680,104 +669,116 @@ define i1 @fold_fcmp_full_clamp_oeq_lo(double %x) {
 ; CHECK-LABEL: @fold_fcmp_full_clamp_oeq_lo(
 ; CHECK-NEXT:    [[MIN_CMP:%.*]] = fcmp ogt double [[X:%.*]], 5.000000e+00
 ; CHECK-NEXT:    [[MIN:%.*]] = select i1 [[MIN_CMP]], double 5.000000e+00, double [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp ule double [[MIN]], 2.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ole double [[MIN]], 2.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %min_cmp = fcmp ogt double %x, 5.0
   %min = select i1 %min_cmp, double 5.0, double %x
-  %max_cmp = fcmp ogt double %min, 2.0
-  %clamp = select i1 %max_cmp, double %min, double 2.0
+  %max_cmp = fcmp olt double %min, 2.0
+  %clamp = select i1 %max_cmp, double 2.0, double %min
   %cmp = fcmp oeq double %clamp, 2.0
   ret i1 %cmp
 }
 
 define i1 @fold_fcmp_full_clamp_mid_const(double %x) {
 ; CHECK-LABEL: @fold_fcmp_full_clamp_mid_const(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[X:%.*]], 4.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp one double [[X:%.*]], 4.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %min_cmp = fcmp ogt double %x, 5.0
   %min = select i1 %min_cmp, double 5.0, double %x
-  %max_cmp = fcmp ogt double %min, 2.0
-  %clamp = select i1 %max_cmp, double %min, double 2.0
-  %cmp = fcmp oeq double %clamp, 4.0
+  %max_cmp = fcmp olt double %min, 2.0
+  %clamp = select i1 %max_cmp, double 2.0, double %min
+  %cmp = fcmp one double %clamp, 4.0
   ret i1 %cmp
 }
+
+; clamp as min(max(X, 2), 5) instead of max(min(X, 5), 2) pattern
 define i1 @fold_fcmp_clamp_minmax_alt_oeq_hi(double %x) {
 ; CHECK-LABEL: @fold_fcmp_clamp_minmax_alt_oeq_hi(
 ; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
-; CHECK-NEXT:    [[MAX:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge double [[MAX]], 5.000000e+00
+; CHECK-NEXT:    [[MAX:%.*]] = select nnan i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp uge double [[MAX]], 5.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  ; clamp as min(max(X, 2), 5) instead of max(min(X, 5), 2) pattern
   %max_cmp = fcmp ogt double %x, 2.0
-  %max = select i1 %max_cmp, double %x, double 2.0
+  %max = select nnan i1 %max_cmp, double %x, double 2.0
   %min_cmp = fcmp ogt double %max, 5.0
   %clamp = select i1 %min_cmp, double 5.0, double %max
-  %cmp = fcmp oeq double %clamp, 5.0
+  %cmp = fcmp nnan ueq double %clamp, 5.0
   ret i1 %cmp
 }
 
 define i1 @fold_two_clamp_hi(float %arg0, float %arg1) {
 ; CHECK-LABEL: @fold_two_clamp_hi(
-; CHECK-DAG:    [[CMP0:%.*]] = fcmp oge float [[ARG0:%.*]], 1.000000e+00
-; CHECK-DAG:    [[CMP1:%.*]] = fcmp oge float [[ARG1:%.*]], 1.000000e+00
-; CHECK:        [[RES:%.*]] = and i1
-; CHECK-NEXT:   ret i1 [[RES]]
-;
-  %v0 = fcmp ogt float %arg1, 1.000000e+00
-  %v1 = select i1 %v0, float 1.000000e+00, float %arg1
-  %v2 = fcmp ogt float %v1, 0.000000e+00
-  %v3 = select i1 %v2, float %v1, float 0.000000e+00
-  %v4 = fcmp ogt float %arg0, 1.000000e+00
-  %v5 = select i1 %v4, float 1.000000e+00, float %arg0
-  %v6 = fcmp ogt float %v5, 0.000000e+00
-  %v7 = select i1 %v6, float %v5, float 0.000000e+00
-  %v8 = fcmp oeq float %v3, 1.000000e+00
-  %v9 = fcmp oeq float %v7, 1.000000e+00
+; CHECK-NEXT:    [[V8:%.*]] = fcmp uge float [[ARG1:%.*]], 1.000000e+00
+; CHECK-NEXT:    [[V9:%.*]] = fcmp ult float [[ARG0:%.*]], 2.000000e+00
+; CHECK-NEXT:    [[V10:%.*]] = and i1 [[V8]], [[V9]]
+; CHECK-NEXT:    ret i1 [[V10]]
+;
+  %v0 = fcmp olt float %arg1, 1.000000e+00
+  %v1 = select i1 %v0, float %arg1, float 1.000000e+00
+  %v2 = fcmp olt float %v1, 0.000000e+00
+  %v3 = select i1 %v2, float 0.000000e+00, float %v1
+  %v4 = fcmp ogt float %arg0, 2.000000e+00
+  %v5 = select i1 %v4, float 2.000000e+00, float %arg0
+  %v6 = fcmp olt float %v5, 3.000000e+00
+  %v7 = select i1 %v6,  float %v5, float 3.000000e+00
+  %v8 = fcmp nnan ueq float %v3, 1.000000e+00
+  %v9 = fcmp nnan une float %v7, 2.000000e+00
   %v10 = and i1 %v8, %v9
   ret i1 %v10
 }
 
-; Negative test cases (including non-OGT/OGE predicate shapes)
+define <2 x i1> @fold_two_clamp_hi_vec(<2 x float> %arg0, <2 x float> %arg1) {
+; CHECK-LABEL: @fold_two_clamp_hi_vec(
+; CHECK-NEXT:    [[V8:%.*]] = fcmp ult <2 x float> [[ARG1:%.*]], splat (float 1.000000e+00)
+; CHECK-NEXT:    [[V9:%.*]] = fcmp oeq <2 x float> [[ARG0:%.*]], splat (float 3.000000e+00)
+; CHECK-NEXT:    [[V10:%.*]] = and <2 x i1> [[V8]], [[V9]]
+; CHECK-NEXT:    ret <2 x i1> [[V10]]
+;
+  %v0 = fcmp ogt <2 x float> %arg1, splat (float 1.000000e+00)
+  %v1 = select <2 x i1> %v0, <2 x float> splat (float 1.000000e+00), <2 x float> %arg1
+  %v2 = fcmp ogt <2 x float> %v1, splat (float 0.000000e+00)
+  %v3 = select <2 x i1> %v2, <2 x float> %v1, <2 x float> splat (float 0.000000e+00)
+
+  %v4 = fcmp olt <2 x float> %arg0, splat (float 5.000000e+00)
+  %v5 = select <2 x i1> %v4, <2 x float> %arg0, <2 x float> splat (float 5.000000e+00)
+  %v6 = fcmp ogt <2 x float> %v5, splat (float 1.000000e+00)
+  %v7 = select <2 x i1> %v6, <2 x float> %v5, <2 x float> splat (float 1.000000e+00)
+
+  %v8 = fcmp one <2 x float> %v3, splat (float 1.000000e+00)
+  %v9 = fcmp ueq <2 x float> %v7, splat (float 3.000000e+00)
+
+  %v10 = and <2 x i1> %v8, %v9
+  ret <2 x i1> %v10
+}
+
+; Negative test cases
 
 define i1 @fold_fcmp_non_minmax_shape(double %x, double %y) {
 ; CHECK-LABEL: @fold_fcmp_non_minmax_shape(
 ; CHECK-NEXT:    [[CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
 ; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
-; CHECK-NEXT:    [[RES:%.*]] = fcmp oeq double [[CLAMP]], 5.000000e+00
+; CHECK-NEXT:    [[RES:%.*]] = fcmp one double [[CLAMP]], 5.000000e+00
 ; CHECK-NEXT:    ret i1 [[RES]]
 ;
   %cmp = fcmp ogt double %x, 2.0
   %clamp = select i1 %cmp, double %x, double %y
-  %res = fcmp oeq double %clamp, 5.0
+  %res = fcmp one double %clamp, 5.0
   ret i1 %res
 }
 
-define i1 @fold_fcmp_min_clamp_non_og(double %x) {
-; CHECK-LABEL: @fold_fcmp_min_clamp_non_og(
-; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp olt double [[X:%.*]], 2.000000e+00
-; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 5.000000e+00
-; CHECK-NEXT:    ret i1 [[CMP]]
-;
-  %max_cmp = fcmp olt double %x, 2.0
-  %clamp = select i1 %max_cmp, double %x, double 2.0
-  %cmp = fcmp oeq double %clamp, 5.0
-  ret i1 %cmp
-}
-
 define i1 @fold_fcmp_max_clamp_bigger_const_no_fold(double %x) {
 ; CHECK-LABEL: @fold_fcmp_max_clamp_bigger_const_no_fold(
-; CHECK-NEXT:    [[MIN_CMP:%.*]] = fcmp ogt double [[X:%.*]], 5.000000e+00
-; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MIN_CMP]], double 5.000000e+00, double [[X]]
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 6.000000e+00
+; CHECK-NEXT:    [[MIN_CMP_INV:%.*]] = fcmp ole double [[X:%.*]], 5.000000e+00
+; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MIN_CMP_INV]], double [[X]], double 5.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp une double [[CLAMP]], 6.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
-  %min_cmp = fcmp ogt double %x, 5.0
+  %min_cmp = fcmp ugt double %x, 5.0
   %clamp = select i1 %min_cmp, double 5.0, double %x
-  %cmp = fcmp oeq double %clamp, 6.0
+  %cmp = fcmp une double %clamp, 6.0
   ret i1 %cmp
 }
 
@@ -785,11 +786,24 @@ define i1 @fold_fcmp_min_clamp_smaller_const_no_fold(double %x) {
 ; CHECK-LABEL: @fold_fcmp_min_clamp_smaller_const_no_fold(
 ; CHECK-NEXT:    [[MAX_CMP:%.*]] = fcmp ogt double [[X:%.*]], 2.000000e+00
 ; CHECK-NEXT:    [[CLAMP:%.*]] = select i1 [[MAX_CMP]], double [[X]], double 2.000000e+00
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[CLAMP]], 1.000000e+00
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp nnan oeq double [[CLAMP]], 1.000000e+00
 ; CHECK-NEXT:    ret i1 [[CMP]]
 ;
   %max_cmp = fcmp ogt double %x, 2.0
   %clamp = select i1 %max_cmp, double %x, double 2.0
-  %cmp = fcmp oeq double %clamp, 1.0
+  %cmp = fcmp nnan oeq double %clamp, 1.0
   ret i1 %cmp
 }
+
+define <2 x i1> @fold_fcmp_max_clamp_neg_test_vect(<2 x float> %x) {
+; CHECK-LABEL: @fold_fcmp_max_clamp_neg_test_vect(
+; CHECK-NEXT:    [[MIN_CMP:%.*]] = fcmp olt <2 x float> [[X:%.*]], splat (float 5.000000e+00)
+; CHECK-NEXT:    [[CLAMP:%.*]] = select <2 x i1> [[MIN_CMP]], <2 x float> [[X]], <2 x float> splat (float 5.000000e+00)
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp nnan ueq <2 x float> [[CLAMP]], <float 3.000000e+00, float 2.000000e+00>
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %min_cmp = fcmp olt <2 x float> %x, <float 5.0, float 5.0>
+  %clamp = select <2 x i1> %min_cmp, <2 x float> %x, <2 x float> <float 5.0, float 5.0>
+  %cmp = fcmp nnan ueq <2 x float> %clamp, <float 3.0, float 2.0>
+  ret <2 x i1> %cmp
+}



More information about the llvm-commits mailing list