//===- TosaOps.cpp - MLIR Dialect for TOSA --------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // \file // This file implements the TOSA Specification: // https://developer.mlplatform.org/w/tosa/ // //===----------------------------------------------------------------------===// #include "mlir/Dialect/Tosa/IR/TosaOps.h" #include "mlir/Dialect/Quant/QuantOps.h" #include "mlir/Dialect/Tensor/IR/Tensor.h" #include "mlir/Dialect/Tosa/Utils/QuantUtils.h" #include "mlir/Dialect/Tosa/Utils/ShapeUtils.h" #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/DialectImplementation.h" #include "mlir/IR/Matchers.h" #include "mlir/IR/PatternMatch.h" #include "mlir/Transforms/InliningUtils.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/TypeSwitch.h" using namespace mlir; using namespace mlir::tosa; #include "mlir/Dialect/Tosa/IR/TosaOpsDialect.cpp.inc" //===----------------------------------------------------------------------===// // Tosa dialect interface includes. //===----------------------------------------------------------------------===// #include "mlir/Dialect/Tosa/IR/TosaInterfaces.cpp.inc" namespace { //===----------------------------------------------------------------------===// // Dialect Function Inliner Interface. //===----------------------------------------------------------------------===// struct TosaInlinerInterface : public DialectInlinerInterface { using DialectInlinerInterface::DialectInlinerInterface; //===--------------------------------------------------------------------===// // Analysis Hooks. //===--------------------------------------------------------------------===// /// All operations can be inlined by default. bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned, BlockAndValueMapping &map) const final { return true; } /// All regions with If and While parent operators can be inlined. bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, BlockAndValueMapping &map) const final { return (isa(dest->getParentOp()) || isa(dest->getParentOp())); } }; } // namespace //===----------------------------------------------------------------------===// // TOSA control flow support. //===----------------------------------------------------------------------===// /// Returns the while loop body. Region &tosa::WhileOp::getLoopBody() { return getBody(); } //===----------------------------------------------------------------------===// // Tosa dialect initialization. //===----------------------------------------------------------------------===// void TosaDialect::initialize() { addOperations< #define GET_OP_LIST #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc" >(); addAttributes< #define GET_ATTRDEF_LIST #include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc" >(); addInterfaces(); } Operation *TosaDialect::materializeConstant(OpBuilder &builder, Attribute value, Type type, Location loc) { // Tosa dialect constants only support ElementsAttr unlike standard dialect // constant which supports all attributes. if (value.isa()) return builder.create(loc, type, value.cast()); return nullptr; } //===----------------------------------------------------------------------===// // TOSA Operator Verifiers. //===----------------------------------------------------------------------===// template static LogicalResult verifyConvOp(T op) { // All TOSA conv ops have an input() and weight(). auto inputType = op.getInput().getType().template dyn_cast(); auto weightType = op.getWeight().getType().template dyn_cast(); // Must be ranked tensor types if (!inputType) { op.emitOpError("expect a ranked tensor for input, got ") << op.getInput(); return failure(); } if (!weightType) { op.emitOpError("expect a ranked tensor for weight, got ") << op.getWeight(); return failure(); } auto inputEType = inputType.getElementType(); auto weightEType = weightType.getElementType(); bool inputIsQuant = !inputEType.template isa(); bool weightIsQuant = !weightEType.template isa(); // Either both must be quantized or both unquantized. if (inputIsQuant != weightIsQuant) { op.emitOpError( "expect both input and weight to be float or not together, got ") << inputEType << " and " << weightEType; return failure(); } // Quantized type must have constructed the quantizationattr, and unquantized // types should not have a quantizationattr. if ((inputIsQuant && !op.getQuantizationInfo()) || (!inputIsQuant && op.getQuantizationInfo())) { op.emitOpError("quantizationattr is required for quantized type, and not " "allowed for float type"); return failure(); } return success(); } LogicalResult tosa::AvgPool2dOp::verify() { auto inputETy = getInput().getType().cast().getElementType(); auto resultETy = getType().cast().getElementType(); if (auto quantType = inputETy.dyn_cast()) inputETy = quantType.getStorageType(); if (auto quantType = resultETy.dyn_cast()) resultETy = quantType.getStorageType(); if (inputETy.isF32() && resultETy.isF32()) return success(); if (inputETy.isInteger(8) && resultETy.isInteger(8)) return success(); if (inputETy.isInteger(16) && resultETy.isInteger(16)) return success(); return emitOpError("input/output element types are incompatible."); } //===----------------------------------------------------------------------===// // TOSA Operator Quantization Builders. //===----------------------------------------------------------------------===// /// This builder is called on all convolution operators except TransposeConv, /// which has specialized output shape semantics. The builder also defines the /// bitwidth of the output given the bit width of the input & weight content. static void buildConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value weight, Value bias, ArrayAttr pad, ArrayAttr stride, ArrayAttr dilation) { result.addOperands({input, weight, bias}); result.addAttribute("pad", pad); result.addAttribute("stride", stride); result.addAttribute("dilation", dilation); auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight); if (quantAttr) { result.addAttribute("quantization_info", quantAttr); result.addTypes( buildConvOpResultTypeInfo(builder, outputType, input, weight)); } else { result.addTypes(outputType); } } /// Handles tosa.transpose_conv2d which has outpad and output shape attributes. static void buildTransConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value weight, Value bias, ArrayAttr outpad, ArrayAttr stride, ArrayAttr outputShape) { result.addOperands({input, weight, bias}); result.addAttribute("out_pad", outpad); result.addAttribute("stride", stride); result.addAttribute("out_shape", outputShape); auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight); if (quantAttr) { result.addAttribute("quantization_info", quantAttr); result.addTypes( buildConvOpResultTypeInfo(builder, outputType, input, weight)); } else { result.addTypes(outputType); } } /// The tosa.fully_connected op has its own builder as it does not have /// strides/dilation/padding. static void buildFCOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value weight, Value bias) { result.addOperands({input, weight, bias}); auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight); if (quantAttr) { result.addAttribute("quantization_info", quantAttr); result.addTypes( buildConvOpResultTypeInfo(builder, outputType, input, weight)); } else { result.addTypes(outputType); } } /// The tosa.matmul op is also intended to be generated where a fully_connected /// op must be constructed where the weight is not a constant. In this case, /// the fully_connected op must be expressed using matmul. /// TODO: Add link to the leglization document explaining this. static void buildMatMulOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value a, Value b) { result.addOperands({a, b}); auto quantAttr = ::buildMatMulOpQuantizationAttr(builder, a, b); if (quantAttr) { result.addAttribute("quantization_info", quantAttr); auto inputType = a.getType().dyn_cast(); assert(inputType && "Input must be a shaped tensor type!"); auto inputQType = inputType.getElementType() .dyn_cast(); assert(inputQType && "Tensor must have quantized datatype!"); unsigned inputBits = inputQType.getStorageTypeIntegralWidth(); auto outputShapedType = outputType.dyn_cast(); assert(outputShapedType && "Output must be a shaped type"); IntegerType accElementType; if (inputBits == 16) accElementType = builder.getIntegerType(48); else accElementType = builder.getI32Type(); auto accType = outputShapedType.clone(accElementType); result.addTypes(accType); } else { result.addTypes(outputType); } } /// Both the tosa.avg_pool2d and unary ops use the same UnaruOpQuantizationAttr /// but avg_pool operator has its own builder as it has additional parameters /// not part of the unary ops. static void buildAvgPool2dOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, ArrayAttr kernel, ArrayAttr stride, ArrayAttr pad) { result.addOperands(input); result.addAttribute("kernel", kernel); result.addAttribute("stride", stride); result.addAttribute("pad", pad); auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType); if (quantAttr) result.addAttribute("quantization_info", quantAttr); result.types.push_back(outputType); } /// This builder is called on single-parameter unary operators that have scale /// relationship between their input and output, expressed by the /// UnaryOpQuantizationAttr. static void buildUnaryOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input) { result.addOperands(input); auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType); if (quantAttr) result.addAttribute("quantization_info", quantAttr); result.types.push_back(outputType); } /// This builder is called on TOSA pad operator that needs to create its own /// OptionalAttr quantization_attr parameter to scale the padding values /// correctly. No pad_const is interpreted as zero-padding. static void buildPadOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value paddings) { result.addOperands({input, paddings}); auto quantAttr = buildPadOpQuantizationAttr(builder, input); if (quantAttr) result.addAttribute("quantization_info", quantAttr); result.types.push_back(outputType); } /// This builder is called on TOSA pad operator when an explicit pad_const /// value is passed in. It also optionally constructs quantization_attr. static void buildExplicitValuePadOpWithQuantInfo(OpBuilder &builder, OperationState &result, Type outputType, Value input, Value paddings, Value padConst) { result.addOperands({input, paddings, padConst}); auto quantAttr = buildPadOpQuantizationAttr(builder, input); if (quantAttr) result.addAttribute("quantization_info", quantAttr); result.types.push_back(outputType); } //===----------------------------------------------------------------------===// // TOSA Operator Return Type Inference. //===----------------------------------------------------------------------===// static void getI64Values(ArrayAttr arrayAttr, SmallVector &values) { for (auto it : arrayAttr) { values.push_back(it.cast().getValue().getSExtValue()); } } static void getF64Values(ArrayAttr arrayAttr, SmallVector &values) { for (auto it : arrayAttr) { values.push_back(it.cast().getValueAsDouble()); } } LogicalResult tosa::ArgMaxOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); IntegerAttr axis = attributes.get("axis").cast(); int32_t axisVal = axis.getValue().getSExtValue(); if (!inputShape.hasRank()) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } SmallVector outShape; outShape.reserve(inputShape.getRank() - 1); for (int i = 0, s = inputShape.getRank(); i < s; i++) { if (i == axisVal) continue; outShape.push_back(inputShape.getDimSize(i)); } inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); return success(); } LogicalResult tosa::ConcatOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { // Infer all dimension sizes by reducing based on inputs. int32_t axis = attributes.get("axis").cast().getValue().getSExtValue(); llvm::SmallVector outputShape; bool hasRankedInput = false; for (auto operand : operands) { ShapeAdaptor operandShape = operands.getShape(operand); if (!operandShape.hasRank()) continue; // Copy the Operand's rank. if (!hasRankedInput) outputShape.resize(operandShape.getRank(), ShapedType::kDynamicSize); // Copy shapes until the dim is non-dynamic. for (int i = 0, s = operandShape.getRank(); i < s; i++) { if (i == axis || operandShape.isDynamicDim(i)) continue; if (outputShape[i] == ShapedType::kDynamicSize) outputShape[i] = operandShape.getDimSize(i); if (outputShape[i] != operandShape.getDimSize(i)) return failure(); } hasRankedInput = true; } if (!hasRankedInput) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } // Determine the dimension size along the concatenation axis. int concatDimSize = 0; for (auto operand : operands) { ShapeAdaptor operandShape = operands.getShape(operand); // We need to know the length of the concatenation axis of all inputs to // determine the dimension size of the output shape. if (!operandShape.hasRank() || operandShape.isDynamicDim(axis)) { concatDimSize = ShapedType::kDynamicSize; break; } concatDimSize += operandShape.getDimSize(axis); } outputShape[axis] = concatDimSize; inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::FullyConnectedOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); ShapeAdaptor weightShape = operands.getShape(1); ShapeAdaptor biasShape = operands.getShape(2); // All shapes are dynamic. SmallVector outShape; outShape.resize(2, ShapedType::kDynamicSize); if (inputShape.hasRank()) { outShape[0] = inputShape.getDimSize(0); } if (weightShape.hasRank()) { outShape[1] = weightShape.getDimSize(0); } if (biasShape.hasRank()) { outShape[1] = outShape[1] == ShapedType::kDynamicSize ? biasShape.getDimSize(0) : outShape[1]; } inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); return success(); } LogicalResult FullyConnectedOp::verify() { return verifyConvOp(*this); } LogicalResult tosa::MatMulOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor lhsShape = operands.getShape(0); ShapeAdaptor rhsShape = operands.getShape(1); // All shapes are dynamic. SmallVector outShape; outShape.resize(3, ShapedType::kDynamicSize); if (lhsShape.hasRank()) { outShape[0] = lhsShape.getDimSize(0); outShape[1] = lhsShape.getDimSize(1); } if (rhsShape.hasRank()) { outShape[0] = outShape[0] == ShapedType::kDynamicSize ? rhsShape.getDimSize(0) : outShape[0]; outShape[2] = rhsShape.getDimSize(2); } inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); return success(); } LogicalResult tosa::PadOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); ShapeAdaptor paddingShape = operands.getShape(1); SmallVector outputShape; // If both inputs have unknown shape, we cannot determine the shape of the // output. if (!inputShape.hasRank() && !paddingShape.hasRank()) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } // If the input rank is unknown we can info the output rank using the padding // shape's first dim. if (!inputShape.hasRank()) { if (paddingShape.isDynamicDim(0)) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } outputShape.resize(paddingShape.getDimSize(0), ShapedType::kDynamicSize); inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } DenseIntElementsAttr paddings; // If the paddings value is not a constant, all dimensions must be dynamic. if (!matchPattern(operands[1], m_Constant(&paddings))) { outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize); inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } SmallVector paddingValues; for (auto val : paddings) { paddingValues.push_back(val.getSExtValue()); } outputShape.reserve(inputShape.getRank()); for (int i = 0, s = inputShape.getRank(); i < s; i++) { if (inputShape.isDynamicDim(i)) { outputShape.push_back(ShapedType::kDynamicSize); continue; } outputShape.push_back(inputShape.getDimSize(i) + paddingValues[i * 2] + paddingValues[i * 2 + 1]); } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::SliceOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ArrayAttr sizes = SliceOpAdaptor(operands, attributes).getSize(); SmallVector outputShape; outputShape.reserve(sizes.size()); for (auto val : sizes) { outputShape.push_back(val.cast().getValue().getSExtValue()); } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::TableOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); if (!inputShape.hasRank()) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } inferredReturnShapes.resize(1); inputShape.getDims(inferredReturnShapes[0]); return success(); } LogicalResult tosa::TileOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { TileOpAdaptor adaptor(operands, attributes); ArrayAttr multiples = adaptor.getMultiples(); ShapeAdaptor inputShape = operands.getShape(0); SmallVector outputShape; if (!inputShape.hasRank()) { outputShape.resize(multiples.size(), ShapedType::kDynamicSize); inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } // We need the multiple values to determine the output shape. SmallVector multipleValues; multipleValues.reserve(multiples.size()); for (auto val : multiples) { multipleValues.push_back(val.cast().getValue().getSExtValue()); } // Any non dynamic dimension can be multiplied to a known size. outputShape.reserve(multiples.size()); for (int i = 0, s = inputShape.getRank(); i < s; i++) { int dim = inputShape.getDimSize(i); if (dim != ShapedType::kDynamicSize) dim *= multipleValues[i]; outputShape.push_back(dim); } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::ReshapeOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ReshapeOpAdaptor adaptor(operands, attributes); ShapeAdaptor inputShape = operands.getShape(0); ArrayAttr newShape = adaptor.getNewShape(); llvm::SmallVector newShapeValue; getI64Values(newShape, newShapeValue); // We cannot infer from the total number of elements so we must take the // shape attribute as exact. if (!inputShape.hasRank() || !inputShape.hasStaticShape()) { inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue)); return success(); } // Determine the number of elements covered by the slice of all static // dimensions. This allows us to infer the length of the remaining dynamic // dimension. int64_t numElements = inputShape.getNumElements(); int64_t staticMul = 1; for (auto val : newShapeValue) { if (val != ShapedType::kDynamicSize) { staticMul *= val; } } // Determine the length of the dynamic dimension. for (auto &val : newShapeValue) { if (val == ShapedType::kDynamicSize) val = numElements / staticMul; } inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue)); return success(); } LogicalResult tosa::TransposeOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); ShapeAdaptor permsShape = operands.getShape(1); // If input rank and permutation length is unknown, the output rank is // unknown. if (!inputShape.hasRank() || !permsShape.hasRank() || permsShape.isDynamicDim(0)) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } // This would imply the number of permutations does not match the rank of the // input which is illegal. if (permsShape.getDimSize(0) != inputShape.getRank()) { return failure(); } // Without the input dims we cannot determine the output dim sizes but we // can determine the output rank. SmallVector outputShape; if (!inputShape.hasRank()) { outputShape.resize(permsShape.getDimSize(0), ShapedType::kDynamicSize); inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } // Rank-0 means no permutations matter. if (inputShape.getRank() == 0) { inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } // Check whether the input dimensions are all the same. bool allTheSame = true; for (int i = 1, s = inputShape.getRank(); i < s; i++) { if (inputShape.getDimSize(0) != inputShape.getDimSize(i)) { allTheSame = false; break; } } // If all of the input dimensions are the same we don't care about the // permutation. if (allTheSame) { outputShape.resize(inputShape.getRank(), inputShape.getDimSize(0)); inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize); // If the permuations are a constant we can directly determine the output // shape. if (ShapeAdaptor permShape = operands.getValueAsShape(1)) { outputShape.reserve(inputShape.getRank()); for (int i = 0, s = inputShape.getRank(); i < s; i++) { outputShape[i] = inputShape.getDimSize(permShape.getDimSize(i)); } } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::GatherOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outputShape; outputShape.resize(3, ShapedType::kDynamicSize); ShapeAdaptor valuesShape = operands.getShape(0); if (valuesShape.hasRank()) { outputShape[0] = valuesShape.getDimSize(0); outputShape[2] = valuesShape.getDimSize(2); } ShapeAdaptor indicesShape = operands.getShape(1); if (indicesShape.hasRank()) { if (outputShape[0] == ShapedType::kDynamicSize) outputShape[0] = indicesShape.getDimSize(0); if (outputShape[1] == ShapedType::kDynamicSize) outputShape[1] = indicesShape.getDimSize(1); } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::ResizeOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { ResizeOpAdaptor adaptor(operands, attributes); llvm::SmallVector outputShape; outputShape.resize(4, ShapedType::kDynamicSize); int32_t inHeight = ShapedType::kDynamicSize; int32_t inWidth = ShapedType::kDynamicSize; ShapeAdaptor inputShape = operands.getShape(adaptor.getInput()); if (inputShape.hasRank()) { outputShape[0] = inputShape.getDimSize(0); outputShape[3] = inputShape.getDimSize(3); inHeight = inputShape.getDimSize(1); inWidth = inputShape.getDimSize(2); } int32_t shift = adaptor.getShift(); llvm::SmallVector newShape; getI64Values(adaptor.getOutputSize(), newShape); outputShape[1] = newShape[0]; outputShape[2] = newShape[1]; llvm::SmallVector strideInt; llvm::SmallVector offsetInt; llvm::SmallVector strideFp; llvm::SmallVector offsetFp; getI64Values(adaptor.getOffset(), offsetInt); getF64Values(adaptor.getOffsetFp(), offsetFp); getI64Values(adaptor.getStride(), strideInt); getF64Values(adaptor.getStrideFp(), strideFp); // If we have a 0 zero in integers we know that the resize indexing needs to // be performed in floating point. Use the floating point varient to compute // the resize shape. bool fpMode = strideInt[0] == 0; // We can compute the output shape if attribute specifies unknown dimensions // based on the offset and stride. If we perfectly line up to the last index // we need to round up the size to include it. if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && fpMode) { float sizeFp = (inHeight - offsetFp[0] - 1) / strideFp[0]; float round = std::floor(sizeFp) == sizeFp ? 1 : 0; outputShape[1] = std::ceil(sizeFp) + round; } if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && fpMode) { float sizeFp = (inWidth - offsetFp[1] - 1) / strideFp[1]; float round = std::floor(sizeFp) == sizeFp ? 1 : 0; outputShape[2] = std::ceil(sizeFp) + round; } if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && !fpMode) { int64_t size = (inHeight - 1); size = ((size << shift) - offsetInt[0]) / strideInt[0]; outputShape[1] = size + 1; } if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && !fpMode) { int64_t size = (inWidth - 1); size = ((size << shift) - offsetInt[1]) / strideInt[1]; outputShape[2] = size + 1; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult tosa::ScatterOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outputShape; outputShape.resize(3, ShapedType::kDynamicSize); ShapeAdaptor valuesInShape = operands.getShape(0); if (valuesInShape.hasRank()) { outputShape[0] = valuesInShape.getDimSize(0); outputShape[1] = valuesInShape.getDimSize(1); outputShape[2] = valuesInShape.getDimSize(2); } ShapeAdaptor indicesShape = operands.getShape(1); if (indicesShape.hasRank()) { if (outputShape[0] == ShapedType::kDynamicSize) outputShape[0] = indicesShape.getDimSize(0); } ShapeAdaptor inputShape = operands.getShape(2); if (inputShape.hasRank()) { if (outputShape[0] == ShapedType::kDynamicSize) outputShape[0] = inputShape.getDimSize(0); if (outputShape[2] == ShapedType::kDynamicSize) outputShape[2] = inputShape.getDimSize(2); } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } static LogicalResult ReduceInferReturnTypes( ShapeAdaptor operandShape, IntegerAttr axis, SmallVectorImpl &inferredReturnShapes) { if (!operandShape.hasRank()) { inferredReturnShapes.push_back(ShapedTypeComponents()); return success(); } SmallVector outputShape; operandShape.getDims(outputShape); int64_t axisVal = axis.getValue().getSExtValue(); outputShape[axisVal] = 1; inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } #define REDUCE_SHAPE_INFER(OP) \ LogicalResult OP::inferReturnTypeComponents( \ MLIRContext *context, ::llvm::Optional location, \ ValueShapeRange operands, DictionaryAttr attributes, \ RegionRange regions, \ SmallVectorImpl &inferredReturnShapes) { \ return ReduceInferReturnTypes(operands.getShape(0), \ attributes.get("axis").cast(), \ inferredReturnShapes); \ } REDUCE_SHAPE_INFER(tosa::ReduceAllOp) REDUCE_SHAPE_INFER(tosa::ReduceAnyOp) REDUCE_SHAPE_INFER(tosa::ReduceMaxOp) REDUCE_SHAPE_INFER(tosa::ReduceMinOp) REDUCE_SHAPE_INFER(tosa::ReduceProdOp) REDUCE_SHAPE_INFER(tosa::ReduceSumOp) #undef REDUCE_SHAPE_INFER static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands, SmallVector &outShape) { int64_t outRank = 0; for (int i = 0, e = operands.size(); i != e; ++i) { auto shape = operands.getShape(i); if (!shape.hasRank()) { return failure(); } outRank = std::max(outRank, shape.getRank()); } outShape.resize(outRank, 1); for (int i = 0, e = operands.size(); i != e; ++i) { auto shape = operands.getShape(i); auto rankDiff = outShape.size() - shape.getRank(); for (size_t i = 0, e = shape.getRank(); i < e; ++i) { auto dim1 = outShape[i + rankDiff]; auto dim2 = shape.getDimSize(i); auto resolvedDim = dim1; if (dim1 == 1) { resolvedDim = dim2; } else if (dim2 == 1) { resolvedDim = dim1; } else if (dim1 != dim2) { return failure(); } outShape[i + rankDiff] = resolvedDim; } } return success(); } static LogicalResult NAryInferReturnTypes( const ValueShapeRange &operands, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outShape; if (resolveBroadcastShape(operands, outShape).failed()) { inferredReturnShapes.push_back(ShapedTypeComponents()); } else { inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); } return success(); } #define NARY_SHAPE_INFER(OP) \ LogicalResult OP::inferReturnTypeComponents( \ MLIRContext *context, ::llvm::Optional location, \ ValueShapeRange operands, DictionaryAttr attributes, \ RegionRange regions, \ SmallVectorImpl &inferredReturnShapes) { \ return NAryInferReturnTypes(operands, inferredReturnShapes); \ } NARY_SHAPE_INFER(tosa::AbsOp) NARY_SHAPE_INFER(tosa::AddOp) NARY_SHAPE_INFER(tosa::ArithmeticRightShiftOp) NARY_SHAPE_INFER(tosa::BitwiseAndOp) NARY_SHAPE_INFER(tosa::BitwiseOrOp) NARY_SHAPE_INFER(tosa::BitwiseXorOp) NARY_SHAPE_INFER(tosa::BitwiseNotOp) NARY_SHAPE_INFER(tosa::CastOp) NARY_SHAPE_INFER(tosa::CeilOp) NARY_SHAPE_INFER(tosa::ClampOp) NARY_SHAPE_INFER(tosa::ClzOp) NARY_SHAPE_INFER(tosa::DivOp) NARY_SHAPE_INFER(tosa::EqualOp) NARY_SHAPE_INFER(tosa::ExpOp) NARY_SHAPE_INFER(tosa::FloorOp) NARY_SHAPE_INFER(tosa::GreaterEqualOp) NARY_SHAPE_INFER(tosa::GreaterOp) NARY_SHAPE_INFER(tosa::IdentityOp) NARY_SHAPE_INFER(tosa::LogOp) NARY_SHAPE_INFER(tosa::LogicalAndOp) NARY_SHAPE_INFER(tosa::LogicalLeftShiftOp) NARY_SHAPE_INFER(tosa::LogicalNotOp) NARY_SHAPE_INFER(tosa::LogicalOrOp) NARY_SHAPE_INFER(tosa::LogicalRightShiftOp) NARY_SHAPE_INFER(tosa::LogicalXorOp) NARY_SHAPE_INFER(tosa::MaximumOp) NARY_SHAPE_INFER(tosa::MinimumOp) NARY_SHAPE_INFER(tosa::MulOp) NARY_SHAPE_INFER(tosa::NegateOp) NARY_SHAPE_INFER(tosa::PowOp) NARY_SHAPE_INFER(tosa::ReciprocalOp) NARY_SHAPE_INFER(tosa::ReluNOp) NARY_SHAPE_INFER(tosa::RescaleOp) NARY_SHAPE_INFER(tosa::ReverseOp) NARY_SHAPE_INFER(tosa::RsqrtOp) NARY_SHAPE_INFER(tosa::SelectOp) NARY_SHAPE_INFER(tosa::SubOp) NARY_SHAPE_INFER(tosa::TanhOp) NARY_SHAPE_INFER(tosa::SigmoidOp) #undef PRED_SHAPE_INFER static LogicalResult poolingInferReturnTypes( const ValueShapeRange &operands, DictionaryAttr attributes, SmallVectorImpl &inferredReturnShapes) { ShapeAdaptor inputShape = operands.getShape(0); llvm::SmallVector outputShape; outputShape.resize(4, -1); // We only know the rank if the input type is unranked. if (!inputShape) { inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } // Batch and number of channels are identical for pooling layer. outputShape[0] = inputShape.getDimSize(0); outputShape[3] = inputShape.getDimSize(3); int32_t height = inputShape.getDimSize(1); int32_t width = inputShape.getDimSize(2); llvm::SmallVector kernel; llvm::SmallVector stride; llvm::SmallVector pad; getI64Values(attributes.get("kernel").cast(), kernel); getI64Values(attributes.get("stride").cast(), stride); getI64Values(attributes.get("pad").cast(), pad); if (height != -1) { int32_t padded = height + pad[0] + pad[1] - kernel[0]; outputShape[1] = padded / stride[0] + 1; } if (width != -1) { int32_t padded = width + pad[2] + pad[3] - kernel[1]; outputShape[2] = padded / stride[1] + 1; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult Conv2DOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outputShape(4, ShapedType::kDynamicSize); Conv2DOp::Adaptor adaptor(operands.getValues(), attributes); int32_t inputWidth = ShapedType::kDynamicSize; int32_t inputHeight = ShapedType::kDynamicSize; int32_t weightWidth = ShapedType::kDynamicSize; int32_t weightHeight = ShapedType::kDynamicSize; // Input shape describes input width/height and batch. ShapeAdaptor inputShape = operands.getShape(adaptor.getInput()); if (inputShape.hasRank()) { outputShape[0] = inputShape.getDimSize(0); inputHeight = inputShape.getDimSize(1); inputWidth = inputShape.getDimSize(2); } // Weight shapes describes the filter width/height and the output channels. ShapeAdaptor weightShape = operands.getShape(adaptor.getWeight()); if (weightShape.hasRank()) { outputShape[3] = weightShape.getDimSize(0); weightHeight = weightShape.getDimSize(1); weightWidth = weightShape.getDimSize(2); } // Bias shape can describe the output channels. ShapeAdaptor biasShape = operands.getShape(adaptor.getBias()); if (biasShape.hasRank()) { outputShape[3] = ShapedType::isDynamic(outputShape[3]) ? biasShape.getDimSize(0) : outputShape[3]; } llvm::SmallVector dilation; llvm::SmallVector padding; llvm::SmallVector stride; getI64Values(adaptor.getDilation(), dilation); getI64Values(adaptor.getPad(), padding); getI64Values(adaptor.getStride(), stride); if (!ShapedType::isDynamic(inputHeight) && !ShapedType::isDynamic(weightHeight)) { int32_t inputSize = inputHeight + padding[0] + padding[1]; int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[1] = (unstridedResult - 1) / stride[0] + 1; } if (!ShapedType::isDynamic(inputWidth) && !ShapedType::isDynamic(weightWidth)) { int32_t inputSize = inputWidth + padding[2] + padding[3]; int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[2] = (unstridedResult - 1) / stride[1] + 1; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult Conv2DOp::verify() { return verifyConvOp(*this); } LogicalResult Conv3DOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outputShape(5, ShapedType::kDynamicSize); Conv2DOp::Adaptor adaptor(operands.getValues(), attributes); int32_t inputWidth = ShapedType::kDynamicSize; int32_t inputHeight = ShapedType::kDynamicSize; int32_t inputDepth = ShapedType::kDynamicSize; int32_t weightWidth = ShapedType::kDynamicSize; int32_t weightHeight = ShapedType::kDynamicSize; int32_t weightDepth = ShapedType::kDynamicSize; // Input shape describes input width/height and batch. ShapeAdaptor inputShape = operands.getShape(adaptor.getInput()); if (inputShape.hasRank()) { outputShape[0] = inputShape.getDimSize(0); inputHeight = inputShape.getDimSize(1); inputWidth = inputShape.getDimSize(2); inputDepth = inputShape.getDimSize(3); } // Weight shapes describes the filter width/height and the output channels. ShapeAdaptor weightShape = operands.getShape(adaptor.getWeight()); if (weightShape.hasRank()) { outputShape[4] = weightShape.getDimSize(0); weightHeight = weightShape.getDimSize(1); weightWidth = weightShape.getDimSize(2); weightDepth = weightShape.getDimSize(3); } // Bias shape can describe the output channels. ShapeAdaptor biasShape = operands.getShape(adaptor.getBias()); if (biasShape.hasRank()) { outputShape[4] = (outputShape[4] == -1) ? biasShape.getDimSize(0) : outputShape[4]; } llvm::SmallVector dilation; llvm::SmallVector padding; llvm::SmallVector stride; getI64Values(adaptor.getDilation(), dilation); getI64Values(adaptor.getPad(), padding); getI64Values(adaptor.getStride(), stride); if (!ShapedType::isDynamic(inputHeight) && !ShapedType::isDynamic(weightHeight)) { int32_t inputSize = inputHeight + padding[0] + padding[1]; int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[1] = (unstridedResult - 1) / stride[0] + 1; } if (!ShapedType::isDynamic(inputWidth) && !ShapedType::isDynamic(weightWidth)) { int32_t inputSize = inputWidth + padding[2] + padding[3]; int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[2] = (unstridedResult - 1) / stride[1] + 1; } if (!ShapedType::isDynamic(inputDepth) && !ShapedType::isDynamic(weightDepth)) { int32_t inputSize = inputDepth + padding[4] + padding[5]; int32_t filterSize = (weightDepth - 1) * dilation[2] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[3] = (unstridedResult - 1) / stride[2] + 1; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult Conv3DOp::verify() { return verifyConvOp(*this); } LogicalResult AvgPool2dOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { return poolingInferReturnTypes(operands, attributes, inferredReturnShapes); } LogicalResult MaxPool2dOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { return poolingInferReturnTypes(operands, attributes, inferredReturnShapes); } LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector outputShape(4, ShapedType::kDynamicSize); DepthwiseConv2DOp::Adaptor adaptor(operands.getValues(), attributes); int32_t inputWidth = ShapedType::kDynamicSize; int32_t inputHeight = ShapedType::kDynamicSize; int32_t inputChannels = ShapedType::kDynamicSize; int32_t weightWidth = ShapedType::kDynamicSize; int32_t weightHeight = ShapedType::kDynamicSize; int32_t depthChannels = ShapedType::kDynamicSize; // Input shape describes input width/height and batch. ShapeAdaptor inputShape = operands.getShape(adaptor.getInput()); if (inputShape.hasRank()) { outputShape[0] = inputShape.getDimSize(0); inputHeight = inputShape.getDimSize(1); inputWidth = inputShape.getDimSize(2); inputChannels = inputShape.getDimSize(3); } // Weight shapes describes the filter width/height and the output channels. ShapeAdaptor weightShape = operands.getShape(adaptor.getWeight()); if (weightShape.hasRank()) { weightHeight = weightShape.getDimSize(0); weightWidth = weightShape.getDimSize(1); inputChannels = ShapedType::isDynamic(inputChannels) ? weightShape.getDimSize(2) : inputChannels; depthChannels = weightShape.getDimSize(3); } // If both inputChannels and depthChannels are available we can determine // the output channels. if (!ShapedType::isDynamic(inputChannels) && !ShapedType::isDynamic(depthChannels)) { outputShape[3] = inputChannels * depthChannels; } // Bias shape can describe the output channels. ShapeAdaptor biasShape = operands.getShape(adaptor.getBias()); if (biasShape.hasRank()) { outputShape[3] = ShapedType::isDynamic(outputShape[3]) ? biasShape.getDimSize(0) : outputShape[3]; } llvm::SmallVector dilation; llvm::SmallVector padding; llvm::SmallVector stride; getI64Values(adaptor.getDilation(), dilation); getI64Values(adaptor.getPad(), padding); getI64Values(adaptor.getStride(), stride); if (!ShapedType::isDynamic(inputHeight) && !ShapedType::isDynamic(weightHeight)) { int32_t inputSize = inputHeight + padding[0] + padding[1]; int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[1] = (unstridedResult - 1) / stride[0] + 1; } if (!ShapedType::isDynamic(inputWidth) && !ShapedType::isDynamic(weightWidth)) { int32_t inputSize = inputWidth + padding[2] + padding[3]; int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; int32_t unstridedResult = inputSize - filterSize + 1; outputShape[2] = (unstridedResult - 1) / stride[1] + 1; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult DepthwiseConv2DOp::verify() { return verifyConvOp(*this); } LogicalResult TransposeConv2DOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { TransposeConv2DOp::Adaptor adaptor(operands.getValues(), attributes); llvm::SmallVector outputShape; getI64Values(adaptor.getOutShape(), outputShape); int32_t inputWidth = ShapedType::kDynamicSize; int32_t inputHeight = ShapedType::kDynamicSize; int32_t weightWidth = ShapedType::kDynamicSize; int32_t weightHeight = ShapedType::kDynamicSize; // Input shape describes input width/height and batch. ShapeAdaptor inputShape = operands.getShape(adaptor.getInput()); if (inputShape.hasRank()) { outputShape[0] = ShapedType::isDynamic(outputShape[0]) ? inputShape.getDimSize(0) : outputShape[0]; inputHeight = inputShape.getDimSize(1); inputWidth = inputShape.getDimSize(2); } // Weight shapes describes the filter width/height and the output channels. ShapeAdaptor weightShape = operands.getShape(adaptor.getFilter()); if (weightShape.hasRank()) { outputShape[3] = ShapedType::isDynamic(outputShape[3]) ? weightShape.getDimSize(0) : outputShape[3]; weightHeight = weightShape.getDimSize(1); weightWidth = weightShape.getDimSize(2); } // Bias shape can describe the output channels. ShapeAdaptor biasShape = operands.getShape(adaptor.getInput()); if (biasShape.hasRank()) { outputShape[3] = ShapedType::isDynamic(outputShape[3]) ? biasShape.getDimSize(0) : outputShape[3]; } llvm::SmallVector padding; llvm::SmallVector stride; getI64Values(adaptor.getOutPad(), padding); getI64Values(adaptor.getStride(), stride); if (!ShapedType::isDynamic(inputHeight) && !ShapedType::isDynamic(weightHeight)) { int32_t calculateSize = (inputHeight - 1) * stride[0] - padding[0] - padding[1] + weightHeight; outputShape[1] = outputShape[1] == -1 ? calculateSize : outputShape[1]; } if (!ShapedType::isDynamic(inputWidth) && !ShapedType::isDynamic(weightWidth)) { int32_t calculateSize = (inputWidth - 1) * stride[1] - padding[2] - padding[3] + weightWidth; outputShape[2] = outputShape[2] == -1 ? calculateSize : outputShape[2]; } inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); return success(); } LogicalResult IfOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector yieldOps; for (Region *region : regions) { for (auto &block : *region) if (auto returnOp = dyn_cast(block.getTerminator())) yieldOps.push_back(returnOp); } if (yieldOps.empty()) return failure(); // Get the initial type information for the yield op. llvm::SmallVector resultKnowledge; resultKnowledge.reserve(yieldOps.front().getNumOperands()); for (auto operand : yieldOps.front().getOperands()) { resultKnowledge.push_back( ValueKnowledge::getKnowledgeFromType(operand.getType())); } for (auto yieldOp : yieldOps) { if (resultKnowledge.size() != yieldOp.getNumOperands()) return failure(); for (const auto &it : llvm::enumerate(yieldOp.getOperands())) { int32_t index = it.index(); auto meet = ValueKnowledge::meet( resultKnowledge[index], ValueKnowledge::getKnowledgeFromType(it.value().getType())); if (!meet) continue; resultKnowledge[index] = meet; } } for (const ValueKnowledge &result : resultKnowledge) { inferredReturnShapes.push_back(result.getShapedTypeComponents()); } return success(); } LogicalResult WhileOp::inferReturnTypeComponents( MLIRContext *context, ::llvm::Optional location, ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, SmallVectorImpl &inferredReturnShapes) { llvm::SmallVector yieldOps; for (auto &block : *regions[1]) if (auto returnOp = dyn_cast(block.getTerminator())) yieldOps.push_back(returnOp); // TOSA's while must have a tosa.yield as its terminator. If not found this // tosa.while is invalid. if (yieldOps.empty()) return failure(); // Get the initial type information from the operand types. llvm::SmallVector resultKnowledge; resultKnowledge.reserve(yieldOps.front().getNumOperands()); for (auto operand : yieldOps.front().getOperands()) { resultKnowledge.push_back( ValueKnowledge::getKnowledgeFromType(operand.getType())); } for (auto yieldOp : yieldOps) { if (resultKnowledge.size() != yieldOp.getNumOperands()) return failure(); for (const auto &it : llvm::enumerate(yieldOp.getOperands())) { int32_t index = it.index(); if (auto meet = ValueKnowledge::meet( resultKnowledge[index], ValueKnowledge::getKnowledgeFromType(it.value().getType()))) { resultKnowledge[index] = meet; } } } for (const ValueKnowledge &result : resultKnowledge) { inferredReturnShapes.push_back(result.getShapedTypeComponents()); } return success(); } //===----------------------------------------------------------------------===// // TOSA Attribute Definitions. //===----------------------------------------------------------------------===// #define GET_ATTRDEF_CLASSES #include "mlir/Dialect/Tosa/IR/TosaAttributes.cpp.inc" //===----------------------------------------------------------------------===// // TOSA Operator Definitions. //===----------------------------------------------------------------------===// #define GET_OP_CLASSES #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc"