brintos

brintos / llvm-project-archived public Read only

0
0
Text · 175.1 KiB · 1c175f9 Raw
4686 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// \file10// This file implements the TOSA Specification:11// https://www.mlplatform.org/tosa/tosa_spec.html12//13//===----------------------------------------------------------------------===//14 15#include "mlir/Dialect/Tosa/IR/TosaOps.h"16#include "mlir/Dialect/Quant/IR/Quant.h"17#include "mlir/Dialect/Shard/Interfaces/ShardingInterface.h"18#include "mlir/Dialect/Tensor/IR/Tensor.h"19#include "mlir/Dialect/Tosa/Utils/QuantUtils.h"20#include "mlir/Dialect/Tosa/Utils/ShapeUtils.h"21#include "mlir/Dialect/Utils/IndexingUtils.h"22#include "mlir/IR/BuiltinTypes.h"23#include "mlir/IR/DialectImplementation.h"24#include "mlir/IR/Matchers.h"25#include "mlir/IR/TypeUtilities.h"26#include "mlir/Interfaces/InferTypeOpInterface.h"27#include "mlir/Transforms/InliningUtils.h"28#include "llvm/ADT/APFloat.h"29#include "llvm/ADT/TypeSwitch.h"30 31#include <numeric>32 33using namespace mlir;34using namespace mlir::tosa;35 36#include "mlir/Dialect/Tosa/IR/TosaOpsDialect.cpp.inc"37#include "mlir/Dialect/Tosa/Utils/ConversionUtils.h"38 39//===----------------------------------------------------------------------===//40// Tosa dialect interface includes.41//===----------------------------------------------------------------------===//42 43#include "mlir/Dialect/Tosa/IR/TosaAvailability.cpp.inc"44#include "mlir/Dialect/Tosa/IR/TosaEnums.cpp.inc"45#include "mlir/Dialect/Tosa/IR/TosaInterfaces.cpp.inc"46#include "mlir/Dialect/Tosa/IR/TosaOpAvailabilityImpl.inc"47 48namespace {49#include "mlir/Dialect/Tosa/IR/TosaDialectBytecode.cpp.inc"50 51//===----------------------------------------------------------------------===//52// Dialect Function Inliner Interface.53//===----------------------------------------------------------------------===//54struct TosaInlinerInterface : public DialectInlinerInterface {55  using DialectInlinerInterface::DialectInlinerInterface;56 57  //===--------------------------------------------------------------------===//58  // Analysis Hooks.59  //===--------------------------------------------------------------------===//60 61  /// All operations can be inlined by default.62  bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned,63                       IRMapping &map) const final {64    return true;65  }66 67  /// All regions with If and While parent operators can be inlined.68  bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned,69                       IRMapping &map) const final {70    return (isa<tosa::IfOp>(dest->getParentOp()) ||71            isa<tosa::WhileOp>(dest->getParentOp()));72  }73};74 75/// This class implements the bytecode interface for the Tosa dialect.76struct TosaDialectBytecodeInterface : public BytecodeDialectInterface {77  TosaDialectBytecodeInterface(Dialect *dialect)78      : BytecodeDialectInterface(dialect) {}79 80  //===--------------------------------------------------------------------===//81  // Attributes82 83  Attribute readAttribute(DialectBytecodeReader &reader) const override {84    return ::readAttribute(getContext(), reader);85  }86 87  LogicalResult writeAttribute(Attribute attr,88                               DialectBytecodeWriter &writer) const override {89    return ::writeAttribute(attr, writer);90  }91 92  //===--------------------------------------------------------------------===//93  // Types94 95  Type readType(DialectBytecodeReader &reader) const override {96    return ::readType(getContext(), reader);97  }98 99  LogicalResult writeType(Type type,100                          DialectBytecodeWriter &writer) const override {101    return ::writeType(type, writer);102  }103 104  void writeVersion(DialectBytecodeWriter &writer) const final {105    // TODO: Populate.106  }107 108  std::unique_ptr<DialectVersion>109  readVersion(DialectBytecodeReader &reader) const final {110    // TODO: Populate111    reader.emitError("Dialect does not support versioning");112    return nullptr;113  }114 115  LogicalResult upgradeFromVersion(Operation *topLevelOp,116                                   const DialectVersion &version) const final {117    return success();118  }119};120 121} // namespace122 123//===----------------------------------------------------------------------===//124// TOSA control flow support.125//===----------------------------------------------------------------------===//126 127/// Returns the while loop body.128SmallVector<Region *> tosa::WhileOp::getLoopRegions() {129  return {&getBodyGraph()};130}131 132//===----------------------------------------------------------------------===//133// TOSA variable operator support.134//===----------------------------------------------------------------------===//135 136static SmallVector<int64_t> convertToMlirShape(ArrayRef<int64_t> shape) {137  return to_vector(llvm::map_range(shape, [](int64_t dim) {138    return dim == -1 ? ShapedType::kDynamic : dim;139  }));140}141 142// returns type of variable op143RankedTensorType mlir::tosa::getVariableType(tosa::VariableOp variableOp) {144  Type elementType = variableOp.getType();145  DenseIntElementsAttr varShapeAttr = variableOp.getVarShape();146  auto shape = convertToMlirShape(to_vector(varShapeAttr.getValues<int64_t>()));147  return RankedTensorType::get(shape, elementType);148}149 150//===----------------------------------------------------------------------===//151// Tosa dialect initialization.152//===----------------------------------------------------------------------===//153 154void TosaDialect::initialize() {155  addTypes<156#define GET_TYPEDEF_LIST157#include "mlir/Dialect/Tosa/IR/TosaOpsTypesBase.cpp.inc"158      >();159  addOperations<160#define GET_OP_LIST161#include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"162      >();163  addAttributes<164#define GET_ATTRDEF_LIST165#include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc"166      >();167  addInterfaces<TosaDialectBytecodeInterface, TosaInlinerInterface>();168  declarePromisedInterfaces<169      shard::ShardingInterface, ClampOp, SigmoidOp, TanhOp, AddOp,170      ArithmeticRightShiftOp, BitwiseAndOp, BitwiseOrOp, BitwiseXorOp, IntDivOp,171      LogicalAndOp, LogicalLeftShiftOp, LogicalRightShiftOp, LogicalOrOp,172      LogicalXorOp, MaximumOp, MinimumOp, MulOp, PowOp, SubOp, AbsOp,173      BitwiseNotOp, CeilOp, ClzOp, ExpOp, FloorOp, LogOp, LogicalNotOp,174      NegateOp, ReciprocalOp, RsqrtOp, SelectOp, EqualOp, GreaterOp,175      GreaterEqualOp, MatMulOp>();176}177 178Operation *TosaDialect::materializeConstant(OpBuilder &builder, Attribute value,179                                            Type type, Location loc) {180  // Tosa dialect constants only support ElementsAttr unlike standard dialect181  // constant which supports all attributes.182  if (llvm::isa<shapeType>(type) && llvm::isa<DenseIntElementsAttr>(value)) {183    return tosa::ConstShapeOp::create(builder, loc, type,184                                      llvm::cast<DenseIntElementsAttr>(value));185  }186  if (llvm::isa<ElementsAttr>(value))187    return tosa::ConstOp::create(builder, loc, type,188                                 llvm::cast<ElementsAttr>(value));189  return nullptr;190}191 192//===----------------------------------------------------------------------===//193// Parsers and printers194//===----------------------------------------------------------------------===//195 196namespace {197 198ParseResult getShapeAndElementType(OpAsmParser &parser, Type parsedType,199                                   DenseElementsAttr &varShapeAttr,200                                   TypeAttr &typeAttr) {201  if (auto shapedType = dyn_cast<ShapedType>(parsedType)) {202    if (!shapedType.hasRank())203      return parser.emitError(parser.getCurrentLocation())204             << "expected ranked type";205 206    auto elementType = shapedType.getElementType();207    typeAttr = TypeAttr::get(elementType);208    ArrayRef<int64_t> shape = shapedType.getShape();209    Builder builder(parser.getContext());210    varShapeAttr = builder.getIndexTensorAttr(convertFromMlirShape(shape));211    return success();212  }213  return parser.emitError(parser.getCurrentLocation())214         << "expected shaped type";215}216 217} // namespace218 219// parses the optional initial value or type for a tosa variable220//  with initial value:221//    tosa.variable @name = dense<0.0> : tensor<1x8xf32>222//223//  without initial value:224//    tosa.variable @name : tensor<1x8xf32>225ParseResult mlir::tosa::parseVariableOpTypeOrInitialValue(226    OpAsmParser &parser, DenseElementsAttr &varShapeAttr, TypeAttr &typeAttr,227    Attribute &initialValueAttr) {228  if (succeeded(parser.parseOptionalEqual())) {229    if (failed(parser.parseAttribute(initialValueAttr))) {230      return parser.emitError(parser.getCurrentLocation())231             << "expected attribute";232    }233    if (auto typedAttr = dyn_cast<TypedAttr>(initialValueAttr)) {234      return getShapeAndElementType(parser, typedAttr.getType(), varShapeAttr,235                                    typeAttr);236    }237    return parser.emitError(parser.getCurrentLocation())238           << "expected Typed attr";239  }240 241  initialValueAttr = nullptr;242  Type parsedType;243  if (failed(parser.parseColonType(parsedType))) {244    return parser.emitError(parser.getCurrentLocation())245           << "expected type after colon";246  }247  return getShapeAndElementType(parser, parsedType, varShapeAttr, typeAttr);248}249 250void mlir::tosa::printVariableOpTypeOrInitialValue(251    OpAsmPrinter &p, Operation *op, DenseElementsAttr varShapeAttr,252    TypeAttr typeAttr, Attribute initialValueAttr) {253  bool needsSpace = false;254  if (!dyn_cast_or_null<TypedAttr>(initialValueAttr)) {255    auto shape =256        convertToMlirShape(to_vector(varShapeAttr.getValues<int64_t>()));257    Type elementType = typeAttr.getValue();258    RankedTensorType tensorType =259        RankedTensorType::get(ArrayRef<int64_t>(shape), elementType);260    auto tensorTypeAttr = TypeAttr::get(tensorType);261    p << ": ";262    p.printAttribute(tensorTypeAttr);263    needsSpace = true; // subsequent attr value needs a space separator264  }265  if (initialValueAttr) {266    if (needsSpace)267      p << ' ';268    p << "= ";269    p.printAttribute(initialValueAttr);270  }271}272 273namespace {274 275// parse attributes with special handling for tosa enum attributes276template <typename EnumType>277ParseResult parseAttrEntryWithEnumHandling(OpAsmParser &parser,278                                           NamedAttrList &outAttrs) {279  llvm::StringRef name;280  if (parser.parseOptionalKeyword(&name) || parser.parseEqual())281    return failure();282 283  // special handling: rounding_mode accepts a *bare* RoundingMode enum284  // keyword.285  llvm::StringRef kw;286  if constexpr (std::is_same_v<EnumType, tosa::RoundingMode>) {287    if (name == "rounding_mode" &&288        succeeded(parser.parseOptionalKeyword(&kw))) {289      auto sym = symbolizeRoundingMode(kw);290      if (!sym)291        return parser.emitError(parser.getCurrentLocation())292               << "invalid rounding_mode value: " << kw;293      auto attr = RoundingModeAttr::get(parser.getContext(), sym.value());294      outAttrs.push_back(NamedAttribute(name, attr));295      return success();296    }297  }298  // special handling: mode accepts a *bare* ResizeMode enum keyword.299  if constexpr (std::is_same_v<EnumType, tosa::ResizeMode>) {300    if (name == "mode" && succeeded(parser.parseOptionalKeyword(&kw))) {301      auto sym = symbolizeResizeMode(kw);302      if (!sym)303        return parser.emitError(parser.getCurrentLocation())304               << "invalid resize mode value: " << kw;305      auto attr = ResizeModeAttr::get(parser.getContext(), sym.value());306      outAttrs.push_back(NamedAttribute(name, attr));307      return success();308    }309  }310  // special handling: nan_mode accepts a *bare* NanPropagationMode enum311  // keyword.312  if constexpr (std::is_same_v<EnumType, tosa::NanPropagationMode>) {313    if (name == "nan_mode" && succeeded(parser.parseOptionalKeyword(&kw))) {314      auto sym = symbolizeNanPropagationMode(kw);315      if (!sym)316        return parser.emitError(parser.getCurrentLocation())317               << "invalid nan_mode value: " << kw;318      auto attr = NanPropagationModeAttr::get(parser.getContext(), sym.value());319      outAttrs.push_back(NamedAttribute(name, attr));320      return success();321    }322  }323 324  // special handling: block_size accepts a *bare* BlockSizeMode enum325  if constexpr (std::is_same_v<EnumType, tosa::BlockSize>) {326    if (name == "block_size" && succeeded(parser.parseOptionalKeyword(&kw))) {327      auto sym = symbolizeBlockSize(kw);328      if (!sym)329        return parser.emitError(parser.getCurrentLocation())330               << "invalid block_size value: " << kw;331      auto attr = BlockSizeAttr::get(parser.getContext(), sym.value());332      outAttrs.push_back(NamedAttribute(name, attr));333      return success();334    }335  }336 337  // Default path: parse any normal attribute literal, including fully qualified338  // enum keyword339  Attribute attr;340  return parser.parseAttribute(attr, name, outAttrs);341}342 343template <typename EnumType>344ParseResult parseWithEnumHandling(OpAsmParser &parser, OperationState &result) {345  // parse operands346  SmallVector<OpAsmParser::UnresolvedOperand, 5> operands;347  if (parser.parseCommaSeparatedList(348          [&]() { return parser.parseOperand(operands.emplace_back()); }))349    return failure();350 351  // Parse { attr-dict } with special handling for enum bare token352  NamedAttrList attrs;353  if (succeeded(parser.parseOptionalLBrace()) &&354      failed(parser.parseOptionalRBrace())) {355    do {356      if (parseAttrEntryWithEnumHandling<EnumType>(parser, attrs))357        return failure();358    } while (succeeded(parser.parseOptionalComma()));359    if (parser.parseRBrace())360      return failure();361  }362 363  FunctionType fnTy;364  if (parser.parseColonType(fnTy))365    return failure();366 367  // Resolve operands and types368  if (failed(parser.resolveOperands(operands, fnTy.getInputs(),369                                    parser.getCurrentLocation(),370                                    result.operands)))371    return failure();372 373  result.addTypes(fnTy.getResults());374  result.addAttributes(attrs);375 376  return success();377}378 379void printNamedAttr(OpAsmPrinter &parser, const NamedAttribute namedAttr) {380  parser << namedAttr.getName().strref() << " = ";381  auto attr = namedAttr.getValue();382  if (auto roundingModeAttr = dyn_cast<tosa::RoundingModeAttr>(attr)) {383    parser << roundingModeAttr.getValue();384  } else if (auto resizeModeAttr = dyn_cast<tosa::ResizeModeAttr>(attr)) {385    parser << resizeModeAttr.getValue();386  } else if (auto nanPropagationModeAttr =387                 dyn_cast<tosa::NanPropagationModeAttr>(attr)) {388    parser << nanPropagationModeAttr.getValue();389  } else if (auto blockSizeAttr = dyn_cast<tosa::BlockSizeAttr>(attr)) {390    parser << blockSizeAttr.getValue();391  } else {392    parser.printAttribute(attr);393  }394}395 396// print with special handling for default valued NanPropagationMode attribute397void printWithNanPropagationHandling(OpAsmPrinter &parser, Operation *op) {398  parser << " ";399  parser.printOperands(op->getOperands());400 401  NamedAttrList toPrint(op->getAttrs());402  // remove default NanPropagate attribute403  const auto kDefaultNanValue = NanPropagationMode::PROPAGATE;404  for (auto attr : op->getAttrs()) {405    if (auto nanAttr = dyn_cast<NanPropagationModeAttr>(attr.getValue())) {406      if (nanAttr.getValue() == kDefaultNanValue) {407        // elide from toPrint408        toPrint.erase(attr.getName());409        break;410      }411    }412  }413 414  if (!toPrint.empty()) {415    parser << " {";416    llvm::interleaveComma(toPrint, parser, [&](const NamedAttribute namedAttr) {417      printNamedAttr(parser, namedAttr);418    });419    parser << "}";420  }421 422  parser << " : ";423  parser.printFunctionalType(op);424}425 426// print with special handling for enums: RoundingMode, ResizeMode427void printWithEnumHandling(OpAsmPrinter &parser, Operation *op) {428  parser << " ";429  parser.printOperands(op->getOperands());430 431  if (!op->getAttrs().empty()) {432    parser << " {";433    llvm::interleaveComma(op->getAttrs(), parser,434                          [&](const NamedAttribute namedAttr) {435                            printNamedAttr(parser, namedAttr);436                          });437    parser << "}";438  }439 440  parser << " : ";441  parser.printFunctionalType(op);442}443 444} // namespace445 446ParseResult RescaleOp::parse(OpAsmParser &parser, OperationState &result) {447  return parseWithEnumHandling<tosa::RoundingMode>(parser, result);448}449 450void RescaleOp::print(OpAsmPrinter &parser) {451  printWithEnumHandling(parser, *this);452}453 454ParseResult ApplyScaleOp::parse(OpAsmParser &parser, OperationState &result) {455  return parseWithEnumHandling<tosa::RoundingMode>(parser, result);456}457 458void ApplyScaleOp::print(OpAsmPrinter &parser) {459  printWithEnumHandling(parser, *this);460}461 462ParseResult ResizeOp::parse(OpAsmParser &parser, OperationState &result) {463  return parseWithEnumHandling<tosa::ResizeMode>(parser, result);464}465 466void ResizeOp::print(OpAsmPrinter &parser) {467  printWithEnumHandling(parser, *this);468}469 470ParseResult ArgMaxOp::parse(OpAsmParser &parser, OperationState &result) {471  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);472}473 474void ArgMaxOp::print(OpAsmPrinter &parser) {475  printWithNanPropagationHandling(parser, *this);476}477 478ParseResult MaxPool2dOp::parse(OpAsmParser &parser, OperationState &result) {479  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);480}481 482void MaxPool2dOp::print(OpAsmPrinter &parser) {483  printWithNanPropagationHandling(parser, *this);484}485 486ParseResult ClampOp::parse(OpAsmParser &parser, OperationState &result) {487  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);488}489 490void ClampOp::print(OpAsmPrinter &parser) {491  printWithNanPropagationHandling(parser, *this);492}493 494ParseResult MaximumOp::parse(OpAsmParser &parser, OperationState &result) {495  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);496}497 498void MaximumOp::print(OpAsmPrinter &parser) {499  printWithNanPropagationHandling(parser, *this);500}501 502ParseResult MinimumOp::parse(OpAsmParser &parser, OperationState &result) {503  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);504}505 506void MinimumOp::print(OpAsmPrinter &parser) {507  printWithNanPropagationHandling(parser, *this);508}509 510ParseResult ReduceMaxOp::parse(OpAsmParser &parser, OperationState &result) {511  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);512}513 514void ReduceMaxOp::print(OpAsmPrinter &parser) {515  printWithNanPropagationHandling(parser, *this);516}517 518ParseResult ReduceMinOp::parse(OpAsmParser &parser, OperationState &result) {519  return parseWithEnumHandling<tosa::NanPropagationMode>(parser, result);520}521 522void ReduceMinOp::print(OpAsmPrinter &parser) {523  printWithNanPropagationHandling(parser, *this);524}525 526ParseResult MatmulTBlockScaledOp::parse(OpAsmParser &parser,527                                        OperationState &result) {528  return parseWithEnumHandling<tosa::BlockSize>(parser, result);529}530 531void MatmulTBlockScaledOp::print(OpAsmPrinter &parser) {532  printWithEnumHandling(parser, *this);533}534 535ParseResult CastFromBlockScaledOp::parse(OpAsmParser &parser,536                                         OperationState &result) {537  return parseWithEnumHandling<tosa::BlockSize>(parser, result);538}539 540void CastFromBlockScaledOp::print(OpAsmPrinter &parser) {541  printWithEnumHandling(parser, *this);542}543 544ParseResult CastToBlockScaledOp::parse(OpAsmParser &parser,545                                       OperationState &result) {546  return parseWithEnumHandling<tosa::BlockSize>(parser, result);547}548 549void CastToBlockScaledOp::print(OpAsmPrinter &parser) {550  printWithEnumHandling(parser, *this);551}552 553//===----------------------------------------------------------------------===//554// Tosa utilities.555//===----------------------------------------------------------------------===//556 557static std::optional<int64_t> idivCheck(const int64_t lhs, const int64_t rhs) {558  if (lhs % rhs != 0)559    return std::nullopt;560  return lhs / rhs;561}562 563static Type getStorageElementTypeOrSelf(Type type) {564  auto srcType = getElementTypeOrSelf(type);565  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(srcType))566    srcType = getStorageElementTypeFromQuantized(quantType);567  return srcType;568}569 570static Type getStorageElementTypeOrSelf(Value value) {571  return getStorageElementTypeOrSelf(value.getType());572}573 574static LogicalResult verifyRescaleValueAndZpTypes(Operation *op, Value val,575                                                  Value valZp, StringRef name) {576  Type eType = getStorageElementTypeOrSelf(val.getType());577  Type eZpType = getStorageElementTypeOrSelf(valZp.getType());578 579  bool bothInts =580      mlir::isa<IntegerType>(eType) && mlir::isa<IntegerType>(eZpType);581  bool sameBitWidth =582      (eType.getIntOrFloatBitWidth() == eZpType.getIntOrFloatBitWidth());583 584  if (!bothInts || !sameBitWidth) {585    return op->emitOpError()586           << "expected " << name << " and " << name587           << "_zp to both be integer of the same bitwidth, but got " << eType588           << " vs. " << eZpType;589  }590  return success();591}592 593// Create a pad-const const tensor with value of `val` of required data-type594Value mlir::tosa::createPadConstTensor(OpBuilder &builder, Location loc,595                                       Value src, int32_t val) {596  const auto srcType = getElementTypeOrSelf(src);597  const auto srcElemType = getStorageElementTypeOrSelf(src);598  const auto padConstType = mlir::RankedTensorType::get({1}, srcType);599  const auto padConstEType = mlir::RankedTensorType::get({1}, srcElemType);600  const auto padConstAttr{601      llvm::isa<FloatType>(srcElemType)602          ? DenseElementsAttr::get(padConstEType,603                                   builder.getFloatAttr(srcElemType, val))604          : DenseElementsAttr::get(padConstEType,605                                   builder.getIntegerAttr(srcElemType, val))};606  return tosa::ConstOp::create(builder, loc, padConstType, padConstAttr);607}608 609unsigned mlir::tosa::getBitWidth(Type type) {610  if (dyn_cast<tosa::mxint8Type>(type))611    return 8;612  return type.getIntOrFloatBitWidth();613}614 615//===----------------------------------------------------------------------===//616// TOSA Operator Verifiers.617//===----------------------------------------------------------------------===//618 619template <typename T>620static LogicalResult verifyConvOp(T op) {621  const auto inputType = llvm::dyn_cast<TensorType>(op.getInput().getType());622  const auto weightType = llvm::dyn_cast<TensorType>(op.getWeight().getType());623 624  auto inputEType = inputType.getElementType();625  auto weightEType = weightType.getElementType();626  auto biasEType =627      llvm::cast<ShapedType>(op.getBias().getType()).getElementType();628  auto resultEType =629      llvm::cast<ShapedType>(op.getResult().getType()).getElementType();630  bool biasIsFloat = llvm::isa<FloatType>(biasEType);631  bool resultIsFloat = llvm::isa<FloatType>(resultEType);632 633  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(inputEType))634    inputEType = getStorageElementTypeFromQuantized(quantType);635 636  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(weightEType))637    weightEType = getStorageElementTypeFromQuantized(quantType);638 639  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(biasEType))640    biasEType = getStorageElementTypeFromQuantized(quantType);641 642  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(resultEType))643    resultEType = getStorageElementTypeFromQuantized(quantType);644 645  if (biasIsFloat && resultIsFloat && (biasEType != resultEType)) {646    // for now, only enforce bias element type == result element type for647    // float types.648    op.emitOpError(649        "expect both bias and result to have same element type, got ")650        << biasEType << " and " << resultEType;651    return failure();652  }653 654  if (isa<Float8E5M2Type>(inputEType) || isa<Float8E4M3FNType>(inputEType) ||655      isa<Float8E5M2Type>(weightEType) || isa<Float8E4M3FNType>(weightEType)) {656    if (inputEType != weightEType) {657      op.emitOpError(658          "expect both input and weight to have same element type, got ")659          << inputEType << " and " << weightEType;660      return failure();661    }662  }663 664  bool inputIsFloat = llvm::isa<FloatType>(inputEType);665  bool weightIsFloat = llvm::isa<FloatType>(weightEType);666 667  // Either both must be float or both non-float.668  if (inputIsFloat != weightIsFloat) {669    op.emitOpError(670        "expect both input and weight to be float or not together, got ")671        << inputEType << " and " << weightEType;672    return failure();673  }674 675  auto inputZpEType = getStorageElementTypeOrSelf(op.getInputZp().getType());676  if (inputEType != inputZpEType) {677    return op.emitOpError("expect both input and its zero point are the same "678                          "element type, got ")679           << inputEType << " and " << inputZpEType;680  }681 682  auto weightZpEType = getStorageElementTypeOrSelf(op.getWeightZp().getType());683  if (weightEType != weightZpEType) {684    return op.emitOpError("expect both weight and its zero point are the same "685                          "element type, got ")686           << weightEType << " and " << weightZpEType;687  }688 689  FailureOr<int64_t> maybeIZp = op.getInputZeroPoint();690  if (succeeded(maybeIZp) && op.verifyInputZeroPoint(*maybeIZp).failed())691    return failure();692 693  FailureOr<int64_t> maybeWZp = op.getWeightZeroPoint();694  if (succeeded(maybeWZp) && op.verifyWeightZeroPoint(*maybeWZp).failed())695    return failure();696 697  return success();698}699 700LogicalResult tosa::ConstOp::verify() {701 702  auto attrType = llvm::dyn_cast<TensorType>(getValuesAttr().getType());703  auto outputType = llvm::dyn_cast<TensorType>(getOutput().getType());704 705  if (!attrType || !outputType) {706    emitOpError("expected tensors for attr/result type");707    return failure();708  }709 710  if (auto result = llvm::dyn_cast<mlir::quant::QuantizedType>(711          outputType.getElementType())) {712    if (getStorageElementTypeFromQuantized(result) == attrType.getElementType())713      return success();714  }715 716  if (attrType.getElementType() != outputType.getElementType()) {717    emitOpError("expected same attr/result element types");718    return failure();719  }720 721  return success();722}723 724template <typename T>725static LogicalResult verifyConvOpModes(T op) {726  auto inputEType =727      llvm::cast<ShapedType>(op.getInput().getType()).getElementType();728 729  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(inputEType))730    inputEType = getStorageElementTypeFromQuantized(quantType);731 732  auto accType = op.getAccType();733  if (inputEType.isInteger(8) && !accType.isInteger(32))734    return op.emitOpError("accumulator type for i8 tensor is not i32");735 736  if (inputEType.isInteger(16) && !accType.isInteger(48))737    return op.emitOpError("accumulator type for i16 tensor is not i48");738 739  if (isa<Float8E5M2Type, Float8E4M3Type>(inputEType) && !accType.isF16())740    return op.emitOpError("accumulator type for f8 tensor is not f16");741 742  if (inputEType.isF16() && !(accType.isF16() || accType.isF32()))743    return op.emitOpError("accumulator type for f16 tensor is not f16/f32");744 745  if (inputEType.isBF16() && !accType.isF32())746    return op.emitOpError("accumulator type for bf16 tensor is not f32");747 748  if (inputEType.isF32() && !accType.isF32())749    return op.emitOpError("accumulator type for f32 tensor is not f32");750 751  auto resultEType =752      llvm::cast<ShapedType>(op.getResult().getType()).getElementType();753 754  if (auto quantType = llvm::dyn_cast<mlir::quant::QuantizedType>(resultEType))755    resultEType = getStorageElementTypeFromQuantized(quantType);756 757  return success();758}759 760//===----------------------------------------------------------------------===//761// ERROR_IF functions.762// ERROR_IF is a predicate that must set an error if the condition holds.763//===----------------------------------------------------------------------===//764 765template <typename T>766static LogicalResult verifyConvOpErrorIf(T op) {767  llvm::ArrayRef<int64_t> padding = op.getPad();768  if (llvm::any_of(padding, [](int64_t p) { return p < 0; }))769    return op.emitOpError("expect all padding values to be >= 0, got ")770           << padding;771 772  llvm::ArrayRef<int64_t> strides = op.getStride();773  if (llvm::any_of(strides, [](int64_t s) { return s < 1; }))774    return op.emitOpError("expect all stride values to be >= 1, got ")775           << strides;776 777  llvm::ArrayRef<int64_t> dilations = op.getDilation();778  if (llvm::any_of(dilations, [](int64_t d) { return d < 1; }))779    return op.emitOpError("expect all dilation values to be >= 1, got ")780           << dilations;781 782  const RankedTensorType outputType =783      llvm::dyn_cast<RankedTensorType>(op.getOutput().getType());784  if (!outputType)785    // Skip following checks if output is not ranked786    return success();787 788  const RankedTensorType inputType =789      llvm::dyn_cast<RankedTensorType>(op.getInput().getType());790  const RankedTensorType weightType =791      llvm::dyn_cast<RankedTensorType>(op.getWeight().getType());792 793  if (inputType && weightType) {794    const auto verifyOutputSize =795        [&op](const int64_t inputSize, const int64_t kernelSize,796              const int64_t outputSize, const int64_t padBefore,797              const int64_t padAfter, const int64_t stride,798              const int64_t dilation, const llvm::StringRef dimName,799              const llvm::StringRef dimAxis,800              const llvm::StringRef padBeforeName,801              const llvm::StringRef padAfterName) -> LogicalResult {802      if (inputSize == ShapedType::kDynamic ||803          kernelSize == ShapedType::kDynamic)804        return success();805 806      // ERROR_IF: O != idiv_check(I - 1 + pa + pb - (K - 1) * d, s) + 1807 808      const std::optional<int64_t> calculatedOutSizeMinusOne = idivCheck(809          inputSize - 1 + padBefore + padAfter - (kernelSize - 1) * dilation,810          stride);811      if (!calculatedOutSizeMinusOne.has_value())812        return op.emitOpError("expected input_")813               << dimName << " - 1 + pad_" << padBeforeName << " + pad_"814               << padAfterName << " - (kernel_" << dimName815               << " - 1) * dilation_" << dimAxis816               << " to be wholly divisible by stride_" << dimAxis << ", got ("817               << inputSize << " - 1 + " << padBefore << " + " << padAfter818               << " - (" << kernelSize << " - 1) * " << dilation << ") / "819               << stride;820 821      const int64_t calculatedOutSize = calculatedOutSizeMinusOne.value() + 1;822      if (outputSize != ShapedType::kDynamic && calculatedOutSize != outputSize)823        return op.emitOpError("calculated output ")824               << dimName << " did not match expected: "825               << "calculated=" << calculatedOutSize826               << ", expected=" << outputSize;827 828      return success();829    };830 831    // input = [_,IH,IW,_], weight = [_,KH,KW,_], output = [_,OH,OW,_]832    if constexpr (std::is_same<T, tosa::Conv2DOp>::value) {833      if (failed(verifyOutputSize(834              inputType.getDimSize(1), weightType.getDimSize(1),835              outputType.getDimSize(1), padding[0], padding[1], strides[0],836              dilations[0], "height", "y", "top", "bottom")))837        return failure();838 839      if (failed(verifyOutputSize(840              inputType.getDimSize(2), weightType.getDimSize(2),841              outputType.getDimSize(2), padding[2], padding[3], strides[1],842              dilations[1], "width", "x", "left", "right")))843        return failure();844    }845 846    // input = [_,IH,IW,_], weight = [KH,KW,_,_], output = [_,OH,OW,_]847    if constexpr (std::is_same<T, tosa::DepthwiseConv2DOp>::value) {848      if (failed(verifyOutputSize(849              inputType.getDimSize(1), weightType.getDimSize(0),850              outputType.getDimSize(1), padding[0], padding[1], strides[0],851              dilations[0], "height", "y", "top", "bottom")))852        return failure();853 854      if (failed(verifyOutputSize(855              inputType.getDimSize(2), weightType.getDimSize(1),856              outputType.getDimSize(2), padding[2], padding[3], strides[1],857              dilations[1], "width", "x", "left", "right")))858        return failure();859    }860 861    // input = [_,ID,IH,IW,_], weight = [_,KD,KH,KW,_], output = [_,OD,OH,OW,_]862    if constexpr (std::is_same<T, tosa::Conv3DOp>::value) {863      if (failed(verifyOutputSize(864              inputType.getDimSize(1), weightType.getDimSize(1),865              outputType.getDimSize(1), padding[0], padding[1], strides[0],866              dilations[0], "depth", "d", "front", "back")))867        return failure();868 869      if (failed(verifyOutputSize(870              inputType.getDimSize(2), weightType.getDimSize(2),871              outputType.getDimSize(2), padding[2], padding[3], strides[1],872              dilations[1], "height", "y", "top", "bottom")))873        return failure();874 875      if (failed(verifyOutputSize(876              inputType.getDimSize(3), weightType.getDimSize(3),877              outputType.getDimSize(3), padding[4], padding[5], strides[2],878              dilations[2], "width", "x", "left", "right")))879        return failure();880    }881  }882 883  const RankedTensorType biasType =884      llvm::dyn_cast<RankedTensorType>(op.getBias().getType());885  if (!biasType)886    // Skip following checks if bias is not ranked887    return success();888 889  const int64_t biasChannels = biasType.getDimSize(0);890  const int64_t outputChannels =891      outputType.getDimSize(outputType.getRank() - 1);892  if (biasChannels == ShapedType::kDynamic ||893      outputChannels == ShapedType::kDynamic)894    // Skip following checks if biasChannels or outputChannels is dynamic dim895    return success();896 897  if (biasChannels != outputChannels && biasChannels != 1)898    return op.emitOpError(899               "bias channels expected to be equal to output channels (")900           << outputChannels << ") or 1, got " << biasChannels;901 902  return success();903}904 905// Verify whether same type and shape of the given two types.906static LogicalResult errorIfTypeOrShapeMismatch(Operation *op, Type type1,907                                                StringRef name1, Type type2,908                                                StringRef name2) {909  auto shapeType1 = dyn_cast<ShapedType>(type1);910  auto shapeType2 = dyn_cast<ShapedType>(type2);911  if (!shapeType1 || !shapeType2)912    return failure();913 914  auto elemType1 = shapeType1.getElementType();915  auto elemType2 = shapeType2.getElementType();916  if (elemType1 != elemType2)917    return op->emitOpError()918           << "require same element type for " << name1 << " (" << elemType1919           << ") and " << name2 << " (" << elemType2 << ")";920 921  if (failed(verifyCompatibleShape(type1, type2)))922    return op->emitOpError()923           << "require same shapes for " << name1 << " (" << type1 << ") and "924           << name2 << " (" << type2 << ")";925 926  return success();927}928 929// Verify whether same length, type, and shape of the given two tensor lists.930static LogicalResult errorIfTypeOrShapeMismatch(Operation *op, ValueRange list1,931                                                StringRef name1,932                                                ValueRange list2,933                                                StringRef name2) {934  if (list1.size() != list2.size())935    return op->emitOpError()936           << "require same number of values in " << name1 << " ("937           << list1.size() << ") and " << name2 << " (" << list2.size() << ")";938 939  for (auto [type1, type2] :940       llvm::zip_equal(list1.getTypes(), list2.getTypes())) {941    if (errorIfTypeOrShapeMismatch(op, type1, name1, type2, name2).failed())942      return failure();943  }944 945  return success();946}947 948static inline LogicalResult errorIfShapeNotSizeOne(Operation *op, Type type) {949  ShapeAdaptor shapeAdaptor(type);950  if (!shapeAdaptor.hasRank() || !shapeAdaptor.hasStaticShape())951    return success();952 953  return shapeAdaptor.getNumElements() == 1 ? success() : failure();954}955 956template <typename T>957static LogicalResult verifyVariableOpErrorIf(T op, Type type, StringRef name) {958  Operation *symTableOp =959      op->template getParentWithTrait<OpTrait::SymbolTable>();960  if (!symTableOp)961    // If the operation is not the scope of a symbol table, we cannot962    // verify it against it's declaration.963    return success();964 965  SymbolTable symTable(symTableOp);966  const auto varOp = symTable.lookup<tosa::VariableOp>(op.getName());967 968  // Verify prior declaration969  if (!varOp)970    return op->emitOpError("'")971           << op.getName() << "' has not been declared by 'tosa.variable'";972 973  // Verify type and shape974  auto variableType = getVariableType(varOp);975  if (errorIfTypeOrShapeMismatch(op, type, name, variableType,976                                 "the input tensor")977          .failed())978    return failure();979  return success();980}981 982// verify that inType and outType have same element types983template <typename T>984static LogicalResult verifySameElementTypes(T op, Type aType, Type bType,985                                            StringRef aName = "input",986                                            StringRef bName = "output") {987  auto aTType = llvm::dyn_cast<TensorType>(aType);988  auto bTType = llvm::dyn_cast<TensorType>(bType);989  if (!aTType) {990    op.emitOpError("expect shaped tensor for") << aName << ", got " << aType;991    return failure();992  }993  if (!bTType) {994    op.emitOpError("expect shaped tensor for") << bName << ", got" << bType;995    return failure();996  }997  auto aElementType = aTType.getElementType();998  auto bElementType = bTType.getElementType();999  auto aQuantType =1000      llvm::dyn_cast<mlir::quant::UniformQuantizedType>(aElementType);1001  auto bQuantType =1002      llvm::dyn_cast<mlir::quant::UniformQuantizedType>(bElementType);1003  if ((aElementType.isIntOrIndexOrFloat() || aQuantType) &&1004      (bElementType.isIntOrIndexOrFloat() || bQuantType) &&1005      aElementType != bElementType) {1006    // only check if both element types are int/index/float/UniformQuantized1007    // eg, not sure how to check quant::QuantizedType1008    // this happens in test_conv2d_q_grouped_convolution in1009    // tfl-to-tosa-pipeline.mlir1010    op.emitOpError("expect ")1011        << aName << " and " << bName << " to have same element type, got "1012        << aElementType << " and " << bElementType;1013    return failure();1014  }1015  return success();1016}1017 1018LogicalResult tosa::ArgMaxOp::verify() {1019  const ShapedType resultType = llvm::cast<ShapedType>(getType());1020 1021  // Ensure output is of 32-bit integer1022  if (const auto resultETy = resultType.getElementType();1023      !resultETy.isIntOrIndex())1024    return emitOpError("result tensor is not of integer type");1025 1026  const auto inputType = llvm::cast<ShapedType>(getInput().getType());1027  if (!inputType.hasRank())1028    return success();1029 1030  // Ensure axis is within the tensor rank1031  const int64_t axis = getAxisAttr().getInt();1032  if (((axis < 0) || axis >= inputType.getRank()))1033    return emitOpError("specified axis is outside the rank of the tensor");1034 1035  if (!resultType.hasRank())1036    return success();1037 1038  const ArrayRef<int64_t> inputShape = inputType.getShape();1039  const ArrayRef<int64_t> outputShape = resultType.getShape();1040  llvm::SmallVector<int64_t> expectedOutputShape(inputShape);1041  expectedOutputShape.erase(expectedOutputShape.begin() + axis);1042  if (failed(verifyCompatibleShape(expectedOutputShape, outputShape)))1043    return emitOpError("expected output shape '")1044           << expectedOutputShape << "', got '" << outputShape << "'";1045 1046  return success();1047}1048 1049template <typename T>1050static LogicalResult verifyPoolingOp(T op) {1051  const llvm::ArrayRef<int64_t> kernel = op.getKernel();1052  if (llvm::any_of(kernel, [](int64_t s) { return s < 1; }))1053    return op.emitOpError("expect all kernel values to be >= 1, got ")1054           << kernel;1055 1056  const llvm::ArrayRef<int64_t> strides = op.getStride();1057  if (llvm::any_of(strides, [](int64_t s) { return s < 1; }))1058    return op.emitOpError("expect all stride values to be >= 1, got ")1059           << strides;1060 1061  const llvm::ArrayRef<int64_t> padding = op.getPad();1062  if (llvm::any_of(padding, [](int64_t p) { return p < 0; }))1063    return op.emitOpError("expect all padding values to be >= 0, got ")1064           << padding;1065 1066  // Padding must be less than kernel size to avoid a divide-by-zero1067  const int64_t kernelX = kernel[1];1068  const int64_t padLeft = padding[2];1069  const int64_t padRight = padding[3];1070  if (padRight >= kernelX || padLeft >= kernelX)1071    return op.emitOpError("expected left/right padding to be less than the "1072                          "width of the kernel, got pad_left=")1073           << padLeft << ", pad_right=" << padRight << ", kernel_x=" << kernelX;1074 1075  const int64_t kernelY = kernel[0];1076  const int64_t padTop = padding[0];1077  const int64_t padBottom = padding[1];1078  if (padTop >= kernelY || padBottom >= kernelY)1079    return op.emitOpError("expected top/bottom padding to be less than the "1080                          "height of the kernel, got pad_top=")1081           << padTop << ", pad_bottom=" << padBottom1082           << ", kernel_y=" << kernelY;1083 1084  const auto inputType =1085      llvm::dyn_cast<RankedTensorType>(op.getInput().getType());1086  const auto outputType =1087      llvm::dyn_cast<RankedTensorType>(op.getResult().getType());1088  if (!inputType || !outputType)1089    return success();1090 1091  const auto verifyOutputSize =1092      [&op](const int64_t inputSize, const int64_t outputSize,1093            const int64_t kernelSize, const int64_t strideSize,1094            const int64_t padBefore, const int64_t padAfter,1095            const llvm::StringRef dimName, const llvm::StringRef dimAxis,1096            const llvm::StringRef padBeforeName,1097            const llvm::StringRef padAfterName) -> LogicalResult {1098    if (ShapedType::isDynamic(inputSize))1099      return success();1100 1101    const std::optional<int64_t> calculatedOutSizeMinusOne =1102        idivCheck(inputSize + padBefore + padAfter - kernelSize, strideSize);1103    if (!calculatedOutSizeMinusOne.has_value())1104      return op.emitOpError("expected input_")1105             << dimName << " + pad_" << padBeforeName << " + pad_"1106             << padAfterName << " - kernel_" << dimAxis1107             << " to be wholly divisible by stride_" << dimAxis << ", got ("1108             << inputSize << " + " << padBefore << " + " << padAfter << " - "1109             << kernelSize << ") / " << strideSize;1110 1111    const int64_t calculatedOutSize = calculatedOutSizeMinusOne.value() + 1;1112    if (ShapedType::isStatic(outputSize) && calculatedOutSize != outputSize)1113      return op.emitOpError("calculated output ")1114             << dimName << " did not match expected: "1115             << "calculated=" << calculatedOutSize1116             << ", expected=" << outputSize;1117 1118    return success();1119  };1120 1121  if (failed(verifyOutputSize(inputType.getDimSize(1), outputType.getDimSize(1),1122                              kernel[0], strides[0], padding[0], padding[1],1123                              "height", "y", "top", "bottom")))1124    return failure();1125 1126  if (failed(verifyOutputSize(inputType.getDimSize(2), outputType.getDimSize(2),1127                              kernel[1], strides[1], padding[2], padding[3],1128                              "width", "x", "left", "right")))1129    return failure();1130 1131  return success();1132}1133 1134LogicalResult tosa::AvgPool2dOp::verify() {1135  if (failed(verifyPoolingOp(*this)))1136    return failure();1137 1138  const Type inputETy = getStorageElementTypeOrSelf(getInput().getType());1139  const Type resultETy = getStorageElementTypeOrSelf(getOutput().getType());1140  const Type inputZpETy = getStorageElementTypeOrSelf(getInputZp().getType());1141  const Type outputZpETy = getStorageElementTypeOrSelf(getOutputZp().getType());1142 1143  auto accType = getAccType();1144  if (llvm::isa<IntegerType>(inputETy) && !accType.isInteger(32))1145    return emitOpError("accumulator type for integer tensor is not i32");1146 1147  if (inputETy.isF16() && !(accType.isF16() || accType.isF32()))1148    return emitOpError("accumulator type for f16 tensor is not f16/f32");1149 1150  if (inputETy.isBF16() && !accType.isF32())1151    return emitOpError("accumulator type for bf16 tensor is not f32");1152 1153  if (inputETy.isF32() && !accType.isF32())1154    return emitOpError("accumulator type for f32 tensor is not f32");1155 1156  if (inputETy != inputZpETy)1157    return emitOpError("expect both input and its zero point are the same "1158                       "element type, got ")1159           << inputETy << " and " << inputZpETy;1160 1161  if (resultETy != outputZpETy)1162    return emitOpError("expect both output and its zero point are the same "1163                       "element type, got ")1164           << resultETy << " and " << outputZpETy;1165 1166  FailureOr<int64_t> maybeIZp = getInputZeroPoint();1167  if (succeeded(maybeIZp) && verifyInputZeroPoint(*maybeIZp).failed())1168    return failure();1169 1170  FailureOr<int64_t> maybeOZp = getOutputZeroPoint();1171  if (succeeded(maybeOZp) && verifyOutputZeroPoint(*maybeOZp).failed())1172    return failure();1173 1174  return success();1175}1176 1177LogicalResult tosa::ClampOp::verify() {1178  mlir::Type inputETy =1179      llvm::cast<ShapedType>(getInput().getType()).getElementType();1180  if (auto quantType =1181          llvm::dyn_cast<mlir::quant::UniformQuantizedType>(inputETy)) {1182    inputETy = getStorageElementTypeFromQuantized(quantType);1183  }1184  mlir::Type outputETy =1185      llvm::cast<ShapedType>(getOutput().getType()).getElementType();1186  if (auto quantType =1187          llvm::dyn_cast<mlir::quant::UniformQuantizedType>(outputETy)) {1188    outputETy = getStorageElementTypeFromQuantized(quantType);1189  }1190  if (inputETy != outputETy)1191    return emitOpError("input/output element types are incompatible.");1192 1193  auto maxValAttr = getMaxValAttr();1194  auto minValAttr = getMinValAttr();1195 1196  unsigned dataTypeBitWidth = inputETy.getIntOrFloatBitWidth();1197 1198  if (inputETy.isInteger(dataTypeBitWidth)) {1199    // if input datatype is integer, check that the min_val/max_val attributes1200    // are integer attributes, and that their type is the same as the input's1201    // datatype1202    auto intMaxValAttr = mlir::dyn_cast<mlir::IntegerAttr>(maxValAttr);1203    auto intMinValAttr = mlir::dyn_cast<mlir::IntegerAttr>(minValAttr);1204    if (!intMaxValAttr || !intMinValAttr ||1205        (intMaxValAttr.getType() != intMinValAttr.getType()) ||1206        (intMaxValAttr.getType() != inputETy))1207      return emitOpError("min/max attributes types are incompatible with "1208                         "input/output element types.");1209 1210    const bool isUnsigned = inputETy.isUnsignedInteger();1211    const bool isBoolean = inputETy.isInteger(1);1212    const APInt minVal = intMinValAttr.getValue();1213    const APInt maxVal = intMaxValAttr.getValue();1214    if ((isUnsigned || isBoolean) ? maxVal.ult(minVal) : maxVal.slt(minVal))1215      return emitOpError("expected min_val <= max_val, got min_val=")1216             << minValAttr << ", max_val=" << maxValAttr;1217  } else {1218    // otherwise, input datatype is float, check that the min_val/max_val1219    // attributes share the same type and that their type is the same as the1220    // input's datatype1221    auto floatMaxValAttr = mlir::dyn_cast<mlir::FloatAttr>(maxValAttr);1222    auto floatMinValAttr = mlir::dyn_cast<mlir::FloatAttr>(minValAttr);1223    if (!floatMaxValAttr || !floatMinValAttr ||1224        (floatMaxValAttr.getType() != floatMinValAttr.getType()) ||1225        (floatMaxValAttr.getType() != inputETy))1226      return emitOpError("min/max attributes types are incompatible with "1227                         "input/output element types.");1228 1229    const APFloat minVal = floatMinValAttr.getValue();1230    const APFloat maxVal = floatMaxValAttr.getValue();1231    if (minVal.isNaN() || maxVal.isNaN())1232      return emitOpError("min/max attributes should not be 'NaN', got min_val=")1233             << minValAttr << ", max_val=" << maxValAttr;1234 1235    if (maxVal < minVal)1236      return emitOpError("expected min_val <= max_val, got min_val=")1237             << minValAttr << ", max_val=" << maxValAttr;1238  }1239 1240  return success();1241}1242 1243//===----------------------------------------------------------------------===//1244// TOSA Operator Quantization Builders.1245//===----------------------------------------------------------------------===//1246 1247/// This builder is called on all convolution operators except TransposeConv,1248/// which has specialized output shape semantics. The builder also defines the1249/// bitwidth of the output given the bit width of the input & weight content.1250static void buildConvOpWithQuantInfo(OpBuilder &builder, OperationState &result,1251                                     Type outputType, Value input, Value weight,1252                                     Value bias, DenseI64ArrayAttr pad,1253                                     DenseI64ArrayAttr stride,1254                                     DenseI64ArrayAttr dilation,1255                                     TypeAttr accType) {1256  auto zps = createZPsAsConst(builder, input, weight);1257  result.addOperands({input, weight, bias, zps.first, zps.second});1258  result.addAttribute("pad", pad);1259  result.addAttribute("stride", stride);1260  result.addAttribute("dilation", dilation);1261  result.addAttribute("acc_type", accType);1262  Type finalOutputType = outputType;1263  auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight);1264  if (quantAttr) {1265    finalOutputType =1266        buildConvOpResultTypeInfo(builder, outputType, input, weight);1267  }1268  result.addTypes(finalOutputType);1269}1270 1271/// Handles tosa.transpose_conv2d which has outpad and output shape1272/// attributes.1273static void1274buildTransConvOpWithQuantInfo(OpBuilder &builder, OperationState &result,1275                              Type outputType, Value input, Value weight,1276                              Value bias, DenseI64ArrayAttr outpad,1277                              DenseI64ArrayAttr stride, TypeAttr accType) {1278  auto zps = createZPsAsConst(builder, input, weight);1279  result.addOperands({input, weight, bias, zps.first, zps.second});1280  result.addAttribute("out_pad", outpad);1281  result.addAttribute("stride", stride);1282  result.addAttribute("acc_type", accType);1283  Type finalOutputType = outputType;1284  auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight);1285  if (quantAttr) {1286    finalOutputType =1287        buildConvOpResultTypeInfo(builder, outputType, input, weight);1288  }1289  result.addTypes(finalOutputType);1290}1291 1292/// The tosa.matmul op is also intended to be generated where a fully_connected1293/// op must be constructed where the weight is not a constant. In this case,1294/// the fully_connected op must be expressed using matmul.1295/// TODO: Add link to the leglization document explaining this.1296static void buildMatMulOpWithQuantInfo(OpBuilder &builder,1297                                       OperationState &result, Type outputType,1298                                       Value a, Value b) {1299  auto zps = createZPsAsConst(builder, a, b);1300  result.addOperands({a, b, zps.first, zps.second});1301 1302  Type finalOutputType{outputType};1303  if (auto quantAttr = buildMatMulOpQuantizationAttr(builder, a, b)) {1304    auto eType = getStorageElementTypeOrSelf(a.getType());1305    auto inputBits = eType.getIntOrFloatBitWidth();1306 1307    auto outputShapedType = llvm::dyn_cast<ShapedType>(outputType);1308    assert(outputShapedType && "Output must be a shaped type");1309 1310    IntegerType accElementType;1311    if (inputBits == 16)1312      accElementType = builder.getIntegerType(48);1313    else1314      accElementType = builder.getI32Type();1315 1316    finalOutputType = outputShapedType.clone(accElementType);1317  }1318  result.addTypes(finalOutputType);1319}1320 1321/// Both the tosa.avg_pool2d and unary ops use the same1322/// UnaryOpQuantizationAttr but avg_pool operator has its own builder as it1323/// has additional parameters not part of the unary ops.1324static void1325buildAvgPool2dOpWithQuantInfo(OpBuilder &builder, OperationState &result,1326                              Type outputType, Value input,1327                              DenseArrayAttr kernel, DenseArrayAttr stride,1328                              DenseArrayAttr pad, TypeAttr accType) {1329  const Location loc{result.location};1330  int64_t inputZp{0};1331  int64_t outputZp{0};1332 1333  if (auto quantAttr =1334          buildUnaryOpQuantizationAttr(builder, input, outputType)) {1335    inputZp = quantAttr.getInputZp();1336    outputZp = quantAttr.getOutputZp();1337  }1338  const std::optional<Value> inputZpOp =1339      createZeroPointTensor(builder, loc, input.getType(), inputZp);1340  if (!inputZpOp) {1341    (void)emitError(1342        loc,1343        "Failed to create input zero point tensor for quantized AVG_POOL2D op");1344  }1345  const std::optional<Value> outputZpOp =1346      createZeroPointTensor(builder, loc, outputType, outputZp);1347  if (!outputZpOp) {1348    (void)emitError(loc, "Failed to create output zero point tensor for "1349                         "quantized AVG_POOL2D op");1350  }1351 1352  if (inputZpOp && outputZpOp) {1353    result.addOperands({input, inputZpOp.value(), outputZpOp.value()});1354  } else {1355    // failed to create one or more zero points above: just add input as1356    // operands this will trigger error in building the op because of missing1357    // zero points1358    result.addOperands({input});1359  }1360  result.addAttribute("kernel", kernel);1361  result.addAttribute("stride", stride);1362  result.addAttribute("pad", pad);1363  result.addAttribute("acc_type", accType);1364  result.types.push_back(outputType);1365}1366 1367/// This builder is called on single-parameter negate operator1368/// to construct input and output zero points based on their1369/// types.1370static void buildNegateOpWithQuantInfo(OpBuilder &builder,1371                                       OperationState &result, Type outputType,1372                                       Value input) {1373  const Location loc{result.location};1374  int64_t input1Zp{0};1375  int64_t outputZp{0};1376  auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType);1377  if (quantAttr) {1378    input1Zp = quantAttr.getInputZp();1379    outputZp = quantAttr.getOutputZp();1380  }1381  const std::optional<Value> input1ZpOp =1382      createZeroPointTensor(builder, loc, input.getType(), input1Zp);1383  if (!input1ZpOp) {1384    (void)emitError(1385        loc, "Failed to create input1 zero point for quantized NEGATE op");1386  }1387 1388  const std::optional<Value> outputZpOp =1389      createZeroPointTensor(builder, loc, input.getType(), outputZp);1390  if (!outputZpOp) {1391    (void)emitError(1392        loc, "Failed to create output zero point for quantized NEGATE op");1393  }1394 1395  if (input1ZpOp && outputZpOp) {1396    result.addOperands({input, input1ZpOp.value(), outputZpOp.value()});1397  } else {1398    // failed to create one or more zero points above: just add input as1399    // operands. This will trigger error in building the op because of1400    // missing zero points1401    result.addOperands({input});1402  }1403 1404  result.types.push_back(outputType);1405}1406 1407/// This builder is called on TOSA pad operator that needs to create its own1408/// OptionalAttr quantization_attr parameter to scale the padding values1409/// correctly. No pad_const is interpreted as zero-padding.1410static void buildPadOpWithQuantInfo(OpBuilder &builder, OperationState &result,1411                                    Type outputType, Value input,1412                                    Value paddings) {1413  const Location loc{result.location};1414  int32_t zp{0};1415  const auto quantAttr = buildPadOpQuantizationAttr(builder, input);1416  if (quantAttr) {1417    zp = static_cast<int32_t>(quantAttr.getInputZp());1418  }1419  const auto padConstOp{createPadConstTensor(builder, loc, input, zp)};1420  result.addOperands({input, paddings, padConstOp});1421  result.types.push_back(outputType);1422}1423 1424static void buildVariableOp(OpBuilder &builder, OperationState &result,1425                            StringRef name, Type variableType,1426                            Attribute initialValue) {1427  const Location loc{result.location};1428  auto nameAttr = builder.getStringAttr(name);1429 1430  auto shapedType = dyn_cast<ShapedType>(variableType);1431  if (!shapedType) {1432    (void)emitError(loc, "variable type must be a shaped type");1433    return;1434  }1435  if (!shapedType.hasRank()) {1436    (void)emitError(loc, "variable type must be a ranked type");1437    return;1438  }1439 1440  auto elementType = shapedType.getElementType();1441  auto elementTypeAttr = TypeAttr::get(elementType);1442  ArrayRef<int64_t> shape = shapedType.getShape();1443  auto varShapeAttr = builder.getIndexTensorAttr(convertFromMlirShape(shape));1444 1445  result.addAttribute("sym_name", nameAttr);1446  result.addAttribute("var_shape", varShapeAttr);1447  result.addAttribute("type", elementTypeAttr);1448  result.addAttribute("initial_value", initialValue);1449}1450 1451//===----------------------------------------------------------------------===//1452// TOSA Operator Return Type Inference.1453//===----------------------------------------------------------------------===//1454 1455static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands,1456                                           SmallVector<int64_t> &outShape) {1457  int64_t outRank = 0;1458  for (int i = 0, e = operands.size(); i != e; ++i) {1459    auto shape = operands.getShape(i);1460    if (!shape.hasRank()) {1461      // TODO(jennik): Update function to have better case handling for1462      // invalid operands and for ranked tensors.1463      return failure();1464    }1465    outRank = std::max<int64_t>(outRank, shape.getRank());1466  }1467 1468  outShape.resize(outRank, 1);1469 1470  for (int i = 0, e = operands.size(); i != e; ++i) {1471    auto shape = operands.getShape(i);1472    auto rankDiff = outShape.size() - shape.getRank();1473 1474    for (size_t i = 0, e = shape.getRank(); i < e; ++i) {1475      auto dim1 = outShape[i + rankDiff];1476      auto dim2 = shape.getDimSize(i);1477      auto resolvedDim = dim1;1478 1479      if (dim1 == 1) {1480        resolvedDim = dim2;1481      } else if (dim2 == 1) {1482        resolvedDim = dim1;1483      } else if (dim1 != dim2) {1484        return failure();1485      }1486      outShape[i + rankDiff] = resolvedDim;1487    }1488  }1489 1490  return success();1491}1492 1493LogicalResult tosa::ArgMaxOp::inferReturnTypeComponents(1494    MLIRContext *context, ::std::optional<Location> location,1495    ArgMaxOp::Adaptor adaptor,1496    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1497  ShapeAdaptor inputShape(adaptor.getInput().getType());1498  IntegerAttr axis = adaptor.getProperties().axis;1499  int32_t axisVal = axis.getValue().getSExtValue();1500 1501  if (!inputShape.hasRank()) {1502    inferredReturnShapes.push_back(ShapedTypeComponents());1503    return success();1504  }1505 1506  SmallVector<int64_t> outShape;1507  outShape.reserve(inputShape.getRank() - 1);1508  for (int i = 0, s = inputShape.getRank(); i < s; i++) {1509    if (i == axisVal)1510      continue;1511    outShape.push_back(inputShape.getDimSize(i));1512  }1513 1514  inferredReturnShapes.push_back(ShapedTypeComponents(outShape));1515  return success();1516}1517 1518LogicalResult tosa::RFFT2dOp::inferReturnTypeComponents(1519    MLIRContext *context, ::std::optional<Location> location,1520    RFFT2dOp::Adaptor adaptor,1521    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1522  ShapeAdaptor inputShape(adaptor.getInputReal().getType());1523 1524  if (!inputShape.hasRank())1525    return failure();1526 1527  llvm::SmallVector<int64_t> outputShape;1528  outputShape.resize(3, ShapedType::kDynamic);1529  outputShape[0] = inputShape.getDimSize(0);1530  outputShape[1] = inputShape.getDimSize(1);1531  int64_t inWidth = inputShape.getDimSize(2);1532 1533  // Note that we can support this calculation symbolically1534  // in the future e.g. [x, y, z] -> [x, y, z / 2 + 1]1535  if (inWidth != ShapedType::kDynamic)1536    outputShape[2] = inWidth / 2 + 1;1537 1538  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));1539  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));1540 1541  return success();1542}1543 1544static LogicalResult verifyDimIsPowerOfTwo(Operation *op, const int64_t dimSize,1545                                           const llvm::StringRef dimName) {1546  const bool isPowerOfTwo = (dimSize & (dimSize - 1)) == 0 && dimSize > 0;1547  if (!isPowerOfTwo)1548    return op->emitOpError("expected ")1549           << dimName << " to be a power of two, got " << dimSize;1550 1551  return success();1552}1553 1554LogicalResult tosa::RFFT2dOp::verify() {1555  const auto outputTypes = getResultTypes();1556  if (failed(verifyCompatibleShapes(outputTypes)))1557    return emitOpError("expected output shapes to match, got ") << outputTypes;1558 1559  const auto inputType =1560      llvm::dyn_cast<RankedTensorType>(getInputReal().getType());1561  if (!inputType)1562    return success();1563 1564  const int64_t height = inputType.getDimSize(1);1565  if (ShapedType::isStatic(height) &&1566      failed(verifyDimIsPowerOfTwo(*this, height, "height")))1567    return failure();1568 1569  const int64_t width = inputType.getDimSize(2);1570  if (ShapedType::isStatic(width) &&1571      failed(verifyDimIsPowerOfTwo(*this, width, "width")))1572    return failure();1573 1574  const auto outputType = llvm::dyn_cast<RankedTensorType>(outputTypes[0]);1575  if (!outputType)1576    return success();1577 1578  // Batch and height input/output dimensions should match1579  if (failed(verifyCompatibleShape(inputType.getShape().drop_back(),1580                                   outputType.getShape().drop_back())))1581    return emitOpError("expected batch and height dimensions of input/output "1582                       "to match, got input=")1583           << inputType << " output=" << outputType;1584 1585  // Output width dimension expected to be input_width / 2 + 11586  const int64_t outputWidth = outputType.getDimSize(2);1587  if (ShapedType::isStatic(width) && ShapedType::isStatic(outputWidth) &&1588      (outputWidth != (width / 2) + 1))1589    return emitOpError(1590               "expected output width to be equal to input_width / 2 + 1, got ")1591           << outputWidth;1592 1593  return success();1594}1595 1596LogicalResult tosa::FFT2dOp::inferReturnTypeComponents(1597    MLIRContext *context, ::std::optional<Location> location,1598    FFT2dOp::Adaptor adaptor,1599    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1600  inferredReturnShapes.push_back(1601      ShapedTypeComponents(ShapeAdaptor(adaptor.getInputReal().getType())));1602  inferredReturnShapes.push_back(1603      ShapedTypeComponents(ShapeAdaptor(adaptor.getInputImag().getType())));1604  return success();1605}1606 1607LogicalResult tosa::FFT2dOp::verify() {1608  const auto inputRealType =1609      llvm::dyn_cast<RankedTensorType>(getInputReal().getType());1610  const auto inputImagType =1611      llvm::dyn_cast<RankedTensorType>(getInputImag().getType());1612  if (!inputRealType || !inputImagType)1613    return success();1614 1615  const auto trySelectStaticDim = [](const int64_t a, const int64_t b) {1616    return ShapedType::isDynamic(a) ? a : b;1617  };1618 1619  const int64_t height = trySelectStaticDim(inputRealType.getDimSize(1),1620                                            inputImagType.getDimSize(1));1621  if (ShapedType::isStatic(height) &&1622      failed(verifyDimIsPowerOfTwo(*this, height, "height")))1623    return failure();1624 1625  const int64_t width = trySelectStaticDim(inputRealType.getDimSize(2),1626                                           inputImagType.getDimSize(2));1627  if (ShapedType::isStatic(width) &&1628      failed(verifyDimIsPowerOfTwo(*this, width, "width")))1629    return failure();1630 1631  return success();1632}1633 1634LogicalResult tosa::ConcatOp::inferReturnTypeComponents(1635    MLIRContext *context, ::std::optional<Location> location,1636    ConcatOp::Adaptor adaptor,1637    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1638  // Infer all dimension sizes by reducing based on inputs.1639  const Properties &prop = adaptor.getProperties();1640  int32_t axis = prop.axis.getValue().getSExtValue();1641  llvm::SmallVector<int64_t> outputShape;1642  bool hasRankedInput = false;1643  for (auto operand : adaptor.getOperands()) {1644    ShapeAdaptor operandShape(operand.getType());1645    if (!operandShape.hasRank())1646      continue;1647 1648    // Copy the Operand's rank.1649    if (!hasRankedInput)1650      outputShape.resize(operandShape.getRank(), ShapedType::kDynamic);1651 1652    // Copy shapes until the dim is non-dynamic.1653    for (int i = 0, s = operandShape.getRank(); i < s; i++) {1654      if (i == axis || operandShape.isDynamicDim(i))1655        continue;1656      if (outputShape[i] == ShapedType::kDynamic)1657        outputShape[i] = operandShape.getDimSize(i);1658      if (outputShape[i] != operandShape.getDimSize(i))1659        return emitOptionalError(location,1660                                 "Cannot concat tensors with different sizes"1661                                 " on the non-axis dimension ",1662                                 i);1663    }1664 1665    hasRankedInput = true;1666  }1667 1668  if (adaptor.getInput1().empty())1669    return failure();1670 1671  Type inputType =1672      llvm::cast<TensorType>(adaptor.getInput1().getType()[0]).getElementType();1673  if (!hasRankedInput) {1674    inferredReturnShapes.push_back(ShapedTypeComponents(inputType));1675    return success();1676  }1677 1678  // Determine the dimension size along the concatenation axis.1679  int64_t concatDimSize = 0;1680  for (auto operand : adaptor.getOperands()) {1681    ShapeAdaptor operandShape(operand.getType());1682 1683    // We need to know the length of the concatenation axis of all inputs to1684    // determine the dimension size of the output shape.1685    if (!operandShape.hasRank() || operandShape.isDynamicDim(axis)) {1686      concatDimSize = ShapedType::kDynamic;1687      break;1688    }1689 1690    concatDimSize += operandShape.getDimSize(axis);1691  }1692 1693  outputShape[axis] = concatDimSize;1694 1695  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));1696  return success();1697}1698 1699LogicalResult tosa::ConcatOp::verify() {1700  // check that each input has same element type as output1701  auto outType = getOutput().getType();1702  const Operation::operand_range inputList = getInput1();1703 1704  // Check there is at least one input1705  if (inputList.empty())1706    return emitOpError("expect at least one input");1707 1708  if (!llvm::all_of(inputList, [&](auto input) {1709        return succeeded(verifySameElementTypes(1710            *this, /* inType = */ input.getType(), outType));1711      })) {1712    return failure();1713  }1714 1715  const int32_t axis = getAxis();1716  ShapeAdaptor firstRankedInputShape = nullptr;1717  for (const auto &input : inputList) {1718    const Type inputType = input.getType();1719    ShapeAdaptor currShape(inputType);1720    if (currShape.hasRank()) {1721      firstRankedInputShape = currShape;1722      // Check axis is in expected range1723      if (axis < 0 || axis >= firstRankedInputShape.getRank())1724        return emitOpError("expect axis to be within range 0 < axis < "1725                           "rank(input1[firstRankedTensorIdx]), got ")1726               << axis;1727      break;1728    }1729  }1730 1731  const auto allOperandsHasRank = [](const Value input) {1732    return ShapeAdaptor(input.getType()).hasRank();1733  };1734  if (llvm::all_of(inputList, allOperandsHasRank)) {1735    const int64_t firstInputRank = firstRankedInputShape.getRank();1736 1737    for (const auto &[index, input] : llvm::enumerate(inputList.drop_front())) {1738      const ShapeAdaptor inputShape(input.getType());1739      const int64_t inputRank = inputShape.getRank();1740      const size_t operandNum = index + 1;1741 1742      // Check that each operand has the same rank1743      if (inputRank != firstInputRank)1744        return emitOpError(1745                   "expect all operands to have the same rank, but got ")1746               << firstInputRank << " vs " << inputRank << " on operands 0 and "1747               << operandNum;1748 1749      // Check non-axis dims match1750      for (int i = 0; i < inputRank; i++) {1751        const int64_t inputDim = inputShape.getDimSize(i);1752        const int64_t firstInputDim = firstRankedInputShape.getDimSize(i);1753        if (i == axis || firstRankedInputShape.isDynamicDim(i) ||1754            inputShape.isDynamicDim(i))1755          continue;1756        if (inputDim != firstInputDim)1757          return emitOpError("expect all operand shapes to have the same sizes "1758                             "on non-axis dimensions, but got ")1759                 << inputDim << " vs " << firstInputDim << " at index " << i1760                 << " on operands 0 and " << operandNum;1761      }1762    }1763 1764    const ShapeAdaptor outputShape(outType);1765    if (outputShape.hasRank() && outputShape.getRank() != firstInputRank)1766      return emitOpError("expect output rank to match inputs rank, got ")1767             << outputShape.getRank() << " vs " << firstInputRank;1768 1769    // ERROR_IF(axis_sum != shape[axis]);1770    int64_t axisSum = 0;1771    for (const auto &input : inputList) {1772      const ShapeAdaptor inputShape(input.getType());1773      if (inputShape.isDynamicDim(axis)) {1774        // make axisSum negative to indicate invalid value1775        axisSum = -1;1776        break;1777      }1778      axisSum += inputShape.getDimSize(axis);1779    }1780 1781    if (axisSum >= 0 && outputShape.hasRank() &&1782        !outputShape.isDynamicDim(axis) &&1783        axisSum != outputShape.getDimSize(axis))1784      return emitOpError("requires sum of axis dimensions of input1 "1785                         "equal to output axis dimension, got ")1786             << axisSum << " and " << outputShape.getDimSize(axis);1787  }1788 1789  return success();1790}1791 1792LogicalResult tosa::EqualOp::inferReturnTypeComponents(1793    MLIRContext *context, ::std::optional<Location> location,1794    ValueShapeRange operands, DictionaryAttr attributes,1795    OpaqueProperties properties, RegionRange regions,1796    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1797  auto elementType = IntegerType::get(context, /*width=*/1);1798 1799  llvm::SmallVector<int64_t> outShape;1800  if (resolveBroadcastShape(operands, outShape).failed()) {1801    inferredReturnShapes.push_back(ShapedTypeComponents(elementType));1802    return success();1803  }1804 1805  inferredReturnShapes.push_back(ShapedTypeComponents(outShape, elementType));1806  return success();1807}1808 1809bool tosa::EqualOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {1810  if (l.size() != r.size() || l.size() != 1)1811    return false;1812  return succeeded(verifyCompatibleShape(l[0], r[0]));1813}1814 1815LogicalResult tosa::MatMulOp::inferReturnTypeComponents(1816    MLIRContext *context, ::std::optional<Location> location,1817    MatMulOp::Adaptor adaptor,1818    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1819  ShapeAdaptor lhsShape(adaptor.getA().getType());1820  ShapeAdaptor rhsShape(adaptor.getB().getType());1821 1822  // All shapes are dynamic.1823  SmallVector<int64_t> outShape;1824  outShape.resize(3, ShapedType::kDynamic);1825 1826  if (lhsShape.hasRank()) {1827    outShape[0] = lhsShape.getDimSize(0);1828    outShape[1] = lhsShape.getDimSize(1);1829  }1830 1831  if (rhsShape.hasRank()) {1832    outShape[0] = outShape[0] == ShapedType::kDynamic ? rhsShape.getDimSize(0)1833                                                      : outShape[0];1834    outShape[2] = rhsShape.getDimSize(2);1835  }1836 1837  inferredReturnShapes.push_back(ShapedTypeComponents(outShape));1838  return success();1839}1840 1841LogicalResult MatMulOp::verify() {1842  auto aType = llvm::dyn_cast<ShapedType>(getA().getType());1843  auto bType = llvm::dyn_cast<ShapedType>(getB().getType());1844 1845  // Must be shaped tensor types1846  if (!aType)1847    return emitOpError("expect a shaped tensor for input a, got ")1848           << getA().getType();1849 1850  if (!bType)1851    return emitOpError("expect a shaped tensor for input b, got ")1852           << getB().getType();1853 1854  auto aElementType = aType.getElementType();1855  auto bElementType = bType.getElementType();1856 1857  auto aQuantizedEType =1858      llvm::dyn_cast<quant::UniformQuantizedType>(aElementType);1859  auto bQuantizedEType =1860      llvm::dyn_cast<quant::UniformQuantizedType>(bElementType);1861 1862  if (aQuantizedEType || bQuantizedEType) {1863    if (!aQuantizedEType || !bQuantizedEType) {1864      return emitOpError("expect operands to be both quantized or both not "1865                         "quantized, got ")1866             << aElementType << " and " << bElementType;1867    }1868    // both a and b have quantized element types1869    auto aQuantWidth = aQuantizedEType.getStorageTypeIntegralWidth();1870    auto bQuantWidth = bQuantizedEType.getStorageTypeIntegralWidth();1871    if (aQuantWidth != bQuantWidth) {1872      return emitOpError("expect quantized operands to have same widths, got ")1873             << aQuantWidth << " and " << bQuantWidth;1874    }1875  }1876 1877  // check a_zp and b_zp1878  auto aEType = getStorageElementTypeOrSelf(aType);1879  auto aZpEType = getStorageElementTypeOrSelf(getAZp().getType());1880  if (aEType != aZpEType) {1881    return emitOpError("expect input a and a_zp have the same "1882                       "element type, got ")1883           << aEType << " and " << aZpEType;1884  }1885 1886  auto bEType = getStorageElementTypeOrSelf(bType);1887  auto bZpEType = getStorageElementTypeOrSelf(getBZp().getType());1888  if (bEType != bZpEType) {1889    return emitOpError("expect input b and b_zp have the same "1890                       "element type, got ")1891           << bEType << " and " << bZpEType;1892  }1893 1894  FailureOr<int64_t> maybeAZp = getAZeroPoint();1895  if (succeeded(maybeAZp) && verifyAZeroPoint(*maybeAZp).failed())1896    return failure();1897 1898  FailureOr<int64_t> maybeBZp = getBZeroPoint();1899  if (succeeded(maybeBZp) && verifyBZeroPoint(*maybeBZp).failed())1900    return failure();1901 1902  return success();1903}1904 1905LogicalResult tosa::MatmulTBlockScaledOp::inferReturnTypeComponents(1906    MLIRContext *context, ::std::optional<Location> location,1907    MatmulTBlockScaledOp::Adaptor adaptor,1908    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {1909  SmallVector<int64_t, 3> outShape(3, ShapedType::kDynamic);1910 1911  const auto aDataShape = cast<ShapedType>(adaptor.getAData().getType());1912  if (aDataShape.hasRank()) {1913    outShape[0] = aDataShape.getDimSize(0);1914    outShape[1] = aDataShape.getDimSize(1);1915  }1916 1917  const auto aScaleShape = cast<ShapedType>(adaptor.getAScale().getType());1918  if (aScaleShape.hasRank()) {1919    outShape[0] = ShapedType::isDynamic(outShape[0]) ? aScaleShape.getDimSize(0)1920                                                     : outShape[0];1921    outShape[1] = ShapedType::isDynamic(outShape[1]) ? aScaleShape.getDimSize(1)1922                                                     : outShape[1];1923  }1924 1925  // If B batch size is 1, it is broadcast across A's batch size1926  const auto bDataShape = cast<ShapedType>(adaptor.getBData().getType());1927  if (bDataShape.hasRank()) {1928    const int64_t bDataBatchSize = bDataShape.getDimSize(0);1929    if (bDataBatchSize != 1)1930      outShape[0] =1931          ShapedType::isDynamic(outShape[0]) ? bDataBatchSize : outShape[0];1932    outShape[2] = bDataShape.getDimSize(1);1933  }1934 1935  const auto bScaleShape = cast<ShapedType>(adaptor.getBScale().getType());1936  if (bScaleShape.hasRank()) {1937    const int64_t bScaleBatchSize = bScaleShape.getDimSize(0);1938    if (bScaleBatchSize != 1)1939      outShape[0] =1940          ShapedType::isDynamic(outShape[0]) ? bScaleBatchSize : outShape[0];1941    outShape[2] = ShapedType::isDynamic(outShape[2]) ? bScaleShape.getDimSize(1)1942                                                     : outShape[2];1943  }1944 1945  inferredReturnShapes.push_back(ShapedTypeComponents(outShape));1946  return success();1947}1948 1949LogicalResult MatmulTBlockScaledOp::verify() {1950  // Verify same input data types1951  const Type aDataType = getAData().getType();1952  const Type bDataType = getBData().getType();1953  if (failed(verifySameElementTypes(*this, aDataType, bDataType, "A_data",1954                                    "B_data")))1955    return failure();1956 1957  auto tryUpdateDimOrFailure = [&](int64_t &currDim, const int64_t newDim,1958                                   const StringRef operandName,1959                                   const StringRef dimName) -> LogicalResult {1960    if (ShapedType::isDynamic(currDim)) {1961      currDim = newDim;1962      return success();1963    } else if (ShapedType::isStatic(newDim) && currDim != newDim) {1964      return emitOpError("expected ")1965             << dimName << " of " << operandName << " to match size " << currDim1966             << ", got " << newDim;1967    }1968    return success();1969  };1970 1971  // Verify input shape compatibility1972  int64_t N = ShapedType::kDynamic;1973  int64_t D = ShapedType::kDynamic;1974  int64_t H = ShapedType::kDynamic;1975  int64_t W = ShapedType::kDynamic;1976  int64_t C = ShapedType::kDynamic;1977  int64_t multiplesOfC = ShapedType::kDynamic;1978 1979  const ShapeAdaptor aDataShape = ShapeAdaptor(aDataType);1980  if (aDataShape.hasRank()) {1981    N = aDataShape.getDimSize(0);1982    H = aDataShape.getDimSize(1);1983    C = aDataShape.getDimSize(2);1984  }1985 1986  const ShapeAdaptor aScaleShape = ShapeAdaptor(getAScale().getType());1987  if (aScaleShape.hasRank()) {1988    if (failed(tryUpdateDimOrFailure(N, aScaleShape.getDimSize(0), "a_scale",1989                                     "batch")) ||1990        failed(tryUpdateDimOrFailure(H, aScaleShape.getDimSize(1), "a_scale",1991                                     "height")))1992      return failure();1993    multiplesOfC = aScaleShape.getDimSize(2);1994  }1995 1996  const ShapeAdaptor bDataShape = ShapeAdaptor(bDataType);1997  if (bDataShape.hasRank()) {1998    if (failed(tryUpdateDimOrFailure(D, bDataShape.getDimSize(0), "b_data",1999                                     "batch")) ||2000        failed(tryUpdateDimOrFailure(C, bDataShape.getDimSize(2), "b_data",2001                                     "channels")))2002      return failure();2003    W = bDataShape.getDimSize(1);2004  }2005 2006  const ShapeAdaptor bScaleShape = ShapeAdaptor(getBScale().getType());2007  if (bScaleShape.hasRank()) {2008    if (failed(tryUpdateDimOrFailure(D, bScaleShape.getDimSize(0), "b_scale",2009                                     "batch")) ||2010        failed(tryUpdateDimOrFailure(W, bScaleShape.getDimSize(1), "b_scale",2011                                     "width")) ||2012        failed(tryUpdateDimOrFailure(multiplesOfC, bScaleShape.getDimSize(2),2013                                     "b_scale", "C/block_size")))2014      return failure();2015  }2016 2017  // Verify batch size is broadcast compatible2018  if (ShapedType::isStatic(N) && ShapedType::isStatic(D) && N != D && D != 1)2019    return emitOpError("expect B matrix batch size to be broadcast compatible "2020                       "with A, got D=")2021           << D << " vs N=" << N;2022 2023  // Verify C is a multiple of block size2024  const uint32_t blockSize = BlockSizeAttr::getBlockSizeValue(getBlockSize());2025  if (ShapedType::isStatic(C) && C % blockSize != 0)2026    return emitOpError("expect C to be a multiple of block size, got C=")2027           << C << ", block_size=" << blockSize;2028 2029  // Verify multiplesOfC is C / block size2030  if (ShapedType::isStatic(C) && ShapedType::isStatic(multiplesOfC) &&2031      multiplesOfC != C / blockSize)2032    return emitOpError(2033               "expect scale operands dimension 2 to equal C/block_size (")2034           << C << "/" << blockSize << ")"2035           << ", got " << multiplesOfC;2036 2037  // Verify output shape2038  N = ShapedType::isDynamic(N) ? D : N;2039  const SmallVector<int64_t, 3> expectedOutputShape = {N, H, W};2040  const auto outputType = cast<ShapedType>(getResult().getType());2041  if (outputType.hasRank() &&2042      failed(2043          verifyCompatibleShape(outputType.getShape(), expectedOutputShape))) {2044    InFlightDiagnostic opError = emitOpError("expected output shape ");2045    auto stringifyDim = [&](int64_t d) {2046      if (ShapedType::isDynamic(d))2047        opError << "?";2048      else2049        opError << d;2050    };2051    llvm::interleaveComma(outputType.getShape(), opError, stringifyDim);2052    opError << " to be compatible with expected output shape ";2053    llvm::interleaveComma(expectedOutputShape, opError, stringifyDim);2054    return opError;2055  }2056 2057  return success();2058}2059 2060LogicalResult tosa::PadOp::inferReturnTypeComponents(2061    MLIRContext *context, ::std::optional<Location> location,2062    PadOp::Adaptor adaptor,2063    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2064  ShapeAdaptor inputShape(adaptor.getInput1().getType());2065  auto paddingRank =2066      cast<tosa::shapeType>(adaptor.getPadding().getType()).getRank();2067  SmallVector<int64_t> outputShape;2068 2069  // If the input rank is unknown, we can infer the output rank using the2070  // padding shape's rank divided by 2.2071  if (!inputShape.hasRank()) {2072    outputShape.resize(paddingRank / 2, ShapedType::kDynamic);2073    inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2074    return success();2075  }2076 2077  SmallVector<int64_t> paddingValues;2078  // If the paddings value is not a constant, all dimensions must be dynamic.2079  if (!tosa::getConstShapeValues(adaptor.getPadding().getDefiningOp(),2080                                 paddingValues)) {2081    outputShape.resize(inputShape.getRank(), ShapedType::kDynamic);2082    inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2083    return success();2084  }2085 2086  outputShape.reserve(inputShape.getRank());2087  for (int i = 0, s = inputShape.getRank(); i < s; i++) {2088    if (inputShape.isDynamicDim(i)) {2089      outputShape.push_back(ShapedType::kDynamic);2090      continue;2091    }2092    auto padFront = paddingValues[i * 2];2093    auto padBack = paddingValues[i * 2 + 1];2094    if (padFront < 0 || padBack < 0) {2095      // if either padding for dim i is -1, output dim is unknown2096      outputShape.push_back(ShapedType::kDynamic);2097      continue;2098    }2099 2100    outputShape.push_back(inputShape.getDimSize(i) + padFront + padBack);2101  }2102 2103  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2104  return success();2105}2106 2107LogicalResult tosa::PadOp::verify() {2108  if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),2109                             /* outType = */ getOutput().getType())2110          .failed()) {2111    return failure();2112  }2113 2114  if (auto padConst = getPadConst()) {2115    if (verifySameElementTypes(*this, /* inType = */ padConst.getType(),2116                               /* outType = */ getOutput().getType())2117            .failed()) {2118      return failure();2119    }2120  }2121 2122  RankedTensorType inputType =2123      llvm::dyn_cast<RankedTensorType>(getInput1().getType());2124  RankedTensorType outputType =2125      llvm::dyn_cast<RankedTensorType>(getOutput().getType());2126  if (!inputType || !outputType)2127    return success();2128 2129  auto inputRank = inputType.getRank();2130  auto outputRank = outputType.getRank();2131  if (inputRank != outputRank)2132    return emitOpError() << "expect same input and output tensor rank, but got "2133                         << "inputRank: " << inputRank2134                         << ", outputRank: " << outputRank;2135 2136  DenseIntElementsAttr paddingAttr;2137  if (!matchPattern(getPadding(), m_Constant(&paddingAttr))) {2138    return failure();2139  }2140 2141  auto paddingValues = paddingAttr.getValues<APInt>();2142  if (paddingValues.size() != static_cast<size_t>(inputRank * 2))2143    return emitOpError() << "padding tensor must have " << inputRank2144                         << " * 2 = " << inputRank * 2 << " elements, but got "2145                         << paddingValues.size();2146 2147  auto inputShape = inputType.getShape();2148  auto outputShape = outputType.getShape();2149 2150  for (int64_t i = 0; i < inputRank; ++i) {2151    int64_t padStart = paddingValues[i * 2].getSExtValue();2152    int64_t padEnd = paddingValues[i * 2 + 1].getSExtValue();2153 2154    if ((padStart < 0 && padStart != -1) || (padEnd < 0 && padEnd != -1)) {2155      return emitOpError()2156             << "invalid padding values at dimension " << i2157             << ": values must be non-negative or -1 for dynamic padding, got ["2158             << padStart << ", " << padEnd << "]";2159    }2160 2161    // Skip shape verification for dynamic input/output2162    if (inputShape[i] == ShapedType::kDynamic ||2163        outputShape[i] == ShapedType::kDynamic)2164      continue;2165 2166    if (outputShape[i] != inputShape[i] + padStart + padEnd) {2167      return emitOpError() << "mismatch in output shape at dimension " << i2168                           << ": expected " << inputShape[i] << " + "2169                           << padStart << " + " << padEnd << " = "2170                           << (inputShape[i] + padStart + padEnd)2171                           << ", but got " << outputShape[i];2172    }2173  }2174 2175  return success();2176}2177 2178LogicalResult tosa::SliceOp::inferReturnTypeComponents(2179    MLIRContext *context, ::std::optional<Location> location,2180    SliceOp::Adaptor adaptor,2181    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2182 2183  Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());2184  SmallVector<int64_t> start;2185  SmallVector<int64_t> size;2186 2187  if (!tosa::getConstShapeValues(adaptor.getStart().getDefiningOp(), start) ||2188      !tosa::getConstShapeValues(adaptor.getSize().getDefiningOp(), size)) {2189    auto rank = cast<tosa::shapeType>(adaptor.getSize().getType()).getRank();2190    SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);2191    inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));2192    return success();2193  }2194 2195  // if size[i] is -1, all remaining elements in dimension i are included2196  // in the slice, similar to TF.2197  ShapeAdaptor inputShape(adaptor.getInput1().getType());2198  // initialize outputShape to all unknown2199  SmallVector<int64_t> outputShape(size.size(), ShapedType::kDynamic);2200  if (inputShape.hasRank()) {2201    for (size_t i = 0; i < size.size(); i++) {2202      if (size[i] != 0 && size[i] >= -1 && start[i] >= 0 &&2203          (ShapedType::isDynamic(inputShape.getDimSize(i)) ||2204           start[i] < inputShape.getDimSize(i))) {2205        // size[i] is not 0 and not < -1, and start[i] is in valid range2206        if (ShapedType::isDynamic(inputShape.getDimSize(i))) {2207          // input shape has unknown dim[i] - only valid if size[i] > 02208          if (size[i] > 0) {2209            outputShape[i] = size[i];2210          }2211        } else {2212          // input shape has known dim[i]2213          if (size[i] == -1) {2214            outputShape[i] = inputShape.getDimSize(i) - start[i];2215          } else if (start[i] + size[i] <= inputShape.getDimSize(i)) {2216            // start[i] + size[i] is within bound of input shape's dim[i]2217            outputShape[i] = size[i];2218          }2219        }2220      }2221    }2222  } else {2223    outputShape = convertToMlirShape(size);2224  }2225  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2226  return success();2227}2228 2229LogicalResult tosa::SliceOp::verify() {2230  if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),2231                             /* outType = */ getOutput().getType())2232          .failed())2233    return failure();2234 2235  const ShapeAdaptor inputShape(getInput1().getType());2236  if (inputShape.hasRank()) {2237    const auto inputRank = inputShape.getRank();2238    const ShapeAdaptor outputShape(getOutput().getType());2239    if (outputShape.hasRank() && inputRank != outputShape.getRank())2240      return emitOpError(2241                 "expect input1 and output to have the same ranks, got ")2242             << inputRank << " and " << outputShape.getRank();2243 2244    const auto startShapeRank =2245        llvm::cast<tosa::shapeType>(getStart().getType()).getRank();2246    if (inputRank != startShapeRank)2247      return emitOpError("length of start is not equal to rank of input shape");2248 2249    const auto sizeShapeRank =2250        llvm::cast<tosa::shapeType>(getSize().getType()).getRank();2251    if (inputRank != sizeShapeRank)2252      return emitOpError("length of size is not equal to rank of input shape");2253  }2254 2255  return success();2256}2257 2258LogicalResult tosa::MulOp::inferReturnTypeComponents(2259    MLIRContext *context, ::std::optional<Location> location,2260    ValueShapeRange operands, DictionaryAttr attributes,2261    OpaqueProperties properties, RegionRange regions,2262    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2263  // mul op's output shape only depend on input1 and input2, not on shift2264  ValueShapeRange twoInputs = operands.drop_back();2265  llvm::SmallVector<int64_t> outShape;2266  if (resolveBroadcastShape(twoInputs, outShape).failed()) {2267    inferredReturnShapes.push_back(ShapedTypeComponents());2268  } else {2269    inferredReturnShapes.push_back(ShapedTypeComponents(outShape));2270  }2271  return success();2272}2273 2274LogicalResult tosa::MulOp::verify() {2275  const Value output = getOutput();2276  auto resElemType = getElementTypeOrSelf(output);2277 2278  // Verify if the element type among operands and result match tosa2279  // specification.2280  if (auto resIntType = dyn_cast<IntegerType>(resElemType)) {2281    IntegerType lhsIntType =2282        dyn_cast<IntegerType>(getElementTypeOrSelf(getInput1()));2283    IntegerType rhsIntType =2284        dyn_cast<IntegerType>(getElementTypeOrSelf(getInput2()));2285    if (!lhsIntType || !rhsIntType || lhsIntType != rhsIntType)2286      return emitOpError("requires the same element type for all operands");2287 2288    // Though the spec requires the element type of result to be i32, a more2289    // relaxed way is provided at dialect level for easier cooperating with2290    // other dialects.2291    if (lhsIntType.getWidth() > resIntType.getWidth())2292      return emitOpError("invalid data type size for operands or result");2293 2294  } else {2295    // For other supported type, the spec requires requires the same element2296    // type for all operands (excludes `shift` operand) and results.2297    for (int i = 0; i < 2; ++i) {2298      if (getElementTypeOrSelf(getOperand(i)) != resElemType)2299        return emitOpError(2300            "requires the same element type for all operands and results");2301    }2302 2303    // verify shift has value 0 for non-integer types2304    ElementsAttr shift_elem;2305    if (matchPattern(getShift(), m_Constant(&shift_elem))) {2306      int32_t shift = shift_elem.getValues<IntegerAttr>()[0].getInt();2307      if (shift != 0) {2308        return emitOpError() << "require shift to be 0 for float type";2309      }2310    }2311  }2312 2313  // Verify the op has same ranks for all main operands (excludes extra operands2314  // such as shift of mul op, so this is the only difference with the built-in2315  // `SameOperandsAndResultRank` trait) and results types, if known.2316  TypeRange operandTypes = getOperandTypes();2317  ShapedType aType = cast<ShapedType>(operandTypes[0]);2318  ShapedType bType = cast<ShapedType>(operandTypes[1]);2319 2320  const bool aHasRank = aType.hasRank();2321  const bool bHasRank = bType.hasRank();2322  if (aHasRank && bHasRank) {2323    const int64_t aRank = aType.getRank();2324    const int64_t bRank = bType.getRank();2325    if (aRank != bRank)2326      return emitOpError("a and b operands don't have matching ranks, got ")2327             << aRank << " and " << bRank;2328 2329    // check for broadcast compatible shapes2330    SmallVector<int64_t> resultShape;2331    if (!mlir::OpTrait::util::getBroadcastedShape(2332            aType.getShape(), bType.getShape(), resultShape))2333      return emitOpError("a and b operands don't have broadcast-compatible "2334                         "shapes, got ")2335             << aType << " and " << bType;2336  }2337 2338  ShapedType resultType = cast<ShapedType>(output.getType());2339  if (!resultType.hasRank())2340    return success();2341 2342  const int64_t resultRank = resultType.getRank();2343  if (aHasRank && resultRank != aType.getRank())2344    return emitOpError("result type has different rank than a, got ")2345           << resultRank << " vs " << aType.getRank();2346  if (bHasRank && resultRank != bType.getRank())2347    return emitOpError("result type has different rank than b, got ")2348           << resultRank << " vs " << bType.getRank();2349 2350  return success();2351}2352 2353LogicalResult tosa::TableOp::inferReturnTypeComponents(2354    MLIRContext *context, ::std::optional<Location> location,2355    TableOp::Adaptor adaptor,2356    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2357  ShapeAdaptor inputShape(adaptor.getInput1().getType());2358 2359  if (!inputShape.hasRank()) {2360    inferredReturnShapes.push_back(ShapedTypeComponents());2361    return success();2362  }2363 2364  inferredReturnShapes.resize(1);2365  inputShape.getDims(inferredReturnShapes[0]);2366  return success();2367}2368 2369LogicalResult tosa::TableOp::verify() {2370  const TensorType inputType = getInput1().getType();2371  const TensorType outputType = getOutput().getType();2372 2373  if (!inputType.hasRank() || !outputType.hasRank())2374    return success();2375 2376  if (inputType.getRank() != outputType.getRank())2377    return emitOpError()2378           << "expected input tensor rank to equal result tensor rank";2379 2380  auto inputDims = inputType.getShape();2381  auto outputDims = outputType.getShape();2382  for (auto it : llvm::enumerate(llvm::zip(inputDims, outputDims))) {2383    int64_t dim = it.index();2384    auto [inputDim, outputDim] = it.value();2385    if (ShapedType::isStatic(outputDim) && outputDim != inputDim) {2386      return emitOpError() << "dim(result, " << dim << ") = " << outputDim2387                           << " doesn't match dim(input, " << dim2388                           << ") = " << inputDim;2389    }2390  }2391  return success();2392}2393 2394LogicalResult2395tosa::TileOp::getConstantMultiples(SmallVector<int64_t> &multiples) {2396  // Multiples must be constants.2397  DenseIntElementsAttr multiplesAttr;2398  if (!matchPattern(getMultiples(), m_Constant(&multiplesAttr)))2399    return failure();2400  multiples = llvm::to_vector(2401      llvm::map_range(multiplesAttr.getValues<APInt>(),2402                      [](const APInt &val) { return val.getSExtValue(); }));2403  return success();2404}2405 2406LogicalResult tosa::TileOp::inferReturnTypeComponents(2407    MLIRContext *context, ::std::optional<Location> location,2408    TileOp::Adaptor adaptor,2409    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2410  Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());2411  SmallVector<int64_t> multiples;2412  if (!tosa::getConstShapeValues(adaptor.getMultiples().getDefiningOp(),2413                                 multiples)) {2414    auto rank =2415        cast<tosa::shapeType>(adaptor.getMultiples().getType()).getRank();2416    SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);2417    inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));2418    return success();2419  } else {2420    multiples = convertToMlirShape(multiples);2421  }2422 2423  ShapeAdaptor inputShape(adaptor.getInput1().getType());2424  SmallVector<int64_t> outputShape;2425  if (!inputShape.hasRank()) {2426    outputShape.resize(multiples.size(), ShapedType::kDynamic);2427    inferredReturnShapes.push_back(2428        ShapedTypeComponents(outputShape, inputType));2429    return success();2430  } else if (static_cast<size_t>(inputShape.getRank()) != multiples.size())2431    return failure();2432 2433  // Any non dynamic dimension can be multiplied to a known size.2434  outputShape.reserve(multiples.size());2435  for (int i = 0, s = inputShape.getRank(); i < s; i++) {2436    if (multiples[i] == ShapedType::kDynamic) {2437      outputShape.push_back(ShapedType::kDynamic);2438    } else {2439      int64_t dim = inputShape.getDimSize(i);2440      if (dim != ShapedType::kDynamic)2441        dim *= multiples[i];2442      outputShape.push_back(dim);2443    }2444  }2445 2446  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));2447  return success();2448}2449 2450LogicalResult tosa::TileOp::verify() {2451  if (verifySameElementTypes(*this, /* intype = */ getInput1().getType(),2452                             /* outType = */ getOutput().getType())2453          .failed()) {2454    return failure();2455  }2456  ShapedType inputType = llvm::cast<ShapedType>(getInput1().getType());2457  ShapedType outputType = llvm::cast<ShapedType>(getType());2458 2459  shapeType multiplesType =2460      llvm::cast<tosa::shapeType>(getMultiples().getType());2461 2462  auto multiplesRank = multiplesType.getRank();2463 2464  if (inputType.hasRank()) {2465    if (inputType.getRank() != multiplesRank)2466      return emitOpError("expect 'multiples' to have rank ")2467             << inputType.getRank() << " but got " << multiplesRank << ".";2468    if (outputType.hasRank() && inputType.getRank() != outputType.getRank())2469      return emitOpError("expect same input and output tensor rank.");2470  } else if (outputType.hasRank() && outputType.getRank() != multiplesRank)2471    return emitOpError("expect 'multiples' array to have length ")2472           << outputType.getRank() << " but got " << multiplesRank << ".";2473 2474  SmallVector<int64_t> multiples;2475  if (getConstantMultiples(multiples).succeeded() &&2476      llvm::any_of(multiples, [](int64_t v) { return v <= 0 && v != -1; }))2477    return emitOpError(2478        "expect element of 'multiples' to be positive integer or -1.");2479 2480  return success();2481}2482 2483bool tosa::ReshapeOp::isCompatibleReturnTypes(TypeRange l, TypeRange r) {2484  if (l.size() != r.size() || l.size() != 1)2485    return false;2486  return getElementTypeOrSelf(l[0]) == getElementTypeOrSelf(r[0]);2487}2488 2489LogicalResult tosa::ReshapeOp::inferReturnTypeComponents(2490    MLIRContext *context, ::std::optional<Location> location,2491    ReshapeOp::Adaptor adaptor,2492    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2493  ShapeAdaptor inputShape(adaptor.getInput1().getType());2494  Type inputType = getElementTypeOrSelf(adaptor.getInput1().getType());2495  llvm::SmallVector<int64_t> newShapeValue;2496  if (!tosa::getConstShapeValues(adaptor.getShape().getDefiningOp(),2497                                 newShapeValue)) {2498    auto rank = cast<tosa::shapeType>(adaptor.getShape().getType()).getRank();2499    SmallVector<int64_t> fallback(rank, ShapedType::kDynamic);2500    inferredReturnShapes.push_back(ShapedTypeComponents(fallback, inputType));2501    return success();2502  } else {2503    newShapeValue = convertToMlirShape(newShapeValue);2504  }2505 2506  // We cannot infer from the total number of elements so we must take the2507  // shape attribute as exact.2508  if (!inputShape.hasRank() || !inputShape.hasStaticShape()) {2509    inferredReturnShapes.push_back(2510        ShapedTypeComponents(newShapeValue, inputType));2511    return success();2512  }2513 2514  // Determine the number of elements covered by the slice of all static2515  // dimensions. This allows us to infer the length of the remaining dynamic2516  // dimension.2517  int64_t numElements = inputShape.getNumElements();2518  int64_t staticMul = 1;2519  for (auto val : newShapeValue) {2520    if (ShapedType::isStatic(val)) {2521      staticMul *= val;2522    }2523  }2524 2525  // Determine the length of the dynamic dimension.2526  for (auto &val : newShapeValue) {2527    if (ShapedType::isDynamic(val))2528      val = numElements / staticMul;2529  }2530 2531  inferredReturnShapes.push_back(2532      ShapedTypeComponents(newShapeValue, inputType));2533  return success();2534}2535 2536llvm::LogicalResult tosa::ReshapeOp::verify() {2537  if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),2538                             /* outType = */ getOutput().getType())2539          .failed()) {2540    return failure();2541  }2542  TensorType inputType = getInput1().getType();2543 2544  SmallVector<int64_t> shapeValues;2545  if (!tosa::getConstShapeValues(getShape().getDefiningOp(), shapeValues)) {2546    // skip following checks if shape is not constant2547    return mlir::success();2548  }2549 2550  int missingDims = llvm::count(shapeValues, -1);2551  if (missingDims > 1)2552    return emitOpError() << "expected at most one target dimension to be -1";2553 2554  const auto outputType = dyn_cast<RankedTensorType>(getType());2555  if (!outputType)2556    return success();2557 2558  if ((int64_t)shapeValues.size() != outputType.getRank())2559    return emitOpError() << "new shape does not match result rank";2560 2561  for (auto [newShapeDim, outputShapeDim] :2562       zip(shapeValues, outputType.getShape())) {2563    if (newShapeDim != -1 && newShapeDim != ShapedType::kDynamic &&2564        outputShapeDim != ShapedType::kDynamic && newShapeDim != outputShapeDim)2565      return emitOpError() << "new shape is inconsistent with result shape";2566 2567    if (newShapeDim != ShapedType::kDynamic && newShapeDim < -1)2568      return emitOpError() << "new shape has invalid tensor dimension size "2569                           << newShapeDim;2570  }2571 2572  if (inputType.hasStaticShape()) {2573    int64_t inputElementsNum = inputType.getNumElements();2574    if (outputType.hasStaticShape()) {2575      int64_t outputElementsNum = outputType.getNumElements();2576      if (inputElementsNum != outputElementsNum) {2577        return emitOpError() << "cannot reshape " << inputElementsNum2578                             << " elements into " << outputElementsNum;2579      }2580    }2581 2582    int64_t newShapeElementsNum =2583        llvm::accumulate(shapeValues, int64_t(1), [](int64_t acc, int64_t dim) {2584          return (dim > 0) ? acc * dim : acc;2585        });2586    bool isStaticNewShape =2587        llvm::all_of(shapeValues, [](int64_t s) { return s > 0; });2588    if ((isStaticNewShape && inputElementsNum != newShapeElementsNum) ||2589        (!isStaticNewShape && newShapeElementsNum > inputElementsNum)) {2590      return emitOpError() << "cannot reshape " << inputElementsNum2591                           << " elements into " << newShapeElementsNum;2592    }2593  }2594 2595  return mlir::success();2596}2597 2598// return failure if val is not a constant2599// set zp to -1 if val is non-zero float or val is not integer nor float2600// otherwise set zp to val's constant value2601static FailureOr<int64_t> getZeroPoint(Value val, bool signExtend) {2602  ElementsAttr zpAttr;2603  if (!matchPattern(val, m_Constant(&zpAttr))) {2604    return failure();2605  }2606 2607  Type zpElemType = zpAttr.getElementType();2608 2609  if (llvm::isa<FloatType>(zpElemType)) {2610    if (zpAttr.getValues<APFloat>()[0].isZero()) {2611      return 0;2612    }2613    // return non-zero value to trigger error check2614    return -1;2615  }2616 2617  if (llvm::isa<IntegerType>(zpElemType)) {2618    if (signExtend)2619      return zpAttr.getValues<APInt>()[0].getSExtValue();2620    else2621      return zpAttr.getValues<APInt>()[0].getZExtValue();2622  }2623 2624  // return non-zero value to trigger error check2625  return -1;2626}2627 2628template <typename T>2629static LogicalResult verifyZeroPoint(T op, Value val, const int64_t &zp,2630                                     const std::string &operand) {2631  Type zpElemType = getElementTypeOrSelf(val);2632 2633  if (!zpElemType.isInteger(8) && zp != 0) {2634    // convert operand to lower case for error message2635    std::string lower = operand;2636    llvm::transform(lower, lower.begin(), ::tolower);2637    return op.emitOpError()2638           << lower << " zero point must be zero for non-int8 integer types";2639  }2640 2641  return success();2642}2643 2644static LogicalResult verifyZeroPoint(tosa::RescaleOp op, Value zpVal,2645                                     const int64_t &zp,2646                                     const std::string &operand) {2647  bool isInputZp = (operand == "Input");2648 2649  bool tensorUnsigned =2650      isInputZp ? op.getInputUnsigned() : op.getOutputUnsigned();2651  StringRef tensorName = isInputZp ? "input" : "output";2652 2653  Type zpElemType = getElementTypeOrSelf(zpVal);2654 2655  if (zp != 0) {2656    if (!zpElemType.isInteger(8) &&2657        !(zpElemType.isInteger(16) && tensorUnsigned)) {2658      return op.emitOpError()2659             << "expect " << tensorName << "_zp of 0, got " << zp;2660    }2661    if (zpElemType.isInteger(16) && tensorUnsigned && zp != 32768) {2662      return op.emitOpError() << "expect " << tensorName2663                              << "_zp of 0 or 32768 for unsigned int16 "2664                              << tensorName << ", got " << zp;2665    }2666  }2667 2668  return success();2669}2670 2671#define ZERO_POINT_HELPER(OP, OPERAND_NAME, SIGN_EXTEND)                       \2672  FailureOr<int64_t> tosa::OP::get##OPERAND_NAME##ZeroPoint() {                \2673    return getZeroPoint(get##OPERAND_NAME##Zp(), SIGN_EXTEND);                 \2674  }                                                                            \2675  LogicalResult tosa::OP::verify##OPERAND_NAME##ZeroPoint(int64_t zp) {        \2676    return verifyZeroPoint(*this, get##OPERAND_NAME##Zp(), zp, #OPERAND_NAME); \2677  }2678 2679ZERO_POINT_HELPER(Conv2DOp, Input, true)2680ZERO_POINT_HELPER(Conv2DOp, Weight, true)2681ZERO_POINT_HELPER(Conv3DOp, Input, true)2682ZERO_POINT_HELPER(Conv3DOp, Weight, true)2683ZERO_POINT_HELPER(DepthwiseConv2DOp, Input, true)2684ZERO_POINT_HELPER(DepthwiseConv2DOp, Weight, true)2685ZERO_POINT_HELPER(TransposeConv2DOp, Input, true)2686ZERO_POINT_HELPER(TransposeConv2DOp, Weight, true)2687ZERO_POINT_HELPER(AvgPool2dOp, Input, true)2688ZERO_POINT_HELPER(AvgPool2dOp, Output, true)2689ZERO_POINT_HELPER(MatMulOp, A, true)2690ZERO_POINT_HELPER(MatMulOp, B, true)2691ZERO_POINT_HELPER(NegateOp, Input1, true)2692ZERO_POINT_HELPER(NegateOp, Output, true)2693ZERO_POINT_HELPER(RescaleOp, Input, !getInputUnsigned())2694ZERO_POINT_HELPER(RescaleOp, Output, !getOutputUnsigned())2695#undef ZERO_POINT_HELPER2696 2697LogicalResult tosa::TransposeOp::inferReturnTypeComponents(2698    MLIRContext *context, ::std::optional<Location> location,2699    TransposeOp::Adaptor adaptor,2700    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2701  ShapeAdaptor inputShape(adaptor.getInput1().getType());2702 2703  // If input rank and permutation length is unknown, the output rank is2704  // unknown.2705  if (!inputShape.hasRank()) {2706    inferredReturnShapes.push_back(ShapedTypeComponents());2707    return success();2708  }2709 2710  const auto inputRank = inputShape.getRank();2711 2712  // This would imply the number of permutations does not match the rank of2713  // the input which is illegal.2714  if (adaptor.getPerms().size() != static_cast<size_t>(inputRank)) {2715    return failure();2716  }2717 2718  SmallVector<int64_t> outputShape;2719  // Rank-0 means no permutations matter.2720  if (inputRank == 0) {2721    inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2722    return success();2723  }2724 2725  // Check whether the input dimensions are all the same.2726  bool allTheSame = true;2727  for (int i = 1, s = inputRank; i < s; i++) {2728    if (inputShape.getDimSize(0) != inputShape.getDimSize(i)) {2729      allTheSame = false;2730      break;2731    }2732  }2733 2734  // If all of the input dimensions are the same we don't care about the2735  // permutation.2736  if (allTheSame) {2737    outputShape.resize(inputRank, inputShape.getDimSize(0));2738    inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2739    return success();2740  }2741 2742  outputShape.resize(inputRank, ShapedType::kDynamic);2743 2744  // Constant permutation values must be within the input rank.2745  if (llvm::any_of(adaptor.getPerms(),2746                   [inputRank](const auto i) { return i >= inputRank; }))2747    return failure();2748 2749  outputShape.reserve(inputRank);2750  for (int i = 0, s = inputRank; i < s; i++) {2751    outputShape[i] = inputShape.getDimSize(adaptor.getPerms()[i]);2752  }2753 2754  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2755  return success();2756}2757 2758LogicalResult tosa::TransposeOp::verify() {2759  if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),2760                             /* outType = */ getOutput().getType())2761          .failed()) {2762    return failure();2763  }2764 2765  const ShapeAdaptor inputShape(getInput1().getType());2766  const ShapeAdaptor outputShape(getOutput().getType());2767 2768  const llvm::ArrayRef<int32_t> constantPerms = getPerms();2769 2770  if (inputShape.hasRank() &&2771      constantPerms.size() != static_cast<size_t>(inputShape.getRank()))2772    return emitOpError() << "expected perms attribute to have size "2773                         << inputShape.getRank()2774                         << " (input rank) but got size "2775                         << constantPerms.size();2776 2777  if (inputShape.hasRank() && outputShape.hasRank() &&2778      inputShape.getRank() != outputShape.getRank())2779    return emitOpError()2780           << "expected input tensor rank to equal result tensor rank";2781 2782  if (outputShape.hasRank() &&2783      constantPerms.size() != static_cast<size_t>(outputShape.getRank()))2784    return emitOpError() << "expected perms attribute to have size "2785                         << outputShape.getRank()2786                         << " (output rank) but got size "2787                         << constantPerms.size();2788 2789  if (!llvm::all_of(constantPerms,2790                    [&constantPerms](int32_t s) {2791                      return s >= 0 &&2792                             static_cast<size_t>(s) < constantPerms.size();2793                    }) ||2794      !isPermutationVector(llvm::to_vector(llvm::map_range(2795          constantPerms, [](int32_t v) -> int64_t { return v; }))))2796    return emitOpError() << "expected valid permutation indices";2797 2798  // ERROR_IF(tensor_size(shape1) != tensor_size(shape))2799  if (inputShape.hasStaticShape() && outputShape.hasStaticShape() &&2800      inputShape.getNumElements() != outputShape.getNumElements())2801    return emitOpError() << "expected input1 and output to have same numbers "2802                            "of elements, got "2803                         << inputShape.getNumElements() << " and "2804                         << outputShape.getNumElements();2805 2806  // Verify that the types of the input and output tensors are properly2807  // permuted.2808  if (inputShape.hasRank() && outputShape.hasRank()) {2809    for (auto i = 0; i < outputShape.getRank(); i++) {2810      if (inputShape.isDynamicDim(constantPerms[i]) ||2811          outputShape.isDynamicDim(i))2812        continue;2813 2814      if (inputShape.getDimSize(constantPerms[i]) != outputShape.getDimSize(i))2815        return emitOpError()2816               << "expected output tensor dim " << i << " to match "2817               << "input dim " << constantPerms[i] << " with value of "2818               << inputShape.getDimSize(constantPerms[i]);2819    }2820  }2821 2822  return success();2823}2824 2825LogicalResult TransposeOp::reifyResultShapes(2826    OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes) {2827 2828  const llvm::ArrayRef<int32_t> transposePerms = getPerms();2829 2830  Value input = getInput1();2831  auto inputType = cast<TensorType>(input.getType());2832 2833  SmallVector<OpFoldResult> returnedDims(inputType.getRank());2834  for (auto dim : transposePerms) {2835    int32_t dimInInput = transposePerms[dim];2836    if (inputType.isDynamicDim(dimInInput))2837      returnedDims[dim] =2838          tensor::DimOp::create(builder, getLoc(), input, dimInInput)2839              .getResult();2840    else2841      returnedDims[dim] =2842          builder.getIndexAttr(inputType.getDimSize(dimInInput));2843  }2844 2845  reifiedReturnShapes.emplace_back(std::move(returnedDims));2846  return success();2847}2848 2849LogicalResult tosa::GatherOp::inferReturnTypeComponents(2850    MLIRContext *context, ::std::optional<Location> location,2851    GatherOp::Adaptor adaptor,2852    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2853  llvm::SmallVector<int64_t> outputShape;2854  outputShape.resize(3, ShapedType::kDynamic);2855 2856  ShapeAdaptor valuesShape(adaptor.getValues().getType());2857  if (valuesShape.hasRank()) {2858    outputShape[0] = valuesShape.getDimSize(0);2859    outputShape[2] = valuesShape.getDimSize(2);2860  }2861 2862  ShapeAdaptor indicesShape(adaptor.getIndices().getType());2863  if (indicesShape.hasRank()) {2864    if (outputShape[0] == ShapedType::kDynamic)2865      outputShape[0] = indicesShape.getDimSize(0);2866    if (outputShape[1] == ShapedType::kDynamic)2867      outputShape[1] = indicesShape.getDimSize(1);2868  }2869 2870  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2871  return success();2872}2873 2874LogicalResult tosa::GatherOp::verify() {2875  if (verifySameElementTypes(*this, /* inType = */ getValues().getType(),2876                             /* outType = */ getOutput().getType())2877          .failed()) {2878    return failure();2879  }2880 2881  const ShapeAdaptor valuesShape(getValues().getType());2882  const ShapeAdaptor indicesShape(getIndices().getType());2883  const ShapeAdaptor outputShape(getOutput().getType());2884 2885  int64_t N = ShapedType::kDynamic;2886  int64_t W = ShapedType::kDynamic;2887  int64_t C = ShapedType::kDynamic;2888 2889  if (valuesShape.hasRank()) {2890    N = valuesShape.getDimSize(0);2891    C = valuesShape.getDimSize(2);2892  }2893  if (indicesShape.hasRank()) {2894    const int64_t indicesN = indicesShape.getDimSize(0);2895    W = indicesShape.getDimSize(1);2896    if (N == ShapedType::kDynamic)2897      N = indicesN;2898    else if (indicesN != ShapedType::kDynamic && N != indicesN)2899      return emitOpError() << "requires indices dimension 0 to have size " << N2900                           << ", got " << indicesN;2901  }2902  if (outputShape.hasRank()) {2903    const int64_t outputN = outputShape.getDimSize(0);2904    const int64_t outputW = outputShape.getDimSize(1);2905    const int64_t outputC = outputShape.getDimSize(2);2906    if (N != ShapedType::kDynamic && outputN != ShapedType::kDynamic &&2907        N != outputN)2908      return emitOpError() << "requires output dimension 0 to have size " << N2909                           << ", got " << outputN;2910 2911    if (W != ShapedType::kDynamic && outputW != ShapedType::kDynamic &&2912        W != outputW)2913      return emitOpError() << "requires output dimension 1 to have size " << W2914                           << ", got " << outputW;2915    if (C != ShapedType::kDynamic && outputC != ShapedType::kDynamic &&2916        C != outputC)2917      return emitOpError() << "requires output dimension 2 to have size " << C2918                           << ", got " << outputC;2919  }2920  return success();2921}2922 2923LogicalResult tosa::ResizeOp::inferReturnTypeComponents(2924    MLIRContext *context, ::std::optional<Location> location,2925    ResizeOp::Adaptor adaptor,2926    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {2927  llvm::SmallVector<int64_t, 4> outputShape;2928  outputShape.resize(4, ShapedType::kDynamic);2929 2930  ShapeAdaptor inputShape(adaptor.getInput().getType());2931  if (!inputShape.hasRank())2932    return failure();2933 2934  outputShape[0] = inputShape.getDimSize(0);2935  outputShape[3] = inputShape.getDimSize(3);2936  int64_t inputHeight = inputShape.getDimSize(1);2937  int64_t inputWidth = inputShape.getDimSize(2);2938 2939  if ((inputHeight == ShapedType::kDynamic) ||2940      (inputWidth == ShapedType::kDynamic))2941    return failure();2942 2943  SmallVector<int64_t> scaleInt, offsetInt, borderInt;2944  if (!tosa::getConstShapeValues(adaptor.getScale().getDefiningOp(),2945                                 scaleInt) ||2946      !tosa::getConstShapeValues(adaptor.getOffset().getDefiningOp(),2947                                 offsetInt) ||2948      !tosa::getConstShapeValues(adaptor.getBorder().getDefiningOp(),2949                                 borderInt)) {2950    return failure();2951  }2952 2953  // Compute the output shape based on attributes: scale, offset, and border.2954  const int64_t outputHeight =2955      (((inputHeight - 1) * scaleInt[0] - offsetInt[0] + borderInt[0]) /2956       scaleInt[1]) +2957      1;2958 2959  const int64_t outputWidth =2960      (((inputWidth - 1) * scaleInt[2] - offsetInt[1] + borderInt[1]) /2961       scaleInt[3]) +2962      1;2963 2964  if (outputHeight < 0 || outputWidth < 0) {2965    return emitOptionalError(2966        location,2967        "calculated output height and width must be non-negative, "2968        "got height = ",2969        outputHeight, ", width = ", outputWidth);2970  }2971 2972  outputShape[1] = outputHeight;2973  outputShape[2] = outputWidth;2974  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));2975  return success();2976}2977 2978LogicalResult tosa::ResizeOp::verify() {2979  const Value input = getInput();2980  const Value output = getOutput();2981  const RankedTensorType inputType =2982      llvm::dyn_cast<RankedTensorType>(input.getType());2983  const RankedTensorType outputType =2984      llvm::dyn_cast<RankedTensorType>(output.getType());2985 2986  SmallVector<int64_t> scaleValues;2987  SmallVector<int64_t> offsetValues;2988  SmallVector<int64_t> borderValues;2989  if (!tosa::getConstShapeValues(getScale().getDefiningOp(), scaleValues) ||2990      !tosa::getConstShapeValues(getOffset().getDefiningOp(), offsetValues) ||2991      !tosa::getConstShapeValues(getBorder().getDefiningOp(), borderValues)) {2992    // Skip following checks if shape is not constant2993    return success();2994  }2995 2996  if (llvm::any_of(scaleValues, [](int64_t s) { return s <= 0; }))2997    return emitOpError("expect all scale values to be > 0, got ")2998           << scaleValues;2999 3000  const int64_t scaleYN = scaleValues[0];3001  const int64_t scaleYD = scaleValues[1];3002  const int64_t scaleXN = scaleValues[2];3003  const int64_t scaleXD = scaleValues[3];3004 3005  const int64_t offsetY = offsetValues[0];3006  const int64_t offsetX = offsetValues[1];3007 3008  const int64_t borderY = borderValues[0];3009  const int64_t borderX = borderValues[1];3010 3011  if (!inputType)3012    return success();3013  if (!outputType)3014    return success();3015 3016  const int64_t oh = outputType.getDimSize(1);3017  const int64_t ow = outputType.getDimSize(2);3018  const int64_t ih = inputType.getDimSize(1);3019  const int64_t iw = inputType.getDimSize(2);3020 3021  // Don't check with input height that could be broadcast (ih != 1)3022  // since Linalg, a consumer of TOSA, expects broadcasting support3023  // in resize to be available. Taking the cautious approach for now,3024  // we can consider removing support for broadcasting later.3025  if (ih != ShapedType::kDynamic && ih != 1) {3026    const std::optional<int64_t> calculatedOutHeightMinusOne =3027        idivCheck((ih - 1) * scaleYN - offsetY + borderY, scaleYD);3028    if (!calculatedOutHeightMinusOne.has_value())3029      return emitOpError("expected (input_height - 1) * scale_y_n - offset_y + "3030                         "border_y ")3031             << "to be wholly divisible by scale_y_d, got ((" << ih3032             << " - 1) * " << scaleYN << " - " << offsetY << " + " << borderY3033             << ") / " << scaleYD;3034    const int64_t calculatedOutHeight = calculatedOutHeightMinusOne.value() + 1;3035    if (oh != ShapedType::kDynamic && calculatedOutHeight != oh)3036      return emitOpError("calculated output height did not match expected: ")3037             << "calculated=" << calculatedOutHeight << ", expected=" << oh;3038  }3039 3040  // Don't check with input width that could be broadcast (iw != 1)3041  // since Linalg, a consumer of TOSA, expects broadcasting support3042  // in resize to be available. Taking the cautious approach for now,3043  // we can consider removing support for broadcasting later.3044  if (iw != ShapedType::kDynamic && iw != 1) {3045    const int64_t scaledInWidth = (iw - 1) * scaleXN - offsetX + borderX;3046    const std::optional<int64_t> calculatedOutWidthMinusOne =3047        idivCheck(scaledInWidth, scaleXD);3048    if (!calculatedOutWidthMinusOne.has_value())3049      return emitOpError("expected (input_width - 1) * scale_x_n - offset_x + "3050                         "border_x ")3051             << "to be wholly divisible by scale_x_d, got ((" << iw3052             << " - 1) * " << scaleXN << " - " << offsetX << " + " << borderX3053             << ") / " << scaleXD;3054    const int64_t calculatedOutWidth = calculatedOutWidthMinusOne.value() + 1;3055    if (ow != ShapedType::kDynamic && calculatedOutWidth != ow)3056      return emitOpError("calculated output width did not match expected: ")3057             << "calculated=" << calculatedOutWidth << ", expected=" << ow;3058  }3059 3060  return success();3061}3062 3063LogicalResult tosa::ScatterOp::inferReturnTypeComponents(3064    MLIRContext *context, ::std::optional<Location> location,3065    ScatterOp::Adaptor adaptor,3066    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3067  llvm::SmallVector<int64_t> outputShape;3068  outputShape.resize(3, ShapedType::kDynamic);3069 3070  ShapeAdaptor valuesInShape(adaptor.getValuesIn().getType());3071  if (valuesInShape.hasRank()) {3072    outputShape[0] = valuesInShape.getDimSize(0);3073    outputShape[1] = valuesInShape.getDimSize(1);3074    outputShape[2] = valuesInShape.getDimSize(2);3075  }3076 3077  ShapeAdaptor indicesShape(adaptor.getIndices().getType());3078  if (indicesShape.hasRank()) {3079    if (outputShape[0] == ShapedType::kDynamic)3080      outputShape[0] = indicesShape.getDimSize(0);3081  }3082 3083  ShapeAdaptor inputShape(adaptor.getInput().getType());3084  if (inputShape.hasRank()) {3085    if (outputShape[0] == ShapedType::kDynamic)3086      outputShape[0] = inputShape.getDimSize(0);3087    if (outputShape[2] == ShapedType::kDynamic)3088      outputShape[2] = inputShape.getDimSize(2);3089  }3090 3091  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3092  return success();3093}3094 3095LogicalResult tosa::ScatterOp::verify() {3096  if (verifySameElementTypes(*this, /* inType = */ getValuesIn().getType(),3097                             /* outType = */ getValuesOut().getType())3098          .failed() ||3099      verifySameElementTypes(*this, /* inType = */ getInput().getType(),3100                             /* outType = */ getValuesOut().getType())3101          .failed()) {3102    return failure();3103  }3104 3105  const ShapeAdaptor valuesInShape(getValuesIn().getType());3106  const ShapeAdaptor indicesShape(getIndices().getType());3107  const ShapeAdaptor inputShape(getInput().getType());3108  const ShapeAdaptor outputShape(getValuesOut().getType());3109 3110  int64_t N = ShapedType::kDynamic;3111  int64_t K = ShapedType::kDynamic;3112  int64_t W = ShapedType::kDynamic;3113  int64_t C = ShapedType::kDynamic;3114  if (valuesInShape.hasRank()) {3115    N = valuesInShape.getDimSize(0);3116    K = valuesInShape.getDimSize(1);3117    C = valuesInShape.getDimSize(2);3118  }3119  if (indicesShape.hasRank()) {3120    const int64_t indicesN = indicesShape.getDimSize(0);3121    W = indicesShape.getDimSize(1);3122    if (N == ShapedType::kDynamic)3123      N = indicesN;3124    else if (indicesN != ShapedType::kDynamic && N != indicesN)3125      return emitOpError() << "requires indices dimension 0 to have size " << N3126                           << ", got " << indicesN;3127  }3128  if (inputShape.hasRank()) {3129    const int64_t inputN = inputShape.getDimSize(0);3130    const int64_t inputW = inputShape.getDimSize(1);3131    const int64_t inputC = inputShape.getDimSize(2);3132    if (N == ShapedType::kDynamic)3133      N = inputN;3134    else if (inputN != ShapedType::kDynamic && N != inputN)3135      return emitOpError() << "requires input dimension 0 to have size " << N3136                           << ", got " << inputN;3137    if (W == ShapedType::kDynamic)3138      W = inputW;3139    else if (inputW != ShapedType::kDynamic && W != inputW)3140      return emitOpError() << "requires input dimension 1 to have size " << W3141                           << ", got " << inputW;3142 3143    if (C == ShapedType::kDynamic)3144      C = inputC;3145    else if (inputC != ShapedType::kDynamic && C != inputC)3146      return emitOpError() << "requires input dimension 2 to have size " << C3147                           << ", got " << inputC;3148  }3149  if (outputShape.hasRank()) {3150    const int64_t outputN = outputShape.getDimSize(0);3151    const int64_t outputK = outputShape.getDimSize(1);3152    const int64_t outputC = outputShape.getDimSize(2);3153    if (N != ShapedType::kDynamic && outputN != ShapedType::kDynamic &&3154        N != outputN)3155      return emitOpError() << "requires values_out dimension 0 to have size "3156                           << N << ", got " << outputN;3157    if (K == ShapedType::kDynamic)3158      K = outputK;3159    else if (outputK != ShapedType::kDynamic && K != outputK)3160      return emitOpError() << "requires values_out dimension 1 to have size "3161                           << K << ", got " << outputK;3162    if (C != ShapedType::kDynamic && outputC != ShapedType::kDynamic &&3163        C != outputC)3164      return emitOpError() << "requires values_out dimension 2 to have size "3165                           << C << ", got " << outputC;3166  }3167  if (K != ShapedType::kDynamic && W != ShapedType::kDynamic && !(K >= W))3168    return emitOpError() << "requires dimensions K >= W, got K=" << K3169                         << " and W=" << W;3170 3171  return success();3172}3173 3174static LogicalResult ReduceInferReturnTypes(3175    ShapeAdaptor operandShape, Type inputType, IntegerAttr axis,3176    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3177  int64_t axisVal = axis.getValue().getSExtValue();3178  if (!operandShape.hasRank() || operandShape.getRank() <= axisVal) {3179    inferredReturnShapes.push_back(ShapedTypeComponents(inputType));3180    return success();3181  }3182 3183  SmallVector<int64_t> outputShape;3184  operandShape.getDims(outputShape);3185  outputShape[axisVal] = 1;3186  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape, inputType));3187  return success();3188}3189 3190#define COMPATIBLE_RETURN_TYPES(OP)                                            \3191  bool OP::isCompatibleReturnTypes(TypeRange l, TypeRange r) {                 \3192    if (l.size() != r.size() || l.size() != 1)                                 \3193      return false;                                                            \3194    if (getElementTypeOrSelf(l[0]) != getElementTypeOrSelf(r[0]))              \3195      return false;                                                            \3196    return succeeded(verifyCompatibleShape(l[0], r[0]));                       \3197  }3198 3199#define REDUCE_SHAPE_INFER(OP)                                                 \3200  LogicalResult OP::inferReturnTypeComponents(                                 \3201      MLIRContext *context, ::std::optional<Location> location,                \3202      OP::Adaptor adaptor,                                                     \3203      SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {           \3204    Type inputType =                                                           \3205        llvm::cast<TensorType>(adaptor.getInput().getType()).getElementType(); \3206    ShapeAdaptor inputShape(adaptor.getInput().getType());                     \3207    const Properties &prop = adaptor.getProperties();                          \3208    return ReduceInferReturnTypes(inputShape, inputType, prop.axis,            \3209                                  inferredReturnShapes);                       \3210  }                                                                            \3211  COMPATIBLE_RETURN_TYPES(OP)3212 3213REDUCE_SHAPE_INFER(tosa::ReduceAllOp)3214REDUCE_SHAPE_INFER(tosa::ReduceAnyOp)3215REDUCE_SHAPE_INFER(tosa::ReduceMaxOp)3216REDUCE_SHAPE_INFER(tosa::ReduceMinOp)3217REDUCE_SHAPE_INFER(tosa::ReduceProductOp)3218REDUCE_SHAPE_INFER(tosa::ReduceSumOp)3219#undef REDUCE_SHAPE_INFER3220COMPATIBLE_RETURN_TYPES(tosa::ConcatOp)3221#undef COMPATIBLE_RETURN_TYPES3222 3223template <typename T>3224static LogicalResult verifyReduceOp(T op) {3225  // All TOSA reduce Ops have input, output and axis.3226  TensorType inputType = op.getInput().getType();3227  TensorType outputType = op.getOutput().getType();3228  int32_t reduceAxis = op.getAxis();3229 3230  if (reduceAxis < 0) {3231    op.emitOpError("reduce axis must not be negative");3232    return failure();3233  }3234  if (inputType.hasRank()) {3235    int64_t inputRank = inputType.getRank();3236    // We allow for a special case where the input/output shape has rank 0 and3237    // axis is also 0.3238    if (reduceAxis >= inputRank && (reduceAxis != 0 || inputRank != 0)) {3239      op.emitOpError("expect input tensor rank (")3240          << inputRank << ") to be larger than reduce axis (" << reduceAxis3241          << ")";3242      return failure();3243    }3244  }3245  if (outputType.hasRank()) {3246    int64_t outputRank = outputType.getRank();3247    if (inputType.hasRank() && outputRank != inputType.getRank()) {3248      op.emitOpError(3249          "expect output tensor rank to be equal to input tensor rank");3250      return failure();3251    }3252    if (reduceAxis >= outputRank && (reduceAxis != 0 || outputRank != 0)) {3253      op.emitOpError("expect output tensor rank (")3254          << outputRank << ") to be larger than reduce axis (" << reduceAxis3255          << ")";3256      return failure();3257    }3258    // We can only verify the reduced dimension size to be 1 if this is not3259    // the special case of output rank == 0.3260    if (outputRank != 0) {3261      auto outputShape = outputType.getShape();3262      if (!outputType.isDynamicDim(reduceAxis) &&3263          outputShape[reduceAxis] != 1) {3264        op.emitOpError("expect reduced dimension size to be 1, got ")3265            << outputShape[reduceAxis];3266        return failure();3267      }3268    }3269  }3270  return success();3271}3272 3273LogicalResult tosa::ReduceAllOp::verify() { return verifyReduceOp(*this); }3274LogicalResult tosa::ReduceAnyOp::verify() { return verifyReduceOp(*this); }3275LogicalResult tosa::ReduceMaxOp::verify() { return verifyReduceOp(*this); }3276LogicalResult tosa::ReduceMinOp::verify() { return verifyReduceOp(*this); }3277LogicalResult tosa::ReduceProductOp::verify() { return verifyReduceOp(*this); }3278LogicalResult tosa::ReduceSumOp::verify() { return verifyReduceOp(*this); }3279 3280static LogicalResult NAryInferReturnTypes(3281    const ValueShapeRange &operands,3282    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3283  llvm::SmallVector<int64_t> outShape;3284  if (resolveBroadcastShape(operands, outShape).failed()) {3285    inferredReturnShapes.push_back(ShapedTypeComponents());3286  } else {3287    inferredReturnShapes.push_back(ShapedTypeComponents(outShape));3288  }3289  return success();3290}3291 3292#define NARY_SHAPE_INFER(OP)                                                   \3293  LogicalResult OP::inferReturnTypeComponents(                                 \3294      MLIRContext *context, ::std::optional<Location> location,                \3295      ValueShapeRange operands, DictionaryAttr attributes,                     \3296      OpaqueProperties properties, RegionRange regions,                        \3297      SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {           \3298    return NAryInferReturnTypes(operands, inferredReturnShapes);               \3299  }3300 3301NARY_SHAPE_INFER(tosa::AbsOp)3302NARY_SHAPE_INFER(tosa::AddOp)3303NARY_SHAPE_INFER(tosa::ArithmeticRightShiftOp)3304NARY_SHAPE_INFER(tosa::BitwiseAndOp)3305NARY_SHAPE_INFER(tosa::BitwiseOrOp)3306NARY_SHAPE_INFER(tosa::BitwiseXorOp)3307NARY_SHAPE_INFER(tosa::BitwiseNotOp)3308NARY_SHAPE_INFER(tosa::CastOp)3309NARY_SHAPE_INFER(tosa::CeilOp)3310NARY_SHAPE_INFER(tosa::ClampOp)3311NARY_SHAPE_INFER(tosa::ClzOp)3312NARY_SHAPE_INFER(tosa::CosOp)3313NARY_SHAPE_INFER(tosa::ExpOp)3314NARY_SHAPE_INFER(tosa::FloorOp)3315NARY_SHAPE_INFER(tosa::GreaterEqualOp)3316NARY_SHAPE_INFER(tosa::GreaterOp)3317NARY_SHAPE_INFER(tosa::IdentityOp)3318NARY_SHAPE_INFER(tosa::IntDivOp)3319NARY_SHAPE_INFER(tosa::LogOp)3320NARY_SHAPE_INFER(tosa::LogicalAndOp)3321NARY_SHAPE_INFER(tosa::LogicalLeftShiftOp)3322NARY_SHAPE_INFER(tosa::LogicalNotOp)3323NARY_SHAPE_INFER(tosa::LogicalOrOp)3324NARY_SHAPE_INFER(tosa::LogicalRightShiftOp)3325NARY_SHAPE_INFER(tosa::LogicalXorOp)3326NARY_SHAPE_INFER(tosa::MaximumOp)3327NARY_SHAPE_INFER(tosa::MinimumOp)3328NARY_SHAPE_INFER(tosa::PowOp)3329NARY_SHAPE_INFER(tosa::ReciprocalOp)3330NARY_SHAPE_INFER(tosa::ReverseOp)3331NARY_SHAPE_INFER(tosa::RsqrtOp)3332NARY_SHAPE_INFER(tosa::SinOp)3333NARY_SHAPE_INFER(tosa::SelectOp)3334NARY_SHAPE_INFER(tosa::SubOp)3335NARY_SHAPE_INFER(tosa::TanhOp)3336NARY_SHAPE_INFER(tosa::ErfOp)3337NARY_SHAPE_INFER(tosa::SigmoidOp)3338#undef PRED_SHAPE_INFER3339 3340LogicalResult tosa::NegateOp::inferReturnTypeComponents(3341    MLIRContext *context, ::std::optional<Location> location,3342    NegateOp::Adaptor adaptor,3343    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3344  ShapeAdaptor inputShape(adaptor.getInput1().getType());3345  inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));3346  return success();3347}3348 3349LogicalResult tosa::NegateOp::verify() {3350  // Verify same element type3351  const Type input1Type = getInput1().getType();3352  const Type outputType = getOutput().getType();3353  if (verifySameElementTypes(*this, input1Type, outputType).failed())3354    return failure();3355 3356  // Verify same shape3357  const SmallVector<Type, 2> types = {input1Type, outputType};3358  if (failed(verifyCompatibleShapes(types)))3359    return emitOpError() << "requires the same shape for input1 and output";3360 3361  const Type input1EType = getStorageElementTypeOrSelf(getInput1().getType());3362  const Type input1ZpEType =3363      getStorageElementTypeOrSelf(getInput1Zp().getType());3364  if (input1EType != input1ZpEType) {3365    return emitOpError("expect both input1 and its zero point are the same "3366                       "element type, got ")3367           << input1EType << " and " << input1ZpEType;3368  }3369  const Type outputEType = getStorageElementTypeOrSelf(getOutput().getType());3370  const Type outputZpEType =3371      getStorageElementTypeOrSelf(getOutputZp().getType());3372  if (outputEType != outputZpEType) {3373    return emitOpError("expect both output and its zero point are the same "3374                       "element type, got ")3375           << outputEType << " and " << outputZpEType;3376  }3377 3378  FailureOr<int64_t> maybeIZp = getInput1ZeroPoint();3379  if (succeeded(maybeIZp) && verifyInput1ZeroPoint(*maybeIZp).failed())3380    return failure();3381 3382  FailureOr<int64_t> maybeOZp = getOutputZeroPoint();3383  if (succeeded(maybeOZp) && verifyOutputZeroPoint(*maybeOZp).failed())3384    return failure();3385 3386  return success();3387}3388 3389static LogicalResult poolingInferReturnTypes(3390    ShapeAdaptor inputShape, ArrayRef<int64_t> kernel, ArrayRef<int64_t> stride,3391    ArrayRef<int64_t> pad,3392    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3393  llvm::SmallVector<int64_t> outputShape;3394  outputShape.resize(4, ShapedType::kDynamic);3395 3396  // We only know the rank if the input type is unranked.3397  if (!inputShape) {3398    inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3399    return success();3400  }3401 3402  // Batch and number of channels are identical for pooling layer.3403  outputShape[0] = inputShape.getDimSize(0);3404  outputShape[3] = inputShape.getDimSize(3);3405 3406  int64_t height = inputShape.getDimSize(1);3407  int64_t width = inputShape.getDimSize(2);3408 3409  if (ShapedType::isStatic(height)) {3410    int64_t padded = height + pad[0] + pad[1] - kernel[0];3411    outputShape[1] = padded / stride[0] + 1;3412  }3413 3414  if (ShapedType::isStatic(width)) {3415    int64_t padded = width + pad[2] + pad[3] - kernel[1];3416    outputShape[2] = padded / stride[1] + 1;3417  }3418 3419  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3420  return success();3421}3422 3423LogicalResult Conv2DOp::inferReturnTypeComponents(3424    MLIRContext *context, ::std::optional<Location> location,3425    Conv2DOp::Adaptor adaptor,3426    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3427  llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);3428 3429  int64_t inputWidth = ShapedType::kDynamic;3430  int64_t inputHeight = ShapedType::kDynamic;3431  int64_t weightWidth = ShapedType::kDynamic;3432  int64_t weightHeight = ShapedType::kDynamic;3433 3434  // Input shape describes input width/height and batch.3435 3436  ShapeAdaptor inputShape(adaptor.getInput().getType());3437  if (inputShape.hasRank()) {3438    outputShape[0] = inputShape.getDimSize(0);3439    inputHeight = inputShape.getDimSize(1);3440    inputWidth = inputShape.getDimSize(2);3441  }3442 3443  // Weight shapes describes the filter width/height and the output channels.3444  ShapeAdaptor weightShape(adaptor.getWeight().getType());3445  if (weightShape.hasRank()) {3446    outputShape[3] = weightShape.getDimSize(0);3447    weightHeight = weightShape.getDimSize(1);3448    weightWidth = weightShape.getDimSize(2);3449  }3450 3451  // Bias shape can describe the output channels.3452  ShapeAdaptor biasShape(adaptor.getBias().getType());3453  if (biasShape.hasRank()) {3454    outputShape[3] = ShapedType::isDynamic(outputShape[3])3455                         ? biasShape.getDimSize(0)3456                         : outputShape[3];3457  }3458 3459  llvm::ArrayRef<int64_t> dilation = adaptor.getDilation();3460  llvm::ArrayRef<int64_t> stride = adaptor.getStride();3461  llvm::ArrayRef<int64_t> padding = adaptor.getPad();3462 3463  if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {3464    int64_t inputSize = inputHeight + padding[0] + padding[1];3465    int64_t filterSize = (weightHeight - 1) * dilation[0] + 1;3466    int64_t unstridedResult = inputSize - filterSize + 1;3467    outputShape[1] = (unstridedResult - 1) / stride[0] + 1;3468  }3469 3470  if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {3471    int64_t inputSize = inputWidth + padding[2] + padding[3];3472    int64_t filterSize = (weightWidth - 1) * dilation[1] + 1;3473    int64_t unstridedResult = inputSize - filterSize + 1;3474    outputShape[2] = (unstridedResult - 1) / stride[1] + 1;3475  }3476 3477  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3478  return success();3479}3480 3481LogicalResult Conv2DOp::verify() {3482  if (verifyConvOp(*this).failed() || verifyConvOpModes(*this).failed() ||3483      verifyConvOpErrorIf(*this).failed())3484    return failure();3485  return success();3486}3487 3488LogicalResult Conv3DOp::inferReturnTypeComponents(3489    MLIRContext *context, ::std::optional<Location> location,3490    Conv3DOp::Adaptor adaptor,3491    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3492  llvm::SmallVector<int64_t> outputShape(5, ShapedType::kDynamic);3493 3494  int64_t inputWidth = ShapedType::kDynamic;3495  int64_t inputHeight = ShapedType::kDynamic;3496  int64_t inputDepth = ShapedType::kDynamic;3497 3498  int64_t weightWidth = ShapedType::kDynamic;3499  int64_t weightHeight = ShapedType::kDynamic;3500  int64_t weightDepth = ShapedType::kDynamic;3501 3502  // Input shape describes input width/height and batch.3503  ShapeAdaptor inputShape(adaptor.getInput().getType());3504  if (inputShape.hasRank()) {3505    outputShape[0] = inputShape.getDimSize(0);3506    inputDepth = inputShape.getDimSize(1);3507    inputHeight = inputShape.getDimSize(2);3508    inputWidth = inputShape.getDimSize(3);3509  }3510 3511  // Weight shapes describes the filter width/height and the output channels.3512  ShapeAdaptor weightShape(adaptor.getWeight().getType());3513  if (weightShape.hasRank()) {3514    outputShape[4] = weightShape.getDimSize(0);3515    weightDepth = weightShape.getDimSize(1);3516    weightHeight = weightShape.getDimSize(2);3517    weightWidth = weightShape.getDimSize(3);3518  }3519 3520  // Bias shape can describe the output channels.3521  ShapeAdaptor biasShape(adaptor.getBias().getType());3522  if (biasShape.hasRank() && ShapedType::isDynamic(outputShape[4])) {3523    outputShape[4] = biasShape.getDimSize(0);3524  }3525 3526  llvm::ArrayRef<int64_t> dilation = adaptor.getDilation();3527  llvm::ArrayRef<int64_t> stride = adaptor.getStride();3528  llvm::ArrayRef<int64_t> pad = adaptor.getPad();3529 3530  if (ShapedType::isStatic(inputDepth) && ShapedType::isStatic(weightDepth)) {3531    int32_t inputSize = inputDepth + pad[0] + pad[1];3532    int32_t filterSize = (weightDepth - 1) * dilation[0] + 1;3533    int32_t unstridedResult = inputSize - filterSize + 1;3534    outputShape[1] = (unstridedResult - 1) / stride[0] + 1;3535  }3536 3537  if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {3538    int32_t inputSize = inputHeight + pad[2] + pad[3];3539    int32_t filterSize = (weightHeight - 1) * dilation[1] + 1;3540    int32_t unstridedResult = inputSize - filterSize + 1;3541    outputShape[2] = (unstridedResult - 1) / stride[1] + 1;3542  }3543 3544  if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {3545    int32_t inputSize = inputWidth + pad[4] + pad[5];3546    int32_t filterSize = (weightWidth - 1) * dilation[2] + 1;3547    int32_t unstridedResult = inputSize - filterSize + 1;3548    outputShape[3] = (unstridedResult - 1) / stride[2] + 1;3549  }3550 3551  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3552  return success();3553}3554 3555LogicalResult Conv3DOp::verify() {3556  if (verifyConvOp(*this).failed() || verifyConvOpModes(*this).failed() ||3557      verifyConvOpErrorIf(*this).failed())3558    return failure();3559  return success();3560}3561 3562LogicalResult AvgPool2dOp::inferReturnTypeComponents(3563    MLIRContext *context, ::std::optional<Location> location,3564    AvgPool2dOp::Adaptor adaptor,3565    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3566  ShapeAdaptor inputShape(adaptor.getInput().getType());3567  const Properties &prop = adaptor.getProperties();3568  return poolingInferReturnTypes(inputShape, prop.kernel, prop.stride, prop.pad,3569                                 inferredReturnShapes);3570}3571 3572LogicalResult MaxPool2dOp::inferReturnTypeComponents(3573    MLIRContext *context, ::std::optional<Location> location,3574    MaxPool2dOp::Adaptor adaptor,3575    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3576  ShapeAdaptor inputShape(adaptor.getInput().getType());3577  const Properties &prop = adaptor.getProperties();3578  return poolingInferReturnTypes(inputShape, prop.kernel, prop.stride, prop.pad,3579                                 inferredReturnShapes);3580}3581 3582LogicalResult MaxPool2dOp::verify() {3583  if (failed(verifySameElementTypes(*this, /* intype = */ getInput().getType(),3584                                    /* outType = */ getOutput().getType())))3585    return failure();3586 3587  if (failed(verifyPoolingOp(*this)))3588    return failure();3589 3590  return success();3591}3592 3593LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents(3594    MLIRContext *context, ::std::optional<Location> location,3595    DepthwiseConv2DOp::Adaptor adaptor,3596    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3597  llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);3598 3599  int64_t inputWidth = ShapedType::kDynamic;3600  int64_t inputHeight = ShapedType::kDynamic;3601  int64_t inputChannels = ShapedType::kDynamic;3602 3603  int64_t weightWidth = ShapedType::kDynamic;3604  int64_t weightHeight = ShapedType::kDynamic;3605  int64_t depthChannels = ShapedType::kDynamic;3606 3607  // Input shape describes input width/height and batch.3608  ShapeAdaptor inputShape(adaptor.getInput().getType());3609  if (inputShape.hasRank()) {3610    outputShape[0] = inputShape.getDimSize(0);3611    inputHeight = inputShape.getDimSize(1);3612    inputWidth = inputShape.getDimSize(2);3613    inputChannels = inputShape.getDimSize(3);3614  }3615 3616  // Weight shapes describes the filter width/height and the output channels.3617  ShapeAdaptor weightShape(adaptor.getWeight().getType());3618  if (weightShape.hasRank()) {3619    weightHeight = weightShape.getDimSize(0);3620    weightWidth = weightShape.getDimSize(1);3621    inputChannels = ShapedType::isDynamic(inputChannels)3622                        ? weightShape.getDimSize(2)3623                        : inputChannels;3624    depthChannels = weightShape.getDimSize(3);3625  }3626 3627  // If both inputChannels and depthChannels are available we can determine3628  // the output channels.3629  if (ShapedType::isStatic(inputChannels) &&3630      ShapedType::isStatic(depthChannels)) {3631    outputShape[3] = inputChannels * depthChannels;3632  }3633 3634  // Bias shape can describe the output channels.3635  ShapeAdaptor biasShape(adaptor.getBias().getType());3636  if (biasShape.hasRank()) {3637    outputShape[3] = ShapedType::isDynamic(outputShape[3])3638                         ? biasShape.getDimSize(0)3639                         : outputShape[3];3640  }3641 3642  llvm::ArrayRef<int64_t> dilation = adaptor.getDilation();3643  llvm::ArrayRef<int64_t> padding = adaptor.getPad();3644  llvm::ArrayRef<int64_t> stride = adaptor.getStride();3645 3646  if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {3647    int64_t inputSize = inputHeight + padding[0] + padding[1];3648    int64_t filterSize = (weightHeight - 1) * dilation[0] + 1;3649    int64_t unstridedResult = inputSize - filterSize + 1;3650    outputShape[1] = (unstridedResult - 1) / stride[0] + 1;3651  }3652 3653  if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {3654    int64_t inputSize = inputWidth + padding[2] + padding[3];3655    int64_t filterSize = (weightWidth - 1) * dilation[1] + 1;3656    int64_t unstridedResult = inputSize - filterSize + 1;3657    outputShape[2] = (unstridedResult - 1) / stride[1] + 1;3658  }3659 3660  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3661  return success();3662}3663 3664LogicalResult DepthwiseConv2DOp::verify() {3665  if (verifyConvOp(*this).failed() || verifyConvOpModes(*this).failed() ||3666      verifyConvOpErrorIf(*this).failed())3667    return failure();3668  return success();3669}3670 3671LogicalResult TransposeConv2DOp::inferReturnTypeComponents(3672    MLIRContext *context, ::std::optional<Location> location,3673    TransposeConv2DOp::Adaptor adaptor,3674    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3675  llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamic);3676 3677  int64_t inputWidth = ShapedType::kDynamic;3678  int64_t inputHeight = ShapedType::kDynamic;3679  int64_t weightWidth = ShapedType::kDynamic;3680  int64_t weightHeight = ShapedType::kDynamic;3681 3682  // Input shape describes input width/height and batch.3683  ShapeAdaptor inputShape(adaptor.getInput().getType());3684  if (inputShape.hasRank()) {3685    outputShape[0] = ShapedType::isDynamic(outputShape[0])3686                         ? inputShape.getDimSize(0)3687                         : outputShape[0];3688    inputHeight = inputShape.getDimSize(1);3689    inputWidth = inputShape.getDimSize(2);3690  }3691 3692  // Weight shapes describes the filter width/height and the output channels.3693  ShapeAdaptor weightShape(adaptor.getWeight().getType());3694  if (weightShape.hasRank()) {3695    outputShape[3] = ShapedType::isDynamic(outputShape[3])3696                         ? weightShape.getDimSize(0)3697                         : outputShape[3];3698    weightHeight = weightShape.getDimSize(1);3699    weightWidth = weightShape.getDimSize(2);3700  }3701 3702  // Bias shape can describe the output channels.3703  ShapeAdaptor biasShape(adaptor.getInput().getType());3704  if (biasShape.hasRank()) {3705    outputShape[3] = ShapedType::isDynamic(outputShape[3])3706                         ? biasShape.getDimSize(0)3707                         : outputShape[3];3708  }3709 3710  llvm::ArrayRef<int64_t> padding = adaptor.getOutPad();3711  llvm::ArrayRef<int64_t> stride = adaptor.getStride();3712 3713  if (ShapedType::isStatic(inputHeight) && ShapedType::isStatic(weightHeight)) {3714    int64_t calculateSize =3715        (inputHeight - 1) * stride[0] + padding[0] + padding[1] + weightHeight;3716    outputShape[1] =3717        ShapedType::isDynamic(outputShape[1]) ? calculateSize : outputShape[1];3718  }3719 3720  if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(weightWidth)) {3721    int64_t calculateSize =3722        (inputWidth - 1) * stride[1] + padding[2] + padding[3] + weightWidth;3723    outputShape[2] =3724        ShapedType::isDynamic(outputShape[2]) ? calculateSize : outputShape[2];3725  }3726 3727  inferredReturnShapes.push_back(ShapedTypeComponents(outputShape));3728  return success();3729}3730 3731LogicalResult TransposeConv2DOp::verify() {3732  if (verifyConvOp(*this).failed() || verifyConvOpModes(*this).failed())3733    return failure();3734 3735  const llvm::ArrayRef<int64_t> strides = getStride();3736  const int64_t strideY = strides[0];3737  const int64_t strideX = strides[1];3738 3739  if (strideY < 1 || strideX < 1)3740    return emitOpError("expect all stride values to be >= 1, got [")3741           << strides << "]";3742 3743  const auto checkPadAgainstKernelDim =3744      [this](int64_t pad_value, int64_t kernel_dim_size,3745             llvm::StringRef pad_name,3746             llvm::StringRef kernel_dim_name) -> LogicalResult {3747    if (pad_value <= -kernel_dim_size)3748      return emitOpError("expected ")3749             << pad_name << " > -" << kernel_dim_name3750             << ", but got: " << pad_name << "=" << pad_value << " and "3751             << kernel_dim_name << "=" << kernel_dim_size;3752    return success();3753  };3754 3755  const llvm::ArrayRef<int64_t> padding = getOutPad();3756  const int64_t outPadTop = padding[0];3757  const int64_t outPadBottom = padding[1];3758  const int64_t outPadLeft = padding[2];3759  const int64_t outPadRight = padding[3];3760 3761  const auto weightType =3762      llvm::dyn_cast<RankedTensorType>(getWeight().getType());3763 3764  if (weightType) {3765    const int64_t kernelHeight = weightType.getDimSize(1);3766    if (ShapedType::isStatic(kernelHeight)) {3767      if (failed(checkPadAgainstKernelDim(outPadTop, kernelHeight,3768                                          "out_pad_top", "KH")))3769        return failure();3770 3771      if (failed(checkPadAgainstKernelDim(outPadBottom, kernelHeight,3772                                          "out_pad_bottom", "KH")))3773        return failure();3774    }3775 3776    const int64_t kernelWidth = weightType.getDimSize(2);3777    if (ShapedType::isStatic(kernelWidth)) {3778      if (failed(checkPadAgainstKernelDim(outPadLeft, kernelWidth,3779                                          "out_pad_left", "KW")))3780        return failure();3781 3782      if (failed(checkPadAgainstKernelDim(outPadRight, kernelWidth,3783                                          "out_pad_right", "KW")))3784        return failure();3785    }3786  }3787 3788  // Rest of the checks depend on the output type being a RankedTensorType3789  const auto outputType =3790      llvm::dyn_cast<RankedTensorType>(getOutput().getType());3791  if (!outputType)3792    return success();3793 3794  const auto inputType = llvm::dyn_cast<RankedTensorType>(getInput().getType());3795  if (inputType && weightType) {3796    const int64_t inputHeight = inputType.getDimSize(1);3797    const int64_t kernelHeight = weightType.getDimSize(1);3798    const int64_t outputHeight = outputType.getDimSize(1);3799 3800    if (ShapedType::isStatic(inputHeight) &&3801        ShapedType::isStatic(outputHeight)) {3802      if (outputHeight !=3803          (inputHeight - 1) * strideY + outPadTop + outPadBottom + kernelHeight)3804        return emitOpError(3805                   "dimension mismatch: expected OH == (IH - 1) * stride_y "3806                   "+ out_pad_top + out_pad_bottom + KH, but got ")3807               << outputHeight << " != (" << inputHeight << " - 1) * "3808               << strideY << " + " << outPadTop << " + " << outPadBottom3809               << " + " << kernelHeight;3810    }3811 3812    const int64_t inputWidth = inputType.getDimSize(2);3813    const int64_t kernelWidth = weightType.getDimSize(2);3814    const int64_t outputWidth = outputType.getDimSize(2);3815 3816    if (ShapedType::isStatic(inputWidth) && ShapedType::isStatic(outputWidth)) {3817      if (outputWidth !=3818          (inputWidth - 1) * strideX + outPadLeft + outPadRight + kernelWidth)3819        return emitOpError(3820                   "dimension mismatch: expected OW == (IW - 1) * stride_x "3821                   "+ out_pad_left + out_pad_right + KW, but got ")3822               << outputWidth << " != (" << inputWidth << " - 1) * " << strideX3823               << " + " << outPadLeft << " + " << outPadRight << " + "3824               << kernelWidth;3825    }3826  }3827 3828  const auto biasType = llvm::dyn_cast<RankedTensorType>(getBias().getType());3829 3830  if (!biasType)3831    return success();3832 3833  const int64_t biasChannels = biasType.getDimSize(0);3834 3835  // Skip further checks if bias is dynamic3836  if (biasChannels == ShapedType::kDynamic)3837    return success();3838 3839  const int64_t outputChannels = outputType.getDimSize(3);3840  if (!ShapedType::isDynamic(outputChannels) &&3841      biasChannels != outputChannels && biasChannels != 1)3842    return emitOpError(3843               "bias channels expected to be equal to output channels (")3844           << outputChannels << ") or 1, got " << biasChannels;3845 3846  return success();3847}3848 3849LogicalResult RescaleOp::verify() {3850  auto inputType = llvm::dyn_cast<ShapedType>(getInput().getType());3851  if (!inputType) {3852    emitOpError("expect shaped tensor for input, got ") << getInput().getType();3853    return failure();3854  }3855 3856  auto inputElementType =3857      getStorageElementTypeOrSelf(inputType.getElementType());3858  if (!mlir::isa<IntegerType>(inputElementType)) {3859    emitOpError("expect input to have integer element type, got ")3860        << inputElementType;3861    return failure();3862  }3863 3864  auto outputType = llvm::dyn_cast<ShapedType>(getOutput().getType());3865  if (!outputType) {3866    emitOpError("expect shaped tensor for output, got ")3867        << getOutput().getType();3868    return failure();3869  }3870 3871  auto outputElementType =3872      getStorageElementTypeOrSelf(outputType.getElementType());3873  if (!mlir::isa<IntegerType>(outputElementType)) {3874    emitOpError("expect output to have integer element type, got ")3875        << outputElementType;3876    return failure();3877  }3878 3879  if (verifyRescaleValueAndZpTypes(*this, getInput(), getInputZp(), "input")3880          .failed())3881    return failure();3882 3883  if (verifyRescaleValueAndZpTypes(*this, getOutput(), getOutputZp(), "output")3884          .failed())3885    return failure();3886 3887  FailureOr<int64_t> maybeIZp = getInputZeroPoint();3888  if (succeeded(maybeIZp) && verifyInputZeroPoint(*maybeIZp).failed())3889    return failure();3890 3891  FailureOr<int64_t> maybeOZp = getOutputZeroPoint();3892  if (succeeded(maybeOZp) && verifyOutputZeroPoint(*maybeOZp).failed())3893    return failure();3894 3895  auto multiplierType = llvm::dyn_cast<ShapedType>(getMultiplier().getType());3896  if (!multiplierType) {3897    emitOpError("expect shaped tensor for multiplier, got ")3898        << getMultiplier().getType();3899    return failure();3900  }3901 3902  auto shiftType = llvm::dyn_cast<ShapedType>(getShift().getType());3903  if (!shiftType) {3904    emitOpError("expect shaped tensor for shift, got ") << getShift().getType();3905    return failure();3906  }3907 3908  // multiplier element type must be i32 for scale32 = true3909  if (getScale32() && !multiplierType.getElementType().isInteger(32)) {3910    emitOpError("expect i32 element type for multiplier for scale32=true, got ")3911        << multiplierType.getElementType();3912    return failure();3913  }3914 3915  // multiplier element type must be i16 for scale32 = false3916  if (!getScale32() && !multiplierType.getElementType().isInteger(16)) {3917    emitOpError(3918        "expect i16 element type for multiplier for scale32=false, got ")3919        << multiplierType.getElementType();3920    return failure();3921  }3922 3923  if (!inputType.hasRank())3924    return success();3925 3926  // multiplier/shift must have shape = {numChannels},3927  // where numChannel is 1 if per_channel = false3928  // otherwise numChannel is dimension in input shape's last axis3929  int64_t numChannels = 1;3930  if (getPerChannel()) {3931    if (inputType.getRank() < 1) {3932      emitOpError("requires input to be at least rank 1 when per_channel is "3933                  "true, but got rank ")3934          << inputType.getRank();3935      return failure();3936    }3937    numChannels = inputType.getDimSize(inputType.getRank() - 1);3938  }3939 3940  if (!multiplierType.hasRank())3941    return success();3942 3943  ArrayRef<int64_t> multiplierShape = multiplierType.getShape();3944  // multiplier input has rank 1 by dialect definition3945  if (multiplierShape[0] != ShapedType::kDynamic &&3946      multiplierShape[0] != numChannels) {3947    emitOpError("expect shape of { ")3948        << numChannels << " } for multiplier input, got { "3949        << multiplierShape[0] << " }";3950    return failure();3951  }3952 3953  if (!shiftType.hasRank())3954    return success();3955 3956  ArrayRef<int64_t> shiftShape = shiftType.getShape();3957  // shift input has rank 1 by dialect definition3958  if (shiftShape[0] != ShapedType::kDynamic && shiftShape[0] != numChannels) {3959    emitOpError("expect shape of { ")3960        << numChannels << " } for shift input, got { " << shiftShape[0] << " }";3961    return failure();3962  }3963 3964  return success();3965}3966 3967LogicalResult RescaleOp::inferReturnTypeComponents(3968    MLIRContext *context, ::std::optional<Location> location,3969    RescaleOp::Adaptor adaptor,3970    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3971  ShapeAdaptor inputShape(adaptor.getInput().getType());3972  inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));3973  return success();3974}3975 3976LogicalResult CastFromBlockScaledOp::inferReturnTypeComponents(3977    MLIRContext *context, ::std::optional<Location> location,3978    CastFromBlockScaledOp::Adaptor adaptor,3979    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {3980  const ShapeAdaptor inputShape(adaptor.getInputData().getType());3981  inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));3982  return success();3983}3984 3985LogicalResult CastFromBlockScaledOp::verify() {3986  const Type inputDataType = getInputData().getType();3987  const Type outputDataType = getResult().getType();3988  if (failed(verifyCompatibleShape(inputDataType, outputDataType)))3989    return emitOpError() << "require compatible shapes for input_data ("3990                         << inputDataType << ") and "3991                         << "output_data (" << outputDataType << ")";3992 3993  const ShapeAdaptor inputDataShape = ShapeAdaptor(inputDataType);3994 3995  if (inputDataShape.hasRank()) {3996    const unsigned int blockSize =3997        BlockSizeAttr::getBlockSizeValue(getBlockSize());3998    const int64_t inputDataLastDim =3999        inputDataShape.getDimSize(inputDataShape.getRank() - 1);4000    if (inputDataLastDim % blockSize != 0)4001      return emitOpError() << "expect last dimension of input_data ("4002                           << inputDataLastDim4003                           << ") to be divisible by block_size (" << blockSize4004                           << ")";4005 4006    const Type inputScaleType = getInputScale().getType();4007    const ShapeAdaptor inputScaleShape = ShapeAdaptor(inputScaleType);4008 4009    if (inputScaleShape.hasRank()) {4010      SmallVector<int64_t> inputDataDims, inputScaleDims;4011      inputDataShape.getDims(inputDataDims);4012      inputScaleShape.getDims(inputScaleDims);4013 4014      if (inputDataDims.size() != inputScaleDims.size() ||4015          failed(verifyCompatibleShape(4016              ArrayRef<int64_t>(inputDataDims).drop_back(1),4017              ArrayRef<int64_t>(inputScaleDims).drop_back(1))))4018        return emitOpError() << "require compatible shapes for input_data ("4019                             << inputDataType << ") and "4020                             << "input_scale (" << inputScaleType4021                             << ") except for the last dimension";4022 4023      const SmallVector<int64_t, 2> dimsToCheck{inputDataLastDim / blockSize,4024                                                inputScaleDims.back()};4025      if (ShapedType::isStatic(inputDataLastDim) &&4026          failed(verifyCompatibleDims(dimsToCheck)))4027        return emitOpError()4028               << "expect last dimension of input_scale ("4029               << inputScaleDims.back()4030               << ") to be equal to last dimension of input_data / block_size ("4031               << inputDataDims.back() / blockSize << ")";4032    }4033  }4034 4035  return success();4036}4037 4038LogicalResult CastToBlockScaledOp::inferReturnTypeComponents(4039    MLIRContext *context, ::std::optional<Location> location,4040    CastToBlockScaledOp::Adaptor adaptor,4041    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {4042  const ShapeAdaptor inputShape(adaptor.getInputData().getType());4043  inferredReturnShapes.push_back(ShapedTypeComponents(inputShape));4044  if (!inputShape.hasRank())4045    return success();4046 4047  // Calculate output_scale shape if ranked input provided4048  SmallVector<int64_t> outputScaleShape;4049  inputShape.getDims(outputScaleShape);4050  const int64_t lastDimLoc = inputShape.getRank() - 1;4051  const int64_t lastDimSize = inputShape.getDimSize(lastDimLoc);4052  if (ShapedType::isStatic(lastDimSize)) {4053    const unsigned int blockSize =4054        BlockSizeAttr::getBlockSizeValue(adaptor.getBlockSize());4055    outputScaleShape[lastDimLoc] = lastDimSize / blockSize;4056  }4057  inferredReturnShapes.push_back(ShapedTypeComponents(outputScaleShape));4058  return success();4059}4060 4061LogicalResult CastToBlockScaledOp::verify() {4062  const Type inputDataType = getInputData().getType();4063  const Type outputDataType = getResult(0).getType();4064  if (failed(verifyCompatibleShape(inputDataType, outputDataType)))4065    return emitOpError() << "require compatible shapes for input_data ("4066                         << inputDataType << ") and "4067                         << "output_data (" << outputDataType << ")";4068 4069  const unsigned int blockSize =4070      BlockSizeAttr::getBlockSizeValue(getBlockSize());4071  const ShapeAdaptor inputDataShape = ShapeAdaptor(inputDataType);4072  if (inputDataShape.hasRank()) {4073    const int64_t inputDataLastDim =4074        inputDataShape.getDimSize(inputDataShape.getRank() - 1);4075    if (ShapedType::isStatic(inputDataLastDim) &&4076        inputDataLastDim % blockSize != 0)4077      return emitOpError() << "expect last dimension of input_data ("4078                           << inputDataLastDim4079                           << ") to be divisible by block_size (" << blockSize4080                           << ")";4081  }4082 4083  const ShapeAdaptor outputDataShape = ShapeAdaptor(outputDataType);4084  const Type outputScaleType = getResult(1).getType();4085  const ShapeAdaptor outputScaleShape = ShapeAdaptor(outputScaleType);4086  if (outputDataShape.hasRank() && outputScaleShape.hasRank()) {4087    SmallVector<int64_t> outputDataDims, outputScaleDims;4088    outputDataShape.getDims(outputDataDims);4089    outputScaleShape.getDims(outputScaleDims);4090 4091    if (outputDataDims.size() != outputScaleDims.size() ||4092        failed(verifyCompatibleShape(4093            ArrayRef<int64_t>(outputDataDims).drop_back(1),4094            ArrayRef<int64_t>(outputScaleDims).drop_back(1))))4095      return emitOpError() << "require compatible shapes for output_data ("4096                           << outputDataType << ") and "4097                           << "output_scale (" << outputScaleType4098                           << ") except for the last dimension";4099 4100    const int64_t outputDataLastDim = outputDataDims.back();4101    const SmallVector<int64_t, 2> dimsToCheck{outputDataLastDim / blockSize,4102                                              outputScaleDims.back()};4103    if (ShapedType::isStatic(outputDataLastDim) &&4104        failed(verifyCompatibleDims(dimsToCheck)))4105      return emitOpError()4106             << "expect last dimension of output_scale ("4107             << outputScaleDims.back()4108             << ") to be equal to last dimension of output_data / block_size ("4109             << outputDataDims.back() / blockSize << ")";4110  }4111 4112  return success();4113}4114 4115LogicalResult IfOp::inferReturnTypeComponents(4116    MLIRContext *context, ::std::optional<Location> location,4117    IfOp::Adaptor adaptor,4118    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {4119  llvm::SmallVector<tosa::YieldOp> yieldOps;4120  for (Region *region : adaptor.getRegions()) {4121    for (auto &block : *region)4122      if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))4123        yieldOps.push_back(returnOp);4124  }4125 4126  if (yieldOps.empty())4127    return failure();4128 4129  // Get the initial type information for the yield op.4130  llvm::SmallVector<ValueKnowledge> resultKnowledge;4131  resultKnowledge.reserve(yieldOps.front().getNumOperands());4132  for (auto operand : yieldOps.front().getOperands()) {4133    resultKnowledge.push_back(4134        ValueKnowledge::getKnowledgeFromType(operand.getType()));4135  }4136 4137  for (auto yieldOp : yieldOps) {4138    if (resultKnowledge.size() != yieldOp.getNumOperands())4139      return failure();4140 4141    for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {4142      int32_t index = it.index();4143      auto meet = ValueKnowledge::meet(4144          resultKnowledge[index],4145          ValueKnowledge::getKnowledgeFromType(it.value().getType()));4146      if (!meet)4147        continue;4148      resultKnowledge[index] = meet;4149    }4150  }4151 4152  for (const ValueKnowledge &result : resultKnowledge) {4153    inferredReturnShapes.push_back(result.getShapedTypeComponents());4154  }4155 4156  return success();4157}4158 4159LogicalResult WhileOp::inferReturnTypeComponents(4160    MLIRContext *context, ::std::optional<Location> location,4161    WhileOp::Adaptor adaptor,4162    SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) {4163  llvm::SmallVector<tosa::YieldOp> yieldOps;4164  for (auto &block : adaptor.getBodyGraph())4165    if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator()))4166      yieldOps.push_back(returnOp);4167 4168  // TOSA's while must have a tosa.yield as its terminator. If not found this4169  // tosa.while is invalid.4170  if (yieldOps.empty())4171    return failure();4172 4173  // Get the initial type information from the operand types.4174  llvm::SmallVector<ValueKnowledge> resultKnowledge;4175  resultKnowledge.reserve(yieldOps.front().getNumOperands());4176  for (auto operand : yieldOps.front().getOperands()) {4177    resultKnowledge.push_back(4178        ValueKnowledge::getKnowledgeFromType(operand.getType()));4179  }4180 4181  for (auto yieldOp : yieldOps) {4182    if (resultKnowledge.size() != yieldOp.getNumOperands())4183      return failure();4184 4185    for (const auto &it : llvm::enumerate(yieldOp.getOperands())) {4186      int32_t index = it.index();4187      if (auto meet = ValueKnowledge::meet(4188              resultKnowledge[index],4189              ValueKnowledge::getKnowledgeFromType(it.value().getType()))) {4190        resultKnowledge[index] = meet;4191      }4192    }4193  }4194 4195  for (const ValueKnowledge &result : resultKnowledge) {4196    inferredReturnShapes.push_back(result.getShapedTypeComponents());4197  }4198 4199  return success();4200}4201 4202std::optional<SmallVector<int64_t, 4>> ApplyScaleOp::getShapeForUnroll() {4203  if (auto vt = llvm::dyn_cast<VectorType>(getType()))4204    return llvm::to_vector<4>(vt.getShape());4205  return std::nullopt;4206}4207 4208static void printInitializationList(OpAsmPrinter &parser,4209                                    Block::BlockArgListType blocksArgs,4210                                    ValueRange initializers,4211                                    StringRef prefix = "") {4212  assert(blocksArgs.size() == initializers.size() &&4213         "expected same length of arguments and initializers");4214  if (initializers.empty())4215    return;4216 4217  parser << prefix << '(';4218  llvm::interleaveComma(4219      llvm::zip(blocksArgs, initializers), parser,4220      [&](auto it) { parser << std::get<0>(it) << " = " << std::get<1>(it); });4221  parser << ")";4222}4223 4224// parse and print of IfOp refer to the implementation of SCF dialect.4225ParseResult IfOp::parse(OpAsmParser &parser, OperationState &result) {4226  // Create the regions for 'then'.4227  result.regions.reserve(2);4228  Region *thenRegion = result.addRegion();4229  Region *elseRegion = result.addRegion();4230 4231  OpAsmParser::UnresolvedOperand cond;4232 4233  if (parser.parseOperand(cond))4234    return failure();4235 4236  SmallVector<OpAsmParser::Argument, 4> regionArgs;4237  SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;4238 4239  // Parse the optional block arguments4240  OptionalParseResult listResult =4241      parser.parseOptionalAssignmentList(regionArgs, operands);4242  if (listResult.has_value() && failed(listResult.value()))4243    return failure();4244 4245  // Parse a colon.4246  if (failed(parser.parseColon()))4247    return parser.emitError(parser.getCurrentLocation(),4248                            "expected type for condition operand");4249 4250  // Parse the type of the condition operand4251  Type condType;4252  if (failed(parser.parseType(condType)))4253    return parser.emitError(parser.getCurrentLocation(),4254                            "expected type for condition operand");4255 4256  // Resolve operand with provided type4257  if (failed(parser.resolveOperand(cond, condType, result.operands)))4258    return failure();4259 4260  // Parse optional block arg types4261  if (listResult.has_value()) {4262    FunctionType functionType;4263 4264    if (failed(parser.parseType(functionType)))4265      return parser.emitError(parser.getCurrentLocation())4266             << "expected list of types for block arguments "4267             << "followed by arrow type and list of return types";4268 4269    result.addTypes(functionType.getResults());4270 4271    if (functionType.getNumInputs() != operands.size()) {4272      return parser.emitError(parser.getCurrentLocation())4273             << "expected as many input types as operands "4274             << "(expected " << operands.size() << " got "4275             << functionType.getNumInputs() << ")";4276    }4277 4278    // Resolve input operands.4279    if (failed(parser.resolveOperands(operands, functionType.getInputs(),4280                                      parser.getCurrentLocation(),4281                                      result.operands)))4282      return failure();4283  } else {4284    // Parse optional results type list.4285    if (parser.parseOptionalArrowTypeList(result.types))4286      return failure();4287  }4288 4289  // Parse the 'then' region.4290  if (parser.parseRegion(*thenRegion, /*arguments=*/{}, /*argTypes=*/{}))4291    return failure();4292 4293  // If we find an 'else' keyword then parse the 'else' region.4294  if (!parser.parseOptionalKeyword("else")) {4295    if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{}))4296      return failure();4297  }4298 4299  // Parse the optional attribute list.4300  if (parser.parseOptionalAttrDict(result.attributes))4301    return failure();4302  return success();4303}4304 4305void IfOp::print(OpAsmPrinter &p) {4306  p << " " << getCondition();4307 4308  printInitializationList(p, getThenGraph().front().getArguments(),4309                          getInputList(), " ");4310  p << " : ";4311  p << getCondition().getType();4312 4313  if (!getInputList().empty()) {4314    p << " (";4315    llvm::interleaveComma(getInputList().getTypes(), p);4316    p << ")";4317  }4318  p.printArrowTypeList(getResultTypes());4319  p << " ";4320 4321  p.printRegion(getThenGraph());4322 4323  // Print the 'else' regions if it exists and has a block.4324  auto &elseRegion = getElseGraph();4325  if (!elseRegion.empty()) {4326    p << " else ";4327    p.printRegion(elseRegion);4328  }4329 4330  p.printOptionalAttrDict((*this)->getAttrs());4331}4332 4333LogicalResult IfOp::verify() {4334  if (errorIfTypeOrShapeMismatch(*this, getThenGraph().front().getArguments(),4335                                 "'then_graph' arguments", getInputList(),4336                                 "'input_list'")4337          .failed())4338    return failure();4339 4340  if (errorIfTypeOrShapeMismatch(*this, getElseGraph().front().getArguments(),4341                                 "'else_graph' arguments", getInputList(),4342                                 "'input_list'")4343          .failed())4344    return failure();4345 4346  // MLIR will verify the absence of the terminator for us if otherwise.4347  if (getThenGraph().front().mightHaveTerminator()) {4348    auto thenYield =4349        dyn_cast<tosa::YieldOp>(getThenGraph().front().getTerminator());4350    if (thenYield && errorIfTypeOrShapeMismatch(4351                         *this, thenYield.getInputs(), "'then_graph' results",4352                         getOutputList(), "'output_list'")4353                         .failed())4354      return failure();4355  }4356 4357  // MLIR will verify the absence of the terminator for us if otherwise.4358  if (getElseGraph().front().mightHaveTerminator()) {4359    auto elseYield =4360        dyn_cast<tosa::YieldOp>(getElseGraph().front().getTerminator());4361    if (elseYield && errorIfTypeOrShapeMismatch(4362                         *this, elseYield.getInputs(), "'else_graph' results",4363                         getOutputList(), "'output_list'")4364                         .failed())4365      return failure();4366  }4367 4368  auto condType = getCondition().getType();4369  if (errorIfShapeNotSizeOne(*this, condType).failed())4370    return emitOpError() << "'condition' must be a size 1 tensor, got "4371                         << condType;4372 4373  return success();4374}4375 4376LogicalResult WhileOp::verify() {4377  if (errorIfTypeOrShapeMismatch(*this, getInputList(), "'input_list'",4378                                 getOutputList(), "'output_list'")4379          .failed())4380    return failure();4381 4382  if (errorIfTypeOrShapeMismatch(*this, getCondGraph().front().getArguments(),4383                                 "'cond_graph' arguments", getInputList(),4384                                 "'input_list'")4385          .failed())4386    return failure();4387 4388  if (errorIfTypeOrShapeMismatch(*this, getBodyGraph().front().getArguments(),4389                                 "'body_graph' arguments", getInputList(),4390                                 "'input_list'")4391          .failed())4392    return failure();4393 4394  if (getBodyGraph().front().mightHaveTerminator()) {4395    auto bodyYield =4396        dyn_cast<tosa::YieldOp>(getBodyGraph().front().getTerminator());4397    if (bodyYield && errorIfTypeOrShapeMismatch(*this, bodyYield.getInputs(),4398                                                "'body_graph' results",4399                                                getInputList(), "'input_list'")4400                         .failed())4401      return failure();4402  }4403 4404  // Condition block output must be a single element tensor with a single bool4405  // value.4406  if (!getCondGraph().front().mightHaveTerminator())4407    return success();4408 4409  auto condYield =4410      dyn_cast<tosa::YieldOp>(getCondGraph().front().getTerminator());4411  if (!condYield)4412    return success();4413 4414  if (condYield.getInputs().size() != 1)4415    return emitOpError() << "require 'cond_graph' only have one result";4416 4417  auto condOutType = condYield.getInputs()[0].getType();4418  if (errorIfShapeNotSizeOne(*this, condOutType).failed())4419    return emitOpError() << "'cond_graph' result must be a size 1 tensor, got "4420                         << condOutType;4421 4422  if (!getElementTypeOrSelf(condOutType).isInteger(1))4423    return emitOpError() << "'cond_graph' result must be a boolean tensor, got "4424                         << condOutType;4425 4426  return success();4427}4428 4429LogicalResult ReverseOp::verify() {4430  if (verifySameElementTypes(*this, /* inType = */ getInput1().getType(),4431                             /* outType = */ getOutput().getType())4432          .failed())4433    return failure();4434  TensorType inputType = getInput1().getType();4435  TensorType outputType = getOutput().getType();4436  int32_t reverseAxis = getAxis();4437 4438  if (reverseAxis < 0)4439    return emitOpError("expected non-negative reverse axis");4440  if (inputType.hasRank()) {4441    int64_t inputRank = inputType.getRank();4442    // We allow for a special case where the input/output shape has rank 0 and4443    // axis is also 0.4444    if (reverseAxis >= inputRank && (reverseAxis != 0 || inputRank != 0))4445      return emitOpError("expect input tensor rank (")4446             << inputRank << ") to be larger than reverse axis (" << reverseAxis4447             << ")";4448  }4449  if (outputType.hasRank()) {4450    int64_t outputRank = outputType.getRank();4451    if (inputType.hasRank() && outputRank != inputType.getRank())4452      return emitOpError(4453          "expect output tensor rank to be equal to input tensor rank");4454    if (reverseAxis >= outputRank && (reverseAxis != 0 || outputRank != 0))4455      return emitOpError("expect output tensor rank (")4456             << outputRank << ") to be larger than reverse axis ("4457             << reverseAxis << ")";4458  }4459  return success();4460}4461 4462LogicalResult tosa::SelectOp::verify() {4463  // verify input2 and input3 have same element type as output4464  if (verifySameElementTypes(*this, /* inType = */ getOnTrue().getType(),4465                             /* outType = */ getOutput().getType())4466          .failed() ||4467      verifySameElementTypes(*this, /* inType = */ getOnFalse().getType(),4468                             /* outType = */ getOutput().getType())4469          .failed()) {4470    return failure();4471  }4472  // verify input1 has element type of bool4473  auto predicateType = llvm::dyn_cast<ShapedType>(getPred().getType());4474  if (!predicateType) {4475    return emitOpError("expect shaped tensor for input1, got ")4476           << getInput1().getType();4477  }4478  auto predicateElementType = predicateType.getElementType();4479  if (!predicateElementType.isInteger(1)) {4480    return emitOpError("expect element type of bool for input1, got ")4481           << predicateElementType;4482  }4483 4484  return success();4485}4486 4487LogicalResult tosa::VariableReadOp::verify() {4488  if (verifyVariableOpErrorIf(*this, getOutput1().getType(), "'output1'")4489          .failed())4490    return failure();4491 4492  return success();4493}4494 4495LogicalResult tosa::VariableWriteOp::verify() {4496  if (verifyVariableOpErrorIf(*this, getInput1().getType(), "'input1'")4497          .failed())4498    return failure();4499 4500  return success();4501}4502 4503// parse and print of WhileOp refer to the implementation of SCF dialect.4504ParseResult WhileOp::parse(OpAsmParser &parser, OperationState &result) {4505  SmallVector<OpAsmParser::Argument, 4> regionArgs;4506  SmallVector<OpAsmParser::UnresolvedOperand, 4> operands;4507  Region *cond = result.addRegion();4508  Region *body = result.addRegion();4509 4510  OptionalParseResult listResult =4511      parser.parseOptionalAssignmentList(regionArgs, operands);4512  if (listResult.has_value() && failed(listResult.value()))4513    return failure();4514 4515  FunctionType functionType;4516  SMLoc typeLoc = parser.getCurrentLocation();4517  if (failed(parser.parseColonType(functionType)))4518    return failure();4519 4520  result.addTypes(functionType.getResults());4521 4522  if (functionType.getNumInputs() != operands.size()) {4523    return parser.emitError(typeLoc)4524           << "expected as many input types as operands "4525           << "(expected " << operands.size() << " got "4526           << functionType.getNumInputs() << ")";4527  }4528 4529  // Resolve input operands.4530  if (failed(parser.resolveOperands(operands, functionType.getInputs(),4531                                    parser.getCurrentLocation(),4532                                    result.operands)))4533    return failure();4534 4535  // Propagate the types into the region arguments.4536  for (size_t i = 0, e = regionArgs.size(); i != e; ++i)4537    regionArgs[i].type = functionType.getInput(i);4538 4539  return failure(parser.parseRegion(*cond, regionArgs) ||4540                 parser.parseKeyword("do") || parser.parseRegion(*body) ||4541                 parser.parseOptionalAttrDictWithKeyword(result.attributes));4542}4543 4544void WhileOp::print(OpAsmPrinter &parser) {4545  printInitializationList(parser, getCondGraph().front().getArguments(),4546                          getInputList(), " ");4547  parser << " : ";4548  parser.printFunctionalType(getInputList().getTypes(),4549                             getResults().getTypes());4550  parser << ' ';4551  parser.printRegion(getCondGraph(), /*printEntryBlockArgs=*/false);4552  parser << " do ";4553  parser.printRegion(getBodyGraph());4554  parser.printOptionalAttrDictWithKeyword((*this)->getAttrs());4555}4556 4557// Create a rank-1 const tensor for zero point of the source tensor.4558std::optional<Value> mlir::tosa::createZeroPointTensor(OpBuilder &builder,4559                                                       Location loc,4560                                                       Type srcElemType,4561                                                       int64_t zp) {4562  srcElemType = getStorageElementTypeOrSelf(srcElemType);4563  auto zpType = mlir::RankedTensorType::get({1}, srcElemType);4564  if (llvm::isa<FloatType>(srcElemType)) {4565    auto zpAttr = DenseElementsAttr::get(4566        zpType, builder.getFloatAttr(srcElemType, static_cast<double>(zp)));4567    return tosa::ConstOp::create(builder, loc, zpType, zpAttr);4568  }4569  if (llvm::isa<IntegerType>(srcElemType)) {4570    auto zpAttr =4571        DenseElementsAttr::get(zpType, builder.getIntegerAttr(srcElemType, zp));4572    return tosa::ConstOp::create(builder, loc, zpType, zpAttr);4573  }4574  llvm::errs() << "zero point is not allowed for unsupported data types\n";4575  return std::nullopt;4576}4577 4578//===----------------------------------------------------------------------===//4579// TOSA Shape and Shape Operators Helper functions.4580//===----------------------------------------------------------------------===//4581 4582bool mlir::tosa::isa_tosa_shape_type(mlir::Type t) {4583  return mlir::isa<tosa::shapeType>(t);4584}4585 4586LogicalResult4587mlir::tosa::shapeType::verify(function_ref<InFlightDiagnostic()> emitError,4588                              int rank) {4589  if (rank < 0)4590    return emitError() << "invalid rank (must be >= 0): " << rank;4591  return success();4592}4593 4594LogicalResult OpTrait::tosa::verifyTosaResolvableShapeOperands(Operation *op) {4595  for (auto v : op->getOperands()) {4596    if (mlir::isa<::mlir::tosa::shapeType>(v.getType())) {4597      Operation *definingOp = v.getDefiningOp();4598      if (!definingOp || !definingOp->hasTrait<TosaShapeOperator>()) {4599        return op->emitOpError("shape operand is not compile time resolvable");4600      }4601    }4602  }4603  return success();4604}4605 4606LogicalResult OpTrait::tosa::verifyTosaShapeOperator(Operation *op) {4607  for (auto type : op->getOperandTypes()) {4608    if (!mlir::isa<mlir::tosa::shapeType>(type)) {4609      return op->emitOpError("must have operands with tosa shape type");4610    }4611  }4612  for (auto type : op->getResultTypes()) {4613    if (!mlir::isa<mlir::tosa::shapeType>(type)) {4614      return op->emitOpError("must have result with tosa shape type");4615    }4616  }4617  return success();4618}4619 4620LogicalResult4621OpTrait::tosa::verifyTosaShapeOperatorWithSameRanks(Operation *op) {4622  if (failed(OpTrait::impl::verifyAtLeastNOperands(op, 1)) ||4623      failed(verifyTosaShapeOperator(op)))4624    return failure();4625 4626  // delegate function that returns rank of shape type4627  auto getRank = [](const Type type) {4628    return mlir::cast<mlir::tosa::shapeType>(type).getRank();4629  };4630  auto operandTypes = op->getOperandTypes();4631  auto resultTypes = op->getResultTypes();4632 4633  auto rank = getRank(*op->getOperandTypes().begin());4634  for (auto type : operandTypes) {4635    if (getRank(type) != rank) {4636      return op->emitOpError("operands don't have matching ranks");4637    }4638  }4639  for (auto type : resultTypes) {4640    if (getRank(type) != rank) {4641      return op->emitOpError("result shape has different rank than operands");4642    }4643  }4644  return success();4645}4646 4647//===----------------------------------------------------------------------===//4648// TOSA Shape Operators verify functions.4649//===----------------------------------------------------------------------===//4650 4651LogicalResult tosa::ConstShapeOp::verify() {4652  // check one dimensional rank4653  auto valuesRank = getValues().getType().getRank();4654  if (valuesRank != 1)4655    return emitOpError("expect elements in attribute values with rank 1");4656  // check that number of elements in values attr equal to rank of result shape4657  auto count = getValues().getNumElements();4658  auto rank = (cast<tosa::shapeType>(getResult().getType())).getRank();4659  if (count != rank && (count != 1 || rank != 0)) {4660    return emitOpError("expect number of elements in attribute values (")4661           << count << ") to be equal to the rank (" << rank4662           << ") for the result shape type";4663  }4664  return success();4665}4666 4667//===----------------------------------------------------------------------===//4668// TOSA Attribute Definitions.4669//===----------------------------------------------------------------------===//4670 4671#define GET_ATTRDEF_CLASSES4672#include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc"4673 4674//===----------------------------------------------------------------------===//4675// TOSA Type Definitions.4676//===----------------------------------------------------------------------===//4677#define GET_TYPEDEF_CLASSES4678#include "mlir/Dialect/Tosa/IR/TosaOpsTypesBase.cpp.inc"4679 4680//===----------------------------------------------------------------------===//4681// TOSA Operator Definitions.4682//===----------------------------------------------------------------------===//4683 4684#define GET_OP_CLASSES4685#include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"4686