MLIR-AIE 1.4.5.dev
AIEVecOps.cpp
Go to the documentation of this file.
1//===---- AIEVecOps.cpp - MLIR AIE Vector Dialect Operations ----*- C++ -*-===//
2//
3// Copyright (C) 2022-2024 Advanced Micro Devices, Inc.
4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5//
6//===----------------------------------------------------------------------===//
7// This file implements AIE vector op printing, pasing, and verification.
8//===----------------------------------------------------------------------===//
9
10#include "mlir/Dialect/Arith/IR/Arith.h"
11#include "mlir/Dialect/LLVMIR/LLVMTypes.h"
12#include "mlir/IR/DialectImplementation.h"
13#include "mlir/IR/OpDefinition.h"
14#include "mlir/IR/TypeUtilities.h"
15#include "mlir/Transforms/FoldUtils.h"
16#include "llvm/ADT/TypeSwitch.h"
17
23
24using namespace llvm;
25using namespace mlir;
26using namespace xilinx;
27using namespace xilinx::aievec;
28
29#include "aie/Dialect/AIEVec/IR/AIEVecEnums.cpp.inc"
30#include "aie/Dialect/AIEVec/IR/AIEVecOpsDialect.cpp.inc"
31
32//===----------------------------------------------------------------------===//
33// AIEVecDialect
34//===----------------------------------------------------------------------===//
35
36void AIEVecDialect::initialize() {
37 registerTypes();
38 addAttributes<
39#define GET_ATTRDEF_LIST
40#include "aie/Dialect/AIEVec/IR/AIEVecAttributes.cpp.inc"
41 >();
42 addOperations<
43#define GET_OP_LIST
44#include "aie/Dialect/AIEVec/IR/AIEVecOps.cpp.inc"
45 >();
46}
47
48//===----------------------------------------------------------------------===//
49// UPDOp
50//===----------------------------------------------------------------------===//
51
52// Print out UPD op.
53void UPDOp::print(OpAsmPrinter &p) {
54 // Print the source memref
55 p << " " << getSource() << "[" << getIndices() << "]";
56 // Now print the optional vector that links upd idx=1 with idx=0
57 if (getVector())
58 p << ", " << getVector();
59
60 // Print the attributes, but don't print the operand segment sizes
61 SmallVector<StringRef, 3> elidedAttrs;
62 elidedAttrs.push_back(UPDOp::getOperandSegmentSizeAttr());
63 p.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs);
64
65 // And now print the types
66 p << " : " << getSource().getType() << ", " << getResult().getType();
67}
68
69// Verify UPD op.
70LogicalResult UPDOp::verify() {
71 // Verify the types: source is memref, and result is vector
72 MemRefType sourceType = llvm::dyn_cast<MemRefType>(getSource().getType());
73 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
74 if (!sourceType)
75 return emitError("requires memref type");
76 if (!resultType)
77 return emitError("requires vector type");
78 if (getIndices().empty())
79 return emitError("upd source cannot come from scalar value");
80
81 // If this UPD op is linked to another UPD op, then verify that the linked
82 // vector and the result vector match.
83 if (getVector()) {
84 Type vecType = llvm::dyn_cast<VectorType>(getVector().getType());
85 if (vecType != resultType)
86 return emitError("result types of linked UPD ops do not match");
87 }
88 return success();
89}
90
91// Parse UPD op.
92ParseResult UPDOp::parse(OpAsmParser &parser, OperationState &result) {
93 auto &builder = parser.getBuilder();
94 llvm::SMLoc typesLoc;
95 SmallVector<Type, 2> types;
96 OpAsmParser::UnresolvedOperand source, vector;
97 SmallVector<OpAsmParser::UnresolvedOperand, 8> indices;
98
99 // Parse the source, indices, and optional vector
100 if (parser.parseOperand(source) ||
101 parser.parseOperandList(indices, OpAsmParser::Delimiter::Square))
102 return failure();
103 ParseResult hasVector = parser.parseOptionalComma();
104 if (hasVector.succeeded() && parser.parseOperand(vector))
105 return failure();
106
107 // Parse all the attributes and types
108 if (parser.parseOptionalAttrDict(result.attributes) ||
109 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
110 return failure();
111
112 if (result.attributes.getAttrs().size() != 2)
113 return parser.emitError(typesLoc, "requires two attributes");
114
115 // Assert that there are two types (memref source and vector result)
116 if (types.size() != 2)
117 return parser.emitError(typesLoc, "requires two types");
118
119 // Some verification
120 auto memrefType = llvm::dyn_cast<MemRefType>(types[0]);
121 if (!memrefType)
122 return parser.emitError(typesLoc, "requires memref type");
123 VectorType vectorType = llvm::dyn_cast<VectorType>(types[1]);
124 if (!vectorType)
125 return parser.emitError(typesLoc, "requires vector type");
126 auto indicesType = builder.getIndexType();
127
128 // Populate the source and indices in result
129 if (parser.resolveOperand(source, memrefType, result.operands) ||
130 parser.resolveOperands(indices, indicesType, result.operands))
131 return failure();
132 // Populate optional vector in result
133 if (hasVector.succeeded())
134 if (parser.resolveOperand(vector, vectorType, result.operands))
135 return failure();
136
137 // Populate operand size attribute in result
138 result.addAttribute(UPDOp::getOperandSegmentSizeAttr(),
139 builder.getDenseI32ArrayAttr(
140 {1, static_cast<int32_t>(indices.size()),
141 static_cast<int32_t>(hasVector.succeeded())}));
142
143 return parser.addTypeToList(vectorType, result.types);
144}
145
146//===----------------------------------------------------------------------===//
147// CastOp
148//===----------------------------------------------------------------------===//
149
150// Print out Cast op.
151void CastOp::print(OpAsmPrinter &p) {
152 // Print the source accumulator
153 p << " " << getSource();
154
155 // Print the attributes
156 p.printOptionalAttrDict((*this)->getAttrs());
157
158 // And now print the types
159 p << " : " << getSource().getType() << ", " << getResult().getType();
160}
161
162// Verify Cast op.
163LogicalResult CastOp::verify() {
164 // Verify the types
165 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
166 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
167 if (!sourceType)
168 return emitError("requires source vector type");
169 if (!resultType)
170 return emitError("requires result vector type");
171
172 if (sourceType.getElementType().getIntOrFloatBitWidth() !=
173 resultType.getElementType().getIntOrFloatBitWidth()) {
174 return emitError("the bitwidth of resource and result should be equal");
175 }
176
177 return success();
178}
179
180// Parse Cast op.
181ParseResult CastOp::parse(OpAsmParser &parser, OperationState &result) {
182 llvm::SMLoc typesLoc;
183 SmallVector<Type, 2> types;
184 OpAsmParser::UnresolvedOperand source;
185
186 // Parse the source vector
187 if (parser.parseOperand(source))
188 return failure();
189
190 // Parse all the attributes and types
191 if (parser.parseOptionalAttrDict(result.attributes) ||
192 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
193 return failure();
194
195 if (result.attributes.getAttrs().size() != 1)
196 return parser.emitError(typesLoc, "requires one attribute");
197
198 // Assert that there are two types (source and result)
199 if (types.size() != 2)
200 return parser.emitError(typesLoc, "requires two types");
201
202 // Some verification of types
203 VectorType sourceType = llvm::dyn_cast<VectorType>(types[0]);
204 if (!sourceType)
205 return parser.emitError(typesLoc, "requires vector type");
206 VectorType vectorType = llvm::dyn_cast<VectorType>(types[1]);
207 if (!vectorType)
208 return parser.emitError(typesLoc, "requires vector type");
209
210 // Populate the source in result
211 if (parser.resolveOperand(source, sourceType, result.operands))
212 return failure();
213
214 return parser.addTypeToList(vectorType, result.types);
215}
216
217// Cast fold method. It will fold with a preceding Cast operation.
218OpFoldResult CastOp::fold(FoldAdaptor adaptor) {
219 auto srcCastOp = getSource().getDefiningOp<aievec::CastOp>();
220 if (!srcCastOp)
221 return nullptr;
222
223 if (srcCastOp.getIsResAcc() == getIsResAcc())
224 return srcCastOp.getResult();
225
226 return srcCastOp.getSource();
227}
228
229//===----------------------------------------------------------------------===//
230// SRSOp
231//===----------------------------------------------------------------------===//
232
233// SRS fold method. It will fold with a preceding UPS operation.
234OpFoldResult SRSOp::fold(FoldAdaptor adaptor) {
235 auto *srcDefOp = getSource().getDefiningOp();
236 if (!srcDefOp)
237 return nullptr;
238
239 auto upsOp = dyn_cast<UPSOp>(srcDefOp);
240 if (!upsOp)
241 return nullptr;
242
243 auto *shiftDefOp = getShift().getDefiningOp();
244 if (!shiftDefOp)
245 return nullptr;
246
247 auto constOp = dyn_cast<arith::ConstantOp>(shiftDefOp);
248 if (!constOp)
249 return nullptr;
250
251 // Only fold srs(ups(x), 0) → x. Non-zero shift performs actual
252 // shift-round-saturate and must not be folded away.
253 auto intAttr = dyn_cast<IntegerAttr>(constOp.getValue());
254 if (!intAttr || intAttr.getInt() != 0)
255 return nullptr;
256
257 if (upsOp.getSource().getType() != getResult().getType())
258 return nullptr;
259
260 return upsOp.getSource();
261}
262
263// Print out SRS op.
264void SRSOp::print(OpAsmPrinter &p) {
265 // Print the source accumulator
266 p << " " << getSource() << ", ";
267
268 // Print the shift
269 p << getShift();
270
271 // Always print the attribute dict, but elide the sign attribute when it
272 // has the default value (1 = signed) to avoid breaking existing tests.
273 if (getSign() == 1) {
274 p.printOptionalAttrDict((*this)->getAttrs(), {"sign"});
275 } else {
276 p.printOptionalAttrDict((*this)->getAttrs());
277 }
278
279 // And now print the types
280 p << " : " << getSource().getType() << ", " << getShift().getType() << ", "
281 << getResult().getType();
282}
283
284// Verify SRS op.
285LogicalResult SRSOp::verify() {
286 // Verify the types
287 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
288 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
289 if (!sourceType)
290 return emitError("requires accumulator type");
291 if (!resultType)
292 return emitError("requires vector type");
293
294 // The number of lanes of source accumulator and result vector must match
295 unsigned accLanes = getVectorLaneSize(sourceType);
296 unsigned vecLanes = getVectorLaneSize(resultType);
297 if (accLanes != vecLanes)
298 return emitError("The number of lanes in result vector "
299 "and source accumulator must match");
300
301 // The datatype of accumulator must have greater width
302 Type stype = resultType.getElementType();
303 Type atype = sourceType.getElementType();
304 unsigned stypeWidth = stype.getIntOrFloatBitWidth();
305 unsigned atypeWidth = atype.getIntOrFloatBitWidth();
306
307 if (isa<IntegerType>(atype) && stypeWidth >= atypeWidth)
308 return emitError("the element type of source accumulator must be "
309 "wider than that of the result vector");
310 if (isa<FloatType>(atype) && stypeWidth != 16 && stypeWidth != atypeWidth)
311 return emitError("the element type of source accumulator must be "
312 "same as the result vector");
313
314 return success();
315}
316
317// Parse SRS op.
318ParseResult SRSOp::parse(OpAsmParser &parser, OperationState &result) {
319 llvm::SMLoc typesLoc;
320 SmallVector<Type, 3> types;
321 OpAsmParser::UnresolvedOperand source, shift;
322
323 // Parse the source accumulator
324 if (parser.parseOperand(source) || parser.parseComma() ||
325 parser.parseOperand(shift))
326 return failure();
327
328 // Parse optional attributes (e.g., {sign = 0 : i32})
329 if (parser.parseOptionalAttrDict(result.attributes))
330 return failure();
331
332 // Parse types
333 if (parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
334 return failure();
335
336 // Assert that there are two types (accumulator source and vector result)
337 if (types.size() != 3)
338 return parser.emitError(typesLoc, "requires three types");
339
340 // Some verification of types
341 VectorType accType = llvm::dyn_cast<VectorType>(types[0]);
342 if (!accType)
343 return parser.emitError(typesLoc, "requires vector type");
344
345 IntegerType shiftType = llvm::dyn_cast<IntegerType>(types[1]);
346 if (!shiftType)
347 return parser.emitError(typesLoc, "requires integer type");
348
349 VectorType vectorType = llvm::dyn_cast<VectorType>(types[2]);
350 if (!vectorType)
351 return parser.emitError(typesLoc, "requires vector type");
352
353 // Populate the source in result
354 if (parser.resolveOperand(source, accType, result.operands) ||
355 parser.resolveOperand(shift, shiftType, result.operands))
356 return failure();
357
358 return parser.addTypeToList(vectorType, result.types);
359}
360
361//===----------------------------------------------------------------------===//
362// UPSOp
363//===----------------------------------------------------------------------===//
364
365// UPS fold method. It will fold with a preceding SRS operation.
366OpFoldResult UPSOp::fold(FoldAdaptor adaptor) {
367 // TODO: Both UPS and SRS have an aditional parameter (shift) that's being
368 // TODO: ignored here. Somebody should take a careful look at it.
369 // TODO: In next llvm version: auto srsDefOp =
370 // adaptor.getSource().getDefiningOp();
371 auto *srcDefOp = getSource().getDefiningOp();
372 if (!srcDefOp)
373 return nullptr;
374 auto srsOp = llvm::dyn_cast<SRSOp>(srcDefOp);
375 if (!srsOp)
376 return nullptr;
377 return srsOp.getSource();
378}
379
380// Print out UPS op.
381void UPSOp::print(OpAsmPrinter &p) {
382 // Print the source vector
383 p << " " << getSource();
384
385 // Print the attributes
386 p.printOptionalAttrDict((*this)->getAttrs());
387
388 // And now print the types
389 p << " : " << getSource().getType() << ", " << getResult().getType();
390}
391
392// Verify UPS op.
393LogicalResult UPSOp::verify() {
394 // Verify the types
395 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
396 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
397 if (!sourceType)
398 return emitError("requires vector type");
399 if (!resultType)
400 return emitError("requires vector type");
401
402 // The number of lanes must match
403 unsigned vecLanes = getVectorLaneSize(sourceType);
404 unsigned accLanes = getVectorLaneSize(resultType);
405 if (vecLanes != accLanes)
406 return emitError("The number of lanes in source vector "
407 "and result accumulator must match");
408
409 // The datatype of accumulator must always be greater width
410 Type stype = sourceType.getElementType();
411 Type atype = resultType.getElementType();
412 unsigned stypeWidth = stype.getIntOrFloatBitWidth();
413 unsigned atypeWidth = atype.getIntOrFloatBitWidth();
414
415 if (stypeWidth >= atypeWidth)
416 return emitError("the element type of result accumulator "
417 "must be wider than that of the source vector");
418
419 return success();
420}
421
422// Parse UPS op.
423ParseResult UPSOp::parse(OpAsmParser &parser, OperationState &result) {
424 llvm::SMLoc typesLoc;
425 SmallVector<Type, 2> types;
426 OpAsmParser::UnresolvedOperand source;
427
428 // Parse the source vector
429 if (parser.parseOperand(source))
430 return failure();
431
432 // Parse all the attributes and types
433 if (parser.parseOptionalAttrDict(result.attributes) ||
434 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
435 return failure();
436
437 if (result.attributes.getAttrs().size() != 1)
438 return parser.emitError(typesLoc, "requires one attribute");
439
440 // Assert that there are two types (source vector and accumulator result)
441 if (types.size() != 2)
442 return parser.emitError(typesLoc, "requires two types");
443
444 // Some verification
445 VectorType vectorType = llvm::dyn_cast<VectorType>(types[0]);
446 if (!vectorType)
447 return parser.emitError(typesLoc, "requires vector type");
448 VectorType accType = llvm::dyn_cast<VectorType>(types[1]);
449 if (!accType)
450 return parser.emitError(typesLoc, "requires vector type");
451
452 // Populate the source in result
453 if (parser.resolveOperand(source, vectorType, result.operands))
454 return failure();
455
456 return parser.addTypeToList(accType, result.types);
457}
458
459//===----------------------------------------------------------------------===//
460// BroadcastOp
461//===----------------------------------------------------------------------===//
462
463// Print out Broadcast op.
464void BroadcastOp::print(OpAsmPrinter &p) {
465 // Print the source vector
466 p << " " << getSource();
467
468 // Print the attributes
469 p.printOptionalAttrDict((*this)->getAttrs());
470
471 // And now print the types
472 p << " : " << getSource().getType() << ", " << getResult().getType();
473}
474
475// Verify Broadcast op.
476LogicalResult BroadcastOp::verify() {
477 // Verify the types
478 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
479 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
480
481 if (!sourceType)
482 return emitError("requires vector type");
483 if (!resultType)
484 return emitError("requires vector type");
485
486 if (sourceType != resultType) {
487 return emitError("The vector type of source vector "
488 "and result vector must match");
489 }
490 // The number of lanes must match
491 unsigned sourceLanes = getVectorLaneSize(sourceType);
492 unsigned resultLanes = getVectorLaneSize(resultType);
493 if (sourceLanes != resultLanes)
494 return emitError("The number of lanes in source vector "
495 "and result vector must match");
496
497 // The element type of vectors must always be the same
498 Type stype = sourceType.getElementType();
499 Type rtype = resultType.getElementType();
500
501 if (stype != rtype) {
502 return emitError("the element type of result vector "
503 "must be the same as source vector");
504 }
505
506 return success();
507}
508
509// Parse Broadcast op.
510ParseResult BroadcastOp::parse(OpAsmParser &parser, OperationState &result) {
511 llvm::SMLoc typesLoc;
512 SmallVector<Type, 2> types;
513 OpAsmParser::UnresolvedOperand source;
514
515 // Parse the source vector
516 if (parser.parseOperand(source))
517 return failure();
518
519 // Parse all the attributes and types
520 if (parser.parseOptionalAttrDict(result.attributes) ||
521 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
522 return failure();
523
524 if (result.attributes.getAttrs().size() != 1)
525 return parser.emitError(typesLoc, "requires one attribute");
526
527 // Assert that there are two types (source vector and result vector)
528 if (types.size() != 2)
529 return parser.emitError(typesLoc, "requires two types");
530
531 // Some verification
532 VectorType vecType = llvm::dyn_cast<VectorType>(types[0]);
533 if (!vecType)
534 return parser.emitError(typesLoc, "requires vector type");
535
536 VectorType resType = llvm::dyn_cast<VectorType>(types[1]);
537 if (!resType)
538 return parser.emitError(typesLoc, "requires vector type");
539
540 // Populate the source in result
541 if (parser.resolveOperand(source, vecType, result.operands))
542 return failure();
543
544 return parser.addTypeToList(resType, result.types);
545}
546
547//===----------------------------------------------------------------------===//
548// BroadcastScalarOp
549//===----------------------------------------------------------------------===//
550
551// Print out BroadcastScalar op.
552void BroadcastScalarOp::print(OpAsmPrinter &p) {
553 // Print the source vector
554 p << " " << getSource();
555
556 // And now print the types
557 p << " : " << getSource().getType() << ", " << getResult().getType();
558}
559
560// Verify BroadcastScalar op.
561LogicalResult BroadcastScalarOp::verify() {
562 // Verify the types
563 Type sourceType = getSource().getType();
564 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
565
566 if (!resultType)
567 return emitError("requires vector type");
568
569 if (!isa<IntegerType, FloatType>(sourceType))
570 return emitError("requires source type to be integer or float");
571
572 Type resultElemType = resultType.getElementType();
573 if (sourceType != resultElemType) {
574 return emitError("the element type of result vector must be the same as "
575 "the source type");
576 }
577
578 return success();
579}
580
581// Parse BroadcastScalar op.
582ParseResult BroadcastScalarOp::parse(OpAsmParser &parser,
583 OperationState &result) {
584 llvm::SMLoc typesLoc;
585 SmallVector<Type, 2> types;
586 OpAsmParser::UnresolvedOperand source;
587
588 // Parse the source vector
589 if (parser.parseOperand(source))
590 return failure();
591
592 // Parse all the attributes and types
593 if (parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
594 return failure();
595
596 if (!result.attributes.getAttrs().empty())
597 return parser.emitError(typesLoc, "do not require attributes");
598
599 // Assert that there is two type (source and result vector)
600 if (types.size() != 2)
601 return parser.emitError(typesLoc, "requires two types");
602
603 // Some verification
604 VectorType resType = llvm::dyn_cast<VectorType>(types[1]);
605 if (!resType)
606 return parser.emitError(typesLoc, "requires vector type");
607
608 if (parser.resolveOperand(source, types[0], result.operands))
609 return failure();
610
611 return parser.addTypeToList(resType, result.types);
612}
613
614//===----------------------------------------------------------------------===//
615// MulElemOp and FMAElemOp
616//===----------------------------------------------------------------------===//
617
618// MulElemOp and FMAElemOp are structurally similar, except that FMAElem op
619// has few extra fields (accumulator, bool flag to indicate if it is fmsub,
620// etc.). We create some specializations to print those fields specifically for
621// FMAElemOp and MULElemOp.
622
623// Print the accumulator
624template <typename T>
625void printAccumulator(OpAsmPrinter &p, T op);
626template <>
627inline void printAccumulator(OpAsmPrinter &p, aievec::FMAElemOp op) {
628 p << ", " << op.getAcc();
629}
630template <>
631inline void printAccumulator(OpAsmPrinter &p, aievec::MulElemOp op) {}
632
633// Mark fmsub indicator as elided if the FMAElem op is not fmsub
634template <typename T>
635void elideFMSubAttr(T op, SmallVector<StringRef, 4> &elidedAttrs);
636template <>
637inline void elideFMSubAttr(aievec::FMAElemOp op,
638 SmallVector<StringRef, 4> &elidedAttrs) {
639 if (!op.getFmsub())
640 elidedAttrs.push_back(op.getSubAttrName());
641}
642
643template <>
644inline void elideFMSubAttr(aievec::MulElemOp op,
645 SmallVector<StringRef, 4> &elidedAttrs) {}
646
647// Print out MulElem and FMAElem op.
648template <typename T>
649static void printMulFMAElemOp(OpAsmPrinter &p, T op) {
650 // Print the left operand
651 p << " " << op.getLhs();
652 // Print the right operand
653 p << ", " << op.getRhs();
654 // For fma op, print the accumulator
655 printAccumulator(p, op);
656
657 // Print the attributes, but don't print attributes that are empty strings
658 SmallVector<StringRef, 4> elidedAttrs;
659 for (int idx = 0; idx < 2; ++idx) {
660 elideFMSubAttr(op, elidedAttrs);
661 }
662 p.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
663
664 // And now print the types
665 p << " : " << op.getLhs().getType() << ", " << op.getRhs().getType();
666 p << ", " << op.getResult().getType();
667}
668
669void MulElemOp::print(OpAsmPrinter &p) {
670 printMulFMAElemOp<aievec::MulElemOp>(p, *this);
671}
672
673void aievec::FMAElemOp::print(OpAsmPrinter &p) {
674 printMulFMAElemOp<aievec::FMAElemOp>(p, *this);
675}
676
677// Verify MulElem and FMAElem op.
678template <typename T>
679static LogicalResult verifyMulFMAElemOp(T op) {
680 // Verify the types
681 auto lhsType = llvm::dyn_cast<VectorType>(op.getLhs().getType());
682 auto rhsType = llvm::dyn_cast<VectorType>(op.getRhs().getType());
683
684 if (!lhsType || !rhsType)
685 return op.emitError("requires vector type");
686
687 auto resultType = llvm::dyn_cast<VectorType>(op.getResult().getType());
688
689 if (!resultType)
690 return op.emitError("requires vector type");
691
692 // Additional checks for FMAElem op
693 // Get the width of the underlying scalars of all the vectors
694 Type ltype = lhsType.getElementType();
695 Type rtype = rhsType.getElementType();
696 Type atype = resultType.getElementType();
697 unsigned ltypeWidth = ltype.getIntOrFloatBitWidth();
698 unsigned rtypeWidth = rtype.getIntOrFloatBitWidth();
699 unsigned atypeWidth = atype.getIntOrFloatBitWidth();
700
701 // Checks on the number of lanes
702 unsigned rhsLanes = getVectorLaneSize(rhsType);
703 unsigned lhsLanes = getVectorLaneSize(lhsType);
704
705 // lane size must match
706 if (lhsLanes != rhsLanes) {
707 return op.emitError("The number of lanes in lhs operand "
708 "must be the same as rhs operand");
709 }
710
711 // lhs and rhs vector's element type must match
712 if (ltype != rtype)
713 return op.emitError("The element type of lhs and rhs "
714 "operand vectors must match");
715
716 // The integer datatype of accumulator must always be greater width
717 if (isa<IntegerType>(atype)) {
718 if (!isa<IntegerType>(ltype))
719 return op.emitError("Integer result must have integer operands");
720
721 if (ltypeWidth >= atypeWidth || rtypeWidth >= atypeWidth)
722 return op.emitError("the element type of accumulator must have "
723 "wider width than that of the operand vectors");
724 } else if (isa<FloatType>(atype)) {
725 if (!isa<FloatType>(ltype))
726 return op.emitError("Floating point result must have "
727 "floating point operands");
728 }
729
730 return success();
731}
732
733LogicalResult aievec::MulElemOp::verify() {
734 if (getVectorLaneSize(cast<VectorType>(getLhs().getType())) == 64 &&
735 (!getElementTypeOrSelf(getLhs().getType()).isBF16() ||
736 !getElementTypeOrSelf(getResult().getType()).isF32()))
737 return emitError("64 lanes are supported only for bf16 operands with an "
738 "f32 result");
739 return verifyMulFMAElemOp<aievec::MulElemOp>(*this);
740}
741
742LogicalResult aievec::FMAElemOp::verify() {
743 return verifyMulFMAElemOp<aievec::FMAElemOp>(*this);
744}
745
746// Parse MulElem and FMAElem op.
747static ParseResult parseMulFMAElemOp(OpAsmParser &parser,
748 OperationState &result,
749 bool isFMAElemOp = true) {
750 llvm::SMLoc typesLoc;
751 SmallVector<Type, 3> types;
752 OpAsmParser::UnresolvedOperand lhs, rhs, acc;
753
754 // Parse the lhs and rhs
755 if (parser.parseOperand(lhs) || parser.parseComma() ||
756 parser.parseOperand(rhs))
757 return failure();
758
759 // Parse the acc for FMA op
760 if (isFMAElemOp) {
761 if (parser.parseComma() || parser.parseOperand(acc))
762 return failure();
763 }
764
765 // Parse all the attributes and types
766 if (parser.parseOptionalAttrDict(result.attributes) ||
767 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
768 return failure();
769
770 // Assert that there are three types: lhs, rhs, and acc
771 if (types.size() != 3)
772 return parser.emitError(typesLoc, "requires three types");
773
774 // Some verification
775 VectorType lhsType = llvm::dyn_cast<VectorType>(types[0]);
776 if (!lhsType)
777 return parser.emitError(typesLoc, "requires vector type");
778 VectorType rhsType = llvm::dyn_cast<VectorType>(types[1]);
779 if (!rhsType)
780 return parser.emitError(typesLoc, "requires vector type");
781
782 // Int ops use the accumulator while float ops use normal vector registers
783 VectorType accType = llvm::dyn_cast<VectorType>(types[2]);
784 if (!accType)
785 return parser.emitError(typesLoc, "requires vector type");
786
787 // Populate the lhs and rhs operands, and result
788 if (parser.resolveOperand(lhs, lhsType, result.operands) ||
789 parser.resolveOperand(rhs, rhsType, result.operands))
790 return failure();
791
792 // Populate acc operand for FMA op
793 if (isFMAElemOp) {
794 if (parser.resolveOperand(acc, accType, result.operands))
795 return failure();
796 }
797
798 return parser.addTypeToList(accType, result.types);
799}
800
801ParseResult MulElemOp::parse(OpAsmParser &parser, OperationState &result) {
802 return parseMulFMAElemOp(parser, result, false);
803}
804
805ParseResult FMAElemOp::parse(OpAsmParser &parser, OperationState &result) {
806 return parseMulFMAElemOp(parser, result, true);
807}
808
809//===----------------------------------------------------------------------===//
810// ConcatOp
811//===----------------------------------------------------------------------===//
812
813// Print out Concat op.
814void ConcatOp::print(OpAsmPrinter &p) {
815 // Print the source vectors
816 assert(!getSources().empty() && "concat source empty");
817 p << " " << getSources();
818
819 // Print the attributes
820 p.printOptionalAttrDict((*this)->getAttrs());
821
822 // And now print the types
823 p << " : " << getSources().getTypes().front() << ", "
824 << getResult().getType();
825}
826
827// Verify Concat op.
828LogicalResult ConcatOp::verify() {
829 // Must be concatenating at least two sources
830 if (getSources().size() < 2)
831 return emitError("Must concatenate at least two vectors");
832
833 // Verify the types
834 VectorType sourceType =
835 llvm::dyn_cast<VectorType>(getSources().getTypes().front());
836 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
837 if (!sourceType || !resultType)
838 return emitError("requires vector type");
839
840 SmallVector<Value, 8> srcs(getSources().begin(), getSources().end());
841 // All the sources must have the same type
842 for (auto source : srcs) {
843 VectorType type = llvm::dyn_cast<VectorType>(source.getType());
844 if (!type)
845 return emitError("requires vector type");
846 if (type != sourceType)
847 return emitError("All sources must have same type");
848 }
849
850 // The lanes in concatenated type must be the sum of lanes of source vector
851 unsigned totalLanes = 0;
852 for (auto source : srcs) {
853 VectorType type = llvm::dyn_cast<VectorType>(source.getType());
854 totalLanes += getVectorLaneSize(type);
855 }
856
857 if (totalLanes != getVectorLaneSize(resultType))
858 return emitError("mismatch between vector lanes "
859 "and sum of source lanes");
860
861 return success();
862}
863
864// Parse Concat op.
865ParseResult ConcatOp::parse(OpAsmParser &parser, OperationState &result) {
866 llvm::SMLoc typesLoc;
867 SmallVector<Type, 2> types;
868 SmallVector<OpAsmParser::UnresolvedOperand, 8> sources;
869
870 // Parse the source vectors
871 if (parser.parseOperandList(sources))
872 return failure();
873
874 // Parse all the attributes and types
875 if (parser.parseOptionalAttrDict(result.attributes) ||
876 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
877 return failure();
878
879 // Currently there are no attributes in concat op
880 if (!result.attributes.getAttrs().empty())
881 return parser.emitError(typesLoc, "expects no attribute");
882
883 // Assert that there are two types (type of all sources, and result)
884 if (types.size() != 2)
885 return parser.emitError(typesLoc, "requires two types");
886
887 // Some verification
888 VectorType sourceType = llvm::dyn_cast<VectorType>(types[0]);
889 VectorType resultType = llvm::dyn_cast<VectorType>(types[1]);
890 if (!sourceType || !resultType)
891 return parser.emitError(typesLoc, "requires vector type");
892
893 // Populate the source vectors in result
894 if (parser.resolveOperands(sources, sourceType, result.operands))
895 return failure();
896
897 return parser.addTypeToList(resultType, result.types);
898}
899
900LogicalResult
901ConcatOp::inferReturnTypes(MLIRContext *, std::optional<Location>,
902 ConcatOp::Adaptor adaptor,
903 SmallVectorImpl<Type> &inferredReturnTypes) {
904 SmallVector<Value, 8> srcs(adaptor.getSources().begin(),
905 adaptor.getSources().end());
906 unsigned totalLength = 0;
907 for (auto source : srcs) {
908 VectorType type = llvm::cast<VectorType>(source.getType());
909 assert(type.getRank() == 1 &&
910 "only rank 1 vectors currently supported by concat");
911 totalLength += type.getDimSize(0);
912 }
913 inferredReturnTypes.push_back(VectorType::get(
914 {totalLength},
915 llvm::cast<VectorType>(srcs[0].getType()).getElementType()));
916 return success();
917}
918
919//===----------------------------------------------------------------------===//
920// ExtOp
921//===----------------------------------------------------------------------===//
922
923// Print out Ext op.
924void ExtOp::print(OpAsmPrinter &p) {
925 // Print the source vector
926 p << " " << getSource();
927
928 // Print the attributes
929 p.printOptionalAttrDict((*this)->getAttrs());
930
931 // And now print the types
932 p << " : " << getSource().getType() << ", " << getResult().getType();
933}
934
935// Verify Ext op.
936LogicalResult ExtOp::verify() {
937 // Verify the types
938 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
939 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
940 if (!sourceType || !resultType)
941 return emitError("requires vector type");
942
943 // Check the number of lanes
944 unsigned sourceLanes = getVectorLaneSize(sourceType);
945 unsigned resultLanes = getVectorLaneSize(resultType);
946 // Source lanes must be greater than result lanes
947 if (sourceLanes / resultLanes <= 1)
948 return emitError("lanes in source vector must be at least "
949 "twice that of result vector");
950 // Source lanes must be a multiple of result lanes
951 if (sourceLanes % resultLanes != 0)
952 return emitError("lanes in result vector must be a multiple "
953 "of source vector lanes");
954
955 // Verify validity of index
956 unsigned factor = sourceLanes / resultLanes;
957 if (static_cast<unsigned>(getIndex()) >= factor)
958 return emitError("index out of bounds");
959
960 // The datatype of source and result must match
961 Type stype = sourceType.getElementType();
962 Type rtype = resultType.getElementType();
963 if (stype != rtype)
964 return emitError("source and result element type must be same");
965
966 return success();
967}
968
969// Parse Ext op.
970ParseResult ExtOp::parse(OpAsmParser &parser, OperationState &result) {
971 llvm::SMLoc typesLoc;
972 SmallVector<Type, 2> types;
973 OpAsmParser::UnresolvedOperand source;
974
975 // Parse the source vector
976 if (parser.parseOperand(source))
977 return failure();
978
979 // Parse all the attributes and types
980 if (parser.parseOptionalAttrDict(result.attributes) ||
981 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
982 return failure();
983
984 if (result.attributes.getAttrs().size() != 1)
985 return parser.emitError(typesLoc, "requires one attribute");
986
987 // Assert that there are two types (source and result)
988 if (types.size() != 2)
989 return parser.emitError(typesLoc, "requires two types");
990
991 // Some verification
992 VectorType sourceType = llvm::dyn_cast<VectorType>(types[0]);
993 VectorType resultType = llvm::dyn_cast<VectorType>(types[1]);
994 if (!sourceType || !resultType)
995 return parser.emitError(typesLoc, "requires vector type");
996
997 // Populate the source in result
998 if (parser.resolveOperand(source, sourceType, result.operands))
999 return failure();
1000
1001 return parser.addTypeToList(resultType, result.types);
1002}
1003
1004//===----------------------------------------------------------------------===//
1005// PackOp and UnpackOp
1006//===----------------------------------------------------------------------===//
1007
1008// Print out Pack and Unpack op.
1009template <typename T>
1010static void printPackUnpackOp(OpAsmPrinter &p, T op) {
1011 // Print the source vector
1012 p << " " << op.getSource();
1013
1014 // Print the attributes
1015 p.printOptionalAttrDict(op->getAttrs());
1016
1017 // And now print the types
1018 p << " : " << op.getSource().getType() << ", " << op.getResult().getType();
1019}
1020
1021void PackOp::print(OpAsmPrinter &p) { printPackUnpackOp<PackOp>(p, *this); }
1022
1023void UnpackOp::print(OpAsmPrinter &p) { printPackUnpackOp<UnpackOp>(p, *this); }
1024
1025// Verify Pack and Unpack op.
1026template <typename T>
1027static LogicalResult verifyPackUnpackOp(T op) {
1028 // Verify the types
1029 auto sourceType = llvm::dyn_cast<VectorType>(op.getSource().getType());
1030 auto resultType = llvm::dyn_cast<VectorType>(op.getResult().getType());
1031 if (!sourceType || !resultType)
1032 return op.emitError("requires vector type");
1033
1034 unsigned sourceLanes = getVectorLaneSize(sourceType);
1035 unsigned resultLanes = getVectorLaneSize(resultType);
1036
1037 Type stype = sourceType.getElementType();
1038 Type rtype = resultType.getElementType();
1039 // Pack/unpack are integer packing ops; reject non-integer element types
1040 // (e.g. f16/bf16) even though they share bitwidths with the legal forms.
1041 if (!isa<IntegerType>(stype) || !isa<IntegerType>(rtype))
1042 return op.emitError("requires integer element types");
1043 unsigned stypeWidth = stype.getIntOrFloatBitWidth();
1044 unsigned rtypeWidth = rtype.getIntOrFloatBitWidth();
1045
1046 if (isa<PackOp>(op)) {
1047 // Pack: i16 -> i8 with matching lane count (existing AIE2 path) OR
1048 // i8 -> i8 with input lanes = 2 * output lanes (AIE2P int4 path; the
1049 // result carries 2 packed nibbles per byte).
1050 bool legalI16I8 =
1051 stypeWidth == 16 && rtypeWidth == 8 && sourceLanes == resultLanes;
1052 bool legalI8I4Packed =
1053 stypeWidth == 8 && rtypeWidth == 8 && sourceLanes == 2 * resultLanes;
1054 if (!legalI16I8 && !legalI8I4Packed)
1055 return op.emitError(
1056 "pack must narrow i16->i8 (same lanes) or i8->i8 (input lanes = "
1057 "2 * output lanes, int4-packed)");
1058 } else {
1059 // Unpack: i8 -> i16 with matching lane count (existing AIE2 path) OR
1060 // i8 -> i8 with output lanes = 2 * input lanes (AIE2P int4 path; the
1061 // input carries 2 packed nibbles per byte and the output expands each
1062 // nibble into a full byte).
1063 bool legalI8I16 =
1064 stypeWidth == 8 && rtypeWidth == 16 && sourceLanes == resultLanes;
1065 bool legalI4PackedI8 =
1066 stypeWidth == 8 && rtypeWidth == 8 && resultLanes == 2 * sourceLanes;
1067 if (!legalI8I16 && !legalI4PackedI8)
1068 return op.emitError(
1069 "unpack must widen i8->i16 (same lanes) or i8->i8 (output lanes = "
1070 "2 * input lanes, int4-packed)");
1071 }
1072
1073 return success();
1074}
1075
1076LogicalResult PackOp::verify() { return verifyPackUnpackOp<PackOp>(*this); }
1077
1078LogicalResult UnpackOp::verify() { return verifyPackUnpackOp<UnpackOp>(*this); }
1079
1080// Parse Pack and Unpack op.
1081static ParseResult parsePackUnpackOp(OpAsmParser &parser,
1082 OperationState &result) {
1083 llvm::SMLoc typesLoc;
1084 SmallVector<Type, 2> types;
1085 OpAsmParser::UnresolvedOperand source;
1086
1087 // Parse the source vector
1088 if (parser.parseOperand(source))
1089 return failure();
1090
1091 // Parse all the attributes and types
1092 if (parser.parseOptionalAttrDict(result.attributes) ||
1093 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
1094 return failure();
1095
1096 // Currently there are no attributes in pack/unpack op
1097 if (!result.attributes.getAttrs().empty())
1098 return parser.emitError(typesLoc, "expects no attributes");
1099
1100 // Assert that there are two types (source and result vectors)
1101 if (types.size() != 2)
1102 return parser.emitError(typesLoc, "requires two types");
1103
1104 // Some verification
1105 VectorType sourceType = llvm::dyn_cast<VectorType>(types[0]);
1106 VectorType resultType = llvm::dyn_cast<VectorType>(types[1]);
1107 if (!sourceType || !resultType)
1108 return parser.emitError(typesLoc, "requires vector type");
1109
1110 // Populate the source in result
1111 if (parser.resolveOperand(source, sourceType, result.operands))
1112 return failure();
1113
1114 return parser.addTypeToList(resultType, result.types);
1115}
1116
1117ParseResult PackOp::parse(OpAsmParser &parser, OperationState &result) {
1118 return parsePackUnpackOp(parser, result);
1119}
1120
1121ParseResult UnpackOp::parse(OpAsmParser &parser, OperationState &result) {
1122 return parsePackUnpackOp(parser, result);
1123}
1124
1125//===----------------------------------------------------------------------===//
1126// ExtElemOp
1127//===----------------------------------------------------------------------===//
1128
1129// Verify Extract Element op.
1130LogicalResult ExtElemOp::verify() {
1131 // Verify the types
1132 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
1133
1134 if (!sourceType)
1135 return emitError("source requires vector type");
1136
1137 // The element type of vectors must always be the same
1138 Type stype = sourceType.getElementType();
1139 Type rtype = getResult().getType();
1140
1141 if (stype != rtype) {
1142 return emitError("the type of result must be the same as the element "
1143 "type of source vector");
1144 }
1145
1146 return success();
1147}
1148
1149//===----------------------------------------------------------------------===//
1150// ShiftOp
1151//===----------------------------------------------------------------------===//
1152
1153// Print out Shift op.
1154void ShiftOp::print(OpAsmPrinter &p) {
1155 // Print the lhs and rhs vectors
1156 p << " " << getLhs() << ", " << getRhs();
1157
1158 // Print shift
1159 p << ", " << getShift();
1160
1161 // Print the attributes
1162 p.printOptionalAttrDict((*this)->getAttrs());
1163
1164 // And now print the types
1165 p << " : " << getLhs().getType() << ", " << getLhs().getType() << ", "
1166 << getShift().getType() << ", " << getResult().getType();
1167}
1168
1169// Verify Shift op.
1170LogicalResult ShiftOp::verify() {
1171 // Verify the types
1172 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
1173 if (!resultType)
1174 return emitError("requires vector type");
1175
1176 // lhs, rhs and result must have the same type
1177 VectorType lhsType = llvm::dyn_cast<VectorType>(getLhs().getType());
1178 VectorType rhsType = llvm::dyn_cast<VectorType>(getRhs().getType());
1179
1180 if (!lhsType || !rhsType)
1181 return emitError("requires vector type");
1182 if (lhsType != resultType || rhsType != resultType)
1183 return emitError("All vectors must have same type");
1184
1185 if (!isa<IntegerType>(getShift().getType()))
1186 return emitError("requires integer type");
1187
1188 return success();
1189}
1190
1191// Parse Shift op.
1192ParseResult ShiftOp::parse(OpAsmParser &parser, OperationState &result) {
1193 llvm::SMLoc typesLoc;
1194 SmallVector<Type, 4> types;
1195 OpAsmParser::UnresolvedOperand lhs, rhs, shift;
1196
1197 // Parse the source vectors
1198 if (parser.parseOperand(lhs) || parser.parseComma() ||
1199 parser.parseOperand(rhs) || parser.parseComma() ||
1200 parser.parseOperand(shift))
1201 return failure();
1202
1203 // Parse all the attributes and types
1204 if (parser.parseOptionalAttrDict(result.attributes) ||
1205 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
1206 return failure();
1207
1208 if (result.attributes.getAttrs().size() != 1)
1209 return parser.emitError(typesLoc, "expects one attribute");
1210
1211 // Assert that there are two types (source and result vectors)
1212 if (types.size() != 4)
1213 return parser.emitError(typesLoc, "requires four types");
1214
1215 // Some verification
1216 VectorType lhsType = llvm::dyn_cast<VectorType>(types[0]);
1217 VectorType rhsType = llvm::dyn_cast<VectorType>(types[1]);
1218 IntegerType shiftType = llvm::dyn_cast<IntegerType>(types[2]);
1219 VectorType resultType = llvm::dyn_cast<VectorType>(types[3]);
1220 if (!lhsType || !rhsType || !resultType)
1221 return parser.emitError(typesLoc, "requires vector type");
1222
1223 if (!shiftType)
1224 return parser.emitError(typesLoc, "requires integer type");
1225
1226 // Populate the lhs vector, rhs vectors and shift in result
1227 if (parser.resolveOperand(lhs, lhsType, result.operands) ||
1228 parser.resolveOperand(rhs, rhsType, result.operands) ||
1229 parser.resolveOperand(shift, shiftType, result.operands))
1230 return failure();
1231
1232 return parser.addTypeToList(resultType, result.types);
1233}
1234
1235//===----------------------------------------------------------------------===//
1236// MatMulOp and MatMulOp_AIE2P
1237//===----------------------------------------------------------------------===//
1238
1239namespace {
1240// One row of the recipe table. Element types are given by kind and width,
1241// with 0 standing for bf16 operands or an f32 accumulator.
1242struct MatMulRecipeEntry {
1243 bool aie2p;
1244 unsigned lhsWidth, rhsWidth, accWidth;
1245 int64_t m, k, n;
1246 MatMulRecipe recipe;
1247};
1248} // namespace
1249
1250// The lhs and rhs layouts in each row are those the multiply reads: the MAC
1251// modes read lhs [m, k] and rhs [k, n]; bfp16 `8x8_8x8T` reads rhs [n, k],
1252// since both of its operands share an exponent along k; the elementwise bf16
1253// recipes broadcast the lhs a column at a time and so read it [k, m], except
1254// that the 4-row one pads lhs [m, k] to 8 rows before transposing it itself.
1255// A 1x8 rhs or 8x1 lhs reads the same either way.
1256static ArrayRef<MatMulRecipeEntry> getMatMulRecipeTable() {
1257 using K = MatMulRecipeKind;
1258 // clang-format off
1259 static const MatMulRecipeEntry table[] = {
1260 // aie2p, lhs, rhs and acc widths, m, k, n, {kind, lhsKM, rhsNK}
1261 {false, 8, 4, 32, 4, 16, 8, {K::AIE2_I8xI4_4x16x8, false, false}},
1262 {false, 8, 8, 32, 4, 8, 8, {K::AIE2_I8xI8_4x8x8, false, false}},
1263 {false, 16, 8, 32, 4, 4, 8, {K::AIE2_I16xI8_4x4x8, false, false}},
1264 {false, 16, 16, 32, 4, 2, 8, {K::AIE2_I16xI16_4x2x8, false, false}},
1265 {false, 16, 8, 64, 2, 8, 8, {K::AIE2_I16xI8_2x8x8_Acc64, false, false}},
1266 {false, 16, 8, 64, 4, 8, 4, {K::AIE2_I16xI8_4x8x4_Acc64, false, false}},
1267 {false, 16, 16, 64, 2, 4, 8, {K::AIE2_I16xI16_2x4x8_Acc64, false, false}},
1268 {false, 16, 16, 64, 4, 4, 4, {K::AIE2_I16xI16_4x4x4_Acc64, false, false}},
1269 {false, 32, 16, 64, 4, 2, 4, {K::AIE2_I32xI16_4x2x4_Acc64, false, false}},
1270 {false, 0, 0, 0, 4, 8, 4, {K::AIE2_BF16_4x8x4, false, false}},
1271 {true, 8, 8, 32, 8, 8, 8, {K::AIE2P_I8_8x8x8, false, false}},
1272 {true, 16, 16, 32, 8, 2, 8, {K::AIE2P_I16_8x2x8, false, false}},
1273 {true, 0, 0, 0, 8, 8, 8, {K::AIE2P_BFP16_8x8x8, false, true}},
1274 {true, 0, 0, 0, 4, 8, 8, {K::AIE2P_BFP16_4x8x8, false, true}},
1275 {true, 0, 0, 0, 8, 8, 4, {K::AIE2P_BF16Elem_8x8x4, true, false}},
1276 {true, 0, 0, 0, 4, 8, 4, {K::AIE2P_BF16Elem_4x8x4, false, false}},
1277 {true, 0, 0, 0, 8, 1, 8, {K::AIE2P_BF16Outer_8x1x8, false, false}},
1278 };
1279 // clang-format on
1280 return table;
1281}
1282
1283// The table width of an operand element type: its width for an integer, 0
1284// for bf16, and nullopt for anything else.
1285static std::optional<unsigned> getRecipeOperandWidth(Type t) {
1286 if (auto intTy = dyn_cast<IntegerType>(t))
1287 return intTy.getWidth();
1288 if (t.isBF16())
1289 return 0u;
1290 return std::nullopt;
1291}
1292
1293static std::optional<unsigned> getRecipeAccWidth(Type t) {
1294 if (auto intTy = dyn_cast<IntegerType>(t))
1295 return intTy.getWidth();
1296 if (t.isF32())
1297 return 0u;
1298 return std::nullopt;
1299}
1300
1301std::optional<MatMulRecipe>
1302xilinx::aievec::getMatMulRecipe(bool aie2p, Type lhsElemTy, Type rhsElemTy,
1303 Type accElemTy, int64_t m, int64_t k,
1304 int64_t n) {
1305 auto lhsWidth = getRecipeOperandWidth(lhsElemTy);
1306 auto rhsWidth = getRecipeOperandWidth(rhsElemTy);
1307 auto accWidth = getRecipeAccWidth(accElemTy);
1308 if (!lhsWidth || !rhsWidth || !accWidth)
1309 return std::nullopt;
1310 // bf16 multiplies accumulate in f32, and integers in integers.
1311 if ((*lhsWidth == 0 || *rhsWidth == 0 || *accWidth == 0) &&
1312 (*lhsWidth | *rhsWidth | *accWidth) != 0)
1313 return std::nullopt;
1314 for (const MatMulRecipeEntry &e : getMatMulRecipeTable())
1315 if (e.aie2p == aie2p && e.lhsWidth == *lhsWidth &&
1316 e.rhsWidth == *rhsWidth && e.accWidth == *accWidth && e.m == m &&
1317 e.k == k && e.n == n)
1318 return e.recipe;
1319 return std::nullopt;
1320}
1321
1322std::optional<SmallVector<ShuffleMode, 2>>
1323xilinx::aievec::getTileTransposeModes(bool aie2p, Type elemTy, int64_t rows,
1324 int64_t cols) {
1325 if (rows == 1 || cols == 1)
1326 return SmallVector<ShuffleMode, 2>{};
1327 if (!elemTy.isIntOrFloat())
1328 return std::nullopt;
1329 unsigned width = elemTy.getIntOrFloatBitWidth();
1330 int64_t bits = rows * cols * width;
1331 // Full 512-bit transposes, as AIE2 and AIE2P both provide them. Modes are
1332 // named after the shape they read.
1333 static const struct {
1334 unsigned width;
1335 int64_t rows, cols;
1336 ShuffleMode mode;
1337 } modes512[] = {
1338 {8, 4, 16, ShuffleMode::T8_4X16}, {8, 8, 8, ShuffleMode::T8_8X8},
1339 {8, 16, 4, ShuffleMode::T8_16X4}, {16, 2, 16, ShuffleMode::T16_2X16},
1340 {16, 4, 8, ShuffleMode::T16_4X8}, {16, 8, 4, ShuffleMode::T16_8X4},
1341 {16, 16, 2, ShuffleMode::T16_16X2}, {32, 4, 4, ShuffleMode::T32_4X4},
1342 };
1343 // AIE2P modes that transpose each 256-bit half of a register on its own.
1344 static const struct {
1345 unsigned width;
1346 int64_t rows, cols;
1347 ShuffleMode mode;
1348 } modes256[] = {
1349 {8, 4, 8, ShuffleMode::T8_4X8}, {8, 8, 4, ShuffleMode::T8_8X4},
1350 {16, 2, 8, ShuffleMode::T16_2X8}, {16, 4, 4, ShuffleMode::T16_4X4},
1351 {16, 8, 2, ShuffleMode::T16_8X2},
1352 };
1353 if (bits == 512) {
1354 for (const auto &e : modes512)
1355 if (e.width == width && e.rows == rows && e.cols == cols)
1356 return SmallVector<ShuffleMode, 2>{e.mode};
1357 return std::nullopt;
1358 }
1359 if (!aie2p)
1360 return std::nullopt;
1361 if (bits == 256) {
1362 for (const auto &e : modes256)
1363 if (e.width == width && e.rows == rows && e.cols == cols)
1364 return SmallVector<ShuffleMode, 2>{e.mode};
1365 return std::nullopt;
1366 }
1367 if (width == 16 && rows == 8 && cols == 8)
1368 return SmallVector<ShuffleMode, 2>{ShuffleMode::T16_8X8_LO,
1369 ShuffleMode::T16_8X8_HI};
1370 return std::nullopt;
1371}
1372
1373// checkMatMulTile, with the reason for a failure written to `os`.
1374static std::optional<MatMulRecipe>
1375checkMatMulTileImpl(bool aie2p, VectorType lhsTy, VectorType rhsTy,
1376 VectorType accTy, bool lhsTransposed, bool rhsTransposed,
1377 llvm::raw_ostream &os) {
1378 if (lhsTy.getRank() != 2 || rhsTy.getRank() != 2 || accTy.getRank() != 2) {
1379 os << "expects rank-2 operands";
1380 return std::nullopt;
1381 }
1382 ArrayRef<int64_t> lhsShape = lhsTy.getShape(), rhsShape = rhsTy.getShape(),
1383 accShape = accTy.getShape();
1384 int64_t m = lhsShape[lhsTransposed ? 1 : 0];
1385 int64_t k = lhsShape[lhsTransposed ? 0 : 1];
1386 int64_t rhsK = rhsShape[rhsTransposed ? 1 : 0];
1387 int64_t n = rhsShape[rhsTransposed ? 0 : 1];
1388 if (k != rhsK || accShape[0] != m || accShape[1] != n) {
1389 os << "lhs " << lhsTy << (lhsTransposed ? " [k, m]" : " [m, k]")
1390 << " times rhs " << rhsTy << (rhsTransposed ? " [n, k]" : " [k, n]")
1391 << " is not a contraction into " << accTy;
1392 return std::nullopt;
1393 }
1394 StringRef target = aie2p ? "AIE2P" : "AIE2";
1395 std::optional<MatMulRecipe> recipe =
1396 getMatMulRecipe(aie2p, lhsTy.getElementType(), rhsTy.getElementType(),
1397 accTy.getElementType(), m, k, n);
1398 if (!recipe) {
1399 os << "no " << target << " matrix multiply takes " << m << "x" << k << " "
1400 << lhsTy.getElementType() << " times " << k << "x" << n << " "
1401 << rhsTy.getElementType() << " into " << accTy.getElementType();
1402 return std::nullopt;
1403 }
1404 auto layoutReachable = [&](VectorType ty, bool stored, bool native) {
1405 return stored == native ||
1406 getTileTransposeModes(aie2p, ty.getElementType(), ty.getDimSize(0),
1407 ty.getDimSize(1));
1408 };
1409 if (!layoutReachable(lhsTy, lhsTransposed, recipe->lhsKM)) {
1410 os << "the " << target << " " << m << "x" << k << "x" << n
1411 << " multiply reads lhs as " << (recipe->lhsKM ? "[k, m]" : "[m, k]")
1412 << ", and no shuffle transposes " << lhsTy;
1413 return std::nullopt;
1414 }
1415 if (!layoutReachable(rhsTy, rhsTransposed, recipe->rhsNK)) {
1416 os << "the " << target << " " << m << "x" << k << "x" << n
1417 << " multiply reads rhs as " << (recipe->rhsNK ? "[n, k]" : "[k, n]")
1418 << ", and no shuffle transposes " << rhsTy;
1419 return std::nullopt;
1420 }
1421 return recipe;
1422}
1423
1424std::optional<MatMulRecipe> xilinx::aievec::checkMatMulTile(
1425 bool aie2p, VectorType lhsTy, VectorType rhsTy, VectorType accTy,
1426 bool lhsTransposed, bool rhsTransposed,
1427 llvm::function_ref<InFlightDiagnostic()> emitError) {
1428 std::string message;
1429 llvm::raw_string_ostream os(message);
1430 std::optional<MatMulRecipe> recipe = checkMatMulTileImpl(
1431 aie2p, lhsTy, rhsTy, accTy, lhsTransposed, rhsTransposed, os);
1432 if (!recipe && emitError)
1433 emitError() << message;
1434 return recipe;
1435}
1436
1437template <typename OpTy>
1438static LogicalResult verifyMatMulOp(OpTy op, bool aie2p) {
1439 auto lhsTy = cast<VectorType>(op.getLhs().getType());
1440 auto rhsTy = cast<VectorType>(op.getRhs().getType());
1441 auto accTy = cast<VectorType>(op.getAcc().getType());
1442 if (!checkMatMulTile(aie2p, lhsTy, rhsTy, accTy, op.getLhsTransposed(),
1443 op.getRhsTransposed(),
1444 [&]() { return op.emitOpError(); }))
1445 return failure();
1446 return success();
1447}
1448
1449LogicalResult MatMulOp::verify() { return verifyMatMulOp(*this, false); }
1450
1451LogicalResult MatMulOp_AIE2P::verify() { return verifyMatMulOp(*this, true); }
1452
1453//===----------------------------------------------------------------------===//
1454// ShuffleOp
1455//===----------------------------------------------------------------------===//
1456
1458 // Every mode is named t<w>_<r>x<c> with an optional _lo, _hi or _flip.
1459 StringRef name = stringifyShuffleMode(mode);
1460 ShuffleModeShape shape{};
1461 bool parsed =
1462 name.consume_front("t") && !name.consumeInteger(10, shape.elemBits) &&
1463 name.consume_front("_") && !name.consumeInteger(10, shape.rows) &&
1464 name.consume_front("x") && !name.consumeInteger(10, shape.cols);
1465 shape.flip = name == "_flip";
1466 shape.hi = name == "_hi";
1467 assert(parsed && (name.empty() || name == "_lo" || shape.hi || shape.flip) &&
1468 "unexpected shuffle mode name");
1469 (void)parsed;
1470 return shape;
1471}
1472
1474 return static_cast<uint32_t>(mode) >= 48;
1475}
1476
1477std::array<uint8_t, 64>
1480 unsigned elemBytes = shape.elemBits / 8;
1481 unsigned blockBytes = shape.blockBits() / 8;
1482 unsigned blockElems = shape.rows * shape.cols;
1483 std::array<uint8_t, 64> perm;
1484 for (unsigned out = 0; out < 64; ++out) {
1485 // Position in the 1024-bit result of a two-input mode, or in the 512-bit
1486 // result otherwise.
1487 unsigned pos = shape.isTwoInput() && shape.hi ? out + 64 : out;
1488 unsigned block = pos / blockBytes;
1489 unsigned elem = pos % blockBytes / elemBytes;
1490 unsigned src;
1491 if (shape.flip) {
1492 src = blockElems - 1 - elem;
1493 } else {
1494 // elem = j * rows + i reads in[i * cols + j].
1495 unsigned i = elem % shape.rows, j = elem / shape.rows;
1496 src = i * shape.cols + j;
1497 }
1498 perm[out] = block * blockBytes + src * elemBytes + pos % elemBytes;
1499 }
1500 return perm;
1501}
1502
1503// The mode fixes the element width and whether rhs is needed.
1504LogicalResult ShuffleOp::verify() {
1505 ShuffleMode mode = getMode();
1507 bool requireRhs = shape.isTwoInput();
1508
1509 if (requireRhs && !getRhs())
1510 return emitError() << "shuffle mode '" << stringifyEnum(mode)
1511 << "' requires a second operand";
1512
1513 if (!requireRhs && getRhs())
1514 return emitError() << "shuffle mode '" << stringifyEnum(mode)
1515 << "' does not admit a second operand";
1516
1517 auto elemBitWidth =
1518 cast<VectorType>(getLhs().getType()).getElementTypeBitWidth();
1519 if (shape.elemBits != elemBitWidth)
1520 return emitError() << "shuffle mode '" << stringifyEnum(mode)
1521 << "' requires vectors of " << shape.elemBits
1522 << "-bit elements";
1523
1524 // When the op sits in a device, the device fixes the architecture.
1525 if (isAIE2POnlyShuffleMode(mode))
1526 if (auto target = (*this)->getParentOfType<AIE::AIETarget>())
1527 if (target.getTargetModel().getTargetArch() != AIE::AIEArch::AIE2p)
1528 return emitError() << "shuffle mode '" << stringifyEnum(mode)
1529 << "' is only available on AIE2P";
1530
1531 return success();
1532}
1533
1534// Print out Shuffle op.
1535void LegacyShuffleOp::print(OpAsmPrinter &p) {
1536 // Print the source vector
1537 p << " " << getSource();
1538
1539 // Print the attributes
1540 p.printOptionalAttrDict((*this)->getAttrs());
1541
1542 // And now print the types
1543 p << " : " << getSource().getType() << ", " << getResult().getType();
1544}
1545
1546// Verify Shuffle op.
1547LogicalResult LegacyShuffleOp::verify() {
1548 // Verify the types
1549 VectorType sourceType = llvm::dyn_cast<VectorType>(getSource().getType());
1550 VectorType resultType = llvm::dyn_cast<VectorType>(getResult().getType());
1551 if (!sourceType || !resultType)
1552 return emitError("requires vector type");
1553
1554 // The number of lanes must match
1555 unsigned sourceLanes = getVectorLaneSize(sourceType);
1556 unsigned resultLanes = getVectorLaneSize(resultType);
1557 if (sourceLanes != resultLanes)
1558 return emitError("The number of lanes in input and "
1559 "output vector must match");
1560
1561 Type stype = sourceType.getElementType();
1562 unsigned stypeWidth = stype.getIntOrFloatBitWidth();
1563 Type rtype = resultType.getElementType();
1564 unsigned rtypeWidth = rtype.getIntOrFloatBitWidth();
1565
1566 if (stypeWidth != rtypeWidth)
1567 return emitError("The type width in input and "
1568 "output must match");
1569
1570 return success();
1571}
1572
1573// Parse Shuffle op.
1574ParseResult LegacyShuffleOp::parse(OpAsmParser &parser,
1575 OperationState &result) {
1576 llvm::SMLoc typesLoc;
1577 SmallVector<Type, 2> types;
1578 OpAsmParser::UnresolvedOperand source;
1579
1580 // Parse the source vector
1581 if (parser.parseOperand(source))
1582 return failure();
1583
1584 // Parse all the attributes and types
1585 if (parser.parseOptionalAttrDict(result.attributes) ||
1586 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
1587 return failure();
1588
1589 // Currently there is one attribute in shuffle op
1590 if (result.attributes.getAttrs().size() != 1)
1591 return parser.emitError(typesLoc, "expects one attribute");
1592
1593 // Some verification
1594 VectorType sourceType = llvm::dyn_cast<VectorType>(types[0]);
1595 VectorType resultType = llvm::dyn_cast<VectorType>(types[1]);
1596 if (!sourceType || !resultType)
1597 return parser.emitError(typesLoc, "requires vector type");
1598
1599 // Populate the source vectors in result
1600 if (parser.resolveOperand(source, sourceType, result.operands))
1601 return failure();
1602
1603 return parser.addTypeToList(resultType, result.types);
1604}
1605
1606//===----------------------------------------------------------------------===//
1607// MulConvOp and FMAConvOp
1608//===----------------------------------------------------------------------===//
1609
1610// MulConvOp and FMAConvOp are structurally similar, except that FMAConv op
1611// has few extra fields (accumulator, bool flag to indicate if it is fmsub,
1612// etc.). We create some specializations to print those fields specifically for
1613// FMAConvOp and MULConvOp.
1614
1615// Print the accumulator
1616template <typename T>
1617void printAccumulator(OpAsmPrinter &p, T op);
1618template <>
1619inline void printAccumulator(OpAsmPrinter &p, aievec::FMAConvOp op) {
1620 p << ", " << op.getAcc();
1621}
1622template <>
1623inline void printAccumulator(OpAsmPrinter &p, aievec::MulConvOp op) {}
1624
1625// Mark fmsub indicator as elided if the FMAElem op is not fmsub
1626template <typename T>
1627void elideFMSubAttr(T op, SmallVector<StringRef, 4> &elidedAttrs);
1628template <>
1629inline void elideFMSubAttr(FMAConvOp op,
1630 SmallVector<StringRef, 4> &elidedAttrs) {
1631 if (!op.getFmsub())
1632 elidedAttrs.push_back(op.getSubAttrName());
1633}
1634
1635template <>
1636inline void elideFMSubAttr(MulConvOp op,
1637 SmallVector<StringRef, 4> &elidedAttrs) {}
1638
1639// Print out MulConv and FMAConv op.
1640template <typename T>
1641static void printMulFMAConvOp(OpAsmPrinter &p, T op) {
1642 // Print the left operand
1643 p << " " << op.getLhs();
1644 // Print the right operand
1645 p << ", " << op.getRhs();
1646 // For fma op, print the accumulator
1647 printAccumulator(p, op);
1648
1649 // Print the attributes, but don't print attributes that are empty strings
1650 SmallVector<StringRef, 4> elidedAttrs;
1651 for (int idx = 0; idx < 2; ++idx) {
1652 elideFMSubAttr(op, elidedAttrs);
1653 }
1654 p.printOptionalAttrDict(op->getAttrs(), elidedAttrs);
1655
1656 // And now print the types
1657 p << " : " << op.getLhs().getType() << ", " << op.getRhs().getType();
1658 p << ", " << op.getResult().getType();
1659}
1660
1661void MulConvOp::print(OpAsmPrinter &p) {
1662 printMulFMAConvOp<aievec::MulConvOp>(p, *this);
1663}
1664
1665void aievec::FMAConvOp::print(OpAsmPrinter &p) {
1666 printMulFMAConvOp<aievec::FMAConvOp>(p, *this);
1667}
1668
1669// Verify MulConv and FMAConv op.
1670template <typename T>
1671static LogicalResult verifyMulFMAConvOp(T op) {
1672 // Verify the types
1673 auto lhsType = llvm::dyn_cast<VectorType>(op.getLhs().getType());
1674 auto rhsType = llvm::dyn_cast<VectorType>(op.getRhs().getType());
1675
1676 if (!lhsType || !rhsType)
1677 return op.emitError("requires vector type");
1678
1679 unsigned M = op.getM();
1680 unsigned N = op.getN();
1681
1682 if (M <= 0 || N <= 0 || 2 * M < M + N - 1)
1683 return op.emitError(
1684 "M and N should be larger than 0 and 2*M should be no less than M+N-1");
1685
1686 auto resultType = llvm::dyn_cast<VectorType>(op.getResult().getType());
1687
1688 if (!resultType)
1689 return op.emitError("requires vector type");
1690
1691 // Additional checks for FMAElem op
1692 // Get the width of the underlying scalars of all the vectors
1693 Type ltype = lhsType.getElementType();
1694 Type rtype = rhsType.getElementType();
1695 Type atype = resultType.getElementType();
1696
1697 // lhs and rhs vector's element type must match
1698 if (ltype != rtype)
1699 return op.emitError("The element type of lhs and rhs "
1700 "operand vectors must match");
1701
1702 if (!isa<IntegerType>(ltype) || !isa<IntegerType>(rtype) ||
1703 !isa<IntegerType>(atype)) {
1704 return op.emitError("requires integer type");
1705 }
1706
1707 unsigned ltypeWidth = ltype.getIntOrFloatBitWidth();
1708 unsigned rtypeWidth = rtype.getIntOrFloatBitWidth();
1709 unsigned atypeWidth = atype.getIntOrFloatBitWidth();
1710
1711 // Checks on the number of lanes
1712 unsigned accLanes = getVectorLaneSize(resultType);
1713 unsigned rhsLanes = getVectorLaneSize(rhsType);
1714 unsigned lhsLanes = getVectorLaneSize(lhsType);
1715
1716 // lane size must match
1717 if (accLanes != M || accLanes != (rhsLanes / 2) || lhsLanes != rhsLanes) {
1718 return op.emitError(
1719 "The number of lanes in accumulator "
1720 "must be the same as M and the half as lhs and rhs operand");
1721 }
1722
1723 // The integer datatype of accumulator must always be greater width
1724 if (ltypeWidth >= atypeWidth || rtypeWidth >= atypeWidth)
1725 return op.emitError("the element type of accumulator must have "
1726 "wider width than that of the operand vectors");
1727
1728 return success();
1729}
1730
1731LogicalResult aievec::MulConvOp::verify() {
1732 return verifyMulFMAConvOp<aievec::MulConvOp>(*this);
1733}
1734
1735LogicalResult aievec::FMAConvOp::verify() {
1736 return verifyMulFMAConvOp<aievec::FMAConvOp>(*this);
1737}
1738
1739// Parse MulConv and FMAConv op.
1740static ParseResult parseMulFMAConvOp(OpAsmParser &parser,
1741 OperationState &result,
1742 bool isFMAConvOp = true) {
1743 llvm::SMLoc typesLoc;
1744 SmallVector<Type, 3> types;
1745 OpAsmParser::UnresolvedOperand lhs, rhs, acc;
1746
1747 // Parse the lhs and rhs
1748 if (parser.parseOperand(lhs) || parser.parseComma() ||
1749 parser.parseOperand(rhs))
1750 return failure();
1751
1752 // Parse the acc for FMA op
1753 if (isFMAConvOp) {
1754 if (parser.parseComma() || parser.parseOperand(acc))
1755 return failure();
1756 }
1757
1758 // Parse all the attributes and types
1759 if (parser.parseOptionalAttrDict(result.attributes) ||
1760 parser.getCurrentLocation(&typesLoc) || parser.parseColonTypeList(types))
1761 return failure();
1762
1763 // Assert that there are three types: lhs, rhs, and acc
1764 if (types.size() != 3)
1765 return parser.emitError(typesLoc, "requires three types");
1766
1767 // Some verification
1768 VectorType lhsType = llvm::dyn_cast<VectorType>(types[0]);
1769 if (!lhsType)
1770 return parser.emitError(typesLoc, "requires vector type");
1771 VectorType rhsType = llvm::dyn_cast<VectorType>(types[1]);
1772 if (!rhsType)
1773 return parser.emitError(typesLoc, "requires vector type");
1774
1775 // Int ops use the accumulator
1776 VectorType accType = llvm::dyn_cast<VectorType>(types[2]);
1777 if (!accType)
1778 return parser.emitError(typesLoc, "requires vector type");
1779
1780 // Populate the lhs and rhs operands, and result
1781 if (parser.resolveOperand(lhs, lhsType, result.operands) ||
1782 parser.resolveOperand(rhs, rhsType, result.operands))
1783 return failure();
1784
1785 // Populate acc operand for FMA op
1786 if (isFMAConvOp) {
1787 if (parser.resolveOperand(acc, accType, result.operands))
1788 return failure();
1789 }
1790
1791 return parser.addTypeToList(accType, result.types);
1792}
1793
1794ParseResult MulConvOp::parse(OpAsmParser &parser, OperationState &result) {
1795 return parseMulFMAConvOp(parser, result, false);
1796}
1797
1798ParseResult FMAConvOp::parse(OpAsmParser &parser, OperationState &result) {
1799 return parseMulFMAConvOp(parser, result, true);
1800}
1801
1802#define GET_ATTRDEF_CLASSES
1803#include "aie/Dialect/AIEVec/IR/AIEVecAttributes.cpp.inc"
1804
1805#define GET_OP_CLASSES
1806#include "aie/Dialect/AIEVec/IR/AIEVecOps.cpp.inc"
void printAccumulator(OpAsmPrinter &p, T op)
void elideFMSubAttr(T op, SmallVector< StringRef, 4 > &elidedAttrs)
std::optional< MatMulRecipe > checkMatMulTile(bool aie2p, mlir::VectorType lhsTy, mlir::VectorType rhsTy, mlir::VectorType accTy, bool lhsTransposed, bool rhsTransposed, llvm::function_ref< mlir::InFlightDiagnostic()> emitError=nullptr)
unsigned getVectorLaneSize(mlir::VectorType type)
Definition AIEVecUtils.h:54
bool isAIE2POnlyShuffleMode(ShuffleMode mode)
std::optional< llvm::SmallVector< ShuffleMode, 2 > > getTileTransposeModes(bool aie2p, mlir::Type elemTy, int64_t rows, int64_t cols)
ShuffleModeShape getShuffleModeShape(ShuffleMode mode)
std::optional< MatMulRecipe > getMatMulRecipe(bool aie2p, mlir::Type lhsElemTy, mlir::Type rhsElemTy, mlir::Type accElemTy, int64_t m, int64_t k, int64_t n)
std::array< uint8_t, 64 > getShuffleModeBytePermutation(ShuffleMode mode)