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
+}

…ce 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.
…tions (llvm#149991)

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
@sebpop sebpop changed the title [DA] Fix zero coeff bug in Strong SIV test with runtime assumptions [DA] Fix zero coeff bug in Strong SIV test with runtime assumptions (#149991) Sep 9, 2025
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)) {
Copy link
Member

Choose a reason for hiding this comment

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

This is the same test as on line 1288. If they are not equally powerful (that would be some TODO to fix):

boo lsKnownNonZero = SE->isKnownNonZero(Coeff) || SE->isKnownPredicate(ICmpInst::ICMP_NE, Coeff, Zero);

There is also DependenceInfo::isKnownPredicate

Comment on lines +1300 to +1306
const SCEVPredicate *Pred =
SE->getComparePredicate(ICmpInst::ICMP_NE, Coeff, Zero);
SmallVector<const SCEVPredicate *, 4> NewPreds(
Assumptions.getPredicates());
NewPreds.push_back(Pred);
const_cast<DependenceInfo *>(this)->Assumptions =
SCEVUnionPredicate(NewPreds, *SE);
Copy link
Member

Choose a reason for hiding this comment

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

Consider refactoring this. There already is SCEVUnionPredicate::add but it is private, mayb add a method that reurns the union.

Comment on lines +1305 to +1306
const_cast<DependenceInfo *>(this)->Assumptions =
SCEVUnionPredicate(NewPreds, *SE);
Copy link
Member

Choose a reason for hiding this comment

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

To avoid const_cast<DependenceInfo *>(this), consider making this method non-const (since with this change, it isn't).

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