From 892f156e586f9ade3aa3aa032a0ad76b9fe86201 Mon Sep 17 00:00:00 2001 From: Matt Arsenault Date: Wed, 10 Dec 2025 20:09:04 +0100 Subject: [PATCH] ValueTracking: Teach computeKnownFPClass that multiply can avoid denormals 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. --- llvm/lib/Analysis/ValueTracking.cpp | 18 +++++++++++++++++ .../Transforms/Attributor/nofpclass-fmul.ll | 20 +++++++++---------- .../Attributor/nofpclass-nan-fmul.ll | 2 +- 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp index 92577cd7517e6..e98d13486d023 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]]