[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