From 9bfa9d5d0a2c05665861f808b4b0a11f746101f0 Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Tue, 7 Oct 2025 12:35:16 +0000 Subject: [PATCH 1/6] [DA] Add initial support for monotonicity check --- llvm/lib/Analysis/DependenceAnalysis.cpp | 276 ++++++++++- .../DependenceAnalysis/monotonicity-cast.ll | 174 +++++++ .../monotonicity-invariant.ll | 150 ++++++ .../monotonicity-no-wrap-flags.ll | 459 ++++++++++++++++++ 4 files changed, 1056 insertions(+), 3 deletions(-) create mode 100644 llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll create mode 100644 llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll create mode 100644 llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp index 1f0da8d1830d3..a3134f8571481 100644 --- a/llvm/lib/Analysis/DependenceAnalysis.cpp +++ b/llvm/lib/Analysis/DependenceAnalysis.cpp @@ -128,6 +128,18 @@ static cl::opt RunSIVRoutinesOnly( "The purpose is mainly to exclude the influence of those routines " "in regression tests for SIV routines.")); +// TODO: This flag is disabled by default because it is still under development. +// Enable it or delete this flag when the feature is ready. +static cl::opt EnableMonotonicityCheck( + "da-enable-monotonicity-check", cl::init(false), cl::Hidden, + cl::desc("Check if the subscripts are monotonic. If it's not, dependence " + "is reported as unknown.")); + +static cl::opt DumpMonotonicityReport( + "da-dump-monotonicity-report", cl::init(false), cl::Hidden, + cl::desc( + "When printing analysis, dump the results of monotonicity checks.")); + //===----------------------------------------------------------------------===// // basics @@ -177,13 +189,189 @@ void DependenceAnalysisWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.addRequiredTransitive(); } +namespace { + +/// The type of monotonicity of a SCEV. This property is defined with respect to +/// the outermost loop that DA is analyzing. +/// +/// This is designed to classify the behavior of AddRec expressions, and does +/// not care about other SCEVs. For example, given the two loop-invariant values +/// `A` and `B`, `A + B` is treated as Invariant even if the addition wraps. +enum class SCEVMonotonicityType { + /// The expression is neither loop-invariant nor monotonic (or we fail to + /// prove it). + Unknown, + + /// The expression is loop-invariant with respect to the outermost loop. + Invariant, + + /// The expression is a (nested) affine AddRec and is monotonically increasing + /// or decreasing in a signed sense with respect to each loop. Monotonicity is + /// checked independently for each loop, and the expression is classified as + /// MultiSignedMonotonic if all AddRecs are nsw. For example, in the following + /// loop: + /// + /// for (i = 0; i < 100; i++) + /// for (j = 0; j < 100; j++) + /// A[i + j] = ...; + /// + /// The SCEV for `i + j` is classified as MultiSignedMonotonic. On the other + /// hand, in the following loop: + /// + /// for (i = 0; i < 100; i++) + /// for (j = 0; j <= (1ULL << 63); j++) + /// A[i + j] = ...; + /// + /// The SCEV for `i + j` is NOT classified as MultiMonotonic, because the + /// AddRec for `j` wraps in a signed sense. We don't consider the "direction" + /// of each AddRec. For example, in the following loop: + /// + /// for (int i = 0; i < 100; i++) + /// for (int j = 0; j < 100; j++) + /// A[i - j] = ...; + /// + /// The SCEV for `i - j` is classified as MultiSignedMonotonic, even though it + /// contains both increasing and decreasing AddRecs. + /// + /// Note that we don't check if the step recurrence can be zero. For + /// example,an AddRec `{0,+,%a} is classifed as Monotonic if `%a` can be + /// zero. That is, the expression can be Invariant. + MultiSignedMonotonic, +}; + +struct SCEVMonotonicity { + SCEVMonotonicity(SCEVMonotonicityType Type, + const SCEV *FailurePoint = nullptr); + + SCEVMonotonicityType getType() const { return Type; } + + const SCEV *getFailurePoint() const { return FailurePoint; } + + bool isUnknown() const { return Type == SCEVMonotonicityType::Unknown; } + + void print(raw_ostream &OS, unsigned Depth) const; + +private: + SCEVMonotonicityType Type; + + /// The subexpression that caused Unknown. Mainly for debugging purpose. + const SCEV *FailurePoint; +}; + +struct SCEVMonotonicityChecker + : public SCEVVisitor { + + SCEVMonotonicityChecker(ScalarEvolution *SE) : SE(SE) {} + + /// Check the monotonicity of \p Expr. \p Expr must be integer type. If \p + /// OutermostLoop is not null, \p Expr must be defined in \p OutermostLoop or + /// one of its nested loops. + SCEVMonotonicity checkMonotonicity(const SCEV *Expr, + const Loop *OutermostLoop); + +private: + ScalarEvolution *SE; + + /// The outermost loop that DA is analyzing. + const Loop *OutermostLoop; + + /// A helper to classify \p Expr as either Invariant or Unknown. + SCEVMonotonicity invariantOrUnknown(const SCEV *Expr); + + /// Return true if \p Expr is loop-invariant with respect to the outermost + /// loop. + bool isLoopInvariant(const SCEV *Expr) const; + + /// A helper to create an Unknown SCEVMonotonicity. + SCEVMonotonicity createUnknown(const SCEV *FailurePoint) { + return SCEVMonotonicity(SCEVMonotonicityType::Unknown, FailurePoint); + } + + SCEVMonotonicity visitAddRecExpr(const SCEVAddRecExpr *Expr); + + SCEVMonotonicity visitConstant(const SCEVConstant *) { + return SCEVMonotonicity(SCEVMonotonicityType::Invariant); + } + SCEVMonotonicity visitVScale(const SCEVVScale *) { + return SCEVMonotonicity(SCEVMonotonicityType::Invariant); + } + + // TODO: Handle more cases. + SCEVMonotonicity visitZeroExtendExpr(const SCEVZeroExtendExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitSignExtendExpr(const SCEVSignExtendExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitAddExpr(const SCEVAddExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitMulExpr(const SCEVMulExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitPtrToIntExpr(const SCEVPtrToIntExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitTruncateExpr(const SCEVTruncateExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitUDivExpr(const SCEVUDivExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitSMaxExpr(const SCEVSMaxExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitUMaxExpr(const SCEVUMaxExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitSMinExpr(const SCEVSMinExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitUMinExpr(const SCEVUMinExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitSequentialUMinExpr(const SCEVSequentialUMinExpr *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitUnknown(const SCEVUnknown *Expr) { + return invariantOrUnknown(Expr); + } + SCEVMonotonicity visitCouldNotCompute(const SCEVCouldNotCompute *Expr) { + return invariantOrUnknown(Expr); + } + + friend struct SCEVVisitor; +}; + +} // anonymous namespace + // Used to test the dependence analyzer. // Looks through the function, noting instructions that may access memory. // Calls depends() on every possible pair and prints out the result. // Ignores all other instructions. static void dumpExampleDependence(raw_ostream &OS, DependenceInfo *DA, - ScalarEvolution &SE, bool NormalizeResults) { + ScalarEvolution &SE, LoopInfo &LI, + bool NormalizeResults) { auto *F = DA->getFunction(); + + if (DumpMonotonicityReport) { + SCEVMonotonicityChecker Checker(&SE); + OS << "Monotonicity check:\n"; + for (Instruction &Inst : instructions(F)) { + if (!isa(Inst) && !isa(Inst)) + continue; + Value *Ptr = getLoadStorePointerOperand(&Inst); + const Loop *L = LI.getLoopFor(Inst.getParent()); + const SCEV *PtrSCEV = SE.getSCEVAtScope(Ptr, L); + const SCEV *AccessFn = SE.removePointerBase(PtrSCEV); + SCEVMonotonicity Mon = Checker.checkMonotonicity(AccessFn, L); + OS.indent(2) << "Inst: " << Inst << "\n"; + OS.indent(4) << "Expr: " << *AccessFn << "\n"; + Mon.print(OS, 4); + } + OS << "\n"; + } + for (inst_iterator SrcI = inst_begin(F), SrcE = inst_end(F); SrcI != SrcE; ++SrcI) { if (SrcI->mayReadOrWriteMemory()) { @@ -235,7 +423,8 @@ static void dumpExampleDependence(raw_ostream &OS, DependenceInfo *DA, void DependenceAnalysisWrapperPass::print(raw_ostream &OS, const Module *) const { dumpExampleDependence( - OS, info.get(), getAnalysis().getSE(), false); + OS, info.get(), getAnalysis().getSE(), + getAnalysis().getLoopInfo(), false); } PreservedAnalyses @@ -244,7 +433,7 @@ DependenceAnalysisPrinterPass::run(Function &F, FunctionAnalysisManager &FAM) { << "':\n"; dumpExampleDependence(OS, &FAM.getResult(F), FAM.getResult(F), - NormalizeResults); + FAM.getResult(F), NormalizeResults); return PreservedAnalyses::all(); } @@ -670,6 +859,70 @@ bool DependenceInfo::intersectConstraints(Constraint *X, const Constraint *Y) { return false; } +//===----------------------------------------------------------------------===// +// SCEVMonotonicity + +SCEVMonotonicity::SCEVMonotonicity(SCEVMonotonicityType Type, + const SCEV *FailurePoint) + : Type(Type), FailurePoint(FailurePoint) { + assert( + ((Type == SCEVMonotonicityType::Unknown) == (FailurePoint != nullptr)) && + "FailurePoint must be provided iff Type is Unknown"); +} + +void SCEVMonotonicity::print(raw_ostream &OS, unsigned Depth) const { + OS.indent(Depth) << "Monotonicity: "; + switch (Type) { + case SCEVMonotonicityType::Unknown: + assert(FailurePoint && "FailurePoint must be provided for Unknown"); + OS << "Unknown\n"; + OS.indent(Depth) << "Reason: " << *FailurePoint << "\n"; + break; + case SCEVMonotonicityType::Invariant: + OS << "Invariant\n"; + break; + case SCEVMonotonicityType::MultiSignedMonotonic: + OS << "MultiSignedMonotonic\n"; + break; + } +} + +bool SCEVMonotonicityChecker::isLoopInvariant(const SCEV *Expr) const { + return !OutermostLoop || SE->isLoopInvariant(Expr, OutermostLoop); +} + +SCEVMonotonicity SCEVMonotonicityChecker::invariantOrUnknown(const SCEV *Expr) { + if (isLoopInvariant(Expr)) + return SCEVMonotonicity(SCEVMonotonicityType::Invariant); + return createUnknown(Expr); +} + +SCEVMonotonicity +SCEVMonotonicityChecker::checkMonotonicity(const SCEV *Expr, + const Loop *OutermostLoop) { + assert(Expr->getType()->isIntegerTy() && "Expr must be integer type"); + this->OutermostLoop = OutermostLoop; + return visit(Expr); +} + +SCEVMonotonicity +SCEVMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) { + if (!Expr->isAffine() || !Expr->hasNoSignedWrap()) + return createUnknown(Expr); + + const SCEV *Start = Expr->getStart(); + const SCEV *Step = Expr->getStepRecurrence(*SE); + + SCEVMonotonicity StartMon = visit(Start); + if (StartMon.isUnknown()) + return StartMon; + + if (!isLoopInvariant(Step)) + return createUnknown(Expr); + + return SCEVMonotonicity(SCEVMonotonicityType::MultiSignedMonotonic); +} + //===----------------------------------------------------------------------===// // DependenceInfo methods @@ -3479,10 +3732,19 @@ bool DependenceInfo::tryDelinearize(Instruction *Src, Instruction *Dst, // resize Pair to contain as many pairs of subscripts as the delinearization // has found, and then initialize the pairs following the delinearization. Pair.resize(Size); + SCEVMonotonicityChecker MonChecker(SE); + const Loop *OutermostLoop = SrcLoop ? SrcLoop->getOutermostLoop() : nullptr; for (int I = 0; I < Size; ++I) { Pair[I].Src = SrcSubscripts[I]; Pair[I].Dst = DstSubscripts[I]; unifySubscriptType(&Pair[I]); + + if (EnableMonotonicityCheck) { + if (MonChecker.checkMonotonicity(Pair[I].Src, OutermostLoop).isUnknown()) + return false; + if (MonChecker.checkMonotonicity(Pair[I].Dst, OutermostLoop).isUnknown()) + return false; + } } return true; @@ -3815,6 +4077,14 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst, Pair[0].Src = SrcEv; Pair[0].Dst = DstEv; + SCEVMonotonicityChecker MonChecker(SE); + const Loop *OutermostLoop = SrcLoop ? SrcLoop->getOutermostLoop() : nullptr; + if (EnableMonotonicityCheck) + if (MonChecker.checkMonotonicity(Pair[0].Src, OutermostLoop).isUnknown() || + MonChecker.checkMonotonicity(Pair[0].Dst, OutermostLoop).isUnknown()) + return std::make_unique(Src, Dst, + SCEVUnionPredicate(Assume, *SE)); + if (Delinearize) { if (tryDelinearize(Src, Dst, Pair)) { LLVM_DEBUG(dbgs() << " delinearized\n"); diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll new file mode 100644 index 0000000000000..7a72755bcaf2f --- /dev/null +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll @@ -0,0 +1,174 @@ +; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -disable-output -passes="print" -da-dump-monotonicity-report \ +; RUN: -da-enable-monotonicity-check 2>&1 | FileCheck %s + +; int8_t offset = start; +; for (int i = 0; i < 100; i++, offset += step) +; a[sext(offset)] = 0; +; +define void @sext_nsw(ptr %a, i8 %start, i8 %step) { +; CHECK-LABEL: 'sext_nsw' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {(sext i8 %start to i64),+,(sext i8 %step to i64)}<%loop> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; 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.inc, %loop ] + %offset = phi i8 [ %start, %entry ], [ %offset.next, %loop ] + %offset.sext = sext i8 %offset to i64 + %idx = getelementptr i8, ptr %a, i64 %offset.sext + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %offset.next = add nsw i8 %offset, %step + %exitcond = icmp eq i64 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; The addition for `%offset.next` can wrap, so we cannot prove monotonicity. +; +; int8_t offset = start; +; for (int i = 0; i < 100; i++, offset += step) +; a[sext(offset)] = 0; +; +define void @sext_may_wrap(ptr %a, i8 %start, i8 %step) { +; CHECK-LABEL: 'sext_may_wrap' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: (sext i8 {%start,+,%step}<%loop> to i64) +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: (sext i8 {%start,+,%step}<%loop> to i64) +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %offset = phi i8 [ %start, %entry ], [ %offset.next, %loop ] + %offset.sext = sext i8 %offset to i64 + %idx = getelementptr i8, ptr %a, i64 %offset.sext + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %offset.next = add i8 %offset, %step + %exitcond = icmp eq i64 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; for (int8_t i = 0; i < 100; i++) +; a[zext(offset)] = 0; +; +define void @zext_pos(ptr %a) { +; CHECK-LABEL: 'zext_pos' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {0,+,1}<%loop> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; 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 i8 [ 0, %entry ], [ %i.inc, %loop ] + %offset.zext = zext nneg i8 %i to i64 + %idx = getelementptr i8, ptr %a, i64 %offset.zext + store i8 0, ptr %idx + %i.inc = add nsw i8 %i, 1 + %exitcond = icmp eq i8 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; The zero-extened value of `offset` is no longer monotonic. In fact, the +; values of `offset` in each iteration are: +; +; iteration | 0 | 1 | 2 | ... +; -------------|-----|---|---|--------- +; offset | -1 | 0 | 1 | ... +; zext(offset) | 255 | 0 | 1 | ... +; +; +; for (int8_t i = -1; i < 100; i++) +; a[zext(offset)] = 0; +; +define void @zext_cross_zero(ptr %a) { +; CHECK-LABEL: 'zext_cross_zero' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: (zext i8 {-1,+,1}<%loop> to i64) +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: (zext i8 {-1,+,1}<%loop> to i64) +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop + +loop: + %i = phi i8 [ -1, %entry ], [ %i.inc, %loop ] + %offset.zext = zext nneg i8 %i to i64 + %idx = getelementptr i8, ptr %a, i64 %offset.zext + store i8 0, ptr %idx + %i.inc = add nsw i8 %i, 1 + %exitcond = icmp eq i8 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; In principle, we can prove that `zext(offset)` is monotonic since we know +; that `offset` is non-negative. +; +; int8_t offset = 0; +; for (int i = 0; i < 100; i++, offset += step) +; a[zext(offset)] = 0; +; +define void @zext_nneg_nsw(ptr %a, i8 %step) { +; CHECK-LABEL: 'zext_nneg_nsw' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: (zext i8 {0,+,%step}<%loop> to i64) +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: (zext i8 {0,+,%step}<%loop> to i64) +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %offset = phi i8 [ 0, %entry ], [ %offset.next, %loop ] + %offset.zext = zext nneg i8 %offset to i64 + %idx = getelementptr i8, ptr %a, i64 %offset.zext + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %offset.next = add nsw i8 %offset, %step + %exitcond = icmp eq i64 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll new file mode 100644 index 0000000000000..8f45dfa3af5dd --- /dev/null +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll @@ -0,0 +1,150 @@ +; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -disable-output -passes="print" -da-dump-monotonicity-report \ +; RUN: -da-enable-monotonicity-check 2>&1 | FileCheck %s + +; for (int i = 0; i < n; i++) +; a[x] = 0; +define void @single_loop_invariant(ptr %a, i64 %x, i64 %n) { +; CHECK-LABEL: 'single_loop_invariant' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: %x +; CHECK-NEXT: Monotonicity: Invariant +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - consistent output [S]! +; +entry: + %guard = icmp sgt i64 %n, 0 + br i1 %guard, label %loop, label %exit + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %idx = getelementptr inbounds i8, ptr %a, i64 %x + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %exitcond = icmp eq i64 %i.inc, %n + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; for (int i = 0; i < n; i++) +; a[(i % 2 == 0 ? x : y)] = 0; +define void @single_loop_variant(ptr %a, i64 %x, i64 %y, i64 %n) { +; CHECK-LABEL: 'single_loop_variant' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: %offset +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: %offset +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + %guard = icmp sgt i64 %n, 0 + br i1 %guard, label %loop, label %exit + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %offset = phi i64 [ %x, %entry ], [ %offset.next, %loop ] + %offset.next = phi i64 [ %y, %entry ], [ %offset, %loop ] + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %exitcond = icmp eq i64 %i.inc, %n + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; a[x + i] = 0; +define void @invariant_plus_monotonic0(ptr %a, i64 %x, i64 %n, i64 %m) { +; CHECK-LABEL: 'invariant_plus_monotonic0' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {%x,+,1}<%loop.i.header> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - consistent output [0 S]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + %offset = phi i64 [ %x, %entry ], [ %offset.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nuw nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %offset.inc = add nsw i64 %offset, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; a[x + j] = 0; +define void @invariant_plus_monotonic1(ptr %a, i64 %x, i64 %n, i64 %m) { +; CHECK-LABEL: 'invariant_plus_monotonic1' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {%x,+,1}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - consistent output [S 0]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset = phi i64 [ %x, %loop.j.preheader ], [ %offset.inc, %loop.j ] + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nuw nsw i64 %j, 1 + %offset.inc = add nsw i64 %offset, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll new file mode 100644 index 0000000000000..83ea15bf76682 --- /dev/null +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll @@ -0,0 +1,459 @@ +; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -disable-output -passes="print" -da-dump-monotonicity-report \ +; RUN: -da-enable-monotonicity-check 2>&1 | FileCheck %s + +; for (int i = 0; i < n; i++) +; a[i] = 0; +; +define void @single_loop_nsw(ptr %a, i64 %n) { +; CHECK-LABEL: 'single_loop_nsw' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {0,+,1}<%loop> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - none! +; +entry: + %guard = icmp sgt i64 %n, 0 + br i1 %guard, label %loop, label %exit + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %idx = getelementptr inbounds i8, ptr %a, i64 %i + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %exitcond = icmp eq i64 %i.inc, %n + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; The purpose of the variable `begin` is to avoid violating the size limitation +; of the allocated object in LLVM IR, which would cause UB. +; +; for (unsigned long long i = begin; i < end; i++) +; a[i] = 0; +; +define void @single_loop_nuw(ptr %a, i64 %begin, i64 %end) { +; CHECK-LABEL: 'single_loop_nuw' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {%begin,+,1}<%loop> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {%begin,+,1}<%loop> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + %guard = icmp ult i64 %begin, %end + br i1 %guard, label %loop, label %exit + +loop: + %i = phi i64 [ %begin, %entry ], [ %i.inc, %loop ] + %idx = getelementptr i8, ptr %a, i64 %i + store i8 0, ptr %idx + %i.inc = add nuw i64 %i, 1 + %exitcond = icmp eq i64 %i.inc, %end + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} + +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; a[i + j] = 0; +; +define void @nested_loop_nsw0(ptr %a, i64 %n, i64 %m) { +; CHECK-LABEL: 'nested_loop_nsw0' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - output [* *]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset = add nsw i64 %i, %j + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; for (int i = n - 1; i >= 0; i--) +; for (int j = 0; j < m; j++) +; a[i + j] = 0; +; +define void @nested_loop_nsw1(ptr %a, i64 %n, i64 %m) { +; CHECK-LABEL: 'nested_loop_nsw1' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}(-1 + %n),+,-1}<%loop.i.header>,+,1}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - output [* *]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ %n, %entry ], [ %i.dec, %loop.i.latch ] + %i.dec = add nsw i64 %i, -1 + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset = add nsw i64 %i.dec, %j + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %exitcond.i = icmp eq i64 %i.dec, 0 + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; for (int i = 0; i < n; i--) +; for (int j = 0; j < m; j++) +; a[i - j] = 0; +; +define void @nested_loop_nsw2(ptr %a, i64 %n, i64 %m) { +; CHECK-LABEL: 'nested_loop_nsw2' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,-1}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - output [* *]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset = sub nsw i64 %i, %j + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; for (int i = begin0; i < end0; i++) +; for (int j = begin1; j < end1; j++) { +; unsigned long long offset = (unsigned long long)i + (unsigned long long)j; +; a[offset] = 0; +; } +; +define void @nested_loop_nuw(ptr %a, i64 %begin0, i64 %end0, i64 %begin1, i64 %end1) { +; CHECK-LABEL: 'nested_loop_nuw' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}(%begin0 + %begin1),+,1}<%loop.i.header>,+,1}<%loop.j> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {{\{\{}}(%begin0 + %begin1),+,1}<%loop.i.header>,+,1}<%loop.j> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + %guard.i.0 = icmp slt i64 0, %begin0 + %guard.i.1 = icmp slt i64 %begin0, %end0 + %guard.i.2 = icmp slt i64 0, %end0 + %and.i.0 = and i1 %guard.i.0, %guard.i.1 + %and.i.1 = and i1 %and.i.0, %guard.i.2 + br i1 %and.i.1, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ %begin0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %guard.j.0 = icmp slt i64 0, %begin1 + %guard.j.1 = icmp slt i64 %begin1, %end1 + %guard.j.2 = icmp slt i64 0, %end1 + %and.j.0 = and i1 %guard.j.0, %guard.j.1 + %and.j.1 = and i1 %and.j.0, %guard.j.2 + br i1 %and.j.1, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ %begin1, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset = add nuw i64 %i, %j + %idx = getelementptr i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %end1 + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %end0 + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; a[i + step*j] = 0; +; +define void @nested_loop_step(ptr %a, i64 %n, i64 %m, i64 %step) { +; CHECK-LABEL: 'nested_loop_step' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,%step}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - output [* *]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j, label %loop.i.latch + +loop.j: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j ] + %offset.j = phi i64 [ 0, %loop.j.preheader ], [ %offset.j.next, %loop.j ] + %offset = add nsw i64 %i, %offset.j + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %offset.j.next = add nsw i64 %offset.j, %step + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; The value of step reccurence is not invariant with respect to the outer most +; loop (the i-loop). +; +; offset_i = 0; +; for (int i = 0; i < 100; i++) { +; for (int j = 0; j < 100; j++) +; a[offset_i + j] = 0; +; offset_i += (i % 2 == 0) ? 0 : 3; +; } +; +define void @step_is_variant(ptr %a) { +; CHECK-LABEL: 'step_is_variant' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {%offset.i,+,1}<%loop.j> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop.i.header + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + %offset.i = phi i64 [ 0, %entry ], [ %offset.i.next, %loop.i.latch ] + %step.i.0 = phi i64 [ 0, %entry ], [ %step.i.1, %loop.i.latch ] + %step.i.1 = phi i64 [ 3, %entry ], [ %step.i.0, %loop.i.latch ] + br label %loop.j + +loop.j: + %j = phi i64 [ 0, %loop.i.header ], [ %j.inc, %loop.j ] + %offset = add nsw i64 %offset.i, %j + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, 100 + br i1 %exitcond.j, label %loop.i.latch, label %loop.j + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %offset.i.next = add nsw i64 %offset.i, %step.i.0 + %exitcond.i = icmp eq i64 %i.inc, 100 + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; The AddRec doesn't have nsw flag for the j-loop, since the store may not be +; executed. +; +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; if (cond) +; a[i + j] = 0; +; +define void @conditional_store0(ptr %a, i64 %n, i64 %m) { +; CHECK-LABEL: 'conditional_store0' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j.header, label %loop.i.latch + +loop.j.header: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j.latch ] + %offset = add nsw i64 %i, %j + %cond = freeze i1 poison + br i1 %cond, label %if.then, label %loop.j.latch + +if.then: + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + br label %loop.j.latch + +loop.j.latch: + %j.inc = add nsw i64 %j, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j.header + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} + +; Similar to the @conditional_store0, but the definition of the `%offset` is +; different from it and we can infer `nsw` in this case. +; +; for (int i = 0; i < n; i++) +; for (int j = 0; j < m; j++) +; if (cond) +; a[i + j] = 0; +; +define void @conditional_store1(ptr %a, i64 %n, i64 %m) { +; CHECK-LABEL: 'conditional_store1' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - output [* *]! +; +entry: + %guard.i = icmp sgt i64 %n, 0 + br i1 %guard.i, label %loop.i.header, label %exit + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.preheader + +loop.j.preheader: + %gurard.j = icmp sgt i64 %m, 0 + br i1 %gurard.j, label %loop.j.header, label %loop.i.latch + +loop.j.header: + %j = phi i64 [ 0, %loop.j.preheader ], [ %j.inc, %loop.j.latch ] + %offset = phi i64 [ %i, %loop.j.preheader ], [ %offset.next, %loop.j.latch ] + %cond = freeze i1 poison + br i1 %cond, label %if.then, label %loop.j.latch + +if.then: + %idx = getelementptr inbounds i8, ptr %a, i64 %offset + store i8 0, ptr %idx + br label %loop.j.latch + +loop.j.latch: + %j.inc = add nsw i64 %j, 1 + %offset.next = add nsw i64 %offset, 1 + %exitcond.j = icmp eq i64 %j.inc, %m + br i1 %exitcond.j, label %loop.i.latch, label %loop.j.header + +loop.i.latch: + %i.inc = add nsw i64 %i, 1 + %exitcond.i = icmp eq i64 %i.inc, %n + br i1 %exitcond.i, label %exit, label %loop.i.header + +exit: + ret void +} From d088dbe94b2c0e0a71af76f5237009e5ff7b3dbe Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Thu, 9 Oct 2025 11:29:37 +0000 Subject: [PATCH 2/6] add another test --- .../DependenceAnalysis/non-monotonic.ll | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll diff --git a/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll b/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll new file mode 100644 index 0000000000000..04db865f8b1ee --- /dev/null +++ b/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll @@ -0,0 +1,77 @@ +; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -disable-output -passes="print" -da-dump-monotonicity-report \ +; RUN: -da-enable-monotonicity-check 2>&1 | FileCheck %s +; RUN: opt < %s -disable-output -passes="print" 2>&1 | FileCheck %s -check-prefix=DISABLE-CHECK + +; +; for (i = 0; i < (1ULL << 60); i++) { +; A[i] = 1; +; +; unsigned long long offset = i * 32 + (1ULL << 62); +; // offset is positive when interpreted as a signed value. +; // To prevent violating the size limitation for an allocated object. +; if (offset < (1ULL << 63)) +; A[offset] = 2; +; } +; +; ----------------------------------------------------------------------------- +; +; There is a dependency between the two stores. To detect it, we need to check +; the monotonicity and bail out the analysis since `offset` is not monotonic. +; +; memory location | first store (A[i]) | second store (A[offset]) +; ------------------|--------------------|---------------------------- +; A[0] | i = 0 | i = 2^59 - 2^57 +; A[2^60 - 32] | i = 2^60 - 32 | i = 2^59 - 2^57 + 2^55 - 1 +; +define void @f(ptr %A) { +; CHECK-LABEL: 'f' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 1, ptr %idx.0, align 1 +; CHECK-NEXT: Expr: {0,+,1}<%loop.header> +; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Inst: store i8 2, ptr %idx.1, align 1 +; CHECK-NEXT: Expr: {4611686018427387904,+,32}<%loop.header> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {4611686018427387904,+,32}<%loop.header> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 1, ptr %idx.0, align 1 --> Dst: store i8 1, ptr %idx.0, align 1 +; CHECK-NEXT: da analyze - none! +; CHECK-NEXT: Src: store i8 1, ptr %idx.0, align 1 --> Dst: store i8 2, ptr %idx.1, align 1 +; CHECK-NEXT: da analyze - confused! +; CHECK-NEXT: Src: store i8 2, ptr %idx.1, align 1 --> Dst: store i8 2, ptr %idx.1, align 1 +; CHECK-NEXT: da analyze - confused! +; +; DISABLE-CHECK-LABEL: 'f' +; DISABLE-CHECK-NEXT: Src: store i8 1, ptr %idx.0, align 1 --> Dst: store i8 1, ptr %idx.0, align 1 +; DISABLE-CHECK-NEXT: da analyze - none! +; DISABLE-CHECK-NEXT: Src: store i8 1, ptr %idx.0, align 1 --> Dst: store i8 2, ptr %idx.1, align 1 +; DISABLE-CHECK-NEXT: da analyze - none! +; DISABLE-CHECK-NEXT: Src: store i8 2, ptr %idx.1, align 1 --> Dst: store i8 2, ptr %idx.1, align 1 +; DISABLE-CHECK-NEXT: da analyze - none! +; +entry: + br label %loop.header + +loop.header: + %i = phi i64 [ 0, %entry ], [ %i.next, %loop.latch ] + %idx.0 = getelementptr inbounds i8, ptr %A, i64 %i + store i8 1, ptr %idx.0 + %offset.tmp = mul i64 %i, 32 + %offset = add i64 %offset.tmp, 4611686018427387904 ; 1ULL << 62 + %if.cond = icmp sge i64 %offset, 0 + br i1 %if.cond, label %if.then, label %loop.latch + +if.then: + %idx.1 = getelementptr inbounds i8, ptr %A, i64 %offset + store i8 2, ptr %idx.1 + br label %loop.latch + +loop.latch: + %i.next = add nuw nsw i64 %i, 1 + %exit.cond = icmp eq i64 %i.next, 1152921504606846976 ; 1ULL << 60 + br i1 %exit.cond, label %exit, label %loop.header + +exit: + ret void +} From 9c29421211506d728c67e43838cccbd741885cf3 Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Tue, 14 Oct 2025 10:21:38 +0000 Subject: [PATCH 3/6] add outer_loop_may_wrap --- .../monotonicity-no-wrap-flags.ll | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll index 83ea15bf76682..439224efd4e8e 100644 --- a/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll @@ -457,3 +457,63 @@ loop.i.latch: exit: ret void } + +; In the following case, the computation `offset = offset_i + j` will not wrap, +; but `offset_i += 1024` will wrap both in a signed sense and an unsigned +; sense. We cannot prove the monotonicity in this case. +; +; offset_i = (1ULL << 63) - 256; +; for (i = 0; i < (1ULL << 62); i++, offset_i += 1024) +; for (j = 0; j < 32; j++) { +; offset = offset_i + j; +; +; // The value of `offset` is positive in a signed sense. +; if (offset < (1ULL << 63)) +; a[offset] = 0; +; } +; +define void @outer_loop_may_wrap(ptr %a) { +; CHECK-LABEL: 'outer_loop_may_wrap' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %gep, align 1 +; CHECK-NEXT: Expr: {{\{\{}}9223372036854775552,+,1024}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {9223372036854775552,+,1024}<%loop.i.header> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %gep, align 1 --> Dst: store i8 0, ptr %gep, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop.i.header + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + %subscript.i = phi i64 [ 9223372036854775552, %entry ], [ %subscript.i.next, %loop.i.latch ] ; The initial value is 2^63 - 256 + br label %loop.j.header + +loop.j.header: + %j = phi i64 [ 0, %loop.i.header ], [ %j.inc, %loop.j.latch ] + %subscript = phi i64 [ %subscript.i, %loop.i.header ], [ %subscript.next, %loop.j.latch ] + %cond = icmp sge i64 %subscript, 0 + br i1 %cond, label %if.then, label %loop.j.latch + +if.then: + %gep = getelementptr inbounds i8, ptr %a, i64 %subscript + store i8 0, ptr %gep + br label %loop.j.latch + +loop.j.latch: + %j.inc = add nuw nsw i64 %j, 1 + %subscript.next = add nuw nsw i64 %subscript, 1 + %ec.j = icmp eq i64 %j.inc, 32 + br i1 %ec.j, label %loop.i.latch, label %loop.j.header + +loop.i.latch: + %i.inc = add nuw nsw i64 %i, 1 + %subscript.i.next = add i64 %subscript.i, 1024 + %ec.i = icmp eq i64 %i.inc, 4611686018427387904 ; 2^62 + br i1 %ec.i, label %exit, label %loop.i.header + +exit: + ret void +} From da8859cb562b4887f1c5d28cfc2b7b3dc8d7062f Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Wed, 15 Oct 2025 13:23:49 +0000 Subject: [PATCH 4/6] add more tests --- .../DependenceAnalysis/monotonicity-cast.ll | 33 +++++++++++ .../monotonicity-delinearize.ll | 59 +++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 llvm/test/Analysis/DependenceAnalysis/monotonicity-delinearize.ll diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll index 7a72755bcaf2f..9c784149b33d6 100644 --- a/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll @@ -172,3 +172,36 @@ loop: exit: ret void } + +; SCEV handles `i & 1` as an i1 addrec. Ensure that the monotonicity analysis +; properly analyzes it. +; +; for (i = 0; i < 100; i++) +; a[i & 1] = 0; +; +define void @offset_truncated_to_i1(ptr %a) { +; CHECK-LABEL: 'offset_truncated_to_i1' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: Expr: (zext i1 {false,+,true}<%loop> to i64) +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: (zext i1 {false,+,true}<%loop> to i64) +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop + +loop: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ] + %and = and i64 %i, 1 + %idx = getelementptr inbounds i8, ptr %a, i64 %and + store i8 0, ptr %idx + %i.inc = add nsw i64 %i, 1 + %exitcond = icmp eq i64 %i.inc, 100 + br i1 %exitcond, label %exit, label %loop + +exit: + ret void +} diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-delinearize.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-delinearize.ll new file mode 100644 index 0000000000000..71ea4e95059a0 --- /dev/null +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-delinearize.ll @@ -0,0 +1,59 @@ +; NOTE: Assertions have been autogenerated by utils/update_analyze_test_checks.py UTC_ARGS: --version 6 +; RUN: opt < %s -disable-output -passes="print" -da-dump-monotonicity-report \ +; RUN: -da-enable-monotonicity-check 2>&1 | FileCheck %s + +; The offset SCEV will be delinearized into a 2D array access, like as follows: +; +; - Outer subscript: {0,+,1}<%loop.i.header> +; - Inner subscript: {0,+,1}<%loop.j.header> +; +; These subscripts are both monotonic, but we also need to check the +; monotonicity of the original addrec. +; +; char A[...][32]; +; for (i = 0; i < 1ll << 62; i++) +; for (j = 0; j < 32; j++) +; if (i < (1ll << 57)) +; A[i][j] = 0; +; +define void @linearized_offset_wrap(ptr %a) { +; CHECK-LABEL: 'linearized_offset_wrap' +; CHECK-NEXT: Monotonicity check: +; CHECK-NEXT: Inst: store i8 0, ptr %gep, align 1 +; CHECK-NEXT: Expr: {{\{\{}}0,+,32}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-NEXT: Monotonicity: Unknown +; CHECK-NEXT: Reason: {{\{\{}}0,+,32}<%loop.i.header>,+,1}<%loop.j.header> +; CHECK-EMPTY: +; CHECK-NEXT: Src: store i8 0, ptr %gep, align 1 --> Dst: store i8 0, ptr %gep, align 1 +; CHECK-NEXT: da analyze - confused! +; +entry: + br label %loop.i.header + +loop.i.header: + %i = phi i64 [ 0, %entry ], [ %i.inc, %loop.i.latch ] + br label %loop.j.header + +loop.j.header: + %j = phi i64 [ 0, %loop.i.header ], [ %j.inc, %loop.j.latch ] + %cond = icmp slt i64 %i, 144115188075855872 ; 2^57 + br i1 %cond, label %if.then, label %loop.j.latch + +if.then: + %gep = getelementptr inbounds [32 x i8], ptr %a, i64 %i, i64 %j + store i8 0, ptr %gep + br label %loop.j.latch + +loop.j.latch: + %j.inc = add nuw nsw i64 %j, 1 + %ec.j = icmp eq i64 %j.inc, 32 + br i1 %ec.j, label %loop.i.latch, label %loop.j.header + +loop.i.latch: + %i.inc = add nuw nsw i64 %i, 1 + %ec.i = icmp eq i64 %i.inc, 4611686018427387904 ; 2^62 + br i1 %ec.i, label %exit, label %loop.i.header + +exit: + ret void +} From 83ab4c6dd9c45724989737bcb74da62d6f83ee51 Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Fri, 17 Oct 2025 01:26:31 +0900 Subject: [PATCH 5/6] revise the definition of monotonicity --- llvm/lib/Analysis/DependenceAnalysis.cpp | 91 ++++++++++--------- .../DependenceAnalysis/monotonicity-cast.ll | 4 +- .../monotonicity-invariant.ll | 4 +- .../monotonicity-no-wrap-flags.ll | 14 +-- .../DependenceAnalysis/non-monotonic.ll | 2 +- 5 files changed, 60 insertions(+), 55 deletions(-) diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp index a3134f8571481..638f7eb9b8d35 100644 --- a/llvm/lib/Analysis/DependenceAnalysis.cpp +++ b/llvm/lib/Analysis/DependenceAnalysis.cpp @@ -191,52 +191,55 @@ void DependenceAnalysisWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { namespace { -/// The type of monotonicity of a SCEV. This property is defined with respect to -/// the outermost loop that DA is analyzing. +/// The property of monotonicity of a SCEV. To define the monotonicity, assume +/// a SCEV defined within N-nested loops. Let i_k denote the iteration number +/// of the k-th loop. Then we can regard the SCEV as an N-ary function: /// -/// This is designed to classify the behavior of AddRec expressions, and does -/// not care about other SCEVs. For example, given the two loop-invariant values -/// `A` and `B`, `A + B` is treated as Invariant even if the addition wraps. +/// F(i_1, i_2, ..., i_N) +/// +/// The domain of i_k is the closed range [0, BTC_k], where BTC_k is the +/// backedge-taken count of the k-th loop. +/// +/// A function F is said to be "monotonically increasing with respect to the +/// k-th loop" if x <= y implies the following condition: +/// +/// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) <= +/// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N) +/// +/// where i_1, ..., i_{k-1}, i_{k+1}, ..., i_N, x, y in their domains. +/// +/// Likewise F is "monotonically decreasing with respect to the k-th loop" +/// if x <= y implies +/// +/// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) >= +/// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N) +/// +/// A function F with either monotonically increasing or decreasing with +/// respect to the k-th loop is simply called +/// "monotonic with respect to k-th loop". +/// +/// A function F is said to be "multimonotonic" when it is monotonic with +/// respect to all of the N loops. +/// +/// Since integer comparison can be either signed or unsigned, we need to +/// distinguish monotonicity in the signed sense from that in the unsigned +/// sense. Note that the inequality "x <= y" merely indicates loop progression +/// and is not affected by the difference between signed and unsigned order. +/// +/// Currently we only consider monotonicity in a signed sense. enum class SCEVMonotonicityType { - /// The expression is neither loop-invariant nor monotonic (or we fail to - /// prove it). + /// We don't know anything about the monotonicity of the SCEV. Unknown, - /// The expression is loop-invariant with respect to the outermost loop. + /// The SCEV is loop-invariant with respect to the outermost loop. In other + /// words, the function F corresponding to the SCEV is a constant function. Invariant, - /// The expression is a (nested) affine AddRec and is monotonically increasing - /// or decreasing in a signed sense with respect to each loop. Monotonicity is - /// checked independently for each loop, and the expression is classified as - /// MultiSignedMonotonic if all AddRecs are nsw. For example, in the following - /// loop: - /// - /// for (i = 0; i < 100; i++) - /// for (j = 0; j < 100; j++) - /// A[i + j] = ...; - /// - /// The SCEV for `i + j` is classified as MultiSignedMonotonic. On the other - /// hand, in the following loop: - /// - /// for (i = 0; i < 100; i++) - /// for (j = 0; j <= (1ULL << 63); j++) - /// A[i + j] = ...; - /// - /// The SCEV for `i + j` is NOT classified as MultiMonotonic, because the - /// AddRec for `j` wraps in a signed sense. We don't consider the "direction" - /// of each AddRec. For example, in the following loop: - /// - /// for (int i = 0; i < 100; i++) - /// for (int j = 0; j < 100; j++) - /// A[i - j] = ...; - /// - /// The SCEV for `i - j` is classified as MultiSignedMonotonic, even though it - /// contains both increasing and decreasing AddRecs. - /// - /// Note that we don't check if the step recurrence can be zero. For - /// example,an AddRec `{0,+,%a} is classifed as Monotonic if `%a` can be - /// zero. That is, the expression can be Invariant. - MultiSignedMonotonic, + /// The function F corresponding to the SCEV is multimonotonic in a signed + /// sense. Note that the multimonotonic function may also be a constant + /// function. The order employed in the definition of monotonicity is not + /// strict order. + MultivariateSignedMonotonic, }; struct SCEVMonotonicity { @@ -881,8 +884,8 @@ void SCEVMonotonicity::print(raw_ostream &OS, unsigned Depth) const { case SCEVMonotonicityType::Invariant: OS << "Invariant\n"; break; - case SCEVMonotonicityType::MultiSignedMonotonic: - OS << "MultiSignedMonotonic\n"; + case SCEVMonotonicityType::MultivariateSignedMonotonic: + OS << "MultivariateSignedMonotonic\n"; break; } } @@ -905,6 +908,8 @@ SCEVMonotonicityChecker::checkMonotonicity(const SCEV *Expr, return visit(Expr); } +/// We only care about an affine AddRec at the moment. For an affine AddRec, +/// the monotonicity can be inferred from its nowrap property. SCEVMonotonicity SCEVMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) { if (!Expr->isAffine() || !Expr->hasNoSignedWrap()) @@ -920,7 +925,7 @@ SCEVMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) { if (!isLoopInvariant(Step)) return createUnknown(Expr); - return SCEVMonotonicity(SCEVMonotonicityType::MultiSignedMonotonic); + return SCEVMonotonicity(SCEVMonotonicityType::MultivariateSignedMonotonic); } //===----------------------------------------------------------------------===// diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll index 9c784149b33d6..e43d00d0bf651 100644 --- a/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-cast.ll @@ -11,7 +11,7 @@ define void @sext_nsw(ptr %a, i8 %start, i8 %step) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {(sext i8 %start to i64),+,(sext i8 %step to i64)}<%loop> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - none! @@ -77,7 +77,7 @@ define void @zext_pos(ptr %a) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {0,+,1}<%loop> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - none! diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll index 8f45dfa3af5dd..e5b6ddbaca6fe 100644 --- a/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-invariant.ll @@ -69,7 +69,7 @@ define void @invariant_plus_monotonic0(ptr %a, i64 %x, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {%x,+,1}<%loop.i.header> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - consistent output [0 S]! @@ -113,7 +113,7 @@ define void @invariant_plus_monotonic1(ptr %a, i64 %x, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {%x,+,1}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - consistent output [S 0]! diff --git a/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll index 439224efd4e8e..7411dc9f5c053 100644 --- a/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll +++ b/llvm/test/Analysis/DependenceAnalysis/monotonicity-no-wrap-flags.ll @@ -10,7 +10,7 @@ define void @single_loop_nsw(ptr %a, i64 %n) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {0,+,1}<%loop> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - none! @@ -73,7 +73,7 @@ define void @nested_loop_nsw0(ptr %a, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - output [* *]! @@ -117,7 +117,7 @@ define void @nested_loop_nsw1(ptr %a, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {{\{\{}}(-1 + %n),+,-1}<%loop.i.header>,+,1}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - output [* *]! @@ -161,7 +161,7 @@ define void @nested_loop_nsw2(ptr %a, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,-1}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - output [* *]! @@ -260,7 +260,7 @@ define void @nested_loop_step(ptr %a, i64 %n, i64 %m, i64 %step) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,%step}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - output [* *]! @@ -312,7 +312,7 @@ define void @step_is_variant(ptr %a) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {%offset.i,+,1}<%loop.j> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - confused! @@ -415,7 +415,7 @@ define void @conditional_store1(ptr %a, i64 %n, i64 %m) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: Expr: {{\{\{}}0,+,1}<%loop.i.header>,+,1}<%loop.j.header> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-EMPTY: ; CHECK-NEXT: Src: store i8 0, ptr %idx, align 1 --> Dst: store i8 0, ptr %idx, align 1 ; CHECK-NEXT: da analyze - output [* *]! diff --git a/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll b/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll index 04db865f8b1ee..6247336456d2c 100644 --- a/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll +++ b/llvm/test/Analysis/DependenceAnalysis/non-monotonic.ll @@ -29,7 +29,7 @@ define void @f(ptr %A) { ; CHECK-NEXT: Monotonicity check: ; CHECK-NEXT: Inst: store i8 1, ptr %idx.0, align 1 ; CHECK-NEXT: Expr: {0,+,1}<%loop.header> -; CHECK-NEXT: Monotonicity: MultiSignedMonotonic +; CHECK-NEXT: Monotonicity: MultivariateSignedMonotonic ; CHECK-NEXT: Inst: store i8 2, ptr %idx.1, align 1 ; CHECK-NEXT: Expr: {4611686018427387904,+,32}<%loop.header> ; CHECK-NEXT: Monotonicity: Unknown From bb17ccd03f929b11040dcfacc2b2671a87804721 Mon Sep 17 00:00:00 2001 From: Ryotaro Kasuga Date: Sat, 18 Oct 2025 01:46:18 +0900 Subject: [PATCH 6/6] address review comments --- llvm/lib/Analysis/DependenceAnalysis.cpp | 35 ++++++++++++++++-------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/llvm/lib/Analysis/DependenceAnalysis.cpp b/llvm/lib/Analysis/DependenceAnalysis.cpp index 344e99f3b6561..0a8c2f82e162f 100644 --- a/llvm/lib/Analysis/DependenceAnalysis.cpp +++ b/llvm/lib/Analysis/DependenceAnalysis.cpp @@ -206,7 +206,8 @@ namespace { /// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) <= /// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N) /// -/// where i_1, ..., i_{k-1}, i_{k+1}, ..., i_N, x, y in their domains. +/// where i_1, ..., i_{k-1}, i_{k+1}, ..., i_N, x, and y are elements of their +/// respective domains. /// /// Likewise F is "monotonically decreasing with respect to the k-th loop" /// if x <= y implies @@ -214,12 +215,11 @@ namespace { /// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) >= /// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N) /// -/// A function F with either monotonically increasing or decreasing with -/// respect to the k-th loop is simply called -/// "monotonic with respect to k-th loop". +/// A function F that is monotonically increasing or decreasing with respect to +/// the k-th loop is simply called "monotonic with respect to k-th loop". /// -/// A function F is said to be "multimonotonic" when it is monotonic with -/// respect to all of the N loops. +/// A function F is said to be "multivariate monotonic" when it is monotonic +/// with respect to all of the N loops. /// /// Since integer comparison can be either signed or unsigned, we need to /// distinguish monotonicity in the signed sense from that in the unsigned @@ -235,10 +235,10 @@ enum class SCEVMonotonicityType { /// words, the function F corresponding to the SCEV is a constant function. Invariant, - /// The function F corresponding to the SCEV is multimonotonic in a signed - /// sense. Note that the multimonotonic function may also be a constant - /// function. The order employed in the definition of monotonicity is not - /// strict order. + /// The function F corresponding to the SCEV is multivariate monotonic in a + /// signed sense. Note that the multivariate monotonic function may also be a + /// constant function. The order employed in the definition of monotonicity + /// is not strict order. MultivariateSignedMonotonic, }; @@ -261,6 +261,10 @@ struct SCEVMonotonicity { const SCEV *FailurePoint; }; +/// Check the monotonicity of a SCEV. Since dependence tests (SIV, MIV, etc.) +/// assume that subscript expressions are (multivariate) monotonic, we need to +/// verify this property before applying those tests. Violating this assumption +/// may cause them to produce incorrect results. struct SCEVMonotonicityChecker : public SCEVVisitor { @@ -909,7 +913,16 @@ SCEVMonotonicityChecker::checkMonotonicity(const SCEV *Expr, } /// We only care about an affine AddRec at the moment. For an affine AddRec, -/// the monotonicity can be inferred from its nowrap property. +/// the monotonicity can be inferred from its nowrap property. For example, let +/// X and Y be loop-invariant, and assume Y is non-negative. An AddRec +/// {X,+.Y} implies: +/// +/// X <=s (X + Y) <=s ((X + Y) + Y) <=s ... +/// +/// Thus, we can conclude that the AddRec is monotonically increasing with +/// respect to the associated loop in a signed sense. The similar reasoning +/// applies when Y is non-positive, leading to a monotonically decreasing +/// AddRec. SCEVMonotonicity SCEVMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) { if (!Expr->isAffine() || !Expr->hasNoSignedWrap())