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