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