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

Kunqiu Chen via llvm-commits llvm-commits at lists.llvm.org
Sat Dec 27 06:38:14 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 14601fe5e95d7545e59bccb73545649cb6d61de0 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 | 102 ++++++++++++++++++++++
 llvm/include/llvm/IR/ConstantRange.h      |   9 ++
 llvm/lib/Analysis/LazyValueInfo.cpp       |  60 ++++++++-----
 llvm/lib/Analysis/ValueLattice.cpp        |  56 ++++++++++++
 llvm/lib/IR/ConstantRange.cpp             |  98 +++++++++++++++++++++
 llvm/unittests/IR/ConstantRangeTest.cpp   |  52 +++++++++++
 6 files changed, 356 insertions(+), 21 deletions(-)

diff --git a/llvm/include/llvm/Analysis/ValueLattice.h b/llvm/include/llvm/Analysis/ValueLattice.h
index 262ff58f07dfd..f880a20f17bc4 100644
--- a/llvm/include/llvm/Analysis/ValueLattice.h
+++ b/llvm/include/llvm/Analysis/ValueLattice.h
@@ -9,9 +9,11 @@
 #ifndef LLVM_ANALYSIS_VALUELATTICE_H
 #define LLVM_ANALYSIS_VALUELATTICE_H
 
+#include "llvm/ADT/ArrayRef.h"
 #include "llvm/IR/ConstantRange.h"
 #include "llvm/IR/Constants.h"
 #include "llvm/Support/Compiler.h"
+#include <type_traits>
 
 //===----------------------------------------------------------------------===//
 //                               ValueLatticeElement
@@ -465,6 +467,17 @@ class ValueLatticeElement {
         Opts.setMayIncludeUndef(RHS.isConstantRangeIncludingUndef()));
   }
 
+  /// Updates this object to approximate this object with a union of lattices.
+  /// Returns true if this object has been changed.
+  bool mergeIn(ArrayRef<const ValueLatticeElement *> Lattices,
+               MergeOptions Opts = MergeOptions()) {
+    return mergeInLatticesImpl(make_pointee_range(Lattices), Opts);
+  }
+  bool mergeIn(ArrayRef<const ValueLatticeElement> Lattices,
+               MergeOptions Opts = MergeOptions()) {
+    return mergeInLatticesImpl(Lattices, Opts);
+  }
+
   // Compares this symbolic value with Other using Pred and returns either
   /// true, false or undef constants, or nullptr if the comparison cannot be
   /// evaluated.
@@ -492,6 +505,95 @@ class ValueLatticeElement {
 
   unsigned getNumRangeExtensions() const { return NumRangeExtensions; }
   void setNumRangeExtensions(unsigned N) { NumRangeExtensions = N; }
+
+private:
+  /// Collect all the APInts in this constant. If something is not int, return
+  /// false.
+  bool collectAPInts(SmallVectorImpl<APInt> &Ints) const;
+
+  template <typename RangeT>
+  bool mergeInLatticesImpl(RangeT &&Lattices,
+                           MergeOptions Opts = MergeOptions()) {
+    if (isOverdefined() || Lattices.empty())
+      return false;
+    SmallVector<ConstantRange, 8> Ranges;
+    SmallVector<APInt, 8> IntsInVec;
+    Ranges.reserve(size(Lattices));
+    bool Changed = false;
+    bool RangeIncludingUndef = false;
+    for (const auto &L : Lattices) {
+      static_assert(std::is_same_v<const ValueLatticeElement &, decltype(L)>,
+                    "Should take range<ValueLatticeElement> as input");
+      // For any X, ⊥ ∨ X = ⊥
+      if (L.isUnknown())
+        continue;
+      // For any X, ⊤ ∨ X = ⊤
+      if (L.isOverdefined()) {
+        markOverdefined();
+        return true;
+      }
+      if (L.isConstantRange()) {
+        RangeIncludingUndef |= L.isConstantRangeIncludingUndef();
+        Ranges.push_back(L.getConstantRange());
+        continue;
+      }
+      if (L.isConstant() && L.getConstant()->getType()->isVectorTy() &&
+          L.getConstant()->getType()->getScalarType()->isIntegerTy()) {
+        // If any constant in vec is not int, return overdefined
+        if (!L.collectAPInts(IntsInVec)) {
+          markOverdefined();
+          return true;
+        }
+        continue;
+      }
+      // Directly merge any non-constant-range lattices
+      Changed |= mergeIn(L, Opts);
+      if (isOverdefined())
+        return true;
+    }
+
+    // Cannot collect any constrange
+    if (Ranges.empty() && IntsInVec.empty())
+      return Changed;
+
+    if (isConstant()) {
+      Constant *C = getConstant();
+      if (!C->getType()->isVectorTy() ||
+          !C->getType()->getScalarType()->isIntegerTy()) {
+        // constantrange ∨ const = overdefined
+        markOverdefined();
+        return true;
+      }
+      if (!collectAPInts(IntsInVec)) {
+        markOverdefined();
+        return true;
+      }
+    }
+
+    if (isNotConstant()) {
+      if (Ranges.empty())
+        return Changed;
+      markOverdefined();
+      return true;
+    }
+
+    // Canonicalize APInts as Ranges
+    for (APInt &Int : IntsInVec)
+      Ranges.push_back(ConstantRange(Int));
+
+    if (isConstantRange())
+      Ranges.push_back(getConstantRange());
+
+    // Now, this object may ∈
+    // { unknown, undef, constrange, constantrange_including_undef}
+    // The final result should be the union of all ranges.
+
+    RangeIncludingUndef |= isUndef();
+    ConstantRange UnionedRange = ConstantRange::unionOf(Ranges);
+
+    return markConstantRange(std::move(UnionedRange),
+                             Opts.setMayIncludeUndef(RangeIncludingUndef));
+  }
 };
 
 static_assert(sizeof(ValueLatticeElement) <= 40,
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..ef3bc48ddfccb 100644
--- a/llvm/lib/Analysis/LazyValueInfo.cpp
+++ b/llvm/lib/Analysis/LazyValueInfo.cpp
@@ -14,6 +14,7 @@
 #include "llvm/Analysis/LazyValueInfo.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"
@@ -729,6 +730,10 @@ LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
     return ValueLatticeElement::getOverdefined();
   }
 
+  SmallVector<ValueLatticeElement, 8> IncomingIVs;
+  IncomingIVs.reserve(pred_size(BB));
+  BasicBlock *OverdefinedPred = nullptr;
+
   // Loop over all of our predecessors, merging what we know from them into
   // result.  If we encounter an unexplored predecessor, we eagerly explore it
   // in a depth first manner.  In practice, this has the effect of discovering
@@ -750,20 +755,29 @@ LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
       // Explore that input, then return here
       return std::nullopt;
 
-    Result.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;
+    IncomingIVs.push_back(*EdgeResult);
+    if (EdgeResult->isOverdefined()) {
+      OverdefinedPred = Pred;
+      break;
     }
+
     if (PerPredRanges)
       PredLatticeElements->insert({Pred, *EdgeResult});
   }
 
+  Result.mergeIn(IncomingIVs);
+
+  // 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";
+               if (OverdefinedPred) 
+                 dbgs() << " '" << OverdefinedPred->getName() << "'";
+               dbgs() << " (non local).\n");
+    return Result;
+  }
+
   if (PerPredRanges)
     TheCache.insertPredecessorResults(Val, BB, *PredLatticeElements);
 
@@ -774,7 +788,9 @@ LazyValueInfoImpl::solveBlockValueNonLocal(Value *Val, BasicBlock *BB) {
 
 std::optional<ValueLatticeElement>
 LazyValueInfoImpl::solveBlockValuePHINode(PHINode *PN, BasicBlock *BB) {
-  ValueLatticeElement Result;  // Start Undefined.
+  ValueLatticeElement Result; // Start Undefined.
+  SmallVector<ValueLatticeElement, 8> IncomingIVs;
+  IncomingIVs.reserve(PN->getNumIncomingValues());
 
   // Loop over all of our predecessors, merging what we know from them into
   // result.  See the comment about the chosen traversal order in
@@ -794,26 +810,28 @@ LazyValueInfoImpl::solveBlockValuePHINode(PHINode *PN, BasicBlock *BB) {
       // Explore that input, then return here
       return std::nullopt;
 
-    Result.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");
-
-      return Result;
-    }
+    IncomingIVs.push_back(*EdgeResult);
+    if (EdgeResult->isOverdefined())
+      break;
 
     if (PerPredRanges)
       PredLatticeElements->insert({PhiBB, *EdgeResult});
   }
 
+  Result.mergeIn(IncomingIVs);
+
+  // 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;
+  }
   if (PerPredRanges)
     TheCache.insertPredecessorResults(PN, BB, *PredLatticeElements);
 
   // Return the merged value, which is more precise than 'overdefined'.
-  assert(!Result.isOverdefined() && "Possible PHI in entry block?");
   return Result;
 }
 
diff --git a/llvm/lib/Analysis/ValueLattice.cpp b/llvm/lib/Analysis/ValueLattice.cpp
index 03810f1c554e5..7015e7d27b1b4 100644
--- a/llvm/lib/Analysis/ValueLattice.cpp
+++ b/llvm/lib/Analysis/ValueLattice.cpp
@@ -8,6 +8,7 @@
 
 #include "llvm/Analysis/ValueLattice.h"
 #include "llvm/Analysis/ConstantFolding.h"
+#include "llvm/IR/Constant.h"
 #include "llvm/IR/Instructions.h"
 
 namespace llvm {
@@ -109,6 +110,61 @@ ValueLatticeElement::intersect(const ValueLatticeElement &Other) const {
                             Other.isConstantRangeIncludingUndef());
 }
 
+/// Collect all the APInts in this constant. If something is not int, return
+/// false.
+bool ValueLatticeElement::collectAPInts(SmallVectorImpl<APInt> &Ints) const {
+  assert(isConstant() && "Can only collect APInts from constant");
+
+  // This is a precise version of Constant::toConstantRange
+
+  const Constant *C = getConstant();
+  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;
+}
+
 raw_ostream &operator<<(raw_ostream &OS, const ValueLatticeElement &Val) {
   if (Val.isUnknown())
     return OS << "unknown";
diff --git a/llvm/lib/IR/ConstantRange.cpp b/llvm/lib/IR/ConstantRange.cpp
index 9beaee60d0bc1..94d4740ef60d0 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,103 @@ 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))
+        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;
+
+  // 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);
+      }
+      APInt Gap = L - GapL;
+      if (Gap.ugt(MaxGap)) {
+        MaxGap = Gap;
+        MaxGapL = GapL;
+      }
+      GapL = R;
+    } else if (R.ugt(GapL)) {
+      GapL = R;
+    }
+
+    // We reach the end of the unioned ranges.
+    if (R == RR)
+      break;
+  }
+
+  if (MaxGap.isZero())
+    return getFull(BitWidth);
+
+  // Max gap wraps around the unsigned domain
+  if (MaxGapL == RR)
+    return ConstantRange(LL, RR);
+
+  // Max gap is within the unsigned domain
+  return ConstantRange(MaxGapL + MaxGap, MaxGapL);
+}
+
 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..9138e4474242f 100644
--- a/llvm/unittests/IR/ConstantRangeTest.cpp
+++ b/llvm/unittests/IR/ConstantRangeTest.cpp
@@ -696,6 +696,58 @@ 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));
+
+#undef CR
+}
+
 TEST_F(ConstantRangeTest, SetDifference) {
   EXPECT_EQ(Full.difference(Empty), Full);
   EXPECT_EQ(Full.difference(Full), Empty);

>From d4ad5d101519bcf14dfb1ea76a7334bcb7bc70fb 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