1 //===- TosaOps.cpp - MLIR Dialect for TOSA --------------------------------===//
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 // \file
10 // This file implements the TOSA Specification:
11 // https://developer.mlplatform.org/w/tosa/
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "mlir/Dialect/Tosa/IR/TosaOps.h"
16 #include "mlir/Dialect/StandardOps/IR/Ops.h"
17 #include "mlir/Dialect/Tensor/IR/Tensor.h"
18 #include "mlir/Dialect/Tosa/Utils/QuantUtils.h"
19 #include "mlir/Dialect/Tosa/Utils/ShapeUtils.h"
20 #include "mlir/IR/BuiltinTypes.h"
21 #include "mlir/IR/Matchers.h"
22 #include "mlir/IR/PatternMatch.h"
23 #include "mlir/Transforms/FoldUtils.h"
24 #include "mlir/Transforms/InliningUtils.h"
25 #include "mlir/Transforms/RegionUtils.h"
26 #include "llvm/ADT/DenseMap.h"
27 
28 using namespace mlir;
29 using namespace mlir::tosa;
30 
31 #include "mlir/Dialect/Tosa/IR/TosaOpsDialect.cpp.inc"
32 
33 //===----------------------------------------------------------------------===//
34 // Tosa dialect structs and interface includes.
35 //===----------------------------------------------------------------------===//
36 #include "mlir/Dialect/Tosa/IR/TosaInterfaces.cpp.inc"
37 #include "mlir/Dialect/Tosa/IR/TosaStructs.cpp.inc"
38 
39 namespace {
40 //===----------------------------------------------------------------------===//
41 // Dialect Function Inliner Interface.
42 //===----------------------------------------------------------------------===//
43 struct TosaInlinerInterface : public DialectInlinerInterface {
44   using DialectInlinerInterface::DialectInlinerInterface;
45 
46   //===--------------------------------------------------------------------===//
47   // Analysis Hooks.
48   //===--------------------------------------------------------------------===//
49 
50   /// All operations can be inlined by default.
51   bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned,
52                        BlockAndValueMapping &map) const final {
53     return true;
54   }
55 
56   /// All regions with If and While parent operators can be inlined.
57   bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
58                        BlockAndValueMapping &map) const final {
59     return (isa<tosa::IfOp>(dest->getParentOp()) ||
60             isa<tosa::WhileOp>(dest->getParentOp()));
61   }
62 };
63 } // namespace
64 
65 //===----------------------------------------------------------------------===//
66 // TOSA control flow support.
67 //===----------------------------------------------------------------------===//
68 
69 /// Returns the while loop body.
70 Region &tosa::WhileOp::getLoopBody() { return body(); }
71 
72 bool tosa::WhileOp::isDefinedOutsideOfLoop(Value value) {
73   return !body().isAncestor(value.getParentRegion());
74 }
75 
76 LogicalResult WhileOp::moveOutOfLoop(ArrayRef<mlir::Operation *> ops) {
77   if (ops.empty())
78     return success();
79 
80   Operation *tosaWhileOp = this->getOperation();
81   for (auto *op : ops)
82     op->moveBefore(tosaWhileOp);
83 
84   return success();
85 }
86 
87 //===----------------------------------------------------------------------===//
88 // Tosa dialect initialization.
89 //===----------------------------------------------------------------------===//
90 
91 void TosaDialect::initialize() {
92   addOperations<
93 #define GET_OP_LIST
94 #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"
95       >();
96   addInterfaces<TosaInlinerInterface>();
97 }
98 
99 Operation *TosaDialect::materializeConstant(OpBuilder &builder, Attribute value,
100                                             Type type, Location loc) {
101   // Tosa dialect constants only support ElementsAttr unlike standard dialect
102   // constant which supports all attributes.
103   if (value.isa<ElementsAttr>())
104     return builder.create<tosa::ConstOp>(loc, type, value.cast<ElementsAttr>());
105   return nullptr;
106 }
107 
108 //===----------------------------------------------------------------------===//
109 // Operator Canonicalizers.
110 //===----------------------------------------------------------------------===//
111 
112 struct ConcatOptimization : public OpRewritePattern<tosa::ConcatOp> {
113   using OpRewritePattern<tosa::ConcatOp>::OpRewritePattern;
114 
115   LogicalResult matchAndRewrite(tosa::ConcatOp op,
116                                 PatternRewriter &rewriter) const override {
117     if (op.input1().size() != 1)
118       return failure();
119     if (op.input1().front().getType() != op.getType()) {
120       rewriter
121           .replaceOpWithNewOp<tensor::CastOp>(op, op.getType(),
122                                               op.input1().front())
123           .getResult();
124       return success();
125     }
126 
127     rewriter.replaceOp(op, op.input1().front());
128     return success();
129   }
130 };
131 
132 void ConcatOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
133                                            MLIRContext *context) {
134   results.insert<ConcatOptimization>(context);
135 }
136 
137 struct ReshapeReshapeOptimization : public OpRewritePattern<tosa::ReshapeOp> {
138   using OpRewritePattern<tosa::ReshapeOp>::OpRewritePattern;
139 
140   LogicalResult matchAndRewrite(tosa::ReshapeOp op,
141                                 PatternRewriter &rewriter) const override {
142     Value input = op.input1();
143     Operation *definingOp = input.getDefiningOp();
144     if (!definingOp)
145       return failure();
146 
147     if (tosa::ReshapeOp reshapeOp = dyn_cast<tosa::ReshapeOp>(definingOp)) {
148       rewriter.replaceOpWithNewOp<tosa::ReshapeOp>(
149           op, op.getType(), reshapeOp.input1(), op.new_shape());
150       return success();
151     }
152 
153     return failure();
154   }
155 };
156 
157 struct ReshapeConstOptimization : public OpRewritePattern<tosa::ReshapeOp> {
158   using OpRewritePattern<tosa::ReshapeOp>::OpRewritePattern;
159 
160   LogicalResult matchAndRewrite(tosa::ReshapeOp op,
161                                 PatternRewriter &rewriter) const override {
162     Value input = op.input1();
163     ArrayAttr newShape = op.new_shape();
164 
165     // Check if input is constant
166     DenseElementsAttr inputAttr;
167     if (!matchPattern(input, m_Constant(&inputAttr)))
168       return failure();
169 
170     // Check if has >1 consumer and is not splat
171     if (!input.hasOneUse() && !inputAttr.isSplat())
172       return failure();
173 
174     // Grab the new shape
175     SmallVector<int64_t> newShapeValues = llvm::to_vector<6>(
176         llvm::map_range(newShape.getValue(), [](const Attribute &val) {
177           return val.cast<IntegerAttr>().getValue().getSExtValue();
178         }));
179 
180     // Build new const op with correct output shape
181     ShapedType inputShape = input.getType().cast<ShapedType>();
182     DenseElementsAttr outputAttr =
183         inputAttr.reshape(inputShape.clone(newShapeValues));
184     rewriter.replaceOpWithNewOp<tosa::ConstOp>(op, outputAttr.getType(),
185                                                outputAttr);
186     return success();
187   }
188 };
189 
190 void ReshapeOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
191                                             MLIRContext *context) {
192   results.insert<ReshapeReshapeOptimization>(context);
193   results.insert<ReshapeConstOptimization>(context);
194 }
195 
196 struct ConstantTransposeOptimization
197     : public OpRewritePattern<tosa::TransposeOp> {
198   using OpRewritePattern::OpRewritePattern;
199 
200   LogicalResult matchAndRewrite(tosa::TransposeOp op,
201                                 PatternRewriter &rewriter) const override {
202     auto outputType = op.getType().cast<ShapedType>();
203     ArrayRef<int64_t> outputShape = outputType.getShape();
204     // TOSA supports quantized types.
205     if (!outputType.getElementType().isIntOrIndexOrFloat())
206       return failure();
207 
208     DenseElementsAttr inputValues;
209     if (!matchPattern(op.input1(), m_Constant(&inputValues)))
210       return failure();
211     // Make sure the input is a constant that has a single user.
212     if (!llvm::hasSingleElement(op.input1().getDefiningOp()->getUsers()))
213       return failure();
214 
215     DenseIntElementsAttr permAttr;
216     if (!matchPattern(op.perms(), m_Constant(&permAttr)))
217       return failure();
218     auto permValues = llvm::to_vector<6>(llvm::map_range(
219         // TOSA allows both 32- and 64-bit integer tensors here.
220         permAttr.getValues<APInt>(),
221         [](const APInt &val) { return val.getZExtValue(); }));
222 
223     auto inputType = op.input1().getType().cast<ShapedType>();
224     ArrayRef<int64_t> inputShape = inputType.getShape();
225     int64_t numElements = inputType.getNumElements();
226 
227     SmallVector<Attribute, 4> outputValues;
228     outputValues.resize(numElements);
229 
230     // Transpose the input constant. Because we don't know its rank in advance,
231     // we need to loop over the range [0, element count) and delinearize the
232     // index.
233     auto attrValues = inputValues.getValues<Attribute>();
234     for (int srcLinearIndex = 0; srcLinearIndex < numElements;
235          ++srcLinearIndex) {
236       SmallVector<uint64_t, 6> srcIndices(inputType.getRank(), 0);
237       int totalCount = srcLinearIndex;
238       for (int dim = inputType.getRank() - 1; dim >= 0; --dim) {
239         srcIndices[dim] = totalCount % inputShape[dim];
240         totalCount /= inputShape[dim];
241       }
242 
243       SmallVector<uint64_t, 6> dstIndices(outputType.getRank(), 0);
244       for (int dim = outputType.getRank() - 1; dim >= 0; --dim)
245         dstIndices[dim] = srcIndices[permValues[dim]];
246 
247       uint64_t dstLinearIndex = dstIndices.front();
248       for (int dim = 1; dim < outputType.getRank(); ++dim)
249         dstLinearIndex = dstLinearIndex * outputShape[dim] + dstIndices[dim];
250 
251       outputValues[dstLinearIndex] = attrValues[srcIndices];
252     }
253 
254     rewriter.replaceOpWithNewOp<tosa::ConstOp>(
255         op, outputType, DenseElementsAttr::get(outputType, outputValues));
256     return success();
257   }
258 };
259 
260 struct NoOpOptimization : public OpRewritePattern<tosa::TransposeOp> {
261   using OpRewritePattern::OpRewritePattern;
262 
263   LogicalResult matchAndRewrite(tosa::TransposeOp op,
264                                 PatternRewriter &rewriter) const override {
265     auto perm = op.perms();
266 
267     DenseIntElementsAttr permAttr;
268     if (!matchPattern(perm, m_Constant(&permAttr))) {
269       return failure();
270     }
271 
272     SmallVector<int64_t> permValues = llvm::to_vector<6>(
273         llvm::map_range(permAttr.getValues<APInt>(),
274                         [](const APInt &val) { return val.getSExtValue(); }));
275 
276     for (int i = 0, s = permValues.size(); i < s; i++) {
277       if (i != permValues[i]) {
278         return failure();
279       }
280     }
281 
282     rewriter.replaceOp(op, op.input1());
283     return success();
284   }
285 };
286 
287 void TransposeOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
288                                               MLIRContext *context) {
289   results.insert<ConstantTransposeOptimization>(context);
290   results.insert<NoOpOptimization>(context);
291 }
292 
293 struct AddZeroOptimization : public OpRewritePattern<tosa::AddOp> {
294   using OpRewritePattern::OpRewritePattern;
295 
296   LogicalResult matchAndRewrite(tosa::AddOp op,
297                                 PatternRewriter &rewriter) const override {
298     auto input1 = op.input1();
299     auto input2 = op.input2();
300 
301     DenseElementsAttr input1Attr;
302     if (matchPattern(input1, m_Constant(&input1Attr)) && input1Attr.isSplat() &&
303         input2.getType() == op.getType()) {
304       if (input1Attr.getType().getElementType().isa<IntegerType>() &&
305           input1Attr.getSplatValue<APInt>().isZero()) {
306         rewriter.replaceOp(op, op.input2());
307         return success();
308       }
309     }
310 
311     DenseElementsAttr input2Attr;
312     if (matchPattern(input2, m_Constant(&input2Attr)) && input2Attr.isSplat() &&
313         input1.getType() == op.getType()) {
314       if (input2Attr.getType().getElementType().isa<IntegerType>() &&
315           input2Attr.getSplatValue<APInt>().isZero()) {
316         rewriter.replaceOp(op, op.input1());
317         return success();
318       }
319     }
320 
321     return failure();
322   }
323 };
324 
325 void AddOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
326                                         MLIRContext *context) {
327   results.insert<AddZeroOptimization>(context);
328 }
329 
330 struct MulOneOptimization : public OpRewritePattern<tosa::MulOp> {
331   using OpRewritePattern::OpRewritePattern;
332 
333   LogicalResult matchAndRewrite(tosa::MulOp op,
334                                 PatternRewriter &rewriter) const override {
335     auto input1 = op.input1();
336     auto input2 = op.input2();
337 
338     DenseElementsAttr input1Attr;
339     if (matchPattern(input1, m_Constant(&input1Attr)) && input1Attr.isSplat() &&
340         input2.getType() == op.getType()) {
341       if (input1Attr.getType().getElementType().isa<FloatType>() &&
342           input1Attr.getSplatValue<APFloat>().isExactlyValue(1)) {
343         rewriter.replaceOp(op, op.input2());
344         return success();
345       }
346 
347       if (input1Attr.getType().getElementType().isa<IntegerType>() &&
348           matchPattern(input1, m_One())) {
349         rewriter.replaceOp(op, op.input2());
350         return success();
351       }
352     }
353 
354     DenseElementsAttr input2Attr;
355     if (matchPattern(input2, m_Constant(&input2Attr)) && input2Attr.isSplat() &&
356         input1.getType() == op.getType()) {
357       if (input2Attr.getType().getElementType().isa<FloatType>() &&
358           input2Attr.getSplatValue<APFloat>().isExactlyValue(1)) {
359         rewriter.replaceOp(op, op.input1());
360         return success();
361       }
362 
363       if (input2Attr.getType().getElementType().isa<IntegerType>() &&
364           matchPattern(input2, m_One())) {
365         rewriter.replaceOp(op, op.input1());
366         return success();
367       }
368     }
369 
370     return failure();
371   }
372 };
373 
374 void MulOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
375                                         MLIRContext *context) {
376   results.insert<MulOneOptimization>(context);
377 }
378 
379 struct MaterializePadValue : public OpRewritePattern<tosa::PadOp> {
380   using OpRewritePattern::OpRewritePattern;
381 
382   LogicalResult matchAndRewrite(tosa::PadOp op,
383                                 PatternRewriter &rewriter) const override {
384     if (op.pad_const())
385       return failure();
386 
387     auto input = op.input1();
388     auto padding = op.padding();
389 
390     ShapedType inputTy = input.getType().cast<ShapedType>();
391     Type elementTy = inputTy.getElementType();
392 
393     Attribute constantAttr;
394     if (elementTy.isa<FloatType>())
395       constantAttr = rewriter.getFloatAttr(elementTy, 0.0);
396     else if (elementTy.isa<IntegerType>() && !op.quantization_info())
397       constantAttr = rewriter.getIntegerAttr(elementTy, 0);
398     else if (elementTy.isa<IntegerType>() && op.quantization_info()) {
399       auto value = op.quantization_info().getValue().input_zp().getValue();
400       constantAttr = rewriter.getIntegerAttr(elementTy, value.getZExtValue());
401     }
402 
403     if (!constantAttr) {
404       return rewriter.notifyMatchFailure(
405           op,
406           "tosa.pad to linalg lowering encountered an unknown element type");
407     }
408 
409     auto denseAttr = DenseElementsAttr::get(
410         RankedTensorType::get({}, elementTy), constantAttr);
411     auto constantVal = rewriter.create<tosa::ConstOp>(
412         op.getLoc(), denseAttr.getType(), denseAttr);
413 
414     rewriter.replaceOpWithNewOp<tosa::PadOp>(
415         op, op.getType(), ValueRange{input, padding, constantVal},
416         op->getAttrs());
417     return success();
418   }
419 };
420 
421 void PadOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
422                                         MLIRContext *context) {
423   results.insert<MaterializePadValue>(context);
424 }
425 
426 struct MaxPool2dIsNoOp : public OpRewritePattern<tosa::MaxPool2dOp> {
427   using OpRewritePattern::OpRewritePattern;
428 
429   LogicalResult matchAndRewrite(tosa::MaxPool2dOp op,
430                                 PatternRewriter &rewriter) const override {
431     Value input = op.input();
432     Value output = op.output();
433     ShapedType inputType = input.getType().cast<ShapedType>();
434     ShapedType outputType = output.getType().cast<ShapedType>();
435 
436     if (!inputType.hasStaticShape() || !outputType.hasStaticShape()) {
437       return failure();
438     }
439 
440     // If the output and input shapes are 1x1, then this is a no op.
441     ArrayRef<int64_t> outputShape = outputType.getShape();
442     if (outputShape[1] != 1 || outputShape[2] != 1) {
443       return failure();
444     }
445 
446     ArrayRef<int64_t> inputShape = inputType.getShape();
447     if (inputShape[1] != 1 || inputShape[2] != 1) {
448       return failure();
449     }
450 
451     rewriter.replaceOp(op, input);
452     return success();
453   }
454 };
455 
456 void MaxPool2dOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
457                                               MLIRContext *context) {
458   results.insert<MaxPool2dIsNoOp>(context);
459 }
460 
461 struct ClampIsNoOp : public OpRewritePattern<tosa::ClampOp> {
462   using OpRewritePattern::OpRewritePattern;
463 
464   LogicalResult matchAndRewrite(tosa::ClampOp op,
465                                 PatternRewriter &rewriter) const override {
466     Value input = op.input();
467     auto inputType = op.input().getType().template dyn_cast<RankedTensorType>();
468     auto inputElementType = inputType.getElementType();
469 
470     if (!inputType.hasStaticShape()) {
471       return failure();
472     }
473 
474     if (inputElementType.isF32()) {
475       auto minClamp = op.min_fp();
476       auto maxClamp = op.max_fp();
477       bool isMin = (minClamp.isLargest() || minClamp.isInfinity()) &&
478                    minClamp.isNegative();
479       bool isMax = (maxClamp.isLargest() || maxClamp.isInfinity()) &&
480                    !maxClamp.isNegative();
481 
482       if (isMin && isMax) {
483         rewriter.replaceOp(op, input);
484         return success();
485       }
486       return failure();
487     }
488 
489     if (inputElementType.isUnsignedInteger()) {
490       int64_t minClamp = op.min_int();
491       int64_t maxClamp = op.max_int();
492 
493       int64_t intMin =
494           APInt::getMinValue(inputElementType.getIntOrFloatBitWidth())
495               .getZExtValue();
496       int64_t intMax =
497           APInt::getMaxValue(inputElementType.getIntOrFloatBitWidth())
498               .getZExtValue();
499 
500       if (minClamp <= intMin && maxClamp >= intMax) {
501         rewriter.replaceOp(op, input);
502         return success();
503       }
504       return failure();
505     }
506 
507     if (inputElementType.isa<IntegerType>()) {
508       int64_t minClamp = op.min_int();
509       int64_t maxClamp = op.max_int();
510 
511       int64_t intMin =
512           APInt::getSignedMinValue(inputElementType.getIntOrFloatBitWidth())
513               .getSExtValue();
514       int64_t intMax =
515           APInt::getSignedMaxValue(inputElementType.getIntOrFloatBitWidth())
516               .getSExtValue();
517 
518       if (minClamp <= intMin && maxClamp >= intMax) {
519         rewriter.replaceOp(op, input);
520         return success();
521       }
522       return failure();
523     }
524 
525     return failure();
526   }
527 };
528 
529 void ClampOp::getCanonicalizationPatterns(OwningRewritePatternList &results,
530                                           MLIRContext *context) {
531   results.insert<ClampIsNoOp>(context);
532 }
533 
534 //===----------------------------------------------------------------------===//
535 // Operator Folders.
536 //===----------------------------------------------------------------------===//
537 
538 OpFoldResult CastOp::fold(ArrayRef<Attribute> operands) {
539   if (input().getType() == getType())
540     return input();
541   return {};
542 }
543 
544 OpFoldResult ConstOp::fold(ArrayRef<Attribute> operands) {
545   assert(operands.empty() && "constant has no operands");
546   return valueAttr();
547 }
548 
549 #define ReduceFolder(OP)                                                       \
550   OpFoldResult OP::fold(ArrayRef<Attribute> operands) {                        \
551     ShapedType inputTy = input().getType().cast<ShapedType>();                 \
552     if (!inputTy.hasRank())                                                    \
553       return {};                                                               \
554     if (inputTy.getDimSize(axis()) == 1)                                       \
555       return input();                                                          \
556     return {};                                                                 \
557   }
558 
559 ReduceFolder(ReduceAllOp) ReduceFolder(ReduceAnyOp) ReduceFolder(ReduceMaxOp)
560     ReduceFolder(ReduceMinOp) ReduceFolder(ReduceProdOp)
561         ReduceFolder(ReduceSumOp)
562 #undef ReduceFolder
563 
564             OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) {
565   auto inputTy = input1().getType().dyn_cast<RankedTensorType>();
566   auto outputTy = getType().dyn_cast<RankedTensorType>();
567 
568   if (!inputTy || !outputTy || inputTy != outputTy)
569     return {};
570   return input1();
571 }
572 
573 OpFoldResult PadOp::fold(ArrayRef<Attribute> operands) {
574   // If the pad is all zeros we can fold this operation away.
575   if (operands[1]) {
576     auto densePad = operands[1].cast<DenseElementsAttr>();
577     if (densePad.isSplat() && densePad.getSplatValue<APInt>().isZero()) {
578       return input1();
579     }
580   }
581 
582   return {};
583 }
584 
585 OpFoldResult SliceOp::fold(ArrayRef<Attribute> operands) {
586   auto inputTy = input().getType().dyn_cast<RankedTensorType>();
587   auto outputTy = getType().dyn_cast<RankedTensorType>();
588 
589   if (!inputTy || !outputTy || inputTy != outputTy)
590     return {};
591   if (inputTy.hasStaticShape())
592     return input();
593 
594   return {};
595 }
596 
597 OpFoldResult TileOp::fold(ArrayRef<Attribute> operands) {
598   bool allOnes = true;
599   for (Attribute val : multiples().getValue()) {
600     allOnes = allOnes && val.cast<IntegerAttr>().getValue().getSExtValue() == 1;
601   }
602 
603   if (allOnes && input1().getType() == getType())
604     return input1();
605   return {};
606 }
607 
608 OpFoldResult TransposeOp::fold(ArrayRef<Attribute> operands) {
609   if (!operands[1])
610     return {};
611 
612   // Transposing splat values just means reshaping.
613   if (auto input = operands[0].dyn_cast_or_null<DenseElementsAttr>()) {
614     if (input.isSplat())
615       return input.reshape(getType().cast<ShapedType>());
616   }
617 
618   auto perms = llvm::to_vector<6>(llvm::map_range(
619       operands[1].cast<DenseIntElementsAttr>().getValues<APInt>(),
620       [](const APInt &val) { return val.getSExtValue(); }));
621 
622   if (llvm::equal(llvm::seq<int64_t>(0, perms.size()), perms) &&
623       input1().getType() == getType())
624     return input1();
625   return {};
626 }
627 
628 //===----------------------------------------------------------------------===//
629 // TOSA Operator Verifiers.
630 //===----------------------------------------------------------------------===//
631 
632 template <typename T>
633 static LogicalResult verifyConvOp(T op) {
634   // All TOSA conv ops have an input() and weight().
635   auto inputType = op.input().getType().template dyn_cast<RankedTensorType>();
636   auto weightType = op.weight().getType().template dyn_cast<RankedTensorType>();
637 
638   // Must be ranked tensor types
639   if (!inputType) {
640     op.emitOpError("expect a ranked tensor for input, got ") << op.input();
641     return failure();
642   }
643   if (!weightType) {
644     op.emitOpError("expect a ranked tensor for weight, got ") << op.weight();
645     return failure();
646   }
647 
648   auto inputEType = inputType.getElementType();
649   auto weightEType = weightType.getElementType();
650 
651   bool inputIsQuant = !inputEType.template isa<FloatType>();
652   bool weightIsQuant = !weightEType.template isa<FloatType>();
653 
654   // Either both must be quantized or both unquantized.
655   if (inputIsQuant != weightIsQuant) {
656     op.emitOpError(
657         "expect both input and weight to be float or not together, got ")
658         << inputEType << " and " << weightEType;
659     return failure();
660   }
661 
662   // Quantized type must have constructed the quantizationattr, and unquantized
663   // types should not have a quantizationattr.
664   if ((inputIsQuant && !op.quantization_info()) ||
665       (!inputIsQuant && op.quantization_info())) {
666     op.emitOpError("quantizationattr is required for quantized type, and not "
667                    "allowed for float type");
668     return failure();
669   }
670 
671   return success();
672 }
673 
674 static LogicalResult verifyAveragePoolOp(tosa::AvgPool2dOp op) {
675   auto inputETy = op.input().getType().cast<ShapedType>().getElementType();
676   auto resultETy = op.getType().cast<ShapedType>().getElementType();
677 
678   if (auto quantType = inputETy.dyn_cast<mlir::quant::UniformQuantizedType>())
679     inputETy = quantType.getStorageType();
680 
681   if (auto quantType = resultETy.dyn_cast<mlir::quant::UniformQuantizedType>())
682     resultETy = quantType.getStorageType();
683 
684   if (inputETy.isF32() && resultETy.isF32())
685     return success();
686   if (inputETy.isInteger(8) && resultETy.isInteger(8))
687     return success();
688   if (inputETy.isInteger(16) && resultETy.isInteger(16))
689     return success();
690 
691   return op.emitOpError("input/output element types are incompatible.");
692 }
693 
694 //===----------------------------------------------------------------------===//
695 // TOSA Operator Quantization Builders.
696 //===----------------------------------------------------------------------===//
697 
698 /// This builder is called on all convolution operators except TransposeConv,
699 /// which has specialized output shape semantics. The builder also defines the
700 /// bitwidth of the output given the bit width of the input & weight content.
701 static void buildConvOpWithQuantInfo(OpBuilder &builder, OperationState &result,
702                                      Type outputType, Value input, Value weight,
703                                      Value bias, ArrayAttr pad,
704                                      ArrayAttr stride, ArrayAttr dilation) {
705 
706   result.addOperands({input, weight, bias});
707   result.addAttribute("pad", pad);
708   result.addAttribute("stride", stride);
709   result.addAttribute("dilation", dilation);
710 
711   auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight);
712   if (quantAttr) {
713     result.addAttribute("quantization_info", quantAttr);
714     result.addTypes(
715         buildConvOpResultTypeInfo(builder, outputType, input, weight));
716   } else {
717     result.addTypes(outputType);
718   }
719 }
720 
721 /// Handles tosa.transpose_conv2d which has outpad and output shape attributes.
722 static void
723 buildTransConvOpWithQuantInfo(OpBuilder &builder, OperationState &result,
724                               Type outputType, Value input, Value weight,
725                               Value bias, ArrayAttr outpad, ArrayAttr stride,
726                               ArrayAttr dilation, ArrayAttr outputShape) {
727   result.addOperands({input, weight, bias});
728   result.addAttribute("out_pad", outpad);
729   result.addAttribute("stride", stride);
730   result.addAttribute("dilation", dilation);
731   result.addAttribute("out_shape", outputShape);
732   auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight);
733 
734   if (quantAttr) {
735     result.addAttribute("quantization_info", quantAttr);
736     result.addTypes(
737         buildConvOpResultTypeInfo(builder, outputType, input, weight));
738   } else {
739     result.addTypes(outputType);
740   }
741 }
742 
743 /// The tosa.fully_connected op has its own builder as it does not have
744 /// strides/dilation/padding.
745 static void buildFCOpWithQuantInfo(OpBuilder &builder, OperationState &result,
746                                    Type outputType, Value input, Value weight,
747                                    Value bias) {
748 
749   result.addOperands({input, weight, bias});
750   auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight);
751   if (quantAttr) {
752     result.addAttribute("quantization_info", quantAttr);
753     result.addTypes(
754         buildConvOpResultTypeInfo(builder, outputType, input, weight));
755   } else {
756     result.addTypes(outputType);
757   }
758 }
759 
760 /// The tosa.matmul op is also intended to be generated where a fully_connected
761 /// op must be constructed where the weight is not a constant. In this case,
762 /// the fully_connected op must be expressed using matmul.
763 /// TODO: Add link to the leglization document explaining this.
764 static void buildMatMulOpWithQuantInfo(OpBuilder &builder,
765                                        OperationState &result, Type outputType,
766                                        Value a, Value b) {
767   result.addOperands({a, b});
768   auto quantAttr = ::buildMatMulOpQuantizationAttr(builder, a, b);
769 
770   if (quantAttr) {
771     result.addAttribute("quantization_info", quantAttr);
772 
773     auto inputType = a.getType().dyn_cast<ShapedType>();
774     assert(inputType && "Input must be a shaped tensor type!");
775 
776     auto inputQType = inputType.getElementType()
777                           .dyn_cast<mlir::quant::UniformQuantizedType>();
778     assert(inputQType && "Tensor must have quantized datatype!");
779 
780     unsigned inputBits = inputQType.getStorageTypeIntegralWidth();
781 
782     auto outputShapedType = outputType.dyn_cast<ShapedType>();
783     assert(outputShapedType && "Output must be a shaped type");
784 
785     IntegerType accElementType;
786     if (inputBits == 16)
787       accElementType = builder.getIntegerType(48);
788     else
789       accElementType = builder.getI32Type();
790     auto accType = outputShapedType.clone(accElementType);
791     result.addTypes(accType);
792   } else {
793     result.addTypes(outputType);
794   }
795 }
796 
797 /// Both the tosa.avg_pool2d and unary ops use the same UnaruOpQuantizationAttr
798 /// but avg_pool operator has its own builder as it has additional parameters
799 /// not part of the unary ops.
800 static void buildAvgPool2dOpWithQuantInfo(OpBuilder &builder,
801                                           OperationState &result,
802                                           Type outputType, Value input,
803                                           ArrayAttr kernel, ArrayAttr stride,
804                                           ArrayAttr pad) {
805   result.addOperands(input);
806   result.addAttribute("kernel", kernel);
807   result.addAttribute("stride", stride);
808   result.addAttribute("pad", pad);
809   auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType);
810   if (quantAttr)
811     result.addAttribute("quantization_info", quantAttr);
812   result.types.push_back(outputType);
813 }
814 
815 /// This builder is called on single-parameter unary operators that have scale
816 /// relationship between their input and output, expressed by the
817 /// UnaryOpQuantizationAttr.
818 static void buildUnaryOpWithQuantInfo(OpBuilder &builder,
819                                       OperationState &result, Type outputType,
820                                       Value input) {
821   result.addOperands(input);
822   auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType);
823   if (quantAttr)
824     result.addAttribute("quantization_info", quantAttr);
825   result.types.push_back(outputType);
826 }
827 
828 /// This builder is called on TOSA pad operator that needs to create its own
829 /// OptionalAttr quantization_attr parameter to scale the padding values
830 /// correctly. No pad_const is interpreted as zero-padding.
831 static void buildPadOpWithQuantInfo(OpBuilder &builder, OperationState &result,
832                                     Type outputType, Value input,
833                                     Value paddings) {
834   result.addOperands({input, paddings});
835   auto quantAttr = buildPadOpQuantizationAttr(builder, input);
836   if (quantAttr)
837     result.addAttribute("quantization_info", quantAttr);
838   result.types.push_back(outputType);
839 }
840 
841 /// This builder is called on TOSA pad operator when an explicit pad_const
842 /// value is passed in. It also optionally constructs quantization_attr.
843 static void buildExplicitValuePadOpWithQuantInfo(OpBuilder &builder,
844                                                  OperationState &result,
845                                                  Type outputType, Value input,
846                                                  Value paddings,
847                                                  Value padConst) {
848   result.addOperands({input, paddings, padConst});
849   auto quantAttr = buildPadOpQuantizationAttr(builder, input);
850   if (quantAttr)
851     result.addAttribute("quantization_info", quantAttr);
852   result.types.push_back(outputType);
853 }
854 
855 //===----------------------------------------------------------------------===//
856 // TOSA Operator Return Type Inference.
857 //===----------------------------------------------------------------------===//
858 
859 static void getI64Values(ArrayAttr arrayAttr, SmallVector<int64_t> &values) {
860   for (auto it : arrayAttr) {
861     values.push_back(it.cast<IntegerAttr>().getValue().getSExtValue());
862   }
863 }
864 
865 static void getF64Values(ArrayAttr arrayAttr, SmallVector<double> &values) {
866   for (auto it : arrayAttr) {
867     values.push_back(it.cast<FloatAttr>().getValueAsDouble());
868   }
869 }
870 
871 LogicalResult tosa::ArgMaxOp::inferReturnTypeComponents(
872     MLIRContext *context, ::llvm::Optional<Location> location,
873     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
874     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
875   ShapeAdaptor inputShape = operands.getShape(0);
876   IntegerAttr axis = attributes.get("axis").cast<IntegerAttr>();
877   int32_t axisVal = axis.getValue().getSExtValue();
878 
879   if (!inputShape.hasRank()) {
880     inferredReturnShapes.push_back(ShapedTypeComponents());
881     return success();
882   }
883 
884   SmallVector<int64_t> outShape;
885   outShape.reserve(inputShape.getRank() - 1);
886   for (int i = 0, s = inputShape.getRank(); i < s; i++) {
887     if (i == axisVal)
888       continue;
889     outShape.push_back(inputShape.getDimSize(i));
890   }
891 
892   inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
893   return success();
894 }
895 
896 LogicalResult tosa::ConcatOp::inferReturnTypeComponents(
897     MLIRContext *context, ::llvm::Optional<Location> location,
898     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
899     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
900   // Infer all dimension sizes by reducing based on inputs.
901   int32_t axis =
902       attributes.get("axis").cast<IntegerAttr>().getValue().getSExtValue();
903   llvm::SmallVector<int64_t> outputShape;
904   bool hasRankedInput = false;
905   for (auto operand : operands) {
906     ShapeAdaptor operandShape = operands.getShape(operand);
907     if (!operandShape.hasRank())
908       continue;
909 
910     // Copy the Operand's rank.
911     if (!hasRankedInput)
912       outputShape.resize(operandShape.getRank(), ShapedType::kDynamicSize);
913 
914     // Copy shapes until the dim is non-dynamic.
915     for (int i = 0, s = operandShape.getRank(); i < s; i++) {
916       if (i == axis || operandShape.isDynamicDim(i))
917         continue;
918       if (outputShape[i] == ShapedType::kDynamicSize)
919         outputShape[i] = operandShape.getDimSize(i);
920       if (outputShape[i] != operandShape.getDimSize(i))
921         return failure();
922     }
923 
924     hasRankedInput = true;
925   }
926 
927   if (!hasRankedInput) {
928     inferredReturnShapes.push_back(ShapedTypeComponents());
929     return success();
930   }
931 
932   // Determine the dimension size along the concatenation axis.
933   int concatDimSize = 0;
934   for (auto operand : operands) {
935     ShapeAdaptor operandShape = operands.getShape(operand);
936 
937     // We need to know the length of the concatenation axis of all inputs to
938     // determine the dimension size of the output shape.
939     if (!operandShape.hasRank() || operandShape.isDynamicDim(axis)) {
940       concatDimSize = ShapedType::kDynamicSize;
941       break;
942     }
943 
944     concatDimSize += operandShape.getDimSize(axis);
945   }
946 
947   outputShape[axis] = concatDimSize;
948 
949   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
950   return success();
951 }
952 
953 LogicalResult tosa::FullyConnectedOp::inferReturnTypeComponents(
954     MLIRContext *context, ::llvm::Optional<Location> location,
955     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
956     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
957   ShapeAdaptor inputShape = operands.getShape(0);
958   ShapeAdaptor weightShape = operands.getShape(1);
959   ShapeAdaptor biasShape = operands.getShape(2);
960 
961   // All shapes are dynamic.
962   SmallVector<int64_t> outShape;
963   outShape.resize(2, ShapedType::kDynamicSize);
964 
965   if (inputShape.hasRank()) {
966     outShape[0] = inputShape.getDimSize(0);
967   }
968 
969   if (weightShape.hasRank()) {
970     outShape[1] = weightShape.getDimSize(0);
971   }
972 
973   if (biasShape.hasRank()) {
974     outShape[1] = outShape[1] == ShapedType::kDynamicSize
975                       ? biasShape.getDimSize(0)
976                       : outShape[1];
977   }
978 
979   inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
980   return success();
981 }
982 
983 LogicalResult tosa::MatMulOp::inferReturnTypeComponents(
984     MLIRContext *context, ::llvm::Optional<Location> location,
985     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
986     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
987   ShapeAdaptor lhsShape = operands.getShape(0);
988   ShapeAdaptor rhsShape = operands.getShape(1);
989 
990   // All shapes are dynamic.
991   SmallVector<int64_t> outShape;
992   outShape.resize(3, ShapedType::kDynamicSize);
993 
994   if (lhsShape.hasRank()) {
995     outShape[0] = lhsShape.getDimSize(0);
996     outShape[1] = lhsShape.getDimSize(1);
997   }
998 
999   if (rhsShape.hasRank()) {
1000     outShape[0] = outShape[0] == ShapedType::kDynamicSize
1001                       ? rhsShape.getDimSize(0)
1002                       : outShape[0];
1003     outShape[2] = rhsShape.getDimSize(2);
1004   }
1005 
1006   inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
1007   return success();
1008 }
1009 
1010 LogicalResult tosa::PadOp::inferReturnTypeComponents(
1011     MLIRContext *context, ::llvm::Optional<Location> location,
1012     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1013     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1014   ShapeAdaptor inputShape = operands.getShape(0);
1015   ShapeAdaptor paddingShape = operands.getShape(1);
1016   SmallVector<int64_t> outputShape;
1017 
1018   // If both inputs have unknown shape, we cannot determine the shape of the
1019   // output.
1020   if (!inputShape.hasRank() && !paddingShape.hasRank()) {
1021     inferredReturnShapes.push_back(ShapedTypeComponents());
1022     return success();
1023   }
1024 
1025   // If the input rank is unknown we can info the output rank using the padding
1026   // shape's first dim.
1027   if (!inputShape.hasRank()) {
1028     if (paddingShape.isDynamicDim(0)) {
1029       inferredReturnShapes.push_back(ShapedTypeComponents());
1030       return success();
1031     }
1032 
1033     outputShape.resize(paddingShape.getDimSize(0), ShapedType::kDynamicSize);
1034     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1035     return success();
1036   }
1037 
1038   DenseIntElementsAttr paddings;
1039   // If the paddings value is not a constant, all dimensions must be dynamic.
1040   if (!matchPattern(operands[1], m_Constant(&paddings))) {
1041     outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize);
1042     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1043     return success();
1044   }
1045 
1046   SmallVector<int64_t> paddingValues;
1047   for (auto val : paddings) {
1048     paddingValues.push_back(val.getSExtValue());
1049   }
1050 
1051   outputShape.reserve(inputShape.getRank());
1052   for (int i = 0, s = inputShape.getRank(); i < s; i++) {
1053     if (inputShape.isDynamicDim(i)) {
1054       outputShape.push_back(ShapedType::kDynamicSize);
1055       continue;
1056     }
1057 
1058     outputShape.push_back(inputShape.getDimSize(i) + paddingValues[i * 2] +
1059                           paddingValues[i * 2 + 1]);
1060   }
1061 
1062   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1063   return success();
1064 }
1065 
1066 LogicalResult tosa::SliceOp::inferReturnTypeComponents(
1067     MLIRContext *context, ::llvm::Optional<Location> location,
1068     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1069     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1070   ArrayAttr sizes = SliceOpAdaptor(operands, attributes).size();
1071   SmallVector<int64_t> outputShape;
1072   outputShape.reserve(sizes.size());
1073   for (auto val : sizes) {
1074     outputShape.push_back(val.cast<IntegerAttr>().getValue().getSExtValue());
1075   }
1076 
1077   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1078   return success();
1079 }
1080 
1081 LogicalResult tosa::TableOp::inferReturnTypeComponents(
1082     MLIRContext *context, ::llvm::Optional<Location> location,
1083     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1084     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1085   ShapeAdaptor inputShape = operands.getShape(0);
1086 
1087   if (!inputShape.hasRank()) {
1088     inferredReturnShapes.push_back(ShapedTypeComponents());
1089     return success();
1090   }
1091 
1092   inferredReturnShapes.resize(1);
1093   inputShape.getDims(inferredReturnShapes[0]);
1094   return success();
1095 }
1096 
1097 LogicalResult tosa::TileOp::inferReturnTypeComponents(
1098     MLIRContext *context, ::llvm::Optional<Location> location,
1099     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1100     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1101   TileOpAdaptor adaptor(operands, attributes);
1102   ArrayAttr multiples = adaptor.multiples();
1103   ShapeAdaptor inputShape = operands.getShape(0);
1104   SmallVector<int64_t> outputShape;
1105   if (!inputShape.hasRank()) {
1106     outputShape.resize(multiples.size(), ShapedType::kDynamicSize);
1107     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1108     return success();
1109   }
1110 
1111   // We need the multiple values to determine the output shape.
1112   SmallVector<int64_t> multipleValues;
1113   multipleValues.reserve(multiples.size());
1114   for (auto val : multiples) {
1115     multipleValues.push_back(val.cast<IntegerAttr>().getValue().getSExtValue());
1116   }
1117 
1118   // Any non dynamic dimension can be multiplied to a known size.
1119   outputShape.reserve(multiples.size());
1120   for (int i = 0, s = inputShape.getRank(); i < s; i++) {
1121     int dim = inputShape.getDimSize(i);
1122     if (dim != ShapedType::kDynamicSize)
1123       dim *= multipleValues[i];
1124     outputShape.push_back(dim);
1125   }
1126 
1127   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1128   return success();
1129 }
1130 
1131 LogicalResult tosa::ReshapeOp::inferReturnTypeComponents(
1132     MLIRContext *context, ::llvm::Optional<Location> location,
1133     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1134     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1135   ReshapeOpAdaptor adaptor(operands, attributes);
1136   ShapeAdaptor inputShape = operands.getShape(0);
1137 
1138   ArrayAttr newShape = adaptor.new_shape();
1139   llvm::SmallVector<int64_t> newShapeValue;
1140   getI64Values(newShape, newShapeValue);
1141 
1142   // We cannot infer from the total number of elements so we must take the
1143   // shape attribute as exact.
1144   if (!inputShape.hasRank() || !inputShape.hasStaticShape()) {
1145     inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue));
1146     return success();
1147   }
1148 
1149   // Determine the number of elements covered by the slice of all static
1150   // dimensions. This allows us to infer the length of the remaining dynamic
1151   // dimension.
1152   int64_t numElements = inputShape.getNumElements();
1153   int64_t staticMul = 1;
1154   for (auto val : newShapeValue) {
1155     if (val != ShapedType::kDynamicSize) {
1156       staticMul *= val;
1157     }
1158   }
1159 
1160   // Determine the length of the dynamic dimension.
1161   for (auto &val : newShapeValue) {
1162     if (val == ShapedType::kDynamicSize)
1163       val = numElements / staticMul;
1164   }
1165 
1166   inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue));
1167   return success();
1168 }
1169 
1170 LogicalResult tosa::TransposeOp::inferReturnTypeComponents(
1171     MLIRContext *context, ::llvm::Optional<Location> location,
1172     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1173     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1174   ShapeAdaptor inputShape = operands.getShape(0);
1175   ShapeAdaptor permsShape = operands.getShape(1);
1176 
1177   // If input rank and permutation length is unknown, the output rank is
1178   // unknown.
1179   if (!inputShape.hasRank() || !permsShape.hasRank() ||
1180       permsShape.isDynamicDim(0)) {
1181     inferredReturnShapes.push_back(ShapedTypeComponents());
1182     return success();
1183   }
1184 
1185   // This would imply the number of permutations does not match the rank of the
1186   // input which is illegal.
1187   if (permsShape.getDimSize(0) != inputShape.getRank()) {
1188     return failure();
1189   }
1190 
1191   // Without the input dims we cannot determine the output dim sizes but we
1192   // can determine the output rank.
1193   SmallVector<int64_t> outputShape;
1194   if (!inputShape.hasRank()) {
1195     outputShape.resize(permsShape.getDimSize(0), ShapedType::kDynamicSize);
1196     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1197     return success();
1198   }
1199 
1200   // Rank-0 means no permutations matter.
1201   if (inputShape.getRank() == 0) {
1202     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1203     return success();
1204   }
1205 
1206   // Check whether the input dimensions are all the same.
1207   bool allTheSame = true;
1208   for (int i = 1, s = inputShape.getRank(); i < s; i++) {
1209     if (inputShape.getDimSize(0) != inputShape.getDimSize(i)) {
1210       allTheSame = false;
1211       break;
1212     }
1213   }
1214 
1215   // If all of the input dimensions are the same we don't care about the
1216   // permutation.
1217   if (allTheSame) {
1218     outputShape.resize(inputShape.getRank(), inputShape.getDimSize(0));
1219     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1220     return success();
1221   }
1222 
1223   outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize);
1224   // If the permuations are a constant we can directly determine the output
1225   // shape.
1226   if (ShapeAdaptor permShape = operands.getValueAsShape(1)) {
1227     outputShape.reserve(inputShape.getRank());
1228     for (int i = 0, s = inputShape.getRank(); i < s; i++) {
1229       outputShape[i] = inputShape.getDimSize(permShape.getDimSize(i));
1230     }
1231   }
1232 
1233   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1234   return success();
1235 }
1236 
1237 LogicalResult tosa::GatherOp::inferReturnTypeComponents(
1238     MLIRContext *context, ::llvm::Optional<Location> location,
1239     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1240     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1241   llvm::SmallVector<int64_t> outputShape;
1242   outputShape.resize(3, ShapedType::kDynamicSize);
1243 
1244   ShapeAdaptor valuesShape = operands.getShape(0);
1245   if (valuesShape.hasRank()) {
1246     outputShape[0] = valuesShape.getDimSize(0);
1247     outputShape[2] = valuesShape.getDimSize(2);
1248   }
1249 
1250   ShapeAdaptor indicesShape = operands.getShape(1);
1251   if (indicesShape.hasRank()) {
1252     if (outputShape[0] == ShapedType::kDynamicSize)
1253       outputShape[0] = indicesShape.getDimSize(0);
1254     if (outputShape[1] == ShapedType::kDynamicSize)
1255       outputShape[1] = indicesShape.getDimSize(1);
1256   }
1257 
1258   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1259   return success();
1260 }
1261 
1262 LogicalResult tosa::ResizeOp::inferReturnTypeComponents(
1263     MLIRContext *context, ::llvm::Optional<Location> location,
1264     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1265     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1266   ResizeOpAdaptor adaptor(operands, attributes);
1267   llvm::SmallVector<int64_t, 4> outputShape;
1268   outputShape.resize(4, ShapedType::kDynamicSize);
1269 
1270   int32_t inHeight = ShapedType::kDynamicSize;
1271   int32_t inWidth = ShapedType::kDynamicSize;
1272 
1273   ShapeAdaptor inputShape = operands.getShape(adaptor.input());
1274   if (inputShape.hasRank()) {
1275     outputShape[0] = inputShape.getDimSize(0);
1276     outputShape[3] = inputShape.getDimSize(3);
1277 
1278     inHeight = inputShape.getDimSize(1);
1279     inWidth = inputShape.getDimSize(2);
1280   }
1281 
1282   int32_t shift = adaptor.shift();
1283   llvm::SmallVector<int64_t> newShape;
1284   getI64Values(adaptor.output_size(), newShape);
1285   outputShape[1] = newShape[0];
1286   outputShape[2] = newShape[1];
1287 
1288   llvm::SmallVector<int64_t> strideInt;
1289   llvm::SmallVector<int64_t> offsetInt;
1290   llvm::SmallVector<double> strideFp;
1291   llvm::SmallVector<double> offsetFp;
1292   getI64Values(adaptor.offset(), offsetInt);
1293   getF64Values(adaptor.offset_fp(), offsetFp);
1294   getI64Values(adaptor.stride(), strideInt);
1295   getF64Values(adaptor.stride_fp(), strideFp);
1296 
1297   // If we have a 0 zero in integers we know that the resize indexing needs to
1298   // be performed in floating point. Use the floating point varient to compute
1299   // the resize shape.
1300   bool fpMode = strideInt[0] == 0;
1301 
1302   // We can compute the output shape if attribute specifies unknown dimensions
1303   // based on the offset and stride. If we perfectly line up to the last index
1304   // we need to round up the size to include it.
1305   if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && fpMode) {
1306     float sizeFp = (inHeight - offsetFp[0] - 1) / strideFp[0];
1307     float round = std::floor(sizeFp) == sizeFp ? 1 : 0;
1308     outputShape[1] = std::ceil(sizeFp) + round;
1309   }
1310 
1311   if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && fpMode) {
1312     float sizeFp = (inWidth - offsetFp[1] - 1) / strideFp[1];
1313     float round = std::floor(sizeFp) == sizeFp ? 1 : 0;
1314     outputShape[2] = std::ceil(sizeFp) + round;
1315   }
1316 
1317   if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && !fpMode) {
1318     int64_t size = (inHeight - 1);
1319     size = ((size << shift) - offsetInt[0]) / strideInt[0];
1320     outputShape[1] = size + 1;
1321   }
1322 
1323   if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && !fpMode) {
1324     int64_t size = (inWidth - 1);
1325     size = ((size << shift) - offsetInt[1]) / strideInt[1];
1326     outputShape[2] = size + 1;
1327   }
1328 
1329   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1330   return success();
1331 }
1332 
1333 LogicalResult tosa::ScatterOp::inferReturnTypeComponents(
1334     MLIRContext *context, ::llvm::Optional<Location> location,
1335     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1336     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1337   llvm::SmallVector<int64_t> outputShape;
1338   outputShape.resize(3, ShapedType::kDynamicSize);
1339 
1340   ShapeAdaptor valuesInShape = operands.getShape(0);
1341   if (valuesInShape.hasRank()) {
1342     outputShape[0] = valuesInShape.getDimSize(0);
1343     outputShape[1] = valuesInShape.getDimSize(1);
1344     outputShape[2] = valuesInShape.getDimSize(2);
1345   }
1346 
1347   ShapeAdaptor indicesShape = operands.getShape(1);
1348   if (indicesShape.hasRank()) {
1349     if (outputShape[0] == ShapedType::kDynamicSize)
1350       outputShape[0] = indicesShape.getDimSize(0);
1351   }
1352 
1353   ShapeAdaptor inputShape = operands.getShape(2);
1354   if (inputShape.hasRank()) {
1355     if (outputShape[0] == ShapedType::kDynamicSize)
1356       outputShape[0] = inputShape.getDimSize(0);
1357     if (outputShape[2] == ShapedType::kDynamicSize)
1358       outputShape[2] = inputShape.getDimSize(2);
1359   }
1360 
1361   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1362   return success();
1363 }
1364 
1365 static LogicalResult ReduceInferReturnTypes(
1366     ShapeAdaptor operandShape, IntegerAttr axis,
1367     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1368   if (!operandShape.hasRank()) {
1369     inferredReturnShapes.push_back(ShapedTypeComponents());
1370     return success();
1371   }
1372 
1373   SmallVector<int64_t> outputShape;
1374   operandShape.getDims(outputShape);
1375   int64_t axisVal = axis.getValue().getSExtValue();
1376   outputShape[axisVal] = 1;
1377   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1378   return success();
1379 }
1380 
1381 #define REDUCE_SHAPE_INFER(OP)                                                 \
1382   LogicalResult OP::inferReturnTypeComponents(                                 \
1383       MLIRContext *context, ::llvm::Optional<Location> location,               \
1384       ValueShapeRange operands, DictionaryAttr attributes,                     \
1385       RegionRange regions,                                                     \
1386       SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {           \
1387     return ReduceInferReturnTypes(operands.getShape(0),                        \
1388                                   attributes.get("axis").cast<IntegerAttr>(),  \
1389                                   inferredReturnShapes);                       \
1390   }
1391 
1392 REDUCE_SHAPE_INFER(tosa::ReduceAllOp)
1393 REDUCE_SHAPE_INFER(tosa::ReduceAnyOp)
1394 REDUCE_SHAPE_INFER(tosa::ReduceMaxOp)
1395 REDUCE_SHAPE_INFER(tosa::ReduceMinOp)
1396 REDUCE_SHAPE_INFER(tosa::ReduceProdOp)
1397 REDUCE_SHAPE_INFER(tosa::ReduceSumOp)
1398 #undef REDUCE_SHAPE_INFER
1399 
1400 static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands,
1401                                            SmallVector<int64_t> &outShape) {
1402   int64_t outRank = 0;
1403   for (int i = 0, e = operands.size(); i != e; ++i) {
1404     auto shape = operands.getShape(i);
1405     if (!shape.hasRank()) {
1406       return failure();
1407     }
1408     outRank = std::max<int64_t>(outRank, shape.getRank());
1409   }
1410 
1411   outShape.resize(outRank, 1);
1412 
1413   for (int i = 0, e = operands.size(); i != e; ++i) {
1414     auto shape = operands.getShape(i);
1415     auto rankDiff = outShape.size() - shape.getRank();
1416 
1417     for (size_t i = 0, e = shape.getRank(); i < e; ++i) {
1418       auto dim1 = outShape[i + rankDiff];
1419       auto dim2 = shape.getDimSize(i);
1420       auto resolvedDim = dim1;
1421 
1422       if (dim1 == 1) {
1423         resolvedDim = dim2;
1424       } else if (dim2 == 1) {
1425         resolvedDim = dim1;
1426       } else if (dim1 != dim2) {
1427         return failure();
1428       }
1429       outShape[i + rankDiff] = resolvedDim;
1430     }
1431   }
1432 
1433   return success();
1434 }
1435 
1436 static LogicalResult NAryInferReturnTypes(
1437     const ValueShapeRange &operands,
1438     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1439   llvm::SmallVector<int64_t> outShape;
1440   if (resolveBroadcastShape(operands, outShape).failed()) {
1441     inferredReturnShapes.push_back(ShapedTypeComponents());
1442   } else {
1443     inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
1444   }
1445   return success();
1446 }
1447 
1448 #define NARY_SHAPE_INFER(OP)                                                   \
1449   LogicalResult OP::inferReturnTypeComponents(                                 \
1450       MLIRContext *context, ::llvm::Optional<Location> location,               \
1451       ValueShapeRange operands, DictionaryAttr attributes,                     \
1452       RegionRange regions,                                                     \
1453       SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {           \
1454     return NAryInferReturnTypes(operands, inferredReturnShapes);               \
1455   }
1456 
1457 NARY_SHAPE_INFER(tosa::AbsOp)
1458 NARY_SHAPE_INFER(tosa::AddOp)
1459 NARY_SHAPE_INFER(tosa::ArithmeticRightShiftOp)
1460 NARY_SHAPE_INFER(tosa::BitwiseAndOp)
1461 NARY_SHAPE_INFER(tosa::BitwiseOrOp)
1462 NARY_SHAPE_INFER(tosa::BitwiseXorOp)
1463 NARY_SHAPE_INFER(tosa::BitwiseNotOp)
1464 NARY_SHAPE_INFER(tosa::CastOp)
1465 NARY_SHAPE_INFER(tosa::CeilOp)
1466 NARY_SHAPE_INFER(tosa::ClampOp)
1467 NARY_SHAPE_INFER(tosa::ClzOp)
1468 NARY_SHAPE_INFER(tosa::DivOp)
1469 NARY_SHAPE_INFER(tosa::EqualOp)
1470 NARY_SHAPE_INFER(tosa::ExpOp)
1471 NARY_SHAPE_INFER(tosa::FloorOp)
1472 NARY_SHAPE_INFER(tosa::GreaterEqualOp)
1473 NARY_SHAPE_INFER(tosa::GreaterOp)
1474 NARY_SHAPE_INFER(tosa::IdentityOp)
1475 NARY_SHAPE_INFER(tosa::LogOp)
1476 NARY_SHAPE_INFER(tosa::LogicalAndOp)
1477 NARY_SHAPE_INFER(tosa::LogicalLeftShiftOp)
1478 NARY_SHAPE_INFER(tosa::LogicalNotOp)
1479 NARY_SHAPE_INFER(tosa::LogicalOrOp)
1480 NARY_SHAPE_INFER(tosa::LogicalRightShiftOp)
1481 NARY_SHAPE_INFER(tosa::LogicalXorOp)
1482 NARY_SHAPE_INFER(tosa::MaximumOp)
1483 NARY_SHAPE_INFER(tosa::MinimumOp)
1484 NARY_SHAPE_INFER(tosa::MulOp)
1485 NARY_SHAPE_INFER(tosa::NegateOp)
1486 NARY_SHAPE_INFER(tosa::PowOp)
1487 NARY_SHAPE_INFER(tosa::ReciprocalOp)
1488 NARY_SHAPE_INFER(tosa::ReluNOp)
1489 NARY_SHAPE_INFER(tosa::RescaleOp)
1490 NARY_SHAPE_INFER(tosa::ReverseOp)
1491 NARY_SHAPE_INFER(tosa::RsqrtOp)
1492 NARY_SHAPE_INFER(tosa::SelectOp)
1493 NARY_SHAPE_INFER(tosa::SubOp)
1494 NARY_SHAPE_INFER(tosa::TanhOp)
1495 NARY_SHAPE_INFER(tosa::SigmoidOp)
1496 #undef PRED_SHAPE_INFER
1497 
1498 static LogicalResult poolingInferReturnTypes(
1499     const ValueShapeRange &operands, DictionaryAttr attributes,
1500     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1501   ShapeAdaptor inputShape = operands.getShape(0);
1502   llvm::SmallVector<int64_t> outputShape;
1503   outputShape.resize(4, -1);
1504 
1505   // We only know the rank if the input type is unranked.
1506   if (!inputShape) {
1507     inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1508     return success();
1509   }
1510 
1511   // Batch and number of channels are identical for pooling layer.
1512   outputShape[0] = inputShape.getDimSize(0);
1513   outputShape[3] = inputShape.getDimSize(3);
1514 
1515   int32_t height = inputShape.getDimSize(1);
1516   int32_t width = inputShape.getDimSize(2);
1517 
1518   llvm::SmallVector<int64_t> kernel;
1519   llvm::SmallVector<int64_t> stride;
1520   llvm::SmallVector<int64_t> pad;
1521 
1522   getI64Values(attributes.get("kernel").cast<ArrayAttr>(), kernel);
1523   getI64Values(attributes.get("stride").cast<ArrayAttr>(), stride);
1524   getI64Values(attributes.get("pad").cast<ArrayAttr>(), pad);
1525 
1526   if (height != -1) {
1527     int32_t padded = height + pad[0] + pad[1] - kernel[0];
1528     outputShape[1] = padded / stride[0] + 1;
1529   }
1530 
1531   if (width != -1) {
1532     int32_t padded = width + pad[2] + pad[3] - kernel[1];
1533     outputShape[2] = padded / stride[1] + 1;
1534   }
1535 
1536   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1537   return success();
1538 }
1539 
1540 LogicalResult Conv2DOp::inferReturnTypeComponents(
1541     MLIRContext *context, ::llvm::Optional<Location> location,
1542     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1543     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1544   llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamicSize);
1545   Conv2DOp::Adaptor adaptor(operands.getValues(), attributes);
1546 
1547   int32_t inputWidth = ShapedType::kDynamicSize;
1548   int32_t inputHeight = ShapedType::kDynamicSize;
1549   int32_t weightWidth = ShapedType::kDynamicSize;
1550   int32_t weightHeight = ShapedType::kDynamicSize;
1551 
1552   // Input shape describes input width/height and batch.
1553 
1554   ShapeAdaptor inputShape = operands.getShape(adaptor.input());
1555   if (inputShape.hasRank()) {
1556     outputShape[0] = inputShape.getDimSize(0);
1557     inputHeight = inputShape.getDimSize(1);
1558     inputWidth = inputShape.getDimSize(2);
1559   }
1560 
1561   // Weight shapes describes the filter width/height and the output channels.
1562   ShapeAdaptor weightShape = operands.getShape(adaptor.weight());
1563   if (weightShape.hasRank()) {
1564     outputShape[3] = weightShape.getDimSize(0);
1565     weightHeight = weightShape.getDimSize(1);
1566     weightWidth = weightShape.getDimSize(2);
1567   }
1568 
1569   // Bias shape can describe the output channels.
1570   ShapeAdaptor biasShape = operands.getShape(adaptor.bias());
1571   if (biasShape.hasRank()) {
1572     outputShape[3] = ShapedType::isDynamic(outputShape[3])
1573                          ? biasShape.getDimSize(0)
1574                          : outputShape[3];
1575   }
1576 
1577   llvm::SmallVector<int64_t> dilation;
1578   llvm::SmallVector<int64_t> padding;
1579   llvm::SmallVector<int64_t> stride;
1580 
1581   getI64Values(adaptor.dilation(), dilation);
1582   getI64Values(adaptor.pad(), padding);
1583   getI64Values(adaptor.stride(), stride);
1584 
1585   if (!ShapedType::isDynamic(inputHeight) &&
1586       !ShapedType::isDynamic(weightHeight)) {
1587     int32_t inputSize = inputHeight + padding[0] + padding[1];
1588     int32_t filterSize = (weightHeight - 1) * dilation[0] + 1;
1589     int32_t unstridedResult = inputSize - filterSize + 1;
1590     outputShape[1] = (unstridedResult - 1) / stride[0] + 1;
1591   }
1592 
1593   if (!ShapedType::isDynamic(inputWidth) &&
1594       !ShapedType::isDynamic(weightWidth)) {
1595     int32_t inputSize = inputWidth + padding[2] + padding[3];
1596     int32_t filterSize = (weightWidth - 1) * dilation[1] + 1;
1597     int32_t unstridedResult = inputSize - filterSize + 1;
1598     outputShape[2] = (unstridedResult - 1) / stride[1] + 1;
1599   }
1600 
1601   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1602   return success();
1603 }
1604 
1605 LogicalResult Conv3DOp::inferReturnTypeComponents(
1606     MLIRContext *context, ::llvm::Optional<Location> location,
1607     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1608     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1609   llvm::SmallVector<int64_t> outputShape(5, ShapedType::kDynamicSize);
1610   Conv2DOp::Adaptor adaptor(operands.getValues(), attributes);
1611 
1612   int32_t inputWidth = ShapedType::kDynamicSize;
1613   int32_t inputHeight = ShapedType::kDynamicSize;
1614   int32_t inputDepth = ShapedType::kDynamicSize;
1615 
1616   int32_t weightWidth = ShapedType::kDynamicSize;
1617   int32_t weightHeight = ShapedType::kDynamicSize;
1618   int32_t weightDepth = ShapedType::kDynamicSize;
1619 
1620   // Input shape describes input width/height and batch.
1621   ShapeAdaptor inputShape = operands.getShape(adaptor.input());
1622   if (inputShape.hasRank()) {
1623     outputShape[0] = inputShape.getDimSize(0);
1624     inputHeight = inputShape.getDimSize(1);
1625     inputWidth = inputShape.getDimSize(2);
1626     inputDepth = inputShape.getDimSize(3);
1627   }
1628 
1629   // Weight shapes describes the filter width/height and the output channels.
1630   ShapeAdaptor weightShape = operands.getShape(adaptor.weight());
1631   if (weightShape.hasRank()) {
1632     outputShape[4] = weightShape.getDimSize(0);
1633     weightHeight = weightShape.getDimSize(1);
1634     weightWidth = weightShape.getDimSize(2);
1635     weightDepth = weightShape.getDimSize(3);
1636   }
1637 
1638   // Bias shape can describe the output channels.
1639   ShapeAdaptor biasShape = operands.getShape(adaptor.bias());
1640   if (biasShape.hasRank()) {
1641     outputShape[4] =
1642         (outputShape[4] == -1) ? biasShape.getDimSize(0) : outputShape[4];
1643   }
1644 
1645   llvm::SmallVector<int64_t> dilation;
1646   llvm::SmallVector<int64_t> padding;
1647   llvm::SmallVector<int64_t> stride;
1648 
1649   getI64Values(adaptor.dilation(), dilation);
1650   getI64Values(adaptor.pad(), padding);
1651   getI64Values(adaptor.stride(), stride);
1652 
1653   if (!ShapedType::isDynamic(inputHeight) &&
1654       !ShapedType::isDynamic(weightHeight)) {
1655     int32_t inputSize = inputHeight + padding[0] + padding[1];
1656     int32_t filterSize = (weightHeight - 1) * dilation[0] + 1;
1657     int32_t unstridedResult = inputSize - filterSize + 1;
1658     outputShape[1] = (unstridedResult - 1) / stride[0] + 1;
1659   }
1660 
1661   if (!ShapedType::isDynamic(inputWidth) &&
1662       !ShapedType::isDynamic(weightWidth)) {
1663     int32_t inputSize = inputWidth + padding[2] + padding[3];
1664     int32_t filterSize = (weightWidth - 1) * dilation[1] + 1;
1665     int32_t unstridedResult = inputSize - filterSize + 1;
1666     outputShape[2] = (unstridedResult - 1) / stride[1] + 1;
1667   }
1668 
1669   if (!ShapedType::isDynamic(inputDepth) &&
1670       !ShapedType::isDynamic(weightDepth)) {
1671     int32_t inputSize = inputDepth + padding[4] + padding[5];
1672     int32_t filterSize = (weightDepth - 1) * dilation[2] + 1;
1673     int32_t unstridedResult = inputSize - filterSize + 1;
1674     outputShape[3] = (unstridedResult - 1) / stride[2] + 1;
1675   }
1676 
1677   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1678   return success();
1679 }
1680 
1681 LogicalResult AvgPool2dOp::inferReturnTypeComponents(
1682     MLIRContext *context, ::llvm::Optional<Location> location,
1683     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1684     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1685   return poolingInferReturnTypes(operands, attributes, inferredReturnShapes);
1686 }
1687 
1688 LogicalResult MaxPool2dOp::inferReturnTypeComponents(
1689     MLIRContext *context, ::llvm::Optional<Location> location,
1690     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1691     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1692   return poolingInferReturnTypes(operands, attributes, inferredReturnShapes);
1693 }
1694 
1695 LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(
1696     MLIRContext *context, ::llvm::Optional<Location> location,
1697     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1698     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1699   llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamicSize);
1700   DepthwiseConv2DOp::Adaptor adaptor(operands.getValues(), attributes);
1701 
1702   int32_t inputWidth = ShapedType::kDynamicSize;
1703   int32_t inputHeight = ShapedType::kDynamicSize;
1704   int32_t inputChannels = ShapedType::kDynamicSize;
1705 
1706   int32_t weightWidth = ShapedType::kDynamicSize;
1707   int32_t weightHeight = ShapedType::kDynamicSize;
1708   int32_t depthChannels = ShapedType::kDynamicSize;
1709 
1710   // Input shape describes input width/height and batch.
1711   ShapeAdaptor inputShape = operands.getShape(adaptor.input());
1712   if (inputShape.hasRank()) {
1713     outputShape[0] = inputShape.getDimSize(0);
1714     inputHeight = inputShape.getDimSize(1);
1715     inputWidth = inputShape.getDimSize(2);
1716     inputChannels = inputShape.getDimSize(3);
1717   }
1718 
1719   // Weight shapes describes the filter width/height and the output channels.
1720   ShapeAdaptor weightShape = operands.getShape(adaptor.weight());
1721   if (weightShape.hasRank()) {
1722     weightHeight = weightShape.getDimSize(0);
1723     weightWidth = weightShape.getDimSize(1);
1724     inputChannels = ShapedType::isDynamic(inputChannels)
1725                         ? weightShape.getDimSize(2)
1726                         : inputChannels;
1727     depthChannels = weightShape.getDimSize(3);
1728   }
1729 
1730   // If both inputChannels and depthChannels are available we can determine
1731   // the output channels.
1732   if (!ShapedType::isDynamic(inputChannels) &&
1733       !ShapedType::isDynamic(depthChannels)) {
1734     outputShape[3] = inputChannels * depthChannels;
1735   }
1736 
1737   // Bias shape can describe the output channels.
1738   ShapeAdaptor biasShape = operands.getShape(adaptor.bias());
1739   if (biasShape.hasRank()) {
1740     outputShape[3] = ShapedType::isDynamic(outputShape[3])
1741                          ? biasShape.getDimSize(0)
1742                          : outputShape[3];
1743   }
1744 
1745   llvm::SmallVector<int64_t> dilation;
1746   llvm::SmallVector<int64_t> padding;
1747   llvm::SmallVector<int64_t> stride;
1748 
1749   getI64Values(adaptor.dilation(), dilation);
1750   getI64Values(adaptor.pad(), padding);
1751   getI64Values(adaptor.stride(), stride);
1752 
1753   if (!ShapedType::isDynamic(inputHeight) &&
1754       !ShapedType::isDynamic(weightHeight)) {
1755     int32_t inputSize = inputHeight + padding[0] + padding[1];
1756     int32_t filterSize = (weightHeight - 1) * dilation[0] + 1;
1757     int32_t unstridedResult = inputSize - filterSize + 1;
1758     outputShape[1] = (unstridedResult - 1) / stride[0] + 1;
1759   }
1760 
1761   if (!ShapedType::isDynamic(inputWidth) &&
1762       !ShapedType::isDynamic(weightWidth)) {
1763     int32_t inputSize = inputWidth + padding[2] + padding[3];
1764     int32_t filterSize = (weightWidth - 1) * dilation[1] + 1;
1765     int32_t unstridedResult = inputSize - filterSize + 1;
1766     outputShape[2] = (unstridedResult - 1) / stride[1] + 1;
1767   }
1768 
1769   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1770   return success();
1771 }
1772 
1773 LogicalResult TransposeConv2DOp::inferReturnTypeComponents(
1774     MLIRContext *context, ::llvm::Optional<Location> location,
1775     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1776     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1777   TransposeConv2DOp::Adaptor adaptor(operands.getValues(), attributes);
1778   llvm::SmallVector<int64_t> outputShape;
1779   getI64Values(adaptor.out_shape(), outputShape);
1780 
1781   int32_t inputWidth = ShapedType::kDynamicSize;
1782   int32_t inputHeight = ShapedType::kDynamicSize;
1783   int32_t weightWidth = ShapedType::kDynamicSize;
1784   int32_t weightHeight = ShapedType::kDynamicSize;
1785 
1786   // Input shape describes input width/height and batch.
1787   ShapeAdaptor inputShape = operands.getShape(adaptor.input());
1788   if (inputShape.hasRank()) {
1789     outputShape[0] = ShapedType::isDynamic(outputShape[0])
1790                          ? inputShape.getDimSize(0)
1791                          : outputShape[0];
1792     inputHeight = inputShape.getDimSize(1);
1793     inputWidth = inputShape.getDimSize(2);
1794   }
1795 
1796   // Weight shapes describes the filter width/height and the output channels.
1797   ShapeAdaptor weightShape = operands.getShape(adaptor.filter());
1798   if (weightShape.hasRank()) {
1799     outputShape[3] = ShapedType::isDynamic(outputShape[3])
1800                          ? weightShape.getDimSize(0)
1801                          : outputShape[3];
1802     weightHeight = weightShape.getDimSize(1);
1803     weightWidth = weightShape.getDimSize(2);
1804   }
1805 
1806   // Bias shape can describe the output channels.
1807   ShapeAdaptor biasShape = operands.getShape(adaptor.input());
1808   if (biasShape.hasRank()) {
1809     outputShape[3] = ShapedType::isDynamic(outputShape[3])
1810                          ? biasShape.getDimSize(0)
1811                          : outputShape[3];
1812   }
1813 
1814   llvm::SmallVector<int64_t> dilation;
1815   llvm::SmallVector<int64_t> padding;
1816   llvm::SmallVector<int64_t> stride;
1817 
1818   getI64Values(adaptor.dilation(), dilation);
1819   getI64Values(adaptor.out_pad(), padding);
1820   getI64Values(adaptor.stride(), stride);
1821 
1822   if (!ShapedType::isDynamic(inputHeight) &&
1823       !ShapedType::isDynamic(weightHeight)) {
1824     int32_t dilated = (weightHeight - 1) * dilation[0] + 1;
1825     int32_t calculateSize =
1826         (inputHeight - 1) * stride[0] - padding[0] + dilated;
1827     outputShape[1] = outputShape[1] == -1 ? calculateSize : outputShape[1];
1828   }
1829 
1830   if (!ShapedType::isDynamic(inputWidth) &&
1831       !ShapedType::isDynamic(weightWidth)) {
1832     int32_t dilated = (weightWidth - 1) * dilation[1] + 1;
1833     int32_t calculateSize = (inputWidth - 1) * stride[1] - padding[1] + dilated;
1834     outputShape[2] = outputShape[2] == -1 ? calculateSize : outputShape[2];
1835   }
1836 
1837   inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1838   return success();
1839 }
1840 
1841 LogicalResult IfOp::inferReturnTypeComponents(
1842     MLIRContext *context, ::llvm::Optional<Location> location,
1843     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1844     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1845   llvm::SmallVector<tosa::YieldOp> yieldOps;
1846   for (Region *region : regions) {
1847     for (auto &block : *region)
1848       if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))
1849         yieldOps.push_back(returnOp);
1850   }
1851 
1852   if (yieldOps.empty())
1853     return failure();
1854 
1855   // Get the initial type information for the yield op.
1856   llvm::SmallVector<ValueKnowledge> resultKnowledge;
1857   resultKnowledge.reserve(yieldOps.front().getNumOperands());
1858   for (auto operand : yieldOps.front().getOperands()) {
1859     resultKnowledge.push_back(
1860         ValueKnowledge::getKnowledgeFromType(operand.getType()));
1861   }
1862 
1863   for (auto yieldOp : yieldOps) {
1864     if (resultKnowledge.size() != yieldOp.getNumOperands())
1865       return failure();
1866 
1867     for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {
1868       int32_t index = it.index();
1869       auto meet = ValueKnowledge::meet(
1870           resultKnowledge[index],
1871           ValueKnowledge::getKnowledgeFromType(it.value().getType()));
1872       if (!meet)
1873         continue;
1874       resultKnowledge[index] = meet;
1875     }
1876   }
1877 
1878   for (const ValueKnowledge &result : resultKnowledge) {
1879     inferredReturnShapes.push_back(result.getShapedTypeComponents());
1880   }
1881 
1882   return success();
1883 }
1884 
1885 LogicalResult WhileOp::inferReturnTypeComponents(
1886     MLIRContext *context, ::llvm::Optional<Location> location,
1887     ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions,
1888     SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1889   llvm::SmallVector<tosa::YieldOp> yieldOps;
1890   for (auto &block : *regions[1])
1891     if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))
1892       yieldOps.push_back(returnOp);
1893 
1894   // TOSA's while must have a tosa.yield as its terminator. If not found this
1895   // tosa.while is invalid.
1896   if (yieldOps.empty())
1897     return failure();
1898 
1899   // Get the initial type information from the operand types.
1900   llvm::SmallVector<ValueKnowledge> resultKnowledge;
1901   resultKnowledge.reserve(yieldOps.front().getNumOperands());
1902   for (auto operand : yieldOps.front().getOperands()) {
1903     resultKnowledge.push_back(
1904         ValueKnowledge::getKnowledgeFromType(operand.getType()));
1905   }
1906 
1907   for (auto yieldOp : yieldOps) {
1908     if (resultKnowledge.size() != yieldOp.getNumOperands())
1909       return failure();
1910 
1911     for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {
1912       int32_t index = it.index();
1913       if (auto meet = ValueKnowledge::meet(
1914               resultKnowledge[index],
1915               ValueKnowledge::getKnowledgeFromType(it.value().getType()))) {
1916         resultKnowledge[index] = meet;
1917       };
1918     }
1919   }
1920 
1921   for (const ValueKnowledge &result : resultKnowledge) {
1922     inferredReturnShapes.push_back(result.getShapedTypeComponents());
1923   }
1924 
1925   return success();
1926 }
1927 
1928 //===----------------------------------------------------------------------===//
1929 // TOSA Operator Definitions.
1930 //===----------------------------------------------------------------------===//
1931 
1932 #define GET_OP_CLASSES
1933 #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"
1934