[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