Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
276 changes: 273 additions & 3 deletions llvm/lib/Analysis/DependenceAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,18 @@ static cl::opt<bool> 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<bool> 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<bool> DumpMonotonicityReport(
"da-dump-monotonicity-report", cl::init(false), cl::Hidden,
cl::desc(
"When printing analysis, dump the results of monotonicity checks."));

//===----------------------------------------------------------------------===//
// basics

Expand Down Expand Up @@ -177,13 +189,189 @@ void DependenceAnalysisWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const {
AU.addRequiredTransitive<LoopInfoWrapperPass>();
}

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.
Copy link
Member

Choose a reason for hiding this comment

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

Using Monotonic is fine if the documentation/definition reflects what it is meant to be, even if it only implements it for AddRec atm. That would give a clear path how it cold to be extended.

This property is defined with respect to the outermost loop that DA is analyzing.

Could be understood as FlagNSW of the outermost loop only, but I think you mean wrapping of any nested loop.

This is designed to classify the behavior of AddRec expressions, and does not care about other SCEVs.

The current doxygen for SCEVMonotonicityType says it it only for AddRecs, and mixes monotonicity and wrapping (I think we consider monotonicity to imply no-wrapping, so if a wrapping AddRec is found it cannot be monotonic, but the other way around may not be true, e.g. with a non-affine SCEVAddRecExpr). Only caring about AddRecs seems arbitrary. Why? What is the property we want to ensure? Could you use a clearer definition?

For example, given the two loop-invariant values A and B, A + B is treated as Invariant even if the addition wraps.

I think this is easier to explain with monotonicity, which is always relative to a variates, the loop induction variables in this case. A and B are just constants (so A + B also evaluates to just a constant, even if it is the result of an overflow, and could have been hoisted out of the loop), i.e. the function over which we defined monotonicity is $f_{A,B}(i)$, where $i$ is the variate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's clearly mixing up the definition and the implementation. The definition itself doesn't need to be limited to AddRecs. I'll rewrite the definition.

I think we consider monotonicity to imply no-wrapping, so if a wrapping AddRec is found it cannot be monotonic

Correct, that's what I tried to describe.

For example, given the two loop-invariant values A and B, A + B is treated as Invariant even if the addition wraps.

I think this is easier to explain with monotonicity, which is always relative to a variates, the loop induction variables in this case. A and B are just constants (so A + B also evaluates to just a constant, even if it is the result of an overflow, and could have been hoisted out of the loop), i.e. the function over which we defined monotonicity is f A , B ( i ) , where i is the variate.

Do you mean that monotonicity is not defined for constants in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Revised the definition of monotonicity. I believe it's better than before...

Copy link
Member

Choose a reason for hiding this comment

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

Do you mean that monotonicity is not defined for constants in the first place?

Constants, in the sense of a parametric function, are not the subject of the monotonicity property, only the function argument/variate is. It is the question "when modifying x, how does $f_c(x)$ behave". The answer may depend on $c$, such as $f_c(x)=c*x$ (strictly monotonically decreasing for $c &lt; 0$, monotonic for $c &gt; 0$, strictly monotonically increasing for $c &gt; 0$). Like in case of a derivative, we ask for the slope of $f_c(x)$ at some position $x$. The answer may depend on c ($f'_c(x)=c$), but we are not asking for the slope of infinitismally close $c$ and $c'$.

For loop nests, $c$ is typically the increament of a loop/stride of an array subscript and therefore usually a literal constant, i.e. known $c$. If $c$ is not known at compile-time, the query of monotonicity must either include "for which value of c?", or answer with the most pessimistic result for any $c$, possibly Unkown, i.e. using a forall quantifier. For the $f_c(x)=c*x$ example above, without any wrapping behavior, the answer can be monotonic, as long as we do not care whether it is strict/increasing/decreasing.

For DA, it just means within the scope of the anlysis, $c$ will always have the same value, the behavior of the same LLVM function called with a different value of $c'$ when is irrelvant. DA does not give you dependency information accross function calls (respectively: different values of an invariant variable), only between loop iterations within the same function call. If a and b are invariant/constant, a + b is just some value, no matter how it was computed. It could also be an unknown function g(a,b), as long as we know that the result of g only depends on its arguments.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I think I got it. It seems that I was mixed parameters and arguments for a parametric function.

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
Copy link
Member

@Meinersbur Meinersbur Oct 15, 2025

Choose a reason for hiding this comment

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

When I brought up monotonicity, I did not mean to apply it to only AddRec expression (which already has a FlagNW property), but to SCEVs in general. An expression could overflow, while none of its operands does. For instance

for (int i = 0; i < INT_MAX; ++i)
  A[i + 2];

Additions usually get folded into the SCEVAddRecExpr, but others are not, such as UDiv, URem, UMax, UMin, ... . That is

for (int i = 0; i < INT_MAX; ++i)
  A[i/2];

is monotonic

It is fine if we want to only handle outermost-AddRecs at first, but the comments implies this is about AddRec expressions only. The goal was to ensure that the closed range [Expr(first-iteration), Expr(last-iteration)] (or [Expr(last-iteration), Expr(first-iteration)]) describes the entire range of the expression, i.e. there is no i for which Expr(i) that falls outside that range. The name "monotonic" came from because last-iteration could be any iteration (SCEV does not know when the loop will actually terminate), and the range therefore be the range of values Expr could evaluate so far, leading to a (not necessarily strictly) (increasing or decreasing) monotonic function.

For multiple iteration variables, the range could be [Expr(i_first, j_first) .. Expr(i_last, j_last)] (or [Expr(i_last, j_last) .. Expr(i_first, j_first)]), or the combinatorial

ExtremeValueList = {Expr(i_first, j_first), Expr(i_first, j_last), Expr(i_last, j_first), Expr(i_last, j_last)};
Range := [min(ExtremeValueList), max(ExtremeValueList)]`. 

According to the definition of MultiSignedMonotonic, it would be the latter. In my reading of https://en.wikipedia.org/wiki/Monotonic_function, functional analysis would use the first, topology the lastter definition.

If on the other side you understand SCEVMonotonicityType as the same as FlagNSW, but taking for taking all loops of the nest into account, not just the one store in the SCEVAddRecExpr), I would suggest to not call that property "monotonicity".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At first, I was thinking of handling various cases like as you mentioned, but now I think it's fine to focus on AddRecs for the time being. I don't have a strong preference, so changing the name seems reasonable to me (it's also a bit questionable that Invariant is included in the SCEVMonotonicityType in the first place, as you said in #162280 (comment)).

I'm thinking of renaming it something like MultivariateWrapType. Since I'm not good at naming, I'm happy if you have a better idea.

As for the definition, the latter one seems to match my intention.

ExtremeValueList := {Expr(i_first, j_first), Expr(i_first, j_last), Expr(i_last, j_first), Expr(i_last, j_last)};
Range := [min(ExtremeValueList), max(ExtremeValueList)]
IsMonotonic(Expr) := Expr(i, j) is in Range for all i in [i_first, i_last] and j in [j_first, j_last]

(since I slacked off on studying, I don't really understand about topology...)

/// 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}<nsw> is classifed as Monotonic if `%a` can be
/// zero. That is, the expression can be Invariant.
MultiSignedMonotonic,
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 think this name is not good. Please let me know if you have a better one. (it would be better if the name also imply that the step value is loop invariant.)

Copy link
Member

Choose a reason for hiding this comment

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

MultivariateMonotonic

For a mathematical (monotonic) function, invariants are just constants that do not appear in the function's domain.

};

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, SCEVMonotonicity> {
Comment on lines +268 to +269
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As for the testability, maybe is it better to split the file, like ScalarEvolutionDivision.cpp? Or would it be better to avoid creating separate files unnecessarily?


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<SCEVMonotonicityChecker, SCEVMonotonicity>;
};

} // 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<LoadInst>(Inst) && !isa<StoreInst>(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()) {
Expand Down Expand Up @@ -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<ScalarEvolutionWrapperPass>().getSE(), false);
OS, info.get(), getAnalysis<ScalarEvolutionWrapperPass>().getSE(),
getAnalysis<LoopInfoWrapperPass>().getLoopInfo(), false);
}

PreservedAnalyses
Expand All @@ -244,7 +433,7 @@ DependenceAnalysisPrinterPass::run(Function &F, FunctionAnalysisManager &FAM) {
<< "':\n";
dumpExampleDependence(OS, &FAM.getResult<DependenceAnalysis>(F),
FAM.getResult<ScalarEvolutionAnalysis>(F),
NormalizeResults);
FAM.getResult<LoopAnalysis>(F), NormalizeResults);
return PreservedAnalyses::all();
}

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
}
Comment on lines +3769 to +3774
Copy link
Contributor

Choose a reason for hiding this comment

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

Another question here, otherwise LGTM.

If we have mutliple subscripts and all of them are monotonic, how could the other monotonicity check (line 4083-4) fail? We need to answer this to make sure we are not running the test redundantly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider the following case (godbolt):

; 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 @outer_loop_may_wrap(ptr %a) {
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
}

The subscripts {0,+,1}<nuw><nsw><%loop.i.header> and {0,+,1}<nuw><nsw><%loop.j.header> are monotonic, but the original offset {{0,+,32}<%loop.i.header>,+,1}<nw><%loop.j.header> is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added the above test case.

}

return true;
Expand Down Expand Up @@ -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())
Comment on lines +4109 to +4111
Copy link
Contributor

Choose a reason for hiding this comment

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

I have a basic question about these two tests here: If we have an AddRec with a nsw flag, that means this AddRec doesn't wrap. Why that is not enough and we need to recursively check each component of AddRec?

I guess the flags from SCEV assume all the internal components are fixed and only the top level calculation doesn't overflow? Is that correct?

In that case you may want to have a testcase where the top level AddRec has nsw, but monotonicity fails. I didn't see that in your test, but in other test files we have examples of that. It is helpful to add that.

Copy link
Contributor

Choose a reason for hiding this comment

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

However the example that I see is this loop (the first test in SameSDLoops.ll)

;;  for (long int i = 0; i < 10; i++) {
;;    for (long int j = 0; j < 10; j++) {
;;      for (long int k = 0; k < 10; k++) {
;;        for (long int l = 0; l < 10; l++)
;;          A[i][j][k][l] = i;
;;      }
;;      for (long int k = 1; k < 11; k++) {
;;        for (long int l = 0; l < 10; l++)
;;          A[i + 4][j + 3][k + 2][l + 1] = l;

It is strange that we cannot prove monotonicity here:

Printing analysis 'Dependence Analysis' for function 'samebd0':
Monotonicity check:
  Inst:   store i64 %i.013, ptr %arrayidx12, align 8
    Expr: {{{{0,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nuw><nsw><%for.cond4.preheader>,+,800}<nuw><nsw><%for.cond7.preheader>,+,8}<nuw><nsw><%for.body9>
    Monotonicity: MultiSignedMonotonic
  Inst:   store i64 %l17.04, ptr %arrayidx24, align 8
    Expr: {{{{32242408,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nw><%for.cond4.preheader>,+,800}<nuw><nsw><%for.cond18.preheader>,+,8}<nuw><nsw><%for.body20>
    Monotonicity: Unknown
    Reason: {{32242408,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nw><%for.cond4.preheader>

Copy link
Contributor Author

@kasuga-fj kasuga-fj Oct 14, 2025

Choose a reason for hiding this comment

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

I have a basic question about these two tests here: If we have an AddRec with a nsw flag, that means this AddRec doesn't wrap. Why that is not enough and we need to recursively check each component of AddRec?

I guess the flags from SCEV assume all the internal components are fixed and only the top level calculation doesn't overflow? Is that correct?

In my understanding, your guess is correct. I added a test case @outer_loop_may_wrap, which I believe demonstrates the scenario where only the outer addrec is guaranteed not to wrap.

However the example that I see is this loop (the first test in SameSDLoops.ll)

;;  for (long int i = 0; i < 10; i++) {
;;    for (long int j = 0; j < 10; j++) {
;;      for (long int k = 0; k < 10; k++) {
;;        for (long int l = 0; l < 10; l++)
;;          A[i][j][k][l] = i;
;;      }
;;      for (long int k = 1; k < 11; k++) {
;;        for (long int l = 0; l < 10; l++)
;;          A[i + 4][j + 3][k + 2][l + 1] = l;

It is strange that we cannot prove monotonicity here:

Printing analysis 'Dependence Analysis' for function 'samebd0':
Monotonicity check:
  Inst:   store i64 %i.013, ptr %arrayidx12, align 8
    Expr: {{{{0,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nuw><nsw><%for.cond4.preheader>,+,800}<nuw><nsw><%for.cond7.preheader>,+,8}<nuw><nsw><%for.body9>
    Monotonicity: MultiSignedMonotonic
  Inst:   store i64 %l17.04, ptr %arrayidx24, align 8
    Expr: {{{{32242408,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nw><%for.cond4.preheader>,+,800}<nuw><nsw><%for.cond18.preheader>,+,8}<nuw><nsw><%for.body20>
    Monotonicity: Unknown
    Reason: {{32242408,+,8000000}<nuw><nsw><%for.cond1.preheader>,+,80000}<nw><%for.cond4.preheader>

I don't know much about how nowrap flags are transferred from IR to SCEV, but this appears to be a limitation of SCEV. At a glance, it’s not obvious that the second store A[i + 4][j + 3][k + 2][l + 1] = l is always executed when entering the j-loop. This may be the reason why the nowrap flags for %for.cond4.preheader are not preserved in SCEV.

Anyway, for this specific case, I think we could perform additional cheap analysis similar to range analysis in SCEV, since all values except the induction variables are constants. That said, I'm not planning to include such a feature in this PR.

return std::make_unique<Dependence>(Src, Dst,
SCEVUnionPredicate(Assume, *SE));

if (Delinearize) {
if (tryDelinearize(Src, Dst, Pair)) {
LLVM_DEBUG(dbgs() << " delinearized\n");
Expand Down
Loading