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