[llvm] [KnownFPClass] Refine known classes for `atan2(y, x)` when `x` is negative (PR #214629)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Aug 10 20:47:06 PDT 2026
https://github.com/ZERICO2005 updated https://github.com/llvm/llvm-project/pull/214629
>From 5f2699703aa77105b75721331ca010b9f4af4d3f Mon Sep 17 00:00:00 2001
From: zerico <zerico2005 at gmail.com>
Date: Mon, 10 Aug 2026 20:06:37 -0600
Subject: [PATCH] [KnownFPClass] Refine known classes for atan2(y, x) when x is
negative
---
llvm/include/llvm/Support/KnownFPClass.h | 5 +-
llvm/lib/Analysis/ValueTracking.cpp | 8 +-
.../CodeGen/GlobalISel/GISelValueTracking.cpp | 4 +-
llvm/lib/Support/KnownFPClass.cpp | 20 ++++-
.../Transforms/Attributor/nofpclass-atan2.ll | 88 +++++++++++++++++++
.../Transforms/Attributor/nofpclass-trig.ll | 22 -----
6 files changed, 118 insertions(+), 29 deletions(-)
create mode 100644 llvm/test/Transforms/Attributor/nofpclass-atan2.ll
diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index 956141b9449a2..bc4fd6ca8b95b 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -333,8 +333,9 @@ struct KnownFPClass {
LLVM_ABI static KnownFPClass atan(const KnownFPClass &Src);
/// Report known values for atan2
- LLVM_ABI static KnownFPClass atan2(const KnownFPClass &LHS,
- const KnownFPClass &RHS);
+ LLVM_ABI static KnownFPClass
+ atan2(const KnownFPClass &LHS, const KnownFPClass &RHS,
+ DenormalMode Mode = DenormalMode::getDynamic());
/// Return true if the sign bit must be 0, ignoring the sign of nans.
bool signBitIsZeroOrNaN() const { return isKnownNever(fcNegative); }
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 44a1240f5635a..7c793d82d3c8d 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5430,7 +5430,13 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
KnownLHS, Q, Depth + 1);
computeKnownFPClass(II->getArgOperand(1), DemandedElts, InterestedClasses,
KnownRHS, Q, Depth + 1);
- Known = KnownFPClass::atan2(KnownLHS, KnownRHS);
+
+ const Function *F = II->getFunction();
+ DenormalMode Mode =
+ F ? F->getDenormalMode(
+ II->getType()->getScalarType()->getFltSemantics())
+ : DenormalMode::getDynamic();
+ Known = KnownFPClass::atan2(KnownLHS, KnownRHS, Mode);
break;
}
case Intrinsic::maxnum:
diff --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index 7ff23d8e1bd6e..aac9426d662e3 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -1351,7 +1351,9 @@ void GISelValueTracking::computeKnownFPClass(Register R,
KnownFPClass KnownY, KnownX;
computeKnownFPClass(Y, DemandedElts, InterestedClasses, KnownY, Depth + 1);
computeKnownFPClass(X, DemandedElts, InterestedClasses, KnownX, Depth + 1);
- Known = KnownFPClass::atan2(KnownY, KnownX);
+ DenormalMode Mode =
+ MF->getDenormalMode(getFltSemanticForLLT(DstTy.getScalarType()));
+ Known = KnownFPClass::atan2(KnownY, KnownX, Mode);
break;
}
case TargetOpcode::G_FSINH: {
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index eccd83451a05f..3053965a2e40c 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -680,17 +680,31 @@ KnownFPClass KnownFPClass::atan(const KnownFPClass &KnownSrc) {
return Known;
}
-KnownFPClass KnownFPClass::atan2(const KnownFPClass &KnownLHS,
- const KnownFPClass &KnownRHS) {
+KnownFPClass KnownFPClass::atan2(const KnownFPClass &KnownY,
+ const KnownFPClass &KnownX,
+ DenormalMode Mode) {
KnownFPClass Known;
+ // Even though these deductions are correct, we are ignoring the following
+ // potentially erroneous cases:
+ // * atan2(y, inf) is not subnormal
+ // * atan2(inf, x) is not zero or subnormal
+
// atan2 result is in (-pi, pi], never Inf.
Known.knownNot(fcInf);
// NaN if either operand is NaN.
- if (KnownLHS.isKnownNeverNaN() && KnownRHS.isKnownNeverNaN())
+ if (KnownY.isKnownNeverNaN() && KnownX.isKnownNeverNaN())
Known.knownNot(fcNan);
+ // Negative subnormals could be treated like positive zero.
+ const bool XCannotHavePositiveValue = KnownX.isKnownNever(fcPositive) &&
+ KnownX.isKnownNeverLogicalPosZero(Mode);
+
+ // If x <= -0.0, then |atan2(y, x)| >= pi/2
+ if (XCannotHavePositiveValue)
+ Known.knownNot(fcZero | fcSubnormal);
+
return Known;
}
diff --git a/llvm/test/Transforms/Attributor/nofpclass-atan2.ll b/llvm/test/Transforms/Attributor/nofpclass-atan2.ll
new file mode 100644
index 0000000000000..6ff260980c270
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/nofpclass-atan2.ll
@@ -0,0 +1,88 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal -S < %s | FileCheck %s
+
+declare float @llvm.atan2.f32(float, float)
+
+; atan2 result is in (-pi, pi], never Inf.
+define float @ret_atan2(float %arg0, float %arg1) {
+; CHECK-LABEL: define nofpclass(inf) float @ret_atan2(
+; CHECK-SAME: float [[ARG0:%.*]], float [[ARG1:%.*]]) #[[ATTR1:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf) float @llvm.atan2.f32(float [[ARG0]], float [[ARG1]]) #[[ATTR6:[0-9]+]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.atan2.f32(float %arg0, float %arg1)
+ ret float %call
+}
+
+define float @ret_atan2_nonan(float nofpclass(nan) %arg0, float nofpclass(nan) %arg1) {
+; CHECK-LABEL: define nofpclass(nan inf) float @ret_atan2_nonan(
+; CHECK-SAME: float nofpclass(nan) [[ARG0:%.*]], float nofpclass(nan) [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf) float @llvm.atan2.f32(float nofpclass(nan) [[ARG0]], float nofpclass(nan) [[ARG1]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.atan2.f32(float %arg0, float %arg1)
+ ret float %call
+}
+
+; |atan2(y, x)| >= pi/2 when x is a negative normal or negative infinity.
+define float @ret_atan2_neg_normal_x(float %y, float nofpclass(nan pinf zero sub pnorm) %x) {
+; CHECK-LABEL: define nofpclass(inf zero sub) float @ret_atan2_neg_normal_x(
+; CHECK-SAME: float [[Y:%.*]], float nofpclass(nan pinf zero sub pnorm) [[X:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub) float @llvm.atan2.f32(float [[Y]], float nofpclass(nan pinf zero sub pnorm) [[X]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.atan2.f32(float %y, float %x)
+ ret float %call
+}
+
+; |atan2(y, x)| >= pi/2 when x <= -0.0, which tells us that the result will not
+; be zero or subnormal. However, a negative subnormal may be treated as a
+; logical positive zero depending on how the floating point environment handles
+; denormals. Here we make sure to test every input denormal-mode combination.
+define float @ret_atan2_neg_subnormal_x_ieee_ieee(
+; CHECK-LABEL: define nofpclass(inf zero sub) float @ret_atan2_neg_subnormal_x_ieee_ieee(
+; CHECK-SAME: float [[Y:%.*]], float nofpclass(nan inf zero psub norm) [[X:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub) float @llvm.atan2.f32(float [[Y]], float nofpclass(nan inf zero psub norm) [[X]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ float %y, float nofpclass(nan inf zero norm psub) %x)
+ denormal_fpenv(float: ieee|ieee) {
+ %call = call float @llvm.atan2.f32(float %y, float %x)
+ ret float %call
+}
+
+define float @ret_atan2_neg_subnormal_x_ieee_preservesign(
+; CHECK-LABEL: define nofpclass(inf zero sub) float @ret_atan2_neg_subnormal_x_ieee_preservesign(
+; CHECK-SAME: float [[Y:%.*]], float nofpclass(nan inf zero psub norm) [[X:%.*]]) #[[ATTR3:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub) float @llvm.atan2.f32(float [[Y]], float nofpclass(nan inf zero psub norm) [[X]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ float %y, float nofpclass(nan inf zero norm psub) %x)
+ denormal_fpenv(float: ieee|preservesign) {
+ %call = call float @llvm.atan2.f32(float %y, float %x)
+ ret float %call
+}
+
+define float @ret_atan2_neg_subnormal_x_ieee_positivezero(
+; CHECK-LABEL: define nofpclass(inf) float @ret_atan2_neg_subnormal_x_ieee_positivezero(
+; CHECK-SAME: float [[Y:%.*]], float nofpclass(nan inf zero psub norm) [[X:%.*]]) #[[ATTR4:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf) float @llvm.atan2.f32(float [[Y]], float nofpclass(nan inf zero psub norm) [[X]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ float %y, float nofpclass(nan inf zero norm psub) %x)
+ denormal_fpenv(float: ieee|positivezero) {
+ %call = call float @llvm.atan2.f32(float %y, float %x)
+ ret float %call
+}
+
+define float @ret_atan2_neg_subnormal_x_ieee_dynamic(
+; CHECK-LABEL: define nofpclass(inf) float @ret_atan2_neg_subnormal_x_ieee_dynamic(
+; CHECK-SAME: float [[Y:%.*]], float nofpclass(nan inf zero psub norm) [[X:%.*]]) #[[ATTR5:[0-9]+]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf) float @llvm.atan2.f32(float [[Y]], float nofpclass(nan inf zero psub norm) [[X]]) #[[ATTR6]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ float %y, float nofpclass(nan inf zero norm psub) %x)
+ denormal_fpenv(float: ieee|dynamic) {
+ %call = call float @llvm.atan2.f32(float %y, float %x)
+ ret float %call
+}
diff --git a/llvm/test/Transforms/Attributor/nofpclass-trig.ll b/llvm/test/Transforms/Attributor/nofpclass-trig.ll
index c832432d42e31..d94c294738302 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-trig.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-trig.ll
@@ -8,7 +8,6 @@ declare float @llvm.tanh.f32(float)
declare float @llvm.asin.f32(float)
declare float @llvm.acos.f32(float)
declare float @llvm.atan.f32(float)
-declare float @llvm.atan2.f32(float, float)
; tan never returns Inf (tan(+-Inf) = NaN; tan(finite) = finite).
define float @ret_tan(float %arg) {
@@ -197,24 +196,3 @@ define float @ret_atan_nonan(float nofpclass(nan) %arg) {
%call = call float @llvm.atan.f32(float %arg)
ret float %call
}
-
-; atan2 result is in (-pi, pi], never Inf.
-define float @ret_atan2(float %arg0, float %arg1) {
-; CHECK-LABEL: define nofpclass(inf) float @ret_atan2
-; CHECK-SAME: (float [[ARG0:%.*]], float [[ARG1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf) float @llvm.atan2.f32(float [[ARG0]], float [[ARG1]]) #[[ATTR2]]
-; CHECK-NEXT: ret float [[CALL]]
-;
- %call = call float @llvm.atan2.f32(float %arg0, float %arg1)
- ret float %call
-}
-
-define float @ret_atan2_nonan(float nofpclass(nan) %arg0, float nofpclass(nan) %arg1) {
-; CHECK-LABEL: define nofpclass(nan inf) float @ret_atan2_nonan
-; CHECK-SAME: (float nofpclass(nan) [[ARG0:%.*]], float nofpclass(nan) [[ARG1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan inf) float @llvm.atan2.f32(float nofpclass(nan) [[ARG0]], float nofpclass(nan) [[ARG1]]) #[[ATTR2]]
-; CHECK-NEXT: ret float [[CALL]]
-;
- %call = call float @llvm.atan2.f32(float %arg0, float %arg1)
- ret float %call
-}
More information about the llvm-commits
mailing list