30#include "llvm/ADT/TypeSwitch.h"
36#define GEN_PASS_DEF_CONVERTVECTORTOXEGPU
37#include "mlir/Conversion/Passes.h.inc"
44static bool isZeroConstant(
Value val) {
50 .Case([](FloatAttr floatAttr) {
return floatAttr.getValue().isZero(); })
51 .Case([](IntegerAttr intAttr) {
return intAttr.getValue().isZero(); })
60static bool isZeroOrPoisonPadding(
Value val) {
61 return isZeroConstant(val) || val.
getDefiningOp<ub::PoisonOp>();
71static bool isInnermostTwoDimsTransposed(
AffineMap map) {
78 for (
unsigned i = 0; i + 2 < numResults; ++i)
101 bool hasTranspose =
false) {
102 if (
shape.size() < 2)
107 const auto *blockInst = dyn_cast<xegpu::uArch::BlockIOInstructionInterface>(
112 int width =
static_cast<int>(
shape.back());
113 int height =
static_cast<int>(
shape[
shape.size() - 2]);
119 auto fitsBlockShapes = [&](
bool hasTransform) {
120 std::optional<xegpu::uArch::BlockIOInstructionInterface::BlockShapes>
121 blockShapes = blockInst->getBlockWidthHeightCount(elemTy, hasTransform,
125 auto [widths, heights, counts] = *blockShapes;
134 return fitsBlockShapes(
false) ||
135 fitsBlockShapes(
true);
139 VectorTransferOpInterface xferOp) {
140 if (xferOp.getMask())
142 "Masked transfer is not supported");
144 auto srcTy = dyn_cast<MemRefType>(xferOp.getShapedType());
147 if (isa<VectorType>(srcTy.getElementType()))
149 "Expects a memref of scalar elements");
154 if (
failed(srcTy.getStridesAndOffset(strides, offset)))
156 "The memref strides cannot be inferred");
159 if (strides.back() != 1)
161 xferOp,
"Buffer must be contiguous in the innermost dimension");
163 VectorType vecTy = xferOp.getVectorType();
164 unsigned vecRank = vecTy.getRank();
176 unsigned numAccessibleDims = std::max(vecRank, 2u);
177 unsigned firstAccessibleDim =
178 numInputDims > numAccessibleDims ? numInputDims - numAccessibleDims : 0;
181 auto dim = dyn_cast<AffineDimExpr>(expr);
184 if (dim.getPosition() < firstAccessibleDim)
186 xferOp,
"Only the innermost dimensions can be accessed");
203 return strides[cast<AffineDimExpr>(expr).getPosition()];
212 typename = std::enable_if_t<llvm::is_one_of<
213 std::decay_t<OpType>, vector::TransferReadOp, vector::TransferWriteOp,
214 vector::GatherOp, vector::ScatterOp>::value>>
215static std::pair<SmallVector<Value>,
Value>
218 Value baseMemref = xferOp.getBase();
219 MemRefType memrefType = dyn_cast<MemRefType>(baseMemref.
getType());
222 Value offsetVal =
nullptr;
223 if (memrefType.hasStaticShape()) {
226 if (
failed(memrefType.getStridesAndOffset(intStrides, offset)))
227 return {{}, offsetVal};
228 bool hasDynamicStrides = llvm::any_of(intStrides, [](
int64_t strideVal) {
229 return ShapedType::isDynamic(strideVal);
232 if (!hasDynamicStrides)
236 if (!ShapedType::isDynamic(offset))
240 if (strides.empty() || !offsetVal) {
243 unsigned rank = memrefType.getRank();
249 resultTypes.push_back(MemRefType::get(
250 {}, memrefType.getElementType()));
251 resultTypes.push_back(indexType);
253 for (
unsigned i = 0; i < rank; ++i)
254 resultTypes.push_back(indexType);
256 for (
unsigned i = 0; i < rank; ++i)
257 resultTypes.push_back(indexType);
259 auto meta = memref::ExtractStridedMetadataOp::create(
260 rewriter, loc, resultTypes, baseMemref);
263 strides.append(meta.getStrides().begin(), meta.getStrides().end());
266 offsetVal = meta.getOffset();
271 return {strides, offsetVal};
276static Value computeBaseOffset(VectorTransferOpInterface xferOp,
280 for (
auto [
index, stride] : llvm::zip_equal(xferOp.getIndices(), strides)) {
281 Value contrib = arith::MulIOp::create(rewriter, loc,
index, stride);
282 baseOffset = arith::AddIOp::create(rewriter, loc, baseOffset, contrib);
315static Value computeOffsets(VectorTransferOpInterface xferOp,
319 VectorType vectorType = xferOp.getVectorType();
325 auto stepType = VectorType::get({dim}, rewriter.
getIndexType());
326 auto stepOp = vector::StepOp::create(rewriter, loc, stepType);
327 stepVectors.push_back(stepOp);
332 getStridesInVectorOrder(xferOp.getPermutationMap(), strides);
337 for (
size_t i = 0; i < vectorRank; ++i) {
338 Value strideValue = vectorStrides[i];
339 auto mulType = dyn_cast<VectorType>(stepVectors[i].
getType());
341 vector::BroadcastOp::create(rewriter, loc, mulType, strideValue);
342 auto mulOp = arith::MulIOp::create(rewriter, loc, stepVectors[i], bcastOp);
343 strideMultiplied.push_back(mulOp);
348 for (
size_t i = 0; i < vectorRank; ++i) {
351 auto newType = VectorType::get(newShape, rewriter.
getIndexType());
352 auto castOp = vector::ShapeCastOp::create(rewriter, loc, newType,
353 strideMultiplied[i]);
354 shapeCasted.push_back(castOp);
359 auto fullIndexVectorType =
361 for (
Value shapeCastVal : shapeCasted) {
362 auto broadcastOp = vector::BroadcastOp::create(
363 rewriter, loc, fullIndexVectorType, shapeCastVal);
364 broadcasted.push_back(broadcastOp);
368 Value localOffsets = broadcasted[0];
369 for (
size_t i = 1; i < broadcasted.size(); ++i)
371 arith::AddIOp::create(rewriter, loc, localOffsets, broadcasted[i]);
374 baseOffset = computeBaseOffset(xferOp, rewriter, strides, baseOffset);
375 Value bcastBase = vector::BroadcastOp::create(
376 rewriter, loc, fullIndexVectorType, baseOffset);
377 localOffsets = arith::AddIOp::create(rewriter, loc, bcastBase, localOffsets);
382static Value getMemrefDimSize(VectorTransferOpInterface xferOp,
unsigned dim,
385 auto memrefTy = cast<MemRefType>(xferOp.getShapedType());
386 if (memrefTy.isDynamicDim(dim))
387 return memref::DimOp::create(rewriter, loc, xferOp.getBase(), dim)
408static Value computeInBoundsMask(VectorTransferOpInterface xferOp,
417 for (
unsigned v = 0, e =
vectorShape.size(); v < e; ++v) {
418 if (xferOp.isDimInBounds(v))
420 unsigned d = cast<AffineDimExpr>(map.
getResult(v)).getPosition();
421 Value bound = getMemrefDimSize(xferOp, d, rewriter);
424 Value limit = arith::SubIOp::create(rewriter, loc, bound,
indices[d]);
426 Value step = vector::StepOp::create(rewriter, loc, stepType);
428 vector::BroadcastOp::create(rewriter, loc, stepType, limit);
429 Value dimMask = arith::CmpIOp::create(
430 rewriter, loc, arith::CmpIPredicate::slt, step, limitVec);
434 dimMask = vector::ShapeCastOp::create(
435 rewriter, loc, VectorType::get(expandedShape, rewriter.
getI1Type()),
437 dimMask = vector::BroadcastOp::create(rewriter, loc, maskType, dimMask);
440 ? arith::AndIOp::create(rewriter, loc, mask, dimMask).getResult()
445 return vector::ConstantMaskOp::create(rewriter, loc, maskType,
vectorShape);
456static Value computeUnitInBoundsMask(VectorTransferOpInterface xferOp,
463 for (
unsigned v = 0, e = xferOp.getVectorType().getRank(); v < e; ++v) {
464 if (xferOp.isDimInBounds(v))
466 unsigned d = cast<AffineDimExpr>(map.
getResult(v)).getPosition();
467 Value bound = getMemrefDimSize(xferOp, d, rewriter);
468 Value dimMask = arith::CmpIOp::create(
469 rewriter, loc, arith::CmpIPredicate::slt,
indices[d], bound);
471 ? arith::AndIOp::create(rewriter, loc, mask, dimMask).getResult()
476 return arith::ConstantOp::create(rewriter, loc, rewriter.
getBoolAttr(
true));
486 typename = std::enable_if_t<llvm::is_one_of<
487 std::decay_t<OpType>, vector::GatherOp, vector::ScatterOp>::value>>
492 for (
size_t i = 0; i < offsets.size(); ++i) {
493 Value offsetContrib =
494 arith::MulIOp::create(rewriter, loc, offsets[i], strides[i]);
496 arith::AddIOp::create(rewriter, loc, baseOffset, offsetContrib);
499 VectorType vecType = cast<VectorType>(
indices.getType());
502 vector::BroadcastOp::create(rewriter, loc, vecType, strides.back())
504 Value stridedIndices =
505 arith::MulIOp::create(rewriter, loc, strideVector,
indices).getResult();
508 vector::BroadcastOp::create(
510 VectorType::get(vecType.getShape(), rewriter.
getIndexType()),
513 return arith::AddIOp::create(rewriter, loc, baseVector, stridedIndices)
522static std::pair<Value, SmallVector<OpFoldResult>>
527 auto memrefType = cast<MemRefType>(
memref.getType());
528 unsigned rank = memrefType.getRank();
530 if (rank <= targetRank)
533 int64_t numCombinedDims = rank - targetRank;
539 for (
unsigned i = 0; i < numCombinedDims; ++i) {
540 subviewOffsets.push_back(offsets[i]);
547 auto originalShape = memrefType.getShape();
548 auto meta = memref::ExtractStridedMetadataOp::create(rewriter, loc,
memref);
549 for (
unsigned i = numCombinedDims; i < rank; ++i) {
551 if (ShapedType::isDynamic(originalShape[i])) {
552 subviewSizes.push_back(meta.getSizes()[i]);
553 resultShape.push_back(ShapedType::kDynamic);
556 resultShape.push_back(originalShape[i]);
561 auto resultType = memref::SubViewOp::inferRankReducedResultType(
562 resultShape, memrefType, subviewOffsets, subviewSizes, subviewStrides);
564 memref::SubViewOp::create(rewriter, loc, resultType,
memref,
565 subviewOffsets, subviewSizes, subviewStrides);
570 return {subviewOp.getResult(), newOffsets};
575 typename = std::enable_if_t<llvm::is_one_of<
576 std::decay_t<OpType>, vector::TransferReadOp, vector::TransferWriteOp,
577 vector::GatherOp, vector::ScatterOp>::value>>
581 auto indexPtr = memref::ExtractAlignedPointerAsIndexOp::create(
582 rewriter, loc, xferOp.getBase())
584 return arith::IndexCastOp::create(rewriter, loc, rewriter.
getI64Type(),
591static bool isUsedAsScalar(
Value vec) {
595 auto extractOp = dyn_cast<vector::ExtractOp>(user);
596 return extractOp && !isa<VectorType>(extractOp.getResult().getType());
610static LogicalResult lowerToScalarLoadOp(vector::TransferReadOp readOp,
613 VectorType vectorType = readOp.getVectorType();
614 if (!isa<MemRefType>(readOp.getShapedType()))
617 auto meta = computeMemrefMeta(readOp, rewriter);
618 if (meta.first.empty())
621 Value offset = computeBaseOffset(readOp, rewriter, meta.first, meta.second);
622 Value flatMemref = memrefToIndexPtr(readOp, rewriter);
624 Value mask = computeUnitInBoundsMask(readOp, rewriter);
625 auto loadOp = xegpu::LoadGatherOp::create(
626 rewriter, loc, vectorType.getElementType(), flatMemref, offset, mask,
627 xegpu::CachePolicyAttr{},
628 xegpu::CachePolicyAttr{},
629 xegpu::CachePolicyAttr{},
634 Value scalar = loadOp.getResult();
635 if (readOp.hasOutOfBoundsDim() &&
636 !readOp.getPadding().getDefiningOp<ub::PoisonOp>())
637 scalar = arith::SelectOp::create(rewriter, loc, mask, scalar,
638 readOp.getPadding());
644static LogicalResult lowerToScatteredLoadOp(vector::TransferReadOp readOp,
648 VectorType vectorType = readOp.getVectorType();
649 auto memrefType = dyn_cast<MemRefType>(readOp.getShapedType());
653 auto meta = computeMemrefMeta(readOp, rewriter);
654 if (meta.first.empty())
658 computeOffsets(readOp, rewriter, meta.first, meta.second);
660 Value flatMemref = memrefToIndexPtr(readOp, rewriter);
662 Value mask = computeInBoundsMask(readOp, rewriter);
663 auto gatherOp = xegpu::LoadGatherOp::create(
664 rewriter, loc, vectorType, flatMemref, localOffsets, mask,
665 xegpu::CachePolicyAttr{},
666 xegpu::CachePolicyAttr{},
667 xegpu::CachePolicyAttr{},
674 if (readOp.hasOutOfBoundsDim() &&
675 !readOp.getPadding().getDefiningOp<ub::PoisonOp>()) {
676 Value padding = vector::BroadcastOp::create(rewriter, loc, vectorType,
677 readOp.getPadding());
678 result = arith::SelectOp::create(rewriter, loc, mask,
result, padding);
700static LogicalResult lowerToUnitDimRead(vector::TransferReadOp readOp,
702 VectorType vecTy = readOp.getVectorType();
706 if (numInputs < 2 || pos != numInputs - 1)
708 readOp,
"1D transfer does not run along the innermost dimension");
710 Value block = vector::TransferReadOp::create(
711 rewriter, readOp.getLoc(),
712 VectorType::get({1, vecTy.getDimSize(0)}, vecTy.getElementType()),
713 readOp.getBase(), readOp.getIndices(),
716 readOp.getPadding(),
Value(),
743static LogicalResult lowerBroadcastRead(vector::TransferReadOp readOp,
746 VectorType vecTy = readOp.getVectorType();
749 int64_t vecRank = vecTy.getRank();
752 for (
int64_t i = 0; i < vecRank; ++i) {
753 if (isa<AffineDimExpr>(map.
getResult(i)))
754 dataPos.push_back(i);
756 bcastPos.push_back(i);
764 readShape.push_back(vecShape[i]);
765 readInBounds.push_back(readOp.isDimInBounds(i));
770 if (readExprs.empty()) {
772 readShape.push_back(1);
773 readInBounds.push_back(
true);
776 auto readTy = VectorType::get(readShape, vecTy.getElementType());
777 Value read = vector::TransferReadOp::create(
778 rewriter, loc, readTy, readOp.getBase(), readOp.getIndices(),
781 readOp.getPadding(),
Value(),
790 llvm::append_range(order, dataPos);
793 castShape.push_back(vecShape[i]);
794 auto castTy = VectorType::get(castShape, vecTy.getElementType());
795 Value cast = castTy == readTy
797 : vector::ShapeCastOp::create(rewriter, loc, castTy, read);
799 if (!llvm::is_sorted(order)) {
801 for (
int64_t i = 0; i < vecRank; ++i)
803 cast = vector::TransposeOp::create(rewriter, loc, cast, perm);
813static LogicalResult lowerToScatteredStoreOp(vector::TransferWriteOp writeOp,
817 auto memrefType = dyn_cast<MemRefType>(writeOp.getShapedType());
821 auto meta = computeMemrefMeta(writeOp, rewriter);
822 if (meta.first.empty())
826 computeOffsets(writeOp, rewriter, meta.first, meta.second);
828 Value flatMemref = memrefToIndexPtr(writeOp, rewriter);
832 Value mask = computeInBoundsMask(writeOp, rewriter);
833 xegpu::StoreScatterOp::create(rewriter, loc, writeOp.getVector(), flatMemref,
835 xegpu::CachePolicyAttr{},
836 xegpu::CachePolicyAttr{},
837 xegpu::CachePolicyAttr{},
847struct LegalizeBroadcastTransferRead
851 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
852 PatternRewriter &rewriter)
const override {
853 if (readOp.getPermutationMap().getBroadcastDims().empty())
855 if (
failed(transferPreconditions(rewriter, readOp)))
857 return lowerBroadcastRead(readOp, rewriter);
865static bool is2DBlockLoadable(vector::TransferReadOp readOp,
867 bool hasTranspose =
false) {
868 auto readMemTy = cast<MemRefType>(readOp.getShapedType());
869 if (!readMemTy.getElementType().isIntOrFloat())
871 if (readOp.hasOutOfBoundsDim() && !isZeroOrPoisonPadding(readOp.getPadding()))
875 return isSupportedBlockShape(
877 readOp.getVectorType().getElementType(), hasTranspose);
884struct PromoteTransferReadToUnitDim
888 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
889 PatternRewriter &rewriter)
const override {
890 VectorType vecTy = readOp.getVectorType();
891 if (vecTy.getRank() != 1)
894 if (!readOp.getPermutationMap().getBroadcastDims().empty())
896 if (
failed(transferPreconditions(rewriter, readOp)))
900 if (xegpu::XeGPUDialect::isSharedMemory(
901 cast<MemRefType>(readOp.getShapedType())))
904 if (vecTy.getNumElements() == 1 && isUsedAsScalar(readOp.getResult()))
906 if (!is2DBlockLoadable(readOp, {1, vecTy.getDimSize(0)}))
908 readOp,
"<1xN> tile is not accessible by a 2D block load");
910 return lowerToUnitDimRead(readOp, rewriter);
914struct TransferReadLowering :
public OpRewritePattern<vector::TransferReadOp> {
917 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
918 PatternRewriter &rewriter)
const override {
919 Location loc = readOp.getLoc();
921 if (
failed(transferPreconditions(rewriter, readOp)))
926 if (!readOp.getPermutationMap().getBroadcastDims().empty())
928 readOp,
"Broadcast permutation map must be legalized first");
930 auto readMemTy = cast<MemRefType>(readOp.getShapedType());
931 VectorType loadedVecTy = readOp.getVectorType();
932 bool isOutOfBounds = readOp.hasOutOfBoundsDim();
934 bool isSharedMemory = xegpu::XeGPUDialect::isSharedMemory(readMemTy);
938 if (loadedVecTy.getRank() != 1 && loadedVecTy.getRank() != 2)
940 readOp,
"Only 1D and 2D vector loads are supported for SLM");
941 AffineMap readMap = readOp.getPermutationMap();
945 "Non identity transposition is not supported for SLM loads.");
949 readOp,
"Out-of-bounds access is not supported for SLM loads");
953 xegpu::MemDescType::get(rewriter.
getContext(), readMemTy.getShape(),
954 readMemTy.getElementType(),
956 auto createMemDescOp = xegpu::CreateMemDescOp::create(
957 rewriter, loc, memDescType, readOp.getBase());
959 SmallVector<OpFoldResult>
indices =
961 auto loadMatrixOp = xegpu::LoadMatrixOp::create(
962 rewriter, loc, loadedVecTy, createMemDescOp.getResult(),
indices,
965 rewriter.
replaceOp(readOp, loadMatrixOp.getResult());
973 if (loadedVecTy.getNumElements() == 1 && isUsedAsScalar(readOp.getResult()))
974 return lowerToScalarLoadOp(readOp, rewriter);
981 bool isTransposeLoad = isInnermostTwoDimsTransposed(readMap);
985 Type elementType = loadedVecTy.getElementType();
986 SmallVector<int64_t> descShape(loadedVecTy.getShape());
987 if (isTransposeLoad) {
988 size_t rank = descShape.size();
989 assert(rank >= 2 &&
"Transpose requires at least 2 dimensions");
990 std::swap(descShape[rank - 1], descShape[rank - 2]);
995 bool needsTransposedBlockLoad = isTransposeLoad &&
996 descShape[descShape.size() - 1] != 1 &&
997 descShape[descShape.size() - 2] != 1;
1005 bool canLowerToLoadNd =
1006 loadedVecTy.getRank() > 1 &&
1008 is2DBlockLoadable(readOp, descShape,
1009 needsTransposedBlockLoad);
1011 if (canLowerToLoadNd) {
1014 if (isTransposeLoad)
1015 loadedVecTy = VectorType::get(descShape, elementType);
1016 auto descType = xegpu::TensorDescType::get(
1017 descShape, elementType, 1,
1018 isOutOfBounds, xegpu::MemorySpace::Global);
1019 auto [src,
indices] = convertMemrefAndOffsetsToTargetRank(
1020 rewriter, loc, readOp.getBase(),
1023 xegpu::CachePolicyAttr hint =
nullptr;
1024 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
1027 Operation *loadedOp =
1028 xegpu::LoadNdOp::create(rewriter, loc, loadedVecTy, ndDesc,
indices,
1033 if (isTransposeLoad) {
1037 int64_t rank = loadedVecTy.getRank();
1038 SmallVector<int64_t> perm(llvm::to_vector(llvm::seq<int64_t>(0, rank)));
1039 std::swap(perm[rank - 1], perm[rank - 2]);
1040 loadedOp = vector::TransposeOp::create(rewriter, loc,
1049 return lowerToScatteredLoadOp(readOp, rewriter);
1053struct TransferWriteLowering
1057 LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp,
1058 PatternRewriter &rewriter)
const override {
1059 Location loc = writeOp.getLoc();
1061 if (
failed(transferPreconditions(rewriter, writeOp)))
1064 VectorType vecTy = writeOp.getVectorType();
1065 auto writeMemTy = cast<MemRefType>(writeOp.getShapedType());
1067 bool isSharedMemory = xegpu::XeGPUDialect::isSharedMemory(writeMemTy);
1073 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
1075 writeOp,
"Only 1D and 2D vector stores are supported for SLM");
1079 if (!writeOp.getPermutationMap().isMinorIdentity())
1082 "Non identity transposition is not supported for SLM stores.");
1084 if (writeOp.hasOutOfBoundsDim())
1086 writeOp,
"Out-of-bounds access is not supported for SLM stores");
1089 xegpu::MemDescType::get(rewriter.
getContext(), writeMemTy.getShape(),
1090 writeMemTy.getElementType(),
1093 auto createMemDescOp = xegpu::CreateMemDescOp::create(
1094 rewriter, loc, memDescType, writeOp.getBase());
1097 SmallVector<OpFoldResult>
indices =
1100 xegpu::StoreMatrixOp::create(rewriter, loc, writeOp.getVector(),
1101 createMemDescOp.getResult(),
indices,
1108 const xegpu::uArch::uArch *uArch =
1118 bool canLowerToStoreNd =
1120 writeMemTy.getElementType().isIntOrFloat() &&
1121 isSupportedBlockShape(
1122 uArch, xegpu::uArch::InstructionKind::Subgroup2DBlockStore,
1123 vecTy.getShape(), vecTy.getElementType());
1125 if (canLowerToStoreNd) {
1126 auto [src,
indices] = convertMemrefAndOffsetsToTargetRank(
1127 rewriter, loc, writeOp.getBase(),
1130 auto descType = xegpu::TensorDescType::get(
1131 vecTy.getShape(), vecTy.getElementType(),
1132 1, writeOp.hasOutOfBoundsDim(),
1133 xegpu::MemorySpace::Global);
1135 xegpu::CachePolicyAttr hint =
nullptr;
1136 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
1139 auto storeOp = xegpu::StoreNdOp::create(
1140 rewriter, loc, writeOp.getVector(), ndDesc,
indices,
1150 return lowerToScatteredStoreOp(writeOp, rewriter);
1157 LogicalResult matchAndRewrite(vector::GatherOp gatherOp,
1158 PatternRewriter &rewriter)
const override {
1159 auto srcTy = dyn_cast<MemRefType>(gatherOp.getBase().getType());
1163 if (gatherOp.getVectorType().isScalable())
1165 "Scalable vectors are not supported");
1167 Location loc = gatherOp.getLoc();
1168 VectorType vectorType = gatherOp.getVectorType();
1170 auto meta = computeMemrefMeta(gatherOp, rewriter);
1171 if (meta.first.empty())
1174 Value localOffsets =
1175 computeOffsets(rewriter, gatherOp, meta.first, meta.second);
1176 Value flatMemref = memrefToIndexPtr(gatherOp, rewriter);
1178 auto xeGatherOp = xegpu::LoadGatherOp::create(
1179 rewriter, loc, vectorType, flatMemref, localOffsets, gatherOp.getMask(),
1180 xegpu::CachePolicyAttr{},
1181 xegpu::CachePolicyAttr{},
1182 xegpu::CachePolicyAttr{},
1186 arith::SelectOp::create(rewriter, loc, gatherOp.getMask(),
1187 xeGatherOp.getResult(), gatherOp.getPassThru());
1188 rewriter.
replaceOp(gatherOp, selectOp.getResult());
1196 LogicalResult matchAndRewrite(vector::ScatterOp scatterOp,
1197 PatternRewriter &rewriter)
const override {
1198 auto srcTy = dyn_cast<MemRefType>(scatterOp.getBase().getType());
1202 if (scatterOp.getVectorType().isScalable())
1204 "Scalable vectors are not supported");
1206 Location loc = scatterOp.getLoc();
1207 auto meta = computeMemrefMeta(scatterOp, rewriter);
1208 if (meta.first.empty())
1210 "Failed to compute strides");
1212 Value localOffsets =
1213 computeOffsets(rewriter, scatterOp, meta.first, meta.second);
1214 Value flatMemref = memrefToIndexPtr(scatterOp, rewriter);
1216 xegpu::StoreScatterOp::create(rewriter, loc, scatterOp.getValueToStore(),
1217 flatMemref, localOffsets, scatterOp.getMask(),
1218 xegpu::CachePolicyAttr{},
1219 xegpu::CachePolicyAttr{},
1220 xegpu::CachePolicyAttr{},
1231 LogicalResult matchAndRewrite(vector::LoadOp loadOp,
1232 PatternRewriter &rewriter)
const override {
1233 Location loc = loadOp.getLoc();
1235 VectorType vecTy = loadOp.getResult().getType();
1236 MemRefType memTy = loadOp.getBase().getType();
1238 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
1240 if (!memTy.getElementType().isIntOrFloat())
1242 loadOp,
"Unsupported memref element type: expected integer or float");
1245 bool boundaryCheck = vecTy.getRank() > 1;
1247 xegpu::CachePolicyAttr hint =
nullptr;
1249 auto [src,
indices] = convertMemrefAndOffsetsToTargetRank(
1253 auto descType = xegpu::TensorDescType::get(
1254 vecTy.getShape(), vecTy.getElementType(), 1,
1255 boundaryCheck, xegpu::MemorySpace::Global);
1257 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
1260 xegpu::LoadNdOp::create(rewriter, loc, vecTy, ndDesc,
indices,
1274 LogicalResult matchAndRewrite(vector::StoreOp storeOp,
1275 PatternRewriter &rewriter)
const override {
1276 Location loc = storeOp.getLoc();
1279 VectorType vecTy = vector.getType();
1280 MemRefType memTy = storeOp.getBase().getType();
1282 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
1284 if (!memTy.getElementType().isIntOrFloat())
1287 "Unsupported memref element type: expected integer or float");
1290 bool boundaryCheck = vecTy.getRank() > 1;
1292 auto [src,
indices] = convertMemrefAndOffsetsToTargetRank(
1293 rewriter, loc, storeOp.getBase(),
1296 auto descType = xegpu::TensorDescType::get(
1297 vecTy.getShape(), vecTy.getElementType(),
1298 1, boundaryCheck, xegpu::MemorySpace::Global);
1301 xegpu::CachePolicyAttr hint =
nullptr;
1302 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
1306 xegpu::StoreNdOp::create(rewriter, loc, vector, ndDesc,
indices,
1321static std::optional<int64_t>
1322getRowMajorMatmulBatchRank(
ArrayAttr indexingMaps) {
1323 if (indexingMaps.size() != 3)
1324 return std::nullopt;
1326 AffineMap mapA = cast<AffineMapAttr>(indexingMaps[0]).getValue();
1327 AffineMap mapB = cast<AffineMapAttr>(indexingMaps[1]).getValue();
1328 AffineMap mapC = cast<AffineMapAttr>(indexingMaps[2]).getValue();
1332 return std::nullopt;
1337 unsigned numDims =
static_cast<unsigned>(batchRank) + 3;
1338 unsigned numOperandResults =
static_cast<unsigned>(batchRank) + 2;
1341 return std::nullopt;
1344 return std::nullopt;
1364 auto expected = ArrayAttr::get(
1368 AffineMapAttr::get(
AffineMap::get(numDims, 0, cDims, context))});
1369 if (indexingMaps != expected)
1370 return std::nullopt;
1378 matchAndRewriteAddContractOp(vector::ContractionOp contractOp,
1379 PatternRewriter &rewriter)
const override {
1380 Location loc = contractOp.getLoc();
1385 VectorType accType = dyn_cast<VectorType>(acc.getType());
1389 std::optional<int64_t> batchRank =
1390 getRowMajorMatmulBatchRank(contractOp.getIndexingMapsAttr());
1394 "Expects a (batched) row-major matmul: leading dims must "
1395 "be batch dims shared by lhs, rhs, and acc; innermost two "
1396 "dims must be (M, K), (K, N), and (M, N)");
1401 "Expects operands of rank 4 or less");
1403 auto dpasOp = xegpu::DpasOp::create(
1404 rewriter, loc, contractOp.getResultType(),
lhs,
rhs, acc,
1405 nullptr,
nullptr,
nullptr);
1414static vector::ShapeCastOp getFlattenCast(
Value flat, VectorType ndType) {
1416 if (shapeCast && shapeCast.getSourceVectorType() == ndType)
1428static vector::BroadcastOp getSplatBroadcast(
Value flat) {
1437static bool canUnflatten(
Value flat, VectorType ndType) {
1438 return getFlattenCast(flat, ndType) || getDenseConstant(flat) ||
1439 getSplatBroadcast(flat);
1444 VectorType ndType) {
1445 assert(canUnflatten(flat, ndType) &&
"expected the cast to fold away");
1446 return vector::ShapeCastOp::create(rewriter, flat.
getLoc(), ndType, flat);
1470template <
typename OpTy>
1472 using OpRewritePattern<OpTy>::OpRewritePattern;
1474 LogicalResult matchAndRewrite(OpTy op,
1475 PatternRewriter &rewriter)
const override {
1476 constexpr bool isGather = std::is_same_v<OpTy, vector::GatherOp>;
1478 if (!isa<MemRefType>(op.getBase().getType()))
1481 if (op.getIndexVectorType().getRank() != 1)
1487 op.getIndices().template getDefiningOp<vector::ShapeCastOp>();
1488 if (!indexCast || indexCast.getSourceVectorType().getRank() < 2)
1490 op,
"index vector is not a shape_cast of an N-D vector");
1491 VectorType ndIndexType = indexCast.getSourceVectorType();
1492 VectorType ndMaskType =
1493 ndIndexType.cloneWith(std::nullopt, rewriter.
getI1Type());
1494 VectorType ndType = ndIndexType.cloneWith(
1495 std::nullopt, op.getVectorType().getElementType());
1499 if (!canUnflatten(op.getMask(), ndMaskType))
1502 if constexpr (isGather) {
1503 if (!canUnflatten(op.getPassThru(), ndType))
1505 "cannot un-flatten the pass-thru");
1507 Value mask = unflatten(rewriter, op.getMask(), ndMaskType);
1508 Value passThru = unflatten(rewriter, op.getPassThru(), ndType);
1509 auto ndGather = vector::GatherOp::create(
1510 rewriter, op.getLoc(), ndType, op.getBase(), op.getOffsets(),
1511 indexCast.getSource(), mask, passThru, op.getAlignmentAttr());
1512 ndGather->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1516 if (!canUnflatten(op.getValueToStore(), ndType))
1518 op,
"cannot un-flatten the stored value");
1520 Value mask = unflatten(rewriter, op.getMask(), ndMaskType);
1521 Value valueToStore = unflatten(rewriter, op.getValueToStore(), ndType);
1525 op.getIndicesMutable().assign(indexCast.getSource());
1526 op.getMaskMutable().assign(mask);
1527 op.getValueToStoreMutable().assign(valueToStore);
1555struct TransposedBroadcastToStretch
1557 using OpRewritePattern<vector::TransposeOp>::OpRewritePattern;
1559 LogicalResult matchAndRewrite(vector::TransposeOp op,
1560 PatternRewriter &rewriter)
const override {
1561 auto bcast = op.getVector().getDefiningOp<vector::BroadcastOp>();
1566 auto srcTy = dyn_cast<VectorType>(bcast.getSourceType());
1567 if (!srcTy || srcTy.getRank() < 1)
1569 "broadcast source is not a vector");
1571 VectorType bcastTy = bcast.getResultVectorType();
1572 if (srcTy.isScalable() || bcastTy.isScalable())
1575 int64_t srcRank = srcTy.getRank();
1576 int64_t numNewDims = bcastTy.getRank() - srcRank;
1581 if (srcTy.getShape() != bcastTy.getShape().take_back(srcRank))
1583 "broadcast stretches a source dim");
1587 ArrayRef<int64_t> perm = op.getPermutation();
1588 for (int64_t i = 0; i < srcRank; ++i)
1589 if (perm[i] != numNewDims + i)
1591 op,
"source dims do not move to the front in order");
1592 for (int64_t i = 0; i < numNewDims; ++i)
1593 if (perm[srcRank + i] != i)
1595 op,
"broadcast dims do not stay trailing in order");
1598 SmallVector<int64_t> castShape(srcTy.getShape());
1599 castShape.append(numNewDims, 1);
1600 auto castTy = VectorType::get(castShape, srcTy.getElementType());
1602 DictionaryAttr attrs = op->getDiscardableAttrDictionary();
1603 auto cast = vector::ShapeCastOp::create(rewriter, op.getLoc(), castTy,
1606 op, op.getResultVectorType(), cast);
1607 stretch->setDiscardableAttrs(attrs);
1614static LogicalResult legalizeForVectorToXeGPU(
Operation *root) {
1621static MemRefType withMemorySpace(MemRefType memrefTy,
Attribute newMemSpace) {
1622 return MemRefType::get(memrefTy.getShape(), memrefTy.getElementType(),
1623 memrefTy.getLayout(), newMemSpace);
1636static void promoteAllocasToSLM(
Operation *root) {
1638 Attribute slmAttr = IntegerAttr::get(IntegerType::get(ctx, 64), 3);
1644 auto isMemrefResultOp = [](
Operation *op) {
1647 return llvm::any_of(op->getResultTypes(),
1648 [](
Type t) { return isa<MemRefType>(t); });
1654 auto memrefTy = dyn_cast<MemRefType>(v.getType());
1655 if (!memrefTy || xegpu::XeGPUDialect::isSharedMemory(memrefTy))
1657 v.setType(withMemorySpace(memrefTy, slmAttr));
1659 if (!isMemrefResultOp(user))
1667 root->
walk([&](memref::AllocaOp op) {
1668 auto memrefTy = dyn_cast<MemRefType>(op.getResult().getType());
1669 if (!memrefTy || xegpu::XeGPUDialect::isSharedMemory(memrefTy))
1671 allocas.push_back(op);
1674 for (memref::AllocaOp alloca : allocas) {
1676 auto memrefTy = cast<MemRefType>(alloca.getResult().getType());
1677 auto newTy = withMemorySpace(memrefTy, slmAttr);
1678 auto newOp = memref::AllocaOp::create(
1679 builder, alloca.getLoc(), newTy, alloca.getDynamicSizes(),
1680 alloca.getSymbolOperands(), alloca.getAlignmentAttr());
1681 alloca.getResult().replaceAllUsesWith(newOp.getResult());
1685 if (!isMemrefResultOp(user))
1693struct ConvertVectorToXeGPUPass
1695 void runOnOperation()
override {
1698 promoteAllocasToSLM(getOperation());
1703 if (
failed(legalizeForVectorToXeGPU(getOperation())))
1704 return signalPassFailure();
1710 return signalPassFailure();
1719 .
add<TransferReadLowering, TransferWriteLowering, LoadLowering,
1720 ScatterLowering, GatherLowering, StoreLowering, ContractionLowering>(
1724 patterns.
add<PromoteTransferReadToUnitDim>(ctx, 2);
1734 patterns.
add<UnflattenGatherScatter<vector::GatherOp>,
1735 UnflattenGatherScatter<vector::ScatterOp>,
1736 TransposedBroadcastToStretch>(ctx);
1737 vector::ShapeCastOp::getCanonicalizationPatterns(patterns, ctx);
1740 patterns.
add<LegalizeBroadcastTransferRead>(ctx);
static std::optional< VectorShape > vectorShape(Type type)
static Value broadcast(Location loc, Value toBroadcast, unsigned numElements, const TypeConverter &typeConverter, ConversionPatternRewriter &rewriter)
Broadcasts the value to vector with numElements number of elements.
static bool isSharedMemory(MemRefType type)
Return true if this is a shared memory memref type.
Base type for affine expression.
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
static AffineMap getMinorIdentityMap(unsigned dims, unsigned results, MLIRContext *context)
Returns an identity affine map (d0, ..., dn) -> (dp, ..., dn) on the most minor dimensions.
MLIRContext * getContext() const
bool isMinorIdentity() const
Returns true if this affine map is a minor identity, i.e.
static AffineMap get(MLIRContext *context)
Returns a zero result affine map with no dimensions or symbols: () -> ().
bool isProjectedPermutation(bool allowZeroInResults=false) const
Returns true if the AffineMap represents a subset (i.e.
ArrayRef< AffineExpr > getResults() const
unsigned getNumResults() const
unsigned getNumInputs() const
AffineExpr getResult(unsigned idx) const
static AffineMap getPermutationMap(ArrayRef< unsigned > permutation, MLIRContext *context)
Returns an AffineMap representing a permutation.
Attributes are known-constant values of operations.
IntegerAttr getI64IntegerAttr(int64_t value)
BoolAttr getBoolAttr(bool value)
AffineExpr getAffineDimExpr(unsigned position)
MLIRContext * getContext() const
ArrayAttr getBoolArrayAttr(ArrayRef< bool > values)
An attribute that represents a reference to a dense vector or tensor object.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
MLIRContext is the top-level object for a collection of MLIR operations.
This class helps build Operations.
This class implements the operand iterators for the Operation class.
Operation is the basic unit of execution within MLIR.
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
std::enable_if_t< llvm::function_traits< std::decay_t< FnT > >::num_args==1, RetT > walk(FnT &&callback)
Walk the operation by calling the callback for each nested operation (including this one),...
user_range getUsers()
Returns a range of all users.
MLIRContext * getContext()
Return the context this operation is associated with.
A special type of RewriterBase that coordinates the application of a rewrite pattern on the current I...
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
virtual void eraseOp(Operation *op)
This method erases an operation that is known to have no uses.
std::enable_if_t<!std::is_convertible< CallbackT, Twine >::value, LogicalResult > notifyMatchFailure(Location loc, CallbackT &&reasonCallback)
Used to notify the listener that the IR failed to be rewritten because of a match failure,...
void modifyOpInPlace(Operation *root, CallableT &&callable)
This method is a utility wrapper around an in-place modification of an operation.
OpTy replaceOpWithNewOp(Operation *op, Args &&...args)
Replace the results of the given (original) op with a new op that is created without verification (re...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
bool use_empty() const
Returns true if this value has no uses.
Type getType() const
Return the type of this value.
user_range getUsers() const
Location getLoc() const
Return the location of this value.
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
const uArch * getUArch(llvm::StringRef archName)
int getLargestDivisor(T dim, ArrayRef< T > candidates, ArrayRef< T > candidateMultiples={})
Helper Function to find a proper instruction multiple for the user-supplied sg-level data shape (dive...
std::optional< std::string > getChipStr(Operation *op)
Retrieves the chip string from the XeVM target attribute of the parent GPU module operation.
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
void populateVectorToXeGPULegalizationPatterns(RewritePatternSet &patterns)
Collect a set of patterns that bring vector ops into a form the conversion patterns lower directly: g...
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
LogicalResult applyPatternsGreedily(Region ®ion, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
bool isMemoryEffectFree(Operation *op)
Returns true if the given operation is free of memory effects.
std::conditional_t< std::is_same_v< Ty, mlir::Type >, mlir::Value, detail::TypedValue< Ty > > TypedValue
If Ty is mlir::Type this will select Value instead of having a wrapper around it.
void populateVectorToXeGPUConversionPatterns(RewritePatternSet &patterns)
Collect a set of patterns to convert from the vector to XeGPU ops.
llvm::TypeSwitch< T, ResultT > TypeSwitch
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
void populatePrepareVectorToMMAPatterns(RewritePatternSet &patterns)
Populate patterns that prepare vector operations for lowering to GPU dialect MMA matrix operations.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...
Convenience wrapper for patterns that are only intended for default contractions, i....
bool isSupportedInstruction(InstructionKind instr) const
const Instruction * getInstruction(InstructionKind instKind) const