[llvm] 3e2f4ca - [KnownFPClass] Refine known classes for `atan2(y, x)` when `x` is negative (#214629)

via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 26 02:40:21 PDT 2026


Author: ZERICO2005
Date: 2026-08-26T09:40:16Z
New Revision: 3e2f4cad8bc381bb856383663bf060352889c4f4

URL: https://github.com/llvm/llvm-project/commit/3e2f4cad8bc381bb856383663bf060352889c4f4
DIFF: https://github.com/llvm/llvm-project/commit/3e2f4cad8bc381bb856383663bf060352889c4f4.diff

LOG: [KnownFPClass] Refine known classes for `atan2(y, x)` when `x` is negative (#214629)

`|atan2(y, x)| >= pi/2` when `x` is negative, which allows us to deduce
that the result cannot be zero or subnormal.

There are two possible deductions that I did not add since they rely on
one of the arguments being a known infinity/NaN. If we can determine
that one of the arguments is infinity/NaN at compile time, then the code
is probably erroneous:
- `atan2(y, inf)` is not subnormal
- `atan2(inf, x)` is not zero or subnormal

Also moved `atan2` tests to their own file.

AI disclosure:
I used OpenAI Codex (GPT-5.6-terra and GPT-5.6-sol) to help generate the
test updates and to retrieve the denormal-mode, which I reviewed and
tested locally.

Added: 
    llvm/test/Transforms/Attributor/nofpclass-atan2.ll
    llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-atan2.ll

Modified: 
    llvm/include/llvm/Support/KnownFPClass.h
    llvm/lib/Analysis/ValueTracking.cpp
    llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
    llvm/lib/Support/KnownFPClass.cpp
    llvm/test/Transforms/Attributor/nofpclass-trig.ll
    llvm/unittests/Analysis/ValueTrackingTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index 4f1c28a87ba2e..fd9e68b2475bb 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -344,8 +344,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 66fde465ea761..1eafd4df1a7c5 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5427,12 +5427,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 0f2595015efc7..e4de12a80ee3d 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 525f85100b34e..2680abaddfc12 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -736,14 +736,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 1a1ade239ce59..b164d342f9340 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) {
@@ -228,34 +227,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
+}

diff  --git a/llvm/unittests/Analysis/ValueTrackingTest.cpp b/llvm/unittests/Analysis/ValueTrackingTest.cpp
index 5356dd07d42e4..a969b8fdd866f 100644
--- a/llvm/unittests/Analysis/ValueTrackingTest.cpp
+++ b/llvm/unittests/Analysis/ValueTrackingTest.cpp
@@ -1992,6 +1992,27 @@ TEST_F(ComputeKnownFPClassTest, PowiInfSecond) {
   expectKnownFPClass(fcAllFlags, std::nullopt, A7);
 }
 
+TEST_F(ComputeKnownFPClassTest, Atan2DemandXSign) {
+  parseAssembly("declare float @llvm.atan2.f32(float, float)\n"
+                "declare float @llvm.fabs.f32(float)\n"
+                "define float @test(float %y, float %x) #0 {\n"
+                "  %abs = call float @llvm.fabs.f32(float %x)\n"
+                "  %sum = fadd float %abs, 1.0\n"
+                "  %negative = fneg float %sum\n"
+                "  %A = call float @llvm.atan2.f32(float %y, float %negative)\n"
+                "  ret float %A\n"
+                "}\n"
+                "attributes #0 = { \"denormal-fp-math\"=\"ieee,ieee\" }\n");
+  // atan2(y, -(|x| + 1.0))
+  // Note that -(|x| + 1.0) is never positive, zero, or subnormal.
+  // atan2(y, negative_x) is never zero or subnormal. But we need to know that
+  // x is never positive or a negative subnormal to make this deduction. Which
+  // requires us to pass more than just InterestedClasses, which is the purpose
+  // of this test.
+  KnownFPClass Known = computeKnownFPClass(A, M->getDataLayout(), fcPosZero);
+  EXPECT_EQ(fcNan | fcNormal, Known.KnownFPClasses);
+}
+
 TEST_F(ComputeKnownFPClassTest, Phi) {
   parseAssembly(
       "define float @test(i1 %cond, float nofpclass(nan inf) %arg0, float nofpclass(nan) %arg1) {\n"


        


More information about the llvm-commits mailing list