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