MLIR 24.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 DenseSet<int64_t> uniquedDims(llvm::from_range, dimensionsRef);
2360 if (uniquedDims.size() != dimensionsRef.size())
2361 return emitOpError() << "dimensions should not contain duplicates";
2362
2363 // Mapping from input dims to init dims.
2364 SmallVector<int64_t> dimMap;
2365 for (auto dim : llvm::seq<int64_t>(0, initRank)) {
2366 if (!llvm::is_contained(dimensionsRef, dim))
2367 dimMap.push_back(dim);
2368 }
2369
2370 for (const auto &[inputDimIdx, initDimIdx] : llvm::enumerate(dimMap)) {
2371 // This dimensions is mapped from the input. Init and input dims should
2372 // match.
2373 if (inputShape[inputDimIdx] != initShape[initDimIdx])
2374 return emitOpError() << "input dim " << inputDimIdx
2375 << " should match init dim " << initDimIdx
2376 << ". input: " << inputShape[inputDimIdx]
2377 << ", init: " << initShape[initDimIdx];
2378 }
2379
2380 return success();
2381}
2382
2383SmallVector<utils::IteratorType> BroadcastOp::getIteratorTypesArray() {
2384 int64_t rank = getInit().getType().getRank();
2385 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
2386}
2387
2388ArrayAttr BroadcastOp::getIndexingMaps() {
2389 Builder builder(getContext());
2390 int64_t rank = getInit().getType().getRank();
2391 return builder.getAffineMapArrayAttr(
2392 {builder.getMultiDimIdentityMap(rank).dropResults(getDimensions()),
2393 builder.getMultiDimIdentityMap(rank)});
2394}
2395
2396void BroadcastOp::getEffects(
2397 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
2398 &effects) {
2399 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
2400}
2401
2402Speculation::Speculatability BroadcastOp::getSpeculatability() {
2403 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
2404}
2405
2406/// Fold back-to-back broadcasts together.
2407struct FoldBroadcasts : OpRewritePattern<linalg::BroadcastOp> {
2408 using OpRewritePattern<linalg::BroadcastOp>::OpRewritePattern;
2409
2410 LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp,
2411 PatternRewriter &rewriter) const override {
2412 auto defBroadcastOp = broadcastOp.getInput().getDefiningOp<BroadcastOp>();
2413 if (!defBroadcastOp)
2414 return failure();
2415 ArrayRef<int64_t> defDimensions = defBroadcastOp.getDimensions();
2416 ArrayRef<int64_t> dimensions = broadcastOp.getDimensions();
2417 SmallVector<int64_t> foldedDims(dimensions);
2418 Value init = broadcastOp.getInit();
2419 int64_t initRank = cast<ShapedType>(init.getType()).getRank();
2420 // Mapping from input dims to init dims.
2421 SmallVector<int64_t> dimMap;
2422 for (auto dim : llvm::seq<int64_t>(0, initRank)) {
2423 if (!llvm::is_contained(dimensions, dim))
2424 dimMap.push_back(dim);
2425 }
2426 for (auto dim : defDimensions)
2427 foldedDims.push_back(dimMap[dim]);
2428
2429 llvm::sort(foldedDims);
2430 rewriter.replaceOpWithNewOp<BroadcastOp>(
2431 broadcastOp, defBroadcastOp.getInput(), init, foldedDims);
2432 return success();
2433 }
2434};
2435
2436/// Rewrite a broadcast of a dense splat constant into a dense splat constant of
2437/// the broadcast output shape.
2438struct FoldBroadcastSplatConstant : OpRewritePattern<linalg::BroadcastOp> {
2439 using OpRewritePattern<linalg::BroadcastOp>::OpRewritePattern;
2440
2441 LogicalResult matchAndRewrite(linalg::BroadcastOp broadcastOp,
2442 PatternRewriter &rewriter) const override {
2443 if (!broadcastOp.hasPureTensorSemantics())
2444 return failure();
2445
2446 auto splatValue =
2447 getScalarConstantAttrFromDenseSplat(broadcastOp.getInput());
2448
2449 if (!splatValue.has_value())
2450 return failure();
2451
2452 auto resultType =
2453 cast<RankedTensorType>(broadcastOp.getResult()[0].getType());
2454 if (!resultType.hasStaticShape())
2455 return rewriter.notifyMatchFailure(broadcastOp,
2456 "result type has dynamic shape");
2457
2458 auto resultAttr = DenseElementsAttr::get(resultType, splatValue.value());
2459 rewriter.replaceOpWithNewOp<arith::ConstantOp>(broadcastOp, resultType,
2460 resultAttr);
2461 return success();
2462 }
2463};
2464
2465void BroadcastOp::getCanonicalizationPatterns(RewritePatternSet &results,
2466 MLIRContext *context) {
2467 results.add<EraseIdentityLinalgOp<BroadcastOp>, FoldBroadcasts,
2468 FoldBroadcastSplatConstant>(context);
2469}
2470
2471//===----------------------------------------------------------------------===//
2472// YieldOp
2473//===----------------------------------------------------------------------===//
2474
2475void linalg::YieldOp::print(OpAsmPrinter &p) {
2476 if (getNumOperands() > 0)
2477 p << ' ' << getOperands();
2478 p.printOptionalAttrDict((*this)->getAttrs());
2479 if (getNumOperands() > 0)
2480 p << " : " << getOperandTypes();
2481}
2482
2483ParseResult YieldOp::parse(OpAsmParser &parser, OperationState &result) {
2484 SmallVector<OpAsmParser::UnresolvedOperand, 2> opInfo;
2485 SmallVector<Type, 2> types;
2486 SMLoc loc = parser.getCurrentLocation();
2487 return failure(parser.parseOperandList(opInfo) ||
2488 parser.parseOptionalAttrDict(result.attributes) ||
2489 (!opInfo.empty() && parser.parseColonTypeList(types)) ||
2490 parser.resolveOperands(opInfo, types, loc, result.operands));
2491}
2492
2493// Check the operand number and types must match the element types of the
2494// LinalgOp interface's shaped operands.
2495static LogicalResult verifyYield(linalg::YieldOp op, LinalgOp linalgOp) {
2496 if (op.getNumOperands() != linalgOp.getNumDpsInits())
2497 return op.emitOpError("expected number of yield values (")
2498 << op.getNumOperands()
2499 << ") to match the number of inits / outs operands of the enclosing "
2500 << "LinalgOp (" << linalgOp.getNumDpsInits() << ")";
2501
2502 for (OpOperand &opOperand : op->getOpOperands()) {
2503 OpOperand *outputOperand =
2504 linalgOp.getDpsInitOperand(opOperand.getOperandNumber());
2505 Type elementType = outputOperand->get().getType();
2506 if (isa<MemRefType, RankedTensorType>(elementType))
2507 elementType = getElementTypeOrSelf(outputOperand->get().getType());
2508 if (opOperand.get().getType() != elementType)
2509 return op.emitOpError("type of yield operand ")
2510 << (opOperand.getOperandNumber() + 1) << " ("
2511 << opOperand.get().getType() << ") doesn't match "
2512 << "the element type of the enclosing linalg.generic op ("
2513 << elementType << ")";
2514 }
2515 return success();
2516}
2517
2518LogicalResult linalg::YieldOp::verify() {
2519 auto *parentOp = (*this)->getParentOp();
2520 if (parentOp->getNumRegions() != 1 || parentOp->getRegion(0).empty())
2521 return emitOpError("expected single non-empty parent region");
2522
2523 if (auto linalgOp = dyn_cast<LinalgOp>(parentOp))
2524 return verifyYield(*this, linalgOp);
2525
2526 return emitOpError("expected parent op with LinalgOp interface");
2527}
2528
2529//===----------------------------------------------------------------------===//
2530// IndexOp
2531//===----------------------------------------------------------------------===//
2532
2533LogicalResult IndexOp::verify() {
2534 auto linalgOp = dyn_cast<LinalgOp>((*this)->getParentOp());
2535 if (!linalgOp)
2536 return emitOpError("expected parent op with LinalgOp interface");
2537 if (linalgOp.getNumLoops() <= getDim())
2538 return emitOpError("expected dim (")
2539 << getDim() << ") to be lower than the number of loops ("
2540 << linalgOp.getNumLoops() << ") of the enclosing LinalgOp";
2541 return success();
2542}
2543
2544OpFoldResult IndexOp::fold(FoldAdaptor adaptor) {
2545 auto linalgOp = dyn_cast_or_null<LinalgOp>((*this)->getParentOp());
2546 // Bail out if `linalg.index` does not have a proper parent yet at this
2547 // point, e.g., when calling `createOrFold` during IR construction in
2548 // `genericOp::build`.
2549 if (!linalgOp)
2550 return OpFoldResult{};
2551
2552 // Index of unit dims is always 0.
2553 SmallVector<int64_t, 4> loopBounds = linalgOp.getStaticLoopRanges();
2554 uint64_t dim = getDim();
2555 assert(dim < loopBounds.size() && "Dim is out of bounds");
2556 if (loopBounds[dim] == 1)
2557 return IntegerAttr::get(IndexType::get(getContext()), 0);
2558
2559 return OpFoldResult{};
2560}
2561
2562/////// Operations corresponding to library calls defined with Tablegen ////////
2563
2564#include "mlir/Dialect/Linalg/IR/LinalgNamedStructuredOps.yamlgen.cpp.inc"
2565
2566#define GET_OP_CLASSES
2567#include "mlir/Dialect/Linalg/IR/LinalgOps.cpp.inc"
2568
2569#define GET_OP_CLASSES
2570#include "mlir/Dialect/Linalg/IR/LinalgStructuredOps.cpp.inc"
2571#define GET_OP_CLASSES
2572#include "mlir/Dialect/Linalg/IR/LinalgRelayoutOps.cpp.inc"
2573
2574AffineMap mlir::linalg::extractOrIdentityMap(std::optional<AffineMap> maybeMap,
2575 unsigned rank,
2576 MLIRContext *context) {
2577 if (maybeMap)
2578 return *maybeMap;
2579 if (rank == 0)
2580 return AffineMap::get(context);
2581 return AffineMap::getMultiDimIdentityMap(rank, context);
2582}
2583
2585mlir::linalg::makeAffineDimExprs(unsigned num, unsigned &startIdx,
2586 MLIRContext *context) {
2588 res.reserve(num);
2589 for (unsigned i = 0; i < num; ++i)
2590 res.push_back(getAffineDimExpr(startIdx++, context));
2591 return res;
2592}
2593
2596 auto rangeA = llvm::make_range(a.begin(), a.end());
2597 auto rangeB = llvm::make_range(b.begin(), b.end());
2598 auto concatRanges = llvm::concat<const AffineExpr>(rangeA, rangeB);
2599 return llvm::to_vector<4>(concatRanges);
2600}
2601
2602static LogicalResult appendMangledType(llvm::raw_string_ostream &ss, Type t) {
2603 if (auto memref = llvm::dyn_cast<MemRefType>(t)) {
2604 ss << "view";
2605 for (auto size : memref.getShape())
2606 if (size < 0)
2607 ss << "sx";
2608 else
2609 ss << size << "x";
2610 if (failed(appendMangledType(ss, memref.getElementType())))
2611 return failure();
2612 if (auto as = memref.getMemorySpace()) {
2613 if (auto attr = llvm::dyn_cast<IntegerAttr>(as))
2614 ss << "as" << attr.getInt();
2615 else
2616 return failure();
2617 }
2618 return success();
2619 }
2620 if (auto vec = llvm::dyn_cast<VectorType>(t)) {
2621 ss << "vector";
2622 llvm::interleave(
2623 vec.getShape(), [&](int64_t i) { ss << i; }, [&]() { ss << "x"; });
2624 if (failed(appendMangledType(ss, vec.getElementType())))
2625 return failure();
2626 return success();
2627 }
2629 ss << t;
2630 return success();
2631 }
2632 return failure();
2633}
2634
2636 assert(isa<LinalgOp>(op));
2637 std::string name(op->getName().getStringRef().str());
2638 std::string fun = "";
2639 for (NamedAttribute kv : op->getAttrs()) {
2640 if (UnaryFnAttr ufa = llvm::dyn_cast<UnaryFnAttr>(kv.getValue())) {
2641 fun = stringifyEnum(ufa.getValue()).str() + "_";
2642 } else if (BinaryFnAttr bfa = llvm::dyn_cast<BinaryFnAttr>(kv.getValue())) {
2643 fun = stringifyEnum(bfa.getValue()).str() + "_";
2644 }
2645 }
2646 name.reserve(128);
2647 llvm::replace(name, '.', '_');
2648 llvm::raw_string_ostream ss(name);
2649 ss << "_" << fun;
2650 for (Type t : op->getOperandTypes()) {
2651 if (failed(appendMangledType(ss, t)))
2652 return std::string();
2653 ss << "_";
2654 }
2655 name.pop_back();
2656 return name;
2657}
2658
2659//===----------------------------------------------------------------------===//
2660// Canonicalizers and Folders.
2661//===----------------------------------------------------------------------===//
2662
2663namespace {
2664struct EraseDeadLinalgOp : public OpInterfaceRewritePattern<LinalgOp> {
2666
2667 LogicalResult matchAndRewrite(LinalgOp op,
2668 PatternRewriter &rewriter) const override {
2669 for (OpOperand &opOperand : op->getOpOperands()) {
2670 // Linalg "inputs" may be either tensor or memref type.
2671 // tensor<0xelt_type> is a convention that may not always mean
2672 // "0 iterations". Only erase in cases we see memref<...x0x...>.
2673 auto mt = llvm::dyn_cast<MemRefType>(opOperand.get().getType());
2674 if (!mt)
2675 continue;
2676 if (llvm::is_contained(op.getShape(&opOperand), 0)) {
2677 rewriter.eraseOp(op);
2678 return success();
2679 }
2680 }
2681 return failure();
2682 }
2683};
2684
2685/// Fold LinalgOps with `tensor.cast` consumer if the `tensor.cast` has
2686/// result that is more static than the linalg op.
2687struct FoldTensorCastConsumerOp : public OpRewritePattern<tensor::CastOp> {
2688 using OpRewritePattern<tensor::CastOp>::OpRewritePattern;
2689
2690 LogicalResult matchAndRewrite(tensor::CastOp castOp,
2691 PatternRewriter &rewriter) const override {
2692 if (!tensor::canFoldIntoProducerOp(castOp))
2693 return failure();
2694
2695 auto linalgOp = castOp.getSource().getDefiningOp<LinalgOp>();
2696 if (!linalgOp)
2697 return failure();
2698
2699 // Cast can be in conditionally reachable region, if which case folding will
2700 // generate invalid code. Only conservatively fold ops in same block for
2701 // now.
2702 if (castOp->getBlock() != linalgOp->getBlock())
2703 return failure();
2704
2705 OpBuilder::InsertionGuard guard(rewriter);
2706 rewriter.setInsertionPoint(linalgOp);
2707
2708 Location loc = linalgOp.getLoc();
2709 OpResult resultValue = llvm::cast<OpResult>(castOp.getSource());
2710 unsigned resultNumber = resultValue.getResultNumber();
2711 auto resultType =
2712 llvm::cast<RankedTensorType>(castOp->getResult(0).getType());
2713 // Replace the `outs` for the result with a `tensor.cast`. This cast is now
2714 // going from a more dynamic shape to a less dynamic shape. If the producer
2715 // for this cast, i.e. producer of the out operand, is also an operation
2716 // that folds with tensor.cast consumer (like this pattern), the cast will
2717 // continue to propagate as far up the stack as it can go.
2718 OpOperand *outOperand = linalgOp.getDpsInitOperand(resultNumber);
2719 Value newOperand =
2720 tensor::CastOp::create(rewriter, loc, resultType, outOperand->get());
2721 SmallVector<Value> newOperands = linalgOp.getDpsInputs();
2722 SmallVector<Value> outputOperands(linalgOp.getDpsInits().begin(),
2723 linalgOp.getDpsInits().end());
2724 outputOperands[resultNumber] = newOperand;
2725 newOperands.append(outputOperands.begin(), outputOperands.end());
2726
2727 SmallVector<Type> resultTypes(linalgOp->result_type_begin(),
2728 linalgOp->result_type_end());
2729 resultTypes[resultNumber] = resultType;
2730 Operation *newOp = clone(rewriter, linalgOp, resultTypes, newOperands);
2731
2732 // Create a tensor.cast operation back to the original type.
2733 Value castBack = tensor::CastOp::create(
2734 rewriter, loc, resultValue.getType(), newOp->getResult(resultNumber));
2735
2736 SmallVector<Value> results(newOp->result_begin(), newOp->result_end());
2737 results[resultNumber] = castBack;
2738 rewriter.replaceOp(linalgOp, results);
2739 rewriter.replaceOp(castOp, newOp->getResult(resultNumber));
2740 return success();
2741 }
2742};
2743
2744/// For each of the operand in `operands` this function maps the static sizes of
2745/// dimensions to their affine dim expressions.
2746static void populateMap(LinalgOp linalgOp, MutableArrayRef<OpOperand> operands,
2747 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize) {
2748 for (OpOperand &opOperand : operands) {
2749 if (linalgOp.isScalar(&opOperand))
2750 continue;
2751 Value src = opOperand.get();
2752 auto sourceType = llvm::cast<RankedTensorType>(src.getType());
2753 auto sourceMap = linalgOp.getMatchingIndexingMap(&opOperand);
2754
2755 // Get the `sourceShape` of the `sourceType`. If the operand is a result of
2756 // `tensor.cast` operation and source of the cast operation has a static
2757 // shape, then assign it to the `sourceShape`.
2758 auto *parentOp = src.getDefiningOp();
2759 ArrayRef<int64_t> sourceShape = sourceType.getShape();
2760 if (parentOp) {
2761 if (auto castOp = dyn_cast<tensor::CastOp>(parentOp)) {
2762 Value castSource = castOp.getSource();
2763 auto castSourceType =
2764 llvm::dyn_cast<RankedTensorType>(castSource.getType());
2765 if (castSourceType && castSourceType.hasStaticShape())
2766 sourceShape = castSourceType.getShape();
2767 }
2768 }
2769
2770 // If the source shape's dimension has a static shape, map the affine dim
2771 // expression to the known static size.
2772 for (unsigned i = 0; i < sourceShape.size(); i++) {
2773 if (sourceType.isDynamicDim(i))
2774 continue;
2775 if (auto affineDimExpr = dyn_cast<AffineDimExpr>(sourceMap.getResult(i)))
2776 affineExprToSize.try_emplace(affineDimExpr, sourceShape[i]);
2777 }
2778 }
2779}
2780
2781/// Creates new operand w.r.t 'opOperand' of `linalgOp` with static sizes
2782/// mapped in `affineExprToSize`. New operands are created in `newOperands` and
2783/// their result types is stored in `resultTypes`. If `opOperand` requires no
2784/// change then `changeNeeded` is false and same operand is added in the
2785/// `newOperands` list.
2786static void createNewOperandWithStaticSizes(
2787 Location loc, PatternRewriter &rewriter, OpOperand *opOperand,
2788 llvm::DenseMap<AffineExpr, int64_t> &affineExprToSize, LinalgOp linalgOp,
2789 SmallVector<Value> &newOperands, SmallVector<Type> &resultTypes,
2790 bool &changeNeeded) {
2791 Value src = opOperand->get();
2792 newOperands.push_back(src);
2793 if (linalgOp.isScalar(opOperand))
2794 return;
2795 auto sourceType = llvm::cast<RankedTensorType>(src.getType());
2796 Type resultType = sourceType;
2797 if (sourceType.hasStaticShape() && linalgOp.isDpsInit(opOperand)) {
2798 resultTypes.push_back(resultType);
2799 return;
2800 }
2801 ArrayRef<int64_t> sourceShape = sourceType.getShape();
2802 AffineMap sourceMap = linalgOp.getMatchingIndexingMap(opOperand);
2803 SmallVector<int64_t> newShape;
2804 // If operand is updated with new shape, `newOperandNeeded` will be
2805 // true.
2806 bool newOperandNeeded = false;
2807 for (unsigned i = 0; i < sourceShape.size(); i++) {
2808 int64_t dimShape = sourceShape[i];
2809 AffineExpr dimExpr = sourceMap.getResult(i);
2810 if (!affineExprToSize.contains(dimExpr) || !sourceType.isDynamicDim(i)) {
2811 newShape.push_back(dimShape);
2812 continue;
2813 }
2814 // Dimension has a dynamic shape and corresponding affine dim
2815 // expression is present in the map. So assign the size for the
2816 // given affine dim expression to the dimension.
2817 newShape.push_back(affineExprToSize[dimExpr]);
2818 newOperandNeeded = true;
2819 }
2820 resultType = RankedTensorType::get(newShape, sourceType.getElementType(),
2821 sourceType.getEncoding());
2822 if (newOperandNeeded) {
2823 changeNeeded = true;
2824 // Get the new operand value given its size and element type by
2825 // casting it.
2826 Value newOperand = tensor::CastOp::create(rewriter, loc, resultType, src);
2827 unsigned index = opOperand->getOperandNumber();
2828 newOperands[index] = newOperand;
2829 }
2830 if (linalgOp.isDpsInit(opOperand))
2831 resultTypes.push_back(resultType);
2832}
2833
2834/// Static shapes for the operands can be inferred if any one of the operands
2835/// have a static shape. This can be done by referring to the affine dim
2836/// expressions for the operand.
2837struct InferStaticShapeOfOperands : public OpInterfaceRewritePattern<LinalgOp> {
2838 using OpInterfaceRewritePattern<LinalgOp>::OpInterfaceRewritePattern;
2839
2840 LogicalResult matchAndRewrite(LinalgOp linalgOp,
2841 PatternRewriter &rewriter) const override {
2842 if (!linalgOp.hasPureTensorSemantics())
2843 return failure();
2844
2845 // Maps must be projected permutations.
2846 if (llvm::any_of(linalgOp.getIndexingMapsArray(), [](AffineMap map) {
2847 return !map.isProjectedPermutation();
2848 }))
2849 return failure();
2850
2851 // Maps affine dim expressions to the static size of that dimension.
2852 llvm::DenseMap<AffineExpr, int64_t> affineExprToSize;
2853 Location loc = linalgOp.getLoc();
2854
2855 // For each of the affine dim expression, check if the size is known. If
2856 // known add that in the map.
2857 populateMap(linalgOp, linalgOp->getOpOperands(), affineExprToSize);
2858
2859 SmallVector<Value> newOperands;
2860 SmallVector<Type> resultTypes;
2861
2862 // `changeNeeded` is `false` if the operands of `linalgOp` require no
2863 // change in their types.
2864 bool changeNeeded = false;
2865 newOperands.reserve(linalgOp->getNumOperands());
2866 resultTypes.reserve(linalgOp.getNumDpsInits());
2867
2868 // Iterate over all the operands and update the static sizes.
2869 for (OpOperand &opOperand : linalgOp->getOpOperands()) {
2870 createNewOperandWithStaticSizes(loc, rewriter, &opOperand,
2871 affineExprToSize, linalgOp, newOperands,
2872 resultTypes, changeNeeded);
2873 }
2874
2875 // If the generic op has all the required static information, no
2876 // canonicalization needed.
2877 if (!changeNeeded)
2878 return failure();
2879
2880 // Clone op.
2881 Operation *newOp = clone(rewriter, linalgOp, resultTypes, newOperands);
2882 SmallVector<Value> replacements;
2883 replacements.reserve(newOp->getNumResults());
2884 for (auto it : llvm::zip(linalgOp->getResults(), newOp->getResults())) {
2885 Value newResult = std::get<1>(it);
2886 Value oldResult = std::get<0>(it);
2887 Type newType = newResult.getType();
2888 Type oldType = oldResult.getType();
2889 replacements.push_back(
2890 (newType != oldType)
2891 ? tensor::CastOp::create(rewriter, loc, oldType, newResult)
2892 : newResult);
2893 }
2894 rewriter.replaceOp(linalgOp, replacements);
2895 return success();
2896 }
2897};
2898
2899} // namespace
2900
2901// All named ops canonicalizers and folders are auto-generated in the
2902// .cpp.inc.
2903
2904//===----------------------------------------------------------------------===//
2905// SoftmaxOp
2906//===----------------------------------------------------------------------===//
2907
2908LogicalResult SoftmaxOp::verify() {
2909 ShapedType inputType = getInputOperandType();
2910 ShapedType outputType = getOutputOperandType();
2911
2912 ArrayRef<int64_t> inputShape = inputType.getShape();
2913 ArrayRef<int64_t> outputShape = outputType.getShape();
2914 if (failed(verifyCompatibleShape(inputShape, outputShape)))
2915 return emitOpError("incompatible output shape");
2916
2917 int64_t inputRank = getInputOperandRank();
2918 int64_t dimension = getDimension();
2919 if ((dimension < 0) || (dimension >= inputRank))
2920 return emitOpError("incorrect dimension specified");
2921
2922 return success();
2923}
2924
2925SmallVector<Range> SoftmaxOp::getIterationDomain(OpBuilder &builder) {
2926 int64_t operandRank = getInputOperandRank();
2927 SmallVector<Range> loopBounds(operandRank);
2928 Location loc = getLoc();
2929 Value zero = arith::ConstantIndexOp::create(builder, loc, 0);
2930 Value one = arith::ConstantIndexOp::create(builder, loc, 1);
2931 Value source = getInput();
2932 for (auto dim : llvm::seq<int64_t>(0, operandRank)) {
2933 loopBounds[dim].offset = zero;
2934 loopBounds[dim].size = getDimValue(builder, loc, source, dim);
2935 loopBounds[dim].stride = one;
2936 }
2937 return loopBounds;
2938}
2939
2940SmallVector<utils::IteratorType> SoftmaxOp::getLoopIteratorTypes() {
2941 SmallVector<utils::IteratorType> iteratorTypes(getInputOperandRank(),
2942 utils::IteratorType::parallel);
2943 iteratorTypes[getDimension()] = utils::IteratorType::reduction;
2944 return iteratorTypes;
2945}
2946
2947/// The inner tile alignment hint is only used by `linalg.pack` and
2948/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
2949/// overload.
2950FailureOr<TilingResult> SoftmaxOp::getTiledImplementation(
2951 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
2952 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
2953 return getTiledImplementation(builder, offsets, sizes);
2954}
2955
2956FailureOr<TilingResult>
2957SoftmaxOp::getTiledImplementation(OpBuilder &builder,
2958 ArrayRef<OpFoldResult> offsets,
2959 ArrayRef<OpFoldResult> sizes) {
2960 int64_t rank = getInputOperandRank();
2961 auto oneAttr = builder.getI64IntegerAttr(1);
2962 SmallVector<OpFoldResult> strides(rank, oneAttr);
2963 SmallVector<Value> tiledOperands;
2964 Operation *inputSlice =
2965 getSlice(builder, getLoc(), getInput(), offsets, sizes, strides);
2966 if (!inputSlice) {
2967 return emitOpError("failed to compute input slice");
2968 }
2969 tiledOperands.emplace_back(inputSlice->getResult(0));
2970 Operation *outputSlice =
2971 getSlice(builder, getLoc(), getOutput(), offsets, sizes, strides);
2972 if (!outputSlice) {
2973 return emitOpError("failed to compute output slice");
2974 }
2975 tiledOperands.emplace_back(outputSlice->getResult(0));
2976
2977 SmallVector<Type, 4> resultTypes;
2978 if (hasPureTensorSemantics())
2979 resultTypes.push_back(tiledOperands[1].getType());
2980 Operation *tiledOp =
2981 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
2982
2983 return TilingResult{
2984 {tiledOp},
2985 SmallVector<Value>(tiledOp->getResults()),
2986 llvm::to_vector(ArrayRef<Operation *>{inputSlice, outputSlice})};
2987}
2988
2989LogicalResult SoftmaxOp::getResultTilePosition(
2990 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
2991 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
2992 SmallVector<OpFoldResult> &resultSizes) {
2993 if (resultNumber == 0) {
2994 resultOffsets.assign(offsets.begin(), offsets.end());
2995 resultSizes.assign(sizes.begin(), sizes.end());
2996 return success();
2997 }
2998 return failure();
2999}
3000
3001// cast(dynamic) -> static.
3002LogicalResult SoftmaxOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
3003 return memref::foldMemRefCast(*this);
3004}
3005
3006LogicalResult
3007SoftmaxOp::reifyResultShapes(OpBuilder &b,
3008 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
3009 SmallVector<OpFoldResult> shapes;
3010 Location loc = getOperation()->getLoc();
3011 IRRewriter rewriter(b);
3012 auto inputShapedType = llvm::cast<ShapedType>(getInputOperandType());
3013 auto outputShapedType = llvm::cast<ShapedType>(getOutputOperandType());
3014 for (int64_t dim : llvm::seq<int64_t>(0, getOutputOperandRank())) {
3015 if (!outputShapedType.isDynamicDim(dim)) {
3016 // Static dim: Return IntegerAttr.
3017 shapes.push_back(b.getIndexAttr(inputShapedType.getDimSize(dim)));
3018 } else {
3019 // Dynamic dim: Return Value.
3020 OpFoldResult ofr = createOrFoldDimOp(b, loc, getInput(), dim);
3021 shapes.push_back(getValueOrCreateConstantIndexOp(b, loc, ofr));
3022 }
3023 }
3024 reifiedReturnShapes.emplace_back(std::move(shapes));
3025 return success();
3026}
3027
3028void SoftmaxOp::getEffects(
3029 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
3030 &effects) {
3031 for (auto [index, operand] : llvm::enumerate(getDpsInputs())) {
3032 if (!llvm::isa<MemRefType>(operand.getType()))
3033 continue;
3034 effects.emplace_back(MemoryEffects::Read::get(),
3035 &getOperation()->getOpOperand(index), /*stage=*/0,
3036 /*effectOnFullRegion=*/true,
3038 }
3039
3040 for (OpOperand &operand : getDpsInitsMutable()) {
3041 if (!llvm::isa<MemRefType>(operand.get().getType()))
3042 continue;
3043 effects.emplace_back(MemoryEffects::Read::get(), &operand, /*stage=*/0,
3044 /*effectOnFullRegion=*/true,
3046 effects.emplace_back(MemoryEffects::Write::get(), &operand, /*stage=*/0,
3047 /*effectOnFullRegion=*/true,
3049 }
3050}
3051
3052// Helper functions for softmax decomposition.
3053// @{
3054
3055// Helper function to produce the iterator types (reduction or parallel) and
3056// affine maps for the iterators used in the decomposition of softmax.
3057// This method creates:
3058// If allParallel == true:
3059// - iterator type: {parallel, ..., parallel}
3060// - affine maps:
3061// -- identity with inputRank dimensions.
3062// -- (d0, ..., dN) -> (d0, ..., d_dim-1, d_dim+1, ..., dN),
3063// where N == inputRank.
3064//
3065// If allParallel == false:
3066// - iterator type at dim(i) == parallel for i != \p dim and
3067// dim(dim) == reduction.
3068// - affine map:
3069// -- identity with inputRank dimensions.
3070// -- (d0, ..., dN) -> (d0, ..., d_dim-1, d_dim+1, ..., dN),
3071// where N == inputRank.
3072static std::tuple<SmallVector<utils::IteratorType>, SmallVector<AffineMap>>
3074 int64_t dim, bool allParallel = false) {
3075 SmallVector<utils::IteratorType> iteratorTypes(inputRank,
3076 utils::IteratorType::parallel);
3077 if (!allParallel)
3078 iteratorTypes[dim] = utils::IteratorType::reduction;
3079 MLIRContext *ctxt = builder.getContext();
3080 auto identityMap = AffineMap::getMultiDimIdentityMap(inputRank, ctxt);
3081 SmallVector<AffineExpr, 2> affineExprs;
3082 for (int i = 0; i < inputRank; i++) {
3083 if (i != dim)
3084 affineExprs.push_back(mlir::getAffineDimExpr(i, ctxt));
3085 }
3086 auto reductionMap =
3087 AffineMap::get(inputRank, /*symbols=*/0, affineExprs, ctxt);
3088 SmallVector<AffineMap> indexingMaps{identityMap, reductionMap};
3089 return std::make_tuple(iteratorTypes, indexingMaps);
3090}
3091
3092// Helper function to produce a linalg.generic that computes a reduction on
3093// dimension \p dim with the operation type \p T.
3094template <typename T>
3095static Value reduce(OpBuilder &builder, Location loc, Value input, Value output,
3096 int64_t dim) {
3097 auto inputType = cast<ShapedType>(input.getType());
3098 ArrayRef<int64_t> inputShape = inputType.getShape();
3099 int64_t inputRank = inputShape.size();
3100 auto [iteratorTypes, indexingMaps] =
3101 computeIteratorTypesAndIndexingMaps(builder, inputRank, dim);
3102 assert(indexingMaps.size() == 2 &&
3103 "We should have two maps: 1 for the input, 1 for the output");
3104 assert(indexingMaps[0].isIdentity() && "input map should be identity");
3105
3106 auto genericOp = linalg::GenericOp::create(
3107 builder, loc, output.getType(), input, output, indexingMaps,
3108 iteratorTypes, [&](OpBuilder &b, Location loc, ValueRange args) {
3109 Value result = T::create(b, loc, args[0], args[1]);
3110 linalg::YieldOp::create(b, loc, result);
3111 });
3112 return genericOp.getResult(0);
3113}
3114
3115/// Produce a linalg generic that computes the second step of the softmax
3116/// decomposition: res = exp(input - max), where \p max is the max of \p input
3117/// on dimension \p dim.
3118static Value buildSubAndExpOp(OpBuilder &builder, Location loc, Value input,
3119 Value max, Value output, int64_t dim) {
3120 auto inputType = cast<ShapedType>(input.getType());
3121 ArrayRef<int64_t> inputShape = inputType.getShape();
3122 int64_t inputRank = inputShape.size();
3123 auto [iteratorTypes, indexingMaps] = computeIteratorTypesAndIndexingMaps(
3124 builder, inputRank, dim, /*allParallel=*/true);
3125 assert(indexingMaps.size() == 2 && "We should have one map for each input");
3126 assert(indexingMaps[0].isIdentity() && "input map should be identity");
3127 // Add the affine map for the output argument.
3128 indexingMaps.push_back(indexingMaps[0]);
3129 auto genericOp = linalg::GenericOp::create(
3130 builder, loc, input.getType(), ValueRange{input, max}, output,
3131 indexingMaps, iteratorTypes,
3132 [&](OpBuilder &b, Location loc, ValueRange args) {
3133 Value diff = arith::SubFOp::create(b, loc, args[0], args[1]);
3134 Value result = math::ExpOp::create(b, loc, diff);
3135 linalg::YieldOp::create(b, loc, result);
3136 });
3137 return genericOp.getResult(0);
3138}
3139
3140/// Produce a linalg generic that computes the final step of the softmax
3141/// decomposition.
3142/// \returns linalg.generic ins(\p numerator, \p denominator) outs(\p output) {
3143/// yield n / d
3144/// }
3145static Value buildDivOp(OpBuilder &builder, Location loc, Value numerator,
3146 Value denominator, Value output, int64_t dim) {
3147 auto inputType = cast<ShapedType>(numerator.getType());
3148 ArrayRef<int64_t> inputShape = inputType.getShape();
3149 int64_t inputRank = inputShape.size();
3150 auto [iteratorTypes, indexingMaps] = computeIteratorTypesAndIndexingMaps(
3151 builder, inputRank, dim, /*allParallel=*/true);
3152 assert(indexingMaps.size() == 2 &&
3153 "We should have one map for each input (2)");
3154 assert(indexingMaps[0].isIdentity() && "Numerator map should be identity");
3155 // Add the affine map for the output tensor.
3156 indexingMaps.push_back(indexingMaps[0]);
3157 auto genericOp = linalg::GenericOp::create(
3158 builder, loc, numerator.getType(), ValueRange{numerator, denominator},
3159 output, indexingMaps, iteratorTypes,
3160 [&](OpBuilder &b, Location loc, ValueRange args) {
3161 Value result = arith::DivFOp::create(b, loc, args[0], args[1]);
3162 linalg::YieldOp::create(b, loc, result);
3163 });
3164 return genericOp.getResult(0);
3165}
3166// @} End helper functions for softmax decomposition.
3167
3168/// Given an N-dimensional tensor x, this method converts
3169/// softmax(x) to the following sequence of operations:
3170///
3171/// 1. Compute the max of x along dimension d. This results
3172/// in a N-1 dimensional tensor m.
3173/// m = max(x, dim = d)
3174///
3175/// 2. Subtract a broadcasted m from x and exponentiate. This results in
3176/// a N dimensional tensor z.
3177/// z = exp(x - m)
3178///
3179/// 3. Compute the sum of z along dimension d. This results in
3180/// a N-1 dimensional tensor l.
3181/// l = sum(z, dim = d)
3182///
3183/// 4. Divide z and l. This gives the N-dimensional softmax.
3184/// softmax = z / l
3185///
3186FailureOr<SmallVector<Value>> SoftmaxOp::decomposeOperation(OpBuilder &b) {
3187 OpBuilder::InsertionGuard guard(b);
3188 b.setInsertionPoint(*this);
3189 Location loc = getLoc();
3190 Value input = getInput();
3191 ShapedType inputType = getInputOperandType();
3192 Type elementType = inputType.getElementType();
3193 int64_t reductionDim = getDimension();
3194 SmallVector<OpFoldResult> dims = tensor::getMixedSizes(b, loc, input);
3195 Value output = getOutput();
3196 dims.erase(dims.begin() + reductionDim);
3197 // Step 1: Compute max along dim.
3198 Value outputReduce = tensor::EmptyOp::create(b, loc, dims, elementType);
3199 Value neutralForMaxF = arith::getIdentityValue(arith::AtomicRMWKind::maxnumf,
3200 elementType, b, loc,
3201 /*useOnlyFiniteValue=*/true);
3202 Value neutralForMaxFInit =
3203 linalg::FillOp::create(b, loc, Value{neutralForMaxF}, outputReduce)
3204 .result();
3205 Value max =
3206 reduce<arith::MaxNumFOp>(b, loc, input, neutralForMaxFInit, reductionDim);
3207
3208 // Step 2: Subtract max from input and exponentiate.
3209 Value numerator = buildSubAndExpOp(b, loc, input, max, output, reductionDim);
3210
3211 // Step 3: Compute sum along dim.
3212 Value zero = arith::getIdentityValue(arith::AtomicRMWKind::addf, elementType,
3213 b, loc, /*useOnlyFiniteValue=*/true);
3214 Value zeroInit =
3215 linalg::FillOp::create(b, loc, Value{zero}, outputReduce).result();
3216 Value denominator =
3217 reduce<arith::AddFOp>(b, loc, numerator, zeroInit, reductionDim);
3218
3219 // Step 4: Compute softmax.
3220 Value result =
3221 buildDivOp(b, loc, numerator, denominator, output, reductionDim);
3222 return SmallVector<Value>{result};
3223}
3224
3225//===----------------------------------------------------------------------===//
3226// WinogradFilterTransformOp
3227//===----------------------------------------------------------------------===//
3228
3229LogicalResult WinogradFilterTransformOp::verify() {
3230 auto filterType = cast<ShapedType>(getFilter().getType());
3231 ArrayRef<int64_t> filterShape = filterType.getShape();
3232 int64_t filterH = filterShape[getFilterHDim()];
3233 int64_t filterW = filterShape[getFilterWDim()];
3234 WinogradConv2DFmr fmr = getFmr();
3235 int64_t m, r;
3236 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3237
3238 if (filterH != r && filterH != 1)
3239 return emitOpError("expect filter height either equals to r or 1");
3240 if (filterW != r && filterW != 1)
3241 return emitOpError("expect filter width either equals to r or 1");
3242 if (filterH == 1 && filterW == 1)
3243 return emitOpError("expect either filter height or width equals to r");
3244
3245 SmallVector<int64_t> expectedOutputShape;
3246 expectedOutputShape.push_back(filterH == r ? m + r - 1 : 1);
3247 expectedOutputShape.push_back(filterW == r ? m + r - 1 : 1);
3248 expectedOutputShape.push_back(filterShape[getFilterCDim()]);
3249 expectedOutputShape.push_back(filterShape[getFilterFDim()]);
3250
3251 auto outputType = cast<ShapedType>(getOutput().getType());
3252 ArrayRef<int64_t> outputShape = outputType.getShape();
3253 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3254 return emitOpError("the output shape is not expected");
3255 }
3256 return success();
3257}
3258
3259SmallVector<Range>
3260WinogradFilterTransformOp::getIterationDomain(OpBuilder &builder) {
3261 Location loc = getLoc();
3262 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3263 IntegerAttr oneAttr = builder.getIndexAttr(1);
3264 Value filter = getFilter();
3265 int64_t filterRank = getFilterOperandRank();
3266 SmallVector<Range> loopBounds(filterRank);
3267 for (unsigned dim = 0; dim < filterRank; ++dim) {
3268 loopBounds[dim].offset = zeroAttr;
3269 loopBounds[dim].size = getDimValue(builder, loc, filter, dim);
3270 loopBounds[dim].stride = oneAttr;
3271 }
3272 return loopBounds;
3273}
3274
3275SmallVector<utils::IteratorType>
3276WinogradFilterTransformOp::getLoopIteratorTypes() {
3277 int64_t filterRank = getFilterOperandRank();
3278 SmallVector<utils::IteratorType> iteratorTypes(filterRank,
3279 utils::IteratorType::parallel);
3280 return iteratorTypes;
3281}
3282
3283LogicalResult WinogradFilterTransformOp::getResultTilePosition(
3284 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3285 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3286 SmallVector<OpFoldResult> &resultSizes) {
3287 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3288 ShapedType filterType = getFilterOperandType();
3289 ArrayRef<int64_t> filterShape = filterType.getShape();
3290 int64_t filterH = filterShape[getFilterHDim()];
3291 int64_t filterW = filterShape[getFilterWDim()];
3292 WinogradConv2DFmr fmr = getFmr();
3293 int64_t m, r;
3294 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3295 int64_t alpha = m + r - 1;
3296 int64_t alphaH = filterH != 1 ? alpha : 1;
3297 int64_t alphaW = filterW != 1 ? alpha : 1;
3298 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3299 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3300
3301 resultOffsets.append(
3302 {zeroAttr, zeroAttr, offsets[getFilterCDim()], offsets[getFilterFDim()]});
3303 resultSizes.append(
3304 {alphaHAttr, alphaWAttr, sizes[getFilterCDim()], sizes[getFilterFDim()]});
3305
3306 return success();
3307}
3308
3309/// The inner tile alignment hint is only used by `linalg.pack` and
3310/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3311/// overload.
3312FailureOr<TilingResult> WinogradFilterTransformOp::getTiledImplementation(
3313 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3314 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3315 return getTiledImplementation(builder, offsets, sizes);
3316}
3317
3318/// Implement tiling for winograd_filter_transform
3319/// The input of winograd_filter_transform is (F, KH, KW, C).
3320/// The output of winograd_filter_transform is (alphaH, alphaW, C, F)
3321/// Users can specify the tile sizes of F and C.
3322/// `offsets` are the values for the offsets of F, KH, KW, C for one tile.
3323/// `sizes` are the values for the sizes of F, KH, KW, C for one tile.
3324FailureOr<TilingResult> WinogradFilterTransformOp::getTiledImplementation(
3325 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3326 ArrayRef<OpFoldResult> sizes) {
3327 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3328 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3329 ShapedType filterType = getFilterOperandType();
3330 ArrayRef<int64_t> filterShape = filterType.getShape();
3331 int64_t filterH = filterShape[getFilterHDim()];
3332 int64_t filterW = filterShape[getFilterWDim()];
3333 IntegerAttr filterHAttr = builder.getI64IntegerAttr(filterH);
3334 IntegerAttr filterWAttr = builder.getI64IntegerAttr(filterW);
3335 SmallVector<Value> tiledOperands;
3336 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3337
3338 sliceOffsets.append(
3339 {offsets[getFilterFDim()], zeroAttr, zeroAttr, offsets[getFilterCDim()]});
3340 sliceSizes.append({sizes[getFilterFDim()], filterHAttr, filterWAttr,
3341 sizes[getFilterCDim()]});
3342 int64_t filterRank = getFilterOperandRank();
3343 SmallVector<OpFoldResult> filterStrides(filterRank, oneAttr);
3344 Location loc = getLoc();
3345 auto filterSlice = tensor::ExtractSliceOp::create(
3346 builder, loc, getFilter(), sliceOffsets, sliceSizes, filterStrides);
3347 tiledOperands.emplace_back(filterSlice);
3348
3349 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3350 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3351 resultSizes)))
3352 return failure();
3353
3354 int64_t outputRank = getOutputOperandRank();
3355 SmallVector<OpFoldResult> outputStrides(outputRank, oneAttr);
3356 auto outputSlice = tensor::ExtractSliceOp::create(
3357 builder, loc, getOutput(), resultOffsets, resultSizes, outputStrides);
3358 tiledOperands.emplace_back(outputSlice);
3359
3360 SmallVector<Type> resultTypes;
3361 resultTypes.push_back(tiledOperands[1].getType());
3362 Operation *tiledOp =
3363 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3364
3365 return TilingResult{
3366 {tiledOp},
3367 SmallVector<Value>(tiledOp->getResults()),
3368 llvm::to_vector(ArrayRef<Operation *>{filterSlice, outputSlice})};
3369}
3370
3371//===----------------------------------------------------------------------===//
3372// WinogradInputTransformOp
3373//===----------------------------------------------------------------------===//
3374
3375LogicalResult WinogradInputTransformOp::verify() {
3376 auto inputType = cast<ShapedType>(getInput().getType());
3377 ArrayRef<int64_t> inputShape = inputType.getShape();
3378 int64_t inputH = inputShape[getInputHDim()];
3379 int64_t inputW = inputShape[getInputWDim()];
3380 WinogradConv2DFmr fmr = getFmr();
3381 int64_t m, r;
3382 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3383 int64_t tileSize = m + r - 1;
3384
3385 auto outputType = cast<ShapedType>(getOutput().getType());
3386 ArrayRef<int64_t> outputShape = outputType.getShape();
3387 bool leftTransform = outputShape[getOutputAlphaHDim()] != 1;
3388 bool rightTransform = outputShape[getOutputAlphaWDim()] != 1;
3389
3390 SmallVector<int64_t> expectedOutputShape(6, inputH);
3391 if (ShapedType::isDynamic(inputH)) {
3392 expectedOutputShape[getOutputAlphaHDim()] = tileSize;
3393 expectedOutputShape[getOutputTileHDim()] = ShapedType::kDynamic;
3394 } else {
3395 expectedOutputShape[getOutputAlphaHDim()] = leftTransform ? tileSize : 1;
3396 expectedOutputShape[getOutputTileHDim()] =
3397 leftTransform ? (inputH - (r - 1)) / m : inputH;
3398 }
3399 if (ShapedType::isDynamic(inputW)) {
3400 expectedOutputShape[getOutputAlphaWDim()] = tileSize;
3401 expectedOutputShape[getOutputTileWDim()] = ShapedType::kDynamic;
3402 } else {
3403 expectedOutputShape[getOutputAlphaWDim()] = rightTransform ? tileSize : 1;
3404 expectedOutputShape[getOutputTileWDim()] =
3405 rightTransform ? (inputW - (r - 1)) / m : inputW;
3406 }
3407 expectedOutputShape[getOutputNDim()] = inputShape[getInputNDim()];
3408 expectedOutputShape[getOutputCDim()] = inputShape[getInputCDim()];
3409
3410 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3411 return emitOpError("the output shape is not expected");
3412 }
3413 return success();
3414}
3415
3416SmallVector<Range>
3417WinogradInputTransformOp::getIterationDomain(OpBuilder &builder) {
3418 Location loc = getLoc();
3419 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3420 IntegerAttr oneAttr = builder.getIndexAttr(1);
3421 Value output = getOutput();
3422 int64_t outputRank = getOutputOperandRank();
3423 SmallVector<Range> loopBounds(outputRank);
3424 for (unsigned dim = 0; dim < outputRank; ++dim) {
3425 loopBounds[dim].offset = zeroAttr;
3426 // alphaH, alphaW, tileH, tileW, N, C
3427 loopBounds[dim].size = getDimValue(builder, loc, output, dim);
3428 loopBounds[dim].stride = oneAttr;
3429 }
3430 return loopBounds;
3431}
3432
3433SmallVector<utils::IteratorType>
3434WinogradInputTransformOp::getLoopIteratorTypes() {
3435 int64_t outputRank = getOutputOperandRank();
3436 SmallVector<utils::IteratorType> iteratorTypes(outputRank,
3437 utils::IteratorType::parallel);
3438 return iteratorTypes;
3439}
3440
3441LogicalResult WinogradInputTransformOp::getResultTilePosition(
3442 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3443 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3444 SmallVector<OpFoldResult> &resultSizes) {
3445 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3446 ShapedType outputType = getOutputOperandType();
3447 ArrayRef<int64_t> outputShape = outputType.getShape();
3448 int64_t outputAlphaH = outputShape[getOutputAlphaHDim()];
3449 int64_t outputAlphaW = outputShape[getOutputAlphaWDim()];
3450
3451 WinogradConv2DFmr fmr = getFmr();
3452 int64_t m, r;
3453 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3454 int64_t alpha = m + r - 1;
3455 int64_t alphaH = outputAlphaH != 1 ? alpha : 1;
3456 int64_t alphaW = outputAlphaW != 1 ? alpha : 1;
3457
3458 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3459 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3460
3461 resultOffsets.append({zeroAttr, zeroAttr, offsets[getOutputTileHDim()],
3462 offsets[getOutputTileWDim()], offsets[getOutputNDim()],
3463 offsets[getOutputCDim()]});
3464 resultSizes.append({alphaHAttr, alphaWAttr, sizes[getOutputTileHDim()],
3465 sizes[getOutputTileWDim()], sizes[getOutputNDim()],
3466 sizes[getOutputCDim()]});
3467
3468 return success();
3469}
3470
3471/// The inner tile alignment hint is only used by `linalg.pack` and
3472/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3473/// overload.
3474FailureOr<TilingResult> WinogradInputTransformOp::getTiledImplementation(
3475 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3476 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3477 return getTiledImplementation(builder, offsets, sizes);
3478}
3479
3480/// Implement tiling for winograd_input_transform
3481/// The input of winograd_input_transform is (N, H, W, C).
3482/// The output of winograd_input_transform is (alphaH, alphaW, tileH, tileW, N,
3483/// C) Users can specify the tile sizes of tileH, tileW, N, and C. `offsets` are
3484/// the values for the offsets of tileH, tileW, N, C for one tile. `sizes` are
3485/// the values for the sizes of tileH, tileW, N, C for one tile.
3486FailureOr<TilingResult>
3487WinogradInputTransformOp::getTiledImplementation(OpBuilder &builder,
3488 ArrayRef<OpFoldResult> offsets,
3489 ArrayRef<OpFoldResult> sizes) {
3490 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3491 WinogradConv2DFmr fmr = getFmr();
3492 int64_t m, r;
3493 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3494
3495 ShapedType outputType = getOutputOperandType();
3496 ArrayRef<int64_t> outputShape = outputType.getShape();
3497 int64_t alphaH = outputShape[getOutputAlphaHDim()];
3498 int64_t alphaW = outputShape[getOutputAlphaWDim()];
3499
3500 Location loc = getLoc();
3501 MLIRContext *context = builder.getContext();
3502 auto identityAffineMap =
3503 AffineMap::get(1, 0, {builder.getAffineDimExpr(0)}, context);
3504 auto offsetAffineMap =
3505 AffineMap::get(1, 0, {builder.getAffineDimExpr(0) * m}, context);
3506 Value mappedOffsetH = affine::makeComposedAffineApply(
3507 builder, loc, (alphaH != 1 ? offsetAffineMap : identityAffineMap),
3508 offsets[getOutputTileHDim()]);
3509 Value mappedOffsetW = affine::makeComposedAffineApply(
3510 builder, loc, (alphaW != 1 ? offsetAffineMap : identityAffineMap),
3511 offsets[getOutputTileWDim()]);
3512 auto sizeAffineMap = AffineMap::get(
3513 1, 0, {builder.getAffineDimExpr(0) * m + (r - 1)}, context);
3514 Value mappedSizeH = affine::makeComposedAffineApply(
3515 builder, loc, sizeAffineMap, sizes[getOutputTileHDim()]);
3516 Value mappedSizeW = affine::makeComposedAffineApply(
3517 builder, loc, sizeAffineMap, sizes[getOutputTileWDim()]);
3518
3519 SmallVector<Value> tiledOperands;
3520 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3521
3522 OpFoldResult offsetH = OpFoldResult(mappedOffsetH);
3523 OpFoldResult offsetW = OpFoldResult(mappedOffsetW);
3524 sliceOffsets.append(
3525 {offsets[getOutputNDim()], offsetH, offsetW, offsets[getOutputCDim()]});
3526 OpFoldResult sizeH =
3527 alphaH != 1 ? OpFoldResult(mappedSizeH) : OpFoldResult(oneAttr);
3528 OpFoldResult sizeW =
3529 alphaW != 1 ? OpFoldResult(mappedSizeW) : OpFoldResult(oneAttr);
3530 sliceSizes.append(
3531 {sizes[getOutputNDim()], sizeH, sizeW, sizes[getOutputCDim()]});
3532 int64_t inputRank = getInputOperandRank();
3533 SmallVector<OpFoldResult> inputStrides(inputRank, oneAttr);
3534 auto inputSlice = tensor::ExtractSliceOp::create(
3535 builder, loc, getInput(), sliceOffsets, sliceSizes, inputStrides);
3536 tiledOperands.emplace_back(inputSlice);
3537
3538 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3539 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3540 resultSizes)))
3541 return failure();
3542
3543 int64_t outputRank = getOutputOperandRank();
3544 SmallVector<OpFoldResult> outputStrides(outputRank, oneAttr);
3545 auto outputSlice = tensor::ExtractSliceOp::create(
3546 builder, loc, getOutput(), resultOffsets, resultSizes, outputStrides);
3547 tiledOperands.emplace_back(outputSlice);
3548
3549 SmallVector<Type> resultTypes;
3550 resultTypes.push_back(tiledOperands[1].getType());
3551 Operation *tiledOp =
3552 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3553
3554 return TilingResult{
3555 {tiledOp},
3556 SmallVector<Value>(tiledOp->getResults()),
3557 llvm::to_vector(ArrayRef<Operation *>{inputSlice, outputSlice})};
3558}
3559
3560//===----------------------------------------------------------------------===//
3561// WinogradOutputTransformOp
3562//===----------------------------------------------------------------------===//
3563
3564LogicalResult WinogradOutputTransformOp::verify() {
3565 auto valueType = cast<ShapedType>(getValue().getType());
3566 ArrayRef<int64_t> valueShape = valueType.getShape();
3567 int64_t valueH = valueShape[getValueAlphaHDim()];
3568 int64_t valueW = valueShape[getValueAlphaWDim()];
3569 int64_t valueTileH = valueShape[getValueTileHDim()];
3570 int64_t valueTileW = valueShape[getValueTileWDim()];
3571 WinogradConv2DFmr fmr = getFmr();
3572 int64_t m, r;
3573 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3574 bool leftTransform = valueH != 1;
3575 bool rightTransform = valueW != 1;
3576
3577 int64_t outputRank = getOutputOperandRank();
3578 SmallVector<int64_t> expectedOutputShape(outputRank, valueH);
3579 if (ShapedType::isDynamic(valueH) || ShapedType::isDynamic(valueTileH)) {
3580 expectedOutputShape[getOutputHDim()] = ShapedType::kDynamic;
3581 } else {
3582 if (valueH != (leftTransform ? m + r - 1 : 1))
3583 return emitOpError("expect input height equals to input tile size");
3584 expectedOutputShape[getOutputHDim()] = (leftTransform ? m : 1) * valueTileH;
3585 }
3586 if (ShapedType::isDynamic(valueW) || ShapedType::isDynamic(valueTileW)) {
3587 expectedOutputShape[getOutputWDim()] = ShapedType::kDynamic;
3588 } else {
3589 if (valueW != (rightTransform ? m + r - 1 : 1))
3590 return emitOpError("expect input width equals to input tile size");
3591 expectedOutputShape[getOutputWDim()] =
3592 (rightTransform ? m : 1) * valueTileW;
3593 }
3594 expectedOutputShape[getOutputNDim()] = valueShape[getValueNDim()];
3595 expectedOutputShape[getOutputFDim()] = valueShape[getValueFDim()];
3596
3597 auto outputType = cast<ShapedType>(getOutput().getType());
3598 ArrayRef<int64_t> outputShape = outputType.getShape();
3599 if (failed(verifyCompatibleShape(expectedOutputShape, outputShape))) {
3600 return emitOpError("the output shape is not expected");
3601 }
3602 return success();
3603}
3604
3605SmallVector<Range>
3606WinogradOutputTransformOp::getIterationDomain(OpBuilder &builder) {
3607 Location loc = getLoc();
3608 IntegerAttr zeroAttr = builder.getIndexAttr(0);
3609 IntegerAttr oneAttr = builder.getIndexAttr(1);
3610 Value value = getValue();
3611 int64_t valueRank = getValueOperandRank();
3612 SmallVector<Range> loopBounds(valueRank);
3613 for (unsigned dim = 0; dim < valueRank; ++dim) {
3614 loopBounds[dim].offset = zeroAttr;
3615 // alphaH, alphaW, tileH, tileW, N, F
3616 loopBounds[dim].size = getDimValue(builder, loc, value, dim);
3617 loopBounds[dim].stride = oneAttr;
3618 }
3619 return loopBounds;
3620}
3621
3622SmallVector<utils::IteratorType>
3623WinogradOutputTransformOp::getLoopIteratorTypes() {
3624 int64_t valueRank = getValueOperandRank();
3625 SmallVector<utils::IteratorType> iteratorTypes(valueRank,
3626 utils::IteratorType::parallel);
3627 return iteratorTypes;
3628}
3629
3630LogicalResult WinogradOutputTransformOp::getResultTilePosition(
3631 OpBuilder &builder, unsigned resultNumber, ArrayRef<OpFoldResult> offsets,
3632 ArrayRef<OpFoldResult> sizes, SmallVector<OpFoldResult> &resultOffsets,
3633 SmallVector<OpFoldResult> &resultSizes) {
3634 WinogradConv2DFmr fmr = getFmr();
3635 int64_t m, r;
3636 std::tie(m, r) = getFmrFromWinogradConv2DFmr(fmr);
3637
3638 Location loc = getLoc();
3639 MLIRContext *context = builder.getContext();
3640 auto identityAffineMap =
3641 AffineMap::get(1, 0, {builder.getAffineDimExpr(0)}, context);
3642 auto affineMap =
3643 AffineMap::get(1, 0, {builder.getAffineDimExpr(0) * m}, context);
3644
3645 ShapedType valueType = getValueOperandType();
3646 ArrayRef<int64_t> valueShape = valueType.getShape();
3647 int64_t valueH = valueShape[0];
3648 int64_t valueW = valueShape[1];
3649 Value mappedOffsetH = affine::makeComposedAffineApply(
3650 builder, loc, (valueH != 1 ? affineMap : identityAffineMap),
3651 offsets[getValueTileHDim()]);
3652 Value mappedOffsetW = affine::makeComposedAffineApply(
3653 builder, loc, (valueW != 1 ? affineMap : identityAffineMap),
3654 offsets[getValueTileWDim()]);
3655 Value mappedSizeH = affine::makeComposedAffineApply(
3656 builder, loc, affineMap, sizes[getValueTileHDim()]);
3657 Value mappedSizeW = affine::makeComposedAffineApply(
3658 builder, loc, affineMap, sizes[getValueTileWDim()]);
3659
3660 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3661 OpFoldResult offsetH = OpFoldResult(mappedOffsetH);
3662 OpFoldResult offsetW = OpFoldResult(mappedOffsetW);
3663 OpFoldResult sizeH =
3664 valueH != 1 ? OpFoldResult(mappedSizeH) : OpFoldResult(oneAttr);
3665 OpFoldResult sizeW =
3666 valueW != 1 ? OpFoldResult(mappedSizeW) : OpFoldResult(oneAttr);
3667
3668 resultOffsets.append(
3669 {offsets[getValueNDim()], offsetH, offsetW, offsets[getValueFDim()]});
3670 resultSizes.append(
3671 {sizes[getValueNDim()], sizeH, sizeW, sizes[getValueFDim()]});
3672 return success();
3673}
3674
3675/// The inner tile alignment hint is only used by `linalg.pack` and
3676/// `linalg.unpack` operations. Therefore, this is forwarded to the hint-less
3677/// overload.
3678FailureOr<TilingResult> WinogradOutputTransformOp::getTiledImplementation(
3679 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3680 ArrayRef<OpFoldResult> sizes, ArrayRef<InnerTileAlignment>) {
3681 return getTiledImplementation(builder, offsets, sizes);
3682}
3683
3684/// Implement tiling for winograd_output_transform
3685/// The input of winograd_output_transform is (alphaH, alphaW, tileH, tileW, N,
3686/// F). The output of winograd_output_transform is (N, H, W, F) Users can
3687/// specify the tile sizes of tileH, tileW, N, and F. `offsets` are the values
3688/// for the offsets of tileH, tileW, N, F for one tile. `sizes` are the values
3689/// for the sizes of tileH, tileW, N, F for one tile.
3690FailureOr<TilingResult> WinogradOutputTransformOp::getTiledImplementation(
3691 OpBuilder &builder, ArrayRef<OpFoldResult> offsets,
3692 ArrayRef<OpFoldResult> sizes) {
3693 IntegerAttr oneAttr = builder.getI64IntegerAttr(1);
3694 IntegerAttr zeroAttr = builder.getI64IntegerAttr(0);
3695 Location loc = getLoc();
3696 SmallVector<Value> tiledOperands;
3697 SmallVector<OpFoldResult> sliceOffsets, sliceSizes;
3698
3699 ShapedType valueType = getValueOperandType();
3700 ArrayRef<int64_t> valueShape = valueType.getShape();
3701 int64_t alphaH = valueShape[getValueAlphaHDim()];
3702 int64_t alphaW = valueShape[getValueAlphaWDim()];
3703 IntegerAttr alphaHAttr = builder.getI64IntegerAttr(alphaH);
3704 IntegerAttr alphaWAttr = builder.getI64IntegerAttr(alphaW);
3705
3706 sliceOffsets.append({zeroAttr, zeroAttr, offsets[getValueTileHDim()],
3707 offsets[getValueTileWDim()], offsets[getValueNDim()],
3708 offsets[getValueFDim()]});
3709 sliceSizes.append({alphaHAttr, alphaWAttr, sizes[getValueTileHDim()],
3710 sizes[getValueTileWDim()], sizes[getValueNDim()],
3711 sizes[getValueFDim()]});
3712 int64_t valueRank = getValueOperandRank();
3713 SmallVector<OpFoldResult> sliceStrides(valueRank, oneAttr);
3714 auto valueSlice = tensor::ExtractSliceOp::create(
3715 builder, loc, getValue(), sliceOffsets, sliceSizes, sliceStrides);
3716 tiledOperands.emplace_back(valueSlice);
3717
3718 SmallVector<OpFoldResult> resultOffsets, resultSizes;
3719 if (failed(getResultTilePosition(builder, 1, offsets, sizes, resultOffsets,
3720 resultSizes)))
3721 return failure();
3722
3723 int64_t outputRank = getOutputOperandRank();
3724 SmallVector<OpFoldResult> strides(outputRank, oneAttr);
3725 auto outputSlice = tensor::ExtractSliceOp::create(
3726 builder, loc, getOutput(), resultOffsets, resultSizes, strides);
3727 tiledOperands.emplace_back(outputSlice);
3728
3729 SmallVector<Type> resultTypes;
3730 resultTypes.push_back(tiledOperands[1].getType());
3731 Operation *tiledOp =
3732 mlir::clone(builder, getOperation(), resultTypes, tiledOperands);
3733
3734 return TilingResult{
3735 {tiledOp},
3736 SmallVector<Value>(tiledOp->getResults()),
3737 llvm::to_vector(ArrayRef<Operation *>{valueSlice, outputSlice})};
3738}
3739
3740//===----------------------------------------------------------------------===//
3741// LinalgDialect
3742// TODO: Merge with the LinalgDialect block at the bottom
3743//===----------------------------------------------------------------------===//
3744
3745// Returns true if the result expression of `subMap` are a subset of `fullMap`.
3746static bool areResultExprsSubsetOf(AffineMap subMap, AffineMap fullMap) {
3747 auto explicitRange = subMap.getResults();
3748 auto defaultRange = fullMap.getResults();
3749 DenseSet<AffineExpr> explicitSet(explicitRange.begin(), explicitRange.end());
3750 DenseSet<AffineExpr> defaultSet(defaultRange.begin(), defaultRange.end());
3751 llvm::set_union(explicitSet, defaultSet);
3752 return explicitSet == defaultSet;
3753}
3754
3755/// Check if the user defined map is valid broadcast map. Here broadcast
3756/// indexing maps are defined in context of corresponding default indexing maps
3757/// for the given Op. This way the check becomes very simple i.e just check the
3758/// number of result dims.
3759/// Returns true if the explictMap is broadcasted with respect to the
3760/// defaultMap.
3761static bool isBroadcasted(AffineMap explictMap, AffineMap defaultMap) {
3762 return explictMap.getNumResults() < defaultMap.getNumResults();
3763}
3764
3765/// Verifies the broadcast and transpose semantic sepecified by the explicit
3766/// indexing map for the MatmulOp \p op for each operand specified by \p
3767/// opIndex.
3768static LogicalResult verifyExtendedMatmulSemantic(MatmulOp matmulOp,
3769 unsigned opIndex) {
3770 SmallVector<AffineMap, 3> opIndexingMaps = matmulOp.getIndexingMapsArray();
3771 SmallVector<AffineMap, 3> defaultIndexingMaps =
3772 matmulOp.getDefaultIndexingMaps(matmulOp->getContext());
3773
3774 auto opIndexingMap = opIndexingMaps[opIndex];
3775 auto defaultIndexingMap = defaultIndexingMaps[opIndex];
3776 // Check general validity of indexing map results.
3777 if (!areResultExprsSubsetOf(opIndexingMap, defaultIndexingMap))
3778 return matmulOp->emitOpError()
3779 << "Unexpected dim expression in map result.";
3780
3781 if (isBroadcasted(opIndexingMap, defaultIndexingMap)) {
3782 if (!matmulOp.isValidLhsRhsBroadcastMap(opIndexingMap)) {
3783 return matmulOp->emitOpError()
3784 << "Invalid broadcast requested, should be (d2).";
3785 }
3786 return success();
3787 }
3788 return success();
3789}
3790
3791// Check general validity of input indexing map of
3792// BatchMatmulOp/BatchReduceMatmulOp.
3793template <typename OpTy>
3794static LogicalResult verifyInputMaps(OpTy batchVariantMatmulOp,
3795 AffineMap opIndexingMap,
3796 AffineMap defaultIndexingMap, bool isLHS) {
3797 assert((isa<BatchMatmulOp>(batchVariantMatmulOp) ||
3798 isa<BatchReduceMatmulOp>(batchVariantMatmulOp)) &&
3799 "Expected BatchMatmulOp or BatchReduceMatmulOp");
3800 // Check the result dims are valid.
3801 if (!areResultExprsSubsetOf(opIndexingMap, defaultIndexingMap))
3802 return batchVariantMatmulOp->emitOpError()
3803 << "Unexpected result dim expression (outside the set of default "
3804 "result dims).";
3805
3806 // Check for valid number of result dims of input maps.
3807 if (opIndexingMap.getNumResults() > 3)
3808 return batchVariantMatmulOp->emitOpError()
3809 << "no. of result dim expressions exceeds 3.";
3810
3811 auto hasValidBatchDim = [](AffineMap map) {
3812 AffineExpr batchDim = map.getResult(0);
3813 return batchDim.isFunctionOfDim(0);
3814 };
3815
3816 // Check if the requested broadcast is valid.
3817 if (isBroadcasted(opIndexingMap, defaultIndexingMap)) {
3818 if (!batchVariantMatmulOp.isValidLhsRhsBroadcastMap(opIndexingMap, isLHS))
3819 return batchVariantMatmulOp->emitOpError()
3820 << "Invalid broadcast requested.";
3821 } else if (!hasValidBatchDim(opIndexingMap)) {
3822 return batchVariantMatmulOp->emitOpError()
3823 << "Invalid batch dimension expression.";
3824 }
3825 return success();
3826}
3827
3828/// This function checks if the given AffineMap for the output of a
3829/// BatchMatmulOp/BatchReduceMatmulOp has exactly the desired number of result
3830/// dimensions and if the output map result dimensions are valid.
3831template <typename OpTy>
3832static LogicalResult verifyOutputMap(OpTy batchVariantMatmulOp,
3833 AffineMap opIndexingMap) {
3834 assert((isa<BatchMatmulOp>(batchVariantMatmulOp) ||
3835 isa<BatchReduceMatmulOp>(batchVariantMatmulOp)) &&
3836 "Expected BatchMatmulOp or BatchReduceMatmulOp");
3837 if (isa<BatchMatmulOp>(batchVariantMatmulOp) &&
3838 opIndexingMap.getNumResults() != 3) {
3839
3840 return batchVariantMatmulOp->emitOpError()
3841 << "expects 3 dims, but got (" << opIndexingMap.getNumResults()
3842 << ").";
3843 }
3844 if (isa<BatchReduceMatmulOp>(batchVariantMatmulOp) &&
3845 opIndexingMap.getNumResults() != 2) {
3846 return batchVariantMatmulOp->emitOpError()
3847 << "expects 2 dims, but got (" << opIndexingMap.getNumResults()
3848 << ").";
3849 }
3850
3851 auto areValidOutputResultDim = [&](AffineMap outputMap) {
3852 return isa<BatchMatmulOp>(batchVariantMatmulOp)
3853 ? outputMap.getResult(0).isFunctionOfDim(0) &&
3854 outputMap.getResult(1).isFunctionOfDim(1) &&
3855 outputMap.getResult(2).isFunctionOfDim(2)
3856 : outputMap.getResult(0).isFunctionOfDim(1) &&
3857 outputMap.getResult(1).isFunctionOfDim(2);
3858 };
3859
3860 if (!areValidOutputResultDim(opIndexingMap)) {
3861 return batchVariantMatmulOp->emitOpError()
3862 << "Invalid output map result dimension.";
3863 }
3864
3865 return success();
3866}
3867
3868/// Verifies the broadcast and transpose semantic specified by the explicit
3869/// indexing map for the BatchMatmulOp/BatchReduceMatmulOp op for each operand
3870/// specified by opIndex.
3871template <typename OpTy>
3872static LogicalResult
3874 unsigned opIndex) {
3875 SmallVector<AffineMap, 3> opIndexingMaps =
3876 batchVariantMatmulOp.getIndexingMapsArray();
3877 SmallVector<AffineMap, 3> defaultIndexingMaps =
3878 batchVariantMatmulOp.getDefaultIndexingMaps(
3879 batchVariantMatmulOp->getContext());
3880
3881 if (opIndexingMaps.size() != 3)
3882 return batchVariantMatmulOp->emitOpError()
3883 << "Indexing_map attribute must have 3 affine maps.";
3884
3885 auto opIndexingMap = opIndexingMaps[opIndex];
3886 auto defaultIndexingMap = defaultIndexingMaps[opIndex];
3887
3888 if (opIndex == 2 &&
3889 failed(verifyOutputMap(batchVariantMatmulOp, opIndexingMap)))
3890 return failure();
3891
3892 if (opIndex != 2 &&
3893 failed(verifyInputMaps(batchVariantMatmulOp, opIndexingMap,
3894 defaultIndexingMap, opIndex == 0)))
3895 return failure();
3896
3897 return success();
3898}
3899
3900namespace mlir {
3901namespace linalg {
3902
3903std::optional<WinogradConv2DFmr> getWinogradConv2DFmr(int64_t m, int64_t r) {
3904 if (m == 2 && r == 3)
3905 return WinogradConv2DFmr::F_2_3;
3906 if (m == 4 && r == 3)
3907 return WinogradConv2DFmr::F_4_3;
3908 if (m == 2 && r == 5)
3909 return WinogradConv2DFmr::F_2_5;
3910 return std::nullopt;
3911}
3912
3913std::pair<int64_t, int64_t> getFmrFromWinogradConv2DFmr(WinogradConv2DFmr fmr) {
3914 switch (fmr) {
3915 case WinogradConv2DFmr::F_2_3:
3916 return {2, 3};
3917 case WinogradConv2DFmr::F_4_3:
3918 return {4, 3};
3919 case WinogradConv2DFmr::F_2_5:
3920 return {2, 5};
3921 }
3922 llvm_unreachable("Unkown WinogradConv2DFmr");
3923}
3924
3925//===----------------------------------------------------------------------===//
3926// MatMulOp
3927//===----------------------------------------------------------------------===//
3928
3929static FailureOr<SmallVector<SmallVector<int64_t>>>
3932 for (auto map : maps) {
3933 AffineMapAttr attr = dyn_cast<AffineMapAttr>(map);
3934 if (!attr)
3935 return failure();
3937 for (auto result : attr.getAffineMap().getResults()) {
3938 auto dim = dyn_cast<AffineDimExpr>(result);
3939 if (!dim)
3940 return failure();
3941 pos.push_back(dim.getPosition());
3942 }
3943 positions.push_back(pos);
3944 }
3945 return positions;
3946}
3947
3948/// Returns a list of AffineMap with the typical matmul indexing charactristic.
3949SmallVector<AffineMap> MatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
3950 AffineExpr d0, d1, d2;
3951 SmallVector<AffineMap> indexingMaps;
3952 bindDims(context, d0, d1, d2);
3953 indexingMaps.push_back(AffineMap::get(3, 0, {d0, d2}, context));
3954 indexingMaps.push_back(AffineMap::get(3, 0, {d2, d1}, context));
3955 indexingMaps.push_back(AffineMap::get(3, 0, {d0, d1}, context));
3956 return indexingMaps;
3957}
3958
3959bool MatmulOp::isDefaultIndexingMaps(Attribute attr) {
3960 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
3961 if (!maps)
3962 return false;
3963 if (maps.size() != 3)
3964 return false;
3965 auto positions = getAffineResultPositions(maps);
3966 if (failed(positions))
3967 return false;
3968 return (*positions)[0] == SmallVector<int64_t>{0, 2} &&
3969 (*positions)[1] == SmallVector<int64_t>{2, 1} &&
3970 (*positions)[2] == SmallVector<int64_t>{0, 1};
3971}
3972
3973SmallVector<utils::IteratorType> MatmulOp::getIteratorTypesArray() {
3974 return SmallVector<utils::IteratorType>{utils::IteratorType::parallel,
3975 utils::IteratorType::parallel,
3976 utils::IteratorType::reduction};
3977}
3978
3979unsigned MatmulOp::getNumRegionArgs() { return 3; }
3980
3981std::string MatmulOp::getLibraryCallName() {
3982 return generateLibraryCallName(getOperation());
3983}
3984
3985bool MatmulOp::hasDynamicIndexingMaps() { return true; }
3986
3987/// Check if the op has broadcast and/or transpose semantic. Returns true if
3988/// the user defined indexing maps are not equal to default map.
3989bool MatmulOp::hasUserDefinedMaps() {
3990 SmallVector<AffineMap, 3> defaultMaps =
3991 getDefaultIndexingMaps(this->getContext());
3992 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
3993 return defaultMaps != explicitMaps;
3994}
3995
3996/// Implements the block region builder for the MatmulOp. This is called by
3997/// 'fillStructuredOpRegion'.
3998void MatmulOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block,
3999 ArrayRef<NamedAttribute> attrs,
4000 function_ref<InFlightDiagnostic()> emitError) {
4001 if (emitError && block.getNumArguments() != 3) {
4002 emitError() << "MatmulOp regionBuilder expects 3 args, got "
4003 << block.getNumArguments();
4004 return;
4005 }
4006 assert(block.getNumArguments() == 3 &&
4007 "MatmulOp regionBuilder expects 3 args");
4008 RegionBuilderHelper helper(b, block);
4009 SmallVector<Value> yields;
4010
4011 TypeFn castVal = TypeFn::cast_signed;
4012 const auto *castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4013 return attr.getName() == "cast";
4014 });
4015 if (castIter != attrs.end()) {
4016 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4017 castVal = attr.getValue();
4018 }
4019
4020 Value value1 = helper.buildTypeFn(castVal, block.getArgument(2).getType(),
4021 block.getArgument(0));
4022 Value value2 = helper.buildTypeFn(castVal, block.getArgument(2).getType(),
4023 block.getArgument(1));
4024 Value value3 = helper.buildBinaryFn(BinaryFn::mul, value1, value2, emitError);
4025 if (!value1 || !value2 || !value3)
4026 return;
4027 Value value4 = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4028 value3, emitError);
4029 if (!value4)
4030 return;
4031 yields.push_back(value4);
4032 helper.yieldOutputs(yields);
4033}
4034
4035/// Returns true if the given bcastMap map is a valid broadcast map. A valid
4036/// broadcast map must include K dimension.
4037/// TODO: Strict inclusion of K dimension in the broadcast map is not
4038/// necessary for both input matrices simultaneously. We can relax this
4039/// condition to have K dimension for one input matrix map and infer the K
4040/// dimension for other input matrix map from the one already having K
4041/// dimension.
4042bool MatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap) {
4043 assert(bcastMap.getNumResults() == 1 && "Expected single result dim expr.");
4044 AffineExpr expr = bcastMap.getResult(0);
4045 // Invalid map if the common dimension of matmul not found.
4046 return expr.isFunctionOfDim(bcastMap.getNumDims() - 1);
4047}
4048
4049static FailureOr<ArrayAttr> parseIndexingMapsAttr(OpAsmParser &parser) {
4050 if (parser.parseOptionalKeyword("indexing_maps"))
4051 return ArrayAttr{
4052 nullptr}; // Success in case indexing_maps was not provided.
4053
4054 ArrayAttr arrayAttr;
4055 if (parser.parseEqual() || parser.parseAttribute(arrayAttr))
4056 return failure();
4057
4058 if (llvm::any_of(arrayAttr,
4059 [](auto elt) { return !dyn_cast<AffineMapAttr>(elt); }))
4060 return parser.emitError(parser.getCurrentLocation())
4061 << "element of indexing_maps array is not an affine_map";
4062
4063 return arrayAttr;
4064}
4065
4066ParseResult MatmulOp::parse(OpAsmParser &parser, OperationState &result) {
4067 FailureOr<ArrayAttr> indexingMapsAttr = parseIndexingMapsAttr(parser);
4068 if (failed(indexingMapsAttr))
4069 return failure();
4070
4071 if (*indexingMapsAttr == nullptr) {
4072 auto indexingMapAttrs = llvm::map_to_vector(
4073 MatmulOp::getDefaultIndexingMaps(parser.getContext()),
4074 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4075 indexingMapsAttr = parser.getBuilder().getArrayAttr(indexingMapAttrs);
4076 }
4077
4078 result.addAttribute("indexing_maps", *indexingMapsAttr);
4079 return parseNamedStructuredOp(parser, result, MatmulOp::getNumRegionArgs(),
4080 MatmulOp::getRegionBuilder());
4081}
4082
4083void MatmulOp::print(OpAsmPrinter &p) {
4084 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
4085 MatmulOp::getDefaultIndexingMaps(getContext()),
4086 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4087 if (!llvm::equal(getIndexingMaps(), indexingMaps))
4088 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4089
4090 std::array<StringRef, 3> elidedAttrs = {
4091 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
4092 printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
4093 elidedAttrs);
4094}
4095
4096/// Verify the user defined indexing maps.
4097LogicalResult MatmulOp::verify() {
4098 // Verification of pure matmul is handled by verifyStructuredOpInterface().
4099 if (!hasUserDefinedMaps())
4100 return success();
4101
4102 for (unsigned opIndex = 0; opIndex < 2; opIndex++) {
4103 if (failed(verifyExtendedMatmulSemantic(*this, opIndex)))
4104 return failure();
4105 }
4106 return success();
4107}
4108
4109LogicalResult MatmulOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
4110 return memref::foldMemRefCast(*this);
4111}
4112
4113void MatmulOp::getEffects(
4114 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4115 &effects) {
4116 if (hasPureTensorSemantics())
4117 return;
4118 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4119}
4120
4121Speculation::Speculatability MatmulOp::getSpeculatability() {
4122 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4123}
4124
4125SmallVector<AffineMap>
4126MatmulTransposeAOp::getDefaultIndexingMaps(OpBuilder &builder) {
4127 AffineExpr d0, d1, d2;
4128 MLIRContext *context = builder.getContext();
4129 bindDims(context, d0, d1, d2);
4130 AffineMap mapLHS = AffineMap::get(3, 0, {d2, d0}, context);
4131 AffineMap mapRHS = AffineMap::get(3, 0, {d2, d1}, context);
4132 AffineMap mapOut = AffineMap::get(3, 0, {d0, d1}, context);
4133 return {mapLHS, mapRHS, mapOut};
4134}
4135
4137 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4138 if (!maps)
4139 return false;
4140 if (maps.size() != 3)
4141 return false;
4142 auto positions = getAffineResultPositions(maps);
4143 if (failed(positions))
4144 return false;
4145 return (*positions)[0] == SmallVector<int64_t>{2, 0} &&
4146 (*positions)[1] == SmallVector<int64_t>{2, 1} &&
4147 (*positions)[2] == SmallVector<int64_t>{0, 1};
4148}
4149
4152 ValueRange inputs, ValueRange outputs,
4153 ArrayRef<NamedAttribute> attributes) {
4154 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4155 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4156}
4157
4160 ValueRange inputs, ValueRange outputs,
4161 ArrayRef<NamedAttribute> attributes) {
4162 OperationState state(location, getOperationName());
4163 build(builder, state, inputs, outputs, attributes);
4164 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4165 assert(res && "builder didn't return the right type");
4166 return res;
4167}
4168
4171 TypeRange resultTensorTypes,
4172 ValueRange inputs, ValueRange outputs,
4173 ArrayRef<NamedAttribute> attributes) {
4174 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4175 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4176}
4177
4180 TypeRange resultTensorTypes, ValueRange inputs,
4181 ValueRange outputs,
4182 ArrayRef<NamedAttribute> attributes) {
4183 OperationState state(location, getOperationName());
4184 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4185 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4186 assert(res && "builder didn't return the right type");
4187 return res;
4188}
4189
4192 TypeRange resultTensorTypes,
4193 ValueRange inputs, ValueRange outputs,
4194 Attribute cast,
4195 ArrayRef<NamedAttribute> attributes) {
4196 result.addAttribute("cast", cast);
4197 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4198 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4199}
4200
4203 TypeRange resultTensorTypes, ValueRange inputs,
4204 ValueRange outputs, Attribute cast,
4205 ArrayRef<NamedAttribute> attributes) {
4206 OperationState state(location, getOperationName());
4207 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4208 auto res = dyn_cast<MatmulTransposeAOp>(builder.create(state));
4209 assert(res && "builder didn't return the right type");
4210 return res;
4211}
4212
4214 return dyn_cast_or_null<linalg::MatmulOp>(op) &&
4216 op->getAttr("indexing_maps"));
4217}
4218
4220MatmulTransposeBOp::getDefaultIndexingMaps(OpBuilder &builder) {
4221 AffineExpr d0, d1, d2;
4222 MLIRContext *context = builder.getContext();
4223 bindDims(context, d0, d1, d2);
4224 AffineMap mapLHS = AffineMap::get(3, 0, {d0, d2}, context);
4225 AffineMap mapRHS = AffineMap::get(3, 0, {d1, d2}, context);
4226 AffineMap mapOut = AffineMap::get(3, 0, {d0, d1}, context);
4227 return {mapLHS, mapRHS, mapOut};
4228}
4229
4231 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4232 if (!maps)
4233 return false;
4234 if (maps.size() != 3)
4235 return false;
4236 auto positions = getAffineResultPositions(maps);
4237 if (failed(positions))
4238 return false;
4239 return (*positions)[0] == SmallVector<int64_t>{0, 2} &&
4240 (*positions)[1] == SmallVector<int64_t>{1, 2} &&
4241 (*positions)[2] == SmallVector<int64_t>{0, 1};
4242}
4243
4246 ValueRange inputs, ValueRange outputs,
4247 ArrayRef<NamedAttribute> attributes) {
4248 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4249 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4250}
4251
4254 ValueRange inputs, ValueRange outputs,
4255 ArrayRef<NamedAttribute> attributes) {
4256 OperationState state(location, getOperationName());
4257 build(builder, state, inputs, outputs, attributes);
4258 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4259 assert(res && "builder didn't return the right type");
4260 return res;
4261}
4262
4265 TypeRange resultTensorTypes,
4266 ValueRange inputs, ValueRange outputs,
4267 ArrayRef<NamedAttribute> attributes) {
4268 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4269 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4270}
4271
4274 TypeRange resultTensorTypes, ValueRange inputs,
4275 ValueRange outputs,
4276 ArrayRef<NamedAttribute> attributes) {
4277 OperationState state(location, getOperationName());
4278 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4279 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4280 assert(res && "builder didn't return the right type");
4281 return res;
4282}
4283
4286 TypeRange resultTensorTypes,
4287 ValueRange inputs, ValueRange outputs,
4288 Attribute cast,
4289 ArrayRef<NamedAttribute> attributes) {
4290 result.addAttribute("cast", cast);
4291 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4292 MatmulOp::getRegionBuilder(), getDefaultIndexingMaps(builder));
4293}
4294
4297 TypeRange resultTensorTypes, ValueRange inputs,
4298 ValueRange outputs, Attribute cast,
4299 ArrayRef<NamedAttribute> attributes) {
4300 OperationState state(location, getOperationName());
4301 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4302 auto res = dyn_cast<MatmulTransposeBOp>(builder.create(state));
4303 assert(res && "builder didn't return the right type");
4304 return res;
4305}
4306
4308 return dyn_cast_or_null<linalg::MatmulOp>(op) &&
4310 op->getAttr("indexing_maps"));
4311}
4312
4314BatchMatmulTransposeAOp::getDefaultIndexingMaps(OpBuilder &builder) {
4315 AffineExpr d0, d1, d2, d3;
4316 MLIRContext *context = builder.getContext();
4317 bindDims(context, d0, d1, d2, d3);
4318 AffineMap mapLHS = AffineMap::get(4, 0, {d0, d3, d1}, context);
4319 AffineMap mapRHS = AffineMap::get(4, 0, {d0, d3, d2}, context);
4320 AffineMap mapOut = AffineMap::get(4, 0, {d0, d1, d2}, context);
4321 return {mapLHS, mapRHS, mapOut};
4322}
4323
4325 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4326 if (!maps)
4327 return false;
4328 if (maps.size() != 3)
4329 return false;
4330 auto positions = getAffineResultPositions(maps);
4331 if (failed(positions))
4332 return false;
4333 return (*positions)[0] == SmallVector<int64_t>{0, 3, 1} &&
4334 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
4335 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4336}
4337
4339 OpBuilder &builder, OperationState &result, ValueRange inputs,
4340 ValueRange outputs, ArrayRef<NamedAttribute> attributes) {
4341 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4342 BatchMatmulOp::getRegionBuilder(),
4343 getDefaultIndexingMaps(builder));
4344}
4345
4348 ValueRange inputs, ValueRange outputs,
4349 ArrayRef<NamedAttribute> attributes) {
4350 OperationState state(location, getOperationName());
4351 build(builder, state, inputs, outputs, attributes);
4352 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4353 assert(res && "builder didn't return the right type");
4354 return res;
4355}
4356
4358 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4359 ValueRange inputs, ValueRange outputs,
4360 ArrayRef<NamedAttribute> attributes) {
4361 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4362 BatchMatmulOp::getRegionBuilder(),
4363 getDefaultIndexingMaps(builder));
4364}
4365
4368 TypeRange resultTensorTypes, ValueRange inputs,
4369 ValueRange outputs,
4370 ArrayRef<NamedAttribute> attributes) {
4371 OperationState state(location, getOperationName());
4372 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4373 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4374 assert(res && "builder didn't return the right type");
4375 return res;
4376}
4377
4379 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4380 ValueRange inputs, ValueRange outputs, Attribute cast,
4381 ArrayRef<NamedAttribute> attributes) {
4382 result.addAttribute("cast", cast);
4383 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4384 BatchMatmulOp::getRegionBuilder(),
4385 getDefaultIndexingMaps(builder));
4386}
4387
4390 TypeRange resultTensorTypes, ValueRange inputs,
4391 ValueRange outputs, Attribute cast,
4392 ArrayRef<NamedAttribute> attributes) {
4393 OperationState state(location, getOperationName());
4394 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4395 auto res = dyn_cast<BatchMatmulTransposeAOp>(builder.create(state));
4396 assert(res && "builder didn't return the right type");
4397 return res;
4398}
4399
4401 return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
4403 op->getAttr("indexing_maps"));
4404}
4405
4407BatchMatmulTransposeBOp::getDefaultIndexingMaps(OpBuilder &builder) {
4408 AffineExpr d0, d1, d2, d3;
4409 MLIRContext *context = builder.getContext();
4410 bindDims(context, d0, d1, d2, d3);
4411 AffineMap mapLHS = AffineMap::get(4, 0, {d0, d1, d3}, context);
4412 AffineMap mapRHS = AffineMap::get(4, 0, {d0, d2, d3}, context);
4413 AffineMap mapOut = AffineMap::get(4, 0, {d0, d1, d2}, context);
4414 return {mapLHS, mapRHS, mapOut};
4415}
4416
4418 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4419 if (!maps)
4420 return false;
4421 if (maps.size() != 3)
4422 return false;
4423 auto positions = getAffineResultPositions(maps);
4424 if (failed(positions))
4425 return false;
4426 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
4427 (*positions)[1] == SmallVector<int64_t>{0, 2, 3} &&
4428 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4429}
4430
4432 OpBuilder &builder, OperationState &result, ValueRange inputs,
4433 ValueRange outputs, ArrayRef<NamedAttribute> attributes) {
4434 buildMatmulOp(builder, result, std::nullopt, inputs, outputs, attributes,
4435 BatchMatmulOp::getRegionBuilder(),
4436 getDefaultIndexingMaps(builder));
4437}
4438
4441 ValueRange inputs, ValueRange outputs,
4442 ArrayRef<NamedAttribute> attributes) {
4443 OperationState state(location, getOperationName());
4444 build(builder, state, inputs, outputs, attributes);
4445 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4446 assert(res && "builder didn't return the right type");
4447 return res;
4448}
4449
4451 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4452 ValueRange inputs, ValueRange outputs,
4453 ArrayRef<NamedAttribute> attributes) {
4454 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4455 BatchMatmulOp::getRegionBuilder(),
4456 getDefaultIndexingMaps(builder));
4457}
4458
4461 TypeRange resultTensorTypes, ValueRange inputs,
4462 ValueRange outputs,
4463 ArrayRef<NamedAttribute> attributes) {
4464 OperationState state(location, getOperationName());
4465 build(builder, state, resultTensorTypes, inputs, outputs, attributes);
4466 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4467 assert(res && "builder didn't return the right type");
4468 return res;
4469}
4470
4472 OpBuilder &builder, OperationState &result, TypeRange resultTensorTypes,
4473 ValueRange inputs, ValueRange outputs, Attribute cast,
4474 ArrayRef<NamedAttribute> attributes) {
4475 result.addAttribute("cast", cast);
4476 buildMatmulOp(builder, result, resultTensorTypes, inputs, outputs, attributes,
4477 BatchMatmulOp::getRegionBuilder(),
4478 getDefaultIndexingMaps(builder));
4479}
4480
4483 TypeRange resultTensorTypes, ValueRange inputs,
4484 ValueRange outputs, Attribute cast,
4485 ArrayRef<NamedAttribute> attributes) {
4486 OperationState state(location, getOperationName());
4487 build(builder, state, resultTensorTypes, inputs, outputs, cast, attributes);
4488 auto res = dyn_cast<BatchMatmulTransposeBOp>(builder.create(state));
4489 assert(res && "builder didn't return the right type");
4490 return res;
4491}
4492
4494 return dyn_cast_or_null<linalg::BatchMatmulOp>(op) &&
4496 op->getAttr("indexing_maps"));
4497}
4498
4499//===----------------------------------------------------------------------===//
4500// ContractOp
4501//===----------------------------------------------------------------------===//
4502
4503SmallVector<utils::IteratorType> ContractOp::getIteratorTypesArray() {
4504 AffineMap outAffineMap = getIndexingMapsArray().pop_back_val();
4505 // On well-formed IR, indexing_maps is non-empty, contained affine_maps'
4506 // domains are all the same, and each implements a projected permutation.
4507 // Each iteration space dim must occur for at least one operand and either
4508 // takes part in a contraction/reduction or else has parallel iteration type.
4509 // We have that a dim is a contraction/reduction dim if and only if the dim
4510 // occurs for the output operand. We use this fact for fast inference:
4511 // NB: In case we allow dims to occur solely for one input, the above still
4512 // holds: per the einsum semantics, these are reduction dims as well.
4513 SmallVector<bool> dimsInOutput(outAffineMap.getNumDims(), false);
4514 for (auto result : outAffineMap.getResults()) {
4515 auto dimExpr = dyn_cast<AffineDimExpr>(result);
4516 assert(dimExpr && "affine_map is a projected permutation");
4517 dimsInOutput[dimExpr.getPosition()] = true;
4518 }
4519
4521 for (auto dimOccursInOutput : dimsInOutput)
4522 iteratorTypes.push_back(dimOccursInOutput ? utils::IteratorType::parallel
4523 : utils::IteratorType::reduction);
4524
4525 return iteratorTypes;
4526}
4527
4528unsigned ContractOp::getNumRegionArgs() { return 3; }
4529
4530/// Implement block region builder, which is called by 'fillStructuredOpRegion'.
4531void ContractOp::regionBuilder(ImplicitLocOpBuilder &b, Block &block,
4532 ArrayRef<NamedAttribute> attrs,
4533 function_ref<InFlightDiagnostic()> emitError) {
4534 if (emitError && block.getNumArguments() != 3) {
4535 emitError() << "ContractOp regionBuilder expects 3 args, got "
4536 << block.getNumArguments();
4537 return;
4538 }
4539 assert(block.getNumArguments() == 3 &&
4540 "ContractOp regionBuilder expects 3 args");
4541 RegionBuilderHelper helper(b, block);
4542
4543 TypeFn castSignedness = TypeFn::cast_signed;
4544 auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4545 return attr.getName() == "cast";
4546 });
4547 if (castIter != attrs.end()) {
4548 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4549 castSignedness = attr.getValue();
4550 }
4551
4552 // TODO: Support fields with operators besides mult & add.
4553 Type outType = block.getArgument(2).getType();
4554 Value lhsAtOutType =
4555 helper.buildTypeFn(castSignedness, outType, block.getArgument(0));
4556 Value rhsAtOutType =
4557 helper.buildTypeFn(castSignedness, outType, block.getArgument(1));
4558 Value productAtOutType = helper.buildBinaryFn(BinaryFn::mul, lhsAtOutType,
4559 rhsAtOutType, emitError);
4560 if (!productAtOutType)
4561 return;
4562 Value result = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4563 productAtOutType, emitError);
4564 if (!result)
4565 return;
4566 helper.yieldOutputs({result});
4567}
4568
4569ParseResult ContractOp::parse(OpAsmParser &parser, OperationState &result) {
4570 FailureOr<ArrayAttr> indexingMapsAttr = parseIndexingMapsAttr(parser);
4571 if (failed(indexingMapsAttr) || *indexingMapsAttr == nullptr)
4572 return parser.emitError(parser.getCurrentLocation(),
4573 "expected 'indexing_maps' attribute");
4574 result.addAttribute("indexing_maps", *indexingMapsAttr);
4575
4576 return parseNamedStructuredOp(parser, result, getNumRegionArgs(),
4577 regionBuilder);
4578}
4579
4580void ContractOp::print(OpAsmPrinter &p) {
4581 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4583 p, getOperation(), getInputs(), getOutputs(),
4584 /*elidedAttrs=*/{"indexing_maps", "operandSegmentSizes"});
4585}
4586
4587LogicalResult ContractOp::verify() {
4588 int iterationSpaceDims = -1;
4589 // Map iter space dims to #occurrences in inputs' and output's affine_maps:
4590 // e.g., inOccurrences[0] will hold #times that dim (with index) 0 is used to
4591 // access an input operand (so occurrence count can be at most 2) and
4592 // outOccurrences[1] will indicate whether dim 1 occurred in the output, etc.
4593 SmallVector<size_t> inOccurrences;
4594 SmallVector<size_t> outOccurrences;
4595
4596 // A helper so that for each operand's affine_map and type we check that ...
4597 auto checkAffineMapAndType = [&](AffineMap affineMap, Type operandType,
4598 bool isInput) -> LogicalResult {
4599 // ... the affine_map is a projected permutation;
4600 if (!affineMap.isProjectedPermutation())
4601 return emitError("provided affine_map is not a projected permutation");
4602
4603 // ... the rank of the affine_map's results and corresponding type match;
4604 if (auto shapedType = dyn_cast<ShapedType>(operandType)) {
4605 if (affineMap.getNumResults() != shapedType.getRank())
4606 return emitError("ranks of shaped operand and results of corresponding "
4607 "affine_map differ");
4608 } else if (affineMap.getNumResults() != 0) {
4609 return emitError("affine_map specifies shaped access while operand has "
4610 "non-shaped type");
4611 }
4612
4613 // ... the rank of the affine_map's domain is the same as those seen prior;
4614 if (iterationSpaceDims == -1) {
4615 iterationSpaceDims = affineMap.getNumDims();
4616 inOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
4617 outOccurrences = SmallVector<size_t>(iterationSpaceDims, 0);
4618 } else if (iterationSpaceDims != (int)affineMap.getNumDims()) {
4619 return emitError("iteration spaces of provided affine_maps differ");
4620 }
4621
4622 // ... update counts of dims used to access either an input or the output.
4623 for (AffineExpr affineExpr : affineMap.getResults()) {
4624 auto affineDimExpr = dyn_cast<AffineDimExpr>(affineExpr);
4625 if (!affineDimExpr)
4626 llvm_unreachable("affine_map is a projected permutation");
4627
4628 if (isInput)
4629 inOccurrences[affineDimExpr.getPosition()] += 1;
4630 else
4631 outOccurrences[affineDimExpr.getPosition()] += 1;
4632 }
4633
4634 return success();
4635 };
4636
4637 for (auto &&[affineMap, operandType, isInput] :
4638 llvm::zip(getIndexingMapsArray(), getOperandTypes(),
4639 SmallVector<bool>{true, true, false})) {
4640 if (failed(checkAffineMapAndType(affineMap, operandType, isInput)))
4641 return failure(); // NB: checkAffineMapAndType will emit relevant error.
4642 }
4643
4644 bool hasContractingDim = false;
4645 for (size_t dimIndex = 0; dimIndex < (size_t)iterationSpaceDims; dimIndex++) {
4646 size_t inOccCount = inOccurrences[dimIndex];
4647 size_t outOccCount = outOccurrences[dimIndex];
4648
4649 // We have a contracting dim if and only if ...
4650 hasContractingDim |= inOccCount == 2 && outOccCount == 0;
4651
4652 if (inOccCount == 0 && outOccCount == 0)
4653 return emitError() << "iteration space dim at index " << dimIndex
4654 << " not used to access any operand";
4655
4656 // NB: We disallow a dim which occurs for only one input operand and not
4657 // for the output. In terms of einsum semantics such dims have a
4658 // sensible meaning - namely an additional reduction per each such dim.
4659 // By contrast, the ContractionOpInterface does not know about this
4660 // iter type - cf. inferContractionDims' supported dim kinds. Similarly,
4661 // while vector.contract's verifier accepts dims of this kind many of
4662 // its lowerings give up on encountering these dims.
4663 // TODO: Remove following once we have comprehensive support for input-only
4664 // reduction dims, at both the linalg- and vector-dialect levels.
4665 if (inOccCount == 1 && outOccCount != 1)
4666 return emitError()
4667 << "iteration space dim at index " << dimIndex
4668 << " is neither a contracting dim nor of parallel iteration type";
4669 }
4670
4671 if (!hasContractingDim)
4672 return emitError("'indexing_maps' do not specify a contracting dimension");
4673
4674 return success();
4675}
4676
4677LogicalResult ContractOp::fold(FoldAdaptor, SmallVectorImpl<OpFoldResult> &) {
4678 return memref::foldMemRefCast(*this);
4679}
4680
4681void ContractOp::getEffects(
4682 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4683 &effects) {
4684 if (hasPureTensorSemantics())
4685 return;
4686 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4687}
4688
4689Speculation::Speculatability ContractOp::getSpeculatability() {
4690 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4691}
4692
4693//===----------------------------------------------------------------------===//
4694// Implementation of BatchMatmulOp
4695//===----------------------------------------------------------------------===//
4696SmallVector<AffineMap>
4697BatchMatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
4698 AffineExpr d0, d1, d2, d3;
4699 SmallVector<AffineMap> indexingMaps;
4700 bindDims(context, d0, d1, d2, d3);
4701 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d3}, context));
4702 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d3, d2}, context));
4703 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d2}, context));
4704 return indexingMaps;
4705}
4706
4707bool BatchMatmulOp::isDefaultIndexingMaps(Attribute attr) {
4708 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
4709 if (!maps)
4710 return false;
4711 if (maps.size() != 3)
4712 return false;
4713 auto positions = getAffineResultPositions(maps);
4714 if (failed(positions))
4715 return false;
4716 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
4717 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
4718 (*positions)[2] == SmallVector<int64_t>{0, 1, 2};
4719}
4720
4721SmallVector<utils::IteratorType> BatchMatmulOp::getIteratorTypesArray() {
4722 return SmallVector<utils::IteratorType>{
4723 utils::IteratorType::parallel, utils::IteratorType::parallel,
4724 utils::IteratorType::parallel, utils::IteratorType::reduction};
4725}
4726
4727unsigned BatchMatmulOp::getNumRegionArgs() { return 3; }
4728
4729std::string BatchMatmulOp::getLibraryCallName() {
4730 return generateLibraryCallName(getOperation());
4731}
4732
4733/// Check if the op has broadcast and/or transpose semantic. Returns true if
4734/// the user defined indexing maps are not equal to default map.
4735bool BatchMatmulOp::hasUserDefinedMaps() {
4736 SmallVector<AffineMap, 3> defaultMaps =
4737 getDefaultIndexingMaps(this->getContext());
4738 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
4739 return defaultMaps != explicitMaps;
4740}
4741
4742/// Returns true if the given bcastMap map is a valid broadcast map. A valid
4743/// broadcast map must include K dimension.
4744/// TODO: Strict inclusion of K dimension in the broadcast map is not
4745/// necessary for both input matrices simultaneously. We can relax this
4746/// condition to have K dimension for one input matrix map and infer the K
4747/// dimension for other input matrix map from the one already having K
4748/// dimension.
4749bool BatchMatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap, bool isLHS) {
4750 assert(bcastMap.getNumResults() < 3 &&
4751 "Expected less than 3 result dim expr.");
4752 bool isValid = false;
4753 enum Indices { batchPos, mPos, nPos, kPos };
4754 if (bcastMap.getNumResults() == 1) {
4755 AffineExpr expr = bcastMap.getResult(0);
4756 isValid = expr.isFunctionOfDim(kPos);
4757 } else if (bcastMap.getNumResults() == 2) {
4758 AffineExpr expr0 = bcastMap.getResult(0);
4759 AffineExpr expr1 = bcastMap.getResult(1);
4760 isValid =
4761 isLHS ? ((expr0.isFunctionOfDim(batchPos) ||
4762 expr0.isFunctionOfDim(mPos)) &&
4763 expr1.isFunctionOfDim(kPos))
4764 : ((expr0.isFunctionOfDim(batchPos) &&
4765 expr1.isFunctionOfDim(kPos)) ||
4766 (expr0.isFunctionOfDim(kPos) && expr1.isFunctionOfDim(nPos)));
4767 }
4768 return isValid;
4769}
4770
4771void BatchMatmulOp::regionBuilder(
4772 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
4773 function_ref<InFlightDiagnostic()> emitError) {
4774 if (emitError && block.getNumArguments() != 3) {
4775 emitError() << "BatchMatmulOp regionBuilder expects 3 args, got "
4776 << block.getNumArguments();
4777 return;
4778 }
4779 assert(block.getNumArguments() == 3 &&
4780 "BatchMatmulOp regionBuilder expects 3 args");
4781 RegionBuilderHelper helper(b, block);
4782 SmallVector<Value> yields;
4783
4784 TypeFn castVal = TypeFn::cast_signed;
4785 auto castIter = llvm::find_if(attrs, [&](const NamedAttribute &attr) {
4786 return attr.getName() == "cast";
4787 });
4788 if (castIter != attrs.end()) {
4789 if (auto attr = llvm::dyn_cast<TypeFnAttr>(castIter->getValue()))
4790 castVal = attr.getValue();
4791 }
4792
4793 auto toType = block.getArgument(2).getType();
4794 Value castValA = helper.buildTypeFn(castVal, toType, block.getArgument(0));
4795 Value castValB = helper.buildTypeFn(castVal, toType, block.getArgument(1));
4796 Value mulVal =
4797 helper.buildBinaryFn(BinaryFn::mul, castValA, castValB, emitError);
4798 if (!castValA || !castValB || !mulVal)
4799 return;
4800 Value addVal = helper.buildBinaryFn(BinaryFn::add, block.getArgument(2),
4801 mulVal, emitError);
4802 if (!addVal)
4803 return;
4804 yields.push_back(addVal);
4805 helper.yieldOutputs(yields);
4806}
4807
4808ParseResult BatchMatmulOp::parse(OpAsmParser &parser, OperationState &result) {
4809 SmallVector<Attribute, 3> indexingMapsAttr;
4810 Attribute mapAttr;
4811 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
4812 if (parser.parseEqual())
4813 return failure();
4814
4815 if (parser.parseLSquare())
4816 return failure();
4817
4818 do {
4819 if (parser.parseAttribute(mapAttr))
4820 return failure();
4821 if (!isa<AffineMapAttr>(mapAttr)) {
4822 return parser.emitError(parser.getCurrentLocation(),
4823 "expected affine map attribute");
4824 }
4825 indexingMapsAttr.push_back(mapAttr);
4826
4827 if (parser.parseOptionalComma())
4828 break;
4829 } while (true);
4830
4831 if (parser.parseRSquare())
4832 return failure();
4833 }
4834 // Initialize indexingMaps, if not supplied explicitly.
4835 if (indexingMapsAttr.empty()) {
4836 indexingMapsAttr = llvm::map_to_vector(
4837 BatchMatmulOp::getDefaultIndexingMaps(parser.getContext()),
4838 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4839 }
4840 result.addAttribute("indexing_maps",
4841 parser.getBuilder().getArrayAttr(indexingMapsAttr));
4842
4843 return ::parseNamedStructuredOp(parser, result,
4844 BatchMatmulOp::getNumRegionArgs(),
4845 BatchMatmulOp::getRegionBuilder());
4846}
4847
4848void BatchMatmulOp::print(OpAsmPrinter &p) {
4849 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
4850 BatchMatmulOp::getDefaultIndexingMaps(getContext()),
4851 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
4852 if (!llvm::equal(getIndexingMaps(), indexingMaps))
4853 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
4854
4855 std::array<StringRef, 3> elidedAttrs = {
4856 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
4857 ::printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
4858 elidedAttrs);
4859}
4860
4861/// Verify the user defined indexing maps.
4862LogicalResult BatchMatmulOp::verify() {
4863 // Verification of pure batch_matmul is handled by
4864 // verifyStructuredOpInterface().
4865 if (!hasUserDefinedMaps())
4866 return success();
4867
4868 for (unsigned opIndex = 0; opIndex < 3; opIndex++) {
4870 return failure();
4871 }
4872 return success();
4873}
4874
4875LogicalResult BatchMatmulOp::fold(FoldAdaptor,
4876 SmallVectorImpl<OpFoldResult> &) {
4877 return memref::foldMemRefCast(*this);
4878}
4879
4880void BatchMatmulOp::getEffects(
4881 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
4882 &effects) {
4883 if (hasPureTensorSemantics())
4884 return;
4885 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
4886}
4887
4888Speculation::Speculatability BatchMatmulOp::getSpeculatability() {
4889 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
4890}
4891
4892//===----------------------------------------------------------------------===//
4893// ElementwiseOp
4894//===----------------------------------------------------------------------===//
4895//
4896namespace {
4897struct ArityGroupAndKind {
4898 // The enum class {Unary, Binary, Ternary, ..}
4899 ElementwiseArityGroup arityGroup;
4900
4901 // The kind (e.g. `exp` or `add`) belonging to the arity group.
4902 union Kind {
4903 UnaryFn unaryFn;
4904 BinaryFn binaryFn;
4905 TernaryFn ternaryFn;
4906 } kind;
4907};
4908
4909unsigned getArityGroupAsUInt(ElementwiseArityGroup arityGroup) {
4910 return static_cast<unsigned>(arityGroup);
4911}
4912} // namespace
4913
4914static ArityGroupAndKind getArityGroupAndKind(ElementwiseKind kind) {
4915 constexpr int lastUnary = static_cast<int>(ElementwiseCaseLimits::LastUnary);
4916 constexpr int lastBinary =
4917 static_cast<int>(ElementwiseCaseLimits::LastBinary);
4918 constexpr int lastTernary =
4919 static_cast<int>(ElementwiseCaseLimits::LastTernary);
4920
4921 int val = static_cast<int>(kind);
4922 ArityGroupAndKind result;
4923
4924 if (val < lastUnary) {
4925 result.arityGroup = ElementwiseArityGroup::Unary;
4926 result.kind.unaryFn = static_cast<UnaryFn>(val);
4927 return result;
4928 }
4929 if (val < lastBinary) {
4930 result.arityGroup = ElementwiseArityGroup::Binary;
4931 result.kind.binaryFn = static_cast<BinaryFn>(val - lastUnary);
4932 return result;
4933 }
4934 if (val >= lastTernary) {
4935 llvm_unreachable("unhandled ElementwiseFn");
4936 }
4937 result.arityGroup = ElementwiseArityGroup::Ternary;
4938 result.kind.ternaryFn = static_cast<TernaryFn>(val - lastBinary);
4939 return result;
4940}
4941
4942SmallVector<utils::IteratorType> ElementwiseOp::getIteratorTypesArray() {
4943 auto rank = getResultRank();
4944 return SmallVector<utils::IteratorType>(rank, utils::IteratorType::parallel);
4945}
4946
4948ElementwiseOp::getDefaultIndexingMaps(unsigned numMaps, unsigned numDims,
4949 MLIRContext *context) {
4950 auto map = AffineMap::getMultiDimIdentityMap(numDims, context);
4951 return SmallVector<AffineMap>(numMaps, map);
4952}
4953
4954ParseResult ElementwiseOp::parse(OpAsmParser &parser, OperationState &result) {
4955 // Expect e.g. `kind = #linalg.elemwise_kind<add>`
4956 Attribute attr;
4957 mlir::linalg::ElementwiseKind elemwiseKindVal;
4958 if (parser.parseKeyword("kind") || parser.parseEqual())
4959 return failure();
4960
4961 if (succeeded(parser.parseAttribute(attr))) {
4962 auto elemwiseKindAttr = dyn_cast<ElementwiseKindAttr>(attr);
4963 if (!elemwiseKindAttr)
4964 return parser.emitError(parser.getCurrentLocation(),
4965 "expected ElementwiseKind attribute");
4966 elemwiseKindVal = elemwiseKindAttr.getValue();
4967 } else {
4968 return parser.emitError(parser.getCurrentLocation(),
4969 "expected operation 'kind' attribute");
4970 }
4971 result.addAttribute(
4972 "kind", ElementwiseKindAttr::get(parser.getContext(), elemwiseKindVal));
4973
4974 // Parse optional `indexing_maps`
4975 SmallVector<Attribute, 3> indexingMapsAttr;
4976 Attribute mapAttr;
4977 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
4978 if (parser.parseEqual())
4979 return failure();
4980 if (parser.parseLSquare())
4981 return failure();
4982 do {
4983 if (parser.parseAttribute(mapAttr))
4984 return failure();
4985 if (!isa<AffineMapAttr>(mapAttr))
4986 return parser.emitError(parser.getCurrentLocation(),
4987 "expected affine map attribute");
4988 indexingMapsAttr.push_back(mapAttr);
4989 if (parser.parseOptionalComma())
4990 break;
4991 } while (true);
4992 if (parser.parseRSquare())
4993 return failure();
4994 }
4995 // At this stage of parsing the only way to infer number of region
4996 // args is through op kind, as input output tensors are not parsed yet.
4997 auto arityGroupAndKind = getArityGroupAndKind(elemwiseKindVal);
4998 int numRegionArgs =
4999 getArityGroupAsUInt(arityGroupAndKind.arityGroup) + 1 /*output*/;
5000 if (parseNamedStructuredOp(parser, result, numRegionArgs,
5001 ElementwiseOp::getRegionBuilder())) {
5002 return parser.emitError(parser.getCurrentLocation(),
5003 "unable to parse elemwise op");
5004 }
5005
5006 // Initialize indexingMaps, if not supplied explicitly.
5007 if (indexingMapsAttr.empty()) {
5008 // We need to infer the numDims of the indexing maps from the output
5009 // type which is already parsed by now.
5010 auto resultType = result.operands[result.operands.size() - 1].getType();
5011 auto shapedType = llvm::dyn_cast<ShapedType>(resultType);
5012 if (!shapedType)
5013 return parser.emitError(parser.getCurrentLocation(),
5014 "return type needs to be shaped type");
5015 auto numDims = shapedType.getRank();
5016 indexingMapsAttr = llvm::map_to_vector(
5017 ElementwiseOp::getDefaultIndexingMaps(numRegionArgs, numDims,
5018 parser.getContext()),
5019 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
5020 }
5021
5022 result.addAttribute("indexing_maps",
5023 parser.getBuilder().getArrayAttr(indexingMapsAttr));
5024 return success();
5025}
5026
5027void ElementwiseOp::print(OpAsmPrinter &p) {
5028 p << " kind=";
5029 p.printAttribute(getKindAttr());
5030 SmallVector<StringRef, 3> elidedAttrs = {"operandSegmentSizes", "kind",
5031 "indexing_maps"};
5032 unsigned arity =
5033 getArityGroupAsUInt(getArityGroupAndKind(getKind()).arityGroup);
5034 unsigned numDims = getResultRank();
5035
5036 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector<3>(
5037 ElementwiseOp::getDefaultIndexingMaps(arity + 1 /*output*/, numDims,
5038 getContext()),
5039 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
5040
5041 if (!llvm::equal(getIndexingMaps(), indexingMaps))
5042 p << " indexing_maps = " << llvm::interleaved_array(getIndexingMaps());
5043
5044 printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
5045 elidedAttrs);
5046}
5047
5048/// Implements the block region builder for the ElementwiseOp. This is called by
5049/// 'fillStructuredOpRegion'.
5050void ElementwiseOp::regionBuilder(
5051 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
5052 function_ref<InFlightDiagnostic()> emitError) {
5053 ElementwiseKind elemwiseKind;
5054 for (auto attr : attrs) {
5055 if (attr.getName() == b.getStringAttr("kind")) {
5056 auto kindAttr = dyn_cast<ElementwiseKindAttr>(attr.getValue());
5057 assert(kindAttr && "op kind attribute incorrectly set");
5058 elemwiseKind = kindAttr.getValue();
5059 break;
5060 }
5061 }
5062
5063 ArityGroupAndKind groupAndKind = getArityGroupAndKind(elemwiseKind);
5064 auto arityGroup = groupAndKind.arityGroup;
5065 auto kind = groupAndKind.kind;
5066 if (emitError && block.getNumArguments() !=
5067 getArityGroupAsUInt(arityGroup) + 1 /*output*/) {
5068 emitError() << "Elementwise regionBuilder expects "
5069 << (getArityGroupAsUInt(arityGroup) + 1) << " args, got "
5070 << block.getNumArguments();
5071 return;
5072 }
5073 assert(block.getNumArguments() ==
5074 getArityGroupAsUInt(arityGroup) + 1 /*output*/
5075 && "Elementwise regionBuilder number of block args mismatch");
5076
5077 RegionBuilderHelper helper(b, block);
5078 SmallVector<Value> yields;
5079 Value result;
5080
5081 if (arityGroup == ElementwiseArityGroup::Unary) {
5082 result = helper.buildUnaryFn(kind.unaryFn, block.getArgument(0));
5083
5084 } else if (arityGroup == ElementwiseArityGroup::Binary) {
5085 result = helper.buildBinaryFn(kind.binaryFn, block.getArgument(0),
5086 block.getArgument(1));
5087
5088 } else if (arityGroup == ElementwiseArityGroup::Ternary) {
5089 result = helper.buildTernaryFn(kind.ternaryFn, block.getArgument(0),
5090 block.getArgument(1), block.getArgument(2));
5091
5092 } else {
5093 assert(false && "found unhandled category in elemwise");
5094 }
5095
5096 yields.push_back(result);
5097 helper.yieldOutputs(yields);
5098}
5099
5100LogicalResult ElementwiseOp::fold(FoldAdaptor,
5101 SmallVectorImpl<OpFoldResult> &) {
5102 return memref::foldMemRefCast(*this);
5103}
5104
5105void ElementwiseOp::getEffects(
5106 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
5107 &effects) {
5108 if (hasPureTensorSemantics())
5109 return;
5110 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
5111}
5112
5113Speculation::Speculatability ElementwiseOp::getSpeculatability() {
5114 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
5115}
5116
5117//===----------------------------------------------------------------------===//
5118// PackOp/UnPackOp Common
5119//===----------------------------------------------------------------------===//
5120
5121template <typename OpTy, typename>
5122SmallVector<int64_t>
5124 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5125 ? packOrUnPack.getDestType()
5126 : packOrUnPack.getSourceType();
5127 ShapedType unpackedType = (std::is_same<OpTy, PackOp>::value)
5128 ? packOrUnPack.getSourceType()
5129 : packOrUnPack.getDestType();
5131 packedType.getShape().take_front(unpackedType.getRank()));
5132 if (!packOrUnPack.getOuterDimsPerm().empty()) {
5134 result, invertPermutationVector(packOrUnPack.getOuterDimsPerm()));
5135 }
5136 return result;
5137}
5142
5143// Given the (potentially) updated packed type, `newPackedTy`, generates an
5144// updated mixed-tile-sizes list. For each inner packed dimension that is static
5145// in `newPackedTy`, the tile is set to that static size (replacing SSA values
5146// or mismatched constants). Dynamic packed dimensions preserve the original
5147// tile. The folded tensor type is treated as authoritative for static extents.
5148// Note - packed-type-dim and mixed-tile-size should always match!
5151 ArrayRef<OpFoldResult> mixedTiles) {
5152 SmallVector<OpFoldResult> newMixedTileSizes;
5153 for (auto it : llvm::zip(cast<ShapedType>(newPackedTy)
5154 .getShape()
5155 .take_back(mixedTiles.size()),
5156 mixedTiles)) {
5157 int64_t dimSize = std::get<0>(it);
5158 if (dimSize == ShapedType::kDynamic) {
5159 newMixedTileSizes.push_back(std::get<1>(it));
5160 continue;
5161 }
5162 newMixedTileSizes.push_back(rewriter.getIndexAttr(dimSize));
5163 }
5164
5165 return newMixedTileSizes;
5166}
5167
5168template <typename OpTy>
5169static LogicalResult
5171 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
5172 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5173 "applies to only pack or unpack operations");
5174 int64_t destRank = op.getDestRank();
5175 reifiedReturnShapes.resize(1, SmallVector<OpFoldResult>(destRank));
5176 for (auto dim : llvm::seq<int64_t>(0, destRank))
5177 reifiedReturnShapes[0][dim] =
5178 createFoldedDimOp(builder, op.getLoc(), op.getDest(), dim);
5179 return success();
5180}
5181
5182template <typename OpTy>
5184 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5185 "applies to only pack or unpack operations");
5186 DenseMap<int64_t, OpFoldResult> dimAndTileMapping;
5187 ArrayRef<int64_t> dimsToTile = op.getInnerDimsPos();
5188 SmallVector<OpFoldResult> tiles = op.getMixedTiles();
5189 assert(tiles.size() == dimsToTile.size() &&
5190 "tiles must match indices of dimension to block");
5191 // bind the dimension `i` with the tile factor.
5192 for (auto i : llvm::seq<int64_t>(0, dimsToTile.size()))
5193 dimAndTileMapping[dimsToTile[i]] = tiles[i];
5194 return dimAndTileMapping;
5195}
5196
5197template <typename OpTy>
5199 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5200 "applies to only pack or unpack operations");
5201 Builder builder(op);
5202 SmallVector<OpFoldResult> mixedInnerTiles;
5203 unsigned dynamicValIndex = 0;
5204 for (int64_t staticTile : op.getStaticInnerTiles()) {
5205 if (ShapedType::isStatic(staticTile))
5206 mixedInnerTiles.push_back(builder.getI64IntegerAttr(staticTile));
5207 else
5208 mixedInnerTiles.push_back(op.getInnerTiles()[dynamicValIndex++]);
5209 }
5210 return mixedInnerTiles;
5211}
5212
5213template <typename OpTy>
5215 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5216 "applies to only pack or unpack operations");
5217 SmallVector<Value> dynamicTiles;
5218 SmallVector<int64_t> staticTiles;
5219 dispatchIndexOpFoldResults(op.getMixedTiles(), dynamicTiles, staticTiles);
5220 return staticTiles;
5221}
5222
5223/// Returns true if `dimsPos` is invalid. It is invalid when:
5224/// a) It contains duplicate.
5225/// b) At least one dimension is out of bound (`dimPos` is >= 0 and < rank).
5226/// c) The number of elements in `dimsPos` is > than `rank`.
5228 size_t rank) {
5229 size_t dimsPosSize = dimsPos.size();
5230 if (dimsPosSize > rank)
5231 return true;
5232 DenseSet<int64_t> uniqued(llvm::from_range, dimsPos);
5233 if (dimsPosSize != uniqued.size())
5234 return true;
5235 return llvm::any_of(dimsPos, [rank](int64_t dimPos) {
5236 return dimPos < 0 || dimPos >= static_cast<int64_t>(rank);
5237 });
5238}
5239
5240template <typename OpTy>
5241static LogicalResult commonVerifierPackAndUnPackOp(OpTy packOrUnPack) {
5242 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5243 "applies to only pack or unpack operations");
5244 Operation *op = packOrUnPack.getOperation();
5245
5246 // Return true if we have a zero-value tile.
5247 auto hasZeros = [&](ArrayRef<OpFoldResult> tiles) {
5248 return llvm::any_of(tiles, [](OpFoldResult tile) {
5249 return isa<Attribute>(tile) && isZeroInteger(tile);
5250 });
5251 };
5252
5253 // Verify that the source and destination are ranked types.
5254 if (!packOrUnPack.getSourceType().hasRank() ||
5255 !packOrUnPack.getDestType().hasRank())
5256 return op->emitError("expected both source and destination to have rank");
5257
5258 // Verify that the Operation does not have mixed tensor/buffer semantics.
5259 if (!packOrUnPack.hasPureBufferSemantics() &&
5260 !packOrUnPack.hasPureTensorSemantics())
5261 return op->emitError("mixing tensor and buffer semantics is not allowed");
5262 const unsigned numResults = packOrUnPack.getNumResults();
5263 if (packOrUnPack.hasPureTensorSemantics() && numResults != 1)
5264 return op->emitError("expected 1 result, got ") << numResults;
5265 if (packOrUnPack.hasPureBufferSemantics() && numResults != 0)
5266 return op->emitError("expected 0 results, got ") << numResults;
5267
5268 // Verify tiles. Do not allow zero tiles.
5269 SmallVector<OpFoldResult> mixedTiles = packOrUnPack.getMixedTiles();
5270 if (hasZeros(mixedTiles))
5271 return op->emitError("invalid zero tile factor");
5272
5273 // Verify inner_dims_pos and outer_dims_perm.
5274 ShapedType unpackedType = (std::is_same<OpTy, PackOp>::value)
5275 ? packOrUnPack.getSourceType()
5276 : packOrUnPack.getDestType();
5277 size_t unpackedRank = unpackedType.getRank();
5278 ArrayRef<int64_t> innerDimsPos = packOrUnPack.getInnerDimsPos();
5279 ArrayRef<int64_t> outerDimPerm = packOrUnPack.getOuterDimsPerm();
5280 if (isInvalidPackingPosSpecification(innerDimsPos, unpackedRank))
5281 return op->emitError("invalid inner_dims_pos vector");
5282 if (isInvalidPackingPosSpecification(outerDimPerm, unpackedRank))
5283 return op->emitError("invalid outer_dims_perm vector");
5284 if (!outerDimPerm.empty() && outerDimPerm.size() != unpackedRank)
5285 return op->emitError("outer_dims_perm must be a permutation or empty");
5286
5287 // Tiling factors must be less than or equal to the input rank for pack (or
5288 // output rank for unpack), and must match the number of `inner_dims_pos`.
5289 if (mixedTiles.size() > unpackedRank) {
5290 return op->emitError("tiling factors must be less than or equal to the "
5291 "input rank for pack or output rank for unpack");
5292 }
5293 if (mixedTiles.size() != innerDimsPos.size()) {
5294 return op->emitError(
5295 "tiling factors must equal the number of dimensions to tile");
5296 }
5297
5298 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5299 ? packOrUnPack.getDestType()
5300 : packOrUnPack.getSourceType();
5301 size_t packedRank = packedType.getRank();
5302 // Require output rank to match input rank + number of blocking factors.
5303 size_t expectedPackedRank = unpackedRank + mixedTiles.size();
5304 if (expectedPackedRank != packedRank) {
5305 return op->emitError(
5306 "packed rank != (unpacked rank + num tiling factors), got ")
5307 << packedRank << " != " << expectedPackedRank;
5308 }
5309
5310 // Verify result shape is greater than the minimum expected
5311 // by the pack operation, and that the output shape
5312 // represents full tiles.
5313 SmallVector<int64_t> expectedPackedShape = PackOp::inferPackedShape(
5314 unpackedType.getShape(), packOrUnPack.getStaticTiles(),
5315 packOrUnPack.getInnerDimsPos(), packOrUnPack.getOuterDimsPerm());
5316 for (auto it : llvm::enumerate(llvm::zip(
5317 packedType.getShape().take_back(mixedTiles.size()), mixedTiles))) {
5318 int64_t dimSize = std::get<0>(it.value());
5319 if (Attribute attr =
5320 llvm::dyn_cast_if_present<Attribute>(std::get<1>(it.value()))) {
5321 IntegerAttr intAttr = dyn_cast_or_null<IntegerAttr>(attr);
5322 int64_t staticTileSize = intAttr.getValue().getSExtValue();
5323 if (dimSize != staticTileSize)
5324 return op->emitError(
5325 "mismatch in inner tile sizes specified and shaped of "
5326 "tiled dimension in the packed type at index ")
5327 << it.index() << ": got " << dimSize << " != " << staticTileSize;
5328 } else if (!ShapedType::isDynamic(dimSize)) {
5329 return op->emitError("mismatch in inner tile sizes specified at index ")
5330 << it.index() << ": got static shape " << dimSize
5331 << " but dynamic tile size";
5332 }
5333 }
5334 if (failed(
5335 verifyCompatibleShape(expectedPackedShape, packedType.getShape()))) {
5336 auto elementType = unpackedType.getElementType();
5337 Type expectedType, actualType;
5338 if (packOrUnPack.hasPureTensorSemantics()) {
5339 expectedType = RankedTensorType::get(expectedPackedShape, elementType);
5340 actualType = RankedTensorType::get(packedType.getShape(), elementType);
5341 } else {
5342 expectedType = MemRefType::get(expectedPackedShape, elementType);
5343 actualType = MemRefType::get(packedType.getShape(), elementType);
5344 }
5345 return op->emitError("expected ")
5346 << expectedType << " for the packed domain value, got "
5347 << actualType;
5348 }
5349 return success();
5350}
5351
5352namespace {
5353/// Subset of PackOp/UnPackOp fields used to compute the result of applying
5354/// various permutations to the op.
5355// TODO: Add linalg.transpose + pack/unpack folding patterns that just reuse
5356// these. These may or may not become true foldings / canonicalizations
5357// depending on how aggressive we want to be in automatically folding
5358// transposes.
5359struct PackOrUnPackTransposeResult {
5360 SmallVector<int64_t> innerDimsPos;
5361 SmallVector<OpFoldResult> innerTiles;
5362 SmallVector<int64_t> outerDimsPerm;
5363};
5364} // namespace
5365
5366template <typename OpTy>
5367static PackOrUnPackTransposeResult
5369 ArrayRef<int64_t> innerPermutation,
5370 ArrayRef<int64_t> outerPermutation) {
5371 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5372 "applies to only pack or unpack operations");
5373 assert((!innerPermutation.empty() || !outerPermutation.empty()) &&
5374 "some permutation must be non-empty");
5375 PackOrUnPackTransposeResult metadata;
5376 metadata.innerDimsPos =
5377 SmallVector<int64_t>(packOrUnPackOp.getInnerDimsPos());
5378 metadata.innerTiles =
5379 SmallVector<OpFoldResult>(packOrUnPackOp.getMixedTiles());
5380 int64_t numOuterDims = std::is_same<OpTy, PackOp>::value
5381 ? packOrUnPackOp.getSourceRank()
5382 : packOrUnPackOp.getDestRank();
5383 metadata.outerDimsPerm =
5384 packOrUnPackOp.getOuterDimsPerm().empty()
5385 ? llvm::to_vector(llvm::seq<int64_t>(0, numOuterDims))
5386 : SmallVector<int64_t>(packOrUnPackOp.getOuterDimsPerm());
5387 if (!innerPermutation.empty()) {
5388 assert(innerPermutation.size() == metadata.innerDimsPos.size() &&
5389 isPermutationVector(innerPermutation) &&
5390 "invalid inner permutation");
5391 applyPermutationToVector(metadata.innerDimsPos, innerPermutation);
5392 applyPermutationToVector(metadata.innerTiles, innerPermutation);
5393 }
5394 if (!outerPermutation.empty()) {
5395 assert(outerPermutation.size() == metadata.outerDimsPerm.size() &&
5396 isPermutationVector(outerPermutation) &&
5397 "invalid outer permutation");
5398 applyPermutationToVector(metadata.outerDimsPerm, outerPermutation);
5399 }
5400 return metadata;
5401}
5402
5403//===----------------------------------------------------------------------===//
5404// PackOp
5405//===----------------------------------------------------------------------===//
5406
5407void PackOp::getAsmResultNames(function_ref<void(Value, StringRef)> setNameFn) {
5408 if (!getResults().empty())
5409 setNameFn(getResult(), "pack");
5410}
5411
5412ParseResult PackOp::parse(OpAsmParser &parser, OperationState &result) {
5413 OpAsmParser::UnresolvedOperand source, dest;
5416 SmallVector<Type> paddingValueType;
5417 SmallVector<int64_t> staticTiles;
5418 DenseI64ArrayAttr innerDimsPos, outerDimsPerm;
5419 Type sourceType, destType, resultType;
5420
5421 if (parser.parseOperand(source))
5422 return failure();
5423
5424 if (succeeded(parser.parseOptionalKeyword("padding_value"))) {
5425 if (parser.parseLParen() ||
5426 parser.parseOperandList(paddingValue, /*requiredOperandCount=*/1) ||
5427 parser.parseColon() || parser.parseTypeList(paddingValueType) ||
5428 parser.parseRParen())
5429 return failure();
5430 }
5431
5432 if (succeeded(parser.parseOptionalKeyword("outer_dims_perm"))) {
5433 if (parser.parseEqual())
5434 return failure();
5435
5436 SmallVector<int64_t> outerDimsPermVec;
5438 int64_t value;
5439 if (parser.parseInteger(value))
5440 return failure();
5441 outerDimsPermVec.push_back(value);
5442 return success();
5443 }))
5444 return failure();
5445 outerDimsPerm = parser.getBuilder().getDenseI64ArrayAttr(outerDimsPermVec);
5446 }
5447
5448 if (parser.parseKeyword("inner_dims_pos") || parser.parseEqual())
5449 return failure();
5450
5451 SmallVector<int64_t> innerDimsPosVec;
5453 int64_t value;
5454 if (parser.parseInteger(value))
5455 return failure();
5456 innerDimsPosVec.push_back(value);
5457 return success();
5458 }))
5459 return failure();
5460 innerDimsPos = parser.getBuilder().getDenseI64ArrayAttr(innerDimsPosVec);
5461
5462 if (parser.parseKeyword("inner_tiles") || parser.parseEqual())
5463 return failure();
5464
5465 DenseI64ArrayAttr staticTilesAttr;
5466 if (parseDynamicIndexList(parser, dynamicTiles, staticTilesAttr))
5467 return failure();
5468 for (auto val : staticTilesAttr.asArrayRef())
5469 staticTiles.push_back(val);
5470
5471 if (parser.parseKeyword("into") || parser.parseOperand(dest))
5472 return failure();
5473
5474 if (parser.parseOptionalAttrDict(result.attributes))
5475 return failure();
5476
5477 if (parser.parseColon() || parser.parseType(sourceType))
5478 return failure();
5479
5480 bool hasArrow = succeeded(parser.parseOptionalArrow());
5481 if (hasArrow) {
5482 if (parser.parseType(destType))
5483 return failure();
5484 }
5485
5486 bool isMemRef = llvm::isa<MemRefType>(sourceType);
5487 if (!hasArrow) {
5488 return parser.emitError(parser.getCurrentLocation(),
5489 "pack/unpack requires '->' and destination type");
5490 }
5491
5492 if (!isMemRef)
5493 resultType = destType;
5494
5495 if (parser.resolveOperand(source, sourceType, result.operands) ||
5496 parser.resolveOperand(dest, destType, result.operands))
5497 return failure();
5498
5499 if (!paddingValue.empty() &&
5500 parser.resolveOperands(paddingValue, paddingValueType[0],
5501 result.operands))
5502 return failure();
5503
5504 if (!dynamicTiles.empty() &&
5505 parser.resolveOperands(dynamicTiles, parser.getBuilder().getIndexType(),
5506 result.operands))
5507 return failure();
5508
5509 result.addAttribute("static_inner_tiles",
5510 parser.getBuilder().getDenseI64ArrayAttr(staticTiles));
5511 result.addAttribute("inner_dims_pos", innerDimsPos);
5512 if (outerDimsPerm)
5513 result.addAttribute("outer_dims_perm", outerDimsPerm);
5514
5515 SmallVector<int32_t> segmentSizes = {
5516 1, 1, static_cast<int32_t>(paddingValue.size()),
5517 static_cast<int32_t>(dynamicTiles.size())};
5518 result.addAttribute("operandSegmentSizes",
5519 parser.getBuilder().getDenseI32ArrayAttr(segmentSizes));
5520
5521 if (!isMemRef)
5522 result.addTypes(resultType);
5523
5524 return success();
5525}
5526
5527void PackOp::print(OpAsmPrinter &p) {
5528 p << " " << getSource();
5529
5530 if (getPaddingValue()) {
5531 p << " padding_value(" << getPaddingValue() << " : "
5532 << getPaddingValue().getType() << ")";
5533 }
5534
5535 if (!getOuterDimsPerm().empty()) {
5536 p << " outer_dims_perm = [";
5537 llvm::interleaveComma(getOuterDimsPerm(), p);
5538 p << "]";
5539 }
5540
5541 p << " inner_dims_pos = [";
5542 llvm::interleaveComma(getInnerDimsPos(), p);
5543 p << "]";
5544
5545 p << " inner_tiles = ";
5546 printDynamicIndexList(p, *this, getInnerTiles(), getStaticInnerTilesAttr());
5547
5548 p << " into " << getDest();
5549
5550 p.printOptionalAttrDict((*this)->getAttrs(),
5551 {"static_inner_tiles", "inner_dims_pos",
5552 "outer_dims_perm", "operandSegmentSizes"});
5553
5554 p << " : " << getSource().getType();
5555 p << " -> " << getDest().getType();
5556}
5557
5558void PackOp::build(OpBuilder &builder, OperationState &state, Value source,
5559 Value dest, ArrayRef<int64_t> innerDimsPos,
5560 ArrayRef<OpFoldResult> innerTiles,
5561 std::optional<Value> paddingValue,
5562 ArrayRef<int64_t> outerDimsPerm) {
5563 assert(innerDimsPos.size() == innerTiles.size() &&
5564 "number of tile sizes specified must match the specified number of "
5565 "original dimensions to be tiled");
5566 SmallVector<int64_t> staticTileSizes;
5567 SmallVector<Value> dynamicTileSizes;
5568 dispatchIndexOpFoldResults(innerTiles, dynamicTileSizes, staticTileSizes);
5569 build(builder, state, dest.getType(), source, dest,
5570 paddingValue ? *paddingValue : nullptr,
5571 outerDimsPerm.empty() ? nullptr
5572 : builder.getDenseI64ArrayAttr(outerDimsPerm),
5573 builder.getDenseI64ArrayAttr(innerDimsPos), dynamicTileSizes,
5574 builder.getDenseI64ArrayAttr(staticTileSizes));
5575}
5576
5577LogicalResult
5578PackOp::reifyResultShapes(OpBuilder &builder,
5579 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
5580 return reifyResultShapesImpl(*this, builder, reifiedReturnShapes);
5581}
5582
5583DenseMap<int64_t, OpFoldResult> PackOp::getDimAndTileMapping() {
5584 return getDimAndTileMappingImpl(*this);
5585}
5586
5587SmallVector<OpFoldResult> PackOp::getMixedTiles() {
5588 return getMixedTilesImpl(*this);
5589}
5590
5591SmallVector<int64_t> PackOp::getStaticTiles() {
5592 return getStaticTilesImpl(*this);
5593}
5594
5595ArrayRef<int64_t> PackOp::getAllOuterDims() {
5596 ShapedType inputType = getSourceType();
5597 int64_t inputRank = inputType.getRank();
5598 return getDestType().getShape().take_front(inputRank);
5599}
5600
5601SmallVector<int64_t> PackOp::getTiledOuterDims() {
5602 auto innerDimsPos = getInnerDimsPos();
5603 SmallVector<int64_t> outerDims(getAllOuterDims());
5604 SmallVector<int64_t> res;
5605
5606 // Recover the original order of the outer dims.
5607 SmallVector<int64_t> outerDimPermInv(getOuterDimsPerm());
5608 invertPermutationVector(outerDimPermInv);
5609 if (!outerDimPermInv.empty())
5610 applyPermutationToVector(outerDims, outerDimPermInv);
5611
5612 // Collect the outer dims corresponding to the tilled inner dims.
5613 for (auto index : innerDimsPos)
5614 res.push_back(outerDims[index]);
5615
5616 return res;
5617}
5618
5619bool PackOp::requirePaddingValue(ArrayRef<int64_t> inputShape,
5620 ArrayRef<int64_t> innerDimsPos,
5621 ArrayRef<int64_t> outputShape,
5622 ArrayRef<int64_t> outerDimsPerm,
5623 ArrayRef<OpFoldResult> innerTiles) {
5624 SmallVector<int64_t> outputTileSizes(
5625 outputShape.take_front(inputShape.size()));
5626 if (!outerDimsPerm.empty()) {
5627 assert(outerDimsPerm.size() == outputTileSizes.size() &&
5628 "expected output and outer_dims_perm to have same size");
5629 applyPermutationToVector(outputTileSizes,
5630 invertPermutationVector(outerDimsPerm));
5631 }
5632 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5633 if (ShapedType::isDynamic(inputShape[pos]))
5634 continue;
5635 std::optional<int64_t> constantTile = getConstantIntValue(tileSize);
5636 if (!constantTile) {
5637 if (ShapedType::isStatic(outputTileSizes[pos]) &&
5638 (inputShape[pos] % outputTileSizes[pos] != 0))
5639 return true;
5640 } else {
5641 assert(*constantTile != 0 && "static tile size can't be zero");
5642 if (inputShape[pos] % (*constantTile) != 0) {
5643 return true;
5644 }
5645 }
5646 }
5647 return false;
5648}
5649
5650bool PackOp::requirePaddingValueStrict(ArrayRef<int64_t> inputShape,
5651 ArrayRef<int64_t> innerDimsPos,
5652 ArrayRef<int64_t> outputShape,
5653 ArrayRef<int64_t> outerDimsPerm,
5654 ArrayRef<OpFoldResult> innerTiles) {
5655 SmallVector<int64_t> outputTileSizes(
5656 outputShape.take_front(inputShape.size()));
5657 if (!outerDimsPerm.empty()) {
5658 assert(outerDimsPerm.size() == outputTileSizes.size() &&
5659 "expected output and outer_dims_perm to have same size");
5660 applyPermutationToVector(outputTileSizes,
5661 invertPermutationVector(outerDimsPerm));
5662 }
5663 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5664 if (ShapedType::isDynamic(inputShape[pos]) ||
5665 ShapedType::isDynamic(outputTileSizes[pos]))
5666 return true;
5667 std::optional<int64_t> constantTile = getConstantIntValue(tileSize);
5668 if (!constantTile)
5669 return true;
5670 assert(*constantTile != 0 && "static tile size can't be zero");
5671 if (inputShape[pos] % (*constantTile) != 0)
5672 return true;
5673 }
5674 return false;
5675}
5676
5677LogicalResult PackOp::verify() {
5679 return failure();
5680
5681 // Verify padding value, and bail out if the tile does not divide the
5682 // dimension fully. In the case of dynamic tile factors or dimensions, having
5683 // a partial tile is undefined behavior.
5684 auto paddingValue = getPaddingValue();
5685 if (paddingValue &&
5686 paddingValue.getType() != getSourceType().getElementType()) {
5687 return emitOpError("expected padding_value has ")
5688 << getSourceType().getElementType()
5689 << " but got: " << paddingValue.getType();
5690 }
5691
5692 if (!paddingValue &&
5693 requirePaddingValue(getSourceType().getShape(), getInnerDimsPos(),
5694 getDestType().getShape(), getOuterDimsPerm(),
5695 getMixedTiles())) {
5696 return emitOpError(
5697 "invalid tile factor or output size provided. Only full tiles are "
5698 "supported when padding_value is not set");
5699 }
5700 return success();
5701}
5702
5703/// Converts OpFoldResults to int64_t shape entries, unconditionally mapping all
5704/// Value's to kDynamic, even if they are arith.constant values.
5705static SmallVector<int64_t>
5708 for (auto o : ofrs) {
5709 // Have to do this first, as getConstantIntValue special-cases constants.
5710 if (llvm::dyn_cast_if_present<Value>(o))
5711 result.push_back(ShapedType::kDynamic);
5712 else
5713 result.push_back(getConstantIntValue(o).value_or(ShapedType::kDynamic));
5714 }
5715 return result;
5716}
5717
5718SmallVector<int64_t> PackOp::inferPackedShape(ArrayRef<int64_t> inputShape,
5719 ArrayRef<int64_t> innerTileSizes,
5720 ArrayRef<int64_t> innerDimsPos,
5721 ArrayRef<int64_t> outerDimsPerm) {
5722 SmallVector<int64_t> resultShape = llvm::to_vector(inputShape);
5723 for (auto tiledDim : llvm::enumerate(llvm::to_vector(innerDimsPos))) {
5724 if (ShapedType::isDynamic(resultShape[tiledDim.value()]))
5725 continue;
5726 if (ShapedType::isDynamic(innerTileSizes[tiledDim.index()])) {
5727 resultShape[tiledDim.value()] = ShapedType::kDynamic;
5728 continue;
5729 }
5730 resultShape[tiledDim.value()] = llvm::divideCeilSigned(
5731 resultShape[tiledDim.value()], innerTileSizes[tiledDim.index()]);
5732 }
5733
5734 // Swap tile loops if outer_dims_perm is available.
5735 if (!outerDimsPerm.empty())
5736 applyPermutationToVector(resultShape, outerDimsPerm);
5737
5738 // Append the inner tile dimensions.
5739 resultShape.append(innerTileSizes.begin(), innerTileSizes.end());
5740 return resultShape;
5741}
5742
5743SmallVector<OpFoldResult> PackOp::getResultShape(
5744 OpBuilder &builder, Location loc, ArrayRef<OpFoldResult> sourceDims,
5745 ArrayRef<OpFoldResult> innerTileSizes, ArrayRef<int64_t> innerDimsPos,
5746 ArrayRef<int64_t> outerDimsPerm) {
5747 SmallVector<OpFoldResult> resultDims = llvm::to_vector(sourceDims);
5748
5749 AffineExpr s0, s1;
5750 bindSymbols(builder.getContext(), s0, s1);
5751 AffineExpr ceilDivExpr = s0.ceilDiv(s1);
5752 for (auto tiledDim : llvm::enumerate(llvm::to_vector(innerDimsPos))) {
5753 resultDims[tiledDim.value()] = affine::makeComposedFoldedAffineApply(
5754 builder, loc, ceilDivExpr,
5755 {resultDims[tiledDim.value()], innerTileSizes[tiledDim.index()]});
5756 }
5757 if (!outerDimsPerm.empty())
5758 applyPermutationToVector(resultDims, outerDimsPerm);
5759 resultDims.append(innerTileSizes.begin(), innerTileSizes.end());
5760
5761 SmallVector<int64_t> resultTypeShape =
5762 inferPackedShape(asShapeWithAnyValueAsDynamic(sourceDims),
5763 asShapeWithAnyValueAsDynamic(innerTileSizes),
5764 innerDimsPos, outerDimsPerm);
5765
5766 // Fix-up `resultDims` to ensure that they are Value's if and only if the
5767 // result type shape says it's a dynamic dim. This is needed as callers may
5768 // use dispatchIndexOpFoldResults on the result, and rely on exact number of
5769 // dynamic dims returned by that.
5770 for (unsigned i = 0; i < resultDims.size(); ++i) {
5771 if (ShapedType::isStatic(resultTypeShape[i]))
5772 continue;
5773 resultDims[i] =
5774 getValueOrCreateConstantIndexOp(builder, loc, resultDims[i]);
5775 }
5776
5777 return resultDims;
5778}
5779
5780RankedTensorType PackOp::inferPackedTensorType(
5781 RankedTensorType sourceType, ArrayRef<int64_t> innerTileSizes,
5782 ArrayRef<int64_t> innerDimsPos, ArrayRef<int64_t> outerDimsPerm) {
5783 SmallVector<int64_t> resultShape = inferPackedShape(
5784 sourceType.getShape(), innerTileSizes, innerDimsPos, outerDimsPerm);
5785 return RankedTensorType::get(resultShape, sourceType.getElementType());
5786}
5787
5788MemRefType PackOp::inferPackedMemRefType(MemRefType sourceType,
5789 ArrayRef<int64_t> innerTileSizes,
5790 ArrayRef<int64_t> innerDimsPos,
5791 ArrayRef<int64_t> outerDimsPerm) {
5792 SmallVector<int64_t> resultShape = inferPackedShape(
5793 sourceType.getShape(), innerTileSizes, innerDimsPos, outerDimsPerm);
5794 return MemRefType::get(resultShape, sourceType.getElementType());
5795}
5796
5797Value PackOp::createDestinationTensor(OpBuilder &b, Location loc, Value source,
5798 ArrayRef<OpFoldResult> innerTileSizes,
5799 ArrayRef<int64_t> innerDimsPos,
5800 ArrayRef<int64_t> outerDimsPerm) {
5801 AffineExpr dim0, dim1;
5802 bindDims(b.getContext(), dim0, dim1);
5803 auto ceilDiv = [&](OpFoldResult v1, OpFoldResult v2) -> OpFoldResult {
5804 return affine::makeComposedFoldedAffineApply(b, loc, dim0.ceilDiv(dim1),
5805 {v1, v2});
5806 };
5807
5808 SmallVector<OpFoldResult> mixedSizes;
5809 for (auto [index, value] : llvm::enumerate(
5810 llvm::cast<RankedTensorType>(source.getType()).getShape())) {
5811 if (ShapedType::isDynamic(value))
5812 mixedSizes.push_back(
5813 tensor::DimOp::create(b, loc, source, index).getResult());
5814 else
5815 mixedSizes.push_back(b.getIndexAttr(value));
5816 }
5817 for (auto it : llvm::zip(innerDimsPos, innerTileSizes)) {
5818 int64_t dimPos = std::get<0>(it);
5819 OpFoldResult tileSize = std::get<1>(it);
5820 mixedSizes[dimPos] = ceilDiv(mixedSizes[dimPos], tileSize);
5821 }
5822 if (!outerDimsPerm.empty())
5823 applyPermutationToVector<OpFoldResult>(mixedSizes, outerDimsPerm);
5824
5825 mixedSizes.append(innerTileSizes.begin(), innerTileSizes.end());
5826 auto elemType = llvm::cast<ShapedType>(source.getType()).getElementType();
5827 return tensor::EmptyOp::create(b, loc, mixedSizes, elemType);
5828}
5829
5830PackOp PackOp::createTransposedClone(OpBuilder &b, Location loc,
5831 ArrayRef<int64_t> innerPermutation,
5832 ArrayRef<int64_t> outerPermutation) {
5833 PackOrUnPackTransposeResult metadata = commonPermutationOfPackAndUnPackOp(
5834 *this, innerPermutation, outerPermutation);
5835 Value transposedDest =
5836 createDestinationTensor(b, loc, getSource(), metadata.innerTiles,
5837 metadata.innerDimsPos, metadata.outerDimsPerm);
5838 return PackOp::create(b, loc, getSource(), transposedDest,
5839 metadata.innerDimsPos, metadata.innerTiles,
5840 getPaddingValue(), metadata.outerDimsPerm);
5841}
5842
5843template <typename OpTy>
5846 &effects) {
5847 // No memory effects for pure tensor semantics
5848 if (op.hasPureTensorSemantics())
5849 return;
5850
5851 for (OpOperand &opOperand : op.getOperation()->getOpOperands()) {
5852 if (!llvm::isa<MemRefType>(opOperand.get().getType()))
5853 continue;
5854
5855 if (&opOperand == &op.getSourceMutable()) {
5856 effects.emplace_back(MemoryEffects::Read::get(), &opOperand, /*stage=*/0,
5857 /*effectOnFullRegion=*/true,
5859 } else if (&opOperand == &op.getDestMutable()) {
5860 effects.emplace_back(MemoryEffects::Read::get(), &opOperand, /*stage=*/0,
5861 /*effectOnFullRegion=*/true,
5863 effects.emplace_back(MemoryEffects::Write::get(), &opOperand, /*stage=*/0,
5864 /*effectOnFullRegion=*/true,
5866 }
5867 }
5868}
5869
5870void PackOp::getEffects(
5872 &effects) {
5873 getPackUnPackEffectsImpl(*this, effects);
5874}
5875
5876void UnPackOp::getEffects(
5878 &effects) {
5879 getPackUnPackEffectsImpl(*this, effects);
5880}
5881
5882/// Returns true if the tiles and the tiled dims are constant.
5883template <typename OpTy>
5885 static_assert(llvm::is_one_of<OpTy, PackOp, UnPackOp>::value,
5886 "applies to only pack or unpack operations");
5887 ShapedType packedType = (std::is_same<OpTy, PackOp>::value)
5888 ? op.getDestType()
5889 : op.getSourceType();
5890 SmallVector<OpFoldResult> mixedTiles = op.getMixedTiles();
5891 for (auto [dimDest, tile] : llvm::zip(
5892 packedType.getShape().take_back(mixedTiles.size()), mixedTiles)) {
5893 std::optional<int64_t> constTileSize = getConstantIntValue(tile);
5894 if (!constTileSize || ShapedType::isDynamic(dimDest))
5895 return false;
5896 }
5897 return true;
5898}
5899
5900Speculation::Speculatability PackOp::getSpeculatability() {
5901 if (!hasPureTensorSemantics())
5903 if (getPaddingValue())
5905
5906 // The verifier rejects already operations if we can statically prove that the
5907 // sizes of the tiles do not divide perfectly the dimension; thus, check only
5908 // to have constant tiles and tiled inner dimensions.
5911
5913}
5914
5915// Return true if `inner_dims_pos` and `outer_dims_perm` target the same
5916// dimensions for pack and unpack.
5917static bool hasSameInnerOuterAttribute(PackOp packOp, UnPackOp unPackOp) {
5918 if (packOp.getInnerDimsPos() != unPackOp.getInnerDimsPos())
5919 return false;
5920 if (packOp.getOuterDimsPerm() == unPackOp.getOuterDimsPerm())
5921 return true;
5922 // Outer dims permutation is optional.
5923 // To compare unbalanced pack-unpack pair, treat no permutation as equal to
5924 // identity permutation.
5925 return isIdentityPermutation(packOp.getOuterDimsPerm()) &&
5926 isIdentityPermutation(unPackOp.getOuterDimsPerm());
5927}
5928
5929// Return true if pack and unpack have the same tiles.
5930// Same SSA values or same integer constants.
5931static bool haveSameTiles(PackOp packOp, UnPackOp unPackOp) {
5932 auto packTiles = packOp.getMixedTiles();
5933 auto unPackTiles = unPackOp.getMixedTiles();
5934 if (packTiles.size() != unPackTiles.size())
5935 return false;
5936 for (size_t i = 0, e = packTiles.size(); i < e; i++) {
5937 if (!isEqualConstantIntOrValue(packTiles[i], unPackTiles[i]))
5938 return false;
5939 }
5940 return true;
5941}
5942
5943/// Returns true if the pack op does not need a padding value.
5944static bool paddingIsNotNeeded(PackOp op) {
5945 auto srcType = op.getSourceType();
5946 auto innerDimsPos = op.getInnerDimsPos();
5947 auto innerTiles = op.getStaticInnerTiles();
5948 if (ShapedType::isDynamicShape(innerTiles))
5949 return false;
5950 for (auto [pos, tileSize] : llvm::zip_equal(innerDimsPos, innerTiles)) {
5951 if (srcType.isDynamicDim(pos) && tileSize != 1)
5952 return false;
5953 }
5954 return !PackOp::requirePaddingValue(
5955 srcType.getShape(), op.getInnerDimsPos(), op.getDestType().getShape(),
5956 op.getOuterDimsPerm(), op.getMixedTiles());
5957}
5958
5959/// Returns true if the `srcShape` or `destShape` is different from the one in
5960/// `packOp` and populates each with the inferred static shape.
5961static bool inferStaticShape(PackOp packOp, SmallVectorImpl<int64_t> &srcShape,
5962 SmallVectorImpl<int64_t> &destShape) {
5963 bool changeNeeded = false;
5964 srcShape.assign(packOp.getSourceType().getShape().begin(),
5965 packOp.getSourceType().getShape().end());
5966 destShape.assign(packOp.getDestType().getShape().begin(),
5967 packOp.getDestType().getShape().end());
5968 llvm::SmallSetVector<int64_t, 4> innerDims;
5969 innerDims.insert_range(packOp.getInnerDimsPos());
5970 SmallVector<int64_t> inverseOuterDimsPerm;
5971 if (!packOp.getOuterDimsPerm().empty())
5972 inverseOuterDimsPerm = invertPermutationVector(packOp.getOuterDimsPerm());
5973 int srcRank = packOp.getSourceRank();
5974 for (auto i : llvm::seq<int64_t>(0, srcRank)) {
5975 if (innerDims.contains(i))
5976 continue;
5977 int64_t srcPos = i;
5978 int64_t destPos = i;
5979 if (!inverseOuterDimsPerm.empty())
5980 destPos = inverseOuterDimsPerm[srcPos];
5981 if (ShapedType::isDynamic(srcShape[srcPos]) ==
5982 ShapedType::isDynamic(destShape[destPos])) {
5983 continue;
5984 }
5985 int64_t size = srcShape[srcPos];
5986 if (ShapedType::isDynamic(size))
5987 size = destShape[destPos];
5988 srcShape[srcPos] = size;
5989 destShape[destPos] = size;
5990 changeNeeded = true;
5991 }
5992 return changeNeeded;
5993}
5994
5995LogicalResult PackOp::canonicalize(PackOp packOp, PatternRewriter &rewriter) {
5996 // TODO: Support Memref PackOp. Temporarily return failure.
5997 if (!packOp.hasPureTensorSemantics())
5998 return failure();
5999
6000 // Fold an pack(unpack(x)) to x.
6001 if (auto unPackOp = packOp.getSource().getDefiningOp<UnPackOp>()) {
6002 if (unPackOp.getSourceType() == packOp.getDestType() &&
6003 !packOp.getPaddingValue() &&
6004 hasSameInnerOuterAttribute(packOp, unPackOp) &&
6005 haveSameTiles(packOp, unPackOp)) {
6006 rewriter.replaceOp(packOp, unPackOp.getSource());
6007 return success();
6008 }
6009 }
6010
6011 // Fold optional PaddingValue operand away if padding is not needed.
6012 if (packOp.getPaddingValue() && paddingIsNotNeeded(packOp)) {
6013 rewriter.startOpModification(packOp);
6014 packOp.getPaddingValueMutable().clear();
6015 rewriter.finalizeOpModification(packOp);
6016 return success();
6017 }
6018
6019 // Insert tensor.cast ops if static shape inference is available..
6020 SmallVector<int64_t> srcShape, destShape;
6021 if (inferStaticShape(packOp, srcShape, destShape)) {
6022 Location loc = packOp.getLoc();
6023 Value source = packOp.getSource();
6024 if (srcShape != packOp.getSourceType().getShape()) {
6025 auto newSrcType = packOp.getSourceType().clone(srcShape);
6026 source =
6027 tensor::CastOp::create(rewriter, loc, newSrcType, packOp.getSource());
6028 }
6029 Value dest = packOp.getDest();
6030 ShapedType originalResultType = packOp.getDestType();
6031 bool needUpdateDestType = (destShape != originalResultType.getShape());
6032 if (needUpdateDestType) {
6033 auto newDestType = packOp.getDestType().clone(destShape);
6034 dest =
6035 tensor::CastOp::create(rewriter, loc, newDestType, packOp.getDest());
6036 }
6037 rewriter.modifyOpInPlace(packOp, [&] {
6038 packOp.getSourceMutable().assign(source);
6039 packOp.getDestMutable().assign(dest);
6040 packOp.getResult().setType(cast<RankedTensorType>(dest.getType()));
6041 });
6042 // Insert a cast if needed
6043 if (needUpdateDestType) {
6044 rewriter.setInsertionPointAfter(packOp);
6045 auto castOp = tensor::CastOp::create(rewriter, loc, originalResultType,
6046 packOp.getResult());
6047 rewriter.replaceAllUsesExcept(packOp.getResult(), castOp, castOp);
6048 }
6049 return success();
6050 }
6051
6052 return failure();
6053}
6054
6055template <typename PackOrUnpackOp>
6056static bool isLikePadUnPad(PackOrUnpackOp packOp, ShapedType packedTensorType) {
6057 static_assert(std::is_same<PackOrUnpackOp, PackOp>::value ||
6058 std::is_same<PackOrUnpackOp, UnPackOp>::value,
6059 "Function meant for pack/unpack");
6060 // This is a pad if packing only adds ones and we don't transpose dimensions.
6061
6062 // Check that we are not transposing any dimensions.
6063 ArrayRef<int64_t> innerDimsPos = packOp.getInnerDimsPos();
6064 int64_t numPackedDims = innerDimsPos.size();
6065 auto orderedDims = llvm::to_vector<4>(llvm::seq<int64_t>(0, numPackedDims));
6066 if (orderedDims != innerDimsPos) {
6067 // Dimensions don't happen in order.
6068 return false;
6069 }
6070
6071 ArrayRef<int64_t> packedShape = packedTensorType.getShape();
6072 int64_t packedRank = packedTensorType.getRank();
6073 // At this point we know that we are taking numPackedDims outer
6074 // dimensions and pushing them all the way as the inner most dimensions.
6075 // What's left on the outer most dimensions is, in this order:
6076 // - the factor of the packed dimensions, then
6077 // - the untouched dimensions
6078 // This shifting inward of dimensions is a no-op (as opposed to a transpose)
6079 // if all the dimensions that bubble outerward are ones.
6080 // Therefore check that all the dimensions but the numPackedDims inner most
6081 // ones are ones.
6082 return llvm::all_of(
6083 llvm::seq<int64_t>(0, packedRank - numPackedDims),
6084 [&packedShape](int64_t i) { return packedShape[i] == 1; });
6085}
6086
6087bool PackOp::isLikePad() {
6088 auto packedTensorType =
6089 llvm::cast<ShapedType>((*this)->getResultTypes().front());
6090 return isLikePadUnPad(*this, packedTensorType);
6091}
6092
6093::mlir::LogicalResult
6094PackOp::fold(FoldAdaptor adaptor,
6096 if (!hasPureTensorSemantics())
6097 return failure();
6098 std::optional<Attribute> paddingValue;
6099 if (auto pad = adaptor.getPaddingValue())
6100 paddingValue = pad;
6101 if (OpFoldResult reshapedSource = reshapeConstantSource(
6102 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
6103 cast<TensorType>(getDestType()), paddingValue)) {
6104 results.push_back(reshapedSource);
6105 return success();
6106 }
6107 return failure();
6108}
6109
6110/// Folds a tensor.cast op into a consuming PackOp op if the
6111/// `tensor.cast` has source that is more static than the consuming op.
6112///
6113/// Example:
6114/// ```mlir
6115/// %1 = tensor.cast %0 : tensor<8x16xf32> to tensor<?x?xf32>
6116/// %2 = tensor.pack %1 ... : tensor<?x?xf32> ...
6117/// ```
6118///
6119/// folds into:
6120///
6121/// ```mlir
6122/// %2 = tensor.pack %0 ... : tensor<8x16xf32> ...
6123/// ```
6126
6127 LogicalResult matchAndRewrite(PackOp op,
6128 PatternRewriter &rewriter) const override {
6129 // TODO: Support Memref PackOp. Temporarily return failure.
6130 if (!op.hasPureTensorSemantics())
6131 return failure();
6132
6134 return failure();
6135
6136 SmallVector<Type> newResultTypes(op->getResultTypes());
6137 SmallVector<Value> newOperands =
6139
6140 // Get the updated mixed-tile-sizes attribute.
6141 SmallVector<OpFoldResult> newMixedTileSizes =
6142 getNewMixedTileSizes(rewriter, newResultTypes[0], op.getMixedTiles());
6143 if (llvm::any_of(newMixedTileSizes, isZeroInteger))
6144 return failure();
6145
6146 // Clone op.
6147 // TODO: Strictly speaking, discardable attributes should be _discarded_ at
6148 // this point. However, in practice, we use them for things that we'd like
6149 // to preserve. Implement a better abstraction.
6150 PackOp newOp =
6151 PackOp::create(rewriter, op.getLoc(), newOperands[0], newOperands[1],
6152 op.getInnerDimsPos(), newMixedTileSizes,
6153 op.getPaddingValue(), op.getOuterDimsPerm());
6154 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
6155
6156 // Replace op.
6157 Value oldResult = op.getResult();
6158 Value newResult = newOp.getResult();
6160 (newResult.getType() != oldResult.getType())
6161 ? tensor::CastOp::create(rewriter, op->getLoc(),
6162 oldResult.getType(), newResult)
6163 : newResult;
6164
6165 rewriter.replaceOp(op, {replacement});
6166
6167 return success();
6168 }
6169};
6170
6171//===----------------------------------------------------------------------===//
6172// UnPackOp
6173//===----------------------------------------------------------------------===//
6174
6175void UnPackOp::getAsmResultNames(
6176 function_ref<void(Value, StringRef)> setNameFn) {
6177 if (!getResults().empty())
6178 setNameFn(getResult(), "unpack");
6179}
6180
6181// Custom parser for UnPackOp that handles the memref/tensor case distinction
6182ParseResult UnPackOp::parse(OpAsmParser &parser, OperationState &result) {
6183 OpAsmParser::UnresolvedOperand source, dest;
6185 SmallVector<int64_t> staticTiles;
6186 DenseI64ArrayAttr innerDimsPos, outerDimsPerm;
6187 Type sourceType, destType, resultType;
6188
6189 if (parser.parseOperand(source))
6190 return failure();
6191
6192 if (succeeded(parser.parseOptionalKeyword("outer_dims_perm"))) {
6193 if (parser.parseEqual())
6194 return failure();
6195
6196 SmallVector<int64_t> outerDimsPermVec;
6198 int64_t value;
6199 if (parser.parseInteger(value))
6200 return failure();
6201 outerDimsPermVec.push_back(value);
6202 return success();
6203 }))
6204 return failure();
6205 outerDimsPerm = parser.getBuilder().getDenseI64ArrayAttr(outerDimsPermVec);
6206 }
6207
6208 if (parser.parseKeyword("inner_dims_pos") || parser.parseEqual())
6209 return failure();
6210
6211 SmallVector<int64_t> innerDimsPosVec;
6213 int64_t value;
6214 if (parser.parseInteger(value))
6215 return failure();
6216 innerDimsPosVec.push_back(value);
6217 return success();
6218 }))
6219 return failure();
6220 innerDimsPos = parser.getBuilder().getDenseI64ArrayAttr(innerDimsPosVec);
6221
6222 if (parser.parseKeyword("inner_tiles") || parser.parseEqual())
6223 return failure();
6224
6225 DenseI64ArrayAttr staticTilesAttr;
6226 if (parseDynamicIndexList(parser, dynamicTiles, staticTilesAttr))
6227 return failure();
6228 for (auto val : staticTilesAttr.asArrayRef())
6229 staticTiles.push_back(val);
6230
6231 if (parser.parseKeyword("into") || parser.parseOperand(dest))
6232 return failure();
6233
6234 if (parser.parseOptionalAttrDict(result.attributes))
6235 return failure();
6236
6237 if (parser.parseColon() || parser.parseType(sourceType))
6238 return failure();
6239
6240 bool hasArrow = succeeded(parser.parseOptionalArrow());
6241 if (hasArrow) {
6242 if (parser.parseType(destType))
6243 return failure();
6244 }
6245
6246 bool isMemRef = llvm::isa<MemRefType>(sourceType);
6247 if (!hasArrow) {
6248 return parser.emitError(parser.getCurrentLocation(),
6249 "pack/unpack requires '->' and destination type");
6250 }
6251
6252 if (!isMemRef)
6253 resultType = destType;
6254
6255 if (parser.resolveOperand(source, sourceType, result.operands) ||
6256 parser.resolveOperand(dest, destType, result.operands))
6257 return failure();
6258
6259 if (!dynamicTiles.empty() &&
6260 parser.resolveOperands(dynamicTiles, parser.getBuilder().getIndexType(),
6261 result.operands))
6262 return failure();
6263
6264 result.addAttribute("static_inner_tiles",
6265 parser.getBuilder().getDenseI64ArrayAttr(staticTiles));
6266 result.addAttribute("inner_dims_pos", innerDimsPos);
6267 if (outerDimsPerm)
6268 result.addAttribute("outer_dims_perm", outerDimsPerm);
6269
6270 SmallVector<int32_t> segmentSizes = {
6271 1, 1, 0, static_cast<int32_t>(dynamicTiles.size())};
6272 result.addAttribute("operandSegmentSizes",
6273 parser.getBuilder().getDenseI32ArrayAttr(segmentSizes));
6274
6275 if (!isMemRef)
6276 result.addTypes(resultType);
6277
6278 return success();
6279}
6280
6281void UnPackOp::print(OpAsmPrinter &p) {
6282 p << " " << getSource();
6283
6284 if (!getOuterDimsPerm().empty()) {
6285 p << " outer_dims_perm = [";
6286 llvm::interleaveComma(getOuterDimsPerm(), p);
6287 p << "]";
6288 }
6289
6290 p << " inner_dims_pos = [";
6291 llvm::interleaveComma(getInnerDimsPos(), p);
6292 p << "]";
6293
6294 p << " inner_tiles = ";
6295 printDynamicIndexList(p, *this, getInnerTiles(), getStaticInnerTilesAttr());
6296
6297 p << " into " << getDest();
6298
6299 p.printOptionalAttrDict((*this)->getAttrs(),
6300 {"static_inner_tiles", "inner_dims_pos",
6301 "outer_dims_perm", "operandSegmentSizes"});
6302
6303 p << " : " << getSource().getType();
6304 p << " -> " << getDest().getType();
6305}
6306
6307LogicalResult
6308UnPackOp::reifyResultShapes(OpBuilder &builder,
6309 ReifiedRankedShapedTypeDims &reifiedReturnShapes) {
6310 return reifyResultShapesImpl(*this, builder, reifiedReturnShapes);
6311}
6312
6313DenseMap<int64_t, OpFoldResult> UnPackOp::getDimAndTileMapping() {
6314 return getDimAndTileMappingImpl(*this);
6315}
6316
6317SmallVector<OpFoldResult> UnPackOp::getMixedTiles() {
6318 return getMixedTilesImpl(*this);
6319}
6320
6321SmallVector<int64_t> UnPackOp::getStaticTiles() {
6322 return getStaticTilesImpl(*this);
6323}
6324
6325ArrayRef<int64_t> UnPackOp::getAllOuterDims() {
6326 ShapedType destType = getDestType();
6327 int64_t destRank = destType.getRank();
6328 return getSourceType().getShape().take_front(destRank);
6329}
6330
6331SmallVector<int64_t> UnPackOp::getTiledOuterDims() {
6332 auto innerDimsPos = getInnerDimsPos();
6333 SmallVector<int64_t> outerDims(getAllOuterDims());
6334 SmallVector<int64_t> res;
6335
6336 // Recover the original order of the outer dims.
6337 SmallVector<int64_t> outerDimPermInv(getOuterDimsPerm());
6338 invertPermutationVector(outerDimPermInv);
6339 if (!outerDimPermInv.empty())
6340 applyPermutationToVector(outerDims, outerDimPermInv);
6341
6342 // Collect the outer dims corresponding to the tilled inner dims.
6343 for (auto index : innerDimsPos)
6344 res.push_back(outerDims[index]);
6345
6346 return res;
6347}
6348
6349LogicalResult UnPackOp::verify() {
6350 return commonVerifierPackAndUnPackOp(*this);
6351}
6352
6353Speculation::Speculatability UnPackOp::getSpeculatability() {
6354 if (!hasPureTensorSemantics())
6356 // See PackOp::getSpeculatability.
6359
6361}
6362
6363void UnPackOp::build(OpBuilder &builder, OperationState &state, Value source,
6364 Value dest, ArrayRef<int64_t> innerDimsPos,
6365 ArrayRef<OpFoldResult> innerTiles,
6366 ArrayRef<int64_t> outerDimsPerm) {
6367 assert(innerDimsPos.size() == innerTiles.size() &&
6368 "number of tile sizes specified must match the specified number of "
6369 "original dimensions to be tiled");
6370 SmallVector<int64_t> staticTileSizes;
6371 SmallVector<Value> dynamicTileSizes;
6372 dispatchIndexOpFoldResults(innerTiles, dynamicTileSizes, staticTileSizes);
6373 build(builder, state, dest.getType(), source, dest,
6374 outerDimsPerm.empty() ? nullptr
6375 : builder.getDenseI64ArrayAttr(outerDimsPerm),
6376 builder.getDenseI64ArrayAttr(innerDimsPos), dynamicTileSizes,
6377 builder.getDenseI64ArrayAttr(staticTileSizes));
6378}
6379
6380Value UnPackOp::createDestinationTensor(OpBuilder &b, Location loc,
6381 Value source,
6382 ArrayRef<OpFoldResult> innerTileSizes,
6383 ArrayRef<int64_t> innerDimsPos,
6384 ArrayRef<int64_t> outerDimsPerm) {
6385 AffineExpr sym0, sym1;
6386 bindSymbols(b.getContext(), sym0, sym1);
6387 auto dimMul = [&](OpFoldResult v1, OpFoldResult v2) -> OpFoldResult {
6388 return affine::makeComposedFoldedAffineApply(b, loc, sym0 * sym1, {v1, v2});
6389 };
6390
6391 SmallVector<OpFoldResult> mixedSizes;
6392 auto srcType = llvm::cast<RankedTensorType>(source.getType());
6393 for (auto i :
6394 llvm::seq<unsigned>(0, srcType.getRank() - innerTileSizes.size())) {
6395 if (srcType.isDynamicDim(i))
6396 mixedSizes.push_back(
6397 tensor::DimOp::create(b, loc, source, i).getResult());
6398 else
6399 mixedSizes.push_back(b.getIndexAttr(srcType.getDimSize(i)));
6400 }
6401 if (!outerDimsPerm.empty()) {
6403 mixedSizes, invertPermutationVector(outerDimsPerm));
6404 }
6405
6406 for (auto [dimPos, tileSize] : llvm::zip_equal(innerDimsPos, innerTileSizes))
6407 mixedSizes[dimPos] = dimMul(mixedSizes[dimPos], tileSize);
6408
6409 auto elemType = srcType.getElementType();
6410 return tensor::EmptyOp::create(b, loc, mixedSizes, elemType);
6411}
6412
6413UnPackOp UnPackOp::createTransposedClone(OpBuilder &b, Location loc,
6414 Value transposedSource,
6415 ArrayRef<int64_t> innerPermutation,
6416 ArrayRef<int64_t> outerPermutation) {
6417 PackOrUnPackTransposeResult metadata = commonPermutationOfPackAndUnPackOp(
6418 *this, innerPermutation, outerPermutation);
6419 return UnPackOp::create(b, loc, transposedSource, getDest(),
6420 metadata.innerDimsPos, metadata.innerTiles,
6421 metadata.outerDimsPerm);
6422}
6423
6424/// Returns true if the `srcShape` or `destShape` is different from the one in
6425/// `op` and populates each with the inferred static shape.
6426static bool inferStaticShape(UnPackOp op, SmallVectorImpl<int64_t> &srcShape,
6427 SmallVectorImpl<int64_t> &destShape) {
6428 bool changeNeeded = false;
6429 srcShape.assign(op.getSourceType().getShape().begin(),
6430 op.getSourceType().getShape().end());
6431 destShape.assign(op.getDestType().getShape().begin(),
6432 op.getDestType().getShape().end());
6433 llvm::SmallSetVector<int64_t, 4> innerDims;
6434 innerDims.insert_range(op.getInnerDimsPos());
6435 SmallVector<int64_t> inverseOuterDimsPerm;
6436 if (!op.getOuterDimsPerm().empty())
6437 inverseOuterDimsPerm = invertPermutationVector(op.getOuterDimsPerm());
6438 int destRank = op.getDestRank();
6439 for (auto i : llvm::seq<int64_t>(0, destRank)) {
6440 if (innerDims.contains(i))
6441 continue;
6442 int64_t srcPos = i;
6443 int64_t destPos = i;
6444 if (!inverseOuterDimsPerm.empty())
6445 srcPos = inverseOuterDimsPerm[destPos];
6446 if (ShapedType::isDynamic(srcShape[srcPos]) ==
6447 ShapedType::isDynamic(destShape[destPos])) {
6448 continue;
6449 }
6450 int64_t size = srcShape[srcPos];
6451 if (ShapedType::isDynamic(size))
6452 size = destShape[destPos];
6453 srcShape[srcPos] = size;
6454 destShape[destPos] = size;
6455 changeNeeded = true;
6456 }
6457 return changeNeeded;
6458}
6459
6460LogicalResult UnPackOp::canonicalize(UnPackOp unPackOp,
6461 PatternRewriter &rewriter) {
6462 // TODO: Support Memref UnPackOp. Temporarily return failure.
6463 if (!unPackOp.hasPureTensorSemantics())
6464 return failure();
6465
6466 /// unpack(pack(x)) -> x
6467 if (PackOp packOp = unPackOp.getSource().getDefiningOp<PackOp>()) {
6468 if (packOp.getSourceType() != unPackOp.getDestType())
6469 return failure();
6470 if (packOp.getPaddingValue() ||
6471 !hasSameInnerOuterAttribute(packOp, unPackOp) ||
6472 !haveSameTiles(packOp, unPackOp))
6473 return failure();
6474 rewriter.replaceOp(unPackOp, packOp.getSource());
6475 return success();
6476 }
6477 /// unpack(destinationStyleOp(x)) -> unpack(x)
6478 if (auto dstStyleOp =
6479 unPackOp.getDest().getDefiningOp<DestinationStyleOpInterface>()) {
6480 auto destValue = cast<OpResult>(unPackOp.getDest());
6481 Value newDest = dstStyleOp.getDpsInits()[destValue.getResultNumber()];
6482 rewriter.modifyOpInPlace(unPackOp,
6483 [&]() { unPackOp.setDpsInitOperand(0, newDest); });
6484 return success();
6485 }
6486 /// extract_slice(unpack(x into y)) -> unpack(x into extract_slice(y))
6487 if (unPackOp->hasOneUse()) {
6488 auto extractSliceUser =
6489 dyn_cast<tensor::ExtractSliceOp>(*unPackOp->getUsers().begin());
6490 if (extractSliceUser && unPackOp.canFoldSliceOp(extractSliceUser)) {
6491 OpBuilder::InsertionGuard g(rewriter);
6492 rewriter.setInsertionPoint(unPackOp);
6493 auto newDest = tensor::ExtractSliceOp::create(
6494 rewriter, unPackOp->getLoc(), unPackOp.getDest(),
6495 extractSliceUser.getMixedOffsets(), extractSliceUser.getMixedSizes(),
6496 extractSliceUser.getMixedStrides());
6497 rewriter.modifyOpInPlace(unPackOp, [&]() {
6498 unPackOp.setDpsInitOperand(0, newDest);
6499 unPackOp.getResult().setType(newDest.getType());
6500 });
6501 rewriter.replaceOp(extractSliceUser, unPackOp);
6502 return success();
6503 }
6504 }
6505
6506 // Insert tensor.cast ops if static shape inference is available..
6507 SmallVector<int64_t> srcShape, destShape;
6508 if (inferStaticShape(unPackOp, srcShape, destShape)) {
6509 Location loc = unPackOp.getLoc();
6510 Value source = unPackOp.getSource();
6511 if (srcShape != unPackOp.getSourceType().getShape()) {
6512 auto newSrcType = unPackOp.getSourceType().clone(srcShape);
6513 source = tensor::CastOp::create(rewriter, loc, newSrcType,
6514 unPackOp.getSource());
6515 }
6516 Value dest = unPackOp.getDest();
6517 if (destShape != unPackOp.getDestType().getShape()) {
6518 auto newDestType = unPackOp.getDestType().clone(destShape);
6519 dest = tensor::CastOp::create(rewriter, loc, newDestType,
6520 unPackOp.getDest());
6521 }
6522 UnPackOp newOp = UnPackOp::create(
6523 rewriter, loc, source, dest, unPackOp.getInnerDimsPos(),
6524 unPackOp.getMixedTiles(), unPackOp.getOuterDimsPerm());
6525 rewriter.replaceOpWithNewOp<tensor::CastOp>(
6526 unPackOp, unPackOp.getResult().getType(), newOp.getResult());
6527 return success();
6528 }
6529
6530 return failure();
6531}
6532
6533bool UnPackOp::canFoldSliceOp(tensor::ExtractSliceOp sliceOp) {
6534 // Rank-reduced folding is not supported.
6535 if (sliceOp.getResultType().getRank() != this->getDestType().getRank())
6536 return false;
6537 if (!areAllConstantIntValue(sliceOp.getMixedOffsets(), 0) ||
6538 !areAllConstantIntValue(sliceOp.getMixedStrides(), 1))
6539 return false;
6540 RankedTensorType unpackedTypeAfterFold = sliceOp.getResultType();
6541 SmallVector<int64_t> outerShapeWithoutTranspose =
6543 SmallVector<bool> areOuterDimsTiled(outerShapeWithoutTranspose.size(), false);
6544 for (auto [pos, tileSize] :
6545 llvm::zip_equal(this->getInnerDimsPos(), this->getStaticInnerTiles())) {
6546 areOuterDimsTiled[pos] = true;
6547 if (unpackedTypeAfterFold.isDynamicDim(pos))
6548 return false;
6549 if (ShapedType::isDynamic(outerShapeWithoutTranspose[pos]))
6550 return false;
6551 if (ShapedType::isDynamic(tileSize))
6552 return false;
6553 int64_t paddingSize = outerShapeWithoutTranspose[pos] * tileSize -
6554 unpackedTypeAfterFold.getDimSize(pos);
6555 if (paddingSize >= tileSize)
6556 return false;
6557 }
6558 // extract_slice must not affect dimensions that are not being unpacked
6559 for (int64_t pos = 0, e = outerShapeWithoutTranspose.size(); pos < e; ++pos) {
6560 if (areOuterDimsTiled[pos])
6561 continue;
6562 int64_t dim = outerShapeWithoutTranspose[pos];
6563 if (ShapedType::isDynamic(dim))
6564 return false;
6565 if (dim != unpackedTypeAfterFold.getDimSize(pos))
6566 return false;
6567 }
6568 return true;
6569}
6570
6571bool UnPackOp::isLikeUnPad() {
6572 ShapedType packedTensorType = getSourceType();
6573 return isLikePadUnPad(*this, packedTensorType);
6574}
6575
6576::mlir::LogicalResult
6577UnPackOp::fold(FoldAdaptor adaptor,
6578 ::llvm::SmallVectorImpl<OpFoldResult> &results) {
6579 // TODO: Support Memref UnPackOp. Temporarily return failure.
6580 if (!hasPureTensorSemantics())
6581 return failure();
6582
6583 if (OpFoldResult reshapedSource = reshapeConstantSource(
6584 llvm::dyn_cast_if_present<DenseElementsAttr>(adaptor.getSource()),
6585 cast<TensorType>(getResult().getType()))) {
6586 results.push_back(reshapedSource);
6587 return success();
6588 }
6589 return failure();
6590}
6591
6592/// Folds a tensor.cast op into a consuming UnPackOp op if the
6593/// `tensor.cast` has source that is more static than the consuming op.
6594///
6595/// Example:
6596/// ```mlir
6597/// %1 = tensor.cast %0 : tensor<1x1x8x1xi32> to tensor<1x1x?x1xi32>
6598/// %2 = tensor.unpack %1 ... : tensor<1x1x?x1xi32> -> tensor<7x?xi32>
6599/// ```
6600///
6601/// folds into:
6602///
6603/// ```mlir
6604/// %2 = tensor.unpack %0 ... tensor<1x1x8x1xi32> -> tensor<7x?xi32>
6605/// ```
6606struct FoldTensorCastUnPackOp : public OpRewritePattern<UnPackOp> {
6607 using OpRewritePattern<UnPackOp>::OpRewritePattern;
6608
6609 LogicalResult matchAndRewrite(UnPackOp op,
6610 PatternRewriter &rewriter) const override {
6611 // TODO: Support Memref UnPackOp. Temporarily return failure.
6612 if (!op.hasPureTensorSemantics())
6613 return failure();
6614
6616 return failure();
6617
6618 SmallVector<Type> newResultTypes(op->getResultTypes());
6619 SmallVector<Value> newOperands =
6621 Value sourceTensor = newOperands[0];
6622
6623 // Get the updated mixed-tile-sizes attribute.
6625 rewriter, sourceTensor.getType(), op.getMixedTiles());
6626
6627 // Clone op.
6628 // TODO: Strictly speaking, discardable attributes should be _discarded_ at
6629 // this point. However, in practice, we use them for things that we'd like
6630 // to preserve. Implement a better abstraction.
6631 UnPackOp newOp = UnPackOp::create(rewriter, op.getLoc(), sourceTensor,
6632 newOperands[1], op.getInnerDimsPos(),
6633 newMixedTileSizes, op.getOuterDimsPerm());
6634 newOp->setDiscardableAttrs(op->getDiscardableAttrDictionary());
6635
6636 // Replace op.
6637 Value oldResult = op.getResult();
6638 Value newResult = newOp.getResult();
6640 (newResult.getType() != oldResult.getType())
6641 ? tensor::CastOp::create(rewriter, op->getLoc(),
6642 oldResult.getType(), newResult)
6643 : newResult;
6644
6645 rewriter.replaceOp(op, {replacement});
6646
6647 return success();
6648 }
6649};
6650
6651//===----------------------------------------------------------------------===//
6652// BatchReduceMatmulOp
6653//===----------------------------------------------------------------------===//
6654SmallVector<utils::IteratorType> BatchReduceMatmulOp::getIteratorTypesArray() {
6656 utils::IteratorType::reduction, utils::IteratorType::parallel,
6657 utils::IteratorType::parallel, utils::IteratorType::reduction};
6658}
6659
6660SmallVector<AffineMap>
6661BatchReduceMatmulOp::getDefaultIndexingMaps(MLIRContext *context) {
6662 AffineExpr d0, d1, d2, d3;
6663 SmallVector<AffineMap> indexingMaps;
6664 bindDims(context, d0, d1, d2, d3);
6665 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d1, d3}, context));
6666 indexingMaps.push_back(AffineMap::get(4, 0, {d0, d3, d2}, context));
6667 indexingMaps.push_back(AffineMap::get(4, 0, {d1, d2}, context));
6668 return indexingMaps;
6669}
6670
6671bool BatchReduceMatmulOp::isDefaultIndexingMaps(Attribute attr) {
6672 ArrayAttr maps = dyn_cast<ArrayAttr>(attr);
6673 if (!maps)
6674 return false;
6675 if (maps.size() != 3)
6676 return false;
6677 auto positions = getAffineResultPositions(maps);
6678 if (failed(positions))
6679 return false;
6680 return (*positions)[0] == SmallVector<int64_t>{0, 1, 3} &&
6681 (*positions)[1] == SmallVector<int64_t>{0, 3, 2} &&
6682 (*positions)[2] == SmallVector<int64_t>{1, 2};
6683}
6684unsigned BatchReduceMatmulOp::getNumRegionArgs() { return 3; }
6685
6686std::string BatchReduceMatmulOp::getLibraryCallName() {
6687 return generateLibraryCallName(getOperation());
6688}
6689
6690/// Check if the op has broadcast and/or transpose semantic. Returns true if
6691/// the user defined indexing maps are not equal to default map.
6692bool BatchReduceMatmulOp::hasUserDefinedMaps() {
6693 SmallVector<AffineMap, 3> defaultMaps =
6694 getDefaultIndexingMaps(this->getContext());
6695 SmallVector<AffineMap, 3> explicitMaps = getIndexingMapsArray();
6696 return defaultMaps != explicitMaps;
6697}
6698
6699/// Returns true if the given bcastMap map is a valid broadcast map. A valid
6700/// broadcast map must include K dimension.
6701/// TODO: Strict inclusion of K dimension in the broadcast map is not
6702/// necessary for both input matrices simultaneously. We can relax this
6703/// condition to have K dimension for one input matrix map and infer the K
6704/// dimension for other input matrix map from the one already having K
6705/// dimension.
6706bool BatchReduceMatmulOp::isValidLhsRhsBroadcastMap(AffineMap bcastMap,
6707 bool isLHS) {
6708 assert(bcastMap.getNumResults() < 3 &&
6709 "Expected less than 3 result dim expr.");
6710 bool isValid = false;
6711 enum Indices { batchPos, mPos, nPos, kPos };
6712 if (bcastMap.getNumResults() == 1) {
6713 AffineExpr expr = bcastMap.getResult(0);
6714 isValid = expr.isFunctionOfDim(kPos);
6715 } else if (bcastMap.getNumResults() == 2) {
6716 AffineExpr expr0 = bcastMap.getResult(0);
6717 AffineExpr expr1 = bcastMap.getResult(1);
6718 isValid =
6719 isLHS ? ((expr0.isFunctionOfDim(batchPos) ||
6720 expr0.isFunctionOfDim(mPos)) &&
6721 expr1.isFunctionOfDim(kPos))
6722 : ((expr0.isFunctionOfDim(batchPos) &&
6723 expr1.isFunctionOfDim(kPos)) ||
6724 (expr0.isFunctionOfDim(kPos) && expr1.isFunctionOfDim(nPos)));
6725 }
6726 return isValid;
6727}
6728
6729void BatchReduceMatmulOp::regionBuilder(
6730 ImplicitLocOpBuilder &b, Block &block, ArrayRef<NamedAttribute> attrs,
6731 function_ref<InFlightDiagnostic()> emitError) {
6732 if (emitError && block.getNumArguments() != 3) {
6733 emitError() << "BatchReduceMatmulOp regionBuilder expects 3 args, got "
6734 << block.getNumArguments();
6735 return;
6736 }
6737 assert(block.getNumArguments() == 3 &&
6738 "BatchReduceMatmulOp regionBuilder expects 3 args");
6739 RegionBuilderHelper helper(b, block);
6740 SmallVector<Value> yields;
6741
6742 auto toType = block.getArgument(2).getType();
6743 Value castValA =
6744 helper.buildTypeFn(TypeFn::cast_signed, toType, block.getArgument(0));
6745 Value castValB =
6746 helper.buildTypeFn(TypeFn::cast_signed, toType, block.getArgument(1));
6747 Value mulVal =
6748 helper.buildBinaryFn(BinaryFn::mul, castValA, castValB, emitError);
6749 if (!castValA || !castValB || !mulVal)
6750 return;
6751 Value addVal =
6752 helper.buildBinaryFn(BinaryFn::add, block.getArgument(2), mulVal);
6753 if (!addVal)
6754 return;
6755 yields.push_back(addVal);
6756 helper.yieldOutputs(yields);
6757}
6758
6759ParseResult BatchReduceMatmulOp::parse(OpAsmParser &parser,
6760 OperationState &result) {
6761 SmallVector<Attribute, 3> indexingMapsAttr;
6762 Attribute mapAttr;
6763 if (succeeded(parser.parseOptionalKeyword("indexing_maps"))) {
6764 if (parser.parseEqual())
6765 return failure();
6766 if (parser.parseLSquare())
6767 return failure();
6768
6769 do {
6770 if (parser.parseAttribute(mapAttr))
6771 return failure();
6772 if (!isa<AffineMapAttr>(mapAttr)) {
6773 return parser.emitError(parser.getCurrentLocation(),
6774 "expected affine map attribute");
6775 }
6776 indexingMapsAttr.push_back(mapAttr);
6777
6778 if (parser.parseOptionalComma())
6779 break;
6780 } while (true);
6781
6782 if (parser.parseRSquare())
6783 return failure();
6784 }
6785 // Initialize indexingMaps, if not supplied explicitly.
6786 if (indexingMapsAttr.empty()) {
6787 indexingMapsAttr = llvm::map_to_vector(
6788 BatchReduceMatmulOp::getDefaultIndexingMaps(parser.getContext()),
6789 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
6790 }
6791 result.addAttribute("indexing_maps",
6792 parser.getBuilder().getArrayAttr(indexingMapsAttr));
6793 return ::parseNamedStructuredOp(parser, result,
6794 BatchReduceMatmulOp::getNumRegionArgs(),
6795 BatchReduceMatmulOp::getRegionBuilder());
6796}
6797
6798void BatchReduceMatmulOp::print(OpAsmPrinter &p) {
6799 SmallVector<Attribute, 3> indexingMaps = llvm::map_to_vector(
6800 BatchReduceMatmulOp::getDefaultIndexingMaps(getContext()),
6801 [](AffineMap map) -> Attribute { return AffineMapAttr::get(map); });
6802
6803 if (!llvm::equal(getIndexingMaps(), indexingMaps)) {
6804 p << " indexing_maps = [";
6805 llvm::interleaveComma(getIndexingMaps(), p,
6806 [&](Attribute attr) { p.printAttribute(attr); });
6807 p << "]";
6808 }
6809
6810 SmallVector<StringRef, 3> elidedAttrs = {
6811 "operandSegmentSizes", "linalg.memoized_indexing_maps", "indexing_maps"};
6812 ::printNamedStructuredOp(p, getOperation(), getInputs(), getOutputs(),
6813 elidedAttrs);
6814}
6815
6816/// Verify the user defined indexing maps.
6817LogicalResult BatchReduceMatmulOp::verify() {
6818 // Verification of pure batch_reduce_matmul is handled by
6819 // verifyStructuredOpInterface().
6820 if (!hasUserDefinedMaps())
6821 return success();
6822
6823 for (unsigned opIndex = 0; opIndex < 3; opIndex++) {
6825 return failure();
6826 }
6827 return success();
6828}
6829LogicalResult BatchReduceMatmulOp::fold(FoldAdaptor,
6830 SmallVectorImpl<OpFoldResult> &) {
6831 return memref::foldMemRefCast(*this);
6832}
6833void BatchReduceMatmulOp::getEffects(
6834 SmallVectorImpl<SideEffects::EffectInstance<MemoryEffects::Effect>>
6835 &effects) {
6836 if (hasPureTensorSemantics())
6837 return;
6838 getGenericEffectsImpl(effects, cast<LinalgOp>(getOperation()));
6839}
6840
6841Speculation::Speculatability BatchReduceMatmulOp::getSpeculatability() {
6842 return getGenericSpeculatabilityImpl(cast<LinalgOp>(getOperation()));
6843}
6844
6845} // namespace linalg
6846} // namespace mlir
6847
6848//===----------------------------------------------------------------------===//
6849// LinalgDialect
6850//===----------------------------------------------------------------------===//
6851
6852void LinalgDialect::getCanonicalizationPatterns(
6853 RewritePatternSet &results) const {
6854 results.add<EraseDeadLinalgOp, FoldTensorCastConsumerOp, FoldTensorCastPackOp,
6855 FoldTensorCastUnPackOp, InferStaticShapeOfOperands>(getContext());
6856}
6857
6858Operation *LinalgDialect::materializeConstant(OpBuilder &builder,
6859 Attribute value, Type type,
6860 Location loc) {
6861 return arith::ConstantOp::materialize(builder, value, type, loc);
6862}
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:116
DenseI32ArrayAttr getDenseI32ArrayAttr(ArrayRef< int32_t > values)
Definition Builders.cpp:171
DenseI64ArrayAttr getDenseI64ArrayAttr(ArrayRef< int64_t > values)
Definition Builders.cpp:175
AffineMap getMultiDimIdentityMap(unsigned rank)
Definition Builders.cpp:396
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
StringAttr getStringAttr(const Twine &bytes)
Definition Builders.cpp:271
AffineExpr getAffineDimExpr(unsigned position)
Definition Builders.cpp:373
Location getUnknownLoc()
Definition Builders.cpp:25
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
Definition Builders.cpp:275
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
Definition Builders.cpp:327
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:633
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:351
This class helps build Operations.
Definition Builders.h:210
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:439
void setInsertionPointToStart(Block *block)
Sets the insertion point to the start of the specified block.
Definition Builders.h:434
void setInsertionPoint(Block *block, Block::iterator insertPoint)
Set the insertion point to the specified location.
Definition Builders.h:401
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
Definition Builders.cpp:466
void setInsertionPointAfter(Operation *op)
Sets the insertion point to the node after the specified operation, which will cause subsequent inser...
Definition Builders.h:415
This class represents a single result from folding an operation.
This class represents an operand of an operation.
Definition Value.h:254
unsigned getOperandNumber() const
Return which operand this is in the OpOperand list of the Operation.
Definition Value.cpp:226
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:397
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