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