[llvm] [InstCombine] Fold select of ordered fcmps of fabs over `isKnownNeverNaN`-selects to a single select (PR #192182)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Apr 14 22:11:40 PDT 2026
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Kiva (imkiva)
<details>
<summary>Changes</summary>
Fold `select (fcmp <ordered> (fabs (select isKnownNeverNaN X, X, Y)), K), ...` into a single compare/select directly on `X`. The outer fcmp is limited to ordered predicates, since only they preserve the original non-NaN behavior.
fixes #<!-- -->143649
alive2: https://alive2.llvm.org/ce/z/G8UmjY
Generalized proof (needs local alive2 build):
```alive2
declare double @<!-- -->llvm.fabs.f64(double)
define double @<!-- -->src(double %x, double %y, double %k) {
entry:
%ord = fcmp ord double %x, 0.000000e+00
%s = select i1 %ord, double %x, double %y
%a = call double @<!-- -->llvm.fabs.f64(double %s)
%c = fcmp one double %a, %k
%r = select i1 %c, double %s, double %y
ret double %r
}
define double @<!-- -->tgt(double %x, double %y, double %k) {
entry:
%a2 = call double @<!-- -->llvm.fabs.f64(double %x)
%c2 = fcmp one double %a2, %k
%r2 = select i1 %c2, double %x, double %y
ret double %r2
}
```
---
Full diff: https://github.com/llvm/llvm-project/pull/192182.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp (+104)
- (modified) llvm/test/Transforms/InstCombine/fcmp-select.ll (+189)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index bffc887d93535..21556d8aabee1 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -3347,6 +3347,107 @@ static Instruction *foldSelectWithFCmpToFabs(SelectInst &SI,
return ChangedFMF ? &SI : nullptr;
}
+static bool matchIsNaNTest(FCmpInst *Cmp, Value *X, bool MatchIsNaN,
+ InstCombinerImpl &IC) {
+ FCmpInst::Predicate ExpectedPred =
+ MatchIsNaN ? FCmpInst::FCMP_UNO : FCmpInst::FCMP_ORD;
+ if (Cmp->getPredicate() != ExpectedPred)
+ return false;
+
+ Value *Cmp0 = Cmp->getOperand(0), *Cmp1 = Cmp->getOperand(1);
+ if (Cmp0 == X && Cmp1 == X)
+ return true;
+
+ auto IsKnownNeverNaN = [&](Value *V) {
+ return match(V, m_NonNaN()) ||
+ isKnownNeverNaN(V, IC.getSimplifyQuery().getWithInstruction(Cmp));
+ };
+
+ return (Cmp0 == X && IsKnownNeverNaN(Cmp1)) ||
+ (Cmp1 == X && IsKnownNeverNaN(Cmp0));
+}
+
+// Match a select that returns X when X is not NaN, and Y otherwise.
+static Value *matchNaNScrubbedValue(SelectInst *SI, Value *Y,
+ InstCombinerImpl &IC) {
+ auto *Cmp = dyn_cast<FCmpInst>(SI->getCondition());
+ if (!Cmp || !Cmp->hasOneUse())
+ return nullptr;
+
+ Value *X;
+ bool MatchIsNaN;
+ if (SI->getFalseValue() == Y) {
+ X = SI->getTrueValue();
+ MatchIsNaN = false;
+ } else if (SI->getTrueValue() == Y) {
+ X = SI->getFalseValue();
+ MatchIsNaN = true;
+ } else {
+ return nullptr;
+ }
+
+ return matchIsNaNTest(Cmp, X, MatchIsNaN, IC) ? X : nullptr;
+}
+
+// Fold a select of an ordered fcmp using fabs of a NaN-scrubbed value:
+// %s = select i1 (isnotnan T %x), T %x, T %y
+// %a = call T @llvm.fabs.T(T %s)
+// %c = fcmp <ordered-pred> T %a, %k
+// %r = select i1 %c, T %s, T %y
+// =>
+// %a2 = call T @llvm.fabs.T(T %x)
+// %c2 = fcmp <ordered-pred> T %a2, %k
+// %r2 = select i1 %c2, T %x, T %y
+static Instruction *
+foldSelectOfOrderedFAbsCmpOfNaNScrubbedValue(SelectInst &SI,
+ InstCombinerImpl &IC) {
+ auto *OuterCmp = dyn_cast<FCmpInst>(SI.getCondition());
+ if (!OuterCmp || !OuterCmp->hasOneUse() ||
+ !FCmpInst::isOrdered(OuterCmp->getPredicate()))
+ return nullptr;
+
+ // The NaN path now evaluates fabs(X) instead of fabs(Y), so preserving nnan
+ // on the fcmp could introduce poison when X is NaN.
+ if (OuterCmp->hasNoNaNs())
+ return nullptr;
+
+ Value *Y = SI.getFalseValue();
+ Value *X = nullptr;
+
+ auto *InnerSel = dyn_cast<SelectInst>(SI.getTrueValue());
+ if (!InnerSel)
+ return nullptr;
+
+ X = matchNaNScrubbedValue(InnerSel, Y, IC);
+ if (!X)
+ return nullptr;
+
+ bool Swapped = false;
+ Value *OtherOp = nullptr;
+ auto *FAbs = dyn_cast<IntrinsicInst>(OuterCmp->getOperand(0));
+
+ if (FAbs && FAbs->hasOneUse() && FAbs->getIntrinsicID() == Intrinsic::fabs &&
+ FAbs->getArgOperand(0) == InnerSel) {
+ OtherOp = OuterCmp->getOperand(1);
+ } else if ((FAbs = dyn_cast<IntrinsicInst>(OuterCmp->getOperand(1))) &&
+ FAbs->hasOneUse() && FAbs->getIntrinsicID() == Intrinsic::fabs &&
+ FAbs->getArgOperand(0) == InnerSel) {
+ Swapped = true;
+ OtherOp = OuterCmp->getOperand(0);
+ } else {
+ return nullptr;
+ }
+
+ Value *NewAbs = IC.Builder.CreateUnaryIntrinsic(Intrinsic::fabs, X);
+ Value *NewCmp = Swapped
+ ? IC.Builder.CreateFCmpFMF(OuterCmp->getPredicate(),
+ OtherOp, NewAbs, OuterCmp)
+ : IC.Builder.CreateFCmpFMF(OuterCmp->getPredicate(),
+ NewAbs, OtherOp, OuterCmp);
+ Value *NewSel = IC.Builder.CreateSelectFMF(NewCmp, X, Y, &SI);
+ return IC.replaceInstUsesWith(SI, NewSel);
+}
+
// Match the following IR pattern:
// %x.lowbits = and i8 %x, %lowbitmask
// %x.lowbits.are.zero = icmp eq i8 %x.lowbits, 0
@@ -4599,6 +4700,9 @@ Instruction *InstCombinerImpl::visitSelectInst(SelectInst &SI) {
if (Instruction *Fabs = foldSelectWithFCmpToFabs(SI, *this))
return Fabs;
+ if (Instruction *I = foldSelectOfOrderedFAbsCmpOfNaNScrubbedValue(SI, *this))
+ return I;
+
// See if we are selecting two values based on a comparison of the two values.
if (CmpInst *CI = dyn_cast<CmpInst>(CondVal))
if (Instruction *NewSel = foldSelectValueEquivalence(SI, *CI))
diff --git a/llvm/test/Transforms/InstCombine/fcmp-select.ll b/llvm/test/Transforms/InstCombine/fcmp-select.ll
index 268cd4675c6cc..4cc009c066865 100644
--- a/llvm/test/Transforms/InstCombine/fcmp-select.ll
+++ b/llvm/test/Transforms/InstCombine/fcmp-select.ll
@@ -3,6 +3,7 @@
declare void @use(i1)
declare void @usef64(double)
+declare double @llvm.fabs.f64(double)
; X == 42.0 ? X : 42.0 --> 42.0
@@ -280,6 +281,194 @@ define i1 @test_fcmp_ord_select_fcmp_oeq_var_const(double %x) {
ret i1 %cmp2
}
+define double @test_fcmp_ord_select_fabs_fcmp_one_select_var_const(double %x) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_fcmp_one_select_var_const(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double 0.000000e+00
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp ord double %x, 0.000000e+00
+ %sel1 = select i1 %cmp1, double %x, double 0.000000e+00
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double 0.000000e+00
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_commuted_select_fabs_fcmp_one_select_var_const(double %x) {
+; CHECK-LABEL: @test_fcmp_ord_commuted_select_fabs_fcmp_one_select_var_const(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double 0.000000e+00
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp ord double 0.000000e+00, %x
+ %sel1 = select i1 %cmp1, double %x, double 0.000000e+00
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double 0.000000e+00
+ ret double %sel2
+}
+
+define double @test_fcmp_uno_select_fabs_fcmp_one_select_var_var(double %x, double %y) {
+; CHECK-LABEL: @test_fcmp_uno_select_fabs_fcmp_one_select_var_var(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp uno double %x, 0.000000e+00
+ %sel1 = select i1 %cmp1, double %y, double %x
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_fcmp_one_select_nonnan_const(double %x, double %y) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_fcmp_one_select_nonnan_const(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp ord double %x, 1.000000e+00
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_self_select_fabs_fcmp_one_select_var_var(double %x, double %y) {
+; CHECK-LABEL: @test_fcmp_ord_self_select_fabs_fcmp_one_select_var_var(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp ord double %x, %x
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_fcmp_one_select_uitofp_nonnan(double %x, double %y, i32 %i) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_fcmp_one_select_uitofp_nonnan(
+; CHECK: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %nonnan = uitofp i32 %i to double
+ %cmp1 = fcmp ord double %x, %nonnan
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_commuted_select_fabs_fcmp_one_select_sitofp_nonnan(double %x, double %y, i32 %i) {
+; CHECK-LABEL: @test_fcmp_ord_commuted_select_fabs_fcmp_one_select_sitofp_nonnan(
+; CHECK: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %nonnan = sitofp i32 %i to double
+ %cmp1 = fcmp ord double %nonnan, %x
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_uno_select_fabs_fcmp_one_select_uitofp_nonnan(double %x, double %y, i32 %i) {
+; CHECK-LABEL: @test_fcmp_uno_select_fabs_fcmp_one_select_uitofp_nonnan(
+; CHECK: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %nonnan = uitofp i32 %i to double
+ %cmp1 = fcmp uno double %x, %nonnan
+ %sel1 = select i1 %cmp1, double %y, double %x
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_rhs_fcmp_ogt_select_uitofp_nonnan(double %x, double %y, double %k, i32 %i) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_rhs_fcmp_ogt_select_uitofp_nonnan(
+; CHECK: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp ogt double [[K:%.*]], [[ABS]]
+; CHECK-NEXT: [[SEL:%.*]] = select i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %nonnan = uitofp i32 %i to double
+ %cmp1 = fcmp ord double %x, %nonnan
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp ogt double %k, %abs
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_fcmp_une_select_var_const(double %x) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_fcmp_une_select_var_const(
+; CHECK-NEXT: [[CMP1:%.*]] = fcmp ord double [[X:%.*]], 0.000000e+00
+; CHECK-NEXT: [[SEL1:%.*]] = select i1 [[CMP1]], double [[X]], double 0.000000e+00
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[SEL1]])
+; CHECK-NEXT: [[CMP2:%.*]] = fcmp une double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL2:%.*]] = select i1 [[CMP2]], double [[SEL1]], double 0.000000e+00
+; CHECK-NEXT: ret double [[SEL2]]
+;
+ %cmp1 = fcmp ord double %x, 0.000000e+00
+ %sel1 = select i1 %cmp1, double %x, double 0.000000e+00
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp une double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double 0.000000e+00
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_fcmp_nnan_one_select_var_var(double %x, double %y) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_fcmp_nnan_one_select_var_var(
+; CHECK-NEXT: [[CMP1:%.*]] = fcmp ord double [[X:%.*]], 0.000000e+00
+; CHECK-NEXT: [[SEL1:%.*]] = select i1 [[CMP1]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[SEL1]])
+; CHECK-NEXT: [[CMP2:%.*]] = fcmp nnan one double [[ABS]], 0x7FF0000000000000
+; CHECK-NEXT: [[SEL2:%.*]] = select i1 [[CMP2]], double [[SEL1]], double [[Y]]
+; CHECK-NEXT: ret double [[SEL2]]
+;
+ %cmp1 = fcmp ord double %x, 0.000000e+00
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp nnan one double %abs, 0x7FF0000000000000
+ %sel2 = select i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
+define double @test_fcmp_ord_select_fabs_rhs_fcmp_ogt_select_var_var(double %x, double %y, double %k) {
+; CHECK-LABEL: @test_fcmp_ord_select_fabs_rhs_fcmp_ogt_select_var_var(
+; CHECK-NEXT: [[ABS:%.*]] = call double @llvm.fabs.f64(double [[X:%.*]])
+; CHECK-NEXT: [[CMP:%.*]] = fcmp ninf ogt double [[K:%.*]], [[ABS]]
+; CHECK-NEXT: [[SEL:%.*]] = select nnan i1 [[CMP]], double [[X]], double [[Y:%.*]]
+; CHECK-NEXT: ret double [[SEL]]
+;
+ %cmp1 = fcmp ord double %x, 0.000000e+00
+ %sel1 = select i1 %cmp1, double %x, double %y
+ %abs = call ninf double @llvm.fabs.f64(double %sel1)
+ %cmp2 = fcmp ninf ogt double %k, %abs
+ %sel2 = select nnan i1 %cmp2, double %sel1, double %y
+ ret double %sel2
+}
+
; Make sure that we recognize the SPF correctly.
define float @test_select_nnan_nsz_fcmp_olt(float %x) {
``````````
</details>
https://github.com/llvm/llvm-project/pull/192182
More information about the llvm-commits
mailing list