MLIR 24.0.0git
ElementwiseOpFusion.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the linalg dialect Fusion on tensors operations pass.
10//
11//===----------------------------------------------------------------------===//
12
14
23#include "mlir/IR/AffineExpr.h"
24#include "mlir/IR/AffineMap.h"
25#include "mlir/IR/Matchers.h"
27#include "mlir/Support/LLVM.h"
30#include "llvm/ADT/SmallVectorExtras.h"
31#include <optional>
32#include <utility>
33
34namespace mlir {
35#define GEN_PASS_DEF_LINALGELEMENTWISEOPFUSIONPASS
36#include "mlir/Dialect/Linalg/Passes.h.inc"
37} // namespace mlir
38
39using namespace mlir;
40using namespace mlir::linalg;
41
42//===---------------------------------------------------------------------===//
43// Methods and patterns that fuse elementwise `linalg.generic` operations.
44//===---------------------------------------------------------------------===//
45
46/// Append to `fusedOpIndexingMapAttrs` the indexing maps for the operands of
47/// the `producer` to use in the fused operation given the indexing map of the
48/// result of the producer in the consumer.
50 OpOperand *producerOpOperand, AffineMap producerResultIndexMap,
51 AffineMap fusedConsumerArgIndexMap) {
52 // The indexing map in the consumer op (fusedConsumerArgIndexMap) is a map
53 // from consumer loop -> consumer arg tensor index/producer result tensor
54 // index. The fused loop is same as the consumer loop. For each producer arg
55 // the indexing map to be computed is a map from consumer loop -> producer
56 // arg tensor index.
57 // producerResultIndexMap is a map from producer loop -> tensor index.
58 // Compute the inverse to get map from tensor index -> producer loop.
59 // The inverse is a map from producer result tensor index -> producer loop.
60 AffineMap invProducerResultIndexMap =
61 inversePermutation(producerResultIndexMap);
62 assert(invProducerResultIndexMap &&
63 "expected producer result indexing map to be invertible");
64
65 LinalgOp producer = cast<LinalgOp>(producerOpOperand->getOwner());
66 // argMap is a map from producer loop -> producer arg tensor index.
67 AffineMap argMap = producer.getMatchingIndexingMap(producerOpOperand);
68
69 // Compose argMap with invProducerResultIndexMap to get a map from
70 // producer result tensor index -> producer arg tensor index.
71 AffineMap t1 = argMap.compose(invProducerResultIndexMap);
72
73 // Compose t1 with fusedConsumerArgIndexMap gives an indexing map from
74 // consumer loop/ fused loop -> producer arg tensor index.
75 return t1.compose(fusedConsumerArgIndexMap);
76}
77
78// Checks if the given operand can be dropped, and the remaining operands
79// of the fused producer & consumer after the fusion can still compute the
80// bounds of the op.
82 GenericOp producer, GenericOp consumer,
83 ArrayRef<OpOperand *> opOperandsToIgnore) {
84 SmallVector<AffineMap> indexingMaps;
85
86 SmallVector<GenericOp> ops = {producer, consumer};
87 for (auto &op : ops) {
88 for (auto &opOperand : op->getOpOperands()) {
89 if (llvm::is_contained(opOperandsToIgnore, &opOperand)) {
90 continue;
91 }
92 indexingMaps.push_back(op.getMatchingIndexingMap(&opOperand));
93 }
94 }
95 if (indexingMaps.empty()) {
96 // If there are no indexing maps, the operand can only be dropped
97 // if neither op has loops.
98 return producer.getNumLoops() == 0 && consumer.getNumLoops() == 0;
99 }
100
101 // The concatanation of the remained indexing maps must be invertible, so
102 // the bounds of the op can be still computed after dropping the selected
103 // operand. inversePermutation returns an empty AffineMap in case the
104 // concatanated indexing maps are not invertible.
106 indexingMaps, producer.getContext())) != AffineMap();
107}
108
109/// Returns a set of indices of the producer's results which would
110/// be preserved after the fusion.
111/// * There is a chance that the implementation of the transformation does not
112/// agree with the result of this method. This function gives a prediction based
113/// on an optimized fusion.
115 GenericOp producer, GenericOp consumer, OpOperand *fusedOperand) {
116 llvm::SmallDenseSet<int> preservedProducerResults;
117 llvm::SmallVector<OpOperand *> opOperandsToIgnore;
118
119 // The fusedOperand will be removed during the fusion
120 opOperandsToIgnore.emplace_back(fusedOperand);
121
122 for (const auto &producerResult : llvm::enumerate(producer->getResults())) {
123 auto *outputOperand = producer.getDpsInitOperand(producerResult.index());
124 opOperandsToIgnore.emplace_back(outputOperand);
125 if (producer.payloadUsesValueFromOperand(outputOperand) ||
127 opOperandsToIgnore) ||
128 llvm::any_of(producerResult.value().getUsers(), [&](Operation *user) {
129 return user != consumer.getOperation();
130 })) {
131 preservedProducerResults.insert(producerResult.index());
132
133 // In case the operand can't be dropped
134 (void)opOperandsToIgnore.pop_back_val();
135 }
136 }
137 return preservedProducerResults;
138}
139
140/// Conditions for elementwise fusion of generic operations.
142 if (!fusedOperand)
143 return false;
144
145 auto producer = fusedOperand->get().getDefiningOp<GenericOp>();
146 auto consumer = dyn_cast<GenericOp>(fusedOperand->getOwner());
147
148 // Check producer and consumer are generic ops.
149 if (!producer || !consumer)
150 return false;
151
152 // Consumer can have mixed semantics, just check operand itself has tensor
153 // type. Producer must have full tensor semantics to avoid potential
154 // aliasing between producer and consumer memrefs.
155 if (!producer.hasPureTensorSemantics() ||
156 !isa<RankedTensorType>(fusedOperand->get().getType()))
157 return false;
158
159 // Verify that
160 // - the producer has all "parallel" iterator type.
161 if (producer.getNumParallelLoops() != producer.getNumLoops())
162 return false;
163
164 // Only allow fusing the producer of an input operand for now.
165 // TODO: allow fusing the producer of an output operand.
166 if (!consumer.isDpsInput(fusedOperand))
167 return false;
168
169 // Get the consumer index map. The number of results of the consumer index
170 // map must match the number of loops of the producer.
171 AffineMap consumerIndexMap = consumer.getMatchingIndexingMap(fusedOperand);
172 if (consumerIndexMap.getNumResults() != producer.getNumLoops())
173 return false;
174
175 // Finally the index_map for the result must be invertible. For now just
176 // verify it is a permutation.
177 auto producerResult = cast<OpResult>(fusedOperand->get());
178 AffineMap producerResultIndexMap =
179 producer.getIndexingMapMatchingResult(producerResult);
180 if (!producerResultIndexMap.isPermutation())
181 return false;
182
183 // Ensure that the fusion does not remove size information required to
184 // get the loop bounds. For non-reduction generics, this is trivially the
185 // case due to the output operand. For reductions, we need to check that after
186 // the fusion, each loop dimension has at least one input that defines it.
187 if ((consumer.getNumReductionLoops())) {
188 BitVector coveredDims(consumer.getNumLoops(), false);
189
190 auto addToCoveredDims = [&](AffineMap map) {
191 for (auto result : map.getResults())
192 if (auto dimExpr = dyn_cast<AffineDimExpr>(result))
193 coveredDims[dimExpr.getPosition()] = true;
194 };
195
196 for (auto pair :
197 llvm::zip(consumer->getOperands(), consumer.getIndexingMapsArray())) {
198 Value operand = std::get<0>(pair);
199 if (operand == fusedOperand->get())
200 continue;
201 AffineMap operandMap = std::get<1>(pair);
202 addToCoveredDims(operandMap);
203 }
204
205 for (OpOperand *operand : producer.getDpsInputOperands()) {
206 AffineMap newIndexingMap =
208 operand, producerResultIndexMap, consumerIndexMap);
209 addToCoveredDims(newIndexingMap);
210 }
211 if (!coveredDims.all())
212 return false;
213 }
214
215 return true;
216}
217
218/// Generate the region of the fused tensor operation. The region of the fused
219/// op must be empty.
221 RewriterBase &rewriter, GenericOp fusedOp,
222 AffineMap consumerToProducerLoopsMap, OpOperand *fusedOperand,
223 unsigned nloops, llvm::SmallDenseSet<int> &preservedProducerResults) {
224 auto producer = cast<GenericOp>(fusedOperand->get().getDefiningOp());
225 auto consumer = cast<GenericOp>(fusedOperand->getOwner());
226 // Build the region of the fused op.
227 Block &producerBlock = producer->getRegion(0).front();
228 Block &consumerBlock = consumer->getRegion(0).front();
229 OpBuilder::InsertionGuard guard(rewriter);
230 Block *fusedBlock = rewriter.createBlock(&fusedOp.getRegion());
231 IRMapping mapper;
232
233 // 2. Add an index operation for every fused loop dimension and use the
234 // `consumerToProducerLoopsMap` to map the producer indices.
235 if (producer.hasIndexSemantics()) {
236 // Add an index operation for every fused loop dimension.
237 unsigned numFusedOpLoops = fusedOp.getNumLoops();
238 SmallVector<Value> fusedIndices;
239 fusedIndices.reserve(numFusedOpLoops);
240 llvm::transform(llvm::seq<uint64_t>(0, numFusedOpLoops),
241 std::back_inserter(fusedIndices), [&](uint64_t dim) {
242 return IndexOp::create(rewriter, producer.getLoc(), dim);
243 });
244 for (IndexOp indexOp :
245 llvm::make_early_inc_range(producerBlock.getOps<IndexOp>())) {
246 Value newIndex = affine::AffineApplyOp::create(
247 rewriter, producer.getLoc(),
248 consumerToProducerLoopsMap.getSubMap(indexOp.getDim()), fusedIndices);
249 mapper.map(indexOp.getResult(), newIndex);
250 }
251 }
252 // TODO: allow fusing the producer of an output operand.
253 assert(consumer.isDpsInput(fusedOperand) &&
254 "expected producer of input operand");
255 // 3. Consumer input operands up to consumerIdx (exclusive).
256 for (BlockArgument bbArg : consumerBlock.getArguments().take_front(
257 fusedOperand->getOperandNumber())) // input assumption.
258 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
259
260 // Replacing consumerIdx requires getting the cloned, yielded, value from
261 // the (cloned) producer block. This happens in step 9.
262
263 // 4. Splice in producer's input operands.
264 for (BlockArgument bbArg :
265 producerBlock.getArguments().take_front(producer.getNumDpsInputs()))
266 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
267
268 // 5. Remaining consumer's input operands (drop past index `consumerIdx`).
269 for (BlockArgument bbArg :
270 consumerBlock.getArguments()
271 .take_front(consumer.getNumDpsInputs())
272 .drop_front(fusedOperand->getOperandNumber() + 1))
273 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
274
275 // 6. All of the producer's output operands
276 for (const auto &bbArg : llvm::enumerate(
277 producerBlock.getArguments().take_back(producer.getNumDpsInits()))) {
278 if (!preservedProducerResults.count(bbArg.index()))
279 continue;
280 mapper.map(bbArg.value(), fusedBlock->addArgument(bbArg.value().getType(),
281 bbArg.value().getLoc()));
282 }
283
284 // 7. All of consumer's output operands.
285 for (BlockArgument bbArg :
286 consumerBlock.getArguments().take_back(consumer.getNumDpsInits()))
287 mapper.map(bbArg, fusedBlock->addArgument(bbArg.getType(), bbArg.getLoc()));
288
289 // 8. Clone all producer operations except for the yield and index operations
290 // to the fused operation.
291 for (auto &op : producerBlock.without_terminator()) {
292 if (!isa<IndexOp>(op))
293 rewriter.clone(op, mapper);
294 }
295 // 9. Now we can map the consumerBlock's `consumerIdx` block argument. Just
296 // forward the yield operand.
297 auto producerYieldOp = cast<linalg::YieldOp>(producerBlock.getTerminator());
298 unsigned producerResultNumber =
299 cast<OpResult>(fusedOperand->get()).getResultNumber();
301 mapper.lookupOrDefault(producerYieldOp.getOperand(producerResultNumber));
302
303 // Sanity checks, if replacement is not already in the mapper then it must be
304 // produced outside.
305 if (replacement == producerYieldOp.getOperand(producerResultNumber)) {
306 if (auto bb = dyn_cast<BlockArgument>(replacement))
307 assert(bb.getOwner() != &producerBlock &&
308 "yielded block argument must have been mapped");
309 else
310 assert(!producer->isAncestor(replacement.getDefiningOp()) &&
311 "yielded value must have been mapped");
312 }
313 mapper.map(consumerBlock.getArgument(fusedOperand->getOperandNumber()),
315 // 10. Clone operations from the consumer to the fused op.
316 for (auto &op : consumerBlock.without_terminator())
317 rewriter.clone(op, mapper);
318
319 // 11. Include the final yield (which is the remapped values for all the
320 // yield)
321 auto consumerYieldOp = cast<linalg::YieldOp>(consumerBlock.getTerminator());
322 SmallVector<Value> fusedYieldValues;
323 fusedYieldValues.reserve(producerYieldOp.getNumOperands() +
324 consumerYieldOp.getNumOperands());
325 for (const auto &producerYieldVal :
326 llvm::enumerate(producerYieldOp.getOperands())) {
327 if (preservedProducerResults.count(producerYieldVal.index()))
328 fusedYieldValues.push_back(
329 mapper.lookupOrDefault(producerYieldVal.value()));
330 }
331 for (auto consumerYieldVal : consumerYieldOp.getOperands())
332 fusedYieldValues.push_back(mapper.lookupOrDefault(consumerYieldVal));
333 YieldOp::create(rewriter, fusedOp.getLoc(), fusedYieldValues);
334
335 // Sanity checks.
336 assert(fusedBlock->getNumArguments() == fusedOp.getNumOperands() &&
337 "Ill-formed GenericOp region");
338}
339
340FailureOr<mlir::linalg::ElementwiseOpFusionResult>
342 OpOperand *fusedOperand) {
343 assert(areElementwiseOpsFusable(fusedOperand) &&
344 "expected elementwise operation pre-conditions to pass");
345 auto producerResult = cast<OpResult>(fusedOperand->get());
346 auto producer = cast<GenericOp>(producerResult.getOwner());
347 auto consumer = cast<GenericOp>(fusedOperand->getOwner());
348 // TODO: allow fusing the producer of an output operand.
349 assert(consumer.isDpsInput(fusedOperand) &&
350 "expected producer of input operand");
351 /// Find the results of the producer that have uses outside of the consumer,
352 /// after the fusion.
353 llvm::SmallDenseSet<int> preservedProducerResults =
355 fusedOperand);
356
357 // Compute the fused operands list and indexing maps.
358 SmallVector<Value> fusedInputOperands, fusedOutputOperands;
359 SmallVector<Type> fusedResultTypes;
360 SmallVector<AffineMap> fusedIndexMaps;
361 fusedInputOperands.reserve(producer.getNumDpsInputs() +
362 consumer.getNumDpsInputs());
363 fusedOutputOperands.reserve(preservedProducerResults.size() +
364 consumer.getNumDpsInits());
365 fusedResultTypes.reserve(preservedProducerResults.size() +
366 consumer.getNumDpsInits());
367 fusedIndexMaps.reserve(producer->getNumOperands() +
368 consumer->getNumOperands());
369 // In the following, numbering matches that of `generateFusedTensorOpRegion`.
370 // 3. Consumer input operands/maps up to consumerIdx (exclusive).
371 auto consumerInputs = consumer.getDpsInputOperands();
372 auto *it = llvm::find_if(consumerInputs, [&](OpOperand *operand) {
373 return operand == fusedOperand;
374 });
375 assert(it != consumerInputs.end() && "expected to find the consumer operand");
376 for (OpOperand *opOperand : llvm::make_range(consumerInputs.begin(), it)) {
377 fusedInputOperands.push_back(opOperand->get());
378 fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(opOperand));
379 }
380 // 4. Splice in producer's input operands/maps.
381 AffineMap producerResultIndexMap =
382 producer.getIndexingMapMatchingResult(producerResult);
383 for (OpOperand *opOperand : producer.getDpsInputOperands()) {
384 fusedInputOperands.push_back(opOperand->get());
385 // Compute indexing maps for the producer args in the fused operation.
387 opOperand, producerResultIndexMap,
388 consumer.getMatchingIndexingMap(fusedOperand));
389 fusedIndexMaps.push_back(map);
390 }
391 // 5. Remaining consumer's input operands/maps (drop past index
392 // `consumerIdx`).
393 for (OpOperand *opOperand :
394 llvm::make_range(std::next(it), consumerInputs.end())) {
395 fusedInputOperands.push_back(opOperand->get());
396 fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(opOperand));
397 }
398
399 // 6. Collect all of the producer outputs.
400 for (const auto &opOperand : llvm::enumerate(producer.getDpsInitsMutable())) {
401 if (!preservedProducerResults.count(opOperand.index()))
402 continue;
403
404 fusedOutputOperands.push_back(opOperand.value().get());
406 &opOperand.value(), producerResultIndexMap,
407 consumer.getMatchingIndexingMap(fusedOperand));
408 fusedIndexMaps.push_back(map);
409 fusedResultTypes.push_back(opOperand.value().get().getType());
410 }
411
412 // 7. All of consumer's output operands (skip operands: added by the builder).
413 for (OpOperand &opOperand : consumer.getDpsInitsMutable()) {
414 fusedOutputOperands.push_back(opOperand.get());
415 fusedIndexMaps.push_back(consumer.getMatchingIndexingMap(&opOperand));
416 Type resultType = opOperand.get().getType();
417 if (!isa<MemRefType>(resultType))
418 fusedResultTypes.push_back(resultType);
419 }
420
421 // Generate the fused op.
422 auto fusedOp = GenericOp::create(
423 rewriter, consumer.getLoc(), fusedResultTypes, fusedInputOperands,
424 fusedOutputOperands, rewriter.getAffineMapArrayAttr(fusedIndexMaps),
425 consumer.getIteratorTypes(),
426 /*doc=*/nullptr,
427 /*library_call=*/nullptr);
428 if (!fusedOp.getShapesToLoopsMap()) {
429 // Fused op has invalid indexing maps. Typically this means something is off
430 // in the input, but going ahead here would result in verification errors.
431 // So cleanup and abort.
432 rewriter.eraseOp(fusedOp);
433 return rewriter.notifyMatchFailure(
434 fusedOp, "fused op failed loop bound computation check");
435 }
436
437 // Construct an AffineMap from consumer loops to producer loops.
438 // consumer loop -> tensor index
439 AffineMap consumerResultIndexMap =
440 consumer.getMatchingIndexingMap(fusedOperand);
441 // tensor index -> producer loop
442 AffineMap invProducerResultIndexMap =
443 inversePermutation(producerResultIndexMap);
444 assert(invProducerResultIndexMap &&
445 "expected producer result indexig map to be invertible");
446 // consumer loop -> producer loop
447 AffineMap consumerToProducerLoopsMap =
448 invProducerResultIndexMap.compose(consumerResultIndexMap);
449
451 rewriter, fusedOp, consumerToProducerLoopsMap, fusedOperand,
452 consumer.getNumLoops(), preservedProducerResults);
454 result.fusedOp = fusedOp;
455 int resultNum = 0;
456 for (auto [index, producerResult] : llvm::enumerate(producer->getResults()))
457 if (preservedProducerResults.count(index))
458 result.replacements[producerResult] = fusedOp->getResult(resultNum++);
459 for (auto consumerResult : consumer->getResults())
460 result.replacements[consumerResult] = fusedOp->getResult(resultNum++);
461 return result;
462}
463
464namespace {
465/// Patterns to fuse a generic op, with the producer of its operands.
466class FuseElementwiseOps : public OpRewritePattern<GenericOp> {
467public:
468 FuseElementwiseOps(MLIRContext *context, ControlFusionFn fun,
469 PatternBenefit benefit = 1)
470 : OpRewritePattern<GenericOp>(context, benefit),
471 controlFn(std::move(fun)) {}
472
473 LogicalResult matchAndRewrite(GenericOp genericOp,
474 PatternRewriter &rewriter) const override {
475 // Find the first operand that is defined by another generic op on tensors.
476 for (OpOperand &opOperand : genericOp->getOpOperands()) {
477 if (!areElementwiseOpsFusable(&opOperand))
478 continue;
479 if (!controlFn(&opOperand))
480 continue;
481
482 Operation *producer = opOperand.get().getDefiningOp();
483
484 // Find the producer of the operand.
485 FailureOr<ElementwiseOpFusionResult> fusionResult =
486 fuseElementwiseOps(rewriter, &opOperand);
487 if (failed(fusionResult))
488 return rewriter.notifyMatchFailure(genericOp, "fusion failed");
489
490 // Perform the fusion.
491 for (auto [origVal, replacement] : fusionResult->replacements) {
492 rewriter.replaceUsesWithIf(origVal, replacement, [&](OpOperand &use) {
493 // Only replace consumer uses.
494 return use.get().getDefiningOp() != producer;
495 });
496 }
497 rewriter.eraseOp(genericOp);
498 return success();
499 }
500 return failure();
501 }
502
503private:
504 ControlFusionFn controlFn;
505};
506
507/// Split an elementwise operation at the boundaries of its `tensor.concat`
508/// inputs. This exposes the producers of the concat inputs to the elementwise
509/// fusion patterns.
510///
511/// elementwise(concat(x0, x1), concat(y0, y1))
512///
513/// becomes
514///
515/// concat(elementwise(x0, y0), elementwise(x1, y1))
516///
517/// This pattern is intentionally expressed on `linalg.generic`: tensor
518/// elementwise operations such as `arith.addf` are converted to that form by
519/// `-convert-elementwise-to-linalg`, before this pattern runs as a preamble to
520/// Linalg elementwise fusion.
521///
522/// A partition is one input of a concat, viewed as a contiguous interval of
523/// the concat dimension. All concat inputs must have matching partitions: the
524/// same number of partitions with the same static size at each index.
525///
526/// All concat operands must partition the same iteration-space dimension into
527/// the same statically-sized pieces. Inputs that do not use that iteration
528/// dimension (for example, broadcast inputs) can be shared by all pieces.
529class SplitElementwiseOpWithConcatInputs : public OpRewritePattern<GenericOp> {
530public:
531 using OpRewritePattern<GenericOp>::OpRewritePattern;
532
533 LogicalResult matchAndRewrite(GenericOp genericOp,
534 PatternRewriter &rewriter) const override {
535 if (!genericOp.hasPureTensorSemantics() || !isElementwise(genericOp) ||
536 genericOp.hasIndexSemantics())
537 return failure();
538
539 SmallVector<tensor::ConcatOp> concatOps(genericOp.getNumDpsInputs());
540 SmallVector<OpOperand *> nonConcatInputs;
541 std::optional<unsigned> splitLoopDim;
542 // Now we limit the concat ops to have the same number of inputs for
543 // simplicity.
544 // TODO: technically, elementwise(concat(x0, x1), concat(y0, y1, y2)) ->
545 // concat(elementwise(...), elementwise(...), elementwise(...)) may be
546 // fine too. But that may require we create new slices, which might be
547 // more complex.
548 // The size in the concat dimension of different inputs. For example,
549 //
550 // x0: tensor<2x3xf32>
551 // x1: tensor<2x4xf32>
552 // x: tensor<2x7xf32>
553 // %x = tensor.concat dim(1) %x0, %x1
554 //
555 // The partition sizes in this case are [3, 4]. Same as above, we limit the
556 // partition sizes to be the same for different concat ops.
557 SmallVector<SmallVector<int64_t>> partitionSizes;
558
559 for (auto [index, operand] :
560 llvm::enumerate(genericOp.getDpsInputOperands())) {
561 auto concatOp = operand->get().getDefiningOp<tensor::ConcatOp>();
562 if (!concatOp) {
563 nonConcatInputs.push_back(operand);
564 continue;
565 }
566
567 // Rewriting a concat that has other consumers could increase the amount
568 // of live computation instead of just exposing fusion opportunities.
569 if (!concatOp->hasOneUse())
570 return rewriter.notifyMatchFailure(genericOp,
571 "concat input has another consumer");
572
573 AffineMap inputMap = genericOp.getMatchingIndexingMap(operand);
574 auto concatDimExpr =
575 dyn_cast<AffineDimExpr>(inputMap.getResult(concatOp.getDim()));
576 if (!concatDimExpr)
577 return rewriter.notifyMatchFailure(
578 genericOp, "concat dimension does not map to a loop dimension");
579
580 unsigned currentSplitLoopDim = concatDimExpr.getPosition();
581 if (splitLoopDim && *splitLoopDim != currentSplitLoopDim)
582 return rewriter.notifyMatchFailure(
583 genericOp, "concat inputs partition different loop dimensions");
584 splitLoopDim = currentSplitLoopDim;
585
586 SmallVector<int64_t> currentPartitionSizes;
587 currentPartitionSizes.reserve(concatOp.getInputs().size());
588 for (Value input : concatOp.getInputs()) {
589 int64_t size = cast<RankedTensorType>(input.getType())
590 .getDimSize(concatOp.getDim());
591 if (ShapedType::isDynamic(size))
592 return rewriter.notifyMatchFailure(
593 genericOp, "concat partition size is dynamic");
594 currentPartitionSizes.push_back(size);
595 }
596 partitionSizes.push_back(std::move(currentPartitionSizes));
597 concatOps[index] = concatOp;
598 }
599
600 if (!splitLoopDim)
601 return rewriter.notifyMatchFailure(genericOp, "has no concat input");
602 if (!llvm::all_equal(partitionSizes))
603 return rewriter.notifyMatchFailure(
604 genericOp, "concat inputs have different partition sizes");
605
606 // A tensor input that varies along the split dimension must itself be a
607 // compatible concat. Inputs that are invariant along that dimension can be
608 // reused by every split operation.
609 AffineExpr splitDimExpr =
610 getAffineDimExpr(*splitLoopDim, genericOp.getContext());
611 for (OpOperand *operand : nonConcatInputs) {
612 Type operandType = operand->get().getType();
613 // Scalars do not vary along an iteration-space dimension and can be
614 // reused in every partition.
615 if (isa<IntegerType, FloatType, IndexType, ComplexType>(operandType))
616 continue;
617
618 // Otherwise we want ranked tensors.
619 if (!isa<RankedTensorType>(operandType))
620 return rewriter.notifyMatchFailure(
621 genericOp, "non-concat shaped input is not a ranked tensor");
622
623 if (genericOp.getMatchingIndexingMap(operand).getResultPosition(
624 splitDimExpr))
625 return rewriter.notifyMatchFailure(
626 genericOp, "non-concat input varies along the split dimension");
627 }
628
629 // Map the iteration-space split dimension to each output's physical tensor
630 // dimension. `isElementwise` guarantees that the output maps are
631 // permutations, so every output contains this dimension.
632 SmallVector<unsigned> outputConcatDims;
633 outputConcatDims.reserve(genericOp.getNumDpsInits());
634 for (OpOperand &output : genericOp.getDpsInitsMutable()) {
635 std::optional<unsigned> outputDim =
636 genericOp.getMatchingIndexingMap(&output).getResultPosition(
637 splitDimExpr);
638 // Otherwise the generic op is not elementwise.
639 assert(outputDim &&
640 "elementwise output map must contain the split dimension");
641 outputConcatDims.push_back(*outputDim);
642 }
643
644 Location loc = genericOp.getLoc();
645 // Keep each result in a separate list because a generic can have multiple
646 // outputs.
647 SmallVector<SmallVector<Value>> splitResults(genericOp->getNumResults());
648 SmallVector<int64_t> outputOffsets(genericOp.getNumDpsInits(), 0);
649
650 // Build one generic for each aligned concat partition. For
651 // `elementwise(concat(x0, x1), concat(y0, y1))` becomes
652 // `elementwise(x0, y0)` and `elementwise(x1, y1)`.
653 for (auto [partitionIndex, partitionSize] :
654 llvm::enumerate(partitionSizes.front())) {
655 // For each partition, turn inputs `concat(x0, x1)`, `concat(y0, y1)`, and
656 // `scalar` into the inputs `x0`, `y0`, and `scalar`.
657 SmallVector<Value> inputs =
658 getPartitionInputs(genericOp, concatOps, partitionIndex);
659 // For a `tensor<7xf32>` output and partition size [3, 4], create a
660 // `tensor<3xf32>` output here; if the body reads `%init`, use
661 // `%init[0:3]` as its init instead.
662 PartitionOutputs outputs =
663 createPartitionOutputs(rewriter, loc, genericOp, outputConcatDims,
664 partitionSize, outputOffsets);
665 // For example, clone the body of
666 // `elementwise(concat(x0, x1), concat(y0, y1))` as
667 // `elementwise(x0, y0)`, retaining its indexing maps and iterators.
668 GenericOp splitOp =
669 cloneGenericForPartition(rewriter, loc, genericOp, inputs, outputs);
670 for (auto [resultIndex, result] : llvm::enumerate(splitOp->getResults()))
671 splitResults[resultIndex].push_back(result);
672 }
673
674 // Reassemble every original result from its partition results. The concat
675 // dimension may differ for each output due to its indexing map.
676 SmallVector<Value> replacements;
677 replacements.reserve(genericOp->getNumResults());
678 for (auto [resultIndex, result] :
679 llvm::enumerate(genericOp->getResults())) {
680 replacements.push_back(tensor::ConcatOp::create(
681 rewriter, loc, cast<RankedTensorType>(result.getType()),
682 outputConcatDims[resultIndex], splitResults[resultIndex]));
683 }
684 rewriter.replaceOp(genericOp, replacements);
685 return success();
686 }
687
688private:
689 /// Return the inputs for one concat partition.
690 /// For example, for `elementwise(concat(x0, x1), concat(y0, y1), scalar)`,
691 /// partition 0 uses `(x0, y0, scalar)` and partition 1 uses
692 /// `(x1, y1, scalar)`.
693 static SmallVector<Value>
694 getPartitionInputs(GenericOp genericOp, ArrayRef<tensor::ConcatOp> concatOps,
695 unsigned partitionIndex) {
696 SmallVector<Value> inputs;
697 inputs.reserve(genericOp.getNumDpsInputs());
698 for (auto [index, operand] :
699 llvm::enumerate(genericOp.getDpsInputOperands())) {
700 if (!concatOps[index]) {
701 inputs.push_back(operand->get());
702 continue;
703 }
704 tensor::ConcatOp concatOp = concatOps[index];
705 inputs.push_back(concatOp.getInputs()[partitionIndex]);
706 }
707 return inputs;
708 }
709
710 struct PartitionOutputs {
711 SmallVector<Value> values;
712 SmallVector<Type> resultTypes;
713 };
714
715 /// Create the output operands and result types for one concat partition.
716 /// For an output `tensor<7xf32>` split into sizes `[3, 4]`, this creates a
717 /// `tensor<3xf32>` output for partition 0 and `tensor<4xf32>` for partition
718 /// 1. If the generic body reads its output block argument, the outputs are
719 /// slices of the original init tensor, e.g. `%init[0:3]` and `%init[3:7]`;
720 /// otherwise they are `tensor.empty` values.
721 static PartitionOutputs createPartitionOutputs(
722 PatternRewriter &rewriter, Location loc, GenericOp genericOp,
723 ArrayRef<unsigned> outputConcatDims, int64_t partitionSize,
724 MutableArrayRef<int64_t> outputOffsets) {
725 PartitionOutputs partitionOutputs;
726 partitionOutputs.values.reserve(genericOp.getNumDpsInits());
727 partitionOutputs.resultTypes.reserve(genericOp->getNumResults());
728 for (auto [outputIndex, output] :
729 llvm::enumerate(genericOp.getDpsInitsMutable())) {
730 Value outputValue = output.get();
731 auto outputType = cast<RankedTensorType>(outputValue.getType());
732 unsigned outputConcatDim = outputConcatDims[outputIndex];
733 SmallVector<int64_t> partitionShape(outputType.getShape());
734 partitionShape[outputConcatDim] = partitionSize;
735 auto partitionType =
736 RankedTensorType::get(partitionShape, outputType.getElementType(),
737 outputType.getEncoding());
738
739 SmallVector<OpFoldResult> sizes =
740 tensor::getMixedSizes(rewriter, loc, outputValue);
741 sizes[outputConcatDim] = rewriter.getIndexAttr(partitionSize);
742
743 Value partitionOutput;
744 // A body such as `linalg.yield %in` does not read `%out`, so the init
745 // value is irrelevant and this partition can use `tensor.empty`.
746 if (!genericOp.payloadUsesValueFromOperand(&output)) {
747 partitionOutput = tensor::EmptyOp::create(rewriter, loc, sizes,
748 outputType.getElementType(),
749 outputType.getEncoding());
750 } else {
751 // A body such as `%sum = arith.addf %in, %out` reads the init value.
752 // For a `[3, 4]` partitioning, extract `%init[0:3]` for partition 0
753 // and `%init[3:7]` for partition 1 to preserve that value.
754 SmallVector<OpFoldResult> offsets(outputType.getRank(),
755 rewriter.getIndexAttr(0));
756 SmallVector<OpFoldResult> strides(outputType.getRank(),
757 rewriter.getIndexAttr(1));
758 offsets[outputConcatDim] =
759 rewriter.getIndexAttr(outputOffsets[outputIndex]);
760 partitionOutput = tensor::ExtractSliceOp::create(
761 rewriter, loc, partitionType, outputValue, offsets, sizes, strides);
762 }
763 partitionOutputs.values.push_back(partitionOutput);
764 partitionOutputs.resultTypes.push_back(partitionType);
765 outputOffsets[outputIndex] += partitionSize;
766 }
767 return partitionOutputs;
768 }
769
770 /// Clone `genericOp` for one partition while preserving its computation and
771 /// relevant attributes. For example, this turns the `x0, y0` inputs from
772 /// `getPartitionInputs` into an `elementwise(x0, y0)` generic with the same
773 /// body as the original `elementwise(concat(x0, x1), concat(y0, y1))`.
774 static GenericOp cloneGenericForPartition(PatternRewriter &rewriter,
775 Location loc, GenericOp genericOp,
776 ArrayRef<Value> inputs,
777 const PartitionOutputs &outputs) {
778 GenericOp splitOp = GenericOp::create(
779 rewriter, loc, outputs.resultTypes, inputs, outputs.values,
780 genericOp.getIndexingMapsArray(), genericOp.getIteratorTypesArray());
781 if (StringAttr doc = genericOp.getDocAttr())
782 splitOp->setAttr(splitOp.getDocAttrName(), doc);
783 if (StringAttr libraryCall = genericOp.getLibraryCallAttr())
784 splitOp->setAttr(splitOp.getLibraryCallAttrName(), libraryCall);
785 splitOp->setDiscardableAttrs(genericOp->getDiscardableAttrDictionary());
786 rewriter.cloneRegionBefore(genericOp.getRegion(), splitOp.getRegion(),
787 splitOp.getRegion().begin());
788 return splitOp;
789 }
790};
791} // namespace
792
793//===---------------------------------------------------------------------===//
794// Methods and patterns that fuse reshape ops with elementwise operations by
795// expanding the dimensionality of the elementwise operations.
796//===---------------------------------------------------------------------===//
797
798/// Conditions for folding a structured linalg operation with a reshape op by
799/// expanding the iteration space dimensionality for tensor operations. These
800/// are preconditions assumed by `foldReshapeByDimExpansion` which implements
801/// the following fusion pattern.
802///
803/// Consider
804///
805/// %c = linalg.generic ins(%a, %b : memref<?x?x?xf32>, memref<?x?xf32>)
806/// indexing_maps = [affine_map<(d0, d1, d2) -> (d1, d0, d2)>,
807/// affine_map<(d0, d1, d2) -> (d1, d2)>,
808/// affine_map<(d0, d1, d2) -> (d0, d2, d1)>]
809/// %d = tensor.expand_shape %c [[0, 1], [2], [3, 4, 5]]
810/// : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
811///
812/// The reshape can be folded into the `linalgOp` if its loop dimensionality
813/// is increased to match the result (operand) of the tensor.expand_shape.
814/// The indexing_map of the fused tensor in the `linalgOp` and the
815/// reassociation map helps compute the indexing maps of the modified op.
816/// For the above example, based on the reassociation map it
817/// can be concluded that
818///
819/// - The loop used to access the first dimension of the fused tensor is split
820/// into two.
821/// - The loop used to access the second dimension of the fused tensor is kept
822/// as is.
823/// - The loop used to access the third dimension of the fused tensor is split
824/// into three.
825///
826/// i.e. (e0, e1, e2, e3, e4) is the domain of the indexing map of the modified
827/// op, then
828///
829/// d0 -> e0, e1
830/// d1 -> e2, e3, e4
831/// d2 -> e5
832///
833/// substituting this, the structured op can be rewritten as
834///
835/// %d = linalg.generic ins(%0, %1 : )
836/// indexing_maps =
837/// [affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e0, e1, e5)>,
838/// affine_map<(e0, e1, e2, e3, e4, e5) -> (e2, e3, e4, e5)>,
839/// affine_map<(e0, e1, e2, e3, e4, e5) -> (e0, e1, e5, e2, e3, e4)>]
840///
841/// Since operands to the linalg generic are now 5D, reshapes can be introduced
842/// to make it consistent
843///
844/// %0 = tensor.expand_shape %a [[0, 1, 2], [3, 4], [5]]
845/// : tensor<?x?x?xf32> into tensor<?x?x?x?x?x?xf32>
846/// %1 = tensor.expand_shape %b [[0, 1, 2], [3]]
847/// : tensor<?x?x?xf32> into tensor<?x?x?x?xf32>
848///
849/// The added reshapes are again expanding patterns, so they will get fused
850/// with its producers if possible.
851static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp,
852 OpOperand *fusableOpOperand) {
853 // Is fusable only if:
854 // - All the indexing maps for operands and results are projected
855 // permutations.
856 // - The fused tensor is not a scalar.
858 linalgOp.getIteratorTypesArray();
859 AffineMap operandMap = linalgOp.getMatchingIndexingMap(fusableOpOperand);
860 return linalgOp.hasPureTensorSemantics() &&
861 llvm::all_of(linalgOp.getIndexingMaps().getValue(),
862 [](Attribute attr) {
863 return cast<AffineMapAttr>(attr)
864 .getValue()
865 .isProjectedPermutation();
866 }) &&
867 operandMap.getNumResults() > 0;
868}
869
870namespace {
871/// Information needed to expand a generic operation to fold the reshape with
872/// it.
873class ExpansionInfo {
874public:
875 // Computes the mapping from original dimensions of the op to the dimensions
876 // of the expanded op given the `indexingMap` of the fused operand/result of
877 // the generic op, the `reassocationMaps` of the reshape op and the shape of
878 // the expanded op.
879 LogicalResult compute(LinalgOp linalgOp, OpOperand *fusableOpOperand,
880 ArrayRef<AffineMap> reassociationMaps,
881 ArrayRef<OpFoldResult> expandedShape,
882 PatternRewriter &rewriter);
883 unsigned getOrigOpNumDims() const { return reassociation.size(); }
884 unsigned getExpandedOpNumDims() const { return expandedOpNumDims; }
885 ReassociationIndicesRef getExpandedDims(unsigned i) const {
886 return reassociation[i];
887 }
888 ArrayRef<OpFoldResult> getExpandedShapeOfDim(unsigned i) const {
889 return expandedShapeMap[i];
890 }
891 ArrayRef<OpFoldResult> getOriginalShape() const { return originalLoopExtent; }
892
893private:
894 /// Reassociation from the dimensions in the original operation to the
895 /// dimension of the expanded operation.
896 SmallVector<ReassociationIndices> reassociation;
897 /// Mapping from extent of loops in the original operation, to the extent of
898 /// loops in the expanded operation.
899 SmallVector<SmallVector<OpFoldResult>> expandedShapeMap;
900 /// Extent of the loop in the original operation.
901 SmallVector<OpFoldResult> originalLoopExtent;
902 unsigned expandedOpNumDims;
903};
904} // namespace
905
906LogicalResult ExpansionInfo::compute(LinalgOp linalgOp,
907 OpOperand *fusableOpOperand,
908 ArrayRef<AffineMap> reassociationMaps,
909 ArrayRef<OpFoldResult> expandedShape,
910 PatternRewriter &rewriter) {
911 if (reassociationMaps.empty())
912 return failure();
913 AffineMap fusedIndexMap = linalgOp.getMatchingIndexingMap(fusableOpOperand);
914
915 OpBuilder::InsertionGuard g(rewriter);
916 rewriter.setInsertionPoint(linalgOp);
917 originalLoopExtent = llvm::map_to_vector(
918 linalgOp.createLoopRanges(rewriter, linalgOp->getLoc()),
919 [](Range r) { return r.size; });
920
921 reassociation.clear();
922 expandedShapeMap.clear();
923 // Compute the number of dimension in the expanded op that correspond to each
924 // dimension of the original op.
925 SmallVector<unsigned> numExpandedDims(fusedIndexMap.getNumDims(), 1);
926 expandedShapeMap.resize(fusedIndexMap.getNumDims());
927 for (const auto &resultExpr : llvm::enumerate(fusedIndexMap.getResults())) {
928 unsigned pos = cast<AffineDimExpr>(resultExpr.value()).getPosition();
929 AffineMap foldedDims = reassociationMaps[resultExpr.index()];
930 numExpandedDims[pos] = foldedDims.getNumResults();
931 ArrayRef<OpFoldResult> shape =
932 expandedShape.slice(foldedDims.getDimPosition(0), numExpandedDims[pos]);
933 expandedShapeMap[pos].assign(shape.begin(), shape.end());
934 }
935 // The remaining dimensions remain the same.
936 for (unsigned i : llvm::seq<unsigned>(0, fusedIndexMap.getNumDims()))
937 if (expandedShapeMap[i].empty())
938 expandedShapeMap[i] = {originalLoopExtent[i]};
939
940 // Compute reassociation map from the original op to the expanded op.
941 unsigned sum = 0;
942 reassociation.reserve(fusedIndexMap.getNumDims());
943 for (const auto &numFoldedDim : llvm::enumerate(numExpandedDims)) {
944 auto seq = llvm::seq<int64_t>(sum, sum + numFoldedDim.value());
945 reassociation.emplace_back(seq.begin(), seq.end());
946 sum += numFoldedDim.value();
947 }
948 expandedOpNumDims = sum;
949 return success();
950}
951
952/// Return the indexing map to use in the expanded op for a given the
953/// `indexingMap` of the original operation.
954static AffineMap
956 const ExpansionInfo &expansionInfo) {
958 for (AffineExpr expr : indexingMap.getResults()) {
959 unsigned pos = cast<AffineDimExpr>(expr).getPosition();
960 SmallVector<AffineExpr, 4> expandedExprs = llvm::map_to_vector<4>(
961 expansionInfo.getExpandedDims(pos), [&](int64_t v) {
962 return builder.getAffineDimExpr(static_cast<unsigned>(v));
963 });
964 newExprs.append(expandedExprs.begin(), expandedExprs.end());
965 }
966 return AffineMap::get(expansionInfo.getExpandedOpNumDims(),
967 indexingMap.getNumSymbols(), newExprs,
968 builder.getContext());
969}
970
971/// Return the shape and type of the operand/result to use in the expanded op
972/// given the type in the original op.
973static std::tuple<SmallVector<OpFoldResult>, RankedTensorType>
974getExpandedShapeAndType(RankedTensorType originalType, AffineMap indexingMap,
975 const ExpansionInfo &expansionInfo) {
976 SmallVector<OpFoldResult> expandedShape;
977 for (AffineExpr expr : indexingMap.getResults()) {
978 unsigned dim = cast<AffineDimExpr>(expr).getPosition();
979 ArrayRef<OpFoldResult> dimExpansion =
980 expansionInfo.getExpandedShapeOfDim(dim);
981 expandedShape.append(dimExpansion.begin(), dimExpansion.end());
982 }
983 SmallVector<int64_t> expandedStaticShape;
984 std::tie(expandedStaticShape, std::ignore) =
985 decomposeMixedValues(expandedShape);
986 return {expandedShape, RankedTensorType::get(expandedStaticShape,
987 originalType.getElementType())};
988}
989
990/// Returns the reassociation maps to use in the `tensor.expand_shape`
991/// operation to convert the operands of the original operation to operands of
992/// the expanded operation. The same method is used to compute the
993/// `tensor.collapse_shape` used to collapse the result of the expanded
994/// op to get the value that can replace all uses of the results of the original
995/// op.
996static SmallVector<ReassociationIndices>
998 const ExpansionInfo &expansionInfo) {
1000 unsigned numReshapeDims = 0;
1001 for (AffineExpr expr : indexingMap.getResults()) {
1002 unsigned dim = cast<AffineDimExpr>(expr).getPosition();
1003 auto numExpandedDims = expansionInfo.getExpandedDims(dim).size();
1004 SmallVector<int64_t, 2> indices = llvm::to_vector<2>(
1005 llvm::seq<int64_t>(numReshapeDims, numReshapeDims + numExpandedDims));
1006 reassociation.emplace_back(std::move(indices));
1007 numReshapeDims += numExpandedDims;
1008 }
1009 return reassociation;
1010}
1011
1012/// Update the body of an expanded linalg operation having index semantics. The
1013/// indices of the original operation need to be recovered by linearizing the
1014/// indices of the correspoding dimensions of the expanded operation. For now it
1015/// is assumed that the shapes of the expanded operation needed for
1016/// linearization are static.
1018 Location loc, Region &fusedRegion,
1019 const ExpansionInfo &expansionInfo) {
1020 // Replace the original indices by the linearization of the expanded indices.
1021 for (IndexOp indexOp :
1022 llvm::make_early_inc_range(fusedRegion.front().getOps<IndexOp>())) {
1023 ArrayRef<int64_t> expandedDims =
1024 expansionInfo.getExpandedDims(indexOp.getDim());
1025 assert(!expandedDims.empty() && "expected valid expansion info");
1026
1027 // Skip index operations that are not affected by the expansion.
1028 if (expandedDims.size() == 1 &&
1029 expandedDims.front() == (int64_t)indexOp.getDim())
1030 continue;
1031
1032 // Linearize the expanded indices of the original index dimension.
1033 OpBuilder::InsertionGuard guard(rewriter);
1034 rewriter.setInsertionPointAfter(indexOp);
1035 ArrayRef<OpFoldResult> expandedDimsShape =
1036 expansionInfo.getExpandedShapeOfDim(indexOp.getDim()).drop_front();
1037 SmallVector<Value> expandedIndices;
1038 expandedIndices.reserve(expandedDims.size() - 1);
1039 llvm::transform(
1040 expandedDims.drop_front(), std::back_inserter(expandedIndices),
1041 [&](int64_t dim) { return IndexOp::create(rewriter, loc, dim); });
1042 OpFoldResult newIndex =
1043 IndexOp::create(rewriter, loc, expandedDims.front()).getResult();
1044 for (auto [expandedShape, expandedIndex] :
1045 llvm::zip(expandedDimsShape, expandedIndices)) {
1046 AffineExpr idx, acc, shape;
1047 bindDims(rewriter.getContext(), idx, acc);
1048 bindSymbols(rewriter.getContext(), shape);
1050 rewriter, indexOp.getLoc(), idx + acc * shape,
1051 ArrayRef<OpFoldResult>{expandedIndex, newIndex, expandedShape});
1052 }
1053 Value newIndexVal =
1054 getValueOrCreateConstantIndexOp(rewriter, indexOp.getLoc(), newIndex);
1055 rewriter.replaceOp(indexOp, newIndexVal);
1056 }
1057}
1058
1059// Create an expanded transpose op.
1060// the reassociation map is already permuted hence we inverse permute and then
1061// flatten it. Then we inverse permute it again to get the final expanded
1062// transpose permutation. For example,
1063//
1064// permutation = [2, 0, 1]
1065// reassociation_map for expansion = [[0, 1], [2], [3, 4, 5]]
1066//
1067// inverse permutation = [1, 2, 0]
1068// applied to reassocation_map and then flattened becomes
1069// flatened permutation = [2, 3, 4, 5, 0, 1]
1070// final permuation is the inverse of the flattened permutation.
1071//
1072// Becomes
1073//
1074// permutation=[4, 5, 0, 1, 2, 3]
1075
1077 TransposeOp transposeOp,
1078 Value expandedInput, Value output,
1079 ExpansionInfo &expansionInfo) {
1080 SmallVector<int64_t> newPerm;
1081 for (int64_t perm : invertPermutationVector(transposeOp.getPermutation())) {
1082 auto reassoc = expansionInfo.getExpandedDims(perm);
1083 for (int64_t dim : reassoc) {
1084 newPerm.push_back(dim);
1085 }
1086 }
1087 return TransposeOp::create(rewriter, transposeOp.getLoc(), expandedInput,
1088 output, invertPermutationVector(newPerm));
1089}
1090
1091// Create an expanded generic op.
1093 PatternRewriter &rewriter, LinalgOp linalgOp, TypeRange resultTypes,
1094 ArrayRef<Value> &expandedOpOperands, ArrayRef<Value> outputs,
1095 ExpansionInfo &expansionInfo, ArrayRef<AffineMap> expandedOpIndexingMaps) {
1096 // The iterator types of the expanded op are all parallel.
1098 expansionInfo.getExpandedOpNumDims(), utils::IteratorType::parallel);
1099
1100 for (auto [i, type] : llvm::enumerate(linalgOp.getIteratorTypesArray()))
1101 for (auto j : expansionInfo.getExpandedDims(i))
1102 iteratorTypes[j] = type;
1103
1104 Operation *fused = GenericOp::create(rewriter, linalgOp.getLoc(), resultTypes,
1105 expandedOpOperands, outputs,
1106 expandedOpIndexingMaps, iteratorTypes);
1107
1108 Region &fusedRegion = fused->getRegion(0);
1109 Region &originalRegion = linalgOp->getRegion(0);
1110 rewriter.cloneRegionBefore(originalRegion, fusedRegion, fusedRegion.begin());
1111
1112 // Update the index accesses after the expansion.
1113 updateExpandedGenericOpRegion(rewriter, linalgOp.getLoc(), fusedRegion,
1114 expansionInfo);
1115
1116 return fused;
1117}
1118
1119// Create an expanded fused op that retains the name for certain ops
1120// such as fill, copy and transpose and produce a generic op for
1121// rest of linalg ops.
1122static Operation *createExpandedOp(PatternRewriter &rewriter, LinalgOp linalgOp,
1123 TypeRange resultTypes,
1124 ArrayRef<Value> expandedOpOperands,
1125 ArrayRef<Value> outputs,
1126 ArrayRef<AffineMap> expandedOpIndexingMaps,
1127 ExpansionInfo &expansionInfo) {
1128
1129 return TypeSwitch<Operation *, Operation *>(linalgOp.getOperation())
1130 .Case([&](TransposeOp transposeOp) {
1131 return createExpandedTransposeOp(rewriter, transposeOp,
1132 expandedOpOperands[0], outputs[0],
1133 expansionInfo);
1134 })
1135 .Case<FillOp, CopyOp>([&](Operation *op) {
1136 return clone(rewriter, linalgOp, resultTypes,
1137 llvm::to_vector(llvm::concat<Value>(
1138 llvm::to_vector(expandedOpOperands),
1139 llvm::to_vector(outputs))));
1140 })
1141 .Default([&](Operation *op) {
1142 return createExpandedGenericOp(rewriter, linalgOp, resultTypes,
1143 expandedOpOperands, outputs,
1144 expansionInfo, expandedOpIndexingMaps);
1145 });
1146}
1147
1148/// Implements the fusion of a tensor.collapse_shape or a tensor.expand_shape op
1149/// and a generic op as explained in `isFusableWithReshapeByExpansion`. Assumes
1150/// that those conditions have been satisfied.
1151static std::optional<SmallVector<Value>>
1152fuseWithReshapeByExpansion(LinalgOp linalgOp, Operation *reshapeOp,
1153 OpOperand *fusableOpOperand,
1154 PatternRewriter &rewriter) {
1155 assert(isFusableWithReshapeByDimExpansion(linalgOp, fusableOpOperand) &&
1156 "preconditions for fuse operation failed");
1157
1158 Location loc = linalgOp.getLoc();
1159 SmallVector<OpFoldResult> expandedShape;
1160 SmallVector<AffineMap, 4> reassociationIndices;
1161 Value src;
1162 if (auto expandingReshapeOp = dyn_cast<tensor::ExpandShapeOp>(reshapeOp)) {
1163 // Try to move the dynamic dimensions in output shape before the `linalgOp`
1164 // to maintain SSA validity
1165 if (failed(moveValueDefinitions(
1166 rewriter, expandingReshapeOp.getOutputShape(), linalgOp)))
1167 return std::nullopt;
1168
1169 expandedShape = expandingReshapeOp.getMixedOutputShape();
1170 reassociationIndices = expandingReshapeOp.getReassociationMaps();
1171 src = expandingReshapeOp.getSrc();
1172 } else {
1173 auto collapsingReshapeOp = dyn_cast<tensor::CollapseShapeOp>(reshapeOp);
1174 if (!collapsingReshapeOp)
1175 return std::nullopt;
1176
1177 expandedShape = tensor::getMixedSizes(
1178 rewriter, collapsingReshapeOp->getLoc(), collapsingReshapeOp.getSrc());
1179 reassociationIndices = collapsingReshapeOp.getReassociationMaps();
1180 src = collapsingReshapeOp.getSrc();
1181 }
1182
1183 ExpansionInfo expansionInfo;
1184 if (failed(expansionInfo.compute(linalgOp, fusableOpOperand,
1185 reassociationIndices, expandedShape,
1186 rewriter)))
1187 return std::nullopt;
1188
1189 SmallVector<AffineMap, 4> expandedOpIndexingMaps =
1190 llvm::map_to_vector<4>(linalgOp.getIndexingMapsArray(), [&](AffineMap m) {
1191 return getIndexingMapInExpandedOp(rewriter, m, expansionInfo);
1192 });
1193
1194 // Set insertion point to the generic op.
1195 OpBuilder::InsertionGuard g(rewriter);
1196 rewriter.setInsertionPoint(linalgOp);
1197
1198 SmallVector<Value> expandedOpOperands;
1199 expandedOpOperands.reserve(linalgOp.getNumDpsInputs());
1200 for (OpOperand *opOperand : linalgOp.getDpsInputOperands()) {
1201 if (opOperand == fusableOpOperand) {
1202 expandedOpOperands.push_back(src);
1203 continue;
1204 }
1205 if (auto opOperandType =
1206 dyn_cast<RankedTensorType>(opOperand->get().getType())) {
1207 AffineMap indexingMap = linalgOp.getMatchingIndexingMap(opOperand);
1208 SmallVector<OpFoldResult> expandedOperandShape;
1209 RankedTensorType expandedOperandType;
1210 std::tie(expandedOperandShape, expandedOperandType) =
1211 getExpandedShapeAndType(opOperandType, indexingMap, expansionInfo);
1212 if (expandedOperandType != opOperand->get().getType()) {
1213 // Reshape the operand to get the right type.
1214 SmallVector<ReassociationIndices> reassociation =
1215 getReassociationForExpansion(indexingMap, expansionInfo);
1216 if (failed(reshapeLikeShapesAreCompatible(
1217 [&](const Twine &msg) {
1218 return rewriter.notifyMatchFailure(linalgOp, msg);
1219 },
1220 opOperandType.getShape(), expandedOperandType.getShape(),
1221 reassociation,
1222 /*isExpandingReshape=*/true)))
1223 return std::nullopt;
1224 expandedOpOperands.push_back(tensor::ExpandShapeOp::create(
1225 rewriter, loc, expandedOperandType, opOperand->get(), reassociation,
1226 expandedOperandShape));
1227 continue;
1228 }
1229 }
1230 expandedOpOperands.push_back(opOperand->get());
1231 }
1232
1233 SmallVector<Value> outputs;
1234 for (OpOperand &opOperand : linalgOp.getDpsInitsMutable()) {
1235 AffineMap indexingMap = linalgOp.getMatchingIndexingMap(&opOperand);
1236 auto opOperandType = cast<RankedTensorType>(opOperand.get().getType());
1237 SmallVector<OpFoldResult> expandedOutputShape;
1238 RankedTensorType expandedOutputType;
1239 std::tie(expandedOutputShape, expandedOutputType) =
1240 getExpandedShapeAndType(opOperandType, indexingMap, expansionInfo);
1241 if (expandedOutputType != opOperand.get().getType()) {
1242 SmallVector<ReassociationIndices> reassociation =
1243 getReassociationForExpansion(indexingMap, expansionInfo);
1244 if (failed(reshapeLikeShapesAreCompatible(
1245 [&](const Twine &msg) {
1246 return rewriter.notifyMatchFailure(linalgOp, msg);
1247 },
1248 opOperandType.getShape(), expandedOutputType.getShape(),
1249 reassociation,
1250 /*isExpandingReshape=*/true)))
1251 return std::nullopt;
1252 outputs.push_back(tensor::ExpandShapeOp::create(
1253 rewriter, loc, expandedOutputType, opOperand.get(), reassociation,
1254 expandedOutputShape));
1255 } else {
1256 outputs.push_back(opOperand.get());
1257 }
1258 }
1259
1260 TypeRange resultTypes = ValueRange(outputs).getTypes();
1261 Operation *fusedOp =
1262 createExpandedOp(rewriter, linalgOp, resultTypes, expandedOpOperands,
1263 outputs, expandedOpIndexingMaps, expansionInfo);
1264 // Reshape the result values to their original shape if this is a collapsing
1265 // reshape folded into its consumer.
1266 SmallVector<Value> resultVals;
1267 for (OpResult opResult : linalgOp->getOpResults()) {
1268 int64_t resultNumber = opResult.getResultNumber();
1269 if (resultTypes[resultNumber] != opResult.getType()) {
1270 SmallVector<ReassociationIndices> reassociation =
1272 linalgOp.getMatchingIndexingMap(
1273 linalgOp.getDpsInitOperand(resultNumber)),
1274 expansionInfo);
1275 resultVals.push_back(tensor::CollapseShapeOp::create(
1276 rewriter, linalgOp.getLoc(), opResult.getType(),
1277 fusedOp->getResult(resultNumber), reassociation));
1278 } else {
1279 resultVals.push_back(fusedOp->getResult(resultNumber));
1280 }
1281 }
1282 // Assuming a single result.
1283 return resultVals;
1284}
1285
1286namespace {
1287
1288/// Pattern to fuse a tensor.collapse_shape op with its consumer structured op,
1289/// when the reshape op is collapsing dimensions. The dimensionality of the loop
1290/// in the consumer is expanded.
1291class FoldWithProducerReshapeOpByExpansion
1292 : public OpInterfaceRewritePattern<LinalgOp> {
1293public:
1294 FoldWithProducerReshapeOpByExpansion(MLIRContext *context,
1295 ControlFusionFn foldReshapes,
1296 PatternBenefit benefit = 1)
1297 : OpInterfaceRewritePattern<LinalgOp>(context, benefit),
1298 controlFoldingReshapes(std::move(foldReshapes)) {}
1299
1300 LogicalResult matchAndRewrite(LinalgOp linalgOp,
1301 PatternRewriter &rewriter) const override {
1302 for (OpOperand *opOperand : linalgOp.getDpsInputOperands()) {
1303 tensor::CollapseShapeOp reshapeOp =
1304 opOperand->get().getDefiningOp<tensor::CollapseShapeOp>();
1305 if (!reshapeOp)
1306 continue;
1307 // Fold only if
1308 // - The tensor reshape op is folding.
1309 // - All constraints of fusing with reshape by expansion are met.
1310 if (!isFusableWithReshapeByDimExpansion(linalgOp, opOperand) ||
1311 (!controlFoldingReshapes(opOperand)))
1312 continue;
1313
1314 std::optional<SmallVector<Value>> replacementValues =
1315 fuseWithReshapeByExpansion(linalgOp, reshapeOp, opOperand, rewriter);
1316 if (!replacementValues)
1317 return failure();
1318 rewriter.replaceOp(linalgOp, *replacementValues);
1319 return success();
1320 }
1321 return failure();
1322 }
1323
1324private:
1325 ControlFusionFn controlFoldingReshapes;
1326};
1327
1328/// Carries information about a padded dimension.
1329struct PadDimInfo {
1330 // The resulting shape after padding each dimension.
1331 SmallVector<int64_t> paddedShape;
1332
1333 // Low and high padding amounts for each dimension.
1334 SmallVector<OpFoldResult> lowPad;
1335 SmallVector<OpFoldResult> highPad;
1336};
1337
1338/// Computes the expanded padding information for the given pad operation based
1339/// on the provided expanded shape and reassociation indices. Returns a list of
1340/// PadDimInfo containing the low and high padding amounts and the padded
1341/// size for each dimension, or failure if the expansion is not possible.
1342static FailureOr<PadDimInfo>
1343computeExpandedPadding(tensor::PadOp padOp, ArrayRef<int64_t> expandedShape,
1344 ArrayRef<ReassociationIndices> reassociations,
1345 PatternRewriter &rewriter) {
1346 // If the padding value depends on the index values of the pad operation,
1347 // then it may not be valid to expand the dimensions, since it will change
1348 // the index values on which the padding value depends. This is not currently
1349 // supported by the pad expansion patterns, but it could be implemented
1350 // similarly to the expansion of linalg.generic ops with linalg.index ops in
1351 // the body, as is done in `updateExpandedGenericOpRegion`.
1352 if (!padOp.getConstantPaddingValue())
1353 return failure();
1354
1355 // Expanded dimensions cannot have padding because the resulting padding may
1356 // not be representable by a tensor.pad op. There are some special cases where
1357 // it is possible (like expanding unit dims), but supporting these cases is
1358 // NYI, so disallow it for now.
1359 ArrayRef<int64_t> low = padOp.getStaticLow();
1360 ArrayRef<int64_t> high = padOp.getStaticHigh();
1361 for (auto [reInd, l, h] : llvm::zip_equal(reassociations, low, high)) {
1362 if (reInd.size() != 1 && (l != 0 || h != 0))
1363 return failure();
1364 }
1365
1366 SmallVector<OpFoldResult> mixedLowPad(padOp.getMixedLowPad());
1367 SmallVector<OpFoldResult> mixedHighPad(padOp.getMixedHighPad());
1368 ArrayRef<int64_t> paddedShape = padOp.getResultType().getShape();
1369 PadDimInfo padDimInfo;
1370 padDimInfo.paddedShape.assign(expandedShape);
1371 padDimInfo.lowPad.assign(expandedShape.size(), rewriter.getIndexAttr(0));
1372 padDimInfo.highPad.assign(expandedShape.size(), rewriter.getIndexAttr(0));
1373 for (auto [idx, reInd] : llvm::enumerate(reassociations)) {
1374 if (reInd.size() == 1) {
1375 padDimInfo.paddedShape[reInd[0]] = paddedShape[idx];
1376 padDimInfo.lowPad[reInd[0]] = mixedLowPad[idx];
1377 padDimInfo.highPad[reInd[0]] = mixedHighPad[idx];
1378 }
1379 }
1380
1381 return padDimInfo;
1382}
1383
1384class FoldPadWithProducerReshapeOpByExpansion
1385 : public OpRewritePattern<tensor::PadOp> {
1386public:
1387 FoldPadWithProducerReshapeOpByExpansion(MLIRContext *context,
1388 ControlFusionFn foldReshapes,
1389 PatternBenefit benefit = 1)
1390 : OpRewritePattern<tensor::PadOp>(context, benefit),
1391 controlFoldingReshapes(std::move(foldReshapes)) {}
1392
1393 LogicalResult matchAndRewrite(tensor::PadOp padOp,
1394 PatternRewriter &rewriter) const override {
1395 tensor::CollapseShapeOp reshapeOp =
1396 padOp.getSource().getDefiningOp<tensor::CollapseShapeOp>();
1397 if (!reshapeOp)
1398 return failure();
1399
1400 if (!controlFoldingReshapes(&padOp.getSourceMutable())) {
1401 return rewriter.notifyMatchFailure(padOp,
1402 "fusion blocked by control function");
1403 }
1404
1405 RankedTensorType expandedType = reshapeOp.getSrcType();
1406 SmallVector<ReassociationIndices> reassociations =
1407 reshapeOp.getReassociationIndices();
1408 FailureOr<PadDimInfo> maybeExpandedPadding = computeExpandedPadding(
1409 padOp, expandedType.getShape(), reassociations, rewriter);
1410 if (failed(maybeExpandedPadding))
1411 return failure();
1412 PadDimInfo &expandedPadding = maybeExpandedPadding.value();
1413
1414 Location loc = padOp->getLoc();
1415 RankedTensorType expandedPaddedType =
1416 padOp.getResultType().clone(expandedPadding.paddedShape);
1417
1418 auto newPadOp = tensor::PadOp::create(
1419 rewriter, loc, expandedPaddedType, reshapeOp.getSrc(),
1420 expandedPadding.lowPad, expandedPadding.highPad,
1421 padOp.getConstantPaddingValue(), padOp.getNofold());
1422
1423 rewriter.replaceOpWithNewOp<tensor::CollapseShapeOp>(
1424 padOp, padOp.getResultType(), newPadOp.getResult(), reassociations);
1425
1426 return success();
1427 }
1428
1429private:
1430 ControlFusionFn controlFoldingReshapes;
1431};
1432
1433class FoldReshapeWithProducerPadOpByExpansion
1434 : public OpRewritePattern<tensor::ExpandShapeOp> {
1435public:
1436 FoldReshapeWithProducerPadOpByExpansion(MLIRContext *context,
1437 ControlFusionFn foldReshapes,
1438 PatternBenefit benefit = 1)
1439 : OpRewritePattern<tensor::ExpandShapeOp>(context, benefit),
1440 controlFoldingReshapes(std::move(foldReshapes)) {}
1441
1442 LogicalResult matchAndRewrite(tensor::ExpandShapeOp expandOp,
1443 PatternRewriter &rewriter) const override {
1444 tensor::PadOp padOp = expandOp.getSrc().getDefiningOp<tensor::PadOp>();
1445 if (!padOp)
1446 return failure();
1447
1448 if (!controlFoldingReshapes(&expandOp.getSrcMutable())) {
1449 return rewriter.notifyMatchFailure(expandOp,
1450 "fusion blocked by control function");
1451 }
1452
1453 RankedTensorType expandedType = expandOp.getResultType();
1454 SmallVector<ReassociationIndices> reassociations =
1455 expandOp.getReassociationIndices();
1456 FailureOr<PadDimInfo> maybeExpandedPadding = computeExpandedPadding(
1457 padOp, expandedType.getShape(), reassociations, rewriter);
1458 if (failed(maybeExpandedPadding))
1459 return failure();
1460 PadDimInfo &expandedPadding = maybeExpandedPadding.value();
1461
1462 Location loc = expandOp->getLoc();
1463 SmallVector<OpFoldResult> newExpandedSizes = expandOp.getMixedOutputShape();
1464 SmallVector<int64_t> newExpandedShape(expandedType.getShape());
1465 rewriter.setInsertionPointAfterValue(padOp.getSource());
1466 SmallVector<OpFoldResult> padSrcSizes =
1467 tensor::getMixedSizes(rewriter, loc, padOp.getSource());
1468 for (auto [idx, reInd] : llvm::enumerate(reassociations)) {
1469 // We know that any reassociation with multiple dims is not padded because
1470 // of the requirements of computeExpandedPadding.
1471 if (reInd.size() == 1) {
1472 newExpandedShape[reInd[0]] = padOp.getSourceType().getDimSize(idx);
1473 newExpandedSizes[reInd[0]] = padSrcSizes[idx];
1474 }
1475 }
1476 RankedTensorType newExpandedType = expandedType.clone(newExpandedShape);
1477 auto newExpandOp = tensor::ExpandShapeOp::create(
1478 rewriter, loc, newExpandedType, padOp.getSource(), reassociations,
1479 newExpandedSizes);
1480 RankedTensorType expandedPaddedType =
1481 padOp.getResultType().clone(expandedPadding.paddedShape);
1482 rewriter.setInsertionPoint(expandOp);
1483 auto newPadOp = tensor::PadOp::create(
1484 rewriter, loc, expandedPaddedType, newExpandOp.getResult(),
1485 expandedPadding.lowPad, expandedPadding.highPad,
1486 padOp.getConstantPaddingValue(), padOp.getNofold());
1487
1488 rewriter.replaceOp(expandOp, newPadOp.getResult());
1489
1490 return success();
1491 }
1492
1493private:
1494 ControlFusionFn controlFoldingReshapes;
1495};
1496
1497/// Pattern to fold a tensor.expand_shape op with its producer generic op
1498/// by expanding the dimensionality of the loop in the producer op.
1499struct FoldReshapeWithGenericOpByExpansion
1500 : public OpRewritePattern<tensor::ExpandShapeOp> {
1501
1502 FoldReshapeWithGenericOpByExpansion(MLIRContext *context,
1503 ControlFusionFn foldReshapes,
1504 PatternBenefit benefit = 1)
1505 : OpRewritePattern<tensor::ExpandShapeOp>(context, benefit),
1506 controlFoldingReshapes(std::move(foldReshapes)) {}
1507
1508 LogicalResult matchAndRewrite(tensor::ExpandShapeOp reshapeOp,
1509 PatternRewriter &rewriter) const override {
1510 // Fold only if all constraints of fusing with reshape by expansion are met.
1511 auto producerResult = dyn_cast<OpResult>(reshapeOp.getSrc());
1512 if (!producerResult) {
1513 return rewriter.notifyMatchFailure(reshapeOp,
1514 "source not produced by an operation");
1515 }
1516
1517 auto producer = dyn_cast<LinalgOp>(producerResult.getOwner());
1518 if (!producer) {
1519 return rewriter.notifyMatchFailure(reshapeOp,
1520 "producer not a generic op");
1521 }
1522
1524 producer,
1525 producer.getDpsInitOperand(producerResult.getResultNumber()))) {
1526 return rewriter.notifyMatchFailure(
1527 reshapeOp, "failed preconditions of fusion with producer generic op");
1528 }
1529
1530 if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {
1531 return rewriter.notifyMatchFailure(reshapeOp,
1532 "fusion blocked by control function");
1533 }
1534
1535 std::optional<SmallVector<Value>> replacementValues =
1537 producer, reshapeOp,
1538 producer.getDpsInitOperand(producerResult.getResultNumber()),
1539 rewriter);
1540 if (!replacementValues) {
1541 return rewriter.notifyMatchFailure(reshapeOp,
1542 "fusion by expansion failed");
1543 }
1544
1545 // Find the replacement for the reshape op. Since the replacements have the
1546 // same type as the returns of the original generic op, the consumer reshape
1547 // op can be replaced by the source of the collapse_shape op that defines
1548 // the replacement.
1549 Value reshapeReplacement =
1550 (*replacementValues)[cast<OpResult>(reshapeOp.getSrc())
1551 .getResultNumber()];
1552 if (auto collapseOp =
1553 reshapeReplacement.getDefiningOp<tensor::CollapseShapeOp>()) {
1554 reshapeReplacement = collapseOp.getSrc();
1555 }
1556 rewriter.replaceOp(reshapeOp, reshapeReplacement);
1557 rewriter.replaceOp(producer, *replacementValues);
1558 return success();
1559 }
1560
1561private:
1562 ControlFusionFn controlFoldingReshapes;
1563};
1564} // namespace
1565
1566//===---------------------------------------------------------------------===//
1567// Methods and patterns to fuse reshape with linalg.generic operations by
1568// contraction of dimensions.
1569//===---------------------------------------------------------------------===//
1570
1571/// For a given list of indices in the range of the `indexingMap` that are
1572/// folded, return the indices of the corresponding domain. Return
1573/// `std::nullopt` on failure. Ensures that all the elements of the returned
1574/// reassociation are distinct.
1577 ReassociationIndicesRef rangeReassociation) {
1578 assert(indexingMap.isProjectedPermutation() &&
1579 "expected projected permutation");
1580
1581 ReassociationIndices domainReassociation =
1582 llvm::map_to_vector<4>(rangeReassociation, [&](int64_t pos) -> int64_t {
1583 return cast<AffineDimExpr>(indexingMap.getResults()[pos]).getPosition();
1584 });
1585 // The projected permutation semantics ensures that there is no repetition of
1586 // the domain indices.
1587 return domainReassociation;
1588}
1589
1590/// For a given `dimSequence`, check if the sequence is conserved in the
1591/// `indexingMap`. `indexingMap` is expected to be a projected permutation.
1592/// Non-existence of the sequence returns true as well.
1594 ReassociationIndicesRef dimSequence) {
1595 assert(!dimSequence.empty() &&
1596 "expected non-empty list for dimension sequence");
1597
1598 // Dimension sequences can only be preserved in projected permutation maps.
1599 if (!indexingMap.isProjectedPermutation()) {
1600 return false;
1601 }
1602
1603 llvm::SmallDenseSet<unsigned, 4> sequenceElements;
1604 sequenceElements.insert_range(dimSequence);
1605
1606 unsigned dimSequenceStart = dimSequence[0];
1607 for (const auto &expr : enumerate(indexingMap.getResults())) {
1608 unsigned dimInMapStart = cast<AffineDimExpr>(expr.value()).getPosition();
1609 // 1. Check if this start of the sequence.
1610 if (dimInMapStart == dimSequenceStart) {
1611 if (expr.index() + dimSequence.size() > indexingMap.getNumResults())
1612 return false;
1613 // 1a. Check if sequence is preserved.
1614 for (const auto &dimInSequence : enumerate(dimSequence)) {
1615 unsigned dimInMap =
1616 cast<AffineDimExpr>(
1617 indexingMap.getResult(expr.index() + dimInSequence.index()))
1618 .getPosition();
1619 if (dimInMap != dimInSequence.value())
1620 return false;
1621 }
1622 // Found the sequence. Projected permutation
1623 // enforces that all AffineDimExprs in the result are unique, so no
1624 // further checks are needed.
1625 return true;
1626 }
1627 // 2. If position in the expr (which is of type AffineDimExpr) is part
1628 // of sequence, return false here. This implies the entire sequence does not
1629 // exist in the indexing map.
1630 if (sequenceElements.count(dimInMapStart))
1631 return false;
1632 }
1633 // 3. No element of sequence found. Return true.
1634 return true;
1635}
1636
1639 return llvm::all_of(maps, [&](AffineMap map) {
1640 return llvm::all_of(dimSequences, [&](ReassociationIndicesRef dimSequence) {
1641 return isDimSequencePreserved(map, dimSequence);
1642 });
1643 });
1644}
1645
1646// Return the list of dimensions of the iteration domain that can be
1647// collapsed to allow for fusion with the a producer that is an expand_shape
1648// operation. If all dimensions created by expansion can be collapsed in the
1649// iteration space then the reshape is defunct.
1650//
1651// Example:
1652//
1653// ```mlir
1654// #map = affine_map<(d0, d1) -> (d0, d1)>
1655// %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1656// %2 = tensor.empty [..] : tensor<?x4xf32>
1657// %3 = linalg.generic {
1658// indexing_maps = [#map, #map],
1659// iterator_types = ["parallel" ,"parallel"]}
1660// ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<?x4xf32>) {.. }
1661// ```
1662//
1663// can be fused by collapsing the dimensions of the iteration space.
1664//
1665// ```mlir
1666// #map = affine_map<(d0) -> (d0)>
1667// %2 = tensor.empty [..] : tensor<?xf32>
1668// %3 = linalg.generic {
1669// indexing_maps = [#map, #map],
1670// iterator_types = ["parallel"]}
1671// ins(%1 : tensor<?xf32>) outs(%2 : tensor<?xf32>) {.. }
1672// %4 = tensor.expand_shape %3 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1673// ```
1674//
1675// In the following example,
1676//
1677// ```mlir
1678// #map0 = affine_map<(d0, d1) -> (d0, d1)>
1679// #map1 = affine_map<(d0, d1) -> (d1, d0)>
1680// %1 = tensor.expand_shape %0 [[0, 1]] : tensor<?xf32> into tensor<?x4xf32>
1681// %2 = tensor.empty [..] : tensor<4x?xf32>
1682// %2 = linalg.generic {
1683// indexing_maps = [#map0, #map1],
1684// iterator_types = ["parallel" ,"parallel"]}
1685// ins(%1 : tensor<?x4xf32>) outs(%2 : tensor<4x?xf32>) {.. }
1686// ```
1687//
1688// the reshape cannot be fused with the generic op by collapsing the op
1689// dimensions since the indexing maps will have to contain mods and divs
1690// to preserve the accesses pattern. When no dimensions of the iteration
1691// space are collapsable and empty vector is returned.
1693getCollapsableIterationSpaceDims(GenericOp genericOp, OpOperand *fusableOperand,
1694 ArrayRef<ReassociationIndices> reassociation) {
1695 // Some basic checks for this fusion to be valid.
1696 if (!genericOp.hasPureTensorSemantics())
1697 return {};
1698
1699 if (!llvm::all_of(genericOp.getIndexingMapsArray(), [](AffineMap map) {
1700 return map.isProjectedPermutation();
1701 })) {
1702 return {};
1703 }
1704
1705 // Compute all the loops with the reduction iterator types.
1706 SmallVector<unsigned> reductionDims;
1707 genericOp.getReductionDims(reductionDims);
1708
1709 llvm::SmallDenseSet<unsigned, 4> processedIterationDims;
1710 AffineMap indexingMap = genericOp.getMatchingIndexingMap(fusableOperand);
1711 auto iteratorTypes = genericOp.getIteratorTypesArray();
1712 SmallVector<ReassociationIndices> iterationSpaceReassociation;
1713 for (ReassociationIndicesRef foldedRangeDims : reassociation) {
1714 assert(!foldedRangeDims.empty() && "unexpected empty reassociation");
1715
1716 // Ignore dims that are not folded.
1717 if (foldedRangeDims.size() == 1)
1718 continue;
1719
1720 ReassociationIndices foldedIterationSpaceDims =
1721 getDomainReassociation(indexingMap, foldedRangeDims);
1722
1723 // Check that the folded iteration dims do not contain already processed
1724 // dims.
1725 if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {
1726 return processedIterationDims.count(dim);
1727 }))
1728 continue;
1729
1730 // Check that all folded iterator types are all parallel or all reductions.
1731 utils::IteratorType startIteratorType =
1732 iteratorTypes[foldedIterationSpaceDims[0]];
1733 if (!isParallelIterator(startIteratorType) &&
1734 !isReductionIterator(startIteratorType))
1735 continue;
1736 if (llvm::any_of(foldedIterationSpaceDims, [&](int64_t dim) {
1737 return iteratorTypes[dim] != startIteratorType;
1738 }))
1739 continue;
1740
1741 // If the folded dimensions correspond to a "reduction" iterator type,
1742 // the folded dimensions need to be "in-order". Strictly speaking this is
1743 // not necessary, for reductions that are associative and commutative, but
1744 // using a more strict definition of reduction for now.
1745 if (isReductionIterator(startIteratorType)) {
1746 bool isContiguous = false;
1747 for (const auto &startDim : llvm::enumerate(reductionDims)) {
1748 // Move window in `reductionDims` to start of the folded iteration dims.
1749 if (startDim.value() != foldedIterationSpaceDims[0])
1750 continue;
1751 // If sizes doesnt match, trivial not contiguous. This condition should
1752 // not be hit.
1753 if (startDim.index() + foldedIterationSpaceDims.size() >
1754 reductionDims.size())
1755 break;
1756 // Check that the contiguity is maintained.
1757 isContiguous = true;
1758 for (const auto &foldedDim :
1759 llvm::enumerate(foldedIterationSpaceDims)) {
1760 if (reductionDims[foldedDim.index() + startDim.index()] !=
1761 foldedDim.value()) {
1762 isContiguous = false;
1763 break;
1764 }
1765 }
1766 break;
1767 }
1768 if (!isContiguous)
1769 continue;
1770 }
1771
1772 // Check that the sequence is preserved in all indexing maps.
1773 if (llvm::any_of(genericOp.getIndexingMapsArray(),
1774 [&](AffineMap indexingMap) {
1775 return !isDimSequencePreserved(indexingMap,
1776 foldedIterationSpaceDims);
1777 }))
1778 continue;
1779
1780 processedIterationDims.insert_range(foldedIterationSpaceDims);
1781 iterationSpaceReassociation.emplace_back(
1782 std::move(foldedIterationSpaceDims));
1783 }
1784
1785 return iterationSpaceReassociation;
1786}
1787
1788/// Helper class to carry state while collapsing the `linalg.generic` op.
1789namespace {
1790class CollapsingInfo {
1791public:
1792 LogicalResult initialize(unsigned origNumLoops,
1793 ArrayRef<ReassociationIndices> foldedIterationDims) {
1794 llvm::SmallDenseSet<int64_t, 4> processedDims;
1795 // Find all the dims that are folded.
1796 for (ReassociationIndicesRef foldedIterationDim : foldedIterationDims) {
1797 if (foldedIterationDim.empty())
1798 continue;
1799 // If the folded dims contain dims already folded, that's illegal
1800 // specification. Repetition within a list is also illegal.
1801 for (auto dim : foldedIterationDim) {
1802 if (dim >= origNumLoops)
1803 return failure();
1804 if (processedDims.count(dim))
1805 return failure();
1806 processedDims.insert(dim);
1807 }
1808 collapsedOpToOrigOpIterationDim.emplace_back(foldedIterationDim.begin(),
1809 foldedIterationDim.end());
1810 }
1811 if (processedDims.size() > origNumLoops)
1812 return failure();
1813
1814 // Add all the preserved dims of the original op as single
1815 // elements to `collapsedOpToOrigOpIterationDim`.
1816 for (auto dim : llvm::seq<int64_t>(0, origNumLoops)) {
1817 if (processedDims.count(dim))
1818 continue;
1819 collapsedOpToOrigOpIterationDim.emplace_back(ReassociationIndices{dim});
1820 }
1821
1822 llvm::sort(collapsedOpToOrigOpIterationDim,
1824 return lhs[0] < rhs[0];
1825 });
1826 origOpToCollapsedOpIterationDim.resize(origNumLoops);
1827 for (const auto &foldedDims :
1828 llvm::enumerate(collapsedOpToOrigOpIterationDim)) {
1829 for (const auto &dim : enumerate(foldedDims.value()))
1830 origOpToCollapsedOpIterationDim[dim.value()] =
1831 std::make_pair<int64_t, unsigned>(foldedDims.index(), dim.index());
1832 }
1833 return success();
1834 }
1835
1836 /// Return mapping from collapsed loop domain to original loop domain.
1837 ArrayRef<ReassociationIndices> getCollapsedOpToOrigOpMapping() const {
1838 return collapsedOpToOrigOpIterationDim;
1839 }
1840
1841 /// Return mapping from original loop domain to collapsed loop domain. The
1842 /// mapping is a pair. First value is the dimension in the collapsed loop that
1843 /// the original loop is mapped to. Second is the relative position in folded
1844 /// list of this domain. For example if the original loop domain is 3D, and
1845 /// the collapsed loop domain is folding all of it, i.e.
1846 ///
1847 /// ```
1848 /// collapsedOpToOrigOpMapping = [[0, 1, 2] [3, 4]]`
1849 /// ```
1850 ///
1851 /// then
1852 ///
1853 /// ```
1854 /// origOpToCollapsedOpMapping[0] = {0, 0};
1855 /// origOpToCollapsedOpMapping[1] = {0, 1};
1856 /// origOpToCollapsedOpMapping[2] = {0, 2};
1857 /// origOpToCollapsedOpMapping[3] = {1, 0};
1858 /// origOpToCollapsedOpMapping[4] = {1, 1};
1859 /// ```
1860 ///
1861 ArrayRef<std::pair<int64_t, unsigned>> getOrigOpToCollapsedOpMapping() const {
1862 return origOpToCollapsedOpIterationDim;
1863 }
1864
1865 /// Return the collapsed op iteration domain rank.
1866 unsigned getCollapsedOpIterationRank() const {
1867 return collapsedOpToOrigOpIterationDim.size();
1868 }
1869
1870private:
1871 /// Map from the iteration domain index in collapsed op to the iteration
1872 /// domain indices in the original op.
1873 SmallVector<ReassociationIndices> collapsedOpToOrigOpIterationDim;
1874
1875 /// Map from iteration domain index in the original op to the iteration domain
1876 /// index in the collapsed op.
1877 SmallVector<std::pair<int64_t, unsigned>> origOpToCollapsedOpIterationDim;
1878};
1879} // namespace
1880
1881/// Get the iterator types for the collapsed operation given the original
1882/// iterator types and collapsed dimensions.
1883static SmallVector<utils::IteratorType>
1884getCollapsedOpIteratorTypes(ArrayRef<utils::IteratorType> iteratorTypes,
1885 const CollapsingInfo &collapsingInfo) {
1886 SmallVector<utils::IteratorType> collapsedIteratorTypes;
1887 for (ReassociationIndicesRef foldedIterDims :
1888 collapsingInfo.getCollapsedOpToOrigOpMapping()) {
1889 assert(!foldedIterDims.empty() &&
1890 "reassociation indices expected to have non-empty sets");
1891 // Just pick the iterator type of the first folded dim. Pre-condition checks
1892 // expected to have checked that iterator types of all folded dimensions are
1893 // the same.
1894 collapsedIteratorTypes.push_back(iteratorTypes[foldedIterDims[0]]);
1895 }
1896 return collapsedIteratorTypes;
1897}
1898
1899/// Compute the indexing map in the collapsed op that corresponds to the given
1900/// `indexingMap` of the original operation.
1901static AffineMap
1902getCollapsedOpIndexingMap(AffineMap indexingMap,
1903 const CollapsingInfo &collapsingInfo) {
1904 MLIRContext *context = indexingMap.getContext();
1905 assert(indexingMap.isProjectedPermutation() &&
1906 "expected indexing map to be projected permutation");
1907 SmallVector<AffineExpr> resultExprs;
1908 auto origOpToCollapsedOpMapping =
1909 collapsingInfo.getOrigOpToCollapsedOpMapping();
1910 for (auto expr : indexingMap.getResults()) {
1911 unsigned dim = cast<AffineDimExpr>(expr).getPosition();
1912 // If the dim is not the first of the collapsed dim, do nothing.
1913 if (origOpToCollapsedOpMapping[dim].second != 0)
1914 continue;
1915 // The next n-dims are guaranteed to be collapsed. So just use the
1916 // iteration dimension of the collapsed op.
1917 resultExprs.push_back(
1918 getAffineDimExpr(origOpToCollapsedOpMapping[dim].first, context));
1919 }
1920 return AffineMap::get(collapsingInfo.getCollapsedOpIterationRank(), 0,
1921 resultExprs, context);
1922}
1923
1924/// Return the `reassociation` indices to use to collapse the operand when the
1925/// iteration space of a generic op is collapsed.
1926static SmallVector<ReassociationIndices>
1927getOperandReassociation(AffineMap indexingMap,
1928 const CollapsingInfo &collapsingInfo) {
1929 unsigned counter = 0;
1930 SmallVector<ReassociationIndices> operandReassociation;
1931 auto origOpToCollapsedOpMapping =
1932 collapsingInfo.getOrigOpToCollapsedOpMapping();
1933 auto collapsedOpToOrigOpMapping =
1934 collapsingInfo.getCollapsedOpToOrigOpMapping();
1935 while (counter < indexingMap.getNumResults()) {
1936 unsigned dim =
1937 cast<AffineDimExpr>(indexingMap.getResult(counter)).getPosition();
1938 // This is the start of a collapsed dimensions of the iteration that
1939 // is gauranteed to be preserved in the indexing map. The number of folded
1940 // dims is obtained from the collapsed op to original op mapping.
1941 unsigned numFoldedDims =
1942 collapsedOpToOrigOpMapping[origOpToCollapsedOpMapping[dim].first]
1943 .size();
1944 if (origOpToCollapsedOpMapping[dim].second == 0) {
1945 auto range = llvm::seq<unsigned>(counter, counter + numFoldedDims);
1946 operandReassociation.emplace_back(range.begin(), range.end());
1947 }
1948 counter += numFoldedDims;
1949 }
1950 return operandReassociation;
1951}
1952
1953/// Get the new value to use for a given `OpOperand` in the collapsed operation.
1954static Value getCollapsedOpOperand(Location loc, LinalgOp op,
1955 OpOperand *opOperand,
1956 const CollapsingInfo &collapsingInfo,
1957 OpBuilder &builder) {
1958 AffineMap indexingMap = op.getMatchingIndexingMap(opOperand);
1959 SmallVector<ReassociationIndices> operandReassociation =
1960 getOperandReassociation(indexingMap, collapsingInfo);
1961
1962 // If the number of entries in the reassociation for the operand is same as
1963 // the number of results of the indexing map, then nothing to do for this
1964 // operand.
1965 Value operand = opOperand->get();
1966 if (operandReassociation.size() == indexingMap.getNumResults())
1967 return operand;
1968
1969 // Insert a reshape to collapse the dimensions.
1970 if (isa<MemRefType>(operand.getType())) {
1971 return memref::CollapseShapeOp::create(builder, loc, operand,
1972 operandReassociation)
1973 .getResult();
1974 }
1975 return tensor::CollapseShapeOp::create(builder, loc, operand,
1976 operandReassociation)
1977 .getResult();
1978}
1979
1980/// Modify the `linalg.index` operations in the original generic op, to its
1981/// value in the collapsed operation.
1982static void generateCollapsedIndexingRegion(
1983 Location loc, Block *block, const CollapsingInfo &collapsingInfo,
1984 ArrayRef<OpFoldResult> loopRange, RewriterBase &rewriter) {
1985 OpBuilder::InsertionGuard g(rewriter);
1986 rewriter.setInsertionPointToStart(block);
1987
1988 // Collect all the original index ops.
1989 auto indexOps = llvm::to_vector(block->getOps<linalg::IndexOp>());
1990
1991 // For each folded dimension list resolve the original induction variable
1992 // values in terms of the folded dimension induction variable.
1993 // i_{folded} = (i_0 * d1 + i1) * d2 + i2.
1994 // can be inverted to
1995 // i2 = i_{folded} % d2
1996 // i1 = (i_{folded} / d2) % d1
1997 // i0 = i_{folded} / (d1 * d2)
1998 llvm::DenseMap<unsigned, Value> indexReplacementVals;
1999 for (auto foldedDims :
2000 enumerate(collapsingInfo.getCollapsedOpToOrigOpMapping())) {
2001 ReassociationIndicesRef foldedDimsRef(foldedDims.value());
2002 Value newIndexVal =
2003 linalg::IndexOp::create(rewriter, loc, foldedDims.index());
2004 for (auto dim : llvm::reverse(foldedDimsRef.drop_front())) {
2005 Value loopDim =
2006 getValueOrCreateConstantIndexOp(rewriter, loc, loopRange[dim]);
2007 indexReplacementVals[dim] =
2008 rewriter.createOrFold<arith::RemSIOp>(loc, newIndexVal, loopDim);
2009 newIndexVal =
2010 rewriter.createOrFold<arith::DivSIOp>(loc, newIndexVal, loopDim);
2011 }
2012 indexReplacementVals[foldedDims.value().front()] = newIndexVal;
2013 }
2014
2015 for (auto indexOp : indexOps) {
2016 auto dim = indexOp.getDim();
2017 rewriter.replaceOp(indexOp, indexReplacementVals[dim]);
2018 }
2019}
2020
2021static void collapseOperandsAndResults(LinalgOp op,
2022 const CollapsingInfo &collapsingInfo,
2023 RewriterBase &rewriter,
2024 SmallVectorImpl<Value> &inputOperands,
2025 SmallVectorImpl<Value> &outputOperands,
2026 SmallVectorImpl<Type> &resultTypes) {
2027 Location loc = op->getLoc();
2028 inputOperands =
2029 llvm::map_to_vector(op.getDpsInputOperands(), [&](OpOperand *opOperand) {
2030 return getCollapsedOpOperand(loc, op, opOperand, collapsingInfo,
2031 rewriter);
2032 });
2033
2034 // Get the output operands and result types.
2035 resultTypes.reserve(op.getNumDpsInits());
2036 outputOperands.reserve(op.getNumDpsInits());
2037 for (OpOperand &output : op.getDpsInitsMutable()) {
2038 Value newOutput =
2039 getCollapsedOpOperand(loc, op, &output, collapsingInfo, rewriter);
2040 outputOperands.push_back(newOutput);
2041 // If the op has "buffer semantics", then the init operands are ranked
2042 // memrefs and the op has no results.
2043 if (!op.hasPureBufferSemantics())
2044 resultTypes.push_back(newOutput.getType());
2045 }
2046}
2047
2048/// Clone a `LinalgOp` to a collapsed version of same name
2049template <typename OpTy>
2050static OpTy cloneToCollapsedOp(RewriterBase &rewriter, OpTy origOp,
2051 const CollapsingInfo &collapsingInfo) {
2052 return nullptr;
2053}
2054
2055/// Collapse any `LinalgOp` that does not require any specialization such as
2056/// indexing_maps, iterator_types, etc.
2057template <>
2058LinalgOp cloneToCollapsedOp<LinalgOp>(RewriterBase &rewriter, LinalgOp origOp,
2059 const CollapsingInfo &collapsingInfo) {
2060 SmallVector<Value> inputOperands, outputOperands;
2061 SmallVector<Type> resultTypes;
2062 collapseOperandsAndResults(origOp, collapsingInfo, rewriter, inputOperands,
2063 outputOperands, resultTypes);
2064
2065 return clone(
2066 rewriter, origOp, resultTypes,
2067 llvm::to_vector(llvm::concat<Value>(inputOperands, outputOperands)));
2068}
2069
2070/// Collapse a `GenericOp`
2071template <>
2072GenericOp cloneToCollapsedOp<GenericOp>(RewriterBase &rewriter,
2073 GenericOp origOp,
2074 const CollapsingInfo &collapsingInfo) {
2075 SmallVector<Value> inputOperands, outputOperands;
2076 SmallVector<Type> resultTypes;
2077 collapseOperandsAndResults(origOp, collapsingInfo, rewriter, inputOperands,
2078 outputOperands, resultTypes);
2079 SmallVector<AffineMap> indexingMaps(
2080 llvm::map_range(origOp.getIndexingMapsArray(), [&](AffineMap map) {
2081 return getCollapsedOpIndexingMap(map, collapsingInfo);
2082 }));
2083
2084 SmallVector<utils::IteratorType> iteratorTypes(getCollapsedOpIteratorTypes(
2085 origOp.getIteratorTypesArray(), collapsingInfo));
2086
2087 GenericOp collapsedOp = linalg::GenericOp::create(
2088 rewriter, origOp.getLoc(), resultTypes, inputOperands, outputOperands,
2089 indexingMaps, iteratorTypes,
2090 [](OpBuilder &builder, Location loc, ValueRange args) {});
2091 Block *origOpBlock = &origOp->getRegion(0).front();
2092 Block *collapsedOpBlock = &collapsedOp->getRegion(0).front();
2093 rewriter.mergeBlocks(origOpBlock, collapsedOpBlock,
2094 collapsedOpBlock->getArguments());
2095 return collapsedOp;
2096}
2097
2098/// Collapse a `BroadcastOp` with a 0-D input into the single flattened
2099/// dimension (`dimensions = [0]`).
2100template <>
2101BroadcastOp
2102cloneToCollapsedOp<BroadcastOp>(RewriterBase &rewriter, BroadcastOp origOp,
2103 const CollapsingInfo &collapsingInfo) {
2104 assert(origOp.getInput().getType().getRank() == 0 && "expected a 0-D input");
2105
2106 SmallVector<Value> inputOperands, outputOperands;
2107 SmallVector<Type> resultTypes;
2108 collapseOperandsAndResults(origOp, collapsingInfo, rewriter, inputOperands,
2109 outputOperands, resultTypes);
2110
2111 SmallVector<int64_t> newDimensions = {0};
2112 return BroadcastOp::create(rewriter, origOp.getLoc(), inputOperands[0],
2113 outputOperands[0], newDimensions);
2114}
2115
2116static LinalgOp createCollapsedOp(LinalgOp op,
2117 const CollapsingInfo &collapsingInfo,
2118 RewriterBase &rewriter) {
2119 if (GenericOp genericOp = dyn_cast<GenericOp>(op.getOperation())) {
2120 return cloneToCollapsedOp(rewriter, genericOp, collapsingInfo);
2121 }
2122 if (BroadcastOp broadcastOp = dyn_cast<BroadcastOp>(op.getOperation())) {
2123 return cloneToCollapsedOp(rewriter, broadcastOp, collapsingInfo);
2124 }
2125 return cloneToCollapsedOp(rewriter, op, collapsingInfo);
2126}
2127
2128/// Implementation of fusion with reshape operation by collapsing dimensions.
2129FailureOr<CollapseResult> mlir::linalg::collapseOpIterationDims(
2130 LinalgOp op, ArrayRef<ReassociationIndices> foldedIterationDims,
2131 RewriterBase &rewriter) {
2132 // Bail on trivial no-op cases.
2133 if (op.getNumLoops() <= 1 || foldedIterationDims.empty() ||
2134 llvm::all_of(foldedIterationDims, [](ReassociationIndicesRef foldedDims) {
2135 return foldedDims.size() <= 1;
2136 }))
2137 return failure();
2138
2139 CollapsingInfo collapsingInfo;
2140 if (failed(
2141 collapsingInfo.initialize(op.getNumLoops(), foldedIterationDims))) {
2142 return rewriter.notifyMatchFailure(
2143 op, "illegal to collapse specified dimensions");
2144 }
2145
2146 bool hasPureBufferSemantics = op.hasPureBufferSemantics();
2147 if (hasPureBufferSemantics &&
2148 !llvm::all_of(op->getOpOperands(), [&](OpOperand &opOperand) -> bool {
2149 MemRefType memRefToCollapse =
2150 dyn_cast<MemRefType>(opOperand.get().getType());
2151 if (!memRefToCollapse)
2152 return true;
2153
2154 AffineMap indexingMap = op.getMatchingIndexingMap(&opOperand);
2155 SmallVector<ReassociationIndices> operandReassociation =
2156 getOperandReassociation(indexingMap, collapsingInfo);
2157 return memref::CollapseShapeOp::isGuaranteedCollapsible(
2158 memRefToCollapse, operandReassociation);
2159 }))
2160 return rewriter.notifyMatchFailure(op,
2161 "memref is not guaranteed collapsible");
2162
2163 // Bail on non-canonical ranges.
2164 SmallVector<Range> loopRanges = op.createLoopRanges(rewriter, op.getLoc());
2165 auto opFoldIsConstantValue = [](OpFoldResult ofr, int64_t value) {
2166 if (auto attr = llvm::dyn_cast_if_present<Attribute>(ofr))
2167 return cast<IntegerAttr>(attr).getInt() == value;
2168 llvm::APInt actual;
2169 return matchPattern(cast<Value>(ofr), m_ConstantInt(&actual)) &&
2170 actual.getSExtValue() == value;
2171 };
2172 if (!llvm::all_of(loopRanges, [&](Range range) {
2173 return opFoldIsConstantValue(range.offset, 0) &&
2174 opFoldIsConstantValue(range.stride, 1);
2175 })) {
2176 return rewriter.notifyMatchFailure(
2177 op, "expected all loop ranges to have zero start and unit stride");
2178 }
2179
2180 LinalgOp collapsedOp = createCollapsedOp(op, collapsingInfo, rewriter);
2181
2182 Location loc = op->getLoc();
2183 SmallVector<OpFoldResult> loopBound =
2184 llvm::map_to_vector(loopRanges, [](Range range) { return range.size; });
2185
2186 if (collapsedOp.hasIndexSemantics()) {
2187 // Collect the loop range of the generic op.
2188 OpBuilder::InsertionGuard g(rewriter);
2189 rewriter.setInsertionPoint(collapsedOp);
2190 generateCollapsedIndexingRegion(loc, &collapsedOp->getRegion(0).front(),
2191 collapsingInfo, loopBound, rewriter);
2192 }
2193
2194 // Insert expanding reshape for the result to get back the original result
2195 // type.
2196 SmallVector<Value> results;
2197 for (const auto &originalResult : llvm::enumerate(op->getResults())) {
2198 Value collapsedOpResult = collapsedOp->getResult(originalResult.index());
2199 auto originalResultType =
2200 cast<ShapedType>(originalResult.value().getType());
2201 auto collapsedOpResultType = cast<ShapedType>(collapsedOpResult.getType());
2202 if (collapsedOpResultType.getRank() != originalResultType.getRank()) {
2203 AffineMap indexingMap =
2204 op.getIndexingMapMatchingResult(originalResult.value());
2205 SmallVector<ReassociationIndices> reassociation =
2206 getOperandReassociation(indexingMap, collapsingInfo);
2207 assert(
2208 indexingMap.isProjectedPermutation() &&
2209 "Expected indexing map to be a projected permutation for collapsing");
2210 SmallVector<OpFoldResult> resultShape =
2211 applyPermutationMap(indexingMap, ArrayRef(loopBound));
2212 Value result;
2213 if (isa<MemRefType>(collapsedOpResult.getType())) {
2214 result = memref::ExpandShapeOp::create(
2215 rewriter, loc, originalResultType, collapsedOpResult, reassociation,
2216 resultShape);
2217 } else {
2218 result = tensor::ExpandShapeOp::create(
2219 rewriter, loc, originalResultType, collapsedOpResult, reassociation,
2220 resultShape);
2221 }
2222 results.push_back(result);
2223 } else {
2224 results.push_back(collapsedOpResult);
2225 }
2226 }
2227 return CollapseResult{results, collapsedOp};
2228}
2229
2230namespace {
2231
2232/// Pattern to fuse a tensor.expand_shape op with its consumer generic op by
2233/// contracting dimensions of the loop.
2234class FoldWithProducerReshapeOpByCollapsing
2235 : public OpRewritePattern<GenericOp> {
2236public:
2237 // TODO : support fusion with all linalg ops, not just generic.
2238 FoldWithProducerReshapeOpByCollapsing(MLIRContext *context,
2239 ControlFusionFn foldReshapes,
2240 PatternBenefit benefit = 1)
2241 : OpRewritePattern<GenericOp>(context, benefit),
2242 controlFoldingReshapes(std::move(foldReshapes)) {}
2243
2244 LogicalResult matchAndRewrite(GenericOp genericOp,
2245 PatternRewriter &rewriter) const override {
2246 for (OpOperand &opOperand : genericOp->getOpOperands()) {
2247 tensor::ExpandShapeOp reshapeOp =
2248 opOperand.get().getDefiningOp<tensor::ExpandShapeOp>();
2249 if (!reshapeOp)
2250 continue;
2251
2252 SmallVector<ReassociationIndices> collapsableIterationDims =
2253 getCollapsableIterationSpaceDims(genericOp, &opOperand,
2254 reshapeOp.getReassociationIndices());
2255 if (collapsableIterationDims.empty() ||
2256 !controlFoldingReshapes(&opOperand)) {
2257 continue;
2258 }
2259
2260 std::optional<CollapseResult> collapseResult = collapseOpIterationDims(
2261 genericOp, collapsableIterationDims, rewriter);
2262 if (!collapseResult) {
2263 return rewriter.notifyMatchFailure(
2264 genericOp, "failed to do the fusion by collapsing transformation");
2265 }
2266
2267 rewriter.replaceOp(genericOp, collapseResult->results);
2268 return success();
2269 }
2270 return failure();
2271 }
2272
2273private:
2274 ControlFusionFn controlFoldingReshapes;
2275};
2276
2277/// Pattern to fold a tensor.collapse_shape op with its producer generic op
2278/// by expanding the dimensionality of the loop in the producer op.
2279struct FoldReshapeWithGenericOpByCollapsing
2280 : public OpRewritePattern<tensor::CollapseShapeOp> {
2281
2282 FoldReshapeWithGenericOpByCollapsing(MLIRContext *context,
2283 ControlFusionFn foldReshapes,
2284 PatternBenefit benefit = 1)
2285 : OpRewritePattern<tensor::CollapseShapeOp>(context, benefit),
2286 controlFoldingReshapes(std::move(foldReshapes)) {}
2287
2288 LogicalResult matchAndRewrite(tensor::CollapseShapeOp reshapeOp,
2289 PatternRewriter &rewriter) const override {
2290 // Fold only if all constraints of fusing with reshape by collapsing are
2291 // met.
2292 auto producerResult = dyn_cast<OpResult>(reshapeOp.getSrc());
2293 if (!producerResult) {
2294 return rewriter.notifyMatchFailure(reshapeOp,
2295 "source not produced by an operation");
2296 }
2297
2298 // TODO : support fusion with all linalg producers, not just generic.
2299 auto producer = dyn_cast<GenericOp>(producerResult.getOwner());
2300 if (!producer) {
2301 return rewriter.notifyMatchFailure(reshapeOp,
2302 "producer not a generic op");
2303 }
2304
2305 SmallVector<ReassociationIndices> collapsableIterationDims =
2307 producer,
2308 producer.getDpsInitOperand(producerResult.getResultNumber()),
2309 reshapeOp.getReassociationIndices());
2310 if (collapsableIterationDims.empty()) {
2311 return rewriter.notifyMatchFailure(
2312 reshapeOp, "failed preconditions of fusion with producer generic op");
2313 }
2314
2315 if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {
2316 return rewriter.notifyMatchFailure(reshapeOp,
2317 "fusion blocked by control function");
2318 }
2319
2320 // Set the insertion point after `producer` because there could be uses
2321 // of `producer` between it and the `tensor.collapse_shape` op.
2322 rewriter.setInsertionPointAfter(producer);
2323 std::optional<CollapseResult> collapseResult =
2324 collapseOpIterationDims(producer, collapsableIterationDims, rewriter);
2325 if (!collapseResult) {
2326 return rewriter.notifyMatchFailure(
2327 producer, "failed to do the fusion by collapsing transformation");
2328 }
2329
2330 rewriter.replaceOp(producer, collapseResult->results);
2331 return success();
2332 }
2333
2334private:
2335 ControlFusionFn controlFoldingReshapes;
2336};
2337
2338/// Computes the collapsed padding information for the given pad operation based
2339/// on the provided collapsed shape and reassociation indices. Returns a
2340/// PadDimInfo containing the low and high padding amounts and the collapsed
2341/// shape for each dimension, or failure if the collapse is not possible.
2342static FailureOr<PadDimInfo>
2343computeCollapsedPadding(tensor::PadOp padOp,
2344 ArrayRef<ReassociationIndices> reassociations,
2345 PatternRewriter &rewriter) {
2346 // If the padding value depends on the index values of the pad operation,
2347 // then it may not be valid to collapse the dimensions, since it will change
2348 // the index values on which the padding value depends. This is not currently
2349 // supported by the pad collapsing patterns, but it could be implemented
2350 // similarly to the collapsing of linalg.generic ops with linalg.index ops in
2351 // the body, as is done in `generateCollapsedIndexingRegion`.
2352 if (!padOp.getConstantPaddingValue())
2353 return failure();
2354
2355 // Collapsed dimensions cannot have padding because this can produce strided
2356 // padding that isn't representable by a tensor.pad op. There are some special
2357 // cases where it is possible (like collapsing unit dims), but supporting
2358 // these cases is NYI, so disallow it for now.
2359 ArrayRef<int64_t> low = padOp.getStaticLow();
2360 ArrayRef<int64_t> high = padOp.getStaticHigh();
2361 for (auto [idx, reInd] : llvm::enumerate(reassociations)) {
2362 for (int64_t dim : reInd) {
2363 if ((low[dim] != 0 || high[dim] != 0) && reInd.size() != 1)
2364 return failure();
2365 }
2366 }
2367
2368 // Initialize padding values for collapsed tensors with zeros
2369 ArrayRef<int64_t> expandedPaddedShape = padOp.getType().getShape();
2370 PadDimInfo padDimInfo;
2371 padDimInfo.lowPad.assign(reassociations.size(), rewriter.getIndexAttr(0));
2372 padDimInfo.highPad.assign(reassociations.size(), rewriter.getIndexAttr(0));
2373
2374 // Update padding for dimensions that are not being collapsed, and compute
2375 // the collapsed padded shape.
2376 SmallVector<OpFoldResult> mixedLowPad(padOp.getMixedLowPad());
2377 SmallVector<OpFoldResult> mixedHighPad(padOp.getMixedHighPad());
2378 for (auto [idx, reInd] : llvm::enumerate(reassociations)) {
2379 if (reInd.size() == 1) {
2380 padDimInfo.lowPad[idx] = mixedLowPad[reInd[0]];
2381 padDimInfo.highPad[idx] = mixedHighPad[reInd[0]];
2382 }
2383 SaturatedInteger collapsedSize = SaturatedInteger::wrap(1);
2384 for (int64_t dim : reInd) {
2385 collapsedSize =
2386 collapsedSize * SaturatedInteger::wrap(expandedPaddedShape[dim]);
2387 }
2388 padDimInfo.paddedShape.push_back(collapsedSize.asInteger());
2389 }
2390
2391 return padDimInfo;
2392}
2393
2394class FoldPadWithProducerReshapeOpByCollapsing
2395 : public OpRewritePattern<tensor::PadOp> {
2396public:
2397 FoldPadWithProducerReshapeOpByCollapsing(MLIRContext *context,
2398 ControlFusionFn foldReshapes,
2399 PatternBenefit benefit = 1)
2400 : OpRewritePattern<tensor::PadOp>(context, benefit),
2401 controlFoldingReshapes(std::move(foldReshapes)) {}
2402
2403 LogicalResult matchAndRewrite(tensor::PadOp padOp,
2404 PatternRewriter &rewriter) const override {
2405 tensor::ExpandShapeOp reshapeOp =
2406 padOp.getSource().getDefiningOp<tensor::ExpandShapeOp>();
2407 if (!reshapeOp)
2408 return failure();
2409
2410 if (!controlFoldingReshapes(&padOp.getSourceMutable())) {
2411 return rewriter.notifyMatchFailure(padOp,
2412 "fusion blocked by control function");
2413 }
2414
2415 SmallVector<ReassociationIndices> reassociations =
2416 reshapeOp.getReassociationIndices();
2417 FailureOr<PadDimInfo> maybeCollapsedPadding =
2418 computeCollapsedPadding(padOp, reassociations, rewriter);
2419 if (failed(maybeCollapsedPadding))
2420 return failure();
2421 PadDimInfo &collapsedPadding = maybeCollapsedPadding.value();
2422
2423 SmallVector<OpFoldResult> expandedPaddedSizes =
2424 reshapeOp.getMixedOutputShape();
2425 AffineExpr d0, d1, d2;
2426 bindDims(rewriter.getContext(), d0, d1, d2);
2427 auto addMap = AffineMap::get(3, 0, {d0 + d1 + d2});
2428 Location loc = reshapeOp->getLoc();
2429 for (auto [reInd, l, h] :
2430 llvm::zip_equal(reassociations, collapsedPadding.lowPad,
2431 collapsedPadding.highPad)) {
2432 if (reInd.size() == 1) {
2433 expandedPaddedSizes[reInd[0]] = affine::makeComposedFoldedAffineApply(
2434 rewriter, loc, addMap, {l, h, expandedPaddedSizes[reInd[0]]});
2435 }
2436 }
2437
2438 RankedTensorType collapsedPaddedType =
2439 padOp.getType().clone(collapsedPadding.paddedShape);
2440 auto newPadOp = tensor::PadOp::create(
2441 rewriter, loc, collapsedPaddedType, reshapeOp.getSrc(),
2442 collapsedPadding.lowPad, collapsedPadding.highPad,
2443 padOp.getConstantPaddingValue(), padOp.getNofold());
2444
2445 rewriter.replaceOpWithNewOp<tensor::ExpandShapeOp>(
2446 padOp, padOp.getResultType(), newPadOp.getResult(), reassociations,
2447 expandedPaddedSizes);
2448
2449 return success();
2450 }
2451
2452private:
2453 ControlFusionFn controlFoldingReshapes;
2454};
2455
2456class FoldReshapeWithProducerPadOpByCollapsing
2457 : public OpRewritePattern<tensor::CollapseShapeOp> {
2458public:
2459 FoldReshapeWithProducerPadOpByCollapsing(MLIRContext *context,
2460 ControlFusionFn foldReshapes,
2461 PatternBenefit benefit = 1)
2462 : OpRewritePattern<tensor::CollapseShapeOp>(context, benefit),
2463 controlFoldingReshapes(std::move(foldReshapes)) {}
2464
2465 LogicalResult matchAndRewrite(tensor::CollapseShapeOp reshapeOp,
2466 PatternRewriter &rewriter) const override {
2467 tensor::PadOp padOp = reshapeOp.getSrc().getDefiningOp<tensor::PadOp>();
2468 if (!padOp)
2469 return failure();
2470
2471 if (!controlFoldingReshapes(&reshapeOp.getSrcMutable())) {
2472 return rewriter.notifyMatchFailure(padOp,
2473 "fusion blocked by control function");
2474 }
2475
2476 SmallVector<ReassociationIndices> reassociations =
2477 reshapeOp.getReassociationIndices();
2478 RankedTensorType collapsedPaddedType = reshapeOp.getResultType();
2479 FailureOr<PadDimInfo> maybeCollapsedPadding =
2480 computeCollapsedPadding(padOp, reassociations, rewriter);
2481 if (failed(maybeCollapsedPadding))
2482 return failure();
2483 PadDimInfo &collapsedPadding = maybeCollapsedPadding.value();
2484
2485 Location loc = reshapeOp->getLoc();
2486 auto newCollapseOp = tensor::CollapseShapeOp::create(
2487 rewriter, loc, padOp.getSource(), reassociations);
2488
2489 auto newPadOp = tensor::PadOp::create(
2490 rewriter, loc, collapsedPaddedType, newCollapseOp.getResult(),
2491 collapsedPadding.lowPad, collapsedPadding.highPad,
2492 padOp.getConstantPaddingValue(), padOp.getNofold());
2493
2494 rewriter.replaceOp(reshapeOp, newPadOp.getResult());
2495 return success();
2496 }
2497
2498private:
2499 ControlFusionFn controlFoldingReshapes;
2500};
2501
2502/// Pattern to collapse dimensions.
2503template <typename LinalgType>
2504class CollapseLinalgDimensions : public OpRewritePattern<LinalgType> {
2505public:
2506 CollapseLinalgDimensions(MLIRContext *context,
2507 GetCollapsableDimensionsFn collapseDimensions,
2508 PatternBenefit benefit = 1)
2509 : OpRewritePattern<LinalgType>(context, benefit),
2510 controlCollapseDimension(std::move(collapseDimensions)) {}
2511
2512 LogicalResult matchAndRewrite(LinalgType op,
2513 PatternRewriter &rewriter) const override {
2514 SmallVector<ReassociationIndices> collapsableIterationDims =
2515 controlCollapseDimension(op);
2516 if (collapsableIterationDims.empty())
2517 return failure();
2518
2519 // Check if the specified list of dimensions to collapse is a valid list.
2520 if (!areDimSequencesPreserved(op.getIndexingMapsArray(),
2521 collapsableIterationDims)) {
2522 return rewriter.notifyMatchFailure(
2523 op, "specified dimensions cannot be collapsed");
2524 }
2525
2526 std::optional<CollapseResult> collapseResult =
2527 collapseOpIterationDims(op, collapsableIterationDims, rewriter);
2528 if (!collapseResult) {
2529 return rewriter.notifyMatchFailure(op, "failed to collapse dimensions");
2530 }
2531 rewriter.replaceOp(op, collapseResult->results);
2532 return success();
2533 }
2534
2535private:
2536 GetCollapsableDimensionsFn controlCollapseDimension;
2537};
2538
2539} // namespace
2540
2541//===---------------------------------------------------------------------===//
2542// Methods and patterns that fuse constants with linalg.generic operations.
2543//===---------------------------------------------------------------------===//
2544
2545namespace {
2546/// Pattern to fold a generic op with a splat constant/scalar constant. Does not
2547/// handle cases where the constant is not single-valued.
2548class FoldScalarOrSplatConstant : public OpRewritePattern<GenericOp> {
2549public:
2550 FoldScalarOrSplatConstant(MLIRContext *context, PatternBenefit benefit = 1)
2551 : OpRewritePattern<GenericOp>(context, benefit) {}
2552
2553 LogicalResult matchAndRewrite(GenericOp genericOp,
2554 PatternRewriter &rewriter) const override {
2555 if (!genericOp.hasPureTensorSemantics())
2556 return failure();
2557 for (OpOperand *opOperand : genericOp.getDpsInputOperands()) {
2558 Operation *def = opOperand->get().getDefiningOp();
2559 TypedAttr constantAttr;
2560 auto isScalarOrSplatConstantOp = [&constantAttr](Operation *def) -> bool {
2561 {
2562 DenseElementsAttr splatAttr;
2563 if (matchPattern(def, m_Constant<DenseElementsAttr>(&splatAttr)) &&
2564 splatAttr.isSplat() &&
2565 splatAttr.getType().getElementType().isIntOrFloat()) {
2566 constantAttr = splatAttr.getSplatValue<TypedAttr>();
2567 return true;
2568 }
2569 }
2570 {
2571 IntegerAttr intAttr;
2572 if (matchPattern(def, m_Constant<IntegerAttr>(&intAttr))) {
2573 constantAttr = intAttr;
2574 return true;
2575 }
2576 }
2577 {
2578 FloatAttr floatAttr;
2579 if (matchPattern(def, m_Constant<FloatAttr>(&floatAttr))) {
2580 constantAttr = floatAttr;
2581 return true;
2582 }
2583 }
2584 return false;
2585 };
2586
2587 auto resultValue = dyn_cast<OpResult>(opOperand->get());
2588 if (!def || !resultValue || !isScalarOrSplatConstantOp(def))
2589 continue;
2590
2591 // The operands and the indexing_maps of the fused operation the same as
2592 // the operands and indexing_maps of the generic operations with the
2593 // values at the constant index dropped.
2594 SmallVector<AffineMap> fusedIndexMaps;
2595 SmallVector<Value> fusedOperands;
2596 SmallVector<Location> fusedLocs{genericOp.getLoc()};
2597 fusedIndexMaps.reserve(genericOp->getNumOperands());
2598 fusedOperands.reserve(genericOp.getNumDpsInputs());
2599 fusedLocs.reserve(fusedLocs.size() + genericOp.getNumDpsInputs());
2600 for (OpOperand *inputOperand : genericOp.getDpsInputOperands()) {
2601 if (inputOperand == opOperand)
2602 continue;
2603 Value inputValue = inputOperand->get();
2604 fusedIndexMaps.push_back(
2605 genericOp.getMatchingIndexingMap(inputOperand));
2606 fusedOperands.push_back(inputValue);
2607 fusedLocs.push_back(inputValue.getLoc());
2608 }
2609 for (OpOperand &outputOperand : genericOp.getDpsInitsMutable())
2610 fusedIndexMaps.push_back(
2611 genericOp.getMatchingIndexingMap(&outputOperand));
2612
2613 // Check if the operation shapes to loops map is computable.
2614 if (!inversePermutation(
2615 concatAffineMaps(fusedIndexMaps, rewriter.getContext()))) {
2616 return rewriter.notifyMatchFailure(
2617 genericOp, "fused op loop bound computation failed");
2618 }
2619
2620 // Create a constant scalar value from the splat constant.
2621 Value scalarConstant =
2622 arith::ConstantOp::create(rewriter, def->getLoc(), constantAttr);
2623
2624 SmallVector<Value> outputOperands = genericOp.getOutputs();
2625 auto fusedOp =
2626 GenericOp::create(rewriter, rewriter.getFusedLoc(fusedLocs),
2627 genericOp->getResultTypes(),
2628 /*inputs=*/fusedOperands,
2629 /*outputs=*/outputOperands,
2630 rewriter.getAffineMapArrayAttr(fusedIndexMaps),
2631 genericOp.getIteratorTypes(),
2632 /*doc=*/nullptr,
2633 /*library_call=*/nullptr);
2634
2635 // Map the block argument corresponding to the replaced argument with the
2636 // scalar constant.
2637 Region &region = genericOp->getRegion(0);
2638 Block &entryBlock = *region.begin();
2639 IRMapping mapping;
2640 mapping.map(entryBlock.getArgument(opOperand->getOperandNumber()),
2641 scalarConstant);
2642 Region &fusedRegion = fusedOp->getRegion(0);
2643 rewriter.cloneRegionBefore(region, fusedRegion, fusedRegion.begin(),
2644 mapping);
2645 rewriter.replaceOp(genericOp, fusedOp->getResults());
2646 return success();
2647 }
2648 return failure();
2649 }
2650};
2651
2652} // namespace
2653
2654//===---------------------------------------------------------------------===//
2655// Miscellaneous patterns that help fusion.
2656//===---------------------------------------------------------------------===//
2657
2658namespace {
2659/// Forces `outs` operands of linalg operations to use `tensor.empty` if the
2660/// value of the `outs` operand is not used within the op. This is only
2661/// implemented for `linalg.generic` operations for now, but should hold for all
2662/// linalg structured ops.
2663struct RemoveOutsDependency : public OpRewritePattern<GenericOp> {
2664 using OpRewritePattern<GenericOp>::OpRewritePattern;
2665
2666 LogicalResult matchAndRewrite(GenericOp op,
2667 PatternRewriter &rewriter) const override {
2668 rewriter.startOpModification(op);
2669 bool modifiedOutput = false;
2670 Location loc = op.getLoc();
2671 for (OpOperand &opOperand : op.getDpsInitsMutable()) {
2672 if (!op.payloadUsesValueFromOperand(&opOperand)) {
2673 Value operandVal = opOperand.get();
2674 auto operandType = dyn_cast<RankedTensorType>(operandVal.getType());
2675 if (!operandType)
2676 continue;
2677
2678 // If outs is sparse, leave it to the sparsifier.
2680 continue;
2681
2682 // If outs is already an `empty` operation, nothing to do.
2683 auto definingOp = operandVal.getDefiningOp<tensor::EmptyOp>();
2684 if (definingOp)
2685 continue;
2686 modifiedOutput = true;
2687 SmallVector<OpFoldResult> mixedSizes =
2688 tensor::getMixedSizes(rewriter, loc, operandVal);
2689 Value emptyTensor = tensor::EmptyOp::create(
2690 rewriter, loc, mixedSizes, operandType.getElementType());
2691 op->setOperand(opOperand.getOperandNumber(), emptyTensor);
2692 }
2693 }
2694 if (!modifiedOutput) {
2695 rewriter.cancelOpModification(op);
2696 return failure();
2697 }
2698 rewriter.finalizeOpModification(op);
2699 return success();
2700 }
2701};
2702
2703/// Fold linalg.fill into linalg.generic
2704struct FoldFillWithGenericOp : public OpRewritePattern<GenericOp> {
2705 using OpRewritePattern<GenericOp>::OpRewritePattern;
2706
2707 LogicalResult matchAndRewrite(GenericOp genericOp,
2708 PatternRewriter &rewriter) const override {
2709 if (!genericOp.hasPureTensorSemantics())
2710 return failure();
2711 bool fillFound = false;
2712 Block &payload = genericOp.getRegion().front();
2713 for (OpOperand *opOperand : genericOp.getDpsInputOperands()) {
2714 if (!genericOp.payloadUsesValueFromOperand(opOperand))
2715 continue;
2716 FillOp fillOp = opOperand->get().getDefiningOp<FillOp>();
2717 if (!fillOp)
2718 continue;
2719 fillFound = true;
2720 Value fillVal = fillOp.value();
2721 auto resultType =
2722 cast<RankedTensorType>(fillOp.result().getType()).getElementType();
2723 Value convertedVal =
2724 convertScalarToDtype(rewriter, fillOp.getLoc(), fillVal, resultType,
2725 /*isUnsignedCast =*/false);
2726 rewriter.replaceAllUsesWith(
2727 payload.getArgument(opOperand->getOperandNumber()), convertedVal);
2728 }
2729 return success(fillFound);
2730 }
2731};
2732} // namespace
2733
2735 RewritePatternSet &patterns,
2736 const ControlFusionFn &controlFoldingReshapes) {
2737 patterns.add<FoldReshapeWithGenericOpByExpansion>(patterns.getContext(),
2738 controlFoldingReshapes);
2739 patterns.add<FoldPadWithProducerReshapeOpByExpansion>(patterns.getContext(),
2740 controlFoldingReshapes);
2741 patterns.add<FoldReshapeWithProducerPadOpByExpansion>(patterns.getContext(),
2742 controlFoldingReshapes);
2743 patterns.add<FoldWithProducerReshapeOpByExpansion>(patterns.getContext(),
2744 controlFoldingReshapes);
2745}
2746
2748 RewritePatternSet &patterns,
2749 const ControlFusionFn &controlFoldingReshapes) {
2750 patterns.add<FoldWithProducerReshapeOpByCollapsing>(patterns.getContext(),
2751 controlFoldingReshapes);
2752 patterns.add<FoldPadWithProducerReshapeOpByCollapsing>(
2753 patterns.getContext(), controlFoldingReshapes);
2754 patterns.add<FoldReshapeWithProducerPadOpByCollapsing>(
2755 patterns.getContext(), controlFoldingReshapes);
2756 patterns.add<FoldReshapeWithGenericOpByCollapsing>(patterns.getContext(),
2757 controlFoldingReshapes);
2758}
2759
2761 RewritePatternSet &patterns,
2762 const ControlFusionFn &controlElementwiseOpsFusion) {
2763 auto *context = patterns.getContext();
2764 patterns.add<FuseElementwiseOps>(context, controlElementwiseOpsFusion);
2765 patterns.add<FoldFillWithGenericOp, FoldScalarOrSplatConstant,
2766 RemoveOutsDependency>(context);
2767 // Add the patterns that clean up dead operands and results.
2769}
2770
2772 RewritePatternSet &patterns) {
2773 patterns.add<SplitElementwiseOpWithConcatInputs>(patterns.getContext());
2774}
2775
2777 RewritePatternSet &patterns,
2778 const GetCollapsableDimensionsFn &controlCollapseDimensions) {
2779 patterns.add<CollapseLinalgDimensions<linalg::GenericOp>,
2780 CollapseLinalgDimensions<linalg::CopyOp>>(
2781 patterns.getContext(), controlCollapseDimensions);
2782}
2783
2784//===---------------------------------------------------------------------===//
2785// Passes
2786//===---------------------------------------------------------------------===//
2787
2788namespace {
2789
2790/// Pass that fuses generic ops on tensors. Used only for testing.
2791// TODO(ravishankarm): This pass is to be deprecated. The efficacy of the
2792// patterns added here heavily depends on the cost function used. Having an
2793// opinionated pass of this form is not recommended. Deprecate this pass in
2794// favor of test passes that check the functionality of each of the patterns
2795// added here individually.
2796struct LinalgElementwiseOpFusionPass
2797 : public impl::LinalgElementwiseOpFusionPassBase<
2798 LinalgElementwiseOpFusionPass> {
2799 using impl::LinalgElementwiseOpFusionPassBase<
2800 LinalgElementwiseOpFusionPass>::LinalgElementwiseOpFusionPassBase;
2801 void runOnOperation() override {
2802 Operation *op = getOperation();
2803 MLIRContext *context = op->getContext();
2804 RewritePatternSet patterns(context);
2805
2806 // Add folding with reshape by expansion patterns.
2807 ControlFusionFn defaultControlFn = [](OpOperand *fusedOperand) {
2808 Operation *producer = fusedOperand->get().getDefiningOp();
2809 return producer && producer->hasOneUse();
2810 };
2811
2812 // Add elementwise op fusion patterns.
2814 populateElementwiseOpsFusionPatterns(patterns, defaultControlFn);
2815 populateFoldReshapeOpsByExpansionPatterns(patterns, defaultControlFn);
2817
2818 // General canonicalization patterns.
2819 affine::AffineApplyOp::getCanonicalizationPatterns(patterns, context);
2820 GenericOp::getCanonicalizationPatterns(patterns, context);
2821 tensor::ExpandShapeOp::getCanonicalizationPatterns(patterns, context);
2822 tensor::CollapseShapeOp::getCanonicalizationPatterns(patterns, context);
2823 context->getLoadedDialect<LinalgDialect>()->getCanonicalizationPatterns(
2824 patterns);
2825
2826 // Add constant folding patterns.
2827 populateConstantFoldLinalgOperations(patterns, defaultControlFn);
2828
2829 // Use TopDownTraversal for compile time reasons.
2830 (void)applyPatternsGreedily(op, std::move(patterns),
2831 GreedyRewriteConfig().setUseTopDownTraversal());
2832 }
2833};
2834
2835} // namespace
return success()
static bool isOpOperandCanBeDroppedAfterFusedLinalgs(GenericOp producer, GenericOp consumer, ArrayRef< OpOperand * > opOperandsToIgnore)
static AffineMap getIndexingMapOfProducerOperandsInCoordinatesOfFusedOp(OpOperand *producerOpOperand, AffineMap producerResultIndexMap, AffineMap fusedConsumerArgIndexMap)
Append to fusedOpIndexingMapAttrs the indexing maps for the operands of the producer to use in the fu...
static SmallVector< ReassociationIndices > getCollapsableIterationSpaceDims(GenericOp genericOp, OpOperand *fusableOperand, ArrayRef< ReassociationIndices > reassociation)
ArrayRef< ReassociationIndices > getCollapsedOpToOrigOpMapping() const
Return mapping from collapsed loop domain to original loop domain.
LogicalResult initialize(unsigned origNumLoops, ArrayRef< ReassociationIndices > foldedIterationDims)
static std::tuple< SmallVector< OpFoldResult >, RankedTensorType > getExpandedShapeAndType(RankedTensorType originalType, AffineMap indexingMap, const ExpansionInfo &expansionInfo)
Return the shape and type of the operand/result to use in the expanded op given the type in the origi...
static void updateExpandedGenericOpRegion(PatternRewriter &rewriter, Location loc, Region &fusedRegion, const ExpansionInfo &expansionInfo)
Update the body of an expanded linalg operation having index semantics.
static Operation * createExpandedTransposeOp(PatternRewriter &rewriter, TransposeOp transposeOp, Value expandedInput, Value output, ExpansionInfo &expansionInfo)
static SmallVector< ReassociationIndices > getReassociationForExpansion(AffineMap indexingMap, const ExpansionInfo &expansionInfo)
Returns the reassociation maps to use in the tensor.expand_shape operation to convert the operands of...
static bool isFusableWithReshapeByDimExpansion(LinalgOp linalgOp, OpOperand *fusableOpOperand)
Conditions for folding a structured linalg operation with a reshape op by expanding the iteration spa...
static Operation * createExpandedGenericOp(PatternRewriter &rewriter, LinalgOp linalgOp, TypeRange resultTypes, ArrayRef< Value > &expandedOpOperands, ArrayRef< Value > outputs, ExpansionInfo &expansionInfo, ArrayRef< AffineMap > expandedOpIndexingMaps)
static Operation * createExpandedOp(PatternRewriter &rewriter, LinalgOp linalgOp, TypeRange resultTypes, ArrayRef< Value > expandedOpOperands, ArrayRef< Value > outputs, ArrayRef< AffineMap > expandedOpIndexingMaps, ExpansionInfo &expansionInfo)
static ReassociationIndices getDomainReassociation(AffineMap indexingMap, ReassociationIndicesRef rangeReassociation)
For a given list of indices in the range of the indexingMap that are folded, return the indices of th...
static void generateFusedElementwiseOpRegion(RewriterBase &rewriter, GenericOp fusedOp, AffineMap consumerToProducerLoopsMap, OpOperand *fusedOperand, unsigned nloops, llvm::SmallDenseSet< int > &preservedProducerResults)
Generate the region of the fused tensor operation.
static std::optional< SmallVector< Value > > fuseWithReshapeByExpansion(LinalgOp linalgOp, Operation *reshapeOp, OpOperand *fusableOpOperand, PatternRewriter &rewriter)
Implements the fusion of a tensor.collapse_shape or a tensor.expand_shape op and a generic op as expl...
static AffineMap getIndexingMapInExpandedOp(OpBuilder &builder, AffineMap indexingMap, const ExpansionInfo &expansionInfo)
Return the indexing map to use in the expanded op for a given the indexingMap of the original operati...
lhs
*if copies could not be generated due to yet unimplemented cases *copyInPlacementStart and copyOutPlacementStart in copyPlacementBlock *specify the insertion points where the incoming copies and outgoing should be the output argument nBegin is set to its * replacement(set to `begin` if no invalidation happens). Since outgoing *copies could have been inserted at `end`
Base type for affine expression.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
MLIRContext * getContext() const
unsigned getDimPosition(unsigned idx) const
Extracts the position of the dimensional expression at the given result, when the caller knows it is ...
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumSymbols() const
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
AffineMap getSubMap(ArrayRef< unsigned > resultPos) const
Returns the map consisting of the resultPos subset.
AffineMap compose(AffineMap map) const
Returns the AffineMap resulting from composing this with map.
bool isPermutation() const
Returns true if the AffineMap represents a symbol-less permutation map.
Attributes are known-constant values of operations.
Definition Attributes.h:25
This class represents an argument of a Block.
Definition Value.h:306
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
iterator_range< op_iterator< OpT > > getOps()
Return an iterator range over the operations within this block that are of 'OpT'.
Definition Block.h:217
Operation & front()
Definition Block.h:177
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
BlockArgListType getArguments()
Definition Block.h:111
iterator_range< iterator > without_terminator()
Return an iterator range over the operation within this block excluding the terminator operation at t...
Definition Block.h:236
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
Location getFusedLoc(ArrayRef< Location > locs, Attribute metadata=Attribute())
Definition Builders.cpp:27
MLIRContext * getContext() const
Definition Builders.h:56
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:327
std::enable_if_t<!std::is_base_of< Attribute, T >::value||std::is_same< Attribute, T >::value, T > getSplatValue() const
Return the splat value for this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
ShapedType getType() const
Return the type of this ElementsAttr, guaranteed to be a vector or tensor with static shape.
This class allows control over how the GreedyPatternRewriteDriver works.
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
auto lookupOrDefault(T from) const
Lookup a mapped value within the map.
Definition IRMapping.h:65
void map(Value from, Value to)
Inserts a new mapping for 'from' to 'to'.
Definition IRMapping.h:30
IRValueT get() const
Return the current value being used by this operand.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
Dialect * getLoadedDialect(StringRef name)
Get a registered IR dialect with the given namespace.
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
This class helps build Operations.
Definition Builders.h:210
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:439
Operation * clone(Operation &op, IRMapping &mapper)
Creates a deep copy of the specified operation, remapping any operands that use values outside of the...
Definition Builders.cpp:581
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
void cloneRegionBefore(Region &region, Region &parent, Region::iterator before, IRMapping &mapping)
Clone the blocks that belong to "region" before the given position in another region "parent".
Definition Builders.cpp:608
void createOrFold(SmallVectorImpl< Value > &results, Location location, Args &&...args)
Create an operation of specific op type at the current insertion point, and immediately try to fold i...
Definition Builders.h:528
void setInsertionPointAfterValue(Value val)
Sets the insertion point to the node after the specified value.
Definition Builders.h:424
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
This is a value defined by a result of an operation.
Definition Value.h:454
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:738
bool hasOneUse()
Returns true if this operation has exactly one use.
Definition Operation.h:901
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
This class represents the benefit of a pattern match in a unitless scheme that ranges from 0 (very li...
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
iterator begin()
Definition Region.h:55
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
virtual void cancelOpModification(Operation *op)
This method cancels a pending in-place modification.
virtual void replaceUsesWithIf(Value from, Value to, function_ref< bool(OpOperand &)> functor, bool *allUsesReplaced=nullptr)
Find uses of from and replace them with to if the functor returns true.
void mergeBlocks(Block *source, Block *dest, ValueRange argValues={})
Inline the operations of block 'source' into the end of block 'dest'.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
virtual void startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:40
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
OpFoldResult makeComposedFoldedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Constructs an AffineApplyOp that applies map to operands after composing the map with the maps of any...
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
bool areDimSequencesPreserved(ArrayRef< AffineMap > maps, ArrayRef< ReassociationIndices > dimSequences)
Return true if all sequences of dimensions specified in dimSequences are contiguous in all the ranges...
bool isParallelIterator(utils::IteratorType iteratorType)
Check if iterator type has "parallel" semantics.
Definition Utils.cpp:232
bool isDimSequencePreserved(AffineMap map, ReassociationIndicesRef dimSequence)
Return true if a given sequence of dimensions are contiguous in the range of the specified indexing m...
void populateFoldReshapeOpsByCollapsingPatterns(RewritePatternSet &patterns, const ControlFusionFn &controlFoldingReshapes)
Patterns to fold an expanding tensor.expand_shape operation with its producer generic operation by co...
FailureOr< ElementwiseOpFusionResult > fuseElementwiseOps(RewriterBase &rewriter, OpOperand *fusedOperand)
This transformation is intended to be used with a top-down traversal (from producer to consumer).
llvm::SmallDenseSet< int > getPreservedProducerResults(GenericOp producer, GenericOp consumer, OpOperand *fusedOperand)
Returns a set of indices of the producer's results which would be preserved after the fusion.
bool isReductionIterator(utils::IteratorType iteratorType)
Check if iterator type has "reduction" semantics.
Definition Utils.cpp:236
std::function< SmallVector< ReassociationIndices >(linalg::LinalgOp)> GetCollapsableDimensionsFn
Function type to control generic op dimension collapsing.
bool isElementwise(LinalgOp op)
Check if a LinalgOp is an element-wise operation.
Definition Utils.cpp:217
void populateCollapseDimensions(RewritePatternSet &patterns, const GetCollapsableDimensionsFn &controlCollapseDimensions)
Pattern to collapse dimensions in a linalg.generic op.
bool areElementwiseOpsFusable(OpOperand *fusedOperand)
Return true if two linalg.generic operations with producer/consumer relationship through fusedOperand...
void populateEraseUnusedOperandsAndResultsPatterns(RewritePatternSet &patterns)
Pattern to remove dead operands and results of linalg.generic operations.
std::function< bool(OpOperand *fusedOperand)> ControlFusionFn
Function type which is used to control when to stop fusion.
void populateSplitElementwiseOpsWithConcatInputsPatterns(RewritePatternSet &patterns)
Patterns that split elementwise linalg.generic operations at the boundaries of compatible tensor....
void populateFoldReshapeOpsByExpansionPatterns(RewritePatternSet &patterns, const ControlFusionFn &controlFoldingReshapes)
Patterns to fold an expanding (collapsing) tensor_reshape operation with its producer (consumer) gene...
void populateConstantFoldLinalgOperations(RewritePatternSet &patterns, const ControlFusionFn &controlFn)
Patterns to constant fold Linalg operations.
FailureOr< CollapseResult > collapseOpIterationDims(LinalgOp op, ArrayRef< ReassociationIndices > foldedIterationDims, RewriterBase &rewriter)
Collapses dimensions of linalg.generic/linalg.copy operation.
std::pair< TilingInterface, TilingInterface > splitOp(RewriterBase &rewriter, TilingInterface op, unsigned dimension, OpFoldResult splitPoint)
Split the given op into two parts along the given iteration space dimension at the specified splitPoi...
Definition Split.cpp:68
void populateElementwiseOpsFusionPatterns(RewritePatternSet &patterns, const ControlFusionFn &controlElementwiseOpFusion)
Patterns for fusing linalg operation on tensors.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
SparseTensorEncodingAttr getSparseTensorEncoding(Type type)
Convenience method to get a sparse encoding attribute from a type.
void populateBubbleUpExpandShapePatterns(RewritePatternSet &patterns)
Populates patterns with patterns that bubble up tensor.expand_shape through tensor....
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
AffineMap concatAffineMaps(ArrayRef< AffineMap > maps, MLIRContext *context)
Concatenates a list of maps into a single AffineMap, stepping over potentially empty maps.
Value convertScalarToDtype(OpBuilder &b, Location loc, Value operand, Type toType, bool isUnsignedCast)
Converts a scalar value operand to type toType.
Definition Utils.cpp:244
ArrayRef< int64_t > ReassociationIndicesRef
void bindDims(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to DimExpr at positions: [0 .
Definition AffineExpr.h:311
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
Value getValueOrCreateConstantIndexOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:114
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
SmallVector< T > applyPermutationMap(AffineMap map, llvm::ArrayRef< T > source)
Apply a permutation from map to source and return the result.
Definition AffineMap.h:675
SmallVector< int64_t, 2 > ReassociationIndices
Definition Utils.h:27
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
LogicalResult moveValueDefinitions(RewriterBase &rewriter, ValueRange values, Operation *insertionPoint, DominanceInfo &dominance)
Move definitions of values (and their transitive dependencies) before insertionPoint.
std::pair< SmallVector< int64_t >, SmallVector< Value > > decomposeMixedValues(ArrayRef< OpFoldResult > mixedValues)
Decompose a vector of mixed static or dynamic values into the corresponding pair of arrays.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpFoldResult stride
OpFoldResult size
OpFoldResult offset
static SaturatedInteger wrap(int64_t v)
Fuse two linalg.generic operations that have a producer-consumer relationship captured through fusedO...
Definition Transforms.h:656
Eliminates variable at the specified position using Fourier-Motzkin variable elimination.