[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