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