[llvm] [InstCombine] Generalize ignoreSignBitOfZero/NaN to handle more cases (PR #141015)

Yingwei Zheng via llvm-commits llvm-commits at lists.llvm.org
Thu May 22 09:08:38 PDT 2025


https://github.com/dtcxzyw updated https://github.com/llvm/llvm-project/pull/141015

>From db7e339019d868f2d23923672c75e02f6e7a5476 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 15:16:49 +0800
Subject: [PATCH 1/8] [InstCombine] Add pre-commit tests. NFC.

---
 llvm/test/Transforms/InstCombine/fabs.ll | 119 +++++++++++++++++++++++
 1 file changed, 119 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index ab4376bf78a67..089ec0e44d001 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -1361,6 +1361,110 @@ define float @test_fabs_used_by_fcopysign_mag(float %x, float %y) {
   ret float %copysign
 }
 
+define float @test_fabs_nsz_used_by_canonicalize(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_canonicalize(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SEL]])
+; CHECK-NEXT:    ret float [[CANON]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %canon = call float @llvm.canonicalize.f32(float %sel)
+  ret float %canon
+}
+
+define void @test_fabs_used_by_nofpclass_nan(float %x) {
+; CHECK-LABEL: @test_fabs_used_by_nofpclass_nan(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    call void @use(float nofpclass(nan) [[SEL]])
+; CHECK-NEXT:    ret void
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  call void @use(float nofpclass(nan) %sel)
+  ret void
+}
+
+define i32 @test_fabs_used_fptosi(float %x) {
+; CHECK-LABEL: @test_fabs_used_fptosi(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[FPTOSI:%.*]] = fptosi float [[SEL]] to i32
+; CHECK-NEXT:    ret i32 [[FPTOSI]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select i1 %cmp, float %x, float %neg
+  %fptosi = fptosi float %sel to i32
+  ret i32 %fptosi
+}
+
+define i32 @test_fabs_used_fptoui(float %x) {
+; CHECK-LABEL: @test_fabs_used_fptoui(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[FPTOSI:%.*]] = fptoui float [[SEL]] to i32
+; CHECK-NEXT:    ret i32 [[FPTOSI]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select i1 %cmp, float %x, float %neg
+  %fptosi = fptoui float %sel to i32
+  ret i32 %fptosi
+}
+
+define float @test_fabs_nsz_used_by_maxnum(float %x, float %y) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_maxnum(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[MAX:%.*]] = call float @llvm.maxnum.f32(float [[Y:%.*]], float [[SEL]])
+; CHECK-NEXT:    ret float [[MAX]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %max = call float @llvm.maxnum.f32(float %y, float %sel)
+  ret float %max
+}
+
+define i1 @test_fabs_used_is_fpclass_pnorm_or_nan(float %x) {
+; CHECK-LABEL: @test_fabs_used_is_fpclass_pnorm_or_nan(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[SEL]], i32 259)
+; CHECK-NEXT:    ret i1 [[IS_FPCLASS]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select i1 %cmp, float %x, float %neg
+  %is_fpclass = call i1 @llvm.is.fpclass.f32(float %sel, i32 259)
+  ret i1 %is_fpclass
+}
+
+define i1 @test_fabs_used_is_fpclass_zero_or_pinf(float %x) {
+; CHECK-LABEL: @test_fabs_used_is_fpclass_zero_or_pinf(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[SEL]], i32 608)
+; CHECK-NEXT:    ret i1 [[IS_FPCLASS]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select i1 %cmp, float %x, float %neg
+  %is_fpclass = call i1 @llvm.is.fpclass.f32(float %sel, i32 608)
+  ret i1 %is_fpclass
+}
 
 ; Negative tests
 
@@ -1455,3 +1559,18 @@ define float @test_fabs_used_by_select(float %x, i1 %cond) {
   %sel2 = select i1 %cond, float %sel, float 0.000000e+00
   ret float %sel2
 }
+
+define i1 @test_fabs_used_is_fpclass_pzero(float %x) {
+; CHECK-LABEL: @test_fabs_used_is_fpclass_pzero(
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
+; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
+; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[SEL]], i32 64)
+; CHECK-NEXT:    ret i1 [[IS_FPCLASS]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select i1 %cmp, float %x, float %neg
+  %is_fpclass = call i1 @llvm.is.fpclass.f32(float %sel, i32 64)
+  ret i1 %is_fpclass
+}

>From 4ac3c5396e32669bf608d04309975ec83f4f5395 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 15:29:34 +0800
Subject: [PATCH 2/8] [InstCombine] Generalize ignoreSignBitOfZero/NaN to
 handle more cases

---
 .../InstCombine/InstCombineSelect.cpp         | 103 ++++++++++++++++--
 llvm/test/Transforms/InstCombine/fabs.ll      |  41 ++-----
 2 files changed, 101 insertions(+), 43 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 2ef233bc25d72..a6655796ab173 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -2783,15 +2783,42 @@ static bool ignoreSignBitOfZero(Instruction &I) {
   if (!I.hasOneUse())
     return false;
   Instruction *User = I.user_back();
-
-  // fcmp treats both positive and negative zero as equal.
-  if (User->getOpcode() == Instruction::FCmp)
+  if (User->getOpcode() == Instruction::FPToSI ||
+      User->getOpcode() == Instruction::FPToUI)
     return true;
 
-  if (auto *FPOp = dyn_cast<FPMathOperator>(User))
-    return FPOp->hasNoSignedZeros();
+  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
+    if (FPOp->hasNoSignedZeros())
+      return true;
+  }
 
-  return false;
+  switch (User->getOpcode()) {
+  case Instruction::FCmp:
+    // fcmp treats both positive and negative zero as equal.
+    return true;
+  case Instruction::Call:
+    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
+      switch (II->getIntrinsicID()) {
+      case Intrinsic::fabs:
+        return true;
+      case Intrinsic::copysign:
+        return II->getArgOperand(0) == &I;
+      case Intrinsic::is_fpclass:
+      case Intrinsic::vp_is_fpclass: {
+        auto Test =
+            static_cast<FPClassTest>(
+                cast<ConstantInt>(II->getArgOperand(1))->getZExtValue()) &
+            FPClassTest::fcZero;
+        return Test == FPClassTest::fcZero || Test == FPClassTest::fcNone;
+      }
+      default:
+        return false;
+      }
+    }
+    return false;
+  default:
+    return false;
+  }
 }
 
 /// Return true if the sign bit of result can be ignored when the result is NaN.
@@ -2804,14 +2831,68 @@ static bool ignoreSignBitOfNaN(Instruction &I) {
     return false;
   Instruction *User = I.user_back();
 
-  // fcmp ignores the sign bit of NaN.
-  if (User->getOpcode() == Instruction::FCmp)
+  if (User->getOpcode() == Instruction::FPToSI ||
+      User->getOpcode() == Instruction::FPToUI)
     return true;
 
-  if (auto *FPOp = dyn_cast<FPMathOperator>(User))
-    return FPOp->hasNoNaNs();
+  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
+    if (FPOp->hasNoNaNs())
+      return true;
+  }
+
+  switch (User->getOpcode()) {
+  // Proper FP math operations ignore the sign bit of NaN.
+  case Instruction::FAdd:
+  case Instruction::FSub:
+  case Instruction::FMul:
+  case Instruction::FDiv:
+  case Instruction::FRem:
+  case Instruction::FPTrunc:
+  case Instruction::FPExt:
+  case Instruction::FCmp:
+    return true;
+  // Bitwise FP operations should preserve the sign bit of NaN.
+  case Instruction::FNeg:
+  case Instruction::Select:
+  case Instruction::PHI:
+    return false;
+  case Instruction::Call:
+  case Instruction::Invoke: {
+    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
+      switch (II->getIntrinsicID()) {
+      case Intrinsic::fabs:
+        return true;
+      case Intrinsic::copysign:
+        return II->getArgOperand(0) == &I;
+      // Other proper FP math intrinsics ignore the sign bit of NaN.
+      case Intrinsic::maxnum:
+      case Intrinsic::minnum:
+      case Intrinsic::maximum:
+      case Intrinsic::minimum:
+      case Intrinsic::maximumnum:
+      case Intrinsic::minimumnum:
+      case Intrinsic::canonicalize:
+      case Intrinsic::fma:
+      case Intrinsic::fmuladd:
+      case Intrinsic::sqrt:
+      case Intrinsic::pow:
+      case Intrinsic::powi:
+      case Intrinsic::fptoui_sat:
+      case Intrinsic::fptosi_sat:
+      case Intrinsic::is_fpclass:
+        return true;
+      default:
+        return false;
+      }
+    }
 
-  return false;
+    FPClassTest NoFPClass = cast<CallBase>(User)->getParamNoFPClass(
+        I.uses().begin()->getOperandNo());
+    return NoFPClass & FPClassTest::fcNan;
+  }
+  default:
+    return false;
+  }
 }
 
 // Canonicalize select with fcmp to fabs(). -0.0 makes this tricky. We need
diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index 089ec0e44d001..b889f6c2c028c 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -1329,12 +1329,9 @@ define float @test_fabs_fsub_used_by_fpop_nnan(float %x, float %y) {
   ret float %add
 }
 
-; TODO: fadd ignores the sign bit of NaN.
 define float @test_fabs_used_by_fpop_nsz(float %x, float %y) {
 ; CHECK-LABEL: @test_fabs_used_by_fpop_nsz(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    [[ADD:%.*]] = fadd nsz float [[SEL]], [[Y:%.*]]
 ; CHECK-NEXT:    ret float [[ADD]]
 ;
@@ -1345,13 +1342,9 @@ define float @test_fabs_used_by_fpop_nsz(float %x, float %y) {
   ret float %add
 }
 
-; TODO: copysign ignores the sign bit of NaN magnitude.
 define float @test_fabs_used_by_fcopysign_mag(float %x, float %y) {
 ; CHECK-LABEL: @test_fabs_used_by_fcopysign_mag(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X1:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X1]]
-; CHECK-NEXT:    [[X:%.*]] = select i1 [[CMP]], float [[X1]], float [[NEG]]
-; CHECK-NEXT:    [[COPYSIGN:%.*]] = call float @llvm.copysign.f32(float [[X]], float [[Y:%.*]])
+; CHECK-NEXT:    [[COPYSIGN:%.*]] = call float @llvm.copysign.f32(float [[X:%.*]], float [[Y:%.*]])
 ; CHECK-NEXT:    ret float [[COPYSIGN]]
 ;
   %cmp = fcmp oge float %x, 0.000000e+00
@@ -1363,9 +1356,7 @@ define float @test_fabs_used_by_fcopysign_mag(float %x, float %y) {
 
 define float @test_fabs_nsz_used_by_canonicalize(float %x) {
 ; CHECK-LABEL: @test_fabs_nsz_used_by_canonicalize(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    [[CANON:%.*]] = call float @llvm.canonicalize.f32(float [[SEL]])
 ; CHECK-NEXT:    ret float [[CANON]]
 ;
@@ -1378,9 +1369,7 @@ define float @test_fabs_nsz_used_by_canonicalize(float %x) {
 
 define void @test_fabs_used_by_nofpclass_nan(float %x) {
 ; CHECK-LABEL: @test_fabs_used_by_nofpclass_nan(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    call void @use(float nofpclass(nan) [[SEL]])
 ; CHECK-NEXT:    ret void
 ;
@@ -1393,9 +1382,7 @@ define void @test_fabs_used_by_nofpclass_nan(float %x) {
 
 define i32 @test_fabs_used_fptosi(float %x) {
 ; CHECK-LABEL: @test_fabs_used_fptosi(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    [[FPTOSI:%.*]] = fptosi float [[SEL]] to i32
 ; CHECK-NEXT:    ret i32 [[FPTOSI]]
 ;
@@ -1408,9 +1395,7 @@ define i32 @test_fabs_used_fptosi(float %x) {
 
 define i32 @test_fabs_used_fptoui(float %x) {
 ; CHECK-LABEL: @test_fabs_used_fptoui(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    [[FPTOSI:%.*]] = fptoui float [[SEL]] to i32
 ; CHECK-NEXT:    ret i32 [[FPTOSI]]
 ;
@@ -1423,9 +1408,7 @@ define i32 @test_fabs_used_fptoui(float %x) {
 
 define float @test_fabs_nsz_used_by_maxnum(float %x, float %y) {
 ; CHECK-LABEL: @test_fabs_nsz_used_by_maxnum(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select nsz i1 [[CMP]], float [[X]], float [[NEG]]
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
 ; CHECK-NEXT:    [[MAX:%.*]] = call float @llvm.maxnum.f32(float [[Y:%.*]], float [[SEL]])
 ; CHECK-NEXT:    ret float [[MAX]]
 ;
@@ -1438,10 +1421,7 @@ define float @test_fabs_nsz_used_by_maxnum(float %x, float %y) {
 
 define i1 @test_fabs_used_is_fpclass_pnorm_or_nan(float %x) {
 ; CHECK-LABEL: @test_fabs_used_is_fpclass_pnorm_or_nan(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
-; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[SEL]], i32 259)
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[X:%.*]], i32 267)
 ; CHECK-NEXT:    ret i1 [[IS_FPCLASS]]
 ;
   %cmp = fcmp oge float %x, 0.000000e+00
@@ -1453,10 +1433,7 @@ define i1 @test_fabs_used_is_fpclass_pnorm_or_nan(float %x) {
 
 define i1 @test_fabs_used_is_fpclass_zero_or_pinf(float %x) {
 ; CHECK-LABEL: @test_fabs_used_is_fpclass_zero_or_pinf(
-; CHECK-NEXT:    [[CMP:%.*]] = fcmp oge float [[X:%.*]], 0.000000e+00
-; CHECK-NEXT:    [[NEG:%.*]] = fneg float [[X]]
-; CHECK-NEXT:    [[SEL:%.*]] = select i1 [[CMP]], float [[X]], float [[NEG]]
-; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[SEL]], i32 608)
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call i1 @llvm.is.fpclass.f32(float [[X:%.*]], i32 612)
 ; CHECK-NEXT:    ret i1 [[IS_FPCLASS]]
 ;
   %cmp = fcmp oge float %x, 0.000000e+00

>From b314ab2ff0563574da09964c600167cb61b8aca1 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 15:32:59 +0800
Subject: [PATCH 3/8] [InstCombine] Simplify code. NFC.

---
 .../Transforms/InstCombine/InstCombineSelect.cpp  | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index a6655796ab173..13cbf7b215557 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -2783,16 +2783,15 @@ static bool ignoreSignBitOfZero(Instruction &I) {
   if (!I.hasOneUse())
     return false;
   Instruction *User = I.user_back();
-  if (User->getOpcode() == Instruction::FPToSI ||
-      User->getOpcode() == Instruction::FPToUI)
-    return true;
-
   if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
     if (FPOp->hasNoSignedZeros())
       return true;
   }
 
   switch (User->getOpcode()) {
+  case Instruction::FPToSI:
+  case Instruction::FPToUI:
+    return true;
   case Instruction::FCmp:
     // fcmp treats both positive and negative zero as equal.
     return true;
@@ -2830,17 +2829,15 @@ static bool ignoreSignBitOfNaN(Instruction &I) {
   if (!I.hasOneUse())
     return false;
   Instruction *User = I.user_back();
-
-  if (User->getOpcode() == Instruction::FPToSI ||
-      User->getOpcode() == Instruction::FPToUI)
-    return true;
-
   if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
     if (FPOp->hasNoNaNs())
       return true;
   }
 
   switch (User->getOpcode()) {
+  case Instruction::FPToSI:
+  case Instruction::FPToUI:
+    return true;
   // Proper FP math operations ignore the sign bit of NaN.
   case Instruction::FAdd:
   case Instruction::FSub:

>From 473b9edb4e69b8225d526bca57deb3a7efac4b96 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 16:03:32 +0800
Subject: [PATCH 4/8] [InstCombine] Add support for ret nofpclass

---
 llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp |  4 ++++
 llvm/test/Transforms/InstCombine/fabs.ll              | 11 +++++++++++
 2 files changed, 15 insertions(+)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 13cbf7b215557..4eb2404170522 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -12,6 +12,7 @@
 
 #include "InstCombineInternal.h"
 #include "llvm/ADT/APInt.h"
+#include "llvm/ADT/FloatingPointMode.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Analysis/AssumptionCache.h"
@@ -2853,6 +2854,9 @@ static bool ignoreSignBitOfNaN(Instruction &I) {
   case Instruction::Select:
   case Instruction::PHI:
     return false;
+  case Instruction::Ret:
+    return I.getFunction()->getAttributes().getRetNoFPClass() &
+           FPClassTest::fcNan;
   case Instruction::Call:
   case Instruction::Invoke: {
     if (auto *II = dyn_cast<IntrinsicInst>(User)) {
diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index b889f6c2c028c..523ef2220aca6 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -1380,6 +1380,17 @@ define void @test_fabs_used_by_nofpclass_nan(float %x) {
   ret void
 }
 
+define nofpclass(nan) float @test_fabs_used_by_ret_nofpclass_nan(float %x) {
+; CHECK-LABEL: @test_fabs_used_by_ret_nofpclass_nan(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    ret float [[SEL]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  ret float %sel
+}
+
 define i32 @test_fabs_used_fptosi(float %x) {
 ; CHECK-LABEL: @test_fabs_used_fptosi(
 ; CHECK-NEXT:    [[SEL:%.*]] = call float @llvm.fabs.f32(float [[X:%.*]])

>From a8e88b171b1bc857a156fe3aa2adf1774013417e Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 20:23:58 +0800
Subject: [PATCH 5/8] [InstCombine] Remove unused header

---
 llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 4eb2404170522..5ce28e5aa435e 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -12,7 +12,6 @@
 
 #include "InstCombineInternal.h"
 #include "llvm/ADT/APInt.h"
-#include "llvm/ADT/FloatingPointMode.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/Analysis/AssumptionCache.h"

>From f0cea1f2f31767a7eec9ea5ca859d9a085aa55d0 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 20:52:43 +0800
Subject: [PATCH 6/8] [InstCombine] Move helper functions into ValueTracking

---
 llvm/include/llvm/Analysis/ValueTracking.h    |   7 +
 llvm/lib/Analysis/ValueTracking.cpp           | 124 ++++++++++++++++++
 .../InstCombine/InstCombineSelect.cpp         | 122 -----------------
 3 files changed, 131 insertions(+), 122 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index 61dbb07e7128e..cc39e28994164 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -330,6 +330,13 @@ bool isKnownNeverNaN(const Value *V, unsigned Depth, const SimplifyQuery &SQ);
 std::optional<bool> computeKnownFPSignBit(const Value *V, unsigned Depth,
                                           const SimplifyQuery &SQ);
 
+/// Return true if the sign bit of result can be ignored when the result is
+/// zero.
+bool ignoreSignBitOfZero(Instruction &I);
+
+/// Return true if the sign bit of result can be ignored when the result is NaN.
+bool ignoreSignBitOfNaN(Instruction &I);
+
 /// If the specified value can be set by repeating the same byte in memory,
 /// return the i8 value that it is represented with. This is true for all i8
 /// values obviously, but is also true for i32 0, i32 -1, i16 0xF0F0, double
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 8405678aa9680..f5f257ea52703 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -6357,6 +6357,130 @@ std::optional<bool> llvm::computeKnownFPSignBit(const Value *V, unsigned Depth,
   return Known.SignBit;
 }
 
+/// Return true if the sign bit of result can be ignored when the result is
+/// zero.
+bool llvm::ignoreSignBitOfZero(Instruction &I) {
+  if (auto *FPOp = dyn_cast<FPMathOperator>(&I))
+    if (FPOp->hasNoSignedZeros())
+      return true;
+
+  // Check if the sign bit is ignored by the only user.
+  if (!I.hasOneUse())
+    return false;
+  Instruction *User = I.user_back();
+  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
+    if (FPOp->hasNoSignedZeros())
+      return true;
+  }
+
+  switch (User->getOpcode()) {
+  case Instruction::FPToSI:
+  case Instruction::FPToUI:
+    return true;
+  case Instruction::FCmp:
+    // fcmp treats both positive and negative zero as equal.
+    return true;
+  case Instruction::Call:
+    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
+      switch (II->getIntrinsicID()) {
+      case Intrinsic::fabs:
+        return true;
+      case Intrinsic::copysign:
+        return II->getArgOperand(0) == &I;
+      case Intrinsic::is_fpclass:
+      case Intrinsic::vp_is_fpclass: {
+        auto Test =
+            static_cast<FPClassTest>(
+                cast<ConstantInt>(II->getArgOperand(1))->getZExtValue()) &
+            FPClassTest::fcZero;
+        return Test == FPClassTest::fcZero || Test == FPClassTest::fcNone;
+      }
+      default:
+        return false;
+      }
+    }
+    return false;
+  default:
+    return false;
+  }
+}
+
+bool llvm::ignoreSignBitOfNaN(Instruction &I) {
+  if (auto *FPOp = dyn_cast<FPMathOperator>(&I))
+    if (FPOp->hasNoNaNs())
+      return true;
+
+  // Check if the sign bit is ignored by the only user.
+  if (!I.hasOneUse())
+    return false;
+  Instruction *User = I.user_back();
+  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
+    if (FPOp->hasNoNaNs())
+      return true;
+  }
+
+  switch (User->getOpcode()) {
+  case Instruction::FPToSI:
+  case Instruction::FPToUI:
+    return true;
+  // Proper FP math operations ignore the sign bit of NaN.
+  case Instruction::FAdd:
+  case Instruction::FSub:
+  case Instruction::FMul:
+  case Instruction::FDiv:
+  case Instruction::FRem:
+  case Instruction::FPTrunc:
+  case Instruction::FPExt:
+  case Instruction::FCmp:
+    return true;
+  // Bitwise FP operations should preserve the sign bit of NaN.
+  case Instruction::FNeg:
+  case Instruction::Select:
+  case Instruction::PHI:
+    return false;
+  case Instruction::Ret:
+    return I.getFunction()->getAttributes().getRetNoFPClass() &
+           FPClassTest::fcNan;
+  case Instruction::Call:
+  case Instruction::Invoke: {
+    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
+      switch (II->getIntrinsicID()) {
+      case Intrinsic::fabs:
+        return true;
+      case Intrinsic::copysign:
+        return II->getArgOperand(0) == &I;
+      // Other proper FP math intrinsics ignore the sign bit of NaN.
+      case Intrinsic::maxnum:
+      case Intrinsic::minnum:
+      case Intrinsic::maximum:
+      case Intrinsic::minimum:
+      case Intrinsic::maximumnum:
+      case Intrinsic::minimumnum:
+      case Intrinsic::canonicalize:
+      case Intrinsic::fma:
+      case Intrinsic::fmuladd:
+      case Intrinsic::sqrt:
+      case Intrinsic::pow:
+      case Intrinsic::powi:
+      case Intrinsic::fptoui_sat:
+      case Intrinsic::fptosi_sat:
+      case Intrinsic::is_fpclass:
+      case Intrinsic::vp_is_fpclass:
+        return true;
+      default:
+        return false;
+      }
+    }
+
+    FPClassTest NoFPClass = cast<CallBase>(User)->getParamNoFPClass(
+        I.uses().begin()->getOperandNo());
+    return NoFPClass & FPClassTest::fcNan;
+  }
+  default:
+    return false;
+  }
+}
+
 Value *llvm::isBytewiseValue(Value *V, const DataLayout &DL) {
 
   // All byte-wide stores are splatable, even of arbitrary variables.
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index 5ce28e5aa435e..cc71e75135f04 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -2773,128 +2773,6 @@ Instruction *InstCombinerImpl::foldAndOrOfSelectUsingImpliedCond(Value *Op,
   return nullptr;
 }
 
-/// Return true if the sign bit of result can be ignored when the result is
-/// zero.
-static bool ignoreSignBitOfZero(Instruction &I) {
-  if (I.hasNoSignedZeros())
-    return true;
-
-  // Check if the sign bit is ignored by the only user.
-  if (!I.hasOneUse())
-    return false;
-  Instruction *User = I.user_back();
-  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
-    if (FPOp->hasNoSignedZeros())
-      return true;
-  }
-
-  switch (User->getOpcode()) {
-  case Instruction::FPToSI:
-  case Instruction::FPToUI:
-    return true;
-  case Instruction::FCmp:
-    // fcmp treats both positive and negative zero as equal.
-    return true;
-  case Instruction::Call:
-    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
-      switch (II->getIntrinsicID()) {
-      case Intrinsic::fabs:
-        return true;
-      case Intrinsic::copysign:
-        return II->getArgOperand(0) == &I;
-      case Intrinsic::is_fpclass:
-      case Intrinsic::vp_is_fpclass: {
-        auto Test =
-            static_cast<FPClassTest>(
-                cast<ConstantInt>(II->getArgOperand(1))->getZExtValue()) &
-            FPClassTest::fcZero;
-        return Test == FPClassTest::fcZero || Test == FPClassTest::fcNone;
-      }
-      default:
-        return false;
-      }
-    }
-    return false;
-  default:
-    return false;
-  }
-}
-
-/// Return true if the sign bit of result can be ignored when the result is NaN.
-static bool ignoreSignBitOfNaN(Instruction &I) {
-  if (I.hasNoNaNs())
-    return true;
-
-  // Check if the sign bit is ignored by the only user.
-  if (!I.hasOneUse())
-    return false;
-  Instruction *User = I.user_back();
-  if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
-    if (FPOp->hasNoNaNs())
-      return true;
-  }
-
-  switch (User->getOpcode()) {
-  case Instruction::FPToSI:
-  case Instruction::FPToUI:
-    return true;
-  // Proper FP math operations ignore the sign bit of NaN.
-  case Instruction::FAdd:
-  case Instruction::FSub:
-  case Instruction::FMul:
-  case Instruction::FDiv:
-  case Instruction::FRem:
-  case Instruction::FPTrunc:
-  case Instruction::FPExt:
-  case Instruction::FCmp:
-    return true;
-  // Bitwise FP operations should preserve the sign bit of NaN.
-  case Instruction::FNeg:
-  case Instruction::Select:
-  case Instruction::PHI:
-    return false;
-  case Instruction::Ret:
-    return I.getFunction()->getAttributes().getRetNoFPClass() &
-           FPClassTest::fcNan;
-  case Instruction::Call:
-  case Instruction::Invoke: {
-    if (auto *II = dyn_cast<IntrinsicInst>(User)) {
-      switch (II->getIntrinsicID()) {
-      case Intrinsic::fabs:
-        return true;
-      case Intrinsic::copysign:
-        return II->getArgOperand(0) == &I;
-      // Other proper FP math intrinsics ignore the sign bit of NaN.
-      case Intrinsic::maxnum:
-      case Intrinsic::minnum:
-      case Intrinsic::maximum:
-      case Intrinsic::minimum:
-      case Intrinsic::maximumnum:
-      case Intrinsic::minimumnum:
-      case Intrinsic::canonicalize:
-      case Intrinsic::fma:
-      case Intrinsic::fmuladd:
-      case Intrinsic::sqrt:
-      case Intrinsic::pow:
-      case Intrinsic::powi:
-      case Intrinsic::fptoui_sat:
-      case Intrinsic::fptosi_sat:
-      case Intrinsic::is_fpclass:
-        return true;
-      default:
-        return false;
-      }
-    }
-
-    FPClassTest NoFPClass = cast<CallBase>(User)->getParamNoFPClass(
-        I.uses().begin()->getOperandNo());
-    return NoFPClass & FPClassTest::fcNan;
-  }
-  default:
-    return false;
-  }
-}
-
 // Canonicalize select with fcmp to fabs(). -0.0 makes this tricky. We need
 // fast-math-flags (nsz) or fsub with +0.0 (not fneg) for this to work.
 static Instruction *foldSelectWithFCmpToFabs(SelectInst &SI,

>From 2c7395e18eba1032353ad892659b7e1eefae6566 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 21:29:14 +0800
Subject: [PATCH 7/8] [InstCombine] Add more tests. NFC.

---
 llvm/test/Transforms/InstCombine/fabs.ll | 220 +++++++++++++++++++++++
 1 file changed, 220 insertions(+)

diff --git a/llvm/test/Transforms/InstCombine/fabs.ll b/llvm/test/Transforms/InstCombine/fabs.ll
index 523ef2220aca6..0a22d1431b5f1 100644
--- a/llvm/test/Transforms/InstCombine/fabs.ll
+++ b/llvm/test/Transforms/InstCombine/fabs.ll
@@ -1454,6 +1454,226 @@ define i1 @test_fabs_used_is_fpclass_zero_or_pinf(float %x) {
   ret i1 %is_fpclass
 }
 
+define float @test_fabs_nsz_used_by_fadd(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fadd(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fadd float [[SEL]], 1.000000e+00
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fadd float %sel, 1.000000e+00
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_fsub(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fsub(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fsub float 1.000000e+00, [[SEL]]
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fsub float 1.000000e+00, %sel
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_fmul(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fmul(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fmul float [[SEL]], 3.000000e+00
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fmul float %sel, 3.000000e+00
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_fdiv(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fdiv(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fdiv float [[SEL]], 3.000000e+00
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fdiv float %sel, 3.000000e+00
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_frem(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_frem(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fdiv float [[SEL]], 3.000000e+00
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fdiv float %sel, 3.000000e+00
+  ret float %op
+}
+
+define half @test_fabs_nsz_used_by_fptrunc(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fptrunc(
+; CHECK-NEXT:    [[TMP1:%.*]] = fptrunc float [[X:%.*]] to half
+; CHECK-NEXT:    [[OP:%.*]] = call nsz half @llvm.fabs.f16(half [[TMP1]])
+; CHECK-NEXT:    ret half [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = fptrunc float %sel to half
+  ret half %op
+}
+
+define float @test_fabs_nsz_used_by_fpext(half %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fpext(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz half @llvm.fabs.f16(half [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = fpext half [[SEL]] to float
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge half %x, 0.000000e+00
+  %neg = fneg half %x
+  %sel = select nsz i1 %cmp, half %x, half %neg
+  %op = fpext half %sel to float
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_maximum(float %x, float %y) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_maximum(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[MAX:%.*]] = call float @llvm.maximum.f32(float [[Y:%.*]], float [[SEL]])
+; CHECK-NEXT:    ret float [[MAX]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %max = call float @llvm.maximum.f32(float %y, float %sel)
+  ret float %max
+}
+
+define float @test_fabs_nsz_used_by_maximumnum(float %x, float %y) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_maximumnum(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[MAX:%.*]] = call float @llvm.maximumnum.f32(float [[Y:%.*]], float [[SEL]])
+; CHECK-NEXT:    ret float [[MAX]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %max = call float @llvm.maximumnum.f32(float %y, float %sel)
+  ret float %max
+}
+
+define float @test_fabs_nsz_used_by_fma(float %x, float %y, float %z) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fma(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[MAX:%.*]] = call float @llvm.fma.f32(float [[SEL]], float [[Z:%.*]], float [[Z]])
+; CHECK-NEXT:    ret float [[MAX]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %max = call float @llvm.fma.f32(float %sel, float %z, float %z)
+  ret float %max
+}
+
+define float @test_fabs_nsz_used_by_sqrt(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_sqrt(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = call float @llvm.sqrt.f32(float [[SEL]])
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = call float @llvm.sqrt.f32(float %sel)
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_pow(float %x, float %y) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_pow(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = call float @llvm.pow.f32(float [[SEL]], float [[Y:%.*]])
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = call float @llvm.pow.f32(float %sel, float %y)
+  ret float %op
+}
+
+define float @test_fabs_nsz_used_by_powi(float %x, i32 %y) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_powi(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = call float @llvm.powi.f32.i32(float [[SEL]], i32 [[Y:%.*]])
+; CHECK-NEXT:    ret float [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = call float @llvm.powi.f32(float %sel, i32 %y)
+  ret float %op
+}
+
+define i32 @test_fabs_nsz_used_by_fptoui_sat(float %x) {
+; CHECK-LABEL: @test_fabs_nsz_used_by_fptoui_sat(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    [[OP:%.*]] = call i32 @llvm.fptoui.sat.i32.f32(float [[SEL]])
+; CHECK-NEXT:    ret i32 [[OP]]
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  %op = call i32 @llvm.fptoui.sat.i32.f32(float %sel)
+  ret i32 %op
+}
+
+define <2 x i1> @test_fabs_used_vp_is_fpclass_zero_or_pinf(<2 x float> %x, <2 x i1> %mask, i32 %evl) {
+; CHECK-LABEL: @test_fabs_used_vp_is_fpclass_zero_or_pinf(
+; CHECK-NEXT:    [[SEL:%.*]] = call <2 x float> @llvm.fabs.v2f32(<2 x float> [[X:%.*]])
+; CHECK-NEXT:    [[IS_FPCLASS:%.*]] = call <2 x i1> @llvm.vp.is.fpclass.v2f32(<2 x float> [[SEL]], i32 608, <2 x i1> [[MASK:%.*]], i32 [[EVL:%.*]])
+; CHECK-NEXT:    ret <2 x i1> [[IS_FPCLASS]]
+;
+  %cmp = fcmp oge <2 x float> %x, zeroinitializer
+  %neg = fneg <2 x float> %x
+  %sel = select <2 x i1> %cmp, <2 x float> %x, <2 x float> %neg
+  %is_fpclass = call <2 x i1> @llvm.vp.is.fpclass.v2f32(<2 x float> %sel, i32 608, <2 x i1> %mask, i32 %evl)
+  ret <2 x i1> %is_fpclass
+}
+
+define void @test_fabs_nsz_used_by_invoke(float %x) personality ptr null {
+; CHECK-LABEL: @test_fabs_nsz_used_by_invoke(
+; CHECK-NEXT:    [[SEL:%.*]] = call nsz float @llvm.fabs.f32(float [[X:%.*]])
+; CHECK-NEXT:    invoke void @use(float nofpclass(nan) [[SEL]])
+; CHECK-NEXT:            to label [[NORMAL:%.*]] unwind label [[UNWIND:%.*]]
+; CHECK:       normal:
+; CHECK-NEXT:    ret void
+; CHECK:       unwind:
+; CHECK-NEXT:    [[TMP1:%.*]] = landingpad ptr
+; CHECK-NEXT:            cleanup
+; CHECK-NEXT:    resume ptr null
+;
+  %cmp = fcmp oge float %x, 0.000000e+00
+  %neg = fneg float %x
+  %sel = select nsz i1 %cmp, float %x, float %neg
+  invoke void @use(float nofpclass(nan) %sel) to label %normal unwind label %unwind
+
+normal:
+  ret void
+
+unwind:
+  landingpad ptr cleanup
+  resume ptr null
+}
+
 ; Negative tests
 
 define float @test_fabs_used_by_fpop_nnan(float %x, float %y) {

>From e02d070224709de0d0d0ee3b96266ecdd6705bd7 Mon Sep 17 00:00:00 2001
From: Yingwei Zheng <dtcxzyw2333 at gmail.com>
Date: Thu, 22 May 2025 23:54:47 +0800
Subject: [PATCH 8/8] [InstCombine] Address review comemnts. NFC.

---
 llvm/include/llvm/Analysis/ValueTracking.h    | 11 +++---
 llvm/lib/Analysis/ValueTracking.cpp           | 38 +++++++------------
 .../InstCombine/InstCombineSelect.cpp         |  9 ++++-
 3 files changed, 26 insertions(+), 32 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueTracking.h b/llvm/include/llvm/Analysis/ValueTracking.h
index cc39e28994164..18a209bf2ebee 100644
--- a/llvm/include/llvm/Analysis/ValueTracking.h
+++ b/llvm/include/llvm/Analysis/ValueTracking.h
@@ -330,12 +330,13 @@ bool isKnownNeverNaN(const Value *V, unsigned Depth, const SimplifyQuery &SQ);
 std::optional<bool> computeKnownFPSignBit(const Value *V, unsigned Depth,
                                           const SimplifyQuery &SQ);
 
-/// Return true if the sign bit of result can be ignored when the result is
-/// zero.
-bool ignoreSignBitOfZero(Instruction &I);
+/// Return true if the sign bit of result can be ignored by the user when the
+/// result is zero.
+bool ignoreSignBitOfZero(const Use &U);
 
-/// Return true if the sign bit of result can be ignored when the result is NaN.
-bool ignoreSignBitOfNaN(Instruction &I);
+/// Return true if the sign bit of result can be ignored by the user when the
+/// result is NaN.
+bool ignoreSignBitOfNaN(const Use &U);
 
 /// If the specified value can be set by repeating the same byte in memory,
 /// return the i8 value that it is represented with. This is true for all i8
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index f5f257ea52703..73fa841095a46 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -6357,17 +6357,10 @@ std::optional<bool> llvm::computeKnownFPSignBit(const Value *V, unsigned Depth,
   return Known.SignBit;
 }
 
-/// Return true if the sign bit of result can be ignored when the result is
-/// zero.
-bool llvm::ignoreSignBitOfZero(Instruction &I) {
-  if (auto *FPOp = dyn_cast<FPMathOperator>(&I))
-    if (FPOp->hasNoSignedZeros())
-      return true;
-
-  // Check if the sign bit is ignored by the only user.
-  if (!I.hasOneUse())
-    return false;
-  Instruction *User = I.user_back();
+/// Return true if the sign bit of result can be ignored by the user when the
+/// result is zero.
+bool llvm::ignoreSignBitOfZero(const Use &U) {
+  auto *User = cast<Instruction>(U.getUser());
   if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
     if (FPOp->hasNoSignedZeros())
       return true;
@@ -6386,7 +6379,7 @@ bool llvm::ignoreSignBitOfZero(Instruction &I) {
       case Intrinsic::fabs:
         return true;
       case Intrinsic::copysign:
-        return II->getArgOperand(0) == &I;
+        return U.getOperandNo() == 0;
       case Intrinsic::is_fpclass:
       case Intrinsic::vp_is_fpclass: {
         auto Test =
@@ -6405,15 +6398,10 @@ bool llvm::ignoreSignBitOfZero(Instruction &I) {
   }
 }
 
-bool llvm::ignoreSignBitOfNaN(Instruction &I) {
-  if (auto *FPOp = dyn_cast<FPMathOperator>(&I))
-    if (FPOp->hasNoNaNs())
-      return true;
-
-  // Check if the sign bit is ignored by the only user.
-  if (!I.hasOneUse())
-    return false;
-  Instruction *User = I.user_back();
+/// Return true if the sign bit of result can be ignored by the user when the
+/// result is NaN.
+bool llvm::ignoreSignBitOfNaN(const Use &U) {
+  auto *User = cast<Instruction>(U.getUser());
   if (auto *FPOp = dyn_cast<FPMathOperator>(User)) {
     if (FPOp->hasNoNaNs())
       return true;
@@ -6439,7 +6427,7 @@ bool llvm::ignoreSignBitOfNaN(Instruction &I) {
   case Instruction::PHI:
     return false;
   case Instruction::Ret:
-    return I.getFunction()->getAttributes().getRetNoFPClass() &
+    return User->getFunction()->getAttributes().getRetNoFPClass() &
            FPClassTest::fcNan;
   case Instruction::Call:
   case Instruction::Invoke: {
@@ -6448,7 +6436,7 @@ bool llvm::ignoreSignBitOfNaN(Instruction &I) {
       case Intrinsic::fabs:
         return true;
       case Intrinsic::copysign:
-        return II->getArgOperand(0) == &I;
+        return U.getOperandNo() == 0;
       // Other proper FP math intrinsics ignore the sign bit of NaN.
       case Intrinsic::maxnum:
       case Intrinsic::minnum:
@@ -6472,8 +6460,8 @@ bool llvm::ignoreSignBitOfNaN(Instruction &I) {
       }
     }
 
-    FPClassTest NoFPClass = cast<CallBase>(User)->getParamNoFPClass(
-        I.uses().begin()->getOperandNo());
+    FPClassTest NoFPClass =
+        cast<CallBase>(User)->getParamNoFPClass(U.getOperandNo());
     return NoFPClass & FPClassTest::fcNan;
   }
   default:
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
index cc71e75135f04..6bd3cefbcfda6 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSelect.cpp
@@ -2797,7 +2797,8 @@ static Instruction *foldSelectWithFCmpToFabs(SelectInst &SI,
     //       of NAN, but IEEE-754 specifies the signbit of NAN values with
     //       fneg/fabs operations.
     if (match(TrueVal, m_FSub(m_PosZeroFP(), m_Specific(X))) &&
-        (cast<FPMathOperator>(CondVal)->hasNoNaNs() || ignoreSignBitOfNaN(SI) ||
+        (cast<FPMathOperator>(CondVal)->hasNoNaNs() || SI.hasNoNaNs() ||
+         (SI.hasOneUse() && ignoreSignBitOfNaN(*SI.use_begin())) ||
          isKnownNeverNaN(X, /*Depth=*/0,
                          IC.getSimplifyQuery().getWithInstruction(
                              cast<Instruction>(CondVal))))) {
@@ -2844,7 +2845,11 @@ static Instruction *foldSelectWithFCmpToFabs(SelectInst &SI,
     // Note: We require "nnan" for this fold because fcmp ignores the signbit
     //       of NAN, but IEEE-754 specifies the signbit of NAN values with
     //       fneg/fabs operations.
-    if (!ignoreSignBitOfZero(SI) || !ignoreSignBitOfNaN(SI))
+    if (!SI.hasNoSignedZeros() &&
+        !(SI.hasOneUse() && ignoreSignBitOfZero(*SI.use_begin())))
+      return nullptr;
+    if (!SI.hasNoNaNs() &&
+        !(SI.hasOneUse() && ignoreSignBitOfNaN(*SI.use_begin())))
       return nullptr;
 
     if (Swap)



More information about the llvm-commits mailing list