[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