20#include "llvm/ADT/TypeSwitch.h"
22#define DEBUG_TYPE "arm-sme-outerproduct-fusion"
25#define GEN_PASS_DEF_OUTERPRODUCTFUSION
26#include "mlir/Dialect/ArmSME/Transforms/Passes.h.inc"
35static constexpr StringLiteral
36 kMatchFailureNoAccumulator(
"no accumulator operand");
37static constexpr StringLiteral kMatchFailureExpectedOuterProductDefOp(
38 "defining op of accumulator must be 'arm_sme.outerproduct'");
39static constexpr StringLiteral kMatchFailureInconsistentCombiningKind(
40 "combining kind (add or sub) of outer products must match");
41static constexpr StringLiteral kMatchFailureInconsistentMasking(
42 "unsupported masking, either both outerproducts are masked "
44static constexpr StringLiteral kMatchFailureOuterProductNotSingleUse(
45 "outer product(s) not single use and cannot be removed, no benefit to "
54template <
typename LhsExtOp,
typename RhsExtOp = LhsExtOp>
56 arm_sme::OuterProductOp op,
57 VectorType resultType, VectorType inputType) {
58 if (op.getResultType() != resultType)
60 diag <<
"unsupported result type, expected " << resultType;
63 auto lhsDefOp = op.getLhs().getDefiningOp<LhsExtOp>();
64 auto rhsDefOp = op.getRhs().getDefiningOp<RhsExtOp>();
66 if (!lhsDefOp || !rhsDefOp)
68 op,
"defining op of outerproduct operands must be one of: "
69 "'arith.extf' or 'arith.extsi' or 'arith.extui'");
71 auto lhsInType = cast<VectorType>(lhsDefOp.getIn().getType());
72 auto rhsInType = cast<VectorType>(rhsDefOp.getIn().getType());
74 if (lhsInType != inputType || rhsInType != inputType)
76 diag <<
"unsupported input type, expected " << inputType;
103class OuterProductFusion2Way
108 LogicalResult matchAndRewrite(arm_sme::OuterProductOp op,
109 PatternRewriter &rewriter)
const override {
110 Value acc = op.getAcc();
114 arm_sme::OuterProductOp op1 = acc.
getDefiningOp<arm_sme::OuterProductOp>();
115 arm_sme::OuterProductOp op2 = op;
118 op, kMatchFailureExpectedOuterProductDefOp);
120 if (op1.getKind() != op2.getKind())
122 op, kMatchFailureInconsistentCombiningKind);
124 if (!op1->hasOneUse()) {
128 kMatchFailureOuterProductNotSingleUse);
131 if (
bool(op1.getLhsMask()) !=
bool(op2.getLhsMask()))
134 if (
failed(canFuseOuterProducts(rewriter, op1, op2)))
137 auto loc = op.getLoc();
138 auto packInputs = [&](Value
lhs, Value
rhs) {
139 return vector::InterleaveOp::create(rewriter, loc,
lhs,
rhs);
142 auto lhs = packInputs(op1.getLhs().getDefiningOp()->getOperand(0),
143 op2.getLhs().getDefiningOp()->getOperand(0));
144 auto rhs = packInputs(op1.getRhs().getDefiningOp()->getOperand(0),
145 op2.getRhs().getDefiningOp()->getOperand(0));
148 if (op1.getLhsMask() || op2.getLhsMask()) {
149 lhsMask = packInputs(op1.getLhsMask(), op2.getLhsMask());
150 rhsMask = packInputs(op1.getRhsMask(), op2.getRhsMask());
155 arm_sme::CombiningKind kind = op.getKind();
156 if (kind == arm_sme::CombiningKind::Add) {
158 .Case([&](arith::ExtFOp) {
160 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
163 .Case([&](arith::ExtSIOp) {
165 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
168 .Case([&](arith::ExtUIOp) {
170 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
173 .DefaultUnreachable(
"unexpected extend op!");
174 }
else if (kind == arm_sme::CombiningKind::Sub) {
176 .Case([&](arith::ExtFOp) {
178 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
181 .Case([&](arith::ExtSIOp) {
183 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
186 .Case([&](arith::ExtUIOp) {
188 op2, op.getResultType(), lhs, rhs, lhsMask, rhsMask,
191 .DefaultUnreachable(
"unexpected extend op!");
193 llvm_unreachable(
"unexpected arm_sme::CombiningKind!");
208 LogicalResult canFuseOuterProducts(PatternRewriter &rewriter,
209 arm_sme::OuterProductOp op1,
210 arm_sme::OuterProductOp op2)
const {
213 VectorType::get({4, 4}, rewriter.
getI32Type(), {
true,
true});
215 VectorType::get({4, 4}, rewriter.
getF32Type(), {
true,
true});
219 auto nxv4i16 = VectorType::get({4}, rewriter.
getI16Type(),
true);
220 auto nxv4f16 = VectorType::get({4}, rewriter.
getF16Type(),
true);
221 auto nxv4bf16 = VectorType::get({4}, rewriter.
getBF16Type(),
true);
222 const bool isF16Compatible =
224 isCompatible<arith::ExtFOp>(rewriter, op1, nxnxv4f32, nxv4f16)) &&
226 isCompatible<arith::ExtFOp>(rewriter, op2, nxnxv4f32, nxv4f16));
227 const bool isBF16Compatible =
229 isCompatible<arith::ExtFOp>(rewriter, op1, nxnxv4f32, nxv4bf16)) &&
231 isCompatible<arith::ExtFOp>(rewriter, op2, nxnxv4f32, nxv4bf16));
232 const bool isI16Compatible =
234 isCompatible<arith::ExtSIOp>(rewriter, op1, nxnxv4i32, nxv4i16)) &&
236 isCompatible<arith::ExtSIOp>(rewriter, op2, nxnxv4i32, nxv4i16));
237 const bool isUI16Compatible =
239 isCompatible<arith::ExtUIOp>(rewriter, op1, nxnxv4i32, nxv4i16)) &&
241 isCompatible<arith::ExtUIOp>(rewriter, op2, nxnxv4i32, nxv4i16));
243 return (isF16Compatible || isBF16Compatible || isI16Compatible ||
252class OuterProductFusion4Way
257 LogicalResult matchAndRewrite(arm_sme::OuterProductOp op,
258 PatternRewriter &rewriter)
const override {
259 SmallVector<arm_sme::OuterProductOp, 4> outerProductChain;
260 outerProductChain.push_back(op);
262 for (
int i = 0; i < 3; ++i) {
263 auto currentOp = outerProductChain.back();
264 auto acc = currentOp.getAcc();
267 auto previousOp = acc.
getDefiningOp<arm_sme::OuterProductOp>();
270 op, kMatchFailureExpectedOuterProductDefOp);
271 if (!previousOp->hasOneUse())
273 op, kMatchFailureOuterProductNotSingleUse);
274 if (previousOp.getKind() != currentOp.getKind())
276 op, kMatchFailureInconsistentCombiningKind);
277 if (
bool(previousOp.getLhsMask()) !=
bool(currentOp.getLhsMask()))
279 op, kMatchFailureInconsistentCombiningKind);
280 outerProductChain.push_back(previousOp);
283 if (
failed(canFuseOuterProducts(rewriter, outerProductChain)))
286 arm_sme::OuterProductOp op1 = outerProductChain[3];
287 arm_sme::OuterProductOp op2 = outerProductChain[2];
288 arm_sme::OuterProductOp op3 = outerProductChain[1];
289 arm_sme::OuterProductOp op4 = outerProductChain[0];
291 auto loc = op.getLoc();
292 auto packInputs = [&](Value
lhs, Value
rhs) {
293 return vector::InterleaveOp::create(rewriter, loc,
lhs,
rhs);
296 auto lhs0 = packInputs(op1.getLhs().getDefiningOp()->getOperand(0),
297 op3.getLhs().getDefiningOp()->getOperand(0));
298 auto lhs1 = packInputs(op2.getLhs().getDefiningOp()->getOperand(0),
299 op4.getLhs().getDefiningOp()->getOperand(0));
300 auto lhs = packInputs(lhs0, lhs1);
302 auto rhs0 = packInputs(op1.getRhs().getDefiningOp()->getOperand(0),
303 op3.getRhs().getDefiningOp()->getOperand(0));
304 auto rhs1 = packInputs(op2.getRhs().getDefiningOp()->getOperand(0),
305 op4.getRhs().getDefiningOp()->getOperand(0));
306 auto rhs = packInputs(rhs0, rhs1);
308 Value lhsMask, rhsMask;
309 if (op1.getLhsMask() || op2.getLhsMask() || op3.getLhsMask() ||
311 auto lhs0Mask = packInputs(op1.getLhsMask(), op3.getLhsMask());
312 auto lhs1Mask = packInputs(op2.getLhsMask(), op4.getLhsMask());
313 lhsMask = packInputs(lhs0Mask, lhs1Mask);
315 auto rhs0Mask = packInputs(op1.getRhsMask(), op3.getRhsMask());
316 auto rhs1Mask = packInputs(op2.getRhsMask(), op4.getRhsMask());
317 rhsMask = packInputs(rhs0Mask, rhs1Mask);
321 auto *rhsExtOp = op.getRhs().getDefiningOp();
323 arm_sme::CombiningKind kind = op.getKind();
324 if (kind == arm_sme::CombiningKind::Add) {
325 if (isa<arith::ExtSIOp>(lhsExtOp) && isa<arith::ExtSIOp>(rhsExtOp)) {
328 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
329 }
else if (isa<arith::ExtUIOp>(lhsExtOp) &&
330 isa<arith::ExtUIOp>(rhsExtOp)) {
333 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
334 }
else if (isa<arith::ExtSIOp>(lhsExtOp) &&
335 isa<arith::ExtUIOp>(rhsExtOp)) {
338 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
339 }
else if (isa<arith::ExtUIOp>(lhsExtOp) &&
340 isa<arith::ExtSIOp>(rhsExtOp)) {
343 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
345 llvm_unreachable(
"unexpected extend op!");
347 }
else if (kind == arm_sme::CombiningKind::Sub) {
348 if (isa<arith::ExtSIOp>(lhsExtOp) && isa<arith::ExtSIOp>(rhsExtOp)) {
351 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
352 }
else if (isa<arith::ExtUIOp>(lhsExtOp) &&
353 isa<arith::ExtUIOp>(rhsExtOp)) {
356 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
357 }
else if (isa<arith::ExtSIOp>(lhsExtOp) &&
358 isa<arith::ExtUIOp>(rhsExtOp)) {
361 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
362 }
else if (isa<arith::ExtUIOp>(lhsExtOp) &&
363 isa<arith::ExtSIOp>(rhsExtOp)) {
366 op4, op.getResultType(),
lhs,
rhs, lhsMask, rhsMask, op1.getAcc());
368 llvm_unreachable(
"unexpected extend op!");
371 llvm_unreachable(
"unexpected arm_sme::CombiningKind!");
387 canFuseOuterProducts(PatternRewriter &rewriter,
388 ArrayRef<arm_sme::OuterProductOp> ops)
const {
391 VectorType::get({4, 4}, rewriter.
getI32Type(), {
true,
true});
393 VectorType::get({2, 2}, rewriter.
getI64Type(), {
true,
true});
398 auto nxv4i8 = VectorType::get({4}, rewriter.
getI8Type(),
true);
399 auto nxv2i16 = VectorType::get({2}, rewriter.
getI16Type(),
true);
401 auto failedToMatch = [&](VectorType resultType, VectorType inputType,
402 auto lhsExtendOp,
auto rhsExtendOp) {
403 using LhsExtendOpTy =
decltype(lhsExtendOp);
404 using RhsExtendOpTy =
decltype(rhsExtendOp);
405 for (
auto op : ops) {
406 if (
failed(isCompatible<LhsExtendOpTy, RhsExtendOpTy>(
407 rewriter, op, resultType, inputType)))
413 if (failedToMatch(nxnxv4i32, nxv4i8, arith::ExtSIOp{}, arith::ExtSIOp{}) &&
414 failedToMatch(nxnxv4i32, nxv4i8, arith::ExtUIOp{}, arith::ExtUIOp{}) &&
415 failedToMatch(nxnxv4i32, nxv4i8, arith::ExtSIOp{}, arith::ExtUIOp{}) &&
416 failedToMatch(nxnxv4i32, nxv4i8, arith::ExtUIOp{}, arith::ExtSIOp{}) &&
417 failedToMatch(nxnxv2i64, nxv2i16, arith::ExtSIOp{}, arith::ExtSIOp{}) &&
418 failedToMatch(nxnxv2i64, nxv2i16, arith::ExtUIOp{}, arith::ExtUIOp{}) &&
419 failedToMatch(nxnxv2i64, nxv2i16, arith::ExtSIOp{}, arith::ExtUIOp{}) &&
420 failedToMatch(nxnxv2i64, nxv2i16, arith::ExtUIOp{}, arith::ExtSIOp{}))
438struct SwapVectorExtractOfArithExtend
442 LogicalResult matchAndRewrite(vector::ExtractOp extractOp,
443 PatternRewriter &rewriter)
const override {
444 VectorType resultType = llvm::dyn_cast<VectorType>(extractOp.getType());
447 "extracted type is not a vector type");
449 auto numScalableDims = resultType.getNumScalableDims();
450 if (numScalableDims != 1)
452 extractOp,
"extracted type is not a 1-D scalable vector type");
454 auto *extendOp = extractOp.getSource().getDefiningOp();
455 if (!isa_and_present<arith::ExtSIOp, arith::ExtUIOp, arith::ExtFOp>(
458 "extract not from extend op");
460 auto loc = extractOp.getLoc();
461 StringAttr extendOpName = extendOp->getName().getIdentifier();
462 Value extendSource = extendOp->getOperand(0);
465 Value newExtract = vector::ExtractOp::create(rewriter, loc, extendSource,
466 extractOp.getMixedPosition());
469 Operation *newExtend =
470 rewriter.
create(loc, extendOpName, Value(newExtract), resultType);
472 rewriter.
replaceOp(extractOp, newExtend);
489struct SwapVectorScalableExtractOfArithExtend
493 LogicalResult matchAndRewrite(vector::ScalableExtractOp extractOp,
494 PatternRewriter &rewriter)
const override {
495 auto *extendOp = extractOp.getSource().getDefiningOp();
496 if (!isa_and_present<arith::ExtSIOp, arith::ExtUIOp, arith::ExtFOp>(
499 "extract not from extend op");
501 auto loc = extractOp.getLoc();
502 VectorType resultType = extractOp.getResultVectorType();
504 Value extendSource = extendOp->getOperand(0);
505 StringAttr extendOpName = extendOp->getName().getIdentifier();
506 VectorType extendSourceVectorType =
507 cast<VectorType>(extendSource.
getType());
510 VectorType extractResultVectorType =
511 resultType.clone(extendSourceVectorType.getElementType());
512 Value newExtract = vector::ScalableExtractOp::create(
513 rewriter, loc, extractResultVectorType, extendSource,
517 Operation *newExtend =
518 rewriter.
create(loc, extendOpName, Value(newExtract), resultType);
520 rewriter.
replaceOp(extractOp, newExtend);
526struct OuterProductFusionPass
529 void runOnOperation()
override {
544 patterns.
add<SwapVectorExtractOfArithExtend,
545 SwapVectorScalableExtractOfArithExtend>(context, 1024);
546 patterns.
add<OuterProductFusion2Way, OuterProductFusion4Way>(context);
550 return std::make_unique<OuterProductFusionPass>();
This class contains all of the information necessary to report a diagnostic to the DiagnosticEngine.
MLIRContext is the top-level object for a collection of MLIR operations.
Operation * create(const OperationState &state)
Creates an operation given the fields represented as an OperationState.
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
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.
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 represents an instance of an SSA value in the MLIR system, representing a computable value...
Type getType() const
Return the type of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
std::unique_ptr< Pass > createOuterProductFusionPass()
Pass that fuses 'arm_sme.outerproduct' ops into 2-way or 4-way widening variants.
void populateOuterProductFusionPatterns(RewritePatternSet &patterns)
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...
llvm::TypeSwitch< T, ResultT > TypeSwitch
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...