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