MLIR 24.0.0git
TosaOps.cpp
Go to the documentation of this file.
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://www.mlplatform.org/tosa/tosa_spec.html
12//
13//===----------------------------------------------------------------------===//
14
25#include "mlir/IR/Matchers.h"
29#include "llvm/ADT/APFloat.h"
30#include "llvm/ADT/SmallVectorExtras.h"
31
32#include <numeric>
33#include <type_traits>
34
35using namespace mlir;
36using namespace mlir::tosa;
37
38#include "mlir/Dialect/Tosa/IR/TosaOpsDialect.cpp.inc"
40
41//===----------------------------------------------------------------------===//
42// Tosa dialect interface includes.
43//===----------------------------------------------------------------------===//
44
45#include "mlir/Dialect/Tosa/IR/TosaDialectBytecode.cpp.inc"
46#include "mlir/Dialect/Tosa/IR/TosaEnums.cpp.inc"
47#include "mlir/Dialect/Tosa/IR/TosaInterfaces.cpp.inc"
48
49namespace {
50//===----------------------------------------------------------------------===//
51// Dialect Function Inliner Interface.
52//===----------------------------------------------------------------------===//
53struct TosaInlinerInterface : public DialectInlinerInterface {
54 using DialectInlinerInterface::DialectInlinerInterface;
55
56 //===--------------------------------------------------------------------===//
57 // Analysis Hooks.
58 //===--------------------------------------------------------------------===//
59
60 /// All operations can be inlined by default.
61 bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned,
62 IRMapping &map) const final {
63 return true;
64 }
65
66 /// All regions with If and While parent operators can be inlined.
67 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,
68 IRMapping &map) const final {
69 return (isa<tosa::IfOp>(dest->getParentOp()) ||
70 isa<tosa::WhileOp>(dest->getParentOp()));
71 }
72};
73
74/// This class implements the bytecode interface for the Tosa dialect.
75struct TosaDialectBytecodeInterface : public BytecodeDialectInterface {
76 TosaDialectBytecodeInterface(Dialect *dialect)
77 : BytecodeDialectInterface(dialect) {}
78
79 //===--------------------------------------------------------------------===//
80 // Attributes
81
82 Attribute readAttribute(DialectBytecodeReader &reader) const override {
83 return ::readAttribute(getContext(), reader);
84 }
85
86 LogicalResult writeAttribute(Attribute attr,
87 DialectBytecodeWriter &writer) const override {
88 return ::writeAttribute(attr, writer);
89 }
90
91 //===--------------------------------------------------------------------===//
92 // Types
93
94 Type readType(DialectBytecodeReader &reader) const override {
95 return ::readType(getContext(), reader);
96 }
97
98 LogicalResult writeType(Type type,
99 DialectBytecodeWriter &writer) const override {
100 return ::writeType(type, writer);
101 }
102
103 void writeVersion(DialectBytecodeWriter &writer) const final {
104 // TODO: Populate.
105 }
106
107 std::unique_ptr<DialectVersion>
108 readVersion(DialectBytecodeReader &reader) const final {
109 // TODO: Populate
110 reader.emitError("Dialect does not support versioning");
111 return nullptr;
112 }
113
114 LogicalResult upgradeFromVersion(Operation *topLevelOp,
115 const DialectVersion &version) const final {
116 return success();
117 }
118};
119} // namespace
120
121//===----------------------------------------------------------------------===//
122// TOSA control flow support.
123//===----------------------------------------------------------------------===//
124
125/// Returns the while loop body.
126SmallVector<Region *> tosa::WhileOp::getLoopRegions() {
127 return {&getBodyGraph()};
128}
129
130//===----------------------------------------------------------------------===//
131// TOSA variable operator support.
132//===----------------------------------------------------------------------===//
133
135 return map_to_vector(shape, [](int64_t dim) {
136 return dim == -1 ? ShapedType::kDynamic : dim;
137 });
138}
139
140// returns type of variable op
141RankedTensorType mlir::tosa::getVariableType(tosa::VariableOp variableOp) {
142 Type elementType = variableOp.getType();
143 DenseIntElementsAttr varShapeAttr = variableOp.getVarShape();
144 auto shape = convertToMlirShape(to_vector(varShapeAttr.getValues<int64_t>()));
145 return RankedTensorType::get(shape, elementType);
146}
147
148//===----------------------------------------------------------------------===//
149// Tosa dialect initialization.
150//===----------------------------------------------------------------------===//
151
152void TosaDialect::initialize() {
153 addTypes<
154#define GET_TYPEDEF_LIST
155#include "mlir/Dialect/Tosa/IR/TosaOpsTypesBase.cpp.inc"
156 >();
157 addOperations<
158#define GET_OP_LIST
159#include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"
160 >();
161 addAttributes<
162#define GET_ATTRDEF_LIST
163#include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc"
164 >();
165 addInterfaces<TosaDialectBytecodeInterface, TosaInlinerInterface>();
166 declarePromisedInterfaces<
167 shard::ShardingInterface, ClampOp, SigmoidOp, TanhOp, AddOp,
168 ArithmeticRightShiftOp, BitwiseAndOp, BitwiseOrOp, BitwiseXorOp, IntDivOp,
169 LogicalAndOp, LogicalLeftShiftOp, LogicalRightShiftOp, LogicalOrOp,
170 LogicalXorOp, MaximumOp, MinimumOp, MulOp, PowOp, SubOp, AbsOp,
171 BitwiseNotOp, CeilOp, ClzOp, ExpOp, FloorOp, LogOp, LogicalNotOp,
172 NegateOp, ReciprocalOp, RsqrtOp, SelectOp, EqualOp, GreaterOp,
173 GreaterEqualOp, MatMulOp>();
174}
175
176Operation *TosaDialect::materializeConstant(OpBuilder &builder, Attribute value,
177 Type type, Location loc) {
178 // Tosa dialect constants only support ElementsAttr unlike standard dialect
179 // constant which supports all attributes.
180 if (llvm::isa<shapeType>(type) && llvm::isa<DenseIntElementsAttr>(value)) {
181 return tosa::ConstShapeOp::create(builder, loc, type,
182 llvm::cast<DenseIntElementsAttr>(value));
183 }
184 if (llvm::isa<ElementsAttr>(value))
185 return tosa::ConstOp::create(builder, loc, type,
186 llvm::cast<ElementsAttr>(value));
187 return nullptr;
188}
189
190//===----------------------------------------------------------------------===//
191// Parsers and printers
192//===----------------------------------------------------------------------===//
193
194namespace {
195
196ParseResult getShapeAndElementType(OpAsmParser &parser, Type parsedType,
197 DenseElementsAttr &varShapeAttr,
198 TypeAttr &typeAttr) {
199 if (auto shapedType = dyn_cast<ShapedType>(parsedType)) {
200 if (!shapedType.hasRank())
201 return parser.emitError(parser.getCurrentLocation())
202 << "expected ranked type";
203
204 auto elementType = shapedType.getElementType();
205 typeAttr = TypeAttr::get(elementType);
206 ArrayRef<int64_t> shape = shapedType.getShape();
207 Builder builder(parser.getContext());
208 varShapeAttr = builder.getIndexTensorAttr(convertFromMlirShape(shape));
209 return success();
210 }
211 return parser.emitError(parser.getCurrentLocation())
212 << "expected shaped type";
213}
214
215} // namespace
216
217// parses the optional initial value or type for a tosa variable
218// with initial value:
219// tosa.variable @name = dense<0.0> : tensor<1x8xf32>
220//
221// without initial value:
222// tosa.variable @name : tensor<1x8xf32>
224 OpAsmParser &parser, DenseElementsAttr &varShapeAttr, TypeAttr &typeAttr,
225 Attribute &initialValueAttr) {
226 if (succeeded(parser.parseOptionalEqual())) {
227 if (failed(parser.parseAttribute(initialValueAttr))) {
228 return parser.emitError(parser.getCurrentLocation())
229 << "expected attribute";
230 }
231 if (auto typedAttr = dyn_cast<TypedAttr>(initialValueAttr)) {
232 return getShapeAndElementType(parser, typedAttr.getType(), varShapeAttr,
233 typeAttr);
234 }
235 return parser.emitError(parser.getCurrentLocation())
236 << "expected Typed attr";
237 }
238
239 initialValueAttr = nullptr;
240 Type parsedType;
241 if (failed(parser.parseColonType(parsedType))) {
242 return parser.emitError(parser.getCurrentLocation())
243 << "expected type after colon";
244 }
245 return getShapeAndElementType(parser, parsedType, varShapeAttr, typeAttr);
246}
247
249 OpAsmPrinter &p, Operation *op, DenseElementsAttr varShapeAttr,
250 TypeAttr typeAttr, Attribute initialValueAttr) {
251 bool needsSpace = false;
252 if (!dyn_cast_or_null<TypedAttr>(initialValueAttr)) {
253 auto shape =
254 convertToMlirShape(to_vector(varShapeAttr.getValues<int64_t>()));
255 Type elementType = typeAttr.getValue();
256 RankedTensorType tensorType =
257 RankedTensorType::get(ArrayRef<int64_t>(shape), elementType);
258 auto tensorTypeAttr = TypeAttr::get(tensorType);
259 p << ": ";
260 p.printAttribute(tensorTypeAttr);
261 needsSpace = true; // subsequent attr value needs a space separator
262 }
263 if (initialValueAttr) {
264 if (needsSpace)
265 p << ' ';
266 p << "= ";
267 p.printAttribute(initialValueAttr);
268 }
269}
270
271//===----------------------------------------------------------------------===//
272// Tosa utilities.
273//===----------------------------------------------------------------------===//
274
275static std::optional<int64_t> idivCheck(const int64_t lhs, const int64_t rhs) {
276 if (lhs % rhs != 0)
277 return std::nullopt;
278 return lhs / rhs;
279}
280
282 auto srcType = getElementTypeOrSelf(type);
283 if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(srcType))
284 srcType = getStorageElementTypeFromQuantized(quantType);
285 return srcType;
286}
287
291
292static LogicalResult verifyRescaleValueAndZpTypes(Operation *op, Value val,
293 Value valZp, StringRef name) {
295 Type eZpType = getStorageElementTypeOrSelf(valZp.getType());
296
297 bool bothInts =
298 mlir::isa<IntegerType>(eType) && mlir::isa<IntegerType>(eZpType);
299 bool sameBitWidth =
300 (eType.getIntOrFloatBitWidth() == eZpType.getIntOrFloatBitWidth());
301
302 if (!bothInts || !sameBitWidth) {
303 return op->emitOpError()
304 << "expected " << name << " and " << name
305 << "_zp to both be integer of the same bitwidth, but got " << eType
306 << " vs. " << eZpType;
307 }
308 return success();
309}
310
311// Create a pad-const const tensor with value of `val` of required data-type
313 Value src, int32_t val) {
314 const auto srcType = getElementTypeOrSelf(src);
315 const auto srcElemType = getStorageElementTypeOrSelf(src);
316 const auto padConstType = mlir::RankedTensorType::get({1}, srcType);
317 const auto padConstEType = mlir::RankedTensorType::get({1}, srcElemType);
318 const auto padConstAttr{
319 llvm::isa<FloatType>(srcElemType)
320 ? DenseElementsAttr::get(padConstEType,
321 builder.getFloatAttr(srcElemType, val))
322 : DenseElementsAttr::get(padConstEType,
323 builder.getIntegerAttr(srcElemType, val))};
324 return tosa::ConstOp::create(builder, loc, padConstType, padConstAttr);
325}
326
328 if (auto blockScaledTy = dyn_cast<tosa::BlockScaledType>(type))
329 return getBitWidth(blockScaledTy.getValueType());
330 if (dyn_cast<tosa::mxint8Type>(type))
331 return 8;
332 return type.getIntOrFloatBitWidth();
333}
334
335// Update dim size if current dim is dynamic, otherwise raise an error if sizes
336// do not match
337LogicalResult tryUpdateDimOrFailure(Operation *op, int64_t &currDim,
338 const int64_t newDim,
339 const StringRef operandName,
340 const StringRef dimName) {
341 if (ShapedType::isDynamic(currDim)) {
342 currDim = newDim;
343 return success();
344 } else if (ShapedType::isStatic(newDim) && currDim != newDim) {
345 return op->emitOpError("expected ")
346 << dimName << " of " << operandName << " to match size " << currDim
347 << ", got " << newDim;
348 }
349 return success();
350}
351
354 auto printDim = [&](int64_t dim) {
355 if (ShapedType::isDynamic(dim))
356 diag << "?";
357 else
358 diag << dim;
359 };
360
361 llvm::interleaveComma(shape, diag, printDim);
362}
363
364static LogicalResult
366 ArrayRef<int64_t> expectedShape,
367 StringRef outputName = "output") {
368 assert(outputType.hasRank() && "expected output type to be ranked");
369
370 if (succeeded(verifyCompatibleShape(outputType.getShape(), expectedShape)))
371 return success();
372
373 InFlightDiagnostic diag = op->emitOpError("expected ");
374 diag << outputName << " shape ";
375 printShapeToDiagnostic(diag, outputType.getShape());
376 diag << " to be compatible with inferred shape ";
377 printShapeToDiagnostic(diag, expectedShape);
378 return diag;
379}
380
382 Operation *op, const int64_t inputSize, const int64_t kernelSize,
383 const int64_t outputSize, const int64_t padBefore, const int64_t padAfter,
384 const int64_t stride, const int64_t dilation, const llvm::StringRef dimName,
385 const llvm::StringRef dimAxis, const llvm::StringRef padBeforeName,
386 const llvm::StringRef padAfterName) {
387 if (inputSize == ShapedType::kDynamic || kernelSize == ShapedType::kDynamic)
388 return success();
389
390 // ERROR_IF: O != idiv_check(I - 1 + pa + pb - (K - 1) * d, s) + 1
391
392 const std::optional<int64_t> calculatedOutSizeMinusOne = idivCheck(
393 inputSize - 1 + padBefore + padAfter - (kernelSize - 1) * dilation,
394 stride);
395 if (!calculatedOutSizeMinusOne.has_value())
396 return op->emitOpError("expected input_")
397 << dimName << " - 1 + pad_" << padBeforeName << " + pad_"
398 << padAfterName << " - (kernel_" << dimName << " - 1) * dilation_"
399 << dimAxis << " to be wholly divisible by stride_" << dimAxis
400 << ", got (" << inputSize << " - 1 + " << padBefore << " + "
401 << padAfter << " - (" << kernelSize << " - 1) * " << dilation
402 << ") / " << stride;
403
404 const int64_t calculatedOutSize = calculatedOutSizeMinusOne.value() + 1;
405 if (outputSize != ShapedType::kDynamic && calculatedOutSize != outputSize)
406 return op->emitOpError("calculated output ")
407 << dimName << " did not match expected: "
408 << "calculated=" << calculatedOutSize << ", expected=" << outputSize;
409
410 return success();
411}
412
413//===----------------------------------------------------------------------===//
414// mxint8Type DenseElementTypeInterface implementation.
415//===----------------------------------------------------------------------===//
416size_t mlir::tosa::mxint8Type::getDenseElementBitSize() const { return 8; }
417
419mlir::tosa::mxint8Type::convertToAttribute(ArrayRef<char> rawData) const {
420 assert(rawData.size() == 1 && "expected 1 byte for tosa.mxint8 element");
421 const auto intType = IntegerType::get(getContext(), 8);
422 return intType.convertToAttribute(rawData);
423}
424
425LogicalResult mlir::tosa::mxint8Type::convertFromAttribute(
427 const auto intAttr = dyn_cast<IntegerAttr>(attr);
428 if (!intAttr)
429 return failure();
430 const Type attrType = intAttr.getType();
431 if (!attrType.isSignlessInteger(8))
432 return failure();
433 return cast<IntegerType>(attrType).convertFromAttribute(attr, result);
434}
435
436//===----------------------------------------------------------------------===//
437// TOSA block scaling utilities.
438//===----------------------------------------------------------------------===//
439
442 bool allowScaleValues) {
443 const auto tensorType = llvm::cast<ShapedType>(type);
444 const BlockScaledType elemType =
445 llvm::dyn_cast<BlockScaledType>(tensorType.getElementType());
446 if (!elemType)
447 return success();
448
449 if (!allowScaleValues && elemType.hasScaleValues()) {
450 if (emitError)
451 emitError()
452 << "block scaled tensor type with scale values is not allowed";
453 return failure();
454 }
455
456 if (!tensorType.hasRank())
457 return success();
458
459 if (tensorType.getRank() == 0) {
460 if (emitError)
461 emitError() << "block scaled tensor type must have rank greater than "
462 "zero";
463 return failure();
464 }
465
466 const ArrayRef<int64_t> tensorShape = tensorType.getShape();
467 const uint32_t blockSize =
468 BlockShapeAttr::getBlockShapeValue(elemType.getBlockShape());
469
470 if (allowScaleValues && elemType.hasScaleValues() &&
471 tensorType.hasStaticShape()) {
472 const size_t numBlocks = tensorType.getNumElements() / blockSize;
473 if (elemType.getScaleValues().size() != numBlocks) {
474 if (emitError)
475 emitError() << "block scaled tensor type with scale values must have "
476 "scale values for each block, expected "
477 << numBlocks << ", got "
478 << elemType.getScaleValues().size();
479 return failure();
480 }
481 }
482
483 const int64_t blockedDimension = tensorShape.back();
484 if (ShapedType::isDynamic(blockedDimension))
485 return success();
486
487 if (blockedDimension % blockSize != 0) {
488 if (emitError)
489 emitError() << "last dimension of block scaled tensor type ("
490 << blockedDimension << ") must be divisible by block size ("
491 << blockSize << ")";
492
493 return failure();
494 }
495
496 return success();
497}
498
500 MLIRContext *ctx = type.getContext();
501 std::string message;
503 ctx, [&](Diagnostic &diag) { message = diag.str(); });
504
506 type, [ctx] { return emitError(UnknownLoc::get(ctx)); })) &&
507 !message.empty()) {
508 return ": " + message;
509 }
510
511 return "";
512}
513
514static ParseResult parseScaleValues(AsmParser &parser,
515 SmallVector<Attribute> &scaleValues,
516 Type scaleType) {
517 const auto parseScaleValue = [&]() -> ParseResult {
518 const SMLoc loc = parser.getCurrentLocation();
519
520 double floatValue;
521 if (parser.parseFloat(floatValue))
522 return failure();
523
524 if (floatValue < 0.0)
525 return parser.emitError(loc, "scale value must be non-negative, got ")
526 << floatValue;
527
528 Type attrType = scaleType;
529 if (succeeded(parser.parseOptionalColon()) && parser.parseType(attrType))
530 return failure();
531
532 if (attrType != scaleType)
533 return parser.emitError(loc, "parsed attribute type ")
534 << attrType << " does not match expected scale type " << scaleType;
535
536 scaleValues.push_back(FloatAttr::get(attrType, floatValue));
537 return success();
538 };
539
540 return parser.parseCommaSeparatedList(parseScaleValue);
541}
542
543static void printScaleValues(AsmPrinter &printer,
544 ArrayRef<Attribute> scaleValues, Type) {
545 llvm::interleaveComma(scaleValues, printer, [&](Attribute scaleValue) {
546 printer.printAttributeWithoutType(scaleValue);
547 });
548}
549
550size_t mlir::tosa::BlockScaledType::getDenseElementBitSize() const {
551 const Type valueType = getValueType();
552 if (isa<tosa::mxint8Type>(valueType))
553 return 8;
554 return valueType.getIntOrFloatBitWidth();
555}
556
558mlir::tosa::BlockScaledType::convertToAttribute(ArrayRef<char> rawData) const {
559 // Block scaled values are stored as a single byte. This is because possible
560 // value data types are either 8-bit or sub-byte. Sub-byte types are aligned
561 // to 8-bits.
562 assert(rawData.size() == 1 && "expected 1 byte for block_scaled element");
563 const Type valueType = getValueType();
564 if (const auto mxint8Value = dyn_cast<tosa::mxint8Type>(valueType))
565 return mxint8Value.convertToAttribute(rawData);
566 if (!isa<FloatType>(valueType))
567 return {};
568 return mlir::detail::convertFloatTypeToAttribute(valueType, rawData);
569}
570
571LogicalResult mlir::tosa::BlockScaledType::convertFromAttribute(
573 const Type valueType = getValueType();
574 if (const auto mxint8Value = dyn_cast<tosa::mxint8Type>(valueType))
575 return mxint8Value.convertFromAttribute(attr, result);
576
577 const auto floatAttr = dyn_cast<FloatAttr>(attr);
578 if (!floatAttr || floatAttr.getType() != valueType)
579 return failure();
580 // const APFloat value = floatAttr.getValue();
581 return mlir::detail::convertFloatTypeFromAttribute(valueType, floatAttr,
582 result);
583}
584
585//===----------------------------------------------------------------------===//
586// TOSA Operator shape inference
587//===----------------------------------------------------------------------===//
588
589template <typename A, std::enable_if_t<std::is_same_v<A, ArgMaxOp::Adaptor> ||
590 std::is_same_v<A, ArgMinOp::Adaptor>,
591 int> = 0>
593 MLIRContext *context, ::std::optional<Location> location, A adaptor,
594 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
595 ShapeAdaptor inputShape(adaptor.getInput().getType());
596 IntegerAttr axis = adaptor.getProperties().axis;
597 int32_t axisVal = axis.getValue().getSExtValue();
598
599 if (!inputShape.hasRank()) {
600 inferredReturnShapes.push_back(ShapedTypeComponents());
601 return success();
602 }
603
604 const auto inputRank = inputShape.getRank();
605 SmallVector<int64_t> outShape;
606 outShape.reserve(inputRank - 1);
607 for (int i = 0, s = inputRank; i < s; i++) {
608 if (i == axisVal)
609 continue;
610 outShape.push_back(inputShape.getDimSize(i));
611 }
612
613 inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
614 return success();
615}
616
617//===----------------------------------------------------------------------===//
618// TOSA Operator Verifiers.
619//===----------------------------------------------------------------------===//
620template <typename T>
621LogicalResult argMaxMinVerify(T op) {
622 const ShapedType resultType = llvm::cast<ShapedType>(op.getType());
623
624 if (const auto resultETy = resultType.getElementType();
625 !resultETy.isIntOrIndex())
626 return op.emitOpError("result tensor is not of integer type");
627
628 const auto inputType = llvm::cast<ShapedType>(op.getInput().getType());
629 if (!inputType.hasRank())
630 return success();
631
632 // Ensure axis is within the tensor rank
633 const int64_t axis = op.getAxisAttr().getInt();
634 if (((axis < 0) || axis >= inputType.getRank()))
635 return op.emitOpError("specified axis is outside the rank of the tensor");
636
637 if (!resultType.hasRank())
638 return success();
639
640 const ArrayRef<int64_t> inputShape = inputType.getShape();
641 const ArrayRef<int64_t> outputShape = resultType.getShape();
642 llvm::SmallVector<int64_t> expectedOutputShape(inputShape);
643 expectedOutputShape.erase(expectedOutputShape.begin() + axis);
644 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape)))
645 return op.emitOpError("expected output shape '")
646 << expectedOutputShape << "', got '" << outputShape << "'";
647
648 return success();
649}
650
651template <typename T>
652static LogicalResult verifyConvOp(T op) {
653 const auto inputType = llvm::dyn_cast<TensorType>(op.getInput().getType());
654 const auto weightType = llvm::dyn_cast<TensorType>(op.getWeight().getType());
655
656 auto inputEType = inputType.getElementType();
657 auto weightEType = weightType.getElementType();
658 auto biasEType =
659 llvm::cast<ShapedType>(op.getBias().getType()).getElementType();
660 auto resultEType =
661 llvm::cast<ShapedType>(op.getResult().getType()).getElementType();
662 bool biasIsFloat = llvm::isa<FloatType>(biasEType);
663 bool resultIsFloat = llvm::isa<FloatType>(resultEType);
664
665 if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(inputEType))
666 inputEType = getStorageElementTypeFromQuantized(quantType);
667
668 if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(weightEType))
669 weightEType = getStorageElementTypeFromQuantized(quantType);
670
671 if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(biasEType))
672 biasEType = getStorageElementTypeFromQuantized(quantType);
673
674 if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(resultEType))
675 resultEType = getStorageElementTypeFromQuantized(quantType);
676
677 if (biasIsFloat && resultIsFloat && (biasEType != resultEType)) {
678 // for now, only enforce bias element type == result element type for
679 // float types.
680 op.emitOpError(
681 "expect both bias and result to have same element type, got ")
682 << biasEType << " and " << resultEType;
683 return failure();
684 }
685
686 const bool isInputBlockScaled = llvm::isa<BlockScaledType>(inputEType);
687 const bool isWeightBlockScaled = llvm::isa<BlockScaledType>(weightEType);
688 const bool isInputFloat = llvm::isa<FloatType>(inputEType);
689 const bool isWeightFloat = llvm::isa<FloatType>(weightEType);
690
691 const bool isInputBSorFloat = isInputBlockScaled || isInputFloat;
692 const bool isWeightBSorFloat = isWeightBlockScaled || isWeightFloat;
693
694 // Either both must be float or both non-float.
695 if (isInputBSorFloat != isWeightBSorFloat) {
696 op.emitOpError(
697 "expect both input and weight to be float or not together, got ")
698 << inputEType << " and " << weightEType;
699 return failure();
700 }
701
702 auto inputZpEType = getStorageElementTypeOrSelf(op.getInputZp().getType());
703 if (!isInputBlockScaled && inputEType != inputZpEType) {
704 return op.emitOpError("expect both input and its zero point are the same "
705 "element type, got ")
706 << inputEType << " and " << inputZpEType;
707 }
708 if (isInputBlockScaled && !llvm::isa<Float32Type>(inputZpEType)) {
709 return op.emitOpError(
710 "expect block scaled input to have fp32 zero point, got ")
711 << inputEType << " and " << inputZpEType;
712 }
713
714 auto weightZpEType = getStorageElementTypeOrSelf(op.getWeightZp().getType());
715 if (!isWeightBlockScaled && weightEType != weightZpEType) {
716 return op.emitOpError("expect both weight and its zero point are the same "
717 "element type, got ")
718 << weightEType << " and " << weightZpEType;
719 }
720 if (isWeightBlockScaled && !llvm::isa<Float32Type>(weightZpEType)) {
721 return op.emitOpError(
722 "expect block scaled weight to have fp32 zero point, got ")
723 << weightEType << " and " << weightZpEType;
724 }
725
726 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
727 if (succeeded(maybeIZp) && op.verifyInputZeroPoint(*maybeIZp).failed())
728 return failure();
729
730 FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();
731 if (succeeded(maybeWZp) && op.verifyWeightZeroPoint(*maybeWZp).failed())
732 return failure();
733
734 return success();
735}
736
737LogicalResult tosa::ConstOp::verify() {
738 Operation &op = *getOperation();
739 auto attrType = llvm::dyn_cast<TensorType>(getValuesAttr().getType());
740 auto outputType = llvm::dyn_cast<TensorType>(getOutput().getType());
741
742 if (!attrType || !outputType) {
743 emitOpError("expected tensors for attr/result type");
744 return failure();
745 }
746
747 const Type attrElemType = attrType.getElementType();
748 const Type resultElemType = outputType.getElementType();
749
750 if (auto result =
751 llvm::dyn_cast<mlir::quant::QuantizedType>(resultElemType)) {
752 if (getStorageElementTypeFromQuantized(result) == attrElemType)
753 return success();
754 }
755
756 if (auto attrBlockScaledType =
757 llvm::dyn_cast<mlir::tosa::BlockScaledType>(attrElemType)) {
758 if (!attrBlockScaledType.hasScaleValues())
759 return op.emitOpError(
760 "attribute block scaled type must have scale values");
761
762 const auto emitAttributeError = [&op]() {
763 return op.emitOpError("attribute block scaled type is invalid: ");
764 };
765
766 if (failed(verifyBlockScaledTensorType(attrType, emitAttributeError, true)))
767 return failure();
768
769 const BlockScaledType resultBlockScaledType =
770 llvm::dyn_cast<mlir::tosa::BlockScaledType>(resultElemType);
771 if (!resultBlockScaledType)
772 return op.emitOpError(
773 "result type must be block scaled type if attribute is block "
774 "scaled type");
775
776 if (attrBlockScaledType.getValueType() !=
777 resultBlockScaledType.getValueType() ||
778 attrBlockScaledType.getScaleType() !=
779 resultBlockScaledType.getScaleType() ||
780 attrBlockScaledType.getBlockShape() !=
781 resultBlockScaledType.getBlockShape())
782 return op.emitOpError(
783 "expected block scaled element type to be compatible "
784 "between attr and result, got ")
785 << attrBlockScaledType << " vs. " << resultBlockScaledType;
786
787 return success();
788 }
789
790 if (attrElemType != resultElemType)
791 return emitOpError("expected same attr/result element types");
792
793 return success();
794}
795
796//===----------------------------------------------------------------------===//
797// ERROR_IF functions.
798// ERROR_IF is a predicate that must set an error if the condition holds.
799//===----------------------------------------------------------------------===//
800
801template <typename T>
802static LogicalResult verifyConvOpErrorIf(T op) {
803 llvm::ArrayRef<int64_t> padding = op.getPad();
804 if (llvm::any_of(padding, [](int64_t p) { return p < 0; }))
805 return op.emitOpError("expect all padding values to be >= 0, got ")
806 << padding;
807
808 llvm::ArrayRef<int64_t> strides = op.getStride();
809 if (llvm::any_of(strides, [](int64_t s) { return s < 1; }))
810 return op.emitOpError("expect all stride values to be >= 1, got ")
811 << strides;
812
813 llvm::ArrayRef<int64_t> dilations = op.getDilation();
814 if (llvm::any_of(dilations, [](int64_t d) { return d < 1; }))
815 return op.emitOpError("expect all dilation values to be >= 1, got ")
816 << dilations;
817
818 const RankedTensorType outputType =
819 llvm::dyn_cast<RankedTensorType>(op.getOutput().getType());
820 if (!outputType)
821 // Skip following checks if output is not ranked
822 return success();
823
824 const RankedTensorType inputType =
825 llvm::dyn_cast<RankedTensorType>(op.getInput().getType());
826 const RankedTensorType weightType =
827 llvm::dyn_cast<RankedTensorType>(op.getWeight().getType());
828
829 if (inputType && weightType) {
830 // input = [_,IH,IW,_], weight = [_,KH,KW,_], output = [_,OH,OW,_]
831 if constexpr (std::is_same<T, tosa::Conv2DOp>::value) {
832 if (failed(verifyConvOutputSize(
833 op, inputType.getDimSize(1), weightType.getDimSize(1),
834 outputType.getDimSize(1), padding[0], padding[1], strides[0],
835 dilations[0], "height", "y", "top", "bottom")))
836 return failure();
837
838 if (failed(verifyConvOutputSize(
839 op, inputType.getDimSize(2), weightType.getDimSize(2),
840 outputType.getDimSize(2), padding[2], padding[3], strides[1],
841 dilations[1], "width", "x", "left", "right")))
842 return failure();
843 }
844
845 // input = [_,IH,IW,_], weight = [KH,KW,_,_], output = [_,OH,OW,_]
846 if constexpr (std::is_same<T, tosa::DepthwiseConv2DOp>::value) {
847 if (failed(verifyConvOutputSize(
848 op, inputType.getDimSize(1), weightType.getDimSize(0),
849 outputType.getDimSize(1), padding[0], padding[1], strides[0],
850 dilations[0], "height", "y", "top", "bottom")))
851 return failure();
852
853 if (failed(verifyConvOutputSize(
854 op, inputType.getDimSize(2), weightType.getDimSize(1),
855 outputType.getDimSize(2), padding[2], padding[3], strides[1],
856 dilations[1], "width", "x", "left", "right")))
857 return failure();
858 }
859
860 // input = [_,ID,IH,IW,_], weight = [_,KD,KH,KW,_], output = [_,OD,OH,OW,_]
861 if constexpr (std::is_same<T, tosa::Conv3DOp>::value) {
862 if (failed(verifyConvOutputSize(
863 op, inputType.getDimSize(1), weightType.getDimSize(1),
864 outputType.getDimSize(1), padding[0], padding[1], strides[0],
865 dilations[0], "depth", "d", "front", "back")))
866 return failure();
867
868 if (failed(verifyConvOutputSize(
869 op, inputType.getDimSize(2), weightType.getDimSize(2),
870 outputType.getDimSize(2), padding[2], padding[3], strides[1],
871 dilations[1], "height", "y", "top", "bottom")))
872 return failure();
873
874 if (failed(verifyConvOutputSize(
875 op, inputType.getDimSize(3), weightType.getDimSize(3),
876 outputType.getDimSize(3), padding[4], padding[5], strides[2],
877 dilations[2], "width", "x", "left", "right")))
878 return failure();
879 }
880 }
881
882 const RankedTensorType biasType =
883 llvm::dyn_cast<RankedTensorType>(op.getBias().getType());
884 if (!biasType)
885 // Skip following checks if bias is not ranked
886 return success();
887
888 const int64_t biasChannels = biasType.getDimSize(0);
889 const int64_t outputChannels =
890 outputType.getDimSize(outputType.getRank() - 1);
891 if (biasChannels == ShapedType::kDynamic ||
892 outputChannels == ShapedType::kDynamic)
893 // Skip following checks if biasChannels or outputChannels is dynamic dim
894 return success();
895
896 if (biasChannels != outputChannels && biasChannels != 1)
897 return op.emitOpError(
898 "bias channels expected to be equal to output channels (")
899 << outputChannels << ") or 1, got " << biasChannels;
900
901 return success();
902}
903
904// Verify whether same type and shape of the given two types.
905static LogicalResult errorIfTypeOrShapeMismatch(Operation *op, Type type1,
906 StringRef name1, Type type2,
907 StringRef name2) {
908 auto shapeType1 = dyn_cast<ShapedType>(type1);
909 auto shapeType2 = dyn_cast<ShapedType>(type2);
910 if (!shapeType1 || !shapeType2)
911 return failure();
912
913 auto elemType1 = shapeType1.getElementType();
914 auto elemType2 = shapeType2.getElementType();
915 if (elemType1 != elemType2)
916 return op->emitOpError()
917 << "require same element type for " << name1 << " (" << elemType1
918 << ") and " << name2 << " (" << elemType2 << ")";
919
920 if (failed(verifyCompatibleShape(type1, type2)))
921 return op->emitOpError()
922 << "require same shapes for " << name1 << " (" << type1 << ") and "
923 << name2 << " (" << type2 << ")";
924
925 return success();
926}
927
928// Verify whether same length, type, and shape of the given two tensor lists.
929static LogicalResult errorIfTypeOrShapeMismatch(Operation *op, ValueRange list1,
930 StringRef name1,
931 ValueRange list2,
932 StringRef name2) {
933 if (list1.size() != list2.size())
934 return op->emitOpError()
935 << "require same number of values in " << name1 << " ("
936 << list1.size() << ") and " << name2 << " (" << list2.size() << ")";
937
938 for (auto [type1, type2] :
939 llvm::zip_equal(list1.getTypes(), list2.getTypes())) {
940 if (errorIfTypeOrShapeMismatch(op, type1, name1, type2, name2).failed())
941 return failure();
942 }
943
944 return success();
945}
946
947static inline LogicalResult errorIfShapeNotSizeOne(Operation *op, Type type) {
948 ShapeAdaptor shapeAdaptor(type);
949 if (!shapeAdaptor.hasRank() || !shapeAdaptor.hasStaticShape())
950 return success();
951
952 return shapeAdaptor.getNumElements() == 1 ? success() : failure();
953}
954
955template <typename T>
956static LogicalResult verifyVariableOpErrorIf(T op, Type type, StringRef name) {
957 Operation *symTableOp =
958 op->template getParentWithTrait<OpTrait::SymbolTable>();
959 if (!symTableOp)
960 // If the operation is not the scope of a symbol table, we cannot
961 // verify it against it's declaration.
962 return success();
963
964 SymbolTable symTable(symTableOp);
965 const auto varOp = symTable.lookup<tosa::VariableOp>(op.getName());
966
967 // Verify prior declaration
968 if (!varOp)
969 return op->emitOpError("'")
970 << op.getName() << "' has not been declared by 'tosa.variable'";
971
972 // Verify type and shape
973 auto variableType = getVariableType(varOp);
974 if (errorIfTypeOrShapeMismatch(op, type, name, variableType,
975 "the input tensor")
976 .failed())
977 return failure();
978 return success();
979}
980
981// verify that inType and outType have same element types
982static LogicalResult verifySameElementTypes(Operation *op, Type aType,
983 Type bType,
984 StringRef aName = "input",
985 StringRef bName = "output") {
986 auto aTType = llvm::dyn_cast<TensorType>(aType);
987 auto bTType = llvm::dyn_cast<TensorType>(bType);
988 if (!aTType) {
989 op->emitOpError("expect shaped tensor for") << aName << ", got " << aType;
990 return failure();
991 }
992 if (!bTType) {
993 op->emitOpError("expect shaped tensor for") << bName << ", got" << bType;
994 return failure();
995 }
996 auto aElementType = aTType.getElementType();
997 auto bElementType = bTType.getElementType();
998 auto aQuantType =
999 llvm::dyn_cast<mlir::quant::UniformQuantizedType>(aElementType);
1000 auto bQuantType =
1001 llvm::dyn_cast<mlir::quant::UniformQuantizedType>(bElementType);
1002 if ((aElementType.isIntOrIndexOrFloat() || aQuantType) &&
1003 (bElementType.isIntOrIndexOrFloat() || bQuantType) &&
1004 aElementType != bElementType) {
1005 // only check if both element types are int/index/float/UniformQuantized
1006 // eg, not sure how to check quant::QuantizedType
1007 // this happens in test_conv2d_q_grouped_convolution in
1008 // tfl-to-tosa-pipeline.mlir
1009 op->emitOpError("expect ")
1010 << aName << " and " << bName << " to have same element type, got "
1011 << aElementType << " and " << bElementType;
1012 return failure();
1013 }
1014 return success();
1015}
1016
1017LogicalResult tosa::ArgMaxOp::verify() { return argMaxMinVerify(*this); }
1018
1019LogicalResult tosa::ArgMinOp::verify() { return argMaxMinVerify(*this); }
1020
1021static LogicalResult verifyPoolingOpImpl(Operation *op,
1022 ArrayRef<int64_t> kernel,
1023 ArrayRef<int64_t> strides,
1024 ArrayRef<int64_t> padding, Value input,
1025 Value output) {
1026 if (failed(verifySameElementTypes(op, input.getType(), output.getType())))
1027 return failure();
1028
1029 const bool hasKernel = kernel.size() > 0;
1030 const bool hasStrides = strides.size() > 0;
1031 const bool hasPad = padding.size() > 0;
1032
1033 if (hasKernel && llvm::any_of(kernel, [](int64_t s) { return s < 1; }))
1034 return op->emitOpError("expect all kernel values to be >= 1, got ")
1035 << kernel;
1036
1037 if (hasStrides && llvm::any_of(strides, [](int64_t s) { return s < 1; }))
1038 return op->emitOpError("expect all stride values to be >= 1, got ")
1039 << strides;
1040
1041 if (hasPad && llvm::any_of(padding, [](int64_t p) { return p < 0; }))
1042 return op->emitOpError("expect all padding values to be >= 0, got ")
1043 << padding;
1044
1045 if (hasKernel && hasPad) {
1046 // Padding must be less than kernel size to avoid a divide-by-zero
1047 const int64_t kernelX = kernel[1];
1048 const int64_t padLeft = padding[2];
1049 const int64_t padRight = padding[3];
1050 if (padRight >= kernelX || padLeft >= kernelX)
1051 return op->emitOpError("expected left/right padding to be less than the "
1052 "width of the kernel, got pad_left=")
1053 << padLeft << ", pad_right=" << padRight
1054 << ", kernel_x=" << kernelX;
1055
1056 const int64_t kernelY = kernel[0];
1057 const int64_t padTop = padding[0];
1058 const int64_t padBottom = padding[1];
1059 if (padTop >= kernelY || padBottom >= kernelY)
1060 return op->emitOpError("expected top/bottom padding to be less than the "
1061 "height of the kernel, got pad_top=")
1062 << padTop << ", pad_bottom=" << padBottom
1063 << ", kernel_y=" << kernelY;
1064 }
1065
1066 const auto inputType = llvm::dyn_cast<RankedTensorType>(input.getType());
1067 const auto outputType = llvm::dyn_cast<RankedTensorType>(output.getType());
1068 if (!inputType || !outputType)
1069 return success();
1070
1071 if (hasKernel && hasStrides && hasPad) {
1072 const auto verifyOutputSize =
1073 [op](const int64_t inputSize, const int64_t outputSize,
1074 const int64_t kernelSize, const int64_t strideSize,
1075 const int64_t padBefore, const int64_t padAfter,
1076 const llvm::StringRef dimName, const llvm::StringRef dimAxis,
1077 const llvm::StringRef padBeforeName,
1078 const llvm::StringRef padAfterName) -> LogicalResult {
1079 if (ShapedType::isDynamic(inputSize))
1080 return success();
1081
1082 const std::optional<int64_t> calculatedOutSizeMinusOne =
1083 idivCheck(inputSize + padBefore + padAfter - kernelSize, strideSize);
1084 if (!calculatedOutSizeMinusOne.has_value())
1085 return op->emitOpError("expected input_")
1086 << dimName << " + pad_" << padBeforeName << " + pad_"
1087 << padAfterName << " - kernel_" << dimAxis
1088 << " to be wholly divisible by stride_" << dimAxis << ", got ("
1089 << inputSize << " + " << padBefore << " + " << padAfter << " - "
1090 << kernelSize << ") / " << strideSize;
1091
1092 const int64_t calculatedOutSize = calculatedOutSizeMinusOne.value() + 1;
1093 if (ShapedType::isStatic(outputSize) && calculatedOutSize != outputSize)
1094 return op->emitOpError("calculated output ")
1095 << dimName << " did not match expected: " << "calculated="
1096 << calculatedOutSize << ", expected=" << outputSize;
1097
1098 return success();
1099 };
1100
1101 if (failed(verifyOutputSize(inputType.getDimSize(1),
1102 outputType.getDimSize(1), kernel[0], strides[0],
1103 padding[0], padding[1], "height", "y", "top",
1104 "bottom")))
1105 return failure();
1106
1107 if (failed(verifyOutputSize(
1108 inputType.getDimSize(2), outputType.getDimSize(2), kernel[1],
1109 strides[1], padding[2], padding[3], "width", "x", "left", "right")))
1110 return failure();
1111 }
1112 return success();
1113}
1114
1115template <typename T>
1116static LogicalResult verifyPoolingOp(T op) {
1117 return verifyPoolingOpImpl(op.getOperation(), op.getKernel(), op.getStride(),
1118 op.getPad(), op.getInput(), op.getOutput());
1119}
1120
1121template <typename T>
1122static LogicalResult verifyAvgPoolCommonTypeAndZpChecks(T op) {
1123 const Type inputETy = getStorageElementTypeOrSelf(op.getInput().getType());
1124 const Type resultETy = getStorageElementTypeOrSelf(op.getOutput().getType());
1125 const Type inputZpETy =
1126 getStorageElementTypeOrSelf(op.getInputZp().getType());
1127 const Type outputZpETy =
1128 getStorageElementTypeOrSelf(op.getOutputZp().getType());
1129
1130 auto accType = op.getAccType();
1131 if (llvm::isa<IntegerType>(inputETy) && !accType.isInteger(32))
1132 return op.emitOpError("accumulator type for integer tensor is not i32");
1133
1134 if (inputETy.isF16() && !(accType.isF16() || accType.isF32()))
1135 return op.emitOpError("accumulator type for f16 tensor is not f16/f32");
1136
1137 if (inputETy.isBF16() && !accType.isF32())
1138 return op.emitOpError("accumulator type for bf16 tensor is not f32");
1139
1140 if (inputETy.isF32() && !accType.isF32())
1141 return op.emitOpError("accumulator type for f32 tensor is not f32");
1142
1143 if (inputETy != inputZpETy)
1144 return op.emitOpError("expect both input and its zero point are the same "
1145 "element type, got ")
1146 << inputETy << " and " << inputZpETy;
1147
1148 if (resultETy != outputZpETy)
1149 return op.emitOpError("expect both output and its zero point are the same "
1150 "element type, got ")
1151 << resultETy << " and " << outputZpETy;
1152
1153 FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();
1154 if (succeeded(maybeIZp) && op.verifyInputZeroPoint(*maybeIZp).failed())
1155 return failure();
1156
1157 FailureOr<int64_t> maybeOZp = op.getOutputZeroPoint();
1158 if (succeeded(maybeOZp) && op.verifyOutputZeroPoint(*maybeOZp).failed())
1159 return failure();
1160
1161 return success();
1162}
1163
1164namespace {
1165struct AdaptivePoolingConstShapeValues {
1166 llvm::SmallVector<int64_t> kernel;
1167 llvm::SmallVector<int64_t> stride;
1168 llvm::SmallVector<int64_t> pad;
1169};
1170} // namespace
1171
1172template <typename T>
1174 std::is_same_v<T, tosa::AvgPool2dAdaptiveOp> ||
1175 std::is_same_v<T, tosa::MaxPool2dAdaptiveOp>;
1176
1177template <typename T,
1178 typename std::enable_if<IsSupportedAdaptivePoolConstShapeVerifyOp<T>,
1179 int>::type = 0>
1181 T op, AdaptivePoolingConstShapeValues &values) {
1182 tosa::getConstShapeValues(op.getKernel().getDefiningOp(), values.kernel);
1183 tosa::getConstShapeValues(op.getStride().getDefiningOp(), values.stride);
1184 tosa::getConstShapeValues(op.getPad().getDefiningOp(), values.pad);
1185}
1186
1187LogicalResult tosa::AvgPool2dOp::verify() {
1188 if (failed(verifyPoolingOp(*this)))
1189 return failure();
1191 return failure();
1192 return success();
1193}
1194
1195LogicalResult tosa::AvgPool2dAdaptiveOp::verify() {
1196 AdaptivePoolingConstShapeValues values;
1198
1199 // If pad/stride/kernel are not constant, this is okay, we just can't check
1200 // their values. extractAdaptivePoolingConstShapeOperands will return an empty
1201 // list for each non CTC input. verifyPoolingOpImpl will need to handle values
1202 // not being present, and return success if they cannot be checked.
1203
1204 if (failed(verifyPoolingOpImpl(getOperation(), values.kernel, values.stride,
1205 values.pad, getInput(), getOutput())))
1206 return failure();
1207
1209 return failure();
1210
1211 return success();
1212}
1213
1214LogicalResult tosa::ClampOp::verify() {
1215 mlir::Type inputETy =
1216 llvm::cast<ShapedType>(getInput().getType()).getElementType();
1217 if (auto quantType =
1218 llvm::dyn_cast<mlir::quant::UniformQuantizedType>(inputETy)) {
1219 inputETy = getStorageElementTypeFromQuantized(quantType);
1220 }
1221 mlir::Type outputETy =
1222 llvm::cast<ShapedType>(getOutput().getType()).getElementType();
1223 if (auto quantType =
1224 llvm::dyn_cast<mlir::quant::UniformQuantizedType>(outputETy)) {
1225 outputETy = getStorageElementTypeFromQuantized(quantType);
1226 }
1227 if (inputETy != outputETy)
1228 return emitOpError("input/output element types are incompatible.");
1229
1230 auto maxValAttr = getMaxValAttr();
1231 auto minValAttr = getMinValAttr();
1232
1233 unsigned dataTypeBitWidth = inputETy.getIntOrFloatBitWidth();
1234
1235 if (inputETy.isInteger(dataTypeBitWidth)) {
1236 // if input datatype is integer, check that the min_val/max_val attributes
1237 // are integer attributes, and that their type is the same as the input's
1238 // datatype
1239 auto intMaxValAttr = mlir::dyn_cast<mlir::IntegerAttr>(maxValAttr);
1240 auto intMinValAttr = mlir::dyn_cast<mlir::IntegerAttr>(minValAttr);
1241 if (!intMaxValAttr || !intMinValAttr ||
1242 (intMaxValAttr.getType() != intMinValAttr.getType()) ||
1243 (intMaxValAttr.getType() != inputETy))
1244 return emitOpError("min/max attributes types are incompatible with "
1245 "input/output element types.");
1246
1247 const bool isUnsigned = inputETy.isUnsignedInteger();
1248 const bool isBoolean = inputETy.isInteger(1);
1249 const APInt minVal = intMinValAttr.getValue();
1250 const APInt maxVal = intMaxValAttr.getValue();
1251 if ((isUnsigned || isBoolean) ? maxVal.ult(minVal) : maxVal.slt(minVal))
1252 return emitOpError("expected min_val <= max_val, got min_val=")
1253 << minValAttr << ", max_val=" << maxValAttr;
1254 } else {
1255 // otherwise, input datatype is float, check that the min_val/max_val
1256 // attributes share the same type and that their type is the same as the
1257 // input's datatype
1258 auto floatMaxValAttr = mlir::dyn_cast<mlir::FloatAttr>(maxValAttr);
1259 auto floatMinValAttr = mlir::dyn_cast<mlir::FloatAttr>(minValAttr);
1260 if (!floatMaxValAttr || !floatMinValAttr ||
1261 (floatMaxValAttr.getType() != floatMinValAttr.getType()) ||
1262 (floatMaxValAttr.getType() != inputETy))
1263 return emitOpError("min/max attributes types are incompatible with "
1264 "input/output element types.");
1265
1266 const APFloat minVal = floatMinValAttr.getValue();
1267 const APFloat maxVal = floatMaxValAttr.getValue();
1268 if (minVal.isNaN() || maxVal.isNaN())
1269 return emitOpError("min/max attributes should not be 'NaN', got min_val=")
1270 << minValAttr << ", max_val=" << maxValAttr;
1271
1272 if (maxVal < minVal)
1273 return emitOpError("expected min_val <= max_val, got min_val=")
1274 << minValAttr << ", max_val=" << maxValAttr;
1275 }
1276
1277 return success();
1278}
1279
1280//===----------------------------------------------------------------------===//
1281// TOSA Operator Quantization Builders.
1282//===----------------------------------------------------------------------===//
1283
1284/// This builder is called on all convolution operators except TransposeConv,
1285/// which has specialized output shape semantics. The builder also defines the
1286/// bitwidth of the output given the bit width of the input & weight content.
1288 Type outputType, Value input, Value weight,
1289 Value bias, DenseI64ArrayAttr pad,
1290 DenseI64ArrayAttr stride,
1291 DenseI64ArrayAttr dilation,
1292 TypeAttr accType) {
1293 auto zps = createZPsAsConst(builder, input, weight);
1294 result.addOperands({input, weight, bias, zps.first, zps.second});
1295 result.addAttribute("pad", pad);
1296 result.addAttribute("stride", stride);
1297 result.addAttribute("dilation", dilation);
1298 result.addAttribute("acc_type", accType);
1299 Type finalOutputType = outputType;
1300 auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight);
1301 if (quantAttr) {
1302 finalOutputType =
1303 buildConvOpResultTypeInfo(builder, outputType, input, weight);
1304 }
1305 result.addTypes(finalOutputType);
1306}
1307
1308/// Handles tosa.transpose_conv2d which has outpad and output shape
1309/// attributes.
1310static void
1312 Type outputType, Value input, Value weight,
1313 Value bias, DenseI64ArrayAttr outpad,
1314 DenseI64ArrayAttr stride, TypeAttr accType) {
1315 auto zps = createZPsAsConst(builder, input, weight);
1316 result.addOperands({input, weight, bias, zps.first, zps.second});
1317 result.addAttribute("out_pad", outpad);
1318 result.addAttribute("stride", stride);
1319 result.addAttribute("acc_type", accType);
1320 Type finalOutputType = outputType;
1321 auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight);
1322 if (quantAttr) {
1323 finalOutputType =
1324 buildConvOpResultTypeInfo(builder, outputType, input, weight);
1325 }
1326 result.addTypes(finalOutputType);
1327}
1328
1331 Type outputType, Value a, Value b) {
1332 const std::pair<Value, Value> zps = createZPsAsConst(builder, a, b);
1333 result.addOperands({a, b, zps.first, zps.second});
1334
1335 Type finalOutputType{outputType};
1336 if (buildMatMulOpQuantizationAttr(builder, a, b)) {
1337 auto eType = getStorageElementTypeOrSelf(a.getType());
1338 auto inputBits = eType.getIntOrFloatBitWidth();
1339
1340 auto outputShapedType = llvm::dyn_cast<ShapedType>(outputType);
1341 assert(outputShapedType && "Output must be a shaped type");
1342
1343 IntegerType accElementType;
1344 if (inputBits == 16)
1345 accElementType = builder.getIntegerType(48);
1346 else
1347 accElementType = builder.getI32Type();
1348
1349 finalOutputType = outputShapedType.clone(accElementType);
1350 }
1351 result.addTypes(finalOutputType);
1352}
1353
1355 OperationState &result, Type outputType,
1356 Value a, Value b) {
1357 buildMatMulLikeOpWithQuantInfo(builder, result, outputType, a, b);
1358}
1359
1361 OperationState &result, Type outputType,
1362 Value a, Value b) {
1363 buildMatMulLikeOpWithQuantInfo(builder, result, outputType, a, b);
1364}
1365
1366/// Both the tosa.avg_pool2d and unary ops use the same
1367/// UnaryOpQuantizationAttr but avg_pool operator has its own builder as it
1368/// has additional parameters not part of the unary ops.
1369static void
1371 Type outputType, Value input,
1372 DenseArrayAttr kernel, DenseArrayAttr stride,
1373 DenseArrayAttr pad, TypeAttr accType) {
1374 const Location loc{result.location};
1375 int64_t inputZp{0};
1376 int64_t outputZp{0};
1377
1378 if (auto quantAttr =
1379 buildUnaryOpQuantizationAttr(builder, input, outputType)) {
1380 inputZp = quantAttr.getInputZp();
1381 outputZp = quantAttr.getOutputZp();
1382 }
1383 const std::optional<Value> inputZpOp =
1384 createZeroPointTensor(builder, loc, input.getType(), inputZp);
1385 if (!inputZpOp) {
1386 (void)emitError(
1387 loc,
1388 "Failed to create input zero point tensor for quantized AVG_POOL2D op");
1389 }
1390 const std::optional<Value> outputZpOp =
1391 createZeroPointTensor(builder, loc, outputType, outputZp);
1392 if (!outputZpOp) {
1393 (void)emitError(loc, "Failed to create output zero point tensor for "
1394 "quantized AVG_POOL2D op");
1395 }
1396
1397 if (inputZpOp && outputZpOp) {
1398 result.addOperands({input, inputZpOp.value(), outputZpOp.value()});
1399 } else {
1400 // failed to create one or more zero points above: just add input as
1401 // operands this will trigger error in building the op because of missing
1402 // zero points
1403 result.addOperands({input});
1404 }
1405 result.addAttribute("kernel", kernel);
1406 result.addAttribute("stride", stride);
1407 result.addAttribute("pad", pad);
1408 result.addAttribute("acc_type", accType);
1409 result.types.push_back(outputType);
1410}
1411
1412/// This builder mirrors avg_pool2d quant-info handling and materializes
1413/// kernel/stride/pad as const_shape operands for avg_pool2d_adaptive.
1415 OpBuilder &builder, OperationState &result, Type outputType, Value input,
1417 TypeAttr accType) {
1418 const Location loc{result.location};
1419 int64_t inputZp{0};
1420 int64_t outputZp{0};
1421
1422 if (auto quantAttr =
1423 buildUnaryOpQuantizationAttr(builder, input, outputType)) {
1424 inputZp = quantAttr.getInputZp();
1425 outputZp = quantAttr.getOutputZp();
1426 }
1427 const std::optional<Value> inputZpOp =
1428 createZeroPointTensor(builder, loc, input.getType(), inputZp);
1429 if (!inputZpOp) {
1430 (void)emitError(loc,
1431 "Failed to create input zero point tensor for quantized "
1432 "AVG_POOL2D_ADAPTIVE op");
1433 }
1434 const std::optional<Value> outputZpOp =
1435 createZeroPointTensor(builder, loc, outputType, outputZp);
1436 if (!outputZpOp) {
1437 (void)emitError(loc, "Failed to create output zero point tensor for "
1438 "quantized AVG_POOL2D_ADAPTIVE op");
1439 }
1440
1441 if (inputZpOp && outputZpOp) {
1442 ImplicitLocOpBuilder b(loc, builder);
1443 Value kernelShape = getTosaConstShape(b, kernel.asArrayRef());
1444 Value strideShape = getTosaConstShape(b, stride.asArrayRef());
1445 Value padShape = getTosaConstShape(b, pad.asArrayRef());
1446 result.addOperands({input, inputZpOp.value(), outputZpOp.value(),
1447 kernelShape, strideShape, padShape});
1448 } else {
1449 // Failed to create one or more zero points above: just add input as
1450 // operands. This will trigger error in building the op because of missing
1451 // operands.
1452 result.addOperands({input});
1453 }
1454 result.addAttribute("acc_type", accType);
1455 result.types.push_back(outputType);
1456}
1457
1458/// This builder is called on single-parameter negate operator
1459/// to construct input and output zero points based on their
1460/// types.
1462 OperationState &result, Type outputType,
1463 Value input) {
1464 const Location loc{result.location};
1465 int64_t input1Zp{0};
1466 int64_t outputZp{0};
1467 auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType);
1468 if (quantAttr) {
1469 input1Zp = quantAttr.getInputZp();
1470 outputZp = quantAttr.getOutputZp();
1471 }
1472 const std::optional<Value> input1ZpOp =
1473 createZeroPointTensor(builder, loc, input.getType(), input1Zp);
1474 if (!input1ZpOp) {
1475 (void)emitError(
1476 loc, "Failed to create input1 zero point for quantized NEGATE op");
1477 }
1478
1479 const std::optional<Value> outputZpOp =
1480 createZeroPointTensor(builder, loc, input.getType(), outputZp);
1481 if (!outputZpOp) {
1482 (void)emitError(
1483 loc, "Failed to create output zero point for quantized NEGATE op");
1484 }
1485
1486 if (input1ZpOp && outputZpOp) {
1487 result.addOperands({input, input1ZpOp.value(), outputZpOp.value()});
1488 } else {
1489 // failed to create one or more zero points above: just add input as
1490 // operands. This will trigger error in building the op because of
1491 // missing zero points
1492 result.addOperands({input});
1493 }
1494
1495 result.types.push_back(outputType);
1496}
1497
1498/// This builder is called on TOSA pad operator that needs to create its own
1499/// OptionalAttr quantization_attr parameter to scale the padding values
1500/// correctly. No pad_const is interpreted as zero-padding.
1502 Type outputType, Value input,
1503 Value paddings) {
1504 const Location loc{result.location};
1505 int32_t zp{0};
1506 const auto quantAttr = buildPadOpQuantizationAttr(builder, input);
1507 if (quantAttr) {
1508 zp = static_cast<int32_t>(quantAttr.getInputZp());
1509 }
1510 const auto padConstOp{createPadConstTensor(builder, loc, input, zp)};
1511 result.addOperands({input, paddings, padConstOp});
1512 result.types.push_back(outputType);
1513}
1514
1516 StringRef name, Type variableType,
1517 Attribute initialValue) {
1518 const Location loc{result.location};
1519 auto nameAttr = builder.getStringAttr(name);
1520
1521 auto shapedType = dyn_cast<ShapedType>(variableType);
1522 if (!shapedType) {
1523 (void)emitError(loc, "variable type must be a shaped type");
1524 return;
1525 }
1526 if (!shapedType.hasRank()) {
1527 (void)emitError(loc, "variable type must be a ranked type");
1528 return;
1529 }
1530
1531 auto elementType = shapedType.getElementType();
1532 auto elementTypeAttr = TypeAttr::get(elementType);
1533 ArrayRef<int64_t> shape = shapedType.getShape();
1534 auto varShapeAttr = builder.getIndexTensorAttr(convertFromMlirShape(shape));
1535
1536 result.addAttribute("sym_name", nameAttr);
1537 result.addAttribute("var_shape", varShapeAttr);
1538 result.addAttribute("type", elementTypeAttr);
1539 result.addAttribute("initial_value", initialValue);
1540}
1541
1542//===----------------------------------------------------------------------===//
1543// TOSA Operator Return Type Inference.
1544//===----------------------------------------------------------------------===//
1545static FailureOr<int64_t> resolveBroadcastDim(const int64_t dim1,
1546 const int64_t dim2) {
1547 if (dim1 == 1)
1548 return dim2;
1549 if (dim2 == 1)
1550 return dim1;
1551
1552 if (ShapedType::isStatic(dim1) && ShapedType::isStatic(dim2) && dim1 != dim2)
1553 return failure();
1554
1555 // Prefer static dimension over dynamic
1556 return ShapedType::isDynamic(dim1) ? dim2 : dim1;
1557}
1558
1559static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands,
1560 SmallVector<int64_t> &outShape) {
1561 int64_t outRank = 0;
1562 for (int i = 0, e = operands.size(); i != e; ++i) {
1563 auto shape = operands.getShape(i);
1564 if (!shape.hasRank()) {
1565 // TODO(jennik): Update function to have better case handling for
1566 // invalid operands and for ranked tensors.
1567 return failure();
1568 }
1569 outRank = std::max<int64_t>(outRank, shape.getRank());
1570 }
1571
1572 outShape.resize(outRank, 1);
1573
1574 for (int i = 0, e = operands.size(); i != e; ++i) {
1575 auto shape = operands.getShape(i);
1576 auto rankDiff = outShape.size() - shape.getRank();
1577
1578 for (size_t i = 0, e = shape.getRank(); i < e; ++i) {
1579 auto dim1 = outShape[i + rankDiff];
1580 auto dim2 = shape.getDimSize(i);
1581
1582 const FailureOr<int64_t> maybeResolvedDim =
1583 resolveBroadcastDim(dim1, dim2);
1584 if (failed(maybeResolvedDim))
1585 return failure();
1586 const int64_t resolvedDim = *maybeResolvedDim;
1587 outShape[i + rankDiff] = resolvedDim;
1588 }
1589 }
1590
1591 return success();
1592}
1593
1594LogicalResult tosa::ArgMaxOp::inferReturnTypeComponents(
1595 MLIRContext *context, ::std::optional<Location> location,
1596 ArgMaxOp::Adaptor adaptor,
1597 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1598 return inferArgMaxMinReturnTypeComponents(context, location, adaptor,
1599 inferredReturnShapes);
1600}
1601
1602LogicalResult tosa::ArgMinOp::inferReturnTypeComponents(
1603 MLIRContext *context, ::std::optional<Location> location,
1604 ArgMinOp::Adaptor adaptor,
1605 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1606 return inferArgMaxMinReturnTypeComponents(context, location, adaptor,
1607 inferredReturnShapes);
1608}
1609
1610LogicalResult tosa::RFFT2dOp::inferReturnTypeComponents(
1611 MLIRContext *context, ::std::optional<Location> location,
1612 RFFT2dOp::Adaptor adaptor,
1613 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1614 ShapeAdaptor inputShape(adaptor.getInputReal().getType());
1615
1616 if (!inputShape.hasRank())
1617 return failure();
1618
1619 llvm::SmallVector<int64_t> outputShape;
1620 outputShape.resize(3, ShapedType::kDynamic);
1621 outputShape[0] = inputShape.getDimSize(0);
1622 outputShape[1] = inputShape.getDimSize(1);
1623 int64_t inWidth = inputShape.getDimSize(2);
1624
1625 // Note that we can support this calculation symbolically
1626 // in the future e.g. [x, y, z] -> [x, y, z / 2 + 1]
1627 if (inWidth != ShapedType::kDynamic)
1628 outputShape[2] = inWidth / 2 + 1;
1629
1630 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1631 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
1632
1633 return success();
1634}
1635
1636static LogicalResult verifyDimIsPowerOfTwo(Operation *op, const int64_t dimSize,
1637 const llvm::StringRef dimName) {
1638 const bool isPowerOfTwo = (dimSize & (dimSize - 1)) == 0 && dimSize > 0;
1639 if (!isPowerOfTwo)
1640 return op->emitOpError("expected ")
1641 << dimName << " to be a power of two, got " << dimSize;
1642
1643 return success();
1644}
1645
1646LogicalResult tosa::RFFT2dOp::verify() {
1647 const auto outputTypes = getResultTypes();
1648 if (failed(verifyCompatibleShapes(outputTypes)))
1649 return emitOpError("expected output shapes to match, got ") << outputTypes;
1650
1651 const auto inputType =
1652 llvm::dyn_cast<RankedTensorType>(getInputReal().getType());
1653 if (!inputType)
1654 return success();
1655
1656 const int64_t height = inputType.getDimSize(1);
1657 if (ShapedType::isStatic(height) &&
1658 failed(verifyDimIsPowerOfTwo(*this, height, "height")))
1659 return failure();
1660
1661 const int64_t width = inputType.getDimSize(2);
1662 if (ShapedType::isStatic(width) &&
1663 failed(verifyDimIsPowerOfTwo(*this, width, "width")))
1664 return failure();
1665
1666 const auto outputType = llvm::dyn_cast<RankedTensorType>(outputTypes[0]);
1667 if (!outputType)
1668 return success();
1669
1670 // Batch and height input/output dimensions should match
1671 if (failed(verifyCompatibleShape(inputType.getShape().drop_back(),
1672 outputType.getShape().drop_back())))
1673 return emitOpError("expected batch and height dimensions of input/output "
1674 "to match, got input=")
1675 << inputType << " output=" << outputType;
1676
1677 // Output width dimension expected to be input_width / 2 + 1
1678 const int64_t outputWidth = outputType.getDimSize(2);
1679 if (ShapedType::isStatic(width) && ShapedType::isStatic(outputWidth) &&
1680 (outputWidth != (width / 2) + 1))
1681 return emitOpError(
1682 "expected output width to be equal to input_width / 2 + 1, got ")
1683 << outputWidth;
1684
1685 return success();
1686}
1687
1688LogicalResult tosa::FFT2dOp::inferReturnTypeComponents(
1689 MLIRContext *context, ::std::optional<Location> location,
1690 FFT2dOp::Adaptor adaptor,
1691 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1692 inferredReturnShapes.push_back(
1693 ShapedTypeComponents(ShapeAdaptor(adaptor.getInputReal().getType())));
1694 inferredReturnShapes.push_back(
1695 ShapedTypeComponents(ShapeAdaptor(adaptor.getInputImag().getType())));
1696 return success();
1697}
1698
1699LogicalResult tosa::FFT2dOp::verify() {
1700 const auto inputRealType =
1701 llvm::dyn_cast<RankedTensorType>(getInputReal().getType());
1702 const auto inputImagType =
1703 llvm::dyn_cast<RankedTensorType>(getInputImag().getType());
1704 if (!inputRealType || !inputImagType)
1705 return success();
1706
1707 const auto trySelectStaticDim = [](const int64_t a, const int64_t b) {
1708 return ShapedType::isDynamic(a) ? a : b;
1709 };
1710
1711 const int64_t height = trySelectStaticDim(inputRealType.getDimSize(1),
1712 inputImagType.getDimSize(1));
1713 if (ShapedType::isStatic(height) &&
1714 failed(verifyDimIsPowerOfTwo(*this, height, "height")))
1715 return failure();
1716
1717 const int64_t width = trySelectStaticDim(inputRealType.getDimSize(2),
1718 inputImagType.getDimSize(2));
1719 if (ShapedType::isStatic(width) &&
1720 failed(verifyDimIsPowerOfTwo(*this, width, "width")))
1721 return failure();
1722
1723 return success();
1724}
1725
1726LogicalResult tosa::ConcatOp::inferReturnTypeComponents(
1727 MLIRContext *context, ::std::optional<Location> location,
1728 ConcatOp::Adaptor adaptor,
1729 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1730 // Infer all dimension sizes by reducing based on inputs.
1731 const Properties &prop = adaptor.getProperties();
1732 int32_t axis = prop.axis.getValue().getSExtValue();
1733 llvm::SmallVector<int64_t> outputShape;
1734 bool hasRankedInput = false;
1735 for (auto operand : adaptor.getOperands()) {
1736 ShapeAdaptor operandShape(operand.getType());
1737 if (!operandShape.hasRank())
1738 continue;
1739
1740 // Copy the Operand's rank.
1741 if (!hasRankedInput)
1742 outputShape.resize(operandShape.getRank(), ShapedType::kDynamic);
1743
1744 // Copy shapes until the dim is non-dynamic.
1745 for (int i = 0, s = operandShape.getRank(); i < s; i++) {
1746 if (i == axis || operandShape.isDynamicDim(i))
1747 continue;
1748 if (outputShape[i] == ShapedType::kDynamic)
1749 outputShape[i] = operandShape.getDimSize(i);
1750 if (outputShape[i] != operandShape.getDimSize(i))
1751 return emitOptionalError(location,
1752 "Cannot concat tensors with different sizes"
1753 " on the non-axis dimension ",
1754 i);
1755 }
1756
1757 hasRankedInput = true;
1758 }
1759
1760 if (adaptor.getInput1().empty())
1761 return failure();
1762
1763 Type inputType =
1764 llvm::cast<TensorType>(adaptor.getInput1().getType()[0]).getElementType();
1765 if (!hasRankedInput) {
1766 inferredReturnShapes.push_back(ShapedTypeComponents(inputType));
1767 return success();
1768 }
1769
1770 // Determine the dimension size along the concatenation axis.
1771 int64_t concatDimSize = 0;
1772 for (auto operand : adaptor.getOperands()) {
1773 ShapeAdaptor operandShape(operand.getType());
1774
1775 // We need to know the length of the concatenation axis of all inputs to
1776 // determine the dimension size of the output shape.
1777 if (!operandShape.hasRank() || operandShape.isDynamicDim(axis)) {
1778 concatDimSize = ShapedType::kDynamic;
1779 break;
1780 }
1781
1782 concatDimSize += operandShape.getDimSize(axis);
1783 }
1784
1785 outputShape[axis] = concatDimSize;
1786
1787 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));
1788 return success();
1789}
1790
1791LogicalResult tosa::ConcatOp::verify() {
1792 // check that each input has same element type as output
1793 auto outType = getOutput().getType();
1794 const Operation::operand_range inputList = getInput1();
1795
1796 // Check there is at least one input
1797 if (inputList.empty())
1798 return emitOpError("expect at least one input");
1799
1800 if (!llvm::all_of(inputList, [&](auto input) {
1801 return succeeded(verifySameElementTypes(
1802 *this, /* inType = */ input.getType(), outType));
1803 })) {
1804 return failure();
1805 }
1806
1807 const int32_t axis = getAxis();
1808 ShapeAdaptor firstRankedInputShape = nullptr;
1809 for (const auto &input : inputList) {
1810 const Type inputType = input.getType();
1811 ShapeAdaptor currShape(inputType);
1812 if (currShape.hasRank()) {
1813 firstRankedInputShape = currShape;
1814 // Check axis is in expected range
1815 if (axis < 0 || axis >= firstRankedInputShape.getRank())
1816 return emitOpError("expect axis to be within range 0 < axis < "
1817 "rank(input1[firstRankedTensorIdx]), got ")
1818 << axis;
1819 break;
1820 }
1821 }
1822
1823 const auto allOperandsHasRank = [](const Value input) {
1824 return ShapeAdaptor(input.getType()).hasRank();
1825 };
1826 if (llvm::all_of(inputList, allOperandsHasRank)) {
1827 const int64_t firstInputRank = firstRankedInputShape.getRank();
1828
1829 for (const auto &[index, input] : llvm::enumerate(inputList.drop_front())) {
1830 const ShapeAdaptor inputShape(input.getType());
1831 const int64_t inputRank = inputShape.getRank();
1832 const size_t operandNum = index + 1;
1833
1834 // Check that each operand has the same rank
1835 if (inputRank != firstInputRank)
1836 return emitOpError(
1837 "expect all operands to have the same rank, but got ")
1838 << firstInputRank << " vs " << inputRank << " on operands 0 and "
1839 << operandNum;
1840
1841 // Check non-axis dims match
1842 for (int i = 0; i < inputRank; i++) {
1843 const int64_t inputDim = inputShape.getDimSize(i);
1844 const int64_t firstInputDim = firstRankedInputShape.getDimSize(i);
1845 if (i == axis || firstRankedInputShape.isDynamicDim(i) ||
1846 inputShape.isDynamicDim(i))
1847 continue;
1848 if (inputDim != firstInputDim)
1849 return emitOpError("expect all operand shapes to have the same sizes "
1850 "on non-axis dimensions, but got ")
1851 << inputDim << " vs " << firstInputDim << " at index " << i
1852 << " on operands 0 and " << operandNum;
1853 }
1854 }
1855
1856 const ShapeAdaptor outputShape(outType);
1857 if (outputShape.hasRank() && outputShape.getRank() != firstInputRank)
1858 return emitOpError("expect output rank to match inputs rank, got ")
1859 << outputShape.getRank() << " vs " << firstInputRank;
1860
1861 // ERROR_IF(axis_sum != shape[axis]);
1862 int64_t axisSum = 0;
1863 for (const auto &input : inputList) {
1864 const ShapeAdaptor inputShape(input.getType());
1865 if (inputShape.isDynamicDim(axis)) {
1866 // make axisSum negative to indicate invalid value
1867 axisSum = -1;
1868 break;
1869 }
1870 axisSum += inputShape.getDimSize(axis);
1871 }
1872
1873 if (axisSum >= 0 && outputShape.hasRank() &&
1874 !outputShape.isDynamicDim(axis) &&
1875 axisSum != outputShape.getDimSize(axis))
1876 return emitOpError("requires sum of axis dimensions of input1 "
1877 "equal to output axis dimension, got ")
1878 << axisSum << " and " << outputShape.getDimSize(axis);
1879 }
1880
1881 return success();
1882}
1883
1884LogicalResult tosa::EqualOp::inferReturnTypeComponents(
1885 MLIRContext *context, ::std::optional<Location> location,
1886 ValueShapeRange operands, DictionaryAttr attributes, PropertyRef properties,
1887 RegionRange regions,
1888 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1889 auto elementType = IntegerType::get(context, /*width=*/1);
1890
1892 if (resolveBroadcastShape(operands, outShape).failed()) {
1893 inferredReturnShapes.push_back(ShapedTypeComponents(elementType));
1894 return success();
1895 }
1896
1897 inferredReturnShapes.push_back(ShapedTypeComponents(outShape, elementType));
1898 return success();
1899}
1900
1901bool tosa::EqualOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
1902 if (l.size() != r.size() || l.size() != 1)
1903 return false;
1904 return succeeded(verifyCompatibleShape(l[0], r[0]));
1905}
1906
1907// MATMUL batch shapes are right aligned and may have different ranks. Missing
1908// leading dimensions are treated as one, while an unranked input remains
1909// unknown because it may contain any number of batch dimensions.
1911 int64_t axis) {
1912 if (!shape.hasRank())
1913 return ShapedType::kDynamic;
1914 const int64_t inputAxis = axis - (outputRank - shape.getRank());
1915 return inputAxis < 0 ? 1 : shape.getDimSize(inputAxis);
1916}
1917
1918static FailureOr<SmallVector<int64_t>>
1920 int64_t outputRank, bool transposeB) {
1921 if (outputRank < 2 ||
1922 (aShape.hasRank() &&
1923 (aShape.getRank() < 2 || aShape.getRank() > outputRank)) ||
1924 (bShape.hasRank() &&
1925 (bShape.getRank() < 2 || bShape.getRank() > outputRank)))
1926 return failure();
1927
1928 SmallVector<int64_t> outputShape(outputRank, ShapedType::kDynamic);
1929 for (int64_t axis = 0; axis < outputRank - 2; ++axis) {
1930 const int64_t aDim = getMatMulBatchDim(aShape, outputRank, axis);
1931 const int64_t bDim = getMatMulBatchDim(bShape, outputRank, axis);
1932 FailureOr<int64_t> resolvedDim = resolveBroadcastDim(aDim, bDim);
1933 if (failed(resolvedDim))
1934 return failure();
1935 outputShape[axis] = *resolvedDim;
1936 }
1937
1938 if (aShape.hasRank())
1939 outputShape[outputRank - 2] = aShape.getDimSize(aShape.getRank() - 2);
1940 if (bShape.hasRank())
1941 outputShape[outputRank - 1] =
1942 bShape.getDimSize(bShape.getRank() - (transposeB ? 2 : 1));
1943 return outputShape;
1944}
1945
1947 const ShapeAdaptor &aShape, const ShapeAdaptor &bShape, bool transposeB,
1948 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1949 if ((aShape.hasRank() && aShape.getRank() < 2) ||
1950 (bShape.hasRank() && bShape.getRank() < 2))
1951 return failure();
1952
1953 // An unranked input can have an arbitrary batch prefix, so the output rank
1954 // cannot yet be inferred.
1955 if (!aShape.hasRank() || !bShape.hasRank()) {
1956 inferredReturnShapes.emplace_back();
1957 return success();
1958 }
1959
1960 const int64_t aChannels = aShape.getDimSize(aShape.getRank() - 1);
1961 const int64_t bChannels =
1962 bShape.getDimSize(bShape.getRank() - (transposeB ? 1 : 2));
1963 if (ShapedType::isStatic(aChannels) && ShapedType::isStatic(bChannels) &&
1964 aChannels != bChannels)
1965 return failure();
1966
1967 const int64_t outputRank = std::max(aShape.getRank(), bShape.getRank());
1968 FailureOr<SmallVector<int64_t>> outputShape =
1969 resolveMatMulOutputShape(aShape, bShape, outputRank, transposeB);
1970 if (failed(outputShape))
1971 return failure();
1972
1973 inferredReturnShapes.emplace_back(*outputShape);
1974 return success();
1975}
1976
1977LogicalResult tosa::MatMulOp::inferReturnTypeComponents(
1978 MLIRContext *context, ::std::optional<Location> location,
1979 MatMulOp::Adaptor adaptor,
1980 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
1981 return inferMatMulReturnTypeComponents(ShapeAdaptor(adaptor.getA().getType()),
1982 ShapeAdaptor(adaptor.getB().getType()),
1983 /*transposeB=*/false,
1984 inferredReturnShapes);
1985}
1986
1987template <typename T>
1988static LogicalResult verifyMatMulQuantizedOperandsType(T op, Type aElementType,
1989 Type bElementType) {
1990 const auto aQuantizedEType =
1991 llvm::dyn_cast<quant::UniformQuantizedType>(aElementType);
1992 const auto bQuantizedEType =
1993 llvm::dyn_cast<quant::UniformQuantizedType>(bElementType);
1994
1995 if (aQuantizedEType || bQuantizedEType) {
1996 if (!aQuantizedEType || !bQuantizedEType) {
1997 return op.emitOpError("expect operands to be both quantized or both not "
1998 "quantized, got ")
1999 << aElementType << " and " << bElementType;
2000 }
2001 // both a and b have quantized element types
2002 auto aQuantWidth = aQuantizedEType.getStorageTypeIntegralWidth();
2003 auto bQuantWidth = bQuantizedEType.getStorageTypeIntegralWidth();
2004 if (aQuantWidth != bQuantWidth) {
2005 return op.emitOpError("expect quantized operands to have same widths, "
2006 "got ")
2007 << aQuantWidth << " and " << bQuantWidth;
2008 }
2009 }
2010
2011 return success();
2012}
2013
2014template <typename T>
2015static LogicalResult verifyMatMulZeroPointType(T op, Value input, Value zp,
2016 StringRef inputName,
2017 StringRef zpName) {
2018 const Type inputElementType = getElementTypeOrSelf(input.getType());
2019 const Type inputStorageElementType = getStorageElementTypeOrSelf(input);
2020 const Type zpElementType = getStorageElementTypeOrSelf(zp);
2021 Type expectedElementType = inputStorageElementType;
2022
2023 if (isa<BlockScaledType>(inputElementType))
2024 expectedElementType = Float32Type::get(op.getContext());
2025
2026 if (expectedElementType == zpElementType)
2027 return success();
2028
2029 InFlightDiagnostic diag = op.emitOpError("expect input ");
2030 diag << inputName << " and " << zpName;
2031 if (isa<BlockScaledType>(inputElementType))
2032 diag << " have compatible element types, got " << inputElementType
2033 << " and " << zpElementType;
2034 else
2035 diag << " have the same element type, got " << inputStorageElementType
2036 << " and " << zpElementType;
2037 return diag;
2038}
2039
2041 SmallVector<int64_t> batchShape;
2042 if (!shape.hasRank() || shape.getRank() < 2)
2043 return batchShape;
2044 batchShape.reserve(shape.getRank() - 2);
2045 for (int64_t i = 0, e = shape.getRank() - 2; i < e; ++i)
2046 batchShape.push_back(shape.getDimSize(i));
2047 return batchShape;
2048}
2049
2050template <typename T>
2051static LogicalResult verifyMatMulShapes(T op, bool transposeB) {
2052 const ShapeAdaptor aShape(op.getA().getType());
2053 const ShapeAdaptor bShape(op.getB().getType());
2054 const auto outputType = cast<ShapedType>(op.getResult().getType());
2055
2056 int64_t channels = aShape.hasRank() ? aShape.getDimSize(aShape.getRank() - 1)
2057 : ShapedType::kDynamic;
2058 if (bShape.hasRank() &&
2059 failed(tryUpdateDimOrFailure(
2060 op, channels,
2061 bShape.getDimSize(bShape.getRank() - (transposeB ? 1 : 2)), "b",
2062 "channels")))
2063 return failure();
2064
2065 const int64_t minimumOutputRank =
2066 std::max(aShape.hasRank() ? aShape.getRank() : 2,
2067 bShape.hasRank() ? bShape.getRank() : 2);
2068 if (outputType.hasRank() && outputType.getRank() < minimumOutputRank)
2069 return op.emitOpError("expected output rank of at least ")
2070 << minimumOutputRank << ", got " << outputType.getRank();
2071
2072 const bool bothInputsRanked = aShape.hasRank() && bShape.hasRank();
2073 if (!bothInputsRanked && !outputType.hasRank())
2074 return success();
2075
2076 // When an input is unranked, use the declared output rank to validate every
2077 // result dimension constrained by the ranked input.
2078 const int64_t expectedOutputRank =
2079 bothInputsRanked ? minimumOutputRank : outputType.getRank();
2080 FailureOr<SmallVector<int64_t>> expectedOutputShape =
2081 resolveMatMulOutputShape(aShape, bShape, expectedOutputRank, transposeB);
2082 if (failed(expectedOutputShape)) {
2083 InFlightDiagnostic diag = op.emitOpError(
2084 "expected batch dimensions of a and b to be broadcast compatible, "
2085 "got a=[");
2087 diag << "] and b=[";
2089 diag << "]";
2090 return diag;
2091 }
2092
2093 if (outputType.hasRank())
2095 op.getOperation(), outputType, *expectedOutputShape);
2096 return success();
2097}
2098
2099LogicalResult MatMulOp::verify() {
2100 const Type aElementType = getElementTypeOrSelf(getA());
2101 const Type bElementType = getElementTypeOrSelf(getB());
2102
2103 if (failed(
2104 verifyMatMulQuantizedOperandsType(*this, aElementType, bElementType)))
2105 return failure();
2106
2107 if (failed(verifyMatMulZeroPointType(*this, getA(), getAZp(), "a", "a_zp")) ||
2108 failed(verifyMatMulZeroPointType(*this, getB(), getBZp(), "b", "b_zp")))
2109 return failure();
2110
2111 FailureOr<int64_t> maybeAZp = getAZeroPoint();
2112 if (succeeded(maybeAZp) && verifyAZeroPoint(*maybeAZp).failed())
2113 return failure();
2114
2115 FailureOr<int64_t> maybeBZp = getBZeroPoint();
2116 if (succeeded(maybeBZp) && verifyBZeroPoint(*maybeBZp).failed())
2117 return failure();
2118
2119 return verifyMatMulShapes(*this, /*transposeB=*/false);
2120}
2121
2122LogicalResult tosa::MatMulTOp::inferReturnTypeComponents(
2123 MLIRContext *context, ::std::optional<Location> location,
2124 MatMulTOp::Adaptor adaptor,
2125 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2126 return inferMatMulReturnTypeComponents(ShapeAdaptor(adaptor.getA().getType()),
2127 ShapeAdaptor(adaptor.getB().getType()),
2128 /*transposeB=*/true,
2129 inferredReturnShapes);
2130}
2131
2132LogicalResult MatMulTOp::verify() {
2133 const Type aElementType = getElementTypeOrSelf(getA());
2134 const Type bElementType = getElementTypeOrSelf(getB());
2135
2136 if (failed(
2137 verifyMatMulQuantizedOperandsType(*this, aElementType, bElementType)))
2138 return failure();
2139
2140 if (failed(verifyMatMulZeroPointType(*this, getA(), getAZp(), "a", "a_zp")) ||
2141 failed(verifyMatMulZeroPointType(*this, getB(), getBZp(), "b", "b_zp")))
2142 return failure();
2143
2144 FailureOr<int64_t> maybeAZp = getAZeroPoint();
2145 if (succeeded(maybeAZp) && verifyAZeroPoint(*maybeAZp).failed())
2146 return failure();
2147
2148 FailureOr<int64_t> maybeBZp = getBZeroPoint();
2149 if (succeeded(maybeBZp) && verifyBZeroPoint(*maybeBZp).failed())
2150 return failure();
2151
2152 return verifyMatMulShapes(*this, /*transposeB=*/true);
2153}
2154
2155LogicalResult tosa::MatmulTBlockScaledOp::inferReturnTypeComponents(
2156 MLIRContext *context, ::std::optional<Location> location,
2157 MatmulTBlockScaledOp::Adaptor adaptor,
2158 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2159 SmallVector<int64_t, 3> outShape(3, ShapedType::kDynamic);
2160
2161 const auto aDataShape = cast<ShapedType>(adaptor.getAData().getType());
2162 if (aDataShape.hasRank()) {
2163 outShape[0] = aDataShape.getDimSize(0);
2164 outShape[1] = aDataShape.getDimSize(1);
2165 }
2166
2167 const auto aScaleShape = cast<ShapedType>(adaptor.getAScale().getType());
2168 if (aScaleShape.hasRank()) {
2169 outShape[0] = ShapedType::isDynamic(outShape[0]) ? aScaleShape.getDimSize(0)
2170 : outShape[0];
2171 outShape[1] = ShapedType::isDynamic(outShape[1]) ? aScaleShape.getDimSize(1)
2172 : outShape[1];
2173 }
2174
2175 // If B batch size is 1, it is broadcast across A's batch size
2176 const auto bDataShape = cast<ShapedType>(adaptor.getBData().getType());
2177 if (bDataShape.hasRank()) {
2178 const int64_t bDataBatchSize = bDataShape.getDimSize(0);
2179 if (bDataBatchSize != 1)
2180 outShape[0] =
2181 ShapedType::isDynamic(outShape[0]) ? bDataBatchSize : outShape[0];
2182 outShape[2] = bDataShape.getDimSize(1);
2183 }
2184
2185 const auto bScaleShape = cast<ShapedType>(adaptor.getBScale().getType());
2186 if (bScaleShape.hasRank()) {
2187 const int64_t bScaleBatchSize = bScaleShape.getDimSize(0);
2188 if (bScaleBatchSize != 1)
2189 outShape[0] =
2190 ShapedType::isDynamic(outShape[0]) ? bScaleBatchSize : outShape[0];
2191 outShape[2] = ShapedType::isDynamic(outShape[2]) ? bScaleShape.getDimSize(1)
2192 : outShape[2];
2193 }
2194
2195 inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
2196 return success();
2197}
2198
2199LogicalResult MatmulTBlockScaledOp::verify() {
2200 // Verify same input data types
2201 const Type aDataType = getAData().getType();
2202 const Type bDataType = getBData().getType();
2203 if (failed(verifySameElementTypes(*this, aDataType, bDataType, "A_data",
2204 "B_data")))
2205 return failure();
2206
2207 // Verify input shape compatibility
2208 int64_t N = ShapedType::kDynamic;
2209 int64_t D = ShapedType::kDynamic;
2210 int64_t H = ShapedType::kDynamic;
2211 int64_t W = ShapedType::kDynamic;
2212 int64_t C = ShapedType::kDynamic;
2213 int64_t multiplesOfC = ShapedType::kDynamic;
2214
2215 const ShapeAdaptor aDataShape = ShapeAdaptor(aDataType);
2216 if (aDataShape.hasRank()) {
2217 N = aDataShape.getDimSize(0);
2218 H = aDataShape.getDimSize(1);
2219 C = aDataShape.getDimSize(2);
2220 }
2221
2222 const ShapeAdaptor aScaleShape = ShapeAdaptor(getAScale().getType());
2223 if (aScaleShape.hasRank()) {
2224 if (failed(tryUpdateDimOrFailure(*this, N, aScaleShape.getDimSize(0),
2225 "a_scale", "batch")) ||
2226 failed(tryUpdateDimOrFailure(*this, H, aScaleShape.getDimSize(1),
2227 "a_scale", "height")))
2228 return failure();
2229 multiplesOfC = aScaleShape.getDimSize(2);
2230 }
2231
2232 const ShapeAdaptor bDataShape = ShapeAdaptor(bDataType);
2233 if (bDataShape.hasRank()) {
2234 if (failed(tryUpdateDimOrFailure(*this, D, bDataShape.getDimSize(0),
2235 "b_data", "batch")) ||
2236 failed(tryUpdateDimOrFailure(*this, C, bDataShape.getDimSize(2),
2237 "b_data", "channels")))
2238 return failure();
2239 W = bDataShape.getDimSize(1);
2240 }
2241
2242 const ShapeAdaptor bScaleShape = ShapeAdaptor(getBScale().getType());
2243 if (bScaleShape.hasRank()) {
2244 if (failed(tryUpdateDimOrFailure(*this, D, bScaleShape.getDimSize(0),
2245 "b_scale", "batch")) ||
2246 failed(tryUpdateDimOrFailure(*this, W, bScaleShape.getDimSize(1),
2247 "b_scale", "width")) ||
2248 failed(tryUpdateDimOrFailure(*this, multiplesOfC,
2249 bScaleShape.getDimSize(2), "b_scale",
2250 "C/block_size")))
2251 return failure();
2252 }
2253
2254 // Verify batch size is broadcast compatible
2255 if (ShapedType::isStatic(N) && ShapedType::isStatic(D) && N != D && D != 1)
2256 return emitOpError("expect B matrix batch size to be broadcast compatible "
2257 "with A, got D=")
2258 << D << " vs N=" << N;
2259
2260 // Verify C is a multiple of block size
2261 const uint32_t blockSize = BlockSizeAttr::getBlockSizeValue(getBlockSize());
2262 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_32))
2263 return emitOpError("expect block size to be 32, got ") << blockSize;
2264 if (ShapedType::isStatic(C) && C % blockSize != 0)
2265 return emitOpError("expect C to be a multiple of block size, got C=")
2266 << C << ", block_size=" << blockSize;
2267
2268 // Verify multiplesOfC is C / block size
2269 if (ShapedType::isStatic(C) && ShapedType::isStatic(multiplesOfC) &&
2270 multiplesOfC != C / blockSize)
2271 return emitOpError(
2272 "expect scale operands dimension 2 to equal C/block_size (")
2273 << C << "/" << blockSize << ")" << ", got " << multiplesOfC;
2274
2275 // Verify output shape
2276 N = ShapedType::isDynamic(N) ? D : N;
2277 const SmallVector<int64_t, 3> expectedOutputShape = {N, H, W};
2278 const auto outputType = cast<ShapedType>(getResult().getType());
2279 if (outputType.hasRank() &&
2280 failed(
2281 verifyCompatibleShape(outputType.getShape(), expectedOutputShape))) {
2282 InFlightDiagnostic opError = emitOpError("expected output shape ");
2283 printShapeToDiagnostic(opError, outputType.getShape());
2284 opError << " to be compatible with expected output shape ";
2285 printShapeToDiagnostic(opError, expectedOutputShape);
2286 return opError;
2287 }
2288
2289 return success();
2290}
2291
2292LogicalResult tosa::PadOp::inferReturnTypeComponents(
2293 MLIRContext *context, ::std::optional<Location> location,
2294 PadOp::Adaptor adaptor,
2295 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2296 ShapeAdaptor inputShape(adaptor.getInput1().getType());
2297 auto paddingRank =
2298 cast<tosa::shapeType>(adaptor.getPadding().getType()).getRank();
2299 SmallVector<int64_t> outputShape;
2300
2301 // If the input rank is unknown, we can infer the output rank using the
2302 // padding shape's rank divided by 2.
2303 if (!inputShape.hasRank()) {
2304 outputShape.resize(paddingRank / 2, ShapedType::kDynamic);
2305 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
2306 return success();
2307 }
2308
2309 SmallVector<int64_t> paddingValues;
2310 // If the paddings value is not a constant, all dimensions must be dynamic.
2311 if (!tosa::getConstShapeValues(adaptor.getPadding().getDefiningOp(),
2312 paddingValues)) {
2313 outputShape.resize(inputShape.getRank(), ShapedType::kDynamic);
2314 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
2315 return success();
2316 }
2317
2318 outputShape.reserve(inputShape.getRank());
2319 for (int i = 0, s = inputShape.getRank(); i < s; i++) {
2320 if (inputShape.isDynamicDim(i)) {
2321 outputShape.push_back(ShapedType::kDynamic);
2322 continue;
2323 }
2324 auto padFront = paddingValues[i * 2];
2325 auto padBack = paddingValues[i * 2 + 1];
2326 outputShape.push_back(inputShape.getDimSize(i) + padFront + padBack);
2327 }
2328
2329 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
2330 return success();
2331}
2332
2333LogicalResult tosa::PadOp::verify() {
2334 if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),
2335 /* outType = */ getOutput().getType())
2336 .failed()) {
2337 return failure();
2338 }
2339
2340 if (auto padConst = getPadConst()) {
2341 if (verifySameElementTypes(*this, /* inType = */ padConst.getType(),
2342 /* outType = */ getOutput().getType())
2343 .failed()) {
2344 return failure();
2345 }
2346 }
2347
2348 RankedTensorType inputType =
2349 llvm::dyn_cast<RankedTensorType>(getInput1().getType());
2350 RankedTensorType outputType =
2351 llvm::dyn_cast<RankedTensorType>(getOutput().getType());
2352 if (!inputType || !outputType)
2353 return success();
2354
2355 if (failed(verifyRanksMatch(getOperation(), inputType, outputType, "input",
2356 "output")))
2357 return failure();
2358
2359 auto inputRank = inputType.getRank();
2360 auto paddingRank = cast<tosa::shapeType>(getPadding().getType()).getRank();
2361 if (paddingRank != inputRank * 2)
2362 return emitOpError() << "padding tensor must have " << inputRank
2363 << " * 2 = " << inputRank * 2 << " elements, but got "
2364 << paddingRank;
2365
2366 return success();
2367}
2368
2369LogicalResult tosa::SliceOp::inferReturnTypeComponents(
2370 MLIRContext *context, ::std::optional<Location> location,
2371 SliceOp::Adaptor adaptor,
2372 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2373
2374 Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());
2377
2378 if (!tosa::getConstShapeValues(adaptor.getStart().getDefiningOp(), start) ||
2379 !tosa::getConstShapeValues(adaptor.getSize().getDefiningOp(), size)) {
2380 auto rank = cast<tosa::shapeType>(adaptor.getSize().getType()).getRank();
2381 SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);
2382 inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));
2383 return success();
2384 }
2385
2386 // if size[i] is -1, all remaining elements in dimension i are included
2387 // in the slice, similar to TF.
2388 ShapeAdaptor inputShape(adaptor.getInput1().getType());
2389 // initialize outputShape to all unknown
2390 SmallVector<int64_t> outputShape(size.size(), ShapedType::kDynamic);
2391 if (inputShape.hasRank()) {
2392 for (size_t i = 0; i < size.size(); i++) {
2393 if (size[i] != 0 && size[i] >= -1 && start[i] >= 0 &&
2394 (ShapedType::isDynamic(inputShape.getDimSize(i)) ||
2395 start[i] < inputShape.getDimSize(i))) {
2396 // size[i] is not 0 and not < -1, and start[i] is in valid range
2397 if (ShapedType::isDynamic(inputShape.getDimSize(i))) {
2398 // input shape has unknown dim[i] - only valid if size[i] > 0
2399 if (size[i] > 0) {
2400 outputShape[i] = size[i];
2401 }
2402 } else {
2403 // input shape has known dim[i]
2404 if (size[i] == -1) {
2405 outputShape[i] = inputShape.getDimSize(i) - start[i];
2406 } else if (start[i] + size[i] <= inputShape.getDimSize(i)) {
2407 // start[i] + size[i] is within bound of input shape's dim[i]
2408 outputShape[i] = size[i];
2409 }
2410 }
2411 }
2412 }
2413 } else {
2414 outputShape = convertToMlirShape(size);
2415 }
2416 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
2417 return success();
2418}
2419
2420LogicalResult tosa::SliceOp::verify() {
2421 const Value input = getInput1();
2422 const Value output = getOutput();
2423 if (verifySameElementTypes(*this, /* inType = */ input.getType(),
2424 /* outType = */ output.getType())
2425 .failed())
2426 return failure();
2427
2428 const Value start = getStart();
2429 const Value size = getSize();
2430 const ShapeAdaptor inputShape(input.getType());
2431 const ShapeAdaptor outputShape(output.getType());
2432
2433 if (inputShape.hasRank()) {
2434 const auto inputRank = inputShape.getRank();
2435 if (outputShape.hasRank() && inputRank != outputShape.getRank())
2436 return emitOpError(
2437 "expect input1 and output to have the same ranks, got ")
2438 << inputRank << " and " << outputShape.getRank();
2439
2440 const auto startShapeRank =
2441 llvm::cast<tosa::shapeType>(start.getType()).getRank();
2442 if (inputRank != startShapeRank)
2443 return emitOpError("length of start is not equal to rank of input shape");
2444
2445 const auto sizeShapeRank =
2446 llvm::cast<tosa::shapeType>(size.getType()).getRank();
2447 if (inputRank != sizeShapeRank)
2448 return emitOpError("length of size is not equal to rank of input shape");
2449 }
2450
2451 return success();
2452}
2453
2454LogicalResult tosa::MulOp::inferReturnTypeComponents(
2455 MLIRContext *context, ::std::optional<Location> location,
2456 ValueShapeRange operands, DictionaryAttr attributes, PropertyRef properties,
2457 RegionRange regions,
2458 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2459 // mul op's output shape only depend on input1 and input2, not on shift
2460 ValueShapeRange twoInputs = operands.drop_back();
2462 if (resolveBroadcastShape(twoInputs, outShape).failed()) {
2463 inferredReturnShapes.push_back(ShapedTypeComponents());
2464 } else {
2465 inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
2466 }
2467 return success();
2468}
2469
2470LogicalResult tosa::MulOp::verify() {
2471 const Value output = getOutput();
2472 auto resElemType = getElementTypeOrSelf(output);
2473
2474 // Verify if the element type among operands and result match tosa
2475 // specification.
2476 if (auto resIntType = dyn_cast<IntegerType>(resElemType)) {
2477 IntegerType lhsIntType =
2478 dyn_cast<IntegerType>(getElementTypeOrSelf(getInput1()));
2479 IntegerType rhsIntType =
2480 dyn_cast<IntegerType>(getElementTypeOrSelf(getInput2()));
2481 if (!lhsIntType || !rhsIntType || lhsIntType != rhsIntType)
2482 return emitOpError("requires the same element type for all operands");
2483
2484 // Though the spec requires the element type of result to be i32, a more
2485 // relaxed way is provided at dialect level for easier cooperating with
2486 // other dialects.
2487 if (lhsIntType.getWidth() > resIntType.getWidth())
2488 return emitOpError("invalid data type size for operands or result");
2489
2490 } else {
2491 // For other supported type, the spec requires requires the same element
2492 // type for all operands (excludes `shift` operand) and results.
2493 for (int i = 0; i < 2; ++i) {
2494 if (getElementTypeOrSelf(getOperand(i)) != resElemType)
2495 return emitOpError(
2496 "requires the same element type for all operands and results");
2497 }
2498 }
2499
2500 // Verify the op has same ranks for all main operands (excludes extra operands
2501 // such as shift of mul op, so this is the only difference with the built-in
2502 // `SameOperandsAndResultRank` trait) and results types, if known.
2503 TypeRange operandTypes = getOperandTypes();
2504 ShapedType aType = cast<ShapedType>(operandTypes[0]);
2505 ShapedType bType = cast<ShapedType>(operandTypes[1]);
2506
2507 const bool aHasRank = aType.hasRank();
2508 const bool bHasRank = bType.hasRank();
2509
2510 bool hasExpectedOutputShape = false;
2511 SmallVector<int64_t> expectedOutputShape;
2512
2513 if (aHasRank && bHasRank) {
2514 const int64_t aRank = aType.getRank();
2515 const int64_t bRank = bType.getRank();
2516 if (aRank != bRank)
2517 return emitOpError("a and b operands don't have matching ranks, got ")
2518 << aRank << " and " << bRank;
2519
2520 // check for broadcast compatible shapes
2522 aType.getShape(), bType.getShape(), expectedOutputShape))
2523 return emitOpError("a and b operands don't have broadcast-compatible "
2524 "shapes, got ")
2525 << aType << " and " << bType;
2526 hasExpectedOutputShape = true;
2527 }
2528
2529 ShapedType resultType = cast<ShapedType>(output.getType());
2530 if (!resultType.hasRank())
2531 return success();
2532
2533 const int64_t resultRank = resultType.getRank();
2534 if (aHasRank && resultRank != aType.getRank())
2535 return emitOpError("result type has different rank than a, got ")
2536 << resultRank << " vs " << aType.getRank();
2537 if (bHasRank && resultRank != bType.getRank())
2538 return emitOpError("result type has different rank than b, got ")
2539 << resultRank << " vs " << bType.getRank();
2540
2541 if (hasExpectedOutputShape &&
2542 failed(verifyOutputShapeCompatibleWithExpected(getOperation(), resultType,
2543 expectedOutputShape)))
2544 return failure();
2545
2546 return success();
2547}
2548
2549LogicalResult tosa::TableOp::inferReturnTypeComponents(
2550 MLIRContext *context, ::std::optional<Location> location,
2551 TableOp::Adaptor adaptor,
2552 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2553 ShapeAdaptor inputShape(adaptor.getInput1().getType());
2554
2555 if (!inputShape.hasRank()) {
2556 inferredReturnShapes.push_back(ShapedTypeComponents());
2557 return success();
2558 }
2559
2560 inferredReturnShapes.resize(1);
2561 inputShape.getDims(inferredReturnShapes[0]);
2562 return success();
2563}
2564
2565LogicalResult tosa::TableOp::verify() {
2566 const TensorType inputType = getInput1().getType();
2567 const TensorType outputType = getOutput().getType();
2568
2569 if (!inputType.hasRank() || !outputType.hasRank())
2570 return success();
2571
2572 if (failed(verifyRanksMatch(getOperation(), inputType, outputType, "input",
2573 "result")))
2574 return failure();
2575
2576 auto inputDims = inputType.getShape();
2577 auto outputDims = outputType.getShape();
2578 for (auto it : llvm::enumerate(llvm::zip(inputDims, outputDims))) {
2579 int64_t dim = it.index();
2580 auto [inputDim, outputDim] = it.value();
2581 if (ShapedType::isStatic(outputDim) && outputDim != inputDim) {
2582 return emitOpError() << "dim(result, " << dim << ") = " << outputDim
2583 << " doesn't match dim(input, " << dim
2584 << ") = " << inputDim;
2585 }
2586 }
2587 return success();
2588}
2589
2590LogicalResult
2591tosa::TileOp::getConstantMultiples(SmallVector<int64_t> &multiples) {
2592 // Multiples must be constants.
2593 DenseIntElementsAttr multiplesAttr;
2594 if (!matchPattern(getMultiples(), m_Constant(&multiplesAttr)))
2595 return failure();
2596 multiples =
2597 llvm::map_to_vector(multiplesAttr.getValues<APInt>(),
2598 [](const APInt &val) { return val.getSExtValue(); });
2599 return success();
2600}
2601
2602LogicalResult tosa::TileOp::inferReturnTypeComponents(
2603 MLIRContext *context, ::std::optional<Location> location,
2604 TileOp::Adaptor adaptor,
2605 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2606 Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());
2607 SmallVector<int64_t> multiples;
2608 if (!tosa::getConstShapeValues(adaptor.getMultiples().getDefiningOp(),
2609 multiples)) {
2610 auto rank =
2611 cast<tosa::shapeType>(adaptor.getMultiples().getType()).getRank();
2612 SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);
2613 inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));
2614 return success();
2615 }
2616 multiples = convertToMlirShape(multiples);
2617
2618 ShapeAdaptor inputShape(adaptor.getInput1().getType());
2619 SmallVector<int64_t> outputShape;
2620 if (!inputShape.hasRank()) {
2621 outputShape.resize(multiples.size(), ShapedType::kDynamic);
2622 inferredReturnShapes.push_back(
2623 ShapedTypeComponents(outputShape, inputType));
2624 return success();
2625 }
2626 if (static_cast<size_t>(inputShape.getRank()) != multiples.size())
2627 return failure();
2628
2629 // Any non dynamic dimension can be multiplied to a known size.
2630 outputShape.reserve(multiples.size());
2631 for (int i = 0, s = inputShape.getRank(); i < s; i++) {
2632 if (multiples[i] == ShapedType::kDynamic) {
2633 outputShape.push_back(ShapedType::kDynamic);
2634 } else {
2635 int64_t dim = inputShape.getDimSize(i);
2636 if (dim != ShapedType::kDynamic)
2637 dim *= multiples[i];
2638 outputShape.push_back(dim);
2639 }
2640 }
2641
2642 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));
2643 return success();
2644}
2645
2646LogicalResult tosa::TileOp::verify() {
2647 if (verifySameElementTypes(*this, /* intype = */ getInput1().getType(),
2648 /* outType = */ getOutput().getType())
2649 .failed()) {
2650 return failure();
2651 }
2652 ShapedType inputType = llvm::cast<ShapedType>(getInput1().getType());
2653 ShapedType outputType = llvm::cast<ShapedType>(getType());
2654
2655 shapeType multiplesType =
2656 llvm::cast<tosa::shapeType>(getMultiples().getType());
2657
2658 auto multiplesRank = multiplesType.getRank();
2659
2660 if (inputType.hasRank()) {
2661 if (inputType.getRank() != multiplesRank)
2662 return emitOpError("expect 'multiples' to have rank ")
2663 << inputType.getRank() << " but got " << multiplesRank << ".";
2664 if (outputType.hasRank() &&
2665 failed(verifyRanksMatch(getOperation(), inputType, outputType, "input",
2666 "output")))
2667 return failure();
2668 } else if (outputType.hasRank() && outputType.getRank() != multiplesRank)
2669 return emitOpError("expect 'multiples' array to have length ")
2670 << outputType.getRank() << " but got " << multiplesRank << ".";
2671
2672 SmallVector<int64_t> multiples;
2673 if (getConstantMultiples(multiples).succeeded() &&
2674 llvm::any_of(multiples, [](int64_t v) { return v <= 0 && v != -1; }))
2675 return emitOpError(
2676 "expect element of 'multiples' to be positive integer or -1.");
2677
2678 return success();
2679}
2680
2681bool tosa::ReshapeOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {
2682 if (l.size() != r.size() || l.size() != 1)
2683 return false;
2684 return getElementTypeOrSelf(l[0]) == getElementTypeOrSelf(r[0]);
2685}
2686
2687LogicalResult tosa::ReshapeOp::inferReturnTypeComponents(
2688 MLIRContext *context, ::std::optional<Location> location,
2689 ReshapeOp::Adaptor adaptor,
2690 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2691 ShapeAdaptor inputShape(adaptor.getInput1().getType());
2692 Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());
2693 llvm::SmallVector<int64_t> newShapeValue;
2694 if (!tosa::getConstShapeValues(adaptor.getShape().getDefiningOp(),
2695 newShapeValue)) {
2696 auto rank = cast<tosa::shapeType>(adaptor.getShape().getType()).getRank();
2697 SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);
2698 inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));
2699 return success();
2700 }
2701 newShapeValue = convertToMlirShape(newShapeValue);
2702
2703 // We cannot infer from the total number of elements so we must take the
2704 // shape attribute as exact.
2705 if (!inputShape.hasRank() || !inputShape.hasStaticShape()) {
2706 inferredReturnShapes.push_back(
2707 ShapedTypeComponents(newShapeValue, inputType));
2708 return success();
2709 }
2710
2711 // Determine the number of elements covered by the slice of all static
2712 // dimensions. This allows us to infer the length of the remaining dynamic
2713 // dimension.
2714 int64_t numElements = inputShape.getNumElements();
2715 int64_t staticMul = 1;
2716 for (auto val : newShapeValue) {
2717 if (ShapedType::isStatic(val)) {
2718 staticMul *= val;
2719 }
2720 }
2721
2722 // Determine the length of the dynamic dimension.
2723 for (auto &val : newShapeValue) {
2724 if (ShapedType::isDynamic(val))
2725 val = numElements / staticMul;
2726 }
2727
2728 inferredReturnShapes.push_back(
2729 ShapedTypeComponents(newShapeValue, inputType));
2730 return success();
2731}
2732
2733llvm::LogicalResult tosa::ReshapeOp::verify() {
2734 if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),
2735 /* outType = */ getOutput().getType())
2736 .failed()) {
2737 return failure();
2738 }
2739 TensorType inputType = getInput1().getType();
2740
2741 SmallVector<int64_t> shapeValues;
2742 if (!tosa::getConstShapeValues(getShape().getDefiningOp(), shapeValues)) {
2743 // skip following checks if shape is not constant
2744 return mlir::success();
2745 }
2746
2747 int missingDims = llvm::count(shapeValues, kInferableDimSize);
2748 if (missingDims > 1)
2749 return emitOpError() << "expected at most one target dimension to be "
2751
2752 const auto outputType = dyn_cast<RankedTensorType>(getType());
2753 if (!outputType)
2754 return success();
2755
2756 if ((int64_t)shapeValues.size() != outputType.getRank())
2757 return emitOpError() << "new shape does not match result rank";
2758
2759 for (auto [newShapeDim, outputShapeDim] :
2760 zip(shapeValues, outputType.getShape())) {
2761 if (newShapeDim != kInferableDimSize &&
2762 newShapeDim != ShapedType::kDynamic &&
2763 outputShapeDim != ShapedType::kDynamic && newShapeDim != outputShapeDim)
2764 return emitOpError() << "new shape is inconsistent with result shape";
2765
2766 if (newShapeDim != ShapedType::kDynamic && newShapeDim < kInferableDimSize)
2767 return emitOpError() << "new shape has invalid tensor dimension size "
2768 << newShapeDim;
2769 }
2770
2771 if (inputType.hasStaticShape()) {
2772 int64_t inputElementsNum = inputType.getNumElements();
2773 if (outputType.hasStaticShape()) {
2774 int64_t outputElementsNum = outputType.getNumElements();
2775 if (inputElementsNum != outputElementsNum) {
2776 return emitOpError() << "cannot reshape " << inputElementsNum
2777 << " elements into " << outputElementsNum;
2778 }
2779 }
2780
2781 int64_t newShapeElementsNum =
2782 llvm::accumulate(shapeValues, int64_t(1), [](int64_t acc, int64_t dim) {
2783 return (dim > 0) ? acc * dim : acc;
2784 });
2785 bool isStaticNewShape =
2786 llvm::all_of(shapeValues, [](int64_t s) { return s > 0; });
2787 if ((isStaticNewShape && inputElementsNum != newShapeElementsNum) ||
2788 (!isStaticNewShape && newShapeElementsNum > inputElementsNum)) {
2789 return emitOpError() << "cannot reshape " << inputElementsNum
2790 << " elements into " << newShapeElementsNum;
2791 }
2792 }
2793
2794 return mlir::success();
2795}
2796
2797bool tosa::ReshapeBlockScaledOp::isCompatibleReturnTypes(TypeRange l,
2798 TypeRange r) {
2799 if (l.size() != r.size() || l.size() < 1 || l.size() > 2)
2800 return false;
2801 bool ok = (getElementTypeOrSelf(l[0]) == getElementTypeOrSelf(r[0]));
2802 if (l.size() == 2)
2803 ok = ok && (getElementTypeOrSelf(l[1]) == getElementTypeOrSelf(r[1]));
2804 return ok;
2805}
2806
2807LogicalResult tosa::ReshapeBlockScaledOp::inferReturnTypeComponents(
2808 MLIRContext *context, ::std::optional<Location> location,
2809 ReshapeBlockScaledOp::Adaptor adaptor,
2810 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
2811
2812 const auto numInputs = adaptor.getInput().size();
2813 ShapeAdaptor inputShape(adaptor.getInput()[0].getType());
2814 Type inputType = getElementTypeOrSelf(adaptor.getInput()[0].getType());
2815 llvm::SmallVector<int64_t> newShapeValue;
2816 const auto newShape = adaptor.getNewValueShape();
2817 if (!tosa::getConstShapeValues(newShape.getDefiningOp(), newShapeValue)) {
2818 auto rank = cast<tosa::shapeType>(newShape.getType()).getRank();
2819 SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);
2820 inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));
2821 if (numInputs == 2)
2822 inferredReturnShapes.push_back(ShapedTypeComponents(
2823 fallback, getElementTypeOrSelf(adaptor.getInput()[1].getType())));
2824 return success();
2825 }
2826
2827 const uint32_t blockSize =
2828 BlockSizeAttr::getBlockSizeValue(adaptor.getBlockSize());
2829
2830 llvm::SmallVector<int64_t> newScaleShapeValue;
2831 if (numInputs == 2) {
2832 newScaleShapeValue.assign(newShapeValue.begin(), newShapeValue.end());
2833 if (!newScaleShapeValue.empty() &&
2834 ShapedType::isStatic(newScaleShapeValue.back()))
2835 newScaleShapeValue.back() /= blockSize;
2836 }
2837
2838 inferredReturnShapes.push_back(
2839 ShapedTypeComponents(newShapeValue, inputType));
2840 if (numInputs == 2) {
2841 // Fix up scale shape - with special case for last dimension
2842 for (size_t idx = 0; idx < newShapeValue.size(); idx++) {
2843 if (ShapedType::isDynamic(newScaleShapeValue[idx])) {
2844 newScaleShapeValue[idx] = newShapeValue[idx];
2845 if (idx + 1 == newShapeValue.size())
2846 newScaleShapeValue[idx] /= blockSize;
2847 }
2848 }
2849
2850 inferredReturnShapes.push_back(ShapedTypeComponents(
2851 newScaleShapeValue,
2852 getElementTypeOrSelf(adaptor.getInput()[1].getType())));
2853 }
2854 return success();
2855}
2856
2857llvm::LogicalResult tosa::ReshapeBlockScaledOp::verify() {
2858 const Operation::operand_range inputList = getInput();
2859 const Operation::result_range outputList = getResults();
2860
2861 if (inputList.size() == 0)
2862 return emitOpError("requires at least one input");
2863
2864 if (inputList.size() > 2)
2865 return emitOpError("requires at most two inputs");
2866
2867 if (inputList.size() != outputList.size())
2868 return emitOpError("requires number of results to match inputs");
2869
2870 if (verifySameElementTypes(*this, /* inType = */ inputList[0].getType(),
2871 /* outType = */ outputList[0].getType())
2872 .failed()) {
2873 return failure();
2874 }
2875
2876 if (inputList.size() == 2 &&
2877 cast<tosa::shapeType>(getNewValueShape().getType()).getRank() == 0)
2878 return emitOpError("requires new shape to have a rank greater than 0");
2879
2880 const auto inputType = llvm::cast<ShapedType>(inputList[0].getType());
2881 if (!inputType.hasRank())
2882 return success();
2883 const uint32_t blockSize = BlockSizeAttr::getBlockSizeValue(getBlockSize());
2884
2885 if (inputList.size() == 2) {
2886 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_32))
2887 return emitOpError("expect block size to be 32, got ") << blockSize;
2888 if (llvm::any_of(inputList, [](Value v) {
2889 const auto input = cast<ShapedType>(v.getType());
2890 return input.hasRank() && input.getRank() == 0;
2891 }))
2892 return emitOpError(
2893 "requires all input shapes have a rank greater than 0");
2894 if (llvm::any_of(outputList, [](Value v) {
2895 const auto output = cast<ShapedType>(v.getType());
2896 return output.hasRank() && output.getRank() == 0;
2897 }))
2898 return emitOpError(
2899 "requires all result shapes have a rank greater than 0");
2900
2901 if (verifySameElementTypes(*this, /* inType = */ inputList[1].getType(),
2902 /* outType = */ outputList[1].getType())
2903 .failed()) {
2904 return failure();
2905 }
2906
2907 const auto inputScaleType = llvm::cast<ShapedType>(inputList[1].getType());
2908 if (inputScaleType.hasRank()) {
2909 if (inputType.getRank() != inputScaleType.getRank())
2910 return emitOpError("input shapes do not have same rank");
2911
2912 // Check all but the last dimension that the input shape dimensions match
2913 for (auto dimIdx = 0; dimIdx < inputType.getRank() - 1; dimIdx++) {
2914 const int64_t inputValueDim = inputType.getDimSize(dimIdx);
2915 const int64_t inputScaleDim = inputScaleType.getShape()[dimIdx];
2916 if (ShapedType::isStatic(inputValueDim) &&
2917 ShapedType::isStatic(inputScaleDim) &&
2918 inputValueDim != inputScaleDim)
2919 return emitOpError("input shapes for data and scale do not match on "
2920 "dimension ")
2921 << dimIdx;
2922 }
2923
2924 // Verify last dimension of input is a multiple of block size
2925 const int64_t lastValueDim =
2926 inputType.getDimSize(inputType.getRank() - 1);
2927 if (ShapedType::isStatic(lastValueDim)) {
2928 if (lastValueDim % blockSize != 0)
2929 return emitOpError("expect last dimension of input_data (")
2930 << lastValueDim << ") to be divisible by block_size ("
2931 << blockSize << ")";
2932
2933 const int64_t lastScaleDim =
2934 inputScaleType.getDimSize(inputScaleType.getRank() - 1);
2935 // Verify last dimension of scale is lastValueDim / block size
2936 if (ShapedType::isStatic(lastScaleDim) &&
2937 lastScaleDim != lastValueDim / blockSize)
2938 return emitOpError("expect last dimension of scale_data (")
2939 << lastScaleDim << ") to be " << lastValueDim << "/"
2940 << blockSize;
2941 }
2942 }
2943 } else {
2944 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_1))
2945 return emitOpError("expect block size to be 1, got ") << blockSize;
2946 }
2947
2948 // Get the new value shape dimension values.
2949 SmallVector<int64_t> shapeValues;
2950 if (!tosa::getConstShapeValues(getNewValueShape().getDefiningOp(),
2951 shapeValues)) {
2952 // skip following checks if shape is not constant
2953 return mlir::success();
2954 }
2955
2956 if (inputList.size() == 2) {
2957 const int64_t lastShapeDim = shapeValues.back();
2958 if (ShapedType::isStatic(lastShapeDim) && lastShapeDim % blockSize != 0)
2959 return emitOpError("expect last dimension of new shape (")
2960 << lastShapeDim << ") to be divisible by block_size (" << blockSize
2961 << ")";
2962 }
2963
2964 const auto outputType = llvm::cast<ShapedType>(outputList[0].getType());
2965 if (!outputType.hasRank())
2966 return success();
2967
2968 if (static_cast<int64_t>(shapeValues.size()) != outputType.getRank())
2969 return emitOpError() << "result does not match new shape rank";
2970
2971 for (auto [newShapeDim, outputShapeDim] :
2972 zip(shapeValues, outputType.getShape())) {
2973 if (ShapedType::isStatic(newShapeDim) &&
2974 ShapedType::isStatic(outputShapeDim) && newShapeDim != outputShapeDim)
2975 return emitOpError() << "result shape is inconsistent with new shape";
2976 }
2977
2978 if (outputList.size() == 2) {
2979 // Set up scale shape from new shape given
2980 SmallVector<int64_t> scaleShapeValues(shapeValues.begin(),
2981 shapeValues.end());
2982 scaleShapeValues.back() /= blockSize;
2983
2984 const auto outputScaleType =
2985 llvm::cast<ShapedType>(outputList[1].getType());
2986 if (outputScaleType.hasRank()) {
2987 if ((int64_t)scaleShapeValues.size() != outputScaleType.getRank())
2988 return emitOpError() << "result scale does not match new shape rank";
2989
2990 for (auto [newScaleShapeDim, outputScaleShapeDim] :
2991 zip(scaleShapeValues, outputScaleType.getShape())) {
2992 if (ShapedType::isStatic(newScaleShapeDim) &&
2993 ShapedType::isStatic(outputScaleShapeDim) &&
2994 newScaleShapeDim != outputScaleShapeDim)
2995 return emitOpError()
2996 << "result scale shape is inconsistent with new shape";
2997 }
2998 }
2999 }
3000
3001 if (inputType.hasStaticShape()) {
3002 int64_t inputElementsNum = inputType.getNumElements();
3003 if (outputType.hasStaticShape()) {
3004 int64_t outputElementsNum = outputType.getNumElements();
3005 if (inputElementsNum != outputElementsNum) {
3006 return emitOpError() << "cannot reshape " << inputElementsNum
3007 << " elements into " << outputElementsNum;
3008 }
3009 }
3010
3011 int64_t newShapeElementsNum =
3012 llvm::accumulate(shapeValues, int64_t(1), [](int64_t acc, int64_t dim) {
3013 return (dim > 0) ? acc * dim : acc;
3014 });
3015 bool isStaticNewShape =
3016 llvm::all_of(shapeValues, [](int64_t s) { return s > 0; });
3017 if ((isStaticNewShape && inputElementsNum != newShapeElementsNum) ||
3018 (!isStaticNewShape && newShapeElementsNum > inputElementsNum)) {
3019 return emitOpError() << "cannot reshape " << inputElementsNum
3020 << " elements into " << newShapeElementsNum;
3021 }
3022 }
3023
3024 return mlir::success();
3025}
3026
3027// return failure if val is not a constant
3028// set zp to -1 if val is non-zero float or val is not integer nor float
3029// otherwise set zp to val's constant value
3030static FailureOr<int64_t> getZeroPoint(Value val, bool signExtend) {
3031 ElementsAttr zpAttr;
3032 if (!matchPattern(val, m_Constant(&zpAttr))) {
3033 return failure();
3034 }
3035
3036 Type zpElemType = zpAttr.getElementType();
3037
3038 if (llvm::isa<FloatType>(zpElemType)) {
3039 if (zpAttr.getValues<APFloat>()[0].isZero()) {
3040 return 0;
3041 }
3042 // return non-zero value to trigger error check
3043 return -1;
3044 }
3045
3046 if (llvm::isa<IntegerType>(zpElemType)) {
3047 if (signExtend)
3048 return zpAttr.getValues<APInt>()[0].getSExtValue();
3049 return zpAttr.getValues<APInt>()[0].getZExtValue();
3050 }
3051
3052 // return non-zero value to trigger error check
3053 return -1;
3054}
3055
3056template <typename T>
3057static LogicalResult verifyZeroPoint(T op, Value val, const int64_t &zp,
3058 const std::string &operand) {
3059 Type zpElemType = getElementTypeOrSelf(val);
3060
3061 if (!zpElemType.isInteger(8) && zp != 0) {
3062 // convert operand to lower case for error message
3063 std::string lower = operand;
3064 llvm::transform(lower, lower.begin(), ::tolower);
3065 return op.emitOpError()
3066 << lower << " zero point must be zero for non-int8 integer types";
3067 }
3068
3069 return success();
3070}
3071
3072static LogicalResult verifyZeroPoint(tosa::RescaleOp op, Value zpVal,
3073 const int64_t &zp,
3074 const std::string &operand) {
3075 bool isInputZp = (operand == "Input");
3076
3077 bool tensorUnsigned =
3078 isInputZp ? op.getInputUnsigned() : op.getOutputUnsigned();
3079 StringRef tensorName = isInputZp ? "input" : "output";
3080
3081 Type zpElemType = getElementTypeOrSelf(zpVal);
3082
3083 if (zp != 0) {
3084 if (!zpElemType.isInteger(8) &&
3085 !(zpElemType.isInteger(16) && tensorUnsigned)) {
3086 return op.emitOpError()
3087 << "expect " << tensorName << "_zp of 0, got " << zp;
3088 }
3089 if (zpElemType.isInteger(16) && tensorUnsigned && zp != 32768) {
3090 return op.emitOpError() << "expect " << tensorName
3091 << "_zp of 0 or 32768 for unsigned int16 "
3092 << tensorName << ", got " << zp;
3093 }
3094 }
3095
3096 return success();
3097}
3098
3099#define ZERO_POINT_HELPER(OP, OPERAND_NAME, SIGN_EXTEND) \
3100 FailureOr<int64_t> tosa::OP::get##OPERAND_NAME##ZeroPoint() { \
3101 return getZeroPoint(get##OPERAND_NAME##Zp(), SIGN_EXTEND); \
3102 } \
3103 LogicalResult tosa::OP::verify##OPERAND_NAME##ZeroPoint(int64_t zp) { \
3104 return verifyZeroPoint(*this, get##OPERAND_NAME##Zp(), zp, #OPERAND_NAME); \
3105 }
3106
3107ZERO_POINT_HELPER(Conv2DOp, Input, true)
3108ZERO_POINT_HELPER(Conv2DOp, Weight, true)
3109ZERO_POINT_HELPER(Conv3DOp, Input, true)
3110ZERO_POINT_HELPER(Conv3DOp, Weight, true)
3111ZERO_POINT_HELPER(DepthwiseConv2DOp, Input, true)
3112ZERO_POINT_HELPER(DepthwiseConv2DOp, Weight, true)
3113ZERO_POINT_HELPER(TransposeConv2DOp, Input, true)
3114ZERO_POINT_HELPER(TransposeConv2DOp, Weight, true)
3115ZERO_POINT_HELPER(AvgPool2dOp, Input, true)
3116ZERO_POINT_HELPER(AvgPool2dOp, Output, true)
3117ZERO_POINT_HELPER(AvgPool2dAdaptiveOp, Input, true)
3118ZERO_POINT_HELPER(AvgPool2dAdaptiveOp, Output, true)
3119ZERO_POINT_HELPER(MatMulOp, A, true)
3120ZERO_POINT_HELPER(MatMulOp, B, true)
3121ZERO_POINT_HELPER(MatMulTOp, A, true)
3122ZERO_POINT_HELPER(MatMulTOp, B, true)
3123ZERO_POINT_HELPER(NegateOp, Input1, true)
3124ZERO_POINT_HELPER(NegateOp, Output, true)
3125ZERO_POINT_HELPER(RescaleOp, Input, !getInputUnsigned())
3126ZERO_POINT_HELPER(RescaleOp, Output, !getOutputUnsigned())
3127#undef ZERO_POINT_HELPER
3128
3129LogicalResult tosa::TransposeOp::inferReturnTypeComponents(
3130 MLIRContext *context, ::std::optional<Location> location,
3131 TransposeOp::Adaptor adaptor,
3132 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3133 ShapeAdaptor inputShape(adaptor.getInput1().getType());
3134
3135 // If input rank and permutation length is unknown, the output rank is
3136 // unknown.
3137 if (!inputShape.hasRank()) {
3138 inferredReturnShapes.push_back(ShapedTypeComponents());
3139 return success();
3140 }
3141
3142 const auto inputRank = inputShape.getRank();
3143
3144 // This would imply the number of permutations does not match the rank of
3145 // the input which is illegal.
3146 if (adaptor.getPerms().size() != static_cast<size_t>(inputRank)) {
3147 return failure();
3148 }
3149
3150 SmallVector<int64_t> outputShape;
3151 // Rank-0 means no permutations matter.
3152 if (inputRank == 0) {
3153 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3154 return success();
3155 }
3156
3157 // Check whether the input dimensions are all the same.
3158 bool allTheSame = true;
3159 for (int i = 1, s = inputRank; i < s; i++) {
3160 if (inputShape.getDimSize(0) != inputShape.getDimSize(i)) {
3161 allTheSame = false;
3162 break;
3163 }
3164 }
3165
3166 // If all of the input dimensions are the same we don't care about the
3167 // permutation.
3168 if (allTheSame) {
3169 outputShape.resize(inputRank, inputShape.getDimSize(0));
3170 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3171 return success();
3172 }
3173
3174 outputShape.resize(inputRank, ShapedType::kDynamic);
3175
3176 // Constant permutation values must be within the input rank.
3177 if (llvm::any_of(adaptor.getPerms(),
3178 [inputRank](const auto i) { return i >= inputRank; }))
3179 return failure();
3180
3181 outputShape.reserve(inputRank);
3182 for (int i = 0, s = inputRank; i < s; i++) {
3183 outputShape[i] = inputShape.getDimSize(adaptor.getPerms()[i]);
3184 }
3185
3186 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3187 return success();
3188}
3189
3190LogicalResult tosa::TransposeOp::verify() {
3191 if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),
3192 /* outType = */ getOutput().getType())
3193 .failed()) {
3194 return failure();
3195 }
3196
3197 const ShapeAdaptor inputShape(getInput1().getType());
3198 const ShapeAdaptor outputShape(getOutput().getType());
3199
3200 const llvm::ArrayRef<int32_t> constantPerms = getPerms();
3201
3202 if (inputShape.hasRank() &&
3203 constantPerms.size() != static_cast<size_t>(inputShape.getRank()))
3204 return emitOpError() << "expected perms attribute to have size "
3205 << inputShape.getRank()
3206 << " (input rank) but got size "
3207 << constantPerms.size();
3208
3209 if (inputShape.hasRank() && outputShape.hasRank() &&
3210 inputShape.getRank() != outputShape.getRank())
3211 return emitOpError()
3212 << "expected input tensor rank to equal result tensor rank";
3213
3214 if (outputShape.hasRank() &&
3215 constantPerms.size() != static_cast<size_t>(outputShape.getRank()))
3216 return emitOpError() << "expected perms attribute to have size "
3217 << outputShape.getRank()
3218 << " (output rank) but got size "
3219 << constantPerms.size();
3220
3221 if (!llvm::all_of(constantPerms,
3222 [&constantPerms](int32_t s) {
3223 return s >= 0 &&
3224 static_cast<size_t>(s) < constantPerms.size();
3225 }) ||
3226 !isPermutationVector(llvm::map_to_vector(
3227 constantPerms, [](int32_t v) -> int64_t { return v; })))
3228 return emitOpError() << "expected valid permutation indices";
3229
3230 if (isa<BlockScaledType>(getInput1().getType().getElementType()) &&
3231 constantPerms.back() != static_cast<int32_t>(constantPerms.size()) - 1) {
3232 return emitOpError() << "expected no-op permutation on innermost dimension "
3233 "for block scaled input";
3234 }
3235
3236 // ERROR_IF(tensor_size(shape1) != tensor_size(shape))
3237 if (inputShape.hasStaticShape() && outputShape.hasStaticShape() &&
3238 inputShape.getNumElements() != outputShape.getNumElements())
3239 return emitOpError() << "expected input1 and output to have same numbers "
3240 "of elements, got "
3241 << inputShape.getNumElements() << " and "
3242 << outputShape.getNumElements();
3243
3244 // Verify that the types of the input and output tensors are properly
3245 // permuted.
3246 if (inputShape.hasRank() && outputShape.hasRank()) {
3247 for (auto i = 0; i < outputShape.getRank(); i++) {
3248 if (inputShape.isDynamicDim(constantPerms[i]) ||
3249 outputShape.isDynamicDim(i))
3250 continue;
3251
3252 if (inputShape.getDimSize(constantPerms[i]) != outputShape.getDimSize(i))
3253 return emitOpError()
3254 << "expected output tensor dim " << i << " to match "
3255 << "input dim " << constantPerms[i] << " with value of "
3256 << inputShape.getDimSize(constantPerms[i]);
3257 }
3258 }
3259
3260 return success();
3261}
3262
3263LogicalResult TransposeOp::reifyResultShapes(
3264 OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3265
3266 const llvm::ArrayRef<int32_t> transposePerms = getPerms();
3267
3268 Value input = getInput1();
3269 auto inputType = cast<TensorType>(input.getType());
3270
3271 SmallVector<OpFoldResult> returnedDims(inputType.getRank());
3272 for (auto dim : transposePerms) {
3273 int32_t dimInInput = transposePerms[dim];
3274 if (inputType.isDynamicDim(dimInInput))
3275 returnedDims[dim] =
3276 tensor::DimOp::create(builder, getLoc(), input, dimInInput)
3277 .getResult();
3278 else
3279 returnedDims[dim] =
3280 builder.getIndexAttr(inputType.getDimSize(dimInInput));
3281 }
3282
3283 reifiedReturnShapes.emplace_back(std::move(returnedDims));
3284 return success();
3285}
3286
3287LogicalResult tosa::GatherOp::inferReturnTypeComponents(
3288 MLIRContext *context, ::std::optional<Location> location,
3289 GatherOp::Adaptor adaptor,
3290 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3291 llvm::SmallVector<int64_t> outputShape;
3292 outputShape.resize(3, ShapedType::kDynamic);
3293
3294 ShapeAdaptor valuesShape(adaptor.getValues().getType());
3295 if (valuesShape.hasRank()) {
3296 outputShape[0] = valuesShape.getDimSize(0);
3297 outputShape[2] = valuesShape.getDimSize(2);
3298 }
3299
3300 ShapeAdaptor indicesShape(adaptor.getIndices().getType());
3301 if (indicesShape.hasRank()) {
3302 if (outputShape[0] == ShapedType::kDynamic)
3303 outputShape[0] = indicesShape.getDimSize(0);
3304 if (outputShape[1] == ShapedType::kDynamic)
3305 outputShape[1] = indicesShape.getDimSize(1);
3306 }
3307
3308 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3309 return success();
3310}
3311
3312LogicalResult tosa::RowGatherOp::inferReturnTypeComponents(
3313 MLIRContext *context, ::std::optional<Location> location,
3314 RowGatherOp::Adaptor adaptor,
3315 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3316 llvm::SmallVector<int64_t> outputShape;
3317 outputShape.resize(3, ShapedType::kDynamic);
3318
3319 const ShapeAdaptor valuesShape(adaptor.getValues().getType());
3320 if (valuesShape.hasRank()) {
3321 outputShape[0] = valuesShape.getDimSize(0);
3322 outputShape[2] = valuesShape.getDimSize(2);
3323 }
3324
3325 const ShapeAdaptor indicesShape(adaptor.getIndices().getType());
3326 if (indicesShape.hasRank()) {
3327 if (outputShape[0] == ShapedType::kDynamic)
3328 outputShape[0] = indicesShape.getDimSize(0);
3329
3330 const FailureOr<int32_t> maybeRowCount =
3331 getConstantScalarIntValue<int32_t>(adaptor.getRowCount());
3332 if (succeeded(maybeRowCount)) {
3333 const int64_t indicesW = indicesShape.getDimSize(1);
3334 if (ShapedType::isStatic(indicesW))
3335 outputShape[1] = indicesW * maybeRowCount.value();
3336 }
3337 }
3338
3339 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3340 return success();
3341}
3342
3343LogicalResult tosa::RowGatherBlockScaledOp::inferReturnTypeComponents(
3344 MLIRContext *context, ::std::optional<Location> location,
3345 RowGatherBlockScaledOp::Adaptor adaptor,
3346 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3347 const auto values = adaptor.getValues();
3348 if (values.empty())
3349 return failure();
3350
3351 SmallVector<int64_t> dataShape(3, ShapedType::kDynamic);
3352 const ShapeAdaptor valuesShape(values.front().getType());
3353 if (valuesShape.hasRank()) {
3354 dataShape[0] = valuesShape.getDimSize(0);
3355 dataShape[2] = valuesShape.getDimSize(2);
3356 }
3357
3358 const ShapeAdaptor indicesShape(adaptor.getIndices().getType());
3359 if (indicesShape.hasRank()) {
3360 if (dataShape[0] == ShapedType::kDynamic)
3361 dataShape[0] = indicesShape.getDimSize(0);
3362
3363 if (auto rowCount =
3364 getConstantScalarIntValue<int32_t>(adaptor.getRowCount());
3365 succeeded(rowCount) && rowCount.value() > 0) {
3366 const int64_t indicesW = indicesShape.getDimSize(1);
3367 if (ShapedType::isStatic(indicesW))
3368 dataShape[1] = indicesW * rowCount.value();
3369 }
3370 }
3371
3372 inferredReturnShapes.push_back(ShapedTypeComponents(dataShape));
3373 if (values.size() == 1)
3374 return success();
3375
3376 SmallVector<int64_t> scaleShape = dataShape;
3377 const uint32_t blockSize =
3378 BlockSizeAttr::getBlockSizeValue(adaptor.getBlockSize());
3379 if (ShapedType::isStatic(dataShape[2]))
3380 scaleShape[2] = dataShape[2] / blockSize;
3381
3382 inferredReturnShapes.push_back(ShapedTypeComponents(scaleShape));
3383 return success();
3384}
3385
3386LogicalResult tosa::GatherOp::verify() {
3387 if (verifySameElementTypes(*this, /* inType = */ getValues().getType(),
3388 /* outType = */ getOutput().getType())
3389 .failed()) {
3390 return failure();
3391 }
3392
3393 const ShapeAdaptor valuesShape(getValues().getType());
3394 const ShapeAdaptor indicesShape(getIndices().getType());
3395 const ShapeAdaptor outputShape(getOutput().getType());
3396
3397 int64_t n = ShapedType::kDynamic;
3398 int64_t w = ShapedType::kDynamic;
3399 int64_t c = ShapedType::kDynamic;
3400
3401 if (valuesShape.hasRank()) {
3402 n = valuesShape.getDimSize(0);
3403 c = valuesShape.getDimSize(2);
3404 }
3405 if (indicesShape.hasRank()) {
3406 const int64_t indicesN = indicesShape.getDimSize(0);
3407 w = indicesShape.getDimSize(1);
3408 if (n == ShapedType::kDynamic)
3409 n = indicesN;
3410 else if (indicesN != ShapedType::kDynamic && n != indicesN)
3411 return emitOpError() << "requires indices dimension 0 to have size " << n
3412 << ", got " << indicesN;
3413 }
3414 if (outputShape.hasRank()) {
3415 const int64_t outputN = outputShape.getDimSize(0);
3416 const int64_t outputW = outputShape.getDimSize(1);
3417 const int64_t outputC = outputShape.getDimSize(2);
3418 if (n != ShapedType::kDynamic && outputN != ShapedType::kDynamic &&
3419 n != outputN)
3420 return emitOpError() << "requires output dimension 0 to have size " << n
3421 << ", got " << outputN;
3422
3423 if (w != ShapedType::kDynamic && outputW != ShapedType::kDynamic &&
3424 w != outputW)
3425 return emitOpError() << "requires output dimension 1 to have size " << w
3426 << ", got " << outputW;
3427 if (c != ShapedType::kDynamic && outputC != ShapedType::kDynamic &&
3428 c != outputC)
3429 return emitOpError() << "requires output dimension 2 to have size " << c
3430 << ", got " << outputC;
3431 }
3432 return success();
3433}
3434
3435LogicalResult tosa::RowGatherOp::verify() {
3436 if (failed(verifySameElementTypes(*this, /* inType = */ getValues().getType(),
3437 /* outType = */ getOutput().getType())))
3438 return failure();
3439
3440 const FailureOr<int32_t> maybeRowCount =
3442 if (succeeded(maybeRowCount) && maybeRowCount.value() <= 0)
3443 return emitOpError() << "requires row_count to be > 0, got "
3444 << maybeRowCount.value();
3445
3446 int64_t n = ShapedType::kDynamic;
3447 int64_t c = ShapedType::kDynamic;
3448 int64_t w = ShapedType::kDynamic;
3449
3450 const ShapeAdaptor valuesShape(getValues().getType());
3451 if (valuesShape.hasRank()) {
3452 n = valuesShape.getDimSize(0);
3453 c = valuesShape.getDimSize(2);
3454 }
3455
3456 const ShapeAdaptor indicesShape(getIndices().getType());
3457 if (indicesShape.hasRank()) {
3458 if (failed(tryUpdateDimOrFailure(*this, n, indicesShape.getDimSize(0),
3459 "indices", "batch")))
3460 return failure();
3461 w = indicesShape.getDimSize(1);
3462 }
3463
3464 const ShapeAdaptor outputShape(getOutput().getType());
3465 if (outputShape.hasRank()) {
3466 if (failed(tryUpdateDimOrFailure(*this, n, outputShape.getDimSize(0),
3467 "output", "batch")) ||
3468 failed(tryUpdateDimOrFailure(*this, c, outputShape.getDimSize(2),
3469 "output", "channels")))
3470 return failure();
3471
3472 if (succeeded(maybeRowCount) && maybeRowCount.value() > 0 &&
3473 ShapedType::isStatic(w)) {
3474 const int64_t expectedOutputRows = w * maybeRowCount.value();
3475 if (ShapedType::isStatic(outputShape.getDimSize(1)) &&
3476 outputShape.getDimSize(1) != expectedOutputRows)
3477 return emitOpError()
3478 << "requires output dimension to be equal to "
3479 "indices[1]*row_count ("
3480 << expectedOutputRows << "), got " << outputShape.getDimSize(1);
3481 }
3482 }
3483
3484 return success();
3485}
3486
3487LogicalResult tosa::RowGatherBlockScaledOp::verify() {
3488 const OperandRange values = getValues();
3489 const ResultRange output = getOutput();
3490 if (values.empty() || values.size() > 2)
3491 return emitOpError()
3492 << "expects values tensor list length to be 1 or 2, got "
3493 << values.size();
3494 if (output.size() != values.size())
3495 return emitOpError()
3496 << "expects output tensor list length to match values tensor list "
3497 "length, got "
3498 << output.size() << " results for " << values.size()
3499 << " input tensors";
3500
3501 const uint32_t blockSize = BlockSizeAttr::getBlockSizeValue(getBlockSize());
3502 if (values.size() == 1 && blockSize != 1)
3503 return emitOpError()
3504 << "requires block_size to be BLOCK_SIZE_1 when values tensor list "
3505 "length is 1";
3506 if (values.size() == 2 && blockSize == 1)
3507 return emitOpError()
3508 << "requires block_size to not be BLOCK_SIZE_1 when values tensor "
3509 "list length is 2";
3510
3511 if (failed(verifySameElementTypes(*this, values[0].getType(),
3512 output[0].getType(), "values[0]",
3513 "output[0]")))
3514 return failure();
3515 if (values.size() == 2 && failed(verifySameElementTypes(
3516 *this, values[1].getType(), output[1].getType(),
3517 "values[1]", "output[1]")))
3518 return failure();
3519
3520 if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
3521 succeeded(rowCount) && rowCount.value() <= 0)
3522 return emitOpError() << "requires row_count to be > 0, got "
3523 << rowCount.value();
3524
3525 int64_t n = ShapedType::kDynamic;
3526 int64_t k = ShapedType::kDynamic;
3527 int64_t c = ShapedType::kDynamic;
3528 int64_t w = ShapedType::kDynamic;
3529 int64_t multiplesOfC = ShapedType::kDynamic;
3530
3531 const ShapeAdaptor valuesDataShape(values[0].getType());
3532 if (valuesDataShape.hasRank()) {
3533 n = valuesDataShape.getDimSize(0);
3534 k = valuesDataShape.getDimSize(1);
3535 c = valuesDataShape.getDimSize(2);
3536 }
3537
3538 if (ShapedType::isStatic(c) && c % blockSize != 0)
3539 return emitOpError() << "expects channels of values[0] (" << c
3540 << ") to be divisible by block_size (" << blockSize
3541 << ")";
3542
3543 const ShapeAdaptor indicesShape(getIndices().getType());
3544 if (indicesShape.hasRank()) {
3545 if (failed(tryUpdateDimOrFailure(*this, n, indicesShape.getDimSize(0),
3546 "indices", "batch")))
3547 return failure();
3548 w = indicesShape.getDimSize(1);
3549 }
3550
3551 const ShapeAdaptor outputDataShape(output[0].getType());
3552 if (outputDataShape.hasRank()) {
3553 if (failed(tryUpdateDimOrFailure(*this, n, outputDataShape.getDimSize(0),
3554 "output[0]", "batch")) ||
3555 failed(tryUpdateDimOrFailure(*this, c, outputDataShape.getDimSize(2),
3556 "output[0]", "channels")))
3557 return failure();
3558
3559 if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
3560 succeeded(rowCount) && rowCount.value() > 0 &&
3561 ShapedType::isStatic(w)) {
3562 const int64_t expectedOutputRows = w * rowCount.value();
3563 if (ShapedType::isStatic(outputDataShape.getDimSize(1)) &&
3564 outputDataShape.getDimSize(1) != expectedOutputRows)
3565 return emitOpError() << "requires output[0] dimension 1 to have size "
3566 << expectedOutputRows << ", got "
3567 << outputDataShape.getDimSize(1);
3568 }
3569 }
3570
3571 if (values.size() == 2) {
3572 const ShapeAdaptor valuesScaleShape(values[1].getType());
3573 if (valuesScaleShape.hasRank()) {
3574 if (failed(tryUpdateDimOrFailure(*this, n, valuesScaleShape.getDimSize(0),
3575 "values[1]", "batch")) ||
3576 failed(tryUpdateDimOrFailure(*this, k, valuesScaleShape.getDimSize(1),
3577 "values[1]", "rows")))
3578 return failure();
3579 multiplesOfC = valuesScaleShape.getDimSize(2);
3580 }
3581
3582 const ShapeAdaptor outputScaleShape(output[1].getType());
3583 if (outputScaleShape.hasRank()) {
3584 if (failed(tryUpdateDimOrFailure(*this, n, outputScaleShape.getDimSize(0),
3585 "output[1]", "batch")))
3586 return failure();
3587
3588 if (auto rowCount = getConstantScalarIntValue<int32_t>(getRowCount());
3589 succeeded(rowCount) && rowCount.value() > 0 &&
3590 ShapedType::isStatic(w)) {
3591 const int64_t expectedOutputRows = w * rowCount.value();
3592 if (ShapedType::isStatic(outputScaleShape.getDimSize(1)) &&
3593 outputScaleShape.getDimSize(1) != expectedOutputRows)
3594 return emitOpError() << "requires output[1] dimension 1 to have size "
3595 << expectedOutputRows << ", got "
3596 << outputScaleShape.getDimSize(1);
3597 }
3598
3599 if (ShapedType::isDynamic(multiplesOfC))
3600 multiplesOfC = outputScaleShape.getDimSize(2);
3601 else if (ShapedType::isStatic(outputScaleShape.getDimSize(2)) &&
3602 multiplesOfC != outputScaleShape.getDimSize(2))
3603 return emitOpError()
3604 << "expected channels of output[1] to match size "
3605 << multiplesOfC << ", got " << outputScaleShape.getDimSize(2);
3606 }
3607
3608 if (ShapedType::isStatic(c) && ShapedType::isStatic(multiplesOfC) &&
3609 multiplesOfC != c / blockSize)
3610 return emitOpError()
3611 << "expects channels of scale tensors to equal C/block_size (" << c
3612 << "/" << blockSize << "), got " << multiplesOfC;
3613 }
3614
3615 return success();
3616}
3617
3618LogicalResult tosa::ResizeOp::inferReturnTypeComponents(
3619 MLIRContext *context, ::std::optional<Location> location,
3620 ResizeOp::Adaptor adaptor,
3621 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3622 llvm::SmallVector<int64_t, 4> outputShape;
3623 outputShape.resize(4, ShapedType::kDynamic);
3624
3625 ShapeAdaptor inputShape(adaptor.getInput().getType());
3626 if (!inputShape.hasRank())
3627 return failure();
3628
3629 outputShape[0] = inputShape.getDimSize(0);
3630 outputShape[3] = inputShape.getDimSize(3);
3631 int64_t inputHeight = inputShape.getDimSize(1);
3632 int64_t inputWidth = inputShape.getDimSize(2);
3633
3634 if ((inputHeight == ShapedType::kDynamic) ||
3635 (inputWidth == ShapedType::kDynamic))
3636 return failure();
3637
3638 SmallVector<int64_t> scaleInt, offsetInt, borderInt;
3639 if (!tosa::getConstShapeValues(adaptor.getScale().getDefiningOp(),
3640 scaleInt) ||
3641 !tosa::getConstShapeValues(adaptor.getOffset().getDefiningOp(),
3642 offsetInt) ||
3643 !tosa::getConstShapeValues(adaptor.getBorder().getDefiningOp(),
3644 borderInt)) {
3645 return failure();
3646 }
3647
3648 // Non-positive scale values are invalid, so the output shape cannot be
3649 // inferred here. They are reported by the ERROR_IF check in tosa-validate.
3650 if (llvm::any_of(scaleInt, [](int64_t s) { return s <= 0; }))
3651 return failure();
3652
3653 // Compute the output shape based on attributes: scale, offset, and border.
3654 const int64_t outputHeight =
3655 (((inputHeight - 1) * scaleInt[0] - offsetInt[0] + borderInt[0]) /
3656 scaleInt[1]) +
3657 1;
3658
3659 const int64_t outputWidth =
3660 (((inputWidth - 1) * scaleInt[2] - offsetInt[1] + borderInt[1]) /
3661 scaleInt[3]) +
3662 1;
3663
3664 if (outputHeight < 0 || outputWidth < 0) {
3665 return emitOptionalError(
3666 location,
3667 "calculated output height and width must be non-negative, "
3668 "got height = ",
3669 outputHeight, ", width = ", outputWidth);
3670 }
3671
3672 outputShape[1] = outputHeight;
3673 outputShape[2] = outputWidth;
3674 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3675 return success();
3676}
3677
3678LogicalResult tosa::ResizeOp::verify() {
3679 const Type inputElementType = getElementTypeOrSelf(getInput().getType());
3680
3681 if (isa<BlockScaledType>(inputElementType) &&
3682 getMode() != ResizeMode::NEAREST_NEIGHBOR)
3683 return emitOpError("requires NEAREST_NEIGHBOR mode for block scaled input");
3684
3685 return success();
3686}
3687
3688LogicalResult tosa::ScatterOp::inferReturnTypeComponents(
3689 MLIRContext *context, ::std::optional<Location> location,
3690 ScatterOp::Adaptor adaptor,
3691 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3692 llvm::SmallVector<int64_t> outputShape;
3693 outputShape.resize(3, ShapedType::kDynamic);
3694
3695 ShapeAdaptor valuesInShape(adaptor.getValuesIn().getType());
3696 if (valuesInShape.hasRank()) {
3697 outputShape[0] = valuesInShape.getDimSize(0);
3698 outputShape[1] = valuesInShape.getDimSize(1);
3699 outputShape[2] = valuesInShape.getDimSize(2);
3700 }
3701
3702 ShapeAdaptor indicesShape(adaptor.getIndices().getType());
3703 if (indicesShape.hasRank()) {
3704 if (outputShape[0] == ShapedType::kDynamic)
3705 outputShape[0] = indicesShape.getDimSize(0);
3706 }
3707
3708 ShapeAdaptor inputShape(adaptor.getInput().getType());
3709 if (inputShape.hasRank()) {
3710 if (outputShape[0] == ShapedType::kDynamic)
3711 outputShape[0] = inputShape.getDimSize(0);
3712 if (outputShape[2] == ShapedType::kDynamic)
3713 outputShape[2] = inputShape.getDimSize(2);
3714 }
3715
3716 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
3717 return success();
3718}
3719
3720LogicalResult tosa::ScatterOp::verify() {
3721 if (verifySameElementTypes(*this, /* inType = */ getValuesIn().getType(),
3722 /* outType = */ getValuesOut().getType())
3723 .failed() ||
3724 verifySameElementTypes(*this, /* inType = */ getInput().getType(),
3725 /* outType = */ getValuesOut().getType())
3726 .failed()) {
3727 return failure();
3728 }
3729
3730 const ShapeAdaptor valuesInShape(getValuesIn().getType());
3731 const ShapeAdaptor indicesShape(getIndices().getType());
3732 const ShapeAdaptor inputShape(getInput().getType());
3733 const ShapeAdaptor outputShape(getValuesOut().getType());
3734
3735 int64_t n = ShapedType::kDynamic;
3736 int64_t k = ShapedType::kDynamic;
3737 int64_t w = ShapedType::kDynamic;
3738 int64_t c = ShapedType::kDynamic;
3739 if (valuesInShape.hasRank()) {
3740 n = valuesInShape.getDimSize(0);
3741 k = valuesInShape.getDimSize(1);
3742 c = valuesInShape.getDimSize(2);
3743 }
3744 if (indicesShape.hasRank()) {
3745 const int64_t indicesN = indicesShape.getDimSize(0);
3746 w = indicesShape.getDimSize(1);
3747 if (n == ShapedType::kDynamic)
3748 n = indicesN;
3749 else if (indicesN != ShapedType::kDynamic && n != indicesN)
3750 return emitOpError() << "requires indices dimension 0 to have size " << n
3751 << ", got " << indicesN;
3752 }
3753 if (inputShape.hasRank()) {
3754 const int64_t inputN = inputShape.getDimSize(0);
3755 const int64_t inputW = inputShape.getDimSize(1);
3756 const int64_t inputC = inputShape.getDimSize(2);
3757 if (n == ShapedType::kDynamic)
3758 n = inputN;
3759 else if (inputN != ShapedType::kDynamic && n != inputN)
3760 return emitOpError() << "requires input dimension 0 to have size " << n
3761 << ", got " << inputN;
3762 if (w == ShapedType::kDynamic)
3763 w = inputW;
3764 else if (inputW != ShapedType::kDynamic && w != inputW)
3765 return emitOpError() << "requires input dimension 1 to have size " << w
3766 << ", got " << inputW;
3767
3768 if (c == ShapedType::kDynamic)
3769 c = inputC;
3770 else if (inputC != ShapedType::kDynamic && c != inputC)
3771 return emitOpError() << "requires input dimension 2 to have size " << c
3772 << ", got " << inputC;
3773 }
3774 if (outputShape.hasRank()) {
3775 const int64_t outputN = outputShape.getDimSize(0);
3776 const int64_t outputK = outputShape.getDimSize(1);
3777 const int64_t outputC = outputShape.getDimSize(2);
3778 if (n != ShapedType::kDynamic && outputN != ShapedType::kDynamic &&
3779 n != outputN)
3780 return emitOpError() << "requires values_out dimension 0 to have size "
3781 << n << ", got " << outputN;
3782 if (k == ShapedType::kDynamic)
3783 k = outputK;
3784 else if (outputK != ShapedType::kDynamic && k != outputK)
3785 return emitOpError() << "requires values_out dimension 1 to have size "
3786 << k << ", got " << outputK;
3787 if (c != ShapedType::kDynamic && outputC != ShapedType::kDynamic &&
3788 c != outputC)
3789 return emitOpError() << "requires values_out dimension 2 to have size "
3790 << c << ", got " << outputC;
3791 }
3792 if (k != ShapedType::kDynamic && w != ShapedType::kDynamic && !(k >= w))
3793 return emitOpError() << "requires dimensions K >= W, got K=" << k
3794 << " and W=" << w;
3795
3796 return success();
3797}
3798
3799static LogicalResult ReduceInferReturnTypes(
3800 ShapeAdaptor operandShape, Type inputType, IntegerAttr axis,
3801 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3802 int64_t axisVal = axis.getValue().getSExtValue();
3803 if (!operandShape.hasRank() || operandShape.getRank() <= axisVal) {
3804 inferredReturnShapes.push_back(ShapedTypeComponents(inputType));
3805 return success();
3806 }
3807
3808 SmallVector<int64_t> outputShape;
3809 operandShape.getDims(outputShape);
3810 outputShape[axisVal] = 1;
3811 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));
3812 return success();
3813}
3814
3815#define COMPATIBLE_RETURN_TYPES(OP) \
3816 bool OP::isCompatibleReturnTypes(TypeRange l, TypeRange r) { \
3817 if (l.size() != r.size() || l.size() != 1) \
3818 return false; \
3819 if (getElementTypeOrSelf(l[0]) != getElementTypeOrSelf(r[0])) \
3820 return false; \
3821 return succeeded(verifyCompatibleShape(l[0], r[0])); \
3822 }
3823
3824#define REDUCE_SHAPE_INFER(OP) \
3825 LogicalResult OP::inferReturnTypeComponents( \
3826 MLIRContext *context, ::std::optional<Location> location, \
3827 OP::Adaptor adaptor, \
3828 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { \
3829 Type inputType = \
3830 llvm::cast<TensorType>(adaptor.getInput().getType()).getElementType(); \
3831 ShapeAdaptor inputShape(adaptor.getInput().getType()); \
3832 const Properties &prop = adaptor.getProperties(); \
3833 return ReduceInferReturnTypes(inputShape, inputType, prop.axis, \
3834 inferredReturnShapes); \
3835 } \
3836 COMPATIBLE_RETURN_TYPES(OP)
3837
3838REDUCE_SHAPE_INFER(tosa::ReduceAllOp)
3839REDUCE_SHAPE_INFER(tosa::ReduceAnyOp)
3840REDUCE_SHAPE_INFER(tosa::ReduceMaxOp)
3841REDUCE_SHAPE_INFER(tosa::ReduceMinOp)
3842REDUCE_SHAPE_INFER(tosa::ReduceProductOp)
3843REDUCE_SHAPE_INFER(tosa::ReduceSumOp)
3844#undef REDUCE_SHAPE_INFER
3845COMPATIBLE_RETURN_TYPES(tosa::ConcatOp)
3846#undef COMPATIBLE_RETURN_TYPES
3847
3848template <typename T>
3849static LogicalResult verifyReduceOp(T op) {
3850 // All TOSA reduce Ops have input, output and axis.
3851 TensorType inputType = op.getInput().getType();
3852 TensorType outputType = op.getOutput().getType();
3853 int32_t reduceAxis = op.getAxis();
3854
3855 if (reduceAxis < 0) {
3856 op.emitOpError("reduce axis must not be negative");
3857 return failure();
3858 }
3859 if (inputType.hasRank()) {
3860 int64_t inputRank = inputType.getRank();
3861 // We allow for a special case where the input/output shape has rank 0 and
3862 // axis is also 0.
3863 if (reduceAxis >= inputRank && (reduceAxis != 0 || inputRank != 0)) {
3864 op.emitOpError("expect input tensor rank (")
3865 << inputRank << ") to be larger than reduce axis (" << reduceAxis
3866 << ")";
3867 return failure();
3868 }
3869 }
3870 if (outputType.hasRank()) {
3871 int64_t outputRank = outputType.getRank();
3872 if (inputType.hasRank() && outputRank != inputType.getRank()) {
3873 op.emitOpError(
3874 "expect output tensor rank to be equal to input tensor rank");
3875 return failure();
3876 }
3877 if (reduceAxis >= outputRank && (reduceAxis != 0 || outputRank != 0)) {
3878 op.emitOpError("expect output tensor rank (")
3879 << outputRank << ") to be larger than reduce axis (" << reduceAxis
3880 << ")";
3881 return failure();
3882 }
3883 // We can only verify the reduced dimension size to be 1 if this is not
3884 // the special case of output rank == 0.
3885 if (outputRank != 0) {
3886 auto outputShape = outputType.getShape();
3887 if (!outputType.isDynamicDim(reduceAxis) &&
3888 outputShape[reduceAxis] != 1) {
3889 op.emitOpError("expect reduced dimension size to be 1, got ")
3890 << outputShape[reduceAxis];
3891 return failure();
3892 }
3893 }
3894 }
3895 return success();
3896}
3897
3898LogicalResult tosa::ReduceAllOp::verify() { return verifyReduceOp(*this); }
3899LogicalResult tosa::ReduceAnyOp::verify() { return verifyReduceOp(*this); }
3900LogicalResult tosa::ReduceMaxOp::verify() { return verifyReduceOp(*this); }
3901LogicalResult tosa::ReduceMinOp::verify() { return verifyReduceOp(*this); }
3902LogicalResult tosa::ReduceProductOp::verify() { return verifyReduceOp(*this); }
3903LogicalResult tosa::ReduceSumOp::verify() { return verifyReduceOp(*this); }
3904
3905static LogicalResult NAryInferReturnTypes(
3906 const ValueShapeRange &operands,
3907 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3909 if (resolveBroadcastShape(operands, outShape).failed()) {
3910 inferredReturnShapes.push_back(ShapedTypeComponents());
3911 } else {
3912 inferredReturnShapes.push_back(ShapedTypeComponents(outShape));
3913 }
3914 return success();
3915}
3916
3917#define NARY_SHAPE_INFER(OP) \
3918 LogicalResult OP::inferReturnTypeComponents( \
3919 MLIRContext *context, ::std::optional<Location> location, \
3920 ValueShapeRange operands, DictionaryAttr attributes, \
3921 PropertyRef properties, RegionRange regions, \
3922 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { \
3923 return NAryInferReturnTypes(operands, inferredReturnShapes); \
3924 }
3925
3926NARY_SHAPE_INFER(tosa::AbsOp)
3927NARY_SHAPE_INFER(tosa::AddOp)
3928NARY_SHAPE_INFER(tosa::ArithmeticRightShiftOp)
3929NARY_SHAPE_INFER(tosa::BitwiseAndOp)
3930NARY_SHAPE_INFER(tosa::BitwiseOrOp)
3931NARY_SHAPE_INFER(tosa::BitwiseXorOp)
3932NARY_SHAPE_INFER(tosa::BitwiseNotOp)
3933NARY_SHAPE_INFER(tosa::CastOp)
3934NARY_SHAPE_INFER(tosa::CeilOp)
3935NARY_SHAPE_INFER(tosa::ClampOp)
3936NARY_SHAPE_INFER(tosa::ClzOp)
3937NARY_SHAPE_INFER(tosa::CosOp)
3938NARY_SHAPE_INFER(tosa::ExpOp)
3939NARY_SHAPE_INFER(tosa::FloorOp)
3940NARY_SHAPE_INFER(tosa::GreaterEqualOp)
3941NARY_SHAPE_INFER(tosa::GreaterOp)
3942NARY_SHAPE_INFER(tosa::IdentityOp)
3943NARY_SHAPE_INFER(tosa::IntDivOp)
3944NARY_SHAPE_INFER(tosa::LogOp)
3945NARY_SHAPE_INFER(tosa::LogicalAndOp)
3946NARY_SHAPE_INFER(tosa::LogicalLeftShiftOp)
3947NARY_SHAPE_INFER(tosa::LogicalNotOp)
3948NARY_SHAPE_INFER(tosa::LogicalOrOp)
3949NARY_SHAPE_INFER(tosa::LogicalRightShiftOp)
3950NARY_SHAPE_INFER(tosa::LogicalXorOp)
3951NARY_SHAPE_INFER(tosa::MaximumOp)
3952NARY_SHAPE_INFER(tosa::MinimumOp)
3953NARY_SHAPE_INFER(tosa::PowOp)
3954NARY_SHAPE_INFER(tosa::ReciprocalOp)
3955NARY_SHAPE_INFER(tosa::ReverseOp)
3956NARY_SHAPE_INFER(tosa::RsqrtOp)
3957NARY_SHAPE_INFER(tosa::SinOp)
3958NARY_SHAPE_INFER(tosa::SelectOp)
3959NARY_SHAPE_INFER(tosa::SubOp)
3960NARY_SHAPE_INFER(tosa::TanhOp)
3961NARY_SHAPE_INFER(tosa::ErfOp)
3962NARY_SHAPE_INFER(tosa::SigmoidOp)
3963#undef PRED_SHAPE_INFER
3964
3965LogicalResult tosa::NegateOp::inferReturnTypeComponents(
3966 MLIRContext *context, ::std::optional<Location> location,
3967 NegateOp::Adaptor adaptor,
3968 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
3969 ShapeAdaptor inputShape(adaptor.getInput1().getType());
3970 inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));
3971 return success();
3972}
3973
3974LogicalResult tosa::NegateOp::verify() {
3975 // Verify same element type
3976 const Type input1Type = getInput1().getType();
3977 const Type outputType = getOutput().getType();
3978 if (verifySameElementTypes(*this, input1Type, outputType).failed())
3979 return failure();
3980
3981 // Verify same shape
3982 const SmallVector<Type, 2> types = {input1Type, outputType};
3983 if (failed(verifyCompatibleShapes(types)))
3984 return emitOpError() << "requires the same shape for input1 and output";
3985
3986 const Type input1EType = getStorageElementTypeOrSelf(getInput1().getType());
3987 const Type input1ZpEType =
3988 getStorageElementTypeOrSelf(getInput1Zp().getType());
3989 if (input1EType != input1ZpEType) {
3990 return emitOpError("expect both input1 and its zero point are the same "
3991 "element type, got ")
3992 << input1EType << " and " << input1ZpEType;
3993 }
3994 const Type outputEType = getStorageElementTypeOrSelf(getOutput().getType());
3995 const Type outputZpEType =
3996 getStorageElementTypeOrSelf(getOutputZp().getType());
3997 if (outputEType != outputZpEType) {
3998 return emitOpError("expect both output and its zero point are the same "
3999 "element type, got ")
4000 << outputEType << " and " << outputZpEType;
4001 }
4002
4003 FailureOr<int64_t> maybeIZp = getInput1ZeroPoint();
4004 if (succeeded(maybeIZp) && verifyInput1ZeroPoint(*maybeIZp).failed())
4005 return failure();
4006
4007 FailureOr<int64_t> maybeOZp = getOutputZeroPoint();
4008 if (succeeded(maybeOZp) && verifyOutputZeroPoint(*maybeOZp).failed())
4009 return failure();
4010
4011 return success();
4012}
4013
4014static LogicalResult poolingInferReturnTypes(
4015 ShapeAdaptor inputShape, ArrayRef<int64_t> kernel, ArrayRef<int64_t> stride,
4017 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4018 llvm::SmallVector<int64_t> outputShape;
4019 outputShape.resize(4, ShapedType::kDynamic);
4020
4021 // We only know the rank if the input type is unranked.
4022 if (!inputShape.hasRank()) {
4023 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4024 return success();
4025 }
4026
4027 // Batch and number of channels are identical for pooling layer.
4028 outputShape[0] = inputShape.getDimSize(0);
4029 outputShape[3] = inputShape.getDimSize(3);
4030
4031 int64_t height = inputShape.getDimSize(1);
4032 int64_t width = inputShape.getDimSize(2);
4033
4034 if (ShapedType::isStatic(height)) {
4035 int64_t padded = height + pad[0] + pad[1] - kernel[0];
4036 outputShape[1] = padded / stride[0] + 1;
4037 }
4038
4039 if (ShapedType::isStatic(width)) {
4040 int64_t padded = width + pad[2] + pad[3] - kernel[1];
4041 outputShape[2] = padded / stride[1] + 1;
4042 }
4043
4044 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4045 return success();
4046}
4047
4048template <typename AdaptorT>
4050
4052protected:
4053 static void updateIfDynamic(int64_t &current, int64_t candidate) {
4054 if (ShapedType::isDynamic(current))
4055 current = candidate;
4056 }
4057};
4058
4059template <>
4060class ConvInferShapeAdaptor<Conv2DOp::Adaptor>
4061 : public ConvInferShapeAdaptorBase {
4062public:
4063 explicit ConvInferShapeAdaptor(Conv2DOp::Adaptor adaptor)
4064 : adaptor(adaptor) {}
4065
4067 SmallVectorImpl<int64_t> &inputSpatial) {
4068 const ShapeAdaptor inputShape(adaptor.getInput().getType());
4069 if (!inputShape.hasRank())
4070 return;
4071
4072 const int64_t outputBatch = inputShape.getDimSize(0);
4073 const int64_t inputHeight = inputShape.getDimSize(1);
4074 const int64_t inputWidth = inputShape.getDimSize(2);
4075
4076 outputShape[0] = outputBatch;
4077 inputSpatial[0] = inputHeight;
4078 inputSpatial[1] = inputWidth;
4079 }
4080
4082 SmallVectorImpl<int64_t> &weightSpatial) {
4083 const ShapeAdaptor weightShape(adaptor.getWeight().getType());
4084 if (!weightShape.hasRank())
4085 return;
4086
4087 const int64_t outputChannels = weightShape.getDimSize(0);
4088 const int64_t kernelHeight = weightShape.getDimSize(1);
4089 const int64_t kernelWidth = weightShape.getDimSize(2);
4090
4091 outputShape[3] = outputChannels;
4092 weightSpatial[0] = kernelHeight;
4093 weightSpatial[1] = kernelWidth;
4094 }
4095
4096 int64_t getNumSpatialDims() const { return 2; }
4097 int64_t getOutputRank() const { return 4; }
4098
4100 SmallVector<int64_t> &strideValues,
4101 SmallVector<int64_t> &dilationValues) {
4102 padValues.assign(adaptor.getPad().begin(), adaptor.getPad().end());
4103 strideValues.assign(adaptor.getStride().begin(), adaptor.getStride().end());
4104 dilationValues.assign(adaptor.getDilation().begin(),
4105 adaptor.getDilation().end());
4106 return success();
4107 }
4108
4109private:
4110 Conv2DOp::Adaptor adaptor;
4111};
4112
4113template <>
4114class ConvInferShapeAdaptor<Conv2DBlockScaledOp::Adaptor>
4115 : public ConvInferShapeAdaptorBase {
4116public:
4117 explicit ConvInferShapeAdaptor(Conv2DBlockScaledOp::Adaptor adaptor)
4118 : adaptor(adaptor) {}
4119
4121 SmallVectorImpl<int64_t> &inputSpatial) {
4122 const ShapeAdaptor inputDataShape(adaptor.getInputData().getType());
4123 if (inputDataShape.hasRank()) {
4124 const int64_t outputBatch = inputDataShape.getDimSize(0);
4125 const int64_t inputHeight = inputDataShape.getDimSize(1);
4126 const int64_t inputWidth = inputDataShape.getDimSize(2);
4127
4128 outputShape[0] = outputBatch;
4129 inputSpatial[0] = inputHeight;
4130 inputSpatial[1] = inputWidth;
4131 }
4132
4133 const ShapeAdaptor inputScaleShape(adaptor.getInputScale().getType());
4134 if (!inputScaleShape.hasRank())
4135 return;
4136
4137 const int64_t scaleBatch = inputScaleShape.getDimSize(0);
4138 const int64_t scaleHeight = inputScaleShape.getDimSize(1);
4139 const int64_t scaleWidth = inputScaleShape.getDimSize(2);
4140
4141 updateIfDynamic(outputShape[0], scaleBatch);
4142 updateIfDynamic(inputSpatial[0], scaleHeight);
4143 updateIfDynamic(inputSpatial[1], scaleWidth);
4144 }
4145
4147 SmallVectorImpl<int64_t> &weightSpatial) {
4148 const ShapeAdaptor weightDataShape(adaptor.getWeightData().getType());
4149 if (weightDataShape.hasRank()) {
4150 const int64_t outputChannels = weightDataShape.getDimSize(0);
4151 const int64_t kernelHeight = weightDataShape.getDimSize(1);
4152 const int64_t kernelWidth = weightDataShape.getDimSize(2);
4153
4154 outputShape[3] = outputChannels;
4155 weightSpatial[0] = kernelHeight;
4156 weightSpatial[1] = kernelWidth;
4157 }
4158
4159 const ShapeAdaptor weightScaleShape(adaptor.getWeightScale().getType());
4160 if (!weightScaleShape.hasRank())
4161 return;
4162
4163 const int64_t scaleOutputChannels = weightScaleShape.getDimSize(0);
4164 const int64_t scaleKernelHeight = weightScaleShape.getDimSize(1);
4165 const int64_t scaleKernelWidth = weightScaleShape.getDimSize(2);
4166
4167 updateIfDynamic(outputShape[3], scaleOutputChannels);
4168 updateIfDynamic(weightSpatial[0], scaleKernelHeight);
4169 updateIfDynamic(weightSpatial[1], scaleKernelWidth);
4170 }
4171
4172 int64_t getNumSpatialDims() const { return 2; }
4173 int64_t getOutputRank() const { return 4; }
4174
4176 SmallVector<int64_t> &strideValues,
4177 SmallVector<int64_t> &dilationValues) {
4178 if (!tosa::getConstShapeValues(adaptor.getPad().getDefiningOp(),
4179 padValues) ||
4180 !tosa::getConstShapeValues(adaptor.getStride().getDefiningOp(),
4181 strideValues) ||
4182 !tosa::getConstShapeValues(adaptor.getDilation().getDefiningOp(),
4183 dilationValues))
4184 return failure();
4185 return success();
4186 }
4187
4188private:
4189 Conv2DBlockScaledOp::Adaptor adaptor;
4190};
4191
4192template <>
4193class ConvInferShapeAdaptor<Conv3DOp::Adaptor>
4194 : public ConvInferShapeAdaptorBase {
4195public:
4196 explicit ConvInferShapeAdaptor(Conv3DOp::Adaptor adaptor)
4197 : adaptor(adaptor) {}
4198
4200 SmallVectorImpl<int64_t> &inputSpatial) {
4201 const ShapeAdaptor inputShape(adaptor.getInput().getType());
4202 if (!inputShape.hasRank())
4203 return;
4204
4205 const int64_t outputBatch = inputShape.getDimSize(0);
4206 const int64_t inputDepth = inputShape.getDimSize(1);
4207 const int64_t inputHeight = inputShape.getDimSize(2);
4208 const int64_t inputWidth = inputShape.getDimSize(3);
4209
4210 outputShape[0] = outputBatch;
4211 inputSpatial[0] = inputDepth;
4212 inputSpatial[1] = inputHeight;
4213 inputSpatial[2] = inputWidth;
4214 }
4215
4217 SmallVectorImpl<int64_t> &weightSpatial) {
4218 const ShapeAdaptor weightShape(adaptor.getWeight().getType());
4219 if (!weightShape.hasRank())
4220 return;
4221
4222 const int64_t outputChannels = weightShape.getDimSize(0);
4223 const int64_t kernelDepth = weightShape.getDimSize(1);
4224 const int64_t kernelHeight = weightShape.getDimSize(2);
4225 const int64_t kernelWidth = weightShape.getDimSize(3);
4226
4227 outputShape[4] = outputChannels;
4228 weightSpatial[0] = kernelDepth;
4229 weightSpatial[1] = kernelHeight;
4230 weightSpatial[2] = kernelWidth;
4231 }
4232
4233 int64_t getNumSpatialDims() const { return 3; }
4234 int64_t getOutputRank() const { return 5; }
4235
4237 SmallVector<int64_t> &strideValues,
4238 SmallVector<int64_t> &dilationValues) {
4239 padValues.assign(adaptor.getPad().begin(), adaptor.getPad().end());
4240 strideValues.assign(adaptor.getStride().begin(), adaptor.getStride().end());
4241 dilationValues.assign(adaptor.getDilation().begin(),
4242 adaptor.getDilation().end());
4243 return success();
4244 }
4245
4246private:
4247 Conv3DOp::Adaptor adaptor;
4248};
4249
4250template <typename AdaptorT>
4252 AdaptorT adaptor,
4253 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4254 ConvInferShapeAdaptor<AdaptorT> convShapeAdaptor(adaptor);
4255 llvm::SmallVector<int64_t> outputShape(convShapeAdaptor.getOutputRank(),
4256 ShapedType::kDynamic);
4257 llvm::SmallVector<int64_t> inputSpatial(convShapeAdaptor.getNumSpatialDims(),
4258 ShapedType::kDynamic);
4259 llvm::SmallVector<int64_t> weightSpatial(convShapeAdaptor.getNumSpatialDims(),
4260 ShapedType::kDynamic);
4261
4262 convShapeAdaptor.inferInputShape(outputShape, inputSpatial);
4263 convShapeAdaptor.inferWeightShape(outputShape, weightSpatial);
4264
4265 const ShapeAdaptor biasShape = adaptor.getBias().getType();
4266 if (biasShape.hasRank()) {
4267 const int64_t biasSize = biasShape.getDimSize(0);
4268 if (biasSize != 1) {
4269 const size_t outputChannelDim = convShapeAdaptor.getOutputRank() - 1;
4270 outputShape[outputChannelDim] =
4271 ShapedType::isDynamic(outputShape[outputChannelDim])
4272 ? biasSize
4273 : outputShape[outputChannelDim];
4274 }
4275 }
4276
4277 SmallVector<int64_t> padValues;
4278 SmallVector<int64_t> strideValues;
4279 SmallVector<int64_t> dilationValues;
4280 if (failed(convShapeAdaptor.getSpatialParameters(padValues, strideValues,
4281 dilationValues))) {
4282 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4283 return success();
4284 }
4285
4286 for (int64_t dim = 0; dim < convShapeAdaptor.getNumSpatialDims(); ++dim) {
4287 if (!ShapedType::isStatic(inputSpatial[dim]) ||
4288 !ShapedType::isStatic(weightSpatial[dim]))
4289 continue;
4290 const int64_t inputSize =
4291 inputSpatial[dim] + padValues[2 * dim] + padValues[2 * dim + 1];
4292 const int64_t filterSize =
4293 (weightSpatial[dim] - 1) * dilationValues[dim] + 1;
4294 const int64_t unstridedResult = inputSize - filterSize + 1;
4295 outputShape[dim + 1] = (unstridedResult - 1) / strideValues[dim] + 1;
4296 }
4297
4298 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4299 return success();
4300}
4301
4302LogicalResult Conv2DOp::inferReturnTypeComponents(
4303 MLIRContext *context, ::std::optional<Location> location,
4304 Conv2DOp::Adaptor adaptor,
4305 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4306 return inferConvReturnTypeComponents(adaptor, inferredReturnShapes);
4307}
4308
4309LogicalResult Conv2DOp::verify() {
4310 if (verifyConvOp(*this).failed() || verifyConvOpErrorIf(*this).failed())
4311 return failure();
4312 return success();
4313}
4314
4315LogicalResult Conv2DBlockScaledOp::inferReturnTypeComponents(
4316 MLIRContext *context, ::std::optional<Location> location,
4317 Conv2DBlockScaledOp::Adaptor adaptor,
4318 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4319 return inferConvReturnTypeComponents(adaptor, inferredReturnShapes);
4320}
4321
4322LogicalResult Conv2DBlockScaledOp::verify() {
4323 if (failed(verifySameElementTypes(*this, getInputData().getType(),
4324 getWeightData().getType(), "input_data",
4325 "weight_data")) ||
4326 failed(verifySameElementTypes(*this, getInputScale().getType(),
4327 getWeightScale().getType(), "input_scale",
4328 "weight_scale")) ||
4329 failed(verifySameElementTypes(*this, getBias().getType(),
4330 getOutput().getType(), "bias", "output")))
4331 return failure();
4332
4333 // Verify input shape compatibility
4334 int64_t N = ShapedType::kDynamic;
4335 int64_t IH = ShapedType::kDynamic;
4336 int64_t IW = ShapedType::kDynamic;
4337 int64_t IC = ShapedType::kDynamic;
4338 int64_t multiplesOfIC = ShapedType::kDynamic;
4339 int64_t OC = ShapedType::kDynamic;
4340 int64_t KH = ShapedType::kDynamic;
4341 int64_t KW = ShapedType::kDynamic;
4342
4343 const ShapeAdaptor inputDataShape(getInputData().getType());
4344 if (inputDataShape.hasRank()) {
4345 N = inputDataShape.getDimSize(0);
4346 IH = inputDataShape.getDimSize(1);
4347 IW = inputDataShape.getDimSize(2);
4348 IC = inputDataShape.getDimSize(3);
4349 }
4350
4351 const ShapeAdaptor inputScaleShape(getInputScale().getType());
4352 if (inputScaleShape.hasRank()) {
4353 if (failed(tryUpdateDimOrFailure(*this, N, inputScaleShape.getDimSize(0),
4354 "input_scale", "batch size")) ||
4355 failed(tryUpdateDimOrFailure(*this, IH, inputScaleShape.getDimSize(1),
4356 "input_scale", "input height")) ||
4357 failed(tryUpdateDimOrFailure(*this, IW, inputScaleShape.getDimSize(2),
4358 "input_scale", "input width")))
4359 return failure();
4360 multiplesOfIC = inputScaleShape.getDimSize(3);
4361 }
4362
4363 const ShapeAdaptor weightDataShape(getWeightData().getType());
4364 if (weightDataShape.hasRank()) {
4365 OC = weightDataShape.getDimSize(0);
4366 KH = weightDataShape.getDimSize(1);
4367 KW = weightDataShape.getDimSize(2);
4368 if (failed(tryUpdateDimOrFailure(*this, IC, weightDataShape.getDimSize(3),
4369 "weight_data", "input channels")))
4370 return failure();
4371 }
4372
4373 const ShapeAdaptor weightScaleShape(getWeightScale().getType());
4374 if (weightScaleShape.hasRank()) {
4375 if (failed(tryUpdateDimOrFailure(*this, OC, weightScaleShape.getDimSize(0),
4376 "weight_scale", "output channels")) ||
4377 failed(tryUpdateDimOrFailure(*this, KH, weightScaleShape.getDimSize(1),
4378 "weight_scale", "kernel height")) ||
4379 failed(tryUpdateDimOrFailure(*this, KW, weightScaleShape.getDimSize(2),
4380 "weight_scale", "kernel width")) ||
4381 failed(tryUpdateDimOrFailure(*this, multiplesOfIC,
4382 weightScaleShape.getDimSize(3),
4383 "weight_scale", "input channel blocks")))
4384 return failure();
4385 }
4386
4387 const uint32_t blockSize = BlockSizeAttr::getBlockSizeValue(getBlockSize());
4388 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_32))
4389 return emitOpError("expect block size to be 32, got ") << blockSize;
4390 // Verify IC is a multiple of block size
4391 if (ShapedType::isStatic(IC) && IC % blockSize != 0)
4392 return emitOpError("expect IC to be a multiple of block size, got IC=")
4393 << IC << ", block_size=" << blockSize;
4394
4395 // Verify multiplesOfIC is IC / block size
4396 if (ShapedType::isStatic(IC) && ShapedType::isStatic(multiplesOfIC) &&
4397 multiplesOfIC != IC / blockSize)
4398 return emitOpError(
4399 "expect scale operands dimension 2 to equal IC/block_size (")
4400 << IC << "/" << blockSize << ")"
4401 << ", got " << multiplesOfIC;
4402
4403 // Verify pad/stride/dilation values
4404 SmallVector<int64_t> padValues;
4405 if (tosa::getConstShapeValues(getPad().getDefiningOp(), padValues)) {
4406 if (llvm::any_of(padValues, [](int64_t p) { return p < 0; }))
4407 return emitOpError("expect all padding values to be >= 0, got ")
4408 << padValues;
4409 }
4410
4411 SmallVector<int64_t> strideValues;
4412 if (tosa::getConstShapeValues(getStride().getDefiningOp(), strideValues)) {
4413 if (llvm::any_of(strideValues, [](int64_t s) { return s < 1; }))
4414 return emitOpError("expect all stride values to be >= 1, got ")
4415 << strideValues;
4416 }
4417
4418 SmallVector<int64_t> dilationValues;
4419 if (tosa::getConstShapeValues(getDilation().getDefiningOp(),
4420 dilationValues)) {
4421 if (llvm::any_of(dilationValues, [](int64_t d) { return d < 1; }))
4422 return emitOpError("expect all dilation values to be >= 1, got ")
4423 << dilationValues;
4424 }
4425
4426 // Verify output shape compatibility
4427 const ShapeAdaptor outputShape(getOutput().getType());
4428 if (!padValues.empty() && !strideValues.empty() && !dilationValues.empty() &&
4429 outputShape.hasRank()) {
4430 if (failed(verifyConvOutputSize(*this, IH, KH, outputShape.getDimSize(1),
4431 padValues[0], padValues[1], strideValues[0],
4432 dilationValues[0], "height", "y", "top",
4433 "bottom")) ||
4434 failed(verifyConvOutputSize(*this, IW, KW, outputShape.getDimSize(2),
4435 padValues[2], padValues[3], strideValues[1],
4436 dilationValues[1], "width", "x", "left",
4437 "right")))
4438 return failure();
4439 }
4440
4441 // Verify bias
4442 const ShapeAdaptor biasShape(getBias().getType());
4443 if (biasShape.hasRank() && outputShape.hasRank()) {
4444 const int64_t biasChannels = biasShape.getDimSize(0);
4445 const int64_t outputChannels =
4446 outputShape.getDimSize(outputShape.getRank() - 1);
4447 if (biasChannels == ShapedType::kDynamic ||
4448 outputChannels == ShapedType::kDynamic)
4449 // Skip following checks if biasChannels or outputChannels is dynamic dim
4450 return success();
4451
4452 if (biasChannels != outputChannels && biasChannels != 1)
4453 return emitOpError(
4454 "bias channels expected to be equal to output channels (")
4455 << outputChannels << ") or 1, got " << biasChannels;
4456 }
4457
4458 return success();
4459}
4460
4461LogicalResult Conv3DOp::inferReturnTypeComponents(
4462 MLIRContext *context, ::std::optional<Location> location,
4463 Conv3DOp::Adaptor adaptor,
4464 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4465 return inferConvReturnTypeComponents(adaptor, inferredReturnShapes);
4466}
4467
4468LogicalResult Conv3DOp::verify() {
4469 if (verifyConvOp(*this).failed() || verifyConvOpErrorIf(*this).failed())
4470 return failure();
4471 return success();
4472}
4473
4474LogicalResult AvgPool2dOp::inferReturnTypeComponents(
4475 MLIRContext *context, ::std::optional<Location> location,
4476 AvgPool2dOp::Adaptor adaptor,
4477 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4478 ShapeAdaptor inputShape(adaptor.getInput().getType());
4479 const Properties &prop = adaptor.getProperties();
4480 return poolingInferReturnTypes(inputShape, prop.kernel, prop.stride, prop.pad,
4481 inferredReturnShapes);
4482}
4483
4484LogicalResult AvgPool2dAdaptiveOp::inferReturnTypeComponents(
4485 MLIRContext *context, ::std::optional<Location> location,
4486 AvgPool2dAdaptiveOp::Adaptor adaptor,
4487 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4488 ShapeAdaptor inputShape(adaptor.getInput().getType());
4489
4490 llvm::SmallVector<int64_t> kernelValues;
4491 llvm::SmallVector<int64_t> strideValues;
4492 llvm::SmallVector<int64_t> padValues;
4493 if (tosa::getConstShapeValues(adaptor.getKernel().getDefiningOp(),
4494 kernelValues) &&
4495 tosa::getConstShapeValues(adaptor.getStride().getDefiningOp(),
4496 strideValues) &&
4497 tosa::getConstShapeValues(adaptor.getPad().getDefiningOp(), padValues)) {
4498 return poolingInferReturnTypes(inputShape, kernelValues, strideValues,
4499 padValues, inferredReturnShapes);
4500 }
4501
4502 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);
4503 if (inputShape.hasRank()) {
4504 // Keep N & C as pooling only changes H & W.
4505 outputShape[0] = inputShape.getDimSize(0);
4506 outputShape[3] = inputShape.getDimSize(3);
4507 }
4508
4509 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4510 return success();
4511}
4512
4513LogicalResult MaxPool2dOp::inferReturnTypeComponents(
4514 MLIRContext *context, ::std::optional<Location> location,
4515 MaxPool2dOp::Adaptor adaptor,
4516 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4517 ShapeAdaptor inputShape(adaptor.getInput().getType());
4518 const Properties &prop = adaptor.getProperties();
4519 return poolingInferReturnTypes(inputShape, prop.kernel, prop.stride, prop.pad,
4520 inferredReturnShapes);
4521}
4522
4523LogicalResult MaxPool2dAdaptiveOp::inferReturnTypeComponents(
4524 MLIRContext *context, ::std::optional<Location> location,
4525 MaxPool2dAdaptiveOp::Adaptor adaptor,
4526 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4527 ShapeAdaptor inputShape(adaptor.getInput().getType());
4528
4529 llvm::SmallVector<int64_t> kernelValues;
4530 llvm::SmallVector<int64_t> strideValues;
4531 llvm::SmallVector<int64_t> padValues;
4532 if (tosa::getConstShapeValues(adaptor.getKernel().getDefiningOp(),
4533 kernelValues) &&
4534 tosa::getConstShapeValues(adaptor.getStride().getDefiningOp(),
4535 strideValues) &&
4536 tosa::getConstShapeValues(adaptor.getPad().getDefiningOp(), padValues)) {
4537 return poolingInferReturnTypes(inputShape, kernelValues, strideValues,
4538 padValues, inferredReturnShapes);
4539 }
4540
4541 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);
4542 if (inputShape.hasRank()) {
4543 outputShape[0] = inputShape.getDimSize(0);
4544 outputShape[3] = inputShape.getDimSize(3);
4545 }
4546 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4547 return success();
4548}
4549
4550LogicalResult MaxPool2dOp::verify() {
4551 if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),
4552 /* outType = */ getOutput().getType())))
4553 return failure();
4554
4555 if (failed(verifyPoolingOp(*this)))
4556 return failure();
4557
4558 return success();
4559}
4560
4561LogicalResult MaxPool2dAdaptiveOp::verify() {
4562 if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),
4563 /* outType = */ getOutput().getType())))
4564 return failure();
4565
4566 AdaptivePoolingConstShapeValues values;
4568
4569 if (failed(verifyPoolingOpImpl(getOperation(), values.kernel, values.stride,
4570 values.pad, getInput(), getOutput())))
4571 return failure();
4572
4573 return success();
4574}
4575
4576LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(
4577 MLIRContext *context, ::std::optional<Location> location,
4578 DepthwiseConv2DOp::Adaptor adaptor,
4579 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4580 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);
4581
4582 int64_t inputWidth = ShapedType::kDynamic;
4583 int64_t inputHeight = ShapedType::kDynamic;
4584 int64_t inputChannels = ShapedType::kDynamic;
4585
4586 int64_t weightWidth = ShapedType::kDynamic;
4587 int64_t weightHeight = ShapedType::kDynamic;
4588 int64_t depthChannels = ShapedType::kDynamic;
4589
4590 // Input shape describes input width/height and batch.
4591 ShapeAdaptor inputShape(adaptor.getInput().getType());
4592 if (inputShape.hasRank()) {
4593 outputShape[0] = inputShape.getDimSize(0);
4594 inputHeight = inputShape.getDimSize(1);
4595 inputWidth = inputShape.getDimSize(2);
4596 inputChannels = inputShape.getDimSize(3);
4597 }
4598
4599 // Weight shapes describes the filter width/height and the output channels.
4600 ShapeAdaptor weightShape(adaptor.getWeight().getType());
4601 if (weightShape.hasRank()) {
4602 weightHeight = weightShape.getDimSize(0);
4603 weightWidth = weightShape.getDimSize(1);
4604 inputChannels = ShapedType::isDynamic(inputChannels)
4605 ? weightShape.getDimSize(2)
4606 : inputChannels;
4607 depthChannels = weightShape.getDimSize(3);
4608 }
4609
4610 // If both inputChannels and depthChannels are available we can determine
4611 // the output channels.
4612 if (ShapedType::isStatic(inputChannels) &&
4613 ShapedType::isStatic(depthChannels)) {
4614 outputShape[3] = inputChannels * depthChannels;
4615 }
4616
4617 // Bias shape can describe the output channels.
4618 ShapeAdaptor biasShape(adaptor.getBias().getType());
4619 if (biasShape.hasRank() && ShapedType::isDynamic(outputShape[3])) {
4620 int64_t bc = biasShape.getDimSize(0);
4621 if (bc != ShapedType::kDynamic && bc != 1)
4622 outputShape[3] = bc;
4623 }
4624
4625 llvm::ArrayRef<int64_t> dilation = adaptor.getDilation();
4626 llvm::ArrayRef<int64_t> padding = adaptor.getPad();
4627 llvm::ArrayRef<int64_t> stride = adaptor.getStride();
4628
4629 if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {
4630 int64_t inputSize = inputHeight + padding[0] + padding[1];
4631 int64_t filterSize = (weightHeight - 1) * dilation[0] + 1;
4632 int64_t unstridedResult = inputSize - filterSize + 1;
4633 outputShape[1] = (unstridedResult - 1) / stride[0] + 1;
4634 }
4635
4636 if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {
4637 int64_t inputSize = inputWidth + padding[2] + padding[3];
4638 int64_t filterSize = (weightWidth - 1) * dilation[1] + 1;
4639 int64_t unstridedResult = inputSize - filterSize + 1;
4640 outputShape[2] = (unstridedResult - 1) / stride[1] + 1;
4641 }
4642
4643 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4644 return success();
4645}
4646
4647LogicalResult DepthwiseConv2DOp::verify() {
4648 if (verifyConvOp(*this).failed() || verifyConvOpErrorIf(*this).failed())
4649 return failure();
4650 return success();
4651}
4652
4653LogicalResult TransposeConv2DOp::inferReturnTypeComponents(
4654 MLIRContext *context, ::std::optional<Location> location,
4655 TransposeConv2DOp::Adaptor adaptor,
4656 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4657 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);
4658
4659 int64_t inputWidth = ShapedType::kDynamic;
4660 int64_t inputHeight = ShapedType::kDynamic;
4661 int64_t weightWidth = ShapedType::kDynamic;
4662 int64_t weightHeight = ShapedType::kDynamic;
4663
4664 // Input shape describes input width/height and batch.
4665 ShapeAdaptor inputShape(adaptor.getInput().getType());
4666 if (inputShape.hasRank()) {
4667 outputShape[0] = ShapedType::isDynamic(outputShape[0])
4668 ? inputShape.getDimSize(0)
4669 : outputShape[0];
4670 inputHeight = inputShape.getDimSize(1);
4671 inputWidth = inputShape.getDimSize(2);
4672 }
4673
4674 // Weight shapes describes the filter width/height and the output channels.
4675 ShapeAdaptor weightShape(adaptor.getWeight().getType());
4676 if (weightShape.hasRank()) {
4677 outputShape[3] = ShapedType::isDynamic(outputShape[3])
4678 ? weightShape.getDimSize(0)
4679 : outputShape[3];
4680 weightHeight = weightShape.getDimSize(1);
4681 weightWidth = weightShape.getDimSize(2);
4682 }
4683
4684 // Bias shape can describe the output channels.
4685 ShapeAdaptor biasShape(adaptor.getBias().getType());
4686 if (biasShape.hasRank() && ShapedType::isDynamic(outputShape[3])) {
4687 int64_t bc = biasShape.getDimSize(0);
4688 if (bc != ShapedType::kDynamic && bc != 1)
4689 outputShape[3] = bc;
4690 }
4691
4692 llvm::ArrayRef<int64_t> padding = adaptor.getOutPad();
4693 llvm::ArrayRef<int64_t> stride = adaptor.getStride();
4694
4695 if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {
4696 int64_t calculateSize =
4697 (inputHeight - 1) * stride[0] + padding[0] + padding[1] + weightHeight;
4698 outputShape[1] =
4699 ShapedType::isDynamic(outputShape[1]) ? calculateSize : outputShape[1];
4700 }
4701
4702 if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {
4703 int64_t calculateSize =
4704 (inputWidth - 1) * stride[1] + padding[2] + padding[3] + weightWidth;
4705 outputShape[2] =
4706 ShapedType::isDynamic(outputShape[2]) ? calculateSize : outputShape[2];
4707 }
4708
4709 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));
4710 return success();
4711}
4712
4713LogicalResult TransposeConv2DOp::verify() {
4714 if (verifyConvOp(*this).failed())
4715 return failure();
4716
4717 const llvm::ArrayRef<int64_t> strides = getStride();
4718 const int64_t strideY = strides[0];
4719 const int64_t strideX = strides[1];
4720
4721 if (strideY < 1 || strideX < 1)
4722 return emitOpError("expect all stride values to be >= 1, got [")
4723 << strides << "]";
4724
4725 const auto checkPadAgainstKernelDim =
4726 [this](int64_t padValue, int64_t kernelDimSize, llvm::StringRef padName,
4727 llvm::StringRef kernelDimName) -> LogicalResult {
4728 if (padValue <= -kernelDimSize)
4729 return emitOpError("expected ")
4730 << padName << " > -" << kernelDimName << ", but got: " << padName
4731 << "=" << padValue << " and " << kernelDimName << "="
4732 << kernelDimSize;
4733 return success();
4734 };
4735
4736 const llvm::ArrayRef<int64_t> padding = getOutPad();
4737 const int64_t outPadTop = padding[0];
4738 const int64_t outPadBottom = padding[1];
4739 const int64_t outPadLeft = padding[2];
4740 const int64_t outPadRight = padding[3];
4741
4742 const auto weightType =
4743 llvm::dyn_cast<RankedTensorType>(getWeight().getType());
4744
4745 if (weightType) {
4746 const int64_t kernelHeight = weightType.getDimSize(1);
4747 if (ShapedType::isStatic(kernelHeight)) {
4748 if (failed(checkPadAgainstKernelDim(outPadTop, kernelHeight,
4749 "out_pad_top", "KH")))
4750 return failure();
4751
4752 if (failed(checkPadAgainstKernelDim(outPadBottom, kernelHeight,
4753 "out_pad_bottom", "KH")))
4754 return failure();
4755 }
4756
4757 const int64_t kernelWidth = weightType.getDimSize(2);
4758 if (ShapedType::isStatic(kernelWidth)) {
4759 if (failed(checkPadAgainstKernelDim(outPadLeft, kernelWidth,
4760 "out_pad_left", "KW")))
4761 return failure();
4762
4763 if (failed(checkPadAgainstKernelDim(outPadRight, kernelWidth,
4764 "out_pad_right", "KW")))
4765 return failure();
4766 }
4767 }
4768
4769 // Rest of the checks depend on the output type being a RankedTensorType
4770 const auto outputType =
4771 llvm::dyn_cast<RankedTensorType>(getOutput().getType());
4772 if (!outputType)
4773 return success();
4774
4775 const auto inputType = llvm::dyn_cast<RankedTensorType>(getInput().getType());
4776 if (inputType && weightType) {
4777 const int64_t inputHeight = inputType.getDimSize(1);
4778 const int64_t kernelHeight = weightType.getDimSize(1);
4779 const int64_t outputHeight = outputType.getDimSize(1);
4780
4781 if (ShapedType::isStatic(inputHeight) &&
4782 ShapedType::isStatic(outputHeight)) {
4783 if (outputHeight !=
4784 (inputHeight - 1) * strideY + outPadTop + outPadBottom + kernelHeight)
4785 return emitOpError(
4786 "dimension mismatch: expected OH == (IH - 1) * stride_y "
4787 "+ out_pad_top + out_pad_bottom + KH, but got ")
4788 << outputHeight << " != (" << inputHeight << " - 1) * "
4789 << strideY << " + " << outPadTop << " + " << outPadBottom
4790 << " + " << kernelHeight;
4791 }
4792
4793 const int64_t inputWidth = inputType.getDimSize(2);
4794 const int64_t kernelWidth = weightType.getDimSize(2);
4795 const int64_t outputWidth = outputType.getDimSize(2);
4796
4797 if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(outputWidth)) {
4798 if (outputWidth !=
4799 (inputWidth - 1) * strideX + outPadLeft + outPadRight + kernelWidth)
4800 return emitOpError(
4801 "dimension mismatch: expected OW == (IW - 1) * stride_x "
4802 "+ out_pad_left + out_pad_right + KW, but got ")
4803 << outputWidth << " != (" << inputWidth << " - 1) * " << strideX
4804 << " + " << outPadLeft << " + " << outPadRight << " + "
4805 << kernelWidth;
4806 }
4807 }
4808
4809 const auto biasType = llvm::dyn_cast<RankedTensorType>(getBias().getType());
4810
4811 if (!biasType)
4812 return success();
4813
4814 const int64_t biasChannels = biasType.getDimSize(0);
4815
4816 // Skip further checks if bias is dynamic
4817 if (biasChannels == ShapedType::kDynamic)
4818 return success();
4819
4820 const int64_t outputChannels = outputType.getDimSize(3);
4821 if (!ShapedType::isDynamic(outputChannels) &&
4822 biasChannels != outputChannels && biasChannels != 1)
4823 return emitOpError(
4824 "bias channels expected to be equal to output channels (")
4825 << outputChannels << ") or 1, got " << biasChannels;
4826
4827 return success();
4828}
4829
4830LogicalResult RescaleOp::verify() {
4831 const auto inputType = llvm::cast<ShapedType>(getInput().getType());
4832 auto inputElementType =
4833 getStorageElementTypeOrSelf(inputType.getElementType());
4834 if (!mlir::isa<IntegerType>(inputElementType)) {
4835 emitOpError("expect input to have integer element type, got ")
4836 << inputElementType;
4837 return failure();
4838 }
4839
4840 const auto outputType = llvm::cast<ShapedType>(getOutput().getType());
4841 auto outputElementType =
4842 getStorageElementTypeOrSelf(outputType.getElementType());
4843 if (!mlir::isa<IntegerType>(outputElementType)) {
4844 emitOpError("expect output to have integer element type, got ")
4845 << outputElementType;
4846 return failure();
4847 }
4848
4849 if (verifyRescaleValueAndZpTypes(*this, getInput(), getInputZp(), "input")
4850 .failed())
4851 return failure();
4852
4853 if (verifyRescaleValueAndZpTypes(*this, getOutput(), getOutputZp(), "output")
4854 .failed())
4855 return failure();
4856
4857 const auto multiplierType = llvm::cast<ShapedType>(getMultiplier().getType());
4858 // multiplier element type must be i32 for scale32 = true
4859 if (getScale32() && !multiplierType.getElementType().isInteger(32)) {
4860 emitOpError("expect i32 element type for multiplier for scale32=true, got ")
4861 << multiplierType.getElementType();
4862 return failure();
4863 }
4864
4865 // multiplier element type must be i16 for scale32 = false
4866 if (!getScale32() && !multiplierType.getElementType().isInteger(16)) {
4867 emitOpError(
4868 "expect i16 element type for multiplier for scale32=false, got ")
4869 << multiplierType.getElementType();
4870 return failure();
4871 }
4872
4873 if (!inputType.hasRank())
4874 return success();
4875
4876 // multiplier/shift must have shape = {numChannels},
4877 // where numChannel is 1 if per_channel = false
4878 // otherwise numChannel is dimension in input shape's last axis
4879 int64_t numChannels = 1;
4880 if (getPerChannel()) {
4881 if (inputType.getRank() < 1) {
4882 emitOpError("requires input to be at least rank 1 when per_channel is "
4883 "true, but got rank ")
4884 << inputType.getRank();
4885 return failure();
4886 }
4887 numChannels = inputType.getDimSize(inputType.getRank() - 1);
4888 }
4889
4890 if (outputType.hasRank()) {
4892 getOperation(), outputType, inputType.getShape())))
4893 return failure();
4894 }
4895
4896 if (multiplierType.hasRank()) {
4897 ArrayRef<int64_t> multiplierShape = multiplierType.getShape();
4898 // multiplier input has rank 1 by dialect definition
4899 if (multiplierShape[0] != ShapedType::kDynamic &&
4900 multiplierShape[0] != numChannels) {
4901 emitOpError("expect shape of { ")
4902 << numChannels << " } for multiplier input, got { "
4903 << multiplierShape[0] << " }";
4904 return failure();
4905 }
4906 }
4907
4908 const auto shiftType = llvm::cast<ShapedType>(getShift().getType());
4909 if (shiftType.hasRank()) {
4910 ArrayRef<int64_t> shiftShape = shiftType.getShape();
4911 // shift input has rank 1 by dialect definition
4912 if (shiftShape[0] != ShapedType::kDynamic && shiftShape[0] != numChannels) {
4913 emitOpError("expect shape of { ")
4914 << numChannels << " } for shift input, got { " << shiftShape[0]
4915 << " }";
4916 return failure();
4917 }
4918 }
4919
4920 return success();
4921}
4922
4923LogicalResult RescaleOp::inferReturnTypeComponents(
4924 MLIRContext *context, ::std::optional<Location> location,
4925 RescaleOp::Adaptor adaptor,
4926 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4927 ShapeAdaptor inputShape(adaptor.getInput().getType());
4928 inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));
4929 return success();
4930}
4931
4932LogicalResult CastOp::verify() {
4933 const ShapedType inputType = llvm::cast<ShapedType>(getInput().getType());
4934 const ShapedType outputType = llvm::cast<ShapedType>(getType());
4935 const Type inputElementType = inputType.getElementType();
4936 const Type outputElementType = outputType.getElementType();
4937
4938 const bool inputIsBlockScaled = llvm::isa<BlockScaledType>(inputElementType);
4939 const bool outputIsBlockScaled =
4940 llvm::isa<BlockScaledType>(outputElementType);
4941
4942 const bool isUnsigned = this->getInputUnsigned();
4943 const Type inputDataType = getStorageElementTypeOrSelf(inputType);
4944
4945 if (isUnsigned)
4946 if (!inputDataType.isInteger() || inputDataType.isInteger(1))
4947 return emitOpError()
4948 << "attribute input_unsigned requires integer type inputs. Got: "
4949 << inputDataType;
4950
4951 if (!inputIsBlockScaled && !outputIsBlockScaled)
4952 return success();
4953
4954 if (inputIsBlockScaled && outputIsBlockScaled)
4955 return emitOpError()
4956 << "requires exactly one of input or output to have block scaled "
4957 "element type";
4958
4959 const Type scalarElementType =
4960 inputIsBlockScaled ? outputElementType : inputElementType;
4961 if (!llvm::isa<FloatType>(scalarElementType))
4962 return emitOpError()
4963 << "requires non-block-scaled element type to be floating-point "
4964 "when casting to or from block scaled element type, got "
4965 << scalarElementType;
4966
4967 return success();
4968}
4969
4970LogicalResult CastFromBlockScaledOp::inferReturnTypeComponents(
4971 MLIRContext *context, ::std::optional<Location> location,
4972 CastFromBlockScaledOp::Adaptor adaptor,
4973 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
4974 const ShapeAdaptor inputShape(adaptor.getInputData().getType());
4975 inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));
4976 return success();
4977}
4978
4979LogicalResult CastFromBlockScaledOp::verify() {
4980 const Type inputDataType = getInputData().getType();
4981 const Type outputDataType = getResult().getType();
4982 if (failed(verifyCompatibleShape(inputDataType, outputDataType)))
4983 return emitOpError() << "require compatible shapes for input_data ("
4984 << inputDataType << ") and " << "output_data ("
4985 << outputDataType << ")";
4986
4987 const ShapeAdaptor inputDataShape = ShapeAdaptor(inputDataType);
4988
4989 if (inputDataShape.hasRank()) {
4990 const unsigned int blockSize =
4991 BlockSizeAttr::getBlockSizeValue(getBlockSize());
4992 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_32))
4993 return emitOpError("expect block size to be 32, got ") << blockSize;
4994 const int64_t inputDataLastDim =
4995 inputDataShape.getDimSize(inputDataShape.getRank() - 1);
4996 if (inputDataLastDim % blockSize != 0)
4997 return emitOpError() << "expect last dimension of input_data ("
4998 << inputDataLastDim
4999 << ") to be divisible by block_size (" << blockSize
5000 << ")";
5001
5002 const Type inputScaleType = getInputScale().getType();
5003 const ShapeAdaptor inputScaleShape = ShapeAdaptor(inputScaleType);
5004
5005 if (inputScaleShape.hasRank()) {
5006 SmallVector<int64_t> inputDataDims, inputScaleDims;
5007 inputDataShape.getDims(inputDataDims);
5008 inputScaleShape.getDims(inputScaleDims);
5009
5010 if (inputDataDims.size() != inputScaleDims.size() ||
5012 ArrayRef<int64_t>(inputDataDims).drop_back(1),
5013 ArrayRef<int64_t>(inputScaleDims).drop_back(1))))
5014 return emitOpError()
5015 << "require compatible shapes for input_data (" << inputDataType
5016 << ") and " << "input_scale (" << inputScaleType
5017 << ") except for the last dimension";
5018
5019 const SmallVector<int64_t, 2> dimsToCheck{inputDataLastDim / blockSize,
5020 inputScaleDims.back()};
5021 if (ShapedType::isStatic(inputDataLastDim) &&
5022 failed(verifyCompatibleDims(dimsToCheck)))
5023 return emitOpError()
5024 << "expect last dimension of input_scale ("
5025 << inputScaleDims.back()
5026 << ") to be equal to last dimension of input_data / block_size ("
5027 << inputDataDims.back() / blockSize << ")";
5028 }
5029 }
5030
5031 return success();
5032}
5033
5034LogicalResult CastToBlockScaledOp::inferReturnTypeComponents(
5035 MLIRContext *context, ::std::optional<Location> location,
5036 CastToBlockScaledOp::Adaptor adaptor,
5037 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
5038 const ShapeAdaptor inputShape(adaptor.getInputData().getType());
5039 inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));
5040 if (!inputShape.hasRank())
5041 return success();
5042
5043 // Calculate output_scale shape if ranked input provided
5044 SmallVector<int64_t> outputScaleShape;
5045 inputShape.getDims(outputScaleShape);
5046 const int64_t lastDimLoc = inputShape.getRank() - 1;
5047 const int64_t lastDimSize = inputShape.getDimSize(lastDimLoc);
5048 if (ShapedType::isStatic(lastDimSize)) {
5049 const unsigned int blockSize =
5050 BlockSizeAttr::getBlockSizeValue(adaptor.getBlockSize());
5051 outputScaleShape[lastDimLoc] = lastDimSize / blockSize;
5052 }
5053 inferredReturnShapes.push_back(ShapedTypeComponents(outputScaleShape));
5054 return success();
5055}
5056
5057LogicalResult CastToBlockScaledOp::verify() {
5058 const Type inputDataType = getInputData().getType();
5059 const Type outputDataType = getResult(0).getType();
5060 if (failed(verifyCompatibleShape(inputDataType, outputDataType)))
5061 return emitOpError() << "require compatible shapes for input_data ("
5062 << inputDataType << ") and " << "output_data ("
5063 << outputDataType << ")";
5064
5065 const unsigned int blockSize =
5066 BlockSizeAttr::getBlockSizeValue(getBlockSize());
5067 if (blockSize != BlockSizeAttr::getBlockSizeValue(BlockSize::BLOCK_SIZE_32))
5068 return emitOpError("expect block size to be 32, got ") << blockSize;
5069 const ShapeAdaptor inputDataShape = ShapeAdaptor(inputDataType);
5070 if (inputDataShape.hasRank()) {
5071 const int64_t inputDataLastDim =
5072 inputDataShape.getDimSize(inputDataShape.getRank() - 1);
5073 if (ShapedType::isStatic(inputDataLastDim) &&
5074 inputDataLastDim % blockSize != 0)
5075 return emitOpError() << "expect last dimension of input_data ("
5076 << inputDataLastDim
5077 << ") to be divisible by block_size (" << blockSize
5078 << ")";
5079 }
5080
5081 const ShapeAdaptor outputDataShape = ShapeAdaptor(outputDataType);
5082 const Type outputScaleType = getResult(1).getType();
5083 const ShapeAdaptor outputScaleShape = ShapeAdaptor(outputScaleType);
5084 if (outputDataShape.hasRank() && outputScaleShape.hasRank()) {
5085 SmallVector<int64_t> outputDataDims, outputScaleDims;
5086 outputDataShape.getDims(outputDataDims);
5087 outputScaleShape.getDims(outputScaleDims);
5088
5089 if (outputDataDims.size() != outputScaleDims.size() ||
5091 ArrayRef<int64_t>(outputDataDims).drop_back(1),
5092 ArrayRef<int64_t>(outputScaleDims).drop_back(1))))
5093 return emitOpError() << "require compatible shapes for output_data ("
5094 << outputDataType << ") and " << "output_scale ("
5095 << outputScaleType
5096 << ") except for the last dimension";
5097
5098 const int64_t outputDataLastDim = outputDataDims.back();
5099 const SmallVector<int64_t, 2> dimsToCheck{outputDataLastDim / blockSize,
5100 outputScaleDims.back()};
5101 if (ShapedType::isStatic(outputDataLastDim) &&
5102 failed(verifyCompatibleDims(dimsToCheck)))
5103 return emitOpError()
5104 << "expect last dimension of output_scale ("
5105 << outputScaleDims.back()
5106 << ") to be equal to last dimension of output_data / block_size ("
5107 << outputDataDims.back() / blockSize << ")";
5108 }
5109
5110 return success();
5111}
5112
5113LogicalResult IfOp::inferReturnTypeComponents(
5114 MLIRContext *context, ::std::optional<Location> location,
5115 IfOp::Adaptor adaptor,
5116 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
5117 llvm::SmallVector<tosa::YieldOp> yieldOps;
5118 for (Region *region : adaptor.getRegions()) {
5119 for (auto &block : *region)
5120 if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))
5121 yieldOps.push_back(returnOp);
5122 }
5123
5124 if (yieldOps.empty())
5125 return failure();
5126
5127 // Get the initial type information for the yield op.
5128 llvm::SmallVector<ValueKnowledge> resultKnowledge;
5129 resultKnowledge.reserve(yieldOps.front().getNumOperands());
5130 for (auto operand : yieldOps.front().getOperands()) {
5131 resultKnowledge.push_back(
5132 ValueKnowledge::getKnowledgeFromType(operand.getType()));
5133 }
5134
5135 for (auto yieldOp : yieldOps) {
5136 if (resultKnowledge.size() != yieldOp.getNumOperands())
5137 return failure();
5138
5139 for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {
5140 int32_t index = it.index();
5141 auto meet = ValueKnowledge::meet(
5142 resultKnowledge[index],
5143 ValueKnowledge::getKnowledgeFromType(it.value().getType()));
5144 if (!meet)
5145 continue;
5146 resultKnowledge[index] = meet;
5147 }
5148 }
5149
5150 for (const ValueKnowledge &result : resultKnowledge) {
5151 inferredReturnShapes.push_back(result.getShapedTypeComponents());
5152 }
5153
5154 return success();
5155}
5156
5157LogicalResult WhileOp::inferReturnTypeComponents(
5158 MLIRContext *context, ::std::optional<Location> location,
5159 WhileOp::Adaptor adaptor,
5160 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {
5161 llvm::SmallVector<tosa::YieldOp> yieldOps;
5162 for (auto &block : adaptor.getBodyGraph())
5163 if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))
5164 yieldOps.push_back(returnOp);
5165
5166 // TOSA's while must have a tosa.yield as its terminator. If not found this
5167 // tosa.while is invalid.
5168 if (yieldOps.empty())
5169 return failure();
5170
5171 // Get the initial type information from the operand types.
5172 llvm::SmallVector<ValueKnowledge> resultKnowledge;
5173 resultKnowledge.reserve(yieldOps.front().getNumOperands());
5174 for (auto operand : yieldOps.front().getOperands()) {
5175 resultKnowledge.push_back(
5176 ValueKnowledge::getKnowledgeFromType(operand.getType()));
5177 }
5178
5179 for (auto yieldOp : yieldOps) {
5180 if (resultKnowledge.size() != yieldOp.getNumOperands())
5181 return failure();
5182
5183 for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {
5184 int32_t index = it.index();
5185 if (auto meet = ValueKnowledge::meet(
5186 resultKnowledge[index],
5187 ValueKnowledge::getKnowledgeFromType(it.value().getType()))) {
5188 resultKnowledge[index] = meet;
5189 }
5190 }
5191 }
5192
5193 for (const ValueKnowledge &result : resultKnowledge) {
5194 inferredReturnShapes.push_back(result.getShapedTypeComponents());
5195 }
5196
5197 return success();
5198}
5199
5201 Block::BlockArgListType blocksArgs,
5202 ValueRange initializers,
5203 StringRef prefix = "") {
5204 assert(blocksArgs.size() == initializers.size() &&
5205 "expected same length of arguments and initializers");
5206 if (initializers.empty())
5207 return;
5208
5209 parser << prefix << '(';
5210 llvm::interleaveComma(
5211 llvm::zip(blocksArgs, initializers), parser,
5212 [&](auto it) { parser << std::get<0>(it) << " = " << std::get<1>(it); });
5213 parser << ")";
5214}
5215
5216// parse and print of IfOp refer to the implementation of SCF dialect.
5217ParseResult IfOp::parse(OpAsmParser &parser, OperationState &result) {
5218 // Create the regions for 'then'.
5219 result.regions.reserve(2);
5220 Region *thenRegion = result.addRegion();
5221 Region *elseRegion = result.addRegion();
5222
5223 OpAsmParser::UnresolvedOperand cond;
5224
5225 if (parser.parseOperand(cond))
5226 return failure();
5227
5228 SmallVector<OpAsmParser::Argument, 4> regionArgs;
5229 SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;
5230
5231 // Parse the optional block arguments
5232 OptionalParseResult listResult =
5233 parser.parseOptionalAssignmentList(regionArgs, operands);
5234 if (listResult.has_value() && failed(listResult.value()))
5235 return failure();
5236
5237 // Parse a colon.
5238 if (failed(parser.parseColon()))
5239 return parser.emitError(parser.getCurrentLocation(),
5240 "expected type for condition operand");
5241
5242 // Parse the type of the condition operand
5243 Type condType;
5244 if (failed(parser.parseType(condType)))
5245 return parser.emitError(parser.getCurrentLocation(),
5246 "expected type for condition operand");
5247
5248 // Resolve operand with provided type
5249 if (failed(parser.resolveOperand(cond, condType, result.operands)))
5250 return failure();
5251
5252 // Parse optional block arg types
5253 if (listResult.has_value()) {
5254 FunctionType functionType;
5255
5256 if (failed(parser.parseType(functionType)))
5257 return parser.emitError(parser.getCurrentLocation())
5258 << "expected list of types for block arguments "
5259 << "followed by arrow type and list of return types";
5260
5261 result.addTypes(functionType.getResults());
5262
5263 if (functionType.getNumInputs() != operands.size()) {
5264 return parser.emitError(parser.getCurrentLocation())
5265 << "expected as many input types as operands " << "(expected "
5266 << operands.size() << " got " << functionType.getNumInputs()
5267 << ")";
5268 }
5269
5270 // Resolve input operands.
5271 if (failed(parser.resolveOperands(operands, functionType.getInputs(),
5272 parser.getCurrentLocation(),
5273 result.operands)))
5274 return failure();
5275 } else {
5276 // Parse optional results type list.
5277 if (parser.parseOptionalArrowTypeList(result.types))
5278 return failure();
5279 }
5280
5281 // Parse the 'then' region.
5282 if (parser.parseRegion(*thenRegion, /*arguments=*/{}, /*argTypes=*/{}))
5283 return failure();
5284
5285 // If we find an 'else' keyword then parse the 'else' region.
5286 if (!parser.parseOptionalKeyword("else")) {
5287 if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{}))
5288 return failure();
5289 }
5290
5291 // Parse the optional attribute list.
5292 if (parser.parseOptionalAttrDict(result.attributes))
5293 return failure();
5294 return success();
5295}
5296
5297void IfOp::print(OpAsmPrinter &p) {
5298 p << " " << getCondition();
5299
5300 printInitializationList(p, getThenGraph().front().getArguments(),
5301 getInputList(), " ");
5302 p << " : ";
5303 p << getCondition().getType();
5304
5305 if (!getInputList().empty()) {
5306 p << " (";
5307 llvm::interleaveComma(getInputList().getTypes(), p);
5308 p << ")";
5309 }
5310 p.printArrowTypeList(getResultTypes());
5311 p << " ";
5312
5313 p.printRegion(getThenGraph());
5314
5315 // Print the 'else' regions if it exists and has a block.
5316 auto &elseRegion = getElseGraph();
5317 if (!elseRegion.empty()) {
5318 p << " else ";
5319 p.printRegion(elseRegion);
5320 }
5321
5322 p.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
5323}
5324
5325LogicalResult IfOp::verify() {
5326 if (errorIfTypeOrShapeMismatch(*this, getThenGraph().front().getArguments(),
5327 "'then_graph' arguments", getInputList(),
5328 "'input_list'")
5329 .failed())
5330 return failure();
5331
5332 if (errorIfTypeOrShapeMismatch(*this, getElseGraph().front().getArguments(),
5333 "'else_graph' arguments", getInputList(),
5334 "'input_list'")
5335 .failed())
5336 return failure();
5337
5338 // MLIR will verify the absence of the terminator for us if otherwise.
5339 if (getThenGraph().front().mightHaveTerminator()) {
5340 auto thenYield =
5341 dyn_cast<tosa::YieldOp>(getThenGraph().front().getTerminator());
5342 if (thenYield && errorIfTypeOrShapeMismatch(
5343 *this, thenYield.getInputs(), "'then_graph' results",
5344 getOutputList(), "'output_list'")
5345 .failed())
5346 return failure();
5347 }
5348
5349 // MLIR will verify the absence of the terminator for us if otherwise.
5350 if (getElseGraph().front().mightHaveTerminator()) {
5351 auto elseYield =
5352 dyn_cast<tosa::YieldOp>(getElseGraph().front().getTerminator());
5353 if (elseYield && errorIfTypeOrShapeMismatch(
5354 *this, elseYield.getInputs(), "'else_graph' results",
5355 getOutputList(), "'output_list'")
5356 .failed())
5357 return failure();
5358 }
5359
5360 auto condType = getCondition().getType();
5361 if (errorIfShapeNotSizeOne(*this, condType).failed())
5362 return emitOpError() << "'condition' must be a size 1 tensor, got "
5363 << condType;
5364
5365 return success();
5366}
5367
5368LogicalResult WhileOp::verify() {
5369 if (errorIfTypeOrShapeMismatch(*this, getInputList(), "'input_list'",
5370 getOutputList(), "'output_list'")
5371 .failed())
5372 return failure();
5373
5374 if (errorIfTypeOrShapeMismatch(*this, getCondGraph().front().getArguments(),
5375 "'cond_graph' arguments", getInputList(),
5376 "'input_list'")
5377 .failed())
5378 return failure();
5379
5380 if (errorIfTypeOrShapeMismatch(*this, getBodyGraph().front().getArguments(),
5381 "'body_graph' arguments", getInputList(),
5382 "'input_list'")
5383 .failed())
5384 return failure();
5385
5386 if (getBodyGraph().front().mightHaveTerminator()) {
5387 auto bodyYield =
5388 dyn_cast<tosa::YieldOp>(getBodyGraph().front().getTerminator());
5389 if (bodyYield && errorIfTypeOrShapeMismatch(*this, bodyYield.getInputs(),
5390 "'body_graph' results",
5391 getInputList(), "'input_list'")
5392 .failed())
5393 return failure();
5394 }
5395
5396 // Condition block output must be a single element tensor with a single bool
5397 // value.
5398 if (!getCondGraph().front().mightHaveTerminator())
5399 return success();
5400
5401 auto condYield =
5402 dyn_cast<tosa::YieldOp>(getCondGraph().front().getTerminator());
5403 if (!condYield)
5404 return success();
5405
5406 if (condYield.getInputs().size() != 1)
5407 return emitOpError() << "require 'cond_graph' only have one result";
5408
5409 auto condOutType = condYield.getInputs()[0].getType();
5410 if (errorIfShapeNotSizeOne(*this, condOutType).failed())
5411 return emitOpError() << "'cond_graph' result must be a size 1 tensor, got "
5412 << condOutType;
5413
5414 if (!getElementTypeOrSelf(condOutType).isInteger(1))
5415 return emitOpError() << "'cond_graph' result must be a boolean tensor, got "
5416 << condOutType;
5417
5418 return success();
5419}
5420
5421LogicalResult ReverseOp::verify() {
5422 TensorType inputType = getInput1().getType();
5423 int32_t reverseAxis = getAxis();
5424
5425 if (reverseAxis < 0)
5426 return emitOpError("expected non-negative reverse axis");
5427 if (inputType.hasRank()) {
5428 int64_t inputRank = inputType.getRank();
5429 // We allow for a special case where the input/output shape has rank 0 and
5430 // axis is also 0.
5431 if (reverseAxis >= inputRank && (reverseAxis != 0 || inputRank != 0))
5432 return emitOpError("expect input tensor rank (")
5433 << inputRank << ") to be larger than reverse axis (" << reverseAxis
5434 << ")";
5435 }
5436
5437 return success();
5438}
5439
5440LogicalResult tosa::SelectOp::verify() {
5441 // verify input2 and input3 have same element type as output
5442 if (verifySameElementTypes(*this, /* inType = */ getOnTrue().getType(),
5443 /* outType = */ getOutput().getType())
5444 .failed() ||
5445 verifySameElementTypes(*this, /* inType = */ getOnFalse().getType(),
5446 /* outType = */ getOutput().getType())
5447 .failed()) {
5448 return failure();
5449 }
5450 // verify input1 has element type of bool
5451 auto predicateType = llvm::dyn_cast<ShapedType>(getPred().getType());
5452 if (!predicateType) {
5453 return emitOpError("expect shaped tensor for input1, got ")
5454 << getInput1().getType();
5455 }
5456 auto predicateElementType = predicateType.getElementType();
5457 if (!predicateElementType.isInteger(1)) {
5458 return emitOpError("expect element type of bool for input1, got ")
5459 << predicateElementType;
5460 }
5461
5462 return success();
5463}
5464
5465LogicalResult tosa::VariableReadOp::verify() {
5466 if (verifyVariableOpErrorIf(*this, getOutput1().getType(), "'output1'")
5467 .failed())
5468 return failure();
5469
5470 return success();
5471}
5472
5473LogicalResult tosa::VariableWriteOp::verify() {
5474 if (verifyVariableOpErrorIf(*this, getInput1().getType(), "'input1'")
5475 .failed())
5476 return failure();
5477
5478 return success();
5479}
5480
5481// parse and print of WhileOp refer to the implementation of SCF dialect.
5482ParseResult WhileOp::parse(OpAsmParser &parser, OperationState &result) {
5483 SmallVector<OpAsmParser::Argument, 4> regionArgs;
5484 SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;
5485 Region *cond = result.addRegion();
5486 Region *body = result.addRegion();
5487
5488 OptionalParseResult listResult =
5489 parser.parseOptionalAssignmentList(regionArgs, operands);
5490 if (listResult.has_value() && failed(listResult.value()))
5491 return failure();
5492
5493 FunctionType functionType;
5494 SMLoc typeLoc = parser.getCurrentLocation();
5495 if (failed(parser.parseColonType(functionType)))
5496 return failure();
5497
5498 result.addTypes(functionType.getResults());
5499
5500 if (functionType.getNumInputs() != operands.size()) {
5501 return parser.emitError(typeLoc)
5502 << "expected as many input types as operands " << "(expected "
5503 << operands.size() << " got " << functionType.getNumInputs() << ")";
5504 }
5505
5506 // Resolve input operands.
5507 if (failed(parser.resolveOperands(operands, functionType.getInputs(),
5508 parser.getCurrentLocation(),
5509 result.operands)))
5510 return failure();
5511
5512 // Propagate the types into the region arguments.
5513 for (size_t i = 0, e = regionArgs.size(); i != e; ++i)
5514 regionArgs[i].type = functionType.getInput(i);
5515
5516 return failure(parser.parseRegion(*cond, regionArgs) ||
5517 parser.parseKeyword("do") || parser.parseRegion(*body) ||
5518 parser.parseOptionalAttrDictWithKeyword(result.attributes));
5519}
5520
5521void WhileOp::print(OpAsmPrinter &parser) {
5522 printInitializationList(parser, getCondGraph().front().getArguments(),
5523 getInputList(), " ");
5524 parser << " : ";
5525 parser.printFunctionalType(getInputList().getTypes(),
5526 getResults().getTypes());
5527 parser << ' ';
5528 parser.printRegion(getCondGraph(), /*printEntryBlockArgs=*/false);
5529 parser << " do ";
5530 parser.printRegion(getBodyGraph());
5532 (*this)->getDiscardableAttrDictionary().getValue());
5533}
5534
5535// Create a rank-1 const tensor for zero point of the source tensor.
5536std::optional<Value> mlir::tosa::createZeroPointTensor(OpBuilder &builder,
5537 Location loc,
5538 Type srcElemType,
5539 int64_t zp) {
5540 srcElemType = getStorageElementTypeOrSelf(srcElemType);
5541 auto zpType = mlir::RankedTensorType::get({1}, srcElemType);
5542 if (llvm::isa<FloatType>(srcElemType)) {
5543 auto zpAttr = DenseElementsAttr::get(
5544 zpType, builder.getFloatAttr(srcElemType, static_cast<double>(zp)));
5545 return tosa::ConstOp::create(builder, loc, zpType, zpAttr);
5546 }
5547 if (llvm::isa<IntegerType>(srcElemType)) {
5548 auto zpAttr =
5549 DenseElementsAttr::get(zpType, builder.getIntegerAttr(srcElemType, zp));
5550 return tosa::ConstOp::create(builder, loc, zpType, zpAttr);
5551 }
5552 llvm::errs() << "zero point is not allowed for unsupported data types\n";
5553 return std::nullopt;
5554}
5555
5556//===----------------------------------------------------------------------===//
5557// TOSA Shape and Shape Operators Helper functions.
5558//===----------------------------------------------------------------------===//
5559
5561 return mlir::isa<tosa::shapeType>(t);
5562}
5563
5564LogicalResult
5565mlir::tosa::shapeType::verify(function_ref<InFlightDiagnostic()> emitError,
5566 int rank) {
5567 if (rank < 0)
5568 return emitError() << "invalid rank (must be >= 0): " << rank;
5569 return success();
5570}
5571
5573 for (auto v : op->getOperands()) {
5574 if (mlir::isa<::mlir::tosa::shapeType>(v.getType())) {
5575 Operation *definingOp = v.getDefiningOp();
5576 if (!definingOp || !definingOp->hasTrait<TosaShapeOperator>()) {
5577 return op->emitOpError("shape operand is not compile time resolvable");
5578 }
5579 }
5580 }
5581 return success();
5582}
5583
5584LogicalResult
5586 if (failed(OpTrait::impl::verifyAtLeastNOperands(op, 1)))
5587 return failure();
5588
5589 // delegate function that returns rank of shape type
5590 auto getRank = [](const Type type) {
5591 return mlir::cast<mlir::tosa::shapeType>(type).getRank();
5592 };
5593 auto operandTypes = op->getOperandTypes();
5594 auto resultTypes = op->getResultTypes();
5595
5596 auto rank = getRank(*op->getOperandTypes().begin());
5597 for (auto type : operandTypes) {
5598 if (getRank(type) != rank) {
5599 return op->emitOpError("operands don't have matching ranks");
5600 }
5601 }
5602 for (auto type : resultTypes) {
5603 if (getRank(type) != rank) {
5604 return op->emitOpError("result shape has different rank than operands");
5605 }
5606 }
5607 return success();
5608}
5609
5610//===----------------------------------------------------------------------===//
5611// TOSA Shape Operators verify functions.
5612//===----------------------------------------------------------------------===//
5613
5614LogicalResult tosa::ConstShapeOp::verify() {
5615 // check one dimensional rank
5616 auto valuesRank = getValues().getType().getRank();
5617 if (valuesRank != 1)
5618 return emitOpError("expect elements in attribute values with rank 1");
5619 // check that number of elements in values attr equal to rank of result shape
5620 auto count = getValues().getNumElements();
5621 auto rank = (cast<tosa::shapeType>(getResult().getType())).getRank();
5622 if (count != rank && (count != 1 || rank != 0)) {
5623 return emitOpError("expect number of elements in attribute values (")
5624 << count << ") to be equal to the rank (" << rank
5625 << ") for the result shape type";
5626 }
5627 return success();
5628}
5629
5630LogicalResult tosa::DimOp::verify() {
5631 const tosa::shapeType outShapeType =
5632 cast<tosa::shapeType>(getResult().getType());
5633 if (outShapeType.getRank() != 1)
5634 return emitOpError("expect output shape type to contain one element, got ")
5635 << outShapeType;
5636
5637 const ShapeAdaptor inputType(getInput1().getType());
5638 if (inputType.hasRank()) {
5639 const int64_t inputRank = inputType.getRank();
5640 const int64_t axis = getAxisAttr().getInt();
5641 if (axis < 0 || axis >= inputRank)
5642 return emitOpError("expect axis to be in the range [0, ")
5643 << inputRank << "), got " << axis;
5644 }
5645 return success();
5646}
5647
5648LogicalResult tosa::ConcatShapeOp::verify() {
5649 const tosa::shapeType outShapeType =
5650 cast<tosa::shapeType>(getResult().getType());
5651 const int64_t outputRank = outShapeType.getRank();
5652 const Operation::operand_range inputList = getInput();
5653
5654 if (inputList.size() == 0)
5655 return emitOpError("requires at least one input shape");
5656
5657 if (llvm::any_of(inputList, [](Value v) {
5658 return cast<tosa::shapeType>(v.getType()).getRank() == 0;
5659 }))
5660 return emitOpError("requires all inputs shapes have a rank greater than 0");
5661
5662 const int64_t inputsRank =
5663 llvm::accumulate(inputList, 0, [](int64_t acc, const Value &input) {
5664 const tosa::shapeType inShapeType =
5665 cast<tosa::shapeType>(input.getType());
5666 return acc + inShapeType.getRank();
5667 });
5668 if (outputRank != inputsRank)
5669 return emitOpError("requires output shape rank to be equal to the sum of "
5670 "the input shape ranks (")
5671 << inputsRank << "), got " << outputRank;
5672
5673 return success();
5674}
5675
5676LogicalResult tosa::SliceShapeOp::verify() {
5677 std::optional<int32_t> start;
5678 DenseIntElementsAttr startAttr;
5679 if (matchPattern(getStart(), m_Constant(&startAttr)))
5680 start = startAttr.getValues<int32_t>()[0];
5681 if (start && start.value() < 0)
5682 return emitOpError("expected non-negative start index, got ")
5683 << start.value();
5684
5685 std::optional<int32_t> size;
5686 DenseIntElementsAttr sizeAttr;
5687 if (matchPattern(getSize(), m_Constant(&sizeAttr)))
5688 size = sizeAttr.getValues<int32_t>()[0];
5689 if (size && size.value() <= 0)
5690 return emitOpError("expected positive size, got ") << size.value();
5691
5692 if (!size)
5693 return success();
5694
5695 const tosa::shapeType outShapeType =
5696 cast<tosa::shapeType>(getResult().getType());
5697 const int64_t outputRank = outShapeType.getRank();
5698 if (outputRank != size)
5699 return emitOpError(
5700 "expected output type size to be equal to size attribute, got ")
5701 << outputRank << " vs " << size.value();
5702
5703 if (!start)
5704 return success();
5705
5706 const tosa::shapeType inShapeType =
5707 cast<tosa::shapeType>(getInput().getType());
5708 const int64_t inputRank = inShapeType.getRank();
5709 const int64_t sliceSize = start.value() + size.value();
5710 if (sliceSize > inputRank)
5711 return emitOpError("expected start + size to be less than or equal to "
5712 "input shape rank (")
5713 << inputRank << "), got " << sliceSize;
5714
5715 return success();
5716}
5717
5718//===----------------------------------------------------------------------===//
5719// TOSA Attribute Definitions.
5720//===----------------------------------------------------------------------===//
5721
5722#define GET_ATTRDEF_CLASSES
5723#include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc"
5724
5725//===----------------------------------------------------------------------===//
5726// TOSA Type Definitions.
5727//===----------------------------------------------------------------------===//
5728#define GET_TYPEDEF_CLASSES
5729#include "mlir/Dialect/Tosa/IR/TosaOpsTypesBase.cpp.inc"
5730
5731//===----------------------------------------------------------------------===//
5732// TOSA Operator Definitions.
5733//===----------------------------------------------------------------------===//
5734
5735static ParseResult parseOptionalBoolClause(OpAsmParser &parser,
5736 StringRef keyword,
5737 BoolAttr &result) {
5738 if (failed(parser.parseOptionalKeyword(keyword)))
5739 return success();
5740 if (parser.parseLParen() || parser.parseAttribute(result) ||
5741 parser.parseRParen())
5742 return failure();
5743 return success();
5744}
5745
5746static void printOptionalBoolClause(OpAsmPrinter &printer, StringRef keyword,
5747 BoolAttr attr) {
5748 if (!attr)
5749 return;
5750 printer << keyword << '(';
5751 printer.printAttribute(attr);
5752 printer << ')';
5753}
5754
5755static ParseResult parseLocalBound(OpAsmParser &parser, BoolAttr &result) {
5756 return parseOptionalBoolClause(parser, "local_bound", result);
5757}
5758
5759static void printLocalBound(OpAsmPrinter &printer, Operation *, BoolAttr attr) {
5760 printOptionalBoolClause(printer, "local_bound", attr);
5761}
5762
5763static ParseResult parseInputUnsigned(OpAsmParser &parser, BoolAttr &result) {
5764 return parseOptionalBoolClause(parser, "input_unsigned", result);
5765}
5766
5768 BoolAttr attr) {
5769 printOptionalBoolClause(printer, "input_unsigned", attr);
5770}
5771
5772#define GET_OP_CLASSES
5773#include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"
return success()
static void printInitializationList(OpAsmPrinter &p, Block::BlockArgListType blocksArgs, ValueRange initializers, StringRef prefix="")
Prints the initialization list in the form of <prefix>(inner = outer, inner2 = outer2,...
Definition SCF.cpp:503
true
Given two iterators into the same block, return "true" if a is before `b.
static bool isLegalToInline(InlinerInterface &interface, Region *src, Region *insertRegion, bool shouldCloneInlinedRegion, IRMapping &valueMapping)
Utility to check that all of the operations within 'src' can be inlined.
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
b getContext())
static Type getValueType(Attribute attr)
Definition SPIRVOps.cpp:840
static Type getElementType(Type type, ArrayRef< int32_t > indices, function_ref< InFlightDiagnostic(StringRef)> emitErrorFn)
Walks the given type hierarchy with the given indices, potentially down to component granularity,...
Definition SPIRVOps.cpp:229
static LogicalResult verifyMatMulShapes(T op, bool transposeB)
Definition TosaOps.cpp:2051
static ParseResult parseOptionalBoolClause(OpAsmParser &parser, StringRef keyword, BoolAttr &result)
Definition TosaOps.cpp:5735
static void printShapeToDiagnostic(InFlightDiagnostic &diag, ArrayRef< int64_t > shape)
Definition TosaOps.cpp:352
static void buildMatMulOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value a, Value b)
Definition TosaOps.cpp:1354
static LogicalResult verifySameElementTypes(Operation *op, Type aType, Type bType, StringRef aName="input", StringRef bName="output")
Definition TosaOps.cpp:982
static ParseResult parseLocalBound(OpAsmParser &parser, BoolAttr &result)
Definition TosaOps.cpp:5755
LogicalResult inferConvReturnTypeComponents(AdaptorT adaptor, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:4251
static int64_t getMatMulBatchDim(const ShapeAdaptor &shape, int64_t outputRank, int64_t axis)
Definition TosaOps.cpp:1910
static SmallVector< int64_t > convertToMlirShape(ArrayRef< int64_t > shape)
Definition TosaOps.cpp:134
static LogicalResult ReduceInferReturnTypes(ShapeAdaptor operandShape, Type inputType, IntegerAttr axis, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:3799
static void printScaleValues(AsmPrinter &printer, ArrayRef< Attribute > scaleValues, Type)
Definition TosaOps.cpp:543
static void buildAvgPool2dAdaptiveOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, DenseI64ArrayAttr kernel, DenseI64ArrayAttr stride, DenseI64ArrayAttr pad, TypeAttr accType)
This builder mirrors avg_pool2d quant-info handling and materializes kernel/stride/pad as const_shape...
Definition TosaOps.cpp:1414
static LogicalResult verifyRescaleValueAndZpTypes(Operation *op, Value val, Value valZp, StringRef name)
Definition TosaOps.cpp:292
static void printOptionalBoolClause(OpAsmPrinter &printer, StringRef keyword, BoolAttr attr)
Definition TosaOps.cpp:5746
static LogicalResult errorIfShapeNotSizeOne(Operation *op, Type type)
Definition TosaOps.cpp:947
static void printLocalBound(OpAsmPrinter &printer, Operation *, BoolAttr attr)
Definition TosaOps.cpp:5759
LogicalResult argMaxMinVerify(T op)
Definition TosaOps.cpp:621
static LogicalResult verifyMatMulZeroPointType(T op, Value input, Value zp, StringRef inputName, StringRef zpName)
Definition TosaOps.cpp:2015
static ParseResult parseScaleValues(AsmParser &parser, SmallVector< Attribute > &scaleValues, Type scaleType)
Definition TosaOps.cpp:514
static ParseResult parseInputUnsigned(OpAsmParser &parser, BoolAttr &result)
Definition TosaOps.cpp:5763
#define REDUCE_SHAPE_INFER(OP)
Definition TosaOps.cpp:3824
static LogicalResult verifyConvOp(T op)
Definition TosaOps.cpp:652
static LogicalResult verifyAvgPoolCommonTypeAndZpChecks(T op)
Definition TosaOps.cpp:1122
static LogicalResult verifyVariableOpErrorIf(T op, Type type, StringRef name)
Definition TosaOps.cpp:956
static LogicalResult poolingInferReturnTypes(ShapeAdaptor inputShape, ArrayRef< int64_t > kernel, ArrayRef< int64_t > stride, ArrayRef< int64_t > pad, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:4014
static void buildPadOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value paddings)
This builder is called on TOSA pad operator that needs to create its own OptionalAttr quantization_at...
Definition TosaOps.cpp:1501
static LogicalResult verifyPoolingOpImpl(Operation *op, ArrayRef< int64_t > kernel, ArrayRef< int64_t > strides, ArrayRef< int64_t > padding, Value input, Value output)
Definition TosaOps.cpp:1021
static std::optional< int64_t > idivCheck(const int64_t lhs, const int64_t rhs)
Definition TosaOps.cpp:275
static void buildVariableOp(OpBuilder &builder, OperationState &result, StringRef name, Type variableType, Attribute initialValue)
Definition TosaOps.cpp:1515
static void buildMatMulLikeOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value a, Value b)
Definition TosaOps.cpp:1329
LogicalResult verifyConvOutputSize(Operation *op, const int64_t inputSize, const int64_t kernelSize, const int64_t outputSize, const int64_t padBefore, const int64_t padAfter, const int64_t stride, const int64_t dilation, const llvm::StringRef dimName, const llvm::StringRef dimAxis, const llvm::StringRef padBeforeName, const llvm::StringRef padAfterName)
Definition TosaOps.cpp:381
static LogicalResult verifyReduceOp(T op)
Definition TosaOps.cpp:3849
#define NARY_SHAPE_INFER(OP)
Definition TosaOps.cpp:3917
#define ZERO_POINT_HELPER(OP, OPERAND_NAME, SIGN_EXTEND)
Definition TosaOps.cpp:3099
static void buildTransConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value weight, Value bias, DenseI64ArrayAttr outpad, DenseI64ArrayAttr stride, TypeAttr accType)
Handles tosa.transpose_conv2d which has outpad and output shape attributes.
Definition TosaOps.cpp:1311
LogicalResult inferArgMaxMinReturnTypeComponents(MLIRContext *context, ::std::optional< Location > location, A adaptor, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:592
static void extractAdaptivePoolingConstShapeOperands(T op, AdaptivePoolingConstShapeValues &values)
Definition TosaOps.cpp:1180
static LogicalResult verifyConvOpErrorIf(T op)
Definition TosaOps.cpp:802
static FailureOr< int64_t > getZeroPoint(Value val, bool signExtend)
Definition TosaOps.cpp:3030
static constexpr bool IsSupportedAdaptivePoolConstShapeVerifyOp
Definition TosaOps.cpp:1173
LogicalResult tryUpdateDimOrFailure(Operation *op, int64_t &currDim, const int64_t newDim, const StringRef operandName, const StringRef dimName)
Definition TosaOps.cpp:337
static LogicalResult NAryInferReturnTypes(const ValueShapeRange &operands, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:3905
#define COMPATIBLE_RETURN_TYPES(OP)
Definition TosaOps.cpp:3815
static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands, SmallVector< int64_t > &outShape)
Definition TosaOps.cpp:1559
static LogicalResult verifyMatMulQuantizedOperandsType(T op, Type aElementType, Type bElementType)
Definition TosaOps.cpp:1988
static LogicalResult verifyOutputShapeCompatibleWithExpected(Operation *op, ShapedType outputType, ArrayRef< int64_t > expectedShape, StringRef outputName="output")
Definition TosaOps.cpp:365
static void printInputUnsigned(OpAsmPrinter &printer, Operation *, BoolAttr attr)
Definition TosaOps.cpp:5767
static void buildNegateOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input)
This builder is called on single-parameter negate operator to construct input and output zero points ...
Definition TosaOps.cpp:1461
static void buildConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value weight, Value bias, DenseI64ArrayAttr pad, DenseI64ArrayAttr stride, DenseI64ArrayAttr dilation, TypeAttr accType)
This builder is called on all convolution operators except TransposeConv, which has specialized outpu...
Definition TosaOps.cpp:1287
static SmallVector< int64_t > getMatMulBatchShape(const ShapeAdaptor &shape)
Definition TosaOps.cpp:2040
static void buildAvgPool2dOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, DenseArrayAttr kernel, DenseArrayAttr stride, DenseArrayAttr pad, TypeAttr accType)
Both the tosa.avg_pool2d and unary ops use the same UnaryOpQuantizationAttr but avg_pool operator has...
Definition TosaOps.cpp:1370
static LogicalResult errorIfTypeOrShapeMismatch(Operation *op, Type type1, StringRef name1, Type type2, StringRef name2)
Definition TosaOps.cpp:905
static LogicalResult inferMatMulReturnTypeComponents(const ShapeAdaptor &aShape, const ShapeAdaptor &bShape, bool transposeB, SmallVectorImpl< ShapedTypeComponents > &inferredReturnShapes)
Definition TosaOps.cpp:1946
static void buildMatMulTOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value a, Value b)
Definition TosaOps.cpp:1360
static FailureOr< SmallVector< int64_t > > resolveMatMulOutputShape(const ShapeAdaptor &aShape, const ShapeAdaptor &bShape, int64_t outputRank, bool transposeB)
Definition TosaOps.cpp:1919
static FailureOr< int64_t > resolveBroadcastDim(const int64_t dim1, const int64_t dim2)
Definition TosaOps.cpp:1545
static LogicalResult verifyZeroPoint(T op, Value val, const int64_t &zp, const std::string &operand)
Definition TosaOps.cpp:3057
static LogicalResult verifyPoolingOp(T op)
Definition TosaOps.cpp:1116
static LogicalResult verifyDimIsPowerOfTwo(Operation *op, const int64_t dimSize, const llvm::StringRef dimName)
Definition TosaOps.cpp:1636
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
static void updateIfDynamic(int64_t &current, int64_t candidate)
Definition TosaOps.cpp:4053
void inferWeightShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &weightSpatial)
Definition TosaOps.cpp:4146
LogicalResult getSpatialParameters(SmallVector< int64_t > &padValues, SmallVector< int64_t > &strideValues, SmallVector< int64_t > &dilationValues)
Definition TosaOps.cpp:4175
void inferInputShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &inputSpatial)
Definition TosaOps.cpp:4120
ConvInferShapeAdaptor(Conv2DBlockScaledOp::Adaptor adaptor)
Definition TosaOps.cpp:4117
void inferInputShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &inputSpatial)
Definition TosaOps.cpp:4066
void inferWeightShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &weightSpatial)
Definition TosaOps.cpp:4081
ConvInferShapeAdaptor(Conv2DOp::Adaptor adaptor)
Definition TosaOps.cpp:4063
LogicalResult getSpatialParameters(SmallVector< int64_t > &padValues, SmallVector< int64_t > &strideValues, SmallVector< int64_t > &dilationValues)
Definition TosaOps.cpp:4099
void inferWeightShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &weightSpatial)
Definition TosaOps.cpp:4216
ConvInferShapeAdaptor(Conv3DOp::Adaptor adaptor)
Definition TosaOps.cpp:4196
void inferInputShape(SmallVectorImpl< int64_t > &outputShape, SmallVectorImpl< int64_t > &inputSpatial)
Definition TosaOps.cpp:4199
LogicalResult getSpatialParameters(SmallVector< int64_t > &padValues, SmallVector< int64_t > &strideValues, SmallVector< int64_t > &dilationValues)
Definition TosaOps.cpp:4236
This base class exposes generic asm parser hooks, usable across the various derived parsers.
virtual ParseResult parseCommaSeparatedList(Delimiter delimiter, function_ref< ParseResult()> parseElementFn, StringRef contextMessage=StringRef())=0
Parse a list of comma-separated items with an optional delimiter.
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseOptionalEqual()=0
Parse a = token if present.
virtual ParseResult parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
MLIRContext * getContext() const
virtual ParseResult parseRParen()=0
Parse a ) token.
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
virtual ParseResult parseOptionalColon()=0
Parse a : token if present.
virtual ParseResult parseOptionalAttrDictWithKeyword(NamedAttrList &result)=0
Parse a named dictionary into 'result' if the attributes keyword is present.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual ParseResult parseColon()=0
Parse a : token.
virtual ParseResult parseLParen()=0
Parse a ( token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
virtual ParseResult parseFloat(double &result)=0
Parse a floating point value from the stream.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
This base class exposes generic asm printer hooks, usable across the various derived printers.
virtual void printAttributeWithoutType(Attribute attr)
Print the given attribute without its type.
virtual void printAttribute(Attribute attr)
void printArrowTypeList(TypeRange &&types)
Attributes are known-constant values of operations.
Definition Attributes.h:25
MutableArrayRef< BlockArgument > BlockArgListType
Definition Block.h:110
Special case of IntegerAttr to represent boolean integers, i.e., signless i1 integers.
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
FloatAttr getFloatAttr(Type type, double value)
Definition Builders.cpp:263
IntegerType getI32Type()
Definition Builders.cpp:71
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
DenseIntElementsAttr getIndexTensorAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:201
An attribute that represents a reference to a dense vector or tensor object.
auto getValues() const
Return the held element values as a range of the given type.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
An attribute that represents a reference to a dense integer vector or tensor object.
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
virtual InFlightDiagnostic emitError(const Twine &msg={}) const =0
Emit an error to the reader.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
This class represents a diagnostic that is inflight and set to be reported.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:71
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual OptionalParseResult parseOptionalAssignmentList(SmallVectorImpl< Argument > &lhs, SmallVectorImpl< UnresolvedOperand > &rhs)=0
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDictWithKeyword(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary prefixed with 'attribute...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
void printFunctionalType(Operation *op)
Print the complete type of an operation in functional form.
virtual void printRegion(Region &blocks, bool printEntryBlockArgs=true, bool printBlockTerminators=true, bool printEmptyBlock=false)=0
Prints a region.
This class helps build Operations.
Definition Builders.h:210
This class indicates that op operates on tosa shape types.
Definition TosaOps.h:73
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
ResultRange result_range
Support result iteration.
Definition Operation.h:437
bool hasTrait()
Returns true if the operation was registered with a particular trait, e.g.
Definition Operation.h:803
OperandRange operand_range
Definition Operation.h:398
operand_type_range getOperandTypes()
Definition Operation.h:424
result_type_range getResultTypes()
Definition Operation.h:455
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:405
InFlightDiagnostic emitOpError(const Twine &message={})
Emit an error with the op name prefixed, like "'dim' op " which is convenient for verifiers.
ParseResult value() const
Access the internal ParseResult value.
bool has_value() const
Returns true if we contain a valid ParseResult value.
Type-safe wrapper around a void* for passing properties, including the properties structs of operatio...
This class provides an abstraction over the different types of ranges over Regions.
Definition Region.h:363
bool empty()
Definition Region.h:60
This diagnostic handler is a simple RAII class that registers and erases a diagnostic handler on a gi...
Adaptor class to abstract the differences between whether value is from a ShapedType or ShapedTypeCom...
bool isDynamicDim(int index) const
Returns whether the index'th dimension is dynamic.
int64_t getDimSize(int index) const
Returns the size of the index'th dimension.
int64_t getRank() const
Returns the rank of the shape.
bool hasStaticShape() const
Returns whether the shape is fully static.
int64_t getNumElements() const
Returns the number of elements in the shape.
void getDims(SmallVectorImpl< int64_t > &res) const
Populates the dimensions from shape referenced.
bool hasRank() const
Returns whether the shape has a rank.
ShapedTypeComponents that represents the components of a ShapedType.
This class allows for representing and managing the symbol table used by operations with the 'SymbolT...
Definition SymbolTable.h:25
Operation * lookup(StringRef name) const
Look up a symbol with the specified name, returning null if no such name exists.
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.
bool hasRank() const
Returns if this type is ranked, i.e. it has a known number of dimensions.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
MLIRContext * getContext() const
Return the MLIRContext in which this type was uniqued.
Definition Types.cpp:35
bool isSignlessInteger() const
Return true if this is a signless integer type (with the specified width).
Definition Types.cpp:66
bool isF32() const
Definition Types.cpp:40
bool isUnsignedInteger() const
Return true if this is an unsigned integer type (with the specified width).
Definition Types.cpp:90
bool isInteger() const
Return true if this is an integer type (with the specified width).
Definition Types.cpp:58
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isBF16() const
Definition Types.cpp:37
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:392
type_range getTypes() const
Range of values and shapes (corresponding effectively to Shapes dialect's ValueShape type concept).
ShapeAdaptor getShape(int index) const
Returns the shape of index'th operand.
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
LogicalResult verifyAtLeastNOperands(Operation *op, unsigned numOperands)
LogicalResult verifyTosaShapeOperatorWithSameRanks(Operation *op)
Definition TosaOps.cpp:5585
LogicalResult verifyTosaResolvableShapeOperands(Operation *op)
Definition TosaOps.cpp:5572
bool getBroadcastedShape(ArrayRef< int64_t > shape1, ArrayRef< int64_t > shape2, SmallVectorImpl< int64_t > &resultShape)
Returns true and sets resultShape to the broadcasted shape from the two given shapes if they are broa...
Definition Traits.cpp:59
LogicalResult convertFloatTypeFromAttribute(Type type, Attribute attr, llvm::SmallVectorImpl< char > &result)
Float type implementation of DenseElementTypeInterface::convertFromAttribute.
Attribute convertFloatTypeToAttribute(Type type, llvm::ArrayRef< char > rawData)
Float type implementation of DenseElementTypeInterface::convertToAttribute.
Operation::operand_range getIndices(Operation *op)
Get the indices that the given load/store operation is operating on.
Definition Utils.cpp:18
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:853
SmallVector< unsigned > getBlockSize(AffineMap dimToLvl)
Given the dimToLvl map, returns the block sizes in a vector.
ConvOpQuantizationAttr buildConvOpQuantizationAttr(OpBuilder &builder, Value input, Value weight)
Method to build ConvOpQuantizationAttr, called from ConvOpQuantInfoBuilder/TransConvOpQuantInfoBuilde...
Type getStorageElementTypeOrSelf(Type type)
Definition TosaOps.cpp:281
RankedTensorType getVariableType(VariableOp variableOp)
Type buildConvOpResultTypeInfo(OpBuilder &builder, Type outputType, Value input, Value weight)
construct ConvOp output type with correct bitwidth based on input/weight width.
ParseResult parseVariableOpTypeOrInitialValue(OpAsmParser &parser, DenseElementsAttr &varShapeAttr, TypeAttr &typeAttr, Attribute &initialValueAttr)
Definition TosaOps.cpp:223
PadOpQuantizationAttr buildPadOpQuantizationAttr(OpBuilder &builder, Value input)
Builds PadOpQuantizationAttr, called from PadOpQuantInfoBuilder: inputZp: input zeropoint.
constexpr int64_t kInferableDimSize
Represents a dimension in the shape of a tensor that can be inferred based on the other provided dime...
Definition TosaOps.h:97
std::pair< Value, Value > createZPsAsConst(OpBuilder &builder, Value input, Value weight)
void printVariableOpTypeOrInitialValue(OpAsmPrinter &p, Operation *op, DenseElementsAttr varShapeAttr, TypeAttr typeAttr, Attribute initialValueAttr)
Definition TosaOps.cpp:248
FailureOr< T > getConstantScalarIntValue(Value val)
Value getTosaConstShape(ImplicitLocOpBuilder &builder, llvm::ArrayRef< int64_t > shape)
MatMulOpQuantizationAttr buildMatMulOpQuantizationAttr(OpBuilder &builder, Value a, Value b)
Builds MatMulOpQuantizationAttr, called from MatMulOpQuantInfoBuilder: aZp: input a zeropoint bZp: in...
unsigned getBitWidth(Type type)
Definition TosaOps.cpp:327
std::optional< Value > createZeroPointTensor(OpBuilder &builder, Location loc, Type srcElemType, int64_t zp=0)
Definition TosaOps.cpp:5536
bool isa_tosa_shape_type(mlir::Type t)
Definition TosaOps.cpp:5560
SmallVector< int64_t > convertFromMlirShape(ArrayRef< int64_t > shape)
UnaryOpQuantizationAttr buildUnaryOpQuantizationAttr(OpBuilder &builder, Value input, Type outputRawType)
Builds UnaryOpQuantizationAttr UnaryOpQuantInfoBuilder: inputZp: input zeropoint outputZp: output zer...
Type getStorageElementTypeFromQuantized(quant::QuantizedType quantizedType)
Value createPadConstTensor(OpBuilder &builder, Location loc, Value src, int32_t val=0)
Definition TosaOps.cpp:312
LogicalResult verifyBlockScaledTensorType(mlir::Type type, llvm::function_ref< mlir::InFlightDiagnostic()> emitError=nullptr, bool allowScaleValues=false)
Definition TosaOps.cpp:440
std::string getTosaTensorTypeErrorMessage(mlir::Type type)
Definition TosaOps.cpp:499
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
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
LogicalResult verifyCompatibleShapes(TypeRange types1, TypeRange types2)
Returns success if the given two arrays have the same number of elements and each pair wise entries h...
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
LogicalResult emitOptionalError(std::optional< Location > loc, Args &&...args)
Overloads of the above emission functions that take an optionally null location.
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
LogicalResult verifyCompatibleDims(ArrayRef< int64_t > dims)
Dimensions are compatible if all non-dynamic dims are equal.
LogicalResult verifyRanksMatch(Operation *op, ShapedType lhs, ShapedType rhs, StringRef lhsName, StringRef rhsName)
Verify that two shaped types have matching ranks.
LogicalResult verifyCompatibleShape(ArrayRef< int64_t > shape1, ArrayRef< int64_t > shape2)
Returns success if the given two shapes are compatible.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
This represents an operation in an abstracted form, suitable for use with the builder APIs.
static ValueKnowledge meet(const ValueKnowledge &lhs, const ValueKnowledge &rhs)
Definition ShapeUtils.h:136
static ValueKnowledge getKnowledgeFromType(Type type)
Definition ShapeUtils.h:45