[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