Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 298 additions & 0 deletions llvm/lib/Analysis/DependenceAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3308,6 +3308,242 @@ void DependenceInfo::updateDirection(Dependence::DVEntry &Level,
llvm_unreachable("constraint has unexpected kind");
}

namespace {

/// The type of signed monotonicity of a SCEV expression. This property is
/// defined with respect to the outermost loop that DA is analyzing. Invariant
/// and MultiMonotonic mutually exclusive, and both imply NoSignedWrap.
///
/// This is designed to classify the behavior of AddRec expressions, and does
/// not care about other SCEVs. For example, given the two loop invariants `A`
/// and `B`, `A + B` is treated as Invariant even if the addition may wrap. On
/// the other hand, if either `A` or `B` is an AddRec and we cannot prove the
/// addition doesn't wrap, the result is classified as Unknown.
enum class MonotonicityType {
Unknown, ///< The expression contains some non loop-invariant SCEVUnknown or
///< arithmetic operation that has some AddRec as its subexpression
///< and may cause signed wrap.
NoSignedWrap, ///< The expression doesn't contain any AddRecs that may wrap.
///< This is a weaker property than Invariant or MultiMonotonic.
///< Invariant and MultiMonotonic imply NoSignedWrap.
Invariant, ///< The expression is a loop-invariant.
MultiMonotonic, ///< The expression is monotonically increasing or decreasing
///< with respect to each loop. This is exclusive of
///< Invariant. That is, we say an SCEV is MultiMonotonic only
///< if it contains at least one AddRec where its step
///< reccurence value is non-zero. Monotonicity is checked
///< independently for each loop. It is allowed to contain
///< both increasing and decreasing AddRecs.
};

/// A visitor that checks the signed monotonicity of SCEVs.
struct SCEVSignedMonotonicityChecker
: public SCEVVisitor<SCEVSignedMonotonicityChecker, MonotonicityType> {

/// \p Ptr is the pointer that the SCEV is associated with, if any. It may be
/// used for the inferrence.
static MonotonicityType checkMonotonicity(ScalarEvolution *SE,
const SCEV *Expr,
const Loop *OutermostLoop,
const Value *Ptr = nullptr);

MonotonicityType visitAddRecExpr(const SCEVAddRecExpr *Expr);
MonotonicityType visitUnknown(const SCEVUnknown *Expr);
MonotonicityType visitZeroExtendExpr(const SCEVZeroExtendExpr *Expr);
MonotonicityType visitSignExtendExpr(const SCEVSignExtendExpr *Expr);

MonotonicityType visitConstant(const SCEVConstant *) {
return MonotonicityType::Invariant;
}
MonotonicityType visitVScale(const SCEVVScale *) {
return MonotonicityType::Invariant;
}

// TODO: Handle more cases.
MonotonicityType visitAddExpr(const SCEVAddExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitMulExpr(const SCEVMulExpr *Expr) {
return checkInvarianceOnly(Expr);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed support for SCEVAddExpr and SCEVMulExpr in e83fdb8. The rationale is as follows:

  • This logic can conflict with inference based GEP attributes, which is the reused logic from LoopAccessAnalysis. For instance, if the given SCEV is %m * {0,+,%n}<%loop>, I think this reasoning is not valid (but if it is {0,+,(%m * %n)}<%loop>, it seems valid). Additionally, I found leveraging GEP attributes is beneficial -- removing this logic caused some tests to fail, which seems non-trivial at a glance. At least, as a first step, I feel it's reasonable to remove these handlings for add/mul and leave the reasoning to GEP attributes.
  • Even without the GEP based inference, I think handling SCEVAddExpr and SCEVMulExpr is non-trivial. For example, in the case of {0,+,1}<%loop> + {0,+,-1}<%loop> (apparently, this could happen), it is unclear whether this should be considered Monotonic or Invariant.

Removing the support caused some test regressions, but all failures were of the form like [* *] becoming confused. I believe the semantic impact of these changes is essentially negligible.

MonotonicityType visitPtrToIntExpr(const SCEVPtrToIntExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitTruncateExpr(const SCEVTruncateExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitUDivExpr(const SCEVUDivExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitSMaxExpr(const SCEVSMaxExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitUMaxExpr(const SCEVUMaxExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitSMinExpr(const SCEVSMinExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitUMinExpr(const SCEVUMinExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitSequentialUMinExpr(const SCEVSequentialUMinExpr *Expr) {
return checkInvarianceOnly(Expr);
}
MonotonicityType visitCouldNotCompute(const SCEVCouldNotCompute *Expr) {
return checkInvarianceOnly(Expr);
}

private:
ScalarEvolution *SE;
const Loop *OutermostLoop;
Copy link
Member

@Meinersbur Meinersbur Aug 25, 2025

Choose a reason for hiding this comment

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

Possibly remove the "Outermost", does not necessarily need to be an outermost loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel it's safer to make this outermost. Otherwise, unexpected behavior might occur in cases like below

; 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: Failed to prove monotonicity for: %offset.i
; CHECK: Failed to prove monotonicity for: {%offset.i,+,1}<nuw><nsw><%loop.j>
; CHECK: Monotonicity: Unknown expr: {%offset.i,+,1}<nuw><nsw><%loop.j>
;
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
}

bool NoWrapFromGEP = false;

SCEVSignedMonotonicityChecker(ScalarEvolution *SE, const Loop *OutermostLoop,
const Value *Ptr);

/// A helper to classify \p Expr as either Invariant or Unknown.
MonotonicityType checkInvarianceOnly(const SCEV *Expr);

/// Return true if \p Expr is loop-invariant with respect to the outermost
/// loop.
bool isLoopInvariant(const SCEV *Expr) const;
};

} // anonymous namespace

SCEVSignedMonotonicityChecker::SCEVSignedMonotonicityChecker(
ScalarEvolution *SE, const Loop *OutermostLoop, const Value *Ptr)
: SE(SE), OutermostLoop(OutermostLoop) {
if (Ptr) {
// Perform reasoning similar to LoopAccessAnalysis. If an AddRec would wrap
// and the GEP would have nusw, the wrapped memory location would become
// like as follows (in the mathmatical sense, assuming the step recurrence
// is positive):
//
// (previously accessed location) + (step recurrence) - 2^N
//
// where N is the size of the pointer index type. Since the value of step
// recurrence is less than 2^(N-1), the distance between the previously
// accessed location and the wrapped location will be greater than 2^(N-1),
// which is larger than half the pointer index type space. The size of
// allocated object must not exceed the largest signed integer that fits
// into the index type, so the GEP value would be poison and any memory
// access using it would be immediate UB when executed.
//
// TODO: The monotonicity check ensures that the given SCEV does not wrap
// in "any" iteration. Thus, inference from nusw should be valid only if
// the GEP is executed and its result is used in every iteration of the
// loop.
auto *GEP = dyn_cast<GetElementPtrInst>(Ptr);
if (GEP && GEP->hasNoUnsignedSignedWrap())
NoWrapFromGEP = true;
}
}

MonotonicityType SCEVSignedMonotonicityChecker::checkMonotonicity(
ScalarEvolution *SE, const SCEV *Expr, const Loop *OutermostLoop,
const Value *Ptr) {
SCEVSignedMonotonicityChecker Checker(SE, OutermostLoop, Ptr);
MonotonicityType MT = Checker.visit(Expr);

#ifndef NDEBUG
switch (MT) {
case MonotonicityType::Unknown:
LLVM_DEBUG(dbgs() << "Monotonicity: Unknown expr: " << *Expr << "\n");
break;
case MonotonicityType::NoSignedWrap:
LLVM_DEBUG(dbgs() << "Monotonicity: No signed wrap expr: " << *Expr
<< "\n");
break;
case MonotonicityType::Invariant:
LLVM_DEBUG(dbgs() << "Monotonicity: Invariant expr: " << *Expr << "\n");
break;
case MonotonicityType::MultiMonotonic:
LLVM_DEBUG(dbgs() << "Monotonicity: Monotonic expr: " << *Expr << "\n");
break;
}
#endif
return MT;
}

MonotonicityType
SCEVSignedMonotonicityChecker::checkInvarianceOnly(const SCEV *Expr) {
if (isLoopInvariant(Expr))
return MonotonicityType::Invariant;
LLVM_DEBUG(dbgs() << "Failed to prove monotonicity for: " << *Expr << "\n");
return MonotonicityType::Unknown;
}

bool SCEVSignedMonotonicityChecker::isLoopInvariant(const SCEV *Expr) const {
return !OutermostLoop || SE->isLoopInvariant(Expr, OutermostLoop);
}

MonotonicityType
SCEVSignedMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) {
if (!Expr->isAffine())
return checkInvarianceOnly(Expr);

const SCEV *Start = Expr->getStart();
const SCEV *Step = Expr->getStepRecurrence(*SE);

MonotonicityType StartRes = visit(Start);
if (StartRes == MonotonicityType::Unknown)
return checkInvarianceOnly(Expr);

MonotonicityType StepRes = visit(Step);
if (StepRes != MonotonicityType::Invariant)
return checkInvarianceOnly(Expr);

// TODO: Enhance the inference here.
if (!Expr->hasNoSignedWrap() && !NoWrapFromGEP) {
if (!SE->isKnownNegative(Step))
// If the coefficient can be positive value, ensure that the AddRec is
// monotonically increasing.
if (!SE->isKnownOnEveryIteration(ICmpInst::ICMP_SGE, Expr, Start))
return checkInvarianceOnly(Expr);

if (!SE->isKnownPositive(Step))
// If the coefficient can be positive value, ensure that the AddRec is
// monotonically decreasing.
if (!SE->isKnownOnEveryIteration(ICmpInst::ICMP_SLE, Expr, Start))
return checkInvarianceOnly(Expr);
}

bool IsKnownNonZero = SE->isKnownNonZero(Step);
switch (StartRes) {
case MonotonicityType::Unknown:
llvm_unreachable("should have been handled above");
case MonotonicityType::NoSignedWrap:
return MonotonicityType::NoSignedWrap;
case MonotonicityType::Invariant:
return IsKnownNonZero ? MonotonicityType::MultiMonotonic
: MonotonicityType::NoSignedWrap;
case MonotonicityType::MultiMonotonic:
// TODO: Should handle SCEV like `{{0,+,-1}<%loop>,+,1}<%loop>`?
return IsKnownNonZero ? MonotonicityType::MultiMonotonic
: MonotonicityType::NoSignedWrap;
}
llvm_unreachable("unhandled MonotonicityType");
}

MonotonicityType SCEVSignedMonotonicityChecker::visitZeroExtendExpr(
const SCEVZeroExtendExpr *Expr) {
return visit(Expr->getOperand());
}

MonotonicityType SCEVSignedMonotonicityChecker::visitSignExtendExpr(
const SCEVSignExtendExpr *Expr) {
return visit(Expr->getOperand());
}

MonotonicityType
SCEVSignedMonotonicityChecker::visitUnknown(const SCEVUnknown *Expr) {
if (!isLoopInvariant(Expr))
return checkInvarianceOnly(Expr);
return MonotonicityType::Invariant;
}

/// Check if we can delinearize the subscripts. If the SCEVs representing the
/// source and destination array references are recurrences on a nested loop,
/// this function flattens the nested recurrences into separate recurrences
Expand Down Expand Up @@ -3512,6 +3748,40 @@ bool DependenceInfo::tryDelinearizeParametricSize(
// to the dependency checks.
if (!DisableDelinearizationChecks)
for (size_t I = 1; I < Size; ++I) {
const Loop *OutermostLoop =
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Found a more serious issue (ref: #157859). To keep this PR simpler, I now think it is better to ignore the delinearized subscripts at the moment, and check monotonicity only when delinearization fails...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

LI->getLoopFor(Src->getParent())->getOutermostLoop();

// TODO: Inferring a subscript's monotonicity from the base pointer can
// lead to incorrect results. Consider the following code:
//
// %offset = ...
// %gep = getelementptr nusw i8, ptr %base, i64 %offset
//
// We might infer the monotonicity of %offset from nusw on the GEP (see
// the implementation of checkMonotonicity for details). This inference
// may be valid, but the same does not necessarily hold for each
// subscript. For example, assume %offset is {0,+,(%m * %n)}<%loop> where
// %m and %n are loop invariants. Delinearization can "decompose" this
// SCEV into something like:
//
// Size: [UnknownSize][%m]
// Subscripts: [{0,+,%n}][{0,+,1}]
//
// Here, if (%m * %n) wraps, %n can be larger than (%m * %n). Hence even
// if we know {0,+,(%m * %n)} doesn't wrap, we cannot conclude the same
// for {0,+,%n}.
MonotonicityType SrcMonotonicity =
SCEVSignedMonotonicityChecker::checkMonotonicity(
SE, SrcSubscripts[I], OutermostLoop, SrcPtr);
if (SrcMonotonicity == MonotonicityType::Unknown)
return false;

MonotonicityType DstMonotonicity =
SCEVSignedMonotonicityChecker::checkMonotonicity(
SE, DstSubscripts[I], OutermostLoop, DstPtr);
if (DstMonotonicity == MonotonicityType::Unknown)
return false;

bool SNN = isKnownNonNegative(SrcSubscripts[I], SrcPtr);
bool DNN = isKnownNonNegative(DstSubscripts[I], DstPtr);
bool SLT = isKnownLessThan(SrcSubscripts[I], Sizes[I - 1]);
Expand All @@ -3535,6 +3805,22 @@ bool DependenceInfo::tryDelinearizeParametricSize(
return false;
}

// TODO: Probably we need to prove that the "offset calculation" doesn't
// wrap. Here the offset calculation is:
//
// Offset =
// Subscripts[0] +
// Subscripts[1]*Sizes[0] +
// Subscripts[2]*Sizes[0]*Sizes[1] +
// ...
// Subscripts[N-1]*Sizes[0]*Sizes[1]*...*Sizes[N-2]
//
// where N is the number of dimensions. The subsequent dependence tests assume
// that different subscript values result in different offset values. If the
// above calculation wraps, this assumption is violated. Note that if every
// element of Subscripts is positive, the situation would be simple. However,
// the subscript for the outermost dimension (Subscripts[0]) can be negative.

return true;
}

Expand Down Expand Up @@ -3701,11 +3987,23 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
Pair[0].Src = SrcSCEV;
Pair[0].Dst = DstSCEV;

const Loop *OutermostLoop = SrcLoop ? SrcLoop->getOutermostLoop() : nullptr;
if (SCEVSignedMonotonicityChecker::checkMonotonicity(
SE, SrcEv, OutermostLoop, SrcPtr) == MonotonicityType::Unknown)
return std::make_unique<Dependence>(Src, Dst,
SCEVUnionPredicate(Assume, *SE));
if (SCEVSignedMonotonicityChecker::checkMonotonicity(
SE, DstEv, OutermostLoop, DstPtr) == MonotonicityType::Unknown)
return std::make_unique<Dependence>(Src, Dst,
SCEVUnionPredicate(Assume, *SE));

if (Delinearize) {
if (tryDelinearize(Src, Dst, Pair)) {
LLVM_DEBUG(dbgs() << " delinearized\n");
Pairs = Pair.size();
}
// TODO: Check that the original offsets are monotonic when delinearization
// fails.
}

for (unsigned P = 0; P < Pairs; ++P) {
Expand Down
4 changes: 2 additions & 2 deletions llvm/test/Analysis/DependenceAnalysis/AA.ll
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,11 @@ define void @test_tbaa_diff(ptr %A, ptr %B) {
define void @tbaa_loop(i32 %I, i32 %J, ptr nocapture %A, ptr nocapture readonly %B) {
; CHECK-LABEL: 'tbaa_loop'
; CHECK-NEXT: Src: %0 = load i16, ptr %arrayidx.us, align 4, !tbaa !0 --> Dst: %0 = load i16, ptr %arrayidx.us, align 4, !tbaa !0
; CHECK-NEXT: da analyze - input [* *]!
; CHECK-NEXT: da analyze - confused!
; CHECK-NEXT: Src: %0 = load i16, ptr %arrayidx.us, align 4, !tbaa !0 --> Dst: store i32 %add.us.lcssa, ptr %arrayidx6.us, align 4, !tbaa !4
; CHECK-NEXT: da analyze - none!
; CHECK-NEXT: Src: store i32 %add.us.lcssa, ptr %arrayidx6.us, align 4, !tbaa !4 --> Dst: store i32 %add.us.lcssa, ptr %arrayidx6.us, align 4, !tbaa !4
; CHECK-NEXT: da analyze - output [*]!
; CHECK-NEXT: da analyze - confused!
;
entry:
%cmp = icmp ne i32 %J, 0
Expand Down
Loading