Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
331 changes: 331 additions & 0 deletions llvm/lib/Analysis/DependenceAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3308,6 +3308,300 @@ 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 visitAddExpr(const SCEVAddExpr *Expr) {
return visitNAryHelper(Expr);
}
MonotonicityType visitMulExpr(const SCEVMulExpr *Expr) {
return visitNAryHelper(Expr);
}

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

// TODO: Handle more cases.
MonotonicityType visitPtrToIntExpr(const SCEVPtrToIntExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitTruncateExpr(const SCEVTruncateExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitUDivExpr(const SCEVUDivExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitSMaxExpr(const SCEVSMaxExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitUMaxExpr(const SCEVUMaxExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitSMinExpr(const SCEVSMinExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitUMinExpr(const SCEVUMinExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitSequentialUMinExpr(const SCEVSequentialUMinExpr *Expr) {
return unknownMonotonicity(Expr);
}
MonotonicityType visitCouldNotCompute(const SCEVCouldNotCompute *Expr) {
return unknownMonotonicity(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);

MonotonicityType visitNAryHelper(const SCEVNAryExpr *Expr);
MonotonicityType unknownMonotonicity(const SCEV *Expr);
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: We don't check if the result of the GEP is always used. Maybe we
// should check the reachability from the GEP to the target instruction.
// E.g., in the following case, no-wrap would not trigger immediate UB:
//
// entry:
// ...
// %gep = getelementptr inbounds i32, ptr %ptr, i32 %addrec
// br i1 %cond, label %store, label %sink
//
// store:
// store i32 42, ptr %ptr
// br label %sink
//
// sink:
// ...
//
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::visitNAryHelper(const SCEVNAryExpr *Expr) {
assert((isa<SCEVAddExpr>(Expr) || isa<SCEVMulExpr>(Expr)) &&
"Unexpected SCEV");

if (isLoopInvariant(Expr))
return MonotonicityType::Invariant;

MonotonicityType Result = MonotonicityType::Invariant;
for (const SCEV *Op : Expr->operands()) {
assert(Result != MonotonicityType::Unknown && "Unexpected state");
switch (visit(Op)) {
case MonotonicityType::Unknown:
return unknownMonotonicity(Expr);
case MonotonicityType::NoSignedWrap:
Result = MonotonicityType::NoSignedWrap;
break;
case MonotonicityType::Invariant:
break;
case MonotonicityType::MultiMonotonic: {
switch (Result) {
case MonotonicityType::Unknown:
llvm_unreachable("should have been handled above");
case MonotonicityType::NoSignedWrap:
break;
case MonotonicityType::Invariant:
if (!Expr->hasNoSignedWrap())
return unknownMonotonicity(Expr);
Result = MonotonicityType::MultiMonotonic;
break;
case MonotonicityType::MultiMonotonic:
if (!Expr->hasNoSignedWrap())
return unknownMonotonicity(Expr);
if (!isa<SCEVAddExpr>(Expr))
return unknownMonotonicity(Expr);
// Monotonic + Monotonic might be a loop invariant, e.g., the following
// SCEV:
//
// {0,+,1}<%loop> + {0,+,-1}<%loop>
//
// In that case, relax the property to NoSignedWrap.
Result = MonotonicityType::NoSignedWrap;
break;
}
} break;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont understand one thing here. If the entire SCEV is NSW, why do we need to check if its NSW for individual operands? Do you have specific case in mind?

Also, I am trying to understand what MonotonicityType::Monotonic really means. Just going by the mathematical definition of monotonicity,

  1. why Monotonic + Monotonic should be MaySignedWrap? Shouldnt it be Monotonic?
  2. why Monotonic + Constant should be Constant? Shouldnt it be Monotonic?

Copy link
Contributor Author

@kasuga-fj kasuga-fj Aug 25, 2025

Choose a reason for hiding this comment

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

First of all, probably the name is misleading. MaySignedWrap should be renamed to something like Unknown.

If the entire SCEV is NSW, why do we need to check if its NSW for individual operands? Do you have specific case in mind?

I was imagining an example like {0,+,%m}<%loop> + {0,+,%n}<%loop>, but I'm not sure if such a representation can actually exist. If ScalarEvolution guarantees that this form is always folded into something like {0,+,(%m+%n)}<%loop>, then maybe this is unnecessary. But if not, I'm not confident it's safe when each operand can potentially overflow (although DA doesn't support this kind of format)

Just going by the mathematical definition of monotonicity

DA breaks exactly due to the gap between mathematical theory and LLVM IR semantics.

  1. why Monotonic + Monotonic should be MaySignedWrap? Shouldnt it be Monotonic?

To clearly distinguish between Constant and Monotonic. I was considering a case like {0,+,1}<%loop> + {0,+,-1}<%loop>. This always seems to evaluate to 0 (i.e., a Constant), but again, I'm not sure if such a representation can exist in SCEV.

  1. why Monotonic + Constant should be Constant? Shouldnt it be Monotonic?

I think this is just a simple implementation mistake. Thanks for pointing it out.

Copy link
Contributor

Choose a reason for hiding this comment

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

First of all, probably the name is misleading. MaySignedWrap should be renamed to something like Unknown

ok, please change to Unknown or CouldNotCompute. MaySignedWrap is confusing

example like {0,+,%m}<%loop> + {0,+,%n}<%loop>

If the entire expression is nuw/nsw then individual SCEVs must follow the same pattern but vice-versa cant be true(this may wrap).

that this form is always folded into something like {0,+,(%m+%n)}<%loop>

this is not true because when its split form, each AddRed can have different values . But with (%m+%n) , every itr is multiple of (%m+%n)

{0,+,1}<%loop> + {0,+,-1}<%loop>

this expr can have values 0(=0+0), -1(=0-1), 1(=1+0), 0(=1-1). This is definitely not a constant. So, this should be Unknown

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What I'm not entirely sure about is whether, given the following IR, the SCEV corresponding to %mn_i is guaranteed to be {0,+,(%m + %n)}<%loop> rather than {0,+,%m}<%loop> + {0,+,%n}<%loop>

loop:
  %i = phi i64 [ 0, %entry ], [ %i.inc, %loop ]
  %m_i = mul nsw i64 %m, %i
  %n_i = mul nsw i64 %n, %i
  %mn_i = add nsw i64 %m_i, %n_i
  ...

If not, then I don't think we can say "Monotonic + Monotonic = Monotonic", since %m could be equal to -1 * %n, in which case %mn_i would always be zero. I don't know whether a constant value is considered to "monotonic" in general mathematical theory, but it is a corner case in the context of DA.

Copy link
Contributor

Choose a reason for hiding this comment

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

if m is invariant in current loop then it should be {0,+,(%m + %n)}<%loop> and vice-versa if n comes from outer 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.

Okay, then I think the logic can be simplified.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kasuga-fj Expressions like {0,+,%m}<%loop> + {0,+,%n}<%loop> are possible if SCEV either hits the arithmetic depth limit, or the huge expression limit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nikic I see, thanks for letting me know.

}
return Result;
}

MonotonicityType
SCEVSignedMonotonicityChecker::unknownMonotonicity(const SCEV *Expr) {
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 unknownMonotonicity(Expr);

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

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

MonotonicityType StepRes = visit(Step);
if (StepRes != MonotonicityType::Invariant)
return unknownMonotonicity(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 unknownMonotonicity(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 unknownMonotonicity(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 unknownMonotonicity(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 @@ -3500,12 +3794,37 @@ bool DependenceInfo::tryDelinearizeParametricSize(
// to the dependency checks.
if (!DisableDelinearizationChecks)
for (size_t I = 1; I < Size; ++I) {
const Loop *OutermostLoop =
LI->getLoopFor(Src->getParent())->getOutermostLoop();

// TODO: In general, reasoning about monotonicity of a subscript from the
// base pointer would lead incorrect result. Probably we need to check
// the loops associated with this subscript are disjoint from those
// associated with the other subscripts. The validation would be
// something like:
//
// LoopsI = collectCommonLoops(SrcSubscripts[I])
// LoopsOthers = collectCommonLoops(SrcSCEV - SrcSubscripts[I])
// CanUsePtr = (LoopsI intersect LoopsOthers) is empty.
//
MonotonicityType SrcMonotonicity =
SCEVSignedMonotonicityChecker::checkMonotonicity(
SE, SrcSubscripts[I], OutermostLoop, SrcPtr);
if (SrcMonotonicity == MonotonicityType::Unknown)
return false;

if (!isKnownNonNegative(SrcSubscripts[I], SrcPtr))
return false;

if (!isKnownLessThan(SrcSubscripts[I], Sizes[I - 1]))
return false;

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

if (!isKnownNonNegative(DstSubscripts[I], DstPtr))
return false;

Expand Down Expand Up @@ -3679,11 +3998,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
Loading