25#define GEN_PASS_DEF_LINALGSPECIALIZEGENERICOPSPASS
26#include "mlir/Dialect/Linalg/Passes.h.inc"
29#define DEBUG_TYPE "linalg-specialization"
50 Block *body = genericOp.getBody();
57 "binary op uses just one block arg");
81 Block *body = genericOp.getBody();
83 for (
auto [i, v] : llvm::enumerate(op->
getOperands())) {
84 if (
auto blockArg = dyn_cast<BlockArgument>(v);
85 blockArg && blockArg.getOwner() == body)
124 bool emitCategoryOp) {
125 bool hasNonIdentityMaps =
126 !llvm::all_of(genericOp.getIndexingMapsArray(),
127 [](
AffineMap map) { return map.isIdentity(); });
130 if (hasNonIdentityMaps && !emitCategoryOp)
133 "non-identity indexing maps prevent specialization to named op");
136 bool isUnary = genericOp.getNumDpsInputs() == 1;
137 bool isBinary = genericOp.getNumDpsInputs() == 2;
140 Operation *op = &genericOp.getBody()->front();
144 int scalarOprIdx = -1;
151 auto replaceOp = [&](
auto namedOp, ElementwiseKind kind,
152 bool mayHoistScalarOperand =
true) -> LinalgOp {
154 if (hasSwappedOperands)
155 std::swap(inputs[0], inputs[1]);
158 using NamedOpTy =
decltype(namedOp);
161 if (!std::is_null_pointer_v<NamedOpTy>) {
162 if constexpr (!std::is_null_pointer_v<NamedOpTy>)
163 newOp = NamedOpTy::create(rewriter, genericOp.getLoc(), inputs,
164 genericOp.getDpsInits(),
169 if (hasSwappedOperands)
170 std::swap(indexingMaps[0], indexingMaps[1]);
174 if (hasScalarOperand && mayHoistScalarOperand) {
176 inputs.insert(inputs.begin() + scalarOprIdx,
178 auto scalarBroadcastMap =
181 indexingMaps.insert(indexingMaps.begin() + scalarOprIdx,
184 newOp = ElementwiseOp::create(
185 rewriter, genericOp.getLoc(), inputs, genericOp.getDpsInits(), kind,
195 if (emitCategoryOp) {
196 if (isa<math::ExpOp>(op))
197 return replaceOp(
nullptr, ElementwiseKind::exp);
198 if (isa<math::AbsFOp>(op))
199 return replaceOp(
nullptr, ElementwiseKind::abs);
200 if (isa<math::CeilOp>(op))
201 return replaceOp(
nullptr, ElementwiseKind::ceil);
202 if (isa<math::FloorOp>(op))
203 return replaceOp(
nullptr, ElementwiseKind::floor);
204 if (isa<arith::NegFOp>(op))
205 return replaceOp(
nullptr, ElementwiseKind::negf);
206 if (
auto divOp = dyn_cast<arith::DivFOp>(op)) {
207 if (
auto constOp = dyn_cast_if_present<arith::ConstantOp>(
208 divOp.getLhs().getDefiningOp()))
209 if (cast<FloatAttr>(constOp.getValue()).getValue().isExactlyValue(1.0))
210 return replaceOp(
nullptr, ElementwiseKind::reciprocal,
213 if (isa<math::RoundOp>(op))
214 return replaceOp(
nullptr, ElementwiseKind::round);
215 if (isa<math::SqrtOp>(op))
216 return replaceOp(
nullptr, ElementwiseKind::sqrt);
217 if (isa<math::RsqrtOp>(op))
218 return replaceOp(
nullptr, ElementwiseKind::rsqrt);
219 if (
auto mulOp = dyn_cast<arith::MulFOp>(op);
220 mulOp && mulOp.getLhs() == mulOp.getRhs())
221 return replaceOp(
nullptr, ElementwiseKind::square);
222 if (isa<math::TanhOp>(op))
223 return replaceOp(
nullptr, ElementwiseKind::tanh);
224 if (isa<math::ErfOp>(op))
225 return replaceOp(
nullptr, ElementwiseKind::erf);
226 if (isa<math::SinOp>(op))
227 return replaceOp(
nullptr, ElementwiseKind::sin);
228 if (isa<math::CosOp>(op))
229 return replaceOp(
nullptr, ElementwiseKind::cos);
230 if (isa<math::TanOp>(op))
231 return replaceOp(
nullptr, ElementwiseKind::tan);
232 if (isa<math::AcosOp>(op))
233 return replaceOp(
nullptr, ElementwiseKind::acos);
234 if (isa<math::AcoshOp>(op))
235 return replaceOp(
nullptr, ElementwiseKind::acosh);
236 if (isa<math::AsinOp>(op))
237 return replaceOp(
nullptr, ElementwiseKind::asin);
238 if (isa<math::AsinhOp>(op))
239 return replaceOp(
nullptr, ElementwiseKind::asinh);
240 if (isa<math::AtanOp>(op))
241 return replaceOp(
nullptr, ElementwiseKind::atan);
242 if (isa<math::AtanhOp>(op))
243 return replaceOp(
nullptr, ElementwiseKind::atanh);
244 if (isa<math::LogOp>(op))
245 return replaceOp(
nullptr, ElementwiseKind::log);
246 if (isa<math::Log10Op>(op))
247 return replaceOp(
nullptr, ElementwiseKind::log10);
248 if (isa<math::Log1pOp>(op))
249 return replaceOp(
nullptr, ElementwiseKind::log1p);
250 if (isa<math::Log2Op>(op))
251 return replaceOp(
nullptr, ElementwiseKind::log2);
257 if (isUnary && !hasScalarOperand)
259 genericOp,
"unary elementwise operation cannot be specialized to a "
263 bool allBool = llvm::all_of(
266 if (isa<arith::AddFOp, arith::AddIOp, complex::AddOp>(op) ||
267 (allBool && isa<arith::OrIOp>(op)))
268 return replaceOp(
nullptr, ElementwiseKind::add);
269 if (isa<arith::SubIOp, arith::SubFOp, complex::SubOp>(op))
270 return replaceOp(
nullptr, ElementwiseKind::sub);
271 if (isa<arith::MulIOp, arith::MulFOp, complex::MulOp>(op) ||
272 (allBool && isa<arith::AndIOp>(op)))
273 return replaceOp(
nullptr, ElementwiseKind::mul);
274 if (isa<arith::DivSIOp, arith::DivFOp, complex::DivOp>(op))
275 return replaceOp(
nullptr, ElementwiseKind::div);
276 if (isa<arith::DivUIOp>(op))
277 return replaceOp(
nullptr, ElementwiseKind::div_unsigned);
278 if (isa<arith::MaxSIOp, arith::MaximumFOp>(op))
279 return replaceOp(
nullptr, ElementwiseKind::max_signed);
280 if (isa<arith::MinSIOp, arith::MinimumFOp>(op))
281 return replaceOp(
nullptr, ElementwiseKind::min_signed);
282 if (isa<math::PowFOp>(op))
283 return replaceOp(
nullptr, ElementwiseKind::powf);
285 if (isa<arith::MaxUIOp>(op))
286 return replaceOp(
nullptr, ElementwiseKind::max_unsigned);
287 if (isa<arith::MinUIOp>(op))
288 return replaceOp(
nullptr, ElementwiseKind::min_unsigned);
293 "elementwise operation cannot be specialized to named or category op");
322enum class IndexMatchResult {
336static IndexMatchResult matchOperandMap(
AffineMap map,
unsigned rowDimIdx,
337 unsigned expectedPosOfRowDim,
338 unsigned expectedPosOfColDim) {
340 auto exprOfRowDim = map.
getResults()[rowDimIdx];
341 auto exprOfColDim = map.
getResults()[rowDimIdx + 1];
346 return IndexMatchResult::Mismatch;
348 auto posRowDim = cast<AffineDimExpr>(exprOfRowDim).getPosition();
349 auto posColDim = cast<AffineDimExpr>(exprOfColDim).getPosition();
351 if (expectedPosOfRowDim == posRowDim && expectedPosOfColDim == posColDim)
352 return IndexMatchResult::Match;
354 if (expectedPosOfRowDim == posColDim && expectedPosOfColDim == posRowDim)
355 return IndexMatchResult::Transposed;
357 return IndexMatchResult::Mismatch;
366template <
typename NamedOpTy>
367static LinalgOp replaceWithMatmulVariant(
RewriterBase &rewriter, GenericOp op,
368 std::optional<TypeFn> castTy,
373 if (castTy.has_value() && *castTy == TypeFn::cast_unsigned) {
375 "cast", TypeFnAttr::get(rewriter.
getContext(), *castTy));
376 attributes.push_back(castAttr);
383 return AffineMapAttr::get(map);
386 "indexing_maps", rewriter.
getArrayAttr(indexingMapsAttrVal));
387 attributes.push_back(indexingMapsAttr);
390 op,
ValueRange{op.getDpsInputs()[0], op.getDpsInputs()[1]},
399static std::optional<TypeFn> getCastTypeForMatmulLikeOp(GenericOp genericOp) {
400 bool foundCastForMatmulOutput =
false;
402 genericOp.getBody()->walk([&](CastOpInterface castOp) {
411 if (!llvm::any_of(forwardSlice, [](
Operation *op) {
414 return isa<arith::MulIOp, arith::MulFOp, complex::MulOp>(op);
416 foundCastForMatmulOutput =
true;
421 if (isa<arith::ExtUIOp, arith::UIToFPOp, arith::FPToUIOp>(castOp))
422 castTyFns.push_back(TypeFn::cast_unsigned);
423 else if (isa<arith::ExtSIOp, arith::SIToFPOp, arith::FPToSIOp>(castOp))
424 castTyFns.push_back(TypeFn::cast_signed);
429 if (foundCastForMatmulOutput)
432 if (!castTyFns.empty()) {
436 if (!llvm::all_equal(castTyFns))
438 return castTyFns.front();
442 return TypeFn::cast_signed;
445static FailureOr<LinalgOp> specializeLinalgMmt4D(
RewriterBase &rewriter,
447 std::optional<TypeFn> castTy,
450 auto indexingMaps = genericOp.getIndexingMapsArray();
451 if (llvm::any_of(indexingMaps, [](
AffineMap m) {
456 auto aOuter = matchOperandMap(indexingMaps[0], 0, dims.
m[0], dims.
k[0]);
457 auto aInner = matchOperandMap(indexingMaps[0], 2, dims.
m[1], dims.
k[1]);
459 auto bOuter = matchOperandMap(indexingMaps[1], 0, dims.
k[0], dims.
n[0]);
460 auto bInner = matchOperandMap(indexingMaps[1], 2, dims.
k[1], dims.
n[1]);
462 auto cOuter = matchOperandMap(indexingMaps[2], 0, dims.
m[0], dims.
n[0]);
463 auto cInner = matchOperandMap(indexingMaps[2], 2, dims.
m[1], dims.
n[1]);
465 if (llvm::is_contained({aOuter, bOuter, cOuter}, IndexMatchResult::Mismatch))
467 if (llvm::is_contained({aInner, bInner, cInner}, IndexMatchResult::Mismatch))
473 return replaceWithMatmulVariant<Mmt4DOp>(rewriter, genericOp, castTy,
478 if (isa<arith::MulFOp>(first) && isa<arith::AddFOp>(second))
480 if (isa<arith::MulIOp>(first) && isa<arith::AddIOp>(second))
482 if (isa<complex::MulOp>(first) && isa<complex::AddOp>(second))
484 if (isa<arith::AndIOp>(first) && isa<arith::OrIOp>(second) &&
492static FailureOr<LinalgOp> specializeLinalgContractions(
RewriterBase &rewriter,
494 bool emitCategoryOp) {
495 if (genericOp.getNumDpsInputs() != 2 || genericOp.getNumDpsInits() != 1)
499 auto mapRange = genericOp.getIndexingMapsArray();
500 if (llvm::any_of(mapRange,
509 isSupportedContractionPair))
514 std::optional<TypeFn> castTy = getCastTypeForMatmulLikeOp(genericOp);
517 genericOp,
"contains invalid cast ops for the named matmul op");
521 return replaceWithMatmulVariant<ContractOp>(
522 rewriter, genericOp, castTy, genericOp.getIndexingMapsArray());
549 if (dims.
m.size() == 2 && dims.
n.size() == 2 && dims.
k.size() == 2)
550 return specializeLinalgMmt4D(rewriter, genericOp, castTy, dims);
551 if (dims.
m.size() != 1 || dims.
n.size() != 1 || dims.
k.size() != 1)
555 auto indexingMaps = genericOp.getIndexingMapsArray();
556 if (llvm::any_of(indexingMaps, [&dims](
AffineMap m) {
558 dims.
batch.size() + 2 ;
562 auto numOfBatchDims = dims.
batch.size();
563 if (indexingMaps[0].getNumDims() != numOfBatchDims + 3)
566 if (numOfBatchDims) {
570 if (llvm::any_of(indexingMaps, [numOfBatchDims](
AffineMap m) {
571 for (
unsigned i = 0; i < numOfBatchDims; ++i) {
574 cast<AffineDimExpr>(expr).getPosition() != i)
583 matchOperandMap(indexingMaps[0], numOfBatchDims, dims.
m[0], dims.
k[0]);
585 matchOperandMap(indexingMaps[1], numOfBatchDims, dims.
k[0], dims.
n[0]);
587 matchOperandMap(indexingMaps[2], numOfBatchDims, dims.
m[0], dims.
n[0]);
589 if (llvm::is_contained({a,
b, c}, IndexMatchResult::Mismatch))
593 auto *ctx = genericOp.getContext();
594 unsigned numLoopDims = numOfBatchDims + 3;
595 unsigned mIdx = numOfBatchDims;
596 unsigned nIdx = mIdx + 1;
597 unsigned kIdx = mIdx + 2;
600 auto makeMap = [&](IndexMatchResult match,
unsigned rowIdx,
unsigned colIdx) {
602 for (
unsigned i = 0; i < numOfBatchDims; ++i)
603 tensorDims.push_back(i);
604 if (match == IndexMatchResult::Transposed)
605 llvm::append_values(tensorDims, colIdx, rowIdx);
607 llvm::append_values(tensorDims, rowIdx, colIdx);
611 auto mapA = makeMap(a, mIdx, kIdx);
612 auto mapB = makeMap(
b, kIdx, nIdx);
613 auto mapC = makeMap(c, mIdx, nIdx);
618 if (numOfBatchDims) {
619 return replaceWithMatmulVariant<BatchMatmulOp>(rewriter, genericOp, castTy,
622 return replaceWithMatmulVariant<MatmulOp>(rewriter, genericOp, castTy,
628template <
typename ConvOpTy>
629static FailureOr<LinalgOp>
630specializeToConvOp(
RewriterBase &rewriter, GenericOp genericOp,
639 if constexpr (std::is_same_v<ConvOpTy, linalg::Conv1DOp> ||
640 std::is_same_v<ConvOpTy, linalg::Conv2DOp> ||
641 std::is_same_v<ConvOpTy, linalg::Conv3DOp>) {
648 genericOp, resultTypes, inputs, outputs, stridesAttr, dilationsAttr);
654static FailureOr<LinalgOp> specializeLinalgConvolutions(
RewriterBase &rewriter,
655 GenericOp genericOp) {
656#define CONV_OP_SPECIALIZER(ConvOpTy) \
657 if (std::optional<DilationsAndStrides> convParams = \
658 matchConvolutionOpOfType<ConvOpTy>(genericOp)) \
659 return specializeToConvOp<ConvOpTy>( \
660 rewriter, genericOp, convParams->dilations, convParams->strides); \
717#undef CONV_OP_SPECIALIZER
738 return specializeLinalgContractions(rewriter, genericOp,
747 genericOp,
"no matching category op specialization");
752 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0]);
760 genericOp, *fillValue, genericOp.getDpsInits()[0]);
765 std::optional<SmallVector<int64_t>> equivalentToBroadcast =
767 if (equivalentToBroadcast) {
768 auto dims = *equivalentToBroadcast;
770 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0],
776 std::optional<SmallVector<int64_t>> equivalentToTranspose =
778 if (equivalentToTranspose) {
779 auto permutation = *equivalentToTranspose;
781 genericOp, genericOp.getDpsInputs()[0], genericOp.getDpsInits()[0],
788 return specializeLinalgConvolutions(rewriter, genericOp);
791 "no matching named op specialization");
795struct LinalgSpecializeGenericOpsPass
796 :
public impl::LinalgSpecializeGenericOpsPassBase<
797 LinalgSpecializeGenericOpsPass> {
799 using impl::LinalgSpecializeGenericOpsPassBase<
800 LinalgSpecializeGenericOpsPass>::LinalgSpecializeGenericOpsPassBase;
801 void runOnOperation()
override;
805void LinalgSpecializeGenericOpsPass::runOnOperation() {
static llvm::ManagedStatic< PassManagerOptions > options
static bool findIndexOfScalarOperand(GenericOp genericOp, int &index)
#define CONV_OP_SPECIALIZER(ConvOpTy)
static bool areBinOpsSwapped(GenericOp genericOp)
static FailureOr< LinalgOp > specializeLinalgElementwise(RewriterBase &rewriter, GenericOp genericOp, bool emitCategoryOp)
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)
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
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,...
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.
bool isInteger() const
Return true if this is an integer type (with the specified width).
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...
Type getType() const
Return the type of this value.
static WalkResult advance()
static WalkResult interrupt()
bool isContractionBody(Block &block, function_ref< bool(Operation *, Operation *)> isaPair, llvm::raw_ostream &errs=mlir::thread_safe_nulls())
Returns true if the block contains a contraction of the following form:
std::optional< SmallVector< int64_t > > isaTransposeOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a linalg.transpose.
bool isaCopyOpInterface(LinalgOp linalgOp)
Checks whether linalgOp is semantically equivalent to a linalg.copyOp.
bool isaElemwiseSingleBinaryOpInterface(GenericOp genericOp, bool allowNonIdentityMaps=false)
Checks whether genericOp is semantically equivalent to a single linalg elementwise binary op e....
void populateDecomposeProjectedPermutationPatterns(RewritePatternSet &patterns)
Add patterns to make explicit broadcasts and transforms in the input operands of a genericOp.
FailureOr< LinalgOp > specializeGenericOp(RewriterBase &rewriter, GenericOp genericOp, const GenericOpSpecializationOptions &options={})
Replace the given GenericOp with a namedOp or categoryOp.
bool isaConvolutionOpInterface(LinalgOp linalgOp, bool allowEmptyConvolvedDims=false)
Checks whether linalgOp conforms to ConvolutionOpInterface.
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.
bool isaElemwiseSingleUnaryOpInterface(GenericOp genericOp, bool allowNonIdentityMaps=false)
Checks whether a given genericOp is semantically equivalent to a single linalg elementwise unary op,...
std::optional< Value > isaFillOpInterface(GenericOp genericOp)
Checks whether genericOp is semantically equivalent to a linalg.fill.
void populateLinalgGenericOpsSpecializationPatterns(RewritePatternSet &patterns, const GenericOpSpecializationOptions &options={})
Populates patterns with patterns to convert linalg.generic ops to named or category ops where possibl...
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