[llvm] [DependenceAnalysis] Prevent non-AddRec expressions from reaching testSIV (PR #154980)

Sebastian Pop via llvm-commits llvm-commits at lists.llvm.org
Tue Sep 9 11:48:38 PDT 2025


https://github.com/sebpop updated https://github.com/llvm/llvm-project/pull/154980

>From a0b058263cc6a23c6ec6b472a0c40b3811a858af Mon Sep 17 00:00:00 2001
From: Sebastian Pop <spop at nvidia.com>
Date: Thu, 21 Aug 2025 14:39:49 -0500
Subject: [PATCH] [DependenceAnalysis] Prevent non-AddRec expressions from
 reaching testSIV

Fixes GitHub issue #148435 where testSIV() would hit an assertion failure with
Src and Dst not containing AddRec expressions.

The fix adds reclassification logic at all points where SIV subscripts are
tested to ensure that if expressions have become non-AddRec, they are
reclassified as ZIV instead of causing assertion failures.

Add regression test to prevent future occurrences of this issue.
---
 llvm/lib/Analysis/DependenceAnalysis.cpp      | 152 ++++++++++++++----
 .../Analysis/DependenceAnalysis/PR148435.ll   |  92 +++++++++++
 .../DependenceAnalysis/bounds-check.ll        |  27 ++++
 3 files changed, 240 insertions(+), 31 deletions(-)
 create mode 100644 llvm/test/Analysis/DependenceAnalysis/PR148435.ll
 create mode 100644 llvm/test/Analysis/DependenceAnalysis/bounds-check.ll

diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp
index da86a8d2cc9c0..17d617d9eaf97 100644
--- a/llvm/lib/Analysis/DependenceAnalysis.cpp
+++ b/llvm/lib/Analysis/DependenceAnalysis.cpp
@@ -873,6 +873,9 @@ void DependenceInfo::collectCommonLoops(const SCEV *Expression,
                                         SmallBitVector &Loops) const {
   while (LoopNest) {
     unsigned Level = LoopNest->getLoopDepth();
+    LLVM_DEBUG(dbgs() << "MaxLevels = " << MaxLevels << "\n");
+    LLVM_DEBUG(dbgs() << "Level = " << Level << "\n");
+    assert(Level <= MaxLevels && "Level larger than MaxLevels.");
     if (Level <= CommonLevels && !SE->isLoopInvariant(Expression, LoopNest))
       Loops.set(Level);
     LoopNest = LoopNest->getParentLoop();
@@ -959,6 +962,10 @@ bool DependenceInfo::checkSubscript(const SCEV *Expr, const Loop *LoopNest,
   if (!AddRec)
     return isLoopInvariant(Expr, LoopNest);
 
+  const SCEV *Step = AddRec->getStepRecurrence(*SE);
+  if (!isLoopInvariant(Step, LoopNest))
+    return false;
+
   // The AddRec must depend on one of the containing loops. Otherwise,
   // mapSrcLoop and mapDstLoop return indices outside the intended range. This
   // can happen when a subscript in one loop references an IV from a sibling
@@ -970,14 +977,16 @@ bool DependenceInfo::checkSubscript(const SCEV *Expr, const Loop *LoopNest,
   if (!L)
     return false;
 
+  unsigned Level = IsSrc ? mapSrcLoop(L) : mapDstLoop(L);
+  // Check that the mapped loop index is within bounds for the SmallBitVector.
+  // This can happen when loop depths exceed MaxLevels due to the mapping
+  // algorithm.
+
+  LLVM_DEBUG(dbgs() << "MaxLevels = " << MaxLevels << "\n");
+  LLVM_DEBUG(dbgs() << "Level = " << Level << "\n");
+  assert(Level <= MaxLevels && "Level larger than MaxLevels.");
+  Loops.set(Level);
   const SCEV *Start = AddRec->getStart();
-  const SCEV *Step = AddRec->getStepRecurrence(*SE);
-  if (!isLoopInvariant(Step, LoopNest))
-    return false;
-  if (IsSrc)
-    Loops.set(mapSrcLoop(AddRec->getLoop()));
-  else
-    Loops.set(mapDstLoop(AddRec->getLoop()));
   return checkSubscript(Start, LoopNest, Loops, IsSrc);
 }
 
@@ -3835,6 +3844,15 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
   // test separable subscripts
   for (unsigned SI : Separable.set_bits()) {
     LLVM_DEBUG(dbgs() << "testing subscript " << SI);
+
+    // For SIV subscripts, reclassify to handle cases where expressions
+    // may have become non-AddRec.
+    if (Pair[SI].Classification == Subscript::SIV) {
+      Pair[SI].Classification = classifyPair(
+          Pair[SI].Src, LI->getLoopFor(Src->getParent()), Pair[SI].Dst,
+          LI->getLoopFor(Dst->getParent()), Pair[SI].Loops);
+    }
+
     switch (Pair[SI].Classification) {
     case Subscript::ZIV:
       LLVM_DEBUG(dbgs() << ", ZIV\n");
@@ -3843,7 +3861,7 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
       break;
     case Subscript::SIV: {
       LLVM_DEBUG(dbgs() << ", SIV\n");
-      unsigned Level;
+      unsigned Level = 0;
       const SCEV *SplitIter = nullptr;
       if (testSIV(Pair[SI].Src, Pair[SI].Dst, Level, Result, NewConstraint,
                   SplitIter))
@@ -3893,13 +3911,52 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
         bool Changed = false;
         for (unsigned SJ : Sivs.set_bits()) {
           LLVM_DEBUG(dbgs() << "testing subscript " << SJ << ", SIV\n");
-          // SJ is an SIV subscript that's part of the current coupled group
-          unsigned Level;
+          // SJ is an SIV subscript that's part of the current coupled group.
+
+          // Reclassify to handle cases where expressions may have become
+          // non-AddRec.
+          Pair[SJ].Classification = classifyPair(
+              Pair[SJ].Src, LI->getLoopFor(Src->getParent()), Pair[SJ].Dst,
+              LI->getLoopFor(Dst->getParent()), Pair[SJ].Loops);
+
+          unsigned Level = 0;
           const SCEV *SplitIter = nullptr;
-          LLVM_DEBUG(dbgs() << "SIV\n");
-          if (testSIV(Pair[SJ].Src, Pair[SJ].Dst, Level, Result, NewConstraint,
-                      SplitIter))
-            return nullptr;
+
+          switch (Pair[SJ].Classification) {
+          case Subscript::ZIV:
+            LLVM_DEBUG(dbgs() << " -> reclassified as ZIV\n");
+            if (testZIV(Pair[SJ].Src, Pair[SJ].Dst, Result))
+              return nullptr;
+            Sivs.reset(SJ);
+            continue;
+          case Subscript::SIV:
+            LLVM_DEBUG(dbgs() << "SIV\n");
+            if (testSIV(Pair[SJ].Src, Pair[SJ].Dst, Level, Result,
+                        NewConstraint, SplitIter))
+              return nullptr;
+            break;
+          case Subscript::RDIV:
+            LLVM_DEBUG(dbgs() << " -> reclassified as RDIV\n");
+            if (testRDIV(Pair[SJ].Src, Pair[SJ].Dst, Result))
+              return nullptr;
+            Sivs.reset(SJ);
+            continue;
+          case Subscript::MIV:
+            LLVM_DEBUG(dbgs() << " -> reclassified as MIV\n");
+            Mivs.set(SJ);
+            Sivs.reset(SJ);
+            continue;
+          case Subscript::NonLinear:
+            LLVM_DEBUG(dbgs() << " -> reclassified as NonLinear\n");
+            Result.Consistent = false;
+            Sivs.reset(SJ);
+            continue;
+          }
+
+          LLVM_DEBUG(dbgs() << "MaxLevels = " << MaxLevels << "\n");
+          LLVM_DEBUG(dbgs() << "Level = " << Level << "\n");
+          assert(Level <= MaxLevels && "Level larger than MaxLevels.");
+
           ConstrainedLevels.set(Level);
           if (intersectConstraints(&Constraints[Level], &NewConstraint)) {
             if (Constraints[Level].isEmpty()) {
@@ -3939,8 +3996,11 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
               case Subscript::RDIV:
               case Subscript::MIV:
                 break;
-              default:
-                llvm_unreachable("bad subscript classification");
+              case Subscript::NonLinear:
+                LLVM_DEBUG(dbgs() << "NonLinear\n");
+                Result.Consistent = false;
+                Mivs.reset(SJ);
+                break;
               }
             }
           }
@@ -4177,14 +4237,22 @@ const SCEV *DependenceInfo::getSplitIteration(const Dependence &Dep,
   for (unsigned SI : Separable.set_bits()) {
     switch (Pair[SI].Classification) {
     case Subscript::SIV: {
-      unsigned Level;
-      const SCEV *SplitIter = nullptr;
-      (void)testSIV(Pair[SI].Src, Pair[SI].Dst, Level, Result, NewConstraint,
-                    SplitIter);
-      if (Level == SplitLevel) {
-        assert(SplitIter != nullptr);
-        return SplitIter;
+      // Reclassify to handle cases where expressions may have become non-AddRec.
+      Pair[SI].Classification = classifyPair(
+          Pair[SI].Src, LI->getLoopFor(Src->getParent()), Pair[SI].Dst,
+          LI->getLoopFor(Dst->getParent()), Pair[SI].Loops);
+      if (Pair[SI].Classification == Subscript::SIV) {
+        unsigned Level = 0;
+        const SCEV *SplitIter = nullptr;
+        (void)testSIV(Pair[SI].Src, Pair[SI].Dst, Level, Result, NewConstraint,
+                      SplitIter);
+        if (Level == SplitLevel) {
+          assert(SplitIter != nullptr);
+          return SplitIter;
+        }
       }
+      // If reclassified as non-SIV, we can't get a split iteration from this
+      // subscript.
       break;
     }
     case Subscript::ZIV:
@@ -4216,13 +4284,33 @@ const SCEV *DependenceInfo::getSplitIteration(const Dependence &Dep,
     while (Sivs.any()) {
       bool Changed = false;
       for (unsigned SJ : Sivs.set_bits()) {
-        // SJ is an SIV subscript that's part of the current coupled group
-        unsigned Level;
+        // SJ is an SIV subscript that's part of the current coupled group.
+
+        // Reclassify to handle cases where expressions may have become
+        // non-AddRec.
+        Pair[SJ].Classification = classifyPair(
+            Pair[SJ].Src, LI->getLoopFor(Src->getParent()), Pair[SJ].Dst,
+            LI->getLoopFor(Dst->getParent()), Pair[SJ].Loops);
+
+        unsigned Level = 0;
         const SCEV *SplitIter = nullptr;
-        (void)testSIV(Pair[SJ].Src, Pair[SJ].Dst, Level, Result, NewConstraint,
-                      SplitIter);
-        if (Level == SplitLevel && SplitIter)
-          return SplitIter;
+
+        if (Pair[SJ].Classification == Subscript::SIV) {
+          (void)testSIV(Pair[SJ].Src, Pair[SJ].Dst, Level, Result,
+                        NewConstraint, SplitIter);
+          if (Level == SplitLevel && SplitIter)
+            return SplitIter;
+        } else {
+          // If reclassified as non-SIV, we can't get a split iteration from
+          // this subscript.
+          Sivs.reset(SJ);
+          continue;
+        }
+
+        LLVM_DEBUG(dbgs() << "MaxLevels = " << MaxLevels << "\n");
+        LLVM_DEBUG(dbgs() << "Level = " << Level << "\n");
+        assert(Level <= MaxLevels && "Level larger than MaxLevels.");
+
         ConstrainedLevels.set(Level);
         if (intersectConstraints(&Constraints[Level], &NewConstraint))
           Changed = true;
@@ -4250,8 +4338,10 @@ const SCEV *DependenceInfo::getSplitIteration(const Dependence &Dep,
         case Subscript::RDIV:
         case Subscript::MIV:
           break;
-        default:
-          llvm_unreachable("bad subscript classification");
+        case Subscript::NonLinear:
+          Result.Consistent = false;
+          Mivs.reset(SJ);
+          break;
         }
       }
     }
diff --git a/llvm/test/Analysis/DependenceAnalysis/PR148435.ll b/llvm/test/Analysis/DependenceAnalysis/PR148435.ll
new file mode 100644
index 0000000000000..aa63741e0f7b8
--- /dev/null
+++ b/llvm/test/Analysis/DependenceAnalysis/PR148435.ll
@@ -0,0 +1,92 @@
+; 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 bug #148435 - SIV test assertion failure.
+; This test ensures that testSIV handles the case where neither Src nor Dst
+; expressions contain AddRec after propagation, which can happen when
+; constraints simplify the expressions to non-AddRec forms.
+
+define void @_Z1cb(ptr %a) {
+; CHECK-LABEL: '_Z1cb'
+; CHECK-NEXT:  Src: store i8 0, ptr %arrayidx9, align 1 --> Dst: store i8 0, ptr %arrayidx9, align 1
+; CHECK-NEXT:    da analyze - output [*]!
+;
+entry:
+  br label %for.body
+
+for.cond.cleanup.loopexit:                        ; preds = %for.body
+  ret void
+
+for.body:                                         ; preds = %for.body, %entry
+  %indvars.iv23 = phi i64 [ 0, %entry ], [ %indvars.iv.next24, %for.body ]
+  %idxprom = and i64 %indvars.iv23, 1
+  %arrayidx9 = getelementptr inbounds [0 x [12 x [12 x i8]]], ptr %a, i64 0, i64 %idxprom, i64 0, i64 %indvars.iv23
+  store i8 0, ptr %arrayidx9, align 1
+  %indvars.iv.next24 = add i64 %indvars.iv23, 1
+  %exitcond.not = icmp eq i64 %indvars.iv.next24, 0
+  br i1 %exitcond.not, label %for.cond.cleanup.loopexit, label %for.body
+}
+
+ at a = external global [0 x [12 x [12 x i8]]], align 1
+
+define void @test_siv_no_addrec(i1 %d, i32 %b) {
+; CHECK-LABEL: 'test_siv_no_addrec'
+; CHECK-NEXT:  Src: store i8 0, ptr %arrayidx7, align 1 --> Dst: store i8 0, ptr %arrayidx7, align 1
+; CHECK-NEXT:    da analyze - output [* *]!
+;
+entry:
+  %conv.val = select i1 %d, i16 1, i16 0
+  br label %for.cond
+
+for.cond:                                         ; preds = %for.inc8, %entry
+  %e.0 = phi i32 [ %b, %entry ], [ %inc9, %for.inc8 ]
+  %cmp = icmp ult i32 %e.0, 10
+  br i1 %cmp, label %for.cond1, label %for.end10
+
+for.cond1:                                        ; preds = %for.inc, %for.cond
+  %f.0 = phi i16 [ %conv.val, %for.cond ], [ %add, %for.inc ]
+  %cmp2 = icmp slt i16 %f.0, 10
+  br i1 %cmp2, label %for.body4, label %for.inc8
+
+for.body4:                                        ; preds = %for.cond1
+  %sub = add i32 %e.0, -3
+  %idxprom = zext i32 %sub to i64
+  %idxprom5 = sext i16 %f.0 to i64
+  %idxprom6 = zext i32 %e.0 to i64
+  %arrayidx7 = getelementptr inbounds [0 x [12 x [12 x i8]]], ptr @a, i64 0, i64 %idxprom, i64 %idxprom5, i64 %idxprom6
+  store i8 0, ptr %arrayidx7, align 1
+  br label %for.inc
+
+for.inc:                                          ; preds = %for.body4
+  %add = add i16 %f.0, 2
+  br label %for.cond1
+
+for.inc8:                                         ; preds = %for.cond1
+  %inc9 = add i32 %e.0, 1
+  br label %for.cond
+
+for.end10:                                        ; preds = %for.cond
+  ret void
+}
+
+define void @f1(ptr %a) {
+; CHECK-LABEL: 'f1'
+; CHECK-NEXT:  Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1
+; CHECK-NEXT:    da analyze - none!
+; Note: the second patch for PR148435 modifies the above CHECK to correct "output [*]".
+;
+entry:
+  br label %loop
+
+loop:
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %and = and i64 %i, 1
+  %idx = getelementptr inbounds [4 x [4 x i8]], ptr %a, i64 0, i64 %and, i64 %and
+  store i8 0, ptr %idx
+  %i.next = add i64 %i, 1
+  %exitcond.not = icmp slt i64 %i.next, 8
+  br i1 %exitcond.not, label %loop, label %exit
+
+exit:
+  ret void
+}
diff --git a/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll b/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll
new file mode 100644
index 0000000000000..0531524dd3e79
--- /dev/null
+++ b/llvm/test/Analysis/DependenceAnalysis/bounds-check.ll
@@ -0,0 +1,27 @@
+; 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 SmallBitVector bounds checking bug in DependenceAnalysis.
+; This test ensures that loop index mapping functions don't cause out-of-bounds
+; access to SmallBitVector when loop depths exceed MaxLevels.
+
+define void @bounds_check_test(ptr %a) {
+; CHECK-LABEL: 'bounds_check_test'
+; CHECK-NEXT:  Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1
+; CHECK-NEXT:    da analyze - none!
+;
+entry:
+  br label %loop
+
+loop:
+  %i = phi i64 [ 0, %entry ], [ %i.next, %loop ]
+  %and = and i64 %i, 1  ; Creates index 0 or 1
+  %idx = getelementptr inbounds [4 x [4 x i8]], ptr %a, i64 0, i64 %and, i64 %i
+  store i8 0, ptr %idx
+  %i.next = add i64 %i, 1
+  %exitcond.not = icmp slt i64 %i.next, 4
+  br i1 %exitcond.not, label %loop, label %exit
+
+exit:
+  ret void
+}



More information about the llvm-commits mailing list