MLIR 24.0.0git
VectorToXeGPU.cpp
Go to the documentation of this file.
1//===- VectorToXeGPU.cpp - Convert vector to XeGPU dialect ------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file implements lowering of vector operations to XeGPU dialect ops.
10//
11//===----------------------------------------------------------------------===//
12
15
24#include "mlir/IR/Matchers.h"
26#include "mlir/Pass/Pass.h"
28#include "llvm/ADT/TypeSwitch.h"
29
30#include <algorithm>
31#include <optional>
32
33namespace mlir {
34#define GEN_PASS_DEF_CONVERTVECTORTOXEGPU
35#include "mlir/Conversion/Passes.h.inc"
36} // namespace mlir
37
38using namespace mlir;
39
40namespace {
41
42// Return true if value represents a zero constant.
43static bool isZeroConstant(Value val) {
44 auto constant = val.getDefiningOp<arith::ConstantOp>();
45 if (!constant)
46 return false;
47
48 return TypeSwitch<Attribute, bool>(constant.getValue())
49 .Case([](FloatAttr floatAttr) { return floatAttr.getValue().isZero(); })
50 .Case([](IntegerAttr intAttr) { return intAttr.getValue().isZero(); })
51 .Default(false);
52}
53
54// Return true if the transfer padding value is compatible with the implicit
55// padding of an nd block load. LoadNdOp fills out-of-bounds elements with zero,
56// so a zero constant matches its semantics exactly. A poison padding means the
57// out-of-bounds elements are "don't care", so any implicit padding (including
58// zero) is also acceptable.
59static bool isZeroOrPoisonPadding(Value val) {
60 return isZeroConstant(val) || val.getDefiningOp<ub::PoisonOp>();
61}
62
63// Return true if the permutation map keeps every dimension in place except the
64// innermost two, which are swapped, e.g.:
65// (d0, d1) -> (d1, d0)
66// (d0, d1, d2) -> (d2, d1)
67// (d0, d1, d2, d3) -> (d0, d1, d3, d2)
68// This is the only non-identity permutation an nd block load can realize (by
69// loading the untransposed block and applying a trailing vector.transpose).
70static bool isInnermostTwoDimsTransposed(AffineMap map) {
71 unsigned numResults = map.getNumResults();
72 if (numResults < 2)
73 return false;
74 MLIRContext *ctx = map.getContext();
75 unsigned numInputs = map.getNumInputs();
76 // All but the innermost two results must match the minor-identity map.
77 for (unsigned i = 0; i + 2 < numResults; ++i)
78 if (map.getResult(i) != getAffineDimExpr(numInputs - numResults + i, ctx))
79 return false;
80 // The innermost two results must be the last two input dims, swapped.
81 return map.getResult(numResults - 2) ==
82 getAffineDimExpr(numInputs - 1, ctx) &&
83 map.getResult(numResults - 1) == getAffineDimExpr(numInputs - 2, ctx);
84}
85
86static LogicalResult transferPreconditions(PatternRewriter &rewriter,
87 VectorTransferOpInterface xferOp) {
88 if (xferOp.getMask())
89 return rewriter.notifyMatchFailure(xferOp,
90 "Masked transfer is not supported");
91
92 auto srcTy = dyn_cast<MemRefType>(xferOp.getShapedType());
93 if (!srcTy)
94 return rewriter.notifyMatchFailure(xferOp, "Expects memref source");
95
96 // Validate further transfer op semantics.
98 int64_t offset;
99 if (failed(srcTy.getStridesAndOffset(strides, offset)))
100 return rewriter.notifyMatchFailure(xferOp,
101 "The memref strides cannot be inferred");
102 if (strides.empty())
103 return rewriter.notifyMatchFailure(xferOp, "0D memref is not supported");
104 if (strides.back() != 1)
105 return rewriter.notifyMatchFailure(
106 xferOp, "Buffer must be contiguous in the innermost dimension");
107
108 VectorType vecTy = xferOp.getVectorType();
109 unsigned vecRank = vecTy.getRank();
110 if (vecRank == 0)
111 return rewriter.notifyMatchFailure(xferOp, "0D vectors are not supported");
112 if (xferOp.hasOutOfBoundsDim() && vecRank < 2)
113 return rewriter.notifyMatchFailure(
114 xferOp, "Boundary check is available only for block instructions.");
115
116 AffineMap map = xferOp.getPermutationMap();
117 if (!map.isProjectedPermutation(/*allowZeroInResults=*/false))
118 return rewriter.notifyMatchFailure(xferOp, "Unsupported permutation map");
119 unsigned numInputDims = map.getNumInputs();
120 for (AffineExpr expr : map.getResults().take_back(vecRank)) {
121 auto dim = dyn_cast<AffineDimExpr>(expr);
122 if (dim.getPosition() < (numInputDims - vecRank))
123 return rewriter.notifyMatchFailure(
124 xferOp, "Only the innermost dimensions can be accessed");
125 }
126
127 return success();
128}
129
130// Adjusts the strides of a memref according to a given permutation map for
131// vector operations.
132//
133// This function updates the innermost strides in the `strides` array to
134// reflect the permutation specified by `permMap`. The permutation is computed
135// using the inverse and broadcasting-aware version of the permutation map,
136// and is applied to the relevant strides. This ensures that memory accesses
137// are consistent with the logical permutation of vector elements.
138//
139// Example:
140// Suppose we have a memref of rank 4 with strides `[s0, s1, s2, s3]`.
141// If the permutation map swaps the last two dimensions (e.g., [0, 1] -> [1,
142// 0]), then after calling this function, the last two strides will be
143// swapped:
144// Original strides: [s0, s1, s2, s3]
145// After permutation: [s0, s1, s3, s2]
146//
147static void adjustStridesForPermutation(AffineMap permMap,
148 SmallVectorImpl<Value> &strides) {
149
153 SmallVector<int64_t> perms64(perms.begin(), perms.end());
154 strides = applyPermutation(strides, perms64);
155}
156
157// Computes memory strides and a memref offset for vector transfer operations,
158// handling both static and dynamic memrefs while applying permutation
159// transformations for XeGPU lowering.
160template <
161 typename OpType,
162 typename = std::enable_if_t<llvm::is_one_of<
163 std::decay_t<OpType>, vector::TransferReadOp, vector::TransferWriteOp,
164 vector::GatherOp, vector::ScatterOp>::value>>
165static std::pair<SmallVector<Value>, Value>
166computeMemrefMeta(OpType xferOp, PatternRewriter &rewriter) {
167 SmallVector<Value> strides;
168 Value baseMemref = xferOp.getBase();
169 MemRefType memrefType = dyn_cast<MemRefType>(baseMemref.getType());
170
171 Location loc = xferOp.getLoc();
172 Value offsetVal = nullptr;
173 if (memrefType.hasStaticShape()) {
174 int64_t offset;
175 SmallVector<int64_t> intStrides;
176 if (failed(memrefType.getStridesAndOffset(intStrides, offset)))
177 return {{}, offsetVal};
178 bool hasDynamicStrides = llvm::any_of(intStrides, [](int64_t strideVal) {
179 return ShapedType::isDynamic(strideVal);
180 });
181
182 if (!hasDynamicStrides)
183 for (int64_t s : intStrides)
184 strides.push_back(arith::ConstantIndexOp::create(rewriter, loc, s));
185
186 if (!ShapedType::isDynamic(offset))
187 offsetVal = arith::ConstantIndexOp::create(rewriter, loc, offset);
188 }
189
190 if (strides.empty() || !offsetVal) {
191 // For dynamic shape memref, use memref.extract_strided_metadata to get
192 // stride values
193 unsigned rank = memrefType.getRank();
194 Type indexType = rewriter.getIndexType();
195
196 // Result types: [base_memref, offset, stride0, stride1, ..., strideN-1,
197 // size0, size1, ..., sizeN-1]
198 SmallVector<Type> resultTypes;
199 resultTypes.push_back(MemRefType::get(
200 {}, memrefType.getElementType())); // base memref (unranked)
201 resultTypes.push_back(indexType); // offset
202
203 for (unsigned i = 0; i < rank; ++i)
204 resultTypes.push_back(indexType); // strides
205
206 for (unsigned i = 0; i < rank; ++i)
207 resultTypes.push_back(indexType); // sizes
208
209 auto meta = memref::ExtractStridedMetadataOp::create(
210 rewriter, loc, resultTypes, baseMemref);
211
212 if (strides.empty())
213 strides.append(meta.getStrides().begin(), meta.getStrides().end());
214
215 if (!offsetVal)
216 offsetVal = meta.getOffset();
217 }
218
219 // Strides are returned in original memref order; permutation is applied in
220 // computeOffsets only where offsets are indexed in vector order.
221 return {strides, offsetVal};
222}
223
224// This function compute the vectors of localOffsets for scattered load/stores.
225// It is used in the lowering of vector.transfer_read/write to
226// load_gather/store_scatter Example:
227// %0 = vector.transfer_read %expand_shape[%block_id_y, %c0, %c0, %c0, %c0],
228// %cst {in_bounds = [true, true, true, true]}>} :
229// memref<8x4x2x6x32xbf16>, vector<4x2x6x32xbf16>
230//
231// %6 = vector.step: vector<4xindex>
232// %7 = vector.step: vector<2xindex>
233// %8 = vector.step: vector<6xindex>
234// %9 = vector.step: vector<32xindex>
235// %10 = arith.mul %6, 384
236// %11 = arith.mul %7, 192
237// %12 = arith.mul %8, 32
238// %13 = arith.mul %9, 1
239// %14 = vector.shape_cast %10: vector<4xindex> -> vector<4x1x1x1xbf16>
240// %15 = vector.shape_cast %11: vector<2xindex> -> vector<1x2x1x1xbf16>
241// %16 = vector.shape_cast %12: vector<6xindex> -> vector<1x1x6x1xbf16>
242// %17 = vector.shape_cast %13: vector<32xindex> -> vector<1x1x1x32xbf16>
243// %18 = vector.broadcast %14: vector<4x1x1x1xbf16> -> vector<4x2x6x32xindex>
244// %19 = vector.broadcast %15: vector<1x2x1x1xbf16> -> vector<4x2x6x32xindex>
245// %20 = vector.broadcast %16: vector<1x1x6x1xbf16> -> vector<4x2x6x32xindex>
246// %21 = vector.broadcast %17: vector<1x1x1x32xbf16> -> vector<4x2x6x32xindex>
247// %22 = arith.add %18, %19
248// %23 = arith.add %20, %21
249// %local_offsets = arith.add %22, %23
250// %orig_offset = %block_id_y * 4x2x6x32 // consider using affine map
251// %offsets = memref_offset + orig_offset + local_offsets
252static Value computeOffsets(VectorTransferOpInterface xferOp,
253 PatternRewriter &rewriter, ArrayRef<Value> strides,
254 Value baseOffset) {
255 Location loc = xferOp.getLoc();
256 VectorType vectorType = xferOp.getVectorType();
257 SmallVector<Value> indices(xferOp.getIndices().begin(),
258 xferOp.getIndices().end());
259 ArrayRef<int64_t> vectorShape = vectorType.getShape();
260
261 // Create vector.step operations for each dimension
262 SmallVector<Value> stepVectors;
263 llvm::map_to_vector(vectorShape, [&](int64_t dim) {
264 auto stepType = VectorType::get({dim}, rewriter.getIndexType());
265 auto stepOp = vector::StepOp::create(rewriter, loc, stepType);
266 stepVectors.push_back(stepOp);
267 return stepOp;
268 });
269
270 // Local offsets are indexed in vector order, so permute strides; the base
271 // offset below uses the original memref-order strides.
272 SmallVector<Value> permutedStrides(strides.begin(), strides.end());
273 adjustStridesForPermutation(xferOp.getPermutationMap(), permutedStrides);
274
275 // Multiply step vectors by corresponding strides
276 size_t memrefRank = permutedStrides.size();
277 size_t vectorRank = vectorShape.size();
278 SmallVector<Value> strideMultiplied;
279 for (size_t i = 0; i < vectorRank; ++i) {
280 size_t memrefDim = memrefRank - vectorRank + i;
281 Value strideValue = permutedStrides[memrefDim];
282 auto mulType = dyn_cast<VectorType>(stepVectors[i].getType());
283 auto bcastOp =
284 vector::BroadcastOp::create(rewriter, loc, mulType, strideValue);
285 auto mulOp = arith::MulIOp::create(rewriter, loc, stepVectors[i], bcastOp);
286 strideMultiplied.push_back(mulOp);
287 }
288
289 // Shape cast each multiplied vector to add singleton dimensions
290 SmallVector<Value> shapeCasted;
291 for (size_t i = 0; i < vectorRank; ++i) {
292 SmallVector<int64_t> newShape(vectorRank, 1);
293 newShape[i] = vectorShape[i];
294 auto newType = VectorType::get(newShape, rewriter.getIndexType());
295 auto castOp = vector::ShapeCastOp::create(rewriter, loc, newType,
296 strideMultiplied[i]);
297 shapeCasted.push_back(castOp);
298 }
299
300 // Broadcast each shape-casted vector to full vector shape
301 SmallVector<Value> broadcasted;
302 auto fullIndexVectorType =
303 VectorType::get(vectorShape, rewriter.getIndexType());
304 for (Value shapeCastVal : shapeCasted) {
305 auto broadcastOp = vector::BroadcastOp::create(
306 rewriter, loc, fullIndexVectorType, shapeCastVal);
307 broadcasted.push_back(broadcastOp);
308 }
309
310 // Add all broadcasted vectors together to compute local offsets
311 Value localOffsets = broadcasted[0];
312 for (size_t i = 1; i < broadcasted.size(); ++i)
313 localOffsets =
314 arith::AddIOp::create(rewriter, loc, localOffsets, broadcasted[i]);
315
316 // Compute base offset from transfer read indices
317 for (size_t i = 0; i < indices.size(); ++i) {
318 Value strideVal = strides[i];
319 Value offsetContrib =
320 arith::MulIOp::create(rewriter, loc, indices[i], strideVal);
321 baseOffset =
322 arith::AddIOp::create(rewriter, loc, baseOffset, offsetContrib);
323 }
324 // Broadcast base offset to match vector shape
325 Value bcastBase = vector::BroadcastOp::create(
326 rewriter, loc, fullIndexVectorType, baseOffset);
327 localOffsets = arith::AddIOp::create(rewriter, loc, bcastBase, localOffsets);
328 return localOffsets;
329}
330
331// Compute the element-wise offsets for vector.gather or vector.scatter ops.
332//
333// This function linearizes the base offsets of the gather/scatter operation
334// and combines them with the per-element indices to produce a final vector of
335// memory offsets.
336template <
337 typename OpType,
338 typename = std::enable_if_t<llvm::is_one_of<
339 std::decay_t<OpType>, vector::GatherOp, vector::ScatterOp>::value>>
340static Value computeOffsets(PatternRewriter &rewriter, OpType gatScatOp,
341 ArrayRef<Value> strides, Value baseOffset) {
342 Location loc = gatScatOp.getLoc();
343 SmallVector<Value> offsets = gatScatOp.getOffsets();
344 for (size_t i = 0; i < offsets.size(); ++i) {
345 Value offsetContrib =
346 arith::MulIOp::create(rewriter, loc, offsets[i], strides[i]);
347 baseOffset =
348 arith::AddIOp::create(rewriter, loc, baseOffset, offsetContrib);
349 }
350 Value indices = gatScatOp.getIndices();
351 VectorType vecType = cast<VectorType>(indices.getType());
352
353 Value strideVector =
354 vector::BroadcastOp::create(rewriter, loc, vecType, strides.back())
355 .getResult();
356 Value stridedIndices =
357 arith::MulIOp::create(rewriter, loc, strideVector, indices).getResult();
358
359 Value baseVector =
360 vector::BroadcastOp::create(
361 rewriter, loc,
362 VectorType::get(vecType.getShape(), rewriter.getIndexType()),
363 baseOffset)
364 .getResult();
365 return arith::AddIOp::create(rewriter, loc, baseVector, stridedIndices)
366 .getResult();
367}
368
369// Collapses shapes of a nD memref to the target rank while applying offsets for
370// the collapsed dimensions. Returns the new memref value and the remaining
371// offsets for the last targetRank dimensions. For example:
372// input: %memref = memref<2x4x8x32xf32>, offsets=[%i0, %i1, %i2, %i3],
373// output: %memref[%i0, %i1, 0, 0] -> memref<8x32xf32>, offsets: [%i2, %i3]
374static std::pair<Value, SmallVector<OpFoldResult>>
375convertMemrefAndOffsetsToTargetRank(PatternRewriter &rewriter, Location loc,
378 int64_t targetRank) {
379 auto memrefType = cast<MemRefType>(memref.getType());
380 unsigned rank = memrefType.getRank();
381
382 if (rank <= targetRank)
383 return {memref, offsets};
384
385 int64_t numCombinedDims = rank - targetRank;
386 SmallVector<OpFoldResult> subviewOffsets;
387 SmallVector<OpFoldResult> subviewSizes;
388 SmallVector<OpFoldResult> subviewStrides;
389
390 // For the combined dimensions: use the provided offsets, size=1, stride=1
391 for (unsigned i = 0; i < numCombinedDims; ++i) {
392 subviewOffsets.push_back(offsets[i]);
393 subviewSizes.push_back(rewriter.getI64IntegerAttr(1));
394 subviewStrides.push_back(rewriter.getI64IntegerAttr(1));
395 }
396
397 // For the last targetRank dimensions: offset=0, use full size, stride=1
398 SmallVector<int64_t> resultShape;
399 auto originalShape = memrefType.getShape();
400 auto meta = memref::ExtractStridedMetadataOp::create(rewriter, loc, memref);
401 for (unsigned i = numCombinedDims; i < rank; ++i) {
402 subviewOffsets.push_back(rewriter.getI64IntegerAttr(0));
403 if (ShapedType::isDynamic(originalShape[i])) {
404 subviewSizes.push_back(meta.getSizes()[i]);
405 resultShape.push_back(ShapedType::kDynamic);
406 } else {
407 subviewSizes.push_back(rewriter.getI64IntegerAttr(originalShape[i]));
408 resultShape.push_back(originalShape[i]);
409 }
410 subviewStrides.push_back(rewriter.getI64IntegerAttr(1));
411 }
412
413 auto resultType = memref::SubViewOp::inferRankReducedResultType(
414 resultShape, memrefType, subviewOffsets, subviewSizes, subviewStrides);
415 auto subviewOp =
416 memref::SubViewOp::create(rewriter, loc, resultType, memref,
417 subviewOffsets, subviewSizes, subviewStrides);
418
419 // Return the remaining offsets for the last targetRank dimensions
420 SmallVector<OpFoldResult> newOffsets(offsets.begin() + numCombinedDims,
421 offsets.end());
422 return {subviewOp.getResult(), newOffsets};
423}
424
425template <
426 typename OpType,
427 typename = std::enable_if_t<llvm::is_one_of<
428 std::decay_t<OpType>, vector::TransferReadOp, vector::TransferWriteOp,
429 vector::GatherOp, vector::ScatterOp>::value>>
430// Convert memref to i64 base pointer
431static Value memrefToIndexPtr(OpType xferOp, PatternRewriter &rewriter) {
432 Location loc = xferOp.getLoc();
433 auto indexPtr = memref::ExtractAlignedPointerAsIndexOp::create(
434 rewriter, loc, xferOp.getBase())
435 .getResult();
436 return arith::IndexCastOp::create(rewriter, loc, rewriter.getI64Type(),
437 indexPtr)
438 .getResult();
439}
440
441static LogicalResult lowerToScatteredLoadOp(vector::TransferReadOp readOp,
442 PatternRewriter &rewriter) {
443
444 Location loc = readOp.getLoc();
445 VectorType vectorType = readOp.getVectorType();
446 ArrayRef<int64_t> vectorShape = vectorType.getShape();
447 auto memrefType = dyn_cast<MemRefType>(readOp.getShapedType());
448 if (!memrefType)
449 return rewriter.notifyMatchFailure(readOp, "Expected memref source");
450
451 auto meta = computeMemrefMeta(readOp, rewriter);
452 if (meta.first.empty())
453 return rewriter.notifyMatchFailure(readOp, "Failed to compute strides");
454
455 Value localOffsets =
456 computeOffsets(readOp, rewriter, meta.first, meta.second);
457
458 Value flatMemref = memrefToIndexPtr(readOp, rewriter);
459
460 Value mask = vector::ConstantMaskOp::create(
461 rewriter, loc, VectorType::get(vectorShape, rewriter.getI1Type()),
463 auto gatherOp = xegpu::LoadGatherOp::create(
464 rewriter, loc, vectorType, flatMemref, localOffsets, mask,
465 /*chunk_size=*/IntegerAttr{},
466 /*l1_hint=*/xegpu::CachePolicyAttr{},
467 /*l2_hint=*/xegpu::CachePolicyAttr{},
468 /*l3_hint=*/xegpu::CachePolicyAttr{},
469 /*layout=*/nullptr, /*contiguity=*/nullptr);
470
471 rewriter.replaceOp(readOp, gatherOp.getResult());
472 return success();
473}
474
475static LogicalResult lowerToScatteredStoreOp(vector::TransferWriteOp writeOp,
476 PatternRewriter &rewriter) {
477
478 Location loc = writeOp.getLoc();
479 VectorType vectorType = writeOp.getVectorType();
480 ArrayRef<int64_t> vectorShape = vectorType.getShape();
481
482 auto memrefType = dyn_cast<MemRefType>(writeOp.getShapedType());
483 if (!memrefType)
484 return rewriter.notifyMatchFailure(writeOp, "Expected memref source");
485
486 auto meta = computeMemrefMeta(writeOp, rewriter);
487 if (meta.first.empty())
488 return rewriter.notifyMatchFailure(writeOp, "Failed to compute strides");
489
490 Value localOffsets =
491 computeOffsets(writeOp, rewriter, meta.first, meta.second);
492
493 Value flatMemref = memrefToIndexPtr(writeOp, rewriter);
494
495 Value mask = vector::ConstantMaskOp::create(
496 rewriter, loc, VectorType::get(vectorShape, rewriter.getI1Type()),
498 xegpu::StoreScatterOp::create(rewriter, loc, writeOp.getVector(), flatMemref,
499 localOffsets, mask,
500 /*chunk_size=*/IntegerAttr{},
501 /*l1_hint=*/xegpu::CachePolicyAttr{},
502 /*l2_hint=*/xegpu::CachePolicyAttr{},
503 /*l3_hint=*/xegpu::CachePolicyAttr{},
504 /*layout=*/nullptr, /*contiguity=*/nullptr);
505 rewriter.eraseOp(writeOp);
506 return success();
507}
508
509struct TransferReadLowering : public OpRewritePattern<vector::TransferReadOp> {
510 using Base::Base;
511
512 LogicalResult matchAndRewrite(vector::TransferReadOp readOp,
513 PatternRewriter &rewriter) const override {
514 Location loc = readOp.getLoc();
515
516 if (failed(transferPreconditions(rewriter, readOp)))
517 return failure();
518 auto readMemTy = cast<MemRefType>(readOp.getShapedType());
519 VectorType loadedVecTy = readOp.getVectorType();
520 bool isOutOfBounds = readOp.hasOutOfBoundsDim();
521 // Check if the memref has address space 3 (shared local memory)
522 bool isSharedMemory = xegpu::XeGPUDialect::isSharedMemory(readMemTy);
523 // Handle the SLM case.
524 if (isSharedMemory) {
525 // load_matrix supports 1D and 2D loads from SLM.
526 if (loadedVecTy.getRank() != 1 && loadedVecTy.getRank() != 2)
527 return rewriter.notifyMatchFailure(
528 readOp, "Only 1D and 2D vector loads are supported for SLM");
529 AffineMap readMap = readOp.getPermutationMap();
530 if (!readMap.isMinorIdentity())
531 return rewriter.notifyMatchFailure(
532 readOp,
533 "Non identity transposition is not supported for SLM loads.");
534 // Out of bounds case is not supported for SLM loads.
535 if (isOutOfBounds)
536 return rewriter.notifyMatchFailure(
537 readOp, "Out-of-bounds access is not supported for SLM loads");
538
539 // Create mem_desc for SLM
540 auto memDescType =
541 xegpu::MemDescType::get(rewriter.getContext(), readMemTy.getShape(),
542 readMemTy.getElementType(),
543 /*mem_layout=*/nullptr);
544 auto createMemDescOp = xegpu::CreateMemDescOp::create(
545 rewriter, loc, memDescType, readOp.getBase());
546 // Convert indices to OpFoldResult for LoadMatrixOp
547 SmallVector<OpFoldResult> indices =
548 getAsOpFoldResult(readOp.getIndices());
549 auto loadMatrixOp = xegpu::LoadMatrixOp::create(
550 rewriter, loc, loadedVecTy, createMemDescOp.getResult(), indices,
551 /*layout=*/nullptr);
552
553 rewriter.replaceOp(readOp, loadMatrixOp.getResult());
554 return success();
555 }
556
557 // TODO: This check needs to be replaced with proper uArch capability check.
558 auto chip = xegpu::getChipStr(readOp);
559 bool hasBlockLoadSupport =
560 (chip == "pvc" || chip == "bmg" || chip == "cri");
561
562 // An nd block load can realize a minor-identity map directly, or an
563 // innermost-two-dims transpose via a trailing vector.transpose. Any other
564 // permutation (e.g. a mid-vector transpose of a high-dim load) is left to
565 // the scattered path, which permutes strides explicitly.
566 AffineMap readMap = readOp.getPermutationMap();
567 bool isTransposeLoad = isInnermostTwoDimsTransposed(readMap);
568
569 // Prefer an nd block load. It requires HW block-load support, a vector of
570 // rank >= 2 backed by a scalar-element memref, and a map the block load can
571 // realize. 1D vectors use the scattered xegpu.load path instead, which has
572 // a richer interface (e.g. layout capabilities). Out-of-bounds reads are
573 // allowed as long as the padding matches load_nd's implicit zero padding.
574 bool canLowerToLoadNd =
575 hasBlockLoadSupport && loadedVecTy.getRank() > 1 &&
576 (readMap.isMinorIdentity() || isTransposeLoad) &&
577 readMemTy.getElementType().isIntOrFloat() &&
578 (!isOutOfBounds || isZeroOrPoisonPadding(readOp.getPadding()));
579
580 if (canLowerToLoadNd) {
581 auto elementType = loadedVecTy.getElementType();
582
583 SmallVector<int64_t> descShape(loadedVecTy.getShape());
584 if (isTransposeLoad) {
585 // If load is transposed, simply swap the last two dimensions of the
586 // loaded vector type to get the descriptor shape.
587 size_t rank = descShape.size();
588 assert(rank >= 2 && "Transpose requires at least 2 dimensions");
589 std::swap(descShape[rank - 1], descShape[rank - 2]);
590 loadedVecTy = VectorType::get(descShape, elementType);
591 }
592 auto descType = xegpu::TensorDescType::get(
593 descShape, elementType, /*array_length=*/1,
594 /*boundary_check=*/isOutOfBounds, xegpu::MemorySpace::Global);
595 auto [src, indices] = convertMemrefAndOffsetsToTargetRank(
596 rewriter, loc, readOp.getBase(),
597 getAsOpFoldResult(readOp.getIndices()), loadedVecTy.getRank());
598 // By default, no specific caching policy is assigned.
599 xegpu::CachePolicyAttr hint = nullptr;
600 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
601 rewriter, loc, descType, dyn_cast<TypedValue<MemRefType>>(src));
602
603 Operation *loadedOp =
604 xegpu::LoadNdOp::create(rewriter, loc, loadedVecTy, ndDesc, indices,
605 /*packed=*/nullptr, /*transpose=*/nullptr,
606 /*l1_hint=*/hint,
607 /*l2_hint=*/hint, /*l3_hint=*/hint,
608 /*layout=*/nullptr);
609 if (isTransposeLoad) {
610 // Undo the innermost-two-dims swap with a trailing vector.transpose:
611 // keep the leading dimensions in place and interchange only the last
612 // two.
613 int64_t rank = loadedVecTy.getRank();
614 SmallVector<int64_t> perm(llvm::to_vector(llvm::seq<int64_t>(0, rank)));
615 std::swap(perm[rank - 1], perm[rank - 2]);
616 loadedOp = vector::TransposeOp::create(rewriter, loc,
617 loadedOp->getResult(0), perm);
618 }
619 rewriter.replaceOp(readOp, loadedOp);
620 return success();
621 }
622
623 // Fall back to a scattered load. It supports arbitrary permutations and any
624 // rank, but cannot express out-of-bounds accesses.
625 // TODO: add support for OutOfBound access.
626 if (isOutOfBounds)
627 return failure();
628 return lowerToScatteredLoadOp(readOp, rewriter);
629 }
630};
631
632struct TransferWriteLowering
633 : public OpRewritePattern<vector::TransferWriteOp> {
634 using Base::Base;
635
636 LogicalResult matchAndRewrite(vector::TransferWriteOp writeOp,
637 PatternRewriter &rewriter) const override {
638 Location loc = writeOp.getLoc();
639
640 if (failed(transferPreconditions(rewriter, writeOp)))
641 return failure();
642 // Perform common data transfer checks.
643 VectorType vecTy = writeOp.getVectorType();
644 auto writeMemTy = cast<MemRefType>(writeOp.getShapedType());
645 // Check if the memref has address space 3 (shared local memory)
646 bool isSharedMemory = xegpu::XeGPUDialect::isSharedMemory(writeMemTy);
647
648 // For shared local memory (address space 3), use create_mem_desc +
649 // store_matrix
650 if (isSharedMemory) {
651 // store_matrix supports 1D and 2D stores to SLM.
652 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
653 return rewriter.notifyMatchFailure(
654 writeOp, "Only 1D and 2D vector stores are supported for SLM");
655 // Create mem_desc for SLM
656 auto memDescType =
657 xegpu::MemDescType::get(rewriter.getContext(), writeMemTy.getShape(),
658 writeMemTy.getElementType(),
659 /*mem_layout=*/nullptr);
660
661 auto createMemDescOp = xegpu::CreateMemDescOp::create(
662 rewriter, loc, memDescType, writeOp.getBase());
663
664 // Convert indices to OpFoldResult for StoreMatrixOp
665 SmallVector<OpFoldResult> indices =
666 getAsOpFoldResult(writeOp.getIndices());
667
668 xegpu::StoreMatrixOp::create(rewriter, loc, writeOp.getVector(),
669 createMemDescOp.getResult(), indices,
670 /*layout=*/nullptr);
671
672 rewriter.eraseOp(writeOp);
673 return success();
674 }
675
676 // TODO: This check needs to be replaced with proper uArch capability check.
677 auto chip = xegpu::getChipStr(writeOp);
678 bool hasBlockStoreSupport =
679 (chip == "pvc" || chip == "bmg" || chip == "cri");
680
681 // Prefer an nd block store. It requires HW block-store support, a vector of
682 // rank >= 2 backed by a scalar-element memref, and a minor-identity map
683 // (block stores have no transpose support). 1D vectors use the scattered
684 // xegpu.store path instead, which has a richer interface. Out-of-bounds
685 // writes are handled by the descriptor's boundary check.
686 AffineMap map = writeOp.getPermutationMap();
687 bool canLowerToStoreNd = hasBlockStoreSupport && vecTy.getRank() > 1 &&
688 map.isMinorIdentity() &&
689 writeMemTy.getElementType().isIntOrFloat();
690
691 if (canLowerToStoreNd) {
692 auto [src, indices] = convertMemrefAndOffsetsToTargetRank(
693 rewriter, loc, writeOp.getBase(),
694 getAsOpFoldResult(writeOp.getIndices()), vecTy.getRank());
695
696 auto descType = xegpu::TensorDescType::get(
697 vecTy.getShape(), vecTy.getElementType(),
698 /*array_length=*/1, /*boundary_check=*/writeOp.hasOutOfBoundsDim(),
699 xegpu::MemorySpace::Global);
700 // By default, no specific caching policy is assigned.
701 xegpu::CachePolicyAttr hint = nullptr;
702 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
703 rewriter, loc, descType, dyn_cast<TypedValue<MemRefType>>(src));
704
705 auto storeOp = xegpu::StoreNdOp::create(
706 rewriter, loc, writeOp.getVector(), ndDesc, indices,
707 /*l1_hint=*/hint,
708 /*l2_hint=*/hint, /*l3_hint=*/hint,
709 /*layout=*/nullptr);
710 rewriter.replaceOp(writeOp, storeOp);
711 return success();
712 }
713
714 // Fall back to a scattered store. It supports arbitrary permutations and
715 // any rank, but cannot express out-of-bounds accesses.
716 // TODO: add support for OutOfBound access.
717 if (writeOp.hasOutOfBoundsDim())
718 return failure();
719 return lowerToScatteredStoreOp(writeOp, rewriter);
720 }
721};
722
723struct GatherLowering : public OpRewritePattern<vector::GatherOp> {
724 using Base::Base;
725
726 LogicalResult matchAndRewrite(vector::GatherOp gatherOp,
727 PatternRewriter &rewriter) const override {
728 auto srcTy = dyn_cast<MemRefType>(gatherOp.getBase().getType());
729 if (!srcTy)
730 return rewriter.notifyMatchFailure(gatherOp, "Expects memref source");
731
732 Location loc = gatherOp.getLoc();
733 VectorType vectorType = gatherOp.getVectorType();
734
735 auto meta = computeMemrefMeta(gatherOp, rewriter);
736 if (meta.first.empty())
737 return rewriter.notifyMatchFailure(gatherOp, "Failed to compute strides");
738
739 Value localOffsets =
740 computeOffsets(rewriter, gatherOp, meta.first, meta.second);
741 Value flatMemref = memrefToIndexPtr(gatherOp, rewriter);
742
743 auto xeGatherOp = xegpu::LoadGatherOp::create(
744 rewriter, loc, vectorType, flatMemref, localOffsets, gatherOp.getMask(),
745 /*chunk_size=*/IntegerAttr{},
746 /*l1_hint=*/xegpu::CachePolicyAttr{},
747 /*l2_hint=*/xegpu::CachePolicyAttr{},
748 /*l3_hint=*/xegpu::CachePolicyAttr{},
749 /*layout=*/nullptr, /*contiguity=*/nullptr);
750
751 auto selectOp =
752 arith::SelectOp::create(rewriter, loc, gatherOp.getMask(),
753 xeGatherOp.getResult(), gatherOp.getPassThru());
754 rewriter.replaceOp(gatherOp, selectOp.getResult());
755 return success();
756 }
757};
758
759struct ScatterLowering : public OpRewritePattern<vector::ScatterOp> {
760 using Base::Base;
761
762 LogicalResult matchAndRewrite(vector::ScatterOp scatterOp,
763 PatternRewriter &rewriter) const override {
764 auto srcTy = dyn_cast<MemRefType>(scatterOp.getBase().getType());
765 if (!srcTy)
766 return rewriter.notifyMatchFailure(scatterOp, "Expects memref source");
767
768 Location loc = scatterOp.getLoc();
769 auto meta = computeMemrefMeta(scatterOp, rewriter);
770 if (meta.first.empty())
771 return rewriter.notifyMatchFailure(scatterOp,
772 "Failed to compute strides");
773
774 Value localOffsets =
775 computeOffsets(rewriter, scatterOp, meta.first, meta.second);
776 Value flatMemref = memrefToIndexPtr(scatterOp, rewriter);
777
778 xegpu::StoreScatterOp::create(rewriter, loc, scatterOp.getValueToStore(),
779 flatMemref, localOffsets, scatterOp.getMask(),
780 /*chunk_size=*/IntegerAttr{},
781 /*l1_hint=*/xegpu::CachePolicyAttr{},
782 /*l2_hint=*/xegpu::CachePolicyAttr{},
783 /*l3_hint=*/xegpu::CachePolicyAttr{},
784 /*layout=*/nullptr,
785 /*contiguity=*/nullptr);
786 rewriter.eraseOp(scatterOp);
787 return success();
788 }
789};
790
791struct LoadLowering : public OpRewritePattern<vector::LoadOp> {
792 using Base::Base;
793
794 LogicalResult matchAndRewrite(vector::LoadOp loadOp,
795 PatternRewriter &rewriter) const override {
796 Location loc = loadOp.getLoc();
797
798 VectorType vecTy = loadOp.getResult().getType();
799 MemRefType memTy = loadOp.getBase().getType();
800 // The plain vector.load lowering only supports 1D/2D block loads.
801 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
802 return rewriter.notifyMatchFailure(loadOp, "Expects 1D or 2D vector");
803 if (!memTy.getElementType().isIntOrFloat())
804 return rewriter.notifyMatchFailure(
805 loadOp, "Unsupported memref element type: expected integer or float");
806
807 // Boundary check is available only for block instructions.
808 bool boundaryCheck = vecTy.getRank() > 1;
809 // By default, no specific caching policy is assigned.
810 xegpu::CachePolicyAttr hint = nullptr;
811
812 auto [src, indices] = convertMemrefAndOffsetsToTargetRank(
813 rewriter, loc, loadOp.getBase(), getAsOpFoldResult(loadOp.getIndices()),
814 vecTy.getRank());
815
816 auto descType = xegpu::TensorDescType::get(
817 vecTy.getShape(), vecTy.getElementType(), /*array_length=*/1,
818 boundaryCheck, xegpu::MemorySpace::Global);
819
820 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
821 rewriter, loc, descType, dyn_cast<TypedValue<MemRefType>>(src));
822 auto loadNdOp =
823 xegpu::LoadNdOp::create(rewriter, loc, vecTy, ndDesc, indices,
824 /*packed=*/nullptr, /*transpose=*/nullptr,
825 /*l1_hint=*/hint,
826 /*l2_hint=*/hint, /*l3_hint=*/hint,
827 /*layout=*/nullptr);
828 rewriter.replaceOp(loadOp, loadNdOp);
829
830 return success();
831 }
832};
833
834struct StoreLowering : public OpRewritePattern<vector::StoreOp> {
835 using Base::Base;
836
837 LogicalResult matchAndRewrite(vector::StoreOp storeOp,
838 PatternRewriter &rewriter) const override {
839 Location loc = storeOp.getLoc();
840
841 TypedValue<VectorType> vector = storeOp.getValueToStore();
842 VectorType vecTy = vector.getType();
843 MemRefType memTy = storeOp.getBase().getType();
844 // The plain vector.store lowering only supports 1D/2D block stores.
845 if (vecTy.getRank() != 1 && vecTy.getRank() != 2)
846 return rewriter.notifyMatchFailure(storeOp, "Expects 1D or 2D vector");
847 if (!memTy.getElementType().isIntOrFloat())
848 return rewriter.notifyMatchFailure(
849 storeOp,
850 "Unsupported memref element type: expected integer or float");
851
852 // Boundary check is available only for block instructions.
853 bool boundaryCheck = vecTy.getRank() > 1;
854
855 auto [src, indices] = convertMemrefAndOffsetsToTargetRank(
856 rewriter, loc, storeOp.getBase(),
857 getAsOpFoldResult(storeOp.getIndices()), vecTy.getRank());
858
859 auto descType = xegpu::TensorDescType::get(
860 vecTy.getShape(), vecTy.getElementType(),
861 /*array_length=*/1, boundaryCheck, xegpu::MemorySpace::Global);
862
863 // By default, no specific caching policy is assigned.
864 xegpu::CachePolicyAttr hint = nullptr;
865 xegpu::CreateNdDescOp ndDesc = xegpu::CreateNdDescOp::create(
866 rewriter, loc, descType, dyn_cast<TypedValue<MemRefType>>(src));
867
868 auto storeNdOp =
869 xegpu::StoreNdOp::create(rewriter, loc, vector, ndDesc, indices,
870 /*l1_hint=*/hint,
871 /*l2_hint=*/hint, /*l3_hint=*/hint,
872 /*layout=*/nullptr);
873
874 rewriter.replaceOp(storeOp, storeNdOp);
875
876 return success();
877 }
878};
879
880// If `indexingMaps` describe a (batched) row-major matmul
881// lhs[b..., m, k], rhs[b..., k, n], acc[b..., m, n]
882// return the number of leading batch dims (0 for a plain 2D matmul);
883// otherwise return std::nullopt.
884static std::optional<int64_t>
885getRowMajorMatmulBatchRank(ArrayAttr indexingMaps) {
886 if (indexingMaps.size() != 3)
887 return std::nullopt;
888
889 AffineMap mapA = cast<AffineMapAttr>(indexingMaps[0]).getValue();
890 AffineMap mapB = cast<AffineMapAttr>(indexingMaps[1]).getValue();
891 AffineMap mapC = cast<AffineMapAttr>(indexingMaps[2]).getValue();
892
893 // The result map exposes the batch dims followed by the core (m, n) dims.
894 if (mapC.getNumResults() < 2)
895 return std::nullopt;
896 int64_t batchRank = mapC.getNumResults() - 2;
897
898 // A single `k` reduction gives batchRank + 3 iteration dims; each operand
899 // map exposes batchRank + 2 dims (batch dims + 2 core dims).
900 unsigned numDims = static_cast<unsigned>(batchRank) + 3;
901 unsigned numOperandResults = static_cast<unsigned>(batchRank) + 2;
902 if (mapA.getNumInputs() != numDims || mapB.getNumInputs() != numDims ||
903 mapC.getNumInputs() != numDims)
904 return std::nullopt;
905 if (mapA.getNumResults() != numOperandResults ||
906 mapB.getNumResults() != numOperandResults)
907 return std::nullopt;
908
909 // Reconstruct the canonical maps from the batch/m/n dims of the result and
910 // the k dim of lhs, then compare against the actual maps.
911 MLIRContext *context = indexingMaps.getContext();
912 ArrayRef<AffineExpr> batchDims = mapC.getResults().take_front(batchRank);
913 AffineExpr m = mapC.getResult(batchRank);
914 AffineExpr n = mapC.getResult(batchRank + 1);
915 AffineExpr k = mapA.getResult(batchRank + 1);
916
917 SmallVector<AffineExpr> aDims = llvm::to_vector(batchDims);
918 aDims.push_back(m);
919 aDims.push_back(k);
920 SmallVector<AffineExpr> bDims = llvm::to_vector(batchDims);
921 bDims.push_back(k);
922 bDims.push_back(n);
923 SmallVector<AffineExpr> cDims = llvm::to_vector(batchDims);
924 cDims.push_back(m);
925 cDims.push_back(n);
926
927 auto expected = ArrayAttr::get(
928 context,
929 {AffineMapAttr::get(AffineMap::get(numDims, 0, aDims, context)),
930 AffineMapAttr::get(AffineMap::get(numDims, 0, bDims, context)),
931 AffineMapAttr::get(AffineMap::get(numDims, 0, cDims, context))});
932 if (indexingMaps != expected)
933 return std::nullopt;
934 return batchRank;
935}
936
937struct ContractionLowering : public OpRewritePattern<vector::ContractionOp> {
938 using Base::Base;
939
940 LogicalResult matchAndRewrite(vector::ContractionOp contractOp,
941 PatternRewriter &rewriter) const override {
942 Location loc = contractOp.getLoc();
943
944 if (contractOp.getKind() != vector::CombiningKind::ADD)
945 return rewriter.notifyMatchFailure(contractOp,
946 "Expects add combining kind");
947
948 TypedValue<VectorType> lhs = contractOp.getLhs();
949 TypedValue<VectorType> rhs = contractOp.getRhs();
950 TypedValue<Type> acc = contractOp.getAcc();
951 VectorType accType = dyn_cast<VectorType>(acc.getType());
952 if (!accType)
953 return rewriter.notifyMatchFailure(contractOp, "Expects vector acc");
954
955 std::optional<int64_t> batchRank =
956 getRowMajorMatmulBatchRank(contractOp.getIndexingMapsAttr());
957 if (!batchRank)
958 return rewriter.notifyMatchFailure(
959 contractOp,
960 "Expects a (batched) row-major matmul: leading dims must "
961 "be batch dims shared by lhs, rhs, and acc; innermost two "
962 "dims must be (M, K), (K, N), and (M, N)");
963
964 // xegpu.dpas operands are limited to 2 batch + 2 core dims.
965 if (*batchRank > 2)
966 return rewriter.notifyMatchFailure(contractOp,
967 "Expects operands of rank 4 or less");
968
969 auto dpasOp = xegpu::DpasOp::create(
970 rewriter, loc, contractOp.getResultType(), lhs, rhs, acc,
971 /*layout_a=*/nullptr, /*layout_b=*/nullptr, /*layout_cd=*/nullptr);
972 rewriter.replaceOp(contractOp, dpasOp);
973
974 return success();
975 }
976};
977
978// Returns the `vector.shape_cast` that flattened a value of type `ndType` into
979// `flat`, if that is how `flat` was produced.
980static vector::ShapeCastOp getFlattenCast(Value flat, VectorType ndType) {
981 auto shapeCast = flat.getDefiningOp<vector::ShapeCastOp>();
982 if (shapeCast && shapeCast.getSourceVectorType() == ndType)
983 return shapeCast;
984 return nullptr;
985}
986
987static DenseElementsAttr getDenseConstant(Value flat) {
988 DenseElementsAttr elements;
989 if (matchPattern(flat, m_Constant(&elements)))
990 return elements;
991 return nullptr;
992}
993
994static vector::BroadcastOp getSplatBroadcast(Value flat) {
995 auto broadcast = flat.getDefiningOp<vector::BroadcastOp>();
996 if (broadcast && !isa<VectorType>(broadcast.getSourceType()))
997 return broadcast;
998 return nullptr;
999}
1000
1001// Returns true if the cast `unflatten` creates will fold away, i.e. if `flat`
1002// is a flattening cast, a constant or a splat.
1003static bool canUnflatten(Value flat, VectorType ndType) {
1004 return getFlattenCast(flat, ndType) || getDenseConstant(flat) ||
1005 getSplatBroadcast(flat);
1006}
1007
1008// Reshape `flat` to `ndType`; `canUnflatten` must hold so the cast folds away.
1009static Value unflatten(PatternRewriter &rewriter, Value flat,
1010 VectorType ndType) {
1011 assert(canUnflatten(flat, ndType) && "expected the cast to fold away");
1012 return vector::ShapeCastOp::create(rewriter, flat.getLoc(), ndType, flat);
1013}
1014
1015// Restore the N-D form of a flattened `vector.gather` / `vector.scatter`.
1016//
1017// XeGPU layouts are expressed in terms of the N-D shape of the accessed data,
1018// so a flattened gather/scatter forces layout propagation to reason through the
1019// surrounding `vector.shape_cast` ops. That adds complexity and tends to yield
1020// layouts that lower to unoptimized code.
1021//
1022// Before:
1023// %cst = arith.constant dense<0.0> : vector<8192xbf16>
1024// %fi = vector.shape_cast %idx : vector<128x64xindex> to vector<8192xindex>
1025// %fm = vector.shape_cast %mask : vector<128x64xi1> to vector<8192xi1>
1026// %fr = vector.gather %src[%c0] [%fi], %fm, %cst : memref<?xbf16>,
1027// vector<8192xindex>, vector<8192xi1>, vector<8192xbf16>
1028// into vector<8192xbf16>
1029// %res = vector.shape_cast %fr : vector<8192xbf16> to vector<128x64xbf16>
1030//
1031// After:
1032// %cst = arith.constant dense<0.0> : vector<128x64xbf16>
1033// %res = vector.gather %src[%c0] [%idx], %mask, %cst : memref<?xbf16>,
1034// vector<128x64xindex>, vector<128x64xi1>, vector<128x64xbf16>
1035// into vector<128x64xbf16>
1036template <typename OpTy>
1037struct UnflattenGatherScatter : public OpRewritePattern<OpTy> {
1038 using OpRewritePattern<OpTy>::OpRewritePattern;
1039
1040 LogicalResult matchAndRewrite(OpTy op,
1041 PatternRewriter &rewriter) const override {
1042 constexpr bool isGather = std::is_same_v<OpTy, vector::GatherOp>;
1043
1044 if (!isa<MemRefType>(op.getBase().getType()))
1045 return rewriter.notifyMatchFailure(op, "expects a memref source");
1046
1047 if (op.getIndexVectorType().getRank() != 1)
1048 return rewriter.notifyMatchFailure(op, "index vector is not 1-D");
1049
1050 // The N-D shape comes from the index operand's producer: this only undoes
1051 // a flattening that already happened, it never invents a shape.
1052 auto indexCast =
1053 op.getIndices().template getDefiningOp<vector::ShapeCastOp>();
1054 if (!indexCast || indexCast.getSourceVectorType().getRank() < 2)
1055 return rewriter.notifyMatchFailure(
1056 op, "index vector is not a shape_cast of an N-D vector");
1057 VectorType ndIndexType = indexCast.getSourceVectorType();
1058 VectorType ndMaskType =
1059 ndIndexType.cloneWith(std::nullopt, rewriter.getI1Type());
1060 VectorType ndType = ndIndexType.cloneWith(
1061 std::nullopt, op.getVectorType().getElementType());
1062
1063 // Check everything before creating any IR: a partially applied rewrite
1064 // would leave dead ops behind.
1065 if (!canUnflatten(op.getMask(), ndMaskType))
1066 return rewriter.notifyMatchFailure(op, "cannot un-flatten the mask");
1067
1068 if constexpr (isGather) {
1069 if (!canUnflatten(op.getPassThru(), ndType))
1070 return rewriter.notifyMatchFailure(op,
1071 "cannot un-flatten the pass-thru");
1072
1073 Value mask = unflatten(rewriter, op.getMask(), ndMaskType);
1074 Value passThru = unflatten(rewriter, op.getPassThru(), ndType);
1075 auto ndGather = vector::GatherOp::create(
1076 rewriter, op.getLoc(), ndType, op.getBase(), op.getOffsets(),
1077 indexCast.getSource(), mask, passThru, op.getAlignmentAttr());
1078 ndGather->setDiscardableAttrs(op->getDiscardableAttrDictionary());
1079 rewriter.replaceOpWithNewOp<vector::ShapeCastOp>(op, op.getVectorType(),
1080 ndGather);
1081 } else {
1082 if (!canUnflatten(op.getValueToStore(), ndType))
1083 return rewriter.notifyMatchFailure(
1084 op, "cannot un-flatten the stored value");
1085
1086 Value mask = unflatten(rewriter, op.getMask(), ndMaskType);
1087 Value valueToStore = unflatten(rewriter, op.getValueToStore(), ndType);
1088 // Only operand types change, and this keeps the optional tensor result
1089 // untouched.
1090 rewriter.modifyOpInPlace(op, [&] {
1091 op.getIndicesMutable().assign(indexCast.getSource());
1092 op.getMaskMutable().assign(mask);
1093 op.getValueToStoreMutable().assign(valueToStore);
1094 });
1095 }
1096 return success();
1097 }
1098};
1099
1100// Un-flatten every gather/scatter that was flattened to 1-D operands, so that
1101// the conversion patterns and the XeGPU layouts downstream of them see the N-D
1102// shape of the accessed data.
1103static LogicalResult unflattenGatherScatter(Operation *root) {
1104 MLIRContext *ctx = root->getContext();
1105 RewritePatternSet patterns(ctx);
1106 patterns.add<UnflattenGatherScatter<vector::GatherOp>,
1107 UnflattenGatherScatter<vector::ScatterOp>>(ctx);
1108 vector::ShapeCastOp::getCanonicalizationPatterns(patterns, ctx);
1109 return applyPatternsGreedily(root, std::move(patterns));
1110}
1111
1112// Returns `memrefTy` with its memory space replaced by `newMemSpace`.
1113static MemRefType withMemorySpace(MemRefType memrefTy, Attribute newMemSpace) {
1114 return MemRefType::get(memrefTy.getShape(), memrefTy.getElementType(),
1115 memrefTy.getLayout(), newMemSpace);
1116}
1117
1118// Rewrite every `memref.alloca` not already in shared local memory (SLM) to
1119// be in SLM (address space 3), and propagate the new memory space through
1120// memref-producing aliasing users (e.g. memref.cast, memref.subview,
1121// memref.expand_shape, ...). Consumers that take a memref operand but
1122// produce a non-memref result (e.g. vector.transfer_read, vector.load) are
1123// left untouched: their operand type simply reflects the new memory space.
1124//
1125// This makes `xegpu.load_matrix`/`xegpu.store_matrix` lowering work end-to-end
1126// for IR coming from bufferization, which by default assigns memory space 0/1
1127// to allocations.
1128static void promoteAllocasToSLM(Operation *root) {
1129 MLIRContext *ctx = root->getContext();
1130 Attribute slmAttr = IntegerAttr::get(IntegerType::get(ctx, 64), 3);
1131
1132 // A user is treated as a memref-producing alias (e.g. memref.cast,
1133 // memref.subview, memref.expand_shape, ...) if it is side-effect free and
1134 // produces at least one memref result. This excludes ops like memref.copy
1135 // that have memory effects.
1136 auto isMemrefResultOp = [](Operation *op) {
1137 if (!isMemoryEffectFree(op))
1138 return false;
1139 return llvm::any_of(op->getResultTypes(),
1140 [](Type t) { return isa<MemRefType>(t); });
1141 };
1142
1143 // Update `v`'s type to have SLM memory space, then walk forward through
1144 // memref-producing users and update their result types accordingly.
1145 std::function<void(Value)> propagate = [&](Value v) {
1146 auto memrefTy = dyn_cast<MemRefType>(v.getType());
1147 if (!memrefTy || xegpu::XeGPUDialect::isSharedMemory(memrefTy))
1148 return;
1149 v.setType(withMemorySpace(memrefTy, slmAttr));
1150 for (Operation *user : v.getUsers()) {
1151 if (!isMemrefResultOp(user))
1152 continue;
1153 for (Value result : user->getResults())
1154 propagate(result);
1155 }
1156 };
1157
1159 root->walk([&](memref::AllocaOp op) {
1160 auto memrefTy = dyn_cast<MemRefType>(op.getResult().getType());
1161 if (!memrefTy || xegpu::XeGPUDialect::isSharedMemory(memrefTy))
1162 return;
1163 allocas.push_back(op);
1164 });
1165
1166 for (memref::AllocaOp alloca : allocas) {
1167 OpBuilder builder(alloca);
1168 auto memrefTy = cast<MemRefType>(alloca.getResult().getType());
1169 auto newTy = withMemorySpace(memrefTy, slmAttr);
1170 auto newOp = memref::AllocaOp::create(
1171 builder, alloca.getLoc(), newTy, alloca.getDynamicSizes(),
1172 alloca.getSymbolOperands(), alloca.getAlignmentAttr());
1173 alloca.getResult().replaceAllUsesWith(newOp.getResult());
1174 alloca.erase();
1175 // Propagate the new memory space through memref-producing consumers.
1176 for (Operation *user : newOp.getResult().getUsers()) {
1177 if (!isMemrefResultOp(user))
1178 continue;
1179 for (Value result : user->getResults())
1180 propagate(result);
1181 }
1182 }
1183}
1184
1185struct ConvertVectorToXeGPUPass
1186 : public impl::ConvertVectorToXeGPUBase<ConvertVectorToXeGPUPass> {
1187 void runOnOperation() override {
1188 // Promote local allocations to SLM (address space 3) so that
1189 // load_matrix/store_matrix lowerings have well-typed memref operands.
1190 promoteAllocasToSLM(getOperation());
1191
1192 // Undo any flattening of gather/scatter operands, so that the conversion
1193 // below sees the N-D shape the XeGPU layouts are expressed in.
1194 if (failed(unflattenGatherScatter(getOperation())))
1195 return signalPassFailure();
1196
1197 RewritePatternSet patterns(&getContext());
1200 if (failed(applyPatternsGreedily(getOperation(), std::move(patterns))))
1201 return signalPassFailure();
1202 }
1203};
1204
1205} // namespace
1206
1208 RewritePatternSet &patterns) {
1209 patterns
1210 .add<TransferReadLowering, TransferWriteLowering, LoadLowering,
1211 ScatterLowering, GatherLowering, StoreLowering, ContractionLowering>(
1212 patterns.getContext());
1213}
return success()
lhs
ArrayAttr()
b getContext())
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.
Definition AffineExpr.h:68
A multi-dimensional affine map Affine map's are immutable like Type's, and they are uniqued.
Definition AffineMap.h:46
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
bool isPermutationOfMinorIdentityWithBroadcasting(SmallVectorImpl< unsigned > &permutedDims) const
Return true if this affine map can be converted to a minor identity with broadcast by doing a permute...
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.
Definition Attributes.h:25
IntegerType getI64Type()
Definition Builders.cpp:73
IntegerAttr getI64IntegerAttr(int64_t value)
Definition Builders.cpp:120
IntegerType getI1Type()
Definition Builders.cpp:61
MLIRContext * getContext() const
Definition Builders.h:56
IndexType getIndexType()
Definition Builders.cpp:59
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...
Definition Location.h:76
MLIRContext is the top-level object for a collection of MLIR operations.
Definition MLIRContext.h:63
This class helps build Operations.
Definition Builders.h:210
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
OpResult getResult(unsigned idx)
Get the 'idx'th result of this operation.
Definition Operation.h:432
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),...
Definition Operation.h:849
user_range getUsers()
Returns a range of all users.
Definition Operation.h:925
MLIRContext * getContext()
Return the context this operation is associated with.
Definition Operation.h:233
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...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
user_range getUsers() const
Definition Value.h:218
Location getLoc() const
Return the location of this value.
Definition Value.cpp:24
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static ConstantIndexOp create(OpBuilder &builder, Location location, int64_t value)
Definition ArithOps.cpp:398
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:732
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.
Definition Matchers.h:490
void populatePrepareVectorToMMAPatterns(RewritePatternSet &patterns, bool useNvGpu=false)
Patterns to transform vector ops into a canonical form to convert to MMA matrix operations.
Type getType(OpFoldResult ofr)
Returns the int type of the integer in ofr.
Definition Utils.cpp:310
AffineMap inverseAndBroadcastProjectedPermutation(AffineMap map)
Return the reverse map of a projected permutation where the projected dimensions are transformed into...
SmallVector< T > applyPermutation(ArrayRef< T > input, ArrayRef< int64_t > permutation)
LogicalResult applyPatternsGreedily(Region &region, 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.
Definition Value.h:494
void populateVectorToXeGPUConversionPatterns(RewritePatternSet &patterns)
Collect a set of patterns to convert from the vector to XeGPU ops.
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
OpFoldResult getAsOpFoldResult(Value val)
Given a value, try to extract a constant Attribute.
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369
AffineExpr getAffineDimExpr(unsigned position, MLIRContext *context)
These free functions allow clients of the API to not use classes in detail.
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...