MLIR  21.0.0git
PadTilingInterface.cpp
Go to the documentation of this file.
1 //===- PaddingTilingInterface.cpp - Padding of TilingInterface ops --------===//
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 
10 
15 #include "mlir/IR/AffineExpr.h"
18 #include "mlir/IR/BuiltinTypes.h"
19 #include "mlir/IR/OpDefinition.h"
20 #include "mlir/IR/Value.h"
22 #include "llvm/ADT/STLExtras.h"
23 #include "llvm/Support/Casting.h"
24 
25 #define DEBUG_TYPE "pad-tiling-interface"
26 
27 using namespace mlir;
28 using namespace mlir::linalg;
29 using namespace mlir::tensor;
30 
31 #define DBGS() (llvm::dbgs() << "[" DEBUG_TYPE << "]: ")
32 #define DBGSNL() (llvm::dbgs() << "\n")
33 
34 /// Form a "full-rank" padding specification so that the application is easy.
38  SmallVector<OpFoldResult> paddingSizes;
39  // Complete the padding specification to specify all dimensions.
40  for (size_t idx = 0, e = indexingSizes.size(); idx != e; ++idx) {
41  // Complete to zero if needed.
42  paddingSizes.push_back(options.paddingSizes.size() > idx
43  ? options.paddingSizes[idx]
44  : b.getIndexAttr(0));
45  // If a dimension is zero (either specified or completed), replace by:
46  // - 1 if we are padding to the next multiple of.
47  // - indexingSizes[idx] otherwise
48  if (isZeroInteger(paddingSizes[idx])) {
49  paddingSizes[idx] =
50  options.padToMultipleOf ? b.getIndexAttr(1) : indexingSizes[idx];
51  }
52  LLVM_DEBUG(DBGS() << "----idx: " << idx << " : " << paddingSizes[idx]
53  << "\n");
54  }
55  return paddingSizes;
56 }
57 
58 /// Compute the padded shape of the given value `v` of `RankedTensorType` given
59 /// - `indexingSizes` a list of OpFoldResult.
60 /// - an `indexingMap` that encodes how the shape of varies with increases
61 /// in `indexingSizes`.
62 /// The `indexingMap` encodes how the shape of varies with `indexingSizes`.
63 /// The `indexingMap` + `indexingSizes` encoding suits StructuredOps.
64 /// The implementaiton below iteratively combines increases from contributing
65 /// dimensions using affine.apply operations.
66 /// In the future, more general interfaces can be devised to encode similar
67 /// shape evolutions and map between an op and its operands.
70  AffineMap indexingMap, ArrayRef<OpFoldResult> indexingSizes,
72  Location loc = v.getLoc();
73  SmallVector<OpFoldResult> paddedShape;
74  auto tensorType = cast<RankedTensorType>(v.getType());
75  paddedShape.resize_for_overwrite(tensorType.getRank());
76  assert(tensorType.getRank() == indexingMap.getNumResults() &&
77  "expect the number of results of the affine map to match the tensor "
78  "rank");
79 
80  // "Full-rank" padding specification.
81  SmallVector<OpFoldResult> paddingSizes =
82  getFullRankPaddingSizes(rewriter, indexingSizes, options);
83 
84  // For each dimension in the operand's shape, iterate over indexingSizes and
85  // add the various term contributions.
86  for (const auto &enResults : enumerate(indexingMap.getResults())) {
87  int64_t resultIndex = enResults.index();
88  AffineMap partialIndexingMap = indexingMap.getSubMap(
89  ArrayRef<unsigned>{static_cast<unsigned>(resultIndex)});
90 
91  LLVM_DEBUG(DBGS() << "----resultIndex: " << resultIndex
92  << " with partialIndexingMap: " << partialIndexingMap
93  << "\n");
94 
95  // Find all padding dimensions that contribute to this operand dimension
96  // and compute the padded term contribution to the final padded shape.
98  for (size_t paddingDim = 0, e = paddingSizes.size(); paddingDim != e;
99  ++paddingDim) {
100  OpFoldResult paddingSize = paddingSizes[paddingDim];
101  LLVM_DEBUG(DBGS() << "------try apply padding of dim: " << paddingDim
102  << " to: " << paddingSize << "\n");
103  if (!enResults.value().isFunctionOfDim(paddingDim))
104  continue;
105 
106  LLVM_DEBUG(DBGS() << "------apply padding of dim: " << paddingDim
107  << " to: " << paddingSize << "\n");
108 
109  // Project non-'paddingDim' dimensions and compress the result.
110  llvm::SmallBitVector projectedDims(partialIndexingMap.getNumDims(), true);
111  projectedDims.flip(paddingDim);
112  AffineMap projectedMap =
113  mlir::projectDims(partialIndexingMap, projectedDims,
114  /*compressDims=*/true);
115 
116  // If we are padding to the next multiple of, compose with ceil(sz) * sz.
117  if (options.padToMultipleOf) {
118  AffineExpr d0, s0;
119  bindDims(rewriter.getContext(), d0);
120  bindSymbols(rewriter.getContext(), s0);
121  AffineMap ceilMap = AffineMap::get(1, 1, d0.ceilDiv(s0) * s0);
122  AffineMap composedMap = projectedMap.compose(ceilMap);
124  rewriter, loc, composedMap,
125  {indexingSizes[paddingDim], paddingSize},
126  /*composeAffineMin=*/true);
127  terms.push_back(paddingDimOfr);
128  } else {
129  // Otherwise just set to paddingSize.
131  rewriter, loc, projectedMap, paddingSize);
132  terms.push_back(paddingDimOfr);
133  }
134 
135  LLVM_DEBUG(DBGS() << "------new term: " << terms.back() << "\n");
136  }
137 
138  // If there are no terms, just return the dim.
139  if (terms.empty()) {
140  paddedShape[resultIndex] =
141  createFoldedDimOp(rewriter, loc, v, resultIndex);
142  continue;
143  }
144 
145  // Sum individual terms' contributions.
146  SmallVector<AffineExpr> dims(terms.size());
147  bindDimsList(rewriter.getContext(), MutableArrayRef{dims});
148  AffineExpr sumExpr = dims.front();
149  for (unsigned i = 1; i < dims.size(); ++i)
150  sumExpr = sumExpr + dims[i];
151  OpFoldResult paddedDimOfr =
152  affine::makeComposedFoldedAffineApply(rewriter, loc, sumExpr, terms);
153  paddedShape[resultIndex] = paddedDimOfr;
154  }
155 
156  return paddedShape;
157 }
158 
159 FailureOr<SmallVector<OpFoldResult>>
161  RewriterBase &rewriter, OpOperand &operandToPad,
162  ArrayRef<Range> iterationDomain, const PadTilingInterfaceOptions &options) {
163  auto transferOp =
164  llvm::dyn_cast<IndexingMapOpInterface>(operandToPad.getOwner());
165  if (!transferOp)
166  return failure();
167 
168  // clang-format off
169  assert(llvm::all_of(iterationDomain, [&rewriter](Range r) {
170  return r.offset == OpFoldResult(rewriter.getIndexAttr(0)) &&
171  r.stride == OpFoldResult(rewriter.getIndexAttr(1));
172  }) && "expected 0-offset 1-stride loop ranges");
173  // clang-format on
174  SmallVector<OpFoldResult> loopUpperBounds;
175  loopUpperBounds.reserve(iterationDomain.size());
176  for (const Range &range : iterationDomain)
177  loopUpperBounds.push_back(range.size);
178 
179  AffineMap indexingMap = transferOp.getMatchingIndexingMap(&operandToPad);
180  return computePaddedShape(
181  rewriter, cast<TypedValue<RankedTensorType>>(operandToPad.get()),
182  indexingMap, loopUpperBounds, options);
183 }
184 
185 /// Pad a single operand to `paddedShape` using `paddingValueAttr` as padding
186 /// Value.
187 static Value padOperand(RewriterBase &rewriter, TilingInterface opToPad,
189  ArrayRef<OpFoldResult> paddedShape,
190  Attribute paddingValueAttr) {
191  Value paddingValue;
192  if (auto complexTy =
193  dyn_cast<ComplexType>(getElementTypeOrSelf(v.getType()))) {
194  auto complexAttr = cast<ArrayAttr>(paddingValueAttr);
195  paddingValue = rewriter.create<complex::ConstantOp>(opToPad.getLoc(),
196  complexTy, complexAttr);
197  } else {
198  paddingValue = rewriter.create<arith::ConstantOp>(
199  opToPad.getLoc(), cast<TypedAttr>(paddingValueAttr));
200  }
201 
202  // Pad the operand to the bounding box defined by `paddedShape`.
203  SmallVector<int64_t> tensorShape;
204  SmallVector<Value> dynDims;
205  for (OpFoldResult ofr : paddedShape) {
206  std::optional<int64_t> cst = getConstantIntValue(ofr);
207  tensorShape.push_back(cst.has_value() ? *cst : ShapedType::kDynamic);
208  if (!cst.has_value())
209  dynDims.push_back(ofr.dyn_cast<Value>());
210  }
211  // TODO: use dispatchIndexOpFoldResults(paddedShape, dynDims, paddedShape);
212 
213  auto paddedTensorType =
215  LLVM_DEBUG(DBGS() << "--SUCCESS, makeComposedPadHighOp with type: "
216  << paddedTensorType);
217  return makeComposedPadHighOp(rewriter, opToPad.getLoc(), paddedTensorType, v,
218  paddingValue, /*nofold=*/false, dynDims);
219 }
220 
221 FailureOr<TilingInterface>
222 linalg::rewriteAsPaddedOp(RewriterBase &rewriter, TilingInterface opToPad,
223  const PadTilingInterfaceOptions &constOptions,
225  PadSizeComputationFunction computePaddingSizeFun) {
226  LLVM_DEBUG(DBGS() << "Start rewriteAsPaddedOp : " << opToPad << "\n");
227 
228  Location loc = opToPad.getLoc();
229  PadTilingInterfaceOptions options(constOptions);
230  // Allow inference of pad values if they are not explicitly specified.
231  // TODO: be mindful about the value depending on the actual operation.
232  if (options.paddingValues.empty()) {
233  SmallVector<Type> types(opToPad->getOperandTypes());
234  llvm::append_range(types, opToPad->getResultTypes());
235  for (Type t : types) {
236  options.paddingValues.push_back(
237  rewriter.getZeroAttr(getElementTypeOrSelf(t)));
238  }
239  }
240 
241  if (llvm::any_of(opToPad->getOperands(),
242  [](Value v) { return isa<MemRefType>(v.getType()); })) {
243  return rewriter.notifyMatchFailure(opToPad,
244  "expected operation on tensors");
245  }
246 
247  OpBuilder::InsertionGuard g(rewriter);
248  // Set IP after opToPad because we also take the dims of opToPad's output.
249  rewriter.setInsertionPointAfter(opToPad);
250 
251  // 1. Get the loopUpperBounds from the TilingInterface.
252  SmallVector<Range> iterationDomain = opToPad.getIterationDomain(rewriter);
253 
254  // 2. For each operand.
255  SmallVector<Value> newOperands;
256  newOperands.reserve(opToPad->getNumOperands());
257  for (OpOperand &opOperand : opToPad->getOpOperands()) {
258  Value operand = opOperand.get();
259  LLVM_DEBUG(DBGS() << "--start padding oprd: " << operand << "\n");
260 
261  // 2.a. Skip scalar-like operands.
262  Type operandType = operand.getType();
263  if (!isa<RankedTensorType>(operandType)) {
264  assert((!isa<ShapedType>(operandType) || isa<VectorType>(operandType)) &&
265  "Unexpected non-vector ShapedType");
266  newOperands.push_back(operand);
267  continue;
268  }
269  // 2.a. Compute padded shape.
270  FailureOr<SmallVector<OpFoldResult>> maybePaddedShape =
271  computePaddingSizeFun(rewriter, opOperand, iterationDomain, options);
272  if (failed(maybePaddedShape)) {
273  return rewriter.notifyMatchFailure(opToPad, "could not pad op");
274  }
275 
276  // 2.b. Expect proper `paddingValues`.
277  // TODO: we may want to allow garbage padding in the future, in which case
278  // we would just not assert.
279  if (opOperand.getOperandNumber() >= options.paddingValues.size()) {
280  return rewriter.notifyMatchFailure(opToPad,
281  "--no padding value specified");
282  }
283  Attribute paddingValueAttr =
284  options.paddingValues[opOperand.getOperandNumber()];
285 
286  // 2.c. Perform actual padding.
287  Value paddedOperand = padOperand(
288  rewriter, opToPad, cast<TypedValue<RankedTensorType>>(operand),
289  *maybePaddedShape, paddingValueAttr);
290  LLVM_DEBUG(DBGS() << "--done padding operand: " << paddedOperand << "\n");
291 
292  // 2.d. Perform actual padding.
293  newOperands.push_back(paddedOperand);
294  if (auto padOp = paddedOperand.getDefiningOp<tensor::PadOp>())
295  padOps.push_back(padOp);
296  }
297 
298  // 3. Form the resulting tensor::ExtractSliceOp.
299  ReifiedRankedShapedTypeDims reifiedResultShapes;
300  if (failed(reifyResultShapes(rewriter, opToPad, reifiedResultShapes))) {
301  LLVM_DEBUG(DBGS() << "--failed to reify result shapes -> FAIL\n");
302  return rewriter.notifyMatchFailure(opToPad,
303  "failed to reify result shapes");
304  }
305  assert(reifiedResultShapes.size() == opToPad->getNumResults() &&
306  "expected same number of results");
307 
308  // Clone `opToPad` to operate on the statically padded shapes.
309  auto resultTensorTypes =
310  ValueRange(newOperands).take_back(opToPad->getNumResults()).getTypes();
311  // clone **should** properly notify the rewriter.
312  TilingInterface paddedOp =
313  clone(rewriter, opToPad, resultTensorTypes, newOperands);
314  LLVM_DEBUG(DBGS() << "--cloned padded op: " << paddedOp << "\n");
315 
316  // Recover the slice out of the new static results. This keeps the original
317  // opToPad around because it uses the dims of the original results.
318  SmallVector<Value> paddedSubtensorResults;
319  paddedSubtensorResults.reserve(opToPad->getNumResults());
320  for (const auto &en : llvm::enumerate(paddedOp->getResults())) {
321  Value paddedResult = en.value();
322  int64_t resultNumber = en.index();
323  int64_t rank = cast<RankedTensorType>(paddedResult.getType()).getRank();
324  SmallVector<OpFoldResult> offsets(rank, rewriter.getIndexAttr(0));
325  SmallVector<OpFoldResult> strides(rank, rewriter.getIndexAttr(1));
326  paddedSubtensorResults.push_back(rewriter.create<tensor::ExtractSliceOp>(
327  loc, paddedResult, offsets, reifiedResultShapes[resultNumber],
328  strides));
329  }
330 
331  rewriter.replaceOp(opToPad, paddedSubtensorResults);
332 
333  return paddedOp;
334 }
static Value padOperand(RewriterBase &rewriter, TilingInterface opToPad, TypedValue< RankedTensorType > v, ArrayRef< OpFoldResult > paddedShape, Attribute paddingValueAttr)
Pad a single operand to paddedShape using paddingValueAttr as padding Value.
#define DBGS()
static SmallVector< OpFoldResult > getFullRankPaddingSizes(Builder &b, ArrayRef< OpFoldResult > indexingSizes, const PadTilingInterfaceOptions &options)
Form a "full-rank" padding specification so that the application is easy.
static llvm::ManagedStatic< PassManagerOptions > options
Base type for affine expression.
Definition: AffineExpr.h:68
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition: AffineMap.h:46
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
unsigned getNumDims() const
Definition: AffineMap.cpp:390
ArrayRef< AffineExpr > getResults() const
Definition: AffineMap.cpp:403
unsigned getNumResults() const
Definition: AffineMap.cpp:398
AffineMap getSubMap(ArrayRef< unsigned > resultPos) const
Returns the map consisting of the resultPos subset.
Definition: AffineMap.cpp:647
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
Definition: AffineMap.cpp:552
Attributes are known-constant values of operations.
Definition: Attributes.h:25
This class is a general helper class for creating context-global objects like types,...
Definition: Builders.h:50
IntegerAttr getIndexAttr(int64_t value)
Definition: Builders.cpp:103
TypedAttr getZeroAttr(Type type)
Definition: Builders.cpp:319
MLIRContext * getContext() const
Definition: Builders.h:55
IRValueT get() const
Return the current value being used by this operand.
Definition: UseDefLists.h:160
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition: Builders.h:346
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition: Builders.cpp:452
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition: Builders.h:410
This class represents a single result from folding an operation.
Definition: OpDefinition.h:271
This class represents an operand of an operation.
Definition: Value.h:257
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
Definition: PatternMatch.h:358
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
Definition: PatternMatch.h:681
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition: Types.h:74
This class provides an abstraction over the different types of ranges over Values.
Definition: ValueRange.h:387
type_range getTypes() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
Type getType() const
Return the type of this value.
Definition: Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition: Value.cpp:20
Operation * getOwner() const
Return the owner of this operand.
Definition: UseDefLists.h:38
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
Definition: AffineOps.cpp:1331
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition: Matchers.h:344
LogicalResult rewriteAsPaddedOp(RewriterBase &rewriter, LinalgOp opToPad, const LinalgPaddingOptions &options, LinalgOp &paddedOp, SmallVector< Value > &replacements, SmallVector< tensor::PadOp > &padOps)
Pad the iterator dimensions options.paddingDimensions of all opToPad operands to a static bounding bo...
Definition: Padding.cpp:244
SmallVector< OpFoldResult > computePaddedShape(RewriterBase &rewriter, TypedValue< RankedTensorType > v, AffineMap indexingMap, ArrayRef< OpFoldResult > indexingSizes, const PadTilingInterfaceOptions &options)
Helper function to compute the padded shape of the given value v of RankedTensorType given:
OpFoldResult createFoldedDimOp(OpBuilder &b, Location loc, Value val, int64_t dim)
Create one memref::DimOp or tensor::DimOp depending on the type of val.
Definition: LinalgOps.cpp:103
FailureOr< SmallVector< OpFoldResult > > computeIndexingMapOpInterfacePaddedShape(RewriterBase &rewriter, OpOperand &operandToPad, ArrayRef< Range > iterationDomain, const PadTilingInterfaceOptions &options)
Specific helper for Linalg ops.
std::function< FailureOr< SmallVector< OpFoldResult > >(RewriterBase &, OpOperand &, ArrayRef< Range >, const PadTilingInterfaceOptions &)> PadSizeComputationFunction
Definition: Transforms.h:614
Value makeComposedPadHighOp(OpBuilder &b, Location loc, RankedTensorType type, Value source, Value padding, bool nofold, ValueRange typeDynDims={})
Create a tensor::PadOp that pads source to the shape of type whose sizes are assumed to be greater th...
Definition: Utils.cpp:243
Include the generated interface declarations.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
void bindDimsList(MLIRContext *ctx, MutableArrayRef< AffineExprTy > exprs)
Definition: AffineExpr.h:316
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
Definition: Value.h:488
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition: AffineExpr.h:311
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isZeroInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 0.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition: AffineExpr.h:325
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
AffineMap projectDims(AffineMap map, const llvm::SmallBitVector &projectedDimensions, bool compressDimsFlag=false)
Returns the map that results from projecting out the dimensions specified in projectedDimensions.
Definition: AffineMap.cpp:899
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
OpFoldResult stride
OpFoldResult offset