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