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