[llvm] a8482df - [KnownFPClass] Fix `KnownFPClass::log` deductions for ppcf128 (#223296)

via llvm-commits llvm-commits at lists.llvm.org
Sun Oct 11 10:15:10 PDT 2026


Author: Eric Ross
Date: 2026-10-11T17:15:04Z
New Revision: a8482df7df5bbffc2ed654c664c009f8e2ccb0e4

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

LOG: [KnownFPClass] Fix `KnownFPClass::log` deductions for ppcf128 (#223296)

Fixes https://github.com/llvm/llvm-project/issues/217658

AI Disclosure:
I used ChatGPT Codex (5.6 sol) and Claude (Opus 5) to help implement the
changes and write tests.

Added: 
    llvm/test/Transforms/Attributor/nofpclass-multiunitfptype.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/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
    llvm/test/Transforms/Attributor/nofpclass-log.ll
    llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-log.ll
    llvm/unittests/CodeGen/GlobalISel/KnownFPClassTest.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index 63f697ce20a12..03c28fcc42e44 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -337,8 +337,10 @@ struct KnownFPClass {
   sqrt(const KnownFPClass &Src, DenormalMode Mode = DenormalMode::getDynamic());
 
   /// Propagate known class for log/log2/log10
-  LLVM_ABI static KnownFPClass
-  log(const KnownFPClass &Src, DenormalMode Mode = DenormalMode::getDynamic());
+  ///
+  /// \p IsMultiUnitFPType if this is for a multi-unit floating-point type.
+  LLVM_ABI static KnownFPClass log(const KnownFPClass &Src, DenormalMode Mode,
+                                   bool IsMultiUnitFPType);
 
   /// Report known values for exp, exp2 and exp10.
   /// This function assumes that exp10(-1.0) = +0.1 and exp10(+1.0) = +10.0 are

diff  --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index cf0e04d671cfc..1cb2f2eb80a13 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5757,7 +5757,9 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
           F ? F->getDenormalMode(
                   II->getType()->getScalarType()->getFltSemantics())
             : DenormalMode::getDynamic();
-      Known = KnownFPClass::log(KnownSrc, Mode);
+      const bool IsMultiUnitFPType =
+          V->getType()->getScalarType()->isMultiUnitFPType();
+      Known = KnownFPClass::log(KnownSrc, Mode, IsMultiUnitFPType);
       break;
     }
     case Intrinsic::pow: {

diff  --git a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
index 24cf0fef18420..3bd31d8227862 100644
--- a/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/GISelValueTracking.cpp
@@ -1816,7 +1816,8 @@ void GISelValueTracking::computeKnownFPClass(Register R,
     LLT Ty = MRI.getType(Val).getScalarType();
     const fltSemantics &FltSem = getFltSemanticForLLT(Ty);
     DenormalMode Mode = MF->getDenormalMode(FltSem);
-    Known = KnownFPClass::log(KnownSrc, Mode);
+    bool IsMultiUnitFPType = &FltSem == &APFloat::PPCDoubleDouble();
+    Known = KnownFPClass::log(KnownSrc, Mode, IsMultiUnitFPType);
     break;
   }
   case TargetOpcode::G_FPOW: {

diff  --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index b491c82cd0a16..e69cb55d174be 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -786,10 +786,14 @@ void KnownFPClass::propagateCanonicalizingSrc(const KnownFPClass &Src,
   propagateNonNaN(Src);
 }
 
-KnownFPClass KnownFPClass::log(const KnownFPClass &KnownSrc,
-                               DenormalMode Mode) {
+KnownFPClass KnownFPClass::log(const KnownFPClass &KnownSrc, DenormalMode Mode,
+                               bool IsMultiUnitFPType) {
   KnownFPClass Known;
-  Known.knownNot(fcNegZero | fcSubnormal);
+
+  // PPCDoubleDouble can represent the exact value of 1.0 + subnormal, and
+  // log(1.0 + subnormal) = subnormal.
+  if (!IsMultiUnitFPType)
+    Known.knownNot(fcNegZero | fcSubnormal);
 
   Known.propagateNonSNaN(KnownSrc);
 

diff  --git a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
index 19d0ed8dd4c23..1c0b5af5594d3 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineSimplifyDemanded.cpp
@@ -3187,13 +3187,24 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Instruction *I,
     case Intrinsic::log:
     case Intrinsic::log2:
     case Intrinsic::log10: {
+      Type *EltTy = VTy->getScalarType();
+      bool IsMultiUnitFPType = EltTy->isMultiUnitFPType();
+
       FPClassTest DemandedSrcMask = DemandedMask & (fcNan | fcPosInf);
       if (DemandedMask & fcNan)
         DemandedSrcMask |= fcNan;
 
-      Type *EltTy = VTy->getScalarType();
       DenormalMode Mode = F.getDenormalMode(EltTy->getFltSemantics());
 
+      // log(x) can be negative zero or subnormal if x is close to +1.0 for the
+      // PPCDoubleDouble type.
+      if (IsMultiUnitFPType && (DemandedMask & (fcNegZero | fcSubnormal))) {
+        DemandedSrcMask |= fcPosNormal;
+        // Note that DoubleAPFloat::isDenormal considers 1.0 + DBL_TRUE_MIN to
+        // be denormal, which APFloat::classify treats as fcSubnormal.
+        DemandedSrcMask |= fcPosSubnormal;
+      }
+
       // log(x < 0) = nan
       if (DemandedMask & fcNan)
         DemandedSrcMask |= (fcNegative & ~fcNegZero);
@@ -3202,7 +3213,7 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Instruction *I,
       if (DemandedMask & fcNegInf) {
         DemandedSrcMask |= fcZero;
 
-        // No value produces subnormal result.
+        // Subnormals may be treated as logical zero.
         if (Mode.inputsMayBeZero())
           DemandedSrcMask |= fcSubnormal;
       }
@@ -3219,7 +3230,7 @@ Value *InstCombinerImpl::SimplifyDemandedUseFPClass(Instruction *I,
                                   Depth + 1))
         return I;
 
-      Known = KnownFPClass::log(KnownSrc, Mode);
+      Known = KnownFPClass::log(KnownSrc, Mode, IsMultiUnitFPType);
       Known.knownNot(~DemandedMask);
 
       return simplifyDemandedFPClassResult(CI, FMF, DemandedMask, Known,

diff  --git a/llvm/test/Transforms/Attributor/nofpclass-log.ll b/llvm/test/Transforms/Attributor/nofpclass-log.ll
index faaa3725fbbe4..aabe69ee1c86c 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-log.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-log.ll
@@ -365,6 +365,29 @@ define float @ret_log10_noinf_noneg(float nofpclass(inf nsub nnorm) %arg) #0 {
   ret float %call
 }
 
+define float @ret_log_no_pnorm(float nofpclass(pnorm) %arg) #0 {
+; CHECK-LABEL: define nofpclass(nzero sub) float @ret_log_no_pnorm
+; CHECK-SAME: (float nofpclass(pnorm) [[ARG:%.*]]) #[[ATTR2]] {
+; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nzero sub) float @llvm.log.f32(float nofpclass(pnorm) [[ARG]]) #[[ATTR12]]
+; CHECK-NEXT:    ret float [[CALL]]
+;
+  %call = call float @llvm.log.f32(float %arg)
+  ret float %call
+}
+
+; log(x) can be negative zero or subnormal if x is close to +1.0 for ppc_fp128.
+; However, if x is not close to +1.0 (e.g. x is not positive normal), then it is
+; safe to rule out negative zero and subnormal for ppc_fp128.
+define ppc_fp128 @ret_log_ppcf128_no_pnorm(ppc_fp128 nofpclass(pnorm) %arg) #0 {
+; CHECK-LABEL: define ppc_fp128 @ret_log_ppcf128_no_pnorm
+; CHECK-SAME: (ppc_fp128 nofpclass(pnorm) [[ARG:%.*]]) #[[ATTR2]] {
+; CHECK-NEXT:    [[CALL:%.*]] = call ppc_fp128 @llvm.log.ppcf128(ppc_fp128 nofpclass(pnorm) [[ARG]]) #[[ATTR12]]
+; CHECK-NEXT:    ret ppc_fp128 [[CALL]]
+;
+  %call = call ppc_fp128 @llvm.log.ppcf128(ppc_fp128 %arg)
+  ret ppc_fp128 %call
+}
+
 define float @ret_constrained_log2_noinf_noneg(float nofpclass(inf nsub nnorm) %arg) strictfp {
 ; CHECK-LABEL: define nofpclass(pinf nzero sub) float @ret_constrained_log2_noinf_noneg
 ; CHECK-SAME: (float nofpclass(inf nsub nnorm) [[ARG:%.*]]) #[[ATTR9]] {

diff  --git a/llvm/test/Transforms/Attributor/nofpclass-multiunitfptype.ll b/llvm/test/Transforms/Attributor/nofpclass-multiunitfptype.ll
new file mode 100644
index 0000000000000..c8f3987797076
--- /dev/null
+++ b/llvm/test/Transforms/Attributor/nofpclass-multiunitfptype.ll
@@ -0,0 +1,44 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -aa-pipeline=basic-aa -passes=attributor -attributor-manifest-internal < %s | FileCheck %s
+
+declare ppc_fp128 @llvm.log.ppcf128(ppc_fp128)
+declare ppc_fp128 @llvm.log2.ppcf128(ppc_fp128)
+declare ppc_fp128 @llvm.log10.ppcf128(ppc_fp128)
+
+; We cannot rule out negative zero or subnormal here. ppc_fp128 can represent the
+; exact value of 1.0 +/- subnormal, and log(1.0 +/- subnormal) may be zero or
+; subnormal.
+define ppc_fp128 @ret_log_ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) %arg) {
+; CHECK-LABEL: define nofpclass(nan inf) ppc_fp128 @ret_log_ppcf128(
+; CHECK-SAME: ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG:%.*]]) #[[ATTR2:[0-9]+]] {
+; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan inf) ppc_fp128 @llvm.log.ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG]]) #[[ATTR4:[0-9]+]]
+; CHECK-NEXT:    ret ppc_fp128 [[CALL]]
+;
+  %call = call ppc_fp128 @llvm.log.ppcf128(ppc_fp128 %arg)
+  ret ppc_fp128 %call
+}
+
+; |log2(x)| > |log(x)|. log2(1.0 + x) can be approximated by x * log2(e) or
+; x * 1.442695 when x is close to zero, so it may technically be safe to rule
+; out negative zero (although this is likely too risky to implement). This test
+; watches for such a change.
+define ppc_fp128 @ret_log2_ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) %arg) {
+; CHECK-LABEL: define nofpclass(nan inf) ppc_fp128 @ret_log2_ppcf128(
+; CHECK-SAME: ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG:%.*]]) #[[ATTR2]] {
+; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan inf) ppc_fp128 @llvm.log2.ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG]]) #[[ATTR4]]
+; CHECK-NEXT:    ret ppc_fp128 [[CALL]]
+;
+  %call = call ppc_fp128 @llvm.log2.ppcf128(ppc_fp128 %arg)
+  ret ppc_fp128 %call
+}
+
+; |log10(x)| < |log(x)|
+define ppc_fp128 @ret_log10_ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) %arg) {
+; CHECK-LABEL: define nofpclass(nan inf) ppc_fp128 @ret_log10_ppcf128(
+; CHECK-SAME: ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG:%.*]]) #[[ATTR2]] {
+; CHECK-NEXT:    [[CALL:%.*]] = call nofpclass(nan inf) ppc_fp128 @llvm.log10.ppcf128(ppc_fp128 nofpclass(nan inf zero sub nnorm) [[ARG]]) #[[ATTR4]]
+; CHECK-NEXT:    ret ppc_fp128 [[CALL]]
+;
+  %call = call ppc_fp128 @llvm.log10.ppcf128(ppc_fp128 %arg)
+  ret ppc_fp128 %call
+}

diff  --git a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-log.ll b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-log.ll
index 0b85420bd6a1e..459e7561c52ee 100644
--- a/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-log.ll
+++ b/llvm/test/Transforms/InstCombine/simplify-demanded-fpclass-log.ll
@@ -367,9 +367,44 @@ define nofpclass(snan) float @qnan_result_demands_snan_src(i1 %cond, float %unkn
   ret float %result
 }
 
+define nofpclass(nan inf pzero norm) float @ret__nzero_sub__log_select__pnorm__unknown(i1 %cond, float nofpclass(nan inf zero sub nnorm) %must.be.pnorm, float %unknown) {
+; CHECK-LABEL: define nofpclass(nan inf pzero norm) float @ret__nzero_sub__log_select__pnorm__unknown(
+; CHECK-SAME: i1 [[COND:%.*]], float nofpclass(nan inf zero sub nnorm) [[MUST_BE_PNORM:%.*]], float [[UNKNOWN:%.*]]) {
+; CHECK-NEXT:    ret float poison
+;
+  %select = select i1 %cond, float %must.be.pnorm, float %unknown
+  %result = call float @llvm.log.f32(float %select)
+  ret float %result
+}
+
+; log(x) can be negative zero or subnormal if x is close to +1.0 for ppc_fp128.
+define nofpclass(nan inf pzero norm) ppc_fp128 @ret__nzero_sub__log_select__pnorm__unknown_ppcf128(i1 %cond, ppc_fp128 nofpclass(nan inf zero sub nnorm) %must.be.pnorm, ppc_fp128 %unknown) {
+; CHECK-LABEL: define nofpclass(nan inf pzero norm) ppc_fp128 @ret__nzero_sub__log_select__pnorm__unknown_ppcf128(
+; CHECK-SAME: i1 [[COND:%.*]], ppc_fp128 nofpclass(nan inf zero sub nnorm) [[MUST_BE_PNORM:%.*]], ppc_fp128 [[UNKNOWN:%.*]]) {
+; CHECK-NEXT:    [[SELECT:%.*]] = select i1 [[COND]], ppc_fp128 [[MUST_BE_PNORM]], ppc_fp128 [[UNKNOWN]]
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan ninf ppc_fp128 @llvm.log.ppcf128(ppc_fp128 [[SELECT]])
+; CHECK-NEXT:    ret ppc_fp128 [[RESULT]]
+;
+  %select = select i1 %cond, ppc_fp128 %must.be.pnorm, ppc_fp128 %unknown
+  %result = call ppc_fp128 @llvm.log.ppcf128(ppc_fp128 %select)
+  ret ppc_fp128 %result
+}
+
+; Note that DoubleAPFloat::isDenormal considers 1.0 + DBL_TRUE_MIN to be
+; denormal, which APFloat::classify treats as fcSubnormal.
+define nofpclass(nan inf pzero norm) ppc_fp128 @ret__nzero_sub__log_select__psub__unknown_ppcf128(i1 %cond, ppc_fp128 nofpclass(nan inf zero nsub norm) %must.be.psub, ppc_fp128 %unknown) {
+; CHECK-LABEL: define nofpclass(nan inf pzero norm) ppc_fp128 @ret__nzero_sub__log_select__psub__unknown_ppcf128(
+; CHECK-SAME: i1 [[COND:%.*]], ppc_fp128 nofpclass(nan inf zero nsub norm) [[MUST_BE_PSUB:%.*]], ppc_fp128 [[UNKNOWN:%.*]]) {
+; CHECK-NEXT:    [[SELECT:%.*]] = select i1 [[COND]], ppc_fp128 [[MUST_BE_PSUB]], ppc_fp128 [[UNKNOWN]]
+; CHECK-NEXT:    [[RESULT:%.*]] = call nnan ninf ppc_fp128 @llvm.log.ppcf128(ppc_fp128 [[SELECT]])
+; CHECK-NEXT:    ret ppc_fp128 [[RESULT]]
+;
+  %select = select i1 %cond, ppc_fp128 %must.be.psub, ppc_fp128 %unknown
+  %result = call ppc_fp128 @llvm.log.ppcf128(ppc_fp128 %select)
+  ret ppc_fp128 %result
+}
+
 attributes #0 = { denormal_fpenv(preservesign) }
 attributes #1 = { denormal_fpenv(dynamic) }
 
 !0 = !{}
-
-

diff  --git a/llvm/unittests/CodeGen/GlobalISel/KnownFPClassTest.cpp b/llvm/unittests/CodeGen/GlobalISel/KnownFPClassTest.cpp
index 57656a91ddcd3..f5d3193b173c3 100644
--- a/llvm/unittests/CodeGen/GlobalISel/KnownFPClassTest.cpp
+++ b/llvm/unittests/CodeGen/GlobalISel/KnownFPClassTest.cpp
@@ -10,8 +10,9 @@
 #include "llvm/CodeGen/GlobalISel/GISelValueTracking.h"
 #include "gtest/gtest.h"
 
-// This test exercises the signaling-NaN query that the
-// `print<gisel-value-tracking-fpclass>` pass cannot represent.
+// These tests exercise queries that the `print<gisel-value-tracking-fpclass>`
+// pass cannot represent: the signaling-NaN query, and ppcf128 types, which the
+// MIR parser cannot spell.
 
 TEST_F(AArch64GISelMITest, TestFPClassFPowPosNeverSNaN) {
   StringRef MIRString = R"(
@@ -34,3 +35,32 @@ TEST_F(AArch64GISelMITest, TestFPClassFPowPosNeverSNaN) {
   GISelValueTracking Info(*MF);
   EXPECT_TRUE(Info.isKnownNeverNaN(SrcReg, true));
 }
+
+TEST_F(AArch64GISelMITest, TestFPClassFLogPPCF128) {
+  StringRef MIRString = R"(
+    %val:_(s128) = G_IMPLICIT_DEF
+    %fabs:_(s128) = nnan G_FABS %val
+    %flog:_(s128) = G_FLOG %fabs
+    %copy_flog:_(s128) = COPY %flog
+)";
+
+  setUp(MIRString);
+  if (!TM)
+    GTEST_SKIP();
+
+  Register CopyReg = Copies.back();
+  MachineInstr *FinalCopy = MRI->getVRegDef(CopyReg);
+  Register SrcReg = FinalCopy->getOperand(1).getReg();
+
+  // The MIR parser cannot spell a ppcf128 LLT.
+  MachineInstr *FLog = MRI->getVRegDef(SrcReg);
+  Register ValReg = FLog->getOperand(1).getReg();
+  MRI->setType(ValReg, LLT::ppcf128());
+  MRI->setType(SrcReg, LLT::ppcf128());
+
+  GISelValueTracking Info(*MF);
+  KnownFPClass Known = Info.computeKnownFPClass(SrcReg);
+
+  EXPECT_EQ(fcNegative | fcPositive, Known.getKnownFPClasses());
+  EXPECT_EQ(std::nullopt, Known.getSignBit());
+}


        


More information about the llvm-commits mailing list