brintos

brintos / llvm-project-archived public Read only

0
0
Text · 105.7 KiB · 421ab5e Raw
2522 lines · cpp
1//===- ElementwiseOpFusion.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-exception6//7//===----------------------------------------------------------------------===//8//9// This file implements the linalg dialect Fusion on tensors operations pass.10//11//===----------------------------------------------------------------------===//12 13#include "mlir/Dialect/Linalg/Passes.h"14 15#include "mlir/Dialect/Affine/IR/AffineOps.h"16#include "mlir/Dialect/Arith/IR/Arith.h"17#include "mlir/Dialect/Arith/Utils/Utils.h"18#include "mlir/Dialect/Linalg/IR/Linalg.h"19#include "mlir/Dialect/Linalg/Transforms/Transforms.h"20#include "mlir/Dialect/SparseTensor/IR/SparseTensor.h"21#include "mlir/Dialect/Tensor/Transforms/Transforms.h"22#include "mlir/IR/AffineExpr.h"23#include "mlir/IR/AffineMap.h"24#include "mlir/IR/Matchers.h"25#include "mlir/IR/PatternMatch.h"26#include "mlir/Support/LLVM.h"27#include "mlir/Transforms/GreedyPatternRewriteDriver.h"28#include "mlir/Transforms/RegionUtils.h"29#include <optional>30#include <utility>31 32namespace mlir {33#define GEN_PASS_DEF_LINALGELEMENTWISEOPFUSIONPASS34#include "mlir/Dialect/Linalg/Passes.h.inc"35} // namespace mlir36 37using namespace mlir;38using namespace mlir::linalg;39 40//===---------------------------------------------------------------------===//41// Methods and patterns that fuse elementwise `linalg.generic` operations.42//===---------------------------------------------------------------------===//43 44/// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of45/// the `producer` to use in the fused operation given the indexing map of the46/// result of the producer in the consumer.47static AffineMap getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(48    OpOperand *producerOpOperand, AffineMap producerResultIndexMap,49    AffineMap fusedConsumerArgIndexMap) {50  // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map51  // from consumer loop -> consumer arg tensor index/producer result tensor52  // index. The fused loop is same as the consumer loop. For each producer arg53  // the indexing map to be computed is a map from consumer loop -> producer54  // arg tensor index.55  // producerResultIndexMap is a map from producer loop -> tensor index.56  // Compute the inverse to get map from tensor index -> producer loop.57  // The inverse is a map from producer result tensor index -> producer loop.58  AffineMap invProducerResultIndexMap =59      inversePermutation(producerResultIndexMap);60  assert(invProducerResultIndexMap &&61         "expected producer result indexing map to be invertible");62 63  LinalgOp producer = cast<LinalgOp>(producerOpOperand->getOwner());64  // argMap is a map from producer loop -> producer arg tensor index.65  AffineMap argMap = producer.getMatchingIndexingMap(producerOpOperand);66 67  // Compose argMap with invProducerResultIndexMap to get a map from68  // producer result tensor index -> producer arg tensor index.69  AffineMap t1 = argMap.compose(invProducerResultIndexMap);70 71  // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from72  // consumer loop/ fused loop -> producer arg tensor index.73  return t1.compose(fusedConsumerArgIndexMap);74}75 76// Checks if the given operand can be dropped, and the remaining operands77// of the fused producer & consumer after the fusion can still compute the78// bounds of the op.79static bool isOpOperandCanBeDroppedAfterFusedLinalgs(80    GenericOp producer, GenericOp consumer,81    ArrayRef<OpOperand *> opOperandsToIgnore) {82  SmallVector<AffineMap> indexingMaps;83 84  SmallVector<GenericOp> ops = {producer, consumer};85  for (auto &op : ops) {86    for (auto &opOperand : op->getOpOperands()) {87      if (llvm::is_contained(opOperandsToIgnore, &opOperand)) {88        continue;89      }90      indexingMaps.push_back(op.getMatchingIndexingMap(&opOperand));91    }92  }93  if (indexingMaps.empty()) {94    // If there are no indexing maps, the operand can only be dropped95    // if neither op has loops.96    return producer.getNumLoops() == 0 && consumer.getNumLoops() == 0;97  }98 99  // The concatanation of the remained indexing maps must be invertible, so100  // the bounds of the op can be still computed after dropping the selected101  // operand. inversePermutation returns an empty AffineMap in case the102  // concatanated indexing maps are not invertible.103  return inversePermutation(concatAffineMaps(104             indexingMaps, producer.getContext())) != AffineMap();105}106 107/// Returns a set of indices of the producer's results which would108/// be preserved after the fusion.109/// * There is a chance that the implementation of the transformation does not110/// agree with the result of this method. This function gives a prediction based111/// on an optimized fusion.112llvm::SmallDenseSet<int> mlir::linalg::getPreservedProducerResults(113    GenericOp producer, GenericOp consumer, OpOperand *fusedOperand) {114  llvm::SmallDenseSet<int> preservedProducerResults;115  llvm::SmallVector<OpOperand *> opOperandsToIgnore;116 117  // The fusedOperand will be removed during the fusion118  opOperandsToIgnore.emplace_back(fusedOperand);119 120  for (const auto &producerResult : llvm::enumerate(producer->getResults())) {121    auto *outputOperand = producer.getDpsInitOperand(producerResult.index());122    opOperandsToIgnore.emplace_back(outputOperand);123    if (producer.payloadUsesValueFromOperand(outputOperand) ||124        !isOpOperandCanBeDroppedAfterFusedLinalgs(producer, consumer,125                                                  opOperandsToIgnore) ||126        llvm::any_of(producerResult.value().getUsers(), [&](Operation *user) {127          return user != consumer.getOperation();128        })) {129      preservedProducerResults.insert(producerResult.index());130 131      // In case the operand can't be dropped132      (void)opOperandsToIgnore.pop_back_val();133    }134  }135  return preservedProducerResults;136}137 138/// Conditions for elementwise fusion of generic operations.139bool mlir::linalg::areElementwiseOpsFusable(OpOperand *fusedOperand) {140  if (!fusedOperand)141    return false;142 143  auto producer = fusedOperand->get().getDefiningOp<GenericOp>();144  auto consumer = dyn_cast<GenericOp>(fusedOperand->getOwner());145 146  // Check producer and consumer are generic ops.147  if (!producer || !consumer)148    return false;149 150  // Consumer can have mixed semantics, just check operand itself has tensor151  // type. Producer must have full tensor semantics to avoid potential152  // aliasing between producer and consumer memrefs.153  if (!producer.hasPureTensorSemantics() ||154      !isa<RankedTensorType>(fusedOperand->get().getType()))155    return false;156 157  // Verify that158  // - the producer has all "parallel" iterator type.159  if (producer.getNumParallelLoops() != producer.getNumLoops())160    return false;161 162  // Only allow fusing the producer of an input operand for now.163  // TODO: allow fusing the producer of an output operand.164  if (!consumer.isDpsInput(fusedOperand))165    return false;166 167  // Get the consumer index map. The number of results of the consumer index168  // map must match the number of loops of the producer.169  AffineMap consumerIndexMap = consumer.getMatchingIndexingMap(fusedOperand);170  if (consumerIndexMap.getNumResults() != producer.getNumLoops())171    return false;172 173  // Finally the index_map for the result must be invertible. For now just174  // verify it is a permutation.175  auto producerResult = cast<OpResult>(fusedOperand->get());176  AffineMap producerResultIndexMap =177      producer.getIndexingMapMatchingResult(producerResult);178  if (!producerResultIndexMap.isPermutation())179    return false;180 181  // Ensure that the fusion does not remove size information required to182  // get the loop bounds. For non-reduction generics, this is trivially the183  // case due to the output operand. For reductions, we need to check that after184  // the fusion, each loop dimension has at least one input that defines it.185  if ((consumer.getNumReductionLoops())) {186    BitVector coveredDims(consumer.getNumLoops(), false);187 188    auto addToCoveredDims = [&](AffineMap map) {189      for (auto result : map.getResults())190        if (auto dimExpr = dyn_cast<AffineDimExpr>(result))191          coveredDims[dimExpr.getPosition()] = true;192    };193 194    for (auto pair :195         llvm::zip(consumer->getOperands(), consumer.getIndexingMapsArray())) {196      Value operand = std::get<0>(pair);197      if (operand == fusedOperand->get())198        continue;199      AffineMap operandMap = std::get<1>(pair);200      addToCoveredDims(operandMap);201    }202 203    for (OpOperand *operand : producer.getDpsInputOperands()) {204      AffineMap newIndexingMap =205          getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(206              operand, producerResultIndexMap, consumerIndexMap);207      addToCoveredDims(newIndexingMap);208    }209    if (!coveredDims.all())210      return false;211  }212 213  return true;214}215 216/// Generate the region of the fused tensor operation. The region of the fused217/// op must be empty.218static void generateFusedElementwiseOpRegion(219    RewriterBase &rewriter, GenericOp fusedOp,220    AffineMap consumerToProducerLoopsMap, OpOperand *fusedOperand,221    unsigned nloops, llvm::SmallDenseSet<int> &preservedProducerResults) {222  auto producer = cast<GenericOp>(fusedOperand->get().getDefiningOp());223  auto consumer = cast<GenericOp>(fusedOperand->getOwner());224  // Build the region of the fused op.225  Block &producerBlock = producer->getRegion(0).front();226  Block &consumerBlock = consumer->getRegion(0).front();227  OpBuilder::InsertionGuard guard(rewriter);228  Block *fusedBlock = rewriter.createBlock(&fusedOp.getRegion());229  IRMapping mapper;230 231  // 2. Add an index operation for every fused loop dimension and use the232  // `consumerToProducerLoopsMap` to map the producer indices.233  if (producer.hasIndexSemantics()) {234    // Add an index operation for every fused loop dimension.235    unsigned numFusedOpLoops = fusedOp.getNumLoops();236    SmallVector<Value> fusedIndices;237    fusedIndices.reserve(numFusedOpLoops);238    llvm::transform(llvm::seq<uint64_t>(0, numFusedOpLoops),239                    std::back_inserter(fusedIndices), [&](uint64_t dim) {240                      return IndexOp::create(rewriter, producer.getLoc(), dim);241                    });242    for (IndexOp indexOp :243         llvm::make_early_inc_range(producerBlock.getOps<IndexOp>())) {244      Value newIndex = affine::AffineApplyOp::create(245          rewriter, producer.getLoc(),246          consumerToProducerLoopsMap.getSubMap(indexOp.getDim()), fusedIndices);247      mapper.map(indexOp.getResult(), newIndex);248    }249  }250  // TODO: allow fusing the producer of an output operand.251  assert(consumer.isDpsInput(fusedOperand) &&252         "expected producer of input operand");253  // 3. Consumer input operands up to consumerIdx (exclusive).254  for (BlockArgument bbArg : consumerBlock.getArguments().take_front(255           fusedOperand->getOperandNumber())) // input assumption.256    mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));257 258  // Replacing consumerIdx requires getting the cloned, yielded, value from259  // the (cloned) producer block. This happens in step 9.260 261  // 4. Splice in producer's input operands.262  for (BlockArgument bbArg :263       producerBlock.getArguments().take_front(producer.getNumDpsInputs()))264    mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));265 266  // 5. Remaining consumer's input operands (drop past index `consumerIdx`).267  for (BlockArgument bbArg :268       consumerBlock.getArguments()269           .take_front(consumer.getNumDpsInputs())270           .drop_front(fusedOperand->getOperandNumber() + 1))271    mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));272 273  // 6. All of the producer's output operands274  for (const auto &bbArg : llvm::enumerate(275           producerBlock.getArguments().take_back(producer.getNumDpsInits()))) {276    if (!preservedProducerResults.count(bbArg.index()))277      continue;278    mapper.map(bbArg.value(), fusedBlock->addArgument(bbArg.value().getType(),279                                                      bbArg.value().getLoc()));280  }281 282  // 7. All of consumer's output operands.283  for (BlockArgument bbArg :284       consumerBlock.getArguments().take_back(consumer.getNumDpsInits()))285    mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));286 287  // 8. Clone all producer operations except for the yield and index operations288  // to the fused operation.289  for (auto &op : producerBlock.without_terminator()) {290    if (!isa<IndexOp>(op))291      rewriter.clone(op, mapper);292  }293  // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just294  // forward the yield operand.295  auto producerYieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator());296  unsigned producerResultNumber =297      cast<OpResult>(fusedOperand->get()).getResultNumber();298  Value replacement =299      mapper.lookupOrDefault(producerYieldOp.getOperand(producerResultNumber));300 301  // Sanity checks, if replacement is not already in the mapper then it must be302  // produced outside.303  if (replacement == producerYieldOp.getOperand(producerResultNumber)) {304    if (auto bb = dyn_cast<BlockArgument>(replacement))305      assert(bb.getOwner() != &producerBlock &&306             "yielded block argument must have been mapped");307    else308      assert(!producer->isAncestor(replacement.getDefiningOp()) &&309             "yielded value must have been mapped");310  }311  mapper.map(consumerBlock.getArgument(fusedOperand->getOperandNumber()),312             replacement);313  // 10. Clone operations from the consumer to the fused op.314  for (auto &op : consumerBlock.without_terminator())315    rewriter.clone(op, mapper);316 317  // 11. Include the final yield (which is the remapped values for all the318  // yield)319  auto consumerYieldOp = cast<linalg::YieldOp>(consumerBlock.getTerminator());320  SmallVector<Value> fusedYieldValues;321  fusedYieldValues.reserve(producerYieldOp.getNumOperands() +322                           consumerYieldOp.getNumOperands());323  for (const auto &producerYieldVal :324       llvm::enumerate(producerYieldOp.getOperands())) {325    if (preservedProducerResults.count(producerYieldVal.index()))326      fusedYieldValues.push_back(327          mapper.lookupOrDefault(producerYieldVal.value()));328  }329  for (auto consumerYieldVal : consumerYieldOp.getOperands())330    fusedYieldValues.push_back(mapper.lookupOrDefault(consumerYieldVal));331  YieldOp::create(rewriter, fusedOp.getLoc(), fusedYieldValues);332 333  // Sanity checks.334  assert(fusedBlock->getNumArguments() == fusedOp.getNumOperands() &&335         "Ill-formed GenericOp region");336}337 338FailureOr<mlir::linalg::ElementwiseOpFusionResult>339mlir::linalg::fuseElementwiseOps(RewriterBase &rewriter,340                                 OpOperand *fusedOperand) {341  assert(areElementwiseOpsFusable(fusedOperand) &&342         "expected elementwise operation pre-conditions to pass");343  auto producerResult = cast<OpResult>(fusedOperand->get());344  auto producer = cast<GenericOp>(producerResult.getOwner());345  auto consumer = cast<GenericOp>(fusedOperand->getOwner());346  // TODO: allow fusing the producer of an output operand.347  assert(consumer.isDpsInput(fusedOperand) &&348         "expected producer of input operand");349  /// Find the results of the producer that have uses outside of the consumer,350  /// after the fusion.351  llvm::SmallDenseSet<int> preservedProducerResults =352      mlir::linalg::getPreservedProducerResults(producer, consumer,353                                                fusedOperand);354 355  // Compute the fused operands list and indexing maps.356  SmallVector<Value> fusedInputOperands, fusedOutputOperands;357  SmallVector<Type> fusedResultTypes;358  SmallVector<AffineMap> fusedIndexMaps;359  fusedInputOperands.reserve(producer.getNumDpsInputs() +360                             consumer.getNumDpsInputs());361  fusedOutputOperands.reserve(preservedProducerResults.size() +362                              consumer.getNumDpsInits());363  fusedResultTypes.reserve(preservedProducerResults.size() +364                           consumer.getNumDpsInits());365  fusedIndexMaps.reserve(producer->getNumOperands() +366                         consumer->getNumOperands());367  // In the following, numbering matches that of `generateFusedTensorOpRegion`.368  // 3. Consumer input operands/maps up to consumerIdx (exclusive).369  auto consumerInputs = consumer.getDpsInputOperands();370  auto *it = llvm::find_if(consumerInputs, [&](OpOperand *operand) {371    return operand == fusedOperand;372  });373  assert(it != consumerInputs.end() && "expected to find the consumer operand");374  for (OpOperand *opOperand : llvm::make_range(consumerInputs.begin(), it)) {375    fusedInputOperands.push_back(opOperand->get());376    fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(opOperand));377  }378  // 4. Splice in producer's input operands/maps.379  AffineMap producerResultIndexMap =380      producer.getIndexingMapMatchingResult(producerResult);381  for (OpOperand *opOperand : producer.getDpsInputOperands()) {382    fusedInputOperands.push_back(opOperand->get());383    // Compute indexing maps for the producer args in the fused operation.384    AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(385        opOperand, producerResultIndexMap,386        consumer.getMatchingIndexingMap(fusedOperand));387    fusedIndexMaps.push_back(map);388  }389  // 5. Remaining consumer's input operands/maps (drop past index390  // `consumerIdx`).391  for (OpOperand *opOperand :392       llvm::make_range(std::next(it), consumerInputs.end())) {393    fusedInputOperands.push_back(opOperand->get());394    fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(opOperand));395  }396 397  // 6. Collect all of the producer outputs.398  for (const auto &opOperand : llvm::enumerate(producer.getDpsInitsMutable())) {399    if (!preservedProducerResults.count(opOperand.index()))400      continue;401 402    fusedOutputOperands.push_back(opOperand.value().get());403    AffineMap map = getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(404        &opOperand.value(), producerResultIndexMap,405        consumer.getMatchingIndexingMap(fusedOperand));406    fusedIndexMaps.push_back(map);407    fusedResultTypes.push_back(opOperand.value().get().getType());408  }409 410  // 7. All of consumer's output operands (skip operands: added by the builder).411  for (OpOperand &opOperand : consumer.getDpsInitsMutable()) {412    fusedOutputOperands.push_back(opOperand.get());413    fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(&opOperand));414    Type resultType = opOperand.get().getType();415    if (!isa<MemRefType>(resultType))416      fusedResultTypes.push_back(resultType);417  }418 419  // Generate the fused op.420  auto fusedOp = GenericOp::create(421      rewriter, consumer.getLoc(), fusedResultTypes, fusedInputOperands,422      fusedOutputOperands, rewriter.getAffineMapArrayAttr(fusedIndexMaps),423      consumer.getIteratorTypes(),424      /*doc=*/nullptr,425      /*library_call=*/nullptr);426  if (!fusedOp.getShapesToLoopsMap()) {427    // Fused op has invalid indexing maps. Typically this means something is off428    // in the input, but going ahead here would result in verification errors.429    // So cleanup and abort.430    rewriter.eraseOp(fusedOp);431    return rewriter.notifyMatchFailure(432        fusedOp, "fused op failed loop bound computation check");433  }434 435  // Construct an AffineMap from consumer loops to producer loops.436  // consumer loop -> tensor index437  AffineMap consumerResultIndexMap =438      consumer.getMatchingIndexingMap(fusedOperand);439  // tensor index -> producer loop440  AffineMap invProducerResultIndexMap =441      inversePermutation(producerResultIndexMap);442  assert(invProducerResultIndexMap &&443         "expected producer result indexig map to be invertible");444  // consumer loop -> producer loop445  AffineMap consumerToProducerLoopsMap =446      invProducerResultIndexMap.compose(consumerResultIndexMap);447 448  generateFusedElementwiseOpRegion(449      rewriter, fusedOp, consumerToProducerLoopsMap, fusedOperand,450      consumer.getNumLoops(), preservedProducerResults);451  ElementwiseOpFusionResult result;452  result.fusedOp = fusedOp;453  int resultNum = 0;454  for (auto [index, producerResult] : llvm::enumerate(producer->getResults()))455    if (preservedProducerResults.count(index))456      result.replacements[producerResult] = fusedOp->getResult(resultNum++);457  for (auto consumerResult : consumer->getResults())458    result.replacements[consumerResult] = fusedOp->getResult(resultNum++);459  return result;460}461 462namespace {463/// Patterns to fuse a generic op, with the producer of its operands.464class FuseElementwiseOps : public OpRewritePattern<GenericOp> {465public:466  FuseElementwiseOps(MLIRContext *context, ControlFusionFn fun,467                     PatternBenefit benefit = 1)468      : OpRewritePattern<GenericOp>(context, benefit),469        controlFn(std::move(fun)) {}470 471  LogicalResult matchAndRewrite(GenericOp genericOp,472                                PatternRewriter &rewriter) const override {473    // Find the first operand that is defined by another generic op on tensors.474    for (OpOperand &opOperand : genericOp->getOpOperands()) {475      if (!areElementwiseOpsFusable(&opOperand))476        continue;477      if (!controlFn(&opOperand))478        continue;479 480      Operation *producer = opOperand.get().getDefiningOp();481 482      // Find the producer of the operand.483      FailureOr<ElementwiseOpFusionResult> fusionResult =484          fuseElementwiseOps(rewriter, &opOperand);485      if (failed(fusionResult))486        return rewriter.notifyMatchFailure(genericOp, "fusion failed");487 488      // Perform the fusion.489      for (auto [origVal, replacement] : fusionResult->replacements) {490        rewriter.replaceUsesWithIf(origVal, replacement, [&](OpOperand &use) {491          // Only replace consumer uses.492          return use.get().getDefiningOp() != producer;493        });494      }495      rewriter.eraseOp(genericOp);496      return success();497    }498    return failure();499  }500 501private:502  ControlFusionFn controlFn;503};504} // namespace505 506//===---------------------------------------------------------------------===//507// Methods and patterns that fuse reshape ops with elementwise operations by508// expanding the dimensionality of the elementwise operations.509//===---------------------------------------------------------------------===//510 511/// Conditions for folding a structured linalg operation with a reshape op by512/// expanding the iteration space dimensionality for tensor operations. These513/// are preconditions assumed by `foldReshapeByDimExpansion` which implements514/// the following fusion pattern.515///516///  Consider517///518///  %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)519///         indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,520///                          affine_map<(d0, d1, d2) -> (d1, d2)>,521///                          affine_map<(d0, d1, d2) -> (d0, d2, d1)>]522///  %d = tensor.expand_shape %c [[0, 1], [2], [3, 4, 5]]523///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>524///525///  The reshape can be folded into the `linalgOp` if its loop dimensionality526///  is increased to match the result (operand) of the tensor.expand_shape.527///  The indexing_map of the fused tensor in the `linalgOp` and the528///  reassociation map helps compute the indexing maps of the modified op.529///  For the above example, based on the reassociation map it530///  can be concluded that531///532///  - The loop used to access the first dimension of the fused tensor is split533///    into two.534///  - The loop used to access the second dimension of the fused tensor is kept535///    as is.536///  - The loop used to access the third dimension of the fused tensor is split537///    into three.538///539///  i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified540///  op, then541///542///   d0 -> e0, e1543///   d1 -> e2, e3, e4544///   d2 -> e5545///546///  substituting this, the structured op can be rewritten as547///548///  %d = linalg.generic ins(%0, %1 : )549///        indexing_maps =550///         [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,551///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,552///          affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]553///554///  Since operands to the linalg generic are now 5D, reshapes can be introduced555///  to make it consistent556///557///  %0 = tensor.expand_shape %a [[0, 1, 2], [3, 4], [5]]558///       : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>559///  %1 = tensor.expand_shape %b [[0, 1, 2], [3]]560///       : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>561///562///  The added reshapes are again expanding patterns, so they will get fused563///  with its producers if possible.564static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp,565                                               OpOperand *fusableOpOperand) {566  // Is fusable only if:567  // - All the indexing maps for operands and results are projected568  //   permutations.569  // - The fused tensor is not a scalar.570  SmallVector<utils::IteratorType> iteratorTypes =571      linalgOp.getIteratorTypesArray();572  AffineMap operandMap = linalgOp.getMatchingIndexingMap(fusableOpOperand);573  return linalgOp.hasPureTensorSemantics() &&574         llvm::all_of(linalgOp.getIndexingMaps().getValue(),575                      [](Attribute attr) {576                        return cast<AffineMapAttr>(attr)577                            .getValue()578                            .isProjectedPermutation();579                      }) &&580         operandMap.getNumResults() > 0;581}582 583namespace {584/// Information needed to expand a generic operation to fold the reshape with585/// it.586class ExpansionInfo {587public:588  // Computes the mapping from original dimensions of the op to the dimensions589  // of the expanded op given the `indexingMap` of the fused operand/result of590  // the generic op, the `reassocationMaps` of the reshape op and the shape of591  // the expanded op.592  LogicalResult compute(LinalgOp linalgOp, OpOperand *fusableOpOperand,593                        ArrayRef<AffineMap> reassociationMaps,594                        ArrayRef<OpFoldResult> expandedShape,595                        PatternRewriter &rewriter);596  unsigned getOrigOpNumDims() const { return reassociation.size(); }597  unsigned getExpandedOpNumDims() const { return expandedOpNumDims; }598  ReassociationIndicesRef getExpandedDims(unsigned i) const {599    return reassociation[i];600  }601  ArrayRef<OpFoldResult> getExpandedShapeOfDim(unsigned i) const {602    return expandedShapeMap[i];603  }604  ArrayRef<OpFoldResult> getOriginalShape() const { return originalLoopExtent; }605 606private:607  /// Reassociation from the dimensions in the original operation to the608  /// dimension of the expanded operation.609  SmallVector<ReassociationIndices> reassociation;610  /// Mapping from extent of loops in the original operation, to the extent of611  /// loops in the expanded operation.612  SmallVector<SmallVector<OpFoldResult>> expandedShapeMap;613  /// Extent of the loop in the original operation.614  SmallVector<OpFoldResult> originalLoopExtent;615  unsigned expandedOpNumDims;616};617} // namespace618 619LogicalResult ExpansionInfo::compute(LinalgOp linalgOp,620                                     OpOperand *fusableOpOperand,621                                     ArrayRef<AffineMap> reassociationMaps,622                                     ArrayRef<OpFoldResult> expandedShape,623                                     PatternRewriter &rewriter) {624  if (reassociationMaps.empty())625    return failure();626  AffineMap fusedIndexMap = linalgOp.getMatchingIndexingMap(fusableOpOperand);627 628  OpBuilder::InsertionGuard g(rewriter);629  rewriter.setInsertionPoint(linalgOp);630  originalLoopExtent = llvm::map_to_vector(631      linalgOp.createLoopRanges(rewriter, linalgOp->getLoc()),632      [](Range r) { return r.size; });633 634  reassociation.clear();635  expandedShapeMap.clear();636  // Compute the number of dimension in the expanded op that correspond to each637  // dimension of the original op.638  SmallVector<unsigned> numExpandedDims(fusedIndexMap.getNumDims(), 1);639  expandedShapeMap.resize(fusedIndexMap.getNumDims());640  for (const auto &resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {641    unsigned pos = cast<AffineDimExpr>(resultExpr.value()).getPosition();642    AffineMap foldedDims = reassociationMaps[resultExpr.index()];643    numExpandedDims[pos] = foldedDims.getNumResults();644    ArrayRef<OpFoldResult> shape =645        expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]);646    expandedShapeMap[pos].assign(shape.begin(), shape.end());647  }648  // The remaining dimensions remain the same.649  for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims()))650    if (expandedShapeMap[i].empty())651      expandedShapeMap[i] = {originalLoopExtent[i]};652 653  // Compute reassociation map from the original op to the expanded op.654  unsigned sum = 0;655  reassociation.reserve(fusedIndexMap.getNumDims());656  for (const auto &numFoldedDim : llvm::enumerate(numExpandedDims)) {657    auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value());658    reassociation.emplace_back(seq.begin(), seq.end());659    sum += numFoldedDim.value();660  }661  expandedOpNumDims = sum;662  return success();663}664 665/// Return the indexing map to use in the expanded op for a given the666/// `indexingMap` of the original operation.667static AffineMap668getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap,669                           const ExpansionInfo &expansionInfo) {670  SmallVector<AffineExpr> newExprs;671  for (AffineExpr expr : indexingMap.getResults()) {672    unsigned pos = cast<AffineDimExpr>(expr).getPosition();673    SmallVector<AffineExpr, 4> expandedExprs = llvm::to_vector<4>(674        llvm::map_range(expansionInfo.getExpandedDims(pos), [&](int64_t v) {675          return builder.getAffineDimExpr(static_cast<unsigned>(v));676        }));677    newExprs.append(expandedExprs.begin(), expandedExprs.end());678  }679  return AffineMap::get(expansionInfo.getExpandedOpNumDims(),680                        indexingMap.getNumSymbols(), newExprs,681                        builder.getContext());682}683 684/// Return the shape and type of the operand/result to use in the expanded op685/// given the type in the original op.686static std::tuple<SmallVector<OpFoldResult>, RankedTensorType>687getExpandedShapeAndType(RankedTensorType originalType, AffineMap indexingMap,688                        const ExpansionInfo &expansionInfo) {689  SmallVector<OpFoldResult> expandedShape;690  for (AffineExpr expr : indexingMap.getResults()) {691    unsigned dim = cast<AffineDimExpr>(expr).getPosition();692    ArrayRef<OpFoldResult> dimExpansion =693        expansionInfo.getExpandedShapeOfDim(dim);694    expandedShape.append(dimExpansion.begin(), dimExpansion.end());695  }696  SmallVector<int64_t> expandedStaticShape;697  std::tie(expandedStaticShape, std::ignore) =698      decomposeMixedValues(expandedShape);699  return {expandedShape, RankedTensorType::get(expandedStaticShape,700                                               originalType.getElementType())};701}702 703/// Returns the reassociation maps to use in the `tensor.expand_shape`704/// operation to convert the operands of the original operation to operands of705/// the expanded operation. The same method is used to compute the706/// `tensor.collapse_shape` used to collapse the result of the expanded707/// op to get the value that can replace all uses of the results of the original708/// op.709static SmallVector<ReassociationIndices>710getReassociationForExpansion(AffineMap indexingMap,711                             const ExpansionInfo &expansionInfo) {712  SmallVector<ReassociationIndices> reassociation;713  unsigned numReshapeDims = 0;714  for (AffineExpr expr : indexingMap.getResults()) {715    unsigned dim = cast<AffineDimExpr>(expr).getPosition();716    auto numExpandedDims = expansionInfo.getExpandedDims(dim).size();717    SmallVector<int64_t, 2> indices = llvm::to_vector<2>(718        llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims));719    reassociation.emplace_back(std::move(indices));720    numReshapeDims += numExpandedDims;721  }722  return reassociation;723}724 725/// Update the body of an expanded linalg operation having index semantics. The726/// indices of the original operation need to be recovered by linearizing the727/// indices of the correspoding dimensions of the expanded operation. For now it728/// is assumed that the shapes of the expanded operation needed for729/// linearization are static.730static void updateExpandedGenericOpRegion(PatternRewriter &rewriter,731                                          Location loc, Region &fusedRegion,732                                          const ExpansionInfo &expansionInfo) {733  // Replace the original indices by the linearization of the expanded indices.734  for (IndexOp indexOp :735       llvm::make_early_inc_range(fusedRegion.front().getOps<IndexOp>())) {736    ArrayRef<int64_t> expandedDims =737        expansionInfo.getExpandedDims(indexOp.getDim());738    assert(!expandedDims.empty() && "expected valid expansion info");739 740    // Skip index operations that are not affected by the expansion.741    if (expandedDims.size() == 1 &&742        expandedDims.front() == (int64_t)indexOp.getDim())743      continue;744 745    // Linearize the expanded indices of the original index dimension.746    OpBuilder::InsertionGuard guard(rewriter);747    rewriter.setInsertionPointAfter(indexOp);748    ArrayRef<OpFoldResult> expandedDimsShape =749        expansionInfo.getExpandedShapeOfDim(indexOp.getDim()).drop_front();750    SmallVector<Value> expandedIndices;751    expandedIndices.reserve(expandedDims.size() - 1);752    llvm::transform(753        expandedDims.drop_front(), std::back_inserter(expandedIndices),754        [&](int64_t dim) { return IndexOp::create(rewriter, loc, dim); });755    OpFoldResult newIndex =756        IndexOp::create(rewriter, loc, expandedDims.front()).getResult();757    for (auto [expandedShape, expandedIndex] :758         llvm::zip(expandedDimsShape, expandedIndices)) {759      AffineExpr idx, acc, shape;760      bindDims(rewriter.getContext(), idx, acc);761      bindSymbols(rewriter.getContext(), shape);762      newIndex = affine::makeComposedFoldedAffineApply(763          rewriter, indexOp.getLoc(), idx + acc * shape,764          ArrayRef<OpFoldResult>{expandedIndex, newIndex, expandedShape});765    }766    Value newIndexVal =767        getValueOrCreateConstantIndexOp(rewriter, indexOp.getLoc(), newIndex);768    rewriter.replaceOp(indexOp, newIndexVal);769  }770}771 772// Create an expanded transpose op.773// the reassociation map is already permuted hence we inverse permute and then774// flatten it. Then we inverse permute it again to get the final expanded775// transpose permutation. For example,776//777// permutation = [2, 0, 1]778// reassociation_map for expansion = [[0, 1], [2], [3, 4, 5]]779//780// inverse permutation = [1, 2, 0]781// applied to reassocation_map and then flattened becomes782// flatened permutation = [2, 3, 4, 5, 0, 1]783// final permuation is the inverse of the flattened permutation.784//785// Becomes786//787// permutation=[4, 5, 0, 1, 2, 3]788 789static Operation *createExpandedTransposeOp(PatternRewriter &rewriter,790                                            TransposeOp transposeOp,791                                            Value expandedInput, Value output,792                                            ExpansionInfo &expansionInfo) {793  SmallVector<int64_t> newPerm;794  for (int64_t perm : invertPermutationVector(transposeOp.getPermutation())) {795    auto reassoc = expansionInfo.getExpandedDims(perm);796    for (int64_t dim : reassoc) {797      newPerm.push_back(dim);798    }799  }800  return TransposeOp::create(rewriter, transposeOp.getLoc(), expandedInput,801                             output, invertPermutationVector(newPerm));802}803 804// Create an expanded generic op.805static Operation *createExpandedGenericOp(806    PatternRewriter &rewriter, LinalgOp linalgOp, TypeRange resultTypes,807    ArrayRef<Value> &expandedOpOperands, ArrayRef<Value> outputs,808    ExpansionInfo &expansionInfo, ArrayRef<AffineMap> expandedOpIndexingMaps) {809  // The iterator types of the expanded op are all parallel.810  SmallVector<utils::IteratorType> iteratorTypes(811      expansionInfo.getExpandedOpNumDims(), utils::IteratorType::parallel);812 813  for (auto [i, type] : llvm::enumerate(linalgOp.getIteratorTypesArray()))814    for (auto j : expansionInfo.getExpandedDims(i))815      iteratorTypes[j] = type;816 817  Operation *fused = GenericOp::create(rewriter, linalgOp.getLoc(), resultTypes,818                                       expandedOpOperands, outputs,819                                       expandedOpIndexingMaps, iteratorTypes);820 821  Region &fusedRegion = fused->getRegion(0);822  Region &originalRegion = linalgOp->getRegion(0);823  rewriter.cloneRegionBefore(originalRegion, fusedRegion, fusedRegion.begin());824 825  // Update the index accesses after the expansion.826  updateExpandedGenericOpRegion(rewriter, linalgOp.getLoc(), fusedRegion,827                                expansionInfo);828 829  return fused;830}831 832// Create an expanded fused op that retains the name for certain ops833// such as fill, copy and transpose and produce a generic op for834// rest of linalg ops.835static Operation *createExpandedOp(PatternRewriter &rewriter, LinalgOp linalgOp,836                                   TypeRange resultTypes,837                                   ArrayRef<Value> expandedOpOperands,838                                   ArrayRef<Value> outputs,839                                   ArrayRef<AffineMap> expandedOpIndexingMaps,840                                   ExpansionInfo &expansionInfo) {841 842  return TypeSwitch<Operation *, Operation *>(linalgOp.getOperation())843      .Case<TransposeOp>([&](TransposeOp transposeOp) {844        return createExpandedTransposeOp(rewriter, transposeOp,845                                         expandedOpOperands[0], outputs[0],846                                         expansionInfo);847      })848      .Case<FillOp, CopyOp>([&](Operation *op) {849        return clone(rewriter, linalgOp, resultTypes,850                     llvm::to_vector(llvm::concat<Value>(851                         llvm::to_vector(expandedOpOperands),852                         llvm::to_vector(outputs))));853      })854      .Default([&](Operation *op) {855        return createExpandedGenericOp(rewriter, linalgOp, resultTypes,856                                       expandedOpOperands, outputs,857                                       expansionInfo, expandedOpIndexingMaps);858      });859}860 861/// Implements the fusion of a tensor.collapse_shape or a tensor.expand_shape op862/// and a generic op as explained in `isFusableWithReshapeByExpansion`. Assumes863/// that those conditions have been satisfied.864static std::optional<SmallVector<Value>>865fuseWithReshapeByExpansion(LinalgOp linalgOp, Operation *reshapeOp,866                           OpOperand *fusableOpOperand,867                           PatternRewriter &rewriter) {868  assert(isFusableWithReshapeByDimExpansion(linalgOp, fusableOpOperand) &&869         "preconditions for fuse operation failed");870 871  Location loc = linalgOp.getLoc();872  SmallVector<OpFoldResult> expandedShape;873  SmallVector<AffineMap, 4> reassociationIndices;874  Value src;875  if (auto expandingReshapeOp = dyn_cast<tensor::ExpandShapeOp>(reshapeOp)) {876    // Try to move the dynamic dimensions in output shape before the `linalgOp`877    // to maintain SSA validity878    if (failed(moveValueDefinitions(879            rewriter, expandingReshapeOp.getOutputShape(), linalgOp)))880      return std::nullopt;881 882    expandedShape = expandingReshapeOp.getMixedOutputShape();883    reassociationIndices = expandingReshapeOp.getReassociationMaps();884    src = expandingReshapeOp.getSrc();885  } else {886    auto collapsingReshapeOp = dyn_cast<tensor::CollapseShapeOp>(reshapeOp);887    if (!collapsingReshapeOp)888      return std::nullopt;889 890    expandedShape = tensor::getMixedSizes(891        rewriter, collapsingReshapeOp->getLoc(), collapsingReshapeOp.getSrc());892    reassociationIndices = collapsingReshapeOp.getReassociationMaps();893    src = collapsingReshapeOp.getSrc();894  }895 896  ExpansionInfo expansionInfo;897  if (failed(expansionInfo.compute(linalgOp, fusableOpOperand,898                                   reassociationIndices, expandedShape,899                                   rewriter)))900    return std::nullopt;901 902  SmallVector<AffineMap, 4> expandedOpIndexingMaps = llvm::to_vector<4>(903      llvm::map_range(linalgOp.getIndexingMapsArray(), [&](AffineMap m) {904        return getIndexingMapInExpandedOp(rewriter, m, expansionInfo);905      }));906 907  // Set insertion point to the generic op.908  OpBuilder::InsertionGuard g(rewriter);909  rewriter.setInsertionPoint(linalgOp);910 911  SmallVector<Value> expandedOpOperands;912  expandedOpOperands.reserve(linalgOp.getNumDpsInputs());913  for (OpOperand *opOperand : linalgOp.getDpsInputOperands()) {914    if (opOperand == fusableOpOperand) {915      expandedOpOperands.push_back(src);916      continue;917    }918    if (auto opOperandType =919            dyn_cast<RankedTensorType>(opOperand->get().getType())) {920      AffineMap indexingMap = linalgOp.getMatchingIndexingMap(opOperand);921      SmallVector<OpFoldResult> expandedOperandShape;922      RankedTensorType expandedOperandType;923      std::tie(expandedOperandShape, expandedOperandType) =924          getExpandedShapeAndType(opOperandType, indexingMap, expansionInfo);925      if (expandedOperandType != opOperand->get().getType()) {926        // Reshape the operand to get the right type.927        SmallVector<ReassociationIndices> reassociation =928            getReassociationForExpansion(indexingMap, expansionInfo);929        if (failed(reshapeLikeShapesAreCompatible(930                [&](const Twine &msg) {931                  return rewriter.notifyMatchFailure(linalgOp, msg);932                },933                opOperandType.getShape(), expandedOperandType.getShape(),934                reassociation,935                /*isExpandingReshape=*/true)))936          return std::nullopt;937        expandedOpOperands.push_back(tensor::ExpandShapeOp::create(938            rewriter, loc, expandedOperandType, opOperand->get(), reassociation,939            expandedOperandShape));940        continue;941      }942    }943    expandedOpOperands.push_back(opOperand->get());944  }945 946  SmallVector<Value> outputs;947  for (OpOperand &opOperand : linalgOp.getDpsInitsMutable()) {948    AffineMap indexingMap = linalgOp.getMatchingIndexingMap(&opOperand);949    auto opOperandType = cast<RankedTensorType>(opOperand.get().getType());950    SmallVector<OpFoldResult> expandedOutputShape;951    RankedTensorType expandedOutputType;952    std::tie(expandedOutputShape, expandedOutputType) =953        getExpandedShapeAndType(opOperandType, indexingMap, expansionInfo);954    if (expandedOutputType != opOperand.get().getType()) {955      SmallVector<ReassociationIndices> reassociation =956          getReassociationForExpansion(indexingMap, expansionInfo);957      if (failed(reshapeLikeShapesAreCompatible(958              [&](const Twine &msg) {959                return rewriter.notifyMatchFailure(linalgOp, msg);960              },961              opOperandType.getShape(), expandedOutputType.getShape(),962              reassociation,963              /*isExpandingReshape=*/true)))964        return std::nullopt;965      outputs.push_back(tensor::ExpandShapeOp::create(966          rewriter, loc, expandedOutputType, opOperand.get(), reassociation,967          expandedOutputShape));968    } else {969      outputs.push_back(opOperand.get());970    }971  }972 973  TypeRange resultTypes = ValueRange(outputs).getTypes();974  Operation *fusedOp =975      createExpandedOp(rewriter, linalgOp, resultTypes, expandedOpOperands,976                       outputs, expandedOpIndexingMaps, expansionInfo);977  // Reshape the result values to their original shape if this is a collapsing978  // reshape folded into its consumer.979  SmallVector<Value> resultVals;980  for (OpResult opResult : linalgOp->getOpResults()) {981    int64_t resultNumber = opResult.getResultNumber();982    if (resultTypes[resultNumber] != opResult.getType()) {983      SmallVector<ReassociationIndices> reassociation =984          getReassociationForExpansion(985              linalgOp.getMatchingIndexingMap(986                  linalgOp.getDpsInitOperand(resultNumber)),987              expansionInfo);988      resultVals.push_back(tensor::CollapseShapeOp::create(989          rewriter, linalgOp.getLoc(), opResult.getType(),990          fusedOp->getResult(resultNumber), reassociation));991    } else {992      resultVals.push_back(fusedOp->getResult(resultNumber));993    }994  }995  // Assuming a single result.996  return resultVals;997}998 999namespace {1000 1001/// Pattern to fuse a tensor.collapse_shape op with its consumer structured op,1002/// when the reshape op is collapsing dimensions. The dimensionality of the loop1003/// in the consumer is expanded.1004class FoldWithProducerReshapeOpByExpansion1005    : public OpInterfaceRewritePattern<LinalgOp> {1006public:1007  FoldWithProducerReshapeOpByExpansion(MLIRContext *context,1008                                       ControlFusionFn foldReshapes,1009                                       PatternBenefit benefit = 1)1010      : OpInterfaceRewritePattern<LinalgOp>(context, benefit),1011        controlFoldingReshapes(std::move(foldReshapes)) {}1012 1013  LogicalResult matchAndRewrite(LinalgOp linalgOp,1014                                PatternRewriter &rewriter) const override {1015    for (OpOperand *opOperand : linalgOp.getDpsInputOperands()) {1016      tensor::CollapseShapeOp reshapeOp =1017          opOperand->get().getDefiningOp<tensor::CollapseShapeOp>();1018      if (!reshapeOp)1019        continue;1020      // Fold only if1021      // - The tensor reshape op is folding.1022      // - All constraints of fusing with reshape by expansion are met.1023      if (!isFusableWithReshapeByDimExpansion(linalgOp, opOperand) ||1024          (!controlFoldingReshapes(opOperand)))1025        continue;1026 1027      std::optional<SmallVector<Value>> replacementValues =1028          fuseWithReshapeByExpansion(linalgOp, reshapeOp, opOperand, rewriter);1029      if (!replacementValues)1030        return failure();1031      rewriter.replaceOp(linalgOp, *replacementValues);1032      return success();1033    }1034    return failure();1035  }1036 1037private:1038  ControlFusionFn controlFoldingReshapes;1039};1040 1041/// Carries information about a padded dimension.1042struct PadDimInfo {1043  // The resulting shape after padding each dimension.1044  SmallVector<int64_t> paddedShape;1045 1046  // Low and high padding amounts for each dimension.1047  SmallVector<OpFoldResult> lowPad;1048  SmallVector<OpFoldResult> highPad;1049};1050 1051/// Computes the expanded padding information for the given pad operation based1052/// on the provided expanded shape and reassociation indices. Returns a list of1053/// PadDimInfo containing the low and high padding amounts and the padded1054/// size for each dimension, or failure if the expansion is not possible.1055static FailureOr<PadDimInfo>1056computeExpandedPadding(tensor::PadOp padOp, ArrayRef<int64_t> expandedShape,1057                       ArrayRef<ReassociationIndices> reassociations,1058                       PatternRewriter &rewriter) {1059  // If the padding value depends on the index values of the pad operation,1060  // then it may not be valid to expand the dimensions, since it will change1061  // the index values on which the padding value depends. This is not currently1062  // supported by the pad expansion patterns, but it could be implemented1063  // similarly to the expansion of linalg.generic ops with linalg.index ops in1064  // the body, as is done in `updateExpandedGenericOpRegion`.1065  if (!padOp.getConstantPaddingValue())1066    return failure();1067 1068  // Expanded dimensions cannot have padding because the resulting padding may1069  // not be representable by a tensor.pad op. There are some special cases where1070  // it is possible (like expanding unit dims), but supporting these cases is1071  // NYI, so disallow it for now.1072  ArrayRef<int64_t> low = padOp.getStaticLow();1073  ArrayRef<int64_t> high = padOp.getStaticHigh();1074  for (auto [reInd, l, h] : llvm::zip_equal(reassociations, low, high)) {1075    if (reInd.size() != 1 && (l != 0 || h != 0))1076      return failure();1077  }1078 1079  SmallVector<OpFoldResult> mixedLowPad(padOp.getMixedLowPad());1080  SmallVector<OpFoldResult> mixedHighPad(padOp.getMixedHighPad());1081  ArrayRef<int64_t> paddedShape = padOp.getResultType().getShape();1082  PadDimInfo padDimInfo;1083  padDimInfo.paddedShape.assign(expandedShape);1084  padDimInfo.lowPad.assign(expandedShape.size(), rewriter.getIndexAttr(0));1085  padDimInfo.highPad.assign(expandedShape.size(), rewriter.getIndexAttr(0));1086  for (auto [idx, reInd] : llvm::enumerate(reassociations)) {1087    if (reInd.size() == 1) {1088      padDimInfo.paddedShape[reInd[0]] = paddedShape[idx];1089      padDimInfo.lowPad[reInd[0]] = mixedLowPad[idx];1090      padDimInfo.highPad[reInd[0]] = mixedHighPad[idx];1091    }1092  }1093 1094  return padDimInfo;1095}1096 1097class FoldPadWithProducerReshapeOpByExpansion1098    : public OpRewritePattern<tensor::PadOp> {1099public:1100  FoldPadWithProducerReshapeOpByExpansion(MLIRContext *context,1101                                          ControlFusionFn foldReshapes,1102                                          PatternBenefit benefit = 1)1103      : OpRewritePattern<tensor::PadOp>(context, benefit),1104        controlFoldingReshapes(std::move(foldReshapes)) {}1105 1106  LogicalResult matchAndRewrite(tensor::PadOp padOp,1107                                PatternRewriter &rewriter) const override {1108    tensor::CollapseShapeOp reshapeOp =1109        padOp.getSource().getDefiningOp<tensor::CollapseShapeOp>();1110    if (!reshapeOp)1111      return failure();1112 1113    if (!controlFoldingReshapes(&padOp.getSourceMutable())) {1114      return rewriter.notifyMatchFailure(padOp,1115                                         "fusion blocked by control function");1116    }1117 1118    RankedTensorType expandedType = reshapeOp.getSrcType();1119    SmallVector<ReassociationIndices> reassociations =1120        reshapeOp.getReassociationIndices();1121    FailureOr<PadDimInfo> maybeExpandedPadding = computeExpandedPadding(1122        padOp, expandedType.getShape(), reassociations, rewriter);1123    if (failed(maybeExpandedPadding))1124      return failure();1125    PadDimInfo &expandedPadding = maybeExpandedPadding.value();1126 1127    Location loc = padOp->getLoc();1128    RankedTensorType expandedPaddedType =1129        padOp.getResultType().clone(expandedPadding.paddedShape);1130 1131    auto newPadOp = tensor::PadOp::create(1132        rewriter, loc, expandedPaddedType, reshapeOp.getSrc(),1133        expandedPadding.lowPad, expandedPadding.highPad,1134        padOp.getConstantPaddingValue(), padOp.getNofold());1135 1136    rewriter.replaceOpWithNewOp<tensor::CollapseShapeOp>(1137        padOp, padOp.getResultType(), newPadOp.getResult(), reassociations);1138 1139    return success();1140  }1141 1142private:1143  ControlFusionFn controlFoldingReshapes;1144};1145 1146class FoldReshapeWithProducerPadOpByExpansion1147    : public OpRewritePattern<tensor::ExpandShapeOp> {1148public:1149  FoldReshapeWithProducerPadOpByExpansion(MLIRContext *context,1150                                          ControlFusionFn foldReshapes,1151                                          PatternBenefit benefit = 1)1152      : OpRewritePattern<tensor::ExpandShapeOp>(context, benefit),1153        controlFoldingReshapes(std::move(foldReshapes)) {}1154 1155  LogicalResult matchAndRewrite(tensor::ExpandShapeOp expandOp,1156                                PatternRewriter &rewriter) const override {1157    tensor::PadOp padOp = expandOp.getSrc().getDefiningOp<tensor::PadOp>();1158    if (!padOp)1159      return failure();1160 1161    if (!controlFoldingReshapes(&expandOp.getSrcMutable())) {1162      return rewriter.notifyMatchFailure(expandOp,1163                                         "fusion blocked by control function");1164    }1165 1166    RankedTensorType expandedType = expandOp.getResultType();1167    SmallVector<ReassociationIndices> reassociations =1168        expandOp.getReassociationIndices();1169    FailureOr<PadDimInfo> maybeExpandedPadding = computeExpandedPadding(1170        padOp, expandedType.getShape(), reassociations, rewriter);1171    if (failed(maybeExpandedPadding))1172      return failure();1173    PadDimInfo &expandedPadding = maybeExpandedPadding.value();1174 1175    Location loc = expandOp->getLoc();1176    SmallVector<OpFoldResult> newExpandedSizes = expandOp.getMixedOutputShape();1177    SmallVector<int64_t> newExpandedShape(expandedType.getShape());1178    rewriter.setInsertionPointAfterValue(padOp.getSource());1179    SmallVector<OpFoldResult> padSrcSizes =1180        tensor::getMixedSizes(rewriter, loc, padOp.getSource());1181    for (auto [idx, reInd] : llvm::enumerate(reassociations)) {1182      // We know that any reassociation with multiple dims is not padded because1183      // of the requirements of computeExpandedPadding.1184      if (reInd.size() == 1) {1185        newExpandedShape[reInd[0]] = padOp.getSourceType().getDimSize(idx);1186        newExpandedSizes[reInd[0]] = padSrcSizes[idx];1187      }1188    }1189    RankedTensorType newExpandedType = expandedType.clone(newExpandedShape);1190    auto newExpandOp = tensor::ExpandShapeOp::create(1191        rewriter, loc, newExpandedType, padOp.getSource(), reassociations,1192        newExpandedSizes);1193    RankedTensorType expandedPaddedType =1194        padOp.getResultType().clone(expandedPadding.paddedShape);1195    rewriter.setInsertionPoint(expandOp);1196    auto newPadOp = tensor::PadOp::create(1197        rewriter, loc, expandedPaddedType, newExpandOp.getResult(),1198        expandedPadding.lowPad, expandedPadding.highPad,1199        padOp.getConstantPaddingValue(), padOp.getNofold());1200 1201    rewriter.replaceOp(expandOp, newPadOp.getResult());1202 1203    return success();1204  }1205 1206private:1207  ControlFusionFn controlFoldingReshapes;1208};1209 1210/// Pattern to fold a tensor.expand_shape op with its producer generic op1211/// by expanding the dimensionality of the loop in the producer op.1212struct FoldReshapeWithGenericOpByExpansion1213    : public OpRewritePattern<tensor::ExpandShapeOp> {1214 1215  FoldReshapeWithGenericOpByExpansion(MLIRContext *context,1216                                      ControlFusionFn foldReshapes,1217                                      PatternBenefit benefit = 1)1218      : OpRewritePattern<tensor::ExpandShapeOp>(context, benefit),1219        controlFoldingReshapes(std::move(foldReshapes)) {}1220 1221  LogicalResult matchAndRewrite(tensor::ExpandShapeOp reshapeOp,1222                                PatternRewriter &rewriter) const override {1223    // Fold only if all constraints of fusing with reshape by expansion are met.1224    auto producerResult = dyn_cast<OpResult>(reshapeOp.getSrc());1225    if (!producerResult) {1226      return rewriter.notifyMatchFailure(reshapeOp,1227                                         "source not produced by an operation");1228    }1229 1230    auto producer = dyn_cast<LinalgOp>(producerResult.getOwner());1231    if (!producer) {1232      return rewriter.notifyMatchFailure(reshapeOp,1233                                         "producer not a generic op");1234    }1235 1236    if (!isFusableWithReshapeByDimExpansion(1237            producer,1238            producer.getDpsInitOperand(producerResult.getResultNumber()))) {1239      return rewriter.notifyMatchFailure(1240          reshapeOp, "failed preconditions of fusion with producer generic op");1241    }1242 1243    if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {1244      return rewriter.notifyMatchFailure(reshapeOp,1245                                         "fusion blocked by control function");1246    }1247 1248    std::optional<SmallVector<Value>> replacementValues =1249        fuseWithReshapeByExpansion(1250            producer, reshapeOp,1251            producer.getDpsInitOperand(producerResult.getResultNumber()),1252            rewriter);1253    if (!replacementValues) {1254      return rewriter.notifyMatchFailure(reshapeOp,1255                                         "fusion by expansion failed");1256    }1257 1258    // Find the replacement for the reshape op. Since the replacements have the1259    // same type as the returns of the original generic op, the consumer reshape1260    // op can be replaced by the source of the collapse_shape op that defines1261    // the replacement.1262    Value reshapeReplacement =1263        (*replacementValues)[cast<OpResult>(reshapeOp.getSrc())1264                                 .getResultNumber()];1265    if (auto collapseOp =1266            reshapeReplacement.getDefiningOp<tensor::CollapseShapeOp>()) {1267      reshapeReplacement = collapseOp.getSrc();1268    }1269    rewriter.replaceOp(reshapeOp, reshapeReplacement);1270    rewriter.replaceOp(producer, *replacementValues);1271    return success();1272  }1273 1274private:1275  ControlFusionFn controlFoldingReshapes;1276};1277} // namespace1278 1279//===---------------------------------------------------------------------===//1280// Methods and patterns to fuse reshape with linalg.generic operations by1281// contraction of dimensions.1282//===---------------------------------------------------------------------===//1283 1284/// For a given list of indices in the range of the `indexingMap` that are1285/// folded, return the indices of the corresponding domain. Return1286/// `std::nullopt` on failure. Ensures that all the elements of the returned1287/// reassociation are distinct.1288static ReassociationIndices1289getDomainReassociation(AffineMap indexingMap,1290                       ReassociationIndicesRef rangeReassociation) {1291  assert(indexingMap.isProjectedPermutation() &&1292         "expected projected permutation");1293 1294  ReassociationIndices domainReassociation = llvm::to_vector<4>(1295      llvm::map_range(rangeReassociation, [&](int64_t pos) -> int64_t {1296        return cast<AffineDimExpr>(indexingMap.getResults()[pos]).getPosition();1297      }));1298  // The projected permutation semantics ensures that there is no repetition of1299  // the domain indices.1300  return domainReassociation;1301}1302 1303/// For a given `dimSequence`, check if the sequence is conserved in the1304/// `indexingMap`. `indexingMap` is expected to be a projected permutation.1305/// Non-existence of the sequence returns true as well.1306bool mlir::linalg::isDimSequencePreserved(AffineMap indexingMap,1307                                          ReassociationIndicesRef dimSequence) {1308  assert(!dimSequence.empty() &&1309         "expected non-empty list for dimension sequence");1310  assert(indexingMap.isProjectedPermutation() &&1311         "expected indexing map to be projected permutation");1312 1313  llvm::SmallDenseSet<unsigned, 4> sequenceElements;1314  sequenceElements.insert_range(dimSequence);1315 1316  unsigned dimSequenceStart = dimSequence[0];1317  for (const auto &expr : enumerate(indexingMap.getResults())) {1318    unsigned dimInMapStart = cast<AffineDimExpr>(expr.value()).getPosition();1319    // 1.  Check if this start of the sequence.1320    if (dimInMapStart == dimSequenceStart) {1321      if (expr.index() + dimSequence.size() > indexingMap.getNumResults())1322        return false;1323      // 1a. Check if sequence is preserved.1324      for (const auto &dimInSequence : enumerate(dimSequence)) {1325        unsigned dimInMap =1326            cast<AffineDimExpr>(1327                indexingMap.getResult(expr.index() + dimInSequence.index()))1328                .getPosition();1329        if (dimInMap != dimInSequence.value())1330          return false;1331      }1332      // Found the sequence. Projected permutation1333      // enforces that all AffineDimExprs in the result are unique, so no1334      // further checks are needed.1335      return true;1336    }1337    // 2. If position in the expr (which is of type AffineDimExpr) is part1338    // of sequence, return false here. This implies the entire sequence does not1339    // exist in the indexing map.1340    if (sequenceElements.count(dimInMapStart))1341      return false;1342  }1343  // 3. No element of sequence found. Return true.1344  return true;1345}1346 1347bool mlir::linalg::areDimSequencesPreserved(1348    ArrayRef<AffineMap> maps, ArrayRef<ReassociationIndices> dimSequences) {1349  return llvm::all_of(maps, [&](AffineMap map) {1350    return llvm::all_of(dimSequences, [&](ReassociationIndicesRef dimSequence) {1351      return isDimSequencePreserved(map, dimSequence);1352    });1353  });1354}1355 1356// Return the list of dimensions of the iteration domain that can be1357// collapsed to allow for fusion with the a producer that is an expand_shape1358// operation. If all dimensions created by expansion can be collapsed in the1359// iteration space then the reshape is defunct.1360//1361// Example:1362//1363// ```mlir1364// #map = affine_map<(d0, d1) -> (d0, d1)>1365// %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>1366// %2 = tensor.empty [..] : tensor<?x4xf32>1367// %3 = linalg.generic {1368//     indexing_maps = [#map, #map],1369//     iterator_types = ["parallel" ,"parallel"]}1370//     ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<?x4xf32>) {.. }1371// ```1372//1373// can be fused by collapsing the dimensions of the iteration space.1374//1375// ```mlir1376// #map = affine_map<(d0) -> (d0)>1377// %2 = tensor.empty [..] : tensor<?xf32>1378// %3 = linalg.generic {1379//     indexing_maps = [#map, #map],1380//     iterator_types = ["parallel"]}1381//     ins(%1 : tensor<?xf32>) outs(%2 : tensor<?xf32>) {.. }1382// %4 = tensor.expand_shape %3 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>1383// ```1384//1385// In the following example,1386//1387// ```mlir1388// #map0 = affine_map<(d0, d1) -> (d0, d1)>1389// #map1 = affine_map<(d0, d1) -> (d1, d0)>1390// %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>1391// %2 = tensor.empty [..] : tensor<4x?xf32>1392// %2 = linalg.generic {1393//     indexing_maps = [#map0, #map1],1394//     iterator_types = ["parallel" ,"parallel"]}1395//     ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<4x?xf32>) {.. }1396// ```1397//1398// the reshape cannot be fused with the generic op by collapsing the op1399// dimensions since the indexing maps will have to contain mods and divs1400// to preserve the accesses pattern. When no dimensions of the iteration1401// space are collapsable and empty vector is returned.1402static SmallVector<ReassociationIndices>1403getCollapsableIterationSpaceDims(GenericOp genericOp, OpOperand *fusableOperand,1404                                 ArrayRef<ReassociationIndices> reassociation) {1405  // Some basic checks for this fusion to be valid.1406  if (!genericOp.hasPureTensorSemantics())1407    return {};1408 1409  if (!llvm::all_of(genericOp.getIndexingMapsArray(), [](AffineMap map) {1410        return map.isProjectedPermutation();1411      })) {1412    return {};1413  }1414 1415  // Compute all the loops with the reduction iterator types.1416  SmallVector<unsigned> reductionDims;1417  genericOp.getReductionDims(reductionDims);1418 1419  llvm::SmallDenseSet<unsigned, 4> processedIterationDims;1420  AffineMap indexingMap = genericOp.getMatchingIndexingMap(fusableOperand);1421  auto iteratorTypes = genericOp.getIteratorTypesArray();1422  SmallVector<ReassociationIndices> iterationSpaceReassociation;1423  for (ReassociationIndicesRef foldedRangeDims : reassociation) {1424    assert(!foldedRangeDims.empty() && "unexpected empty reassociation");1425 1426    // Ignore dims that are not folded.1427    if (foldedRangeDims.size() == 1)1428      continue;1429 1430    ReassociationIndices foldedIterationSpaceDims =1431        getDomainReassociation(indexingMap, foldedRangeDims);1432 1433    // Check that the folded iteration dims do not contain already processed1434    // dims.1435    if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {1436          return processedIterationDims.count(dim);1437        }))1438      continue;1439 1440    // Check that all folded iterator types are all parallel or all reductions.1441    utils::IteratorType startIteratorType =1442        iteratorTypes[foldedIterationSpaceDims[0]];1443    if (!isParallelIterator(startIteratorType) &&1444        !isReductionIterator(startIteratorType))1445      continue;1446    if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {1447          return iteratorTypes[dim] != startIteratorType;1448        }))1449      continue;1450 1451    // If the folded dimensions correspond to a "reduction" iterator type,1452    // the folded dimensions need to be "in-order". Strictly speaking this is1453    // not necessary, for reductions that are associative and commutative,  but1454    // using a more strict definition of reduction for now.1455    if (isReductionIterator(startIteratorType)) {1456      bool isContiguous = false;1457      for (const auto &startDim : llvm::enumerate(reductionDims)) {1458        // Move window in `reductionDims` to start of the folded iteration dims.1459        if (startDim.value() != foldedIterationSpaceDims[0])1460          continue;1461        // If sizes doesnt match, trivial not contiguous. This condition should1462        // not be hit.1463        if (startDim.index() + foldedIterationSpaceDims.size() >1464            reductionDims.size())1465          break;1466        // Check that the contiguity is maintained.1467        isContiguous = true;1468        for (const auto &foldedDim :1469             llvm::enumerate(foldedIterationSpaceDims)) {1470          if (reductionDims[foldedDim.index() + startDim.index()] !=1471              foldedDim.value()) {1472            isContiguous = false;1473            break;1474          }1475        }1476        break;1477      }1478      if (!isContiguous)1479        continue;1480    }1481 1482    // Check that the sequence is preserved in all indexing maps.1483    if (llvm::any_of(genericOp.getIndexingMapsArray(),1484                     [&](AffineMap indexingMap) {1485                       return !isDimSequencePreserved(indexingMap,1486                                                      foldedIterationSpaceDims);1487                     }))1488      continue;1489 1490    processedIterationDims.insert_range(foldedIterationSpaceDims);1491    iterationSpaceReassociation.emplace_back(1492        std::move(foldedIterationSpaceDims));1493  }1494 1495  return iterationSpaceReassociation;1496}1497 1498/// Helper class to carry state while collapsing the `linalg.generic` op.1499namespace {1500class CollapsingInfo {1501public:1502  LogicalResult initialize(unsigned origNumLoops,1503                           ArrayRef<ReassociationIndices> foldedIterationDims) {1504    llvm::SmallDenseSet<int64_t, 4> processedDims;1505    // Find all the dims that are folded.1506    for (ReassociationIndicesRef foldedIterationDim : foldedIterationDims) {1507      if (foldedIterationDim.empty())1508        continue;1509      // If the folded dims contain dims already folded, that's illegal1510      // specification. Repetition within a list is also illegal.1511      for (auto dim : foldedIterationDim) {1512        if (dim >= origNumLoops)1513          return failure();1514        if (processedDims.count(dim))1515          return failure();1516        processedDims.insert(dim);1517      }1518      collapsedOpToOrigOpIterationDim.emplace_back(foldedIterationDim.begin(),1519                                                   foldedIterationDim.end());1520    }1521    if (processedDims.size() > origNumLoops)1522      return failure();1523 1524    // Add all the preserved dims of the original op as single1525    // elements to `collapsedOpToOrigOpIterationDim`.1526    for (auto dim : llvm::seq<int64_t>(0, origNumLoops)) {1527      if (processedDims.count(dim))1528        continue;1529      collapsedOpToOrigOpIterationDim.emplace_back(ReassociationIndices{dim});1530    }1531 1532    llvm::sort(collapsedOpToOrigOpIterationDim,1533               [&](ReassociationIndicesRef lhs, ReassociationIndicesRef rhs) {1534                 return lhs[0] < rhs[0];1535               });1536    origOpToCollapsedOpIterationDim.resize(origNumLoops);1537    for (const auto &foldedDims :1538         llvm::enumerate(collapsedOpToOrigOpIterationDim)) {1539      for (const auto &dim : enumerate(foldedDims.value()))1540        origOpToCollapsedOpIterationDim[dim.value()] =1541            std::make_pair<int64_t, unsigned>(foldedDims.index(), dim.index());1542    }1543    return success();1544  }1545 1546  /// Return mapping from collapsed loop domain to original loop domain.1547  ArrayRef<ReassociationIndices> getCollapsedOpToOrigOpMapping() const {1548    return collapsedOpToOrigOpIterationDim;1549  }1550 1551  /// Return mapping from original loop domain to collapsed loop domain. The1552  /// mapping is a pair. First value is the dimension in the collapsed loop that1553  /// the original loop is mapped to. Second is the relative position in folded1554  /// list of this domain. For example if the original loop domain is 3D, and1555  /// the collapsed loop domain is folding all of it, i.e.1556  ///1557  /// ```1558  /// collapsedOpToOrigOpMapping = [[0, 1, 2] [3, 4]]`1559  /// ```1560  ///1561  /// then1562  ///1563  /// ```1564  ///  origOpToCollapsedOpMapping[0] = {0, 0};1565  ///  origOpToCollapsedOpMapping[1] = {0, 1};1566  ///  origOpToCollapsedOpMapping[2] = {0, 2};1567  ///  origOpToCollapsedOpMapping[3] = {1, 0};1568  ///  origOpToCollapsedOpMapping[4] = {1, 1};1569  /// ```1570  ///1571  ArrayRef<std::pair<int64_t, unsigned>> getOrigOpToCollapsedOpMapping() const {1572    return origOpToCollapsedOpIterationDim;1573  }1574 1575  /// Return the collapsed op iteration domain rank.1576  unsigned getCollapsedOpIterationRank() const {1577    return collapsedOpToOrigOpIterationDim.size();1578  }1579 1580private:1581  /// Map from the iteration domain index in collapsed op to the iteration1582  /// domain indices in the original op.1583  SmallVector<ReassociationIndices> collapsedOpToOrigOpIterationDim;1584 1585  /// Map from iteration domain index in the original op to the iteration domain1586  /// index in the collapsed op.1587  SmallVector<std::pair<int64_t, unsigned>> origOpToCollapsedOpIterationDim;1588};1589} // namespace1590 1591/// Get the iterator types for the collapsed operation given the original1592/// iterator types and collapsed dimensions.1593static SmallVector<utils::IteratorType>1594getCollapsedOpIteratorTypes(ArrayRef<utils::IteratorType> iteratorTypes,1595                            const CollapsingInfo &collapsingInfo) {1596  SmallVector<utils::IteratorType> collapsedIteratorTypes;1597  for (ReassociationIndicesRef foldedIterDims :1598       collapsingInfo.getCollapsedOpToOrigOpMapping()) {1599    assert(!foldedIterDims.empty() &&1600           "reassociation indices expected to have non-empty sets");1601    // Just pick the iterator type of the first folded dim. Pre-condition checks1602    // expected to have checked that iterator types of all folded dimensions are1603    // the same.1604    collapsedIteratorTypes.push_back(iteratorTypes[foldedIterDims[0]]);1605  }1606  return collapsedIteratorTypes;1607}1608 1609/// Compute the indexing map in the collapsed op that corresponds to the given1610/// `indexingMap` of the original operation.1611static AffineMap1612getCollapsedOpIndexingMap(AffineMap indexingMap,1613                          const CollapsingInfo &collapsingInfo) {1614  MLIRContext *context = indexingMap.getContext();1615  assert(indexingMap.isProjectedPermutation() &&1616         "expected indexing map to be projected permutation");1617  SmallVector<AffineExpr> resultExprs;1618  auto origOpToCollapsedOpMapping =1619      collapsingInfo.getOrigOpToCollapsedOpMapping();1620  for (auto expr : indexingMap.getResults()) {1621    unsigned dim = cast<AffineDimExpr>(expr).getPosition();1622    // If the dim is not the first of the collapsed dim, do nothing.1623    if (origOpToCollapsedOpMapping[dim].second != 0)1624      continue;1625    // The next n-dims are guaranteed to be collapsed. So just use the1626    // iteration dimension of the collapsed op.1627    resultExprs.push_back(1628        getAffineDimExpr(origOpToCollapsedOpMapping[dim].first, context));1629  }1630  return AffineMap::get(collapsingInfo.getCollapsedOpIterationRank(), 0,1631                        resultExprs, context);1632}1633 1634/// Return the `reassociation` indices to use to collapse the operand when the1635/// iteration space of a generic op is collapsed.1636static SmallVector<ReassociationIndices>1637getOperandReassociation(AffineMap indexingMap,1638                        const CollapsingInfo &collapsingInfo) {1639  unsigned counter = 0;1640  SmallVector<ReassociationIndices> operandReassociation;1641  auto origOpToCollapsedOpMapping =1642      collapsingInfo.getOrigOpToCollapsedOpMapping();1643  auto collapsedOpToOrigOpMapping =1644      collapsingInfo.getCollapsedOpToOrigOpMapping();1645  while (counter < indexingMap.getNumResults()) {1646    unsigned dim =1647        cast<AffineDimExpr>(indexingMap.getResult(counter)).getPosition();1648    // This is the start of a collapsed dimensions of the iteration that1649    // is gauranteed to be preserved in the indexing map. The number of folded1650    // dims is obtained from the collapsed op to original op mapping.1651    unsigned numFoldedDims =1652        collapsedOpToOrigOpMapping[origOpToCollapsedOpMapping[dim].first]1653            .size();1654    if (origOpToCollapsedOpMapping[dim].second == 0) {1655      auto range = llvm::seq<unsigned>(counter, counter + numFoldedDims);1656      operandReassociation.emplace_back(range.begin(), range.end());1657    }1658    counter += numFoldedDims;1659  }1660  return operandReassociation;1661}1662 1663/// Get the new value to use for a given `OpOperand` in the collapsed operation.1664static Value getCollapsedOpOperand(Location loc, LinalgOp op,1665                                   OpOperand *opOperand,1666                                   const CollapsingInfo &collapsingInfo,1667                                   OpBuilder &builder) {1668  AffineMap indexingMap = op.getMatchingIndexingMap(opOperand);1669  SmallVector<ReassociationIndices> operandReassociation =1670      getOperandReassociation(indexingMap, collapsingInfo);1671 1672  // If the number of entries in the reassociation for the operand is same as1673  // the number of results of the indexing map, then nothing to do for this1674  // operand.1675  Value operand = opOperand->get();1676  if (operandReassociation.size() == indexingMap.getNumResults())1677    return operand;1678 1679  // Insert a reshape to collapse the dimensions.1680  if (isa<MemRefType>(operand.getType())) {1681    return memref::CollapseShapeOp::create(builder, loc, operand,1682                                           operandReassociation)1683        .getResult();1684  }1685  return tensor::CollapseShapeOp::create(builder, loc, operand,1686                                         operandReassociation)1687      .getResult();1688}1689 1690/// Modify the `linalg.index` operations in the original generic op, to its1691/// value in the collapsed operation.1692static void generateCollapsedIndexingRegion(1693    Location loc, Block *block, const CollapsingInfo &collapsingInfo,1694    ArrayRef<OpFoldResult> loopRange, RewriterBase &rewriter) {1695  OpBuilder::InsertionGuard g(rewriter);1696  rewriter.setInsertionPointToStart(block);1697 1698  // Collect all the original index ops.1699  auto indexOps = llvm::to_vector(block->getOps<linalg::IndexOp>());1700 1701  // For each folded dimension list resolve the original induction variable1702  // values in terms of the folded dimension induction variable.1703  //   i_{folded} = (i_0 * d1 + i1) * d2 + i2.1704  // can be inverted to1705  //   i2 = i_{folded} % d21706  //   i1 = (i_{folded} / d2) % d11707  //   i0 = i_{folded} / (d1 * d2)1708  llvm::DenseMap<unsigned, Value> indexReplacementVals;1709  for (auto foldedDims :1710       enumerate(collapsingInfo.getCollapsedOpToOrigOpMapping())) {1711    ReassociationIndicesRef foldedDimsRef(foldedDims.value());1712    Value newIndexVal =1713        linalg::IndexOp::create(rewriter, loc, foldedDims.index());1714    for (auto dim : llvm::reverse(foldedDimsRef.drop_front())) {1715      Value loopDim =1716          getValueOrCreateConstantIndexOp(rewriter, loc, loopRange[dim]);1717      indexReplacementVals[dim] =1718          rewriter.createOrFold<arith::RemSIOp>(loc, newIndexVal, loopDim);1719      newIndexVal =1720          rewriter.createOrFold<arith::DivSIOp>(loc, newIndexVal, loopDim);1721    }1722    indexReplacementVals[foldedDims.value().front()] = newIndexVal;1723  }1724 1725  for (auto indexOp : indexOps) {1726    auto dim = indexOp.getDim();1727    rewriter.replaceOp(indexOp, indexReplacementVals[dim]);1728  }1729}1730 1731static void collapseOperandsAndResults(LinalgOp op,1732                                       const CollapsingInfo &collapsingInfo,1733                                       RewriterBase &rewriter,1734                                       SmallVectorImpl<Value> &inputOperands,1735                                       SmallVectorImpl<Value> &outputOperands,1736                                       SmallVectorImpl<Type> &resultTypes) {1737  Location loc = op->getLoc();1738  inputOperands =1739      llvm::map_to_vector(op.getDpsInputOperands(), [&](OpOperand *opOperand) {1740        return getCollapsedOpOperand(loc, op, opOperand, collapsingInfo,1741                                     rewriter);1742      });1743 1744  // Get the output operands and result types.1745  resultTypes.reserve(op.getNumDpsInits());1746  outputOperands.reserve(op.getNumDpsInits());1747  for (OpOperand &output : op.getDpsInitsMutable()) {1748    Value newOutput =1749        getCollapsedOpOperand(loc, op, &output, collapsingInfo, rewriter);1750    outputOperands.push_back(newOutput);1751    // If the op has "buffer semantics", then the init operands are ranked1752    // memrefs and the op has no results.1753    if (!op.hasPureBufferSemantics())1754      resultTypes.push_back(newOutput.getType());1755  }1756}1757 1758/// Clone a `LinalgOp` to a collapsed version of same name1759template <typename OpTy>1760static OpTy cloneToCollapsedOp(RewriterBase &rewriter, OpTy origOp,1761                               const CollapsingInfo &collapsingInfo) {1762  return nullptr;1763}1764 1765/// Collapse any `LinalgOp` that does not require any specialization such as1766/// indexing_maps, iterator_types, etc.1767template <>1768LinalgOp cloneToCollapsedOp<LinalgOp>(RewriterBase &rewriter, LinalgOp origOp,1769                                      const CollapsingInfo &collapsingInfo) {1770  SmallVector<Value> inputOperands, outputOperands;1771  SmallVector<Type> resultTypes;1772  collapseOperandsAndResults(origOp, collapsingInfo, rewriter, inputOperands,1773                             outputOperands, resultTypes);1774 1775  return clone(1776      rewriter, origOp, resultTypes,1777      llvm::to_vector(llvm::concat<Value>(inputOperands, outputOperands)));1778}1779 1780/// Collapse a `GenericOp`1781template <>1782GenericOp cloneToCollapsedOp<GenericOp>(RewriterBase &rewriter,1783                                        GenericOp origOp,1784                                        const CollapsingInfo &collapsingInfo) {1785  SmallVector<Value> inputOperands, outputOperands;1786  SmallVector<Type> resultTypes;1787  collapseOperandsAndResults(origOp, collapsingInfo, rewriter, inputOperands,1788                             outputOperands, resultTypes);1789  SmallVector<AffineMap> indexingMaps(1790      llvm::map_range(origOp.getIndexingMapsArray(), [&](AffineMap map) {1791        return getCollapsedOpIndexingMap(map, collapsingInfo);1792      }));1793 1794  SmallVector<utils::IteratorType> iteratorTypes(getCollapsedOpIteratorTypes(1795      origOp.getIteratorTypesArray(), collapsingInfo));1796 1797  GenericOp collapsedOp = linalg::GenericOp::create(1798      rewriter, origOp.getLoc(), resultTypes, inputOperands, outputOperands,1799      indexingMaps, iteratorTypes,1800      [](OpBuilder &builder, Location loc, ValueRange args) {});1801  Block *origOpBlock = &origOp->getRegion(0).front();1802  Block *collapsedOpBlock = &collapsedOp->getRegion(0).front();1803  rewriter.mergeBlocks(origOpBlock, collapsedOpBlock,1804                       collapsedOpBlock->getArguments());1805  return collapsedOp;1806}1807 1808static LinalgOp createCollapsedOp(LinalgOp op,1809                                  const CollapsingInfo &collapsingInfo,1810                                  RewriterBase &rewriter) {1811  if (GenericOp genericOp = dyn_cast<GenericOp>(op.getOperation())) {1812    return cloneToCollapsedOp(rewriter, genericOp, collapsingInfo);1813  } else {1814    return cloneToCollapsedOp(rewriter, op, collapsingInfo);1815  }1816}1817 1818/// Implementation of fusion with reshape operation by collapsing dimensions.1819FailureOr<CollapseResult> mlir::linalg::collapseOpIterationDims(1820    LinalgOp op, ArrayRef<ReassociationIndices> foldedIterationDims,1821    RewriterBase &rewriter) {1822  // Bail on trivial no-op cases.1823  if (op.getNumLoops() <= 1 || foldedIterationDims.empty() ||1824      llvm::all_of(foldedIterationDims, [](ReassociationIndicesRef foldedDims) {1825        return foldedDims.size() <= 1;1826      }))1827    return failure();1828 1829  CollapsingInfo collapsingInfo;1830  if (failed(1831          collapsingInfo.initialize(op.getNumLoops(), foldedIterationDims))) {1832    return rewriter.notifyMatchFailure(1833        op, "illegal to collapse specified dimensions");1834  }1835 1836  bool hasPureBufferSemantics = op.hasPureBufferSemantics();1837  if (hasPureBufferSemantics &&1838      !llvm::all_of(op->getOpOperands(), [&](OpOperand &opOperand) -> bool {1839        MemRefType memRefToCollapse =1840            dyn_cast<MemRefType>(opOperand.get().getType());1841        if (!memRefToCollapse)1842          return true;1843 1844        AffineMap indexingMap = op.getMatchingIndexingMap(&opOperand);1845        SmallVector<ReassociationIndices> operandReassociation =1846            getOperandReassociation(indexingMap, collapsingInfo);1847        return memref::CollapseShapeOp::isGuaranteedCollapsible(1848            memRefToCollapse, operandReassociation);1849      }))1850    return rewriter.notifyMatchFailure(op,1851                                       "memref is not guaranteed collapsible");1852 1853  // Bail on non-canonical ranges.1854  SmallVector<Range> loopRanges = op.createLoopRanges(rewriter, op.getLoc());1855  auto opFoldIsConstantValue = [](OpFoldResult ofr, int64_t value) {1856    if (auto attr = llvm::dyn_cast_if_present<Attribute>(ofr))1857      return cast<IntegerAttr>(attr).getInt() == value;1858    llvm::APInt actual;1859    return matchPattern(cast<Value>(ofr), m_ConstantInt(&actual)) &&1860           actual.getSExtValue() == value;1861  };1862  if (!llvm::all_of(loopRanges, [&](Range range) {1863        return opFoldIsConstantValue(range.offset, 0) &&1864               opFoldIsConstantValue(range.stride, 1);1865      })) {1866    return rewriter.notifyMatchFailure(1867        op, "expected all loop ranges to have zero start and unit stride");1868  }1869 1870  LinalgOp collapsedOp = createCollapsedOp(op, collapsingInfo, rewriter);1871 1872  Location loc = op->getLoc();1873  SmallVector<OpFoldResult> loopBound =1874      llvm::map_to_vector(loopRanges, [](Range range) { return range.size; });1875 1876  if (collapsedOp.hasIndexSemantics()) {1877    // Collect the loop range of the generic op.1878    OpBuilder::InsertionGuard g(rewriter);1879    rewriter.setInsertionPoint(collapsedOp);1880    generateCollapsedIndexingRegion(loc, &collapsedOp->getRegion(0).front(),1881                                    collapsingInfo, loopBound, rewriter);1882  }1883 1884  // Insert expanding reshape for the result to get back the original result1885  // type.1886  SmallVector<Value> results;1887  for (const auto &originalResult : llvm::enumerate(op->getResults())) {1888    Value collapsedOpResult = collapsedOp->getResult(originalResult.index());1889    auto originalResultType =1890        cast<ShapedType>(originalResult.value().getType());1891    auto collapsedOpResultType = cast<ShapedType>(collapsedOpResult.getType());1892    if (collapsedOpResultType.getRank() != originalResultType.getRank()) {1893      AffineMap indexingMap =1894          op.getIndexingMapMatchingResult(originalResult.value());1895      SmallVector<ReassociationIndices> reassociation =1896          getOperandReassociation(indexingMap, collapsingInfo);1897      assert(1898          indexingMap.isProjectedPermutation() &&1899          "Expected indexing map to be a projected permutation for collapsing");1900      SmallVector<OpFoldResult> resultShape =1901          applyPermutationMap(indexingMap, ArrayRef(loopBound));1902      Value result;1903      if (isa<MemRefType>(collapsedOpResult.getType())) {1904        MemRefType expandShapeResultType = MemRefType::get(1905            originalResultType.getShape(), originalResultType.getElementType());1906        result = memref::ExpandShapeOp::create(1907            rewriter, loc, expandShapeResultType, collapsedOpResult,1908            reassociation, resultShape);1909      } else {1910        result = tensor::ExpandShapeOp::create(1911            rewriter, loc, originalResultType, collapsedOpResult, reassociation,1912            resultShape);1913      }1914      results.push_back(result);1915    } else {1916      results.push_back(collapsedOpResult);1917    }1918  }1919  return CollapseResult{results, collapsedOp};1920}1921 1922namespace {1923 1924/// Pattern to fuse a tensor.expand_shape op with its consumer generic op by1925/// contracting dimensions of the loop.1926class FoldWithProducerReshapeOpByCollapsing1927    : public OpRewritePattern<GenericOp> {1928public:1929  // TODO : support fusion with all linalg ops, not just generic.1930  FoldWithProducerReshapeOpByCollapsing(MLIRContext *context,1931                                        ControlFusionFn foldReshapes,1932                                        PatternBenefit benefit = 1)1933      : OpRewritePattern<GenericOp>(context, benefit),1934        controlFoldingReshapes(std::move(foldReshapes)) {}1935 1936  LogicalResult matchAndRewrite(GenericOp genericOp,1937                                PatternRewriter &rewriter) const override {1938    for (OpOperand &opOperand : genericOp->getOpOperands()) {1939      tensor::ExpandShapeOp reshapeOp =1940          opOperand.get().getDefiningOp<tensor::ExpandShapeOp>();1941      if (!reshapeOp)1942        continue;1943 1944      SmallVector<ReassociationIndices> collapsableIterationDims =1945          getCollapsableIterationSpaceDims(genericOp, &opOperand,1946                                           reshapeOp.getReassociationIndices());1947      if (collapsableIterationDims.empty() ||1948          !controlFoldingReshapes(&opOperand)) {1949        continue;1950      }1951 1952      std::optional<CollapseResult> collapseResult = collapseOpIterationDims(1953          genericOp, collapsableIterationDims, rewriter);1954      if (!collapseResult) {1955        return rewriter.notifyMatchFailure(1956            genericOp, "failed to do the fusion by collapsing transformation");1957      }1958 1959      rewriter.replaceOp(genericOp, collapseResult->results);1960      return success();1961    }1962    return failure();1963  }1964 1965private:1966  ControlFusionFn controlFoldingReshapes;1967};1968 1969/// Pattern to fold a tensor.collapse_shape op with its producer generic op1970/// by expanding the dimensionality of the loop in the producer op.1971struct FoldReshapeWithGenericOpByCollapsing1972    : public OpRewritePattern<tensor::CollapseShapeOp> {1973 1974  FoldReshapeWithGenericOpByCollapsing(MLIRContext *context,1975                                       ControlFusionFn foldReshapes,1976                                       PatternBenefit benefit = 1)1977      : OpRewritePattern<tensor::CollapseShapeOp>(context, benefit),1978        controlFoldingReshapes(std::move(foldReshapes)) {}1979 1980  LogicalResult matchAndRewrite(tensor::CollapseShapeOp reshapeOp,1981                                PatternRewriter &rewriter) const override {1982    // Fold only if all constraints of fusing with reshape by collapsing are1983    // met.1984    auto producerResult = dyn_cast<OpResult>(reshapeOp.getSrc());1985    if (!producerResult) {1986      return rewriter.notifyMatchFailure(reshapeOp,1987                                         "source not produced by an operation");1988    }1989 1990    // TODO : support fusion with all linalg producers, not just generic.1991    auto producer = dyn_cast<GenericOp>(producerResult.getOwner());1992    if (!producer) {1993      return rewriter.notifyMatchFailure(reshapeOp,1994                                         "producer not a generic op");1995    }1996 1997    SmallVector<ReassociationIndices> collapsableIterationDims =1998        getCollapsableIterationSpaceDims(1999            producer,2000            producer.getDpsInitOperand(producerResult.getResultNumber()),2001            reshapeOp.getReassociationIndices());2002    if (collapsableIterationDims.empty()) {2003      return rewriter.notifyMatchFailure(2004          reshapeOp, "failed preconditions of fusion with producer generic op");2005    }2006 2007    if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {2008      return rewriter.notifyMatchFailure(reshapeOp,2009                                         "fusion blocked by control function");2010    }2011 2012    // Set the insertion point after `producer` because there could be uses2013    // of `producer` between it and the `tensor.collapse_shape` op.2014    rewriter.setInsertionPointAfter(producer);2015    std::optional<CollapseResult> collapseResult =2016        collapseOpIterationDims(producer, collapsableIterationDims, rewriter);2017    if (!collapseResult) {2018      return rewriter.notifyMatchFailure(2019          producer, "failed to do the fusion by collapsing transformation");2020    }2021 2022    rewriter.replaceOp(producer, collapseResult->results);2023    return success();2024  }2025 2026private:2027  ControlFusionFn controlFoldingReshapes;2028};2029 2030/// Computes the collapsed padding information for the given pad operation based2031/// on the provided collapsed shape and reassociation indices. Returns a2032/// PadDimInfo containing the low and high padding amounts and the collapsed2033/// shape for each dimension, or failure if the collapse is not possible.2034static FailureOr<PadDimInfo>2035computeCollapsedPadding(tensor::PadOp padOp,2036                        ArrayRef<ReassociationIndices> reassociations,2037                        PatternRewriter &rewriter) {2038  // If the padding value depends on the index values of the pad operation,2039  // then it may not be valid to collapse the dimensions, since it will change2040  // the index values on which the padding value depends. This is not currently2041  // supported by the pad collapsing patterns, but it could be implemented2042  // similarly to the collapsing of linalg.generic ops with linalg.index ops in2043  // the body, as is done in `generateCollapsedIndexingRegion`.2044  if (!padOp.getConstantPaddingValue())2045    return failure();2046 2047  // Collapsed dimensions cannot have padding because this can produce strided2048  // padding that isn't representable by a tensor.pad op. There are some special2049  // cases where it is possible (like collapsing unit dims), but supporting2050  // these cases is NYI, so disallow it for now.2051  ArrayRef<int64_t> low = padOp.getStaticLow();2052  ArrayRef<int64_t> high = padOp.getStaticHigh();2053  for (auto [idx, reInd] : llvm::enumerate(reassociations)) {2054    for (int64_t dim : reInd) {2055      if ((low[dim] != 0 || high[dim] != 0) && reInd.size() != 1)2056        return failure();2057    }2058  }2059 2060  // Initialize padding values for collapsed tensors with zeros2061  ArrayRef<int64_t> expandedPaddedShape = padOp.getType().getShape();2062  PadDimInfo padDimInfo;2063  padDimInfo.lowPad.assign(reassociations.size(), rewriter.getIndexAttr(0));2064  padDimInfo.highPad.assign(reassociations.size(), rewriter.getIndexAttr(0));2065 2066  // Update padding for dimensions that are not being collapsed, and compute2067  // the collapsed padded shape.2068  SmallVector<OpFoldResult> mixedLowPad(padOp.getMixedLowPad());2069  SmallVector<OpFoldResult> mixedHighPad(padOp.getMixedHighPad());2070  for (auto [idx, reInd] : llvm::enumerate(reassociations)) {2071    if (reInd.size() == 1) {2072      padDimInfo.lowPad[idx] = mixedLowPad[reInd[0]];2073      padDimInfo.highPad[idx] = mixedHighPad[reInd[0]];2074    }2075    SaturatedInteger collapsedSize = SaturatedInteger::wrap(1);2076    for (int64_t dim : reInd) {2077      collapsedSize =2078          collapsedSize * SaturatedInteger::wrap(expandedPaddedShape[dim]);2079    }2080    padDimInfo.paddedShape.push_back(collapsedSize.asInteger());2081  }2082 2083  return padDimInfo;2084}2085 2086class FoldPadWithProducerReshapeOpByCollapsing2087    : public OpRewritePattern<tensor::PadOp> {2088public:2089  FoldPadWithProducerReshapeOpByCollapsing(MLIRContext *context,2090                                           ControlFusionFn foldReshapes,2091                                           PatternBenefit benefit = 1)2092      : OpRewritePattern<tensor::PadOp>(context, benefit),2093        controlFoldingReshapes(std::move(foldReshapes)) {}2094 2095  LogicalResult matchAndRewrite(tensor::PadOp padOp,2096                                PatternRewriter &rewriter) const override {2097    tensor::ExpandShapeOp reshapeOp =2098        padOp.getSource().getDefiningOp<tensor::ExpandShapeOp>();2099    if (!reshapeOp)2100      return failure();2101 2102    if (!controlFoldingReshapes(&padOp.getSourceMutable())) {2103      return rewriter.notifyMatchFailure(padOp,2104                                         "fusion blocked by control function");2105    }2106 2107    SmallVector<ReassociationIndices> reassociations =2108        reshapeOp.getReassociationIndices();2109    FailureOr<PadDimInfo> maybeCollapsedPadding =2110        computeCollapsedPadding(padOp, reassociations, rewriter);2111    if (failed(maybeCollapsedPadding))2112      return failure();2113    PadDimInfo &collapsedPadding = maybeCollapsedPadding.value();2114 2115    SmallVector<OpFoldResult> expandedPaddedSizes =2116        reshapeOp.getMixedOutputShape();2117    AffineExpr d0, d1, d2;2118    bindDims(rewriter.getContext(), d0, d1, d2);2119    auto addMap = AffineMap::get(3, 0, {d0 + d1 + d2});2120    Location loc = reshapeOp->getLoc();2121    for (auto [reInd, l, h] :2122         llvm::zip_equal(reassociations, collapsedPadding.lowPad,2123                         collapsedPadding.highPad)) {2124      if (reInd.size() == 1) {2125        expandedPaddedSizes[reInd[0]] = affine::makeComposedFoldedAffineApply(2126            rewriter, loc, addMap, {l, h, expandedPaddedSizes[reInd[0]]});2127      }2128    }2129 2130    RankedTensorType collapsedPaddedType =2131        padOp.getType().clone(collapsedPadding.paddedShape);2132    auto newPadOp = tensor::PadOp::create(2133        rewriter, loc, collapsedPaddedType, reshapeOp.getSrc(),2134        collapsedPadding.lowPad, collapsedPadding.highPad,2135        padOp.getConstantPaddingValue(), padOp.getNofold());2136 2137    rewriter.replaceOpWithNewOp<tensor::ExpandShapeOp>(2138        padOp, padOp.getResultType(), newPadOp.getResult(), reassociations,2139        expandedPaddedSizes);2140 2141    return success();2142  }2143 2144private:2145  ControlFusionFn controlFoldingReshapes;2146};2147 2148class FoldReshapeWithProducerPadOpByCollapsing2149    : public OpRewritePattern<tensor::CollapseShapeOp> {2150public:2151  FoldReshapeWithProducerPadOpByCollapsing(MLIRContext *context,2152                                           ControlFusionFn foldReshapes,2153                                           PatternBenefit benefit = 1)2154      : OpRewritePattern<tensor::CollapseShapeOp>(context, benefit),2155        controlFoldingReshapes(std::move(foldReshapes)) {}2156 2157  LogicalResult matchAndRewrite(tensor::CollapseShapeOp reshapeOp,2158                                PatternRewriter &rewriter) const override {2159    tensor::PadOp padOp = reshapeOp.getSrc().getDefiningOp<tensor::PadOp>();2160    if (!padOp)2161      return failure();2162 2163    if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {2164      return rewriter.notifyMatchFailure(padOp,2165                                         "fusion blocked by control function");2166    }2167 2168    SmallVector<ReassociationIndices> reassociations =2169        reshapeOp.getReassociationIndices();2170    RankedTensorType collapsedPaddedType = reshapeOp.getResultType();2171    FailureOr<PadDimInfo> maybeCollapsedPadding =2172        computeCollapsedPadding(padOp, reassociations, rewriter);2173    if (failed(maybeCollapsedPadding))2174      return failure();2175    PadDimInfo &collapsedPadding = maybeCollapsedPadding.value();2176 2177    Location loc = reshapeOp->getLoc();2178    auto newCollapseOp = tensor::CollapseShapeOp::create(2179        rewriter, loc, padOp.getSource(), reassociations);2180 2181    auto newPadOp = tensor::PadOp::create(2182        rewriter, loc, collapsedPaddedType, newCollapseOp.getResult(),2183        collapsedPadding.lowPad, collapsedPadding.highPad,2184        padOp.getConstantPaddingValue(), padOp.getNofold());2185 2186    rewriter.replaceOp(reshapeOp, newPadOp.getResult());2187    return success();2188  }2189 2190private:2191  ControlFusionFn controlFoldingReshapes;2192};2193 2194/// Pattern to collapse dimensions.2195template <typename LinalgType>2196class CollapseLinalgDimensions : public OpRewritePattern<LinalgType> {2197public:2198  CollapseLinalgDimensions(MLIRContext *context,2199                           GetCollapsableDimensionsFn collapseDimensions,2200                           PatternBenefit benefit = 1)2201      : OpRewritePattern<LinalgType>(context, benefit),2202        controlCollapseDimension(std::move(collapseDimensions)) {}2203 2204  LogicalResult matchAndRewrite(LinalgType op,2205                                PatternRewriter &rewriter) const override {2206    SmallVector<ReassociationIndices> collapsableIterationDims =2207        controlCollapseDimension(op);2208    if (collapsableIterationDims.empty())2209      return failure();2210 2211    // Check if the specified list of dimensions to collapse is a valid list.2212    if (!areDimSequencesPreserved(op.getIndexingMapsArray(),2213                                  collapsableIterationDims)) {2214      return rewriter.notifyMatchFailure(2215          op, "specified dimensions cannot be collapsed");2216    }2217 2218    std::optional<CollapseResult> collapseResult =2219        collapseOpIterationDims(op, collapsableIterationDims, rewriter);2220    if (!collapseResult) {2221      return rewriter.notifyMatchFailure(op, "failed to collapse dimensions");2222    }2223    rewriter.replaceOp(op, collapseResult->results);2224    return success();2225  }2226 2227private:2228  GetCollapsableDimensionsFn controlCollapseDimension;2229};2230 2231} // namespace2232 2233//===---------------------------------------------------------------------===//2234// Methods and patterns that fuse constants with linalg.generic operations.2235//===---------------------------------------------------------------------===//2236 2237namespace {2238/// Pattern to fold a generic op with a splat constant/scalar constant. Does not2239/// handle cases where the constant is not single-valued.2240class FoldScalarOrSplatConstant : public OpRewritePattern<GenericOp> {2241public:2242  FoldScalarOrSplatConstant(MLIRContext *context, PatternBenefit benefit = 1)2243      : OpRewritePattern<GenericOp>(context, benefit) {}2244 2245  LogicalResult matchAndRewrite(GenericOp genericOp,2246                                PatternRewriter &rewriter) const override {2247    if (!genericOp.hasPureTensorSemantics())2248      return failure();2249    for (OpOperand *opOperand : genericOp.getDpsInputOperands()) {2250      Operation *def = opOperand->get().getDefiningOp();2251      TypedAttr constantAttr;2252      auto isScalarOrSplatConstantOp = [&constantAttr](Operation *def) -> bool {2253        {2254          DenseElementsAttr splatAttr;2255          if (matchPattern(def, m_Constant<DenseElementsAttr>(&splatAttr)) &&2256              splatAttr.isSplat() &&2257              splatAttr.getType().getElementType().isIntOrFloat()) {2258            constantAttr = splatAttr.getSplatValue<TypedAttr>();2259            return true;2260          }2261        }2262        {2263          IntegerAttr intAttr;2264          if (matchPattern(def, m_Constant<IntegerAttr>(&intAttr))) {2265            constantAttr = intAttr;2266            return true;2267          }2268        }2269        {2270          FloatAttr floatAttr;2271          if (matchPattern(def, m_Constant<FloatAttr>(&floatAttr))) {2272            constantAttr = floatAttr;2273            return true;2274          }2275        }2276        return false;2277      };2278 2279      auto resultValue = dyn_cast<OpResult>(opOperand->get());2280      if (!def || !resultValue || !isScalarOrSplatConstantOp(def))2281        continue;2282 2283      // The operands and the indexing_maps of the fused operation the same as2284      // the operands and indexing_maps of the generic operations with the2285      // values at the constant index dropped.2286      SmallVector<AffineMap> fusedIndexMaps;2287      SmallVector<Value> fusedOperands;2288      SmallVector<Location> fusedLocs{genericOp.getLoc()};2289      fusedIndexMaps.reserve(genericOp->getNumOperands());2290      fusedOperands.reserve(genericOp.getNumDpsInputs());2291      fusedLocs.reserve(fusedLocs.size() + genericOp.getNumDpsInputs());2292      for (OpOperand *inputOperand : genericOp.getDpsInputOperands()) {2293        if (inputOperand == opOperand)2294          continue;2295        Value inputValue = inputOperand->get();2296        fusedIndexMaps.push_back(2297            genericOp.getMatchingIndexingMap(inputOperand));2298        fusedOperands.push_back(inputValue);2299        fusedLocs.push_back(inputValue.getLoc());2300      }2301      for (OpOperand &outputOperand : genericOp.getDpsInitsMutable())2302        fusedIndexMaps.push_back(2303            genericOp.getMatchingIndexingMap(&outputOperand));2304 2305      // Check if the operation shapes to loops map is computable.2306      if (!inversePermutation(2307              concatAffineMaps(fusedIndexMaps, rewriter.getContext()))) {2308        return rewriter.notifyMatchFailure(2309            genericOp, "fused op loop bound computation failed");2310      }2311 2312      // Create a constant scalar value from the splat constant.2313      Value scalarConstant =2314          arith::ConstantOp::create(rewriter, def->getLoc(), constantAttr);2315 2316      SmallVector<Value> outputOperands = genericOp.getOutputs();2317      auto fusedOp =2318          GenericOp::create(rewriter, rewriter.getFusedLoc(fusedLocs),2319                            genericOp->getResultTypes(),2320                            /*inputs=*/fusedOperands,2321                            /*outputs=*/outputOperands,2322                            rewriter.getAffineMapArrayAttr(fusedIndexMaps),2323                            genericOp.getIteratorTypes(),2324                            /*doc=*/nullptr,2325                            /*library_call=*/nullptr);2326 2327      // Map the block argument corresponding to the replaced argument with the2328      // scalar constant.2329      Region &region = genericOp->getRegion(0);2330      Block &entryBlock = *region.begin();2331      IRMapping mapping;2332      mapping.map(entryBlock.getArgument(opOperand->getOperandNumber()),2333                  scalarConstant);2334      Region &fusedRegion = fusedOp->getRegion(0);2335      rewriter.cloneRegionBefore(region, fusedRegion, fusedRegion.begin(),2336                                 mapping);2337      rewriter.replaceOp(genericOp, fusedOp->getResults());2338      return success();2339    }2340    return failure();2341  }2342};2343 2344} // namespace2345 2346//===---------------------------------------------------------------------===//2347// Miscellaneous patterns that help fusion.2348//===---------------------------------------------------------------------===//2349 2350namespace {2351/// Forces `outs` operands of linalg operations to use `tensor.empty` if the2352/// value of the `outs` operand is not used within the op.  This is only2353/// implemented for `linalg.generic` operations for now, but should hold for all2354/// linalg structured ops.2355struct RemoveOutsDependency : public OpRewritePattern<GenericOp> {2356  using OpRewritePattern<GenericOp>::OpRewritePattern;2357 2358  LogicalResult matchAndRewrite(GenericOp op,2359                                PatternRewriter &rewriter) const override {2360    rewriter.startOpModification(op);2361    bool modifiedOutput = false;2362    Location loc = op.getLoc();2363    for (OpOperand &opOperand : op.getDpsInitsMutable()) {2364      if (!op.payloadUsesValueFromOperand(&opOperand)) {2365        Value operandVal = opOperand.get();2366        auto operandType = dyn_cast<RankedTensorType>(operandVal.getType());2367        if (!operandType)2368          continue;2369 2370        // If outs is sparse, leave it to the sparsifier.2371        if (sparse_tensor::getSparseTensorEncoding(operandVal.getType()))2372          continue;2373 2374        // If outs is already an `empty` operation, nothing to do.2375        auto definingOp = operandVal.getDefiningOp<tensor::EmptyOp>();2376        if (definingOp)2377          continue;2378        modifiedOutput = true;2379        SmallVector<OpFoldResult> mixedSizes =2380            tensor::getMixedSizes(rewriter, loc, operandVal);2381        Value emptyTensor = tensor::EmptyOp::create(2382            rewriter, loc, mixedSizes, operandType.getElementType());2383        op->setOperand(opOperand.getOperandNumber(), emptyTensor);2384      }2385    }2386    if (!modifiedOutput) {2387      rewriter.cancelOpModification(op);2388      return failure();2389    }2390    rewriter.finalizeOpModification(op);2391    return success();2392  }2393};2394 2395/// Fold linalg.fill into linalg.generic2396struct FoldFillWithGenericOp : public OpRewritePattern<GenericOp> {2397  using OpRewritePattern<GenericOp>::OpRewritePattern;2398 2399  LogicalResult matchAndRewrite(GenericOp genericOp,2400                                PatternRewriter &rewriter) const override {2401    if (!genericOp.hasPureTensorSemantics())2402      return failure();2403    bool fillFound = false;2404    Block &payload = genericOp.getRegion().front();2405    for (OpOperand *opOperand : genericOp.getDpsInputOperands()) {2406      if (!genericOp.payloadUsesValueFromOperand(opOperand))2407        continue;2408      FillOp fillOp = opOperand->get().getDefiningOp<FillOp>();2409      if (!fillOp)2410        continue;2411      fillFound = true;2412      Value fillVal = fillOp.value();2413      auto resultType =2414          cast<RankedTensorType>(fillOp.result().getType()).getElementType();2415      Value convertedVal =2416          convertScalarToDtype(rewriter, fillOp.getLoc(), fillVal, resultType,2417                               /*isUnsignedCast =*/false);2418      rewriter.replaceAllUsesWith(2419          payload.getArgument(opOperand->getOperandNumber()), convertedVal);2420    }2421    return success(fillFound);2422  }2423};2424} // namespace2425 2426void mlir::linalg::populateFoldReshapeOpsByExpansionPatterns(2427    RewritePatternSet &patterns,2428    const ControlFusionFn &controlFoldingReshapes) {2429  patterns.add<FoldReshapeWithGenericOpByExpansion>(patterns.getContext(),2430                                                    controlFoldingReshapes);2431  patterns.add<FoldPadWithProducerReshapeOpByExpansion>(patterns.getContext(),2432                                                        controlFoldingReshapes);2433  patterns.add<FoldReshapeWithProducerPadOpByExpansion>(patterns.getContext(),2434                                                        controlFoldingReshapes);2435  patterns.add<FoldWithProducerReshapeOpByExpansion>(patterns.getContext(),2436                                                     controlFoldingReshapes);2437}2438 2439void mlir::linalg::populateFoldReshapeOpsByCollapsingPatterns(2440    RewritePatternSet &patterns,2441    const ControlFusionFn &controlFoldingReshapes) {2442  patterns.add<FoldWithProducerReshapeOpByCollapsing>(patterns.getContext(),2443                                                      controlFoldingReshapes);2444  patterns.add<FoldPadWithProducerReshapeOpByCollapsing>(2445      patterns.getContext(), controlFoldingReshapes);2446  patterns.add<FoldReshapeWithProducerPadOpByCollapsing>(2447      patterns.getContext(), controlFoldingReshapes);2448  patterns.add<FoldReshapeWithGenericOpByCollapsing>(patterns.getContext(),2449                                                     controlFoldingReshapes);2450}2451 2452void mlir::linalg::populateElementwiseOpsFusionPatterns(2453    RewritePatternSet &patterns,2454    const ControlFusionFn &controlElementwiseOpsFusion) {2455  auto *context = patterns.getContext();2456  patterns.add<FuseElementwiseOps>(context, controlElementwiseOpsFusion);2457  patterns.add<FoldFillWithGenericOp, FoldScalarOrSplatConstant,2458               RemoveOutsDependency>(context);2459  // Add the patterns that clean up dead operands and results.2460  populateEraseUnusedOperandsAndResultsPatterns(patterns);2461}2462 2463void mlir::linalg::populateCollapseDimensions(2464    RewritePatternSet &patterns,2465    const GetCollapsableDimensionsFn &controlCollapseDimensions) {2466  patterns.add<CollapseLinalgDimensions<linalg::GenericOp>,2467               CollapseLinalgDimensions<linalg::CopyOp>>(2468      patterns.getContext(), controlCollapseDimensions);2469}2470 2471//===---------------------------------------------------------------------===//2472// Passes2473//===---------------------------------------------------------------------===//2474 2475namespace {2476 2477/// Pass that fuses generic ops on tensors. Used only for testing.2478// TODO(ravishankarm): This pass is to be deprecated. The efficacy of the2479// patterns added here heavily depends on the cost function used. Having an2480// opinionated pass of this form is not recommended. Deprecate this pass in2481// favor of test passes that check the functionality of each of the patterns2482// added here individually.2483struct LinalgElementwiseOpFusionPass2484    : public impl::LinalgElementwiseOpFusionPassBase<2485          LinalgElementwiseOpFusionPass> {2486  using impl::LinalgElementwiseOpFusionPassBase<2487      LinalgElementwiseOpFusionPass>::LinalgElementwiseOpFusionPassBase;2488  void runOnOperation() override {2489    Operation *op = getOperation();2490    MLIRContext *context = op->getContext();2491    RewritePatternSet patterns(context);2492 2493    // Add folding with reshape by expansion patterns.2494    ControlFusionFn defaultControlFn = [](OpOperand *fusedOperand) {2495      Operation *producer = fusedOperand->get().getDefiningOp();2496      return producer && producer->hasOneUse();2497    };2498 2499    // Add elementwise op fusion patterns.2500    populateElementwiseOpsFusionPatterns(patterns, defaultControlFn);2501    populateFoldReshapeOpsByExpansionPatterns(patterns, defaultControlFn);2502    tensor::populateBubbleUpExpandShapePatterns(patterns);2503 2504    // General canonicalization patterns.2505    affine::AffineApplyOp::getCanonicalizationPatterns(patterns, context);2506    GenericOp::getCanonicalizationPatterns(patterns, context);2507    tensor::ExpandShapeOp::getCanonicalizationPatterns(patterns, context);2508    tensor::CollapseShapeOp::getCanonicalizationPatterns(patterns, context);2509    context->getLoadedDialect<LinalgDialect>()->getCanonicalizationPatterns(2510        patterns);2511 2512    // Add constant folding patterns.2513    populateConstantFoldLinalgOperations(patterns, defaultControlFn);2514 2515    // Use TopDownTraversal for compile time reasons.2516    (void)applyPatternsGreedily(op, std::move(patterns),2517                                GreedyRewriteConfig().setUseTopDownTraversal());2518  }2519};2520 2521} // namespace2522