Skip to content

Commit bd39ae6

Browse files
authored
[Delinearization] Add function for fixed size array without relying on GEP (#145050)
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 new functions `findFixedSizeArrayDimensions` and `delinearizeFixedSizeArray` to delinearize a fixed size array without using the type information in GEP. The new function `findFixedSizeArrayDimensions` infers the size of each dimension of the array based on the value to be added to the address as induction variables are incremented. `delinearizeFixedSizeArray` attempts to restore the subscripts of each dimension based on the estimated array size. This is an initial implementation that may not cover all cases, but is intended to replace the existing function in the future. Related: - https://discourse.llvm.org/t/enabling-loop-interchange/82589/4 - #124911 (comment)
1 parent 92f6b15 commit bd39ae6

File tree

3 files changed

+726
-2
lines changed

3 files changed

+726
-2
lines changed

llvm/include/llvm/Analysis/Delinearization.h

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

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

llvm/lib/Analysis/Delinearization.cpp

Lines changed: 198 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,184 @@ 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 divisible 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<uint64_t> &Steps,
501+
uint64_t 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 uint64_t 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<uint64_t> 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+
bool llvm::findFixedSizeArrayDimensions(ScalarEvolution &SE, const SCEV *Expr,
534+
SmallVectorImpl<uint64_t> &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<uint64_t> 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 divisible 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+
575+
// The last element in Sizes should be ElementSize. At this point, all values
576+
// in Sizes are assumed to be divided by ElementSize, so replace it with 1.
577+
assert(Sizes.back() != 0 && "Unexpected zero size in Sizes.");
578+
Sizes.back() = 1;
579+
580+
for (unsigned I = 0; I + 1 < Sizes.size(); I++) {
581+
uint64_t PrevSize = Sizes[I + 1];
582+
if (Sizes[I] % PrevSize) {
583+
Sizes.clear();
584+
return false;
585+
}
586+
Sizes[I] /= PrevSize;
587+
}
588+
589+
// Finally, the last element in Sizes should be ElementSize.
590+
Sizes.back() = *ElementSizeConst;
591+
return true;
592+
}
593+
594+
/// Splits the SCEV into two vectors of SCEVs representing the subscripts and
595+
/// sizes of an array access, assuming that the array is a fixed size array.
596+
///
597+
/// E.g., if we have the code like as follows:
598+
///
599+
/// double A[42][8][32];
600+
/// for i
601+
/// for j
602+
/// for k
603+
/// use A[i][j][k]
604+
///
605+
/// The access function will be represented as an AddRec SCEV like:
606+
///
607+
/// AddRec: {{{0,+,2048}<%for.i>,+,256}<%for.j>,+,8}<%for.k> (ElementSize=8)
608+
///
609+
/// Then findFixedSizeArrayDimensions infers the size of each dimension of the
610+
/// array based on the fact that the value of the step recurrence is a multiple
611+
/// of the size of the corresponding array element. In the above example, it
612+
/// results in the following:
613+
///
614+
/// CHECK: ArrayDecl[UnknownSize][8][32] with elements of 8 bytes.
615+
///
616+
/// Finally each subscript will be computed as follows:
617+
///
618+
/// CHECK: ArrayRef[{0,+,1}<%for.i>][{0,+,1}<%for.j>][{0,+,1}<%for.k>]
619+
///
620+
/// Note that this function doesn't check the range of possible values for each
621+
/// subscript, so the caller should perform additional boundary checks if
622+
/// necessary.
623+
///
624+
/// Also note that this function doesn't guarantee that the original array size
625+
/// is restored "correctly". For example, in the following case:
626+
///
627+
/// double A[42][4][64];
628+
/// double B[42][8][32];
629+
/// for i
630+
/// for j
631+
/// for k
632+
/// use A[i][j][k]
633+
/// use B[i][2*j][k]
634+
///
635+
/// The access function for both accesses will be the same:
636+
///
637+
/// AddRec: {{{0,+,2048}<%for.i>,+,512}<%for.j>,+,8}<%for.k> (ElementSize=8)
638+
///
639+
/// The array sizes for both A and B will be computed as
640+
/// ArrayDecl[UnknownSize][4][64], which matches for A, but not for B.
641+
///
642+
/// TODO: At the moment, this function can handle only simple cases. For
643+
/// example, we cannot handle a case where a step recurrence is not divisible
644+
/// by the next smaller step recurrence, e.g., A[i][3*j].
645+
bool llvm::delinearizeFixedSizeArray(ScalarEvolution &SE, const SCEV *Expr,
646+
SmallVectorImpl<const SCEV *> &Subscripts,
647+
SmallVectorImpl<const SCEV *> &Sizes,
648+
const SCEV *ElementSize) {
649+
650+
// First step: find the fixed array size.
651+
SmallVector<uint64_t, 4> ConstSizes;
652+
if (!findFixedSizeArrayDimensions(SE, Expr, ConstSizes, ElementSize)) {
653+
Sizes.clear();
654+
return false;
655+
}
656+
657+
// Convert the constant size to SCEV.
658+
for (uint64_t Size : ConstSizes)
659+
Sizes.push_back(SE.getConstant(Expr->getType(), Size));
660+
661+
// Second step: compute the access functions for each subscript.
662+
computeAccessFunctions(SE, Expr, Subscripts, Sizes);
663+
664+
return !Subscripts.empty();
665+
}
666+
483667
bool llvm::getIndexExpressionsFromGEP(ScalarEvolution &SE,
484668
const GetElementPtrInst *GEP,
485669
SmallVectorImpl<const SCEV *> &Subscripts,
@@ -586,9 +770,21 @@ void printDelinearization(raw_ostream &O, Function *F, LoopInfo *LI,
586770
O << "AccessFunction: " << *AccessFn << "\n";
587771

588772
SmallVector<const SCEV *, 3> Subscripts, Sizes;
773+
774+
auto IsDelinearizationFailed = [&]() {
775+
return Subscripts.size() == 0 || Sizes.size() == 0 ||
776+
Subscripts.size() != Sizes.size();
777+
};
778+
589779
delinearize(*SE, AccessFn, Subscripts, Sizes, SE->getElementSize(&Inst));
590-
if (Subscripts.size() == 0 || Sizes.size() == 0 ||
591-
Subscripts.size() != Sizes.size()) {
780+
if (UseFixedSizeArrayHeuristic && IsDelinearizationFailed()) {
781+
Subscripts.clear();
782+
Sizes.clear();
783+
delinearizeFixedSizeArray(*SE, AccessFn, Subscripts, Sizes,
784+
SE->getElementSize(&Inst));
785+
}
786+
787+
if (IsDelinearizationFailed()) {
592788
O << "failed to delinearize\n";
593789
continue;
594790
}

0 commit comments

Comments
 (0)