Skip to content

Conversation

sebpop
Copy link
Contributor

@sebpop sebpop commented Aug 22, 2025

Fix GitHub issue #149991 where Strong SIV test incorrectly concludes 'none!' for symbolic coefficients that could be zero, leading to 0/0 undef 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.

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.

@llvmbot llvmbot added the llvm:analysis Includes value tracking, cost tables and constant folding label Aug 22, 2025
@llvmbot
Copy link
Member

llvmbot commented Aug 22, 2025

@llvm/pr-subscribers-llvm-analysis

Author: Sebastian Pop (sebpop)

Changes

Fix GitHub issue #149991 where Strong SIV test incorrectly concludes 'none!' for symbolic coefficients that could be zero, leading to 0/0 undef 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.

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.

Full diff: https://github.com/llvm/llvm-project/pull/155037.diff

3 Files Affected:

  • (modified) llvm/lib/Analysis/DependenceAnalysis.cpp (+35-14)
  • (modified) llvm/test/Analysis/DependenceAnalysis/DADelin.ll (+10)
  • (added) llvm/test/Analysis/DependenceAnalysis/zero-coefficient.ll (+30)
diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index f33e04e804e3d..50e0fb7b3bcd3 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_SGT, Coeff, Zero)) {
+        LLVM_DEBUG(
+            dbgs()
+            << "\t    Coefficient proven positive by SCEV range analysis\n");
+      } else {
+        // Cannot prove at compile time, add runtime assumption
+        const SCEVPredicate *Pred =
+            SE->getComparePredicate(ICmpInst::ICMP_SGT, 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;
@@ -3574,8 +3595,8 @@ 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, SCEVUnionPredicate(this->Assumptions, *SE));
   }
 
   const MemoryLocation &DstLoc = MemoryLocation::get(Dst);
@@ -3586,8 +3607,8 @@ 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, SCEVUnionPredicate(this->Assumptions, *SE));
   case AliasResult::NoAlias:
     // If the objects noalias, they are distinct, accesses are independent.
     LLVM_DEBUG(dbgs() << "no alias\n");
@@ -3601,8 +3622,8 @@ 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, SCEVUnionPredicate(this->Assumptions, *SE));
   }
 
   Value *SrcPtr = getLoadStorePointerOperand(Src);
@@ -3621,8 +3642,8 @@ 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, SCEVUnionPredicate(this->Assumptions, *SE));
   }
 
   // Even if the base pointers are the same, they may not be loop-invariant. It
@@ -3634,8 +3655,8 @@ 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, SCEVUnionPredicate(this->Assumptions, *SE));
   }
 
   uint64_t EltSize = SrcLoc.Size.toRaw();
@@ -3646,8 +3667,8 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
   if (!SE->isKnownMultipleOf(SrcEv, EltSize, Assume) ||
       !SE->isKnownMultipleOf(DstEv, EltSize, Assume)) {
     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, SCEVUnionPredicate(this->Assumptions, *SE));
   }
 
   if (!Assume.empty()) {
@@ -3670,7 +3691,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *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(this->Assumptions, *SE),
                         PossiblyLoopIndependent, CommonLevels);
   ++TotalArrayPairs;
 
diff --git a/llvm/test/Analysis/DependenceAnalysis/DADelin.ll b/llvm/test/Analysis/DependenceAnalysis/DADelin.ll
index 8f94a455d3724..c7b9865329214 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 sgt) 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 sgt) 0
+; CHECK-NEXT:  Compare predicate: %k sgt) 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 sgt) 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 sgt) 0
+; CHECK-NEXT:  Compare predicate: %k sgt) 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..d6ad924da722b
--- /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 sgt) 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
+}

sebpop added 2 commits August 23, 2025 09:53
…ce 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.
…tions (llvm#149977)

Fix GitHub issue llvm#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
const SCEV *Zero = SE->getZero(Coeff->getType());

// Ask SCEV's range analysis if it can prove Coeff > Zero
if (SE->isKnownPredicate(ICmpInst::ICMP_SGT, Coeff, Zero)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this (or should be) deduction already redudant with SE->isKnownNonZero?

Comment on lines +3588 to +3595
auto I = UniqueAssumptions.begin();
while (I != UniqueAssumptions.end()) {
if (P->implies(*I, *SE)) {
I = UniqueAssumptions.erase(I);
} else {
++I;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto I = UniqueAssumptions.begin();
while (I != UniqueAssumptions.end()) {
if (P->implies(*I, *SE)) {
I = UniqueAssumptions.erase(I);
} else {
++I;
}
}
erase_if(UniqueAssumptions, [](SCEVPredicate *) {...})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
llvm:analysis Includes value tracking, cost tables and constant folding
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants