23#include "llvm/ADT/TypeSwitch.h"
26#define GEN_PASS_DEF_LINALGSPECIALIZEGENERICOPSPASS
27#include "mlir/Dialect/Linalg/Passes.h.inc"
30#define DEBUG_TYPE "linalg-specialization"
51 Block *body = genericOp.getBody();
61 "binary op uses just one block arg");
85 Block *body = genericOp.getBody();
87 for (
auto [i, v] : llvm::enumerate(op->
getOperands())) {
88 if (
auto blockArg = dyn_cast<BlockArgument>(v);
89 blockArg && blockArg.getOwner() == body)
103 .Case<arith::AddFOp, arith::AddIOp, complex::AddOp>(
104 [](
Operation *) {
return ElementwiseKind::add; })
105 .Case<arith::MulIOp, arith::MulFOp, complex::MulOp>(
106 [](
Operation *) {
return ElementwiseKind::mul; })
107 .Case([](math::ExpOp) {
return ElementwiseKind::exp; })
108 .Case([](math::AbsFOp) {
return ElementwiseKind::abs; })
109 .Case([](math::CeilOp) {
return ElementwiseKind::ceil; })
110 .Case([](math::FloorOp) {
return ElementwiseKind::floor; })
111 .Case([](arith::NegFOp) {
return ElementwiseKind::negf; })
112 .Case([](math::RoundOp) {
return ElementwiseKind::round; })
113 .Case([](math::SqrtOp) {
return ElementwiseKind::sqrt; })
114 .Case([](math::RsqrtOp) {
return ElementwiseKind::rsqrt; })
115 .Case([](math::TanhOp) {
return ElementwiseKind::tanh; })
116 .Case([](math::ErfOp) {
return ElementwiseKind::erf; })
117 .Case([](math::SinOp) {
return ElementwiseKind::sin; })
118 .Case([](math::CosOp) {
return ElementwiseKind::cos; })
119 .Case([](math::TanOp) {
return ElementwiseKind::tan; })
120 .Case([](math::AcosOp) {
return ElementwiseKind::acos; })
121 .Case([](math::AcoshOp) {
return ElementwiseKind::acosh; })
122 .Case([](math::AsinOp) {
return ElementwiseKind::asin; })
123 .Case([](math::AsinhOp) {
return ElementwiseKind::asinh; })
124 .Case([](math::AtanOp) {
return ElementwiseKind::atan; })
125 .Case([](math::AtanhOp) {
return ElementwiseKind::atanh; })
126 .Case([](math::LogOp) {
return ElementwiseKind::log; })
127 .Case([](math::Log10Op) {
return ElementwiseKind::log10; })
128 .Case([](math::Log1pOp) {
return ElementwiseKind::log1p; })
129 .Case([](math::Log2Op) {
return ElementwiseKind::log2; })
130 .Case<arith::SubIOp, arith::SubFOp, complex::SubOp>(
131 [](
Operation *) {
return ElementwiseKind::sub; })
132 .Case<arith::DivSIOp, arith::DivFOp, complex::DivOp>(
133 [](
Operation *) {
return ElementwiseKind::div; })
134 .Case([](arith::DivUIOp) {
return ElementwiseKind::div_unsigned; })
135 .Case<arith::MaxSIOp, arith::MaximumFOp>(
136 [](
Operation *) {
return ElementwiseKind::max_signed; })
137 .Case<arith::MinSIOp, arith::MinimumFOp>(
138 [](
Operation *) {
return ElementwiseKind::min_signed; })
139 .Case([](math::PowFOp) {
return ElementwiseKind::powf; })
140 .Case([](arith::MaxUIOp) {
return ElementwiseKind::max_unsigned; })
141 .Case([](arith::MinUIOp) {
return ElementwiseKind::min_unsigned; })
142 .Case([](arith::SelectOp) {
return ElementwiseKind::select; })
143 .Default([](
Operation *) {
return std::nullopt; });
173 GenericOp genericOp) {
175 unsigned arity = genericOp.getNumDpsInputs();
176 bool isUnary = arity == 1;
177 bool isBinary = arity == 2;
178 bool isTernary = arity == 3;
181 Operation *op = &genericOp.getBody()->front();
184 bool hasSwappedOperands =
186 int scalarOprIdx = -1;
193 auto replaceOp = [&](ElementwiseKind kind,
194 bool mayHoistScalarOperand =
true) -> LinalgOp {
197 if (hasSwappedOperands) {
201 std::swap(inputs[0 + isTernary], inputs[1 + isTernary]);
202 std::swap(indexingMaps[0 + isTernary], indexingMaps[1 + isTernary]);
205 if (hasScalarOperand && mayHoistScalarOperand) {
207 inputs.insert(inputs.begin() + scalarOprIdx,
209 auto scalarBroadcastMap =
212 indexingMaps.insert(indexingMaps.begin() + scalarOprIdx,
215 auto newOp = ElementwiseOp::create(
216 rewriter, genericOp.getLoc(), inputs, genericOp.getDpsInits(), kind,
224 if (
auto divOp = dyn_cast<arith::DivFOp>(op)) {
225 if (
auto constOp = dyn_cast_if_present<arith::ConstantOp>(
226 divOp.getLhs().getDefiningOp()))
227 if (cast<FloatAttr>(constOp.getValue()).getValue().isExactlyValue(1.0))
228 return replaceOp(ElementwiseKind::reciprocal,
233 if (
auto mulOp = dyn_cast<arith::MulFOp>(op))
234 if (mulOp.getLhs() == mulOp.getRhs())
235 return replaceOp(ElementwiseKind::square);
239 return v.getType().isInteger(1);
241 if (isa<arith::OrIOp>(op))
242 return replaceOp(ElementwiseKind::add);
243 if (isa<arith::AndIOp>(op))
244 return replaceOp(ElementwiseKind::mul);
251 unsigned numInputs = arity + (hasScalarOperand ? 1 : 0);
252 if (numInputs ==
static_cast<unsigned>(arityGroupAndKind.arityGroup))
253 return replaceOp(*kind);
257 genericOp,
"elementwise operation cannot be specialized to category op");
266static FailureOr<LinalgOp>
269 auto reduceOp = ReduceOp::create(
270 rewriter, genericOp.getLoc(), genericOp.getDpsInputs(),
271 genericOp.getDpsInits(), reductionDims,
nullptr);
275 reduceOp.getCombiner().end());
277 return cast<LinalgOp>(reduceOp.getOperation());
306enum class IndexMatchResult {
320static IndexMatchResult matchOperandMap(
AffineMap map,
unsigned rowDimIdx,
321 unsigned expectedPosOfRowDim,
322 unsigned expectedPosOfColDim) {
324 auto exprOfRowDim = map.
getResults()[rowDimIdx];
325 auto exprOfColDim = map.
getResults()[rowDimIdx + 1];
330 return IndexMatchResult::Mismatch;
332 auto posRowDim = cast<AffineDimExpr>(exprOfRowDim).getPosition();
333 auto posColDim = cast<AffineDimExpr>(exprOfColDim).getPosition();
335 if (expectedPosOfRowDim == posRowDim && expectedPosOfColDim == posColDim)
336 return IndexMatchResult::Match;
338 if (expectedPosOfRowDim == posColDim && expectedPosOfColDim == posRowDim)
339 return IndexMatchResult::Transposed;
341 return IndexMatchResult::Mismatch;
350template <
typename NamedOpTy>
351static LinalgOp replaceWithMatmulVariant(
RewriterBase &rewriter, GenericOp op,
352 std::optional<TypeFn> castTy,
357 if (castTy.has_value() && *castTy == TypeFn::cast_unsigned) {
359 "cast", TypeFnAttr::get(rewriter.
getContext(), *castTy));
360 attributes.push_back(castAttr);
367 return AffineMapAttr::get(map);
370 "indexing_maps", rewriter.
getArrayAttr(indexingMapsAttrVal));
371 attributes.push_back(indexingMapsAttr);
374 op,
ValueRange{op.getDpsInputs()[0], op.getDpsInputs()[1]},
383static std::optional<TypeFn> getCastTypeForMatmulLikeOp(GenericOp genericOp) {
384 bool foundCastForMatmulOutput =
false;
386 genericOp.getBody()->walk([&](CastOpInterface castOp) {
395 if (!llvm::any_of(forwardSlice, [](
Operation *op) {
398 return isa<arith::MulIOp, arith::MulFOp, complex::MulOp>(op);
400 foundCastForMatmulOutput =
true;
405 if (isa<arith::ExtUIOp, arith::UIToFPOp, arith::FPToUIOp>(castOp))
406 castTyFns.push_back(TypeFn::cast_unsigned);
407 else if (isa<arith::ExtSIOp, arith::SIToFPOp, arith::FPToSIOp>(castOp))
408 castTyFns.push_back(TypeFn::cast_signed);
413 if (foundCastForMatmulOutput)
416 if (!castTyFns.empty()) {
420 if (!llvm::all_equal(castTyFns))
422 return castTyFns.front();
426 return TypeFn::cast_signed;
429static FailureOr<LinalgOp> specializeLinalgMmt4D(
RewriterBase &rewriter,
431 std::optional<TypeFn> castTy,
434 auto indexingMaps = genericOp.getIndexingMapsArray();
435 if (llvm::any_of(indexingMaps, [](
AffineMap m) {
440 auto aOuter = matchOperandMap(indexingMaps[0], 0, dims.
m[0], dims.
k[0]);
441 auto aInner = matchOperandMap(indexingMaps[0], 2, dims.
m[1], dims.
k[1]);
443 auto bOuter = matchOperandMap(indexingMaps[1], 0, dims.
k[0], dims.
n[0]);
444 auto bInner = matchOperandMap(indexingMaps[1], 2, dims.
k[1], dims.
n[1]);
446 auto cOuter = matchOperandMap(indexingMaps[2], 0, dims.
m[0], dims.
n[0]);
447 auto cInner = matchOperandMap(indexingMaps[2], 2, dims.
m[1], dims.
n[1]);
449 if (llvm::is_contained({aOuter, bOuter, cOuter}, IndexMatchResult::Mismatch))
451 if (llvm::is_contained({aInner, bInner, cInner}, IndexMatchResult::Mismatch))
457 return replaceWithMatmulVariant<Mmt4DOp>(rewriter, genericOp, castTy,
465static FailureOr<LinalgOp>
466specializeToNamedContraction(
RewriterBase &rewriter, GenericOp genericOp,
467 std::optional<TypeFn> castTy) {
491 if (dims.
m.size() == 2 && dims.
n.size() == 2 && dims.
k.size() == 2)
492 return specializeLinalgMmt4D(rewriter, genericOp, castTy, dims);
493 if (dims.
m.size() != 1 || dims.
n.size() != 1 || dims.
k.size() != 1)
497 auto indexingMaps = genericOp.getIndexingMapsArray();
498 if (llvm::any_of(indexingMaps, [&dims](
AffineMap m) {
500 dims.
batch.size() + 2 ;
504 auto numOfBatchDims = dims.
batch.size();
505 if (indexingMaps[0].getNumDims() != numOfBatchDims + 3)
508 if (numOfBatchDims) {
512 if (llvm::any_of(indexingMaps, [numOfBatchDims](
AffineMap m) {
513 for (
unsigned i = 0; i < numOfBatchDims; ++i) {
516 cast<AffineDimExpr>(expr).getPosition() != i)
525 matchOperandMap(indexingMaps[0], numOfBatchDims, dims.
m[0], dims.
k[0]);
527 matchOperandMap(indexingMaps[1], numOfBatchDims, dims.
k[0], dims.
n[0]);
529 matchOperandMap(indexingMaps[2], numOfBatchDims, dims.
m[0], dims.
n[0]);
531 if (llvm::is_contained({a,
b, c}, IndexMatchResult::Mismatch))
535 auto *ctx = genericOp.getContext();
536 unsigned numLoopDims = numOfBatchDims + 3;
537 unsigned mIdx = numOfBatchDims;
538 unsigned nIdx = mIdx + 1;
539 unsigned kIdx = mIdx + 2;
542 auto makeMap = [&](IndexMatchResult match,
unsigned rowIdx,
unsigned colIdx) {
544 for (
unsigned i = 0; i < numOfBatchDims; ++i)
545 tensorDims.push_back(i);
546 if (match == IndexMatchResult::Transposed)
547 llvm::append_values(tensorDims, colIdx, rowIdx);
549 llvm::append_values(tensorDims, rowIdx, colIdx);
553 auto mapA = makeMap(a, mIdx, kIdx);
554 auto mapB = makeMap(
b, kIdx, nIdx);
555 auto mapC = makeMap(c, mIdx, nIdx);
560 if (numOfBatchDims) {
561 return replaceWithMatmulVariant<BatchMatmulOp>(rewriter, genericOp, castTy,
564 return replaceWithMatmulVariant<MatmulOp>(rewriter, genericOp, castTy,
570static FailureOr<LinalgOp> specializeLinalgContractions(
RewriterBase &rewriter,
572 bool emitCategoryOp) {
574 auto mapRange = genericOp.getIndexingMapsArray();
575 if (llvm::any_of(mapRange,
581 std::optional<TypeFn> castTy = getCastTypeForMatmulLikeOp(genericOp);
584 genericOp,
"contains invalid cast ops for the named matmul op");
590 if (!emitCategoryOp) {
591 FailureOr<LinalgOp> namedOp =
592 specializeToNamedContraction(rewriter, genericOp, castTy);
593 if (succeeded(namedOp))
599 return replaceWithMatmulVariant<ContractOp>(rewriter, genericOp, castTy,
600 genericOp.getIndexingMapsArray());
605template <
typename ConvOpTy>
606static FailureOr<LinalgOp>
607specializeToConvOp(
RewriterBase &rewriter, GenericOp genericOp,
616 if constexpr (std::is_same_v<ConvOpTy, linalg::Conv1DOp> ||
617 std::is_same_v<ConvOpTy, linalg::Conv2DOp> ||
618 std::is_same_v<ConvOpTy, linalg::Conv3DOp>) {
625 genericOp, resultTypes, inputs, outputs, stridesAttr, dilationsAttr);
631static FailureOr<LinalgOp> specializeLinalgConvolutions(
RewriterBase &rewriter,
632 GenericOp genericOp) {
633#define CONV_OP_SPECIALIZER(ConvOpTy) \
634 if (std::optional<DilationsAndStrides> convParams = \
635 matchConvolutionOpOfType<ConvOpTy>(genericOp)) \
636 return specializeToConvOp<ConvOpTy>( \
637 rewriter, genericOp, convParams->dilations, convParams->strides); \
694#undef CONV_OP_SPECIALIZER
705 bool emitCategoryOps) {
715 return specializeLinalgContractions(rewriter, genericOp, emitCategoryOps);
723 genericOp,
"no matching category op specialization");
728 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0]);
736 genericOp, *fillValue, genericOp.getDpsInits()[0]);
741 std::optional<SmallVector<int64_t>> equivalentToBroadcast =
743 if (equivalentToBroadcast) {
744 auto dims = *equivalentToBroadcast;
746 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0],
752 std::optional<SmallVector<int64_t>> equivalentToTranspose =
754 if (equivalentToTranspose) {
755 auto permutation = *equivalentToTranspose;
757 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0],
770 return specializeLinalgConvolutions(rewriter, genericOp);
773 "no matching named op specialization");
777struct LinalgSpecializeGenericOpsPass
779 LinalgSpecializeGenericOpsPass> {
782 LinalgSpecializeGenericOpsPass>::LinalgSpecializeGenericOpsPassBase;
783 void runOnOperation()
override;
787void LinalgSpecializeGenericOpsPass::runOnOperation() {
static bool findIndexOfScalarOperand(GenericOp genericOp, int &index)
static std::optional< ElementwiseKind > getElementwiseKind(Operation *op)
static FailureOr< LinalgOp > specializeLinalgReductions(RewriterBase &rewriter, GenericOp genericOp, ArrayRef< int64_t > reductionDims)
static bool areBinOpsSwapped(GenericOp genericOp, bool isTernary)
#define CONV_OP_SPECIALIZER(ConvOpTy)
static FailureOr< LinalgOp > specializeLinalgElementwise(RewriterBase &rewriter, GenericOp genericOp)
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
unsigned getNumDims() const
ArrayRef< AffineExpr > getResults() const
static AffineMap getMultiDimMapWithTargets(unsigned numDims, ArrayRef< unsigned > targets, MLIRContext *context)
Returns an affine map with numDims input dimensions and results specified by targets.
Attributes are known-constant values of operations.
Block represents an ordered list of Operations.
BlockArgument getArgument(unsigned i)
DenseIntElementsAttr getI64TensorAttr(ArrayRef< int64_t > values)
ArrayAttr getArrayAttr(ArrayRef< Attribute > value)
MLIRContext * getContext() const
NamedAttribute getNamedAttr(StringRef name, Attribute val)
ArrayAttr getAffineMapArrayAttr(ArrayRef< AffineMap > values)
IRValueT get() const
Return the current value being used by this operand.
Operation is the basic unit of execution within MLIR.
Value getOperand(unsigned idx)
unsigned getNumOperands()
operand_range getOperands()
Returns an iterator on the underlying Value's.
OpOperand & getOpOperand(unsigned idx)
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
This class coordinates the application of a rewrite on a set of IR, providing a way for clients to tr...
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
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 inlineRegionBefore(Region ®ion, Region &parent, Region::iterator before)
Move the blocks that belong to "region" before the given position in another region "parent".
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
This class provides an abstraction over the various different ranges of value types.
This class provides an abstraction over the different types of ranges over Values.
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
static WalkResult advance()
static WalkResult interrupt()
std::optional< SmallVector< int64_t > > isaTransposeOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a linalg.transpose.
bool isaElemwiseSingleUnaryOpInterface(GenericOp genericOp)
Checks whether a given genericOp is semantically equivalent to a single linalg elementwise unary op,...
bool isaCopyOpInterface(LinalgOp linalgOp)
Checks whether linalgOp is semantically equivalent to a linalg.copyOp.
void populateLinalgGenericOpsSpecializationPatterns(RewritePatternSet &patterns, bool emitCategoryOps=false)
Populates patterns with patterns to convert linalg.generic ops to named or category ops where possibl...
void populateDecomposeProjectedPermutationPatterns(RewritePatternSet &patterns)
Add patterns to make explicit broadcasts and transforms in the input operands of a genericOp.
std::optional< SmallVector< int64_t > > isaReductionOpInterface(GenericOp genericOp)
Checks whether the genericOp is semantically equivalent to a linalg.reduce.
bool isaConvolutionOpInterface(LinalgOp linalgOp, bool allowEmptyConvolvedDims=false)
Checks whether linalgOp conforms to ConvolutionOpInterface.
bool isaElemwiseSingleTernaryOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a single linalg elementwise ternary op e....
FailureOr< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, bool emitCategoryOps=false)
Replace the given GenericOp with a namedOp or categoryOp.
std::optional< SmallVector< int64_t > > isaBroadcastOpInterface(LinalgOp linalgOp)
Checks whether linalgOp is semantically equivalent to a broadcast operation.
FailureOr< ContractionDimensions > inferContractionDims(LinalgOp linalgOp)
Find at least 2 parallel (m and n) and 1 reduction (k) dimension candidates that form a matmul subcom...
bool isaContractionOpInterface(LinalgOp linalgOp)
Checks whether linalgOp conforms to ContractionOpInterface.
ArityGroupAndKind getArityGroupAndKind(ElementwiseKind kind)
std::optional< Value > isaFillOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a linalg.fill.
bool isaElemwiseSingleBinaryOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a single linalg elementwise binary op e....
Include the generated interface declarations.
LogicalResult applyPatternsGreedily(Region ®ion, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
@ DimId
Dimensional identifier.
llvm::SetVector< T, Vector, Set, N > SetVector
void getForwardSlice(Operation *op, SetVector< Operation * > *forwardSlice, const ForwardSliceOptions &options={})
Fills forwardSlice with the computed forward slice (i.e.
Positions of a Linalg op loops that correspond to different kinds of a contraction dimension.
SmallVector< unsigned, 2 > batch
SmallVector< unsigned, 2 > m
SmallVector< unsigned, 2 > n
SmallVector< unsigned, 2 > k