MLIR-AIE 1.4.5.dev
AIEVecToLLVM.cpp
Go to the documentation of this file.
1//===- AIEVecToLLVM.cpp - AIEVec to LLVM dialect conversion ---------------===//
2//
3// Copyright (C) 2022 Xilinx, Inc.
4// Copyright (C) 2024 Advanced Micro Devices, Inc.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "../PassDetail.h"
10
19#include "mlir/Conversion/LLVMCommon/ConversionTarget.h"
20#include "mlir/Conversion/LLVMCommon/Pattern.h"
21#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
22#include "mlir/Dialect/Math/IR/Math.h"
23#include "mlir/Dialect/UB/IR/UBOps.h"
24#include "mlir/IR/TypeUtilities.h"
25#include <numeric>
26#include <sstream>
27
28namespace xilinx {
29using namespace mlir; // For LLVM::LLVMDialect in generated getDependentDialects
30#define GEN_PASS_DEF_CONVERTAIEVECTOLLVM
31#include "aie/Conversion/Passes.h.inc"
32} // namespace xilinx
33
34using namespace mlir;
35
36namespace xilinx::aievec {
37
38inline static Value bitcastValueToType(OpBuilder &builder, Location loc,
39 Value val, Type dstTy) {
40 return LLVM::BitcastOp::create(builder, loc, dstTy, val).getResult();
41}
42
43// This function emits the instructions required to widen a 128b input vector
44// into a 512b encoded as a vector<16xi32>. It first bitcasts it to a
45// vector<4xi32> to respect the intrinsic signature.
46inline static Value widen128bVectorValueTo512b(OpBuilder &builder, Location loc,
47 Value val) {
48 return xllvm::VectorSetI512I128IntrOp::create(
49 builder, loc, VectorType::get({16}, builder.getI32Type()),
50 bitcastValueToType(builder, loc, val,
51 VectorType::get({4}, builder.getI32Type())))
52 .getResult();
53}
54
55// This function emits the instructions required to widen a 256b input vector
56// into a 512b encoded as a vector<16xi32>. It first bitcasts it to a
57// vector<8xi32> to respect the intrinsic signature. It will also materialize
58// a constant 0, used as an insertion index.
59inline static Value widen256bVectorValueTo512b(OpBuilder &builder, Location loc,
60 Value val) {
61 auto cst0 =
62 LLVM::ConstantOp::create(builder, loc, builder.getI32Type(), (int32_t)0);
63 return xllvm::VectorSetI512I256IntrOp::create(
64 builder, loc, VectorType::get({16}, builder.getI32Type()),
65 bitcastValueToType(builder, loc, val,
66 VectorType::get({8}, builder.getI32Type())),
67 cst0)
68 .getResult();
69}
70
71// This function emits the sequence of operations that forces a value into a
72// specific type. This may include widening vectors to match a specific bit
73// length.
74static Value forceCastValueToType(OpBuilder &builder, Location loc, Value val,
75 Type type) {
76 auto valTy = val.getType();
77 if (valTy == type)
78 return val;
79 auto srcVecTy = dyn_cast<VectorType>(valTy);
80 auto dstVecTy = dyn_cast<VectorType>(type);
81
82 if (srcVecTy) {
83 assert(dstVecTy && "vector values cannot be forced into a non-vector type");
84
85 // Flatten source vector if it's not rank-1
86 auto flatSrcVecTy = getFlattenedVectorType(srcVecTy);
87 if (srcVecTy != flatSrcVecTy)
88 val = vector::ShapeCastOp::create(builder, loc, flatSrcVecTy, val);
89
90 // Flatten destination type if it's not rank-1
91 auto flatDstVecTy = getFlattenedVectorType(dstVecTy);
92
93 int64_t dstVecLength =
94 flatDstVecTy.getElementTypeBitWidth() * flatDstVecTy.getShape()[0];
95 int64_t srcVecLength =
96 flatSrcVecTy.getElementTypeBitWidth() * flatSrcVecTy.getShape()[0];
97 if (srcVecLength != dstVecLength) {
98 assert(srcVecLength < dstVecLength &&
99 "only widening forced casts are supported");
100 assert(dstVecLength == 512 &&
101 (srcVecLength == 128 || srcVecLength == 256) &&
102 "only 128b to 512b and 256b to 512b forced casts are supported");
103 if (srcVecLength == 128)
104 val = widen128bVectorValueTo512b(builder, loc, val);
105 else
106 val = widen256bVectorValueTo512b(builder, loc, val);
107 }
108
109 // Bitcast to flat destination type (bitcast only supports flat vectors)
110 val = bitcastValueToType(builder, loc, val, flatDstVecTy);
111
112 // Reshape back to original destination shape if needed
113 if (flatDstVecTy != dstVecTy)
114 val = vector::ShapeCastOp::create(builder, loc, dstVecTy, val);
115
116 return val;
117 }
118
119 // Non-vector types can be bitcast directly
120 assert(!dstVecTy && "cannot force cast scalar to vector type");
121 return bitcastValueToType(builder, loc, val, type);
122}
123
124// This function emits the sequence of operations that forces a range of values
125// to match the signature specified by the TypeRange. It can be used to convert
126// the parameters of an op being converted to the types accepted by an
127// intrinsic with a fixed signature that treats its inputs as "bags of bits".
128static SmallVector<Value> forceCastOperandsToSignature(OpBuilder &builder,
129 Location loc,
130 ValueRange operands,
131 TypeRange signature) {
132 return llvm::to_vector(llvm::map_range(
133 llvm::zip_equal(operands, signature), [&](auto &&vt) -> Value {
134 return forceCastValueToType(builder, loc, std::get<0>(vt),
135 std::get<1>(vt));
136 }));
137}
138
139// Utility function to get or create a noinline scalar helper function.
140// This is used to create optimization barriers that prevent LLVM from
141// re-vectorizing unrolled scalar operations.
142//
143// Parameters:
144// - module: The parent module to insert the function into
145// - rewriter: The pattern rewriter
146// - opName: Base name of the operation (e.g., "fdiv", "addf", "mulf")
147// - device: Target device ("aie2", "aie2p", etc.)
148// - argTypes: Input argument types
149// - resultType: Return type
150// - bodyBuilder: Lambda that builds the function body given (builder, loc,
151// args)
152//
153// Returns: The helper function (created or existing)
154//
155// Function naming convention: __<device>_scalar_<opName>
156// Example: __aie2p_scalar_fdiv, __aie2_scalar_addf
157static LLVM::LLVMFuncOp getOrCreateScalarHelperFunc(
158 ModuleOp module, OpBuilder &rewriter, StringRef opName, StringRef device,
159 TypeRange argTypes, Type resultType,
160 const std::function<void(OpBuilder &, Location, ValueRange)> &bodyBuilder) {
161
162 // Build function name: __<device>_scalar_<opName>
163 std::string funcName = "__" + device.str() + "_scalar_" + opName.str();
164
165 // Check if function already exists
166 auto helperFunc = module.lookupSymbol<LLVM::LLVMFuncOp>(funcName);
167 if (helperFunc)
168 return helperFunc;
169
170 // Create new function
171 OpBuilder::InsertionGuard guard(rewriter);
172 rewriter.setInsertionPointToStart(module.getBody());
173
174 // Convert TypeRange to SmallVector<Type> for LLVM::LLVMFunctionType::get
175 SmallVector<Type> argTypesVec(argTypes.begin(), argTypes.end());
176
177 helperFunc = LLVM::LLVMFuncOp::create(
178 rewriter, rewriter.getUnknownLoc(), funcName,
179 LLVM::LLVMFunctionType::get(resultType, argTypesVec));
180
181 // Mark as noinline to act as optimization barrier
182 helperFunc->setAttr("passthrough", rewriter.getArrayAttr(
183 {rewriter.getStringAttr("noinline")}));
184
185 // Add function body
186 auto *entryBlock = helperFunc.addEntryBlock(rewriter);
187 OpBuilder::InsertionGuard bodyGuard(rewriter);
188 rewriter.setInsertionPointToStart(entryBlock);
189
190 // Collect function arguments
191 SmallVector<Value> args;
192 for (unsigned i = 0; i < argTypes.size(); ++i)
193 args.push_back(entryBlock->getArgument(i));
194
195 // Call the body builder with the function arguments
196 bodyBuilder(rewriter, rewriter.getUnknownLoc(), args);
197
198 return helperFunc;
199}
200
202 uint32_t start;
203 uint32_t offsets;
204 uint32_t offsets_hi;
205 uint32_t step;
206 uint32_t square;
207};
208
209// sgn_x: Sign mask of matrix X. If it is one matrix X is interpreted as
210// signed, else it treated as unsigned.
211// sgn_y: Sign mask of matrix Y. If it is one matrix Y is interpreted as
212// signed, else it treated as unsigned.
213// amode/bmode/variant: config acc width, mul precision, and mul mode
214// zero_acc: Zeroing of acc1. If it is one then acc1 is zeroed.
215// shift16: Shift mask of acc1. If a bit is set the <<16 operation will be
216// executed on acc1.
217// sub_mul: Negation mask of the matrix multiplication result. If it is
218// one the result of the operation will be negated.
219// sub_acc1: Negation mask of acc1. If it is one acc1 will be negated.
220// sub_acc2: Negation mask of acc2. If it is one acc2 will be negated.
221// sub_mask: Negation mask of complex multiplications. Negates a term of a
222// complex multiplication.
223static inline int aiev2_vmac_compute_control(int sgn_x, int sgn_y, int amode,
224 int bmode, int variant,
225 int zero_acc, int shift16,
226 int sub_mul, int sub_acc1,
227 int sub_acc2, int sub_mask) {
228 return ((unsigned)sub_mask << 16) | ((unsigned)shift16 << 10) |
229 ((unsigned)sub_mul << 11) | ((unsigned)sub_acc1 << 12) |
230 ((unsigned)sub_acc2 << 13) | ((unsigned)amode << 1) |
231 ((unsigned)bmode << 3) | ((unsigned)variant << 5) |
232 (((unsigned)sgn_x << 9) | ((unsigned)sgn_y << 8)) |
233 ((unsigned)zero_acc << 0);
234}
235
236static std::string getVectorTypeString(VectorType type, bool abbrev = false,
237 bool acc = false) {
238 std::stringstream ss;
239 auto size = getVectorLaneSize(type);
240 ss << "v" << size;
241 if (auto intType = dyn_cast<IntegerType>(type.getElementType())) {
242 ss << (acc ? "acc" : abbrev ? "i" : "int") << intType.getWidth();
243 } else if (dyn_cast<FloatType>(type.getElementType())) {
244 ss << (abbrev ? "f" : "float");
245 }
246 return ss.str();
247}
248
249static std::string getMulOrFMAIntrinsicName(Operation *op) {
250 std::string baseName;
251 Value lhs, result;
252 if (auto mulOp = dyn_cast<aievec::aie1::MulOp>(op)) {
253 baseName = "mul";
254 lhs = mulOp.getLhs();
255 result = mulOp.getResult();
256 } else if (auto fmaOp = dyn_cast<aievec::aie1::FMAOp>(op)) {
257 baseName = "mac";
258 lhs = fmaOp.getLhs();
259 result = fmaOp.getResult();
260 }
261 VectorType resultType = cast<VectorType>(result.getType());
262 int resultSize = getVectorLaneSize(resultType);
263 std::stringstream ss;
264 ss << "llvm.aie.";
265 if (dyn_cast<IntegerType>(resultType.getElementType())) {
266 ss << baseName;
267 ss << resultSize << "."
268 << getVectorTypeString(cast<VectorType>(lhs.getType()));
269 } else if (dyn_cast<FloatType>(resultType.getElementType())) {
270 ss << "vfp" << baseName;
271 }
272 return ss.str();
273}
274
275// Squashes the easy-to-read 16-bit square encoding into
276// the 8-bit encoding the configuration register uses
277static uint32_t encodeSquare(uint32_t square) {
278 uint32_t out = 0;
279 out |= ((square >> 0) & 0x3) << 0;
280 out |= ((square >> 4) & 0x3) << 2;
281 out |= ((square >> 8) & 0x3) << 4;
282 out |= ((square >> 12) & 0x3) << 6;
283 return out & 0xFF;
284}
285
286// Encode the configuration register with buffer parameters and options
287// TODO: struct to handle this?
288static void encodeConf(uint32_t conf[2], const BufferParams &x,
289 const BufferParams &z, bool sub) {
290 conf[0] |= ((x.step & 0x3F) << 0) | ((z.step & 0x3F) << 8);
291 conf[1] |= (encodeSquare(x.square) << 0) | (encodeSquare(z.square) << 8);
292 conf[1] |= sub << 17;
293}
294
296 : public mlir::ConvertOpToLLVMPattern<aievec::aie1::AddOp> {
297public:
298 using ConvertOpToLLVMPattern<aievec::aie1::AddOp>::ConvertOpToLLVMPattern;
299
300 LogicalResult
301 matchAndRewrite(aievec::aie1::AddOp op, OpAdaptor adaptor,
302 ConversionPatternRewriter &rewriter) const override {
303 op.emitWarning() << "aie.add conversion is not implemented\n";
304 return failure();
305 }
306};
307
308// AIE2 version of AddElemOp conversion
310 : public mlir::ConvertOpToLLVMPattern<aievec::AddElemOp> {
311public:
312 using ConvertOpToLLVMPattern<aievec::AddElemOp>::ConvertOpToLLVMPattern;
313
319
320 static DecodedAddElemOp decodeAddElemOp(OpAdaptor op) {
321 auto lhs = op.getLhs();
322 auto lhsVecTy = cast<VectorType>(lhs.getType());
323 auto lhsScaTy = lhsVecTy.getElementType();
324 unsigned lhsBitWidth = lhsScaTy.getIntOrFloatBitWidth();
325
326 // Integer types
327 if (llvm::isa<IntegerType>(lhsScaTy)) {
329 } // Float types
330 if (lhsBitWidth == 32) {
331 // FP32 add_elem
333 }
334
336 }
337
338 LogicalResult
339 matchAndRewrite(aievec::AddElemOp op, OpAdaptor adaptor,
340 ConversionPatternRewriter &rewriter) const override {
341 Location loc = op.getLoc();
342 auto decodedAddElemOp = decodeAddElemOp(adaptor);
343
344 if (decodedAddElemOp.kind == DecodedAddElemOp::Kind::UNSUPPORTED) {
345 op.emitWarning() << "aievec.add_elem conversion is not supported.\n";
346 return failure();
347 }
348
349 // Handle the FP32 add_elem for AIE2 - uses packed I64 representation
350 if (decodedAddElemOp.kind ==
352 auto confCst = LLVM::ConstantOp::create(
353 rewriter, loc, rewriter.getI32Type(),
354 rewriter.getI32IntegerAttr(decodedAddElemOp.conf));
355 SmallVector<Value> operands(
356 {adaptor.getLhs(), adaptor.getRhs(), confCst});
357
358 auto addElemOp = xllvm::AddAccFloatAIE2IntrOp::create(
359 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
360 forceCastOperandsToSignature(
361 rewriter, loc, operands,
362 {VectorType::get({8}, rewriter.getI64Type()),
363 VectorType::get({8}, rewriter.getI64Type()),
364 rewriter.getI32Type()}));
365
366 // create bitcast/shape_cast for result
367 auto resultVal = forceCastValueToType(rewriter, loc, addElemOp,
368 op.getResult().getType());
369 rewriter.replaceOp(op, resultVal);
370 return success();
371 }
372
373 op.emitWarning() << "aievec.add_elem conversion is not supported.\n";
374 return failure();
375 }
376};
377
378// AIE2 version of SubElemOp conversion
380 : public mlir::ConvertOpToLLVMPattern<aievec::SubElemOp> {
381public:
382 using ConvertOpToLLVMPattern<aievec::SubElemOp>::ConvertOpToLLVMPattern;
383
389
390 static DecodedSubElemOp decodeSubElemOp(OpAdaptor op) {
391 auto lhs = op.getLhs();
392 auto lhsVecTy = cast<VectorType>(lhs.getType());
393 auto lhsScaTy = lhsVecTy.getElementType();
394 unsigned lhsBitWidth = lhsScaTy.getIntOrFloatBitWidth();
395
396 // Integer types
397 if (llvm::isa<IntegerType>(lhsScaTy)) {
399 } // Float types
400 if (lhsBitWidth == 32) {
401 // FP32 sub_elem
403 }
404
406 }
407
408 LogicalResult
409 matchAndRewrite(aievec::SubElemOp op, OpAdaptor adaptor,
410 ConversionPatternRewriter &rewriter) const override {
411 Location loc = op.getLoc();
412 auto decodedSubElemOp = decodeSubElemOp(adaptor);
413
414 if (decodedSubElemOp.kind == DecodedSubElemOp::Kind::UNSUPPORTED) {
415 op.emitWarning() << "aievec.sub_elem conversion is not supported.\n";
416 return failure();
417 }
418
419 // Handle the FP32 sub_elem for AIE2 - uses packed I64 representation
420 if (decodedSubElemOp.kind ==
422 auto confCst = LLVM::ConstantOp::create(
423 rewriter, loc, rewriter.getI32Type(),
424 rewriter.getI32IntegerAttr(decodedSubElemOp.conf));
425 SmallVector<Value> operands(
426 {adaptor.getLhs(), adaptor.getRhs(), confCst});
427
428 auto subElemOp = xllvm::SubAccFloatAIE2IntrOp::create(
429 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
430 forceCastOperandsToSignature(
431 rewriter, loc, operands,
432 {VectorType::get({8}, rewriter.getI64Type()),
433 VectorType::get({8}, rewriter.getI64Type()),
434 rewriter.getI32Type()}));
435
436 // create bitcast/shape_cast for result
437 auto resultVal = forceCastValueToType(rewriter, loc, subElemOp,
438 op.getResult().getType());
439 rewriter.replaceOp(op, resultVal);
440 return success();
441 }
442
443 op.emitWarning() << "aievec.sub_elem conversion is not supported.\n";
444 return failure();
445 }
446};
447
448// AIE2p version of AddElemOp and SubElemOp conversion for f32.
449//
450// AIE2p adds and subtracts f32 only on the 2048-bit accumulator, which holds
451// 64 lanes: the ACC2048 accfloat intrinsic. An operand with fewer lanes that
452// divide 64 is padded with poison, and the low lanes of the result are kept.
453// The intrinsic is used even at 64 lanes, as Peano's generic fsub on
454// <64 x float> gives wrong results.
455template <typename SrcOpTy, typename IntrOpTy>
457 : public mlir::ConvertOpToLLVMPattern<SrcOpTy> {
458public:
459 using mlir::ConvertOpToLLVMPattern<SrcOpTy>::ConvertOpToLLVMPattern;
460 using OpAdaptor = typename SrcOpTy::Adaptor;
461
462 LogicalResult
463 matchAndRewrite(SrcOpTy op, OpAdaptor adaptor,
464 ConversionPatternRewriter &rewriter) const override {
465 constexpr int64_t accLanes = 64;
466 Location loc = op.getLoc();
467 auto type = cast<VectorType>(adaptor.getLhs().getType());
468 int64_t lanes = type.getNumElements();
469 if (!type.getElementType().isF32() || type.getRank() != 1 ||
470 accLanes % lanes != 0 || lanes % 2 != 0) {
471 op.emitWarning() << op->getName() << " conversion is not supported.\n";
472 return failure();
473 }
474
475 // The padding and the extraction shuffle pairs of lanes as i64, which
476 // Peano lowers to fewer register moves than f32 shuffles.
477 auto accType = VectorType::get({accLanes}, rewriter.getF32Type());
478 auto accI64Type = VectorType::get({accLanes / 2}, rewriter.getI64Type());
479 auto i64Type = VectorType::get({lanes / 2}, rewriter.getI64Type());
480 auto lowLanes = [](int64_t n, int64_t size) {
481 SmallVector<int64_t> mask(size, -1);
482 std::iota(mask.begin(), mask.begin() + std::min(n, size), 0);
483 return mask;
484 };
485 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
486 if (lanes != accLanes) {
487 lhs = LLVM::BitcastOp::create(rewriter, loc, i64Type, lhs);
488 rhs = LLVM::BitcastOp::create(rewriter, loc, i64Type, rhs);
489 SmallVector<int64_t> padMask = lowLanes(lanes / 2, accLanes / 2);
490 lhs = vector::ShuffleOp::create(rewriter, loc, lhs, lhs, padMask);
491 rhs = vector::ShuffleOp::create(rewriter, loc, rhs, rhs, padMask);
492 lhs = LLVM::BitcastOp::create(rewriter, loc, accType, lhs);
493 rhs = LLVM::BitcastOp::create(rewriter, loc, accType, rhs);
494 }
495 // conf 60 selects the fp32 accumulator datapath.
496 auto confCst = LLVM::ConstantOp::create(
497 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(60));
498 Value result = IntrOpTy::create(rewriter, loc, accType, lhs, rhs, confCst);
499 if (lanes != accLanes) {
500 result = LLVM::BitcastOp::create(rewriter, loc, accI64Type, result);
501 result = vector::ShuffleOp::create(rewriter, loc, result, result,
502 lowLanes(lanes / 2, lanes / 2));
503 result = LLVM::BitcastOp::create(rewriter, loc, type, result);
504 }
505 rewriter.replaceOp(op, result);
506 return success();
507 }
508};
509
511 AccFloatAddSubAIE2pConversion<aievec::AddElemOp,
512 xllvm::AddACC2048AccFloatAIE2pIntrOp>;
514 AccFloatAddSubAIE2pConversion<aievec::SubElemOp,
515 xllvm::SubACC2048AccFloatAIE2pIntrOp>;
516
517// AIE2 version of NegOp conversion.
518//
519// aievec.neg operates on an accumulator. For floats the accumulator is
520// v16accfloat, which is carried as vector<16xf32> in AIEVec and as <8 x i64>
521// at the intrinsic boundary, so this maps directly onto the ACC512 accfloat
522// negate.
523class NegOpAIE2Conversion : public mlir::ConvertOpToLLVMPattern<aievec::NegOp> {
524public:
525 using ConvertOpToLLVMPattern<aievec::NegOp>::ConvertOpToLLVMPattern;
526
527 LogicalResult
528 matchAndRewrite(aievec::NegOp op, OpAdaptor adaptor,
529 ConversionPatternRewriter &rewriter) const override {
530 Location loc = op.getLoc();
531 auto srcVecTy = cast<VectorType>(adaptor.getSource().getType());
532 auto srcScaTy = srcVecTy.getElementType();
533
534 // Only the float accumulator (v16accfloat) is wired up. Integer
535 // accumulators would need the ACC1024 acc32/acc64 negates instead.
536 if (!isa<FloatType>(srcScaTy) || srcScaTy.getIntOrFloatBitWidth() != 32 ||
537 srcVecTy.getNumElements() != 16) {
538 op.emitWarning() << "aievec.neg conversion is not supported.\n";
539 return failure();
540 }
541
542 auto v8i64Ty = VectorType::get({8}, rewriter.getI64Type());
543 // conf selects the fp32 accumulator datapath. Matches the AIE API's
544 // neg(v16accfloat): aiev2_compute_control(0, 0, /*amode=*/2, /*bmode=*/3,
545 // 0, 0, 0, 0, 0, 0, 0) == (2 << 1) | (3 << 3) == 28.
546 auto confCst = LLVM::ConstantOp::create(
547 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(28));
548 SmallVector<Value> operands({adaptor.getSource(), confCst});
549
550 auto negOp = xllvm::NegACC512AccFloatAIE2IntrOp::create(
551 rewriter, loc, v8i64Ty,
552 forceCastOperandsToSignature(rewriter, loc, operands,
553 {v8i64Ty, rewriter.getI32Type()}));
554
555 auto resultVal =
556 forceCastValueToType(rewriter, loc, negOp, op.getResult().getType());
557 rewriter.replaceOp(op, resultVal);
558 return success();
559 }
560};
561
562// AIE2p version of NegOp conversion.
563//
564// AIE2p only exposes the negate on the 2048-bit accumulator, so a
565// v16accfloat operand is widened to <64 x float>, negated, and the low 16
566// lanes extracted back out. This mirrors AddElemOpAIE2pConversion.
568 : public mlir::ConvertOpToLLVMPattern<aievec::NegOp> {
569public:
570 using ConvertOpToLLVMPattern<aievec::NegOp>::ConvertOpToLLVMPattern;
571
572 LogicalResult
573 matchAndRewrite(aievec::NegOp op, OpAdaptor adaptor,
574 ConversionPatternRewriter &rewriter) const override {
575 Location loc = op.getLoc();
576 auto srcVecTy = cast<VectorType>(adaptor.getSource().getType());
577 auto srcScaTy = srcVecTy.getElementType();
578 unsigned laneSize = srcVecTy.getNumElements();
579
580 if (!isa<FloatType>(srcScaTy) || srcScaTy.getIntOrFloatBitWidth() != 32 ||
581 (laneSize != 16 && laneSize != 32)) {
582 op.emitWarning() << "aievec.neg conversion is not supported.\n";
583 return failure();
584 }
585
586 auto v64f32Ty = VectorType::get({64}, rewriter.getF32Type());
587
588 // The masks below address individual lanes, but vector.shuffle indexes
589 // the leading dimension, so an n-D operand has to be flattened first.
590 Value srcVal = adaptor.getSource();
591 auto fltSrcVecTy = getFlattenedVectorType(srcVecTy);
592 if (srcVecTy != fltSrcVecTy)
593 srcVal = vector::ShapeCastOp::create(rewriter, loc, fltSrcVecTy, srcVal)
594 .getResult();
595
596 // Widen the accumulator to ACC2048, filling the unused lanes with poison.
597 SmallVector<int64_t> expandMask;
598 for (unsigned i = 0; i < laneSize; ++i)
599 expandMask.push_back(i);
600 for (unsigned i = laneSize; i < 64; ++i)
601 expandMask.push_back(-1);
602 auto srcExpanded =
603 vector::ShuffleOp::create(rewriter, loc, srcVal, srcVal, expandMask);
604
605 // conf selects the fp32 accumulator datapath, matching the ACC2048
606 // accfloat add/sub lowerings and the AIE API's neg(v64accfloat):
607 // aie2p_compute_control(0, 0, /*amode=*/2, /*bmode=*/3, /*variant=*/1,
608 // 0, 0, 0, 0, 0, 0) == (2 << 1) | (3 << 3) | (1 << 5) == 60.
609 auto confCst = LLVM::ConstantOp::create(
610 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(60));
611 auto negResult = xllvm::NegACC2048AccFloatAIE2pIntrOp::create(
612 rewriter, loc, v64f32Ty, srcExpanded, confCst);
613
614 SmallVector<int64_t> extractMask;
615 for (unsigned i = 0; i < laneSize; ++i)
616 extractMask.push_back(i);
617 Value finalResult = vector::ShuffleOp::create(rewriter, loc, negResult,
618 negResult, extractMask)
619 .getResult();
620
621 // Restore the operand's shape.
622 if (srcVecTy != fltSrcVecTy)
623 finalResult =
624 vector::ShapeCastOp::create(rewriter, loc, srcVecTy, finalResult)
625 .getResult();
626
627 rewriter.replaceOp(op, finalResult);
628 return success();
629 }
630};
631
633 : public mlir::ConvertOpToLLVMPattern<aievec::aie1::SubOp> {
634public:
635 using ConvertOpToLLVMPattern<aievec::aie1::SubOp>::ConvertOpToLLVMPattern;
636
637 LogicalResult
638 matchAndRewrite(aievec::aie1::SubOp op, OpAdaptor adaptor,
639 ConversionPatternRewriter &rewriter) const override {
640 op.emitWarning() << "aie.sub conversion is not implemented\n";
641 return failure();
642 }
643};
644
646 : public mlir::ConvertOpToLLVMPattern<aievec::aie1::FMAOp> {
647public:
648 using ConvertOpToLLVMPattern<aievec::aie1::FMAOp>::ConvertOpToLLVMPattern;
649
650 LogicalResult
651 matchAndRewrite(aievec::aie1::FMAOp op, OpAdaptor adaptor,
652 ConversionPatternRewriter &rewriter) const override {
653 auto module = op->getParentOfType<ModuleOp>();
654 MLIRContext *context = rewriter.getContext();
655
656 auto startType = IntegerType::get(context, 32);
657 auto offsetsType = VectorType::get({2}, IntegerType::get(context, 32));
658 auto confType = VectorType::get({2}, IntegerType::get(context, 32));
659
660 // If the intrinsic declaration doesn't exist, create it
661 std::string intrinsicName = getMulOrFMAIntrinsicName(op);
662 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
663 StringAttr::get(context, intrinsicName));
664
665 if (!func) {
666 OpBuilder::InsertionGuard guard(rewriter);
667 rewriter.setInsertionPointToStart(module.getBody());
668 func = LLVM::LLVMFuncOp::create(
669 rewriter, rewriter.getUnknownLoc(), intrinsicName,
670 LLVM::LLVMFunctionType::get(
671 op.getResult().getType(),
672 {op.getLhs().getType(), op.getRhs().getType(),
673 op.getAcc().getType(), startType, /* xstart */
674 startType, /* ystart */
675 startType, /* zstart */
676 offsetsType, /* xoffsets */
677 offsetsType, /* zoffsets */
678 confType}));
679 }
680
681 // Parse the string attribute values
682 BufferParams x = {};
683 BufferParams z = {};
684 op.getXstart().getAsInteger(0, x.start);
685 op.getXoffsets().getAsInteger(0, x.offsets);
686 op.getXoffsetsHi().getAsInteger(0, x.offsets_hi);
687 op.getXstep().getAsInteger(0, x.step);
688 op.getXsquare().getAsInteger(0, x.square);
689 op.getZstart().getAsInteger(0, z.start);
690 op.getZoffsets().getAsInteger(0, z.offsets);
691 op.getZoffsetsHi().getAsInteger(0, z.offsets_hi);
692 op.getZstep().getAsInteger(0, z.step);
693 op.getZsquare().getAsInteger(0, z.square);
694
695 // Encode the configuration register
696 uint32_t conf[2] = {0, 0};
697 encodeConf(conf, x, z, op.getFmsub());
698
699 // Create the constants and replace the op
700 auto xstartVal = LLVM::ConstantOp::create(
701 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(x.start));
702 auto ystartVal = LLVM::ConstantOp::create(rewriter, op->getLoc(), startType,
703 rewriter.getI32IntegerAttr(0));
704 auto zstartVal = LLVM::ConstantOp::create(
705 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(z.start));
706 auto xoffsetsVal = LLVM::ConstantOp::create(
707 rewriter, op->getLoc(), offsetsType,
708 rewriter.getI32VectorAttr({(int32_t)x.offsets, (int32_t)x.offsets_hi}));
709 auto zoffsetsVal = LLVM::ConstantOp::create(
710 rewriter, op->getLoc(), offsetsType,
711 rewriter.getI32VectorAttr({(int32_t)z.offsets, (int32_t)z.offsets_hi}));
712 auto confVal = LLVM::ConstantOp::create(
713 rewriter, op->getLoc(), confType,
714 rewriter.getI32VectorAttr({(int32_t)conf[0], (int32_t)conf[1]}));
715 rewriter.replaceOpWithNewOp<LLVM::CallOp>(
716 op, func,
717 ValueRange{op.getLhs(), op.getRhs(), op.getAcc(), xstartVal, ystartVal,
718 zstartVal, xoffsetsVal, zoffsetsVal, confVal});
719 return success();
720 }
721};
722
724 : public mlir::ConvertOpToLLVMPattern<aievec::aie1::MulOp> {
725public:
726 using ConvertOpToLLVMPattern<aievec::aie1::MulOp>::ConvertOpToLLVMPattern;
727
728 LogicalResult
729 matchAndRewrite(aievec::aie1::MulOp op, OpAdaptor adaptor,
730 ConversionPatternRewriter &rewriter) const override {
731 auto module = op->getParentOfType<ModuleOp>();
732 MLIRContext *context = rewriter.getContext();
733
734 auto startType = IntegerType::get(context, 32);
735 auto offsetsType = VectorType::get({2}, IntegerType::get(context, 32));
736 auto confType = VectorType::get({2}, IntegerType::get(context, 32));
737
738 // If the intrinsic declaration doesn't exist, create it
739 std::string intrinsicName = getMulOrFMAIntrinsicName(op);
740 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
741 StringAttr::get(context, intrinsicName));
742
743 if (!func) {
744 OpBuilder::InsertionGuard guard(rewriter);
745 rewriter.setInsertionPointToStart(module.getBody());
746 func = LLVM::LLVMFuncOp::create(
747 rewriter, rewriter.getUnknownLoc(), intrinsicName,
748 LLVM::LLVMFunctionType::get(op.getResult().getType(),
749 {op.getLhs().getType(),
750 op.getRhs().getType(),
751 startType, /* xstart */
752 startType, /* ystart */
753 startType, /* zstart */
754 offsetsType, /* xoffsets */
755 offsetsType, /* zoffsets */
756 confType}));
757 }
758
759 // Parse the string attribute values
760 BufferParams x = {};
761 BufferParams z = {};
762 op.getXstart().getAsInteger(0, x.start);
763 op.getXoffsets().getAsInteger(0, x.offsets);
764 op.getXoffsetsHi().getAsInteger(0, x.offsets_hi);
765 op.getXstep().getAsInteger(0, x.step);
766 op.getXsquare().getAsInteger(0, x.square);
767 op.getZstart().getAsInteger(0, z.start);
768 op.getZoffsets().getAsInteger(0, z.offsets);
769 op.getZoffsetsHi().getAsInteger(0, z.offsets_hi);
770 op.getZstep().getAsInteger(0, z.step);
771 op.getZsquare().getAsInteger(0, z.square);
772
773 // Encode the configuration register
774 uint32_t conf[2] = {0, 0};
775 encodeConf(conf, x, z, false);
776
777 // Create the constants and replace the op
778 auto xstartVal = LLVM::ConstantOp::create(
779 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(x.start));
780 auto ystartVal = LLVM::ConstantOp::create(rewriter, op->getLoc(), startType,
781 rewriter.getI32IntegerAttr(0));
782 auto zstartVal = LLVM::ConstantOp::create(
783 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(z.start));
784 auto xoffsetsVal = LLVM::ConstantOp::create(
785 rewriter, op->getLoc(), offsetsType,
786 rewriter.getI32VectorAttr({(int32_t)x.offsets, (int32_t)x.offsets_hi}));
787 auto zoffsetsVal = LLVM::ConstantOp::create(
788 rewriter, op->getLoc(), offsetsType,
789 rewriter.getI32VectorAttr({(int32_t)z.offsets, (int32_t)z.offsets_hi}));
790 auto confVal = LLVM::ConstantOp::create(
791 rewriter, op->getLoc(), confType,
792 rewriter.getI32VectorAttr({(int32_t)conf[0], (int32_t)conf[1]}));
793 rewriter.replaceOpWithNewOp<LLVM::CallOp>(
794 op, func,
795 ValueRange{op.getLhs(), op.getRhs(), xstartVal, ystartVal, zstartVal,
796 xoffsetsVal, zoffsetsVal, confVal});
797 return success();
798 }
799};
800
802 : public mlir::ConvertOpToLLVMPattern<aievec::MulElemOp> {
803public:
804 using ConvertOpToLLVMPattern<aievec::MulElemOp>::ConvertOpToLLVMPattern;
805
806 MulElemOpConversion(const LLVMTypeConverter &typeConverter,
807 Aie2Fp32Emulation aie2Fp32EmulationOption)
808 : ConvertOpToLLVMPattern(typeConverter),
810
811 Aie2Fp32Emulation aie2Fp32EmulationOption;
812
814 enum class Kind {
815 // DtIn0_DtIn1_DtRes_CxMxKxN
822 // TODO: I16_I16_I64_16x1x2x1
823 };
824
826 int conf;
827 };
828
829 static DecodedMulElemOp decodeMulElemOp(OpAdaptor op) {
830 auto lhs = op.getLhs();
831 auto lhsVecTy = cast<VectorType>(lhs.getType());
832 auto lhsScaTy = lhsVecTy.getElementType();
833 unsigned lhsBitWidth = lhsScaTy.getIntOrFloatBitWidth();
834
835 // Integer types
836 if (llvm::isa<IntegerType>(lhsScaTy)) {
837 if (lhsBitWidth == 8) {
839 aiev2_vmac_compute_control(
840 /*sgn_x=*/1, /*sgn_y=*/1, /*amode=*/0, /*bmode=*/1,
841 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
842 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0,
843 /*sub_mask=*/0)};
844 }
845 if (lhsBitWidth == 16) {
847 aiev2_vmac_compute_control(
848 /*sgn_x=*/1, /*sgn_y=*/1, /*amode=*/0, /*bmode=*/3,
849 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
850 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0,
851 /*sub_mask=*/0)};
852 }
853 if (lhsBitWidth == 32) {
854 // emulated I32 mul_elem
856 }
857 } else {
858 // Float types
859 if (lhsBitWidth == 16) {
861 aiev2_vmac_compute_control(
862 /*sgn_x=*/0, /*sgn_y=*/0, /*amode=*/2, /*bmode=*/3,
863 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
864 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0,
865 /*sub_mask=*/0)};
866 }
867 if (lhsBitWidth == 32) {
868 // emulated FP32 mul_elem
870 }
871 }
872
874 }
875
876 // This conversion pattern implements the below CPP emulated I32 mul_elem.
877 // INTRINSIC(v16acc64)
878 // mul_elem_16_2(v16int32 a0, v16int32 a1, v16int32 b0, v16int32 b1) {
879 // v32uint16 a_lo = (v32uint16)shuffle(a0, a1, 2);
880 // v32int16 a_hi = (v32int16)shuffle(a0, a1, 3);
881 // v32uint16 b_lo = (v32uint16)shuffle(b0, b1, 2);
882 // v32int16 b_hi = (v32int16)shuffle(b0, b1, 3);
883 // v16acc64 acc = ::mul_elem_16_2(a_hi, b_hi);
884 // acc = mac_elem_16_2_conf(a_hi, 1, b_lo, false, acc, 0, 1, 0, 0);
885 // acc = mac_elem_16_2_conf(a_lo, false, b_hi, 1, acc, 0, 0, 0, 0);
886 // acc = mac_elem_16_2_conf(a_lo, false, b_lo, false, acc, 0, 1, 0, 0);
887 // return acc;
888 // }
889 // Caller example when handling the elementwise mul of two v16int32 vectors.
890 // v16int32 v1 = LHS();
891 // v16int32 v2 = RHS();
892 // v16acc64 v3 = mul_elem_16_2(v1, broadcast_zero_s32(), v2,
893 // undef_v16int32());
894 // Explantion:
895 // a_lo = low_part(a0[0]--a0[15], a1[0]--a1[15])
896 // a_hi = high_part(a0[0]--a0[15], a1[0]--a1[15])
897 // b_lo = low_part(b0[0]--b0[15], b1[0]--b1[15])
898 // b_hi = high_part(b0[0]--b0[15], b1[0]--b1[15])
899 // The firt `acc` is from mul_elem_16_2(a_hi, b_hi), which performs 16 channel
900 // of 1x2x1 matmul, acc[0] = a_hi[0]*b_hi[0]+a_hi[16]*b_hi[16], ... , acc[15]
901 // = a_hi[15]*b_hi[15]+a_hi[31]*b_hi[31]. Then, the first MAC performs `acc`
902 // left shift 16bit, and then 16 channel of 1x2x1 matmul (a_hi, b_lo)
903 // accumulating to `acc`. The second MAC performs 16 channel of 1x2x1 matmul
904 // (a_lo, b_hi) accumulating to `acc`. Finally, the third MAC performs 16
905 // channel of 1x2x1 matmul (a_lo, b_hi) accumulating to `acc`.
906 LogicalResult
907 convertToEmulatedI32MulElem(aievec::MulElemOp op, OpAdaptor adaptor,
908 ConversionPatternRewriter &rewriter) const {
909
910 Location loc = op.getLoc();
911 auto zeroCst = LLVM::ConstantOp::create(
912 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
913 auto a0 = adaptor.getLhs();
914 auto a1 = xllvm::VectorBroadcast32I512IntrOp::create(
915 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()), zeroCst);
916 auto b0 = adaptor.getRhs();
917 auto b1 = xllvm::UndefV16I32IntrOp::create(
918 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()));
919
920 // 4* Shuffle
921 auto a_lo = xllvm::VectorShuffleIntrOp::create(
922 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()), a0, a1,
923 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
924 rewriter.getI32IntegerAttr(2)));
925 auto a_hi = xllvm::VectorShuffleIntrOp::create(
926 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()), a0, a1,
927 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
928 rewriter.getI32IntegerAttr(3)));
929 auto b_lo = xllvm::VectorShuffleIntrOp::create(
930 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()), b0, b1,
931 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
932 rewriter.getI32IntegerAttr(2)));
933 auto b_hi = xllvm::VectorShuffleIntrOp::create(
934 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()), b0, b1,
935 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
936 rewriter.getI32IntegerAttr(3)));
937 // MUL + 3 * MAC
938 auto mulConfCst = LLVM::ConstantOp::create(
939 rewriter, loc, rewriter.getI32Type(),
940 rewriter.getI32IntegerAttr(aiev2_vmac_compute_control(
941 /*sgn_x=*/1, /*sgn_y=*/1, /*amode=*/1, /*bmode=*/3,
942 /*variant=*/2, /*zero_acc=*/0, /*shift16=*/0,
943 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0, /*sub_mask=*/0)));
944 auto mulConfOp = xllvm::MulConfAcc64IntrOp::create(
945 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
946 forceCastOperandsToSignature(
947 rewriter, loc,
948 /*operands=*/{a_hi, b_hi, mulConfCst},
949 /*signature=*/
950 {VectorType::get({64}, rewriter.getI8Type()),
951 VectorType::get({16}, rewriter.getI32Type()),
952 rewriter.getI32Type()}));
953
954 auto createMacConfOp = [&](SmallVector<Value> operands,
955 int macConf) -> Value {
956 operands.push_back(
957 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
958 rewriter.getI32IntegerAttr(macConf)));
959 return xllvm::MacConfAcc64IntrOp::create(
960 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
961 forceCastOperandsToSignature(
962 rewriter, loc,
963 /*operands=*/operands,
964 /*signature=*/
965 {VectorType::get({64}, rewriter.getI8Type()),
966 VectorType::get({16}, rewriter.getI32Type()),
967 VectorType::get({16}, rewriter.getI64Type()),
968 rewriter.getI32Type()}))
969 .getResult();
970 };
971 auto acc64Val = mulConfOp.getResult();
972 acc64Val = createMacConfOp(
973 SmallVector<Value>{a_hi, b_lo, acc64Val},
974 aiev2_vmac_compute_control(
975 /*sgn_x=*/1, /*sgn_y=*/0, /*amode=*/1, /*bmode=*/3,
976 /*variant=*/2, /*zero_acc=*/0, /*shift16=*/1,
977 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0, /*sub_mask=*/0));
978 acc64Val = createMacConfOp(
979 SmallVector<Value>{a_lo, b_hi, acc64Val},
980 aiev2_vmac_compute_control(
981 /*sgn_x=*/0, /*sgn_y=*/1, /*amode=*/1, /*bmode=*/3,
982 /*variant=*/2, /*zero_acc=*/0, /*shift16=*/0,
983 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0, /*sub_mask=*/0));
984 acc64Val = createMacConfOp(
985 SmallVector<Value>{a_lo, b_lo, acc64Val},
986 aiev2_vmac_compute_control(
987 /*sgn_x=*/0, /*sgn_y=*/0, /*amode=*/1, /*bmode=*/3,
988 /*variant=*/2, /*zero_acc=*/0, /*shift16=*/1,
989 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0, /*sub_mask=*/0));
990
991 // create bitcast/shape_cast for result
992 auto resultVal =
993 forceCastValueToType(rewriter, loc, acc64Val, op.getResult().getType());
994 rewriter.replaceOp(op, resultVal);
995 return success();
996 }
997
998 // This conversion pattern implements the below CPP emulated FP32 mul_elem.
999 // inline v16accfloat mul_elem_16_accuracy_safe(v16float v1, v16float v2) {
1000 // v32bfloat16 a = broadcast_zero_to_v32bfloat16();
1001 // v32bfloat16 b = broadcast_zero_to_v32bfloat16();
1002 // v32bfloat16 c = broadcast_zero_to_v32bfloat16();
1003 // v32bfloat16 d = broadcast_zero_to_v32bfloat16();
1004 // v32bfloat16 e = broadcast_zero_to_v32bfloat16();
1005 // v32bfloat16 f = broadcast_zero_to_v32bfloat16();
1006 // v32bfloat16 dummy0 = broadcast_one_to_v32bfloat16();
1007 // a = insert(a,0,to_v16bfloat16((v16accfloat)v1));
1008 // v16accfloat acc0 = msc_elem_16_2(a, dummy0, (v16accfloat)v1);
1009 // b = insert(b,0,to_v16bfloat16(acc0));
1010 // c = insert(c,0,to_v16bfloat16(msc_elem_16_2(b, dummy0, acc0)));
1011 // d = insert(d,0,to_v16bfloat16((v16accfloat)v2));
1012 // v16accfloat acc1 = msc_elem_16_2(d, dummy0, (v16accfloat)v2);
1013 // e = insert(e,0,to_v16bfloat16(acc1));
1014 // f = insert(f,0,to_v16bfloat16(msc_elem_16_2(e, dummy0, acc1)));
1015 // return
1016 // mac_elem_16_2(a,d,mac_elem_16_2(a,e,mac_elem_16_2(b,d,mac_elem_16_2(
1017 // d,c,mac_elem_16_2(b,e,mac_elem_16_2(a,f,mac_elem_16_2(
1018 // b,f,mac_elem_16_2(c,e,mul_elem_16_2(c,f)))))))));
1019 // }
1020 // Caller example when handling the elementwise mul of two v16float vectors.
1021 // v16float v1 = LHS(); v16float v2 = RHS();
1022 // v16accfloat v3 = mul_elem_16(v1, v2);
1023 // Explantion: For v32bfloat16 `a`, the first half v16bf16 contains `most
1024 // significant 7 bits of mantissa` from v1, and the second half v16bf16 are
1025 // zeros. For v16accfloat `acc0`, the MSC equals to "(original `v1` with 23
1026 // bits of mantissa) - (`a` with MSB 7 bits of mantissa from v1)". For
1027 // v32bfloat16 `b`, the first half v16bf16 contains `[7:13] bits of mantissa
1028 // from v1` from v1, and the second half v16bf16 are zeros. For v32bfloat16
1029 // `c`, the first half v16bf16 contains `[14:20] bits of mantissa from v1`
1030 // from v1, and the second half v16bf16 are zeros. Hence, we can represent
1031 // v16float in three v32bfloat16 and then perform 9 MUL/MAC in v32bfloat16 to
1032 // get the final elementwise multiplication result.
1033
1034 LogicalResult
1035 convertToEmulatedFP32MulElem(aievec::MulElemOp op, OpAdaptor adaptor,
1036 ConversionPatternRewriter &rewriter) const {
1037 Location loc = op.getLoc();
1038 auto zeroCst =
1039 LLVM::ConstantOp::create(rewriter, loc, rewriter.getBF16Type(),
1040 rewriter.getZeroAttr(rewriter.getBF16Type()));
1041 auto aZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1042 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1043 auto bZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1044 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1045 auto cZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1046 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1047 auto dZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1048 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1049 auto eZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1050 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1051 auto fZeros = xllvm::VectorBroadcast16BF512IntrOp::create(
1052 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), zeroCst);
1053 auto oneCst =
1054 LLVM::ConstantOp::create(rewriter, loc, rewriter.getBF16Type(),
1055 rewriter.getOneAttr(rewriter.getBF16Type()));
1056 auto dummy0 = xllvm::VectorBroadcast16BF512IntrOp::create(
1057 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), oneCst);
1058 auto zeroCstI32 = LLVM::ConstantOp::create(
1059 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
1060 auto mscMacMulConfCst = LLVM::ConstantOp::create(
1061 rewriter, loc, rewriter.getI32Type(),
1062 rewriter.getI32IntegerAttr(aiev2_vmac_compute_control(
1063 /*sgn_x=*/0, /*sgn_y=*/0, /*amode=*/2, /*bmode=*/3,
1064 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
1065 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0, /*sub_mask=*/0)));
1066
1067 auto extractV16FP32ToThreeV16BF16 =
1068 [&](Value inputV16FP32, Value aZeros, Value bZeros,
1069 Value cZeros) -> std::tuple<Value, Value, Value> {
1070 // a = insert(a,0,to_v16bfloat16((v16accfloat)v1));
1071 auto inputBitCasted =
1072 forceCastValueToType(rewriter, loc, inputV16FP32,
1073 VectorType::get({8}, rewriter.getI64Type()));
1074 auto v1ToBF16 = xllvm::Vector16AccFloatToV16BF16AIE2IntrOp::create(
1075 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
1076 inputBitCasted);
1077 auto a = xllvm::UpdBF512BF256IntrOp::create(
1078 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), aZeros,
1079 v1ToBF16, zeroCstI32);
1080
1081 // v16accfloat acc0 = msc_elem_16_2(a, dummy0, (v16accfloat)v1);
1082 auto acc0 = xllvm::MscConfBF16IntrOp::create(
1083 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), a, dummy0,
1084 inputBitCasted, mscMacMulConfCst);
1085
1086 // b = insert(b,0,to_v16bfloat16(acc0));
1087 auto acc0ToBF16 = xllvm::Vector16AccFloatToV16BF16AIE2IntrOp::create(
1088 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()), acc0);
1089 auto b = xllvm::UpdBF512BF256IntrOp::create(
1090 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), bZeros,
1091 acc0ToBF16, zeroCstI32);
1092
1093 // c = insert(c,0,to_v16bfloat16(msc_elem_16_2(b, dummy0, acc0)));
1094 auto acc0Mscb = xllvm::MscConfBF16IntrOp::create(
1095 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), b, dummy0,
1096 acc0, mscMacMulConfCst);
1097 auto acc0MscbToBF16 = xllvm::Vector16AccFloatToV16BF16AIE2IntrOp::create(
1098 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
1099 acc0Mscb);
1100 auto c = xllvm::UpdBF512BF256IntrOp::create(
1101 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), cZeros,
1102 acc0MscbToBF16, zeroCstI32);
1103 return std::make_tuple(a.getResult(), b.getResult(), c.getResult());
1104 };
1105
1106 // Get v16vfloat16 a, b, c for representing v16float v1
1107 auto [a, b, c] =
1108 extractV16FP32ToThreeV16BF16(adaptor.getLhs(), aZeros, bZeros, cZeros);
1109 // Get v16vfloat16 d, e, f for representing v16float v2
1110 auto [d, e, f] =
1111 extractV16FP32ToThreeV16BF16(adaptor.getRhs(), dZeros, eZeros, fZeros);
1112
1113 // Create 1 MUL and 2/5/8 MACs depending on the Aie2Fp32EmulationOption
1114 auto createMacOps = [&](Value lhs, Value rhs, Value acc) -> Value {
1115 return xllvm::MacConfBF16IntrOp::create(
1116 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1117 lhs, rhs, acc, mscMacMulConfCst)
1118 .getResult();
1119 };
1120
1121 Value finalMacVal;
1122 if (aie2Fp32EmulationOption == Aie2Fp32Emulation::AccuracyFast) {
1123 // Fast and Accurate option. float a*b would require 6 mac operations.
1124 // Input fp32 number is split in to 3 bfloat16 numbers to extract all the
1125 // bits of the mantissa. float a,b; both a and b are split in to 3
1126 // bfloat16 numbers each. Hence there would be 9 mac operations in
1127 // multiplication of a and b. In the 9 mac operations to emulate fp32 mul,
1128 // mac operations with LSBs are ignored. (3 last terms). This helps
1129 // improve cycle count of mul and has least impact on accuracy of result.
1130 // This is the default option to the aiecompiler
1131 auto afMul = xllvm::MulConfBF16IntrOp::create(
1132 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), a, f,
1133 mscMacMulConfCst);
1134 finalMacVal = createMacOps(
1135 a, d,
1136 createMacOps(
1137 a, e,
1138 createMacOps(b, d,
1139 createMacOps(d, c, createMacOps(b, e, afMul)))));
1140 } else if (aie2Fp32EmulationOption == Aie2Fp32Emulation::AccuracyLow) {
1141 // Fast and least accurate option. float a*b would require 3 mac
1142 // operations.
1143 // Input fp32 number is split in to 2 bfloat16 numbers. Hence not all the
1144 // bits from mantissa can be used. float a,b; Both a and b are split in to
1145 // 2 bfloat16 numbers each. Hence there would be 4 mac operations in
1146 // multiplication of a and b. In the 4 mac operations to emulate fp32 mul,
1147 // mac operations with LSBs are ignored. (1 last term). This helps improve
1148 // cycle count of mul float a, b;
1149 auto bdMul = xllvm::MulConfBF16IntrOp::create(
1150 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), b, d,
1151 mscMacMulConfCst);
1152 finalMacVal = createMacOps(a, d, createMacOps(a, e, bdMul));
1153 } else {
1154 // aie2Fp32EmulationOption == Aie2Fp32Emulation::AccuracySafe
1155 // Most accurate option since input fp32 number is split in to 3 bfloat16
1156 // numbers to extract all the bits of the mantissa. float a*b would
1157 // require 9 mac operations due to 3 bfloat16 splits each.
1158 auto cfMul = xllvm::MulConfBF16IntrOp::create(
1159 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), c, f,
1160 mscMacMulConfCst);
1161 finalMacVal = createMacOps(
1162 a, d,
1163 createMacOps(
1164 a, e,
1165 createMacOps(
1166 b, d,
1167 createMacOps(
1168 d, c,
1169 createMacOps(
1170 b, e,
1171 createMacOps(
1172 a, f,
1173 createMacOps(b, f,
1174 createMacOps(c, e, cfMul))))))));
1175 }
1176
1177 // create bitcast/shape_cast for result
1178 auto resultVal = forceCastValueToType(rewriter, loc, finalMacVal,
1179 op.getResult().getType());
1180 rewriter.replaceOp(op, resultVal);
1181 return success();
1182 }
1183
1184 LogicalResult
1185 matchAndRewrite(aievec::MulElemOp op, OpAdaptor adaptor,
1186 ConversionPatternRewriter &rewriter) const override {
1187 Location loc = op.getLoc();
1188 auto decodedMulElemOp = decodeMulElemOp(adaptor);
1189
1190 if (decodedMulElemOp.kind == DecodedMulElemOp::Kind::UNSUPPORTED) {
1191 op.emitWarning() << "aievec.mul_elem conversion is not supported.\n";
1192 return failure();
1193 }
1194
1195 // Handle the emulated I32/FP32 mul_elem
1196 if (decodedMulElemOp.kind == DecodedMulElemOp::Kind::I32_I32_I64_32x1x2x1) {
1197 return convertToEmulatedI32MulElem(op, adaptor, rewriter);
1198 }
1199 if (decodedMulElemOp.kind ==
1201 return convertToEmulatedFP32MulElem(op, adaptor, rewriter);
1202 }
1203
1204 // create constant for config
1205 auto confCst = LLVM::ConstantOp::create(
1206 rewriter, loc, rewriter.getI32Type(),
1207 rewriter.getI32IntegerAttr(decodedMulElemOp.conf));
1208 Value mulElemOp = nullptr;
1209 SmallVector<Value> operands({adaptor.getLhs(), adaptor.getRhs(), confCst});
1210
1211 // create xllvm intrinsic
1212 if (decodedMulElemOp.kind == DecodedMulElemOp::Kind::I16_I16_I32_32x1x1x1 ||
1213 decodedMulElemOp.kind == DecodedMulElemOp::Kind::I8_I8_I32_32x1x2x1) {
1214 mulElemOp = xllvm::MulConfAcc32IntrOp::create(
1215 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1216 forceCastOperandsToSignature(
1217 rewriter, loc, operands,
1218 {VectorType::get({64}, rewriter.getI8Type()),
1219 VectorType::get({16}, rewriter.getI32Type()),
1220 rewriter.getI32Type()}));
1221 } else if (decodedMulElemOp.kind ==
1223 // Create zero vector using the exact pattern from working reference:
1224 // vbroadcast16.I512(0) -> bitcast to bf16 -> extract lower 256 bits
1225 auto zero32 = LLVM::ConstantOp::create(
1226 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
1227 auto zeros_i16 = xllvm::VectorBroadcast16I512IntrOp::create(
1228 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()), zero32);
1229 auto zeros_bf16 = LLVM::BitcastOp::create(
1230 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
1231 zeros_i16);
1232 auto zeroVec = xllvm::ExtBF256BF512IntrOp::create(
1233 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
1234 zeros_bf16, zero32);
1235
1236 // Use set+upd pattern to match working reference
1237 auto idx1 = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1238 rewriter.getI32IntegerAttr(1));
1239
1240 // Set lhs at lower 256 bits, then update upper 256 bits with zeros
1241 auto lhsSet = xllvm::VectorSetBF512BF256IntrOp::create(
1242 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
1243 adaptor.getLhs(), zero32);
1244 auto lhsConcat = xllvm::UpdBF512BF256IntrOp::create(
1245 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), lhsSet,
1246 zeroVec, idx1);
1247
1248 // Set rhs at lower 256 bits, then update upper 256 bits with zeros
1249 auto rhsSet = xllvm::VectorSetBF512BF256IntrOp::create(
1250 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
1251 adaptor.getRhs(), zero32);
1252 auto rhsConcat = xllvm::UpdBF512BF256IntrOp::create(
1253 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()), rhsSet,
1254 zeroVec, idx1);
1255
1256 // Call bf.mul16.conf with padded vectors
1257 mulElemOp = xllvm::MulConfBF16IntrOp::create(
1258 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()), lhsConcat,
1259 rhsConcat, confCst);
1260 }
1261
1262 // create bitcast/shape_cast for result
1263 auto resultVal = forceCastValueToType(rewriter, loc, mulElemOp,
1264 op.getResult().getType());
1265 rewriter.replaceOp(op, resultVal);
1266 return success();
1267 }
1268};
1269
1270// AIE2p version of MulElemOp conversion
1272 : public mlir::ConvertOpToLLVMPattern<aievec::MulElemOp> {
1273public:
1274 using ConvertOpToLLVMPattern<aievec::MulElemOp>::ConvertOpToLLVMPattern;
1275
1277 enum class Kind {
1278 BF16_BF16_FP32_16x1x1x1, // 16-lane bf16 -> 16-lane f32
1279 BF16_BF16_FP32_32x1x2x1, // 32-lane bf16 -> 32-lane f32
1280 BF16_BF16_FP32_64x1x2x1, // 64-lane bf16 -> 64-lane f32
1282 };
1284 int conf;
1285 };
1286
1287 static DecodedMulElemOp decodeMulElemOp(OpAdaptor op) {
1288 auto lhs = op.getLhs();
1289 auto lhsVecTy = cast<VectorType>(lhs.getType());
1290 auto lhsScaTy = lhsVecTy.getElementType();
1291 unsigned lhsBitWidth = lhsScaTy.getIntOrFloatBitWidth();
1292 int lhsLanes = getVectorLaneSize(lhsVecTy);
1293
1294 // Integer types - not supported for AIE2p elementwise mul
1295 if (llvm::isa<IntegerType>(lhsScaTy)) {
1297 } // Float types
1298 if (lhsBitWidth == 16) {
1299 // BF16 mul_elem
1300 if (lhsLanes == 16) {
1301 // 16-lane bfloat16 uses I512.I512.ACC512 intrinsic
1303 }
1304 if (lhsLanes == 32) {
1305 // 32-lane bfloat16 uses I512.I512.ACC1024 intrinsic
1307 }
1308 if (lhsLanes == 64) {
1309 // 64-lane bfloat16 uses I1024.I1024.ACC2048 intrinsic
1311 }
1312 }
1313
1315 }
1316
1317 LogicalResult
1318 matchAndRewrite(aievec::MulElemOp op, OpAdaptor adaptor,
1319 ConversionPatternRewriter &rewriter) const override {
1320 Location loc = op.getLoc();
1321 auto decodedMulElemOp = decodeMulElemOp(adaptor);
1322
1323 if (decodedMulElemOp.kind == DecodedMulElemOp::Kind::UNSUPPORTED) {
1324 op.emitWarning() << "aievec.mul_elem conversion is not supported for "
1325 "AIE2p.\n";
1326 return failure();
1327 }
1328
1329 // Create constant for config
1330 auto confCst = LLVM::ConstantOp::create(
1331 rewriter, loc, rewriter.getI32Type(),
1332 rewriter.getI32IntegerAttr(decodedMulElemOp.conf));
1333
1334 Value mulElemOp = nullptr;
1335
1336 // Handle BF16 mul_elem for AIE2p
1337 if (decodedMulElemOp.kind ==
1339 // 16-lane bfloat16: <16 x bfloat> x <16 x bfloat> -> <16 x float>
1340 // The intrinsic requires <32 x bfloat> inputs, so we need to pad
1341
1342 // Pad LHS from 16 to 32 bfloat16 using shuffle
1343 SmallVector<int64_t> padMask;
1344 for (int i = 0; i < 16; ++i)
1345 padMask.push_back(i);
1346 for (int i = 16; i < 32; ++i)
1347 padMask.push_back(-1); // poison/undef
1348
1349 auto lhsPadded = vector::ShuffleOp::create(
1350 rewriter, loc, adaptor.getLhs(), adaptor.getLhs(), padMask);
1351 auto rhsPadded = vector::ShuffleOp::create(
1352 rewriter, loc, adaptor.getRhs(), adaptor.getRhs(), padMask);
1353
1354 SmallVector<Value> operands({lhsPadded, rhsPadded, confCst});
1355
1356 // Call I512.I512.ACC512 intrinsic
1357 mulElemOp = xllvm::MulConfBF16I512ACC512AIE2pIntrOp::create(
1358 rewriter, loc, VectorType::get({16}, rewriter.getF32Type()),
1359 forceCastOperandsToSignature(
1360 rewriter, loc, operands,
1361 {VectorType::get({32}, rewriter.getBF16Type()),
1362 VectorType::get({32}, rewriter.getBF16Type()),
1363 rewriter.getI32Type()}));
1364 } else if (decodedMulElemOp.kind ==
1366 // 32-lane bfloat16: <32 x bfloat> x <32 x bfloat> -> <32 x float>
1367 SmallVector<Value> operands(
1368 {adaptor.getLhs(), adaptor.getRhs(), confCst});
1369 mulElemOp = xllvm::MulConfBF16I512ACC1024AIE2pIntrOp::create(
1370 rewriter, loc, VectorType::get({32}, rewriter.getF32Type()),
1371 forceCastOperandsToSignature(
1372 rewriter, loc, operands,
1373 {VectorType::get({32}, rewriter.getBF16Type()),
1374 VectorType::get({32}, rewriter.getBF16Type()),
1375 rewriter.getI32Type()}));
1376 } else if (decodedMulElemOp.kind ==
1378 // 64-lane bfloat16: <64 x bfloat> x <64 x bfloat> -> <64 x float>
1379 SmallVector<Value> operands(
1380 {adaptor.getLhs(), adaptor.getRhs(), confCst});
1381 mulElemOp = xllvm::MulConfBF16I1024ACC2048AIE2pIntrOp::create(
1382 rewriter, loc, VectorType::get({64}, rewriter.getF32Type()),
1383 forceCastOperandsToSignature(
1384 rewriter, loc, operands,
1385 {VectorType::get({64}, rewriter.getBF16Type()),
1386 VectorType::get({64}, rewriter.getBF16Type()),
1387 rewriter.getI32Type()}));
1388 }
1389
1390 // create bitcast/shape_cast for result
1391 auto resultVal = forceCastValueToType(rewriter, loc, mulElemOp,
1392 op.getResult().getType());
1393 rewriter.replaceOp(op, resultVal);
1394 return success();
1395 }
1396};
1397
1398// AIE2p version of FMAElemOp conversion. Uses native F32 accumulators
1399// and AIE2p-specific MAC intrinsics.
1401 : public mlir::ConvertOpToLLVMPattern<aievec::FMAElemOp> {
1402public:
1403 using ConvertOpToLLVMPattern<aievec::FMAElemOp>::ConvertOpToLLVMPattern;
1404
1405 LogicalResult
1406 matchAndRewrite(aievec::FMAElemOp fmaOp, OpAdaptor adaptor,
1407 ConversionPatternRewriter &rewriter) const override {
1408 auto loc = fmaOp.getLoc();
1409 auto lhs = adaptor.getLhs();
1410 auto rhs = adaptor.getRhs();
1411 auto acc = adaptor.getAcc();
1412 auto lhsTy = cast<VectorType>(lhs.getType());
1413 auto accTy = cast<VectorType>(acc.getType());
1414 auto flatLhsTy = getFlattenedVectorType(lhsTy);
1415 auto flatAccTy = getFlattenedVectorType(accTy);
1416
1417 // Flatten operands, if needed
1418 if (lhsTy != flatLhsTy)
1419 lhs = vector::ShapeCastOp::create(rewriter, loc, flatLhsTy, lhs);
1420 if (cast<VectorType>(rhs.getType()) != flatLhsTy)
1421 rhs = vector::ShapeCastOp::create(rewriter, loc, flatLhsTy, rhs);
1422 if (accTy != flatAccTy)
1423 acc = vector::ShapeCastOp::create(rewriter, loc, flatAccTy, acc);
1424
1425 if (!flatLhsTy.getElementType().isBF16()) {
1426 fmaOp.emitWarning()
1427 << "aievec.mac_elem AIE2p conversion only supports bf16 inputs.\n";
1428 return failure();
1429 }
1430
1431 Type i32ty = rewriter.getI32Type();
1432 auto confCst = LLVM::ConstantOp::create(
1433 rewriter, loc, i32ty,
1434 rewriter.getI32IntegerAttr(aiev2_vmac_compute_control(
1435 /*sgn_x=*/0, /*sgn_y=*/0, /*amode=*/2, /*bmode=*/3,
1436 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
1437 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0,
1438 /*sub_mask=*/0)));
1439
1440 unsigned lhsLanes = flatLhsTy.getNumElements();
1441 Value macIntrOp = nullptr;
1442
1443 if (lhsLanes == 16) {
1444 // 16-lane bf16: pad to v32bf16, use I512.I512.ACC512 intrinsic
1445 SmallVector<int64_t> padMask;
1446 for (int i = 0; i < 16; ++i)
1447 padMask.push_back(i);
1448 for (int i = 16; i < 32; ++i)
1449 padMask.push_back(-1); // poison
1450
1451 auto lhsPadded =
1452 vector::ShuffleOp::create(rewriter, loc, lhs, lhs, padMask);
1453 auto rhsPadded =
1454 vector::ShuffleOp::create(rewriter, loc, rhs, rhs, padMask);
1455
1456 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
1457 auto v16f32Ty = VectorType::get({16}, rewriter.getF32Type());
1458 macIntrOp = xllvm::MacConfBF16I512ACC512AIE2pIntrOp::create(
1459 rewriter, loc, v16f32Ty,
1460 forceCastOperandsToSignature(
1461 rewriter, loc, {lhsPadded, rhsPadded, acc, confCst},
1462 {v32bf16Ty, v32bf16Ty, v16f32Ty, i32ty}));
1463 } else if (lhsLanes == 32) {
1464 // 32-lane bf16: direct, use I512.I512.ACC1024 intrinsic
1465 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
1466 auto v32f32Ty = VectorType::get({32}, rewriter.getF32Type());
1467 macIntrOp = xllvm::MacConfBF16I512ACC1024AIE2pIntrOp::create(
1468 rewriter, loc, v32f32Ty,
1469 forceCastOperandsToSignature(
1470 rewriter, loc, {lhs, rhs, acc, confCst},
1471 {v32bf16Ty, v32bf16Ty, v32f32Ty, i32ty}));
1472 } else {
1473 fmaOp.emitWarning()
1474 << "aievec.mac_elem AIE2p conversion: unsupported lane count "
1475 << lhsLanes << ".\n";
1476 return failure();
1477 }
1478
1479 // Recast/Reshape result
1480 auto resVal = forceCastValueToType(rewriter, loc, macIntrOp, flatAccTy);
1481 if (flatAccTy != accTy)
1482 resVal = vector::ShapeCastOp::create(rewriter, loc, accTy, resVal);
1483
1484 rewriter.replaceOp(fmaOp, resVal);
1485 return success();
1486 }
1487};
1488
1489// Enum to represent different AIE target architectures
1490enum class AIEArch {
1493};
1494
1495class UPSOpAIE2Conversion : public mlir::ConvertOpToLLVMPattern<aievec::UPSOp> {
1496public:
1497 using ConvertOpToLLVMPattern<aievec::UPSOp>::ConvertOpToLLVMPattern;
1498
1499 LogicalResult
1500 matchAndRewrite(aievec::UPSOp op, OpAdaptor adaptor,
1501 ConversionPatternRewriter &rewriter) const override {
1502 Location loc = op.getLoc();
1503
1504 Value result = op.getResult();
1505 VectorType resultType = cast<VectorType>(result.getType());
1506 VectorType flatResTy = getFlattenedVectorType(resultType);
1507 Type resultScaTy = resultType.getElementType();
1508 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
1509 int resultLanes = getVectorLaneSize(resultType);
1510 int resultVectorSize = resultBitWidth * resultLanes;
1511
1512 Value opSrcVal = adaptor.getSource();
1513 auto srcVecTy = cast<VectorType>(opSrcVal.getType());
1514 auto fltSrcVecTy = getFlattenedVectorType(srcVecTy);
1515 if (srcVecTy != fltSrcVecTy)
1516 opSrcVal = vector::ShapeCastOp::create(rewriter, op.getLoc(), fltSrcVecTy,
1517 opSrcVal)
1518 .getResult();
1519
1520 // create xllvm intrinsic
1521 // Integer types
1522 Value upsIntrOp = nullptr;
1523 if (llvm::isa<IntegerType>(resultScaTy)) {
1524 // create constant for sign
1525 auto signCst = LLVM::ConstantOp::create(
1526 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
1527 auto shiftCst =
1528 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1529 rewriter.getI32IntegerAttr(op.getShift()));
1530
1531 SmallVector<Value> operands({opSrcVal, shiftCst, signCst});
1532 if (resultVectorSize == 512) {
1533 if (resultBitWidth == 32) {
1534 // v16int16 -> v16acc32
1535 upsIntrOp = xllvm::Acc32V16I256UpsAIE2IntrOp::create(
1536 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1537 forceCastOperandsToSignature(
1538 rewriter, loc, operands,
1539 {VectorType::get({16}, rewriter.getI16Type()),
1540 rewriter.getI32Type(), rewriter.getI32Type()}));
1541 } else if (resultBitWidth == 64) {
1542 // v8int32 -> v8acc64
1543 upsIntrOp = xllvm::Acc64V8I256UpsAIE2IntrOp::create(
1544 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1545 forceCastOperandsToSignature(
1546 rewriter, loc, operands,
1547 {VectorType::get({8}, rewriter.getI32Type()),
1548 rewriter.getI32Type(), rewriter.getI32Type()}));
1549 }
1550 } else if (resultVectorSize == 1024) {
1551 Value src = opSrcVal;
1552 VectorType srcType = cast<VectorType>(src.getType());
1553 Type srcScaType = srcType.getElementType();
1554 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
1555
1556 if (resultBitWidth == 32 && srcBitWidth == 16) {
1557 // v32int16 -> v32acc32
1558 upsIntrOp = xllvm::Acc32V32I512UpsAIE2IntrOp::create(
1559 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1560 forceCastOperandsToSignature(
1561 rewriter, loc, operands,
1562 {VectorType::get({32}, rewriter.getI16Type()),
1563 rewriter.getI32Type(), rewriter.getI32Type()}));
1564 } else if (resultBitWidth == 64 && srcBitWidth == 32) {
1565 // v16int32 -> v16acc64
1566 upsIntrOp = xllvm::Acc64V16I512UpsAIE2IntrOp::create(
1567 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1568 forceCastOperandsToSignature(
1569 rewriter, loc, operands,
1570 {VectorType::get({16}, rewriter.getI32Type()),
1571 rewriter.getI32Type(), rewriter.getI32Type()}));
1572 } else if (resultBitWidth == 64 && srcBitWidth == 16) {
1573 // v16int16 -> v16acc64
1574 upsIntrOp = xllvm::Acc64V16I256UpsAIE2IntrOp::create(
1575 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1576 forceCastOperandsToSignature(
1577 rewriter, loc, operands,
1578 {VectorType::get({16}, rewriter.getI16Type()),
1579 rewriter.getI32Type(), rewriter.getI32Type()}));
1580 } else if (resultBitWidth == 32 && srcBitWidth == 8) {
1581 // v32int8 -> v32acc32
1582 upsIntrOp = xllvm::Acc32V32I256UpsAIE2IntrOp::create(
1583 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1584 forceCastOperandsToSignature(
1585 rewriter, loc, operands,
1586 {VectorType::get({32}, rewriter.getI8Type()),
1587 rewriter.getI32Type(), rewriter.getI32Type()}));
1588 }
1589 }
1590 } else {
1591 // Float types
1592 // AIE2p uses native F32 types, AIE2 uses packed I64 types
1593 if (resultVectorSize == 512) {
1594 // v16bfloat16 -> v16accfloat
1595 upsIntrOp = xllvm::Vector16BF16ToV16AccFloatAIE2IntrOp::create(
1596 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1597 forceCastOperandsToSignature(
1598 rewriter, loc, {opSrcVal},
1599 {VectorType::get({16}, rewriter.getBF16Type())}));
1600 } else if (resultVectorSize == 1024) {
1601 // v32bfloat16 -> v32accfloat
1602 // The CPP example of the implementation is below:
1603 // INTRINSIC(v32accfloat) ups_to_v32accfloat(v32bfloat16 a) {
1604 // v16accfloat x0 = ups_to_v16accfloat(extract_v16bfloat16(a, 0));
1605 // v16accfloat x1 = ups_to_v16accfloat(extract_v16bfloat16(a, 1));
1606 // return concat(x0, x1);
1607 // }
1608 auto indexZeroCst =
1609 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1610 rewriter.getI32IntegerAttr(0));
1611 auto indexOneCst =
1612 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1613 rewriter.getI32IntegerAttr(1));
1614 auto extractUps = [&](Value source, Value index) -> Value {
1615 auto extOp = xllvm::ExtI256I512IntrOp::create(
1616 rewriter, loc, VectorType::get({8}, rewriter.getI32Type()),
1617 forceCastOperandsToSignature(
1618 rewriter, loc, {source, index},
1619 {VectorType::get({16}, rewriter.getI32Type()),
1620 rewriter.getI32Type()}));
1621 return xllvm::Vector16BF16ToV16AccFloatAIE2IntrOp::create(
1622 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1623 forceCastOperandsToSignature(
1624 rewriter, loc, {extOp},
1625 {VectorType::get({16}, rewriter.getBF16Type())}));
1626 };
1627 auto resLo = extractUps(opSrcVal, indexZeroCst);
1628 auto resHi = extractUps(opSrcVal, indexOneCst);
1629 // Concat the two 512-bit vector to a 1024-bit vector.
1630 // Note that given sources a0 and a1, the result is [a1; a0].
1631 upsIntrOp = xllvm::ConcatI1024I512IntrOp::create(
1632 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
1633 forceCastOperandsToSignature(
1634 rewriter, loc, {resLo, resHi},
1635 {VectorType::get({16}, rewriter.getI32Type()),
1636 VectorType::get({16}, rewriter.getI32Type())}));
1637 }
1638 }
1639
1640 if (!upsIntrOp) {
1641 op.emitWarning() << "aievec.ups is not supported.\n";
1642 return failure();
1643 }
1644
1645 // create bitcast for result if needed
1646 if (flatResTy != upsIntrOp.getType())
1647 upsIntrOp = LLVM::BitcastOp::create(rewriter, loc, flatResTy, upsIntrOp);
1648
1649 if (flatResTy != resultType)
1650 upsIntrOp =
1651 vector::ShapeCastOp::create(rewriter, loc, resultType, upsIntrOp);
1652
1653 rewriter.replaceOp(op, upsIntrOp);
1654
1655 return success();
1656 }
1657};
1658
1659// TODO: Split the op at AIEVec dialect level
1661 : public mlir::ConvertOpToLLVMPattern<aievec::UPSOp> {
1662public:
1663 using ConvertOpToLLVMPattern<aievec::UPSOp>::ConvertOpToLLVMPattern;
1664
1665 LogicalResult
1666 matchAndRewrite(aievec::UPSOp op, OpAdaptor adaptor,
1667 ConversionPatternRewriter &rewriter) const override {
1668 Location loc = op.getLoc();
1669
1670 Value result = op.getResult();
1671 VectorType resultType = cast<VectorType>(result.getType());
1672 VectorType flatResTy = getFlattenedVectorType(resultType);
1673 Type resultScaTy = resultType.getElementType();
1674 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
1675 int resultLanes = getVectorLaneSize(resultType);
1676 int resultVectorSize = resultBitWidth * resultLanes;
1677
1678 Value opSrcVal = adaptor.getSource();
1679 auto srcVecTy = cast<VectorType>(opSrcVal.getType());
1680 auto fltSrcVecTy = getFlattenedVectorType(srcVecTy);
1681 if (srcVecTy != fltSrcVecTy)
1682 opSrcVal = vector::ShapeCastOp::create(rewriter, op.getLoc(), fltSrcVecTy,
1683 opSrcVal)
1684 .getResult();
1685
1686 // create xllvm intrinsic
1687 // Integer types
1688 Value upsIntrOp = nullptr;
1689 if (llvm::isa<IntegerType>(resultScaTy)) {
1690 // create constant for sign
1691 auto signCst = LLVM::ConstantOp::create(
1692 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
1693 auto shiftCst =
1694 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1695 rewriter.getI32IntegerAttr(op.getShift()));
1696
1697 SmallVector<Value> operands({opSrcVal, shiftCst, signCst});
1698 if (resultVectorSize == 512) {
1699 if (resultBitWidth == 32) {
1700 // v16int16 -> v16acc32
1701 upsIntrOp = xllvm::Acc32V16I256UpsAIE2pIntrOp::create(
1702 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
1703 forceCastOperandsToSignature(
1704 rewriter, loc, operands,
1705 {VectorType::get({16}, rewriter.getI16Type()),
1706 rewriter.getI32Type(), rewriter.getI32Type()}));
1707 } else if (resultBitWidth == 64) {
1708 // v8int32 -> v8acc64
1709 upsIntrOp = xllvm::Acc64V8I256UpsAIE2pIntrOp::create(
1710 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
1711 forceCastOperandsToSignature(
1712 rewriter, loc, operands,
1713 {VectorType::get({8}, rewriter.getI32Type()),
1714 rewriter.getI32Type(), rewriter.getI32Type()}));
1715 }
1716 } else if (resultVectorSize == 1024) {
1717 Value src = opSrcVal;
1718 VectorType srcType = cast<VectorType>(src.getType());
1719 Type srcScaType = srcType.getElementType();
1720 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
1721 int srcLanes = getVectorLaneSize(srcType);
1722 int srcVectorSize = srcBitWidth * srcLanes;
1723
1724 if (resultBitWidth == 32 && srcBitWidth == 16 && srcVectorSize == 512) {
1725 // v32int16 -> v32acc32
1726 upsIntrOp = xllvm::Acc32V32I512UpsAIE2pIntrOp::create(
1727 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
1728 forceCastOperandsToSignature(
1729 rewriter, loc, operands,
1730 {VectorType::get({32}, rewriter.getI16Type()),
1731 rewriter.getI32Type(), rewriter.getI32Type()}));
1732 } else if (resultBitWidth == 64 && srcBitWidth == 32 &&
1733 srcVectorSize == 512) {
1734 // v16int32 -> v16acc64
1735 upsIntrOp = xllvm::Acc64V16I512UpsAIE2pIntrOp::create(
1736 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1737 forceCastOperandsToSignature(
1738 rewriter, loc, operands,
1739 {VectorType::get({16}, rewriter.getI32Type()),
1740 rewriter.getI32Type(), rewriter.getI32Type()}));
1741 } else if (resultBitWidth == 64 && srcBitWidth == 16 &&
1742 srcVectorSize == 256) {
1743 // v16int16 -> v16acc64
1744 upsIntrOp = xllvm::Acc64V16I256UpsAIE2pIntrOp::create(
1745 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()),
1746 forceCastOperandsToSignature(
1747 rewriter, loc, operands,
1748 {VectorType::get({16}, rewriter.getI16Type()),
1749 rewriter.getI32Type(), rewriter.getI32Type()}));
1750 } else if (resultBitWidth == 32 && srcBitWidth == 8 &&
1751 srcVectorSize == 256) {
1752 // v32int8 -> v32acc32
1753 upsIntrOp = xllvm::Acc32V32I256UpsAIE2pIntrOp::create(
1754 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
1755 forceCastOperandsToSignature(
1756 rewriter, loc, operands,
1757 {VectorType::get({32}, rewriter.getI8Type()),
1758 rewriter.getI32Type(), rewriter.getI32Type()}));
1759 }
1760 } else if (resultVectorSize == 2048) {
1761 Value src = opSrcVal;
1762 VectorType srcType = cast<VectorType>(src.getType());
1763 Type srcScaType = srcType.getElementType();
1764 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
1765 int srcLanes = getVectorLaneSize(srcType);
1766 int srcVectorSize = srcBitWidth * srcLanes;
1767
1768 if (resultBitWidth == 32 && srcBitWidth == 8 && srcVectorSize == 512) {
1769 // v64int8 -> v64acc32
1770 upsIntrOp = xllvm::Acc32V64I512UpsAIE2pIntrOp::create(
1771 rewriter, loc, VectorType::get({64}, rewriter.getI32Type()),
1772 forceCastOperandsToSignature(
1773 rewriter, loc, operands,
1774 {VectorType::get({64}, rewriter.getI8Type()),
1775 rewriter.getI32Type(), rewriter.getI32Type()}));
1776 } else if (resultBitWidth == 64 && srcBitWidth == 16 &&
1777 srcVectorSize == 512) {
1778 // v32int16 -> v32acc64
1779 upsIntrOp = xllvm::Acc64V32I512UpsAIE2pIntrOp::create(
1780 rewriter, loc, VectorType::get({32}, rewriter.getI64Type()),
1781 forceCastOperandsToSignature(
1782 rewriter, loc, operands,
1783 {VectorType::get({32}, rewriter.getI16Type()),
1784 rewriter.getI32Type(), rewriter.getI32Type()}));
1785 } else if (resultBitWidth == 32 && srcBitWidth == 16 &&
1786 srcVectorSize == 1024) {
1787 // v64int16 -> v64acc32
1788 // Extract 2 chunks of v32int16 and convert each to v32acc32
1789 auto index0Cst =
1790 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1791 rewriter.getI32IntegerAttr(0));
1792 auto index1Cst =
1793 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1794 rewriter.getI32IntegerAttr(1));
1795
1796 auto extractUps2048 = [&](Value source, Value index, Value shiftCst,
1797 Value signCst) -> Value {
1798 // Use vector::ShuffleOp to extract 512-bit from 1024-bit
1799 // Cast source to v32xi32 for shuffling
1800 auto v32i32Source = forceCastValueToType(
1801 rewriter, loc, source,
1802 VectorType::get({32}, rewriter.getI32Type()));
1803
1804 // Determine shuffle mask based on index
1805 // index 0: elements [0-15]
1806 // index 1: elements [16-31]
1807 SmallVector<int64_t> shuffleMask;
1808 if (auto constIndex = index.getDefiningOp<LLVM::ConstantOp>()) {
1809 auto indexAttr = cast<IntegerAttr>(constIndex.getValue());
1810 int64_t idxVal = indexAttr.getInt();
1811 int startIdx = idxVal * 16;
1812 for (int i = 0; i < 16; ++i) {
1813 shuffleMask.push_back(startIdx + i);
1814 }
1815 } else {
1816 // Default to index 0 if not constant
1817 for (int i = 0; i < 16; ++i) {
1818 shuffleMask.push_back(i);
1819 }
1820 }
1821
1822 auto extOp = vector::ShuffleOp::create(rewriter, loc, v32i32Source,
1823 v32i32Source, shuffleMask);
1824
1825 return xllvm::Acc32V32I512UpsAIE2pIntrOp::create(
1826 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
1827 forceCastOperandsToSignature(
1828 rewriter, loc, {extOp, shiftCst, signCst},
1829 {VectorType::get({32}, rewriter.getI16Type()),
1830 rewriter.getI32Type(), rewriter.getI32Type()}));
1831 };
1832
1833 auto res0 = extractUps2048(opSrcVal, index0Cst, shiftCst, signCst);
1834 auto res1 = extractUps2048(opSrcVal, index1Cst, shiftCst, signCst);
1835
1836 // Concat two 1024-bit vectors to a 2048-bit vector using
1837 // vector::ShuffleOp
1838 SmallVector<int64_t> concatMask;
1839 for (int i = 0; i < 64; ++i) {
1840 concatMask.push_back(i);
1841 }
1842 upsIntrOp =
1843 vector::ShuffleOp::create(rewriter, loc, res0, res1, concatMask);
1844 }
1845 }
1846 } else {
1847 // Float types
1848 // AIE2p uses native F32 types, AIE2 uses packed I64 types
1849 if (resultVectorSize == 512) {
1850 // v16bfloat16 -> v16accfloat
1851 upsIntrOp = xllvm::Vector16BF16ToV16AccFloatAIE2pIntrOp::create(
1852 rewriter, loc, VectorType::get({16}, rewriter.getF32Type()),
1853 forceCastOperandsToSignature(
1854 rewriter, loc, {opSrcVal},
1855 {VectorType::get({16}, rewriter.getBF16Type())}));
1856 } else if (resultVectorSize == 1024) {
1857 // v32bfloat16 -> v32accfloat
1858 upsIntrOp = xllvm::Vector32BF16ToV32AccFloatAIE2pIntrOp::create(
1859 rewriter, loc, VectorType::get({32}, rewriter.getF32Type()),
1860 forceCastOperandsToSignature(
1861 rewriter, loc, {opSrcVal},
1862 {VectorType::get({32}, rewriter.getBF16Type())}));
1863 } else if (resultVectorSize == 2048) {
1864 // v64bfloat16 -> v64accfloat
1865 // Extract 2 chunks of v32bfloat16 and convert each to v32accfloat
1866 auto index0Cst =
1867 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1868 rewriter.getI32IntegerAttr(0));
1869 auto index1Cst =
1870 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1871 rewriter.getI32IntegerAttr(1));
1872
1873 auto extractUps2048 = [&](Value source, Value index) -> Value {
1874 // Use vector::ShuffleOp to extract 512-bit from 1024-bit
1875 // Cast source to v32xi32 for shuffling
1876 auto v32i32Source = forceCastValueToType(
1877 rewriter, loc, source,
1878 VectorType::get({32}, rewriter.getI32Type()));
1879
1880 // Determine shuffle mask based on index
1881 // index 0: elements [0-15]
1882 // index 1: elements [16-31]
1883 SmallVector<int64_t> shuffleMask;
1884 if (auto constIndex = index.getDefiningOp<LLVM::ConstantOp>()) {
1885 auto indexAttr = cast<IntegerAttr>(constIndex.getValue());
1886 int64_t idxVal = indexAttr.getInt();
1887 int startIdx = idxVal * 16;
1888 for (int i = 0; i < 16; ++i) {
1889 shuffleMask.push_back(startIdx + i);
1890 }
1891 } else {
1892 // Default to index 0 if not constant
1893 for (int i = 0; i < 16; ++i) {
1894 shuffleMask.push_back(i);
1895 }
1896 }
1897
1898 auto extOp = vector::ShuffleOp::create(rewriter, loc, v32i32Source,
1899 v32i32Source, shuffleMask);
1900
1901 return xllvm::Vector32BF16ToV32AccFloatAIE2pIntrOp::create(
1902 rewriter, loc, VectorType::get({32}, rewriter.getF32Type()),
1903 forceCastOperandsToSignature(
1904 rewriter, loc, {extOp},
1905 {VectorType::get({32}, rewriter.getBF16Type())}));
1906 };
1907
1908 auto res0 = extractUps2048(opSrcVal, index0Cst);
1909 auto res1 = extractUps2048(opSrcVal, index1Cst);
1910
1911 // Concat two 1024-bit vectors to a 2048-bit vector using
1912 // vector::ShuffleOp
1913 auto v32i32Res0 = forceCastValueToType(
1914 rewriter, loc, res0, VectorType::get({32}, rewriter.getI32Type()));
1915 auto v32i32Res1 = forceCastValueToType(
1916 rewriter, loc, res1, VectorType::get({32}, rewriter.getI32Type()));
1917
1918 SmallVector<int64_t> concatMask;
1919 for (int i = 0; i < 64; ++i) {
1920 concatMask.push_back(i);
1921 }
1922 upsIntrOp = vector::ShuffleOp::create(rewriter, loc, v32i32Res0,
1923 v32i32Res1, concatMask);
1924 }
1925 }
1926
1927 if (!upsIntrOp) {
1928 op.emitWarning() << "aievec.ups is not supported.\n";
1929 return failure();
1930 }
1931
1932 // create bitcast for result if needed
1933 if (flatResTy != upsIntrOp.getType())
1934 upsIntrOp = LLVM::BitcastOp::create(rewriter, loc, flatResTy, upsIntrOp);
1935
1936 if (flatResTy != resultType)
1937 upsIntrOp =
1938 vector::ShapeCastOp::create(rewriter, loc, resultType, upsIntrOp);
1939
1940 rewriter.replaceOp(op, upsIntrOp);
1941
1942 return success();
1943 }
1944};
1945
1946class SRSOpAIE2Conversion : public mlir::ConvertOpToLLVMPattern<aievec::SRSOp> {
1947public:
1948 using ConvertOpToLLVMPattern<aievec::SRSOp>::ConvertOpToLLVMPattern;
1949
1950 LogicalResult
1951 matchAndRewrite(aievec::SRSOp op, OpAdaptor adaptor,
1952 ConversionPatternRewriter &rewriter) const override {
1953 Location loc = op.getLoc();
1954
1955 Value result = op.getResult();
1956 VectorType resultType = cast<VectorType>(result.getType());
1957 Type resultScaTy = resultType.getElementType();
1958 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
1959 int resultLanes = getVectorLaneSize(resultType);
1960 int resultVectorSize = resultBitWidth * resultLanes;
1961
1962 // Integer types
1963 Value srsIntrOp = nullptr;
1964 if (llvm::isa<IntegerType>(resultScaTy)) {
1965 // create constant for sign from the op's sign attribute
1966 auto signCst =
1967 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
1968 rewriter.getI32IntegerAttr(op.getSign()));
1969
1970 // create xllvm intrinsic
1971 SmallVector<Value> operands(
1972 {adaptor.getSource(), adaptor.getShift(), signCst});
1973 if (resultVectorSize == 512) {
1974 if (resultBitWidth == 16) {
1975 srsIntrOp = xllvm::I512V32Acc32SrsAIE2IntrOp::create(
1976 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()),
1977 forceCastOperandsToSignature(
1978 rewriter, loc, operands,
1979 {VectorType::get({16}, rewriter.getI64Type()),
1980 rewriter.getI32Type(), rewriter.getI32Type()}));
1981 } else if (resultBitWidth == 32) {
1982 srsIntrOp = xllvm::I512V16Acc64SrsAIE2IntrOp::create(
1983 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
1984 forceCastOperandsToSignature(
1985 rewriter, loc, operands,
1986 {VectorType::get({16}, rewriter.getI64Type()),
1987 rewriter.getI32Type(), rewriter.getI32Type()}));
1988 }
1989 } else if (resultVectorSize == 256) {
1990 Value src = adaptor.getSource();
1991 VectorType srcType = cast<VectorType>(src.getType());
1992 Type srcScaType = srcType.getElementType();
1993 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
1994
1995 if (resultBitWidth == 16 && srcBitWidth == 32) {
1996 srsIntrOp = xllvm::I256V16Acc32SrsAIE2IntrOp::create(
1997 rewriter, loc, VectorType::get({16}, rewriter.getI16Type()),
1998 forceCastOperandsToSignature(
1999 rewriter, loc, operands,
2000 {VectorType::get({8}, rewriter.getI64Type()),
2001 rewriter.getI32Type(), rewriter.getI32Type()}));
2002 } else if (resultBitWidth == 8 && srcBitWidth == 32) {
2003 srsIntrOp = xllvm::I256V32Acc32SrsAIE2IntrOp::create(
2004 rewriter, loc, VectorType::get({32}, rewriter.getI8Type()),
2005 forceCastOperandsToSignature(
2006 rewriter, loc, operands,
2007 {VectorType::get({16}, rewriter.getI64Type()),
2008 rewriter.getI32Type(), rewriter.getI32Type()}));
2009 } else if (resultBitWidth == 16 && srcBitWidth == 64) {
2010 srsIntrOp = xllvm::I256V16Acc64SrsAIE2IntrOp::create(
2011 rewriter, loc, VectorType::get({16}, rewriter.getI16Type()),
2012 forceCastOperandsToSignature(
2013 rewriter, loc, operands,
2014 {VectorType::get({16}, rewriter.getI64Type()),
2015 rewriter.getI32Type(), rewriter.getI32Type()}));
2016 } else if (resultBitWidth == 32 && srcBitWidth == 64) {
2017 srsIntrOp = xllvm::I256V8Acc64SrsAIE2IntrOp::create(
2018 rewriter, loc, VectorType::get({8}, rewriter.getI32Type()),
2019 forceCastOperandsToSignature(
2020 rewriter, loc, operands,
2021 {VectorType::get({8}, rewriter.getI64Type()),
2022 rewriter.getI32Type(), rewriter.getI32Type()}));
2023 }
2024 }
2025 } else {
2026 // Float types
2027 if (resultVectorSize == 256) {
2028 srsIntrOp = xllvm::Vector16AccFloatToV16BF16AIE2IntrOp::create(
2029 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
2030 forceCastOperandsToSignature(
2031 rewriter, loc, {adaptor.getSource()},
2032 {VectorType::get({8}, rewriter.getI64Type())}));
2033 } else if (resultVectorSize == 512) {
2034 // v32accfloat -> v32bfloat16
2035 // The CPP example of the implementation is below:
2036 // v32bfloat16 to_v32bfloat16(v32accfloat acc) {
2037 // v16bfloat16 x0 = to_v16bfloat16(extract_v16accfloat(acc, 0));
2038 // v16bfloat16 x1 = to_v16bfloat16(extract_v16accfloat(acc, 1));
2039 // return concat(x0, x1);
2040 // }
2041 auto indexZeroCst =
2042 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2043 rewriter.getI32IntegerAttr(0));
2044 auto indexOneCst =
2045 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2046 rewriter.getI32IntegerAttr(1));
2047 auto extractSrs = [&](Value source, Value index) -> Value {
2048 auto extOp = xllvm::ExtI512I1024IntrOp::create(
2049 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2050 forceCastOperandsToSignature(
2051 rewriter, loc, {source, index},
2052 {VectorType::get({32}, rewriter.getI32Type()),
2053 rewriter.getI32Type()}));
2054 return xllvm::Vector16AccFloatToV16BF16AIE2IntrOp::create(
2055 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
2056 forceCastOperandsToSignature(
2057 rewriter, loc, {extOp},
2058 {VectorType::get({8}, rewriter.getI64Type())}));
2059 };
2060 auto resLo = extractSrs(adaptor.getSource(), indexZeroCst);
2061 auto resHi = extractSrs(adaptor.getSource(), indexOneCst);
2062 // Concat the two 256-bit vector to a 512-bit vector.
2063 // Note that given sources a0 and a1, the result is [a1; a0].
2064 srsIntrOp = xllvm::ConcatI512I256IntrOp::create(
2065 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2066 forceCastOperandsToSignature(
2067 rewriter, loc, {resLo, resHi},
2068 {VectorType::get({8}, rewriter.getI32Type()),
2069 VectorType::get({8}, rewriter.getI32Type())}));
2070 }
2071 }
2072
2073 if (!srsIntrOp) {
2074 op.emitWarning() << "aievec.srs is not supported.\n";
2075 return failure();
2076 }
2077
2078 // create bitcast/shape_cast for result if needed
2079 auto resultVal = forceCastValueToType(rewriter, loc, srsIntrOp,
2080 op.getResult().getType());
2081 rewriter.replaceOp(op, resultVal);
2082
2083 return success();
2084 }
2085};
2086
2087// TODO: Split the op at AIEVec dialect level
2089 : public mlir::ConvertOpToLLVMPattern<aievec::SRSOp> {
2090public:
2091 using ConvertOpToLLVMPattern<aievec::SRSOp>::ConvertOpToLLVMPattern;
2092
2093 LogicalResult
2094 matchAndRewrite(aievec::SRSOp op, OpAdaptor adaptor,
2095 ConversionPatternRewriter &rewriter) const override {
2096 Location loc = op.getLoc();
2097
2098 Value result = op.getResult();
2099 VectorType resultType = cast<VectorType>(result.getType());
2100 Type resultScaTy = resultType.getElementType();
2101 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
2102 int resultLanes = getVectorLaneSize(resultType);
2103 int resultVectorSize = resultBitWidth * resultLanes;
2104
2105 // Integer types
2106 Value srsIntrOp = nullptr;
2107 if (llvm::isa<IntegerType>(resultScaTy)) {
2108 // create constant for sign from the op's sign attribute
2109 auto signCst =
2110 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2111 rewriter.getI32IntegerAttr(op.getSign()));
2112
2113 // create xllvm intrinsic
2114 SmallVector<Value> operands(
2115 {adaptor.getSource(), adaptor.getShift(), signCst});
2116 if (resultVectorSize == 512) {
2117 Value src = adaptor.getSource();
2118 VectorType srcType = cast<VectorType>(src.getType());
2119 Type srcScaType = srcType.getElementType();
2120 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
2121
2122 if (resultBitWidth == 16 && srcBitWidth == 32) {
2123 // v32acc32 -> v32int16
2124 srsIntrOp = xllvm::I512V32Acc32SrsAIE2pIntrOp::create(
2125 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()),
2126 forceCastOperandsToSignature(
2127 rewriter, loc, operands,
2128 {VectorType::get({32}, rewriter.getI32Type()),
2129 rewriter.getI32Type(), rewriter.getI32Type()}));
2130 } else if (resultBitWidth == 16 && srcBitWidth == 64) {
2131 // v32acc64 -> v32int16
2132 srsIntrOp = xllvm::I512V32Acc64SrsAIE2pIntrOp::create(
2133 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()),
2134 forceCastOperandsToSignature(
2135 rewriter, loc, operands,
2136 {VectorType::get({32}, rewriter.getI64Type()),
2137 rewriter.getI32Type(), rewriter.getI32Type()}));
2138 } else if (resultBitWidth == 32 && srcBitWidth == 64) {
2139 // v16acc64 -> v16int32
2140 srsIntrOp = xllvm::I512V16Acc64SrsAIE2pIntrOp::create(
2141 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2142 forceCastOperandsToSignature(
2143 rewriter, loc, operands,
2144 {VectorType::get({16}, rewriter.getI64Type()),
2145 rewriter.getI32Type(), rewriter.getI32Type()}));
2146 } else if (resultBitWidth == 8 && srcBitWidth == 32) {
2147 // v64acc32 -> v64int8
2148 srsIntrOp = xllvm::I512V64Acc32SrsAIE2pIntrOp::create(
2149 rewriter, loc, VectorType::get({64}, rewriter.getI8Type()),
2150 forceCastOperandsToSignature(
2151 rewriter, loc, operands,
2152 {VectorType::get({64}, rewriter.getI32Type()),
2153 rewriter.getI32Type(), rewriter.getI32Type()}));
2154 }
2155 } else if (resultVectorSize == 256) {
2156 Value src = adaptor.getSource();
2157 VectorType srcType = cast<VectorType>(src.getType());
2158 Type srcScaType = srcType.getElementType();
2159 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
2160
2161 if (resultBitWidth == 16 && srcBitWidth == 32) {
2162 // v16acc32 -> v16int16
2163 srsIntrOp = xllvm::I256V16Acc32SrsAIE2pIntrOp::create(
2164 rewriter, loc, VectorType::get({16}, rewriter.getI16Type()),
2165 forceCastOperandsToSignature(
2166 rewriter, loc, operands,
2167 {VectorType::get({16}, rewriter.getI32Type()),
2168 rewriter.getI32Type(), rewriter.getI32Type()}));
2169 } else if (resultBitWidth == 8 && srcBitWidth == 32) {
2170 // v32acc32 -> v32int8
2171 srsIntrOp = xllvm::I256V32Acc32SrsAIE2pIntrOp::create(
2172 rewriter, loc, VectorType::get({32}, rewriter.getI8Type()),
2173 forceCastOperandsToSignature(
2174 rewriter, loc, operands,
2175 {VectorType::get({32}, rewriter.getI32Type()),
2176 rewriter.getI32Type(), rewriter.getI32Type()}));
2177 } else if (resultBitWidth == 16 && srcBitWidth == 64) {
2178 // v16acc64 -> v16int16
2179 srsIntrOp = xllvm::I256V16Acc64SrsAIE2pIntrOp::create(
2180 rewriter, loc, VectorType::get({16}, rewriter.getI16Type()),
2181 forceCastOperandsToSignature(
2182 rewriter, loc, operands,
2183 {VectorType::get({16}, rewriter.getI64Type()),
2184 rewriter.getI32Type(), rewriter.getI32Type()}));
2185 } else if (resultBitWidth == 32 && srcBitWidth == 64) {
2186 // v8acc64 -> v8int32
2187 srsIntrOp = xllvm::I256V8Acc64SrsAIE2pIntrOp::create(
2188 rewriter, loc, VectorType::get({8}, rewriter.getI32Type()),
2189 forceCastOperandsToSignature(
2190 rewriter, loc, operands,
2191 {VectorType::get({8}, rewriter.getI64Type()),
2192 rewriter.getI32Type(), rewriter.getI32Type()}));
2193 }
2194 } else if (resultVectorSize == 1024) {
2195 Value src = adaptor.getSource();
2196 VectorType srcType = cast<VectorType>(src.getType());
2197 Type srcScaType = srcType.getElementType();
2198 unsigned srcBitWidth = srcScaType.getIntOrFloatBitWidth();
2199
2200 if (resultBitWidth == 16 && srcBitWidth == 32) {
2201 // v64acc32 -> v64int16
2202 // Extract 2 chunks of v32acc32 and convert each to v32int16
2203 auto index0Cst =
2204 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2205 rewriter.getI32IntegerAttr(0));
2206 auto index1Cst =
2207 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2208 rewriter.getI32IntegerAttr(1));
2209
2210 auto extractSrs1024 = [&](Value source, Value index, Value shiftCst,
2211 Value signCst) -> Value {
2212 // Use vector::ShuffleOp to extract 1024-bit from 2048-bit
2213 // Cast source to v64xi32 for shuffling
2214 auto v64i32Source = forceCastValueToType(
2215 rewriter, loc, source,
2216 VectorType::get({64}, rewriter.getI32Type()));
2217
2218 // Determine shuffle mask based on index
2219 // index 0: elements [0-31]
2220 // index 1: elements [32-63]
2221 SmallVector<int64_t> shuffleMask;
2222 if (auto constIndex = index.getDefiningOp<LLVM::ConstantOp>()) {
2223 auto indexAttr = cast<IntegerAttr>(constIndex.getValue());
2224 int64_t idxVal = indexAttr.getInt();
2225 int startIdx = idxVal * 32;
2226 for (int i = 0; i < 32; ++i) {
2227 shuffleMask.push_back(startIdx + i);
2228 }
2229 } else {
2230 // Default to index 0 if not constant
2231 for (int i = 0; i < 32; ++i) {
2232 shuffleMask.push_back(i);
2233 }
2234 }
2235
2236 auto extOp = vector::ShuffleOp::create(rewriter, loc, v64i32Source,
2237 v64i32Source, shuffleMask);
2238
2239 return xllvm::I512V32Acc32SrsAIE2pIntrOp::create(
2240 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()),
2241 forceCastOperandsToSignature(
2242 rewriter, loc, {extOp, shiftCst, signCst},
2243 {VectorType::get({32}, rewriter.getI32Type()),
2244 rewriter.getI32Type(), rewriter.getI32Type()}));
2245 };
2246
2247 auto res0 =
2248 extractSrs1024(src, index0Cst, adaptor.getShift(), signCst);
2249 auto res1 =
2250 extractSrs1024(src, index1Cst, adaptor.getShift(), signCst);
2251
2252 // Concat two 512-bit vectors to a 1024-bit vector using
2253 // vector::ShuffleOp
2254 auto v16i32Res0 = forceCastValueToType(
2255 rewriter, loc, res0,
2256 VectorType::get({16}, rewriter.getI32Type()));
2257 auto v16i32Res1 = forceCastValueToType(
2258 rewriter, loc, res1,
2259 VectorType::get({16}, rewriter.getI32Type()));
2260
2261 SmallVector<int64_t> concatMask;
2262 for (int i = 0; i < 32; ++i) {
2263 concatMask.push_back(i);
2264 }
2265 srsIntrOp = vector::ShuffleOp::create(rewriter, loc, v16i32Res0,
2266 v16i32Res1, concatMask);
2267 }
2268 }
2269 } else {
2270 // Float types
2271 // AIE2p uses native F32 types, AIE2 uses packed I64 types
2272 if (resultVectorSize == 256) {
2273 // v16accfloat -> v16bfloat16
2274 srsIntrOp = xllvm::Vector16AccFloatToV16BF16AIE2pIntrOp::create(
2275 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
2276 forceCastOperandsToSignature(
2277 rewriter, loc, {adaptor.getSource()},
2278 {VectorType::get({16}, rewriter.getF32Type())}));
2279 } else if (resultVectorSize == 512) {
2280 // v32accfloat -> v32bfloat16
2281 srsIntrOp = xllvm::Vector32AccFloatToV32BF16AIE2pIntrOp::create(
2282 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
2283 forceCastOperandsToSignature(
2284 rewriter, loc, {adaptor.getSource()},
2285 {VectorType::get({32}, rewriter.getF32Type())}));
2286 } else if (resultVectorSize == 1024) {
2287 // v64accfloat -> v64bfloat16
2288 // Extract 2 chunks of v32accfloat and convert each to v32bfloat16
2289 auto index0Cst =
2290 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2291 rewriter.getI32IntegerAttr(0));
2292 auto index1Cst =
2293 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2294 rewriter.getI32IntegerAttr(1));
2295
2296 auto extractSrs1024 = [&](Value source, Value index) -> Value {
2297 // Use vector::ShuffleOp to extract 1024-bit from 2048-bit
2298 // Cast source to v64xi32 for shuffling
2299 auto v64i32Source = forceCastValueToType(
2300 rewriter, loc, source,
2301 VectorType::get({64}, rewriter.getI32Type()));
2302
2303 // Determine shuffle mask based on index
2304 // index 0: elements [0-31]
2305 // index 1: elements [32-63]
2306 SmallVector<int64_t> shuffleMask;
2307 if (auto constIndex = index.getDefiningOp<LLVM::ConstantOp>()) {
2308 auto indexAttr = cast<IntegerAttr>(constIndex.getValue());
2309 int64_t idxVal = indexAttr.getInt();
2310 int startIdx = idxVal * 32;
2311 for (int i = 0; i < 32; ++i) {
2312 shuffleMask.push_back(startIdx + i);
2313 }
2314 } else {
2315 // Default to index 0 if not constant
2316 for (int i = 0; i < 32; ++i) {
2317 shuffleMask.push_back(i);
2318 }
2319 }
2320
2321 auto extOp = vector::ShuffleOp::create(rewriter, loc, v64i32Source,
2322 v64i32Source, shuffleMask);
2323
2324 return xllvm::Vector32AccFloatToV32BF16AIE2pIntrOp::create(
2325 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
2326 forceCastOperandsToSignature(
2327 rewriter, loc, {extOp},
2328 {VectorType::get({32}, rewriter.getF32Type())}));
2329 };
2330
2331 auto res0 = extractSrs1024(adaptor.getSource(), index0Cst);
2332 auto res1 = extractSrs1024(adaptor.getSource(), index1Cst);
2333
2334 // Concat two 512-bit vectors to a 1024-bit vector using
2335 // vector::ShuffleOp
2336 auto v16i32Res0 = forceCastValueToType(
2337 rewriter, loc, res0, VectorType::get({16}, rewriter.getI32Type()));
2338 auto v16i32Res1 = forceCastValueToType(
2339 rewriter, loc, res1, VectorType::get({16}, rewriter.getI32Type()));
2340
2341 SmallVector<int64_t> concatMask;
2342 for (int i = 0; i < 32; ++i) {
2343 concatMask.push_back(i);
2344 }
2345 srsIntrOp = vector::ShuffleOp::create(rewriter, loc, v16i32Res0,
2346 v16i32Res1, concatMask);
2347 }
2348 }
2349
2350 if (!srsIntrOp) {
2351 op.emitWarning() << "aievec.srs is not supported.\n";
2352 return failure();
2353 }
2354
2355 // create bitcast/shape_cast for result if needed
2356 auto resultVal = forceCastValueToType(rewriter, loc, srsIntrOp,
2357 op.getResult().getType());
2358 rewriter.replaceOp(op, resultVal);
2359
2360 return success();
2361 }
2362};
2363
2364class UPDOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::UPDOp> {
2365public:
2366 using ConvertOpToLLVMPattern<aievec::UPDOp>::ConvertOpToLLVMPattern;
2367
2368 static std::string getIntrinsicName(aievec::UPDOp op, int loadSize) {
2369 auto resultType = cast<VectorType>(op.getResult().getType());
2370 std::stringstream ss;
2371 ss << "llvm.aie.upd.";
2372 ss << (loadSize == 128 ? 'v' : loadSize == 256 ? 'w' : 'x') << ".";
2373 ss << getVectorTypeString(resultType) << ".";
2374 // The index affects which intrinsic to call
2375 ss << (op.getIndex() == 0 ? "lo" : "hi");
2376 return ss.str();
2377 }
2378
2379 LogicalResult
2380 matchAndRewrite(aievec::UPDOp op, OpAdaptor adaptor,
2381 ConversionPatternRewriter &rewriter) const override {
2382 auto module = op->getParentOfType<ModuleOp>();
2383 MLIRContext *context = rewriter.getContext();
2384
2385 // A bit more complicated: load the vector, then update result vector
2386 // AIE1 is capable of 128-bit on one bank and 256-bit loads on even-odd
2387 // banks Identify size of update
2388 int vecSizeInBits =
2389 getVectorSizeInBits(cast<VectorType>(op.getResult().getType()));
2390
2391 auto ptr = this->getStridedElementPtr(
2392 rewriter, op->getLoc(), cast<MemRefType>(op.getSource().getType()),
2393 adaptor.getSource(), adaptor.getIndices());
2394
2395 // TODO: handle the offset field
2396
2397 if (vecSizeInBits <= 256) {
2398 // Total <=256-bit updates are much simpler:
2399 // we can do a direct load into the vector register
2400 // look at the indices to calculate the address
2401 auto vectorPtrType = LLVM::LLVMPointerType::get(
2402 getContext(),
2403 cast<MemRefType>(op.getSource().getType()).getMemorySpaceAsInt());
2404 auto castedPtr =
2405 LLVM::BitcastOp::create(rewriter, op->getLoc(), vectorPtrType, ptr);
2406 auto vecType = cast<VectorType>(op.getResult().getType());
2407 rewriter.replaceOpWithNewOp<LLVM::LoadOp>(op, vecType, castedPtr, 1);
2408 } else {
2409 // Total >256-bit updates will require upd ops to fill the whole vector
2410 // each UDP op represents one of these 256-bit loads and updates
2411
2412 // Determine the load size
2413 // TODO: no examples of 1024-bit output vectors: doesn't feel right
2414 // to attempt a 512-bit load to do an update like this
2415 int loadSize = vecSizeInBits == 256 ? 128
2416 : vecSizeInBits == 512 ? 256
2417 : 512;
2418
2419 // Create a vectorType for the load proper
2420 // Load half of the final result vector
2421 auto resultType = cast<VectorType>(op.getResult().getType());
2422 int lanes = getVectorLaneSize(resultType);
2423 auto loadType =
2424 VectorType::get({(int64_t)lanes / 2}, resultType.getElementType());
2425
2426 // Load the vector
2427 auto vectorPtrType = LLVM::LLVMPointerType::get(
2428 getContext(),
2429 cast<MemRefType>(op.getSource().getType()).getMemorySpaceAsInt());
2430 auto castedPtr =
2431 LLVM::BitcastOp::create(rewriter, op->getLoc(), vectorPtrType, ptr);
2432 auto loadValue =
2433 LLVM::LoadOp::create(rewriter, op->getLoc(), loadType, castedPtr, 1);
2434
2435 // Get set up for the intrinsic
2436 std::string intrinsicName = getIntrinsicName(op, loadSize);
2437
2438 // If the intrinsic declaration doesn't exist, create it
2439 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
2440 StringAttr::get(context, intrinsicName));
2441
2442 if (!func) {
2443 OpBuilder::InsertionGuard guard(rewriter);
2444 rewriter.setInsertionPointToStart(module.getBody());
2445 func = LLVM::LLVMFuncOp::create(
2446 rewriter, rewriter.getUnknownLoc(), intrinsicName,
2447 LLVM::LLVMFunctionType::get(resultType, {resultType, loadType}));
2448 }
2449
2450 // Determine what the destination is
2451 Value destValue;
2452 if (adaptor.getVector()) {
2453 // This UPD is using an existing destination vector
2454 destValue = adaptor.getVector();
2455 } else {
2456 // If this UPD is not working off of an existing destination vector,
2457 // create an undefined vector as the destination
2458
2459 // TODO: determine if the undef intrinsic is needed or if an LLVM
2460 // undef suffices destValue =
2461 // LLVM::UndefOp::create(rewriter, op->getLoc(), resultType);
2462
2463 std::stringstream ss;
2464 ss << "llvm.aie." << getVectorTypeString(resultType) << ".undef";
2465 std::string intrinsicName = ss.str();
2466
2467 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
2468 StringAttr::get(rewriter.getContext(), intrinsicName));
2469
2470 if (!func) {
2471 OpBuilder::InsertionGuard guard(rewriter);
2472 rewriter.setInsertionPointToStart(module.getBody());
2473 func = LLVM::LLVMFuncOp::create(
2474 rewriter, rewriter.getUnknownLoc(), intrinsicName,
2475 LLVM::LLVMFunctionType::get(resultType, {}));
2476 }
2477 destValue =
2478 LLVM::CallOp::create(rewriter, op->getLoc(), func, ValueRange{})
2479 ->getOpResult(0);
2480 }
2481
2482 // Create our call
2483 rewriter.replaceOpWithNewOp<LLVM::CallOp>(
2484 op, func, ValueRange{destValue, loadValue});
2485 }
2486
2487 return success();
2488 }
2489};
2490
2492 : public mlir::ConvertOpToLLVMPattern<aievec::ConcatOp> {
2493public:
2494 using ConvertOpToLLVMPattern<aievec::ConcatOp>::ConvertOpToLLVMPattern;
2495
2496 LogicalResult
2497 matchAndRewrite(aievec::ConcatOp op, OpAdaptor adaptor,
2498 ConversionPatternRewriter &rewriter) const override {
2499 Location loc = op.getLoc();
2500
2501 SmallVector<Value> sources = adaptor.getSources();
2502 Value src = sources.front();
2503 VectorType srcType = cast<VectorType>(src.getType());
2504 Type srcScalarType = srcType.getElementType();
2505 unsigned srcBitWidth = srcScalarType.getIntOrFloatBitWidth();
2506 int srcLanes = getVectorLaneSize(srcType);
2507 int srcVectorSize = srcBitWidth * srcLanes;
2508
2509 Value result = op.getResult();
2510 VectorType resultType = cast<VectorType>(result.getType());
2511 Type resultScaTy = resultType.getElementType();
2512 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
2513 int resultLanes = getVectorLaneSize(resultType);
2514 int resultVectorSize = resultBitWidth * resultLanes;
2515
2516 if (sources.size() != 2 && sources.size() != 4) {
2517 op.emitWarning() << "aievec.concat with " << sources.size()
2518 << " operands is not supported.\n";
2519 return failure();
2520 }
2521
2522 // create xllvm intrinsic
2523 Value concatOp = nullptr;
2524 if (srcVectorSize == 256 && resultVectorSize == 512) {
2525 concatOp = xllvm::ConcatI512I256IntrOp::create(
2526 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2527 forceCastOperandsToSignature(
2528 rewriter, loc, adaptor.getSources(),
2529 {VectorType::get({8}, rewriter.getI32Type()),
2530 VectorType::get({8}, rewriter.getI32Type())}));
2531 } else if (srcVectorSize == 256 && resultVectorSize == 1024) {
2532 concatOp = xllvm::ConcatI1024I256IntrOp::create(
2533 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
2534 forceCastOperandsToSignature(
2535 rewriter, loc, adaptor.getSources(),
2536 {VectorType::get({8}, rewriter.getI32Type()),
2537 VectorType::get({8}, rewriter.getI32Type()),
2538 VectorType::get({8}, rewriter.getI32Type()),
2539 VectorType::get({8}, rewriter.getI32Type())}));
2540 } else if (srcVectorSize == 512 && resultVectorSize == 1024) {
2541 concatOp = xllvm::ConcatI1024I512IntrOp::create(
2542 rewriter, loc, VectorType::get({32}, rewriter.getI32Type()),
2543 forceCastOperandsToSignature(
2544 rewriter, loc, adaptor.getSources(),
2545 {VectorType::get({16}, rewriter.getI32Type()),
2546 VectorType::get({16}, rewriter.getI32Type())}));
2547 } else {
2548 op.emitWarning() << "aievec.concat with " << srcVectorSize
2549 << "-bit operands, and " << resultVectorSize
2550 << "-bit result is not supported.\n";
2551 return failure();
2552 }
2553
2554 // create bitcast/shape_cast for result
2555 auto resultVal =
2556 forceCastValueToType(rewriter, loc, concatOp, op.getResult().getType());
2557 rewriter.replaceOp(op, resultVal);
2558
2559 return success();
2560 }
2561};
2562
2563class ExtOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::ExtOp> {
2564public:
2565 using ConvertOpToLLVMPattern<aievec::ExtOp>::ConvertOpToLLVMPattern;
2566
2567 LogicalResult
2568 matchAndRewrite(aievec::ExtOp op, OpAdaptor adaptor,
2569 ConversionPatternRewriter &rewriter) const override {
2570 Location loc = op.getLoc();
2571
2572 Value src = adaptor.getSource();
2573 VectorType srcType = cast<VectorType>(src.getType());
2574 Type srcScalarType = srcType.getElementType();
2575 unsigned srcBitWidth = srcScalarType.getIntOrFloatBitWidth();
2576 int srcLanes = getVectorLaneSize(srcType);
2577 int srcVectorSize = srcBitWidth * srcLanes;
2578
2579 Value result = op.getResult();
2580 VectorType resultType = cast<VectorType>(result.getType());
2581 Type resultScaTy = resultType.getElementType();
2582 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
2583 int resultLanes = getVectorLaneSize(resultType);
2584 int resultVectorSize = resultBitWidth * resultLanes;
2585
2586 // create constant for index
2587 auto indexCst =
2588 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2589 rewriter.getI32IntegerAttr(op.getIndex()));
2590
2591 // create xllvm intrinsic
2592 SmallVector<Value> operands({adaptor.getSource(), indexCst});
2593 Value extOp = nullptr;
2594 // Integer types
2595 if (resultVectorSize == 256 && srcVectorSize == 512) {
2596 extOp = xllvm::ExtI256I512IntrOp::create(
2597 rewriter, loc, VectorType::get({8}, rewriter.getI32Type()),
2598 forceCastOperandsToSignature(
2599 rewriter, loc, operands,
2600 {VectorType::get({16}, rewriter.getI32Type()),
2601 rewriter.getI32Type()}));
2602 } else if (resultVectorSize == 512 && srcVectorSize == 1024) {
2603 extOp = xllvm::ExtI512I1024IntrOp::create(
2604 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2605 forceCastOperandsToSignature(
2606 rewriter, loc, operands,
2607 {VectorType::get({32}, rewriter.getI32Type()),
2608 rewriter.getI32Type()}));
2609 } else if (resultVectorSize == 256 && srcVectorSize == 1024) {
2610 extOp = xllvm::ExtI256I1024IntrOp::create(
2611 rewriter, loc, VectorType::get({8}, rewriter.getI32Type()),
2612 forceCastOperandsToSignature(
2613 rewriter, loc, operands,
2614 {VectorType::get({32}, rewriter.getI32Type()),
2615 rewriter.getI32Type()}));
2616 } else if (resultVectorSize == 128 && srcVectorSize == 512) {
2617 auto shiftOp = adaptor.getSource();
2618 if (op.getIndex() > 0) {
2619 auto undefOp = xllvm::UndefV16I32IntrOp::create(
2620 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()));
2621 auto stepCst =
2622 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
2623 rewriter.getI32IntegerAttr(0));
2624 auto shiftCst = LLVM::ConstantOp::create(
2625 rewriter, loc, rewriter.getI32Type(),
2626 rewriter.getI32IntegerAttr(op.getIndex() * 16));
2627 SmallVector<Value> shiftOperands{adaptor.getSource(), undefOp, stepCst,
2628 shiftCst};
2629 // Right shift the source vector in index * 16 bytes (i.e. in index *
2630 // 128 bits). The integer index is expected to be 0 to 3.
2631 shiftOp = xllvm::VectorShiftI512I512IntrOp::create(
2632 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
2633 forceCastOperandsToSignature(
2634 rewriter, loc, shiftOperands,
2635 {VectorType::get({16}, rewriter.getI32Type()),
2636 VectorType::get({16}, rewriter.getI32Type()),
2637 rewriter.getI32Type(), rewriter.getI32Type()}));
2638 }
2639 // The underlying intrinsic takes a source vector and extract the lowest
2640 // 128-bit. i.e. it always extracts the input vector with index = 0.
2641 extOp = xllvm::ExtI128I512IntrOp::create(
2642 rewriter, loc, VectorType::get({4}, rewriter.getI32Type()),
2643 forceCastOperandsToSignature(
2644 rewriter, loc, /*operands=*/{shiftOp},
2645 {VectorType::get({16}, rewriter.getI32Type())}));
2646 } else {
2647 op.emitWarning() << "aievec.ext with " << srcVectorSize
2648 << "-bit source, and " << resultVectorSize
2649 << "-bit result is not supported.\n";
2650 return failure();
2651 }
2652
2653 // create bitcast/shape_cast for result
2654 auto resultVal =
2655 forceCastValueToType(rewriter, loc, extOp, op.getResult().getType());
2656 rewriter.replaceOp(op, resultVal);
2657
2658 return success();
2659 }
2660};
2661
2662// AIE2p version of ExtOp conversion using vector.shuffle
2664 : public mlir::ConvertOpToLLVMPattern<aievec::ExtOp> {
2665public:
2666 using ConvertOpToLLVMPattern<aievec::ExtOp>::ConvertOpToLLVMPattern;
2667
2668 LogicalResult
2669 matchAndRewrite(aievec::ExtOp op, OpAdaptor adaptor,
2670 ConversionPatternRewriter &rewriter) const override {
2671 Location loc = op.getLoc();
2672
2673 Value src = adaptor.getSource();
2674 VectorType srcType = cast<VectorType>(src.getType());
2675 VectorType resultType = cast<VectorType>(op.getResult().getType());
2676
2677 int srcLanes = getVectorLaneSize(srcType);
2678 int resultLanes = getVectorLaneSize(resultType);
2679
2680 // The result is one of the equal parts the source divides into.
2681 if (srcLanes % resultLanes != 0) {
2682 op.emitWarning() << "aievec.ext of a part that does not divide the "
2683 "source is not supported for AIE2p.\n";
2684 return failure();
2685 }
2686
2687 // Part `index`: [index * resultLanes, (index + 1) * resultLanes)
2688 SmallVector<int64_t> shuffleMask;
2689 int startIdx = op.getIndex() * resultLanes;
2690 for (int i = 0; i < resultLanes; ++i) {
2691 shuffleMask.push_back(startIdx + i);
2692 }
2693
2694 auto extracted =
2695 vector::ShuffleOp::create(rewriter, loc, src, src, shuffleMask);
2696
2697 rewriter.replaceOp(op, extracted);
2698 return success();
2699 }
2700};
2701
2703 : public mlir::ConvertOpToLLVMPattern<aievec::aie1::SelectOp> {
2704public:
2705 using ConvertOpToLLVMPattern<aievec::aie1::SelectOp>::ConvertOpToLLVMPattern;
2706
2707 static std::string getIntrinsicName(aievec::aie1::SelectOp op) {
2708 auto xbuffType = cast<VectorType>(op.getXbuff().getType());
2709 std::stringstream ss;
2710 ss << "llvm.aie.prim." << getVectorTypeString(xbuffType) << ".select";
2711 return ss.str();
2712 }
2713
2714 LogicalResult
2715 matchAndRewrite(aievec::aie1::SelectOp op, OpAdaptor adaptor,
2716 ConversionPatternRewriter &rewriter) const override {
2717 auto module = op->getParentOfType<ModuleOp>();
2718 MLIRContext *context = rewriter.getContext();
2719
2720 auto selectType = IntegerType::get(context, 32);
2721 auto startType = IntegerType::get(context, 32);
2722 auto offsetsType = VectorType::get({2}, IntegerType::get(context, 32));
2723 auto confType = VectorType::get({2}, IntegerType::get(context, 32));
2724
2725 // If the intrinsic declaration doesn't exist, create it
2726 std::string intrinsicName = getIntrinsicName(op);
2727 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
2728 StringAttr::get(context, intrinsicName));
2729
2730 if (!func) {
2731 OpBuilder::InsertionGuard guard(rewriter);
2732 rewriter.setInsertionPointToStart(module.getBody());
2733 func = LLVM::LLVMFuncOp::create(
2734 rewriter, rewriter.getUnknownLoc(), intrinsicName,
2735 LLVM::LLVMFunctionType::get(op.getResult().getType(),
2736 {op.getXbuff().getType(), selectType,
2737 startType, /* xstart */
2738 startType, /* ystart */
2739 offsetsType, /* xoffsets */
2740 offsetsType, /* yoffsets */
2741 confType}));
2742 }
2743
2744 // Parse the string attribute values
2745 uint32_t select = 0;
2746 BufferParams x = {};
2747 BufferParams y = {};
2748 BufferParams z = {};
2749
2750 op.getSelect().getAsInteger(0, select);
2751 op.getXstart().getAsInteger(0, x.start);
2752 op.getXoffsets().getAsInteger(0, x.offsets);
2753 op.getXoffsetsHi().getAsInteger(0, x.offsets_hi);
2754 op.getXsquare().getAsInteger(0, x.square);
2755 op.getYstart().getAsInteger(0, y.start);
2756 op.getYoffsets().getAsInteger(0, y.offsets);
2757 op.getYoffsetsHi().getAsInteger(0, y.offsets_hi);
2758 op.getYsquare().getAsInteger(0, y.square);
2759
2760 // Encode the configuration register
2761 uint32_t conf[2] = {0, 0};
2762 encodeConf(conf, x, z, false);
2763 conf[1] |= encodeSquare(y.square) << 21;
2764
2765 // Create the constants and replace the op
2766 auto selectVal = LLVM::ConstantOp::create(
2767 rewriter, op->getLoc(), selectType, rewriter.getI32IntegerAttr(select));
2768 auto xstartVal = LLVM::ConstantOp::create(
2769 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(x.start));
2770 auto ystartVal = LLVM::ConstantOp::create(
2771 rewriter, op->getLoc(), startType, rewriter.getI32IntegerAttr(y.start));
2772 auto xoffsetsVal = LLVM::ConstantOp::create(
2773 rewriter, op->getLoc(), offsetsType,
2774 rewriter.getI32VectorAttr({(int32_t)x.offsets, (int32_t)x.offsets_hi}));
2775 auto yoffsetsVal = LLVM::ConstantOp::create(
2776 rewriter, op->getLoc(), offsetsType,
2777 rewriter.getI32VectorAttr({(int32_t)y.offsets, (int32_t)y.offsets_hi}));
2778 auto confVal = LLVM::ConstantOp::create(
2779 rewriter, op->getLoc(), confType,
2780 rewriter.getI32VectorAttr({(int32_t)conf[0], (int32_t)conf[1]}));
2781 rewriter.replaceOpWithNewOp<LLVM::CallOp>(
2782 op, func,
2783 ValueRange{op.getXbuff(), selectVal, xstartVal, ystartVal, xoffsetsVal,
2784 yoffsetsVal, confVal});
2785 return success();
2786 }
2787};
2788
2789class PackOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::PackOp> {
2790public:
2791 using ConvertOpToLLVMPattern<aievec::PackOp>::ConvertOpToLLVMPattern;
2792
2793 static std::string getIntrinsicName(aievec::PackOp op) {
2794 auto sourceType = cast<VectorType>(op.getSource().getType());
2795 std::stringstream ss;
2796 ss << "llvm.aie.pack." << getVectorTypeString(sourceType);
2797 return ss.str();
2798 }
2799
2800 LogicalResult
2801 matchAndRewrite(aievec::PackOp op, OpAdaptor adaptor,
2802 ConversionPatternRewriter &rewriter) const override {
2803 auto module = op->getParentOfType<ModuleOp>();
2804 MLIRContext *context = rewriter.getContext();
2805
2806 // If the intrinsic declaration doesn't exist, create it
2807 std::string intrinsicName = getIntrinsicName(op);
2808 auto func = module.lookupSymbol<LLVM::LLVMFuncOp>(
2809 StringAttr::get(context, intrinsicName));
2810
2811 if (!func) {
2812 OpBuilder::InsertionGuard guard(rewriter);
2813 rewriter.setInsertionPointToStart(module.getBody());
2814 func = LLVM::LLVMFuncOp::create(
2815 rewriter, rewriter.getUnknownLoc(), intrinsicName,
2816 LLVM::LLVMFunctionType::get(op.getResult().getType(),
2817 {op.getSource().getType()}));
2818 }
2819
2820 rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, func,
2821 ValueRange{op.getSource()});
2822 return success();
2823 }
2824};
2825
2827 : public mlir::ConvertOpToLLVMPattern<aievec::UnpackOp> {
2828public:
2829 using ConvertOpToLLVMPattern<aievec::UnpackOp>::ConvertOpToLLVMPattern;
2830
2831 LogicalResult
2832 matchAndRewrite(aievec::UnpackOp op, OpAdaptor adaptor,
2833 ConversionPatternRewriter &rewriter) const override {
2834 op.emitWarning() << "aie.unpack conversion is not implemented\n";
2835 return failure();
2836 }
2837};
2838
2839// AIE2P unpack int4-packed: source is vector<N/2 x i8> carrying N packed
2840// nibbles, result is vector<N x i8> (one byte per nibble). Maps directly
2841// to llvm.aie2p.unpack.I512.I8.I4 (N=64) or .I1024.I8.I4 (N=128).
2842// AWQ packed weights are unsigned; the signed offset is folded into the
2843// per-group zero point so the intrinsic is invoked with sign=0.
2845 : public mlir::ConvertOpToLLVMPattern<aievec::UnpackOp> {
2846public:
2847 using ConvertOpToLLVMPattern<aievec::UnpackOp>::ConvertOpToLLVMPattern;
2848
2849 LogicalResult
2850 matchAndRewrite(aievec::UnpackOp op, OpAdaptor adaptor,
2851 ConversionPatternRewriter &rewriter) const override {
2852 Location loc = op.getLoc();
2853 auto srcTy = cast<VectorType>(op.getSource().getType());
2854 auto dstTy = cast<VectorType>(op.getResult().getType());
2855 if (!srcTy.getElementType().isInteger(8) ||
2856 !dstTy.getElementType().isInteger(8))
2857 return failure();
2858
2859 unsigned srcLanes = getVectorLaneSize(srcTy);
2860 unsigned dstLanes = getVectorLaneSize(dstTy);
2861 if (dstLanes != 2 * srcLanes)
2862 return failure();
2863 if (dstLanes != 64 && dstLanes != 128)
2864 return failure();
2865
2866 auto signCst = LLVM::ConstantOp::create(
2867 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
2868
2869 Value unpacked;
2870 if (dstLanes == 64) {
2871 unpacked = xllvm::UnpackI512I8I4AIE2pIntrOp::create(
2872 rewriter, loc, VectorType::get({64}, rewriter.getI8Type()),
2873 adaptor.getSource(), signCst)
2874 ->getResult(0);
2875 } else {
2876 unpacked =
2877 xllvm::UnpackI1024I8I4AIE2pIntrOp::create(
2878 rewriter, loc, VectorType::get({128}, rewriter.getI8Type()),
2879 adaptor.getSource(), signCst)
2880 ->getResult(0);
2881 }
2882 rewriter.replaceOp(op, unpacked);
2883 return success();
2884 }
2885};
2886
2888 : public mlir::ConvertOpToLLVMPattern<aievec::BroadcastOp> {
2889public:
2890 using ConvertOpToLLVMPattern<aievec::BroadcastOp>::ConvertOpToLLVMPattern;
2891
2892 LogicalResult
2893 matchAndRewrite(aievec::BroadcastOp op, OpAdaptor adaptor,
2894 ConversionPatternRewriter &rewriter) const override {
2895 op.emitWarning() << "aie.broadcast conversion is not implemented\n";
2896 return failure();
2897 }
2898};
2899
2900// Helper to pad a vector from N lanes to dstLanes using vector.shuffle
2901// with poison (-1) for the upper portion.
2902static Value padVectorWithPoison(ConversionPatternRewriter &rewriter,
2903 Location loc, Value vec, int srcLanes,
2904 int dstLanes) {
2905 SmallVector<int64_t> padMask;
2906 for (int i = 0; i < srcLanes; ++i)
2907 padMask.push_back(i);
2908 for (int i = srcLanes; i < dstLanes; ++i)
2909 padMask.push_back(-1);
2910 return vector::ShuffleOp::create(rewriter, loc, vec, vec, padMask);
2911}
2912
2913// Helper to extract the first N lanes from a wider vector using
2914// vector.shuffle.
2915static Value extractLowerLanes(ConversionPatternRewriter &rewriter,
2916 Location loc, Value vec, int lanes) {
2917 SmallVector<int64_t> extractMask;
2918 for (int i = 0; i < lanes; ++i)
2919 extractMask.push_back(i);
2920 return vector::ShuffleOp::create(rewriter, loc, vec, vec, extractMask);
2921}
2922
2923class MaxOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::MaxOp> {
2924public:
2925 using ConvertOpToLLVMPattern<aievec::MaxOp>::ConvertOpToLLVMPattern;
2926
2927 LogicalResult
2928 matchAndRewrite(aievec::MaxOp op, OpAdaptor adaptor,
2929 ConversionPatternRewriter &rewriter) const override {
2930 Location loc = op.getLoc();
2931
2932 VectorType resultType = cast<VectorType>(op.getResult().getType());
2933 Type resultScaTy = resultType.getElementType();
2934 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
2935 int resultLanes = getVectorLaneSize(resultType);
2936 int resultVectorSize = resultBitWidth * resultLanes;
2937
2938 // aievec.max op has the AllTypesMatch constraint on lhs/rhs/res
2939 if (resultVectorSize != 512 && resultVectorSize != 256) {
2940 op.emitWarning() << "aievec.max conversion with " << resultVectorSize
2941 << "-bit result is not supported.\n";
2942 return failure();
2943 }
2944
2945 // create xllvm intrinsic
2946 Value maxOp = nullptr;
2947 if (llvm::isa<IntegerType>(resultScaTy)) {
2948 // create constant for third operand `cmp`
2949 // Note: `cmp` is implicitly treated as `sign` to the vmax intrinsic
2950 auto cmpCst = LLVM::ConstantOp::create(
2951 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
2952 SmallVector<Value> operands{adaptor.getLhs(), adaptor.getRhs(), cmpCst};
2953 if (resultBitWidth == 8) {
2954 maxOp = xllvm::VectorMaxLt8IntrOp::create(
2955 rewriter, loc,
2956 mlir::LLVM::LLVMStructType::getLiteral(
2957 rewriter.getContext(),
2958 {VectorType::get({64}, rewriter.getI8Type()),
2959 VectorType::get({2}, rewriter.getI32Type())}),
2960 forceCastOperandsToSignature(
2961 rewriter, loc, operands,
2962 {VectorType::get({64}, rewriter.getI8Type()),
2963 VectorType::get({64}, rewriter.getI8Type()),
2964 rewriter.getI32Type()}));
2965 } else if (resultBitWidth == 16) {
2966 maxOp = xllvm::VectorMaxLt16IntrOp::create(
2967 rewriter, loc,
2968 mlir::LLVM::LLVMStructType::getLiteral(
2969 rewriter.getContext(),
2970 {VectorType::get({32}, rewriter.getI16Type()),
2971 rewriter.getI32Type()}),
2972 forceCastOperandsToSignature(
2973 rewriter, loc, operands,
2974 {VectorType::get({32}, rewriter.getI16Type()),
2975 VectorType::get({32}, rewriter.getI16Type()),
2976 rewriter.getI32Type()}));
2977 } else if (resultBitWidth == 32) {
2978 maxOp = xllvm::VectorMaxLt32IntrOp::create(
2979 rewriter, loc,
2980 mlir::LLVM::LLVMStructType::getLiteral(
2981 rewriter.getContext(),
2982 {VectorType::get({16}, rewriter.getI32Type()),
2983 rewriter.getI32Type()}),
2984 forceCastOperandsToSignature(
2985 rewriter, loc, operands,
2986 {VectorType::get({16}, rewriter.getI32Type()),
2987 VectorType::get({16}, rewriter.getI32Type()),
2988 rewriter.getI32Type()}));
2989 }
2990 } else {
2991 if (resultBitWidth == 16) {
2992 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
2993 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
2994
2995 // Pad 16-lane to 32-lane if needed
2996 if (resultLanes == 16) {
2997 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
2998 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
2999 }
3000
3001 maxOp = xllvm::VectorMaxLtBf16IntrOp::create(
3002 rewriter, loc,
3003 mlir::LLVM::LLVMStructType::getLiteral(
3004 rewriter.getContext(), {v32bf16Ty, rewriter.getI32Type()}),
3005 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs},
3006 {v32bf16Ty, v32bf16Ty}));
3007 }
3008 }
3009
3010 if (!maxOp) {
3011 op.emitWarning() << "aievec.max conversion fails due to unsupported "
3012 "element data type.\n";
3013 return failure();
3014 }
3015
3016 // Extract the vector result from the struct
3017 Value resultVec = LLVM::ExtractValueOp::create(rewriter, loc, maxOp,
3018 /*position=*/0);
3019 // Truncate back to 16 lanes if padded
3020 if (resultLanes == 16 && !llvm::isa<IntegerType>(resultScaTy))
3021 resultVec = extractLowerLanes(rewriter, loc, resultVec, 16);
3022
3023 rewriter.replaceOp(op, resultVec);
3024
3025 return success();
3026 }
3027};
3028
3029class MinOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::MinOp> {
3030public:
3031 using ConvertOpToLLVMPattern<aievec::MinOp>::ConvertOpToLLVMPattern;
3032
3033 LogicalResult
3034 matchAndRewrite(aievec::MinOp op, OpAdaptor adaptor,
3035 ConversionPatternRewriter &rewriter) const override {
3036 Location loc = op.getLoc();
3037
3038 VectorType resultType = cast<VectorType>(op.getResult().getType());
3039 Type resultScaTy = resultType.getElementType();
3040 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3041 int resultLanes = getVectorLaneSize(resultType);
3042 int resultVectorSize = resultBitWidth * resultLanes;
3043
3044 // aievec.min op has the AllTypesMatch constraint on lhs/rhs/res
3045 if (resultVectorSize != 512 && resultVectorSize != 256) {
3046 op.emitWarning() << "aievec.min conversion with " << resultVectorSize
3047 << "-bit result is not supported.\n";
3048 return failure();
3049 }
3050
3051 // create xllvm intrinsic
3052 Value minOp = nullptr;
3053 if (llvm::isa<IntegerType>(resultScaTy)) {
3054 // create constant for third operand `cmp`
3055 // Note: `cmp` is implicitly treated as `sign` to the vmin intrinsic
3056 auto cmpCst = LLVM::ConstantOp::create(
3057 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
3058 SmallVector<Value> operands{adaptor.getLhs(), adaptor.getRhs(), cmpCst};
3059 if (resultBitWidth == 8) {
3060 minOp = xllvm::VectorMinGe8IntrOp::create(
3061 rewriter, loc,
3062 mlir::LLVM::LLVMStructType::getLiteral(
3063 rewriter.getContext(),
3064 {VectorType::get({64}, rewriter.getI8Type()),
3065 VectorType::get({2}, rewriter.getI32Type())}),
3066 forceCastOperandsToSignature(
3067 rewriter, loc, operands,
3068 {VectorType::get({64}, rewriter.getI8Type()),
3069 VectorType::get({64}, rewriter.getI8Type()),
3070 rewriter.getI32Type()}));
3071 } else if (resultBitWidth == 16) {
3072 minOp = xllvm::VectorMinGe16IntrOp::create(
3073 rewriter, loc,
3074 mlir::LLVM::LLVMStructType::getLiteral(
3075 rewriter.getContext(),
3076 {VectorType::get({32}, rewriter.getI16Type()),
3077 rewriter.getI32Type()}),
3078 forceCastOperandsToSignature(
3079 rewriter, loc, operands,
3080 {VectorType::get({32}, rewriter.getI16Type()),
3081 VectorType::get({32}, rewriter.getI16Type()),
3082 rewriter.getI32Type()}));
3083 } else if (resultBitWidth == 32) {
3084 minOp = xllvm::VectorMinGe32IntrOp::create(
3085 rewriter, loc,
3086 mlir::LLVM::LLVMStructType::getLiteral(
3087 rewriter.getContext(),
3088 {VectorType::get({16}, rewriter.getI32Type()),
3089 rewriter.getI32Type()}),
3090 forceCastOperandsToSignature(
3091 rewriter, loc, operands,
3092 {VectorType::get({16}, rewriter.getI32Type()),
3093 VectorType::get({16}, rewriter.getI32Type()),
3094 rewriter.getI32Type()}));
3095 }
3096 } else {
3097 if (resultBitWidth == 16) {
3098 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3099 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
3100
3101 // Pad 16-lane to 32-lane if needed
3102 if (resultLanes == 16) {
3103 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
3104 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
3105 }
3106
3107 minOp = xllvm::VectorMinGeBf16IntrOp::create(
3108 rewriter, loc,
3109 mlir::LLVM::LLVMStructType::getLiteral(
3110 rewriter.getContext(), {v32bf16Ty, rewriter.getI32Type()}),
3111 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs},
3112 {v32bf16Ty, v32bf16Ty}));
3113 }
3114 }
3115
3116 if (!minOp) {
3117 op.emitWarning() << "aievec.min conversion fails due to unsupported "
3118 "element data type.\n";
3119 return failure();
3120 }
3121
3122 // Extract the vector result from the struct
3123 Value resultVec = LLVM::ExtractValueOp::create(rewriter, loc, minOp,
3124 /*position=*/0);
3125 // Truncate back to 16 lanes if padded
3126 if (resultLanes == 16 && !llvm::isa<IntegerType>(resultScaTy))
3127 resultVec = extractLowerLanes(rewriter, loc, resultVec, 16);
3128
3129 rewriter.replaceOp(op, resultVec);
3130
3131 return success();
3132 }
3133};
3134
3135// AIE2p version of MaxOp conversion
3137 : public mlir::ConvertOpToLLVMPattern<aievec::MaxOp> {
3138public:
3139 using ConvertOpToLLVMPattern<aievec::MaxOp>::ConvertOpToLLVMPattern;
3140
3141 LogicalResult
3142 matchAndRewrite(aievec::MaxOp op, OpAdaptor adaptor,
3143 ConversionPatternRewriter &rewriter) const override {
3144 Location loc = op.getLoc();
3145
3146 VectorType resultType = cast<VectorType>(op.getResult().getType());
3147 Type resultScaTy = resultType.getElementType();
3148 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3149 int resultLanes = getVectorLaneSize(resultType);
3150 int resultVectorSize = resultBitWidth * resultLanes;
3151
3152 // aievec.max op has the AllTypesMatch constraint on lhs/rhs/res
3153 if (resultVectorSize != 512 && resultVectorSize != 256) {
3154 op.emitWarning() << "aievec.max conversion with " << resultVectorSize
3155 << "-bit result is not supported.\n";
3156 return failure();
3157 }
3158
3159 // create xllvm intrinsic
3160 Value maxOp = nullptr;
3161 if (llvm::isa<IntegerType>(resultScaTy)) {
3162 // create constant for third operand `cmp`
3163 // Note: `cmp` is implicitly treated as `sign` to the vmax intrinsic
3164 auto cmpCst = LLVM::ConstantOp::create(
3165 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
3166 SmallVector<Value> operands{adaptor.getLhs(), adaptor.getRhs(), cmpCst};
3167 if (resultBitWidth == 8) {
3168 maxOp = xllvm::VectorMaxLt8AIE2pIntrOp::create(
3169 rewriter, loc,
3170 mlir::LLVM::LLVMStructType::getLiteral(
3171 rewriter.getContext(),
3172 {VectorType::get({64}, rewriter.getI8Type()),
3173 VectorType::get({2}, rewriter.getI32Type())}),
3174 forceCastOperandsToSignature(
3175 rewriter, loc, operands,
3176 {VectorType::get({64}, rewriter.getI8Type()),
3177 VectorType::get({64}, rewriter.getI8Type()),
3178 rewriter.getI32Type()}));
3179 } else if (resultBitWidth == 16) {
3180 maxOp = xllvm::VectorMaxLt16AIE2pIntrOp::create(
3181 rewriter, loc,
3182 mlir::LLVM::LLVMStructType::getLiteral(
3183 rewriter.getContext(),
3184 {VectorType::get({32}, rewriter.getI16Type()),
3185 rewriter.getI32Type()}),
3186 forceCastOperandsToSignature(
3187 rewriter, loc, operands,
3188 {VectorType::get({32}, rewriter.getI16Type()),
3189 VectorType::get({32}, rewriter.getI16Type()),
3190 rewriter.getI32Type()}));
3191 } else if (resultBitWidth == 32) {
3192 maxOp = xllvm::VectorMaxLt32AIE2pIntrOp::create(
3193 rewriter, loc,
3194 mlir::LLVM::LLVMStructType::getLiteral(
3195 rewriter.getContext(),
3196 {VectorType::get({16}, rewriter.getI32Type()),
3197 rewriter.getI32Type()}),
3198 forceCastOperandsToSignature(
3199 rewriter, loc, operands,
3200 {VectorType::get({16}, rewriter.getI32Type()),
3201 VectorType::get({16}, rewriter.getI32Type()),
3202 rewriter.getI32Type()}));
3203 }
3204 } else {
3205 if (resultBitWidth == 16) {
3206 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3207 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
3208
3209 if (resultLanes == 16) {
3210 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
3211 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
3212 }
3213
3214 maxOp = xllvm::VectorMaxLtBf16AIE2pIntrOp::create(
3215 rewriter, loc,
3216 mlir::LLVM::LLVMStructType::getLiteral(
3217 rewriter.getContext(), {v32bf16Ty, rewriter.getI32Type()}),
3218 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs},
3219 {v32bf16Ty, v32bf16Ty}));
3220 }
3221 }
3222
3223 if (!maxOp) {
3224 op.emitWarning() << "aievec.max conversion fails due to unsupported "
3225 "element data type.\n";
3226 return failure();
3227 }
3228
3229 Value resultVec = LLVM::ExtractValueOp::create(rewriter, loc, maxOp,
3230 /*position=*/0);
3231 if (resultLanes == 16 && !llvm::isa<IntegerType>(resultScaTy))
3232 resultVec = extractLowerLanes(rewriter, loc, resultVec, 16);
3233
3234 rewriter.replaceOp(op, resultVec);
3235
3236 return success();
3237 }
3238};
3239
3240// AIE2p version of MinOp conversion
3242 : public mlir::ConvertOpToLLVMPattern<aievec::MinOp> {
3243public:
3244 using ConvertOpToLLVMPattern<aievec::MinOp>::ConvertOpToLLVMPattern;
3245
3246 LogicalResult
3247 matchAndRewrite(aievec::MinOp op, OpAdaptor adaptor,
3248 ConversionPatternRewriter &rewriter) const override {
3249 Location loc = op.getLoc();
3250
3251 VectorType resultType = cast<VectorType>(op.getResult().getType());
3252 Type resultScaTy = resultType.getElementType();
3253 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3254 int resultLanes = getVectorLaneSize(resultType);
3255 int resultVectorSize = resultBitWidth * resultLanes;
3256
3257 // aievec.min op has the AllTypesMatch constraint on lhs/rhs/res
3258 if (resultVectorSize != 512 && resultVectorSize != 256) {
3259 op.emitWarning() << "aievec.min conversion with " << resultVectorSize
3260 << "-bit result is not supported.\n";
3261 return failure();
3262 }
3263
3264 // create xllvm intrinsic
3265 Value minOp = nullptr;
3266 if (llvm::isa<IntegerType>(resultScaTy)) {
3267 // create constant for third operand `cmp`
3268 // Note: `cmp` is implicitly treated as `sign` to the vmin intrinsic
3269 auto cmpCst = LLVM::ConstantOp::create(
3270 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
3271 SmallVector<Value> operands{adaptor.getLhs(), adaptor.getRhs(), cmpCst};
3272 if (resultBitWidth == 8) {
3273 minOp = xllvm::VectorMinGe8AIE2pIntrOp::create(
3274 rewriter, loc,
3275 mlir::LLVM::LLVMStructType::getLiteral(
3276 rewriter.getContext(),
3277 {VectorType::get({64}, rewriter.getI8Type()),
3278 VectorType::get({2}, rewriter.getI32Type())}),
3279 forceCastOperandsToSignature(
3280 rewriter, loc, operands,
3281 {VectorType::get({64}, rewriter.getI8Type()),
3282 VectorType::get({64}, rewriter.getI8Type()),
3283 rewriter.getI32Type()}));
3284 } else if (resultBitWidth == 16) {
3285 minOp = xllvm::VectorMinGe16AIE2pIntrOp::create(
3286 rewriter, loc,
3287 mlir::LLVM::LLVMStructType::getLiteral(
3288 rewriter.getContext(),
3289 {VectorType::get({32}, rewriter.getI16Type()),
3290 rewriter.getI32Type()}),
3291 forceCastOperandsToSignature(
3292 rewriter, loc, operands,
3293 {VectorType::get({32}, rewriter.getI16Type()),
3294 VectorType::get({32}, rewriter.getI16Type()),
3295 rewriter.getI32Type()}));
3296 } else if (resultBitWidth == 32) {
3297 minOp = xllvm::VectorMinGe32AIE2pIntrOp::create(
3298 rewriter, loc,
3299 mlir::LLVM::LLVMStructType::getLiteral(
3300 rewriter.getContext(),
3301 {VectorType::get({16}, rewriter.getI32Type()),
3302 rewriter.getI32Type()}),
3303 forceCastOperandsToSignature(
3304 rewriter, loc, operands,
3305 {VectorType::get({16}, rewriter.getI32Type()),
3306 VectorType::get({16}, rewriter.getI32Type()),
3307 rewriter.getI32Type()}));
3308 }
3309 } else {
3310 if (resultBitWidth == 16) {
3311 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3312 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
3313
3314 if (resultLanes == 16) {
3315 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
3316 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
3317 }
3318
3319 minOp = xllvm::VectorMinGeBf16AIE2pIntrOp::create(
3320 rewriter, loc,
3321 mlir::LLVM::LLVMStructType::getLiteral(
3322 rewriter.getContext(), {v32bf16Ty, rewriter.getI32Type()}),
3323 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs},
3324 {v32bf16Ty, v32bf16Ty}));
3325 }
3326 }
3327
3328 if (!minOp) {
3329 op.emitWarning() << "aievec.min conversion fails due to unsupported "
3330 "element data type.\n";
3331 return failure();
3332 }
3333
3334 Value resultVec = LLVM::ExtractValueOp::create(rewriter, loc, minOp,
3335 /*position=*/0);
3336 if (resultLanes == 16 && !llvm::isa<IntegerType>(resultScaTy))
3337 resultVec = extractLowerLanes(rewriter, loc, resultVec, 16);
3338
3339 rewriter.replaceOp(op, resultVec);
3340
3341 return success();
3342 }
3343};
3344
3345// The i32 bitmask of the lanes where `lhs rel rhs` holds, with `rel` one of
3346// lt, ge, gt, le, eq and ne. `LtOp` and `GeOp` are vmax.lt and vmin.ge
3347// intrinsics, which return the lanes where a < b and a >= b in field 1 of their
3348// struct result. gt and le swap the operands; eq and ne combine the compares in
3349// both directions. `extraOperands` follow the two vectors in each call.
3350template <typename LtOp, typename GeOp>
3351static Value buildCmpBitmask(OpBuilder &builder, Location loc, StringRef rel,
3352 Value lhs, Value rhs, ValueRange extraOperands) {
3353 auto structTy = LLVM::LLVMStructType::getLiteral(
3354 builder.getContext(), {lhs.getType(), builder.getI32Type()});
3355 auto compare = [&](bool lessThan, Value a, Value b) -> Value {
3356 SmallVector<Value> operands{a, b};
3357 llvm::append_range(operands, extraOperands);
3358 Value result = lessThan
3359 ? Value(LtOp::create(builder, loc, structTy, operands))
3360 : Value(GeOp::create(builder, loc, structTy, operands));
3361 return LLVM::ExtractValueOp::create(builder, loc, result, 1);
3362 };
3363 if (rel == "lt")
3364 return compare(/*lessThan=*/true, lhs, rhs);
3365 if (rel == "ge")
3366 return compare(/*lessThan=*/false, lhs, rhs);
3367 if (rel == "gt")
3368 return compare(/*lessThan=*/true, rhs, lhs);
3369 if (rel == "le")
3370 return compare(/*lessThan=*/false, rhs, lhs);
3371 assert((rel == "eq" || rel == "ne") && "unexpected compare predicate");
3372 // Separate statements fix the order in which the two compares are emitted.
3373 bool isEq = rel == "eq";
3374 Value forward = compare(/*lessThan=*/!isEq, lhs, rhs);
3375 Value backward = compare(/*lessThan=*/!isEq, rhs, lhs);
3376 if (isEq)
3377 return LLVM::AndOp::create(builder, loc, forward, backward);
3378 return LLVM::OrOp::create(builder, loc, forward, backward);
3379}
3380
3381// ----- CmpOp conversion for AIE2 and AIE2P -----
3382// Implements aievec.cmp using vmax.lt / vmin.ge intrinsics. The integer
3383// intrinsics take a last operand that selects a signed (1) or unsigned (0)
3384// compare, which follows the predicate.
3385template <typename MaxLtBf16IntrOp, typename MinGeBf16IntrOp,
3386 typename MaxLt32IntrOp, typename MinGe32IntrOp,
3387 typename MaxLt16IntrOp, typename MinGe16IntrOp>
3388class CmpOpConversionBase : public mlir::ConvertOpToLLVMPattern<aievec::CmpOp> {
3389public:
3390 using ConvertOpToLLVMPattern<aievec::CmpOp>::ConvertOpToLLVMPattern;
3391
3392 LogicalResult
3393 matchAndRewrite(aievec::CmpOp op, OpAdaptor adaptor,
3394 ConversionPatternRewriter &rewriter) const override {
3395 Location loc = op.getLoc();
3396 auto vecTy = cast<VectorType>(op.getLhs().getType());
3397 auto elTy = vecTy.getElementType();
3398 unsigned elWidth = elTy.getIntOrFloatBitWidth();
3399 unsigned lanes = getVectorLaneSize(vecTy);
3400 StringRef pred = op.getPred();
3401
3402 bool isUnsigned = llvm::is_contained({"ult", "uge", "ugt", "ule"}, pred);
3403 if (!isUnsigned &&
3404 !llvm::is_contained({"slt", "sge", "sgt", "sle", "eq", "ne"}, pred))
3405 return failure();
3406 // The relation without its signedness prefix: slt -> lt, uge -> ge.
3407 StringRef rel = pred.size() == 3 ? pred.drop_front() : pred;
3408
3409 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
3410 Value bitmask;
3411 if (elWidth == 16 && isa<FloatType>(elTy) && (lanes == 16 || lanes == 32)) {
3412 // bf16 vectors (16 or 32 lanes); 16 lanes are padded to 32
3413 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3414 if (lanes == 16) {
3415 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
3416 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
3417 }
3418 SmallVector<Value> operands = forceCastOperandsToSignature(
3419 rewriter, loc, {lhs, rhs}, {v32bf16Ty, v32bf16Ty});
3420 bitmask = buildCmpBitmask<MaxLtBf16IntrOp, MinGeBf16IntrOp>(
3421 rewriter, loc, rel, operands[0], operands[1], {});
3422 // Mask off upper bits for 16-lane inputs
3423 if (lanes == 16) {
3424 auto mask =
3425 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(),
3426 rewriter.getI32IntegerAttr(0xFFFF));
3427 bitmask = LLVM::AndOp::create(rewriter, loc, bitmask, mask);
3428 }
3429 } else if (isa<IntegerType>(elTy) && elWidth * lanes == 512 &&
3430 (elWidth == 32 || elWidth == 16)) {
3431 // v16i32 and v32i16 vectors. eq and ne do not depend on the signedness
3432 // and use the signed form.
3433 auto intVecTy =
3434 VectorType::get({lanes}, rewriter.getIntegerType(elWidth));
3435 Value sign = LLVM::ConstantOp::create(
3436 rewriter, loc, rewriter.getI32Type(),
3437 rewriter.getI32IntegerAttr(isUnsigned ? 0 : 1));
3438 SmallVector<Value> operands = forceCastOperandsToSignature(
3439 rewriter, loc, {lhs, rhs}, {intVecTy, intVecTy});
3440 bitmask = elWidth == 32
3441 ? buildCmpBitmask<MaxLt32IntrOp, MinGe32IntrOp>(
3442 rewriter, loc, rel, operands[0], operands[1], sign)
3443 : buildCmpBitmask<MaxLt16IntrOp, MinGe16IntrOp>(
3444 rewriter, loc, rel, operands[0], operands[1], sign);
3445 } else {
3446 return failure();
3447 }
3448
3449 // Cast i32 bitmask to the op's unsigned integer result type
3450 rewriter.replaceOpWithNewOp<UnrealizedConversionCastOp>(
3451 op, op.getResult().getType(), bitmask);
3452 return success();
3453 }
3454};
3455
3457 CmpOpConversionBase<xllvm::VectorMaxLtBf16IntrOp,
3458 xllvm::VectorMinGeBf16IntrOp,
3459 xllvm::VectorMaxLt32IntrOp, xllvm::VectorMinGe32IntrOp,
3460 xllvm::VectorMaxLt16IntrOp, xllvm::VectorMinGe16IntrOp>;
3461
3463 xllvm::VectorMaxLtBf16AIE2pIntrOp, xllvm::VectorMinGeBf16AIE2pIntrOp,
3464 xllvm::VectorMaxLt32AIE2pIntrOp, xllvm::VectorMinGe32AIE2pIntrOp,
3465 xllvm::VectorMaxLt16AIE2pIntrOp, xllvm::VectorMinGe16AIE2pIntrOp>;
3466
3467// ----- SelOp conversion for AIE2/AIE2p -----
3468// Implements aievec.sel using vsel16/vsel32 intrinsics.
3469// For bf16 vectors, bitcasts to i16, calls vsel16, bitcasts back.
3470template <typename Sel16IntrOp, typename Sel32IntrOp>
3471class SelOpConversionBase : public mlir::ConvertOpToLLVMPattern<aievec::SelOp> {
3472public:
3473 using ConvertOpToLLVMPattern<aievec::SelOp>::ConvertOpToLLVMPattern;
3474
3475 LogicalResult
3476 matchAndRewrite(aievec::SelOp op, OpAdaptor adaptor,
3477 ConversionPatternRewriter &rewriter) const override {
3478 Location loc = op.getLoc();
3479 auto resultType = cast<VectorType>(op.getResult().getType());
3480 auto elTy = resultType.getElementType();
3481 unsigned elWidth = elTy.getIntOrFloatBitWidth();
3482 unsigned lanes = getVectorLaneSize(resultType);
3483 auto i32Ty = rewriter.getI32Type();
3484
3485 // Cast the sel bitmask from unsigned integer to i32
3486 Value selMask = adaptor.getSel();
3487 if (selMask.getType() != i32Ty)
3488 selMask =
3489 UnrealizedConversionCastOp::create(rewriter, loc, i32Ty, selMask)
3490 .getResult(0);
3491
3492 // Handle bf16 vectors (16 or 32 lanes) via bitcast to i16
3493 if (elWidth == 16 && isa<FloatType>(elTy)) {
3494 auto v32i16Ty = VectorType::get({32}, rewriter.getI16Type());
3495 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3496
3497 Value lhs = adaptor.getLhs(), rhs = adaptor.getRhs();
3498 bool needExtract = false;
3499
3500 if (lanes == 16) {
3501 lhs = padVectorWithPoison(rewriter, loc, lhs, 16, 32);
3502 rhs = padVectorWithPoison(rewriter, loc, rhs, 16, 32);
3503 needExtract = true;
3504 } else if (lanes != 32) {
3505 return failure();
3506 }
3507
3508 // Bitcast bf16 -> i16
3509 auto lhsCast = forceCastValueToType(rewriter, loc, lhs, v32bf16Ty);
3510 auto lhsI16 = LLVM::BitcastOp::create(rewriter, loc, v32i16Ty, lhsCast);
3511 auto rhsCast = forceCastValueToType(rewriter, loc, rhs, v32bf16Ty);
3512 auto rhsI16 = LLVM::BitcastOp::create(rewriter, loc, v32i16Ty, rhsCast);
3513
3514 auto selResult = Sel16IntrOp::create(
3515 rewriter, loc, v32i16Ty,
3516 forceCastOperandsToSignature(rewriter, loc, {lhsI16, rhsI16, selMask},
3517 {v32i16Ty, v32i16Ty, i32Ty}));
3518
3519 // Bitcast i16 -> bf16
3520 Value result =
3521 LLVM::BitcastOp::create(rewriter, loc, v32bf16Ty, selResult);
3522
3523 if (needExtract)
3524 result = extractLowerLanes(rewriter, loc, result, 16);
3525
3526 rewriter.replaceOp(op, result);
3527 return success();
3528 }
3529
3530 // Handle i32 vectors (16 lanes)
3531 if (elWidth == 32 && isa<IntegerType>(elTy) && lanes == 16) {
3532 auto v16i32Ty = VectorType::get({16}, rewriter.getI32Type());
3533 auto selResult = Sel32IntrOp::create(
3534 rewriter, loc, v16i32Ty,
3535 forceCastOperandsToSignature(
3536 rewriter, loc, {adaptor.getLhs(), adaptor.getRhs(), selMask},
3537 {v16i32Ty, v16i32Ty, i32Ty}));
3538 rewriter.replaceOp(op, selResult->getResult(0));
3539 return success();
3540 }
3541
3542 // Handle i16 vectors (32 lanes)
3543 if (elWidth == 16 && isa<IntegerType>(elTy) && lanes == 32) {
3544 auto v32i16Ty = VectorType::get({32}, rewriter.getI16Type());
3545 auto selResult = Sel16IntrOp::create(
3546 rewriter, loc, v32i16Ty,
3547 forceCastOperandsToSignature(
3548 rewriter, loc, {adaptor.getLhs(), adaptor.getRhs(), selMask},
3549 {v32i16Ty, v32i16Ty, i32Ty}));
3550 rewriter.replaceOp(op, selResult->getResult(0));
3551 return success();
3552 }
3553
3554 return failure();
3555 }
3556};
3557
3560
3561using SelOpAIE2pConversion = SelOpConversionBase<xllvm::VectorSel16AIE2pIntrOp,
3562 xllvm::VectorSel32AIE2pIntrOp>;
3563
3565 : public mlir::ConvertOpToLLVMPattern<aievec::BroadcastScalarOp> {
3566public:
3567 using ConvertOpToLLVMPattern<
3568 aievec::BroadcastScalarOp>::ConvertOpToLLVMPattern;
3569
3570 LogicalResult
3571 matchAndRewrite(aievec::BroadcastScalarOp op, OpAdaptor adaptor,
3572 ConversionPatternRewriter &rewriter) const override {
3573 Location loc = op.getLoc();
3574
3575 Value result = op.getResult();
3576 VectorType resultType = cast<VectorType>(result.getType());
3577 Type resultScaTy = resultType.getElementType();
3578 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3579 int resultLanes = getVectorLaneSize(resultType);
3580 int resultVectorSize = resultBitWidth * resultLanes;
3581
3582 if (resultVectorSize != 512) {
3583 op.emitWarning()
3584 << "aievec.broadcast_scalar conversion with result vector size "
3585 << resultVectorSize << " is not implemented.\n";
3586 return failure();
3587 }
3588
3589 // Integer types
3590 if (llvm::isa<IntegerType>(resultScaTy)) {
3591 Value src = adaptor.getSource();
3592 Type srcType = src.getType();
3593 unsigned srcBitWidth = srcType.getIntOrFloatBitWidth();
3594
3595 if (srcBitWidth < 32) {
3596 src = LLVM::SExtOp::create(rewriter, loc, rewriter.getI32Type(), src);
3597 }
3598
3599 if (resultBitWidth == 8) {
3600 rewriter.replaceOpWithNewOp<xllvm::VectorBroadcast8I512IntrOp>(
3601 op, VectorType::get({64}, rewriter.getI8Type()), src);
3602 } else if (resultBitWidth == 16) {
3603 rewriter.replaceOpWithNewOp<xllvm::VectorBroadcast16I512IntrOp>(
3604 op, VectorType::get({32}, rewriter.getI16Type()), src);
3605 } else if (resultBitWidth == 32) {
3606 rewriter.replaceOpWithNewOp<xllvm::VectorBroadcast32I512IntrOp>(
3607 op, VectorType::get({16}, rewriter.getI32Type()), src);
3608 } else {
3609 op.emitWarning()
3610 << "aievec.broadcast_scalar conversion with result bitwidth "
3611 << resultBitWidth << " is not implemented.\n";
3612 return failure();
3613 }
3614 } else {
3615 // Float types
3616 if (resultBitWidth == 16) {
3617 rewriter.replaceOpWithNewOp<xllvm::VectorBroadcast16BF512IntrOp>(
3618 op, VectorType::get({32}, rewriter.getBF16Type()),
3619 adaptor.getSource());
3620 } else if (resultBitWidth == 32) {
3621 // Use llvm.aie2.vbroadcast32.I512 with bitcasts (float -> i32 -> float)
3622 // Following the pattern: %0 = bitcast float %b to i32
3623 // %1 = tail call <16 x i32>
3624 // @llvm.aie2.vbroadcast32.I512(i32 %0) %2 =
3625 // bitcast <16 x i32> %1 to <16 x float>
3626 auto srcAsI32 = bitcastValueToType(rewriter, loc, adaptor.getSource(),
3627 rewriter.getI32Type());
3628 auto broadcastI32 = xllvm::VectorBroadcast32I512IntrOp::create(
3629 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
3630 srcAsI32);
3631 auto resultF32 =
3632 bitcastValueToType(rewriter, loc, broadcastI32,
3633 VectorType::get({16}, rewriter.getF32Type()));
3634 rewriter.replaceOp(op, resultF32);
3635 } else {
3636 op.emitWarning()
3637 << "aievec.broadcast_scalar conversion with result bitwidth "
3638 << resultBitWidth << " is not implemented.\n";
3639 return failure();
3640 }
3641 }
3642
3643 return success();
3644 }
3645};
3646
3647// AIE2p version of BroadcastScalarOp conversion using insertelement +
3648// shufflevector
3650 : public mlir::ConvertOpToLLVMPattern<aievec::BroadcastScalarOp> {
3651public:
3652 using ConvertOpToLLVMPattern<
3653 aievec::BroadcastScalarOp>::ConvertOpToLLVMPattern;
3654
3655 LogicalResult
3656 matchAndRewrite(aievec::BroadcastScalarOp op, OpAdaptor adaptor,
3657 ConversionPatternRewriter &rewriter) const override {
3658 Location loc = op.getLoc();
3659
3660 Value result = op.getResult();
3661 VectorType resultType = cast<VectorType>(result.getType());
3662 Type resultScaTy = resultType.getElementType();
3663 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3664 int resultLanes = getVectorLaneSize(resultType);
3665 int resultVectorSize = resultBitWidth * resultLanes;
3666
3667 // Support both 256-bit and 512-bit vectors for AIE2p
3668 if (resultVectorSize != 256 && resultVectorSize != 512) {
3669 op.emitWarning()
3670 << "aievec.broadcast_scalar conversion with result vector size "
3671 << resultVectorSize << " is not implemented for AIE2p.\n";
3672 return failure();
3673 }
3674
3675 Value src = adaptor.getSource();
3676 Type srcType = src.getType();
3677
3678 // For integer types, extend or truncate to match result element type
3679 if (llvm::isa<IntegerType>(resultScaTy)) {
3680 unsigned srcBitWidth = srcType.getIntOrFloatBitWidth();
3681 if (srcBitWidth < resultBitWidth) {
3682 src = LLVM::SExtOp::create(rewriter, loc, resultScaTy, src);
3683 } else if (srcBitWidth > resultBitWidth) {
3684 src = LLVM::TruncOp::create(rewriter, loc, resultScaTy, src);
3685 }
3686 }
3687
3688 // Create poison vector of the result type
3689 auto poisonVec = LLVM::PoisonOp::create(rewriter, loc, resultType);
3690
3691 // Insert scalar at position 0
3692 auto idx0 = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(),
3693 rewriter.getI64IntegerAttr(0));
3694 auto insertedVec = LLVM::InsertElementOp::create(rewriter, loc, resultType,
3695 poisonVec, src, idx0);
3696
3697 // Create shufflevector mask with all zeros (broadcast position 0 to all
3698 // lanes)
3699 SmallVector<int64_t> broadcastMask(resultLanes, 0);
3700 auto broadcastVec = vector::ShuffleOp::create(rewriter, loc, insertedVec,
3701 insertedVec, broadcastMask);
3702
3703 rewriter.replaceOp(op, broadcastVec);
3704 return success();
3705 }
3706};
3707
3708class ShiftOpConversion : public mlir::ConvertOpToLLVMPattern<aievec::ShiftOp> {
3709public:
3710 using ConvertOpToLLVMPattern<aievec::ShiftOp>::ConvertOpToLLVMPattern;
3711
3712 LogicalResult
3713 matchAndRewrite(aievec::ShiftOp op, OpAdaptor adaptor,
3714 ConversionPatternRewriter &rewriter) const override {
3715 Location loc = op.getLoc();
3716
3717 Value result = op.getResult();
3718 VectorType resultType = cast<VectorType>(result.getType());
3719 Type resultScaTy = resultType.getElementType();
3720 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3721 int resultLanes = getVectorLaneSize(resultType);
3722 int resultVectorSize = resultBitWidth * resultLanes;
3723
3724 if (resultVectorSize != 512) {
3725 op.emitWarning() << "aievec.shift conversion with result vector size "
3726 << resultVectorSize << " is not implemented.\n";
3727 return failure();
3728 }
3729
3730 // assume step is always zero
3731 auto stepCst = LLVM::ConstantOp::create(
3732 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
3733
3734 // create xllvm intrinsic
3735 Value shiftOp = nullptr;
3736 SmallVector<Value> operands(
3737 {adaptor.getLhs(), adaptor.getRhs(), stepCst, adaptor.getShift()});
3738 if (llvm::isa<IntegerType>(resultScaTy)) {
3739 // Integer types
3740 shiftOp = xllvm::VectorShiftI512I512IntrOp::create(
3741 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
3742 forceCastOperandsToSignature(
3743 rewriter, loc, operands,
3744 {VectorType::get({16}, rewriter.getI32Type()),
3745 VectorType::get({16}, rewriter.getI32Type()),
3746 rewriter.getI32Type(), rewriter.getI32Type()}));
3747 } else {
3748 // Float types
3749 shiftOp = xllvm::VectorShiftBF512BF512IntrOp::create(
3750 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
3751 forceCastOperandsToSignature(
3752 rewriter, loc, operands,
3753 {VectorType::get({32}, rewriter.getBF16Type()),
3754 VectorType::get({32}, rewriter.getBF16Type()),
3755 rewriter.getI32Type(), rewriter.getI32Type()}));
3756 }
3757
3758 // create bitcast/shape_cast for result
3759 auto resultVal =
3760 forceCastValueToType(rewriter, loc, shiftOp, op.getResult().getType());
3761 rewriter.replaceOp(op, resultVal);
3762
3763 return success();
3764 }
3765};
3766
3767// AIE2p version of ShiftOp conversion
3769 : public mlir::ConvertOpToLLVMPattern<aievec::ShiftOp> {
3770public:
3771 using ConvertOpToLLVMPattern<aievec::ShiftOp>::ConvertOpToLLVMPattern;
3772
3773 LogicalResult
3774 matchAndRewrite(aievec::ShiftOp op, OpAdaptor adaptor,
3775 ConversionPatternRewriter &rewriter) const override {
3776 Location loc = op.getLoc();
3777
3778 Value result = op.getResult();
3779 VectorType resultType = cast<VectorType>(result.getType());
3780 Type resultScaTy = resultType.getElementType();
3781 unsigned resultBitWidth = resultScaTy.getIntOrFloatBitWidth();
3782 int resultLanes = getVectorLaneSize(resultType);
3783 int resultVectorSize = resultBitWidth * resultLanes;
3784
3785 if (resultVectorSize != 512) {
3786 op.emitWarning() << "aievec.shift conversion with result vector size "
3787 << resultVectorSize << " is not implemented.\n";
3788 return failure();
3789 }
3790
3791 // assume step is always zero
3792 auto stepCst = LLVM::ConstantOp::create(
3793 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(0));
3794
3795 // create xllvm intrinsic
3796 Value shiftOp = nullptr;
3797 SmallVector<Value> operands(
3798 {adaptor.getLhs(), adaptor.getRhs(), stepCst, adaptor.getShift()});
3799 if (llvm::isa<IntegerType>(resultScaTy)) {
3800 // Integer types - use AIE2p intrinsic
3801 shiftOp = xllvm::VectorShiftI512I512AIE2pIntrOp::create(
3802 rewriter, loc, VectorType::get({16}, rewriter.getI32Type()),
3803 forceCastOperandsToSignature(
3804 rewriter, loc, operands,
3805 {VectorType::get({16}, rewriter.getI32Type()),
3806 VectorType::get({16}, rewriter.getI32Type()),
3807 rewriter.getI32Type(), rewriter.getI32Type()}));
3808 } else {
3809 // Float types - use AIE2p intrinsic
3810 shiftOp = xllvm::VectorShiftBF512BF512AIE2pIntrOp::create(
3811 rewriter, loc, VectorType::get({32}, rewriter.getBF16Type()),
3812 forceCastOperandsToSignature(
3813 rewriter, loc, operands,
3814 {VectorType::get({32}, rewriter.getBF16Type()),
3815 VectorType::get({32}, rewriter.getBF16Type()),
3816 rewriter.getI32Type(), rewriter.getI32Type()}));
3817 }
3818
3819 // create bitcast/shape_cast for result
3820 auto resultVal =
3821 forceCastValueToType(rewriter, loc, shiftOp, op.getResult().getType());
3822 rewriter.replaceOp(op, resultVal);
3823
3824 return success();
3825 }
3826};
3827
3829 : public mlir::ConvertOpToLLVMPattern<aievec::ExtElemOp> {
3830public:
3831 using ConvertOpToLLVMPattern<aievec::ExtElemOp>::ConvertOpToLLVMPattern;
3832
3833 LogicalResult
3834 matchAndRewrite(aievec::ExtElemOp op, OpAdaptor adaptor,
3835 ConversionPatternRewriter &rewriter) const override {
3836 Location loc = op.getLoc();
3837
3838 Type resultType = op.getResult().getType();
3839 unsigned resultBitWidth = resultType.getIntOrFloatBitWidth();
3840
3841 Value src = adaptor.getSource();
3842 VectorType srcType = cast<VectorType>(src.getType());
3843 Type srcScalarType = srcType.getElementType();
3844 unsigned srcBitWidth = srcScalarType.getIntOrFloatBitWidth();
3845 int srcLanes = getVectorLaneSize(srcType);
3846 int srcVectorSize = srcBitWidth * srcLanes;
3847
3848 if (srcVectorSize != 512) {
3849 op.emitWarning() << "aievec.ext_elem conversion with source vector size "
3850 << srcVectorSize << " is not supported.\n";
3851 return failure();
3852 }
3853
3854 // create constant for sign
3855 auto signCst = LLVM::ConstantOp::create(
3856 rewriter, loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(1));
3857
3858 // create xllvm intrinsic
3859 Value extElemOp = nullptr;
3860 SmallVector<Value> operands(
3861 {adaptor.getSource(), adaptor.getIndex(), signCst});
3862 if (resultBitWidth == 8) {
3863 extElemOp = xllvm::VectorExtractElem8I512IntrOp::create(
3864 rewriter, loc, rewriter.getI32Type(),
3865 forceCastOperandsToSignature(
3866 rewriter, loc, operands,
3867 {VectorType::get({64}, rewriter.getI8Type()),
3868 rewriter.getI32Type(), rewriter.getI32Type()}));
3869 } else if (resultBitWidth == 16) {
3870 extElemOp = xllvm::VectorExtractElem16I512IntrOp::create(
3871 rewriter, loc, rewriter.getI32Type(),
3872 forceCastOperandsToSignature(
3873 rewriter, loc, operands,
3874 {VectorType::get({32}, rewriter.getI16Type()),
3875 rewriter.getI32Type(), rewriter.getI32Type()}));
3876 } else if (resultBitWidth == 32) {
3877 extElemOp = xllvm::VectorExtractElem32I512IntrOp::create(
3878 rewriter, loc, rewriter.getI32Type(),
3879 forceCastOperandsToSignature(
3880 rewriter, loc, operands,
3881 {VectorType::get({16}, rewriter.getI32Type()),
3882 rewriter.getI32Type(), rewriter.getI32Type()}));
3883 } else {
3884 op.emitWarning() << "aievec.ext_elem conversion with result bit width "
3885 << resultBitWidth << " is not implemented.\n";
3886 return failure();
3887 }
3888
3889 // create truncation op (and bitcast op)
3890 if (llvm::isa<IntegerType>(resultType)) {
3891 if (resultBitWidth < 32) {
3892 // Two-step truncation to avoid direct i32→i8 which the AIE2
3893 // backend cannot legalize after SLP vectorization.
3894 if (resultBitWidth < 16) {
3895 auto i16Ty = rewriter.getI16Type();
3896 auto trunc16 = LLVM::TruncOp::create(rewriter, loc, i16Ty, extElemOp);
3897 rewriter.replaceOpWithNewOp<LLVM::TruncOp>(op, resultType,
3898 trunc16.getResult());
3899 } else {
3900 rewriter.replaceOpWithNewOp<LLVM::TruncOp>(op, resultType, extElemOp);
3901 }
3902 } else {
3903 rewriter.replaceOp(op, extElemOp);
3904 }
3905 } else {
3906 // Float types
3907 if (resultBitWidth == 16) {
3908 extElemOp = LLVM::TruncOp::create(rewriter, loc, rewriter.getI16Type(),
3909 extElemOp);
3910 }
3911 rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(op, resultType, extElemOp);
3912 }
3913
3914 return success();
3915 }
3916};
3917
3919 : public mlir::ConvertOpToLLVMPattern<aievec::FMAElemOp> {
3920public:
3921 using ConvertOpToLLVMPattern<aievec::FMAElemOp>::ConvertOpToLLVMPattern;
3922
3923 LogicalResult
3924 matchAndRewrite(aievec::FMAElemOp fmaOp, OpAdaptor adaptor,
3925 ConversionPatternRewriter &rewriter) const override {
3926 auto loc = fmaOp.getLoc();
3927 auto lhs = adaptor.getLhs();
3928 auto rhs = adaptor.getRhs();
3929 auto acc = adaptor.getAcc();
3930 auto lhsTy = cast<VectorType>(lhs.getType());
3931 auto rhsTy = cast<VectorType>(rhs.getType());
3932 auto accTy = cast<VectorType>(acc.getType());
3933 auto flatLhsTy = getFlattenedVectorType(lhsTy);
3934 auto flatRhsTy = getFlattenedVectorType(rhsTy);
3935 auto flatAccTy = getFlattenedVectorType(accTy);
3936
3937 // Flatten operands, if needed
3938 if (lhsTy != flatLhsTy)
3939 lhs = vector::ShapeCastOp::create(rewriter, loc, flatLhsTy, lhs);
3940 if (rhsTy != flatRhsTy)
3941 rhs = vector::ShapeCastOp::create(rewriter, loc, flatRhsTy, rhs);
3942 if (accTy != flatAccTy)
3943 acc = vector::ShapeCastOp::create(rewriter, loc, flatAccTy, acc);
3944
3945 // Build vmac configuration constant
3946 Type i32ty = rewriter.getI32Type();
3947 auto confCst = LLVM::ConstantOp::create(
3948 rewriter, loc, i32ty,
3949 rewriter.getI32IntegerAttr(aiev2_vmac_compute_control(
3950 /*sgn_x=*/0, /*sgn_y=*/0, /*amode=*/2, /*bmode=*/3,
3951 /*variant=*/1, /*zero_acc=*/0, /*shift16=*/0,
3952 /*sub_mul=*/0, /*sub_acc1=*/0, /*sub_acc2=*/0,
3953 /*sub_mask=*/0)));
3954
3955 // Pad 16-lane bf16 operands to 32-lane using set+upd intrinsics.
3956 // forceCastOperandsToSignature only does bitwise reinterpretation, which
3957 // leaves garbage in the upper lanes. The MAC intrinsic requires properly
3958 // zero-padded v32bf16 inputs.
3959 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
3960 if (flatLhsTy.getElementType().isBF16() &&
3961 flatLhsTy.getNumElements() < 32) {
3962 auto zero32 = LLVM::ConstantOp::create(rewriter, loc, i32ty,
3963 rewriter.getI32IntegerAttr(0));
3964 auto zeros_i16 = xllvm::VectorBroadcast16I512IntrOp::create(
3965 rewriter, loc, VectorType::get({32}, rewriter.getI16Type()), zero32);
3966 auto zeros_bf16 =
3967 LLVM::BitcastOp::create(rewriter, loc, v32bf16Ty, zeros_i16);
3968 auto zeroVec = xllvm::ExtBF256BF512IntrOp::create(
3969 rewriter, loc, VectorType::get({16}, rewriter.getBF16Type()),
3970 zeros_bf16, zero32);
3971
3972 auto idx1 = LLVM::ConstantOp::create(rewriter, loc, i32ty,
3973 rewriter.getI32IntegerAttr(1));
3974
3975 auto lhsSet = xllvm::VectorSetBF512BF256IntrOp::create(
3976 rewriter, loc, v32bf16Ty, lhs, zero32);
3977 lhs = xllvm::UpdBF512BF256IntrOp::create(rewriter, loc, v32bf16Ty, lhsSet,
3978 zeroVec, idx1);
3979
3980 auto rhsSet = xllvm::VectorSetBF512BF256IntrOp::create(
3981 rewriter, loc, v32bf16Ty, rhs, zero32);
3982 rhs = xllvm::UpdBF512BF256IntrOp::create(rewriter, loc, v32bf16Ty, rhsSet,
3983 zeroVec, idx1);
3984 }
3985
3986 // Insert vmac intrinsic
3987 auto v8i64Ty = VectorType::get({8}, rewriter.getI64Type());
3988 auto macIntrOp = xllvm::MacConfBF16IntrOp::create(
3989 rewriter, loc, v8i64Ty,
3990 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs, acc, confCst},
3991 {v32bf16Ty, v32bf16Ty, v8i64Ty, i32ty}));
3992
3993 // Recast/Reshape result
3994 auto resVal =
3995 forceCastValueToType(rewriter, loc, macIntrOp.getResult(), flatAccTy);
3996 if (flatAccTy != accTy)
3997 resVal = vector::ShapeCastOp::create(rewriter, loc, accTy, resVal);
3998
3999 rewriter.replaceOp(fmaOp, resVal);
4000 return success();
4001 }
4002};
4003
4004// Look through vector.shape_cast ops to find the defining op. The
4005// VectorToAIEVec pass inserts shape_casts (via reshapeLeadingUnitDims) between
4006// the extension ops and the matmul, which would otherwise hide the signedness.
4007static Value lookThroughShapeCasts(Value v) {
4008 while (auto castOp = v.getDefiningOp<vector::ShapeCastOp>())
4009 v = castOp.getSource();
4010 return v;
4011}
4012
4013// If `v` is defined by a signedness-carrying unrealized_conversion_cast --
4014// which is how VectorToAIEVec attaches si*/ui* to an otherwise signless
4015// operand -- return the signedness it carries and advance `v` past it, so the
4016// intrinsic consumes the signless input and the cast is left dead for
4017// canonicalization rather than leaking to LLVM translation.
4018//
4019// A cast whose result element type is not explicitly signed or unsigned is
4020// left in place: it is some other type conversion, and dropping it would
4021// silently change the operand.
4022static std::optional<bool> peelSignednessCast(Value &v) {
4023 auto castOp = v.getDefiningOp<UnrealizedConversionCastOp>();
4024 if (!castOp)
4025 return std::nullopt;
4026 auto vecTy = dyn_cast<VectorType>(v.getType());
4027 auto intTy =
4028 vecTy ? dyn_cast<IntegerType>(vecTy.getElementType()) : IntegerType();
4029 if (!intTy || intTy.isSignless())
4030 return std::nullopt;
4031 v = castOp.getInputs()[0];
4032 return intTy.isSigned();
4033}
4034
4035// The result of resolving one integer matmul operand.
4037 // The value the MAC intrinsic should consume, with any widening op and any
4038 // signedness-carrying cast peeled off.
4040 // Whether that value is to be treated as signed, i.e. whether the MAC
4041 // configuration word's signX (lhs) / signY (rhs) bit should be set.
4043};
4044
4045// Recover the signedness of an integer matmul operand. MLIR integers are
4046// signless, so it has to come from context; in priority order:
4047//
4048// 1. an arith.extsi / arith.extui feeding the operand -- peel it and take
4049// its signedness;
4050// 2. an unrealized_conversion_cast to a signed/unsigned element type, which
4051// is how VectorToAIEVec carries signedness across the peel it performs.
4052// Peel it too, so the intrinsic consumes the signless value and the cast
4053// is left dead for canonicalization rather than leaking to LLVM;
4054// 3. an explicitly signed or unsigned element type;
4055// 4. otherwise the element type is signless -- treat it as signed.
4056//
4057// Case 4 matches the conventional reading of a signless integer, and is the
4058// more useful default: the shapes that reach it are the ones whose operand
4059// type is already a legal AIE2 narrow type (so nothing was extended), which in
4060// practice means i16/i32 operands carrying signed data.
4061//
4062// Both aievec.matmul and aievec.matmul_aie2p resolve signedness through this
4063// function. They previously each had their own copy and had drifted apart.
4064static ResolvedMatMulOperand resolveMatMulOperand(Value v) {
4065 std::optional<bool> castSigned = peelSignednessCast(v);
4066
4067 Value orig = lookThroughShapeCasts(v);
4068 if (auto extSIOp = orig.getDefiningOp<arith::ExtSIOp>())
4069 return {lookThroughShapeCasts(extSIOp.getIn()), true};
4070 if (auto extUIOp = orig.getDefiningOp<arith::ExtUIOp>())
4071 return {lookThroughShapeCasts(extUIOp.getIn()), false};
4072 if (castSigned)
4073 return {v, *castSigned};
4074
4075 auto vecTy = dyn_cast<VectorType>(v.getType());
4076 auto intTy =
4077 vecTy ? dyn_cast<IntegerType>(vecTy.getElementType()) : IntegerType();
4078 return {v, !(intTy && intTy.isUnsigned())};
4079}
4080
4081// Transposes the rows x cols tile held row-major in the flat vector `v` with
4082// the shuffle modes getTileTransposeModes picks for it. A 256-bit tile is
4083// padded to 512 bits for the shuffle, and a 1024-bit one is shuffled from its
4084// two 512-bit halves into the two halves of the result.
4085static Value transposeTile(OpBuilder &b, Location loc, Value v, int64_t rows,
4086 int64_t cols, bool aie2p) {
4087 auto vecTy = cast<VectorType>(v.getType());
4088 std::optional<SmallVector<aievec::ShuffleMode, 2>> modes =
4089 aievec::getTileTransposeModes(aie2p, vecTy.getElementType(), rows, cols);
4090 assert(modes && "the matmul verifier admits only transposable tiles");
4091 if (modes->empty())
4092 return v;
4093 Type i32ty = b.getI32Type();
4094 auto v16i32Ty = VectorType::get({16}, i32ty);
4095 SmallVector<Value, 2> modeCsts;
4096 auto createModeConstants = [&]() {
4097 for (aievec::ShuffleMode mode : *modes)
4098 modeCsts.push_back(LLVM::ConstantOp::create(
4099 b, loc, i32ty, b.getI32IntegerAttr(static_cast<int32_t>(mode))));
4100 };
4101 auto shuffle = [&](Value x, Value y, Value modeCst) -> Value {
4102 if (aie2p)
4103 return xllvm::VectorShuffleAIE2pIntrOp::create(b, loc, v16i32Ty, x, y,
4104 modeCst);
4105 return xllvm::VectorShuffleIntrOp::create(b, loc, v16i32Ty, x, y, modeCst);
4106 };
4107 int64_t numElems = vecTy.getNumElements();
4108 if (modes->size() == 2) {
4109 auto halves = forceCastValueToType(b, loc, v, VectorType::get({32}, i32ty));
4110 auto lo = vector::ShuffleOp::create(
4111 b, loc, halves, halves, llvm::to_vector(llvm::seq<int64_t>(0, 16)));
4112 auto hi = vector::ShuffleOp::create(
4113 b, loc, halves, halves, llvm::to_vector(llvm::seq<int64_t>(16, 32)));
4114 createModeConstants();
4115 Value resLo = shuffle(lo, hi, modeCsts[0]);
4116 Value resHi = shuffle(lo, hi, modeCsts[1]);
4117 auto res = vector::ShuffleOp::create(
4118 b, loc, resLo, resHi, llvm::to_vector(llvm::seq<int64_t>(0, 32)));
4119 return forceCastValueToType(b, loc, res, vecTy);
4120 }
4121 bool half = numElems * vecTy.getElementTypeBitWidth() == 256;
4122 Value x = v;
4123 if (half) {
4124 SmallVector<int64_t> padMask =
4125 llvm::to_vector(llvm::seq<int64_t>(0, numElems));
4126 padMask.append(numElems, vector::ShuffleOp::kPoisonIndex);
4127 x = vector::ShuffleOp::create(b, loc, v, v, padMask);
4128 }
4129 Value xi = forceCastValueToType(b, loc, x, v16i32Ty);
4130 Value y =
4131 aie2p ? xi
4132 : xllvm::UndefV16I32IntrOp::create(b, loc, v16i32Ty).getResult();
4133 createModeConstants();
4134 Value res =
4135 forceCastValueToType(b, loc, shuffle(xi, y, modeCsts[0]), x.getType());
4136 if (half)
4137 res = vector::ShuffleOp::create(
4138 b, loc, res, res, llvm::to_vector(llvm::seq<int64_t>(0, numElems)));
4139 return res;
4140}
4141
4142// Brings the flattened operand `flat`, stored as `storedTy` (transposed when
4143// `stored` is set), into the layout the recipe reads it in.
4144static Value materializeOperandLayout(OpBuilder &b, Location loc, Value flat,
4145 VectorType storedTy, bool stored,
4146 bool native, bool aie2p) {
4147 if (stored == native)
4148 return flat;
4149 return transposeTile(b, loc, flat, storedTy.getDimSize(0),
4150 storedTy.getDimSize(1), aie2p);
4151}
4152
4154 : public mlir::ConvertOpToLLVMPattern<aievec::MatMulOp> {
4155 using ConvertOpToLLVMPattern<aievec::MatMulOp>::ConvertOpToLLVMPattern;
4156
4157 enum class Accumulator { I32, I64, BF16 };
4158
4159 // The accumulator kind and `mac.conf` modes of each AIE2 recipe.
4160 static std::tuple<Accumulator, int, int, int>
4161 getAccAndModes(aievec::MatMulRecipeKind kind) {
4162 using K = aievec::MatMulRecipeKind;
4163 switch (kind) {
4164 case K::AIE2_I8xI4_4x16x8:
4165 return {Accumulator::I32, 0, 0, 0};
4166 case K::AIE2_I8xI8_4x8x8:
4167 return {Accumulator::I32, 0, 1, 0};
4168 case K::AIE2_I16xI8_4x4x8:
4169 return {Accumulator::I32, 0, 2, 0};
4170 case K::AIE2_I16xI16_4x2x8:
4171 return {Accumulator::I32, 0, 3, 0};
4172 case K::AIE2_I16xI8_2x8x8_Acc64:
4173 return {Accumulator::I64, 1, 2, 0};
4174 case K::AIE2_I16xI8_4x8x4_Acc64:
4175 return {Accumulator::I64, 1, 2, 1};
4176 case K::AIE2_I16xI16_2x4x8_Acc64:
4177 return {Accumulator::I64, 1, 3, 0};
4178 case K::AIE2_I16xI16_4x4x4_Acc64:
4179 return {Accumulator::I64, 1, 3, 1};
4180 case K::AIE2_I32xI16_4x2x4_Acc64:
4181 return {Accumulator::I64, 1, 0, 0};
4182 case K::AIE2_BF16_4x8x4:
4183 return {Accumulator::BF16, 2, 3, 0};
4184 default:
4185 llvm_unreachable("not an AIE2 recipe");
4186 }
4187 }
4188
4189 LogicalResult
4190 matchAndRewrite(aievec::MatMulOp op, OpAdaptor adaptor,
4191 ConversionPatternRewriter &rewriter) const override {
4192 auto lhsTy = cast<VectorType>(op.getLhs().getType());
4193 auto rhsTy = cast<VectorType>(op.getRhs().getType());
4194 auto accTy = cast<VectorType>(op.getAcc().getType());
4195 std::optional<aievec::MatMulRecipe> recipe =
4196 aievec::checkMatMulTile(/*aie2p=*/false, lhsTy, rhsTy, accTy,
4197 op.getLhsTransposed(), op.getRhsTransposed());
4198 if (!recipe)
4199 return rewriter.notifyMatchFailure(op, "not an AIE2 matmul tile");
4200 auto [accKind, amode, bmode, variant] = getAccAndModes(recipe->kind);
4201
4202 // Recover operand signedness, and narrow each operand to the value the MAC
4203 // should consume. An operand is only narrowed to a value of its own
4204 // element width, as the recipe was chosen for that width.
4205 Value lhs = adaptor.getLhs();
4206 Value rhs = adaptor.getRhs();
4207 int signX = 0, signY = 0;
4208 if (accKind != Accumulator::BF16) {
4209 auto narrow = [](Value v, VectorType ty, int &sign) {
4210 ResolvedMatMulOperand resolved = resolveMatMulOperand(v);
4211 sign = resolved.isSigned ? 1 : 0;
4212 if (getElementTypeOrSelf(resolved.narrowed).getIntOrFloatBitWidth() ==
4213 ty.getElementTypeBitWidth())
4214 return resolved.narrowed;
4215 return v;
4216 };
4217 lhs = narrow(lhs, lhsTy, signX);
4218 rhs = narrow(rhs, rhsTy, signY);
4219 }
4220 int conf = aiev2_vmac_compute_control(
4221 /*sgn_x=*/signX, /*sgn_y=*/signY, amode, bmode, variant,
4222 /*zero_acc=*/0, /*shift16=*/0, /*sub_mul=*/0, /*sub_acc1=*/0,
4223 /*sub_acc2=*/0, /*sub_mask=*/0);
4224
4225 Location loc = op.getLoc();
4226 // Flatten the inputs, and transpose those the multiply reads the other
4227 // way.
4228 auto flatten = [&](Value v) -> Value {
4229 auto flatTy = getFlattenedVectorType(cast<VectorType>(v.getType()));
4230 if (v.getType() == flatTy)
4231 return v;
4232 return vector::ShapeCastOp::create(rewriter, loc, flatTy, v);
4233 };
4234 lhs = flatten(lhs);
4235 rhs = flatten(rhs);
4236 auto accFlattenedVecTy = getFlattenedVectorType(accTy);
4237 Value acc = vector::ShapeCastOp::create(rewriter, loc, accFlattenedVecTy,
4238 adaptor.getAcc());
4239 lhs = materializeOperandLayout(rewriter, loc, lhs, lhsTy,
4240 op.getLhsTransposed(), recipe->lhsKM,
4241 /*aie2p=*/false);
4242 rhs = materializeOperandLayout(rewriter, loc, rhs, rhsTy,
4243 op.getRhsTransposed(), recipe->rhsNK,
4244 /*aie2p=*/false);
4245
4246 Type i32ty = rewriter.getI32Type();
4247 auto confCst = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4248 rewriter.getI32IntegerAttr(conf));
4249 SmallVector<Value> operands({lhs, rhs, acc, confCst});
4250 Value matMulResVal;
4251 if (accKind == Accumulator::BF16)
4252 matMulResVal =
4253 xllvm::MacConfBF16IntrOp::create(
4254 rewriter, loc, VectorType::get({8}, rewriter.getI64Type()),
4255 forceCastOperandsToSignature(
4256 rewriter, loc, operands,
4257 {VectorType::get({32}, rewriter.getBF16Type()),
4258 VectorType::get({32}, rewriter.getBF16Type()),
4259 VectorType::get({8}, rewriter.getI64Type()), i32ty}))
4260 .getResult();
4261 else {
4262 SmallVector<Type> intrFuncSig(
4263 {VectorType::get({64}, rewriter.getI8Type()),
4264 VectorType::get({16}, i32ty),
4265 VectorType::get({16}, rewriter.getI64Type()), i32ty});
4266 VectorType v16xi64ty = VectorType::get({16}, rewriter.getI64Type());
4267 if (accKind == Accumulator::I32)
4268 matMulResVal = xllvm::MacConfAcc32IntrOp::create(
4269 rewriter, loc, v16xi64ty,
4270 forceCastOperandsToSignature(rewriter, loc, operands,
4271 intrFuncSig))
4272 .getResult();
4273 else
4274 matMulResVal = xllvm::MacConfAcc64IntrOp::create(
4275 rewriter, loc, v16xi64ty,
4276 forceCastOperandsToSignature(rewriter, loc, operands,
4277 intrFuncSig))
4278 .getResult();
4279 }
4280
4281 auto castFromAcc =
4282 bitcastValueToType(rewriter, loc, matMulResVal, accFlattenedVecTy);
4283
4284 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, op.getType(),
4285 castFromAcc);
4286
4287 return success();
4288 }
4289};
4290
4291// Widens a v64bf16 to v64accfloat by multiplying it by 1.0, which is exact
4292// and runs on the multiplier instead of the conversion unit.
4293static Value widenBF16ToAccFloat(OpBuilder &rewriter, Location loc, Type i32ty,
4294 Value v64bf16) {
4295 auto v64bf16Ty = VectorType::get({64}, rewriter.getBF16Type());
4296 auto ones = LLVM::ConstantOp::create(
4297 rewriter, loc, v64bf16Ty,
4298 DenseElementsAttr::get(
4299 v64bf16Ty, rewriter.getFloatAttr(rewriter.getBF16Type(), 1.0)));
4300 // bf16 x bf16 elementwise multiply into an fp32 accumulator.
4301 auto conf = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4302 rewriter.getI32IntegerAttr(60));
4303 return xllvm::MulConfBF16I1024ACC2048AIE2pIntrOp::create(
4304 rewriter, loc, VectorType::get({64}, rewriter.getF32Type()), v64bf16,
4305 ones, conf);
4306}
4307
4308// Helper function to perform BFP16-based 8×8 matmul via mac_8x8_8x8T_conf
4309// LHS: v64accfloat, RHS: v64accfloat (transposed 8×8), ACC: v64i32
4310// Returns: v64i32 result
4311static Value performBFP16_8x8MatMul(OpBuilder &rewriter, Location loc,
4312 Type i32ty, Value lhs64f32,
4313 Value rhs64f32Transposed, Value acc64i32,
4314 Value confCst) {
4315 auto v64i32Ty = VectorType::get({64}, rewriter.getI32Type());
4316
4317 // Convert both to BFP16 format
4318 auto bfpStructTy = mlir::LLVM::LLVMStructType::getLiteral(
4319 rewriter.getContext(), {VectorType::get({64}, rewriter.getI8Type()),
4320 VectorType::get({8}, rewriter.getI8Type())});
4321
4322 auto lhsBFP = xllvm::Vector64AccFloatToV64BFP16EBS8AIE2pIntrOp::create(
4323 rewriter, loc, bfpStructTy, lhs64f32);
4324 auto rhsBFP = xllvm::Vector64AccFloatToV64BFP16EBS8AIE2pIntrOp::create(
4325 rewriter, loc, bfpStructTy, rhs64f32Transposed);
4326
4327 // Extract mantissa and exponent
4328 auto lhsData = LLVM::ExtractValueOp::create(rewriter, loc, lhsBFP, 0);
4329 auto lhsExp = LLVM::ExtractValueOp::create(rewriter, loc, lhsBFP, 1);
4330 auto rhsData = LLVM::ExtractValueOp::create(rewriter, loc, rhsBFP, 0);
4331 auto rhsExp = LLVM::ExtractValueOp::create(rewriter, loc, rhsBFP, 1);
4332
4333 // Perform BFP16 matmul
4334 return xllvm::MacConfBFP576ACC2048AIE2pIntrOp::create(
4335 rewriter, loc, v64i32Ty, lhsData, lhsExp, rhsData, rhsExp, acc64i32,
4336 confCst);
4337}
4338
4339// Helper function to perform 8×8×4 BF16 matmul following mac_8x8_8x4_bf16
4340// LHS: 64 bfloat16 (8×8 matrix stored [k, m]), RHS: 32 bfloat16 (8×4 matrix)
4341// ACC: 32 float (8×4 result)
4342static Value perform8x8x4MatMul(OpBuilder &rewriter, Location loc, Type i32ty,
4343 Value lhsKM64bf16, Value rhs32bf16,
4344 Value acc32f32) {
4345 auto v32bf16Ty = VectorType::get({32}, rewriter.getBF16Type());
4346 auto v32f32Ty = VectorType::get({32}, rewriter.getF32Type());
4347
4348 // Rows 0-3 and 4-7 of the [k, m] lhs: one column of A per 8 values.
4349 auto xaBF16 =
4350 vector::ShuffleOp::create(rewriter, loc, lhsKM64bf16, lhsKM64bf16,
4351 llvm::to_vector(llvm::seq<int64_t>(0, 32)));
4352 auto xbBF16 =
4353 vector::ShuffleOp::create(rewriter, loc, lhsKM64bf16, lhsKM64bf16,
4354 llvm::to_vector(llvm::seq<int64_t>(32, 64)));
4355
4356 // Helper to extract and broadcast 8 elements to 32, then shuffle with T16_4x8
4357 auto extractBroadcastShuffle = [&](Value src, int idx) -> Value {
4358 SmallVector<int64_t> extractMask;
4359 int startIdx = idx * 8;
4360 for (int i = 0; i < 8; ++i)
4361 extractMask.push_back(startIdx + i);
4362 // Broadcast by repeating 4 times to get 32 elements
4363 for (int rep = 0; rep < 3; ++rep) {
4364 for (int i = 0; i < 8; ++i)
4365 extractMask.push_back(startIdx + i);
4366 }
4367 auto broadcasted =
4368 vector::ShuffleOp::create(rewriter, loc, src, src, extractMask);
4369
4370 // Apply T16_4x8 shuffle pattern (mode 29)
4371 auto broadI32 = forceCastValueToType(rewriter, loc, broadcasted,
4372 VectorType::get({16}, i32ty));
4373 auto shuffleMode4x8 = LLVM::ConstantOp::create(
4374 rewriter, loc, i32ty, rewriter.getI32IntegerAttr(29));
4375 auto shuffled = xllvm::VectorShuffleAIE2pIntrOp::create(
4376 rewriter, loc, VectorType::get({16}, i32ty), broadI32, broadI32,
4377 shuffleMode4x8);
4378
4379 return forceCastValueToType(rewriter, loc, shuffled, v32bf16Ty);
4380 };
4381
4382 // Prepare 8 row vectors from xa and xb
4383 SmallVector<Value> rowVectors;
4384 for (int i = 0; i < 4; ++i)
4385 rowVectors.push_back(extractBroadcastShuffle(xaBF16, i));
4386 for (int i = 0; i < 4; ++i)
4387 rowVectors.push_back(extractBroadcastShuffle(xbBF16, i));
4388
4389 // Helper to extract and broadcast 4 elements to 32 (for RHS columns)
4390 auto extractBroadcast4 = [&](Value src, int idx) -> Value {
4391 SmallVector<int64_t> mask;
4392 int startIdx = idx * 4;
4393 // Repeat the 4 elements 8 times to get 32 elements
4394 for (int rep = 0; rep < 8; ++rep) {
4395 for (int i = 0; i < 4; ++i)
4396 mask.push_back(startIdx + i);
4397 }
4398 return vector::ShuffleOp::create(rewriter, loc, src, src, mask);
4399 };
4400
4401 // Prepare 8 column vectors from RHS
4402 SmallVector<Value> colVectors;
4403 for (int i = 0; i < 8; ++i)
4404 colVectors.push_back(extractBroadcast4(rhs32bf16, i));
4405
4406 // Perform 8 MAC operations with conf=60 (no zero_acc)
4407 auto conf60 = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4408 rewriter.getI32IntegerAttr(60));
4409
4410 Value acc = acc32f32;
4411 for (int i = 0; i < 8; ++i) {
4412 acc = xllvm::MacConfBF16I512ACC1024AIE2pIntrOp::create(
4413 rewriter, loc, v32f32Ty, rowVectors[i], colVectors[i], acc, conf60);
4414 }
4415
4416 return acc;
4417}
4418
4420 : public mlir::ConvertOpToLLVMPattern<aievec::MatMulOp_AIE2P> {
4421 using ConvertOpToLLVMPattern<aievec::MatMulOp_AIE2P>::ConvertOpToLLVMPattern;
4422
4423 LogicalResult
4424 matchAndRewrite(aievec::MatMulOp_AIE2P op, OpAdaptor adaptor,
4425 ConversionPatternRewriter &rewriter) const override {
4426 using K = aievec::MatMulRecipeKind;
4427 auto lhsTy = cast<VectorType>(op.getLhs().getType());
4428 auto rhsTy = cast<VectorType>(op.getRhs().getType());
4429 auto accTy = cast<VectorType>(op.getAcc().getType());
4430 std::optional<aievec::MatMulRecipe> recipe =
4431 aievec::checkMatMulTile(/*aie2p=*/true, lhsTy, rhsTy, accTy,
4432 op.getLhsTransposed(), op.getRhsTransposed());
4433 if (!recipe)
4434 return rewriter.notifyMatchFailure(op, "not an AIE2P matmul tile");
4435
4436 // Signedness follows the same rule as aievec.matmul; see
4437 // resolveMatMulOperand. Only the signedness is taken from it here; the
4438 // operand values are only advanced past a signedness-carrying cast, not
4439 // past a widening op, as the recipe was chosen for the operand types.
4440 Value lhsOperand = adaptor.getLhs();
4441 Value rhsOperand = adaptor.getRhs();
4442 bool lhsIsSigned = resolveMatMulOperand(lhsOperand).isSigned;
4443 bool rhsIsSigned = resolveMatMulOperand(rhsOperand).isSigned;
4444 peelSignednessCast(lhsOperand);
4445 peelSignednessCast(rhsOperand);
4446
4447 Location loc = op.getLoc();
4448 // Flatten the inputs. An operand the multiply reads the other way is
4449 // transposed where the recipe first uses it.
4450 Value lhsFlat = vector::ShapeCastOp::create(
4451 rewriter, loc,
4452 getFlattenedVectorType(cast<VectorType>(lhsOperand.getType())),
4453 lhsOperand);
4454 Value rhsFlat = vector::ShapeCastOp::create(
4455 rewriter, loc,
4456 getFlattenedVectorType(cast<VectorType>(rhsOperand.getType())),
4457 rhsOperand);
4458 auto accFlattenedVecTy = getFlattenedVectorType(accTy);
4459 Value acc = vector::ShapeCastOp::create(rewriter, loc, accFlattenedVecTy,
4460 adaptor.getAcc());
4461 auto getLhs = [&]() {
4462 return materializeOperandLayout(rewriter, loc, lhsFlat, lhsTy,
4463 op.getLhsTransposed(), recipe->lhsKM,
4464 /*aie2p=*/true);
4465 };
4466 auto getRhs = [&]() {
4467 return materializeOperandLayout(rewriter, loc, rhsFlat, rhsTy,
4468 op.getRhsTransposed(), recipe->rhsNK,
4469 /*aie2p=*/true);
4470 };
4471 Type i32ty = rewriter.getI32Type();
4472
4473 Value matMulResVal;
4474 switch (recipe->kind) {
4475 case K::AIE2P_I8_8x8x8: {
4476 // <8x8xi8> x <8x8xi8> + <8x8xi32>
4477 // Signature: <32 x i64> @llvm.aie2p.I512.I512.ACC2048.mac.conf(
4478 // <16 x i32>, <32 x i16>, <32 x i64>, i32)
4479 // Base conf for amode=0/bmode=1 is bmode<<3 = 8; signedness lives in
4480 // signX (bit 9) / signY (bit 8).
4481 int conf =
4482 8 | ((lhsIsSigned ? 1 : 0) << 9) | ((rhsIsSigned ? 1 : 0) << 8);
4483 auto confCst = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4484 rewriter.getI32IntegerAttr(conf));
4485 matMulResVal =
4486 xllvm::MacConfI512ACC2048AIE2pIntrOp::create(
4487 rewriter, loc, VectorType::get({32}, rewriter.getI64Type()),
4488 forceCastOperandsToSignature(
4489 rewriter, loc, {getLhs(), getRhs(), acc, confCst},
4490 {VectorType::get({16}, rewriter.getI32Type()),
4491 VectorType::get({32}, rewriter.getI16Type()),
4492 VectorType::get({32}, rewriter.getI64Type()), i32ty}))
4493 .getResult();
4494 break;
4495 }
4496 case K::AIE2P_I16_8x2x8: {
4497 // <8x2xi16> x <2x8xi16> + <8x8xi32>
4498 // Input vectors are 16 lanes each, need to pad to 64 lanes for intrinsic
4499 // Signature: <32 x i64> @llvm.aie2p.I1024.I1024.ACC2048.mac.conf(
4500 // <32 x i32>, <64 x i16>, <32 x i64>, i32)
4501 SmallVector<int64_t> padMask = llvm::to_vector(llvm::seq<int64_t>(0, 16));
4502 padMask.append(48, vector::ShuffleOp::kPoisonIndex);
4503 Value lhs = getLhs(), rhs = getRhs();
4504 auto lhsPadded =
4505 vector::ShuffleOp::create(rewriter, loc, lhs, lhs, padMask);
4506 auto rhsPadded =
4507 vector::ShuffleOp::create(rewriter, loc, rhs, rhs, padMask);
4508 auto confCst = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4509 rewriter.getI32IntegerAttr(24));
4510 matMulResVal =
4511 xllvm::MacConfI1024ACC2048AIE2pIntrOp::create(
4512 rewriter, loc, VectorType::get({32}, rewriter.getI64Type()),
4513 forceCastOperandsToSignature(
4514 rewriter, loc, {lhsPadded, rhsPadded, acc, confCst},
4515 {VectorType::get({32}, rewriter.getI32Type()),
4516 VectorType::get({64}, rewriter.getI16Type()),
4517 VectorType::get({32}, rewriter.getI64Type()), i32ty}))
4518 .getResult();
4519 break;
4520 }
4521 case K::AIE2P_BFP16_8x8x8: {
4522 // <8x8xbf16> x <8x8xbf16> + <8x8xf32>
4523 // This implements the 8×8×8 BF16 matmul using BFP16 format
4524 // Following the aie_api reference implementation that converts to BFP16
4525 auto v32f32Ty = VectorType::get({32}, rewriter.getF32Type());
4526
4527 // Step 1: Convert LHS v64bfloat16 to v64accfloat (in two v32 chunks)
4528 Value lhs = getLhs();
4529 auto lhs32bf16_lo = vector::ShuffleOp::create(
4530 rewriter, loc, lhs, lhs, llvm::to_vector(llvm::seq<int64_t>(0, 32)));
4531 auto lhs32bf16_hi = vector::ShuffleOp::create(
4532 rewriter, loc, lhs, lhs, llvm::to_vector(llvm::seq<int64_t>(32, 64)));
4533 auto lhs32f32_lo = xllvm::Vector32BF16ToV32AccFloatAIE2pIntrOp::create(
4534 rewriter, loc, v32f32Ty, lhs32bf16_lo);
4535 auto lhs32f32_hi = xllvm::Vector32BF16ToV32AccFloatAIE2pIntrOp::create(
4536 rewriter, loc, v32f32Ty, lhs32bf16_hi);
4537
4538 // Concat to v64accfloat
4539 auto lhs64f32 =
4540 vector::ShuffleOp::create(rewriter, loc, lhs32f32_lo, lhs32f32_hi,
4541 llvm::to_vector(llvm::seq<int64_t>(0, 64)));
4542
4543 // Step 2: Widen the [n, k] RHS to accfloat
4544 auto rhsNK = widenBF16ToAccFloat(rewriter, loc, i32ty, getRhs());
4545
4546 // Step 3: BFP16 8×8 matmul
4547 auto conf780 = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4548 rewriter.getI32IntegerAttr(780));
4549 matMulResVal = performBFP16_8x8MatMul(
4550 rewriter, loc, i32ty, lhs64f32, rhsNK,
4551 forceCastValueToType(rewriter, loc, acc,
4552 VectorType::get({64}, rewriter.getI32Type())),
4553 conf780);
4554 break;
4555 }
4556 case K::AIE2P_BFP16_4x8x8: {
4557 // <4x8xbf16> x <8x8xbf16> + <4x8xf32>
4558 // LHS: 32 lanes, RHS: 64 lanes, ACC: 32 lanes
4559 // Similar to 8×8×8 but only use first 32 lanes of LHS
4560 auto v32f32Ty = VectorType::get({32}, rewriter.getF32Type());
4561 SmallVector<int64_t> padMask = llvm::to_vector(llvm::seq<int64_t>(0, 32));
4562 padMask.append(32, vector::ShuffleOp::kPoisonIndex);
4563
4564 // Step 1: Convert LHS v32bfloat16 to v32accfloat, then pad to v64accfloat
4565 auto lhs32f32 = xllvm::Vector32BF16ToV32AccFloatAIE2pIntrOp::create(
4566 rewriter, loc, v32f32Ty, getLhs());
4567 auto lhs64f32 =
4568 vector::ShuffleOp::create(rewriter, loc, lhs32f32, lhs32f32, padMask);
4569
4570 // Step 2: Widen the [n, k] RHS to accfloat
4571 auto rhsNK = widenBF16ToAccFloat(rewriter, loc, i32ty, getRhs());
4572
4573 // Step 3: Pad ACC from 32 to 64 i32
4574 auto acc32i32 = forceCastValueToType(
4575 rewriter, loc, acc, VectorType::get({32}, rewriter.getI32Type()));
4576 auto acc64i32 =
4577 vector::ShuffleOp::create(rewriter, loc, acc32i32, acc32i32, padMask);
4578
4579 // Step 4: BFP16 8×8 matmul
4580 auto confCst = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4581 rewriter.getI32IntegerAttr(780));
4582 auto result64i32 = performBFP16_8x8MatMul(rewriter, loc, i32ty, lhs64f32,
4583 rhsNK, acc64i32, confCst);
4584
4585 // Step 5: Extract first 32 elements
4586 matMulResVal =
4587 vector::ShuffleOp::create(rewriter, loc, result64i32, result64i32,
4588 llvm::to_vector(llvm::seq<int64_t>(0, 32)));
4589 break;
4590 }
4591 case K::AIE2P_BF16Outer_8x1x8: {
4592 // <8x1xbf16> x <1x8xbf16> + <8x8xf32>
4593 // Outer product: grow_replicate both to 64, transpose LHS, use
4594 // mac_elem_64_conf
4595 auto v64f32Ty = VectorType::get({64}, rewriter.getF32Type());
4596
4597 // Replicate LHS and RHS from 8 to 64 elements (8 times each)
4598 SmallVector<int64_t> replicateMask;
4599 for (int rep = 0; rep < 8; ++rep)
4600 for (int i = 0; i < 8; ++i)
4601 replicateMask.push_back(i);
4602 Value lhs = lhsFlat, rhs = rhsFlat;
4603 auto lhs64bf16 =
4604 vector::ShuffleOp::create(rewriter, loc, lhs, lhs, replicateMask);
4605
4606 // Transpose LHS as 8×8 matrix
4607 SmallVector<int64_t> transposeMask;
4608 for (int c = 0; c < 8; ++c)
4609 for (int r = 0; r < 8; ++r)
4610 transposeMask.push_back(r * 8 + c);
4611 auto lhs64bf16Transposed = vector::ShuffleOp::create(
4612 rewriter, loc, lhs64bf16, lhs64bf16, transposeMask);
4613 auto rhs64bf16 =
4614 vector::ShuffleOp::create(rewriter, loc, rhs, rhs, replicateMask);
4615
4616 // mac_elem_64_conf: 64 bf16 lanes, so the I1024 operand form.
4617 auto confCst = LLVM::ConstantOp::create(rewriter, loc, i32ty,
4618 rewriter.getI32IntegerAttr(60));
4619 matMulResVal = xllvm::MacConfBF16I1024ACC2048AIE2pIntrOp::create(
4620 rewriter, loc, v64f32Ty, lhs64bf16Transposed, rhs64bf16, acc,
4621 confCst);
4622 break;
4623 }
4624 case K::AIE2P_BF16Elem_4x8x4: {
4625 // 4×8×4 matmul: <4x8xbf16> x <8x4xbf16> + <4x4xf32>
4626 // Following the reference pattern: a.grow<64>(), b,
4627 // acc.grow<32>().extract<16>(0). Pad the [m, k] LHS to 8 rows, transpose
4628 // it to [k, m] and pad ACC 16→32 for the 8×8×4 multiply, then extract
4629 // 32→16.
4630 SmallVector<int64_t> lhsPadMask =
4631 llvm::to_vector(llvm::seq<int64_t>(0, 32));
4632 lhsPadMask.append(32, vector::ShuffleOp::kPoisonIndex);
4633 Value lhs = getLhs();
4634 Value lhsPadded =
4635 vector::ShuffleOp::create(rewriter, loc, lhs, lhs, lhsPadMask);
4636 lhsPadded = transposeTile(rewriter, loc, lhsPadded, 8, 8,
4637 /*aie2p=*/true);
4638
4639 SmallVector<int64_t> accPadMask =
4640 llvm::to_vector(llvm::seq<int64_t>(0, 16));
4641 accPadMask.append(16, vector::ShuffleOp::kPoisonIndex);
4642 auto accPadded =
4643 vector::ShuffleOp::create(rewriter, loc, acc, acc, accPadMask);
4644
4645 Value acc32 = perform8x8x4MatMul(rewriter, loc, i32ty, lhsPadded,
4646 getRhs(), accPadded);
4647
4648 matMulResVal =
4649 vector::ShuffleOp::create(rewriter, loc, acc32, acc32,
4650 llvm::to_vector(llvm::seq<int64_t>(0, 16)));
4651 break;
4652 }
4653 case K::AIE2P_BF16Elem_8x8x4: {
4654 // 8×8×4 matmul: <8x8xbf16> [k, m] x <8x4xbf16> + <8x4xf32>
4655 Value lhs = getLhs();
4656 Value rhs = getRhs();
4657 matMulResVal = perform8x8x4MatMul(rewriter, loc, i32ty, lhs, rhs, acc);
4658 break;
4659 }
4660 default:
4661 return rewriter.notifyMatchFailure(op, "not an AIE2P recipe");
4662 }
4663
4664 // Cast from flattened result back to original accumulator shape
4665 auto castFromAcc =
4666 forceCastValueToType(rewriter, loc, matMulResVal, accFlattenedVecTy);
4667 // Reshape back to original shape
4668 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, op.getType(),
4669 castFromAcc);
4670 return success();
4671 }
4672};
4673
4674// This pattern folds aievec.cast op. For AIE2, the accumulators are in 32/64
4675// bits, and the vectors are in 4/8/16/32 bits. Hence, we don't have to
4676// explicitly express the casting between accumulators and vectors at the LLVM
4677// dialect level. The backend LLVM compiler will decide the correct accumulator
4678// or vector registers given the ops and intrinsics.
4679class FoldAIECastOps : public mlir::ConvertOpToLLVMPattern<aievec::CastOp> {
4680 using ConvertOpToLLVMPattern<aievec::CastOp>::ConvertOpToLLVMPattern;
4681
4682 // Helper to check if a value is a constant zero
4683 static bool isConstantZero(Value val) {
4684 DenseElementsAttr denseAttr;
4685
4686 // Check for both arith.constant and llvm.mlir.constant
4687 if (auto arithConstOp = val.getDefiningOp<arith::ConstantOp>()) {
4688 denseAttr = dyn_cast<DenseElementsAttr>(arithConstOp.getValue());
4689 } else if (auto llvmConstOp = val.getDefiningOp<LLVM::ConstantOp>()) {
4690 denseAttr = dyn_cast<DenseElementsAttr>(llvmConstOp.getValue());
4691 }
4692
4693 if (!denseAttr || !denseAttr.isSplat())
4694 return false;
4695
4696 auto splatAttr = denseAttr.getSplatValue<Attribute>();
4697 if (auto floatAttr = dyn_cast<FloatAttr>(splatAttr))
4698 return floatAttr.getValue().isZero();
4699 if (auto intAttr = dyn_cast<IntegerAttr>(splatAttr))
4700 return intAttr.getValue().isZero();
4701
4702 return false;
4703 }
4704
4705 LogicalResult
4706 matchAndRewrite(aievec::CastOp castOp, OpAdaptor adaptor,
4707 ConversionPatternRewriter &rewriter) const override {
4708 // Special handling for isResAcc=true with zero constant source
4709 // The backend cannot handle zeroinitializer for accumulator types,
4710 // so we must use the vbroadcast.zero.acc1024 intrinsic instead
4711 if (!castOp.getIsResAcc() || !isConstantZero(adaptor.getSource())) {
4712 // Default behavior: fold the cast
4713 rewriter.replaceOp(castOp, adaptor.getSource());
4714 return success();
4715 }
4716
4717 Location loc = castOp.getLoc();
4718 auto srcVecType = cast<VectorType>(castOp.getSource().getType());
4719 Type srcElemType = srcVecType.getElementType();
4720 int lanes = getVectorLaneSize(srcVecType);
4721
4722 // For f32 vectors (accfloat), use vbroadcast.zero.acc1024
4723 if (srcElemType.isF32() && lanes == 16) {
4724 // Call vbroadcast.zero.acc1024 to get vector<16xi64>
4725 auto zeroAcc1024 = xllvm::VectorBroadcastZeroAcc1024IntrOp::create(
4726 rewriter, loc, VectorType::get({16}, rewriter.getI64Type()));
4727
4728 // Extract lower 8 elements to get vector<8xi64> (512-bit accumulator)
4729 SmallVector<int64_t> extractMask = {0, 1, 2, 3, 4, 5, 6, 7};
4730 auto zeroAcc512 = vector::ShuffleOp::create(rewriter, loc, zeroAcc1024,
4731 zeroAcc1024, extractMask);
4732
4733 // Bitcast back to vector<16xf32> to match the cast result type
4734 auto result = LLVM::BitcastOp::create(
4735 rewriter, loc, VectorType::get({16}, rewriter.getF32Type()),
4736 zeroAcc512);
4737
4738 rewriter.replaceOp(castOp, result);
4739 return success();
4740 }
4741
4742 // Fallback: fold the cast (should not reach here for supported cases)
4743 rewriter.replaceOp(castOp, adaptor.getSource());
4744 return success();
4745 }
4746};
4747
4748// AIE2p version of FoldAIECastOps.
4749// On AIE2P the int/float accumulators share the same physical register, so
4750// aievec.cast between e.g. v32i32 (acc32) and v32f32 (accfloat) is a no-op
4751// at hardware level. LLVM IR still needs a typed value, so emit an
4752// llvm.bitcast when the source/destination LLVM types differ but have the
4753// same bitwidth. Replace with the source value directly when types already
4754// match (the cast becomes a pure type alias).
4756 : public mlir::ConvertOpToLLVMPattern<aievec::CastOp> {
4757 using ConvertOpToLLVMPattern<aievec::CastOp>::ConvertOpToLLVMPattern;
4758
4759 LogicalResult
4760 matchAndRewrite(aievec::CastOp castOp, OpAdaptor adaptor,
4761 ConversionPatternRewriter &rewriter) const override {
4762 Value src = adaptor.getSource();
4763 Type srcTy = src.getType();
4764 Type dstTy = getTypeConverter()->convertType(castOp.getResult().getType());
4765 if (srcTy == dstTy) {
4766 rewriter.replaceOp(castOp, src);
4767 return success();
4768 }
4769 rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(castOp, dstTy, src);
4770 return success();
4771 }
4772};
4773
4775 : public mlir::ConvertOpToLLVMPattern<aievec::ShuffleOp> {
4776 using ConvertOpToLLVMPattern<aievec::ShuffleOp>::ConvertOpToLLVMPattern;
4777
4778 LogicalResult
4779 matchAndRewrite(aievec::ShuffleOp shuffleOp, OpAdaptor adaptor,
4780 ConversionPatternRewriter &rewriter) const override {
4781 if (isAIE2POnlyShuffleMode(shuffleOp.getMode()))
4782 return shuffleOp.emitOpError()
4783 << "shuffle mode '" << stringifyShuffleMode(shuffleOp.getMode())
4784 << "' is not available on AIE2";
4785 auto loc = shuffleOp.getLoc();
4786 auto lhs = adaptor.getLhs();
4787 auto rhs = adaptor.getRhs();
4788 auto i32ty = rewriter.getI32Type();
4789 auto v16xi32ty = VectorType::get({16}, i32ty);
4790 if (!rhs)
4791 rhs = xllvm::UndefV16I32IntrOp::create(rewriter, loc, v16xi32ty);
4792
4793 auto modeAttrVal =
4794 LLVM::ConstantOp::create(rewriter, loc, i32ty,
4795 static_cast<int32_t>(shuffleOp.getMode()))
4796 .getResult();
4797 auto vShuffleVal = xllvm::VectorShuffleIntrOp::create(
4798 rewriter, loc, v16xi32ty,
4799 forceCastOperandsToSignature(
4800 rewriter, loc,
4801 /*operands=*/{lhs, rhs, modeAttrVal},
4802 /*signature=*/{v16xi32ty, v16xi32ty, i32ty}))
4803 .getResult();
4804
4805 vShuffleVal = forceCastValueToType(rewriter, loc, vShuffleVal,
4806 shuffleOp.getResult().getType());
4807
4808 rewriter.replaceOp(shuffleOp, vShuffleVal);
4809
4810 return success();
4811 }
4812};
4813
4814// aievec.shuffle on AIE2P: AIE2P keeps AIE2's mode numbers and adds its own;
4815// the intrinsic is AIE2P's.
4817 : public mlir::ConvertOpToLLVMPattern<aievec::ShuffleOp> {
4818 using ConvertOpToLLVMPattern<aievec::ShuffleOp>::ConvertOpToLLVMPattern;
4819
4820 LogicalResult
4821 matchAndRewrite(aievec::ShuffleOp shuffleOp, OpAdaptor adaptor,
4822 ConversionPatternRewriter &rewriter) const override {
4823 auto loc = shuffleOp.getLoc();
4824 auto i32ty = rewriter.getI32Type();
4825 auto v16xi32ty = VectorType::get({16}, i32ty);
4826 Value lhs = adaptor.getLhs();
4827 Value rhs = adaptor.getRhs();
4828 if (!rhs)
4829 rhs = lhs;
4830 auto mode = LLVM::ConstantOp::create(
4831 rewriter, loc, i32ty, static_cast<int32_t>(shuffleOp.getMode()));
4832 Value v = xllvm::VectorShuffleAIE2pIntrOp::create(
4833 rewriter, loc, v16xi32ty,
4834 forceCastOperandsToSignature(rewriter, loc, {lhs, rhs, mode},
4835 {v16xi32ty, v16xi32ty, i32ty}))
4836 .getResult();
4837 rewriter.replaceOp(shuffleOp,
4838 forceCastValueToType(rewriter, loc, v,
4839 shuffleOp.getResult().getType()));
4840 return success();
4841 }
4842};
4843
4844// Convert aievec.inv to xllvm.intr.aie2p.inv intrinsic for AIE2P
4845// Scalar f32: direct conversion to xllvm.intr.aie2p.inv
4846// Vector f32: unroll into scalar xllvm.intr.aie2p.inv operations
4848 : public mlir::ConvertOpToLLVMPattern<aievec::InvOp> {
4849public:
4850 using ConvertOpToLLVMPattern<aievec::InvOp>::ConvertOpToLLVMPattern;
4851
4852 LogicalResult
4853 matchAndRewrite(aievec::InvOp invOp, OpAdaptor adaptor,
4854 ConversionPatternRewriter &rewriter) const override {
4855 auto loc = invOp.getLoc();
4856 auto operandType = adaptor.getSource().getType();
4857
4858 // Handle scalar f32 inverse
4859 if (operandType.isF32()) {
4860 auto invResult = xllvm::InvAIE2pIntrOp::create(
4861 rewriter, loc, rewriter.getF32Type(), adaptor.getSource());
4862 rewriter.replaceOp(invOp, invResult);
4863 return success();
4864 }
4865
4866 // Handle vector<N x f32> inverse
4867 auto vecType = dyn_cast<VectorType>(operandType);
4868 if (!vecType || !vecType.getElementType().isF32())
4869 return failure();
4870
4871 // Unroll vector inverse into scalar operations
4872 int numElements = getVectorLaneSize(vecType);
4873 Value result = LLVM::PoisonOp::create(rewriter, loc, vecType);
4874
4875 for (int i = 0; i < numElements; ++i) {
4876 // Extract element i
4877 auto indexCst = LLVM::ConstantOp::create(
4878 rewriter, loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(i));
4879 auto extractedElem = LLVM::ExtractElementOp::create(
4880 rewriter, loc, adaptor.getSource(), indexCst);
4881
4882 // Call xllvm.intr.aie2p.inv on the scalar
4883 auto invResult = xllvm::InvAIE2pIntrOp::create(
4884 rewriter, loc, rewriter.getF32Type(), extractedElem);
4885
4886 // Insert result back into vector
4887 result = LLVM::InsertElementOp::create(rewriter, loc, vecType, result,
4888 invResult, indexCst);
4889 }
4890
4891 rewriter.replaceOp(invOp, result);
4892 return success();
4893 }
4894};
4895
4896// Apply `IntrOpTy`, an AIE2P intrinsic from v16 accfloat to v16 bf16 such as
4897// exp2 or tanh, to a 16- or 32-lane bf16 or f32 vector. A 32-lane vector is
4898// processed as two 16-lane halves. bf16 lanes are widened to accfloat before
4899// the intrinsic, and the bf16 results are widened again when `resultElemType`
4900// is f32.
4901template <typename IntrOpTy>
4902static Value applyAccFloatUnaryIntrinsic(ConversionPatternRewriter &rewriter,
4903 Location loc, Value source,
4904 Type resultElemType) {
4905 auto srcType = cast<VectorType>(source.getType());
4906 unsigned laneSize = getVectorLaneSize(srcType);
4907 auto v16bf16Ty = VectorType::get({16}, rewriter.getBF16Type());
4908 auto v16f32Ty = VectorType::get({16}, rewriter.getF32Type());
4909 auto toF32 = [&](Value v16bf16) -> Value {
4910 return xllvm::Vector16BF16ToV16AccFloatAIE2pIntrOp::create(
4911 rewriter, loc, v16f32Ty, v16bf16);
4912 };
4913
4914 SmallVector<Value, 2> parts;
4915 if (laneSize == 16) {
4916 parts.push_back(source);
4917 } else {
4918 for (int64_t half = 0; half < 2; ++half) {
4919 SmallVector<int64_t> mask =
4920 llvm::to_vector(llvm::seq<int64_t>(16 * half, 16 * (half + 1)));
4921 parts.push_back(
4922 vector::ShuffleOp::create(rewriter, loc, source, source, mask));
4923 }
4924 }
4925 if (srcType.getElementType().isBF16())
4926 for (Value &part : parts)
4927 part = toF32(part);
4928 for (Value &part : parts)
4929 part = IntrOpTy::create(rewriter, loc, v16bf16Ty, part);
4930 if (resultElemType.isF32())
4931 for (Value &part : parts)
4932 part = toF32(part);
4933
4934 if (parts.size() == 1)
4935 return parts.front();
4936 SmallVector<int64_t> combineMask = llvm::to_vector(llvm::seq<int64_t>(0, 32));
4937 return vector::ShuffleOp::create(rewriter, loc, parts[0], parts[1],
4938 combineMask);
4939}
4940
4941// Convert aievec.exp to xllvm.exp2 intrinsic for AIE2P
4942// Uses the identity: exp(x) = exp2(x * log2(e))
4943// Supports both lane-16 and lane-32 bf16 vectors
4945 : public mlir::ConvertOpToLLVMPattern<aievec::ExpOp> {
4946public:
4947 using ConvertOpToLLVMPattern<aievec::ExpOp>::ConvertOpToLLVMPattern;
4948
4949 LogicalResult
4950 matchAndRewrite(aievec::ExpOp expOp, OpAdaptor adaptor,
4951 ConversionPatternRewriter &rewriter) const override {
4952 auto loc = expOp.getLoc();
4953 auto srcType = cast<VectorType>(adaptor.getSource().getType());
4954 auto srcElemType = srcType.getElementType();
4955 unsigned laneSize = getVectorLaneSize(srcType);
4956
4957 // Support v16bfloat16 and v32bfloat16
4958 if ((laneSize != 16 && laneSize != 32) || !srcElemType.isBF16())
4959 return expOp.emitWarning()
4960 << "aievec.exp conversion only supports v16bfloat16 and "
4961 "v32bfloat16.\n";
4962
4963 // Step 1: Create bf16 constant for log2(e) ≈ 1.442695
4964 auto log2eBF16Const = LLVM::ConstantOp::create(
4965 rewriter, loc, rewriter.getBF16Type(),
4966 rewriter.getFloatAttr(rewriter.getBF16Type(), 1.442695));
4967
4968 // Broadcast log2(e) to match input lane size
4969 SmallVector<int64_t> broadcastMask;
4970 for (unsigned i = 0; i < laneSize; ++i)
4971 broadcastMask.push_back(0);
4972
4973 auto v1bf16 = LLVM::UndefOp::create(
4974 rewriter, loc, VectorType::get({1}, rewriter.getBF16Type()));
4975 auto v1bf16Inserted = LLVM::InsertElementOp::create(
4976 rewriter, loc, v1bf16, log2eBF16Const,
4977 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI32Type(), 0));
4978
4979 auto log2eVec = vector::ShuffleOp::create(rewriter, loc, v1bf16Inserted,
4980 v1bf16Inserted, broadcastMask);
4981
4982 // Step 2: Multiply input by log2(e) in bf16 domain using MulElemOp
4983 // For lane-16: uses I512.I512.ACC512
4984 // For lane-32: uses I512.I512.ACC1024
4985 auto resultF32Ty =
4986 VectorType::get({(int64_t)laneSize}, rewriter.getF32Type());
4987 auto mulResult = aievec::MulElemOp::create(rewriter, loc, resultF32Ty,
4988 adaptor.getSource(), log2eVec);
4989
4990 // Step 3: exp2 of the product
4991 Value exp2Result = applyAccFloatUnaryIntrinsic<xllvm::Exp2AIE2pIntrOp>(
4992 rewriter, loc, mulResult, rewriter.getBF16Type());
4993
4994 rewriter.replaceOp(expOp, exp2Result);
4995
4996 return success();
4997 }
4998};
4999
5000// Convert aievec.tanh or aievec.exp2 to the matching xllvm intrinsic for
5001// AIE2P. Supports lane-16 and lane-32 bf16 and f32 vectors.
5002template <typename SrcOpTy, typename IntrOpTy>
5004 : public mlir::ConvertOpToLLVMPattern<SrcOpTy> {
5005public:
5006 using ConvertOpToLLVMPattern<SrcOpTy>::ConvertOpToLLVMPattern;
5007 using OpAdaptor = typename SrcOpTy::Adaptor;
5008
5009 LogicalResult
5010 matchAndRewrite(SrcOpTy op, OpAdaptor adaptor,
5011 ConversionPatternRewriter &rewriter) const override {
5012 auto srcType = cast<VectorType>(adaptor.getSource().getType());
5013 Type srcElemType = srcType.getElementType();
5014 unsigned laneSize = getVectorLaneSize(srcType);
5015
5016 if ((laneSize != 16 && laneSize != 32) ||
5017 (!srcElemType.isBF16() && !srcElemType.isF32()))
5018 return op.emitWarning()
5019 << op->getName()
5020 << " conversion only supports v16 and v32 of bfloat16 or float.\n";
5021
5022 rewriter.replaceOp(
5023 op, applyAccFloatUnaryIntrinsic<IntrOpTy>(
5024 rewriter, op.getLoc(), adaptor.getSource(), srcElemType));
5025 return success();
5026 }
5027};
5028
5029// Convert math.rsqrt (scalar f32 or vector f32) to xllvm.intr.aie2p.invsqrt
5030// Scalar f32: direct conversion to xllvm.intr.aie2p.invsqrt
5031// Vector f32: unroll into scalar xllvm.intr.aie2p.invsqrt operations
5033 : public mlir::ConvertOpToLLVMPattern<math::RsqrtOp> {
5034public:
5035 using ConvertOpToLLVMPattern<math::RsqrtOp>::ConvertOpToLLVMPattern;
5036
5037 LogicalResult
5038 matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor,
5039 ConversionPatternRewriter &rewriter) const override {
5040 auto loc = rsqrtOp.getLoc();
5041 auto operandType = adaptor.getOperand().getType();
5042
5043 // Handle scalar f32 rsqrt
5044 if (operandType.isF32()) {
5045 auto rsqrtResult = xllvm::InvsqrtAIE2pIntrOp::create(
5046 rewriter, loc, rewriter.getF32Type(), adaptor.getOperand());
5047 rewriter.replaceOp(rsqrtOp, rsqrtResult);
5048 return success();
5049 }
5050
5051 // Handle vector<N x f32> rsqrt
5052 auto vecType = dyn_cast<VectorType>(operandType);
5053 if (!vecType || !vecType.getElementType().isF32())
5054 return failure();
5055
5056 // Unroll vector rsqrt into scalar operations
5057 int numElements = getVectorLaneSize(vecType);
5058 Value result = LLVM::PoisonOp::create(rewriter, loc, vecType);
5059
5060 for (int i = 0; i < numElements; ++i) {
5061 // Extract element i
5062 auto indexCst = LLVM::ConstantOp::create(
5063 rewriter, loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(i));
5064 auto extractedElem = LLVM::ExtractElementOp::create(
5065 rewriter, loc, adaptor.getOperand(), indexCst);
5066
5067 // Call xllvm.intr.aie2p.invsqrt on the scalar
5068 auto rsqrtResult = xllvm::InvsqrtAIE2pIntrOp::create(
5069 rewriter, loc, rewriter.getF32Type(), extractedElem);
5070
5071 // Insert result back into vector
5072 result = LLVM::InsertElementOp::create(rewriter, loc, vecType, result,
5073 rsqrtResult, indexCst);
5074 }
5075
5076 rewriter.replaceOp(rsqrtOp, result);
5077 return success();
5078 }
5079};
5080
5081// A one-element vector built from a scalar is lowered to llvm.bitcast. As an
5082// insertelement, LLVM folds it with the extract of a lane into a shufflevector
5083// with a one-element result, which Peano's AIE combines do not handle.
5084static bool isOneLaneVectorOfScalar(Type resultType, Type sourceType) {
5085 auto vecType = dyn_cast<VectorType>(resultType);
5086 return vecType && !vecType.isScalable() && vecType.getRank() == 1 &&
5087 vecType.getNumElements() == 1 && !isa<VectorType>(sourceType);
5088}
5089
5090static bool isOneLaneBroadcastOfScalar(vector::BroadcastOp op) {
5091 return isOneLaneVectorOfScalar(op.getType(), op.getSourceType());
5092}
5093
5094static bool isOneLaneInsertOfScalar(vector::InsertOp op) {
5095 return isOneLaneVectorOfScalar(op.getType(),
5096 op.getValueToStore().getType()) &&
5097 !op.hasDynamicPosition();
5098}
5099
5101 : public mlir::ConvertOpToLLVMPattern<vector::BroadcastOp> {
5102public:
5103 using ConvertOpToLLVMPattern<vector::BroadcastOp>::ConvertOpToLLVMPattern;
5104
5105 LogicalResult
5106 matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor,
5107 ConversionPatternRewriter &rewriter) const override {
5108 if (!isOneLaneBroadcastOfScalar(bcastOp))
5109 return failure();
5110 rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(bcastOp, bcastOp.getType(),
5111 adaptor.getSource());
5112 return success();
5113 }
5114};
5115
5117 : public mlir::ConvertOpToLLVMPattern<vector::InsertOp> {
5118public:
5119 using ConvertOpToLLVMPattern<vector::InsertOp>::ConvertOpToLLVMPattern;
5120
5121 LogicalResult
5122 matchAndRewrite(vector::InsertOp insertOp, OpAdaptor adaptor,
5123 ConversionPatternRewriter &rewriter) const override {
5124 if (!isOneLaneInsertOfScalar(insertOp))
5125 return failure();
5126 rewriter.replaceOpWithNewOp<LLVM::BitcastOp>(insertOp, insertOp.getType(),
5127 adaptor.getValueToStore());
5128 return success();
5129 }
5130};
5131
5132// Convert arith.divf for vector<N x f32> to unrolled scalar divisions
5133// Uses a noinline helper function call as a barrier to prevent LLVM
5134// re-vectorization. Scalar f32 divisions are handled by downstream passes.
5135class FdivOpConversion : public mlir::ConvertOpToLLVMPattern<arith::DivFOp> {
5136public:
5137 using ConvertOpToLLVMPattern<arith::DivFOp>::ConvertOpToLLVMPattern;
5138
5139 FdivOpConversion(const LLVMTypeConverter &typeConverter, StringRef device)
5140 : ConvertOpToLLVMPattern(typeConverter), deviceName(device.str()) {}
5141
5142 std::string deviceName;
5143
5144 LogicalResult
5145 matchAndRewrite(arith::DivFOp divOp, OpAdaptor adaptor,
5146 ConversionPatternRewriter &rewriter) const override {
5147 auto loc = divOp.getLoc();
5148 auto lhsType = adaptor.getLhs().getType();
5149
5150 // Only handle vector<N x f32> fdiv
5151 // Scalar f32 fdiv is handled by downstream passes
5152 auto vecType = dyn_cast<VectorType>(lhsType);
5153 if (!vecType || !vecType.getElementType().isF32())
5154 return failure();
5155
5156 auto rhsType = adaptor.getRhs().getType();
5157 auto rhsVecType = dyn_cast<VectorType>(rhsType);
5158 if (!rhsVecType || rhsVecType != vecType)
5159 return failure();
5160
5161 // For AIE2P, implement a/b as a * inv(b) using the hardware reciprocal
5162 // intrinsic. For AIE2, use scalar fdiv helper function.
5163 auto module = divOp->getParentOfType<ModuleOp>();
5164 auto f32Ty = rewriter.getF32Type();
5165
5166 // Select device-specific body for the scalar helper function.
5167 // AIE2P: use inv(b) * a (hardware reciprocal intrinsic is reliable).
5168 // AIE2: use scalar fdiv directly.
5169 std::function<void(OpBuilder &, Location, ValueRange)> bodyBuilder;
5170 if (deviceName == "aie2p") {
5171 bodyBuilder = [](OpBuilder &builder, Location loc, ValueRange args) {
5172 auto invResult = xllvm::InvAIE2pIntrOp::create(
5173 builder, loc, builder.getF32Type(), args[1]);
5174 auto mulResult =
5175 arith::MulFOp::create(builder, loc, args[0], invResult);
5176 LLVM::ReturnOp::create(builder, loc, ValueRange{mulResult});
5177 };
5178 } else {
5179 bodyBuilder = [](OpBuilder &builder, Location loc, ValueRange args) {
5180 auto divResult = arith::DivFOp::create(builder, loc, args[0], args[1]);
5181 LLVM::ReturnOp::create(builder, loc, ValueRange{divResult});
5182 };
5183 }
5184
5185 // Get or create the noinline scalar helper (acts as barrier to prevent
5186 // LLVM from re-vectorizing the scalar ops).
5187 auto helperFunc =
5188 getOrCreateScalarHelperFunc(module, rewriter, "fdiv", deviceName,
5189 /*argTypes=*/{f32Ty, f32Ty},
5190 /*resultType=*/f32Ty, bodyBuilder);
5191
5192 // Unroll vector fdiv into scalar helper function calls.
5193 int numElements = getVectorLaneSize(vecType);
5194 Value result = LLVM::PoisonOp::create(rewriter, loc, vecType);
5195
5196 for (int i = 0; i < numElements; ++i) {
5197 auto indexCst = LLVM::ConstantOp::create(
5198 rewriter, loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(i));
5199 auto lhsElem = LLVM::ExtractElementOp::create(rewriter, loc,
5200 adaptor.getLhs(), indexCst);
5201 auto rhsElem = LLVM::ExtractElementOp::create(rewriter, loc,
5202 adaptor.getRhs(), indexCst);
5203
5204 auto divResult = LLVM::CallOp::create(rewriter, loc, helperFunc,
5205 ValueRange{lhsElem, rhsElem})
5206 ->getResult(0);
5207
5208 result = LLVM::InsertElementOp::create(rewriter, loc, vecType, result,
5209 divResult, indexCst);
5210 }
5211
5212 rewriter.replaceOp(divOp, result);
5213 return success();
5214 }
5215};
5216
5218 mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns) {
5219 // clang-format off
5220 // Patterns that work for all backends (AIE1, AIE2, AIE2p)
5221 patterns.add<AddOpConversion,
5232 ShuffleOpConversion>(converter);
5233 // clang-format on
5234}
5235
5236static void populateAIEVecToLLVMAIE2ConversionPatterns(
5237 mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns,
5238 Aie2Fp32Emulation aie2Fp32EmulationOption) {
5239 // Patterns specific to AIE2 backend
5240 patterns.add<AddElemOpAIE2Conversion, SubElemOpAIE2Conversion>(converter);
5241 patterns.add<NegOpAIE2Conversion>(converter);
5242 patterns.add<MulElemOpConversion>(converter, aie2Fp32EmulationOption);
5243 patterns.add<UPSOpAIE2Conversion, SRSOpAIE2Conversion>(converter);
5244 patterns.add<ShiftOpConversion>(converter);
5245 patterns.add<MaxOpConversion, MinOpConversion>(converter);
5246 patterns.add<CmpOpConversion, SelOpConversion>(converter);
5247 patterns.add<ExtOpConversion>(converter);
5248 patterns.add<ExtractElemOpConversion>(converter);
5249 patterns.add<ConcatOpConversion>(converter);
5250 patterns.add<FMAElemOpConversion>(converter);
5251 patterns.add<FoldAIECastOps>(converter);
5252 patterns.add<FdivOpConversion>(converter, "aie2");
5253 patterns.add<OneLaneBroadcastOpConversion, OneLaneInsertOpConversion>(
5254 converter);
5255}
5256
5257// AIE2p version of ExtractElemOp conversion using LLVM extractelement
5259 : public mlir::ConvertOpToLLVMPattern<aievec::ExtElemOp> {
5260public:
5261 using ConvertOpToLLVMPattern<aievec::ExtElemOp>::ConvertOpToLLVMPattern;
5262
5263 LogicalResult
5264 matchAndRewrite(aievec::ExtElemOp op, OpAdaptor adaptor,
5265 ConversionPatternRewriter &rewriter) const override {
5266 Location loc = op.getLoc();
5267
5268 // AIE2p doesn't have dedicated vextract intrinsics, so use LLVM
5269 // extractelement
5270 Value extracted = LLVM::ExtractElementOp::create(
5271 rewriter, loc, adaptor.getSource(), adaptor.getIndex());
5272
5273 rewriter.replaceOp(op, extracted);
5274 return success();
5275 }
5276};
5277
5278// AIE2p version of ConcatOp conversion using vector.shuffle
5280 : public mlir::ConvertOpToLLVMPattern<aievec::ConcatOp> {
5281public:
5282 using ConvertOpToLLVMPattern<aievec::ConcatOp>::ConvertOpToLLVMPattern;
5283
5284 // AIE2p has no concat intrinsic: the sources are joined two at a time with
5285 // vector.shuffle. When a round has an odd number, the last one is first
5286 // widened with poison to the width of the others.
5287 LogicalResult
5288 matchAndRewrite(aievec::ConcatOp op, OpAdaptor adaptor,
5289 ConversionPatternRewriter &rewriter) const override {
5290 Location loc = op.getLoc();
5291 SmallVector<Value> parts(adaptor.getSources());
5292 if (parts.empty()) {
5293 op.emitWarning() << "aievec.concat with no sources is not supported.\n";
5294 return failure();
5295 }
5296 auto lanesOf = [](Value v) {
5297 return cast<VectorType>(v.getType()).getNumElements();
5298 };
5299 while (parts.size() > 1) {
5300 SmallVector<Value> next;
5301 for (size_t i = 0; i + 1 < parts.size(); i += 2) {
5302 Value lhs = parts[i], rhs = parts[i + 1];
5303 int64_t lhsLanes = lanesOf(lhs), rhsLanes = lanesOf(rhs);
5304 if (rhsLanes < lhsLanes) {
5305 SmallVector<int64_t> padMask(lhsLanes, -1);
5306 std::iota(padMask.begin(), padMask.begin() + rhsLanes, 0);
5307 rhs = vector::ShuffleOp::create(rewriter, loc, rhs, rhs, padMask);
5308 }
5309 SmallVector<int64_t> mask(lhsLanes + rhsLanes);
5310 std::iota(mask.begin(), mask.end(), 0);
5311 next.push_back(
5312 vector::ShuffleOp::create(rewriter, loc, lhs, rhs, mask));
5313 }
5314 if (parts.size() % 2)
5315 next.push_back(parts.back());
5316 parts = std::move(next);
5317 }
5318 rewriter.replaceOp(op, parts.front());
5319 return success();
5320 }
5321};
5322
5324 mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns) {
5325 // Takes precedence over the common pattern, which emits the AIE2 intrinsic.
5326 patterns.add<ShuffleOpAIE2pConversion>(converter, /*benefit=*/2);
5327 // Patterns specific to AIE2p backend
5328 patterns.add<AddElemOpAIE2pConversion, SubElemOpAIE2pConversion>(converter);
5329 patterns.add<NegOpAIE2pConversion>(converter);
5330 patterns.add<MulElemOpAIE2pConversion>(converter);
5331 patterns.add<FMAElemOpAIE2pConversion>(converter);
5332 patterns.add<UPSOpAIE2pConversion, SRSOpAIE2pConversion>(converter);
5333 patterns.add<MatMulOpAIE2pConversion>(converter);
5334 patterns.add<ShiftOpAIE2pConversion>(converter);
5335 patterns.add<MaxOpAIE2pConversion, MinOpAIE2pConversion>(converter);
5336 patterns.add<CmpOpAIE2pConversion, SelOpAIE2pConversion>(converter);
5337 patterns.add<ExtOpAIE2pConversion>(converter);
5338 patterns.add<ExtractElemOpAIE2pConversion>(converter);
5339 patterns.add<ConcatOpAIE2pConversion>(converter);
5340 // Higher benefit so it matches before the generic stub UnpackOpConversion
5341 // returns failure for i4->i8 inputs.
5342 patterns.add<UnpackI4OpAIE2pConversion>(converter, /*benefit=*/2);
5343 patterns.add<ExpOpAIE2pConversion>(converter);
5344 patterns.add<
5347 converter);
5348 patterns.add<InvOpAIE2pConversion>(converter);
5349 patterns.add<BroadcastScalarOpAIE2pConversion>(converter);
5350 patterns.add<RsqrtOpAIE2pConversion>(converter);
5351 patterns.add<FdivOpConversion>(converter, "aie2p");
5352 patterns.add<FoldAIECastOpsAIE2p>(converter);
5354 converter);
5355}
5356
5358 mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns,
5359 Aie2Fp32Emulation aie2Fp32EmulationOption, StringRef aieTarget) {
5361 if (aieTarget == "aie2p")
5363 else
5365 aie2Fp32EmulationOption);
5366}
5367
5368// Configure legalization rules shared by AIE2 and AIE2p
5369static void configureAIEVecToLLVMLegalizations(LLVMConversionTarget &target) {
5370 // Vector f32 divf is illegal (needs unrolling to scalar divf)
5371 // Scalar f32 divf is legal (handled by downstream passes)
5372 target.addDynamicallyLegalOp<arith::DivFOp>([](arith::DivFOp divOp) {
5373 auto resultType = divOp.getType();
5374 if (auto vecType = dyn_cast<VectorType>(resultType)) {
5375 // Vector f32 divf is illegal and needs conversion
5376 return !vecType.getElementType().isF32();
5377 }
5378 // Scalar divf is legal
5379 return true;
5380 });
5381
5382 // One-element vectors built from a scalar are illegal (lowered to bitcast)
5383 target.addDynamicallyLegalOp<vector::BroadcastOp>(
5384 [](vector::BroadcastOp op) { return !isOneLaneBroadcastOfScalar(op); });
5385 target.addDynamicallyLegalOp<vector::InsertOp>(
5386 [](vector::InsertOp op) { return !isOneLaneInsertOfScalar(op); });
5387}
5388
5390 : xilinx::impl::ConvertAIEVecToLLVMBase<ConvertAIEVecToLLVMPass> {
5392 ConvertAIEVecToLLVMPass(const xilinx::ConvertAIEVecToLLVMOptions &options) {
5393 aieTarget = options.aieTarget;
5394 aie2Fp32Emulation = options.aie2Fp32Emulation;
5395 }
5396
5397 void runOnOperation() override {
5398 RewritePatternSet patterns(&getContext());
5399 LLVMTypeConverter converter(&getContext());
5400
5401 // Don't convert vector types, we want to handle multi-dimensional
5402 // vector on our own.
5403 converter.addConversion(
5404 [&](VectorType type) -> std::optional<Type> { return type; });
5405
5406 populateAIEVecToLLVMConversionPatterns(converter, patterns,
5407 aie2Fp32Emulation, aieTarget);
5408
5409 LLVMConversionTarget target(getContext());
5410 target.addIllegalDialect<xilinx::aievec::AIEVecDialect,
5411 xilinx::aievec::aie1::AIEVecAIE1Dialect>();
5412 target.addLegalDialect<arith::ArithDialect, vector::VectorDialect,
5413 xilinx::xllvm::XLLVMDialect, ub::UBDialect>();
5414
5415 // Configure legalizations for AIE2/AIE2p
5416 configureAIEVecToLLVMLegalizations(target);
5417
5418 if (failed(applyPartialConversion(getOperation(), target,
5419 std::move(patterns))))
5420 signalPassFailure();
5421 }
5422};
5423
5424std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
5426 return std::make_unique<ConvertAIEVecToLLVMPass>();
5427}
5428
5429std::unique_ptr<mlir::OperationPass<mlir::ModuleOp>>
5431 const xilinx::ConvertAIEVecToLLVMOptions &options) {
5432 return std::make_unique<ConvertAIEVecToLLVMPass>(options);
5433}
5434
5435} // namespace xilinx::aievec
LogicalResult matchAndRewrite(SrcOpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(SrcOpTy op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static DecodedAddElemOp decodeAddElemOp(OpAdaptor op)
LogicalResult matchAndRewrite(aievec::AddElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::aie1::AddOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::BroadcastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::BroadcastScalarOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::BroadcastScalarOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::CmpOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ConcatOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ConcatOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExpOp expOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExtOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExtOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExtElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ExtElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::FMAElemOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::FMAElemOp fmaOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::aie1::FMAOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(arith::DivFOp divOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
FdivOpConversion(const LLVMTypeConverter &typeConverter, StringRef device)
LogicalResult matchAndRewrite(aievec::InvOp invOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::MaxOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::MaxOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::MinOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::MinOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static DecodedMulElemOp decodeMulElemOp(OpAdaptor op)
LogicalResult matchAndRewrite(aievec::MulElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult convertToEmulatedFP32MulElem(aievec::MulElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const
LogicalResult convertToEmulatedI32MulElem(aievec::MulElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const
MulElemOpConversion(const LLVMTypeConverter &typeConverter, Aie2Fp32Emulation aie2Fp32EmulationOption)
LogicalResult matchAndRewrite(aievec::MulElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static DecodedMulElemOp decodeMulElemOp(OpAdaptor op)
LogicalResult matchAndRewrite(aievec::aie1::MulOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::NegOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::NegOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::BroadcastOp bcastOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(vector::InsertOp insertOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::PackOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static std::string getIntrinsicName(aievec::PackOp op)
LogicalResult matchAndRewrite(math::RsqrtOp rsqrtOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::SRSOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::SRSOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::SelOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static std::string getIntrinsicName(aievec::aie1::SelectOp op)
LogicalResult matchAndRewrite(aievec::aie1::SelectOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ShiftOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::ShiftOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::SubElemOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static DecodedSubElemOp decodeSubElemOp(OpAdaptor op)
LogicalResult matchAndRewrite(aievec::aie1::SubOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
static std::string getIntrinsicName(aievec::UPDOp op, int loadSize)
LogicalResult matchAndRewrite(aievec::UPDOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::UPSOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::UPSOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::UnpackOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(aievec::UnpackOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
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)
int32_t getVectorSizeInBits(mlir::VectorType type)
Definition AIEVecUtils.h:65
unsigned getVectorLaneSize(mlir::VectorType type)
Definition AIEVecUtils.h:54
bool isAIE2POnlyShuffleMode(ShuffleMode mode)
void populateAIEVecToLLVMCommonConversionPatterns(mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns)
SelOpConversionBase< xllvm::VectorSel16AIE2pIntrOp, xllvm::VectorSel32AIE2pIntrOp > SelOpAIE2pConversion
AccFloatAddSubAIE2pConversion< aievec::AddElemOp, xllvm::AddACC2048AccFloatAIE2pIntrOp > AddElemOpAIE2pConversion
CmpOpConversionBase< xllvm::VectorMaxLtBf16AIE2pIntrOp, xllvm::VectorMinGeBf16AIE2pIntrOp, xllvm::VectorMaxLt32AIE2pIntrOp, xllvm::VectorMinGe32AIE2pIntrOp, xllvm::VectorMaxLt16AIE2pIntrOp, xllvm::VectorMinGe16AIE2pIntrOp > CmpOpAIE2pConversion
void populateAIEVecToLLVMAIE2ConversionPatterns(mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns)
void populateAIEVecToLLVMAIE2pConversionPatterns(mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns)
SelOpConversionBase< xllvm::VectorSel16IntrOp, xllvm::VectorSel32IntrOp > SelOpConversion
CmpOpConversionBase< xllvm::VectorMaxLtBf16IntrOp, xllvm::VectorMinGeBf16IntrOp, xllvm::VectorMaxLt32IntrOp, xllvm::VectorMinGe32IntrOp, xllvm::VectorMaxLt16IntrOp, xllvm::VectorMinGe16IntrOp > CmpOpConversion
AccFloatAddSubAIE2pConversion< aievec::SubElemOp, xllvm::SubACC2048AccFloatAIE2pIntrOp > SubElemOpAIE2pConversion
std::unique_ptr< mlir::OperationPass< mlir::ModuleOp > > createConvertAIEVecToLLVMPass()
void populateAIEVecToLLVMConversionPatterns(mlir::LLVMTypeConverter &converter, mlir::RewritePatternSet &patterns, Aie2Fp32Emulation aie2Fp32EmulationOption, llvm::StringRef aieTarget)
ConvertAIEVecToLLVMPass(const xilinx::ConvertAIEVecToLLVMOptions &options)