MLIR 24.0.0git
XeGPUToXeVM.cpp
Go to the documentation of this file.
1//===-- XeGPUToXeVM.cpp - XeGPU to XeVM dialect conversion ------*- C++ -*-===//
2//
3// This file is licensed 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
12
27#include "mlir/Pass/Pass.h"
28#include "mlir/Support/LLVM.h"
29#include "llvm/ADT/STLExtras.h"
30#include "llvm/Support/FormatVariadic.h"
31
33#include "mlir/IR/Types.h"
34
35#include "llvm/ADT/TypeSwitch.h"
36
37#include <string>
38
39namespace mlir {
40#define GEN_PASS_DEF_CONVERTXEGPUTOXEVMPASS
41#include "mlir/Conversion/Passes.h.inc"
42} // namespace mlir
43
44using namespace mlir;
45
46namespace {
47
48// TODO: Below are uArch dependent values, should move away from hardcoding
49static constexpr int32_t systolicDepth{8};
50static constexpr int32_t executionSize{16};
51
52// Offsets to individual fields of the 8xi32 layout nd tensor descriptor.
53enum class NdTdescOffset : uint32_t {
54 BasePtr = 0, // Base pointer (i64)
55 BaseShapeW = 2, // Base shape width (i32)
56 BaseShapeH = 3, // Base shape height (i32)
57 BasePitch = 4, // Base pitch/stride of dim rank-2 (i32)
58 LeadingStride0 = 5, // Row strides of the leading (batch) dims of a >2D
59 LeadingStride1 = 6, // descriptor (i32); added into offset_h by the load/store
60 LeadingStride2 = 7, // lowering. Left at 0 for 2D descriptors.
61};
62
63// Spare payload slots above, and the resulting max lowerable descriptor rank.
64static constexpr int64_t maxNdTdescLeadingDims{3};
65static constexpr int64_t maxNdTdescRank{2 + maxNdTdescLeadingDims};
66
67static int32_t getNumericXeVMAddrSpace(xegpu::MemorySpace xeGpuMemspace) {
68 switch (xeGpuMemspace) {
69 case xegpu::MemorySpace::Global:
70 return static_cast<int>(xevm::AddrSpace::GLOBAL);
71 case xegpu::MemorySpace::SLM:
72 return static_cast<int>(xevm::AddrSpace::SHARED);
73 }
74 llvm_unreachable("Unknown XeGPU memory space");
75}
76
77/// Translates a memref memory space attribute into XeVM's numeric address
78/// space, which follows the OpenCL/SPIR-V convention (0 = private, 1 =
79/// global, 2 = constant, 3 = shared/local, 4 = generic). A null attribute,
80/// meaning the memory space was left unspecified, maps to the default space
81/// 0. Returns failure if `memSpace` is a representation this pass does not
82/// know how to translate (e.g. a SPIR-V storage class or an arbitrary string
83/// attribute), rather than assuming it is an `IntegerAttr` and asserting.
84static FailureOr<unsigned> getNumericMemorySpace(Attribute memSpace) {
85 if (!memSpace)
86 return 0u;
87 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(memSpace))
88 return static_cast<unsigned>(intAttr.getInt());
89 if (auto xevmSpace = llvm::dyn_cast<xevm::AddrSpaceAttr>(memSpace))
90 return static_cast<unsigned>(xevmSpace.getValue());
91 if (auto gpuSpace = llvm::dyn_cast<gpu::AddressSpaceAttr>(memSpace)) {
92 switch (gpuSpace.getValue()) {
93 case gpu::AddressSpace::Global:
94 return static_cast<unsigned>(xevm::AddrSpace::GLOBAL);
95 case gpu::AddressSpace::Workgroup:
96 return static_cast<unsigned>(xevm::AddrSpace::SHARED);
97 case gpu::AddressSpace::Private:
98 return static_cast<unsigned>(xevm::AddrSpace::PRIVATE);
99 case gpu::AddressSpace::Constant:
100 return static_cast<unsigned>(xevm::AddrSpace::CONSTANT);
101 }
102 llvm_unreachable("Unknown GPU address space");
103 }
104 return failure();
105}
106
107/// Checks if the given MemRefType refers to shared memory.
108static bool isSharedMemRef(const MemRefType &memrefTy) {
109 FailureOr<unsigned> addrSpace =
110 getNumericMemorySpace(memrefTy.getMemorySpace());
111 return succeeded(addrSpace) &&
112 *addrSpace == static_cast<unsigned>(xevm::AddrSpace::SHARED);
113}
114
115// Get same bitwidth flat vector type of new element type.
116static VectorType encodeVectorTypeTo(VectorType currentVecType,
117 Type toElemType) {
118 auto elemType = currentVecType.getElementType();
119 auto currentBitWidth = elemType.getIntOrFloatBitWidth();
120 auto newBitWidth = toElemType.getIntOrFloatBitWidth();
121 const int size =
122 currentVecType.getNumElements() * currentBitWidth / newBitWidth;
123 return VectorType::get(size, toElemType);
124}
125
126static xevm::LoadCacheControl
127translateLoadXeGPUCacheHint(std::optional<xegpu::CachePolicy> L1hint,
128 std::optional<xegpu::CachePolicy> L3hint) {
129 // If no hints are provided, use the default cache control.
130 if (!L1hint && !L3hint)
131 return xevm::LoadCacheControl::USE_DEFAULT;
132 // If only one of the hints is provided, use the default for the other level.
133 auto L1hintVal = L1hint.value_or(xegpu::CachePolicy::CACHED);
134 auto L3hintVal = L3hint.value_or(xegpu::CachePolicy::CACHED);
135 switch (L1hintVal) {
136 case xegpu::CachePolicy::CACHED:
137 if (L3hintVal == xegpu::CachePolicy::CACHED)
138 return xevm::LoadCacheControl::L1C_L2UC_L3C;
139 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
140 return xevm::LoadCacheControl::L1C_L2UC_L3UC;
141 else
142 llvm_unreachable("Unsupported cache control.");
143 case xegpu::CachePolicy::UNCACHED:
144 if (L3hintVal == xegpu::CachePolicy::CACHED)
145 return xevm::LoadCacheControl::L1UC_L2UC_L3C;
146 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
147 return xevm::LoadCacheControl::L1UC_L2UC_L3UC;
148 else
149 llvm_unreachable("Unsupported cache control.");
150 case xegpu::CachePolicy::STREAMING:
151 if (L3hintVal == xegpu::CachePolicy::CACHED)
152 return xevm::LoadCacheControl::L1S_L2UC_L3C;
153 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
154 return xevm::LoadCacheControl::L1S_L2UC_L3UC;
155 else
156 llvm_unreachable("Unsupported cache control.");
157 case xegpu::CachePolicy::READ_INVALIDATE:
158 return xevm::LoadCacheControl::INVALIDATE_READ;
159 default:
160 llvm_unreachable("Unsupported cache control.");
161 }
162}
163
164static xevm::StoreCacheControl
165translateStoreXeGPUCacheHint(std::optional<xegpu::CachePolicy> L1hint,
166 std::optional<xegpu::CachePolicy> L3hint) {
167 // If no hints are provided, use the default cache control.
168 if (!L1hint && !L3hint)
169 return xevm::StoreCacheControl::USE_DEFAULT;
170 // If only one of the hints is provided, use the default for the other level.
171 auto L1hintVal = L1hint.value_or(xegpu::CachePolicy::UNCACHED);
172 auto L3hintVal = L3hint.value_or(xegpu::CachePolicy::WRITE_BACK);
173 switch (L1hintVal) {
174 case xegpu::CachePolicy::UNCACHED:
175 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
176 return xevm::StoreCacheControl::L1UC_L2UC_L3UC;
177 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
178 return xevm::StoreCacheControl::L1UC_L2UC_L3WB;
179 else
180 llvm_unreachable("Unsupported cache control.");
181 case xegpu::CachePolicy::STREAMING:
182 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
183 return xevm::StoreCacheControl::L1S_L2UC_L3UC;
184 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
185 return xevm::StoreCacheControl::L1S_L2UC_L3WB;
186 else
187 llvm_unreachable("Unsupported cache control.");
188 case xegpu::CachePolicy::WRITE_BACK:
189 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
190 return xevm::StoreCacheControl::L1WB_L2UC_L3UC;
191 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
192 return xevm::StoreCacheControl::L1WB_L2UC_L3WB;
193 else
194 llvm_unreachable("Unsupported cache control.");
195 case xegpu::CachePolicy::WRITE_THROUGH:
196 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
197 return xevm::StoreCacheControl::L1WT_L2UC_L3UC;
198 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
199 return xevm::StoreCacheControl::L1WT_L2UC_L3WB;
200 else
201 llvm_unreachable("Unsupported cache control.");
202 default:
203 llvm_unreachable("Unsupported cache control.");
204 }
205}
206
207//
208// Note:
209// Block operations for tile of sub byte element types are handled by
210// emulating with larger element types.
211// Tensor descriptor are keep intact and only ops consuming them are
212// emulated
213//
214
215//
216// High-D (>2D) nd descriptors are lowered by viewing the source as a single
217// flattened 2D plane, so the 2D-block surface covers every leading (batch)
218// plane at once and a batch position becomes a row offset into it. The surface
219// height is the row extent the leading dims reach:
220// base_height = size[R-2] + sum_d (size[d] - 1) * (stride[d] / stride[R-2])
221//
222// Leaving `base_ptr` at the true base means an out-of-range batch index lands
223// past `base_height`, where the HW boundary check handles it, instead of aiming
224// the surface at unmapped memory. Encoding the leading strides as row counts
225// (`stride[d] / stride[R-2]`) also makes them dimensionless, so they survive
226// element-type repacking (e.g. the f16 -> i32 transpose repack) without a unit
227// conversion.
228//
229// Limitations of the flattened-plane view:
230// 1. Each leading stride must be a whole number of rows. A source with gaps
231// between planes (`stride[d] % stride[R-2] != 0`) is not lowered. This can
232// only be checked when the strides are static; for dynamic strides the
233// divisibility is assumed.
234// 2. `base_height` grows with the leading extent, so a source with a large
235// batch x head x sequence extent can exceed the HW 2D-block surface height.
236// 3. Plane boundaries are invisible to the boundary check, which only knows
237// the surface: a tile whose rows run past `size[R-2]` spills into the
238// following rows of the surface -- the next plane, or the padding between
239// planes -- rather than being clipped there as it would be on a per-plane
240// surface. A load reads those rows instead of returning zeros, and a store
241// overwrites them, so it can corrupt the next plane. This only matters
242// when `size[R-2]` is not a multiple of the tile height.
243//
244
245class CreateNdDescToXeVMPattern
246 : public OpConversionPattern<xegpu::CreateNdDescOp> {
247 using Base::Base;
248
249 // Checks the surface width and pitch against the target's 2D block memory
250 // restrictions. Only these two are enforced: both are backed by the source's
251 // static shape and strides, whereas the base address alignment is a property
252 // of the allocation that is not visible here. The width and the pitch are
253 // checked independently, each only when it is static; a value known only at
254 // runtime is left alone, as guarding it inside the kernel would cost far more
255 // than it is worth.
256 //
257 // A violation is a dead end rather than a pattern that does not apply: the op
258 // has no other legalization path, so it is reported with an error that names
259 // the limit it misses.
260 LogicalResult checkMemoryRestrictions(xegpu::CreateNdDescOp op,
261 int64_t rank) const {
262 const auto *uArch =
264 // Without a known target chip there is no uArch to check against, so the
265 // descriptor must be accepted as-is.
266 if (!uArch)
267 return success();
268 const xegpu::uArch::BlockIOMemoryRestrictions &limits =
269 uArch->getBlockIOMemoryRestrictions();
270
271 // Recover the width and the pitch as a number of elements. A memref source
272 // carries both in its type, and either one may be dynamic independently of
273 // the other, so they are queried separately instead of being dropped
274 // together when the source is not fully static. `getMixedSizes` /
275 // `getMixedStrides` are only consulted for an integer source, where the op
276 // itself holds the extents.
277 std::optional<int64_t> widthElems;
278 std::optional<int64_t> pitchElems;
279 if (auto memrefTy = dyn_cast<MemRefType>(op.getSource().getType())) {
280 if (!ShapedType::isDynamic(memrefTy.getDimSize(rank - 1)))
281 widthElems = memrefTy.getDimSize(rank - 1);
282 // The source is known to be strided; the caller checked that already.
283 SmallVector<int64_t> staticStrides;
284 int64_t staticOffset;
285 if (succeeded(
286 memrefTy.getStridesAndOffset(staticStrides, staticOffset)) &&
287 !ShapedType::isDynamic(staticStrides[rank - 2]))
288 pitchElems = staticStrides[rank - 2];
289 } else {
290 widthElems = getConstantIntValue(op.getMixedSizes()[rank - 1]);
291 pitchElems = getConstantIntValue(op.getMixedStrides()[rank - 2]);
292 }
293
294 unsigned elemBitSize =
295 op.getType().getElementType().getIntOrFloatBitWidth();
296 // The restrictions are expressed in bytes, but a sub-byte element type can
297 // leave the width or the pitch on a fraction of a byte, so the comparisons
298 // are done in bits. Such a size meets no byte alignment at all and is
299 // reported as a violation rather than waved through.
300 auto describe = [](int64_t bits) -> std::string {
301 if (bits % 8 == 0)
302 return llvm::formatv("{0} bytes", bits / 8).str();
303 return llvm::formatv("{0} bits", bits).str();
304 };
305 auto check = [&](std::optional<int64_t> elems, int64_t minBytes,
306 int64_t alignBytes,
307 llvm::StringRef what) -> LogicalResult {
308 if (!elems)
309 return success();
310 int64_t bits = *elems * elemBitSize;
311 if (bits < minBytes * 8)
312 return op.emitError(llvm::formatv(
313 "2D block surface {0} must be at least {1} bytes on this "
314 "target, got {2}.",
315 what, minBytes, describe(bits)));
316 if (alignBytes > 0 && bits % (alignBytes * 8) != 0)
317 return op.emitError(llvm::formatv(
318 "2D block surface {0} must be a multiple of {1} bytes on this "
319 "target, got {2}.",
320 what, alignBytes, describe(bits)));
321 return success();
322 };
323
324 if (failed(check(widthElems, limits.minBaseWidthBytes,
325 limits.baseWidthAlignmentBytes, "width")))
326 return failure();
327 // The pitch is the row stride of the innermost 2D tile.
328 if (failed(check(pitchElems, limits.minBasePitchBytes,
329 limits.basePitchAlignmentBytes, "pitch")))
330 return failure();
331 // Consecutive rows may not overlap, so the pitch has to span the full
332 // width.
333 if (widthElems && pitchElems && *pitchElems < *widthElems)
334 return op.emitError(
335 llvm::formatv("2D block surface pitch must be at least the surface "
336 "width, got a pitch of {0} for a surface {1} wide.",
337 describe(*pitchElems * elemBitSize),
338 describe(*widthElems * elemBitSize)));
339 return success();
340 }
341
342 LogicalResult
343 matchAndRewrite(xegpu::CreateNdDescOp op,
344 xegpu::CreateNdDescOp::Adaptor adaptor,
345 ConversionPatternRewriter &rewriter) const override {
346 auto loc = op.getLoc();
347 auto source = op.getSource();
348
349 // Check all failure conditions before generating any IR, so nothing has to
350 // be rolled back.
351 int64_t rank = op.getType().getRank();
352 int64_t sourceRank;
353 auto memrefTy = dyn_cast<MemRefType>(source.getType());
354 if (memrefTy) {
355 if (!memrefTy.isStrided())
356 return rewriter.notifyMatchFailure(op, "Expected strided Memref.");
357 sourceRank = memrefTy.getRank();
358 } else if (isa<IntegerType>(source.getType())) {
359 sourceRank = op.getMixedSizes().size();
360 } else {
361 return rewriter.notifyMatchFailure(op,
362 "Expected ranked Memref or integer.");
363 }
364 if (sourceRank != rank)
365 return rewriter.notifyMatchFailure(
366 op, "Expected descriptor rank to match source rank; subview the "
367 "source down to the descriptor rank.");
368 if (rank > maxNdTdescRank)
369 return rewriter.notifyMatchFailure(
370 op, "Batched nd descriptor supports at most " +
371 std::to_string(maxNdTdescLeadingDims) +
372 " leading dims (rank <= " + std::to_string(maxNdTdescRank) +
373 ").");
374 // Limitation 1 above; dynamic strides are assumed to divide evenly.
375 if (rank > 2) {
376 SmallVector<std::optional<int64_t>> constStrides(rank, std::nullopt);
377 if (memrefTy) {
378 SmallVector<int64_t> staticStrides;
379 int64_t staticOffset;
380 if (succeeded(
381 memrefTy.getStridesAndOffset(staticStrides, staticOffset)))
382 for (int64_t d = 0; d < rank; ++d)
383 if (!ShapedType::isDynamic(staticStrides[d]))
384 constStrides[d] = staticStrides[d];
385 } else {
386 SmallVector<OpFoldResult> mixed = op.getMixedStrides();
387 for (int64_t d = 0; d < rank; ++d)
388 constStrides[d] = getConstantIntValue(mixed[d]);
389 }
390 if (std::optional<int64_t> pitch = constStrides[rank - 2]) {
391 for (int64_t d = 0; d < rank - 2; ++d) {
392 std::optional<int64_t> leading = constStrides[d];
393 if (leading && (*pitch == 0 || *leading % *pitch != 0))
394 return rewriter.notifyMatchFailure(
395 op, "Expected each leading (batch) stride to be a multiple of "
396 "the row stride; the source has gaps between planes.");
397 }
398 }
399 }
400 // Enforce the target's 2D block memory restrictions. A rank-1 descriptor is
401 // just a base address and is not consumed by a 2D block op, so it is
402 // exempt.
403 if (rank >= 2 && failed(checkMemoryRestrictions(op, rank)))
404 return failure();
405
406 Type payloadElemTy = rewriter.getI32Type();
407 Type i64Ty = rewriter.getI64Type();
408
409 // Access the adaptor only after the failure checks, so a bail-out leaves no
410 // materialization cast behind.
411 Value baseAddr = adaptor.getSource();
412 if (isa<IntegerType>(source.getType()) && baseAddr.getType() != i64Ty) {
413 // Pointer type may be i32. Cast to i64 if needed.
414 baseAddr = arith::ExtUIOp::create(rewriter, loc, i64Ty, baseAddr);
415 }
416 // 1D tensor descriptor is just the base address.
417 if (rank == 1) {
418 rewriter.replaceOp(op, baseAddr);
419 return success();
420 }
421
422 SmallVector<OpFoldResult> mixedSizes;
423 SmallVector<OpFoldResult> mixedStrides;
424 if (memrefTy && !xegpu::hasStaticShapeAndStrides(memrefTy)) {
425 auto meta =
426 memref::ExtractStridedMetadataOp::create(rewriter, loc, source);
427 mixedSizes = meta.getConstifiedMixedSizes();
428 mixedStrides = meta.getConstifiedMixedStrides();
429 } else {
430 mixedSizes = op.getMixedSizes();
431 mixedStrides = op.getMixedStrides();
432 }
433
434 // Op is lowered to a code sequence that populates payload.
435 // Payload is a 8xi32 vector. Offset to individual fields are defined in
436 // NdTdescOffset enum.
437 VectorType payloadTy = VectorType::get(8, payloadElemTy);
438 // 4xi64 view is used for inserting the base pointer.
439 VectorType payloadI64Ty = VectorType::get(4, i64Ty);
440 // Initialize payload to zero.
441 Value payload = arith::ConstantOp::create(
442 rewriter, loc,
443 DenseElementsAttr::get(payloadTy, IntegerAttr::get(payloadElemTy, 0)));
444
445 // Utility for creating offset values from op fold result.
446 auto createOffset = [&](SmallVector<OpFoldResult> &ofrVec,
447 unsigned idx) -> Value {
448 Value val = getValueOrCreateConstantIntOp(rewriter, loc, ofrVec[idx]);
449 val = getValueOrCreateCastToIndexLike(rewriter, loc, payloadElemTy, val);
450 return val;
451 };
452 // The descriptor's innermost 2 dims are the 2D tile (H, W).
453 Value baseShapeW = createOffset(mixedSizes, rank - 1);
454 // Pitch is the stride of dim rank-2 (the row stride of the 2D tile).
455 Value basePitch = createOffset(mixedStrides, rank - 2);
456 // Leading (batch) strides as a number of rows (`stride[d] / pitch`).
457 SmallVector<Value> leadingRowStrides;
458 for (int64_t d = 0; d < rank - 2; ++d) {
459 std::optional<int64_t> leading = getConstantIntValue(mixedStrides[d]);
460 std::optional<int64_t> pitch =
461 getConstantIntValue(mixedStrides[rank - 2]);
462 if (leading && pitch && *pitch != 0)
463 leadingRowStrides.push_back(arith::ConstantIntOp::create(
464 rewriter, loc, payloadElemTy, *leading / *pitch));
465 else
466 leadingRowStrides.push_back(arith::DivUIOp::create(
467 rewriter, loc, createOffset(mixedStrides, d), basePitch));
468 }
469 // Height of the flattened plane: every leading (batch) plane is stacked
470 // into the surface, so the boundary check covers an out-of-range batch.
471 // base_height = size[R-2] + sum_d (size[d] - 1) * leadingRowStride[d]
472 // For rank 2 this is just size[0].
473 Value baseShapeH = createOffset(mixedSizes, rank - 2);
474 if (rank > 2) {
475 Value one = arith::ConstantIntOp::create(rewriter, loc, payloadElemTy, 1);
476 for (int64_t d = 0; d < rank - 2; ++d) {
477 Value planesBelow = rewriter.createOrFold<arith::SubIOp>(
478 loc, createOffset(mixedSizes, d), one);
479 Value rows = rewriter.createOrFold<arith::MulIOp>(loc, planesBelow,
480 leadingRowStrides[d]);
481 baseShapeH =
482 rewriter.createOrFold<arith::AddIOp>(loc, baseShapeH, rows);
483 }
484 }
485 // Populate payload.
486 Value payLoadAsI64 =
487 vector::BitCastOp::create(rewriter, loc, payloadI64Ty, payload);
488 payLoadAsI64 =
489 vector::InsertOp::create(rewriter, loc, baseAddr, payLoadAsI64,
490 static_cast<int>(NdTdescOffset::BasePtr));
491 payload = vector::BitCastOp::create(rewriter, loc, payloadTy, payLoadAsI64);
492 payload =
493 vector::InsertOp::create(rewriter, loc, baseShapeW, payload,
494 static_cast<int>(NdTdescOffset::BaseShapeW));
495 payload =
496 vector::InsertOp::create(rewriter, loc, baseShapeH, payload,
497 static_cast<int>(NdTdescOffset::BaseShapeH));
498 payload =
499 vector::InsertOp::create(rewriter, loc, basePitch, payload,
500 static_cast<int>(NdTdescOffset::BasePitch));
501 // The leading (batch) row strides go into the spare payload slots; the
502 // load/store/prefetch lowering turns the batch offsets into a row offset
503 // with them.
504 for (int64_t d = 0; d < rank - 2; ++d)
505 payload = vector::InsertOp::create(
506 rewriter, loc, leadingRowStrides[d], payload,
507 static_cast<int>(NdTdescOffset::LeadingStride0) + d);
508 rewriter.replaceOp(op, payload);
509 return success();
510 }
511};
512
513template <
514 typename OpType,
515 typename = std::enable_if_t<llvm::is_one_of<
516 OpType, xegpu::LoadNdOp, xegpu::StoreNdOp, xegpu::PrefetchNdOp>::value>>
517class LoadStorePrefetchNdToXeVMPattern : public OpConversionPattern<OpType> {
518 using OpConversionPattern<OpType>::OpConversionPattern;
519 LogicalResult
520 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
521 ConversionPatternRewriter &rewriter) const override {
522 auto mixedOffsets = op.getMixedOffsets();
523 int64_t opOffsetsSize = mixedOffsets.size();
524 auto loc = op.getLoc();
525 auto ctxt = rewriter.getContext();
526
527 auto tdesc = adaptor.getTensorDesc();
528 auto tdescTy = op.getTensorDescType();
529 auto tileRank = tdescTy.getRank();
530 if (opOffsetsSize != tileRank)
531 return rewriter.notifyMatchFailure(
532 op, "Expected offset rank to match descriptor rank.");
533 if (tileRank > 2 && llvm::any_of(tdescTy.getShape().drop_back(2),
534 [](int64_t d) { return d != 1; }))
535 return rewriter.notifyMatchFailure(
536 op, "Expected leading (batch) descriptor dims to be unit.");
537 if (tileRank > maxNdTdescRank)
538 return rewriter.notifyMatchFailure(
539 op, "Expected descriptor rank <= " + std::to_string(maxNdTdescRank) +
540 ".");
541 auto elemType = tdescTy.getElementType();
542 auto elemBitSize = elemType.getIntOrFloatBitWidth();
543 bool isSubByte = elemBitSize < 8;
544 uint64_t wScaleFactor = 1;
545
546 if (!isSubByte && (elemBitSize % 8 != 0))
547 return rewriter.notifyMatchFailure(
548 op, "Expected element type bit width to be multiple of 8.");
549 auto tileW = tdescTy.getDimSize(tileRank - 1);
550 // For sub byte types, only 4bits are currently supported.
551 if (isSubByte) {
552 if (elemBitSize != 4)
553 return rewriter.notifyMatchFailure(
554 op, "Only sub byte types of 4bits are supported.");
555 if (tileRank != 2)
556 return rewriter.notifyMatchFailure(
557 op, "Sub byte types are only supported for 2D tensor descriptors.");
558 auto subByteFactor = 8 / elemBitSize;
559 auto tileH = tdescTy.getDimSize(0);
560 // Handle special case for packed load.
561 if constexpr (std::is_same_v<OpType, xegpu::LoadNdOp>) {
562 if (op.getPacked().value_or(false)) {
563 // packed load is implemented as packed loads of 8bit elements.
564 if (tileH == systolicDepth * 4 &&
565 tileW == executionSize * subByteFactor) {
566 // Usage case for loading as Matrix B with pack request.
567 // source is assumed to pre-packed into 8bit elements
568 // Emulate with 8bit loads with pack request.
569 // scaled_tileW = executionSize
570 elemType = rewriter.getIntegerType(8);
571 tileW = executionSize;
572 wScaleFactor = subByteFactor;
573 }
574 }
575 }
576 // If not handled by packed load case above, handle other cases.
577 if (wScaleFactor == 1) {
578 auto sub16BitFactor = subByteFactor * 2;
579 if (tileW == executionSize * sub16BitFactor) {
580 // Usage case for loading as Matrix A operand
581 // Emulate with 16bit loads/stores.
582 // scaled_tileW = executionSize
583 elemType = rewriter.getIntegerType(16);
584 tileW = executionSize;
585 wScaleFactor = sub16BitFactor;
586 } else {
587 return rewriter.notifyMatchFailure(
588 op, "Unsupported tile shape for sub byte types.");
589 }
590 }
591 // recompute element bit size for emulation.
592 elemBitSize = elemType.getIntOrFloatBitWidth();
593 }
594
595 // Get address space from tensor descriptor memory space.
596 auto ptrTypeLLVM = LLVM::LLVMPointerType::get(
597 ctxt, getNumericXeVMAddrSpace(tdescTy.getMemorySpace()));
598 if (tileRank >= 2) {
599 // Compute element byte size.
600 Value elemByteSize = arith::ConstantIntOp::create(
601 rewriter, loc, rewriter.getI32Type(), elemBitSize / 8);
602 VectorType payloadI64Ty = VectorType::get(4, rewriter.getI64Type());
603 Value payLoadAsI64 =
604 vector::BitCastOp::create(rewriter, loc, payloadI64Ty, tdesc);
605 Value basePtr =
606 vector::ExtractOp::create(rewriter, loc, payLoadAsI64,
607 static_cast<int>(NdTdescOffset::BasePtr));
608 Value baseShapeW = vector::ExtractOp::create(
609 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BaseShapeW));
610 Value baseShapeH = vector::ExtractOp::create(
611 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BaseShapeH));
612 Value basePitch = vector::ExtractOp::create(
613 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BasePitch));
614
615 Value offsetW = getValueOrCreateConstantIntOp(rewriter, loc,
616 mixedOffsets[tileRank - 1]);
617 offsetW = getValueOrCreateCastToIndexLike(rewriter, loc,
618 rewriter.getI32Type(), offsetW);
619 Value offsetH = getValueOrCreateConstantIntOp(rewriter, loc,
620 mixedOffsets[tileRank - 2]);
621 offsetH = getValueOrCreateCastToIndexLike(rewriter, loc,
622 rewriter.getI32Type(), offsetH);
623 // Turn the leading (batch) offsets into a row offset into the flattened
624 // plane, using the row-unit batch strides encoded at create time:
625 // offsetH += sum_d offset[d] * leadingRowStride[d]
626 // The base pointer stays at the true base, so an out-of-range batch index
627 // is caught by the HW boundary check instead of moving the surface to
628 // unmapped memory.
629 for (int64_t d = 0; d < tileRank - 2; ++d) {
630 Value off =
631 getValueOrCreateConstantIntOp(rewriter, loc, mixedOffsets[d]);
632 off = getValueOrCreateCastToIndexLike(rewriter, loc,
633 rewriter.getI32Type(), off);
634 Value rowStride = vector::ExtractOp::create(
635 rewriter, loc, tdesc,
636 static_cast<int>(NdTdescOffset::LeadingStride0) + d);
637 Value term = arith::MulIOp::create(rewriter, loc, off, rowStride);
638 offsetH = arith::AddIOp::create(rewriter, loc, offsetH, term);
639 }
640 // Convert base pointer (i64) to LLVM pointer type.
641 Value basePtrLLVM =
642 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtr);
643 // FIXME: width or pitch is not the same as baseShapeW it should be the
644 // stride of the second to last dimension in row major layout.
645 // Compute width in bytes.
646 Value baseShapeWInBytes =
647 arith::MulIOp::create(rewriter, loc, baseShapeW, elemByteSize);
648 // Compute pitch in bytes.
649 Value basePitchBytes =
650 arith::MulIOp::create(rewriter, loc, basePitch, elemByteSize);
651
652 if (wScaleFactor > 1) {
653 // Scale offsetW, baseShapeWInBytes for sub byte emulation.
654 // Note: tileW is already scaled above.
655 Value wScaleFactorValLog2 = arith::ConstantIntOp::create(
656 rewriter, loc, rewriter.getI32Type(), llvm::Log2_64(wScaleFactor));
657 baseShapeWInBytes = arith::ShRSIOp::create(
658 rewriter, loc, baseShapeWInBytes, wScaleFactorValLog2);
659 basePitchBytes = arith::ShRSIOp::create(rewriter, loc, basePitchBytes,
660 wScaleFactorValLog2);
661 offsetW =
662 arith::ShRSIOp::create(rewriter, loc, offsetW, wScaleFactorValLog2);
663 }
664 // Get tile height from the tensor descriptor type (second-to-last dim).
665 auto tileH = tdescTy.getDimSize(tileRank - 2);
666 // Get vblocks from the tensor descriptor type.
667 int32_t vblocks = tdescTy.getArrayLength();
668 if constexpr (std::is_same_v<OpType, xegpu::StoreNdOp>) {
669 Value src = adaptor.getValue();
670 // If store value is a scalar, get value from op instead of adaptor.
671 // Adaptor might have optimized away single element vector
672 if (src.getType().isIntOrFloat()) {
673 src = op.getValue();
674 }
675 VectorType srcVecTy = dyn_cast<VectorType>(src.getType());
676 if (!srcVecTy)
677 return rewriter.notifyMatchFailure(
678 op, "Expected store value to be a vector type.");
679 // Get flat vector type of integer type with matching element bit size.
680 VectorType newSrcVecTy =
681 encodeVectorTypeTo(srcVecTy, rewriter.getIntegerType(elemBitSize));
682 if (srcVecTy != newSrcVecTy)
683 src = vector::BitCastOp::create(rewriter, loc, newSrcVecTy, src);
684 auto storeCacheControl =
685 translateStoreXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
686 xevm::BlockStore2dOp::create(
687 rewriter, loc, basePtrLLVM, baseShapeWInBytes, baseShapeH,
688 basePitchBytes, offsetW, offsetH, elemBitSize, tileW, tileH, src,
689 xevm::StoreCacheControlAttr::get(ctxt, storeCacheControl));
690 rewriter.eraseOp(op);
691 } else {
692 auto loadCacheControl =
693 translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
694 if constexpr (std::is_same_v<OpType, xegpu::PrefetchNdOp>) {
695 xevm::BlockPrefetch2dOp::create(
696 rewriter, loc, basePtrLLVM, baseShapeWInBytes, baseShapeH,
697 basePitchBytes, offsetW, offsetH, elemBitSize, tileW, tileH,
698 vblocks, xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
699 rewriter.eraseOp(op);
700 } else {
701 VectorType dstVecTy = cast<VectorType>(op.getValue().getType());
702 bool vnni = op.getPacked().value_or(false);
703 auto transposeValue = op.getTranspose();
704 bool transpose =
705 transposeValue.has_value() && transposeValue.value()[0] == 1;
706 // Handle special case of 32x16 and 8bit element load
707 // with no vnni, no transpose, no vblocks.
708 // For this special case, vnni and non vnni yields the same output
709 // and only the vnni variant is supported by HW.
710 // Check and set vnni of the special case.
711 if (elemBitSize == 8 && tileW == 16 && tileH == 32 && !vnni &&
712 !transpose) {
713 vnni = true;
714 }
715 // Handle tranpose request on small element size
716 // Transpose needs to be requested on 32bit element type.
717 // offsetW and tileW needs to be adjusted to account for element type
718 // change.
719 if (transpose && elemBitSize < 32) {
720 int32_t scale = 32 / elemBitSize;
721 Value scaleLog2 = arith::ConstantIntOp::create(
722 rewriter, loc, rewriter.getI32Type(), llvm::Log2_64(scale));
723 offsetW = arith::ShRSIOp::create(rewriter, loc, offsetW, scaleLog2);
724 tileW = tileW * elemBitSize / 32;
725 elemBitSize = 32;
726 }
727 VectorType loadedTy = encodeVectorTypeTo(
728 dstVecTy, vnni ? rewriter.getI32Type()
729 : rewriter.getIntegerType(elemBitSize));
730
731 Value resultFlatVec = xevm::BlockLoad2dOp::create(
732 rewriter, loc, loadedTy, basePtrLLVM, baseShapeWInBytes,
733 baseShapeH, basePitchBytes, offsetW, offsetH, elemBitSize, tileW,
734 tileH, vblocks, transpose, vnni,
735 xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
736 resultFlatVec = vector::BitCastOp::create(
737 rewriter, loc,
738 encodeVectorTypeTo(loadedTy, dstVecTy.getElementType()),
739 resultFlatVec);
740 rewriter.replaceOp(op, resultFlatVec);
741 }
742 }
743 } else {
744 // 1D tensor descriptor.
745 // `tdesc` represents base address as i64
746 // Offset in number of elements, need to multiply by element byte size.
747 // Compute byte offset.
748 // byteOffset = offset * elementByteSize
749 Value offset =
750 getValueOrCreateConstantIntOp(rewriter, loc, mixedOffsets[0]);
751 offset = getValueOrCreateCastToIndexLike(rewriter, loc,
752 rewriter.getI64Type(), offset);
753 // Compute element byte size.
754 Value elemByteSize = arith::ConstantIntOp::create(
755 rewriter, loc, rewriter.getI64Type(), elemBitSize / 8);
756 Value byteOffset =
757 rewriter.createOrFold<arith::MulIOp>(loc, offset, elemByteSize);
758 // Final address = basePtr + byteOffset
759 Value finalAddrI64 = rewriter.createOrFold<arith::AddIOp>(
760 loc, tdesc,
761 getValueOrCreateCastToIndexLike(rewriter, loc, rewriter.getI64Type(),
762 byteOffset));
763 // Convert base pointer (i64) to LLVM pointer type.
764 Value finalPtrLLVM =
765 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, finalAddrI64);
766 if constexpr (std::is_same_v<OpType, xegpu::StoreNdOp>) {
767 Value src = adaptor.getValue();
768 // If store value is a scalar, get value from op instead of adaptor.
769 // Adaptor might have optimized away single element vector
770 if (src.getType().isIntOrFloat()) {
771 src = op.getValue();
772 }
773 VectorType srcVecTy = dyn_cast<VectorType>(src.getType());
774 if (!srcVecTy)
775 return rewriter.notifyMatchFailure(
776 op, "Expected store value to be a vector type.");
777 // Get flat vector type of integer type with matching element bit size.
778 VectorType newSrcVecTy =
779 encodeVectorTypeTo(srcVecTy, rewriter.getIntegerType(elemBitSize));
780 if (srcVecTy != newSrcVecTy)
781 src = vector::BitCastOp::create(rewriter, loc, newSrcVecTy, src);
782 auto storeCacheControl =
783 translateStoreXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
784 rewriter.replaceOpWithNewOp<xevm::BlockStoreOp>(
785 op, finalPtrLLVM, src,
786 xevm::StoreCacheControlAttr::get(ctxt, storeCacheControl));
787 } else if constexpr (std::is_same_v<OpType, xegpu::LoadNdOp>) {
788 auto loadCacheControl =
789 translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
790 VectorType resTy = cast<VectorType>(op.getValue().getType());
791 VectorType loadedTy =
792 encodeVectorTypeTo(resTy, rewriter.getIntegerType(elemBitSize));
793 Value load = xevm::BlockLoadOp::create(
794 rewriter, loc, loadedTy, finalPtrLLVM,
795 xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
796 if (loadedTy != resTy)
797 load = vector::BitCastOp::create(rewriter, loc, resTy, load);
798 rewriter.replaceOp(op, load);
799 } else {
800 return rewriter.notifyMatchFailure(
801 op, "Unsupported operation: xegpu.prefetch_nd with tensor "
802 "descriptor rank == 1");
803 }
804 }
805 return success();
806 }
807};
808
809// Add a builder that creates
810// offset * elemByteSize + baseAddr
811static Value addOffsetToBaseAddr(ConversionPatternRewriter &rewriter,
812 Location loc, Value baseAddr, Value offset,
813 int64_t elemByteSize) {
815 rewriter, loc, baseAddr.getType(), elemByteSize);
816 Value byteOffset = arith::MulIOp::create(rewriter, loc, offset, byteSize);
817 Value newAddr = arith::AddIOp::create(rewriter, loc, baseAddr, byteOffset);
818 return newAddr;
819}
820
821// Returns true when every element of `mask` carries the same bit, so gating a
822// whole contiguous block on element 0 is equivalent. Splat constants, an
823// all-ones or all-zeros `vector.constant_mask`, broadcasts of a scalar, and a
824// `vector.from_elements` of one repeated value qualify.
825//
826// TODO: this recognizes a fixed list of producers. A general uniformity query
827// on the vector dialect would cover more forms and would not need updating
828// every time a new producer shows up here.
829static bool isUniformMask(Value mask) {
830 if (!isa<VectorType>(mask.getType()))
831 return true;
832 DenseElementsAttr splat;
833 if (matchPattern(mask, m_Constant(&splat)) && splat.isSplat())
834 return true;
835 if (auto constantMask = mask.getDefiningOp<vector::ConstantMaskOp>()) {
836 // All-ones and all-zeros are uniform. A zero dim size is only legal when
837 // every dim is zero, so those are the only two uniform cases.
838 return constantMask.isAllOnesMask() ||
839 llvm::all_of(constantMask.getMaskDimSizes(),
840 [](int64_t size) { return size == 0; });
841 }
842 if (auto broadcast = mask.getDefiningOp<vector::BroadcastOp>())
843 return !isa<VectorType>(broadcast.getSource().getType());
844 if (auto fromElements = mask.getDefiningOp<vector::FromElementsOp>())
845 return llvm::all_equal(fromElements.getElements());
846 // Flattening a rank > 1 operand to rank 1 inserts a shape cast. It keeps the
847 // same elements, so it keeps uniformity.
848 if (auto shapeCast = mask.getDefiningOp<vector::ShapeCastOp>())
849 return isUniformMask(shapeCast.getSource());
850 return false;
851}
852
853template <typename OpType,
854 typename = std::enable_if_t<llvm::is_one_of<
855 OpType, xegpu::LoadGatherOp, xegpu::StoreScatterOp>::value>>
856class LoadStoreToXeVMPattern : public OpConversionPattern<OpType> {
857 using OpConversionPattern<OpType>::OpConversionPattern;
858 LogicalResult
859 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
860 ConversionPatternRewriter &rewriter) const override {
861 Value offset = adaptor.getOffsets();
862 if (!offset)
863 return rewriter.notifyMatchFailure(op, "Expected offset to be provided.");
864 auto loc = op.getLoc();
865 auto ctxt = rewriter.getContext();
866 Value basePtrI64;
867 // Load result or Store valye Type can be vector or scalar.
868 Type valOrResTy;
869 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>)
870 valOrResTy =
871 this->getTypeConverter()->convertType(op.getResult().getType());
872 else
873 valOrResTy = adaptor.getValue().getType();
874 VectorType valOrResVecTy = dyn_cast<VectorType>(valOrResTy);
875 bool hasScalarVal = !valOrResVecTy;
876 int64_t elemBitWidth =
877 hasScalarVal ? valOrResTy.getIntOrFloatBitWidth()
878 : valOrResVecTy.getElementType().getIntOrFloatBitWidth();
879 // Element type must be multiple of 8 bits.
880 if (elemBitWidth % 8 != 0)
881 return rewriter.notifyMatchFailure(
882 op, "Expected element type bit width to be multiple of 8.");
883 int64_t elemByteSize = elemBitWidth / 8;
884 // Default memory space is global.
885 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
886 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::Global));
887 // Base pointer can come from source (load) or dest (store).
888 // If they are memrefs, we use their memory space.
889 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>) {
890 basePtrI64 = adaptor.getSource();
891 if (auto memRefTy = dyn_cast<MemRefType>(op.getSource().getType())) {
892 FailureOr<unsigned> addrSpace =
893 getNumericMemorySpace(memRefTy.getMemorySpace());
894 if (failed(addrSpace))
895 return rewriter.notifyMatchFailure(
896 op, "Unsupported memref memory space attribute.");
897 if (*addrSpace != 0)
898 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
899 }
900 } else {
901 basePtrI64 = adaptor.getDest();
902 if (auto memRefTy = dyn_cast<MemRefType>(op.getDest().getType())) {
903 FailureOr<unsigned> addrSpace =
904 getNumericMemorySpace(memRefTy.getMemorySpace());
905 if (failed(addrSpace))
906 return rewriter.notifyMatchFailure(
907 op, "Unsupported memref memory space attribute.");
908 if (*addrSpace != 0)
909 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
910 }
911 }
912 // Base pointer is passed as i32 or i64 by adaptor, cast to i64 if needed.
913 if (basePtrI64.getType() != rewriter.getI64Type()) {
914 basePtrI64 = arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(),
915 basePtrI64);
916 }
917 Value mask = adaptor.getMask();
918
919 // Coalesce a lane's multi-element access into one block access.
920 //
921 // Distribution gives every element its own offset and mask bit, so a lane
922 // that takes D neighbouring elements arrives with `vector<D>` offsets and
923 // mask. One block access needs one base offset and one mask bit, so take
924 // both from element 0. A non-uniform mask cannot be reduced to one bit, so
925 // it stays a vector and fails to match below.
926 auto origOffsetsTy = dyn_cast<VectorType>(op.getOffsets().getType());
927 if (isa<VectorType>(offset.getType()) && origOffsetsTy && valOrResVecTy &&
928 origOffsetsTy.getNumElements() == valOrResVecTy.getNumElements() &&
929 isUniformMask(op.getMask())) {
930 offset = vector::ExtractOp::create(rewriter, loc, offset,
931 ArrayRef<int64_t>{0});
932 if (isa<VectorType>(mask.getType()))
933 mask = vector::ExtractOp::create(rewriter, loc, mask,
934 ArrayRef<int64_t>{0});
935 }
936
937 if (dyn_cast<VectorType>(offset.getType())) {
938 // Offset needs be scalar. Single element vector is converted to scalar
939 // by type converter.
940 return rewriter.notifyMatchFailure(op, "Expected offset to be a scalar.");
941 } else {
942 // If offset is provided, we add them to the base pointer.
943 // Offset is in number of elements, we need to multiply by
944 // element byte size.
945 basePtrI64 =
946 addOffsetToBaseAddr(rewriter, loc, basePtrI64, offset, elemByteSize);
947 }
948 // Convert base pointer (i64) to LLVM pointer type.
949 Value basePtrLLVM =
950 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
951
952 Value maskForLane;
953 VectorType maskVecTy = dyn_cast<VectorType>(mask.getType());
954 if (maskVecTy) {
955 // Mask needs be scalar. Single element vector is converted to scalar by
956 // type converter.
957 return rewriter.notifyMatchFailure(op, "Expected mask to be a scalar.");
958 } else
959 maskForLane = mask;
960 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>) {
961 scf::IfOp ifOp = scf::IfOp::create(rewriter, loc, {valOrResTy},
962 maskForLane, true, true);
963 // If mask is true,- then clause - load from memory and yield.
964 rewriter.setInsertionPointToStart(&ifOp.getThenRegion().front());
965 if (!hasScalarVal)
966 valOrResTy = VectorType::get({valOrResVecTy.getNumElements()},
967 valOrResVecTy.getElementType());
968 Value loaded =
969 LLVM::LoadOp::create(rewriter, loc, valOrResTy, basePtrLLVM);
970 // Set cache control attribute on the load operation.
972 "cache_control", xevm::LoadCacheControlAttr::get(
973 ctxt, translateLoadXeGPUCacheHint(
974 op.getL1Hint(), op.getL3Hint())));
975 scf::YieldOp::create(rewriter, loc, ValueRange{loaded});
976 rewriter.setInsertionPointToStart(&ifOp.getElseRegion().front());
977 // If mask is false - else clause -yield a vector of zeros.
978 auto eTy = hasScalarVal ? valOrResTy : valOrResVecTy.getElementType();
979 TypedAttr eVal;
980 if (eTy.isFloat())
981 eVal = FloatAttr::get(eTy, 0.0);
982 else
983 eVal = IntegerAttr::get(eTy, 0);
984 if (hasScalarVal)
985 loaded = arith::ConstantOp::create(rewriter, loc, eVal);
986 else
987 loaded = arith::ConstantOp::create(
988 rewriter, loc, DenseElementsAttr::get(valOrResVecTy, eVal));
989 scf::YieldOp::create(rewriter, loc, ValueRange{loaded});
990 rewriter.replaceOp(op, ifOp.getResult(0));
991 } else {
992 // If mask is true, perform the store.
993 scf::IfOp ifOp = scf::IfOp::create(rewriter, loc, maskForLane, false);
994 auto body = ifOp.getBody();
995 rewriter.setInsertionPointToStart(body);
996 auto storeOp =
997 LLVM::StoreOp::create(rewriter, loc, adaptor.getValue(), basePtrLLVM);
998 // Set cache control attribute on the store operation.
999 storeOp.getOperation()->setDiscardableAttr(
1000 "cache_control", xevm::StoreCacheControlAttr::get(
1001 ctxt, translateStoreXeGPUCacheHint(
1002 op.getL1Hint(), op.getL3Hint())));
1003 rewriter.eraseOp(op);
1004 }
1005 return success();
1006 }
1007};
1008
1009class CreateMemDescOpPattern final
1010 : public OpConversionPattern<xegpu::CreateMemDescOp> {
1011public:
1012 using OpConversionPattern<xegpu::CreateMemDescOp>::OpConversionPattern;
1013 LogicalResult
1014 matchAndRewrite(xegpu::CreateMemDescOp op, OpAdaptor adaptor,
1015 ConversionPatternRewriter &rewriter) const override {
1016
1017 rewriter.replaceOp(op, adaptor.getSource());
1018 return success();
1019 }
1020};
1021
1022template <typename OpType,
1023 typename = std::enable_if_t<llvm::is_one_of<
1024 OpType, xegpu::LoadMatrixOp, xegpu::StoreMatrixOp>::value>>
1025class LoadStoreMatrixToXeVMPattern : public OpConversionPattern<OpType> {
1026 using OpConversionPattern<OpType>::OpConversionPattern;
1027 LogicalResult
1028 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
1029 ConversionPatternRewriter &rewriter) const override {
1030
1031 SmallVector<OpFoldResult> offsets = op.getMixedOffsets();
1032 if (offsets.empty())
1033 return rewriter.notifyMatchFailure(op, "Expected offset to be provided.");
1034
1035 auto loc = op.getLoc();
1036 auto ctxt = rewriter.getContext();
1037 Value baseAddr32 = adaptor.getMemDesc();
1038 Value mdescVal = op.getMemDesc();
1039 // Load result or Store value Type can be vector or scalar.
1040 Type dataTy;
1041 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
1042 Type resType = op.getResult().getType();
1043 // Some transforms may leave unit dimension in the 2D vector, adaptors do
1044 // not catch it for results.
1045 if (auto vecType = dyn_cast<VectorType>(resType)) {
1046 assert(llvm::count_if(vecType.getShape(),
1047 [](int64_t d) { return d != 1; }) <= 1 &&
1048 "Expected either 1D vector or nD with unit dimensions");
1049 resType = VectorType::get({vecType.getNumElements()},
1050 vecType.getElementType());
1051 }
1052 dataTy = resType;
1053 } else
1054 dataTy = adaptor.getData().getType();
1055 VectorType valOrResVecTy = dyn_cast<VectorType>(dataTy);
1056 if (!valOrResVecTy)
1057 valOrResVecTy = VectorType::get(1, dataTy);
1058
1059 int64_t elemBitWidth =
1060 valOrResVecTy.getElementType().getIntOrFloatBitWidth();
1061 // Element type must be multiple of 8 bits.
1062 if (elemBitWidth % 8 != 0)
1063 return rewriter.notifyMatchFailure(
1064 op, "Expected element type bit width to be multiple of 8.");
1065 int64_t elemByteSize = elemBitWidth / 8;
1066
1067 // Default memory space is SLM.
1068 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
1069 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::SLM));
1070
1071 auto mdescTy = cast<xegpu::MemDescType>(mdescVal.getType());
1072
1073 Value linearOffset = mdescTy.getLinearOffsets(rewriter, loc, offsets);
1074 linearOffset = arith::IndexCastUIOp::create(
1075 rewriter, loc, rewriter.getI32Type(), linearOffset);
1076 Value basePtrI32 = addOffsetToBaseAddr(rewriter, loc, baseAddr32,
1077 linearOffset, elemByteSize);
1078
1079 // convert base pointer (i32) to LLVM pointer type
1080 Value basePtrLLVM =
1081 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI32);
1082
1083 if (op.getSubgroupBlockIoAttr()) {
1084 // if the attribute 'subgroup_block_io' is set to true, it lowers to
1085 // xevm.blockload
1086
1087 Type intElemTy = rewriter.getIntegerType(elemBitWidth);
1088 VectorType intVecTy =
1089 VectorType::get(valOrResVecTy.getShape(), intElemTy);
1090
1091 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
1092 Value loadOp = xevm::BlockLoadOp::create(
1093 rewriter, loc, intVecTy, basePtrLLVM, /*cache_control=*/nullptr);
1094 if (intVecTy != valOrResVecTy) {
1095 loadOp =
1096 vector::BitCastOp::create(rewriter, loc, valOrResVecTy, loadOp);
1097 }
1098 rewriter.replaceOp(op, loadOp);
1099 } else {
1100 Value dataToStore = adaptor.getData();
1101 if (valOrResVecTy != intVecTy) {
1102 dataToStore =
1103 vector::BitCastOp::create(rewriter, loc, intVecTy, dataToStore);
1104 }
1105 xevm::BlockStoreOp::create(rewriter, loc, basePtrLLVM, dataToStore,
1106 nullptr);
1107 rewriter.eraseOp(op);
1108 }
1109 return success();
1110 }
1111
1112 if (valOrResVecTy.getNumElements() >= 1) {
1113 auto chipOpt = xegpu::getChipStr(op);
1114 if (!chipOpt ||
1115 (*chipOpt != "pvc" && *chipOpt != "bmg" && *chipOpt != "cri")) {
1116 // the lowering for chunk load only works for pvc, bmg or cri
1117 return rewriter.notifyMatchFailure(
1118 op, "The lowering is specific to pvc, bmg or cri.");
1119 }
1120 }
1121
1122 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
1123 // The load result type is taken from the type converter. This maps
1124 // element types that are not directly representable in LLVM (e.g.
1125 // f8E8M0FNU) to an integer storage type of the same bit width, and
1126 // collapses single-element vectors to a scalar, since LLVM load/store
1127 // does not support vectors of size 1.
1128 Type loadTy =
1129 this->getTypeConverter()->convertType(op.getResult().getType());
1130 auto loadOp = LLVM::LoadOp::create(rewriter, loc, loadTy, basePtrLLVM);
1131 rewriter.replaceOp(op, loadOp);
1132 } else {
1133 LLVM::StoreOp::create(rewriter, loc, adaptor.getData(), basePtrLLVM);
1134 rewriter.eraseOp(op);
1135 }
1136 return success();
1137 }
1138};
1139
1140class PrefetchToXeVMPattern : public OpConversionPattern<xegpu::PrefetchOp> {
1141 using Base::Base;
1142 LogicalResult
1143 matchAndRewrite(xegpu::PrefetchOp op, xegpu::PrefetchOp::Adaptor adaptor,
1144 ConversionPatternRewriter &rewriter) const override {
1145 auto loc = op.getLoc();
1146 auto ctxt = rewriter.getContext();
1147 Value basePtrI64 = adaptor.getSource();
1148 // Base pointer is passed as i32 or i64 by adaptor, cast to i64 if needed.
1149 if (basePtrI64.getType() != rewriter.getI64Type())
1150 basePtrI64 = arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(),
1151 basePtrI64);
1152 Value offsets = adaptor.getOffsets();
1153 if (offsets) {
1154 VectorType offsetsVecTy = dyn_cast<VectorType>(offsets.getType());
1155 if (offsetsVecTy) {
1156 // Offset needs be scalar.
1157 return rewriter.notifyMatchFailure(op,
1158 "Expected offsets to be a scalar.");
1159 } else {
1160 int64_t elemBitWidth{0};
1161 int64_t elemByteSize;
1162 // Element byte size can come from two sources:
1163 if (auto memRefTy = dyn_cast<MemRefType>(op.getSourceType())) {
1164 // If memref is available, we use its element type to
1165 // determine element byte size.
1166 elemBitWidth = memRefTy.getElementType().getIntOrFloatBitWidth();
1167 } else {
1168 // Otherwise, we use the provided offset byte alignment.
1169 elemByteSize = *op.getOffsetAlignByte();
1170 }
1171 if (elemBitWidth != 0) {
1172 if (elemBitWidth % 8 != 0)
1173 return rewriter.notifyMatchFailure(
1174 op, "Expected element type bit width to be multiple of 8.");
1175 elemByteSize = elemBitWidth / 8;
1176 }
1177 basePtrI64 = addOffsetToBaseAddr(rewriter, loc, basePtrI64, offsets,
1178 elemByteSize);
1179 }
1180 }
1181 // Default memory space is global.
1182 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
1183 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::Global));
1184 // If source is a memref, we use its memory space.
1185 if (auto memRefTy = dyn_cast<MemRefType>(op.getSource().getType())) {
1186 FailureOr<unsigned> addrSpace =
1187 getNumericMemorySpace(memRefTy.getMemorySpace());
1188 if (failed(addrSpace))
1189 return rewriter.notifyMatchFailure(
1190 op, "Unsupported memref memory space attribute.");
1191 if (*addrSpace != 0)
1192 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
1193 }
1194 // Convert base pointer (i64) to LLVM pointer type.
1195 Value ptrLLVM =
1196 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
1197 // Create the prefetch op with cache control attribute.
1198 xevm::PrefetchOp::create(
1199 rewriter, loc, ptrLLVM,
1200 xevm::LoadCacheControlAttr::get(
1201 ctxt, translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint())));
1202 rewriter.eraseOp(op);
1203 return success();
1204 }
1205};
1206
1207class FenceToXeVMPattern : public OpConversionPattern<xegpu::FenceOp> {
1208 using Base::Base;
1209 LogicalResult
1210 matchAndRewrite(xegpu::FenceOp op, xegpu::FenceOp::Adaptor adaptor,
1211 ConversionPatternRewriter &rewriter) const override {
1212 auto loc = op.getLoc();
1213 xevm::MemScope memScope{xevm::MemScope::WORKGROUP};
1214 switch (op.getFenceScope()) {
1215 case xegpu::FenceScope::Workgroup:
1216 memScope = xevm::MemScope::WORKGROUP;
1217 break;
1218 case xegpu::FenceScope::GPU:
1219 memScope = xevm::MemScope::DEVICE;
1220 break;
1221 }
1222 xevm::AddrSpace addrSpace{xevm::AddrSpace::GLOBAL};
1223 switch (op.getMemoryKind()) {
1224 case xegpu::MemorySpace::Global:
1225 addrSpace = xevm::AddrSpace::GLOBAL;
1226 break;
1227 case xegpu::MemorySpace::SLM:
1228 addrSpace = xevm::AddrSpace::SHARED;
1229 break;
1230 }
1231 xevm::MemfenceOp::create(rewriter, loc, memScope, addrSpace);
1232 rewriter.eraseOp(op);
1233 return success();
1234 }
1235};
1236
1237static auto encodePrecision = [](Type type) -> xevm::ElemType {
1238 if (type.isBF16())
1239 return xevm::ElemType::BF16;
1240 else if (type.isF16())
1241 return xevm::ElemType::F16;
1242 else if (type.isTF32())
1243 return xevm::ElemType::TF32;
1244 else if (type.isInteger(8)) {
1245 if (type.isUnsignedInteger())
1246 return xevm::ElemType::U8;
1247 return xevm::ElemType::S8;
1248 } else if (type.isF32())
1249 return xevm::ElemType::F32;
1250 else if (type.isInteger(32))
1251 return xevm::ElemType::S32;
1252 else if (type.isF8E5M2())
1253 return xevm::ElemType::BF8;
1254 else if (type.isF8E4M3FN())
1255 return xevm::ElemType::F8;
1256 else if (mlir::isa<Float4E2M1FNType>(type))
1257 return xevm::ElemType::E2M1;
1258 llvm_unreachable("add more support for ElemType");
1259};
1260
1261static unsigned getNumOperandsPerDword(xevm::ElemType pTy) {
1262 switch (pTy) {
1263 case xevm::ElemType::TF32:
1264 return 1;
1265 case xevm::ElemType::BF16:
1266 case xevm::ElemType::F16:
1267 return 2;
1268 case xevm::ElemType::U8:
1269 case xevm::ElemType::S8:
1270 case xevm::ElemType::F8:
1271 case xevm::ElemType::BF8:
1272 return 4;
1273 case xevm::ElemType::E2M1:
1274 return 8;
1275 default:
1276 llvm_unreachable("unsupported xevm::ElemType");
1277 }
1278}
1279
1280class DpasToXeVMPattern : public OpConversionPattern<xegpu::DpasOp> {
1281 using Base::Base;
1282 LogicalResult
1283 matchAndRewrite(xegpu::DpasOp op, xegpu::DpasOp::Adaptor adaptor,
1284 ConversionPatternRewriter &rewriter) const override {
1285 auto loc = op.getLoc();
1286 auto ctxt = rewriter.getContext();
1287 auto aTy = cast<VectorType>(op.getLhs().getType());
1288 auto bTy = cast<VectorType>(op.getRhs().getType());
1289 auto resultType = cast<VectorType>(op.getResultType());
1290
1291 // get the correct dpasInst by getting info from chip
1292 auto chipStr = xegpu::getChipStr(op);
1293 if (!chipStr)
1294 return rewriter.notifyMatchFailure(op, "cannot determine target chip");
1295
1296 const auto *uArch = mlir::xegpu::uArch::getUArch(*chipStr);
1297 if (!uArch)
1298 return rewriter.notifyMatchFailure(op, "unsupported target uArch");
1299
1300 auto *dpasInst = const_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc *>(
1301 llvm::dyn_cast_or_null<xegpu::uArch::SubgroupMatrixMultiplyAcc>(
1302 uArch->getInstruction(
1303 xegpu::uArch::InstructionKind::SubgroupMatrixMultiplyAcc)));
1304 if (!dpasInst)
1305 return rewriter.notifyMatchFailure(op,
1306 "DPAS not supported by target uArch");
1307
1308 auto checkSupportedTypes = [&](VectorType vecTy,
1309 xegpu::uArch::MMAOpndKind kind) -> bool {
1310 auto supported = dpasInst->getSupportedTypes(*ctxt, kind);
1311 return llvm::find(supported, vecTy.getElementType()) != supported.end();
1312 };
1313
1314 if (!checkSupportedTypes(aTy, xegpu::uArch::MMAOpndKind::MatrixA))
1315 return rewriter.notifyMatchFailure(
1316 op, "A-matrix element type not supported by target uArch");
1317 if (!checkSupportedTypes(bTy, xegpu::uArch::MMAOpndKind::MatrixB))
1318 return rewriter.notifyMatchFailure(
1319 op, "B-matrix element type not supported by target uArch");
1320 // NOTE: Supported types for MatrixC and MatrixD are identical
1321 if (!checkSupportedTypes(resultType, xegpu::uArch::MMAOpndKind::MatrixD))
1322 return rewriter.notifyMatchFailure(
1323 op, "result/accumulator element type not supported by target uArch");
1324
1325 xevm::ElemType precATy = encodePrecision(aTy.getElementType());
1326 xevm::ElemType precBTy = encodePrecision(bTy.getElementType());
1327 Value c = op.getAcc();
1328 if (!c) {
1329 auto elementTy = resultType.getElementType();
1330 Attribute initValueAttr;
1331 if (isa<FloatType>(elementTy))
1332 initValueAttr = FloatAttr::get(elementTy, 0.0);
1333 else
1334 initValueAttr = IntegerAttr::get(elementTy, 0);
1335 c = arith::ConstantOp::create(
1336 rewriter, loc, DenseElementsAttr::get(resultType, initValueAttr));
1337 }
1338
1339 Value aVec = op.getLhs();
1340 Value bVec = op.getRhs();
1341 auto cvecty = cast<VectorType>(c.getType());
1342 xevm::ElemType precCTy = encodePrecision(cvecty.getElementType());
1343 xevm::ElemType precDTy = encodePrecision(resultType.getElementType());
1344 VectorType cNty =
1345 VectorType::get(cvecty.getNumElements(), cvecty.getElementType());
1346 if (cvecty != cNty)
1347 c = vector::ShapeCastOp::create(rewriter, loc, cNty, c);
1348 Value dpasRes = xevm::MMAOp::create(
1349 rewriter, loc, cNty, aVec, bVec, c,
1350 xevm::MMAShapeAttr::get(ctxt, cvecty.getNumElements(), executionSize,
1351 systolicDepth *
1352 getNumOperandsPerDword(precATy)),
1353 xevm::MMATypesAttr::get(ctxt, precDTy, precATy, precBTy, precCTy));
1354 if (cvecty != cNty)
1355 dpasRes = vector::ShapeCastOp::create(rewriter, loc, resultType, dpasRes);
1356 rewriter.replaceOp(op, dpasRes);
1357 return success();
1358 }
1359};
1360
1361static std::optional<LLVM::AtomicBinOp>
1362matchSimpleAtomicOp(arith::AtomicRMWKind arithKind) {
1363 switch (arithKind) {
1364 case arith::AtomicRMWKind::addf:
1365 return LLVM::AtomicBinOp::fadd;
1366 case arith::AtomicRMWKind::addi:
1367 return LLVM::AtomicBinOp::add;
1368 case arith::AtomicRMWKind::assign:
1369 return LLVM::AtomicBinOp::xchg;
1370 case arith::AtomicRMWKind::maximumf:
1371 return LLVM::AtomicBinOp::fmax;
1372 case arith::AtomicRMWKind::maxs:
1373 return LLVM::AtomicBinOp::max;
1374 case arith::AtomicRMWKind::maxu:
1375 return LLVM::AtomicBinOp::umax;
1376 case arith::AtomicRMWKind::minimumf:
1377 return LLVM::AtomicBinOp::fmin;
1378 case arith::AtomicRMWKind::mins:
1379 return LLVM::AtomicBinOp::min;
1380 case arith::AtomicRMWKind::minu:
1381 return LLVM::AtomicBinOp::umin;
1382 case arith::AtomicRMWKind::ori:
1383 return LLVM::AtomicBinOp::_or;
1384 case arith::AtomicRMWKind::andi:
1385 return LLVM::AtomicBinOp::_and;
1386 default:
1387 return std::nullopt;
1388 }
1389}
1390
1391class AtomicRMWToXeVMPattern : public OpConversionPattern<xegpu::AtomicRMWOp> {
1392 using Base::Base;
1393 LogicalResult
1394 matchAndRewrite(xegpu::AtomicRMWOp op, xegpu::AtomicRMWOp::Adaptor adaptor,
1395 ConversionPatternRewriter &rewriter) const override {
1396 auto loc = op.getLoc();
1397 auto ctxt = rewriter.getContext();
1398 auto tdesc = op.getTensorDesc().getType();
1399 auto ptrTypeLLVM = LLVM::LLVMPointerType::get(
1400 ctxt, getNumericXeVMAddrSpace(tdesc.getMemorySpace()));
1401 Value basePtrI64 = arith::IndexCastOp::create(
1402 rewriter, loc, rewriter.getI64Type(), adaptor.getTensorDesc());
1403 Value basePtrLLVM =
1404 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
1405 VectorType srcOrDstVecTy = cast<VectorType>(op.getValue().getType());
1406 VectorType srcOrDstFlatVecTy = VectorType::get(
1407 srcOrDstVecTy.getNumElements(), srcOrDstVecTy.getElementType());
1408 Value srcFlatVec = vector::ShapeCastOp::create(
1409 rewriter, loc, srcOrDstFlatVecTy, op.getValue());
1410 auto atomicKind = matchSimpleAtomicOp(op.getKind());
1411 assert(atomicKind.has_value());
1412 Value resVec = srcFlatVec;
1413 for (int i = 0; i < srcOrDstVecTy.getNumElements(); i++) {
1414 auto val = vector::ExtractOp::create(rewriter, loc, resVec, i);
1415 Value idx = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(),
1416 rewriter.getI64IntegerAttr(i));
1417 Value currPtr =
1418 LLVM::GEPOp::create(rewriter, loc, ptrTypeLLVM,
1419 srcOrDstVecTy.getElementType(), basePtrLLVM, idx);
1420 Value newVal =
1421 LLVM::AtomicRMWOp::create(rewriter, loc, atomicKind.value(), currPtr,
1422 val, LLVM::AtomicOrdering::seq_cst);
1423 resVec = vector::InsertOp::create(rewriter, loc, newVal, resVec, i);
1424 }
1425 rewriter.replaceOp(op, resVec);
1426 return success();
1427 }
1428};
1429
1430class DpasMxToXeVMPattern : public OpConversionPattern<xegpu::DpasMxOp> {
1431 using Base::Base;
1432 LogicalResult
1433 matchAndRewrite(xegpu::DpasMxOp op, xegpu::DpasMxOp::Adaptor adaptor,
1434 ConversionPatternRewriter &rewriter) const override {
1435 auto loc = op.getLoc();
1436 auto ctxt = rewriter.getContext();
1437 auto aTy = op.getA().getType();
1438 auto bTy = op.getB().getType();
1439 auto resVecTy =
1440 cast<VectorType>(getTypeConverter()->convertType(op.getType()));
1441
1442 auto chipStr = xegpu::getChipStr(op);
1443 if (!chipStr)
1444 return rewriter.notifyMatchFailure(op, "cannot determine target chip");
1445
1446 const auto *uArch = xegpu::uArch::getUArch(*chipStr);
1447 if (!uArch)
1448 return rewriter.notifyMatchFailure(op, "unsupported target uArch");
1449
1450 // TODO: Add supported shape check
1451
1452 xevm::ElemType precATy = encodePrecision(aTy.getElementType());
1453 xevm::ElemType precBTy = encodePrecision(bTy.getElementType());
1454 Value c = adaptor.getAcc();
1455 if (!c) {
1456 auto elementTy = resVecTy.getElementType();
1457 Attribute initValueAttr;
1458 if (isa<FloatType>(elementTy))
1459 initValueAttr = FloatAttr::get(elementTy, 0.0);
1460 else
1461 initValueAttr = IntegerAttr::get(elementTy, 0);
1462 c = arith::ConstantOp::create(
1463 rewriter, loc, DenseElementsAttr::get(resVecTy, initValueAttr));
1464 }
1465
1466 Value aVec = adaptor.getA();
1467 Value bVec = adaptor.getB();
1468 auto aVecTy = cast<VectorType>(aVec.getType());
1469 auto bVecTy = cast<VectorType>(bVec.getType());
1470 if (aVecTy.getElementTypeBitWidth() == 4)
1471 aVec = vector::BitCastOp::create(
1472 rewriter, loc,
1473 VectorType::get(aVecTy.getNumElements() / 2, rewriter.getI8Type()),
1474 aVec);
1475 if (bVecTy.getElementTypeBitWidth() == 4)
1476 bVec = vector::BitCastOp::create(
1477 rewriter, loc,
1478 VectorType::get(bVecTy.getNumElements() / 2, rewriter.getI8Type()),
1479 bVec);
1480 auto cVecTy = cast<VectorType>(c.getType());
1481 xevm::ElemType precCTy = encodePrecision(cVecTy.getElementType());
1482 xevm::ElemType precDTy = encodePrecision(resVecTy.getElementType());
1483 Value scaleA = adaptor.getScaleA();
1484 Value scaleB = adaptor.getScaleB();
1485 Value dpasMxRes = xevm::MMAMxOp::create(
1486 rewriter, loc, resVecTy, aVec, bVec, scaleA, scaleB, c,
1487 xevm::MMAShapeAttr::get(ctxt, cVecTy.getNumElements(), executionSize,
1488 systolicDepth *
1489 getNumOperandsPerDword(precATy)),
1490 xevm::MMATypesAttr::get(ctxt, precDTy, precATy, precBTy, precCTy));
1491 rewriter.replaceOp(op, dpasMxRes);
1492 return success();
1493 }
1494};
1495
1496//===----------------------------------------------------------------------===//
1497// arith.extf / arith.truncf to xevm.extf / xevm.truncf
1498//===----------------------------------------------------------------------===//
1499//
1500// Micro-scaling (MX) GEMM lowering breaks arith.scaling_extf/scaling_truncf
1501// into plain arith.extf/arith.truncf whose narrow side uses one of the MX float
1502// formats (f8E5M2, f8E4M3FN or f4E2M1FN). These narrow floats have no native
1503// LLVM support, so the conversions are mapped onto the dedicated xevm.extf /
1504// xevm.truncf ops which lower to hardware builtins. The f8E8M0FNU scale type is
1505// intentionally not handled here: it is expanded into integer arithmetic by
1506// arith-expand before this pass runs.
1507
1508// xevm.extf / xevm.truncf only convert between the MX narrow floats and
1509// f16/bf16, and the underlying builtins operate on exactly 16 f16/bf16 values.
1510static constexpr int64_t kXeVMExtfTruncfNumElems = 16;
1511
1512// Maps a narrow MX float element type to the matching xevm.extf source enum.
1513static std::optional<xevm::ExtfSrcElemTypes> getExtfNarrowType(Type etype) {
1514 if (isa<Float8E5M2Type>(etype))
1515 return xevm::ExtfSrcElemTypes::BF8;
1516 if (isa<Float8E4M3FNType>(etype))
1517 return xevm::ExtfSrcElemTypes::F8;
1518 if (isa<Float4E2M1FNType>(etype))
1519 return xevm::ExtfSrcElemTypes::E2M1;
1520 return std::nullopt;
1521}
1522
1523// Maps a narrow MX float element type to the matching xevm.truncf dest enum.
1524static std::optional<xevm::TruncfDstElemTypes> getTruncfNarrowType(Type etype) {
1525 if (isa<Float8E5M2Type>(etype))
1526 return xevm::TruncfDstElemTypes::BF8;
1527 if (isa<Float8E4M3FNType>(etype))
1528 return xevm::TruncfDstElemTypes::F8;
1529 if (isa<Float4E2M1FNType>(etype))
1530 return xevm::TruncfDstElemTypes::E2M1;
1531 return std::nullopt;
1532}
1533
1534// Returns true if `op` is an arith.extf that can be lowered to xevm.extf, i.e.
1535// a rank-1 widening from an MX narrow float to a 16-element f16/bf16 vector.
1536static bool isXeVMExtf(arith::ExtFOp op) {
1537 auto srcTy = dyn_cast<VectorType>(op.getIn().getType());
1538 auto dstTy = dyn_cast<VectorType>(op.getType());
1539 if (!srcTy || !dstTy || srcTy.getRank() != 1 || dstTy.getRank() != 1)
1540 return false;
1541 if (dstTy.getNumElements() != kXeVMExtfTruncfNumElems)
1542 return false;
1543 Type dstETy = dstTy.getElementType();
1544 if (!dstETy.isF16() && !dstETy.isBF16())
1545 return false;
1546 return getExtfNarrowType(srcTy.getElementType()).has_value();
1547}
1548
1549// Returns true if `op` is an arith.truncf that can be lowered to xevm.truncf,
1550// i.e. a rank-1 truncation from an f16/bf16 vector to an MX narrow float. The
1551// source has to hold a whole number of the fixed-size groups xevm.truncf
1552// converts at a time; wider vectors are converted in several steps.
1553static bool isXeVMTruncf(arith::TruncFOp op) {
1554 auto srcTy = dyn_cast<VectorType>(op.getIn().getType());
1555 auto dstTy = dyn_cast<VectorType>(op.getType());
1556 if (!srcTy || !dstTy || srcTy.getRank() != 1 || dstTy.getRank() != 1)
1557 return false;
1558 int64_t numElems = srcTy.getNumElements();
1559 if (numElems == 0 || numElems % kXeVMExtfTruncfNumElems != 0)
1560 return false;
1561 Type srcETy = srcTy.getElementType();
1562 if (!srcETy.isF16() && !srcETy.isBF16())
1563 return false;
1564 return getTruncfNarrowType(dstTy.getElementType()).has_value();
1565}
1566
1567class ExtfToXeVMPattern : public OpConversionPattern<arith::ExtFOp> {
1568 using Base::Base;
1569 LogicalResult
1570 matchAndRewrite(arith::ExtFOp op, OpAdaptor adaptor,
1571 ConversionPatternRewriter &rewriter) const override {
1572 if (!isXeVMExtf(op))
1573 return rewriter.notifyMatchFailure(op, "not a xevm.extf compatible extf");
1574 Location loc = op.getLoc();
1575 MLIRContext *ctx = op.getContext();
1576 auto srcVecTy = cast<VectorType>(op.getIn().getType());
1577 auto dstVecTy = cast<VectorType>(op.getType());
1578 xevm::ExtfSrcElemTypes srcEnum =
1579 *getExtfNarrowType(srcVecTy.getElementType());
1580 xevm::ExtfDstElemTypes dstEnum = dstVecTy.getElementType().isF16()
1581 ? xevm::ExtfDstElemTypes::F16
1582 : xevm::ExtfDstElemTypes::BF16;
1583 // The narrow float operand has already been type-converted to an integer
1584 // vector of the same bit width (i4 for fp4, i8 for fp8). xevm.extf takes
1585 // the values packed into an i8 vector, so re-pack fp4 (i4) operands.
1586 Value src = adaptor.getIn();
1587 auto convSrcTy = cast<VectorType>(src.getType());
1588 if (convSrcTy.getElementTypeBitWidth() == 4)
1589 src = vector::BitCastOp::create(
1590 rewriter, loc,
1591 VectorType::get(convSrcTy.getNumElements() / 2, rewriter.getI8Type()),
1592 src);
1593 Type resTy = getTypeConverter()->convertType(dstVecTy);
1594 Value res = xevm::ExtfOp::create(
1595 rewriter, loc, resTy, src, xevm::ExtfSrcElemTypeAttr::get(ctx, srcEnum),
1596 xevm::ExtfDstElemTypeAttr::get(ctx, dstEnum));
1597 rewriter.replaceOp(op, res);
1598 return success();
1599 }
1600};
1601
1602class TruncfToXeVMPattern : public OpConversionPattern<arith::TruncFOp> {
1603 using Base::Base;
1604 LogicalResult
1605 matchAndRewrite(arith::TruncFOp op, OpAdaptor adaptor,
1606 ConversionPatternRewriter &rewriter) const override {
1607 if (!isXeVMTruncf(op))
1608 return rewriter.notifyMatchFailure(op,
1609 "not a xevm.truncf compatible truncf");
1610 Location loc = op.getLoc();
1611 MLIRContext *ctx = op.getContext();
1612 auto srcVecTy = cast<VectorType>(op.getIn().getType());
1613 auto dstVecTy = cast<VectorType>(op.getType());
1614 xevm::TruncfSrcElemTypes srcEnum = srcVecTy.getElementType().isF16()
1615 ? xevm::TruncfSrcElemTypes::F16
1616 : xevm::TruncfSrcElemTypes::BF16;
1617 xevm::TruncfDstElemTypes dstEnum =
1618 *getTruncfNarrowType(dstVecTy.getElementType());
1619 auto srcEnumAttr = xevm::TruncfSrcElemTypeAttr::get(ctx, srcEnum);
1620 auto dstEnumAttr = xevm::TruncfDstElemTypeAttr::get(ctx, dstEnum);
1621
1622 // xevm.truncf lowers to instructions that convert a fixed number of
1623 // elements at a time, so a wider source is converted one group at a time
1624 // and the packed results are concatenated. Each group produces the narrow
1625 // floats packed into an i8 vector.
1626 int64_t numGroups = srcVecTy.getNumElements() / kXeVMExtfTruncfNumElems;
1627 int64_t groupBytes =
1628 kXeVMExtfTruncfNumElems * dstVecTy.getElementTypeBitWidth() / 8;
1629 Type groupTy = VectorType::get(groupBytes, rewriter.getI8Type());
1630
1631 Value src = adaptor.getIn();
1632 Value packed;
1633 if (numGroups == 1) {
1634 packed = xevm::TruncfOp::create(rewriter, loc, groupTy, src, srcEnumAttr,
1635 dstEnumAttr);
1636 } else {
1637 auto packedTy =
1638 VectorType::get(groupBytes * numGroups, rewriter.getI8Type());
1639 packed = arith::ConstantOp::create(rewriter, loc, packedTy,
1640 rewriter.getZeroAttr(packedTy));
1641 for (int64_t group = 0; group < numGroups; group++) {
1642 Value slice = vector::ExtractStridedSliceOp::create(
1643 rewriter, loc, src, group * kXeVMExtfTruncfNumElems,
1644 kXeVMExtfTruncfNumElems, /*strides=*/1);
1645 Value converted = xevm::TruncfOp::create(rewriter, loc, groupTy, slice,
1646 srcEnumAttr, dstEnumAttr);
1647 packed = vector::InsertStridedSliceOp::create(
1648 rewriter, loc, converted, packed, group * groupBytes,
1649 /*strides=*/1);
1650 }
1651 }
1652 // Re-shape to the type-converted result type (i4 vector for fp4).
1653 Type resTy = getTypeConverter()->convertType(dstVecTy);
1654 if (packed.getType() != resTy)
1655 packed = vector::BitCastOp::create(rewriter, loc, resTy, packed);
1656 rewriter.replaceOp(op, packed);
1657 return success();
1658 }
1659};
1660
1661// Lowers `xegpu.lane_shuffle` to `xevm.bitcast_shuffle`.
1662//
1663// `xevm.bitcast_shuffle` concatenates the components of its operand across the
1664// subgroup, the first component of every lane first, and then hands chunks the
1665// size of a result component back out to the lanes in order. Numbering the
1666// elements of a `vector<NxT>` fragment held by lane `i` of a subgroup of size
1667// `S` by their logical position, that concatenation is exactly the `pack` mode
1668// input numbering `j * S + i`. Taking the result as a single `N * width(T)` bit
1669// scalar then hands lane `i` the logical positions `i * N .. i * N + N - 1`,
1670// which is the `pack` mode output numbering.
1671//
1672// So `pack` is a vector-to-scalar `xevm.bitcast_shuffle` followed by a bitcast
1673// back to the fragment type, and `unpack`, being its inverse, is a bitcast to
1674// the scalar followed by a scalar-to-vector `xevm.bitcast_shuffle`.
1675//
1676// `xevm.bitcast_shuffle` only accepts the integer types `i8`, `i16`, `i32` and
1677// `i64`, since it is bit-preserving and so does not depend on how the bits are
1678// interpreted. A fragment of a floating point type is therefore bitcast to a
1679// same-width integer vector on the way in and back on the way out.
1680class LaneShuffleToXeVMPattern
1681 : public OpConversionPattern<xegpu::LaneShuffleOp> {
1682 using Base::Base;
1683 LogicalResult
1684 matchAndRewrite(xegpu::LaneShuffleOp op, OpAdaptor adaptor,
1685 ConversionPatternRewriter &rewriter) const override {
1686 auto vecTy = dyn_cast<VectorType>(adaptor.getSource().getType());
1687 if (!vecTy)
1688 return rewriter.notifyMatchFailure(op, "Expected a vector fragment.");
1689 // The shuffle redistributes whole bytes between the lanes, so sub-byte
1690 // element types, fp4 in particular, cannot be shuffled. Widths without a
1691 // matching integer type the op accepts are rejected for the same reason.
1692 unsigned elemBits = vecTy.getElementTypeBitWidth();
1693 if (elemBits != 8 && elemBits != 16 && elemBits != 32 && elemBits != 64)
1694 return rewriter.notifyMatchFailure(
1695 op, "Expected an element type of 8, 16, 32 or 64 bits.");
1696 int64_t fragmentBits = vecTy.getNumElements() * elemBits;
1697 if (fragmentBits > 64 || !llvm::isPowerOf2_64(fragmentBits))
1698 return rewriter.notifyMatchFailure(
1699 op, "Expected a fragment of 8, 16, 32 or 64 bits.");
1700
1701 Location loc = op.getLoc();
1702 Type packedTy = rewriter.getIntegerType(fragmentBits);
1703 // The integer vector type the shuffle actually operates on. Equal to the
1704 // fragment type when that is already an integer vector.
1705 VectorType shuffleTy =
1706 VectorType::get(vecTy.getShape(), rewriter.getIntegerType(elemBits));
1707
1708 Value res;
1709 if (op.getMode() == xegpu::LaneShuffleMode::Pack) {
1710 Value src = adaptor.getSource();
1711 if (shuffleTy != vecTy)
1712 src = LLVM::BitcastOp::create(rewriter, loc, shuffleTy, src);
1713 res = xevm::BitcastShuffleOp::create(rewriter, loc, packedTy, src);
1714 res = LLVM::BitcastOp::create(rewriter, loc, vecTy, res);
1715 } else {
1716 Value packed =
1717 LLVM::BitcastOp::create(rewriter, loc, packedTy, adaptor.getSource());
1718 res = xevm::BitcastShuffleOp::create(rewriter, loc, shuffleTy, packed);
1719 if (shuffleTy != vecTy)
1720 res = LLVM::BitcastOp::create(rewriter, loc, vecTy, res);
1721 }
1722 rewriter.replaceOp(op, res);
1723 return success();
1724 }
1725};
1726
1727//===----------------------------------------------------------------------===//
1728// Pass Definition
1729//===----------------------------------------------------------------------===//
1730
1731struct ConvertXeGPUToXeVMPass
1732 : public impl::ConvertXeGPUToXeVMPassBase<ConvertXeGPUToXeVMPass> {
1733 using Base::Base;
1734
1735 void runOnOperation() override {
1736 MLIRContext *context = &getContext();
1737
1738 // XeVM type converter is based on LLVM type converter with the
1739 // following customizations.
1740 // First, type conversion rules are added for xegpu custom types,
1741 // TensorDescType and MemDescType.
1742 // Second, MemRefType is lowered to single integer type
1743 // Third, VectorType of single element or 0D is converted to vector
1744 // element type. Otherwise, vector type is flatten to 1D.
1745 LowerToLLVMOptions options(context);
1746 options.overrideIndexBitwidth(this->use64bitIndex ? 64 : 32);
1747 LLVMTypeConverter typeConverter(context, options);
1748
1749 Type xevmIndexType = typeConverter.convertType(IndexType::get(context));
1750 Type i32Type = IntegerType::get(context, 32);
1751 typeConverter.addConversion([&](VectorType type) -> Type {
1752 auto elemType = typeConverter.convertType(type.getElementType());
1753 // If the vector rank is 0 or has a single element, return the element
1754 unsigned rank = type.getRank();
1755 if (rank == 0 || type.getNumElements() == 1)
1756 return elemType;
1757 // Otherwise, convert the vector to a flat vector type.
1758 int64_t sum = llvm::product_of(type.getShape());
1759 return VectorType::get(sum, elemType);
1760 });
1761 typeConverter.addConversion([&](xegpu::TensorDescType type) -> Type {
1762 if (type.getRank() == 1)
1763 return xevmIndexType;
1764 return VectorType::get(8, i32Type);
1765 });
1766 // SLM access related type conversions.
1767 // TODO: LLVM DLTI provides clean way of representing different pointer size
1768 // based on address space. Currently pointer size of SLM access is hard
1769 // coded to 32bit. Update to use DLTI when switching overall XeGPU lowering
1770 // to use DLTI instead of use64bitIndex option used above.
1771
1772 // Convert MemDescType into i32 for SLM
1773 typeConverter.addConversion(
1774 [&](xegpu::MemDescType type) -> Type { return i32Type; });
1775
1776 typeConverter.addConversion([&](MemRefType type) -> Type {
1777 return isSharedMemRef(type) ? i32Type : xevmIndexType;
1778 });
1779
1780 // LLVM type converter puts unrealized casts for the following cases:
1781 // add materialization casts to handle them.
1782
1783 // Materialization to convert memref to i64 or i32 depending on global/SLM
1784 // Applies only to target materialization.
1785 // Note: int type to memref materialization is not required as xegpu ops
1786 // currently do not produce memrefs as result.
1787 auto memrefToIntMaterializationCast = [](OpBuilder &builder, Type type,
1788 ValueRange inputs,
1789 Location loc) -> Value {
1790 if (inputs.size() != 1)
1791 return {};
1792 auto input = inputs.front();
1793 if (auto memrefTy = dyn_cast<MemRefType>(input.getType())) {
1794 unsigned rank = memrefTy.getRank();
1795 Type indexType = builder.getIndexType();
1796
1797 int64_t intOffsets;
1798 SmallVector<int64_t> intStrides;
1799 Value addr;
1800 Value offset;
1801 if (succeeded(memrefTy.getStridesAndOffset(intStrides, intOffsets)) &&
1802 ShapedType::isStatic(intOffsets)) {
1803 addr = memref::ExtractAlignedPointerAsIndexOp::create(builder, loc,
1804 input);
1805 offset = arith::ConstantOp::create(builder, loc,
1806 builder.getIndexAttr(intOffsets));
1807 } else {
1808
1809 // Result types: [base_memref, offset, stride0, stride1, ...,
1810 // strideN-1, size0, size1, ..., sizeN-1]
1811 SmallVector<Type> resultTypes{
1812 MemRefType::get({}, memrefTy.getElementType(),
1813 MemRefLayoutAttrInterface(),
1814 memrefTy.getMemorySpace()),
1815 indexType};
1816 // strides + sizes
1817 resultTypes.append(2 * rank, indexType);
1818
1819 auto meta = memref::ExtractStridedMetadataOp::create(
1820 builder, loc, resultTypes, input);
1821
1822 addr = memref::ExtractAlignedPointerAsIndexOp::create(
1823 builder, loc, meta.getBaseBuffer());
1824 offset = meta.getOffset();
1825 }
1826
1827 auto addrCasted =
1828 arith::IndexCastUIOp::create(builder, loc, type, addr);
1829 auto offsetCasted =
1830 arith::IndexCastUIOp::create(builder, loc, type, offset);
1831
1832 // Compute the final address: base address + byte offset
1833 auto byteSize = arith::ConstantOp::create(
1834 builder, loc, type,
1835 builder.getIntegerAttr(type,
1836 memrefTy.getElementTypeBitWidth() / 8));
1837 auto byteOffset =
1838 arith::MulIOp::create(builder, loc, offsetCasted, byteSize);
1839 auto addrWithOffset =
1840 arith::AddIOp::create(builder, loc, addrCasted, byteOffset);
1841
1842 return addrWithOffset.getResult();
1843 }
1844 return {};
1845 };
1846
1847 // Materialization to convert ui64 to i64
1848 // Applies only to target materialization.
1849 // Note: i64 to ui64 materialization is not required as xegpu ops
1850 // currently do not produce ui64 as result.
1851 auto ui64ToI64MaterializationCast = [](OpBuilder &builder, Type type,
1852 ValueRange inputs,
1853 Location loc) -> Value {
1854 if (inputs.size() != 1)
1855 return {};
1856 auto input = inputs.front();
1857 if (input.getType() == builder.getIntegerType(64, false)) {
1858 Value cast =
1859 index::CastUOp::create(builder, loc, builder.getIndexType(), input)
1860 .getResult();
1861 return arith::IndexCastUIOp::create(builder, loc, type, cast)
1862 .getResult();
1863 }
1864 return {};
1865 };
1866
1867 // Materialization to convert ui32 to i32
1868 // Applies only to target materialization.
1869 // Note: i32 to ui32 materialization is not required as xegpu ops
1870 // currently do not produce ui32 as result.
1871 auto ui32ToI32MaterializationCast = [](OpBuilder &builder, Type type,
1872 ValueRange inputs,
1873 Location loc) -> Value {
1874 if (inputs.size() != 1)
1875 return {};
1876 auto input = inputs.front();
1877 if (input.getType() == builder.getIntegerType(32, false)) {
1878 Value cast =
1879 index::CastUOp::create(builder, loc, builder.getIndexType(), input)
1880 .getResult();
1881 return arith::IndexCastUIOp::create(builder, loc, type, cast)
1882 .getResult();
1883 }
1884 return {};
1885 };
1886
1887 // Materialization to convert between vector types
1888 // - Add shape cast for different shapes
1889 // - Add bitcast for different element types
1890 // Applies to both source and target materialization.
1891 auto vectorToVectorMaterializationCast = [](OpBuilder &builder, Type type,
1892 ValueRange inputs,
1893 Location loc) -> Value {
1894 if (inputs.size() != 1)
1895 return {};
1896 auto input = inputs.front();
1897 if (auto vecTy = dyn_cast<VectorType>(input.getType())) {
1898 if (auto targetVecTy = dyn_cast<VectorType>(type)) {
1899 Value cast = input;
1900 // If the target type has a different shape, add a shape cast
1901 // If the target type has a different element type, add a bitcast
1902 if (targetVecTy.getShape() != vecTy.getShape()) {
1903 cast = vector::ShapeCastOp::create(
1904 builder, loc,
1905 VectorType::get(targetVecTy.getShape(),
1906 vecTy.getElementType()),
1907 cast)
1908 .getResult();
1909 }
1910 if (targetVecTy.getElementType() != vecTy.getElementType()) {
1911 cast = vector::BitCastOp::create(builder, loc, targetVecTy, cast)
1912 .getResult();
1913 }
1914 return cast;
1915 }
1916 }
1917 return {};
1918 };
1919
1920 // Materialization to convert
1921 // - single element vector to single element of vector element type
1922 // Applies only to target materialization.
1923 auto vectorToSingleElementMaterializationCast =
1924 [](OpBuilder &builder, Type type, ValueRange inputs,
1925 Location loc) -> Value {
1926 if (inputs.size() != 1)
1927 return {};
1928 auto input = inputs.front();
1929 if (auto vecTy = dyn_cast<VectorType>(input.getType())) {
1930 // Source needs to be single element vector
1931 auto rank = vecTy.getRank();
1932 if (rank != 0 && vecTy.getNumElements() != 1)
1933 return {};
1934 auto inElemTy = vecTy.getElementType();
1935 // extract scalar
1936 Value cast = input;
1937 if (rank == 0) {
1938 cast = vector::ExtractOp::create(builder, loc, cast, {}).getResult();
1939 } else {
1940 cast = vector::ExtractOp::create(builder, loc, cast,
1941 SmallVector<int64_t>(rank, 0))
1942 .getResult();
1943 }
1944 // Extracted element type may need conversion
1945 // Two cases
1946 // 1. Index type to integer type
1947 // 2. Other element type mismatch
1948 if (inElemTy.isIndex()) {
1949 cast = arith::IndexCastUIOp::create(builder, loc, type, cast)
1950 .getResult();
1951 } else if (inElemTy != type) {
1952 cast = arith::BitcastOp::create(builder, loc, type, cast).getResult();
1953 }
1954 return cast;
1955 }
1956 return {};
1957 };
1958
1959 // Materialization to convert
1960 // - single element of vector element type to single element vector
1961 // If result type of original op is single element vector and lowered type
1962 // is scalar. This materialization cast creates a single element vector by
1963 // First convert element type if needed and then broadcast to single
1964 // element vector.
1965 // Applies only to source materialization.
1966 auto singleElementToVectorMaterializationCast =
1967 [](OpBuilder &builder, Type type, ValueRange inputs,
1968 Location loc) -> Value {
1969 if (inputs.size() != 1)
1970 return {};
1971 auto input = inputs.front();
1972 auto inTy = input.getType();
1973 if (!inTy.isIntOrFloat())
1974 return {};
1975 // If the target type is a vector of rank 0 or single element vector
1976 // of element type matching input type, broadcast input to target type.
1977 if (auto vecTy = dyn_cast<VectorType>(type)) {
1978 if (vecTy.getRank() != 0 && vecTy.getNumElements() != 1)
1979 return {};
1980 auto outElemTy = vecTy.getElementType();
1981 Value cast = input;
1982 if (outElemTy.isIndex()) {
1983 cast = arith::IndexCastUIOp::create(builder, loc,
1984 builder.getIndexType(), cast)
1985 .getResult();
1986 } else if (inTy != outElemTy) {
1987 cast = arith::BitcastOp::create(builder, loc, outElemTy, cast)
1988 .getResult();
1989 }
1990 return vector::BroadcastOp::create(builder, loc, vecTy, cast)
1991 .getResult();
1992 }
1993 return {};
1994 };
1995 typeConverter.addSourceMaterialization(
1996 singleElementToVectorMaterializationCast);
1997 typeConverter.addSourceMaterialization(vectorToVectorMaterializationCast);
1998 typeConverter.addTargetMaterialization(memrefToIntMaterializationCast);
1999 typeConverter.addTargetMaterialization(ui32ToI32MaterializationCast);
2000 typeConverter.addTargetMaterialization(ui64ToI64MaterializationCast);
2001 typeConverter.addTargetMaterialization(
2002 vectorToSingleElementMaterializationCast);
2003 typeConverter.addTargetMaterialization(vectorToVectorMaterializationCast);
2004 ConversionTarget target(*context);
2005 target.addLegalDialect<xevm::XeVMDialect, LLVM::LLVMDialect,
2006 vector::VectorDialect, arith::ArithDialect,
2007 memref::MemRefDialect, gpu::GPUDialect,
2008 index::IndexDialect>();
2009 target.addIllegalDialect<xegpu::XeGPUDialect>();
2010 // arith.extf/arith.truncf between MX narrow floats and f16/bf16 are routed
2011 // to xevm.extf/xevm.truncf; all other arith float casts stay legal.
2012 target.addDynamicallyLegalOp<arith::ExtFOp>(
2013 [](arith::ExtFOp op) { return !isXeVMExtf(op); });
2014 target.addDynamicallyLegalOp<arith::TruncFOp>(
2015 [](arith::TruncFOp op) { return !isXeVMTruncf(op); });
2016
2017 RewritePatternSet patterns(context);
2018 populateXeGPUToXeVMConversionPatterns(typeConverter, patterns);
2020 patterns, target);
2021 if (failed(applyPartialConversion(getOperation(), target,
2022 std::move(patterns))))
2023 signalPassFailure();
2024 }
2025};
2026} // namespace
2027
2028//===----------------------------------------------------------------------===//
2029// Pattern Population
2030//===----------------------------------------------------------------------===//
2032 const LLVMTypeConverter &typeConverter, RewritePatternSet &patterns) {
2033 patterns.add<CreateNdDescToXeVMPattern,
2034 LoadStorePrefetchNdToXeVMPattern<xegpu::LoadNdOp>,
2035 LoadStorePrefetchNdToXeVMPattern<xegpu::StoreNdOp>,
2036 LoadStorePrefetchNdToXeVMPattern<xegpu::PrefetchNdOp>>(
2037 typeConverter, patterns.getContext());
2038 patterns.add<AtomicRMWToXeVMPattern, PrefetchToXeVMPattern,
2039 LoadStoreToXeVMPattern<xegpu::LoadGatherOp>,
2040 LoadStoreToXeVMPattern<xegpu::StoreScatterOp>>(
2041 typeConverter, patterns.getContext());
2042 patterns.add<LoadStoreMatrixToXeVMPattern<xegpu::LoadMatrixOp>,
2043 LoadStoreMatrixToXeVMPattern<xegpu::StoreMatrixOp>,
2044 CreateMemDescOpPattern>(typeConverter, patterns.getContext());
2045 patterns.add<FenceToXeVMPattern, DpasToXeVMPattern>(typeConverter,
2046 patterns.getContext());
2047 patterns.add<DpasMxToXeVMPattern>(typeConverter, patterns.getContext());
2048 patterns.add<ExtfToXeVMPattern, TruncfToXeVMPattern>(typeConverter,
2049 patterns.getContext());
2050 patterns.add<LaneShuffleToXeVMPattern>(typeConverter, patterns.getContext());
2051}
return success()
b getContext())
auto load
static llvm::ManagedStatic< PassManagerOptions > options
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 SmallVector< Value > slice(OpBuilder &b, Location loc, Value value, VectorType nativeType)
Attributes are known-constant values of operations.
Definition Attributes.h:25
IntegerAttr getIndexAttr(int64_t value)
Definition Builders.cpp:116
IntegerAttr getIntegerAttr(Type type, int64_t value)
Definition Builders.cpp:237
IntegerType getIntegerType(unsigned width)
Definition Builders.cpp:75
IndexType getIndexType()
Definition Builders.cpp:59
An attribute that represents a reference to a dense vector or tensor object.
bool isSplat() const
Returns true if this attribute corresponds to a splat, i.e.
static DenseElementsAttr get(ShapedType type, ArrayRef< Attribute > values)
Constructs a dense elements attribute from an array of element values.
Conversion from types to the LLVM IR dialect.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
void setDiscardableAttr(StringAttr name, Attribute value)
Set a discardable attribute by name.
Definition Operation.h:512
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.
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
bool isIntOrFloat() const
Return true if this is an integer (of any signedness) or a float type.
Definition Types.cpp:118
bool isF16() const
Definition Types.cpp:38
unsigned getIntOrFloatBitWidth() const
Return the bit width of an integer or a float type, assert failure on other types.
Definition Types.cpp:124
bool isBF16() const
Definition Types.cpp:37
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
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static ConstantIntOp create(OpBuilder &builder, Location location, int64_t value, unsigned width)
Definition ArithOps.cpp:333
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:734
void populateSCFStructuralTypeConversionsAndLegality(const TypeConverter &typeConverter, RewritePatternSet &patterns, ConversionTarget &target, PatternBenefit benefit=1)
Populates patterns for SCF structural type conversions and sets up the provided ConversionTarget with...
const uArch * getUArch(llvm::StringRef archName)
Definition uArchCommon.h:24
bool hasStaticShapeAndStrides(MemRefType type)
Returns true if type has a static shape and static strides.
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
std::optional< int64_t > getConstantIntValue(OpFoldResult ofr)
If ofr is a constant integer or an IntegerAttr, return the integer.
Value getValueOrCreateConstantIntOp(OpBuilder &b, Location loc, OpFoldResult ofr)
Converts an OpFoldResult to a Value.
Definition Utils.cpp:105
Value getValueOrCreateCastToIndexLike(OpBuilder &b, Location loc, Type targetType, Value value)
Create a cast from an index-like value (index or integer) to another index-like value.
Definition Utils.cpp:122
void populateXeGPUToXeVMConversionPatterns(const LLVMTypeConverter &typeConverter, RewritePatternSet &patterns)
detail::constant_op_matcher m_Constant()
Matches a constant foldable operation.
Definition Matchers.h:369