Skip to content

Commit e3e2f5a

Browse files
committed
[Delinearization] Add function for fixed size array without relying on GEP
The existing functions `getIndexExpressionsFromGEP` and `tryDelinearizeFixedSizeImpl` provide functionality to delinearize memory accesses for fixed size array. They use the GEP source element type in their optimization heuristics. However, driving optimization heuristics based on GEP type information is not allowed. This patch introduces a new function `delinearizeFixedSizeArray` to remove them. This is an initial implementation that may not cover all cases, but is intended to replace the existing function in the future.
1 parent e8be733 commit e3e2f5a

File tree

3 files changed

+661
-2
lines changed

3 files changed

+661
-2
lines changed

llvm/include/llvm/Analysis/Delinearization.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ void delinearize(ScalarEvolution &SE, const SCEV *Expr,
112112
SmallVectorImpl<const SCEV *> &Subscripts,
113113
SmallVectorImpl<const SCEV *> &Sizes, const SCEV *ElementSize);
114114

115+
/// Split this SCEVAddRecExpr into two vectors of SCEVs representing the
116+
/// subscripts and sizes of an access to a fixed size array. This is a special
117+
/// case of delinearization for fixed size arrays.
118+
///
119+
/// The delinearization is a 2 step process: the first step estimates the sizes
120+
/// of each dimension of the array. The second step computes the access
121+
/// functions for the delinearized array:
122+
///
123+
/// 1. Compute the array size
124+
/// 2. Compute the access function: same as normal delinearization
125+
///
126+
/// Different from the normal delinearization, this function assumes that NO
127+
/// terms exist in the \p Expr. In other words, it assumes that the all step
128+
/// values are constant.
129+
///
130+
/// This function is intended to replace getIndexExpressionsFromGEP and
131+
/// tryDelinearizeFixedSizeImpl. They rely on the GEP source element type so
132+
/// that they will be removed in the future.
133+
void delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
134+
SmallVectorImpl<const SCEV *> &Subscripts,
135+
SmallVectorImpl<const SCEV *> &Sizes,
136+
const SCEV *ElementSize);
137+
115138
/// Gathers the individual index expressions from a GEP instruction.
116139
///
117140
/// This function optimistically assumes the GEP references into a fixed size

llvm/lib/Analysis/Delinearization.cpp

Lines changed: 192 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "llvm/IR/InstIterator.h"
2525
#include "llvm/IR/Instructions.h"
2626
#include "llvm/IR/PassManager.h"
27+
#include "llvm/Support/CommandLine.h"
2728
#include "llvm/Support/Debug.h"
2829
#include "llvm/Support/raw_ostream.h"
2930

@@ -32,6 +33,11 @@ using namespace llvm;
3233
#define DL_NAME "delinearize"
3334
#define DEBUG_TYPE DL_NAME
3435

36+
static cl::opt<bool> UseFixedSizeArrayHeuristic(
37+
"delinearize-use-fixed-size-array-heuristic", cl::init(false), cl::Hidden,
38+
cl::desc("When printing analysis, use the heuristic for fixed-size arrays "
39+
"if the default delinearizetion fails."));
40+
3541
// Return true when S contains at least an undef value.
3642
static inline bool containsUndefs(const SCEV *S) {
3743
return SCEVExprContains(S, [](const SCEV *S) {
@@ -480,6 +486,178 @@ void llvm::delinearize(ScalarEvolution &SE, const SCEV *Expr,
480486
});
481487
}
482488

489+
static std::optional<APInt> tryIntoAPInt(const SCEV *S) {
490+
if (const auto *Const = dyn_cast<SCEVConstant>(S))
491+
return Const->getAPInt();
492+
return std::nullopt;
493+
}
494+
495+
/// Collects the absolute values of constant steps for all induction variables.
496+
/// Returns true if we can prove that all step recurrences are constants and \p
497+
/// Expr is dividable by \p ElementSize. Each step recurrence is stored in \p
498+
/// Steps after divided by \p ElementSize.
499+
static bool collectConstantAbsSteps(ScalarEvolution &SE, const SCEV *Expr,
500+
SmallVectorImpl<unsigned> &Steps,
501+
unsigned ElementSize) {
502+
// End of recursion. The constant value also must be a multiple of
503+
// ElementSize.
504+
if (const auto *Const = dyn_cast<SCEVConstant>(Expr)) {
505+
const unsigned Mod = Const->getAPInt().urem(ElementSize);
506+
return Mod == 0;
507+
}
508+
509+
const SCEVAddRecExpr *AR = dyn_cast<SCEVAddRecExpr>(Expr);
510+
if (!AR || !AR->isAffine())
511+
return false;
512+
513+
const SCEV *Step = AR->getStepRecurrence(SE);
514+
std::optional<APInt> StepAPInt = tryIntoAPInt(Step);
515+
if (!StepAPInt)
516+
return false;
517+
518+
APInt Q;
519+
uint64_t R;
520+
APInt::udivrem(StepAPInt->abs(), ElementSize, Q, R);
521+
if (R != 0)
522+
return false;
523+
524+
// Bail out when the step is too large.
525+
std::optional<unsigned> StepVal = Q.tryZExtValue();
526+
if (!StepVal)
527+
return false;
528+
529+
Steps.push_back(*StepVal);
530+
return collectConstantAbsSteps(SE, AR->getStart(), Steps, ElementSize);
531+
}
532+
533+
static bool findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
534+
SmallVectorImpl<unsigned> &Sizes,
535+
const SCEV *ElementSize) {
536+
if (!ElementSize)
537+
return false;
538+
539+
std::optional<APInt> ElementSizeAPInt = tryIntoAPInt(ElementSize);
540+
if (!ElementSizeAPInt || *ElementSizeAPInt == 0)
541+
return false;
542+
543+
std::optional<unsigned> ElementSizeConst = ElementSizeAPInt->tryZExtValue();
544+
545+
// Early exit when ElementSize is not a positive constant.
546+
if (!ElementSizeConst)
547+
return false;
548+
549+
if (!collectConstantAbsSteps(SE, Expr, Sizes, *ElementSizeConst) ||
550+
Sizes.empty()) {
551+
Sizes.clear();
552+
return false;
553+
}
554+
555+
// At this point, Sizes contains the absolute step recurrences for all
556+
// induction variables. Each step recurrence must be a multiple of the size of
557+
// the array element. Assuming that the each value represents the size of an
558+
// array for each dimension, attempts to restore the length of each dimension
559+
// by dividing the step recurrence by the next smaller value. For example, if
560+
// we have the following AddRec SCEV:
561+
//
562+
// AddRec: {{{0,+,2048}<%for.i>,+,256}<%for.j>,+,8}<%for.k> (ElementSize=8)
563+
//
564+
// Then Sizes will become [256, 32, 1] after sorted. We don't know the size of
565+
// the outermost dimension, the next dimension will be computed as 256 / 32 =
566+
// 8, and the last dimension will be computed as 32 / 1 = 32. Thus it results
567+
// in like Arr[UnknownSize][8][32] with elements of size 8 bytes, where Arr is
568+
// a base pointer.
569+
//
570+
// TODO: Catch more cases, e.g., when a step recurrence is not dividable by
571+
// the next smaller one, like A[i][3*j].
572+
llvm::sort(Sizes.rbegin(), Sizes.rend());
573+
Sizes.erase(llvm::unique(Sizes), Sizes.end());
574+
for (unsigned I = 0; I + 1 < Sizes.size(); I++) {
575+
unsigned PrevSize = Sizes[I + 1];
576+
if (Sizes[I] % PrevSize) {
577+
Sizes.clear();
578+
return false;
579+
}
580+
Sizes[I] /= PrevSize;
581+
}
582+
583+
// The last element should be ElementSize.
584+
Sizes.back() = *ElementSizeConst;
585+
return true;
586+
}
587+
588+
/// Splits the SCEV into two vectors of SCEVs representing the subscripts and
589+
/// sizes of an array access, assuming that the array is a fixed size array.
590+
///
591+
/// E.g., if we have the code like as follows:
592+
///
593+
/// double A[42][8][32];
594+
/// for i
595+
/// for j
596+
/// for k
597+
/// use A[i][j][k]
598+
///
599+
/// The access function will be represented as an AddRec SCEV like:
600+
///
601+
/// AddRec: {{{0,+,2048}<%for.i>,+,256}<%for.j>,+,8}<%for.k> (ElementSize=8)
602+
///
603+
/// Then findFixedSizeArrayDimensions infers the size of each dimension of the
604+
/// array based on the fact that the value of the step recurrence is a multiple
605+
/// of the size of the corresponding array element. In the above example, it
606+
/// results in the following:
607+
///
608+
/// CHECK: ArrayDecl[UnknownSize][8][32] with elements of 8 bytes.
609+
///
610+
/// Finally each subscript will be computed as follows:
611+
///
612+
/// CHECK: ArrayRef[{0,+,1}<%for.i>][{0,+,1}<%for.j>][{0,+,1}<%for.k>]
613+
///
614+
/// Note that this function doesn't check the range of possible values for each
615+
/// subscript, so the caller should perform additional boundary checks if
616+
/// necessary.
617+
///
618+
/// TODO: At the moment, this function can handle only simple cases. For
619+
/// example, we cannot handle a case where a step recurrence is not dividable by
620+
/// the next smaller step recurrence, e.g., A[i][3*j]. Furthermore, this
621+
/// function doesn't guarantee that the original array size is restored
622+
/// "correctly". For example, in the following case:
623+
///
624+
/// double A[42][4][32];
625+
/// double B[42][8][64];
626+
/// for i
627+
/// for j
628+
/// for k
629+
/// use A[i][j][k]
630+
/// use B[i][2*j][k]
631+
///
632+
/// The access function for both accesses will be the same:
633+
///
634+
/// AddRec: {{{0,+,2048}<%for.i>,+,512}<%for.j>,+,8}<%for.k> (ElementSize=8)
635+
///
636+
/// The array sizes for both A and B will be computed as
637+
/// ArrayDecl[UnknownSize][4][64], which matches for A, but not for B.
638+
void llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
639+
SmallVectorImpl<const SCEV *> &Subscripts,
640+
SmallVectorImpl<const SCEV *> &Sizes,
641+
const SCEV *ElementSize) {
642+
643+
// First step: find the fixed array size.
644+
SmallVector<unsigned, 4> ConstSizes;
645+
if (!findFixedSizeArrayDimensions(SE, Expr, ConstSizes, ElementSize)) {
646+
Sizes.clear();
647+
return;
648+
}
649+
650+
// Convert the constant size to SCEV.
651+
for (unsigned Size : ConstSizes)
652+
Sizes.push_back(SE.getConstant(Expr->getType(), Size));
653+
654+
// Second step: compute the access functions for each subscript.
655+
computeAccessFunctions(SE, Expr, Subscripts, Sizes);
656+
657+
if (Subscripts.empty())
658+
return;
659+
}
660+
483661
bool llvm::getIndexExpressionsFromGEP(ScalarEvolution &SE,
484662
const GetElementPtrInst *GEP,
485663
SmallVectorImpl<const SCEV *> &Subscripts,
@@ -586,9 +764,21 @@ void printDelinearization(raw_ostream &O, Function *F, LoopInfo *LI,
586764
O << "AccessFunction: " << *AccessFn << "\n";
587765

588766
SmallVector<const SCEV *, 3> Subscripts, Sizes;
767+
768+
auto IsDelinearizationFailed = [&]() {
769+
return Subscripts.size() == 0 || Sizes.size() == 0 ||
770+
Subscripts.size() != Sizes.size();
771+
};
772+
589773
delinearize(*SE, AccessFn, Subscripts, Sizes, SE->getElementSize(&Inst));
590-
if (Subscripts.size() == 0 || Sizes.size() == 0 ||
591-
Subscripts.size() != Sizes.size()) {
774+
if (UseFixedSizeArrayHeuristic && IsDelinearizationFailed()) {
775+
Subscripts.clear();
776+
Sizes.clear();
777+
delinearizeFixedSizeArray(*SE, AccessFn, Subscripts, Sizes,
778+
SE->getElementSize(&Inst));
779+
}
780+
781+
if (IsDelinearizationFailed()) {
592782
O << "failed to delinearize\n";
593783
continue;
594784
}

0 commit comments

Comments
 (0)