[llvm] [KnownFPClass] Improve known class deductions for exp/exp2/exp10 (PR #217526)
Eric Ross via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 29 19:33:02 PDT 2026
https://github.com/ZERICO2005 updated https://github.com/llvm/llvm-project/pull/217526
>From f75bf4c6d0815f6c07fe0697109a5bdcf5425900 Mon Sep 17 00:00:00 2001
From: zerico <zerico2005 at gmail.com>
Date: Wed, 19 Aug 2026 22:02:36 -0600
Subject: [PATCH 1/3] [KnownFPClass] Improve known class deductions for
exp/exp2/exp10
---
llvm/lib/Support/KnownFPClass.cpp | 18 ++++--
.../AArch64/GlobalISel/knownfpclass-ops.mir | 30 ++++++++++
.../Transforms/Attributor/nofpclass-exp.ll | 57 +++++++++++++++++++
3 files changed, 99 insertions(+), 6 deletions(-)
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index 1451a407983ff..d4c8a988e2812 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -646,16 +646,22 @@ KnownFPClass KnownFPClass::exp(const KnownFPClass &KnownSrc) {
Known.propagateNonNaN(KnownSrc);
- if (KnownSrc.cannotBeOrderedLessThanZero()) {
- // If the source is positive this cannot underflow.
+ // The following deductions assume that both exp10(-1.0) = +0.1 and
+ // exp10(+1.0) = +10.0 are both finite normal values.
+
+ // Only a negative normal or negative infinity can produce positive zero.
+ // A negative subnormal input is too small to produce positive zero.
+ if (KnownSrc.isKnownNever(fcNegNormal | fcNegInf))
Known.knownNot(fcPosZero);
- // Cannot introduce denormal values.
+ // Only a negative normal can produce a positive subnormal.
+ // A negative subnormal input is too small to produce a subnormal result.
+ if (KnownSrc.isKnownNever(fcNegNormal))
Known.knownNot(fcPosSubnormal);
- }
- // If the source is negative, this cannot overflow to infinity.
- if (KnownSrc.cannotBeOrderedGreaterThanZero())
+ // Only a positive normal or positive infinity can produce positive infinity.
+ // A positive subnormal input is too small to cause an overflow.
+ if (KnownSrc.isKnownNever(fcPosNormal | fcPosInf))
Known.knownNot(fcPosInf);
return Known;
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownfpclass-ops.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownfpclass-ops.mir
index 0e301d0a4b974..c3c7f21e32c98 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownfpclass-ops.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownfpclass-ops.mir
@@ -614,6 +614,36 @@ body: |
%copy:_(s32) = COPY %flog
...
---
+name: FExpNegSubnormal
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: @FExpNegSubnormal
+ ; CHECK-NEXT: %subnormal:_ FPClasses:(psub) SignBitKnown:0
+ ; CHECK-NEXT: %negative_subnormal:_ FPClasses:(nsub) SignBitKnown:1
+ ; CHECK-NEXT: %exp:_ FPClasses:(pnorm) SignBitKnown:0
+ ; CHECK-NEXT: %copy:_ FPClasses:(pnorm) SignBitKnown:0
+ %subnormal:_(s32) = G_FCONSTANT float f0x00000001
+ %negative_subnormal:_(s32) = G_FNEG %subnormal
+ %exp:_(s32) = G_FEXP %negative_subnormal
+ %copy:_(s32) = COPY %exp
+...
+---
+name: FExp2Positive
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: @FExp2Positive
+ ; CHECK-NEXT: %ptr:_ FPClasses:(all) SignBitKnown:?
+ ; CHECK-NEXT: %val:_ FPClasses:(all) SignBitKnown:?
+ ; CHECK-NEXT: %positive:_ FPClasses:(pinf pzero psub pnorm) SignBitKnown:0
+ ; CHECK-NEXT: %exp2:_ FPClasses:(pinf pnorm) SignBitKnown:0
+ ; CHECK-NEXT: %copy:_ FPClasses:(pinf pnorm) SignBitKnown:0
+ %ptr:_(p0) = G_IMPLICIT_DEF
+ %val:_(s32) = G_LOAD %ptr(p0) :: (load (s32))
+ %positive:_(s32) = nnan G_FABS %val
+ %exp2:_(s32) = G_FEXP2 %positive
+ %copy:_(s32) = COPY %exp2
+...
+---
name: CopyFabs
body: |
bb.0:
diff --git a/llvm/test/Transforms/Attributor/nofpclass-exp.ll b/llvm/test/Transforms/Attributor/nofpclass-exp.ll
index 670e5bce06788..595bf4908b4f0 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-exp.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-exp.ll
@@ -4,6 +4,7 @@
declare float @llvm.exp.f32(float)
declare float @llvm.exp2.f32(float)
declare float @llvm.exp10.f32(float)
+declare float @llvm.sqrt.f32(float)
define float @ret_exp(float %arg0) {
; CHECK-LABEL: define nofpclass(ninf nzero nsub nnorm) float @ret_exp
@@ -450,5 +451,61 @@ define float @ret_exp_fneg_fabs(float %arg) {
ret float %call
}
+; A negative subnormal input cannot produce zero or a subnormal result.
+define float @ret_exp_negative_subnormal(float nofpclass(inf zero norm psub) %arg) {
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_exp_negative_subnormal
+; CHECK-SAME: (float nofpclass(inf zero psub norm) [[ARG:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.exp.f32(float nofpclass(inf zero psub norm) [[ARG]]) #[[ATTR2]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.exp.f32(float %arg)
+ ret float %call
+}
+
+; A positive subnormal input cannot produce infinity.
+define float @ret_exp_positive_subnormal(float nofpclass(inf zero norm nsub) %arg) {
+; CHECK-LABEL: define nofpclass(inf zero sub nnorm) float @ret_exp_positive_subnormal
+; CHECK-SAME: (float nofpclass(inf zero nsub norm) [[ARG:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(inf zero sub nnorm) float @llvm.exp.f32(float nofpclass(inf zero nsub norm) [[ARG]]) #[[ATTR2]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %call = call float @llvm.exp.f32(float %arg)
+ ret float %call
+}
+
+; Exercise exp after 1.0 / sqrt(x). For non-poison inputs, the division is
+; -Inf, +zero, positive normal, or +Inf.
+define float @ret_exp_one_over_sqrt(float %arg) {
+; CHECK-LABEL: define nofpclass(nan ninf nzero sub nnorm) float @ret_exp_one_over_sqrt
+; CHECK-SAME: (float [[ARG:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[SQRT:%.*]] = call nnan float @llvm.sqrt.f32(float [[ARG]]) #[[ATTR2]]
+; CHECK-NEXT: [[ONE_OVER_SQRT:%.*]] = fdiv nnan float 1.000000e+00, [[SQRT]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan ninf nzero sub nnorm) float @llvm.exp.f32(float [[ONE_OVER_SQRT]]) #[[ATTR2]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %sqrt = call nnan float @llvm.sqrt.f32(float %arg)
+ %one.over.sqrt = fdiv nnan float 1.000000e+00, %sqrt
+ %call = call float @llvm.exp.f32(float %one.over.sqrt)
+ ret float %call
+}
+
+; Exercise exp after -1.0 / sqrt(x). For non-poison inputs, the result is
+; -Inf, negative normal, -zero, or +Inf.
+define float @ret_exp_neg_one_over_sqrt(float %arg) {
+; CHECK-LABEL: define nofpclass(nan ninf nzero nsub nnorm) float @ret_exp_neg_one_over_sqrt
+; CHECK-SAME: (float [[ARG:%.*]]) #[[ATTR1]] {
+; CHECK-NEXT: [[SQRT:%.*]] = call nnan float @llvm.sqrt.f32(float [[ARG]]) #[[ATTR2]]
+; CHECK-NEXT: [[ONE_OVER_SQRT:%.*]] = fdiv nnan float 1.000000e+00, [[SQRT]]
+; CHECK-NEXT: [[NEG_ONE_OVER_SQRT:%.*]] = fneg float [[ONE_OVER_SQRT]]
+; CHECK-NEXT: [[CALL:%.*]] = call nofpclass(nan ninf nzero nsub nnorm) float @llvm.exp.f32(float [[NEG_ONE_OVER_SQRT]]) #[[ATTR2]]
+; CHECK-NEXT: ret float [[CALL]]
+;
+ %sqrt = call nnan float @llvm.sqrt.f32(float %arg)
+ %one.over.sqrt = fdiv nnan float 1.000000e+00, %sqrt
+ %neg.one.over.sqrt = fneg float %one.over.sqrt
+ %call = call float @llvm.exp.f32(float %neg.one.over.sqrt)
+ ret float %call
+}
+
;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line:
; TUNIT: {{.*}}
>From 241ce9fb6a150ee02a7d8be74ff112e0cb0a61e6 Mon Sep 17 00:00:00 2001
From: zerico <zerico2005 at gmail.com>
Date: Sat, 12 Sep 2026 12:53:16 -0600
Subject: [PATCH 2/3] update KnownFPClass::exp deduction explainations
---
llvm/lib/Support/KnownFPClass.cpp | 11 ++++-------
1 file changed, 4 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Support/KnownFPClass.cpp b/llvm/lib/Support/KnownFPClass.cpp
index d4c8a988e2812..3d996642be09b 100644
--- a/llvm/lib/Support/KnownFPClass.cpp
+++ b/llvm/lib/Support/KnownFPClass.cpp
@@ -646,21 +646,18 @@ KnownFPClass KnownFPClass::exp(const KnownFPClass &KnownSrc) {
Known.propagateNonNaN(KnownSrc);
- // The following deductions assume that both exp10(-1.0) = +0.1 and
+ // The following deductions assume that exp10(-1.0) = +0.1 and
// exp10(+1.0) = +10.0 are both finite normal values.
- // Only a negative normal or negative infinity can produce positive zero.
- // A negative subnormal input is too small to produce positive zero.
+ // Zero can only be produced if x < -1.0.
if (KnownSrc.isKnownNever(fcNegNormal | fcNegInf))
Known.knownNot(fcPosZero);
- // Only a negative normal can produce a positive subnormal.
- // A negative subnormal input is too small to produce a subnormal result.
+ // Subnormals can only be produced if x < -1.0 and x is finite.
if (KnownSrc.isKnownNever(fcNegNormal))
Known.knownNot(fcPosSubnormal);
- // Only a positive normal or positive infinity can produce positive infinity.
- // A positive subnormal input is too small to cause an overflow.
+ // Infinity can only be produced if x > +1.0.
if (KnownSrc.isKnownNever(fcPosNormal | fcPosInf))
Known.knownNot(fcPosInf);
>From e71231b32d8e2ef09560636f10a6b92bf4cf3289 Mon Sep 17 00:00:00 2001
From: zerico <zerico2005 at gmail.com>
Date: Sun, 13 Sep 2026 18:25:01 -0600
Subject: [PATCH 3/3] Document KnownFPClass::exp assumptions in KnownFPClass.h
---
llvm/include/llvm/Support/KnownFPClass.h | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/llvm/include/llvm/Support/KnownFPClass.h b/llvm/include/llvm/Support/KnownFPClass.h
index 22097a70a361b..6ded720849384 100644
--- a/llvm/include/llvm/Support/KnownFPClass.h
+++ b/llvm/include/llvm/Support/KnownFPClass.h
@@ -330,7 +330,10 @@ struct KnownFPClass {
LLVM_ABI static KnownFPClass
log(const KnownFPClass &Src, DenormalMode Mode = DenormalMode::getDynamic());
- /// Report known values for exp, exp2 and exp10
+ /// Report known values for exp, exp2 and exp10.
+ /// This function assumes that exp10(-1.0) = +0.1 and exp10(+1.0) = +10.0 are
+ /// both finite normal values, which holds for formats with a corresponding
+ /// LLVM IR type (e.g. does not hold for Float4E2M1FN).
LLVM_ABI static KnownFPClass exp(const KnownFPClass &Src);
/// Report known values for sin
More information about the llvm-commits
mailing list