diff --git a/llvm/include/llvm/Analysis/DependenceAnalysis.h b/llvm/include/llvm/Analysis/DependenceAnalysis.h index f66c79d915665..300cfb73af5c1 100644 --- a/llvm/include/llvm/Analysis/DependenceAnalysis.h +++ b/llvm/include/llvm/Analysis/DependenceAnalysis.h @@ -285,7 +285,8 @@ class LLVM_ABI FullDependence final : public Dependence { class DependenceInfo { public: DependenceInfo(Function *F, AAResults *AA, ScalarEvolution *SE, LoopInfo *LI) - : AA(AA), SE(SE), LI(LI), F(F) {} + : AA(AA), SE(SE), LI(LI), F(F), Assumptions({}, *SE), + UnderRuntimeAssumptions(false) {} /// Handle transitive invalidation when the cached analysis results go away. LLVM_ABI bool invalidate(Function &F, const PreservedAnalyses &PA, @@ -355,7 +356,33 @@ class DependenceInfo { ScalarEvolution *SE; LoopInfo *LI; Function *F; - SmallVector Assumptions; + + /// Runtime assumptions collected during dependence analysis. + /// + /// The dependence analysis employs a cascade of tests from simple to complex: + /// ZIV -> SIV (Strong/Weak-Crossing/Weak-Zero) -> RDIV -> MIV -> Banerjee. + /// Each test attempts to characterize the dependence with increasing + /// precision. + /// + /// Assumption Management Strategy: + /// - Each test may require runtime assumptions (e.g., "coefficient != 0") + /// to provide precise analysis. + /// - If UnderRuntimeAssumptions=true: tests can add assumptions and continue. + /// - If UnderRuntimeAssumptions=false: tests that need assumptions fail + /// gracefully, allowing more complex tests to attempt analysis. + /// - Only assumptions from successful tests contribute to the final result. + /// - SCEVUnionPredicate automatically deduplicates redundant assumptions. + /// + /// This design ensures: + /// 1. Simpler tests get priority (better performance). + /// 2. Complex tests serve as fallbacks when simple tests fail. + /// 3. No unnecessary runtime checks from failed test attempts. + /// 4. Maintains the intended cascade behavior of the dependence analysis. + SCEVUnionPredicate Assumptions; + + /// Indicates whether runtime assumptions are collected during the analysis. + /// When false, dependence tests that would require runtime assumptions fail. + bool UnderRuntimeAssumptions; /// Subscript - This private struct represents a pair of subscripts from /// a pair of potentially multi-dimensional array references. We use a diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp index da86a8d2cc9c0..ad5415d2f765a 100644 --- a/llvm/lib/Analysis/DependenceAnalysis.cpp +++ b/llvm/lib/Analysis/DependenceAnalysis.cpp @@ -3567,7 +3567,7 @@ bool DependenceInfo::invalidate(Function &F, const PreservedAnalyses &PA, } SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const { - return SCEVUnionPredicate(Assumptions, *SE); + return Assumptions; } // depends - @@ -3584,7 +3584,12 @@ SCEVUnionPredicate DependenceInfo::getRuntimeAssumptions() const { std::unique_ptr DependenceInfo::depends(Instruction *Src, Instruction *Dst, bool UnderRuntimeAssumptions) { - SmallVector Assume; + // Set the flag for whether we're allowed to add runtime assumptions. + this->UnderRuntimeAssumptions = UnderRuntimeAssumptions; + + // Clear any previous assumptions + Assumptions = SCEVUnionPredicate({}, *SE); + bool PossiblyLoopIndependent = true; if (Src == Dst) PossiblyLoopIndependent = false; @@ -3596,8 +3601,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(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } const MemoryLocation &DstLoc = MemoryLocation::get(Dst); @@ -3608,8 +3612,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(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); case AliasResult::NoAlias: // If the objects noalias, they are distinct, accesses are independent. LLVM_DEBUG(dbgs() << "no alias\n"); @@ -3623,8 +3626,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(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } Value *SrcPtr = getLoadStorePointerOperand(Src); @@ -3643,8 +3645,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(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } // Even if the base pointers are the same, they may not be loop-invariant. It @@ -3656,8 +3657,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(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } uint64_t EltSize = SrcLoc.Size.toRaw(); @@ -3665,35 +3665,40 @@ 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)) { + SmallVector TempAssumptions; + if (!SE->isKnownMultipleOf(SrcEv, EltSize, TempAssumptions) || + !SE->isKnownMultipleOf(DstEv, EltSize, TempAssumptions)) { LLVM_DEBUG(dbgs() << "can't analyze SCEV with different offsets\n"); - return std::make_unique(Src, Dst, - SCEVUnionPredicate(Assume, *SE)); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } - if (!Assume.empty()) { - if (!UnderRuntimeAssumptions) - return std::make_unique(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); + // Add any new assumptions from the isKnownMultipleOf calls + if (!TempAssumptions.empty()) { + if (UnderRuntimeAssumptions) { + SmallVector NewPreds( + Assumptions.getPredicates()); + NewPreds.append(TempAssumptions.begin(), TempAssumptions.end()); + const_cast(this)->Assumptions = + SCEVUnionPredicate(NewPreds, *SE); + } else { + // Runtime assumptions needed but not allowed. + // Return confused dependence since we cannot proceed with precise + // analysis. + LLVM_DEBUG(dbgs() << "Runtime assumptions needed for offset analysis but " + "not allowed\n"); + return std::make_unique(Src, Dst, getRuntimeAssumptions()); } } + // Assert that we haven't added runtime assumptions when not allowed + assert(UnderRuntimeAssumptions || Assumptions.isAlwaysTrue()); + 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), - PossiblyLoopIndependent, CommonLevels); + FullDependence Result(Src, Dst, Assumptions, PossiblyLoopIndependent, + CommonLevels); ++TotalArrayPairs; unsigned Pairs = 1; @@ -4036,6 +4041,10 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst, return nullptr; } + // Assert that we haven't added runtime assumptions when not allowed + assert(UnderRuntimeAssumptions || Assumptions.isAlwaysTrue()); + + Result.Assumptions = getRuntimeAssumptions(); return std::make_unique(std::move(Result)); } diff --git a/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll b/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll index d9ccea55dd478..719a62a3d5113 100644 --- a/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll +++ b/llvm/test/Analysis/DependenceAnalysis/DifferentOffsets.ll @@ -34,8 +34,6 @@ define i32 @alias_with_parametric_offset(ptr nocapture %A, i64 %n) { ; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0 ; CHECK-NEXT: Src: %0 = load i32, ptr %A, align 1 --> Dst: %0 = load i32, ptr %A, align 1 ; CHECK-NEXT: da analyze - none! -; CHECK-NEXT: Runtime Assumptions: -; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0 ; entry: %arrayidx = getelementptr inbounds i8, ptr %A, i64 %n @@ -56,7 +54,6 @@ define i32 @alias_with_parametric_expr(ptr nocapture %A, i64 %n, i64 %m) { ; CHECK-NEXT: Src: %0 = load i32, ptr %arrayidx1, align 1 --> Dst: %0 = load i32, ptr %arrayidx1, align 1 ; CHECK-NEXT: da analyze - none! ; CHECK-NEXT: Runtime Assumptions: -; CHECK-NEXT: Equal predicate: (zext i2 ((trunc i64 %m to i2) + (-2 * (trunc i64 %n to i2))) to i64) == 0 ; CHECK-NEXT: Equal predicate: (zext i2 (-2 + (trunc i64 %m to i2)) to i64) == 0 ; entry: @@ -81,8 +78,6 @@ define i32 @gep_i8_vs_i32(ptr nocapture %A, i64 %n, i64 %m) { ; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0 ; CHECK-NEXT: Src: store i32 42, ptr %arrayidx1, align 4 --> Dst: store i32 42, ptr %arrayidx1, align 4 ; CHECK-NEXT: da analyze - none! -; CHECK-NEXT: Runtime Assumptions: -; CHECK-NEXT: Equal predicate: (zext i2 (trunc i64 %n to i2) to i64) == 0 ; entry: %arrayidx0 = getelementptr inbounds i8, ptr %A, i64 %n diff --git a/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll b/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll index b498d70648bad..bb6d2d7c4c8f2 100644 --- a/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll +++ b/llvm/test/Analysis/DependenceAnalysis/MIVCheckConst.ll @@ -50,9 +50,6 @@ define void @test(ptr %A, ptr %B, i1 %arg, i32 %n, i32 %m) #0 align 2 { ; CHECK-NEXT: Equal predicate: (8 * (zext i4 (trunc i32 %v1 to i4) to i32)) == 0 ; CHECK-NEXT: Src: %v32 = load <32 x i32>, ptr %v30, align 128 --> Dst: %v32 = load <32 x i32>, ptr %v30, align 128 ; CHECK-NEXT: da analyze - consistent input [0 S S]! -; CHECK-NEXT: Runtime Assumptions: -; CHECK-NEXT: Equal predicate: (zext i7 (4 * (trunc i32 %v1 to i7) * (1 + (trunc i32 %n to i7))) to i32) == 0 -; CHECK-NEXT: Equal predicate: (8 * (zext i4 (trunc i32 %v1 to i4) to i32)) == 0 ; entry: %v1 = load i32, ptr %B, align 4