Skip to content

Commit 23fc12d

Browse files
kasuga-fjaokblast
authored andcommitted
[DA] Add initial support for monotonicity check (llvm#162280)
The dependence testing functions in DA assume that the analyzed AddRec does not wrap over the entire iteration space. For AddRecs that may wrap, DA should conservatively return unknown dependence. However, no validation is currently performed to ensure that this condition holds, which can lead to incorrect results in some cases. This patch introduces the notion of *monotonicity* and a validation logic to check whether a SCEV is monotonic. The monotonicity check classifies the SCEV into one of the following categories: - Unknown: Nothing is known about the monotonicity of the SCEV. - Invariant: The SCEV is loop-invariant. - MultivariateSignedMonotonic: The SCEV doesn't wrap in a signed sense for any iteration of the loops in the loop nest. The current validation logic basically searches an affine AddRec recursively and checks whether the `nsw` flag is present. Notably, it is still unclear whether we should also have a category for unsigned monotonicity. The monotonicity check is still under development and disabled by default for now. Since such a check is necessary to make DA sound, it should be enabled by default once the functionality is sufficient. Split off from llvm#154527.
1 parent aeb008e commit 23fc12d

File tree

6 files changed

+1303
-3
lines changed

6 files changed

+1303
-3
lines changed

llvm/lib/Analysis/DependenceAnalysis.cpp

Lines changed: 291 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,18 @@ static cl::opt<bool> RunSIVRoutinesOnly(
128128
"The purpose is mainly to exclude the influence of those routines "
129129
"in regression tests for SIV routines."));
130130

131+
// TODO: This flag is disabled by default because it is still under development.
132+
// Enable it or delete this flag when the feature is ready.
133+
static cl::opt<bool> EnableMonotonicityCheck(
134+
"da-enable-monotonicity-check", cl::init(false), cl::Hidden,
135+
cl::desc("Check if the subscripts are monotonic. If it's not, dependence "
136+
"is reported as unknown."));
137+
138+
static cl::opt<bool> DumpMonotonicityReport(
139+
"da-dump-monotonicity-report", cl::init(false), cl::Hidden,
140+
cl::desc(
141+
"When printing analysis, dump the results of monotonicity checks."));
142+
131143
//===----------------------------------------------------------------------===//
132144
// basics
133145

@@ -177,13 +189,196 @@ void DependenceAnalysisWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const {
177189
AU.addRequiredTransitive<LoopInfoWrapperPass>();
178190
}
179191

192+
namespace {
193+
194+
/// The property of monotonicity of a SCEV. To define the monotonicity, assume
195+
/// a SCEV defined within N-nested loops. Let i_k denote the iteration number
196+
/// of the k-th loop. Then we can regard the SCEV as an N-ary function:
197+
///
198+
/// F(i_1, i_2, ..., i_N)
199+
///
200+
/// The domain of i_k is the closed range [0, BTC_k], where BTC_k is the
201+
/// backedge-taken count of the k-th loop.
202+
///
203+
/// A function F is said to be "monotonically increasing with respect to the
204+
/// k-th loop" if x <= y implies the following condition:
205+
///
206+
/// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) <=
207+
/// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N)
208+
///
209+
/// where i_1, ..., i_{k-1}, i_{k+1}, ..., i_N, x, and y are elements of their
210+
/// respective domains.
211+
///
212+
/// Likewise F is "monotonically decreasing with respect to the k-th loop"
213+
/// if x <= y implies
214+
///
215+
/// F(i_1, ..., i_{k-1}, x, i_{k+1}, ..., i_N) >=
216+
/// F(i_1, ..., i_{k-1}, y, i_{k+1}, ..., i_N)
217+
///
218+
/// A function F that is monotonically increasing or decreasing with respect to
219+
/// the k-th loop is simply called "monotonic with respect to k-th loop".
220+
///
221+
/// A function F is said to be "multivariate monotonic" when it is monotonic
222+
/// with respect to all of the N loops.
223+
///
224+
/// Since integer comparison can be either signed or unsigned, we need to
225+
/// distinguish monotonicity in the signed sense from that in the unsigned
226+
/// sense. Note that the inequality "x <= y" merely indicates loop progression
227+
/// and is not affected by the difference between signed and unsigned order.
228+
///
229+
/// Currently we only consider monotonicity in a signed sense.
230+
enum class SCEVMonotonicityType {
231+
/// We don't know anything about the monotonicity of the SCEV.
232+
Unknown,
233+
234+
/// The SCEV is loop-invariant with respect to the outermost loop. In other
235+
/// words, the function F corresponding to the SCEV is a constant function.
236+
Invariant,
237+
238+
/// The function F corresponding to the SCEV is multivariate monotonic in a
239+
/// signed sense. Note that the multivariate monotonic function may also be a
240+
/// constant function. The order employed in the definition of monotonicity
241+
/// is not strict order.
242+
MultivariateSignedMonotonic,
243+
};
244+
245+
struct SCEVMonotonicity {
246+
SCEVMonotonicity(SCEVMonotonicityType Type,
247+
const SCEV *FailurePoint = nullptr);
248+
249+
SCEVMonotonicityType getType() const { return Type; }
250+
251+
const SCEV *getFailurePoint() const { return FailurePoint; }
252+
253+
bool isUnknown() const { return Type == SCEVMonotonicityType::Unknown; }
254+
255+
void print(raw_ostream &OS, unsigned Depth) const;
256+
257+
private:
258+
SCEVMonotonicityType Type;
259+
260+
/// The subexpression that caused Unknown. Mainly for debugging purpose.
261+
const SCEV *FailurePoint;
262+
};
263+
264+
/// Check the monotonicity of a SCEV. Since dependence tests (SIV, MIV, etc.)
265+
/// assume that subscript expressions are (multivariate) monotonic, we need to
266+
/// verify this property before applying those tests. Violating this assumption
267+
/// may cause them to produce incorrect results.
268+
struct SCEVMonotonicityChecker
269+
: public SCEVVisitor<SCEVMonotonicityChecker, SCEVMonotonicity> {
270+
271+
SCEVMonotonicityChecker(ScalarEvolution *SE) : SE(SE) {}
272+
273+
/// Check the monotonicity of \p Expr. \p Expr must be integer type. If \p
274+
/// OutermostLoop is not null, \p Expr must be defined in \p OutermostLoop or
275+
/// one of its nested loops.
276+
SCEVMonotonicity checkMonotonicity(const SCEV *Expr,
277+
const Loop *OutermostLoop);
278+
279+
private:
280+
ScalarEvolution *SE;
281+
282+
/// The outermost loop that DA is analyzing.
283+
const Loop *OutermostLoop;
284+
285+
/// A helper to classify \p Expr as either Invariant or Unknown.
286+
SCEVMonotonicity invariantOrUnknown(const SCEV *Expr);
287+
288+
/// Return true if \p Expr is loop-invariant with respect to the outermost
289+
/// loop.
290+
bool isLoopInvariant(const SCEV *Expr) const;
291+
292+
/// A helper to create an Unknown SCEVMonotonicity.
293+
SCEVMonotonicity createUnknown(const SCEV *FailurePoint) {
294+
return SCEVMonotonicity(SCEVMonotonicityType::Unknown, FailurePoint);
295+
}
296+
297+
SCEVMonotonicity visitAddRecExpr(const SCEVAddRecExpr *Expr);
298+
299+
SCEVMonotonicity visitConstant(const SCEVConstant *) {
300+
return SCEVMonotonicity(SCEVMonotonicityType::Invariant);
301+
}
302+
SCEVMonotonicity visitVScale(const SCEVVScale *) {
303+
return SCEVMonotonicity(SCEVMonotonicityType::Invariant);
304+
}
305+
306+
// TODO: Handle more cases.
307+
SCEVMonotonicity visitZeroExtendExpr(const SCEVZeroExtendExpr *Expr) {
308+
return invariantOrUnknown(Expr);
309+
}
310+
SCEVMonotonicity visitSignExtendExpr(const SCEVSignExtendExpr *Expr) {
311+
return invariantOrUnknown(Expr);
312+
}
313+
SCEVMonotonicity visitAddExpr(const SCEVAddExpr *Expr) {
314+
return invariantOrUnknown(Expr);
315+
}
316+
SCEVMonotonicity visitMulExpr(const SCEVMulExpr *Expr) {
317+
return invariantOrUnknown(Expr);
318+
}
319+
SCEVMonotonicity visitPtrToIntExpr(const SCEVPtrToIntExpr *Expr) {
320+
return invariantOrUnknown(Expr);
321+
}
322+
SCEVMonotonicity visitTruncateExpr(const SCEVTruncateExpr *Expr) {
323+
return invariantOrUnknown(Expr);
324+
}
325+
SCEVMonotonicity visitUDivExpr(const SCEVUDivExpr *Expr) {
326+
return invariantOrUnknown(Expr);
327+
}
328+
SCEVMonotonicity visitSMaxExpr(const SCEVSMaxExpr *Expr) {
329+
return invariantOrUnknown(Expr);
330+
}
331+
SCEVMonotonicity visitUMaxExpr(const SCEVUMaxExpr *Expr) {
332+
return invariantOrUnknown(Expr);
333+
}
334+
SCEVMonotonicity visitSMinExpr(const SCEVSMinExpr *Expr) {
335+
return invariantOrUnknown(Expr);
336+
}
337+
SCEVMonotonicity visitUMinExpr(const SCEVUMinExpr *Expr) {
338+
return invariantOrUnknown(Expr);
339+
}
340+
SCEVMonotonicity visitSequentialUMinExpr(const SCEVSequentialUMinExpr *Expr) {
341+
return invariantOrUnknown(Expr);
342+
}
343+
SCEVMonotonicity visitUnknown(const SCEVUnknown *Expr) {
344+
return invariantOrUnknown(Expr);
345+
}
346+
SCEVMonotonicity visitCouldNotCompute(const SCEVCouldNotCompute *Expr) {
347+
return invariantOrUnknown(Expr);
348+
}
349+
350+
friend struct SCEVVisitor<SCEVMonotonicityChecker, SCEVMonotonicity>;
351+
};
352+
353+
} // anonymous namespace
354+
180355
// Used to test the dependence analyzer.
181356
// Looks through the function, noting instructions that may access memory.
182357
// Calls depends() on every possible pair and prints out the result.
183358
// Ignores all other instructions.
184359
static void dumpExampleDependence(raw_ostream &OS, DependenceInfo *DA,
185-
ScalarEvolution &SE, bool NormalizeResults) {
360+
ScalarEvolution &SE, LoopInfo &LI,
361+
bool NormalizeResults) {
186362
auto *F = DA->getFunction();
363+
364+
if (DumpMonotonicityReport) {
365+
SCEVMonotonicityChecker Checker(&SE);
366+
OS << "Monotonicity check:\n";
367+
for (Instruction &Inst : instructions(F)) {
368+
if (!isa<LoadInst>(Inst) && !isa<StoreInst>(Inst))
369+
continue;
370+
Value *Ptr = getLoadStorePointerOperand(&Inst);
371+
const Loop *L = LI.getLoopFor(Inst.getParent());
372+
const SCEV *PtrSCEV = SE.getSCEVAtScope(Ptr, L);
373+
const SCEV *AccessFn = SE.removePointerBase(PtrSCEV);
374+
SCEVMonotonicity Mon = Checker.checkMonotonicity(AccessFn, L);
375+
OS.indent(2) << "Inst: " << Inst << "\n";
376+
OS.indent(4) << "Expr: " << *AccessFn << "\n";
377+
Mon.print(OS, 4);
378+
}
379+
OS << "\n";
380+
}
381+
187382
for (inst_iterator SrcI = inst_begin(F), SrcE = inst_end(F); SrcI != SrcE;
188383
++SrcI) {
189384
if (SrcI->mayReadOrWriteMemory()) {
@@ -235,7 +430,8 @@ static void dumpExampleDependence(raw_ostream &OS, DependenceInfo *DA,
235430
void DependenceAnalysisWrapperPass::print(raw_ostream &OS,
236431
const Module *) const {
237432
dumpExampleDependence(
238-
OS, info.get(), getAnalysis<ScalarEvolutionWrapperPass>().getSE(), false);
433+
OS, info.get(), getAnalysis<ScalarEvolutionWrapperPass>().getSE(),
434+
getAnalysis<LoopInfoWrapperPass>().getLoopInfo(), false);
239435
}
240436

241437
PreservedAnalyses
@@ -244,7 +440,7 @@ DependenceAnalysisPrinterPass::run(Function &F, FunctionAnalysisManager &FAM) {
244440
<< "':\n";
245441
dumpExampleDependence(OS, &FAM.getResult<DependenceAnalysis>(F),
246442
FAM.getResult<ScalarEvolutionAnalysis>(F),
247-
NormalizeResults);
443+
FAM.getResult<LoopAnalysis>(F), NormalizeResults);
248444
return PreservedAnalyses::all();
249445
}
250446

@@ -670,6 +866,81 @@ bool DependenceInfo::intersectConstraints(Constraint *X, const Constraint *Y) {
670866
return false;
671867
}
672868

869+
//===----------------------------------------------------------------------===//
870+
// SCEVMonotonicity
871+
872+
SCEVMonotonicity::SCEVMonotonicity(SCEVMonotonicityType Type,
873+
const SCEV *FailurePoint)
874+
: Type(Type), FailurePoint(FailurePoint) {
875+
assert(
876+
((Type == SCEVMonotonicityType::Unknown) == (FailurePoint != nullptr)) &&
877+
"FailurePoint must be provided iff Type is Unknown");
878+
}
879+
880+
void SCEVMonotonicity::print(raw_ostream &OS, unsigned Depth) const {
881+
OS.indent(Depth) << "Monotonicity: ";
882+
switch (Type) {
883+
case SCEVMonotonicityType::Unknown:
884+
assert(FailurePoint && "FailurePoint must be provided for Unknown");
885+
OS << "Unknown\n";
886+
OS.indent(Depth) << "Reason: " << *FailurePoint << "\n";
887+
break;
888+
case SCEVMonotonicityType::Invariant:
889+
OS << "Invariant\n";
890+
break;
891+
case SCEVMonotonicityType::MultivariateSignedMonotonic:
892+
OS << "MultivariateSignedMonotonic\n";
893+
break;
894+
}
895+
}
896+
897+
bool SCEVMonotonicityChecker::isLoopInvariant(const SCEV *Expr) const {
898+
return !OutermostLoop || SE->isLoopInvariant(Expr, OutermostLoop);
899+
}
900+
901+
SCEVMonotonicity SCEVMonotonicityChecker::invariantOrUnknown(const SCEV *Expr) {
902+
if (isLoopInvariant(Expr))
903+
return SCEVMonotonicity(SCEVMonotonicityType::Invariant);
904+
return createUnknown(Expr);
905+
}
906+
907+
SCEVMonotonicity
908+
SCEVMonotonicityChecker::checkMonotonicity(const SCEV *Expr,
909+
const Loop *OutermostLoop) {
910+
assert(Expr->getType()->isIntegerTy() && "Expr must be integer type");
911+
this->OutermostLoop = OutermostLoop;
912+
return visit(Expr);
913+
}
914+
915+
/// We only care about an affine AddRec at the moment. For an affine AddRec,
916+
/// the monotonicity can be inferred from its nowrap property. For example, let
917+
/// X and Y be loop-invariant, and assume Y is non-negative. An AddRec
918+
/// {X,+.Y}<nsw> implies:
919+
///
920+
/// X <=s (X + Y) <=s ((X + Y) + Y) <=s ...
921+
///
922+
/// Thus, we can conclude that the AddRec is monotonically increasing with
923+
/// respect to the associated loop in a signed sense. The similar reasoning
924+
/// applies when Y is non-positive, leading to a monotonically decreasing
925+
/// AddRec.
926+
SCEVMonotonicity
927+
SCEVMonotonicityChecker::visitAddRecExpr(const SCEVAddRecExpr *Expr) {
928+
if (!Expr->isAffine() || !Expr->hasNoSignedWrap())
929+
return createUnknown(Expr);
930+
931+
const SCEV *Start = Expr->getStart();
932+
const SCEV *Step = Expr->getStepRecurrence(*SE);
933+
934+
SCEVMonotonicity StartMon = visit(Start);
935+
if (StartMon.isUnknown())
936+
return StartMon;
937+
938+
if (!isLoopInvariant(Step))
939+
return createUnknown(Expr);
940+
941+
return SCEVMonotonicity(SCEVMonotonicityType::MultivariateSignedMonotonic);
942+
}
943+
673944
//===----------------------------------------------------------------------===//
674945
// DependenceInfo methods
675946

@@ -3488,10 +3759,19 @@ bool DependenceInfo::tryDelinearize(Instruction *Src, Instruction *Dst,
34883759
// resize Pair to contain as many pairs of subscripts as the delinearization
34893760
// has found, and then initialize the pairs following the delinearization.
34903761
Pair.resize(Size);
3762+
SCEVMonotonicityChecker MonChecker(SE);
3763+
const Loop *OutermostLoop = SrcLoop ? SrcLoop->getOutermostLoop() : nullptr;
34913764
for (int I = 0; I < Size; ++I) {
34923765
Pair[I].Src = SrcSubscripts[I];
34933766
Pair[I].Dst = DstSubscripts[I];
34943767
unifySubscriptType(&Pair[I]);
3768+
3769+
if (EnableMonotonicityCheck) {
3770+
if (MonChecker.checkMonotonicity(Pair[I].Src, OutermostLoop).isUnknown())
3771+
return false;
3772+
if (MonChecker.checkMonotonicity(Pair[I].Dst, OutermostLoop).isUnknown())
3773+
return false;
3774+
}
34953775
}
34963776

34973777
return true;
@@ -3824,6 +4104,14 @@ DependenceInfo::depends(Instruction *Src, Instruction *Dst,
38244104
Pair[0].Src = SrcEv;
38254105
Pair[0].Dst = DstEv;
38264106

4107+
SCEVMonotonicityChecker MonChecker(SE);
4108+
const Loop *OutermostLoop = SrcLoop ? SrcLoop->getOutermostLoop() : nullptr;
4109+
if (EnableMonotonicityCheck)
4110+
if (MonChecker.checkMonotonicity(Pair[0].Src, OutermostLoop).isUnknown() ||
4111+
MonChecker.checkMonotonicity(Pair[0].Dst, OutermostLoop).isUnknown())
4112+
return std::make_unique<Dependence>(Src, Dst,
4113+
SCEVUnionPredicate(Assume, *SE));
4114+
38274115
if (Delinearize) {
38284116
if (tryDelinearize(Src, Dst, Pair)) {
38294117
LLVM_DEBUG(dbgs() << " delinearized\n");

0 commit comments

Comments
 (0)