1 //===- Fusion.cpp - Implementation of linalg Fusion -----------------------===// 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 dialect Fusion on tensors operations pass. 10 // 11 //===----------------------------------------------------------------------===// 12 #include "PassDetail.h" 13 #include "mlir/Dialect/Affine/IR/AffineOps.h" 14 #include "mlir/Dialect/Linalg/IR/LinalgOps.h" 15 #include "mlir/Dialect/Linalg/IR/LinalgTypes.h" 16 #include "mlir/Dialect/Linalg/Passes.h" 17 #include "mlir/Dialect/Linalg/Transforms/Transforms.h" 18 #include "mlir/Dialect/Linalg/Utils/Utils.h" 19 #include "mlir/IR/AffineExpr.h" 20 #include "mlir/IR/AffineMap.h" 21 #include "mlir/IR/PatternMatch.h" 22 #include "mlir/Support/LLVM.h" 23 #include "mlir/Transforms/GreedyPatternRewriteDriver.h" 24 25 using namespace mlir; 26 using namespace mlir::linalg; 27 28 /// Implementation of fusion of generic ops and indexed_generic ops. 29 static bool areTensorOpsFusable(LinalgOp producer, LinalgOp consumer, 30 unsigned consumerIdx) { 31 // Producer and consumer must have tensor semantics. 32 if (!producer.hasTensorSemantics() || !consumer.hasTensorSemantics()) 33 return false; 34 35 // Verify that 36 // - the producer has all "parallel" iterator type. 37 if (producer.getNumParallelLoops() != producer.getNumLoops()) 38 return false; 39 40 // Only allow fusing the producer of an input operand for now. 41 // TODO: allow fusing the producer of an output operand. 42 if (consumerIdx >= consumer.getNumInputs()) 43 return false; 44 45 // Get the consumer index map. The number of results of the consumer index 46 // map must match the number of loops of the producer. 47 AffineMap consumerIndexMap = consumer.getIndexingMap(consumerIdx); 48 if (consumerIndexMap.getNumResults() != producer.getNumLoops()) 49 return false; 50 51 // Finally the index_map for the result must be invertible. For now just 52 // verify it is a permutation. 53 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 54 return producerResultIndexMap.isPermutation(); 55 } 56 57 /// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of 58 /// the `producer` to use in the fused operation given the indexing map of the 59 /// result of the producer in the consumer. 60 static void getIndexingMapOfProducerOperandsInFusedOp( 61 LinalgOp producer, AffineMap fusedConsumerArgIndexMap, 62 SmallVectorImpl<Attribute> &fusedOpIndexingMapAttrs) { 63 // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map 64 // from consumer loop -> consumer arg tensor index/producer result tensor 65 // index. The fused loop is same as the consumer loop. For each producer arg 66 // the indexing map to be computed is a map from consumer loop -> producer 67 // arg tensor index. 68 69 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 70 // producerResultIndexMap is a map from producer loop -> tensor index. 71 // Compute the inverse to get map from tensor index -> producer loop. 72 // The inverse is a map from producer result tensor index -> producer loop. 73 AffineMap invProducerResultIndexMap = 74 inversePermutation(producerResultIndexMap); 75 assert(invProducerResultIndexMap && 76 "expected producer result indexig map to be invertible"); 77 for (unsigned argNum : llvm::seq<unsigned>(0, producer.getNumInputs())) { 78 // argMap is a map from producer loop -> producer arg tensor index. 79 AffineMap argMap = producer.getInputIndexingMap(argNum); 80 81 // Compose argMap with invProducerResultIndexMap to get a map from 82 // producer result tensor index -> producer arg tensor index. 83 AffineMap t1 = argMap.compose(invProducerResultIndexMap); 84 85 // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from 86 // consumer loop/ fused loop -> producer arg tensor index. 87 AffineMap indexingMap = t1.compose(fusedConsumerArgIndexMap); 88 fusedOpIndexingMapAttrs.push_back(AffineMapAttr::get(indexingMap)); 89 } 90 } 91 92 /// Generate the region of the fused tensor operation. The region of the fused 93 /// op must be empty. 94 static void generateFusedTensorOpRegion(PatternRewriter &rewriter, 95 Operation *fusedOp, LinalgOp producer, 96 LinalgOp consumer, 97 AffineMap consumerToProducerLoopsMap, 98 unsigned consumerIdx, unsigned nloops) { 99 // Build the region of the fused op. 100 Block &producerBlock = producer->getRegion(0).front(); 101 Block &consumerBlock = consumer->getRegion(0).front(); 102 Block *fusedBlock = new Block(); 103 fusedOp->getRegion(0).push_back(fusedBlock); 104 BlockAndValueMapping mapper; 105 OpBuilder::InsertionGuard guard(rewriter); 106 rewriter.setInsertionPointToStart(fusedBlock); 107 108 // The block arguments are 109 // [index_0, index_1, ... , 110 // consumer_operand_0, ... , consumer_operand_(`consumerIdx`-1), 111 // producer_operand_0, ... , producer_operand_(n-1)], 112 // consumer_operand_(`consumerIdx`), .. consumer_operand_(m-1)] 113 // , where n is the number of producer's operand and m is the number 114 // consumer's operand. 115 // If both `numProducerIndices` and `numConsumerIndices` are zero, this is a 116 // generic op. In this case, there are no indices in block arguments. 117 unsigned numProducerIndices = isa<IndexedGenericOp>(producer.getOperation()) 118 ? producer.getNumLoops() 119 : 0; 120 unsigned numConsumerIndices = isa<IndexedGenericOp>(consumer.getOperation()) 121 ? consumer.getNumLoops() 122 : 0; 123 unsigned numFusedOpIndices = 124 (isa<IndexedGenericOp>(producer.getOperation()) || 125 isa<IndexedGenericOp>(consumer.getOperation())) 126 ? std::max(producer.getNumLoops(), consumer.getNumLoops()) 127 : 0; 128 129 // 0. Firstly, add all the indices to the block arguments. 130 for (unsigned i = 0, e = numFusedOpIndices; i < e; ++i) 131 fusedBlock->addArgument(rewriter.getIndexType()); 132 // 1. Map consumer indices to fusedBlock indices 1-1. 133 mapper.map(consumerBlock.getArguments().take_front(numConsumerIndices), 134 fusedBlock->getArguments().take_front(numConsumerIndices)); 135 // 2. Embed producer indices into fusedBlock index space 1-1. 136 for (auto it : 137 llvm::zip(producerBlock.getArguments().take_front(numProducerIndices), 138 fusedBlock->getArguments().take_front(numProducerIndices))) { 139 auto newIndex = rewriter.create<mlir::AffineApplyOp>( 140 producer.getLoc(), 141 consumerToProducerLoopsMap.getSubMap(std::get<0>(it).getArgNumber()), 142 fusedBlock->getArguments().take_front(numFusedOpIndices)); 143 mapper.map(std::get<0>(it), newIndex); 144 } 145 // TODO: allow fusing the producer of an output operand. 146 assert(consumerIdx < consumer.getNumInputs() && 147 "expected producer of input operand"); 148 // 3. Consumer input operands up to consumerIdx (exclusive). 149 for (BlockArgument bbArg : consumerBlock.getArguments() 150 .drop_front(numConsumerIndices) 151 .take_front(consumerIdx)) // input assumption. 152 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType())); 153 154 // Replacing consumerIdx requires getting the cloned, yielded, value from 155 // the (cloned) producer block. This happens in step 9. 156 157 // 4. Splice in producer's input operands. 158 for (BlockArgument bbArg : producerBlock.getArguments() 159 .drop_front(numProducerIndices) 160 .take_front(producer.getNumInputs())) 161 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType())); 162 // 5. Remaining consumer's input operands (drop past index `consumerIdx`). 163 for (BlockArgument bbArg : consumerBlock.getArguments() 164 .drop_front(numConsumerIndices) 165 .take_front(consumer.getNumInputs()) 166 .drop_front(consumerIdx + 1)) 167 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType())); 168 // 6. All of consumer's output operands. 169 for (BlockArgument bbArg : 170 consumerBlock.getArguments().take_back(consumer.getNumOutputs())) 171 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType())); 172 // 7. All of producer's output operands except the one fused. 173 // TODO: allow fusion of multi-result producers. 174 assert(producer->getNumResults() == 1 && "expected single result producer"); 175 176 // 8. Clone operations from producer (except the yield operation) to the fused 177 // op. 178 for (auto &op : producerBlock.without_terminator()) 179 rewriter.clone(op, mapper); 180 // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just 181 // forward the yield operand. 182 auto yieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator()); 183 // TODO: allow fusion of multi-result producers. 184 assert(producer->getNumResults() == 1 && "expected single result producer"); 185 unsigned producerResultNumber = 0; 186 Value replacement = 187 mapper.lookupOrDefault(yieldOp.getOperand(producerResultNumber)); 188 // Sanity checks, if replacement is not already in the mapper then it must be 189 // produced outside. 190 if (replacement == yieldOp.getOperand(producerResultNumber)) { 191 if (auto bb = replacement.dyn_cast<BlockArgument>()) 192 assert(bb.getOwner() != &producerBlock && 193 "yielded block argument must have been mapped"); 194 else 195 assert(!producer->isAncestor(replacement.getDefiningOp()) && 196 "yielded value must have been mapped"); 197 } 198 mapper.map(consumerBlock.getArgument(consumerIdx + numConsumerIndices), 199 replacement); 200 // 10. Clone operations from the consumer to the fused op. 201 for (auto &op : consumerBlock.getOperations()) 202 rewriter.clone(op, mapper); 203 204 // Sanity checks. 205 assert(fusedBlock->getNumArguments() == 206 fusedOp->getNumOperands() + numFusedOpIndices && 207 "Ill-formed LinalgOp region"); 208 } 209 210 static Optional<SmallVector<Value, 1>> 211 fuseTensorOpsImpl(LinalgOp producer, OpOperand &consumerOpOperand, 212 PatternRewriter &rewriter) { 213 LinalgOp consumer = cast<LinalgOp>(consumerOpOperand.getOwner()); 214 unsigned consumerIdx = consumerOpOperand.getOperandNumber(); 215 if (!areTensorOpsFusable(producer, consumer, consumerIdx)) 216 return llvm::None; 217 218 unsigned numFusedOperands = 219 producer.getNumInputs() + consumer.getNumInputs() - 1; 220 221 // Compute the fused operands list, 222 SmallVector<Value, 2> fusedOperands; 223 fusedOperands.reserve(numFusedOperands); 224 auto consumerOperands = consumer.getInputs(); 225 auto producerOperands = producer.getInputs(); 226 fusedOperands.assign(consumerOperands.begin(), 227 std::next(consumerOperands.begin(), consumerIdx)); 228 fusedOperands.append(producerOperands.begin(), producerOperands.end()); 229 fusedOperands.append(std::next(consumerOperands.begin(), consumerIdx + 1), 230 consumerOperands.end()); 231 232 // Compute indexing_maps for the fused operation. The indexing_maps for the 233 // operands of the consumers that aren't fused are the same. The 234 // indexing_maps for the producers need to be computed based on the 235 // indexing_map of the operand at consumerIdx in the consumer. 236 SmallVector<Attribute, 4> fusedIndexMaps; 237 auto consumerIndexMaps = consumer.indexing_maps(); 238 fusedIndexMaps.reserve(fusedOperands.size() + consumer.getNumOutputs()); 239 fusedIndexMaps.assign(consumerIndexMaps.begin(), 240 std::next(consumerIndexMaps.begin(), consumerIdx)); 241 // Compute indexing maps for the producer args in the fused operation. 242 getIndexingMapOfProducerOperandsInFusedOp( 243 producer, consumer.getInputIndexingMap(consumerIdx), fusedIndexMaps); 244 245 // Append the indexing maps for the remaining consumer operands. 246 fusedIndexMaps.append(std::next(consumerIndexMaps.begin(), consumerIdx + 1), 247 consumerIndexMaps.end()); 248 249 // Generate the fused op. 250 LinalgOp fusedOp; 251 if (isa<GenericOp>(producer.getOperation()) && 252 isa<GenericOp>(consumer.getOperation())) { 253 fusedOp = 254 rewriter 255 .create<GenericOp>(consumer.getLoc(), consumer->getResultTypes(), 256 /*inputs=*/fusedOperands, 257 // TODO: handle outputs. 258 consumer.getOutputs(), 259 rewriter.getArrayAttr(fusedIndexMaps), 260 consumer.iterator_types(), 261 /*doc=*/nullptr, 262 /*library_call=*/nullptr, 263 /*sparse=*/nullptr) 264 .getOperation(); 265 } else { 266 fusedOp = 267 rewriter 268 .create<IndexedGenericOp>( 269 consumer.getLoc(), consumer->getResultTypes(), 270 /*inputs=*/fusedOperands, 271 // TODO: handle outputs. 272 consumer.getOutputs(), rewriter.getArrayAttr(fusedIndexMaps), 273 consumer.iterator_types(), 274 /*doc=*/nullptr, 275 /*library_call=*/nullptr, 276 /*sparse=*/nullptr) 277 .getOperation(); 278 } 279 280 // Construct an AffineMap from consumer loops to producer loops. 281 // consumer loop -> tensor index 282 AffineMap consumerResultIndexMap = consumer.getInputIndexingMap(consumerIdx); 283 // producer loop -> tensor index 284 AffineMap producerResultIndexMap = producer.getOutputIndexingMap(0); 285 // tensor index -> producer loop 286 AffineMap invProducerResultIndexMap = 287 inversePermutation(producerResultIndexMap); 288 assert(invProducerResultIndexMap && 289 "expected producer result indexig map to be invertible"); 290 // consumer loop -> producer loop 291 AffineMap consumerToProducerLoopsMap = 292 invProducerResultIndexMap.compose(consumerResultIndexMap); 293 294 generateFusedTensorOpRegion(rewriter, fusedOp.getOperation(), producer, 295 consumer, consumerToProducerLoopsMap, consumerIdx, 296 consumer.getNumLoops()); 297 return SmallVector<Value, 1>(fusedOp->getResults()); 298 } 299 300 /// Linearize the expressions in `sourceMap` based on the `reassociationMaps` 301 /// provided, given the shape of the source tensor that corresponds to the 302 /// `sourceMap`. Note that this implicitly assumes that the tensors dimensions 303 /// are "row-major" ordered logically. 304 /// 305 /// For example: 306 /// 307 /// %0 = op ... : tensor<?x?x4x5xf32> 308 /// with output index_map `affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)>` 309 /// 310 /// and reshape: 311 /// %1 = linalg.tensor_reshape %0 [affine_map<(i, j, k, l) -> (i)>, 312 /// affine_map<(i, j, k, l) -> (j, k, l)>] : 313 /// tensor<?x?x4x5xf32> into tensor<?x?xf32> 314 /// 315 /// would be rewritten into: 316 /// %0 = op ... : tensor<?x?x4x5xf32> 317 /// with output index_map 318 /// `affine_map<(d0, d1, d2, d3) -> (d0, d1 * 20 + d2 * 5 + d3)>` 319 static AffineMap linearizeCollapsedDims(AffineMap sourceMap, 320 ArrayRef<int64_t> sourceShape, 321 ArrayRef<AffineMap> reassociationMaps) { 322 SmallVector<AffineExpr, 4> resultExprs; 323 resultExprs.reserve(reassociationMaps.size()); 324 ArrayRef<AffineExpr> sourceExprs = sourceMap.getResults(); 325 MLIRContext *context = sourceMap.getContext(); 326 327 // Compute the result exprs based on the reassociation maps. 328 for (AffineMap map : reassociationMaps) { 329 ArrayRef<AffineExpr> collapsedDims = map.getResults(); 330 // Assume that they are in-order and contiguous (already checked in 331 // verifier). 332 assert(!collapsedDims.empty()); 333 unsigned startDim = 334 collapsedDims.front().cast<AffineDimExpr>().getPosition(); 335 SmallVector<int64_t, 4> sizes; 336 SmallVector<AffineExpr, 4> dimExprs; 337 for (auto en : 338 llvm::zip(sourceShape.slice(startDim, collapsedDims.size()), 339 sourceExprs.slice(startDim, collapsedDims.size()))) { 340 if (std::get<0>(en) == 1) 341 continue; 342 sizes.push_back(std::get<0>(en)); 343 dimExprs.push_back(std::get<1>(en)); 344 } 345 AffineExpr linearizedExpr = 346 makeCanonicalStridedLayoutExpr(sizes, dimExprs, context); 347 resultExprs.push_back(linearizedExpr); 348 } 349 return AffineMap::get(sourceMap.getNumDims(), sourceMap.getNumSymbols(), 350 resultExprs, context); 351 } 352 353 /// Checks if the `reshapeOp` can be fused with it consumer (if `asProducer` is 354 /// true) or its producer (if `asProducer` is false) given the indexing map at 355 /// its use. 356 static bool isTensorReshapeOpFoldableByLinearization(TensorReshapeOp reshapeOp, 357 AffineMap useIndexMap, 358 bool asProducer) { 359 RankedTensorType returnType = reshapeOp.getResultType(); 360 RankedTensorType operandType = reshapeOp.getSrcType(); 361 // Reshape is fusable with its consumer (i.e. reshape as a producer) when its 362 // operand is of lesser rank than the result. Fusing when operand has higher 363 // rank will require use of mods and divs in the indexing maps of the fused op 364 // which would make it non-invertible. Similarly reshape is fused with its 365 // producer (i.e. reshape as consumer) only if the return type has lesser 366 // rank. 367 if ((asProducer && reshapeOp.getSrcType().hasStaticShape() && 368 returnType.getRank() < operandType.getRank()) || 369 (!asProducer && reshapeOp.getResultType().hasStaticShape() && 370 operandType.getRank() < returnType.getRank())) 371 return false; 372 return useIndexMap.isPermutation(); 373 } 374 375 /// Based on the type of `op` create a linalg op of the same type, i.e. if `op` 376 /// is a linalg.generic operation, the create a `linalg.generic` operation with 377 /// the given `args`. Expects `op` to be `linalg.generic` or 378 /// `linalg.indexed_generic`. 379 template <typename... Args> 380 static LinalgOp createLinalgOpOfSameType(LinalgOp op, PatternRewriter &rewriter, 381 Args... args) { 382 if (isa<GenericOp>(op.getOperation())) 383 return rewriter.create<GenericOp>(args...); 384 if (isa<IndexedGenericOp>(op.getOperation())) 385 return rewriter.create<IndexedGenericOp>(args...); 386 llvm_unreachable( 387 "expected only linalg.generic or linalg.indexed_generic ops"); 388 return nullptr; 389 } 390 391 /// Check if the reshape operation is only expansion into/collapsing of 392 /// unit-dimension. 393 static bool isUnitDimExpansionOnly(ArrayRef<int64_t> expandedShape, 394 ArrayRef<AffineMap> reassociation) { 395 for (auto &map : reassociation) { 396 unsigned numUnitDims = 0; 397 for (AffineExpr expr : map.getResults()) { 398 unsigned position = expr.cast<AffineDimExpr>().getPosition(); 399 if (expandedShape[position] == 1) 400 numUnitDims++; 401 } 402 if (numUnitDims != map.getNumResults() - 1) 403 return false; 404 } 405 return true; 406 } 407 408 /// Conditions for folding a generic/indexed-generic operation with a reshape op 409 /// by expanding the iteration space dimensionality for tensor operations. These 410 /// are preconditions assumed by `foldReshapeByDimExpansion` which implements 411 /// the following fusion pattern. 412 /// 413 /// Consider 414 /// 415 /// %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>) 416 /// indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>, 417 /// affine_map<(d0, d1, d2) -> (d1, d2)>, 418 /// affine_map<(d0, d1, d2) -> (d0, d2, d1)>] 419 /// %d = linalg.tensor_reshape %c 420 /// [affine_map<(d0, d1, d2, d3, d4, d5) -> (d0, d1)>, 421 /// affine_map<(d0, d1, d2, d3, d4, d5) -> (d2)>, 422 /// affine_map<(d0, d1, d2, d3, d4, d5) -> (d3, d4, d5)>] 423 /// : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32> 424 /// 425 /// The reshape can be folded into the `linalgOp` if the 426 /// generic/indexed-generic op loop dimensionality is increased to match the 427 /// result (operand) of the tensor_reshape when the reshape is expanding 428 /// (folding). The indexing_map of the fused tensor in the `linalgOp` and the 429 /// reassociation map helps compute the indexing maps of the modified op. For 430 /// the above example, based on the reassociation map it can be concluded that 431 /// 432 /// - The loop used to access the first dimension of the fused tensor is split 433 /// into two. 434 /// - The loop used to access the second dimension of the fused tensor is kept 435 /// as is. 436 /// - The loop used to access the third dimension of the fused tensor is split 437 /// into three. 438 /// 439 /// i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified 440 /// op, then 441 /// 442 /// d0 -> e0, e1 443 /// d1 -> e2, e3, e4 444 /// d2 -> e5 445 /// 446 /// substituting this, the generic op can be rewritten as 447 /// 448 /// %d = linalg.generic ins(%0, %1 : ) 449 /// indexing_maps = 450 /// [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>, 451 /// affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>, 452 /// affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>] 453 /// 454 /// Since operands to the linalg generic are now 5D, reshapes can be introduced 455 /// to make it consistent 456 /// 457 /// %0 = linalg.tensor_reshape %a 458 /// [affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e2), 459 /// affine_map<(e0, e1, e2, e3, e4, e5) -> (e3, e4), 460 /// affine_map<(e0, e1, e2, e3, e4, e5) -> (e5)] 461 /// : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32> 462 /// %1 = linalg.tensor_reshape %b 463 /// [affine_map<(e0, e1, e2, e3) -> (e0, e1, e2), 464 /// affine_map<(e0, e1, e2, e3) -> (e3)] 465 /// : tensor<?x?x?xf32> into tensor<?x?x?x?xf32> 466 /// 467 /// The added reshapes are again expanding patterns, so they will get fused 468 /// with its producers if possible. 469 static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp, 470 unsigned fusedTensorIndex) { 471 // Is fusable only if: 472 // - The linalgOp is a generic op, or an indexed_generic. 473 // - All the indexing maps for operands and results in linalgOp are projected 474 // permutations. 475 // - The fused tensor is not a scalar. 476 // - All the loops in linalgOp are parallel loops. 477 return isa<GenericOp, IndexedGenericOp>(linalgOp.getOperation()) && 478 linalgOp.hasTensorSemantics() && 479 llvm::all_of(linalgOp.indexing_maps().getValue(), 480 [](Attribute attr) { 481 return attr.cast<AffineMapAttr>() 482 .getValue() 483 .isProjectedPermutation(); 484 }) && 485 linalgOp.getIndexingMap(fusedTensorIndex).getNumResults() > 0 && 486 llvm::all_of(linalgOp.iterator_types(), [](Attribute attr) { 487 return attr.cast<StringAttr>().getValue() == 488 getParallelIteratorTypeName(); 489 }); 490 } 491 492 namespace { 493 /// Information needed to expand a generic/indexed_generic operation to fold the 494 /// reshape with it. 495 class ExpansionInfo { 496 public: 497 // Computes the mapping from original dimensions of the op to the dimensions 498 // of the expanded op given the `indexingMap` of the fused operand/result of 499 // the generic/indexed_generic op, the `reassocationMaps` of the reshape op 500 // and the shape of the expanded op. 501 LogicalResult compute(LinalgOp linalgOp, unsigned fusedTensorIndex, 502 ArrayRef<AffineMap> reassociationMaps, 503 ArrayRef<int64_t> expandedShape); 504 unsigned getOrigOpNumDims() const { return reassociation.size(); } 505 unsigned getExpandedOpNumDims() const { return expandedOpNumDims; } 506 ReassociationIndicesRef getExpandedDims(unsigned i) const { 507 return reassociation[i]; 508 } 509 ArrayRef<int64_t> getExpandedShapeOfDim(unsigned i) const { 510 return expandedShapeMap[i]; 511 } 512 513 private: 514 /// Reassociation from the dimensions in the original operation to the 515 /// dimension of the expanded operation. 516 SmallVector<ReassociationIndices, 4> reassociation; 517 /// Mapping from extent of loops in the original operation, to the extent of 518 /// loops in the expanded operation. 519 SmallVector<SmallVector<int64_t, 4>, 4> expandedShapeMap; 520 unsigned expandedOpNumDims; 521 }; 522 } // namespace 523 524 LogicalResult ExpansionInfo::compute(LinalgOp linalgOp, 525 unsigned fusedTensorIndex, 526 ArrayRef<AffineMap> reassociationMaps, 527 ArrayRef<int64_t> expandedShape) { 528 if (reassociationMaps.empty()) 529 return failure(); 530 AffineMap fusedIndexMap = linalgOp.getIndexingMap(fusedTensorIndex); 531 532 Optional<SmallVector<int64_t, 4>> originalLoopRange = 533 linalgOp.getStaticLoopRanges(); 534 if (!originalLoopRange) 535 return linalgOp.emitError("unable to find loop range for operation"); 536 537 reassociation.clear(); 538 expandedShapeMap.clear(); 539 // Compute the number of dimension in the expanded op that correspond to each 540 // dimension of the original op. 541 SmallVector<unsigned, 4> numExpandedDims(fusedIndexMap.getNumDims(), 1); 542 expandedShapeMap.resize(fusedIndexMap.getNumDims()); 543 for (auto resultExpr : llvm::enumerate(fusedIndexMap.getResults())) { 544 unsigned pos = resultExpr.value().cast<AffineDimExpr>().getPosition(); 545 AffineMap foldedDims = reassociationMaps[resultExpr.index()]; 546 numExpandedDims[pos] = foldedDims.getNumResults(); 547 ArrayRef<int64_t> shape = 548 expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]); 549 expandedShapeMap[pos].assign(shape.begin(), shape.end()); 550 } 551 // The remaining dimensions remain the same. 552 for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims())) 553 if (expandedShapeMap[i].empty()) 554 expandedShapeMap[i] = {(*originalLoopRange)[i]}; 555 556 // Compute reassociation map from the original op to the expanded op. 557 unsigned sum = 0; 558 reassociation.reserve(fusedIndexMap.getNumDims()); 559 for (auto numFoldedDim : llvm::enumerate(numExpandedDims)) { 560 auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value()); 561 reassociation.emplace_back(seq.begin(), seq.end()); 562 sum += numFoldedDim.value(); 563 } 564 expandedOpNumDims = sum; 565 return success(); 566 } 567 568 /// To expand an indexed_generic operation, the body of the indexed generic op 569 /// need to be modified appropriately. Specifically, uses of arguments for 570 /// induction variables in the original operation need to be replaced with 571 /// linearization of the corresponding arguments in the expanded op. That 572 /// requires the shape of the expanded dimensions (at least all but the most 573 /// significant. For now check that these are all statically sized. Note that 574 /// this could be extended to handle dynamic case, but the implementation below 575 /// uses `affine.apply` which seems to have issues when the shapes are not 576 /// static. 577 LogicalResult isIndexedGenericOpExpandable(LinalgOp linalgOp, 578 const ExpansionInfo &expansionInfo) { 579 for (unsigned i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) { 580 ArrayRef<int64_t> expandedShape = expansionInfo.getExpandedShapeOfDim(i); 581 if (expandedShape.size() == 1) 582 continue; 583 for (int64_t shape : expandedShape.drop_front()) { 584 if (ShapedType::isDynamic(shape)) { 585 return linalgOp.emitError( 586 "unable to fuse indexed generic op where the expanded dim is " 587 "dynamic"); 588 } 589 } 590 } 591 return success(); 592 } 593 594 /// Return the indexing map to use in the expanded op for a given the 595 /// `indexingMap` of the original operation. 596 static AffineMap 597 getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap, 598 const ExpansionInfo &expansionInfo) { 599 SmallVector<AffineExpr, 4> newExprs; 600 for (AffineExpr expr : indexingMap.getResults()) { 601 unsigned pos = expr.cast<AffineDimExpr>().getPosition(); 602 SmallVector<AffineExpr, 4> expandedExprs = llvm::to_vector<4>( 603 llvm::map_range(expansionInfo.getExpandedDims(pos), [&](int64_t v) { 604 return builder.getAffineDimExpr(static_cast<unsigned>(v)); 605 })); 606 newExprs.append(expandedExprs.begin(), expandedExprs.end()); 607 } 608 return AffineMap::get(expansionInfo.getExpandedOpNumDims(), 609 indexingMap.getNumSymbols(), newExprs, 610 builder.getContext()); 611 } 612 613 /// Return the type of the operand/result to use in the expanded op given the 614 /// type in the original op. 615 static RankedTensorType getExpandedType(RankedTensorType originalType, 616 AffineMap indexingMap, 617 const ExpansionInfo &expansionInfo) { 618 SmallVector<int64_t, 4> expandedShape; 619 for (AffineExpr expr : indexingMap.getResults()) { 620 unsigned dim = expr.cast<AffineDimExpr>().getPosition(); 621 auto dimExpansion = expansionInfo.getExpandedShapeOfDim(dim); 622 expandedShape.append(dimExpansion.begin(), dimExpansion.end()); 623 } 624 return RankedTensorType::get(expandedShape, originalType.getElementType()); 625 } 626 627 /// Returns the reassociation maps to use in the `linalg.tensor_reshape` 628 /// operation to convert the operands of the origial operation to operands of 629 /// the expanded operation. The same method is used to compute the 630 /// `linalg.tensor_reshape` used to collapse the result of the expanded op to 631 /// get the value that can replace all uses of the results of the original op. 632 static SmallVector<ReassociationIndices, 4> 633 getReassociationForExpansion(AffineMap indexingMap, 634 const ExpansionInfo &expansionInfo) { 635 SmallVector<ReassociationIndices, 4> reassociation; 636 unsigned numReshapeDims = 0; 637 for (AffineExpr expr : indexingMap.getResults()) { 638 unsigned dim = expr.cast<AffineDimExpr>().getPosition(); 639 auto numExpandedDims = expansionInfo.getExpandedDims(dim).size(); 640 auto indices = llvm::to_vector<2>( 641 llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims)); 642 reassociation.emplace_back(std::move(indices)); 643 numReshapeDims += numExpandedDims; 644 } 645 return reassociation; 646 } 647 648 /// Build the body of the expanded IndexedGenericOp. The arguments for the 649 /// induction variables of the original operation need to be recovered by 650 /// linearizing the arguments of the corresponding dimensions of the expanded 651 /// op. For now it is assumed that the shapes of the expanded op needed for 652 /// linearization are static. 653 static void buildExpandedIndexedGenericOpRegion( 654 PatternRewriter &rewriter, Location loc, Region &originalOpRegion, 655 Region &fusedOpRegion, const ExpansionInfo &expansionInfo) { 656 assert(fusedOpRegion.empty() && "expected fused op to have empty region"); 657 // Create an entry block in the fused region with same number of arguments 658 // as the fused op 659 Block *fusedEntryBlock = new Block; 660 fusedOpRegion.push_back(fusedEntryBlock); 661 rewriter.cloneRegionBefore(originalOpRegion, fusedOpRegion, 662 fusedOpRegion.end()); 663 664 // Merge the entry block of the fused op with the cloned blocks. For this 665 // compute the value for arguments of the region in the original operation 666 // in terms of the arguments of the fused op. Since the original operation 667 // is expanded, the expanded dimensions need to be folded back to get the 668 // replacement value for the arguments corresponding to interation index. 669 // For now this expects that all the loop ranges are constants, which is 670 // true if the shapes are all static. This has already been checked in the 671 // precondition. 672 using namespace edsc::op; 673 using namespace edsc::intrinsics; 674 OpBuilder::InsertionGuard guard(rewriter); 675 SmallVector<Value, 4> argReplacements(originalOpRegion.getNumArguments()); 676 rewriter.setInsertionPointToStart(fusedEntryBlock); 677 edsc::ScopedContext scopedContext(rewriter, loc); 678 IndexType indexType = rewriter.getIndexType(); 679 for (auto i : llvm::seq<unsigned>(0, expansionInfo.getOrigOpNumDims())) { 680 Value linearizedIndex = fusedEntryBlock->addArgument(indexType); 681 ArrayRef<int64_t> expandedDimsShape = 682 expansionInfo.getExpandedShapeOfDim(i).drop_front(); 683 for (unsigned shape : expandedDimsShape) { 684 assert(!ShapedType::isDynamic(shape)); 685 linearizedIndex = linearizedIndex * std_constant_index(shape); 686 linearizedIndex = 687 linearizedIndex + fusedEntryBlock->addArgument(indexType); 688 } 689 argReplacements[i] = linearizedIndex; 690 } 691 for (auto i : llvm::seq<unsigned>(expansionInfo.getOrigOpNumDims(), 692 argReplacements.size())) { 693 argReplacements[i] = 694 fusedEntryBlock->addArgument(originalOpRegion.getArgument(i).getType()); 695 } 696 rewriter.mergeBlocks(fusedEntryBlock->getNextNode(), fusedEntryBlock, 697 argReplacements); 698 } 699 700 /// Implements the fusion of a tensor_reshape op and a generic/indexed_generic 701 /// op as explained in `isFusableWithReshapeByExpansion`. Assumes that those 702 /// conditions have been satisfied. 703 static Optional<SmallVector<Value, 1>> 704 fuseWithReshapeByExpansion(LinalgOp linalgOp, TensorReshapeOp reshapeOp, 705 unsigned fusedTensorIndex, 706 PatternRewriter &rewriter) { 707 assert(isFusableWithReshapeByDimExpansion(linalgOp, fusedTensorIndex) && 708 "preconditions for fuse operation failed"); 709 // Check if reshape is expanding or collapsing. 710 bool isExpanding = 711 reshapeOp.getSrcType().getRank() < reshapeOp.getResultType().getRank(); 712 RankedTensorType expandedType = 713 isExpanding ? reshapeOp.getResultType() : reshapeOp.getSrcType(); 714 715 ExpansionInfo expansionInfo; 716 if (failed(expansionInfo.compute(linalgOp, fusedTensorIndex, 717 reshapeOp.getReassociationMaps(), 718 expandedType.getShape()))) 719 return llvm::None; 720 721 if (isa<IndexedGenericOp>(linalgOp.getOperation()) && 722 failed(isIndexedGenericOpExpandable(linalgOp, expansionInfo))) 723 return llvm::None; 724 725 SmallVector<AffineMap, 4> expandedOpIndexingMaps = llvm::to_vector<4>( 726 llvm::map_range(linalgOp.getIndexingMaps(), [&](AffineMap m) { 727 return getIndexingMapInExpandedOp(rewriter, m, expansionInfo); 728 })); 729 730 SmallVector<Value, 4> expandedOpOperands; 731 for (auto operand : llvm::enumerate(linalgOp.getInputs())) { 732 if (operand.index() == fusedTensorIndex) { 733 expandedOpOperands.push_back(reshapeOp.src()); 734 continue; 735 } 736 AffineMap indexingMap = linalgOp.getInputIndexingMap(operand.index()); 737 RankedTensorType expandedOperandType = 738 getExpandedType(operand.value().getType().cast<RankedTensorType>(), 739 indexingMap, expansionInfo); 740 if (expandedOperandType != operand.value().getType()) { 741 // Reshape the operand to get the right type. 742 SmallVector<ReassociationIndices, 4> reassociation = 743 getReassociationForExpansion(indexingMap, expansionInfo); 744 expandedOpOperands.push_back(rewriter.create<TensorReshapeOp>( 745 linalgOp.getLoc(), expandedOperandType, operand.value(), 746 reassociation)); 747 continue; 748 } 749 expandedOpOperands.push_back(operand.value()); 750 } 751 752 Location loc = linalgOp.getLoc(); 753 SmallVector<Value, 1> outputs; 754 for (auto result : llvm::enumerate(linalgOp.getOutputs())) { 755 AffineMap indexingMap = linalgOp.getOutputIndexingMap(result.index()); 756 RankedTensorType expandedOutputType = 757 getExpandedType(result.value().getType().cast<RankedTensorType>(), 758 indexingMap, expansionInfo); 759 if (expandedOutputType != result.value().getType()) { 760 SmallVector<ReassociationIndices, 4> reassociation = 761 getReassociationForExpansion(indexingMap, expansionInfo); 762 outputs.push_back(rewriter.create<TensorReshapeOp>( 763 linalgOp.getLoc(), expandedOutputType, result.value(), 764 reassociation)); 765 } 766 } 767 768 // The iterator types of the expanded op are all parallel. 769 SmallVector<StringRef, 4> iteratorTypes(expansionInfo.getExpandedOpNumDims(), 770 getParallelIteratorTypeName()); 771 772 TypeRange resultTypes = ValueRange(outputs).getTypes(); 773 LinalgOp fusedOp = createLinalgOpOfSameType( 774 linalgOp, rewriter, linalgOp.getLoc(), resultTypes, 775 /*inputs=*/expandedOpOperands, outputs, expandedOpIndexingMaps, 776 iteratorTypes); 777 Region &fusedRegion = fusedOp->getRegion(0); 778 Region &originalRegion = linalgOp->getRegion(0); 779 780 if (isa<GenericOp>(linalgOp.getOperation())) { 781 rewriter.cloneRegionBefore(originalRegion, fusedRegion, 782 fusedRegion.begin()); 783 } else { 784 assert(isa<IndexedGenericOp>(linalgOp.getOperation())); 785 buildExpandedIndexedGenericOpRegion(rewriter, loc, originalRegion, 786 fusedRegion, expansionInfo); 787 } 788 789 // Reshape the result values to their original shape if this is a collapsing 790 // reshape folded into its consumer. 791 SmallVector<Value, 1> resultVals; 792 for (auto result : llvm::enumerate(linalgOp->getResults())) { 793 if (!isExpanding && 794 resultTypes[result.index()] != result.value().getType()) { 795 SmallVector<ReassociationIndices, 4> reassociation = 796 getReassociationForExpansion( 797 linalgOp.getOutputIndexingMap(result.index()), expansionInfo); 798 resultVals.push_back(rewriter.create<TensorReshapeOp>( 799 linalgOp.getLoc(), result.value().getType(), 800 fusedOp->getResult(result.index()), reassociation)); 801 } else { 802 resultVals.push_back(fusedOp->getResult(result.index())); 803 } 804 } 805 // Assuming a single result. 806 return resultVals; 807 } 808 809 namespace { 810 811 /// Pattern to fold tensor_reshape op with its consumer by using the source of 812 /// the reshape op as the operand in the consumer (instead of the result of the 813 /// tensor_reshapeop) when the tensor_reshape op is collapsing. The 814 /// corresponding index map in the consumer needs to be modified to linearize 815 /// the folded dimension. 816 /// 817 /// For example, 818 /// 819 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)> 820 /// %0 = linalg.tensor_reshape %arg0 821 /// [affine_map<(i, j, k, l) -> (i)>, affine_map<(i, j, k, l) -> (j, k)>, 822 /// affine_map<(i, j, k, l) -> (l)>] 823 /// tensor<?x?x?xf32> into tensor<?x?x4x?xf32> 824 /// %1 = linalg.generic { indexing_maps = [#map0, #map0, #map0], ... } 825 /// ins(%0, %arg1 : tensor<?x?x4x?xf32>, tensor<?x?x4x?xf32>) ... 826 /// -> tensor<?x?x4x?xf32> 827 /// 828 /// can be folded into 829 /// 830 /// #map0 = affine_map<(d0, d1, d2, d3) -> (d0, d1 * 4 + d2, d3)> 831 /// #map1 = affine_map<(d0, d1, d2, d3) -> (d0, d1, d2, d3)> 832 /// %0 = linalg.generic { indexing_maps = [#map0, #map1, #map1] ... } 833 /// ins(%arg0, %arg1 : tensor<?x?x?xf32>, tensor<?x?x4x?xf32>) ... 834 /// -> tensor<?x?x4x?xf32> 835 template <typename LinalgOpTy, bool foldUnitDimReshapesOnly> 836 struct FoldProducerReshapeOpByLinearization 837 : public OpRewritePattern<LinalgOpTy> { 838 using OpRewritePattern<LinalgOpTy>::OpRewritePattern; 839 840 LogicalResult matchAndRewrite(LinalgOpTy op, 841 PatternRewriter &rewriter) const override { 842 if (!op.hasTensorSemantics()) 843 return failure(); 844 LinalgOp linalgOp = cast<LinalgOp>(op.getOperation()); 845 for (auto operand : llvm::enumerate(linalgOp.getInputs())) { 846 TensorReshapeOp reshapeOp = 847 operand.value().getDefiningOp<TensorReshapeOp>(); 848 if (!reshapeOp || 849 !isTensorReshapeOpFoldableByLinearization( 850 reshapeOp, linalgOp.getInputIndexingMap(operand.index()), 851 /*asProducer =*/true) || 852 (foldUnitDimReshapesOnly && 853 !isUnitDimExpansionOnly(reshapeOp.getResultType().getShape(), 854 reshapeOp.getReassociationMaps()))) 855 continue; 856 857 // Compute the fused operands list, 858 SmallVector<Value, 2> fusedOperands(linalgOp.getInputs()); 859 fusedOperands[operand.index()] = reshapeOp.src(); 860 fusedOperands.append(linalgOp.getOutputs().begin(), 861 linalgOp.getOutputs().end()); 862 863 // Compute indexing_maps for the fused operation. The indexing_maps for 864 // the operands of the consumers that arent fused are the same. 865 SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>( 866 op.indexing_maps().template getAsValueRange<AffineMapAttr>()); 867 868 // Accepted consumer maps are either identity or permutation. 869 auto invMap = inversePermutation(fusedIndexMaps[operand.index()]); 870 871 // Compute the indexing map to use for the result of the producer. 872 AffineMap modifiedMap = 873 linearizeCollapsedDims(invMap, reshapeOp.getResultType().getShape(), 874 reshapeOp.getReassociationMaps()); 875 for (AffineExpr expr : modifiedMap.getResults()) { 876 if (!expr.isPureAffine()) 877 return failure(); 878 } 879 fusedIndexMaps[operand.index()] = modifiedMap; 880 881 // Further check that the resulting index maps can be fused and 882 // inverted. Without this the resultant op is not legal. 883 if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) 884 return op.emitRemark("fused op loop bound computation failed"); 885 886 rewriter.startRootUpdate(op); 887 op->setOperands(fusedOperands); 888 op.indexing_mapsAttr(rewriter.getAffineMapArrayAttr(fusedIndexMaps)); 889 rewriter.finalizeRootUpdate(op); 890 return success(); 891 } 892 return failure(); 893 } 894 }; 895 896 /// Pattern to fuse a tensor_reshape op with its consumer 897 /// generic/indexed_generic op, when the reshape op is collapsing 898 /// dimensions. The dimensionality of the loop in the consumer is expanded. 899 template <typename GenericOpTy> 900 struct FoldWithProducerReshapeOpByExpansion 901 : public OpRewritePattern<GenericOpTy> { 902 using OpRewritePattern<GenericOpTy>::OpRewritePattern; 903 904 LogicalResult matchAndRewrite(GenericOpTy genericOp, 905 PatternRewriter &rewriter) const override { 906 LinalgOp linalgOp = cast<LinalgOp>(genericOp.getOperation()); 907 for (auto operand : llvm::enumerate(linalgOp.getInputs())) { 908 TensorReshapeOp reshapeOp = 909 operand.value().getDefiningOp<TensorReshapeOp>(); 910 if (!reshapeOp) 911 continue; 912 913 // Fold only if 914 // - The tensor reshape op is folding. 915 // - All constraints of fusing with reshape by expansion are met. 916 if (reshapeOp.getSrcType().getRank() < 917 reshapeOp.getResultType().getRank() || 918 !isFusableWithReshapeByDimExpansion(linalgOp, operand.index()) || 919 isUnitDimExpansionOnly(reshapeOp.getSrcType().getShape(), 920 reshapeOp.getReassociationMaps())) 921 continue; 922 923 Optional<SmallVector<Value, 1>> replacementValues = 924 fuseWithReshapeByExpansion(linalgOp, reshapeOp, operand.index(), 925 rewriter); 926 if (!replacementValues) 927 return failure(); 928 rewriter.replaceOp(genericOp, replacementValues.getValue()); 929 return success(); 930 } 931 return failure(); 932 } 933 }; 934 935 /// Pattern to fold tensor_reshape op with its producer. The corresponding index 936 /// map in the consumer needs to be modified to linearize the folded dimension. 937 template <bool foldUnitDimReshapesOnly> 938 struct FoldConsumerReshapeOpByLinearization 939 : public OpRewritePattern<TensorReshapeOp> { 940 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 941 942 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 943 PatternRewriter &rewriter) const override { 944 LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>(); 945 if (!producer || 946 !isa<GenericOp, IndexedGenericOp>(producer.getOperation()) || 947 !producer.hasTensorSemantics() || producer.getNumOutputs() != 1 || 948 !isTensorReshapeOpFoldableByLinearization( 949 reshapeOp, producer.getOutputIndexingMap(0), 950 /*asProducer =*/false) || 951 (foldUnitDimReshapesOnly && 952 !isUnitDimExpansionOnly(reshapeOp.getSrcType().getShape(), 953 reshapeOp.getReassociationMaps()))) 954 return failure(); 955 // The indexing_maps for the operands of the fused operation are same as 956 // those for the operands of the producer. 957 SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>( 958 producer.indexing_maps().getAsValueRange<AffineMapAttr>()); 959 960 auto invMap = inversePermutation(producer.getOutputIndexingMap(0)); 961 962 // Compute the indexing map to use for the operand of the producer. 963 AffineMap modifiedMap = 964 linearizeCollapsedDims(invMap, reshapeOp.getSrcType().getShape(), 965 reshapeOp.getReassociationMaps()); 966 for (AffineExpr expr : modifiedMap.getResults()) { 967 if (!expr.isPureAffine()) 968 return producer.emitRemark("fused op indexing map is not affine"); 969 } 970 fusedIndexMaps.back() = modifiedMap; 971 972 // Further check that the resulting index maps can be fused and 973 // inverted. Without this the resultant op is not legal. 974 if (!inversePermutation(concatAffineMaps(fusedIndexMaps))) 975 return reshapeOp.emitRemark("fused op loop bound computation failed"); 976 977 Location loc = producer.getLoc(); 978 Value output = rewriter.create<TensorReshapeOp>( 979 loc, producer.getOutputs()[0], reshapeOp.getReassociationExprs()); 980 LinalgOp fusedOp = createLinalgOpOfSameType( 981 producer, rewriter, loc, reshapeOp.getResultType(), 982 /*inputs=*/producer.getInputs(), 983 // TODO: handle outputs. 984 /*outputs=*/output, rewriter.getAffineMapArrayAttr(fusedIndexMaps), 985 producer.iterator_types(), 986 /*doc=*/nullptr, 987 /*library_call=*/nullptr, 988 /*sparse=*/nullptr); 989 auto &fusedRegion = fusedOp->getRegion(0); 990 rewriter.cloneRegionBefore(producer->getRegion(0), fusedRegion, 991 fusedRegion.begin()); 992 rewriter.replaceOp(reshapeOp, fusedOp->getResults()); 993 return success(); 994 } 995 }; 996 997 /// Pattern to fold a tensor_reshape op with its producer generic op if the 998 /// tensor_reshape op is expanding, by expanding the dimensionality of the loop 999 /// in the producer op. 1000 struct FoldReshapeWithGenericOpByExpansion 1001 : public OpRewritePattern<TensorReshapeOp> { 1002 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern; 1003 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp, 1004 PatternRewriter &rewriter) const override { 1005 // Fold only if 1006 // - The tensor reshape op is a expanding case. 1007 // - All constraints of fusing with reshape by expansion are met. 1008 if (reshapeOp.getSrcType().getRank() > reshapeOp.getResultType().getRank()) 1009 return failure(); 1010 LinalgOp producer = reshapeOp.src().getDefiningOp<LinalgOp>(); 1011 if (!producer || producer.getNumOutputs() != 1 || 1012 !isFusableWithReshapeByDimExpansion(producer, 1013 producer.getNumInputs()) || 1014 isUnitDimExpansionOnly(reshapeOp.getResultType().getShape(), 1015 reshapeOp.getReassociationMaps())) 1016 return failure(); 1017 Optional<SmallVector<Value, 1>> replacementValues = 1018 fuseWithReshapeByExpansion(producer, reshapeOp, producer.getNumInputs(), 1019 rewriter); 1020 if (!replacementValues) 1021 return failure(); 1022 rewriter.replaceOp(reshapeOp, replacementValues.getValue()); 1023 return success(); 1024 } 1025 }; 1026 1027 /// Pattern to fold a GenericOp/IndexedGenericOp with a splat constant. 1028 template <typename LinalgOpTy> 1029 struct FoldSplatConstants : public OpRewritePattern<LinalgOpTy> { 1030 using OpRewritePattern<LinalgOpTy>::OpRewritePattern; 1031 1032 LogicalResult matchAndRewrite(LinalgOpTy op, 1033 PatternRewriter &rewriter) const override { 1034 if (!op.hasTensorSemantics()) 1035 return failure(); 1036 LinalgOp linalgOp = cast<LinalgOp>(op.getOperation()); 1037 for (auto operand : llvm::enumerate(linalgOp.getInputs())) { 1038 ConstantOp constantOp = operand.value().getDefiningOp<ConstantOp>(); 1039 if (!constantOp || 1040 !constantOp.value().cast<DenseElementsAttr>().isSplat()) 1041 continue; 1042 1043 // The indexing_maps for the operands of the fused operation are same as 1044 // those for the operands of the linalgOp without the indexing map at 1045 // operand.index() 1046 SmallVector<AffineMap, 4> fusedIndexMaps = llvm::to_vector<4>( 1047 linalgOp.indexing_maps().getAsValueRange<AffineMapAttr>()); 1048 fusedIndexMaps.erase(std::next(fusedIndexMaps.begin(), operand.index())); 1049 1050 // The operands list is same as the linalgOp with the argument for 1051 // constant index dropped. 1052 SmallVector<Value, 4> fusedOperands(linalgOp.getInputs()); 1053 fusedOperands.erase(std::next(fusedOperands.begin(), operand.index())); 1054 1055 // Create a constant scalar value from the splat constant. 1056 Value scalarConstant = rewriter.create<ConstantOp>( 1057 constantOp.getLoc(), 1058 constantOp.value().cast<DenseElementsAttr>().getSplatValue()); 1059 1060 LinalgOp fusedOp = createLinalgOpOfSameType( 1061 linalgOp, rewriter, rewriter.getUnknownLoc(), 1062 linalgOp->getResultTypes(), 1063 /*inputs=*/fusedOperands, 1064 /*outputs=*/linalgOp.getOutputs(), 1065 rewriter.getAffineMapArrayAttr(fusedIndexMaps), 1066 linalgOp.iterator_types(), 1067 /*doc=*/nullptr, 1068 /*library_call=*/nullptr, 1069 /*sparse=*/nullptr); 1070 1071 // Map the block argument corresponding to the replaced argument with the 1072 // scalar constant. 1073 Region &linalgOpRegion = linalgOp->getRegion(0); 1074 Block &entryBlock = *linalgOpRegion.begin(); 1075 unsigned argIndex = entryBlock.getNumArguments() - 1076 linalgOp.getNumShapedOperands() + operand.index(); 1077 BlockAndValueMapping mapping; 1078 mapping.map(entryBlock.getArgument(argIndex), scalarConstant); 1079 Region &fusedRegion = fusedOp->getRegion(0); 1080 rewriter.cloneRegionBefore(linalgOpRegion, fusedRegion, 1081 fusedRegion.begin(), mapping); 1082 rewriter.replaceOp(linalgOp, fusedOp->getResults()); 1083 return success(); 1084 } 1085 return failure(); 1086 } 1087 }; 1088 } // namespace 1089 1090 Optional<SmallVector<Value, 1>> 1091 mlir::linalg::fuseTensorOps(PatternRewriter &rewriter, 1092 OpOperand &consumerOpOperand) { 1093 Operation *producer = consumerOpOperand.get().getDefiningOp(); 1094 if (!producer || producer->getNumResults() != 1) 1095 return llvm::None; 1096 1097 // Fuse when consumer is GenericOp or IndexedGenericOp. 1098 if (!isa<GenericOp, IndexedGenericOp>(consumerOpOperand.getOwner()) || 1099 !isa<GenericOp, IndexedGenericOp>(producer)) 1100 return llvm::None; 1101 1102 return fuseTensorOpsImpl(cast<LinalgOp>(producer), consumerOpOperand, 1103 rewriter); 1104 } 1105 1106 namespace { 1107 /// Patterns to fuse a generic op, with the producer of its operands. 1108 template <typename LinalgOpTy> 1109 struct FuseTensorOps : public OpRewritePattern<LinalgOpTy> { 1110 using OpRewritePattern<LinalgOpTy>::OpRewritePattern; 1111 1112 LogicalResult matchAndRewrite(LinalgOpTy op, 1113 PatternRewriter &rewriter) const override { 1114 // Find the first operand that is defined by another generic op on tensors. 1115 for (OpOperand &opOperand : op.getShapedOpOperands()) { 1116 LinalgOp producerOp = 1117 dyn_cast_or_null<LinalgOp>(opOperand.get().getDefiningOp()); 1118 if (!producerOp || !producerOp.hasTensorSemantics()) 1119 continue; 1120 Optional<SmallVector<Value, 1>> fusedOpResults = 1121 fuseTensorOps(rewriter, opOperand); 1122 if (fusedOpResults) { 1123 rewriter.replaceOp(op, *fusedOpResults); 1124 return success(); 1125 } 1126 } 1127 return failure(); 1128 } 1129 }; 1130 1131 /// Pass that fuses generic ops on tensors. Used only for testing. 1132 struct FusionOfTensorOpsPass 1133 : public LinalgFusionOfTensorOpsBase<FusionOfTensorOpsPass> { 1134 void runOnOperation() override { 1135 Operation *op = getOperation(); 1136 OwningRewritePatternList patterns(op->getContext()); 1137 populateLinalgTensorOpsFusionPatterns(patterns); 1138 (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns)); 1139 } 1140 }; 1141 1142 /// Pass to test folding of reshape op with generic/indexed_generic ops by 1143 /// linearization. 1144 struct FoldReshapeOpsByLinearizationPass 1145 : public LinalgFoldReshapeOpsByLinearizationBase< 1146 FoldReshapeOpsByLinearizationPass> { 1147 void runOnOperation() override { 1148 Operation *op = getOperation(); 1149 OwningRewritePatternList patterns(op->getContext()); 1150 populateFoldReshapeOpsByLinearizationPatterns(patterns); 1151 (void)applyPatternsAndFoldGreedily(op->getRegions(), std::move(patterns)); 1152 } 1153 }; 1154 1155 } // namespace 1156 1157 void mlir::populateFoldReshapeOpsByLinearizationPatterns( 1158 OwningRewritePatternList &patterns) { 1159 patterns.insert<FoldProducerReshapeOpByLinearization<GenericOp, false>, 1160 FoldProducerReshapeOpByLinearization<IndexedGenericOp, false>, 1161 FoldConsumerReshapeOpByLinearization<false>>( 1162 patterns.getContext()); 1163 } 1164 1165 void mlir::populateFoldUnitDimsReshapeOpsByLinearizationPatterns( 1166 OwningRewritePatternList &patterns) { 1167 patterns.insert<FoldProducerReshapeOpByLinearization<GenericOp, true>, 1168 FoldProducerReshapeOpByLinearization<IndexedGenericOp, true>, 1169 FoldConsumerReshapeOpByLinearization<true>>( 1170 patterns.getContext()); 1171 } 1172 1173 void mlir::populateFoldReshapeOpsByExpansionPatterns( 1174 OwningRewritePatternList &patterns) { 1175 patterns.insert<FoldReshapeWithGenericOpByExpansion, 1176 FoldWithProducerReshapeOpByExpansion<GenericOp>, 1177 FoldWithProducerReshapeOpByExpansion<IndexedGenericOp>>( 1178 patterns.getContext()); 1179 } 1180 1181 void mlir::populateLinalgTensorOpsFusionPatterns( 1182 OwningRewritePatternList &patterns) { 1183 auto *context = patterns.getContext(); 1184 patterns.insert<FuseTensorOps<GenericOp>, FuseTensorOps<IndexedGenericOp>, 1185 FoldSplatConstants<GenericOp>, 1186 FoldSplatConstants<IndexedGenericOp>>(context); 1187 populateFoldReshapeOpsByExpansionPatterns(patterns); 1188 GenericOp::getCanonicalizationPatterns(patterns, context); 1189 IndexedGenericOp::getCanonicalizationPatterns(patterns, context); 1190 TensorReshapeOp::getCanonicalizationPatterns(patterns, context); 1191 } 1192 1193 std::unique_ptr<Pass> mlir::createLinalgFusionOfTensorOpsPass() { 1194 return std::make_unique<FusionOfTensorOpsPass>(); 1195 } 1196 1197 std::unique_ptr<Pass> mlir::createFoldReshapeOpsByLinearizationPass() { 1198 return std::make_unique<FoldReshapeOpsByLinearizationPass>(); 1199 } 1200