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