[llvm] [KnownFPClass] Refine known classes for `atan2(y, x)` when `x` is negative (PR #214629)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 20 19:53:23 PDT 2026
https://github.com/ZERICO2005 updated https://github.com/llvm/llvm-project/pull/214629
>From 0c41d34b8532ef1110cec189164cc1600d846539 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 | 25 +++--
.../CodeGen/GlobalISel/GISelValueTracking.cpp | 15 ++-
llvm/lib/Support/KnownFPClass.cpp | 20 +++-
.../Transforms/Attributor/nofpclass-atan2.ll | 98 +++++++++++++++++++
.../Transforms/Attributor/nofpclass-trig.ll | 32 ------
.../simplify-demanded-fpclass-atan2.ll | 30 ++++++
7 files changed, 179 insertions(+), 46 deletions(-)
create mode 100644 llvm/test/Transforms/Attributor/nofpclass-atan2.ll
create mode 100644 llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-atan2.ll
diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index dadf4133d826b..a126838c77195 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 55f6696fe4914..ef822346a7a62 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5425,12 +5425,25 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
break;
}
case Intrinsic::atan2: {
- KnownFPClass KnownLHS, KnownRHS;
- computeKnownFPClass(II->getArgOperand(0), DemandedElts, InterestedClasses,
- KnownLHS, Q, Depth + 1);
- computeKnownFPClass(II->getArgOperand(1), DemandedElts, InterestedClasses,
- KnownRHS, Q, Depth + 1);
- Known = KnownFPClass::atan2(KnownLHS, KnownRHS);
+ FPClassTest InterestedY = InterestedClasses;
+ FPClassTest InterestedX = InterestedClasses;
+
+ // We can rule out zero and subnormal if x cannot have a positive value.
+ if ((InterestedClasses & (fcZero | fcSubnormal)) != fcNone)
+ InterestedX |= fcPositive | fcNegSubnormal;
+
+ KnownFPClass KnownY, KnownX;
+ computeKnownFPClass(II->getArgOperand(0), DemandedElts, InterestedY,
+ KnownY, Q, Depth + 1);
+ computeKnownFPClass(II->getArgOperand(1), DemandedElts, InterestedX,
+ KnownX, Q, Depth + 1);
+
+ const Function *F = II->getFunction();
+ DenormalMode Mode =
+ F ? F->getDenormalMode(
+ II->getType()->getScalarType()->getFltSemantics())
+ : DenormalMode::getDynamic();
+ Known = KnownFPClass::atan2(KnownY, KnownX, Mode);
break;
}
case Intrinsic::maxnum:
diff --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index f5b8cb3a0fbd7..ad1b02a75a776 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -1409,12 +1409,21 @@ void GISelValueTracking::computeKnownFPClass(Register R,
break;
}
case TargetOpcode::G_FATAN2: {
+ FPClassTest InterestedY = InterestedClasses;
+ FPClassTest InterestedX = InterestedClasses;
+
+ // We can rule out zero and subnormal if x cannot have a positive value.
+ if ((InterestedClasses & (fcZero | fcSubnormal)) != fcNone)
+ InterestedX |= fcPositive | fcNegSubnormal;
+
Register Y = MI.getOperand(1).getReg();
Register X = MI.getOperand(2).getReg();
KnownFPClass KnownY, KnownX;
- computeKnownFPClass(Y, DemandedElts, InterestedClasses, KnownY, Depth + 1);
- computeKnownFPClass(X, DemandedElts, InterestedClasses, KnownX, Depth + 1);
- Known = KnownFPClass::atan2(KnownY, KnownX);
+ computeKnownFPClass(Y, DemandedElts, InterestedY, KnownY, Depth + 1);
+ computeKnownFPClass(X, DemandedElts, InterestedX, KnownX, Depth + 1);
+ 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 ac9406ecd0280..9956b0e788569 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -696,14 +696,28 @@ 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);
- Known.propagateNonNaN(KnownLHS, KnownRHS);
+ Known.propagateNonNaN(KnownY, KnownX);
+
+ // 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..feb4a7075b435
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/nofpclass-atan2.ll
@@ -0,0 +1,98 @@
+; 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
+}
+
+define float @ret_atan2_nosnan(float nofpclass(snan) %arg0, float nofpclass(snan) %arg1) {
+; CHECK-LABEL: define nofpclass(snan inf) float @ret_atan2_nosnan(
+; CHECK-SAME: float nofpclass(snan) [[ARG0:%.*]], float nofpclass(snan) [[ARG1:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(snan inf) float @llvm.atan2.f32(float nofpclass(snan) [[ARG0]], float nofpclass(snan) [[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 a2d6a19975041..0308aa8c7ca27 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,34 +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
-}
-
-define float @ret_atan2_nosnan(float nofpclass(snan) %arg0, float nofpclass(snan) %arg1) {
-; CHECK-LABEL: define nofpclass(snan inf) float @ret_atan2_nosnan
-; CHECK-SAME: (float nofpclass(snan) [[ARG0:%.*]], float nofpclass(snan) [[ARG1:%.*]]) #[[ATTR1]] {
-; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(snan inf) float @llvm.atan2.f32(float nofpclass(snan) [[ARG0]], float nofpclass(snan) [[ARG1]]) #[[ATTR2]]
-; CHECK-NEXT: ret float [[CALL]]
-;
- %call = call float @llvm.atan2.f32(float %arg0, float %arg1)
- ret float %call
-}
diff --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-atan2.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-atan2.ll
new file mode 100644
index 0000000000000..47a6dd2b0d4a0
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-atan2.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+declare float @llvm.atan2.f32(float, float)
+declare float @llvm.fabs.f32(float)
+declare i1 @llvm.is.fpclass.f32(float, i32 immarg)
+
+define i1 @atan2_negative_x_eq_zero(float %y, float %x) {
+; CHECK-LABEL: define i1 @atan2_negative_x_eq_zero(
+; CHECK-SAME: float [[Y:%.*]], float [[X:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %abs_x = call float @llvm.fabs.f32(float %x)
+ %negative_x = fneg float %abs_x
+ %atan2 = call float @llvm.atan2.f32(float %y, float %negative_x)
+ %cmp = fcmp oeq float %atan2, 0.0
+ ret i1 %cmp
+}
+
+define i1 @atan2_negative_x_is_subnormal(float %y, float %x) {
+; CHECK-LABEL: define i1 @atan2_negative_x_is_subnormal(
+; CHECK-SAME: float [[Y:%.*]], float [[X:%.*]]) {
+; CHECK-NEXT: ret i1 false
+;
+ %abs_x = call float @llvm.fabs.f32(float %x)
+ %negative_x = fneg float %abs_x
+ %atan2 = call float @llvm.atan2.f32(float %y, float %negative_x)
+ %class = call i1 @llvm.is.fpclass.f32(float %atan2, i32 144)
+ ret i1 %class
+}
More information about the llvm-commits
mailing list