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 Strong SIV test was incorrectly concluding "no dependence" when the
coefficient is symbolic and the delta (difference between source and
destination) is zero.

When delta=0, the Strong SIV test divides delta/coeff to get the distance.
The bug occurs when coeff is an unknown symbolic value: if coeff=0 at runtime,
then 0/0 is undefined and all iterations access the same memory location,
creating a true dependence that was being missed.

@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 sebpop force-pushed the pr149991 branch 3 times, most recently from 22d4899 to f7365db Compare September 9, 2025 19:37
@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
Copy link
Collaborator

@sjoerdmeijer sjoerdmeijer left a comment

Choose a reason for hiding this comment

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

LGTM, provided that @Meinersbur previous minor comments have been addressed, which I think is the case.

…tions

The Strong SIV test was incorrectly concluding "no dependence" when the
coefficient is symbolic and the delta (difference between source and
destination) is zero.

When delta=0, the Strong SIV test divides delta/coeff to get the distance.
The bug occurs when coeff is an unknown symbolic value: if coeff=0 at runtime,
then 0/0 is undefined and all iterations access the same memory location,
creating a true dependence that was being missed.
Comment on lines +3711 to +3713
bool AddedAssumptionsDuringTests =
Result.Assumptions.getPredicates().size() > AssumptionsBeforeTests;
if (AllEqual && !AddedAssumptionsDuringTests)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it allowed to ignore the assumptions added before tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Currently we return nullptr when dependence vector is "AllEqual".
This code is here to guard against returning nullptr when the dependence is conditional to runtime assumptions: if we return nullptr the assumptions would be discarded.
We may need to revisit what we return for "no dependence" because we can't attach assumptions to a "nullptr".

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the condition should be Result.Assumptions.getPredicates().empty(), not !AddedAssumptionsDuringTests. If I understand correctly, current implementation silently discards the assumptions added before tests.

// 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)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why only SCEVUnknown? What happens if Coeff is like %m * %n? I think this may also be zero.

Copy link
Contributor Author

@sebpop sebpop Nov 28, 2025

Choose a reason for hiding this comment

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

I rewrote the code to avoid enumerating all possible expressions that Coeff can take at this location:

    if (isa<SCEVConstant>(Coeff) || SE->isKnownNonZero(Coeff)) {
      LLVM_DEBUG(
          dbgs() << "\t    Coefficient proven non-zero by SCEV analysis\n");
    } else {
      // Cannot prove at compile time, would need runtime assumption.
[...]
    }

Copy link
Contributor

Choose a reason for hiding this comment

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

isa<SCEVConstant>(Coeff) would be redundant.

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.

5 participants