[llvm] [InstCombine] Fold icmp ugt (sdiv exact X, C2), C into a range check (PR #221577)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 6 07:40:10 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: armandeep singh (armandeep23947)
<details>
<summary>Changes</summary>
Fixes #<!-- -->163084.
This PR folds icmp ugt (sdiv exact X, C2), C directly into a range check on X, allowing us to completely drop the division. It handles all sign combinations for the divisor and threshold.
This also generalizes the fold originally added in #<!-- -->76439. That PR only handled the specific edge case where Threshold equals SignedMax / C2. Because this new logic covers any threshold, it naturally handles the SignedMax case too and outputs the exact same icmp ugt X, Prod form that was agreed upon there. We no longer need to special case it.
Alive2:
Positive divisor, fits: https://alive2.llvm.org/ce/z/CnXPW7
Negative divisor, fits: https://alive2.llvm.org/ce/z/xCKoPJ
Positive divisor, overflow: https://alive2.llvm.org/ce/z/mMc87K
Negative divisor, overflow:https://alive2.llvm.org/ce/z/KhDNZM
(Note: I worked out the core logic myself, used Claude to help debug a couple of sign/overflow edge cases and clean up the comments before submitting.)
---
Full diff: https://github.com/llvm/llvm-project/pull/221577.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp (+47)
- (added) llvm/test/Transforms/InstCombine/icmp-sdiv-exact.ll (+147)
``````````diff
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
index 72f08b398e45d..3cb2d16fed97a 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCompares.cpp
@@ -2893,6 +2893,52 @@ Instruction *InstCombinerImpl::foldICmpDivConstant(ICmpInst &Cmp,
if (!match(Y, m_APInt(C2)))
return nullptr;
+ // Fold icmp ugt (sdiv exact X,C2), C into direct range check on X
+ // since it's an exact division, X=Q*C2. we want to check Q> u C
+ // without performing the division actually at runtime.
+ //
+ // note- C2 can't be power of 2 here because visitSDiv already
+ // turns those into ashr shifts before we get here. That guarantees
+ // Prod-1 can never wrap around into signed minimum.
+ if (DivIsSigned && Div->isExact() && Pred == ICmpInst::ICMP_UGT) {
+ // skip div by zero,div by one,div by minus one (handled elsewhere)
+ if (C2->isZero() || C2->isOne() || C2->isAllOnes())
+ return nullptr;
+
+ bool Overflow = false;
+ // Prod= divisor C2 * Threshold C
+ // use signed multiplication to detect if it overflows
+ APInt Prod = C2->smul_ov(C, Overflow);
+
+ if (C2->isStrictlyPositive()) {
+ // Divisor is positive
+ if (Overflow) {
+ // if Prod overflows , no non negative Q can ever be > C
+ // so this is only true when X is negative
+ return new ICmpInst(ICmpInst::ICMP_SLT, X,
+ ConstantInt::getNullValue(Ty));
+ } else {
+ // Q >u C turns into a straightforward check against X
+ return new ICmpInst(ICmpInst::ICMP_UGT, X, ConstantInt::get(Ty, Prod));
+ }
+ } else {
+ // divisor is negative
+ if (Overflow) {
+ // opposite of above: Q is > C only when Q is negative
+ // which means X has to positive here
+ return new ICmpInst(ICmpInst::ICMP_SGT, X,
+ ConstantInt::getNullValue(Ty));
+
+ } else {
+ // it fits: bounded range around zero
+
+ Value *XMinusOne = Builder.CreateSub(X, ConstantInt::get(Ty, 1));
+ return new ICmpInst(ICmpInst::ICMP_ULT, XMinusOne,
+ ConstantInt::get(Ty, Prod - 1));
+ }
+ }
+ }
+
// FIXME: If the operand types don't match the type of the divide
// then don't attempt this transform. The code below doesn't have the
// logic to deal with a signed divide and an unsigned compare (and
@@ -6504,6 +6550,7 @@ Instruction *InstCombinerImpl::foldICmpWithTrunc(ICmpInst &ICmp) {
}
Instruction *InstCombinerImpl::foldICmpWithZextOrSext(ICmpInst &ICmp) {
+
assert(isa<CastInst>(ICmp.getOperand(0)) && "Expected cast for operand 0");
auto *CastOp0 = cast<CastInst>(ICmp.getOperand(0));
Value *X;
diff --git a/llvm/test/Transforms/InstCombine/icmp-sdiv-exact.ll b/llvm/test/Transforms/InstCombine/icmp-sdiv-exact.ll
new file mode 100644
index 0000000000000..149fd7c951906
--- /dev/null
+++ b/llvm/test/Transforms/InstCombine/icmp-sdiv-exact.ll
@@ -0,0 +1,147 @@
+; 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 @test_c1p_c2p_fit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1p_c2p_fit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ugt i8 [[X]], 120
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 10
+ %cmp = icmp ugt i8 %div, 12
+ ret i1 %cmp
+}
+
+define i1 @test_c1p_c2p_nfit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1p_c2p_nfit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 20
+ %cmp = icmp ugt i8 %div, 7
+ ret i1 %cmp
+}
+
+define i1 @test_c1n_c2p_fit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1n_c2p_fit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[DIV_NEG:%.*]] = ashr exact i8 [[X]], 1
+; CHECK-NEXT: [[NOTSUB:%.*]] = add nsw i8 [[DIV_NEG]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[NOTSUB]], -6
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, -2
+ %cmp = icmp ugt i8 %div, 5
+ ret i1 %cmp
+}
+
+define i1 @test_c1n_c2p_nfit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1n_c2p_nfit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, -20
+ %cmp = icmp ugt i8 %div, 7
+ ret i1 %cmp
+}
+
+define i1 @test_c1p_c2n_fit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1p_c2n_fit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ugt i8 [[X]], -10
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 2
+ %cmp = icmp ugt i8 %div, -5
+ ret i1 %cmp
+}
+
+define i1 @test_c1p_c2n_nfit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1p_c2n_nfit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 20
+ %cmp = icmp ugt i8 %div, -7
+ ret i1 %cmp
+}
+
+define i1 @test_c1n_c2n_fit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1n_c2n_fit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[DIV_NEG:%.*]] = ashr exact i8 [[X]], 1
+; CHECK-NEXT: [[TMP1:%.*]] = add nsw i8 [[DIV_NEG]], -1
+; CHECK-NEXT: [[CMP:%.*]] = icmp ult i8 [[TMP1]], 4
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, -2
+ %cmp = icmp ugt i8 %div, -5
+ ret i1 %cmp
+}
+
+define i1 @test_c1n_c2n_nfit(i8 %X) {
+; CHECK-LABEL: define i1 @test_c1n_c2n_nfit(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, -20
+ %cmp = icmp ugt i8 %div, -7
+ ret i1 %cmp
+}
+
+define i1 @test_isnt_exact(i8 %X) {
+; CHECK-LABEL: define i1 @test_isnt_exact(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[DIV:%.*]] = sdiv i8 [[X]], 2
+; CHECK-NEXT: [[CMP:%.*]] = icmp ugt i8 [[DIV]], 5
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv i8 %X, 2
+ %cmp = icmp ugt i8 %div, 5
+ ret i1 %cmp
+}
+
+define i1 @test_overflow_pos_divisor(i8 %X) {
+; CHECK-LABEL: define i1 @test_overflow_pos_divisor(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp slt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 10
+ %cmp = icmp ugt i8 %div, 13
+ ret i1 %cmp
+}
+
+define i1 @test_overflow_neg_divisor(i8 %X) {
+; CHECK-LABEL: define i1 @test_overflow_neg_divisor(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp sgt i8 [[X]], 0
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, -10
+ %cmp = icmp ugt i8 %div, 13
+ ret i1 %cmp
+}
+
+define i1 @test_signedmax_over_divisor(i8 %X) {
+ ; The specific pattern discussed in #76439: Threshold == SignedMax /
+ ; Divisor (127 / 9 = 14 here). Confirms our fold produces "icmp ugt X,
+ ; 126" for it (NOT a further-reduced "icmp slt X, 0") -- matching
+ ; nikic/dtcxzyw's alive2 finding in that thread that this doesn't
+ ; collapse to the sign-bit test in general, so "leave the ugt" is correct.
+ ; Expected: icmp ugt i8 %X, 126
+; CHECK-LABEL: define i1 @test_signedmax_over_divisor(
+; CHECK-SAME: i8 [[X:%.*]]) {
+; CHECK-NEXT: [[CMP:%.*]] = icmp ugt i8 [[X]], 126
+; CHECK-NEXT: ret i1 [[CMP]]
+;
+ %div = sdiv exact i8 %X, 9
+ %cmp = icmp ugt i8 %div, 14
+ ret i1 %cmp
+}
+
``````````
</details>
https://github.com/llvm/llvm-project/pull/221577
More information about the llvm-commits
mailing list