[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