[llvm] [DA] Simplify runtime predicate collection and extend to all dependence tests (PR #157523)
Sebastian Pop via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 9 09:30:20 PDT 2025
https://github.com/sebpop updated https://github.com/llvm/llvm-project/pull/157523
>From ca28604e723af7940ab764a139fa512f5fa59ebc Mon Sep 17 00:00:00 2001
From: Sebastian Pop <spop at nvidia.com>
Date: Sat, 23 Aug 2025 09:33:00 -0500
Subject: [PATCH] [DA] Simplify runtime predicate collection and extend to all
dependence tests
Previously, predicates were collected using a local `Assume` vector. This patch
removes local `Assume` vector, uses class member `Assumptions` instead, and
extends predicate collection to all dependence tests.
---
.../llvm/Analysis/DependenceAnalysis.h | 31 +++++++-
llvm/lib/Analysis/DependenceAnalysis.cpp | 71 +++++++++++--------
.../DependenceAnalysis/DifferentOffsets.ll | 5 --
.../DependenceAnalysis/MIVCheckConst.ll | 3 -
4 files changed, 69 insertions(+), 41 deletions(-)
diff --git a/llvm/include/llvm/Analysis/DependenceAnalysis.h b/llvm/include/llvm/Analysis/DependenceAnalysis.h
index f66c79d915665..300cfb73af5c1 100644
--- a/llvm/include/llvm/Analysis/DependenceAnalysis.h
+++ b/llvm/include/llvm/Analysis/DependenceAnalysis.h
@@ -285,7 +285,8 @@ class LLVM_ABI FullDependence final : public Dependence {
class DependenceInfo {
public:
DependenceInfo(Function *F, AAResults *AA, ScalarEvolution *SE, LoopInfo *LI)
- : AA(AA), SE(SE), LI(LI), F(F) {}
+ : AA(AA), SE(SE), LI(LI), F(F), Assumptions({}, *SE),
+ UnderRuntimeAssumptions(false) {}
/// Handle transitive invalidation when the cached analysis results go away.
LLVM_ABI bool invalidate(Function &F, const PreservedAnalyses &PA,
@@ -355,7 +356,33 @@ class DependenceInfo {
ScalarEvolution *SE;
LoopInfo *LI;
Function *F;
- SmallVector<const SCEVPredicate *, 4> Assumptions;
+
+ /// Runtime assumptions collected during dependence analysis.
+ ///
+ /// The dependence analysis employs a cascade of tests from simple to complex:
+ /// ZIV -> SIV (Strong/Weak-Crossing/Weak-Zero) -> RDIV -> MIV -> Banerjee.
+ /// Each test attempts to characterize the dependence with increasing
+ /// precision.
+ ///
+ /// Assumption Management Strategy:
+ /// - Each test may require runtime assumptions (e.g., "coefficient != 0")
+ /// to provide precise analysis.
+ /// - If UnderRuntimeAssumptions=true: tests can add assumptions and continue.
+ /// - If UnderRuntimeAssumptions=false: tests that need assumptions fail
+ /// gracefully, allowing more complex tests to attempt analysis.
+ /// - Only assumptions from successful tests contribute to the final result.
+ /// - SCEVUnionPredicate automatically deduplicates redundant assumptions.
+ ///
+ /// This design ensures:
+ /// 1. Simpler tests get priority (better performance).
+ /// 2. Complex tests serve as fallbacks when simple tests fail.
+ /// 3. No unnecessary runtime checks from failed test attempts.
+ /// 4. Maintains the intended cascade behavior of the dependence analysis.
+ SCEVUnionPredicate Assumptions;
+
+ /// Indicates whether runtime assumptions are collected during the analysis.
+ /// When false, dependence tests that would require runtime assumptions fail.
+ bool UnderRuntimeAssumptions;
/// Subscript - This private struct represents a pair of subscripts from
/// a pair of potentially multi-dimensional array references. We use a
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index da86a8d2cc9c0..ad5415d2f765a 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -3567,7 +3567,7 @@ bool DependenceInfo::invalidate(Function &F, const PreservedAnalyses &PA,
}
SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const {
- return SCEVUnionPredicate(Assumptions, *SE);
+ return Assumptions;
}
// depends -
@@ -3584,7 +3584,12 @@ SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const {
std::unique_ptr<Dependence>
DependenceInfo::depends(Instruction *Src, Instruction *Dst,
bool UnderRuntimeAssumptions) {
- SmallVector<const SCEVPredicate *, 4> Assume;
+ // Set the flag for whether we're allowed to add runtime assumptions.
+ this->UnderRuntimeAssumptions = UnderRuntimeAssumptions;
+
+ // Clear any previous assumptions
+ Assumptions = SCEVUnionPredicate({}, *SE);
+
bool PossiblyLoopIndependent = true;
if (Src == Dst)
PossiblyLoopIndependent = false;
@@ -3596,8 +3601,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
if (!isLoadOrStore(Src) || !isLoadOrStore(Dst)) {
// can only analyze simple loads and stores, i.e., no calls, invokes, etc.
LLVM_DEBUG(dbgs() << "can only handle simple loads and stores\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
const MemoryLocation &DstLoc = MemoryLocation::get(Dst);
@@ -3608,8 +3612,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
case AliasResult::PartialAlias:
// cannot analyse objects if we don't understand their aliasing.
LLVM_DEBUG(dbgs() << "can't analyze may or partial alias\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
case AliasResult::NoAlias:
// If the objects noalias, they are distinct, accesses are independent.
LLVM_DEBUG(dbgs() << "no alias\n");
@@ -3623,8 +3626,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
// The dependence test gets confused if the size of the memory accesses
// differ.
LLVM_DEBUG(dbgs() << "can't analyze must alias with different sizes\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
Value *SrcPtr = getLoadStorePointerOperand(Src);
@@ -3643,8 +3645,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
// We check this upfront so we don't crash in cases where getMinusSCEV()
// returns a SCEVCouldNotCompute.
LLVM_DEBUG(dbgs() << "can't analyze SCEV with different pointer base\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
// Even if the base pointers are the same, they may not be loop-invariant. It
@@ -3656,8 +3657,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
if (!isLoopInvariant(SrcBase, SrcLoop) ||
!isLoopInvariant(DstBase, DstLoop)) {
LLVM_DEBUG(dbgs() << "The base pointer is not loop invariant.\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
uint64_t EltSize = SrcLoc.Size.toRaw();
@@ -3665,35 +3665,40 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
const SCEV *DstEv = SE->getMinusSCEV(DstSCEV, DstBase);
// Check that memory access offsets are multiples of element sizes.
- if (!SE->isKnownMultipleOf(SrcEv, EltSize, Assume) ||
- !SE->isKnownMultipleOf(DstEv, EltSize, Assume)) {
+ SmallVector<const SCEVPredicate *, 4> TempAssumptions;
+ if (!SE->isKnownMultipleOf(SrcEv, EltSize, TempAssumptions) ||
+ !SE->isKnownMultipleOf(DstEv, EltSize, TempAssumptions)) {
LLVM_DEBUG(dbgs() << "can't analyze SCEV with different offsets\n");
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
- if (!Assume.empty()) {
- if (!UnderRuntimeAssumptions)
- return std::make_unique<Dependence>(Src, Dst,
- SCEVUnionPredicate(Assume, *SE));
- // Add non-redundant assumptions.
- unsigned N = Assumptions.size();
- for (const SCEVPredicate *P : Assume) {
- bool Implied = false;
- for (unsigned I = 0; I != N && !Implied; I++)
- if (Assumptions[I]->implies(P, *SE))
- Implied = true;
- if (!Implied)
- Assumptions.push_back(P);
+ // Add any new assumptions from the isKnownMultipleOf calls
+ if (!TempAssumptions.empty()) {
+ if (UnderRuntimeAssumptions) {
+ SmallVector<const SCEVPredicate *, 4> NewPreds(
+ Assumptions.getPredicates());
+ NewPreds.append(TempAssumptions.begin(), TempAssumptions.end());
+ const_cast<DependenceInfo *>(this)->Assumptions =
+ SCEVUnionPredicate(NewPreds, *SE);
+ } else {
+ // Runtime assumptions needed but not allowed.
+ // Return confused dependence since we cannot proceed with precise
+ // analysis.
+ LLVM_DEBUG(dbgs() << "Runtime assumptions needed for offset analysis but "
+ "not allowed\n");
+ return std::make_unique<Dependence>(Src, Dst, getRuntimeAssumptions());
}
}
+ // Assert that we haven't added runtime assumptions when not allowed
+ assert(UnderRuntimeAssumptions || Assumptions.isAlwaysTrue());
+
establishNestingLevels(Src, Dst);
LLVM_DEBUG(dbgs() << " common nesting levels = " << CommonLevels << "\n");
LLVM_DEBUG(dbgs() << " maximum nesting levels = " << MaxLevels << "\n");
- FullDependence Result(Src, Dst, SCEVUnionPredicate(Assume, *SE),
- PossiblyLoopIndependent, CommonLevels);
+ FullDependence Result(Src, Dst, Assumptions, PossiblyLoopIndependent,
+ CommonLevels);
++TotalArrayPairs;
unsigned Pairs = 1;
@@ -4036,6 +4041,10 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
return nullptr;
}
+ // Assert that we haven't added runtime assumptions when not allowed
+ assert(UnderRuntimeAssumptions || Assumptions.isAlwaysTrue());
+
+ Result.Assumptions = getRuntimeAssumptions();
return std::make_unique<FullDependence>(std::move(Result));
}
diff --git a/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll b/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll
index d9ccea55dd478..719a62a3d5113 100644
--- a/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll
@@ -34,8 +34,6 @@ define i32 @alias_with_parametric_offset(ptr nocapture %A, i64 %n) {
; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0
; CHECK-NEXT: Src: %0 = load i32, ptr %A, align 1 --> Dst: %0 = load i32, ptr %A, align 1
; CHECK-NEXT: da analyze - none!
-; CHECK-NEXT: Runtime Assumptions:
-; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0
;
entry:
%arrayidx = getelementptr inbounds i8, ptr %A, i64 %n
@@ -56,7 +54,6 @@ define i32 @alias_with_parametric_expr(ptr nocapture %A, i64 %n, i64 %m) {
; CHECK-NEXT: Src: %0 = load i32, ptr %arrayidx1, align 1 --> Dst: %0 = load i32, ptr %arrayidx1, align 1
; CHECK-NEXT: da analyze - none!
; CHECK-NEXT: Runtime Assumptions:
-; CHECK-NEXT: Equal predicate: (zext i2 ((trunc i64 %m to i2) + (-2 * (trunc i64 %n to i2))) to i64) == 0
; CHECK-NEXT: Equal predicate: (zext i2 (-2 + (trunc i64 %m to i2)) to i64) == 0
;
entry:
@@ -81,8 +78,6 @@ define i32 @gep_i8_vs_i32(ptr nocapture %A, i64 %n, i64 %m) {
; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0
; CHECK-NEXT: Src: store i32 42, ptr %arrayidx1, align 4 --> Dst: store i32 42, ptr %arrayidx1, align 4
; CHECK-NEXT: da analyze - none!
-; CHECK-NEXT: Runtime Assumptions:
-; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0
;
entry:
%arrayidx0 = getelementptr inbounds i8, ptr %A, i64 %n
diff --git a/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll b/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll
index b498d70648bad..bb6d2d7c4c8f2 100644
--- a/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll
@@ -50,9 +50,6 @@ define void @test(ptr %A, ptr %B, i1 %arg, i32 %n, i32 %m) #0 align 2 {
; CHECK-NEXT: Equal predicate: (8 * (zext i4 (trunc i32 %v1 to i4) to i32))<nuw><nsw> == 0
; CHECK-NEXT: Src: %v32 = load <32 x i32>, ptr %v30, align 128 --> Dst: %v32 = load <32 x i32>, ptr %v30, align 128
; CHECK-NEXT: da analyze - consistent input [0 S S]!
-; CHECK-NEXT: Runtime Assumptions:
-; CHECK-NEXT: Equal predicate: (zext i7 (4 * (trunc i32 %v1 to i7) * (1 + (trunc i32 %n to i7))) to i32) == 0
-; CHECK-NEXT: Equal predicate: (8 * (zext i4 (trunc i32 %v1 to i4) to i32))<nuw><nsw> == 0
;
entry:
%v1 = load i32, ptr %B, align 4
More information about the llvm-commits
mailing list