[llvm] [ConstantRange] Bail out early in unsignedMulMayOverflow for wide integer types (PR #192275)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 17 00:59:49 PDT 2026


https://github.com/Firebear518 updated https://github.com/llvm/llvm-project/pull/192275

>From 6ac4714876cbbd68ac841c699f4ac607e5fa988a Mon Sep 17 00:00:00 2001
From: Firebear518 <phj040518 at gmail.com>
Date: Thu, 16 Apr 2026 00:15:11 +0900
Subject: [PATCH 1/4] [ConstantRange] Bail out early in unsignedMulMayOverflow
 for wide integer types

ConstantRange::unsignedMulMayOverflow calls APInt::umul_ov on the range
bounds to determine overflow. APInt multiplication is O(n^2) in the word
count, so integer types wider than ~1024 bits cause multi-second hangs in
InstCombine.

For example, an IR function with i5754496 arguments and two add instructions
causes opt to hang for 30+ seconds via the add->mul fold path:

  add i5754496 %x, %x  ->  mul i5754496 %x, 2
    -> visitMul -> willNotOverflowUnsignedMul
      -> ConstantRange::unsignedMulMayOverflow
        -> APInt::umul_ov  (89914 words, ~4 billion word ops)

Fix: return a conservative MayOverflow early when getBitWidth() > 1024.
MayOverflow is always a valid upper bound; callers that receive it simply
forgo adding 'nuw' to the folded instruction, which is safe.

The sibling functions (unsignedAddMayOverflow, signedSubMayOverflow, etc.)
are unaffected because APInt addition/subtraction is O(n), not O(n^2).

Add a regression test UnsignedMulOverflowWideTypeBailout that completes in
0 ms with the fix (and would hang >30 s without it).

Discovered via llvm-opt-fuzzer.
---
 llvm/lib/IR/ConstantRange.cpp           |  6 ++++++
 llvm/unittests/IR/ConstantRangeTest.cpp | 23 +++++++++++++++++++++++
 2 files changed, 29 insertions(+)

diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 4c10dfb140cbe..9e12ac0c03f3e 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -2261,6 +2261,12 @@ ConstantRange::OverflowResult ConstantRange::unsignedMulMayOverflow(
   if (isEmptySet() || Other.isEmptySet())
     return OverflowResult::MayOverflow;
 
+  // Bail out for very wide integer types: APInt multiplication is O(n^2) in
+  // the number of words, so types wider than ~1024 bits can cause multi-second
+  // hangs. Return a conservative result instead.
+  if (getBitWidth() > 1024)
+    return OverflowResult::MayOverflow;
+
   APInt Min = getUnsignedMin(), Max = getUnsignedMax();
   APInt OtherMin = Other.getUnsignedMin(), OtherMax = Other.getUnsignedMax();
   bool Overflow;
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 13712a76d3edf..8c774d522b122 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2506,6 +2506,29 @@ TEST_F(ConstantRangeTest, UnsignedMulOverflowExhaustive) {
       });
 }
 
+// Regression test: very wide integer types (>1024 bits) triggered an O(n^2)
+// APInt multiplication inside unsignedMulMayOverflow via APInt::umul_ov,
+// causing multi-second hangs in InstCombine (discovered via llvm-opt-fuzzer
+// with i5754496). The fix returns a conservative MayOverflow early for bit
+// widths above 1024.
+TEST_F(ConstantRangeTest, UnsignedMulOverflowWideTypeBailout) {
+  // 2048 bits is above the 1024-bit bailout threshold.
+  const unsigned W = 2048;
+  ConstantRange WFull = ConstantRange::getFull(W);
+  ConstantRange WOne(APInt(W, 1));
+  ConstantRange WMax(APInt::getMaxValue(W));
+  ConstantRange WEmpty = ConstantRange::getEmpty(W);
+
+  // For types wider than 1024 bits all non-empty pairs return MayOverflow
+  // (conservative but always correct).
+  EXPECT_MAY_OVERFLOW(WFull.unsignedMulMayOverflow(WFull));
+  EXPECT_MAY_OVERFLOW(WOne.unsignedMulMayOverflow(WMax));
+  EXPECT_MAY_OVERFLOW(WMax.unsignedMulMayOverflow(WOne));
+  // Empty-set cases are still handled before the width check.
+  EXPECT_MAY_OVERFLOW(WEmpty.unsignedMulMayOverflow(WFull));
+  EXPECT_MAY_OVERFLOW(WFull.unsignedMulMayOverflow(WEmpty));
+}
+
 TEST_F(ConstantRangeTest, SignedAddOverflowExhaustive) {
   TestOverflowExhaustive(
       [](bool &IsOverflowHigh, const APInt &N1, const APInt &N2) {

>From f8080cfe478c873f11323b3456c4e8aa2baa066d Mon Sep 17 00:00:00 2001
From: Firebear518 <phj040518 at gmail.com>
Date: Thu, 16 Apr 2026 12:32:40 +0900
Subject: [PATCH 2/4] [APInt][ConstantRange] Add umul_overflows and use it in
 unsignedMulMayOverflow

Introduce APInt::umul_overflows(), a flag-only overflow check that avoids
computing the full product. The implementation uses an O(n) fast path based
on countl_zero: if the combined leading-zero count is small enough, overflow
is certain without any multiplication. It also handles trivial no-overflow
cases (zero or one operand) before falling back to the existing detection
logic from umul_ov.

Use umul_overflows in ConstantRange::unsignedMulMayOverflow, replacing the
earlier O(n^2) umul_ov calls (which computed an unneeded product) and the
temporary >1024-bit bailout that was added as a stopgap.

Also add a note to docs/Frontend/PerformanceTips.rst advising frontend
authors to avoid extremely wide integer types, since several analysis
algorithms have super-linear complexity in bit width.

Fixes #192277
---
 llvm/docs/Frontend/PerformanceTips.rst  | 17 +++++++++++++++++
 llvm/include/llvm/ADT/APInt.h           |  4 ++++
 llvm/lib/IR/ConstantRange.cpp           | 13 ++-----------
 llvm/lib/Support/APInt.cpp              | 22 ++++++++++++++++++++++
 llvm/unittests/IR/ConstantRangeTest.cpp | 17 +++++++++++------
 5 files changed, 56 insertions(+), 17 deletions(-)

diff --git a/llvm/docs/Frontend/PerformanceTips.rst b/llvm/docs/Frontend/PerformanceTips.rst
index a11c8e3a16857..7cc3222340415 100644
--- a/llvm/docs/Frontend/PerformanceTips.rst
+++ b/llvm/docs/Frontend/PerformanceTips.rst
@@ -83,6 +83,23 @@ There are some exceptions to this rule:
   they are commonly used in ``getelementptr`` instructions or attributes like
   ``sret``.
 
+Avoid extremely wide integer types
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Avoid using integer types with very large bit widths (e.g. ``i5754496``).
+LLVM's analysis and optimization passes operate on ``APInt`` values whose
+internal representation scales with bit width, and several algorithms have
+super-linear complexity in the number of 64-bit words required to represent the
+type. For example, overflow analysis, range intersection, and known-bits
+propagation can all be significantly slower for very wide types than for types
+that fit in one or two machine words.
+
+Practically speaking, types wider than a few thousand bits are unlikely to be
+optimized well and may cause compilation to hang or time out. If your language
+needs to represent very large integers, consider decomposing them into arrays or
+structs of machine-word-sized pieces and implementing big-integer arithmetic in
+IR rather than relying on LLVM's native arbitrary-width integer support.
+
 Avoid loads and stores of non-byte-sized types
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
diff --git a/llvm/include/llvm/ADT/APInt.h b/llvm/include/llvm/ADT/APInt.h
index 683eea4fb19a3..cb60079a2fccb 100644
--- a/llvm/include/llvm/ADT/APInt.h
+++ b/llvm/include/llvm/ADT/APInt.h
@@ -1021,6 +1021,10 @@ class [[nodiscard]] APInt {
   LLVM_ABI APInt sdiv_ov(const APInt &RHS, bool &Overflow) const;
   LLVM_ABI APInt smul_ov(const APInt &RHS, bool &Overflow) const;
   LLVM_ABI APInt umul_ov(const APInt &RHS, bool &Overflow) const;
+  /// Determine whether unsigned multiplication of the two values would
+  /// overflow, without computing the full product. Callers that only need
+  /// the overflow flag (not the product) should prefer this over umul_ov.
+  LLVM_ABI bool umul_overflows(const APInt &RHS) const;
   LLVM_ABI APInt sshl_ov(const APInt &Amt, bool &Overflow) const;
   LLVM_ABI APInt sshl_ov(unsigned Amt, bool &Overflow) const;
   LLVM_ABI APInt ushl_ov(const APInt &Amt, bool &Overflow) const;
diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 9e12ac0c03f3e..3e76c650c195b 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -2261,22 +2261,13 @@ ConstantRange::OverflowResult ConstantRange::unsignedMulMayOverflow(
   if (isEmptySet() || Other.isEmptySet())
     return OverflowResult::MayOverflow;
 
-  // Bail out for very wide integer types: APInt multiplication is O(n^2) in
-  // the number of words, so types wider than ~1024 bits can cause multi-second
-  // hangs. Return a conservative result instead.
-  if (getBitWidth() > 1024)
-    return OverflowResult::MayOverflow;
-
   APInt Min = getUnsignedMin(), Max = getUnsignedMax();
   APInt OtherMin = Other.getUnsignedMin(), OtherMax = Other.getUnsignedMax();
-  bool Overflow;
 
-  (void) Min.umul_ov(OtherMin, Overflow);
-  if (Overflow)
+  if (Min.umul_overflows(OtherMin))
     return OverflowResult::AlwaysOverflowsHigh;
 
-  (void) Max.umul_ov(OtherMax, Overflow);
-  if (Overflow)
+  if (Max.umul_overflows(OtherMax))
     return OverflowResult::MayOverflow;
 
   return OverflowResult::NeverOverflows;
diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index 6aa5fc615a302..8d2ddddd40cbc 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -2025,6 +2025,28 @@ APInt APInt::umul_ov(const APInt &RHS, bool &Overflow) const {
   return Res;
 }
 
+bool APInt::umul_overflows(const APInt &RHS) const {
+  // 0 * x = 0, 1 * x = x — neither overflows an n-bit unsigned integer.
+  if (isZero() || RHS.isZero() || isOne() || RHS.isOne())
+    return false;
+  // Fast path: overflow is certain based on leading-zero count (O(n)).
+  // Avoids the O(n^2) product computation in the overflow-certain branch of
+  // umul_ov — critical for very wide integer types (e.g. i5754496).
+  if (countl_zero() + RHS.countl_zero() + 2 <= BitWidth)
+    return true;
+  // Slow path: reuse the detection logic from umul_ov, but skip computing
+  // the final product since callers of this function don't need it.
+  APInt Res = lshr(1) * RHS;
+  if (Res.isNegative())
+    return true;
+  Res <<= 1;
+  if ((*this)[0]) {
+    Res += RHS;
+    return Res.ult(RHS);
+  }
+  return false;
+}
+
 APInt APInt::sshl_ov(const APInt &ShAmt, bool &Overflow) const {
   return sshl_ov(ShAmt.getLimitedValue(getBitWidth()), Overflow);
 }
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 8c774d522b122..5b410fa827728 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2512,19 +2512,24 @@ TEST_F(ConstantRangeTest, UnsignedMulOverflowExhaustive) {
 // with i5754496). The fix returns a conservative MayOverflow early for bit
 // widths above 1024.
 TEST_F(ConstantRangeTest, UnsignedMulOverflowWideTypeBailout) {
-  // 2048 bits is above the 1024-bit bailout threshold.
+  // Verify correctness and O(n) fast-path for wide integer types (2048-bit).
+  // Previously, unsignedMulMayOverflow called umul_ov which is O(n^2) and
+  // caused multi-second hangs on wide types. Now umul_overflows is used with
+  // an O(n) fast path via countl_zero.
   const unsigned W = 2048;
   ConstantRange WFull = ConstantRange::getFull(W);
   ConstantRange WOne(APInt(W, 1));
   ConstantRange WMax(APInt::getMaxValue(W));
   ConstantRange WEmpty = ConstantRange::getEmpty(W);
 
-  // For types wider than 1024 bits all non-empty pairs return MayOverflow
-  // (conservative but always correct).
+  // [0, MAX] x [0, MAX]: 0*0 ok, MAX*MAX overflows -> MayOverflow.
   EXPECT_MAY_OVERFLOW(WFull.unsignedMulMayOverflow(WFull));
-  EXPECT_MAY_OVERFLOW(WOne.unsignedMulMayOverflow(WMax));
-  EXPECT_MAY_OVERFLOW(WMax.unsignedMulMayOverflow(WOne));
-  // Empty-set cases are still handled before the width check.
+  // 1 * MAX = MAX, fits in W bits -> NeverOverflows (precise result).
+  EXPECT_NEVER_OVERFLOWS(WOne.unsignedMulMayOverflow(WMax));
+  EXPECT_NEVER_OVERFLOWS(WMax.unsignedMulMayOverflow(WOne));
+  // MAX * MAX always overflows; fast path via countl_zero -> AlwaysOverflowsHigh.
+  EXPECT_ALWAYS_OVERFLOWS_HIGH(WMax.unsignedMulMayOverflow(WMax));
+  // Empty-set cases return MayOverflow by convention.
   EXPECT_MAY_OVERFLOW(WEmpty.unsignedMulMayOverflow(WFull));
   EXPECT_MAY_OVERFLOW(WFull.unsignedMulMayOverflow(WEmpty));
 }

>From 9715414c9ba3eb19cbdc8e5a160903581ff124c0 Mon Sep 17 00:00:00 2001
From: Firebear518 <phj040518 at gmail.com>
Date: Thu, 16 Apr 2026 23:23:55 +0900
Subject: [PATCH 3/4] [APInt] Apply leading-zero no-overflow fast path and add
 umul_overflows unit test
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Apply MaxGraey's suggestion from PR review: add a lower-bound check
  if (LZ >= BitWidth) return false;
before the guaranteed-overflow check, avoiding the slow path entirely
when the product is known to fit within BitWidth bits.

The three cases are now:
  LZ >= BitWidth       → no overflow (bitlen(x)+bitlen(y) <= BitWidth)
  LZ < BitWidth-1      → guaranteed overflow
  LZ == BitWidth-1     → borderline: fall through to the existing slow path

Also add APIntTest.umul_overflows per RKSimon's review request:
- exhaustive agreement with umul_ov for 1–5 bit values
- 64-bit spot checks for known overflow/non-overflow pairs
- edge cases (zero, one, UINT64_MAX)
- 2048-bit performance smoke test

Co-Authored-By: Claude Sonnet 4.6 <noreply at anthropic.com>
---
 llvm/lib/Support/APInt.cpp       | 23 ++++++++++----
 llvm/unittests/ADT/APIntTest.cpp | 51 ++++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+), 6 deletions(-)

diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index 8d2ddddd40cbc..48b767a4e5989 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -2029,13 +2029,24 @@ bool APInt::umul_overflows(const APInt &RHS) const {
   // 0 * x = 0, 1 * x = x — neither overflows an n-bit unsigned integer.
   if (isZero() || RHS.isZero() || isOne() || RHS.isOne())
     return false;
-  // Fast path: overflow is certain based on leading-zero count (O(n)).
-  // Avoids the O(n^2) product computation in the overflow-certain branch of
-  // umul_ov — critical for very wide integer types (e.g. i5754496).
-  if (countl_zero() + RHS.countl_zero() + 2 <= BitWidth)
+
+  // Use leading-zero counts to bracket the result bit-length.
+  // bitlen(x) = BitWidth - lz_x, so bitlen(x*y) <= bitlen(x) + bitlen(y)
+  //           = 2*BitWidth - LZ.
+  unsigned LZ = countl_zero() + RHS.countl_zero();
+
+  // No-overflow: product fits in BitWidth bits when LZ >= BitWidth.
+  // (bitlen(x) + bitlen(y) <= BitWidth iff LZ >= BitWidth)
+  if (LZ >= BitWidth)
+    return false;
+
+  // Guaranteed overflow: product needs more than BitWidth bits when LZ < BitWidth-1.
+  // (bitlen(x) + bitlen(y) > BitWidth+1 iff LZ < BitWidth-1)
+  if (LZ < BitWidth - 1)
     return true;
-  // Slow path: reuse the detection logic from umul_ov, but skip computing
-  // the final product since callers of this function don't need it.
+
+  // Borderline (LZ == BitWidth-1): slow path determines the exact answer.
+  // Avoids computing the full O(n^2) product by checking only the overflow flag.
   APInt Res = lshr(1) * RHS;
   if (Res.isNegative())
     return true;
diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp
index ee4c59de34fc4..0b5926e243b05 100644
--- a/llvm/unittests/ADT/APIntTest.cpp
+++ b/llvm/unittests/ADT/APIntTest.cpp
@@ -3419,6 +3419,57 @@ TEST(APIntTest, umul_ov) {
       }
 }
 
+TEST(APIntTest, umul_overflows) {
+  // umul_overflows must agree with umul_ov for all values up to 5 bits.
+  for (unsigned Bits = 1; Bits <= 5; ++Bits) {
+    for (unsigned A = 0; A != 1u << Bits; ++A) {
+      for (unsigned B = 0; B != 1u << Bits; ++B) {
+        APInt N1(Bits, A), N2(Bits, B);
+        bool OvFromOv;
+        (void)N1.umul_ov(N2, OvFromOv);
+        EXPECT_EQ(OvFromOv, N1.umul_overflows(N2))
+            << "Mismatch at " << Bits << "-bit " << A << " * " << B;
+      }
+    }
+  }
+
+  // Spot-check 64-bit known-overflow and known-non-overflow cases.
+  const std::pair<uint64_t, uint64_t> Overflows64[] = {
+      {0x8000000000000000ULL, 2},
+      {0x5555555555555556ULL, 3},
+      {4294967296ULL, 4294967296ULL},
+  };
+  const std::pair<uint64_t, uint64_t> NonOverflows64[] = {
+      {0x7fffffffffffffffULL, 2},
+      {0x5555555555555555ULL, 3},
+      {4294967295ULL, 4294967297ULL},
+  };
+  for (auto &[X, Y] : Overflows64) {
+    EXPECT_TRUE(APInt(64, X).umul_overflows(APInt(64, Y)));
+  }
+  for (auto &[X, Y] : NonOverflows64) {
+    EXPECT_FALSE(APInt(64, X).umul_overflows(APInt(64, Y)));
+  }
+
+  // Edge cases: zero and one never overflow.
+  EXPECT_FALSE(APInt(64, 0).umul_overflows(APInt(64, UINT64_MAX)));
+  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_overflows(APInt(64, 0)));
+  EXPECT_FALSE(APInt(64, 1).umul_overflows(APInt(64, UINT64_MAX)));
+  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_overflows(APInt(64, 1)));
+
+  // Wide-integer performance: 2048-bit values should complete instantly.
+  // (Without the O(n) fast paths these would take seconds for ~1M-bit types.)
+  const unsigned W = 2048;
+  APInt WMax = APInt::getMaxValue(W);
+  APInt WTwo(W, 2);
+  APInt WHalf = APInt::getSignedMaxValue(W); // top bit clear, rest 1s
+  EXPECT_TRUE(WMax.umul_overflows(WTwo));    // 0xFF..F * 2 overflows
+  EXPECT_TRUE(WTwo.umul_overflows(WMax));    // symmetric
+  EXPECT_FALSE(APInt(W, 0).umul_overflows(WMax));
+  EXPECT_FALSE(APInt(W, 1).umul_overflows(WMax));
+  EXPECT_FALSE(WHalf.umul_overflows(WTwo));  // (2^W/2 - 1) * 2 < 2^W, no overflow
+}
+
 TEST(APIntTest, smul_ov) {
   for (unsigned Bits = 1; Bits <= 5; ++Bits)
     for (unsigned A = 0; A != 1u << Bits; ++A)

>From 8a50b92cc3dc03493f4a733965841a22f77922e7 Mon Sep 17 00:00:00 2001
From: Firebear518 <phj040518 at gmail.com>
Date: Fri, 17 Apr 2026 16:59:19 +0900
Subject: [PATCH 4/4] [APInt] Rename umul_overflows to umul_has_overflow; add
 no-overflow fast path to umul_ov

Address review feedback from MaxGraey and RKSimon:

- Rename umul_overflows -> umul_has_overflow and move it to a new
  'Overflow predicates' group in APInt.h, separate from the _ov
  methods that return an APInt product (MaxGraey).
- Add LZ >= BitWidth no-overflow fast path to umul_ov, matching the
  symmetry already present in umul_has_overflow (MaxGraey).
- Fix misleading comment on the borderline slow path: the shift-trick
  multiplication is still O(n^2); it only avoids returning the product
  value, not the multiplication itself (MaxGraey).
- Shrink the PerformanceTips.rst wide-integer type section to a single
  bullet point under "Other Things to Consider" (RKSimon).

Co-Authored-By: Claude Sonnet 4.6 <noreply at anthropic.com>
---
 llvm/docs/Frontend/PerformanceTips.rst  | 21 ++++---------------
 llvm/include/llvm/ADT/APInt.h           |  9 ++++----
 llvm/lib/IR/ConstantRange.cpp           |  4 ++--
 llvm/lib/Support/APInt.cpp              | 19 +++++++++++++----
 llvm/unittests/ADT/APIntTest.cpp        | 28 ++++++++++++-------------
 llvm/unittests/IR/ConstantRangeTest.cpp | 15 +++++++------
 6 files changed, 47 insertions(+), 49 deletions(-)

diff --git a/llvm/docs/Frontend/PerformanceTips.rst b/llvm/docs/Frontend/PerformanceTips.rst
index 7cc3222340415..c16b0e1c049b9 100644
--- a/llvm/docs/Frontend/PerformanceTips.rst
+++ b/llvm/docs/Frontend/PerformanceTips.rst
@@ -83,23 +83,6 @@ There are some exceptions to this rule:
   they are commonly used in ``getelementptr`` instructions or attributes like
   ``sret``.
 
-Avoid extremely wide integer types
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-Avoid using integer types with very large bit widths (e.g. ``i5754496``).
-LLVM's analysis and optimization passes operate on ``APInt`` values whose
-internal representation scales with bit width, and several algorithms have
-super-linear complexity in the number of 64-bit words required to represent the
-type. For example, overflow analysis, range intersection, and known-bits
-propagation can all be significantly slower for very wide types than for types
-that fit in one or two machine words.
-
-Practically speaking, types wider than a few thousand bits are unlikely to be
-optimized well and may cause compilation to hang or time out. If your language
-needs to represent very large integers, consider decomposing them into arrays or
-structs of machine-word-sized pieces and implementing big-integer arithmetic in
-IR rather than relying on LLVM's native arbitrary-width integer support.
-
 Avoid loads and stores of non-byte-sized types
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -174,6 +157,10 @@ As a result, alignment is mandatory for atomic loads and stores.
 Other Things to Consider
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
+#. Avoid extremely wide integer types (e.g. ``i5754496``); several analysis
+   passes have super-linear complexity in the bit width and may cause long
+   compile times for types wider than a few thousand bits.
+
 #. Use ptrtoint/inttoptr sparingly (they interfere with pointer aliasing
    analysis), prefer GEPs
 
diff --git a/llvm/include/llvm/ADT/APInt.h b/llvm/include/llvm/ADT/APInt.h
index cb60079a2fccb..d1334af1d33bc 100644
--- a/llvm/include/llvm/ADT/APInt.h
+++ b/llvm/include/llvm/ADT/APInt.h
@@ -1021,15 +1021,16 @@ class [[nodiscard]] APInt {
   LLVM_ABI APInt sdiv_ov(const APInt &RHS, bool &Overflow) const;
   LLVM_ABI APInt smul_ov(const APInt &RHS, bool &Overflow) const;
   LLVM_ABI APInt umul_ov(const APInt &RHS, bool &Overflow) const;
-  /// Determine whether unsigned multiplication of the two values would
-  /// overflow, without computing the full product. Callers that only need
-  /// the overflow flag (not the product) should prefer this over umul_ov.
-  LLVM_ABI bool umul_overflows(const APInt &RHS) const;
   LLVM_ABI APInt sshl_ov(const APInt &Amt, bool &Overflow) const;
   LLVM_ABI APInt sshl_ov(unsigned Amt, bool &Overflow) const;
   LLVM_ABI APInt ushl_ov(const APInt &Amt, bool &Overflow) const;
   LLVM_ABI APInt ushl_ov(unsigned Amt, bool &Overflow) const;
 
+  // Overflow predicates (flag only, no product or shifted value computed).
+  /// Return true if unsigned multiplication of this and RHS would overflow.
+  /// Prefer this over umul_ov when only the overflow flag is needed.
+  LLVM_ABI bool umul_has_overflow(const APInt &RHS) const;
+
   /// Signed integer floor division operation.
   ///
   /// Rounds towards negative infinity, i.e. 5 / -2 = -3. Iff minimum value
diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 3e76c650c195b..5945855cd738e 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -2264,10 +2264,10 @@ ConstantRange::OverflowResult ConstantRange::unsignedMulMayOverflow(
   APInt Min = getUnsignedMin(), Max = getUnsignedMax();
   APInt OtherMin = Other.getUnsignedMin(), OtherMax = Other.getUnsignedMax();
 
-  if (Min.umul_overflows(OtherMin))
+  if (Min.umul_has_overflow(OtherMin))
     return OverflowResult::AlwaysOverflowsHigh;
 
-  if (Max.umul_overflows(OtherMax))
+  if (Max.umul_has_overflow(OtherMax))
     return OverflowResult::MayOverflow;
 
   return OverflowResult::NeverOverflows;
diff --git a/llvm/lib/Support/APInt.cpp b/llvm/lib/Support/APInt.cpp
index 48b767a4e5989..3cd1048f9927d 100644
--- a/llvm/lib/Support/APInt.cpp
+++ b/llvm/lib/Support/APInt.cpp
@@ -2009,11 +2009,21 @@ APInt APInt::smul_ov(const APInt &RHS, bool &Overflow) const {
 }
 
 APInt APInt::umul_ov(const APInt &RHS, bool &Overflow) const {
-  if (countl_zero() + RHS.countl_zero() + 2 <= BitWidth) {
+  unsigned LZ = countl_zero() + RHS.countl_zero();
+
+  // No-overflow: product fits in BitWidth bits (bitlen(a)+bitlen(b) <= BitWidth).
+  if (LZ >= BitWidth) {
+    Overflow = false;
+    return *this * RHS;
+  }
+
+  // Guaranteed overflow: product needs more than BitWidth bits.
+  if (LZ + 2 <= BitWidth) {
     Overflow = true;
     return *this * RHS;
   }
 
+  // Borderline (LZ == BitWidth-1): use shift trick to determine overflow bit.
   APInt Res = lshr(1) * RHS;
   Overflow = Res.isNegative();
   Res <<= 1;
@@ -2025,7 +2035,7 @@ APInt APInt::umul_ov(const APInt &RHS, bool &Overflow) const {
   return Res;
 }
 
-bool APInt::umul_overflows(const APInt &RHS) const {
+bool APInt::umul_has_overflow(const APInt &RHS) const {
   // 0 * x = 0, 1 * x = x — neither overflows an n-bit unsigned integer.
   if (isZero() || RHS.isZero() || isOne() || RHS.isOne())
     return false;
@@ -2045,8 +2055,9 @@ bool APInt::umul_overflows(const APInt &RHS) const {
   if (LZ < BitWidth - 1)
     return true;
 
-  // Borderline (LZ == BitWidth-1): slow path determines the exact answer.
-  // Avoids computing the full O(n^2) product by checking only the overflow flag.
+  // Borderline (LZ == BitWidth-1): the shift trick from umul_ov determines the
+  // exact overflow bit. The multiplication is still O(n^2), but we avoid
+  // reconstructing the full product value.
   APInt Res = lshr(1) * RHS;
   if (Res.isNegative())
     return true;
diff --git a/llvm/unittests/ADT/APIntTest.cpp b/llvm/unittests/ADT/APIntTest.cpp
index 0b5926e243b05..c5e85c782bf00 100644
--- a/llvm/unittests/ADT/APIntTest.cpp
+++ b/llvm/unittests/ADT/APIntTest.cpp
@@ -3419,15 +3419,15 @@ TEST(APIntTest, umul_ov) {
       }
 }
 
-TEST(APIntTest, umul_overflows) {
-  // umul_overflows must agree with umul_ov for all values up to 5 bits.
+TEST(APIntTest, umul_has_overflow) {
+  // umul_has_overflow must agree with umul_ov for all values up to 5 bits.
   for (unsigned Bits = 1; Bits <= 5; ++Bits) {
     for (unsigned A = 0; A != 1u << Bits; ++A) {
       for (unsigned B = 0; B != 1u << Bits; ++B) {
         APInt N1(Bits, A), N2(Bits, B);
         bool OvFromOv;
         (void)N1.umul_ov(N2, OvFromOv);
-        EXPECT_EQ(OvFromOv, N1.umul_overflows(N2))
+        EXPECT_EQ(OvFromOv, N1.umul_has_overflow(N2))
             << "Mismatch at " << Bits << "-bit " << A << " * " << B;
       }
     }
@@ -3445,17 +3445,17 @@ TEST(APIntTest, umul_overflows) {
       {4294967295ULL, 4294967297ULL},
   };
   for (auto &[X, Y] : Overflows64) {
-    EXPECT_TRUE(APInt(64, X).umul_overflows(APInt(64, Y)));
+    EXPECT_TRUE(APInt(64, X).umul_has_overflow(APInt(64, Y)));
   }
   for (auto &[X, Y] : NonOverflows64) {
-    EXPECT_FALSE(APInt(64, X).umul_overflows(APInt(64, Y)));
+    EXPECT_FALSE(APInt(64, X).umul_has_overflow(APInt(64, Y)));
   }
 
   // Edge cases: zero and one never overflow.
-  EXPECT_FALSE(APInt(64, 0).umul_overflows(APInt(64, UINT64_MAX)));
-  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_overflows(APInt(64, 0)));
-  EXPECT_FALSE(APInt(64, 1).umul_overflows(APInt(64, UINT64_MAX)));
-  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_overflows(APInt(64, 1)));
+  EXPECT_FALSE(APInt(64, 0).umul_has_overflow(APInt(64, UINT64_MAX)));
+  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_has_overflow(APInt(64, 0)));
+  EXPECT_FALSE(APInt(64, 1).umul_has_overflow(APInt(64, UINT64_MAX)));
+  EXPECT_FALSE(APInt(64, UINT64_MAX).umul_has_overflow(APInt(64, 1)));
 
   // Wide-integer performance: 2048-bit values should complete instantly.
   // (Without the O(n) fast paths these would take seconds for ~1M-bit types.)
@@ -3463,11 +3463,11 @@ TEST(APIntTest, umul_overflows) {
   APInt WMax = APInt::getMaxValue(W);
   APInt WTwo(W, 2);
   APInt WHalf = APInt::getSignedMaxValue(W); // top bit clear, rest 1s
-  EXPECT_TRUE(WMax.umul_overflows(WTwo));    // 0xFF..F * 2 overflows
-  EXPECT_TRUE(WTwo.umul_overflows(WMax));    // symmetric
-  EXPECT_FALSE(APInt(W, 0).umul_overflows(WMax));
-  EXPECT_FALSE(APInt(W, 1).umul_overflows(WMax));
-  EXPECT_FALSE(WHalf.umul_overflows(WTwo));  // (2^W/2 - 1) * 2 < 2^W, no overflow
+  EXPECT_TRUE(WMax.umul_has_overflow(WTwo));    // 0xFF..F * 2 overflows
+  EXPECT_TRUE(WTwo.umul_has_overflow(WMax));    // symmetric
+  EXPECT_FALSE(APInt(W, 0).umul_has_overflow(WMax));
+  EXPECT_FALSE(APInt(W, 1).umul_has_overflow(WMax));
+  EXPECT_FALSE(WHalf.umul_has_overflow(WTwo));  // (2^W/2 - 1) * 2 < 2^W, no overflow
 }
 
 TEST(APIntTest, smul_ov) {
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 5b410fa827728..df89e4afe6d6a 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -2506,16 +2506,15 @@ TEST_F(ConstantRangeTest, UnsignedMulOverflowExhaustive) {
       });
 }
 
-// Regression test: very wide integer types (>1024 bits) triggered an O(n^2)
-// APInt multiplication inside unsignedMulMayOverflow via APInt::umul_ov,
-// causing multi-second hangs in InstCombine (discovered via llvm-opt-fuzzer
-// with i5754496). The fix returns a conservative MayOverflow early for bit
-// widths above 1024.
+// Regression test: very wide integer types triggered an O(n^2) APInt
+// multiplication inside unsignedMulMayOverflow via APInt::umul_ov, causing
+// multi-second hangs in InstCombine (discovered via llvm-opt-fuzzer with
+// i5754496). The fix uses umul_has_overflow with O(1)/O(n) fast paths.
 TEST_F(ConstantRangeTest, UnsignedMulOverflowWideTypeBailout) {
-  // Verify correctness and O(n) fast-path for wide integer types (2048-bit).
+  // Verify correctness and fast-path for wide integer types (2048-bit).
   // Previously, unsignedMulMayOverflow called umul_ov which is O(n^2) and
-  // caused multi-second hangs on wide types. Now umul_overflows is used with
-  // an O(n) fast path via countl_zero.
+  // caused multi-second hangs on wide types. Now umul_has_overflow is used
+  // with O(1) fast paths via leading-zero counts.
   const unsigned W = 2048;
   ConstantRange WFull = ConstantRange::getFull(W);
   ConstantRange WOne(APInt(W, 1));



More information about the llvm-commits mailing list