1 //===- LinalgOps.cpp - Implementation of the linalg operations ------------===// 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 // This file implements the Linalg operations. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 14 15 #include "mlir/Dialect/Affine/IR/AffineOps.h" 16 #include "mlir/Dialect/Linalg/EDSC/Intrinsics.h" 17 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 18 #include "mlir/Dialect/StandardOps/IR/Ops.h" 19 #include "mlir/IR/AffineExprVisitor.h" 20 #include "mlir/IR/Matchers.h" 21 #include "mlir/IR/OpImplementation.h" 22 #include "mlir/IR/PatternMatch.h" 23 24 #include "llvm/ADT/DenseMap.h" 25 #include "llvm/ADT/SetVector.h" 26 #include "llvm/ADT/SmallSet.h" 27 #include "llvm/ADT/StringSet.h" 28 #include "llvm/Support/FormatVariadic.h" 29 #include "llvm/Support/MathExtras.h" 30 #include "llvm/Support/raw_ostream.h" 31 32 using namespace mlir; 33 using namespace mlir::linalg; 34 35 /// Forward declarations. 36 37 /// Generic entry point to create the block for the region of a LinalgOp. 38 /// This is used by both named structured ops created by ods-gen and by manually 39 /// defined C++ ops. 40 /// This is used by both builders and parsers. 41 /// This function creates the block in the region with arguments corresponding 42 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 43 /// to be ShapedType. 44 template <typename NamedStructuredOpType> 45 static void fillStructuredOpRegion( 46 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 47 TypeRange outputTypes, ValueRange captures = {}, 48 std::function<void(unsigned, unsigned)> errorHandler = nullptr); 49 50 /// Generic entry point to create both the region and the block of a LinalgOp. 51 template <typename NamedStructuredOpType> 52 static void 53 createAndFillStructuredOpRegion(OpBuilder &opBuilder, OperationState &result, 54 TypeRange inputTypes, TypeRange outputTypes, 55 ValueRange captures = {}); 56 57 /// Common parsing and printing used for both named structured ops created by 58 /// ods-gen and by manually defined C++ ops. Does not handle regions. 59 static ParseResult 60 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 61 SmallVectorImpl<Type> &inputTypes, 62 SmallVectorImpl<Type> &outputTypes); 63 template <typename NamedStructuredOpType> 64 static void printCommonStructuredOpParts(OpAsmPrinter &p, 65 NamedStructuredOpType op); 66 67 /// Specific parsing and printing for named structured ops created by ods-gen. 68 template <typename NamedStructuredOpType> 69 static ParseResult 70 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 71 TypeRange inputTypes, TypeRange outputTypes, 72 ArrayRef<OpAsmParser::OperandType> captures = {}); 73 74 static ParseResult 75 parseNamedStructuredOpResults(OpAsmParser &parser, 76 SmallVectorImpl<Type> &resultTypes); 77 78 template <typename NamedStructuredOpType> 79 static ParseResult 80 parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, 81 ArrayRef<OpAsmParser::OperandType> captures = {}); 82 83 static void printNamedStructuredOpResults(OpAsmPrinter &p, 84 TypeRange resultTypes); 85 86 template <typename NamedStructuredOpType> 87 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 88 89 /// Helper function to dispatch an OpFoldResult into either the `dynamicVec` if 90 /// it is a Value or into `staticVec` if it is an IntegerAttr. 91 /// In the case of a Value, a copy of the `sentinel` value is also pushed to 92 /// `staticVec`. This is useful to extract mixed static and dynamic entries that 93 /// come from an AttrSizedOperandSegments trait. 94 static void dispatchIndexOpFoldResult(OpFoldResult ofr, 95 SmallVectorImpl<Value> &dynamicVec, 96 SmallVectorImpl<int64_t> &staticVec, 97 int64_t sentinel) { 98 if (auto v = ofr.dyn_cast<Value>()) { 99 dynamicVec.push_back(v); 100 staticVec.push_back(sentinel); 101 return; 102 } 103 APInt apInt = ofr.dyn_cast<Attribute>().cast<IntegerAttr>().getValue(); 104 staticVec.push_back(apInt.getSExtValue()); 105 } 106 107 /// This is a common class used for patterns of the form 108 /// ``` 109 /// someop(memrefcast) -> someop 110 /// ``` 111 /// It folds the source of the memref_cast into the root operation directly. 112 static LogicalResult foldMemRefCast(Operation *op) { 113 bool folded = false; 114 for (OpOperand &operand : op->getOpOperands()) { 115 auto castOp = operand.get().getDefiningOp<MemRefCastOp>(); 116 if (castOp && canFoldIntoConsumerOp(castOp)) { 117 operand.set(castOp.getOperand()); 118 folded = true; 119 } 120 } 121 return success(folded); 122 } 123 124 //===----------------------------------------------------------------------===// 125 // CopyOp 126 //===----------------------------------------------------------------------===// 127 void CopyOp::regionBuilder(Block &block, ValueRange captures) { 128 using namespace edsc::intrinsics; 129 assert(block.getNumArguments() == 2 && "CopyOp regionBuilder expects 2 args"); 130 (linalg_yield(block.getArgument(0))); 131 } 132 133 void CopyOp::build(OpBuilder &builder, OperationState &result, Value input, 134 Value output, AffineMap inputPermutation, 135 AffineMap outputPermutation, 136 ArrayRef<NamedAttribute> namedAttrs) { 137 result.addOperands({input, output}); 138 result.addAttributes(namedAttrs); 139 if (inputPermutation) 140 result.addAttribute("inputPermutation", 141 AffineMapAttr::get(inputPermutation)); 142 if (outputPermutation) 143 result.addAttribute("outputPermutation", 144 AffineMapAttr::get(outputPermutation)); 145 result.addRegion(); 146 fillStructuredOpRegion<CopyOp>(builder, *result.regions.front(), 147 TypeRange{input.getType()}, 148 TypeRange{output.getType()}); 149 } 150 151 ParseResult parseCopyOpRegion(OpAsmParser &parser, Region &r, Type inputType, 152 Type outputType) { 153 OpBuilder opBuilder(parser.getBuilder().getContext()); 154 fillStructuredOpRegion<CopyOp>(opBuilder, r, TypeRange{inputType}, 155 TypeRange{outputType}); 156 return success(); 157 } 158 159 /// CopyOp region is elided when printing. 160 void printCopyOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Type) {} 161 162 static LogicalResult verify(CopyOp op) { 163 auto outputViewType = op.getOutputShapedType(0); 164 auto inputViewType = op.getInputShapedType(0); 165 if (inputViewType.getElementType() != outputViewType.getElementType()) 166 return op.emitOpError("expects views of the same type"); 167 if (inputViewType.getRank() != outputViewType.getRank()) 168 return op.emitOpError("expects views of the same rank"); 169 auto rank = op.getNumParallelLoops(); 170 auto inputPermutationMap = op.inputPermutation(); 171 if (inputPermutationMap) { 172 if (inputPermutationMap->getNumInputs() != rank) 173 return op.emitOpError("expects optional input_permutation map of rank ") 174 << rank; 175 if (!inputPermutationMap->isPermutation()) 176 return op.emitOpError( 177 "expects optional input_permutation map to be a permutation"); 178 } 179 auto outputPermutationMap = op.outputPermutation(); 180 if (outputPermutationMap) { 181 if (outputPermutationMap->getNumInputs() != rank) 182 return op.emitOpError("expects optional output_permutation map of rank ") 183 << rank; 184 if (!outputPermutationMap->isPermutation()) 185 return op.emitOpError( 186 "expects optional output_permutation map to be a permutation"); 187 } 188 if (rank == 0 && inputPermutationMap) 189 return op.emitOpError("expected no input permutation when rank == 0"); 190 if (rank == 0 && outputPermutationMap) 191 return op.emitOpError("expected no output permutation when rank == 0"); 192 return success(); 193 } 194 195 void CopyOp::getEffects( 196 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 197 &effects) { 198 effects.emplace_back(MemoryEffects::Read::get(), input(), 199 SideEffects::DefaultResource::get()); 200 effects.emplace_back(MemoryEffects::Write::get(), output(), 201 SideEffects::DefaultResource::get()); 202 } 203 204 //===----------------------------------------------------------------------===// 205 // FillOp 206 //===----------------------------------------------------------------------===// 207 void FillOp::regionBuilder(Block &block, ValueRange captures) { 208 using namespace edsc::intrinsics; 209 assert(captures.size() == 1 && "FillOp regionBuilder expects 1 capture"); 210 (linalg_yield(captures)); 211 } 212 213 void FillOp::build(OpBuilder &builder, OperationState &result, Value output, 214 Value value) { 215 build(builder, result, output.getType().dyn_cast<RankedTensorType>(), output, 216 value); 217 fillStructuredOpRegion<FillOp>(builder, *result.regions.front(), TypeRange{}, 218 TypeRange{output.getType()}, value); 219 } 220 221 ParseResult parseFillOpRegion(OpAsmParser &parser, Region &r, Type outputType, 222 OpAsmParser::OperandType valueRef) { 223 OpBuilder opBuilder(parser.getBuilder().getContext()); 224 // Resolve `valueRef` into `value` at parse time so we can build the region 225 // with captures. 226 SmallVector<Value> value; 227 parser.resolveOperand(valueRef, getElementTypeOrSelf(outputType), value); 228 fillStructuredOpRegion<FillOp>(opBuilder, r, TypeRange{}, 229 TypeRange{outputType}, value); 230 return success(); 231 } 232 233 /// FillOp region is elided when printing. 234 void printFillOpRegion(OpAsmPrinter &, Operation *, Region &, Type, Value) {} 235 236 static LogicalResult verify(FillOp op) { 237 auto viewType = op.getOutputShapedType(0); 238 auto fillType = op.value().getType(); 239 if (viewType.getElementType() != fillType) 240 return op.emitOpError("expects fill type to match view elemental type"); 241 if (!op.getNumResults() && !viewType.isa<MemRefType>()) { 242 return op.emitOpError( 243 "expected fill op with no result value to use memref type"); 244 } 245 return success(); 246 } 247 248 void FillOp::getEffects( 249 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 250 &effects) { 251 if (output().getType().isa<MemRefType>()) 252 effects.emplace_back(MemoryEffects::Write::get(), output(), 253 SideEffects::DefaultResource::get()); 254 } 255 256 //===----------------------------------------------------------------------===// 257 // GenericOps 258 //===----------------------------------------------------------------------===// 259 void GenericOp::build( 260 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 261 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 262 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 263 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 264 build(builder, result, resultTensorTypes, inputs, outputs, 265 builder.getAffineMapArrayAttr(indexingMaps), 266 builder.getStrArrayAttr(iteratorTypes), 267 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 268 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 269 ArrayAttr()); 270 if (!bodyBuild) 271 return; 272 273 SmallVector<Type, 4> blockArgTypes; 274 for (ValueRange container : {inputs, outputs}) 275 for (Value v : container) 276 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 277 278 OpBuilder::InsertionGuard guard(builder); 279 auto ®ion = *result.regions.front(); 280 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 281 bodyBuild(builder, result.location, bodyBlock->getArguments()); 282 } 283 284 void GenericOp::build( 285 OpBuilder &builder, OperationState &result, ValueRange inputs, 286 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 287 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 288 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 289 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 290 iteratorTypes, doc, libraryCall, bodyBuild); 291 } 292 293 void GenericOp::build( 294 OpBuilder &builder, OperationState &result, ValueRange inputs, 295 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 296 ArrayRef<StringRef> iteratorTypes, 297 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 298 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 299 /*doc=*/"", 300 /*libraryCall=*/"", bodyBuild); 301 } 302 303 void GenericOp::build( 304 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 305 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 306 ArrayRef<StringRef> iteratorTypes, 307 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 308 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 309 iteratorTypes, 310 /*doc=*/"", 311 /*libraryCall=*/"", bodyBuild); 312 } 313 void IndexedGenericOp::build( 314 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 315 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 316 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 317 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 318 bodyBuild) { 319 build(builder, result, resultTensorTypes, inputs, outputs, 320 builder.getAffineMapArrayAttr(indexingMaps), 321 builder.getStrArrayAttr(iteratorTypes), 322 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 323 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 324 ArrayAttr()); 325 if (!bodyBuild) 326 return; 327 328 unsigned nLoops = iteratorTypes.size(); 329 SmallVector<Type, 4> blockArgTypes(nLoops, builder.getIndexType()); 330 for (ValueRange container : {inputs, outputs}) 331 for (Value v : container) 332 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 333 334 OpBuilder::InsertionGuard guard(builder); 335 auto ®ion = *result.regions.front(); 336 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 337 bodyBuild(builder, result.location, 338 bodyBlock->getArguments().take_front(nLoops), 339 bodyBlock->getArguments().drop_front(nLoops)); 340 } 341 342 void IndexedGenericOp::build( 343 OpBuilder &builder, OperationState &result, ValueRange inputs, 344 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 345 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 346 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 347 bodyBuild) { 348 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps, 349 iteratorTypes, doc, libraryCall, bodyBuild); 350 } 351 352 void IndexedGenericOp::build( 353 OpBuilder &builder, OperationState &result, ValueRange inputs, 354 ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 355 ArrayRef<StringRef> iteratorTypes, 356 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 357 bodyBuild) { 358 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes, 359 /*doc=*/"", /*libraryCall=*/"", bodyBuild); 360 } 361 362 void IndexedGenericOp::build( 363 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 364 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps, 365 ArrayRef<StringRef> iteratorTypes, 366 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 367 bodyBuild) { 368 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps, 369 iteratorTypes, 370 /*doc=*/"", 371 /*libraryCall=*/"", bodyBuild); 372 } 373 374 template <typename GenericOpType> 375 static void printGenericOp(OpAsmPrinter &p, GenericOpType op) { 376 p << op.getOperationName() << " "; 377 378 // Print extra attributes. 379 auto genericAttrNames = op.linalgTraitAttrNames(); 380 381 llvm::StringSet<> genericAttrNamesSet; 382 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 383 SmallVector<NamedAttribute, 8> genericAttrs; 384 for (auto attr : op.getAttrs()) 385 if (genericAttrNamesSet.count(attr.first.strref()) > 0) 386 genericAttrs.push_back(attr); 387 if (!genericAttrs.empty()) { 388 auto genericDictAttr = DictionaryAttr::get(op.getContext(), genericAttrs); 389 p << genericDictAttr; 390 } 391 392 // Printing is shared with named ops, except for the region and attributes 393 printCommonStructuredOpParts(p, op); 394 395 genericAttrNames.push_back("operand_segment_sizes"); 396 genericAttrNamesSet.insert(genericAttrNames.back()); 397 398 bool hasExtraAttrs = false; 399 for (NamedAttribute n : op.getAttrs()) { 400 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref()))) 401 break; 402 } 403 if (hasExtraAttrs) { 404 p << " attrs = "; 405 p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/genericAttrNames); 406 } 407 408 // Print region. 409 if (!op.region().empty()) 410 p.printRegion(op.region()); 411 412 // Print results. 413 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 414 } 415 416 static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); } 417 418 static void print(OpAsmPrinter &p, IndexedGenericOp op) { 419 printGenericOp(p, op); 420 } 421 422 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 423 DictionaryAttr dictAttr; 424 // Parse the core linalg traits that must check into a dictAttr. 425 // The name is unimportant as we will overwrite result.attributes. 426 // The core linalg traits must contain the information necessary to pass the 427 // verifier. 428 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 429 return failure(); 430 result.attributes.assign(dictAttr.getValue().begin(), 431 dictAttr.getValue().end()); 432 433 // Parsing is shared with named ops, except for the region. 434 SmallVector<Type, 1> inputTypes, outputTypes; 435 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 436 return failure(); 437 438 // Optional attributes may be added. 439 if (succeeded(parser.parseOptionalKeyword("attrs"))) 440 if (failed(parser.parseEqual()) || 441 failed(parser.parseOptionalAttrDict(result.attributes))) 442 return failure(); 443 444 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 445 std::unique_ptr<Region> region = std::make_unique<Region>(); 446 SmallVector<Type, 8> operandTypes, regionTypes; 447 if (parser.parseRegion(*region, regionOperands, regionTypes)) 448 return failure(); 449 result.addRegion(std::move(region)); 450 451 // Generic ops may specify that a subset of its outputs are tensors. Such 452 // outputs are specified in the result type. 453 // TODO: may need to move output parsing before region parsing. 454 // Need to wait for declarative assembly resolution to decide. 455 SmallVector<Type, 1> outputTensorsTypes; 456 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 457 return failure(); 458 result.addTypes(outputTensorsTypes); 459 460 return success(); 461 } 462 463 static void getGenericEffectsImpl( 464 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 465 &effects, 466 ValueRange results, ValueRange inputBuffers, ValueRange outputs) { 467 for (Value value : results) { 468 effects.emplace_back(MemoryEffects::Allocate::get(), value, 469 SideEffects::DefaultResource::get()); 470 } 471 for (Value value : inputBuffers) { 472 effects.emplace_back(MemoryEffects::Read::get(), value, 473 SideEffects::DefaultResource::get()); 474 } 475 for (Value value : outputs) { 476 effects.emplace_back(MemoryEffects::Read::get(), value, 477 SideEffects::DefaultResource::get()); 478 effects.emplace_back(MemoryEffects::Write::get(), value, 479 SideEffects::DefaultResource::get()); 480 } 481 } 482 483 void GenericOp::getEffects( 484 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 485 &effects) { 486 getGenericEffectsImpl(effects, getOperation()->getResults(), 487 getInputBuffers(), getOutputBuffers()); 488 } 489 490 void IndexedGenericOp::getEffects( 491 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 492 &effects) { 493 getGenericEffectsImpl(effects, getOperation()->getResults(), 494 getInputBuffers(), getOutputBuffers()); 495 } 496 497 namespace { 498 499 template <typename GenericOpType> 500 struct AnnotationsVerifier { 501 static LogicalResult verify(GenericOpType op) { return success(); } 502 }; 503 504 template <> 505 LogicalResult AnnotationsVerifier<GenericOp>::verify(GenericOp op) { 506 ArrayAttr sparseAttr = op.sparseAttr(); 507 if (!sparseAttr) 508 return success(); 509 // Verify consistency of sparse annotations. 510 if (!op.hasTensorSemantics()) 511 return op.emitOpError("expected sparse annotations on tensors only"); 512 if (op.getNumOutputs() != 1) 513 return op.emitOpError("expected single output tensor"); 514 unsigned numTensors = op.getNumShapedOperands(); 515 if (sparseAttr.size() != numTensors) 516 return op.emitOpError("expected one sparse annotation for each tensor"); 517 for (unsigned t = 0; t < numTensors; t++) { 518 auto dimAttr = sparseAttr[t].dyn_cast_or_null<ArrayAttr>(); 519 if (!dimAttr) 520 return op.emitOpError("expected sparse annotation array for tensor ") 521 << t; 522 unsigned rank = op.getShapedType(t).getRank(); 523 if (dimAttr.size() != rank) 524 return op.emitOpError("expected sparse annotation with rank ") 525 << rank << " for tensor " << t; 526 // Per-dimension annotations for each tensor consist of only "D" or "S". 527 for (unsigned d = 0; d < rank; d++) { 528 if (isDenseDim(dimAttr[d])) { 529 continue; 530 } else if (isSparseDim(dimAttr[d])) { 531 if (t == numTensors - 1) 532 return op.emitOpError("sparse output tensors not supported (yet)"); 533 continue; 534 } 535 return op.emitOpError("expected sparse annotation at position ") 536 << d << " for tensor " << t; 537 } 538 } 539 return success(); 540 } 541 542 } // namespace 543 544 template <typename GenericOpType> 545 static LogicalResult verifyGenericOp(GenericOpType op) { 546 if (failed(AnnotationsVerifier<GenericOpType>::verify(op))) 547 return failure(); 548 549 return success(); 550 } 551 552 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 553 554 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); } 555 556 //===----------------------------------------------------------------------===// 557 // InitTensorOp 558 //===----------------------------------------------------------------------===// 559 void InitTensorOp::build(OpBuilder &b, OperationState &result, 560 ArrayRef<OpFoldResult> sizes, Type elementType, 561 ArrayRef<NamedAttribute> attrs) { 562 unsigned rank = sizes.size(); 563 SmallVector<Value, 4> dynamicSizes; 564 SmallVector<int64_t, 4> staticSizes; 565 for (unsigned i = 0; i < rank; ++i) { 566 // staticLow and staticHigh have full information of the padding config. 567 // This will grow staticLow and staticHigh with 1 value. If the config is 568 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 569 // value as well. 570 dispatchIndexOpFoldResult(sizes[i], dynamicSizes, staticSizes, 571 ShapedType::kDynamicSize); 572 } 573 auto resultType = RankedTensorType ::get(staticSizes, elementType); 574 build(b, result, resultType, dynamicSizes, b.getI64ArrayAttr(staticSizes)); 575 result.addAttributes(attrs); 576 } 577 578 static LogicalResult verify(InitTensorOp op) { 579 RankedTensorType resultType = op.getType(); 580 SmallVector<int64_t, 4> staticSizes = llvm::to_vector<4>(llvm::map_range( 581 op.static_sizes().cast<ArrayAttr>(), 582 [](Attribute a) -> int64_t { return a.cast<IntegerAttr>().getInt(); })); 583 584 if (failed(verifyListOfOperandsOrIntegers(op, "sizes", resultType.getRank(), 585 op.static_sizes(), op.sizes(), 586 ShapedType::isDynamic))) 587 return failure(); 588 589 if (op.static_sizes().size() != static_cast<unsigned>(resultType.getRank())) 590 return op->emitError("expected ") 591 << resultType.getRank() << " sizes values"; 592 593 Type expectedType = 594 InitTensorOp::inferResultType(staticSizes, resultType.getElementType()); 595 if (resultType != expectedType) { 596 return op.emitError("specified type ") 597 << resultType << " does not match the inferred type " 598 << expectedType; 599 } 600 return success(); 601 } 602 603 Type InitTensorOp::inferResultType(ArrayRef<int64_t> staticSizes, 604 Type elementType) { 605 return RankedTensorType::get(staticSizes, elementType); 606 } 607 608 static Value getCollapsedInitTensor(OpBuilder &builder, 609 TensorReshapeOp reshapeOp) { 610 Location loc = reshapeOp.getLoc(); 611 SmallVector<Value, 4> dynamicShapes; 612 SmallVector<int64_t, 4> staticShapes; 613 auto reassociation = reshapeOp.getReassociationMaps(); 614 Value src = reshapeOp.src(); 615 RankedTensorType srcType = reshapeOp.getSrcType(); 616 ArrayRef<int64_t> srcShape = srcType.getShape(); 617 for (auto map : reassociation) { 618 Value linearizedDynamicDim = nullptr; 619 int64_t linearizedStaticDim = 1; 620 for (unsigned i : llvm::map_range(map.getResults(), [](AffineExpr e) { 621 return e.cast<AffineDimExpr>().getPosition(); 622 })) { 623 if (ShapedType::isDynamic(srcShape[i])) { 624 Value shapeVal = builder.create<DimOp>(loc, src, i); 625 if (linearizedDynamicDim) { 626 linearizedDynamicDim = 627 builder.create<MulIOp>(loc, linearizedDynamicDim, shapeVal); 628 } else { 629 linearizedDynamicDim = shapeVal; 630 } 631 } else { 632 linearizedStaticDim *= srcShape[i]; 633 } 634 } 635 if (linearizedDynamicDim) { 636 if (linearizedStaticDim != 1) { 637 linearizedDynamicDim = builder.create<MulIOp>( 638 loc, linearizedDynamicDim, 639 builder.create<ConstantIndexOp>(loc, linearizedStaticDim)); 640 } 641 dynamicShapes.push_back(linearizedDynamicDim); 642 staticShapes.push_back(ShapedType::kDynamicSize); 643 } else { 644 staticShapes.push_back(linearizedStaticDim); 645 } 646 } 647 return builder.create<InitTensorOp>(loc, dynamicShapes, staticShapes, 648 srcType.getElementType()); 649 } 650 651 static Value getExpandedInitTensor(OpBuilder &builder, 652 TensorReshapeOp reshapeOp) { 653 SmallVector<Value, 4> dynamicShapes; 654 SmallVector<int64_t, 4> staticShapes; 655 auto reassociation = reshapeOp.getReassociationMaps(); 656 Value src = reshapeOp.src(); 657 RankedTensorType srcType = reshapeOp.getSrcType(); 658 ArrayRef<int64_t> srcShape = srcType.getShape(); 659 ArrayRef<int64_t> dstShape = reshapeOp.getResultType().getShape(); 660 Location loc = reshapeOp.getLoc(); 661 for (auto map : enumerate(reassociation)) { 662 int64_t linearizedStaticDim = 1; 663 bool hasDynamic = false; 664 for (unsigned i : 665 llvm::map_range(map.value().getResults(), [](AffineExpr e) { 666 return e.cast<AffineDimExpr>().getPosition(); 667 })) { 668 if (ShapedType::isDynamic(dstShape[i])) { 669 // Only one of the dimensions of the expanded shape should be dynamic. 670 if (hasDynamic) 671 return nullptr; 672 hasDynamic = true; 673 staticShapes.push_back(ShapedType::kDynamicSize); 674 continue; 675 } 676 staticShapes.push_back(dstShape[i]); 677 linearizedStaticDim *= dstShape[i]; 678 } 679 if (hasDynamic) { 680 // If the expanded dimensions has a dynamic shape, the src shape must be 681 // dynamic as well. 682 if (!ShapedType::isDynamic(srcShape[map.index()])) 683 return nullptr; 684 Value dynamicDim = builder.create<DimOp>(loc, src, map.index()); 685 if (linearizedStaticDim != 1) { 686 dynamicDim = builder.create<UnsignedDivIOp>( 687 loc, dynamicDim, 688 builder.create<ConstantIndexOp>(loc, linearizedStaticDim)); 689 } 690 dynamicShapes.push_back(dynamicDim); 691 } 692 } 693 return builder.create<InitTensorOp>(loc, dynamicShapes, staticShapes, 694 srcType.getElementType()); 695 } 696 697 namespace { 698 /// Change the type of the result of a `linalg.init_tensor` by making the result 699 /// type statically sized along dimension that in the original operation where 700 /// defined as dynamic, but the size was defined using a `constant` op. For 701 /// example 702 /// 703 /// %c5 = constant 5: index 704 /// %0 = linalg.init_tensor [%arg0, %c5] : tensor<?x?xf32> 705 /// 706 /// to 707 /// 708 /// %0 = linalg.init_tensor [%arg0, 5] : tensor<?x5xf32> 709 struct ReplaceStaticShapeDims : OpRewritePattern<InitTensorOp> { 710 using OpRewritePattern<InitTensorOp>::OpRewritePattern; 711 712 LogicalResult matchAndRewrite(InitTensorOp op, 713 PatternRewriter &rewriter) const override { 714 SmallVector<Value, 4> dynamicSizes; 715 SmallVector<int64_t, 4> staticSizes; 716 for (unsigned i = 0, e = op.getType().getRank(); i != e; ++i) { 717 // If the size is already static, nothing to do. 718 if (!op.isDynamicSize(i)) { 719 staticSizes.push_back(op.getStaticSize(i)); 720 continue; 721 } 722 723 // If the size is dynamic but defined using a `constant` op, get the 724 // constant value to find the static size to use. 725 unsigned operandNum = op.getIndexOfDynamicSize(i); 726 Value sizeOperand = op.getOperand(operandNum); 727 if (auto constantIndexOp = sizeOperand.getDefiningOp<ConstantIndexOp>()) { 728 staticSizes.push_back(constantIndexOp.getValue()); 729 continue; 730 } 731 732 // Fallback case. Keep the size dynamic. 733 dynamicSizes.push_back(sizeOperand); 734 staticSizes.push_back(ShapedType::kDynamicSize); 735 } 736 RankedTensorType newType = 737 RankedTensorType::get(staticSizes, op.getType().getElementType()); 738 if (newType == op.getType()) 739 return failure(); 740 auto newOp = 741 rewriter.create<InitTensorOp>(op.getLoc(), newType, dynamicSizes, 742 rewriter.getI64ArrayAttr(staticSizes)); 743 rewriter.replaceOpWithNewOp<tensor::CastOp>(op, op.getType(), newOp); 744 return success(); 745 } 746 }; 747 748 /// Canonicalize a `linalg.init_tensor` -> `dim` pattern by replacing the `dim` 749 /// with 750 /// - A constant value if the size is static along the dimension. 751 /// - The dynamic value that defines the size of the result of 752 /// `linalg.init_tensor` op. 753 struct ReplaceDimOfInitTensorOp : public OpRewritePattern<DimOp> { 754 using OpRewritePattern<DimOp>::OpRewritePattern; 755 756 LogicalResult matchAndRewrite(DimOp dimOp, 757 PatternRewriter &rewriter) const override { 758 auto initTensorOp = dimOp.memrefOrTensor().getDefiningOp<InitTensorOp>(); 759 if (!initTensorOp) 760 return failure(); 761 auto dimIndex = dimOp.index().getDefiningOp<ConstantIndexOp>(); 762 if (!dimIndex) 763 return failure(); 764 int64_t index = dimIndex.getValue(); 765 if (!initTensorOp.isDynamicSize(index)) { 766 rewriter.replaceOpWithNewOp<ConstantIndexOp>( 767 dimOp, initTensorOp.getStaticSize(index)); 768 } else { 769 rewriter.replaceOp(dimOp, initTensorOp.getDynamicSize(index)); 770 } 771 return success(); 772 } 773 }; 774 } // namespace 775 776 namespace { 777 /// Since `init_tensor` operation creates a tensor needed only for its shape, a 778 /// subtensor of this is also needed only for its shape. The result can be 779 /// replaced by a new init_tensor operation of the same size as the subtensor 780 /// op. 781 struct FoldInitTensorWithSubTensorOp : public OpRewritePattern<SubTensorOp> { 782 using OpRewritePattern<SubTensorOp>::OpRewritePattern; 783 784 LogicalResult matchAndRewrite(SubTensorOp subtensorOp, 785 PatternRewriter &rewriter) const override { 786 if (!subtensorOp.source().getDefiningOp<linalg::InitTensorOp>()) 787 return failure(); 788 rewriter.replaceOpWithNewOp<linalg::InitTensorOp>( 789 subtensorOp, subtensorOp.sizes(), 790 llvm::to_vector<4>(llvm::map_range( 791 subtensorOp.static_sizes(), 792 [](Attribute attr) { return attr.cast<IntegerAttr>().getInt(); })), 793 subtensorOp.getSourceType().getElementType()); 794 return success(); 795 } 796 }; 797 798 struct FoldInitTensorWithTensorReshapeOp 799 : public OpRewritePattern<TensorReshapeOp> { 800 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 801 802 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 803 PatternRewriter &rewriter) const override { 804 if (!reshapeOp.src().getDefiningOp<InitTensorOp>()) 805 return failure(); 806 Location loc = reshapeOp.getLoc(); 807 SmallVector<Value, 4> resultShapeValues = 808 reshapeOp.getOutputShape(rewriter, loc); 809 Value initTensor = rewriter.create<InitTensorOp>( 810 loc, resultShapeValues, reshapeOp.getResultType().getElementType()); 811 rewriter.replaceOpWithNewOp<tensor::CastOp>( 812 reshapeOp, reshapeOp.getResultType(), initTensor); 813 return success(); 814 } 815 }; 816 } // namespace 817 818 void InitTensorOp::getCanonicalizationPatterns( 819 OwningRewritePatternList &results, MLIRContext *context) { 820 results 821 .insert<FoldInitTensorWithSubTensorOp, FoldInitTensorWithTensorReshapeOp, 822 ReplaceDimOfInitTensorOp, ReplaceStaticShapeDims>(context); 823 } 824 825 //===----------------------------------------------------------------------===// 826 // PadTensorOp 827 //===----------------------------------------------------------------------===// 828 829 /// Extract int64_t values from the assumed ArrayAttr of IntegerAttr. 830 static SmallVector<int64_t, 4> extractFromI64ArrayAttr(Attribute attr) { 831 return llvm::to_vector<4>( 832 llvm::map_range(attr.cast<ArrayAttr>(), [](Attribute a) -> int64_t { 833 return a.cast<IntegerAttr>().getInt(); 834 })); 835 } 836 837 static LogicalResult verify(PadTensorOp op) { 838 auto sourceType = op.source().getType().cast<RankedTensorType>(); 839 auto resultType = op.result().getType().cast<RankedTensorType>(); 840 auto expectedType = PadTensorOp::inferResultType( 841 sourceType, extractFromI64ArrayAttr(op.static_low()), 842 extractFromI64ArrayAttr(op.static_high())); 843 for (int i = 0, e = sourceType.getRank(); i < e; ++i) { 844 if (resultType.getDimSize(i) == expectedType.getDimSize(i)) 845 continue; 846 if (expectedType.isDynamicDim(i)) 847 continue; 848 return op.emitError("specified type ") 849 << resultType << " does not match the inferred type " 850 << expectedType; 851 } 852 853 auto ®ion = op.region(); 854 unsigned rank = resultType.getRank(); 855 Block &block = region.front(); 856 if (block.getNumArguments() != rank) 857 return op.emitError("expected the block to have ") << rank << " arguments"; 858 859 // Note: the number and type of yield values are checked in the YieldOp. 860 for (auto en : llvm::enumerate(block.getArgumentTypes())) { 861 if (!en.value().isIndex()) 862 return op.emitOpError("expected block argument ") 863 << (en.index() + 1) << " to be an index"; 864 } 865 866 return success(); 867 } 868 869 RankedTensorType PadTensorOp::inferResultType(RankedTensorType sourceType, 870 ArrayRef<int64_t> staticLow, 871 ArrayRef<int64_t> staticHigh) { 872 unsigned rank = sourceType.getRank(); 873 assert(staticLow.size() == rank && "unexpected staticLow size mismatch"); 874 assert(staticHigh.size() == rank && "unexpected staticHigh size mismatch"); 875 876 SmallVector<int64_t, 4> resultShape; 877 for (auto i : llvm::seq<unsigned>(0, rank)) { 878 if (sourceType.isDynamicDim(i) || 879 staticLow[i] == ShapedType::kDynamicSize || 880 staticHigh[i] == ShapedType::kDynamicSize) { 881 resultShape.push_back(ShapedType::kDynamicSize); 882 } else { 883 int64_t size = sourceType.getDimSize(i) + staticLow[i] + staticHigh[i]; 884 resultShape.push_back(size); 885 } 886 } 887 888 return RankedTensorType::get(resultShape, sourceType.getElementType()); 889 } 890 891 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 892 ArrayRef<int64_t> staticLow, 893 ArrayRef<int64_t> staticHigh, ValueRange low, 894 ValueRange high, ArrayRef<NamedAttribute> attrs) { 895 auto sourceType = source.getType().cast<RankedTensorType>(); 896 auto resultType = inferResultType(sourceType, staticLow, staticHigh); 897 build(b, result, resultType, source, low, high, b.getI64ArrayAttr(staticLow), 898 b.getI64ArrayAttr(staticHigh)); 899 result.addAttributes(attrs); 900 } 901 902 void PadTensorOp::build(OpBuilder &b, OperationState &result, Value source, 903 ValueRange low, ValueRange high, 904 ArrayRef<NamedAttribute> attrs) { 905 auto sourceType = source.getType().cast<RankedTensorType>(); 906 unsigned rank = sourceType.getRank(); 907 SmallVector<int64_t, 4> staticVector(ShapedType::kDynamicSize, rank); 908 build(b, result, source, staticVector, staticVector, low, high, attrs); 909 } 910 911 void PadTensorOp::build(OpBuilder &b, OperationState &result, Type resultType, 912 Value source, ArrayRef<OpFoldResult> low, 913 ArrayRef<OpFoldResult> high, 914 ArrayRef<NamedAttribute> attrs) { 915 assert(resultType.isa<RankedTensorType>()); 916 auto sourceType = source.getType().cast<RankedTensorType>(); 917 unsigned rank = sourceType.getRank(); 918 SmallVector<Value, 4> dynamicLow, dynamicHigh; 919 SmallVector<int64_t, 4> staticLow, staticHigh; 920 for (unsigned i = 0; i < rank; ++i) { 921 // staticLow and staticHigh have full information of the padding config. 922 // This will grow staticLow and staticHigh with 1 value. If the config is 923 // dynamic (ie not a constant), dynamicLow and dynamicHigh will grow with 1 924 // value as well. 925 dispatchIndexOpFoldResult(low[i], dynamicLow, staticLow, 926 ShapedType::kDynamicSize); 927 dispatchIndexOpFoldResult(high[i], dynamicHigh, staticHigh, 928 ShapedType::kDynamicSize); 929 } 930 if (!resultType) { 931 resultType = 932 PadTensorOp::inferResultType(sourceType, staticLow, staticHigh); 933 } 934 build(b, result, resultType, source, dynamicLow, dynamicHigh, 935 b.getI64ArrayAttr(staticLow), b.getI64ArrayAttr(staticHigh)); 936 } 937 938 PadTensorOp PadTensorOp::createPadScalarOp(Type type, Value source, Value pad, 939 ArrayRef<OpFoldResult> low, 940 ArrayRef<OpFoldResult> high, 941 Location loc, OpBuilder &builder) { 942 auto padTensorOp = 943 builder.create<linalg::PadTensorOp>(loc, type, source, low, high); 944 int rank = padTensorOp.getResultType().getRank(); 945 SmallVector<Type, 4> blockArgTypes; 946 blockArgTypes.assign(rank, builder.getIndexType()); 947 auto ®ion = padTensorOp.region(); 948 // `builder.createBlock` changes the insertion point within the block. Create 949 // a guard to reset the insertion point of the builder after it is destroyed. 950 OpBuilder::InsertionGuard guard(builder); 951 builder.createBlock(®ion, region.end(), blockArgTypes); 952 builder.create<linalg::YieldOp>(loc, pad); 953 return padTensorOp; 954 } 955 956 PadTensorOp PadTensorOp::createPadHighOp(Type type, Value source, Value pad, 957 Location loc, OpBuilder &builder) { 958 SmallVector<OpFoldResult, 4> low, high; 959 auto rankedTensorType = type.cast<RankedTensorType>(); 960 assert(rankedTensorType.hasStaticShape()); 961 int rank = rankedTensorType.getRank(); 962 for (int i = 0; i < rank; ++i) { 963 auto dimOp = builder.createOrFold<DimOp>(loc, source, i); 964 auto resultDimSize = builder.createOrFold<ConstantIndexOp>( 965 loc, rankedTensorType.getDimSize(i)); 966 auto highValue = builder.createOrFold<SubIOp>(loc, resultDimSize, dimOp); 967 high.push_back(highValue); 968 low.push_back(builder.createOrFold<ConstantIndexOp>(loc, 0)); 969 } 970 return PadTensorOp::createPadScalarOp(type, source, pad, low, high, loc, 971 builder); 972 } 973 974 //===----------------------------------------------------------------------===// 975 // ReshapeOp 976 //===----------------------------------------------------------------------===// 977 978 /// Collapse reassociation maps that are used in pair of reshape ops where one 979 /// is a producer and other is the consumer. Only valid to use this method when 980 /// both the producer and consumer are collapsing dimensions or both are 981 /// expanding dimensions. 982 /// 983 /// For example, 984 /// mapsProducer = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1)>, 985 /// affine_map<(d0, d1, d2, d3, d4) -> (d2)>, 986 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 987 /// mapsConsumer = [affine_map<(d0, d1, d2) -> (d0, d1)>, 988 /// affine_map<(d0, d1, d2) -> (d2)>] 989 /// 990 /// is folded into 991 /// 992 /// result = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1, d2)>, 993 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 994 static ArrayAttr collapseReassociationMaps(ArrayRef<AffineMap> mapsProducer, 995 ArrayRef<AffineMap> mapsConsumer, 996 MLIRContext *context) { 997 // Handle the corner case of the result being a rank 0 shaped type. Return an 998 // emtpy ArrayAttr. 999 if (mapsConsumer.empty() && !mapsProducer.empty()) 1000 return ArrayAttr::get(context, ArrayRef<Attribute>()); 1001 if (mapsProducer.empty() || mapsConsumer.empty() || 1002 mapsProducer[0].getNumDims() < mapsConsumer[0].getNumDims() || 1003 mapsProducer.size() != mapsConsumer[0].getNumDims()) 1004 return nullptr; 1005 unsigned numLhsDims = mapsProducer[0].getNumDims(); 1006 unsigned currDim = 0; 1007 SmallVector<AffineExpr, 4> reassociations; 1008 SmallVector<Attribute, 4> reassociationMaps; 1009 for (AffineMap rhs : mapsConsumer) { 1010 for (AffineExpr rhsExpr : rhs.getResults()) { 1011 AffineDimExpr dimExpr = rhsExpr.cast<AffineDimExpr>(); 1012 for (int i = 0, e = mapsProducer[dimExpr.getPosition()].getNumResults(); 1013 i < e; ++i) { 1014 reassociations.push_back(getAffineDimExpr(currDim++, context)); 1015 } 1016 } 1017 reassociationMaps.push_back(AffineMapAttr::get(AffineMap::get( 1018 numLhsDims, /*numSymbols =*/0, reassociations, context))); 1019 reassociations.clear(); 1020 } 1021 return ArrayAttr::get(context, reassociationMaps); 1022 } 1023 1024 namespace { 1025 /// Pattern to collapse producer/consumer reshape ops that are both collapsing 1026 /// dimensions or are both expanding dimensions. 1027 template <typename ReshapeOpTy> 1028 struct CollapseReshapeOps : public OpRewritePattern<ReshapeOpTy> { 1029 using OpRewritePattern<ReshapeOpTy>::OpRewritePattern; 1030 LogicalResult matchAndRewrite(ReshapeOpTy reshapeOp, 1031 PatternRewriter &rewriter) const override { 1032 auto srcReshapeOp = reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 1033 if (!srcReshapeOp) 1034 return failure(); 1035 1036 auto areReshapeOpsFoldable = [](ShapedType largerType, 1037 ShapedType intermediateType, 1038 ShapedType smallerType) -> bool { 1039 return largerType.getRank() > intermediateType.getRank() && 1040 intermediateType.getRank() > smallerType.getRank(); 1041 }; 1042 // Check if producer and consumer are both expanding dims. 1043 if (areReshapeOpsFoldable(reshapeOp.getResultType(), reshapeOp.getSrcType(), 1044 srcReshapeOp.getSrcType())) { 1045 rewriter.replaceOpWithNewOp<ReshapeOpTy>( 1046 reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(), 1047 collapseReassociationMaps(reshapeOp.getReassociationMaps(), 1048 srcReshapeOp.getReassociationMaps(), 1049 rewriter.getContext())); 1050 return success(); 1051 } 1052 // Check if producer and consumer are both collapsing dims. 1053 if (areReshapeOpsFoldable(srcReshapeOp.getSrcType(), reshapeOp.getSrcType(), 1054 reshapeOp.getResultType())) { 1055 rewriter.replaceOpWithNewOp<ReshapeOpTy>( 1056 reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(), 1057 collapseReassociationMaps(srcReshapeOp.getReassociationMaps(), 1058 reshapeOp.getReassociationMaps(), 1059 rewriter.getContext())); 1060 return success(); 1061 } 1062 return failure(); 1063 } 1064 }; 1065 } // namespace 1066 1067 template <typename ReshapeOpTy> 1068 static OpFoldResult foldReshapeOp(ReshapeOpTy reshapeOp, 1069 ArrayRef<Attribute> operands) { 1070 // Fold producer-consumer reshape ops that where the operand type of the 1071 // producer is same as the return type of the consumer. 1072 ReshapeOpTy reshapeSrcOp = 1073 reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 1074 if (reshapeSrcOp && reshapeSrcOp.getSrcType() == reshapeOp.getResultType()) 1075 return reshapeSrcOp.src(); 1076 // Reshape of a constant can be replaced with a new constant. 1077 if (auto elements = operands.front().dyn_cast_or_null<DenseElementsAttr>()) { 1078 return elements.reshape( 1079 reshapeOp.getResult().getType().template cast<ShapedType>()); 1080 } 1081 return nullptr; 1082 } 1083 1084 /// Return true if the reassociation specification is valid, false otherwise. 1085 /// When false, the `invalidIndex` integer pointer is optionally filled with the 1086 /// index of the offending reassociation map. 1087 static bool isReassociationValid(ArrayRef<AffineMap> reassociation, 1088 int *invalidIndex = nullptr) { 1089 if (reassociation.empty()) 1090 return true; 1091 unsigned nDims = reassociation[0].getNumDims(); 1092 unsigned nextExpectedDim = 0; 1093 for (auto it : llvm::enumerate(reassociation)) { 1094 auto m = it.value(); 1095 if (m.getNumDims() != nDims || m.getNumSymbols() != 0) { 1096 if (invalidIndex) 1097 *invalidIndex = it.index(); 1098 return false; 1099 } 1100 for (auto e : m.getResults()) { 1101 auto d = e.dyn_cast<AffineDimExpr>(); 1102 if (!d || d.getPosition() != nextExpectedDim++) { 1103 if (invalidIndex) 1104 *invalidIndex = it.index(); 1105 return false; 1106 } 1107 } 1108 } 1109 if (nextExpectedDim != nDims) { 1110 if (invalidIndex) 1111 *invalidIndex = reassociation.size() - 1; 1112 return false; 1113 } 1114 return true; 1115 } 1116 1117 /// Detect whether memref dims [dim, dim + extent) can be reshaped without 1118 /// copies. 1119 static bool isReshapableDimBand(unsigned dim, unsigned extent, 1120 ArrayRef<int64_t> sizes, 1121 ArrayRef<AffineExpr> strides) { 1122 assert(sizes.size() == strides.size() && "mismatched ranks"); 1123 // off by 1 indexing to avoid out of bounds 1124 // V 1125 for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) { 1126 // Only bands of static shapes are reshapable. This is due to the fact that 1127 // there is no relation between dynamic sizes and dynamic strides: we do not 1128 // have enough information to know whether a "-1" size corresponds to the 1129 // proper symbol in the AffineExpr of a stride. 1130 if (ShapedType::isDynamic(sizes[dim + 1])) 1131 return false; 1132 // TODO: Refine this by passing the proper nDims and nSymbols so we can 1133 // simplify on the fly and catch more reshapable cases. 1134 if (strides[idx] != strides[idx + 1] * sizes[idx + 1]) 1135 return false; 1136 } 1137 return true; 1138 } 1139 1140 /// Compute the MemRefType obtained by applying the `reassociation` (which is 1141 /// expected to be valid) to `type`. 1142 /// If `type` is Contiguous MemRefType, this always produce a contiguous 1143 /// MemRefType. 1144 static MemRefType 1145 computeReshapeCollapsedType(MemRefType type, 1146 ArrayRef<AffineMap> reassociation) { 1147 auto sizes = type.getShape(); 1148 AffineExpr offset; 1149 SmallVector<AffineExpr, 4> strides; 1150 auto status = getStridesAndOffset(type, strides, offset); 1151 (void)status; 1152 assert(succeeded(status) && "expected strided memref"); 1153 1154 SmallVector<int64_t, 4> newSizes; 1155 newSizes.reserve(reassociation.size()); 1156 SmallVector<AffineExpr, 4> newStrides; 1157 newStrides.reserve(reassociation.size()); 1158 1159 // Use the fact that reassociation is valid to simplify the logic: only use 1160 // each map's rank. 1161 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1162 unsigned currentDim = 0; 1163 for (AffineMap m : reassociation) { 1164 unsigned dim = m.getNumResults(); 1165 int64_t size = 1; 1166 AffineExpr stride = strides[currentDim + dim - 1]; 1167 if (!isReshapableDimBand(currentDim, dim, sizes, strides)) { 1168 size = ShapedType::kDynamicSize; 1169 stride = AffineExpr(); 1170 } else { 1171 for (unsigned d = 0; d < dim; ++d) 1172 size *= sizes[currentDim + d]; 1173 } 1174 newSizes.push_back(size); 1175 newStrides.push_back(stride); 1176 currentDim += dim; 1177 } 1178 1179 // Early-exit: if `type` is contiguous, the result must be contiguous. 1180 if (canonicalizeStridedLayout(type).getAffineMaps().empty()) 1181 return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({}); 1182 1183 // Convert back to int64_t because we don't have enough information to create 1184 // new strided layouts from AffineExpr only. This corresponds to a case where 1185 // copies may be necessary. 1186 int64_t intOffset = ShapedType::kDynamicStrideOrOffset; 1187 if (auto o = offset.dyn_cast<AffineConstantExpr>()) 1188 intOffset = o.getValue(); 1189 SmallVector<int64_t, 4> intStrides; 1190 intStrides.reserve(strides.size()); 1191 for (auto stride : newStrides) { 1192 if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>()) 1193 intStrides.push_back(cst.getValue()); 1194 else 1195 intStrides.push_back(ShapedType::kDynamicStrideOrOffset); 1196 } 1197 auto layout = 1198 makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext()); 1199 return canonicalizeStridedLayout( 1200 MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout})); 1201 } 1202 1203 /// Helper functions assert Attribute of the proper type in attr and returns the 1204 /// corresponding vector. 1205 /// TODO: this should be evolved into a generic 1206 /// `getRangeOfType<AffineMap>(ArrayAttr attrs)` that does not copy. 1207 static SmallVector<AffineMap, 4> getAffineMaps(ArrayAttr attrs) { 1208 return llvm::to_vector<8>(llvm::map_range( 1209 attrs, [](Attribute a) { return a.cast<AffineMapAttr>().getValue(); })); 1210 } 1211 1212 template <typename AffineExprTy> 1213 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) { 1214 unsigned pos = 0; 1215 for (const auto &exprs : exprArrays) { 1216 for (auto expr : exprs) { 1217 expr.walk([&pos](AffineExpr e) { 1218 if (auto d = e.dyn_cast<AffineExprTy>()) 1219 pos = std::max(pos, d.getPosition()); 1220 }); 1221 } 1222 } 1223 return pos; 1224 } 1225 1226 static SmallVector<AffineMap, 4> 1227 getSymbolLessAffineMaps(ArrayRef<ReassociationExprs> reassociation) { 1228 unsigned maxDim = getMaxPosOfType<AffineDimExpr>(reassociation); 1229 assert(getMaxPosOfType<AffineSymbolExpr>(reassociation) == 0 && 1230 "Expected symbol-less expressions"); 1231 SmallVector<AffineMap, 4> maps; 1232 maps.reserve(reassociation.size()); 1233 for (const auto &exprs : reassociation) { 1234 assert(!exprs.empty()); 1235 maps.push_back(AffineMap::get(maxDim + 1, 0, exprs, exprs[0].getContext())); 1236 } 1237 return maps; 1238 } 1239 1240 static SmallVector<SmallVector<AffineExpr, 2>, 2> 1241 convertReassociationIndicesToMaps( 1242 OpBuilder &b, ArrayRef<ReassociationIndices> reassociationIndices) { 1243 SmallVector<SmallVector<AffineExpr, 2>, 2> reassociationMaps; 1244 for (const auto &indices : reassociationIndices) { 1245 SmallVector<AffineExpr, 2> reassociationMap; 1246 reassociationMap.reserve(indices.size()); 1247 for (int64_t index : indices) 1248 reassociationMap.push_back(b.getAffineDimExpr(index)); 1249 reassociationMaps.push_back(std::move(reassociationMap)); 1250 } 1251 return reassociationMaps; 1252 } 1253 1254 /// For reshape op compute the shape at dimension `dimIndex` of the output in 1255 /// terms of shape of the `src`, when the reshape op is a collapsing 1256 /// operation. It is the product of the shape of the collapsed dimensions of the 1257 /// `src`. 1258 static Value 1259 getCollapsedOutputDimFromInputShape(OpBuilder &builder, Location loc, 1260 int64_t dimIndex, Value src, 1261 ArrayRef<AffineMap> reassociationMap) { 1262 AffineMap map = reassociationMap[dimIndex]; 1263 unsigned startPos = 1264 map.getResults().front().cast<AffineDimExpr>().getPosition(); 1265 unsigned endPos = map.getResults().back().cast<AffineDimExpr>().getPosition(); 1266 AffineExpr expr; 1267 SmallVector<Value, 2> dynamicDims; 1268 for (auto dim : llvm::seq(startPos, endPos + 1)) { 1269 dynamicDims.push_back(builder.create<DimOp>(loc, src, dim)); 1270 AffineExpr currExpr = builder.getAffineSymbolExpr(dim - startPos); 1271 expr = (expr ? expr * currExpr : currExpr); 1272 } 1273 return applyMapToValues(builder, loc, 1274 AffineMap::get(0, endPos - startPos + 1, expr), 1275 dynamicDims)[0]; 1276 } 1277 1278 /// Given the `src` of a collapsing reshape op and its reassociation maps, 1279 /// compute the shape of the result of the reshape. 1280 static SmallVector<Value, 4> getCollapsedOutputShapeFromInputShape( 1281 OpBuilder &builder, Location loc, Value src, 1282 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1283 return llvm::to_vector<4>(llvm::map_range( 1284 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1285 return getCollapsedOutputDimFromInputShape(builder, loc, dim, src, 1286 reassociation); 1287 })); 1288 } 1289 1290 /// Compute a map that for a given dimension of the expanded type gives the 1291 /// dimension in the collapsed type it maps to. Essentially its the inverse of 1292 /// the `reassocation` maps. 1293 static llvm::DenseMap<int64_t, int64_t> 1294 getExpandedDimToCollapsedDimMap(ArrayRef<AffineMap> reassociation) { 1295 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim; 1296 for (auto map : enumerate(reassociation)) { 1297 unsigned startPos = 1298 map.value().getResults().front().cast<AffineDimExpr>().getPosition(); 1299 unsigned endPos = 1300 map.value().getResults().back().cast<AffineDimExpr>().getPosition(); 1301 for (auto dim : llvm::seq(startPos, endPos + 1)) { 1302 expandedDimToCollapsedDim[dim] = map.index(); 1303 } 1304 } 1305 return expandedDimToCollapsedDim; 1306 } 1307 1308 /// For an expanding reshape op, compute the value for a dimension of the output 1309 /// from the shape of the input. 1310 static Value getExpandedOutputDimFromInputShape( 1311 OpBuilder &builder, Location loc, int64_t dimIndex, Value src, 1312 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation, 1313 llvm::DenseMap<int64_t, int64_t> &expandedDimToCollapsedDim) { 1314 if (!ShapedType::isDynamic(dstStaticShape[dimIndex])) { 1315 return builder.create<ConstantIndexOp>(loc, dstStaticShape[dimIndex]); 1316 } 1317 unsigned sourceDimPos = expandedDimToCollapsedDim[dimIndex]; 1318 unsigned startPos = reassociation[sourceDimPos] 1319 .getResults() 1320 .front() 1321 .cast<AffineDimExpr>() 1322 .getPosition(); 1323 unsigned endPos = reassociation[sourceDimPos] 1324 .getResults() 1325 .back() 1326 .cast<AffineDimExpr>() 1327 .getPosition(); 1328 int64_t linearizedStaticDim = 1; 1329 for (auto d : 1330 llvm::enumerate(dstStaticShape.slice(startPos, endPos - startPos + 1))) { 1331 if (d.index() + startPos == static_cast<unsigned>(dimIndex)) 1332 continue; 1333 assert(!ShapedType::isDynamic(d.value()) && 1334 "single dimension cannot be expanded into multiple dynamic " 1335 "dimensions"); 1336 linearizedStaticDim *= d.value(); 1337 } 1338 Value sourceDim = builder.create<DimOp>(loc, src, sourceDimPos); 1339 return applyMapToValues( 1340 builder, loc, 1341 AffineMap::get( 1342 0, 1, builder.getAffineSymbolExpr(0).floorDiv(linearizedStaticDim)), 1343 sourceDim)[0]; 1344 } 1345 1346 /// Given the `src` of an expanding reshape op, the reassociation maps and the 1347 /// result type, compute the shape of the result of the reshape. 1348 static SmallVector<Value, 4> getExpandedOutputShapeFromInputShape( 1349 OpBuilder &builder, Location loc, Value src, 1350 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1351 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim = 1352 getExpandedDimToCollapsedDimMap(reassociation); 1353 return llvm::to_vector<4>(llvm::map_range( 1354 llvm::seq<int64_t>(0, dstStaticShape.size()), [&](int64_t dim) { 1355 return getExpandedOutputDimFromInputShape(builder, loc, dim, src, 1356 dstStaticShape, reassociation, 1357 expandedDimToCollapsedDim); 1358 })); 1359 } 1360 1361 SmallVector<Value, 4> mlir::linalg::getReshapeOutputShapeFromInputShape( 1362 OpBuilder &builder, Location loc, Value src, 1363 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassocation) { 1364 return dstStaticShape.size() > 1365 static_cast<size_t>(src.getType().cast<ShapedType>().getRank()) 1366 ? getExpandedOutputShapeFromInputShape( 1367 builder, loc, src, dstStaticShape, reassocation) 1368 : getCollapsedOutputShapeFromInputShape( 1369 builder, loc, src, dstStaticShape, reassocation); 1370 } 1371 1372 /// For a reshape op, compute the value of a given dimension of the output 1373 /// (`dimIndex`) from the shape of the inputs and type of the result. 1374 static Value getReshapeOutputDimFromInputShape( 1375 OpBuilder &builder, Location loc, int64_t dimIndex, Value src, 1376 ArrayRef<int64_t> dstStaticShape, ArrayRef<AffineMap> reassociation) { 1377 if (dstStaticShape.size() > 1378 static_cast<size_t>(src.getType().cast<ShapedType>().getRank())) { 1379 llvm::DenseMap<int64_t, int64_t> expandedDimToCollapsedDim = 1380 getExpandedDimToCollapsedDimMap(reassociation); 1381 return getExpandedOutputDimFromInputShape(builder, loc, dimIndex, src, 1382 dstStaticShape, reassociation, 1383 expandedDimToCollapsedDim); 1384 } 1385 return getCollapsedOutputDimFromInputShape(builder, loc, dimIndex, src, 1386 reassociation); 1387 } 1388 1389 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result, 1390 Value src, 1391 ArrayRef<ReassociationExprs> reassociation, 1392 ArrayRef<NamedAttribute> attrs) { 1393 auto maps = getSymbolLessAffineMaps(reassociation); 1394 auto memRefType = src.getType().cast<MemRefType>(); 1395 auto resultType = computeReshapeCollapsedType(memRefType, maps); 1396 build(b, result, resultType, src, attrs); 1397 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1398 b.getAffineMapArrayAttr(maps)); 1399 } 1400 1401 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result, 1402 Type resultType, Value src, 1403 ArrayRef<ReassociationExprs> reassociation, 1404 ArrayRef<NamedAttribute> attrs) { 1405 auto maps = getSymbolLessAffineMaps(reassociation); 1406 build(b, result, resultType, src, attrs); 1407 result.addAttribute(ReshapeOp::getReassociationAttrName(), 1408 b.getAffineMapArrayAttr(maps)); 1409 } 1410 1411 Value mlir::linalg::ReshapeOp::getViewSource() { return src(); } 1412 1413 /// Verify that shapes of the reshaped types using following rules 1414 /// 1) if a dimension in the collapsed type is static, then the corresponding 1415 /// dimensions in the expanded shape should be 1416 /// a) static 1417 /// b) the product should be same as the collaped shape. 1418 /// 2) if a dimension in the collaped type is dynamic, one and only one of the 1419 /// corresponding dimensions in the expanded type should be dynamic. This 1420 /// rule is only needed with reshape operations that are expanding. 1421 template <typename OpTy> 1422 static LogicalResult verifyReshapeLikeShapes(OpTy op, ShapedType collapsedType, 1423 ShapedType expandedType, 1424 bool isExpandingReshape) { 1425 ArrayRef<int64_t> collapsedShape = collapsedType.getShape(); 1426 ArrayRef<int64_t> expandedShape = expandedType.getShape(); 1427 unsigned expandedDimStart = 0; 1428 for (auto map : llvm::enumerate(op.getReassociationMaps())) { 1429 Optional<int64_t> dynamicShape; 1430 int64_t linearizedStaticShape = 1; 1431 for (auto dim : llvm::enumerate(expandedShape.slice( 1432 expandedDimStart, map.value().getNumResults()))) { 1433 if (ShapedType::isDynamic(dim.value())) { 1434 if (isExpandingReshape && dynamicShape) { 1435 return op->emitOpError("invalid to have a single dimension (") 1436 << map.index() << ") expanded into multiple dynamic dims (" 1437 << expandedDimStart + dynamicShape.getValue() << "," 1438 << expandedDimStart + dim.index() << ")"; 1439 } 1440 dynamicShape = dim.index(); 1441 } else { 1442 linearizedStaticShape *= dim.value(); 1443 } 1444 } 1445 if (dynamicShape) { 1446 if (!ShapedType::isDynamic(collapsedShape[map.index()])) { 1447 return op->emitOpError("expected dimension ") 1448 << map.index() 1449 << " of collapsed type to be dynamic since one or more of the " 1450 "corresponding dimensions in the expanded type is dynamic"; 1451 } 1452 } else { 1453 if (collapsedShape[map.index()] != linearizedStaticShape) { 1454 return op->emitOpError("expected dimension ") 1455 << map.index() << " of collapsed type to be static value of " 1456 << linearizedStaticShape << " "; 1457 } 1458 } 1459 expandedDimStart += map.value().getNumResults(); 1460 } 1461 return success(); 1462 } 1463 1464 // Common verifier for reshape-like types. Fills `expandedType` and 1465 // `collapsedType` with the proper `src` or `result` type. 1466 template <typename Op, typename T> 1467 static LogicalResult verifyReshapeLikeTypes(Op op, T &expandedType, 1468 T &collapsedType) { 1469 expandedType = op.getSrcType(); 1470 collapsedType = op.getResultType(); 1471 unsigned expandedRank = expandedType.getRank(); 1472 unsigned collapsedRank = collapsedType.getRank(); 1473 bool isCollapse = expandedRank > collapsedRank; 1474 if (!isCollapse) { 1475 std::swap(expandedRank, collapsedRank); 1476 std::swap(expandedType, collapsedType); 1477 } 1478 if (expandedRank == 0) 1479 return op.emitOpError("expected non-zero memref ranks"); 1480 if (expandedRank == collapsedRank) 1481 return op.emitOpError("expected to collapse or expand dims"); 1482 1483 if (collapsedRank == 0) { 1484 // If collapsed rank is 0, then expanded type must be static shaped and of 1485 // sizes 1. 1486 if (llvm::any_of(expandedType.getShape(), 1487 [](int64_t dim) -> bool { return dim != 1; })) 1488 return op.emitOpError( 1489 "invalid to reshape tensor/memref with non-unit extent dimensions to " 1490 "zero-rank tensor/memref"); 1491 return success(); 1492 } 1493 if (collapsedRank != op.reassociation().size()) 1494 return op.emitOpError("expected rank of the collapsed type(") 1495 << collapsedRank << ") to be the number of reassociation maps(" 1496 << op.reassociation().size() << ")"; 1497 auto maps = getAffineMaps(op.reassociation()); 1498 for (auto it : llvm::enumerate(maps)) 1499 if (it.value().getNumDims() != expandedRank) 1500 return op.emitOpError("expected reassociation map #") 1501 << it.index() << " of same rank as expanded memref(" 1502 << expandedRank << "), but got " << it.value().getNumDims(); 1503 int invalidIdx = 0; 1504 if (!isReassociationValid(maps, &invalidIdx)) 1505 return op.emitOpError("expected reassociation map #") 1506 << invalidIdx << " to be valid and contiguous"; 1507 return verifyReshapeLikeShapes(op, collapsedType, expandedType, !isCollapse); 1508 } 1509 1510 static LogicalResult verify(ReshapeOp op) { 1511 MemRefType expandedType, collapsedType; 1512 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 1513 return failure(); 1514 auto maps = getAffineMaps(op.reassociation()); 1515 MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps); 1516 if (collapsedType != expectedType) 1517 return op.emitOpError("expected collapsed type to be ") 1518 << expectedType << ", but got " << collapsedType; 1519 return success(); 1520 } 1521 1522 void ReshapeOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 1523 MLIRContext *context) { 1524 results.insert<CollapseReshapeOps<ReshapeOp>>(context); 1525 } 1526 1527 //===----------------------------------------------------------------------===// 1528 // TensorReshapeOp 1529 //===----------------------------------------------------------------------===// 1530 1531 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`. 1532 static RankedTensorType 1533 computeTensorReshapeCollapsedType(RankedTensorType type, 1534 ArrayRef<AffineMap> reassociation) { 1535 auto shape = type.getShape(); 1536 SmallVector<int64_t, 4> newShape; 1537 newShape.reserve(reassociation.size()); 1538 1539 // Use the fact that reassociation is valid to simplify the logic: only use 1540 // each map's rank. 1541 assert(isReassociationValid(reassociation) && "invalid reassociation"); 1542 unsigned currentDim = 0; 1543 for (AffineMap m : reassociation) { 1544 unsigned dim = m.getNumResults(); 1545 auto band = shape.slice(currentDim, dim); 1546 int64_t size = 1; 1547 if (llvm::is_contained(band, ShapedType::kDynamicSize)) 1548 size = ShapedType::kDynamicSize; 1549 else 1550 for (unsigned d = 0; d < dim; ++d) 1551 size *= shape[currentDim + d]; 1552 newShape.push_back(size); 1553 currentDim += dim; 1554 } 1555 1556 return RankedTensorType::get(newShape, type.getElementType()); 1557 } 1558 1559 void mlir::linalg::TensorReshapeOp::build( 1560 OpBuilder &b, OperationState &result, Value src, 1561 ArrayRef<ReassociationExprs> reassociation, 1562 ArrayRef<NamedAttribute> attrs) { 1563 auto maps = getSymbolLessAffineMaps(reassociation); 1564 auto resultType = computeTensorReshapeCollapsedType( 1565 src.getType().cast<RankedTensorType>(), maps); 1566 build(b, result, resultType, src, attrs); 1567 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 1568 b.getAffineMapArrayAttr(maps)); 1569 } 1570 1571 void mlir::linalg::TensorReshapeOp::build( 1572 OpBuilder &b, OperationState &result, Type resultType, Value src, 1573 ArrayRef<ReassociationExprs> reassociation, 1574 ArrayRef<NamedAttribute> attrs) { 1575 auto maps = getSymbolLessAffineMaps(reassociation); 1576 build(b, result, resultType, src, attrs); 1577 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 1578 b.getAffineMapArrayAttr(maps)); 1579 } 1580 1581 static LogicalResult verify(TensorReshapeOp op) { 1582 RankedTensorType expandedType, collapsedType; 1583 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 1584 return failure(); 1585 auto maps = getAffineMaps(op.reassociation()); 1586 RankedTensorType expectedType = 1587 computeTensorReshapeCollapsedType(expandedType, maps); 1588 if (collapsedType != expectedType) 1589 return op.emitOpError("expected collapsed type to be ") 1590 << expectedType << ", but got " << collapsedType; 1591 return success(); 1592 } 1593 1594 namespace { 1595 /// Reshape of a splat constant can be replaced with a constant of the result 1596 /// type. 1597 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> { 1598 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1599 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1600 PatternRewriter &rewriter) const override { 1601 DenseElementsAttr attr; 1602 if (!matchPattern(reshapeOp.src(), m_Constant(&attr))) 1603 return failure(); 1604 if (!attr || !attr.isSplat()) 1605 return failure(); 1606 DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer( 1607 reshapeOp.getResultType(), attr.getRawData(), true); 1608 rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr); 1609 return success(); 1610 } 1611 }; 1612 1613 /// Canonicalize dim ops that use the output shape with dim of the input. 1614 struct ReplaceDimOfReshapeOpResult : OpRewritePattern<DimOp> { 1615 using OpRewritePattern<DimOp>::OpRewritePattern; 1616 LogicalResult matchAndRewrite(DimOp dimOp, 1617 PatternRewriter &rewriter) const override { 1618 Value dimValue = dimOp.memrefOrTensor(); 1619 Optional<int64_t> dimIndex = dimOp.getConstantIndex(); 1620 if (!dimIndex) 1621 return failure(); 1622 1623 auto reshapeOp = dimValue.getDefiningOp<TensorReshapeOp>(); 1624 if (!reshapeOp) 1625 return failure(); 1626 1627 rewriter.replaceOp(dimOp, 1628 getReshapeOutputDimFromInputShape( 1629 rewriter, dimOp.getLoc(), *dimIndex, reshapeOp.src(), 1630 reshapeOp.getResultType().getShape(), 1631 reshapeOp.getReassociationMaps())); 1632 return success(); 1633 } 1634 }; 1635 } // namespace 1636 1637 void TensorReshapeOp::getCanonicalizationPatterns( 1638 OwningRewritePatternList &results, MLIRContext *context) { 1639 results.insert<CollapseReshapeOps<TensorReshapeOp>, FoldReshapeWithConstant, 1640 ReplaceDimOfReshapeOpResult>(context); 1641 } 1642 1643 //===----------------------------------------------------------------------===// 1644 // YieldOp 1645 //===----------------------------------------------------------------------===// 1646 1647 static void print(OpAsmPrinter &p, linalg::YieldOp op) { 1648 p << op.getOperationName(); 1649 if (op.getNumOperands() > 0) 1650 p << ' ' << op.getOperands(); 1651 p.printOptionalAttrDict(op.getAttrs()); 1652 if (op.getNumOperands() > 0) 1653 p << " : " << op.getOperandTypes(); 1654 } 1655 1656 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 1657 SmallVector<OpAsmParser::OperandType, 2> opInfo; 1658 SmallVector<Type, 2> types; 1659 llvm::SMLoc loc = parser.getCurrentLocation(); 1660 return failure(parser.parseOperandList(opInfo) || 1661 parser.parseOptionalAttrDict(result.attributes) || 1662 (!opInfo.empty() && parser.parseColonTypeList(types)) || 1663 parser.resolveOperands(opInfo, types, loc, result.operands)); 1664 } 1665 1666 // Check the operand number and types must match the element types of the 1667 // LinalgOp interface's shaped operands. 1668 static LogicalResult verifyYield(linalg::YieldOp op, 1669 LinalgOp linalgOpInterface) { 1670 auto nOutputs = linalgOpInterface.getNumOutputs(); 1671 if (op.getNumOperands() != nOutputs) 1672 return op.emitOpError("expected number of yield values (") 1673 << nOutputs << ") to match the number of operands of the enclosing " 1674 << "LinalgOp (" << op.getNumOperands() << ")"; 1675 1676 for (unsigned i = 0; i != nOutputs; ++i) { 1677 auto elementType = 1678 linalgOpInterface.getOutputShapedType(i).getElementType(); 1679 if (op.getOperand(i).getType() != elementType) 1680 return op.emitOpError("type of yield operand ") 1681 << (i + 1) << " (" << op.getOperand(i).getType() 1682 << ") doesn't match " 1683 << "the element type of the enclosing linalg.generic op (" 1684 << elementType << ")"; 1685 } 1686 return success(); 1687 } 1688 1689 static LogicalResult verify(linalg::YieldOp op) { 1690 auto *parentOp = op->getParentOp(); 1691 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1692 return op.emitOpError("expected single non-empty parent region"); 1693 1694 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1695 return verifyYield(op, cast<LinalgOp>(parentOp)); 1696 1697 if (auto padTensorOp = dyn_cast<linalg::PadTensorOp>(parentOp)) { 1698 if (op.getNumOperands() != 1) 1699 return op.emitOpError("expected single yield operand (got ") 1700 << op->getNumOperands() << ")"; 1701 if (op.getOperand(0).getType() != 1702 padTensorOp.getType().cast<ShapedType>().getElementType()) 1703 return op.emitOpError("expected yield type to match shape element type"); 1704 return success(); 1705 } 1706 1707 return op.emitOpError("expected parent op with LinalgOp interface"); 1708 } 1709 1710 /////// Operations corresponding to library calls defined with Tablegen //////// 1711 1712 template <typename LinalgPoolingOp> 1713 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 1714 ArrayRef<Attribute> attrs, 1715 bool isStride) { 1716 auto strideOrDilation = isStride ? "stride" : "dilation"; 1717 if (attrs.size() != op.getNumWindowLoops()) 1718 return op.emitOpError("expects num ") 1719 << strideOrDilation 1720 << "s equal to number of window dimensions: " << attrs.size() 1721 << " vs " << op.getNumWindowLoops(); 1722 return success(); 1723 } 1724 1725 void ConvOp::getEffects( 1726 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> 1727 &effects) { 1728 effects.emplace_back(MemoryEffects::Read::get(), input(), 1729 SideEffects::DefaultResource::get()); 1730 effects.emplace_back(MemoryEffects::Read::get(), filter(), 1731 SideEffects::DefaultResource::get()); 1732 effects.emplace_back(MemoryEffects::Write::get(), output(), 1733 SideEffects::DefaultResource::get()); 1734 } 1735 1736 static LogicalResult verify(ConvOp op) { 1737 auto oType = op.output().getType().cast<MemRefType>(); 1738 auto fType = op.filter().getType().cast<MemRefType>(); 1739 auto iType = op.input().getType().cast<MemRefType>(); 1740 if (oType.getElementType() != iType.getElementType() || 1741 oType.getElementType() != fType.getElementType()) 1742 return op.emitOpError("expects memref elemental types to match"); 1743 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 1744 return op.emitOpError("expects memref ranks to match"); 1745 if (auto strides = op.strides()) { 1746 if (failed( 1747 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1748 return failure(); 1749 } 1750 if (auto dilations = op.dilations()) { 1751 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1752 /*isStride=*/false))) 1753 return failure(); 1754 } 1755 return success(); 1756 } 1757 1758 template <typename PoolingOp> 1759 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 1760 auto inputType = op.input().getType().template cast<MemRefType>(); 1761 auto outputType = op.output().getType().template cast<MemRefType>(); 1762 if (outputType.getElementType() != inputType.getElementType()) 1763 return op.emitOpError("expects memref elemental types to match"); 1764 1765 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 1766 if (outputType.getRank() != inputType.getRank() || 1767 outputType.getRank() != windowDimsType.getRank()) 1768 return op.emitOpError("expects memref ranks to match"); 1769 1770 if (auto strides = op.strides()) { 1771 if (failed( 1772 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1773 return failure(); 1774 } 1775 if (auto dilations = op.dilations()) { 1776 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1777 /*isStride=*/false))) 1778 return failure(); 1779 } 1780 return success(); 1781 } 1782 1783 #define DEFINE_POOLING_OP_GET_EFFECTS(OP_NAME) \ 1784 void OP_NAME::getEffects( \ 1785 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>> \ 1786 &effects) { \ 1787 effects.emplace_back(MemoryEffects::Read::get(), input(), \ 1788 SideEffects::DefaultResource::get()); \ 1789 effects.emplace_back(MemoryEffects::Write::get(), output(), \ 1790 SideEffects::DefaultResource::get()); \ 1791 } 1792 1793 static LogicalResult verify(PoolingMaxOp op) { 1794 return verifySingleInputPoolingOp(op); 1795 } 1796 static LogicalResult verify(PoolingMinOp op) { 1797 return verifySingleInputPoolingOp(op); 1798 } 1799 static LogicalResult verify(PoolingSumOp op) { 1800 return verifySingleInputPoolingOp(op); 1801 } 1802 1803 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMaxOp) 1804 DEFINE_POOLING_OP_GET_EFFECTS(PoolingMinOp) 1805 DEFINE_POOLING_OP_GET_EFFECTS(PoolingSumOp) 1806 1807 namespace { 1808 struct EraseDeadLinalgOp; 1809 struct FoldTensorCastOp; 1810 } // namespace 1811 1812 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.cpp.inc" 1813 1814 #define GET_OP_CLASSES 1815 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 1816 1817 #define GET_OP_CLASSES 1818 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 1819 1820 #define GET_OP_CLASSES 1821 #include "mlir/Dialect/Linalg/IR/LinalgSparseOps.cpp.inc" 1822 1823 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 1824 /// Assumes `op` is a LinalgOp. 1825 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 1826 SmallVectorImpl<AffineExpr> &res) { 1827 if (!cast<LinalgOp>(op).iterator_types()) 1828 return; 1829 1830 unsigned dim = 0; 1831 MLIRContext *ctx = op->getContext(); 1832 for (auto tn : 1833 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 1834 if (tn == iteratorTypeName) 1835 res.push_back(getAffineDimExpr(dim, ctx)); 1836 ++dim; 1837 } 1838 } 1839 1840 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 1841 unsigned rank, 1842 MLIRContext *context) { 1843 if (maybeMap) 1844 return maybeMap.getValue(); 1845 if (rank == 0) 1846 return AffineMap::get(context); 1847 return AffineMap::getMultiDimIdentityMap(rank, context); 1848 } 1849 1850 SmallVector<AffineExpr, 4> 1851 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 1852 MLIRContext *context) { 1853 SmallVector<AffineExpr, 4> res; 1854 res.reserve(num); 1855 for (unsigned i = 0; i < num; ++i) 1856 res.push_back(getAffineDimExpr(startIdx++, context)); 1857 return res; 1858 } 1859 1860 template <typename PoolingOp> 1861 SmallVector<AffineExpr, 4> 1862 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 1863 ArrayRef<AffineExpr> outputDims, 1864 ArrayRef<AffineExpr> windowDims) { 1865 assert(outputDims.size() == windowDims.size()); 1866 SmallVector<AffineExpr, 4> res; 1867 res.reserve(outputDims.size()); 1868 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 1869 // TODO: add a level of indirection to linalg.generic. 1870 auto expr = op.getStride(i) * outputDims[i] + 1871 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 1872 res.push_back(expr); 1873 } 1874 return res; 1875 } 1876 1877 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 1878 template SmallVector<AffineExpr, 4> \ 1879 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 1880 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 1881 ArrayRef<AffineExpr> windowDims); 1882 1883 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 1884 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 1885 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 1886 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 1887 1888 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 1889 ArrayRef<AffineExpr> b) { 1890 auto rangeA = llvm::make_range(a.begin(), a.end()); 1891 auto rangeB = llvm::make_range(b.begin(), b.end()); 1892 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 1893 return llvm::to_vector<4>(concatRanges); 1894 } 1895 1896 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 1897 if (auto memref = t.dyn_cast<MemRefType>()) { 1898 ss << "view"; 1899 for (auto size : memref.getShape()) 1900 if (size < 0) 1901 ss << "sx"; 1902 else 1903 ss << size << "x"; 1904 appendMangledType(ss, memref.getElementType()); 1905 } else if (auto vec = t.dyn_cast<VectorType>()) { 1906 ss << "vector"; 1907 llvm::interleave( 1908 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 1909 appendMangledType(ss, vec.getElementType()); 1910 } else if (t.isSignlessIntOrIndexOrFloat()) { 1911 ss << t; 1912 } else { 1913 llvm_unreachable("Invalid type for linalg library name mangling"); 1914 } 1915 } 1916 1917 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 1918 assert(isa<LinalgOp>(op)); 1919 std::string name(op->getName().getStringRef().str()); 1920 name.reserve(128); 1921 std::replace(name.begin(), name.end(), '.', '_'); 1922 llvm::raw_string_ostream ss(name); 1923 ss << "_"; 1924 auto types = op->getOperandTypes(); 1925 llvm::interleave( 1926 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 1927 [&]() { ss << "_"; }); 1928 return ss.str(); 1929 } 1930 1931 // TODO: Consider making all this boilerplate easy to autogenerate 1932 // with Tablegen. This seems a desirable property in the context of 1933 // OpInterfaces where a Linalg "named" op **isa** LinalgOp. 1934 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) { 1935 if (succeeded(foldMemRefCast(*this))) 1936 return getResult(); 1937 return foldReshapeOp(*this, operands); 1938 } 1939 OpFoldResult TensorReshapeOp::fold(ArrayRef<Attribute> operands) { 1940 return foldReshapeOp(*this, operands); 1941 } 1942 1943 //===----------------------------------------------------------------------===// 1944 // Support for named Linalg ops defined in ods-gen. 1945 //===----------------------------------------------------------------------===// 1946 1947 /// Generic entry point to create the block for the region of a LinalgOp. 1948 /// This is used by both named structured ops created by ods-gen and by manually 1949 /// defined C++ ops. 1950 /// This is used by both builders and parsers. 1951 /// This function creates the block in the region with arguments corresponding 1952 /// to the elemental types of `inputTypes` and `outputTypes`, which are asserted 1953 /// to be ShapedType. 1954 template <typename NamedStructuredOpType> 1955 static void 1956 fillStructuredOpRegion(OpBuilder &opBuilder, Region ®ion, 1957 TypeRange inputTypes, TypeRange outputTypes, 1958 ValueRange captures, 1959 std::function<void(unsigned, unsigned)> errorHandler) { 1960 assert(llvm::all_of(inputTypes, [](Type t) { return t.isa<ShapedType>(); })); 1961 assert(llvm::all_of(outputTypes, [](Type t) { return t.isa<ShapedType>(); })); 1962 1963 // TODO: atm all operands go through getElementTypeOrSelf, 1964 // reconsider when we have evidence we need to. 1965 SmallVector<Type, 8> argTypes; 1966 for (auto containers : {inputTypes, outputTypes}) 1967 for (auto t : containers) 1968 argTypes.push_back(getElementTypeOrSelf(t)); 1969 1970 // RAII. 1971 OpBuilder::InsertionGuard guard(opBuilder); 1972 Block *body = opBuilder.createBlock(®ion, /*insertPt=*/{}, argTypes); 1973 unsigned actual = body->getNumArguments(); 1974 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 1975 if (expected != actual) { 1976 if (errorHandler) errorHandler(expected, actual); 1977 return; 1978 } 1979 1980 opBuilder.setInsertionPointToStart(body); 1981 mlir::edsc::ScopedContext scope(opBuilder, opBuilder.getUnknownLoc()); 1982 NamedStructuredOpType::regionBuilder(*body, captures); 1983 1984 // indexing_maps is an auto-generated method. 1985 1986 // iterator_types is an auto-generated method. 1987 } 1988 1989 /// Generic entry point to create both the region and the block of a LinalgOp. 1990 template <typename NamedStructuredOpType> 1991 void createAndFillStructuredOpRegion(OpBuilder &opBuilder, 1992 OperationState &result, 1993 TypeRange inputTypes, 1994 TypeRange outputTypes, 1995 ValueRange captures) { 1996 Region ®ion = *result.addRegion(); 1997 fillStructuredOpRegion<NamedStructuredOpType>( 1998 opBuilder, region, inputTypes, outputTypes, captures, 1999 [&](unsigned expected, unsigned actual) { 2000 assert(expected != actual && "incorrect number of arguments"); 2001 }); 2002 } 2003 2004 /// Common parsing used for both named structured ops created by ods-gen and by 2005 /// manually defined C++ ops. Does not handle regions. 2006 static ParseResult 2007 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 2008 SmallVectorImpl<Type> &inputTypes, 2009 SmallVectorImpl<Type> &outputTypes) { 2010 llvm::SMLoc inputsOperandsLoc, outputsOperandsLoc; 2011 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, outputsOperands; 2012 2013 parser.parseOptionalAttrDict(result.attributes); 2014 2015 if (succeeded(parser.parseOptionalKeyword("ins"))) { 2016 if (parser.parseLParen()) 2017 return failure(); 2018 2019 inputsOperandsLoc = parser.getCurrentLocation(); 2020 if (parser.parseOperandList(inputsOperands) || 2021 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 2022 return failure(); 2023 } 2024 2025 if (succeeded(parser.parseOptionalKeyword("outs"))) { 2026 outputsOperandsLoc = parser.getCurrentLocation(); 2027 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) || 2028 parser.parseColonTypeList(outputTypes) || parser.parseRParen()) 2029 return failure(); 2030 } 2031 2032 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 2033 result.operands) || 2034 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc, 2035 result.operands)) 2036 return failure(); 2037 2038 result.addAttribute("operand_segment_sizes", 2039 parser.getBuilder().getI32VectorAttr( 2040 {static_cast<int32_t>(inputsOperands.size()), 2041 static_cast<int32_t>(outputsOperands.size())})); 2042 return success(); 2043 } 2044 2045 template <typename NamedStructuredOpType> 2046 static void printCommonStructuredOpParts(OpAsmPrinter &p, 2047 NamedStructuredOpType op) { 2048 if (!op.inputs().empty()) 2049 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 2050 if (!op.outputs().empty()) 2051 p << " outs(" << op.outputs() << " : " << op.outputs().getTypes() << ")"; 2052 } 2053 2054 //===----------------------------------------------------------------------===// 2055 // Specific parsing and printing for named structured ops created by ods-gen. 2056 //===----------------------------------------------------------------------===// 2057 2058 template <typename NamedStructuredOpType> 2059 static ParseResult 2060 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 2061 TypeRange inputTypes, TypeRange outputTypes, 2062 ArrayRef<OpAsmParser::OperandType> captures) { 2063 ParseResult res = success(); 2064 OpBuilder opBuilder(parser.getBuilder().getContext()); 2065 // Resolve `captures` into `capturedValues` at parse time so we can build the 2066 // region with captures. 2067 SmallVector<Value> capturedValues; 2068 fillStructuredOpRegion<NamedStructuredOpType>( 2069 opBuilder, region, inputTypes, outputTypes, capturedValues, 2070 [&](unsigned expected, unsigned actual) { 2071 res = parser.emitError( 2072 parser.getCurrentLocation(), 2073 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated " 2074 "region expects {0} args, got {1}", 2075 expected, actual)); 2076 region.front().dump(); 2077 }); 2078 return res; 2079 } 2080 2081 static ParseResult 2082 parseNamedStructuredOpResults(OpAsmParser &parser, 2083 SmallVectorImpl<Type> &resultTypes) { 2084 if (succeeded(parser.parseOptionalArrow())) 2085 if (parser.parseTypeList(resultTypes)) 2086 return failure(); 2087 return success(); 2088 } 2089 2090 template <typename NamedStructuredOpType> 2091 static ParseResult 2092 parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, 2093 ArrayRef<OpAsmParser::OperandType> captures) { 2094 // TODO: Enable when ods-gen supports captures. 2095 assert(captures.empty() && "unexpected captures for named structured ops"); 2096 SmallVector<Type, 1> inputTypes, outputTypes; 2097 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes)) 2098 return failure(); 2099 2100 // TODO: consider merging results parsing into region parsing. 2101 // Need to wait for declarative assembly resolution to decide. 2102 SmallVector<Type, 1> outputTensorsTypes; 2103 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 2104 return failure(); 2105 result.addTypes(outputTensorsTypes); 2106 2107 std::unique_ptr<Region> region = std::make_unique<Region>(); 2108 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 2109 parser, *region, inputTypes, outputTypes, captures)) 2110 return failure(); 2111 result.addRegion(std::move(region)); 2112 2113 return success(); 2114 } 2115 2116 static void printNamedStructuredOpResults(OpAsmPrinter &p, 2117 TypeRange resultTypes) { 2118 if (resultTypes.empty()) 2119 return; 2120 p.printOptionalArrowTypeList(resultTypes); 2121 } 2122 2123 template <typename NamedStructuredOpType> 2124 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 2125 p << op.getOperationName(); 2126 p.printOptionalAttrDict(op.getAttrs(), 2127 /*elidedAttrs=*/{"operand_segment_sizes"}); 2128 2129 // Printing is shared with generic ops, except for the region and 2130 // attributes. 2131 printCommonStructuredOpParts(p, op); 2132 2133 // Results printing. 2134 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 2135 2136 // Region is elided. 2137 } 2138 2139 template <typename NamedStructuredOpType> 2140 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 2141 return verifyGenericOp<NamedStructuredOpType>(op); 2142 } 2143 2144 //===----------------------------------------------------------------------===// 2145 // Canonicalizers and Folders. 2146 //===----------------------------------------------------------------------===// 2147 2148 namespace { 2149 struct EraseDeadLinalgOp : public RewritePattern { 2150 EraseDeadLinalgOp(PatternBenefit benefit = 1) 2151 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2152 2153 LogicalResult matchAndRewrite(Operation *op, 2154 PatternRewriter &rewriter) const override { 2155 auto linalgOp = dyn_cast<LinalgOp>(op); 2156 if (!linalgOp) 2157 return failure(); 2158 for (Value v : linalgOp.getShapedOperands()) { 2159 // Linalg "inputs" may be either tensor or memref type. 2160 // tensor<0xelt_type> is a convention that may not always mean 2161 // "0 iterations". Only erase in cases we see memref<...x0x...>. 2162 auto mt = v.getType().dyn_cast<MemRefType>(); 2163 if (!mt) 2164 continue; 2165 if (llvm::is_contained(mt.getShape(), 0)) { 2166 rewriter.eraseOp(linalgOp); 2167 return success(); 2168 } 2169 } 2170 return failure(); 2171 } 2172 }; 2173 2174 struct FoldTensorCastOp : public RewritePattern { 2175 FoldTensorCastOp(PatternBenefit benefit = 1) 2176 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2177 2178 LogicalResult matchAndRewrite(Operation *op, 2179 PatternRewriter &rewriter) const override { 2180 auto linalgOp = dyn_cast<LinalgOp>(op); 2181 if (!linalgOp) 2182 return failure(); 2183 2184 // If no operand comes from a tensor::CastOp and can be folded then fail. 2185 bool hasTensorCastOperand = 2186 llvm::any_of(linalgOp.getShapedOperands(), [&](Value v) { 2187 if (v.isa<BlockArgument>()) 2188 return false; 2189 auto castOp = v.getDefiningOp<tensor::CastOp>(); 2190 return castOp && canFoldIntoConsumerOp(castOp); 2191 }); 2192 if (!hasTensorCastOperand) 2193 return failure(); 2194 2195 SmallVector<Type, 4> newResultTypes; 2196 newResultTypes.reserve(op->getNumResults()); 2197 SmallVector<Value, 4> newOperands; 2198 newOperands.reserve(op->getNumOperands()); 2199 // Inputs may fold. 2200 for (Value v : linalgOp.getInputs()) { 2201 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2202 newOperands.push_back( 2203 canFoldIntoConsumerOp(tensorCastOp) ? tensorCastOp.source() : v); 2204 } 2205 // Init tensors may fold, in which case the resultType must also change. 2206 for (Value v : linalgOp.getOutputs()) { 2207 auto tensorCastOp = v.getDefiningOp<tensor::CastOp>(); 2208 bool fold = canFoldIntoConsumerOp(tensorCastOp); 2209 newOperands.push_back(fold ? tensorCastOp.getOperand() : v); 2210 newResultTypes.push_back(newOperands.back().getType()); 2211 } 2212 auto extraOperands = linalgOp.getAssumedNonShapedOperands(); 2213 newOperands.append(extraOperands.begin(), extraOperands.end()); 2214 // Clone op. 2215 Operation *newOp = 2216 linalgOp.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 2217 rewriter.replaceOp(op, newOp->getResults()); 2218 2219 return success(); 2220 } 2221 }; 2222 2223 /// Replaces std.dim operations that use the result of a LinalgOp (on tensors) 2224 /// with std.dim operations that use one of the arguments. For example, 2225 /// 2226 /// %0 = linalg.matmul ins(%arg0, %arg1, ...) 2227 /// %1 = dim %0, %c0 2228 /// 2229 /// with 2230 /// 2231 /// %1 = dim %arg0, %c0 2232 /// 2233 /// where possible. With this the result of the `linalg.matmul` is not used in 2234 /// dim operations. If the value produced is replaced with another value (say by 2235 /// tiling `linalg.matmul`) will make the `linalg.matmul` truly dead instead of 2236 /// used in a dim op that would prevent the DCE of this op. 2237 struct ReplaceDimOfLinalgOpResult : public OpRewritePattern<DimOp> { 2238 using OpRewritePattern<DimOp>::OpRewritePattern; 2239 2240 LogicalResult matchAndRewrite(DimOp dimOp, 2241 PatternRewriter &rewriter) const override { 2242 Value dimValue = dimOp.memrefOrTensor(); 2243 Optional<int64_t> dimIndex = dimOp.getConstantIndex(); 2244 if (!dimIndex) 2245 return failure(); 2246 auto linalgOp = dimValue.getDefiningOp<LinalgOp>(); 2247 if (!linalgOp) 2248 return failure(); 2249 2250 unsigned resultIndex = dimValue.cast<OpResult>().getResultNumber(); 2251 Optional<Value> operandDimValue = linalgOp.inferResultDimFromInputShapes( 2252 rewriter, dimOp.getLoc(), resultIndex, 2253 static_cast<unsigned>(*dimIndex)); 2254 if (!operandDimValue) { 2255 // Its always possible to replace using the corresponding `outs` 2256 // parameter. 2257 operandDimValue = rewriter.create<DimOp>( 2258 dimOp.getLoc(), linalgOp.getOutput(resultIndex), *dimIndex); 2259 } 2260 rewriter.replaceOp(dimOp, *operandDimValue); 2261 return success(); 2262 } 2263 }; 2264 2265 } // namespace 2266 2267 namespace { 2268 // Deduplicate redundant args of a linalg op. 2269 // An arg is redundant if it has the same Value and indexing map as another. 2270 struct DeduplicateInputs : public RewritePattern { 2271 DeduplicateInputs(PatternBenefit benefit = 1) 2272 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2273 2274 LogicalResult matchAndRewrite(Operation *op, 2275 PatternRewriter &rewriter) const override { 2276 // This pattern reduces the number of arguments of an op, which breaks 2277 // the invariants of semantically charged named ops. 2278 if (!isa<GenericOp, IndexedGenericOp>(op)) 2279 return failure(); 2280 auto linalgOp = cast<LinalgOp>(op); 2281 2282 // Associate each input to an equivalent "canonical" input that has the same 2283 // Value and indexing map. 2284 // 2285 // In the non-duplicate case, input `i` will have canonical input `i`. But 2286 // in the case of duplicated inputs, the canonical input could be some other 2287 // input `< i`. That is, a later input will have some earlier input as its 2288 // canonical input. 2289 llvm::SmallDenseMap<std::pair<Value, AffineMap>, int> canonicalInput; 2290 // For later remapping tasks like deduplicating payload block arguments, 2291 // having a simple "inputIndex -> canonicalInputIndex" integer mapping is 2292 // convenient. 2293 SmallVector<int, 6> canonicalInputIndices; 2294 for (int i = 0, e = linalgOp.getNumInputs(); i != e; i++) { 2295 Value input = linalgOp.getInput(i); 2296 AffineMap indexingMap = linalgOp.getInputIndexingMap(i); 2297 // STL-like maps have a convenient behavior for our use case here. In the 2298 // case of duplicate keys, the insertion is rejected, and the returned 2299 // iterator gives access to the value already in the map. 2300 auto pair = canonicalInput.insert({{input, indexingMap}, i}); 2301 canonicalInputIndices.push_back(pair.first->second); 2302 } 2303 2304 // If there are no duplicate args, then bail out. 2305 if (canonicalInput.size() == linalgOp.getNumInputs()) 2306 return failure(); 2307 2308 // The operands for the newly canonicalized op. 2309 SmallVector<Value, 6> newOperands; 2310 for (auto v : llvm::enumerate(linalgOp.getInputs())) 2311 if (canonicalInputIndices[v.index()] == static_cast<int>(v.index())) 2312 newOperands.push_back(v.value()); 2313 llvm::append_range(newOperands, linalgOp.getOutputs()); 2314 llvm::append_range(newOperands, linalgOp.getAssumedNonShapedOperands()); 2315 2316 // Clone the old op with new operands. 2317 Operation *newOp = linalgOp.clone(rewriter, op->getLoc(), 2318 op->getResultTypes(), newOperands); 2319 auto newLinalgOp = cast<LinalgOp>(newOp); 2320 2321 // Repair the indexing maps by filtering out the ones that have been 2322 // eliminated. 2323 SmallVector<AffineMap, 6> newIndexingMaps; 2324 for (int i = 0, e = newLinalgOp.getNumInputs(); i != e; i++) 2325 if (canonicalInputIndices[i] == i) 2326 newIndexingMaps.push_back(newLinalgOp.getIndexingMap(i)); 2327 for (int i = 0, e = newLinalgOp.getNumOutputs(); i != e; i++) 2328 newIndexingMaps.push_back(newLinalgOp.getOutputIndexingMap(i)); 2329 newOp->setAttr("indexing_maps", 2330 rewriter.getAffineMapArrayAttr(newIndexingMaps)); 2331 2332 // Set the number of inputs to the new value. The `clone` call above kept 2333 // the value from the original op. 2334 newLinalgOp.setNumInputs(canonicalInput.size()); 2335 2336 // linalg.indexed_generic payloads have additional arguments prepended to 2337 // the block arg list. 2338 int bbArgBaseOffset = newLinalgOp.getNumPayloadInductionVariables(); 2339 2340 // Repair the payload entry block by RAUW'ing redundant arguments and 2341 // erasing them. 2342 Block &payload = newOp->getRegion(0).front(); 2343 for (int i = 0, e = linalgOp.getNumInputs(); i < e; i++) { 2344 // Iterate in reverse, so that we erase later args first, preventing the 2345 // argument list from shifting unexpectedly and invalidating all our 2346 // indices. 2347 int reversed = e - i - 1; 2348 int canonicalIndex = canonicalInputIndices[reversed]; 2349 if (canonicalInputIndices[reversed] == reversed) 2350 continue; 2351 payload.getArgument(bbArgBaseOffset + reversed) 2352 .replaceAllUsesWith( 2353 payload.getArgument(bbArgBaseOffset + canonicalIndex)); 2354 payload.eraseArgument(bbArgBaseOffset + reversed); 2355 } 2356 2357 rewriter.replaceOp(op, newOp->getResults()); 2358 return success(); 2359 } 2360 }; 2361 2362 /// Remove generic/indexed_generic operations (on tensors) that are just copying 2363 /// the values from inputs to the results. Requirements are 2364 /// 1) All iterator types are parallel 2365 /// 2) The body contains just a yield operation with the yielded values being 2366 /// the arguments corresponding to the operands. 2367 struct RemoveIdentityLinalgOps : public RewritePattern { 2368 RemoveIdentityLinalgOps(PatternBenefit benefit = 1) 2369 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 2370 2371 LogicalResult matchAndRewrite(Operation *op, 2372 PatternRewriter &rewriter) const override { 2373 if (!isa<GenericOp, IndexedGenericOp>(op)) 2374 return failure(); 2375 LinalgOp genericOp = cast<LinalgOp>(op); 2376 if (!genericOp.hasTensorSemantics()) 2377 return failure(); 2378 // Check all indexing maps are identity. 2379 if (llvm::any_of(genericOp.getIndexingMaps(), 2380 [](AffineMap map) { return !map.isIdentity(); })) 2381 return failure(); 2382 2383 // Check that the body of the linalg operation is just a linalg.yield 2384 // operation. 2385 Block &body = op->getRegion(0).front(); 2386 if (!llvm::hasSingleElement(body)) 2387 return failure(); 2388 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator()); 2389 if (!yieldOp) 2390 return failure(); 2391 2392 // Get the argument number of the returned values. That is the operand 2393 // number to use for replacing uses of this operation. 2394 unsigned numIndexArgs = genericOp.getNumPayloadInductionVariables(); 2395 SmallVector<Value, 4> returnedArgs; 2396 for (Value yieldVal : yieldOp.values()) { 2397 auto yieldArg = yieldVal.dyn_cast<BlockArgument>(); 2398 if (!yieldArg || yieldArg.getOwner() != &body) 2399 return failure(); 2400 unsigned argumentNumber = yieldArg.getArgNumber(); 2401 if (argumentNumber < numIndexArgs) 2402 return failure(); 2403 returnedArgs.push_back(op->getOperand(argumentNumber - numIndexArgs)); 2404 } 2405 if (returnedArgs.size() != genericOp.getOperation()->getNumResults()) 2406 return failure(); 2407 rewriter.replaceOp(genericOp, returnedArgs); 2408 return success(); 2409 } 2410 }; 2411 } // namespace 2412 2413 #define CANONICALIZERS_AND_FOLDERS(XXX) \ 2414 void XXX::getCanonicalizationPatterns(OwningRewritePatternList &results, \ 2415 MLIRContext *context) { \ 2416 results.insert<DeduplicateInputs, EraseDeadLinalgOp, FoldTensorCastOp, \ 2417 RemoveIdentityLinalgOps>(); \ 2418 results.insert<ReplaceDimOfLinalgOpResult>(context); \ 2419 } \ 2420 \ 2421 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 2422 SmallVectorImpl<OpFoldResult> &) { \ 2423 return foldMemRefCast(*this); \ 2424 } 2425 2426 CANONICALIZERS_AND_FOLDERS(ConvOp) 2427 CANONICALIZERS_AND_FOLDERS(PoolingMaxOp) 2428 CANONICALIZERS_AND_FOLDERS(PoolingMinOp) 2429 CANONICALIZERS_AND_FOLDERS(PoolingSumOp) 2430 CANONICALIZERS_AND_FOLDERS(CopyOp) 2431 CANONICALIZERS_AND_FOLDERS(FillOp) 2432 CANONICALIZERS_AND_FOLDERS(GenericOp) 2433 CANONICALIZERS_AND_FOLDERS(IndexedGenericOp) 2434 2435 // All named ops canonicalizers and folders are auto-generated in the 2436 // .cpp.inc. 2437