[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
Thu May 7 10:35:29 PDT 2026


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

>From 122eb3b04eac46fcbe1c8954079976ac2f4585f5 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] [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 0b1f683e10408..2f8dabd570cc1 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8980,6 +8980,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)
@@ -9181,6 +9255,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
+}



More information about the llvm-commits mailing list