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