[llvm] [InstCombine] Fold fcmp of two int-to-fp conversions to an integer compare (PR #210521)

via llvm-commits llvm-commits at lists.llvm.org
Sat Jul 18 09:01:48 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-llvm-transforms

Author: Lucas Ly Ba (lucasly-ba)

<details>
<summary>Changes</summary>

When both integer operands are exactly representable in the float type, comparing their int-to-fp conversions is the same as comparing the integers directly -- int->fp never yields NaN and keeps their ordering:

fcmp pred (uitofp X), (uitofp Y) --> icmp upred X, Y
fcmp pred (sitofp X), (sitofp Y) --> icmp spred X, Y

Fixes #<!-- -->206329

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


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+66) 
- (added) llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll (+129) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 42c2983034e22..dffb0ad611066 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -8942,6 +8942,69 @@ static Instruction *foldFCmpFAbsFSubIntToFP(FCmpInst &I, InstCombinerImpl &IC) {
   return new ICmpInst(ResultPred, A, B);
 }
 
+// fcmp pred (uitofp X), (uitofp Y) --> icmp upred X, Y
+// fcmp pred (sitofp X), (sitofp Y) --> icmp spred X, Y
+// Valid when both integers are exactly representable in the float: int->fp is
+// never NaN and keeps their ordering. canBeCastedExactlyIntToFP() uses range
+// info, so this also fires for range-limited operands (e.g. masked values).
+static Instruction *foldFCmpTwoIntToFP(FCmpInst &I, InstCombinerImpl &IC) {
+  Value *X, *Y;
+  bool IsSigned;
+  if (match(I.getOperand(0), m_UIToFP(m_Value(X))) &&
+      match(I.getOperand(1), m_UIToFP(m_Value(Y))))
+    IsSigned = false;
+  else if (match(I.getOperand(0), m_SIToFP(m_Value(X))) &&
+           match(I.getOperand(1), m_SIToFP(m_Value(Y))))
+    IsSigned = true;
+  else
+    return nullptr;
+
+  // Only handle a matching integer type on both sides.
+  if (X->getType() != Y->getType())
+    return nullptr;
+
+  Type *FPTy = I.getOperand(0)->getType();
+  if (!IC.canBeCastedExactlyIntToFP(X, FPTy, IsSigned, &I) ||
+      !IC.canBeCastedExactlyIntToFP(Y, FPTy, IsSigned, &I))
+    return nullptr;
+
+  // Neither operand can be NaN, so ordered and unordered predicates match.
+  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_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;
+  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_ORD:
+    return IC.replaceInstUsesWith(I, ConstantInt::getTrue(I.getType()));
+  case FCmpInst::FCMP_UNO:
+    return IC.replaceInstUsesWith(I, ConstantInt::getFalse(I.getType()));
+  default:
+    return nullptr;
+  }
+  return new ICmpInst(Pred, X, Y);
+}
+
 static Instruction *foldFCmpWithFloorAndCeil(FCmpInst &I,
                                              InstCombinerImpl &IC) {
   Value *LHS = I.getOperand(0), *RHS = I.getOperand(1);
@@ -9259,6 +9322,9 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
   if (Instruction *R = foldFCmpFAbsFSubIntToFP(I, *this))
     return R;
 
+  if (Instruction *R = foldFCmpTwoIntToFP(I, *this))
+    return R;
+
   if (Instruction *R = foldSqrtWithFcmpZero(I, *this))
     return R;
 
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..efb0daea4ad90
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/fcmp-int-to-fp.ll
@@ -0,0 +1,129 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+; A comparison of two integers converted to floating-point can be performed as
+; an integer comparison when both operands are exactly representable in the
+; float type. Range information lets this fire even when the whole integer type
+; is not exactly representable.
+
+define i1 @uitofp_olt(i32 %a, i32 %b) {
+; CHECK-LABEL: @uitofp_olt(
+; CHECK-NEXT:    [[MA:%.*]] = and i32 [[A:%.*]], 255
+; CHECK-NEXT:    [[MB:%.*]] = and i32 [[B:%.*]], 255
+; CHECK-NEXT:    [[C:%.*]] = icmp samesign ult i32 [[MA]], [[MB]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %ma = and i32 %a, 255
+  %mb = and i32 %b, 255
+  %fa = uitofp i32 %ma to float
+  %fb = uitofp i32 %mb to float
+  %c = fcmp olt float %fa, %fb
+  ret i1 %c
+}
+
+define i1 @uitofp_uge(i32 %a, i32 %b) {
+; CHECK-LABEL: @uitofp_uge(
+; CHECK-NEXT:    [[MA:%.*]] = and i32 [[A:%.*]], 65535
+; CHECK-NEXT:    [[MB:%.*]] = and i32 [[B:%.*]], 65535
+; CHECK-NEXT:    [[C:%.*]] = icmp samesign uge i32 [[MA]], [[MB]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %ma = and i32 %a, 65535
+  %mb = and i32 %b, 65535
+  %fa = uitofp i32 %ma to float
+  %fb = uitofp i32 %mb to float
+  %c = fcmp uge float %fa, %fb
+  ret i1 %c
+}
+
+define i1 @sitofp_ole(i32 %a, i32 %b) {
+; CHECK-LABEL: @sitofp_ole(
+; CHECK-NEXT:    [[MA:%.*]] = and i32 [[A:%.*]], 255
+; CHECK-NEXT:    [[MB:%.*]] = and i32 [[B:%.*]], 255
+; CHECK-NEXT:    [[C:%.*]] = icmp samesign ule i32 [[MA]], [[MB]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %ma = and i32 %a, 255
+  %mb = and i32 %b, 255
+  %fa = sitofp i32 %ma to float
+  %fb = sitofp i32 %mb to float
+  %c = fcmp ole float %fa, %fb
+  ret i1 %c
+}
+
+define i1 @sitofp_one(i32 %a, i32 %b) {
+; CHECK-LABEL: @sitofp_one(
+; CHECK-NEXT:    [[TMP1:%.*]] = xor i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    [[TMP2:%.*]] = and i32 [[TMP1]], 255
+; CHECK-NEXT:    [[C:%.*]] = icmp ne i32 [[TMP2]], 0
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %ma = and i32 %a, 255
+  %mb = and i32 %b, 255
+  %fa = sitofp i32 %ma to float
+  %fb = sitofp i32 %mb to float
+  %c = fcmp one float %fa, %fb
+  ret i1 %c
+}
+
+; i32 always fits exactly in a double, so no range limit is needed.
+define i1 @sitofp_double_ogt(i32 %a, i32 %b) {
+; CHECK-LABEL: @sitofp_double_ogt(
+; CHECK-NEXT:    [[C:%.*]] = icmp sgt i32 [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %fa = sitofp i32 %a to double
+  %fb = sitofp i32 %b to double
+  %c = fcmp ogt double %fa, %fb
+  ret i1 %c
+}
+
+; int->fp is never NaN, so ord is always true.
+define i1 @uitofp_ord(i8 %a, i8 %b) {
+; CHECK-LABEL: @uitofp_ord(
+; CHECK-NEXT:    ret i1 true
+;
+  %fa = uitofp i8 %a to float
+  %fb = uitofp i8 %b to float
+  %c = fcmp ord float %fa, %fb
+  ret i1 %c
+}
+
+define <2 x i1> @uitofp_vec_olt(<2 x i8> %a, <2 x i8> %b) {
+; CHECK-LABEL: @uitofp_vec_olt(
+; CHECK-NEXT:    [[C:%.*]] = icmp ult <2 x i8> [[A:%.*]], [[B:%.*]]
+; CHECK-NEXT:    ret <2 x i1> [[C]]
+;
+  %fa = uitofp <2 x i8> %a to <2 x float>
+  %fb = uitofp <2 x i8> %b to <2 x float>
+  %c = fcmp olt <2 x float> %fa, %fb
+  ret <2 x i1> %c
+}
+
+; Negative: full i32 is not exactly representable in a float.
+define i1 @negative_full_i32(i32 %a, i32 %b) {
+; CHECK-LABEL: @negative_full_i32(
+; CHECK-NEXT:    [[FA:%.*]] = uitofp i32 [[A:%.*]] to float
+; CHECK-NEXT:    [[FB:%.*]] = uitofp i32 [[B:%.*]] to float
+; CHECK-NEXT:    [[C:%.*]] = fcmp olt float [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %fa = uitofp i32 %a to float
+  %fb = uitofp i32 %b to float
+  %c = fcmp olt float %fa, %fb
+  ret i1 %c
+}
+
+; Negative: the two casts have different signedness.
+define i1 @negative_mixed_signedness(i8 %a, i8 %b) {
+; CHECK-LABEL: @negative_mixed_signedness(
+; CHECK-NEXT:    [[FA:%.*]] = uitofp i8 [[A:%.*]] to float
+; CHECK-NEXT:    [[FB:%.*]] = sitofp i8 [[B:%.*]] to float
+; CHECK-NEXT:    [[C:%.*]] = fcmp olt float [[FA]], [[FB]]
+; CHECK-NEXT:    ret i1 [[C]]
+;
+  %fa = uitofp i8 %a to float
+  %fb = sitofp i8 %b to float
+  %c = fcmp olt float %fa, %fb
+  ret i1 %c
+}

``````````

</details>


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


More information about the llvm-commits mailing list