[llvm] [KnownBits] Use min non-zero divisor and quotient lower bound in udiv (PR #209360)
Joel Walker via llvm-commits
llvm-commits at lists.llvm.org
Tue Jul 14 06:22:57 PDT 2026
https://github.com/Joel-Wwalker updated https://github.com/llvm/llvm-project/pull/209360
>From 758a9094630cd8d1244a6497d9fae820b1dd6b92 Mon Sep 17 00:00:00 2001
From: Joel-Wwalker <theagingboy05 at gmail.com>
Date: Mon, 13 Jul 2026 23:29:18 -0400
Subject: [PATCH] [KnownBits] Use min non-zero divisor and quotient lower bound
in udiv
`KnownBits::udiv` was discarding information in two cases. This tightens
both:
- A zero divisor is UB, so when the divisor's known bits permit zero,
use the smallest non-zero value as the minimum divisor instead of
bailing out.
- Compute a lower bound on the quotient (`MinNum / MaxDenom`) and keep
the leading bits it shares with the upper bound, the same way
`ConstantRange::toKnownBits` does.
Both follow from the quotient being monotonic in each operand, with all
four operand bounds attainable.
`KnownBitsTest.BinaryExhaustive` passes with no unsound results, and an
exhaustive width-4 check drops suboptimal outputs from 1790 to 864.
Regenerated the affected MIR and InstCombine tests; two X86 fixed-point
tests lose now-redundant instructions.
Assisted by Claude (Anthropic).
---
llvm/lib/Support/KnownBits.cpp | 27 ++++++++----
.../AArch64/GlobalISel/knownbits-sdiv.mir | 4 +-
.../AArch64/GlobalISel/knownbits-udiv.mir | 44 ++++++++++++++++++-
llvm/test/CodeGen/X86/udiv_fix.ll | 10 -----
llvm/test/CodeGen/X86/udiv_fix_sat.ll | 18 --------
.../test/Transforms/InstCombine/known-bits.ll | 22 ++++++++++
6 files changed, 85 insertions(+), 40 deletions(-)
diff --git a/llvm/lib/Support/KnownBits.cpp b/llvm/lib/Support/KnownBits.cpp
index d9c588780510c..9678225dff5f5 100644
--- a/llvm/lib/Support/KnownBits.cpp
+++ b/llvm/lib/Support/KnownBits.cpp
@@ -1292,16 +1292,27 @@ KnownBits KnownBits::udiv(const KnownBits &LHS, const KnownBits &RHS,
return Known;
}
- // We can figure out the minimum number of upper zero bits by doing
- // MaxNumerator / MinDenominator. If the Numerator gets smaller or Denominator
- // gets larger, the number of upper zero bits increases.
+ // A zero denominator is UB, so the minimum attainable denominator is the
+ // smallest non-zero value consistent with the known bits.
APInt MinDenom = RHS.getMinValue();
- APInt MaxNum = LHS.getMaxValue();
- APInt MaxRes = MinDenom.isZero() ? MaxNum : MaxNum.udiv(MinDenom);
-
- unsigned LeadZ = MaxRes.countLeadingZeros();
+ if (MinDenom.isZero())
+ MinDenom.setBit(RHS.countMinTrailingZeros());
+
+ // The quotient grows when the numerator grows and shrinks when the
+ // denominator grows, and all four operand bounds are attainable, so the
+ // result is in [MinNum / MaxDenom, MaxNum / MinDenom]. Keep the common
+ // leading bits of the two bounds (as ConstantRange::toKnownBits does),
+ // which can include leading ones, not just leading zeros.
+ APInt MaxRes = LHS.getMaxValue().udiv(MinDenom);
+ APInt MinRes = LHS.getMinValue().udiv(RHS.getMaxValue());
+
+ Known = KnownBits::makeConstant(MinRes);
+ if (std::optional<unsigned> DifferentBit =
+ APIntOps::GetMostSignificantDifferentBit(MinRes, MaxRes)) {
+ Known.Zero.clearLowBits(*DifferentBit + 1);
+ Known.One.clearLowBits(*DifferentBit + 1);
+ }
- Known.Zero.setHighBits(LeadZ);
Known = divComputeLowBit(Known, LHS, RHS, Exact);
return Known;
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-sdiv.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-sdiv.mir
index 8b7a9b6bb2cb6..3ddcf2313cabe 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-sdiv.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-sdiv.mir
@@ -8,7 +8,7 @@ body: |
; CHECK-LABEL: name: @Cst
; CHECK-NEXT: %0:_ KnownBits:01100100 SignBits:1
; CHECK-NEXT: %1:_ KnownBits:00000100 SignBits:5
- ; CHECK-NEXT: %2:_ KnownBits:000????? SignBits:3
+ ; CHECK-NEXT: %2:_ KnownBits:00011001 SignBits:3
%0:_(i8) = G_CONSTANT i8 100
%1:_(i8) = G_CONSTANT i8 4
%2:_(i8) = G_SDIV %0, %1
@@ -36,7 +36,7 @@ body: |
; CHECK-LABEL: name: @Exact
; CHECK-NEXT: %0:_ KnownBits:00001100 SignBits:4
; CHECK-NEXT: %1:_ KnownBits:00000100 SignBits:5
- ; CHECK-NEXT: %2:_ KnownBits:000000?1 SignBits:6
+ ; CHECK-NEXT: %2:_ KnownBits:00000011 SignBits:6
%0:_(i8) = G_CONSTANT i8 12
%1:_(i8) = G_CONSTANT i8 4
%2:_(i8) = exact G_SDIV %0, %1
diff --git a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-udiv.mir b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-udiv.mir
index 81f7c0e4f0ca0..353a8baa2a333 100644
--- a/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-udiv.mir
+++ b/llvm/test/CodeGen/AArch64/GlobalISel/knownbits-udiv.mir
@@ -8,7 +8,7 @@ body: |
; CHECK-LABEL: name: @Cst
; CHECK-NEXT: %0:_ KnownBits:01100100 SignBits:1
; CHECK-NEXT: %1:_ KnownBits:00000100 SignBits:5
- ; CHECK-NEXT: %2:_ KnownBits:000????? SignBits:3
+ ; CHECK-NEXT: %2:_ KnownBits:00011001 SignBits:3
%0:_(i8) = G_CONSTANT i8 100
%1:_(i8) = G_CONSTANT i8 4
%2:_(i8) = G_UDIV %0, %1
@@ -36,7 +36,7 @@ body: |
; CHECK-LABEL: name: @Exact
; CHECK-NEXT: %0:_ KnownBits:00001100 SignBits:4
; CHECK-NEXT: %1:_ KnownBits:00000100 SignBits:5
- ; CHECK-NEXT: %2:_ KnownBits:000000?1 SignBits:6
+ ; CHECK-NEXT: %2:_ KnownBits:00000011 SignBits:6
%0:_(i8) = G_CONSTANT i8 12
%1:_(i8) = G_CONSTANT i8 4
%2:_(i8) = exact G_UDIV %0, %1
@@ -53,3 +53,43 @@ body: |
%1:_(i8) = COPY $b1
%2:_(i8) = G_UDIV %0, %1
...
+---
+name: EvenDivisor
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: @EvenDivisor
+ ; CHECK-NEXT: %0:_ KnownBits:???????? SignBits:1
+ ; CHECK-NEXT: %1:_ KnownBits:???????? SignBits:1
+ ; CHECK-NEXT: %2:_ KnownBits:11111110 SignBits:7
+ ; CHECK-NEXT: %3:_ KnownBits:???????0 SignBits:1
+ ; CHECK-NEXT: %4:_ KnownBits:0??????? SignBits:1
+ %0:_(i8) = COPY $b0
+ %1:_(i8) = COPY $b1
+ %2:_(i8) = G_CONSTANT i8 -2
+ %3:_(i8) = G_AND %1, %2
+ %4:_(i8) = G_UDIV %0, %3
+...
+---
+name: RangeKnownOnes
+body: |
+ bb.0:
+ ; CHECK-LABEL: name: @RangeKnownOnes
+ ; CHECK-NEXT: %0:_ KnownBits:???????? SignBits:1
+ ; CHECK-NEXT: %1:_ KnownBits:???????? SignBits:1
+ ; CHECK-NEXT: %2:_ KnownBits:11000000 SignBits:2
+ ; CHECK-NEXT: %3:_ KnownBits:11?????? SignBits:2
+ ; CHECK-NEXT: %4:_ KnownBits:00000001 SignBits:7
+ ; CHECK-NEXT: %5:_ KnownBits:0000000? SignBits:7
+ ; CHECK-NEXT: %6:_ KnownBits:00000010 SignBits:6
+ ; CHECK-NEXT: %7:_ KnownBits:0000001? SignBits:6
+ ; CHECK-NEXT: %8:_ KnownBits:01?????? SignBits:1
+ %0:_(i8) = COPY $b0
+ %1:_(i8) = COPY $b1
+ %2:_(i8) = G_CONSTANT i8 -64
+ %3:_(i8) = G_OR %0, %2
+ %4:_(i8) = G_CONSTANT i8 1
+ %5:_(i8) = G_AND %1, %4
+ %6:_(i8) = G_CONSTANT i8 2
+ %7:_(i8) = G_OR %5, %6
+ %8:_(i8) = G_UDIV %3, %7
+...
diff --git a/llvm/test/CodeGen/X86/udiv_fix.ll b/llvm/test/CodeGen/X86/udiv_fix.ll
index 6e653a67fba2b..b2ad846b1be7e 100644
--- a/llvm/test/CodeGen/X86/udiv_fix.ll
+++ b/llvm/test/CodeGen/X86/udiv_fix.ll
@@ -80,11 +80,6 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X64-NEXT: # kill: def $ax killed $ax killed $eax
; X64-NEXT: xorl %edx, %edx
; X64-NEXT: divw %cx
-; X64-NEXT: # kill: def $ax killed $ax def $eax
-; X64-NEXT: addl %eax, %eax
-; X64-NEXT: cwtl
-; X64-NEXT: shrl %eax
-; X64-NEXT: # kill: def $ax killed $ax killed $eax
; X64-NEXT: retq
;
; X86-LABEL: func3:
@@ -97,11 +92,6 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X86-NEXT: # kill: def $ax killed $ax killed $eax
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: divw %cx
-; X86-NEXT: # kill: def $ax killed $ax def $eax
-; X86-NEXT: addl %eax, %eax
-; X86-NEXT: cwtl
-; X86-NEXT: shrl %eax
-; X86-NEXT: # kill: def $ax killed $ax killed $eax
; X86-NEXT: retl
%y2 = sext i8 %y to i15
%y3 = shl i15 %y2, 7
diff --git a/llvm/test/CodeGen/X86/udiv_fix_sat.ll b/llvm/test/CodeGen/X86/udiv_fix_sat.ll
index 2c4716bb74e70..565918e5b159b 100644
--- a/llvm/test/CodeGen/X86/udiv_fix_sat.ll
+++ b/llvm/test/CodeGen/X86/udiv_fix_sat.ll
@@ -95,15 +95,6 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X64-NEXT: # kill: def $ax killed $ax killed $eax
; X64-NEXT: xorl %edx, %edx
; X64-NEXT: divw %cx
-; X64-NEXT: # kill: def $ax killed $ax def $eax
-; X64-NEXT: movzwl %ax, %ecx
-; X64-NEXT: cmpl $32767, %ecx # imm = 0x7FFF
-; X64-NEXT: movl $32767, %ecx # imm = 0x7FFF
-; X64-NEXT: cmovbl %eax, %ecx
-; X64-NEXT: addl %ecx, %ecx
-; X64-NEXT: movswl %cx, %eax
-; X64-NEXT: shrl %eax
-; X64-NEXT: # kill: def $ax killed $ax killed $eax
; X64-NEXT: retq
;
; X86-LABEL: func3:
@@ -116,15 +107,6 @@ define i16 @func3(i15 %x, i8 %y) nounwind {
; X86-NEXT: # kill: def $ax killed $ax killed $eax
; X86-NEXT: xorl %edx, %edx
; X86-NEXT: divw %cx
-; X86-NEXT: # kill: def $ax killed $ax def $eax
-; X86-NEXT: movzwl %ax, %ecx
-; X86-NEXT: cmpl $32767, %ecx # imm = 0x7FFF
-; X86-NEXT: movl $32767, %ecx # imm = 0x7FFF
-; X86-NEXT: cmovbl %eax, %ecx
-; X86-NEXT: addl %ecx, %ecx
-; X86-NEXT: movswl %cx, %eax
-; X86-NEXT: shrl %eax
-; X86-NEXT: # kill: def $ax killed $ax killed $eax
; X86-NEXT: retl
%y2 = sext i8 %y to i15
%y3 = shl i15 %y2, 7
diff --git a/llvm/test/Transforms/InstCombine/known-bits.ll b/llvm/test/Transforms/InstCombine/known-bits.ll
index acf09bc03c1eb..1981445f3bea6 100644
--- a/llvm/test/Transforms/InstCombine/known-bits.ll
+++ b/llvm/test/Transforms/InstCombine/known-bits.ll
@@ -2445,3 +2445,25 @@ define <vscale x 4 x i32> @scalable_add_to_disjoint_or(i8 %x, <vscale x 4 x i32>
declare void @dummy()
declare void @use(i1)
declare void @sink(i8)
+
+define i1 @udiv_by_even_divisor_ult_128(i8 %x, i8 %y) {
+; CHECK-LABEL: @udiv_by_even_divisor_ult_128(
+; CHECK-NEXT: ret i1 true
+;
+ %d = and i8 %y, -2
+ %q = udiv i8 %x, %d
+ %r = icmp ult i8 %q, 128
+ ret i1 %r
+}
+
+define i1 @udiv_high_known_ones_ugt_63(i8 %x, i8 %y) {
+; CHECK-LABEL: @udiv_high_known_ones_ugt_63(
+; CHECK-NEXT: ret i1 true
+;
+ %n = or i8 %x, -64
+ %d0 = and i8 %y, 1
+ %d = or i8 %d0, 2
+ %q = udiv i8 %n, %d
+ %r = icmp ugt i8 %q, 63
+ ret i1 %r
+}
More information about the llvm-commits
mailing list