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