Skip to content

Commit a1d611b

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 a1d611b

File tree

3 files changed

+644
-2
lines changed

3 files changed

+644
-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: 175 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,161 @@ 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 induction variables. Each step recurrence must be a multiple of the size of the array element. Assuming that the each value represents the size of an array for each dimension, attempts to restore the length of each dimension by dividing the step recurrence by the next smaller value. For example, if we have the following AddRec SCEV:
556+
//
557+
// AddRec: {{{0,+,2048}<%for.i>,+,256}<%for.j>,+,8}<%for.k> (ElementSize=8)
558+
//
559+
// Then Sizes will become [256, 32, 1] after sorted. We don't know the size of the outermost dimension, the next dimension will be computed as 256 / 32 = 8, and the last dimension will be computed as 32 / 1 = 32. Thus it results in like Arr[UnknownSize][8][32] with elements of size 8 bytes, where Arr is a base pointer.
560+
//
561+
// TODO: Catch more cases, e.g., when a step recurrence is not dividable by the next smaller one, like A[i][3*j].
562+
llvm::sort(Sizes.rbegin(), Sizes.rend());
563+
Sizes.erase(llvm::unique(Sizes), Sizes.end());
564+
for (unsigned I = 0; I + 1 < Sizes.size(); I++) {
565+
unsigned PrevSize = Sizes[I + 1];
566+
if (Sizes[I] % PrevSize) {
567+
Sizes.clear();
568+
return false;
569+
}
570+
Sizes[I] /= PrevSize;
571+
}
572+
573+
// The last element should be ElementSize.
574+
Sizes.back() = *ElementSizeConst;
575+
return true;
576+
}
577+
578+
/// Splits the SCEV into two vectors of SCEVs representing the subscripts and
579+
/// sizes of an array access, assuming that the array is a fixed size array.
580+
///
581+
/// E.g., if we have the code like as follows:
582+
///
583+
/// double A[42][8][32];
584+
/// for i
585+
/// for j
586+
/// for k
587+
/// use A[i][j][k]
588+
///
589+
/// The access function will be represented as an AddRec SCEV like:
590+
///
591+
/// AddRec: {{{0,+,2048}<%for.i>,+,256}<%for.j>,+,8}<%for.k> (ElementSize=8)
592+
///
593+
/// Then findFixedSizeArrayDimensions infers the size of each dimension of the array based on the fact that the value of the step recurrence is a multiple of the size of the corresponding array element. In the above example, it results in the following:
594+
///
595+
/// CHECK: ArrayDecl[UnknownSize][8][32] with elements of 8 bytes.
596+
///
597+
/// Finally each subscript will be computed as follows:
598+
///
599+
/// CHECK: ArrayRef[{0,+,1}<%for.i>][{0,+,1}<%for.j>][{0,+,1}<%for.k>]
600+
///
601+
/// Note that this function doesn't check the range of possible values for each
602+
/// subscript, so the caller should perform additional boundary checks if
603+
/// necessary.
604+
///
605+
/// TODO: At the moment, this function can handle only simple cases. For example, we cannot handle a case where a step recurrence is not dividable by the next smaller step recurrence, e.g., A[i][3*j]. Furthermore, this function doesn't guarantee that the original array size is restored "correctly". For example, in the following case:
606+
///
607+
/// double A[42][4][32];
608+
/// double B[42][8][64];
609+
/// for i
610+
/// for j
611+
/// for k
612+
/// use A[i][j][k]
613+
/// use B[i][2*j][k]
614+
///
615+
/// The access function for both accesses will be the same:
616+
///
617+
/// AddRec: {{{0,+,2048}<%for.i>,+,512}<%for.j>,+,8}<%for.k> (ElementSize=8)
618+
///
619+
/// The array sizes for both A and B will be computed as
620+
/// ArrayDecl[UnknownSize][4][64], which matches for A, but not for B.
621+
void llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
622+
SmallVectorImpl<const SCEV *> &Subscripts,
623+
SmallVectorImpl<const SCEV *> &Sizes,
624+
const SCEV *ElementSize) {
625+
626+
// First step: find the fixed array size.
627+
SmallVector<unsigned, 4> ConstSizes;
628+
if (!findFixedSizeArrayDimensions(SE, Expr, ConstSizes, ElementSize)) {
629+
Sizes.clear();
630+
return;
631+
}
632+
633+
// Convert the constant size to SCEV.
634+
for (unsigned Size : ConstSizes)
635+
Sizes.push_back(SE.getConstant(Expr->getType(), Size));
636+
637+
// Second step: compute the access functions for each subscript.
638+
computeAccessFunctions(SE, Expr, Subscripts, Sizes);
639+
640+
if (Subscripts.empty())
641+
return;
642+
}
643+
483644
bool llvm::getIndexExpressionsFromGEP(ScalarEvolution &SE,
484645
const GetElementPtrInst *GEP,
485646
SmallVectorImpl<const SCEV *> &Subscripts,
@@ -586,9 +747,21 @@ void printDelinearization(raw_ostream &O, Function *F, LoopInfo *LI,
586747
O << "AccessFunction: " << *AccessFn << "\n";
587748

588749
SmallVector<const SCEV *, 3> Subscripts, Sizes;
750+
751+
auto IsDelinearizationFailed = [&]() {
752+
return Subscripts.size() == 0 || Sizes.size() == 0 ||
753+
Subscripts.size() != Sizes.size();
754+
};
755+
589756
delinearize(*SE, AccessFn, Subscripts, Sizes, SE->getElementSize(&Inst));
590-
if (Subscripts.size() == 0 || Sizes.size() == 0 ||
591-
Subscripts.size() != Sizes.size()) {
757+
if (UseFixedSizeArrayHeuristic && IsDelinearizationFailed()) {
758+
Subscripts.clear();
759+
Sizes.clear();
760+
delinearizeFixedSizeArray(*SE, AccessFn, Subscripts, Sizes,
761+
SE->getElementSize(&Inst));
762+
}
763+
764+
if (IsDelinearizationFailed()) {
592765
O << "failed to delinearize\n";
593766
continue;
594767
}

0 commit comments

Comments
 (0)