[llvm] [InstCombine] Fold udiv by constant into icmp and add when maximum quotient is small (PR #209321)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 13 16:00:30 PDT 2026


https://github.com/AZero13 updated https://github.com/llvm/llvm-project/pull/209321

>From ecaa4264681060b078c1bd1fa1a515c567960e89 Mon Sep 17 00:00:00 2001
From: AZero13 <gfunni234 at gmail.com>
Date: Mon, 13 Jul 2026 18:11:35 -0400
Subject: [PATCH] [InstCombine] Fold udiv by constant into icmp and add when
 maximum quotient is small

This patch extends InstCombine to optimize udiv operations where the divisor is a constant and the numerator is constrained such that the maximum possible quotient is very small (either 1 or 2).

Previously, InstCombine would only fold Op0 / C -> zext(Op0 >= C) when C was negative (i.e. having the sign bit set), as this syntactically guarantees the quotient is 0 or 1.

This patch generalizes this behavior by using computeConstantRange to dynamically determine the maximum unsigned quotient MaxQ.

If MaxQ == 1, it folds to zext(Op0 >= C) (catching previously missed cases where C is positive, but Op0 is constrained by range metadata or assume()).

If MaxQ == 2, it folds to zext(Op0 >= C) + zext(Op0 >= 2C), which avoids the expensive hardware division and allows targets (like x86 and ARM) to efficiently lower the sequence into optimal conditional accumulations.

This specifically optimizes ubiquitous edge cases like value / INT32_MAX, which previously forced a full division by magic constant expansion, but now lowers flawlessly into native IR comparisons.

Alive2 proof: https://alive2.llvm.org/ce/z/tZG5WS
---
 .../InstCombine/InstCombineMulDivRem.cpp      | 48 ++++++++++++++++++-
 llvm/test/Transforms/InstCombine/div.ll       |  6 ++-
 .../InstCombine/udiv-small-quotient.ll        | 48 +++++++++++++++++++
 3 files changed, 99 insertions(+), 3 deletions(-)
 create mode 100644 llvm/test/Transforms/InstCombine/udiv-small-quotient.ll

diff --git a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
index 39598dbbf6c67..793d98c75a757 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineMulDivRem.cpp
@@ -1775,13 +1775,59 @@ Instruction *InstCombinerImpl::visitUDiv(BinaryOperator &I) {
     }
   }
 
+  Type *Ty = I.getType();
+
   // Op0 / C where C is large (negative) --> zext (Op0 >= C)
   // TODO: Could use isKnownNegative() to handle non-constant values.
-  Type *Ty = I.getType();
   if (match(Op1, m_Negative())) {
     Value *Cmp = Builder.CreateICmpUGE(Op0, Op1);
     return CastInst::CreateZExtOrBitCast(Cmp, Ty);
   }
+
+  // Op0 / C --> sum_i (Op0 >= i*C) if MaxQuotient <= MaxComparisons
+  if (match(Op1, m_APInt(C2)) && !C2->isZero()) {
+    ConstantRange CR = computeConstantRange(Op0, /*ForSigned=*/false,
+                                            SQ.getWithInstruction(&I));
+    APInt MaxOp0 = CR.getUnsignedMax();
+    APInt MaxQ = MaxOp0.udiv(*C2);
+
+    constexpr unsigned MaxComparisons = 3;
+    uint64_t N = MaxQ.getLimitedValue(MaxComparisons + 1);
+
+    if (N == 0)
+      return replaceInstUsesWith(I, ConstantInt::getNullValue(Ty));
+
+    if (N <= MaxComparisons) {
+      Value *Sum = nullptr;
+      APInt Threshold = *C2;
+
+      for (uint64_t Idx = 0; Idx != N; ++Idx) {
+        Value *Cmp =
+            Builder.CreateICmpUGE(Op0, ConstantInt::get(Ty, Threshold));
+        Value *Term = Builder.CreateZExtOrBitCast(Cmp, Ty);
+
+        if (!Sum) {
+          Sum = Term;
+        } else {
+          Sum = Builder.CreateNUWAdd(Sum, Term);
+          if (auto *Add = dyn_cast<BinaryOperator>(Sum))
+            Add->setHasNoSignedWrap();
+        }
+
+        if (Idx + 1 == N)
+          break;
+
+        bool Overflow;
+        Threshold = Threshold.uadd_ov(*C2, Overflow);
+        if (Overflow)
+          break;
+      }
+
+      if (Sum)
+        return replaceInstUsesWith(I, Sum);
+    }
+  }
+
   // Op0 / (sext i1 X) --> zext (Op0 == -1) (if X is 0, the div is undefined)
   if (match(Op1, m_SExt(m_Value(X))) && X->getType()->isIntOrIntVectorTy(1)) {
     Value *Cmp = Builder.CreateICmpEQ(Op0, ConstantInt::getAllOnesValue(Ty));
diff --git a/llvm/test/Transforms/InstCombine/div.ll b/llvm/test/Transforms/InstCombine/div.ll
index 2adf6036d4bcf..7947e677e9561 100644
--- a/llvm/test/Transforms/InstCombine/div.ll
+++ b/llvm/test/Transforms/InstCombine/div.ll
@@ -555,7 +555,8 @@ define <2 x i8> @sdiv_exact_negated_dividend_constant_divisor_vec_overflow(<2 x
 define i32 @test35(i32 %A) {
 ; CHECK-LABEL: @test35(
 ; CHECK-NEXT:    [[AND:%.*]] = and i32 [[A:%.*]], 2147483647
-; CHECK-NEXT:    [[MUL:%.*]] = udiv exact i32 [[AND]], 2147483647
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq i32 [[AND]], 2147483647
+; CHECK-NEXT:    [[MUL:%.*]] = zext i1 [[TMP1]] to i32
 ; CHECK-NEXT:    ret i32 [[MUL]]
 ;
   %and = and i32 %A, 2147483647
@@ -566,7 +567,8 @@ define i32 @test35(i32 %A) {
 define <2 x i32> @test35vec(<2 x i32> %A) {
 ; CHECK-LABEL: @test35vec(
 ; CHECK-NEXT:    [[AND:%.*]] = and <2 x i32> [[A:%.*]], splat (i32 2147483647)
-; CHECK-NEXT:    [[MUL:%.*]] = udiv exact <2 x i32> [[AND]], splat (i32 2147483647)
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp eq <2 x i32> [[AND]], splat (i32 2147483647)
+; CHECK-NEXT:    [[MUL:%.*]] = zext <2 x i1> [[TMP1]] to <2 x i32>
 ; CHECK-NEXT:    ret <2 x i32> [[MUL]]
 ;
   %and = and <2 x i32> %A, <i32 2147483647, i32 2147483647>
diff --git a/llvm/test/Transforms/InstCombine/udiv-small-quotient.ll b/llvm/test/Transforms/InstCombine/udiv-small-quotient.ll
new file mode 100644
index 0000000000000..9b4e40f992f40
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/udiv-small-quotient.ll
@@ -0,0 +1,48 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt < %s -passes=instcombine -S | FileCheck %s
+
+define i32 @udiv_max_q1(i32 %x) {
+; CHECK-LABEL: @udiv_max_q1(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X:%.*]], 100
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp samesign ugt i32 [[X]], 59
+; CHECK-NEXT:    [[DIV:%.*]] = zext i1 [[TMP1]] to i32
+; CHECK-NEXT:    ret i32 [[DIV]]
+;
+  %cmp = icmp ult i32 %x, 100
+  call void @llvm.assume(i1 %cmp)
+  %div = udiv i32 %x, 60
+  ret i32 %div
+}
+
+define i32 @udiv_max_q2(i32 %x) {
+; CHECK-LABEL: @udiv_max_q2(
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp ugt i32 [[X:%.*]], 2147483646
+; CHECK-NEXT:    [[TMP2:%.*]] = zext i1 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp ugt i32 [[X]], -3
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
+; CHECK-NEXT:    [[DIV:%.*]] = add nuw nsw i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    ret i32 [[DIV]]
+;
+  %div = udiv i32 %x, 2147483647
+  ret i32 %div
+}
+
+define i32 @udiv_max_q2_with_assume(i32 %x) {
+; CHECK-LABEL: @udiv_max_q2_with_assume(
+; CHECK-NEXT:    [[CMP:%.*]] = icmp ult i32 [[X:%.*]], 150
+; CHECK-NEXT:    call void @llvm.assume(i1 [[CMP]])
+; CHECK-NEXT:    [[TMP1:%.*]] = icmp samesign ugt i32 [[X]], 59
+; CHECK-NEXT:    [[TMP2:%.*]] = zext i1 [[TMP1]] to i32
+; CHECK-NEXT:    [[TMP3:%.*]] = icmp samesign ugt i32 [[X]], 119
+; CHECK-NEXT:    [[TMP4:%.*]] = zext i1 [[TMP3]] to i32
+; CHECK-NEXT:    [[DIV:%.*]] = add nuw nsw i32 [[TMP2]], [[TMP4]]
+; CHECK-NEXT:    ret i32 [[DIV]]
+;
+  %cmp = icmp ult i32 %x, 150
+  call void @llvm.assume(i1 %cmp)
+  %div = udiv i32 %x, 60
+  ret i32 %div
+}
+
+declare void @llvm.assume(i1)



More information about the llvm-commits mailing list