[llvm] [ValueTracking] Fix computeKnownFPClass handling of nsz (PR #186315)
Yunbo Ni via llvm-commits
llvm-commits at lists.llvm.org
Sat Apr 11 05:21:51 PDT 2026
https://github.com/cardigan1008 updated https://github.com/llvm/llvm-project/pull/186315
>From d99350053e15bd8be83c52fcb1049351aacf0779 Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Fri, 13 Mar 2026 12:49:21 +0800
Subject: [PATCH 1/6] [ValueTracking] Improve nofpclass inference for nsz fadd
---
llvm/lib/Analysis/ValueTracking.cpp | 7 +++++--
llvm/test/Transforms/Attributor/nofpclass.ll | 11 +++++++++++
2 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 0672dc889b640..eab514892fc1a 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5587,8 +5587,11 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
Op->getType()->getScalarType()->getFltSemantics();
DenormalMode Mode =
F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
-
- if (Self && Opc == Instruction::FAdd) {
+
+ const FPMathOperator *FPop = cast<FPMathOperator>(Op);
+ bool HasNSZ = FPop->hasNoSignedZeros();
+
+ if (!HasNSZ && Self && Opc == Instruction::FAdd) {
Known = KnownFPClass::fadd_self(KnownLHS, Mode);
} else {
// RHS is canonically cheaper to compute. Skip inspecting the LHS if
diff --git a/llvm/test/Transforms/Attributor/nofpclass.ll b/llvm/test/Transforms/Attributor/nofpclass.ll
index 507d0f3ff98f3..3946c9f88e41d 100644
--- a/llvm/test/Transforms/Attributor/nofpclass.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass.ll
@@ -3326,6 +3326,17 @@ define float @fadd_double_known_negative_nonsub_dynamic(float noundef nofpclass(
ret float %add
}
+define float @fadd_double_known_negative_zero_nsz(float noundef nofpclass(ninf pzero sub nnorm) %arg) {
+; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
+; CHECK-LABEL: define noundef nofpclass(ninf nsub nnorm) float @fadd_double_known_negative_zero_nsz
+; CHECK-SAME: (float noundef nofpclass(ninf pzero sub nnorm) [[ARG:%.*]]) #[[ATTR3]] {
+; CHECK-NEXT: [[ADD:%.*]] = fadd nsz float [[ARG]], [[ARG]]
+; CHECK-NEXT: ret float [[ADD]]
+;
+ %add = fadd nsz float %arg, %arg
+ ret float %add
+}
+
define float @fsub_self(float noundef %arg) {
; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define noundef float @fsub_self
>From db4d943ca5ae1fbc757cad71bb43f3b6f6dbb011 Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Fri, 13 Mar 2026 12:50:10 +0800
Subject: [PATCH 2/6] [ValueTracking] Fix format issues
---
llvm/lib/Analysis/ValueTracking.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index eab514892fc1a..25a8a8166d3af 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5587,10 +5587,10 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
Op->getType()->getScalarType()->getFltSemantics();
DenormalMode Mode =
F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
-
- const FPMathOperator *FPop = cast<FPMathOperator>(Op);
+
+ const FPMathOperator *FPop = cast<FPMathOperator>(Op);
bool HasNSZ = FPop->hasNoSignedZeros();
-
+
if (!HasNSZ && Self && Opc == Instruction::FAdd) {
Known = KnownFPClass::fadd_self(KnownLHS, Mode);
} else {
>From 5cf73a400c422bcfeefcca7f15e67a26c5580c37 Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Fri, 10 Apr 2026 16:48:55 +0800
Subject: [PATCH 3/6] [ValueTracking] Handle nsz more generically
---
llvm/lib/Analysis/ValueTracking.cpp | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 25a8a8166d3af..edd96ac264614 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5588,10 +5588,7 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
DenormalMode Mode =
F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
- const FPMathOperator *FPop = cast<FPMathOperator>(Op);
- bool HasNSZ = FPop->hasNoSignedZeros();
-
- if (!HasNSZ && Self && Opc == Instruction::FAdd) {
+ if (Self && Opc == Instruction::FAdd) {
Known = KnownFPClass::fadd_self(KnownLHS, Mode);
} else {
// RHS is canonically cheaper to compute. Skip inspecting the LHS if
@@ -6070,6 +6067,20 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
default:
break;
}
+
+ // With no-signed-zeros semantics, +0 and -0 are interchangeable.
+ // If the operation has nsz and only one sign of zero is possible in the result,
+ // the other must also be considered possible.
+ if (const auto *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
+ if (FPOp->hasNoSignedZeros()) {
+ FPClassTest KnownZero = Known.KnownFPClasses & fcZero;
+ if (KnownZero && KnownZero != fcZero) {
+ Known.KnownFPClasses |= fcZero;
+ // The sign of zero is now indeterminate since nsz allows either sign.
+ Known.SignBit = std::nullopt;
+ }
+ }
+ }
}
KnownFPClass llvm::computeKnownFPClass(const Value *V,
>From f28f81f9fdf62b2a2c061436233d1941439664aa Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Fri, 10 Apr 2026 16:49:28 +0800
Subject: [PATCH 4/6] [ValueTracking] Fix format issues
---
llvm/lib/Analysis/ValueTracking.cpp | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index edd96ac264614..c530230a8d840 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -6068,9 +6068,9 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
break;
}
- // With no-signed-zeros semantics, +0 and -0 are interchangeable.
- // If the operation has nsz and only one sign of zero is possible in the result,
- // the other must also be considered possible.
+ // With no-signed-zeros semantics, +0 and -0 are interchangeable.
+ // If the operation has nsz and only one sign of zero is possible in the
+ // result, the other must also be considered possible.
if (const auto *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
if (FPOp->hasNoSignedZeros()) {
FPClassTest KnownZero = Known.KnownFPClasses & fcZero;
>From 2a2fa1cd47c022d1d0c245077cea405e728d5f57 Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Sat, 11 Apr 2026 18:47:57 +0800
Subject: [PATCH 5/6] [ValueTracking] Handle DAZ and exclude ops not appliable
to nsz
---
llvm/lib/Analysis/ValueTracking.cpp | 68 +++++++++++++++++++++++------
1 file changed, 54 insertions(+), 14 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index c530230a8d840..1623df5d108b1 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -4982,6 +4982,31 @@ static bool isAbsoluteValueLessEqualOne(const Value *V) {
return match(V, m_Intrinsic<Intrinsic::amdgcn_trig_preop>(m_Value()));
}
+static bool shouldApplyNSZToResult(const Operator *Op) {
+ switch (Op->getOpcode()) {
+ case Instruction::FNeg:
+ case Instruction::Select:
+ case Instruction::PHI:
+ return false;
+ case Instruction::Call:
+ if (const auto *II = dyn_cast<IntrinsicInst>(Op)) {
+ switch (II->getIntrinsicID()) {
+ case Intrinsic::fabs:
+ case Intrinsic::copysign:
+ case Intrinsic::ssa_copy:
+ return false;
+ default:
+ break;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
FPClassTest InterestedClasses, KnownFPClass &Known,
const SimplifyQuery &Q, unsigned Depth) {
@@ -5081,11 +5106,24 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
KnownNotFromFlags |= Arg->getNoFPClass();
const Operator *Op = dyn_cast<Operator>(V);
+ bool HasNoSignedZeros = false;
+ DenormalMode Mode = DenormalMode::getDynamic();
if (const FPMathOperator *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
if (FPOp->hasNoNaNs())
KnownNotFromFlags |= fcNan;
if (FPOp->hasNoInfs())
KnownNotFromFlags |= fcInf;
+ HasNoSignedZeros =
+ FPOp->hasNoSignedZeros() && shouldApplyNSZToResult(Op);
+ if (HasNoSignedZeros) {
+ if (const auto *I = dyn_cast<Instruction>(Op)) {
+ const Function *F = I->getFunction();
+ const fltSemantics &FltSem =
+ Op->getType()->getScalarType()->getFltSemantics();
+ Mode =
+ F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
+ }
+ }
}
KnownFPClass AssumedClasses = computeKnownFPClassFromContext(V, Q);
@@ -5096,6 +5134,22 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
InterestedClasses &= ~KnownNotFromFlags;
llvm::scope_exit ClearClassesFromFlags([=, &Known] {
+ // With no-signed-zeros semantics, +0 and -0 are interchangeable.
+ // If the operation has nsz and only one sign of zero is possible in the
+ // result, the other must also be considered possible.
+ if (HasNoSignedZeros) {
+ bool NeverPosZero = Known.isKnownNeverLogicalPosZero(Mode);
+ bool NeverNegZero = Known.isKnownNeverLogicalNegZero(Mode);
+ if (NeverPosZero != NeverNegZero) {
+ if (!NeverPosZero)
+ Known.KnownFPClasses |= fcPosZero | fcPosSubnormal;
+ if (!NeverNegZero)
+ Known.KnownFPClasses |= fcNegZero | fcNegSubnormal;
+ // The sign of zero is now indeterminate since nsz allows either sign.
+ Known.SignBit = std::nullopt;
+ }
+ }
+
Known.knownNot(KnownNotFromFlags);
if (!Known.SignBit && AssumedClasses.SignBit) {
if (*AssumedClasses.SignBit)
@@ -6067,20 +6121,6 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
default:
break;
}
-
- // With no-signed-zeros semantics, +0 and -0 are interchangeable.
- // If the operation has nsz and only one sign of zero is possible in the
- // result, the other must also be considered possible.
- if (const auto *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
- if (FPOp->hasNoSignedZeros()) {
- FPClassTest KnownZero = Known.KnownFPClasses & fcZero;
- if (KnownZero && KnownZero != fcZero) {
- Known.KnownFPClasses |= fcZero;
- // The sign of zero is now indeterminate since nsz allows either sign.
- Known.SignBit = std::nullopt;
- }
- }
- }
}
KnownFPClass llvm::computeKnownFPClass(const Value *V,
>From 49f3e5e3285dd4771d7cb9f4dcccd3ab12286dfe Mon Sep 17 00:00:00 2001
From: cardigan1008 <ybni at cse.cuhk.edu.hk>
Date: Sat, 11 Apr 2026 20:20:44 +0800
Subject: [PATCH 6/6] [ValueTracking] Move check after inference and add tests
---
llvm/lib/Analysis/ValueTracking.cpp | 72 +++++++++-----------
llvm/test/Transforms/Attributor/nofpclass.ll | 11 +++
2 files changed, 42 insertions(+), 41 deletions(-)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 1623df5d108b1..86021caec9ca0 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -4982,24 +4982,19 @@ static bool isAbsoluteValueLessEqualOne(const Value *V) {
return match(V, m_Intrinsic<Intrinsic::amdgcn_trig_preop>(m_Value()));
}
+/// \return true if result-side NSZ relaxation should be applied to this
+/// opcode.
+///
+/// With the stricter NSZ interpretation, NSZ only relaxes input zero sign.
+/// Do not relax opcodes whose output zero sign is fixed or sign-preserving
+/// (for example, fabs, copysign, and fneg).
static bool shouldApplyNSZToResult(const Operator *Op) {
switch (Op->getOpcode()) {
case Instruction::FNeg:
case Instruction::Select:
case Instruction::PHI:
- return false;
case Instruction::Call:
- if (const auto *II = dyn_cast<IntrinsicInst>(Op)) {
- switch (II->getIntrinsicID()) {
- case Intrinsic::fabs:
- case Intrinsic::copysign:
- case Intrinsic::ssa_copy:
- return false;
- default:
- break;
- }
- }
- break;
+ return false;
default:
break;
}
@@ -5106,24 +5101,11 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
KnownNotFromFlags |= Arg->getNoFPClass();
const Operator *Op = dyn_cast<Operator>(V);
- bool HasNoSignedZeros = false;
- DenormalMode Mode = DenormalMode::getDynamic();
if (const FPMathOperator *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
if (FPOp->hasNoNaNs())
KnownNotFromFlags |= fcNan;
if (FPOp->hasNoInfs())
KnownNotFromFlags |= fcInf;
- HasNoSignedZeros =
- FPOp->hasNoSignedZeros() && shouldApplyNSZToResult(Op);
- if (HasNoSignedZeros) {
- if (const auto *I = dyn_cast<Instruction>(Op)) {
- const Function *F = I->getFunction();
- const fltSemantics &FltSem =
- Op->getType()->getScalarType()->getFltSemantics();
- Mode =
- F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
- }
- }
}
KnownFPClass AssumedClasses = computeKnownFPClassFromContext(V, Q);
@@ -5134,22 +5116,6 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
InterestedClasses &= ~KnownNotFromFlags;
llvm::scope_exit ClearClassesFromFlags([=, &Known] {
- // With no-signed-zeros semantics, +0 and -0 are interchangeable.
- // If the operation has nsz and only one sign of zero is possible in the
- // result, the other must also be considered possible.
- if (HasNoSignedZeros) {
- bool NeverPosZero = Known.isKnownNeverLogicalPosZero(Mode);
- bool NeverNegZero = Known.isKnownNeverLogicalNegZero(Mode);
- if (NeverPosZero != NeverNegZero) {
- if (!NeverPosZero)
- Known.KnownFPClasses |= fcPosZero | fcPosSubnormal;
- if (!NeverNegZero)
- Known.KnownFPClasses |= fcNegZero | fcNegSubnormal;
- // The sign of zero is now indeterminate since nsz allows either sign.
- Known.SignBit = std::nullopt;
- }
- }
-
Known.knownNot(KnownNotFromFlags);
if (!Known.SignBit && AssumedClasses.SignBit) {
if (*AssumedClasses.SignBit)
@@ -6121,6 +6087,30 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
default:
break;
}
+
+ // With no-signed-zeros semantics, +0 and -0 are interchangeable.
+ // If only one sign of logical zero is possible in the result, the other
+ // sign must also be considered possible. Apply this selectively because
+ // some ops preserve or explicitly determine the zero sign.
+ if (const auto *FPOp = dyn_cast_or_null<FPMathOperator>(Op)) {
+ if (FPOp->hasNoSignedZeros() && shouldApplyNSZToResult(Op)) {
+ const auto *I = dyn_cast<Instruction>(Op);
+ const Function *F = I ? I->getFunction() : nullptr;
+ const fltSemantics &FltSem =
+ Op->getType()->getScalarType()->getFltSemantics();
+ DenormalMode Mode =
+ F ? F->getDenormalMode(FltSem) : DenormalMode::getDynamic();
+ bool NeverPosZero = Known.isKnownNeverLogicalPosZero(Mode);
+ bool NeverNegZero = Known.isKnownNeverLogicalNegZero(Mode);
+ if (NeverPosZero != NeverNegZero) {
+ if (NeverPosZero)
+ Known.KnownFPClasses |= fcPosZero | fcPosSubnormal;
+ if (NeverNegZero)
+ Known.KnownFPClasses |= fcNegZero | fcNegSubnormal;
+ Known.SignBit = std::nullopt;
+ }
+ }
+ }
}
KnownFPClass llvm::computeKnownFPClass(const Value *V,
diff --git a/llvm/test/Transforms/Attributor/nofpclass.ll b/llvm/test/Transforms/Attributor/nofpclass.ll
index 3946c9f88e41d..a2c82146b3daf 100644
--- a/llvm/test/Transforms/Attributor/nofpclass.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass.ll
@@ -3337,6 +3337,17 @@ define float @fadd_double_known_negative_zero_nsz(float noundef nofpclass(ninf p
ret float %add
}
+define float @fadd_double_known_negative_zero_nsz_daz(float noundef nofpclass(ninf pzero sub nnorm) %arg) #0 {
+; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn denormal_fpenv(preservesign) memory(none)
+; CHECK-LABEL: define noundef nofpclass(ninf pzero nsub nnorm) float @fadd_double_known_negative_zero_nsz_daz
+; CHECK-SAME: (float noundef nofpclass(ninf pzero sub nnorm) [[ARG:%.*]]) #[[ATTR10]] {
+; CHECK-NEXT: [[ADD:%.*]] = fadd nsz float [[ARG]], [[ARG]]
+; CHECK-NEXT: ret float [[ADD]]
+;
+ %add = fadd nsz float %arg, %arg
+ ret float %add
+}
+
define float @fsub_self(float noundef %arg) {
; CHECK: Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none)
; CHECK-LABEL: define noundef float @fsub_self
More information about the llvm-commits
mailing list