[llvm] [InstCombine] Fold comparisons of exact integer-to-FP casts (PR #228787)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 16:30:22 PDT 2026


=?utf-8?q?Dávid_Bolvanský?= <david.bolvansky at gmail.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/228787 at github.com>


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Dávid Bolvanský (davidbolvansky)

<details>
<summary>Changes</summary>

Fold comparisons of two `uitofp` or two `sitofp` casts to integer comparisons when the floating-point significand can represent every source value exactly. Supports equality and ordering, including different integer widths and vectors.

Motivated by GraphicsMagick RGB-to-HWB conversion: the patch reduces conversion time by 9.9% on Mars image pixels and 10.4% on random pixels in an isolated kernel benchmark on Apple M1 Pro (`-O3`, no PGO).

Developed with assistance from OpenAI Codex.


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


3 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+58) 
- (modified) llvm/lib/Transforms/InstCombine/InstCombineInternal.h (+1) 
- (added) llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll (+264) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 289e074d289c9..7ce1d16156f59 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8376,6 +8376,61 @@ Instruction *InstCombinerImpl::visitICmpInst(ICmpInst &I) {
   return Changed ? &I : nullptr;
 }
 
+/// Compare integers directly when both integer-to-FP conversions are exact.
+Instruction *InstCombinerImpl::foldFCmpIntToFP(FCmpInst &I) {
+  Value *A, *B;
+  bool IsSigned;
+  if (match(&I, m_FCmp(m_UIToFP(m_Value(A)), m_UIToFP(m_Value(B)))))
+    IsSigned = false;
+  else if (match(&I, m_FCmp(m_SIToFP(m_Value(A)), m_SIToFP(m_Value(B)))))
+    IsSigned = true;
+  else
+    return nullptr;
+
+  unsigned Width = std::max(A->getType()->getScalarSizeInBits(),
+                            B->getType()->getScalarSizeInBits());
+  // Exact conversions preserve both equality and ordering. Account for the
+  // sign bit, which does not consume floating-point significand bits.
+  int Precision = I.getOperand(0)->getType()->getFPMantissaWidth();
+  if (Precision < 0 || Width - IsSigned > unsigned(Precision))
+    return nullptr;
+
+  ICmpInst::Predicate Pred;
+  switch (I.getPredicate()) {
+  case FCmpInst::FCMP_OEQ:
+  case FCmpInst::FCMP_UEQ:
+    Pred = ICmpInst::ICMP_EQ;
+    break;
+  case FCmpInst::FCMP_ONE:
+  case FCmpInst::FCMP_UNE:
+    Pred = ICmpInst::ICMP_NE;
+    break;
+  case FCmpInst::FCMP_OLT:
+  case FCmpInst::FCMP_ULT:
+    Pred = IsSigned ? ICmpInst::ICMP_SLT : ICmpInst::ICMP_ULT;
+    break;
+  case FCmpInst::FCMP_OLE:
+  case FCmpInst::FCMP_ULE:
+    Pred = IsSigned ? ICmpInst::ICMP_SLE : ICmpInst::ICMP_ULE;
+    break;
+  case FCmpInst::FCMP_OGT:
+  case FCmpInst::FCMP_UGT:
+    Pred = IsSigned ? ICmpInst::ICMP_SGT : ICmpInst::ICMP_UGT;
+    break;
+  case FCmpInst::FCMP_OGE:
+  case FCmpInst::FCMP_UGE:
+    Pred = IsSigned ? ICmpInst::ICMP_SGE : ICmpInst::ICMP_UGE;
+    break;
+  default:
+    return nullptr;
+  }
+
+  Type *IntTy = A->getType()->getWithNewBitWidth(Width);
+  A = Builder.CreateIntCast(A, IntTy, IsSigned);
+  B = Builder.CreateIntCast(B, IntTy, IsSigned);
+  return new ICmpInst(Pred, A, B);
+}
+
 /// Fold fcmp ([us]itofp x, cst) if possible.
 Instruction *InstCombinerImpl::foldFCmpIntToFPConst(FCmpInst &I,
                                                     Instruction *LHSI,
@@ -9411,6 +9466,9 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
     }
   }
 
+  if (Instruction *R = foldFCmpIntToFP(I))
+    return R;
+
   // Handle fcmp with instruction LHS and constant RHS.
   Instruction *LHSI;
   Constant *RHSC;
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 388530edc10da..fbd7b0ac3b8cf 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -730,6 +730,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
                                             GetElementPtrInst *GEP,
                                             CmpInst &ICI,
                                             ConstantInt *AndCst = nullptr);
+  Instruction *foldFCmpIntToFP(FCmpInst &I);
   Instruction *foldFCmpIntToFPConst(FCmpInst &I, Instruction *LHSI,
                                     Constant *RHSC);
   Instruction *foldICmpAddOpConst(Value *X, const APInt &C, CmpPredicate Pred);
diff --git a/llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll b/llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll
new file mode 100644
index 0000000000000..1a50f328fbf07
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll
@@ -0,0 +1,264 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt -S -passes=instcombine < %s | FileCheck %s
+
+define i1 @unsigned_oeq(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_oeq(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp oeq double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_ueq(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_ueq(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp ueq double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_one(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_one(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp one double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_une(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_une(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ne i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp une double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_olt(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_olt(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp olt double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_ult(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_ult(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp ult double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_ole(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_ole(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp ole double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_ule(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_ule(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sle i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp ule double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_ogt(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_ogt(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp ogt double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_ugt(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_ugt(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp ugt double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_oge(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_oge(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp uge i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp oge double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_uge(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @signed_uge(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i32 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %cmp = fcmp uge double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_mixed_widths(i16 %a, i32 %b) {
+; CHECK-LABEL: define i1 @unsigned_mixed_widths(
+; CHECK-SAME: i16 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = zext i16 [[A]] to i32
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i32 [[B]], [[TMP1]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i16 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp oeq double %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_mixed_widths(i8 %a, i16 %b) {
+; CHECK-LABEL: define i1 @signed_mixed_widths(
+; CHECK-SAME: i8 [[A:%.*]], i16 [[B:%.*]]) {
+; CHECK-NEXT:    [[TMP1:%.*]] = sext i8 [[A]] to i16
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sgt i16 [[B]], [[TMP1]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i8 %a to float
+  %fb = sitofp i16 %b to float
+  %cmp = fcmp olt float %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_float_exact_boundary(i24 %a, i24 %b) {
+; CHECK-LABEL: define i1 @unsigned_float_exact_boundary(
+; CHECK-SAME: i24 [[A:%.*]], i24 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i24 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i24 %a to float
+  %fb = uitofp i24 %b to float
+  %cmp = fcmp oeq float %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_float_inexact(i25 %a, i25 %b) {
+; CHECK-LABEL: define i1 @unsigned_float_inexact(
+; CHECK-SAME: i25 [[A:%.*]], i25 [[B:%.*]]) {
+; CHECK-NEXT:    [[FA:%.*]] = uitofp i25 [[A]] to float
+; CHECK-NEXT:    [[FB:%.*]] = uitofp i25 [[B]] to float
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq float [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i25 %a to float
+  %fb = uitofp i25 %b to float
+  %cmp = fcmp oeq float %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_float_exact_boundary(i25 %a, i25 %b) {
+; CHECK-LABEL: define i1 @signed_float_exact_boundary(
+; CHECK-SAME: i25 [[A:%.*]], i25 [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i25 [[A]], [[B]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i25 %a to float
+  %fb = sitofp i25 %b to float
+  %cmp = fcmp olt float %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @signed_float_inexact(i26 %a, i26 %b) {
+; CHECK-LABEL: define i1 @signed_float_inexact(
+; CHECK-SAME: i26 [[A:%.*]], i26 [[B:%.*]]) {
+; CHECK-NEXT:    [[FA:%.*]] = sitofp i26 [[A]] to float
+; CHECK-NEXT:    [[FB:%.*]] = sitofp i26 [[B]] to float
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt float [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i26 %a to float
+  %fb = sitofp i26 %b to float
+  %cmp = fcmp olt float %fa, %fb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_double_inexact(i64 %a, i64 %b) {
+; CHECK-LABEL: define i1 @unsigned_double_inexact(
+; CHECK-SAME: i64 [[A:%.*]], i64 [[B:%.*]]) {
+; CHECK-NEXT:    [[FA:%.*]] = uitofp i64 [[A]] to double
+; CHECK-NEXT:    [[FB:%.*]] = uitofp i64 [[B]] to double
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = uitofp i64 %a to double
+  %fb = uitofp i64 %b to double
+  %cmp = fcmp oeq double %fa, %fb
+  ret i1 %cmp
+}
+
+define <4 x i1> @unsigned_vector(<4 x i16> %a, <4 x i16> %b) {
+; CHECK-LABEL: define <4 x i1> @unsigned_vector(
+; CHECK-SAME: <4 x i16> [[A:%.*]], <4 x i16> [[B:%.*]]) {
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ule <4 x i16> [[A]], [[B]]
+; CHECK-NEXT:    ret <4 x i1> [[CMP]]
+;
+  %fa = uitofp <4 x i16> %a to <4 x float>
+  %fb = uitofp <4 x i16> %b to <4 x float>
+  %cmp = fcmp ule <4 x float> %fa, %fb
+  ret <4 x i1> %cmp
+}
+
+define i1 @mixed_signedness(i32 %a, i32 %b) {
+; CHECK-LABEL: define i1 @mixed_signedness(
+; CHECK-SAME: i32 [[A:%.*]], i32 [[B:%.*]]) {
+; CHECK-NEXT:    [[FA:%.*]] = sitofp i32 [[A]] to double
+; CHECK-NEXT:    [[FB:%.*]] = uitofp i32 [[B]] to double
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp oeq double [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = uitofp i32 %b to double
+  %cmp = fcmp oeq double %fa, %fb
+  ret i1 %cmp
+}

``````````

</details>


https://github.com/llvm/llvm-project/pull/228787


More information about the llvm-commits mailing list