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