[llvm] ValueTracking: Handle frexp exp in computeKnownConstantRange (PR #191282)
Matt Arsenault via llvm-commits
llvm-commits at lists.llvm.org
Thu Apr 9 13:02:40 PDT 2026
https://github.com/arsenm created https://github.com/llvm/llvm-project/pull/191282
Compute the bounds based on the known exponent range.
Only handles IEEE cases since I don't see an easy way to
get the bounds in general.
Test uses instcombine instead of checking for the range
attribute, since apparently attributor doesn't handle introducing
range attributes from computeConstantRange.
>From 85dff9b2ef927c50fc10521f21f63be55048e7f3 Mon Sep 17 00:00:00 2001
From: Matt Arsenault <Matthew.Arsenault at amd.com>
Date: Wed, 11 Mar 2026 22:06:49 +0100
Subject: [PATCH] ValueTracking: Handle frexp exp in computeKnownConstantRange
Compute the bounds based on the known exponent range.
Only handles IEEE cases since I don't see an easy way to
get the bounds in general.
Test uses instcombine instead of checking for the range
attribute, since apparently attributor doesn't handle introducing
range attributes from computeConstantRange.
---
llvm/lib/Analysis/ValueTracking.cpp | 26 +++-
.../InstCombine/known-range-frexp-exp.ll | 136 ++++++++++++++++++
2 files changed, 161 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Transforms/InstCombine/known-range-frexp-exp.ll
diff --git a/llvm/lib/Analysis/ValueTracking.cpp b/llvm/lib/Analysis/ValueTracking.cpp
index 9bd6fd0dcc733..ce1527153e81e 100644
--- a/llvm/lib/Analysis/ValueTracking.cpp
+++ b/llvm/lib/Analysis/ValueTracking.cpp
@@ -10451,9 +10451,33 @@ ConstantRange llvm::computeConstantRange(const Value *V, bool ForSigned,
if (auto *Range = IIQ.getMetadata(I, LLVMContext::MD_range))
CR = CR.intersectWith(getConstantRangeFromMetadata(*Range));
- if (const auto *CB = dyn_cast<CallBase>(V))
+ Value *FrexpSrc;
+ if (const auto *CB = dyn_cast<CallBase>(V)) {
if (std::optional<ConstantRange> Range = CB->getRange())
CR = CR.intersectWith(*Range);
+ } else if (match(I, m_ExtractValue<1>(m_Intrinsic<Intrinsic::frexp>(
+ m_Value(FrexpSrc))))) {
+ const fltSemantics &FltSem =
+ FrexpSrc->getType()->getScalarType()->getFltSemantics();
+ // It should be possible to implement this for any type, but this logic
+ // only computes the range assuming standard subnormal handling.
+ if (APFloat::isIEEELikeFP(FltSem)) {
+ const DataLayout &DL = I->getFunction()->getDataLayout();
+ SimplifyQuery SQ(DL, nullptr, DT, AC, CtxI, UseInstrInfo);
+
+ KnownFPClass KnownSrc =
+ computeKnownFPClass(FrexpSrc, fcSubnormal, SQ, Depth + 1);
+
+ int MinExp = APFloat::semanticsMinExponent(FltSem) + 1;
+ if (!KnownSrc.isKnownNeverSubnormal())
+ MinExp -= (APFloat::semanticsPrecision(FltSem) - 1);
+
+ int MaxExp = APFloat::semanticsMaxExponent(FltSem) + 1;
+ CR = ConstantRange::getNonEmpty(
+ APInt(BitWidth, MinExp, /*isSigned=*/true),
+ APInt(BitWidth, MaxExp + 1, /*isSigned=*/true));
+ }
+ }
}
if (CtxI && AC) {
diff --git a/llvm/test/Transforms/InstCombine/known-range-frexp-exp.ll b/llvm/test/Transforms/InstCombine/known-range-frexp-exp.ll
new file mode 100644
index 0000000000000..8cb0e308683a2
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/known-range-frexp-exp.ll
@@ -0,0 +1,136 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+define i32 @frexp_f32_clamp_min(float %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_min(
+; CHECK-SAME: float [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: ret i32 [[EXP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smax.i32(i32 %exp, i32 -148)
+ ret i32 %clamp
+}
+
+define i16 @frexp_f32_clamp_min_i16(float %x) {
+; CHECK-LABEL: define i16 @frexp_f32_clamp_min_i16(
+; CHECK-SAME: float [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i16 } @llvm.frexp.f32.i16(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i16 } [[FREXP]], 1
+; CHECK-NEXT: ret i16 [[EXP]]
+;
+ %frexp = call { float, i16 } @llvm.frexp.f32.i16(float %x)
+ %exp = extractvalue { float, i16 } %frexp, 1
+ %clamp = call i16 @llvm.smax.i16(i16 %exp, i16 -148)
+ ret i16 %clamp
+}
+
+define i32 @frexp_f32_clamp_min_off_by_one(float %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_min_off_by_one(
+; CHECK-SAME: float [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: [[CLAMP:%.*]] = call i32 @llvm.smax.i32(i32 [[EXP]], i32 -147)
+; CHECK-NEXT: ret i32 [[CLAMP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smax.i32(i32 %exp, i32 -147)
+ ret i32 %clamp
+}
+
+define i32 @frexp_f32_clamp_max(float %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_max(
+; CHECK-SAME: float [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: ret i32 [[EXP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smin.i32(i32 %exp, i32 128)
+ ret i32 %clamp
+}
+
+define i32 @frexp_f32_clamp_max_off_by_one(float %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_max_off_by_one(
+; CHECK-SAME: float [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: [[CLAMP:%.*]] = call i32 @llvm.smin.i32(i32 [[EXP]], i32 127)
+; CHECK-NEXT: ret i32 [[CLAMP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smin.i32(i32 %exp, i32 127)
+ ret i32 %clamp
+}
+
+define i32 @frexp_f32_clamp_min_no_denormal(float nofpclass(sub) %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_min_no_denormal(
+; CHECK-SAME: float nofpclass(sub) [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: ret i32 [[EXP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smax.i32(i32 %exp, i32 -125)
+ ret i32 %clamp
+}
+
+define i32 @frexp_f32_clamp_min_no_denormal_off_by_one(float nofpclass(sub) %x) {
+; CHECK-LABEL: define i32 @frexp_f32_clamp_min_no_denormal_off_by_one(
+; CHECK-SAME: float nofpclass(sub) [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { float, i32 } @llvm.frexp.f32.i32(float [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { float, i32 } [[FREXP]], 1
+; CHECK-NEXT: [[CLAMP:%.*]] = call i32 @llvm.smax.i32(i32 [[EXP]], i32 -124)
+; CHECK-NEXT: ret i32 [[CLAMP]]
+;
+ %frexp = call { float, i32 } @llvm.frexp.f32.i32(float %x)
+ %exp = extractvalue { float, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smax.i32(i32 %exp, i32 -124)
+ ret i32 %clamp
+}
+
+define <2 x i32> @frexp_v2f32_clamp_min(<2 x float> %x) {
+; CHECK-LABEL: define <2 x i32> @frexp_v2f32_clamp_min(
+; CHECK-SAME: <2 x float> [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { <2 x float>, <2 x i32> } @llvm.frexp.v2f32.v2i32(<2 x float> [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { <2 x float>, <2 x i32> } [[FREXP]], 1
+; CHECK-NEXT: ret <2 x i32> [[EXP]]
+;
+ %frexp = call { <2 x float>, <2 x i32> } @llvm.frexp.v2f32.v2i32(<2 x float> %x)
+ %exp = extractvalue { <2 x float>, <2 x i32> } %frexp, 1
+ %clamp = call <2 x i32> @llvm.smax.v2i32(<2 x i32> %exp, <2 x i32> splat (i32 -148))
+ ret <2 x i32> %clamp
+}
+
+
+define i32 @frexp_f64_clamp_min(double %x) {
+; CHECK-LABEL: define i32 @frexp_f64_clamp_min(
+; CHECK-SAME: double [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { double, i32 } @llvm.frexp.f64.i32(double [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { double, i32 } [[FREXP]], 1
+; CHECK-NEXT: ret i32 [[EXP]]
+;
+ %frexp = call { double, i32 } @llvm.frexp.f32.i32(double %x)
+ %exp = extractvalue { double, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smax.i32(i32 %exp, i32 -1073)
+ ret i32 %clamp
+}
+
+define i32 @frexp_f64_clamp_max(double %x) {
+; CHECK-LABEL: define i32 @frexp_f64_clamp_max(
+; CHECK-SAME: double [[X:%.*]]) {
+; CHECK-NEXT: [[FREXP:%.*]] = call { double, i32 } @llvm.frexp.f64.i32(double [[X]])
+; CHECK-NEXT: [[EXP:%.*]] = extractvalue { double, i32 } [[FREXP]], 1
+; CHECK-NEXT: ret i32 [[EXP]]
+;
+ %frexp = call { double, i32 } @llvm.frexp.f32.i32(double %x)
+ %exp = extractvalue { double, i32 } %frexp, 1
+ %clamp = call i32 @llvm.smin.i32(i32 %exp, i32 1024)
+ ret i32 %clamp
+}
More information about the llvm-commits
mailing list