| // Copyright 2025 The IREE Authors |
| // |
| // Licensed under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| |
| #include "iree/compiler/GlobalOptimization/Passes.h" |
| #include "mlir/Dialect/Linalg/IR/Linalg.h" |
| #include "mlir/Dialect/Linalg/Transforms/Transforms.h" |
| #include "mlir/IR/AffineExpr.h" |
| #include "mlir/IR/PatternMatch.h" |
| #include "mlir/Pass/Pass.h" |
| #include "mlir/Transforms/WalkPatternRewriteDriver.h" |
| |
| namespace mlir::iree_compiler::GlobalOptimization { |
| |
| #define GEN_PASS_DEF_CONVERTSTRIDEDCONTRACTIONTOCONTRACTIONPASS |
| #include "iree/compiler/GlobalOptimization/Passes.h.inc" |
| |
| namespace { |
| |
| class ConvertStridedContractionToContraction |
| : public OpRewritePattern<linalg::GenericOp> { |
| public: |
| using Base::Base; |
| LogicalResult matchAndRewrite(linalg::GenericOp op, |
| PatternRewriter &rewriter) const override { |
| // Check if the generic op satisfies all other conditions for being a |
| // contraction. |
| if (op.getNumDpsInputs() != 2 || op.getNumDpsInits() != 1) { |
| return failure(); |
| } |
| if (op.getNumReductionLoops() == 0) { |
| return failure(); |
| } |
| if (!mlir::linalg::detail::isContractionBody( |
| *op.getBlock(), [](Operation *first, Operation *second) { |
| if ((isa<arith::MulFOp>(first) && isa<arith::AddFOp>(second)) || |
| (isa<arith::MulIOp>(first) && isa<arith::AddIOp>(second))) { |
| return true; |
| } |
| return false; |
| })) { |
| return failure(); |
| } |
| |
| SmallVector<AffineMap> mapRange = op.getIndexingMapsArray(); |
| unsigned inputPos = op.getDpsInputOperand(0)->getOperandNumber(); |
| unsigned filterPos = op.getDpsInputOperand(1)->getOperandNumber(); |
| unsigned resInitPos = op.getDpsInitOperand(0)->getOperandNumber(); |
| AffineMap inputMap = mapRange[inputPos]; |
| AffineMap filterMap = mapRange[filterPos]; |
| AffineMap resultMap = mapRange[resInitPos]; |
| // For now, we are only handling the case where the first input is the |
| // only non-projected permutation. |
| if (!filterMap.isProjectedPermutation() || |
| !resultMap.isProjectedPermutation()) { |
| return failure(); |
| } |
| if (inputMap.isProjectedPermutation()) { |
| return failure(); |
| } |
| SmallVector<int64_t> staticShape = op.getStaticLoopRanges(); |
| |
| llvm::SmallDenseMap<unsigned, int64_t> strides; |
| SmallVector<AffineExpr> replacementExprs; |
| Value input = op.getDpsInputs()[0]; |
| auto inputTy = dyn_cast<RankedTensorType>(input.getType()); |
| if (!inputTy) { |
| return failure(); |
| } |
| SmallVector<int64_t> inputShape(inputTy.getShape()); |
| replacementExprs.reserve(inputMap.getNumResults()); |
| // Walk through input map and look for expressions of the form `dim * cst`. |
| for (auto [pos, expr] : llvm::enumerate(inputMap.getResults())) { |
| // Skip dim exprs and constant exprs. |
| if (isa<AffineDimExpr>(expr) || isa<AffineConstantExpr>(expr)) { |
| replacementExprs.push_back(expr); |
| continue; |
| } |
| // Look at binary op expressions. |
| auto binexpr = dyn_cast<AffineBinaryOpExpr>(expr); |
| // Fail if we see some unexpected kind of expression. |
| if (!binexpr) { |
| return failure(); |
| } |
| auto rhs = dyn_cast<AffineConstantExpr>(binexpr.getRHS()); |
| auto lhs = dyn_cast<AffineDimExpr>(binexpr.getLHS()); |
| // Binary expressions must be of the form `dim * cst`. |
| if (!rhs || !lhs || binexpr.getKind() != AffineExprKind::Mul) { |
| replacementExprs.push_back(expr); |
| continue; |
| } |
| strides.insert(std::pair<unsigned, int64_t>(pos, rhs.getValue())); |
| int64_t newSize = staticShape[lhs.getPosition()]; |
| if (newSize == ShapedType::kDynamic || newSize == 0) { |
| return failure(); |
| } |
| inputShape[pos] = newSize; |
| replacementExprs.push_back(lhs); |
| } |
| |
| // Fail if we don't have any work to do. |
| if (strides.empty()) { |
| return failure(); |
| } |
| |
| mapRange[inputPos] = |
| AffineMap::get(inputMap.getNumDims(), inputMap.getNumSymbols(), |
| replacementExprs, op.getContext()); |
| auto sliceTy = RankedTensorType::get(inputShape, inputTy.getElementType()); |
| |
| unsigned rank = inputTy.getRank(); |
| SmallVector<OpFoldResult> vOffset(rank, rewriter.getIndexAttr(0)); |
| SmallVector<OpFoldResult> vSizes; |
| SmallVector<OpFoldResult> vStride(rank, rewriter.getIndexAttr(1)); |
| Location loc = op.getLoc(); |
| for (unsigned i = 0; i < inputTy.getRank(); i++) { |
| if (strides.contains(i)) { |
| vStride[i] = rewriter.getIndexAttr(strides.at(i)); |
| } |
| if (inputShape[i] != ShapedType::kDynamic) { |
| vSizes.push_back(rewriter.getIndexAttr(inputShape[i])); |
| continue; |
| } |
| vSizes.push_back(rewriter.createOrFold<tensor::DimOp>(loc, input, i)); |
| } |
| Value extractedSlice = tensor::ExtractSliceOp::create( |
| rewriter, loc, sliceTy, input, vOffset, vSizes, vStride); |
| rewriter.startOpModification(op); |
| op.setIndexingMapsAttr(rewriter.getAffineMapArrayAttr(mapRange)); |
| op.setOperand(0, extractedSlice); |
| rewriter.finalizeOpModification(op); |
| return success(); |
| } |
| }; |
| |
| struct ConvertStridedContractionToContractionPass |
| : impl::ConvertStridedContractionToContractionPassBase< |
| ConvertStridedContractionToContractionPass> { |
| void getDependentDialects(DialectRegistry ®istry) const override { |
| registry.insert<arith::ArithDialect, tensor::TensorDialect>(); |
| } |
| |
| void runOnOperation() override { |
| MLIRContext *context = &getContext(); |
| RewritePatternSet patterns(&getContext()); |
| patterns.insert<ConvertStridedContractionToContraction>(context); |
| walkAndApplyPatterns(getOperation(), std::move(patterns)); |
| } |
| }; |
| } // namespace |
| } // namespace mlir::iree_compiler::GlobalOptimization |