[llvm] [DA] Fix zero coeff bug in Strong SIV test with runtime assumptions (PR #155037)
Sebastian Pop via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 9 06:58:22 PDT 2025
https://github.com/sebpop updated https://github.com/llvm/llvm-project/pull/155037
>From 6d20fdd3e47a77c639e96b1e4de72a99b61c6c6e 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 1/2] [DA] Simplify runtime predicate collection and extend to
all dependence tests
Previously, predicates were collected using a local `Assume` vector.
This patch:
1. Removes local `Assume` vector, uses class member `Assumptions` instead.
2. Adds a `getNonRedundantAssumptions()` helper for deduplication.
3. Extends predicate collection to all dependence tests.
---
.../llvm/Analysis/DependenceAnalysis.h | 6 ++
llvm/lib/Analysis/DependenceAnalysis.cpp | 76 +++++++++++--------
2 files changed, 51 insertions(+), 31 deletions(-)
diff --git a/llvm/include/llvm/Analysis/DependenceAnalysis.h b/llvm/include/llvm/Analysis/DependenceAnalysis.h
index f66c79d915665..0db8f5d8eca02 100644
--- a/llvm/include/llvm/Analysis/DependenceAnalysis.h
+++ b/llvm/include/llvm/Analysis/DependenceAnalysis.h
@@ -598,6 +598,12 @@ class DependenceInfo {
/// returns NULL.
const SCEVConstant *collectConstantUpperBound(const Loop *l, Type *T) const;
+ /// getNonRedundantAssumptions - Remove redundant assumptions from the
+ /// collection and return a SCEVUnionPredicate with unique assumptions.
+ /// This ensures that each assumption is only present once and that
+ /// stronger assumptions imply weaker ones.
+ SCEVUnionPredicate getNonRedundantAssumptions() const;
+
/// classifyPair - Examines the subscript pair (the Src and Dst SCEVs)
/// and classifies it as either ZIV, SIV, RDIV, MIV, or Nonlinear.
/// Collects the associated loops in a set.
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index da86a8d2cc9c0..848914e4855e0 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -3570,6 +3570,32 @@ SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const {
return SCEVUnionPredicate(Assumptions, *SE);
}
+// getNonRedundantAssumptions - Remove redundant assumptions from the collection
+// and return a SCEVUnionPredicate with the unique assumptions. This ensures
+// that each assumption is only present once and that stronger assumptions imply
+// weaker ones, avoiding unnecessary runtime checks.
+SCEVUnionPredicate DependenceInfo::getNonRedundantAssumptions() const {
+ SmallVector<const SCEVPredicate *, 4> UniqueAssumptions;
+
+ for (const SCEVPredicate *P : Assumptions) {
+ bool Implied = false;
+ for (const SCEVPredicate *Existing : UniqueAssumptions) {
+ if (Existing->implies(P, *SE)) {
+ Implied = true;
+ break;
+ }
+ }
+ if (!Implied) {
+ erase_if(UniqueAssumptions, [&](const SCEVPredicate *Existing) {
+ return P->implies(Existing, *SE);
+ });
+ UniqueAssumptions.push_back(P);
+ }
+ }
+
+ return SCEVUnionPredicate(UniqueAssumptions, *SE);
+}
+
// depends -
// Returns NULL if there is no dependence.
// Otherwise, return a Dependence with as many details as possible.
@@ -3584,7 +3610,6 @@ SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const {
std::unique_ptr<Dependence>
DependenceInfo::depends(Instruction *Src, Instruction *Dst,
bool UnderRuntimeAssumptions) {
- SmallVector<const SCEVPredicate *, 4> Assume;
bool PossiblyLoopIndependent = true;
if (Src == Dst)
PossiblyLoopIndependent = false;
@@ -3596,8 +3621,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, getNonRedundantAssumptions());
}
const MemoryLocation &DstLoc = MemoryLocation::get(Dst);
@@ -3608,8 +3632,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, getNonRedundantAssumptions());
case AliasResult::NoAlias:
// If the objects noalias, they are distinct, accesses are independent.
LLVM_DEBUG(dbgs() << "no alias\n");
@@ -3623,8 +3646,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, getNonRedundantAssumptions());
}
Value *SrcPtr = getLoadStorePointerOperand(Src);
@@ -3643,8 +3665,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, getNonRedundantAssumptions());
}
// Even if the base pointers are the same, they may not be loop-invariant. It
@@ -3656,8 +3677,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, getNonRedundantAssumptions());
}
uint64_t EltSize = SrcLoc.Size.toRaw();
@@ -3665,34 +3685,22 @@ 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)) {
+ if (!SE->isKnownMultipleOf(SrcEv, EltSize, Assumptions) ||
+ !SE->isKnownMultipleOf(DstEv, EltSize, Assumptions)) {
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, getNonRedundantAssumptions());
}
- 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);
- }
- }
+ // If runtime assumptions were added but not allowed, return confused
+ // dependence.
+ if (!UnderRuntimeAssumptions && !Assumptions.empty())
+ return std::make_unique<Dependence>(Src, Dst, getNonRedundantAssumptions());
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),
+ FullDependence Result(Src, Dst, SCEVUnionPredicate(Assumptions, *SE),
PossiblyLoopIndependent, CommonLevels);
++TotalArrayPairs;
@@ -4036,6 +4044,12 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
return nullptr;
}
+ // If runtime assumptions were added but not allowed, return confused
+ // dependence.
+ if (!UnderRuntimeAssumptions && !Assumptions.empty())
+ return std::make_unique<Dependence>(Src, Dst, getNonRedundantAssumptions());
+
+ Result.Assumptions = getNonRedundantAssumptions();
return std::make_unique<FullDependence>(std::move(Result));
}
>From c981a8b1a9eca3a989ec0ef8573d65d0bae89b1c Mon Sep 17 00:00:00 2001
From: Sebastian Pop <spop at nvidia.com>
Date: Sat, 23 Aug 2025 09:34:44 -0500
Subject: [PATCH 2/2] [DA] Fix zero coefficient bug in Strong SIV test using
runtime assumptions (#149977)
Fix GitHub issue #149991 where Strong SIV test incorrectly concludes 'none'
for symbolic coefficients that could be zero, leading to 0/0 undefined behavior.
The issue occurs in subscripts like {base,+,coeff} where coeff is symbolic:
- When coeff != 0: different iterations access different locations
- When coeff = 0: all iterations access the same location (many dependencies)
The Strong SIV test's Delta=0 case assumed 0/X=0 where X is the coefficient,
but when X could be zero, we have 0/0 which is undefined. The analysis needs
to be conservative when the coefficient might be zero.
Solution:
When coefficient is SCEVUnknown and cannot be proven non-zero at compile time,
use SCEV range analysis to attempt proving coefficient > 0. If this fails,
add a runtime assumption 'coeff > 0' to the dependence result.
This allows precise analysis when possible (none under assumption coeff > 0)
while maintaining correctness by exposing the required assumption.
Test cases:
- zero-coefficient.ll: New test for the reported bug
- DADelin.ll: Updated to expect runtime assumptions for symbolic coefficients
---
llvm/lib/Analysis/DependenceAnalysis.cpp | 23 +++++++++++++-
.../Analysis/DependenceAnalysis/DADelin.ll | 10 +++++++
.../DependenceAnalysis/zero-coefficient.ll | 30 +++++++++++++++++++
3 files changed, 62 insertions(+), 1 deletion(-)
create mode 100644 llvm/test/Analysis/DependenceAnalysis/zero-coefficient.ll
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index 848914e4855e0..cfa11432abadd 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -1282,7 +1282,28 @@ bool DependenceInfo::strongSIVtest(const SCEV *Coeff, const SCEV *SrcConst,
Result.DV[Level].Direction &= Dependence::DVEntry::EQ;
++StrongSIVsuccesses;
} else if (Delta->isZero()) {
- // since 0/X == 0
+ // Check if coefficient could be zero. If so, 0/0 is undefined and we
+ // cannot conclude that only same-iteration dependencies exist.
+ // When coeff=0, all iterations access the same location.
+ if (isa<SCEVUnknown>(Coeff) && !SE->isKnownNonZero(Coeff)) {
+ // Use SCEV range analysis to prove coefficient != 0 in loop context.
+ const SCEV *Zero = SE->getZero(Coeff->getType());
+
+ // Ask SCEV's range analysis if it can prove Coeff != Zero.
+ if (SE->isKnownPredicate(ICmpInst::ICMP_NE, Coeff, Zero)) {
+ LLVM_DEBUG(
+ dbgs()
+ << "\t Coefficient proven non-zero by SCEV range analysis\n");
+ } else {
+ // Cannot prove at compile time, add runtime assumption.
+ const SCEVPredicate *Pred =
+ SE->getComparePredicate(ICmpInst::ICMP_NE, Coeff, Zero);
+ const_cast<DependenceInfo *>(this)->Assumptions.push_back(Pred);
+ LLVM_DEBUG(dbgs() << "\t Added runtime assumption: " << *Coeff
+ << " != 0\n");
+ }
+ }
+ // since 0/X == 0 (where X is known non-zero)
Result.DV[Level].Distance = Delta;
NewConstraint.setDistance(Delta, CurLoop);
Result.DV[Level].Direction &= Dependence::DVEntry::EQ;
diff --git a/llvm/test/Analysis/DependenceAnalysis/DADelin.ll b/llvm/test/Analysis/DependenceAnalysis/DADelin.ll
index 8f94a455d3724..81eb6cfc9f361 100644
--- a/llvm/test/Analysis/DependenceAnalysis/DADelin.ll
+++ b/llvm/test/Analysis/DependenceAnalysis/DADelin.ll
@@ -649,8 +649,13 @@ define void @coeff_may_negative(ptr %a, i32 %k) {
; CHECK-NEXT: da analyze - none!
; CHECK-NEXT: Src: store i8 42, ptr %idx.0, align 1 --> Dst: store i8 42, ptr %idx.1, align 1
; CHECK-NEXT: da analyze - output [*|<]!
+; CHECK-NEXT: Runtime Assumptions:
+; CHECK-NEXT: Compare predicate: %k ne) 0
; CHECK-NEXT: Src: store i8 42, ptr %idx.1, align 1 --> Dst: store i8 42, ptr %idx.1, align 1
; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Runtime Assumptions:
+; CHECK-NEXT: Compare predicate: %k ne) 0
+; CHECK-NEXT: Compare predicate: %k ne) 0
;
entry:
br label %loop
@@ -688,8 +693,13 @@ define void @coeff_positive(ptr %a, i32 %k) {
; CHECK-NEXT: da analyze - none!
; CHECK-NEXT: Src: store i8 42, ptr %idx.0, align 1 --> Dst: store i8 42, ptr %idx.1, align 1
; CHECK-NEXT: da analyze - output [*|<]!
+; CHECK-NEXT: Runtime Assumptions:
+; CHECK-NEXT: Compare predicate: %k ne) 0
; CHECK-NEXT: Src: store i8 42, ptr %idx.1, align 1 --> Dst: store i8 42, ptr %idx.1, align 1
; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Runtime Assumptions:
+; CHECK-NEXT: Compare predicate: %k ne) 0
+; CHECK-NEXT: Compare predicate: %k ne) 0
;
entry:
br label %loop
diff --git a/llvm/test/Analysis/DependenceAnalysis/zero-coefficient.ll b/llvm/test/Analysis/DependenceAnalysis/zero-coefficient.ll
new file mode 100644
index 0000000000000..6cc94d7d5590a
--- /dev/null
+++ b/llvm/test/Analysis/DependenceAnalysis/zero-coefficient.ll
@@ -0,0 +1,30 @@
+; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 5
+; RUN: opt < %s -disable-output "-passes=print<da>" 2>&1 | FileCheck %s
+
+; Test case for GitHub issue #149991: Strong SIV test with symbolic coefficient
+; that could be zero. Fixed using runtime assumptions: assume coefficient != 0.
+
+target datalayout = "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128"
+
+define void @test_zero_coefficient(ptr %a, i64 %k) {
+; CHECK-LABEL: 'test_zero_coefficient'
+; CHECK-NEXT: Src: store i8 42, ptr %idx, align 1 --> Dst: store i8 42, ptr %idx, align 1
+; CHECK-NEXT: da analyze - none!
+; CHECK-NEXT: Runtime Assumptions:
+; CHECK-NEXT: Compare predicate: %k ne) 0
+;
+entry:
+ br label %loop
+
+loop:
+ %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+ %subscript = mul i64 %i, %k ; When %k=0, all iterations access %a[0]
+ %idx = getelementptr i8, ptr %a, i64 %subscript
+ store i8 42, ptr %idx
+ %i.next = add i64 %i, 1
+ %cond.exit = icmp eq i64 %i.next, 100
+ br i1 %cond.exit, label %exit, label %loop
+
+exit:
+ ret void
+}
More information about the llvm-commits
mailing list