1 //===- TosaOps.cpp - MLIR Dialect for TOSA --------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // \file 10 // This file implements the TOSA Specification: 11 // https://developer.mlplatform.org/w/tosa/ 12 // 13 //===----------------------------------------------------------------------===// 14 15 #include "mlir/Dialect/Tosa/IR/TosaOps.h" 16 #include "mlir/Dialect/StandardOps/IR/Ops.h" 17 #include "mlir/Dialect/Tensor/IR/Tensor.h" 18 #include "mlir/Dialect/Tosa/Utils/QuantUtils.h" 19 #include "mlir/Dialect/Tosa/Utils/ShapeUtils.h" 20 #include "mlir/IR/BuiltinTypes.h" 21 #include "mlir/IR/Matchers.h" 22 #include "mlir/IR/PatternMatch.h" 23 #include "mlir/Transforms/FoldUtils.h" 24 #include "mlir/Transforms/InliningUtils.h" 25 #include "mlir/Transforms/RegionUtils.h" 26 #include "llvm/ADT/DenseMap.h" 27 28 using namespace mlir; 29 using namespace mlir::tosa; 30 31 #include "mlir/Dialect/Tosa/IR/TosaOpsDialect.cpp.inc" 32 33 //===----------------------------------------------------------------------===// 34 // Tosa dialect structs and interface includes. 35 //===----------------------------------------------------------------------===// 36 #include "mlir/Dialect/Tosa/IR/TosaInterfaces.cpp.inc" 37 #include "mlir/Dialect/Tosa/IR/TosaStructs.cpp.inc" 38 39 namespace { 40 //===----------------------------------------------------------------------===// 41 // Dialect Function Inliner Interface. 42 //===----------------------------------------------------------------------===// 43 struct TosaInlinerInterface : public DialectInlinerInterface { 44 using DialectInlinerInterface::DialectInlinerInterface; 45 46 //===--------------------------------------------------------------------===// 47 // Analysis Hooks. 48 //===--------------------------------------------------------------------===// 49 50 /// All operations can be inlined by default. 51 bool isLegalToInline(Operation *op, Region *region, bool wouldBeCloned, 52 BlockAndValueMapping &map) const final { 53 return true; 54 } 55 56 /// All regions with If and While parent operators can be inlined. 57 bool isLegalToInline(Region *dest, Region *src, bool wouldBeCloned, 58 BlockAndValueMapping &map) const final { 59 return (isa<tosa::IfOp>(dest->getParentOp()) || 60 isa<tosa::WhileOp>(dest->getParentOp())); 61 } 62 }; 63 } // namespace 64 65 //===----------------------------------------------------------------------===// 66 // TOSA control flow support. 67 //===----------------------------------------------------------------------===// 68 69 /// Returns the while loop body. 70 Region &tosa::WhileOp::getLoopBody() { return body(); } 71 72 bool tosa::WhileOp::isDefinedOutsideOfLoop(Value value) { 73 return !body().isAncestor(value.getParentRegion()); 74 } 75 76 LogicalResult WhileOp::moveOutOfLoop(ArrayRef<mlir::Operation *> ops) { 77 if (ops.empty()) 78 return success(); 79 80 Operation *tosaWhileOp = this->getOperation(); 81 for (auto *op : ops) 82 op->moveBefore(tosaWhileOp); 83 84 return success(); 85 } 86 87 //===----------------------------------------------------------------------===// 88 // Tosa dialect initialization. 89 //===----------------------------------------------------------------------===// 90 91 void TosaDialect::initialize() { 92 addOperations< 93 #define GET_OP_LIST 94 #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc" 95 >(); 96 addInterfaces<TosaInlinerInterface>(); 97 } 98 99 Operation *TosaDialect::materializeConstant(OpBuilder &builder, Attribute value, 100 Type type, Location loc) { 101 // Tosa dialect constants only support ElementsAttr unlike standard dialect 102 // constant which supports all attributes. 103 if (value.isa<ElementsAttr>()) 104 return builder.create<tosa::ConstOp>(loc, type, value.cast<ElementsAttr>()); 105 return nullptr; 106 } 107 108 //===----------------------------------------------------------------------===// 109 // Operator Canonicalizers. 110 //===----------------------------------------------------------------------===// 111 112 struct ConcatOptimization : public OpRewritePattern<tosa::ConcatOp> { 113 using OpRewritePattern<tosa::ConcatOp>::OpRewritePattern; 114 115 LogicalResult matchAndRewrite(tosa::ConcatOp op, 116 PatternRewriter &rewriter) const override { 117 if (op.input1().size() != 1) 118 return failure(); 119 if (op.input1().front().getType() != op.getType()) { 120 rewriter 121 .replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), 122 op.input1().front()) 123 .getResult(); 124 return success(); 125 } 126 127 rewriter.replaceOp(op, op.input1().front()); 128 return success(); 129 } 130 }; 131 132 void ConcatOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 133 MLIRContext *context) { 134 results.insert<ConcatOptimization>(context); 135 } 136 137 struct ReshapeReshapeOptimization : public OpRewritePattern<tosa::ReshapeOp> { 138 using OpRewritePattern<tosa::ReshapeOp>::OpRewritePattern; 139 140 LogicalResult matchAndRewrite(tosa::ReshapeOp op, 141 PatternRewriter &rewriter) const override { 142 Value input = op.input1(); 143 Operation *definingOp = input.getDefiningOp(); 144 if (!definingOp) 145 return failure(); 146 147 if (tosa::ReshapeOp reshapeOp = dyn_cast<tosa::ReshapeOp>(definingOp)) { 148 rewriter.replaceOpWithNewOp<tosa::ReshapeOp>( 149 op, op.getType(), reshapeOp.input1(), op.new_shape()); 150 return success(); 151 } 152 153 return failure(); 154 } 155 }; 156 157 struct ReshapeConstOptimization : public OpRewritePattern<tosa::ReshapeOp> { 158 using OpRewritePattern<tosa::ReshapeOp>::OpRewritePattern; 159 160 LogicalResult matchAndRewrite(tosa::ReshapeOp op, 161 PatternRewriter &rewriter) const override { 162 Value input = op.input1(); 163 ArrayAttr newShape = op.new_shape(); 164 165 // Check if input is constant 166 DenseElementsAttr inputAttr; 167 if (!matchPattern(input, m_Constant(&inputAttr))) 168 return failure(); 169 170 // Check if has >1 consumer and is not splat 171 if (!input.hasOneUse() && !inputAttr.isSplat()) 172 return failure(); 173 174 // Grab the new shape 175 SmallVector<int64_t> newShapeValues = llvm::to_vector<6>( 176 llvm::map_range(newShape.getValue(), [](const Attribute &val) { 177 return val.cast<IntegerAttr>().getValue().getSExtValue(); 178 })); 179 180 // Build new const op with correct output shape 181 ShapedType inputShape = input.getType().cast<ShapedType>(); 182 DenseElementsAttr outputAttr = 183 inputAttr.reshape(inputShape.clone(newShapeValues)); 184 rewriter.replaceOpWithNewOp<tosa::ConstOp>(op, outputAttr.getType(), 185 outputAttr); 186 return success(); 187 } 188 }; 189 190 void ReshapeOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 191 MLIRContext *context) { 192 results.insert<ReshapeReshapeOptimization>(context); 193 results.insert<ReshapeConstOptimization>(context); 194 } 195 196 struct ConstantTransposeOptimization 197 : public OpRewritePattern<tosa::TransposeOp> { 198 using OpRewritePattern::OpRewritePattern; 199 200 LogicalResult matchAndRewrite(tosa::TransposeOp op, 201 PatternRewriter &rewriter) const override { 202 auto outputType = op.getType().cast<ShapedType>(); 203 ArrayRef<int64_t> outputShape = outputType.getShape(); 204 // TOSA supports quantized types. 205 if (!outputType.getElementType().isIntOrIndexOrFloat()) 206 return failure(); 207 208 DenseElementsAttr inputValues; 209 if (!matchPattern(op.input1(), m_Constant(&inputValues))) 210 return failure(); 211 // Make sure the input is a constant that has a single user. 212 if (!llvm::hasSingleElement(op.input1().getDefiningOp()->getUsers())) 213 return failure(); 214 215 DenseIntElementsAttr permAttr; 216 if (!matchPattern(op.perms(), m_Constant(&permAttr))) 217 return failure(); 218 auto permValues = llvm::to_vector<6>(llvm::map_range( 219 // TOSA allows both 32- and 64-bit integer tensors here. 220 permAttr.getValues<APInt>(), 221 [](const APInt &val) { return val.getZExtValue(); })); 222 223 auto inputType = op.input1().getType().cast<ShapedType>(); 224 ArrayRef<int64_t> inputShape = inputType.getShape(); 225 int64_t numElements = inputType.getNumElements(); 226 227 SmallVector<Attribute, 4> outputValues; 228 outputValues.resize(numElements); 229 230 // Transpose the input constant. Because we don't know its rank in advance, 231 // we need to loop over the range [0, element count) and delinearize the 232 // index. 233 auto attrValues = inputValues.getValues<Attribute>(); 234 for (int srcLinearIndex = 0; srcLinearIndex < numElements; 235 ++srcLinearIndex) { 236 SmallVector<uint64_t, 6> srcIndices(inputType.getRank(), 0); 237 int totalCount = srcLinearIndex; 238 for (int dim = inputType.getRank() - 1; dim >= 0; --dim) { 239 srcIndices[dim] = totalCount % inputShape[dim]; 240 totalCount /= inputShape[dim]; 241 } 242 243 SmallVector<uint64_t, 6> dstIndices(outputType.getRank(), 0); 244 for (int dim = outputType.getRank() - 1; dim >= 0; --dim) 245 dstIndices[dim] = srcIndices[permValues[dim]]; 246 247 uint64_t dstLinearIndex = dstIndices.front(); 248 for (int dim = 1; dim < outputType.getRank(); ++dim) 249 dstLinearIndex = dstLinearIndex * outputShape[dim] + dstIndices[dim]; 250 251 outputValues[dstLinearIndex] = attrValues[srcIndices]; 252 } 253 254 rewriter.replaceOpWithNewOp<tosa::ConstOp>( 255 op, outputType, DenseElementsAttr::get(outputType, outputValues)); 256 return success(); 257 } 258 }; 259 260 struct NoOpOptimization : public OpRewritePattern<tosa::TransposeOp> { 261 using OpRewritePattern::OpRewritePattern; 262 263 LogicalResult matchAndRewrite(tosa::TransposeOp op, 264 PatternRewriter &rewriter) const override { 265 auto perm = op.perms(); 266 267 DenseIntElementsAttr permAttr; 268 if (!matchPattern(perm, m_Constant(&permAttr))) { 269 return failure(); 270 } 271 272 SmallVector<int64_t> permValues = llvm::to_vector<6>( 273 llvm::map_range(permAttr.getValues<APInt>(), 274 [](const APInt &val) { return val.getSExtValue(); })); 275 276 for (int i = 0, s = permValues.size(); i < s; i++) { 277 if (i != permValues[i]) { 278 return failure(); 279 } 280 } 281 282 rewriter.replaceOp(op, op.input1()); 283 return success(); 284 } 285 }; 286 287 void TransposeOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 288 MLIRContext *context) { 289 results.insert<ConstantTransposeOptimization>(context); 290 results.insert<NoOpOptimization>(context); 291 } 292 293 struct AddZeroOptimization : public OpRewritePattern<tosa::AddOp> { 294 using OpRewritePattern::OpRewritePattern; 295 296 LogicalResult matchAndRewrite(tosa::AddOp op, 297 PatternRewriter &rewriter) const override { 298 auto input1 = op.input1(); 299 auto input2 = op.input2(); 300 301 DenseElementsAttr input1Attr; 302 if (matchPattern(input1, m_Constant(&input1Attr)) && input1Attr.isSplat() && 303 input2.getType() == op.getType()) { 304 if (input1Attr.getType().getElementType().isa<IntegerType>() && 305 input1Attr.getSplatValue<APInt>().isZero()) { 306 rewriter.replaceOp(op, op.input2()); 307 return success(); 308 } 309 } 310 311 DenseElementsAttr input2Attr; 312 if (matchPattern(input2, m_Constant(&input2Attr)) && input2Attr.isSplat() && 313 input1.getType() == op.getType()) { 314 if (input2Attr.getType().getElementType().isa<IntegerType>() && 315 input2Attr.getSplatValue<APInt>().isZero()) { 316 rewriter.replaceOp(op, op.input1()); 317 return success(); 318 } 319 } 320 321 return failure(); 322 } 323 }; 324 325 void AddOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 326 MLIRContext *context) { 327 results.insert<AddZeroOptimization>(context); 328 } 329 330 struct MulOneOptimization : public OpRewritePattern<tosa::MulOp> { 331 using OpRewritePattern::OpRewritePattern; 332 333 LogicalResult matchAndRewrite(tosa::MulOp op, 334 PatternRewriter &rewriter) const override { 335 auto input1 = op.input1(); 336 auto input2 = op.input2(); 337 338 DenseElementsAttr input1Attr; 339 if (matchPattern(input1, m_Constant(&input1Attr)) && input1Attr.isSplat() && 340 input2.getType() == op.getType()) { 341 if (input1Attr.getType().getElementType().isa<FloatType>() && 342 input1Attr.getSplatValue<APFloat>().isExactlyValue(1)) { 343 rewriter.replaceOp(op, op.input2()); 344 return success(); 345 } 346 347 if (input1Attr.getType().getElementType().isa<IntegerType>() && 348 matchPattern(input1, m_One())) { 349 rewriter.replaceOp(op, op.input2()); 350 return success(); 351 } 352 } 353 354 DenseElementsAttr input2Attr; 355 if (matchPattern(input2, m_Constant(&input2Attr)) && input2Attr.isSplat() && 356 input1.getType() == op.getType()) { 357 if (input2Attr.getType().getElementType().isa<FloatType>() && 358 input2Attr.getSplatValue<APFloat>().isExactlyValue(1)) { 359 rewriter.replaceOp(op, op.input1()); 360 return success(); 361 } 362 363 if (input2Attr.getType().getElementType().isa<IntegerType>() && 364 matchPattern(input2, m_One())) { 365 rewriter.replaceOp(op, op.input1()); 366 return success(); 367 } 368 } 369 370 return failure(); 371 } 372 }; 373 374 void MulOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 375 MLIRContext *context) { 376 results.insert<MulOneOptimization>(context); 377 } 378 379 struct MaterializePadValue : public OpRewritePattern<tosa::PadOp> { 380 using OpRewritePattern::OpRewritePattern; 381 382 LogicalResult matchAndRewrite(tosa::PadOp op, 383 PatternRewriter &rewriter) const override { 384 if (op.pad_const()) 385 return failure(); 386 387 auto input = op.input1(); 388 auto padding = op.padding(); 389 390 ShapedType inputTy = input.getType().cast<ShapedType>(); 391 Type elementTy = inputTy.getElementType(); 392 393 Attribute constantAttr; 394 if (elementTy.isa<FloatType>()) 395 constantAttr = rewriter.getFloatAttr(elementTy, 0.0); 396 else if (elementTy.isa<IntegerType>() && !op.quantization_info()) 397 constantAttr = rewriter.getIntegerAttr(elementTy, 0); 398 else if (elementTy.isa<IntegerType>() && op.quantization_info()) { 399 auto value = op.quantization_info().getValue().input_zp().getValue(); 400 constantAttr = rewriter.getIntegerAttr(elementTy, value.getZExtValue()); 401 } 402 403 if (!constantAttr) { 404 return rewriter.notifyMatchFailure( 405 op, 406 "tosa.pad to linalg lowering encountered an unknown element type"); 407 } 408 409 auto denseAttr = DenseElementsAttr::get( 410 RankedTensorType::get({}, elementTy), constantAttr); 411 auto constantVal = rewriter.create<tosa::ConstOp>( 412 op.getLoc(), denseAttr.getType(), denseAttr); 413 414 rewriter.replaceOpWithNewOp<tosa::PadOp>( 415 op, op.getType(), ValueRange{input, padding, constantVal}, 416 op->getAttrs()); 417 return success(); 418 } 419 }; 420 421 void PadOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 422 MLIRContext *context) { 423 results.insert<MaterializePadValue>(context); 424 } 425 426 struct MaxPool2dIsNoOp : public OpRewritePattern<tosa::MaxPool2dOp> { 427 using OpRewritePattern::OpRewritePattern; 428 429 LogicalResult matchAndRewrite(tosa::MaxPool2dOp op, 430 PatternRewriter &rewriter) const override { 431 Value input = op.input(); 432 Value output = op.output(); 433 ShapedType inputType = input.getType().cast<ShapedType>(); 434 ShapedType outputType = output.getType().cast<ShapedType>(); 435 436 if (!inputType.hasStaticShape() || !outputType.hasStaticShape()) { 437 return failure(); 438 } 439 440 // If the output and input shapes are 1x1, then this is a no op. 441 ArrayRef<int64_t> outputShape = outputType.getShape(); 442 if (outputShape[1] != 1 || outputShape[2] != 1) { 443 return failure(); 444 } 445 446 ArrayRef<int64_t> inputShape = inputType.getShape(); 447 if (inputShape[1] != 1 || inputShape[2] != 1) { 448 return failure(); 449 } 450 451 rewriter.replaceOp(op, input); 452 return success(); 453 } 454 }; 455 456 void MaxPool2dOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 457 MLIRContext *context) { 458 results.insert<MaxPool2dIsNoOp>(context); 459 } 460 461 struct ClampIsNoOp : public OpRewritePattern<tosa::ClampOp> { 462 using OpRewritePattern::OpRewritePattern; 463 464 LogicalResult matchAndRewrite(tosa::ClampOp op, 465 PatternRewriter &rewriter) const override { 466 Value input = op.input(); 467 auto inputType = op.input().getType().template dyn_cast<RankedTensorType>(); 468 auto inputElementType = inputType.getElementType(); 469 470 if (!inputType.hasStaticShape()) { 471 return failure(); 472 } 473 474 if (inputElementType.isF32()) { 475 auto minClamp = op.min_fp(); 476 auto maxClamp = op.max_fp(); 477 bool isMin = (minClamp.isLargest() || minClamp.isInfinity()) && 478 minClamp.isNegative(); 479 bool isMax = (maxClamp.isLargest() || maxClamp.isInfinity()) && 480 !maxClamp.isNegative(); 481 482 if (isMin && isMax) { 483 rewriter.replaceOp(op, input); 484 return success(); 485 } 486 return failure(); 487 } 488 489 if (inputElementType.isUnsignedInteger()) { 490 int64_t minClamp = op.min_int(); 491 int64_t maxClamp = op.max_int(); 492 493 int64_t intMin = 494 APInt::getMinValue(inputElementType.getIntOrFloatBitWidth()) 495 .getZExtValue(); 496 int64_t intMax = 497 APInt::getMaxValue(inputElementType.getIntOrFloatBitWidth()) 498 .getZExtValue(); 499 500 if (minClamp <= intMin && maxClamp >= intMax) { 501 rewriter.replaceOp(op, input); 502 return success(); 503 } 504 return failure(); 505 } 506 507 if (inputElementType.isa<IntegerType>()) { 508 int64_t minClamp = op.min_int(); 509 int64_t maxClamp = op.max_int(); 510 511 int64_t intMin = 512 APInt::getSignedMinValue(inputElementType.getIntOrFloatBitWidth()) 513 .getSExtValue(); 514 int64_t intMax = 515 APInt::getSignedMaxValue(inputElementType.getIntOrFloatBitWidth()) 516 .getSExtValue(); 517 518 if (minClamp <= intMin && maxClamp >= intMax) { 519 rewriter.replaceOp(op, input); 520 return success(); 521 } 522 return failure(); 523 } 524 525 return failure(); 526 } 527 }; 528 529 void ClampOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 530 MLIRContext *context) { 531 results.insert<ClampIsNoOp>(context); 532 } 533 534 //===----------------------------------------------------------------------===// 535 // Operator Folders. 536 //===----------------------------------------------------------------------===// 537 538 OpFoldResult CastOp::fold(ArrayRef<Attribute> operands) { 539 if (input().getType() == getType()) 540 return input(); 541 return {}; 542 } 543 544 OpFoldResult ConstOp::fold(ArrayRef<Attribute> operands) { 545 assert(operands.empty() && "constant has no operands"); 546 return valueAttr(); 547 } 548 549 #define ReduceFolder(OP) \ 550 OpFoldResult OP::fold(ArrayRef<Attribute> operands) { \ 551 ShapedType inputTy = input().getType().cast<ShapedType>(); \ 552 if (!inputTy.hasRank()) \ 553 return {}; \ 554 if (inputTy.getDimSize(axis()) == 1) \ 555 return input(); \ 556 return {}; \ 557 } 558 559 ReduceFolder(ReduceAllOp) ReduceFolder(ReduceAnyOp) ReduceFolder(ReduceMaxOp) 560 ReduceFolder(ReduceMinOp) ReduceFolder(ReduceProdOp) 561 ReduceFolder(ReduceSumOp) 562 #undef ReduceFolder 563 564 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) { 565 auto inputTy = input1().getType().dyn_cast<RankedTensorType>(); 566 auto outputTy = getType().dyn_cast<RankedTensorType>(); 567 568 if (!inputTy || !outputTy || inputTy != outputTy) 569 return {}; 570 return input1(); 571 } 572 573 OpFoldResult PadOp::fold(ArrayRef<Attribute> operands) { 574 // If the pad is all zeros we can fold this operation away. 575 if (operands[1]) { 576 auto densePad = operands[1].cast<DenseElementsAttr>(); 577 if (densePad.isSplat() && densePad.getSplatValue<APInt>().isZero()) { 578 return input1(); 579 } 580 } 581 582 return {}; 583 } 584 585 OpFoldResult SliceOp::fold(ArrayRef<Attribute> operands) { 586 auto inputTy = input().getType().dyn_cast<RankedTensorType>(); 587 auto outputTy = getType().dyn_cast<RankedTensorType>(); 588 589 if (!inputTy || !outputTy || inputTy != outputTy) 590 return {}; 591 if (inputTy.hasStaticShape()) 592 return input(); 593 594 return {}; 595 } 596 597 OpFoldResult TileOp::fold(ArrayRef<Attribute> operands) { 598 bool allOnes = true; 599 for (Attribute val : multiples().getValue()) { 600 allOnes = allOnes && val.cast<IntegerAttr>().getValue().getSExtValue() == 1; 601 } 602 603 if (allOnes && input1().getType() == getType()) 604 return input1(); 605 return {}; 606 } 607 608 OpFoldResult TransposeOp::fold(ArrayRef<Attribute> operands) { 609 if (!operands[1]) 610 return {}; 611 612 // Transposing splat values just means reshaping. 613 if (auto input = operands[0].dyn_cast_or_null<DenseElementsAttr>()) { 614 if (input.isSplat()) 615 return input.reshape(getType().cast<ShapedType>()); 616 } 617 618 auto perms = llvm::to_vector<6>(llvm::map_range( 619 operands[1].cast<DenseIntElementsAttr>().getValues<APInt>(), 620 [](const APInt &val) { return val.getSExtValue(); })); 621 622 if (llvm::equal(llvm::seq<int64_t>(0, perms.size()), perms) && 623 input1().getType() == getType()) 624 return input1(); 625 return {}; 626 } 627 628 //===----------------------------------------------------------------------===// 629 // TOSA Operator Verifiers. 630 //===----------------------------------------------------------------------===// 631 632 template <typename T> 633 static LogicalResult verifyConvOp(T op) { 634 // All TOSA conv ops have an input() and weight(). 635 auto inputType = op.input().getType().template dyn_cast<RankedTensorType>(); 636 auto weightType = op.weight().getType().template dyn_cast<RankedTensorType>(); 637 638 // Must be ranked tensor types 639 if (!inputType) { 640 op.emitOpError("expect a ranked tensor for input, got ") << op.input(); 641 return failure(); 642 } 643 if (!weightType) { 644 op.emitOpError("expect a ranked tensor for weight, got ") << op.weight(); 645 return failure(); 646 } 647 648 auto inputEType = inputType.getElementType(); 649 auto weightEType = weightType.getElementType(); 650 651 bool inputIsQuant = !inputEType.template isa<FloatType>(); 652 bool weightIsQuant = !weightEType.template isa<FloatType>(); 653 654 // Either both must be quantized or both unquantized. 655 if (inputIsQuant != weightIsQuant) { 656 op.emitOpError( 657 "expect both input and weight to be float or not together, got ") 658 << inputEType << " and " << weightEType; 659 return failure(); 660 } 661 662 // Quantized type must have constructed the quantizationattr, and unquantized 663 // types should not have a quantizationattr. 664 if ((inputIsQuant && !op.quantization_info()) || 665 (!inputIsQuant && op.quantization_info())) { 666 op.emitOpError("quantizationattr is required for quantized type, and not " 667 "allowed for float type"); 668 return failure(); 669 } 670 671 return success(); 672 } 673 674 static LogicalResult verifyAveragePoolOp(tosa::AvgPool2dOp op) { 675 auto inputETy = op.input().getType().cast<ShapedType>().getElementType(); 676 auto resultETy = op.getType().cast<ShapedType>().getElementType(); 677 678 if (auto quantType = inputETy.dyn_cast<mlir::quant::UniformQuantizedType>()) 679 inputETy = quantType.getStorageType(); 680 681 if (auto quantType = resultETy.dyn_cast<mlir::quant::UniformQuantizedType>()) 682 resultETy = quantType.getStorageType(); 683 684 if (inputETy.isF32() && resultETy.isF32()) 685 return success(); 686 if (inputETy.isInteger(8) && resultETy.isInteger(8)) 687 return success(); 688 if (inputETy.isInteger(16) && resultETy.isInteger(16)) 689 return success(); 690 691 return op.emitOpError("input/output element types are incompatible."); 692 } 693 694 //===----------------------------------------------------------------------===// 695 // TOSA Operator Quantization Builders. 696 //===----------------------------------------------------------------------===// 697 698 /// This builder is called on all convolution operators except TransposeConv, 699 /// which has specialized output shape semantics. The builder also defines the 700 /// bitwidth of the output given the bit width of the input & weight content. 701 static void buildConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, 702 Type outputType, Value input, Value weight, 703 Value bias, ArrayAttr pad, 704 ArrayAttr stride, ArrayAttr dilation) { 705 706 result.addOperands({input, weight, bias}); 707 result.addAttribute("pad", pad); 708 result.addAttribute("stride", stride); 709 result.addAttribute("dilation", dilation); 710 711 auto quantAttr = buildConvOpQuantizationAttr(builder, input, weight); 712 if (quantAttr) { 713 result.addAttribute("quantization_info", quantAttr); 714 result.addTypes( 715 buildConvOpResultTypeInfo(builder, outputType, input, weight)); 716 } else { 717 result.addTypes(outputType); 718 } 719 } 720 721 /// Handles tosa.transpose_conv2d which has outpad and output shape attributes. 722 static void 723 buildTransConvOpWithQuantInfo(OpBuilder &builder, OperationState &result, 724 Type outputType, Value input, Value weight, 725 Value bias, ArrayAttr outpad, ArrayAttr stride, 726 ArrayAttr dilation, ArrayAttr outputShape) { 727 result.addOperands({input, weight, bias}); 728 result.addAttribute("out_pad", outpad); 729 result.addAttribute("stride", stride); 730 result.addAttribute("dilation", dilation); 731 result.addAttribute("out_shape", outputShape); 732 auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight); 733 734 if (quantAttr) { 735 result.addAttribute("quantization_info", quantAttr); 736 result.addTypes( 737 buildConvOpResultTypeInfo(builder, outputType, input, weight)); 738 } else { 739 result.addTypes(outputType); 740 } 741 } 742 743 /// The tosa.fully_connected op has its own builder as it does not have 744 /// strides/dilation/padding. 745 static void buildFCOpWithQuantInfo(OpBuilder &builder, OperationState &result, 746 Type outputType, Value input, Value weight, 747 Value bias) { 748 749 result.addOperands({input, weight, bias}); 750 auto quantAttr = ::buildConvOpQuantizationAttr(builder, input, weight); 751 if (quantAttr) { 752 result.addAttribute("quantization_info", quantAttr); 753 result.addTypes( 754 buildConvOpResultTypeInfo(builder, outputType, input, weight)); 755 } else { 756 result.addTypes(outputType); 757 } 758 } 759 760 /// The tosa.matmul op is also intended to be generated where a fully_connected 761 /// op must be constructed where the weight is not a constant. In this case, 762 /// the fully_connected op must be expressed using matmul. 763 /// TODO: Add link to the leglization document explaining this. 764 static void buildMatMulOpWithQuantInfo(OpBuilder &builder, 765 OperationState &result, Type outputType, 766 Value a, Value b) { 767 result.addOperands({a, b}); 768 auto quantAttr = ::buildMatMulOpQuantizationAttr(builder, a, b); 769 770 if (quantAttr) { 771 result.addAttribute("quantization_info", quantAttr); 772 773 auto inputType = a.getType().dyn_cast<ShapedType>(); 774 assert(inputType && "Input must be a shaped tensor type!"); 775 776 auto inputQType = inputType.getElementType() 777 .dyn_cast<mlir::quant::UniformQuantizedType>(); 778 assert(inputQType && "Tensor must have quantized datatype!"); 779 780 unsigned inputBits = inputQType.getStorageTypeIntegralWidth(); 781 782 auto outputShapedType = outputType.dyn_cast<ShapedType>(); 783 assert(outputShapedType && "Output must be a shaped type"); 784 785 IntegerType accElementType; 786 if (inputBits == 16) 787 accElementType = builder.getIntegerType(48); 788 else 789 accElementType = builder.getI32Type(); 790 auto accType = outputShapedType.clone(accElementType); 791 result.addTypes(accType); 792 } else { 793 result.addTypes(outputType); 794 } 795 } 796 797 /// Both the tosa.avg_pool2d and unary ops use the same UnaruOpQuantizationAttr 798 /// but avg_pool operator has its own builder as it has additional parameters 799 /// not part of the unary ops. 800 static void buildAvgPool2dOpWithQuantInfo(OpBuilder &builder, 801 OperationState &result, 802 Type outputType, Value input, 803 ArrayAttr kernel, ArrayAttr stride, 804 ArrayAttr pad) { 805 result.addOperands(input); 806 result.addAttribute("kernel", kernel); 807 result.addAttribute("stride", stride); 808 result.addAttribute("pad", pad); 809 auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType); 810 if (quantAttr) 811 result.addAttribute("quantization_info", quantAttr); 812 result.types.push_back(outputType); 813 } 814 815 /// This builder is called on single-parameter unary operators that have scale 816 /// relationship between their input and output, expressed by the 817 /// UnaryOpQuantizationAttr. 818 static void buildUnaryOpWithQuantInfo(OpBuilder &builder, 819 OperationState &result, Type outputType, 820 Value input) { 821 result.addOperands(input); 822 auto quantAttr = buildUnaryOpQuantizationAttr(builder, input, outputType); 823 if (quantAttr) 824 result.addAttribute("quantization_info", quantAttr); 825 result.types.push_back(outputType); 826 } 827 828 /// This builder is called on TOSA pad operator that needs to create its own 829 /// OptionalAttr quantization_attr parameter to scale the padding values 830 /// correctly. No pad_const is interpreted as zero-padding. 831 static void buildPadOpWithQuantInfo(OpBuilder &builder, OperationState &result, 832 Type outputType, Value input, 833 Value paddings) { 834 result.addOperands({input, paddings}); 835 auto quantAttr = buildPadOpQuantizationAttr(builder, input); 836 if (quantAttr) 837 result.addAttribute("quantization_info", quantAttr); 838 result.types.push_back(outputType); 839 } 840 841 /// This builder is called on TOSA pad operator when an explicit pad_const 842 /// value is passed in. It also optionally constructs quantization_attr. 843 static void buildExplicitValuePadOpWithQuantInfo(OpBuilder &builder, 844 OperationState &result, 845 Type outputType, Value input, 846 Value paddings, 847 Value padConst) { 848 result.addOperands({input, paddings, padConst}); 849 auto quantAttr = buildPadOpQuantizationAttr(builder, input); 850 if (quantAttr) 851 result.addAttribute("quantization_info", quantAttr); 852 result.types.push_back(outputType); 853 } 854 855 //===----------------------------------------------------------------------===// 856 // TOSA Operator Return Type Inference. 857 //===----------------------------------------------------------------------===// 858 859 static void getI64Values(ArrayAttr arrayAttr, SmallVector<int64_t> &values) { 860 for (auto it : arrayAttr) { 861 values.push_back(it.cast<IntegerAttr>().getValue().getSExtValue()); 862 } 863 } 864 865 static void getF64Values(ArrayAttr arrayAttr, SmallVector<double> &values) { 866 for (auto it : arrayAttr) { 867 values.push_back(it.cast<FloatAttr>().getValueAsDouble()); 868 } 869 } 870 871 LogicalResult tosa::ArgMaxOp::inferReturnTypeComponents( 872 MLIRContext *context, ::llvm::Optional<Location> location, 873 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 874 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 875 ShapeAdaptor inputShape = operands.getShape(0); 876 IntegerAttr axis = attributes.get("axis").cast<IntegerAttr>(); 877 int32_t axisVal = axis.getValue().getSExtValue(); 878 879 if (!inputShape.hasRank()) { 880 inferredReturnShapes.push_back(ShapedTypeComponents()); 881 return success(); 882 } 883 884 SmallVector<int64_t> outShape; 885 outShape.reserve(inputShape.getRank() - 1); 886 for (int i = 0, s = inputShape.getRank(); i < s; i++) { 887 if (i == axisVal) 888 continue; 889 outShape.push_back(inputShape.getDimSize(i)); 890 } 891 892 inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); 893 return success(); 894 } 895 896 LogicalResult tosa::ConcatOp::inferReturnTypeComponents( 897 MLIRContext *context, ::llvm::Optional<Location> location, 898 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 899 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 900 // Infer all dimension sizes by reducing based on inputs. 901 int32_t axis = 902 attributes.get("axis").cast<IntegerAttr>().getValue().getSExtValue(); 903 llvm::SmallVector<int64_t> outputShape; 904 bool hasRankedInput = false; 905 for (auto operand : operands) { 906 ShapeAdaptor operandShape = operands.getShape(operand); 907 if (!operandShape.hasRank()) 908 continue; 909 910 // Copy the Operand's rank. 911 if (!hasRankedInput) 912 outputShape.resize(operandShape.getRank(), ShapedType::kDynamicSize); 913 914 // Copy shapes until the dim is non-dynamic. 915 for (int i = 0, s = operandShape.getRank(); i < s; i++) { 916 if (i == axis || operandShape.isDynamicDim(i)) 917 continue; 918 if (outputShape[i] == ShapedType::kDynamicSize) 919 outputShape[i] = operandShape.getDimSize(i); 920 if (outputShape[i] != operandShape.getDimSize(i)) 921 return failure(); 922 } 923 924 hasRankedInput = true; 925 } 926 927 if (!hasRankedInput) { 928 inferredReturnShapes.push_back(ShapedTypeComponents()); 929 return success(); 930 } 931 932 // Determine the dimension size along the concatenation axis. 933 int concatDimSize = 0; 934 for (auto operand : operands) { 935 ShapeAdaptor operandShape = operands.getShape(operand); 936 937 // We need to know the length of the concatenation axis of all inputs to 938 // determine the dimension size of the output shape. 939 if (!operandShape.hasRank() || operandShape.isDynamicDim(axis)) { 940 concatDimSize = ShapedType::kDynamicSize; 941 break; 942 } 943 944 concatDimSize += operandShape.getDimSize(axis); 945 } 946 947 outputShape[axis] = concatDimSize; 948 949 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 950 return success(); 951 } 952 953 LogicalResult tosa::FullyConnectedOp::inferReturnTypeComponents( 954 MLIRContext *context, ::llvm::Optional<Location> location, 955 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 956 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 957 ShapeAdaptor inputShape = operands.getShape(0); 958 ShapeAdaptor weightShape = operands.getShape(1); 959 ShapeAdaptor biasShape = operands.getShape(2); 960 961 // All shapes are dynamic. 962 SmallVector<int64_t> outShape; 963 outShape.resize(2, ShapedType::kDynamicSize); 964 965 if (inputShape.hasRank()) { 966 outShape[0] = inputShape.getDimSize(0); 967 } 968 969 if (weightShape.hasRank()) { 970 outShape[1] = weightShape.getDimSize(0); 971 } 972 973 if (biasShape.hasRank()) { 974 outShape[1] = outShape[1] == ShapedType::kDynamicSize 975 ? biasShape.getDimSize(0) 976 : outShape[1]; 977 } 978 979 inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); 980 return success(); 981 } 982 983 LogicalResult tosa::MatMulOp::inferReturnTypeComponents( 984 MLIRContext *context, ::llvm::Optional<Location> location, 985 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 986 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 987 ShapeAdaptor lhsShape = operands.getShape(0); 988 ShapeAdaptor rhsShape = operands.getShape(1); 989 990 // All shapes are dynamic. 991 SmallVector<int64_t> outShape; 992 outShape.resize(3, ShapedType::kDynamicSize); 993 994 if (lhsShape.hasRank()) { 995 outShape[0] = lhsShape.getDimSize(0); 996 outShape[1] = lhsShape.getDimSize(1); 997 } 998 999 if (rhsShape.hasRank()) { 1000 outShape[0] = outShape[0] == ShapedType::kDynamicSize 1001 ? rhsShape.getDimSize(0) 1002 : outShape[0]; 1003 outShape[2] = rhsShape.getDimSize(2); 1004 } 1005 1006 inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); 1007 return success(); 1008 } 1009 1010 LogicalResult tosa::PadOp::inferReturnTypeComponents( 1011 MLIRContext *context, ::llvm::Optional<Location> location, 1012 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1013 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1014 ShapeAdaptor inputShape = operands.getShape(0); 1015 ShapeAdaptor paddingShape = operands.getShape(1); 1016 SmallVector<int64_t> outputShape; 1017 1018 // If both inputs have unknown shape, we cannot determine the shape of the 1019 // output. 1020 if (!inputShape.hasRank() && !paddingShape.hasRank()) { 1021 inferredReturnShapes.push_back(ShapedTypeComponents()); 1022 return success(); 1023 } 1024 1025 // If the input rank is unknown we can info the output rank using the padding 1026 // shape's first dim. 1027 if (!inputShape.hasRank()) { 1028 if (paddingShape.isDynamicDim(0)) { 1029 inferredReturnShapes.push_back(ShapedTypeComponents()); 1030 return success(); 1031 } 1032 1033 outputShape.resize(paddingShape.getDimSize(0), ShapedType::kDynamicSize); 1034 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1035 return success(); 1036 } 1037 1038 DenseIntElementsAttr paddings; 1039 // If the paddings value is not a constant, all dimensions must be dynamic. 1040 if (!matchPattern(operands[1], m_Constant(&paddings))) { 1041 outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize); 1042 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1043 return success(); 1044 } 1045 1046 SmallVector<int64_t> paddingValues; 1047 for (auto val : paddings) { 1048 paddingValues.push_back(val.getSExtValue()); 1049 } 1050 1051 outputShape.reserve(inputShape.getRank()); 1052 for (int i = 0, s = inputShape.getRank(); i < s; i++) { 1053 if (inputShape.isDynamicDim(i)) { 1054 outputShape.push_back(ShapedType::kDynamicSize); 1055 continue; 1056 } 1057 1058 outputShape.push_back(inputShape.getDimSize(i) + paddingValues[i * 2] + 1059 paddingValues[i * 2 + 1]); 1060 } 1061 1062 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1063 return success(); 1064 } 1065 1066 LogicalResult tosa::SliceOp::inferReturnTypeComponents( 1067 MLIRContext *context, ::llvm::Optional<Location> location, 1068 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1069 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1070 ArrayAttr sizes = SliceOpAdaptor(operands, attributes).size(); 1071 SmallVector<int64_t> outputShape; 1072 outputShape.reserve(sizes.size()); 1073 for (auto val : sizes) { 1074 outputShape.push_back(val.cast<IntegerAttr>().getValue().getSExtValue()); 1075 } 1076 1077 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1078 return success(); 1079 } 1080 1081 LogicalResult tosa::TableOp::inferReturnTypeComponents( 1082 MLIRContext *context, ::llvm::Optional<Location> location, 1083 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1084 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1085 ShapeAdaptor inputShape = operands.getShape(0); 1086 1087 if (!inputShape.hasRank()) { 1088 inferredReturnShapes.push_back(ShapedTypeComponents()); 1089 return success(); 1090 } 1091 1092 inferredReturnShapes.resize(1); 1093 inputShape.getDims(inferredReturnShapes[0]); 1094 return success(); 1095 } 1096 1097 LogicalResult tosa::TileOp::inferReturnTypeComponents( 1098 MLIRContext *context, ::llvm::Optional<Location> location, 1099 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1100 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1101 TileOpAdaptor adaptor(operands, attributes); 1102 ArrayAttr multiples = adaptor.multiples(); 1103 ShapeAdaptor inputShape = operands.getShape(0); 1104 SmallVector<int64_t> outputShape; 1105 if (!inputShape.hasRank()) { 1106 outputShape.resize(multiples.size(), ShapedType::kDynamicSize); 1107 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1108 return success(); 1109 } 1110 1111 // We need the multiple values to determine the output shape. 1112 SmallVector<int64_t> multipleValues; 1113 multipleValues.reserve(multiples.size()); 1114 for (auto val : multiples) { 1115 multipleValues.push_back(val.cast<IntegerAttr>().getValue().getSExtValue()); 1116 } 1117 1118 // Any non dynamic dimension can be multiplied to a known size. 1119 outputShape.reserve(multiples.size()); 1120 for (int i = 0, s = inputShape.getRank(); i < s; i++) { 1121 int dim = inputShape.getDimSize(i); 1122 if (dim != ShapedType::kDynamicSize) 1123 dim *= multipleValues[i]; 1124 outputShape.push_back(dim); 1125 } 1126 1127 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1128 return success(); 1129 } 1130 1131 LogicalResult tosa::ReshapeOp::inferReturnTypeComponents( 1132 MLIRContext *context, ::llvm::Optional<Location> location, 1133 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1134 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1135 ReshapeOpAdaptor adaptor(operands, attributes); 1136 ShapeAdaptor inputShape = operands.getShape(0); 1137 1138 ArrayAttr newShape = adaptor.new_shape(); 1139 llvm::SmallVector<int64_t> newShapeValue; 1140 getI64Values(newShape, newShapeValue); 1141 1142 // We cannot infer from the total number of elements so we must take the 1143 // shape attribute as exact. 1144 if (!inputShape.hasRank() || !inputShape.hasStaticShape()) { 1145 inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue)); 1146 return success(); 1147 } 1148 1149 // Determine the number of elements covered by the slice of all static 1150 // dimensions. This allows us to infer the length of the remaining dynamic 1151 // dimension. 1152 int64_t numElements = inputShape.getNumElements(); 1153 int64_t staticMul = 1; 1154 for (auto val : newShapeValue) { 1155 if (val != ShapedType::kDynamicSize) { 1156 staticMul *= val; 1157 } 1158 } 1159 1160 // Determine the length of the dynamic dimension. 1161 for (auto &val : newShapeValue) { 1162 if (val == ShapedType::kDynamicSize) 1163 val = numElements / staticMul; 1164 } 1165 1166 inferredReturnShapes.push_back(ShapedTypeComponents(newShapeValue)); 1167 return success(); 1168 } 1169 1170 LogicalResult tosa::TransposeOp::inferReturnTypeComponents( 1171 MLIRContext *context, ::llvm::Optional<Location> location, 1172 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1173 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1174 ShapeAdaptor inputShape = operands.getShape(0); 1175 ShapeAdaptor permsShape = operands.getShape(1); 1176 1177 // If input rank and permutation length is unknown, the output rank is 1178 // unknown. 1179 if (!inputShape.hasRank() || !permsShape.hasRank() || 1180 permsShape.isDynamicDim(0)) { 1181 inferredReturnShapes.push_back(ShapedTypeComponents()); 1182 return success(); 1183 } 1184 1185 // This would imply the number of permutations does not match the rank of the 1186 // input which is illegal. 1187 if (permsShape.getDimSize(0) != inputShape.getRank()) { 1188 return failure(); 1189 } 1190 1191 // Without the input dims we cannot determine the output dim sizes but we 1192 // can determine the output rank. 1193 SmallVector<int64_t> outputShape; 1194 if (!inputShape.hasRank()) { 1195 outputShape.resize(permsShape.getDimSize(0), ShapedType::kDynamicSize); 1196 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1197 return success(); 1198 } 1199 1200 // Rank-0 means no permutations matter. 1201 if (inputShape.getRank() == 0) { 1202 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1203 return success(); 1204 } 1205 1206 // Check whether the input dimensions are all the same. 1207 bool allTheSame = true; 1208 for (int i = 1, s = inputShape.getRank(); i < s; i++) { 1209 if (inputShape.getDimSize(0) != inputShape.getDimSize(i)) { 1210 allTheSame = false; 1211 break; 1212 } 1213 } 1214 1215 // If all of the input dimensions are the same we don't care about the 1216 // permutation. 1217 if (allTheSame) { 1218 outputShape.resize(inputShape.getRank(), inputShape.getDimSize(0)); 1219 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1220 return success(); 1221 } 1222 1223 outputShape.resize(inputShape.getRank(), ShapedType::kDynamicSize); 1224 // If the permuations are a constant we can directly determine the output 1225 // shape. 1226 if (ShapeAdaptor permShape = operands.getValueAsShape(1)) { 1227 outputShape.reserve(inputShape.getRank()); 1228 for (int i = 0, s = inputShape.getRank(); i < s; i++) { 1229 outputShape[i] = inputShape.getDimSize(permShape.getDimSize(i)); 1230 } 1231 } 1232 1233 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1234 return success(); 1235 } 1236 1237 LogicalResult tosa::GatherOp::inferReturnTypeComponents( 1238 MLIRContext *context, ::llvm::Optional<Location> location, 1239 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1240 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1241 llvm::SmallVector<int64_t> outputShape; 1242 outputShape.resize(3, ShapedType::kDynamicSize); 1243 1244 ShapeAdaptor valuesShape = operands.getShape(0); 1245 if (valuesShape.hasRank()) { 1246 outputShape[0] = valuesShape.getDimSize(0); 1247 outputShape[2] = valuesShape.getDimSize(2); 1248 } 1249 1250 ShapeAdaptor indicesShape = operands.getShape(1); 1251 if (indicesShape.hasRank()) { 1252 if (outputShape[0] == ShapedType::kDynamicSize) 1253 outputShape[0] = indicesShape.getDimSize(0); 1254 if (outputShape[1] == ShapedType::kDynamicSize) 1255 outputShape[1] = indicesShape.getDimSize(1); 1256 } 1257 1258 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1259 return success(); 1260 } 1261 1262 LogicalResult tosa::ResizeOp::inferReturnTypeComponents( 1263 MLIRContext *context, ::llvm::Optional<Location> location, 1264 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1265 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1266 ResizeOpAdaptor adaptor(operands, attributes); 1267 llvm::SmallVector<int64_t, 4> outputShape; 1268 outputShape.resize(4, ShapedType::kDynamicSize); 1269 1270 int32_t inHeight = ShapedType::kDynamicSize; 1271 int32_t inWidth = ShapedType::kDynamicSize; 1272 1273 ShapeAdaptor inputShape = operands.getShape(adaptor.input()); 1274 if (inputShape.hasRank()) { 1275 outputShape[0] = inputShape.getDimSize(0); 1276 outputShape[3] = inputShape.getDimSize(3); 1277 1278 inHeight = inputShape.getDimSize(1); 1279 inWidth = inputShape.getDimSize(2); 1280 } 1281 1282 int32_t shift = adaptor.shift(); 1283 llvm::SmallVector<int64_t> newShape; 1284 getI64Values(adaptor.output_size(), newShape); 1285 outputShape[1] = newShape[0]; 1286 outputShape[2] = newShape[1]; 1287 1288 llvm::SmallVector<int64_t> strideInt; 1289 llvm::SmallVector<int64_t> offsetInt; 1290 llvm::SmallVector<double> strideFp; 1291 llvm::SmallVector<double> offsetFp; 1292 getI64Values(adaptor.offset(), offsetInt); 1293 getF64Values(adaptor.offset_fp(), offsetFp); 1294 getI64Values(adaptor.stride(), strideInt); 1295 getF64Values(adaptor.stride_fp(), strideFp); 1296 1297 // If we have a 0 zero in integers we know that the resize indexing needs to 1298 // be performed in floating point. Use the floating point varient to compute 1299 // the resize shape. 1300 bool fpMode = strideInt[0] == 0; 1301 1302 // We can compute the output shape if attribute specifies unknown dimensions 1303 // based on the offset and stride. If we perfectly line up to the last index 1304 // we need to round up the size to include it. 1305 if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && fpMode) { 1306 float sizeFp = (inHeight - offsetFp[0] - 1) / strideFp[0]; 1307 float round = std::floor(sizeFp) == sizeFp ? 1 : 0; 1308 outputShape[1] = std::ceil(sizeFp) + round; 1309 } 1310 1311 if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && fpMode) { 1312 float sizeFp = (inWidth - offsetFp[1] - 1) / strideFp[1]; 1313 float round = std::floor(sizeFp) == sizeFp ? 1 : 0; 1314 outputShape[2] = std::ceil(sizeFp) + round; 1315 } 1316 1317 if (outputShape[1] == ShapedType::kDynamicSize && inHeight >= 0 && !fpMode) { 1318 int64_t size = (inHeight - 1); 1319 size = ((size << shift) - offsetInt[0]) / strideInt[0]; 1320 outputShape[1] = size + 1; 1321 } 1322 1323 if (outputShape[2] == ShapedType::kDynamicSize && inWidth >= 0 && !fpMode) { 1324 int64_t size = (inWidth - 1); 1325 size = ((size << shift) - offsetInt[1]) / strideInt[1]; 1326 outputShape[2] = size + 1; 1327 } 1328 1329 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1330 return success(); 1331 } 1332 1333 LogicalResult tosa::ScatterOp::inferReturnTypeComponents( 1334 MLIRContext *context, ::llvm::Optional<Location> location, 1335 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1336 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1337 llvm::SmallVector<int64_t> outputShape; 1338 outputShape.resize(3, ShapedType::kDynamicSize); 1339 1340 ShapeAdaptor valuesInShape = operands.getShape(0); 1341 if (valuesInShape.hasRank()) { 1342 outputShape[0] = valuesInShape.getDimSize(0); 1343 outputShape[1] = valuesInShape.getDimSize(1); 1344 outputShape[2] = valuesInShape.getDimSize(2); 1345 } 1346 1347 ShapeAdaptor indicesShape = operands.getShape(1); 1348 if (indicesShape.hasRank()) { 1349 if (outputShape[0] == ShapedType::kDynamicSize) 1350 outputShape[0] = indicesShape.getDimSize(0); 1351 } 1352 1353 ShapeAdaptor inputShape = operands.getShape(2); 1354 if (inputShape.hasRank()) { 1355 if (outputShape[0] == ShapedType::kDynamicSize) 1356 outputShape[0] = inputShape.getDimSize(0); 1357 if (outputShape[2] == ShapedType::kDynamicSize) 1358 outputShape[2] = inputShape.getDimSize(2); 1359 } 1360 1361 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1362 return success(); 1363 } 1364 1365 static LogicalResult ReduceInferReturnTypes( 1366 ShapeAdaptor operandShape, IntegerAttr axis, 1367 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1368 if (!operandShape.hasRank()) { 1369 inferredReturnShapes.push_back(ShapedTypeComponents()); 1370 return success(); 1371 } 1372 1373 SmallVector<int64_t> outputShape; 1374 operandShape.getDims(outputShape); 1375 int64_t axisVal = axis.getValue().getSExtValue(); 1376 outputShape[axisVal] = 1; 1377 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1378 return success(); 1379 } 1380 1381 #define REDUCE_SHAPE_INFER(OP) \ 1382 LogicalResult OP::inferReturnTypeComponents( \ 1383 MLIRContext *context, ::llvm::Optional<Location> location, \ 1384 ValueShapeRange operands, DictionaryAttr attributes, \ 1385 RegionRange regions, \ 1386 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { \ 1387 return ReduceInferReturnTypes(operands.getShape(0), \ 1388 attributes.get("axis").cast<IntegerAttr>(), \ 1389 inferredReturnShapes); \ 1390 } 1391 1392 REDUCE_SHAPE_INFER(tosa::ReduceAllOp) 1393 REDUCE_SHAPE_INFER(tosa::ReduceAnyOp) 1394 REDUCE_SHAPE_INFER(tosa::ReduceMaxOp) 1395 REDUCE_SHAPE_INFER(tosa::ReduceMinOp) 1396 REDUCE_SHAPE_INFER(tosa::ReduceProdOp) 1397 REDUCE_SHAPE_INFER(tosa::ReduceSumOp) 1398 #undef REDUCE_SHAPE_INFER 1399 1400 static LogicalResult resolveBroadcastShape(const ValueShapeRange &operands, 1401 SmallVector<int64_t> &outShape) { 1402 int64_t outRank = 0; 1403 for (int i = 0, e = operands.size(); i != e; ++i) { 1404 auto shape = operands.getShape(i); 1405 if (!shape.hasRank()) { 1406 return failure(); 1407 } 1408 outRank = std::max<int64_t>(outRank, shape.getRank()); 1409 } 1410 1411 outShape.resize(outRank, 1); 1412 1413 for (int i = 0, e = operands.size(); i != e; ++i) { 1414 auto shape = operands.getShape(i); 1415 auto rankDiff = outShape.size() - shape.getRank(); 1416 1417 for (size_t i = 0, e = shape.getRank(); i < e; ++i) { 1418 auto dim1 = outShape[i + rankDiff]; 1419 auto dim2 = shape.getDimSize(i); 1420 auto resolvedDim = dim1; 1421 1422 if (dim1 == 1) { 1423 resolvedDim = dim2; 1424 } else if (dim2 == 1) { 1425 resolvedDim = dim1; 1426 } else if (dim1 != dim2) { 1427 return failure(); 1428 } 1429 outShape[i + rankDiff] = resolvedDim; 1430 } 1431 } 1432 1433 return success(); 1434 } 1435 1436 static LogicalResult NAryInferReturnTypes( 1437 const ValueShapeRange &operands, 1438 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1439 llvm::SmallVector<int64_t> outShape; 1440 if (resolveBroadcastShape(operands, outShape).failed()) { 1441 inferredReturnShapes.push_back(ShapedTypeComponents()); 1442 } else { 1443 inferredReturnShapes.push_back(ShapedTypeComponents(outShape)); 1444 } 1445 return success(); 1446 } 1447 1448 #define NARY_SHAPE_INFER(OP) \ 1449 LogicalResult OP::inferReturnTypeComponents( \ 1450 MLIRContext *context, ::llvm::Optional<Location> location, \ 1451 ValueShapeRange operands, DictionaryAttr attributes, \ 1452 RegionRange regions, \ 1453 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { \ 1454 return NAryInferReturnTypes(operands, inferredReturnShapes); \ 1455 } 1456 1457 NARY_SHAPE_INFER(tosa::AbsOp) 1458 NARY_SHAPE_INFER(tosa::AddOp) 1459 NARY_SHAPE_INFER(tosa::ArithmeticRightShiftOp) 1460 NARY_SHAPE_INFER(tosa::BitwiseAndOp) 1461 NARY_SHAPE_INFER(tosa::BitwiseOrOp) 1462 NARY_SHAPE_INFER(tosa::BitwiseXorOp) 1463 NARY_SHAPE_INFER(tosa::BitwiseNotOp) 1464 NARY_SHAPE_INFER(tosa::CastOp) 1465 NARY_SHAPE_INFER(tosa::CeilOp) 1466 NARY_SHAPE_INFER(tosa::ClampOp) 1467 NARY_SHAPE_INFER(tosa::ClzOp) 1468 NARY_SHAPE_INFER(tosa::DivOp) 1469 NARY_SHAPE_INFER(tosa::EqualOp) 1470 NARY_SHAPE_INFER(tosa::ExpOp) 1471 NARY_SHAPE_INFER(tosa::FloorOp) 1472 NARY_SHAPE_INFER(tosa::GreaterEqualOp) 1473 NARY_SHAPE_INFER(tosa::GreaterOp) 1474 NARY_SHAPE_INFER(tosa::IdentityOp) 1475 NARY_SHAPE_INFER(tosa::LogOp) 1476 NARY_SHAPE_INFER(tosa::LogicalAndOp) 1477 NARY_SHAPE_INFER(tosa::LogicalLeftShiftOp) 1478 NARY_SHAPE_INFER(tosa::LogicalNotOp) 1479 NARY_SHAPE_INFER(tosa::LogicalOrOp) 1480 NARY_SHAPE_INFER(tosa::LogicalRightShiftOp) 1481 NARY_SHAPE_INFER(tosa::LogicalXorOp) 1482 NARY_SHAPE_INFER(tosa::MaximumOp) 1483 NARY_SHAPE_INFER(tosa::MinimumOp) 1484 NARY_SHAPE_INFER(tosa::MulOp) 1485 NARY_SHAPE_INFER(tosa::NegateOp) 1486 NARY_SHAPE_INFER(tosa::PowOp) 1487 NARY_SHAPE_INFER(tosa::ReciprocalOp) 1488 NARY_SHAPE_INFER(tosa::ReluNOp) 1489 NARY_SHAPE_INFER(tosa::RescaleOp) 1490 NARY_SHAPE_INFER(tosa::ReverseOp) 1491 NARY_SHAPE_INFER(tosa::RsqrtOp) 1492 NARY_SHAPE_INFER(tosa::SelectOp) 1493 NARY_SHAPE_INFER(tosa::SubOp) 1494 NARY_SHAPE_INFER(tosa::TanhOp) 1495 NARY_SHAPE_INFER(tosa::SigmoidOp) 1496 #undef PRED_SHAPE_INFER 1497 1498 static LogicalResult poolingInferReturnTypes( 1499 const ValueShapeRange &operands, DictionaryAttr attributes, 1500 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1501 ShapeAdaptor inputShape = operands.getShape(0); 1502 llvm::SmallVector<int64_t> outputShape; 1503 outputShape.resize(4, -1); 1504 1505 // We only know the rank if the input type is unranked. 1506 if (!inputShape) { 1507 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1508 return success(); 1509 } 1510 1511 // Batch and number of channels are identical for pooling layer. 1512 outputShape[0] = inputShape.getDimSize(0); 1513 outputShape[3] = inputShape.getDimSize(3); 1514 1515 int32_t height = inputShape.getDimSize(1); 1516 int32_t width = inputShape.getDimSize(2); 1517 1518 llvm::SmallVector<int64_t> kernel; 1519 llvm::SmallVector<int64_t> stride; 1520 llvm::SmallVector<int64_t> pad; 1521 1522 getI64Values(attributes.get("kernel").cast<ArrayAttr>(), kernel); 1523 getI64Values(attributes.get("stride").cast<ArrayAttr>(), stride); 1524 getI64Values(attributes.get("pad").cast<ArrayAttr>(), pad); 1525 1526 if (height != -1) { 1527 int32_t padded = height + pad[0] + pad[1] - kernel[0]; 1528 outputShape[1] = padded / stride[0] + 1; 1529 } 1530 1531 if (width != -1) { 1532 int32_t padded = width + pad[2] + pad[3] - kernel[1]; 1533 outputShape[2] = padded / stride[1] + 1; 1534 } 1535 1536 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1537 return success(); 1538 } 1539 1540 LogicalResult Conv2DOp::inferReturnTypeComponents( 1541 MLIRContext *context, ::llvm::Optional<Location> location, 1542 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1543 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1544 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamicSize); 1545 Conv2DOp::Adaptor adaptor(operands.getValues(), attributes); 1546 1547 int32_t inputWidth = ShapedType::kDynamicSize; 1548 int32_t inputHeight = ShapedType::kDynamicSize; 1549 int32_t weightWidth = ShapedType::kDynamicSize; 1550 int32_t weightHeight = ShapedType::kDynamicSize; 1551 1552 // Input shape describes input width/height and batch. 1553 1554 ShapeAdaptor inputShape = operands.getShape(adaptor.input()); 1555 if (inputShape.hasRank()) { 1556 outputShape[0] = inputShape.getDimSize(0); 1557 inputHeight = inputShape.getDimSize(1); 1558 inputWidth = inputShape.getDimSize(2); 1559 } 1560 1561 // Weight shapes describes the filter width/height and the output channels. 1562 ShapeAdaptor weightShape = operands.getShape(adaptor.weight()); 1563 if (weightShape.hasRank()) { 1564 outputShape[3] = weightShape.getDimSize(0); 1565 weightHeight = weightShape.getDimSize(1); 1566 weightWidth = weightShape.getDimSize(2); 1567 } 1568 1569 // Bias shape can describe the output channels. 1570 ShapeAdaptor biasShape = operands.getShape(adaptor.bias()); 1571 if (biasShape.hasRank()) { 1572 outputShape[3] = ShapedType::isDynamic(outputShape[3]) 1573 ? biasShape.getDimSize(0) 1574 : outputShape[3]; 1575 } 1576 1577 llvm::SmallVector<int64_t> dilation; 1578 llvm::SmallVector<int64_t> padding; 1579 llvm::SmallVector<int64_t> stride; 1580 1581 getI64Values(adaptor.dilation(), dilation); 1582 getI64Values(adaptor.pad(), padding); 1583 getI64Values(adaptor.stride(), stride); 1584 1585 if (!ShapedType::isDynamic(inputHeight) && 1586 !ShapedType::isDynamic(weightHeight)) { 1587 int32_t inputSize = inputHeight + padding[0] + padding[1]; 1588 int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; 1589 int32_t unstridedResult = inputSize - filterSize + 1; 1590 outputShape[1] = (unstridedResult - 1) / stride[0] + 1; 1591 } 1592 1593 if (!ShapedType::isDynamic(inputWidth) && 1594 !ShapedType::isDynamic(weightWidth)) { 1595 int32_t inputSize = inputWidth + padding[2] + padding[3]; 1596 int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; 1597 int32_t unstridedResult = inputSize - filterSize + 1; 1598 outputShape[2] = (unstridedResult - 1) / stride[1] + 1; 1599 } 1600 1601 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1602 return success(); 1603 } 1604 1605 LogicalResult Conv3DOp::inferReturnTypeComponents( 1606 MLIRContext *context, ::llvm::Optional<Location> location, 1607 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1608 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1609 llvm::SmallVector<int64_t> outputShape(5, ShapedType::kDynamicSize); 1610 Conv2DOp::Adaptor adaptor(operands.getValues(), attributes); 1611 1612 int32_t inputWidth = ShapedType::kDynamicSize; 1613 int32_t inputHeight = ShapedType::kDynamicSize; 1614 int32_t inputDepth = ShapedType::kDynamicSize; 1615 1616 int32_t weightWidth = ShapedType::kDynamicSize; 1617 int32_t weightHeight = ShapedType::kDynamicSize; 1618 int32_t weightDepth = ShapedType::kDynamicSize; 1619 1620 // Input shape describes input width/height and batch. 1621 ShapeAdaptor inputShape = operands.getShape(adaptor.input()); 1622 if (inputShape.hasRank()) { 1623 outputShape[0] = inputShape.getDimSize(0); 1624 inputHeight = inputShape.getDimSize(1); 1625 inputWidth = inputShape.getDimSize(2); 1626 inputDepth = inputShape.getDimSize(3); 1627 } 1628 1629 // Weight shapes describes the filter width/height and the output channels. 1630 ShapeAdaptor weightShape = operands.getShape(adaptor.weight()); 1631 if (weightShape.hasRank()) { 1632 outputShape[4] = weightShape.getDimSize(0); 1633 weightHeight = weightShape.getDimSize(1); 1634 weightWidth = weightShape.getDimSize(2); 1635 weightDepth = weightShape.getDimSize(3); 1636 } 1637 1638 // Bias shape can describe the output channels. 1639 ShapeAdaptor biasShape = operands.getShape(adaptor.bias()); 1640 if (biasShape.hasRank()) { 1641 outputShape[4] = 1642 (outputShape[4] == -1) ? biasShape.getDimSize(0) : outputShape[4]; 1643 } 1644 1645 llvm::SmallVector<int64_t> dilation; 1646 llvm::SmallVector<int64_t> padding; 1647 llvm::SmallVector<int64_t> stride; 1648 1649 getI64Values(adaptor.dilation(), dilation); 1650 getI64Values(adaptor.pad(), padding); 1651 getI64Values(adaptor.stride(), stride); 1652 1653 if (!ShapedType::isDynamic(inputHeight) && 1654 !ShapedType::isDynamic(weightHeight)) { 1655 int32_t inputSize = inputHeight + padding[0] + padding[1]; 1656 int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; 1657 int32_t unstridedResult = inputSize - filterSize + 1; 1658 outputShape[1] = (unstridedResult - 1) / stride[0] + 1; 1659 } 1660 1661 if (!ShapedType::isDynamic(inputWidth) && 1662 !ShapedType::isDynamic(weightWidth)) { 1663 int32_t inputSize = inputWidth + padding[2] + padding[3]; 1664 int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; 1665 int32_t unstridedResult = inputSize - filterSize + 1; 1666 outputShape[2] = (unstridedResult - 1) / stride[1] + 1; 1667 } 1668 1669 if (!ShapedType::isDynamic(inputDepth) && 1670 !ShapedType::isDynamic(weightDepth)) { 1671 int32_t inputSize = inputDepth + padding[4] + padding[5]; 1672 int32_t filterSize = (weightDepth - 1) * dilation[2] + 1; 1673 int32_t unstridedResult = inputSize - filterSize + 1; 1674 outputShape[3] = (unstridedResult - 1) / stride[2] + 1; 1675 } 1676 1677 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1678 return success(); 1679 } 1680 1681 LogicalResult AvgPool2dOp::inferReturnTypeComponents( 1682 MLIRContext *context, ::llvm::Optional<Location> location, 1683 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1684 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1685 return poolingInferReturnTypes(operands, attributes, inferredReturnShapes); 1686 } 1687 1688 LogicalResult MaxPool2dOp::inferReturnTypeComponents( 1689 MLIRContext *context, ::llvm::Optional<Location> location, 1690 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1691 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1692 return poolingInferReturnTypes(operands, attributes, inferredReturnShapes); 1693 } 1694 1695 LogicalResult DepthwiseConv2DOp::inferReturnTypeComponents( 1696 MLIRContext *context, ::llvm::Optional<Location> location, 1697 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1698 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1699 llvm::SmallVector<int64_t> outputShape(4, ShapedType::kDynamicSize); 1700 DepthwiseConv2DOp::Adaptor adaptor(operands.getValues(), attributes); 1701 1702 int32_t inputWidth = ShapedType::kDynamicSize; 1703 int32_t inputHeight = ShapedType::kDynamicSize; 1704 int32_t inputChannels = ShapedType::kDynamicSize; 1705 1706 int32_t weightWidth = ShapedType::kDynamicSize; 1707 int32_t weightHeight = ShapedType::kDynamicSize; 1708 int32_t depthChannels = ShapedType::kDynamicSize; 1709 1710 // Input shape describes input width/height and batch. 1711 ShapeAdaptor inputShape = operands.getShape(adaptor.input()); 1712 if (inputShape.hasRank()) { 1713 outputShape[0] = inputShape.getDimSize(0); 1714 inputHeight = inputShape.getDimSize(1); 1715 inputWidth = inputShape.getDimSize(2); 1716 inputChannels = inputShape.getDimSize(3); 1717 } 1718 1719 // Weight shapes describes the filter width/height and the output channels. 1720 ShapeAdaptor weightShape = operands.getShape(adaptor.weight()); 1721 if (weightShape.hasRank()) { 1722 weightHeight = weightShape.getDimSize(0); 1723 weightWidth = weightShape.getDimSize(1); 1724 inputChannels = ShapedType::isDynamic(inputChannels) 1725 ? weightShape.getDimSize(2) 1726 : inputChannels; 1727 depthChannels = weightShape.getDimSize(3); 1728 } 1729 1730 // If both inputChannels and depthChannels are available we can determine 1731 // the output channels. 1732 if (!ShapedType::isDynamic(inputChannels) && 1733 !ShapedType::isDynamic(depthChannels)) { 1734 outputShape[3] = inputChannels * depthChannels; 1735 } 1736 1737 // Bias shape can describe the output channels. 1738 ShapeAdaptor biasShape = operands.getShape(adaptor.bias()); 1739 if (biasShape.hasRank()) { 1740 outputShape[3] = ShapedType::isDynamic(outputShape[3]) 1741 ? biasShape.getDimSize(0) 1742 : outputShape[3]; 1743 } 1744 1745 llvm::SmallVector<int64_t> dilation; 1746 llvm::SmallVector<int64_t> padding; 1747 llvm::SmallVector<int64_t> stride; 1748 1749 getI64Values(adaptor.dilation(), dilation); 1750 getI64Values(adaptor.pad(), padding); 1751 getI64Values(adaptor.stride(), stride); 1752 1753 if (!ShapedType::isDynamic(inputHeight) && 1754 !ShapedType::isDynamic(weightHeight)) { 1755 int32_t inputSize = inputHeight + padding[0] + padding[1]; 1756 int32_t filterSize = (weightHeight - 1) * dilation[0] + 1; 1757 int32_t unstridedResult = inputSize - filterSize + 1; 1758 outputShape[1] = (unstridedResult - 1) / stride[0] + 1; 1759 } 1760 1761 if (!ShapedType::isDynamic(inputWidth) && 1762 !ShapedType::isDynamic(weightWidth)) { 1763 int32_t inputSize = inputWidth + padding[2] + padding[3]; 1764 int32_t filterSize = (weightWidth - 1) * dilation[1] + 1; 1765 int32_t unstridedResult = inputSize - filterSize + 1; 1766 outputShape[2] = (unstridedResult - 1) / stride[1] + 1; 1767 } 1768 1769 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1770 return success(); 1771 } 1772 1773 LogicalResult TransposeConv2DOp::inferReturnTypeComponents( 1774 MLIRContext *context, ::llvm::Optional<Location> location, 1775 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1776 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1777 TransposeConv2DOp::Adaptor adaptor(operands.getValues(), attributes); 1778 llvm::SmallVector<int64_t> outputShape; 1779 getI64Values(adaptor.out_shape(), outputShape); 1780 1781 int32_t inputWidth = ShapedType::kDynamicSize; 1782 int32_t inputHeight = ShapedType::kDynamicSize; 1783 int32_t weightWidth = ShapedType::kDynamicSize; 1784 int32_t weightHeight = ShapedType::kDynamicSize; 1785 1786 // Input shape describes input width/height and batch. 1787 ShapeAdaptor inputShape = operands.getShape(adaptor.input()); 1788 if (inputShape.hasRank()) { 1789 outputShape[0] = ShapedType::isDynamic(outputShape[0]) 1790 ? inputShape.getDimSize(0) 1791 : outputShape[0]; 1792 inputHeight = inputShape.getDimSize(1); 1793 inputWidth = inputShape.getDimSize(2); 1794 } 1795 1796 // Weight shapes describes the filter width/height and the output channels. 1797 ShapeAdaptor weightShape = operands.getShape(adaptor.filter()); 1798 if (weightShape.hasRank()) { 1799 outputShape[3] = ShapedType::isDynamic(outputShape[3]) 1800 ? weightShape.getDimSize(0) 1801 : outputShape[3]; 1802 weightHeight = weightShape.getDimSize(1); 1803 weightWidth = weightShape.getDimSize(2); 1804 } 1805 1806 // Bias shape can describe the output channels. 1807 ShapeAdaptor biasShape = operands.getShape(adaptor.input()); 1808 if (biasShape.hasRank()) { 1809 outputShape[3] = ShapedType::isDynamic(outputShape[3]) 1810 ? biasShape.getDimSize(0) 1811 : outputShape[3]; 1812 } 1813 1814 llvm::SmallVector<int64_t> dilation; 1815 llvm::SmallVector<int64_t> padding; 1816 llvm::SmallVector<int64_t> stride; 1817 1818 getI64Values(adaptor.dilation(), dilation); 1819 getI64Values(adaptor.out_pad(), padding); 1820 getI64Values(adaptor.stride(), stride); 1821 1822 if (!ShapedType::isDynamic(inputHeight) && 1823 !ShapedType::isDynamic(weightHeight)) { 1824 int32_t dilated = (weightHeight - 1) * dilation[0] + 1; 1825 int32_t calculateSize = 1826 (inputHeight - 1) * stride[0] - padding[0] + dilated; 1827 outputShape[1] = outputShape[1] == -1 ? calculateSize : outputShape[1]; 1828 } 1829 1830 if (!ShapedType::isDynamic(inputWidth) && 1831 !ShapedType::isDynamic(weightWidth)) { 1832 int32_t dilated = (weightWidth - 1) * dilation[1] + 1; 1833 int32_t calculateSize = (inputWidth - 1) * stride[1] - padding[1] + dilated; 1834 outputShape[2] = outputShape[2] == -1 ? calculateSize : outputShape[2]; 1835 } 1836 1837 inferredReturnShapes.push_back(ShapedTypeComponents(outputShape)); 1838 return success(); 1839 } 1840 1841 LogicalResult IfOp::inferReturnTypeComponents( 1842 MLIRContext *context, ::llvm::Optional<Location> location, 1843 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1844 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1845 llvm::SmallVector<tosa::YieldOp> yieldOps; 1846 for (Region *region : regions) { 1847 for (auto &block : *region) 1848 if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator())) 1849 yieldOps.push_back(returnOp); 1850 } 1851 1852 if (yieldOps.empty()) 1853 return failure(); 1854 1855 // Get the initial type information for the yield op. 1856 llvm::SmallVector<ValueKnowledge> resultKnowledge; 1857 resultKnowledge.reserve(yieldOps.front().getNumOperands()); 1858 for (auto operand : yieldOps.front().getOperands()) { 1859 resultKnowledge.push_back( 1860 ValueKnowledge::getKnowledgeFromType(operand.getType())); 1861 } 1862 1863 for (auto yieldOp : yieldOps) { 1864 if (resultKnowledge.size() != yieldOp.getNumOperands()) 1865 return failure(); 1866 1867 for (const auto &it : llvm::enumerate(yieldOp.getOperands())) { 1868 int32_t index = it.index(); 1869 auto meet = ValueKnowledge::meet( 1870 resultKnowledge[index], 1871 ValueKnowledge::getKnowledgeFromType(it.value().getType())); 1872 if (!meet) 1873 continue; 1874 resultKnowledge[index] = meet; 1875 } 1876 } 1877 1878 for (const ValueKnowledge &result : resultKnowledge) { 1879 inferredReturnShapes.push_back(result.getShapedTypeComponents()); 1880 } 1881 1882 return success(); 1883 } 1884 1885 LogicalResult WhileOp::inferReturnTypeComponents( 1886 MLIRContext *context, ::llvm::Optional<Location> location, 1887 ValueShapeRange operands, DictionaryAttr attributes, RegionRange regions, 1888 SmallVectorImpl<ShapedTypeComponents> &inferredReturnShapes) { 1889 llvm::SmallVector<tosa::YieldOp> yieldOps; 1890 for (auto &block : *regions[1]) 1891 if (auto returnOp = dyn_cast<tosa::YieldOp>(block.getTerminator())) 1892 yieldOps.push_back(returnOp); 1893 1894 // TOSA's while must have a tosa.yield as its terminator. If not found this 1895 // tosa.while is invalid. 1896 if (yieldOps.empty()) 1897 return failure(); 1898 1899 // Get the initial type information from the operand types. 1900 llvm::SmallVector<ValueKnowledge> resultKnowledge; 1901 resultKnowledge.reserve(yieldOps.front().getNumOperands()); 1902 for (auto operand : yieldOps.front().getOperands()) { 1903 resultKnowledge.push_back( 1904 ValueKnowledge::getKnowledgeFromType(operand.getType())); 1905 } 1906 1907 for (auto yieldOp : yieldOps) { 1908 if (resultKnowledge.size() != yieldOp.getNumOperands()) 1909 return failure(); 1910 1911 for (const auto &it : llvm::enumerate(yieldOp.getOperands())) { 1912 int32_t index = it.index(); 1913 if (auto meet = ValueKnowledge::meet( 1914 resultKnowledge[index], 1915 ValueKnowledge::getKnowledgeFromType(it.value().getType()))) { 1916 resultKnowledge[index] = meet; 1917 }; 1918 } 1919 } 1920 1921 for (const ValueKnowledge &result : resultKnowledge) { 1922 inferredReturnShapes.push_back(result.getShapedTypeComponents()); 1923 } 1924 1925 return success(); 1926 } 1927 1928 //===----------------------------------------------------------------------===// 1929 // TOSA Operator Definitions. 1930 //===----------------------------------------------------------------------===// 1931 1932 #define GET_OP_CLASSES 1933 #include "mlir/Dialect/Tosa/IR/TosaOps.cpp.inc" 1934