Skip to content

Conversation

@arsenm
Copy link
Contributor

@arsenm arsenm commented Dec 29, 2025

Alive isn't particularly happy with this in the case where
one of the inputs could be zero, but I think
it's wrong: https://alive2.llvm.org/ce/z/dF7V6k

nsz shouldn't permit introducing a -0 result where
there wasn't one in the input here.

Copy link
Contributor Author

arsenm commented Dec 29, 2025

@arsenm arsenm added floating-point Floating-point math llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:support llvm:transforms labels Dec 29, 2025 — with Graphite App
@arsenm arsenm marked this pull request as ready for review December 29, 2025 18:04
@llvmbot
Copy link
Member

llvmbot commented Dec 29, 2025

@llvm/pr-subscribers-llvm-analysis

Author: Matt Arsenault (arsenm)

Changes

Alive isn't particularly happy with this in the case where
one of the inputs could be zero, but I think
it's wrong: https://alive2.llvm.org/ce/z/dF7V6k

nsz shouldn't permit introducing a -0 result where
there wasn't one in the input here.


Patch is 43.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/173898.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp (+18-2)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll (+30-30)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-minimum.ll (+30-30)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 762f7421631b3..588aeffff8a8d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2211,9 +2211,25 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
       auto *FPOp = cast<FPMathOperator>(CI);
 
       bool ChangedFlags = false;
+      if (!FPOp->hasNoSignedZeros()) {
+        // Add NSZ flag if we know the result will not be sensitive on the sign
+        // of 0.
+        FPClassTest ZeroMask = fcZero;
+
+        if (Mode != DenormalMode::getIEEE())
+          ZeroMask |= fcSubnormal;
+
+        bool ResultNotLogical0 = (ValidResults & ZeroMask) == fcNone;
+        if (ResultNotLogical0 ||
+            ((KnownLHS.isKnownNeverLogicalNegZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalPosZero(Mode)) &&
+             (KnownLHS.isKnownNeverLogicalPosZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalNegZero(Mode)))) {
+          CI->setHasNoSignedZeros(true);
+          ChangedFlags = true;
+        }
+      }
 
-      // TODO: Add NSZ flag if we know the result will not be sensitive on the
-      // sign of 0.
       if (!FPOp->hasNoNaNs() && (ValidResults & fcNan) == fcNone) {
         CI->setHasNoNaNs(true);
         ChangedFlags = true;
diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
index b7c41ff6c3c64..9a81edbf06b96 100644
--- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
@@ -34,7 +34,7 @@ define nofpclass(inf norm sub zero) float @ret_only_nan(float %x, float %y) {
 define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(qnan inf zero sub norm) float @ret_only_snan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -44,7 +44,7 @@ define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y
 define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(snan inf zero sub norm) float @ret_only_qnan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -54,7 +54,7 @@ define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y
 define nofpclass(nan norm sub zero) float @ret_only_inf(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(nan zero sub norm) float @ret_only_inf(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -364,7 +364,7 @@ define nofpclass(snan) float @cannot_fold_negative_or_zero__positive_or_zero_1()
 define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf.or.nan, float %unknown)
@@ -374,7 +374,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf.or.nan)
@@ -384,7 +384,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm
 define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(nan ninf norm zero sub) %must.be.pinf) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf, float %unknown)
@@ -394,7 +394,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(n
 define nofpclass(nsub) float @rhs_must_be_pinf(float nofpclass(nan ninf norm zero sub) %must.be.pinf, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf(
 ; CHECK-SAME: float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf)
@@ -424,7 +424,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_lhs_non_nan(float nofpclass(nan n
 define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.ninf.or.nan, float %unknown)
@@ -434,7 +434,7 @@ define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.ninf.or.nan)
@@ -503,7 +503,7 @@ define nofpclass(pzero) float @result_not_pzero(float %unknown0, float %unknown1
 define nofpclass(zero) float @result_not_zero(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -533,7 +533,7 @@ define nofpclass(zero) float @result_not_zero__dynamic(float %unknown0, float %u
 define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero_or_sub(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -543,7 +543,7 @@ define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unk
 define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, float %unknown1) #0 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__daz(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -553,7 +553,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, f
 define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown0, float %unknown1) #1 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -563,7 +563,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown
 define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, float %unknown) {
 ; CHECK-LABEL: define nofpclass(snan) float @lhs_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero, float %unknown)
@@ -573,7 +573,7 @@ define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, floa
 define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero) %not.zero) {
 ; CHECK-LABEL: define nofpclass(snan) float @rhs_not_zero(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(zero) [[NOT_ZERO:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %not.zero)
@@ -583,7 +583,7 @@ define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero)
 define nofpclass(snan) float @sources_not_zero(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -603,7 +603,7 @@ define nofpclass(snan) float @sources_not_zero__daz(float nofpclass(zero) %not.z
 define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero__dynamic(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -613,7 +613,7 @@ define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %n
 define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -623,7 +623,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub)
 define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__daz(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -633,7 +633,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero
 define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -643,7 +643,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(z
 define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzero0, float nofpclass(nzero) %not.nzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero(
 ; CHECK-SAME: float nofpclass(nzero) [[NOT_NZERO0:%.*]], float nofpclass(nzero) [[NOT_NZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -653,7 +653,7 @@ define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzer
 define nofpclass(snan) float @sources_not_pzero(float nofpclass(pzero) %not.pzero0, float nofpclass(pzero) %not.pzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_pzero(
 ; CHECK-SAME: float nofpclass(pzero) [[NOT_PZERO0:%.*]], float nofpclass(pzero) [[NOT_PZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.pzero0, float %not.pzero1)
@@ -723,7 +723,7 @@ define nofpclass(snan) float @rhs_not_nzero_lhs_not_pzero__dynamic(float nofpcla
 define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(float nofpclass(nzero nsub) %not.nzero0, float nofpclass(nzero nsub) %not.nzero1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(
 ; CHECK-SAME: float nofpclass(nzero nsub) [[NOT_NZERO0:%.*]], float nofpclass(nzero nsub) [[NOT_NZERO1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -916,7 +916,7 @@ define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.positive = call float @returns_positive()
@@ -929,7 +929,7 @@ define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.pzero = call float @returns_pzero()
@@ -1018,7 +1018,7 @@ define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-NEXT:    [[KNOWN_NEGATIVE:%.*]] = call float @returns_negative()
 ; CHECK-NEXT:    [[KNOWN_NZERO_OR_NAN:%.*]] = call float @returns_nzero_or_nan()
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.negative = call float @returns_negative()
@@ -1031,7 +1031,7 @@ define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-NEXT:    [[KNOWN_NZERO_O...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 29, 2025

@llvm/pr-subscribers-llvm-transforms

Author: Matt Arsenault (arsenm)

Changes

Alive isn't particularly happy with this in the case where
one of the inputs could be zero, but I think
it's wrong: https://alive2.llvm.org/ce/z/dF7V6k

nsz shouldn't permit introducing a -0 result where
there wasn't one in the input here.


Patch is 43.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/173898.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp (+18-2)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll (+30-30)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-minimum.ll (+30-30)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 762f7421631b3..588aeffff8a8d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2211,9 +2211,25 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
       auto *FPOp = cast<FPMathOperator>(CI);
 
       bool ChangedFlags = false;
+      if (!FPOp->hasNoSignedZeros()) {
+        // Add NSZ flag if we know the result will not be sensitive on the sign
+        // of 0.
+        FPClassTest ZeroMask = fcZero;
+
+        if (Mode != DenormalMode::getIEEE())
+          ZeroMask |= fcSubnormal;
+
+        bool ResultNotLogical0 = (ValidResults & ZeroMask) == fcNone;
+        if (ResultNotLogical0 ||
+            ((KnownLHS.isKnownNeverLogicalNegZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalPosZero(Mode)) &&
+             (KnownLHS.isKnownNeverLogicalPosZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalNegZero(Mode)))) {
+          CI->setHasNoSignedZeros(true);
+          ChangedFlags = true;
+        }
+      }
 
-      // TODO: Add NSZ flag if we know the result will not be sensitive on the
-      // sign of 0.
       if (!FPOp->hasNoNaNs() && (ValidResults & fcNan) == fcNone) {
         CI->setHasNoNaNs(true);
         ChangedFlags = true;
diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
index b7c41ff6c3c64..9a81edbf06b96 100644
--- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
@@ -34,7 +34,7 @@ define nofpclass(inf norm sub zero) float @ret_only_nan(float %x, float %y) {
 define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(qnan inf zero sub norm) float @ret_only_snan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -44,7 +44,7 @@ define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y
 define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(snan inf zero sub norm) float @ret_only_qnan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -54,7 +54,7 @@ define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y
 define nofpclass(nan norm sub zero) float @ret_only_inf(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(nan zero sub norm) float @ret_only_inf(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -364,7 +364,7 @@ define nofpclass(snan) float @cannot_fold_negative_or_zero__positive_or_zero_1()
 define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf.or.nan, float %unknown)
@@ -374,7 +374,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf.or.nan)
@@ -384,7 +384,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm
 define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(nan ninf norm zero sub) %must.be.pinf) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf, float %unknown)
@@ -394,7 +394,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(n
 define nofpclass(nsub) float @rhs_must_be_pinf(float nofpclass(nan ninf norm zero sub) %must.be.pinf, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf(
 ; CHECK-SAME: float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf)
@@ -424,7 +424,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_lhs_non_nan(float nofpclass(nan n
 define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.ninf.or.nan, float %unknown)
@@ -434,7 +434,7 @@ define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.ninf.or.nan)
@@ -503,7 +503,7 @@ define nofpclass(pzero) float @result_not_pzero(float %unknown0, float %unknown1
 define nofpclass(zero) float @result_not_zero(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -533,7 +533,7 @@ define nofpclass(zero) float @result_not_zero__dynamic(float %unknown0, float %u
 define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero_or_sub(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -543,7 +543,7 @@ define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unk
 define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, float %unknown1) #0 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__daz(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -553,7 +553,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, f
 define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown0, float %unknown1) #1 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -563,7 +563,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown
 define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, float %unknown) {
 ; CHECK-LABEL: define nofpclass(snan) float @lhs_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero, float %unknown)
@@ -573,7 +573,7 @@ define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, floa
 define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero) %not.zero) {
 ; CHECK-LABEL: define nofpclass(snan) float @rhs_not_zero(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(zero) [[NOT_ZERO:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %not.zero)
@@ -583,7 +583,7 @@ define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero)
 define nofpclass(snan) float @sources_not_zero(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -603,7 +603,7 @@ define nofpclass(snan) float @sources_not_zero__daz(float nofpclass(zero) %not.z
 define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero__dynamic(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -613,7 +613,7 @@ define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %n
 define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -623,7 +623,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub)
 define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__daz(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -633,7 +633,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero
 define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -643,7 +643,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(z
 define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzero0, float nofpclass(nzero) %not.nzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero(
 ; CHECK-SAME: float nofpclass(nzero) [[NOT_NZERO0:%.*]], float nofpclass(nzero) [[NOT_NZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -653,7 +653,7 @@ define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzer
 define nofpclass(snan) float @sources_not_pzero(float nofpclass(pzero) %not.pzero0, float nofpclass(pzero) %not.pzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_pzero(
 ; CHECK-SAME: float nofpclass(pzero) [[NOT_PZERO0:%.*]], float nofpclass(pzero) [[NOT_PZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.pzero0, float %not.pzero1)
@@ -723,7 +723,7 @@ define nofpclass(snan) float @rhs_not_nzero_lhs_not_pzero__dynamic(float nofpcla
 define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(float nofpclass(nzero nsub) %not.nzero0, float nofpclass(nzero nsub) %not.nzero1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(
 ; CHECK-SAME: float nofpclass(nzero nsub) [[NOT_NZERO0:%.*]], float nofpclass(nzero nsub) [[NOT_NZERO1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -916,7 +916,7 @@ define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.positive = call float @returns_positive()
@@ -929,7 +929,7 @@ define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.pzero = call float @returns_pzero()
@@ -1018,7 +1018,7 @@ define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-NEXT:    [[KNOWN_NEGATIVE:%.*]] = call float @returns_negative()
 ; CHECK-NEXT:    [[KNOWN_NZERO_OR_NAN:%.*]] = call float @returns_nzero_or_nan()
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.negative = call float @returns_negative()
@@ -1031,7 +1031,7 @@ define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-NEXT:    [[KNOWN_NZERO_O...
[truncated]

@llvmbot
Copy link
Member

llvmbot commented Dec 29, 2025

@llvm/pr-subscribers-llvm-support

Author: Matt Arsenault (arsenm)

Changes

Alive isn't particularly happy with this in the case where
one of the inputs could be zero, but I think
it's wrong: https://alive2.llvm.org/ce/z/dF7V6k

nsz shouldn't permit introducing a -0 result where
there wasn't one in the input here.


Patch is 43.56 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/173898.diff

3 Files Affected:

  • (modified) llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp (+18-2)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll (+30-30)
  • (modified) llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-minimum.ll (+30-30)
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 762f7421631b3..588aeffff8a8d 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -2211,9 +2211,25 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Value *V,
       auto *FPOp = cast<FPMathOperator>(CI);
 
       bool ChangedFlags = false;
+      if (!FPOp->hasNoSignedZeros()) {
+        // Add NSZ flag if we know the result will not be sensitive on the sign
+        // of 0.
+        FPClassTest ZeroMask = fcZero;
+
+        if (Mode != DenormalMode::getIEEE())
+          ZeroMask |= fcSubnormal;
+
+        bool ResultNotLogical0 = (ValidResults & ZeroMask) == fcNone;
+        if (ResultNotLogical0 ||
+            ((KnownLHS.isKnownNeverLogicalNegZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalPosZero(Mode)) &&
+             (KnownLHS.isKnownNeverLogicalPosZero(Mode) ||
+              KnownRHS.isKnownNeverLogicalNegZero(Mode)))) {
+          CI->setHasNoSignedZeros(true);
+          ChangedFlags = true;
+        }
+      }
 
-      // TODO: Add NSZ flag if we know the result will not be sensitive on the
-      // sign of 0.
       if (!FPOp->hasNoNaNs() && (ValidResults & fcNan) == fcNone) {
         CI->setHasNoNaNs(true);
         ChangedFlags = true;
diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
index b7c41ff6c3c64..9a81edbf06b96 100644
--- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-maximum.ll
@@ -34,7 +34,7 @@ define nofpclass(inf norm sub zero) float @ret_only_nan(float %x, float %y) {
 define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(qnan inf zero sub norm) float @ret_only_snan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -44,7 +44,7 @@ define nofpclass(inf norm sub zero qnan) float @ret_only_snan(float %x, float %y
 define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(snan inf zero sub norm) float @ret_only_qnan(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -54,7 +54,7 @@ define nofpclass(inf norm sub zero snan) float @ret_only_qnan(float %x, float %y
 define nofpclass(nan norm sub zero) float @ret_only_inf(float %x, float %y) {
 ; CHECK-LABEL: define nofpclass(nan zero sub norm) float @ret_only_inf(
 ; CHECK-SAME: float [[X:%.*]], float [[Y:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[X]], float [[Y]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[X]], float [[Y]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %x, float %y)
@@ -364,7 +364,7 @@ define nofpclass(snan) float @cannot_fold_negative_or_zero__positive_or_zero_1()
 define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_PINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf.or.nan, float %unknown)
@@ -374,7 +374,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm zero sub) %must.be.pinf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(
 ; CHECK-SAME: float nofpclass(ninf zero sub norm) [[MUST_BE_PINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_PINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf.or.nan)
@@ -384,7 +384,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_or_nan(float nofpclass(ninf norm
 define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(nan ninf norm zero sub) %must.be.pinf) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_pinf(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.pinf, float %unknown)
@@ -394,7 +394,7 @@ define nofpclass(nsub) float @lhs_must_be_pinf(float %unknown, float nofpclass(n
 define nofpclass(nsub) float @rhs_must_be_pinf(float nofpclass(nan ninf norm zero sub) %must.be.pinf, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_pinf(
 ; CHECK-SAME: float nofpclass(nan ninf zero sub norm) [[MUST_BE_PINF:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float 0x7FF0000000000000)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.pinf)
@@ -424,7 +424,7 @@ define nofpclass(nsub) float @rhs_must_be_pinf_lhs_non_nan(float nofpclass(nan n
 define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan) {
 ; CHECK-LABEL: define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[MUST_BE_NINF_OR_NAN]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %must.be.ninf.or.nan, float %unknown)
@@ -434,7 +434,7 @@ define nofpclass(nsub) float @lhs_must_be_ninf_or_nan(float %unknown, float nofp
 define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(float nofpclass(pinf norm zero sub) %must.be.ninf.or.nan, float %unknown) {
 ; CHECK-LABEL: define nofpclass(nsub) float @rhs_must_be_ninf_or_nan(
 ; CHECK-SAME: float nofpclass(pinf zero sub norm) [[MUST_BE_NINF_OR_NAN:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[MUST_BE_NINF_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %must.be.ninf.or.nan)
@@ -503,7 +503,7 @@ define nofpclass(pzero) float @result_not_pzero(float %unknown0, float %unknown1
 define nofpclass(zero) float @result_not_zero(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -533,7 +533,7 @@ define nofpclass(zero) float @result_not_zero__dynamic(float %unknown0, float %u
 define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unknown1) {
 ; CHECK-LABEL: define nofpclass(zero) float @result_not_zero_or_sub(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -543,7 +543,7 @@ define nofpclass(zero) float @result_not_zero_or_sub(float %unknown0, float %unk
 define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, float %unknown1) #0 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__daz(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR0]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -553,7 +553,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__daz(float %unknown0, f
 define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown0, float %unknown1) #1 {
 ; CHECK-LABEL: define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float [[UNKNOWN0:%.*]], float [[UNKNOWN1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN0]], float [[UNKNOWN1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown0, float %unknown1)
@@ -563,7 +563,7 @@ define nofpclass(zero sub) float @result_not_zero_or_sub__dynamic(float %unknown
 define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, float %unknown) {
 ; CHECK-LABEL: define nofpclass(snan) float @lhs_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO:%.*]], float [[UNKNOWN:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO]], float [[UNKNOWN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero, float %unknown)
@@ -573,7 +573,7 @@ define nofpclass(snan) float @lhs_not_zero(float nofpclass(zero) %not.zero, floa
 define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero) %not.zero) {
 ; CHECK-LABEL: define nofpclass(snan) float @rhs_not_zero(
 ; CHECK-SAME: float [[UNKNOWN:%.*]], float nofpclass(zero) [[NOT_ZERO:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[UNKNOWN]], float [[NOT_ZERO]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %unknown, float %not.zero)
@@ -583,7 +583,7 @@ define nofpclass(snan) float @rhs_not_zero(float %unknown, float nofpclass(zero)
 define nofpclass(snan) float @sources_not_zero(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -603,7 +603,7 @@ define nofpclass(snan) float @sources_not_zero__daz(float nofpclass(zero) %not.z
 define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %not.zero0, float nofpclass(zero) %not.zero1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero__dynamic(
 ; CHECK-SAME: float nofpclass(zero) [[NOT_ZERO0:%.*]], float nofpclass(zero) [[NOT_ZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO0]], float [[NOT_ZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero0, float %not.zero1)
@@ -613,7 +613,7 @@ define nofpclass(snan) float @sources_not_zero__dynamic(float nofpclass(zero) %n
 define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -623,7 +623,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub(float nofpclass(zero sub)
 define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__daz(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -633,7 +633,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__daz(float nofpclass(zero
 define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(zero sub) %not.zero.sub.0, float nofpclass(zero sub) %not.zero.sub.1) #2 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(
 ; CHECK-SAME: float nofpclass(zero sub) [[NOT_ZERO_SUB_0:%.*]], float nofpclass(zero sub) [[NOT_ZERO_SUB_1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_ZERO_SUB_0]], float [[NOT_ZERO_SUB_1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.zero.sub.0, float %not.zero.sub.1)
@@ -643,7 +643,7 @@ define nofpclass(snan) float @sources_not_zero_or_sub__dynamic(float nofpclass(z
 define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzero0, float nofpclass(nzero) %not.nzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero(
 ; CHECK-SAME: float nofpclass(nzero) [[NOT_NZERO0:%.*]], float nofpclass(nzero) [[NOT_NZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -653,7 +653,7 @@ define nofpclass(snan) float @sources_not_nzero(float nofpclass(nzero) %not.nzer
 define nofpclass(snan) float @sources_not_pzero(float nofpclass(pzero) %not.pzero0, float nofpclass(pzero) %not.pzero1) {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_pzero(
 ; CHECK-SAME: float nofpclass(pzero) [[NOT_PZERO0:%.*]], float nofpclass(pzero) [[NOT_PZERO1:%.*]]) {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_PZERO0]], float [[NOT_PZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.pzero0, float %not.pzero1)
@@ -723,7 +723,7 @@ define nofpclass(snan) float @rhs_not_nzero_lhs_not_pzero__dynamic(float nofpcla
 define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(float nofpclass(nzero nsub) %not.nzero0, float nofpclass(nzero nsub) %not.nzero1) #1 {
 ; CHECK-LABEL: define nofpclass(snan) float @sources_not_nzero_nsub__dynamic(
 ; CHECK-SAME: float nofpclass(nzero nsub) [[NOT_NZERO0:%.*]], float nofpclass(nzero nsub) [[NOT_NZERO1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[NOT_NZERO0]], float [[NOT_NZERO1]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %result = call float @llvm.maximum.f32(float %not.nzero0, float %not.nzero1)
@@ -916,7 +916,7 @@ define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_positive__maximum__only_pzero() {
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.positive = call float @returns_positive()
@@ -929,7 +929,7 @@ define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_pzero__maximum__known_positive() {
 ; CHECK-NEXT:    [[KNOWN_PZERO:%.*]] = call float @returns_pzero()
 ; CHECK-NEXT:    [[KNOWN_POSITIVE:%.*]] = call float @returns_positive()
-; CHECK-NEXT:    [[RESULT:%.*]] = call nnan float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan nsz float @llvm.maximum.f32(float [[KNOWN_POSITIVE]], float 0.000000e+00)
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.pzero = call float @returns_pzero()
@@ -1018,7 +1018,7 @@ define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-LABEL: define nofpclass(snan) float @known_negative__maximum__only_nzero_or_nan() {
 ; CHECK-NEXT:    [[KNOWN_NEGATIVE:%.*]] = call float @returns_negative()
 ; CHECK-NEXT:    [[KNOWN_NZERO_OR_NAN:%.*]] = call float @returns_nzero_or_nan()
-; CHECK-NEXT:    [[RESULT:%.*]] = call float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
+; CHECK-NEXT:    [[RESULT:%.*]] = call nsz float @llvm.maximum.f32(float [[KNOWN_NEGATIVE]], float [[KNOWN_NZERO_OR_NAN]])
 ; CHECK-NEXT:    ret float [[RESULT]]
 ;
   %known.negative = call float @returns_negative()
@@ -1031,7 +1031,7 @@ define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-LABEL: define nofpclass(snan) float @only_nzero_or_nan__maximum__known_negative() {
 ; CHECK-NEXT:    [[KNOWN_NZERO_O...
[truncated]

@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch from 8cd331d to 7deebfa Compare December 29, 2025 22:29
@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch 2 times, most recently from 9df16ea to d72a2f9 Compare December 30, 2025 11:11
@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch from 7deebfa to 2ba4cb9 Compare December 30, 2025 11:11
@github-actions
Copy link

github-actions bot commented Dec 30, 2025

🪟 Windows x64 Test Results

  • 129053 tests passed
  • 2841 tests skipped

✅ The build succeeded and all tests passed.

@github-actions
Copy link

github-actions bot commented Dec 30, 2025

🐧 Linux x64 Test Results

  • 188031 tests passed
  • 4988 tests skipped

✅ The build succeeded and all tests passed.

@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch from d72a2f9 to 89561bd Compare December 30, 2025 18:40
@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch 2 times, most recently from 3b3f609 to dd20a77 Compare December 30, 2025 18:46
@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch from 89561bd to e89e4a6 Compare December 30, 2025 18:46
@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch from dd20a77 to 9744178 Compare December 30, 2025 21:46
@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch from e89e4a6 to 10f04e1 Compare December 30, 2025 21:46
@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch from 9744178 to 9550f8e Compare December 31, 2025 09:45
@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch from 10f04e1 to f1624b0 Compare December 31, 2025 09:45
Copy link
Member

@dtcxzyw dtcxzyw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…dFPClass

Alive isn't particularly happy with this in the case where
one of the inputs could be zero, but I think
it's wrong: https://alive2.llvm.org/ce/z/dF7V6k

nsz shouldn't permit introducing a -0 result where
there wasn't one in the input here.
@arsenm arsenm force-pushed the users/arsenm/instcombine-simplify-demanded-fpclass-maximum-introduce-nsz-flag branch from f1624b0 to e23c934 Compare January 5, 2026 10:04
@arsenm arsenm force-pushed the users/arsenm/instcombine/simplify-demanded-fpclass-minimum-maximum branch from 9550f8e to 4eb7778 Compare January 5, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

floating-point Floating-point math llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:support llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants