[llvm] [LVI] Merge ranges reliably and precisely (PR #173714)

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Tue Dec 30 08:30:17 PST 2025


https://github.com/Camsyn updated https://github.com/llvm/llvm-project/pull/173714

>From c37de9825ad4425348c381d9e59347279805102d Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Wed, 24 Dec 2025 23:14:08 +0800
Subject: [PATCH 1/3] Before-commit test

---
 .../Analysis/LazyValueAnalysis/group-merge.ll | 279 ++++++++++++++++++
 1 file changed, 279 insertions(+)
 create mode 100644 llvm/test/Analysis/LazyValueAnalysis/group-merge.ll

diff --git a/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll b/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll
new file mode 100644
index 0000000000000..d3f2a49812d08
--- /dev/null
+++ b/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll
@@ -0,0 +1,279 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes="correlated-propagation" -S | FileCheck %s
+
+; This test is reduced from b2ShapeCast at box2d/distance.ll, where correlated-propagation
+; performs different behavior under different orderings of the IVs in phi.
+define void @foo(i32 %v) {
+; CHECK-LABEL: define void @foo(
+; CHECK-SAME: i32 [[V:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 false, label %[[CASE2:.*]], label %[[IF_ELSE:.*]]
+; CHECK:       [[IF_ELSE]]:
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 3, label %[[EXIT:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi i32 [ 1, %[[CASE1]] ], [ 2, %[[CASE2]] ], [ [[V]], %[[IF_ELSE]] ]
+; CHECK-NEXT:    br label %[[SWITCH:.*]]
+; CHECK:       [[SWITCH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[UNREACH:.*:]]
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 false, label %case2, label %if.else
+
+if.else:                                          ; preds = %entry
+  switch i32 %v, label %default [
+  i32 3, label %exit
+  i32 2, label %case2
+  i32 1, label %case1
+  ]
+
+case1:                                            ; preds = %if.else
+  br label %default
+
+case2:                                            ; preds = %if.else, %entry
+  br label %default
+
+default:                                          ; preds = %case2, %case1, %if.else
+  %phi = phi i32 [ 1, %case1 ], [ 2, %case2 ], [ %v, %if.else ]
+  br label %switch
+
+switch:                                           ; preds = %default
+  switch i32 %phi, label %exit [
+  i32 3, label %unreach
+  ]
+
+unreach:                                          ; preds = %switch
+  br label %exit
+
+exit:                                             ; preds = %unreach, %switch, %if.else
+  ret void
+}
+
+define void @foo_with_diff_IV_order(i32 %v) {
+; CHECK-LABEL: define void @foo_with_diff_IV_order(
+; CHECK-SAME: i32 [[V:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    br i1 false, label %[[CASE2:.*]], label %[[IF_ELSE:.*]]
+; CHECK:       [[IF_ELSE]]:
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 3, label %[[EXIT:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi i32 [ 2, %[[CASE2]] ], [ [[V]], %[[IF_ELSE]] ], [ 1, %[[CASE1]] ]
+; CHECK-NEXT:    br label %[[SWITCH:.*]]
+; CHECK:       [[SWITCH]]:
+; CHECK-NEXT:    switch i32 [[PHI]], label %[[EXIT]] [
+; CHECK-NEXT:      i32 3, label %[[UNREACH:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[UNREACH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  br i1 false, label %case2, label %if.else
+
+if.else:                                          ; preds = %entry
+  switch i32 %v, label %default [
+  i32 3, label %exit
+  i32 2, label %case2
+  i32 1, label %case1
+  ]
+
+case1:                                            ; preds = %if.else
+  br label %default
+
+case2:                                            ; preds = %if.else, %entry
+  br label %default
+
+default:                                          ; preds = %case2, %case1, %if.else
+  %phi = phi i32 [ 2, %case2 ], [ %v, %if.else ], [ 1, %case1 ]
+  br label %switch
+
+switch:                                           ; preds = %default
+  switch i32 %phi, label %exit [
+  i32 3, label %unreach
+  ]
+
+unreach:                                          ; preds = %switch
+  br label %exit
+
+exit:                                             ; preds = %unreach, %switch, %if.else
+  ret void
+}
+
+define void @foo_nonlocal(i32 %v) {
+; CHECK-LABEL: define void @foo_nonlocal(
+; CHECK-SAME: i32 [[V:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 3, label %[[EXIT:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    br label %[[SWITCH:.*]]
+; CHECK:       [[SWITCH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[UNREACH:.*:]]
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  switch i32 %v, label %default [
+  i32 3, label %exit
+  i32 2, label %case2
+  i32 1, label %case1
+  ]
+
+case1:                                            ; preds = %entry
+  br label %default
+
+case2:                                            ; preds = %entry
+  br label %default
+
+default:                                          ; preds = %case2, %case1, %entry
+  br label %switch
+
+switch:                                           ; preds = %default
+  switch i32 %v, label %exit [
+  i32 3, label %unreach
+  ]
+
+unreach:                                          ; preds = %switch
+  br label %exit
+
+exit:                                             ; preds = %unreach, %switch, %entry
+  ret void
+}
+
+define void @foo_with_diff_pred_order(i32 %v) {
+; CHECK-LABEL: define void @foo_with_diff_pred_order(
+; CHECK-SAME: i32 [[V:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    switch i32 [[V]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i32 3, label %[[EXIT:.*]]
+; CHECK-NEXT:      i32 2, label %[[CASE2:.*]]
+; CHECK-NEXT:      i32 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[SWITCH:.*]]
+; CHECK:       [[CASE2]]:
+; CHECK-NEXT:    br label %[[CASE1]]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    br label %[[CASE1]]
+; CHECK:       [[SWITCH]]:
+; CHECK-NEXT:    switch i32 [[V]], label %[[EXIT]] [
+; CHECK-NEXT:      i32 3, label %[[UNREACH:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[UNREACH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[EXIT]]:
+; CHECK-NEXT:    ret void
+;
+entry:
+  switch i32 %v, label %default [
+  i32 3, label %exit
+  i32 2, label %case2
+  i32 1, label %case1
+  ]
+
+case1:                                            ; preds = %default, %case2, %entry
+  br label %switch
+
+case2:                                            ; preds = %entry
+  br label %case1
+
+default:                                          ; preds = %entry
+  br label %case1
+
+switch:                                           ; preds = %case1
+  switch i32 %v, label %exit [
+  i32 3, label %unreach
+  ]
+
+unreach:                                          ; preds = %switch
+  br label %exit
+
+exit:                                             ; preds = %unreach, %switch, %entry
+  ret void
+}
+
+define i32 @bar(i32 range(i32 4, 1) %v1, i8 %v2) {
+; CHECK-LABEL: define i32 @bar(
+; CHECK-SAME: i32 range(i32 4, 1) [[V1:%.*]], i8 [[V2:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    switch i8 [[V2]], label %[[DEFAULT:.*]] [
+; CHECK-NEXT:      i8 0, label %[[CASE0:.*]]
+; CHECK-NEXT:      i8 1, label %[[CASE1:.*]]
+; CHECK-NEXT:    ]
+; CHECK:       [[CASE0]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[CASE1]]:
+; CHECK-NEXT:    br label %[[DEFAULT]]
+; CHECK:       [[DEFAULT]]:
+; CHECK-NEXT:    [[RET:%.*]] = phi i32 [ 2, %[[CASE1]] ], [ [[V1]], %[[ENTRY]] ], [ 1, %[[CASE0]] ]
+; CHECK-NEXT:    ret i32 [[RET]]
+;
+entry:
+  switch i8 %v2, label %default [
+  i8 0, label %case0
+  i8 1, label %case1
+  ]
+
+case0:                                            ; preds = %entry
+  br label %default
+
+case1:                                            ; preds = %entry
+  br label %default
+
+default:                                          ; preds = %case1, %case0, %entry
+  %ret = phi i32 [ 2, %case1 ], [ %v1, %entry ], [ 1, %case0 ]
+  ret i32 %ret
+}
+
+define <2 x i8> @phi_vector_merge(i1 %c) {
+; CHECK-LABEL: define range(i8 62, 4) <2 x i8> @phi_vector_merge(
+; CHECK-SAME: i1 [[C:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*]]:
+; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[JOIN:.*]]
+; CHECK:       [[IF]]:
+; CHECK-NEXT:    br label %[[JOIN]]
+; CHECK:       [[JOIN]]:
+; CHECK-NEXT:    [[PHI:%.*]] = phi <2 x i8> [ <i8 0, i8 -76>, %[[ENTRY]] ], [ <i8 60, i8 120>, %[[IF]] ]
+; CHECK-NEXT:    [[ADD:%.*]] = add <2 x i8> [[PHI]], <i8 2, i8 3>
+; CHECK-NEXT:    ret <2 x i8> [[ADD]]
+;
+entry:
+  br i1 %c, label %if, label %join
+
+if:                                               ; preds = %entry
+  br label %join
+
+join:                                             ; preds = %if, %entry
+  %phi = phi <2 x i8> [ <i8 0, i8 -76>, %entry ], [ <i8 60, i8 120>, %if ]
+  %add = add <2 x i8> %phi, <i8 2, i8 3>
+  ret <2 x i8> %add
+}

>From 2de29c65cb703cce0f9caaa48f25d3a60688f842 Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Sat, 27 Dec 2025 18:06:19 +0800
Subject: [PATCH 2/3] feat: merge ranges precisely and stably

---
 llvm/include/llvm/Analysis/ValueLattice.h |  12 +-
 llvm/include/llvm/IR/ConstantRange.h      |   9 +
 llvm/lib/Analysis/LazyValueInfo.cpp       | 333 +++++++++++++++++++---
 llvm/lib/IR/ConstantRange.cpp             |  91 ++++++
 llvm/unittests/IR/ConstantRangeTest.cpp   |  59 ++++
 5 files changed, 461 insertions(+), 43 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueLattice.h b/llvm/include/llvm/Analysis/ValueLattice.h
index 262ff58f07dfd..9c413b6edc083 100644
--- a/llvm/include/llvm/Analysis/ValueLattice.h
+++ b/llvm/include/llvm/Analysis/ValueLattice.h
@@ -235,7 +235,14 @@ class ValueLatticeElement {
   bool isUndef() const { return Tag == undef; }
   bool isUnknown() const { return Tag == unknown; }
   bool isUnknownOrUndef() const { return Tag == unknown || Tag == undef; }
-  bool isConstant() const { return Tag == constant; }
+  /// Return true if this value is a constant. If \p IntVecOnly is true, only
+  /// integer vector constants are considered.
+  bool isConstant(bool IntVecOnly = false) const {
+    return Tag == constant &&
+           (!IntVecOnly ||
+            (ConstVal->getType()->isVectorTy() &&
+             ConstVal->getType()->getScalarType()->isIntegerTy()));
+  }
   bool isNotConstant() const { return Tag == notconstant; }
   bool isConstantRangeIncludingUndef() const {
     return Tag == constantrange_including_undef;
@@ -430,8 +437,7 @@ class ValueLatticeElement {
       if (RHS.isUndef())
         return false;
       // If the constant is a vector of integers, try to treat it as a range.
-      if (getConstant()->getType()->isVectorTy() &&
-          getConstant()->getType()->getScalarType()->isIntegerTy()) {
+      if (isConstant(/*IntVecOnly*/ true)) {
         ConstantRange L = getConstant()->toConstantRange();
         ConstantRange NewR = L.unionWith(
             RHS.asConstantRange(L.getBitWidth(), /*UndefAllowed=*/true));
diff --git a/llvm/include/llvm/IR/ConstantRange.h b/llvm/include/llvm/IR/ConstantRange.h
index f4d4f1f555fa4..1353f617af263 100644
--- a/llvm/include/llvm/IR/ConstantRange.h
+++ b/llvm/include/llvm/IR/ConstantRange.h
@@ -346,6 +346,15 @@ class [[nodiscard]] ConstantRange {
   LLVM_ABI ConstantRange unionWith(const ConstantRange &CR,
                                    PreferredRangeType Type = Smallest) const;
 
+  /// Return the range that results from the union of @param Ranges.
+  /// The resultant range is guaranteed to include the elements of all sets, but
+  /// may contain more. For example, [3, 9) union [12,15) is [3, 15), which
+  /// includes 9, 10, and 11, which were not included in either set before.
+  /// In fact, this function return the complement of the maximal gap among the
+  /// input intervals.
+  LLVM_ABI static ConstantRange unionOf(ArrayRef<ConstantRange> Ranges,
+                                        PreferredRangeType Type = Smallest);
+
   /// Intersect the two ranges and return the result if it can be represented
   /// exactly, otherwise return std::nullopt.
   LLVM_ABI std::optional<ConstantRange>
diff --git a/llvm/lib/Analysis/LazyValueInfo.cpp b/llvm/lib/Analysis/LazyValueInfo.cpp
index df75999eb6080..dcd872c27fc0e 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -12,8 +12,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Analysis/LazyValueInfo.h"
+#include "llvm/ADT/APInt.h"
 #include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/Analysis/AssumptionCache.h"
 #include "llvm/Analysis/ConstantFolding.h"
 #include "llvm/Analysis/InstructionSimplify.h"
@@ -40,6 +42,7 @@
 #include "llvm/Support/FormattedStream.h"
 #include "llvm/Support/KnownBits.h"
 #include "llvm/Support/raw_ostream.h"
+#include <map>
 #include <optional>
 using namespace llvm;
 using namespace PatternMatch;
@@ -717,6 +720,248 @@ bool LazyValueInfoImpl::isNonNullAtEndOfBlock(Value *Val, BasicBlock *BB) {
   });
 }
 
+/// RAII helper for merging ValueLatticeElement into \p Result with a stable and
+/// precise ConstantRange.
+///
+/// Instead of incrementally unioning ConstantRanges (which is order-dependent
+/// and may over-approximate), this merger *collects* intervals and defers the
+/// actual union to destruction, where \c ConstantRange::unionOf is used to
+/// compute a deterministic and most-accurate result.
+///
+/// Example:
+///   Incremental merge:
+///     [0,1) \/ [2,3) \/ [3,0)  -> overdefined (order-dependent)
+///   Collected + unionOf:
+///     {[0,1), [2,3), [3,0)}    -> exact [2,1)
+///
+/// The merger updates \p Result eagerly (without deferring to destruction) in
+/// the following cases:
+///  (1) Collected ranges already form the full set exactly, in which case
+///      \p Result is immediately marked overdefined.
+///  (2) \p RHS cannot be represented as a ConstantRange, so it is merged
+///      directly via \c Result.mergeIn(RHS).
+///  (3) Merging a ConstantRange with a \c constant or \c notconstant lattice
+///      state, which by definition yields overdefined.
+///
+/// Wrapped ConstantRanges are split during collection; the final union is
+/// reconstructed reliably by \c ConstantRange::unionOf.
+class ValueLatticeMerger {
+  using MergeOptions = ValueLatticeElement::MergeOptions;
+
+public:
+  ValueLatticeMerger(ValueLatticeElement &Result) : Result(Result) {
+    // Try to collect some ranges from initial Result
+    InitialResultAsCR = tryToInsertRange(Result);
+  }
+  ~ValueLatticeMerger() {
+    assert((InitialResultAsCR || !Result.isConstantRange()) &&
+           "Result should not be constantrange without merging any constrange");
+    if (Result.isOverdefined() || Intervals.empty())
+      return;
+    RangeIncludingUndef |= Result.isUndef();
+    SmallVector<ConstantRange, 8> Ranges;
+    Ranges.reserve(Intervals.size());
+    for (const auto &[L, U] : Intervals)
+      Ranges.emplace_back(L, U);
+    // Get the most accurate approximation reliably
+    ConstantRange UnionCR = ConstantRange::unionOf(Ranges);
+    Result.markConstantRange(
+        std::move(UnionCR),
+        MergeOptions().setMayIncludeUndef(RangeIncludingUndef));
+  }
+  void mergeIn(const ValueLatticeElement &RHS) {
+    if (Result.isOverdefined())
+      return;
+    if (tryToInsertRange(RHS))
+      return;
+
+    // [constant | notconstant] \/ constantrange = overdefined
+    if (!Intervals.empty() && (RHS.isConstant() || RHS.isNotConstant())) {
+      Result.markOverdefined();
+      return;
+    }
+
+    // If EdgeResult is not a constant range, we can merge it directly into
+    // result, which should be precise enough.
+    Result.mergeIn(RHS);
+  }
+
+private:
+  struct APIntULTComparator {
+    bool operator()(const APInt &A, const APInt &B) const { return A.ult(B); }
+  };
+
+  // This is a precise version of Constant::toConstantRange
+  static bool collectAPInts(const Constant *C, SmallVectorImpl<APInt> &Ints) {
+    if (auto *CI = dyn_cast<ConstantInt>(C)) {
+      Ints.push_back(CI->getValue());
+      return true;
+    }
+
+    if (!C->getType()->isVectorTy())
+      return false;
+
+    if (auto *CI = dyn_cast_or_null<ConstantInt>(
+            C->getSplatValue(/*AllowPoison=*/true))) {
+      Ints.push_back(CI->getValue());
+      return true;
+    }
+
+    if (auto *CDV = dyn_cast<ConstantDataVector>(C)) {
+      unsigned NumElements = CDV->getNumElements();
+      Ints.reserve(Ints.size() + NumElements);
+      for (unsigned I = 0, E = NumElements; I < E; ++I)
+        Ints.push_back(CDV->getElementAsAPInt(I));
+      return true;
+    }
+
+    if (auto *CV = dyn_cast<ConstantVector>(C)) {
+      bool AnyNotAPIInt = false;
+      unsigned NumOperands = CV->getNumOperands();
+      Ints.reserve(Ints.size() + NumOperands);
+      for (unsigned I = 0, E = NumOperands; I < E; ++I) {
+        Constant *Elem = CV->getOperand(I);
+        if (!Elem) {
+          AnyNotAPIInt = true;
+          continue;
+        }
+        if (isa<PoisonValue>(Elem))
+          continue;
+        auto *CI = dyn_cast<ConstantInt>(Elem);
+        if (!CI) {
+          AnyNotAPIInt = true;
+          continue;
+        }
+        Ints.push_back(CI->getValue());
+      }
+      return AnyNotAPIInt;
+    }
+
+    return false;
+  }
+
+  /// Try to insert ranges extracted from constantrange / const lattice. Return
+  /// false if failed to insert.
+  bool tryToInsertRange(const ValueLatticeElement &IV) {
+    if (IV.isConstantRange()) {
+      RangeIncludingUndef |= IV.isConstantRangeIncludingUndef();
+      insertRange(IV.getConstantRange());
+      return true;
+    }
+    assert(!IV.isConstantRange() &&
+           "Constant range RHS should be handled earlier");
+    if (/*int vec*/ IV.isConstant(true)) {
+      IntsInVec.clear(); // Reuse the vector to avoid reallocations
+      if (!collectAPInts(IV.getConstant(), IntsInVec))
+        Result.markOverdefined();
+      for (const APInt &Int : IntsInVec)
+        insertRange(Int);
+      return true;
+    }
+    return false;
+  }
+
+  void insertRange(const ConstantRange &Range) {
+    assert(!Range.isFullSet() && !Range.isEmptySet() &&
+           "Unexpected full/empty set");
+    // [constant | notconstant] \/ constantrange = overdefined
+    if ((Result.isConstant() && !Result.isConstant(true)) ||
+        Result.isNotConstant())
+      Result.markOverdefined();
+
+    if (Result.isOverdefined())
+      return;
+
+    if (Range.isWrappedSet()) {
+      unsigned BitWidth = Range.getBitWidth();
+      insertRange(Range.getLower(), APInt::getZero(BitWidth));
+      insertRange(APInt::getZero(BitWidth), Range.getUpper());
+    } else {
+      insertRange(Range.getLower(), Range.getUpper());
+    }
+  }
+
+  void insertRange(APInt L, APInt U) {
+    assert(L.ule(U - 1) && "Unexpected wrapped range");
+    assert(L != U && "Unexpected empty/full range");
+
+    // Find the first interval with start > L
+    auto It = Intervals.upper_bound(L);
+
+    // Try to merge with the previous interval
+    if (It != Intervals.begin()) {
+      auto Prev = std::prev(It);
+      const auto &[PrevL, PrevU] = *Prev;
+      assert(PrevL.ule(L) && "Prev.L should be <= Cur.L");
+      //  L---------U      : PrevR
+      //     L----U        : this
+      // return early as nothing changes
+      if (PrevU.isZero() || (PrevU.uge(U) && !U.isZero()))
+        return;
+      assert((PrevU.ule(U - 1)) && "PrevU >= U should be handled earlier");
+      //  L-------U      : PrevR
+      //        L------U : this
+      //  results
+      //  L------------U : NewR
+      if (PrevU.uge(L)) {
+        L = Prev->first;
+        It = Intervals.erase(Prev);
+      }
+    }
+
+    if (U.isZero()) {
+      // Remove all intervals with NextR.L > L
+      //    L-------------- : this
+      //      L-R L--R L-R  : NextRs
+      It = Intervals.erase(It, Intervals.end());
+      assert(It == Intervals.end());
+    }
+
+    // Merge all Next intervals with NextR.L <= U
+    while (It != Intervals.end()) {
+      assert(!U.isZero() && "U is zero should be handled earlier");
+      const auto &[NextL, NextU] = *It;
+      // If U < NextL, stop
+      if (U.ult(NextL))
+        break;
+      assert(U.uge(NextL) && "U < NextL should be handled earlier");
+      It = Intervals.erase(It);
+      //  L----------U   : this
+      //     L----U      : NextR
+      //  results
+      //  L----------U   : NewR
+      if (U.uge(NextU) && !NextU.isZero())
+        continue;
+      assert((U.ult(NextU) || NextU.isZero()) &&
+             "U >= NextU should be handled earlier");
+      //  L------U       : this
+      //     L-------U   : NextR
+      //  results
+      //  L----------U   : NewR
+      U = NextU;
+      assert((It == Intervals.end() || NextU.ult(It->first)) &&
+             "Unexpected non-disjoint interval");
+      break;
+    }
+
+    // Already overdefined, i.e., [0, MAX]
+    if (Intervals.empty() && L.isZero() && U.isZero()) {
+      Result.markOverdefined();
+      return;
+    }
+
+    // Insert the new merged interval
+    Intervals.emplace(std::move(L), std::move(U));
+  }
+
+  // L as key, U as value, ult as comparator
+  std::map<APInt, APInt, APIntULTComparator> Intervals;
+  SmallVector<APInt, 8> IntsInVec;
+  ValueLatticeElement &Result;
+  bool RangeIncludingUndef = false;
+  bool InitialResultAsCR = false;
+};
+
 std::optional<ValueLatticeElement>
 LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
   ValueLatticeElement Result;  // Start Undefined.
@@ -741,27 +986,32 @@ LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
   std::optional<BBLatticeElementMap> PredLatticeElements;
   if (PerPredRanges)
     PredLatticeElements = std::make_optional<BBLatticeElementMap>();
-  for (BasicBlock *Pred : predecessors(BB)) {
-    // Skip self loops.
-    if (Pred == BB)
-      continue;
-    std::optional<ValueLatticeElement> EdgeResult = getEdgeValue(Val, Pred, BB);
-    if (!EdgeResult)
-      // Explore that input, then return here
-      return std::nullopt;
 
-    Result.mergeIn(*EdgeResult);
+  {
+    ValueLatticeMerger Merger(Result);
+    for (BasicBlock *Pred : predecessors(BB)) {
+      // Skip self loops.
+      if (Pred == BB)
+        continue;
+      std::optional<ValueLatticeElement> EdgeResult =
+          getEdgeValue(Val, Pred, BB);
+      if (!EdgeResult)
+        // Explore that input, then return here
+        return std::nullopt;
+
+      Merger.mergeIn(*EdgeResult);
 
-    // If we hit overdefined, exit early.  The BlockVals entry is already set
-    // to overdefined.
-    if (Result.isOverdefined()) {
-      LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName()
-                        << "' - overdefined because of pred '"
-                        << Pred->getName() << "' (non local).\n");
-      return Result;
+      // If we hit overdefined, exit early.  The BlockVals entry is already set
+      // to overdefined.
+      if (Result.isOverdefined()) {
+        LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName()
+                          << "' - overdefined because of pred '"
+                          << Pred->getName() << "' (non local).\n");
+        return Result;
+      }
+      if (PerPredRanges)
+        PredLatticeElements->insert({Pred, *EdgeResult});
     }
-    if (PerPredRanges)
-      PredLatticeElements->insert({Pred, *EdgeResult});
   }
 
   if (PerPredRanges)
@@ -774,7 +1024,7 @@ LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
 
 std::optional<ValueLatticeElement>
 LazyValueInfoImpl::solveBlockValuePHINode(PHINode *PN, BasicBlock *BB) {
-  ValueLatticeElement Result;  // Start Undefined.
+  ValueLatticeElement Result; // Start Undefined.
 
   // Loop over all of our predecessors, merging what we know from them into
   // result.  See the comment about the chosen traversal order in
@@ -782,31 +1032,34 @@ LazyValueInfoImpl::solveBlockValuePHINode(PHINode *PN, BasicBlock *BB) {
   std::optional<BBLatticeElementMap> PredLatticeElements;
   if (PerPredRanges)
     PredLatticeElements = std::make_optional<BBLatticeElementMap>();
-  for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
-    BasicBlock *PhiBB = PN->getIncomingBlock(i);
-    Value *PhiVal = PN->getIncomingValue(i);
-    // Note that we can provide PN as the context value to getEdgeValue, even
-    // though the results will be cached, because PN is the value being used as
-    // the cache key in the caller.
-    std::optional<ValueLatticeElement> EdgeResult =
-        getEdgeValue(PhiVal, PhiBB, BB, PN);
-    if (!EdgeResult)
-      // Explore that input, then return here
-      return std::nullopt;
+  {
+    ValueLatticeMerger Merger(Result);
+    for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
+      BasicBlock *PhiBB = PN->getIncomingBlock(i);
+      Value *PhiVal = PN->getIncomingValue(i);
+      // Note that we can provide PN as the context value to getEdgeValue, even
+      // though the results will be cached, because PN is the value being used
+      // as the cache key in the caller.
+      std::optional<ValueLatticeElement> EdgeResult =
+          getEdgeValue(PhiVal, PhiBB, BB, PN);
+      if (!EdgeResult)
+        // Explore that input, then return here
+        return std::nullopt;
 
-    Result.mergeIn(*EdgeResult);
+      Merger.mergeIn(*EdgeResult);
 
-    // If we hit overdefined, exit early.  The BlockVals entry is already set
-    // to overdefined.
-    if (Result.isOverdefined()) {
-      LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName()
-                        << "' - overdefined because of pred (local).\n");
+      // If we hit overdefined, exit early.  The BlockVals entry is already set
+      // to overdefined.
+      if (Result.isOverdefined()) {
+        LLVM_DEBUG(dbgs() << " compute BB '" << BB->getName()
+                          << "' - overdefined because of pred (local).\n");
 
-      return Result;
-    }
+        return Result;
+      }
 
-    if (PerPredRanges)
-      PredLatticeElements->insert({PhiBB, *EdgeResult});
+      if (PerPredRanges)
+        PredLatticeElements->insert({PhiBB, *EdgeResult});
+    }
   }
 
   if (PerPredRanges)
diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 9beaee60d0bc1..04552a016de32 100644
--- a/llvm/lib/IR/ConstantRange.cpp
+++ b/llvm/lib/IR/ConstantRange.cpp
@@ -22,6 +22,7 @@
 
 #include "llvm/IR/ConstantRange.h"
 #include "llvm/ADT/APInt.h"
+#include "llvm/ADT/STLExtras.h"
 #include "llvm/Config/llvm-config.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/IR/InstrTypes.h"
@@ -1099,6 +1100,96 @@ ConstantRange ConstantRange::intrinsic(Intrinsic::ID IntrinsicID,
   }
 }
 
+ConstantRange ConstantRange::unionOf(ArrayRef<ConstantRange> Ranges,
+                                     PreferredRangeType Type) {
+  assert(!Ranges.empty() && "Cannot union an empty set of ranges");
+  ConstantRange Rng0 = Ranges[0];
+  unsigned NumWrapped = count_if(
+      Ranges, [](const ConstantRange &CR) { return CR.isWrappedSet(); });
+  unsigned BitWidth = Rng0.getBitWidth();
+  APInt Zero = APInt::getZero(BitWidth);
+  SmallVector<ConstantRange, 16> Segs;
+  Segs.reserve(Ranges.size() + NumWrapped);
+
+  // The left end and the right end of the unioned range.
+  APInt LL, RR = Zero + 1;
+
+  for (const ConstantRange &CR : Ranges) {
+    assert(CR.getBitWidth() == BitWidth &&
+           "All ranges must have the same bitwidth");
+    if (CR.isFullSet())
+      return CR;
+    if (CR.isEmptySet())
+      continue;
+    APInt Upper = CR.getUpper();
+    if (CR.isWrappedSet()) {
+      // We need to split the wrapped set into two parts: [0, R) and [L, 0)
+      APInt Lower = CR.getLower();
+      Segs.push_back(ConstantRange(Zero, Upper));
+      Segs.push_back(ConstantRange(Lower, Zero));
+      RR = Zero;
+    } else {
+      Segs.push_back(CR);
+      // Update the right end of the unioned range.
+      if (!RR.isZero() && (Upper.ugt(RR) || Upper.isZero()))
+        RR = Upper;
+    }
+  }
+
+  if (Segs.empty())
+    return getEmpty(BitWidth);
+
+  if (Segs.size() == 1)
+    return Segs[0];
+
+  llvm::sort(Segs, [](const ConstantRange &A, const ConstantRange &B) {
+    APInt LA = A.getLower(), LB = B.getLower();
+    return LA.ult(LB) || (LA == LB && (A.getUpper() - 1).ult(B.getUpper() - 1));
+  });
+
+  LL = Segs.front().getLower();
+
+  // If RR is zero, just return [LL, 0) is meaningless for
+  // PreferredRangeType::Unsigned
+  if (!RR.isZero() && Type == PreferredRangeType::Unsigned) {
+    // We prefer wrapped gap, i.e., non-wrapped range here.
+    return ConstantRange(LL, RR);
+  }
+
+  // Init with the gap wrapping around the unsigned domain)
+  // LL - RR = 0 - RR + LL = (0xff..ff + 1) - RR + LL = gap size
+  // APInt MaxGapL = RR, MaxGap = LL - RR;
+  ConstantRange MaxGap(RR, LL);
+
+  // Find maximal gap between segments.
+  APInt GapL = Segs.front().getUpper();
+  for (const ConstantRange &Cur : Segs) {
+    APInt L = Cur.getLower(), R = Cur.getUpper();
+    // Fond a new gap if Cur.L > GapL (i.e., Last.R).
+    if (L.ugt(GapL)) {
+      // If Cur.L > Last.R && Cur.L s< Last.R, we found a signed wrapped gap.
+      if (Type == PreferredRangeType::Signed && L.slt(GapL)) {
+        // We prefer signed wrapped gap, i.e., signed non-wrapped range here.
+        return ConstantRange(L, GapL);
+      }
+      ConstantRange Gap(GapL, L);
+      // if (Gap.ugt(MaxGap) ) {
+      if (MaxGap.isSizeStrictlySmallerThan(Gap))
+        MaxGap = Gap;
+      GapL = R;
+    } else if (R.ugt(GapL)) {
+      GapL = R;
+    }
+
+    // We reach the end of the unioned ranges.
+    if (R == RR)
+      break;
+  }
+
+  // Smallest union <--> Maximal gap.
+  return MaxGap.inverse();
+}
+
 ConstantRange
 ConstantRange::add(const ConstantRange &Other) const {
   if (isEmptySet() || Other.isEmptySet())
diff --git a/llvm/unittests/IR/ConstantRangeTest.cpp b/llvm/unittests/IR/ConstantRangeTest.cpp
index 13712a76d3edf..1bb5cd9bb2188 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -696,6 +696,65 @@ TEST_F(ConstantRangeTest, UnionWith) {
             ConstantRange::getFull(16));
 }
 
+TEST_F(ConstantRangeTest, UnionOf) {
+  EXPECT_EQ(ConstantRange::unionOf({Wrap, One}),
+            ConstantRange(APInt(16, 0xaaa), APInt(16, 0xb)));
+  EXPECT_EQ(ConstantRange::unionOf({One, Wrap}),
+            ConstantRange::unionOf({Wrap, One}));
+  EXPECT_EQ(ConstantRange::unionOf({Empty, Empty}), Empty);
+  EXPECT_EQ(ConstantRange::unionOf({Full, Full}), Full);
+  EXPECT_EQ(ConstantRange::unionOf({Some, Wrap}), Full);
+
+#define CR(L, R) ConstantRange(APInt(8, L), APInt(8, R))
+  // Order-irrelevance:
+  EXPECT_EQ(ConstantRange::unionOf({CR(1, 40), CR(50, 100), CR(150, 160)}),
+            CR(1, 160));
+  EXPECT_EQ(CR(1, 40).unionWith(CR(50, 100)).unionWith(CR(150, 160)),
+            CR(1, 160));
+  EXPECT_EQ(ConstantRange::unionOf({CR(1, 40), CR(150, 160), CR(50, 100)}),
+            CR(1, 160));
+  EXPECT_EQ(CR(1, 40).unionWith(CR(150, 160)).unionWith(CR(50, 100)),
+            CR(150, 100));
+
+  // Preference:
+  auto Ranges1 = {CR(1, 2), CR(200, 244), CR(254, 255)};
+  EXPECT_EQ(ConstantRange::unionOf(Ranges1,
+                                   /* default */ ConstantRange::Smallest),
+            CR(200, 2));
+  EXPECT_EQ(ConstantRange::unionOf(Ranges1, ConstantRange::Unsigned),
+            CR(1, 255));
+  auto Ranges2 = {CR(80, 90), CR(100, 126), CR(/* -127 */ 129, /* -1 */ 255)};
+  EXPECT_EQ(ConstantRange::unionOf(Ranges2,
+                                   /* default */ ConstantRange::Smallest),
+            CR(80, 255 /* -1 */));
+  EXPECT_EQ(ConstantRange::unionOf(Ranges2, ConstantRange::Signed),
+            CR(/* -127 */ 129, 126));
+  // We have no choice except to prefer the smallest range:
+  auto Ranges3 = {CR(0, 90), CR(100, 255), CR(250, 0)};
+  EXPECT_EQ(ConstantRange::unionOf(Ranges3, ConstantRange::Smallest),
+            CR(100, 90));
+  EXPECT_EQ(ConstantRange::unionOf(Ranges3, ConstantRange::Unsigned),
+            CR(100, 90));
+  EXPECT_EQ(ConstantRange::unionOf(Ranges3, ConstantRange::Signed),
+            CR(100, 90));
+  // Merge the same ranges:
+  auto Range = CR(4, 3);
+  EXPECT_EQ(ConstantRange::unionOf({Range, Range, Range}), Range);
+  // Merge L--------------------R
+  //  with   L--[R|L]--R
+  EXPECT_EQ(ConstantRange::unionOf({CR(0, 255), CR(1, 2), CR(2, 3)}),
+            CR(0, 255));
+
+  // true ∪ false = overdefined
+  EXPECT_EQ(ConstantRange::unionOf(
+                {/* true */ ConstantRange(APInt(1, 1), APInt(1, 0)),
+
+                 /* false */ ConstantRange(APInt(1, 0), APInt(1, 1))}),
+            ConstantRange::getFull(1));
+
+#undef CR
+}
+
 TEST_F(ConstantRangeTest, SetDifference) {
   EXPECT_EQ(Full.difference(Empty), Full);
   EXPECT_EQ(Full.difference(Full), Empty);

>From bf08d44c67d69405bf409c876354d28eba0b8b0e Mon Sep 17 00:00:00 2001
From: Camsyn <camsyn at foxmail.com>
Date: Sat, 27 Dec 2025 21:55:06 +0800
Subject: [PATCH 3/3] After-commit test

---
 .../Analysis/LazyValueAnalysis/group-merge.ll  | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll b/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll
index d3f2a49812d08..5043ea6de22ca 100644
--- a/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll
+++ b/llvm/test/Analysis/LazyValueAnalysis/group-merge.ll
@@ -79,10 +79,8 @@ define void @foo_with_diff_IV_order(i32 %v) {
 ; CHECK-NEXT:    [[PHI:%.*]] = phi i32 [ 2, %[[CASE2]] ], [ [[V]], %[[IF_ELSE]] ], [ 1, %[[CASE1]] ]
 ; CHECK-NEXT:    br label %[[SWITCH:.*]]
 ; CHECK:       [[SWITCH]]:
-; CHECK-NEXT:    switch i32 [[PHI]], label %[[EXIT]] [
-; CHECK-NEXT:      i32 3, label %[[UNREACH:.*]]
-; CHECK-NEXT:    ]
-; CHECK:       [[UNREACH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[UNREACH:.*:]]
 ; CHECK-NEXT:    br label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
 ; CHECK-NEXT:    ret void
@@ -185,10 +183,8 @@ define void @foo_with_diff_pred_order(i32 %v) {
 ; CHECK:       [[DEFAULT]]:
 ; CHECK-NEXT:    br label %[[CASE1]]
 ; CHECK:       [[SWITCH]]:
-; CHECK-NEXT:    switch i32 [[V]], label %[[EXIT]] [
-; CHECK-NEXT:      i32 3, label %[[UNREACH:.*]]
-; CHECK-NEXT:    ]
-; CHECK:       [[UNREACH]]:
+; CHECK-NEXT:    br label %[[EXIT]]
+; CHECK:       [[UNREACH:.*:]]
 ; CHECK-NEXT:    br label %[[EXIT]]
 ; CHECK:       [[EXIT]]:
 ; CHECK-NEXT:    ret void
@@ -222,7 +218,7 @@ exit:                                             ; preds = %unreach, %switch, %
 }
 
 define i32 @bar(i32 range(i32 4, 1) %v1, i8 %v2) {
-; CHECK-LABEL: define i32 @bar(
+; CHECK-LABEL: define range(i32 4, 3) i32 @bar(
 ; CHECK-SAME: i32 range(i32 4, 1) [[V1:%.*]], i8 [[V2:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    switch i8 [[V2]], label %[[DEFAULT:.*]] [
@@ -255,7 +251,7 @@ default:                                          ; preds = %case1, %case0, %ent
 }
 
 define <2 x i8> @phi_vector_merge(i1 %c) {
-; CHECK-LABEL: define range(i8 62, 4) <2 x i8> @phi_vector_merge(
+; CHECK-LABEL: define range(i8 2, -72) <2 x i8> @phi_vector_merge(
 ; CHECK-SAME: i1 [[C:%.*]]) {
 ; CHECK-NEXT:  [[ENTRY:.*]]:
 ; CHECK-NEXT:    br i1 [[C]], label %[[IF:.*]], label %[[JOIN:.*]]
@@ -263,7 +259,7 @@ define <2 x i8> @phi_vector_merge(i1 %c) {
 ; CHECK-NEXT:    br label %[[JOIN]]
 ; CHECK:       [[JOIN]]:
 ; CHECK-NEXT:    [[PHI:%.*]] = phi <2 x i8> [ <i8 0, i8 -76>, %[[ENTRY]] ], [ <i8 60, i8 120>, %[[IF]] ]
-; CHECK-NEXT:    [[ADD:%.*]] = add <2 x i8> [[PHI]], <i8 2, i8 3>
+; CHECK-NEXT:    [[ADD:%.*]] = add nuw <2 x i8> [[PHI]], <i8 2, i8 3>
 ; CHECK-NEXT:    ret <2 x i8> [[ADD]]
 ;
 entry:



More information about the llvm-commits mailing list