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