MLIR-AIE 1.4.5.dev
VectorToAIEVecConversions.cpp
Go to the documentation of this file.
1//===-VectorToAIEVecConversions.cpp - Vector to AIEVec convs. ---*- C++ -*-===//
2//
3// Copyright (C) 2023 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7// This file contains conversions from the Vector dialect into the AIEVec
8// dialect. Conversions assume that the Vector dialect has been rectricted
9// to ops that can be translated to a sequence of valid AIEVec ops.
10//===----------------------------------------------------------------------===//
11
12#include "AIE2PShufflePlanner.h"
13
20
22#include "mlir/Dialect/Affine/IR/AffineOps.h"
23#include "mlir/Dialect/EmitC/IR/EmitC.h"
24#include "mlir/Dialect/Func/IR/FuncOps.h"
25#include "mlir/Dialect/Math/IR/Math.h"
26#include "mlir/Dialect/MemRef/IR/MemRef.h"
27#include "mlir/Dialect/SCF/IR/SCF.h"
28#include "mlir/Dialect/UB/IR/UBOps.h"
29#include "mlir/IR/PatternMatch.h"
30#include "mlir/IR/SymbolTable.h"
31#include "mlir/IR/TypeUtilities.h"
32#include "mlir/Pass/PassManager.h"
33#include "mlir/Transforms/DialectConversion.h"
34#include "mlir/Transforms/Passes.h"
35#include "llvm/ADT/SmallSet.h"
36#include <bitset>
37#include <optional>
38#include <tuple>
39
40#define DEBUG_TYPE "lower-vector-to-aievec"
41
42using namespace llvm;
43using namespace mlir;
44using namespace arith;
45using namespace vector;
46using namespace xilinx;
47using namespace xilinx::aievec;
48
49//===----------------------------------------------------------------------===//
50// Utility functions
51//===----------------------------------------------------------------------===//
52
53// Check if a TruncIOp is part of a shrsi+[clamp]+trunc chain that can be
54// lowered to a compound SRS pattern. Only returns true for chains that the
55// ShiftClampTruncToSRSPattern rewrite will actually match:
56// - shrsi → trunci (no clamp)
57// - shrsi → maxsi → minsi → trunci (full clamp pair)
58// - shrsi → minsi → maxsi → trunci (reversed clamp pair)
59// Does NOT return true for a single min or max without a matching pair,
60// to avoid marking trunci illegal when no rewrite can handle it.
61static bool isSRSCompoundCandidate(arith::TruncIOp trunciOp) {
62 Value source = trunciOp.getIn();
63
64 // Case 1: direct shrsi → trunci
65 if (source.getDefiningOp<arith::ShRSIOp>())
66 return true;
67
68 // Case 2: minsi(maxsi(shrsi(...))) → trunci
69 if (auto minsiOp = source.getDefiningOp<arith::MinSIOp>()) {
70 if (auto maxsiOp = minsiOp.getLhs().getDefiningOp<arith::MaxSIOp>()) {
71 if (maxsiOp.getLhs().getDefiningOp<arith::ShRSIOp>())
72 return true;
73 }
74 }
75
76 // Case 3: maxsi(minsi(shrsi(...))) → trunci (reversed order)
77 if (auto maxsiOp = source.getDefiningOp<arith::MaxSIOp>()) {
78 if (auto minsiOp = maxsiOp.getLhs().getDefiningOp<arith::MinSIOp>()) {
79 if (minsiOp.getLhs().getDefiningOp<arith::ShRSIOp>())
80 return true;
81 }
82 }
83
84 // Case 4: minsi(maxsi(x)) → trunci (clamp-only, no shrsi)
85 // Used for saturating clamp after skip-add (e.g., skip_scale=0).
86 if (auto minsiOp = source.getDefiningOp<arith::MinSIOp>()) {
87 if (minsiOp.getLhs().getDefiningOp<arith::MaxSIOp>())
88 return true;
89 }
90
91 // Case 5: maxsi(minsi(x)) → trunci (reversed clamp-only, no shrsi)
92 if (auto maxsiOp = source.getDefiningOp<arith::MaxSIOp>()) {
93 if (maxsiOp.getLhs().getDefiningOp<arith::MinSIOp>())
94 return true;
95 }
96
97 return false;
98}
99
100// Check if a ShRSIOp's result feeds into a trunci (possibly through
101// clamp ops), meaning the compound pattern will consume it.
102static bool shrsiUsedByCompoundSRS(arith::ShRSIOp rsOp) {
103 for (Operation *user : rsOp->getUsers()) {
104 // Direct: shrsi → trunci (validate full chain via isSRSCompoundCandidate)
105 if (auto truncOp = dyn_cast<arith::TruncIOp>(user))
106 if (isSRSCompoundCandidate(truncOp))
107 return true;
108 // Through clamp: shrsi → maxsi → minsi → trunci
109 // or: shrsi → minsi → maxsi → trunci
110 if (isa<arith::MaxSIOp, arith::MinSIOp>(user)) {
111 for (Operation *user2 : user->getUsers()) {
112 if (auto truncOp2 = dyn_cast<arith::TruncIOp>(user2))
113 if (isSRSCompoundCandidate(truncOp2))
114 return true;
115 if (isa<arith::MaxSIOp, arith::MinSIOp>(user2)) {
116 for (Operation *user3 : user2->getUsers()) {
117 if (auto truncOp3 = dyn_cast<arith::TruncIOp>(user3))
118 if (isSRSCompoundCandidate(truncOp3))
119 return true;
120 }
121 }
122 }
123 }
124 }
125 return false;
126}
127
128// Check if a scalar maxsi/minsi is sandwiched in a compound SRS chain
129// (between shrsi and trunci). Used to keep the op legal so the scalar
130// compound SRS pattern can consume the entire chain.
131static bool scalarClampInCompoundSRS(Operation *op) {
132 if (!isa<arith::MaxSIOp, arith::MinSIOp>(op))
133 return false;
134 // Only apply to scalar types
135 if (isa<VectorType>(op->getResult(0).getType()))
136 return false;
137 for (Operation *user : op->getUsers()) {
138 if (auto truncOp = dyn_cast<arith::TruncIOp>(user)) {
139 if (isSRSCompoundCandidate(truncOp))
140 return true;
141 }
142 if (isa<arith::MaxSIOp, arith::MinSIOp>(user)) {
143 for (Operation *user2 : user->getUsers()) {
144 if (auto truncOp2 = dyn_cast<arith::TruncIOp>(user2)) {
145 if (isSRSCompoundCandidate(truncOp2))
146 return true;
147 }
148 }
149 }
150 }
151 return false;
152}
153
154// The value at the bottom of a chain of vector.shape_casts that ends at the
155// type it started from, or `v` itself. Flattening an op to rank 1 leaves such
156// round trips between it and the ops that use it.
157static Value skipShapeCastRoundTrip(Value v) {
158 Value src = v;
159 while (auto castOp = src.getDefiningOp<vector::ShapeCastOp>())
160 src = castOp.getSource();
161 return src.getType() == v.getType() ? src : v;
162}
163
164// Given a Value, if it is defined by a widening op (arith:ExtSIOp,
165// arith::ExtUIOp, arith::ExtFOp, aievec::UPSOp + aievec::SRSOp,
166// aievec::UPSOp + aievec::CastOp), return the source of the widening op.
167static std::optional<Value> getSourceOfWideningOp(Value src) {
168 src = skipShapeCastRoundTrip(src);
169 if (auto extSIOp = src.getDefiningOp<arith::ExtSIOp>())
170 return extSIOp.getIn();
171 if (auto extUIOp = src.getDefiningOp<arith::ExtUIOp>())
172 return extUIOp.getIn();
173 if (auto extFOp = src.getDefiningOp<arith::ExtFOp>())
174 return extFOp.getIn();
175 if (auto srsOp = src.getDefiningOp<aievec::SRSOp>()) {
176 // Conversion through AIE intrinsics takes two steps:
177 // 1) Load to accumulator: aievec.ups
178 // 2) Move from accumulator: aievec.srs
179 auto srsSource = srsOp.getSource();
180 if (srsSource)
181 if (auto upsOp = srsSource.getDefiningOp<aievec::UPSOp>())
182 return upsOp.getSource();
183 }
184 if (auto castOp = src.getDefiningOp<aievec::CastOp>()) {
185 // Conversion through AIE intrinsics can also take the following two steps:
186 // 1) Load to accumulator: aievec.ups
187 // 2) Move from accumulator: aievec.cast
188 auto castSource = castOp.getSource();
189 if (castSource)
190 if (auto upsOp = castSource.getDefiningOp<aievec::UPSOp>())
191 return upsOp.getSource();
192 }
193 return std::optional<Value>();
194}
195
196// Whether the f32 vector `src` holds bf16 values: it is widened from bf16, or
197// a constant that is exact in bf16.
198static bool holdsBF16(Value src) {
199 if (auto wideSrc = getSourceOfWideningOp(src))
200 return getElementTypeOrSelf(wideSrc->getType()).isBF16();
201 DenseFPElementsAttr dense;
202 if (!matchPattern(src, m_Constant(&dense)) || !dense.getElementType().isF32())
203 return false;
204 return llvm::all_of(dense.getValues<APFloat>(), [](APFloat v) {
205 bool losesInfo = false;
206 v.convert(APFloat::BFloat(), APFloat::rmNearestTiesToEven, &losesInfo);
207 return !losesInfo;
208 });
209}
210
211// The bf16 values held by `src`, which must satisfy holdsBF16. A constant is
212// narrowed where it is used rather than folded to a bf16 constant, so it does
213// not occupy a vector register outside the code that uses it.
214static Value getBF16Source(OpBuilder &builder, Location loc, Value src) {
215 if (auto wideSrc = getSourceOfWideningOp(src))
216 return *wideSrc;
217 auto bf16Type = VectorType::get(cast<VectorType>(src.getType()).getShape(),
218 builder.getBF16Type());
219 auto shift =
220 arith::ConstantOp::create(builder, loc, builder.getI32IntegerAttr(0));
221 return aievec::SRSOp::create(builder, loc, bf16Type, src, shift);
222}
223
224// Given a Value, if it is defined by an integer widening op, return whether
225// that widening is signed: arith.extsi -> signed, arith.extui -> unsigned, and
226// an aievec.srs (from an already-rewritten extsi/extui) -> its own `sign`
227// attribute. Returns std::nullopt when the defining op is not a recognized
228// integer widening op, or carries no signedness: a floating-point arith.extf,
229// or an aievec.ups/aievec.cast (which do not encode signedness). Callers record
230// this on the created matmul op, since peeling the widening op would otherwise
231// discard it (MLIR integers are signless).
232static std::optional<bool> getSignednessOfWideningOp(Value src) {
233 // Look through shape casts, which may sit between the extension op and the
234 // consumer.
235 while (auto castOp = src.getDefiningOp<vector::ShapeCastOp>())
236 src = castOp.getSource();
237 if (src.getDefiningOp<arith::ExtSIOp>())
238 return true;
239 if (src.getDefiningOp<arith::ExtUIOp>())
240 return false;
241 // An arith.extsi/extui may already have been rewritten into an
242 // aievec.ups + aievec.srs pair by another pattern in this same conversion.
243 // Only aievec.srs records a signedness; aievec.ups and aievec.cast do not.
244 if (auto srsOp = src.getDefiningOp<aievec::SRSOp>())
245 return srsOp.getSign() != 0;
246 return std::nullopt;
247}
248
249// Given a Value, if it is defined by a narrowing op (arith::TruncFOp,
250// arith::TruncIOp), return the source of the narrowing op.
251static std::optional<Value> getSourceOfNarrowingOp(Value src) {
252 if (auto truncFOp = src.getDefiningOp<arith::TruncFOp>())
253 return truncFOp.getIn();
254 if (auto truncIOp = src.getDefiningOp<arith::TruncIOp>())
255 return truncIOp.getIn();
256 return std::optional<Value>();
257}
258
259//===----------------------------------------------------------------------===//
260// Type conversion utilities with narrowing/widening optimization awareness
261//===----------------------------------------------------------------------===//
262
263// Smart widen a value to target type. If the value was previously narrowed
264// from the target type, reuse the original source to avoid truncf->extf chains.
265static Value widenValueWithNarrowingCheck(Value val, Type targetType,
266 Location loc,
267 ConversionPatternRewriter &rewriter) {
268 // Check if this value was narrowed from the target type
269 if (auto narrowedSrc = getSourceOfNarrowingOp(val)) {
270 if (narrowedSrc->getType() == targetType)
271 return *narrowedSrc; // Reuse the original value (skip truncf->extf)
272 }
273
274 // Otherwise, create the widening op if needed
275 if (val.getType() == targetType)
276 return val;
277
278 return arith::ExtFOp::create(rewriter, loc, targetType, val,
279 /*fastmath=*/nullptr);
280}
281
282// Result structure for smart narrowing operation
284 Value narrowedValue; // The narrowed value (or original if optimized)
285 bool skipNarrowing; // True if we should skip creating truncf
286 Operation *wideningUser; // The widening op to replace (if skipNarrowing)
287};
288
289// Smart narrow a value to target type. If the result will be immediately
290// widened back, skip both truncf and extf operations.
291static NarrowingResult
292narrowValueWithWideningCheck(Operation *srcOp, Value val, Type targetType,
293 Location loc,
294 ConversionPatternRewriter &rewriter) {
295 NarrowingResult result;
296 result.narrowedValue = val;
297 result.skipNarrowing = false;
298 result.wideningUser = nullptr;
299
300 // Check if srcOp will be immediately widened back
301 if (srcOp->hasOneUse()) {
302 Operation *user = *srcOp->getUsers().begin();
303 if (auto extfOp = dyn_cast<arith::ExtFOp>(user)) {
304 // The result will be widened - skip both truncf and extf
305 result.skipNarrowing = true;
306 result.wideningUser = extfOp;
307 return result;
308 }
309 }
310
311 // Normal case: create the narrowing op
312 result.narrowedValue =
313 arith::TruncFOp::create(rewriter, loc, targetType, val);
314 return result;
315}
316
317// High-level helper to perform a binary operation on bf16 values in f32
318// precision. This function handles:
319// 1. Smart widening of operands (reuses f32 source if narrowed from f32)
320// 2. Executing the operation in f32
321// 3. Smart narrowing back to bf16 (skips truncf->extf if result is widened)
322static void
323performBF16BinaryOpInF32(Value lhs, Value rhs, Operation *srcOp, Location loc,
324 ConversionPatternRewriter &rewriter,
325 const std::function<Value(Value, Value)> &opBuilder) {
326 Type f32Type = rewriter.getF32Type();
327
328 // Smart widen both operands (reuse f32 source if narrowed from f32)
329 Value lhsF32 = widenValueWithNarrowingCheck(lhs, f32Type, loc, rewriter);
330 Value rhsF32 = widenValueWithNarrowingCheck(rhs, f32Type, loc, rewriter);
331
332 // Perform operation in f32
333 Value resultF32 = opBuilder(lhsF32, rhsF32);
334
335 // Smart narrow back to bf16 (skip if result will be widened)
336 auto narrowResult = narrowValueWithWideningCheck(
337 srcOp, resultF32, lhs.getType(), loc, rewriter);
338
339 if (narrowResult.skipNarrowing) {
340 // Replace the widening user directly with f32 result
341 rewriter.replaceOp(narrowResult.wideningUser, resultF32);
342 rewriter.eraseOp(srcOp);
343 } else {
344 rewriter.replaceOp(srcOp, narrowResult.narrowedValue);
345 }
346}
347
348// Given the LHS and RHS of an `arith::AddIOp`, if one of them is defined by an
349// `arith::MulIOp`, return a tuple with the `lhs`, `rhs`, and `acc` of the MAC
350// operation that can replace them.
351static std::optional<std::tuple<Value, Value, Value>>
352extractMACOperandsFromAddOperands(Value addLhs, Value addRhs) {
353 auto *lhsDefOp = addLhs.getDefiningOp();
354 auto *rhsDefOp = addRhs.getDefiningOp();
355 arith::MulIOp mulOp = nullptr;
356 Value acc;
357 if (lhsDefOp) {
358 mulOp = dyn_cast<arith::MulIOp>(lhsDefOp);
359 acc = addRhs;
360 }
361 if (!mulOp && rhsDefOp) {
362 mulOp = dyn_cast<arith::MulIOp>(rhsDefOp);
363 acc = addLhs;
364 }
365 if (mulOp)
366 return std::make_tuple(mulOp.getLhs(), mulOp.getRhs(), acc);
367
368 // If the MulIOp has been already translated to aievec::aie1::MulOp:
369 auto lhsSrsOp = addLhs.getDefiningOp<aievec::SRSOp>();
370 auto rhsSrsOp = addRhs.getDefiningOp<aievec::SRSOp>();
371 aievec::aie1::MulOp aieMulOp = nullptr;
372 if (lhsSrsOp) {
373 aieMulOp = lhsSrsOp.getSource().getDefiningOp<aievec::aie1::MulOp>();
374 acc = addRhs;
375 }
376 if (!aieMulOp && rhsSrsOp) {
377 aieMulOp = rhsSrsOp.getSource().getDefiningOp<aievec::aie1::MulOp>();
378 acc = addLhs;
379 }
380 if (aieMulOp)
381 return std::make_tuple(aieMulOp.getLhs(), aieMulOp.getRhs(), acc);
382 return {};
383}
384
385// Given the LHS and RHS of an `arith::AddFOp`, if one of them is defined by an
386// `arith::MulFOp`, return a tuple with the `lhs`, `rhs`, and `acc` of the FMA
387// operation that can replace them.
388static std::optional<std::tuple<Value, Value, Value>>
389extractFMACOperandsFromAddOperands(Value addLhs, Value addRhs) {
390 auto *lhsDefOp = addLhs.getDefiningOp();
391 auto *rhsDefOp = addRhs.getDefiningOp();
392 arith::MulFOp mulOp = nullptr;
393 Value acc;
394 if (lhsDefOp) {
395 mulOp = dyn_cast<arith::MulFOp>(lhsDefOp);
396 acc = addRhs;
397 }
398 if (!mulOp && rhsDefOp) {
399 mulOp = dyn_cast<arith::MulFOp>(rhsDefOp);
400 acc = addLhs;
401 }
402 if (mulOp)
403 return std::make_tuple(mulOp.getLhs(), mulOp.getRhs(), acc);
404 return {};
405}
406
407// Convert a input value to a target vector type. This function can insert
408// multiple aievec ops depending on the combination of input and output vector
409// types.
410static std::optional<Value>
411convertValueToTargetTypeAIE2(ConversionPatternRewriter &rewriter, Location loc,
412 Value inputVal, VectorType tgtType) {
413 auto srcType = cast<VectorType>(inputVal.getType());
414 auto srcElemType = srcType.getElementType();
415 unsigned srcBitWidth = srcElemType.getIntOrFloatBitWidth();
416 unsigned srcLaneSize = getVectorLaneSize(srcType);
417
418 auto tgtElemType = tgtType.getElementType();
419 unsigned tgtBitWidth = tgtElemType.getIntOrFloatBitWidth();
420 unsigned tgtLaneSize = getVectorLaneSize(tgtType);
421
422 if (srcType == tgtType)
423 return inputVal;
424
425 if ((srcElemType == tgtElemType) && (srcLaneSize != tgtLaneSize)) {
426 // TODO: relax the condition below?
427 if ((srcLaneSize == 16 && tgtLaneSize == 32 &&
428 isa<FloatType>(srcElemType)) ||
429 (srcLaneSize == 32 && tgtLaneSize == 64 &&
430 isa<IntegerType>(srcElemType))) {
431 auto zeroConstOp = arith::ConstantOp::create(
432 rewriter, loc, srcType.getElementType(),
433 rewriter.getZeroAttr(srcType.getElementType()));
434 auto broadcastZeroOp = aievec::BroadcastScalarOp::create(
435 rewriter, loc, tgtType, zeroConstOp->getResult(0));
436 auto extOp = aievec::ExtOp::create(rewriter, loc, srcType,
437 broadcastZeroOp->getResult(0), 0);
438
439 SmallVector<Value> inputSources = {inputVal, extOp->getResult(0)};
440 auto concatOp =
441 aievec::ConcatOp::create(rewriter, loc, tgtType, inputSources);
442
443 return concatOp.getResult();
444 }
445 } else if ((srcElemType != tgtElemType) && (srcLaneSize == tgtLaneSize) &&
446 isa<IntegerType>(srcElemType) && isa<IntegerType>(tgtElemType)) {
447 if (srcBitWidth == 16 && tgtBitWidth == 32 && srcLaneSize == 16) {
448 // Case 1: vector<16xi16> to vector<16xi32> conversion by aievec.ups +
449 // aievec.cast
450 auto accType = getVectorOpDestType(srcType, /*AIE2 =*/true);
451 auto upsOp = aievec::UPSOp::create(rewriter, loc, accType, inputVal);
452 auto castOp = aievec::CastOp::create(
453 rewriter, loc, tgtType, upsOp.getResult(), /*isResAcc*/ false);
454 return castOp.getResult();
455 }
456
457 if (srcBitWidth == 8 && tgtBitWidth == 32 && srcLaneSize == 16) {
458 // Case 2: vector<16xi8> to vector<16xi32> conversion by aievec.concat +
459 // aievec.ups + aievec.cast + aievec.ext
460 auto concatOutType = createVectorType(32, srcElemType);
461 auto concatOp =
462 aievec::ConcatOp::create(rewriter, loc, concatOutType,
463 SmallVector<Value>({inputVal, inputVal}));
464 auto accType = getVectorOpDestType(concatOutType, /*AIE2 =*/true);
465 auto upsOp =
466 aievec::UPSOp::create(rewriter, loc, accType, concatOp.getResult());
467 auto castType = createVectorType(32, tgtElemType);
468 auto castOp = aievec::CastOp::create(
469 rewriter, loc, castType, upsOp.getResult(), /*isResAcc*/ false);
470 auto extOp =
471 aievec::ExtOp::create(rewriter, loc, tgtType, castOp.getResult(), 0);
472 return extOp.getResult();
473 }
474
475 if (srcBitWidth == 8 && tgtBitWidth == 16 && srcLaneSize == 32) {
476 // Case 3: vector<32xi8> to vector<32xi16> conversion by aievec.unpack
477 auto unpackOp =
478 aievec::UnpackOp::create(rewriter, loc, tgtType, inputVal);
479 return unpackOp.getResult();
480 }
481 }
482
483 return std::nullopt;
484}
485
486// Return the list of attributes that configure an `aievec.select` op to
487// perform a rotation of the input vector by `rotation` number of elements.
488// The attribute values depend on the vector type of the select operation.
489static SmallVector<NamedAttribute>
490buildAttributeListForRotationSelectOp(PatternRewriter &rewriter, VectorType vTy,
491 int64_t rotation) {
492 unsigned width = 0;
493 auto elemTy = vTy.getElementType();
494 if (auto intTy = dyn_cast<IntegerType>(elemTy))
495 width = intTy.getWidth();
496 StringAttr attr0 = rewriter.getStringAttr("0");
497 StringAttr attr0x06040200 = rewriter.getStringAttr("0x06040200");
498 StringAttr attr0x0e0c0a08 = rewriter.getStringAttr("0x0e0c0a08");
499 StringAttr attr0x2103 = rewriter.getStringAttr("0x2103");
500 StringAttr attr0x3210 = rewriter.getStringAttr("0x3210");
501 StringAttr selectAttrName = rewriter.getStringAttr("select");
502 StringAttr xoffsetsAttrName = rewriter.getStringAttr("xoffsets");
503 StringAttr xoffsetsHiAttrName = rewriter.getStringAttr("xoffsets_hi");
504 StringAttr xsquareAttrName = rewriter.getStringAttr("xsquare");
505 StringAttr xstartAttrName = rewriter.getStringAttr("xstart");
506 StringAttr yoffsetsAttrName = rewriter.getStringAttr("yoffsets");
507 StringAttr yoffsetsHiAttrName = rewriter.getStringAttr("yoffsets_hi");
508 StringAttr ysquareAttrName = rewriter.getStringAttr("ysquare");
509 StringAttr ystartAttrName = rewriter.getStringAttr("ystart");
510
511 switch (width) {
512 case 16: {
513 if (rotation % 2) {
514 int64_t xstart = rotation + 1;
515 int64_t ystart = rotation - 1;
516 return SmallVector<NamedAttribute, 9>(
517 {{selectAttrName, rewriter.getStringAttr("0x11111111")},
518 {xoffsetsAttrName, attr0x06040200},
519 {xoffsetsHiAttrName, attr0x0e0c0a08},
520 {xsquareAttrName, attr0x2103},
521 {xstartAttrName, rewriter.getStringAttr(std::to_string(xstart))},
522 {yoffsetsAttrName, rewriter.getStringAttr("0x0503010f")},
523 {yoffsetsHiAttrName, rewriter.getStringAttr("0x0d0b0907")},
524 {ysquareAttrName, attr0x2103},
525 {ystartAttrName, rewriter.getStringAttr(std::to_string(ystart))}});
526 }
527 return SmallVector<NamedAttribute, 9>(
528 {{selectAttrName, attr0},
529 {xoffsetsAttrName, attr0x06040200},
530 {xoffsetsHiAttrName, attr0x0e0c0a08},
531 {xsquareAttrName, attr0x3210},
532 {xstartAttrName, rewriter.getStringAttr(std::to_string(rotation))},
533 {yoffsetsAttrName, attr0},
534 {yoffsetsHiAttrName, attr0},
535 {ysquareAttrName, attr0},
536 {ystartAttrName, attr0}});
537 }
538 case 32:
539 return SmallVector<NamedAttribute, 7>(
540 {{selectAttrName, attr0},
541 {xoffsetsAttrName, rewriter.getStringAttr("0x76543210")},
542 {xsquareAttrName, attr0x3210},
543 {xstartAttrName, rewriter.getStringAttr(std::to_string(rotation))},
544 {yoffsetsAttrName, attr0},
545 {ysquareAttrName, attr0},
546 {ystartAttrName, attr0}});
547 default:
548 llvm::report_fatal_error("Unexpected width!");
549 }
550
551 return {};
552}
553
554namespace xilinx::aievec {
555
556// Also called from AIEVecOptimizations.cpp via a forward declaration there;
557// not static.
558SmallVector<NamedAttribute>
559// NOLINTNEXTLINE(misc-use-internal-linkage)
560buildFMAOpSplatAttrForElemTy(aievec::aie1::FMAOp fmaOp, int64_t bcastPos,
561 int64_t step = 1) {
562 unsigned width = 0;
563 auto elemTy = fmaOp.getLhs().getType().getElementType();
564 if (auto intTy = dyn_cast<IntegerType>(elemTy))
565 width = intTy.getWidth();
566 auto *ctx = fmaOp.getContext();
567 switch (width) {
568 case 16:
569 // NOTE: The pattern is:
570 // acc[0] = x[0] * z[bcastPos] + x[16] * z[bcastPos+step]
571 // acc[1] = x[1] * z[bcastPos] + x[17] * z[bcastPos+step]
572 // acc[2] = x[2] * z[bcastPos] + x[18] * z[bcastPos+step]
573 // acc[3] = x[3] * z[bcastPos] + x[19] * z[bcastPos+step]
574 // acc[4] = x[4] * z[bcastPos] + x[20] * z[bcastPos+step]
575 // acc[5] = x[5] * z[bcastPos] + x[21] * z[bcastPos+step]
576 // acc[6] = x[6] * z[bcastPos] + x[22] * z[bcastPos+step]
577 // acc[7] = x[7] * z[bcastPos] + x[23] * z[bcastPos+step]
578 // acc[8] = x[8] * z[bcastPos] + x[24] * z[bcastPos+step]
579 // acc[9] = x[9] * z[bcastPos] + x[25] * z[bcastPos+step]
580 // acc[10] = x[10] * z[bcastPos] + x[26] * z[bcastPos+step]
581 // acc[11] = x[11] * z[bcastPos] + x[27] * z[bcastPos+step]
582 // acc[12] = x[12] * z[bcastPos] + x[28] * z[bcastPos+step]
583 // acc[13] = x[13] * z[bcastPos] + x[29] * z[bcastPos+step]
584 // acc[14] = x[14] * z[bcastPos] + x[30] * z[bcastPos+step]
585 // acc[15] = x[15] * z[bcastPos] + x[31] * z[bcastPos+step]
586 return SmallVector<NamedAttribute, 11>(
587 {{fmaOp.getXstartAttrName(), StringAttr::get(ctx, "0")},
588 {fmaOp.getXoffsetsAttrName(), StringAttr::get(ctx, "0x73727170")},
589 {fmaOp.getXoffsetsHiAttrName(), StringAttr::get(ctx, "0x77767574")},
590 {fmaOp.getXstepAttrName(), fmaOp.getXstepAttr()},
591 {fmaOp.getXsquareAttrName(), StringAttr::get(ctx, "0x3120")},
592 {fmaOp.getZstartAttrName(),
593 StringAttr::get(ctx, std::to_string(bcastPos))},
594 {fmaOp.getZoffsetsAttrName(), StringAttr::get(ctx, "0")},
595 {fmaOp.getZoffsetsHiAttrName(), StringAttr::get(ctx, "0")},
596 {fmaOp.getZstepAttrName(), StringAttr::get(ctx, std::to_string(step))},
597 {fmaOp.getZsquareAttrName(), fmaOp.getZsquareAttr()},
598 {fmaOp.getFmsubAttrName(), fmaOp.getFmsubAttr()}});
599 case 32:
600 return SmallVector<NamedAttribute, 11>(
601 {{fmaOp.getXstartAttrName(), StringAttr::get(ctx, "0")},
602 {fmaOp.getXoffsetsAttrName(), StringAttr::get(ctx, "0x76543210")},
603 {fmaOp.getXoffsetsHiAttrName(), fmaOp.getXoffsetsHiAttr()},
604 {fmaOp.getXstepAttrName(), fmaOp.getXstepAttr()},
605 {fmaOp.getXsquareAttrName(), fmaOp.getXsquareAttr()},
606 {fmaOp.getZstartAttrName(),
607 StringAttr::get(ctx, std::to_string(bcastPos))},
608 {fmaOp.getZoffsetsAttrName(), StringAttr::get(ctx, "0x00000000")},
609 {fmaOp.getZoffsetsHiAttrName(), fmaOp.getZoffsetsHiAttr()},
610 {fmaOp.getZstepAttrName(), fmaOp.getZstepAttr()},
611 {fmaOp.getZsquareAttrName(), fmaOp.getZsquareAttr()},
612 {fmaOp.getFmsubAttrName(), fmaOp.getFmsubAttr()}});
613 default:
614 llvm::report_fatal_error("Unexpected width!");
615 }
616
617 return {};
618}
619
620} // namespace xilinx::aievec
621
622template <typename SrcOpTy, typename AIEv2ElemOp>
623static LogicalResult genAddElemAIE2(ConversionPatternRewriter &rewriter,
624 Value lval, Value rval, VectorType srcType,
625 SrcOpTy srcOp) {
626 auto lCastOp = aievec::CastOp::create(rewriter, srcOp.getLoc(), srcType, lval,
627 /*isResAcc*/ true);
628 auto rCastOp = aievec::CastOp::create(rewriter, srcOp.getLoc(), srcType, rval,
629 /*isResAcc*/ true);
630 auto elemOp = AIEv2ElemOp::create(
631 rewriter, srcOp.getLoc(), lCastOp->getResult(0).getType(),
632 lCastOp->getResult(0), rCastOp->getResult(0));
633 rewriter.replaceOpWithNewOp<aievec::CastOp>(
634 srcOp, srcOp.getType(), elemOp.getResult(), /*isResAcc*/ false);
635 return success();
636}
637
638static arith::CmpIPredicate
639convertToIntegerPredicate(arith::CmpFPredicate pred) {
640 switch (pred) {
641 case CmpFPredicate::UEQ:
642 case CmpFPredicate::OEQ:
643 return CmpIPredicate::eq;
644 case CmpFPredicate::UGT:
645 return CmpIPredicate::ugt;
646 case CmpFPredicate::OGT:
647 return CmpIPredicate::sgt;
648 case CmpFPredicate::UGE:
649 return CmpIPredicate::uge;
650 case CmpFPredicate::OGE:
651 return CmpIPredicate::sge;
652 case CmpFPredicate::ULT:
653 return CmpIPredicate::ult;
654 case CmpFPredicate::OLT:
655 return CmpIPredicate::slt;
656 case CmpFPredicate::ULE:
657 return CmpIPredicate::ule;
658 case CmpFPredicate::OLE:
659 return CmpIPredicate::sle;
660 case CmpFPredicate::UNE:
661 case CmpFPredicate::ONE:
662 return CmpIPredicate::ne;
663 default:
664 llvm::report_fatal_error("Unexpected predicate!");
665 }
666}
667
668static arith::CmpIPredicate
669convertToIntegerPredicate(arith::CmpIPredicate pred) {
670 return pred;
671}
672
673static aievec::CmpOp createCmpOpAIE2(ConversionPatternRewriter &rewriter,
674 CmpIPredicate pred, Location loc,
675 Type type, Value lhs, Value rhs) {
676 switch (pred) {
677 case CmpIPredicate::eq:
678 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "eq");
679 case CmpIPredicate::ne:
680 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "ne");
681 case CmpIPredicate::slt:
682 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "slt");
683 case CmpIPredicate::ult:
684 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "ult");
685 case CmpIPredicate::sle:
686 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "sle");
687 case CmpIPredicate::ule:
688 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "ule");
689 case CmpIPredicate::sgt:
690 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "sgt");
691 case CmpIPredicate::ugt:
692 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "ugt");
693 case CmpIPredicate::sge:
694 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "sge");
695 case CmpIPredicate::uge:
696 return aievec::CmpOp::create(rewriter, loc, type, lhs, rhs, "uge");
697 }
698 return nullptr;
699}
700
701template <typename DstOpTy>
702static aievec::ExtElemOp
703generateAIEVecOpsForReductionOp(ConversionPatternRewriter &rewriter,
704 vector::ReductionOp srcOp, int shiftIndex,
705 Value curValue) {
706 assert(shiftIndex > 0 && (shiftIndex & (shiftIndex - 1)) == 0 &&
707 "shiftIndex must be power of 2");
708
709 Location loc = srcOp.getLoc();
710 auto vType = cast<VectorType>(curValue.getType());
711 Type scalarType = vType.getElementType();
712 Type vecType = curValue.getType();
713 DstOpTy curOp = nullptr;
714 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
715
716 for (int id = shiftIndex; id > 0; id /= 2) {
717 auto constOp = arith::ConstantOp::create(
718 rewriter, loc, rewriter.getI32IntegerAttr(id * elWidth / 8));
719
720 auto shiftBytesOp = aievec::ShiftOp::create(
721 rewriter, loc, vecType, curValue, curValue, constOp.getResult());
722
723 curOp = DstOpTy::create(rewriter, loc, vecType, curValue,
724 shiftBytesOp.getResult());
725
726 curValue = curOp.getResult();
727 }
728
729 auto zeroConstOp =
730 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
731 return aievec::ExtElemOp::create(rewriter, loc, scalarType, curOp,
732 zeroConstOp.getResult());
733}
734
735// Helper to pad a v16bf16 vector to v32bf16 by concatenating with a splat of
736// the given infinity value. Used by min/max reduction patterns.
737// Returns {paddedVector, newLaneSize}.
738static std::pair<Value, unsigned>
739padV16ToV32WithInfinity(ConversionPatternRewriter &rewriter, Location loc,
740 Value inputVec, Type scalarType, bool negativeInf) {
741 VectorType v32bf16Type = createVectorType(32, scalarType);
742 VectorType v16bf16Type = createVectorType(16, scalarType);
743
744 // Create a scalar infinity constant
745 auto infAttr = rewriter.getFloatAttr(
746 scalarType,
747 APFloat::getInf(cast<FloatType>(scalarType).getFloatSemantics(),
748 negativeInf));
749 auto splatInf = arith::ConstantOp::create(rewriter, loc, infAttr).getResult();
750
751 // Broadcast to v32bf16, then extract upper half (which is also infinity)
752 auto infVec =
753 aievec::BroadcastScalarOp::create(rewriter, loc, v32bf16Type, splatInf);
754 auto infUpperHalf =
755 aievec::ExtOp::create(rewriter, loc, v16bf16Type, infVec, 1);
756
757 // Concatenate input with infinity padding
758 Value paddedVec =
759 aievec::ConcatOp::create(rewriter, loc, v32bf16Type,
760 ValueRange{inputVec, infUpperHalf.getResult()});
761
762 return {paddedVec, 32};
763}
764
765// Helper to pad a v16bf16 vector to v32bf16 by concatenating with zeros.
766// Used by elementwise min/max/cmp/sel patterns for 256-bit bf16 support.
767static Value padV16ToV32WithZeros(ConversionPatternRewriter &rewriter,
768 Location loc, Value inputVec,
769 Type scalarType) {
770 VectorType v32Type = createVectorType(32, scalarType);
771 VectorType v16Type = createVectorType(16, scalarType);
772 auto zeroAttr = rewriter.getZeroAttr(v16Type);
773 auto zeroVec = arith::ConstantOp::create(rewriter, loc, zeroAttr);
774 return aievec::ConcatOp::create(rewriter, loc, v32Type,
775 ValueRange{inputVec, zeroVec.getResult()});
776}
777
778static func::FuncOp getOrInsertFuncDecl(ConversionPatternRewriter &rewriter,
779 Operation *parentSymbolTableOp,
780 StringRef funcName, TypeRange inTypes,
781 TypeRange outTypes) {
782
783 mlir::OpBuilder::InsertionGuard insertGuard(rewriter);
784 rewriter.setInsertionPointToStart(
785 &parentSymbolTableOp->getRegions().front().getBlocks().front());
786 SymbolTable st = SymbolTable(parentSymbolTableOp);
787 func::FuncOp fnOpLookup = st.lookup<func::FuncOp>(funcName);
788 func::FuncOp fnOp;
789 // if the function is already declared, use the existing function, don't
790 // declare multiple times
791 if (fnOpLookup != nullptr) {
792 fnOp = fnOpLookup;
793 } else {
794 StringAttr t1 = rewriter.getStringAttr("sym_visibility");
795 StringAttr t2 = rewriter.getStringAttr("private");
796 NamedAttribute funcAccess = NamedAttribute(t1, t2);
797 FunctionType fnType =
798 mlir::FunctionType::get(rewriter.getContext(), inTypes, outTypes);
799 fnOp = func::FuncOp::create(rewriter, parentSymbolTableOp->getLoc(),
800 funcName, fnType, funcAccess);
801 }
802 return fnOp;
803}
804
805//===----------------------------------------------------------------------===//
806// Wide vector splitting utility
807//===----------------------------------------------------------------------===//
808
809// Utility function to split a wide vector operation (e.g., v32bf16) into two
810// half-width operations (e.g., v16bf16) and concatenate the results.
811// This pattern is common for AIE2 when hardware only supports 16-lane
812// operations but we need to process 32-lane vectors.
813//
814// Template parameters:
815// SrcOpTy - The source operation type (e.g., arith::MulFOp, math::ExpOp)
816//
817// Parameters:
818// srcOp - The source operation to replace
819// wideInputs - The wide input values (e.g., lhs and rhs for binary ops)
820// halfType - The half-width vector type (e.g., vector<16xbf16>)
821// wideType - The wide vector type (e.g., vector<32xbf16>)
822// rewriter - The pattern rewriter
823// processHalves - Callback that processes the lower and upper halves
824// and returns a pair of half-width results to be concatenated
825//
826// The callback signature is:
827// std::pair<Value, Value>(ArrayRef<std::pair<Value, Value>> halfInputs,
828// Location loc, ConversionPatternRewriter &rewriter)
829// where halfInputs[i] is {lowerHalf, upperHalf} for each wideInput
830template <typename SrcOpTy, typename Func>
831static void splitWideVectorOp(SrcOpTy srcOp, ArrayRef<Value> wideInputs,
832 VectorType halfType, VectorType wideType,
833 ConversionPatternRewriter &rewriter,
834 Func &&processHalves) {
835
836 Location loc = srcOp.getLoc();
837
838 // Extract lower and upper halves for each wide input
839 SmallVector<std::pair<Value, Value>> halfInputs;
840 halfInputs.reserve(wideInputs.size());
841 for (Value wideInput : wideInputs) {
842 auto lowerHalf =
843 aievec::ExtOp::create(rewriter, loc, halfType, wideInput, 0);
844 auto upperHalf =
845 aievec::ExtOp::create(rewriter, loc, halfType, wideInput, 1);
846 halfInputs.emplace_back(lowerHalf.getResult(), upperHalf.getResult());
847 }
848
849 // Process halves using the callback
850 auto [lowResult, highResult] = processHalves(halfInputs, loc, rewriter);
851
852 // Concatenate results
853 SmallVector<Value> concatSources = {lowResult, highResult};
854 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(srcOp, wideType, concatSources);
855}
856
857// Simplified version for unary operations
858template <typename SrcOpTy>
859static void splitWideUnaryVectorOp(
860 SrcOpTy srcOp, Value wideInput, VectorType halfType, VectorType wideType,
861 ConversionPatternRewriter &rewriter,
862 std::function<Value(Value, Location, ConversionPatternRewriter &)>
863 processHalf) {
864
865 splitWideVectorOp<SrcOpTy>(
866 srcOp, {wideInput}, halfType, wideType, rewriter,
867 [&processHalf](ArrayRef<std::pair<Value, Value>> halfInputs, Location loc,
868 ConversionPatternRewriter &rewriter) {
869 auto [lowerHalf, upperHalf] = halfInputs[0];
870 Value lowResult = processHalf(lowerHalf, loc, rewriter);
871 Value highResult = processHalf(upperHalf, loc, rewriter);
872 return std::make_pair(lowResult, highResult);
873 });
874}
875
876// Simplified version for binary operations
877template <typename SrcOpTy>
878static void splitWideBinaryVectorOp(
879 SrcOpTy srcOp, Value lhs, Value rhs, VectorType halfType,
880 VectorType wideType, ConversionPatternRewriter &rewriter,
881 std::function<Value(Value, Value, Location, ConversionPatternRewriter &)>
882 processHalf) {
883
884 splitWideVectorOp<SrcOpTy>(
885 srcOp, {lhs, rhs}, halfType, wideType, rewriter,
886 [&processHalf](ArrayRef<std::pair<Value, Value>> halfInputs, Location loc,
887 ConversionPatternRewriter &rewriter) {
888 auto [lhsLow, lhsHigh] = halfInputs[0];
889 auto [rhsLow, rhsHigh] = halfInputs[1];
890 Value lowResult = processHalf(lhsLow, rhsLow, loc, rewriter);
891 Value highResult = processHalf(lhsHigh, rhsHigh, loc, rewriter);
892 return std::make_pair(lowResult, highResult);
893 });
894}
895
896//===----------------------------------------------------------------------===//
897// Math operation matching utilities
898//===----------------------------------------------------------------------===//
899
900// Check if math.exp op matches AIE2 LUT-based exp constraints
901static bool matchExpOpForAIE2LUT(math::ExpOp::Adaptor adaptor) {
902 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
903
904 if (!srcType)
905 return false;
906
907 Type scalarType = srcType.getElementType();
908 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
909 unsigned laneSize = getVectorLaneSize(srcType);
910 // AIE2 LUT-based exp: supports v16bf16 and v32bf16
911 return isa<FloatType>(scalarType) && (laneSize == 16 || laneSize == 32) &&
912 elWidth == 16;
913}
914
915// Check if math.exp op matches AIE2P exp constraints
916static bool matchExpOpForAIE2P(math::ExpOp::Adaptor adaptor) {
917 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
918
919 if (!srcType)
920 return false;
921
922 Type scalarType = srcType.getElementType();
923 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
924 unsigned laneSize = getVectorLaneSize(srcType);
925 // AIE2P exp: supports v16bf16 and v32bf16
926 return scalarType.isBF16() && (laneSize == 16 || laneSize == 32) &&
927 elWidth == 16;
928}
929
930// Check if a vector type matches the AIE2P tanh and exp2 constraints. These
931// intrinsics take an accfloat operand, so f32 vectors map onto them as well as
932// bf16.
933static bool isAIE2PAccFloatUnaryType(VectorType type) {
934 if (type.isScalable())
935 return false;
936 Type elemType = type.getElementType();
937 unsigned laneSize = getVectorLaneSize(type);
938 return (elemType.isBF16() || elemType.isF32()) &&
939 (laneSize == 16 || laneSize == 32);
940}
941
942// Check if math.tanh, math.erf or math.exp2 of `type` is lowered for AIE2P: a
943// bf16 or f32 scalar, one-lane vector, or vector matching
944// isAIE2PAccFloatUnaryType.
945static bool isAIE2PAccFloatUnaryOrScalarType(Type type) {
946 auto vecType = dyn_cast<VectorType>(type);
947 if (!vecType)
948 return type.isBF16() || type.isF32();
949 if (!vecType.isScalable() && vecType.getRank() == 1 &&
950 vecType.getNumElements() == 1) {
951 Type elemType = vecType.getElementType();
952 return elemType.isBF16() || elemType.isF32();
953 }
954 return isAIE2PAccFloatUnaryType(vecType);
955}
956
957//===----------------------------------------------------------------------===//
958// Rewrite patterns
959//===----------------------------------------------------------------------===//
960
961// This pattern fold `vector.extract` and `vector.broadcast` into
962// `aievec.broadcast` for AIE2
964 : OpConversionPattern<vector::BroadcastOp> {
965 using OpConversionPattern::OpConversionPattern;
966
967 LogicalResult
968 matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor,
969 ConversionPatternRewriter &rewriter) const override {
970
971 auto extOp = adaptor.getSource().getDefiningOp<vector::ExtractOp>();
972
973 if (!extOp)
974 return failure();
975
976 auto src = extOp.getSource();
977 auto pos = extOp.getStaticPosition();
978 int64_t posVal = pos[0];
979 auto srcVecType = cast<VectorType>(src.getType());
980 auto resultType = cast<VectorType>(bcastOp.getResult().getType());
981 if (srcVecType != resultType) {
982 if (srcVecType.getNumElements() != 2 * resultType.getNumElements())
983 return failure();
984 auto half = static_cast<int8_t>(posVal / resultType.getNumElements());
985 posVal -= half * resultType.getNumElements();
986 src = aievec::ExtOp::create(rewriter, extOp.getLoc(), resultType, src,
987 rewriter.getI8IntegerAttr(half))
988 .getResult();
989 }
990
991 unsigned elWidth = resultType.getElementType().getIntOrFloatBitWidth();
992
993 if (unsigned laneSize = getVectorLaneSize(resultType);
994 laneSize * elWidth == 512) {
995 // Common use case for the broadcast_elem intrinsic
996 rewriter.replaceOpWithNewOp<aievec::BroadcastOp>(bcastOp, resultType, src,
997 posVal);
998 } else if (laneSize * elWidth == 256) {
999 // e.g. need v16bf16 due to the subsequent v16accfloat operation
1000 VectorType aievecBcastType =
1001 createVectorType(512 / elWidth, resultType.getElementType());
1002 auto concatOp =
1003 aievec::ConcatOp::create(rewriter, bcastOp.getLoc(), aievecBcastType,
1004 SmallVector<Value>({src, src}));
1005 auto aieBcastOp = aievec::BroadcastOp::create(
1006 rewriter, bcastOp.getLoc(), aievecBcastType, concatOp.getResult(),
1007 posVal);
1008 rewriter.replaceOpWithNewOp<aievec::ExtOp>(bcastOp, resultType,
1009 aieBcastOp.getResult(), 0);
1010 } else if (laneSize * elWidth == 1024) {
1011 // e.g. need v32int32 due to the subsequent v32acc32 operation
1012 VectorType aievecBcastType =
1013 createVectorType(512 / elWidth, resultType.getElementType());
1014 auto half = static_cast<int8_t>(posVal / resultType.getNumElements());
1015 posVal -= half * resultType.getNumElements();
1016 auto extOp =
1017 aievec::ExtOp::create(rewriter, bcastOp.getLoc(), aievecBcastType,
1018 src, rewriter.getI8IntegerAttr(half));
1019 auto aieBcastOp = aievec::BroadcastOp::create(rewriter, bcastOp.getLoc(),
1020 aievecBcastType,
1021 extOp.getResult(), posVal);
1022 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(
1023 bcastOp, resultType,
1024 SmallVector<Value>({aieBcastOp.getResult(), aieBcastOp.getResult()}));
1025 } else {
1026 return failure();
1027 }
1028
1029 return success();
1030 }
1031};
1032
1033struct ConvertSplatToAIEBroadcast : OpConversionPattern<vector::BroadcastOp> {
1034 using OpConversionPattern::OpConversionPattern;
1035
1036 LogicalResult
1037 matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor,
1038 ConversionPatternRewriter &rewriter) const override {
1039
1040 if (adaptor.getSource().getDefiningOp<vector::ExtractOp>())
1041 return failure();
1042
1043 auto resultType = cast<VectorType>(bcastOp.getResult().getType());
1044 auto flatResultType = getFlattenedVectorType(resultType);
1045 Type scalarType = resultType.getElementType();
1046 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
1047 unsigned laneSize = getVectorLaneSize(resultType);
1048 auto src = bcastOp.getSource();
1049
1050 if (laneSize * elWidth == 512) {
1051 Value newOp = aievec::BroadcastScalarOp::create(
1052 rewriter, bcastOp.getLoc(), flatResultType, src);
1053 if (resultType != flatResultType)
1054 newOp = vector::ShapeCastOp::create(rewriter, bcastOp.getLoc(),
1055 resultType, newOp);
1056 rewriter.replaceOp(bcastOp, newOp);
1057 return success();
1058 }
1059
1060 if (laneSize * elWidth == 256) {
1061 VectorType vecType = createVectorType(512 / elWidth, scalarType);
1062 auto aieBcastOp = aievec::BroadcastScalarOp::create(
1063 rewriter, bcastOp.getLoc(), vecType, src);
1064 Value newOp =
1065 aievec::ExtOp::create(rewriter, bcastOp.getLoc(), flatResultType,
1066 aieBcastOp.getResult(), 0);
1067 if (resultType != flatResultType)
1068 newOp = vector::ShapeCastOp::create(rewriter, bcastOp.getLoc(),
1069 resultType, newOp);
1070 rewriter.replaceOp(bcastOp, newOp);
1071 return success();
1072 }
1073
1074 if (laneSize * elWidth == 1024) {
1075 VectorType vecType = createVectorType(512 / elWidth, scalarType);
1076 auto aieBcastOp = aievec::BroadcastScalarOp::create(
1077 rewriter, bcastOp.getLoc(), vecType, src);
1078 Value newOp = aievec::ConcatOp::create(
1079 rewriter, bcastOp.getLoc(), flatResultType,
1080 SmallVector<Value>({aieBcastOp.getResult(), aieBcastOp.getResult()}));
1081 if (resultType != flatResultType)
1082 newOp = vector::ShapeCastOp::create(rewriter, bcastOp.getLoc(),
1083 resultType, newOp);
1084 rewriter.replaceOp(bcastOp, newOp);
1085 return success();
1086 }
1087
1088 return failure();
1089 }
1090};
1091
1092// This pattern replaces `arith.muli`+`arith.addi` on vectors with
1093// `aievec.mac_elem`. This pattern works for AIE2.
1095 : OpConversionPattern<arith::AddIOp> {
1096 using OpConversionPattern::OpConversionPattern;
1097
1099 unsigned shiftParam = 0)
1100 : OpConversionPattern(context), shiftParam(shiftParam) {}
1101
1102 LogicalResult
1103 matchAndRewrite(arith::AddIOp addOp, OpAdaptor adaptor,
1104 ConversionPatternRewriter &rewriter) const override {
1105 // Verify it's a vector operation
1106 auto resultType = dyn_cast<VectorType>(addOp.getType());
1107 if (!resultType)
1108 return failure();
1109
1110 // Verify it can be replaced by a MAC
1111 auto res =
1112 extractMACOperandsFromAddOperands(adaptor.getLhs(), adaptor.getRhs());
1113 if (!res)
1114 return failure();
1115 auto [lhs, rhs, acc] = *res;
1116
1117 // Verify the vector type is supported by AIE2
1118 unsigned resultElWidth =
1119 resultType.getElementType().getIntOrFloatBitWidth();
1120 unsigned laneSize = getVectorLaneSize(resultType);
1121
1122 if ((laneSize != 32 || resultElWidth != 16) &&
1123 (laneSize != 16 || resultElWidth != 32))
1124 return failure();
1125
1126 Type accType = getVectorOpDestType(cast<VectorType>(acc.getType()),
1127 /*AIE2 =*/true);
1128 auto upsOp = aievec::UPSOp::create(rewriter, addOp.getLoc(), accType, acc,
1129 shiftParam);
1130 auto fmaElemOp = aievec::FMAElemOp::create(
1131 rewriter, addOp.getLoc(), accType, lhs, rhs, upsOp.getResult(),
1132 /*fmsub=*/false);
1133
1134 auto shiftParamOp = arith::ConstantOp::create(
1135 rewriter, addOp.getLoc(), rewriter.getI32IntegerAttr(shiftParam));
1136 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1137 addOp, resultType, fmaElemOp.getResult(), shiftParamOp.getResult());
1138
1139 return success();
1140 }
1141
1142 unsigned shiftParam;
1143};
1144
1145// Lower a single 16-lane bf16 FMA half: UPS -> FMAElem -> SRS.
1146static Value lowerBF16FMAHalf(Value lhs, Value rhs, Value acc,
1147 unsigned shiftParam, Location loc,
1148 ConversionPatternRewriter &rewriter) {
1149 auto f32AccType = VectorType::get({16}, rewriter.getF32Type());
1150 auto upsOp =
1151 aievec::UPSOp::create(rewriter, loc, f32AccType, acc, shiftParam);
1152 auto fmaElemOp = aievec::FMAElemOp::create(rewriter, loc, f32AccType, lhs,
1153 rhs, upsOp.getResult(),
1154 /*fmsub=*/false);
1155 auto shiftParamOp = arith::ConstantOp::create(
1156 rewriter, loc, rewriter.getI32IntegerAttr(shiftParam));
1157 auto srsOp =
1158 aievec::SRSOp::create(rewriter, loc, cast<VectorType>(lhs.getType()),
1159 fmaElemOp.getResult(), shiftParamOp.getResult());
1160 return srsOp.getResult();
1161}
1162
1163// Convert `vector.fma` to `aievec.mac_elem`. Supported operand types:
1164// `vector<16xf32>`, `vector<16xbf16>`, and `vector<32xbf16>` (split into two
1165// 16-lane FMAs). In the case of vectors with
1166// `f32` elemental type, this pattern will try to match `bf16` to `f32`
1167// widening ops in the `lhs` and `rhs` operands, or fail otherwise.
1168// TODO: When sign extensions are not found, a conversion from `f32` to `bf16`
1169// TODO: can be inserted to emulate `f32` fma with `bf16` logic.
1171 : OpConversionPattern<vector::FMAOp> {
1172 using OpConversionPattern::OpConversionPattern;
1173
1175 unsigned shiftParam = 0)
1176 : OpConversionPattern(context), shiftParam(shiftParam) {}
1177
1178 LogicalResult
1179 matchAndRewrite(vector::FMAOp fmaOp, OpAdaptor adaptor,
1180 ConversionPatternRewriter &rewriter) const override {
1181 // Verify the vector type is supported by AIE2
1182 auto resVecTy = cast<VectorType>(fmaOp.getType());
1183 auto resElemTy = resVecTy.getElementType();
1184 unsigned numElems = getVectorLaneSize(resVecTy);
1185
1186 // Only support f32 and bf16 with 16 or 32 lanes.
1187 if ((!resElemTy.isF32() && !resElemTy.isBF16()) ||
1188 (numElems != 16 && numElems != 32))
1189 return rewriter.notifyMatchFailure(
1190 fmaOp, "Unsupported operand types in vector.fma lowering.");
1191
1192 Value lhs = adaptor.getLhs();
1193 Value rhs = adaptor.getRhs();
1194 Value acc = adaptor.getAcc();
1195
1196 if (resElemTy.isF32()) {
1197 if (!holdsBF16(lhs) || !holdsBF16(rhs))
1198 return rewriter.notifyMatchFailure(
1199 fmaOp, "vector.fma operands are f32 and do not hold bf16 values; "
1200 "can't lower to aievec.");
1201 Location loc = fmaOp.getLoc();
1202 lhs = getBF16Source(rewriter, loc, lhs);
1203 rhs = getBF16Source(rewriter, loc, rhs);
1204 if (numElems == 16) {
1205 rewriter.replaceOpWithNewOp<aievec::FMAElemOp>(
1206 fmaOp, acc.getType(), lhs, rhs, acc, /*fmsub=*/false);
1207 return success();
1208 }
1209 // 32 lanes: two 16-lane halves.
1210 auto bf16HalfTy = createVectorType(16, rewriter.getBF16Type());
1211 auto f32HalfTy = createVectorType(16, rewriter.getF32Type());
1212 SmallVector<Value> halves;
1213 for (int idx : {0, 1}) {
1214 Value lhsHalf =
1215 aievec::ExtOp::create(rewriter, loc, bf16HalfTy, lhs, idx);
1216 Value rhsHalf =
1217 aievec::ExtOp::create(rewriter, loc, bf16HalfTy, rhs, idx);
1218 Value accHalf =
1219 aievec::ExtOp::create(rewriter, loc, f32HalfTy, acc, idx);
1220 halves.push_back(aievec::FMAElemOp::create(rewriter, loc, f32HalfTy,
1221 lhsHalf, rhsHalf, accHalf,
1222 /*fmsub=*/false));
1223 }
1224 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(fmaOp, resVecTy, halves);
1225 return success();
1226 }
1227
1228 // Handle vector<32xbf16> by splitting into two vector<16xbf16> FMAs
1229 if (numElems == 32 && resElemTy.isBF16()) {
1230 VectorType halfType = createVectorType(16, resElemTy);
1231 unsigned localShiftParam = shiftParam;
1232
1233 splitWideVectorOp<vector::FMAOp>(
1234 fmaOp, {lhs, rhs, acc}, halfType, resVecTy, rewriter,
1235 [localShiftParam](ArrayRef<std::pair<Value, Value>> halfInputs,
1236 Location loc, ConversionPatternRewriter &rewriter) {
1237 auto [lhsLow, lhsHigh] = halfInputs[0];
1238 auto [rhsLow, rhsHigh] = halfInputs[1];
1239 auto [accLow, accHigh] = halfInputs[2];
1240
1241 Value lowResult = lowerBF16FMAHalf(lhsLow, rhsLow, accLow,
1242 localShiftParam, loc, rewriter);
1243 Value highResult = lowerBF16FMAHalf(lhsHigh, rhsHigh, accHigh,
1244 localShiftParam, loc, rewriter);
1245 return std::make_pair(lowResult, highResult);
1246 });
1247 return success();
1248 }
1249
1250 acc = aievec::UPSOp::create(rewriter, fmaOp.getLoc(),
1251 VectorType::get({16}, rewriter.getF32Type()),
1252 acc, shiftParam);
1253 Value newOp =
1254 aievec::FMAElemOp::create(rewriter, fmaOp.getLoc(), acc.getType(), lhs,
1255 rhs, acc, /*fmsub=*/false);
1256 auto shiftParamOp = arith::ConstantOp::create(
1257 rewriter, fmaOp.getLoc(), rewriter.getI32IntegerAttr(shiftParam));
1258 newOp = aievec::SRSOp::create(rewriter, fmaOp.getLoc(), resVecTy, newOp,
1259 shiftParamOp);
1260
1261 rewriter.replaceOp(fmaOp, newOp);
1262
1263 return success();
1264 }
1265
1266 unsigned shiftParam;
1267};
1268
1269// AIE2P multiplies 32 bf16 lanes into an f32 accumulator in one op. Lower a
1270// 32-lane f32 `vector.fma` with bf16 inputs to it.
1272 : OpConversionPattern<vector::FMAOp> {
1273 using OpConversionPattern::OpConversionPattern;
1274
1275 LogicalResult
1276 matchAndRewrite(vector::FMAOp fmaOp, OpAdaptor adaptor,
1277 ConversionPatternRewriter &rewriter) const override {
1278 auto resVecTy = cast<VectorType>(fmaOp.getType());
1279 if (!resVecTy.getElementType().isF32() || resVecTy.getRank() != 1 ||
1280 resVecTy.getNumElements() != 32 || !holdsBF16(adaptor.getLhs()) ||
1281 !holdsBF16(adaptor.getRhs()))
1282 return failure();
1283 Location loc = fmaOp.getLoc();
1284 Value lhs = getBF16Source(rewriter, loc, adaptor.getLhs());
1285 Value rhs = getBF16Source(rewriter, loc, adaptor.getRhs());
1286 rewriter.replaceOpWithNewOp<aievec::FMAElemOp>(
1287 fmaOp, resVecTy, lhs, rhs, adaptor.getAcc(), /*fmsub=*/false);
1288 return success();
1289 }
1290};
1291
1292// The multiply-only form of the pattern above, for 32 or 64 lanes.
1294 : OpConversionPattern<arith::MulFOp> {
1295 using OpConversionPattern::OpConversionPattern;
1296
1297 LogicalResult
1298 matchAndRewrite(arith::MulFOp mulOp, OpAdaptor adaptor,
1299 ConversionPatternRewriter &rewriter) const override {
1300 auto resVecTy = dyn_cast<VectorType>(mulOp.getType());
1301 if (!resVecTy || !resVecTy.getElementType().isF32() ||
1302 resVecTy.getRank() != 1 ||
1303 (resVecTy.getNumElements() != 32 && resVecTy.getNumElements() != 64) ||
1304 !holdsBF16(adaptor.getLhs()) || !holdsBF16(adaptor.getRhs()))
1305 return failure();
1306 Location loc = mulOp.getLoc();
1307 Value lhs = getBF16Source(rewriter, loc, adaptor.getLhs());
1308 Value rhs = getBF16Source(rewriter, loc, adaptor.getRhs());
1309 auto mulElemOp =
1310 aievec::MulElemOp::create(rewriter, loc, resVecTy, lhs, rhs);
1311 rewriter.replaceOpWithNewOp<aievec::CastOp>(mulOp, resVecTy, mulElemOp,
1312 /*isResAcc=*/false);
1313 return success();
1314 }
1315};
1316
1317// This pattern fuses `arith.mulf` + `arith.addf` on bf16 vectors into
1318// `aievec.mac_elem` (float FMA). This pattern works for AIE2.
1320 : OpConversionPattern<arith::AddFOp> {
1321 using OpConversionPattern::OpConversionPattern;
1322
1324 unsigned shiftParam = 0)
1325 : OpConversionPattern(context), shiftParam(shiftParam) {}
1326
1327 LogicalResult
1328 matchAndRewrite(arith::AddFOp addOp, OpAdaptor adaptor,
1329 ConversionPatternRewriter &rewriter) const override {
1330 // Verify it's a vector operation
1331 auto resultType = dyn_cast<VectorType>(addOp.getType());
1332 if (!resultType)
1333 return failure();
1334
1335 // Only handle bf16 element type
1336 auto elemType = resultType.getElementType();
1337 if (!elemType.isBF16())
1338 return failure();
1339
1340 // Verify it can be replaced by an FMA
1341 auto res =
1342 extractFMACOperandsFromAddOperands(adaptor.getLhs(), adaptor.getRhs());
1343 if (!res)
1344 return failure();
1345 auto [lhs, rhs, acc] = *res;
1346
1347 unsigned laneSize = getVectorLaneSize(resultType);
1348
1349 // Handle vector<32xbf16> by splitting into two vector<16xbf16> FMAs
1350 if (laneSize == 32) {
1351 VectorType halfType = createVectorType(16, elemType);
1352 unsigned localShiftParam = shiftParam;
1353
1354 splitWideVectorOp<arith::AddFOp>(
1355 addOp, {lhs, rhs, acc}, halfType, resultType, rewriter,
1356 [localShiftParam](ArrayRef<std::pair<Value, Value>> halfInputs,
1357 Location loc, ConversionPatternRewriter &rewriter) {
1358 auto [lhsLow, lhsHigh] = halfInputs[0];
1359 auto [rhsLow, rhsHigh] = halfInputs[1];
1360 auto [accLow, accHigh] = halfInputs[2];
1361
1362 Value lowResult = lowerBF16FMAHalf(lhsLow, rhsLow, accLow,
1363 localShiftParam, loc, rewriter);
1364 Value highResult = lowerBF16FMAHalf(lhsHigh, rhsHigh, accHigh,
1365 localShiftParam, loc, rewriter);
1366 return std::make_pair(lowResult, highResult);
1367 });
1368 return success();
1369 }
1370
1371 // Handle vector<16xbf16>
1372 if (laneSize != 16)
1373 return failure();
1374
1375 auto f32AccType = VectorType::get({16}, rewriter.getF32Type());
1376 auto upsOp = aievec::UPSOp::create(rewriter, addOp.getLoc(), f32AccType,
1377 acc, shiftParam);
1378 auto fmaElemOp = aievec::FMAElemOp::create(
1379 rewriter, addOp.getLoc(), f32AccType, lhs, rhs, upsOp.getResult(),
1380 /*fmsub=*/false);
1381
1382 auto shiftParamOp = arith::ConstantOp::create(
1383 rewriter, addOp.getLoc(), rewriter.getI32IntegerAttr(shiftParam));
1384 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1385 addOp, resultType, fmaElemOp.getResult(), shiftParamOp.getResult());
1386
1387 return success();
1388 }
1389
1390 unsigned shiftParam;
1391};
1392
1393// This pattern replaces `arith.mulf` on vectors with
1394// `aievec.mul_elem`. This pattern works for AIE2.
1396 : OpConversionPattern<arith::MulFOp> {
1397 using OpConversionPattern::OpConversionPattern;
1398
1400 unsigned shiftParam = 0)
1401 : OpConversionPattern(context), shiftParam(shiftParam) {}
1402
1403 LogicalResult
1404 matchAndRewrite(arith::MulFOp mulOp, OpAdaptor adaptor,
1405 ConversionPatternRewriter &rewriter) const override {
1406 // Verify it's a vector operation
1407 auto resultType = dyn_cast<VectorType>(mulOp.getType());
1408 if (!resultType)
1409 return failure();
1410
1411 // Skip standalone mul conversion when this MulFOp feeds into an AddFOp as
1412 // its sole user. ConvertMulAddFToAIEVecFMAElemOpPattern will fuse the
1413 // multiply and add into a single aievec.mac_elem.
1414 // Only defer to FMA for bf16 where the FMA pattern is registered.
1415 auto isAddOp = [&](Operation *op) { return isa<arith::AddFOp>(op); };
1416 if (resultType.getElementType().isBF16() && mulOp->hasOneUse() &&
1417 llvm::any_of(mulOp->getUsers(), isAddOp))
1418 return failure();
1419
1420 unsigned resultElWidth =
1421 resultType.getElementType().getIntOrFloatBitWidth();
1422
1423 unsigned laneSize = getVectorLaneSize(resultType);
1424
1425 // f32 product of bf16 inputs, in 16-lane pieces.
1426 if (resultType.getElementType().isF32() &&
1427 (laneSize == 16 || laneSize == 32) && holdsBF16(adaptor.getLhs()) &&
1428 holdsBF16(adaptor.getRhs())) {
1429 Location loc = mulOp.getLoc();
1430 Value lhs = getBF16Source(rewriter, loc, adaptor.getLhs());
1431 Value rhs = getBF16Source(rewriter, loc, adaptor.getRhs());
1432 auto bf16HalfTy = createVectorType(16, rewriter.getBF16Type());
1433 auto f32HalfTy = createVectorType(16, rewriter.getF32Type());
1434 auto mul16 = [&](Value l, Value r) -> Value {
1435 auto mulElemOp =
1436 aievec::MulElemOp::create(rewriter, loc, f32HalfTy, l, r);
1437 return aievec::CastOp::create(rewriter, loc, f32HalfTy, mulElemOp,
1438 /*isResAcc=*/false);
1439 };
1440 if (laneSize == 16) {
1441 rewriter.replaceOp(mulOp, mul16(lhs, rhs));
1442 return success();
1443 }
1444 SmallVector<Value> halves;
1445 for (int idx : {0, 1})
1446 halves.push_back(
1447 mul16(aievec::ExtOp::create(rewriter, loc, bf16HalfTy, lhs, idx),
1448 aievec::ExtOp::create(rewriter, loc, bf16HalfTy, rhs, idx)));
1449 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(mulOp, resultType, halves);
1450 return success();
1451 }
1452
1453 // Handle vector<32xbf16> by splitting into two vector<16xbf16> operations
1454 if (laneSize == 32 && resultElWidth == 16) {
1455 VectorType halfType = createVectorType(16, resultType.getElementType());
1456 unsigned localShiftParam = shiftParam;
1457
1458 splitWideBinaryVectorOp<arith::MulFOp>(
1459 mulOp, adaptor.getLhs(), adaptor.getRhs(), halfType, resultType,
1460 rewriter,
1461 [localShiftParam](Value lhsHalf, Value rhsHalf, Location loc,
1462 ConversionPatternRewriter &rewriter) -> Value {
1463 Type accType = getVectorOpDestType(
1464 cast<VectorType>(lhsHalf.getType()), /*AIE2 =*/true);
1465 auto mulElemOp = aievec::MulElemOp::create(rewriter, loc, accType,
1466 lhsHalf, rhsHalf);
1467 auto shiftParamOp = arith::ConstantOp::create(
1468 rewriter, loc, rewriter.getI32IntegerAttr(localShiftParam));
1469 auto srsOp = aievec::SRSOp::create(
1470 rewriter, loc, cast<VectorType>(lhsHalf.getType()),
1471 mulElemOp.getResult(), shiftParamOp.getResult());
1472 return srsOp.getResult();
1473 });
1474 return success();
1475 }
1476
1477 // bfloat16 and float type (laneSize == 16)
1478 if (laneSize != 16 || (resultElWidth != 16 && resultElWidth != 32))
1479 return failure();
1480
1481 // Decide the accType for aievec.mul_elem based on mulOp's lhs & rhs
1482 auto lval = adaptor.getLhs();
1483 auto rval = adaptor.getRhs();
1484 lval = getSourceOfWideningOp(lval).value_or(lval);
1485 rval = getSourceOfWideningOp(rval).value_or(rval);
1486 auto lSrcType = cast<VectorType>(lval.getType());
1487 auto rSrcType = cast<VectorType>(rval.getType());
1488 unsigned lBitWidth = lSrcType.getElementType().getIntOrFloatBitWidth();
1489 unsigned rBitWidth = rSrcType.getElementType().getIntOrFloatBitWidth();
1490 Type accType = getVectorOpDestType(lSrcType, /*AIE2 =*/true);
1491 if (rBitWidth > lBitWidth) {
1492 accType = getVectorOpDestType(rSrcType, /*AIE2 =*/true);
1493 }
1494 // Only support the same lhs/rhs type at the moment
1495 if (lSrcType != rSrcType) {
1496 return failure();
1497 }
1498
1499 // Prepare lhr/rhs for the aievec.mul_elem op
1500 unsigned bitWidth = (rBitWidth > lBitWidth) ? rBitWidth : lBitWidth;
1501 Type srcElemType = (rBitWidth > lBitWidth) ? rSrcType.getElementType()
1502 : lSrcType.getElementType();
1503 unsigned numLanes = 0;
1504 // float16/32 and int32 both land on 16 lanes -- genuinely distinct
1505 // source types happen to share a lane count, not a copy-paste clone.
1506 if (isa<FloatType>(srcElemType) &&
1507 (bitWidth == 16 || bitWidth == 32)) { // NOLINT(bugprone-branch-clone)
1508 numLanes = 16;
1509 } else if (isa<IntegerType>(srcElemType) &&
1510 (bitWidth == 8 || bitWidth == 16)) {
1511 numLanes = 32;
1512 } else if (isa<IntegerType>(srcElemType) && (bitWidth == 32)) {
1513 numLanes = 16;
1514 } else {
1515 return failure();
1516 }
1517 VectorType targetInputType = createVectorType(numLanes, srcElemType);
1518 if (targetInputType != lSrcType) {
1519 auto converted = convertValueToTargetTypeAIE2(rewriter, mulOp.getLoc(),
1520 lval, targetInputType);
1521 if (!converted)
1522 return failure();
1523 lval = *converted;
1524 }
1525 if (targetInputType != rSrcType) {
1526 auto converted = convertValueToTargetTypeAIE2(rewriter, mulOp.getLoc(),
1527 rval, targetInputType);
1528 if (!converted)
1529 return failure();
1530 rval = *converted;
1531 }
1532 if (!lval || !rval)
1533 return failure();
1534
1535 // Create an aievec.mul_elem op
1536 auto mulElemOp = aievec::MulElemOp::create(rewriter, mulOp.getLoc(),
1537 accType, lval, rval);
1538
1539 // Create an aievec.cast or an aievec.srs op
1540 auto mulElemResultType = mulElemOp.getType();
1541 auto mulElemResultElWidth =
1542 mulElemResultType.getElementType().getIntOrFloatBitWidth();
1543
1544 if (mulElemResultElWidth == resultElWidth) {
1545 rewriter.replaceOpWithNewOp<aievec::CastOp>(
1546 mulOp, resultType, mulElemOp.getResult(), /*isResAcc*/ false);
1547 } else if (mulElemResultElWidth > resultElWidth) {
1548 auto shiftParamOp = arith::ConstantOp::create(
1549 rewriter, mulOp.getLoc(), rewriter.getI32IntegerAttr(shiftParam));
1550 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1551 mulOp, resultType, mulElemOp.getResult(), shiftParamOp.getResult());
1552 } else {
1553 return failure();
1554 }
1555
1556 return success();
1557 }
1558
1559 unsigned shiftParam;
1560};
1561
1562// This pattern replaces `arith.muli` on vectors with
1563// `aievec.mul_elem`. This pattern works for AIE2.
1565 : OpConversionPattern<arith::MulIOp> {
1566 using OpConversionPattern::OpConversionPattern;
1567
1569 unsigned shiftParam = 0)
1570 : OpConversionPattern(context), shiftParam(shiftParam) {}
1571
1572 LogicalResult
1573 matchAndRewrite(arith::MulIOp mulOp, OpAdaptor adaptor,
1574 ConversionPatternRewriter &rewriter) const override {
1575 // Verify it's a vector operation
1576 auto resultType = dyn_cast<VectorType>(mulOp.getType());
1577 if (!resultType)
1578 return failure();
1579
1580 // FIXME: Verify it is not a part of MAC
1581 auto isAddOp = [&](Operation *op) { return isa<arith::AddIOp>(op); };
1582 if (mulOp->hasOneUse() && llvm::any_of(mulOp->getUsers(), isAddOp))
1583 return failure();
1584
1585 // Verify the vector type is supported by AIE2
1586 unsigned resultElWidth =
1587 resultType.getElementType().getIntOrFloatBitWidth();
1588 unsigned laneSize = getVectorLaneSize(resultType);
1589
1590 if ((laneSize != 32 || (resultElWidth != 16 && resultElWidth != 8)) &&
1591 ((laneSize != 16 && laneSize != 32) || resultElWidth != 32))
1592 return failure();
1593
1594 // Decide the accType for aievec.mul_elem based on mulOp's lhs & rhs
1595 auto lval = adaptor.getLhs();
1596 auto rval = adaptor.getRhs();
1597
1598 lval = getSourceOfWideningOp(lval).value_or(lval);
1599 rval = getSourceOfWideningOp(rval).value_or(rval);
1600
1601 auto lSrcType = cast<VectorType>(lval.getType());
1602 auto rSrcType = cast<VectorType>(rval.getType());
1603 unsigned lBitWidth = lSrcType.getElementType().getIntOrFloatBitWidth();
1604 unsigned rBitWidth = rSrcType.getElementType().getIntOrFloatBitWidth();
1605 Type accType = getVectorOpDestType(lSrcType, /*AIE2 =*/true);
1606 if (rBitWidth > lBitWidth) {
1607 accType = getVectorOpDestType(rSrcType, /*AIE2 =*/true);
1608 }
1609
1610 // Prepare lhr/rhs for the aievec.mul_elem op
1611 unsigned bitWidth = (rBitWidth > lBitWidth) ? rBitWidth : lBitWidth;
1612 Type srcElemType = (rBitWidth > lBitWidth) ? rSrcType.getElementType()
1613 : lSrcType.getElementType();
1614 unsigned numLanes = 0;
1615 // float16/32 and int32 both land on 16 lanes -- genuinely distinct
1616 // source types happen to share a lane count, not a copy-paste clone.
1617 if (isa<FloatType>(srcElemType) &&
1618 (bitWidth == 16 || bitWidth == 32)) { // NOLINT(bugprone-branch-clone)
1619 numLanes = 16;
1620 } else if (isa<IntegerType>(srcElemType) &&
1621 (bitWidth == 8 || bitWidth == 16)) {
1622 numLanes = 32;
1623 } else if (isa<IntegerType>(srcElemType) && (bitWidth == 32)) {
1624 numLanes = 16;
1625 } else {
1626 return failure();
1627 }
1628 VectorType targetInputType = createVectorType(numLanes, srcElemType);
1629 if (targetInputType != lSrcType) {
1630 auto converted = convertValueToTargetTypeAIE2(rewriter, mulOp.getLoc(),
1631 lval, targetInputType);
1632 if (!converted)
1633 return failure();
1634 lval = *converted;
1635 }
1636 if (targetInputType != rSrcType) {
1637 auto converted = convertValueToTargetTypeAIE2(rewriter, mulOp.getLoc(),
1638 rval, targetInputType);
1639 if (!converted)
1640 return failure();
1641 rval = *converted;
1642 }
1643 if (!lval || !rval)
1644 return failure();
1645
1646 // Create an aievec.mul_elem op
1647 auto mulElemOp = aievec::MulElemOp::create(rewriter, mulOp.getLoc(),
1648 accType, lval, rval);
1649
1650 // Create an aievec.cast or an aievec.srs op
1651 auto mulElemResultType = mulElemOp.getType();
1652 auto mulElemResultElWidth =
1653 mulElemResultType.getElementType().getIntOrFloatBitWidth();
1654
1655 if (mulElemResultElWidth == resultElWidth) {
1656 rewriter.replaceOpWithNewOp<aievec::CastOp>(
1657 mulOp, resultType, mulElemOp.getResult(), /*isResAcc*/ false);
1658 } else if (mulElemResultElWidth > resultElWidth) {
1659 auto shiftParamOp = arith::ConstantOp::create(
1660 rewriter, mulOp.getLoc(), rewriter.getI32IntegerAttr(shiftParam));
1661 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1662 mulOp, resultType, mulElemOp.getResult(), shiftParamOp.getResult());
1663 } else {
1664 return failure();
1665 }
1666
1667 return success();
1668 }
1669
1670 unsigned shiftParam;
1671};
1672
1673// This pattern folds an extract + broadcast feeding into an
1674// `aievec::aie1::FMAOp` into the op, using the shuffle attributes.
1675struct FoldSplatToFMAOp : OpConversionPattern<aievec::aie1::FMAOp> {
1676 using OpConversionPattern::OpConversionPattern;
1677
1678 LogicalResult
1679 matchAndRewrite(aievec::aie1::FMAOp fmaOp, OpAdaptor adaptor,
1680 ConversionPatternRewriter &rewriter) const override {
1681 auto concatOp =
1682 dyn_cast<aievec::ConcatOp>(adaptor.getLhs().getDefiningOp());
1683 if (!concatOp)
1684 return failure();
1685 vector::BroadcastOp bcastOp = nullptr;
1686 auto *concatDefOp = concatOp.getSources()[0].getDefiningOp();
1687 if (concatDefOp)
1688 bcastOp = dyn_cast<vector::BroadcastOp>(concatDefOp);
1689 Value lhs = adaptor.getRhs();
1690 if (!bcastOp) {
1691 bcastOp = dyn_cast<vector::BroadcastOp>(adaptor.getRhs().getDefiningOp());
1692 if (!bcastOp)
1693 return failure();
1694 lhs = concatOp.getSources()[0];
1695 }
1696 auto extOp =
1697 dyn_cast<vector::ExtractOp>(bcastOp.getSource().getDefiningOp());
1698 if (!extOp)
1699 return failure();
1700
1701 auto rhs = extOp.getSource();
1702 auto concatVecType = cast<VectorType>(concatOp.getResult().getType());
1703 auto zvec =
1704 arith::ConstantOp::create(rewriter, concatOp.getLoc(), lhs.getType(),
1705 rewriter.getZeroAttr(lhs.getType()));
1706 auto lhsX2 =
1707 aievec::ConcatOp::create(rewriter, concatOp.getLoc(), concatVecType,
1708 SmallVector<Value, 2>({lhs, zvec}))
1709 .getResult();
1710 // XXX: We assume a 1D vector
1711 auto pos = extOp.getStaticPosition();
1712 int64_t zstart = pos[0];
1713 auto fmaOpAttr = buildFMAOpSplatAttrForElemTy(fmaOp, zstart);
1714 aievec::aie1::FMAOp::Properties fmaProps;
1715 if (failed(aievec::aie1::FMAOp::setPropertiesFromAttr(
1716 fmaProps, rewriter.getDictionaryAttr(fmaOpAttr),
1717 [&]() { return fmaOp.emitError(); })))
1718 return failure();
1719 rewriter.replaceOpWithNewOp<aievec::aie1::FMAOp>(
1720 fmaOp, TypeRange({fmaOp.getResult().getType()}),
1721 ValueRange({lhsX2, rhs, adaptor.getAcc()}), fmaProps);
1722
1723 return success();
1724 }
1725};
1726
1728 : OpConversionPattern<aievec::aie1::AddOp> {
1729 using OpConversionPattern::OpConversionPattern;
1730
1731 LogicalResult
1732 matchAndRewrite(aievec::aie1::AddOp addOp, OpAdaptor adaptor,
1733 ConversionPatternRewriter &rewriter) const override {
1734 auto vecType = cast<VectorType>(addOp.getType());
1735
1736 auto res =
1737 extractMACOperandsFromAddOperands(adaptor.getLhs(), adaptor.getRhs());
1738 if (!res)
1739 return failure();
1740 auto [lhs, rhs, acc] = *res;
1741
1742 SmallVector<int64_t, 4> concatVecShape(vecType.getShape().begin(),
1743 vecType.getShape().end());
1744 concatVecShape[vecType.getRank() - 1] *= 2;
1745 auto concatVecType =
1746 VectorType::get(concatVecShape, vecType.getElementType());
1747 Type accType = getVectorOpDestType(cast<VectorType>(acc.getType()),
1748 /*AIE2 =*/false);
1749 auto lhsX2 =
1750 aievec::ConcatOp::create(rewriter, addOp.getLoc(), concatVecType,
1751 SmallVector<Value, 2>(2, lhs))
1752 .getResult();
1753 auto upsOp = aievec::UPSOp::create(rewriter, addOp.getLoc(), accType, acc);
1754 auto fmaOp = aievec::aie1::FMAOp::create(
1755 rewriter, addOp.getLoc(), accType, lhsX2, rhs, upsOp.getResult(),
1756 /*xstart=*/"", /*xoffsets=*/"", /*xoffsets_hi=*/"", /*xstep=*/"",
1757 /*xsquare=*/"", /*zstart=*/"", /*zoffsets=*/"", /*zoffsets_hi=*/"",
1758 /*zstep=*/"", /*zsquare=*/"", /*fmsub=*/false);
1759 auto shiftParamOp = arith::ConstantOp::create(
1760 rewriter, addOp.getLoc(), rewriter.getI32IntegerAttr(0));
1761 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1762 addOp, vecType, fmaOp.getResult(), shiftParamOp.getResult());
1763 return success();
1764 }
1765};
1766
1767// This pattern replaces `vector.transfer_read` with `aievec.upd`. Right now,
1768// it performs a naïve direct translation. This needs to be expanded to
1769// support more complex scenarios.
1771 : OpConversionPattern<vector::TransferReadOp> {
1772 using OpConversionPattern::OpConversionPattern;
1773
1774 LowerVectorTransferReadToAIEUPD(MLIRContext *context, int64_t minVectorSize,
1775 int64_t maxVectorSize, int64_t alignment,
1776 int64_t maxLoadSize)
1777 : OpConversionPattern(context), minVectorSize(minVectorSize),
1780
1781 LogicalResult
1782 matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor,
1783 ConversionPatternRewriter &rewriter) const override {
1784 // Masked loads
1785 if (readOp.getMask())
1786 return readOp.emitError() << "AIE doesn't support masked loads.";
1787
1788 // Non-contiguous loads
1789 AffineMap map = readOp.getPermutationMap();
1790 if (!map.isMinorIdentity())
1791 return failure();
1792
1793 // Splats
1794 if (map.isConstant())
1795 return failure();
1796
1797 // Misaligned accesses
1798 auto vType = readOp.getVectorType();
1800 .value_or(0) != 0)
1801 return failure();
1802
1803 // Invalid vector size.
1804 // We can handle cases where the vector size is:
1805 // 1) the minimum vector size
1806 // 2) a square multiple of the alignment size and up to the maximum
1807 // vector size.
1808 int64_t vSize = vType.getNumElements() * vType.getElementTypeBitWidth();
1809 if (vSize > maxVectorSize ||
1810 (vSize % vectorAlignment && vSize != minVectorSize))
1811 return failure();
1812 // We can deal with linked update instructions when the vector size is
1813 // exactly twice the load size. This could change in future architectures
1814 if (vSize > maxLoadSize && vSize != maxLoadSize * 2)
1815 return failure();
1816 int64_t multiplicity = vSize / vectorAlignment;
1817 if ((vSize > minVectorSize) && std::bitset<8>(multiplicity).count() != 1)
1818 return failure();
1819
1820 auto updOp = xilinx::aievec::UPDOp::create(
1821 rewriter, readOp.getLoc(), vType, adaptor.getBase(),
1822 adaptor.getIndices(), 0, 0, TypedValue<VectorType>(nullptr));
1823 if (vSize > maxLoadSize) {
1824 updOp = xilinx::aievec::UPDOp::create(
1825 rewriter, readOp.getLoc(), vType, adaptor.getBase(),
1826 adaptor.getIndices(), maxLoadSize, 1, updOp.getResult());
1827 }
1828 rewriter.replaceOp(readOp, updOp.getResult());
1829
1830 return success();
1831 }
1832
1834};
1835
1836// XXX: Notice that this template doesn't verify that the vector element type
1837// XXX: is supported by the target architecture.
1838template <typename SrcOpTy, typename DstOpTy>
1839struct OneToOneVectorOpToAIEVecOpPattern : OpConversionPattern<SrcOpTy> {
1840 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
1841 using OpAdaptor = typename SrcOpTy::Adaptor;
1842
1843 LogicalResult
1844 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
1845 ConversionPatternRewriter &rewriter) const override {
1846 rewriter.replaceOpWithNewOp<DstOpTy>(
1847 srcOp, srcOp.getResult().getType(), adaptor.getLhs(), adaptor.getRhs(),
1848 /*xstart=*/"", /*xoffsets=*/"", /*xoffsets_hi=*/"", /*xsquare=*/"",
1849 /*zstart=*/"", /*zoffsets=*/"", /*zoffsets_hi=*/"", /*zsquare=*/"");
1850 return success();
1851 }
1852};
1853
1854struct LowerVectorAddIOpToAIEVecAddOp : OpConversionPattern<arith::AddIOp> {
1855 using OpConversionPattern::OpConversionPattern;
1856
1857 LogicalResult
1858 matchAndRewrite(arith::AddIOp addOp, OpAdaptor adaptor,
1859 ConversionPatternRewriter &rewriter) const override {
1860 auto resType = addOp.getType();
1861 if (!isa<VectorType>(resType))
1862 return failure();
1863
1864 auto lhs = adaptor.getLhs();
1865 auto rhs = adaptor.getRhs();
1866 auto *lhsDefOp = lhs.getDefiningOp();
1867 auto *rhsDefOp = rhs.getDefiningOp();
1868 if ((isa_and_nonnull<arith::MulIOp>(lhsDefOp)) ||
1869 (isa_and_nonnull<arith::MulIOp>(rhsDefOp)))
1870 return failure();
1871
1872 rewriter.replaceOpWithNewOp<aievec::aie1::AddOp>(
1873 addOp, resType, lhs, rhs,
1874 /*xstart=*/"", /*xoffsets=*/"", /*xoffsets_hi=*/"", /*xsquare=*/"",
1875 /*zstart=*/"", /*zoffsets=*/"", /*zoffsets_hi=*/"", /*zsquare=*/"");
1876 return success();
1877 }
1878};
1879
1888
1889struct LowerVectorMulIOpToAIEVecMulOp : OpConversionPattern<arith::MulIOp> {
1890 using OpConversionPattern::OpConversionPattern;
1891 LogicalResult
1892 matchAndRewrite(arith::MulIOp mulOp, OpAdaptor adaptor,
1893 ConversionPatternRewriter &rewriter) const override {
1894 auto resTy = dyn_cast<VectorType>(mulOp.getType());
1895 if (!resTy)
1896 return failure();
1897 auto accTy = getVectorOpDestType(resTy, /*AIE2 =*/false);
1898 auto newMulOp = aievec::aie1::MulOp::create(
1899 rewriter, mulOp.getLoc(), accTy, adaptor.getLhs(), adaptor.getRhs());
1900 auto shiftParamOp = arith::ConstantOp::create(
1901 rewriter, mulOp.getLoc(), rewriter.getI32IntegerAttr(0));
1902 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
1903 mulOp, resTy, newMulOp.getResult(), shiftParamOp.getResult());
1904 return success();
1905 }
1906};
1907
1908template <typename SrcOpTy, typename DstOpTy>
1910 : OpConversionPattern<SrcOpTy> {
1911 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
1912 using OpAdaptor = typename SrcOpTy::Adaptor;
1913
1914 LogicalResult
1915 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
1916 ConversionPatternRewriter &rewriter) const override {
1917 VectorType resultType = dyn_cast<VectorType>(srcOp.getType());
1918 if (!resultType)
1919 return failure();
1920
1921 // A set recording the vector lane size and element width we are supporting
1922 // for AIE2.
1923 llvm::SmallSet<std::pair<unsigned, signed>, 16> laneSizeElWidthPairSet;
1924 laneSizeElWidthPairSet.insert({64, 8});
1925 laneSizeElWidthPairSet.insert({32, 16});
1926 laneSizeElWidthPairSet.insert({16, 32});
1927 laneSizeElWidthPairSet.insert({32, 32});
1928
1929 auto lhs = adaptor.getLhs();
1930 auto rhs = adaptor.getRhs();
1931 auto lhsDefOp = lhs.getDefiningOp();
1932 auto rhsDefOp = rhs.getDefiningOp();
1933 // Check if this is part of a MAC/FMA pattern (mul + add).
1934 // We only skip conversion if BOTH operands could potentially be part of an
1935 // FMA pattern (i.e., neither is a constant). Constants can never be the
1936 // multiply result in an FMA, so we should allow conversion in those cases.
1937 bool lhsIsMul = lhsDefOp && (isa<arith::MulIOp>(lhsDefOp) ||
1938 isa<arith::MulFOp>(lhsDefOp));
1939 bool rhsIsMul = rhsDefOp && (isa<arith::MulIOp>(rhsDefOp) ||
1940 isa<arith::MulFOp>(rhsDefOp));
1941 bool lhsIsConst = isa_and_nonnull<arith::ConstantOp>(lhsDefOp);
1942 bool rhsIsConst = isa_and_nonnull<arith::ConstantOp>(rhsDefOp);
1943
1944 // Defer to FMA/MAC patterns when a multiply feeds into add, UNLESS the
1945 // element type is f32 (where no FMA pattern exists). For bf16 and integer
1946 // types, FMA/MAC patterns handle the fusion.
1947 if (!resultType.getElementType().isF32() &&
1948 ((lhsIsMul && !rhsIsConst) || (rhsIsMul && !lhsIsConst)))
1949 return failure();
1950
1951 Type scalarType = resultType.getElementType();
1952 unsigned resultElWidth = scalarType.getIntOrFloatBitWidth();
1953 unsigned laneSize = getVectorLaneSize(resultType);
1954
1955 // Integer cases
1956 if (isa<IntegerType>(scalarType)) {
1957 if (!laneSizeElWidthPairSet.count(
1958 std::make_pair(laneSize, resultElWidth)))
1959 return failure();
1960
1961 // If the ops are defined without extension ops and with supported data
1962 // type, the arith::AddI or arith::SubI can be directly replaced with
1963 // aievec::AddElem or aievec::SubElem.
1964 if (!lhsDefOp && !rhsDefOp) {
1965 if (laneSize * resultElWidth == 512) {
1966 rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcOp.getType(), lhs,
1967 rhs);
1968 return success();
1969 }
1970 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs, resultType,
1971 srcOp);
1972 }
1973
1974 // If element width is 32, we need to consider sign extension cases
1975 if (resultElWidth == 32) {
1976 auto lhsExt = getSourceOfWideningOp(lhs).value_or(nullptr);
1977 auto rhsExt = getSourceOfWideningOp(rhs).value_or(nullptr);
1978
1979 if (!lhsExt && !rhsExt) {
1980 if (laneSize * resultElWidth == 512) {
1981 rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcOp.getType(), lhs,
1982 rhs);
1983 return success();
1984 }
1985 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs,
1986 resultType, srcOp);
1987 }
1988
1989 if (lhsExt && rhsExt) {
1990 auto lval = lhsExt;
1991 auto rval = rhsExt;
1992 VectorType lSrcType = cast<VectorType>(lval.getType());
1993
1994 Type accType = getVectorOpDestType(lSrcType, /*AIE2 =*/true);
1995 auto lUpsOp =
1996 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, lval);
1997 auto rUpsOp =
1998 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, rval);
1999 auto elemOp = DstOpTy::create(
2000 rewriter, srcOp.getLoc(), lUpsOp->getResult(0).getType(),
2001 lUpsOp->getResult(0), rUpsOp->getResult(0));
2002 rewriter.replaceOpWithNewOp<aievec::CastOp>(
2003 srcOp, srcOp.getType(), elemOp.getResult(), /*isResAcc*/ false);
2004 return success();
2005 }
2006
2007 if (!lhsExt || !rhsExt) {
2008 auto lval = lhsExt ? lhsExt : lhs;
2009 auto rval = rhsExt ? rhsExt : rhs;
2010 auto extVal = lhsExt ? lval : rval;
2011 VectorType vType = cast<VectorType>(extVal.getType());
2012 unsigned bitWidth = vType.getElementType().getIntOrFloatBitWidth();
2013
2014 if (bitWidth != 8 && bitWidth != 16) {
2015 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs,
2016 resultType, srcOp);
2017 }
2018
2019 if (bitWidth * laneSize != 256) {
2020 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs,
2021 resultType, srcOp);
2022 }
2023
2024 Type accType = nullptr;
2025
2026 if (bitWidth == 8) {
2027 accType = getVectorOpDestType(vType, /*AIE2 =*/true);
2028 Value valToUps = lhsExt ? lval : rval;
2029 Value valToCast = lhsExt ? rval : lval;
2030 auto upsOp = aievec::UPSOp::create(rewriter, srcOp.getLoc(),
2031 accType, valToUps);
2032 auto castOp =
2033 aievec::CastOp::create(rewriter, srcOp.getLoc(), resultType,
2034 valToCast, /*isResAcc*/ true);
2035 Value lhsToElemOp =
2036 lhsExt ? upsOp->getResult(0) : castOp->getResult(0);
2037 Value rhsToElemOp =
2038 lhsExt ? castOp->getResult(0) : upsOp->getResult(0);
2039 auto elemOp = DstOpTy::create(rewriter, srcOp.getLoc(),
2040 upsOp->getResult(0).getType(),
2041 lhsToElemOp, rhsToElemOp);
2042 rewriter.replaceOpWithNewOp<aievec::CastOp>(
2043 srcOp, srcOp.getType(), elemOp.getResult(), /*isResAcc*/ false);
2044 return success();
2045 }
2046
2047 if (bitWidth == 16) {
2048 accType = getVectorOpDestType(resultType, /*AIE2 =*/true);
2049 auto lUpsOp =
2050 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, lval);
2051 auto rUpsOp =
2052 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, rval);
2053
2054 auto elemOp = DstOpTy::create(
2055 rewriter, srcOp.getLoc(), lUpsOp->getResult(0).getType(),
2056 lUpsOp->getResult(0), rUpsOp->getResult(0));
2057
2058 auto shiftParamOp = arith::ConstantOp::create(
2059 rewriter, srcOp.getLoc(), rewriter.getI32IntegerAttr(0));
2060 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
2061 srcOp, srcOp.getType(), elemOp.getResult(),
2062 shiftParamOp.getResult());
2063 return success();
2064 }
2065 }
2066 } else {
2067 rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcOp.getType(), lhs, rhs);
2068 return success();
2069 }
2070 }
2071 // Float types
2072 else {
2073 // f32 at 64 lanes, the width of the accumulator, as one op.
2074 if (laneSize == 64 && scalarType.isF32())
2075 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs, resultType,
2076 srcOp);
2077 if (laneSize != 16 && laneSize != 32)
2078 return failure();
2079
2080 // v32f32: split into two v16f32 ops
2081 if (laneSize == 32 && resultElWidth == 32) {
2082 VectorType halfType = createVectorType(16, scalarType);
2083
2084 splitWideBinaryVectorOp<SrcOpTy>(
2085 srcOp, lhs, rhs, halfType, resultType, rewriter,
2086 [](Value lhsHalf, Value rhsHalf, Location loc,
2087 ConversionPatternRewriter &rewriter) -> Value {
2088 VectorType halfVecType = cast<VectorType>(lhsHalf.getType());
2089 // For f32, use cast to acc, add_elem/sub_elem, cast back
2090 auto lCastOp = aievec::CastOp::create(rewriter, loc, halfVecType,
2091 lhsHalf, /*isResAcc*/ true);
2092 auto rCastOp = aievec::CastOp::create(rewriter, loc, halfVecType,
2093 rhsHalf, /*isResAcc*/ true);
2094 auto elemOp = DstOpTy::create(
2095 rewriter, loc, lCastOp->getResult(0).getType(),
2096 lCastOp->getResult(0), rCastOp->getResult(0));
2097 auto resCastOp = aievec::CastOp::create(
2098 rewriter, loc, halfVecType, elemOp.getResult(),
2099 /*isResAcc*/ false);
2100 return resCastOp.getResult();
2101 });
2102 return success();
2103 }
2104
2105 // v32bf16: split into two v16bf16 ops
2106 if (laneSize == 32 && resultElWidth == 16) {
2107 VectorType halfType = createVectorType(16, scalarType);
2108
2109 splitWideBinaryVectorOp<SrcOpTy>(
2110 srcOp, lhs, rhs, halfType, resultType, rewriter,
2111 [](Value lhsHalf, Value rhsHalf, Location loc,
2112 ConversionPatternRewriter &rewriter) -> Value {
2113 VectorType halfVecType = cast<VectorType>(lhsHalf.getType());
2114 Type accType = getVectorOpDestType(halfVecType, /*AIE2 =*/true);
2115 auto lUpsOp =
2116 aievec::UPSOp::create(rewriter, loc, accType, lhsHalf);
2117 auto rUpsOp =
2118 aievec::UPSOp::create(rewriter, loc, accType, rhsHalf);
2119 auto elemOp =
2120 DstOpTy::create(rewriter, loc, lUpsOp->getResult(0).getType(),
2121 lUpsOp->getResult(0), rUpsOp->getResult(0));
2122 auto shiftParamOp = arith::ConstantOp::create(
2123 rewriter, loc, rewriter.getI32IntegerAttr(0));
2124 auto srsOp = aievec::SRSOp::create(rewriter, loc, halfVecType,
2125 elemOp.getResult(),
2126 shiftParamOp.getResult());
2127 return srsOp.getResult();
2128 });
2129 return success();
2130 }
2131
2132 // Now we know laneSize == 16 for remaining float cases
2133 // v16float or v16bf16 with extension op case
2134 if (resultElWidth == 32) {
2135 if (!lhsDefOp && !rhsDefOp) {
2136 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs,
2137 resultType, srcOp);
2138 }
2139
2140 auto lhsExt = getSourceOfWideningOp(lhs).value_or(nullptr);
2141 auto rhsExt = getSourceOfWideningOp(rhs).value_or(nullptr);
2142 // v16float
2143 if (!lhsExt && !rhsExt) {
2144 return genAddElemAIE2<SrcOpTy, DstOpTy>(rewriter, lhs, rhs,
2145 resultType, srcOp);
2146 }
2147
2148 // v16bf16 with two extension ops
2149 if (lhsExt && rhsExt) {
2150 auto lval = lhsExt;
2151 auto rval = rhsExt;
2152 VectorType vType = cast<VectorType>(lval.getType());
2153
2154 Type accType = getVectorOpDestType(vType, /*AIE2 =*/true);
2155 auto lUpsOp =
2156 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, lval);
2157 auto rUpsOp =
2158 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, rval);
2159 auto elemOp = DstOpTy::create(
2160 rewriter, srcOp.getLoc(), lUpsOp->getResult(0).getType(),
2161 lUpsOp->getResult(0), rUpsOp->getResult(0));
2162 rewriter.replaceOpWithNewOp<aievec::CastOp>(srcOp, srcOp.getType(),
2163 elemOp.getResult());
2164 return success();
2165 }
2166
2167 // v16bf16 with one extension op
2168 if (!lhsExt || !rhsExt) {
2169 auto lval = lhsExt ? lhsExt : lhs;
2170 auto rval = rhsExt ? rhsExt : rhs;
2171 auto extVal = lhsExt ? lval : rval;
2172 VectorType vType = cast<VectorType>(extVal.getType());
2173 Type accType = getVectorOpDestType(vType, /*AIE2 =*/true);
2174
2175 aievec::UPSOp upsOp;
2176 aievec::CastOp castOp;
2177 if (lhsExt) {
2178 upsOp =
2179 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, lval);
2180 castOp = aievec::CastOp::create(rewriter, srcOp.getLoc(),
2181 resultType, rval,
2182 /*isResAcc*/ true);
2183 } else {
2184 upsOp =
2185 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, rval);
2186 castOp = aievec::CastOp::create(rewriter, srcOp.getLoc(),
2187 resultType, lval,
2188 /*isResAcc*/ true);
2189 }
2190
2191 auto elemOp = DstOpTy::create(
2192 rewriter, srcOp.getLoc(), upsOp->getResult(0).getType(),
2193 upsOp->getResult(0), castOp->getResult(0));
2194
2195 rewriter.replaceOpWithNewOp<aievec::CastOp>(
2196 srcOp, srcOp.getType(), elemOp.getResult(), /*isResAcc*/ false);
2197
2198 return success();
2199 }
2200 }
2201
2202 // v16bfloat16
2203 Type accType = getVectorOpDestType(resultType, /*AIE2 =*/true);
2204 auto lUpsOp =
2205 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, lhs);
2206 auto rUpsOp =
2207 aievec::UPSOp::create(rewriter, srcOp.getLoc(), accType, rhs);
2208 auto elemOp = DstOpTy::create(rewriter, srcOp.getLoc(),
2209 lUpsOp->getResult(0).getType(),
2210 lUpsOp->getResult(0), rUpsOp->getResult(0));
2211 auto shiftParamOp = arith::ConstantOp::create(
2212 rewriter, srcOp.getLoc(), rewriter.getI32IntegerAttr(0));
2213 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
2214 srcOp, srcOp.getType(), elemOp.getResult(), shiftParamOp.getResult());
2215
2216 return success();
2217 }
2218
2219 return failure();
2220 }
2221};
2222
2225 aievec::AddElemOp>;
2228 aievec::SubElemOp>;
2231 aievec::AddElemOp>;
2234 aievec::SubElemOp>;
2235
2236template <typename SrcOpTy, typename DstOpTy>
2237struct LowerVectorMinMaxOpToAIEVecMinMaxOp : OpConversionPattern<SrcOpTy> {
2238 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
2239 using OpAdaptor = typename SrcOpTy::Adaptor;
2240
2241 LogicalResult
2242 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
2243 ConversionPatternRewriter &rewriter) const override {
2244 VectorType resultType = dyn_cast<VectorType>(srcOp.getType());
2245 if (!resultType)
2246 return failure();
2247
2248 // A set recording the element width we are supporting for AIE2.
2249 llvm::SmallSet<unsigned, 16> elWidthSet;
2250 elWidthSet.insert(8);
2251 elWidthSet.insert(16);
2252 elWidthSet.insert(32);
2253
2254 Type scalarType = resultType.getElementType();
2255 unsigned resultElWidth = scalarType.getIntOrFloatBitWidth();
2256 unsigned laneSize = getVectorLaneSize(resultType);
2257
2258 unsigned totalBits = laneSize * resultElWidth;
2259 if (!elWidthSet.count(resultElWidth) ||
2260 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16)))
2261 return failure();
2262
2263 if (totalBits == 256 && resultElWidth == 16) {
2264 // Pad v16bf16 to v32bf16, apply max/min, extract lower half
2265 Location loc = srcOp.getLoc();
2266 VectorType wideType = createVectorType(32, scalarType);
2267 Value lhsPad =
2268 padV16ToV32WithZeros(rewriter, loc, adaptor.getLhs(), scalarType);
2269 Value rhsPad =
2270 padV16ToV32WithZeros(rewriter, loc, adaptor.getRhs(), scalarType);
2271 auto wideOp = DstOpTy::create(rewriter, loc, wideType, lhsPad, rhsPad);
2272 rewriter.replaceOpWithNewOp<aievec::ExtOp>(srcOp, resultType,
2273 wideOp.getResult(), 0);
2274 return success();
2275 }
2276
2277 rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcOp.getType(),
2278 adaptor.getLhs(), adaptor.getRhs());
2279 return success();
2280 }
2281};
2282
2287
2288// aievec.min/aievec.max are signed-only, so unsigned integer min/max have no
2289// direct AIE intrinsic. Re-expand them into cmp+select, which the aievec
2290// cmp/sel lowerings turn into aievec.cmp/aievec.sel.
2291template <typename SrcOpTy, arith::CmpIPredicate Pred>
2292struct LowerVectorUMinMaxOpToCmpSelOp : OpConversionPattern<SrcOpTy> {
2293 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
2294 using OpAdaptor = typename SrcOpTy::Adaptor;
2295
2296 LogicalResult
2297 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
2298 ConversionPatternRewriter &rewriter) const override {
2299 if (!isa<VectorType>(srcOp.getType()))
2300 return failure();
2301 Value lhs = adaptor.getLhs();
2302 Value rhs = adaptor.getRhs();
2303 auto cmpOp =
2304 arith::CmpIOp::create(rewriter, srcOp.getLoc(), Pred, lhs, rhs);
2305 rewriter.replaceOpWithNewOp<arith::SelectOp>(srcOp, cmpOp, lhs, rhs);
2306 return success();
2307 }
2308};
2309
2314
2315// Promote scalar arith.maxsi/arith.minsi to vector aievec.max/aievec.min
2316// to avoid the AIE2 G_SELECT legalizer crash on scalar i32 select.
2317template <typename SrcOpTy, typename DstOpTy>
2318struct LowerScalarMinMaxToAIEVecMinMaxOp : OpConversionPattern<SrcOpTy> {
2319 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
2320 using OpAdaptor = typename SrcOpTy::Adaptor;
2321
2322 LogicalResult
2323 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
2324 ConversionPatternRewriter &rewriter) const override {
2325 // Only match scalar integer types (reject vectors)
2326 Type resultType = srcOp.getType();
2327 if (isa<VectorType>(resultType))
2328 return failure();
2329
2330 auto intType = dyn_cast<IntegerType>(resultType);
2331 if (!intType)
2332 return failure();
2333
2334 unsigned elWidth = intType.getWidth();
2335 if (elWidth != 8 && elWidth != 16 && elWidth != 32)
2336 return failure();
2337
2338 unsigned numLanes = 512 / elWidth;
2339 VectorType vecType = createVectorType(numLanes, intType);
2340 Location loc = srcOp.getLoc();
2341
2342 // Broadcast both scalars to 512-bit vectors
2343 auto lhsBcast = aievec::BroadcastScalarOp::create(rewriter, loc, vecType,
2344 adaptor.getLhs());
2345 auto rhsBcast = aievec::BroadcastScalarOp::create(rewriter, loc, vecType,
2346 adaptor.getRhs());
2347
2348 // Apply vector min/max
2349 auto vecOp = DstOpTy::create(rewriter, loc, vecType, lhsBcast.getResult(),
2350 rhsBcast.getResult());
2351
2352 // Extract element 0 back to scalar
2353 auto zeroIdx =
2354 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
2355 rewriter.replaceOpWithNewOp<aievec::ExtElemOp>(
2356 srcOp, intType, vecOp.getResult(), zeroIdx.getResult());
2357 return success();
2358 }
2359};
2360
2365
2372
2373template <typename SrcOpTy, typename CmpTy>
2374struct LowerVectorCmpOpToAIEVecCmpOp : OpConversionPattern<SrcOpTy> {
2375 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
2376 using OpAdaptor = typename SrcOpTy::Adaptor;
2377
2378 LogicalResult
2379 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
2380 ConversionPatternRewriter &rewriter) const override {
2381 VectorType lhsType = dyn_cast<VectorType>(srcOp.getLhs().getType());
2382 if (!lhsType)
2383 return failure();
2384
2385 llvm::SmallSet<unsigned, 16> elWidthSet;
2386 elWidthSet.insert(8);
2387 elWidthSet.insert(16);
2388 elWidthSet.insert(32);
2389
2390 Type scalarType = lhsType.getElementType();
2391 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2392 unsigned laneSize = getVectorLaneSize(lhsType);
2393
2394 unsigned totalBits = laneSize * elWidth;
2395 if (!elWidthSet.count(elWidth) ||
2396 (totalBits != 512 && (totalBits != 256 || elWidth != 16)))
2397 return failure();
2398
2399 Location loc = srcOp.getLoc();
2400 Value lhs = srcOp.getLhs();
2401 Value rhs = srcOp.getRhs();
2402 unsigned effectiveLaneSize = laneSize;
2403
2404 if (totalBits == 256 && elWidth == 16) {
2405 lhs = padV16ToV32WithZeros(rewriter, loc, lhs, scalarType);
2406 rhs = padV16ToV32WithZeros(rewriter, loc, rhs, scalarType);
2407 effectiveLaneSize = 32;
2408 }
2409
2410 // Unsigned int and unsigned long long are acceptable type.
2411 Type type = mlir::IntegerType::get(srcOp.getContext(),
2412 effectiveLaneSize <= 32 ? 32 : 64,
2413 mlir::IntegerType::Unsigned);
2414
2415 CmpTy pred = srcOp.getPredicate();
2416
2417 arith::CmpIPredicate ipred = convertToIntegerPredicate(pred);
2418
2419 aievec::CmpOp aieCmpOp =
2420 createCmpOpAIE2(rewriter, ipred, loc, type, lhs, rhs);
2421
2422 if (!aieCmpOp)
2423 return failure();
2424
2425 VectorType resultType = cast<VectorType>(srcOp.getResult().getType());
2426 // Convert vector i1 type to unsigned interger type by built-in unrealized
2427 // conversion cast op.
2428 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
2429 srcOp, resultType, aieCmpOp.getResult());
2430
2431 return success();
2432 }
2433};
2434
2439
2440struct LowerVectorSelectOpToAIEVecSelOp : OpConversionPattern<arith::SelectOp> {
2441 using OpConversionPattern::OpConversionPattern;
2442
2443 LogicalResult
2444 matchAndRewrite(arith::SelectOp srcOp, OpAdaptor adaptor,
2445 ConversionPatternRewriter &rewriter) const override {
2446 auto resultType = dyn_cast<VectorType>(srcOp.getType());
2447 if (!resultType)
2448 return failure();
2449
2450 llvm::SmallSet<unsigned, 16> elWidthSet;
2451 elWidthSet.insert(8);
2452 elWidthSet.insert(16);
2453 elWidthSet.insert(32);
2454
2455 Type scalarType = resultType.getElementType();
2456 unsigned resultElWidth = scalarType.getIntOrFloatBitWidth();
2457 unsigned laneSize = getVectorLaneSize(resultType);
2458
2459 unsigned totalBits = laneSize * resultElWidth;
2460 if (!elWidthSet.count(resultElWidth) ||
2461 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16)))
2462 return failure();
2463
2464 if (totalBits == 256 && resultElWidth == 16) {
2465 // Pad trueValue and falseValue to v32, do sel, extract lower half
2466 Location loc = srcOp.getLoc();
2467 VectorType wideType = createVectorType(32, scalarType);
2468
2469 // aievec.sel: bit=0 selects lhs, bit=1 selects rhs.
2470 // aievec.cmp: bit=1 where predicate is true.
2471 // arith.select: condition=1 returns true_value.
2472 // So false_value goes to lhs (bit=0) and true_value goes to rhs (bit=1).
2473 Value falsePad = padV16ToV32WithZeros(rewriter, loc,
2474 srcOp.getFalseValue(), scalarType);
2475 Value truePad =
2476 padV16ToV32WithZeros(rewriter, loc, srcOp.getTrueValue(), scalarType);
2477
2478 // The condition bitmask from cmp was produced at 32 lanes (since cmp
2479 // was also padded). Use 32-lane integer type for the cast.
2480 Type type = mlir::IntegerType::get(srcOp.getContext(), 32,
2481 mlir::IntegerType::Unsigned);
2482 auto convertOp = UnrealizedConversionCastOp::create(
2483 rewriter, loc, type, adaptor.getCondition());
2484
2485 auto wideSelOp = aievec::SelOp::create(rewriter, loc, wideType, falsePad,
2486 truePad, convertOp.getResult(0));
2487
2488 rewriter.replaceOpWithNewOp<aievec::ExtOp>(srcOp, resultType,
2489 wideSelOp.getResult(), 0);
2490 return success();
2491 }
2492
2493 Type type =
2494 mlir::IntegerType::get(srcOp.getContext(), laneSize <= 32 ? 32 : 64,
2495 mlir::IntegerType::Unsigned);
2496
2497 auto convertOp = UnrealizedConversionCastOp::create(
2498 rewriter, srcOp.getLoc(), type, adaptor.getCondition());
2499
2500 // aievec.sel: bit=0 selects lhs, bit=1 selects rhs.
2501 // aievec.cmp: bit=1 where predicate is true.
2502 // arith.select: condition=1 returns true_value.
2503 // So false_value goes to lhs (bit=0) and true_value goes to rhs (bit=1).
2504 rewriter.replaceOpWithNewOp<aievec::SelOp>(
2505 srcOp, srcOp.getResult().getType(), srcOp.getFalseValue(),
2506 srcOp.getTrueValue(), convertOp.getResult(0));
2507
2508 return success();
2509 }
2510};
2511
2512struct LowerVectorReductionMinOp : OpConversionPattern<vector::ReductionOp> {
2513 using OpConversionPattern::OpConversionPattern;
2514
2515 LogicalResult
2516 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2517 ConversionPatternRewriter &rewriter) const override {
2518 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::MINSI &&
2519 kind != vector::CombiningKind::MINUI &&
2520 kind != vector::CombiningKind::MINIMUMF &&
2521 kind != vector::CombiningKind::MINNUMF)
2522 return failure();
2523
2524 auto vType = cast<VectorType>(srcOp.getVector().getType());
2525 Type scalarType = vType.getElementType();
2526 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2527 unsigned laneSize = getVectorLaneSize(vType);
2528 unsigned vectorSize = laneSize * elWidth;
2529
2530 // Support 512-bit vectors directly, and 256-bit bf16 vectors by padding
2531 if (vectorSize != 512 && (vectorSize != 256 || !scalarType.isBF16()))
2532 return failure();
2533
2534 Location loc = srcOp.getLoc();
2535 Value inputVec = srcOp.getVector();
2536
2537 // For 256-bit bf16 (v16bf16), pad to 512-bit (v32bf16) with +inf
2538 if (vectorSize == 256) {
2539 std::tie(inputVec, laneSize) = padV16ToV32WithInfinity(
2540 rewriter, loc, srcOp.getVector(), scalarType, /*negativeInf=*/false);
2541 }
2542
2543 int shiftIndex = laneSize / 2;
2544 auto reduceResultOp = generateAIEVecOpsForReductionOp<aievec::MinOp>(
2545 rewriter, srcOp, shiftIndex, inputVec);
2546
2547 if (srcOp.getAcc()) {
2548 Value reduceResult = reduceResultOp.getResult();
2549 Value acc = srcOp.getAcc();
2550
2551 // If accumulator is bf16, use the high-level helper for bf16->f32->bf16
2552 if (acc.getType().isBF16()) {
2553 // Define the min operation to be performed in f32
2554 auto minOpBuilder = [&](Value lhs, Value rhs) -> Value {
2555 auto cmpOp = arith::CmpFOp::create(
2556 rewriter, srcOp.getLoc(), arith::CmpFPredicate::OLT, lhs, rhs);
2557 return arith::SelectOp::create(rewriter, srcOp.getLoc(), cmpOp, lhs,
2558 rhs);
2559 };
2560
2561 // Use helper to handle bf16->f32 conversion, perform min, and convert
2562 // back
2563 performBF16BinaryOpInF32(reduceResult, acc, srcOp, srcOp.getLoc(),
2564 rewriter, minOpBuilder);
2565 } else {
2566 // Non-bf16 path: perform min using cmpf and select
2567 auto cmpOp =
2568 arith::CmpFOp::create(rewriter, srcOp.getLoc(),
2569 arith::CmpFPredicate::OLT, reduceResult, acc);
2570 rewriter.replaceOpWithNewOp<arith::SelectOp>(srcOp, cmpOp, reduceResult,
2571 acc);
2572 }
2573 } else {
2574 rewriter.replaceOp(srcOp, reduceResultOp);
2575 }
2576 return success();
2577 }
2578};
2579
2580struct LowerVectorReductionMaxOp : OpConversionPattern<vector::ReductionOp> {
2581 using OpConversionPattern::OpConversionPattern;
2582
2583 LogicalResult
2584 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2585 ConversionPatternRewriter &rewriter) const override {
2586 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::MAXSI &&
2587 kind != vector::CombiningKind::MAXUI &&
2588 kind != vector::CombiningKind::MAXIMUMF &&
2589 kind != vector::CombiningKind::MAXNUMF)
2590 return failure();
2591
2592 auto vType = cast<VectorType>(srcOp.getVector().getType());
2593 Type scalarType = vType.getElementType();
2594 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2595 unsigned laneSize = getVectorLaneSize(vType);
2596 unsigned vectorSize = laneSize * elWidth;
2597
2598 // Support 512-bit vectors directly, and 256-bit bf16 vectors by padding
2599 // Only bf16 is supported for the 256-bit padding path (not f16)
2600 if (vectorSize != 512 && (vectorSize != 256 || !scalarType.isBF16()))
2601 return failure();
2602
2603 Location loc = srcOp.getLoc();
2604 Value inputVec = srcOp.getVector();
2605
2606 // For 256-bit bf16 (v16bf16), pad to 512-bit (v32bf16) with -inf
2607 if (vectorSize == 256) {
2608 std::tie(inputVec, laneSize) = padV16ToV32WithInfinity(
2609 rewriter, loc, srcOp.getVector(), scalarType, /*negativeInf=*/true);
2610 }
2611
2612 int shiftIndex = laneSize / 2;
2613 auto reduceResultOp = generateAIEVecOpsForReductionOp<aievec::MaxOp>(
2614 rewriter, srcOp, shiftIndex, inputVec);
2615
2616 if (srcOp.getAcc()) {
2617 Value reduceResult = reduceResultOp.getResult();
2618 Value acc = srcOp.getAcc();
2619
2620 // If accumulator is bf16, use the high-level helper for bf16->f32->bf16
2621 if (acc.getType().isBF16()) {
2622 // Define the max operation to be performed in f32
2623 auto maxOpBuilder = [&](Value lhs, Value rhs) -> Value {
2624 auto cmpOp = arith::CmpFOp::create(
2625 rewriter, srcOp.getLoc(), arith::CmpFPredicate::OGT, lhs, rhs);
2626 return arith::SelectOp::create(rewriter, srcOp.getLoc(), cmpOp, lhs,
2627 rhs);
2628 };
2629
2630 // Use helper to handle bf16->f32 conversion, perform max, and convert
2631 // back
2632 performBF16BinaryOpInF32(reduceResult, acc, srcOp, srcOp.getLoc(),
2633 rewriter, maxOpBuilder);
2634 } else {
2635 // Non-bf16 path: perform max directly
2636 auto cmpOp =
2637 arith::CmpFOp::create(rewriter, srcOp.getLoc(),
2638 arith::CmpFPredicate::OGT, reduceResult, acc);
2639 rewriter.replaceOpWithNewOp<arith::SelectOp>(srcOp, cmpOp, reduceResult,
2640 acc);
2641 }
2642 } else {
2643 rewriter.replaceOp(srcOp, reduceResultOp);
2644 }
2645 return success();
2646 }
2647};
2648
2649struct LowerVectorReductionAddIntOp : OpConversionPattern<vector::ReductionOp> {
2650 using OpConversionPattern::OpConversionPattern;
2651
2652 LogicalResult
2653 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2654 ConversionPatternRewriter &rewriter) const override {
2655 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::ADD)
2656 return failure();
2657
2658 auto vType = cast<VectorType>(srcOp.getVector().getType());
2659 Type scalarType = vType.getElementType();
2660 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2661 unsigned laneSize = getVectorLaneSize(vType);
2662 llvm::SmallSet<std::pair<unsigned, signed>, 16> laneSizeElWidthPairSet;
2663 laneSizeElWidthPairSet.insert({64, 8});
2664 laneSizeElWidthPairSet.insert({32, 16});
2665 laneSizeElWidthPairSet.insert({32, 32});
2666 laneSizeElWidthPairSet.insert({16, 32});
2667
2668 if (!isa<IntegerType>(scalarType) ||
2669 !laneSizeElWidthPairSet.count(std::make_pair(laneSize, elWidth)))
2670 return failure();
2671
2672 int shiftIndex = laneSize / 2;
2673 if (laneSize == 32 && elWidth == 32) {
2674 Location loc = srcOp.getLoc();
2675 VectorType vecType = createVectorType(laneSize / 2, scalarType);
2676
2677 auto lExtOp =
2678 aievec::ExtOp::create(rewriter, loc, vecType, srcOp.getVector(), 0);
2679 auto rExtOp =
2680 aievec::ExtOp::create(rewriter, loc, vecType, srcOp.getVector(), 1);
2681 auto addElemOp =
2682 aievec::AddElemOp::create(rewriter, loc, lExtOp.getResult().getType(),
2683 lExtOp.getResult(), rExtOp.getResult());
2684 shiftIndex /= 2;
2685 auto reduceResultOp = generateAIEVecOpsForReductionOp<aievec::AddElemOp>(
2686 rewriter, srcOp, shiftIndex, addElemOp.getResult());
2687 if (srcOp.getAcc())
2688 rewriter.replaceOpWithNewOp<arith::AddIOp>(
2689 srcOp, reduceResultOp.getResult(), srcOp.getAcc());
2690 else
2691 rewriter.replaceOp(srcOp, reduceResultOp);
2692 } else {
2693 auto reduceResultOp = generateAIEVecOpsForReductionOp<aievec::AddElemOp>(
2694 rewriter, srcOp, shiftIndex, srcOp.getVector());
2695 if (srcOp.getAcc())
2696 rewriter.replaceOpWithNewOp<arith::AddIOp>(
2697 srcOp, reduceResultOp.getResult(), srcOp.getAcc());
2698 else
2699 rewriter.replaceOp(srcOp, reduceResultOp);
2700 }
2701
2702 return success();
2703 }
2704};
2705
2707 : OpConversionPattern<vector::ReductionOp> {
2708 using OpConversionPattern::OpConversionPattern;
2709
2710 LogicalResult
2711 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2712 ConversionPatternRewriter &rewriter) const override {
2713 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::ADD)
2714 return failure();
2715
2716 auto vType = cast<VectorType>(srcOp.getVector().getType());
2717 Type scalarType = vType.getElementType();
2718 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2719 unsigned laneSize = getVectorLaneSize(vType);
2720
2721 if (!isa<FloatType>(scalarType) || laneSize != 16 || elWidth != 32)
2722 return failure();
2723
2724 int shiftIndex = laneSize / 2;
2725 assert(shiftIndex > 0 && (shiftIndex & (shiftIndex - 1)) == 0 &&
2726 "shiftIndex must be power of 2");
2727
2728 Location loc = srcOp.getLoc();
2729 Value curValue = srcOp.getVector();
2730 aievec::CastOp curOp = nullptr;
2731
2732 for (int id = shiftIndex; id > 0; id /= 2) {
2733 auto constOp = arith::ConstantOp::create(
2734 rewriter, loc, rewriter.getI32IntegerAttr(id * elWidth / 8));
2735
2736 auto shiftBytesOp = aievec::ShiftOp::create(
2737 rewriter, loc, vType, curValue, curValue, constOp.getResult());
2738
2739 auto lCastOp = aievec::CastOp::create(rewriter, loc, vType, curValue,
2740 /*isResAcc*/ true);
2741 auto rCastOp =
2742 aievec::CastOp::create(rewriter, loc, vType, shiftBytesOp.getResult(),
2743 /*isResAcc*/ true);
2744 auto elemOp = aievec::AddElemOp::create(
2745 rewriter, loc, lCastOp.getResult().getType(), lCastOp.getResult(),
2746 rCastOp.getResult());
2747 curOp = aievec::CastOp::create(rewriter, loc, vType, elemOp.getResult(),
2748 /*isResAcc*/ false);
2749 curValue = curOp.getResult();
2750 }
2751
2752 auto zeroConstOp =
2753 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
2754 auto reduceResultOp = aievec::ExtElemOp::create(
2755 rewriter, srcOp.getLoc(), scalarType, curOp, zeroConstOp.getResult());
2756
2757 if (srcOp.getAcc())
2758 rewriter.replaceOpWithNewOp<arith::AddFOp>(
2759 srcOp, reduceResultOp.getResult(), srcOp.getAcc());
2760 else
2761 rewriter.replaceOp(srcOp, reduceResultOp);
2762 return success();
2763 }
2764};
2765
2766// AIE2-specific bf16 ADD reduction - requires concat to v32bf16 before ext_elem
2767// due to aie2 ext_elem limitation
2769 : OpConversionPattern<vector::ReductionOp> {
2770 using OpConversionPattern::OpConversionPattern;
2771
2772 LogicalResult
2773 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2774 ConversionPatternRewriter &rewriter) const override {
2775
2776 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::ADD) {
2777 return failure();
2778 }
2779
2780 auto vType = cast<VectorType>(srcOp.getVector().getType());
2781 Type scalarType = vType.getElementType();
2782 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2783 unsigned laneSize = getVectorLaneSize(vType);
2784
2785 // Support both lane=16 and lane=32 for bf16
2786 if (!isa<FloatType>(scalarType) || (laneSize != 16 && laneSize != 32) ||
2787 elWidth != 16) {
2788 return failure();
2789 }
2790
2791 Location loc = srcOp.getLoc();
2792 Value curValue = srcOp.getVector();
2793 VectorType currentVType = vType; // Track current working vector type
2794
2795 // For lane=32, split into two v16bf16 halves and add them
2796 if (laneSize == 32) {
2797 VectorType halfType = createVectorType(laneSize / 2, scalarType);
2798 auto lowerHalf =
2799 aievec::ExtOp::create(rewriter, loc, halfType, srcOp.getVector(), 0);
2800 auto upperHalf =
2801 aievec::ExtOp::create(rewriter, loc, halfType, srcOp.getVector(), 1);
2802
2803 Type accType = getVectorOpDestType(halfType, /*AIE2 =*/true);
2804 auto lUpsOp =
2805 aievec::UPSOp::create(rewriter, loc, accType, lowerHalf.getResult());
2806 auto rUpsOp =
2807 aievec::UPSOp::create(rewriter, loc, accType, upperHalf.getResult());
2808 auto addElemOp = aievec::AddElemOp::create(
2809 rewriter, loc, accType, lUpsOp.getResult(), rUpsOp.getResult());
2810 auto shiftParamOp = arith::ConstantOp::create(
2811 rewriter, loc, rewriter.getI32IntegerAttr(0));
2812 auto srsOp =
2813 aievec::SRSOp::create(rewriter, loc, halfType, addElemOp.getResult(),
2814 shiftParamOp.getResult());
2815 curValue = srsOp.getResult();
2816 currentVType = halfType; // Update to v16bf16 after split
2817 }
2818
2819 int shiftIndex = 8; // Always 8 since we work with v16bf16
2820 Type accType = getVectorOpDestType(cast<VectorType>(curValue.getType()),
2821 /*AIE2 =*/true);
2822 unsigned accWidth =
2823 cast<VectorType>(accType).getElementType().getIntOrFloatBitWidth();
2824
2825 auto upsOp = aievec::UPSOp::create(rewriter, loc, accType, curValue);
2826 curValue = upsOp.getResult();
2827
2828 aievec::AddElemOp curOp = nullptr;
2829
2830 for (int id = shiftIndex; id > 0; id /= 2) {
2831 auto constOp = arith::ConstantOp::create(
2832 rewriter, loc, rewriter.getI32IntegerAttr(id * accWidth / 8));
2833 auto shiftBytesOp = aievec::ShiftOp::create(
2834 rewriter, loc, accType, curValue, curValue, constOp, true);
2835 curOp = aievec::AddElemOp::create(rewriter, loc, accType, curValue,
2836 shiftBytesOp.getResult());
2837 curValue = curOp.getResult();
2838 }
2839
2840 auto shiftParamOp = arith::ConstantOp::create(
2841 rewriter, srcOp.getLoc(), rewriter.getI32IntegerAttr(0));
2842 // Use currentVType instead of vType to ensure lane count matches
2843 auto srsOp =
2844 aievec::SRSOp::create(rewriter, loc, currentVType, curOp.getResult(),
2845 shiftParamOp.getResult());
2846
2847 // AIE2 ext_elem requires v32bf16, so concat v16bf16 to v32bf16
2848 VectorType vecType = createVectorType(32, scalarType);
2849 SmallVector<Value> concatSources = {srsOp.getResult(), srsOp.getResult()};
2850 auto concatOp =
2851 aievec::ConcatOp::create(rewriter, loc, vecType, concatSources);
2852
2853 auto zeroConstOp =
2854 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
2855 auto reduceResultOp =
2856 aievec::ExtElemOp::create(rewriter, srcOp.getLoc(), scalarType,
2857 concatOp, zeroConstOp.getResult());
2858
2859 if (srcOp.getAcc())
2860 rewriter.replaceOpWithNewOp<arith::AddFOp>(
2861 srcOp, reduceResultOp.getResult(), srcOp.getAcc());
2862 else
2863 rewriter.replaceOp(srcOp, reduceResultOp);
2864 return success();
2865 }
2866};
2867
2868// AIE2P-specific bf16 ADD reduction - can extract directly from v16bf16
2870 : OpConversionPattern<vector::ReductionOp> {
2871 using OpConversionPattern::OpConversionPattern;
2872
2873 LogicalResult
2874 matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor,
2875 ConversionPatternRewriter &rewriter) const override {
2876 if (auto kind = srcOp.getKind(); kind != vector::CombiningKind::ADD)
2877 return failure();
2878
2879 auto vType = cast<VectorType>(srcOp.getVector().getType());
2880 Type scalarType = vType.getElementType();
2881 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
2882 unsigned laneSize = getVectorLaneSize(vType);
2883
2884 // Support both lane=16 and lane=32 for bf16
2885 if (!isa<FloatType>(scalarType) || (laneSize != 16 && laneSize != 32) ||
2886 elWidth != 16)
2887 return failure();
2888
2889 Location loc = srcOp.getLoc();
2890 int shiftIndex = laneSize / 2;
2891 Value inputToReduce = srcOp.getVector();
2892
2893 // For lane=32, split into two v16bf16 halves, add them, then reduce
2894 if (laneSize == 32) {
2895 VectorType halfType = createVectorType(laneSize / 2, scalarType);
2896
2897 // Extract lower and upper halves
2898 auto lowerHalf =
2899 aievec::ExtOp::create(rewriter, loc, halfType, srcOp.getVector(), 0);
2900 auto upperHalf =
2901 aievec::ExtOp::create(rewriter, loc, halfType, srcOp.getVector(), 1);
2902
2903 // Add the two halves together
2904 Type accType = getVectorOpDestType(halfType, /*AIE2 =*/true);
2905 auto lUpsOp =
2906 aievec::UPSOp::create(rewriter, loc, accType, lowerHalf.getResult());
2907 auto rUpsOp =
2908 aievec::UPSOp::create(rewriter, loc, accType, upperHalf.getResult());
2909 auto addElemOp = aievec::AddElemOp::create(
2910 rewriter, loc, accType, lUpsOp.getResult(), rUpsOp.getResult());
2911 auto shiftParamOp = arith::ConstantOp::create(
2912 rewriter, loc, rewriter.getI32IntegerAttr(0));
2913 auto srsOp =
2914 aievec::SRSOp::create(rewriter, loc, halfType, addElemOp.getResult(),
2915 shiftParamOp.getResult());
2916
2917 inputToReduce = srsOp.getResult();
2918 shiftIndex = 8;
2919 }
2920
2921 // Perform reduction using utility
2922 Type accType = getVectorOpDestType(
2923 cast<VectorType>(inputToReduce.getType()), /*AIE2 =*/true);
2924 unsigned accWidth =
2925 cast<VectorType>(accType).getElementType().getIntOrFloatBitWidth();
2926
2927 auto upsOp = aievec::UPSOp::create(rewriter, loc, accType, inputToReduce);
2928 Value curValue = upsOp.getResult();
2929
2930 aievec::AddElemOp curOp = nullptr;
2931 for (int id = shiftIndex; id > 0; id /= 2) {
2932 auto constOp = arith::ConstantOp::create(
2933 rewriter, loc, rewriter.getI32IntegerAttr(id * accWidth / 8));
2934 auto shiftBytesOp = aievec::ShiftOp::create(
2935 rewriter, loc, accType, curValue, curValue, constOp, true);
2936 curOp = aievec::AddElemOp::create(rewriter, loc, accType, curValue,
2937 shiftBytesOp.getResult());
2938 curValue = curOp.getResult();
2939 }
2940
2941 // Extract element 0 from the f32 accumulator
2942 // The loop has already fully reduced the vector to a single value in
2943 // element 0
2944 auto zeroConstOp =
2945 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
2946 auto extractedF32 = aievec::ExtElemOp::create(
2947 rewriter, srcOp.getLoc(), rewriter.getF32Type(), curOp.getResult(),
2948 zeroConstOp.getResult());
2949
2950 // Convert extracted f32 to bf16
2951 auto reduceResultBF16 = arith::TruncFOp::create(
2952 rewriter, srcOp.getLoc(), scalarType, extractedF32.getResult());
2953
2954 if (srcOp.getAcc())
2955 rewriter.replaceOpWithNewOp<arith::AddFOp>(srcOp, reduceResultBF16,
2956 srcOp.getAcc());
2957 else
2958 rewriter.replaceOp(srcOp, reduceResultBF16);
2959 return success();
2960 }
2961};
2962
2963// Convert a `vector.extract_strided_slice` op on 1D vectors into an
2964// `aievec.select` + `aievec.ext` op.
2966 : OpConversionPattern<vector::ExtractStridedSliceOp> {
2967 using OpConversionPattern::OpConversionPattern;
2968
2969 LogicalResult
2970 matchAndRewrite(vector::ExtractStridedSliceOp extractOp, OpAdaptor adaptor,
2971 ConversionPatternRewriter &rewriter) const override {
2972 auto vType = extractOp.getSourceVectorType();
2973 if (vType.getRank() != 1)
2974 return failure();
2975
2976 int64_t stride = cast<IntegerAttr>(adaptor.getStrides()[0]).getInt();
2977 if (stride != 1)
2978 return failure();
2979
2980 // AIE doesn't support select operations on i8
2981 if (getElementSizeInBits(vType) == 8)
2982 return extractOp.emitError()
2983 << "AIEv1 doesn't support select ops on int8 types";
2984
2985 // We only accept the case where we are extracting a slice half the size of
2986 // the input vector.
2987 int64_t size = cast<IntegerAttr>(adaptor.getSizes()[0]).getInt();
2988 if (vType.getNumElements() != 2 * size)
2989 return failure();
2990
2991 int64_t offset = cast<IntegerAttr>(adaptor.getOffsets()[0]).getInt();
2992 aievec::aie1::SelectOp::Properties selectProps;
2993 if (failed(aievec::aie1::SelectOp::setPropertiesFromAttr(
2994 selectProps,
2995 rewriter.getDictionaryAttr(
2996 buildAttributeListForRotationSelectOp(rewriter, vType, offset)),
2997 [&]() { return extractOp.emitError(); })))
2998 return failure();
2999 auto selectOp = aievec::aie1::SelectOp::create(
3000 rewriter, extractOp.getLoc(), TypeRange({vType}),
3001 ValueRange({adaptor.getSource()}), selectProps);
3002 rewriter.replaceOpWithNewOp<aievec::aie1::ExtOp>(
3003 extractOp, extractOp.getType(), selectOp.getResult(),
3004 rewriter.getI8IntegerAttr(0));
3005 return success();
3006 }
3007};
3008
3009// Convert a `vector.extract_strided_slice` op on 1D vectors into an
3010// `aievec.shift` op.
3012 : OpConversionPattern<vector::ExtractStridedSliceOp> {
3013 using OpConversionPattern::OpConversionPattern;
3014
3015 LogicalResult
3016 matchAndRewrite(vector::ExtractStridedSliceOp extractOp, OpAdaptor adaptor,
3017 ConversionPatternRewriter &rewriter) const override {
3018 auto vType = cast<VectorType>(adaptor.getSource().getType());
3019 if (vType.getRank() != 1)
3020 return failure();
3021
3022 int64_t stride = cast<IntegerAttr>(adaptor.getStrides()[0]).getInt();
3023 if (stride != 1)
3024 return failure();
3025
3026 // We only accept the case where we are extracting a slice half the size of
3027 // the input vector.
3028 int64_t size = cast<IntegerAttr>(adaptor.getSizes()[0]).getInt();
3029 if (vType.getNumElements() != 2 * size)
3030 return failure();
3031
3032 auto shortVecType = cast<VectorType>(extractOp.getResult().getType());
3033 auto bottomHalf =
3034 aievec::ExtOp::create(rewriter, extractOp.getLoc(), shortVecType,
3035 adaptor.getSource(), rewriter.getI8IntegerAttr(0))
3036 .getResult();
3037 auto topHalf =
3038 aievec::ExtOp::create(rewriter, extractOp.getLoc(), shortVecType,
3039 adaptor.getSource(), rewriter.getI8IntegerAttr(1))
3040 .getResult();
3041 int64_t offset = cast<IntegerAttr>(adaptor.getOffsets()[0]).getInt();
3042 int32_t shiftBytes = offset * getElementSizeInBits(vType) / 8;
3043 auto shiftBytesConstOp = arith::ConstantOp::create(
3044 rewriter, extractOp.getLoc(), rewriter.getIntegerType(32),
3045 rewriter.getI32IntegerAttr(shiftBytes));
3046 rewriter.replaceOpWithNewOp<aievec::ShiftOp>(
3047 extractOp, shortVecType, bottomHalf, topHalf, shiftBytesConstOp);
3048
3049 return success();
3050 }
3051};
3052
3053// Replaces a short UPD op with a wide one followed by an ext op of the bottom
3054// half.
3055struct ExpandUPDToUPDAndExtPattern : OpConversionPattern<aievec::UPDOp> {
3056 using OpConversionPattern::OpConversionPattern;
3057
3058 ExpandUPDToUPDAndExtPattern(MLIRContext *context)
3059 : OpConversionPattern(context) {}
3060
3061 LogicalResult
3062 matchAndRewrite(aievec::UPDOp updOp, OpAdaptor adaptor,
3063 ConversionPatternRewriter &rewriter) const override {
3064 // Verify that we haven't already expanded this one
3065 if (updOp->hasOneUse() && isa<aievec::ExtOp>(*updOp->getUsers().begin()))
3066 return failure();
3067
3068 auto vecType = cast<VectorType>(updOp.getType());
3069 SmallVector<int64_t, 4> vecShape(vecType.getShape().begin(),
3070 vecType.getShape().end());
3071 vecShape[vecType.getRank() - 1] *= 2;
3072 auto longVecType = VectorType::get(vecShape, vecType.getElementType());
3073 auto newUpdOp = aievec::UPDOp::create(
3074 rewriter, updOp.getLoc(), longVecType, adaptor.getSource(),
3075 adaptor.getIndices(), adaptor.getOffset(), adaptor.getIndex(),
3076 adaptor.getVector());
3077 rewriter.replaceOpWithNewOp<aievec::ExtOp>(
3078 updOp, vecType, newUpdOp.getResult(), rewriter.getI8IntegerAttr(0));
3079
3080 return success();
3081 }
3082};
3083
3084// Replaces a wide UPD op followed by an ext op of the bottom half with a short
3085// UPD op.
3086struct FuseExtIntoUPDPattern : OpConversionPattern<aievec::ExtOp> {
3087 using OpConversionPattern::OpConversionPattern;
3088
3089 FuseExtIntoUPDPattern(MLIRContext *context) : OpConversionPattern(context) {}
3090
3091 LogicalResult
3092 matchAndRewrite(aievec::ExtOp extOp, OpAdaptor adaptor,
3093 ConversionPatternRewriter &rewriter) const override {
3094 // Verify we are extracting the lower half...
3095 if (extOp.getIndex() != 0)
3096 return failure();
3097 // ...of a UPDOp
3098 auto updOp = dyn_cast<aievec::UPDOp>(extOp.getSource().getDefiningOp());
3099 if (!updOp)
3100 return failure();
3101
3102 // Verify that this is a direct upd -> ext pattern
3103 if (!updOp->hasOneUse())
3104 return failure();
3105
3106 rewriter.replaceOpWithNewOp<aievec::UPDOp>(
3107 extOp, extOp.getType(), updOp.getSource(), updOp.getIndices(),
3108 updOp.getOffset(), updOp.getIndex(), updOp.getVector());
3109
3110 return success();
3111 }
3112};
3113
3114// Convert math.exp to aievec.exp for AIE2P (will be further lowered to exp2
3115// intrinsic)
3116struct ConvertMathExpToAIEVecExpOpPattern : OpConversionPattern<math::ExpOp> {
3117 using OpConversionPattern::OpConversionPattern;
3118
3119 LogicalResult
3120 matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor,
3121 ConversionPatternRewriter &rewriter) const override {
3122 if (matchExpOpForAIE2P(adaptor)) {
3123 auto srcType = cast<VectorType>(adaptor.getOperand().getType());
3124 rewriter.replaceOpWithNewOp<aievec::ExpOp>(expOp, srcType,
3125 adaptor.getOperand());
3126 return success();
3127 }
3128
3129 // aievec.exp takes bf16, so narrow an f32 operand and widen the result.
3130 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3131 if (!srcType || srcType.isScalable() || srcType.getRank() != 1 ||
3132 !srcType.getElementType().isF32() ||
3133 (srcType.getNumElements() != 16 && srcType.getNumElements() != 32))
3134 return failure();
3135
3136 Location loc = expOp.getLoc();
3137 auto bf16Type = VectorType::get(srcType.getShape(), rewriter.getBF16Type());
3138 auto shift =
3139 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
3140 Value input = aievec::SRSOp::create(rewriter, loc, bf16Type,
3141 adaptor.getOperand(), shift);
3142 Value exp = aievec::ExpOp::create(rewriter, loc, bf16Type, input);
3143 rewriter.replaceOpWithNewOp<aievec::UPSOp>(expOp, srcType, exp,
3144 /*shift=*/0);
3145 return success();
3146 }
3147};
3148
3149// Convert math.tanh to aievec.tanh, or math.exp2 to aievec.exp2, for AIE2P.
3150// Both are further lowered to the hardware intrinsic of the same name.
3151template <typename MathOpTy, typename AIEVecOpTy>
3153 : OpConversionPattern<MathOpTy> {
3154 using OpConversionPattern<MathOpTy>::OpConversionPattern;
3155 using OpAdaptor = typename MathOpTy::Adaptor;
3156
3157 LogicalResult
3158 matchAndRewrite(MathOpTy op, OpAdaptor adaptor,
3159 ConversionPatternRewriter &rewriter) const override {
3160 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3161 if (!srcType || !isAIE2PAccFloatUnaryType(srcType))
3162 return failure();
3163
3164 rewriter.replaceOpWithNewOp<AIEVecOpTy>(op, srcType, adaptor.getOperand());
3165 return success();
3166 }
3167};
3168
3169// Convert math.erf to aievec.tanh for AIE2P, using
3170//
3171// erf(x) ~= tanh(a * x + b * x^3), a = 2/sqrt(pi), b = 2 * a * 0.044715
3172//
3173// (the constants of the tanh form of GELU). The tanh argument is accumulated
3174// in f32 from bf16 multiplies. `a` is split into bf16 high and low parts so
3175// the linear term keeps f32 precision; x^2, x^3 and `b` are rounded to bf16.
3176struct ComputeErfOpAIE2PPattern : OpConversionPattern<math::ErfOp> {
3177 using OpConversionPattern::OpConversionPattern;
3178
3179 LogicalResult
3180 matchAndRewrite(math::ErfOp erfOp, OpAdaptor adaptor,
3181 ConversionPatternRewriter &rewriter) const override {
3182 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3183 if (!srcType || srcType.getRank() != 1 ||
3184 !isAIE2PAccFloatUnaryType(srcType))
3185 return failure();
3186
3187 constexpr double a = 1.1283791670955126; // 2/sqrt(pi)
3188 constexpr double b = 0.10091092485409293; // 2 * a * 0.044715
3189
3190 Location loc = erfOp.getLoc();
3191 auto bf16Type = VectorType::get(srcType.getShape(), rewriter.getBF16Type());
3192 auto f32Type = VectorType::get(srcType.getShape(), rewriter.getF32Type());
3193 auto shift =
3194 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
3195 auto toBF16 = [&](Value v) -> Value {
3196 return aievec::SRSOp::create(rewriter, loc, bf16Type, v, shift);
3197 };
3198 // A bf16 splat of `value`, and the value it was rounded to.
3199 auto splatBF16 = [&](double value) -> std::pair<Value, double> {
3200 auto attr = rewriter.getFloatAttr(rewriter.getBF16Type(), value);
3201 Value splat = arith::ConstantOp::create(
3202 rewriter, loc, DenseElementsAttr::get(bf16Type, attr));
3203 return {splat, attr.getValueAsDouble()};
3204 };
3205
3206 Value x = adaptor.getOperand();
3207 if (srcType.getElementType().isF32())
3208 x = toBF16(x);
3209
3210 auto [aHi, aHiValue] = splatBF16(a);
3211 Value aLo = splatBF16(a - aHiValue).first;
3212 Value bSplat = splatBF16(b).first;
3213
3214 Value x2 = toBF16(aievec::MulElemOp::create(rewriter, loc, f32Type, x, x));
3215 Value x3 = toBF16(aievec::MulElemOp::create(rewriter, loc, f32Type, x2, x));
3216 Value arg = aievec::MulElemOp::create(rewriter, loc, f32Type, x, aHi);
3217 arg = aievec::FMAElemOp::create(rewriter, loc, f32Type, x, aLo, arg,
3218 /*fmsub=*/false);
3219 arg = aievec::FMAElemOp::create(rewriter, loc, f32Type, x3, bSplat, arg,
3220 /*fmsub=*/false);
3221 Value result = aievec::TanhOp::create(rewriter, loc, f32Type, arg);
3222 if (srcType.getElementType().isBF16())
3223 result = toBF16(result);
3224
3225 rewriter.replaceOp(erfOp, result);
3226 return success();
3227 }
3228};
3229
3230// Convert a scalar math.tanh, math.erf or math.exp2 for AIE2P by broadcasting
3231// it to a 16-lane vector, applying the vector op and extracting lane 0.
3232template <typename OpTy>
3233struct ComputeScalarMathOpByVectorAIE2PPattern : OpConversionPattern<OpTy> {
3234 using OpConversionPattern<OpTy>::OpConversionPattern;
3235 using OpAdaptor = typename OpTy::Adaptor;
3236
3237 LogicalResult
3238 matchAndRewrite(OpTy op, OpAdaptor adaptor,
3239 ConversionPatternRewriter &rewriter) const override {
3240 Type type = adaptor.getOperand().getType();
3241 if (!type.isBF16() && !type.isF32())
3242 return failure();
3243
3244 Location loc = op.getLoc();
3245 auto vecType = VectorType::get({16}, type);
3246 Value splat = aievec::BroadcastScalarOp::create(rewriter, loc, vecType,
3247 adaptor.getOperand());
3248 Value result = OpTy::create(rewriter, loc, splat);
3249 rewriter.replaceOpWithNewOp<vector::ExtractOp>(op, result, 0);
3250 return success();
3251 }
3252};
3253
3254// Convert a one-lane math.tanh, math.erf or math.exp2 for AIE2P to the scalar
3255// op, which ComputeScalarMathOpByVectorAIE2PPattern lowers.
3256template <typename OpTy>
3257struct ComputeOneLaneMathOpAIE2PPattern : OpConversionPattern<OpTy> {
3258 using OpConversionPattern<OpTy>::OpConversionPattern;
3259 using OpAdaptor = typename OpTy::Adaptor;
3260
3261 LogicalResult
3262 matchAndRewrite(OpTy op, OpAdaptor adaptor,
3263 ConversionPatternRewriter &rewriter) const override {
3264 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3265 if (!srcType || srcType.isScalable() || srcType.getRank() != 1 ||
3266 srcType.getNumElements() != 1)
3267 return failure();
3268
3269 Location loc = op.getLoc();
3270 Value scalar =
3271 vector::ExtractOp::create(rewriter, loc, adaptor.getOperand(), 0);
3272 Value result = OpTy::create(rewriter, loc, scalar);
3273 Value poison = ub::PoisonOp::create(rewriter, loc, srcType);
3274 rewriter.replaceOpWithNewOp<vector::InsertOp>(op, result, poison, 0);
3275 return success();
3276 }
3277};
3278
3279struct ComputeExpOpByLUTLLVMPattern : OpConversionPattern<math::ExpOp> {
3280 using OpConversionPattern::OpConversionPattern;
3281
3282 LogicalResult
3283 matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor,
3284 ConversionPatternRewriter &rewriter) const override {
3285
3286 if (!matchExpOpForAIE2LUT(adaptor))
3287 return failure();
3288
3289 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3290 unsigned laneSize = getVectorLaneSize(srcType);
3291 Location loc = expOp.getLoc();
3292 StringRef funcName = "getExpBf16";
3293
3294 VectorType v16bf16Ty = mlir::VectorType::get({16}, rewriter.getBF16Type());
3295 VectorType v8i64Ty = mlir::VectorType::get({8}, rewriter.getI64Type());
3296 func::FuncOp fnOp = getOrInsertFuncDecl(
3297 rewriter, expOp->getParentWithTrait<OpTrait::SymbolTable>(), funcName,
3298 TypeRange{v16bf16Ty}, TypeRange{v8i64Ty});
3299
3300 // Handle v32bf16 by splitting into two v16bf16 operations
3301 if (laneSize == 32) {
3302 splitWideUnaryVectorOp<math::ExpOp>(
3303 expOp, adaptor.getOperand(), v16bf16Ty, srcType, rewriter,
3304 [&fnOp](Value halfInput, Location loc,
3305 ConversionPatternRewriter &rewriter) -> Value {
3306 VectorType v16bf16Ty =
3307 mlir::VectorType::get({16}, rewriter.getBF16Type());
3308 auto callOp = func::CallOp::create(rewriter, loc, fnOp,
3309 SmallVector<Value>{halfInput});
3310 Type accType = getVectorOpDestType(v16bf16Ty, /*AIE2 =*/true);
3311 auto resCastOp = vector::BitCastOp::create(rewriter, loc, accType,
3312 callOp.getResults());
3313 auto shiftParamOp = arith::ConstantOp::create(
3314 rewriter, loc, rewriter.getI32IntegerAttr(0));
3315 auto srsOp = aievec::SRSOp::create(rewriter, loc, v16bf16Ty,
3316 resCastOp.getResult(),
3317 shiftParamOp.getResult());
3318 return srsOp.getResult();
3319 });
3320 return success();
3321 }
3322
3323 // Handle v16bf16 directly
3324 SmallVector<Value> expOperands = {adaptor.getOperand()};
3325
3326 Type accTypeNative = getVectorOpDestType(srcType, /*AIE2 =*/true);
3327 auto callOp = func::CallOp::create(rewriter, loc, fnOp, expOperands);
3328 auto resCastOp = vector::BitCastOp::create(rewriter, loc, accTypeNative,
3329 callOp.getResults());
3330 auto shiftParamOp =
3331 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
3332 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
3333 expOp, srcType, resCastOp.getResult(), shiftParamOp.getResult());
3334
3335 return success();
3336 }
3337};
3338// Lower ExpOp to function call
3339struct ComputeExpOpByLUTPattern : OpConversionPattern<math::ExpOp> {
3340 using OpConversionPattern::OpConversionPattern;
3341
3342 LogicalResult
3343 matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor,
3344 ConversionPatternRewriter &rewriter) const override {
3345 if (!matchExpOpForAIE2LUT(adaptor))
3346 return failure();
3347 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3348 StringRef includeName = "lut_based_ops.h";
3349 auto moduleOp = expOp->getParentOfType<mlir::ModuleOp>();
3350 rewriter.setInsertionPointToStart(
3351 &moduleOp.getRegion().getBlocks().front());
3352 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3353
3354 rewriter.setInsertionPoint(expOp);
3355
3356 auto v16bf16OpaqueTy =
3357 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
3358 auto opaquedOperand =
3359 UnrealizedConversionCastOp::create(
3360 rewriter, expOp.getLoc(), v16bf16OpaqueTy, adaptor.getOperand())
3361 .getResult(0);
3362 SmallVector<Value> expOperands = {opaquedOperand};
3363
3364 Type accTypeNative = getVectorOpDestType(srcType, /*AIE2 =*/true);
3365 Type v16accf32OpaqueTy =
3366 emitc::OpaqueType::get(rewriter.getContext(), "v16accfloat");
3367 auto callOp = emitc::CallOpaqueOp::create(
3368 rewriter, expOp.getLoc(), TypeRange{v16accf32OpaqueTy}, "getExpBf16",
3369 nullptr, nullptr, expOperands);
3370 auto resCastOp = UnrealizedConversionCastOp::create(
3371 rewriter, expOp.getLoc(), accTypeNative, callOp.getResults());
3372 auto shiftParamOp = arith::ConstantOp::create(
3373 rewriter, expOp.getLoc(), rewriter.getI32IntegerAttr(0));
3374 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
3375 expOp, srcType, resCastOp.getResult(0), shiftParamOp.getResult());
3376
3377 return success();
3378 }
3379};
3380
3381// Lower the inverse of a float to aievec.inv (LLVMIR backend for AIE2P)
3382// Supports both scalar f32 and vector<Nxf32> types.
3383// Convert the pattern-
3384// %cst = arith.constant 1.000000e+00 : f32
3385// %0 = arith.divf %cst, %arg1 : f32
3386// to -
3387// %0 = aievec.inv %arg1 : f32
3388// Also supports:
3389// %cst = arith.constant dense<1.0> : vector<16xf32>
3390// %0 = arith.divf %cst, %arg1 : vector<16xf32>
3391// to -
3392// %0 = aievec.inv %arg1 : vector<16xf32>
3393struct ConvertDivFToAIEVecInvOpPattern : OpConversionPattern<arith::DivFOp> {
3394 using OpConversionPattern::OpConversionPattern;
3395
3396 LogicalResult
3397 matchAndRewrite(arith::DivFOp divOp, OpAdaptor adaptor,
3398 ConversionPatternRewriter &rewriter) const override {
3399 Type srcType = adaptor.getLhs().getType();
3400
3401 // Check if LHS is defined by an operation
3402 auto *defOp = divOp.getLhs().getDefiningOp();
3403 if (!defOp)
3404 return failure();
3405
3406 auto constOp = dyn_cast<arith::ConstantOp>(defOp);
3407 if (!constOp)
3408 return failure();
3409
3410 // Handle scalar f32 case
3411 if (auto fType = dyn_cast<FloatType>(srcType)) {
3412 if (fType.getWidth() != 32)
3413 return failure();
3414
3415 auto floatAttr = dyn_cast<FloatAttr>(constOp.getValue());
3416 if (!floatAttr || !floatAttr.getValue().isExactlyValue(1.0))
3417 return failure();
3418
3419 rewriter.replaceOpWithNewOp<aievec::InvOp>(divOp, srcType,
3420 adaptor.getRhs());
3421 return success();
3422 }
3423
3424 // Handle vector f32 case
3425 if (auto vecType = dyn_cast<VectorType>(srcType)) {
3426 auto elemType = vecType.getElementType();
3427 if (!elemType.isF32())
3428 return failure();
3429
3430 // Check for supported vector sizes (16 or 32 lanes)
3431 unsigned laneSize = getVectorLaneSize(vecType);
3432 if (laneSize != 16 && laneSize != 32)
3433 return failure();
3434
3435 // Check if it's a splat of 1.0
3436 auto denseAttr = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
3437 if (!denseAttr || !denseAttr.isSplat())
3438 return failure();
3439
3440 if (!denseAttr.getSplatValue<APFloat>().isExactlyValue(1.0))
3441 return failure();
3442
3443 rewriter.replaceOpWithNewOp<aievec::InvOp>(divOp, vecType,
3444 adaptor.getRhs());
3445 return success();
3446 }
3447
3448 return failure();
3449 }
3450};
3451
3452// Convert math.tanh to a function call to compute tanh(x) by look up tables
3453struct ComputeTanhOpByLUTPattern : OpConversionPattern<math::TanhOp> {
3454 using OpConversionPattern::OpConversionPattern;
3455
3456 LogicalResult
3457 matchAndRewrite(math::TanhOp tanhOp, OpAdaptor adaptor,
3458 ConversionPatternRewriter &rewriter) const override {
3459 auto srcType = dyn_cast<VectorType>(tanhOp.getOperand().getType());
3460 if (!srcType)
3461 return failure();
3462
3463 Type scalarType = srcType.getElementType();
3464 if (!isa<FloatType>(scalarType))
3465 return failure();
3466
3467 unsigned laneSize = getVectorLaneSize(srcType);
3468 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3469 if (elWidth != 16 || laneSize != 16)
3470 return failure();
3471
3472 StringRef includeName = "lut_based_ops.h";
3473 auto moduleOp = tanhOp->getParentOfType<mlir::ModuleOp>();
3474 rewriter.setInsertionPointToStart(
3475 &moduleOp.getRegion().getBlocks().front());
3476 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3477
3478 rewriter.setInsertionPoint(tanhOp);
3479 Type v16bf16OpaqueTy =
3480 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
3481 auto opaquedOperand =
3482 UnrealizedConversionCastOp::create(
3483 rewriter, tanhOp.getLoc(), v16bf16OpaqueTy, adaptor.getOperand())
3484 .getResult(0);
3485 SmallVector<Value> tanhOperands = {opaquedOperand};
3486 auto callOp = emitc::CallOpaqueOp::create(rewriter, tanhOp.getLoc(),
3487 v16bf16OpaqueTy, "getTanhBf16",
3488 nullptr, nullptr, tanhOperands);
3489 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3490 tanhOp, TypeRange{tanhOp.getResult().getType()}, callOp.getResults());
3491
3492 return success();
3493 }
3494};
3495
3496// Convert math.sqrt to a function call to compute sqrt(x) for v16bfloat16 and
3497// v32bfloat16 types
3498struct ComputeSqrtOpAIE2Pattern : OpConversionPattern<math::SqrtOp> {
3499 using OpConversionPattern::OpConversionPattern;
3500
3501 LogicalResult
3502 matchAndRewrite(math::SqrtOp sqrtOp, OpAdaptor adaptor,
3503 ConversionPatternRewriter &rewriter) const override {
3504 auto srcType = dyn_cast<VectorType>(sqrtOp.getOperand().getType());
3505 if (!srcType)
3506 return failure();
3507
3508 Type scalarType = srcType.getElementType();
3509 if (!isa<FloatType>(scalarType))
3510 return failure();
3511
3512 unsigned laneSize = getVectorLaneSize(srcType);
3513 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3514 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
3515 return failure();
3516
3517 StringRef includeName = "vec_math.h";
3518 auto moduleOp = sqrtOp->getParentOfType<mlir::ModuleOp>();
3519 rewriter.setInsertionPointToStart(
3520 &moduleOp.getRegion().getBlocks().front());
3521 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3522
3523 rewriter.setInsertionPoint(sqrtOp);
3524 Type vLNbf16OpaqueTy;
3525 if (laneSize == 16)
3526 vLNbf16OpaqueTy =
3527 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
3528 else
3529 vLNbf16OpaqueTy =
3530 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
3531 auto opaquedOperand =
3532 UnrealizedConversionCastOp::create(
3533 rewriter, sqrtOp.getLoc(), vLNbf16OpaqueTy, adaptor.getOperand())
3534 .getResult(0);
3535 SmallVector<Value> sqrtOperands = {opaquedOperand};
3536 auto callOp = emitc::CallOpaqueOp::create(
3537 rewriter, sqrtOp.getLoc(), TypeRange{vLNbf16OpaqueTy}, "getSqrtBf16",
3538 nullptr, nullptr, sqrtOperands);
3539 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3540 sqrtOp, TypeRange{sqrtOp.getResult().getType()}, callOp.getResults());
3541
3542 return success();
3543 }
3544};
3545
3546struct ComputeRsqrtOpLLVMAIE2Pattern : OpConversionPattern<math::RsqrtOp> {
3547 using OpConversionPattern::OpConversionPattern;
3548
3549 LogicalResult
3550 matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor,
3551 ConversionPatternRewriter &rewriter) const override {
3552 auto srcType = dyn_cast<VectorType>(adaptor.getOperand().getType());
3553 if (!srcType)
3554 return failure();
3555
3556 Type scalarType = srcType.getElementType();
3557 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3558 unsigned laneSize = getVectorLaneSize(srcType);
3559
3560 // Only support v16bf16 for LLVM backend
3561 if (!isa<FloatType>(scalarType) || laneSize != 16 || elWidth != 16)
3562 return failure();
3563
3564 StringRef funcName = "getRsqrtBf16";
3565
3566 VectorType v16bf16Ty = mlir::VectorType::get({16}, rewriter.getBF16Type());
3567 VectorType v8i64Ty = mlir::VectorType::get({8}, rewriter.getI64Type());
3568 func::FuncOp fnOp = getOrInsertFuncDecl(
3569 rewriter, rsqrtOp->getParentWithTrait<OpTrait::SymbolTable>(), funcName,
3570 TypeRange{v16bf16Ty}, TypeRange{v8i64Ty});
3571
3572 SmallVector<Value> rsqrtOperands = {adaptor.getOperand()};
3573
3574 Type accTypeNative = getVectorOpDestType(srcType, /*AIE2 =*/true);
3575 auto callOp =
3576 func::CallOp::create(rewriter, rsqrtOp.getLoc(), fnOp, rsqrtOperands);
3577 auto resCastOp = vector::BitCastOp::create(
3578 rewriter, rsqrtOp.getLoc(), accTypeNative, callOp.getResults());
3579 auto shiftParamOp = arith::ConstantOp::create(
3580 rewriter, rsqrtOp.getLoc(), rewriter.getI32IntegerAttr(0));
3581 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
3582 rsqrtOp, srcType, resCastOp.getResult(), shiftParamOp.getResult());
3583
3584 return success();
3585 }
3586};
3587
3588// Convert math.rsqrt to a function call to compute 1.0f / sqrt(x) for
3589// v16bfloat16 and v32bfloat16 types
3590struct ComputeRsqrtOpPattern : OpConversionPattern<math::RsqrtOp> {
3591 using OpConversionPattern::OpConversionPattern;
3592
3593 LogicalResult
3594 matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor,
3595 ConversionPatternRewriter &rewriter) const override {
3596 auto srcType = dyn_cast<VectorType>(rsqrtOp.getOperand().getType());
3597 if (!srcType)
3598 return failure();
3599
3600 Type scalarType = srcType.getElementType();
3601 if (!isa<FloatType>(scalarType))
3602 return failure();
3603
3604 unsigned laneSize = getVectorLaneSize(srcType);
3605 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3606 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
3607 return failure();
3608
3609 StringRef includeName = "vec_math.h";
3610 auto moduleOp = rsqrtOp->getParentOfType<mlir::ModuleOp>();
3611 rewriter.setInsertionPointToStart(
3612 &moduleOp.getRegion().getBlocks().front());
3613 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3614
3615 rewriter.setInsertionPoint(rsqrtOp);
3616 Type vLNbf16OpaqueTy;
3617 if (laneSize == 16)
3618 vLNbf16OpaqueTy =
3619 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
3620 else
3621 vLNbf16OpaqueTy =
3622 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
3623 auto opaquedOperand =
3624 UnrealizedConversionCastOp::create(
3625 rewriter, rsqrtOp.getLoc(), vLNbf16OpaqueTy, adaptor.getOperand())
3626 .getResult(0);
3627 SmallVector<Value> rsqrtOperands = {opaquedOperand};
3628 auto callOp = emitc::CallOpaqueOp::create(
3629 rewriter, rsqrtOp.getLoc(), TypeRange{vLNbf16OpaqueTy}, "getRsqrtBf16",
3630 nullptr, nullptr, rsqrtOperands);
3631 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3632 rsqrtOp, TypeRange{rsqrtOp.getResult().getType()}, callOp.getResults());
3633
3634 return success();
3635 }
3636};
3637
3638// Convert math.erf to a function call to compute erf(x) for v16bfloat16 and
3639// v32bfloat16 types
3640struct ComputeErfOpPattern : OpConversionPattern<math::ErfOp> {
3641 using OpConversionPattern::OpConversionPattern;
3642
3643 LogicalResult
3644 matchAndRewrite(math::ErfOp erfOp, OpAdaptor adaptor,
3645 ConversionPatternRewriter &rewriter) const override {
3646 auto srcType = dyn_cast<VectorType>(erfOp.getOperand().getType());
3647 if (!srcType)
3648 return failure();
3649
3650 Type scalarType = srcType.getElementType();
3651 if (!isa<FloatType>(scalarType))
3652 return failure();
3653
3654 unsigned laneSize = getVectorLaneSize(srcType);
3655 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3656 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
3657 return failure();
3658
3659 StringRef includeName = "vec_math.h";
3660 auto moduleOp = erfOp->getParentOfType<mlir::ModuleOp>();
3661 rewriter.setInsertionPointToStart(
3662 &moduleOp.getRegion().getBlocks().front());
3663 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3664
3665 rewriter.setInsertionPoint(erfOp);
3666 Type vLNbf16OpaqueTy;
3667 if (laneSize == 16)
3668 vLNbf16OpaqueTy =
3669 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
3670 else
3671 vLNbf16OpaqueTy =
3672 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
3673 auto opaquedOperand =
3674 UnrealizedConversionCastOp::create(
3675 rewriter, erfOp.getLoc(), vLNbf16OpaqueTy, adaptor.getOperand())
3676 .getResult(0);
3677 SmallVector<Value> erfOperands = {opaquedOperand};
3678 auto callOp = emitc::CallOpaqueOp::create(
3679 rewriter, erfOp.getLoc(), TypeRange{vLNbf16OpaqueTy}, "getErfBf16",
3680 nullptr, nullptr, erfOperands);
3681 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3682 erfOp, TypeRange{erfOp.getResult().getType()}, callOp.getResults());
3683
3684 return success();
3685 }
3686};
3687
3688// Convert math.absf and math.absi to a function call to compute abs(x) for
3689// v16bfloat16, v32bfloat16, v16float, v16int32, v32int16 and v64int8 types
3690template <typename SrcOpTy>
3691struct ComputeAbsOpPattern : OpConversionPattern<SrcOpTy> {
3692 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
3693 using OpAdaptor = typename SrcOpTy::Adaptor;
3694
3695 LogicalResult
3696 matchAndRewrite(SrcOpTy absOp, OpAdaptor adaptor,
3697 ConversionPatternRewriter &rewriter) const override {
3698 auto vecTy = dyn_cast<VectorType>(absOp.getOperand().getType());
3699 if (!vecTy)
3700 return failure();
3701
3702 Type elemTy = vecTy.getElementType();
3703
3704 unsigned laneSize = getVectorLaneSize(vecTy);
3705 unsigned elWidth = elemTy.getIntOrFloatBitWidth();
3706
3707 StringRef includeName = "vec_math.h";
3708 auto moduleOp = absOp->template getParentOfType<mlir::ModuleOp>();
3709 rewriter.setInsertionPointToStart(
3710 &moduleOp.getRegion().getBlocks().front());
3711 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
3712
3713 rewriter.setInsertionPoint(absOp);
3714 std::ostringstream typeName;
3715 typeName << "v" << laneSize;
3716 if (isa<FloatType>(elemTy)) {
3717 if (elWidth == 16)
3718 typeName << "bfloat16";
3719 else
3720 typeName << "float";
3721 } else
3722 typeName << "int" << elWidth;
3723 Type vecOpaqueTy =
3724 emitc::OpaqueType::get(rewriter.getContext(), typeName.str());
3725 auto opaquedOperand =
3726 UnrealizedConversionCastOp::create(rewriter, absOp.getLoc(),
3727 vecOpaqueTy, adaptor.getOperand())
3728 .getResult(0);
3729 SmallVector<Value> absOperands = {opaquedOperand};
3730 auto callOp = emitc::CallOpaqueOp::create(rewriter, absOp.getLoc(),
3731 TypeRange{vecOpaqueTy}, "getAbs",
3732 nullptr, nullptr, absOperands);
3733 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3734 absOp, TypeRange{absOp.getResult().getType()}, callOp.getResults());
3735
3736 return success();
3737 }
3738};
3739
3742
3743template <typename SrcOpTy>
3744struct LowerExtOpPattern : OpConversionPattern<SrcOpTy> {
3745 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
3746 using OpAdaptor = typename SrcOpTy::Adaptor;
3747
3748 LogicalResult
3749 matchAndRewrite(SrcOpTy extOp, OpAdaptor adaptor,
3750 ConversionPatternRewriter &rewriter) const override {
3751 VectorType srcType = cast<VectorType>(extOp.getIn().getType());
3752 VectorType dstType = cast<VectorType>(extOp.getOut().getType());
3753
3754 Type scalarType = dstType.getElementType();
3755 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3756 auto accType =
3757 isa<IntegerType>(scalarType) && (elWidth == 32 || elWidth == 64)
3758 ? dstType
3759 : getVectorOpDestType(srcType, /*AIE2 =*/true);
3760 auto upsOp =
3761 aievec::UPSOp::create(rewriter, extOp.getLoc(), accType, extOp.getIn());
3762
3763 if (dstType.getElementType().getIntOrFloatBitWidth() == 16) {
3764 auto shiftParamOp = arith::ConstantOp::create(
3765 rewriter, extOp.getLoc(), rewriter.getI32IntegerAttr(0));
3766 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
3767 extOp, dstType, upsOp.getResult(), shiftParamOp.getResult());
3768 } else
3769 rewriter.replaceOpWithNewOp<aievec::CastOp>(
3770 extOp, dstType, upsOp.getResult(), /*isResAcc*/ false);
3771
3772 return success();
3773 }
3774};
3775
3778
3779// Vector arith.sitofp i16 -> bf16 on AIE2P via the classic "magic number"
3780// trick that aie_api uses in detail/aie2p/elementary.hpp:
3781//
3782// 1. UPS the int16 input to acc32 (32-bit integer accumulator lanes).
3783// 2. Add a per-lane "magic_l" constant to the acc32 vector. The constant
3784// is the bit pattern of bf16(0x4b01) (= 2^23 + 128) broadcast over the
3785// lanes and reinterpreted as i32 -- chosen so that the resulting acc32
3786// vector, when bit-reinterpreted as f32 and subtracted from the same
3787// magic constant in accfloat space, yields the float representation of
3788// the original signed int16 input.
3789// 3. aievec.cast acc32 -> accfloat is a register bit-alias on AIE2P
3790// (the same physical accumulator register).
3791// 4. accfloat subtract of the magic constant.
3792// 5. SRS accfloat -> bf16.
3793//
3794// Lane width 16 or 32 maps directly to the native AIE2P 256/1024-bit
3795// accumulator. 64 splits into two 32-wide halves and concats the results.
3797 : OpConversionPattern<arith::SIToFPOp> {
3798 using OpConversionPattern<arith::SIToFPOp>::OpConversionPattern;
3799
3800 static Value emitMagicSiToFp(ConversionPatternRewriter &rewriter,
3801 Location loc, Value srcI16, unsigned lanes) {
3802 Type i16Ty = rewriter.getI16Type();
3803 Type i32Ty = rewriter.getI32Type();
3804 Type bf16Ty = rewriter.getBF16Type();
3805 Type f32Ty = rewriter.getF32Type();
3806 VectorType vecI16 = VectorType::get({lanes}, i16Ty);
3807 VectorType vecI32 = VectorType::get({lanes}, i32Ty);
3808 VectorType vecBF16 = VectorType::get({lanes}, bf16Ty);
3809 VectorType vecF32 = VectorType::get({lanes}, f32Ty);
3810
3811 DenseElementsAttr magicI16Attr = DenseElementsAttr::get(
3812 vecI16, rewriter.getI16IntegerAttr(static_cast<int16_t>(0x4b01)));
3813 Value magicI16Vec =
3814 arith::ConstantOp::create(rewriter, loc, vecI16, magicI16Attr);
3815 Value magicBF16Vec =
3816 vector::BitCastOp::create(rewriter, loc, vecBF16, magicI16Vec);
3817 Value magicAccF =
3818 aievec::UPSOp::create(rewriter, loc, vecF32, magicBF16Vec);
3819 Value magicAccI = aievec::CastOp::create(rewriter, loc, vecI32, magicAccF,
3820 /*isResAcc=*/true);
3821 Value vAccI = aievec::UPSOp::create(rewriter, loc, vecI32, srcI16);
3822 Value vSumI = arith::AddIOp::create(rewriter, loc, vAccI, magicAccI);
3823 Value vSumAccF = aievec::CastOp::create(rewriter, loc, vecF32, vSumI,
3824 /*isResAcc=*/true);
3825 Value vDiffAccF =
3826 aievec::SubElemOp::create(rewriter, loc, vecF32, vSumAccF, magicAccF);
3827 Value shiftCst =
3828 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
3829 return aievec::SRSOp::create(rewriter, loc, vecBF16, vDiffAccF, shiftCst)
3830 .getResult();
3831 }
3832
3833 LogicalResult
3834 matchAndRewrite(arith::SIToFPOp op, OpAdaptor adaptor,
3835 ConversionPatternRewriter &rewriter) const override {
3836 auto srcTy = dyn_cast<VectorType>(adaptor.getIn().getType());
3837 auto dstTy = dyn_cast<VectorType>(op.getType());
3838 if (!srcTy || !dstTy || srcTy.getRank() != 1 || dstTy.getRank() != 1)
3839 return failure();
3840 if (!srcTy.getElementType().isInteger(16))
3841 return failure();
3842 if (!dstTy.getElementType().isBF16())
3843 return failure();
3844 unsigned lanes = srcTy.getNumElements();
3845 if (lanes != 16 && lanes != 32 && lanes != 64)
3846 return failure();
3847
3848 Location loc = op.getLoc();
3849 if (lanes == 16 || lanes == 32) {
3850 Value out = emitMagicSiToFp(rewriter, loc, adaptor.getIn(), lanes);
3851 rewriter.replaceOp(op, out);
3852 return success();
3853 }
3854
3855 // 64-wide: split into two 32-wide halves via vector.shuffle
3856 // (vector.extract_strided_slice is illegal in this conversion target).
3857 SmallVector<int64_t> loMask, hiMask, catMask;
3858 for (int64_t i = 0; i < 32; ++i)
3859 loMask.push_back(i);
3860 for (int64_t i = 32; i < 64; ++i)
3861 hiMask.push_back(i);
3862 for (int64_t i = 0; i < 64; ++i)
3863 catMask.push_back(i);
3864 Value lo = vector::ShuffleOp::create(rewriter, loc, adaptor.getIn(),
3865 adaptor.getIn(), loMask)
3866 .getResult();
3867 Value hi = vector::ShuffleOp::create(rewriter, loc, adaptor.getIn(),
3868 adaptor.getIn(), hiMask)
3869 .getResult();
3870 Value loBF16 = emitMagicSiToFp(rewriter, loc, lo, 32);
3871 Value hiBF16 = emitMagicSiToFp(rewriter, loc, hi, 32);
3872 Value cat =
3873 vector::ShuffleOp::create(rewriter, loc, loBF16, hiBF16, catMask)
3874 .getResult();
3875 rewriter.replaceOp(op, cat);
3876 return success();
3877 }
3878};
3879
3880// Match `arith.extui (vector.bitcast %x : vector<Mxi8> to vector<2Mxi4>)
3881// to vector<2Mxi8>` and replace with `aievec.unpack %x` directly on the
3882// byte-packed source. The vector<...xi4> intermediate never reaches Peano
3883// llc, which cannot legalize llvm.bitcast on i4 vectors.
3884//
3885// This recognises the canonical standard-MLIR encoding of an AWQ int4
3886// unpack: byte load of packed nibbles -> bitcast to i4 vector -> extui to
3887// i8 vector. The aievec.unpack op then lowers to the AIE2P intrinsic
3888// llvm.aie2p.unpack.I512.I8.I4 (or the I1024 variant) directly.
3890 : OpConversionPattern<arith::ExtUIOp> {
3891 using OpConversionPattern<arith::ExtUIOp>::OpConversionPattern;
3892
3893 LogicalResult
3894 matchAndRewrite(arith::ExtUIOp extOp, OpAdaptor adaptor,
3895 ConversionPatternRewriter &rewriter) const override {
3896 auto srcTy = dyn_cast<VectorType>(adaptor.getIn().getType());
3897 auto dstTy = dyn_cast<VectorType>(extOp.getType());
3898 if (!srcTy || !dstTy)
3899 return failure();
3900 if (srcTy.getRank() != 1 || dstTy.getRank() != 1)
3901 return failure();
3902 if (!srcTy.getElementType().isInteger(4) ||
3903 !dstTy.getElementType().isInteger(8))
3904 return failure();
3905 unsigned lanes = srcTy.getNumElements();
3906 if (lanes != dstTy.getNumElements())
3907 return failure();
3908 if (lanes != 64 && lanes != 128)
3909 return failure();
3910
3911 auto bitcastOp = adaptor.getIn().getDefiningOp<vector::BitCastOp>();
3912 if (!bitcastOp)
3913 return failure();
3914 auto bitcastSrcTy = dyn_cast<VectorType>(bitcastOp.getSource().getType());
3915 if (!bitcastSrcTy || bitcastSrcTy.getRank() != 1 ||
3916 !bitcastSrcTy.getElementType().isInteger(8))
3917 return failure();
3918 if (bitcastSrcTy.getNumElements() * 2 != lanes)
3919 return failure();
3920
3921 rewriter.replaceOpWithNewOp<aievec::UnpackOp>(extOp, dstTy,
3922 bitcastOp.getSource());
3923 return success();
3924 }
3925};
3926
3927// Scalarize a vector arith int<->fp conversion (sitofp / uitofp / fptosi /
3928// fptoui). The AIE2/AIE2P GISel legalizers only handle the scalar form of
3929// these conversions (libcalls); there is no vector int<->fp instruction.
3930// Vector forms reach the legalizer as G_SITOFP/G_FPTOSI on a vector type
3931// and abort with "unable to legalize". Lower the vector form here into
3932// per-lane extract -> scalar conversion -> insert so each lane goes through
3933// the supported scalar path.
3934template <typename SrcOpTy>
3935struct ScalarizeVectorIntFpConversionOpPattern : OpConversionPattern<SrcOpTy> {
3936 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
3937 using OpAdaptor = typename SrcOpTy::Adaptor;
3938
3939 LogicalResult
3940 matchAndRewrite(SrcOpTy op, OpAdaptor adaptor,
3941 ConversionPatternRewriter &rewriter) const override {
3942 auto srcType = dyn_cast<VectorType>(adaptor.getIn().getType());
3943 auto dstType = dyn_cast<VectorType>(op.getType());
3944 if (!srcType || !dstType)
3945 return failure(); // already scalar — let the standard path handle it
3946 if (srcType.getRank() != 1 || dstType.getRank() != 1)
3947 return failure(); // conservative: 1-D vectors only
3948 if (srcType.getNumElements() != dstType.getNumElements())
3949 return failure();
3950
3951 Location loc = op.getLoc();
3952 Type scalarDstTy = dstType.getElementType();
3953 int64_t lanes = srcType.getNumElements();
3954
3955 // Seed result with a zero vector of the destination type.
3956 Value result = arith::ConstantOp::create(rewriter, loc, dstType,
3957 rewriter.getZeroAttr(dstType));
3958
3959 for (int64_t i = 0; i < lanes; ++i) {
3960 Value scalar = vector::ExtractOp::create(rewriter, loc, adaptor.getIn(),
3961 ArrayRef<int64_t>{i});
3962 Value cvt = SrcOpTy::create(rewriter, loc, scalarDstTy, scalar);
3963 result = vector::InsertOp::create(rewriter, loc, cvt, result,
3964 ArrayRef<int64_t>{i});
3965 }
3966
3967 rewriter.replaceOp(op, result);
3968 return success();
3969 }
3970};
3971
3980
3981template <typename SrcOpTy>
3982struct LowerTruncOpPattern : OpConversionPattern<SrcOpTy> {
3983 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
3984 using OpAdaptor = typename SrcOpTy::Adaptor;
3985
3986 LogicalResult
3987 matchAndRewrite(SrcOpTy truncOp, OpAdaptor adaptor,
3988 ConversionPatternRewriter &rewriter) const override {
3989 VectorType srcType = cast<VectorType>(truncOp.getIn().getType());
3990 VectorType dstType = cast<VectorType>(truncOp.getOut().getType());
3991 Type scalarType = srcType.getElementType();
3992 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
3993 auto accType =
3994 isa<IntegerType>(scalarType) && (elWidth == 32 || elWidth == 64)
3995 ? srcType
3996 : getVectorOpDestType(srcType, /*AIE2 =*/true);
3997
3998 auto shiftParamOp = arith::ConstantOp::create(
3999 rewriter, truncOp.getLoc(), rewriter.getI32IntegerAttr(0));
4000 if (elWidth == 16) {
4001 auto upsOp = aievec::UPSOp::create(rewriter, truncOp.getLoc(), accType,
4002 truncOp.getIn());
4003 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
4004 truncOp, dstType, upsOp.getResult(), shiftParamOp.getResult());
4005 } else {
4006 auto castOp = aievec::CastOp::create(rewriter, truncOp.getLoc(), accType,
4007 truncOp.getIn(), true);
4008 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
4009 truncOp, dstType, castOp.getResult(), shiftParamOp.getResult());
4010 }
4011
4012 return success();
4013 }
4014};
4015
4018
4019// If `op` is the last operation in the sequence:
4020// %0 = unrealized_conversion_cast <%IN> : <native type>, !emitc.opaque_type
4021// %1 = emitc.call_opaque <funcName>, %0...
4022// %2 = unrealized_conversion_cast %1 : !emitc.opaque_type, <native type>
4023// return the value <%IN>.
4024static std::optional<Value>
4025getUnOpaquedOperandOfEmitCOpaqueCallOp(Operation *op, StringRef funcName) {
4026 auto uccOp = dyn_cast<UnrealizedConversionCastOp>(op);
4027 if (!uccOp)
4028 return {};
4029
4030 auto inVal = uccOp.getInputs()[0];
4031 if (!isa<emitc::OpaqueType>(inVal.getType()))
4032 return {};
4033
4034 auto callOp = inVal.getDefiningOp<emitc::CallOpaqueOp>();
4035 if (callOp.getCallee() != funcName)
4036 return {};
4037
4038 auto callOperandsUccOp =
4039 callOp.getOperands()[0].getDefiningOp<UnrealizedConversionCastOp>();
4040 if (!callOperandsUccOp)
4041 return {};
4042
4043 return callOperandsUccOp.getInputs()[0];
4044}
4045
4046// Check there is an operation chain like-
4047//
4048// %cst_0 = arith.constant dense<1.000000e+00> : vector<16xbf16>
4049// %cst_1 = arith.constant 0.000000e+00 : bf16
4050// %0 = vector.transfer_read %arg0[%arg2], %cst_1 : memref<1024xbf16>,
4051// vector<16xbf16>
4052// %1 = arith.negf %0 : vector<16xbf16>
4053// %2 = math.exp %1 : vector<16xbf16>
4054// %3 = arith.addf %2, %cst_0 : vector<16xbf16>
4055// %4 = arith.divf %cst_0, %3 : vector<16xbf16>
4056//
4057// so that this operation chain can be converted to a function call to compute
4058// sigmoid value for v16bfloat16 and v32bfloat16 types
4059template <typename DivFOpTy>
4060static bool hasSigmoidComputationChain(DivFOpTy divfOp, arith::NegFOp &negOp) {
4061 auto *lhsDefOp = divfOp.getLhs().getDefiningOp();
4062 if (!lhsDefOp)
4063 return false;
4064 auto constOp = dyn_cast<arith::ConstantOp>(lhsDefOp);
4065 if (!constOp)
4066 return false;
4067
4068 auto cstDense = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
4069 if (!cstDense)
4070 return false;
4071
4072 if (cstDense.template getSplatValue<APFloat>().convertToFloat() != 1.0f)
4073 return false;
4074
4075 Operation *addLvalOp;
4076 Operation *addRvalOp;
4077 // divfOp's rval could be an arith::AddFOp or the pattern like-
4078 // %1 = aievec.ups %a
4079 // %2 = aievec.ups %b;
4080 // %3 = aievec.add_elem %1, %2
4081 // %4 = aievec.srs %3;
4082 auto *rhsDefOp = divfOp.getRhs().getDefiningOp();
4083 if (!rhsDefOp)
4084 return false;
4085 auto addOp = dyn_cast<arith::AddFOp>(rhsDefOp);
4086 if (!addOp) {
4087 auto srsOp = dyn_cast<aievec::SRSOp>(rhsDefOp);
4088 if (!srsOp)
4089 return false;
4090
4091 auto addElemOp =
4092 dyn_cast<aievec::AddElemOp>(srsOp.getSource().getDefiningOp());
4093 if (!addElemOp)
4094 return false;
4095
4096 auto lUpsOp = dyn_cast<aievec::UPSOp>(addElemOp.getLhs().getDefiningOp());
4097 auto rUpsOp = dyn_cast<aievec::UPSOp>(addElemOp.getRhs().getDefiningOp());
4098 if (!lUpsOp || !rUpsOp)
4099 return false;
4100
4101 addLvalOp = lUpsOp.getSource().getDefiningOp();
4102 addRvalOp = rUpsOp.getSource().getDefiningOp();
4103 // One of add operation's operand is a constant op and another operand could
4104 // be arith::ExpOp or the combination of emitc.call and aievec.srs
4105 auto addDefOp = isa<arith::ConstantOp>(addLvalOp)
4106 ? dyn_cast<aievec::SRSOp>(addRvalOp)
4107 : dyn_cast<aievec::SRSOp>(addLvalOp);
4108 if (!addDefOp)
4109 addLvalOp = isa<arith::ConstantOp>(addLvalOp)
4110 ? dyn_cast<math::ExpOp>(addRvalOp)
4111 : dyn_cast<math::ExpOp>(addLvalOp);
4112 else
4113 addLvalOp = addDefOp.getSource().getDefiningOp();
4114
4115 addRvalOp = isa<arith::ConstantOp>(addLvalOp)
4116 ? lUpsOp.getSource().getDefiningOp()
4117 : rUpsOp.getSource().getDefiningOp();
4118 } else {
4119 addLvalOp = addOp.getLhs().getDefiningOp();
4120 addRvalOp = addOp.getRhs().getDefiningOp();
4121 }
4122
4123 if (!addLvalOp || !addRvalOp)
4124 return false;
4125
4126 auto addLvalExpOp = dyn_cast<math::ExpOp>(addLvalOp);
4127 auto addRvalExpOp = dyn_cast<math::ExpOp>(addRvalOp);
4128 auto addLvalExpOpIn =
4129 getUnOpaquedOperandOfEmitCOpaqueCallOp(addLvalOp, "getExpBf16")
4130 .value_or(nullptr);
4131 auto addRvalExpOpIn =
4132 getUnOpaquedOperandOfEmitCOpaqueCallOp(addRvalOp, "getExpBf16")
4133 .value_or(nullptr);
4134 if (!addLvalExpOpIn && addLvalExpOp)
4135 addLvalExpOpIn = addLvalExpOp.getOperand();
4136 if (!addRvalExpOpIn && addRvalExpOp)
4137 addRvalExpOpIn = addRvalExpOp.getOperand();
4138
4139 if (!((addLvalExpOpIn && isa<arith::ConstantOp>(addRvalOp)) ||
4140 (addRvalExpOpIn && isa<arith::ConstantOp>(addLvalOp))))
4141 return false;
4142
4143 constOp = isa<arith::ConstantOp>(addLvalOp)
4144 ? cast<arith::ConstantOp>(addLvalOp)
4145 : cast<arith::ConstantOp>(addRvalOp);
4146
4147 cstDense = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
4148 if (!cstDense)
4149 return false;
4150 if (cstDense.template getSplatValue<APFloat>().convertToFloat() != 1.0f)
4151 return false;
4152
4153 auto expOperand = addLvalExpOpIn ? addLvalExpOpIn : addRvalExpOpIn;
4154
4155 negOp = expOperand.getDefiningOp<arith::NegFOp>();
4156
4157 return negOp != nullptr;
4158}
4159
4160// Convert the operation chain like-
4161//
4162// %cst_0 = arith.constant dense<1.000000e+00> : vector<16xbf16>
4163// %cst_1 = arith.constant 0.000000e+00 : bf16
4164// %0 = vector.transfer_read %arg0[%arg2], %cst_1 : memref<1024xbf16>,
4165// vector<16xbf16>
4166// %1 = arith.negf %0 : vector<16xbf16>
4167// %2 = math.exp %1 :vector<16xbf16>
4168// %3 = arith.addf %2, %cst_0 : vector<16xbf16>
4169// %4 = arith.divf %cst_0, %3 : vector<16xbf16>
4170//
4171// to a function call to compute sigmoid value for v16bfloat16 and
4172// v32bfloat16 types
4173struct ComputeSigmoidOpPattern : OpConversionPattern<arith::DivFOp> {
4174 using OpConversionPattern::OpConversionPattern;
4175
4176 LogicalResult
4177 matchAndRewrite(arith::DivFOp divfOp, OpAdaptor adaptor,
4178 ConversionPatternRewriter &rewriter) const override {
4179 auto srcType = dyn_cast<VectorType>(adaptor.getLhs().getType());
4180 if (!srcType)
4181 return failure();
4182
4183 Type scalarType = srcType.getElementType();
4184 if (!isa<FloatType>(scalarType))
4185 return failure();
4186
4187 unsigned laneSize = getVectorLaneSize(srcType);
4188 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4189 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
4190 return failure();
4191
4192 arith::NegFOp negOp = nullptr;
4193 if (!hasSigmoidComputationChain(adaptor, negOp))
4194 return failure();
4195
4196 StringRef includeName = "vec_math.h";
4197 auto moduleOp = divfOp->getParentOfType<mlir::ModuleOp>();
4198 rewriter.setInsertionPointToStart(
4199 &moduleOp.getRegion().getBlocks().front());
4200 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
4201
4202 rewriter.setInsertionPoint(divfOp);
4203 Type vecOpaqueTy;
4204 if (laneSize == 16)
4205 vecOpaqueTy =
4206 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
4207 else
4208 vecOpaqueTy =
4209 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
4210 auto opaquedOperand =
4211 UnrealizedConversionCastOp::create(rewriter, divfOp.getLoc(),
4212 vecOpaqueTy, negOp.getOperand())
4213 .getResult(0);
4214 SmallVector<Value> sigmoidOperands = {opaquedOperand};
4215 auto callOp = emitc::CallOpaqueOp::create(
4216 rewriter, divfOp.getLoc(), TypeRange{vecOpaqueTy}, "getSigmoidBf16",
4217 nullptr, nullptr, sigmoidOperands);
4218 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
4219 divfOp, TypeRange{adaptor.getLhs().getType()}, callOp.getResults());
4220
4221 return success();
4222 }
4223};
4224
4225// Convert math.ceil to a function call to compute ceil(x) for v16bfloat16
4226struct ComputeCeilOpPattern : OpConversionPattern<math::CeilOp> {
4227 using OpConversionPattern::OpConversionPattern;
4228
4229 LogicalResult
4230 matchAndRewrite(math::CeilOp ceilOp, OpAdaptor adaptor,
4231 ConversionPatternRewriter &rewriter) const override {
4232 auto srcType = dyn_cast<VectorType>(ceilOp.getOperand().getType());
4233 if (!srcType)
4234 return failure();
4235
4236 Type scalarType = srcType.getElementType();
4237 if (!isa<FloatType>(scalarType))
4238 return failure();
4239
4240 unsigned laneSize = getVectorLaneSize(srcType);
4241 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4242 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
4243 return failure();
4244
4245 StringRef includeName = "vec_math.h";
4246 auto moduleOp = ceilOp->getParentOfType<mlir::ModuleOp>();
4247 rewriter.setInsertionPointToStart(
4248 &moduleOp.getRegion().getBlocks().front());
4249 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
4250
4251 rewriter.setInsertionPoint(ceilOp);
4252 Type vecOpaqueTy;
4253 if (laneSize == 16)
4254 vecOpaqueTy =
4255 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
4256 else
4257 vecOpaqueTy =
4258 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
4259 auto opaquedOperand =
4260 UnrealizedConversionCastOp::create(rewriter, ceilOp.getLoc(),
4261 vecOpaqueTy, adaptor.getOperand())
4262 .getResult(0);
4263 SmallVector<Value> ceilOperands = {opaquedOperand};
4264 auto callOp = emitc::CallOpaqueOp::create(
4265 rewriter, ceilOp.getLoc(), TypeRange{vecOpaqueTy}, "getCeilBf16",
4266 nullptr, nullptr, ceilOperands);
4267 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
4268 ceilOp, TypeRange{ceilOp.getResult().getType()}, callOp.getResults());
4269
4270 return success();
4271 }
4272};
4273
4274// Convert math.floor to a function call to compute floor(x) for v16bfloat16
4275struct ComputeFloorOpPattern : OpConversionPattern<math::FloorOp> {
4276 using OpConversionPattern::OpConversionPattern;
4277
4278 LogicalResult
4279 matchAndRewrite(math::FloorOp floorOp, OpAdaptor adaptor,
4280 ConversionPatternRewriter &rewriter) const override {
4281 auto srcType = dyn_cast<VectorType>(floorOp.getOperand().getType());
4282 if (!srcType)
4283 return failure();
4284
4285 Type scalarType = srcType.getElementType();
4286 if (!isa<FloatType>(scalarType))
4287 return failure();
4288
4289 unsigned laneSize = getVectorLaneSize(srcType);
4290 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4291 if (elWidth != 16 || (laneSize != 16 && laneSize != 32))
4292 return failure();
4293
4294 StringRef includeName = "vec_math.h";
4295 auto moduleOp = floorOp->getParentOfType<mlir::ModuleOp>();
4296 rewriter.setInsertionPointToStart(
4297 &moduleOp.getRegion().getBlocks().front());
4298 emitc::IncludeOp::create(rewriter, moduleOp.getLoc(), includeName, false);
4299
4300 rewriter.setInsertionPoint(floorOp);
4301 Type vecOpaqueTy;
4302 if (laneSize == 16)
4303 vecOpaqueTy =
4304 emitc::OpaqueType::get(rewriter.getContext(), "v16bfloat16");
4305 else
4306 vecOpaqueTy =
4307 emitc::OpaqueType::get(rewriter.getContext(), "v32bfloat16");
4308 auto opaquedOperand =
4309 UnrealizedConversionCastOp::create(rewriter, floorOp.getLoc(),
4310 vecOpaqueTy, adaptor.getOperand())
4311 .getResult(0);
4312 SmallVector<Value> floorOperands = {opaquedOperand};
4313 auto callOp = emitc::CallOpaqueOp::create(
4314 rewriter, floorOp.getLoc(), TypeRange{vecOpaqueTy}, "getFloorBf16",
4315 nullptr, nullptr, floorOperands);
4316 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
4317 floorOp, TypeRange{floorOp.getResult().getType()}, callOp.getResults());
4318
4319 return success();
4320 }
4321};
4322
4323// Convert arith.negf to aievec.neg to negate the vector for v16bfloat16 and
4324// v16float types, and for v32 of either on AIE2P.
4325struct ComputeNegOpPattern : OpConversionPattern<arith::NegFOp> {
4326 using OpConversionPattern::OpConversionPattern;
4327
4328 LogicalResult
4329 matchAndRewrite(arith::NegFOp negOp, OpAdaptor adaptor,
4330 ConversionPatternRewriter &rewriter) const override {
4331 auto srcType = dyn_cast<VectorType>(negOp.getOperand().getType());
4332 if (!srcType)
4333 return failure();
4334
4335 Type scalarType = srcType.getElementType();
4336 if (!isa<FloatType>(scalarType))
4337 return failure();
4338
4339 unsigned laneSize = getVectorLaneSize(srcType);
4340 if (laneSize != 16 && laneSize != 32)
4341 return failure();
4342
4343 Location loc = negOp.getLoc();
4344 auto accType = getVectorOpDestType(srcType, /*AIE2 =*/true);
4345
4346 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4347 if (elWidth == 16) {
4348 auto upsOp =
4349 aievec::UPSOp::create(rewriter, loc, accType, adaptor.getOperand());
4350 auto aieNegOp =
4351 aievec::NegOp::create(rewriter, loc, accType, upsOp.getResult());
4352 auto shiftParamOp = arith::ConstantOp::create(
4353 rewriter, negOp.getLoc(), rewriter.getI32IntegerAttr(0));
4354 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
4355 negOp, srcType, aieNegOp.getResult(), shiftParamOp.getResult());
4356 } else {
4357 auto castOp = aievec::CastOp::create(
4358 rewriter, loc, accType, adaptor.getOperand(), /*isResAcc*/ true);
4359 auto aieNegOp =
4360 aievec::NegOp::create(rewriter, loc, accType, castOp.getResult());
4361 rewriter.replaceOpWithNewOp<aievec::CastOp>(
4362 negOp, srcType, aieNegOp.getResult(), /*isResAcc*/ false);
4363 }
4364
4365 return success();
4366 }
4367};
4368
4369// Check whether the value of constant operation is int type and the dense value
4370// is -1.
4371static bool hasConstNegOneValue(arith::ConstantOp constOp, unsigned elWidth) {
4372 if (!constOp)
4373 return false;
4374
4375 auto cstDense = dyn_cast<DenseIntElementsAttr>(constOp.getValue());
4376 if (!cstDense)
4377 return false;
4378
4379 if (elWidth == 32)
4380 return cstDense.getSplatValue<int32_t>() == -1;
4381 if (elWidth == 16)
4382 return cstDense.getSplatValue<int16_t>() == -1;
4383 if (elWidth == 8)
4384 return cstDense.getSplatValue<int8_t>() == -1;
4385 return false;
4386}
4387
4388// Convert arith.xori to aievec.bxor to compute bitwise xor of two vectors for
4389// integer types
4390struct ComputeBxorAndBnegOpPattern : OpConversionPattern<arith::XOrIOp> {
4391 using OpConversionPattern::OpConversionPattern;
4392
4393 LogicalResult
4394 matchAndRewrite(arith::XOrIOp xorOp, OpAdaptor adaptor,
4395 ConversionPatternRewriter &rewriter) const override {
4396 auto srcType = dyn_cast<VectorType>(xorOp.getLhs().getType());
4397 if (!srcType)
4398 return failure();
4399
4400 Type scalarType = srcType.getElementType();
4401 if (!isa<IntegerType>(scalarType))
4402 return failure();
4403
4404 unsigned laneSize = getVectorLaneSize(srcType);
4405 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4406 if (laneSize * elWidth != 512)
4407 return failure();
4408
4409 auto lhsConstOp = xorOp.getLhs().getDefiningOp<arith::ConstantOp>();
4410 auto rhsConstOp = xorOp.getRhs().getDefiningOp<arith::ConstantOp>();
4411
4412 // If one of operands in xorOp is a constant -1, xorOp will be replaced with
4413 // aievec::BnegOp.
4414 if ((lhsConstOp && hasConstNegOneValue(lhsConstOp, elWidth)) ||
4415 (rhsConstOp && hasConstNegOneValue(rhsConstOp, elWidth))) {
4416 Value val = hasConstNegOneValue(lhsConstOp, elWidth) ? adaptor.getRhs()
4417 : adaptor.getLhs();
4418 rewriter.replaceOpWithNewOp<aievec::BnegOp>(xorOp, srcType, val);
4419 } else
4420 rewriter.replaceOpWithNewOp<aievec::BxorOp>(
4421 xorOp, srcType, adaptor.getLhs(), adaptor.getRhs());
4422
4423 return success();
4424 }
4425};
4426
4427template <typename SrcOpTy, typename DstOpTy>
4428struct ComputeBandAndBorOpPattern : OpConversionPattern<SrcOpTy> {
4429 using OpConversionPattern<SrcOpTy>::OpConversionPattern;
4430 using OpAdaptor = typename SrcOpTy::Adaptor;
4431
4432 LogicalResult
4433 matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor,
4434 ConversionPatternRewriter &rewriter) const override {
4435 VectorType srcType = dyn_cast<VectorType>(srcOp.getLhs().getType());
4436 if (!srcType)
4437 return failure();
4438
4439 Type scalarType = srcType.getElementType();
4440 if (!isa<IntegerType>(scalarType))
4441 return failure();
4442
4443 unsigned laneSize = getVectorLaneSize(srcType);
4444 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4445 if (laneSize * elWidth != 512)
4446 return failure();
4447
4448 rewriter.replaceOpWithNewOp<DstOpTy>(srcOp, srcOp.getResult().getType(),
4449 adaptor.getLhs(), adaptor.getRhs());
4450
4451 return success();
4452 }
4453};
4454
4459
4460// Convert arith.shrsi to a combination of aievec.ups and aievec.srs to compute
4461// arithmetic right shift for integer types. Currently, only support the shift
4462// value with a broadcast vector.
4464 : OpConversionPattern<arith::ShRSIOp> {
4465 using OpConversionPattern::OpConversionPattern;
4466
4467 LogicalResult
4468 matchAndRewrite(arith::ShRSIOp rsOp, OpAdaptor adaptor,
4469 ConversionPatternRewriter &rewriter) const override {
4470 auto srcType = dyn_cast<VectorType>(adaptor.getLhs().getType());
4471 if (!srcType)
4472 return failure();
4473
4474 Type scalarType = srcType.getElementType();
4475 unsigned laneSize = getVectorLaneSize(srcType);
4476 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
4477 if (laneSize * elWidth != 512)
4478 return failure();
4479
4480 auto bcastOp =
4481 dyn_cast<aievec::BroadcastOp>(adaptor.getRhs().getDefiningOp());
4482 if (!bcastOp)
4483 return failure();
4484
4485 auto constOp =
4486 arith::ConstantOp::create(rewriter, bcastOp.getLoc(),
4487 rewriter.getI32IntegerAttr(bcastOp.getIdx()));
4488 auto extElemOp = aievec::ExtElemOp::create(
4489 rewriter, bcastOp.getLoc(), scalarType, bcastOp, constOp.getResult());
4490 Location loc = rsOp.getLoc();
4491
4492 // The vector with v64int8 type can be divided into two v32int8 vectors and
4493 // be processed individually and be concatenated at the end.
4494 if (elWidth == 8) {
4495 VectorType halfSrcType = createVectorType(laneSize / 2, scalarType);
4496 auto rsOpLow = aievec::ExtOp::create(rewriter, loc, halfSrcType,
4497 adaptor.getLhs(), 0);
4498 auto rsOpHigh = aievec::ExtOp::create(rewriter, loc, halfSrcType,
4499 adaptor.getLhs(), 1);
4500 Type accType = getVectorOpDestType(halfSrcType, /*AIE2 =*/true);
4501 auto upsOpLow =
4502 aievec::UPSOp::create(rewriter, loc, accType, rsOpLow.getResult());
4503 auto srsOpLow =
4504 aievec::SRSOp::create(rewriter, loc, halfSrcType,
4505 upsOpLow.getResult(), extElemOp.getResult());
4506 auto upsOpHigh =
4507 aievec::UPSOp::create(rewriter, loc, accType, rsOpHigh.getResult());
4508 auto srsOpHigh =
4509 aievec::SRSOp::create(rewriter, loc, halfSrcType,
4510 upsOpHigh.getResult(), extElemOp.getResult());
4511 SmallVector<Value> inputSources = {srsOpLow.getResult(),
4512 srsOpHigh.getResult()};
4513 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(rsOp, srcType,
4514 inputSources);
4515 } else {
4516 Type accType = getVectorOpDestType(srcType, /*AIE2 =*/true);
4517 auto upsOp =
4518 aievec::UPSOp::create(rewriter, loc, accType, adaptor.getLhs());
4519 rewriter.replaceOpWithNewOp<aievec::SRSOp>(
4520 rsOp, srcType, upsOp.getResult(), extElemOp.getResult());
4521 }
4522
4523 return success();
4524 }
4525};
4526
4527// Recognize the compound shift+clamp+truncate pattern and lower it to
4528// aievec.ups + aievec.srs. This maps the standard quantized neural network
4529// "shift-round-saturate + clamp" idiom to the AIE2 SRS hardware unit.
4530//
4531// Pattern A (with clamp):
4532// %shifted = arith.shrsi %wide, %shift_splat
4533// %clamped0 = arith.maxsi %shifted, %c_lo
4534// %clamped = arith.minsi %clamped0, %c_hi
4535// %result = arith.trunci %clamped
4536//
4537// Pattern B (without clamp):
4538// %shifted = arith.shrsi %wide, %shift_splat
4539// %result = arith.trunci %shifted
4540//
4541// Both lower to: aievec.ups + aievec.srs
4542struct ShiftClampTruncToSRSPattern : OpConversionPattern<arith::TruncIOp> {
4543 using OpConversionPattern::OpConversionPattern;
4544
4545 ShiftClampTruncToSRSPattern(MLIRContext *context, PatternBenefit benefit = 2)
4546 : OpConversionPattern(context, benefit) {}
4547
4548 // Try to extract a scalar integer splat value from a Value.
4549 // Returns std::nullopt if the value is not a constant splat.
4550 static std::optional<int64_t> getConstantSplatValue(Value val) {
4551 auto defOp = val.getDefiningOp<arith::ConstantOp>();
4552 if (!defOp)
4553 return std::nullopt;
4554 auto denseAttr = dyn_cast<DenseIntElementsAttr>(defOp.getValue());
4555 if (!denseAttr || !denseAttr.isSplat())
4556 return std::nullopt;
4557 return denseAttr.getSplatValue<APInt>().getSExtValue();
4558 }
4559
4560 // Try to extract the shift amount from the right operand of shrsi.
4561 // Accepts either a constant splat vector or an aievec.broadcast of a
4562 // constant.
4563 static std::optional<Value>
4564 getShiftValue(Value rhs, ConversionPatternRewriter &rewriter, Location loc) {
4565 // Case 1: constant splat vector
4566 if (auto constOp = rhs.getDefiningOp<arith::ConstantOp>()) {
4567 auto denseAttr = dyn_cast<DenseIntElementsAttr>(constOp.getValue());
4568 if (denseAttr && denseAttr.isSplat()) {
4569 int64_t shiftVal = denseAttr.getSplatValue<APInt>().getSExtValue();
4570 return arith::ConstantOp::create(rewriter, loc,
4571 rewriter.getI32IntegerAttr(shiftVal))
4572 .getResult();
4573 }
4574 }
4575 // Case 2: aievec.broadcast
4576 if (auto bcastOp = dyn_cast<aievec::BroadcastOp>(rhs.getDefiningOp())) {
4577 auto constOp = arith::ConstantOp::create(
4578 rewriter, bcastOp.getLoc(),
4579 rewriter.getI32IntegerAttr(bcastOp.getIdx()));
4580 return aievec::ExtElemOp::create(rewriter, bcastOp.getLoc(),
4581 rewriter.getI32Type(), bcastOp,
4582 constOp.getResult())
4583 .getResult();
4584 }
4585 return std::nullopt;
4586 }
4587
4588 LogicalResult
4589 matchAndRewrite(arith::TruncIOp truncOp, OpAdaptor adaptor,
4590 ConversionPatternRewriter &rewriter) const override {
4591 auto dstType = dyn_cast<VectorType>(truncOp.getOut().getType());
4592 if (!dstType)
4593 return failure();
4594
4595 Type dstScalarType = dstType.getElementType();
4596 if (!isa<IntegerType>(dstScalarType))
4597 return failure();
4598
4599 // Walk backward through optional clamp chain
4600 Value source = adaptor.getIn();
4601 int32_t sign = 1; // default: signed
4602
4603 // Check for minsi(maxsi(...), hi) or maxsi(minsi(...), lo) clamp pattern
4604 arith::MinSIOp minOp = nullptr;
4605 arith::MaxSIOp maxOp = nullptr;
4606
4607 if (auto minsiOp = source.getDefiningOp<arith::MinSIOp>()) {
4608 if (auto maxsiOp = minsiOp.getLhs().getDefiningOp<arith::MaxSIOp>()) {
4609 minOp = minsiOp;
4610 maxOp = maxsiOp;
4611 source = maxOp.getLhs();
4612 }
4613 } else if (auto maxsiOp = source.getDefiningOp<arith::MaxSIOp>()) {
4614 if (auto minsiOp = maxsiOp.getLhs().getDefiningOp<arith::MinSIOp>()) {
4615 maxOp = maxsiOp;
4616 minOp = minsiOp;
4617 source = minOp.getLhs();
4618 }
4619 }
4620
4621 // If we found a clamp, verify it's a valid saturation range
4622 if (minOp && maxOp) {
4623 auto loVal = getConstantSplatValue(maxOp.getRhs());
4624 auto hiVal = getConstantSplatValue(minOp.getRhs());
4625 if (!loVal || !hiVal)
4626 return failure();
4627
4628 unsigned dstBits = dstScalarType.getIntOrFloatBitWidth();
4629 // Guard against UB from shifting into or past the sign bit.
4630 if (dstBits == 0 || dstBits > 63)
4631 return failure();
4632 uint64_t one = 1ULL;
4633 int64_t unsignedLo = 0;
4634 int64_t unsignedHi = static_cast<int64_t>((one << dstBits) - 1);
4635 int64_t signedLo = -static_cast<int64_t>(one << (dstBits - 1));
4636 int64_t signedHi = static_cast<int64_t>((one << (dstBits - 1)) - 1);
4637
4638 if (*loVal == unsignedLo && *hiVal == unsignedHi) {
4639 sign = 0; // unsigned saturation
4640 } else if (*loVal == signedLo && *hiVal == signedHi) {
4641 sign = 1; // signed saturation
4642 } else {
4643 // Clamp range doesn't match standard saturation — don't match
4644 return failure();
4645 }
4646 }
4647
4648 // Now source should be the shrsi result
4649 auto shrsiOp = source.getDefiningOp<arith::ShRSIOp>();
4650 if (!shrsiOp)
4651 return failure();
4652
4653 auto srcType = dyn_cast<VectorType>(shrsiOp.getLhs().getType());
4654 if (!srcType)
4655 return failure();
4656
4657 Type srcScalarType = srcType.getElementType();
4658 if (!isa<IntegerType>(srcScalarType))
4659 return failure();
4660
4661 unsigned srcElWidth = srcScalarType.getIntOrFloatBitWidth();
4662 unsigned dstElWidth = dstScalarType.getIntOrFloatBitWidth();
4663 if (dstElWidth >= srcElWidth)
4664 return failure();
4665
4666 Location loc = truncOp.getLoc();
4667
4668 // Extract the shift amount
4669 auto shiftVal = getShiftValue(shrsiOp.getRhs(), rewriter, loc);
4670 if (!shiftVal)
4671 return failure();
4672
4673 // Get the wide input (pre-shift)
4674 Value wideInput = shrsiOp.getLhs();
4675
4676 unsigned laneSize = getVectorLaneSize(srcType);
4677 bool needsPadding = (laneSize % 16 != 0);
4678
4679 VectorType paddedSrcType = srcType;
4680 VectorType paddedDstType = dstType;
4681
4682 if (needsPadding) {
4683 // Round up to nearest multiple of 16
4684 unsigned paddedLanes = ((laneSize + 15) / 16) * 16;
4685 paddedSrcType = createVectorType(paddedLanes, srcScalarType);
4686 paddedDstType = createVectorType(paddedLanes, dstScalarType);
4687
4688 // Zero-pad the input using insert_strided_slice
4689 auto zeroAttr = rewriter.getZeroAttr(paddedSrcType);
4690 auto zeroPad =
4691 arith::ConstantOp::create(rewriter, loc, zeroAttr).getResult();
4692 SmallVector<int64_t> offsets(1, 0);
4693 SmallVector<int64_t> strides(1, 1);
4694 wideInput = vector::InsertStridedSliceOp::create(
4695 rewriter, loc, wideInput, zeroPad, offsets, strides)
4696 .getResult();
4697 }
4698
4699 // Determine accumulator type and create the accumulator value.
4700 // For i16 source: use UPS to widen to accumulator type.
4701 // For i32/i64 source: use CastOp (marks as accumulator without widening),
4702 // matching the approach in LowerTruncOpPattern.
4703 Type accScalarType = paddedSrcType.getElementType();
4704 unsigned accElWidth = accScalarType.getIntOrFloatBitWidth();
4705 Value accValue;
4706 if (accElWidth == 16) {
4707 Type accType = getVectorOpDestType(paddedSrcType, /*AIE2=*/true);
4708 accValue =
4709 aievec::UPSOp::create(rewriter, loc, accType, wideInput).getResult();
4710 } else {
4711 // For i32/i64: CastOp with isResAcc=true marks as accumulator
4712 accValue = aievec::CastOp::create(rewriter, loc, paddedSrcType, wideInput,
4713 /*isResAcc=*/true)
4714 .getResult();
4715 }
4716 auto srsOp = aievec::SRSOp::create(rewriter, loc, paddedDstType, accValue,
4717 *shiftVal, sign);
4718
4719 Value result = srsOp.getResult();
4720
4721 if (needsPadding) {
4722 // Extract original lanes from the padded result
4723 SmallVector<int64_t> offsets(1, 0);
4724 SmallVector<int64_t> sizes = {static_cast<int64_t>(laneSize)};
4725 SmallVector<int64_t> strides(1, 1);
4726 result = vector::ExtractStridedSliceOp::create(rewriter, loc, result,
4727 offsets, sizes, strides)
4728 .getResult();
4729 }
4730
4731 rewriter.replaceOp(truncOp, result);
4732
4733 // Erase the intermediate clamp/shift ops if they have no other uses.
4734 // These must be cleaned up because shrsi on 512-bit vectors is marked
4735 // illegal and would cause conversion failure if left as dead ops.
4736 SmallVector<Operation *, 3> opsToErase;
4737 if (minOp && minOp->use_empty())
4738 opsToErase.push_back(minOp);
4739 if (maxOp && maxOp->use_empty())
4740 opsToErase.push_back(maxOp);
4741 if (shrsiOp->use_empty())
4742 opsToErase.push_back(shrsiOp);
4743 for (Operation *op : opsToErase)
4744 rewriter.eraseOp(op);
4745
4746 return success();
4747 }
4748};
4749
4750// Promote scalar shrsi + [clamp] + trunci chain to a vectorized SRS sequence.
4751// Anchored on scalar arith::TruncIOp. Fuses the entire chain into:
4752// broadcast_scalar -> cast(isResAcc) -> srs(narrowed, shift, sign) ->
4753// ext_elem
4754// This prevents scalar arith.trunci i32->i8 from reaching the AIE2 backend
4755// where it crashes (the backend only supports 32->16/20/32 truncations).
4756struct LowerScalarShiftClampTruncToSRS : OpConversionPattern<arith::TruncIOp> {
4757 using OpConversionPattern::OpConversionPattern;
4758
4759 // Try to extract a scalar integer constant value from a Value.
4760 static std::optional<int64_t> getScalarConstantValue(Value val) {
4761 auto defOp = val.getDefiningOp<arith::ConstantOp>();
4762 if (!defOp)
4763 return std::nullopt;
4764 auto intAttr = dyn_cast<IntegerAttr>(defOp.getValue());
4765 if (!intAttr)
4766 return std::nullopt;
4767 return intAttr.getInt();
4768 }
4769
4770 LogicalResult
4771 matchAndRewrite(arith::TruncIOp truncOp, OpAdaptor adaptor,
4772 ConversionPatternRewriter &rewriter) const override {
4773 // Only match scalar types (vector compound pattern handles vectors)
4774 Type dstType = truncOp.getOut().getType();
4775 if (isa<VectorType>(dstType))
4776 return failure();
4777
4778 auto dstIntType = dyn_cast<IntegerType>(dstType);
4779 if (!dstIntType)
4780 return failure();
4781
4782 unsigned dstBits = dstIntType.getWidth();
4783 if (dstBits != 8 && dstBits != 16)
4784 return failure();
4785
4786 auto srcIntType = dyn_cast<IntegerType>(truncOp.getIn().getType());
4787 if (!srcIntType || srcIntType.getWidth() != 32)
4788 return failure();
4789
4790 // Walk backward through optional clamp chain
4791 Value source = truncOp.getIn();
4792 int32_t sign = 1; // default: signed
4793
4794 arith::MinSIOp minOp = nullptr;
4795 arith::MaxSIOp maxOp = nullptr;
4796
4797 if (auto minsiOp = source.getDefiningOp<arith::MinSIOp>()) {
4798 if (auto maxsiOp = minsiOp.getLhs().getDefiningOp<arith::MaxSIOp>()) {
4799 minOp = minsiOp;
4800 maxOp = maxsiOp;
4801 source = maxOp.getLhs();
4802 }
4803 } else if (auto maxsiOp = source.getDefiningOp<arith::MaxSIOp>()) {
4804 if (auto minsiOp = maxsiOp.getLhs().getDefiningOp<arith::MinSIOp>()) {
4805 maxOp = maxsiOp;
4806 minOp = minsiOp;
4807 source = minOp.getLhs();
4808 }
4809 }
4810
4811 // If we found a clamp, verify it's a valid saturation range
4812 if (minOp && maxOp) {
4813 auto loVal = getScalarConstantValue(maxOp.getRhs());
4814 auto hiVal = getScalarConstantValue(minOp.getRhs());
4815 if (!loVal || !hiVal)
4816 return failure();
4817
4818 if (dstBits == 0 || dstBits > 63)
4819 return failure();
4820 uint64_t one = 1ULL;
4821 int64_t unsignedLo = 0;
4822 int64_t unsignedHi = static_cast<int64_t>((one << dstBits) - 1);
4823 int64_t signedLo = -static_cast<int64_t>(one << (dstBits - 1));
4824 int64_t signedHi = static_cast<int64_t>((one << (dstBits - 1)) - 1);
4825
4826 if (*loVal == unsignedLo && *hiVal == unsignedHi) {
4827 sign = 0; // unsigned saturation
4828 } else if (*loVal == signedLo && *hiVal == signedHi) {
4829 sign = 1; // signed saturation
4830 } else {
4831 return failure();
4832 }
4833 }
4834
4835 // Now source should be the shrsi result, or any i32 value for clamp-only
4836 Location loc = truncOp.getLoc();
4837 Value preShiftVal;
4838 Value shiftVal;
4839 arith::ShRSIOp shrsiOp = source.getDefiningOp<arith::ShRSIOp>();
4840 if (shrsiOp) {
4841 // Verify shrsi operand types
4842 if (!isa<IntegerType>(shrsiOp.getLhs().getType()))
4843 return failure();
4844 preShiftVal = shrsiOp.getLhs();
4845 shiftVal = shrsiOp.getRhs();
4846 } else {
4847 // No shrsi found: treat as identity shift (shift=0).
4848 // This handles clamp+trunci patterns without a preceding shift,
4849 // e.g., after skip-add with skip_scale=0.
4850 preShiftVal = source;
4851 shiftVal = arith::ConstantOp::create(rewriter, loc,
4852 rewriter.getI32IntegerAttr(0));
4853 }
4854
4855 // Create 512-bit vector type: vector<16xi32>
4856 unsigned srcLanes = 512 / srcIntType.getWidth(); // 16 for i32
4857 VectorType bcastVecType = createVectorType(srcLanes, srcIntType);
4858
4859 // Broadcast pre-shift value to 512-bit vector
4860 auto bcast = aievec::BroadcastScalarOp::create(rewriter, loc, bcastVecType,
4861 preShiftVal);
4862
4863 Value accValue;
4864 VectorType srsOutType;
4865
4866 if (dstBits == 8) {
4867 // i32→i8: The SRS intrinsic I256V32Acc32Srs needs 1024-bit source
4868 // (vector<32xi32>, cast to vector<16xi64> internally) and produces
4869 // vector<32xi8> (256-bit). Concat two broadcast copies to get 1024 bits.
4870 unsigned accLanes = srcLanes * 2; // 32
4871 VectorType accVecType =
4872 createVectorType(accLanes, srcIntType); // vector<32xi32>
4873 auto concatSrc = aievec::ConcatOp::create(
4874 rewriter, loc, accVecType,
4875 SmallVector<Value>({bcast.getResult(), bcast.getResult()}));
4876 accValue =
4877 aievec::CastOp::create(rewriter, loc, accVecType,
4878 concatSrc.getResult(), /*isResAcc=*/true)
4879 .getResult();
4880 srsOutType = createVectorType(accLanes, dstIntType); // vector<32xi8>
4881 } else {
4882 // i32→i16: The SRS intrinsic I256V16Acc32Srs needs 512-bit source
4883 // (vector<16xi32>, cast to vector<8xi64> internally) and produces
4884 // vector<16xi16> (256-bit). 512-bit broadcast works directly.
4885 accValue = aievec::CastOp::create(rewriter, loc, bcastVecType,
4886 bcast.getResult(), /*isResAcc=*/true)
4887 .getResult();
4888 srsOutType = createVectorType(srcLanes, dstIntType); // vector<16xi16>
4889 }
4890
4891 // SRS: accumulator → narrowed output with shift and sign
4892 auto srsOp = aievec::SRSOp::create(rewriter, loc, srsOutType, accValue,
4893 shiftVal, sign);
4894
4895 // ExtElem needs 512-bit source. SRS output is 256-bit, so concat to 512.
4896 unsigned extLanes = 512 / dstBits; // 64 for i8, 32 for i16
4897 VectorType extVecType = createVectorType(extLanes, dstIntType);
4898 auto concatForExt = aievec::ConcatOp::create(
4899 rewriter, loc, extVecType,
4900 SmallVector<Value>({srsOp.getResult(), srsOp.getResult()}));
4901
4902 // Extract element 0 back to scalar
4903 auto zeroIdx =
4904 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
4905 rewriter.replaceOpWithNewOp<aievec::ExtElemOp>(
4906 truncOp, dstIntType, concatForExt.getResult(), zeroIdx.getResult());
4907
4908 // Erase dead intermediate ops
4909 SmallVector<Operation *, 3> opsToErase;
4910 if (minOp && minOp->use_empty())
4911 opsToErase.push_back(minOp);
4912 if (maxOp && maxOp->use_empty())
4913 opsToErase.push_back(maxOp);
4914 if (shrsiOp && shrsiOp->use_empty())
4915 opsToErase.push_back(shrsiOp);
4916 for (Operation *op : opsToErase)
4917 rewriter.eraseOp(op);
4918
4919 return success();
4920 }
4921};
4922
4923// Promote scalar arith.shrsi to vector aievec.ups + aievec.srs to prevent
4924// LLVM's SLP vectorizer from creating sub-512-bit vector shifts that the
4925// AIE2 backend cannot legalize (G_LSHR on <4 x s32>).
4926struct LowerScalarShRSIToAIEVecUPSSRS : OpConversionPattern<arith::ShRSIOp> {
4927 using OpConversionPattern::OpConversionPattern;
4928
4929 LogicalResult
4930 matchAndRewrite(arith::ShRSIOp rsOp, OpAdaptor adaptor,
4931 ConversionPatternRewriter &rewriter) const override {
4932 // Only match scalar i32
4933 Type resultType = rsOp.getType();
4934 if (isa<VectorType>(resultType))
4935 return failure();
4936
4937 auto intType = dyn_cast<IntegerType>(resultType);
4938 if (!intType || intType.getWidth() != 32)
4939 return failure();
4940
4941 Location loc = rsOp.getLoc();
4942 VectorType vecType = createVectorType(16, intType); // vector<16xi32>
4943
4944 // Broadcast scalar value to 512-bit vector
4945 auto lhsBcast = aievec::BroadcastScalarOp::create(rewriter, loc, vecType,
4946 adaptor.getLhs());
4947
4948 // UPS: vector<16xi32> -> accumulator type (vector<16xi64>)
4949 Type accType = getVectorOpDestType(vecType, /*AIE2=*/true);
4950 auto upsOp =
4951 aievec::UPSOp::create(rewriter, loc, accType, lhsBcast.getResult());
4952
4953 // SRS: accumulator + i32 shift -> vector<16xi32>
4954 auto srsOp = aievec::SRSOp::create(rewriter, loc, vecType,
4955 upsOp.getResult(), adaptor.getRhs());
4956
4957 // Extract element 0 back to scalar
4958 auto zeroIdx =
4959 arith::ConstantOp::create(rewriter, loc, rewriter.getI32IntegerAttr(0));
4960 rewriter.replaceOpWithNewOp<aievec::ExtElemOp>(
4961 rsOp, intType, srsOp.getResult(), zeroIdx.getResult());
4962 return success();
4963 }
4964};
4965
4966// A `vector.contract` read as one matmul tile: the sizes of its m, n and k
4967// dims, and the order each operand stores them in once its unit dims are
4968// dropped. An m (n) dim indexes lhs (rhs) and acc, a k dim indexes lhs and
4969// rhs and is reduced. Unit dims may sit anywhere and play any role.
4971 int64_t m = 1, n = 1, k = 1;
4972 // lhs stored [k, m], rhs stored [n, k], acc stored [n, m]. Only set when
4973 // both dims of the operand are larger than 1.
4974 bool lhsTransposed = false, rhsTransposed = false, accTransposed = false;
4975};
4976
4977static LogicalResult analyzeContraction(vector::ContractionOp op,
4978 ContractionTile &tile,
4979 std::string &why) {
4980 llvm::raw_string_ostream os(why);
4981 if (op.getKind() != vector::CombiningKind::ADD) {
4982 os << "the combining kind is not add";
4983 return failure();
4984 }
4985 auto accTy = dyn_cast<VectorType>(op.getAccType());
4986 if (!accTy) {
4987 os << "the accumulator is a scalar";
4988 return failure();
4989 }
4990 SmallVector<AffineMap, 3> maps = op.getIndexingMapsArray();
4991 if (!llvm::all_of(maps, [](AffineMap map) {
4992 return map.isProjectedPermutation(/*allowZeroInResults=*/false);
4993 })) {
4994 os << "an indexing map is not a projected permutation";
4995 return failure();
4996 }
4997 enum Role { M, N, K, Other };
4998 unsigned numDims = maps[0].getNumDims();
4999 SmallVector<int64_t> dimSize(numDims, 1);
5000 SmallVector<std::array<bool, 3>> indexes(numDims, {false, false, false});
5001 ArrayRef<int64_t> shapes[3] = {op.getLhsType().getShape(),
5002 op.getRhsType().getShape(), accTy.getShape()};
5003 for (unsigned operand = 0; operand < 3; ++operand)
5004 for (auto [i, expr] : llvm::enumerate(maps[operand].getResults())) {
5005 unsigned d = cast<AffineDimExpr>(expr).getPosition();
5006 dimSize[d] = shapes[operand][i];
5007 indexes[d][operand] = true;
5008 }
5009 SmallVector<vector::IteratorType> iterators = op.getIteratorTypesArray();
5010 std::optional<unsigned> dimOf[3];
5011 for (unsigned d = 0; d < numDims; ++d) {
5012 if (dimSize[d] == 1)
5013 continue;
5014 auto [inLhs, inRhs, inAcc] = indexes[d];
5015 bool reduced = iterators[d] == vector::IteratorType::reduction;
5016 Role r = Other;
5017 if (inLhs && !inRhs && inAcc && !reduced)
5018 r = M;
5019 else if (!inLhs && inRhs && inAcc && !reduced)
5020 r = N;
5021 else if (inLhs && inRhs && !inAcc && reduced)
5022 r = K;
5023 if (r == Other || dimOf[r]) {
5024 os << "d" << d << " of size " << dimSize[d]
5025 << " is not the only m, n or k dim larger than 1";
5026 return failure();
5027 }
5028 dimOf[r] = d;
5029 }
5030 auto sizeOf = [&](Role r) -> int64_t {
5031 std::optional<unsigned> d = dimOf[r];
5032 return d ? dimSize[*d] : 1;
5033 };
5034 tile.m = sizeOf(M);
5035 tile.n = sizeOf(N);
5036 tile.k = sizeOf(K);
5037 // Whether `second` comes before `first` among the results of `map`.
5038 auto swapped = [&](AffineMap map, Role first, Role second) {
5039 if (!dimOf[first] || !dimOf[second])
5040 return false;
5041 return *map.getResultPosition(
5042 getAffineDimExpr(*dimOf[second], map.getContext())) <
5043 *map.getResultPosition(
5044 getAffineDimExpr(*dimOf[first], map.getContext()));
5045 };
5046 tile.lhsTransposed = swapped(maps[0], M, K);
5047 tile.rhsTransposed = swapped(maps[1], K, N);
5048 tile.accTransposed = swapped(maps[2], M, N);
5049 return success();
5050}
5051
5052// Convert a `vector.contract` op to an `aievec.matmul` op for AIE2 or
5053// `aievec.matmul_aie2p` for AIE2P. The operands keep the layout the
5054// contraction stores them in, recorded by the lhs_transposed and
5055// rhs_transposed flags; the lowering of the matmul op moves data only where
5056// the multiply reads an operand the other way. An accumulator stored [n, m]
5057// is handled by multiplying rhs^T by lhs^T, which swaps the operands and
5058// flips both of their flags.
5059template <typename MatMulOpTy>
5061 : OpConversionPattern<vector::ContractionOp> {
5062 using OpConversionPattern::OpConversionPattern;
5063
5064 static constexpr bool isAIE2P =
5065 std::is_same_v<MatMulOpTy, aievec::MatMulOp_AIE2P>;
5066
5067 static Value reshape(OpBuilder &b, Value v, ArrayRef<int64_t> shape) {
5068 auto vecTy = cast<VectorType>(v.getType());
5069 auto newTy = VectorType::get(shape, vecTy.getElementType());
5070 if (newTy == vecTy)
5071 return v;
5072 return vector::ShapeCastOp::create(b, v.getLoc(), newTy, v).getResult();
5073 }
5074
5075 LogicalResult
5076 matchAndRewrite(vector::ContractionOp contractOp, OpAdaptor adaptor,
5077 ConversionPatternRewriter &rewriter) const override {
5078 StringRef target = isAIE2P ? "AIE2P" : "AIE2";
5079 std::string why;
5080 ContractionTile tile;
5081 if (failed(analyzeContraction(contractOp, tile, why))) {
5082 contractOp.emitError()
5083 << "cannot lower to an " << target << " matrix multiply: " << why;
5084 return failure();
5085 }
5086
5087 Value lhsIn = adaptor.getLhs(), rhsIn = adaptor.getRhs();
5088 Value lhsOrig = contractOp.getLhs(), rhsOrig = contractOp.getRhs();
5089 int64_t m = tile.m, n = tile.n, k = tile.k;
5090 bool lhsT = tile.lhsTransposed, rhsT = tile.rhsTransposed;
5091 if (tile.accTransposed) {
5092 std::swap(lhsIn, rhsIn);
5093 std::swap(lhsOrig, rhsOrig);
5094 std::swap(m, n);
5095 // A flag only matters, and is only set, when both dims are larger
5096 // than 1.
5097 std::tie(lhsT, rhsT) =
5098 std::make_pair(!rhsT && m > 1 && k > 1, !lhsT && n > 1 && k > 1);
5099 }
5100 SmallVector<int64_t, 2> lhsShape =
5101 lhsT ? SmallVector<int64_t, 2>{k, m} : SmallVector<int64_t, 2>{m, k};
5102 SmallVector<int64_t, 2> rhsShape =
5103 rhsT ? SmallVector<int64_t, 2>{n, k} : SmallVector<int64_t, 2>{k, n};
5104 auto accTy =
5105 VectorType::get({m, n}, getElementTypeOrSelf(adaptor.getAcc()));
5106
5107 // Recover operand signedness from the widening op before it is peeled.
5108 // Consult both the (possibly already-rewritten) adaptor operands and the
5109 // original pre-conversion contraction operands.
5110 auto lhsSigned = getSignednessOfWideningOp(lhsIn);
5111 if (!lhsSigned)
5112 lhsSigned = getSignednessOfWideningOp(lhsOrig);
5113 auto rhsSigned = getSignednessOfWideningOp(rhsIn);
5114 if (!rhsSigned)
5115 rhsSigned = getSignednessOfWideningOp(rhsOrig);
5116
5117 // Carry the signedness in the matmul operand element type (si8/ui8). MLIR
5118 // has no signless->signed/unsigned cast, so materialize an
5119 // unrealized_conversion_cast; it folds to a no-op once both sides lower to
5120 // signless LLVM integers. bf16/unknown-signedness operands are unchanged.
5121 auto retype = [&](Value v, std::optional<bool> isSigned) -> Value {
5122 if (!isSigned)
5123 return v;
5124 auto vecTy = cast<VectorType>(v.getType());
5125 auto intTy = dyn_cast<IntegerType>(vecTy.getElementType());
5126 if (!intTy || !intTy.isSignless())
5127 return v;
5128 auto signednessTy = IntegerType::get(intTy.getContext(), intTy.getWidth(),
5129 *isSigned ? IntegerType::Signed
5130 : IntegerType::Unsigned);
5131 return UnrealizedConversionCastOp::create(rewriter, v.getLoc(),
5132 vecTy.clone(signednessTy), v)
5133 .getResult(0);
5134 };
5135
5136 auto tileFits = [&](Value lhs, Value rhs, bool report) {
5137 auto emitError = [&]() {
5138 return contractOp.emitError()
5139 << "cannot lower to an " << target << " matrix multiply: ";
5140 };
5141 return checkMatMulTile(
5142 isAIE2P, VectorType::get(lhsShape, getElementTypeOrSelf(lhs)),
5143 VectorType::get(rhsShape, getElementTypeOrSelf(rhs)), accTy,
5144 lhsT, rhsT,
5145 report ? llvm::function_ref<InFlightDiagnostic()>(emitError)
5146 : nullptr)
5147 .has_value();
5148 };
5149 // Multiply the operands as they are if they form a tile. Otherwise they
5150 // may be widened from a type a multiply takes, as when a linalg op is
5151 // converted to contractions with its operands cast to the accumulator
5152 // type outside the contraction.
5153 if (!tileFits(lhsIn, rhsIn, /*report=*/false)) {
5154 lhsIn = getSourceOfWideningOp(lhsIn).value_or(lhsIn);
5155 rhsIn = getSourceOfWideningOp(rhsIn).value_or(rhsIn);
5156 if (!tileFits(lhsIn, rhsIn, /*report=*/true))
5157 return failure();
5158 }
5159 Value acc = reshape(rewriter, adaptor.getAcc(), accTy.getShape());
5160 Value lhs = reshape(rewriter, lhsIn, lhsShape);
5161 Value rhs = reshape(rewriter, rhsIn, rhsShape);
5162
5163 auto matmulOp =
5164 MatMulOpTy::create(rewriter, contractOp.getLoc(), acc.getType(),
5165 retype(lhs, lhsSigned), retype(rhs, rhsSigned), acc);
5166 matmulOp.setLhsTransposed(lhsT);
5167 matmulOp.setRhsTransposed(rhsT);
5168 rewriter.replaceOp(
5169 contractOp,
5170 reshape(rewriter, matmulOp.getResult(),
5171 cast<VectorType>(adaptor.getAcc().getType()).getShape()));
5172 return success();
5173 }
5174};
5175
5180
5181// Convert a `vector.transpose` op to an `aievec.shuffle` op for AIE2.
5183 : OpConversionPattern<vector::TransposeOp> {
5184 using OpConversionPattern::OpConversionPattern;
5185 LogicalResult
5186 matchAndRewrite(vector::TransposeOp transpOp, OpAdaptor adaptor,
5187 ConversionPatternRewriter &rewriter) const override {
5188 auto resTy = transpOp.getResultVectorType();
5189 auto resShape = resTy.getShape();
5190 auto elemTyBitWidth = resTy.getElementTypeBitWidth();
5191 auto vBitWidth = std::accumulate(resShape.begin(), resShape.end(),
5192 elemTyBitWidth, std::multiplies<>());
5193 if (vBitWidth != 512)
5194 return failure();
5195
5196 if (elemTyBitWidth != 8 && elemTyBitWidth != 16 && elemTyBitWidth != 32)
5197 return failure();
5198
5199 // Verify leading dimensions are all 1.
5200 for (int64_t i = 0; i < static_cast<int64_t>(resShape.size() - 2); ++i)
5201 if (resShape[i] != 1)
5202 return failure();
5203
5204 // Only permutation of the 2 innermost dimensions are supported.
5205 ArrayRef<int64_t> perm = transpOp.getPermutation();
5206 for (int64_t i = 0; i < static_cast<int64_t>(perm.size() - 2); ++i)
5207 if (perm[i] != i)
5208 return failure();
5209 if (perm.back() != static_cast<int64_t>(perm.size() - 2))
5210 return failure();
5211
5212 auto shuffleMode = aievec::ShuffleMode::T32_4X4;
5213 if (elemTyBitWidth == 8) {
5214 switch (resShape.back()) {
5215 case 4:
5216 shuffleMode = aievec::ShuffleMode::T8_4X16;
5217 break;
5218 case 8:
5219 shuffleMode = aievec::ShuffleMode::T8_8X8;
5220 break;
5221 case 16:
5222 shuffleMode = aievec::ShuffleMode::T8_16X4;
5223 break;
5224 default:
5225 return failure();
5226 }
5227 } else if (elemTyBitWidth == 16) {
5228 switch (resShape.back()) {
5229 case 2:
5230 shuffleMode = aievec::ShuffleMode::T16_2X16;
5231 break;
5232 case 4:
5233 shuffleMode = aievec::ShuffleMode::T16_4X8;
5234 break;
5235 case 8:
5236 shuffleMode = aievec::ShuffleMode::T16_8X4;
5237 break;
5238 case 16:
5239 shuffleMode = aievec::ShuffleMode::T16_16X2;
5240 break;
5241 default:
5242 return failure();
5243 }
5244 } else if (resShape.back() != 4)
5245 return failure();
5246
5247 auto flatVecTy =
5248 VectorType::get({512 / elemTyBitWidth}, resTy.getElementType());
5249 auto loc = transpOp.getLoc();
5250 auto flatInput = vector::ShapeCastOp::create(rewriter, loc, flatVecTy,
5251 adaptor.getVector());
5252 auto shuffOp = aievec::ShuffleOp::create(rewriter, loc, flatVecTy,
5253 flatInput, nullptr, shuffleMode);
5254 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(transpOp, resTy, shuffOp);
5255
5256 return success();
5257 }
5258};
5259
5260//===----------------------------------------------------------------------===//
5261// Pattern collection
5262//===----------------------------------------------------------------------===//
5263
5264static void
5265populateAIEVecCommonConversionPatterns(RewritePatternSet &patterns) {
5266 // clang-format off
5267 patterns.add<LowerExtFOpPattern,
5270 LowerTruncIOpPattern>(patterns.getContext());
5271 // Match canonical int4 unpack (vector.bitcast + arith.extui) ahead of the
5272 // generic ExtUI/ExtSI handling; runs on both AIE2 and AIE2P call sites
5273 // but only triggers for the i4-packed shape, so it is benign for AIE2.
5274 patterns.add<LowerExtUIOfBitcastI4ToUnpackPattern>(patterns.getContext(),
5275 /*benefit=*/2);
5276 // AIE2P magic-number lowering for arith.sitofp v16i16/v32i16 -> v16/v32
5277 // bf16. Higher benefit than the generic per-lane scalarise pattern; runs
5278 // on AIE2 too but only fires when the legality predicate marks the op
5279 // illegal (currently AIE2P only).
5280 patterns.add<LowerVectorSIToFPI16BF16AIE2pPattern>(patterns.getContext(),
5281 /*benefit=*/3);
5282 // clang-format on
5283}
5284
5285static void populateAIEVecV1ConversionPatterns(RewritePatternSet &patterns) {
5286 patterns.add<LowerVectorTransferReadToAIEUPD>(patterns.getContext(), 128, 512,
5287 128, 256);
5288 // clang-format off
5289 patterns.add<LowerVectorAddIOpToAIEVecAddOp,
5297 LowerVectorExtractStridedSliceOpAIEv1Pattern>(patterns.getContext());
5298 // clang-format on
5299}
5300
5301// Populate common conversion patterns for AIE2 and AIE2P
5302static void
5303populateAIEVecV2CommonConversionPatterns(RewritePatternSet &patterns) {
5304 // clang-format off
5305 // TODO: Reorder these alphabetically
5306 patterns.add<
5309 // AIE2/AIE2P have no vector int<->fp instruction; scalarize so each
5310 // lane reaches the supported scalar libcall path.
5315 >(patterns.getContext());
5316
5317 // Add the compound shift+clamp+trunc→SRS pattern with higher benefit
5318 // so it takes priority over the individual shrsi and trunci patterns.
5319 patterns.add<ShiftClampTruncToSRSPattern>(patterns.getContext(),
5320 /*benefit=*/2);
5321 // Scalar version of compound SRS with even higher benefit.
5322 patterns.add<LowerScalarShiftClampTruncToSRS>(patterns.getContext(),
5323 /*benefit=*/3);
5324 patterns.add<
5361 >(patterns.getContext());
5362 // clang-format on
5363}
5364
5365static void populateAIEVecV2ConversionPatterns(RewritePatternSet &patterns) {
5366 populateAIEVecV2CommonConversionPatterns(patterns);
5368 patterns.getContext());
5369 patterns.add<LowerVectorReductionAddBfloat16OpAIE2>(patterns.getContext());
5370 // For AIE2 with LLVMIR backend, use LUT-based exp and rsqrt
5372 patterns.getContext());
5373}
5374
5375// AIE2p-specific version of ConvertSplatToAIEBroadcast that supports direct
5376// 256-bit broadcasts without extract
5378 : OpConversionPattern<vector::BroadcastOp> {
5379 using OpConversionPattern::OpConversionPattern;
5380
5381 LogicalResult
5382 matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor,
5383 ConversionPatternRewriter &rewriter) const override {
5384
5385 if (adaptor.getSource().getDefiningOp<vector::ExtractOp>())
5386 return failure();
5387
5388 auto resultType = cast<VectorType>(bcastOp.getResult().getType());
5389 auto flatResultType = getFlattenedVectorType(resultType);
5390 Type scalarType = resultType.getElementType();
5391 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
5392 unsigned laneSize = getVectorLaneSize(resultType);
5393 auto src = bcastOp.getSource();
5394
5395 // AIE2p supports both 256-bit and 512-bit broadcast directly
5396 if (laneSize * elWidth == 512 || laneSize * elWidth == 256) {
5397 Value newOp = aievec::BroadcastScalarOp::create(
5398 rewriter, bcastOp.getLoc(), flatResultType, src);
5399 if (resultType != flatResultType)
5400 newOp = vector::ShapeCastOp::create(rewriter, bcastOp.getLoc(),
5401 resultType, newOp);
5402 rewriter.replaceOp(bcastOp, newOp);
5403 return success();
5404 }
5405
5406 if (laneSize * elWidth == 1024) {
5407 VectorType vecType = createVectorType(512 / elWidth, scalarType);
5408 auto aieBcastOp = aievec::BroadcastScalarOp::create(
5409 rewriter, bcastOp.getLoc(), vecType, src);
5410 Value newOp = aievec::ConcatOp::create(
5411 rewriter, bcastOp.getLoc(), flatResultType,
5412 SmallVector<Value>({aieBcastOp.getResult(), aieBcastOp.getResult()}));
5413 if (resultType != flatResultType)
5414 newOp = vector::ShapeCastOp::create(rewriter, bcastOp.getLoc(),
5415 resultType, newOp);
5416 rewriter.replaceOp(bcastOp, newOp);
5417 return success();
5418 }
5419
5420 return failure();
5421 }
5422};
5423
5424// Whether FlattenElementwiseVectorToRank1Pattern rewrites `op`: an n-D
5425// elementwise op whose vector operands all have the shape of its result.
5426static bool isFlattenableElementwise(Operation *op) {
5427 if (op->getNumResults() != 1)
5428 return false;
5429 auto resultType = dyn_cast<VectorType>(op->getResult(0).getType());
5430 if (!resultType || resultType.getRank() == 1 || resultType.isScalable())
5431 return false;
5432
5433 // The contraction lowering folds an extf on its operand into the matmul.
5434 // It looks for the extf directly, so a flattened extf hidden behind a
5435 // shape_cast would leave the contraction with no lowering.
5436 if (isa<arith::ExtFOp>(op) &&
5437 llvm::any_of(op->getUsers(), [](Operation *user) {
5438 return isa<vector::ContractionOp>(user);
5439 }))
5440 return false;
5441
5442 // Only shapes that agree with the result are elementwise; anything else (a
5443 // broadcast of a differently shaped vector, say) is left to the patterns
5444 // that understand it.
5445 return llvm::all_of(op->getOperandTypes(), [&](Type type) {
5446 auto operandType = dyn_cast<VectorType>(type);
5447 return !operandType || operandType.getShape() == resultType.getShape();
5448 });
5449}
5450
5451// Rewrite an elementwise op on an n-D vector into the same op on the rank-1
5452// vector with the same lane count, between a pair of vector.shape_casts.
5453//
5454// The legality predicates count lanes with getVectorLaneSize, the product of
5455// all dimensions, while the patterns they select match rank-1 operands.
5456// Flattening first makes the two agree; the widths are left as they were.
5457// Rank 1 is excluded so this cannot re-match its own output.
5458template <typename OpTy>
5459struct FlattenElementwiseVectorToRank1Pattern : OpConversionPattern<OpTy> {
5460 using OpConversionPattern<OpTy>::OpConversionPattern;
5461 using OpAdaptor = typename OpTy::Adaptor;
5462
5463 LogicalResult
5464 matchAndRewrite(OpTy op, OpAdaptor adaptor,
5465 ConversionPatternRewriter &rewriter) const override {
5466 if (!isFlattenableElementwise(op))
5467 return failure();
5468
5469 auto resultType = cast<VectorType>(op.getType());
5470 auto flatResultType = getFlattenedVectorType(resultType);
5471 Location loc = op.getLoc();
5472
5473 SmallVector<Value> flatOperands;
5474 flatOperands.reserve(adaptor.getOperands().size());
5475 for (Value operand : adaptor.getOperands()) {
5476 auto operandType = dyn_cast<VectorType>(operand.getType());
5477 if (!operandType) {
5478 flatOperands.push_back(operand);
5479 continue;
5480 }
5481 auto flatOperandType = getFlattenedVectorType(operandType);
5482 // Rematerialise a splat at the flat type instead of shape_casting it:
5483 // patterns that match on a constant operand (the reciprocal lowering
5484 // looks for a dividend of 1.0) cannot see through a shape_cast.
5485 if (auto cstOp = operand.template getDefiningOp<arith::ConstantOp>()) {
5486 if (auto dense = dyn_cast<DenseElementsAttr>(cstOp.getValue())) {
5487 if (dense.isSplat()) {
5488 flatOperands.push_back(arith::ConstantOp::create(
5489 rewriter, loc,
5490 DenseElementsAttr::get(flatOperandType,
5491 dense.getSplatValue<Attribute>())));
5492 continue;
5493 }
5494 }
5495 }
5496 flatOperands.push_back(
5497 vector::ShapeCastOp::create(rewriter, loc, flatOperandType, operand));
5498 }
5499
5500 auto flatOp = OpTy::create(rewriter, loc, TypeRange{flatResultType},
5501 flatOperands, op.getProperties(),
5502 op->getDiscardableAttrDictionary().getValue());
5503 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, resultType,
5504 flatOp->getResult(0));
5505 return success();
5506 }
5507};
5508
5509// The lane counts at which the AIE2P lowering of `op` takes one op, smallest
5510// first, or none if it has no such lowering. For a compare they are those of
5511// its operands.
5512static SmallVector<int64_t, 5> getAIE2PNativeLanes(Operation *op) {
5513 Value v = isa<arith::CmpIOp, arith::CmpFOp>(op) ? op->getOperand(0)
5514 : op->getResult(0);
5515 auto type = dyn_cast<VectorType>(v.getType());
5516 if (!type || !type.getElementType().isIntOrFloat())
5517 return {};
5518 Type elemType = type.getElementType();
5519 unsigned bitWidth = elemType.getIntOrFloatBitWidth();
5520 bool isInt = isa<IntegerType>(elemType) &&
5521 (bitWidth == 8 || bitWidth == 16 || bitWidth == 32);
5522
5523 // f32 adds and subtracts run on the 2048-bit accumulator, which holds 64
5524 // lanes; the LLVM lowering pads 16 or 32 lanes to it. bf16 ones are moved
5525 // to the accumulator and back at 16 lanes, and 32 lanes as two halves.
5526 if (isa<arith::AddFOp, arith::SubFOp>(op)) {
5527 if (elemType.isF32())
5528 return {16, 32, 64};
5529 if (elemType.isBF16())
5530 return {16, 32};
5531 return {};
5532 }
5533 // Integer adds and subtracts are left to LLVM, which takes one 512-bit
5534 // register or less. Splitting wider ones here rather than in LLVM lets the
5535 // parts feed the parts of a split compare directly.
5536 if (isa<arith::AddIOp, arith::SubIOp>(op)) {
5537 if (!isInt)
5538 return {};
5539 SmallVector<int64_t, 5> lanes;
5540 for (int64_t n = 2; n <= 512 / bitWidth; n *= 2)
5541 lanes.push_back(n);
5542 return lanes;
5543 }
5544 // The tanh and exp2 intrinsics take 16 lanes of accfloat, which f32 maps
5545 // onto as well as bf16; 32 lanes are taken as two halves. erf is computed
5546 // with tanh.
5547 if (isa<math::TanhOp, math::ErfOp, math::Exp2Op>(op)) {
5548 if (elemType.isF32() || elemType.isBF16())
5549 return {16, 32};
5550 return {};
5551 }
5552 // Float max and min take one 512-bit register of bf16.
5553 if (isa<arith::MaximumFOp, arith::MaxNumFOp, arith::MinimumFOp>(op)) {
5554 if (elemType.isBF16())
5555 return {32};
5556 return {};
5557 }
5558 // Compare and select take 32 lanes of bf16 or i16, 16 of which are padded
5559 // to 32, and 16 lanes of i32. The LLVM lowering compares bf16 as ordered
5560 // (false if either is NaN); a compare it does not honour is not given a
5561 // native width, so it is not split or repeated.
5562 if (isa<arith::CmpIOp, arith::CmpFOp, arith::SelectOp>(op)) {
5563 if (auto cmpOp = dyn_cast<arith::CmpFOp>(op);
5564 cmpOp && !llvm::is_contained({CmpFPredicate::OEQ, CmpFPredicate::ONE,
5565 CmpFPredicate::OLT, CmpFPredicate::OLE,
5566 CmpFPredicate::OGT, CmpFPredicate::OGE},
5567 cmpOp.getPredicate()))
5568 return {};
5569 if (elemType.isBF16() || elemType.isInteger(16))
5570 return {16, 32};
5571 if (elemType.isInteger(32))
5572 return {16};
5573 return {};
5574 }
5575 return {};
5576}
5577
5578// Whether the AIE2P lowering of `op` takes it at its lane count.
5579static bool isAIE2PNativeWidth(Operation *op) {
5580 auto type = dyn_cast<VectorType>(op->getResult(0).getType());
5581 return type && !type.isScalable() &&
5582 llvm::is_contained(getAIE2PNativeLanes(op), type.getNumElements());
5583}
5584
5585// The compare that `op`, a select, selects on.
5586static Operation *getSelectCompare(arith::SelectOp op) {
5587 Operation *cmp = skipShapeCastRoundTrip(op.getCondition()).getDefiningOp();
5588 return isa_and_nonnull<arith::CmpIOp, arith::CmpFOp>(cmp) ? cmp : nullptr;
5589}
5590
5591// aievec.ext numbers the parts of its source with an i8.
5592constexpr int64_t kAIE2PMaxParts = 128;
5593
5594// The lanes of each part an elementwise op is rewritten into when the AIE2P
5595// lowering does not take its lane count, or none. Lanes are counted over all
5596// dimensions. A vector is split into parts of the widest native width that
5597// divides it or, if none does, repeated to the narrowest native width that
5598// holds a whole number of copies.
5599//
5600// A compare is split together with the select that uses it, as the parts of
5601// its mask cannot be put back together; the parts are as wide as both the
5602// compare and the select take.
5603static std::optional<int64_t> getAIE2PPartLanes(Operation *op) {
5604 if (op->getNumResults() != 1 || isa<arith::CmpIOp, arith::CmpFOp>(op))
5605 return std::nullopt;
5606 auto type = dyn_cast<VectorType>(op->getResult(0).getType());
5607 if (!type || type.isScalable())
5608 return std::nullopt;
5609 int64_t lanes = type.getNumElements();
5610 if (lanes < 2 || llvm::any_of(
5611 op->getOperandTypes(),
5612 [&](Type t) {
5613 auto vt = dyn_cast<VectorType>(t);
5614 return !vt || vt.getShape() != type.getShape();
5615 }))
5616 return std::nullopt;
5617
5618 SmallVector<int64_t, 5> native = getAIE2PNativeLanes(op);
5619 if (auto selOp = dyn_cast<arith::SelectOp>(op)) {
5620 Operation *cmp = getSelectCompare(selOp);
5621 if (!cmp)
5622 return std::nullopt;
5623 SmallVector<int64_t, 5> cmpNative = getAIE2PNativeLanes(cmp);
5624 llvm::erase_if(
5625 native, [&](int64_t n) { return !llvm::is_contained(cmpNative, n); });
5626 }
5627 if (native.empty() || llvm::is_contained(native, lanes))
5628 return std::nullopt;
5629 // LLVM takes an integer add of one register whatever its lane count.
5630 if (isa<arith::AddIOp, arith::SubIOp>(op) && lanes < native.back())
5631 return std::nullopt;
5632
5633 for (int64_t part : llvm::reverse(native))
5634 if (part < lanes && lanes % part == 0 && lanes / part <= kAIE2PMaxParts)
5635 return part;
5636 if (isa<arith::SelectOp>(op))
5637 return std::nullopt;
5638 for (int64_t part : native)
5639 if (part > lanes && part % lanes == 0)
5640 return part;
5641 return std::nullopt;
5642}
5643
5644// Part `index` of `partLanes` lanes of `v`, or `v` repeated to `partLanes`
5645// lanes when it has fewer. A splat is built again at the width of the part,
5646// which keeps the parts of a splat equal, and the part of a concatenation of
5647// such parts is taken from it directly.
5648static Value getAIE2PPart(ConversionPatternRewriter &rewriter, Location loc,
5649 Value v, int64_t partLanes, int64_t index) {
5650 auto type = cast<VectorType>(v.getType());
5651 auto partType = VectorType::get({partLanes}, type.getElementType());
5652 int64_t lanes = type.getNumElements();
5653 if (auto cstOp = v.getDefiningOp<arith::ConstantOp>())
5654 if (auto dense = dyn_cast<DenseElementsAttr>(cstOp.getValue());
5655 dense && dense.isSplat())
5656 return arith::ConstantOp::create(
5657 rewriter, loc,
5658 DenseElementsAttr::get(partType, dense.getSplatValue<Attribute>()));
5659 if (auto bcastOp = v.getDefiningOp<vector::BroadcastOp>())
5660 if (!isa<VectorType>(bcastOp.getSourceType()))
5661 return vector::BroadcastOp::create(rewriter, loc, partType,
5662 bcastOp.getSource());
5663 if (partLanes < lanes) {
5664 if (auto concatOp = v.getDefiningOp<aievec::ConcatOp>())
5665 if (concatOp.getSources().front().getType() == partType)
5666 return concatOp.getSources()[index];
5667 return aievec::ExtOp::create(rewriter, loc, partType, v, index);
5668 }
5669 SmallVector<Value> copies(partLanes / lanes, v);
5670 return aievec::ConcatOp::create(rewriter, loc, partType, copies);
5671}
5672
5673// Part `index` of the mask of `selOp`. The mask is not cut apart: its compare
5674// is built again on the parts of the compare's operands. The compare is taken
5675// from `selOp` itself, as the mask it is converted to may not be a vector.
5676static Value getAIE2PMaskPart(ConversionPatternRewriter &rewriter, Location loc,
5677 arith::SelectOp selOp, int64_t partLanes,
5678 int64_t index) {
5679 Operation *cmp = getSelectCompare(selOp);
5680 OperationState state(loc, cmp->getName());
5681 for (Value operand : cmp->getOperands())
5682 state.addOperands(getAIE2PPart(
5683 rewriter, loc, rewriter.getRemappedValue(operand), partLanes, index));
5684 state.addTypes(VectorType::get({partLanes}, rewriter.getI1Type()));
5685 state.addAttributes(cmp->getAttrs());
5686 return rewriter.create(state)->getResult(0);
5687}
5688
5689// Rewrite a rank-1 elementwise op whose lane count the AIE2P lowering does
5690// not take into the same op on parts of a lane count it does take (see
5691// getAIE2PPartLanes): ops on the parts joined by aievec.concat, or one op on
5692// the repeated operands followed by aievec.ext of the lanes wanted. A select
5693// takes its compare along.
5694template <typename OpTy>
5695struct NormalizeLanesAIE2PPattern : OpConversionPattern<OpTy> {
5696 using OpConversionPattern<OpTy>::OpConversionPattern;
5697 using OpAdaptor = typename OpTy::Adaptor;
5698
5699 LogicalResult
5700 matchAndRewrite(OpTy op, OpAdaptor adaptor,
5701 ConversionPatternRewriter &rewriter) const override {
5702 auto type = dyn_cast<VectorType>(op->getResult(0).getType());
5703 if (!type || type.getRank() != 1)
5704 return failure();
5705 std::optional<int64_t> partLanes = getAIE2PPartLanes(op);
5706 if (!partLanes)
5707 return failure();
5708 int64_t lanes = type.getNumElements();
5709 auto partType = VectorType::get({*partLanes}, type.getElementType());
5710 Location loc = op.getLoc();
5711 auto createPart = [&](int64_t index) -> Value {
5712 SmallVector<Value> operands;
5713 for (Value operand : adaptor.getOperands()) {
5714 if constexpr (std::is_same_v<OpTy, arith::SelectOp>) {
5715 if (operands.empty()) {
5716 operands.push_back(
5717 getAIE2PMaskPart(rewriter, loc, op, *partLanes, index));
5718 continue;
5719 }
5720 }
5721 operands.push_back(
5722 getAIE2PPart(rewriter, loc, operand, *partLanes, index));
5723 }
5724 return OpTy::create(rewriter, loc, TypeRange{partType}, operands,
5725 op.getProperties(),
5726 op->getDiscardableAttrDictionary().getValue())
5727 ->getResult(0);
5728 };
5729
5730 if (*partLanes > lanes) {
5731 rewriter.replaceOpWithNewOp<aievec::ExtOp>(op, type, createPart(0), 0);
5732 return success();
5733 }
5734 SmallVector<Value> parts;
5735 for (int64_t i = 0; i < lanes / *partLanes; ++i)
5736 parts.push_back(createPart(i));
5737 rewriter.replaceOpWithNewOp<aievec::ConcatOp>(op, type, parts);
5738 return success();
5739 }
5740};
5741
5742// The elementwise ops AIE2P flattens to rank 1 and, for those with an entry
5743// in getAIE2PNativeLanes, brings to a lane count its lowering takes. An n-D
5744// op the rank-1 patterns would leave alone is flattened all the same: the
5745// LLVM lowering cannot take an n-D vector either.
5746template <typename... OpTys>
5748 static void addPatterns(RewritePatternSet &patterns, PatternBenefit benefit) {
5750 NormalizeLanesAIE2PPattern<OpTys>...>(patterns.getContext(),
5751 benefit);
5752 }
5753 static void markIllegal(ConversionTarget &target, MLIRContext *ctx) {
5754 (markIllegal<OpTys>(target, ctx), ...);
5755 }
5756 // An n-D op to flatten, or one to bring to another lane count, is illegal.
5757 // An op that is already illegal stays so; a dynamically legal one keeps its
5758 // predicate for the rest.
5759 template <typename OpTy>
5760 static void markIllegal(ConversionTarget &target, MLIRContext *ctx) {
5761 std::optional<ConversionTarget::LegalizationAction> action =
5762 target.getOpAction(OperationName(OpTy::getOperationName(), ctx));
5763 if (action == ConversionTarget::LegalizationAction::Legal)
5764 target.addDynamicallyLegalOp<OpTy>([](Operation *op) {
5765 return !isFlattenableElementwise(op) && !getAIE2PPartLanes(op);
5766 });
5767 else if (action == ConversionTarget::LegalizationAction::Dynamic)
5768 target.addDynamicallyLegalOp<OpTy>(
5769 [](Operation *op) -> std::optional<bool> {
5770 if (isFlattenableElementwise(op) || getAIE2PPartLanes(op))
5771 return false;
5772 return std::nullopt;
5773 });
5774 }
5775};
5777 arith::MulFOp, arith::DivFOp, arith::AddFOp, arith::SubFOp, arith::NegFOp,
5778 arith::TruncFOp, arith::ExtFOp, math::ExpOp, math::TanhOp, math::ErfOp,
5779 math::Exp2Op, arith::MaximumFOp, arith::MaxNumFOp, arith::MinimumFOp,
5780 arith::CmpFOp, arith::CmpIOp, arith::SelectOp, arith::AddIOp, arith::SubIOp,
5781 arith::MulIOp, arith::IndexCastOp>;
5782
5783static void populateAIEVecV2PConversionPatterns(
5784 RewritePatternSet &patterns, std::shared_ptr<AIE2PShufflePlanner> planner) {
5785 populateAIEVecV2CommonConversionPatterns(patterns);
5787 patterns.getContext());
5789 ConvertMulFToAIEVecMulElemOpAIE2PPattern>(patterns.getContext(),
5790 /*benefit=*/2);
5791 // AIE2p-specific broadcast pattern that handles 256-bit directly
5792 patterns.add<ConvertSplatToAIEBroadcastAIE2p>(patterns.getContext());
5793 populateAIE2PShufflePlannerPatterns(patterns, std::move(planner));
5794 patterns.add<LowerVectorReductionAddBfloat16OpAIE2P>(patterns.getContext());
5795 // For AIE2P with LLVMIR backend, use aievec.exp and aievec.inv
5796 // math.rsqrt is kept legal and will be lowered in AIEVecToLLVM pass
5797 patterns
5799 patterns.getContext());
5800 // Higher benefit than the AIE2P-specific patterns below, which would
5801 // otherwise take an n-D op before it has been flattened.
5802 AIE2PElementwise::addPatterns(patterns, /*benefit=*/3);
5803 // Higher benefit to take priority over the AIE2 LUT-based tanh and the
5804 // emitc-based erf patterns registered in the common patterns.
5805 patterns.add<
5815 /*benefit=*/2);
5816}
5817
5818//===----------------------------------------------------------------------===//
5819// Legalizations
5820//===----------------------------------------------------------------------===//
5821
5822// TODO: Review the validity of these legalizations beyond basic cases.
5823
5824static bool isInSigmoidOperationChain(math::ExpOp expOp) {
5825 if (!expOp.getOperand().getDefiningOp<arith::NegFOp>())
5826 return false;
5827
5828 arith::AddFOp addOp = nullptr;
5829 for (Operation *user : expOp->getUsers()) {
5830 addOp = dyn_cast<arith::AddFOp>(user);
5831 if (addOp)
5832 break;
5833 }
5834
5835 if (!addOp)
5836 return false;
5837
5838 auto *addLvalOp = addOp.getLhs().getDefiningOp();
5839 auto *addRvalOp = addOp.getRhs().getDefiningOp();
5840 if (!((isa<math::ExpOp>(addLvalOp) && isa<arith::ConstantOp>(addRvalOp)) ||
5841 (isa<math::ExpOp>(addRvalOp) && isa<arith::ConstantOp>(addLvalOp))))
5842 return false;
5843
5844 auto constOp = isa<arith::ConstantOp>(addLvalOp)
5845 ? cast<arith::ConstantOp>(addLvalOp)
5846 : cast<arith::ConstantOp>(addRvalOp);
5847
5848 auto cstDense = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
5849 if (!cstDense)
5850 return false;
5851
5852 if (cstDense.getSplatValue<APFloat>().convertToFloat() != 1.0f)
5853 return false;
5854
5855 arith::DivFOp divOp = nullptr;
5856 for (Operation *user : addOp->getUsers()) {
5857 divOp = dyn_cast<arith::DivFOp>(user);
5858 if (divOp)
5859 break;
5860 }
5861
5862 if (!divOp)
5863 return false;
5864
5865 constOp = dyn_cast<arith::ConstantOp>(divOp.getLhs().getDefiningOp());
5866 if (!constOp)
5867 return false;
5868 cstDense = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
5869 if (!cstDense)
5870 return false;
5871 if (cstDense.getSplatValue<APFloat>().convertToFloat() != 1.0f)
5872 return false;
5873
5874 return true;
5875}
5876
5877static void configureAIEVecCommonLegalizations(ConversionTarget &target) {
5878 target
5879 .addLegalDialect<xilinx::aievec::aie1::AIEVecAIE1Dialect,
5880 xilinx::aievec::AIEVecDialect, arith::ArithDialect,
5881 ub::UBDialect, emitc::EmitCDialect, func::FuncDialect>();
5882 target.addIllegalOp<vector::ExtractStridedSliceOp>();
5883 target.addLegalOp<vector::BitCastOp>();
5884
5885 target.addDynamicallyLegalOp<arith::ExtFOp>([](arith::ExtFOp extfOp) {
5886 auto srcType = dyn_cast<VectorType>(extfOp.getIn().getType());
5887 auto dstType = dyn_cast<VectorType>(extfOp.getOut().getType());
5888 if (!srcType || !dstType)
5889 return true;
5890
5891 Type srcScalarType = srcType.getElementType();
5892 Type dstScalarType = dstType.getElementType();
5893 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
5894 return true;
5895
5896 unsigned srcLaneSize = getVectorLaneSize(srcType);
5897 unsigned dstLaneSize = getVectorLaneSize(dstType);
5898 unsigned srcElWidth = srcScalarType.getIntOrFloatBitWidth();
5899 unsigned dstElWidth = dstScalarType.getIntOrFloatBitWidth();
5900 return srcElWidth != 16 || srcLaneSize != 16 || dstElWidth != 32 ||
5901 dstLaneSize != 16;
5902 });
5903
5904 target.addDynamicallyLegalOp<arith::ExtSIOp>([](arith::ExtSIOp extsiOp) {
5905 auto srcType = dyn_cast<VectorType>(extsiOp.getIn().getType());
5906 auto dstType = dyn_cast<VectorType>(extsiOp.getOut().getType());
5907 if (!srcType || !dstType)
5908 return true;
5909
5910 Type srcScalarType = srcType.getElementType();
5911 Type dstScalarType = dstType.getElementType();
5912 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
5913 return true;
5914
5915 unsigned srcLaneSize = getVectorLaneSize(srcType);
5916 unsigned dstLaneSize = getVectorLaneSize(dstType);
5917 unsigned srcElWidth = srcScalarType.getIntOrFloatBitWidth();
5918 unsigned dstElWidth = dstScalarType.getIntOrFloatBitWidth();
5919 return srcLaneSize != 32 || (dstElWidth <= srcElWidth) ||
5920 (dstLaneSize != srcLaneSize);
5921 });
5922
5923 // Mark `arith.extui vector<Nxi4> to vector<Nxi8>` illegal precisely when
5924 // the producer is a `vector.bitcast vector<N/2xi8> to vector<Nxi4>` of a
5925 // supported lane count (64 or 128 nibbles). That is the only shape
5926 // LowerExtUIOfBitcastI4ToUnpackPattern can rewrite; stray ExtUI ops
5927 // (including i4 ones not fed by a bitcast) stay legal and pass through
5928 // to the standard arith-to-llvm path.
5929 target.addDynamicallyLegalOp<arith::ExtUIOp>([](arith::ExtUIOp extuiOp) {
5930 auto srcType = dyn_cast<VectorType>(extuiOp.getIn().getType());
5931 auto dstType = dyn_cast<VectorType>(extuiOp.getOut().getType());
5932 if (!srcType || !dstType)
5933 return true;
5934 if (srcType.getRank() != 1 || dstType.getRank() != 1)
5935 return true;
5936 Type srcScalarType = srcType.getElementType();
5937 Type dstScalarType = dstType.getElementType();
5938 if (!srcScalarType.isInteger(4) || !dstScalarType.isInteger(8))
5939 return true;
5940 unsigned lanes = srcType.getNumElements();
5941 if (lanes != 64 && lanes != 128)
5942 return true;
5943 auto bitcastOp = extuiOp.getIn().getDefiningOp<vector::BitCastOp>();
5944 if (!bitcastOp)
5945 return true;
5946 auto bitcastSrcType = dyn_cast<VectorType>(bitcastOp.getSource().getType());
5947 return !bitcastSrcType || bitcastSrcType.getRank() != 1 ||
5948 !bitcastSrcType.getElementType().isInteger(8) ||
5949 bitcastSrcType.getNumElements() * 2 != lanes;
5950 });
5951
5952 target.addDynamicallyLegalOp<arith::TruncFOp>([](arith::TruncFOp truncfOp) {
5953 auto srcType = dyn_cast<VectorType>(truncfOp.getIn().getType());
5954 auto dstType = dyn_cast<VectorType>(truncfOp.getOut().getType());
5955 if (!srcType || !dstType)
5956 return true;
5957
5958 Type srcScalarType = srcType.getElementType();
5959 Type dstScalarType = dstType.getElementType();
5960 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
5961 return true;
5962
5963 unsigned srcLaneSize = getVectorLaneSize(srcType);
5964 unsigned dstLaneSize = getVectorLaneSize(dstType);
5965 unsigned srcElWidth = srcScalarType.getIntOrFloatBitWidth();
5966 unsigned dstElWidth = dstScalarType.getIntOrFloatBitWidth();
5967 return srcElWidth != 32 || srcLaneSize != 16 || dstElWidth != 16 ||
5968 dstLaneSize != 16;
5969 });
5970
5971 target.addDynamicallyLegalOp<arith::TruncIOp>([](arith::TruncIOp trunciOp) {
5972 auto srcType = dyn_cast<VectorType>(trunciOp.getIn().getType());
5973 auto dstType = dyn_cast<VectorType>(trunciOp.getOut().getType());
5974 if (!srcType || !dstType) {
5975 // Scalar trunci: mark illegal if part of compound SRS chain
5976 // so the LowerScalarShiftClampTruncToSRS pattern can convert it.
5977 return !(!srcType && !dstType && isSRSCompoundCandidate(trunciOp));
5978 }
5979
5980 Type srcScalarType = srcType.getElementType();
5981 Type dstScalarType = dstType.getElementType();
5982 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
5983 return true;
5984
5985 // Also mark vector trunci as illegal if it's part of a compound SRS chain
5986 if (isSRSCompoundCandidate(trunciOp))
5987 return false;
5988
5989 unsigned srcLaneSize = getVectorLaneSize(srcType);
5990 unsigned dstLaneSize = getVectorLaneSize(dstType);
5991 unsigned srcElWidth = srcScalarType.getIntOrFloatBitWidth();
5992 unsigned dstElWidth = dstScalarType.getIntOrFloatBitWidth();
5993
5994 return srcLaneSize != 32 || (dstElWidth >= srcElWidth) ||
5995 (dstLaneSize != srcLaneSize);
5996 });
5997
5998 target.addDynamicallyLegalOp<math::TanhOp>([](math::TanhOp tanhOp) {
5999 auto srcType = dyn_cast<VectorType>(tanhOp.getOperand().getType());
6000 if (!srcType)
6001 return true;
6002
6003 Type scalarType = srcType.getElementType();
6004 if (!isa<FloatType>(scalarType))
6005 return true;
6006
6007 unsigned laneSize = getVectorLaneSize(srcType);
6008 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6009 return elWidth != 16 || laneSize != 16;
6010 });
6011
6012 target.addDynamicallyLegalOp<math::SqrtOp>([](math::SqrtOp sqrtOp) {
6013 auto srcType = dyn_cast<VectorType>(sqrtOp.getOperand().getType());
6014 if (!srcType)
6015 return true;
6016
6017 Type scalarType = srcType.getElementType();
6018 if (!isa<FloatType>(scalarType))
6019 return true;
6020
6021 unsigned laneSize = getVectorLaneSize(srcType);
6022 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6023 return elWidth != 16 || (laneSize != 16 && laneSize != 32);
6024 });
6025
6026 target.addDynamicallyLegalOp<math::ErfOp>([](math::ErfOp erfOp) {
6027 auto srcType = dyn_cast<VectorType>(erfOp.getOperand().getType());
6028 if (!srcType)
6029 return true;
6030
6031 Type scalarType = srcType.getElementType();
6032 if (!isa<FloatType>(scalarType))
6033 return true;
6034
6035 unsigned laneSize = getVectorLaneSize(srcType);
6036 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6037 return elWidth != 16 || (laneSize != 16 && laneSize != 32);
6038 });
6039
6040 target.addDynamicallyLegalOp<math::AbsFOp>([](math::AbsFOp absfOp) {
6041 auto srcType = dyn_cast<VectorType>(absfOp.getOperand().getType());
6042 if (!srcType)
6043 return true;
6044
6045 Type scalarType = srcType.getElementType();
6046 unsigned laneSize = getVectorLaneSize(srcType);
6047 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6048 return elWidth * laneSize != 512 && elWidth * laneSize != 256;
6049 });
6050
6051 target.addDynamicallyLegalOp<math::AbsIOp>([](math::AbsIOp absiOp) {
6052 auto srcType = dyn_cast<VectorType>(absiOp.getOperand().getType());
6053 if (!srcType)
6054 return true;
6055
6056 Type scalarType = srcType.getElementType();
6057 unsigned laneSize = getVectorLaneSize(srcType);
6058 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6059 return elWidth * laneSize != 512 && elWidth * laneSize != 256;
6060 });
6061
6062 target.addDynamicallyLegalOp<math::CeilOp>([](math::CeilOp ceilOp) {
6063 auto srcType = dyn_cast<VectorType>(ceilOp.getOperand().getType());
6064 if (!srcType)
6065 return true;
6066 Type scalarType = srcType.getElementType();
6067 if (!isa<FloatType>(scalarType))
6068 return true;
6069
6070 unsigned laneSize = getVectorLaneSize(srcType);
6071 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6072 return elWidth != 16 || (laneSize != 16 && laneSize != 32);
6073 });
6074
6075 target.addDynamicallyLegalOp<math::FloorOp>([](math::FloorOp floorOp) {
6076 auto srcType = dyn_cast<VectorType>(floorOp.getOperand().getType());
6077 if (!srcType)
6078 return true;
6079 Type scalarType = srcType.getElementType();
6080 if (!isa<FloatType>(scalarType))
6081 return true;
6082
6083 unsigned laneSize = getVectorLaneSize(srcType);
6084 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6085 return elWidth != 16 || (laneSize != 16 && laneSize != 32);
6086 });
6087
6088 target.addDynamicallyLegalOp<arith::NegFOp>([](arith::NegFOp negOp) {
6089 auto srcType = dyn_cast<VectorType>(negOp.getOperand().getType());
6090 if (!srcType)
6091 return true;
6092 if (Type scalarType = srcType.getElementType(); !isa<FloatType>(scalarType))
6093 return true;
6094
6095 unsigned laneSize = getVectorLaneSize(srcType);
6096 return laneSize != 16;
6097 });
6098
6099 target.addDynamicallyLegalOp<arith::ShRSIOp>([](arith::ShRSIOp rsOp) {
6100 auto srcType = dyn_cast<VectorType>(rsOp.getLhs().getType());
6101 if (!srcType) {
6102 // Scalar i32 shrsi: illegal unless it feeds into a compound SRS chain
6103 // (the compound pattern consumes it via the trunci anchor)
6104 if (auto intType = dyn_cast<IntegerType>(rsOp.getLhs().getType()))
6105 if (intType.getWidth() == 32) {
6106 // legal (compound pattern will handle) iff feeding a compound SRS;
6107 // otherwise illegal so the individual pattern promotes it
6108 return shrsiUsedByCompoundSRS(rsOp);
6109 }
6110 return true;
6111 }
6112
6113 // If the shrsi feeds into a compound SRS pattern (shrsi+clamp+trunc),
6114 // keep it legal — the compound pattern will consume it via the trunci.
6115 if (shrsiUsedByCompoundSRS(rsOp))
6116 return true;
6117
6118 Type scalarType = srcType.getElementType();
6119 unsigned laneSize = getVectorLaneSize(srcType);
6120 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6121
6122 return laneSize * elWidth != 512;
6123 });
6124
6125 target.addDynamicallyLegalOp<arith::AddFOp>(
6126 [](arith::AddFOp op) { return !isa<VectorType>(op.getType()); });
6127 target.addDynamicallyLegalOp<arith::SubFOp>(
6128 [](arith::SubFOp op) { return !isa<VectorType>(op.getType()); });
6129
6130 // AIE2/AIE2P GISel legalize only the scalar form of int<->fp conversions
6131 // (libcalls). Vector forms have no legalizer rule and abort with
6132 // "unable to legalize G_SITOFP <N x sX>". Mark the vector forms that
6133 // ScalarizeVectorIntFpConversionOpPattern can rewrite (1-D vectors) as
6134 // illegal so the pattern fires. Scalar and >1-D vector forms stay legal:
6135 // scalar reaches the supported libcall path directly, and >1-D forms
6136 // pass through to surface a clearer error from a later stage instead of
6137 // aborting this conversion. The scalarize pattern emits vector.extract /
6138 // vector.insert / scalar conv sequences, so those ops must be legal too.
6139 auto isRank1Vector = [](Type t) {
6140 auto vt = dyn_cast<VectorType>(t);
6141 return vt && vt.getRank() == 1;
6142 };
6143 target.addDynamicallyLegalOp<arith::SIToFPOp>(
6144 [=](arith::SIToFPOp op) { return !isRank1Vector(op.getType()); });
6145 target.addDynamicallyLegalOp<arith::UIToFPOp>(
6146 [=](arith::UIToFPOp op) { return !isRank1Vector(op.getType()); });
6147 target.addDynamicallyLegalOp<arith::FPToSIOp>(
6148 [=](arith::FPToSIOp op) { return !isRank1Vector(op.getType()); });
6149 target.addDynamicallyLegalOp<arith::FPToUIOp>(
6150 [=](arith::FPToUIOp op) { return !isRank1Vector(op.getType()); });
6151 target.addLegalOp<vector::ExtractOp, vector::InsertOp>();
6152}
6153
6154static void configureAIEVecV1Legalizations(ConversionTarget &target) {
6155 target.addDynamicallyLegalOp<arith::MulIOp>(
6156 [](arith::MulIOp op) { return !isa<VectorType>(op.getType()); });
6157 target.addDynamicallyLegalOp<arith::MulFOp>(
6158 [](arith::MulFOp op) { return !isa<VectorType>(op.getType()); });
6159 target.addDynamicallyLegalOp<aievec::aie1::FMAOp>(
6160 [](xilinx::aievec::aie1::FMAOp op) {
6161 auto *lhsDefOp = op.getLhs().getDefiningOp();
6162 aievec::ConcatOp concatOp = nullptr;
6163 if (lhsDefOp)
6164 concatOp = dyn_cast<aievec::ConcatOp>(op.getLhs().getDefiningOp());
6165 if (!concatOp)
6166 return true;
6167
6168 vector::BroadcastOp srcBcast = nullptr;
6169 if (auto *lhsOp = concatOp.getSources()[0].getDefiningOp())
6170 srcBcast = dyn_cast<vector::BroadcastOp>(lhsOp);
6171 if (!srcBcast) {
6172 auto *rhsOp = op.getRhs().getDefiningOp();
6173 if (!rhsOp)
6174 return true;
6175 srcBcast = dyn_cast<vector::BroadcastOp>(rhsOp);
6176 }
6177
6178 if (srcBcast)
6179 if (auto *srcOp = srcBcast.getSource().getDefiningOp())
6180 return !isa<vector::ExtractOp>(srcOp);
6181
6182 return true;
6183 });
6184
6185 target.addDynamicallyLegalOp<aievec::aie1::AddOp>([](aievec::aie1::AddOp op) {
6186 auto lSrsOp = op.getLhs().getDefiningOp<aievec::SRSOp>();
6187 auto rSrsOp = op.getRhs().getDefiningOp<aievec::SRSOp>();
6188 return (!lSrsOp ||
6189 !lSrsOp.getSource().getDefiningOp<aievec::aie1::MulOp>()) &&
6190 (!rSrsOp ||
6191 !rSrsOp.getSource().getDefiningOp<aievec::aie1::MulOp>());
6192 });
6193 target.addLegalDialect<memref::MemRefDialect>();
6194}
6195
6196static void
6197configureAIEVecV2PLegalizations(ConversionTarget &target,
6198 std::shared_ptr<AIE2PShufflePlanner> planner) {
6199 // AIE2P-specific legalization for rsqrt with LLVMIR backend
6200 // Vector bf16 rsqrt is illegal (no hardware support)
6201 // Scalar f32 and vector f32 rsqrt are legal (lowered in AIEVecToLLVM pass)
6202 target.addDynamicallyLegalOp<math::RsqrtOp>([](math::RsqrtOp rsqrtOp) {
6203 auto vecType = dyn_cast<VectorType>(rsqrtOp.getOperand().getType());
6204 // Vector bf16 rsqrt is illegal; everything else is legal (scalar f32,
6205 // vector f32)
6206 return !vecType || !vecType.getElementType().isBF16();
6207 });
6208
6209 // AIE2P-specific legalization for exp with LLVMIR backend
6210 // v16bf16 and v32bf16 exp are illegal (uses hardware intrinsic)
6211 target.addDynamicallyLegalOp<math::ExpOp>([](math::ExpOp expOp) {
6212 auto srcType = dyn_cast<VectorType>(expOp.getOperand().getType());
6213 if (!srcType)
6214 return true;
6215
6216 Type scalarType = srcType.getElementType();
6217 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6218 unsigned laneSize = getVectorLaneSize(srcType);
6219 // AIE2P LLVMIR: v16f32 and v32f32 are illegal (uses aievec.exp on bf16)
6220 if (scalarType.isF32())
6221 return srcType.isScalable() || (laneSize != 16 && laneSize != 32);
6222 // AIE2P LLVMIR: v16bf16 and v32bf16 are illegal (uses aievec.exp)
6223 if (!scalarType.isBF16() || (laneSize != 16 && laneSize != 32) ||
6224 elWidth != 16)
6225 return true;
6226 if (expOp->hasOneUse() && isInSigmoidOperationChain(expOp))
6227 return true;
6228
6229 return false;
6230 });
6231
6232 // AIE2P-specific legalization for tanh, erf and exp2 with LLVMIR backend
6233 // bf16 and f32 scalars, and one-, 16- and 32-lane vectors are illegal (uses
6234 // aievec.tanh or aievec.exp2)
6235 target.addDynamicallyLegalOp<math::TanhOp>([](math::TanhOp tanhOp) {
6236 return !isAIE2PAccFloatUnaryOrScalarType(tanhOp.getOperand().getType());
6237 });
6238 target.addDynamicallyLegalOp<math::ErfOp>([](math::ErfOp erfOp) {
6239 return !isAIE2PAccFloatUnaryOrScalarType(erfOp.getOperand().getType());
6240 });
6241 target.addDynamicallyLegalOp<math::Exp2Op>([](math::Exp2Op exp2Op) {
6242 return !isAIE2PAccFloatUnaryOrScalarType(exp2Op.getOperand().getType());
6243 });
6244
6245 // AIE2P-specific legalization for divf 1.0/x pattern with LLVMIR backend
6246 // Scalar f32 or vector<Nxf32> divf with constant 1.0 LHS is illegal
6247 target.addDynamicallyLegalOp<arith::DivFOp>([](arith::DivFOp divfOp) {
6248 Type srcType = divfOp.getLhs().getType();
6249
6250 // Check if LHS is defined by a constant operation
6251 auto constOp =
6252 dyn_cast_or_null<arith::ConstantOp>(divfOp.getLhs().getDefiningOp());
6253 if (!constOp)
6254 return true;
6255
6256 // Scalar f32 case - check for exactly 1.0
6257 if (srcType.isF32()) {
6258 auto floatAttr = dyn_cast<FloatAttr>(constOp.getValue());
6259 // illegal (will be converted to aievec.inv) iff exactly 1.0
6260 return !floatAttr || !floatAttr.getValue().isExactlyValue(1.0);
6261 }
6262
6263 // Vector f32 case - check for splat of exactly 1.0
6264 if (auto vecType = dyn_cast<VectorType>(srcType)) {
6265 if (vecType.getElementType().isF32()) {
6266 unsigned laneSize = getVectorLaneSize(vecType);
6267 if (laneSize == 16 || laneSize == 32) {
6268 auto denseAttr = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
6269 if (denseAttr && denseAttr.isSplat() &&
6270 denseAttr.getSplatValue<APFloat>().isExactlyValue(1.0))
6271 return false; // illegal - will be converted to aievec.inv
6272 }
6273 }
6274 }
6275
6276 return true;
6277 });
6278
6279 // AIE2P-specific legalization: ExtFOp on vector is always illegal
6280 target.addDynamicallyLegalOp<arith::ExtFOp>([](arith::ExtFOp extfOp) {
6281 auto srcType = dyn_cast<VectorType>(extfOp.getIn().getType());
6282 auto dstType = dyn_cast<VectorType>(extfOp.getOut().getType());
6283 if (!srcType || !dstType)
6284 return true;
6285
6286 Type srcScalarType = srcType.getElementType();
6287 Type dstScalarType = dstType.getElementType();
6288 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
6289 return true;
6290
6291 unsigned srcLaneSize = getVectorLaneSize(srcType);
6292 unsigned dstLaneSize = getVectorLaneSize(dstType);
6293 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6294 });
6295
6296 // AIE2P-specific legalization: TruncFOp on vector is always illegal
6297 target.addDynamicallyLegalOp<arith::TruncFOp>([](arith::TruncFOp truncfOp) {
6298 auto srcType = dyn_cast<VectorType>(truncfOp.getIn().getType());
6299 auto dstType = dyn_cast<VectorType>(truncfOp.getOut().getType());
6300 if (!srcType || !dstType)
6301 return true;
6302
6303 Type srcScalarType = srcType.getElementType();
6304 Type dstScalarType = dstType.getElementType();
6305 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
6306 return true;
6307
6308 unsigned srcLaneSize = getVectorLaneSize(srcType);
6309 unsigned dstLaneSize = getVectorLaneSize(dstType);
6310 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6311 });
6312
6313 // AIE2P-specific legalization: ExtSIOp on vector is always illegal
6314 target.addDynamicallyLegalOp<arith::ExtSIOp>([](arith::ExtSIOp extsiOp) {
6315 auto srcType = dyn_cast<VectorType>(extsiOp.getIn().getType());
6316 auto dstType = dyn_cast<VectorType>(extsiOp.getOut().getType());
6317 if (!srcType || !dstType)
6318 return true;
6319
6320 Type srcScalarType = srcType.getElementType();
6321 Type dstScalarType = dstType.getElementType();
6322 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
6323 return true;
6324
6325 unsigned srcLaneSize = getVectorLaneSize(srcType);
6326 unsigned dstLaneSize = getVectorLaneSize(dstType);
6327 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6328 });
6329
6330 // AIE2P-specific legalization: TruncIOp on vector is always illegal
6331 target.addDynamicallyLegalOp<arith::TruncIOp>([](arith::TruncIOp trunciOp) {
6332 auto srcType = dyn_cast<VectorType>(trunciOp.getIn().getType());
6333 auto dstType = dyn_cast<VectorType>(trunciOp.getOut().getType());
6334 if (!srcType || !dstType) {
6335 // Scalar trunci: mark illegal if part of compound SRS chain
6336 return !(!srcType && !dstType && isSRSCompoundCandidate(trunciOp));
6337 }
6338 Type srcScalarType = srcType.getElementType();
6339 Type dstScalarType = dstType.getElementType();
6340 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
6341 return true;
6342
6343 // Also mark as illegal if it's part of a shrsi+clamp+trunc SRS pattern,
6344 // even for sub-AIE-width vectors
6345 if (isSRSCompoundCandidate(trunciOp))
6346 return false;
6347
6348 unsigned srcLaneSize = getVectorLaneSize(srcType);
6349 unsigned dstLaneSize = getVectorLaneSize(dstType);
6350 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6351 });
6352
6353 // AIE2P-specific legalization: Override AddFOp to support laneSize==32 for
6354 // float types, and bf16 and f32 at the widths getAIE2PNativeLanes gives.
6355 target.addDynamicallyLegalOp<arith::AddFOp>([](arith::AddFOp op) {
6356 auto resultType = dyn_cast<VectorType>(op.getType());
6357 if (!resultType)
6358 return true;
6359
6360 Type scalarType = resultType.getElementType();
6361 unsigned laneSize = getVectorLaneSize(resultType);
6362
6363 if (scalarType.isBF16() || scalarType.isF32())
6364 return !isAIE2PNativeWidth(op);
6365
6366 // For other float types, support both laneSize==16 and laneSize==32
6367 if (isa<FloatType>(scalarType))
6368 return laneSize != 16 && laneSize != 32;
6369
6370 // For other types, only laneSize==16 (same as AIE2)
6371 return laneSize != 16;
6372 });
6373
6374 // AIE2P-specific legalization: Override SubFOp to support laneSize==32 for
6375 // float types, and bf16 and f32 at the widths getAIE2PNativeLanes gives.
6376 target.addDynamicallyLegalOp<arith::SubFOp>([](arith::SubFOp op) {
6377 auto resultType = dyn_cast<VectorType>(op.getType());
6378 if (!resultType)
6379 return true;
6380
6381 Type scalarType = resultType.getElementType();
6382 unsigned laneSize = getVectorLaneSize(resultType);
6383
6384 if (scalarType.isBF16() || scalarType.isF32())
6385 return !isAIE2PNativeWidth(op);
6386
6387 // For other float types, support both laneSize==16 and laneSize==32
6388 if (isa<FloatType>(scalarType))
6389 return laneSize != 16 && laneSize != 32;
6390
6391 // For other types, only laneSize==16 (same as AIE2)
6392 return laneSize != 16;
6393 });
6394
6395 // AIE2P-specific legalization: Override NegFOp to support laneSize==32 for
6396 // bf16 and f32.
6397 target.addDynamicallyLegalOp<arith::NegFOp>([](arith::NegFOp negOp) {
6398 auto srcType = dyn_cast<VectorType>(negOp.getOperand().getType());
6399 if (!srcType)
6400 return true;
6401
6402 Type scalarType = srcType.getElementType();
6403 unsigned laneSize = getVectorLaneSize(srcType);
6404
6405 // bf16 and f32 only: the aievec lowering needs an f32 accumulator, and
6406 // other 16-bit floats (e.g. f16) would take the bf16 UPS path and be
6407 // reinterpreted.
6408 if (!scalarType.isBF16() && !scalarType.isF32())
6409 return true;
6410
6411 // 16 lanes as before, at whatever rank the common config already took.
6412 if (laneSize == 16)
6413 return false;
6414
6415 // 32 only at rank 1. `getVectorLaneSize` is the product of every
6416 // dimension, so vector<2x16xf32> counts 32 as well; the widening is
6417 // scoped to the rank the aievec patterns match, and leaves n-D negates
6418 // as they were before this branch.
6419 return laneSize != 32 || srcType.getRank() != 1;
6420 });
6421
6422 // AIE2P-specific legalization: Override MulFOp to narrow the mul+add FMA
6423 // exemption to rank 1. The pattern that fuses the pair matches a rank-1
6424 // multiply, so above rank 1 the multiply has to stay illegal and be
6425 // flattened first.
6426 target.addDynamicallyLegalOp<arith::MulFOp>([](arith::MulFOp op) {
6427 auto resultType = dyn_cast<VectorType>(op.getType());
6428 if (!resultType)
6429 return true;
6430
6431 auto isAddOp = [](Operation *user) { return isa<arith::AddFOp>(user); };
6432 if (resultType.getRank() == 1 && op->hasOneUse() &&
6433 llvm::any_of(op->getUsers(), isAddOp) &&
6434 !(holdsBF16(op.getLhs()) && holdsBF16(op.getRhs())))
6435 return true;
6436
6437 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6438 unsigned laneSize = getVectorLaneSize(resultType);
6439
6440 if (laneSize == 16 && (resultElWidth == 16 || resultElWidth == 32))
6441 return false; // illegal - will be converted
6442 if (laneSize == 32 && resultElWidth == 16)
6443 return false; // illegal - will be split into two v16bf16 ops
6444 if ((laneSize == 32 || laneSize == 64) && resultElWidth == 32 &&
6445 resultType.getRank() == 1 && holdsBF16(op.getLhs()) &&
6446 holdsBF16(op.getRhs()))
6447 return false; // illegal - bf16 inputs, f32 product
6448
6449 return true; // legal - not supported
6450 });
6451
6452 // A vector.shuffle stays legal for LLVM unless the shuffle planner has a
6453 // cheaper lowering. Moves of whole register halves, such as the splitting
6454 // and joining shuffles LowerVectorSIToFPI16BF16AIE2pPattern emits, always
6455 // stay.
6456 configureAIE2PShufflePlannerLegality(target, std::move(planner));
6457}
6458
6459static void configureAIEVecV2Legalizations(ConversionTarget &target) {
6460 target.addLegalOp<UnrealizedConversionCastOp>();
6461 target.addLegalOp<vector::ShapeCastOp>();
6462
6463 // A set recording the vector lane size and element width supported
6464 llvm::SmallSet<std::pair<unsigned, unsigned>, 16> laneSizeElWidthPairSet;
6465 laneSizeElWidthPairSet.insert({64, 8});
6466 laneSizeElWidthPairSet.insert({32, 16});
6467 laneSizeElWidthPairSet.insert({16, 32});
6468 laneSizeElWidthPairSet.insert({32, 32});
6469
6470 // A set recording the element width supported
6471 llvm::SmallSet<unsigned, 16> elWidthSet;
6472 elWidthSet.insert(8);
6473 elWidthSet.insert(16);
6474 elWidthSet.insert(32);
6475
6476 target.addDynamicallyLegalOp<arith::AddFOp>([](arith::AddFOp op) {
6477 auto resultType = dyn_cast<VectorType>(op.getType());
6478 if (!resultType)
6479 return true;
6480
6481 Type scalarType = resultType.getElementType();
6482 unsigned laneSize = getVectorLaneSize(resultType);
6483 unsigned resultElWidth = scalarType.getIntOrFloatBitWidth();
6484
6485 // Support laneSize == 16 for f32/bf16
6486 if (laneSize == 16)
6487 return false; // illegal - will be converted
6488 // Support laneSize == 32 for bf16 (split into two v16bf16 ops)
6489 if (laneSize == 32 && resultElWidth == 16)
6490 return false; // illegal - will be split
6491 // Support laneSize == 32 for f32 (split into two v16f32 ops)
6492 if (laneSize == 32 && resultElWidth == 32)
6493 return false; // illegal - will be split into two v16f32 ops
6494
6495 return true; // legal - not supported
6496 });
6497
6498 target.addDynamicallyLegalOp<arith::SubFOp>([](arith::SubFOp op) {
6499 auto resultType = dyn_cast<VectorType>(op.getType());
6500 if (!resultType)
6501 return true;
6502
6503 Type scalarType = resultType.getElementType();
6504 unsigned laneSize = getVectorLaneSize(resultType);
6505 unsigned resultElWidth = scalarType.getIntOrFloatBitWidth();
6506
6507 // Support laneSize == 16 for f32/bf16
6508 if (laneSize == 16)
6509 return false; // illegal - will be converted
6510 // Support laneSize == 32 for bf16 (split into two v16bf16 ops)
6511 if (laneSize == 32 && resultElWidth == 16)
6512 return false; // illegal - will be split
6513 // Support laneSize == 32 for f32 (split into two v16f32 ops)
6514 if (laneSize == 32 && resultElWidth == 32)
6515 return false; // illegal - will be split into two v16f32 ops
6516
6517 return true; // legal - not supported
6518 });
6519
6520 target.addDynamicallyLegalOp<arith::MulIOp>([](arith::MulIOp op) {
6521 auto resultType = dyn_cast<VectorType>(op.getType());
6522 if (!resultType)
6523 return true;
6524 auto isAddOp = [&](Operation *op) { return isa<arith::AddIOp>(op); };
6525 // Verify it is not a part of MAC
6526 if (op->hasOneUse() && llvm::any_of(op->getUsers(), isAddOp))
6527 return true;
6528
6529 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6530 unsigned laneSize = getVectorLaneSize(resultType);
6531
6532 return (laneSize != 32 || (resultElWidth != 16 && resultElWidth != 8)) &&
6533 ((laneSize != 16 && laneSize != 32) || resultElWidth != 32);
6534 });
6535
6536 target.addDynamicallyLegalOp<arith::MulFOp>([](arith::MulFOp op) {
6537 auto resultType = dyn_cast<VectorType>(op.getType());
6538 if (!resultType)
6539 return true;
6540
6541 auto isAddOp = [&](Operation *op) { return isa<arith::AddFOp>(op); };
6542 // Verify it is not a part of FMA
6543 if (op->hasOneUse() && llvm::any_of(op->getUsers(), isAddOp) &&
6544 !(holdsBF16(op.getLhs()) && holdsBF16(op.getRhs())))
6545 return true;
6546
6547 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6548 unsigned laneSize = getVectorLaneSize(resultType);
6549
6550 // Support laneSize == 16 for bf16/f32, and laneSize == 32 for bf16 (split)
6551 if (laneSize == 16 && (resultElWidth == 16 || resultElWidth == 32))
6552 return false; // illegal - will be converted
6553 if (laneSize == 32 && resultElWidth == 16)
6554 return false; // illegal - will be split into two v16bf16 ops
6555 if (laneSize == 32 && resultElWidth == 32 && holdsBF16(op.getLhs()) &&
6556 holdsBF16(op.getRhs()))
6557 return false; // illegal - bf16 inputs, f32 product
6558
6559 return true; // legal - not supported
6560 });
6561
6562 target.addDynamicallyLegalOp<arith::MinSIOp>([=](arith::MinSIOp op) {
6563 auto resultType = dyn_cast<VectorType>(op.getType());
6564 if (!resultType) {
6565 // Scalar i8/i16/i32 minsi: illegal unless in compound SRS chain
6566 if (auto intType = dyn_cast<IntegerType>(op.getType())) {
6567 unsigned w = intType.getWidth();
6568 if (w == 8 || w == 16 || w == 32) {
6569 // legal (compound pattern consumes) iff feeding a compound SRS;
6570 // otherwise illegal so the individual pattern promotes it
6571 return scalarClampInCompoundSRS(op);
6572 }
6573 }
6574 return true;
6575 }
6576
6577 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6578 unsigned laneSize = getVectorLaneSize(resultType);
6579
6580 return !elWidthSet.count(resultElWidth) || laneSize * resultElWidth != 512;
6581 });
6582
6583 target.addDynamicallyLegalOp<arith::MaxSIOp>([=](arith::MaxSIOp op) {
6584 auto resultType = dyn_cast<VectorType>(op.getType());
6585 if (!resultType) {
6586 // Scalar i8/i16/i32 maxsi: illegal unless in compound SRS chain
6587 if (auto intType = dyn_cast<IntegerType>(op.getType())) {
6588 unsigned w = intType.getWidth();
6589 if (w == 8 || w == 16 || w == 32) {
6590 // legal (compound pattern consumes) iff feeding a compound SRS;
6591 // otherwise illegal so the individual pattern promotes it
6592 return scalarClampInCompoundSRS(op);
6593 }
6594 }
6595 return true;
6596 }
6597
6598 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6599 unsigned laneSize = getVectorLaneSize(resultType);
6600
6601 return !elWidthSet.count(resultElWidth) || laneSize * resultElWidth != 512;
6602 });
6603
6604 target.addDynamicallyLegalOp<arith::MinUIOp>([=](arith::MinUIOp op) {
6605 auto resultType = dyn_cast<VectorType>(op.getType());
6606 if (!resultType)
6607 return true;
6608
6609 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6610 unsigned laneSize = getVectorLaneSize(resultType);
6611 unsigned totalBits = laneSize * resultElWidth;
6612
6613 return !elWidthSet.count(resultElWidth) ||
6614 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6615 });
6616
6617 target.addDynamicallyLegalOp<arith::MaxUIOp>([=](arith::MaxUIOp op) {
6618 auto resultType = dyn_cast<VectorType>(op.getType());
6619 if (!resultType)
6620 return true;
6621
6622 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6623 unsigned laneSize = getVectorLaneSize(resultType);
6624 unsigned totalBits = laneSize * resultElWidth;
6625
6626 return !elWidthSet.count(resultElWidth) ||
6627 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6628 });
6629
6630 target.addDynamicallyLegalOp<arith::MinimumFOp>([=](arith::MinimumFOp op) {
6631 auto resultType = dyn_cast<VectorType>(op.getType());
6632 if (!resultType)
6633 return true;
6634
6635 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6636 unsigned laneSize = getVectorLaneSize(resultType);
6637 unsigned totalBits = laneSize * resultElWidth;
6638
6639 return !elWidthSet.count(resultElWidth) ||
6640 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6641 });
6642
6643 target.addDynamicallyLegalOp<arith::MaximumFOp>([=](arith::MaximumFOp op) {
6644 auto resultType = dyn_cast<VectorType>(op.getType());
6645 if (!resultType)
6646 return true;
6647
6648 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6649 unsigned laneSize = getVectorLaneSize(resultType);
6650 unsigned totalBits = laneSize * resultElWidth;
6651
6652 return !elWidthSet.count(resultElWidth) ||
6653 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6654 });
6655
6656 target.addDynamicallyLegalOp<arith::MaxNumFOp>([=](arith::MaxNumFOp op) {
6657 auto resultType = dyn_cast<VectorType>(op.getType());
6658 if (!resultType)
6659 return true;
6660
6661 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6662 unsigned laneSize = getVectorLaneSize(resultType);
6663 unsigned totalBits = laneSize * resultElWidth;
6664
6665 return !elWidthSet.count(resultElWidth) ||
6666 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6667 });
6668
6669 target.addDynamicallyLegalOp<arith::MinNumFOp>([=](arith::MinNumFOp op) {
6670 auto resultType = dyn_cast<VectorType>(op.getType());
6671 if (!resultType)
6672 return true;
6673
6674 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6675 unsigned laneSize = getVectorLaneSize(resultType);
6676 unsigned totalBits = laneSize * resultElWidth;
6677
6678 return !elWidthSet.count(resultElWidth) ||
6679 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6680 });
6681
6682 target.addDynamicallyLegalOp<arith::CmpIOp>([=](arith::CmpIOp op) {
6683 auto lhsType = dyn_cast<VectorType>(op.getLhs().getType());
6684 if (!lhsType)
6685 return true;
6686
6687 auto lhsElWidth = lhsType.getElementType().getIntOrFloatBitWidth();
6688 unsigned laneSize = getVectorLaneSize(lhsType);
6689 unsigned totalBits = laneSize * lhsElWidth;
6690
6691 return !elWidthSet.count(lhsElWidth) ||
6692 (totalBits != 512 && (totalBits != 256 || lhsElWidth != 16));
6693 });
6694
6695 target.addDynamicallyLegalOp<arith::CmpFOp>([=](arith::CmpFOp op) {
6696 auto lhsType = dyn_cast<VectorType>(op.getLhs().getType());
6697 if (!lhsType)
6698 return true;
6699
6700 auto lhsElWidth = lhsType.getElementType().getIntOrFloatBitWidth();
6701 unsigned laneSize = getVectorLaneSize(lhsType);
6702 unsigned totalBits = laneSize * lhsElWidth;
6703
6704 return !elWidthSet.count(lhsElWidth) ||
6705 (totalBits != 512 && (totalBits != 256 || lhsElWidth != 16));
6706 });
6707
6708 target.addDynamicallyLegalOp<arith::SelectOp>([=](arith::SelectOp op) {
6709 auto resultType = dyn_cast<VectorType>(op.getType());
6710 if (!resultType)
6711 return true;
6712
6713 auto resultElWidth = resultType.getElementType().getIntOrFloatBitWidth();
6714 unsigned laneSize = getVectorLaneSize(resultType);
6715 unsigned totalBits = laneSize * resultElWidth;
6716
6717 return !elWidthSet.count(resultElWidth) ||
6718 (totalBits != 512 && (totalBits != 256 || resultElWidth != 16));
6719 });
6720
6721 target.addDynamicallyLegalOp<vector::ReductionOp>(
6722 [=](vector::ReductionOp op) {
6723 if (auto kind = op.getKind(); kind != vector::CombiningKind::ADD &&
6724 kind != vector::CombiningKind::MINSI &&
6725 kind != vector::CombiningKind::MINUI &&
6726 kind != vector::CombiningKind::MINIMUMF &&
6727 kind != vector::CombiningKind::MINNUMF &&
6728 kind != vector::CombiningKind::MAXSI &&
6729 kind != vector::CombiningKind::MAXUI &&
6730 kind != vector::CombiningKind::MAXIMUMF &&
6731 kind != vector::CombiningKind::MAXNUMF)
6732 return true;
6733
6734 auto vType = dyn_cast<VectorType>(op.getVector().getType());
6735 if (!vType)
6736 return true;
6737
6738 llvm::SmallSet<std::pair<unsigned, signed>, 16> laneSizeElWidthPairSet;
6739 laneSizeElWidthPairSet.insert({64, 8});
6740 laneSizeElWidthPairSet.insert({32, 16});
6741 laneSizeElWidthPairSet.insert({32, 32});
6742 laneSizeElWidthPairSet.insert({16, 32});
6743
6744 Type scalarType = vType.getElementType();
6745 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6746 unsigned laneSize = getVectorLaneSize(vType);
6747
6748 if (isa<IntegerType>(scalarType) &&
6749 !laneSizeElWidthPairSet.count(std::make_pair(laneSize, elWidth)))
6750 return true;
6751
6752 if (isa<FloatType>(scalarType) && laneSize != 16 && laneSize != 32)
6753 return true;
6754
6755 return false;
6756 });
6757
6758 // AIE2-specific legalization: ExtFOp on vector is always illegal
6759 target.addDynamicallyLegalOp<arith::ExtFOp>([](arith::ExtFOp extfOp) {
6760 auto srcType = dyn_cast<VectorType>(extfOp.getIn().getType());
6761 auto dstType = dyn_cast<VectorType>(extfOp.getOut().getType());
6762 if (!srcType || !dstType)
6763 return true;
6764
6765 Type srcScalarType = srcType.getElementType();
6766 Type dstScalarType = dstType.getElementType();
6767 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
6768 return true;
6769
6770 unsigned srcLaneSize = getVectorLaneSize(srcType);
6771 unsigned dstLaneSize = getVectorLaneSize(dstType);
6772 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6773 });
6774
6775 // AIE2-specific legalization: TruncFOp on vector is always illegal
6776 target.addDynamicallyLegalOp<arith::TruncFOp>([](arith::TruncFOp truncfOp) {
6777 auto srcType = dyn_cast<VectorType>(truncfOp.getIn().getType());
6778 auto dstType = dyn_cast<VectorType>(truncfOp.getOut().getType());
6779 if (!srcType || !dstType)
6780 return true;
6781
6782 Type srcScalarType = srcType.getElementType();
6783 Type dstScalarType = dstType.getElementType();
6784 if (!isa<FloatType>(srcScalarType) || !isa<FloatType>(dstScalarType))
6785 return true;
6786
6787 unsigned srcLaneSize = getVectorLaneSize(srcType);
6788 unsigned dstLaneSize = getVectorLaneSize(dstType);
6789 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6790 });
6791
6792 // AIE2-specific legalization: ExtSIOp on vector is always illegal
6793 target.addDynamicallyLegalOp<arith::ExtSIOp>([](arith::ExtSIOp extsiOp) {
6794 auto srcType = dyn_cast<VectorType>(extsiOp.getIn().getType());
6795 auto dstType = dyn_cast<VectorType>(extsiOp.getOut().getType());
6796 if (!srcType || !dstType)
6797 return true;
6798
6799 Type srcScalarType = srcType.getElementType();
6800 Type dstScalarType = dstType.getElementType();
6801 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
6802 return true;
6803
6804 unsigned srcLaneSize = getVectorLaneSize(srcType);
6805 unsigned dstLaneSize = getVectorLaneSize(dstType);
6806 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6807 });
6808
6809 // AIE2-specific legalization: TruncIOp on vector is always illegal
6810 target.addDynamicallyLegalOp<arith::TruncIOp>([](arith::TruncIOp trunciOp) {
6811 auto srcType = dyn_cast<VectorType>(trunciOp.getIn().getType());
6812 auto dstType = dyn_cast<VectorType>(trunciOp.getOut().getType());
6813 if (!srcType || !dstType) {
6814 // Scalar trunci: mark illegal if part of compound SRS chain
6815 return !(!srcType && !dstType && isSRSCompoundCandidate(trunciOp));
6816 }
6817 Type srcScalarType = srcType.getElementType();
6818 Type dstScalarType = dstType.getElementType();
6819 if (!isa<IntegerType>(srcScalarType) || !isa<IntegerType>(dstScalarType))
6820 return true;
6821
6822 // Also mark as illegal if it's part of a shrsi+clamp+trunc SRS pattern,
6823 // even for sub-AIE-width vectors
6824 if (isSRSCompoundCandidate(trunciOp))
6825 return false;
6826
6827 unsigned srcLaneSize = getVectorLaneSize(srcType);
6828 unsigned dstLaneSize = getVectorLaneSize(dstType);
6829 return (srcLaneSize % 16 != 0) || (dstLaneSize % 16 != 0);
6830 });
6831
6832 target.addIllegalOp<vector::ContractionOp, vector::TransposeOp,
6833 vector::FMAOp>();
6834
6835 // AIE2-specific legalization: math.exp for v16bf16 and v32bf16 is illegal
6836 // (uses LUT)
6837 target.addDynamicallyLegalOp<math::ExpOp>([](math::ExpOp expOp) {
6838 auto srcType = dyn_cast<VectorType>(expOp.getOperand().getType());
6839 if (!srcType)
6840 return true;
6841
6842 Type scalarType = srcType.getElementType();
6843 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6844 unsigned laneSize = getVectorLaneSize(srcType);
6845 // AIE2: v16bf16 and v32bf16 are illegal (uses LUT-based lowering)
6846 if (!isa<FloatType>(scalarType) || (laneSize != 16 && laneSize != 32) ||
6847 elWidth != 16)
6848 return true;
6849 if (expOp->hasOneUse() && isInSigmoidOperationChain(expOp))
6850 return true;
6851
6852 return false;
6853 });
6854
6855 target.addDynamicallyLegalOp<math::RsqrtOp>([](math::RsqrtOp rsqrtOp) {
6856 auto srcType = dyn_cast<VectorType>(rsqrtOp.getOperand().getType());
6857 if (!srcType)
6858 return true;
6859
6860 Type scalarType = srcType.getElementType();
6861 if (!isa<FloatType>(scalarType))
6862 return true;
6863
6864 unsigned laneSize = getVectorLaneSize(srcType);
6865 unsigned elWidth = scalarType.getIntOrFloatBitWidth();
6866 return elWidth != 16 || (laneSize != 16 && laneSize != 32);
6867 });
6868}
6869
6870//===----------------------------------------------------------------------===//
6871// Lowering passes
6872//===----------------------------------------------------------------------===//
6873
6874/// Lower incoming vector operations into their corresponding AIE vector
6875/// intrinsics.
6876struct LowerVectorToAIEVec : PassWrapper<LowerVectorToAIEVec, OperationPass<>> {
6877 MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(LowerVectorToAIEVec)
6878
6881
6886
6887 // In case we want to register this pass as a standalone pass for test
6888 // purposes.
6889 StringRef getArgument() const final { return "test-lower-vector-to-aievec"; }
6890 StringRef getDescription() const final {
6891 return "Lower vector operations to AIE vector intrinsics";
6892 }
6893 void getDependentDialects(DialectRegistry &registry) const override {
6894 registry.insert<
6895 affine::AffineDialect, xilinx::aievec::aie1::AIEVecAIE1Dialect,
6896 xilinx::aievec::AIEVecDialect, arith::ArithDialect,
6897 memref::MemRefDialect, scf::SCFDialect, ub::UBDialect,
6898 vector::VectorDialect, emitc::EmitCDialect, xilinx::AIE::AIEDialect>();
6899 }
6900
6901 Option<std::string> aieTarget{
6902 *this, "aie-target",
6903 llvm::cl::desc(
6904 "Select AIE version: \"aie\", \"aie2\", or \"aie2p\". This will "
6905 "determine the vector size and available operations."),
6906 llvm::cl::init("aie")};
6907
6908 void runOnOperation() override {
6909 auto *op = getOperation();
6910 MLIRContext *context = &getContext();
6911 RewritePatternSet patterns(context);
6912 ConversionTarget target(*context);
6913
6914 // `aie.runtime_sequence` bodies are host-side control code (either NPU
6915 // instruction streams or C++ TXN builders), not AIE-core compute. Vector
6916 // and AIEVec ops are meaningless there, so leave anything nested inside a
6917 // runtime sequence untouched regardless of the legalizations configured
6918 // below.
6919 target.addLegalOp<xilinx::AIE::RuntimeSequenceOp>();
6920 target.markOpRecursivelyLegal<xilinx::AIE::RuntimeSequenceOp>();
6921
6922 auto aieVersion = AIEArch::AIE;
6923 if (!aieTarget.empty()) {
6924 std::string targetStr = aieTarget;
6925 if (targetStr == "aieml" || targetStr == "aie2")
6926 aieVersion = AIEArch::AIE2;
6927 else if (targetStr == "aie2p")
6928 aieVersion = AIEArch::AIE2P;
6929 else if (targetStr != "aie") {
6930 op->emitError() << "unknown AIE target '" << aieTarget << "'";
6931 return signalPassFailure();
6932 }
6933 }
6934
6935 populateAIEVecCommonConversionPatterns(patterns);
6936 configureAIEVecCommonLegalizations(target);
6937 if (aieVersion == AIEArch::AIE) {
6938 populateAIEVecV1ConversionPatterns(patterns);
6939 configureAIEVecV1Legalizations(target);
6940 } else if (aieVersion == AIEArch::AIE2) {
6941 populateAIEVecV2ConversionPatterns(patterns);
6942 configureAIEVecV2Legalizations(target);
6943 } else if (aieVersion == AIEArch::AIE2P) {
6944 auto planner = std::make_shared<AIE2PShufflePlanner>();
6945 populateAIEVecV2PConversionPatterns(patterns, planner);
6946 configureAIEVecV2Legalizations(target);
6947 configureAIEVecV2PLegalizations(target, planner);
6948 // Last, as it overrides the predicates above for the ops it rewrites.
6949 AIE2PElementwise::markIllegal(target, &getContext());
6950 } else {
6951 llvm_unreachable("AIE version is misconfigured");
6952 }
6953
6954 if (failed(applyPartialConversion(op, target, std::move(patterns))))
6955 return signalPassFailure();
6956 }
6957};
6958
6959static std::unique_ptr<Pass>
6960createLowerVectorToAIEVec(const LowerVectorToAIEVecOptions &options) {
6961 return std::make_unique<LowerVectorToAIEVec>(options);
6962}
6963
6964//===---------------------------------------------------------------------------
6965// Custom canonicalization passes
6966//===---------------------------------------------------------------------------
6967
6968// This pass widens UPD ops to twice the width followed by an ext op of the
6969// bottom half. This can be used together with SimplifyUPDOpsPass to find
6970// additional common subexpressions with UPDs generated from unaligned
6971// `transfer_read` ops.
6972struct ExtendUPDOpsPass : PassWrapper<ExtendUPDOpsPass, OperationPass<>> {
6973
6974 void runOnOperation() override {
6975 MLIRContext *context = &getContext();
6976 RewritePatternSet patterns(context);
6977 ConversionTarget target(*context);
6978 patterns.add<ExpandUPDToUPDAndExtPattern>(patterns.getContext());
6979 target.addLegalDialect<aievec::AIEVecDialect>();
6980 target.addDynamicallyLegalOp<aievec::UPDOp>([](aievec::UPDOp op) {
6981 return op.getVector() ||
6982 (op->hasOneUse() && isa<aievec::UPDOp>(*op->getUsers().begin())) ||
6983 llvm::all_of(op->getUsers(),
6984 [](Operation *op) { return isa<aievec::ExtOp>(op); });
6985 });
6986
6987 if (auto *op = getOperation();
6988 failed(applyPartialConversion(op, target, std::move(patterns)))) {
6989 return signalPassFailure();
6990 }
6991 }
6992};
6993
6994// This pass replaces wide UPD ops that are only used by a single ext op of the
6995// bottom half. This pass undos the work of ExtendUPDOpsPass.
6996// TODO: This pass can be extended to work with wide UPD ops that are used by
6997// TODO: a single ext op of the top half, which might be a good opportunity to
6998// TODO: further optimize wide UPDs.
6999struct SimplifyUPDOpsPass : PassWrapper<SimplifyUPDOpsPass, OperationPass<>> {
7000
7001 void runOnOperation() override {
7002 MLIRContext *context = &getContext();
7003 RewritePatternSet patterns(context);
7004 ConversionTarget target(*context);
7005 patterns.add<FuseExtIntoUPDPattern>(patterns.getContext());
7006 target.addLegalDialect<aievec::AIEVecDialect>();
7007 target.addDynamicallyLegalOp<aievec::ExtOp>([](aievec::ExtOp op) {
7008 auto *defOp = op.getSource().getDefiningOp();
7009 return !defOp || !isa<aievec::UPDOp>(defOp) || !defOp->hasOneUse() ||
7010 op.getIndex() != 0;
7011 });
7012
7013 if (auto *op = getOperation();
7014 failed(applyPartialConversion(op, target, std::move(patterns)))) {
7015 return signalPassFailure();
7016 }
7017 }
7018};
7019
7020//============================================================================//
7021//=============== Main Vector2AIEVec Pipeline Configuration ==================//
7022//============================================================================//
7023
7025 OpPassManager &pm, const LowerVectorToAIEVecOptions &options) {
7026 // Add lowering from `Vector` to `AIEVec`
7027 pm.addPass(createLowerVectorToAIEVec(options));
7028 pm.addPass(createCanonicalizerPass());
7029
7030 // Simplify UPD ops
7031 pm.addPass(std::make_unique<ExtendUPDOpsPass>());
7032 pm.addPass(createCSEPass());
7033 pm.addPass(std::make_unique<SimplifyUPDOpsPass>());
7034 pm.addPass(createCanonicalizerPass());
7035}
LowerVectorAddOrSubOpToAIEVecAddElemOrSubElemOp< arith::SubFOp, aievec::SubElemOp > LowerVectorSubFOpToAIEVecSubElemOp
LowerVectorMinMaxOpToAIEVecMinMaxOp< arith::MaxNumFOp, aievec::MaxOp > LowerVectorMaxNumFFOpToAIEVecMaxOp
LowerVectorCmpOpToAIEVecCmpOp< arith::CmpIOp, CmpIPredicate > LowerVectorCmpIOpToAIEVecCmpOp
ScalarizeVectorIntFpConversionOpPattern< arith::FPToUIOp > ScalarizeVectorFPToUIOpPattern
AIE2PElementwiseOps< arith::MulFOp, arith::DivFOp, arith::AddFOp, arith::SubFOp, arith::NegFOp, arith::TruncFOp, arith::ExtFOp, math::ExpOp, math::TanhOp, math::ErfOp, math::Exp2Op, arith::MaximumFOp, arith::MaxNumFOp, arith::MinimumFOp, arith::CmpFOp, arith::CmpIOp, arith::SelectOp, arith::AddIOp, arith::SubIOp, arith::MulIOp, arith::IndexCastOp > AIE2PElementwise
OneToOneVectorOpToAIEVecOpPattern< arith::SubIOp, aievec::aie1::SubOp > LowerVectorSubIOpToAIEVecSubOp
ScalarizeVectorIntFpConversionOpPattern< arith::FPToSIOp > ScalarizeVectorFPToSIOpPattern
OneToOneVectorOpToAIEVecOpPattern< arith::SubFOp, aievec::aie1::SubOp > LowerVectorSubFOpToAIEVecSubOp
LowerVectorUMinMaxOpToCmpSelOp< arith::MaxUIOp, arith::CmpIPredicate::ugt > LowerVectorMaxUIOpToCmpSelOp
ScalarizeVectorIntFpConversionOpPattern< arith::UIToFPOp > ScalarizeVectorUIToFPOpPattern
LowerVectorMinMaxOpToAIEVecMinMaxOp< arith::MaxSIOp, aievec::MaxOp > LowerVectorMaxSIOpToAIEVecMaxOp
LowerScalarMinMaxToAIEVecMinMaxOp< arith::MinSIOp, aievec::MinOp > LowerScalarMinSIOpToAIEVecMinOp
LowerVectorContractionOpToAIEVecMatMulPattern< aievec::MatMulOp_AIE2P > LowerVectorContractionOpToAIEVecMatMulOpAIE2P
OneToOneVectorOpToAIEVecOpPattern< arith::AddFOp, aievec::aie1::AddOp > LowerVectorAddFOpToAIEVecAddOp
ComputeAbsOpPattern< math::AbsIOp > ComputeAbsIOpPattern
constexpr int64_t kAIE2PMaxParts
LowerTruncOpPattern< arith::TruncFOp > LowerTruncFOpPattern
ScalarizeVectorIntFpConversionOpPattern< arith::SIToFPOp > ScalarizeVectorSIToFPOpPattern
ComputeBandAndBorOpPattern< arith::OrIOp, aievec::BorOp > ComputeBorOpPattern
LowerVectorAddOrSubOpToAIEVecAddElemOrSubElemOp< arith::AddFOp, aievec::AddElemOp > LowerVectorAddFOpToAIEVecAddElemOp
LowerExtOpPattern< arith::ExtFOp > LowerExtFOpPattern
OneToOneVectorOpToAIEVecOpPattern< arith::MulFOp, aievec::aie1::MulOp > LowerVectorMulFOpToAIEVecMulOp
ComputeAbsOpPattern< math::AbsFOp > ComputeAbsFOpPattern
LowerVectorAddOrSubOpToAIEVecAddElemOrSubElemOp< arith::SubIOp, aievec::SubElemOp > LowerVectorSubIOpToAIEVecSubElemOp
LowerVectorUMinMaxOpToCmpSelOp< arith::MinUIOp, arith::CmpIPredicate::ult > LowerVectorMinUIOpToCmpSelOp
ComputeBandAndBorOpPattern< arith::AndIOp, aievec::BandOp > ComputeBandOpPattern
LowerVectorContractionOpToAIEVecMatMulPattern< aievec::MatMulOp > LowerVectorContractionOpToAIEVecMatMulOpAIE2
LowerVectorMinMaxOpToAIEVecMinMaxOp< arith::MinimumFOp, aievec::MinOp > LowerVectorMinimumFOpToAIEVecMinOp
LowerVectorCmpOpToAIEVecCmpOp< arith::CmpFOp, CmpFPredicate > LowerVectorCmpFOpToAIEVecCmpOp
LowerTruncOpPattern< arith::TruncIOp > LowerTruncIOpPattern
LowerExtOpPattern< arith::ExtSIOp > LowerExtSIOpPattern
LowerVectorAddOrSubOpToAIEVecAddElemOrSubElemOp< arith::AddIOp, aievec::AddElemOp > LowerVectorAddIOpToAIEVecAddElemOp
LowerVectorMinMaxOpToAIEVecMinMaxOp< arith::MaximumFOp, aievec::MaxOp > LowerVectorMaximumFOpToAIEVecMaxOp
LowerScalarMinMaxToAIEVecMinMaxOp< arith::MaxSIOp, aievec::MaxOp > LowerScalarMaxSIOpToAIEVecMaxOp
LowerVectorMinMaxOpToAIEVecMinMaxOp< arith::MinSIOp, aievec::MinOp > LowerVectorMinSIOpToAIEVecMinOp
mlir::VectorType getFlattenedVectorType(mlir::VectorType vecTy)
std::optional< MatMulRecipe > checkMatMulTile(bool aie2p, mlir::VectorType lhsTy, mlir::VectorType rhsTy, mlir::VectorType accTy, bool lhsTransposed, bool rhsTransposed, llvm::function_ref< mlir::InFlightDiagnostic()> emitError=nullptr)
unsigned getVectorLaneSize(mlir::VectorType type)
Definition AIEVecUtils.h:54
SmallVector< NamedAttribute > buildFMAOpSplatAttrForElemTy(aievec::aie1::FMAOp fmaOp, int64_t bcastPos, int64_t step=1)
std::optional< int64_t > getTransferReadAlignmentOffset(TransferReadLikeOp readOp, mlir::VectorType vType, int64_t alignment)
void populateAIE2PShufflePlannerPatterns(mlir::RewritePatternSet &patterns, std::shared_ptr< AIE2PShufflePlanner > planner)
mlir::VectorType createVectorType(unsigned lanes, mlir::Type elementType)
Definition AIEVecUtils.h:41
int32_t getElementSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:48
void buildLowerVectorToAIEVec(mlir::OpPassManager &pm, const LowerVectorToAIEVecOptions &options)
mlir::VectorType getVectorOpDestType(mlir::VectorType type, bool AIE2)
Definition AIEVecUtils.h:79
void configureAIE2PShufflePlannerLegality(mlir::ConversionTarget &target, std::shared_ptr< AIE2PShufflePlanner > planner)
static void markIllegal(ConversionTarget &target, MLIRContext *ctx)
static void addPatterns(RewritePatternSet &patterns, PatternBenefit benefit)
static void markIllegal(ConversionTarget &target, MLIRContext *ctx)
LogicalResult matchAndRewrite(SrcOpTy absOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
typename SrcOpTy::Adaptor OpAdaptor
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::XOrIOp xorOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::CeilOp ceilOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::ErfOp erfOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::ErfOp erfOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::FloorOp floorOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::NegFOp negOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::DivFOp divfOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::ShRSIOp rsOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::SqrtOp sqrtOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::TanhOp tanhOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::DivFOp divOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(math::ExpOp expOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(MathOpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::AddFOp addOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertMulAddFToAIEVecFMAElemOpPattern(MLIRContext *context, unsigned shiftParam=0)
LogicalResult matchAndRewrite(arith::AddIOp addOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertMulAddToAIEVecFMAElemOpPattern(MLIRContext *context, unsigned shiftParam=0)
LogicalResult matchAndRewrite(aievec::aie1::AddOp addOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::MulFOp mulOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::MulFOp mulOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertMulFToAIEVecMulElemOpPattern(MLIRContext *context, unsigned shiftParam=0)
LogicalResult matchAndRewrite(arith::MulIOp mulOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertMulIToAIEVecMulElemOpPattern(MLIRContext *context, unsigned shiftParam=0)
LogicalResult matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::FMAOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::FMAOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ConvertVectorFMAOpToAIEVecFMAElemOpPattern(MLIRContext *context, unsigned shiftParam=0)
ExpandUPDToUPDAndExtPattern(MLIRContext *context)
LogicalResult matchAndRewrite(aievec::UPDOp updOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::aie1::FMAOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExtOp extOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
FuseExtIntoUPDPattern(MLIRContext *context)
typename SrcOpTy::Adaptor OpAdaptor
LogicalResult matchAndRewrite(SrcOpTy extOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::ExtUIOp extOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::ShRSIOp rsOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::TruncIOp truncOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static std::optional< int64_t > getScalarConstantValue(Value val)
LogicalResult matchAndRewrite(SrcOpTy truncOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
typename SrcOpTy::Adaptor OpAdaptor
LogicalResult matchAndRewrite(arith::AddIOp addOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static Value reshape(OpBuilder &b, Value v, ArrayRef< int64_t > shape)
LogicalResult matchAndRewrite(vector::ContractionOp contractOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ExtractStridedSliceOp extractOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ExtractStridedSliceOp extractOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::MulIOp mulOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::ReductionOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static Value emitMagicSiToFp(ConversionPatternRewriter &rewriter, Location loc, Value srcI16, unsigned lanes)
LogicalResult matchAndRewrite(arith::SIToFPOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::SelectOp srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
void getDependentDialects(DialectRegistry &registry) const override
LowerVectorToAIEVec(const LowerVectorToAIEVecOptions &options)
StringRef getDescription() const final
LowerVectorToAIEVec()=default
StringRef getArgument() const final
LogicalResult matchAndRewrite(vector::TransferReadOp readOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LowerVectorTransferReadToAIEUPD(MLIRContext *context, int64_t minVectorSize, int64_t maxVectorSize, int64_t alignment, int64_t maxLoadSize)
LogicalResult matchAndRewrite(vector::TransposeOp transpOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(OpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy srcOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ShiftClampTruncToSRSPattern(MLIRContext *context, PatternBenefit benefit=2)
static std::optional< Value > getShiftValue(Value rhs, ConversionPatternRewriter &rewriter, Location loc)
static std::optional< int64_t > getConstantSplatValue(Value val)
LogicalResult matchAndRewrite(arith::TruncIOp truncOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
Options for the "lower-vector-to-aievec" pipeline.
Definition Passes.h:50
PassOptions::Option< std::string > aieTarget
Definition Passes.h:51