MLIR 24.0.0git
LinalgTransformOps.cpp
Go to the documentation of this file.
1//===- LinalgTransformOps.cpp - Implementation of Linalg transform ops ----===//
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
10
40#include "mlir/Support/LLVM.h"
42#include "llvm/ADT/STLExtras.h"
43#include "llvm/ADT/ScopeExit.h"
44#include "llvm/ADT/SmallPtrSet.h"
45#include "llvm/ADT/SmallVectorExtras.h"
46#include "llvm/ADT/TypeSwitch.h"
47#include "llvm/Support/DebugLog.h"
48#include "llvm/Support/LogicalResult.h"
49#include <type_traits>
50
51using namespace mlir;
52using namespace mlir::linalg;
53using namespace mlir::transform;
54
55#define DEBUG_TYPE "linalg-transforms"
56
57/// Assuming that `ofr` is an index attr or a param of index type
58/// or a transform dialect handle mapped to exactly one op
59/// with one index result, return that value.
61 transform::TransformState &state, TransformOpInterface transformOp,
63 for (OpFoldResult ofr : ofrs) {
64 if (auto attr = dyn_cast<Attribute>(ofr)) {
65 if (!isa<IntegerAttr>(attr))
66 return transformOp.emitDefiniteFailure() << "expected IntegerAttr";
67 result.push_back(ofr);
68 continue;
69 }
70
71 Value transformValue = cast<Value>(ofr);
72 if (isa<TransformParamTypeInterface>(transformValue.getType())) {
73 ArrayRef<Attribute> params = state.getParams(transformValue);
74 if (params.size() != 1)
75 return transformOp.emitDefiniteFailure()
76 << "requires exactly one parameter associated";
77 result.push_back(params[0]);
78 continue;
79 }
80
81 auto payloadOps = state.getPayloadOps(transformValue);
82 if (!llvm::hasSingleElement(payloadOps)) {
84 transformOp.emitSilenceableError()
85 << "handle must be mapped to exactly one payload op";
86 diag.attachNote(transformValue.getLoc())
87 << "mapped to " << llvm::range_size(payloadOps) << " payload ops";
88 return diag;
89 }
90
91 Operation *op = *payloadOps.begin();
92 if (op->getNumResults() != 1 || !op->getResult(0).getType().isIndex()) {
94 transformOp.emitSilenceableError()
95 << "payload op must have exactly 1 index result";
96 diag.attachNote(op->getLoc())
97 << "has " << op->getNumResults() << " results";
98 return diag;
99 }
100 result.push_back(op->getResult(0));
101 }
102
104}
105
106// Given a list of params that are index attrs or a list of OpFoldResults
107// that are either index attrs or op handles, return a list of OpFoldResults
108// of index attrs or a list of OpFoldResults where all op handles are
109// replaced with the first (and only) OpResult of that payload op.
110// (There must be exactly one parameter associated with the AnyParamType or
111// one mapped payload op which must have exactly one index result.)
113 transform::TransformState &state, TransformOpInterface transformOp,
114 SmallVector<OpFoldResult> &result, Value packedHandle) {
115 if (isa<TransformParamTypeInterface>(packedHandle.getType())) {
116 ArrayRef<Attribute> params = state.getParams(packedHandle);
117 for (auto param : params) {
118 if (!isa<IntegerAttr>(param))
119 return transformOp.emitDefiniteFailure()
120 << "expected the parameter to be associated with an integer "
121 "attribute";
122 result.push_back(param);
123 }
125 }
126
127 for (Operation *op : state.getPayloadOps(packedHandle)) {
128 if (op->getNumResults() != 1 || !op->getResult(0).getType().isIndex()) {
130 transformOp.emitSilenceableError()
131 << "payload op must have exactly 1 index result";
132 diag.attachNote(op->getLoc())
133 << "has " << op->getNumResults() << " results";
134 return diag;
135 }
136 result.push_back(op->getResult(0));
137 }
138
140}
141
142/// When possible, converts each `OpFoldResult` in `mixedResult` to
143/// an integer if the value can be statically inferred. If a result
144/// is a `Value` then it must be either a `ParamType` or a handle
145/// to an a constant like op.
147 TransformState &state, TransformOpInterface &transformOp,
148 ArrayRef<OpFoldResult> mixedResults, SmallVectorImpl<int64_t> &reified) {
149 for (OpFoldResult paramOrHandle : mixedResults) {
150 if (auto attr = dyn_cast<Attribute>(paramOrHandle)) {
151 reified.push_back(cast<IntegerAttr>(attr).getInt());
152 continue;
153 }
154 if (isa<TransformParamTypeInterface>(
155 cast<Value>(paramOrHandle).getType())) {
156 ArrayRef<Attribute> params = state.getParams(cast<Value>(paramOrHandle));
157 if (params.size() != 1)
158 return transformOp.emitSilenceableError() << "expected a single param";
159 reified.push_back(
160 cast<IntegerAttr>(params.front()).getValue().getSExtValue());
161 continue;
162 }
163
164 Value handle = cast<Value>(paramOrHandle);
165 if (!isa<TransformHandleTypeInterface>(handle.getType()))
166 return transformOp.emitSilenceableError() << "unexpected value handle";
167 auto payload = state.getPayloadOps(handle);
168 if (!llvm::hasSingleElement(payload))
169 return transformOp.emitSilenceableError()
170 << "requires param or handle that is mapped to 1 payload op";
171
172 Operation *paramOrHandlePayloadOp = *payload.begin();
173 if (paramOrHandlePayloadOp->getNumResults() != 1 ||
174 !paramOrHandlePayloadOp->getResult(0).getType().isIndex()) {
175 return transformOp.emitSilenceableError()
176 << "requires param or handle to be result of op with 1 index "
177 "result";
178 }
179
180 IntegerAttr attr;
181 if (!matchPattern(paramOrHandlePayloadOp->getResult(0), m_Constant(&attr)))
182 return transformOp.emitSilenceableError()
183 << "requires param or handle to be the result of a constant like "
184 "op";
185
186 reified.push_back(attr.getInt());
187 }
189}
190
191//===----------------------------------------------------------------------===//
192// Apply...PatternsOp
193//===----------------------------------------------------------------------===//
194
195void transform::ApplyEraseUnnecessaryInputsPatternsOp::populatePatterns(
196 RewritePatternSet &patterns) {
198}
199
200void transform::ApplyDecomposeTensorPackUnpackPatternsOp::populatePatterns(
201 RewritePatternSet &patterns) {
203}
204
205void transform::ApplyDecomposeTensorPadPatternsOp::populatePatterns(
206 RewritePatternSet &patterns) {
208}
209
210void transform::ApplyFoldUnitExtentDimsViaReshapesPatternsOp::populatePatterns(
211 RewritePatternSet &patterns) {
214}
215
216void transform::ApplyFoldUnitExtentDimsViaSlicesPatternsOp::populatePatterns(
217 RewritePatternSet &patterns) {
219 options.rankReductionStrategy =
222}
223
224void transform::ApplyTilingCanonicalizationPatternsOp::populatePatterns(
225 RewritePatternSet &patterns) {
227}
228
229void transform::ApplyFoldAddIntoDestPatternsOp::populatePatterns(
230 RewritePatternSet &patterns) {
232}
233
234void transform::ApplyPadVectorizationPatternsOp::populatePatterns(
235 RewritePatternSet &patterns) {
237}
238
239void transform::ApplyFoldIntoPackAndUnpackPatternsOp::populatePatterns(
240 RewritePatternSet &patterns) {
242}
243
244void transform::ApplyFoldPackUnpackIntoEmptyPatternsOp::populatePatterns(
245 RewritePatternSet &patterns) {
247}
248
249void transform::ApplyDataLayoutPropagationPatternsOp::populatePatterns(
250 RewritePatternSet &patterns) {
251 linalg::ControlPropagationFn defaultControlFn = [](OpOperand *operand) {
252 return true;
253 };
254 linalg::populateDataLayoutPropagationPatterns(patterns, defaultControlFn,
255 getPoisonPadding());
256}
257
258void transform::ApplyExtractSliceSinkingPatternsOp::populatePatterns(
259 RewritePatternSet &patterns) {
260 linalg::ControlPropagationFn defaultControlFn =
261 [](OpOperand *opOperand) -> bool {
262 Operation *producer = opOperand->get().getDefiningOp();
263 Operation *consumer = opOperand->getOwner();
264 return consumer->getBlock() == producer->getBlock();
265 };
266 linalg::populateExtractSliceSinkingPatterns(patterns, defaultControlFn);
267}
268
269void transform::ApplySwapExtractSliceWithFillPatternsOp::populatePatterns(
270 RewritePatternSet &patterns) {
272}
273
274void transform::ApplyEraseUnusedOperandsAndResultsPatternsOp::populatePatterns(
275 RewritePatternSet &patterns) {
277}
278
279//===----------------------------------------------------------------------===//
280// BufferizeToAllocationOp
281//===----------------------------------------------------------------------===//
282
283namespace {
284class NewOpsListener : public RewriterBase::ForwardingListener {
285public:
287
288 SmallVector<Operation *> getNewOps() const {
289 return SmallVector<Operation *>(newOps.begin(), newOps.end());
290 }
291
292private:
293 void notifyOperationInserted(Operation *op,
294 OpBuilder::InsertPoint previous) override {
295 ForwardingListener::notifyOperationInserted(op, previous);
296 // We only care about newly created ops.
297 if (previous.isSet())
298 return;
299 auto inserted = newOps.insert(op);
300 (void)inserted;
301 assert(inserted.second && "expected newly created op");
302 }
303
304 void notifyOperationErased(Operation *op) override {
305 ForwardingListener::notifyOperationErased(op);
306 op->walk([&](Operation *op) { newOps.erase(op); });
307 }
308
310};
311} // namespace
312
313DiagnosedSilenceableFailure transform::BufferizeToAllocationOp::apply(
316 // Attach listener to keep track of newly created ops.
317 OpBuilder::Listener *previousListener = rewriter.getListener();
318 llvm::scope_exit resetListener(
319 [&]() { rewriter.setListener(previousListener); });
320 NewOpsListener newOpsListener(previousListener);
321 rewriter.setListener(&newOpsListener);
322
324 if (getMemcpyOp() == "bufferization.materialize_in_destination") {
325 options.memcpyOp = linalg::BufferizeToAllocationOptions::MemcpyOp::
326 MaterializeInDestination;
327 } else if (getMemcpyOp() == "memref.copy") {
328 options.memcpyOp =
330 } else if (getMemcpyOp() == "linalg.copy") {
331 options.memcpyOp =
333 } else {
334 llvm_unreachable("invalid memcpy op");
335 }
336 if (getAllocOp() == "memref.alloc") {
337 options.allocOp =
339 } else if (getAllocOp() == "memref.alloca") {
340 options.allocOp =
342 } else {
343 llvm_unreachable("invalid alloc op");
344 }
345 options.bufferizeDestinationOnly = getBufferizeDestinationOnly();
346 options.emitDealloc = getEmitDealloc();
347
348 // Bufferize ops.
349 Attribute memorySpace =
350 getMemorySpace().has_value() ? getMemorySpace().value() : Attribute();
351 SmallVector<Value> allocatedBuffers;
352 for (Operation *op : state.getPayloadOps(getTarget())) {
353 Value buffer =
354 linalg::bufferizeToAllocation(rewriter, options, op, memorySpace);
355 if (!buffer) {
356 DiagnosedSilenceableFailure diag = emitSilenceableError()
357 << "failed to bufferize operation";
358 diag.attachNote(op->getLoc()) << "target payload op";
359 return diag;
360 }
361 allocatedBuffers.push_back(buffer);
362 }
363
364 // Set results.
365 results.setValues(cast<OpResult>(getAllocatedBuffer()), allocatedBuffers);
366 results.set(cast<OpResult>(getNewOps()), newOpsListener.getNewOps());
368}
369
370void transform::BufferizeToAllocationOp::getEffects(
372 if (getBufferizeDestinationOnly()) {
373 // The destination is replaced with a newly allocated buffer, but the op
374 // itself remains in place.
375 onlyReadsHandle(getTargetMutable(), effects);
376 } else {
377 consumesHandle(getTargetMutable(), effects);
378 }
379 producesHandle(getOperation()->getOpResults(), effects);
380 modifiesPayload(effects);
381}
382
383LogicalResult transform::BufferizeToAllocationOp::verify() {
384 if (getMemcpyOp() != "bufferization.materialize_in_destination" &&
385 getMemcpyOp() != "memref.copy" && getMemcpyOp() != "linalg.copy")
386 return emitOpError() << "unsupported memcpy op";
387 if (getAllocOp() != "memref.alloc" && getAllocOp() != "memref.alloca")
388 return emitOpError() << "unsupported alloc op";
389 return success();
390}
391
392//===----------------------------------------------------------------------===//
393// PromoteTensorOp
394//===----------------------------------------------------------------------===//
395
396/// Return true if the operand may be read from by its owner. This is currently
397/// very conservative and only looks inside linalg operations to prevent
398/// unintentional data loss.
399static bool mayBeRead(OpOperand &operand) {
400 auto linalgOp = dyn_cast<linalg::LinalgOp>(operand.getOwner());
401
402 // Be conservative about ops we cannot analyze deeper.
403 if (!linalgOp)
404 return true;
405
406 // Look inside linalg ops.
407 Value blockArgument = linalgOp.getMatchingBlockArgument(&operand);
408 return !blockArgument.use_empty();
409}
410
411/// Return true if the value may be read through any of its uses.
412static bool mayBeRead(Value value) {
413 // If the value has a reference semantics, it
414 // may be read through any alias...
415 if (!isa<TensorType, FloatType, IntegerType>(value.getType()))
416 return true;
417 return llvm::any_of(value.getUses(),
418 static_cast<bool (&)(OpOperand &)>(mayBeRead));
419}
420
422transform::PromoteTensorOp::apply(transform::TransformRewriter &rewriter,
425 SmallVector<Value> promoted;
426 for (Value tensor : state.getPayloadValues(getTensor())) {
427 auto type = dyn_cast<RankedTensorType>(tensor.getType());
428 if (!type) {
429 return emitSilenceableError() << "non-tensor type: " << tensor;
430 }
431
432 Operation *definingOp = tensor.getDefiningOp();
433 if (definingOp)
434 rewriter.setInsertionPointAfter(definingOp);
435 else
436 rewriter.setInsertionPointToStart(cast<BlockArgument>(tensor).getOwner());
437
438 // Check this before we emit operations using this value.
439 bool needsMaterialization = mayBeRead(tensor);
440
441 SmallVector<Value> dynamicDims;
443 for (auto [pos, dim] : llvm::enumerate(type.getShape())) {
444 if (!ShapedType::isDynamic(dim))
445 continue;
446 Value cst =
447 arith::ConstantIndexOp::create(rewriter, tensor.getLoc(), pos);
448 auto dimOp =
449 tensor::DimOp::create(rewriter, tensor.getLoc(), tensor, cst);
450 preservedOps.insert(dimOp);
451 dynamicDims.push_back(dimOp);
452 }
453 auto allocation = bufferization::AllocTensorOp::create(
454 rewriter, tensor.getLoc(), type, dynamicDims);
455 // Set memory space if provided.
456 if (getMemorySpaceAttr())
457 allocation.setMemorySpaceAttr(getMemorySpaceAttr());
458 Value allocated = allocation;
459
460 // Only insert a materialization (typically bufferizes to a copy) when the
461 // value may be read from.
462 if (needsMaterialization) {
463 auto copy = bufferization::MaterializeInDestinationOp::create(
464 rewriter, tensor.getLoc(), tensor, allocated);
465 preservedOps.insert(copy);
466 promoted.push_back(copy.getResult());
467 } else {
468 promoted.push_back(allocated);
469 }
470 rewriter.replaceAllUsesExcept(tensor, promoted.back(), preservedOps);
471 }
472 results.setValues(cast<OpResult>(getPromoted()), promoted);
474}
475
476void transform::PromoteTensorOp::getEffects(
478 transform::onlyReadsHandle(getTensorMutable(), effects);
479 transform::producesHandle(getOperation()->getOpResults(), effects);
481}
482
483//===----------------------------------------------------------------------===//
484// DecomposeOp
485//===----------------------------------------------------------------------===//
486
488transform::DecomposeOp::applyToOne(transform::TransformRewriter &rewriter,
489 LinalgOp target,
492 FailureOr<linalg::LinalgOp> res =
494 if (succeeded(res)) {
495 results.push_back(*res);
497 }
498 return emitDefaultSilenceableFailure(target);
499}
500
501//===----------------------------------------------------------------------===//
502// DecomposeInterfaceOp
503//===----------------------------------------------------------------------===//
504
505// Decompose the target operation if it implements the AggregatedOpInterface.
506// Push the decomposed operations (the ones that replaces the values produced by
507// \p target) in the `results`.
508DiagnosedSilenceableFailure transform::DecomposeInterfaceOp::applyToOne(
512 auto decomposableOp = dyn_cast<AggregatedOpInterface>(target);
513 if (!decomposableOp) {
515 "payload is not a decomposable op");
516 return emitDefaultSilenceableFailure(target);
517 }
518
519 FailureOr<SmallVector<Value>> maybeNewResults =
520 decomposableOp.decomposeOperation(rewriter);
521 if (failed(maybeNewResults))
522 return emitDefaultSilenceableFailure(target);
523
524 rewriter.replaceOp(decomposableOp, *maybeNewResults);
525 for (Value val : *maybeNewResults) {
526 Operation *definition = val.getDefiningOp();
527 if (definition)
528 results.push_back(definition);
529 }
531}
532
533//===----------------------------------------------------------------------===//
534// EliminateLinalgOpAnchoredEmptyTensorsOp
535//===----------------------------------------------------------------------===//
536
537void transform::EliminateLinalgOpAnchoredEmptyTensorsOp::getEffects(
539 onlyReadsHandle(getTargetMutable(), effects);
540 modifiesPayload(effects);
541}
542
544transform::EliminateLinalgOpAnchoredEmptyTensorsOp::apply(
545 transform::TransformRewriter &rewriter, TransformResults &transformResults,
546 TransformState &state) {
548 options.allowReturnAllocsFromLoops = true;
549
550 for (Operation *target : state.getPayloadOps(getTarget())) {
552 if (failed(analyzeOp(target, state)))
553 return mlir::emitSilenceableFailure(target->getLoc())
554 << "failed to analyze op";
556 rewriter, target, state)))
557 return mlir::emitSilenceableFailure(target->getLoc())
558 << "failed to eliminate LinalgOp anchored tensor.empty ops";
559 }
561}
562
563//===----------------------------------------------------------------------===//
564// FuseOp
565//===----------------------------------------------------------------------===//
566
567void transform::FuseOp::build(OpBuilder &builder, OperationState &result,
568 TypeRange loopTypes, Value target,
569 ArrayRef<int64_t> staticTileSizes,
570 ArrayRef<int64_t> staticTileInterchange,
571 bool applyCleanup, bool useForall) {
572 return build(
573 builder, result, loopTypes,
574 /*target=*/target,
575 /*mixedTileSizes=*/
576 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileSizes)),
577 /*mixedTileInterchange=*/
578 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileInterchange)),
579 applyCleanup, useForall);
580}
581
582void transform::FuseOp::build(OpBuilder &builder, OperationState &result,
583 Value target, ArrayRef<int64_t> staticTileSizes,
584 ArrayRef<int64_t> staticTileInterchange,
585 bool applyCleanup, bool useForall) {
586 return build(
587 builder, result,
588 /*target=*/target,
589 /*mixedTileSizes=*/
590 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileSizes)),
591 /*mixedTileInterchange=*/
592 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileInterchange)),
593 applyCleanup, useForall);
594}
595
596void transform::FuseOp::build(OpBuilder &builder, OperationState &result,
598 ArrayRef<OpFoldResult> mixedTileSizes,
599 ArrayRef<OpFoldResult> mixedTileInterchange,
600 bool applyCleanup, bool useForall) {
601 // Loop types are automaticaly splat by the callee, setting up one is
602 // enough.
603 SmallVector<Type> loopTypes(1, builder.getType<transform::AnyOpType>());
604 build(builder, result, loopTypes, target, mixedTileSizes,
605 mixedTileInterchange, applyCleanup, useForall);
606}
607
608void transform::FuseOp::build(OpBuilder &builder, OperationState &result,
609 TypeRange loopTypes, Value target,
610 ArrayRef<OpFoldResult> mixedTileSizes,
611 ArrayRef<OpFoldResult> mixedTileInterchange,
612 bool applyCleanup, bool useForall) {
613 SmallVector<int64_t> staticTileSizes;
614 SmallVector<Value> dynamicTileSizes;
615 dispatchIndexOpFoldResults(mixedTileSizes, dynamicTileSizes, staticTileSizes);
616 SmallVector<int64_t> staticTileInterchange;
617 SmallVector<Value> dynamicTileInterchange;
618 dispatchIndexOpFoldResults(mixedTileInterchange, dynamicTileInterchange,
619 staticTileInterchange);
620 // Call the default builder which sets up the proper operands segment sizes
621 // attributes for multiple variadic operands. In the absence of this,
622 // horrible bugs ensue.
623 auto staticTileSizesAttr = builder.getDenseI64ArrayAttr(staticTileSizes);
624 auto staticTileInterchangeAttr =
625 builder.getDenseI64ArrayAttr(staticTileInterchange);
626 unsigned numExpectedLoops =
627 useForall ? 1 : staticTileSizes.size() - llvm::count(staticTileSizes, 0);
628 SmallVector<Type> resultTypes;
629 resultTypes.reserve(numExpectedLoops);
630 assert((loopTypes.size() == 1 || loopTypes.size() == numExpectedLoops) &&
631 "expected one loop type or as many as loops");
632 if (loopTypes.size() == 1)
633 resultTypes.append(numExpectedLoops, loopTypes[0]);
634 else
635 llvm::append_range(resultTypes, loopTypes);
636 build(builder, result, /*transformed=*/target.getType(),
637 /*loops=*/resultTypes,
638 /*target=*/target,
639 /*tile_sizes=*/dynamicTileSizes,
640 /*tile_interchange=*/dynamicTileInterchange,
641 /*packed_tile_sizes=*/Value(),
642 /*static_tile_sizes=*/staticTileSizesAttr,
643 /*static_tile_interchange=*/staticTileInterchangeAttr,
644 /*inner_tile_alignments=*/ArrayRef<int64_t>{},
645 /*apply_cleanup=*/applyCleanup,
646 /*use_forall=*/useForall);
647}
648
649/// Apply a tiling transformation to all payload ops and store both the
650/// tiled operation as well as the created tile loops.
651template <typename Range>
652static LogicalResult applyTilingToAll(
653 RewriterBase &rewriter, Operation *transformOp, Range &&payloadOps,
654 unsigned numLoops, transform::TransformResults &transformResults,
655 bool packedResults,
656 function_ref<FailureOr<scf::SCFTileAndFuseResult>(TilingInterface)>
657 applyFn) {
658 SmallVector<Operation *> tiledLinalgOps;
659 SmallVector<SmallVector<Operation *>> loopOps(numLoops);
660 size_t numTargets = llvm::range_size(payloadOps);
661
662 for (Operation *target : payloadOps) {
663 auto tilingInterfaceOp = dyn_cast<TilingInterface>(target);
664 if (!tilingInterfaceOp)
665 return transformOp->emitError("only TilingInterface ops are supported");
666
667 rewriter.setInsertionPoint(target);
668 FailureOr<scf::SCFTileAndFuseResult> tiledResults =
669 applyFn(tilingInterfaceOp);
670 if (failed(tiledResults))
671 return failure();
672
673 // Perform the replacement of tiled and fused values.
674 SmallVector<Operation *> opsToReplace{target};
675 llvm::append_range(opsToReplace, tiledResults->fusedProducers);
676 for (Operation *toReplace : opsToReplace) {
677 for (OpResult res : toReplace->getResults())
678 if (auto replacement = tiledResults->replacements.lookup(res))
679 rewriter.replaceAllUsesWith(res, replacement);
680 if (toReplace->use_empty()) {
681 rewriter.eraseOp(toReplace);
682 }
683 }
684
685 // Report back the relevant handles to the transform op.
686 tiledLinalgOps.push_back(tiledResults->tiledAndFusedOps.front());
687 assert(tiledResults->loops.size() == numLoops &&
688 "Mismatched number of loops, tile and fuse transform should have "
689 "failed");
690 for (unsigned int i = 0; i < numLoops; ++i)
691 loopOps[i].push_back(tiledResults->loops[i]);
692 }
693
694 transformResults.set(transformOp->getOpResult(0), tiledLinalgOps);
695 if (packedResults) {
696 // In case of packed results, all created loops are assigned to a single
697 // handle. Loops are returned in order of targets such as:
698 // %loops_handle = {
699 // target0:loop0, ..., target0:loopN,
700 // target1:loop0, ..., target1:loopN,
701 // ... }
702 SmallVector<Operation *> flattenedLoopOps;
703 for (unsigned int idx = 0; idx < numTargets; ++idx)
704 for (unsigned int i = 0; i < numLoops; ++i)
705 flattenedLoopOps.push_back(loopOps[i][idx]);
706 transformResults.set(transformOp->getOpResult(1), flattenedLoopOps);
707 } else {
708 for (unsigned int i = 0; i < numLoops; ++i)
709 transformResults.set(transformOp->getOpResult(i + 1), loopOps[i]);
710 }
711
712 return success();
713}
714
716transform::FuseOp::apply(transform::TransformRewriter &rewriter,
717 mlir::transform::TransformResults &transformResults,
719 auto transformOp = cast<TransformOpInterface>(getOperation());
720
721 SmallVector<OpFoldResult> mixedTileSizes;
723 getPackedTileSizes()
725 state, transformOp, mixedTileSizes, getPackedTileSizes())
727 state, transformOp, mixedTileSizes, getMixedTileSizes());
728 if (!status.succeeded())
729 return status;
730 SmallVector<int64_t> tileInterchange;
732 state, transformOp, getMixedTileInterchange(), tileInterchange);
733 if (!status.succeeded())
734 return status;
735
736 scf::SCFTilingOptions tilingOptions;
737 tilingOptions.interchangeVector = std::move(tileInterchange);
738 bool useForall = getUseForall();
739 tilingOptions.setLoopType(useForall
740 ? scf::SCFTilingOptions::LoopType::ForallOp
741 : scf::SCFTilingOptions::LoopType::ForOp);
742 tilingOptions.setTileSizes(mixedTileSizes);
743 scf::SCFTileAndFuseOptions tileAndFuseOptions;
744 tileAndFuseOptions.tilingOptions = std::move(tilingOptions);
745 // Optional caller-asserted pack/unpack inner-tile alignment (see
746 // InnerTileAlignment).
747 tileAndFuseOptions.tilingOptions.setInnerTileAlignments(
748 convertInnerTileAlignments(getInnerTileAlignments()));
749
750 if (getApplyCleanup()) {
751 MLIRContext *context = rewriter.getContext();
752 RewritePatternSet patterns(context);
753 tensor::ExtractSliceOp::getCanonicalizationPatterns(patterns, context);
756 tileAndFuseOptions.cleanupPatterns = std::move(patterns);
757 }
758
759 size_t numLoops;
760 if (useForall) {
761 numLoops = 1;
762 } else {
763 numLoops = llvm::count_if(mixedTileSizes, [](OpFoldResult ofr) {
764 auto attr = dyn_cast<Attribute>(ofr);
765 if (!attr)
766 return true;
767 return cast<IntegerAttr>(attr).getInt() != 0;
768 });
769 }
770 LogicalResult result = applyTilingToAll(
771 rewriter, getOperation(), state.getPayloadOps(getTarget()), numLoops,
772 transformResults, /*packedResults=*/getPackedTileSizes() != nullptr,
773 [&](TilingInterface tilingInterfaceOp)
774 -> FailureOr<scf::SCFTileAndFuseResult> {
775 return tileConsumerAndFuseProducersUsingSCF(rewriter, tilingInterfaceOp,
776 tileAndFuseOptions);
777 });
780}
781
782LogicalResult transform::FuseOp::verify() {
783 bool hasPackedTiles = getPackedTileSizes() != nullptr;
784 if (!getMixedTileSizes().empty() && hasPackedTiles)
785 return emitOpError(
786 "tile_sizes and packed_tile_sizes are mutually exclusive");
787
788 auto iterspace_rank = getStaticTileSizes().size();
789 ArrayRef<int64_t> permutation = getStaticTileInterchange();
790 if (permutation.size() > iterspace_rank)
791 return emitOpError()
792 << "interchange length exceeds iteration space dimensions ("
793 << iterspace_rank << "), found " << getTileInterchange();
794 SmallVector<bool> seen(iterspace_rank, false);
795 for (int64_t v : permutation) {
796 if (!ShapedType::isDynamic(v)) {
797 if (v < 0 || v >= static_cast<int64_t>(iterspace_rank))
798 return emitOpError() << "expects interchange values to be in range [0, "
799 << iterspace_rank << "), found: " << v;
800 if (seen[v])
801 return emitOpError() << "found duplicate interchange value: " << v;
802 seen[v] = true;
803 }
804 }
805
806 ArrayRef<int64_t> sizes = getStaticTileSizes();
807 size_t numExpectedLoops = getUseForall() || hasPackedTiles
808 ? 1
809 : sizes.size() - llvm::count(sizes, 0);
810 if (numExpectedLoops != getNumResults() - 1)
811 return emitOpError() << "expects " << numExpectedLoops << " loop results";
812
813 return verifyInnerTileAlignments(getOperation(), getInnerTileAlignments());
814}
815
816SmallVector<OpFoldResult> transform::FuseOp::getMixedTileSizes() {
817 return getMixedValues(getStaticTileSizes(), getTileSizes(), getContext());
818}
819
820SmallVector<OpFoldResult> transform::FuseOp::getMixedTileInterchange() {
821 return getMixedValues(getStaticTileInterchange(), getTileInterchange(),
822 getContext());
823}
824
825void transform::FuseOp::getEffects(
827 consumesHandle(getTargetMutable(), effects);
828 onlyReadsHandle(getTileSizesMutable(), effects);
829 onlyReadsHandle(getPackedTileSizesMutable(), effects);
830 onlyReadsHandle(getTileInterchangeMutable(), effects);
831 producesHandle(getOperation()->getOpResults(), effects);
832 modifiesPayload(effects);
833}
834
835//===----------------------------------------------------------------------===//
836// FuseIntoContainingOp
837//===----------------------------------------------------------------------===//
838
839void transform::FuseIntoContainingOp::build(OpBuilder &builder,
841 Value producerOp,
842 Value containingOp) {
843 result.addOperands({producerOp, containingOp});
844 auto resultType = transform::AnyOpType::get(builder.getContext());
845 result.addTypes({resultType, resultType});
846}
847
848/// Add new operands to the forall op for users of the producerOp
849/// that are dominated by the containing scf.forall op.
851 RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp,
852 Operation *containingOp, TilingResult &tileAndFuseResult,
853 int64_t resultNumber, SmallVector<OpFoldResult> &offsets,
855
856 // Count number of users not including the containing op
857 SetVector<Operation *> dominatedUsers;
858 DominanceInfo domInfo(containingOp);
859 for (Operation *user : producerOp->getResult(resultNumber).getUsers()) {
860 if (!containingOp->isAncestor(user) &&
861 (domInfo.dominates(containingOp, user))) {
862 dominatedUsers.insert(user);
863 }
864 }
865 if (dominatedUsers.empty())
866 return nullptr;
867
868 // Create new scf.forall op
869 auto forallOp = cast<scf::ForallOp>(containingOp);
870 OpBuilder::InsertionGuard g(rewriter);
871 rewriter.setInsertionPoint(forallOp);
872
873 // Get new output
874 Location loc = forallOp.getLoc();
875 auto genericOp = dyn_cast<linalg::GenericOp>(producerOp);
876 if (!genericOp)
877 return nullptr;
878 SmallVector<Value> outputs = genericOp.getOutputs();
879 SmallVector<Value> newOuts(forallOp.getOutputs());
880 newOuts.push_back(outputs[resultNumber]);
881
882 // Create new scf.forall op
883 auto newforallOp = scf::ForallOp::create(
884 rewriter, loc, forallOp.getMixedLowerBound(),
885 forallOp.getMixedUpperBound(), forallOp.getMixedStep(), newOuts,
886 forallOp.getMapping());
887 rewriter.eraseBlock(newforallOp.getBody());
888 newforallOp.getRegion().takeBody(forallOp.getRegion());
889
890 // Add additional block argument for new value being returned
891 // and replaces all uses of the new output with corresponding bbArg
892 // inside the scf.forall to enable fusion into this new scf.forall.
893 newforallOp.getBody()->addArgument(newOuts.back().getType(),
894 newOuts.back().getLoc());
895 auto bbArgs = newforallOp.getBody()->getArguments();
896 rewriter.replaceUsesWithIf(newOuts.back(), bbArgs.back(),
897 [&](OpOperand &use) {
898 Operation *op = use.getOwner();
899 return newforallOp->isProperAncestor(op);
900 });
901
902 // Fix terminator
903 scf::InParallelOp terminatorOp = newforallOp.getTerminator();
904 SmallVector<Operation *> yieldingOps = llvm::map_to_vector<4>(
905 terminatorOp.getYieldingOps(), [](Operation &op) { return &op; });
906 Operation *firstYieldOp = yieldingOps.front();
907 rewriter.setInsertionPoint(firstYieldOp);
908 Value src = tileAndFuseResult.tiledValues[0];
909 Value dst = newforallOp.getRegionIterArgs().back();
910 SmallVector<OpFoldResult> strides(offsets.size(), rewriter.getIndexAttr(1));
911 tensor::ParallelInsertSliceOp::create(rewriter, firstYieldOp->getLoc(), src,
912 dst, offsets, sizes, strides);
913
914 for (auto result : llvm::enumerate(forallOp.getResults())) {
915 rewriter.replaceAllUsesWith(result.value(),
916 newforallOp->getResult(result.index()));
917 }
918 rewriter.replaceUsesWithIf(producerOp->getResult(resultNumber),
919 newforallOp->getResults().back(),
920 [&](OpOperand &use) {
921 Operation *user = use.getOwner();
922 return dominatedUsers.contains(user);
923 });
924 return newforallOp;
925}
926
927/// Given two operands coming from a loop iter arg, 'src' and 'dst', return true
928/// if the operand 'src' is equal to 'dst' or equal to a iter arg present in a
929/// outer loop. To determine the second condition, this function iterates
930/// using a worklist over the enclosing loops, trying to find 'src' in any of
931/// the parent loop's iter args.
932static bool sameOrEquivalentIterArg(Value src, Value dst) {
933 // Stack like vector containing possible iterArgs candidates. The first one
934 // is dst, and we will transverse the IR from there.
935 SmallVector<Value> destWorklist;
936 destWorklist.push_back(dst);
937
938 while (!destWorklist.empty()) {
939 Value currentDst = destWorklist.pop_back_val();
940
941 // We have found the same operand in some iter arg in the loop structure,
942 // so src and dst are equivalent.
943 if (src == currentDst)
944 return true;
945
946 // The operands are not equivalent, look for enclosing loops over
947 // currentDst.
948 auto bbArg = dyn_cast<BlockArgument>(currentDst);
949 if (!bbArg)
950 continue;
951
952 Block *parentBlock = bbArg.getOwner();
953 assert(parentBlock && "unlinked block argument");
954
955 Operation *parentOp = parentBlock->getParentOp();
956 assert(parentOp && "expected block argument with parent operation");
957
958 // Check if parent is loop-like. If it's not, do not add it to the worklist.
959 auto parentLoop = dyn_cast<LoopLikeOpInterface>(parentOp);
960 if (!parentLoop)
961 continue;
962
963 for (auto innerIterArg : parentLoop.getRegionIterArgs()) {
964 // No need to check for null as innerIterArg is tied to parentLoop.
965 OpOperand *operand = parentLoop.getTiedLoopInit(innerIterArg);
966 Value loopBlockArgument =
967 parentLoop->getOperand(operand->getOperandNumber());
968 destWorklist.push_back(loopBlockArgument);
969 }
970 }
971
972 return false;
973}
974
975/// Find the first "extract" user of `producerOp` and tile it right before its
976/// use. The tiled op is fused under the `containingOp`.
977/// Return this fused op on success or nullptr if anything fails.
978/// If tiled op has uses that are dominated by `containingOp`, return
979/// a new `containingOp` with results of the fused op appended to
980/// results of the `containingOp` or nullptr if there are no dominated uses.
981static std::tuple<SmallVector<Operation *>, Operation *>
983 Operation *producerOp, Operation *containingOp,
984 ArrayRef<InnerTileAlignment> innerTileAlignments) {
985 LDBG() << "Try to fuse a direct extract use";
986 auto tileableProducer = dyn_cast<TilingInterface>(producerOp);
987 if (!tileableProducer) {
988 diag.attachNote(producerOp->getLoc())
989 << "producer is not a TileableInterface: " << *producerOp;
990 return {};
991 }
992
993 // Search the producer slices accessed within the containing operation.
994 // TODO: Generalize to more extract/insert/parallel_insert triples, maybe
995 // evolve into an interface.
996 auto it = llvm::find_if(tileableProducer->getUsers(), [&](Operation *user) {
997 auto sliceOp = dyn_cast<tensor::ExtractSliceOp>(user);
998 return sliceOp && containingOp->isProperAncestor(sliceOp);
999 });
1000
1001 // Find a fusion opportunity.
1002 if (it == tileableProducer->getUsers().end()) {
1003 diag.attachNote(tileableProducer->getLoc())
1004 << "could not find fusion opportunity for: " << *tileableProducer;
1005 return {};
1006 }
1007 auto sliceOpToTile = cast<tensor::ExtractSliceOp>(*it);
1008
1009 // Try to fuse the producer in-place.
1010 OpBuilder::InsertionGuard guard(rewriter);
1011 rewriter.setInsertionPoint(sliceOpToTile);
1012
1013 // Clone the producer inside the consumer and try to update the producer init
1014 // operands using the loop bbArgs if applicable. More precisely, if the bbArg
1015 // of the container loop points to a value that it is used by the consumer op,
1016 // then, instead of using such value on the consumer, use the value coming
1017 // from the bbArg instead. This allows to reuse the output tensor (instead of
1018 // creating a new one) of the container when both producer and container write
1019 // to the same output.
1020 if (LoopLikeOpInterface containerLoop =
1021 dyn_cast<LoopLikeOpInterface>(sliceOpToTile->getParentOp())) {
1022 Operation *clone = rewriter.clone(*producerOp);
1023 rewriter.modifyOpInPlace(clone, [&]() {
1024 // Iterate over the outputs of the producer and over the loop bbArgs and
1025 // check if any bbArg points to the same value as the producer output. In
1026 // such case, make the producer output point to the bbArg directly.
1027 auto dpsInterface = dyn_cast<DestinationStyleOpInterface>(clone);
1028 if (!dpsInterface)
1029 return;
1030
1031 for (OpOperand &initOperandPtr : dpsInterface.getDpsInitsMutable()) {
1032 Value producerOperand =
1033 clone->getOperand(initOperandPtr.getOperandNumber());
1034 for (BlockArgument containerIterArg :
1035 containerLoop.getRegionIterArgs()) {
1036 OpOperand *bbArg = containerLoop.getTiedLoopInit(containerIterArg);
1037 Value consumerOperand =
1038 containerLoop->getOperand(bbArg->getOperandNumber());
1039 // The producer has the same init as the loop bbArg, use it.
1040 if (sameOrEquivalentIterArg(producerOperand, consumerOperand)) {
1041 initOperandPtr.set(containerIterArg);
1042 }
1043 }
1044 }
1045 });
1046
1047 tileableProducer = dyn_cast<TilingInterface>(clone);
1048 }
1049
1050 // Tile the producer.
1051 int64_t resultNumber =
1052 cast<OpResult>(sliceOpToTile.getSource()).getResultNumber();
1053 LDBG() << "resultNumber: " << resultNumber;
1054
1055 SmallVector<OpFoldResult> offsets = sliceOpToTile.getMixedOffsets();
1056 SmallVector<OpFoldResult> sizes = sliceOpToTile.getMixedSizes();
1057
1058 FailureOr<TilingResult> tileAndFuseResult =
1059 tileableProducer.generateResultTileValue(rewriter, resultNumber, offsets,
1060 sizes, innerTileAlignments);
1061
1062 if (failed(tileAndFuseResult)) {
1063 diag.attachNote(tileableProducer->getLoc())
1064 << "failed to tile producer op: " << *tileableProducer;
1065 return {};
1066 }
1067
1068#ifndef NDEBUG
1069 for (auto *tiledOp : tileAndFuseResult->tiledOps) {
1070 LDBG() << "tiledProducer: " << *tiledOp;
1071 }
1072#endif
1073
1074 // Replace the extract op.
1075 auto maybeRankReduced = tensor::ExtractSliceOp::rankReduceIfNeeded(
1076 rewriter, sliceOpToTile->getLoc(), tileAndFuseResult->tiledValues[0],
1077 cast<RankedTensorType>(sliceOpToTile->getResult(0).getType()).getShape());
1078 if (failed(maybeRankReduced)) {
1079 diag.attachNote(producerOp->getLoc())
1080 << "shape types don't match (missing canonicalization?):\nTiledOp: "
1081 << tileAndFuseResult->tiledValues[0]
1082 << "\nSliceOp: " << sliceOpToTile.getOperation() << '\n';
1083 return {};
1084 }
1085 rewriter.replaceOp(sliceOpToTile, *maybeRankReduced);
1086
1087 // Add new outputs to containing op, if required
1088 Operation *newContainingOp = replaceForAllWithNewSignature(
1089 rewriter, diag, producerOp, containingOp, *tileAndFuseResult,
1090 resultNumber, offsets, sizes);
1091
1092 // Cleanup clone.
1093 if (isa<LoopLikeOpInterface>(containingOp))
1094 rewriter.eraseOp(tileableProducer);
1095
1096 return std::make_tuple(tileAndFuseResult->tiledOps, newContainingOp);
1097}
1098
1099/// First, find the first "scf::ForallOp" user of `producerOp` and ensure
1100/// it is exactly the `containingOp`, otherwise bail.
1101/// Then, find the first "extract" user of the tied block argument and tile it
1102/// right before its "extract" use. The tiled op is fused under the
1103/// `containingOp`.
1104/// Return this fused op on success or nullptr if anything fails.
1107 RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp,
1108 Operation *containingOp, ArrayRef<InnerTileAlignment> innerTileAlignments) {
1109 LDBG() << "Try to fuse an extract use through block argument";
1110
1111 auto tileableProducer = dyn_cast<TilingInterface>(producerOp);
1112 if (!tileableProducer) {
1113 diag.attachNote(producerOp->getLoc())
1114 << "producer is not a TileableInterface: " << *producerOp;
1115 return {};
1116 }
1117
1118 // Search the first use by a "scf::ForallOp" user.
1119 scf::ForallOp forallOp;
1120 auto itProducerUses =
1121 llvm::find_if(tileableProducer->getUses(), [&](OpOperand &use) {
1122 forallOp = dyn_cast<scf::ForallOp>(use.getOwner());
1123 return forallOp;
1124 });
1125 // If it's not from the containing op, return.
1126 if (!forallOp || forallOp != containingOp) {
1127 diag.attachNote(tileableProducer->getLoc())
1128 << "could not find a use by the containing op: " << *tileableProducer;
1129 return {};
1130 }
1131
1132 // Search the producer slices accessed within the containing
1133 // operation.
1134 // TODO: Generalize to more extract/insert/parallel_insert triples.
1135 // Maybe evolve into an interface.
1136 OpOperand *pUse = &(*itProducerUses);
1137 BlockArgument bbArg = forallOp.getTiedBlockArgument(pUse);
1138
1139 // Search the producer slices accessed within the containing operation.
1140 // TODO: Generalize to more extract/insert/parallel_insert triples, maybe
1141 // evolve into an interface.
1142 auto itBBArgUsers = llvm::find_if(bbArg.getUsers(), [&](Operation *user) {
1143 auto sliceOp = dyn_cast<tensor::ExtractSliceOp>(user);
1144 return sliceOp && containingOp->isProperAncestor(sliceOp);
1145 });
1146
1147 // Find a fusion opportunity.
1148 if (itBBArgUsers == bbArg.getUsers().end()) {
1149 diag.attachNote(containingOp->getLoc())
1150 << "could not find fusion opportunity for bbArg: " << bbArg;
1151 return {};
1152 }
1153 auto sliceOpToTile = cast<tensor::ExtractSliceOp>(*itBBArgUsers);
1154
1155 // Try to fuse the producer in-place.
1156 OpBuilder::InsertionGuard guard(rewriter);
1157 rewriter.setInsertionPoint(sliceOpToTile);
1158
1159 // Replace the use in the tileableProducer before tiling: clone, replace and
1160 // then tile.
1161 int64_t resultNumber = cast<OpResult>(pUse->get()).getResultNumber();
1162 LDBG() << "resultNumber: " << resultNumber;
1163
1164 // Gather destination tensors.
1165 SmallVector<Value> destinationTensors;
1167 rewriter, tileableProducer->getLoc(), tileableProducer,
1168 destinationTensors))) {
1169 diag.attachNote(tileableProducer->getLoc())
1170 << "failed to get destination tensors for: " << *tileableProducer;
1171 return {};
1172 }
1173
1174 IRMapping bvm;
1175 bvm.map(destinationTensors[resultNumber], bbArg);
1176 auto tileableProducerClone =
1177 cast<TilingInterface>(rewriter.clone(*tileableProducer, bvm));
1178 llvm::scope_exit scopeGuard(
1179 [&]() { rewriter.eraseOp(tileableProducerClone); });
1180
1181 // Tile the producer.
1182 FailureOr<TilingResult> tileAndFuseResult =
1183 tileableProducerClone.generateResultTileValue(
1184 rewriter, resultNumber, sliceOpToTile.getMixedOffsets(),
1185 sliceOpToTile.getMixedSizes(), innerTileAlignments);
1186 if (failed(tileAndFuseResult)) {
1187 diag.attachNote(tileableProducer->getLoc())
1188 << "failed to tile producer op: " << *tileableProducer;
1189 return {};
1190 }
1191
1192 // Replace the extract op.
1193 auto maybeRankReduced = tensor::ExtractSliceOp::rankReduceIfNeeded(
1194 rewriter, sliceOpToTile->getLoc(), tileAndFuseResult->tiledValues[0],
1195 cast<RankedTensorType>(sliceOpToTile->getResult(0).getType()).getShape());
1196 assert(succeeded(maybeRankReduced) && "unexpected shape");
1197 rewriter.replaceOp(sliceOpToTile, *maybeRankReduced);
1198
1199 // Replace the use in containingOp.
1200 rewriter.modifyOpInPlace(containingOp, [&]() {
1201 containingOp->setOperand(pUse->getOperandNumber(),
1202 destinationTensors.front());
1203 });
1204
1205 return tileAndFuseResult->tiledOps;
1206}
1207
1209 Operation *producerOp,
1210 Operation *containingOp) {
1211 LDBG() << "Try to fuse an use by cloning";
1212
1213 // Gather all uses inside the containing op.
1215 for (OpResult result : producerOp->getOpResults()) {
1216 for (OpOperand &use : result.getUses()) {
1217 if (containingOp->isProperAncestor(use.getOwner())) {
1218 uses.push_back(&use);
1219 continue;
1220 }
1221 // Cannot clone and fuse if the use is by the containing op itself: fail
1222 // immediately.
1223 if (containingOp == use.getOwner()) {
1224 diag.attachNote(producerOp->getLoc())
1225 << "producer op use by containing op cannot be fused by cloning";
1226 return nullptr;
1227 }
1228 }
1229 }
1230
1231 // Check for a non-empty list of fusion opportunities.
1232 if (uses.empty()) {
1233 diag.attachNote(producerOp->getLoc()) << "no fusion opportunity by cloning";
1234 return nullptr;
1235 }
1236
1237 // Clone and fuse inside the containing op.
1238 Operation *fusedOp = nullptr;
1239 OpOperand *use = uses.front();
1240 // Parallel insert slice is not a valid clone destination.
1241 // TODO: Generalize to other type of ops.
1242 assert(!isa<tensor::ParallelInsertSliceOp>(use->getOwner()) &&
1243 "Parallel insert slice is not a valid clone destination");
1244 unsigned resultNumber = cast<OpResult>(use->get()).getResultNumber();
1245 LDBG() << "resultNumber: " << resultNumber;
1246
1247 OpBuilder::InsertionGuard guard(rewriter);
1248 rewriter.setInsertionPoint(use->getOwner());
1249 fusedOp = rewriter.clone(*producerOp);
1250 rewriter.modifyOpInPlace(
1251 use->getOwner(), [&] { use->set(fusedOp->getOpResult(resultNumber)); });
1252
1253 return fusedOp;
1254}
1255
1256bool transform::FuseIntoContainingOp::allowsRepeatedHandleOperands() {
1257 // Allow repeated handles since we are fusing everything anyway.
1258 return true;
1259}
1260
1261LogicalResult transform::FuseIntoContainingOp::verify() {
1262 return verifyInnerTileAlignments(getOperation(), getInnerTileAlignments());
1263}
1264
1266transform::FuseIntoContainingOp::apply(transform::TransformRewriter &rewriter,
1269 SmallVector<Operation *> fusedOps;
1270 auto producerOps = state.getPayloadOps(getProducerOp());
1271 auto containingOps = state.getPayloadOps(getContainingOp());
1272 if (!llvm::hasSingleElement(containingOps)) {
1273 return emitDefiniteFailure()
1274 << "requires exactly one containing_op handle (got "
1275 << llvm::range_size(containingOps) << ")";
1276 }
1277 Operation *containingOp = *containingOps.begin();
1278
1279 // Forward the optional, caller-asserted alignment of a fused pack/unpack op's
1280 // inner tiles relative to the loop tile sizes (see InnerTileAlignment) to
1281 // each fused producer.
1282 SmallVector<InnerTileAlignment> innerTileAlignments =
1283 convertInnerTileAlignments(getInnerTileAlignments());
1284
1285 // If nothing to fuse, propagate success.
1286 if (std::empty(producerOps)) {
1287 results.set(cast<OpResult>(getFusedOp()), SmallVector<mlir::Operation *>{});
1288 results.set(cast<OpResult>(getNewContainingOp()), {containingOp});
1290 }
1291
1292 // Helper function to find the next producer that should be fused. Take any
1293 // producer that has a use inside the containing op.
1294 SetVector<Operation *> remainingProducers(llvm::from_range, producerOps);
1295 auto getNextProducer = [&]() -> FailureOr<Operation *> {
1296 for (const auto &it : enumerate(remainingProducers)) {
1297 Operation *producerOp = it.value();
1298 // The containing op may be a user of producerOp: use isAncestor.
1299 int64_t numUsesInContainingOp =
1300 llvm::count_if(producerOp->getUsers(), [&](Operation *op) {
1301 return containingOp->isAncestor(op);
1302 });
1303 // TODO: When resolving the TODO below (no duplicate ops), take an op
1304 // that has no use among the remaining producers. This is a topological
1305 // sorting.
1306 if (numUsesInContainingOp > 0) {
1307 if (numUsesInContainingOp == 1)
1308 remainingProducers.erase(remainingProducers.begin() + it.index());
1309 return producerOp;
1310 }
1311 }
1312 return failure();
1313 };
1314
1315 while (!remainingProducers.empty()) {
1316 auto nextProducer = getNextProducer();
1317 if (failed(nextProducer)) {
1318 auto diag = mlir::emitSilenceableFailure(getLoc())
1319 << "could not find next producer to fuse into container";
1320 diag.attachNote(containingOp->getLoc()) << "containing op";
1321 return diag;
1322 }
1323
1324 Operation *producerOp = *nextProducer;
1325
1326 // Default diagnostic, to be complemented with more failure information.
1328 diag << "could not fuse " << *producerOp << " into " << *containingOp;
1329
1330 // TODO: If there are multiple uses of the producer in the containing op,
1331 // we currently tile/clone the op multiple times (once per use). In some
1332 // cases, we can tile/clone once and reuse the value for each use.
1333 // Futhermore, producers should then be traversed according to a
1334 // topological sorting.
1335 auto [tiledOps, newContainingOp] = tileAndFuseFirstExtractUse(
1336 rewriter, diag, producerOp, containingOp, innerTileAlignments);
1337 if (!tiledOps.empty()) {
1338 LDBG() << "\nFused a direct extract use\n" << *containingOp;
1339 fusedOps.append(tiledOps);
1340 if (newContainingOp) {
1341 // Update handles associated with the containing op so we don't need to
1342 // invalidate them. This is a hack to support better composability
1343 // between tiling and fusion while a proper mechanism is being
1344 // investigated.
1345 //
1346 // DO NOT replicate this elsewhere unless you understand what you are
1347 // doing.
1348 LogicalResult replacementStatus =
1349 rewriter.notifyPayloadOperationReplaced(containingOp,
1350 newContainingOp);
1351 (void)replacementStatus;
1352 assert(succeeded(replacementStatus) &&
1353 "unable to update transform state mapping");
1354 rewriter.eraseOp(containingOp);
1355 containingOp = newContainingOp;
1356 }
1357 continue;
1358 }
1359
1360 SmallVector<Operation *> tiledContainingOpOperand =
1362 rewriter, diag, producerOp, containingOp, innerTileAlignments);
1363 if (!tiledContainingOpOperand.empty()) {
1364 LDBG() << "\nFused an extract use through block argument\n"
1365 << *containingOp;
1366 fusedOps.append(tiledContainingOpOperand);
1367 continue;
1368 }
1369
1370 Operation *cloned =
1371 cloneAndFuseFirstUse(rewriter, diag, producerOp, containingOp);
1372 if (cloned) {
1373 LDBG() << "\nFused an use by cloning\n" << *containingOp;
1374 fusedOps.push_back(cloned);
1375 continue;
1376 }
1378 }
1379
1380 results.set(cast<OpResult>(getFusedOp()), fusedOps);
1381 results.set(cast<OpResult>(getNewContainingOp()), {containingOp});
1383}
1384
1385void transform::FuseIntoContainingOp::getEffects(
1387 consumesHandle(getProducerOpMutable(), effects);
1388 onlyReadsHandle(getContainingOpMutable(), effects);
1389 producesHandle(getOperation()->getOpResults(), effects);
1390 modifiesPayload(effects);
1391}
1392
1393//===----------------------------------------------------------------------===//
1394// GeneralizeOp
1395//===----------------------------------------------------------------------===//
1396
1398transform::GeneralizeOp::applyToOne(transform::TransformRewriter &rewriter,
1399 LinalgOp target,
1402 // Exit early if no transformation is needed.
1403 if (isa<GenericOp>(target)) {
1404 results.push_back(target);
1406 }
1407 rewriter.setInsertionPoint(target);
1408 FailureOr<LinalgOp> generic = generalizeNamedOp(rewriter, target);
1409 if (succeeded(generic)) {
1410 results.push_back(generic->getOperation());
1412 }
1413 return emitDefaultSilenceableFailure(target);
1414}
1415
1416//===----------------------------------------------------------------------===//
1417// SpecializeOp
1418//===----------------------------------------------------------------------===/
1419
1421transform::SpecializeOp::applyToOne(transform::TransformRewriter &rewriter,
1422 LinalgOp target,
1425 // Exit early if the operation is not a generic.
1426 if (!isa<GenericOp>(target)) {
1427 results.push_back(target);
1429 }
1430 rewriter.setInsertionPoint(target);
1431 FailureOr<LinalgOp> named =
1432 specializeGenericOp(rewriter, cast<GenericOp>(target), getEmitCategory());
1433 if (succeeded(named)) {
1434 results.push_back(named->getOperation());
1436 }
1437 return emitDefaultSilenceableFailure(target);
1438}
1439
1440//===----------------------------------------------------------------------===//
1441// InterchangeOp
1442//===----------------------------------------------------------------------===//
1443
1445transform::InterchangeOp::applyToOne(transform::TransformRewriter &rewriter,
1446 GenericOp target,
1449 ArrayRef<int64_t> interchangeVector = getIteratorInterchange();
1450 // Exit early if no transformation is needed.
1451 if (interchangeVector.empty()) {
1452 results.push_back(target);
1454 }
1455
1456 unsigned numLoops = cast<LinalgOp>(target.getOperation()).getNumLoops();
1457 if (interchangeVector.size() != numLoops) {
1458 return emitSilenceableError()
1459 << getIteratorInterchangeAttrName() << " has length ("
1460 << interchangeVector.size()
1461 << ") different from the number of loops in the target operation ("
1462 << numLoops << ")";
1463 }
1464 FailureOr<GenericOp> res = interchangeGenericOp(
1465 rewriter, target, SmallVector<unsigned>(interchangeVector));
1466 if (failed(res))
1467 return emitDefiniteFailure() << "failed to apply";
1468 results.push_back(res->getOperation());
1470}
1471
1472LogicalResult transform::InterchangeOp::verify() {
1473 ArrayRef<int64_t> permutation = getIteratorInterchange();
1474 auto sequence = llvm::to_vector(llvm::seq<int64_t>(0, permutation.size()));
1475 if (!std::is_permutation(sequence.begin(), sequence.end(),
1476 permutation.begin(), permutation.end())) {
1477 return emitOpError()
1478 << "expects iterator_interchange to be a permutation, found "
1479 << getIteratorInterchange();
1480 }
1481 return success();
1482}
1483
1484//===----------------------------------------------------------------------===//
1485// LinalgCopyToMemrefOp
1486//===----------------------------------------------------------------------===//
1487
1488DiagnosedSilenceableFailure transform::LinalgCopyToMemrefOp::applyToOne(
1489 transform::TransformRewriter &rewriter, Operation *targetOp,
1492
1493 // Check if the target can be converted.
1494 if (!isa<linalg::CopyOp>(targetOp)) {
1496 emitSilenceableError() << "only linalg.copy target ops are supported";
1497 diag.attachNote(targetOp->getLoc()) << "target op";
1498 return diag;
1499 }
1500
1501 auto copyOp = dyn_cast<linalg::CopyOp>(targetOp);
1502 if (!copyOp.hasPureBufferSemantics()) {
1504 emitSilenceableError()
1505 << "cannot transform a linalg.copy on tensors into a memref.copy";
1506 diag.attachNote(targetOp->getLoc()) << "target op";
1507 return diag;
1508 }
1509
1510 SmallVector<Value> inputs = copyOp.getInputs();
1511 SmallVector<Value> outputs = copyOp.getOutputs();
1512 assert(inputs.size() == 1 && "expected linalg copy op with one input");
1513 assert(outputs.size() == 1 && "expected memref copy op with one output");
1514 Value input = inputs.front();
1515 Value output = outputs.front();
1516
1517 // linalg.copy supports different element types on source/dest whereas
1518 // memref.copy does not, so we must check that the source and dest types can
1519 // be handled by memref.copy and otherwise reject the transformation.
1520 if (!isa<ShapedType>(input.getType())) {
1522 emitSilenceableError()
1523 << "cannot transform a linalg.copy which input has no shape";
1524 diag.attachNote(targetOp->getLoc()) << "target op";
1525 return diag;
1526 }
1527
1528 // linalg.copy destination must be a shaped type.
1529 assert(isa<ShapedType>(output.getType()));
1530
1531 if (cast<ShapedType>(input.getType()).getElementType() !=
1532 cast<ShapedType>(output.getType()).getElementType()) {
1534 emitSilenceableError()
1535 << "cannot transform a linalg.copy with different source and "
1536 "destination element types ";
1537 diag.attachNote(targetOp->getLoc()) << "target op";
1538 return diag;
1539 }
1540
1541 // Target can be converted, do it.
1542 auto memrefCopyOp =
1543 rewriter.replaceOpWithNewOp<memref::CopyOp>(targetOp, input, output);
1544
1545 results.push_back(memrefCopyOp);
1547}
1548
1549//===----------------------------------------------------------------------===//
1550// LowerPackOp
1551//===----------------------------------------------------------------------===//
1552
1553DiagnosedSilenceableFailure transform::LowerPackOp::applyToOne(
1554 transform::TransformRewriter &rewriter, linalg::PackOp target,
1555 transform::ApplyToEachResultList &transformResults,
1557 if (!target.hasPureTensorSemantics()) {
1558 return mlir::emitSilenceableFailure(target->getLoc())
1559 << "lower_pack only supports tensor semantics. The target has "
1560 "memref operands";
1561 }
1562 rewriter.setInsertionPoint(target);
1563 bool lowerPadLikeWithInsertSlice = getLowerPadLikeWithInsertSlice();
1564 FailureOr<LowerPackResult> res =
1565 lowerPack(rewriter, target, lowerPadLikeWithInsertSlice);
1566 if (failed(res)) {
1567 return mlir::emitSilenceableFailure(target->getLoc())
1568 << "cannot lower to pad + expand + transpose";
1569 }
1570 transformResults.push_back(res->padOp);
1571 transformResults.push_back(res->expandShapeOp);
1572 transformResults.push_back(res->transposeOp);
1574}
1575
1576//===----------------------------------------------------------------------===//
1577// LowerUnPackOp
1578//===----------------------------------------------------------------------===//
1579
1580DiagnosedSilenceableFailure transform::LowerUnPackOp::applyToOne(
1581 transform::TransformRewriter &rewriter, linalg::UnPackOp target,
1582 transform::ApplyToEachResultList &transformResults,
1584 if (!target.hasPureTensorSemantics()) {
1586 emitSilenceableError() << "lower_unpack only supports tensor "
1587 "semantics. The target has memref operands";
1588 diag.attachNote(target->getLoc()) << "target payload op";
1589 return diag;
1590 }
1591 rewriter.setInsertionPoint(target);
1592 bool lowerUnpadLikeWithExtractSlice = getLowerUnpadLikeWithExtractSlice();
1593 FailureOr<LowerUnPackOpResult> res =
1594 lowerUnPack(rewriter, target, lowerUnpadLikeWithExtractSlice);
1595 if (failed(res)) {
1597 emitSilenceableError()
1598 << "cannot lower to transpose + collapse + extract";
1599 diag.attachNote(target->getLoc()) << "target payload op";
1600 return diag;
1601 }
1602 transformResults.push_back(res->emptyOp);
1603 transformResults.push_back(res->transposeOp);
1604 transformResults.push_back(res->collapseShapeOp);
1605 transformResults.push_back(res->extractSliceOp);
1606 transformResults.push_back(res->copyOp);
1608}
1609
1610//===---------------------------------------------------------------------===//
1611// MatchOp
1612//===---------------------------------------------------------------------===//
1613
1614void transform::MatchOp::build(OpBuilder &builder, OperationState &result,
1615 Value target, ArrayRef<StringRef> opNames) {
1616 result.addOperands(target);
1617 result.addAttribute(MatchOp::getOpsAttrName(result.name),
1618 builder.getStrArrayAttr(opNames));
1619 result.addTypes(transform::AnyOpType::get(builder.getContext()));
1620}
1621
1622void transform::MatchOp::build(OpBuilder &builder, OperationState &result,
1623 TypeRange resultTypes, Value target,
1624 ArrayRef<StringRef> opNames) {
1625 result.addOperands(target);
1626 result.addAttribute(MatchOp::getOpsAttrName(result.name),
1627 builder.getStrArrayAttr(opNames));
1628 result.addTypes(resultTypes);
1629}
1630
1632transform::MatchOp::apply(transform::TransformRewriter &rewriter,
1635 llvm::StringSet<> strs;
1636 if (getOps().has_value())
1637 strs.insert_range(getOps()->getAsValueRange<StringAttr>());
1638
1639 auto payloadOps = state.getPayloadOps(getTarget());
1640 if (!llvm::hasSingleElement(payloadOps)) {
1641 return emitDefiniteFailure("requires exactly one target handle");
1642 }
1643
1645 bool incorrectNumOperandTypes = false;
1646 auto matchFun = [&](Operation *op) {
1647 if (getOps().has_value() && !strs.contains(op->getName().getStringRef()))
1648 return;
1649
1650 // Interfaces cannot be matched by name, just by ID.
1651 // So we specifically encode the interfaces we care about for this op.
1652 if (getInterface().has_value()) {
1653 auto iface = getInterface().value();
1654 if (iface == transform::MatchInterfaceEnum::LinalgOp &&
1655 !isa<LinalgOp>(op))
1656 return;
1657 if (iface == transform::MatchInterfaceEnum::TilingInterface &&
1658 !isa<TilingInterface>(op))
1659 return;
1660 if (iface == transform::MatchInterfaceEnum::LoopLikeInterface &&
1661 !isa<LoopLikeOpInterface>(op))
1662 return;
1663 }
1664
1665 // Check if all specified attributes match.
1666 if (getOpAttrs().has_value()) {
1667 DictionaryAttr opAttrs = getOpAttrs().value();
1668 for (NamedAttribute attr : opAttrs) {
1669 if (attr.getName() == getInterfaceAttrName() ||
1670 attr.getName() == getOpsAttrName())
1671 continue;
1672 std::optional<Attribute> inherent = op->getInherentAttr(attr.getName());
1673 Attribute actual = inherent.has_value()
1674 ? *inherent
1675 : op->getDiscardableAttr(attr.getName());
1676 if (!actual)
1677 return;
1678 if (actual != attr.getValue())
1679 return;
1680 }
1681 }
1682
1683 if (getFilterResultType().has_value()) {
1684 Type t = getFilterResultType().value();
1685 if (op->getNumResults() != 1 || op->getResultTypes().front() != t)
1686 return;
1687 }
1688
1689 if (getFilterOperandTypes().has_value()) {
1690 mlir::ArrayAttr types = getFilterOperandTypes().value();
1691 auto operandTypes = op->getOperandTypes();
1692
1693 if (types.size() == 1) {
1694 // All the operands must must be equal to the specified type
1695 auto typeattr =
1696 dyn_cast<mlir::TypeAttr>(getFilterOperandTypes().value()[0]);
1697 Type t = cast<::mlir::Type>(typeattr.getValue());
1698 if (!llvm::all_of(op->getOperandTypes(),
1699 [&](Type operandType) { return operandType == t; }))
1700 return;
1701 } else {
1702 // The operand types must match all the types in the list (in the same
1703 // order in with they are specified)
1704 if (types.size() != operandTypes.size()) {
1705 incorrectNumOperandTypes = true;
1706 return;
1707 }
1708
1709 for (auto [attr, operandType] :
1710 llvm::zip_equal(getFilterOperandTypes().value(), operandTypes)) {
1711 auto typeattr = cast<mlir::TypeAttr>(attr);
1712 Type type = cast<::mlir::Type>(typeattr.getValue());
1713
1714 if (type != operandType)
1715 return;
1716 }
1717 }
1718 }
1719
1720 // All constraints are satisfied.
1721 res.push_back(op);
1722 return;
1723 };
1724
1725 (*payloadOps.begin())->walk(matchFun);
1726 if (incorrectNumOperandTypes)
1727 return emitDefiniteFailure("If filter_operand_types contains more than a "
1728 "type, then it must contain as much types as "
1729 "the number of operands in the target ops");
1730 results.set(cast<OpResult>(getResult()), res);
1732}
1733
1734//===---------------------------------------------------------------------===//
1735// MultiTileSizesOp
1736//===---------------------------------------------------------------------===//
1737
1739 Type targetType, Type lowSizeType, Type,
1740 Type) {
1741 printer.printFunctionalType(TypeRange{targetType}, TypeRange{lowSizeType});
1742}
1743
1744static ParseResult parseMultitileSizesTypes(OpAsmParser &parser,
1745 Type &targetType, Type &lowSizeType,
1746 Type &highSizeType,
1747 Type &splitPointType) {
1748 FunctionType funcType;
1749 llvm::SMLoc typeLoc = parser.getCurrentLocation();
1750 if (failed(parser.parseType<FunctionType>(funcType)))
1751 return failure();
1752
1753 if (funcType.getNumInputs() != 1 || funcType.getNumResults() != 1) {
1754 parser.emitError(typeLoc) << "expects a trailing functional type with one "
1755 "argument and one result";
1756 }
1757 targetType = funcType.getInput(0);
1758 lowSizeType = highSizeType = splitPointType = funcType.getResult(0);
1759
1760 return success();
1761}
1762
1763DiagnosedSilenceableFailure transform::MultiTileSizesOp::applyToOne(
1764 transform::TransformRewriter &rewriter, LinalgOp target,
1766 if (isa<TransformParamTypeInterface>(getLowSize().getType())) {
1767 if (target.hasDynamicShape()) {
1768 auto diag = emitSilenceableError()
1769 << "cannot compute parametric tile sizes for dynamically "
1770 "shaped payload op";
1771 diag.attachNote(target->getLoc()) << "payload op";
1772 return diag;
1773 }
1774
1775 FailureOr<StaticMultiSizeSpecification> spec = computeStaticMultiTileSizes(
1776 target, getDimension(), getTargetSize(), getDivisor());
1777 if (failed(spec)) {
1778 return emitSilenceableError()
1779 << "failed to compute multi-size tiling sizes";
1780 }
1781
1782 Builder builder(target.getContext());
1783 results.assign(llvm::map_range(
1784 ArrayRef<int64_t>({spec->lowTileSize, spec->highTileSize,
1785 spec->lowTileSize * spec->lowTripCount}),
1786 [&builder, this](int64_t value) {
1787 return builder.getIntegerAttr(
1788 cast<ParamType>(getLowSize().getType()).getType(), value);
1789 }));
1791 }
1792
1793 OpBuilder builder(target.getContext());
1794 builder.setInsertionPoint(target);
1795 OpFoldResult targetSize = builder.getIndexAttr(getTargetSize());
1796 OpFoldResult divisor = builder.getIndexAttr(getDivisor());
1797 FailureOr<MultiSizeSpecification> spec = computeMultiTileSizes(
1798 builder, target, getDimension(), targetSize, divisor);
1799 if (failed(spec)) {
1800 return emitSilenceableError() << "could not generate tile size computation";
1801 }
1802
1803 AffineExpr s0 = builder.getAffineSymbolExpr(0);
1804 AffineExpr s1 = builder.getAffineSymbolExpr(1);
1805 Operation *splitPoint =
1806 affine::makeComposedAffineApply(builder, target.getLoc(), s0 * s1,
1807 {spec->lowTileSize, spec->lowTripCount});
1808 Operation *lowTileSize = spec->lowTileSize.getDefiningOp();
1809 Operation *highTileSize = spec->highTileSize.getDefiningOp();
1810 assert(lowTileSize && highTileSize && splitPoint &&
1811 "tile sizes are not produced by operations");
1812 results.reserve(results.size() + 3);
1813 results.push_back(lowTileSize);
1814 results.push_back(highTileSize);
1815 results.push_back(splitPoint);
1817}
1818
1819void transform::MultiTileSizesOp::getEffects(
1821 onlyReadsHandle(getTargetMutable(), effects);
1822 producesHandle(getOperation()->getOpResults(), effects);
1823 if (isa<TransformParamTypeInterface>(getLowSize().getType()))
1824 onlyReadsPayload(effects);
1825 else
1826 modifiesPayload(effects);
1827}
1828
1829LogicalResult transform::MultiTileSizesOp::verify() {
1830 if (getLowSize().getType() != getHighSize().getType() ||
1831 getLowSize().getType() != getSplitPoint().getType()) {
1832 return emitOpError() << "expects all results type to be the same";
1833 }
1834 return success();
1835}
1836
1837//===---------------------------------------------------------------------===//
1838// PackOp
1839//===---------------------------------------------------------------------===//
1840
1841void transform::PackOp::build(OpBuilder &builder, OperationState &result,
1842 Value target,
1843 ArrayRef<OpFoldResult> mixedPackedSizes) {
1844 SmallVector<int64_t> staticPackedSizes;
1845 SmallVector<Value> dynamicPackedSizes;
1846 dispatchIndexOpFoldResults(mixedPackedSizes, dynamicPackedSizes,
1847 staticPackedSizes);
1848 // Call the default builder which sets up the proper operands segment sizes
1849 // attributes for multiple variadic operands. In the absence of this, horrible
1850 // bugs ensue.
1851 Type linalgOpHType = transform::OperationType::get(
1852 builder.getContext(), GenericOp::getOperationName());
1853 build(builder, result,
1854 /*resultType=*/linalgOpHType,
1855 /*target=*/target,
1856 /*dynamic_sizes=*/dynamicPackedSizes,
1857 /*static_sizes=*/builder.getDenseI64ArrayAttr(staticPackedSizes));
1858}
1859
1860SmallVector<OpFoldResult> transform::PackOp::getMixedPackedSizes() {
1861 Builder b(getContext());
1862 return getMixedValues(getStaticPackedSizes(), getPackedSizes(), b);
1863}
1864
1866transform::PackOp::apply(transform::TransformRewriter &rewriter,
1867 transform::TransformResults &transformResults,
1869 auto targetOps = state.getPayloadOps(getTarget());
1870 // If nothing to pack, propagate success.
1871 if (std::empty(targetOps)) {
1872 transformResults.set(cast<OpResult>(getPackedOp()),
1875 }
1876 // Fail on multi-op handles.
1877 auto linalgOp = dyn_cast<LinalgOp>(*targetOps.begin());
1878 if (!llvm::hasSingleElement(targetOps) || !linalgOp) {
1879 return emitSilenceableError()
1880 << "requires target to map to exactly 1 LinalgOp (got "
1881 << llvm::range_size(targetOps) << ")";
1882 }
1883 // Fail on memref operands: pack only supports tensor semantics.
1884 if (!linalgOp.hasPureTensorSemantics()) {
1885 return emitSilenceableError()
1886 << "structured.pack only supports tensor semantics. The target has "
1887 "memref operands";
1888 }
1889 // Fail on mismatched number of pack sizes.
1890 if (getMixedPackedSizes().size() != linalgOp.getNumLoops()) {
1891 return emitSilenceableError()
1892 << "requires number of packed sizes match the number of loops ("
1893 << getMixedPackedSizes().size() << " vs " << linalgOp.getNumLoops()
1894 << ")";
1895 }
1896
1897 // Unpack handles to constants or actual SSA index values.
1898 SmallVector<OpFoldResult> packedSizes;
1900 state, *this, packedSizes, getMixedPackedSizes());
1901
1902 rewriter.setInsertionPoint(linalgOp);
1903 FailureOr<PackResult> maybeResult = pack(rewriter, linalgOp, packedSizes);
1904 if (failed(maybeResult))
1905 return emitDefiniteFailure("data tiling failed");
1906
1907 transformResults.set(cast<OpResult>(getPackedOp()),
1908 {maybeResult->packedLinalgOp.getOperation()});
1910}
1911
1912void transform::PackOp::getEffects(
1914 transform::consumesHandle(getTargetMutable(), effects);
1915 transform::onlyReadsHandle(getPackedSizesMutable(), effects);
1916 transform::producesHandle(getOperation()->getOpResults(), effects);
1918}
1919
1920//===---------------------------------------------------------------------===//
1921// PackGreedilyOp.
1922//===---------------------------------------------------------------------===//
1923
1924LogicalResult transform::PackGreedilyOp::verify() {
1925 if (!isPermutationVector(getMatmulInnerDimsOrder())) {
1926 return emitOpError() << getMatmulInnerDimsOrderAttrName()
1927 << " is not a valid permutation";
1928 }
1929 // TODO: relax to allow empty once we have another strategy than just matmul.
1930 if (!getMatmulPaddedSizesNextMultipleOf().empty()) {
1931 for (auto [s, nmo] :
1932 llvm::zip_equal(getMixedMatmulPackedSizes(),
1933 getMatmulPaddedSizesNextMultipleOf())) {
1934 std::optional<int64_t> maybeStaticPackedSize = getConstantIntValue(s);
1935 if (nmo != 0 &&
1936 (!maybeStaticPackedSize.has_value() || *maybeStaticPackedSize != 0)) {
1937 return emitOpError() << "at most one of the packed_size and the "
1938 "padded_sizes_next_multiple_of can be nonzero "
1939 "for the matmul strategy";
1940 }
1941 }
1942 }
1943 return success();
1944}
1945
1947PackGreedilyOp::apply(transform::TransformRewriter &rewriter,
1948 transform::TransformResults &transformResults,
1951 for (auto linalgOp :
1952 llvm::make_isa_range<LinalgOp>(state.getPayloadOps(getTarget()))) {
1953 // linalgOp will be replaced and the insertion point may be invalidated if
1954 // we set it before -> set it after.
1955 rewriter.setInsertionPointAfter(linalgOp);
1956 // Failing to pack greedily is perfectly fine.
1957 // In the future we will want to order packings according to some metric.
1958 FailureOr<PackResult> packResult = packMatmulGreedily(
1959 /*rewriter=*/rewriter,
1960 /*linalgOp=*/linalgOp,
1961 /*mnkPackedSizes=*/getMixedMatmulPackedSizes(),
1962 /*mnkPaddedSizesNextMultipleOf=*/
1963 getMatmulPaddedSizesNextMultipleOf(),
1964 /*mnkOrder=*/getMatmulInnerDimsOrder());
1965 if (succeeded(packResult)) {
1966 results.push_back(packResult->packedLinalgOp);
1967 continue;
1968 }
1969 results.push_back(linalgOp);
1970 }
1971 transformResults.set(cast<OpResult>(getPackedOp()), results);
1973}
1974
1975SmallVector<OpFoldResult> PackGreedilyOp::getMixedMatmulPackedSizes() {
1976 Builder b(getContext());
1977 return getMixedValues(getStaticMatmulPackedSizes(), getMatmulPackedSizes(),
1978 b);
1979}
1980
1981void transform::PackGreedilyOp::getEffects(
1983 transform::consumesHandle(getTargetMutable(), effects);
1984 transform::onlyReadsHandle(getMatmulPackedSizesMutable(), effects);
1985 transform::producesHandle(getOperation()->getOpResults(), effects);
1987}
1988
1989//===---------------------------------------------------------------------===//
1990// PackTransposeOp
1991//===---------------------------------------------------------------------===//
1992
1993LogicalResult transform::PackTransposeOp::verify() {
1994 if (!isPermutationVector(getInnerPerm())) {
1995 return emitOpError() << getInnerPermAttrName()
1996 << " is not a valid permutation";
1997 }
1998 if (!isPermutationVector(getOuterPerm())) {
1999 return emitOpError() << getOuterPermAttrName()
2000 << " is not a valid permutation";
2001 }
2002 if (getInnerPerm().empty() && getOuterPerm().empty()) {
2003 return emitOpError() << " at least one of " << getInnerPermAttrName()
2004 << " or " << getOuterPermAttrName()
2005 << " must be specified";
2006 }
2007 return success();
2008}
2009
2010namespace {
2011enum class OuterOrInnerPerm { Outer = 0, Inner = 1 };
2012} // namespace
2013
2014/// Return true if `permutation` is a valid permutation of the
2015/// `outer_dims_perm` (case OuterOrInnerPerm::Outer) or `inner_dims_pos`
2016/// (OuterOrInnerPerm::Inner) of the `tensor.pack` or `tensor.unpack` `op.
2017/// This is the case when the `permutation` rank matches the rank expected by
2018/// `op` and `permutation` is itself a permutation vector.
2019/// Return true if either `op` or `permutation` are empty to allow a simpler
2020/// polymorphic implementation.
2021template <typename RelayoutOpTy>
2022static bool isValidPackingPermutation(
2023 RelayoutOpTy op, ArrayRef<int64_t> permutation,
2024 OuterOrInnerPerm outerOrInnerPerm = OuterOrInnerPerm::Outer) {
2025 static_assert(
2026 llvm::is_one_of<RelayoutOpTy, linalg::PackOp, linalg::UnPackOp>::value,
2027 "applies to only pack or unpack operations");
2028 if (!op || permutation.empty())
2029 return true;
2030 size_t innerRank = op.getInnerDimsPos().size();
2031 if (outerOrInnerPerm == OuterOrInnerPerm::Inner)
2032 return permutation.size() == innerRank && isPermutationVector(permutation);
2033 // op.getOuterDimsPerm() may be empty, in which case it is identity.
2034 // Don't rely on it.
2035 if (std::is_same<RelayoutOpTy, linalg::PackOp>::value) {
2036 return permutation.size() == op.getSourceRank() &&
2037 isPermutationVector(permutation);
2038 }
2039 return permutation.size() == op.getDestRank() &&
2040 isPermutationVector(permutation);
2041}
2042
2044transform::PackTransposeOp::apply(transform::TransformRewriter &rewriter,
2045 transform::TransformResults &transformResults,
2047 auto packOrUnpackOps = state.getPayloadOps(getTargetPackOrUnPackOp());
2048 auto linalgOps = state.getPayloadOps(getTargetLinalgOp());
2049 // Step 1. If nothing to pack, propagate success.
2050 if (std::empty(packOrUnpackOps)) {
2051 transformResults.set(cast<OpResult>(getPackedOp()), {});
2052 transformResults.set(cast<OpResult>(getPackOp()), {});
2053 transformResults.set(cast<OpResult>(getUnPackOp()), {});
2055 }
2056
2057 // Step 2. Bunch of runtime sanity check and error messages.
2058 // Step 2.1. Fail on multi-op handles.
2059 if (!llvm::hasSingleElement(packOrUnpackOps) ||
2060 !llvm::hasSingleElement(linalgOps)) {
2061 return emitSilenceableError()
2062 << "requires target to map to exactly 1 "
2063 "packing op and 1 packed op ("
2064 << "got " << llvm::range_size(packOrUnpackOps) << " and "
2065 << llvm::range_size(linalgOps) << ")";
2066 }
2067
2068 // Step 2.2. Fail on wrong type.
2069 auto packOp = dyn_cast<linalg::PackOp>(*packOrUnpackOps.begin());
2070 auto unPackOp = dyn_cast<linalg::UnPackOp>(*packOrUnpackOps.begin());
2071 if ((!packOp && !unPackOp)) {
2072 return emitSilenceableError() << "requires target to map to a "
2073 "linalg.pack or linalg.unpack";
2074 }
2075 LinalgOp linalgOpTarget = dyn_cast<LinalgOp>(*linalgOps.begin());
2076 if (!linalgOpTarget)
2077 return emitSilenceableError() << "requires a LinalgOp target";
2078
2079 // Step 2.3. Fail if we can't get the producer / consumer Linalg op.
2080 LinalgOp linalgOp;
2081 if (packOp && packOp.getResult().hasOneUse())
2082 linalgOp = dyn_cast<LinalgOp>(*(packOp.getResult().getUsers().begin()));
2083 else if (unPackOp)
2084 linalgOp = unPackOp.getSource().getDefiningOp<LinalgOp>();
2085 if (linalgOp != linalgOpTarget) {
2086 auto errorMsg =
2087 packOp ? StringLiteral{"not a single use by the LinalgOp target"}
2088 : StringLiteral{"not produced by the LinalgOp target"};
2089 return emitSilenceableError() << errorMsg;
2090 }
2091
2092 // Step 2.4. If we have an UnPackOp, we need to fetch the symmetrical
2093 // PackOp.
2094 if (unPackOp) {
2095 assert(!packOp && "packOp must be null on entry when unPackOp is not null");
2096 OpOperand *packUse = linalgOp.getDpsInitOperand(
2097 cast<OpResult>(unPackOp.getSource()).getResultNumber());
2098 packOp = packUse->get().getDefiningOp<linalg::PackOp>();
2099 if (!packOp || !packOp.getResult().hasOneUse())
2100 return emitSilenceableError() << "could not find matching pack op";
2101 }
2102
2103 // Step 2.5. Fail if any permutation does not validate.
2104 for (auto permType : {OuterOrInnerPerm::Outer, OuterOrInnerPerm::Inner}) {
2105 ArrayRef<int64_t> perm =
2106 (permType == OuterOrInnerPerm::Outer) ? getOuterPerm() : getInnerPerm();
2107 auto errorMsg = (permType == OuterOrInnerPerm::Outer)
2108 ? StringLiteral{"invalid outer_perm"}
2109 : StringLiteral{"invalid inner_perm"};
2110 if (!isValidPackingPermutation(packOp, perm, permType) ||
2111 !isValidPackingPermutation(unPackOp, perm, permType)) {
2112 Operation *packOrUnpackOp =
2113 unPackOp ? unPackOp.getOperation() : packOp.getOperation();
2114 return emitSilenceableError() << errorMsg << ": " << *packOrUnpackOp;
2115 }
2116 }
2117
2118 // From here on, packOp and linalgOp are always present, unPackOp may or may
2119 // not be present.
2120 assert(packOp && linalgOp && "unexpected null op");
2121
2122 // Step 3. Actually transpose the ops.
2123 FailureOr<PackTransposeResult> res = packTranspose(
2124 rewriter, packOp, linalgOp, unPackOp, getOuterPerm(), getInnerPerm());
2125 // Preconditions have been checked, it is an error to fail here.
2126 assert(succeeded(res) && "unexpected packTranspose failure");
2127
2128 // Step 4. Return results.
2129 transformResults.set(cast<OpResult>(getPackOp()), {res->transposedPackOp});
2130 transformResults.set(cast<OpResult>(getPackedOp()),
2131 {res->transposedLinalgOp});
2132 if (unPackOp) {
2133 transformResults.set(cast<OpResult>(getUnPackOp()),
2134 {res->transposedUnPackOp});
2135 } else {
2136 transformResults.set(cast<OpResult>(getUnPackOp()), {});
2137 }
2138
2140}
2141
2142//===---------------------------------------------------------------------===//
2143// PadOp
2144//===---------------------------------------------------------------------===//
2145
2146void transform::PadOp::build(OpBuilder &b, OperationState &result, Value target,
2147 ArrayRef<int64_t> paddingDimensions,
2148 ArrayRef<int64_t> padToMultipleOf,
2149 ArrayRef<int64_t> nofoldFlags,
2150 ArrayRef<Attribute> transposePaddings,
2151 StringRef copyBackOp,
2152 bool usePrescribedTensorShapes) {
2153 auto resultType = transform::AnyOpType::get(b.getContext());
2154 return build(/*odsBuilder=*/b,
2155 /*result=*/result,
2156 /*types=*/TypeRange{resultType, resultType},
2157 /*target=*/target,
2158 /*padding_values=*/ArrayAttr(), // let inference handle this
2159 /*padding_dimensions=*/b.getI64ArrayAttr(paddingDimensions),
2160 /*pad_to_multiple_of=*/ValueRange{},
2161 /*padToMultipleOf=*/
2162 (padToMultipleOf.empty()
2164 : b.getDenseI64ArrayAttr(padToMultipleOf)),
2165 /*nofold_flags=*/b.getI64ArrayAttr(nofoldFlags),
2166 /*transpose_paddings=*/b.getArrayAttr(transposePaddings),
2167 /*copy_back_op=*/b.getStringAttr(copyBackOp),
2168 /*use_prescribed_tensor_shapes=*/
2169 usePrescribedTensorShapes ? b.getUnitAttr() : nullptr);
2170}
2171
2172void transform::PadOp::build(OpBuilder &b, OperationState &result, Value target,
2173 ArrayRef<int64_t> paddingDimensions,
2174 ArrayRef<OpFoldResult> mixedPadToMultipleOf,
2175 ArrayRef<int64_t> nofoldFlags,
2176 ArrayRef<Attribute> transposePaddings,
2177 StringRef copyBackOp,
2178 bool usePrescribedTensorShapes) {
2179 auto resultType = transform::AnyOpType::get(b.getContext());
2180 SmallVector<int64_t> staticPadToMultipleOf;
2181 SmallVector<Value> dynamicPadToMultipleOf;
2182 dispatchIndexOpFoldResults(mixedPadToMultipleOf, dynamicPadToMultipleOf,
2183 staticPadToMultipleOf);
2184 return build(/*odsBuilder=*/b,
2185 /*result=*/result,
2186 /*types=*/TypeRange{resultType, resultType},
2187 /*target=*/target,
2188 /*padding_values=*/ArrayAttr(), // let inference handle this
2189 /*padding_dimensions=*/b.getI64ArrayAttr(paddingDimensions),
2190 /*pad_to_multiple_of=*/dynamicPadToMultipleOf,
2191 /*padToMultipleOf=*/staticPadToMultipleOf,
2192 /*nofold_flags=*/b.getI64ArrayAttr(nofoldFlags),
2193 /*transpose_paddings=*/b.getArrayAttr(transposePaddings),
2194 /*copy_back_op=*/copyBackOp,
2195 /*use_prescribed_tensor_shapes=*/usePrescribedTensorShapes);
2196}
2197
2198void PadOp::getEffects(
2200 consumesHandle(getTargetMutable(), effects);
2201 onlyReadsHandle(getPadToMultipleOfMutable(), effects);
2202 producesHandle(getOperation()->getOpResults(), effects);
2203 modifiesPayload(effects);
2204}
2205
2206SmallVector<OpFoldResult> PadOp::getMixedPadToMultipleOf() {
2207 Builder b(getContext());
2208 return getMixedValues(getStaticPadToMultipleOf(), getPadToMultipleOf(), b);
2209}
2210
2211DiagnosedSilenceableFailure
2212transform::PadOp::apply(transform::TransformRewriter &rewriter,
2213 transform::TransformResults &results,
2214 transform::TransformState &state) {
2215 auto transformOp = cast<TransformOpInterface>(getOperation());
2216 SmallVector<Operation *> paddedOps, padOps, copyBackOps;
2217
2218 for (Operation *target : state.getPayloadOps(getTarget())) {
2219 auto linalgTarget = dyn_cast<LinalgOp>(target);
2220 if (!linalgTarget) {
2221 auto diag = emitSilenceableError() << "expected LinalgOp target";
2222 diag.attachNote(target->getLoc()) << "target op";
2223 return diag;
2224 }
2225
2226 // Convert the integer packing flags to booleans.
2227 SmallVector<bool> nofoldFlags;
2228 for (int64_t packPadding :
2229 extractFromIntegerArrayAttr<int64_t>(getNofoldFlags()))
2230 nofoldFlags.push_back(static_cast<bool>(packPadding));
2231
2232 // Convert the padding values to attributes.
2233 SmallVector<Attribute> paddingValues;
2234 for (auto const &[untypedAttr, elementOrTensorType] :
2235 llvm::zip(getPaddingValues(), linalgTarget->getOperandTypes())) {
2236
2237 if (matchPattern(untypedAttr, ub::m_Poison())) {
2238 paddingValues.push_back(untypedAttr);
2239 continue;
2240 }
2241 auto attr = dyn_cast<TypedAttr>(untypedAttr);
2242 if (!attr) {
2243 emitOpError("expects padding values to be typed attributes or poison");
2245 }
2246 Type elementType = getElementTypeOrSelf(elementOrTensorType);
2247 // Try to parse string attributes to obtain an attribute of element type.
2248 if (auto stringAttr = dyn_cast<StringAttr>(attr)) {
2249 auto parsedAttr = dyn_cast_if_present<TypedAttr>(parseAttribute(
2250 stringAttr, getContext(), elementType,
2251 /*numRead=*/nullptr, /*isKnownNullTerminated=*/true));
2252 if (!parsedAttr || parsedAttr.getType() != elementType) {
2253 auto diag = this->emitOpError("expects a padding that parses to ")
2254 << elementType << ", got " << untypedAttr;
2255 diag.attachNote(linalgTarget.getLoc()) << "when applied to this op";
2257 }
2258 paddingValues.push_back(parsedAttr);
2259 continue;
2260 }
2261 // Otherwise, add the attribute directly.
2262 if (attr.getType() != elementType) {
2263 auto diag = this->emitOpError("expects a padding value of type ")
2264 << elementType << ", got " << attr;
2265 diag.attachNote(linalgTarget.getLoc()) << "when applied to this op";
2267 }
2268 paddingValues.push_back(attr);
2269 }
2270
2271 // Extract the transpose vectors.
2272 SmallVector<SmallVector<int64_t>> transposePaddings;
2273 for (Attribute transposeVector : cast<ArrayAttr>(getTransposePaddings()))
2274 transposePaddings.push_back(extractFromIntegerArrayAttr<int64_t>(
2275 cast<ArrayAttr>(transposeVector)));
2276
2277 LinalgOp paddedOp;
2278 LinalgPaddingOptions options;
2279 options.paddingDimensions =
2280 extractFromIntegerArrayAttr<int64_t>(getPaddingDimensions());
2281
2282 SmallVector<int64_t> padToMultipleOf;
2283 DiagnosedSilenceableFailure status = reifyMixedParamAndHandleResults(
2284 state, transformOp, getMixedPadToMultipleOf(), padToMultipleOf);
2285 if (!status.succeeded())
2286 return status;
2287 if (padToMultipleOf.empty())
2288 padToMultipleOf =
2289 SmallVector<int64_t>(options.paddingDimensions.size(), 1);
2290
2291 options.padToMultipleOf = std::move(padToMultipleOf);
2292 options.paddingValues = std::move(paddingValues);
2293 options.nofoldFlags = std::move(nofoldFlags);
2294 if (getCopyBackOp() ==
2295 bufferization::MaterializeInDestinationOp::getOperationName()) {
2296 options.copyBackOp = LinalgPaddingOptions::CopyBackOp::
2297 BufferizationMaterializeInDestination;
2298 } else if (getCopyBackOp() == linalg::CopyOp::getOperationName()) {
2299 options.copyBackOp = LinalgPaddingOptions::CopyBackOp::LinalgCopy;
2300 } else if (getCopyBackOp() == kCopyOpNone) {
2301 options.copyBackOp = LinalgPaddingOptions::CopyBackOp::None;
2302 } else {
2303 llvm_unreachable("unsupported copy_back op");
2304 }
2305 // Populate `sizeToPadTo` with the dynamic tensor sizes for each operand.
2306 bool irChanged = false;
2307 if (getUsePrescribedTensorShapes() &&
2308 linalgTarget.hasPureTensorSemantics()) {
2309 OpBuilder::InsertionGuard g(rewriter);
2310 rewriter.setInsertionPoint(linalgTarget);
2311 for (OpOperand &operand : linalgTarget->getOpOperands()) {
2312 for (auto [i, dim] : llvm::enumerate(linalgTarget.getShape(&operand))) {
2313 if (ShapedType::isStatic(dim))
2314 continue;
2315 options.setSizeToPadTo(operand.getOperandNumber(), i,
2316 tensor::getMixedSize(rewriter,
2317 operand.get().getLoc(),
2318 operand.get(), i));
2319 irChanged = true;
2320 }
2321 }
2322 }
2323
2324 SmallVector<Value> replacements;
2325 SmallVector<tensor::PadOp> newPadOps;
2326 if (failed(rewriteAsPaddedOp(rewriter, linalgTarget, options, paddedOp,
2327 replacements, newPadOps))) {
2328 if (irChanged) {
2329 auto diag = emitDefiniteFailure() << "failed to pad op";
2330 diag.attachNote(target->getLoc()) << "target op";
2331 return diag;
2332 }
2333 auto diag = emitSilenceableError() << "failed to pad op";
2334 diag.attachNote(target->getLoc()) << "target op";
2335 return diag;
2336 }
2337
2338 // We need to perform our own replacement here because this API is still
2339 // used in patterns that "pad and hoist", for which the replacement values
2340 // need to be different.
2341 // TODO: clean this up and stop "pad and hoist" behavior more globally now
2342 // that we have more composable abstractions.
2343 rewriter.replaceOp(linalgTarget, replacements);
2344 paddedOps.push_back(paddedOp);
2345 padOps.append(newPadOps.begin(), newPadOps.end());
2346 if (options.copyBackOp != LinalgPaddingOptions::CopyBackOp::None) {
2347 for (Value v : replacements) {
2348 Operation *copyBackOp = v.getDefiningOp();
2349 if (!llvm::is_contained(copyBackOps, copyBackOp))
2350 copyBackOps.push_back(copyBackOp);
2351 }
2352 }
2353 }
2354
2355 results.set(cast<OpResult>(getPadded()), paddedOps);
2356 results.set(cast<OpResult>(getPad()), padOps);
2357 results.set(cast<OpResult>(getCopy()), copyBackOps);
2359}
2360
2361LogicalResult transform::PadOp::verify() {
2362 SmallVector<int64_t> nofoldFlags =
2363 extractFromIntegerArrayAttr<int64_t>(getNofoldFlags());
2364 if (any_of(nofoldFlags, [](int64_t packPadding) {
2365 return packPadding != 0 && packPadding != 1;
2366 })) {
2367 return emitOpError()
2368 << "expects nofold_flags to contain booleans (0/1), found "
2369 << getNofoldFlags();
2370 }
2371
2372 SmallVector<int64_t> paddingDimensions =
2373 extractFromIntegerArrayAttr<int64_t>(getPaddingDimensions());
2374 if (any_of(paddingDimensions,
2375 [](int64_t paddingDimension) { return paddingDimension < 0; })) {
2376 return emitOpError() << "expects padding_dimensions to contain positive "
2377 "integers, found "
2378 << getPaddingDimensions();
2379 }
2380 if (!getMixedPadToMultipleOf().empty()) {
2381 if (getMixedPadToMultipleOf().size() != paddingDimensions.size()) {
2382 return emitOpError() << "expects as many multiples as padding_dimensions";
2383 }
2384 }
2385 ArrayAttr transposes = getTransposePaddings();
2386 for (Attribute attr : transposes) {
2387 SmallVector<int64_t> transpose = extractFromIntegerArrayAttr<int64_t>(attr);
2388 auto sequence = llvm::to_vector(llvm::seq<int64_t>(0, transpose.size()));
2389 if (!std::is_permutation(sequence.begin(), sequence.end(),
2390 transpose.begin(), transpose.end())) {
2391 return emitOpError()
2392 << "expects transpose_paddings to be a permutation, found "
2393 << attr;
2394 }
2395 }
2396 if (getCopyBackOp() !=
2397 bufferization::MaterializeInDestinationOp::getOperationName() &&
2398 getCopyBackOp() != linalg::CopyOp::getOperationName() &&
2399 getCopyBackOp() != kCopyOpNone)
2400 return emitOpError() << "invalid copy_back_op";
2401 return success();
2402}
2403
2404//===---------------------------------------------------------------------===//
2405// PadTilingInterfaceOp
2406//===---------------------------------------------------------------------===//
2407
2408void transform::PadTilingInterfaceOp::build(OpBuilder &b,
2409 OperationState &result,
2410 Value target,
2411 ArrayRef<int64_t> paddingSizes,
2412 bool padToMultipleOf) {
2413 auto resultType = transform::AnyOpType::get(b.getContext());
2414 return build(/*odsBuilder=*/b,
2415 /*result=*/result,
2416 /*types=*/TypeRange{resultType, resultType},
2417 /*target=*/target,
2418 /*padding_values=*/ArrayAttr(), // let inference handle this
2419 /*padding_sizes=*/ValueRange{},
2420 /*paddingSizes=*/
2421 (paddingSizes.empty() ? DenseI64ArrayAttr()
2422 : b.getDenseI64ArrayAttr(paddingSizes)),
2423 /*pad_to_multiple_of=*/
2424 padToMultipleOf ? b.getUnitAttr() : nullptr);
2425}
2426
2427void transform::PadTilingInterfaceOp::build(
2428 OpBuilder &b, OperationState &result, Value target,
2429 ArrayRef<OpFoldResult> mixedPaddingSizes, bool padToMultipleOf) {
2430 auto resultType = transform::AnyOpType::get(b.getContext());
2431 SmallVector<int64_t> staticPaddingSizes;
2432 SmallVector<Value> dynamicPaddingSizes;
2433 dispatchIndexOpFoldResults(mixedPaddingSizes, dynamicPaddingSizes,
2434 staticPaddingSizes);
2435 return build(/*odsBuilder=*/b,
2436 /*result=*/result,
2437 /*types=*/TypeRange{resultType, resultType},
2438 /*target=*/target,
2439 /*padding_values=*/ArrayAttr(), // let inference handle this
2440 /*padding_sizes=*/dynamicPaddingSizes,
2441 /*paddingSizes=*/staticPaddingSizes,
2442 /*usePrescribedTensorShapes=*/padToMultipleOf);
2443}
2444
2445void transform::PadTilingInterfaceOp::getEffects(
2446 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
2447 consumesHandle(getTargetMutable(), effects);
2448 onlyReadsHandle(getPaddingSizesMutable(), effects);
2449 producesHandle(getOperation()->getOpResults(), effects);
2450 modifiesPayload(effects);
2451}
2452
2453SmallVector<OpFoldResult>
2454transform::PadTilingInterfaceOp::getMixedPaddingSizes() {
2455 Builder b(getContext());
2456 return getMixedValues(getStaticPaddingSizes(), getPaddingSizes(), b);
2457}
2458
2459DiagnosedSilenceableFailure
2460transform::PadTilingInterfaceOp::apply(transform::TransformRewriter &rewriter,
2461 transform::TransformResults &results,
2462 transform::TransformState &state) {
2463 SmallVector<Operation *> paddedOps, padOps;
2464
2465 for (Operation *target : state.getPayloadOps(getTarget())) {
2466 auto targetOp = dyn_cast<TilingInterface>(target);
2467 if (!targetOp) {
2468 auto diag = emitSilenceableError() << "expected TilingInterface target";
2469 diag.attachNote(target->getLoc()) << "target op";
2470 return diag;
2471 }
2472
2473 // Only IndexingMapOpInterface ops for now, until TilingInterface exposes a
2474 // loopsToOperand map / C++ APIs to compute the effect of padding on
2475 // operands.
2476 if (!isa<IndexingMapOpInterface>(targetOp.getOperation())) {
2477 auto diag = emitSilenceableError() << "only IndexingMapOpInterface ops "
2478 "supported atm";
2479 diag.attachNote(target->getLoc()) << "target op";
2480 return diag;
2481 }
2482
2483 // Convert the padding values to attributes.
2484 SmallVector<Attribute> paddingValues;
2485 for (auto const &[untypedAttr, elementOrTensorType] :
2486 llvm::zip(getPaddingValues(), targetOp->getOperandTypes())) {
2487 auto attr = dyn_cast<TypedAttr>(untypedAttr);
2488 Type elementType = getElementTypeOrSelf(elementOrTensorType);
2489
2490 if (matchPattern(untypedAttr, ub::m_Poison())) {
2491 paddingValues.push_back(untypedAttr);
2492 continue;
2493 }
2494 if (!attr) {
2495 emitOpError("expects padding values to be typed attributes or poison");
2497 }
2498 // Try to parse string attributes to obtain an attribute of element type.
2499 if (auto stringAttr = dyn_cast<StringAttr>(attr)) {
2500 auto parsedAttr = dyn_cast_if_present<TypedAttr>(parseAttribute(
2501 stringAttr, getContext(), elementType,
2502 /*numRead=*/nullptr, /*isKnownNullTerminated=*/true));
2503 if (!parsedAttr || parsedAttr.getType() != elementType) {
2504 auto diag = this->emitOpError("expects a padding that parses to ")
2505 << elementType << ", got " << attr;
2506 diag.attachNote(targetOp.getLoc()) << "when applied to this op";
2508 }
2509 paddingValues.push_back(parsedAttr);
2510 continue;
2511 }
2512 // Otherwise, add the attribute directly.
2513 if (attr.getType() != elementType) {
2514 auto diag = this->emitOpError("expects a padding value of type ")
2515 << elementType << ", got " << attr;
2516 diag.attachNote(targetOp.getLoc()) << "when applied to this op";
2518 }
2519 paddingValues.push_back(attr);
2520 }
2521
2522 // Set options.
2523 PadTilingInterfaceOptions options;
2524 options.paddingValues = std::move(paddingValues);
2525 options.setPaddingSizes(getMixedPaddingSizes())
2526 .setPadToMultipleOf(getPadToMultipleOf());
2527
2528 OpBuilder::InsertionGuard g(rewriter);
2529 rewriter.setInsertionPointAfter(targetOp);
2530 auto maybePadOps = rewriteAsPaddedOp(
2531 rewriter, cast<TilingInterface>(targetOp.getOperation()),
2532 std::move(options));
2533 if (failed(maybePadOps)) {
2534 auto diag = emitSilenceableError() << "failed to pad op";
2535 diag.attachNote(target->getLoc()) << "target op";
2536 return diag;
2537 }
2538 const auto &[paddedOperands, paddedOp, slicedResults] = maybePadOps.value();
2539
2540 // Set transform results.
2541 paddedOps.push_back(paddedOp);
2542 padOps.append(paddedOperands.begin(), paddedOperands.end());
2543 rewriter.replaceOp(targetOp.getOperation(), slicedResults);
2544 }
2545
2546 results.set(cast<OpResult>(getPadded()), paddedOps);
2547 results.set(cast<OpResult>(getPad()), padOps);
2549}
2550
2551LogicalResult transform::PadTilingInterfaceOp::verify() { return success(); }
2552
2553//===---------------------------------------------------------------------===//
2554// HoistPadOp
2555//===---------------------------------------------------------------------===//
2556
2557DiagnosedSilenceableFailure transform::HoistPadBuildPackingLoopNestOp::apply(
2558 transform::TransformRewriter &rewriter,
2559 transform::TransformResults &transformResults,
2560 transform::TransformState &state) {
2561 auto targetOps = state.getPayloadOps(getTarget());
2562 auto loopOps = state.getPayloadOps(getLoop());
2563 if (!llvm::hasSingleElement(targetOps) || !llvm::hasSingleElement(loopOps)) {
2564 return emitDefiniteFailure()
2565 << "requires exactly one target and one loop handle (got "
2566 << llvm::range_size(targetOps) << " and "
2567 << llvm::range_size(loopOps) << ")";
2568 }
2569
2570 auto padOp = dyn_cast_or_null<tensor::PadOp>(*targetOps.begin());
2571 auto loopOp = dyn_cast_or_null<scf::ForOp>(*loopOps.begin());
2572 if (!padOp || !loopOp)
2573 return emitDefiniteFailure() << "requires exactly 2 non-null handles";
2574
2575 FailureOr<linalg::detail::PackingResult> result =
2576 linalg::detail::buildPackingLoopNest(rewriter, padOp, loopOp,
2577 getTranspose());
2578 if (failed(result))
2579 return emitDefiniteFailure() << "could not build packing loop nest";
2580
2581 if (result->clonedLoopIvs.empty()) {
2582 transformResults.set(cast<OpResult>(getPackingLoop()),
2583 {result->hoistedPadOp.getOperation()});
2585 }
2586 auto outerPackedLoop =
2587 scf::getForInductionVarOwner(result->clonedLoopIvs.front());
2588 transformResults.set(cast<OpResult>(getPackingLoop()),
2589 {outerPackedLoop.getOperation()});
2591}
2592
2593LogicalResult transform::HoistPadBuildPackingLoopNestOp::verify() {
2594 ArrayRef<int64_t> transpose = getTranspose();
2595 auto sequence = llvm::to_vector(llvm::seq<int64_t>(0, transpose.size()));
2596 if (!std::is_permutation(sequence.begin(), sequence.end(), transpose.begin(),
2597 transpose.end())) {
2598 return emitOpError() << "expects transpose to be a permutation, found "
2599 << getTranspose();
2600 }
2601 return success();
2602}
2603
2604void transform::HoistPadBuildPackingLoopNestOp::getEffects(
2605 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
2606 transform::onlyReadsHandle(getTargetMutable(), effects);
2607 transform::onlyReadsHandle(getLoopMutable(), effects);
2608 transform::producesHandle(getOperation()->getOpResults(), effects);
2610}
2611
2612DiagnosedSilenceableFailure
2613transform::HoistPadOp::applyToOne(transform::TransformRewriter &rewriter,
2614 tensor::PadOp target,
2615 transform::ApplyToEachResultList &results,
2616 transform::TransformState &state) {
2617 tensor::PadOp hoistedPadOp;
2618 SmallVector<TransposeOp> transposeOps;
2619 FailureOr<Value> result =
2620 hoistPaddingOnTensors(rewriter, target, getNumLoops(), getTranspose(),
2621 hoistedPadOp, transposeOps);
2622 if (succeeded(result)) {
2623 // We need to perform our own replacement here because this API is still
2624 // used in patterns that "pad and hoist", for which the replacement values
2625 // need to be different.
2626 // TODO: clean this up and stop "pad and hoist" behavior more globally now
2627 // that we have more composable abstractions.
2628 rewriter.replaceOp(target, *result);
2629 results.push_back(hoistedPadOp);
2631 }
2632 return emitDefaultSilenceableFailure(target);
2633}
2634
2635LogicalResult transform::HoistPadOp::verify() {
2636 ArrayRef<int64_t> transpose = getTranspose();
2637 auto sequence = llvm::to_vector(llvm::seq<int64_t>(0, transpose.size()));
2638 if (!std::is_permutation(sequence.begin(), sequence.end(), transpose.begin(),
2639 transpose.end())) {
2640 return emitOpError() << "expects transpose to be a permutation, found "
2641 << getTranspose();
2642 }
2643 return success();
2644}
2645
2646//===----------------------------------------------------------------------===//
2647// PromoteOp
2648//===----------------------------------------------------------------------===//
2649
2650DiagnosedSilenceableFailure
2651transform::PromoteOp::applyToOne(transform::TransformRewriter &rewriter,
2652 LinalgOp target,
2653 transform::ApplyToEachResultList &results,
2654 transform::TransformState &state) {
2655 LinalgPromotionOptions promotionOptions;
2656 if (!getOperandsToPromote().empty())
2657 promotionOptions = promotionOptions.setOperandsToPromote(
2658 extractFromIntegerArrayAttr<int64_t>(getOperandsToPromote()));
2659 if (getUseFullTilesByDefault())
2660 promotionOptions = promotionOptions.setUseFullTileBuffersByDefault(
2661 getUseFullTilesByDefault());
2662 if (getUseOriginalSubviewSize())
2663 promotionOptions =
2664 promotionOptions.setUseOriginalSubviewSize(getUseOriginalSubviewSize());
2665 if (getUseAlloca())
2666 promotionOptions = promotionOptions.setUseAlloca(getUseAlloca());
2667 if (!getUseFullTileBuffers().empty())
2668 promotionOptions = promotionOptions.setUseFullTileBuffers(
2669 llvm::to_vector(getUseFullTileBuffers().getAsValueRange<BoolAttr>()));
2670 if (getAlignment().has_value())
2671 promotionOptions = promotionOptions.setAlignment(*getAlignment());
2672 if (getMemorySpace().has_value())
2673 promotionOptions = promotionOptions.setMemorySpace(*getMemorySpace());
2674
2675 if (getMapping().has_value()) {
2676 // The mapping should only contain an element
2677 auto mapping = *getMapping();
2678 if (mapping.size() > 1)
2679 return emitDefaultDefiniteFailure(target);
2680
2681 auto addressSpace = cast<mlir::gpu::GPUMemorySpaceMappingAttr>(mapping[0]);
2682
2683 if (addressSpace.getAddressSpace() ==
2684 mlir::gpu::GPUDialect::getWorkgroupAddressSpace()) {
2685 promotionOptions =
2686 promotionOptions
2690 .setUseFullTileBuffers({false, false});
2691 } else if (addressSpace.getAddressSpace() ==
2692 mlir::gpu::GPUDialect::getPrivateAddressSpace()) {
2693 promotionOptions =
2694 promotionOptions
2698 .setUseFullTileBuffers({false, false});
2699 } else {
2700 return emitDefaultDefiniteFailure(target);
2701 }
2702 }
2703
2704 if (failed(promoteSubviewsPrecondition(target, promotionOptions)))
2705 return emitDefaultDefiniteFailure(target);
2706
2707 rewriter.setInsertionPoint(target);
2708 FailureOr<LinalgOp> res = promoteSubViews(rewriter, target, promotionOptions);
2709 if (failed(res))
2710 return emitDefaultDefiniteFailure(target);
2711 results.push_back(target);
2713}
2714
2715//===----------------------------------------------------------------------===//
2716// ReplaceOp
2717//===----------------------------------------------------------------------===//
2718
2719DiagnosedSilenceableFailure
2720transform::ReplaceOp::apply(transform::TransformRewriter &rewriter,
2721 TransformResults &transformResults,
2722 TransformState &state) {
2723 auto payload = state.getPayloadOps(getTarget());
2724
2725 // Check for invalid targets.
2726 for (Operation *target : payload) {
2727 if (target->getNumOperands() > 0)
2728 return emitDefiniteFailure() << "expected target without operands";
2729 if (!target->hasTrait<OpTrait::IsIsolatedFromAbove>() &&
2730 target->getNumRegions() > 0)
2731 return emitDefiniteFailure()
2732 << "expected target that is isolated from above";
2733 }
2734
2735 // Clone and replace.
2736 Operation *pattern = &getBodyRegion().front().front();
2737 SmallVector<Operation *> replacements;
2738 for (Operation *target : payload) {
2739 if (getOperation()->isAncestor(target))
2740 continue;
2741 rewriter.setInsertionPoint(target);
2742 Operation *replacement = rewriter.clone(*pattern);
2743 rewriter.replaceOp(target, replacement->getResults());
2744 replacements.push_back(replacement);
2745 }
2746 transformResults.set(cast<OpResult>(getReplacement()), replacements);
2748}
2749
2750void transform::ReplaceOp::getEffects(
2751 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
2752 consumesHandle(getTargetMutable(), effects);
2753 producesHandle(getOperation()->getOpResults(), effects);
2754 modifiesPayload(effects);
2755}
2756
2757LogicalResult transform::ReplaceOp::verify() {
2758 if (!getBodyRegion().hasOneBlock())
2759 return emitOpError() << "expected one block";
2760 if (std::distance(getBodyRegion().front().begin(),
2761 getBodyRegion().front().end()) != 1)
2762 return emitOpError() << "expected one operation in block";
2763 Operation *replacement = &getBodyRegion().front().front();
2764 if (replacement->getNumOperands() > 0)
2765 return replacement->emitOpError()
2766 << "expected replacement without operands";
2767 if (!replacement->hasTrait<OpTrait::IsIsolatedFromAbove>() &&
2768 replacement->getNumRegions() > 0)
2769 return replacement->emitOpError()
2770 << "expect op that is isolated from above";
2771 return success();
2772}
2773
2774//===----------------------------------------------------------------------===//
2775// ScalarizeOp
2776//===----------------------------------------------------------------------===//
2777
2778DiagnosedSilenceableFailure
2779transform::ScalarizeOp::applyToOne(transform::TransformRewriter &rewriter,
2780 LinalgOp target,
2781 transform::ApplyToEachResultList &results,
2782 transform::TransformState &state) {
2783 scf::SCFTilingOptions tilingOptions;
2784 tilingOptions.setTileSizeComputationFunction([&](OpBuilder &b, Operation *) {
2785 SmallVector<OpFoldResult> tileSizes;
2786 Location loc = target.getLoc();
2787 SmallVector<OpFoldResult> allShapeSizes =
2788 target.createFlatListOfOperandDims(b, loc);
2789 AffineMap map = target.getShapesToLoopsMap();
2790 if (!map)
2791 return tileSizes;
2792 SmallVector<OpFoldResult> shapeSizes =
2794 allShapeSizes);
2795 // If the shape size is dynamic, tile by 1.
2796 // Otherwise, do not tile (i.e. tile size 0).
2797 for (OpFoldResult shapeSize : shapeSizes) {
2798 tileSizes.push_back(getConstantIntValue(shapeSize) ? b.getIndexAttr(0)
2799 : b.getIndexAttr(1));
2800 }
2801 return tileSizes;
2802 });
2803 rewriter.setInsertionPoint(target);
2804 FailureOr<scf::SCFTilingResult> maybeTilingResult = tileUsingSCF(
2805 rewriter, cast<TilingInterface>(target.getOperation()), tilingOptions);
2806 if (failed(maybeTilingResult))
2807 return emitDefaultDefiniteFailure(target);
2808
2809 if (target->getNumResults())
2810 rewriter.replaceOp(target, maybeTilingResult->replacements);
2811 else
2812 rewriter.eraseOp(target);
2813
2814 results.reserve(maybeTilingResult->tiledOps.size());
2815 for (Operation *tiled : maybeTilingResult->tiledOps)
2816 results.push_back(tiled);
2818}
2819
2820//===----------------------------------------------------------------------===//
2821// ConvertToLoopsOp
2822//===----------------------------------------------------------------------===//
2823
2824DiagnosedSilenceableFailure
2825transform::ConvertToLoopsOp::apply(transform::TransformRewriter &rewriter,
2826 transform::TransformResults &results,
2827 transform::TransformState &state) {
2828 SmallVector<Operation *> loops;
2829 for (Operation *target : state.getPayloadOps(getTarget())) {
2830 auto tilingOp = dyn_cast<TilingInterface>(*target);
2831 if (!tilingOp) {
2832 DiagnosedSilenceableFailure diag =
2833 emitSilenceableError()
2834 << "expected the payload to implement TilingInterface";
2835 diag.attachNote(target->getLoc()) << "payload op";
2836 return diag;
2837 }
2838 rewriter.setInsertionPoint(target);
2839 FailureOr<SmallVector<scf::ForOp>> generatedLoops =
2840 scf::lowerToLoopsUsingSCFForOp(rewriter, tilingOp);
2841 if (failed(generatedLoops))
2842 return emitDefaultDefiniteFailure(target);
2843 for (scf::ForOp &loop : *generatedLoops) {
2844 loops.push_back(loop.getOperation());
2845 }
2846 rewriter.eraseOp(target);
2847 }
2848 results.set(cast<OpResult>(getResult()), loops);
2850}
2851
2852//===----------------------------------------------------------------------===//
2853// RewriteInDestinationPassingStyleOp
2854//===----------------------------------------------------------------------===//
2855
2856DiagnosedSilenceableFailure
2857transform::RewriteInDestinationPassingStyleOp::applyToOne(
2858 transform::TransformRewriter &rewriter, Operation *target,
2859 transform::ApplyToEachResultList &results,
2860 transform::TransformState &state) {
2861 rewriter.setInsertionPoint(target);
2862 FailureOr<Operation *> maybeResult =
2864 .Case<DestinationStyleOpInterface>([](auto op) { return op; })
2865 .Case<tensor::FromElementsOp, tensor::GenerateOp, tensor::PadOp>(
2866 [&rewriter](auto op) {
2867 return rewriteInDestinationPassingStyle(rewriter, op);
2868 });
2869 if (failed(maybeResult))
2870 return emitDefaultSilenceableFailure(target);
2871 results.push_back(*maybeResult);
2873}
2874
2875//===----------------------------------------------------------------------===//
2876// SplitOp
2877//===----------------------------------------------------------------------===//
2878
2879DiagnosedSilenceableFailure
2880SplitOp::apply(transform::TransformRewriter &rewriter,
2881 TransformResults &results, TransformState &state) {
2882 // Collect the dynamic split points if provided.
2883 SmallVector<Operation *> payload =
2884 llvm::to_vector(state.getPayloadOps(getTarget()));
2885
2886 bool isMultiwaySplit = getMultiway();
2887
2888 if (isMultiwaySplit && !llvm::hasSingleElement(payload)) {
2889 return mlir::emitSilenceableFailure(getLoc())
2890 << "requires exactly one target when "
2891 "multiway split is enabled (got "
2892 << llvm::range_size(payload) << ")";
2893 }
2894
2895 SmallVector<OpFoldResult> chunkSizes;
2896
2897 if (!isMultiwaySplit)
2898 chunkSizes.reserve(payload.size());
2899
2900 if (getDynamicChunkSizes()) {
2902 if (isa<TransformHandleTypeInterface>(getDynamicChunkSizes().getType())) {
2903 chunkSizes = llvm::map_to_vector(
2904 state.getPayloadOps(getDynamicChunkSizes()), [&](Operation *op) {
2905 if (op->getNumResults() != 1 ||
2906 !op->getResult(0).getType().isIndex()) {
2907 diag = emitSilenceableError()
2908 << "expected dynamic split point handle to point to a "
2909 "single-result index-typed op";
2910 diag.attachNote(op->getLoc()) << "dynamic split point";
2911 }
2912 return OpFoldResult(op->getResult(0));
2913 });
2914 } else {
2915 chunkSizes = llvm::map_to_vector(
2916 state.getParams(getDynamicChunkSizes()),
2917 [](Attribute attr) { return OpFoldResult(attr); });
2918 }
2919 if (diag.isSilenceableFailure())
2920 return diag;
2921
2922 // For multiway split, a single payload is expected to have multiple
2923 // split points.
2924 if (!isMultiwaySplit && chunkSizes.size() != payload.size()) {
2925 return emitDefiniteFailure()
2926 << "expected the dynamic split point handle to point to as "
2927 "many operations ("
2928 << chunkSizes.size() << ") as the target handle ("
2929 << payload.size() << ")";
2930 }
2931 } else {
2932 chunkSizes.resize(payload.size(),
2933 rewriter.getIndexAttr(getStaticChunkSizes()));
2934 }
2935
2936 auto checkStructuredOpAndDimensions =
2937 [&](LinalgOp linalgOp, Location loc) -> DiagnosedSilenceableFailure {
2938 if (!linalgOp) {
2939 auto diag = emitSilenceableError() << "only applies to structured ops";
2940 diag.attachNote(loc) << "target op";
2941 return diag;
2942 }
2943
2944 if (getDimension() >= linalgOp.getNumLoops()) {
2945 auto diag = emitSilenceableError() << "dimension " << getDimension()
2946 << " does not exist in target op";
2947 diag.attachNote(loc) << "target op";
2948 return diag;
2949 }
2951 };
2952
2953 auto checkFailureInSplitting =
2954 [&](bool hasFailed, Location loc) -> DiagnosedSilenceableFailure {
2955 if (hasFailed) {
2956 auto diag = emitDefiniteFailure() << "internal failure in splitting";
2957 diag.attachNote(loc) << "target op";
2958 return diag;
2959 }
2961 };
2962
2963 SmallVector<Operation *> opList;
2964 if (isMultiwaySplit) {
2965
2966 // Split a single target operation at multiple points.
2967 TilingInterface head, tail;
2968 Operation *target = payload.front();
2969
2970 LinalgOp linalgOp = dyn_cast<LinalgOp>(target);
2971
2972 // Check that the target is a valid LinalgOp with correct dimensions.
2973 DiagnosedSilenceableFailure diag =
2974 checkStructuredOpAndDimensions(linalgOp, target->getLoc());
2975 if (diag.isSilenceableFailure())
2976 return diag;
2977
2978 for (auto &&[idx, chunkSize] : llvm::enumerate(chunkSizes)) {
2979
2980 if (idx > 0)
2981 target = tail.getOperation();
2982
2983 if (!target)
2984 break;
2985
2986 linalgOp = cast<LinalgOp>(target);
2987 Location loc = target->getLoc();
2988
2989 rewriter.setInsertionPoint(linalgOp);
2990 std::tie(head, tail) = linalg::splitOp(
2991 rewriter, cast<TilingInterface>(linalgOp.getOperation()),
2992 getDimension(), chunkSize);
2993
2994 // Propagate errors.
2995 DiagnosedSilenceableFailure diag =
2996 checkFailureInSplitting(!head && !tail, loc);
2997 if (diag.isDefiniteFailure())
2998 return diag;
2999
3000 opList.push_back(head.getOperation());
3001 }
3002
3003 // Append any leftover parts to the end of the result list.
3004 if (tail)
3005 opList.push_back(tail.getOperation());
3006
3007 } else {
3008 // Split each target operation.
3009 SmallVector<Operation *> first, second;
3010 Operation *noSecondPart = nullptr;
3011 for (const auto &pair : llvm::zip(payload, chunkSizes)) {
3012 Operation *target = std::get<0>(pair);
3013 Location loc = target->getLoc();
3014 LinalgOp linalgOp = dyn_cast<LinalgOp>(target);
3015 DiagnosedSilenceableFailure diag =
3016 checkStructuredOpAndDimensions(linalgOp, target->getLoc());
3017
3018 if (diag.isSilenceableFailure())
3019 return diag;
3020
3021 rewriter.setInsertionPoint(linalgOp);
3022 std::tie(first.emplace_back(), second.emplace_back()) = linalg::splitOp(
3023 rewriter, cast<TilingInterface>(linalgOp.getOperation()),
3024 getDimension(), std::get<1>(pair));
3025
3026 // Propagate errors.
3027 DiagnosedSilenceableFailure diagSplit =
3028 checkFailureInSplitting(!first.back() && !second.back(), loc);
3029 if (diagSplit.isDefiniteFailure())
3030 return diag;
3031
3032 // Do not add null second parts.
3033 if (!second.back()) {
3034 noSecondPart = target;
3035 second.pop_back();
3036 }
3037 }
3038
3039 if (second.size() != first.size() && !second.empty()) {
3040 auto diag = emitSilenceableError()
3041 << "splitting does not produce the second part for a subset "
3042 "of targets";
3043 diag.attachNote()
3044 << "expected splitting to produce the second part of all "
3045 "or none of the targets";
3046 diag.attachNote(noSecondPart->getLoc())
3047 << "first target with no second part";
3048 return diag;
3049 }
3050
3051 opList.append(first);
3052 if (!second.empty())
3053 opList.append(second);
3054 }
3055 results.set(cast<OpResult>(getSplitList()), opList);
3057}
3058
3059void SplitOp::getEffects(
3060 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
3061 consumesHandle(getTargetMutable(), effects);
3062 if (getDynamicChunkSizes())
3063 onlyReadsHandle(getDynamicChunkSizesMutable(), effects);
3064 producesHandle(getOperation()->getOpResults(), effects);
3065 modifiesPayload(effects);
3066}
3067
3068ParseResult SplitOp::parse(OpAsmParser &parser, OperationState &result) {
3069 OpAsmParser::UnresolvedOperand target, dynamicChunkSizes;
3070 IntegerAttr staticChunkSizes;
3071 if (parser.parseOperand(target) || parser.parseKeyword("after"))
3072 return failure();
3073
3074 OptionalParseResult dynamicPointParseResult =
3075 parser.parseOptionalOperand(dynamicChunkSizes);
3076 if (!dynamicPointParseResult.has_value()) {
3077 int64_t staticChunkSizesValue;
3078 if (failed(parser.parseInteger(staticChunkSizesValue)))
3079 return failure();
3080
3081 staticChunkSizes =
3082 parser.getBuilder().getI64IntegerAttr(staticChunkSizesValue);
3083 }
3084
3085 Type targetType;
3086 if (parser.parseOptionalAttrDict(result.attributes) ||
3087 parser.parseColonType(targetType) ||
3088 parser.resolveOperand(target, targetType, result.operands)) {
3089 return failure();
3090 }
3091 if (dynamicPointParseResult.has_value()) {
3092 Type chunkSizesType;
3093 if (failed(*dynamicPointParseResult) || parser.parseComma() ||
3094 parser.parseType(chunkSizesType) ||
3095 parser.resolveOperand(dynamicChunkSizes, chunkSizesType,
3096 result.operands)) {
3097 return failure();
3098 }
3099
3100 staticChunkSizes =
3101 parser.getBuilder().getI64IntegerAttr(ShapedType::kDynamic);
3102 }
3103
3104 result.addAttribute(
3105 SplitOp::getStaticChunkSizesAttrName(result.name).getValue(),
3106 staticChunkSizes);
3107 result.addTypes(targetType);
3108 return success();
3109}
3110
3111void SplitOp::print(OpAsmPrinter &printer) {
3112 printer << " " << getTarget() << " after ";
3113 int64_t staticChunkSize = static_cast<int64_t>(getStaticChunkSizes());
3114 if (staticChunkSize != ShapedType::kDynamic)
3115 printer << staticChunkSize;
3116 else
3117 printer << getDynamicChunkSizes();
3118 printer << " ";
3119 NamedAttrList attrs(getOperation()->getDiscardableAttrDictionary());
3120 attrs.append(getDimensionAttrName(), getDimensionAttr());
3121 if (UnitAttr multiway = getMultiwayAttr())
3122 attrs.append(getMultiwayAttrName(), multiway);
3123 printer.printOptionalAttrDict(attrs, {getStaticChunkSizesAttrName()});
3124 printer << " : " << getTarget().getType();
3125 if (staticChunkSize == ShapedType::kDynamic)
3126 printer << ", " << getDynamicChunkSizes().getType();
3127}
3128
3129LogicalResult SplitOp::verify() {
3130 if ((static_cast<int64_t>(getStaticChunkSizes()) != ShapedType::kDynamic) ^
3131 (getDynamicChunkSizes() == nullptr)) {
3132 return emitOpError() << "expects either a dynamic or a static split "
3133 "point to be provided";
3134 }
3135 return success();
3136}
3137
3138//===----------------------------------------------------------------------===//
3139// SplitReductionOp
3140//===----------------------------------------------------------------------===//
3141
3142void transform::SplitReductionOp::build(
3143 OpBuilder &builder, OperationState &result, Value target,
3144 int64_t splitFactor, int64_t insertSplitDimension, bool innerParallel,
3145 bool useScalingAlgorithm, bool useAlloc) {
3146 MLIRContext *ctx = builder.getContext();
3147 result.addOperands(target);
3148 result.addAttribute(SplitReductionOp::getSplitFactorAttrName(result.name),
3149 builder.getI64IntegerAttr(splitFactor));
3150 result.addAttribute(
3151 SplitReductionOp::getInsertSplitDimensionAttrName(result.name),
3152 builder.getI64IntegerAttr(insertSplitDimension));
3153 if (innerParallel) {
3154 result.addAttribute(SplitReductionOp::getInnerParallelAttrName(result.name),
3155 builder.getUnitAttr());
3156 }
3157 if (useScalingAlgorithm) {
3158 result.addAttribute(
3159 SplitReductionOp::getUseScalingAlgorithmAttrName(result.name),
3160 builder.getUnitAttr());
3161 }
3162 if (useAlloc) {
3163 result.addAttribute(SplitReductionOp::getUseAllocAttrName(result.name),
3164 builder.getUnitAttr());
3165 }
3166 auto resultType = transform::AnyOpType::get(ctx);
3167 result.addTypes({resultType, resultType, resultType, resultType});
3168}
3169
3170DiagnosedSilenceableFailure transform::SplitReductionOp::applyToOne(
3171 transform::TransformRewriter &rewriter, LinalgOp target,
3172 transform::ApplyToEachResultList &results,
3173 transform::TransformState &state) {
3174 ControlSplitReductionFn splitFn = [&](LinalgOp) {
3175 return linalg::SplitReductionOptions{int64_t(getSplitFactor()),
3176 unsigned(getInsertSplitDimension()),
3177 bool(getInnerParallel())};
3178 };
3179 rewriter.setInsertionPoint(target);
3180 FailureOr<SplitReductionResult> splitResult =
3181 (getUseScalingAlgorithm())
3182 ? splitReductionByScaling(rewriter, target, splitFn, getUseAlloc())
3183 : splitReduction(rewriter, target, splitFn, getUseAlloc());
3184 if (failed(splitResult))
3185 return emitDefaultDefiniteFailure(target);
3186
3187 results.push_back(splitResult->initOrAlloc);
3188 results.push_back(splitResult->fillOp);
3189 results.push_back(splitResult->splitLinalgOp);
3190 results.push_back(splitResult->resultCombiningLinalgOp);
3192}
3193
3194//===----------------------------------------------------------------------===//
3195// TileReductionUsingForOp
3196//===----------------------------------------------------------------------===//
3197
3198void transform::TileReductionUsingForOp::build(
3199 OpBuilder &builder, OperationState &result, Value target,
3200 ArrayRef<int64_t> staticTileSizes) {
3201 // Call the default builder.
3202 // This is future-proof re mixed static-dynamic and setting up the proper
3203 // operands segment sizes attributes for multiple variadic operands.
3204 // In the absence of this, horrible bugs ensue.
3205 // TODO: support mixed static-dynamic (see TileUsingForallOp).
3206 MLIRContext *ctx = builder.getContext();
3207 auto opTy = transform::AnyOpType::get(ctx);
3208 auto staticTileSizesAttr = builder.getI64ArrayAttr(staticTileSizes);
3209 build(builder, result,
3210 /*resultTypes=*/TypeRange{opTy, opTy, opTy, opTy},
3211 /*target=*/target,
3212 /*reduction_dims=*/nullptr,
3213 /*tile_sizes=*/staticTileSizesAttr);
3214}
3215
3216DiagnosedSilenceableFailure transform::TileReductionUsingForOp::applyToOne(
3217 transform::TransformRewriter &rewriter, Operation *target,
3218 transform::ApplyToEachResultList &results,
3219 transform::TransformState &state) {
3220 rewriter.setInsertionPoint(target);
3221
3222 auto partialReductionOp = dyn_cast<PartialReductionOpInterface>(target);
3223 if (!partialReductionOp) {
3225 target->getLoc(),
3226 "Operation should implement PartialReductionOpInterface");
3227 }
3228
3229 SmallVector<unsigned> reductionDims =
3230 extractFromIntegerArrayAttr<unsigned>(getReductionDims());
3231 if (reductionDims.empty()) {
3232 for (auto [idx, iteratorType] :
3233 llvm::enumerate(partialReductionOp.getLoopIteratorTypes())) {
3234 if (iteratorType == utils::IteratorType::reduction)
3235 reductionDims.push_back(idx);
3236 }
3237 }
3238
3239 scf::SCFTilingOptions options;
3240 options.setLoopType(scf::SCFTilingOptions::LoopType::ForOp);
3241 options.setReductionTilingStrategy(
3243 options.setTileSizes(getAsOpFoldResult(getTileSizesAttr()));
3244 options.setReductionDims(reductionDims);
3245 FailureOr<scf::SCFTilingResult> result =
3246 scf::tileUsingSCF(rewriter, partialReductionOp, options);
3247
3248 if (failed(result)) {
3249 return emitSilenceableFailure(getLoc(),
3250 "failed to tile using partial reduction");
3251 }
3252 rewriter.replaceOp(target, result->replacements);
3253 for (Value initValue : result->initialValues)
3254 results.push_back(initValue.getDefiningOp());
3255 for (auto *parallelTiledOp : result->tiledOps)
3256 results.push_back(parallelTiledOp);
3257 for (auto *mergeOp : result->mergeOps)
3258 results.push_back(mergeOp);
3259 results.push_back(result->loops.front());
3261}
3262
3263//===----------------------------------------------------------------------===//
3264// TileReductionUsingForallOp
3265//===----------------------------------------------------------------------===//
3266
3267void transform::TileReductionUsingForallOp::build(
3268 OpBuilder &builder, OperationState &result, Value target,
3269 ArrayRef<int64_t> staticNumThreads, ArrayRef<int64_t> staticTileSizes,
3270 ArrayAttr mapping) {
3271 // Call the default builder.
3272 // This is future-proof re mixed static-dynamic and setting up the proper
3273 // operands segment sizes attributes for multiple variadic operands.
3274 // In the absence of this, horrible bugs ensue.
3275 // TODO: support mixed static-dynamic (see TileUsingForallOp).
3276 MLIRContext *ctx = builder.getContext();
3277 auto opTy = transform::AnyOpType::get(ctx);
3278 auto staticNumThreadsAttr = builder.getDenseI64ArrayAttr(staticNumThreads);
3279 auto staticTileSizesAttr = builder.getDenseI64ArrayAttr(staticTileSizes);
3280 build(builder, result,
3281 /*resultTypes=*/TypeRange{opTy, opTy, opTy, opTy},
3282 /*target=*/target,
3283 /*reduction_dims=*/{},
3284 /*num_threads=*/staticNumThreadsAttr,
3285 /*tile_sizes=*/staticTileSizesAttr,
3286 /*mapping=*/mapping);
3287}
3288
3289DiagnosedSilenceableFailure transform::TileReductionUsingForallOp::applyToOne(
3290 transform::TransformRewriter &rewriter, Operation *target,
3291 transform::ApplyToEachResultList &results,
3292 transform::TransformState &state) {
3293 rewriter.setInsertionPoint(target);
3294
3295 auto partialReductionOp = dyn_cast<PartialReductionOpInterface>(target);
3296 if (!partialReductionOp) {
3298 target->getLoc(),
3299 "Operation should implement PartialReductionOpInterface");
3300 }
3301 SmallVector<OpFoldResult> numThreads =
3302 getAsOpFoldResult(rewriter.getI64ArrayAttr(getNumThreads()));
3303 SmallVector<OpFoldResult> tileSizes =
3305
3306 scf::SCFTilingOptions options;
3307 options.setLoopType(scf::SCFTilingOptions::LoopType::ForallOp);
3308 options.setReductionTilingStrategy(
3310 if (!getNumThreads().empty()) {
3311 options.setNumThreads(numThreads);
3312 } else {
3313 options.setTileSizes(tileSizes);
3314 }
3315 if (auto mapping = getMapping()) {
3316 options.setMapping(mapping.value().getValue());
3317 }
3318 SmallVector<unsigned> reductionDims =
3319 extractFromIntegerArrayAttr<unsigned>(getReductionDims());
3320 if (reductionDims.empty()) {
3321 for (auto [idx, iteratorType] :
3322 llvm::enumerate(partialReductionOp.getLoopIteratorTypes())) {
3323 if (iteratorType == utils::IteratorType::reduction)
3324 reductionDims.push_back(idx);
3325 }
3326 }
3327 options.setReductionDims(reductionDims);
3328 FailureOr<scf::SCFTilingResult> result =
3329 scf::tileUsingSCF(rewriter, partialReductionOp, options);
3330
3331 if (failed(result)) {
3332 auto diag = emitSilenceableError() << "could not tile reduction";
3333 return diag;
3334 }
3335 rewriter.replaceOp(target, result->replacements);
3336
3337 for (Value initValue : result->initialValues)
3338 results.push_back(initValue.getDefiningOp());
3339 for (auto *parallelTiledOp : result->tiledOps)
3340 results.push_back(parallelTiledOp);
3341 for (auto *mergeOp : result->mergeOps)
3342 results.push_back(mergeOp);
3343 results.push_back(result->loops.front());
3345}
3346
3347//===----------------------------------------------------------------------===//
3348// ContinuousTileSizesOp
3349//===----------------------------------------------------------------------===//
3350
3351DiagnosedSilenceableFailure
3352transform::ContinuousTileSizesOp::apply(transform::TransformRewriter &rewriter,
3353 TransformResults &transformResults,
3354 TransformState &state) {
3355
3356 SmallVector<Operation *> targetOps =
3357 llvm::to_vector(state.getPayloadOps(getTarget()));
3358
3359 if (!llvm::hasSingleElement(targetOps)) {
3360 return mlir::emitSilenceableFailure(getLoc())
3361 << "requires exactly one target (got " << llvm::range_size(targetOps)
3362 << ")";
3363 }
3364
3365 Operation *target = *targetOps.begin();
3366 auto linalgOp = dyn_cast<LinalgOp>(target);
3367 auto tileableOp = dyn_cast<TilingInterface>(target);
3368
3369 if (!linalgOp)
3370 return emitDefiniteFailure() << "expected Linalg Op";
3371
3372 OpBuilder builder(linalgOp.getContext());
3373
3374 if (isa<TransformParamTypeInterface>(getChunkSizes().getType())) {
3375 if (linalgOp.hasDynamicShape()) {
3376 auto diag = emitSilenceableError()
3377 << "cannot compute parametric tile sizes for dynamically "
3378 "shaped payload op";
3379 diag.attachNote(linalgOp->getLoc()) << "payload op";
3380 return diag;
3381 }
3382
3383 FailureOr<StaticContinuousTileSizeSpecification> spec =
3384 computeStaticContinuousTileSizes(linalgOp, getDimension(),
3385 getTargetSize());
3386 if (failed(spec)) {
3387 return emitSilenceableError()
3388 << "failed to compute multi-size tiling sizes";
3389 }
3390
3391 SmallVector<int64_t> chunkSizes;
3392
3393 for (auto &&[tileSize, tripCount] :
3394 llvm::zip_equal(spec->tileSizes, spec->tripCounts))
3395 chunkSizes.push_back(tileSize * tripCount);
3396
3397 auto getI64AttrsFromI64 = [&](ArrayRef<int64_t> values) {
3398 return llvm::map_to_vector(values, [&](int64_t value) -> Attribute {
3399 return builder.getI64IntegerAttr(value);
3400 });
3401 };
3402 transformResults.setParams(cast<OpResult>(getTileSizes()),
3403 getI64AttrsFromI64(spec->tileSizes));
3404 transformResults.setParams(cast<OpResult>(getChunkSizes()),
3405 getI64AttrsFromI64(chunkSizes));
3406
3408 }
3409
3410 builder.setInsertionPoint(linalgOp);
3411
3412 OpFoldResult targetSize = builder.getIndexAttr(getTargetSize());
3413 unsigned dimension = getDimension();
3414
3415 FailureOr<ContinuousTileSizeSpecification> spec = computeContinuousTileSizes(
3416 builder, tileableOp, dimension, targetSize, true);
3417 if (failed(spec)) {
3418 return emitSilenceableError() << "could not generate tile size computation";
3419 }
3420
3421 AffineExpr s0 = builder.getAffineSymbolExpr(0);
3422 AffineExpr s1 = builder.getAffineSymbolExpr(1);
3423 auto apply = [&](AffineExpr expr, ArrayRef<OpFoldResult> ofrs) -> Value {
3424 return affine::makeComposedAffineApply(builder, linalgOp->getLoc(), expr,
3425 ofrs);
3426 };
3427
3428 SmallVector<Value> chunkSizes;
3429 Value splitPoint;
3430 for (auto &&[tileSize, tripCount] :
3431 llvm::zip_equal(spec->tileSizes, spec->tripCounts)) {
3432 splitPoint = apply(s0 * s1, {tileSize, tripCount});
3433 chunkSizes.push_back(splitPoint);
3434 }
3435
3436 auto getDefiningOps = [&](ArrayRef<Value> values) {
3437 return llvm::map_to_vector(values, [&](Value value) -> Operation * {
3438 return value.getDefiningOp();
3439 });
3440 };
3441
3442 transformResults.set(cast<OpResult>(getTileSizes()),
3443 getDefiningOps(spec->tileSizes));
3444 transformResults.set(cast<OpResult>(getChunkSizes()),
3445 getDefiningOps(chunkSizes));
3446
3448}
3449
3450LogicalResult transform::ContinuousTileSizesOp::verify() {
3451
3452 if (getTileSizes().getType() != getChunkSizes().getType()) {
3453 return emitOpError() << "expects all results type to be the same";
3454 }
3455
3456 return success();
3457}
3458
3459void transform::ContinuousTileSizesOp::getEffects(
3460 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
3461 if (isa<TransformParamTypeInterface>(getTileSizes().getType()))
3462 onlyReadsPayload(effects);
3463 else
3464 modifiesPayload(effects);
3465 onlyReadsHandle(getTargetMutable(), effects);
3466 producesHandle(getOperation()->getOpResults(), effects);
3467}
3468
3470 Type targetType, Type tileSizes,
3471 Type) {
3472 printer.printFunctionalType(TypeRange{targetType}, TypeRange{tileSizes});
3473}
3474
3476 Type &targetType,
3477 Type &tileSizesType,
3478 Type &chunkSizesType) {
3479 FunctionType funcType;
3480 llvm::SMLoc typeLoc = parser.getCurrentLocation();
3481 if (failed(parser.parseType<FunctionType>(funcType)))
3482 return failure();
3483
3484 if (funcType.getNumInputs() != 1 || funcType.getNumResults() != 1) {
3485 parser.emitError(typeLoc) << "expects a trailing functional type with one "
3486 "argument and one result";
3487 }
3488 targetType = funcType.getInput(0);
3489 tileSizesType = chunkSizesType = funcType.getResult(0);
3490
3491 return success();
3492}
3493
3494//===----------------------------------------------------------------------===//
3495// TileUsingForOp
3496//===----------------------------------------------------------------------===//
3497
3498void transform::TileUsingForOp::build(
3499 OpBuilder &builder, OperationState &result, TypeRange loopTypes,
3500 Value target, ArrayRef<int64_t> staticTileSizes,
3501 ArrayRef<int64_t> interchange,
3502 std::optional<ArrayRef<bool>> scalableSizes) {
3503 return build(builder, result, loopTypes,
3504 /*target=*/target,
3505 /*mixedTileSizes=*/
3506 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileSizes)),
3507 interchange, scalableSizes);
3508}
3509
3510void transform::TileUsingForOp::build(
3511 OpBuilder &builder, OperationState &result, Value target,
3512 ArrayRef<int64_t> staticTileSizes, ArrayRef<int64_t> interchange,
3513 std::optional<ArrayRef<bool>> scalableSizes) {
3514 build(builder, result, target,
3515 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileSizes)),
3516 interchange, scalableSizes);
3517}
3518
3519void transform::TileUsingForOp::build(
3520 OpBuilder &builder, OperationState &result, Value target,
3521 ArrayRef<OpFoldResult> mixedTileSizes, ArrayRef<int64_t> interchange,
3522 std::optional<ArrayRef<bool>> scalableSizes) {
3523 // Loop types are automaticaly splat by the callee, setting up one is
3524 // enough.
3525 SmallVector<Type> loopTypes(1, builder.getType<transform::AnyOpType>());
3526 build(builder, result, loopTypes, target, mixedTileSizes, interchange,
3527 scalableSizes);
3528}
3529
3530void transform::TileUsingForOp::build(
3531 OpBuilder &builder, OperationState &result, Value target,
3532 ArrayRef<OpFoldResult> mixedTileSizes,
3533 ArrayRef<OpFoldResult> mixedInterchange,
3534 std::optional<ArrayRef<bool>> scalableSizes) {
3535 // Loop types are automaticaly splat by the callee, setting up one is
3536 // enough.
3537 SmallVector<Type> loopTypes(1, builder.getType<transform::AnyOpType>());
3538 build(builder, result, loopTypes, target, mixedTileSizes, mixedInterchange,
3539 scalableSizes);
3540}
3541
3542void transform::TileUsingForOp::build(
3543 OpBuilder &builder, OperationState &result, TypeRange loopTypes,
3544 Value target, ArrayRef<OpFoldResult> mixedTileSizes,
3545 ArrayRef<int64_t> interchange,
3546 std::optional<ArrayRef<bool>> scalableSizes) {
3547 SmallVector<OpFoldResult> mixedInterchange =
3548 getAsOpFoldResult(builder.getI64ArrayAttr(interchange));
3549 build(builder, result, loopTypes, target, mixedTileSizes, mixedInterchange,
3550 scalableSizes);
3551}
3552
3553void transform::TileUsingForOp::build(
3554 OpBuilder &builder, OperationState &result, TypeRange loopTypes,
3555 Value target, ArrayRef<OpFoldResult> mixedTileSizes,
3556 ArrayRef<OpFoldResult> mixedInterchange,
3557 std::optional<ArrayRef<bool>> scalableSizes) {
3558 SmallVector<int64_t> staticTileSizes;
3559 SmallVector<Value> dynamicTileSizes;
3560 SmallVector<int64_t> staticInterchange;
3561 SmallVector<Value> dynamicInterchange;
3562 dispatchIndexOpFoldResults(mixedTileSizes, dynamicTileSizes, staticTileSizes);
3563 dispatchIndexOpFoldResults(mixedInterchange, dynamicInterchange,
3564 staticInterchange);
3565 // Call the default builder which sets up the proper operands segment sizes
3566 // attributes for multiple variadic operands. In the absence of this,
3567 // horrible bugs ensue.
3568 auto staticTileSizesAttr = builder.getDenseI64ArrayAttr(staticTileSizes);
3569 auto staticInterchangeAttr = builder.getDenseI64ArrayAttr(staticInterchange);
3570 unsigned numExpectedLoops =
3571 staticTileSizes.size() - llvm::count(staticTileSizes, 0);
3572 SmallVector<Type> resultTypes;
3573 resultTypes.reserve(numExpectedLoops);
3574 assert((loopTypes.size() == 1 || loopTypes.size() == numExpectedLoops) &&
3575 "expected one loop type or as many as loops");
3576 if (loopTypes.size() == 1)
3577 resultTypes.append(numExpectedLoops, loopTypes[0]);
3578 else
3579 llvm::append_range(resultTypes, loopTypes);
3580 SmallVector<bool> expandedScalableSizes(mixedTileSizes.size(), false);
3581 if (scalableSizes.has_value())
3582 expandedScalableSizes.assign(scalableSizes->begin(), scalableSizes->end());
3583 Value packedTileSizes;
3584 build(builder, result, /*tiled_linalg_op=*/target.getType(),
3585 /*loops=*/resultTypes,
3586 /*target=*/target,
3587 /*dynamic_sizes=*/dynamicTileSizes,
3588 /*interchange=*/dynamicInterchange,
3589 /*packed_tile_sizes=*/packedTileSizes,
3590 /*packed_interchange=*/Value(),
3591 /*static_sizes=*/staticTileSizesAttr,
3592 /*static_interchange=*/staticInterchangeAttr,
3593 /*scalable_sizes=*/expandedScalableSizes);
3594}
3595
3596LogicalResult transform::TileUsingForOp::verify() {
3597 bool hasPackedTiles = getPackedTileSizes() != Value();
3598 bool hasPackedInterchange = getPackedInterchange() != Value();
3599 if (!getMixedSizes().empty() && hasPackedTiles)
3600 return emitOpError(
3601 "tile_sizes and packed_tile_sizes are mutually exclusive");
3602 if (!getMixedInterchange().empty() && hasPackedInterchange)
3603 return emitOpError(
3604 "interchange and packed_interchange are mutually exclusive");
3605 if (hasPackedTiles && !getScalableSizes().empty())
3606 return emitOpError(
3607 "scalable tile_sizes are not supported with packed_tile_sizes");
3608
3609 if (getMixedSizes().size() != getScalableSizes().size())
3610 return emitOpError("expected same number of sizes (")
3611 << getMixedSizes().size() << ") and scalable sizes ("
3612 << getScalableSizes().size() << ")";
3613
3614 auto iterspaceRank = getStaticSizes().size();
3615 ArrayRef<int64_t> permutation = getStaticInterchange();
3616 if (permutation.size() > iterspaceRank)
3617 return emitOpError()
3618 << "interchange length exceeds iteration space dimensions ("
3619 << iterspaceRank << "), found " << getInterchange();
3620 SmallVector<bool> seen(iterspaceRank, false);
3621 for (int64_t v : permutation) {
3622 if (!ShapedType::isDynamic(v)) {
3623 if (v < 0 || v >= static_cast<int64_t>(iterspaceRank))
3624 return emitOpError() << "expects interchange values to be in range [0, "
3625 << iterspaceRank << "), found: " << v;
3626 if (seen[v])
3627 return emitOpError() << "found duplicate interchange value: " << v;
3628 seen[v] = true;
3629 }
3630 }
3631
3632 ArrayRef<int64_t> staticSizes = getStaticSizes();
3633 unsigned numExpectedLoops =
3634 hasPackedTiles ? 1 : staticSizes.size() - llvm::count(staticSizes, 0);
3635 if (getLoops().size() != numExpectedLoops)
3636 return emitOpError("expected number of loops to tile (")
3637 << numExpectedLoops << ") to match number of `loops` results ("
3638 << getLoops().size() << ")";
3639 return verifyInnerTileAlignments(getOperation(), getInnerTileAlignments());
3640}
3641
3642DiagnosedSilenceableFailure
3643transform::TileUsingForOp::apply(transform::TransformRewriter &rewriter,
3644 TransformResults &transformResults,
3645 TransformState &state) {
3646 ArrayRef<int64_t> tileSizes = getStaticSizes();
3647 bool hasPackedTiles = getPackedTileSizes() != Value();
3648 bool hasPackedInterchange = getPackedInterchange() != Value();
3649 auto transformOp = cast<TransformOpInterface>(getOperation());
3650
3651 SmallVector<OpFoldResult> mixedInterchange;
3652 if (hasPackedInterchange) {
3653 DiagnosedSilenceableFailure status =
3655 state, transformOp, mixedInterchange, getPackedInterchange());
3656 if (!status.succeeded())
3657 return status;
3658 } else {
3659 mixedInterchange = getMixedInterchange();
3660 }
3661 SmallVector<int64_t> tileInterchange;
3662 DiagnosedSilenceableFailure status = reifyMixedParamAndHandleResults(
3663 state, transformOp, mixedInterchange, tileInterchange);
3664 if (!status.succeeded())
3665 return status;
3666
3667 SmallVector<Operation *> targets =
3668 llvm::to_vector(state.getPayloadOps(getTarget()));
3669 SmallVector<SmallVector<Operation *>> dynamicSizeProducers;
3670 SmallVector<SmallVector<int64_t>> paramSizes;
3671 SmallVector<OpFoldResult> mixedTileSizes;
3672 if (hasPackedTiles) {
3674 state, transformOp, mixedTileSizes, getPackedTileSizes());
3675 if (!status.succeeded())
3676 return status;
3677 tileSizes = {};
3678 } else {
3679 dynamicSizeProducers.reserve(getDynamicSizes().size());
3680 paramSizes.reserve(getDynamicSizes().size());
3681 for (Value transformValue : getDynamicSizes()) {
3682 if (isa<TransformParamTypeInterface>(transformValue.getType())) {
3683 dynamicSizeProducers.push_back({});
3684 ArrayRef<Attribute> params = state.getParams(transformValue);
3685 paramSizes.push_back(llvm::map_to_vector(params, [](Attribute attr) {
3686 return cast<IntegerAttr>(attr).getValue().getSExtValue();
3687 }));
3688
3689 if (paramSizes.back().size() != targets.size()) {
3690 DiagnosedSilenceableFailure diag =
3691 emitSilenceableError()
3692 << "expected as many parameter values ("
3693 << dynamicSizeProducers.back().size() << ") as target ops ("
3694 << targets.size() << ")";
3695 diag.attachNote(transformValue.getLoc()) << "for this parameter";
3696 return diag;
3697 }
3698
3699 continue;
3700 }
3701 paramSizes.push_back({});
3702 dynamicSizeProducers.push_back(
3703 llvm::to_vector(state.getPayloadOps(transformValue)));
3704
3705 if (dynamicSizeProducers.back().size() != targets.size()) {
3706 DiagnosedSilenceableFailure diag =
3707 emitSilenceableError()
3708 << "expected as many dynamic size-producing operations ("
3709 << dynamicSizeProducers.back().size() << ") as target ops ("
3710 << targets.size() << ")";
3711 diag.attachNote(transformValue.getLoc()) << "for this handle";
3712 return diag;
3713 }
3714
3715 for (Operation *op : dynamicSizeProducers.back()) {
3716 if (op->getNumResults() == 1 &&
3717 isa<IndexType>(op->getResult(0).getType())) {
3718 continue;
3719 }
3720
3721 DiagnosedSilenceableFailure diag =
3722 emitSilenceableError() << "expected sizes to be produced by ops "
3723 "with a single index-type result";
3724 diag.attachNote(op->getLoc()) << "size producer op";
3725 diag.attachNote(transformValue.getLoc()) << "for this handle";
3726 return diag;
3727 }
3728 }
3729 }
3730
3731 SmallVector<Operation *> tiled;
3732 SmallVector<SmallVector<Operation *, 4>, 4> loops;
3733 size_t numLoops =
3734 hasPackedTiles
3735 ? llvm::count_if(mixedTileSizes,
3736 [](OpFoldResult ofr) {
3737 if (auto attr = dyn_cast<Attribute>(ofr))
3738 return cast<IntegerAttr>(attr).getInt() != 0;
3739 return true;
3740 })
3741 : getLoops().size();
3742 loops.resize(numLoops);
3743 auto scalableSizes = getScalableSizes();
3744 for (auto [i, op] : llvm::enumerate(targets)) {
3745 auto tilingInterface = dyn_cast<TilingInterface>(op);
3746 if (!tilingInterface) {
3747 DiagnosedSilenceableFailure diag =
3748 emitSilenceableError()
3749 << "only ops implementing TilingInterface are supported";
3750 diag.attachNote(op->getLoc()) << "target op";
3751 return diag;
3752 }
3753 if (isa<linalg::RelayoutOpInterface>(op) &&
3754 !cast<DestinationStyleOpInterface>(op).hasPureTensorSemantics()) {
3755 DiagnosedSilenceableFailure diag =
3756 emitSilenceableError()
3757 << "tiling only supports tensor semantics for linalg.pack / "
3758 "linalg.unpack. The target has memref operands";
3759 diag.attachNote(op->getLoc()) << "target op";
3760 return diag;
3761 }
3762
3763 int64_t iterspaceRank = tilingInterface.getLoopIteratorTypes().size();
3764 if (tileInterchange.size() > static_cast<size_t>(iterspaceRank)) {
3765 return emitSilenceableError()
3766 << "interchange length exceeds iteration space dimensions ("
3767 << iterspaceRank << ")";
3768 }
3769 SmallVector<bool> seen(iterspaceRank, false);
3770 for (int64_t v : tileInterchange) {
3771 if (v < 0 || v >= iterspaceRank) {
3772 return emitSilenceableError()
3773 << "expects interchange values to be in range [0, "
3774 << iterspaceRank << "), found: " << v;
3775 }
3776 if (seen[v]) {
3777 return emitSilenceableError()
3778 << "found duplicate interchange value: " << v;
3779 }
3780 seen[v] = true;
3781 }
3782
3783 if (tileSizes.size() > tilingInterface.getLoopIteratorTypes().size()) {
3784 DiagnosedSilenceableFailure diag =
3785 emitSilenceableError()
3786 << "too many tiles provided, expected at most "
3787 << tilingInterface.getLoopIteratorTypes().size() << " found "
3788 << tileSizes.size();
3789 diag.attachNote(op->getLoc()) << "target op";
3790 return diag;
3791 }
3792
3793 scf::SCFTilingOptions tilingOptions;
3794 if (!hasPackedTiles && tileSizes.empty()) {
3795 tilingOptions.setTileSizeComputationFunction(
3796 [](OpBuilder &, Operation *) -> SmallVector<OpFoldResult> {
3797 return {};
3798 });
3799 } else if (hasPackedTiles) {
3800 tilingOptions.setTileSizes(mixedTileSizes);
3801 } else {
3802 tilingOptions.setTileSizeComputationFunction([&, index = i](OpBuilder &b,
3803 Operation *) {
3804 SmallVector<OpFoldResult> sizes;
3805 sizes.reserve(tileSizes.size());
3806 unsigned dynamicIdx = 0;
3807
3808 for (auto [ofrIdx, ofr] : llvm::enumerate(getMixedSizes())) {
3809 if (auto attr = llvm::dyn_cast_if_present<Attribute>(ofr)) {
3810 if (scalableSizes[ofrIdx]) {
3812 b, getLoc(), cast<IntegerAttr>(attr).getInt());
3813 Value vscale =
3814 vector::VectorScaleOp::create(b, getLoc(), b.getIndexType());
3815 sizes.push_back(
3816 arith::MulIOp::create(b, getLoc(), val, vscale).getResult());
3817 } else {
3818 sizes.push_back(attr);
3819 }
3820 continue;
3821 }
3822 ArrayRef<Operation *> dynamicSizes = dynamicSizeProducers[dynamicIdx];
3823 ArrayRef<int64_t> params = paramSizes[dynamicIdx];
3824 ++dynamicIdx;
3825 assert((dynamicSizes.empty() ^ params.empty()) &&
3826 "expected either dynamic sizes or parameters");
3827 if (!params.empty()) {
3828 sizes.push_back(b.getIndexAttr(params[index]));
3829 } else {
3830 sizes.push_back(dynamicSizes[index]->getResult(0));
3831 }
3832 }
3833 return sizes;
3834 });
3835 }
3836
3837 tilingOptions.setInterchange(tileInterchange);
3838 tilingOptions.setInnerTileAlignments(
3839 convertInnerTileAlignments(getInnerTileAlignments()));
3840 FailureOr<scf::SCFTilingResult> maybeTilingResult =
3841 tileUsingSCF(rewriter, tilingInterface, tilingOptions);
3842 if (failed(maybeTilingResult))
3844
3845 rewriter.replaceOp(op, maybeTilingResult->replacements);
3846
3847 tiled.append(maybeTilingResult->tiledOps);
3848 for (const auto &en2 : llvm::enumerate(maybeTilingResult->loops))
3849 loops[en2.index()].push_back(en2.value());
3850 }
3851
3852 transformResults.set(cast<OpResult>(getTiledLinalgOp()), tiled);
3853 if (hasPackedTiles) {
3854 // For packed sizes all created loops are assigned to a single handle.
3855 SmallVector<Operation *> flattenedLoops;
3856 for (auto [targetIdx, _] : llvm::enumerate(targets))
3857 for (auto [loopIdx, __] : llvm::enumerate(loops))
3858 flattenedLoops.push_back(loops[loopIdx][targetIdx]);
3859 transformResults.set(cast<OpResult>(getLoops().front()), flattenedLoops);
3860 } else {
3861 for (const auto &en : llvm::enumerate(loops))
3862 transformResults.set(cast<OpResult>(getLoops()[en.index()]), en.value());
3863 }
3864
3866}
3867
3868SmallVector<OpFoldResult> transform::TileUsingForOp::getMixedSizes() {
3869 ValueRange dynamic = getDynamicSizes();
3870 ArrayRef<int64_t> tileSizes = getStaticSizes();
3871 SmallVector<OpFoldResult> results;
3872 results.reserve(tileSizes.size());
3873 unsigned dynamicPos = 0;
3874 Builder builder(getContext());
3875 for (int64_t size : tileSizes) {
3876 if (size == ShapedType::kDynamic) {
3877 results.push_back(dynamic[dynamicPos++]);
3878 } else {
3879 results.push_back(builder.getIndexAttr(size));
3880 }
3881 }
3882 return results;
3883}
3884
3885SmallVector<OpFoldResult> transform::TileUsingForOp::getMixedInterchange() {
3886 return getMixedValues(getStaticInterchange(), getInterchange(), getContext());
3887}
3888
3889void transform::TileUsingForOp::getEffects(
3890 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
3891 consumesHandle(getTargetMutable(), effects);
3892 onlyReadsHandle(getDynamicSizesMutable(), effects);
3893 onlyReadsHandle(getInterchangeMutable(), effects);
3894 onlyReadsHandle(getPackedTileSizesMutable(), effects);
3895 onlyReadsHandle(getPackedInterchangeMutable(), effects);
3896 producesHandle(getOperation()->getOpResults(), effects);
3897 modifiesPayload(effects);
3898}
3899
3900//===----------------------------------------------------------------------===//
3901// TileUsingForallOp
3902//===----------------------------------------------------------------------===//
3903
3904void transform::TileUsingForallOp::build(OpBuilder &builder,
3905 OperationState &result, Value target,
3906 ArrayRef<int64_t> staticTileSizes,
3907 transform::TileSizesSpec,
3908 ArrayAttr mapping) {
3909 return build(builder, result,
3910 /*target=*/target,
3911 /*mixedTileSizes=*/
3912 getAsOpFoldResult(builder.getI64ArrayAttr(staticTileSizes)),
3913 /*_=*/TileSizesSpec(),
3914 /*mapping=*/mapping);
3915}
3916
3917void transform::TileUsingForallOp::build(OpBuilder &builder,
3918 OperationState &result, Value target,
3919 ArrayRef<OpFoldResult> mixedTileSizes,
3920 transform::TileSizesSpec,
3921 ArrayAttr mapping) {
3922 SmallVector<int64_t> staticTileSizes;
3923 SmallVector<Value> dynamicTileSizes;
3924 dispatchIndexOpFoldResults(mixedTileSizes, dynamicTileSizes, staticTileSizes);
3925 // Call the default builder which sets up the proper operands segment sizes
3926 // attributes for multiple variadic operands. In the absence of this,
3927 // horrible bugs ensue.
3928 MLIRContext *ctx = builder.getContext();
3929 auto operationType = transform::AnyOpType::get(ctx);
3930 auto staticTileSizesAttr = builder.getDenseI64ArrayAttr(staticTileSizes);
3931 build(builder, result,
3932 /*resultTypes=*/TypeRange{operationType, operationType},
3933 /*target=*/target,
3934 /*num_threads=*/ValueRange{},
3935 /*tile_sizes=*/dynamicTileSizes,
3936 /*packed_num_threads=*/Value(),
3937 /*packed_tile_sizes=*/Value(),
3938 /*static_num_threads=*/builder.getDenseI64ArrayAttr({}),
3939 /*static_tile_sizes=*/staticTileSizesAttr,
3940 /*mapping=*/mapping);
3941}
3942
3943void transform::TileUsingForallOp::build(OpBuilder &builder,
3944 OperationState &result, Value target,
3945 ArrayRef<int64_t> staticNumThreads,
3946 transform::NumThreadsSpec,
3947 ArrayAttr mapping) {
3948 return build(builder, result, target,
3949 getAsOpFoldResult(builder.getI64ArrayAttr(staticNumThreads)),
3950 NumThreadsSpec(), mapping);
3951}
3952
3953void transform::TileUsingForallOp::build(OpBuilder &builder,
3954 OperationState &result, Value target,
3955 ArrayRef<OpFoldResult> mixedNumThreads,
3956 transform::NumThreadsSpec,
3957 ArrayAttr mapping) {
3958 SmallVector<int64_t> staticNumThreads;
3959 SmallVector<Value> dynamicNumThreads;
3960 dispatchIndexOpFoldResults(mixedNumThreads, dynamicNumThreads,
3961 staticNumThreads);
3962 // Call the default builder which sets up the proper operands segment sizes
3963 // attributes for multiple variadic operands. In the absence of this,
3964 // horrible bugs ensue.
3965 MLIRContext *ctx = builder.getContext();
3966 auto operationType = transform::AnyOpType::get(ctx);
3967 auto staticNumThreadsAttr = builder.getDenseI64ArrayAttr(staticNumThreads);
3968 build(builder, result,
3969 /*resultTypes=*/TypeRange{operationType, operationType},
3970 /*target=*/target,
3971 /*num_threads=*/dynamicNumThreads,
3972 /*tile_sizes=*/ValueRange{},
3973 /*packed_num_threads=*/Value(),
3974 /*packed_tile_sizes=*/Value(),
3975 /*static_num_threads=*/staticNumThreadsAttr,
3976 /*static_tile_sizes=*/builder.getDenseI64ArrayAttr({}),
3977 /*mapping=*/mapping);
3978}
3979
3980/// Given `lbs`, `ubs` and `steps` of loops, return (for each loop), the
3981/// normalized upper bound.
3982static SmallVector<OpFoldResult>
3985 ArrayRef<OpFoldResult> steps) {
3986 AffineExpr s0, s1, s2;
3987 bindSymbols(rewriter.getContext(), s0, s1, s2);
3988 AffineExpr normalizedUbExpr = (s1 - s0).ceilDiv(s2);
3989 SmallVector<OpFoldResult> normalizedUbs;
3990 for (auto [lb, ub, step] : llvm::zip_equal(lbs, ubs, steps)) {
3992 rewriter, loc, normalizedUbExpr, {lb, ub, step});
3993 normalizedUbs.push_back(normalizedUb);
3994 }
3995 return normalizedUbs;
3996}
3997
3998/// When a loop is normalized, the uses of the induction variable within the
3999/// loop need to replaced with `original_lb + old_iv * original_step`.
4001 Location loc, ValueRange ivs,
4003 ArrayRef<OpFoldResult> steps) {
4004 AffineExpr s0, s1;
4005 AffineExpr d0;
4006 bindSymbols(rewriter.getContext(), s0, s1);
4007 bindDims(rewriter.getContext(), d0);
4008 AffineExpr denormExpr = s0 + d0 * s1;
4009 SmallVector<Value> denormalizedIvs;
4010
4011 for (auto [iv, lb, step] : llvm::zip_equal(ivs, lbs, steps)) {
4013 rewriter, loc, denormExpr, ArrayRef<OpFoldResult>{iv, lb, step});
4014 denormalizedIvs.push_back(
4015 getValueOrCreateConstantIndexOp(rewriter, loc, denormValue));
4016 }
4017 return denormalizedIvs;
4018}
4019
4020/// Given a `scf.forall` loop return a loop op with the loop bounds
4021/// normalized.
4022/// TODO: Replace this with a general utility to normalize `scf.forall`.
4023/// At the time of writing, this wasnt done since adding this to `scf`
4024/// dialect would disallow using of `affine.apply` operations due
4025/// to cyclic dependencies. To avoid churn in lit tests
4026/// with the change this was added with, defer that to a follow up.
4027static scf::ForallOp normalizeForallLoopOp(RewriterBase &rewriter,
4028 scf::ForallOp loop) {
4029 SmallVector<OpFoldResult> lbs = loop.getMixedLowerBound();
4030 SmallVector<OpFoldResult> ubs = loop.getMixedUpperBound();
4031 SmallVector<OpFoldResult> steps = loop.getMixedStep();
4032
4033 if (llvm::all_of(lbs, isZeroInteger) && llvm::all_of(steps, isOneInteger)) {
4034 return loop;
4035 }
4036
4037 Location loc = loop.getLoc();
4038 SmallVector<OpFoldResult> normalizedUbs =
4039 normalizeUpperBounds(rewriter, loc, lbs, ubs, steps);
4040 SmallVector<OpFoldResult> normalizedLbs(normalizedUbs.size(),
4041 rewriter.getIndexAttr(0));
4042 SmallVector<OpFoldResult> normalizedSteps(normalizedUbs.size(),
4043 rewriter.getIndexAttr(1));
4044
4045 auto normalizedForallOp = scf::ForallOp::create(
4046 rewriter, loc, normalizedLbs, normalizedUbs, normalizedSteps,
4047 loop.getOutputs(), loop.getMapping(),
4048 [](OpBuilder &, Location, ValueRange) {});
4049
4050 auto normalizedLoopIvs = normalizedForallOp.getInductionVars();
4051 OpBuilder::InsertionGuard g(rewriter);
4052 Block *normalizedLoopBlock = normalizedForallOp.getBody();
4053 rewriter.setInsertionPointToStart(normalizedLoopBlock);
4054
4055 SmallVector<Value> argValues =
4056 denormalizeIndVar(rewriter, loc, normalizedLoopIvs, lbs, steps);
4057 argValues.append(normalizedForallOp.getRegionIterArgs().begin(),
4058 normalizedForallOp.getRegionIterArgs().end());
4059 Block *origLoopBlock = loop.getBody();
4060 rewriter.mergeBlocks(origLoopBlock, normalizedLoopBlock, argValues);
4061
4062 rewriter.replaceOp(loop, normalizedForallOp);
4063 return normalizedForallOp;
4064}
4065
4067 RewriterBase &rewriter, transform::TransformState &state,
4068 TransformOpInterface transformOp, Operation *target,
4069 ArrayRef<OpFoldResult> mixedNumThreads,
4070 ArrayRef<OpFoldResult> mixedTileSizes, std::optional<ArrayAttr> mapping,
4071 scf::SCFTilingResult &tilingResult) {
4072 // Transform all targets one by one.
4073 auto tileableOp = dyn_cast<TilingInterface>(target);
4074 if (!tileableOp) {
4076 transformOp.emitSilenceableError()
4077 << "only TilingInterface ops are supported";
4078 diag.attachNote(target->getLoc()) << "target op";
4079 return diag;
4080 }
4081 if (isa<linalg::RelayoutOpInterface>(target) &&
4082 !cast<DestinationStyleOpInterface>(target).hasPureTensorSemantics()) {
4084 transformOp.emitSilenceableError()
4085 << "tiling only supports tensor semantics for linalg.pack / "
4086 "linalg.unpack. The target has memref operands";
4087 diag.attachNote(target->getLoc()) << "target op";
4088 return diag;
4089 }
4090 rewriter.setInsertionPoint(tileableOp);
4091 scf::SCFTilingOptions options;
4092 options.setLoopType(scf::SCFTilingOptions::LoopType::ForallOp);
4093 if (!mixedNumThreads.empty()) {
4094 options.setNumThreads(mixedNumThreads);
4095 } else {
4096 options.setTileSizes(mixedTileSizes);
4097 }
4098 if (mapping) {
4099 options.setMapping(mapping.value().getValue());
4100 }
4101 FailureOr<scf::SCFTilingResult> maybeTilingResult =
4102 scf::tileUsingSCF(rewriter, tileableOp, options);
4103
4104 if (failed(maybeTilingResult))
4105 return transformOp.emitDefaultSilenceableFailure(tileableOp);
4106
4107 rewriter.replaceOp(tileableOp, maybeTilingResult->replacements);
4108
4109 tilingResult = *maybeTilingResult;
4110
4111 // Rank-0 ops produce no loops; skip normalization when there is nothing
4112 // to normalize.
4113 if (mixedNumThreads.empty() && !tilingResult.loops.empty()) {
4114 auto generatedForallOp = cast<scf::ForallOp>(tilingResult.loops.front());
4115 OpBuilder::InsertionGuard g(rewriter);
4116 rewriter.setInsertionPoint(generatedForallOp);
4117 scf::ForallOp normalizedForallOp =
4118 normalizeForallLoopOp(rewriter, generatedForallOp);
4119 tilingResult.loops.front() = normalizedForallOp;
4120 }
4121
4123}
4124
4125DiagnosedSilenceableFailure transform::TileUsingForallOp::apply(
4127 transform::TransformResults &transformResults,
4129 auto transformOp = cast<TransformOpInterface>(getOperation());
4130
4131 // Result payload ops.
4133 SmallVector<Operation *> tiledOps;
4134
4135 // Unpack handles.
4136 SmallVector<OpFoldResult> mixedNumThreads;
4138 getPackedNumThreads()
4140 state, transformOp, mixedNumThreads, getPackedNumThreads())
4142 state, transformOp, mixedNumThreads, getMixedNumThreads());
4143 if (!status.succeeded())
4144 return status;
4145 SmallVector<OpFoldResult> mixedTileSizes;
4146 status = getPackedTileSizes()
4148 state, transformOp, mixedTileSizes, getPackedTileSizes())
4150 state, transformOp, mixedTileSizes, getMixedTileSizes());
4151 if (!status.succeeded())
4152 return status;
4153
4154 for (Operation *target : state.getPayloadOps(getTarget())) {
4155 scf::SCFTilingResult tilingResult;
4157 rewriter, state, transformOp, target, mixedNumThreads, mixedTileSizes,
4158 getMapping(), tilingResult);
4159 if (!diag.succeeded())
4160 return diag;
4161 if (!tilingResult.loops.empty())
4162 tileOps.push_back(tilingResult.loops.front());
4163 tiledOps.append(tilingResult.tiledOps);
4164 }
4165
4166 transformResults.set(cast<OpResult>(getForallOp()), tileOps);
4167 transformResults.set(cast<OpResult>(getTiledOp()), tiledOps);
4168
4170}
4171
4172void transform::TileUsingForallOp::getEffects(
4173 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
4174 consumesHandle(getTargetMutable(), effects);
4175 onlyReadsHandle(getTileSizesMutable(), effects);
4176 onlyReadsHandle(getNumThreadsMutable(), effects);
4177 onlyReadsHandle(getPackedNumThreadsMutable(), effects);
4178 onlyReadsHandle(getPackedTileSizesMutable(), effects);
4179 producesHandle(getOperation()->getOpResults(), effects);
4180 modifiesPayload(effects);
4181}
4182
4183SmallVector<OpFoldResult> TileUsingForallOp::getMixedNumThreads() {
4184 Builder b(getContext());
4185 return getMixedValues(getStaticNumThreads(), getNumThreads(), b);
4186}
4187
4188SmallVector<OpFoldResult> TileUsingForallOp::getMixedTileSizes() {
4189 Builder b(getContext());
4190 return getMixedValues(getStaticTileSizes(), getTileSizes(), b);
4191}
4192
4193LogicalResult TileUsingForallOp::verify() {
4194 int numThreadsSpec = static_cast<int>(!getMixedNumThreads().empty()) +
4195 static_cast<int>(getPackedNumThreads() != Value());
4196 if (numThreadsSpec > 1)
4197 return emitOpError(
4198 "num_threads and packed_num_threads are mutually exclusive");
4199 int tileSizesSpec = static_cast<int>(!getMixedTileSizes().empty()) +
4200 static_cast<int>(getPackedTileSizes() != Value());
4201 if (tileSizesSpec > 1)
4202 return emitOpError(
4203 "tile_sizes and packed_tile_sizes are mutually exclusive");
4204 if (numThreadsSpec == 0 && tileSizesSpec == 0)
4205 return emitOpError("either (packed_)num_threads or (packed_)tile_sizes "
4206 "must be specified");
4207 return success();
4208}
4209
4210//===----------------------------------------------------------------------===//
4211// VectorizeChildrenAndApplyPatternsOp
4212//===----------------------------------------------------------------------===//
4213
4214void transform::VectorizeChildrenAndApplyPatternsOp::build(
4215 OpBuilder &builder, OperationState &result, Value target,
4216 bool foldTypeExtensionsIntoContract, bool vectorizePadding,
4217 bool vectorizeExtract, bool flatten1DDepthwiseConv) {
4218 result.addOperands(target);
4219 if (foldTypeExtensionsIntoContract) {
4220 result.addAttribute(
4221 VectorizeChildrenAndApplyPatternsOp::
4222 getFoldTypeExtensionsIntoContractAttrName(result.name),
4223 builder.getUnitAttr());
4224 }
4225 if (vectorizePadding) {
4226 result.addAttribute(
4227 VectorizeChildrenAndApplyPatternsOp::getVectorizePaddingAttrName(
4228 result.name),
4229 builder.getUnitAttr());
4230 }
4231 if (vectorizeExtract) {
4232 result.addAttribute(
4233 VectorizeChildrenAndApplyPatternsOp::getVectorizeNdExtractAttrName(
4234 result.name),
4235 builder.getUnitAttr());
4236 }
4237 if (flatten1DDepthwiseConv) {
4238 result.addAttribute(
4239 VectorizeChildrenAndApplyPatternsOp::getFlatten_1dDepthwiseConvAttrName(
4240 result.name),
4241 builder.getUnitAttr());
4242 }
4243 result.addTypes(transform::AnyOpType::get(builder.getContext()));
4244}
4245
4246namespace {
4247/// This is an helper only to call vectorize via a pattern inside of
4248/// VectorizeChildrenAndApplyPatternsOp::applyToOne.
4249struct VectorizationPattern : public RewritePattern {
4250 explicit VectorizationPattern(MLIRContext *context,
4251 bool vectorizeExtract = false,
4252 bool flattenConv = false)
4253 : RewritePattern(MatchAnyOpTypeTag(), /*benefit=*/1, context),
4254 vectorizeNDExtract(vectorizeExtract),
4255 flatten1DDepthwiseConv(flattenConv) {}
4256 LogicalResult matchAndRewrite(Operation *op,
4257 PatternRewriter &rewriter) const override {
4259 return rewriter.notifyMatchFailure(op,
4260 "Unsupported Op, cannot vectorize");
4261 FailureOr<VectorizationResult> vectorResults =
4262 vectorize(rewriter, op, /*inputVectorSizes=*/{},
4263 /*inputScalableVecDims=*/{}, vectorizeNDExtract,
4264 flatten1DDepthwiseConv);
4265 if (failed(vectorResults))
4266 return failure();
4267 rewriter.replaceOp(op, vectorResults->replacements);
4268 return success();
4269 }
4270
4271private:
4272 /// Controls whether to vectorize `tensor.extract` when the input tensor is
4273 /// rank >= 2.
4274 bool vectorizeNDExtract = false;
4275 /// Controls whether to "flatten" the channel dimension when vectorising 1D
4276 /// depthwise convolutions. This should lead to bette vectorization for
4277 /// tensors with a low number of channel dimensions.
4278 bool flatten1DDepthwiseConv = false;
4279};
4280} // namespace
4281
4282DiagnosedSilenceableFailure
4283transform::VectorizeChildrenAndApplyPatternsOp::applyToOne(
4284 transform::TransformRewriter &rewriter, Operation *target,
4285 transform::ApplyToEachResultList &results,
4286 transform::TransformState &state) {
4287 if (!target->hasTrait<OpTrait::IsIsolatedFromAbove>()) {
4288 auto diag = this->emitOpError("requires isolated-from-above targets");
4289 diag.attachNote(target->getLoc()) << "non-isolated target";
4291 }
4292
4293 MLIRContext *ctx = getContext();
4294 RewritePatternSet patterns(ctx);
4295 patterns.add<VectorizationPattern>(ctx, getVectorizeNdExtract(),
4296 getFlatten_1dDepthwiseConv());
4297
4298 if (!getDisableTransferPermutationMapLoweringPatterns())
4300
4301 if (!getDisableMultiReductionToContractPatterns())
4303
4305
4306 patterns.add<linalg::LinalgCopyVTRForwardingPattern,
4307 linalg::LinalgCopyVTWForwardingPattern>(ctx,
4308 /*benefit=*/2);
4309 vector::TransferReadOp::getCanonicalizationPatterns(patterns, ctx);
4310 vector::TransferWriteOp::getCanonicalizationPatterns(patterns, ctx);
4312
4313 patterns.add<CopyVectorizationPattern>(ctx);
4314
4315 if (getFoldTypeExtensionsIntoContract())
4317
4318 if (getVectorizePadding()) {
4320 // This creates an alternative path for lowering tensor.pad - by
4321 // decomposing it into e.g. linalg.fill.
4323 }
4325
4326 TrackingListener listener(state, *this);
4327 if (failed(
4328 applyPatternsGreedily(target, std::move(patterns),
4329 GreedyRewriteConfig().setListener(&listener))))
4330 return emitDefaultDefiniteFailure(target);
4331
4332 results.push_back(target);
4334}
4335
4336//===----------------------------------------------------------------------===//
4337// VectorizeOp
4338//===----------------------------------------------------------------------===//
4339
4340DiagnosedSilenceableFailure transform::VectorizeOp::apply(
4341 transform::TransformRewriter &rewriter,
4342 mlir::transform::TransformResults &transformResults,
4343 mlir::transform::TransformState &state) {
4344 auto targets = state.getPayloadOps(getTarget());
4345 if (std::empty(targets))
4347 auto transformOp = cast<TransformOpInterface>(getOperation());
4348 SmallVector<int64_t> vectorSizes;
4349 DiagnosedSilenceableFailure status = reifyMixedParamAndHandleResults(
4350 state, transformOp, getMixedVectorSizes(), vectorSizes);
4351 if (!status.succeeded())
4352 return status;
4353
4354 // TODO: Check that the correct number of vectorSizes was provided.
4355 for (Operation *target : targets) {
4357 return mlir::emitSilenceableFailure(target->getLoc())
4358 << "Unsupported Op, cannot vectorize";
4359 }
4360 FailureOr<VectorizationResult> vectorResults =
4361 linalg::vectorize(rewriter, target, vectorSizes, getScalableSizes(),
4362 getVectorizeNdExtract().value_or(false),
4363 /*flatten1DDepthwiseConv=*/false,
4364 getAssumeDynamicDimsMatchVecSizes().value_or(false),
4365 getCreateNamedContraction().value_or(false));
4366 if (failed(vectorResults)) {
4367 return mlir::emitSilenceableFailure(target->getLoc())
4368 << "Attempted to vectorize, but failed";
4369 }
4370 rewriter.replaceOp(target, vectorResults->replacements);
4371 }
4372
4374}
4375
4376void transform::VectorizeOp::getEffects(
4377 SmallVectorImpl<MemoryEffects::EffectInstance> &effects) {
4378 consumesHandle(getTargetMutable(), effects);
4379 onlyReadsHandle(getVectorSizesMutable(), effects);
4380 modifiesPayload(effects);
4381}
4382
4383SmallVector<OpFoldResult> VectorizeOp::getMixedVectorSizes() {
4384 OpBuilder b(getContext());
4385 return getMixedValues(getStaticVectorSizes(), getVectorSizes(), b);
4386}
4387
4388LogicalResult transform::VectorizeOp::verify() {
4389 if (getStaticVectorSizes().size() != getScalableSizes().size())
4390 return emitOpError("expected same number of vector sizes (")
4391 << getStaticVectorSizes().size() << ") and scalable sizes ("
4392 << getScalableSizes().size() << ")";
4393 return success();
4394}
4395
4396//===----------------------------------------------------------------------===//
4397// HoistRedundantVectorTransfersOp
4398//===----------------------------------------------------------------------===//
4399
4400DiagnosedSilenceableFailure
4401transform::HoistRedundantVectorTransfersOp::applyToOne(
4402 transform::TransformRewriter &rewriter, func::FuncOp target,
4403 transform::ApplyToEachResultList &results,
4404 transform::TransformState &state) {
4405 // WARNING: This hoisting does not model parallelism and is generally
4406 // incorrect when used on distributed loops with memref semantics!
4407 // TODO: obsolete and should be retired.
4408 linalg::hoistRedundantVectorTransfers(target, getVerifyNonZeroTrip());
4409 results.push_back(target);
4411}
4412
4413//===----------------------------------------------------------------------===//
4414// HoistRedundantVectorBroadcastsOp
4415//===----------------------------------------------------------------------===//
4416
4417DiagnosedSilenceableFailure
4418transform::HoistRedundantVectorBroadcastsOp::applyToOne(
4419 transform::TransformRewriter &rewriter, mlir::Operation *target,
4420 transform::ApplyToEachResultList &results,
4421 transform::TransformState &state) {
4422 rewriter.setInsertionPoint(target);
4424 results.push_back(target);
4426}
4427
4428//===----------------------------------------------------------------------===//
4429// ConvertConv2DToImg2ColOp.
4430//===----------------------------------------------------------------------===//
4431
4432DiagnosedSilenceableFailure transform::ConvertConv2DToImg2ColOp::applyToOne(
4433 transform::TransformRewriter &rewriter, linalg::LinalgOp target,
4434 transform::ApplyToEachResultList &results,
4435 transform::TransformState &state) {
4436 rewriter.setInsertionPoint(target);
4437 auto maybeTransformed =
4439 target)
4440 .Case([&](linalg::Conv2DNhwcHwcfOp op) {
4441 return rewriteInIm2Col(rewriter, op);
4442 })
4443 .Case([&](linalg::Conv2DNhwcFhwcOp op) {
4444 return rewriteInIm2Col(rewriter, op);
4445 })
4446 .Case([&](linalg::DepthwiseConv2DNhwcHwcOp op) {
4447 return rewriteInIm2Col(rewriter, op);
4448 })
4449 .Case([&](linalg::Conv2DNchwFchwOp op) {
4450 return rewriteInIm2Col(rewriter, op);
4451 })
4452 .Default([&](Operation *op) {
4453 return rewriter.notifyMatchFailure(op, "not supported");
4454 });
4455 if (failed(maybeTransformed))
4456 return emitDefaultSilenceableFailure(target);
4457 // Handle to the operation producing the img2col tensor.
4458 results.push_back(maybeTransformed->first);
4459 // Handle to the operation that replaces the original convolution.
4460 results.push_back(maybeTransformed->second);
4462}
4463
4464//===----------------------------------------------------------------------===//
4465// FlattenElementwiseLinalgOp.
4466//===----------------------------------------------------------------------===//
4467
4468DiagnosedSilenceableFailure transform::FlattenElementwiseLinalgOp::applyToOne(
4469 transform::TransformRewriter &rewriter, linalg::LinalgOp target,
4470 transform::ApplyToEachResultList &results,
4471 transform::TransformState &state) {
4472 rewriter.setInsertionPoint(target);
4473 if (!isElementwise(target))
4474 return mlir::emitSilenceableFailure(target->getLoc())
4475 << "only elementwise flattening is supported";
4476
4477 if (!llvm::all_of(target.getIndexingMapsArray(), [](AffineMap m) {
4478 return m.isPermutation() || m.getNumResults() == 0;
4479 })) {
4480 results.push_back(target);
4481 return mlir::emitSilenceableFailure(target->getLoc())
4482 << "broadcasting of non scalar operands is not supported";
4483 }
4484
4485 // If rank <= 1, do nothing
4486 if (target.getNumLoops() <= 1) {
4487 results.push_back(target);
4489 }
4490
4491 // Only broadcasts with a 0-D input are handled; leave anything else
4492 // unchanged.
4493 if (auto broadcastOp = dyn_cast<linalg::BroadcastOp>(target.getOperation());
4494 broadcastOp && broadcastOp.getInput().getType().getRank() != 0) {
4495 results.push_back(target);
4497 }
4498
4499 // Attempt to flatten all dims to one.
4500 ReassociationIndices reassociation(target.getNumLoops());
4501 std::iota(reassociation.begin(), reassociation.end(), 0);
4502 auto maybeFlattened =
4503 collapseOpIterationDims(target, reassociation, rewriter);
4504 if (failed(maybeFlattened))
4505 return mlir::emitSilenceableFailure(target->getLoc())
4506 << "attempted to flatten, but failed";
4507 results.push_back(maybeFlattened->collapsedOp);
4508 rewriter.replaceOp(target, maybeFlattened->results);
4510}
4511
4512//===----------------------------------------------------------------------===//
4513// TransposeConv2DOp
4514//===----------------------------------------------------------------------===//
4515
4516DiagnosedSilenceableFailure transform::TransposeConv2DOp::applyToOne(
4517 transform::TransformRewriter &rewriter, linalg::LinalgOp target,
4518 transform::ApplyToEachResultList &results,
4519 transform::TransformState &state) {
4520 rewriter.setInsertionPoint(target);
4521 auto maybeTransformed =
4523 .Case([&](linalg::Conv2DNhwcFhwcOp op) {
4524 return transposeConv2D(rewriter, op);
4525 })
4526 .Case([&](linalg::Conv2DNhwcFhwcQOp op) {
4527 return transposeConv2D(rewriter, op);
4528 })
4529 .Default([&](Operation *op) {
4530 return rewriter.notifyMatchFailure(op, "not supported");
4531 });
4532 if (failed(maybeTransformed))
4533 return emitDefaultSilenceableFailure(target);
4534 // Handle to the new Conv2D operation with transposed filters
4535 results.push_back(*maybeTransformed);
4537}
4538
4539//===----------------------------------------------------------------------===//
4540// TransposeMatmulOp
4541//===----------------------------------------------------------------------===//
4542
4543DiagnosedSilenceableFailure transform::TransposeMatmulOp::applyToOne(
4544 transform::TransformRewriter &rewriter, linalg::LinalgOp target,
4545 transform::ApplyToEachResultList &results,
4546 transform::TransformState &state) {
4547 rewriter.setInsertionPoint(target);
4548 bool transposeLHS = getInputToTranspose() == TransposeMatmulInput::lhs;
4549 auto maybeTransformed =
4551 .Case([&](linalg::MatmulOp op) {
4552 return transposeMatmul(rewriter, op, transposeLHS);
4553 })
4554 .Case([&](linalg::BatchMatmulOp op) {
4555 return transposeBatchMatmul(rewriter, op, transposeLHS);
4556 })
4557 .Default(failure());
4558 if (failed(maybeTransformed))
4559 return emitSilenceableFailure(target->getLoc()) << "not supported";
4560 // Handle to the new Matmul operation with transposed filters
4561 results.push_back(*maybeTransformed);
4563}
4564
4565//===----------------------------------------------------------------------===//
4566// InsertSliceToCopyOp
4567//===----------------------------------------------------------------------===//
4568template <typename OpTy>
4569static DiagnosedSilenceableFailure
4570doit(RewriterBase &rewriter, OpTy target,
4573 static_assert(llvm::is_one_of<OpTy, tensor::InsertSliceOp,
4574 tensor::ParallelInsertSliceOp>() &&
4575 "wrong op type");
4576
4577 if (auto copySource =
4578 target.getSource().template getDefiningOp<linalg::CopyOp>()) {
4579 results.push_back(copySource);
4581 }
4582
4583 // If we are inside a `ParallelCombiningOp` region, temporarily set the
4584 // insertion point outside: only ops implementing ParallelCombiningOpInterface
4585 // are allowed in there.
4586 if (isa<mlir::ParallelCombiningOpInterface>(target.getOperation()))
4587 rewriter.setInsertionPoint(target->getParentOp());
4588
4589 Value extracted = tensor::ExtractSliceOp::create(
4590 rewriter, target.getLoc(), target.getDest(), target.getMixedOffsets(),
4591 target.getMixedSizes(), target.getMixedStrides());
4592 Value copied = linalg::CopyOp::create(rewriter, target.getLoc(),
4593 target.getSource(), extracted)
4594 .getResult(0);
4595 // Reset the insertion point.
4596 rewriter.setInsertionPoint(target);
4597 rewriter.replaceOpWithNewOp<OpTy>(
4598 target, copied, target.getDest(), target.getMixedOffsets(),
4599 target.getMixedSizes(), target.getMixedStrides());
4600
4601 results.push_back(copied.getDefiningOp());
4603}
4604
4605DiagnosedSilenceableFailure transform::InsertSliceToCopyOp::applyToOne(
4606 transform::TransformRewriter &rewriter, Operation *targetOp,
4607 transform::ApplyToEachResultList &results,
4608 transform::TransformState &state) {
4609
4610 rewriter.setInsertionPoint(targetOp);
4611 if (auto target = dyn_cast<tensor::InsertSliceOp>(targetOp))
4612 return doit(rewriter, target, results, state);
4613 if (auto target = dyn_cast<tensor::ParallelInsertSliceOp>(targetOp))
4614 return doit(rewriter, target, results, state);
4615
4616 DiagnosedSilenceableFailure diag =
4617 emitSilenceableError()
4618 << "only InsertSliceOp and ParallelInsertSliceOp ops are supported";
4619 diag.attachNote(targetOp->getLoc()) << "target op";
4620 return diag;
4621}
4622
4623//===----------------------------------------------------------------------===//
4624// MapCopyToThreadsOp
4625//===----------------------------------------------------------------------===//
4626
4627DiagnosedSilenceableFailure transform::MapCopyToThreadsOp::applyToOne(
4628 transform::TransformRewriter &rewriter, Operation *target,
4629 transform::ApplyToEachResultList &results,
4630 transform::TransformState &state) {
4631 // Check if the op is supported.
4632 if (!isa<linalg::CopyOp, tensor::PadOp>(target)) {
4633 DiagnosedSilenceableFailure diag =
4634 emitSilenceableError()
4635 << "only linalg.copy and tensor.pad target ops are supported";
4636 diag.attachNote(target->getLoc()) << "target op";
4637 return diag;
4638 }
4639 assert(target->getNumResults() == 1 && "expected single result");
4640 auto resultShapedType = cast<ShapedType>(target->getResult(0).getType());
4641 if (!resultShapedType.hasStaticShape()) {
4642 DiagnosedSilenceableFailure diag =
4643 emitSilenceableError()
4644 << "only statically sized ops of rank <= 3 are supported";
4645 diag.attachNote(target->getLoc()) << "target op";
4646 return diag;
4647 }
4648
4649 // Conservatively set the minimum viable desired bitwidth alignment.
4650 int64_t desiredBitAlignment = getDesiredBitAlignment();
4651 int64_t eltBitwidth =
4652 resultShapedType.getElementType().getIntOrFloatBitWidth();
4653 if (desiredBitAlignment % eltBitwidth != 0) {
4654 desiredBitAlignment = eltBitwidth;
4655 }
4656
4657 gpu::CopyMappingInfo mapping(
4658 /*ctx=*/getContext(),
4659 /*totalNumThreads=*/getTotalNumThreads(),
4660 /*alignment=*/desiredBitAlignment,
4661 /*sizes=*/resultShapedType.getShape(),
4662 /*favorPredication=*/false,
4663 /*elementalBitwidth=*/
4664 resultShapedType.getElementType().getIntOrFloatBitWidth());
4665 if (mapping.status == gpu::CopyMappingInfo::Status::Invalid) {
4666 DiagnosedSilenceableFailure diag =
4667 emitSilenceableError()
4668 << "too few threads to map copy op to threads on the most minor "
4669 "dimension, given alignment and vector size constraints, try "
4670 "smaller tile size of mapping to more threads";
4671 diag.attachNote(target->getLoc()) << "target op";
4672 return diag;
4673 }
4674
4675 // OpBuilder only used to compute attributes.
4676 OpBuilder b(getContext());
4677 scf::SCFTilingResult tilingResult;
4678 DiagnosedSilenceableFailure diag = tileToForallOpImpl(
4679 /*rewriter=*/rewriter,
4680 /*state=*/state,
4681 /*transformOp=*/*this,
4682 /*target=*/target,
4683 /*mixedNumThreads=*/getMixedValues(mapping.numThreads, {}, b),
4684 /*mixedTileSizes=*/ArrayRef<OpFoldResult>{},
4685 /*mapping=*/b.getArrayAttr(mapping.threadMapping),
4686 /*tilingResult=*/tilingResult);
4687 if (!diag.succeeded())
4688 return diag;
4689
4690 results.push_back(tilingResult.loops.front());
4691 for (auto *op : tilingResult.tiledOps)
4692 results.push_back(op);
4694}
4695
4696//===----------------------------------------------------------------------===//
4697// WinogradConv2DOp
4698//===----------------------------------------------------------------------===//
4699
4700DiagnosedSilenceableFailure transform::WinogradConv2DOp::applyToOne(
4701 transform::TransformRewriter &rewriter, linalg::LinalgOp target,
4702 transform::ApplyToEachResultList &results,
4703 transform::TransformState &state) {
4704 rewriter.setInsertionPoint(target);
4705 FailureOr<Operation *> maybeTransformed = failure();
4706 bool supported = TypeSwitch<Operation *, bool>(target)
4707 .Case([&](linalg::Conv2DNhwcFhwcOp op) {
4708 maybeTransformed =
4709 winogradConv2D(rewriter, op, getFmr());
4710 return true;
4711 })
4712 .Default([&](Operation *op) { return false; });
4713
4714 if (!supported) {
4715 return emitSilenceableError()
4716 << "this operation is not supported to convert to Winograd Conv2D";
4717 }
4718
4719 if (failed(maybeTransformed)) {
4720 return emitSilenceableError() << "apply Winograd Conv2D failed";
4721 }
4722
4723 results.push_back(*maybeTransformed);
4725}
4726
4727DiagnosedSilenceableFailure transform::DecomposeWinogradOp::applyToOne(
4728 transform::TransformRewriter &rewriter, Operation *target,
4729 transform::ApplyToEachResultList &results,
4730 transform::TransformState &state) {
4731 rewriter.setInsertionPoint(target);
4732 FailureOr<Operation *> maybeTransformed = failure();
4733 bool supported =
4735 .Case([&](linalg::WinogradFilterTransformOp op) {
4736 maybeTransformed = decomposeWinogradFilterTransformOp(rewriter, op);
4737 return true;
4738 })
4739 .Case([&](linalg::WinogradInputTransformOp op) {
4740 maybeTransformed = decomposeWinogradInputTransformOp(rewriter, op);
4741 return true;
4742 })
4743 .Case([&](linalg::WinogradOutputTransformOp op) {
4744 maybeTransformed = decomposeWinogradOutputTransformOp(rewriter, op);
4745 return true;
4746 })
4747 .Default(false);
4748
4749 if (!supported) {
4750 DiagnosedSilenceableFailure diag =
4751 emitSilenceableError()
4752 << "this operation is not supported to decompose into other operations";
4753 diag.attachNote(target->getLoc()) << "target op";
4754 return diag;
4755 }
4756
4757 if (failed(maybeTransformed)) {
4758 DiagnosedSilenceableFailure diag =
4759 emitSilenceableError() << "decompose Winograd operations failed";
4760 diag.attachNote(target->getLoc()) << "target op";
4761 return diag;
4762 }
4763
4764 results.push_back(*maybeTransformed);
4766}
4767
4768#include "mlir/Dialect/Linalg/TransformOps/LinalgTransformOpsEnums.cpp.inc"
4769
4770#define GET_OP_CLASSES
4771#include "mlir/Dialect/Linalg/TransformOps/LinalgTransformOps.cpp.inc"
for(Operation *op :ops)
return success()
static void copy(Location loc, Value dst, Value src, Value size, OpBuilder &builder)
Copies the given number of bytes from src to dst pointers.
static SmallVector< Value > denormalizeIndVar(RewriterBase &rewriter, Location loc, ValueRange ivs, ArrayRef< OpFoldResult > lbs, ArrayRef< OpFoldResult > steps)
When a loop is normalized, the uses of the induction variable within the loop need to replaced with o...
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static LogicalResult applyTilingToAll(RewriterBase &rewriter, Operation *transformOp, Range &&payloadOps, unsigned numLoops, transform::TransformResults &transformResults, bool packedResults, function_ref< FailureOr< scf::SCFTileAndFuseResult >(TilingInterface)> applyFn)
Apply a tiling transformation to all payload ops and store both the tiled operation as well as the cr...
static Operation * cloneAndFuseFirstUse(RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp, Operation *containingOp)
static DiagnosedSilenceableFailure reifyMixedParamAndHandleResults(TransformState &state, TransformOpInterface &transformOp, ArrayRef< OpFoldResult > mixedResults, SmallVectorImpl< int64_t > &reified)
When possible, converts each OpFoldResult in mixedResult to an integer if the value can be statically...
static DiagnosedSilenceableFailure unpackSingleIndexResultPayloadOperations(transform::TransformState &state, TransformOpInterface transformOp, SmallVector< OpFoldResult > &result, ArrayRef< OpFoldResult > ofrs)
Assuming that ofr is an index attr or a param of index type or a transform dialect handle mapped to e...
static SmallVector< OpFoldResult > normalizeUpperBounds(RewriterBase &rewriter, Location loc, ArrayRef< OpFoldResult > lbs, ArrayRef< OpFoldResult > ubs, ArrayRef< OpFoldResult > steps)
Given lbs, ubs and steps of loops, return (for each loop), the normalized upper bound.
static scf::ForallOp normalizeForallLoopOp(RewriterBase &rewriter, scf::ForallOp loop)
Given a scf.forall loop return a loop op with the loop bounds normalized. TODO: Replace this with a g...
ArrayAttr()
static bool mayBeRead(OpOperand &operand)
Return true if the operand may be read from by its owner.
static void printMultitileSizesTypes(OpAsmPrinter &printer, Operation *op, Type targetType, Type lowSizeType, Type, Type)
static bool sameOrEquivalentIterArg(Value src, Value dst)
Given two operands coming from a loop iter arg, 'src' and 'dst', return true if the operand 'src' is ...
static SmallVector< Operation * > tileAndFuseFirstExtractUseThroughContainingOpBlockArgument(RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp, Operation *containingOp, ArrayRef< InnerTileAlignment > innerTileAlignments)
First, find the first "scf::ForallOp" user of producerOp and ensure it is exactly the containingOp,...
static ParseResult parseContinuousTileSizeTypes(OpAsmParser &parser, Type &targetType, Type &tileSizesType, Type &chunkSizesType)
static Operation * replaceForAllWithNewSignature(RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp, Operation *containingOp, TilingResult &tileAndFuseResult, int64_t resultNumber, SmallVector< OpFoldResult > &offsets, SmallVector< OpFoldResult > &sizes)
Add new operands to the forall op for users of the producerOp that are dominated by the containing sc...
static ParseResult parseMultitileSizesTypes(OpAsmParser &parser, Type &targetType, Type &lowSizeType, Type &highSizeType, Type &splitPointType)
static void printContinuousTileSizeTypes(OpAsmPrinter &printer, Operation *op, Type targetType, Type tileSizes, Type)
static DiagnosedSilenceableFailure doit(RewriterBase &rewriter, OpTy target, transform::ApplyToEachResultList &results, transform::TransformState &state)
static std::tuple< SmallVector< Operation * >, Operation * > tileAndFuseFirstExtractUse(RewriterBase &rewriter, Diagnostic &diag, Operation *producerOp, Operation *containingOp, ArrayRef< InnerTileAlignment > innerTileAlignments)
Find the first "extract" user of producerOp and tile it right before its use.
b getContext())
*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 inserted(the insertion happens right before the *insertion point). Since `begin` can itself be invalidated due to the memref *rewriting done from this method
*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`
memberIdxs push_back(ArrayAttr::get(parser.getContext(), values))
static llvm::ManagedStatic< PassManagerOptions > options
static void getDynamicSizes(RankedTensorType tp, ValueRange sizes, SmallVectorImpl< Value > &dynSizes)
Collects the dynamic dimension sizes for tp with the assumption that sizes are the dimension sizes fo...
static SmallVector< Value > getTileSizes(Location loc, x86::amx::TileType tType, RewriterBase &rewriter)
Maps the 2-dim vector shape to the two 16-bit tile sizes.
Base type for affine expression.
Definition AffineExpr.h:68
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
ParseResult parseInteger(IntT &result)
Parse an integer value from the stream.
virtual ParseResult parseColonType(Type &result)=0
Parse a colon followed by a type.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseComma()=0
Parse a , token.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
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:34
Operation * getParentOp()
Returns the closest surrounding operation that contains this block.
Definition Block.cpp:31
This class is a general helper class for creating context-global objects like types,...
Definition Builders.h:51
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
UnitAttr getUnitAttr()
Definition Builders.cpp:106
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:175
AffineExpr getAffineSymbolExpr(unsigned position)
Definition Builders.cpp:377
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
Ty getType(Args &&...args)
Get or construct an instance of the type Ty with provided arguments.
Definition Builders.h:94
MLIRContext * getContext() const
Definition Builders.h:56
ArrayAttr getI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:290
ArrayAttr getStrArrayAttr(ArrayRef< StringRef > values)
Definition Builders.cpp:315
The result of a transform IR operation application.
static DiagnosedSilenceableFailure success()
Constructs a DiagnosedSilenceableFailure in the success state.
bool isDefiniteFailure() const
Returns true if this is a definite failure.
Diagnostic & attachNote(std::optional< Location > loc=std::nullopt)
Attaches a note to the last diagnostic.
static DiagnosedSilenceableFailure silenceableFailure(Diagnostic &&diag)
Constructs a DiagnosedSilenceableFailure in the silenceable failure state, ready to emit the given di...
bool succeeded() const
Returns true if this is a success.
static DiagnosedSilenceableFailure definiteFailure()
Constructs a DiagnosedSilenceableFailure in the failure state.
bool isSilenceableFailure() const
Returns true if this is a silenceable failure.
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
A class for computing basic dominance information.
Definition Dominance.h:143
bool dominates(Operation *a, Operation *b) const
Return true if operation A dominates operation B, i.e.
Definition Dominance.h:161
This is a utility class for mapping one set of IR entities to another.
Definition IRMapping.h:26
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:64
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult resolveOperand(const UnresolvedOperand &operand, Type type, SmallVectorImpl< Value > &result)=0
Resolve an operand to an SSA value, emitting an error on failure.
virtual OptionalParseResult parseOptionalOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single operand if present.
virtual ParseResult parseOperand(UnresolvedOperand &result, bool allowResultNumber=true)=0
Parse a single SSA value operand name along with a result number if allowResultNumber is true.
This is a pure-virtual base class that exposes the asmprinter hooks necessary to implement a custom p...
virtual void printOptionalAttrDict(ArrayRef< NamedAttribute > attrs, ArrayRef< StringRef > elidedAttrs={})=0
If the specified operation has attributes, print out an attribute dictionary with their values.
void printFunctionalType(Operation *op)
Print the complete type of an operation in functional form.
bool isSet() const
Returns true if this insert point is set.
Definition Builders.h:340
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
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 setListener(Listener *newListener)
Sets the listener of this builder to the one provided.
Definition Builders.h:319
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
Listener * getListener() const
Returns the current listener of this builder, or nullptr if this builder doesn't have a listener.
Definition Builders.h:323
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
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Attribute getDiscardableAttr(StringRef name)
Access a discardable attribute by name, returns a null Attribute if the discardable attribute does no...
Definition Operation.h:485
OpResult getOpResult(unsigned idx)
Definition Operation.h:446
void setOperand(unsigned idx, Value value)
Definition Operation.h:376
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
std::optional< Attribute > getInherentAttr(StringRef name)
Access an inherent attribute by name: returns an empty optional if there is no inherent attribute wit...
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:115
operand_type_range getOperandTypes()
Definition Operation.h:422
result_type_range getResultTypes()
Definition Operation.h:453
bool isAncestor(Operation *other)
Return true if this operation is an ancestor of the other operation.
Definition Operation.h:288
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
Definition Operation.h:849
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
result_range getOpResults()
Definition Operation.h:445
bool isProperAncestor(Operation *other)
Return true if this operation is a proper ancestor of the other operation.
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
bool has_value() const
Returns true if we contain a valid ParseResult value.
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 eraseBlock(Block *block)
This method erases all operations in a block.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
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 replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
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,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
virtual void replaceAllUsesWith(Value from, Value to)
Find uses of from and replace them with to.
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
bool isIndex() const
Definition Types.cpp:56
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
Type front()
Return first type in the range.
Definition TypeRange.h:164
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
bool use_empty() const
Returns true if this value has no uses.
Definition Value.h:208
Type getType() const
Return the type of this value.
Definition Value.h:105
use_range getUses() const
Returns a range of all uses, which is useful for iterating over all uses.
Definition Value.h:188
user_range getUsers() const
Definition Value.h:218
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
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
State for analysis-enabled bufferization.
Operation * getOwner() const
Return the owner of this operand.
Definition UseDefLists.h:38
A list of results of applying a transform op with ApplyEachOpTrait to a single payload operation,...
void assign(unsigned size, std::nullptr_t)
Sets the list of results to size null pointers.
void reserve(unsigned size)
Reserves space for size elements in the list.
size_t size() const
Returns the number of elements in the list.
void push_back(Operation *op)
Appends an element to the list.
Local mapping between values defined by a specific op implementing the TransformOpInterface and the p...
void setValues(OpResult handle, Range &&values)
Indicates that the result of the transform IR op at the given position corresponds to the given range...
void setParams(OpResult value, ArrayRef< TransformState::Param > params)
Indicates that the result of the transform IR op at the given position corresponds to the given list ...
void set(OpResult value, Range &&ops)
Indicates that the result of the transform IR op at the given position corresponds to the given list ...
This is a special rewriter to be used in transform op implementations, providing additional helper fu...
LogicalResult notifyPayloadOperationReplaced(Operation *op, Operation *replacement)
Notify the transform dialect interpreter that the given op has been replaced with another op and that...
The state maintained across applications of various ops implementing the TransformOpInterface.
auto getPayloadOps(Value value) const
Returns an iterator that enumerates all ops that the given transform IR value corresponds to.
auto getPayloadValues(Value handleValue) const
Returns an iterator that enumerates all payload IR values that the given transform IR value correspon...
ArrayRef< Attribute > getParams(Value value) const
Returns the list of parameters that the given transform IR value corresponds to.
AffineApplyOp makeComposedAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Returns a composed AffineApplyOp by composing map and operands with other AffineApplyOps supplying th...
SmallVector< OpFoldResult > makeComposedFoldedMultiResultAffineApply(OpBuilder &b, Location loc, AffineMap map, ArrayRef< OpFoldResult > operands, bool composeAffineMin=false)
Variant of makeComposedFoldedAffineApply suitable for multi-result maps.
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...
LogicalResult analyzeOp(Operation *op, OneShotAnalysisState &state, BufferizationStatistics *statistics=nullptr)
Analyze op and its nested ops.
constexpr void enumerate(std::tuple< Tys... > &tuple, CallbackT &&callback)
Definition Matchers.h:344
FailureOr< PackingResult > buildPackingLoopNest(RewriterBase &rewriter, tensor::PadOp opToHoist, scf::ForOp outermostEnclosingForOp, ArrayRef< int64_t > transposeVector)
Build the packing loop nest required to hoist opToHoist above outermostEnclosingForOp.
void populateDataLayoutPropagationPatterns(RewritePatternSet &patterns, const ControlPropagationFn &controlPackUnPackPropagation, bool PoisonPaddingOk=false)
Patterns to bubble up or down data layout ops across other operations.
LogicalResult rewriteAsPaddedOp(RewriterBase &rewriter, LinalgOp opToPad, const LinalgPaddingOptions &options, LinalgOp &paddedOp, SmallVector< Value > &replacements, SmallVector< tensor::PadOp > &padOps)
Pad the iterator dimensions options.paddingDimensions of all opToPad operands to a static bounding bo...
Definition Padding.cpp:244
FailureOr< std::pair< Operation *, Operation * > > rewriteInIm2Col(RewriterBase &rewriter, linalg::Conv2DNhwcHwcfOp convOp)
Convert linalg.conv_2d_nhwc_hwcf into linalg.generic (for img2col packing) and linalg....
bool hasVectorizationImpl(Operation *)
Return true if there's dedicated logic in the Linalg Vectorizer to vectorize this Op,...
void populateExtractSliceSinkingPatterns(RewritePatternSet &patterns, const ControlPropagationFn &controlPackUnPackPropagation)
Patterns to sink extract slice across other operations.
FailureOr< Operation * > decomposeWinogradFilterTransformOp(RewriterBase &rewriter, linalg::WinogradFilterTransformOp op)
Rewrite linalg.winograd_filter_transform.
std::optional< Value > allocateWorkgroupMemory(OpBuilder &builder, memref::SubViewOp subview, ArrayRef< Value > sizeBounds, DataLayout &)
Allocate the subview in the GPU workgroup memory.
FailureOr< PackTransposeResult > packTranspose(RewriterBase &rewriter, linalg::PackOp packOp, linalg::LinalgOp linalgOp, linalg::UnPackOp maybeUnPackOp, ArrayRef< int64_t > outerPerm, ArrayRef< int64_t > innerPerm)
Transpose a single PackOp -> LinalgOp -> UnPackOp chain and return the transposed PackOp -> LinalgOp ...
Value bufferizeToAllocation(RewriterBase &rewriter, const BufferizeToAllocationOptions &options, tensor::PadOp padOp, Attribute memorySpace={}, Operation *insertionPoint=nullptr)
Materialize a buffer allocation for the given tensor.pad op and lower the op to linalg....
FailureOr< VectorizationResult > vectorize(RewriterBase &rewriter, Operation *op, ArrayRef< int64_t > inputVectorSizes={}, ArrayRef< bool > inputScalableVecDims={}, bool vectorizeNDExtract=false, bool flatten1DDepthwiseConv=false, bool assumeDynamicDimsMatchVecSizes=false, bool createNamedContraction=false)
Returns a VectorizationResult containing the results of the vectorized op, or failure if the transfor...
FailureOr< Value > hoistPaddingOnTensors(RewriterBase &rewriter, tensor::PadOp opToHoist, int64_t numLoops, ArrayRef< int64_t > transposeVector, tensor::PadOp &hoistedOp, SmallVectorImpl< TransposeOp > &transposeOps)
Mechanically hoist padding operations on tensors by numLoops into a new, generally larger tensor.
FailureOr< LowerUnPackOpResult > lowerUnPack(RewriterBase &rewriter, linalg::UnPackOp unPackOp, bool lowerUnpadLikeWithExtractSlice=true)
Rewrite pack as empty + transpose + reshape + extract_slice + copy.
void populatePadOpVectorizationPatterns(RewritePatternSet &patterns, PatternBenefit baseBenefit=1)
Populates patterns with patterns that vectorize tensor.pad.
void populateLinalgTilingCanonicalizationPatterns(RewritePatternSet &patterns)
Canonicalization patterns relevant to apply after tiling patterns.
Definition Tiling.cpp:866
LogicalResult deallocateGPUPrivateMemory(OpBuilder &, Value)
In case of GPU private memory there is no need to deallocate since the memory is freed when going out...
FailureOr< Operation * > decomposeWinogradOutputTransformOp(RewriterBase &rewriter, linalg::WinogradOutputTransformOp op)
Rewrite linalg.winograd_output_transform.
std::function< SplitReductionOptions(LinalgOp op)> ControlSplitReductionFn
Function signature to control reduction splitting.
Definition Transforms.h:489
std::optional< Value > allocateGPUPrivateMemory(OpBuilder &builder, memref::SubViewOp subview, ArrayRef< Value > sizeBounds, DataLayout &)
Allocate the subview in the GPU private memory.
FailureOr< Operation * > rewriteInDestinationPassingStyle(RewriterBase &rewriter, tensor::FromElementsOp fromElementsOp)
Rewrite tensor.from_elements to linalg.generic.
FailureOr< Operation * > winogradConv2D(RewriterBase &rewriter, linalg::Conv2DNhwcFhwcOp op, WinogradConv2DFmr fmr)
Convert linalg.conv_2d_nhwc_fhwc to Winograd Conv2D algorithm F(m x m, r x r).
FailureOr< Operation * > transposeConv2D(RewriterBase &rewriter, linalg::Conv2DNhwcFhwcOp op)
Convert linalg.conv_2d_nhwc_fhwc(_q) to linalg.conv_2d_nhwc_hwcf(_q) by materializing transpose.
void populateFoldUnitExtentDimsPatterns(RewritePatternSet &patterns, ControlDropUnitDims &options)
Patterns to fold unit-extent dimensions in operands/results of linalg ops on tensors and memref.
LogicalResult copyToWorkgroupMemory(OpBuilder &b, Value src, Value dst)
Create Memref copy operations and add gpu barrier guards before and after the copy operation to ensur...
FailureOr< LinalgOp > generalizeNamedOp(RewriterBase &rewriter, LinalgOp linalgOp, bool emitCategoryOps=false)
Create a GenericOp or CategoryOp from the given named operation linalgOp and replace the given linalg...
LogicalResult linalgOpAnchoredEmptyTensorEliminationStep(RewriterBase &rewriter, Operation *op, bufferization::OneShotAnalysisState &state)
Try to eliminate tensor::EmptyOps inside op that are anchored on a LinalgOp.
FailureOr< Operation * > transposeBatchMatmul(RewriterBase &rewriter, linalg::BatchMatmulOp op, bool transposeLHS=true)
Pattern to replace.
FailureOr< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, bool emitCategoryOps=false)
Replace the given GenericOp with a namedOp or categoryOp.
LogicalResult promoteSubviewsPrecondition(Operation *op, LinalgPromotionOptions options)
Promote memref.subviews feeding linalg-on-buffers operations.
LogicalResult copyToGPUPrivateMemory(OpBuilder &b, Value src, Value dst)
Normal copy to between src and dst.
bool isElementwise(LinalgOp op)
Check if a LinalgOp is an element-wise operation.
Definition Utils.cpp:217
FailureOr< GenericOp > interchangeGenericOp(RewriterBase &rewriter, GenericOp genericOp, ArrayRef< unsigned > interchangeVector)
Interchange the iterator_types and iterator_maps dimensions and adapts the index accesses of op.
FailureOr< StaticMultiSizeSpecification > computeStaticMultiTileSizes(LinalgOp op, unsigned dimension, int64_t targetSize, int64_t divisor)
Definition Tiling.cpp:236
void populateDecomposePackUnpackPatterns(RewritePatternSet &patterns)
Populates patterns to decompose linalg.pack and linalg.unpack Ops into e.g.
void populateEraseUnusedOperandsAndResultsPatterns(RewritePatternSet &patterns)
Pattern to remove dead operands and results of linalg.generic operations.
FailureOr< ContinuousTileSizeSpecification > computeContinuousTileSizes(OpBuilder &builder, TilingInterface op, unsigned dimension, OpFoldResult targetSize, bool emitAssertions)
Definition Tiling.cpp:156
FailureOr< StaticContinuousTileSizeSpecification > computeStaticContinuousTileSizes(LinalgOp op, unsigned dimension, unsigned targetSize)
Definition Tiling.cpp:106
FailureOr< SplitReductionResult > splitReduction(RewriterBase &b, LinalgOp op, const ControlSplitReductionFn &controlSplitReductionFn, bool useAlloc=false)
void populateFoldPackUnpackIntoTensorEmptyPatterns(RewritePatternSet &patterns)
Populates patterns with patterns that fold operations like linalg.pack and linalg....
void populateFoldIntoPackAndUnpackPatterns(RewritePatternSet &patterns, const ControlFoldIntoPackUnpackFn &controlFn=nullptr)
Populates patterns with patterns that fold operations like tensor.pad and tensor.extract_slice into t...
void hoistRedundantVectorBroadcasts(RewriterBase &rewriter, Operation *root)
Hoist vector.extract/vector.broadcast pairs out of immediately enclosing scf::ForOp iteratively,...
Definition Hoisting.cpp:91
FailureOr< PackResult > packMatmulGreedily(RewriterBase &rewriter, LinalgOp linalgOp, ArrayRef< OpFoldResult > mnkPackedSizes, ArrayRef< int64_t > mnkPaddedSizesNextMultipleOf, ArrayRef< int64_t > mnkOrder)
Pack a LinalgOp by greedily inferring matmul dimensions (m, n, k) where m and n are proper parallel d...
FailureOr< PackResult > pack(RewriterBase &rewriter, linalg::LinalgOp linalgOp, ArrayRef< OpFoldResult > packedSizes)
Implement packing of a single LinalgOp by packedSizes.
void populateEraseUnnecessaryInputsPatterns(RewritePatternSet &patterns)
Patterns to promote inputs to outputs and remove unused inputs of linalg.generic ops.
std::function< bool(OpOperand *opOperand)> ControlPropagationFn
Function type which is used to control propagation of linalg.pack/unpack ops.
void populateSwapExtractSliceWithFillPatterns(RewritePatternSet &patterns)
Adds patterns that waps tensor.extract_slice(linalg.fill(cst, init)) into linalg.fill(cst,...
FailureOr< LinalgOp > promoteSubViews(OpBuilder &b, LinalgOp op, const LinalgPromotionOptions &options)
Promote the subViews into a new buffer allocated at the insertion point b.
LogicalResult deallocateWorkgroupMemory(OpBuilder &, Value)
In case of GPU group memory there is no need to deallocate.
FailureOr< Operation * > transposeMatmul(RewriterBase &rewriter, linalg::MatmulOp op, bool transposeLHS=true)
Convert Linalg matmul ops to transposed variants.
FailureOr< CollapseResult > collapseOpIterationDims(LinalgOp op, ArrayRef< ReassociationIndices > foldedIterationDims, RewriterBase &rewriter)
Collapses dimensions of linalg.generic/linalg.copy operation.
void hoistRedundantVectorTransfers(Operation *root, bool verifyNonZeroTrip=false)
Hoist vector.transfer_read/vector.transfer_write on buffers pairs out of immediately enclosing scf::F...
Definition Hoisting.cpp:166
FailureOr< Operation * > decomposeWinogradInputTransformOp(RewriterBase &rewriter, linalg::WinogradInputTransformOp op)
Rewrite linalg.winograd_input_transform.
void populateDecomposePadPatterns(RewritePatternSet &patterns)
Populates patterns to decompose tensor.pad into e.g.
void populateFoldAddIntoDestPatterns(RewritePatternSet &patterns)
Pattern to replace linalg.add when destination passing on a contraction op suffices for achieving the...
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
FailureOr< SplitReductionResult > splitReductionByScaling(RewriterBase &b, LinalgOp op, const ControlSplitReductionFn &controlSplitReductionFn, bool useAlloc=false)
Scaling-based implementation of the split reduction transformation.
FailureOr< MultiSizeSpecification > computeMultiTileSizes(OpBuilder &builder, LinalgOp op, unsigned dimension, OpFoldResult targetSize, OpFoldResult divisor, bool emitAssertions=true)
Emits the IR computing the multi-sized tiling specification with two tile sizes not exceeding targetS...
Definition Tiling.cpp:262
FailureOr< LinalgOp > downscaleSizeOneWindowedConvolution(RewriterBase &rewriter, LinalgOp op)
Rewrite convolution/pooling/depthwise ops with size-1 window dimensions into lower-dimensional ops.
FailureOr< LowerPackResult > lowerPack(RewriterBase &rewriter, linalg::PackOp packOp, bool lowerPadLikeWithInsertSlice=true)
Rewrite pack as pad + reshape + transpose.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
ForOp getForInductionVarOwner(Value val)
Returns the loop parent of an induction variable.
Definition SCF.cpp:680
void populateMergeConsecutiveInsertExtractSlicePatterns(RewritePatternSet &patterns)
Collects patterns to merge consecutive tensor.insert_slice/extract_slice into one.
void populateBubbleUpExtractSliceOpPatterns(RewritePatternSet &patterns)
Appends patterns that are used to bubble up tensor.extract slice op above its producer.
OpFoldResult getMixedSize(OpBuilder &builder, Location loc, Value value, int64_t dim)
Return the dimension of the given tensor value.
Definition TensorOps.cpp:82
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:91
LogicalResult getOrCreateDestinations(OpBuilder &b, Location loc, Operation *op, SmallVector< Value > &result)
This is a helper function for DestinationStyleOpInterface.
void populateFoldTensorSubsetIntoVectorTransferPatterns(RewritePatternSet &patterns)
Appends patterns for folding tensor subset ops into vector transfer ops.
void onlyReadsPayload(SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
DiagnosedSilenceableFailure tileToForallOpImpl(RewriterBase &rewriter, transform::TransformState &state, TransformOpInterface transformOp, Operation *target, ArrayRef< OpFoldResult > mixedNumThreads, ArrayRef< OpFoldResult > mixedTileSizes, std::optional< ArrayAttr > mapping, scf::SCFTilingResult &tilingResult)
Implementation of tiling operations using scf.forall.
void producesHandle(ResultRange handles, SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
void consumesHandle(MutableArrayRef< OpOperand > handles, SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
Populates effects with the memory effects indicating the operation on the given handle value:
void onlyReadsHandle(MutableArrayRef< OpOperand > handles, SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
void modifiesPayload(SmallVectorImpl< MemoryEffects::EffectInstance > &effects)
Populates effects with the memory effects indicating the access to payload IR resource.
detail::poison_attr_matcher m_Poison()
Matches a poison constant (any attribute implementing PoisonAttrInterface).
Definition UBMatchers.h:46
void populateVectorTransferPermutationMapLoweringPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Collect a set of transfer read/write lowering patterns that simplify the permutation map (e....
void populateFoldArithExtensionPatterns(RewritePatternSet &patterns)
Collect a set of patterns that fold arithmetic extension on floating point into vector contract for t...
void populateSinkVectorOpsPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Patterns that remove redundant Vector Ops by re-ordering them with e.g.
void populateVectorReductionToContractPatterns(RewritePatternSet &patterns, PatternBenefit benefit=1)
Collect patterns to convert reduction op to vector.contract and fold transpose/broadcast ops into the...
void populateVectorStepLoweringPatterns(RewritePatternSet &patterns, unsigned indexBitwidth=64, PatternBenefit benefit=1)
Populate the pattern set with the following patterns:
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
SmallVector< OpFoldResult > getMixedValues(ArrayRef< int64_t > staticValues, ValueRange dynamicValues, MLIRContext *context)
Return a vector of OpFoldResults with the same size a staticValues, but all elements for which Shaped...
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
SmallVector< InnerTileAlignment > convertInnerTileAlignments(ArrayRef< int64_t > alignments)
Maps a validated inner_tile_alignments integer array onto the per-dimension InnerTileAlignment hints ...
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:311
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...
DiagnosedSilenceableFailure emitSilenceableFailure(Location loc, const Twine &message={})
Emits a silenceable failure with the given message.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:125
Attribute parseAttribute(llvm::StringRef attrStr, MLIRContext *context, Type type={}, size_t *numRead=nullptr, bool isKnownNullTerminated=false)
This parses a single MLIR attribute to an MLIR context if it was valid.
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:128
DiagnosedDefiniteFailure emitDefiniteFailure(Location loc, const Twine &message={})
Emits a definite failure with the given message.
LogicalResult verifyInnerTileAlignments(Operation *op, ArrayRef< int64_t > alignments)
Verifies that every entry of a raw inner_tile_alignments integer array is a valid InnerTileAlignment,...
FailureOr< SCFTileAndFuseResult > tileConsumerAndFuseProducersUsingSCF(RewriterBase &rewriter, TilingInterface consumer, const SCFTileAndFuseOptions &options)
Method to tile and fuse a sequence of operations, by tiling the consumer and fusing its producers.
Type getElementTypeOrSelf(Type type)
Return the element type or return the type itself.
bool isZeroInteger(OpFoldResult v)
Return "true" if v is an integer value/attribute with constant value 0.
void bindSymbols(MLIRContext *ctx, AffineExprTy &...exprs)
Bind a list of AffineExpr references to SymbolExpr at positions: [0 .
Definition AffineExpr.h:325
void dispatchIndexOpFoldResults(ArrayRef< OpFoldResult > ofrs, SmallVectorImpl< Value > &dynamicVec, SmallVectorImpl< int64_t > &staticVec)
Helper function to dispatch multiple OpFoldResults according to the behavior of dispatchIndexOpFoldRe...
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:147
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)
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
SmallVector< int64_t, 2 > ReassociationIndices
Definition Utils.h:27
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
SmallVector< IntTy > extractFromIntegerArrayAttr(Attribute attr)
Extract integer values from the assumed ArrayAttr of IntegerAttr.
llvm::function_ref< Fn > function_ref
Definition LLVM.h:155
bool isPermutationVector(ArrayRef< int64_t > interchange)
Method to check if an interchange vector is a permutation.
bool isOneInteger(OpFoldResult v)
Return true if v is an IntegerAttr with value 1.
FailureOr< SCFTilingResult > tileUsingSCF(RewriterBase &rewriter, TilingInterface op, const SCFTilingOptions &options)
Method to tile an op that implements the TilingInterface using scf.for for iterating over the tiles.
This class represents a listener that may be used to hook into various actions within an OpBuilder.
Definition Builders.h:288
This represents an operation in an abstracted form, suitable for use with the builder APIs.
Represents a range (offset, size, and stride) where each element of the triple may be dynamic or stat...
A listener that forwards all notifications to another listener.
ForwardingListener(OpBuilder::Listener *listener)
Container for result values of tiling.
SmallVector< Value > tiledValues
Options for analysis-enabled bufferization.
Transformation to drop unit-extent dimensions from linalg.generic operations.
Definition Transforms.h:521
LinalgPromotionOptions & setUseFullTileBuffers(ArrayRef< bool > useFullTiles)
Definition Transforms.h:409
LinalgPromotionOptions & setAllocationDeallocationFns(AllocBufferCallbackFn const &allocFn, DeallocBufferCallbackFn const &deallocFn)
Definition Transforms.h:456
LinalgPromotionOptions & setAlignment(unsigned align)
Definition Transforms.h:433
LinalgPromotionOptions & setMemorySpace(Attribute memorySpc)
Definition Transforms.h:440
LinalgPromotionOptions & setOperandsToPromote(ArrayRef< int64_t > operands)
Definition Transforms.h:398
LinalgPromotionOptions & setUseFullTileBuffersByDefault(bool use)
Definition Transforms.h:420
LinalgPromotionOptions & setUseOriginalSubviewSize(bool originalSize)
Definition Transforms.h:427
LinalgPromotionOptions & setUseAlloca(bool use)
Definition Transforms.h:446
LinalgPromotionOptions & setCopyInOutFns(CopyCallbackFn const &copyIn, CopyCallbackFn const &copyOut)
Definition Transforms.h:466