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 #include "mlir/Dialect/Linalg/EDSC/Intrinsics.h" 15 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 16 #include "mlir/Dialect/StandardOps/IR/Ops.h" 17 #include "mlir/IR/AffineExpr.h" 18 #include "mlir/IR/AffineMap.h" 19 #include "mlir/IR/Builders.h" 20 #include "mlir/IR/Function.h" 21 #include "mlir/IR/Matchers.h" 22 #include "mlir/IR/Module.h" 23 #include "mlir/IR/OpImplementation.h" 24 #include "mlir/IR/PatternMatch.h" 25 #include "mlir/IR/StandardTypes.h" 26 #include "mlir/Support/LLVM.h" 27 28 #include "llvm/ADT/SetVector.h" 29 #include "llvm/ADT/StringSet.h" 30 #include "llvm/Support/FormatVariadic.h" 31 #include "llvm/Support/MathExtras.h" 32 #include "llvm/Support/raw_ostream.h" 33 34 using namespace mlir; 35 using namespace mlir::linalg; 36 37 /// Forward declarations. 38 template <typename NamedStructuredOpType> 39 static void buildNamedStructuredOpRegionAndAttributes( 40 OpBuilder &opBuilder, OperationState &result, TypeRange inputTypes, 41 TypeRange outputBufferTypes, TypeRange initTensorTypes, 42 TypeRange resultTypes); 43 44 static ParseResult 45 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 46 SmallVectorImpl<Type> &inputTypes, 47 SmallVectorImpl<Type> &outputBufferTypes, 48 SmallVectorImpl<Type> &initTensorTypes); 49 50 template <typename NamedStructuredOpType> 51 static ParseResult 52 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 53 TypeRange inputTypes, TypeRange outputBufferTypes, 54 TypeRange initTensorTypes, TypeRange resultTypes); 55 static ParseResult 56 parseNamedStructuredOpResults(OpAsmParser &parser, 57 SmallVectorImpl<Type> &resultTypes); 58 59 template <typename NamedStructuredOpType> 60 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 61 OperationState &result); 62 63 template <typename NamedStructuredOpType> 64 static void printCommonStructuredOpParts(OpAsmPrinter &p, 65 NamedStructuredOpType op); 66 67 static void printNamedStructuredOpResults(OpAsmPrinter &p, 68 TypeRange resultTypes); 69 70 template <typename NamedStructuredOpType> 71 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op); 72 73 template <typename NamedStructuredOpType> 74 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op); 75 76 /// This is a common class used for patterns of the form 77 /// ``` 78 /// someop(memrefcast) -> someop 79 /// ``` 80 /// It folds the source of the memref_cast into the root operation directly. 81 static LogicalResult foldMemRefCast(Operation *op) { 82 bool folded = false; 83 for (OpOperand &operand : op->getOpOperands()) { 84 auto castOp = operand.get().getDefiningOp<MemRefCastOp>(); 85 if (castOp && canFoldIntoConsumerOp(castOp)) { 86 operand.set(castOp.getOperand()); 87 folded = true; 88 } 89 } 90 return success(folded); 91 } 92 93 ///////////////////// Operations defined with Tablegen ///////////////////////// 94 // For such operations that do not correspond to library calls (i.e. defined in 95 // LinalgOps.td), we define an overloaded `print` function and a 96 // parse`className` function. 97 98 //===----------------------------------------------------------------------===// 99 // GenericOps 100 //===----------------------------------------------------------------------===// 101 void GenericOp::build( 102 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 103 ValueRange inputs, ValueRange outputBuffers, ValueRange initTensors, 104 ArrayRef<AffineMap> indexingMaps, ArrayRef<StringRef> iteratorTypes, 105 StringRef doc, StringRef libraryCall, IntegerAttr symbolSource, 106 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 107 build(builder, result, resultTensorTypes, inputs, outputBuffers, initTensors, 108 builder.getAffineMapArrayAttr(indexingMaps), 109 builder.getStrArrayAttr(iteratorTypes), 110 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 111 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 112 symbolSource); 113 if (!bodyBuild) 114 return; 115 116 SmallVector<Type, 4> blockArgTypes; 117 for (ValueRange container : {inputs, outputBuffers, initTensors}) 118 for (Value v : container) 119 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 120 121 OpBuilder::InsertionGuard guard(builder); 122 auto ®ion = *result.regions.front(); 123 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 124 bodyBuild(builder, result.location, bodyBlock->getArguments()); 125 } 126 127 void GenericOp::build( 128 OpBuilder &builder, OperationState &result, ValueRange inputs, 129 ValueRange outputBuffers, ArrayRef<AffineMap> indexingMaps, 130 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 131 IntegerAttr symbolSource, 132 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 133 build(builder, result, TypeRange{}, inputs, outputBuffers, ValueRange{}, 134 indexingMaps, iteratorTypes, doc, libraryCall, symbolSource, bodyBuild); 135 } 136 137 void GenericOp::build( 138 OpBuilder &builder, OperationState &result, ValueRange inputs, 139 ValueRange outputBuffers, ArrayRef<AffineMap> indexingMaps, 140 ArrayRef<StringRef> iteratorTypes, 141 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 142 build(builder, result, inputs, outputBuffers, indexingMaps, iteratorTypes, 143 /*doc=*/"", 144 /*libraryCall=*/"", 145 /*symbolSource=*/IntegerAttr(), bodyBuild); 146 } 147 148 void GenericOp::build( 149 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 150 ValueRange inputs, ValueRange outputBuffers, ValueRange initTensors, 151 ArrayRef<AffineMap> indexingMaps, ArrayRef<StringRef> iteratorTypes, 152 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) { 153 build(builder, result, resultTensorTypes, inputs, outputBuffers, initTensors, 154 indexingMaps, iteratorTypes, 155 /*doc=*/"", 156 /*libraryCall=*/"", 157 /*symbolSource=*/IntegerAttr(), bodyBuild); 158 } 159 160 void IndexedGenericOp::build( 161 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 162 ValueRange inputs, ValueRange outputBuffers, ValueRange initTensors, 163 ArrayRef<AffineMap> indexingMaps, ArrayRef<StringRef> iteratorTypes, 164 StringRef doc, StringRef libraryCall, IntegerAttr symbolSource, 165 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 166 bodyBuild) { 167 build(builder, result, resultTensorTypes, inputs, outputBuffers, initTensors, 168 builder.getAffineMapArrayAttr(indexingMaps), 169 builder.getStrArrayAttr(iteratorTypes), 170 doc.empty() ? StringAttr() : builder.getStringAttr(doc), 171 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall), 172 symbolSource); 173 if (!bodyBuild) 174 return; 175 176 unsigned nLoops = iteratorTypes.size(); 177 SmallVector<Type, 4> blockArgTypes(nLoops, builder.getIndexType()); 178 for (ValueRange container : {inputs, outputBuffers, initTensors}) 179 for (Value v : container) 180 blockArgTypes.push_back(v.getType().cast<ShapedType>().getElementType()); 181 182 OpBuilder::InsertionGuard guard(builder); 183 auto ®ion = *result.regions.front(); 184 Block *bodyBlock = builder.createBlock(®ion, region.end(), blockArgTypes); 185 bodyBuild(builder, result.location, 186 bodyBlock->getArguments().take_front(nLoops), 187 bodyBlock->getArguments().drop_front(nLoops)); 188 } 189 190 void IndexedGenericOp::build( 191 OpBuilder &builder, OperationState &result, ValueRange inputs, 192 ValueRange outputBuffers, ArrayRef<AffineMap> indexingMaps, 193 ArrayRef<StringRef> iteratorTypes, StringRef doc, StringRef libraryCall, 194 IntegerAttr symbolSource, 195 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 196 bodyBuild) { 197 build(builder, result, TypeRange{}, inputs, outputBuffers, ValueRange{}, 198 indexingMaps, iteratorTypes, doc, libraryCall, symbolSource, bodyBuild); 199 } 200 201 void IndexedGenericOp::build( 202 OpBuilder &builder, OperationState &result, ValueRange inputs, 203 ValueRange outputBuffers, ArrayRef<AffineMap> indexingMaps, 204 ArrayRef<StringRef> iteratorTypes, 205 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 206 bodyBuild) { 207 build(builder, result, inputs, outputBuffers, indexingMaps, iteratorTypes, 208 /*doc=*/"", 209 /*libraryCall=*/"", 210 /*symbolSource=*/IntegerAttr(), bodyBuild); 211 } 212 213 void IndexedGenericOp::build( 214 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes, 215 ValueRange inputs, ValueRange outputBuffers, ValueRange initTensors, 216 ArrayRef<AffineMap> indexingMaps, ArrayRef<StringRef> iteratorTypes, 217 function_ref<void(OpBuilder &, Location, ValueRange, ValueRange)> 218 bodyBuild) { 219 build(builder, result, resultTensorTypes, inputs, outputBuffers, initTensors, 220 indexingMaps, iteratorTypes, 221 /*doc=*/"", 222 /*libraryCall=*/"", 223 /*symbolSource=*/IntegerAttr(), bodyBuild); 224 } 225 226 template <typename GenericOpType> 227 static void printGenericOp(OpAsmPrinter &p, GenericOpType op) { 228 p << op.getOperationName() << " "; 229 230 // Print extra attributes. 231 auto genericAttrNames = op.linalgTraitAttrNames(); 232 233 llvm::StringSet<> genericAttrNamesSet; 234 genericAttrNamesSet.insert(genericAttrNames.begin(), genericAttrNames.end()); 235 SmallVector<NamedAttribute, 8> genericAttrs; 236 for (auto attr : op.getAttrs()) 237 if (genericAttrNamesSet.count(attr.first.strref()) > 0) 238 genericAttrs.push_back(attr); 239 if (!genericAttrs.empty()) { 240 auto genericDictAttr = DictionaryAttr::get(genericAttrs, op.getContext()); 241 p << genericDictAttr; 242 } 243 244 // Printing is shared with named ops, except for the region and attributes 245 printCommonStructuredOpParts(p, op); 246 247 genericAttrNames.push_back("operand_segment_sizes"); 248 genericAttrNamesSet.insert(genericAttrNames.back()); 249 250 bool hasExtraAttrs = false; 251 for (NamedAttribute n : op.getAttrs()) { 252 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.first.strref()))) 253 break; 254 } 255 if (hasExtraAttrs) { 256 p << " attrs = "; 257 p.printOptionalAttrDict(op.getAttrs(), /*elidedAttrs=*/genericAttrNames); 258 } 259 260 // Print region. 261 if (!op.region().empty()) 262 p.printRegion(op.region()); 263 264 // Print results. 265 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 266 } 267 268 static void print(OpAsmPrinter &p, GenericOp op) { printGenericOp(p, op); } 269 270 static void print(OpAsmPrinter &p, IndexedGenericOp op) { 271 printGenericOp(p, op); 272 } 273 274 static ParseResult parseGenericOp(OpAsmParser &parser, OperationState &result) { 275 DictionaryAttr dictAttr; 276 // Parse the core linalg traits that must check into a dictAttr. 277 // The name is unimportant as we will overwrite result.attributes. 278 // The core linalg traits must contain the information necessary to pass the 279 // verifier. 280 if (parser.parseAttribute(dictAttr, "_", result.attributes)) 281 return failure(); 282 result.attributes.assign(dictAttr.getValue().begin(), 283 dictAttr.getValue().end()); 284 285 // Parsing is shared with named ops, except for the region. 286 SmallVector<Type, 1> inputTypes, outputBufferTypes, initTensorTypes; 287 if (parseCommonStructuredOpParts(parser, result, inputTypes, 288 outputBufferTypes, initTensorTypes)) 289 return failure(); 290 291 // Optional attributes may be added. 292 if (succeeded(parser.parseOptionalKeyword("attrs"))) 293 if (failed(parser.parseEqual()) || 294 failed(parser.parseOptionalAttrDict(result.attributes))) 295 return failure(); 296 297 SmallVector<OpAsmParser::OperandType, 8> regionOperands; 298 std::unique_ptr<Region> region = std::make_unique<Region>(); 299 SmallVector<Type, 8> operandTypes, regionTypes; 300 if (parser.parseRegion(*region, regionOperands, regionTypes)) 301 return failure(); 302 result.addRegion(std::move(region)); 303 304 // Generic ops may specify that a subset of its outputs are tensors. Such 305 // outputs are specified in the result type. 306 // TODO: may need to move output parsing before region parsing. 307 // Need to wait for declarative assembly resolution to decide. 308 SmallVector<Type, 1> outputTensorsTypes; 309 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 310 return failure(); 311 result.addTypes(outputTensorsTypes); 312 313 return success(); 314 } 315 316 namespace { 317 template <typename GenericOpType> 318 struct BlockArgsVerifier { 319 static LogicalResult verify(GenericOpType op, Block &block); 320 }; 321 322 template <typename GenericOpType> 323 LogicalResult BlockArgsVerifier<GenericOpType>::verify(GenericOpType op, 324 Block &block) { 325 auto nOperands = op.getNumOperands(); 326 if (block.getNumArguments() != nOperands) 327 return op.emitOpError("expected number of block arguments to match number " 328 "of operands"); 329 330 // Note: the number and type of yield values are checked in the YieldOp. 331 auto nInputViews = op.getNumInputs(); 332 for (unsigned i = 0; i < nOperands; ++i) { 333 auto viewType = op.getShapedType(i); 334 if (viewType.getElementType() != block.getArgument(i).getType()) 335 return op.emitOpError("expected block argument ") 336 << (i + 1) << " of the same type as elemental type of " 337 << ((i < nInputViews) ? "input " : "output ") 338 << "operand: " << viewType; 339 } 340 return success(); 341 } 342 343 template <> 344 LogicalResult BlockArgsVerifier<IndexedGenericOp>::verify(IndexedGenericOp op, 345 Block &block) { 346 auto nInputViews = op.getNumInputs(); 347 auto nLoops = op.getNumLoops(); 348 auto nOperands = op.getNumOperands(); 349 if (block.getNumArguments() != nOperands + nLoops) 350 return op.emitOpError( 351 "expected number of block arguments to match number of operands + " 352 "number of loops"); 353 354 // Note: the number and type of yield values are checked in the YieldOp. 355 for (unsigned i = 0; i < nLoops; ++i) 356 if (!block.getArgument(i).getType().isIndex()) 357 return op.emitOpError("expected block argument ") 358 << (i + 1) << " to be an index"; 359 360 for (unsigned i = 0; i < nOperands; ++i) { 361 unsigned memrefArgIndex = i + nLoops; 362 auto viewType = op.getShapedType(i); 363 if (viewType.getElementType() != 364 block.getArgument(memrefArgIndex).getType()) 365 return op.emitOpError("expected block argument ") 366 << (memrefArgIndex + 1) 367 << " of the same type as elemental type of " 368 << ((i < nInputViews) ? "input " : "output ") 369 << "operand: " << viewType; 370 } 371 return success(); 372 } 373 } // namespace 374 375 template <typename GenericOpType> 376 static LogicalResult verifyGenericOp(GenericOpType op) { 377 auto nInputViews = op.getNumInputs(); 378 auto nLoops = op.getNumLoops(); 379 380 if (op.inputs().size() + op.output_buffers().size() + 381 op.init_tensors().size() + op.getNumResults() == 382 0) 383 return op.emitOpError("expected at least 1 Shaped operand or return"); 384 385 auto ®ion = op.region(); 386 if (!llvm::hasSingleElement(region)) 387 return op.emitOpError("expected region with 1 block"); 388 if (failed(BlockArgsVerifier<GenericOpType>::verify(op, region.front()))) 389 return failure(); 390 391 auto symbolSourceAttr = 392 op.template getAttrOfType<IntegerAttr>("symbol_source"); 393 int64_t expectedNumSymbols = 0; 394 if (symbolSourceAttr) { 395 unsigned index = symbolSourceAttr.getInt(); 396 if (index >= op.getNumOperands()) 397 return op.emitOpError("symbol_source index out of range"); 398 expectedNumSymbols = op.getShapedType(index).getRank(); 399 } 400 401 SmallVector<AffineMap, 4> indexingMaps; 402 indexingMaps.reserve(op.indexing_maps().size()); 403 for (auto en : llvm::enumerate(op.indexing_maps())) { 404 auto idx = en.index(); 405 auto m = en.value().template cast<AffineMapAttr>().getValue(); 406 indexingMaps.push_back(m); // Save reference to map for further checks. 407 auto view = (idx < nInputViews) ? op.getInputShapedType(idx) 408 : op.getOutputShapedType(idx - nInputViews); 409 410 if (m.getNumSymbols() != expectedNumSymbols) 411 return op.emitOpError("expected the number of symbols in indexing_map #") 412 << idx << " to match rank of operand `symbol_source`"; 413 414 if (m.getNumDims() != nLoops) 415 return op.emitOpError("expected indexing_map #") 416 << idx << " to have " << nLoops 417 << " dim(s) to match the number of loops"; 418 419 if (m.getNumResults() != view.getRank()) 420 return op.emitOpError("expected indexing_map #") 421 << idx << " results to match view rank: " << view; 422 } 423 424 auto concatMap = concatAffineMaps(indexingMaps); 425 // TODO: Bound inference for maps with symbols 426 if (!concatMap.getNumSymbols() && !inversePermutation(concatMap)) 427 return op.emitOpError("expected the concatenation of maps in indexing_map " 428 "to be invertible"); 429 430 return success(); 431 } 432 433 static LogicalResult verify(GenericOp op) { return verifyGenericOp(op); } 434 435 static LogicalResult verify(IndexedGenericOp op) { return verifyGenericOp(op); } 436 437 //===----------------------------------------------------------------------===// 438 // ReshapeOp 439 //===----------------------------------------------------------------------===// 440 441 /// Collapse reassociation maps that are used in pair of reshape ops where one 442 /// is a producer and other is the consumer. Only valid to use this method when 443 /// both the producer and consumer are collapsing dimensions or both are 444 /// expanding dimensions. 445 /// 446 /// For example, 447 /// mapsProducer = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1)>, 448 /// affine_map<(d0, d1, d2, d3, d4) -> (d2)>, 449 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 450 /// mapsConsumer = [affine_map<(d0, d1, d2) -> (d0, d1)>, 451 /// affine_map<(d0, d1, d2) -> (d2)>] 452 /// 453 /// is folded into 454 /// 455 /// result = [affine_map<(d0, d1, d2, d3, d4) -> (d0, d1, d2)>, 456 /// affine_map<(d0, d1, d2, d3, d4) -> (d3, d4)>] 457 static ArrayAttr collapseReassociationMaps(ArrayRef<AffineMap> mapsProducer, 458 ArrayRef<AffineMap> mapsConsumer, 459 MLIRContext *context) { 460 if (mapsProducer.empty() || mapsConsumer.empty() || 461 mapsProducer[0].getNumDims() < mapsConsumer[0].getNumDims() || 462 mapsProducer.size() != mapsConsumer[0].getNumDims()) 463 return nullptr; 464 unsigned numLhsDims = mapsProducer[0].getNumDims(); 465 unsigned currDim = 0; 466 SmallVector<AffineExpr, 4> reassociations; 467 SmallVector<Attribute, 4> reassociationMaps; 468 for (AffineMap rhs : mapsConsumer) { 469 for (AffineExpr rhsExpr : rhs.getResults()) { 470 AffineDimExpr dimExpr = rhsExpr.cast<AffineDimExpr>(); 471 for (int i = 0, e = mapsProducer[dimExpr.getPosition()].getNumResults(); 472 i < e; ++i) { 473 reassociations.push_back(getAffineDimExpr(currDim++, context)); 474 } 475 } 476 reassociationMaps.push_back(AffineMapAttr::get(AffineMap::get( 477 numLhsDims, /*numSymbols =*/0, reassociations, context))); 478 reassociations.clear(); 479 } 480 return ArrayAttr::get(reassociationMaps, context); 481 } 482 483 namespace { 484 /// Pattern to collapse producer/consumer reshape ops that are both collapsing 485 /// dimensions or are both expanding dimensions. 486 template <typename ReshapeOpTy> 487 struct CollapseReshapeOps : public OpRewritePattern<ReshapeOpTy> { 488 using OpRewritePattern<ReshapeOpTy>::OpRewritePattern; 489 LogicalResult matchAndRewrite(ReshapeOpTy reshapeOp, 490 PatternRewriter &rewriter) const override { 491 auto srcReshapeOp = reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 492 if (!srcReshapeOp) 493 return failure(); 494 495 auto areReshapeOpsFoldable = [](ShapedType largerType, 496 ShapedType intermediateType, 497 ShapedType smallerType) -> bool { 498 return largerType.getRank() > intermediateType.getRank() && 499 intermediateType.getRank() > smallerType.getRank() && 500 smallerType.getRank() > 0; 501 }; 502 // Check if producer and consumer are both expanding dims. 503 if (areReshapeOpsFoldable(reshapeOp.getResultType(), reshapeOp.getSrcType(), 504 srcReshapeOp.getSrcType())) { 505 rewriter.replaceOpWithNewOp<ReshapeOpTy>( 506 reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(), 507 collapseReassociationMaps(reshapeOp.getReassociationMaps(), 508 srcReshapeOp.getReassociationMaps(), 509 rewriter.getContext())); 510 return success(); 511 } 512 // Check if producer and consumer are both collapsing dims. 513 else if (areReshapeOpsFoldable(srcReshapeOp.getSrcType(), 514 reshapeOp.getSrcType(), 515 reshapeOp.getResultType())) { 516 rewriter.replaceOpWithNewOp<ReshapeOpTy>( 517 reshapeOp, reshapeOp.getResultType(), srcReshapeOp.src(), 518 collapseReassociationMaps(srcReshapeOp.getReassociationMaps(), 519 reshapeOp.getReassociationMaps(), 520 rewriter.getContext())); 521 return success(); 522 } 523 return failure(); 524 } 525 }; 526 } // namespace 527 528 template <typename ReshapeOpTy> 529 static OpFoldResult foldReshapeOp(ReshapeOpTy reshapeOp, 530 ArrayRef<Attribute> operands) { 531 // Fold producer-consumer reshape ops that where the operand type of the 532 // producer is same as the return type of the consumer. This can only be 533 // verified if the shapes in question are static. 534 ReshapeOpTy reshapeSrcOp = 535 reshapeOp.src().template getDefiningOp<ReshapeOpTy>(); 536 if (reshapeSrcOp && reshapeSrcOp.getSrcType().hasStaticShape() && 537 reshapeOp.getResultType().hasStaticShape() && 538 reshapeSrcOp.getSrcType() == reshapeOp.getResultType()) 539 return reshapeSrcOp.src(); 540 // Reshape of a constant can be replaced with a new constant. 541 if (auto elements = operands.front().dyn_cast_or_null<DenseElementsAttr>()) { 542 return elements.reshape( 543 reshapeOp.getResult().getType().template cast<ShapedType>()); 544 } 545 return nullptr; 546 } 547 548 /// Return true if the reassociation specification is valid, false otherwise. 549 /// When false, the `invalidIndex` integer pointer is optionally filled with the 550 /// index of the offending reassociation map. 551 static bool isReassociationValid(ArrayRef<AffineMap> reassociation, 552 int *invalidIndex = nullptr) { 553 if (reassociation.empty()) 554 return true; 555 unsigned nDims = reassociation[0].getNumDims(); 556 unsigned nextExpectedDim = 0; 557 for (auto it : llvm::enumerate(reassociation)) { 558 auto m = it.value(); 559 if (m.getNumDims() != nDims || m.getNumSymbols() != 0) { 560 if (invalidIndex) 561 *invalidIndex = it.index(); 562 return false; 563 } 564 for (auto e : m.getResults()) { 565 auto d = e.dyn_cast<AffineDimExpr>(); 566 if (!d || d.getPosition() != nextExpectedDim++) { 567 if (invalidIndex) 568 *invalidIndex = it.index(); 569 return false; 570 } 571 } 572 } 573 if (nextExpectedDim != nDims) { 574 if (invalidIndex) 575 *invalidIndex = reassociation.size() - 1; 576 return false; 577 } 578 return true; 579 } 580 581 /// Detect whether memref dims [dim, dim + extent) can be reshaped without 582 /// copies. 583 static bool isReshapableDimBand(unsigned dim, unsigned extent, 584 ArrayRef<int64_t> sizes, 585 ArrayRef<AffineExpr> strides) { 586 assert(sizes.size() == strides.size() && "mismatched ranks"); 587 // off by 1 indexing to avoid out of bounds 588 // V 589 for (auto idx = dim, e = dim + extent; idx + 1 < e; ++idx) { 590 // Only bands of static shapes are reshapable. This is due to the fact that 591 // there is no relation between dynamic sizes and dynamic strides: we do not 592 // have enough information to know whether a "-1" size corresponds to the 593 // proper symbol in the AffineExpr of a stride. 594 if (ShapedType::isDynamic(sizes[dim + 1])) 595 return false; 596 // TODO: Refine this by passing the proper nDims and nSymbols so we can 597 // simplify on the fly and catch more reshapable cases. 598 if (strides[idx] != strides[idx + 1] * sizes[idx + 1]) 599 return false; 600 } 601 return true; 602 } 603 604 /// Compute the MemRefType obtained by applying the `reassociation` (which is 605 /// expected to be valid) to `type`. 606 /// If `type` is Contiguous MemRefType, this always produce a contiguous 607 /// MemRefType. 608 static MemRefType 609 computeReshapeCollapsedType(MemRefType type, 610 ArrayRef<AffineMap> reassociation) { 611 auto sizes = type.getShape(); 612 AffineExpr offset; 613 SmallVector<AffineExpr, 4> strides; 614 auto status = getStridesAndOffset(type, strides, offset); 615 (void)status; 616 assert(succeeded(status) && "expected strided memref"); 617 618 SmallVector<int64_t, 4> newSizes; 619 newSizes.reserve(reassociation.size()); 620 SmallVector<AffineExpr, 4> newStrides; 621 newStrides.reserve(reassociation.size()); 622 623 // Use the fact that reassociation is valid to simplify the logic: only use 624 // each map's rank. 625 assert(isReassociationValid(reassociation) && "invalid reassociation"); 626 unsigned currentDim = 0; 627 for (AffineMap m : reassociation) { 628 unsigned dim = m.getNumResults(); 629 int64_t size = 1; 630 AffineExpr stride = strides[currentDim + dim - 1]; 631 if (!isReshapableDimBand(currentDim, dim, sizes, strides)) { 632 size = ShapedType::kDynamicSize; 633 stride = AffineExpr(); 634 } else { 635 for (unsigned d = 0; d < dim; ++d) 636 size *= sizes[currentDim + d]; 637 } 638 newSizes.push_back(size); 639 newStrides.push_back(stride); 640 currentDim += dim; 641 } 642 643 // Early-exit: if `type` is contiguous, the result must be contiguous. 644 if (canonicalizeStridedLayout(type).getAffineMaps().empty()) 645 return MemRefType::Builder(type).setShape(newSizes).setAffineMaps({}); 646 647 // Convert back to int64_t because we don't have enough information to create 648 // new strided layouts from AffineExpr only. This corresponds to a case where 649 // copies may be necessary. 650 int64_t intOffset = ShapedType::kDynamicStrideOrOffset; 651 if (auto o = offset.dyn_cast<AffineConstantExpr>()) 652 intOffset = o.getValue(); 653 SmallVector<int64_t, 4> intStrides; 654 intStrides.reserve(strides.size()); 655 for (auto stride : newStrides) { 656 if (auto cst = stride.dyn_cast_or_null<AffineConstantExpr>()) 657 intStrides.push_back(cst.getValue()); 658 else 659 intStrides.push_back(ShapedType::kDynamicStrideOrOffset); 660 } 661 auto layout = 662 makeStridedLinearLayoutMap(intStrides, intOffset, type.getContext()); 663 return canonicalizeStridedLayout( 664 MemRefType::Builder(type).setShape(newSizes).setAffineMaps({layout})); 665 } 666 667 /// Helper functions assert Attribute of the proper type in attr and returns the 668 /// corresponding vector. 669 /// TODO: this should be evolved into a generic 670 /// `getRangeOfType<AffineMap>(ArrayAttr attrs)` that does not copy. 671 static SmallVector<AffineMap, 4> getAffineMaps(ArrayAttr attrs) { 672 return llvm::to_vector<8>(llvm::map_range( 673 attrs, [](Attribute a) { return a.cast<AffineMapAttr>().getValue(); })); 674 } 675 676 template <typename AffineExprTy> 677 unsigned getMaxPosOfType(ArrayRef<ReassociationExprs> exprArrays) { 678 unsigned pos = 0; 679 for (const auto &exprs : exprArrays) { 680 for (auto expr : exprs) { 681 expr.walk([&pos](AffineExpr e) { 682 if (auto d = e.dyn_cast<AffineExprTy>()) 683 pos = std::max(pos, d.getPosition()); 684 }); 685 } 686 } 687 return pos; 688 } 689 690 static SmallVector<AffineMap, 4> 691 getSymbolLessAffineMaps(ArrayRef<ReassociationExprs> reassociation) { 692 unsigned maxDim = getMaxPosOfType<AffineDimExpr>(reassociation); 693 assert(getMaxPosOfType<AffineSymbolExpr>(reassociation) == 0 && 694 "Expected symbol-less expressions"); 695 SmallVector<AffineMap, 4> maps; 696 maps.reserve(reassociation.size()); 697 for (const auto &exprs : reassociation) { 698 assert(!exprs.empty()); 699 maps.push_back(AffineMap::get(maxDim + 1, 0, exprs, exprs[0].getContext())); 700 } 701 return maps; 702 } 703 704 static SmallVector<SmallVector<AffineExpr, 2>, 2> 705 convertReassociationIndicesToMaps( 706 OpBuilder &b, ArrayRef<ReassociationIndices> reassociationIndices) { 707 SmallVector<SmallVector<AffineExpr, 2>, 2> reassociationMaps; 708 for (const auto &indicies : reassociationIndices) { 709 SmallVector<AffineExpr, 2> reassociationMap; 710 reassociationMap.reserve(indicies.size()); 711 for (int64_t index : indicies) 712 reassociationMap.push_back(b.getAffineDimExpr(index)); 713 reassociationMaps.push_back(std::move(reassociationMap)); 714 } 715 return reassociationMaps; 716 } 717 718 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result, 719 Value src, 720 ArrayRef<ReassociationExprs> reassociation, 721 ArrayRef<NamedAttribute> attrs) { 722 auto maps = getSymbolLessAffineMaps(reassociation); 723 auto memRefType = src.getType().cast<MemRefType>(); 724 auto resultType = computeReshapeCollapsedType(memRefType, maps); 725 build(b, result, resultType, src, attrs); 726 result.addAttribute(ReshapeOp::getReassociationAttrName(), 727 b.getAffineMapArrayAttr(maps)); 728 } 729 730 void mlir::linalg::ReshapeOp::build(OpBuilder &b, OperationState &result, 731 Type resultType, Value src, 732 ArrayRef<ReassociationExprs> reassociation, 733 ArrayRef<NamedAttribute> attrs) { 734 auto maps = getSymbolLessAffineMaps(reassociation); 735 build(b, result, resultType, src, attrs); 736 result.addAttribute(ReshapeOp::getReassociationAttrName(), 737 b.getAffineMapArrayAttr(maps)); 738 } 739 740 Value mlir::linalg::ReshapeOp::getViewSource() { return src(); } 741 742 // Common verifier for reshape-like types. Fills `expandedType` and 743 // `collapsedType` with the proper `src` or `result` type. 744 template <typename Op, typename T> 745 static LogicalResult verifyReshapeLikeTypes(Op op, T &expandedType, 746 T &collapsedType) { 747 expandedType = op.getSrcType(); 748 collapsedType = op.getResultType(); 749 unsigned expandedRank = expandedType.getRank(); 750 unsigned collapsedRank = collapsedType.getRank(); 751 bool isCollapse = expandedRank > collapsedRank; 752 if (!isCollapse) { 753 std::swap(expandedRank, collapsedRank); 754 std::swap(expandedType, collapsedType); 755 } 756 if (expandedRank == 0) 757 return op.emitOpError("expected non-zero memref ranks"); 758 if (expandedRank == collapsedRank) 759 return op.emitOpError("expected to collapse or expand dims"); 760 761 if (collapsedRank == 0) { 762 // If collapsed rank is 0, then expanded type must be static shaped and of 763 // sizes 1. 764 if (llvm::any_of(expandedType.getShape(), 765 [](int64_t dim) -> bool { return dim != 1; })) 766 return op.emitOpError( 767 "invalid to reshape tensor/memref with non-unit extent dimensions to " 768 "zero-rank tensor/memref"); 769 return success(); 770 } 771 if (collapsedRank != op.reassociation().size()) 772 return op.emitOpError("expected rank of the collapsed type(") 773 << collapsedRank << ") to be the number of reassociation maps(" 774 << op.reassociation().size() << ")"; 775 auto maps = getAffineMaps(op.reassociation()); 776 for (auto it : llvm::enumerate(maps)) 777 if (it.value().getNumDims() != expandedRank) 778 return op.emitOpError("expected reassociation map #") 779 << it.index() << " of same rank as expanded memref(" 780 << expandedRank << "), but got " << it.value().getNumDims(); 781 int invalidIdx = 0; 782 if (!isReassociationValid(maps, &invalidIdx)) 783 return op.emitOpError("expected reassociation map #") 784 << invalidIdx << " to be valid and contiguous"; 785 return success(); 786 } 787 788 static LogicalResult verify(ReshapeOp op) { 789 MemRefType expandedType, collapsedType; 790 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 791 return failure(); 792 auto maps = getAffineMaps(op.reassociation()); 793 MemRefType expectedType = computeReshapeCollapsedType(expandedType, maps); 794 if (collapsedType != expectedType) 795 return op.emitOpError("expected collapsed type to be ") 796 << expectedType << ", but got " << collapsedType; 797 return success(); 798 } 799 800 void ReshapeOp::getCanonicalizationPatterns(OwningRewritePatternList &results, 801 MLIRContext *context) { 802 results.insert<CollapseReshapeOps<ReshapeOp>>(context); 803 } 804 805 //===----------------------------------------------------------------------===// 806 // TensorReshapeOp 807 //===----------------------------------------------------------------------===// 808 809 /// Compute the RankedTensorType obtained by applying `reassociation` to `type`. 810 static RankedTensorType 811 computeTensorReshapeCollapsedType(RankedTensorType type, 812 ArrayRef<AffineMap> reassociation) { 813 auto shape = type.getShape(); 814 SmallVector<int64_t, 4> newShape; 815 newShape.reserve(reassociation.size()); 816 817 // Use the fact that reassociation is valid to simplify the logic: only use 818 // each map's rank. 819 assert(isReassociationValid(reassociation) && "invalid reassociation"); 820 unsigned currentDim = 0; 821 for (AffineMap m : reassociation) { 822 unsigned dim = m.getNumResults(); 823 auto band = shape.slice(currentDim, dim); 824 int64_t size = 1; 825 if (llvm::is_contained(band, ShapedType::kDynamicSize)) 826 size = ShapedType::kDynamicSize; 827 else 828 for (unsigned d = 0; d < dim; ++d) 829 size *= shape[currentDim + d]; 830 newShape.push_back(size); 831 currentDim += dim; 832 } 833 834 return RankedTensorType::get(newShape, type.getElementType()); 835 } 836 837 void mlir::linalg::TensorReshapeOp::build( 838 OpBuilder &b, OperationState &result, Value src, 839 ArrayRef<ReassociationExprs> reassociation, 840 ArrayRef<NamedAttribute> attrs) { 841 auto maps = getSymbolLessAffineMaps(reassociation); 842 auto resultType = computeTensorReshapeCollapsedType( 843 src.getType().cast<RankedTensorType>(), maps); 844 build(b, result, resultType, src, attrs); 845 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 846 b.getAffineMapArrayAttr(maps)); 847 } 848 849 void mlir::linalg::TensorReshapeOp::build( 850 OpBuilder &b, OperationState &result, Type resultType, Value src, 851 ArrayRef<ReassociationExprs> reassociation, 852 ArrayRef<NamedAttribute> attrs) { 853 auto maps = getSymbolLessAffineMaps(reassociation); 854 build(b, result, resultType, src, attrs); 855 result.addAttribute(TensorReshapeOp::getReassociationAttrName(), 856 b.getAffineMapArrayAttr(maps)); 857 } 858 859 static LogicalResult verify(TensorReshapeOp op) { 860 RankedTensorType expandedType, collapsedType; 861 if (failed(verifyReshapeLikeTypes(op, expandedType, collapsedType))) 862 return failure(); 863 auto maps = getAffineMaps(op.reassociation()); 864 // TODO: expanding a ? with a non-constant is under-specified. Error 865 // out. 866 RankedTensorType expectedType = 867 computeTensorReshapeCollapsedType(expandedType, maps); 868 if (collapsedType != expectedType) 869 return op.emitOpError("expected collapsed type to be ") 870 << expectedType << ", but got " << collapsedType; 871 return success(); 872 } 873 874 namespace { 875 /// Reshape of a splat constant can be replaced with a constant of the result 876 /// type. 877 struct FoldReshapeWithConstant : OpRewritePattern<TensorReshapeOp> { 878 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 879 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 880 PatternRewriter &rewriter) const override { 881 DenseElementsAttr attr; 882 if (!matchPattern(reshapeOp.src(), m_Constant(&attr))) 883 return failure(); 884 if (!attr || !attr.isSplat()) 885 return failure(); 886 DenseElementsAttr newAttr = DenseElementsAttr::getFromRawBuffer( 887 reshapeOp.getResultType(), attr.getRawData(), true); 888 rewriter.replaceOpWithNewOp<ConstantOp>(reshapeOp, newAttr); 889 return success(); 890 } 891 }; 892 } // namespace 893 894 void TensorReshapeOp::getCanonicalizationPatterns( 895 OwningRewritePatternList &results, MLIRContext *context) { 896 results.insert<CollapseReshapeOps<TensorReshapeOp>, FoldReshapeWithConstant>( 897 context); 898 } 899 900 //===----------------------------------------------------------------------===// 901 // SliceOp 902 //===----------------------------------------------------------------------===// 903 void mlir::linalg::SliceOp::build(OpBuilder &b, OperationState &result, 904 Value base, ValueRange indexings) { 905 result.addOperands(base); 906 result.addOperands(indexings); 907 908 auto memRefType = base.getType().cast<MemRefType>(); 909 int64_t offset; 910 SmallVector<int64_t, 4> strides; 911 auto res = getStridesAndOffset(memRefType, strides, offset); 912 assert(succeeded(res) && strides.size() == indexings.size()); 913 (void)res; 914 915 unsigned rank = memRefType.getRank(); 916 // TODO: propagate static size and stride information when available. 917 SmallVector<int64_t, 4> sizes(rank, -1); // -1 encodes dynamic size. 918 result.addTypes({MemRefType::Builder(memRefType) 919 .setShape(sizes) 920 .setAffineMaps(makeStridedLinearLayoutMap( 921 strides, offset, b.getContext()))}); 922 } 923 924 static void print(OpAsmPrinter &p, SliceOp op) { 925 auto indexings = op.indexings(); 926 p << SliceOp::getOperationName() << " " << op.view() << "[" << indexings 927 << "] "; 928 p.printOptionalAttrDict(op.getAttrs()); 929 p << " : " << op.getBaseViewType(); 930 if (!indexings.empty()) 931 p << ", " << op.indexings().getTypes(); 932 p << ", " << op.getType(); 933 } 934 935 static ParseResult parseSliceOp(OpAsmParser &parser, OperationState &result) { 936 OpAsmParser::OperandType baseInfo; 937 SmallVector<OpAsmParser::OperandType, 8> operands; 938 SmallVector<Type, 8> types; 939 if (parser.parseOperand(baseInfo) || 940 parser.parseOperandList(operands, OpAsmParser::Delimiter::Square) || 941 parser.parseOptionalAttrDict(result.attributes) || 942 parser.parseColonTypeList(types)) 943 return failure(); 944 945 if (types.size() < 2) 946 return parser.emitError(parser.getCurrentLocation(), 947 "expected at least input and result view types"); 948 949 ArrayRef<Type> indexingTypes = ArrayRef<Type>(types).drop_front().drop_back(); 950 return failure( 951 parser.resolveOperand(baseInfo, types.front(), result.operands) || 952 (!operands.empty() && 953 parser.resolveOperands(operands, indexingTypes, 954 operands.front().location, result.operands)) || 955 parser.addTypeToList(types.back(), result.types)); 956 } 957 958 static LogicalResult verify(SliceOp op) { 959 unsigned rank = op.getBaseViewRank(); 960 if (rank != llvm::size(op.indexings())) 961 return op.emitOpError("expected ") 962 << rank << " indexings, got " << llvm::size(op.indexings()); 963 unsigned index = 0; 964 for (auto indexing : op.indexings()) { 965 if (indexing.getType().isa<IndexType>()) 966 --rank; 967 ++index; 968 } 969 if (op.getRank() != rank) 970 return op.emitOpError() << "expected rank of the view(" << op.getRank() 971 << ") to be the number of ranges(" << rank << ")"; 972 return success(); 973 } 974 975 Value SliceOp::getViewSource() { return view(); } 976 977 //===----------------------------------------------------------------------===// 978 // YieldOp 979 //===----------------------------------------------------------------------===// 980 981 static void print(OpAsmPrinter &p, linalg::YieldOp op) { 982 p << op.getOperationName(); 983 if (op.getNumOperands() > 0) 984 p << ' ' << op.getOperands(); 985 p.printOptionalAttrDict(op.getAttrs()); 986 if (op.getNumOperands() > 0) 987 p << " : " << op.getOperandTypes(); 988 } 989 990 static ParseResult parseYieldOp(OpAsmParser &parser, OperationState &result) { 991 SmallVector<OpAsmParser::OperandType, 2> opInfo; 992 SmallVector<Type, 2> types; 993 llvm::SMLoc loc = parser.getCurrentLocation(); 994 return failure(parser.parseOperandList(opInfo) || 995 parser.parseOptionalAttrDict(result.attributes) || 996 (!opInfo.empty() && parser.parseColonTypeList(types)) || 997 parser.resolveOperands(opInfo, types, loc, result.operands)); 998 } 999 1000 // Check the operand number and types must match the element types of the 1001 // LinalgOp interface's shaped operands. 1002 static LogicalResult verifyYield(linalg::YieldOp op, 1003 LinalgOp linalgOpInterface) { 1004 auto nOutputs = linalgOpInterface.getNumOutputs(); 1005 if (op.getNumOperands() != nOutputs) 1006 return op.emitOpError("expected number of yield values (") 1007 << nOutputs << ") to match the number of operands of the enclosing " 1008 << "LinalgOp (" << op.getNumOperands() << ")"; 1009 1010 for (unsigned i = 0; i != nOutputs; ++i) { 1011 auto elementType = 1012 linalgOpInterface.getOutputShapedType(i).getElementType(); 1013 if (op.getOperand(i).getType() != elementType) 1014 return op.emitOpError("type of yield operand ") 1015 << (i + 1) << " (" << op.getOperand(i).getType() 1016 << ") doesn't match " 1017 << "the element type of the enclosing linalg.generic op (" 1018 << elementType << ")"; 1019 } 1020 return success(); 1021 } 1022 1023 static LogicalResult verify(linalg::YieldOp op) { 1024 auto *parentOp = op.getParentOp(); 1025 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty()) 1026 return op.emitOpError("expected single non-empty parent region"); 1027 1028 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp)) 1029 return verifyYield(op, cast<LinalgOp>(parentOp)); 1030 1031 return op.emitOpError("expected parent op with LinalgOp interface"); 1032 } 1033 1034 /////// Operations corresponding to library calls defined with Tablegen //////// 1035 1036 static LogicalResult verify(FillOp op) { 1037 auto viewType = op.getOutputShapedType(0); 1038 auto fillType = op.value().getType(); 1039 if (viewType.getElementType() != fillType) 1040 return op.emitOpError("expects fill type to match view elemental type"); 1041 return success(); 1042 } 1043 1044 static LogicalResult verify(CopyOp op) { 1045 auto outputViewType = op.getOutputShapedType(0); 1046 auto inputViewType = op.getInputShapedType(0); 1047 if (inputViewType.getElementType() != outputViewType.getElementType()) 1048 return op.emitOpError("expects views of the same type"); 1049 if (inputViewType.getRank() != outputViewType.getRank()) 1050 return op.emitOpError("expects views of the same rank"); 1051 auto rank = op.getNumParallelLoops(); 1052 auto inputPermutationMap = op.inputPermutation(); 1053 if (inputPermutationMap) { 1054 if (inputPermutationMap->getNumInputs() != rank) 1055 return op.emitOpError("expects optional input_permutation map of rank ") 1056 << rank; 1057 if (!inputPermutationMap->isPermutation()) 1058 return op.emitOpError( 1059 "expects optional input_permutation map to be a permutation"); 1060 } 1061 auto outputPermutationMap = op.outputPermutation(); 1062 if (outputPermutationMap) { 1063 if (outputPermutationMap->getNumInputs() != rank) 1064 return op.emitOpError("expects optional output_permutation map of rank ") 1065 << rank; 1066 if (!outputPermutationMap->isPermutation()) 1067 return op.emitOpError( 1068 "expects optional output_permutation map to be a permutation"); 1069 } 1070 if (rank == 0 && inputPermutationMap) 1071 return op.emitOpError("expected no input permutation when rank == 0"); 1072 if (rank == 0 && outputPermutationMap) 1073 return op.emitOpError("expected no output permutation when rank == 0"); 1074 return success(); 1075 } 1076 1077 template <typename LinalgPoolingOp> 1078 static LogicalResult verifyStrideOrDilation(LinalgPoolingOp op, 1079 ArrayRef<Attribute> attrs, 1080 bool isStride) { 1081 auto strideOrDilation = isStride ? "stride" : "dilation"; 1082 if (attrs.size() != op.getNumWindowLoops()) 1083 return op.emitOpError("expects num ") 1084 << strideOrDilation 1085 << "s equal to number of window dimensions: " << attrs.size() 1086 << " vs " << op.getNumWindowLoops(); 1087 return success(); 1088 } 1089 1090 static LogicalResult verify(ConvOp op) { 1091 auto oType = op.output().getType().cast<MemRefType>(); 1092 auto fType = op.filter().getType().cast<MemRefType>(); 1093 auto iType = op.input().getType().cast<MemRefType>(); 1094 if (oType.getElementType() != iType.getElementType() || 1095 oType.getElementType() != fType.getElementType()) 1096 return op.emitOpError("expects memref elemental types to match"); 1097 if (oType.getRank() != iType.getRank() || oType.getRank() != fType.getRank()) 1098 return op.emitOpError("expects memref ranks to match"); 1099 if (oType.getRank() <= 2) 1100 return op.emitOpError("expects memref ranks to be greater than 2"); 1101 if (auto strides = op.strides()) { 1102 if (failed( 1103 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1104 return failure(); 1105 } 1106 if (auto dilations = op.dilations()) { 1107 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1108 /*isStride=*/false))) 1109 return failure(); 1110 } 1111 return success(); 1112 } 1113 1114 template <typename PoolingOp> 1115 static LogicalResult verifySingleInputPoolingOp(PoolingOp op) { 1116 auto inputType = op.input().getType().template cast<MemRefType>(); 1117 auto outputType = op.output().getType().template cast<MemRefType>(); 1118 if (outputType.getElementType() != inputType.getElementType()) 1119 return op.emitOpError("expects memref elemental types to match"); 1120 1121 auto windowDimsType = op.windowDims().getType().template cast<MemRefType>(); 1122 if (outputType.getRank() != inputType.getRank() || 1123 outputType.getRank() != windowDimsType.getRank()) 1124 return op.emitOpError("expects memref ranks to match"); 1125 1126 if (auto strides = op.strides()) { 1127 if (failed( 1128 verifyStrideOrDilation(op, strides->getValue(), /*isStride=*/true))) 1129 return failure(); 1130 } 1131 if (auto dilations = op.dilations()) { 1132 if (failed(verifyStrideOrDilation(op, dilations->getValue(), 1133 /*isStride=*/false))) 1134 return failure(); 1135 } 1136 return success(); 1137 } 1138 1139 static LogicalResult verify(PoolingMaxOp op) { 1140 return verifySingleInputPoolingOp(op); 1141 } 1142 static LogicalResult verify(PoolingMinOp op) { 1143 return verifySingleInputPoolingOp(op); 1144 } 1145 static LogicalResult verify(PoolingSumOp op) { 1146 return verifySingleInputPoolingOp(op); 1147 } 1148 1149 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOpsInterfaces.cpp.inc" 1150 1151 #include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.cpp.inc" 1152 1153 #define GET_OP_CLASSES 1154 #include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc" 1155 1156 #define GET_OP_CLASSES 1157 #include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc" 1158 1159 /// Return the dims that are `iteratorTypeName` loops in the LinalgOp `op`. 1160 /// Assumes `op` is a LinalgOp. 1161 void mlir::linalg::getDimsOfType(Operation *op, StringRef iteratorTypeName, 1162 SmallVectorImpl<AffineExpr> &res) { 1163 if (!cast<LinalgOp>(op).iterator_types()) 1164 return; 1165 1166 unsigned dim = 0; 1167 MLIRContext *ctx = op->getContext(); 1168 for (auto tn : 1169 cast<LinalgOp>(op).iterator_types().getAsValueRange<StringAttr>()) { 1170 if (tn == iteratorTypeName) 1171 res.push_back(getAffineDimExpr(dim, ctx)); 1172 ++dim; 1173 } 1174 } 1175 1176 AffineMap mlir::linalg::extractOrIdentityMap(Optional<AffineMap> maybeMap, 1177 unsigned rank, 1178 MLIRContext *context) { 1179 if (maybeMap) 1180 return maybeMap.getValue(); 1181 if (rank == 0) 1182 return AffineMap::get(context); 1183 return AffineMap::getMultiDimIdentityMap(rank, context); 1184 } 1185 1186 SmallVector<AffineExpr, 4> 1187 mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx, 1188 MLIRContext *context) { 1189 SmallVector<AffineExpr, 4> res; 1190 res.reserve(num); 1191 for (unsigned i = 0; i < num; ++i) 1192 res.push_back(getAffineDimExpr(startIdx++, context)); 1193 return res; 1194 } 1195 1196 template <typename PoolingOp> 1197 SmallVector<AffineExpr, 4> 1198 mlir::linalg::weightedPoolingInputIndex(PoolingOp op, 1199 ArrayRef<AffineExpr> outputDims, 1200 ArrayRef<AffineExpr> windowDims) { 1201 assert(outputDims.size() == windowDims.size()); 1202 SmallVector<AffineExpr, 4> res; 1203 res.reserve(outputDims.size()); 1204 for (unsigned i = 0, e = outputDims.size(); i < e; ++i) { 1205 // TODO: add a level of indirection to linalg.generic. 1206 auto expr = op.getStride(i) * outputDims[i] + 1207 op.getDilation(i) * windowDims[i] - op.getLowPad(i); 1208 res.push_back(expr); 1209 } 1210 return res; 1211 } 1212 1213 #define INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(OP_TYPE) \ 1214 template SmallVector<AffineExpr, 4> \ 1215 mlir::linalg::weightedPoolingInputIndex<OP_TYPE>( \ 1216 OP_TYPE op, ArrayRef<AffineExpr> outputDims, \ 1217 ArrayRef<AffineExpr> windowDims); 1218 1219 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(ConvOp) 1220 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMaxOp) 1221 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingMinOp) 1222 INSTANTIATE_WEIGHTED_POOLING_INPUT_INDEX(PoolingSumOp) 1223 1224 SmallVector<AffineExpr, 4> mlir::linalg::concat(ArrayRef<AffineExpr> a, 1225 ArrayRef<AffineExpr> b) { 1226 auto rangeA = llvm::make_range(a.begin(), a.end()); 1227 auto rangeB = llvm::make_range(b.begin(), b.end()); 1228 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB); 1229 return llvm::to_vector<4>(concatRanges); 1230 } 1231 1232 static void appendMangledType(llvm::raw_string_ostream &ss, Type t) { 1233 if (auto memref = t.dyn_cast<MemRefType>()) { 1234 ss << "view"; 1235 for (auto size : memref.getShape()) 1236 if (size < 0) 1237 ss << "sx"; 1238 else 1239 ss << size << "x"; 1240 appendMangledType(ss, memref.getElementType()); 1241 } else if (auto vec = t.dyn_cast<VectorType>()) { 1242 ss << "vector"; 1243 llvm::interleave( 1244 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; }); 1245 appendMangledType(ss, vec.getElementType()); 1246 } else if (t.isSignlessIntOrIndexOrFloat()) { 1247 ss << t; 1248 } else { 1249 llvm_unreachable("Invalid type for linalg library name mangling"); 1250 } 1251 } 1252 1253 std::string mlir::linalg::generateLibraryCallName(Operation *op) { 1254 assert(isa<LinalgOp>(op)); 1255 std::string name(op->getName().getStringRef().str()); 1256 name.reserve(128); 1257 std::replace(name.begin(), name.end(), '.', '_'); 1258 llvm::raw_string_ostream ss(name); 1259 ss << "_"; 1260 auto types = op->getOperandTypes(); 1261 llvm::interleave( 1262 types.begin(), types.end(), [&](Type t) { appendMangledType(ss, t); }, 1263 [&]() { ss << "_"; }); 1264 return ss.str(); 1265 } 1266 1267 // TODO: Consider making all this boilerplate easy to autogenerate 1268 // with Tablegen. This seems a desirable property in the context of 1269 // OpInterfaces where a Linalg "named" op **isa** LinalgOp. 1270 OpFoldResult ReshapeOp::fold(ArrayRef<Attribute> operands) { 1271 if (succeeded(foldMemRefCast(*this))) 1272 return getResult(); 1273 return foldReshapeOp(*this, operands); 1274 } 1275 OpFoldResult SliceOp::fold(ArrayRef<Attribute>) { 1276 if (succeeded(foldMemRefCast(*this))) 1277 return getResult(); 1278 return {}; 1279 } 1280 OpFoldResult TensorReshapeOp::fold(ArrayRef<Attribute> operands) { 1281 return foldReshapeOp(*this, operands); 1282 } 1283 1284 //===----------------------------------------------------------------------===// 1285 // Auto-generated Linalg named ops. 1286 //===----------------------------------------------------------------------===// 1287 1288 template <typename NamedStructuredOpType> 1289 static void buildNamedStructuredOpRegionAndAttributesImpl( 1290 OpBuilder &opBuilder, Region ®ion, TypeRange inputTypes, 1291 TypeRange outputBufferTypes, TypeRange initTensorTypes, 1292 TypeRange resultTypes, 1293 std::function<void(unsigned, unsigned)> errorHandler) { 1294 // TODO: atm all operands go through getElementTypeOrSelf, 1295 // reconsider when we have evidence we need to. 1296 SmallVector<Type, 8> argTypes; 1297 for (auto containers : {inputTypes, outputBufferTypes, resultTypes}) 1298 for (auto t : containers) 1299 argTypes.push_back(getElementTypeOrSelf(t)); 1300 1301 // RAII. 1302 OpBuilder::InsertionGuard guard(opBuilder); 1303 Block *body = opBuilder.createBlock(®ion, {}, argTypes); 1304 unsigned actual = body->getNumArguments(); 1305 unsigned expected = NamedStructuredOpType::getNumRegionArgs(); 1306 if (expected != actual) 1307 return errorHandler(expected, actual); 1308 1309 opBuilder.setInsertionPointToStart(body); 1310 mlir::edsc::ScopedContext scope(opBuilder, opBuilder.getUnknownLoc()); 1311 NamedStructuredOpType::regionBuilder(*body); 1312 1313 // indexing_maps is an auto-generated method. 1314 1315 // iterator_types is an auto-generated method. 1316 } 1317 1318 template <typename NamedStructuredOpType> 1319 void buildNamedStructuredOpRegionAndAttributes(OpBuilder &opBuilder, 1320 OperationState &result, 1321 TypeRange inputTypes, 1322 TypeRange outputBufferTypes, 1323 TypeRange initTensorTypes, 1324 TypeRange resultTypes) { 1325 Region ®ion = *result.addRegion(); 1326 buildNamedStructuredOpRegionAndAttributesImpl<NamedStructuredOpType>( 1327 opBuilder, region, inputTypes, outputBufferTypes, initTensorTypes, 1328 resultTypes, [&](unsigned expected, unsigned actual) { 1329 llvm::errs() << "region expects " << expected << " args, got " 1330 << actual; 1331 assert(expected != actual && "incorrect number of arguments"); 1332 }); 1333 } 1334 1335 template <typename NamedStructuredOpType> 1336 static ParseResult 1337 parseNamedStructuredOpRegion(OpAsmParser &parser, Region ®ion, 1338 TypeRange inputTypes, TypeRange outputBufferTypes, 1339 TypeRange initTensorTypes, TypeRange resultTypes) { 1340 ParseResult res = success(); 1341 OpBuilder opBuilder(parser.getBuilder().getContext()); 1342 buildNamedStructuredOpRegionAndAttributesImpl<NamedStructuredOpType>( 1343 opBuilder, region, inputTypes, outputBufferTypes, initTensorTypes, 1344 resultTypes, [&](unsigned expected, unsigned actual) { 1345 res = parser.emitError(parser.getCurrentLocation(), 1346 llvm::formatv("region expects {0} args, got {1}", 1347 expected, actual)); 1348 }); 1349 return res; 1350 } 1351 1352 static ParseResult 1353 parseNamedStructuredOpResults(OpAsmParser &parser, 1354 SmallVectorImpl<Type> &resultTypes) { 1355 if (succeeded(parser.parseOptionalArrow())) 1356 if (parser.parseTypeList(resultTypes)) 1357 return failure(); 1358 return success(); 1359 } 1360 1361 static ParseResult 1362 parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, 1363 SmallVectorImpl<Type> &inputTypes, 1364 SmallVectorImpl<Type> &outputBufferTypes, 1365 SmallVectorImpl<Type> &initTensorTypes) { 1366 llvm::SMLoc inputsOperandsLoc, outputBuffersOperandsLoc, 1367 initTensorsOperandsLoc; 1368 SmallVector<OpAsmParser::OperandType, 4> inputsOperands, 1369 outputBuffersOperands, initTensorsOperands; 1370 1371 parser.parseOptionalAttrDict(result.attributes); 1372 1373 if (succeeded(parser.parseOptionalKeyword("ins"))) { 1374 if (parser.parseLParen()) 1375 return failure(); 1376 1377 inputsOperandsLoc = parser.getCurrentLocation(); 1378 if (parser.parseOperandList(inputsOperands) || 1379 parser.parseColonTypeList(inputTypes) || parser.parseRParen()) 1380 return failure(); 1381 } 1382 1383 if (succeeded(parser.parseOptionalKeyword("outs"))) { 1384 outputBuffersOperandsLoc = parser.getCurrentLocation(); 1385 if (parser.parseLParen() || 1386 parser.parseOperandList(outputBuffersOperands) || 1387 parser.parseColonTypeList(outputBufferTypes) || parser.parseRParen()) 1388 return failure(); 1389 } 1390 if (succeeded(parser.parseOptionalKeyword("init"))) { 1391 initTensorsOperandsLoc = parser.getCurrentLocation(); 1392 if (parser.parseLParen() || parser.parseOperandList(initTensorsOperands) || 1393 parser.parseColonTypeList(initTensorTypes) || parser.parseRParen()) 1394 return failure(); 1395 } 1396 1397 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc, 1398 result.operands) || 1399 parser.resolveOperands(outputBuffersOperands, outputBufferTypes, 1400 outputBuffersOperandsLoc, result.operands) || 1401 parser.resolveOperands(initTensorsOperands, initTensorTypes, 1402 initTensorsOperandsLoc, result.operands)) 1403 return failure(); 1404 1405 result.addAttribute("operand_segment_sizes", 1406 parser.getBuilder().getI32VectorAttr( 1407 {static_cast<int32_t>(inputsOperands.size()), 1408 static_cast<int32_t>(outputBuffersOperands.size()), 1409 static_cast<int32_t>(initTensorsOperands.size())})); 1410 return success(); 1411 } 1412 1413 template <typename NamedStructuredOpType> 1414 static ParseResult parseNamedStructuredOp(OpAsmParser &parser, 1415 OperationState &result) { 1416 SmallVector<Type, 1> inputTypes, outputBufferTypes, initTensorTypes; 1417 if (parseCommonStructuredOpParts(parser, result, inputTypes, 1418 outputBufferTypes, initTensorTypes)) 1419 return failure(); 1420 1421 // TODO: consider merging results parsing into region parsing. 1422 // Need to wait for declarative assembly resolution to decide. 1423 SmallVector<Type, 1> outputTensorsTypes; 1424 if (parseNamedStructuredOpResults(parser, outputTensorsTypes)) 1425 return failure(); 1426 result.addTypes(outputTensorsTypes); 1427 1428 std::unique_ptr<Region> region = std::make_unique<Region>(); 1429 if (parseNamedStructuredOpRegion<NamedStructuredOpType>( 1430 parser, *region, inputTypes, outputBufferTypes, initTensorTypes, 1431 outputTensorsTypes)) 1432 return failure(); 1433 result.addRegion(std::move(region)); 1434 1435 return success(); 1436 } 1437 1438 static void printNamedStructuredOpResults(OpAsmPrinter &p, 1439 TypeRange resultTypes) { 1440 if (resultTypes.empty()) 1441 return; 1442 p.printOptionalArrowTypeList(resultTypes); 1443 } 1444 1445 template <typename NamedStructuredOpType> 1446 static void printCommonStructuredOpParts(OpAsmPrinter &p, 1447 NamedStructuredOpType op) { 1448 p << " ins(" << op.inputs() << " : " << op.inputs().getTypes() << ")"; 1449 if (!op.output_buffers().empty()) 1450 p << " outs(" << op.output_buffers() << " : " 1451 << op.output_buffers().getTypes() << ")"; 1452 if (!op.init_tensors().empty()) 1453 p << " init(" << op.init_tensors() << " : " << op.init_tensors().getTypes() 1454 << ") "; 1455 } 1456 1457 template <typename NamedStructuredOpType> 1458 static void printNamedStructuredOp(OpAsmPrinter &p, NamedStructuredOpType op) { 1459 p << op.getOperationName(); 1460 p.printOptionalAttrDict(op.getAttrs(), 1461 /*elidedAttrs=*/{"operand_segment_sizes"}); 1462 1463 // Printing is shared with generic ops, except for the region and attributes. 1464 printCommonStructuredOpParts(p, op); 1465 1466 // Results printing. 1467 printNamedStructuredOpResults(p, op.result_tensors().getTypes()); 1468 1469 // Region is elided. 1470 } 1471 1472 template <typename NamedStructuredOpType> 1473 static LogicalResult verifyNamedStructuredOp(NamedStructuredOpType op) { 1474 return verifyGenericOp<NamedStructuredOpType>(op); 1475 } 1476 1477 namespace { 1478 struct EraseDeadLinalgOp : public RewritePattern { 1479 EraseDeadLinalgOp(PatternBenefit benefit = 1) 1480 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 1481 1482 LogicalResult matchAndRewrite(Operation *op, 1483 PatternRewriter &rewriter) const override { 1484 auto linalgOp = dyn_cast<LinalgOp>(op); 1485 if (!linalgOp) 1486 return failure(); 1487 for (Value v : linalgOp.getInputsAndOutputBuffers()) { 1488 // Linalg "inputs" may be either tensor or memref type. 1489 // tensor<0xelt_type> is a convention that may not always mean 1490 // "0 iterations". Only erase in cases we see memref<...x0x...>. 1491 auto mt = v.getType().dyn_cast<MemRefType>(); 1492 if (!mt) 1493 continue; 1494 if (llvm::is_contained(mt.getShape(), 0)) { 1495 rewriter.eraseOp(linalgOp); 1496 return success(); 1497 } 1498 } 1499 return failure(); 1500 } 1501 }; 1502 1503 struct FoldTensorCastOp : public RewritePattern { 1504 FoldTensorCastOp(PatternBenefit benefit = 1) 1505 : RewritePattern(benefit, MatchAnyOpTypeTag()) {} 1506 1507 LogicalResult matchAndRewrite(Operation *op, 1508 PatternRewriter &rewriter) const override { 1509 auto linalgOp = dyn_cast<LinalgOp>(op); 1510 if (!linalgOp) 1511 return failure(); 1512 1513 // If no operand comes from a TensorCastOp and can be folded then fail. 1514 bool hasTensorCastOperand = 1515 llvm::any_of(linalgOp.getShapedOperands(), [&](Value v) { 1516 if (v.isa<BlockArgument>()) 1517 return false; 1518 auto castOp = v.getDefiningOp<TensorCastOp>(); 1519 return castOp && canFoldIntoConsumerOp(castOp); 1520 }); 1521 if (!hasTensorCastOperand) 1522 return failure(); 1523 1524 SmallVector<Type, 4> newResultTypes; 1525 newResultTypes.reserve(op->getNumResults()); 1526 SmallVector<Value, 4> newOperands; 1527 newOperands.reserve(op->getNumOperands()); 1528 // Inputs may fold. 1529 for (Value v : linalgOp.getInputs()) { 1530 auto tensorCastOp = v.getDefiningOp<TensorCastOp>(); 1531 newOperands.push_back( 1532 canFoldIntoConsumerOp(tensorCastOp) ? tensorCastOp.source() : v); 1533 } 1534 // Output buffers are memrefs, they don't fold. 1535 newOperands.append(linalgOp.getOutputBuffers().begin(), 1536 linalgOp.getOutputBuffers().end()); 1537 // Init tensors may fold, in which case the resultType must also change. 1538 for (Value v : linalgOp.getInitTensors()) { 1539 auto tensorCastOp = v.getDefiningOp<TensorCastOp>(); 1540 bool fold = canFoldIntoConsumerOp(tensorCastOp); 1541 newOperands.push_back(fold ? tensorCastOp.getOperand() : v); 1542 newResultTypes.push_back(newOperands.back().getType()); 1543 } 1544 auto extraOperands = linalgOp.getAssumedNonShapedOperands(); 1545 newOperands.append(extraOperands.begin(), extraOperands.end()); 1546 // Clone op. 1547 Operation *newOp = 1548 linalgOp.clone(rewriter, op->getLoc(), newResultTypes, newOperands); 1549 rewriter.replaceOp(op, newOp->getResults()); 1550 1551 return success(); 1552 } 1553 }; 1554 } // namespace 1555 1556 #define CANONICALIZERS_AND_FOLDERS(XXX) \ 1557 void XXX::getCanonicalizationPatterns(OwningRewritePatternList &results, \ 1558 MLIRContext *context) { \ 1559 results.insert<EraseDeadLinalgOp>(); \ 1560 results.insert<FoldTensorCastOp>(); \ 1561 } \ 1562 \ 1563 LogicalResult XXX::fold(ArrayRef<Attribute>, \ 1564 SmallVectorImpl<OpFoldResult> &) { \ 1565 return foldMemRefCast(*this); \ 1566 } 1567 1568 CANONICALIZERS_AND_FOLDERS(ConvOp) 1569 CANONICALIZERS_AND_FOLDERS(PoolingMaxOp) 1570 CANONICALIZERS_AND_FOLDERS(PoolingMinOp) 1571 CANONICALIZERS_AND_FOLDERS(PoolingSumOp) 1572 CANONICALIZERS_AND_FOLDERS(CopyOp) 1573 CANONICALIZERS_AND_FOLDERS(FillOp) 1574 CANONICALIZERS_AND_FOLDERS(GenericOp) 1575 CANONICALIZERS_AND_FOLDERS(IndexedGenericOp) 1576 1577 // TODO: Determine whether we can generate the folders and verifiers. 1578 CANONICALIZERS_AND_FOLDERS(BatchMatmulOp) 1579 CANONICALIZERS_AND_FOLDERS(DotOp) 1580 CANONICALIZERS_AND_FOLDERS(MatmulOp) 1581 CANONICALIZERS_AND_FOLDERS(MatvecOp) 1582 CANONICALIZERS_AND_FOLDERS(VecmatOp) 1583 CANONICALIZERS_AND_FOLDERS(ConvWOp) 1584 CANONICALIZERS_AND_FOLDERS(ConvNWCOp) 1585 CANONICALIZERS_AND_FOLDERS(ConvNCWOp) 1586 CANONICALIZERS_AND_FOLDERS(ConvHWOp) 1587 CANONICALIZERS_AND_FOLDERS(ConvNHWCOp) 1588 CANONICALIZERS_AND_FOLDERS(ConvNCHWOp) 1589 CANONICALIZERS_AND_FOLDERS(ConvDHWOp) 1590 CANONICALIZERS_AND_FOLDERS(ConvNDHWCOp) 1591 CANONICALIZERS_AND_FOLDERS(ConvNCDHWOp) 1592