MLIR-AIE
IntervalReuse.h
Go to the documentation of this file.
1//===- IntervalReuse.h - AIE Vector Data Reuse Computation ------*- C++ -*-===//
2//
3// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// (c) Copyright 2022 Xilinx Inc.
8//
9//===----------------------------------------------------------------------===//
10// Class to compute potential data reuse in AIE vectors
11//===----------------------------------------------------------------------===//
12
13#ifndef AIE_DIALECT_AIEVEC_TRANSFORMS_INTERVALREUSE_H
14#define AIE_DIALECT_AIEVEC_TRANSFORMS_INTERVALREUSE_H
15
17
18#include <utility>
19
20namespace xilinx::aievec {
21
22// The IntervalReuse class.
23// This class captures the potential data reuse in the AIE vector. Each load
24// from memory (coming from transfer_read op) will exclusively belong to one of
25// the IntervalReuse object. Note that in AIE, the minimum vector size is
26// 128-bit, and they are aligned to the vector size boundary.
27// Let A and B be NxN arrays of 32-bit values. We assume no aliasing. Consider
28// the following three cases, where we want to determine which IntervalReuse
29// object the two read op accesses within the same loop nest will be mapped to:
30// 1. Accesses A[i][j:j+8] and A[i][j+1:j+9]: they exhibit data reuse if we load
31// the range A[j:j+16] from memory into a 512-bit vector. Therefore, these two
32// read ops will belong to the same IntervalReuse object.
33// 2. Accesses A[i][j:j+8] and A[i+1][j:j+8]: there is no non-trivial
34// vector-level reuse possible for these accesses, and they must belong to
35// different IntervalReuse objects.
36// 3. Accesses A[i][j:j+8] and B[i][j:j+8]: there is no possible reuse, since
37// the read ops access different arrays. Therefore, these two read ops must
38// belong to different IntervalReuse objects.
39// We want to correctly map read ops to IntervalReuse object in all these three
40// cases.
41//
42// The class contains 'memref' and 'base' to disambiguate reuse. 'memref'
43// differentiates different arrays. For example, 'memref' for arrays A and B
44// will be different. For accesses coming from the same array, 'base' helps in
45// disambiguation. 'base' is just the linearized base expression of the access.
46// The linearized expression for A[i][j] is i*N+j. We decompose it into base
47// (i*N+j), and offset 0. In contrast, the linearized expression for
48// A[i+1][j+2] is (i+1)*N+j+2, and we decompose it into base (i+1)*N+j, and
49// offset 2. Basically, we abstract away the constant offset from the
50// linearized access expression to form the base.
51// Given a vector of IntervalReuse objects, we just search for an object with
52// matching 'memref' and 'base' to group the read ops that can potentially
53// reuse data in vector.
54//
55// 'extentMap' stores the extent of data that an op reads. We store the extent
56// in bits. For example, the extent for operation reading A[i][j:j+8] is
57// [0,256].
58// The 'read access extent' corresponds to the aligned chunk of data that an
59// operation loads. For example, an 8-lane, 32-bit vector load from A[i+7:i+15]
60// would have read access extent [0:512], whereas under same conditions, the
61// vector load from A[i+9:i+17] would have read access extent [512:768]. Note
62// how the extents are aligned to vector size boundary.
63//
64// 'intervals' merges overlapping intervals to give the view of actual AIE
65// vectors that need to be created. Since AIE only allows aligned vector loads,
66// each interval is aligned to vector size. Continuing with the previous
67// example, the two extents [0,256] and [128,512] overlap. Therefore these will
68// be merged together to form a single interval [0,512]. The entries into
69// 'intervals' are sorted, so given an op, we can find its interval by doing
70// binary search with the op's extent as key (e.g, searching for [128,256] in
71// {[0,512],[512,1024]}).
72
74 // differentiate arrays (e.g., A vs. B)
75 mlir::Value memref;
76 // differentiate accesses coming from the same array, but with different base
77 // expression along the non-vectorized dimension (e.g., A[i+1][j:j+8] vs.
78 // A[i][j:j+8];
79 mlir::AffineExpr base;
80 // A map from each read operation to the extent of bits it reads (aligned to
81 // vector size).
82 llvm::DenseMap<mlir::Operation *, std::pair<int32_t, int32_t>> extentMap;
83 // Disjoint intervals of all the data accesses (i.e., read bits). Each
84 // interval entry corresponds to memory load into an AIE vec.
85 llvm::SmallVector<std::pair<int32_t, int32_t>, 8> intervals;
86 // Identify all the vectors that are only used as LHS operands of mul/mac op.
87 // The LHS operand of mul/mac ops have specific size requirement.
88 llvm::SmallVector<bool, 8> vecIsLHSOperand;
89
90 // Return true if this array access comes from the same array
91 bool sameMemRef(mlir::Value m) { return memref == m; }
92 // Return true if this array access has the same invariant base
93 // expression.
94 bool sameInvariantIndices(mlir::AffineExpr b) { return base == b; }
95 // Return true if this array access is enclosed within the same loop nest as
96 // other accesses belonging to the same IntervalReuse object.
97 bool sameEnclosingLoops(
98 mlir::Operation *op,
99 llvm::DenseMap<mlir::Block *, llvm::SmallVector<mlir::Operation *, 8>>
100 &blockToEnclosingLoops);
101 // For an operation, get the index into intervals that subsumes the
102 // operation's access extent.
103 size_t getIntervalIndex(mlir::Operation *op);
104
105public:
106 // Return true if this read operation has a potential data reuse with other
107 // read operations in this IntervalReuse.
108 bool potentialReuse(
109 mlir::vector::TransferReadOp readOp, mlir::AffineExpr invariantBase,
110 llvm::DenseMap<mlir::Block *, llvm::SmallVector<mlir::Operation *, 8>>
111 &blockToEnclosingLoops);
112 // Insert the access extent of this read operation into intervals
113 void insertInterval(mlir::vector::TransferReadOp readOp,
114 llvm::DenseMap<mlir::Operation *, IntervalReuse *>
115 &dataAccessToIntervalMap,
116 int32_t offset, int32_t forLoopStepSize,
117 bool isSplat = false,
118 unsigned minVecSize = 128 /*min AIE vec size*/);
119 // For a read operation, return the width of the interval its access extent
120 // belongs to. The interval width corresponds to the size of the vector that
121 // will hold the load from read operation.
122 int32_t getIntervalWidth(mlir::Operation *op);
123 // Get the read access extent of this read operation. The returned value
124 // indicates the start and end offsets of the access from the base (in bits).
125 std::pair<int32_t, int32_t> getAccessExtent(mlir::Operation *op);
126 // Set the read access extent of this read operation.
127 void setAccessExtent(mlir::Operation *op,
128 std::pair<int32_t, int32_t> &extent);
129 // Get the interval that contains this read operation's extent
130 std::pair<int32_t, int32_t> getInterval(mlir::Operation *op);
131 // Given that the read operation 'op' is only LHS operand of some mul/mac
132 // op, mark the vector that will load its access extent.
133 void markLHSOperandVec(mlir::Operation *op);
134 // If the interval corresponds to a vector that is marked as the exclusive
135 // LHS operand of some mul/mac op, and if its size is <= 256, try to coalesce
136 // it with the next interval.
137 void coalesceIntervals();
138 // Constructors
139 IntervalReuse(mlir::vector::TransferReadOp readOp, mlir::AffineExpr b)
140 : memref(readOp.getSource()), base(b) {}
141 IntervalReuse() : memref(nullptr), base(nullptr) {}
142};
143
144} // namespace xilinx::aievec
145// end namespace xilinx
146
147#endif // AIE_DIALECT_AIEVEC_TRANSFORMS_INTERVALREUSE_H
void insertInterval(mlir::vector::TransferReadOp readOp, llvm::DenseMap< mlir::Operation *, IntervalReuse * > &dataAccessToIntervalMap, int32_t offset, int32_t forLoopStepSize, bool isSplat=false, unsigned minVecSize=128)
void setAccessExtent(mlir::Operation *op, std::pair< int32_t, int32_t > &extent)
bool potentialReuse(mlir::vector::TransferReadOp readOp, mlir::AffineExpr invariantBase, llvm::DenseMap< mlir::Block *, llvm::SmallVector< mlir::Operation *, 8 > > &blockToEnclosingLoops)
int32_t getIntervalWidth(mlir::Operation *op)
std::pair< int32_t, int32_t > getAccessExtent(mlir::Operation *op)
IntervalReuse(mlir::vector::TransferReadOp readOp, mlir::AffineExpr b)
std::pair< int32_t, int32_t > getInterval(mlir::Operation *op)
void markLHSOperandVec(mlir::Operation *op)