[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