MLIR 23.0.0git
LinalgOps.cpp
Go to the documentation of this file.
1//===- LinalgOps.cpp - Implementation of the linalg operations ------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements the Linalg operations.
10//
11//===----------------------------------------------------------------------===//
12
14
28#include "mlir/IR/AffineMap.h"
29#include "mlir/IR/Attributes.h"
30#include "mlir/IR/Builders.h"
33#include "mlir/IR/Matchers.h"
40
41#include "llvm/ADT/DenseMap.h"
42#include "llvm/ADT/STLExtras.h"
43#include "llvm/ADT/SetOperations.h"
44#include "llvm/ADT/SmallVector.h"
45#include "llvm/ADT/SmallVectorExtras.h"
46#include "llvm/ADT/StringSet.h"
47#include "llvm/ADT/TypeSwitch.h"
48#include "llvm/Support/FormatVariadic.h"
49#include "llvm/Support/InterleavedRange.h"
50#include "llvm/Support/LogicalResult.h"
51#include "llvm/Support/MathExtras.h"
52#include "llvm/Support/raw_ostream.h"
53#include <cassert>
54#include <optional>
55
56using namespace mlir;
57using namespace mlir::linalg;
58
59/// Return a `memref.dim` or `tensor.dim` for the shape of `v` at `dim`.
61 int64_t dim) {
62 auto type = cast<ShapedType>(v.getType());
63 if (!type.isDynamicDim(dim))
64 return builder.getIndexAttr(type.getDimSize(dim));
65
66 return getAsOpFoldResult(
68 .Case([&](RankedTensorType t) -> Value {
69 return tensor::DimOp::create(builder, loc, v, dim);
70 })
71 .Case([&](MemRefType t) -> Value {
72 return memref::DimOp::create(builder, loc, v, dim);
73 }));
74}
75
76/// Returns a memref.subview or a tensor.extract_slice based on the type of the
77/// `source`.
81 ArrayRef<OpFoldResult> strides) {
83 .Case([&](RankedTensorType t) -> Operation * {
84 return tensor::ExtractSliceOp::create(b, loc, source, offsets, sizes,
85 strides);
86 })
87 .Case([&](MemRefType type) -> Operation * {
88 return memref::SubViewOp::create(b, loc, source, offsets, sizes,
89 strides);
90 })
91 .Default([&](Type t) -> Operation * { return nullptr; });
92}
93
94static std::optional<TypedAttr>
96 DenseElementsAttr splatAttr;
98 if (!splatAttr || !splatAttr.isSplat())
99 return std::nullopt;
100
101 return splatAttr.getSplatValue<TypedAttr>();
102}
103
104//===----------------------------------------------------------------------===//
105// Helper functions
106//===----------------------------------------------------------------------===//
107
109 int64_t dim) {
110 if (llvm::isa<UnrankedMemRefType, MemRefType>(source.getType()))
111 return b.createOrFold<memref::DimOp>(loc, source, dim);
112 if (llvm::isa<UnrankedTensorType, RankedTensorType>(source.getType()))
113 return b.createOrFold<tensor::DimOp>(loc, source, dim);
114 llvm_unreachable("Expected MemRefType or TensorType");
115}
116
118 int64_t dim) {
119 auto shapedType = llvm::cast<ShapedType>(source.getType());
120 if (!shapedType.hasRank() || shapedType.isDynamicDim(dim))
121 return createOrFoldDimOp(b, loc, source, dim);
122 return b.getIndexAttr(shapedType.getDimSize(dim));
123}
124
125//===----------------------------------------------------------------------===//
126// Support for named Linalg ops defined in ods-gen.
127//===----------------------------------------------------------------------===//
128
132
133/// Fills the region of a structured operation using the provided
134/// `regionBuilder`. The method is used by both named structured ops created by
135/// ods-gen and by manually defined C++ ops. It is called by both builders and
136/// parsers and creates a block with arguments corresponding to the elemental
137/// types of `inputTypes` and `outputTypes`.
138static void fillStructuredOpRegion(OpBuilder &opBuilder, Region &region,
139 TypeRange inputTypes, TypeRange outputTypes,
142 RegionBuilderFn regionBuilder) {
143 SmallVector<Type, 8> argTypes;
145 for (auto containers : {inputTypes, outputTypes}) {
146 for (auto t : containers) {
147 argTypes.push_back(
148 isa<MemRefType, RankedTensorType>(t) ? getElementTypeOrSelf(t) : t);
149
150 // TODO: Pass in a proper location here.
151 argLocs.push_back(opBuilder.getUnknownLoc());
152 }
153 }
154
155 // RAII.
156 OpBuilder::InsertionGuard guard(opBuilder);
157 Block *body =
158 opBuilder.createBlock(&region, /*insertPt=*/{}, argTypes, argLocs);
159
160 opBuilder.setInsertionPointToStart(body);
161 ImplicitLocOpBuilder b(opBuilder.getUnknownLoc(), opBuilder);
162 regionBuilder(b, *body, attrs, emitError);
163
164 // indexing_maps is an auto-generated method.
165
166 // iterator_types is an auto-generated method.
167}
168
169/// Creates a structured operation given `inputs`, `outputs`, and `attributes`.
170/// The result types are derived automatically if `resultTensorTypes` is none.
171/// The body of the operation is filled using `regionBuilder`. All ods-gen
172/// created structured operations use the method to implement their builders.
174 std::optional<TypeRange> resultTensorTypes,
175 ValueRange inputs, ValueRange outputs,
176 ArrayRef<NamedAttribute> attributes,
177 RegionBuilderFn regionBuilder) {
178 // Derive the result types if needed.
179 SmallVector<Type> derivedResultTypes =
180 resultTensorTypes.value_or(TypeRange());
181 if (!resultTensorTypes)
182 copy_if(outputs.getTypes(), std::back_inserter(derivedResultTypes),
183 llvm::IsaPred<RankedTensorType>);
184
185 state.addOperands(inputs);
186 state.addOperands(outputs);
187 state.addTypes(derivedResultTypes);
188
189 state.addAttributes(attributes);
190 state.addAttribute(
191 "operandSegmentSizes",
192 b.getDenseI32ArrayAttr({static_cast<int32_t>(inputs.size()),
193 static_cast<int32_t>(outputs.size())}));
194
195 // Create and fill the region of the structured operation.
196 Region &region = *state.addRegion();
197 fillStructuredOpRegion(b, region, TypeRange(inputs), TypeRange(outputs),
198 state.attributes.getAttrs(), /*emitError=*/{},
199 regionBuilder);
200}
201
203 std::optional<TypeRange> resultTensorTypes,
204 ValueRange inputs, ValueRange outputs,
205 ArrayRef<NamedAttribute> attributes,
206 RegionBuilderFn regionBuilder,
207 ArrayRef<AffineMap> defaultIndexingMaps) {
208 // If indexing maps are not provided, apply the default ones.
209 if (none_of(attributes, [](NamedAttribute attr) {
210 return attr.getName() == "indexing_maps";
211 })) {
212 SmallVector<Attribute, 3> indexingMapsAttrVal;
213 indexingMapsAttrVal = llvm::map_to_vector(
214 defaultIndexingMaps,
215 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
216 state.addAttribute("indexing_maps", b.getArrayAttr(indexingMapsAttrVal));
217 }
218 return buildStructuredOp(b, state, resultTensorTypes, inputs, outputs,
219 attributes, regionBuilder);
220}
221
223 std::optional<TypeRange> resultTensorTypes,
224 ValueRange inputs, ValueRange outputs,
225 ArrayRef<NamedAttribute> attributes,
226 RegionBuilderFn regionBuilder,
227 ArrayRef<AffineMap> defaultIndexingMaps) {
228 // If indexing maps are not provided, apply the default ones.
229 if (none_of(attributes, [](NamedAttribute attr) {
230 return attr.getName() == "indexing_maps";
231 })) {
232 SmallVector<Attribute, 4> indexingMapsAttrVal;
233 indexingMapsAttrVal = llvm::map_to_vector(
234 defaultIndexingMaps,
235 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
236 state.addAttribute("indexing_maps", b.getArrayAttr(indexingMapsAttrVal));
237 }
238 return buildStructuredOp(b, state, resultTensorTypes, inputs, outputs,
239 attributes, regionBuilder);
240}
241
243 std::optional<TypeRange> resultTensorTypes,
244 ValueRange inputs, ValueRange outputs,
245 ArrayRef<NamedAttribute> attributes,
246 RegionBuilderFn regionBuilder,
247 ArrayRef<AffineMap> indexingMaps) {
248 // Initialize indexingMaps attribute, for BatchReduceMatmulOp.
249 SmallVector<Attribute, 4> indexingMapsAttrVal;
250 indexingMapsAttrVal =
251 llvm::map_to_vector(indexingMaps, [](AffineMap map) -> Attribute {
252 return AffineMapAttr::get(map);
253 });
254 state.addAttribute("indexing_maps", b.getArrayAttr(indexingMapsAttrVal));
255 return buildStructuredOp(b, state, resultTensorTypes, inputs, outputs,
256 attributes, regionBuilder);
257}
258
259/// Common parsing used for both named structured ops created by ods-gen and by
260/// manually defined C++ ops. Does not handle regions.
261static ParseResult
263 SmallVectorImpl<Type> &inputTypes,
264 SmallVectorImpl<Type> &outputTypes,
265 bool addOperandSegmentSizes = true) {
266 SMLoc attrsLoc, inputsOperandsLoc, outputsOperandsLoc;
268 outputsOperands;
269
270 if (succeeded(parser.parseOptionalLess())) {
271 if (parser.parseAttribute(result.propertiesAttr) || parser.parseGreater())
272 return failure();
273 }
274 attrsLoc = parser.getCurrentLocation();
275 if (parser.parseOptionalAttrDict(result.attributes))
276 return failure();
277
278 if (succeeded(parser.parseOptionalKeyword("ins"))) {
279 if (parser.parseLParen())
280 return failure();
281
282 inputsOperandsLoc = parser.getCurrentLocation();
283 if (parser.parseOperandList(inputsOperands) ||
284 parser.parseColonTypeList(inputTypes) || parser.parseRParen())
285 return failure();
286 }
287
288 if (succeeded(parser.parseOptionalKeyword("outs"))) {
289 outputsOperandsLoc = parser.getCurrentLocation();
290 if (parser.parseLParen() || parser.parseOperandList(outputsOperands) ||
291 parser.parseColonTypeList(outputTypes) || parser.parseRParen())
292 return failure();
293 }
294
295 if (parser.resolveOperands(inputsOperands, inputTypes, inputsOperandsLoc,
296 result.operands) ||
297 parser.resolveOperands(outputsOperands, outputTypes, outputsOperandsLoc,
298 result.operands))
299 return failure();
300
301 if (addOperandSegmentSizes) {
302 // This is a bit complex because we're trying to be backward compatible with
303 // operation syntax that mix the inherent attributes and the discardable
304 // ones in the same dictionary. If the properties are used, we append the
305 // operandSegmentSizes there directly. Otherwise we append it to the
306 // discardable attributes dictionary where it is handled by the generic
307 // Operation::create(...) method.
308 if (result.propertiesAttr) {
309 NamedAttrList attrs = llvm::cast<DictionaryAttr>(result.propertiesAttr);
310 attrs.append("operandSegmentSizes",
312 {static_cast<int32_t>(inputsOperands.size()),
313 static_cast<int32_t>(outputsOperands.size())}));
314 result.propertiesAttr = attrs.getDictionary(parser.getContext());
315 } else {
316 result.addAttribute("operandSegmentSizes",
318 {static_cast<int32_t>(inputsOperands.size()),
319 static_cast<int32_t>(outputsOperands.size())}));
320 }
321 }
322 if (!result.propertiesAttr) {
323 std::optional<RegisteredOperationName> info =
324 result.name.getRegisteredInfo();
325 if (info) {
326 if (failed(info->verifyInherentAttrs(result.attributes, [&]() {
327 return parser.emitError(attrsLoc)
328 << "'" << result.name.getStringRef() << "' op ";
329 })))
330 return failure();
331 }
332 }
333 return success();
334}
335
337 ValueRange outputs) {
338 if (!inputs.empty())
339 p << " ins(" << inputs << " : " << inputs.getTypes() << ")";
340 if (!outputs.empty())
341 p << " outs(" << outputs << " : " << outputs.getTypes() << ")";
342}
343
344//===----------------------------------------------------------------------===//
345// Specific parsing and printing for named structured ops created by ods-gen.
346//===----------------------------------------------------------------------===//
347
349 OpAsmParser &parser, Region &region, unsigned numRegionArgs,
350 TypeRange inputTypes, TypeRange outputTypes, ArrayRef<NamedAttribute> attrs,
351 RegionBuilderFn regionBuilder, SMLoc loc) {
352 if (numRegionArgs != inputTypes.size() + outputTypes.size()) {
353 return parser.emitError(
354 parser.getCurrentLocation(),
355 llvm::formatv("[parseNamedStructuredOpRegion] ods-gen generated "
356 "region expects {0} args, got {1}",
357 numRegionArgs, inputTypes.size() + outputTypes.size()));
358 }
359
360 OpBuilder opBuilder(parser.getContext());
361 ParseResult result = success();
363 opBuilder, region, inputTypes, outputTypes, attrs,
364 [&]() {
365 result = failure();
366 return parser.emitError(loc);
367 },
368 regionBuilder);
369 return result;
370}
371
372static ParseResult
374 SmallVectorImpl<Type> &resultTypes) {
375 if (parser.parseOptionalArrowTypeList(resultTypes))
376 return failure();
377 return success();
378}
379
380static ParseResult parseNamedStructuredOp(OpAsmParser &parser,
382 unsigned numRegionArgs,
383 RegionBuilderFn regionBuilder) {
384 // TODO: Enable when ods-gen supports captures.
385 SmallVector<Type, 1> inputTypes, outputTypes;
386 SMLoc loc = parser.getCurrentLocation();
387 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
388 return failure();
389
390 // Parse optional attributes.
391 if (parser.parseOptionalAttrDict(result.attributes))
392 return failure();
393
394 // TODO: consider merging results parsing into region parsing.
395 // Need to wait for declarative assembly resolution to decide.
396 SmallVector<Type, 1> outputTensorsTypes;
397 if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
398 return failure();
399 result.addTypes(outputTensorsTypes);
400
401 std::unique_ptr<Region> region = std::make_unique<Region>();
402 if (parseNamedStructuredOpRegion(parser, *region, numRegionArgs, inputTypes,
403 outputTypes, result.attributes.getAttrs(),
404 regionBuilder, loc))
405 return failure();
406 result.addRegion(std::move(region));
407
408 return success();
409}
410
412 TypeRange resultTypes) {
413 if (resultTypes.empty())
414 return;
415 p.printOptionalArrowTypeList(resultTypes);
416}
417
419 ValueRange inputs, ValueRange outputs,
420 ArrayRef<StringRef> elidedAttrs = {}) {
421 p.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
422
423 // Printing is shared with generic ops, except for the region and
424 // attributes.
425 printCommonStructuredOpParts(p, inputs, outputs);
426
427 // Results printing.
429
430 // Region is elided.
431}
432
433//===----------------------------------------------------------------------===//
434// Region builder helper.
435// TODO: Move this to a utility library.
436// The public methods on this class are referenced directly from generated code.
437// Helper build the unary, binary, and type conversion functions defined by the
438// DSL. See LinalgNamedStructuredOps.yamlgen.cpp.inc for the code that uses this
439// class.
440//
441// Implementations of the math functions must be polymorphic over numeric types,
442// internally performing necessary casts. If the function application makes no
443// sense, then the only recourse is to assert and return nullptr. This can be
444// extended later if it becomes possible to fail construction of the region. The
445// invariant should be enforced at a higher level.
446//
447// TODO: These helpers are currently type polymorphic over the class of integer
448// and floating point types, but they will not internally cast within bit
449// widths of a class (mixed precision such as i8->i32) or across classes
450// (i.e. mixed float and integer). Many such combinations are ambiguous or need
451// to be handled with care and work is being considered to extend the op
452// language to make such cases explicit. In the mean-time, violating this will
453// fail verification, which is deemed acceptable.
454//===----------------------------------------------------------------------===//
455
456namespace {
457
458class RegionBuilderHelper {
459public:
460 RegionBuilderHelper(OpBuilder &builder, Block &block)
461 : builder(builder), block(block) {}
462
463 // Build the unary functions defined by OpDSL.
464 Value buildUnaryFn(UnaryFn unaryFn, Value arg,
465 function_ref<InFlightDiagnostic()> emitError = {}) {
466 if (!isFloatingPoint(arg)) {
467 if (emitError) {
468 emitError() << "unsupported non numeric type";
469 return nullptr;
470 }
471 llvm_unreachable("unsupported non numeric type");
472 }
473 OpBuilder::InsertionGuard g(builder);
474 builder.setInsertionPointToEnd(&block);
475 switch (unaryFn) {
476 case UnaryFn::exp:
477 return math::ExpOp::create(builder, arg.getLoc(), arg);
478 case UnaryFn::log:
479 return math::LogOp::create(builder, arg.getLoc(), arg);
480 case UnaryFn::abs:
481 return math::AbsFOp::create(builder, arg.getLoc(), arg);
482 case UnaryFn::ceil:
483 return math::CeilOp::create(builder, arg.getLoc(), arg);
484 case UnaryFn::floor:
485 return math::FloorOp::create(builder, arg.getLoc(), arg);
486 case UnaryFn::negf:
487 return arith::NegFOp::create(builder, arg.getLoc(), arg);
488 case UnaryFn::reciprocal: {
489 Attribute oneAttr = builder.getOneAttr(arg.getType());
490 auto one = arith::ConstantOp::create(builder, arg.getLoc(),
491 ::cast<TypedAttr>(oneAttr));
492 return arith::DivFOp::create(builder, arg.getLoc(), one, arg);
493 }
494 case UnaryFn::round:
495 return math::RoundOp::create(builder, arg.getLoc(), arg);
496 case UnaryFn::sqrt:
497 return math::SqrtOp::create(builder, arg.getLoc(), arg);
498 case UnaryFn::rsqrt:
499 return math::RsqrtOp::create(builder, arg.getLoc(), arg);
500 case UnaryFn::square:
501 return arith::MulFOp::create(builder, arg.getLoc(), arg, arg);
502 case UnaryFn::tanh:
503 return math::TanhOp::create(builder, arg.getLoc(), arg);
504 case UnaryFn::erf:
505 return math::ErfOp::create(builder, arg.getLoc(), arg);
506 case UnaryFn::sin:
507 return math::SinOp::create(builder, arg.getLoc(), arg);
508 case UnaryFn::cos:
509 return math::CosOp::create(builder, arg.getLoc(), arg);
510 case UnaryFn::tan:
511 return math::TanOp::create(builder, arg.getLoc(), arg);
512 case UnaryFn::acos:
513 return math::AcosOp::create(builder, arg.getLoc(), arg);
514 case UnaryFn::acosh:
515 return math::AcoshOp::create(builder, arg.getLoc(), arg);
516 case UnaryFn::asin:
517 return math::AsinOp::create(builder, arg.getLoc(), arg);
518 case UnaryFn::asinh:
519 return math::AsinhOp::create(builder, arg.getLoc(), arg);
520 case UnaryFn::atan:
521 return math::AtanOp::create(builder, arg.getLoc(), arg);
522 case UnaryFn::atanh:
523 return math::AtanhOp::create(builder, arg.getLoc(), arg);
524 case UnaryFn::log10:
525 return math::Log10Op::create(builder, arg.getLoc(), arg);
526 case UnaryFn::log1p:
527 return math::Log1pOp::create(builder, arg.getLoc(), arg);
528 case UnaryFn::log2:
529 return math::Log2Op::create(builder, arg.getLoc(), arg);
530 }
531 if (emitError) {
532 emitError() << "unsupported unary function";
533 return nullptr;
534 }
535 llvm_unreachable("unsupported unary function");
536 }
537
538 // Build the binary functions defined by OpDSL.
539 // If emitError is provided, an error will be emitted if the operation is not
540 // supported and a nullptr will be returned, otherwise an assertion will be
541 // raised.
542 Value buildBinaryFn(BinaryFn binaryFn, Value arg0, Value arg1,
543 function_ref<InFlightDiagnostic()> emitError = {}) {
544 bool allComplex = isComplex(arg0) && isComplex(arg1);
545 bool allFloatingPoint = isFloatingPoint(arg0) && isFloatingPoint(arg1);
546 bool allInteger = isInteger(arg0) && isInteger(arg1);
547 bool allBool = allInteger && arg0.getType().getIntOrFloatBitWidth() == 1 &&
548 arg1.getType().getIntOrFloatBitWidth() == 1;
549 if (!allComplex && !allFloatingPoint && !allInteger) {
550 if (emitError) {
551 emitError()
552 << "Cannot build binary Linalg operation: expects allComplex, "
553 "allFloatingPoint, or allInteger, got "
554 << arg0.getType() << " and " << arg1.getType();
555 return nullptr;
556 }
557 llvm_unreachable("unsupported non numeric type");
558 }
559 OpBuilder::InsertionGuard g(builder);
560 builder.setInsertionPointToEnd(&block);
561 switch (binaryFn) {
562 case BinaryFn::add:
563 if (allComplex)
564 return complex::AddOp::create(builder, arg0.getLoc(), arg0, arg1);
565 if (allFloatingPoint)
566 return arith::AddFOp::create(builder, arg0.getLoc(), arg0, arg1);
567 if (allBool)
568 return arith::OrIOp::create(builder, arg0.getLoc(), arg0, arg1);
569 return arith::AddIOp::create(builder, arg0.getLoc(), arg0, arg1);
570 case BinaryFn::sub:
571 if (allComplex)
572 return complex::SubOp::create(builder, arg0.getLoc(), arg0, arg1);
573 if (allFloatingPoint)
574 return arith::SubFOp::create(builder, arg0.getLoc(), arg0, arg1);
575 if (allBool) {
576 if (emitError) {
577 emitError() << "unsupported operation: sub with bools";
578 return nullptr;
579 }
580 llvm_unreachable("unsupported operation: sub with bools");
581 }
582 return arith::SubIOp::create(builder, arg0.getLoc(), arg0, arg1);
583 case BinaryFn::mul:
584 if (allComplex)
585 return complex::MulOp::create(builder, arg0.getLoc(), arg0, arg1);
586 if (allFloatingPoint)
587 return arith::MulFOp::create(builder, arg0.getLoc(), arg0, arg1);
588 if (allBool)
589 return arith::AndIOp::create(builder, arg0.getLoc(), arg0, arg1);
590 return arith::MulIOp::create(builder, arg0.getLoc(), arg0, arg1);
591 case BinaryFn::div:
592 if (allComplex)
593 return complex::DivOp::create(builder, arg0.getLoc(), arg0, arg1);
594 if (allFloatingPoint)
595 return arith::DivFOp::create(builder, arg0.getLoc(), arg0, arg1);
596 if (allBool) {
597 if (emitError) {
598 emitError() << "unsupported operation: div with bools";
599 return nullptr;
600 }
601 llvm_unreachable("unsupported operation: div with bools");
602 }
603 return arith::DivSIOp::create(builder, arg0.getLoc(), arg0, arg1);
604 case BinaryFn::div_unsigned:
605 if (!allInteger || allBool) {
606 if (emitError) {
607 emitError() << "unsupported operation: unsigned div not on uint";
608 return nullptr;
609 }
610 llvm_unreachable("unsupported operation: unsigned div not on uint");
611 }
612 return arith::DivUIOp::create(builder, arg0.getLoc(), arg0, arg1);
613 case BinaryFn::max_signed:
614 assert(!allComplex);
615 if (allFloatingPoint)
616 return arith::MaximumFOp::create(builder, arg0.getLoc(), arg0, arg1);
617 return arith::MaxSIOp::create(builder, arg0.getLoc(), arg0, arg1);
618 case BinaryFn::min_signed:
619 assert(!allComplex);
620 if (allFloatingPoint)
621 return arith::MinimumFOp::create(builder, arg0.getLoc(), arg0, arg1);
622 return arith::MinSIOp::create(builder, arg0.getLoc(), arg0, arg1);
623 case BinaryFn::max_unsigned:
624 assert(!allComplex);
625 if (!allInteger || allBool) {
626 if (emitError) {
627 emitError() << "unsupported operation: unsigned max not on uint";
628 return nullptr;
629 }
630 llvm_unreachable("unsupported operation: unsigned max not on uint");
631 }
632 return arith::MaxUIOp::create(builder, arg0.getLoc(), arg0, arg1);
633 case BinaryFn::min_unsigned:
634 assert(!allComplex);
635 if (!allInteger || allBool) {
636 if (emitError) {
637 emitError() << "unsupported operation: unsigned min not on uint";
638 return nullptr;
639 }
640 llvm_unreachable("unsupported operation: unsigned min not on uint");
641 }
642 return arith::MinUIOp::create(builder, arg0.getLoc(), arg0, arg1);
643 case BinaryFn::powf:
644 assert(allFloatingPoint);
645 return math::PowFOp::create(builder, arg0.getLoc(), arg0, arg1);
646 }
647 if (emitError) {
648 emitError() << "unsupported binary function";
649 return nullptr;
650 }
651 llvm_unreachable("unsupported binary function");
652 }
653
654 // Build the ternary functions defined by OpDSL.
655 Value buildTernaryFn(TernaryFn ternaryFn, Value arg0, Value arg1, Value arg2,
656 function_ref<InFlightDiagnostic()> emitError = {}) {
657 OpBuilder::InsertionGuard g(builder);
658 builder.setInsertionPointToEnd(&block);
659 switch (ternaryFn) {
660 case TernaryFn::select:
661 return arith::SelectOp::create(builder, arg0.getLoc(), arg0, arg1, arg2);
662 }
663 if (emitError) {
664 emitError() << "unsupported ternary function";
665 return nullptr;
666 }
667 llvm_unreachable("unsupported ternary function");
668 }
669
670 // Build the type functions defined by OpDSL.
671 Value buildTypeFn(TypeFn typeFn, Type toType, Value operand,
672 function_ref<InFlightDiagnostic()> emitError = {}) {
673 switch (typeFn) {
674 case TypeFn::cast_signed:
675 return cast(toType, operand, false);
676 case TypeFn::cast_unsigned:
677 return cast(toType, operand, true);
678 }
679 if (emitError) {
680 emitError() << "unsupported type conversion function";
681 return nullptr;
682 }
683 llvm_unreachable("unsupported type conversion function");
684 }
685
686 void yieldOutputs(ValueRange values) {
687 OpBuilder::InsertionGuard g(builder);
688 builder.setInsertionPointToEnd(&block);
689 Location loc = builder.getUnknownLoc();
690 YieldOp::create(builder, loc, values);
691 }
692
693 Value constant(const std::string &value) {
694 OpBuilder::InsertionGuard g(builder);
695 builder.setInsertionPointToEnd(&block);
696 Location loc = builder.getUnknownLoc();
697 Attribute valueAttr = parseAttribute(value, builder.getContext());
698 return arith::ConstantOp::create(builder, loc,
699 ::cast<TypedAttr>(valueAttr));
700 }
701
702 Value index(int64_t dim) {
703 OpBuilder::InsertionGuard g(builder);
704 builder.setInsertionPointToEnd(&block);
705 return IndexOp::create(builder, builder.getUnknownLoc(), dim);
706 }
707
708 Type getIntegerType(unsigned width) {
709 return IntegerType::get(builder.getContext(), width);
710 }
711
712 Type getFloat32Type() { return Float32Type::get(builder.getContext()); }
713 Type getFloat64Type() { return Float64Type::get(builder.getContext()); }
714
715private:
716 // Generates operations to cast the given operand to a specified type.
717 // If the cast cannot be performed, a warning will be issued and the
718 // operand returned as-is (which will presumably yield a verification
719 // issue downstream).
720 Value cast(Type toType, Value operand, bool isUnsignedCast) {
721 OpBuilder::InsertionGuard g(builder);
722 builder.setInsertionPointToEnd(&block);
723 auto loc = operand.getLoc();
724 if (isa<UnknownLoc>(loc)) {
725 if (operand.getDefiningOp())
726 loc = operand.getDefiningOp()->getLoc();
727 else if (operand.getParentBlock() &&
728 operand.getParentBlock()->getParentOp())
729 loc = operand.getParentBlock()->getParentOp()->getLoc();
730 }
731 return convertScalarToDtype(builder, loc, operand, toType, isUnsignedCast);
732 }
733
734 bool isComplex(Value value) {
735 return llvm::isa<ComplexType>(value.getType());
736 }
737 bool isFloatingPoint(Value value) {
738 return llvm::isa<FloatType>(value.getType());
739 }
740 bool isInteger(Value value) {
741 return llvm::isa<IntegerType>(value.getType());
742 }
743
744 OpBuilder &builder;
745 Block &block;
746};
747
748} // namespace
749
750//===----------------------------------------------------------------------===//
751// CopyOp
752//===----------------------------------------------------------------------===//
753
754namespace {
755
756struct EraseSelfCopy : OpRewritePattern<CopyOp> {
757 using OpRewritePattern<CopyOp>::OpRewritePattern;
758 LogicalResult matchAndRewrite(CopyOp copyOp,
759 PatternRewriter &rewriter) const override {
760 if (copyOp.getInputs() != copyOp.getOutputs())
761 return rewriter.notifyMatchFailure(copyOp, "not a self copy");
762 if (copyOp.hasPureBufferSemantics())
763 rewriter.eraseOp(copyOp);
764 else
765 rewriter.replaceOp(copyOp, copyOp.getInputs());
766
767 return success();
768 }
769};
770
771} // namespace
772
773void CopyOp::getCanonicalizationPatterns(RewritePatternSet &results,
774 MLIRContext *context) {
775 results.add<EraseSelfCopy>(context);
776}
777
778//===----------------------------------------------------------------------===//
779// FillOp
780//===----------------------------------------------------------------------===//
781
782namespace {
783
784/// Fold linalg.fill -> tensor.expand/collapse_shape chain.
785///
786/// For such op chains, we can create new linalg.fill ops with the result
787/// type of the tensor.expand/collapse_shape op.
788template <typename TensorReshapeOp>
789struct FoldFillWithTensorReshape : OpRewritePattern<TensorReshapeOp> {
790 using OpRewritePattern<TensorReshapeOp>::OpRewritePattern;
791 LogicalResult matchAndRewrite(TensorReshapeOp reshapeOp,
792 PatternRewriter &rewriter) const override {
793 auto oldFill = reshapeOp.getSrc().template getDefiningOp<FillOp>();
794 if (!oldFill)
795 return failure();
796
797 Location loc = oldFill.getLoc();
798 TensorReshapeOp newInit;
799 if constexpr (std::is_same<TensorReshapeOp, tensor::ExpandShapeOp>::value) {
800
801 newInit = TensorReshapeOp::create(
802 rewriter, loc, reshapeOp.getResultType(), oldFill.output(),
803 reshapeOp.getReassociation(), reshapeOp.getOutputShape(),
804 reshapeOp.getStaticOutputShape());
805 } else {
806 newInit = TensorReshapeOp::create(
807 rewriter, loc, reshapeOp.getResultType(), oldFill.output(),
808 reshapeOp.getReassociation());
809 }
810 rewriter.replaceOpWithNewOp<FillOp>(reshapeOp, ValueRange{oldFill.value()},
811 ValueRange{newInit});
812 return success();
813 }
814};
815
816/// Fold tensor.pad(linalg.fill) into linalg.fill if the padding value and the
817/// filling value are the same.
818struct FoldFillWithPad final : public OpRewritePattern<tensor::PadOp> {
820
821 LogicalResult matchAndRewrite(tensor::PadOp padOp,
822 PatternRewriter &rewriter) const override {
823 auto fillOp = padOp.getSource().getDefiningOp<linalg::FillOp>();
824 if (!fillOp)
825 return failure();
826
827 // We can only fold if the padding value is the same as the original
828 // filling value.
829 Value padValue = padOp.getConstantPaddingValue();
830 if (!padValue || fillOp.value() != padValue)
831 return failure();
832
833 ReifiedRankedShapedTypeDims reifiedShape;
834 if (failed(reifyResultShapes(rewriter, padOp, reifiedShape)))
835 return rewriter.notifyMatchFailure(
836 padOp, "failed to reify tensor.pad op result shape");
837
838 auto emptyTensor =
839 tensor::EmptyOp::create(rewriter, padOp.getLoc(), reifiedShape.front(),
840 padOp.getResultType().getElementType());
841 Value replacement =
842 FillOp::create(rewriter, fillOp.getLoc(), ValueRange{padValue},
843 ValueRange{emptyTensor})
844 .getResult(0);
845 if (replacement.getType() != padOp.getResultType()) {
846 replacement = tensor::CastOp::create(rewriter, fillOp.getLoc(),
847 padOp.getResultType(), replacement);
848 }
849 rewriter.replaceOp(padOp, replacement);
850 return success();
851 }
852};
853
854/// Fold tensor.insert_slice(tensor.pad(<input>), linalg.fill) into
855/// tensor.insert_slice(<input>, linalg.fill) if the padding value and the
856/// filling value are the same.
857struct FoldInsertPadIntoFill : public OpRewritePattern<tensor::InsertSliceOp> {
859
860 LogicalResult matchAndRewrite(tensor::InsertSliceOp insertOp,
861 PatternRewriter &rewriter) const override {
862 auto srcPadOp = insertOp.getSource().getDefiningOp<tensor::PadOp>();
863 if (!srcPadOp)
864 return failure();
865
866 if (insertOp.getType().getRank() != insertOp.getSourceType().getRank())
867 return failure();
868
869 // Walk back the tensor.insert_slice chain and find the first destination
870 // value at the start of the chain.
871 Value firstDest = insertOp.getDest();
872 while (auto prevOp = firstDest.getDefiningOp<tensor::InsertSliceOp>()) {
873 if (prevOp.getType().getRank() != prevOp.getSourceType().getRank())
874 return failure();
875
876 // Make sure the range of values accessed are disjoint. Without this, we
877 // cannot fold tensor.pad away.
878 bool disjoint = false;
879 for (int i = 0, e = prevOp.getType().getRank(); i < e; ++i) {
880 // If the dimension has dynamic offset/size, we cannot guarantee
881 // disjoint. So just skip it.
882 if (insertOp.isDynamicOffset(i) || insertOp.isDynamicSize(i) ||
883 insertOp.isDynamicStride(i) || prevOp.isDynamicOffset(i) ||
884 prevOp.isDynamicSize(i) || prevOp.isDynamicStride(i))
885 continue;
886
887 // Get the range start and end, inclusively for both.
888 int64_t prevStart = prevOp.getStaticOffset(i);
889 int64_t prevEnd = prevStart + (prevOp.getStaticSize(i) - 1) *
890 prevOp.getStaticStride(i);
891 int64_t nextStart = insertOp.getStaticOffset(i);
892 int64_t nextEnd = nextStart + (insertOp.getStaticSize(i) - 1) *
893 insertOp.getStaticStride(i);
894 if (prevEnd < nextStart || nextEnd < prevStart) {
895 disjoint = true;
896 break;
897 }
898 }
899
900 if (!disjoint)
901 break;
902 firstDest = prevOp.getDest();
903 }
904
905 // Check whether the first destination is a fill op. For overlapped cases,
906 // this also cannot be true.
907 auto dstFillOp = firstDest.getDefiningOp<linalg::FillOp>();
908 if (!dstFillOp)
909 return failure();
910
911 // We can only fold if the padding value is the same as the original
912 // filling value.
913 Value padValue = srcPadOp.getConstantPaddingValue();
914 if (!padValue || dstFillOp.value() != padValue)
915 return failure();
916
917 SmallVector<OpFoldResult> lowPads = srcPadOp.getMixedLowPad();
918 SmallVector<OpFoldResult> oldOffsets = insertOp.getMixedOffsets();
919
920 Location loc = insertOp.getLoc();
921 MLIRContext *context = getContext();
922
923 AffineExpr sym0, sym1;
924 bindSymbols(context, sym0, sym1);
925 auto addMap = AffineMap::get(0, 2, {sym0 + sym1}, context);
926
927 // Calculate the new offsets for the insert. It should be the old offsets
928 // plus low padding sizes.
929 SmallVector<OpFoldResult, 4> newOffsets;
930 for (const auto &p : llvm::zip(lowPads, oldOffsets)) {
931 newOffsets.push_back(affine::makeComposedFoldedAffineApply(
932 rewriter, loc, addMap, {std::get<0>(p), std::get<1>(p)}));
933 }
934
935 RankedTensorType srcPadType = srcPadOp.getSourceType();
936 SmallVector<OpFoldResult, 4> newSizes;
937 for (int i = 0, e = srcPadType.getRank(); i < e; ++i) {
938 if (srcPadType.isDynamicDim(i)) {
939 newSizes.push_back(
940 tensor::DimOp::create(rewriter, loc, srcPadOp.getSource(), i)
941 .getResult());
942 } else {
943 newSizes.push_back(rewriter.getIndexAttr(srcPadType.getDimSize(i)));
944 }
945 }
946
947 rewriter.replaceOpWithNewOp<tensor::InsertSliceOp>(
948 insertOp, srcPadOp.getSource(), insertOp.getDest(), newOffsets,
949 newSizes, insertOp.getMixedStrides());
950 return success();
951 }
952};
953
954/// Fold tensor.extract(linalg.fill(<input>)) into <input>
955struct FoldFillWithTensorExtract : public OpRewritePattern<tensor::ExtractOp> {
956public:
957 using OpRewritePattern<tensor::ExtractOp>::OpRewritePattern;
958
959 LogicalResult matchAndRewrite(tensor::ExtractOp extractOp,
960 PatternRewriter &rewriter) const override {
961 // See if tensor input of tensor.extract op is the result of a linalg.fill
962 // op.
963 auto fillOp = extractOp.getTensor().getDefiningOp<linalg::FillOp>();
964 if (!fillOp)
965 return failure();
966
967 // Get scalar input operand of linalg.fill op.
968 Value extractedScalar = fillOp.getInputs()[0];
969
970 // Replace tensor.extract op with scalar value used to fill the tensor.
971 rewriter.replaceOp(extractOp, extractedScalar);
972 return success();
973 }
974};
975
976/// Folds pack(fill) into a single fill op if
977/// 1. The pack op does not have padding value, or
978/// 2. The filled value and padding value are the same.
979static FailureOr<FillOp> foldFillPackIntoFillOp(RewriterBase &rewriter,
980 linalg::PackOp packOp) {
981 auto fillOp = packOp.getSource().getDefiningOp<FillOp>();
982 if (!fillOp)
983 return failure();
984
985 if (auto paddingValue = packOp.getPaddingValue())
986 if (!isEqualConstantIntOrValue(paddingValue, fillOp.value()))
987 return failure();
988
989 Value packOpDest = packOp.getDest();
990 if (!packOpDest.hasOneUse())
991 return failure();
992
993 return linalg::FillOp::create(rewriter, packOp.getLoc(), fillOp.getInputs(),
994 packOp.getDest());
995}
996
997/// Wrapper pattern that applies foldFillPackIntoFillOp method.
998struct FoldFillWithPack : public OpRewritePattern<linalg::PackOp> {
999public:
1000 FoldFillWithPack(MLIRContext *context)
1001 : OpRewritePattern<linalg::PackOp>(context) {}
1002
1003 LogicalResult matchAndRewrite(linalg::PackOp packOp,
1004 PatternRewriter &rewriter) const override {
1005 auto fillOp = foldFillPackIntoFillOp(rewriter, packOp);
1006 if (failed(fillOp))
1007 return failure();
1008 rewriter.replaceOp(packOp, fillOp.value().result());
1009 return success();
1010 }
1011};
1012
1013/// Fold fill with copy.
1014struct FoldFillWithCopy : OpRewritePattern<linalg::CopyOp> {
1015 using OpRewritePattern<linalg::CopyOp>::OpRewritePattern;
1016
1017 LogicalResult matchAndRewrite(linalg::CopyOp copyOp,
1018 PatternRewriter &rewriter) const override {
1019 if (auto fillOp = copyOp.getInputs().front().getDefiningOp<FillOp>()) {
1020 rewriter.replaceOpWithNewOp<FillOp>(copyOp, copyOp.getResultTypes(),
1021 fillOp.getInputs(),
1022 copyOp.getOutputs());
1023 return success();
1024 }
1025 if (auto fillOp = copyOp.getOutputs().front().getDefiningOp<FillOp>()) {
1026 rewriter.replaceOpWithNewOp<linalg::CopyOp>(copyOp, copyOp.getInputs(),
1027 fillOp.getOutputs());
1028 return success();
1029 }
1030 return failure();
1031 }
1032};
1033
1034/// Fold fill with transpose.
1035struct FoldFillWithTranspose : OpRewritePattern<linalg::TransposeOp> {
1036 using OpRewritePattern<linalg::TransposeOp>::OpRewritePattern;
1037
1038 LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp,
1039 PatternRewriter &rewriter) const override {
1040 if (auto fillOp = transposeOp.getInput().getDefiningOp<FillOp>()) {
1041 rewriter.replaceOpWithNewOp<FillOp>(
1042 transposeOp, transposeOp.getResultTypes(), fillOp.getInputs(),
1043 transposeOp.getDpsInitOperand(0)->get());
1044 return success();
1045 }
1046 return failure();
1047 }
1048};
1049
1050/// Fold a concat with all elements being fills of the same value
1051/// into a fill of the concat result shape.
1052struct FoldConcatsOfFill : public OpRewritePattern<tensor::ConcatOp> {
1054
1055 LogicalResult matchAndRewrite(tensor::ConcatOp concatOp,
1056 PatternRewriter &rewriter) const override {
1057 auto concatOperands = concatOp.getInputs();
1058 if (concatOperands.empty()) {
1059 return failure();
1060 }
1061
1062 auto firstFillOp = concatOperands.front().getDefiningOp<linalg::FillOp>();
1063 if (!firstFillOp) {
1064 return failure();
1065 }
1066 // Prefetch the fill value.
1067 OpFoldResult firstFillVal =
1068 getAsOpFoldResult(firstFillOp.getDpsInputOperand(0)->get());
1069 // Collect all the outs values for the fill operations.
1070 SmallVector<Value> allOuts;
1071 allOuts.push_back(firstFillOp.getDpsInitOperand(0)->get());
1072
1073 auto isDefinedByCompatibleFillOp = [&](Value v) -> bool {
1074 auto fillOp = v.getDefiningOp<linalg::FillOp>();
1075 if (!fillOp) {
1076 return false;
1077 }
1078
1079 OpFoldResult fillVal =
1080 getAsOpFoldResult(fillOp.getDpsInputOperand(0)->get());
1081 if (fillVal != firstFillVal)
1082 return false;
1083
1084 allOuts.push_back(fillOp.getDpsInitOperand(0)->get());
1085 return true;
1086 };
1087 if (!llvm::all_of(concatOperands.drop_front(),
1088 isDefinedByCompatibleFillOp)) {
1089 return rewriter.notifyMatchFailure(
1090 concatOp, "not all operands are defined by a compatible fill op");
1091 }
1092
1093 Value outsConcat = tensor::ConcatOp::create(rewriter, concatOp.getLoc(),
1094 concatOp.getDim(), allOuts);
1095 rewriter.replaceOpWithNewOp<linalg::FillOp>(
1096 concatOp, firstFillOp.getDpsInputOperand(0)->get(), outsConcat);
1097 return success();
1098 }
1099};
1100
1101} // namespace
1102
1103void FillOp::getCanonicalizationPatterns(RewritePatternSet &results,
1104 MLIRContext *context) {
1105 results.add<FoldConcatsOfFill, FoldFillWithCopy, FoldFillWithTensorExtract,
1106 FoldFillWithPack, FoldFillWithPad,
1107 FoldFillWithTensorReshape<tensor::CollapseShapeOp>,
1108 FoldFillWithTensorReshape<tensor::ExpandShapeOp>,
1109 FoldInsertPadIntoFill, FoldFillWithTranspose>(context);
1110}
1111
1112//===----------------------------------------------------------------------===//
1113// GenericOp
1114//===----------------------------------------------------------------------===//
1115
1117 OpBuilder &builder, Location loc, Region &region, ValueRange inputs,
1118 ValueRange outputs,
1119 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild) {
1120 SmallVector<Type, 4> blockArgTypes;
1121 SmallVector<Location, 4> blockArgLocs;
1122 for (ValueRange container : {inputs, outputs}) {
1123 for (Value v : container) {
1124 Type t = v.getType();
1125 blockArgTypes.push_back(
1126 isa<MemRefType, RankedTensorType>(t) ? getElementTypeOrSelf(t) : t);
1127 blockArgLocs.push_back(v.getLoc());
1128 }
1129 }
1130
1131 OpBuilder::InsertionGuard guard(builder);
1132 Block *bodyBlock =
1133 builder.createBlock(&region, region.end(), blockArgTypes, blockArgLocs);
1134 bodyBuild(builder, loc, bodyBlock->getArguments());
1135}
1136
1137void GenericOp::getAsmBlockArgumentNames(Region &region,
1138 OpAsmSetValueNameFn setNameFn) {
1139 for (Value v : getRegionInputArgs())
1140 setNameFn(v, "in");
1141 for (Value v : getRegionOutputArgs())
1142 setNameFn(v, "out");
1143}
1144
1145void GenericOp::build(
1146 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
1147 ValueRange inputs, ValueRange outputs, ArrayAttr indexingMaps,
1148 ArrayAttr iteratorTypes, StringAttr doc, StringAttr libraryCall,
1149 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1150 ArrayRef<NamedAttribute> attributes) {
1151 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps,
1152 iteratorTypes, doc, libraryCall);
1153 result.addAttributes(attributes);
1154 if (bodyBuild)
1155 buildGenericRegion(builder, result.location, *result.regions.front(),
1156 inputs, outputs, bodyBuild);
1157}
1158
1159void GenericOp::build(
1160 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
1161 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
1162 ArrayRef<utils::IteratorType> iteratorTypes, StringRef doc,
1163 StringRef libraryCall,
1164 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1165 ArrayRef<NamedAttribute> attributes) {
1166 build(builder, result, resultTensorTypes, inputs, outputs,
1167 builder.getAffineMapArrayAttr(indexingMaps),
1168 builder.getArrayAttr(llvm::map_to_vector(
1169 iteratorTypes,
1170 [&](utils::IteratorType iter) -> mlir::Attribute {
1171 return IteratorTypeAttr::get(builder.getContext(), iter);
1172 })),
1173 doc.empty() ? StringAttr() : builder.getStringAttr(doc),
1174 libraryCall.empty() ? StringAttr() : builder.getStringAttr(libraryCall),
1175 bodyBuild, attributes);
1176}
1177
1178void GenericOp::build(
1179 OpBuilder &builder, OperationState &result, ValueRange inputs,
1180 ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
1181 ArrayRef<utils::IteratorType> iteratorTypes, StringRef doc,
1182 StringRef libraryCall,
1183 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1184 ArrayRef<NamedAttribute> attributes) {
1185 build(builder, result, TypeRange{}, inputs, outputs, indexingMaps,
1186 iteratorTypes, doc, libraryCall, bodyBuild, attributes);
1187}
1188
1189void GenericOp::build(
1190 OpBuilder &builder, OperationState &result, ValueRange inputs,
1191 ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
1192 ArrayRef<utils::IteratorType> iteratorTypes,
1193 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1194 ArrayRef<NamedAttribute> attributes) {
1195 build(builder, result, inputs, outputs, indexingMaps, iteratorTypes,
1196 /*doc=*/"",
1197 /*libraryCall=*/"", bodyBuild, attributes);
1198}
1199
1200void GenericOp::build(
1201 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
1202 ValueRange inputs, ValueRange outputs, ArrayRef<AffineMap> indexingMaps,
1203 ArrayRef<utils::IteratorType> iteratorTypes,
1204 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1205 ArrayRef<NamedAttribute> attributes) {
1206 build(builder, result, resultTensorTypes, inputs, outputs, indexingMaps,
1207 iteratorTypes,
1208 /*doc=*/"",
1209 /*libraryCall=*/"", bodyBuild, attributes);
1210}
1211
1212void GenericOp::print(OpAsmPrinter &p) {
1213 p << " ";
1214
1215 // Print extra attributes.
1216 auto genericAttrNames = linalgTraitAttrNames();
1217
1218 llvm::StringSet<> genericAttrNamesSet;
1219 genericAttrNamesSet.insert_range(genericAttrNames);
1220 SmallVector<NamedAttribute, 8> genericAttrs;
1221 for (auto attr : (*this)->getAttrs()) {
1222 if (attr.getName() == getIteratorTypesAttrName()) {
1223 auto iteratorTypes =
1224 llvm::cast<ArrayAttr>(attr.getValue())
1225 .getAsValueRange<IteratorTypeAttr, utils::IteratorType>();
1226 // Convert IteratorType enums into the string representation. This is
1227 // needed, because tests still use the old format when 'iterator_types'
1228 // attribute is represented as an array of strings.
1229 // TODO: Remove this conversion once tests are fixed.
1230 SmallVector<Attribute> iteratorTypeNames = llvm::map_to_vector(
1231 iteratorTypes, [&](utils::IteratorType t) -> Attribute {
1232 return StringAttr::get(getContext(), stringifyIteratorType(t));
1233 });
1234
1235 genericAttrs.emplace_back(
1236 getIteratorTypesAttrName(),
1237 ArrayAttr::get(getContext(), iteratorTypeNames));
1238 } else if (genericAttrNamesSet.count(attr.getName().strref()) > 0) {
1239 genericAttrs.push_back(attr);
1240 }
1241 }
1242 if (!genericAttrs.empty()) {
1243 auto genericDictAttr = DictionaryAttr::get(getContext(), genericAttrs);
1244 p << genericDictAttr;
1245 }
1246
1247 // Printing is shared with named ops, except for the region and attributes
1248 printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
1249
1250 genericAttrNames.push_back("operandSegmentSizes");
1251 genericAttrNamesSet.insert(genericAttrNames.back());
1252
1253 bool hasExtraAttrs = false;
1254 for (NamedAttribute n : (*this)->getAttrs()) {
1255 if ((hasExtraAttrs = !genericAttrNamesSet.contains(n.getName().strref())))
1256 break;
1257 }
1258 if (hasExtraAttrs) {
1259 p << " attrs = ";
1260 p.printOptionalAttrDict((*this)->getAttrs(),
1261 /*elidedAttrs=*/genericAttrNames);
1262 }
1263
1264 // Print region.
1265 if (!getRegion().empty()) {
1266 p << ' ';
1267 p.printRegion(getRegion());
1268 }
1269
1270 // Print results.
1271 printNamedStructuredOpResults(p, getResultTensors().getTypes());
1272}
1273
1274ParseResult GenericOp::parse(OpAsmParser &parser, OperationState &result) {
1275 DictionaryAttr dictAttr;
1276 // Parse the core linalg traits that must check into a dictAttr.
1277 // The name is unimportant as we will overwrite result.attributes.
1278 // The core linalg traits must contain the information necessary to pass the
1279 // verifier.
1280 llvm::SMLoc attributeLocation = parser.getCurrentLocation();
1281 if (parser.parseAttribute(dictAttr, "_", result.attributes))
1282 return failure();
1283 result.attributes.assign(dictAttr.getValue().begin(),
1284 dictAttr.getValue().end());
1285
1286 // Convert array of string into an array of IteratorType enums. This is
1287 // needed, because tests still use the old format when 'iterator_types'
1288 // attribute is represented as an array of strings.
1289 // TODO: Remove this conversion once tests are fixed.
1290 auto iteratorTypes = dyn_cast_or_null<ArrayAttr>(
1291 result.attributes.get(getIteratorTypesAttrName(result.name)));
1292 if (!iteratorTypes) {
1293 return parser.emitError(attributeLocation)
1294 << "expected " << getIteratorTypesAttrName(result.name)
1295 << " array attribute";
1296 }
1297
1298 SmallVector<Attribute> iteratorTypeAttrs;
1299
1300 for (StringRef s : iteratorTypes.getAsValueRange<StringAttr>()) {
1301 auto maybeIteratorType = utils::symbolizeIteratorType(s);
1302 if (!maybeIteratorType.has_value())
1303 return parser.emitError(parser.getCurrentLocation())
1304 << "unexpected iterator_type (" << s << ")";
1305
1306 iteratorTypeAttrs.push_back(
1307 IteratorTypeAttr::get(parser.getContext(), maybeIteratorType.value()));
1308 }
1309 result.attributes.set(getIteratorTypesAttrName(result.name),
1310 parser.getBuilder().getArrayAttr(iteratorTypeAttrs));
1311
1312 // Parsing is shared with named ops, except for the region.
1313 SmallVector<Type, 1> inputTypes, outputTypes;
1314 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes))
1315 return failure();
1316
1317 // Optional attributes may be added.
1318 if (succeeded(parser.parseOptionalKeyword("attrs")))
1319 if (failed(parser.parseEqual()) ||
1320 failed(parser.parseOptionalAttrDict(result.attributes)))
1321 return failure();
1322
1323 std::unique_ptr<Region> region = std::make_unique<Region>();
1324 if (parser.parseRegion(*region, {}))
1325 return failure();
1326 result.addRegion(std::move(region));
1327
1328 // Generic ops may specify that a subset of its outputs are tensors. Such
1329 // outputs are specified in the result type.
1330 // TODO: may need to move output parsing before region parsing.
1331 // Need to wait for declarative assembly resolution to decide.
1332 SmallVector<Type, 1> outputTensorsTypes;
1333 if (parseNamedStructuredOpResults(parser, outputTensorsTypes))
1334 return failure();
1335 result.addTypes(outputTensorsTypes);
1336
1337 return success();
1338}
1339
1342 &effects,
1343 LinalgOp linalgOp) {
1344 for (auto [index, operand] : llvm::enumerate(linalgOp.getDpsInputs())) {
1345 if (!llvm::isa<MemRefType>(operand.getType()))
1346 continue;
1347 effects.emplace_back(
1348 MemoryEffects::Read::get(), &linalgOp->getOpOperand(index), /*stage=*/0,
1349 /*effectOnFullRegion=*/true, SideEffects::DefaultResource::get());
1350 }
1351
1352 for (OpOperand &operand : linalgOp.getDpsInitsMutable()) {
1353 if (!llvm::isa<MemRefType>(operand.get().getType()))
1354 continue;
1355 if (linalgOp.payloadUsesValueFromOperand(&operand)) {
1356 effects.emplace_back(MemoryEffects::Read::get(), &operand, /*stage=*/0,
1357 /*effectOnFullRegion=*/true,
1359 }
1360 effects.emplace_back(MemoryEffects::Write::get(), &operand, /*stage=*/0,
1361 /*effectOnFullRegion=*/true,
1363 }
1364}
1365
1366void GenericOp::getEffects(
1367 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1368 &effects) {
1369 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
1370}
1371
1374 // Operands with value semantics are speculatable, while operands with memory
1375 // semantics are not.
1376 if (!linalgOp.hasPureTensorSemantics())
1378 // The body of the op can still have speculation in its region.
1380}
1381
1382Speculation::Speculatability GenericOp::getSpeculatability() {
1383 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
1384}
1385
1386namespace {
1387
1388/// Remove linalg operations that are just copying the values from inputs to
1389/// results. In the memref case, the operation must be copying to and from the
1390/// same value. Requirements are:
1391/// 1) All iterator types are parallel
1392/// 2) The body contains just a yield operation with the yielded values being
1393/// the arguments corresponding to the operands.
1394template <typename OpTy>
1395struct EraseIdentityLinalgOp : public OpRewritePattern<OpTy> {
1396 using OpRewritePattern<OpTy>::OpRewritePattern;
1397
1398 LogicalResult matchAndRewrite(OpTy linalgOp,
1399 PatternRewriter &rewriter) const override {
1400 // All indexing maps must be equal. It follows that they are permutations.
1401 if (!llvm::all_equal(linalgOp.getIndexingMapsArray()))
1402 return failure();
1403
1404 // Check that the body of the linalg operation is just a linalg.yield
1405 // operation.
1406 Block &body = linalgOp->getRegion(0).front();
1407 if (!llvm::hasSingleElement(body))
1408 return failure();
1409 auto yieldOp = dyn_cast<linalg::YieldOp>(body.getTerminator());
1410 if (!yieldOp)
1411 return failure();
1412
1413 // In the buffer case, we need to check exact buffer equality.
1414 if (linalgOp.hasPureBufferSemantics()) {
1415 if (linalgOp.getNumDpsInputs() != 1 || linalgOp.getNumDpsInits() != 1 ||
1416 linalgOp.getDpsInputOperand(0)->get() !=
1417 linalgOp.getDpsInitOperand(0)->get()) {
1418 return rewriter.notifyMatchFailure(
1419 linalgOp, "expected single input and output to be the same value");
1420 }
1421
1422 auto yieldArg = dyn_cast<BlockArgument>(yieldOp.getOperand(0));
1423 if (!yieldArg || yieldArg.getOwner() != &body) {
1424 return rewriter.notifyMatchFailure(linalgOp,
1425 "cannot fold fill-like op");
1426 }
1427
1428 rewriter.eraseOp(linalgOp);
1429 return success();
1430 }
1431
1432 if (!linalgOp.hasPureTensorSemantics()) {
1433 return rewriter.notifyMatchFailure(
1434 linalgOp, "mixed semantics is not supported yet");
1435 }
1436
1437 // Get the argument number of the returned values. That is the operand
1438 // number to use for replacing uses of this operation.
1439 SmallVector<Value> returnedArgs;
1440 for (const auto &yieldVal : llvm::enumerate(yieldOp.getValues())) {
1441 auto yieldArg = llvm::dyn_cast<BlockArgument>(yieldVal.value());
1442 if (!yieldArg || yieldArg.getOwner() != &body)
1443 return failure();
1444 unsigned argumentNumber = yieldArg.getArgNumber();
1445 Value returnedArg = linalgOp->getOperand(argumentNumber);
1446 Type resultType = linalgOp->getResult(yieldVal.index()).getType();
1447 // The input can have a different type than the result, e.g. a dynamic
1448 // input dimension can be turned into a static output dimension.
1449 Type returnType = returnedArg.getType();
1450 if (returnType != resultType) {
1451 // Distinguish between sparse conversion or dense tensor casting.
1452 // TODO: unify the two ops?
1455 returnedArg = sparse_tensor::ConvertOp::create(
1456 rewriter, linalgOp.getLoc(), resultType, returnedArg);
1457 else {
1458 if (!tensor::CastOp::areCastCompatible(returnedArg.getType(),
1459 resultType))
1460 return failure();
1461 returnedArg = tensor::CastOp::create(rewriter, linalgOp.getLoc(),
1462 resultType, returnedArg);
1463 }
1464 }
1465 returnedArgs.push_back(returnedArg);
1466 }
1467
1468 if (returnedArgs.size() != linalgOp->getNumResults())
1469 return failure();
1470 rewriter.replaceOp(linalgOp, returnedArgs);
1471 return success();
1472 }
1473};
1474
1475} // namespace
1476
1477void GenericOp::getCanonicalizationPatterns(RewritePatternSet &results,
1478 MLIRContext *context) {
1479 results.add<EraseIdentityLinalgOp<GenericOp>>(context);
1480}
1481
1482LogicalResult GenericOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
1483 return memref::foldMemRefCast(*this);
1484}
1485
1486//===----------------------------------------------------------------------===//
1487// MapOp
1488//===----------------------------------------------------------------------===//
1489
1490static ParseResult parseDstStyleOp(
1492 function_ref<ParseResult(OpAsmParser &, NamedAttrList &)> parseAttrsFn =
1493 nullptr) {
1494 // Parse `ins` and `outs`.
1495 SmallVector<Type, 4> inputTypes, outputTypes;
1496 if (parseCommonStructuredOpParts(parser, result, inputTypes, outputTypes,
1497 /*addOperandSegmentSizes=*/false))
1498 return failure();
1499
1500 // Add result types.
1501 for (Type outputType : outputTypes) {
1502 if (llvm::isa<RankedTensorType>(outputType))
1503 result.addTypes(outputType);
1504 }
1505
1506 // Parse required attributes.
1507 if (parseAttrsFn && failed(parseAttrsFn(parser, result.attributes)))
1508 return failure();
1509
1510 // Parse optional attributes.
1511 if (parser.parseOptionalAttrDict(result.attributes))
1512 return failure();
1513 return success();
1514}
1515
1516void MapOp::getAsmBlockArgumentNames(Region &region,
1517 OpAsmSetValueNameFn setNameFn) {
1518 for (Value v : getRegionInputArgs())
1519 setNameFn(v, "in");
1520 for (Value v : getRegionOutputArgs())
1521 setNameFn(v, "init");
1522}
1523
1524void MapOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
1525 if (!getResults().empty())
1526 setNameFn(getResults().front(), "mapped");
1527}
1528
1529void MapOp::build(
1530 OpBuilder &builder, OperationState &result, ValueRange inputs, Value init,
1531 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1532 ArrayRef<NamedAttribute> attributes) {
1533 build(builder, result, TypeRange{}, inputs, init);
1534 result.addAttributes(attributes);
1535
1536 // Add output types for `RankedTensorType` output arguments.
1537 Type initType = init.getType();
1538 if (llvm::isa<RankedTensorType>(initType))
1539 result.addTypes(initType);
1540
1541 if (bodyBuild)
1542 buildGenericRegion(builder, result.location, *result.regions.front(),
1543 inputs, /*outputs=*/{init}, bodyBuild);
1544}
1545
1547 const OperationName &payloadOpName,
1548 const NamedAttrList &payloadOpAttrs,
1549 ArrayRef<Value> operands,
1550 bool initFirst = false, bool mapInit = true) {
1551 OpBuilder b(parser.getContext());
1552 Region *body = result.addRegion();
1553 Block &block = body->emplaceBlock();
1554 b.setInsertionPointToStart(&block);
1555 for (auto &operand : operands) {
1556 block.addArgument(
1557 llvm::cast<ShapedType>(operand.getType()).getElementType(),
1558 b.getUnknownLoc());
1559 }
1560 SmallVector<Value> payloadOpOperands;
1561 // If initFirst flag is enabled, we consider init as the first position of
1562 // payload operands.
1563 if (initFirst) {
1564 if (mapInit)
1565 payloadOpOperands.push_back(block.getArguments().back());
1566 for (const auto &arg : block.getArguments().drop_back())
1567 payloadOpOperands.push_back(arg);
1568 } else {
1569 payloadOpOperands = {block.getArguments().begin(),
1570 block.getArguments().end() - int(!mapInit)};
1571 }
1572
1573 Operation *payloadOp = b.create(
1574 result.location, b.getStringAttr(payloadOpName.getStringRef()),
1575 payloadOpOperands,
1576 TypeRange{llvm::cast<ShapedType>(result.operands.back().getType())
1577 .getElementType()},
1578 payloadOpAttrs);
1579 YieldOp::create(b, result.location, payloadOp->getResults());
1580}
1581
1582ParseResult MapOp::parse(OpAsmParser &parser, OperationState &result) {
1583 std::optional<OperationName> payloadOpName;
1584 NamedAttrList payloadOpAttrs;
1585 if (succeeded(parser.parseOptionalLBrace())) {
1586 FailureOr<OperationName> operationName = parser.parseCustomOperationName();
1587 if (failed(operationName))
1588 return failure();
1589 if (parser.parseOptionalAttrDict(payloadOpAttrs))
1590 return failure();
1591 payloadOpName = operationName.value();
1592 if (parser.parseRBrace())
1593 return failure();
1594 }
1595
1596 if (parseDstStyleOp(parser, result))
1597 return failure();
1598
1599 if (payloadOpName.has_value()) {
1600 if (!result.operands.empty())
1601 addBodyWithPayloadOp(parser, result, payloadOpName.value(),
1602 payloadOpAttrs, ArrayRef(result.operands), false,
1603 false);
1604 else
1605 result.addRegion();
1606 } else {
1607 SmallVector<OpAsmParser::Argument> regionArgs;
1608 if (parser.parseArgumentList(regionArgs, OpAsmParser::Delimiter::Paren,
1609 /*allowType=*/true, /*allowAttrs=*/true)) {
1610 return failure();
1611 }
1612 Region *body = result.addRegion();
1613 if (parser.parseRegion(*body, regionArgs))
1614 return failure();
1615 }
1616 return success();
1617}
1618
1619static bool canUseShortForm(Block *body, bool initFirst = false,
1620 bool mapInit = true) {
1621 // `intFirst == true` implies that we want to map init arg
1622 if (initFirst && !mapInit)
1623 return false;
1624 // Check if the body can be printed in short form. The following 4 conditions
1625 // must be satisfied:
1626
1627 // 1) The body must contain exactly 2 operations: the payload op and a yield.
1628 if (body->getOperations().size() != 2)
1629 return false;
1630 Operation &payload = body->getOperations().front();
1631
1632 // 2) The payload op must have the same number of operands as the number of
1633 // block arguments.
1634 if (payload.getNumOperands() == 0 ||
1635 payload.getNumOperands() != body->getNumArguments() - int(!mapInit))
1636 return false;
1637
1638 // 3) If `initFirst` is true (e.g., for reduction ops), the init block
1639 // must be the first operand of the payload op, otherwise, the operands
1640 // must match the block arguments in order.
1641 if (initFirst) {
1642 // check init
1643 if (payload.getOperands().back() != body->getArgument(0))
1644 return false;
1645 // check rest
1646 for (const auto &[operand, bbArg] :
1647 llvm::zip(payload.getOperands(), body->getArguments().drop_front())) {
1648 if (bbArg != operand)
1649 return false;
1650 }
1651 } else {
1652 for (const auto &[operand, bbArg] :
1653 llvm::zip(payload.getOperands(),
1654 body->getArguments().drop_back(int(!mapInit)))) {
1655 if (bbArg != operand)
1656 return false;
1657 }
1658 }
1659
1660 // 4) The `yield` operand must be the result of the payload op.
1661 auto yieldOp = cast<YieldOp>(body->getTerminator());
1662 return yieldOp.getNumOperands() == 1 &&
1663 yieldOp.getOperand(0).getDefiningOp() &&
1664 yieldOp.getOperand(0).getDefiningOp() == &payload;
1665}
1666
1667static void printShortForm(OpAsmPrinter &p, Operation *payloadOp) {
1668 SmallVector<StringRef> elidedAttrs;
1669 std::string attrToElide;
1670 p << " { " << payloadOp->getName().getStringRef();
1671 for (const auto &attr : payloadOp->getAttrs()) {
1672 auto fastAttr =
1673 llvm::dyn_cast<mlir::arith::FastMathFlagsAttr>(attr.getValue());
1674 if (fastAttr && fastAttr.getValue() == mlir::arith::FastMathFlags::none) {
1675 attrToElide = attr.getName().str();
1676 elidedAttrs.push_back(attrToElide);
1677 break;
1678 }
1679 }
1680 p.printOptionalAttrDict(payloadOp->getAttrs(), elidedAttrs);
1681 p << " }";
1682}
1683
1684void MapOp::print(OpAsmPrinter &p) {
1685 Block *mapper = getBody();
1686 bool useShortForm =
1687 canUseShortForm(mapper, /*initFirst=*/false, /*mapInit*/ false);
1688 if (useShortForm) {
1689 printShortForm(p, &mapper->getOperations().front());
1690 }
1691
1692 printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
1693 p.printOptionalAttrDict((*this)->getAttrs());
1694
1695 if (!useShortForm) {
1696 // Print region if the payload op was not detected.
1697 p.increaseIndent();
1698 p.printNewline();
1699 p << "(";
1700 llvm::interleaveComma(mapper->getArguments(), p,
1701 [&](auto arg) { p.printRegionArgument(arg); });
1702 p << ") ";
1703
1704 p.printRegion(getMapper(), /*printEntryBlockArgs=*/false);
1705 p.decreaseIndent();
1706 }
1707}
1708
1709LogicalResult MapOp::verify() {
1710 auto *bodyBlock = getBody();
1711 auto blockArgs = bodyBlock->getArguments();
1712
1713 // Checks if the number of `inputs` + `init` match the arity of the `mapper`
1714 // region.
1715 if (getInputs().size() + 1 != blockArgs.size())
1716 return emitOpError() << "expects number of operands to match the arity of "
1717 "mapper, but got: "
1718 << getInputs().size() + 1 << " and "
1719 << blockArgs.size();
1720
1721 // The parameters of mapper should all match the element type of inputs.
1722 for (const auto &[bbArgType, inputArg] :
1723 llvm::zip(bodyBlock->getArgumentTypes(), getInputs())) {
1724 auto inputElemType =
1725 llvm::cast<ShapedType>(inputArg.getType()).getElementType();
1726 if (bbArgType != inputElemType) {
1727 return emitOpError() << "expected element type of input " << inputElemType
1728 << " to match bbArg type " << bbArgType;
1729 }
1730 }
1731
1732 // The shape of each input must match the shape of the output.
1733 auto outputShape = getInit().getType().getShape();
1734 for (Type inputArgType : TypeRange{getInputs()}) {
1735 auto inputElemShape = llvm::cast<ShapedType>(inputArgType).getShape();
1736 if (inputElemShape != outputShape) {
1737 return emitOpError() << "expected shape of input (" << inputElemShape
1738 << ") to match shape of output (" << outputShape
1739 << ")";
1740 }
1741 }
1742
1743 return success();
1744}
1745
1746SmallVector<utils::IteratorType> MapOp::getIteratorTypesArray() {
1747 int64_t rank = getInit().getType().getRank();
1748 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
1749}
1750
1751ArrayAttr MapOp::getIndexingMaps() {
1752 Builder builder(getContext());
1753 int64_t rank = getInit().getType().getRank();
1754 int64_t numIndexingMaps = getOperands().size();
1755 return builder.getAffineMapArrayAttr(SmallVector<AffineMap>(
1756 numIndexingMaps, builder.getMultiDimIdentityMap(rank)));
1757}
1758
1759void MapOp::getEffects(
1760 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1761 &effects) {
1762 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
1763}
1764
1765Speculation::Speculatability MapOp::getSpeculatability() {
1766 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
1767}
1768
1769//===----------------------------------------------------------------------===//
1770// ReduceOp
1771//===----------------------------------------------------------------------===//
1772
1773void ReduceOp::getAsmBlockArgumentNames(Region &region,
1774 OpAsmSetValueNameFn setNameFn) {
1775 for (Value v : getRegionInputArgs())
1776 setNameFn(v, "in");
1777 for (Value v : getRegionOutputArgs())
1778 setNameFn(v, "init");
1779}
1780
1781void ReduceOp::getAsmResultNames(
1782 function_ref<void(Value, StringRef)> setNameFn) {
1783 if (!getResults().empty())
1784 setNameFn(getResults().front(), "reduced");
1785}
1786
1787void ReduceOp::build(
1788 OpBuilder &builder, OperationState &result, ValueRange inputs,
1789 ValueRange inits, ArrayRef<int64_t> dimensions,
1790 function_ref<void(OpBuilder &, Location, ValueRange)> bodyBuild,
1791 ArrayRef<NamedAttribute> attributes) {
1792 build(builder, result, TypeRange{}, inputs, inits, dimensions);
1793 result.addAttributes(attributes);
1794
1795 // Add output types for `RankedTensorType` output arguments.
1796 for (Value init : inits) {
1797 Type initType = init.getType();
1798 if (llvm::isa<RankedTensorType>(initType))
1799 result.addTypes(initType);
1800 }
1801
1802 if (bodyBuild)
1803 buildGenericRegion(builder, result.location, *result.regions.front(),
1804 inputs, inits, bodyBuild);
1805}
1806
1807SmallVector<utils::IteratorType> ReduceOp::getIteratorTypesArray() {
1808 int64_t inputRank =
1809 llvm::cast<ShapedType>(getInputs()[0].getType()).getRank();
1810 SmallVector<utils::IteratorType> iteratorTypes(inputRank,
1811 utils::IteratorType::parallel);
1812 for (int64_t reductionDim : getDimensions())
1813 iteratorTypes[reductionDim] = utils::IteratorType::reduction;
1814 return iteratorTypes;
1815}
1816
1817ArrayAttr ReduceOp::getIndexingMaps() {
1818 int64_t inputRank =
1819 llvm::cast<ShapedType>(getInputs()[0].getType()).getRank();
1820 SmallVector<AffineMap> affineMaps(
1821 getNumDpsInputs(),
1823 AffineMap resultMap =
1825 .dropResults(getDimensions());
1826 for (int64_t i = 0, e = getNumDpsInits(); i < e; ++i)
1827 affineMaps.push_back(resultMap);
1828 return Builder(getContext()).getAffineMapArrayAttr(affineMaps);
1829}
1830
1831void ReduceOp::getEffects(
1832 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
1833 &effects) {
1834 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
1835}
1836
1837Speculation::Speculatability ReduceOp::getSpeculatability() {
1838 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
1839}
1840
1841static ParseResult parseDenseI64ArrayAttr(OpAsmParser &parser,
1842 NamedAttrList &attributes,
1843 StringRef attributeName) {
1844 if (parser.parseKeyword(attributeName) || parser.parseEqual())
1845 return failure();
1846
1847 attributes.set(attributeName, DenseI64ArrayAttr::parse(parser, Type{}));
1848 return success();
1849}
1850
1851ParseResult ReduceOp::parse(OpAsmParser &parser, OperationState &result) {
1852 std::optional<OperationName> payloadOpName;
1853 NamedAttrList payloadOpAttrs;
1854 if (succeeded(parser.parseOptionalLBrace())) {
1855 FailureOr<OperationName> operationName = parser.parseCustomOperationName();
1856 if (failed(operationName))
1857 return failure();
1858 if (parser.parseOptionalAttrDict(payloadOpAttrs))
1859 return failure();
1860 payloadOpName = operationName.value();
1861 if (parser.parseRBrace())
1862 return failure();
1863 }
1864
1865 if (parseDstStyleOp(
1866 parser, result, [&](OpAsmParser &parser, NamedAttrList &attributes) {
1867 return parseDenseI64ArrayAttr(parser, attributes, "dimensions");
1868 }))
1869 return failure();
1870
1871 if (payloadOpName.has_value()) {
1872 addBodyWithPayloadOp(parser, result, payloadOpName.value(), payloadOpAttrs,
1873 ArrayRef(result.operands), /*initFirst=*/true);
1874 } else {
1875 SmallVector<OpAsmParser::Argument> regionArgs;
1876 if (parser.parseArgumentList(regionArgs, OpAsmParser::Delimiter::Paren,
1877 /*allowType=*/true, /*allowAttrs=*/true)) {
1878 return failure();
1879 }
1880
1881 Region *body = result.addRegion();
1882 if (parser.parseRegion(*body, regionArgs))
1883 return failure();
1884 }
1885
1886 return success();
1887}
1888
1889static void printDenseI64ArrayAttr(OpAsmPrinter &p, StringRef attributeName,
1890 ArrayRef<int64_t> attributeValue) {
1891 p << ' ' << attributeName << " = [" << attributeValue << "] ";
1892}
1893
1894void ReduceOp::print(OpAsmPrinter &p) {
1895 Block *mapper = getBody();
1896 bool useShortForm = canUseShortForm(mapper, /*initFirst=*/true);
1897 if (useShortForm) {
1898 printShortForm(p, &mapper->getOperations().front());
1899 }
1900
1901 printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
1902 printDenseI64ArrayAttr(p, getDimensionsAttrName(), getDimensions());
1903 p.printOptionalAttrDict((*this)->getAttrs(), {getDimensionsAttrName()});
1904 if (!useShortForm) {
1905 // Print region if the payload op was not detected.
1906 p.increaseIndent();
1907 p.printNewline();
1908 p << "(";
1909 llvm::interleaveComma(mapper->getArguments(), p,
1910 [&](auto arg) { p.printRegionArgument(arg); });
1911 p << ") ";
1912
1913 p.printRegion(getCombiner(), /*printEntryBlockArgs=*/false);
1914 p.decreaseIndent();
1915 }
1916}
1917
1918LogicalResult ReduceOp::verify() {
1919 ArrayRef<int64_t> dimensionsRef = getDimensions();
1920
1921 // The ReduceOp uses `SameVariadicOperandSize`, which requires equal numbers
1922 // of inputs and inits. Detect a mismatch early: when they differ, the
1923 // ODS-generated getInputs()/getInits() accessors compute each group's size
1924 // via floordiv of the total operand count, producing incorrect slices that
1925 // would cause out-of-bounds accesses below.
1926 if (getInputs().size() != static_cast<size_t>(getNumDpsInputs()))
1927 return emitOpError()
1928 << "expected equal number of inputs and outputs (required by "
1929 "SameVariadicOperandSize), got "
1930 << getNumDpsInputs() << " input(s) and " << getNumDpsInits()
1931 << " output(s)";
1932
1933 if (getInputs().empty())
1934 return emitOpError() << "expected at least one input";
1935
1936 for (int64_t i = 1; i < getNumDpsInputs(); ++i) {
1937 if (llvm::cast<ShapedType>(getInputs()[i].getType()).getShape() !=
1938 llvm::cast<ShapedType>(getInputs()[0].getType()).getShape()) {
1939 return emitOpError() << "expects all inputs to have the same shapes. "
1940 "Shape at input-index "
1941 << i
1942 << " is not equal to the shape at input-index 0.";
1943 }
1944 }
1945 for (int64_t i = 1; i < getNumDpsInits(); ++i) {
1946 if (llvm::cast<ShapedType>(getInits()[i].getType()).getShape() !=
1947 llvm::cast<ShapedType>(getInits()[0].getType()).getShape()) {
1948 return emitOpError() << "expects all outputs to have the same shapes. "
1949 "Shape at output-index "
1950 << i
1951 << " is not equal to the shape at output-index 0.";
1952 }
1953 }
1954 auto inputType = llvm::cast<ShapedType>(getInputs()[0].getType());
1955 auto initType = llvm::cast<ShapedType>(getInits()[0].getType());
1956
1957 DenseSet<int64_t> dimensionsToReduce;
1958 for (int64_t dimension : dimensionsRef) {
1959 if (dimension < 0 || dimension >= inputType.getRank()) {
1960 return emitOpError()
1961 << "dimensions for reduction should be in the range [0, "
1962 << inputType.getRank() - 1 << "].";
1963 }
1964 dimensionsToReduce.insert(dimension);
1965 }
1966
1967 auto inputDims = inputType.getShape();
1968 auto initDims = initType.getShape();
1969
1970 // Input dimensions that will be left after the reduction.
1971 SmallVector<int64_t> reducedInputDims;
1972 for (const auto &en : llvm::enumerate(inputDims)) {
1973 if (!dimensionsToReduce.count(en.index()))
1974 reducedInputDims.push_back(en.value());
1975 }
1976
1977 if (reducedInputDims.size() != static_cast<size_t>(initType.getRank())) {
1978 return emitOpError() << "number of dimensions after reduction "
1979 << reducedInputDims.size()
1980 << " doesn't match the init rank "
1981 << initType.getRank();
1982 }
1983
1984 if (reducedInputDims != initDims)
1985 return emitOpError() << "init dimensions [" << initDims
1986 << "] doesn't match input dimensions after reduction ["
1987 << reducedInputDims << "]";
1988
1989 Block *block = getBody();
1990 if (block->getNumArguments() != this->getNumOperands())
1991 return emitOpError()
1992 << "mismatching number of operands and block arguments";
1993
1994 // Check that the first block arguments match the element type of the inputs.
1995 for (auto [input, bbArg] : llvm::zip(getInputs(), block->getArguments())) {
1996 Type inputElementType =
1997 llvm::cast<ShapedType>(input.getType()).getElementType();
1998 if (inputElementType != bbArg.getType())
1999 return emitOpError()
2000 << "input element type " << inputElementType
2001 << " does not match corresponding block argument type "
2002 << bbArg.getType();
2003 }
2004
2005 // Check that the last block arguments match the element type of the outputs.
2006 for (auto [output, bbArg] : llvm::zip(
2007 getDpsInits(), block->getArguments().take_back(getNumDpsInits()))) {
2008 auto outputElementType =
2009 llvm::cast<ShapedType>(output.getType()).getElementType();
2010 if (outputElementType != bbArg.getType())
2011 return emitOpError()
2012 << "output element type " << outputElementType
2013 << " does not match corresponding block argument type "
2014 << bbArg.getType();
2015 }
2016 return success();
2017}
2018
2019//===----------------------------------------------------------------------===//
2020// TransposeOp
2021//===----------------------------------------------------------------------===//
2022
2023static void buildIdentityRegion(OpBuilder &builder, Location loc,
2024 Region &region, ValueRange inputs,
2025 ValueRange outputs) {
2026 buildGenericRegion(builder, loc, region, inputs, outputs,
2027 [](OpBuilder &b, Location loc, ValueRange args) {
2028 if (!args.empty())
2029 linalg::YieldOp::create(b, loc, args[0]);
2030 });
2031}
2032
2033void TransposeOp::build(::mlir::OpBuilder &builder,
2034 ::mlir::OperationState &result, Value input, Value init,
2035 DenseI64ArrayAttr permutation,
2036 ArrayRef<NamedAttribute> attributes) {
2037 result.addOperands(input);
2038 result.addOperands(init);
2039 result.addAttribute(getPermutationAttrName(result.name), permutation);
2040 result.addAttributes(attributes);
2041
2042 // Add output types for `RankedTensorType` output arguments.
2043 Type initType = init.getType();
2044 if (llvm::isa<RankedTensorType>(initType))
2045 result.addTypes(initType);
2046
2047 buildIdentityRegion(builder, result.location, *result.addRegion(), input,
2048 init);
2049}
2050
2051void TransposeOp::build(::mlir::OpBuilder &builder,
2052 ::mlir::OperationState &result, Value input, Value init,
2053 ArrayRef<int64_t> permutation,
2054 ArrayRef<NamedAttribute> attributes) {
2055 build(builder, result, input, init, builder.getDenseI64ArrayAttr(permutation),
2056 attributes);
2057}
2058
2059ParseResult TransposeOp::parse(OpAsmParser &parser, OperationState &result) {
2061 parser, result, [&](OpAsmParser &parser, NamedAttrList &attributes) {
2062 return parseDenseI64ArrayAttr(parser, attributes, "permutation");
2063 })))
2064 return failure();
2065
2066 OpBuilder builder(parser.getContext());
2067 buildIdentityRegion(builder, result.location, *result.addRegion(),
2068 /*inputs=*/result.operands,
2069 /*outputs=*/{});
2070 return success();
2071}
2072
2073void TransposeOp::getAsmResultNames(
2074 function_ref<void(Value, StringRef)> setNameFn) {
2075 if (!getResults().empty())
2076 setNameFn(getResults().front(), "transposed");
2077}
2078
2079void TransposeOp::print(OpAsmPrinter &p) {
2080 printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
2081 printDenseI64ArrayAttr(p, getPermutationAttrName(), getPermutation());
2082 p.printOptionalAttrDict((*this)->getAttrs(), {getPermutationAttrName()});
2083}
2084
2085LogicalResult TransposeOp::verify() {
2086 ArrayRef<int64_t> permutationRef = getPermutation();
2087
2088 if (!isPermutationVector(permutationRef))
2089 return emitOpError("permutation is not valid");
2090
2091 auto inputType = getInput().getType();
2092 auto initType = getInit().getType();
2093
2094 int64_t rank = inputType.getRank();
2095
2096 if (failed(verifyRanksMatch(getOperation(), inputType, initType, "input",
2097 "init")))
2098 return failure();
2099
2100 if (rank != static_cast<int64_t>(permutationRef.size()))
2101 return emitOpError() << "size of permutation " << permutationRef.size()
2102 << " does not match the argument rank " << rank;
2103
2104 auto inputDims = inputType.getShape();
2105 auto initDims = initType.getShape();
2106
2107 for (int64_t i = 0; i < rank; ++i) {
2108 int64_t inputDim = inputDims[permutationRef[i]];
2109 int64_t initDim = initDims[i];
2110
2111 if (inputDim != initDim) {
2112 return emitOpError() << "dim(result, " << i << ") = " << initDim
2113 << " doesn't match dim(input, permutation[" << i
2114 << "]) = " << inputDim;
2115 }
2116 }
2117
2118 return success();
2119}
2120
2121SmallVector<utils::IteratorType> TransposeOp::getIteratorTypesArray() {
2122 int64_t rank = getInit().getType().getRank();
2123 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
2124}
2125
2126ArrayAttr TransposeOp::getIndexingMaps() {
2127 Builder builder(getContext());
2128 int64_t rank = getInit().getType().getRank();
2129 return builder.getAffineMapArrayAttr(
2131 llvm::to_vector_of<unsigned>(getPermutation()), getContext())),
2132 builder.getMultiDimIdentityMap(rank)});
2133}
2134
2135void TransposeOp::getEffects(
2136 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
2137 &effects) {
2138 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
2139}
2140
2141Speculation::Speculatability TransposeOp::getSpeculatability() {
2142 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
2143}
2144
2145LogicalResult TransposeOp::fold(FoldAdaptor adaptor,
2146 SmallVectorImpl<OpFoldResult> &result) {
2147 // Only the tensor type is supported.
2148 if (!isa<TensorType>(getInput().getType()))
2149 return failure();
2150
2151 // Single dimension transpose.
2152 if (getPermutation().empty()) {
2153 result.push_back(getInput());
2154 return success();
2155 }
2156 // Identity permutation.
2157 if (isIdentityPermutation(getPermutation())) {
2158 result.push_back(getInput());
2159 return success();
2160 }
2161
2162 return failure();
2163}
2164
2165/// Fold transpose with transpose.
2166struct FoldTransposeWithTranspose : OpRewritePattern<linalg::TransposeOp> {
2167 using OpRewritePattern<linalg::TransposeOp>::OpRewritePattern;
2168
2169 LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp,
2170 PatternRewriter &rewriter) const override {
2171 auto defTransposeOp = transposeOp.getInput().getDefiningOp<TransposeOp>();
2172 if (!defTransposeOp)
2173 return failure();
2174 ArrayRef<int64_t> defPerms = defTransposeOp.getPermutation();
2175 ArrayRef<int64_t> perms = transposeOp.getPermutation();
2176 SmallVector<int64_t> foldedPerms;
2177 foldedPerms.reserve(perms.size());
2178 for (int64_t perm : perms)
2179 foldedPerms.push_back(defPerms[perm]);
2180
2181 rewriter.replaceOpWithNewOp<TransposeOp>(
2182 transposeOp, defTransposeOp.getInput(), transposeOp.getInit(),
2183 foldedPerms);
2184 return success();
2185 }
2186};
2187
2188/// Rewrite a transpose of a dense splat constant into a dense splat constant of
2189/// the transposed output shape.
2190struct FoldTransposeSplatConstant : OpRewritePattern<linalg::TransposeOp> {
2191 using OpRewritePattern<linalg::TransposeOp>::OpRewritePattern;
2192
2193 LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp,
2194 PatternRewriter &rewriter) const override {
2195 if (!transposeOp.hasPureTensorSemantics())
2196 return failure();
2197
2198 auto splatValue =
2199 getScalarConstantAttrFromDenseSplat(transposeOp.getInput());
2200 if (!splatValue.has_value())
2201 return failure();
2202
2203 auto resultType =
2204 cast<RankedTensorType>(transposeOp.getResult()[0].getType());
2205
2206 auto resultAttr = DenseElementsAttr::get(resultType, splatValue.value());
2207 rewriter.replaceOpWithNewOp<arith::ConstantOp>(transposeOp, resultType,
2208 resultAttr);
2209 return success();
2210 }
2211};
2212
2213/// This pattern canonicalize transpose by swapping the order of
2214/// broadcast and transpose:
2215/// transpose(broadcast(input)) -> broadcast(transpose(input))
2216struct SwapTransposeWithBroadcast : OpRewritePattern<linalg::TransposeOp> {
2217 using OpRewritePattern<linalg::TransposeOp>::OpRewritePattern;
2218
2219 LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp,
2220 PatternRewriter &rewriter) const override {
2221 Value input = transposeOp.getInput();
2222 BroadcastOp broadcastOp = input.getDefiningOp<BroadcastOp>();
2223 if (!input.hasOneUse() || !broadcastOp)
2224 return failure();
2225
2226 ArrayRef<int64_t> dimensions = broadcastOp.getDimensions();
2227 ArrayRef<int64_t> perms = transposeOp.getPermutation();
2228
2229 // Get new perms and new dimensions.
2230 SmallVector<int64_t> resultPerms = dropDims(perms, dimensions);
2232 SmallVector<int64_t> resultDimensions;
2233 unsigned dimensionSize = dimensions.size();
2234 for (unsigned i = 0; i < dimensionSize; ++i)
2235 resultDimensions.push_back(invertPerm[dimensions[i]]);
2236
2237 // Create transpose result.
2238 Value broadcastInput = broadcastOp.getInput();
2239 Location loc = transposeOp.getLoc();
2240 MLIRContext *ctx = transposeOp.getContext();
2242 auto broadcastInputTy =
2243 mlir::cast<RankedTensorType>(broadcastInput.getType());
2244 unsigned inputRank = broadcastInputTy.getRank();
2245 for (unsigned i = 0; i < inputRank; ++i) {
2246 if (broadcastInputTy.isDynamicDim(i)) {
2247 dims.push_back(tensor::DimOp::create(rewriter, loc, broadcastInput, i)
2248 ->getResult(0));
2249 } else {
2250 dims.push_back(IntegerAttr::get(IndexType::get(ctx),
2251 broadcastInputTy.getDimSize(i)));
2252 }
2253 }
2254 SmallVector<OpFoldResult> transposeResultShapes =
2255 applyPermutation(dims, resultPerms);
2256 Value transposeInit = tensor::EmptyOp::create(
2257 rewriter, transposeOp.getLoc(), transposeResultShapes,
2258 broadcastInputTy.getElementType());
2259
2260 // Create broadcast(transpose(input)).
2261 Value transposeResult =
2262 TransposeOp::create(rewriter, loc, broadcastOp.getInput(),
2263 transposeInit, resultPerms)
2264 ->getResult(0);
2265 rewriter.replaceOpWithNewOp<BroadcastOp>(
2266 transposeOp, transposeResult, transposeOp.getInit(), resultDimensions);
2267 return success();
2268 }
2269};
2270
2271void TransposeOp::getCanonicalizationPatterns(RewritePatternSet &results,
2272 MLIRContext *context) {
2273 results.add<FoldTransposeWithTranspose, FoldTransposeSplatConstant,
2274 SwapTransposeWithBroadcast>(context);
2275}
2276
2277//===----------------------------------------------------------------------===//
2278// BroadcastOp
2279//===----------------------------------------------------------------------===//
2280
2281void BroadcastOp::build(::mlir::OpBuilder &builder,
2282 ::mlir::OperationState &result, Value input, Value init,
2283 DenseI64ArrayAttr dimensions,
2284 ArrayRef<NamedAttribute> attributes) {
2285 result.addOperands(input);
2286 result.addOperands(init);
2287 result.addAttribute(getDimensionsAttrName(result.name), dimensions);
2288 result.addAttributes(attributes);
2289
2290 // Add output types for `RankedTensorType` output arguments.
2291 Type initType = init.getType();
2292 if (llvm::isa<RankedTensorType>(initType))
2293 result.addTypes(initType);
2294
2295 buildIdentityRegion(builder, result.location, *result.addRegion(), input,
2296 init);
2297}
2298
2299void BroadcastOp::build(::mlir::OpBuilder &builder,
2300 ::mlir::OperationState &result, Value input, Value init,
2301 ArrayRef<int64_t> dimensions,
2302 ArrayRef<NamedAttribute> attributes) {
2303 build(builder, result, input, init, builder.getDenseI64ArrayAttr(dimensions),
2304 attributes);
2305}
2306
2307ParseResult BroadcastOp::parse(OpAsmParser &parser, OperationState &result) {
2309 parser, result, [&](OpAsmParser &parser, NamedAttrList &attributes) {
2310 return parseDenseI64ArrayAttr(parser, attributes, "dimensions");
2311 })))
2312 return failure();
2313
2314 OpBuilder builder(parser.getContext());
2315 buildIdentityRegion(builder, result.location, *result.addRegion(),
2316 /*inputs=*/result.operands,
2317 /*outputs=*/{});
2318 return success();
2319}
2320
2321void BroadcastOp::getAsmResultNames(
2322 function_ref<void(Value, StringRef)> setNameFn) {
2323 if (!getResults().empty())
2324 setNameFn(getResults().front(), "broadcasted");
2325}
2326
2327void BroadcastOp::print(OpAsmPrinter &p) {
2328 printCommonStructuredOpParts(p, getDpsInputs(), getDpsInits());
2329 printDenseI64ArrayAttr(p, getDimensionsAttrName(), getDimensions());
2330 p.printOptionalAttrDict((*this)->getAttrs(), {getDimensionsAttrName()});
2331}
2332
2333LogicalResult BroadcastOp::verify() {
2334 ArrayRef<int64_t> dimensionsRef = getDimensions();
2335
2336 auto inputType = getInput().getType();
2337 auto initType = getInit().getType();
2338
2339 int64_t inputRank = inputType.getRank();
2340 int64_t initRank = initType.getRank();
2341
2342 auto inputShape = inputType.getShape();
2343 auto initShape = initType.getShape();
2344
2345 if ((size_t)inputRank + dimensionsRef.size() != (size_t)initRank)
2346 return emitOpError() << "input rank plus added dimensions does not "
2347 "match init rank. input rank: "
2348 << inputRank
2349 << ", dimensions size: " << dimensionsRef.size()
2350 << ", init rank: " << initRank;
2351
2352 for (const auto &[idx, dim] : llvm::enumerate(dimensionsRef)) {
2353 if (dim < 0 || dim >= initRank)
2354 return emitOpError() << "dimension " << idx
2355 << " is out of range. expected range: [0, "
2356 << initRank - 1 << "], got: " << dim;
2357 }
2358
2359 // Mapping from input dims to init dims.
2360 SmallVector<int64_t> dimMap;
2361 for (auto dim : llvm::seq<int64_t>(0, initRank)) {
2362 if (!llvm::is_contained(dimensionsRef, dim))
2363 dimMap.push_back(dim);
2364 }
2365
2366 for (const auto &[inputDimIdx, initDimIdx] : llvm::enumerate(dimMap)) {
2367 // This dimensions is mapped from the input. Init and input dims should
2368 // match.
2369 if (inputShape[inputDimIdx] != initShape[initDimIdx])
2370 return emitOpError() << "input dim " << inputDimIdx
2371 << " should match init dim " << initDimIdx
2372 << ". input: " << inputShape[inputDimIdx]
2373 << ", init: " << initShape[initDimIdx];
2374 }
2375
2376 return success();
2377}
2378
2379SmallVector<utils::IteratorType> BroadcastOp::getIteratorTypesArray() {
2380 int64_t rank = getInit().getType().getRank();
2381 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
2382}
2383
2384ArrayAttr BroadcastOp::getIndexingMaps() {
2385 Builder builder(getContext());
2386 int64_t rank = getInit().getType().getRank();
2387 return builder.getAffineMapArrayAttr(
2388 {builder.getMultiDimIdentityMap(rank).dropResults(getDimensions()),
2389 builder.getMultiDimIdentityMap(rank)});
2390}
2391
2392void BroadcastOp::getEffects(
2393 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
2394 &effects) {
2395 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
2396}
2397
2398Speculation::Speculatability BroadcastOp::getSpeculatability() {
2399 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
2400}
2401
2402/// Fold back-to-back broadcasts together.
2403struct FoldBroadcasts : OpRewritePattern<linalg::BroadcastOp> {
2404 using OpRewritePattern<linalg::BroadcastOp>::OpRewritePattern;
2405
2406 LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp,
2407 PatternRewriter &rewriter) const override {
2408 auto defBroadcastOp = broadcastOp.getInput().getDefiningOp<BroadcastOp>();
2409 if (!defBroadcastOp)
2410 return failure();
2411 ArrayRef<int64_t> defDimensions = defBroadcastOp.getDimensions();
2412 ArrayRef<int64_t> dimensions = broadcastOp.getDimensions();
2413 SmallVector<int64_t> foldedDims(dimensions);
2414 Value init = broadcastOp.getInit();
2415 int64_t initRank = cast<ShapedType>(init.getType()).getRank();
2416 // Mapping from input dims to init dims.
2417 SmallVector<int64_t> dimMap;
2418 for (auto dim : llvm::seq<int64_t>(0, initRank)) {
2419 if (!llvm::is_contained(dimensions, dim))
2420 dimMap.push_back(dim);
2421 }
2422 for (auto dim : defDimensions)
2423 foldedDims.push_back(dimMap[dim]);
2424
2425 llvm::sort(foldedDims);
2426 rewriter.replaceOpWithNewOp<BroadcastOp>(
2427 broadcastOp, defBroadcastOp.getInput(), init, foldedDims);
2428 return success();
2429 }
2430};
2431
2432/// Rewrite a broadcast of a dense splat constant into a dense splat constant of
2433/// the broadcast output shape.
2434struct FoldBroadcastSplatConstant : OpRewritePattern<linalg::BroadcastOp> {
2435 using OpRewritePattern<linalg::BroadcastOp>::OpRewritePattern;
2436
2437 LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp,
2438 PatternRewriter &rewriter) const override {
2439 if (!broadcastOp.hasPureTensorSemantics())
2440 return failure();
2441
2442 auto splatValue =
2443 getScalarConstantAttrFromDenseSplat(broadcastOp.getInput());
2444
2445 if (!splatValue.has_value())
2446 return failure();
2447
2448 auto resultType =
2449 cast<RankedTensorType>(broadcastOp.getResult()[0].getType());
2450 if (!resultType.hasStaticShape())
2451 return rewriter.notifyMatchFailure(broadcastOp,
2452 "result type has dynamic shape");
2453
2454 auto resultAttr = DenseElementsAttr::get(resultType, splatValue.value());
2455 rewriter.replaceOpWithNewOp<arith::ConstantOp>(broadcastOp, resultType,
2456 resultAttr);
2457 return success();
2458 }
2459};
2460
2461void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
2462 MLIRContext *context) {
2463 results.add<EraseIdentityLinalgOp<BroadcastOp>, FoldBroadcasts,
2464 FoldBroadcastSplatConstant>(context);
2465}
2466
2467//===----------------------------------------------------------------------===//
2468// YieldOp
2469//===----------------------------------------------------------------------===//
2470
2471void linalg::YieldOp::print(OpAsmPrinter &p) {
2472 if (getNumOperands() > 0)
2473 p << ' ' << getOperands();
2474 p.printOptionalAttrDict((*this)->getAttrs());
2475 if (getNumOperands() > 0)
2476 p << " : " << getOperandTypes();
2477}
2478
2479ParseResult YieldOp::parse(OpAsmParser &parser, OperationState &result) {
2480 SmallVector<OpAsmParser::UnresolvedOperand, 2> opInfo;
2481 SmallVector<Type, 2> types;
2482 SMLoc loc = parser.getCurrentLocation();
2483 return failure(parser.parseOperandList(opInfo) ||
2484 parser.parseOptionalAttrDict(result.attributes) ||
2485 (!opInfo.empty() && parser.parseColonTypeList(types)) ||
2486 parser.resolveOperands(opInfo, types, loc, result.operands));
2487}
2488
2489// Check the operand number and types must match the element types of the
2490// LinalgOp interface's shaped operands.
2491static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) {
2492 if (op.getNumOperands() != linalgOp.getNumDpsInits())
2493 return op.emitOpError("expected number of yield values (")
2494 << op.getNumOperands()
2495 << ") to match the number of inits / outs operands of the enclosing "
2496 << "LinalgOp (" << linalgOp.getNumDpsInits() << ")";
2497
2498 for (OpOperand &opOperand : op->getOpOperands()) {
2499 OpOperand *outputOperand =
2500 linalgOp.getDpsInitOperand(opOperand.getOperandNumber());
2501 Type elementType = outputOperand->get().getType();
2502 if (isa<MemRefType, RankedTensorType>(elementType))
2503 elementType = getElementTypeOrSelf(outputOperand->get().getType());
2504 if (opOperand.get().getType() != elementType)
2505 return op.emitOpError("type of yield operand ")
2506 << (opOperand.getOperandNumber() + 1) << " ("
2507 << opOperand.get().getType() << ") doesn't match "
2508 << "the element type of the enclosing linalg.generic op ("
2509 << elementType << ")";
2510 }
2511 return success();
2512}
2513
2514LogicalResult linalg::YieldOp::verify() {
2515 auto *parentOp = (*this)->getParentOp();
2516 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty())
2517 return emitOpError("expected single non-empty parent region");
2518
2519 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp))
2520 return verifyYield(*this, linalgOp);
2521
2522 return emitOpError("expected parent op with LinalgOp interface");
2523}
2524
2525//===----------------------------------------------------------------------===//
2526// IndexOp
2527//===----------------------------------------------------------------------===//
2528
2529LogicalResult IndexOp::verify() {
2530 auto linalgOp = dyn_cast<LinalgOp>((*this)->getParentOp());
2531 if (!linalgOp)
2532 return emitOpError("expected parent op with LinalgOp interface");
2533 if (linalgOp.getNumLoops() <= getDim())
2534 return emitOpError("expected dim (")
2535 << getDim() << ") to be lower than the number of loops ("
2536 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp";
2537 return success();
2538}
2539
2540OpFoldResult IndexOp::fold(FoldAdaptor adaptor) {
2541 auto linalgOp = dyn_cast_or_null<LinalgOp>((*this)->getParentOp());
2542 // Bail out if `linalg.index` does not have a proper parent yet at this
2543 // point, e.g., when calling `createOrFold` during IR construction in
2544 // `genericOp::build`.
2545 if (!linalgOp)
2546 return OpFoldResult{};
2547
2548 // Index of unit dims is always 0.
2549 SmallVector<int64_t, 4> loopBounds = linalgOp.getStaticLoopRanges();
2550 uint64_t dim = getDim();
2551 assert(dim < loopBounds.size() && "Dim is out of bounds");
2552 if (loopBounds[dim] == 1)
2553 return IntegerAttr::get(IndexType::get(getContext()), 0);
2554
2555 return OpFoldResult{};
2556}
2557
2558/////// Operations corresponding to library calls defined with Tablegen ////////
2559
2560#include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc"
2561
2562#define GET_OP_CLASSES
2563#include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc"
2564
2565#define GET_OP_CLASSES
2566#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2567#define GET_OP_CLASSES
2568#include "mlir/Dialect/Linalg/IR/LinalgRelayoutOps.cpp.inc"
2569
2570AffineMap mlir::linalg::extractOrIdentityMap(std::optional<AffineMap> maybeMap,
2571 unsigned rank,
2572 MLIRContext *context) {
2573 if (maybeMap)
2574 return *maybeMap;
2575 if (rank == 0)
2576 return AffineMap::get(context);
2577 return AffineMap::getMultiDimIdentityMap(rank, context);
2578}
2579
2581mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx,
2582 MLIRContext *context) {
2584 res.reserve(num);
2585 for (unsigned i = 0; i < num; ++i)
2586 res.push_back(getAffineDimExpr(startIdx++, context));
2587 return res;
2588}
2589
2592 auto rangeA = llvm::make_range(a.begin(), a.end());
2593 auto rangeB = llvm::make_range(b.begin(), b.end());
2594 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB);
2595 return llvm::to_vector<4>(concatRanges);
2596}
2597
2598static LogicalResult appendMangledType(llvm::raw_string_ostream &ss, Type t) {
2599 if (auto memref = llvm::dyn_cast<MemRefType>(t)) {
2600 ss << "view";
2601 for (auto size : memref.getShape())
2602 if (size < 0)
2603 ss << "sx";
2604 else
2605 ss << size << "x";
2606 if (failed(appendMangledType(ss, memref.getElementType())))
2607 return failure();
2608 if (auto as = memref.getMemorySpace()) {
2609 if (auto attr = llvm::dyn_cast<IntegerAttr>(as))
2610 ss << "as" << attr.getInt();
2611 else
2612 return failure();
2613 }
2614 return success();
2615 }
2616 if (auto vec = llvm::dyn_cast<VectorType>(t)) {
2617 ss << "vector";
2618 llvm::interleave(
2619 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; });
2620 if (failed(appendMangledType(ss, vec.getElementType())))
2621 return failure();
2622 return success();
2623 }
2625 ss << t;
2626 return success();
2627 }
2628 return failure();
2629}
2630
2632 assert(isa<LinalgOp>(op));
2633 std::string name(op->getName().getStringRef().str());
2634 std::string fun = "";
2635 for (NamedAttribute kv : op->getAttrs()) {
2636 if (UnaryFnAttr ufa = llvm::dyn_cast<UnaryFnAttr>(kv.getValue())) {
2637 fun = stringifyEnum(ufa.getValue()).str() + "_";
2638 } else if (BinaryFnAttr bfa = llvm::dyn_cast<BinaryFnAttr>(kv.getValue())) {
2639 fun = stringifyEnum(bfa.getValue()).str() + "_";
2640 }
2641 }
2642 name.reserve(128);
2643 llvm::replace(name, '.', '_');
2644 llvm::raw_string_ostream ss(name);
2645 ss << "_" << fun;
2646 for (Type t : op->getOperandTypes()) {
2647 if (failed(appendMangledType(ss, t)))
2648 return std::string();
2649 ss << "_";
2650 }
2651 name.pop_back();
2652 return name;
2653}
2654
2655//===----------------------------------------------------------------------===//
2656// Canonicalizers and Folders.
2657//===----------------------------------------------------------------------===//
2658
2659namespace {
2660struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> {
2662
2663 LogicalResult matchAndRewrite(LinalgOp op,
2664 PatternRewriter &rewriter) const override {
2665 for (OpOperand &opOperand : op->getOpOperands()) {
2666 // Linalg "inputs" may be either tensor or memref type.
2667 // tensor<0xelt_type> is a convention that may not always mean
2668 // "0 iterations". Only erase in cases we see memref<...x0x...>.
2669 auto mt = llvm::dyn_cast<MemRefType>(opOperand.get().getType());
2670 if (!mt)
2671 continue;
2672 if (llvm::is_contained(op.getShape(&opOperand), 0)) {
2673 rewriter.eraseOp(op);
2674 return success();
2675 }
2676 }
2677 return failure();
2678 }
2679};
2680
2681/// Fold LinalgOps with `tensor.cast` consumer if the `tensor.cast` has
2682/// result that is more static than the linalg op.
2683struct FoldTensorCastConsumerOp : public OpRewritePattern<tensor::CastOp> {
2684 using OpRewritePattern<tensor::CastOp>::OpRewritePattern;
2685
2686 LogicalResult matchAndRewrite(tensor::CastOp castOp,
2687 PatternRewriter &rewriter) const override {
2688 if (!tensor::canFoldIntoProducerOp(castOp))
2689 return failure();
2690
2691 auto linalgOp = castOp.getSource().getDefiningOp<LinalgOp>();
2692 if (!linalgOp)
2693 return failure();
2694
2695 // Cast can be in conditionally reachable region, if which case folding will
2696 // generate invalid code. Only conservatively fold ops in same block for
2697 // now.
2698 if (castOp->getBlock() != linalgOp->getBlock())
2699 return failure();
2700
2701 OpBuilder::InsertionGuard guard(rewriter);
2702 rewriter.setInsertionPoint(linalgOp);
2703
2704 Location loc = linalgOp.getLoc();
2705 OpResult resultValue = llvm::cast<OpResult>(castOp.getSource());
2706 unsigned resultNumber = resultValue.getResultNumber();
2707 auto resultType =
2708 llvm::cast<RankedTensorType>(castOp->getResult(0).getType());
2709 // Replace the `outs` for the result with a `tensor.cast`. This cast is now
2710 // going from a more dynamic shape to a less dynamic shape. If the producer
2711 // for this cast, i.e. producer of the out operand, is also an operation
2712 // that folds with tensor.cast consumer (like this pattern), the cast will
2713 // continue to propagate as far up the stack as it can go.
2714 OpOperand *outOperand = linalgOp.getDpsInitOperand(resultNumber);
2715 Value newOperand =
2716 tensor::CastOp::create(rewriter, loc, resultType, outOperand->get());
2717 SmallVector<Value> newOperands = linalgOp.getDpsInputs();
2718 SmallVector<Value> outputOperands(linalgOp.getDpsInits().begin(),
2719 linalgOp.getDpsInits().end());
2720 outputOperands[resultNumber] = newOperand;
2721 newOperands.append(outputOperands.begin(), outputOperands.end());
2722
2723 SmallVector<Type> resultTypes(linalgOp->result_type_begin(),
2724 linalgOp->result_type_end());
2725 resultTypes[resultNumber] = resultType;
2726 Operation *newOp = clone(rewriter, linalgOp, resultTypes, newOperands);
2727
2728 // Create a tensor.cast operation back to the original type.
2729 Value castBack = tensor::CastOp::create(
2730 rewriter, loc, resultValue.getType(), newOp->getResult(resultNumber));
2731
2732 SmallVector<Value> results(newOp->result_begin(), newOp->result_end());
2733 results[resultNumber] = castBack;
2734 rewriter.replaceOp(linalgOp, results);
2735 rewriter.replaceOp(castOp, newOp->getResult(resultNumber));
2736 return success();
2737 }
2738};
2739
2740/// For each of the operand in `operands` this function maps the static sizes of
2741/// dimensions to their affine dim expressions.
2742static void populateMap(LinalgOp linalgOp, MutableArrayRef<OpOperand> operands,
2743 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize) {
2744 for (OpOperand &opOperand : operands) {
2745 if (linalgOp.isScalar(&opOperand))
2746 continue;
2747 Value src = opOperand.get();
2748 auto sourceType = llvm::cast<RankedTensorType>(src.getType());
2749 auto sourceMap = linalgOp.getMatchingIndexingMap(&opOperand);
2750
2751 // Get the `sourceShape` of the `sourceType`. If the operand is a result of
2752 // `tensor.cast` operation and source of the cast operation has a static
2753 // shape, then assign it to the `sourceShape`.
2754 auto *parentOp = src.getDefiningOp();
2755 ArrayRef<int64_t> sourceShape = sourceType.getShape();
2756 if (parentOp) {
2757 if (auto castOp = dyn_cast<tensor::CastOp>(parentOp)) {
2758 Value castSource = castOp.getSource();
2759 auto castSourceType =
2760 llvm::dyn_cast<RankedTensorType>(castSource.getType());
2761 if (castSourceType && castSourceType.hasStaticShape())
2762 sourceShape = castSourceType.getShape();
2763 }
2764 }
2765
2766 // If the source shape's dimension has a static shape, map the affine dim
2767 // expression to the known static size.
2768 for (unsigned i = 0; i < sourceShape.size(); i++) {
2769 if (sourceType.isDynamicDim(i))
2770 continue;
2771 if (auto affineDimExpr = dyn_cast<AffineDimExpr>(sourceMap.getResult(i)))
2772 affineExprToSize.try_emplace(affineDimExpr, sourceShape[i]);
2773 }
2774 }
2775}
2776
2777/// Creates new operand w.r.t 'opOperand' of `linalgOp` with static sizes
2778/// mapped in `affineExprToSize`. New operands are created in `newOperands` and
2779/// their result types is stored in `resultTypes`. If `opOperand` requires no
2780/// change then `changeNeeded` is false and same operand is added in the
2781/// `newOperands` list.
2782static void createNewOperandWithStaticSizes(
2783 Location loc, PatternRewriter &rewriter, OpOperand *opOperand,
2784 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize, LinalgOp linalgOp,
2785 SmallVector<Value> &newOperands, SmallVector<Type> &resultTypes,
2786 bool &changeNeeded) {
2787 Value src = opOperand->get();
2788 newOperands.push_back(src);
2789 if (linalgOp.isScalar(opOperand))
2790 return;
2791 auto sourceType = llvm::cast<RankedTensorType>(src.getType());
2792 Type resultType = sourceType;
2793 if (sourceType.hasStaticShape() && linalgOp.isDpsInit(opOperand)) {
2794 resultTypes.push_back(resultType);
2795 return;
2796 }
2797 ArrayRef<int64_t> sourceShape = sourceType.getShape();
2798 AffineMap sourceMap = linalgOp.getMatchingIndexingMap(opOperand);
2799 SmallVector<int64_t> newShape;
2800 // If operand is updated with new shape, `newOperandNeeded` will be
2801 // true.
2802 bool newOperandNeeded = false;
2803 for (unsigned i = 0; i < sourceShape.size(); i++) {
2804 int64_t dimShape = sourceShape[i];
2805 AffineExpr dimExpr = sourceMap.getResult(i);
2806 if (!affineExprToSize.contains(dimExpr) || !sourceType.isDynamicDim(i)) {
2807 newShape.push_back(dimShape);
2808 continue;
2809 }
2810 // Dimension has a dynamic shape and corresponding affine dim
2811 // expression is present in the map. So assign the size for the
2812 // given affine dim expression to the dimension.
2813 newShape.push_back(affineExprToSize[dimExpr]);
2814 newOperandNeeded = true;
2815 }
2816 resultType = RankedTensorType::get(newShape, sourceType.getElementType(),
2817 sourceType.getEncoding());
2818 if (newOperandNeeded) {
2819 changeNeeded = true;
2820 // Get the new operand value given its size and element type by
2821 // casting it.
2822 Value newOperand = tensor::CastOp::create(rewriter, loc, resultType, src);
2823 unsigned index = opOperand->getOperandNumber();
2824 newOperands[index] = newOperand;
2825 }
2826 if (linalgOp.isDpsInit(opOperand))
2827 resultTypes.push_back(resultType);
2828}
2829
2830/// Static shapes for the operands can be inferred if any one of the operands
2831/// have a static shape. This can be done by referring to the affine dim
2832/// expressions for the operand.
2833struct InferStaticShapeOfOperands : public OpInterfaceRewritePattern<LinalgOp> {
2834 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern;
2835
2836 LogicalResult matchAndRewrite(LinalgOp linalgOp,
2837 PatternRewriter &rewriter) const override {
2838 if (!linalgOp.hasPureTensorSemantics())
2839 return failure();
2840
2841 // Maps must be projected permutations.
2842 if (llvm::any_of(linalgOp.getIndexingMapsArray(), [](AffineMap map) {
2843 return !map.isProjectedPermutation();
2844 }))
2845 return failure();
2846
2847 // Maps affine dim expressions to the static size of that dimension.
2848 llvm::DenseMap<AffineExpr, int64_t> affineExprToSize;
2849 Location loc = linalgOp.getLoc();
2850
2851 // For each of the affine dim expression, check if the size is known. If
2852 // known add that in the map.
2853 populateMap(linalgOp, linalgOp->getOpOperands(), affineExprToSize);
2854
2855 SmallVector<Value> newOperands;
2856 SmallVector<Type> resultTypes;
2857
2858 // `changeNeeded` is `false` if the operands of `linalgOp` require no
2859 // change in their types.
2860 bool changeNeeded = false;
2861 newOperands.reserve(linalgOp->getNumOperands());
2862 resultTypes.reserve(linalgOp.getNumDpsInits());
2863
2864 // Iterate over all the operands and update the static sizes.
2865 for (OpOperand &opOperand : linalgOp->getOpOperands()) {
2866 createNewOperandWithStaticSizes(loc, rewriter, &opOperand,
2867 affineExprToSize, linalgOp, newOperands,
2868 resultTypes, changeNeeded);
2869 }
2870
2871 // If the generic op has all the required static information, no
2872 // canonicalization needed.
2873 if (!changeNeeded)
2874 return failure();
2875
2876 // Clone op.
2877 Operation *newOp = clone(rewriter, linalgOp, resultTypes, newOperands);
2878 SmallVector<Value> replacements;
2879 replacements.reserve(newOp->getNumResults());
2880 for (auto it : llvm::zip(linalgOp->getResults(), newOp->getResults())) {
2881 Value newResult = std::get<1>(it);
2882 Value oldResult = std::get<0>(it);
2883 Type newType = newResult.getType();
2884 Type oldType = oldResult.getType();
2885 replacements.push_back(
2886 (newType != oldType)
2887 ? tensor::CastOp::create(rewriter, loc, oldType, newResult)
2888 : newResult);
2889 }
2890 rewriter.replaceOp(linalgOp, replacements);
2891 return success();
2892 }
2893};
2894
2895} // namespace
2896
2897// All named ops canonicalizers and folders are auto-generated in the
2898// .cpp.inc.
2899
2900//===----------------------------------------------------------------------===//
2901// SoftmaxOp
2902//===----------------------------------------------------------------------===//
2903
2904LogicalResult SoftmaxOp::verify() {
2905 ShapedType inputType = getInputOperandType();
2906 ShapedType outputType = getOutputOperandType();
2907
2908 ArrayRef<int64_t> inputShape = inputType.getShape();
2909 ArrayRef<int64_t> outputShape = outputType.getShape();
2910 if (failed(verifyCompatibleShape(inputShape, outputShape)))
2911 return emitOpError("incompatible output shape");
2912
2913 int64_t inputRank = getInputOperandRank();
2914 int64_t dimension = getDimension();
2915 if ((dimension < 0) || (dimension >= inputRank))
2916 return emitOpError("incorrect dimension specified");
2917
2918 return success();
2919}
2920
2921SmallVector<Range> SoftmaxOp::getIterationDomain(OpBuilder &builder) {
2922 int64_t operandRank = getInputOperandRank();
2923 SmallVector<Range> loopBounds(operandRank);
2924 Location loc = getLoc();
2925 Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
2926 Value one = arith::ConstantIndexOp::create(builder, loc, 1);
2927 Value source = getInput();
2928 for (auto dim : llvm::seq<int64_t>(0, operandRank)) {
2929 loopBounds[dim].offset = zero;
2930 loopBounds[dim].size = getDimValue(builder, loc, source, dim);
2931 loopBounds[dim].stride = one;
2932 }
2933 return loopBounds;
2934}
2935
2936SmallVector<utils::IteratorType> SoftmaxOp::getLoopIteratorTypes() {
2937 SmallVector<utils::IteratorType> iteratorTypes(getInputOperandRank(),
2938 utils::IteratorType::parallel);
2939 iteratorTypes[getDimension()] = utils::IteratorType::reduction;
2940 return iteratorTypes;
2941}
2942
2943/// The inner tile alignment hint is only used by `linalg.pack` and
2944/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
2945/// overload.
2946FailureOr<TilingResult> SoftmaxOp::getTiledImplementation(
2947 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
2948 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
2949 return getTiledImplementation(builder, offsets, sizes);
2950}
2951
2952FailureOr<TilingResult>
2953SoftmaxOp::getTiledImplementation(OpBuilder &builder,
2954 ArrayRef<OpFoldResult> offsets,
2955 ArrayRef<OpFoldResult> sizes) {
2956 int64_t rank = getInputOperandRank();
2957 auto oneAttr = builder.getI64IntegerAttr(1);
2958 SmallVector<OpFoldResult> strides(rank, oneAttr);
2959 SmallVector<Value> tiledOperands;
2960 Operation *inputSlice =
2961 getSlice(builder, getLoc(), getInput(), offsets, sizes, strides);
2962 if (!inputSlice) {
2963 return emitOpError("failed to compute input slice");
2964 }
2965 tiledOperands.emplace_back(inputSlice->getResult(0));
2966 Operation *outputSlice =
2967 getSlice(builder, getLoc(), getOutput(), offsets, sizes, strides);
2968 if (!outputSlice) {
2969 return emitOpError("failed to compute output slice");
2970 }
2971 tiledOperands.emplace_back(outputSlice->getResult(0));
2972
2973 SmallVector<Type, 4> resultTypes;
2974 if (hasPureTensorSemantics())
2975 resultTypes.push_back(tiledOperands[1].getType());
2976 Operation *tiledOp =
2977 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
2978
2979 return TilingResult{
2980 {tiledOp},
2981 SmallVector<Value>(tiledOp->getResults()),
2982 llvm::to_vector(ArrayRef<Operation *>{inputSlice, outputSlice})};
2983}
2984
2985LogicalResult SoftmaxOp::getResultTilePosition(
2986 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
2987 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
2988 SmallVector<OpFoldResult> &resultSizes) {
2989 if (resultNumber == 0) {
2990 resultOffsets.assign(offsets.begin(), offsets.end());
2991 resultSizes.assign(sizes.begin(), sizes.end());
2992 return success();
2993 }
2994 return failure();
2995}
2996
2997// cast(dynamic) -> static.
2998LogicalResult SoftmaxOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
2999 return memref::foldMemRefCast(*this);
3000}
3001
3002LogicalResult
3003SoftmaxOp::reifyResultShapes(OpBuilder &b,
3004 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3005 SmallVector<OpFoldResult> shapes;
3006 Location loc = getOperation()->getLoc();
3007 IRRewriter rewriter(b);
3008 auto inputShapedType = llvm::cast<ShapedType>(getInputOperandType());
3009 auto outputShapedType = llvm::cast<ShapedType>(getOutputOperandType());
3010 for (int64_t dim : llvm::seq<int64_t>(0, getOutputOperandRank())) {
3011 if (!outputShapedType.isDynamicDim(dim)) {
3012 // Static dim: Return IntegerAttr.
3013 shapes.push_back(b.getIndexAttr(inputShapedType.getDimSize(dim)));
3014 } else {
3015 // Dynamic dim: Return Value.
3016 OpFoldResult ofr = createOrFoldDimOp(b, loc, getInput(), dim);
3017 shapes.push_back(getValueOrCreateConstantIndexOp(b, loc, ofr));
3018 }
3019 }
3020 reifiedReturnShapes.emplace_back(std::move(shapes));
3021 return success();
3022}
3023
3024void SoftmaxOp::getEffects(
3025 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
3026 &effects) {
3027 for (auto [index, operand] : llvm::enumerate(getDpsInputs())) {
3028 if (!llvm::isa<MemRefType>(operand.getType()))
3029 continue;
3030 effects.emplace_back(MemoryEffects::Read::get(),
3031 &getOperation()->getOpOperand(index), /*stage=*/0,
3032 /*effectOnFullRegion=*/true,
3034 }
3035
3036 for (OpOperand &operand : getDpsInitsMutable()) {
3037 if (!llvm::isa<MemRefType>(operand.get().getType()))
3038 continue;
3039 effects.emplace_back(MemoryEffects::Read::get(), &operand, /*stage=*/0,
3040 /*effectOnFullRegion=*/true,
3042 effects.emplace_back(MemoryEffects::Write::get(), &operand, /*stage=*/0,
3043 /*effectOnFullRegion=*/true,
3045 }
3046}
3047
3048// Helper functions for softmax decomposition.
3049// @{
3050
3051// Helper function to produce the iterator types (reduction or parallel) and
3052// affine maps for the iterators used in the decomposition of softmax.
3053// This method creates:
3054// If allParallel == true:
3055// - iterator type: {parallel, ..., parallel}
3056// - affine maps:
3057// -- identity with inputRank dimensions.
3058// -- (d0, ..., dN) -> (d0, ..., d_dim-1, d_dim+1, ..., dN),
3059// where N == inputRank.
3060//
3061// If allParallel == false:
3062// - iterator type at dim(i) == parallel for i != \p dim and
3063// dim(dim) == reduction.
3064// - affine map:
3065// -- identity with inputRank dimensions.
3066// -- (d0, ..., dN) -> (d0, ..., d_dim-1, d_dim+1, ..., dN),
3067// where N == inputRank.
3068static std::tuple<SmallVector<utils::IteratorType>, SmallVector<AffineMap>>
3070 int64_t dim, bool allParallel = false) {
3071 SmallVector<utils::IteratorType> iteratorTypes(inputRank,
3072 utils::IteratorType::parallel);
3073 if (!allParallel)
3074 iteratorTypes[dim] = utils::IteratorType::reduction;
3075 MLIRContext *ctxt = builder.getContext();
3076 auto identityMap = AffineMap::getMultiDimIdentityMap(inputRank, ctxt);
3077 SmallVector<AffineExpr, 2> affineExprs;
3078 for (int i = 0; i < inputRank; i++) {
3079 if (i != dim)
3080 affineExprs.push_back(mlir::getAffineDimExpr(i, ctxt));
3081 }
3082 auto reductionMap =
3083 AffineMap::get(inputRank, /*symbols=*/0, affineExprs, ctxt);
3084 SmallVector<AffineMap> indexingMaps{identityMap, reductionMap};
3085 return std::make_tuple(iteratorTypes, indexingMaps);
3086}
3087
3088// Helper function to produce a linalg.generic that computes a reduction on
3089// dimension \p dim with the operation type \p T.
3090template <typename T>
3091static Value reduce(OpBuilder &builder, Location loc, Value input, Value output,
3092 int64_t dim) {
3093 auto inputType = cast<ShapedType>(input.getType());
3094 ArrayRef<int64_t> inputShape = inputType.getShape();
3095 int64_t inputRank = inputShape.size();
3096 auto [iteratorTypes, indexingMaps] =
3097 computeIteratorTypesAndIndexingMaps(builder, inputRank, dim);
3098 assert(indexingMaps.size() == 2 &&
3099 "We should have two maps: 1 for the input, 1 for the output");
3100 assert(indexingMaps[0].isIdentity() && "input map should be identity");
3101
3102 auto genericOp = linalg::GenericOp::create(
3103 builder, loc, output.getType(), input, output, indexingMaps,
3104 iteratorTypes, [&](OpBuilder &b, Location loc, ValueRange args) {
3105 Value result = T::create(b, loc, args[0], args[1]);
3106 linalg::YieldOp::create(b, loc, result);
3107 });
3108 return genericOp.getResult(0);
3109}
3110
3111/// Produce a linalg generic that computes the second step of the softmax
3112/// decomposition: res = exp(input - max), where \p max is the max of \p input
3113/// on dimension \p dim.
3114static Value buildSubAndExpOp(OpBuilder &builder, Location loc, Value input,
3115 Value max, Value output, int64_t dim) {
3116 auto inputType = cast<ShapedType>(input.getType());
3117 ArrayRef<int64_t> inputShape = inputType.getShape();
3118 int64_t inputRank = inputShape.size();
3119 auto [iteratorTypes, indexingMaps] = computeIteratorTypesAndIndexingMaps(
3120 builder, inputRank, dim, /*allParallel=*/true);
3121 assert(indexingMaps.size() == 2 && "We should have one map for each input");
3122 assert(indexingMaps[0].isIdentity() && "input map should be identity");
3123 // Add the affine map for the output argument.
3124 indexingMaps.push_back(indexingMaps[0]);
3125 auto genericOp = linalg::GenericOp::create(
3126 builder, loc, input.getType(), ValueRange{input, max}, output,
3127 indexingMaps, iteratorTypes,
3128 [&](OpBuilder &b, Location loc, ValueRange args) {
3129 Value diff = arith::SubFOp::create(b, loc, args[0], args[1]);
3130 Value result = math::ExpOp::create(b, loc, diff);
3131 linalg::YieldOp::create(b, loc, result);
3132 });
3133 return genericOp.getResult(0);
3134}
3135
3136/// Produce a linalg generic that computes the final step of the softmax
3137/// decomposition.
3138/// \returns linalg.generic ins(\p numerator, \p denominator) outs(\p output) {
3139/// yield n / d
3140/// }
3141static Value buildDivOp(OpBuilder &builder, Location loc, Value numerator,
3142 Value denominator, Value output, int64_t dim) {
3143 auto inputType = cast<ShapedType>(numerator.getType());
3144 ArrayRef<int64_t> inputShape = inputType.getShape();
3145 int64_t inputRank = inputShape.size();
3146 auto [iteratorTypes, indexingMaps] = computeIteratorTypesAndIndexingMaps(
3147 builder, inputRank, dim, /*allParallel=*/true);
3148 assert(indexingMaps.size() == 2 &&
3149 "We should have one map for each input (2)");
3150 assert(indexingMaps[0].isIdentity() && "Numerator map should be identity");
3151 // Add the affine map for the output tensor.
3152 indexingMaps.push_back(indexingMaps[0]);
3153 auto genericOp = linalg::GenericOp::create(
3154 builder, loc, numerator.getType(), ValueRange{numerator, denominator},
3155 output, indexingMaps, iteratorTypes,
3156 [&](OpBuilder &b, Location loc, ValueRange args) {
3157 Value result = arith::DivFOp::create(b, loc, args[0], args[1]);
3158 linalg::YieldOp::create(b, loc, result);
3159 });
3160 return genericOp.getResult(0);
3161}
3162// @} End helper functions for softmax decomposition.
3163
3164/// Given an N-dimensional tensor x, this method converts
3165/// softmax(x) to the following sequence of operations:
3166///
3167/// 1. Compute the max of x along dimension d. This results
3168/// in a N-1 dimensional tensor m.
3169/// m = max(x, dim = d)
3170///
3171/// 2. Subtract a broadcasted m from x and exponentiate. This results in
3172/// a N dimensional tensor z.
3173/// z = exp(x - m)
3174///
3175/// 3. Compute the sum of z along dimension d. This results in
3176/// a N-1 dimensional tensor l.
3177/// l = sum(z, dim = d)
3178///
3179/// 4. Divide z and l. This gives the N-dimensional softmax.
3180/// softmax = z / l
3181///
3182FailureOr<SmallVector<Value>> SoftmaxOp::decomposeOperation(OpBuilder &b) {
3183 OpBuilder::InsertionGuard guard(b);
3184 b.setInsertionPoint(*this);
3185 Location loc = getLoc();
3186 Value input = getInput();
3187 ShapedType inputType = getInputOperandType();
3188 Type elementType = inputType.getElementType();
3189 int64_t reductionDim = getDimension();
3190 SmallVector<OpFoldResult> dims = tensor::getMixedSizes(b, loc, input);
3191 Value output = getOutput();
3192 dims.erase(dims.begin() + reductionDim);
3193 // Step 1: Compute max along dim.
3194 Value outputReduce = tensor::EmptyOp::create(b, loc, dims, elementType);
3195 Value neutralForMaxF = arith::getIdentityValue(arith::AtomicRMWKind::maxnumf,
3196 elementType, b, loc,
3197 /*useOnlyFiniteValue=*/true);
3198 Value neutralForMaxFInit =
3199 linalg::FillOp::create(b, loc, Value{neutralForMaxF}, outputReduce)
3200 .result();
3201 Value max =
3202 reduce<arith::MaxNumFOp>(b, loc, input, neutralForMaxFInit, reductionDim);
3203
3204 // Step 2: Subtract max from input and exponentiate.
3205 Value numerator = buildSubAndExpOp(b, loc, input, max, output, reductionDim);
3206
3207 // Step 3: Compute sum along dim.
3208 Value zero = arith::getIdentityValue(arith::AtomicRMWKind::addf, elementType,
3209 b, loc, /*useOnlyFiniteValue=*/true);
3210 Value zeroInit =
3211 linalg::FillOp::create(b, loc, Value{zero}, outputReduce).result();
3212 Value denominator =
3213 reduce<arith::AddFOp>(b, loc, numerator, zeroInit, reductionDim);
3214
3215 // Step 4: Compute softmax.
3216 Value result =
3217 buildDivOp(b, loc, numerator, denominator, output, reductionDim);
3218 return SmallVector<Value>{result};
3219}
3220
3221//===----------------------------------------------------------------------===//
3222// WinogradFilterTransformOp
3223//===----------------------------------------------------------------------===//
3224
3225LogicalResult WinogradFilterTransformOp::verify() {
3226 auto filterType = cast<ShapedType>(getFilter().getType());
3227 ArrayRef<int64_t> filterShape = filterType.getShape();
3228 int64_t filterH = filterShape[getFilterHDim()];
3229 int64_t filterW = filterShape[getFilterWDim()];
3230 WinogradConv2DFmr fmr = getFmr();
3231 int64_t m, r;
3232 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3233
3234 if (filterH != r && filterH != 1)
3235 return emitOpError("expect filter height either equals to r or 1");
3236 if (filterW != r && filterW != 1)
3237 return emitOpError("expect filter width either equals to r or 1");
3238 if (filterH == 1 && filterW == 1)
3239 return emitOpError("expect either filter height or width equals to r");
3240
3241 SmallVector<int64_t> expectedOutputShape;
3242 expectedOutputShape.push_back(filterH == r ? m + r - 1 : 1);
3243 expectedOutputShape.push_back(filterW == r ? m + r - 1 : 1);
3244 expectedOutputShape.push_back(filterShape[getFilterCDim()]);
3245 expectedOutputShape.push_back(filterShape[getFilterFDim()]);
3246
3247 auto outputType = cast<ShapedType>(getOutput().getType());
3248 ArrayRef<int64_t> outputShape = outputType.getShape();
3249 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3250 return emitOpError("the output shape is not expected");
3251 }
3252 return success();
3253}
3254
3255SmallVector<Range>
3256WinogradFilterTransformOp::getIterationDomain(OpBuilder &builder) {
3257 Location loc = getLoc();
3258 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3259 IntegerAttr oneAttr = builder.getIndexAttr(1);
3260 Value filter = getFilter();
3261 int64_t filterRank = getFilterOperandRank();
3262 SmallVector<Range> loopBounds(filterRank);
3263 for (unsigned dim = 0; dim < filterRank; ++dim) {
3264 loopBounds[dim].offset = zeroAttr;
3265 loopBounds[dim].size = getDimValue(builder, loc, filter, dim);
3266 loopBounds[dim].stride = oneAttr;
3267 }
3268 return loopBounds;
3269}
3270
3271SmallVector<utils::IteratorType>
3272WinogradFilterTransformOp::getLoopIteratorTypes() {
3273 int64_t filterRank = getFilterOperandRank();
3274 SmallVector<utils::IteratorType> iteratorTypes(filterRank,
3275 utils::IteratorType::parallel);
3276 return iteratorTypes;
3277}
3278
3279LogicalResult WinogradFilterTransformOp::getResultTilePosition(
3280 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3281 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3282 SmallVector<OpFoldResult> &resultSizes) {
3283 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3284 ShapedType filterType = getFilterOperandType();
3285 ArrayRef<int64_t> filterShape = filterType.getShape();
3286 int64_t filterH = filterShape[getFilterHDim()];
3287 int64_t filterW = filterShape[getFilterWDim()];
3288 WinogradConv2DFmr fmr = getFmr();
3289 int64_t m, r;
3290 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3291 int64_t alpha = m + r - 1;
3292 int64_t alphaH = filterH != 1 ? alpha : 1;
3293 int64_t alphaW = filterW != 1 ? alpha : 1;
3294 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3295 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3296
3297 resultOffsets.append(
3298 {zeroAttr, zeroAttr, offsets[getFilterCDim()], offsets[getFilterFDim()]});
3299 resultSizes.append(
3300 {alphaHAttr, alphaWAttr, sizes[getFilterCDim()], sizes[getFilterFDim()]});
3301
3302 return success();
3303}
3304
3305/// The inner tile alignment hint is only used by `linalg.pack` and
3306/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3307/// overload.
3308FailureOr<TilingResult> WinogradFilterTransformOp::getTiledImplementation(
3309 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3310 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3311 return getTiledImplementation(builder, offsets, sizes);
3312}
3313
3314/// Implement tiling for winograd_filter_transform
3315/// The input of winograd_filter_transform is (F, KH, KW, C).
3316/// The output of winograd_filter_transform is (alphaH, alphaW, C, F)
3317/// Users can specify the tile sizes of F and C.
3318/// `offsets` are the values for the offsets of F, KH, KW, C for one tile.
3319/// `sizes` are the values for the sizes of F, KH, KW, C for one tile.
3320FailureOr<TilingResult> WinogradFilterTransformOp::getTiledImplementation(
3321 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3322 ArrayRef<OpFoldResult> sizes) {
3323 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3324 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3325 ShapedType filterType = getFilterOperandType();
3326 ArrayRef<int64_t> filterShape = filterType.getShape();
3327 int64_t filterH = filterShape[getFilterHDim()];
3328 int64_t filterW = filterShape[getFilterWDim()];
3329 IntegerAttr filterHAttr = builder.getI64IntegerAttr(filterH);
3330 IntegerAttr filterWAttr = builder.getI64IntegerAttr(filterW);
3331 SmallVector<Value> tiledOperands;
3332 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3333
3334 sliceOffsets.append(
3335 {offsets[getFilterFDim()], zeroAttr, zeroAttr, offsets[getFilterCDim()]});
3336 sliceSizes.append({sizes[getFilterFDim()], filterHAttr, filterWAttr,
3337 sizes[getFilterCDim()]});
3338 int64_t filterRank = getFilterOperandRank();
3339 SmallVector<OpFoldResult> filterStrides(filterRank, oneAttr);
3340 Location loc = getLoc();
3341 auto filterSlice = tensor::ExtractSliceOp::create(
3342 builder, loc, getFilter(), sliceOffsets, sliceSizes, filterStrides);
3343 tiledOperands.emplace_back(filterSlice);
3344
3345 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3346 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3347 resultSizes)))
3348 return failure();
3349
3350 int64_t outputRank = getOutputOperandRank();
3351 SmallVector<OpFoldResult> outputStrides(outputRank, oneAttr);
3352 auto outputSlice = tensor::ExtractSliceOp::create(
3353 builder, loc, getOutput(), resultOffsets, resultSizes, outputStrides);
3354 tiledOperands.emplace_back(outputSlice);
3355
3356 SmallVector<Type> resultTypes;
3357 resultTypes.push_back(tiledOperands[1].getType());
3358 Operation *tiledOp =
3359 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3360
3361 return TilingResult{
3362 {tiledOp},
3363 SmallVector<Value>(tiledOp->getResults()),
3364 llvm::to_vector(ArrayRef<Operation *>{filterSlice, outputSlice})};
3365}
3366
3367//===----------------------------------------------------------------------===//
3368// WinogradInputTransformOp
3369//===----------------------------------------------------------------------===//
3370
3371LogicalResult WinogradInputTransformOp::verify() {
3372 auto inputType = cast<ShapedType>(getInput().getType());
3373 ArrayRef<int64_t> inputShape = inputType.getShape();
3374 int64_t inputH = inputShape[getInputHDim()];
3375 int64_t inputW = inputShape[getInputWDim()];
3376 WinogradConv2DFmr fmr = getFmr();
3377 int64_t m, r;
3378 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3379 int64_t tileSize = m + r - 1;
3380
3381 auto outputType = cast<ShapedType>(getOutput().getType());
3382 ArrayRef<int64_t> outputShape = outputType.getShape();
3383 bool leftTransform = outputShape[getOutputAlphaHDim()] != 1;
3384 bool rightTransform = outputShape[getOutputAlphaWDim()] != 1;
3385
3386 SmallVector<int64_t> expectedOutputShape(6, inputH);
3387 if (ShapedType::isDynamic(inputH)) {
3388 expectedOutputShape[getOutputAlphaHDim()] = tileSize;
3389 expectedOutputShape[getOutputTileHDim()] = ShapedType::kDynamic;
3390 } else {
3391 expectedOutputShape[getOutputAlphaHDim()] = leftTransform ? tileSize : 1;
3392 expectedOutputShape[getOutputTileHDim()] =
3393 leftTransform ? (inputH - (r - 1)) / m : inputH;
3394 }
3395 if (ShapedType::isDynamic(inputW)) {
3396 expectedOutputShape[getOutputAlphaWDim()] = tileSize;
3397 expectedOutputShape[getOutputTileWDim()] = ShapedType::kDynamic;
3398 } else {
3399 expectedOutputShape[getOutputAlphaWDim()] = rightTransform ? tileSize : 1;
3400 expectedOutputShape[getOutputTileWDim()] =
3401 rightTransform ? (inputW - (r - 1)) / m : inputW;
3402 }
3403 expectedOutputShape[getOutputNDim()] = inputShape[getInputNDim()];
3404 expectedOutputShape[getOutputCDim()] = inputShape[getInputCDim()];
3405
3406 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3407 return emitOpError("the output shape is not expected");
3408 }
3409 return success();
3410}
3411
3412SmallVector<Range>
3413WinogradInputTransformOp::getIterationDomain(OpBuilder &builder) {
3414 Location loc = getLoc();
3415 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3416 IntegerAttr oneAttr = builder.getIndexAttr(1);
3417 Value output = getOutput();
3418 int64_t outputRank = getOutputOperandRank();
3419 SmallVector<Range> loopBounds(outputRank);
3420 for (unsigned dim = 0; dim < outputRank; ++dim) {
3421 loopBounds[dim].offset = zeroAttr;
3422 // alphaH, alphaW, tileH, tileW, N, C
3423 loopBounds[dim].size = getDimValue(builder, loc, output, dim);
3424 loopBounds[dim].stride = oneAttr;
3425 }
3426 return loopBounds;
3427}
3428
3429SmallVector<utils::IteratorType>
3430WinogradInputTransformOp::getLoopIteratorTypes() {
3431 int64_t outputRank = getOutputOperandRank();
3432 SmallVector<utils::IteratorType> iteratorTypes(outputRank,
3433 utils::IteratorType::parallel);
3434 return iteratorTypes;
3435}
3436
3437LogicalResult WinogradInputTransformOp::getResultTilePosition(
3438 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3439 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3440 SmallVector<OpFoldResult> &resultSizes) {
3441 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3442 ShapedType outputType = getOutputOperandType();
3443 ArrayRef<int64_t> outputShape = outputType.getShape();
3444 int64_t outputAlphaH = outputShape[getOutputAlphaHDim()];
3445 int64_t outputAlphaW = outputShape[getOutputAlphaWDim()];
3446
3447 WinogradConv2DFmr fmr = getFmr();
3448 int64_t m, r;
3449 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3450 int64_t alpha = m + r - 1;
3451 int64_t alphaH = outputAlphaH != 1 ? alpha : 1;
3452 int64_t alphaW = outputAlphaW != 1 ? alpha : 1;
3453
3454 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3455 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3456
3457 resultOffsets.append({zeroAttr, zeroAttr, offsets[getOutputTileHDim()],
3458 offsets[getOutputTileWDim()], offsets[getOutputNDim()],
3459 offsets[getOutputCDim()]});
3460 resultSizes.append({alphaHAttr, alphaWAttr, sizes[getOutputTileHDim()],
3461 sizes[getOutputTileWDim()], sizes[getOutputNDim()],
3462 sizes[getOutputCDim()]});
3463
3464 return success();
3465}
3466
3467/// The inner tile alignment hint is only used by `linalg.pack` and
3468/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3469/// overload.
3470FailureOr<TilingResult> WinogradInputTransformOp::getTiledImplementation(
3471 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3472 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3473 return getTiledImplementation(builder, offsets, sizes);
3474}
3475
3476/// Implement tiling for winograd_input_transform
3477/// The input of winograd_input_transform is (N, H, W, C).
3478/// The output of winograd_input_transform is (alphaH, alphaW, tileH, tileW, N,
3479/// C) Users can specify the tile sizes of tileH, tileW, N, and C. `offsets` are
3480/// the values for the offsets of tileH, tileW, N, C for one tile. `sizes` are
3481/// the values for the sizes of tileH, tileW, N, C for one tile.
3482FailureOr<TilingResult>
3483WinogradInputTransformOp::getTiledImplementation(OpBuilder &builder,
3484 ArrayRef<OpFoldResult> offsets,
3485 ArrayRef<OpFoldResult> sizes) {
3486 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3487 WinogradConv2DFmr fmr = getFmr();
3488 int64_t m, r;
3489 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3490
3491 ShapedType outputType = getOutputOperandType();
3492 ArrayRef<int64_t> outputShape = outputType.getShape();
3493 int64_t alphaH = outputShape[getOutputAlphaHDim()];
3494 int64_t alphaW = outputShape[getOutputAlphaWDim()];
3495
3496 Location loc = getLoc();
3497 MLIRContext *context = builder.getContext();
3498 auto identityAffineMap =
3499 AffineMap::get(1, 0, {builder.getAffineDimExpr(0)}, context);
3500 auto offsetAffineMap =
3501 AffineMap::get(1, 0, {builder.getAffineDimExpr(0) * m}, context);
3502 Value mappedOffsetH = affine::makeComposedAffineApply(
3503 builder, loc, (alphaH != 1 ? offsetAffineMap : identityAffineMap),
3504 offsets[getOutputTileHDim()]);
3505 Value mappedOffsetW = affine::makeComposedAffineApply(
3506 builder, loc, (alphaW != 1 ? offsetAffineMap : identityAffineMap),
3507 offsets[getOutputTileWDim()]);
3508 auto sizeAffineMap = AffineMap::get(
3509 1, 0, {builder.getAffineDimExpr(0) * m + (r - 1)}, context);
3510 Value mappedSizeH = affine::makeComposedAffineApply(
3511 builder, loc, sizeAffineMap, sizes[getOutputTileHDim()]);
3512 Value mappedSizeW = affine::makeComposedAffineApply(
3513 builder, loc, sizeAffineMap, sizes[getOutputTileWDim()]);
3514
3515 SmallVector<Value> tiledOperands;
3516 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3517
3518 OpFoldResult offsetH = OpFoldResult(mappedOffsetH);
3519 OpFoldResult offsetW = OpFoldResult(mappedOffsetW);
3520 sliceOffsets.append(
3521 {offsets[getOutputNDim()], offsetH, offsetW, offsets[getOutputCDim()]});
3522 OpFoldResult sizeH =
3523 alphaH != 1 ? OpFoldResult(mappedSizeH) : OpFoldResult(oneAttr);
3524 OpFoldResult sizeW =
3525 alphaW != 1 ? OpFoldResult(mappedSizeW) : OpFoldResult(oneAttr);
3526 sliceSizes.append(
3527 {sizes[getOutputNDim()], sizeH, sizeW, sizes[getOutputCDim()]});
3528 int64_t inputRank = getInputOperandRank();
3529 SmallVector<OpFoldResult> inputStrides(inputRank, oneAttr);
3530 auto inputSlice = tensor::ExtractSliceOp::create(
3531 builder, loc, getInput(), sliceOffsets, sliceSizes, inputStrides);
3532 tiledOperands.emplace_back(inputSlice);
3533
3534 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3535 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3536 resultSizes)))
3537 return failure();
3538
3539 int64_t outputRank = getOutputOperandRank();
3540 SmallVector<OpFoldResult> outputStrides(outputRank, oneAttr);
3541 auto outputSlice = tensor::ExtractSliceOp::create(
3542 builder, loc, getOutput(), resultOffsets, resultSizes, outputStrides);
3543 tiledOperands.emplace_back(outputSlice);
3544
3545 SmallVector<Type> resultTypes;
3546 resultTypes.push_back(tiledOperands[1].getType());
3547 Operation *tiledOp =
3548 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3549
3550 return TilingResult{
3551 {tiledOp},
3552 SmallVector<Value>(tiledOp->getResults()),
3553 llvm::to_vector(ArrayRef<Operation *>{inputSlice, outputSlice})};
3554}
3555
3556//===----------------------------------------------------------------------===//
3557// WinogradOutputTransformOp
3558//===----------------------------------------------------------------------===//
3559
3560LogicalResult WinogradOutputTransformOp::verify() {
3561 auto valueType = cast<ShapedType>(getValue().getType());
3562 ArrayRef<int64_t> valueShape = valueType.getShape();
3563 int64_t valueH = valueShape[getValueAlphaHDim()];
3564 int64_t valueW = valueShape[getValueAlphaWDim()];
3565 int64_t valueTileH = valueShape[getValueTileHDim()];
3566 int64_t valueTileW = valueShape[getValueTileWDim()];
3567 WinogradConv2DFmr fmr = getFmr();
3568 int64_t m, r;
3569 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3570 bool leftTransform = valueH != 1;
3571 bool rightTransform = valueW != 1;
3572
3573 int64_t outputRank = getOutputOperandRank();
3574 SmallVector<int64_t> expectedOutputShape(outputRank, valueH);
3575 if (ShapedType::isDynamic(valueH) || ShapedType::isDynamic(valueTileH)) {
3576 expectedOutputShape[getOutputHDim()] = ShapedType::kDynamic;
3577 } else {
3578 if (valueH != (leftTransform ? m + r - 1 : 1))
3579 return emitOpError("expect input height equals to input tile size");
3580 expectedOutputShape[getOutputHDim()] = (leftTransform ? m : 1) * valueTileH;
3581 }
3582 if (ShapedType::isDynamic(valueW) || ShapedType::isDynamic(valueTileW)) {
3583 expectedOutputShape[getOutputWDim()] = ShapedType::kDynamic;
3584 } else {
3585 if (valueW != (rightTransform ? m + r - 1 : 1))
3586 return emitOpError("expect input width equals to input tile size");
3587 expectedOutputShape[getOutputWDim()] =
3588 (rightTransform ? m : 1) * valueTileW;
3589 }
3590 expectedOutputShape[getOutputNDim()] = valueShape[getValueNDim()];
3591 expectedOutputShape[getOutputFDim()] = valueShape[getValueFDim()];
3592
3593 auto outputType = cast<ShapedType>(getOutput().getType());
3594 ArrayRef<int64_t> outputShape = outputType.getShape();
3595 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3596 return emitOpError("the output shape is not expected");
3597 }
3598 return success();
3599}
3600
3601SmallVector<Range>
3602WinogradOutputTransformOp::getIterationDomain(OpBuilder &builder) {
3603 Location loc = getLoc();
3604 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3605 IntegerAttr oneAttr = builder.getIndexAttr(1);
3606 Value value = getValue();
3607 int64_t valueRank = getValueOperandRank();
3608 SmallVector<Range> loopBounds(valueRank);
3609 for (unsigned dim = 0; dim < valueRank; ++dim) {
3610 loopBounds[dim].offset = zeroAttr;
3611 // alphaH, alphaW, tileH, tileW, N, F
3612 loopBounds[dim].size = getDimValue(builder, loc, value, dim);
3613 loopBounds[dim].stride = oneAttr;
3614 }
3615 return loopBounds;
3616}
3617
3618SmallVector<utils::IteratorType>
3619WinogradOutputTransformOp::getLoopIteratorTypes() {
3620 int64_t valueRank = getValueOperandRank();
3621 SmallVector<utils::IteratorType> iteratorTypes(valueRank,
3622 utils::IteratorType::parallel);
3623 return iteratorTypes;
3624}
3625
3626LogicalResult WinogradOutputTransformOp::getResultTilePosition(
3627 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3628 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3629 SmallVector<OpFoldResult> &resultSizes) {
3630 WinogradConv2DFmr fmr = getFmr();
3631 int64_t m, r;
3632 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3633
3634 Location loc = getLoc();
3635 MLIRContext *context = builder.getContext();
3636 auto identityAffineMap =
3637 AffineMap::get(1, 0, {builder.getAffineDimExpr(0)}, context);
3638 auto affineMap =
3639 AffineMap::get(1, 0, {builder.getAffineDimExpr(0) * m}, context);
3640
3641 ShapedType valueType = getValueOperandType();
3642 ArrayRef<int64_t> valueShape = valueType.getShape();
3643 int64_t valueH = valueShape[0];
3644 int64_t valueW = valueShape[1];
3645 Value mappedOffsetH = affine::makeComposedAffineApply(
3646 builder, loc, (valueH != 1 ? affineMap : identityAffineMap),
3647 offsets[getValueTileHDim()]);
3648 Value mappedOffsetW = affine::makeComposedAffineApply(
3649 builder, loc, (valueW != 1 ? affineMap : identityAffineMap),
3650 offsets[getValueTileWDim()]);
3651 Value mappedSizeH = affine::makeComposedAffineApply(
3652 builder, loc, affineMap, sizes[getValueTileHDim()]);
3653 Value mappedSizeW = affine::makeComposedAffineApply(
3654 builder, loc, affineMap, sizes[getValueTileWDim()]);
3655
3656 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3657 OpFoldResult offsetH = OpFoldResult(mappedOffsetH);
3658 OpFoldResult offsetW = OpFoldResult(mappedOffsetW);
3659 OpFoldResult sizeH =
3660 valueH != 1 ? OpFoldResult(mappedSizeH) : OpFoldResult(oneAttr);
3661 OpFoldResult sizeW =
3662 valueW != 1 ? OpFoldResult(mappedSizeW) : OpFoldResult(oneAttr);
3663
3664 resultOffsets.append(
3665 {offsets[getValueNDim()], offsetH, offsetW, offsets[getValueFDim()]});
3666 resultSizes.append(
3667 {sizes[getValueNDim()], sizeH, sizeW, sizes[getValueFDim()]});
3668 return success();
3669}
3670
3671/// The inner tile alignment hint is only used by `linalg.pack` and
3672/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3673/// overload.
3674FailureOr<TilingResult> WinogradOutputTransformOp::getTiledImplementation(
3675 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3676 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3677 return getTiledImplementation(builder, offsets, sizes);
3678}
3679
3680/// Implement tiling for winograd_output_transform
3681/// The input of winograd_output_transform is (alphaH, alphaW, tileH, tileW, N,
3682/// F). The output of winograd_output_transform is (N, H, W, F) Users can
3683/// specify the tile sizes of tileH, tileW, N, and F. `offsets` are the values
3684/// for the offsets of tileH, tileW, N, F for one tile. `sizes` are the values
3685/// for the sizes of tileH, tileW, N, F for one tile.
3686FailureOr<TilingResult> WinogradOutputTransformOp::getTiledImplementation(
3687 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3688 ArrayRef<OpFoldResult> sizes) {
3689 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3690 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3691 Location loc = getLoc();
3692 SmallVector<Value> tiledOperands;
3693 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3694
3695 ShapedType valueType = getValueOperandType();
3696 ArrayRef<int64_t> valueShape = valueType.getShape();
3697 int64_t alphaH = valueShape[getValueAlphaHDim()];
3698 int64_t alphaW = valueShape[getValueAlphaWDim()];
3699 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3700 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3701
3702 sliceOffsets.append({zeroAttr, zeroAttr, offsets[getValueTileHDim()],
3703 offsets[getValueTileWDim()], offsets[getValueNDim()],
3704 offsets[getValueFDim()]});
3705 sliceSizes.append({alphaHAttr, alphaWAttr, sizes[getValueTileHDim()],
3706 sizes[getValueTileWDim()], sizes[getValueNDim()],
3707 sizes[getValueFDim()]});
3708 int64_t valueRank = getValueOperandRank();
3709 SmallVector<OpFoldResult> sliceStrides(valueRank, oneAttr);
3710 auto valueSlice = tensor::ExtractSliceOp::create(
3711 builder, loc, getValue(), sliceOffsets, sliceSizes, sliceStrides);
3712 tiledOperands.emplace_back(valueSlice);
3713
3714 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3715 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3716 resultSizes)))
3717 return failure();
3718
3719 int64_t outputRank = getOutputOperandRank();
3720 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
3721 auto outputSlice = tensor::ExtractSliceOp::create(
3722 builder, loc, getOutput(), resultOffsets, resultSizes, strides);
3723 tiledOperands.emplace_back(outputSlice);
3724
3725 SmallVector<Type> resultTypes;
3726 resultTypes.push_back(tiledOperands[1].getType());
3727 Operation *tiledOp =
3728 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3729
3730 return TilingResult{
3731 {tiledOp},
3732 SmallVector<Value>(tiledOp->getResults()),
3733 llvm::to_vector(ArrayRef<Operation *>{valueSlice, outputSlice})};
3734}
3735
3736//===----------------------------------------------------------------------===//
3737// LinalgDialect
3738// TODO: Merge with the LinalgDialect block at the bottom
3739//===----------------------------------------------------------------------===//
3740
3741// Returns true if the result expression of `subMap` are a subset of `fullMap`.
3742static bool areResultExprsSubsetOf(AffineMap subMap, AffineMap fullMap) {
3743 auto explicitRange = subMap.getResults();
3744 auto defaultRange = fullMap.getResults();
3745 DenseSet<AffineExpr> explicitSet(explicitRange.begin(), explicitRange.end());
3746 DenseSet<AffineExpr> defaultSet(defaultRange.begin(), defaultRange.end());
3747 llvm::set_union(explicitSet, defaultSet);
3748 return explicitSet == defaultSet;
3749}
3750
3751/// Check if the user defined map is valid broadcast map. Here broadcast
3752/// indexing maps are defined in context of corresponding default indexing maps
3753/// for the given Op. This way the check becomes very simple i.e just check the
3754/// number of result dims.
3755/// Returns true if the explictMap is broadcasted with respect to the
3756/// defaultMap.
3757static bool isBroadcasted(AffineMap explictMap, AffineMap defaultMap) {
3758 return explictMap.getNumResults() < defaultMap.getNumResults();
3759}
3760
3761/// Verifies the broadcast and transpose semantic sepecified by the explicit
3762/// indexing map for the MatmulOp \p op for each operand specified by \p
3763/// opIndex.
3764static LogicalResult verifyExtendedMatmulSemantic(MatmulOp matmulOp,
3765 unsigned opIndex) {
3766 SmallVector<AffineMap, 3> opIndexingMaps = matmulOp.getIndexingMapsArray();
3767 SmallVector<AffineMap, 3> defaultIndexingMaps =
3768 matmulOp.getDefaultIndexingMaps(matmulOp->getContext());
3769
3770 auto opIndexingMap = opIndexingMaps[opIndex];
3771 auto defaultIndexingMap = defaultIndexingMaps[opIndex];
3772 // Check general validity of indexing map results.
3773 if (!areResultExprsSubsetOf(opIndexingMap, defaultIndexingMap))
3774 return matmulOp->emitOpError()
3775 << "Unexpected dim expression in map result.";
3776
3777 if (isBroadcasted(opIndexingMap, defaultIndexingMap)) {
3778 if (!matmulOp.isValidLhsRhsBroadcastMap(opIndexingMap)) {
3779 return matmulOp->emitOpError()
3780 << "Invalid broadcast requested, should be (d2).";
3781 }
3782 return success();
3783 }
3784 return success();
3785}
3786
3787// Check general validity of input indexing map of
3788// BatchMatmulOp/BatchReduceMatmulOp.
3789template <typename OpTy>
3790static LogicalResult verifyInputMaps(OpTy batchVariantMatmulOp,
3791 AffineMap opIndexingMap,
3792 AffineMap defaultIndexingMap, bool isLHS) {
3793 assert((isa<BatchMatmulOp>(batchVariantMatmulOp) ||
3794 isa<BatchReduceMatmulOp>(batchVariantMatmulOp)) &&
3795 "Expected BatchMatmulOp or BatchReduceMatmulOp");
3796 // Check the result dims are valid.
3797 if (!areResultExprsSubsetOf(opIndexingMap, defaultIndexingMap))
3798 return batchVariantMatmulOp->emitOpError()
3799 << "Unexpected result dim expression (outside the set of default "
3800 "result dims).";
3801
3802 // Check for valid number of result dims of input maps.
3803 if (opIndexingMap.getNumResults() > 3)
3804 return batchVariantMatmulOp->emitOpError()
3805 << "no. of result dim expressions exceeds 3.";
3806
3807 auto hasValidBatchDim = [](AffineMap map) {
3808 AffineExpr batchDim = map.getResult(0);
3809 return batchDim.isFunctionOfDim(0);
3810 };
3811
3812 // Check if the requested broadcast is valid.
3813 if (isBroadcasted(opIndexingMap, defaultIndexingMap)) {
3814 if (!batchVariantMatmulOp.isValidLhsRhsBroadcastMap(opIndexingMap, isLHS))
3815 return batchVariantMatmulOp->emitOpError()
3816 << "Invalid broadcast requested.";
3817 } else if (!hasValidBatchDim(opIndexingMap)) {
3818 return batchVariantMatmulOp->emitOpError()
3819 << "Invalid batch dimension expression.";
3820 }
3821 return success();
3822}
3823
3824/// This function checks if the given AffineMap for the output of a
3825/// BatchMatmulOp/BatchReduceMatmulOp has exactly the desired number of result
3826/// dimensions and if the output map result dimensions are valid.
3827template <typename OpTy>
3828static LogicalResult verifyOutputMap(OpTy batchVariantMatmulOp,
3829 AffineMap opIndexingMap) {
3830 assert((isa<BatchMatmulOp>(batchVariantMatmulOp) ||
3831 isa<BatchReduceMatmulOp>(batchVariantMatmulOp)) &&
3832 "Expected BatchMatmulOp or BatchReduceMatmulOp");
3833 if (isa<BatchMatmulOp>(batchVariantMatmulOp) &&
3834 opIndexingMap.getNumResults() != 3) {
3835
3836 return batchVariantMatmulOp->emitOpError()
3837 << "expects 3 dims, but got (" << opIndexingMap.getNumResults()
3838 << ").";
3839 }
3840 if (isa<BatchReduceMatmulOp>(batchVariantMatmulOp) &&
3841 opIndexingMap.getNumResults() != 2) {
3842 return batchVariantMatmulOp->emitOpError()
3843 << "expects 2 dims, but got (" << opIndexingMap.getNumResults()
3844 << ").";
3845 }
3846
3847 auto areValidOutputResultDim = [&](AffineMap outputMap) {
3848 return isa<BatchMatmulOp>(batchVariantMatmulOp)
3849 ? outputMap.getResult(0).isFunctionOfDim(0) &&
3850 outputMap.getResult(1).isFunctionOfDim(1) &&
3851 outputMap.getResult(2).isFunctionOfDim(2)
3852 : outputMap.getResult(0).isFunctionOfDim(1) &&
3853 outputMap.getResult(1).isFunctionOfDim(2);
3854 };
3855
3856 if (!areValidOutputResultDim(opIndexingMap)) {
3857 return batchVariantMatmulOp->emitOpError()
3858 << "Invalid output map result dimension.";
3859 }
3860
3861 return success();
3862}
3863
3864/// Verifies the broadcast and transpose semantic specified by the explicit
3865/// indexing map for the BatchMatmulOp/BatchReduceMatmulOp op for each operand
3866/// specified by opIndex.
3867template <typename OpTy>
3868static LogicalResult
3870 unsigned opIndex) {
3871 SmallVector<AffineMap, 3> opIndexingMaps =
3872 batchVariantMatmulOp.getIndexingMapsArray();
3873 SmallVector<AffineMap, 3> defaultIndexingMaps =
3874 batchVariantMatmulOp.getDefaultIndexingMaps(
3875 batchVariantMatmulOp->getContext());
3876
3877 if (opIndexingMaps.size() != 3)
3878 return batchVariantMatmulOp->emitOpError()
3879 << "Indexing_map attribute must have 3 affine maps.";
3880
3881 auto opIndexingMap = opIndexingMaps[opIndex];
3882 auto defaultIndexingMap = defaultIndexingMaps[opIndex];
3883
3884 if (opIndex == 2 &&
3885 failed(verifyOutputMap(batchVariantMatmulOp, opIndexingMap)))
3886 return failure();
3887
3888 if (opIndex != 2 &&
3889 failed(verifyInputMaps(batchVariantMatmulOp, opIndexingMap,
3890 defaultIndexingMap, opIndex == 0)))
3891 return failure();
3892
3893 return success();
3894}
3895
3896namespace mlir {
3897namespace linalg {
3898
3899std::optional<WinogradConv2DFmr> getWinogradConv2DFmr(int64_t m, int64_t r) {
3900 if (m == 2 && r == 3)
3901 return WinogradConv2DFmr::F_2_3;
3902 if (m == 4 && r == 3)
3903 return WinogradConv2DFmr::F_4_3;
3904 if (m == 2 && r == 5)
3905 return WinogradConv2DFmr::F_2_5;
3906 return std::nullopt;
3907}
3908
3909std::pair<int64_t, int64_t> getFmrFromWinogradConv2DFmr(WinogradConv2DFmr fmr) {
3910 switch (fmr) {
3911 case WinogradConv2DFmr::F_2_3:
3912 return {2, 3};
3913 case WinogradConv2DFmr::F_4_3:
3914 return {4, 3};
3915 case WinogradConv2DFmr::F_2_5:
3916 return {2, 5};
3917 }
3918 llvm_unreachable("Unkown WinogradConv2DFmr");
3919}
3920
3921//===----------------------------------------------------------------------===//
3922// MatMulOp
3923//===----------------------------------------------------------------------===//
3924
3925static FailureOr<SmallVector<SmallVector<int64_t>>>
3928 for (auto map : maps) {
3929 AffineMapAttr attr = dyn_cast<AffineMapAttr>(map);
3930 if (!attr)
3931 return failure();
3933 for (auto result : attr.getAffineMap().getResults()) {
3934 auto dim = dyn_cast<AffineDimExpr>(result);
3935 if (!dim)
3936 return failure();
3937 pos.push_back(dim.getPosition());
3938 }
3939 positions.push_back(pos);
3940 }
3941 return positions;
3942}
3943
3944/// Returns a list of AffineMap with the typical matmul indexing charactristic.
3945SmallVector<AffineMap> MatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
3946 AffineExpr d0, d1, d2;
3947 SmallVector<AffineMap> indexingMaps;
3948 bindDims(context, d0, d1, d2);
3949 indexingMaps.push_back(AffineMap::get(3, 0, {d0, d2}, context));
3950 indexingMaps.push_back(AffineMap::get(3, 0, {d2, d1}, context));
3951 indexingMaps.push_back(AffineMap::get(3, 0, {d0, d1}, context));
3952 return indexingMaps;
3953}
3954
3955bool MatmulOp::isDefaultIndexingMaps(Attribute attr) {
3956 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
3957 if (!maps)
3958 return false;
3959 if (maps.size() != 3)
3960 return false;
3961 auto positions = getAffineResultPositions(maps);
3962 if (failed(positions))
3963 return false;
3964 return (*positions)[0] == SmallVector<int64_t>{0, 2} &&
3965 (*positions)[1] == SmallVector<int64_t>{2, 1} &&
3966 (*positions)[2] == SmallVector<int64_t>{0, 1};
3967}
3968
3969SmallVector<utils::IteratorType> MatmulOp::getIteratorTypesArray() {
3970 return SmallVector<utils::IteratorType>{utils::IteratorType::parallel,
3971 utils::IteratorType::parallel,
3972 utils::IteratorType::reduction};
3973}
3974
3975unsigned MatmulOp::getNumRegionArgs() { return 3; }
3976
3977std::string MatmulOp::getLibraryCallName() {
3978 return generateLibraryCallName(getOperation());
3979}
3980
3981bool MatmulOp::hasDynamicIndexingMaps() { return true; }
3982
3983/// Check if the op has broadcast and/or transpose semantic. Returns true if
3984/// the user defined indexing maps are not equal to default map.
3985bool MatmulOp::hasUserDefinedMaps() {
3986 SmallVector<AffineMap, 3> defaultMaps =
3987 getDefaultIndexingMaps(this->getContext());
3988 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
3989 return defaultMaps != explicitMaps;
3990}
3991
3992/// Implements the block region builder for the MatmulOp. This is called by
3993/// 'fillStructuredOpRegion'.
3994void MatmulOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block,
3995 ArrayRef<NamedAttribute> attrs,
3996 function_ref<InFlightDiagnostic()> emitError) {
3997 if (emitError && block.getNumArguments() != 3) {
3998 emitError() << "MatmulOp regionBuilder expects 3 args, got "
3999 << block.getNumArguments();
4000 return;
4001 }
4002 assert(block.getNumArguments() == 3 &&
4003 "MatmulOp regionBuilder expects 3 args");
4004 RegionBuilderHelper helper(b, block);
4005 SmallVector<Value> yields;
4006
4007 TypeFn castVal = TypeFn::cast_signed;
4008 const auto *castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4009 return attr.getName() == "cast";
4010 });
4011 if (castIter != attrs.end()) {
4012 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4013 castVal = attr.getValue();
4014 }
4015
4016 Value value1 = helper.buildTypeFn(castVal, block.getArgument(2).getType(),
4017 block.getArgument(0));
4018 Value value2 = helper.buildTypeFn(castVal, block.getArgument(2).getType(),
4019 block.getArgument(1));
4020 Value value3 = helper.buildBinaryFn(BinaryFn::mul, value1, value2, emitError);
4021 if (!value1 || !value2 || !value3)
4022 return;
4023 Value value4 = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4024 value3, emitError);
4025 if (!value4)
4026 return;
4027 yields.push_back(value4);
4028 helper.yieldOutputs(yields);
4029}
4030
4031/// Returns true if the given bcastMap map is a valid broadcast map. A valid
4032/// broadcast map must include K dimension.
4033/// TODO: Strict inclusion of K dimension in the broadcast map is not
4034/// necessary for both input matrices simultaneously. We can relax this
4035/// condition to have K dimension for one input matrix map and infer the K
4036/// dimension for other input matrix map from the one already having K
4037/// dimension.
4038bool MatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap) {
4039 assert(bcastMap.getNumResults() == 1 && "Expected single result dim expr.");
4040 AffineExpr expr = bcastMap.getResult(0);
4041 // Invalid map if the common dimension of matmul not found.
4042 return expr.isFunctionOfDim(bcastMap.getNumDims() - 1);
4043}
4044
4045static FailureOr<ArrayAttr> parseIndexingMapsAttr(OpAsmParser &parser) {
4046 if (parser.parseOptionalKeyword("indexing_maps"))
4047 return ArrayAttr{
4048 nullptr}; // Success in case indexing_maps was not provided.
4049
4050 ArrayAttr arrayAttr;
4051 if (parser.parseEqual() || parser.parseAttribute(arrayAttr))
4052 return failure();
4053
4054 if (llvm::any_of(arrayAttr,
4055 [](auto elt) { return !dyn_cast<AffineMapAttr>(elt); }))
4056 return parser.emitError(parser.getCurrentLocation())
4057 << "element of indexing_maps array is not an affine_map";
4058
4059 return arrayAttr;
4060}
4061
4062ParseResult MatmulOp::parse(OpAsmParser &parser, OperationState &result) {
4063 FailureOr<ArrayAttr> indexingMapsAttr = parseIndexingMapsAttr(parser);
4064 if (failed(indexingMapsAttr))
4065 return failure();
4066
4067 if (*indexingMapsAttr == nullptr) {
4068 auto indexingMapAttrs = llvm::map_to_vector(
4069 MatmulOp::getDefaultIndexingMaps(parser.getContext()),
4070 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4071 indexingMapsAttr = parser.getBuilder().getArrayAttr(indexingMapAttrs);
4072 }
4073
4074 result.addAttribute("indexing_maps", *indexingMapsAttr);
4075 return parseNamedStructuredOp(parser, result, MatmulOp::getNumRegionArgs(),
4076 MatmulOp::getRegionBuilder());
4077}
4078
4079void MatmulOp::print(OpAsmPrinter &p) {
4080 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
4081 MatmulOp::getDefaultIndexingMaps(getContext()),
4082 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4083 if (!llvm::equal(getIndexingMaps(), indexingMaps))
4084 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4085
4086 std::array<StringRef, 3> elidedAttrs = {
4087 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
4088 printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
4089 elidedAttrs);
4090}
4091
4092/// Verify the user defined indexing maps.
4093LogicalResult MatmulOp::verify() {
4094 // Verification of pure matmul is handled by verifyStructuredOpInterface().
4095 if (!hasUserDefinedMaps())
4096 return success();
4097
4098 for (unsigned opIndex = 0; opIndex < 2; opIndex++) {
4099 if (failed(verifyExtendedMatmulSemantic(*this, opIndex)))
4100 return failure();
4101 }
4102 return success();
4103}
4104
4105LogicalResult MatmulOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
4106 return memref::foldMemRefCast(*this);
4107}
4108
4109void MatmulOp::getEffects(
4110 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4111 &effects) {
4112 if (hasPureTensorSemantics())
4113 return;
4114 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4115}
4116
4117Speculation::Speculatability MatmulOp::getSpeculatability() {
4118 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4119}
4120
4121SmallVector<AffineMap>
4122MatmulTransposeAOp::getDefaultIndexingMaps(OpBuilder &builder) {
4123 AffineExpr d0, d1, d2;
4124 MLIRContext *context = builder.getContext();
4125 bindDims(context, d0, d1, d2);
4126 AffineMap mapLHS = AffineMap::get(3, 0, {d2, d0}, context);
4127 AffineMap mapRHS = AffineMap::get(3, 0, {d2, d1}, context);
4128 AffineMap mapOut = AffineMap::get(3, 0, {d0, d1}, context);
4129 return {mapLHS, mapRHS, mapOut};
4130}
4131
4133 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4134 if (!maps)
4135 return false;
4136 if (maps.size() != 3)
4137 return false;
4138 auto positions = getAffineResultPositions(maps);
4139 if (failed(positions))
4140 return false;
4141 return (*positions)[0] == SmallVector<int64_t>{2, 0} &&
4142 (*positions)[1] == SmallVector<int64_t>{2, 1} &&
4143 (*positions)[2] == SmallVector<int64_t>{0, 1};
4144}
4145
4148 ValueRange inputs, ValueRange outputs,
4149 ArrayRef<NamedAttribute> attributes) {
4150 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4151 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4152}
4153
4156 ValueRange inputs, ValueRange outputs,
4157 ArrayRef<NamedAttribute> attributes) {
4158 OperationState state(location, getOperationName());
4159 build(builder, state, inputs, outputs, attributes);
4160 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4161 assert(res && "builder didn't return the right type");
4162 return res;
4163}
4164
4167 TypeRange resultTensorTypes,
4168 ValueRange inputs, ValueRange outputs,
4169 ArrayRef<NamedAttribute> attributes) {
4170 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4171 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4172}
4173
4176 TypeRange resultTensorTypes, ValueRange inputs,
4177 ValueRange outputs,
4178 ArrayRef<NamedAttribute> attributes) {
4179 OperationState state(location, getOperationName());
4180 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4181 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4182 assert(res && "builder didn't return the right type");
4183 return res;
4184}
4185
4188 TypeRange resultTensorTypes,
4189 ValueRange inputs, ValueRange outputs,
4190 Attribute cast,
4191 ArrayRef<NamedAttribute> attributes) {
4192 result.addAttribute("cast", cast);
4193 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4194 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4195}
4196
4199 TypeRange resultTensorTypes, ValueRange inputs,
4200 ValueRange outputs, Attribute cast,
4201 ArrayRef<NamedAttribute> attributes) {
4202 OperationState state(location, getOperationName());
4203 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4204 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4205 assert(res && "builder didn't return the right type");
4206 return res;
4207}
4208
4210 return dyn_cast_or_null<linalg::MatmulOp>(op) &&
4212 op->getAttr("indexing_maps"));
4213}
4214
4216MatmulTransposeBOp::getDefaultIndexingMaps(OpBuilder &builder) {
4217 AffineExpr d0, d1, d2;
4218 MLIRContext *context = builder.getContext();
4219 bindDims(context, d0, d1, d2);
4220 AffineMap mapLHS = AffineMap::get(3, 0, {d0, d2}, context);
4221 AffineMap mapRHS = AffineMap::get(3, 0, {d1, d2}, context);
4222 AffineMap mapOut = AffineMap::get(3, 0, {d0, d1}, context);
4223 return {mapLHS, mapRHS, mapOut};
4224}
4225
4227 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4228 if (!maps)
4229 return false;
4230 if (maps.size() != 3)
4231 return false;
4232 auto positions = getAffineResultPositions(maps);
4233 if (failed(positions))
4234 return false;
4235 return (*positions)[0] == SmallVector<int64_t>{0, 2} &&
4236 (*positions)[1] == SmallVector<int64_t>{1, 2} &&
4237 (*positions)[2] == SmallVector<int64_t>{0, 1};
4238}
4239
4242 ValueRange inputs, ValueRange outputs,
4243 ArrayRef<NamedAttribute> attributes) {
4244 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4245 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4246}
4247
4250 ValueRange inputs, ValueRange outputs,
4251 ArrayRef<NamedAttribute> attributes) {
4252 OperationState state(location, getOperationName());
4253 build(builder, state, inputs, outputs, attributes);
4254 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4255 assert(res && "builder didn't return the right type");
4256 return res;
4257}
4258
4261 TypeRange resultTensorTypes,
4262 ValueRange inputs, ValueRange outputs,
4263 ArrayRef<NamedAttribute> attributes) {
4264 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4265 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4266}
4267
4270 TypeRange resultTensorTypes, ValueRange inputs,
4271 ValueRange outputs,
4272 ArrayRef<NamedAttribute> attributes) {
4273 OperationState state(location, getOperationName());
4274 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4275 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4276 assert(res && "builder didn't return the right type");
4277 return res;
4278}
4279
4282 TypeRange resultTensorTypes,
4283 ValueRange inputs, ValueRange outputs,
4284 Attribute cast,
4285 ArrayRef<NamedAttribute> attributes) {
4286 result.addAttribute("cast", cast);
4287 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4288 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4289}
4290
4293 TypeRange resultTensorTypes, ValueRange inputs,
4294 ValueRange outputs, Attribute cast,
4295 ArrayRef<NamedAttribute> attributes) {
4296 OperationState state(location, getOperationName());
4297 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4298 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4299 assert(res && "builder didn't return the right type");
4300 return res;
4301}
4302
4304 return dyn_cast_or_null<linalg::MatmulOp>(op) &&
4306 op->getAttr("indexing_maps"));
4307}
4308
4310BatchMatmulTransposeAOp::getDefaultIndexingMaps(OpBuilder &builder) {
4311 AffineExpr d0, d1, d2, d3;
4312 MLIRContext *context = builder.getContext();
4313 bindDims(context, d0, d1, d2, d3);
4314 AffineMap mapLHS = AffineMap::get(4, 0, {d0, d3, d1}, context);
4315 AffineMap mapRHS = AffineMap::get(4, 0, {d0, d3, d2}, context);
4316 AffineMap mapOut = AffineMap::get(4, 0, {d0, d1, d2}, context);
4317 return {mapLHS, mapRHS, mapOut};
4318}
4319
4321 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4322 if (!maps)
4323 return false;
4324 if (maps.size() != 3)
4325 return false;
4326 auto positions = getAffineResultPositions(maps);
4327 if (failed(positions))
4328 return false;
4329 return (*positions)[0] == SmallVector<int64_t>{0, 3, 1} &&
4330 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
4331 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4332}
4333
4335 OpBuilder &builder, OperationState &result, ValueRange inputs,
4336 ValueRange outputs, ArrayRef<NamedAttribute> attributes) {
4337 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4338 BatchMatmulOp::getRegionBuilder(),
4339 getDefaultIndexingMaps(builder));
4340}
4341
4344 ValueRange inputs, ValueRange outputs,
4345 ArrayRef<NamedAttribute> attributes) {
4346 OperationState state(location, getOperationName());
4347 build(builder, state, inputs, outputs, attributes);
4348 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4349 assert(res && "builder didn't return the right type");
4350 return res;
4351}
4352
4354 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4355 ValueRange inputs, ValueRange outputs,
4356 ArrayRef<NamedAttribute> attributes) {
4357 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4358 BatchMatmulOp::getRegionBuilder(),
4359 getDefaultIndexingMaps(builder));
4360}
4361
4364 TypeRange resultTensorTypes, ValueRange inputs,
4365 ValueRange outputs,
4366 ArrayRef<NamedAttribute> attributes) {
4367 OperationState state(location, getOperationName());
4368 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4369 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4370 assert(res && "builder didn't return the right type");
4371 return res;
4372}
4373
4375 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4376 ValueRange inputs, ValueRange outputs, Attribute cast,
4377 ArrayRef<NamedAttribute> attributes) {
4378 result.addAttribute("cast", cast);
4379 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4380 BatchMatmulOp::getRegionBuilder(),
4381 getDefaultIndexingMaps(builder));
4382}
4383
4386 TypeRange resultTensorTypes, ValueRange inputs,
4387 ValueRange outputs, Attribute cast,
4388 ArrayRef<NamedAttribute> attributes) {
4389 OperationState state(location, getOperationName());
4390 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4391 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4392 assert(res && "builder didn't return the right type");
4393 return res;
4394}
4395
4397 return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
4399 op->getAttr("indexing_maps"));
4400}
4401
4403BatchMatmulTransposeBOp::getDefaultIndexingMaps(OpBuilder &builder) {
4404 AffineExpr d0, d1, d2, d3;
4405 MLIRContext *context = builder.getContext();
4406 bindDims(context, d0, d1, d2, d3);
4407 AffineMap mapLHS = AffineMap::get(4, 0, {d0, d1, d3}, context);
4408 AffineMap mapRHS = AffineMap::get(4, 0, {d0, d2, d3}, context);
4409 AffineMap mapOut = AffineMap::get(4, 0, {d0, d1, d2}, context);
4410 return {mapLHS, mapRHS, mapOut};
4411}
4412
4414 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4415 if (!maps)
4416 return false;
4417 if (maps.size() != 3)
4418 return false;
4419 auto positions = getAffineResultPositions(maps);
4420 if (failed(positions))
4421 return false;
4422 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
4423 (*positions)[1] == SmallVector<int64_t>{0, 2, 3} &&
4424 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4425}
4426
4428 OpBuilder &builder, OperationState &result, ValueRange inputs,
4429 ValueRange outputs, ArrayRef<NamedAttribute> attributes) {
4430 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4431 BatchMatmulOp::getRegionBuilder(),
4432 getDefaultIndexingMaps(builder));
4433}
4434
4437 ValueRange inputs, ValueRange outputs,
4438 ArrayRef<NamedAttribute> attributes) {
4439 OperationState state(location, getOperationName());
4440 build(builder, state, inputs, outputs, attributes);
4441 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4442 assert(res && "builder didn't return the right type");
4443 return res;
4444}
4445
4447 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4448 ValueRange inputs, ValueRange outputs,
4449 ArrayRef<NamedAttribute> attributes) {
4450 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4451 BatchMatmulOp::getRegionBuilder(),
4452 getDefaultIndexingMaps(builder));
4453}
4454
4457 TypeRange resultTensorTypes, ValueRange inputs,
4458 ValueRange outputs,
4459 ArrayRef<NamedAttribute> attributes) {
4460 OperationState state(location, getOperationName());
4461 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4462 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4463 assert(res && "builder didn't return the right type");
4464 return res;
4465}
4466
4468 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4469 ValueRange inputs, ValueRange outputs, Attribute cast,
4470 ArrayRef<NamedAttribute> attributes) {
4471 result.addAttribute("cast", cast);
4472 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4473 BatchMatmulOp::getRegionBuilder(),
4474 getDefaultIndexingMaps(builder));
4475}
4476
4479 TypeRange resultTensorTypes, ValueRange inputs,
4480 ValueRange outputs, Attribute cast,
4481 ArrayRef<NamedAttribute> attributes) {
4482 OperationState state(location, getOperationName());
4483 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4484 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4485 assert(res && "builder didn't return the right type");
4486 return res;
4487}
4488
4490 return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
4492 op->getAttr("indexing_maps"));
4493}
4494
4495//===----------------------------------------------------------------------===//
4496// ContractOp
4497//===----------------------------------------------------------------------===//
4498
4499SmallVector<utils::IteratorType> ContractOp::getIteratorTypesArray() {
4500 AffineMap outAffineMap = getIndexingMapsArray().pop_back_val();
4501 // On well-formed IR, indexing_maps is non-empty, contained affine_maps'
4502 // domains are all the same, and each implements a projected permutation.
4503 // Each iteration space dim must occur for at least one operand and either
4504 // takes part in a contraction/reduction or else has parallel iteration type.
4505 // We have that a dim is a contraction/reduction dim if and only if the dim
4506 // occurs for the output operand. We use this fact for fast inference:
4507 // NB: In case we allow dims to occur solely for one input, the above still
4508 // holds: per the einsum semantics, these are reduction dims as well.
4509 SmallVector<bool> dimsInOutput(outAffineMap.getNumDims(), false);
4510 for (auto result : outAffineMap.getResults()) {
4511 auto dimExpr = dyn_cast<AffineDimExpr>(result);
4512 assert(dimExpr && "affine_map is a projected permutation");
4513 dimsInOutput[dimExpr.getPosition()] = true;
4514 }
4515
4517 for (auto dimOccursInOutput : dimsInOutput)
4518 iteratorTypes.push_back(dimOccursInOutput ? utils::IteratorType::parallel
4519 : utils::IteratorType::reduction);
4520
4521 return iteratorTypes;
4522}
4523
4524unsigned ContractOp::getNumRegionArgs() { return 3; }
4525
4526/// Implement block region builder, which is called by 'fillStructuredOpRegion'.
4527void ContractOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block,
4528 ArrayRef<NamedAttribute> attrs,
4529 function_ref<InFlightDiagnostic()> emitError) {
4530 if (emitError && block.getNumArguments() != 3) {
4531 emitError() << "ContractOp regionBuilder expects 3 args, got "
4532 << block.getNumArguments();
4533 return;
4534 }
4535 assert(block.getNumArguments() == 3 &&
4536 "ContractOp regionBuilder expects 3 args");
4537 RegionBuilderHelper helper(b, block);
4538
4539 TypeFn castSignedness = TypeFn::cast_signed;
4540 auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4541 return attr.getName() == "cast";
4542 });
4543 if (castIter != attrs.end()) {
4544 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4545 castSignedness = attr.getValue();
4546 }
4547
4548 // TODO: Support fields with operators besides mult & add.
4549 Type outType = block.getArgument(2).getType();
4550 Value lhsAtOutType =
4551 helper.buildTypeFn(castSignedness, outType, block.getArgument(0));
4552 Value rhsAtOutType =
4553 helper.buildTypeFn(castSignedness, outType, block.getArgument(1));
4554 Value productAtOutType = helper.buildBinaryFn(BinaryFn::mul, lhsAtOutType,
4555 rhsAtOutType, emitError);
4556 if (!productAtOutType)
4557 return;
4558 Value result = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4559 productAtOutType, emitError);
4560 if (!result)
4561 return;
4562 helper.yieldOutputs({result});
4563}
4564
4565ParseResult ContractOp::parse(OpAsmParser &parser, OperationState &result) {
4566 FailureOr<ArrayAttr> indexingMapsAttr = parseIndexingMapsAttr(parser);
4567 if (failed(indexingMapsAttr) || *indexingMapsAttr == nullptr)
4568 return parser.emitError(parser.getCurrentLocation(),
4569 "expected 'indexing_maps' attribute");
4570 result.addAttribute("indexing_maps", *indexingMapsAttr);
4571
4572 return parseNamedStructuredOp(parser, result, getNumRegionArgs(),
4573 regionBuilder);
4574}
4575
4576void ContractOp::print(OpAsmPrinter &p) {
4577 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4579 p, getOperation(), getInputs(), getOutputs(),
4580 /*elidedAttrs=*/{"indexing_maps", "operandSegmentSizes"});
4581}
4582
4583LogicalResult ContractOp::verify() {
4584 int iterationSpaceDims = -1;
4585 // Map iter space dims to #occurrences in inputs' and output's affine_maps:
4586 // e.g., inOccurrences[0] will hold #times that dim (with index) 0 is used to
4587 // access an input operand (so occurrence count can be at most 2) and
4588 // outOccurrences[1] will indicate whether dim 1 occurred in the output, etc.
4589 SmallVector<size_t> inOccurrences;
4590 SmallVector<size_t> outOccurrences;
4591
4592 // A helper so that for each operand's affine_map and type we check that ...
4593 auto checkAffineMapAndType = [&](AffineMap affineMap, Type operandType,
4594 bool isInput) -> LogicalResult {
4595 // ... the affine_map is a projected permutation;
4596 if (!affineMap.isProjectedPermutation())
4597 return emitError("provided affine_map is not a projected permutation");
4598
4599 // ... the rank of the affine_map's results and corresponding type match;
4600 if (auto shapedType = dyn_cast<ShapedType>(operandType)) {
4601 if (affineMap.getNumResults() != shapedType.getRank())
4602 return emitError("ranks of shaped operand and results of corresponding "
4603 "affine_map differ");
4604 } else if (affineMap.getNumResults() != 0) {
4605 return emitError("affine_map specifies shaped access while operand has "
4606 "non-shaped type");
4607 }
4608
4609 // ... the rank of the affine_map's domain is the same as those seen prior;
4610 if (iterationSpaceDims == -1) {
4611 iterationSpaceDims = affineMap.getNumDims();
4612 inOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
4613 outOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
4614 } else if (iterationSpaceDims != (int)affineMap.getNumDims()) {
4615 return emitError("iteration spaces of provided affine_maps differ");
4616 }
4617
4618 // ... update counts of dims used to access either an input or the output.
4619 for (AffineExpr affineExpr : affineMap.getResults()) {
4620 auto affineDimExpr = dyn_cast<AffineDimExpr>(affineExpr);
4621 if (!affineDimExpr)
4622 llvm_unreachable("affine_map is a projected permutation");
4623
4624 if (isInput)
4625 inOccurrences[affineDimExpr.getPosition()] += 1;
4626 else
4627 outOccurrences[affineDimExpr.getPosition()] += 1;
4628 }
4629
4630 return success();
4631 };
4632
4633 for (auto &&[affineMap, operandType, isInput] :
4634 llvm::zip(getIndexingMapsArray(), getOperandTypes(),
4635 SmallVector<bool>{true, true, false})) {
4636 if (failed(checkAffineMapAndType(affineMap, operandType, isInput)))
4637 return failure(); // NB: checkAffineMapAndType will emit relevant error.
4638 }
4639
4640 bool hasContractingDim = false;
4641 for (size_t dimIndex = 0; dimIndex < (size_t)iterationSpaceDims; dimIndex++) {
4642 size_t inOccCount = inOccurrences[dimIndex];
4643 size_t outOccCount = outOccurrences[dimIndex];
4644
4645 // We have a contracting dim if and only if ...
4646 hasContractingDim |= inOccCount == 2 && outOccCount == 0;
4647
4648 if (inOccCount == 0 && outOccCount == 0)
4649 return emitError() << "iteration space dim at index " << dimIndex
4650 << " not used to access any operand";
4651
4652 // NB: We disallow a dim which occurs for only one input operand and not
4653 // for the output. In terms of einsum semantics such dims have a
4654 // sensible meaning - namely an additional reduction per each such dim.
4655 // By contrast, the ContractionOpInterface does not know about this
4656 // iter type - cf. inferContractionDims' supported dim kinds. Similarly,
4657 // while vector.contract's verifier accepts dims of this kind many of
4658 // its lowerings give up on encountering these dims.
4659 // TODO: Remove following once we have comprehensive support for input-only
4660 // reduction dims, at both the linalg- and vector-dialect levels.
4661 if (inOccCount == 1 && outOccCount != 1)
4662 return emitError()
4663 << "iteration space dim at index " << dimIndex
4664 << " is neither a contracting dim nor of parallel iteration type";
4665 }
4666
4667 if (!hasContractingDim)
4668 return emitError("'indexing_maps' do not specify a contracting dimension");
4669
4670 return success();
4671}
4672
4673LogicalResult ContractOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
4674 return memref::foldMemRefCast(*this);
4675}
4676
4677void ContractOp::getEffects(
4678 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4679 &effects) {
4680 if (hasPureTensorSemantics())
4681 return;
4682 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4683}
4684
4685Speculation::Speculatability ContractOp::getSpeculatability() {
4686 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4687}
4688
4689//===----------------------------------------------------------------------===//
4690// Implementation of BatchMatmulOp
4691//===----------------------------------------------------------------------===//
4692SmallVector<AffineMap>
4693BatchMatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
4694 AffineExpr d0, d1, d2, d3;
4695 SmallVector<AffineMap> indexingMaps;
4696 bindDims(context, d0, d1, d2, d3);
4697 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d3}, context));
4698 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d3, d2}, context));
4699 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d2}, context));
4700 return indexingMaps;
4701}
4702
4703bool BatchMatmulOp::isDefaultIndexingMaps(Attribute attr) {
4704 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4705 if (!maps)
4706 return false;
4707 if (maps.size() != 3)
4708 return false;
4709 auto positions = getAffineResultPositions(maps);
4710 if (failed(positions))
4711 return false;
4712 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
4713 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
4714 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4715}
4716
4717SmallVector<utils::IteratorType> BatchMatmulOp::getIteratorTypesArray() {
4718 return SmallVector<utils::IteratorType>{
4719 utils::IteratorType::parallel, utils::IteratorType::parallel,
4720 utils::IteratorType::parallel, utils::IteratorType::reduction};
4721}
4722
4723unsigned BatchMatmulOp::getNumRegionArgs() { return 3; }
4724
4725std::string BatchMatmulOp::getLibraryCallName() {
4726 return generateLibraryCallName(getOperation());
4727}
4728
4729/// Check if the op has broadcast and/or transpose semantic. Returns true if
4730/// the user defined indexing maps are not equal to default map.
4731bool BatchMatmulOp::hasUserDefinedMaps() {
4732 SmallVector<AffineMap, 3> defaultMaps =
4733 getDefaultIndexingMaps(this->getContext());
4734 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
4735 return defaultMaps != explicitMaps;
4736}
4737
4738/// Returns true if the given bcastMap map is a valid broadcast map. A valid
4739/// broadcast map must include K dimension.
4740/// TODO: Strict inclusion of K dimension in the broadcast map is not
4741/// necessary for both input matrices simultaneously. We can relax this
4742/// condition to have K dimension for one input matrix map and infer the K
4743/// dimension for other input matrix map from the one already having K
4744/// dimension.
4745bool BatchMatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap, bool isLHS) {
4746 assert(bcastMap.getNumResults() < 3 &&
4747 "Expected less than 3 result dim expr.");
4748 bool isValid = false;
4749 enum Indices { batchPos, mPos, nPos, kPos };
4750 if (bcastMap.getNumResults() == 1) {
4751 AffineExpr expr = bcastMap.getResult(0);
4752 isValid = expr.isFunctionOfDim(kPos);
4753 } else if (bcastMap.getNumResults() == 2) {
4754 AffineExpr expr0 = bcastMap.getResult(0);
4755 AffineExpr expr1 = bcastMap.getResult(1);
4756 isValid =
4757 isLHS ? ((expr0.isFunctionOfDim(batchPos) ||
4758 expr0.isFunctionOfDim(mPos)) &&
4759 expr1.isFunctionOfDim(kPos))
4760 : ((expr0.isFunctionOfDim(batchPos) &&
4761 expr1.isFunctionOfDim(kPos)) ||
4762 (expr0.isFunctionOfDim(kPos) && expr1.isFunctionOfDim(nPos)));
4763 }
4764 return isValid;
4765}
4766
4767void BatchMatmulOp::regionBuilder(
4768 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
4769 function_ref<InFlightDiagnostic()> emitError) {
4770 if (emitError && block.getNumArguments() != 3) {
4771 emitError() << "BatchMatmulOp regionBuilder expects 3 args, got "
4772 << block.getNumArguments();
4773 return;
4774 }
4775 assert(block.getNumArguments() == 3 &&
4776 "BatchMatmulOp regionBuilder expects 3 args");
4777 RegionBuilderHelper helper(b, block);
4778 SmallVector<Value> yields;
4779
4780 TypeFn castVal = TypeFn::cast_signed;
4781 auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4782 return attr.getName() == "cast";
4783 });
4784 if (castIter != attrs.end()) {
4785 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4786 castVal = attr.getValue();
4787 }
4788
4789 auto toType = block.getArgument(2).getType();
4790 Value castValA = helper.buildTypeFn(castVal, toType, block.getArgument(0));
4791 Value castValB = helper.buildTypeFn(castVal, toType, block.getArgument(1));
4792 Value mulVal =
4793 helper.buildBinaryFn(BinaryFn::mul, castValA, castValB, emitError);
4794 if (!castValA || !castValB || !mulVal)
4795 return;
4796 Value addVal = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4797 mulVal, emitError);
4798 if (!addVal)
4799 return;
4800 yields.push_back(addVal);
4801 helper.yieldOutputs(yields);
4802}
4803
4804ParseResult BatchMatmulOp::parse(OpAsmParser &parser, OperationState &result) {
4805 SmallVector<Attribute, 3> indexingMapsAttr;
4806 Attribute mapAttr;
4807 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
4808 if (parser.parseEqual())
4809 return failure();
4810
4811 if (parser.parseLSquare())
4812 return failure();
4813
4814 do {
4815 if (parser.parseAttribute(mapAttr))
4816 return failure();
4817 if (!isa<AffineMapAttr>(mapAttr)) {
4818 return parser.emitError(parser.getCurrentLocation(),
4819 "expected affine map attribute");
4820 }
4821 indexingMapsAttr.push_back(mapAttr);
4822
4823 if (parser.parseOptionalComma())
4824 break;
4825 } while (true);
4826
4827 if (parser.parseRSquare())
4828 return failure();
4829 }
4830 // Initialize indexingMaps, if not supplied explicitly.
4831 if (indexingMapsAttr.empty()) {
4832 indexingMapsAttr = llvm::map_to_vector(
4833 BatchMatmulOp::getDefaultIndexingMaps(parser.getContext()),
4834 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4835 }
4836 result.addAttribute("indexing_maps",
4837 parser.getBuilder().getArrayAttr(indexingMapsAttr));
4838
4839 return ::parseNamedStructuredOp(parser, result,
4840 BatchMatmulOp::getNumRegionArgs(),
4841 BatchMatmulOp::getRegionBuilder());
4842}
4843
4844void BatchMatmulOp::print(OpAsmPrinter &p) {
4845 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
4846 BatchMatmulOp::getDefaultIndexingMaps(getContext()),
4847 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4848 if (!llvm::equal(getIndexingMaps(), indexingMaps))
4849 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4850
4851 std::array<StringRef, 3> elidedAttrs = {
4852 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
4853 ::printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
4854 elidedAttrs);
4855}
4856
4857/// Verify the user defined indexing maps.
4858LogicalResult BatchMatmulOp::verify() {
4859 // Verification of pure batch_matmul is handled by
4860 // verifyStructuredOpInterface().
4861 if (!hasUserDefinedMaps())
4862 return success();
4863
4864 for (unsigned opIndex = 0; opIndex < 3; opIndex++) {
4866 return failure();
4867 }
4868 return success();
4869}
4870
4871LogicalResult BatchMatmulOp::fold(FoldAdaptor,
4872 SmallVectorImpl<OpFoldResult> &) {
4873 return memref::foldMemRefCast(*this);
4874}
4875
4876void BatchMatmulOp::getEffects(
4877 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4878 &effects) {
4879 if (hasPureTensorSemantics())
4880 return;
4881 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4882}
4883
4884Speculation::Speculatability BatchMatmulOp::getSpeculatability() {
4885 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4886}
4887
4888//===----------------------------------------------------------------------===//
4889// ElementwiseOp
4890//===----------------------------------------------------------------------===//
4891//
4892namespace {
4893struct ArityGroupAndKind {
4894 // The enum class {Unary, Binary, Ternary, ..}
4895 ElementwiseArityGroup arityGroup;
4896
4897 // The kind (e.g. `exp` or `add`) belonging to the arity group.
4898 union Kind {
4899 UnaryFn unaryFn;
4900 BinaryFn binaryFn;
4901 TernaryFn ternaryFn;
4902 } kind;
4903};
4904
4905unsigned getArityGroupAsUInt(ElementwiseArityGroup arityGroup) {
4906 return static_cast<unsigned>(arityGroup);
4907}
4908} // namespace
4909
4910static ArityGroupAndKind getArityGroupAndKind(ElementwiseKind kind) {
4911 constexpr int lastUnary = static_cast<int>(ElementwiseCaseLimits::LastUnary);
4912 constexpr int lastBinary =
4913 static_cast<int>(ElementwiseCaseLimits::LastBinary);
4914 constexpr int lastTernary =
4915 static_cast<int>(ElementwiseCaseLimits::LastTernary);
4916
4917 int val = static_cast<int>(kind);
4918 ArityGroupAndKind result;
4919
4920 if (val < lastUnary) {
4921 result.arityGroup = ElementwiseArityGroup::Unary;
4922 result.kind.unaryFn = static_cast<UnaryFn>(val);
4923 return result;
4924 }
4925 if (val < lastBinary) {
4926 result.arityGroup = ElementwiseArityGroup::Binary;
4927 result.kind.binaryFn = static_cast<BinaryFn>(val - lastUnary);
4928 return result;
4929 }
4930 if (val >= lastTernary) {
4931 llvm_unreachable("unhandled ElementwiseFn");
4932 }
4933 result.arityGroup = ElementwiseArityGroup::Ternary;
4934 result.kind.ternaryFn = static_cast<TernaryFn>(val - lastBinary);
4935 return result;
4936}
4937
4938SmallVector<utils::IteratorType> ElementwiseOp::getIteratorTypesArray() {
4939 auto rank = getResultRank();
4940 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
4941}
4942
4944ElementwiseOp::getDefaultIndexingMaps(unsigned numMaps, unsigned numDims,
4945 MLIRContext *context) {
4946 auto map = AffineMap::getMultiDimIdentityMap(numDims, context);
4947 return SmallVector<AffineMap>(numMaps, map);
4948}
4949
4950ParseResult ElementwiseOp::parse(OpAsmParser &parser, OperationState &result) {
4951 // Expect e.g. `kind = #linalg.elemwise_kind<add>`
4952 Attribute attr;
4953 mlir::linalg::ElementwiseKind elemwiseKindVal;
4954 if (parser.parseKeyword("kind") || parser.parseEqual())
4955 return failure();
4956
4957 if (succeeded(parser.parseAttribute(attr))) {
4958 auto elemwiseKindAttr = dyn_cast<ElementwiseKindAttr>(attr);
4959 if (!elemwiseKindAttr)
4960 return parser.emitError(parser.getCurrentLocation(),
4961 "expected ElementwiseKind attribute");
4962 elemwiseKindVal = elemwiseKindAttr.getValue();
4963 } else {
4964 return parser.emitError(parser.getCurrentLocation(),
4965 "expected operation 'kind' attribute");
4966 }
4967 result.addAttribute(
4968 "kind", ElementwiseKindAttr::get(parser.getContext(), elemwiseKindVal));
4969
4970 // Parse optional `indexing_maps`
4971 SmallVector<Attribute, 3> indexingMapsAttr;
4972 Attribute mapAttr;
4973 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
4974 if (parser.parseEqual())
4975 return failure();
4976 if (parser.parseLSquare())
4977 return failure();
4978 do {
4979 if (parser.parseAttribute(mapAttr))
4980 return failure();
4981 if (!isa<AffineMapAttr>(mapAttr))
4982 return parser.emitError(parser.getCurrentLocation(),
4983 "expected affine map attribute");
4984 indexingMapsAttr.push_back(mapAttr);
4985 if (parser.parseOptionalComma())
4986 break;
4987 } while (true);
4988 if (parser.parseRSquare())
4989 return failure();
4990 }
4991 // At this stage of parsing the only way to infer number of region
4992 // args is through op kind, as input output tensors are not parsed yet.
4993 auto arityGroupAndKind = getArityGroupAndKind(elemwiseKindVal);
4994 int numRegionArgs =
4995 getArityGroupAsUInt(arityGroupAndKind.arityGroup) + 1 /*output*/;
4996 if (parseNamedStructuredOp(parser, result, numRegionArgs,
4997 ElementwiseOp::getRegionBuilder())) {
4998 return parser.emitError(parser.getCurrentLocation(),
4999 "unable to parse elemwise op");
5000 }
5001
5002 // Initialize indexingMaps, if not supplied explicitly.
5003 if (indexingMapsAttr.empty()) {
5004 // We need to infer the numDims of the indexing maps from the output
5005 // type which is already parsed by now.
5006 auto resultType = result.operands[result.operands.size() - 1].getType();
5007 auto shapedType = llvm::dyn_cast<ShapedType>(resultType);
5008 if (!shapedType)
5009 return parser.emitError(parser.getCurrentLocation(),
5010 "return type needs to be shaped type");
5011 auto numDims = shapedType.getRank();
5012 indexingMapsAttr = llvm::map_to_vector(
5013 ElementwiseOp::getDefaultIndexingMaps(numRegionArgs, numDims,
5014 parser.getContext()),
5015 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
5016 }
5017
5018 result.addAttribute("indexing_maps",
5019 parser.getBuilder().getArrayAttr(indexingMapsAttr));
5020 return success();
5021}
5022
5023void ElementwiseOp::print(OpAsmPrinter &p) {
5024 p << " kind=";
5025 p.printAttribute(getKindAttr());
5026 SmallVector<StringRef, 3> elidedAttrs = {"operandSegmentSizes", "kind",
5027 "indexing_maps"};
5028 unsigned arity =
5029 getArityGroupAsUInt(getArityGroupAndKind(getKind()).arityGroup);
5030 unsigned numDims = getResultRank();
5031
5032 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
5033 ElementwiseOp::getDefaultIndexingMaps(arity + 1 /*output*/, numDims,
5034 getContext()),
5035 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
5036
5037 if (!llvm::equal(getIndexingMaps(), indexingMaps))
5038 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
5039
5040 printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
5041 elidedAttrs);
5042}
5043
5044/// Implements the block region builder for the ElementwiseOp. This is called by
5045/// 'fillStructuredOpRegion'.
5046void ElementwiseOp::regionBuilder(
5047 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
5048 function_ref<InFlightDiagnostic()> emitError) {
5049 ElementwiseKind elemwiseKind;
5050 for (auto attr : attrs) {
5051 if (attr.getName() == b.getStringAttr("kind")) {
5052 auto kindAttr = dyn_cast<ElementwiseKindAttr>(attr.getValue());
5053 assert(kindAttr && "op kind attribute incorrectly set");
5054 elemwiseKind = kindAttr.getValue();
5055 break;
5056 }
5057 }
5058
5059 ArityGroupAndKind groupAndKind = getArityGroupAndKind(elemwiseKind);
5060 auto arityGroup = groupAndKind.arityGroup;
5061 auto kind = groupAndKind.kind;
5062 if (emitError && block.getNumArguments() !=
5063 getArityGroupAsUInt(arityGroup) + 1 /*output*/) {
5064 emitError() << "Elementwise regionBuilder expects "
5065 << (getArityGroupAsUInt(arityGroup) + 1) << " args, got "
5066 << block.getNumArguments();
5067 return;
5068 }
5069 assert(block.getNumArguments() ==
5070 getArityGroupAsUInt(arityGroup) + 1 /*output*/
5071 && "Elementwise regionBuilder number of block args mismatch");
5072
5073 RegionBuilderHelper helper(b, block);
5074 SmallVector<Value> yields;
5075 Value result;
5076
5077 if (arityGroup == ElementwiseArityGroup::Unary) {
5078 result = helper.buildUnaryFn(kind.unaryFn, block.getArgument(0));
5079
5080 } else if (arityGroup == ElementwiseArityGroup::Binary) {
5081 result = helper.buildBinaryFn(kind.binaryFn, block.getArgument(0),
5082 block.getArgument(1));
5083
5084 } else if (arityGroup == ElementwiseArityGroup::Ternary) {
5085 result = helper.buildTernaryFn(kind.ternaryFn, block.getArgument(0),
5086 block.getArgument(1), block.getArgument(2));
5087
5088 } else {
5089 assert(false && "found unhandled category in elemwise");
5090 }
5091
5092 yields.push_back(result);
5093 helper.yieldOutputs(yields);
5094}
5095
5096LogicalResult ElementwiseOp::fold(FoldAdaptor,
5097 SmallVectorImpl<OpFoldResult> &) {
5098 return memref::foldMemRefCast(*this);
5099}
5100
5101void ElementwiseOp::getEffects(
5102 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
5103 &effects) {
5104 if (hasPureTensorSemantics())
5105 return;
5106 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
5107}
5108
5109Speculation::Speculatability ElementwiseOp::getSpeculatability() {
5110 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
5111}
5112
5113//===----------------------------------------------------------------------===//
5114// PackOp/UnPackOp Common
5115//===----------------------------------------------------------------------===//
5116
5117template <typename OpTy, typename>
5118SmallVector<int64_t>
5120 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5121 ? packOrUnPack.getDestType()
5122 : packOrUnPack.getSourceType();
5123 ShapedType unpackedType = (std::is_same<OpTy, PackOp>::value)
5124 ? packOrUnPack.getSourceType()
5125 : packOrUnPack.getDestType();
5127 packedType.getShape().take_front(unpackedType.getRank()));
5128 if (!packOrUnPack.getOuterDimsPerm().empty()) {
5130 result, invertPermutationVector(packOrUnPack.getOuterDimsPerm()));
5131 }
5132 return result;
5133}
5138
5139// Given the (potentially) updated packed type, `newPackedTy`, generates an
5140// updated mixed-tile-sizes list. For each inner packed dimension that is static
5141// in `newPackedTy`, the tile is set to that static size (replacing SSA values
5142// or mismatched constants). Dynamic packed dimensions preserve the original
5143// tile. The folded tensor type is treated as authoritative for static extents.
5144// Note - packed-type-dim and mixed-tile-size should always match!
5147 ArrayRef<OpFoldResult> mixedTiles) {
5148 SmallVector<OpFoldResult> newMixedTileSizes;
5149 for (auto it : llvm::zip(cast<ShapedType>(newPackedTy)
5150 .getShape()
5151 .take_back(mixedTiles.size()),
5152 mixedTiles)) {
5153 int64_t dimSize = std::get<0>(it);
5154 if (dimSize == ShapedType::kDynamic) {
5155 newMixedTileSizes.push_back(std::get<1>(it));
5156 continue;
5157 }
5158 newMixedTileSizes.push_back(rewriter.getIndexAttr(dimSize));
5159 }
5160
5161 return newMixedTileSizes;
5162}
5163
5164template <typename OpTy>
5165static LogicalResult
5167 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
5168 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5169 "applies to only pack or unpack operations");
5170 int64_t destRank = op.getDestRank();
5171 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(destRank));
5172 for (auto dim : llvm::seq<int64_t>(0, destRank))
5173 reifiedReturnShapes[0][dim] =
5174 createFoldedDimOp(builder, op.getLoc(), op.getDest(), dim);
5175 return success();
5176}
5177
5178template <typename OpTy>
5180 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5181 "applies to only pack or unpack operations");
5182 DenseMap<int64_t, OpFoldResult> dimAndTileMapping;
5183 ArrayRef<int64_t> dimsToTile = op.getInnerDimsPos();
5184 SmallVector<OpFoldResult> tiles = op.getMixedTiles();
5185 assert(tiles.size() == dimsToTile.size() &&
5186 "tiles must match indices of dimension to block");
5187 // bind the dimension `i` with the tile factor.
5188 for (auto i : llvm::seq<int64_t>(0, dimsToTile.size()))
5189 dimAndTileMapping[dimsToTile[i]] = tiles[i];
5190 return dimAndTileMapping;
5191}
5192
5193template <typename OpTy>
5195 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5196 "applies to only pack or unpack operations");
5197 Builder builder(op);
5198 SmallVector<OpFoldResult> mixedInnerTiles;
5199 unsigned dynamicValIndex = 0;
5200 for (int64_t staticTile : op.getStaticInnerTiles()) {
5201 if (ShapedType::isStatic(staticTile))
5202 mixedInnerTiles.push_back(builder.getI64IntegerAttr(staticTile));
5203 else
5204 mixedInnerTiles.push_back(op.getInnerTiles()[dynamicValIndex++]);
5205 }
5206 return mixedInnerTiles;
5207}
5208
5209template <typename OpTy>
5211 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5212 "applies to only pack or unpack operations");
5213 SmallVector<Value> dynamicTiles;
5214 SmallVector<int64_t> staticTiles;
5215 dispatchIndexOpFoldResults(op.getMixedTiles(), dynamicTiles, staticTiles);
5216 return staticTiles;
5217}
5218
5219/// Returns true if `dimsPos` is invalid. It is invalid when:
5220/// a) It contains duplicate.
5221/// b) At least one dimension is out of bound (`dimPos` is >= 0 and < rank).
5222/// c) The number of elements in `dimsPos` is > than `rank`.
5224 size_t rank) {
5225 size_t dimsPosSize = dimsPos.size();
5226 if (dimsPosSize > rank)
5227 return true;
5228 DenseSet<int64_t> uniqued(llvm::from_range, dimsPos);
5229 if (dimsPosSize != uniqued.size())
5230 return true;
5231 return llvm::any_of(dimsPos, [rank](int64_t dimPos) {
5232 return dimPos < 0 || dimPos >= static_cast<int64_t>(rank);
5233 });
5234}
5235
5236template <typename OpTy>
5237static LogicalResult commonVerifierPackAndUnPackOp(OpTy packOrUnPack) {
5238 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5239 "applies to only pack or unpack operations");
5240 Operation *op = packOrUnPack.getOperation();
5241
5242 // Return true if we have a zero-value tile.
5243 auto hasZeros = [&](ArrayRef<OpFoldResult> tiles) {
5244 return llvm::any_of(tiles, [](OpFoldResult tile) {
5245 return isa<Attribute>(tile) && isZeroInteger(tile);
5246 });
5247 };
5248
5249 // Verify that the source and destination are ranked types.
5250 if (!packOrUnPack.getSourceType().hasRank() ||
5251 !packOrUnPack.getDestType().hasRank())
5252 return op->emitError("expected both source and destination to have rank");
5253
5254 // Verify that the Operation does not have mixed tensor/buffer semantics.
5255 if (!packOrUnPack.hasPureBufferSemantics() &&
5256 !packOrUnPack.hasPureTensorSemantics())
5257 return op->emitError("mixing tensor and buffer semantics is not allowed");
5258 const unsigned numResults = packOrUnPack.getNumResults();
5259 if (packOrUnPack.hasPureTensorSemantics() && numResults != 1)
5260 return op->emitError("expected 1 result, got ") << numResults;
5261 if (packOrUnPack.hasPureBufferSemantics() && numResults != 0)
5262 return op->emitError("expected 0 results, got ") << numResults;
5263
5264 // Verify tiles. Do not allow zero tiles.
5265 SmallVector<OpFoldResult> mixedTiles = packOrUnPack.getMixedTiles();
5266 if (hasZeros(mixedTiles))
5267 return op->emitError("invalid zero tile factor");
5268
5269 // Verify inner_dims_pos and outer_dims_perm.
5270 ShapedType unpackedType = (std::is_same<OpTy, PackOp>::value)
5271 ? packOrUnPack.getSourceType()
5272 : packOrUnPack.getDestType();
5273 size_t unpackedRank = unpackedType.getRank();
5274 ArrayRef<int64_t> innerDimsPos = packOrUnPack.getInnerDimsPos();
5275 ArrayRef<int64_t> outerDimPerm = packOrUnPack.getOuterDimsPerm();
5276 if (isInvalidPackingPosSpecification(innerDimsPos, unpackedRank))
5277 return op->emitError("invalid inner_dims_pos vector");
5278 if (isInvalidPackingPosSpecification(outerDimPerm, unpackedRank))
5279 return op->emitError("invalid outer_dims_perm vector");
5280 if (!outerDimPerm.empty() && outerDimPerm.size() != unpackedRank)
5281 return op->emitError("outer_dims_perm must be a permutation or empty");
5282
5283 // Tiling factors must be less than or equal to the input rank for pack (or
5284 // output rank for unpack), and must match the number of `inner_dims_pos`.
5285 if (mixedTiles.size() > unpackedRank) {
5286 return op->emitError("tiling factors must be less than or equal to the "
5287 "input rank for pack or output rank for unpack");
5288 }
5289 if (mixedTiles.size() != innerDimsPos.size()) {
5290 return op->emitError(
5291 "tiling factors must equal the number of dimensions to tile");
5292 }
5293
5294 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5295 ? packOrUnPack.getDestType()
5296 : packOrUnPack.getSourceType();
5297 size_t packedRank = packedType.getRank();
5298 // Require output rank to match input rank + number of blocking factors.
5299 size_t expectedPackedRank = unpackedRank + mixedTiles.size();
5300 if (expectedPackedRank != packedRank) {
5301 return op->emitError(
5302 "packed rank != (unpacked rank + num tiling factors), got ")
5303 << packedRank << " != " << expectedPackedRank;
5304 }
5305
5306 // Verify result shape is greater than the minimum expected
5307 // by the pack operation, and that the output shape
5308 // represents full tiles.
5309 SmallVector<int64_t> expectedPackedShape = PackOp::inferPackedShape(
5310 unpackedType.getShape(), packOrUnPack.getStaticTiles(),
5311 packOrUnPack.getInnerDimsPos(), packOrUnPack.getOuterDimsPerm());
5312 for (auto it : llvm::enumerate(llvm::zip(
5313 packedType.getShape().take_back(mixedTiles.size()), mixedTiles))) {
5314 int64_t dimSize = std::get<0>(it.value());
5315 if (Attribute attr =
5316 llvm::dyn_cast_if_present<Attribute>(std::get<1>(it.value()))) {
5317 IntegerAttr intAttr = dyn_cast_or_null<IntegerAttr>(attr);
5318 int64_t staticTileSize = intAttr.getValue().getSExtValue();
5319 if (dimSize != staticTileSize)
5320 return op->emitError(
5321 "mismatch in inner tile sizes specified and shaped of "
5322 "tiled dimension in the packed type at index ")
5323 << it.index() << ": got " << dimSize << " != " << staticTileSize;
5324 } else if (!ShapedType::isDynamic(dimSize)) {
5325 return op->emitError("mismatch in inner tile sizes specified at index ")
5326 << it.index() << ": got static shape " << dimSize
5327 << " but dynamic tile size";
5328 }
5329 }
5330 if (failed(
5331 verifyCompatibleShape(expectedPackedShape, packedType.getShape()))) {
5332 auto elementType = unpackedType.getElementType();
5333 Type expectedType, actualType;
5334 if (packOrUnPack.hasPureTensorSemantics()) {
5335 expectedType = RankedTensorType::get(expectedPackedShape, elementType);
5336 actualType = RankedTensorType::get(packedType.getShape(), elementType);
5337 } else {
5338 expectedType = MemRefType::get(expectedPackedShape, elementType);
5339 actualType = MemRefType::get(packedType.getShape(), elementType);
5340 }
5341 return op->emitError("expected ")
5342 << expectedType << " for the packed domain value, got "
5343 << actualType;
5344 }
5345 return success();
5346}
5347
5348namespace {
5349/// Subset of PackOp/UnPackOp fields used to compute the result of applying
5350/// various permutations to the op.
5351// TODO: Add linalg.transpose + pack/unpack folding patterns that just reuse
5352// these. These may or may not become true foldings / canonicalizations
5353// depending on how aggressive we want to be in automatically folding
5354// transposes.
5355struct PackOrUnPackTransposeResult {
5356 SmallVector<int64_t> innerDimsPos;
5357 SmallVector<OpFoldResult> innerTiles;
5358 SmallVector<int64_t> outerDimsPerm;
5359};
5360} // namespace
5361
5362template <typename OpTy>
5363static PackOrUnPackTransposeResult
5365 ArrayRef<int64_t> innerPermutation,
5366 ArrayRef<int64_t> outerPermutation) {
5367 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5368 "applies to only pack or unpack operations");
5369 assert((!innerPermutation.empty() || !outerPermutation.empty()) &&
5370 "some permutation must be non-empty");
5371 PackOrUnPackTransposeResult metadata;
5372 metadata.innerDimsPos =
5373 SmallVector<int64_t>(packOrUnPackOp.getInnerDimsPos());
5374 metadata.innerTiles =
5375 SmallVector<OpFoldResult>(packOrUnPackOp.getMixedTiles());
5376 int64_t numOuterDims = std::is_same<OpTy, PackOp>::value
5377 ? packOrUnPackOp.getSourceRank()
5378 : packOrUnPackOp.getDestRank();
5379 metadata.outerDimsPerm =
5380 packOrUnPackOp.getOuterDimsPerm().empty()
5381 ? llvm::to_vector(llvm::seq<int64_t>(0, numOuterDims))
5382 : SmallVector<int64_t>(packOrUnPackOp.getOuterDimsPerm());
5383 if (!innerPermutation.empty()) {
5384 assert(innerPermutation.size() == metadata.innerDimsPos.size() &&
5385 isPermutationVector(innerPermutation) &&
5386 "invalid inner permutation");
5387 applyPermutationToVector(metadata.innerDimsPos, innerPermutation);
5388 applyPermutationToVector(metadata.innerTiles, innerPermutation);
5389 }
5390 if (!outerPermutation.empty()) {
5391 assert(outerPermutation.size() == metadata.outerDimsPerm.size() &&
5392 isPermutationVector(outerPermutation) &&
5393 "invalid outer permutation");
5394 applyPermutationToVector(metadata.outerDimsPerm, outerPermutation);
5395 }
5396 return metadata;
5397}
5398
5399//===----------------------------------------------------------------------===//
5400// PackOp
5401//===----------------------------------------------------------------------===//
5402
5403void PackOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
5404 if (!getResults().empty())
5405 setNameFn(getResult(), "pack");
5406}
5407
5408ParseResult PackOp::parse(OpAsmParser &parser, OperationState &result) {
5409 OpAsmParser::UnresolvedOperand source, dest;
5412 SmallVector<Type> paddingValueType;
5413 SmallVector<int64_t> staticTiles;
5414 DenseI64ArrayAttr innerDimsPos, outerDimsPerm;
5415 Type sourceType, destType, resultType;
5416
5417 if (parser.parseOperand(source))
5418 return failure();
5419
5420 if (succeeded(parser.parseOptionalKeyword("padding_value"))) {
5421 if (parser.parseLParen() ||
5422 parser.parseOperandList(paddingValue, /*requiredOperandCount=*/1) ||
5423 parser.parseColon() || parser.parseTypeList(paddingValueType) ||
5424 parser.parseRParen())
5425 return failure();
5426 }
5427
5428 if (succeeded(parser.parseOptionalKeyword("outer_dims_perm"))) {
5429 if (parser.parseEqual())
5430 return failure();
5431
5432 SmallVector<int64_t> outerDimsPermVec;
5434 int64_t value;
5435 if (parser.parseInteger(value))
5436 return failure();
5437 outerDimsPermVec.push_back(value);
5438 return success();
5439 }))
5440 return failure();
5441 outerDimsPerm = parser.getBuilder().getDenseI64ArrayAttr(outerDimsPermVec);
5442 }
5443
5444 if (parser.parseKeyword("inner_dims_pos") || parser.parseEqual())
5445 return failure();
5446
5447 SmallVector<int64_t> innerDimsPosVec;
5449 int64_t value;
5450 if (parser.parseInteger(value))
5451 return failure();
5452 innerDimsPosVec.push_back(value);
5453 return success();
5454 }))
5455 return failure();
5456 innerDimsPos = parser.getBuilder().getDenseI64ArrayAttr(innerDimsPosVec);
5457
5458 if (parser.parseKeyword("inner_tiles") || parser.parseEqual())
5459 return failure();
5460
5461 DenseI64ArrayAttr staticTilesAttr;
5462 if (parseDynamicIndexList(parser, dynamicTiles, staticTilesAttr))
5463 return failure();
5464 for (auto val : staticTilesAttr.asArrayRef())
5465 staticTiles.push_back(val);
5466
5467 if (parser.parseKeyword("into") || parser.parseOperand(dest))
5468 return failure();
5469
5470 if (parser.parseOptionalAttrDict(result.attributes))
5471 return failure();
5472
5473 if (parser.parseColon() || parser.parseType(sourceType))
5474 return failure();
5475
5476 bool hasArrow = succeeded(parser.parseOptionalArrow());
5477 if (hasArrow) {
5478 if (parser.parseType(destType))
5479 return failure();
5480 }
5481
5482 bool isMemRef = llvm::isa<MemRefType>(sourceType);
5483 if (!hasArrow) {
5484 return parser.emitError(parser.getCurrentLocation(),
5485 "pack/unpack requires '->' and destination type");
5486 }
5487
5488 if (!isMemRef)
5489 resultType = destType;
5490
5491 if (parser.resolveOperand(source, sourceType, result.operands) ||
5492 parser.resolveOperand(dest, destType, result.operands))
5493 return failure();
5494
5495 if (!paddingValue.empty() &&
5496 parser.resolveOperands(paddingValue, paddingValueType[0],
5497 result.operands))
5498 return failure();
5499
5500 if (!dynamicTiles.empty() &&
5501 parser.resolveOperands(dynamicTiles, parser.getBuilder().getIndexType(),
5502 result.operands))
5503 return failure();
5504
5505 result.addAttribute("static_inner_tiles",
5506 parser.getBuilder().getDenseI64ArrayAttr(staticTiles));
5507 result.addAttribute("inner_dims_pos", innerDimsPos);
5508 if (outerDimsPerm)
5509 result.addAttribute("outer_dims_perm", outerDimsPerm);
5510
5511 SmallVector<int32_t> segmentSizes = {
5512 1, 1, static_cast<int32_t>(paddingValue.size()),
5513 static_cast<int32_t>(dynamicTiles.size())};
5514 result.addAttribute("operandSegmentSizes",
5515 parser.getBuilder().getDenseI32ArrayAttr(segmentSizes));
5516
5517 if (!isMemRef)
5518 result.addTypes(resultType);
5519
5520 return success();
5521}
5522
5523void PackOp::print(OpAsmPrinter &p) {
5524 p << " " << getSource();
5525
5526 if (getPaddingValue()) {
5527 p << " padding_value(" << getPaddingValue() << " : "
5528 << getPaddingValue().getType() << ")";
5529 }
5530
5531 if (!getOuterDimsPerm().empty()) {
5532 p << " outer_dims_perm = [";
5533 llvm::interleaveComma(getOuterDimsPerm(), p);
5534 p << "]";
5535 }
5536
5537 p << " inner_dims_pos = [";
5538 llvm::interleaveComma(getInnerDimsPos(), p);
5539 p << "]";
5540
5541 p << " inner_tiles = ";
5542 printDynamicIndexList(p, *this, getInnerTiles(), getStaticInnerTilesAttr());
5543
5544 p << " into " << getDest();
5545
5546 p.printOptionalAttrDict((*this)->getAttrs(),
5547 {"static_inner_tiles", "inner_dims_pos",
5548 "outer_dims_perm", "operandSegmentSizes"});
5549
5550 p << " : " << getSource().getType();
5551 p << " -> " << getDest().getType();
5552}
5553
5554void PackOp::build(OpBuilder &builder, OperationState &state, Value source,
5555 Value dest, ArrayRef<int64_t> innerDimsPos,
5556 ArrayRef<OpFoldResult> innerTiles,
5557 std::optional<Value> paddingValue,
5558 ArrayRef<int64_t> outerDimsPerm) {
5559 assert(innerDimsPos.size() == innerTiles.size() &&
5560 "number of tile sizes specified must match the specified number of "
5561 "original dimensions to be tiled");
5562 SmallVector<int64_t> staticTileSizes;
5563 SmallVector<Value> dynamicTileSizes;
5564 dispatchIndexOpFoldResults(innerTiles, dynamicTileSizes, staticTileSizes);
5565 build(builder, state, dest.getType(), source, dest,
5566 paddingValue ? *paddingValue : nullptr,
5567 outerDimsPerm.empty() ? nullptr
5568 : builder.getDenseI64ArrayAttr(outerDimsPerm),
5569 builder.getDenseI64ArrayAttr(innerDimsPos), dynamicTileSizes,
5570 builder.getDenseI64ArrayAttr(staticTileSizes));
5571}
5572
5573LogicalResult
5574PackOp::reifyResultShapes(OpBuilder &builder,
5575 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
5576 return reifyResultShapesImpl(*this, builder, reifiedReturnShapes);
5577}
5578
5579DenseMap<int64_t, OpFoldResult> PackOp::getDimAndTileMapping() {
5580 return getDimAndTileMappingImpl(*this);
5581}
5582
5583SmallVector<OpFoldResult> PackOp::getMixedTiles() {
5584 return getMixedTilesImpl(*this);
5585}
5586
5587SmallVector<int64_t> PackOp::getStaticTiles() {
5588 return getStaticTilesImpl(*this);
5589}
5590
5591ArrayRef<int64_t> PackOp::getAllOuterDims() {
5592 ShapedType inputType = getSourceType();
5593 int64_t inputRank = inputType.getRank();
5594 return getDestType().getShape().take_front(inputRank);
5595}
5596
5597SmallVector<int64_t> PackOp::getTiledOuterDims() {
5598 auto innerDimsPos = getInnerDimsPos();
5599 SmallVector<int64_t> outerDims(getAllOuterDims());
5600 SmallVector<int64_t> res;
5601
5602 // Recover the original order of the outer dims.
5603 SmallVector<int64_t> outerDimPermInv(getOuterDimsPerm());
5604 invertPermutationVector(outerDimPermInv);
5605 if (!outerDimPermInv.empty())
5606 applyPermutationToVector(outerDims, outerDimPermInv);
5607
5608 // Collect the outer dims corresponding to the tilled inner dims.
5609 for (auto index : innerDimsPos)
5610 res.push_back(outerDims[index]);
5611
5612 return res;
5613}
5614
5615bool PackOp::requirePaddingValue(ArrayRef<int64_t> inputShape,
5616 ArrayRef<int64_t> innerDimsPos,
5617 ArrayRef<int64_t> outputShape,
5618 ArrayRef<int64_t> outerDimsPerm,
5619 ArrayRef<OpFoldResult> innerTiles) {
5620 SmallVector<int64_t> outputTileSizes(
5621 outputShape.take_front(inputShape.size()));
5622 if (!outerDimsPerm.empty()) {
5623 assert(outerDimsPerm.size() == outputTileSizes.size() &&
5624 "expected output and outer_dims_perm to have same size");
5625 applyPermutationToVector(outputTileSizes,
5626 invertPermutationVector(outerDimsPerm));
5627 }
5628 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5629 if (ShapedType::isDynamic(inputShape[pos]))
5630 continue;
5631 std::optional<int64_t> constantTile = getConstantIntValue(tileSize);
5632 if (!constantTile) {
5633 if (ShapedType::isStatic(outputTileSizes[pos]) &&
5634 (inputShape[pos] % outputTileSizes[pos] != 0))
5635 return true;
5636 } else {
5637 assert(*constantTile != 0 && "static tile size can't be zero");
5638 if (inputShape[pos] % (*constantTile) != 0) {
5639 return true;
5640 }
5641 }
5642 }
5643 return false;
5644}
5645
5646bool PackOp::requirePaddingValueStrict(ArrayRef<int64_t> inputShape,
5647 ArrayRef<int64_t> innerDimsPos,
5648 ArrayRef<int64_t> outputShape,
5649 ArrayRef<int64_t> outerDimsPerm,
5650 ArrayRef<OpFoldResult> innerTiles) {
5651 SmallVector<int64_t> outputTileSizes(
5652 outputShape.take_front(inputShape.size()));
5653 if (!outerDimsPerm.empty()) {
5654 assert(outerDimsPerm.size() == outputTileSizes.size() &&
5655 "expected output and outer_dims_perm to have same size");
5656 applyPermutationToVector(outputTileSizes,
5657 invertPermutationVector(outerDimsPerm));
5658 }
5659 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5660 if (ShapedType::isDynamic(inputShape[pos]) ||
5661 ShapedType::isDynamic(outputTileSizes[pos]))
5662 return true;
5663 std::optional<int64_t> constantTile = getConstantIntValue(tileSize);
5664 if (!constantTile)
5665 return true;
5666 assert(*constantTile != 0 && "static tile size can't be zero");
5667 if (inputShape[pos] % (*constantTile) != 0)
5668 return true;
5669 }
5670 return false;
5671}
5672
5673LogicalResult PackOp::verify() {
5675 return failure();
5676
5677 // Verify padding value, and bail out if the tile does not divide the
5678 // dimension fully. In the case of dynamic tile factors or dimensions, having
5679 // a partial tile is undefined behavior.
5680 auto paddingValue = getPaddingValue();
5681 if (paddingValue &&
5682 paddingValue.getType() != getSourceType().getElementType()) {
5683 return emitOpError("expected padding_value has ")
5684 << getSourceType().getElementType()
5685 << " but got: " << paddingValue.getType();
5686 }
5687
5688 if (!paddingValue &&
5689 requirePaddingValue(getSourceType().getShape(), getInnerDimsPos(),
5690 getDestType().getShape(), getOuterDimsPerm(),
5691 getMixedTiles())) {
5692 return emitOpError(
5693 "invalid tile factor or output size provided. Only full tiles are "
5694 "supported when padding_value is not set");
5695 }
5696 return success();
5697}
5698
5699/// Converts OpFoldResults to int64_t shape entries, unconditionally mapping all
5700/// Value's to kDynamic, even if they are arith.constant values.
5701static SmallVector<int64_t>
5704 for (auto o : ofrs) {
5705 // Have to do this first, as getConstantIntValue special-cases constants.
5706 if (llvm::dyn_cast_if_present<Value>(o))
5707 result.push_back(ShapedType::kDynamic);
5708 else
5709 result.push_back(getConstantIntValue(o).value_or(ShapedType::kDynamic));
5710 }
5711 return result;
5712}
5713
5714SmallVector<int64_t> PackOp::inferPackedShape(ArrayRef<int64_t> inputShape,
5715 ArrayRef<int64_t> innerTileSizes,
5716 ArrayRef<int64_t> innerDimsPos,
5717 ArrayRef<int64_t> outerDimsPerm) {
5718 SmallVector<int64_t> resultShape = llvm::to_vector(inputShape);
5719 for (auto tiledDim : llvm::enumerate(llvm::to_vector(innerDimsPos))) {
5720 if (ShapedType::isDynamic(resultShape[tiledDim.value()]))
5721 continue;
5722 if (ShapedType::isDynamic(innerTileSizes[tiledDim.index()])) {
5723 resultShape[tiledDim.value()] = ShapedType::kDynamic;
5724 continue;
5725 }
5726 resultShape[tiledDim.value()] = llvm::divideCeilSigned(
5727 resultShape[tiledDim.value()], innerTileSizes[tiledDim.index()]);
5728 }
5729
5730 // Swap tile loops if outer_dims_perm is available.
5731 if (!outerDimsPerm.empty())
5732 applyPermutationToVector(resultShape, outerDimsPerm);
5733
5734 // Append the inner tile dimensions.
5735 resultShape.append(innerTileSizes.begin(), innerTileSizes.end());
5736 return resultShape;
5737}
5738
5739SmallVector<OpFoldResult> PackOp::getResultShape(
5740 OpBuilder &builder, Location loc, ArrayRef<OpFoldResult> sourceDims,
5741 ArrayRef<OpFoldResult> innerTileSizes, ArrayRef<int64_t> innerDimsPos,
5742 ArrayRef<int64_t> outerDimsPerm) {
5743 SmallVector<OpFoldResult> resultDims = llvm::to_vector(sourceDims);
5744
5745 AffineExpr s0, s1;
5746 bindSymbols(builder.getContext(), s0, s1);
5747 AffineExpr ceilDivExpr = s0.ceilDiv(s1);
5748 for (auto tiledDim : llvm::enumerate(llvm::to_vector(innerDimsPos))) {
5749 resultDims[tiledDim.value()] = affine::makeComposedFoldedAffineApply(
5750 builder, loc, ceilDivExpr,
5751 {resultDims[tiledDim.value()], innerTileSizes[tiledDim.index()]});
5752 }
5753 if (!outerDimsPerm.empty())
5754 applyPermutationToVector(resultDims, outerDimsPerm);
5755 resultDims.append(innerTileSizes.begin(), innerTileSizes.end());
5756
5757 SmallVector<int64_t> resultTypeShape =
5758 inferPackedShape(asShapeWithAnyValueAsDynamic(sourceDims),
5759 asShapeWithAnyValueAsDynamic(innerTileSizes),
5760 innerDimsPos, outerDimsPerm);
5761
5762 // Fix-up `resultDims` to ensure that they are Value's if and only if the
5763 // result type shape says it's a dynamic dim. This is needed as callers may
5764 // use dispatchIndexOpFoldResults on the result, and rely on exact number of
5765 // dynamic dims returned by that.
5766 for (unsigned i = 0; i < resultDims.size(); ++i) {
5767 if (ShapedType::isStatic(resultTypeShape[i]))
5768 continue;
5769 resultDims[i] =
5770 getValueOrCreateConstantIndexOp(builder, loc, resultDims[i]);
5771 }
5772
5773 return resultDims;
5774}
5775
5776RankedTensorType PackOp::inferPackedTensorType(
5777 RankedTensorType sourceType, ArrayRef<int64_t> innerTileSizes,
5778 ArrayRef<int64_t> innerDimsPos, ArrayRef<int64_t> outerDimsPerm) {
5779 SmallVector<int64_t> resultShape = inferPackedShape(
5780 sourceType.getShape(), innerTileSizes, innerDimsPos, outerDimsPerm);
5781 return RankedTensorType::get(resultShape, sourceType.getElementType());
5782}
5783
5784MemRefType PackOp::inferPackedMemRefType(MemRefType sourceType,
5785 ArrayRef<int64_t> innerTileSizes,
5786 ArrayRef<int64_t> innerDimsPos,
5787 ArrayRef<int64_t> outerDimsPerm) {
5788 SmallVector<int64_t> resultShape = inferPackedShape(
5789 sourceType.getShape(), innerTileSizes, innerDimsPos, outerDimsPerm);
5790 return MemRefType::get(resultShape, sourceType.getElementType());
5791}
5792
5793Value PackOp::createDestinationTensor(OpBuilder &b, Location loc, Value source,
5794 ArrayRef<OpFoldResult> innerTileSizes,
5795 ArrayRef<int64_t> innerDimsPos,
5796 ArrayRef<int64_t> outerDimsPerm) {
5797 AffineExpr dim0, dim1;
5798 bindDims(b.getContext(), dim0, dim1);
5799 auto ceilDiv = [&](OpFoldResult v1, OpFoldResult v2) -> OpFoldResult {
5800 return affine::makeComposedFoldedAffineApply(b, loc, dim0.ceilDiv(dim1),
5801 {v1, v2});
5802 };
5803
5804 SmallVector<OpFoldResult> mixedSizes;
5805 for (auto [index, value] : llvm::enumerate(
5806 llvm::cast<RankedTensorType>(source.getType()).getShape())) {
5807 if (ShapedType::isDynamic(value))
5808 mixedSizes.push_back(
5809 tensor::DimOp::create(b, loc, source, index).getResult());
5810 else
5811 mixedSizes.push_back(b.getIndexAttr(value));
5812 }
5813 for (auto it : llvm::zip(innerDimsPos, innerTileSizes)) {
5814 int64_t dimPos = std::get<0>(it);
5815 OpFoldResult tileSize = std::get<1>(it);
5816 mixedSizes[dimPos] = ceilDiv(mixedSizes[dimPos], tileSize);
5817 }
5818 if (!outerDimsPerm.empty())
5819 applyPermutationToVector<OpFoldResult>(mixedSizes, outerDimsPerm);
5820
5821 mixedSizes.append(innerTileSizes.begin(), innerTileSizes.end());
5822 auto elemType = llvm::cast<ShapedType>(source.getType()).getElementType();
5823 return tensor::EmptyOp::create(b, loc, mixedSizes, elemType);
5824}
5825
5826PackOp PackOp::createTransposedClone(OpBuilder &b, Location loc,
5827 ArrayRef<int64_t> innerPermutation,
5828 ArrayRef<int64_t> outerPermutation) {
5829 PackOrUnPackTransposeResult metadata = commonPermutationOfPackAndUnPackOp(
5830 *this, innerPermutation, outerPermutation);
5831 Value transposedDest =
5832 createDestinationTensor(b, loc, getSource(), metadata.innerTiles,
5833 metadata.innerDimsPos, metadata.outerDimsPerm);
5834 return PackOp::create(b, loc, getSource(), transposedDest,
5835 metadata.innerDimsPos, metadata.innerTiles,
5836 getPaddingValue(), metadata.outerDimsPerm);
5837}
5838
5839template <typename OpTy>
5842 &effects) {
5843 // No memory effects for pure tensor semantics
5844 if (op.hasPureTensorSemantics())
5845 return;
5846
5847 for (OpOperand &opOperand : op.getOperation()->getOpOperands()) {
5848 if (!llvm::isa<MemRefType>(opOperand.get().getType()))
5849 continue;
5850
5851 if (&opOperand == &op.getSourceMutable()) {
5852 effects.emplace_back(MemoryEffects::Read::get(), &opOperand, /*stage=*/0,
5853 /*effectOnFullRegion=*/true,
5855 } else if (&opOperand == &op.getDestMutable()) {
5856 effects.emplace_back(MemoryEffects::Read::get(), &opOperand, /*stage=*/0,
5857 /*effectOnFullRegion=*/true,
5859 effects.emplace_back(MemoryEffects::Write::get(), &opOperand, /*stage=*/0,
5860 /*effectOnFullRegion=*/true,
5862 }
5863 }
5864}
5865
5866void PackOp::getEffects(
5868 &effects) {
5869 getPackUnPackEffectsImpl(*this, effects);
5870}
5871
5872void UnPackOp::getEffects(
5874 &effects) {
5875 getPackUnPackEffectsImpl(*this, effects);
5876}
5877
5878/// Returns true if the tiles and the tiled dims are constant.
5879template <typename OpTy>
5881 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5882 "applies to only pack or unpack operations");
5883 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5884 ? op.getDestType()
5885 : op.getSourceType();
5886 SmallVector<OpFoldResult> mixedTiles = op.getMixedTiles();
5887 for (auto [dimDest, tile] : llvm::zip(
5888 packedType.getShape().take_back(mixedTiles.size()), mixedTiles)) {
5889 std::optional<int64_t> constTileSize = getConstantIntValue(tile);
5890 if (!constTileSize || ShapedType::isDynamic(dimDest))
5891 return false;
5892 }
5893 return true;
5894}
5895
5896Speculation::Speculatability PackOp::getSpeculatability() {
5897 if (!hasPureTensorSemantics())
5899 if (getPaddingValue())
5901
5902 // The verifier rejects already operations if we can statically prove that the
5903 // sizes of the tiles do not divide perfectly the dimension; thus, check only
5904 // to have constant tiles and tiled inner dimensions.
5907
5909}
5910
5911// Return true if `inner_dims_pos` and `outer_dims_perm` target the same
5912// dimensions for pack and unpack.
5913static bool hasSameInnerOuterAttribute(PackOp packOp, UnPackOp unPackOp) {
5914 if (packOp.getInnerDimsPos() != unPackOp.getInnerDimsPos())
5915 return false;
5916 if (packOp.getOuterDimsPerm() == unPackOp.getOuterDimsPerm())
5917 return true;
5918 // Outer dims permutation is optional.
5919 // To compare unbalanced pack-unpack pair, treat no permutation as equal to
5920 // identity permutation.
5921 return isIdentityPermutation(packOp.getOuterDimsPerm()) &&
5922 isIdentityPermutation(unPackOp.getOuterDimsPerm());
5923}
5924
5925// Return true if pack and unpack have the same tiles.
5926// Same SSA values or same integer constants.
5927static bool haveSameTiles(PackOp packOp, UnPackOp unPackOp) {
5928 auto packTiles = packOp.getMixedTiles();
5929 auto unPackTiles = unPackOp.getMixedTiles();
5930 if (packTiles.size() != unPackTiles.size())
5931 return false;
5932 for (size_t i = 0, e = packTiles.size(); i < e; i++) {
5933 if (!isEqualConstantIntOrValue(packTiles[i], unPackTiles[i]))
5934 return false;
5935 }
5936 return true;
5937}
5938
5939/// Returns true if the pack op does not need a padding value.
5940static bool paddingIsNotNeeded(PackOp op) {
5941 auto srcType = op.getSourceType();
5942 auto innerDimsPos = op.getInnerDimsPos();
5943 auto innerTiles = op.getStaticInnerTiles();
5944 if (ShapedType::isDynamicShape(innerTiles))
5945 return false;
5946 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5947 if (srcType.isDynamicDim(pos) && tileSize != 1)
5948 return false;
5949 }
5950 return !PackOp::requirePaddingValue(
5951 srcType.getShape(), op.getInnerDimsPos(), op.getDestType().getShape(),
5952 op.getOuterDimsPerm(), op.getMixedTiles());
5953}
5954
5955/// Returns true if the `srcShape` or `destShape` is different from the one in
5956/// `packOp` and populates each with the inferred static shape.
5957static bool inferStaticShape(PackOp packOp, SmallVectorImpl<int64_t> &srcShape,
5958 SmallVectorImpl<int64_t> &destShape) {
5959 bool changeNeeded = false;
5960 srcShape.assign(packOp.getSourceType().getShape().begin(),
5961 packOp.getSourceType().getShape().end());
5962 destShape.assign(packOp.getDestType().getShape().begin(),
5963 packOp.getDestType().getShape().end());
5964 llvm::SmallSetVector<int64_t, 4> innerDims;
5965 innerDims.insert_range(packOp.getInnerDimsPos());
5966 SmallVector<int64_t> inverseOuterDimsPerm;
5967 if (!packOp.getOuterDimsPerm().empty())
5968 inverseOuterDimsPerm = invertPermutationVector(packOp.getOuterDimsPerm());
5969 int srcRank = packOp.getSourceRank();
5970 for (auto i : llvm::seq<int64_t>(0, srcRank)) {
5971 if (innerDims.contains(i))
5972 continue;
5973 int64_t srcPos = i;
5974 int64_t destPos = i;
5975 if (!inverseOuterDimsPerm.empty())
5976 destPos = inverseOuterDimsPerm[srcPos];
5977 if (ShapedType::isDynamic(srcShape[srcPos]) ==
5978 ShapedType::isDynamic(destShape[destPos])) {
5979 continue;
5980 }
5981 int64_t size = srcShape[srcPos];
5982 if (ShapedType::isDynamic(size))
5983 size = destShape[destPos];
5984 srcShape[srcPos] = size;
5985 destShape[destPos] = size;
5986 changeNeeded = true;
5987 }
5988 return changeNeeded;
5989}
5990
5991LogicalResult PackOp::canonicalize(PackOp packOp, PatternRewriter &rewriter) {
5992 // TODO: Support Memref PackOp. Temporarily return failure.
5993 if (!packOp.hasPureTensorSemantics())
5994 return failure();
5995
5996 // Fold an pack(unpack(x)) to x.
5997 if (auto unPackOp = packOp.getSource().getDefiningOp<UnPackOp>()) {
5998 if (unPackOp.getSourceType() == packOp.getDestType() &&
5999 !packOp.getPaddingValue() &&
6000 hasSameInnerOuterAttribute(packOp, unPackOp) &&
6001 haveSameTiles(packOp, unPackOp)) {
6002 rewriter.replaceOp(packOp, unPackOp.getSource());
6003 return success();
6004 }
6005 }
6006
6007 // Fold optional PaddingValue operand away if padding is not needed.
6008 if (packOp.getPaddingValue() && paddingIsNotNeeded(packOp)) {
6009 rewriter.startOpModification(packOp);
6010 packOp.getPaddingValueMutable().clear();
6011 rewriter.finalizeOpModification(packOp);
6012 return success();
6013 }
6014
6015 // Insert tensor.cast ops if static shape inference is available..
6016 SmallVector<int64_t> srcShape, destShape;
6017 if (inferStaticShape(packOp, srcShape, destShape)) {
6018 Location loc = packOp.getLoc();
6019 Value source = packOp.getSource();
6020 if (srcShape != packOp.getSourceType().getShape()) {
6021 auto newSrcType = packOp.getSourceType().clone(srcShape);
6022 source =
6023 tensor::CastOp::create(rewriter, loc, newSrcType, packOp.getSource());
6024 }
6025 Value dest = packOp.getDest();
6026 ShapedType originalResultType = packOp.getDestType();
6027 bool needUpdateDestType = (destShape != originalResultType.getShape());
6028 if (needUpdateDestType) {
6029 auto newDestType = packOp.getDestType().clone(destShape);
6030 dest =
6031 tensor::CastOp::create(rewriter, loc, newDestType, packOp.getDest());
6032 }
6033 rewriter.modifyOpInPlace(packOp, [&] {
6034 packOp.getSourceMutable().assign(source);
6035 packOp.getDestMutable().assign(dest);
6036 packOp.getResult().setType(cast<RankedTensorType>(dest.getType()));
6037 });
6038 // Insert a cast if needed
6039 if (needUpdateDestType) {
6040 rewriter.setInsertionPointAfter(packOp);
6041 auto castOp = tensor::CastOp::create(rewriter, loc, originalResultType,
6042 packOp.getResult());
6043 rewriter.replaceAllUsesExcept(packOp.getResult(), castOp, castOp);
6044 }
6045 return success();
6046 }
6047
6048 return failure();
6049}
6050
6051template <typename PackOrUnpackOp>
6052static bool isLikePadUnPad(PackOrUnpackOp packOp, ShapedType packedTensorType) {
6053 static_assert(std::is_same<PackOrUnpackOp, PackOp>::value ||
6054 std::is_same<PackOrUnpackOp, UnPackOp>::value,
6055 "Function meant for pack/unpack");
6056 // This is a pad if packing only adds ones and we don't transpose dimensions.
6057
6058 // Check that we are not transposing any dimensions.
6059 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
6060 int64_t numPackedDims = innerDimsPos.size();
6061 auto orderedDims = llvm::to_vector<4>(llvm::seq<int64_t>(0, numPackedDims));
6062 if (orderedDims != innerDimsPos) {
6063 // Dimensions don't happen in order.
6064 return false;
6065 }
6066
6067 ArrayRef<int64_t> packedShape = packedTensorType.getShape();
6068 int64_t packedRank = packedTensorType.getRank();
6069 // At this point we know that we are taking numPackedDims outer
6070 // dimensions and pushing them all the way as the inner most dimensions.
6071 // What's left on the outer most dimensions is, in this order:
6072 // - the factor of the packed dimensions, then
6073 // - the untouched dimensions
6074 // This shifting inward of dimensions is a no-op (as opposed to a transpose)
6075 // if all the dimensions that bubble outerward are ones.
6076 // Therefore check that all the dimensions but the numPackedDims inner most
6077 // ones are ones.
6078 return llvm::all_of(
6079 llvm::seq<int64_t>(0, packedRank - numPackedDims),
6080 [&packedShape](int64_t i) { return packedShape[i] == 1; });
6081}
6082
6083bool PackOp::isLikePad() {
6084 auto packedTensorType =
6085 llvm::cast<ShapedType>((*this)->getResultTypes().front());
6086 return isLikePadUnPad(*this, packedTensorType);
6087}
6088
6089::mlir::LogicalResult
6090PackOp::fold(FoldAdaptor adaptor,
6092 if (!hasPureTensorSemantics())
6093 return failure();
6094 std::optional<Attribute> paddingValue;
6095 if (auto pad = adaptor.getPaddingValue())
6096 paddingValue = pad;
6097 if (OpFoldResult reshapedSource = reshapeConstantSource(
6098 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
6099 cast<TensorType>(getDestType()), paddingValue)) {
6100 results.push_back(reshapedSource);
6101 return success();
6102 }
6103 return failure();
6104}
6105
6106/// Folds a tensor.cast op into a consuming PackOp op if the
6107/// `tensor.cast` has source that is more static than the consuming op.
6108///
6109/// Example:
6110/// ```mlir
6111/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
6112/// %2 = tensor.pack %1 ... : tensor<?x?xf32> ...
6113/// ```
6114///
6115/// folds into:
6116///
6117/// ```mlir
6118/// %2 = tensor.pack %0 ... : tensor<8x16xf32> ...
6119/// ```
6122
6123 LogicalResult matchAndRewrite(PackOp op,
6124 PatternRewriter &rewriter) const override {
6125 // TODO: Support Memref PackOp. Temporarily return failure.
6126 if (!op.hasPureTensorSemantics())
6127 return failure();
6128
6130 return failure();
6131
6132 SmallVector<Type> newResultTypes(op->getResultTypes());
6133 SmallVector<Value> newOperands =
6135
6136 // Get the updated mixed-tile-sizes attribute.
6137 SmallVector<OpFoldResult> newMixedTileSizes =
6138 getNewMixedTileSizes(rewriter, newResultTypes[0], op.getMixedTiles());
6139 if (llvm::any_of(newMixedTileSizes, isZeroInteger))
6140 return failure();
6141
6142 // Clone op.
6143 // TODO: Strictly speaking, discardable attributes should be _discarded_ at
6144 // this point. However, in practice, we use them for things that we'd like
6145 // to preserve. Implement a better abstraction.
6146 PackOp newOp =
6147 PackOp::create(rewriter, op.getLoc(), newOperands[0], newOperands[1],
6148 op.getInnerDimsPos(), newMixedTileSizes,
6149 op.getPaddingValue(), op.getOuterDimsPerm());
6150 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
6151
6152 // Replace op.
6153 Value oldResult = op.getResult();
6154 Value newResult = newOp.getResult();
6156 (newResult.getType() != oldResult.getType())
6157 ? tensor::CastOp::create(rewriter, op->getLoc(),
6158 oldResult.getType(), newResult)
6159 : newResult;
6160
6161 rewriter.replaceOp(op, {replacement});
6162
6163 return success();
6164 }
6165};
6166
6167//===----------------------------------------------------------------------===//
6168// UnPackOp
6169//===----------------------------------------------------------------------===//
6170
6171void UnPackOp::getAsmResultNames(
6172 function_ref<void(Value, StringRef)> setNameFn) {
6173 if (!getResults().empty())
6174 setNameFn(getResult(), "unpack");
6175}
6176
6177// Custom parser for UnPackOp that handles the memref/tensor case distinction
6178ParseResult UnPackOp::parse(OpAsmParser &parser, OperationState &result) {
6179 OpAsmParser::UnresolvedOperand source, dest;
6181 SmallVector<int64_t> staticTiles;
6182 DenseI64ArrayAttr innerDimsPos, outerDimsPerm;
6183 Type sourceType, destType, resultType;
6184
6185 if (parser.parseOperand(source))
6186 return failure();
6187
6188 if (succeeded(parser.parseOptionalKeyword("outer_dims_perm"))) {
6189 if (parser.parseEqual())
6190 return failure();
6191
6192 SmallVector<int64_t> outerDimsPermVec;
6194 int64_t value;
6195 if (parser.parseInteger(value))
6196 return failure();
6197 outerDimsPermVec.push_back(value);
6198 return success();
6199 }))
6200 return failure();
6201 outerDimsPerm = parser.getBuilder().getDenseI64ArrayAttr(outerDimsPermVec);
6202 }
6203
6204 if (parser.parseKeyword("inner_dims_pos") || parser.parseEqual())
6205 return failure();
6206
6207 SmallVector<int64_t> innerDimsPosVec;
6209 int64_t value;
6210 if (parser.parseInteger(value))
6211 return failure();
6212 innerDimsPosVec.push_back(value);
6213 return success();
6214 }))
6215 return failure();
6216 innerDimsPos = parser.getBuilder().getDenseI64ArrayAttr(innerDimsPosVec);
6217
6218 if (parser.parseKeyword("inner_tiles") || parser.parseEqual())
6219 return failure();
6220
6221 DenseI64ArrayAttr staticTilesAttr;
6222 if (parseDynamicIndexList(parser, dynamicTiles, staticTilesAttr))
6223 return failure();
6224 for (auto val : staticTilesAttr.asArrayRef())
6225 staticTiles.push_back(val);
6226
6227 if (parser.parseKeyword("into") || parser.parseOperand(dest))
6228 return failure();
6229
6230 if (parser.parseOptionalAttrDict(result.attributes))
6231 return failure();
6232
6233 if (parser.parseColon() || parser.parseType(sourceType))
6234 return failure();
6235
6236 bool hasArrow = succeeded(parser.parseOptionalArrow());
6237 if (hasArrow) {
6238 if (parser.parseType(destType))
6239 return failure();
6240 }
6241
6242 bool isMemRef = llvm::isa<MemRefType>(sourceType);
6243 if (!hasArrow) {
6244 return parser.emitError(parser.getCurrentLocation(),
6245 "pack/unpack requires '->' and destination type");
6246 }
6247
6248 if (!isMemRef)
6249 resultType = destType;
6250
6251 if (parser.resolveOperand(source, sourceType, result.operands) ||
6252 parser.resolveOperand(dest, destType, result.operands))
6253 return failure();
6254
6255 if (!dynamicTiles.empty() &&
6256 parser.resolveOperands(dynamicTiles, parser.getBuilder().getIndexType(),
6257 result.operands))
6258 return failure();
6259
6260 result.addAttribute("static_inner_tiles",
6261 parser.getBuilder().getDenseI64ArrayAttr(staticTiles));
6262 result.addAttribute("inner_dims_pos", innerDimsPos);
6263 if (outerDimsPerm)
6264 result.addAttribute("outer_dims_perm", outerDimsPerm);
6265
6266 SmallVector<int32_t> segmentSizes = {
6267 1, 1, 0, static_cast<int32_t>(dynamicTiles.size())};
6268 result.addAttribute("operandSegmentSizes",
6269 parser.getBuilder().getDenseI32ArrayAttr(segmentSizes));
6270
6271 if (!isMemRef)
6272 result.addTypes(resultType);
6273
6274 return success();
6275}
6276
6277void UnPackOp::print(OpAsmPrinter &p) {
6278 p << " " << getSource();
6279
6280 if (!getOuterDimsPerm().empty()) {
6281 p << " outer_dims_perm = [";
6282 llvm::interleaveComma(getOuterDimsPerm(), p);
6283 p << "]";
6284 }
6285
6286 p << " inner_dims_pos = [";
6287 llvm::interleaveComma(getInnerDimsPos(), p);
6288 p << "]";
6289
6290 p << " inner_tiles = ";
6291 printDynamicIndexList(p, *this, getInnerTiles(), getStaticInnerTilesAttr());
6292
6293 p << " into " << getDest();
6294
6295 p.printOptionalAttrDict((*this)->getAttrs(),
6296 {"static_inner_tiles", "inner_dims_pos",
6297 "outer_dims_perm", "operandSegmentSizes"});
6298
6299 p << " : " << getSource().getType();
6300 p << " -> " << getDest().getType();
6301}
6302
6303LogicalResult
6304UnPackOp::reifyResultShapes(OpBuilder &builder,
6305 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
6306 return reifyResultShapesImpl(*this, builder, reifiedReturnShapes);
6307}
6308
6309DenseMap<int64_t, OpFoldResult> UnPackOp::getDimAndTileMapping() {
6310 return getDimAndTileMappingImpl(*this);
6311}
6312
6313SmallVector<OpFoldResult> UnPackOp::getMixedTiles() {
6314 return getMixedTilesImpl(*this);
6315}
6316
6317SmallVector<int64_t> UnPackOp::getStaticTiles() {
6318 return getStaticTilesImpl(*this);
6319}
6320
6321ArrayRef<int64_t> UnPackOp::getAllOuterDims() {
6322 ShapedType destType = getDestType();
6323 int64_t destRank = destType.getRank();
6324 return getSourceType().getShape().take_front(destRank);
6325}
6326
6327SmallVector<int64_t> UnPackOp::getTiledOuterDims() {
6328 auto innerDimsPos = getInnerDimsPos();
6329 SmallVector<int64_t> outerDims(getAllOuterDims());
6330 SmallVector<int64_t> res;
6331
6332 // Recover the original order of the outer dims.
6333 SmallVector<int64_t> outerDimPermInv(getOuterDimsPerm());
6334 invertPermutationVector(outerDimPermInv);
6335 if (!outerDimPermInv.empty())
6336 applyPermutationToVector(outerDims, outerDimPermInv);
6337
6338 // Collect the outer dims corresponding to the tilled inner dims.
6339 for (auto index : innerDimsPos)
6340 res.push_back(outerDims[index]);
6341
6342 return res;
6343}
6344
6345LogicalResult UnPackOp::verify() {
6346 return commonVerifierPackAndUnPackOp(*this);
6347}
6348
6349Speculation::Speculatability UnPackOp::getSpeculatability() {
6350 if (!hasPureTensorSemantics())
6352 // See PackOp::getSpeculatability.
6355
6357}
6358
6359void UnPackOp::build(OpBuilder &builder, OperationState &state, Value source,
6360 Value dest, ArrayRef<int64_t> innerDimsPos,
6361 ArrayRef<OpFoldResult> innerTiles,
6362 ArrayRef<int64_t> outerDimsPerm) {
6363 assert(innerDimsPos.size() == innerTiles.size() &&
6364 "number of tile sizes specified must match the specified number of "
6365 "original dimensions to be tiled");
6366 SmallVector<int64_t> staticTileSizes;
6367 SmallVector<Value> dynamicTileSizes;
6368 dispatchIndexOpFoldResults(innerTiles, dynamicTileSizes, staticTileSizes);
6369 build(builder, state, dest.getType(), source, dest,
6370 outerDimsPerm.empty() ? nullptr
6371 : builder.getDenseI64ArrayAttr(outerDimsPerm),
6372 builder.getDenseI64ArrayAttr(innerDimsPos), dynamicTileSizes,
6373 builder.getDenseI64ArrayAttr(staticTileSizes));
6374}
6375
6376Value UnPackOp::createDestinationTensor(OpBuilder &b, Location loc,
6377 Value source,
6378 ArrayRef<OpFoldResult> innerTileSizes,
6379 ArrayRef<int64_t> innerDimsPos,
6380 ArrayRef<int64_t> outerDimsPerm) {
6381 AffineExpr sym0, sym1;
6382 bindSymbols(b.getContext(), sym0, sym1);
6383 auto dimMul = [&](OpFoldResult v1, OpFoldResult v2) -> OpFoldResult {
6384 return affine::makeComposedFoldedAffineApply(b, loc, sym0 * sym1, {v1, v2});
6385 };
6386
6387 SmallVector<OpFoldResult> mixedSizes;
6388 auto srcType = llvm::cast<RankedTensorType>(source.getType());
6389 for (auto i :
6390 llvm::seq<unsigned>(0, srcType.getRank() - innerTileSizes.size())) {
6391 if (srcType.isDynamicDim(i))
6392 mixedSizes.push_back(
6393 tensor::DimOp::create(b, loc, source, i).getResult());
6394 else
6395 mixedSizes.push_back(b.getIndexAttr(srcType.getDimSize(i)));
6396 }
6397 if (!outerDimsPerm.empty()) {
6399 mixedSizes, invertPermutationVector(outerDimsPerm));
6400 }
6401
6402 for (auto [dimPos, tileSize] : llvm::zip_equal(innerDimsPos, innerTileSizes))
6403 mixedSizes[dimPos] = dimMul(mixedSizes[dimPos], tileSize);
6404
6405 auto elemType = srcType.getElementType();
6406 return tensor::EmptyOp::create(b, loc, mixedSizes, elemType);
6407}
6408
6409UnPackOp UnPackOp::createTransposedClone(OpBuilder &b, Location loc,
6410 Value transposedSource,
6411 ArrayRef<int64_t> innerPermutation,
6412 ArrayRef<int64_t> outerPermutation) {
6413 PackOrUnPackTransposeResult metadata = commonPermutationOfPackAndUnPackOp(
6414 *this, innerPermutation, outerPermutation);
6415 return UnPackOp::create(b, loc, transposedSource, getDest(),
6416 metadata.innerDimsPos, metadata.innerTiles,
6417 metadata.outerDimsPerm);
6418}
6419
6420/// Returns true if the `srcShape` or `destShape` is different from the one in
6421/// `op` and populates each with the inferred static shape.
6422static bool inferStaticShape(UnPackOp op, SmallVectorImpl<int64_t> &srcShape,
6423 SmallVectorImpl<int64_t> &destShape) {
6424 bool changeNeeded = false;
6425 srcShape.assign(op.getSourceType().getShape().begin(),
6426 op.getSourceType().getShape().end());
6427 destShape.assign(op.getDestType().getShape().begin(),
6428 op.getDestType().getShape().end());
6429 llvm::SmallSetVector<int64_t, 4> innerDims;
6430 innerDims.insert_range(op.getInnerDimsPos());
6431 SmallVector<int64_t> inverseOuterDimsPerm;
6432 if (!op.getOuterDimsPerm().empty())
6433 inverseOuterDimsPerm = invertPermutationVector(op.getOuterDimsPerm());
6434 int destRank = op.getDestRank();
6435 for (auto i : llvm::seq<int64_t>(0, destRank)) {
6436 if (innerDims.contains(i))
6437 continue;
6438 int64_t srcPos = i;
6439 int64_t destPos = i;
6440 if (!inverseOuterDimsPerm.empty())
6441 srcPos = inverseOuterDimsPerm[destPos];
6442 if (ShapedType::isDynamic(srcShape[srcPos]) ==
6443 ShapedType::isDynamic(destShape[destPos])) {
6444 continue;
6445 }
6446 int64_t size = srcShape[srcPos];
6447 if (ShapedType::isDynamic(size))
6448 size = destShape[destPos];
6449 srcShape[srcPos] = size;
6450 destShape[destPos] = size;
6451 changeNeeded = true;
6452 }
6453 return changeNeeded;
6454}
6455
6456LogicalResult UnPackOp::canonicalize(UnPackOp unPackOp,
6457 PatternRewriter &rewriter) {
6458 // TODO: Support Memref UnPackOp. Temporarily return failure.
6459 if (!unPackOp.hasPureTensorSemantics())
6460 return failure();
6461
6462 /// unpack(pack(x)) -> x
6463 if (PackOp packOp = unPackOp.getSource().getDefiningOp<PackOp>()) {
6464 if (packOp.getSourceType() != unPackOp.getDestType())
6465 return failure();
6466 if (packOp.getPaddingValue() ||
6467 !hasSameInnerOuterAttribute(packOp, unPackOp) ||
6468 !haveSameTiles(packOp, unPackOp))
6469 return failure();
6470 rewriter.replaceOp(unPackOp, packOp.getSource());
6471 return success();
6472 }
6473 /// unpack(destinationStyleOp(x)) -> unpack(x)
6474 if (auto dstStyleOp =
6475 unPackOp.getDest().getDefiningOp<DestinationStyleOpInterface>()) {
6476 auto destValue = cast<OpResult>(unPackOp.getDest());
6477 Value newDest = dstStyleOp.getDpsInits()[destValue.getResultNumber()];
6478 rewriter.modifyOpInPlace(unPackOp,
6479 [&]() { unPackOp.setDpsInitOperand(0, newDest); });
6480 return success();
6481 }
6482 /// extract_slice(unpack(x into y)) -> unpack(x into extract_slice(y))
6483 if (unPackOp->hasOneUse()) {
6484 auto extractSliceUser =
6485 dyn_cast<tensor::ExtractSliceOp>(*unPackOp->getUsers().begin());
6486 if (extractSliceUser && unPackOp.canFoldSliceOp(extractSliceUser)) {
6487 OpBuilder::InsertionGuard g(rewriter);
6488 rewriter.setInsertionPoint(unPackOp);
6489 auto newDest = tensor::ExtractSliceOp::create(
6490 rewriter, unPackOp->getLoc(), unPackOp.getDest(),
6491 extractSliceUser.getMixedOffsets(), extractSliceUser.getMixedSizes(),
6492 extractSliceUser.getMixedStrides());
6493 rewriter.modifyOpInPlace(unPackOp, [&]() {
6494 unPackOp.setDpsInitOperand(0, newDest);
6495 unPackOp.getResult().setType(newDest.getType());
6496 });
6497 rewriter.replaceOp(extractSliceUser, unPackOp);
6498 return success();
6499 }
6500 }
6501
6502 // Insert tensor.cast ops if static shape inference is available..
6503 SmallVector<int64_t> srcShape, destShape;
6504 if (inferStaticShape(unPackOp, srcShape, destShape)) {
6505 Location loc = unPackOp.getLoc();
6506 Value source = unPackOp.getSource();
6507 if (srcShape != unPackOp.getSourceType().getShape()) {
6508 auto newSrcType = unPackOp.getSourceType().clone(srcShape);
6509 source = tensor::CastOp::create(rewriter, loc, newSrcType,
6510 unPackOp.getSource());
6511 }
6512 Value dest = unPackOp.getDest();
6513 if (destShape != unPackOp.getDestType().getShape()) {
6514 auto newDestType = unPackOp.getDestType().clone(destShape);
6515 dest = tensor::CastOp::create(rewriter, loc, newDestType,
6516 unPackOp.getDest());
6517 }
6518 UnPackOp newOp = UnPackOp::create(
6519 rewriter, loc, source, dest, unPackOp.getInnerDimsPos(),
6520 unPackOp.getMixedTiles(), unPackOp.getOuterDimsPerm());
6521 rewriter.replaceOpWithNewOp<tensor::CastOp>(
6522 unPackOp, unPackOp.getResult().getType(), newOp.getResult());
6523 return success();
6524 }
6525
6526 return failure();
6527}
6528
6529bool UnPackOp::canFoldSliceOp(tensor::ExtractSliceOp sliceOp) {
6530 // Rank-reduced folding is not supported.
6531 if (sliceOp.getResultType().getRank() != this->getDestType().getRank())
6532 return false;
6533 if (!areAllConstantIntValue(sliceOp.getMixedOffsets(), 0) ||
6534 !areAllConstantIntValue(sliceOp.getMixedStrides(), 1))
6535 return false;
6536 RankedTensorType unpackedTypeAfterFold = sliceOp.getResultType();
6537 SmallVector<int64_t> outerShapeWithoutTranspose =
6539 SmallVector<bool> areOuterDimsTiled(outerShapeWithoutTranspose.size(), false);
6540 for (auto [pos, tileSize] :
6541 llvm::zip_equal(this->getInnerDimsPos(), this->getStaticInnerTiles())) {
6542 areOuterDimsTiled[pos] = true;
6543 if (unpackedTypeAfterFold.isDynamicDim(pos))
6544 return false;
6545 if (ShapedType::isDynamic(outerShapeWithoutTranspose[pos]))
6546 return false;
6547 if (ShapedType::isDynamic(tileSize))
6548 return false;
6549 int64_t paddingSize = outerShapeWithoutTranspose[pos] * tileSize -
6550 unpackedTypeAfterFold.getDimSize(pos);
6551 if (paddingSize >= tileSize)
6552 return false;
6553 }
6554 // extract_slice must not affect dimensions that are not being unpacked
6555 for (int64_t pos = 0, e = outerShapeWithoutTranspose.size(); pos < e; ++pos) {
6556 if (areOuterDimsTiled[pos])
6557 continue;
6558 int64_t dim = outerShapeWithoutTranspose[pos];
6559 if (ShapedType::isDynamic(dim))
6560 return false;
6561 if (dim != unpackedTypeAfterFold.getDimSize(pos))
6562 return false;
6563 }
6564 return true;
6565}
6566
6567bool UnPackOp::isLikeUnPad() {
6568 ShapedType packedTensorType = getSourceType();
6569 return isLikePadUnPad(*this, packedTensorType);
6570}
6571
6572::mlir::LogicalResult
6573UnPackOp::fold(FoldAdaptor adaptor,
6574 ::llvm::SmallVectorImpl<OpFoldResult> &results) {
6575 // TODO: Support Memref UnPackOp. Temporarily return failure.
6576 if (!hasPureTensorSemantics())
6577 return failure();
6578
6579 if (OpFoldResult reshapedSource = reshapeConstantSource(
6580 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
6581 cast<TensorType>(getResult().getType()))) {
6582 results.push_back(reshapedSource);
6583 return success();
6584 }
6585 return failure();
6586}
6587
6588/// Folds a tensor.cast op into a consuming UnPackOp op if the
6589/// `tensor.cast` has source that is more static than the consuming op.
6590///
6591/// Example:
6592/// ```mlir
6593/// %1 = tensor.cast %0 : tensor<1x1x8x1xi32> to tensor<1x1x?x1xi32>
6594/// %2 = tensor.unpack %1 ... : tensor<1x1x?x1xi32> -> tensor<7x?xi32>
6595/// ```
6596///
6597/// folds into:
6598///
6599/// ```mlir
6600/// %2 = tensor.unpack %0 ... tensor<1x1x8x1xi32> -> tensor<7x?xi32>
6601/// ```
6602struct FoldTensorCastUnPackOp : public OpRewritePattern<UnPackOp> {
6603 using OpRewritePattern<UnPackOp>::OpRewritePattern;
6604
6605 LogicalResult matchAndRewrite(UnPackOp op,
6606 PatternRewriter &rewriter) const override {
6607 // TODO: Support Memref UnPackOp. Temporarily return failure.
6608 if (!op.hasPureTensorSemantics())
6609 return failure();
6610
6612 return failure();
6613
6614 SmallVector<Type> newResultTypes(op->getResultTypes());
6615 SmallVector<Value> newOperands =
6617 Value sourceTensor = newOperands[0];
6618
6619 // Get the updated mixed-tile-sizes attribute.
6621 rewriter, sourceTensor.getType(), op.getMixedTiles());
6622
6623 // Clone op.
6624 // TODO: Strictly speaking, discardable attributes should be _discarded_ at
6625 // this point. However, in practice, we use them for things that we'd like
6626 // to preserve. Implement a better abstraction.
6627 UnPackOp newOp = UnPackOp::create(rewriter, op.getLoc(), sourceTensor,
6628 newOperands[1], op.getInnerDimsPos(),
6629 newMixedTileSizes, op.getOuterDimsPerm());
6630 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
6631
6632 // Replace op.
6633 Value oldResult = op.getResult();
6634 Value newResult = newOp.getResult();
6636 (newResult.getType() != oldResult.getType())
6637 ? tensor::CastOp::create(rewriter, op->getLoc(),
6638 oldResult.getType(), newResult)
6639 : newResult;
6640
6641 rewriter.replaceOp(op, {replacement});
6642
6643 return success();
6644 }
6645};
6646
6647//===----------------------------------------------------------------------===//
6648// BatchReduceMatmulOp
6649//===----------------------------------------------------------------------===//
6650SmallVector<utils::IteratorType> BatchReduceMatmulOp::getIteratorTypesArray() {
6652 utils::IteratorType::reduction, utils::IteratorType::parallel,
6653 utils::IteratorType::parallel, utils::IteratorType::reduction};
6654}
6655
6656SmallVector<AffineMap>
6657BatchReduceMatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
6658 AffineExpr d0, d1, d2, d3;
6659 SmallVector<AffineMap> indexingMaps;
6660 bindDims(context, d0, d1, d2, d3);
6661 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d3}, context));
6662 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d3, d2}, context));
6663 indexingMaps.push_back(AffineMap::get(4, 0, {d1, d2}, context));
6664 return indexingMaps;
6665}
6666
6667bool BatchReduceMatmulOp::isDefaultIndexingMaps(Attribute attr) {
6668 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
6669 if (!maps)
6670 return false;
6671 if (maps.size() != 3)
6672 return false;
6673 auto positions = getAffineResultPositions(maps);
6674 if (failed(positions))
6675 return false;
6676 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
6677 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
6678 (*positions)[2] == SmallVector<int64_t>{1, 2};
6679}
6680unsigned BatchReduceMatmulOp::getNumRegionArgs() { return 3; }
6681
6682std::string BatchReduceMatmulOp::getLibraryCallName() {
6683 return generateLibraryCallName(getOperation());
6684}
6685
6686/// Check if the op has broadcast and/or transpose semantic. Returns true if
6687/// the user defined indexing maps are not equal to default map.
6688bool BatchReduceMatmulOp::hasUserDefinedMaps() {
6689 SmallVector<AffineMap, 3> defaultMaps =
6690 getDefaultIndexingMaps(this->getContext());
6691 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
6692 return defaultMaps != explicitMaps;
6693}
6694
6695/// Returns true if the given bcastMap map is a valid broadcast map. A valid
6696/// broadcast map must include K dimension.
6697/// TODO: Strict inclusion of K dimension in the broadcast map is not
6698/// necessary for both input matrices simultaneously. We can relax this
6699/// condition to have K dimension for one input matrix map and infer the K
6700/// dimension for other input matrix map from the one already having K
6701/// dimension.
6702bool BatchReduceMatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap,
6703 bool isLHS) {
6704 assert(bcastMap.getNumResults() < 3 &&
6705 "Expected less than 3 result dim expr.");
6706 bool isValid = false;
6707 enum Indices { batchPos, mPos, nPos, kPos };
6708 if (bcastMap.getNumResults() == 1) {
6709 AffineExpr expr = bcastMap.getResult(0);
6710 isValid = expr.isFunctionOfDim(kPos);
6711 } else if (bcastMap.getNumResults() == 2) {
6712 AffineExpr expr0 = bcastMap.getResult(0);
6713 AffineExpr expr1 = bcastMap.getResult(1);
6714 isValid =
6715 isLHS ? ((expr0.isFunctionOfDim(batchPos) ||
6716 expr0.isFunctionOfDim(mPos)) &&
6717 expr1.isFunctionOfDim(kPos))
6718 : ((expr0.isFunctionOfDim(batchPos) &&
6719 expr1.isFunctionOfDim(kPos)) ||
6720 (expr0.isFunctionOfDim(kPos) && expr1.isFunctionOfDim(nPos)));
6721 }
6722 return isValid;
6723}
6724
6725void BatchReduceMatmulOp::regionBuilder(
6726 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
6727 function_ref<InFlightDiagnostic()> emitError) {
6728 if (emitError && block.getNumArguments() != 3) {
6729 emitError() << "BatchReduceMatmulOp regionBuilder expects 3 args, got "
6730 << block.getNumArguments();
6731 return;
6732 }
6733 assert(block.getNumArguments() == 3 &&
6734 "BatchReduceMatmulOp regionBuilder expects 3 args");
6735 RegionBuilderHelper helper(b, block);
6736 SmallVector<Value> yields;
6737
6738 auto toType = block.getArgument(2).getType();
6739 Value castValA =
6740 helper.buildTypeFn(TypeFn::cast_signed, toType, block.getArgument(0));
6741 Value castValB =
6742 helper.buildTypeFn(TypeFn::cast_signed, toType, block.getArgument(1));
6743 Value mulVal =
6744 helper.buildBinaryFn(BinaryFn::mul, castValA, castValB, emitError);
6745 if (!castValA || !castValB || !mulVal)
6746 return;
6747 Value addVal =
6748 helper.buildBinaryFn(BinaryFn::add, block.getArgument(2), mulVal);
6749 if (!addVal)
6750 return;
6751 yields.push_back(addVal);
6752 helper.yieldOutputs(yields);
6753}
6754
6755ParseResult BatchReduceMatmulOp::parse(OpAsmParser &parser,
6756 OperationState &result) {
6757 SmallVector<Attribute, 3> indexingMapsAttr;
6758 Attribute mapAttr;
6759 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
6760 if (parser.parseEqual())
6761 return failure();
6762 if (parser.parseLSquare())
6763 return failure();
6764
6765 do {
6766 if (parser.parseAttribute(mapAttr))
6767 return failure();
6768 if (!isa<AffineMapAttr>(mapAttr)) {
6769 return parser.emitError(parser.getCurrentLocation(),
6770 "expected affine map attribute");
6771 }
6772 indexingMapsAttr.push_back(mapAttr);
6773
6774 if (parser.parseOptionalComma())
6775 break;
6776 } while (true);
6777
6778 if (parser.parseRSquare())
6779 return failure();
6780 }
6781 // Initialize indexingMaps, if not supplied explicitly.
6782 if (indexingMapsAttr.empty()) {
6783 indexingMapsAttr = llvm::map_to_vector(
6784 BatchReduceMatmulOp::getDefaultIndexingMaps(parser.getContext()),
6785 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
6786 }
6787 result.addAttribute("indexing_maps",
6788 parser.getBuilder().getArrayAttr(indexingMapsAttr));
6789 return ::parseNamedStructuredOp(parser, result,
6790 BatchReduceMatmulOp::getNumRegionArgs(),
6791 BatchReduceMatmulOp::getRegionBuilder());
6792}
6793
6794void BatchReduceMatmulOp::print(OpAsmPrinter &p) {
6795 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector(
6796 BatchReduceMatmulOp::getDefaultIndexingMaps(getContext()),
6797 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
6798
6799 if (!llvm::equal(getIndexingMaps(), indexingMaps)) {
6800 p << " indexing_maps = [";
6801 llvm::interleaveComma(getIndexingMaps(), p,
6802 [&](Attribute attr) { p.printAttribute(attr); });
6803 p << "]";
6804 }
6805
6806 SmallVector<StringRef, 3> elidedAttrs = {
6807 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
6808 ::printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
6809 elidedAttrs);
6810}
6811
6812/// Verify the user defined indexing maps.
6813LogicalResult BatchReduceMatmulOp::verify() {
6814 // Verification of pure batch_reduce_matmul is handled by
6815 // verifyStructuredOpInterface().
6816 if (!hasUserDefinedMaps())
6817 return success();
6818
6819 for (unsigned opIndex = 0; opIndex < 3; opIndex++) {
6821 return failure();
6822 }
6823 return success();
6824}
6825LogicalResult BatchReduceMatmulOp::fold(FoldAdaptor,
6826 SmallVectorImpl<OpFoldResult> &) {
6827 return memref::foldMemRefCast(*this);
6828}
6829void BatchReduceMatmulOp::getEffects(
6830 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
6831 &effects) {
6832 if (hasPureTensorSemantics())
6833 return;
6834 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
6835}
6836
6837Speculation::Speculatability BatchReduceMatmulOp::getSpeculatability() {
6838 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
6839}
6840
6841} // namespace linalg
6842} // namespace mlir
6843
6844//===----------------------------------------------------------------------===//
6845// LinalgDialect
6846//===----------------------------------------------------------------------===//
6847
6848void LinalgDialect::getCanonicalizationPatterns(
6849 RewritePatternSet &results) const {
6850 results.add<EraseDeadLinalgOp, FoldTensorCastConsumerOp, FoldTensorCastPackOp,
6851 FoldTensorCastUnPackOp, InferStaticShapeOfOperands>(getContext());
6852}
6853
6854Operation *LinalgDialect::materializeConstant(OpBuilder &builder,
6855 Attribute value, Type type,
6856 Location loc) {
6857 return arith::ConstantOp::materialize(builder, value, type, loc);
6858}
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 Type getElementType(Type type)
Determine the element type of type.
static LogicalResult verifyExtendedMatmulSemantic(MatmulOp matmulOp, unsigned opIndex)
Verifies the broadcast and transpose semantic sepecified by the explicit indexing map for the MatmulO...
static void fillStructuredOpRegion(OpBuilder &opBuilder, Region &region, TypeRange inputTypes, TypeRange outputTypes, ArrayRef< NamedAttribute > attrs, function_ref< InFlightDiagnostic()> emitError, RegionBuilderFn regionBuilder)
Fills the region of a structured operation using the provided regionBuilder.
static void buildIdentityRegion(OpBuilder &builder, Location loc, Region &region, ValueRange inputs, ValueRange outputs)
static void buildBatchMatmulOp(OpBuilder &b, OperationState &state, std::optional< TypeRange > resultTensorTypes, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes, RegionBuilderFn regionBuilder, ArrayRef< AffineMap > defaultIndexingMaps)
static Value buildDivOp(OpBuilder &builder, Location loc, Value numerator, Value denominator, Value output, int64_t dim)
Produce a linalg generic that computes the final step of the softmax decomposition.
static bool areResultExprsSubsetOf(AffineMap subMap, AffineMap fullMap)
static LogicalResult appendMangledType(llvm::raw_string_ostream &ss, Type t)
static bool canUseShortForm(Block *body, bool initFirst=false, bool mapInit=true)
static bool isBroadcasted(AffineMap explictMap, AffineMap defaultMap)
Check if the user defined map is valid broadcast map.
static void printCommonStructuredOpParts(OpAsmPrinter &p, ValueRange inputs, ValueRange outputs)
llvm::function_ref< void( ImplicitLocOpBuilder &, Block &, ArrayRef< NamedAttribute >, function_ref< InFlightDiagnostic()>)> RegionBuilderFn
static ParseResult parseDenseI64ArrayAttr(OpAsmParser &parser, NamedAttrList &attributes, StringRef attributeName)
static void printDenseI64ArrayAttr(OpAsmPrinter &p, StringRef attributeName, ArrayRef< int64_t > attributeValue)
static Value buildSubAndExpOp(OpBuilder &builder, Location loc, Value input, Value max, Value output, int64_t dim)
Produce a linalg generic that computes the second step of the softmax decomposition: res = exp(input ...
static void printShortForm(OpAsmPrinter &p, Operation *payloadOp)
static LogicalResult verifyOutputMap(OpTy batchVariantMatmulOp, AffineMap opIndexingMap)
This function checks if the given AffineMap for the output of a BatchMatmulOp/BatchReduceMatmulOp has...
static std::optional< TypedAttr > getScalarConstantAttrFromDenseSplat(Value input)
Definition LinalgOps.cpp:95
static void buildStructuredOp(OpBuilder &b, OperationState &state, std::optional< TypeRange > resultTensorTypes, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes, RegionBuilderFn regionBuilder)
Creates a structured operation given inputs, outputs, and attributes.
static ParseResult parseDstStyleOp(OpAsmParser &parser, OperationState &result, function_ref< ParseResult(OpAsmParser &, NamedAttrList &)> parseAttrsFn=nullptr)
static LogicalResult verifyInputMaps(OpTy batchVariantMatmulOp, AffineMap opIndexingMap, AffineMap defaultIndexingMap, bool isLHS)
static Value reduce(OpBuilder &builder, Location loc, Value input, Value output, int64_t dim)
static Speculation::Speculatability getGenericSpeculatabilityImpl(LinalgOp linalgOp)
static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp)
static ParseResult parseNamedStructuredOp(OpAsmParser &parser, OperationState &result, unsigned numRegionArgs, RegionBuilderFn regionBuilder)
static void getGenericEffectsImpl(SmallVectorImpl< SideEffects::EffectInstance< MemoryEffects::Effect > > &effects, LinalgOp linalgOp)
static void buildGenericRegion(OpBuilder &builder, Location loc, Region &region, ValueRange inputs, ValueRange outputs, function_ref< void(OpBuilder &, Location, ValueRange)> bodyBuild)
static ParseResult parseNamedStructuredOpResults(OpAsmParser &parser, SmallVectorImpl< Type > &resultTypes)
static OpFoldResult getDimValue(OpBuilder &builder, Location loc, Value v, int64_t dim)
Return a memref.dim or tensor.dim for the shape of v at dim.
Definition LinalgOps.cpp:60
static void addBodyWithPayloadOp(OpAsmParser &parser, OperationState &result, const OperationName &payloadOpName, const NamedAttrList &payloadOpAttrs, ArrayRef< Value > operands, bool initFirst=false, bool mapInit=true)
static std::tuple< SmallVector< utils::IteratorType >, SmallVector< AffineMap > > computeIteratorTypesAndIndexingMaps(OpBuilder &builder, int64_t inputRank, int64_t dim, bool allParallel=false)
static void buildBatchReduceMatmulOp(OpBuilder &b, OperationState &state, std::optional< TypeRange > resultTensorTypes, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes, RegionBuilderFn regionBuilder, ArrayRef< AffineMap > indexingMaps)
static void printNamedStructuredOpResults(OpAsmPrinter &p, TypeRange resultTypes)
static void buildMatmulOp(OpBuilder &b, OperationState &state, std::optional< TypeRange > resultTensorTypes, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes, RegionBuilderFn regionBuilder, ArrayRef< AffineMap > defaultIndexingMaps)
static LogicalResult verifyExtendedBatchVariantMatmulSemantic(OpTy batchVariantMatmulOp, unsigned opIndex)
Verifies the broadcast and transpose semantic specified by the explicit indexing map for the BatchMat...
static void printNamedStructuredOp(OpAsmPrinter &p, Operation *op, ValueRange inputs, ValueRange outputs, ArrayRef< StringRef > elidedAttrs={})
static ParseResult parseCommonStructuredOpParts(OpAsmParser &parser, OperationState &result, SmallVectorImpl< Type > &inputTypes, SmallVectorImpl< Type > &outputTypes, bool addOperandSegmentSizes=true)
Common parsing used for both named structured ops created by ods-gen and by manually defined C++ ops.
static ParseResult parseNamedStructuredOpRegion(OpAsmParser &parser, Region &region, unsigned numRegionArgs, TypeRange inputTypes, TypeRange outputTypes, ArrayRef< NamedAttribute > attrs, RegionBuilderFn regionBuilder, SMLoc loc)
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
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 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 Value max(ImplicitLocOpBuilder &builder, Value value, Value bound)
static LogicalResult getResultTilePosition(RewriterBase &rewriter, ReductionTilingStrategy reductionStrategy, int64_t index, Value tiledResult, TilingInterface op, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, const SetVector< unsigned > &reductionDims, SmallVector< OpFoldResult > &resultOffset, SmallVector< OpFoldResult > &resultSize)
static FailureOr< TilingResult > getTiledImplementation(RewriterBase &rewriter, TilingInterface op, ReductionTilingStrategy reductionStrategy, ValueRange regionIterArg, ArrayRef< OpFoldResult > offsets, ArrayRef< OpFoldResult > sizes, ValueRange ivs, ArrayRef< OpFoldResult > numThreads, ArrayRef< OpFoldResult > givenTileSizes, ArrayRef< InnerTileAlignment > innerTileAlignments, const SetVector< unsigned > &reductionDims)
static ArrayRef< int64_t > getShape(Type type)
Returns the shape of the given type.
Definition Traits.cpp:117
Base type for affine expression.
Definition AffineExpr.h:68
bool isFunctionOfDim(unsigned position) const
Return true if the affine expression involves AffineDimExpr position.
AffineExpr ceilDiv(uint64_t v) const
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
AffineMap dropResults(ArrayRef< int64_t > positions) const
Definition AffineMap.h:299
static AffineMap getMultiDimIdentityMap(unsigned numDims, MLIRContext *context)
Returns an AffineMap with 'numDims' identity result dim exprs.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
@ Paren
Parens surrounding zero or more operands.
@ Square
Square brackets surrounding zero or more operands.
virtual ParseResult parseColonTypeList(SmallVectorImpl< Type > &result)=0
Parse a colon followed by a type list, which must have at least one type.
virtual Builder & getBuilder() const =0
Return a builder which provides useful access to MLIRContext, global objects like types and attribute...
virtual ParseResult parseCommaSeparatedList(Delimiter delimiter, function_ref< ParseResult()> parseElementFn, StringRef contextMessage=StringRef())=0
Parse a list of comma-separated items with an optional delimiter.
virtual ParseResult parseOptionalAttrDict(NamedAttrList &result)=0
Parse a named dictionary into 'result' if it is present.
virtual ParseResult parseOptionalKeyword(StringRef keyword)=0
Parse the given keyword if present.
MLIRContext * getContext() const
virtual ParseResult parseRParen()=0
Parse a ) token.
virtual InFlightDiagnostic emitError(SMLoc loc, const Twine &message={})=0
Emit a diagnostic at the specified location and return failure.
virtual ParseResult parseLSquare()=0
Parse a [ token.
virtual ParseResult parseRSquare()=0
Parse a ] token.
virtual ParseResult parseOptionalArrow()=0
Parse a '->' token if present.
virtual ParseResult parseRBrace()=0
Parse a } token.
virtual ParseResult parseEqual()=0
Parse a = token.
virtual SMLoc getCurrentLocation()=0
Get the location of the next token and store it into the argument.
virtual ParseResult parseOptionalComma()=0
Parse a , token if present.
virtual ParseResult parseColon()=0
Parse a : token.
virtual ParseResult parseOptionalLess()=0
Parse a '<' token if present.
virtual ParseResult parseGreater()=0
Parse a '>' token.
virtual ParseResult parseLParen()=0
Parse a ( token.
virtual ParseResult parseType(Type &result)=0
Parse a type.
virtual ParseResult parseOptionalArrowTypeList(SmallVectorImpl< Type > &result)=0
Parse an optional arrow followed by a type list.
ParseResult parseTypeList(SmallVectorImpl< Type > &result)
Parse a type list.
ParseResult parseKeyword(StringRef keyword)
Parse a given keyword.
virtual ParseResult parseAttribute(Attribute &result, Type type={})=0
Parse an arbitrary attribute of a given type and return it in result.
virtual ParseResult parseOptionalLBrace()=0
Parse a { token if present.
virtual void decreaseIndent()
Decrease indentation.
virtual void increaseIndent()
Increase indentation.
void printOptionalArrowTypeList(TypeRange &&types)
Print an optional arrow followed by a type list.
virtual void printAttribute(Attribute attr)
virtual void printNewline()
Print a newline and indent the printer to the start of the current operation/attribute/type.
Attributes are known-constant values of operations.
Definition Attributes.h:25
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:153
unsigned getNumArguments()
Definition Block.h:152
OpListType & getOperations()
Definition Block.h:161
Operation * getTerminator()
Get the terminator operation of this block.
Definition Block.cpp:249
BlockArgument addArgument(Type type, Location loc)
Add one value to the argument list.
Definition Block.cpp:158
BlockArgListType getArguments()
Definition Block.h:111
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
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:167
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:171
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:392
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:116
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:267
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:369
Location getUnknownLoc()
Definition Builders.cpp:25
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:271
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:55
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:323
An attribute that represents a reference to a dense vector or tensor object.
std::enable_if_t<!std::is_base_of< Attribute, T >::value||std::is_same< Attribute, T >::value, T > getSplatValue() const
Return the splat value for this attribute.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
IRValueT get() const
Return the current value being used by this operand.
ImplicitLocOpBuilder maintains a 'current location', allowing use of the create<> method without spec...
Definition Builders.h:632
This class represents a diagnostic that is inflight and set to be reported.
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
NamedAttrList is array of NamedAttributes that tracks whether it is sorted and does some basic work t...
ArrayRef< NamedAttribute > getAttrs() const
Return all of the attributes on this operation.
DictionaryAttr getDictionary(MLIRContext *context) const
Return a dictionary attribute for the underlying dictionary.
void append(StringRef name, Attribute attr)
Add an attribute with the specified name.
Attribute set(StringAttr name, Attribute value)
If the an attribute exists with the specified name, change it to the new value.
NamedAttribute represents a combination of a name and an Attribute value.
Definition Attributes.h:164
StringAttr getName() const
Return the name of the attribute.
Attribute getValue() const
Return the value of the attribute.
Definition Attributes.h:179
The OpAsmParser has methods for interacting with the asm parser: parsing things from it,...
virtual ParseResult parseRegion(Region &region, ArrayRef< Argument > arguments={}, bool enableNameShadowing=false)=0
Parses a region.
virtual ParseResult parseArgumentList(SmallVectorImpl< Argument > &result, Delimiter delimiter=Delimiter::None, bool allowType=false, bool allowAttrs=false)=0
Parse zero or more arguments with a specified surrounding delimiter.
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 FailureOr< OperationName > parseCustomOperationName()=0
Parse the name of an operation, in the custom form.
ParseResult resolveOperands(Operands &&operands, Type type, SmallVectorImpl< Value > &result)
Resolve a list of operands to SSA values, emitting an error on failure, or appending the results to t...
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.
virtual ParseResult parseOperandList(SmallVectorImpl< UnresolvedOperand > &result, Delimiter delimiter=Delimiter::None, bool allowResultNumber=true, int requiredOperandCount=-1)=0
Parse zero or more SSA comma-separated operand references with a specified surrounding delimiter,...
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.
virtual void printRegion(Region &blocks, bool printEntryBlockArgs=true, bool printBlockTerminators=true, bool printEmptyBlock=false)=0
Prints a region.
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
Block * createBlock(Region *parent, Region::iterator insertPt={}, TypeRange argTypes={}, ArrayRef< Location > locs={})
Add new block with 'argTypes' arguments and set the insertion point to the end of it.
Definition Builders.cpp:435
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
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:462
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
unsigned getResultNumber() const
Returns the number of this result.
Definition Value.h:466
StringRef getStringRef() const
Return the name of this operation. This always succeeds.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Attribute getAttr(StringAttr name)
Return the specified attribute if present, null otherwise.
Definition Operation.h:559
result_iterator result_begin()
Definition Operation.h:438
ArrayRef< NamedAttribute > getAttrs()
Return all of the attributes on this operation.
Definition Operation.h:537
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
unsigned getNumOperands()
Definition Operation.h:371
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_iterator result_end()
Definition Operation.h:439
result_type_range getResultTypes()
Definition Operation.h:453
operand_range getOperands()
Returns an iterator on the underlying Value's.
Definition Operation.h:403
result_range getResults()
Definition Operation.h:440
unsigned getNumResults()
Return the number of results held by this operation.
Definition Operation.h:429
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & emplaceBlock()
Definition Region.h:46
iterator end()
Definition Region.h:56
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void finalizeOpModification(Operation *op)
This method is used to signal the end of an in-place modification of the given operation.
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
void replaceAllUsesExcept(Value from, Value to, Operation *exceptedUser)
Find uses of from and replace them with to except if the user is exceptedUser.
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 startOpModification(Operation *op)
This method is used to notify the rewriter that an in-place operation modification is about to happen...
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class represents a specific instance of an effect.
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
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isSignlessIntOrIndexOrFloat() const
Return true if this is a signless integer, index, or float type.
Definition Types.cpp:106
This class provides an abstraction over the different types of ranges over Values.
Definition ValueRange.h:389
type_range getTypes() const
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Block * getParentBlock()
Return the Block in which this Value is defined.
Definition Value.cpp:46
bool hasOneUse() const
Returns true if this value has exactly one use.
Definition Value.h:197
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
static Attribute parse(AsmParser &parser, Type type)
Specialization of linalg.batch_matmul op that has a transpose map on A.
Definition Linalg.h:251
static bool isDefaultIndexingMaps(Attribute attr)
Checks if the affine map is the expected one for this operation.
static bool classof(Operation *op)
static void build(OpBuilder &builder, OperationState &result, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
Build a transpose A matmul.
static BatchMatmulTransposeAOp create(OpBuilder &builder, Location location, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
Specialization of linalg.batch_matmul op that has a transpose map on B.
Definition Linalg.h:298
static void build(OpBuilder &builder, OperationState &result, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
Build a transpose B matmul.
static bool classof(Operation *op)
static BatchMatmulTransposeBOp create(OpBuilder &builder, Location location, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
static bool isDefaultIndexingMaps(Attribute attr)
Checks if the affine map is the expected one for this operation.
Specialization of linalg.matmul op that has a transpose map on A.
Definition Linalg.h:157
static bool isDefaultIndexingMaps(Attribute attr)
Checks if the affine map is the expected one for this operation.
static MatmulTransposeAOp create(OpBuilder &builder, Location location, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
static void build(OpBuilder &builder, OperationState &result, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
Build a transpose A matmul.
static bool classof(Operation *op)
Specialization of linalg.matmul op that has a transpose map on B.
Definition Linalg.h:204
static void build(OpBuilder &builder, OperationState &result, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
Build a transpose B matmul.
static MatmulTransposeBOp create(OpBuilder &builder, Location location, ValueRange inputs, ValueRange outputs, ArrayRef< NamedAttribute > attributes={})
static bool isDefaultIndexingMaps(Attribute attr)
Checks if the affine map is the expected one for this operation.
static bool classof(Operation *op)
constexpr auto RecursivelySpeculatable
Speculatability
This enum is returned from the getSpeculatability method in the ConditionallySpeculatable op interfac...
constexpr auto Speculatable
constexpr auto NotSpeculatable
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...
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...
Value getIdentityValue(AtomicRMWKind op, Type resultType, OpBuilder &builder, Location loc, bool useOnlyFiniteValue=false)
Returns the identity value associated with an AtomicRMWKind op.
static SmallVector< int64_t > asShapeWithAnyValueAsDynamic(ArrayRef< OpFoldResult > ofrs)
Converts OpFoldResults to int64_t shape entries, unconditionally mapping all Value's to kDynamic,...
static LogicalResult reifyResultShapesImpl(OpTy op, OpBuilder &builder, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
static bool inferStaticShape(PackOp packOp, SmallVectorImpl< int64_t > &srcShape, SmallVectorImpl< int64_t > &destShape)
Returns true if the srcShape or destShape is different from the one in packOp and populates each with...
static SmallVector< int64_t > getStaticTilesImpl(OpTy op)
static void getPackUnPackEffectsImpl(OpTy op, SmallVectorImpl< SideEffects::EffectInstance< MemoryEffects::Effect > > &effects)
static bool isInvalidPackingPosSpecification(ArrayRef< int64_t > dimsPos, size_t rank)
Returns true if dimsPos is invalid.
static SmallVector< OpFoldResult > getMixedTilesImpl(OpTy op)
static DenseMap< int64_t, OpFoldResult > getDimAndTileMappingImpl(OpTy op)
SmallVector< AffineExpr, 4 > concat(ArrayRef< AffineExpr > a, ArrayRef< AffineExpr > b)
Return the vector that is the concatenation of a and b.
static ArityGroupAndKind getArityGroupAndKind(ElementwiseKind kind)
static PackOrUnPackTransposeResult commonPermutationOfPackAndUnPackOp(OpTy packOrUnPackOp, ArrayRef< int64_t > innerPermutation, ArrayRef< int64_t > outerPermutation)
OpFoldResult createFoldedDimOp(OpBuilder &b, Location loc, Value val, int64_t dim)
Create one memref::DimOp or tensor::DimOp depending on the type of val.
static SmallVector< OpFoldResult > getNewMixedTileSizes(PatternRewriter &rewriter, Type newPackedTy, ArrayRef< OpFoldResult > mixedTiles)
static bool areTilesAndTiledDimsAllConstant(OpTy op)
Returns true if the tiles and the tiled dims are constant.
std::string generateLibraryCallName(Operation *op)
Returns the name mangled library call name to disambiguate between different overloads at the C level...
template SmallVector< int64_t > getPackedOuterShapeWithoutTransposition< UnPackOp >(UnPackOp)
static bool paddingIsNotNeeded(PackOp op)
Returns true if the pack op does not need a padding value.
static bool isLikePadUnPad(PackOrUnpackOp packOp, ShapedType packedTensorType)
AffineMap extractOrIdentityMap(std::optional< AffineMap > maybeMap, unsigned rank, MLIRContext *context)
Returns maybeMap.get() if maybeMap is set, otherwise returns the symbol-less identity map of rank.
SmallVector< AffineExpr, 4 > makeAffineDimExprs(unsigned num, unsigned &startIdx, MLIRContext *context)
Returns num AffineDimExpr dimensions at positions [startIdx, startIdx + num) and increments startIdx ...
static FailureOr< SmallVector< SmallVector< int64_t > > > getAffineResultPositions(ArrayAttr maps)
static bool haveSameTiles(PackOp packOp, UnPackOp unPackOp)
Value createOrFoldDimOp(OpBuilder &b, Location loc, Value val, int64_t dim)
Create one memref::DimOp or tensor::DimOp depending on the type of val.
static bool hasSameInnerOuterAttribute(PackOp packOp, UnPackOp unPackOp)
template SmallVector< int64_t > getPackedOuterShapeWithoutTransposition< PackOp >(PackOp)
std::pair< int64_t, int64_t > getFmrFromWinogradConv2DFmr(WinogradConv2DFmr fmr)
Converts the given WinogradConv2DFmr enumeration value to a pair of m and r parameters.
std::optional< WinogradConv2DFmr > getWinogradConv2DFmr(int64_t m, int64_t r)
Converts the given m and r parameters to a WinogradConv2DFmr enumeration value.
static LogicalResult commonVerifierPackAndUnPackOp(OpTy packOrUnPack)
static FailureOr< ArrayAttr > parseIndexingMapsAttr(OpAsmParser &parser)
SmallVector< int64_t > getPackedOuterShapeWithoutTransposition(OpTy packOrUnPack)
Returns the outer shape in the packed domain before applying the transposition.
LogicalResult foldMemRefCast(Operation *op, Value inner=nullptr)
This is a common utility used for patterns of the form "someop(memref.cast) -> someop".
Definition MemRefOps.cpp:47
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
SparseTensorEncodingAttr getSparseTensorEncoding(Type type)
Convenience method to get a sparse encoding attribute from a type.
bool hasFoldableTensorCastOperand(Operation *op)
Return true if any of the operands of op is a CastOp that can be folded into its consumer,...
bool canFoldIntoProducerOp(CastOp castOp)
Determines whether the tensor::CastOp casts to a more static version of the source tensor.
SmallVector< Value > getUpdatedOperandsAfterCastOpFolding(DestinationStyleOpInterface op, SmallVector< Type > &newResTy)
Assuming that op contains at least one operand that is a foldable CastOp (i.e.
SmallVector< OpFoldResult > getMixedSizes(OpBuilder &builder, Location loc, Value value)
Return the dimensions of the given tensor value.
Definition TensorOps.cpp:69
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
Value convertScalarToDtype(OpBuilder &b, Location loc, Value operand, Type toType, bool isUnsignedCast)
Converts a scalar value operand to type toType.
Definition Utils.cpp:241
detail::DenseArrayAttrImpl< int64_t > DenseI64ArrayAttr
function_ref< void(Value, StringRef)> OpAsmSetValueNameFn
A functor used to set the name of the start of a result group of an operation.
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
LogicalResult reifyResultShapes(OpBuilder &b, Operation *op, ReifiedRankedShapedTypeDims &reifiedReturnShapes)
Reify the shape of the result of an operation (typically in terms of the shape of its operands).
ParseResult parseDynamicIndexList(OpAsmParser &parser, SmallVectorImpl< OpAsmParser::UnresolvedOperand > &values, DenseI64ArrayAttr &integers, DenseBoolArrayAttr &scalableFlags, SmallVectorImpl< Type > *valueTypes=nullptr, AsmParser::Delimiter delimiter=AsmParser::Delimiter::Square)
Parser hooks for custom directive in assemblyFormat.
bool areAllConstantIntValue(ArrayRef< OpFoldResult > ofrs, int64_t value)
Return true if all of ofrs are constant integers equal to value.
bool isEqualConstantIntOrValue(OpFoldResult ofr1, OpFoldResult ofr2)
Return true if ofr1 and ofr2 are the same integer constant attribute values or the same SSA value.
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
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
InFlightDiagnostic emitError(Location loc)
Utility method to emit an error message using this location.
AffineMap inversePermutation(AffineMap map)
Returns a map of codomain to domain dimensions such that the first codomain dimension for a particula...
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.
SmallVector< SmallVector< OpFoldResult > > ReifiedRankedShapedTypeDims
bool isIdentityPermutation(ArrayRef< int64_t > permutation)
Returns true if permutation is an identity permutation.
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
LogicalResult verifyRanksMatch(Operation *op, ShapedType lhs, ShapedType rhs, StringRef lhsName, StringRef rhsName)
Verify that two shaped types have matching ranks.
Operation * clone(OpBuilder &b, Operation *op, TypeRange newResultTypes, ValueRange newOperands)
SmallVector< Loops, 8 > tile(ArrayRef< scf::ForOp > forOps, ArrayRef< Value > sizes, ArrayRef< scf::ForOp > targets)
Performs tiling fo imperfectly nested loops (with interchange) by strip-mining the forOps by sizes an...
Definition Utils.cpp:1330
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
LogicalResult verifyCompatibleShape(ArrayRef< int64_t > shape1, ArrayRef< int64_t > shape2)
Returns success if the given two shapes are compatible.
SetVector< Operation * > getSlice(Operation *op, const BackwardSliceOptions &backwardSliceOptions={}, const ForwardSliceOptions &forwardSliceOptions={})
Iteratively computes backward slices and forward slices until a fixed point is reached.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
void applyPermutationToVector(SmallVector< T, N > &inVec, ArrayRef< int64_t > permutation)
Apply the permutation defined by permutation to inVec.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
SmallVector< int64_t > dropDims(ArrayRef< int64_t > inputPerm, ArrayRef< int64_t > dropPositions)
Returns a permutation vector that drop the input dims in dropPositions from inputPerm.
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.
void printDynamicIndexList(OpAsmPrinter &printer, Operation *op, OperandRange values, ArrayRef< int64_t > integers, ArrayRef< bool > scalableFlags, TypeRange valueTypes=TypeRange(), AsmParser::Delimiter delimiter=AsmParser::Delimiter::Square)
Printer hooks for custom directive in assemblyFormat.
SmallVector< int64_t > invertPermutationVector(ArrayRef< int64_t > permutation)
Helper method to apply to inverse a permutation.
Rewrite a broadcast of a dense splat constant into a dense splat constant of the broadcast output sha...
LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp, PatternRewriter &rewriter) const override
Fold back-to-back broadcasts together.
LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp, PatternRewriter &rewriter) const override
Rewrite a transpose of a dense splat constant into a dense splat constant of the transposed output sh...
LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp, PatternRewriter &rewriter) const override
Fold transpose with transpose.
LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp, PatternRewriter &rewriter) const override
This pattern canonicalize transpose by swapping the order of broadcast and transpose: transpose(broad...
LogicalResult matchAndRewrite(linalg::TransposeOp transposeOp, PatternRewriter &rewriter) const override
This is the representation of an operand reference.
OpInterfaceRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting a...
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
OpRewritePattern(MLIRContext *context, PatternBenefit benefit=1, ArrayRef< StringRef > generatedNames={})
Patterns must specify the root operation name they match against, and can also specify the benefit of...
This represents an operation in an abstracted form, suitable for use with the builder APIs.
void addOperands(ValueRange newOperands)
void addAttributes(ArrayRef< NamedAttribute > newAttributes)
Add an array of named attributes.
void addAttribute(StringRef name, Attribute attr)
Add an attribute with the specified name.
void addTypes(ArrayRef< Type > newTypes)
Region * addRegion()
Create a region that should be attached to the operation.
Folds a tensor.cast op into a consuming PackOp op if the tensor.cast has source that is more static t...
LogicalResult matchAndRewrite(PackOp op, PatternRewriter &rewriter) const override
Folds a tensor.cast op into a consuming UnPackOp op if the tensor.cast has source that is more static...
LogicalResult matchAndRewrite(UnPackOp op, PatternRewriter &rewriter) const override