MLIR 22.0.0git
IndexingUtils.h
Go to the documentation of this file.
1//===- IndexingUtils.h - Helpers related to index computations --*- C++ -*-===//
2//
3// Part of the LLVM Project, 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//===----------------------------------------------------------------------===//
8//
9// This header file defines utilities and common canonicalization patterns for
10// reshape operations.
11//
12//===----------------------------------------------------------------------===//
13
14#ifndef MLIR_DIALECT_UTILS_INDEXINGUTILS_H
15#define MLIR_DIALECT_UTILS_INDEXINGUTILS_H
16
17#include "mlir/IR/Builders.h"
18#include "mlir/Support/LLVM.h"
19#include "llvm/ADT/ArrayRef.h"
20#include "llvm/ADT/SmallVector.h"
21#include "llvm/ADT/iterator.h"
22#include <optional>
23#include <utility>
24
25namespace mlir {
26class ArrayAttr;
27
28//===----------------------------------------------------------------------===//
29// Utils that operate on static integer values.
30//===----------------------------------------------------------------------===//
31
32/// Given a set of sizes, return the suffix product.
33///
34/// When applied to slicing, this is the calculation needed to derive the
35/// strides (i.e. the number of linear indices to skip along the (k-1) most
36/// minor dimensions to get the next k-slice).
37///
38/// This is the basis to linearize an n-D offset confined to `[0 ... sizes]`.
39///
40/// Assuming `sizes` is `[s0, .. sn]`, return the vector<int64_t>
41/// `[s1 * ... * sn, s2 * ... * sn, ..., sn, 1]`.
42///
43/// `sizes` elements `s1` to `sn` are asserted to be non-negative.
44///
45/// Return an empty vector if `sizes` is empty.
50
51/// Return a vector containing llvm::zip_equal(v1, v2) multiplied elementwise.
52///
53/// Return an empty vector if `v1` and `v2` are empty.
55 ArrayRef<int64_t> v2);
56
57/// Self-explicit.
59
60/// Self-explicit.
62
63/// Return the number of elements of basis (i.e. the max linear index).
64/// Return `0` if `basis` is empty.
65///
66/// `basis` elements are asserted to be non-negative.
67///
68/// Return `0` if `basis` is empty.
70 return computeProduct(basis);
71}
72
73/// Return the linearized index of 'offsets' w.r.t. 'basis'.
74///
75/// `basis` elements are asserted to be non-negative.
76int64_t linearize(ArrayRef<int64_t> offsets, ArrayRef<int64_t> basis);
77
78/// Given the strides together with a linear index in the dimension space,
79/// return the vector-space offsets in each dimension for a de-linearized index.
80/// `strides` elements are asserted to be non-negative.
81///
82/// Let `li = linearIndex`, assuming `strides` are `[s0, .. sn]`, return the
83/// vector of int64_t
84/// `[li % s0, (li / s0) % s1, ..., (li / s0 / .. / sn-1) % sn]`
86 ArrayRef<int64_t> strides);
87
88/// Return the multi-dimensional integral ratio of `subShape` to the trailing
89/// dimensions of `shape`. This represents how many times `subShape` fits
90/// within `shape`. If integral division is not possible, return std::nullopt.
91/// The trailing `subShape.size()` entries of both shapes are assumed (and
92/// enforced) to only contain non-negative values.
93///
94/// Examples:
95/// - shapeRatio({3, 5, 8}, {2, 5, 2}) returns {3, 2, 1}.
96/// - shapeRatio({3, 8}, {2, 5, 2}) returns std::nullopt (subshape has
97/// higher
98/// rank).
99/// - shapeRatio({42, 2, 10, 32}, {2, 5, 2}) returns {42, 1, 2, 16} which is
100/// derived as {42(leading shape dim), 2/2, 10/5, 32/2}.
101/// - shapeRatio({42, 2, 11, 32}, {2, 5, 2}) returns std::nullopt which is
102/// derived as {42(leading shape dim), 2/2, 11/5(not divisible), 32/2}.
103std::optional<SmallVector<int64_t>>
104computeShapeRatio(ArrayRef<int64_t> shape, ArrayRef<int64_t> subShape);
105
106//===----------------------------------------------------------------------===//
107// Utils that operate on AffineExpr.
108//===----------------------------------------------------------------------===//
109
110/// Given a set of sizes, return the suffix product.
111///
112/// When applied to slicing, this is the calculation needed to derive the
113/// strides (i.e. the number of linear indices to skip along the (k-1) most
114/// minor dimensions to get the next k-slice).
115///
116/// This is the basis to linearize an n-D offset confined to `[0 ... sizes]`.
117///
118/// Assuming `sizes` is `[s0, .. sn]`, return the vector<AffineExpr>
119/// `[s1 * ... * sn, s2 * ... * sn, ..., sn, 1]`.
120///
121/// It is the caller's responsibility to pass proper AffineExpr kind that
122/// result in valid AffineExpr (i.e. cannot multiply 2 AffineDimExpr or divide
123/// by an AffineDimExpr).
124///
125/// `sizes` elements are expected to bind to non-negative values.
126///
127/// Return an empty vector if `sizes` is empty.
128SmallVector<AffineExpr> computeSuffixProduct(ArrayRef<AffineExpr> sizes);
132
133/// Return a vector containing llvm::zip_equal(v1, v2) multiplied elementwise.
134///
135/// It is the caller's responsibility to pass proper AffineExpr kind that
136/// result in valid AffineExpr (i.e. cannot multiply 2 AffineDimExpr or divide
137/// by an AffineDimExpr).
138///
139/// Return an empty vector if `v1` and `v2` are empty.
140SmallVector<AffineExpr> computeElementwiseMul(ArrayRef<AffineExpr> v1,
141 ArrayRef<AffineExpr> v2);
142
143/// Self-explicit.
144AffineExpr computeSum(MLIRContext *ctx, ArrayRef<AffineExpr> basis);
145
146/// Self-explicit.
147AffineExpr computeProduct(MLIRContext *ctx, ArrayRef<AffineExpr> basis);
148
149/// Return the number of elements of basis (i.e. the max linear index).
150/// Return `0` if `basis` is empty.
151///
152/// It is the caller's responsibility to pass proper AffineExpr kind that
153/// result in valid AffineExpr (i.e. cannot multiply 2 AffineDimExpr or divide
154/// by an AffineDimExpr).
155///
156/// `basis` elements are expected to bind to non-negative values.
157///
158/// Return the `0` AffineConstantExpr if `basis` is empty.
160 ArrayRef<AffineExpr> basis) {
161 return computeProduct(ctx, basis);
162}
163
164/// Return the linearized index of 'offsets' w.r.t. 'basis'.
165///
166/// Assuming `offsets` is `[o0, .. on]` and `basis` is `[b0, .. bn]`, return the
167/// AffineExpr `o0 * b0 + .. + on * bn`.
168///
169/// It is the caller's responsibility to pass proper AffineExpr kind that result
170/// in valid AffineExpr (i.e. cannot multiply 2 AffineDimExpr or divide by an
171/// AffineDimExpr).
172///
173/// `basis` elements are expected to bind to non-negative values.
174AffineExpr linearize(MLIRContext *ctx, ArrayRef<AffineExpr> offsets,
175 ArrayRef<AffineExpr> basis);
176AffineExpr linearize(MLIRContext *ctx, ArrayRef<AffineExpr> offsets,
177 ArrayRef<int64_t> basis);
178
179/// Given the strides together with a linear index in the dimension space,
180/// return the vector-space offsets in each dimension for a de-linearized index.
181///
182/// Let `li = linearIndex`, assuming `strides` are `[s0, .. sn]`, return the
183/// vector of AffineExpr
184/// `[li % s0, (li / s0) % s1, ..., (li / s0 / .. / sn-1) % sn]`
185///
186/// It is the caller's responsibility to pass proper AffineExpr kind that result
187/// in valid AffineExpr (i.e. cannot multiply 2 AffineDimExpr or divide by an
188/// AffineDimExpr).
189///
190/// `strides` elements are expected to bind to non-negative values.
191SmallVector<AffineExpr> delinearize(AffineExpr linearIndex,
192 ArrayRef<AffineExpr> strides);
193SmallVector<AffineExpr> delinearize(AffineExpr linearIndex,
194 ArrayRef<int64_t> strides);
195
196//===----------------------------------------------------------------------===//
197// Permutation utils.
198//===----------------------------------------------------------------------===//
199
200template <typename T>
202 ArrayRef<int64_t> permutation) {
203 assert(input.size() == permutation.size() &&
204 "expected input rank to equal permutation rank");
205 assert(
206 llvm::all_of(permutation, [&](size_t s) { return s < input.size(); }) &&
207 "permutation must be within input bounds");
208 auto permutationRange = llvm::map_range(
209 llvm::seq<unsigned>(0, input.size()),
210 [&](int64_t idx) -> T { return input[permutation[idx]]; });
211 return llvm::to_vector(permutationRange);
212}
213
214template <typename T>
216 ArrayRef<int64_t> permutation) {
217 return applyPermutation(ArrayRef(input), permutation);
218}
219
220/// Apply the permutation defined by `permutation` to `inVec`.
221/// Element `i` in `inVec` is mapped to location `j = permutation[i]`.
222/// E.g.: for an input vector `inVec = ['a', 'b', 'c']` and a permutation
223/// vector `permutation = [2, 0, 1]`, this function leaves `inVec = ['c', 'a',
224/// 'b']`.
225template <typename T, unsigned N>
227 ArrayRef<int64_t> permutation) {
228 inVec = applyPermutation(inVec, permutation);
229}
230
231/// Helper method to apply to inverse a permutation.
232SmallVector<int64_t> invertPermutationVector(ArrayRef<int64_t> permutation);
233
234/// Returns true if `permutation` is an identity permutation.
235bool isIdentityPermutation(ArrayRef<int64_t> permutation);
236
237/// Method to check if an interchange vector is a permutation.
238bool isPermutationVector(ArrayRef<int64_t> interchange);
239
240/// Return a permutation vector of size permSize that would result in moving
241/// positions into desiredPositions.
242///
243/// For example, permSize == 5, positions = {2, 4}, desiredPositions = {1, 0}
244/// would result in a {4, 2, 0, 1, 3} permutation vector.
246computePermutationVector(int64_t permSize, ArrayRef<int64_t> positions,
247 ArrayRef<int64_t> desiredPositions);
248
249/// Returns a permutation vector that drop the input dims in
250/// dropPositions from inputPerm.
251///
252/// For example, inputPerm = {2, 4, 0, 1, 3} and dropPositions= {1, 2} would
253/// result in a {2, 0, 1} permutation vector.
254SmallVector<int64_t> dropDims(ArrayRef<int64_t> inputPerm,
255 ArrayRef<int64_t> dropPositions);
256
257/// Helper to return a subset of `arrayAttr` as a vector of int64_t.
258// TODO: Port everything relevant to DenseArrayAttr and drop this util.
260 unsigned dropBack = 0);
261
262/// Compute linear index from provided strides and indices, assuming strided
263/// layout.
264/// Returns AffineExpr and list of values to apply to it, e.g.:
265///
266/// auto &&[expr, values] = computeLinearIndex(...);
267/// offset = affine::makeComposedFoldedAffineApply(builder, loc, expr, values);
268std::pair<AffineExpr, SmallVector<OpFoldResult>>
269computeLinearIndex(OpFoldResult sourceOffset, ArrayRef<OpFoldResult> strides,
270 ArrayRef<OpFoldResult> indices);
271std::pair<AffineExpr, SmallVector<OpFoldResult>>
272computeLinearIndex(OpFoldResult sourceOffset, ArrayRef<int64_t> strides,
273 ArrayRef<Value> indices);
274
275//===----------------------------------------------------------------------===//
276// Utilities for decomposing larger shapes
277//===----------------------------------------------------------------------===//
278
279namespace detail {
280/// Encapsulates the set of parameters that are used to make tile offset
281/// calculations in the TileOffsetRangeIterator.
283public:
285 ArrayRef<int64_t> loopOrder);
286
287 int64_t getMaxLinearIndex() const { return maxLinearIndex; }
288
290
292
293 template <typename T>
294 SmallVector<T> getTileOffsets(T linearIndex) const {
295 if constexpr (std::is_same_v<T, int64_t>)
296 return getStaticTileOffsets(linearIndex);
297 else
298 return getDynamicTileOffsets(linearIndex);
299 }
300
301 size_t getRank() const { return tileShape.size(); }
302
303private:
304 /// The sub-shape that divides the larger outer shape (which is provided to
305 /// the constructor).
306 SmallVector<int64_t> tileShape;
307 /// The inverse permutation to the `loopOrder` permutation provided in the
308 /// constructor.
309 SmallVector<int64_t> inverseLoopOrder;
310 /// The strides for the basis 'div(shape, tileShape)' permuted by `loopOrder`.
311 SmallVector<int64_t> sliceStrides;
312 /// The maximum linear index in the iteration space given by basis 'div(shape,
313 /// tileShape)'.
314 int64_t maxLinearIndex;
315};
316
317/// The STL-style iterator implementation for StaticTileOffsetRange.
318template <typename ElementType>
320 : public llvm::iterator_facade_base<TileOffsetRangeIterator<ElementType>,
321 std::forward_iterator_tag,
322 SmallVector<ElementType>> {
323public:
324 TileOffsetRangeIterator(const TileOffsetRangeImpl &params, ElementType index)
325 : params(params), index(index) {}
326
327 void operator++() { incrementIndex(1); }
329 const auto copy = *this;
330 ++*this;
331 return copy;
332 }
333
334 bool operator==(const TileOffsetRangeIterator &other) const {
335 return index == other.index;
336 }
337 bool operator!=(const TileOffsetRangeIterator &other) const {
338 return index != other.index;
339 }
340
342 return params.getTileOffsets(index);
343 }
344 void operator+=(int64_t offset) { incrementIndex(offset); }
345
346private:
347 void incrementIndex(int64_t offset) { index = index + offset; }
348 const TileOffsetRangeImpl params;
350};
351} // namespace detail
352
353/// A range-style iterator that allows for iterating over the offsets of all
354/// potential tiles of size `tileShape` within the larger shape `shape`, using
355/// an ordering specified by `loopOrder`. The `loopOrder` specifies the order of
356/// unrolling by numbering the dimensions in order from "outer most for loop"
357/// (slowest changing) to "inner most for loop" (fastest changing).
358///
359/// For example, for `shape = {10, 20, 30}`, `tileShape = {5, 10, 15}`, and
360/// `loopOrder={2, 0, 1}`, the iterating over this range will yield offsets:
361///
362/// ```
363/// {0, 0, 0}, {0, 10, 0}, {5, 0, 0}, {5, 10, 0}, {0, 0, 15},
364/// {0, 10, 15}, {5, 0, 15}, {0, 10, 15}, {5, 10, 15}
365/// ```
366///
367/// This is useful in contexts where a vector computation over a larger shape
368/// needs to be unrolled to a set of operations on subsets of the original
369/// operands, such as during the "vector unrolling" transformations.
370///
371/// The size of `tileShape` must be less-than-or-equal-to the size of `shape`.a
372/// If the rank of `tileShape` is smaller than `shape`, then `tileShape`
373/// elements correspond to the trailing dimensions of `shape`, and the leading
374/// dimensions are considered untiled and `tileShape` is effectively prepended
375/// with the leading dims of `shape`.
377public:
380
382 ArrayRef<int64_t> loopOrder)
383 : params(shape, tileShape, loopOrder), beginValue(params, 0),
384 pastEndValue(params, params.getMaxLinearIndex()) {
385 assert(shape.size() >= tileShape.size());
386 assert(loopOrder.size() == shape.size());
387 }
388
389 /// Create the range with identity loop order.
391 : params(shape, tileShape,
392 llvm::to_vector(llvm::seq<int64_t>(0, shape.size()))),
393 beginValue(params, 0),
394 pastEndValue(params, params.getMaxLinearIndex()) {
395 assert(shape.size() >= tileShape.size());
396 }
397
398 IteratorTy begin() const { return beginValue; }
399 IteratorTy end() const { return pastEndValue; }
400
401 /// Returns the total number of tiles that fit in the larger shape.
402 size_t size() const { return params.getMaxLinearIndex(); }
403
404 /// Returns rank of the iterator's shape.
405 size_t getRank() const { return params.getRank(); }
406
407private:
408 const ParamsTy params;
409 IteratorTy beginValue;
410 IteratorTy pastEndValue;
411};
412} // namespace mlir
413
414#endif // MLIR_DIALECT_UTILS_INDEXINGUTILS_H
void dropFront(int64_t arr[N], int64_t *res)
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
ArrayAttr()
Base type for affine expression.
Definition AffineExpr.h:68
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
size_t size() const
Returns the total number of tiles that fit in the larger shape.
size_t getRank() const
Returns rank of the iterator's shape.
StaticTileOffsetRange(ArrayRef< int64_t > shape, ArrayRef< int64_t > tileShape, ArrayRef< int64_t > loopOrder)
detail::TileOffsetRangeIterator< int64_t > IteratorTy
detail::TileOffsetRangeImpl ParamsTy
StaticTileOffsetRange(ArrayRef< int64_t > shape, ArrayRef< int64_t > tileShape)
Create the range with identity loop order.
Encapsulates the set of parameters that are used to make tile offset calculations in the TileOffsetRa...
SmallVector< T > getTileOffsets(T linearIndex) const
SmallVector< int64_t > getStaticTileOffsets(int64_t linearIndex) const
TileOffsetRangeImpl(ArrayRef< int64_t > shape, ArrayRef< int64_t > tileShape, ArrayRef< int64_t > loopOrder)
SmallVector< AffineExpr > getDynamicTileOffsets(AffineExpr linearIndex) const
The STL-style iterator implementation for StaticTileOffsetRange.
TileOffsetRangeIterator(const TileOffsetRangeImpl &params, ElementType index)
bool operator!=(const TileOffsetRangeIterator &other) const
SmallVector< ElementType > operator*() const
TileOffsetRangeIterator operator++(int)
bool operator==(const TileOffsetRangeIterator &other) const
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition CallGraph.h:229
Include the generated interface declarations.
SmallVector< int64_t > computeElementwiseMul(ArrayRef< int64_t > v1, ArrayRef< int64_t > v2)
Return a vector containing llvm::zip_equal(v1, v2) multiplied elementwise.
SmallVector< int64_t > computeStrides(ArrayRef< int64_t > sizes)
std::pair< AffineExpr, SmallVector< OpFoldResult > > computeLinearIndex(OpFoldResult sourceOffset, ArrayRef< OpFoldResult > strides, ArrayRef< OpFoldResult > indices)
Compute linear index from provided strides and indices, assuming strided layout.
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
SmallVector< int64_t > delinearize(int64_t linearIndex, ArrayRef< int64_t > strides)
Given the strides together with a linear index in the dimension space, return the vector-space offset...
int64_t computeProduct(ArrayRef< int64_t > basis)
Self-explicit.
bool isIdentityPermutation(ArrayRef< int64_t > permutation)
Returns true if permutation is an identity permutation.
SmallVector< int64_t > computePermutationVector(int64_t permSize, ArrayRef< int64_t > positions, ArrayRef< int64_t > desiredPositions)
Return a permutation vector of size permSize that would result in moving positions into desiredPositi...
SmallVector< int64_t > getI64SubArray(ArrayAttr arrayAttr, unsigned dropFront=0, unsigned dropBack=0)
Helper to return a subset of arrayAttr as a vector of int64_t.
SmallVector< int64_t > computeSuffixProduct(ArrayRef< int64_t > sizes)
Given a set of sizes, return the suffix product.
int64_t computeMaxLinearIndex(ArrayRef< int64_t > basis)
Return the number of elements of basis (i.e.
int64_t linearize(ArrayRef< int64_t > offsets, ArrayRef< int64_t > basis)
Return the linearized index of 'offsets' w.r.t.
std::optional< SmallVector< int64_t > > computeShapeRatio(ArrayRef< int64_t > shape, ArrayRef< int64_t > subShape)
Return the multi-dimensional integral ratio of subShape to the trailing dimensions of shape.
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
int64_t computeSum(ArrayRef< int64_t > basis)
Self-explicit.
SmallVector< int64_t > dropDims(ArrayRef< int64_t > inputPerm, ArrayRef< int64_t > dropPositions)
Returns a permutation vector that drop the input dims in dropPositions from inputPerm.
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.