[llvm] [InstCombine] Lower ilogb to bit operations for normal values (PR #218044)

via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 24 06:00:55 PDT 2026


https://github.com/tadeuszjt updated https://github.com/llvm/llvm-project/pull/218044

>From 5d820a241a4031622889cf4f7185987447dc65a5 Mon Sep 17 00:00:00 2001
From: Tadeusz Tomoszek <tadeuszjt at protonmail.com>
Date: Wed, 19 Aug 2026 16:52:01 +0200
Subject: [PATCH] [InstCombine] Lower ilogb to bit operations for normal values

---
 .../llvm/Transforms/Utils/SimplifyLibCalls.h  |   1 +
 .../lib/Transforms/Utils/SimplifyLibCalls.cpp |  55 ++++
 llvm/test/Transforms/InstCombine/ilogb.ll     | 250 +++++++++++++++++-
 3 files changed, 305 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
index 7298ad459a5da..47bec0edacb2f 100644
--- a/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
+++ b/llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h
@@ -213,6 +213,7 @@ class LibCallSimplifier {
   Value *optimizeSymmetric(CallInst *CI, LibFunc Func, IRBuilderBase &B);
   Value *optimizeRemquo(CallInst *CI, IRBuilderBase &B);
   Value *optimizeFdim(CallInst *CI, IRBuilderBase &B);
+  Value *optimizeILogB(CallInst *CI, IRBuilderBase &B);
   // Wrapper for all floating point library call optimizations
   Value *optimizeFloatingPointLibCall(CallInst *CI, LibFunc Func,
                                       IRBuilderBase &B);
diff --git a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
index 44a3cbca85d0c..671212038b1c1 100644
--- a/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp
@@ -3239,6 +3239,57 @@ Value *LibCallSimplifier::optimizeFdim(CallInst *CI, IRBuilderBase &B) {
   return ConstantFP::get(CI->getType(), Difference);
 }
 
+Value *LibCallSimplifier::optimizeILogB(CallInst *CI, IRBuilderBase &B) {
+  // Prevent the optimization from re-triggering and causing the fixpoint error.
+  if (CI->use_empty())
+    return nullptr;
+
+  Value *X = CI->getArgOperand(0);
+  Type *FTy = X->getType();
+
+  // Restricted to standard IEEE floats, doubles and fp128
+  if (!FTy->isFloatTy() && !FTy->isDoubleTy() && !FTy->isFP128Ty())
+    return nullptr;
+
+  SimplifyQuery SQ(DL, TLI, DT, AC, CI,
+                   /*UseInstrInfo*/ true, /*CanUseUndef*/ true, DC);
+  KnownFPClass Known = computeKnownFPClass(X, fcAllFlags, SQ);
+
+  // This optimization works on normal floating point arguments. In the future,
+  // if computing known bits for floating point numbers is possible, then the
+  // optimization can work on subnormals with the MSB of the mantissa set.
+  bool IsNormal =
+      Known.isKnownAlways(fcNormal) && Known.KnownFPClasses != fcNone;
+  if (!IsNormal)
+    return nullptr;
+
+  const fltSemantics &Sem = FTy->getFltSemantics();
+  unsigned BitWidth = APFloat::semanticsSizeInBits(Sem);
+  unsigned MantissaBits = APFloat::semanticsPrecision(Sem) - 1;
+  unsigned ExponentBias = 1 - APFloat::semanticsMinExponent(Sem);
+
+  IntegerType *IntTy = B.getIntNTy(BitWidth);
+  Value *Bits = B.CreateBitCast(X, IntTy);
+
+  // Clear the sign bit.
+  Value *SignMask = ConstantInt::get(IntTy, APInt::getSignedMaxValue(BitWidth));
+  Value *NoSign = B.CreateAnd(Bits, SignMask, "ilogb.nosign");
+
+  Value *Shifted = B.CreateLShr(NoSign, MantissaBits, "ilogb.exp");
+  Value *Result = B.CreateSub(Shifted, ConstantInt::get(IntTy, ExponentBias),
+                              "ilogb.result");
+
+  // ilogb returns int, the float's bit-width integer may not match that.
+  Type *RetTy = CI->getType();
+  unsigned RetWidth = RetTy->getIntegerBitWidth();
+
+  if (RetWidth < BitWidth)
+    return B.CreateTrunc(Result, RetTy);
+  if (RetWidth > BitWidth)
+    return B.CreateSExt(Result, RetTy);
+  return Result;
+}
+
 //===----------------------------------------------------------------------===//
 // Integer Library Call Optimizations
 //===----------------------------------------------------------------------===//
@@ -4214,6 +4265,10 @@ Value *LibCallSimplifier::optimizeFloatingPointLibCall(CallInst *CI,
   case LibFunc_nanf:
   case LibFunc_nanl:
     return optimizeNaN(CI);
+  case LibFunc_ilogb:
+  case LibFunc_ilogbf:
+  case LibFunc_ilogbl:
+    return optimizeILogB(CI, Builder);
   default:
     return nullptr;
   }
diff --git a/llvm/test/Transforms/InstCombine/ilogb.ll b/llvm/test/Transforms/InstCombine/ilogb.ll
index 20126278af7e7..a54bb4b165e24 100644
--- a/llvm/test/Transforms/InstCombine/ilogb.ll
+++ b/llvm/test/Transforms/InstCombine/ilogb.ll
@@ -199,5 +199,253 @@ define i32 @ilogb_poison() {
   ret i32 %r
 }
 
-declare i32 @ilogbf(float)
+
+; The following tests are for the ilogb normal optimization where floating-point
+; numbers which are known to be normal can have the exponent extracted using
+; simple bit manipulations.
+
+; copysign is used to create a fp value which won't cause the ilogb call to
+; be immediately constant-folded.
+
+define i32 @ilogb_normal_non_pow2(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_normal_non_pow2(double %sign) {
+; CHECK-NEXT:    ret i32 3
+;
+  %x = call double @llvm.copysign.f64(double 12.5, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogbf_normal_non_pow2(float %sign) {
+; CHECK-LABEL: define i32 @ilogbf_normal_non_pow2(float %sign) {
+; CHECK-NEXT:    ret i32 3
+;
+  %x = call float @llvm.copysign.f32(float 12.5, float %sign)
+  %r = call i32 @ilogbf(float %x)
+  ret i32 %r
+}
+
+define i32 @ilogbl_normal_non_pow2(fp128 %sign) {
+; CHECK-LABEL: define i32 @ilogbl_normal_non_pow2(fp128 %sign) {
+; CHECK-NEXT:    ret i32 3
+;
+  %x = call fp128 @llvm.copysign.f128(fp128 12.5, fp128 %sign)
+  %r = call i32 @ilogbl(fp128 %x)
+  ret i32 %r
+}
+
+
+define i32 @ilogb_normal_pow2(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_normal_pow2(double %sign) {
+; CHECK-NEXT:    ret i32 4
+;
+  %x = call double @llvm.copysign.f64(double 16.0, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogbf_normal_pow2(float %sign) {
+; CHECK-LABEL: define i32 @ilogbf_normal_pow2(float %sign) {
+; CHECK-NEXT:    ret i32 4
+;
+  %x = call float @llvm.copysign.f32(float 16.0, float %sign)
+  %r = call i32 @ilogbf(float %x)
+  ret i32 %r
+}
+
+define i32 @ilogbl_normal_pow2(fp128 %sign) {
+; CHECK-LABEL: define i32 @ilogbl_normal_pow2(fp128 %sign) {
+; CHECK-NEXT:    ret i32 4
+;
+  %x = call fp128 @llvm.copysign.f128(fp128 16.0, fp128 %sign)
+  %r = call i32 @ilogbl(fp128 %x)
+  ret i32 %r
+}
+
+
+define i32 @ilogb_normal_smallest(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_normal_smallest(double %sign) {
+; CHECK-NEXT:    ret i32 -1022
+;
+  %x = call double @llvm.copysign.f64(double 0x1.0p-1022, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogbf_normal_smallest(float %sign) {
+; CHECK-LABEL: define i32 @ilogbf_normal_smallest(float %sign) {
+; CHECK-NEXT:    ret i32 -126
+;
+  %x = call float @llvm.copysign.f32(float 0x1.0p-126, float %sign)
+  %r = call i32 @ilogbf(float %x)
+  ret i32 %r
+}
+
+define i32 @ilogbl_normal_smallest(fp128 %sign) {
+; CHECK-LABEL: define i32 @ilogbl_normal_smallest(fp128 %sign) {
+; CHECK-NEXT:    ret i32 -16382
+;
+  %x = call fp128 @llvm.copysign.f128(fp128 0x1.0p-16382, fp128 %sign)
+  %r = call i32 @ilogbl(fp128 %x)
+  ret i32 %r
+}
+
+
+define i32 @ilogb_normal_largest(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_normal_largest(double %sign) {
+; CHECK-NEXT:    ret i32 1023
+;
+  %x = call double @llvm.copysign.f64(double 0x1.fffffffffffffp+1023, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogbf_normal_largest(float %sign) {
+; CHECK-LABEL: define i32 @ilogbf_normal_largest(float %sign) {
+; CHECK-NEXT:    ret i32 127
+;
+  %x = call float @llvm.copysign.f32(float 0x1.fffffep+127, float %sign)
+  %r = call i32 @ilogbf(float %x)
+  ret i32 %r
+}
+
+define i32 @ilogbl_normal_largest(fp128 %sign) {
+; CHECK-LABEL: define i32 @ilogbl_normal_largest(fp128 %sign) {
+; CHECK-NEXT:    ret i32 16383
+;
+  %x = call fp128 @llvm.copysign.f128(
+      fp128 0x1.ffffffffffffffffffffffffffffp+16383,
+      fp128 %sign)
+  %r = call i32 @ilogbl(fp128 %x)
+  ret i32 %r
+}
+
+define i32 @ilogb_known_normal_operations(double %a) {
+; CHECK-LABEL: @ilogb_known_normal_operations(
+; CHECK-NEXT:    [[AND:%.*]] = call i1 @llvm.is.fpclass.f64(double [[A:%.*]], /* (norm) */ i32 264)
+; CHECK-NEXT:    call void @llvm.assume(i1 [[AND]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call double @llvm.fabs.f64(double [[A]])
+; CHECK-NEXT:    [[ILOGB_NOSIGN:%.*]] = bitcast double [[TMP1]] to i64
+; CHECK-NEXT:    [[ILOGB_EXP:%.*]] = lshr i64 [[ILOGB_NOSIGN]], 52
+; CHECK-NEXT:    [[TMP2:%.*]] = trunc nuw nsw i64 [[ILOGB_EXP]] to i32
+; CHECK-NEXT:    [[R:%.*]] = add nsw i32 [[TMP2]], -1023
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %and = call i1 @llvm.is.fpclass.f64(double %a, i32 264)
+  call void @llvm.assume(i1 %and)
+  %r = call i32 @ilogb(double %a)
+  ret i32 %r
+}
+
+define i32 @ilogbf_known_normal_operations(float %a) {
+; CHECK-LABEL: @ilogbf_known_normal_operations(
+; CHECK-NEXT:    [[AND:%.*]] = call i1 @llvm.is.fpclass.f32(float [[A:%.*]], /* (norm) */ i32 264)
+; CHECK-NEXT:    call void @llvm.assume(i1 [[AND]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call float @llvm.fabs.f32(float [[A]])
+; CHECK-NEXT:    [[ILOGB_NOSIGN:%.*]] = bitcast float [[TMP1]] to i32
+; CHECK-NEXT:    [[ILOGB_EXP:%.*]] = lshr i32 [[ILOGB_NOSIGN]], 23
+; CHECK-NEXT:    [[R:%.*]] = add nsw i32 [[ILOGB_EXP]], -127
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %and = call i1 @llvm.is.fpclass.f32(float %a, i32 264)
+  call void @llvm.assume(i1 %and)
+  %r = call i32 @ilogbf(float %a)
+  ret i32 %r
+}
+
+define i32 @ilogbl_known_normal_operations(fp128 %a) {
+; CHECK-LABEL: @ilogbl_known_normal_operations(
+; CHECK-NEXT:    [[AND:%.*]] = call i1 @llvm.is.fpclass.f128(fp128 [[A:%.*]], /* (norm) */ i32 264)
+; CHECK-NEXT:    call void @llvm.assume(i1 [[AND]])
+; CHECK-NEXT:    [[TMP1:%.*]] = call fp128 @llvm.fabs.f128(fp128 [[A]])
+; CHECK-NEXT:    [[ILOGB_NOSIGN:%.*]] = bitcast fp128 [[TMP1]] to i128
+; CHECK-NEXT:    [[ILOGB_EXP:%.*]] = lshr i128 [[ILOGB_NOSIGN]], 112
+; CHECK-NEXT:    [[TMP2:%.*]] = trunc nuw nsw i128 [[ILOGB_EXP]] to i32
+; CHECK-NEXT:    [[R:%.*]] = add nsw i32 [[TMP2]], -16383
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %and = call i1 @llvm.is.fpclass.f128(fp128 %a, i32 264)
+  call void @llvm.assume(i1 %and)
+  %r = call i32 @ilogbl(fp128 %a)
+  ret i32 %r
+}
+
+
+; Ensure the ilogb normal optimization will work when the normal class can be inferred
+; from the control flow.
+define i32 @ilogb_known_normal_from_fpclass(double %a) {
+; CHECK-LABEL: @ilogb_known_normal_from_fpclass(
+; CHECK-NOT: call i32 @ilogb
+;
+entry:
+  %cmp = fcmp oeq double %a, 0.0
+  br i1 %cmp, label %is_zero, label %not_zero
+not_zero:
+  %nancmp = fcmp oeq double %a, %a
+  br i1 %nancmp, label %not_nan, label %is_nan
+not_nan:
+  %fa = call double @llvm.fabs.f64(double %a)
+  %infcmp = fcmp one double %fa, +inf
+  br i1 %infcmp, label %not_inf, label %is_inf
+not_inf:
+  %subcmp = fcmp oge double %fa, 0x1.0p-1022  ; smallest normal double (2^-1022)
+  br i1 %subcmp, label %not_sub, label %is_sub
+not_sub:
+  %r = call i32 @ilogb(double %a)
+  ret i32 %r
+is_sub:
+  ret i32 0
+is_inf:
+  ret i32 0
+is_nan:
+  ret i32 0
+is_zero:
+  ret i32 0
+}
+
+
+; Ensure the ilogb normal optimization does not happen for non-normal values.
+
+define i32 @ilogb_non_normal_zero(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_non_normal_zero(double %sign) {
+; CHECK:       [[X:%.*]] = call double @llvm.copysign.f64(double {{.*}}, double %sign)
+; CHECK-NEXT:  [[R:%.*]] = call i32 @ilogb(double [[X]])
+; CHECK-NEXT:  ret i32 [[R]]
+;
+  %x = call double @llvm.copysign.f64(double 0.0, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogb_non_normal_subnormal(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_non_normal_subnormal(double %sign) {
+; CHECK:         [[X:%.*]] = call double @llvm.copysign.f64(double {{.*}}, double %sign)
+; CHECK-NEXT:    [[R:%.*]] = call i32 @ilogb(double [[X]])
+; CHECK-NEXT:    ret i32 [[R]]
+;
+  %x = call double @llvm.copysign.f64(double 0x1.ffffffffffffep-1023, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogb_non_normal_inf(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_non_normal_inf(double %sign) {
+; CHECK:         call i32 @ilogb(double
+;
+  %x = call double @llvm.copysign.f64(double +inf, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
+define i32 @ilogb_non_normal_nan(double %sign) {
+; CHECK-LABEL: define i32 @ilogb_non_normal_nan(double %sign) {
+; CHECK:         call i32 @ilogb(double
+;
+  %x = call double @llvm.copysign.f64(double +qnan, double %sign)
+  %r = call i32 @ilogb(double %x)
+  ret i32 %r
+}
+
 declare i32 @ilogb(double)
+declare i32 @ilogbf(float)
+declare i32 @ilogbl(fp128)



More information about the llvm-commits mailing list