MLIR 24.0.0git
TosaToTensor.cpp
Go to the documentation of this file.
1//===- TosaToTensor.cpp - Lowering Tosa to Tensor Dialect -------------===//
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// These rewriters lower from the Tosa to the Tensor dialect.
10//
11//===----------------------------------------------------------------------===//
12
22#include "llvm/ADT/STLExtras.h"
23
24#include <numeric>
25
26using namespace mlir;
27using namespace tosa;
28
29namespace {
30
31// Infer the type to which the input of a 'tosa.reshape' op must be cast when
32// lowered.
33TensorType inferReshapeInputType(TypedValue<TensorType> input,
34 ArrayRef<int64_t> newShape) {
35 // No need to cast input for non-empty target shape
36 if (!newShape.empty())
37 return input.getType();
38
39 // The input type must be cast into a tensor with the same rank and all static
40 // dimensions set to 1. This prevents the generation of a
41 // tensor.collapse_shape op that converts a dynamically shaped tensor into a
42 // 0D tensor. While such construct is not incorrect on its own, bufferization
43 // cannot properly handle it at the moment, so we avoid it.
44 SmallVector<int64_t> shape(input.getType().getRank(), 1);
45 return input.getType().clone(shape);
46}
47
48// Infer the result type of 'tensor.expand_shape' in the collapse-expand
49// pair emitted for a 'tosa.reshape' op.
50TensorType inferReshapeExpandedType(TensorType inputType,
51 ArrayRef<int64_t> newShape) {
52 // Special case for 0D output tensor. Note: Watch out when using Type::clone()
53 // with just '{}', as it will invoke the incorrect overload.
54 if (newShape.empty())
55 return inputType.clone(ArrayRef<int64_t>{});
56
57 // Check if the input is static, and if so, get its total size
58 bool inputIsStatic = inputType.hasStaticShape();
59 int64_t totalSize = inputIsStatic ? inputType.getNumElements() : -1;
60
61 // Compute result shape
62 auto resultShape =
63 llvm::map_to_vector(newShape, [&](int64_t size) -> int64_t {
64 // If this is not a placeholder, do not change it.
65 if (size >= 0)
66 return size;
67
68 // If we do not know the total size of the tensor, keep this dimension
69 // dynamic in the result shape.
70 if (!inputIsStatic)
71 return ShapedType::kDynamic;
72
73 // Calculate the product of all elements in 'newShape' except for the -1
74 // placeholder, which we discard by negating the result.
75 int64_t totalSizeNoPlaceholder = -llvm::product_of(newShape);
76
77 // If there is a 0 component in 'newShape', resolve the placeholder as
78 // 0.
79 if (totalSizeNoPlaceholder == 0)
80 return 0;
81
82 // Resolve the placeholder as the quotient between the total tensor size
83 // and the product of all other sizes.
84 return totalSize / totalSizeNoPlaceholder;
85 });
86
87 bool resultIsStatic = ShapedType::isStaticShape(resultShape);
88
89 // A syntactic restriction in 'tensor.expand_shape' forbids a dynamically
90 // shaped input from being reshaped into a statically shaped result. We may
91 // simply turn the first result dimension dynamic to address this.
92 if (!inputIsStatic && resultIsStatic)
93 resultShape[0] = ShapedType::kDynamic;
94
95 // The 'tensor.expand_shape' op also forbids a statically shaped input from
96 // being reshaped into a dynamically shaped result, but the placeholder
97 // inference algorithm above guarantees that this will never be the case.
98 assert(!inputIsStatic || resultIsStatic);
99
100 // Create result type
101 return inputType.clone(resultShape);
102}
103
104// Infer the result type of 'tensor.collapse_shape' in the collapse-expand
105// pair emitted for a 'tosa.reshape' op.
106TensorType inferReshapeCollapsedType(TensorType lhsType, TensorType rhsType) {
107 auto lhsShape = lhsType.getShape();
108 auto rhsShape = rhsType.getShape();
109
110 if (lhsShape.empty() || rhsShape.empty())
111 return lhsType.clone(ArrayRef<int64_t>{});
112
113 if (ShapedType::isDynamicShape(lhsShape) ||
114 ShapedType::isDynamicShape(rhsShape))
115 return lhsType.clone({ShapedType::kDynamic});
116
117 SmallVector<int64_t> intermediateShape;
118 unsigned currLhsDim = 0, currRhsDim = 0;
119 while (currLhsDim < lhsShape.size() && currRhsDim < rhsShape.size()) {
120 int64_t rhsSize = rhsShape[currRhsDim];
121 int64_t lhsSize = lhsShape[currLhsDim];
122 while (lhsSize != rhsSize && currLhsDim < lhsShape.size() &&
123 currRhsDim < rhsShape.size()) {
124 if (lhsSize < rhsSize) {
125 currLhsDim++;
126 if (currLhsDim < lhsShape.size()) {
127 lhsSize *= lhsShape[currLhsDim];
128 }
129 } else {
130 currRhsDim++;
131 if (currRhsDim < rhsShape.size()) {
132 rhsSize *= rhsShape[currRhsDim];
133 }
134 }
135 }
136 if (lhsSize == rhsSize) {
137 intermediateShape.push_back(lhsSize);
138 }
139 currRhsDim++;
140 currLhsDim++;
141 }
142
143 // Static shapes are guaranteed to be compatible by the op verifier, so all
144 // leftover dimensions should be 1.
145 for (; currLhsDim < lhsShape.size(); currLhsDim++) {
146 assert(lhsShape[currLhsDim] == 1);
147 }
148 for (; currRhsDim < rhsShape.size(); currRhsDim++) {
149 assert(rhsShape[currRhsDim] == 1);
150 }
151
152 return lhsType.clone(intermediateShape);
153}
154
156createReassociationMapForCollapse(OpBuilder &builder, Type srcType,
157 Type dstType) {
158 auto srcShape = cast<TensorType>(srcType).getShape();
159 auto dstShape = cast<TensorType>(dstType).getShape();
160
161 if (srcShape.empty() || dstShape.empty())
162 return {};
163
164 if (ShapedType::isDynamicShape(srcShape) ||
165 ShapedType::isDynamicShape(dstShape)) {
166 assert(dstShape.size() == 1);
168 for (auto i : llvm::seq<int64_t>(srcShape.size()))
169 exprs.push_back(builder.getAffineDimExpr(i));
170 return {exprs};
171 }
172
173 SmallVector<ReassociationExprs> reassociationMap(dstShape.size());
174 unsigned currSrcDim = 0, currDstDim = 0;
175 while (currSrcDim < srcShape.size() && currDstDim < dstShape.size()) {
176 int64_t dstSize = dstShape[currDstDim];
177 int64_t srcSize = srcShape[currSrcDim];
178 while (srcSize < dstSize && currSrcDim < srcShape.size()) {
179 reassociationMap[currDstDim].push_back(
180 builder.getAffineDimExpr(currSrcDim++));
181 srcSize *= srcShape[currSrcDim];
182 }
183 if (srcSize == dstSize) {
184 reassociationMap[currDstDim].push_back(
185 builder.getAffineDimExpr(currSrcDim++));
186 // If the next dim in collapsedShape is not 1, treat subsequent dims in
187 // expandedShape which are 1 to be collapsed.
188 if (currDstDim == dstShape.size() - 1 || dstShape[currDstDim + 1] != 1) {
189 while (currSrcDim < srcShape.size() && srcShape[currSrcDim] == 1) {
190 reassociationMap[currDstDim].push_back(
191 builder.getAffineDimExpr(currSrcDim++));
192 }
193 }
194 }
195 currDstDim++;
196 }
197
198 // If the source and target shapes are compatible, both iterators must have
199 // reached the end. This condition is guaranteed by the op verifier for
200 // static shapes.
201 assert(currSrcDim == srcShape.size() && currDstDim == dstShape.size());
202 return reassociationMap;
203}
204
205// Create a tensor.collapse_shape op that reshapes the input into the given
206// result type.
207Value createCollapse(OpBuilder &builder, Location loc, TensorType resultType,
208 Value input) {
209 auto reassociationMap =
210 createReassociationMapForCollapse(builder, input.getType(), resultType);
211 return builder.createOrFold<tensor::CollapseShapeOp>(loc, resultType, input,
212 reassociationMap);
213}
214
215// Create a tensor.expand_shape op that reshapes the input into the given result
216// type.
217Value createExpand(OpBuilder &builder, Location loc, TensorType resultType,
218 Value input) {
219 auto reassociationMap =
220 createReassociationMapForCollapse(builder, resultType, input.getType());
221 return builder.createOrFold<tensor::ExpandShapeOp>(loc, resultType, input,
222 reassociationMap);
223}
224
225class ReshapeConverter : public OpConversionPattern<tosa::ReshapeOp> {
226public:
227 using OpConversionPattern<tosa::ReshapeOp>::OpConversionPattern;
228
229 LogicalResult
230 matchAndRewrite(tosa::ReshapeOp reshape, OpAdaptor adaptor,
231 ConversionPatternRewriter &rewriter) const final {
232 auto loc = reshape.getLoc();
233 auto resultType =
234 getTypeConverter()->convertType<ShapedType>(reshape.getType());
235 if (!resultType) {
236 return rewriter.notifyMatchFailure(reshape.getLoc(),
237 "could not convert result type");
238 }
239 auto input = dyn_cast<TypedValue<TensorType>>(adaptor.getInput1());
240 if (!input) {
241 return rewriter.notifyMatchFailure(reshape.getLoc(),
242 "expected input type to be tensor");
243 }
244
245 llvm::SmallVector<int64_t> newShape;
246 if (!tosa::getConstShapeValues(reshape.getShape().getDefiningOp(),
247 newShape)) {
248 return failure();
249 }
250
251 // Infer all intermediate types
252 auto inputType = inferReshapeInputType(input, newShape);
253 auto expandedType = inferReshapeExpandedType(inputType, newShape);
254 auto collapsedType = inferReshapeCollapsedType(inputType, expandedType);
255
256 // Cast input if needed
257 auto castInput =
258 rewriter.createOrFold<tensor::CastOp>(loc, inputType, input);
259
260 // Emit collaspe-expand pair
261 auto collapsed = createCollapse(rewriter, loc, collapsedType, castInput);
262 auto expanded = createExpand(rewriter, loc, expandedType, collapsed);
263
264 // Cast to final result type if needed
265 auto result =
266 rewriter.createOrFold<tensor::CastOp>(loc, resultType, expanded);
267 rewriter.replaceOp(reshape, result);
268 return success();
269 }
270};
271
272class SliceConverter : public OpConversionPattern<tosa::SliceOp> {
273public:
274 using OpConversionPattern<tosa::SliceOp>::OpConversionPattern;
275
276 LogicalResult
277 matchAndRewrite(tosa::SliceOp sliceOp, OpAdaptor adaptor,
278 ConversionPatternRewriter &rewriter) const final {
279 Location loc = sliceOp.getLoc();
280 Value input = adaptor.getInput1();
281 ShapedType resultType = cast<ShapedType>(sliceOp.getType());
282 if (llvm::isa<UnrankedTensorType>(resultType))
283 return failure();
284
285 ElementsAttr startElems;
286 ElementsAttr sizeElems;
287
288 if (!matchPattern(sliceOp.getStart(), m_Constant(&startElems)))
289 return rewriter.notifyMatchFailure(
290 sliceOp, "start of slice must be a static ranked shape");
291
292 if (!matchPattern(sliceOp.getSize(), m_Constant(&sizeElems)))
293 return rewriter.notifyMatchFailure(
294 sliceOp, "size of slice must be a static ranked shape");
295
296 llvm::SmallVector<int64_t> sliceStarts =
297 llvm::to_vector(startElems.getValues<int64_t>());
298 llvm::SmallVector<int64_t> sliceSizes =
299 llvm::to_vector(sizeElems.getValues<int64_t>());
300
301 SmallVector<int64_t> strides, sizes;
302 strides.resize(cast<ShapedType>(sliceOp.getType()).getRank(), 1);
303
304 SmallVector<Value> dynSizes;
305 for (const auto &i : llvm::enumerate(sliceSizes)) {
306 int64_t size = i.value();
307 size_t index = i.index();
308 sizes.push_back(size == -1 ? ShapedType::kDynamic : size);
309 if (ShapedType::isStatic(sizes.back()))
310 continue;
311
312 auto dim = tensor::DimOp::create(rewriter, loc, input, index);
313 auto offset = arith::ConstantOp::create(
314 rewriter, loc, rewriter.getIndexAttr(sliceStarts[index]));
315 dynSizes.push_back(arith::SubIOp::create(rewriter, loc, dim, offset));
316 }
317
318 auto newSliceOp = tensor::ExtractSliceOp::create(
319 rewriter, sliceOp.getLoc(), sliceOp.getType(), input, ValueRange({}),
320 dynSizes, ValueRange({}), rewriter.getDenseI64ArrayAttr(sliceStarts),
321 rewriter.getDenseI64ArrayAttr(sizes),
322 rewriter.getDenseI64ArrayAttr(strides));
323
324 // Remove const_shape ops when it no longer has use point.
325 Operation *startConstShape = sliceOp.getStart().getDefiningOp();
326 if (startConstShape->getResult(0).hasOneUse())
327 rewriter.eraseOp(startConstShape);
328
329 Operation *sizeConstShape = sliceOp.getSize().getDefiningOp();
330 if (sizeConstShape->getResult(0).hasOneUse())
331 rewriter.eraseOp(sizeConstShape);
332
333 rewriter.replaceOp(sliceOp, newSliceOp.getResult());
334 return success();
335 }
336};
337
338class PadConverter : public OpConversionPattern<tosa::PadOp> {
339public:
340 using Base::Base;
341
342 LogicalResult
343 matchAndRewrite(tosa::PadOp padOp, OpAdaptor adaptor,
344 ConversionPatternRewriter &rewriter) const final {
345 auto loc = padOp.getLoc();
346 auto input = padOp.getInput1();
347
348 ElementsAttr paddingElems;
349 if (!matchPattern(padOp.getPadding(), m_Constant(&paddingElems))) {
350 return rewriter.notifyMatchFailure(
351 padOp, "padding must be a static shape value");
352 }
353 llvm::SmallVector<int64_t> paddingVals;
354 for (auto idx : paddingElems.getValues<IntegerAttr>()) {
355 paddingVals.push_back(static_cast<int64_t>(idx.getInt()));
356 }
357
358 // The TOSA specification requires a negative padding to be diagnosed.
359 // We emit a runtime assertion, as this could be dead or unreachable code.
360 if (llvm::any_of(paddingVals, [](int64_t val) { return val < 0; })) {
361 Value fail =
362 arith::ConstantOp::create(rewriter, loc, rewriter.getBoolAttr(false));
363 cf::AssertOp::create(
364 rewriter, loc, fail,
365 rewriter.getStringAttr("tosa.pad: padding must be non-negative"));
366 }
367
368 ShapedType inputTy = cast<ShapedType>(input.getType());
369 int64_t rank = inputTy.getRank();
370
371 // Setup the default constantAttr.
372
373 Value padConstant = rewriter.createOrFold<tensor::ExtractOp>(
374 loc, padOp.getPadConst(),
375 ValueRange({arith::ConstantIndexOp::create(rewriter, loc, 0)}));
376
377 if (!padConstant) {
378 return rewriter.notifyMatchFailure(
379 padOp, "tosa.pad was unable to determine the pad constant value.");
380 }
381
382 SmallVector<OpFoldResult, 3> lowValues;
383 SmallVector<OpFoldResult, 3> highValues;
384
385 lowValues.reserve(rank);
386 highValues.reserve(rank);
387
388 for (int i = 0; i < rank; i++) {
389 Value lowVal = arith::ConstantOp::create(
390 rewriter, loc, rewriter.getIndexAttr(paddingVals[2 * i]));
391 Value highVal = arith::ConstantOp::create(
392 rewriter, loc, rewriter.getIndexAttr(paddingVals[2 * i + 1]));
393 lowValues.push_back(lowVal);
394 highValues.push_back(highVal);
395 }
396
397 auto newPadOp = tensor::PadOp::create(rewriter, loc, padOp.getType(), input,
398 lowValues, highValues, padConstant);
399
400 rewriter.replaceOp(padOp, newPadOp.getResult());
401 return success();
402 }
403};
404
405struct ConcatConverter : public OpConversionPattern<tosa::ConcatOp> {
406 using OpConversionPattern<tosa::ConcatOp>::OpConversionPattern;
407
408 LogicalResult
409 matchAndRewrite(tosa::ConcatOp op, OpAdaptor adaptor,
410 ConversionPatternRewriter &rewriter) const override {
411 auto resultType =
412 getTypeConverter()->convertType<RankedTensorType>(op.getType());
413 if (!resultType)
414 return failure();
415
416 rewriter.replaceOpWithNewOp<tensor::ConcatOp>(
417 op, resultType, rewriter.getI64IntegerAttr(op.getAxis()),
418 adaptor.getInput1());
419 return success();
420 }
421};
422
423} // namespace
424
426 const TypeConverter &converter, RewritePatternSet *patterns) {
427 patterns
428 ->add<ConcatConverter, PadConverter, ReshapeConverter, SliceConverter>(
429 converter, patterns->getContext());
430}
return success()
static LogicalResult fail(function_ref< InFlightDiagnostic()> emitError, const Twine &message)
Reports message through emitError if it is non-null, and returns failure.
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
This class helps build Operations.
Definition Builders.h:210
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
Tensor types represent multi-dimensional arrays, and have two variants: RankedTensorType and Unranked...
ArrayRef< int64_t > getShape() const
Returns the shape of this tensor type.
RankedTensorType clone(ArrayRef< int64_t > shape, Type elementType) const
Return a clone of this type with the given new shape and element type.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
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
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:434
void populateTosaToTensorConversionPatterns(const TypeConverter &converter, RewritePatternSet *patterns)
bool getConstShapeValues(Operation *op, llvm::SmallVector< int64_t > &result_shape)
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
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:494
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369