[llvm] [InstCombine] Fold comparisons of normalized integer conversions (PR #228791)

via llvm-commits llvm-commits at lists.llvm.org
Sat Oct 3 17:23:49 PDT 2026


=?utf-8?q?Dávid_Bolvanský?= <david.bolvansky at gmail.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/228791 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 integer-to-FP conversions divided by the same positive constant, including select and min/max trees, when the normalization is provably order-preserving. The fold rejects inexact conversions and possible overflow or underflow, and bounds the expression walk.

Motivated by GraphicsMagick `TransformHSL`: Q16 RGB values are normalized by 65535 before min/max and equality comparisons. These comparisons can use the original integers; this patch removes nine floating-point comparisons in the extracted kernel. Runtime improvement has not yet been measured.

Developed with assistance from OpenAI Codex.


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


2 Files Affected:

- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+148) 
- (added) llvm/test/Transforms/InstCombine/fcmp-normalized-int.ll (+198) 


``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 289e074d289c90d..9dfd3891ed7f6ea 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -39,6 +39,151 @@ using namespace PatternMatch;
 
 #define DEBUG_TYPE "instcombine"
 
+namespace {
+
+// Recover integer operands from a bounded tree of normalized conversions.
+// The normalization must preserve the order of every representable input.
+class NormalizedIntCompare {
+  const APFloat *Divisor = nullptr;
+  Type *IntTy = nullptr;
+  unsigned CastOpcode = 0;
+  unsigned NumVisited = 0;
+  SmallDenseMap<Value *, Value *, 16> IntegerValues;
+
+  static std::optional<bool> isMinimum(Value *V) {
+    auto *II = dyn_cast<IntrinsicInst>(V);
+    if (!II)
+      return std::nullopt;
+    switch (II->getIntrinsicID()) {
+    case Intrinsic::minnum:
+    case Intrinsic::minimum:
+    case Intrinsic::minimumnum:
+      return true;
+    case Intrinsic::maxnum:
+    case Intrinsic::maximum:
+    case Intrinsic::maximumnum:
+      return false;
+    default:
+      return std::nullopt;
+    }
+  }
+
+  bool analyze(Value *V, bool IsNormalized = false, unsigned Depth = 0) {
+    if (Depth == 8 || ++NumVisited > 32)
+      return false;
+
+    Value *X;
+    const APFloat *C;
+    if (match(V, m_FDiv(m_Value(X), m_APFloat(C)))) {
+      if (IsNormalized || !C->isFinite() || C->isNegative() || C->isZero() ||
+          (Divisor && !Divisor->bitwiseIsEqual(*C)))
+        return false;
+      Divisor = C;
+      return analyze(X, true, Depth + 1);
+    }
+    if (auto *SI = dyn_cast<SelectInst>(V))
+      return analyze(SI->getTrueValue(), IsNormalized, Depth + 1) &&
+             analyze(SI->getFalseValue(), IsNormalized, Depth + 1);
+    if (isMinimum(V)) {
+      auto *II = cast<IntrinsicInst>(V);
+      return analyze(II->getArgOperand(0), IsNormalized, Depth + 1) &&
+             analyze(II->getArgOperand(1), IsNormalized, Depth + 1);
+    }
+    auto *CI = dyn_cast<CastInst>(V);
+    if (!IsNormalized || !CI ||
+        (CI->getOpcode() != Instruction::UIToFP &&
+         CI->getOpcode() != Instruction::SIToFP) ||
+        (CastOpcode && CastOpcode != CI->getOpcode()))
+      return false;
+    CastOpcode = CI->getOpcode();
+    Type *Ty = CI->getOperand(0)->getType();
+    if (!IntTy || Ty->getScalarSizeInBits() > IntTy->getScalarSizeInBits())
+      IntTy = Ty;
+    return true;
+  }
+
+  Value *build(Value *V, IRBuilderBase &Builder) {
+    if (Value *Cached = IntegerValues.lookup(V))
+      return Cached;
+    Value *Result;
+    if (auto *SI = dyn_cast<SelectInst>(V)) {
+      Result = Builder.CreateSelect(SI->getCondition(),
+                                    build(SI->getTrueValue(), Builder),
+                                    build(SI->getFalseValue(), Builder));
+    } else if (auto IsMin = isMinimum(V)) {
+      auto *II = cast<IntrinsicInst>(V);
+      bool IsSigned = CastOpcode == Instruction::SIToFP;
+      Intrinsic::ID ID = *IsMin
+                             ? (IsSigned ? Intrinsic::smin : Intrinsic::umin)
+                             : (IsSigned ? Intrinsic::smax : Intrinsic::umax);
+      Result = Builder.CreateBinaryIntrinsic(
+          ID, build(II->getArgOperand(0), Builder),
+          build(II->getArgOperand(1), Builder));
+    } else {
+      auto *I = cast<Instruction>(V);
+      Result = I->getOpcode() == Instruction::FDiv
+                   ? build(I->getOperand(0), Builder)
+                   : Builder.CreateIntCast(I->getOperand(0), IntTy,
+                                           CastOpcode == Instruction::SIToFP);
+    }
+    IntegerValues[V] = Result;
+    return Result;
+  }
+
+public:
+  Instruction *fold(FCmpInst &I, IRBuilderBase &Builder) {
+    int Precision = I.getOperand(0)->getType()->getFPMantissaWidth();
+    if (Precision < 0 || !analyze(I.getOperand(0)) || !analyze(I.getOperand(1)))
+      return nullptr;
+    bool IsSigned = CastOpcode == Instruction::SIToFP;
+    unsigned MagnitudeBits = IntTy->getScalarSizeInBits() - IsSigned;
+    // Leave two bits of precision so the rounding errors of two quotients
+    // cannot bridge the distance between adjacent integer inputs.
+    if (MagnitudeBits + 2 > unsigned(Precision))
+      return nullptr;
+    APFloat Min = APFloat::getOne(Divisor->getSemantics());
+    APFloat Max = scalbn(Min, MagnitudeBits, APFloat::rmTowardPositive);
+    Min.divide(*Divisor, APFloat::rmTowardZero);
+    Max.divide(*Divisor, APFloat::rmTowardPositive);
+    if (!Min.isNormal() || !Max.isNormal())
+      return nullptr;
+
+    CmpInst::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;
+    }
+    return new ICmpInst(Pred, build(I.getOperand(0), Builder),
+                        build(I.getOperand(1), Builder));
+  }
+};
+
+} // namespace
+
 // How many times is a select replaced by one of its operands?
 STATISTIC(NumSel, "Number of select opts");
 
@@ -9411,6 +9556,9 @@ Instruction *InstCombinerImpl::visitFCmpInst(FCmpInst &I) {
     }
   }
 
+  if (Instruction *V = NormalizedIntCompare().fold(I, Builder))
+    return V;
+
   // Handle fcmp with instruction LHS and constant RHS.
   Instruction *LHSI;
   Constant *RHSC;
diff --git a/llvm/test/Transforms/InstCombine/fcmp-normalized-int.ll b/llvm/test/Transforms/InstCombine/fcmp-normalized-int.ll
new file mode 100644
index 000000000000000..32f5adb38e90483
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/fcmp-normalized-int.ll
@@ -0,0 +1,198 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i1 @unsigned_lt(i16 %x, i16 %y) {
+; CHECK-LABEL: @unsigned_lt(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i16 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65535.0
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @signed_ge(i16 %x, i16 %y) {
+; CHECK-LABEL: @signed_ge(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp sge i16 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = sitofp i16 %x to double
+  %b = sitofp i16 %y to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65535.0
+  %cmp = fcmp uge double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @unsigned_eq(i16 %x, i16 %y) {
+; CHECK-LABEL: @unsigned_eq(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp eq i16 [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65535.0
+  %cmp = fcmp ueq double %na, %nb
+  ret i1 %cmp
+}
+
+define <2 x i1> @vector_lt(<2 x i16> %x, <2 x i16> %y) {
+; CHECK-LABEL: @vector_lt(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult <2 x i16> [[X:%.*]], [[Y:%.*]]
+; CHECK-NEXT:    ret <2 x i1> [[CMP]]
+;
+  %a = uitofp <2 x i16> %x to <2 x double>
+  %b = uitofp <2 x i16> %y to <2 x double>
+  %na = fdiv <2 x double> %a, <double 65535.0, double 65535.0>
+  %nb = fdiv <2 x double> %b, <double 65535.0, double 65535.0>
+  %cmp = fcmp olt <2 x double> %na, %nb
+  ret <2 x i1> %cmp
+}
+
+define i1 @different_divisors(i16 %x, i16 %y) {
+; CHECK-LABEL: @different_divisors(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i16 [[X:%.*]] to double
+; CHECK-NEXT:    [[B:%.*]] = uitofp i16 [[Y:%.*]] to double
+; CHECK-NEXT:    [[NA:%.*]] = fdiv double [[A]], 6.553500e+04
+; CHECK-NEXT:    [[NB:%.*]] = fmul nnan double [[B]], f0x3EF0000000000000
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt double [[NA]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65536.0
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @negative_divisor(i16 %x, i16 %y) {
+; CHECK-LABEL: @negative_divisor(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i16 [[X:%.*]] to double
+; CHECK-NEXT:    [[B:%.*]] = uitofp i16 [[Y:%.*]] to double
+; CHECK-NEXT:    [[NA:%.*]] = fdiv double [[A]], -6.553500e+04
+; CHECK-NEXT:    [[NB:%.*]] = fdiv double [[B]], -6.553500e+04
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt double [[NA]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, -65535.0
+  %nb = fdiv double %b, -65535.0
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @insufficient_precision(i32 %x, i32 %y) {
+; CHECK-LABEL: @insufficient_precision(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i32 [[X:%.*]] to float
+; CHECK-NEXT:    [[B:%.*]] = uitofp i32 [[Y:%.*]] to float
+; CHECK-NEXT:    [[NA:%.*]] = fdiv float [[A]], 6.553500e+04
+; CHECK-NEXT:    [[NB:%.*]] = fdiv float [[B]], 6.553500e+04
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt float [[NA]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i32 %x to float
+  %b = uitofp i32 %y to float
+  %na = fdiv float %a, 65535.0
+  %nb = fdiv float %b, 65535.0
+  %cmp = fcmp olt float %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @underflow(i16 %x, i16 %y) {
+; CHECK-LABEL: @underflow(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i16 [[X:%.*]] to double
+; CHECK-NEXT:    [[B:%.*]] = uitofp i16 [[Y:%.*]] to double
+; CHECK-NEXT:    [[NA:%.*]] = fdiv double [[A]], f0x7FEFFFFFFFFFFFFF
+; CHECK-NEXT:    [[NB:%.*]] = fdiv double [[B]], f0x7FEFFFFFFFFFFFFF
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt double [[NA]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, 0x7FEFFFFFFFFFFFFF
+  %nb = fdiv double %b, 0x7FEFFFFFFFFFFFFF
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @overflow(i16 %x, i16 %y) {
+; CHECK-LABEL: @overflow(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i16 [[X:%.*]] to double
+; CHECK-NEXT:    [[B:%.*]] = uitofp i16 [[Y:%.*]] to double
+; CHECK-NEXT:    [[NA:%.*]] = fmul nnan double [[A]], f0x7FD0000000000000
+; CHECK-NEXT:    [[NB:%.*]] = fmul nnan double [[B]], f0x7FD0000000000000
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp olt double [[NA]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %na = fdiv double %a, 0x0010000000000000
+  %nb = fdiv double %b, 0x0010000000000000
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @select_normalized(i16 %x, i16 %y, i16 %z, i1 %c) {
+; CHECK-LABEL: @select_normalized(
+; CHECK-NEXT:    [[S_V_V:%.*]] = select i1 [[C:%.*]], i16 [[X:%.*]], i16 [[Y:%.*]]
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ugt i16 [[S_V_V]], [[Z:%.*]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %d = uitofp i16 %z to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65535.0
+  %nd = fdiv double %d, 65535.0
+  %s = select i1 %c, double %na, double %nb
+  %cmp = fcmp ogt double %s, %nd
+  ret i1 %cmp
+}
+
+define i1 @normalize_minmax(i16 %x, i16 %y, i16 %z) {
+; CHECK-LABEL: @normalize_minmax(
+; CHECK-NEXT:    [[TMP1:%.*]] = call i16 @llvm.smin.i16(i16 [[X:%.*]], i16 [[Y:%.*]])
+; CHECK-NEXT:    [[TMP2:%.*]] = call i16 @llvm.smax.i16(i16 [[X]], i16 [[Z:%.*]])
+; CHECK-NEXT:    [[CMP:%.*]] = icmp slt i16 [[TMP1]], [[TMP2]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = sitofp i16 %x to double
+  %b = sitofp i16 %y to double
+  %d = sitofp i16 %z to double
+  %lo = call double @llvm.minnum.f64(double %a, double %b)
+  %hi = call double @llvm.maxnum.f64(double %a, double %d)
+  %na = fdiv double %lo, 65535.0
+  %nb = fdiv double %hi, 65535.0
+  %cmp = fcmp olt double %na, %nb
+  ret i1 %cmp
+}
+
+define i1 @mixed_normalized(i16 %x, i16 %y, i16 %z, i1 %c) {
+; CHECK-LABEL: @mixed_normalized(
+; CHECK-NEXT:    [[A:%.*]] = uitofp i16 [[X:%.*]] to double
+; CHECK-NEXT:    [[B:%.*]] = uitofp i16 [[Y:%.*]] to double
+; CHECK-NEXT:    [[D:%.*]] = uitofp i16 [[Z:%.*]] to double
+; CHECK-NEXT:    [[NA:%.*]] = fdiv double [[A]], 6.553500e+04
+; CHECK-NEXT:    [[NB:%.*]] = fdiv double [[B]], 6.553500e+04
+; CHECK-NEXT:    [[S:%.*]] = select i1 [[C:%.*]], double [[NA]], double [[D]]
+; CHECK-NEXT:    [[CMP:%.*]] = fcmp ogt double [[S]], [[NB]]
+; CHECK-NEXT:    ret i1 [[CMP]]
+;
+  %a = uitofp i16 %x to double
+  %b = uitofp i16 %y to double
+  %d = uitofp i16 %z to double
+  %na = fdiv double %a, 65535.0
+  %nb = fdiv double %b, 65535.0
+  %s = select i1 %c, double %na, double %d
+  %cmp = fcmp ogt double %s, %nb
+  ret i1 %cmp
+}
+
+declare double @llvm.minnum.f64(double, double)
+declare double @llvm.maxnum.f64(double, double)

``````````

</details>


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


More information about the llvm-commits mailing list