Skip to content

Conversation

@arsenm
Copy link
Contributor

@arsenm arsenm commented Dec 10, 2025

Multiply by large constant can be used to scale denormal inputs into
a normal range. This pattern appears frequently in math function library
implementations to make use of hardware instructions that do not support
denormals. We already handle this case for ldexp, but now canonicalize
ldexp by a constant to an fmul.

The test cases are mostly the existing nofpclass test for ldexp,
run through the new instcombine to replace ldexp with fmul.

@arsenm arsenm added floating-point Floating-point math llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:ir llvm:transforms labels Dec 10, 2025 — with Graphite App
@arsenm arsenm marked this pull request as ready for review December 10, 2025 23:02
@arsenm arsenm requested a review from nikic as a code owner December 10, 2025 23:02
@llvmbot llvmbot added the llvm:analysis Includes value tracking, cost tables and constant folding label Dec 10, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 10, 2025

@llvm/pr-subscribers-llvm-analysis
@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-llvm-ir

Author: Matt Arsenault (arsenm)

Changes

Multiply by large constant can be used to scale denormal inputs into
a normal range. This pattern appears frequently in math function library
implementations to make use of hardware instructions that do not support
denormals. We already handle this case for ldexp, but now canonicalize
ldexp by a constant to an fmul.

The test cases are mostly the existing nofpclass test for ldexp,
run through the new instcombine to replace ldexp with fmul.


Full diff: https://github.com/llvm/llvm-project/pull/171730.diff

3 Files Affected:

  • (modified) llvm/lib/Analysis/ValueTracking.cpp (+18)
  • (modified) llvm/test/Transforms/Attributor/nofpclass-fmul.ll (+10-10)
  • (modified) llvm/test/Transforms/Attributor/nofpclass-nan-fmul.ll (+1-1)
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 9cb6f19b9340c..730b9a30c77ed 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -5646,6 +5646,24 @@ void computeKnownFPClass(const Value *V, const APInt &DemandedElts,
     KnownFPClass KnownLHS, KnownRHS;
     computeKnownFPClass(Op->getOperand(1), DemandedElts, NeedForNan, KnownRHS,
                         Q, Depth + 1);
+
+    const APFloat *CRHS;
+    if (match(Op->getOperand(1), m_APFloat(CRHS))) {
+      // Match denormal scaling pattern, similar to the case in ldexp. If the
+      // constant's exponent is sufficiently large, the result cannot be
+      // subnormal.
+
+      // TODO: Should do general ConstantFPRange analysis.
+      const fltSemantics &Flt =
+          Op->getType()->getScalarType()->getFltSemantics();
+      unsigned Precision = APFloat::semanticsPrecision(Flt);
+      const int MantissaBits = Precision - 1;
+
+      int MinKnownExponent = ilogb(*CRHS);
+      if (MinKnownExponent >= MantissaBits)
+        Known.knownNot(fcSubnormal);
+    }
+
     if (!KnownRHS.isKnownNeverNaN())
       break;
 
diff --git a/llvm/test/Transforms/Attributor/nofpclass-fmul.ll b/llvm/test/Transforms/Attributor/nofpclass-fmul.ll
index 6d0edf0681ed4..0ad34105d4cb6 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-fmul.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-fmul.ll
@@ -35,7 +35,7 @@ define float @ret_mul_exponent_f32_22(float %arg0) {
 }
 
 define float @ret_mul_exponent_f32_23(float %arg0) {
-; CHECK-LABEL: define float @ret_mul_exponent_f32_23(
+; CHECK-LABEL: define nofpclass(sub) float @ret_mul_exponent_f32_23(
 ; CHECK-SAME: float [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul float [[ARG0]], 0x4160000000000000
 ; CHECK-NEXT:    ret float [[CALL]]
@@ -45,7 +45,7 @@ define float @ret_mul_exponent_f32_23(float %arg0) {
 }
 
 define float @ret_mul_exponent_f32_24(float %arg0) {
-; CHECK-LABEL: define float @ret_mul_exponent_f32_24(
+; CHECK-LABEL: define nofpclass(sub) float @ret_mul_exponent_f32_24(
 ; CHECK-SAME: float [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul float [[ARG0]], 0x4170000000000000
 ; CHECK-NEXT:    ret float [[CALL]]
@@ -55,7 +55,7 @@ define float @ret_mul_exponent_f32_24(float %arg0) {
 }
 
 define float @ret_mul_exponent_f32_23_nnan(float nofpclass(nan) %arg0) {
-; CHECK-LABEL: define nofpclass(nan) float @ret_mul_exponent_f32_23_nnan(
+; CHECK-LABEL: define nofpclass(nan sub) float @ret_mul_exponent_f32_23_nnan(
 ; CHECK-SAME: float nofpclass(nan) [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul float [[ARG0]], 0x4160000000000000
 ; CHECK-NEXT:    ret float [[CALL]]
@@ -85,7 +85,7 @@ define double @ret_mul_exponent_f64_51(double %arg0) {
 }
 
 define double @ret_mul_exponent_f64_52(double %arg0) {
-; CHECK-LABEL: define double @ret_mul_exponent_f64_52(
+; CHECK-LABEL: define nofpclass(sub) double @ret_mul_exponent_f64_52(
 ; CHECK-SAME: double [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul double [[ARG0]], 0x4330000000000000
 ; CHECK-NEXT:    ret double [[CALL]]
@@ -95,7 +95,7 @@ define double @ret_mul_exponent_f64_52(double %arg0) {
 }
 
 define double @ret_mul_exponent_f64_53(double %arg0) {
-; CHECK-LABEL: define double @ret_mul_exponent_f64_53(
+; CHECK-LABEL: define nofpclass(sub) double @ret_mul_exponent_f64_53(
 ; CHECK-SAME: double [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul double [[ARG0]], 0x4340000000000000
 ; CHECK-NEXT:    ret double [[CALL]]
@@ -125,7 +125,7 @@ define half @ret_mul_exponent_f16_9(half %arg0) {
 }
 
 define half @ret_mul_exponent_f16_10(half %arg0) {
-; CHECK-LABEL: define half @ret_mul_exponent_f16_10(
+; CHECK-LABEL: define nofpclass(sub) half @ret_mul_exponent_f16_10(
 ; CHECK-SAME: half [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul half [[ARG0]], 0xH6400
 ; CHECK-NEXT:    ret half [[CALL]]
@@ -145,7 +145,7 @@ define bfloat @ret_mul_exponent_bf16_6(bfloat %arg0) {
 }
 
 define bfloat @ret_mul_exponent_bf16_7(bfloat %arg0) {
-; CHECK-LABEL: define bfloat @ret_mul_exponent_bf16_7(
+; CHECK-LABEL: define nofpclass(sub) bfloat @ret_mul_exponent_bf16_7(
 ; CHECK-SAME: bfloat [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul bfloat [[ARG0]], 0xR4300
 ; CHECK-NEXT:    ret bfloat [[CALL]]
@@ -155,7 +155,7 @@ define bfloat @ret_mul_exponent_bf16_7(bfloat %arg0) {
 }
 
 define bfloat @ret_mul_exponent_bf16_8(bfloat %arg0) {
-; CHECK-LABEL: define bfloat @ret_mul_exponent_bf16_8(
+; CHECK-LABEL: define nofpclass(sub) bfloat @ret_mul_exponent_bf16_8(
 ; CHECK-SAME: bfloat [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul bfloat [[ARG0]], 0xR4380
 ; CHECK-NEXT:    ret bfloat [[CALL]]
@@ -215,7 +215,7 @@ define float @ret_mul_exponent_f32_neg127(float %arg0) {
 }
 
 define <2 x float> @ret_mul_exponent_v2f32_splat_23(<2 x float> %arg0) {
-; CHECK-LABEL: define <2 x float> @ret_mul_exponent_v2f32_splat_23(
+; CHECK-LABEL: define nofpclass(sub) <2 x float> @ret_mul_exponent_v2f32_splat_23(
 ; CHECK-SAME: <2 x float> [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul <2 x float> [[ARG0]], splat (float 0x4160000000000000)
 ; CHECK-NEXT:    ret <2 x float> [[CALL]]
@@ -267,7 +267,7 @@ define float @ret_mul_f32_0(float %arg0) {
 }
 
 define float @ret_mul_f32_inf(float %arg0) {
-; CHECK-LABEL: define float @ret_mul_f32_inf(
+; CHECK-LABEL: define nofpclass(sub) float @ret_mul_f32_inf(
 ; CHECK-SAME: float [[ARG0:%.*]]) #[[ATTR0]] {
 ; CHECK-NEXT:    [[CALL:%.*]] = fmul float [[ARG0]], 0x7FF0000000000000
 ; CHECK-NEXT:    ret float [[CALL]]
diff --git a/llvm/test/Transforms/Attributor/nofpclass-nan-fmul.ll b/llvm/test/Transforms/Attributor/nofpclass-nan-fmul.ll
index fbf6c2e0981fb..f6bb1354cb74f 100644
--- a/llvm/test/Transforms/Attributor/nofpclass-nan-fmul.ll
+++ b/llvm/test/Transforms/Attributor/nofpclass-nan-fmul.ll
@@ -195,7 +195,7 @@ define float @ret_fmul_square_nnan_nzero(float nofpclass(nan zero) %arg) #0 {
 }
 
 define float @ret_fmul_ieee_inf(float %arg) {
-; CHECK-LABEL: define float @ret_fmul_ieee_inf
+; CHECK-LABEL: define nofpclass(sub) float @ret_fmul_ieee_inf
 ; CHECK-SAME: (float [[ARG:%.*]]) #[[ATTR4:[0-9]+]] {
 ; CHECK-NEXT:    [[FMUL:%.*]] = fmul float [[ARG]], 0x7FF0000000000000
 ; CHECK-NEXT:    ret float [[FMUL]]

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@arsenm arsenm force-pushed the users/arsenm/valuetracking/fmul-large-exponent-constant-is-not-subnormal branch from fed7047 to b552c70 Compare December 11, 2025 10:41
@arsenm arsenm force-pushed the users/arsenm/valuetracking/add-baseline-test-fmul-exponent-handling branch from 89db664 to ef8a54e Compare December 11, 2025 10:41
Base automatically changed from users/arsenm/valuetracking/add-baseline-test-fmul-exponent-handling to main December 11, 2025 12:06
…rmals

Multiply by large constant can be used to scale denormal inputs into
a normal range. This pattern appears frequently in math function library
implementations to make use of hardware instructions that do not support
denormals. We already handle this case for ldexp, but now canonicalize
ldexp by a constant to an fmul.

The test cases are mostly the existing nofpclass test for ldexp,
run through the new instcombine to replace ldexp with fmul.
@arsenm arsenm force-pushed the users/arsenm/valuetracking/fmul-large-exponent-constant-is-not-subnormal branch from b552c70 to 892f156 Compare December 11, 2025 12:07
@arsenm arsenm enabled auto-merge (squash) December 11, 2025 12:08
@arsenm arsenm merged commit 39fcd2c into main Dec 11, 2025
9 of 10 checks passed
@arsenm arsenm deleted the users/arsenm/valuetracking/fmul-large-exponent-constant-is-not-subnormal branch December 11, 2025 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

floating-point Floating-point math llvm:analysis Includes value tracking, cost tables and constant folding llvm:instcombine Covers the InstCombine, InstSimplify and AggressiveInstCombine passes llvm:ir llvm:transforms

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants