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