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