[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