MLIR-AIE 1.4.5.dev
VectorToVectorConversions.cpp
Go to the documentation of this file.
1//===-VectorToVectorConversions.cpp - Conversions within Vector -*- C++ -*-===//
2//
3// Copyright (C) 2023-2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7// This file contains conversions and rewrites to the Vector dialect to make
8// it compatible with the available vector instructions in AIE architectures
9//===----------------------------------------------------------------------===//
10
14#include "mlir/Conversion/AffineToStandard/AffineToStandard.h"
15#include "mlir/Dialect/Affine/Analysis/LoopAnalysis.h"
16#include "mlir/Dialect/Affine/IR/AffineOps.h"
17#include "mlir/Dialect/Func/IR/FuncOps.h"
18#include "mlir/Dialect/Math/IR/Math.h"
19#include "mlir/Dialect/MemRef/IR/MemRef.h"
20#include "mlir/Dialect/SCF/IR/SCF.h"
21#include "mlir/Dialect/UB/IR/UBOps.h"
22#include "mlir/Dialect/Utils/ReshapeOpsUtils.h"
23#include "mlir/Dialect/Vector/Transforms/LoweringPatterns.h"
24#include "mlir/IR/Matchers.h"
25#include "mlir/IR/PatternMatch.h"
26#include "mlir/Pass/PassManager.h"
27#include "mlir/Transforms/DialectConversion.h"
28#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
29#include "llvm/ADT/STLExtras.h"
30#include <algorithm>
31#include <utility>
32
33#define DEBUG_TYPE "aievec-canonicalization"
34
35using namespace mlir;
36using namespace arith;
37using namespace vector;
38using namespace xilinx;
39using namespace xilinx::aievec;
40
41//============================================================================//
42//================== Common AIE canonicalization analysis ====================//
43//============================================================================//
44
45static AIEArch decodeAIETarget(const std::string &target) {
46 if (!target.empty()) {
47 if (target == "aieml" || target == "aie2" || target == "aie2p")
48 return AIEArch::AIE2;
49 if (target != "aie")
50 return AIEArch::UNKNOWN;
51 }
52 return AIEArch::AIE;
53}
54
55//============================================================================//
56//============ Common AIE canonicalization conversion patterns ===============//
57//============================================================================//
58
59// This pattern converts a `vector.transfer_read` with an unaligned access
60// into an aligned `vector.transfer_read` twice as long, followed by a
61// `vector.extract_strided_slice` selecting the subvector matching the
62// original `vector.transfer_read`.
64 : public OpConversionPattern<vector::TransferReadOp> {
65 using OpConversionPattern<vector::TransferReadOp>::OpConversionPattern;
66
67 SplitUnalignedTransferReadPattern(MLIRContext *context, int64_t maxVectorSize,
68 int64_t alignment)
69 : OpConversionPattern<vector::TransferReadOp>(context),
71
72 LogicalResult
73 matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor,
74 ConversionPatternRewriter &rewriter) const override {
75 // Check that it's not a splat transfer read.
76 if (adaptor.getPermutationMap().isConstant())
77 return failure();
78
79 // Check if the transfer is unaligned.
80 auto vType = readOp.getVectorType();
81 int64_t offset =
83 .value_or(0);
84 if (offset == 0)
85 return failure();
86
87 // Verify that we can load a vector 2x as long as the original
88 auto vLen = vType.getShape().back();
89 auto longVecTy = VectorType::get(2 * vLen, vType.getElementType());
90 auto longVecSize = getElementSizeInBits(vType) * 2 * vLen;
91 if (longVecSize > maxVectorSize)
92 return failure();
93
94 // Calculate the aligned indices for the lower and higher parts.
95 // TODO: Add support for cases where the offset is greater than the
96 // TODO: vector length.
97 auto loc = readOp.getLoc();
98 Value oldInnerMostIdx = adaptor.getIndices().back();
99 auto offsetCorrectionMap =
100 AffineMap::get(1, 0, getAffineDimExpr(0, readOp.getContext()) - offset);
101 Value newInnerMostIdx = affine::AffineApplyOp::create(
102 rewriter, readOp.getLoc(), offsetCorrectionMap,
103 SmallVector<Value, 1>({oldInnerMostIdx}))
104 .getResult();
105 SmallVector<Value, 8> alignedIdx;
106 alignedIdx.append(adaptor.getIndices().begin(), adaptor.getIndices().end());
107 alignedIdx[alignedIdx.size() - 1] = newInnerMostIdx;
108
109 // Create the aligned transfer read for a vector 2x as long that covers the
110 // elements of the unaligned vector.
111 auto newReadOp = vector::TransferReadOp::create(
112 rewriter, loc, longVecTy, adaptor.getBase(), alignedIdx,
113 adaptor.getPadding());
114
115 // Create a `vector.extract_strided_slice` to extract the unaligned vector.
116 rewriter.replaceOpWithNewOp<vector::ExtractStridedSliceOp>(
117 readOp, newReadOp.getResult(), offset, vLen, 1);
118
119 return success();
120 }
121
124};
125
126// This pattern converts a `vector.transfer_read` with a splat permutation map
127// into a contiguous `vector.transfer_read` followed by a `vector.extract` to
128// obtain the splat value and a `vector.broadcast` to broadcast it into a
129// vector of the right size.
131 : public OpConversionPattern<vector::TransferReadOp> {
132 using OpConversionPattern<vector::TransferReadOp>::OpConversionPattern;
133
135 : OpConversionPattern<vector::TransferReadOp>(context) {}
136
137 LogicalResult
138 matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor,
139 ConversionPatternRewriter &rewriter) const override {
140 AffineMap map = readOp.getPermutationMap();
141 if (!map.isConstant())
142 return failure();
143
144 Value srcMemRef = adaptor.getBase();
145 SmallVector<Value, 8> indices;
146 Value newIdx;
147 int64_t offset = 0;
148 // If it's a zero-rank memory access
149 if (cast<MemRefType>(srcMemRef.getType()).getRank() == 0) {
150 srcMemRef = memref::ExpandShapeOp::create(
151 rewriter, readOp.getLoc(), SmallVector<int64_t, 1>({1}),
152 srcMemRef, SmallVector<ReassociationIndices, 1>({}))
153 .getResult();
154 newIdx = arith::ConstantOp::create(rewriter, readOp.getLoc(),
155 rewriter.getIndexAttr(0L));
156 indices.push_back(newIdx);
157 } else {
158 indices.append(adaptor.getIndices().begin(), adaptor.getIndices().end());
159 newIdx = indices[indices.size() - 1];
160 // If the innermost index comes from an `affine.apply` op, take the base
161 // as the new innermost index for the new `vector.transfer_read`, and the
162 // offset as the index for the `aievec.broadcast` op.
163 if (auto applyOp = newIdx.getDefiningOp<affine::AffineApplyOp>())
164 if (applyOp.getAffineMap().getNumDims() == 1) {
165 newIdx = applyOp.getMapOperands()[0];
166 offset = applyOp.getAffineMap().compose(ArrayRef<int64_t>{0})[0];
167 }
168 }
169 // XXX: We assume we are reading 1D vectors
170 int64_t vlen = readOp.getVector().getType().getShape()[0];
171 if (offset >= vlen) {
172 // If the splat element is beyond the first vector, we calculate the
173 // address of the vector containing the element.
174 int64_t numElemsToSkip = vlen * (offset / vlen);
175 offset = offset % vlen;
176 auto newAddrMap = AffineMap::get(
177 1, 0, getAffineDimExpr(0, readOp.getContext()) + numElemsToSkip);
178 newIdx =
179 affine::AffineApplyOp::create(rewriter, readOp.getLoc(), newAddrMap,
180 SmallVector<Value, 1>({newIdx}))
181 .getResult();
182 }
183 indices[indices.size() - 1] = newIdx;
184 auto newReadOp = vector::TransferReadOp::create(
185 rewriter, readOp.getLoc(), readOp.getVector().getType(), srcMemRef,
186 indices, adaptor.getPadding());
187 auto extractOp = vector::ExtractOp::create(rewriter, readOp.getLoc(),
188 newReadOp.getResult(),
189 ArrayRef<int64_t>{offset});
190 rewriter.replaceOpWithNewOp<vector::BroadcastOp>(
191 readOp, newReadOp.getVector().getType(), extractOp.getResult());
192 return success();
193 }
194};
195
196// This pattern swaps a UnaryOpA followed by UnaryOpB. This pattern can be used
197// to improve pattern matching for mixed-type arithmetic ops, by getting sign
198// extension ops closer to the single-type arithmetic operations.
199template <class UnaryOpA, class UnaryOpB>
200struct SwapUnaryOpsPattern : public OpRewritePattern<UnaryOpB> {
202 // This function takes the chain of operations A->B, and returns the new type
203 // between B and A after the swap.
204 using InferTypeB2AFnTy = std::function<Type(UnaryOpA aOp, UnaryOpB bOp)>;
206
207 SwapUnaryOpsPattern(MLIRContext *context, InferTypeB2AFnTy inferType)
208 : OpRewritePattern<UnaryOpB>(context),
209 inferTypeB2A(std::move(inferType)) {}
210
211 LogicalResult matchAndRewrite(UnaryOpB bOp,
212 PatternRewriter &rewriter) const override {
213 static_assert(
214 UnaryOpA::template hasTrait<OpTrait::OneOperand>(),
215 "SwapUnaryOps can only be instantiated for single-operand ops");
216 static_assert(
217 UnaryOpB::template hasTrait<OpTrait::OneOperand>(),
218 "SwapUnaryOps can only be instantiated for single-operand ops");
219 UnaryOpA aOp = bOp.getOperand().template getDefiningOp<UnaryOpA>();
220 if (!aOp)
221 return rewriter.notifyMatchFailure(bOp, UnaryOpB::getOperationName() +
222 " not preceeded by " +
223 UnaryOpA::getOperationName());
224
225 Type newA2BTy = inferTypeB2A(aOp, bOp);
226
227 auto newA =
228 UnaryOpB::create(rewriter, bOp->getLoc(), SmallVector<Type>({newA2BTy}),
229 aOp->getOperands(), bOp->getAttrs());
230 auto newB = UnaryOpA::create(rewriter, bOp->getLoc(),
231 SmallVector<Type>({bOp.getResult().getType()}),
232 newA->getResults(), aOp->getAttrs());
233 rewriter.replaceOp(bOp, newB.getResult());
234 return success();
235 }
236};
237
238static SmallVector<Value> collapseInnerMostDimIndices(PatternRewriter &b,
239 Location loc, int numDims,
240 ValueRange indices,
241 ArrayRef<int64_t> shape,
242 AffineMap layout) {
243 (void)layout; // Layout is verified by callers; index computation uses shape.
244 auto newIdxExpr = b.getAffineDimExpr(numDims - 1);
245 int64_t stride = 1;
246 for (int64_t dim = numDims - 2; dim >= 0; dim--) {
247 stride *= shape[shape.size() - (numDims - dim - 1)];
248 newIdxExpr = newIdxExpr + b.getAffineDimExpr(dim) * stride;
249 }
250 auto newIndexMap = AffineMap::get(numDims, 0, newIdxExpr);
251 Value newInnerMostIdxValue =
252 affine::AffineApplyOp::create(b, loc, newIndexMap,
253 indices.take_back(numDims))
254 .getResult();
255 SmallVector<Value> newIdxRange;
256 for (auto idx : indices.drop_back(numDims))
257 newIdxRange.push_back(idx);
258 newIdxRange.push_back(newInnerMostIdxValue);
259 return newIdxRange;
260}
261
262static Value collapseInnerMostShapeDims(PatternRewriter &b, Location loc,
263 int numDims, Value val) {
264 auto memRefTy = cast<MemRefType>(val.getType());
265 auto shape = memRefTy.getShape();
266 int64_t newInnerMostDim = std::accumulate(shape.end() - numDims, shape.end(),
267 1, std::multiplies<>());
268 SmallVector<int64_t, 4> newShape{shape.begin(), shape.end() - numDims + 1};
269 newShape[shape.size() - numDims] = newInnerMostDim;
270 // newShape is constructed above as exactly shape with its trailing
271 // numDims dims merged into one, so this is always a valid collapse.
272 auto reassocIndicesOpt = getReassociationIndicesForCollapse(shape, newShape);
273 assert(reassocIndicesOpt && "newShape must be a valid collapse of shape");
274 const auto &reassocIndices = *reassocIndicesOpt;
275 // Let CollapseShapeOp::inferResultType compute the correct result type,
276 // which preserves strided layout and dynamic offset from the source.
277 auto newMemRefTy =
278 memref::CollapseShapeOp::computeCollapsedType(memRefTy, reassocIndices);
279 return memref::CollapseShapeOp::create(b, loc, newMemRefTy, val,
280 reassocIndices)
281 .getResult();
282}
283
284/// Check if a memref has contiguous row-major strides (each stride equals the
285/// product of trailing dimensions). Dynamic strides are accepted when the
286/// corresponding dimension size is 1. Memrefs with dynamic offsets are fine.
287static bool hasContiguousRowMajorStrides(MemRefType memRefTy) {
288 SmallVector<int64_t> strides;
289 int64_t offset;
290 if (failed(memRefTy.getStridesAndOffset(strides, offset)))
291 return false;
292 auto shape = memRefTy.getShape();
293 int64_t expected = 1;
294 for (int i = shape.size() - 1; i >= 0; --i) {
295 if (strides[i] != ShapedType::kDynamic && strides[i] != expected)
296 return false;
297 if (shape[i] != ShapedType::kDynamic)
298 expected *= shape[i];
299 else
300 expected = ShapedType::kDynamic;
301 }
302 return true;
303}
304
305// This pattern flatten multidimensional `vector.transfer_read` operations
306// replacing them with a `memref.collapse_shape`, a 1D `vector.transfer_read`,
307// and a `vector.shape_cast`.
309 : public OpConversionPattern<vector::TransferReadOp> {
310 using OpConversionPattern<vector::TransferReadOp>::OpConversionPattern;
311
312 LogicalResult
313 matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor,
314 ConversionPatternRewriter &rewriter) const override {
315 // We can only deal with unmasked transfer ops with an identity permutation
316 // map.
317 if (!adaptor.getPermutationMap().isMinorIdentity() || adaptor.getMask())
318 return failure();
319 VectorType vectorTy = readOp.getVector().getType();
320 if (vectorTy.getRank() < 2)
321 return failure();
322 // Work only on bufferized reads
323 MemRefType memRefTy = dyn_cast<MemRefType>(adaptor.getBase().getType());
324 if (!memRefTy)
325 return failure();
326 auto memRefShape = memRefTy.getShape();
327 auto vecShape = vectorTy.getShape();
328
329 auto newVectorTy =
330 VectorType::get({std::accumulate(vecShape.begin(), vecShape.end(), 1,
331 std::multiplies<>())},
332 vectorTy.getElementType());
333 if (!hasContiguousRowMajorStrides(memRefTy))
334 return failure();
335
336 AffineMap layout = memRefTy.getLayout().getAffineMap();
337 auto newIndices =
338 collapseInnerMostDimIndices(rewriter, readOp.getLoc(), vecShape.size(),
339 adaptor.getIndices(), memRefShape, layout);
340 auto newSource = collapseInnerMostShapeDims(
341 rewriter, readOp.getLoc(), vecShape.size(), adaptor.getBase());
342 auto newVector = vector::TransferReadOp::create(
343 rewriter, readOp.getLoc(), newVectorTy, newSource, newIndices,
344 /*padding*/
345 arith::getZeroConstant(rewriter, readOp.getLoc(),
346 newVectorTy.getElementType()));
347
348 auto inBoundsArrayAttrOpt = adaptor.getInBounds();
349 if (inBoundsArrayAttrOpt) {
350 SmallVector<bool> inBounds =
351 llvm::to_vector(inBoundsArrayAttrOpt.getAsValueRange<BoolAttr>());
352 SmallVector<bool> newInBounds({false});
353 newInBounds[0] = llvm::all_of(inBounds, [](bool v) { return v; });
354 newVector.getProperties().setInBounds(
355 rewriter.getBoolArrayAttr(newInBounds));
356 }
357
358 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(readOp, vectorTy,
359 newVector);
360
361 return success();
362 }
363};
364
365// This pattern flatten multidimensional `vector.transfer_write` operations
366// replacing them with a `memref.collapse_shape`, a `vector.shape_cast`, and a
367// 1D `vector.transfer_write`,
369 : public OpConversionPattern<vector::TransferWriteOp> {
370 using OpConversionPattern<vector::TransferWriteOp>::OpConversionPattern;
371
372 LogicalResult
373 matchAndRewrite(vector::TransferWriteOp writeOp, OpAdaptor adaptor,
374 ConversionPatternRewriter &rewriter) const override {
375 // We can only deal with unmasked transfer ops with an identity permutation
376 // map.
377 if (!adaptor.getPermutationMap().isMinorIdentity() || adaptor.getMask())
378 return failure();
379 VectorType vectorTy = cast<VectorType>(adaptor.getValueToStore().getType());
380 if (vectorTy.getRank() < 2)
381 return failure();
382 // Work only on bufferized reads
383 MemRefType memRefTy = dyn_cast<MemRefType>(adaptor.getBase().getType());
384 if (!memRefTy)
385 return failure();
386 auto memRefShape = memRefTy.getShape();
387 auto vecShape = vectorTy.getShape();
388
389 if (!hasContiguousRowMajorStrides(memRefTy))
390 return failure();
391
392 auto newVectorTy =
393 VectorType::get({std::accumulate(vecShape.begin(), vecShape.end(), 1,
394 std::multiplies<>())},
395 vectorTy.getElementType());
396 AffineMap layout = memRefTy.getLayout().getAffineMap();
397 auto newVector =
398 vector::ShapeCastOp::create(rewriter, writeOp.getLoc(), newVectorTy,
399 adaptor.getValueToStore())
400 .getResult();
401 auto newIndices =
402 collapseInnerMostDimIndices(rewriter, writeOp.getLoc(), vecShape.size(),
403 adaptor.getIndices(), memRefShape, layout);
404 auto newSource = collapseInnerMostShapeDims(
405 rewriter, writeOp.getLoc(), vecShape.size(), adaptor.getBase());
406
407 auto newOp = rewriter.replaceOpWithNewOp<vector::TransferWriteOp>(
408 writeOp, newVector, newSource, newIndices);
409
410 auto inBoundsArrayAttrOpt = adaptor.getInBounds();
411 if (inBoundsArrayAttrOpt) {
412 SmallVector<bool> inBounds =
413 llvm::to_vector(inBoundsArrayAttrOpt.getAsValueRange<BoolAttr>());
414 SmallVector<bool> newInBounds({false});
415 newInBounds[0] = llvm::all_of(inBounds, [](bool v) { return v; });
416 newOp.getProperties().setInBounds(rewriter.getBoolArrayAttr(newInBounds));
417 }
418
419 return success();
420 }
421};
422
423/// Utility function to check if all provided indices are constant zero values.
424/// @return success() if all indices are constant zeros, failure() otherwise
425static LogicalResult isAllZeroOffsetAccess(mlir::OperandRange indices) {
426 if (!llvm::all_of(indices, [](Value val) {
427 IntegerAttr attr;
428 if (!matchPattern(val, m_Constant(&attr)))
429 return false;
430 return attr.getInt() == 0;
431 })) {
432 return failure();
433 }
434 return success();
435}
436
437/// Utility function to convert OpFoldResult offsets from a SubView operation
438/// into a vector of Values.
439static SmallVector<Value> opFoldResultsToValues(PatternRewriter &rewriter,
440 Location loc,
441 memref::SubViewOp subViewOp) {
442 OpBuilder::InsertionGuard g(rewriter);
443 rewriter.setInsertionPoint(subViewOp);
444 SmallVector<Value> newIndices;
445 for (OpFoldResult offset : subViewOp.getMixedOffsets()) {
446 Value indexVal;
447 if (auto attr = dyn_cast<Attribute>(offset)) {
448 indexVal = arith::ConstantIndexOp::create(
449 rewriter, loc, cast<IntegerAttr>(attr).getInt());
450 } else {
451 indexVal = cast<Value>(offset);
452 }
453 newIndices.push_back(indexVal);
454 }
455 return newIndices;
456}
457
458/// Pattern to canonicalize trivial vector.transfer_read operations on subviews.
459///
460/// This pattern eliminates unnecessary memref.subview operations when the
461/// transfer_read accesses the subview with all-zero indices. It transforms:
462///
463/// INPUT:
464/// %subview = memref.subview %memref [offset0, offset1, ...]
465/// %result = vector.transfer_read %subview[0, 0, ...]
466///
467/// OUTPUT:
468/// %result = vector.transfer_read %memref[offset0, offset1, ...]
469///
470/// The pattern only matches when:
471/// - The base of transfer_read is defined by a memref.subview operation
472/// - All indices in the transfer_read are constant zeros
474 : public OpRewritePattern<vector::TransferReadOp> {
475 using OpRewritePattern<vector::TransferReadOp>::OpRewritePattern;
476
477 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
478 PatternRewriter &rewriter) const override {
479 // Check if the base memref comes from a subview operation
480 auto subViewOp = dyn_cast_if_present<memref::SubViewOp>(
481 readOp.getBase().getDefiningOp());
482 if (!subViewOp)
483 return failure();
484
485 // Verify that all access indices are zero
486 if (failed(isAllZeroOffsetAccess(readOp.getIndices())))
487 return failure();
488
489 // Convert subview offsets to explicit index values
490 SmallVector<Value> newIndices =
491 opFoldResultsToValues(rewriter, readOp.getLoc(), subViewOp);
492
493 // Replace with direct access to the original memref using subview offsets
494 rewriter.replaceOpWithNewOp<vector::TransferReadOp>(
495 readOp, readOp.getType(), subViewOp.getSource(), newIndices,
496 readOp.getPadding(), readOp.getInBoundsValues());
497 return success();
498 }
499};
500
501/// Pattern to canonicalize trivial vector.transfer_write operations on
502/// subviews.
503///
504/// This pattern eliminates unnecessary memref.subview operations when the
505/// transfer_write accesses the subview with all-zero indices. It transforms:
506///
507/// INPUT:
508/// %subview = memref.subview %memref [offset0, offset1, ...]
509/// vector.transfer_write %value, %subview[0, 0, ...]
510///
511/// OUTPUT:
512/// vector.transfer_write %value, %memref[offset0, offset1, ...]
513///
514/// The pattern only matches when:
515/// - The base of transfer_write is defined by a memref.subview operation
516/// - All indices in the transfer_write are constant zeros
518 : public OpRewritePattern<vector::TransferWriteOp> {
519 using OpRewritePattern<vector::TransferWriteOp>::OpRewritePattern;
520
521 LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp,
522 PatternRewriter &rewriter) const override {
523 // Check if the base memref comes from a subview operation
524 auto subViewOp = dyn_cast_if_present<memref::SubViewOp>(
525 writeOp.getBase().getDefiningOp());
526 if (!subViewOp)
527 return failure();
528
529 // Verify that all access indices are zero
530 if (failed(isAllZeroOffsetAccess(writeOp.getIndices())))
531 return failure();
532
533 // Convert subview offsets to explicit index values
534 SmallVector<Value> newIndices =
535 opFoldResultsToValues(rewriter, writeOp.getLoc(), subViewOp);
536
537 // Create new transfer_write with direct access to original memref
538 vector::TransferWriteOp::create(rewriter, writeOp.getLoc(),
539 writeOp.getVector(), subViewOp.getSource(),
540 newIndices, writeOp.getInBoundsValues());
541
542 // Remove the original transfer_write operation
543 rewriter.eraseOp(writeOp);
544 return success();
545 }
546};
547
548// Carry an n-D vector through an scf.for loop as a rank-1 vector. The LLVM
549// lowering turns an n-D vector into an array of rank-1 rows, so a loop that
550// carries one splits it into rows and rebuilds it on every iteration instead
551// of keeping it in registers. AIE2P lowers n-D elementwise ops by flattening
552// them to rank 1, so the shape casts added here at the loop boundary fold
553// against the ones that flattening adds. Each match rewrites one iter_arg.
555 using OpRewritePattern::OpRewritePattern;
556
557 LogicalResult matchAndRewrite(scf::ForOp forOp,
558 PatternRewriter &rewriter) const override {
559 for (OpOperand &init : forOp.getInitArgsMutable()) {
560 auto vecType = dyn_cast<VectorType>(init.get().getType());
561 if (!vecType || vecType.getRank() < 2 || vecType.isScalable())
562 continue;
563 Value flat = vector::ShapeCastOp::create(rewriter, forOp.getLoc(),
564 getFlattenedVectorType(vecType),
565 init.get());
566 auto shapeCast = [](OpBuilder &builder, Location loc, Type type,
567 Value source) -> Value {
568 return vector::ShapeCastOp::create(builder, loc, type, source);
569 };
570 rewriter.replaceOp(forOp, scf::replaceAndCastForOpIterArg(
571 rewriter, forOp, init, flat, shapeCast));
572 return success();
573 }
574 return failure();
575 }
576};
577
578//============================================================================//
579//============ AIE2 canonicalization conversion patterns ===============//
580//============================================================================//
581
583 : public OpRewritePattern<vector::InsertOp> {
584
585 using OpRewritePattern<vector::InsertOp>::OpRewritePattern;
586
587 LogicalResult matchAndRewrite(vector::InsertOp insOp,
588 PatternRewriter &rewriter) const override {
589 auto insSrcTy = dyn_cast<VectorType>(insOp.getValueToStoreType());
590 if (!insSrcTy)
591 return failure();
592
593 auto srcShape = insSrcTy.getShape();
594 auto dstShape = insOp.getDestVectorType().getShape();
595
596 unsigned long numLeadUnitDimDst = 0;
597 while (numLeadUnitDimDst < dstShape.size() &&
598 dstShape[numLeadUnitDimDst] == 1)
599 numLeadUnitDimDst++;
600
601 if (!numLeadUnitDimDst)
602 return failure();
603
604 unsigned long numLeadUnitDimSrc = 0;
605 while (numLeadUnitDimSrc < srcShape.size() &&
606 srcShape[numLeadUnitDimSrc] == 1)
607 numLeadUnitDimSrc++;
608
609 SmallVector<int64_t> nonLeadUnitDimDstShape(
610 dstShape.begin() + numLeadUnitDimDst, dstShape.end());
611 SmallVector<int64_t> nonLeadUnitDimSrcShape(
612 srcShape.begin() + numLeadUnitDimSrc, srcShape.end());
613
614 if (nonLeadUnitDimSrcShape != nonLeadUnitDimDstShape)
615 return failure();
616
617 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(
618 insOp, insOp.getDestVectorType(), insOp.getValueToStore());
619 return success();
620 }
621};
622
623//============================================================================//
624//================ Common AIE canonicalization configuration =================//
625//============================================================================//
626static void
627configureCommonAIECanonicalizeLegalizations(ConversionTarget &target) {
628 target.addLegalDialect<arith::ArithDialect, affine::AffineDialect,
629 memref::MemRefDialect, vector::VectorDialect,
630 ub::UBDialect>();
631}
632
633static void
634populateCommonAIECanonicalizeConversionPatterns(RewritePatternSet &patterns) {
636 patterns.getContext());
637}
638
639//============================================================================//
640//============== AIEv1-specific canonicalization configuration ===============//
641//============================================================================//
642
643static void configureAIEv1CanonicalizeLegalizations(ConversionTarget &target) {
644 target.addDynamicallyLegalOp<vector::TransferReadOp>(
645 [](vector::TransferReadOp op) {
646 return !op.getPermutationMap().isConstant() &&
647 getTransferReadAlignmentOffset(op, op.getVectorType(), 128)
648 .value_or(0) == 0;
649 });
650}
651
652static void
653populateAIEv1CanonicalizeConversionPatterns(RewritePatternSet &patterns) {
654 patterns.add<SplitUnalignedTransferReadPattern>(patterns.getContext(), 512,
655 128);
656}
657
658//============================================================================//
659//============== AIE2-specific canonicalization configuration ===============//
660//============================================================================//
661
662static void configureAIE2CanonicalizeLegalizations(ConversionTarget &target) {
663 target.addDynamicallyLegalOp<vector::TransferReadOp>(
664 [](vector::TransferReadOp op) {
665 return !op.getPermutationMap().isConstant() &&
666 getTransferReadAlignmentOffset(op, op.getVectorType(), 256)
667 .value_or(0) == 0 &&
668 op.getVector().getType().getRank() < 2;
669 });
670 target.addDynamicallyLegalOp<vector::TransferWriteOp>(
671 [](vector::TransferWriteOp op) {
672 return cast<VectorType>(op.getVector().getType()).getRank() < 2;
673 });
674}
675
676static void
677populateAIE2CanonicalizeConversionPatterns(RewritePatternSet &patterns) {
678 patterns.add<SplitUnalignedTransferReadPattern>(patterns.getContext(), 1024,
679 256);
681 FlattenMultDimTransferWritePattern>(patterns.getContext());
682}
683
684//============================================================================//
685//=================== Common AIE Canonicalization Passes =====================//
686//============================================================================//
687
688//===----------------------------------------------------------------------===//
689// BF16 Emulation: Emulate f32 vector arithmetic using bf16 operations.
690//===----------------------------------------------------------------------===//
691
692// Smart truncation helper: if the value was produced by arith.extf from bf16,
693// reuse the bf16 source directly to avoid redundant extf->truncf chains.
694static Value smartTruncF32ToBF16(PatternRewriter &rewriter, Location loc,
695 Value val, Type bf16Type) {
696 if (auto extfOp = val.getDefiningOp<arith::ExtFOp>()) {
697 if (extfOp.getIn().getType() == bf16Type)
698 return extfOp.getIn();
699 }
700 return arith::TruncFOp::create(rewriter, loc, bf16Type, val);
701}
702
703// `v` rounded to the nearest bf16, still in its own semantics.
704static APFloat roundedToBF16(APFloat v) {
705 bool losesInfo = false;
706 const llvm::fltSemantics &sem = v.getSemantics();
707 v.convert(APFloat::BFloat(), APFloat::rmNearestTiesToEven, &losesInfo);
708 v.convert(sem, APFloat::rmNearestTiesToEven, &losesInfo);
709 return v;
710}
711
712// True if `val` is an f32 vector whose elements are all bf16 values.
713static bool holdsBF16(Value val) {
714 if (auto extfOp = val.getDefiningOp<arith::ExtFOp>())
715 return getElementTypeOrSelf(extfOp.getIn().getType()).isBF16();
716 DenseFPElementsAttr dense;
717 return matchPattern(val, m_Constant(&dense)) &&
718 llvm::all_of(dense.getValues<APFloat>(), [](const APFloat &v) {
719 return roundedToBF16(v).bitwiseIsEqual(v);
720 });
721}
722
723// `val` rounded to bf16, still as f32. Constants are rounded here.
724static Value roundToBF16(PatternRewriter &rewriter, Location loc, Value val) {
725 if (holdsBF16(val))
726 return val;
727 auto vecType = cast<VectorType>(val.getType());
728 DenseFPElementsAttr dense;
729 if (matchPattern(val, m_Constant(&dense))) {
730 SmallVector<APFloat> values;
731 for (const APFloat &v : dense.getValues<APFloat>())
732 values.push_back(roundedToBF16(v));
733 return arith::ConstantOp::create(rewriter, loc,
734 DenseElementsAttr::get(vecType, values));
735 }
736 auto bf16VecType =
737 VectorType::get(vecType.getShape(), rewriter.getBF16Type());
738 Value bf16 = smartTruncF32ToBF16(rewriter, loc, val, bf16VecType);
739 return arith::ExtFOp::create(rewriter, loc, vecType, bf16,
740 /*fastmath=*/nullptr);
741}
742
743static bool isF32Vector(Type type) {
744 auto vecType = dyn_cast<VectorType>(type);
745 return vecType && vecType.getElementType().isF32();
746}
747
748/// Pattern to drop `bf16 -> f32 -> bf16` round trips.
749///
750/// Arith used to fold these but no longer does: widening a signaling NaN quiets
751/// it, so the original bit pattern cannot be recovered. This pass has always
752/// made that trade anyway -- `smartTruncF32ToBF16` collapses the same pair on
753/// sight -- so fold here to keep the pipeline's pre-existing behavior rather
754/// than leaving the round trips for the backend.
755struct FoldBF16RoundTripPattern : public OpRewritePattern<arith::TruncFOp> {
756 using OpRewritePattern::OpRewritePattern;
757
758 LogicalResult matchAndRewrite(arith::TruncFOp op,
759 PatternRewriter &rewriter) const override {
760 auto resultType = dyn_cast<VectorType>(op.getType());
761 auto intermediateType = dyn_cast<VectorType>(op.getIn().getType());
762 if (!resultType || !intermediateType ||
763 !resultType.getElementType().isBF16() ||
764 !intermediateType.getElementType().isF32())
765 return failure();
766
767 auto extfOp = op.getIn().getDefiningOp<arith::ExtFOp>();
768 if (!extfOp || extfOp.getIn().getType() != resultType)
769 return failure();
770
771 rewriter.replaceOp(op, extfOp.getIn());
772 return success();
773 }
774};
775
776/// Pattern to emulate f32 binary vector arithmetic ops in bf16.
777/// For an op like: %r = arith.addf %a, %b : vector<16xf32>
778/// Produces:
779/// %a_bf16 = arith.truncf %a : vector<16xf32> to vector<16xbf16>
780/// %b_bf16 = arith.truncf %b : vector<16xf32> to vector<16xbf16>
781/// %r_bf16 = arith.addf %a_bf16, %b_bf16 : vector<16xbf16>
782/// %r = arith.extf %r_bf16 : vector<16xbf16> to vector<16xf32>
783template <typename OpTy>
786
787 LogicalResult matchAndRewrite(OpTy op,
788 PatternRewriter &rewriter) const override {
789 auto resultType = dyn_cast<VectorType>(op.getType());
790 if (!resultType || !resultType.getElementType().isF32())
791 return failure();
792
793 Location loc = op.getLoc();
794 auto bf16VecType =
795 VectorType::get(resultType.getShape(), rewriter.getBF16Type());
796
797 Value lhsBF16 =
798 smartTruncF32ToBF16(rewriter, loc, op.getLhs(), bf16VecType);
799 Value rhsBF16 =
800 smartTruncF32ToBF16(rewriter, loc, op.getRhs(), bf16VecType);
801
802 Value newResult =
803 OpTy::create(rewriter, loc, bf16VecType, lhsBF16, rhsBF16);
804 auto extOp = arith::ExtFOp::create(rewriter, loc, resultType, newResult,
805 /*fastmath=*/nullptr);
806 rewriter.replaceOp(op, extOp);
807 return success();
808 }
809};
810
811/// Pattern to emulate f32 comparison ops in bf16.
812/// Result type stays vector<Nxi1>, only operands are truncated.
813struct EmulateCmpFF32InBF16Pattern : public OpRewritePattern<arith::CmpFOp> {
814 using OpRewritePattern::OpRewritePattern;
815
816 LogicalResult matchAndRewrite(arith::CmpFOp op,
817 PatternRewriter &rewriter) const override {
818 auto lhsType = dyn_cast<VectorType>(op.getLhs().getType());
819 if (!lhsType || !lhsType.getElementType().isF32())
820 return failure();
821
822 Location loc = op.getLoc();
823 auto bf16VecType =
824 VectorType::get(lhsType.getShape(), rewriter.getBF16Type());
825
826 Value lhsBF16 =
827 smartTruncF32ToBF16(rewriter, loc, op.getLhs(), bf16VecType);
828 Value rhsBF16 =
829 smartTruncF32ToBF16(rewriter, loc, op.getRhs(), bf16VecType);
830
831 rewriter.replaceOpWithNewOp<arith::CmpFOp>(op, op.getPredicate(), lhsBF16,
832 rhsBF16);
833 return success();
834 }
835};
836
837/// Pattern to emulate f32 select ops in bf16.
838/// Condition stays vector<Nxi1>, true/false values are truncated.
840 : public OpRewritePattern<arith::SelectOp> {
841 using OpRewritePattern::OpRewritePattern;
842
843 LogicalResult matchAndRewrite(arith::SelectOp op,
844 PatternRewriter &rewriter) const override {
845 auto resultType = dyn_cast<VectorType>(op.getType());
846 if (!resultType || !resultType.getElementType().isF32())
847 return failure();
848
849 Location loc = op.getLoc();
850 auto bf16VecType =
851 VectorType::get(resultType.getShape(), rewriter.getBF16Type());
852
853 Value trueValBF16 =
854 smartTruncF32ToBF16(rewriter, loc, op.getTrueValue(), bf16VecType);
855 Value falseValBF16 =
856 smartTruncF32ToBF16(rewriter, loc, op.getFalseValue(), bf16VecType);
857
858 Value newResult = arith::SelectOp::create(rewriter, loc, op.getCondition(),
859 trueValBF16, falseValBF16);
860 auto extOp = arith::ExtFOp::create(rewriter, loc, resultType, newResult,
861 /*fastmath=*/nullptr);
862 rewriter.replaceOp(op, extOp);
863 return success();
864 }
865};
866
867/// Pattern to emulate an f32 multiply with bf16 inputs. The product of two
868/// bf16 values fits in f32 except at the ends of the exponent range, so only
869/// the inputs are rounded:
870/// %r = arith.mulf %a, %b : vector<16xf32>
871/// becomes
872/// %r = arith.mulf (extf (truncf %a)), (extf (truncf %b)) : vector<16xf32>
873struct EmulateMulFInBF16Pattern : public OpRewritePattern<arith::MulFOp> {
874 using OpRewritePattern::OpRewritePattern;
875
876 LogicalResult matchAndRewrite(arith::MulFOp op,
877 PatternRewriter &rewriter) const override {
878 if (!isF32Vector(op.getType()) ||
879 (holdsBF16(op.getLhs()) && holdsBF16(op.getRhs())))
880 return failure();
881 Location loc = op.getLoc();
882 Value lhs = roundToBF16(rewriter, loc, op.getLhs());
883 Value rhs = roundToBF16(rewriter, loc, op.getRhs());
884 rewriter.replaceOpWithNewOp<arith::MulFOp>(op, lhs, rhs,
885 op.getFastmathAttr());
886 return success();
887 }
888};
889
890/// Pattern to fold an f32 multiply into the add that is its only user, as a
891/// vector.fma with bf16 inputs. Only when both ops allow contraction: the
892/// fused result can differ where the product underflows or overflows.
893struct FuseMulAddFInBF16Pattern : public OpRewritePattern<arith::AddFOp> {
894 using OpRewritePattern::OpRewritePattern;
895
896 LogicalResult matchAndRewrite(arith::AddFOp op,
897 PatternRewriter &rewriter) const override {
898 auto contracts = [](arith::FastMathFlagsAttr flags) {
899 return flags && bitEnumContainsAll(flags.getValue(),
900 arith::FastMathFlags::contract);
901 };
902 if (!isF32Vector(op.getType()) || !contracts(op.getFastmathAttr()))
903 return failure();
904 for (auto [mulOperand, acc] : {std::pair(op.getLhs(), op.getRhs()),
905 std::pair(op.getRhs(), op.getLhs())}) {
906 auto mulOp = mulOperand.getDefiningOp<arith::MulFOp>();
907 if (!mulOp || !mulOp->hasOneUse() || !contracts(mulOp.getFastmathAttr()))
908 continue;
909 Location loc = op.getLoc();
910 Value lhs = roundToBF16(rewriter, loc, mulOp.getLhs());
911 Value rhs = roundToBF16(rewriter, loc, mulOp.getRhs());
912 rewriter.replaceOpWithNewOp<vector::FMAOp>(op, lhs, rhs, acc);
913 rewriter.eraseOp(mulOp);
914 return success();
915 }
916 return failure();
917 }
918};
919
920/// Pattern to emulate f32 vector.fma with bf16 inputs. The accumulator stays
921/// f32.
922struct EmulateFMAF32InBF16Pattern : public OpRewritePattern<vector::FMAOp> {
923 using OpRewritePattern::OpRewritePattern;
924
925 LogicalResult matchAndRewrite(vector::FMAOp op,
926 PatternRewriter &rewriter) const override {
927 if (!isF32Vector(op.getType()) ||
928 (holdsBF16(op.getLhs()) && holdsBF16(op.getRhs())))
929 return failure();
930 Location loc = op.getLoc();
931 Value lhs = roundToBF16(rewriter, loc, op.getLhs());
932 Value rhs = roundToBF16(rewriter, loc, op.getRhs());
933 rewriter.replaceOpWithNewOp<vector::FMAOp>(op, lhs, rhs, op.getAcc());
934 return success();
935 }
936};
937
938/// Pattern to emulate f32 unary vector ops in bf16.
939template <typename OpTy>
942
943 LogicalResult matchAndRewrite(OpTy op,
944 PatternRewriter &rewriter) const override {
945 auto resultType = dyn_cast<VectorType>(op.getType());
946 if (!resultType || !resultType.getElementType().isF32())
947 return failure();
948
949 Location loc = op.getLoc();
950 auto bf16VecType =
951 VectorType::get(resultType.getShape(), rewriter.getBF16Type());
952
953 Value inputBF16 =
954 smartTruncF32ToBF16(rewriter, loc, op->getOperand(0), bf16VecType);
955
956 Value newResult = OpTy::create(rewriter, loc, bf16VecType, inputBF16);
957 auto extOp = arith::ExtFOp::create(rewriter, loc, resultType, newResult,
958 /*fastmath=*/nullptr);
959 rewriter.replaceOp(op, extOp);
960 return success();
961 }
962};
963
965 : public PassWrapper<BF16EmulationPass, OperationPass<>> {
966
967 void runOnOperation() override {
968 auto *op = getOperation();
969 MLIRContext *context = &getContext();
970 RewritePatternSet patterns(context);
971
972 // Multiplies take bf16 inputs; sums stay f32, which the accumulator
973 // adds natively.
974 patterns.add<FuseMulAddFInBF16Pattern>(context, /*benefit=*/2);
978
979 // Note: arith.divf is NOT demoted because bf16 vector divf is unsupported
980 // on all AIE targets (Peano does not legalize G_FDIV on <16 x s16>).
981
982 // Special-case ops (excluding ReductionOp — its scalar accumulator
983 // and result lower to fp_to_bf16/bf16_to_fp which older Peano versions
984 // cannot select on AIE2P; reductions are intentionally left in f32
985 // to avoid these scalar bf16 conversions)
987 context);
988
989 // Unary ops
992
993 // Clean up the round trips the demotion itself introduces.
994 patterns.add<FoldBF16RoundTripPattern>(context);
995
996 (void)applyPatternsGreedily(op, std::move(patterns));
997 }
998};
999
1000std::unique_ptr<::mlir::Pass> xilinx::aievec::createBF16EmulationPass() {
1001 return std::make_unique<BF16EmulationPass>();
1002}
1003
1004// A broadcast of a vector that only adds leading dims, as the vector repeated
1005// by a 1-D shuffle (mask [i % n]). The AIE2P shuffle lowering then sees the
1006// repetition, alone or together with a shuffle that reads it, as one gather.
1008 : public OpRewritePattern<vector::BroadcastOp> {
1009 using OpRewritePattern::OpRewritePattern;
1010
1011 LogicalResult matchAndRewrite(vector::BroadcastOp op,
1012 PatternRewriter &rewriter) const override {
1013 auto srcType = dyn_cast<VectorType>(op.getSourceType());
1014 VectorType resType = op.getResultVectorType();
1015 if (!srcType || srcType.getRank() != 1 || resType.getRank() < 2 ||
1016 resType.getShape().back() != srcType.getNumElements() ||
1017 !srcType.getElementType().isIntOrFloat() || resType.isScalable())
1018 return failure();
1019 int64_t n = srcType.getNumElements();
1020 SmallVector<int64_t> mask;
1021 for (int64_t i = 0; i < resType.getNumElements(); ++i)
1022 mask.push_back(i % n);
1023 Value src = op.getSource();
1024 Value flat =
1025 vector::ShuffleOp::create(rewriter, op.getLoc(), src, src, mask);
1026 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, resType, flat);
1027 return success();
1028 }
1029};
1030
1032 : public PassWrapper<VectorBroadcastLoweringPass, OperationPass<>> {
1033 VectorBroadcastLoweringPass(bool aie2p = false) : aie2p(aie2p) {}
1034
1035 void runOnOperation() override {
1036 auto *op = getOperation();
1037 MLIRContext *context = &getContext();
1038 RewritePatternSet patterns(context);
1039 populateVectorBroadcastLoweringPatterns(patterns);
1041 patterns.getContext());
1042 // Ahead of the generic lowering, which builds the result a row at a time.
1043 if (aie2p)
1044 patterns.add<BroadcastLeadingDimsToRepeatPattern>(patterns.getContext(),
1045 /*benefit=*/2);
1046
1047 (void)applyPatternsGreedily(op, std::move(patterns));
1048 }
1049
1050private:
1051 bool aie2p;
1052};
1053
1054static std::unique_ptr<::mlir::Pass>
1055createVectorBroadcastLoweringPass(bool aie2p = false) {
1056 return std::make_unique<VectorBroadcastLoweringPass>(aie2p);
1057}
1058
1059// This pass converts standard vector ops into a subset of `Vector` ops more
1060// amenable to being converted to `AIEVec`. So far, this process consists of
1061// two steps:
1062// 1) Replace splat transfer reads with contiguous transfer reads followed
1063// by `extract` + `splat` operations.
1064// 2) Split unaligned transfer reads into a wider aligned transfer read
1065// followed by a `vector.extract_strided_slice` operation.
1067 : public PassWrapper<CanonicalizeVectorForAIEVecPass, OperationPass<>> {
1068 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(CanonicalizeVectorForAIEVecPass)
1069
1073
1079
1080 // In case we want to register this pass as a standalone pass for test
1081 // purposes.
1082 StringRef getArgument() const final {
1083 return "test-canonicalize-vector-for-aievec";
1084 }
1085
1086 StringRef getDescription() const final {
1087 return "Canonicalize vector operations for AIEVec conversion";
1088 }
1089
1090 void getDependentDialects(DialectRegistry &registry) const override {
1091 registry
1092 .insert<arith::ArithDialect, memref::MemRefDialect,
1093 vector::VectorDialect, affine::AffineDialect, ub::UBDialect>();
1094 }
1095
1096 Option<std::string> aieTarget{
1097 *this, "aie-target",
1098 llvm::cl::desc(
1099 "Select AIE version: \"aie\", \"aie2\", or \"aie2p\". This will "
1100 "determine the vector size and available operations."),
1101 llvm::cl::init("aie")};
1102
1103 void runOnOperation() override {
1104 auto *op = getOperation();
1105 MLIRContext *context = &getContext();
1106 RewritePatternSet patterns(context);
1107 ConversionTarget target(*context);
1108
1109 AIEArch aieVersion = decodeAIETarget(aieTarget);
1110 if (aieVersion == AIEArch::UNKNOWN) {
1111 op->emitError() << "unknown AIE target '" << aieTarget << "'";
1112 signalPassFailure();
1113 return;
1114 }
1115
1116 populateCommonAIECanonicalizeConversionPatterns(patterns);
1117 configureCommonAIECanonicalizeLegalizations(target);
1118 if (aieVersion == AIEArch::AIE) {
1119 populateAIEv1CanonicalizeConversionPatterns(patterns);
1120 configureAIEv1CanonicalizeLegalizations(target);
1121 } else {
1122 populateAIE2CanonicalizeConversionPatterns(patterns);
1123 configureAIE2CanonicalizeLegalizations(target);
1124 }
1125
1126 {
1127 RewritePatternSet patterns(context);
1130 if (aieTarget == "aie2p")
1131 patterns.add<FlattenVectorIterArgsPattern>(context);
1132 (void)applyPatternsGreedily(op, std::move(patterns));
1133 }
1134
1135 if (failed(applyPartialConversion(op, target, std::move(patterns)))) {
1136 signalPassFailure();
1137 }
1138 }
1139};
1140
1141static std::unique_ptr<::mlir::Pass> createCanonicalizeVectorForAIEVecPass(
1142 const CanonicalizeVectorForAIEVecOptions &options) {
1143 return std::make_unique<CanonicalizeVectorForAIEVecPass>(options);
1144}
1145
1147 : public PassWrapper<ReorderOperationsPass, OperationPass<>> {
1148
1149 void runOnOperation() override {
1150 auto *op = getOperation();
1151 MLIRContext *context = &getContext();
1152 RewritePatternSet patterns(context);
1153
1155 patterns.getContext(),
1156 [](arith::ExtSIOp extOp, vector::BroadcastOp bcastOp) -> Type {
1157 Type extInElemTy = extOp.getIn().getType();
1158 auto extInVecTy = dyn_cast<VectorType>(extInElemTy);
1159 if (extInVecTy)
1160 extInElemTy = extInVecTy.getElementType();
1161 return VectorType::get(bcastOp.getResultVectorType().getShape(),
1162 extInElemTy);
1163 });
1164
1165 (void)applyPatternsGreedily(op, std::move(patterns));
1166 }
1167};
1168
1169static std::unique_ptr<::mlir::Pass> createReorderOperationsPass() {
1170 return std::make_unique<ReorderOperationsPass>();
1171}
1172
1173//============================================================================//
1174//=============== Main Vector2Vector Pipeline Configuration ==================//
1175//============================================================================//
1176
1178 OpPassManager &pm, const CanonicalizeVectorForAIEVecOptions &options) {
1179 // Add `Vector` code canonicalization passes
1180 // TODO: Add passes to unroll vector with unsupported types
1181 // TODO: Add passes to split vectors that won't fit in registers
1182
1183 // If bf16-emulation is enabled, demote f32 vector arithmetic to bf16 first.
1184 if (options.enableBF16Emulation)
1185 pm.addPass(createBF16EmulationPass());
1186
1187 pm.addPass(createReorderOperationsPass());
1188 pm.addPass(createCopyRemovalPass());
1189 pm.addPass(createVectorBroadcastLoweringPass(options.aieTarget == "aie2p"));
1190 pm.addPass(createCanonicalizeVectorForAIEVecPass(options));
1191}
mlir::VectorType getFlattenedVectorType(mlir::VectorType vecTy)
std::unique_ptr<::mlir::Pass > createCopyRemovalPass()
Create a pass that removes unnecessary Copy operations.
std::optional< int64_t > getTransferReadAlignmentOffset(TransferReadLikeOp readOp, mlir::VectorType vType, int64_t alignment)
int32_t getElementSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:48
std::unique_ptr<::mlir::Pass > createBF16EmulationPass()
Create a pass that runs f32 vector arithmetic on the bf16 datapath: multiplies and transcendentals ta...
void buildCanonicalizeVectorForAIEVec(mlir::OpPassManager &pm, const CanonicalizeVectorForAIEVecOptions &options)
AIEArch
Definition Passes.h:19
LogicalResult matchAndRewrite(vector::BroadcastOp op, PatternRewriter &rewriter) const override
Pattern to canonicalize trivial vector.transfer_read operations on subviews.
LogicalResult matchAndRewrite(vector::TransferReadOp readOp, PatternRewriter &rewriter) const override
Pattern to canonicalize trivial vector.transfer_write operations on subviews.
LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp, PatternRewriter &rewriter) const override
void getDependentDialects(DialectRegistry &registry) const override
CanonicalizeVectorForAIEVecPass(const CanonicalizeVectorForAIEVecOptions &options)
LogicalResult matchAndRewrite(vector::InsertOp insOp, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Pattern to emulate f32 binary vector arithmetic ops in bf16.
LogicalResult matchAndRewrite(OpTy op, PatternRewriter &rewriter) const override
Pattern to emulate f32 comparison ops in bf16.
LogicalResult matchAndRewrite(arith::CmpFOp op, PatternRewriter &rewriter) const override
Pattern to emulate f32 vector.fma with bf16 inputs.
LogicalResult matchAndRewrite(vector::FMAOp op, PatternRewriter &rewriter) const override
Pattern to emulate an f32 multiply with bf16 inputs.
LogicalResult matchAndRewrite(arith::MulFOp op, PatternRewriter &rewriter) const override
Pattern to emulate f32 select ops in bf16.
LogicalResult matchAndRewrite(arith::SelectOp op, PatternRewriter &rewriter) const override
Pattern to emulate f32 unary vector ops in bf16.
LogicalResult matchAndRewrite(OpTy op, PatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(scf::ForOp forOp, PatternRewriter &rewriter) const override
Pattern to drop bf16 -> f32 -> bf16 round trips.
LogicalResult matchAndRewrite(arith::TruncFOp op, PatternRewriter &rewriter) const override
Pattern to fold an f32 multiply into the add that is its only user, as a vector.fma with bf16 inputs.
LogicalResult matchAndRewrite(arith::AddFOp op, PatternRewriter &rewriter) const override
SplitUnalignedTransferReadPattern(MLIRContext *context, int64_t maxVectorSize, int64_t alignment)
LogicalResult matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(UnaryOpB bOp, PatternRewriter &rewriter) const override
std::function< Type(UnaryOpA aOp, UnaryOpB bOp)> InferTypeB2AFnTy
SwapUnaryOpsPattern(MLIRContext *context, InferTypeB2AFnTy inferType)
Options for the "canonicalize-vector-for-aievec" pipeline.
Definition Passes.h:34
PassOptions::Option< bool > enableBF16Emulation
Definition Passes.h:40
PassOptions::Option< std::string > aieTarget
Definition Passes.h:35