[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