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};
59
60static int32_t getNumericXeVMAddrSpace(xegpu::MemorySpace xeGpuMemspace) {
61 switch (xeGpuMemspace) {
62 case xegpu::MemorySpace::Global:
63 return static_cast<int>(xevm::AddrSpace::GLOBAL);
64 case xegpu::MemorySpace::SLM:
65 return static_cast<int>(xevm::AddrSpace::SHARED);
66 }
67 llvm_unreachable("Unknown XeGPU memory space");
68}
69
70/// Translates a memref memory space attribute into XeVM's numeric address
71/// space, which follows the OpenCL/SPIR-V convention (0 = private, 1 =
72/// global, 2 = constant, 3 = shared/local, 4 = generic). A null attribute,
73/// meaning the memory space was left unspecified, maps to the default space
74/// 0. Returns failure if `memSpace` is a representation this pass does not
75/// know how to translate (e.g. a SPIR-V storage class or an arbitrary string
76/// attribute), rather than assuming it is an `IntegerAttr` and asserting.
77static FailureOr<unsigned> getNumericMemorySpace(Attribute memSpace) {
78 if (!memSpace)
79 return 0u;
80 if (auto intAttr = llvm::dyn_cast<IntegerAttr>(memSpace))
81 return static_cast<unsigned>(intAttr.getInt());
82 if (auto xevmSpace = llvm::dyn_cast<xevm::AddrSpaceAttr>(memSpace))
83 return static_cast<unsigned>(xevmSpace.getValue());
84 if (auto gpuSpace = llvm::dyn_cast<gpu::AddressSpaceAttr>(memSpace)) {
85 switch (gpuSpace.getValue()) {
86 case gpu::AddressSpace::Global:
87 return static_cast<unsigned>(xevm::AddrSpace::GLOBAL);
88 case gpu::AddressSpace::Workgroup:
89 return static_cast<unsigned>(xevm::AddrSpace::SHARED);
90 case gpu::AddressSpace::Private:
91 return static_cast<unsigned>(xevm::AddrSpace::PRIVATE);
92 case gpu::AddressSpace::Constant:
93 return static_cast<unsigned>(xevm::AddrSpace::CONSTANT);
94 }
95 llvm_unreachable("Unknown GPU address space");
96 }
97 return failure();
98}
99
100/// Checks if the given MemRefType refers to shared memory.
101static bool isSharedMemRef(const MemRefType &memrefTy) {
102 FailureOr<unsigned> addrSpace =
103 getNumericMemorySpace(memrefTy.getMemorySpace());
104 return succeeded(addrSpace) &&
105 *addrSpace == static_cast<unsigned>(xevm::AddrSpace::SHARED);
106}
107
108// Get same bitwidth flat vector type of new element type.
109static VectorType encodeVectorTypeTo(VectorType currentVecType,
110 Type toElemType) {
111 auto elemType = currentVecType.getElementType();
112 auto currentBitWidth = elemType.getIntOrFloatBitWidth();
113 auto newBitWidth = toElemType.getIntOrFloatBitWidth();
114 const int size =
115 currentVecType.getNumElements() * currentBitWidth / newBitWidth;
116 return VectorType::get(size, toElemType);
117}
118
119static xevm::LoadCacheControl
120translateLoadXeGPUCacheHint(std::optional<xegpu::CachePolicy> L1hint,
121 std::optional<xegpu::CachePolicy> L3hint) {
122 // If no hints are provided, use the default cache control.
123 if (!L1hint && !L3hint)
124 return xevm::LoadCacheControl::USE_DEFAULT;
125 // If only one of the hints is provided, use the default for the other level.
126 auto L1hintVal = L1hint.value_or(xegpu::CachePolicy::CACHED);
127 auto L3hintVal = L3hint.value_or(xegpu::CachePolicy::CACHED);
128 switch (L1hintVal) {
129 case xegpu::CachePolicy::CACHED:
130 if (L3hintVal == xegpu::CachePolicy::CACHED)
131 return xevm::LoadCacheControl::L1C_L2UC_L3C;
132 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
133 return xevm::LoadCacheControl::L1C_L2UC_L3UC;
134 else
135 llvm_unreachable("Unsupported cache control.");
136 case xegpu::CachePolicy::UNCACHED:
137 if (L3hintVal == xegpu::CachePolicy::CACHED)
138 return xevm::LoadCacheControl::L1UC_L2UC_L3C;
139 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
140 return xevm::LoadCacheControl::L1UC_L2UC_L3UC;
141 else
142 llvm_unreachable("Unsupported cache control.");
143 case xegpu::CachePolicy::STREAMING:
144 if (L3hintVal == xegpu::CachePolicy::CACHED)
145 return xevm::LoadCacheControl::L1S_L2UC_L3C;
146 else if (L3hintVal == xegpu::CachePolicy::UNCACHED)
147 return xevm::LoadCacheControl::L1S_L2UC_L3UC;
148 else
149 llvm_unreachable("Unsupported cache control.");
150 case xegpu::CachePolicy::READ_INVALIDATE:
151 return xevm::LoadCacheControl::INVALIDATE_READ;
152 default:
153 llvm_unreachable("Unsupported cache control.");
154 }
155}
156
157static xevm::StoreCacheControl
158translateStoreXeGPUCacheHint(std::optional<xegpu::CachePolicy> L1hint,
159 std::optional<xegpu::CachePolicy> L3hint) {
160 // If no hints are provided, use the default cache control.
161 if (!L1hint && !L3hint)
162 return xevm::StoreCacheControl::USE_DEFAULT;
163 // If only one of the hints is provided, use the default for the other level.
164 auto L1hintVal = L1hint.value_or(xegpu::CachePolicy::UNCACHED);
165 auto L3hintVal = L3hint.value_or(xegpu::CachePolicy::WRITE_BACK);
166 switch (L1hintVal) {
167 case xegpu::CachePolicy::UNCACHED:
168 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
169 return xevm::StoreCacheControl::L1UC_L2UC_L3UC;
170 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
171 return xevm::StoreCacheControl::L1UC_L2UC_L3WB;
172 else
173 llvm_unreachable("Unsupported cache control.");
174 case xegpu::CachePolicy::STREAMING:
175 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
176 return xevm::StoreCacheControl::L1S_L2UC_L3UC;
177 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
178 return xevm::StoreCacheControl::L1S_L2UC_L3WB;
179 else
180 llvm_unreachable("Unsupported cache control.");
181 case xegpu::CachePolicy::WRITE_BACK:
182 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
183 return xevm::StoreCacheControl::L1WB_L2UC_L3UC;
184 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
185 return xevm::StoreCacheControl::L1WB_L2UC_L3WB;
186 else
187 llvm_unreachable("Unsupported cache control.");
188 case xegpu::CachePolicy::WRITE_THROUGH:
189 if (L3hintVal == xegpu::CachePolicy::UNCACHED)
190 return xevm::StoreCacheControl::L1WT_L2UC_L3UC;
191 else if (L3hintVal == xegpu::CachePolicy::WRITE_BACK)
192 return xevm::StoreCacheControl::L1WT_L2UC_L3WB;
193 else
194 llvm_unreachable("Unsupported cache control.");
195 default:
196 llvm_unreachable("Unsupported cache control.");
197 }
198}
199
200//
201// Note:
202// Block operations for tile of sub byte element types are handled by
203// emulating with larger element types.
204// Tensor descriptor are keep intact and only ops consuming them are
205// emulated
206//
207
208class CreateNdDescToXeVMPattern
209 : public OpConversionPattern<xegpu::CreateNdDescOp> {
210 using OpConversionPattern::OpConversionPattern;
211 LogicalResult
212 matchAndRewrite(xegpu::CreateNdDescOp op,
213 xegpu::CreateNdDescOp::Adaptor adaptor,
214 ConversionPatternRewriter &rewriter) const override {
215 auto loc = op.getLoc();
216 auto source = op.getSource();
217 // Op is lowered to a code sequence that populates payload.
218 // Payload is a 8xi32 vector. Offset to individual fields are defined in
219 // NdTdescOffset enum.
220 Type payloadElemTy = rewriter.getI32Type();
221 VectorType payloadTy = VectorType::get(8, payloadElemTy);
222 Type i64Ty = rewriter.getI64Type();
223 // 4xi64 view is used for inserting the base pointer.
224 VectorType payloadI64Ty = VectorType::get(4, i64Ty);
225 // Initialize payload to zero.
226 Value payload = arith::ConstantOp::create(
227 rewriter, loc,
228 DenseElementsAttr::get(payloadTy, IntegerAttr::get(payloadElemTy, 0)));
229
230 Value baseAddr;
231 Value baseShapeW;
232 Value baseShapeH;
233
234 // Source can be a memref or a pointer (ui64, ui32, i64 or i32).
235 SmallVector<OpFoldResult> mixedSizes = op.getMixedSizes();
236 SmallVector<OpFoldResult> mixedStrides = op.getMixedStrides();
237 // Descriptor shape is expected to be 2D.
238 int64_t rank = mixedSizes.size();
239 auto sourceTy = source.getType();
240 auto sourceMemrefTy = dyn_cast<MemRefType>(sourceTy);
241 // If source is a memref, we need to extract the aligned pointer as index.
242 // Pointer type is passed as i32 or i64 by type converter.
243 if (sourceMemrefTy) {
244 if (!sourceMemrefTy.hasRank()) {
245 return rewriter.notifyMatchFailure(op, "Expected ranked Memref.");
246 }
247 // Access adaptor after failure check to avoid rolling back generated code
248 // for materialization cast.
249 baseAddr = adaptor.getSource();
250 } else {
251 baseAddr = adaptor.getSource();
252 if (baseAddr.getType() != i64Ty) {
253 // Pointer type may be i32. Cast to i64 if needed.
254 baseAddr = arith::ExtUIOp::create(rewriter, loc, i64Ty, baseAddr);
255 }
256 }
257 // 1D tensor descriptor is just the base address.
258 if (rank == 1) {
259 rewriter.replaceOp(op, baseAddr);
260 return success();
261 }
262 // Utility for creating offset values from op fold result.
263 auto createOffset = [&](SmallVector<OpFoldResult> &ofrVec,
264 unsigned idx) -> Value {
265 Value val = getValueOrCreateConstantIntOp(rewriter, loc, ofrVec[idx]);
266 val = getValueOrCreateCastToIndexLike(rewriter, loc, payloadElemTy, val);
267 return val;
268 };
269 // For ND descriptors, the last 2 dimensions are the 2D tile (H, W).
270 // Any leading dimensions are batch dims with associated strides.
271 baseShapeW = createOffset(mixedSizes, rank - 1);
272 baseShapeH = createOffset(mixedSizes, rank - 2);
273 // Pitch is the stride of dim rank-2 (the row stride of the 2D tile).
274 Value basePitch = createOffset(mixedStrides, rank - 2);
275 // Populate payload.
276 Value payLoadAsI64 =
277 vector::BitCastOp::create(rewriter, loc, payloadI64Ty, payload);
278 payLoadAsI64 =
279 vector::InsertOp::create(rewriter, loc, baseAddr, payLoadAsI64,
280 static_cast<int>(NdTdescOffset::BasePtr));
281 payload = vector::BitCastOp::create(rewriter, loc, payloadTy, payLoadAsI64);
282 payload =
283 vector::InsertOp::create(rewriter, loc, baseShapeW, payload,
284 static_cast<int>(NdTdescOffset::BaseShapeW));
285 payload =
286 vector::InsertOp::create(rewriter, loc, baseShapeH, payload,
287 static_cast<int>(NdTdescOffset::BaseShapeH));
288 payload =
289 vector::InsertOp::create(rewriter, loc, basePitch, payload,
290 static_cast<int>(NdTdescOffset::BasePitch));
291 rewriter.replaceOp(op, payload);
292 return success();
293 }
294};
295
296template <
297 typename OpType,
298 typename = std::enable_if_t<llvm::is_one_of<
299 OpType, xegpu::LoadNdOp, xegpu::StoreNdOp, xegpu::PrefetchNdOp>::value>>
300class LoadStorePrefetchNdToXeVMPattern : public OpConversionPattern<OpType> {
301 using OpConversionPattern<OpType>::OpConversionPattern;
302 LogicalResult
303 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
304 ConversionPatternRewriter &rewriter) const override {
305 auto mixedOffsets = op.getMixedOffsets();
306 int64_t opOffsetsSize = mixedOffsets.size();
307 auto loc = op.getLoc();
308 auto ctxt = rewriter.getContext();
309
310 auto tdesc = adaptor.getTensorDesc();
311 auto tdescTy = op.getTensorDescType();
312 auto tileRank = tdescTy.getRank();
313 if (opOffsetsSize != tileRank)
314 return rewriter.notifyMatchFailure(
315 op, "Expected offset rank to match descriptor rank.");
316 auto elemType = tdescTy.getElementType();
317 auto elemBitSize = elemType.getIntOrFloatBitWidth();
318 bool isSubByte = elemBitSize < 8;
319 uint64_t wScaleFactor = 1;
320
321 if (!isSubByte && (elemBitSize % 8 != 0))
322 return rewriter.notifyMatchFailure(
323 op, "Expected element type bit width to be multiple of 8.");
324 auto tileW = tdescTy.getDimSize(tileRank - 1);
325 // For sub byte types, only 4bits are currently supported.
326 if (isSubByte) {
327 if (elemBitSize != 4)
328 return rewriter.notifyMatchFailure(
329 op, "Only sub byte types of 4bits are supported.");
330 if (tileRank != 2)
331 return rewriter.notifyMatchFailure(
332 op, "Sub byte types are only supported for 2D tensor descriptors.");
333 auto subByteFactor = 8 / elemBitSize;
334 auto tileH = tdescTy.getDimSize(0);
335 // Handle special case for packed load.
336 if constexpr (std::is_same_v<OpType, xegpu::LoadNdOp>) {
337 if (op.getPacked().value_or(false)) {
338 // packed load is implemented as packed loads of 8bit elements.
339 if (tileH == systolicDepth * 4 &&
340 tileW == executionSize * subByteFactor) {
341 // Usage case for loading as Matrix B with pack request.
342 // source is assumed to pre-packed into 8bit elements
343 // Emulate with 8bit loads with pack request.
344 // scaled_tileW = executionSize
345 elemType = rewriter.getIntegerType(8);
346 tileW = executionSize;
347 wScaleFactor = subByteFactor;
348 }
349 }
350 }
351 // If not handled by packed load case above, handle other cases.
352 if (wScaleFactor == 1) {
353 auto sub16BitFactor = subByteFactor * 2;
354 if (tileW == executionSize * sub16BitFactor) {
355 // Usage case for loading as Matrix A operand
356 // Emulate with 16bit loads/stores.
357 // scaled_tileW = executionSize
358 elemType = rewriter.getIntegerType(16);
359 tileW = executionSize;
360 wScaleFactor = sub16BitFactor;
361 } else {
362 return rewriter.notifyMatchFailure(
363 op, "Unsupported tile shape for sub byte types.");
364 }
365 }
366 // recompute element bit size for emulation.
367 elemBitSize = elemType.getIntOrFloatBitWidth();
368 }
369
370 // Get address space from tensor descriptor memory space.
371 auto ptrTypeLLVM = LLVM::LLVMPointerType::get(
372 ctxt, getNumericXeVMAddrSpace(tdescTy.getMemorySpace()));
373 if (tileRank >= 2) {
374 // Compute element byte size.
375 Value elemByteSize = arith::ConstantIntOp::create(
376 rewriter, loc, rewriter.getI32Type(), elemBitSize / 8);
377 VectorType payloadI64Ty = VectorType::get(4, rewriter.getI64Type());
378 Value payLoadAsI64 =
379 vector::BitCastOp::create(rewriter, loc, payloadI64Ty, tdesc);
380 Value basePtr =
381 vector::ExtractOp::create(rewriter, loc, payLoadAsI64,
382 static_cast<int>(NdTdescOffset::BasePtr));
383 Value baseShapeW = vector::ExtractOp::create(
384 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BaseShapeW));
385 Value baseShapeH = vector::ExtractOp::create(
386 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BaseShapeH));
387 Value basePitch = vector::ExtractOp::create(
388 rewriter, loc, tdesc, static_cast<int>(NdTdescOffset::BasePitch));
389
390 // For rank > 2, leading (batch) dim offsets should be 0 after unrolling
391 // (batch is baked into the base pointer via memref.subview during
392 // blocking). Use only the last 2 offsets for the 2D block operation.
393 Value offsetW = getValueOrCreateConstantIntOp(rewriter, loc,
394 mixedOffsets[tileRank - 1]);
395 offsetW = getValueOrCreateCastToIndexLike(rewriter, loc,
396 rewriter.getI32Type(), offsetW);
397 Value offsetH = getValueOrCreateConstantIntOp(rewriter, loc,
398 mixedOffsets[tileRank - 2]);
399 offsetH = getValueOrCreateCastToIndexLike(rewriter, loc,
400 rewriter.getI32Type(), offsetH);
401 // Convert base pointer (i64) to LLVM pointer type.
402 Value basePtrLLVM =
403 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtr);
404 // FIXME: width or pitch is not the same as baseShapeW it should be the
405 // stride of the second to last dimension in row major layout.
406 // Compute width in bytes.
407 Value baseShapeWInBytes =
408 arith::MulIOp::create(rewriter, loc, baseShapeW, elemByteSize);
409 // Compute pitch in bytes.
410 Value basePitchBytes =
411 arith::MulIOp::create(rewriter, loc, basePitch, elemByteSize);
412
413 if (wScaleFactor > 1) {
414 // Scale offsetW, baseShapeWInBytes for sub byte emulation.
415 // Note: tileW is already scaled above.
416 Value wScaleFactorValLog2 = arith::ConstantIntOp::create(
417 rewriter, loc, rewriter.getI32Type(), llvm::Log2_64(wScaleFactor));
418 baseShapeWInBytes = arith::ShRSIOp::create(
419 rewriter, loc, baseShapeWInBytes, wScaleFactorValLog2);
420 basePitchBytes = arith::ShRSIOp::create(rewriter, loc, basePitchBytes,
421 wScaleFactorValLog2);
422 offsetW =
423 arith::ShRSIOp::create(rewriter, loc, offsetW, wScaleFactorValLog2);
424 }
425 // Get tile height from the tensor descriptor type (second-to-last dim).
426 auto tileH = tdescTy.getDimSize(tileRank - 2);
427 // Get vblocks from the tensor descriptor type.
428 int32_t vblocks = tdescTy.getArrayLength();
429 if constexpr (std::is_same_v<OpType, xegpu::StoreNdOp>) {
430 Value src = adaptor.getValue();
431 // If store value is a scalar, get value from op instead of adaptor.
432 // Adaptor might have optimized away single element vector
433 if (src.getType().isIntOrFloat()) {
434 src = op.getValue();
435 }
436 VectorType srcVecTy = dyn_cast<VectorType>(src.getType());
437 if (!srcVecTy)
438 return rewriter.notifyMatchFailure(
439 op, "Expected store value to be a vector type.");
440 // Get flat vector type of integer type with matching element bit size.
441 VectorType newSrcVecTy =
442 encodeVectorTypeTo(srcVecTy, rewriter.getIntegerType(elemBitSize));
443 if (srcVecTy != newSrcVecTy)
444 src = vector::BitCastOp::create(rewriter, loc, newSrcVecTy, src);
445 auto storeCacheControl =
446 translateStoreXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
447 xevm::BlockStore2dOp::create(
448 rewriter, loc, basePtrLLVM, baseShapeWInBytes, baseShapeH,
449 basePitchBytes, offsetW, offsetH, elemBitSize, tileW, tileH, src,
450 xevm::StoreCacheControlAttr::get(ctxt, storeCacheControl));
451 rewriter.eraseOp(op);
452 } else {
453 auto loadCacheControl =
454 translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
455 if constexpr (std::is_same_v<OpType, xegpu::PrefetchNdOp>) {
456 xevm::BlockPrefetch2dOp::create(
457 rewriter, loc, basePtrLLVM, baseShapeWInBytes, baseShapeH,
458 basePitchBytes, offsetW, offsetH, elemBitSize, tileW, tileH,
459 vblocks, xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
460 rewriter.eraseOp(op);
461 } else {
462 VectorType dstVecTy = cast<VectorType>(op.getValue().getType());
463 bool vnni = op.getPacked().value_or(false);
464 auto transposeValue = op.getTranspose();
465 bool transpose =
466 transposeValue.has_value() && transposeValue.value()[0] == 1;
467 // Handle special case of 32x16 and 8bit element load
468 // with no vnni, no transpose, no vblocks.
469 // For this special case, vnni and non vnni yields the same output
470 // and only the vnni variant is supported by HW.
471 // Check and set vnni of the special case.
472 if (elemBitSize == 8 && tileW == 16 && tileH == 32 && !vnni &&
473 !transpose) {
474 vnni = true;
475 }
476 // Handle tranpose request on small element size
477 // Transpose needs to be requested on 32bit element type.
478 // offsetW and tileW needs to be adjusted to account for element type
479 // change.
480 if (transpose && elemBitSize < 32) {
481 int32_t scale = 32 / elemBitSize;
482 Value scaleLog2 = arith::ConstantIntOp::create(
483 rewriter, loc, rewriter.getI32Type(), llvm::Log2_64(scale));
484 offsetW = arith::ShRSIOp::create(rewriter, loc, offsetW, scaleLog2);
485 tileW = tileW * elemBitSize / 32;
486 elemBitSize = 32;
487 }
488 VectorType loadedTy = encodeVectorTypeTo(
489 dstVecTy, vnni ? rewriter.getI32Type()
490 : rewriter.getIntegerType(elemBitSize));
491
492 Value resultFlatVec = xevm::BlockLoad2dOp::create(
493 rewriter, loc, loadedTy, basePtrLLVM, baseShapeWInBytes,
494 baseShapeH, basePitchBytes, offsetW, offsetH, elemBitSize, tileW,
495 tileH, vblocks, transpose, vnni,
496 xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
497 resultFlatVec = vector::BitCastOp::create(
498 rewriter, loc,
499 encodeVectorTypeTo(loadedTy, dstVecTy.getElementType()),
500 resultFlatVec);
501 rewriter.replaceOp(op, resultFlatVec);
502 }
503 }
504 } else {
505 // 1D tensor descriptor.
506 // `tdesc` represents base address as i64
507 // Offset in number of elements, need to multiply by element byte size.
508 // Compute byte offset.
509 // byteOffset = offset * elementByteSize
510 Value offset =
511 getValueOrCreateConstantIntOp(rewriter, loc, mixedOffsets[0]);
512 offset = getValueOrCreateCastToIndexLike(rewriter, loc,
513 rewriter.getI64Type(), offset);
514 // Compute element byte size.
515 Value elemByteSize = arith::ConstantIntOp::create(
516 rewriter, loc, rewriter.getI64Type(), elemBitSize / 8);
517 Value byteOffset =
518 rewriter.createOrFold<arith::MulIOp>(loc, offset, elemByteSize);
519 // Final address = basePtr + byteOffset
520 Value finalAddrI64 = rewriter.createOrFold<arith::AddIOp>(
521 loc, tdesc,
522 getValueOrCreateCastToIndexLike(rewriter, loc, rewriter.getI64Type(),
523 byteOffset));
524 // Convert base pointer (i64) to LLVM pointer type.
525 Value finalPtrLLVM =
526 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, finalAddrI64);
527 if constexpr (std::is_same_v<OpType, xegpu::StoreNdOp>) {
528 Value src = adaptor.getValue();
529 // If store value is a scalar, get value from op instead of adaptor.
530 // Adaptor might have optimized away single element vector
531 if (src.getType().isIntOrFloat()) {
532 src = op.getValue();
533 }
534 VectorType srcVecTy = dyn_cast<VectorType>(src.getType());
535 if (!srcVecTy)
536 return rewriter.notifyMatchFailure(
537 op, "Expected store value to be a vector type.");
538 // Get flat vector type of integer type with matching element bit size.
539 VectorType newSrcVecTy =
540 encodeVectorTypeTo(srcVecTy, rewriter.getIntegerType(elemBitSize));
541 if (srcVecTy != newSrcVecTy)
542 src = vector::BitCastOp::create(rewriter, loc, newSrcVecTy, src);
543 auto storeCacheControl =
544 translateStoreXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
545 rewriter.replaceOpWithNewOp<xevm::BlockStoreOp>(
546 op, finalPtrLLVM, src,
547 xevm::StoreCacheControlAttr::get(ctxt, storeCacheControl));
548 } else if constexpr (std::is_same_v<OpType, xegpu::LoadNdOp>) {
549 auto loadCacheControl =
550 translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint());
551 VectorType resTy = cast<VectorType>(op.getValue().getType());
552 VectorType loadedTy =
553 encodeVectorTypeTo(resTy, rewriter.getIntegerType(elemBitSize));
554 Value load = xevm::BlockLoadOp::create(
555 rewriter, loc, loadedTy, finalPtrLLVM,
556 xevm::LoadCacheControlAttr::get(ctxt, loadCacheControl));
557 if (loadedTy != resTy)
558 load = vector::BitCastOp::create(rewriter, loc, resTy, load);
559 rewriter.replaceOp(op, load);
560 } else {
561 return rewriter.notifyMatchFailure(
562 op, "Unsupported operation: xegpu.prefetch_nd with tensor "
563 "descriptor rank == 1");
564 }
565 }
566 return success();
567 }
568};
569
570// Add a builder that creates
571// offset * elemByteSize + baseAddr
572static Value addOffsetToBaseAddr(ConversionPatternRewriter &rewriter,
573 Location loc, Value baseAddr, Value offset,
574 int64_t elemByteSize) {
576 rewriter, loc, baseAddr.getType(), elemByteSize);
577 Value byteOffset = arith::MulIOp::create(rewriter, loc, offset, byteSize);
578 Value newAddr = arith::AddIOp::create(rewriter, loc, baseAddr, byteOffset);
579 return newAddr;
580}
581
582template <typename OpType,
583 typename = std::enable_if_t<llvm::is_one_of<
584 OpType, xegpu::LoadGatherOp, xegpu::StoreScatterOp>::value>>
585class LoadStoreToXeVMPattern : public OpConversionPattern<OpType> {
586 using OpConversionPattern<OpType>::OpConversionPattern;
587 LogicalResult
588 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
589 ConversionPatternRewriter &rewriter) const override {
590 Value offset = adaptor.getOffsets();
591 if (!offset)
592 return rewriter.notifyMatchFailure(op, "Expected offset to be provided.");
593 auto loc = op.getLoc();
594 auto ctxt = rewriter.getContext();
595 Value basePtrI64;
596 // Load result or Store valye Type can be vector or scalar.
597 Type valOrResTy;
598 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>)
599 valOrResTy =
600 this->getTypeConverter()->convertType(op.getResult().getType());
601 else
602 valOrResTy = adaptor.getValue().getType();
603 VectorType valOrResVecTy = dyn_cast<VectorType>(valOrResTy);
604 bool hasScalarVal = !valOrResVecTy;
605 int64_t elemBitWidth =
606 hasScalarVal ? valOrResTy.getIntOrFloatBitWidth()
607 : valOrResVecTy.getElementType().getIntOrFloatBitWidth();
608 // Element type must be multiple of 8 bits.
609 if (elemBitWidth % 8 != 0)
610 return rewriter.notifyMatchFailure(
611 op, "Expected element type bit width to be multiple of 8.");
612 int64_t elemByteSize = elemBitWidth / 8;
613 // Default memory space is global.
614 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
615 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::Global));
616 // Base pointer can come from source (load) or dest (store).
617 // If they are memrefs, we use their memory space.
618 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>) {
619 basePtrI64 = adaptor.getSource();
620 if (auto memRefTy = dyn_cast<MemRefType>(op.getSource().getType())) {
621 FailureOr<unsigned> addrSpace =
622 getNumericMemorySpace(memRefTy.getMemorySpace());
623 if (failed(addrSpace))
624 return rewriter.notifyMatchFailure(
625 op, "Unsupported memref memory space attribute.");
626 if (*addrSpace != 0)
627 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
628 }
629 } else {
630 basePtrI64 = adaptor.getDest();
631 if (auto memRefTy = dyn_cast<MemRefType>(op.getDest().getType())) {
632 FailureOr<unsigned> addrSpace =
633 getNumericMemorySpace(memRefTy.getMemorySpace());
634 if (failed(addrSpace))
635 return rewriter.notifyMatchFailure(
636 op, "Unsupported memref memory space attribute.");
637 if (*addrSpace != 0)
638 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
639 }
640 }
641 // Base pointer is passed as i32 or i64 by adaptor, cast to i64 if needed.
642 if (basePtrI64.getType() != rewriter.getI64Type()) {
643 basePtrI64 = arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(),
644 basePtrI64);
645 }
646 Value mask = adaptor.getMask();
647 if (dyn_cast<VectorType>(offset.getType())) {
648 // Offset needs be scalar. Single element vector is converted to scalar
649 // by type converter.
650 return rewriter.notifyMatchFailure(op, "Expected offset to be a scalar.");
651 } else {
652 // If offset is provided, we add them to the base pointer.
653 // Offset is in number of elements, we need to multiply by
654 // element byte size.
655 basePtrI64 =
656 addOffsetToBaseAddr(rewriter, loc, basePtrI64, offset, elemByteSize);
657 }
658 // Convert base pointer (i64) to LLVM pointer type.
659 Value basePtrLLVM =
660 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
661
662 Value maskForLane;
663 VectorType maskVecTy = dyn_cast<VectorType>(mask.getType());
664 if (maskVecTy) {
665 // Mask needs be scalar. Single element vector is converted to scalar by
666 // type converter.
667 return rewriter.notifyMatchFailure(op, "Expected mask to be a scalar.");
668 } else
669 maskForLane = mask;
670 if constexpr (std::is_same_v<OpType, xegpu::LoadGatherOp>) {
671 scf::IfOp ifOp = scf::IfOp::create(rewriter, loc, {valOrResTy},
672 maskForLane, true, true);
673 // If mask is true,- then clause - load from memory and yield.
674 rewriter.setInsertionPointToStart(&ifOp.getThenRegion().front());
675 if (!hasScalarVal)
676 valOrResTy = VectorType::get({valOrResVecTy.getNumElements()},
677 valOrResVecTy.getElementType());
678 Value loaded =
679 LLVM::LoadOp::create(rewriter, loc, valOrResTy, basePtrLLVM);
680 // Set cache control attribute on the load operation.
681 loaded.getDefiningOp()->setAttr(
682 "cache_control", xevm::LoadCacheControlAttr::get(
683 ctxt, translateLoadXeGPUCacheHint(
684 op.getL1Hint(), op.getL3Hint())));
685 scf::YieldOp::create(rewriter, loc, ValueRange{loaded});
686 rewriter.setInsertionPointToStart(&ifOp.getElseRegion().front());
687 // If mask is false - else clause -yield a vector of zeros.
688 auto eTy = hasScalarVal ? valOrResTy : valOrResVecTy.getElementType();
689 TypedAttr eVal;
690 if (eTy.isFloat())
691 eVal = FloatAttr::get(eTy, 0.0);
692 else
693 eVal = IntegerAttr::get(eTy, 0);
694 if (hasScalarVal)
695 loaded = arith::ConstantOp::create(rewriter, loc, eVal);
696 else
697 loaded = arith::ConstantOp::create(
698 rewriter, loc, DenseElementsAttr::get(valOrResVecTy, eVal));
699 scf::YieldOp::create(rewriter, loc, ValueRange{loaded});
700 rewriter.replaceOp(op, ifOp.getResult(0));
701 } else {
702 // If mask is true, perform the store.
703 scf::IfOp ifOp = scf::IfOp::create(rewriter, loc, maskForLane, false);
704 auto body = ifOp.getBody();
705 rewriter.setInsertionPointToStart(body);
706 auto storeOp =
707 LLVM::StoreOp::create(rewriter, loc, adaptor.getValue(), basePtrLLVM);
708 // Set cache control attribute on the store operation.
709 storeOp.getOperation()->setAttr(
710 "cache_control", xevm::StoreCacheControlAttr::get(
711 ctxt, translateStoreXeGPUCacheHint(
712 op.getL1Hint(), op.getL3Hint())));
713 rewriter.eraseOp(op);
714 }
715 return success();
716 }
717};
718
719class CreateMemDescOpPattern final
720 : public OpConversionPattern<xegpu::CreateMemDescOp> {
721public:
722 using OpConversionPattern<xegpu::CreateMemDescOp>::OpConversionPattern;
723 LogicalResult
724 matchAndRewrite(xegpu::CreateMemDescOp op, OpAdaptor adaptor,
725 ConversionPatternRewriter &rewriter) const override {
726
727 rewriter.replaceOp(op, adaptor.getSource());
728 return success();
729 }
730};
731
732template <typename OpType,
733 typename = std::enable_if_t<llvm::is_one_of<
734 OpType, xegpu::LoadMatrixOp, xegpu::StoreMatrixOp>::value>>
735class LoadStoreMatrixToXeVMPattern : public OpConversionPattern<OpType> {
736 using OpConversionPattern<OpType>::OpConversionPattern;
737 LogicalResult
738 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
739 ConversionPatternRewriter &rewriter) const override {
740
741 SmallVector<OpFoldResult> offsets = op.getMixedOffsets();
742 if (offsets.empty())
743 return rewriter.notifyMatchFailure(op, "Expected offset to be provided.");
744
745 auto loc = op.getLoc();
746 auto ctxt = rewriter.getContext();
747 Value baseAddr32 = adaptor.getMemDesc();
748 Value mdescVal = op.getMemDesc();
749 // Load result or Store value Type can be vector or scalar.
750 Type dataTy;
751 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
752 Type resType = op.getResult().getType();
753 // Some transforms may leave unit dimension in the 2D vector, adaptors do
754 // not catch it for results.
755 if (auto vecType = dyn_cast<VectorType>(resType)) {
756 assert(llvm::count_if(vecType.getShape(),
757 [](int64_t d) { return d != 1; }) <= 1 &&
758 "Expected either 1D vector or nD with unit dimensions");
759 resType = VectorType::get({vecType.getNumElements()},
760 vecType.getElementType());
761 }
762 dataTy = resType;
763 } else
764 dataTy = adaptor.getData().getType();
765 VectorType valOrResVecTy = dyn_cast<VectorType>(dataTy);
766 if (!valOrResVecTy)
767 valOrResVecTy = VectorType::get(1, dataTy);
768
769 int64_t elemBitWidth =
770 valOrResVecTy.getElementType().getIntOrFloatBitWidth();
771 // Element type must be multiple of 8 bits.
772 if (elemBitWidth % 8 != 0)
773 return rewriter.notifyMatchFailure(
774 op, "Expected element type bit width to be multiple of 8.");
775 int64_t elemByteSize = elemBitWidth / 8;
776
777 // Default memory space is SLM.
778 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
779 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::SLM));
780
781 auto mdescTy = cast<xegpu::MemDescType>(mdescVal.getType());
782
783 Value linearOffset = mdescTy.getLinearOffsets(rewriter, loc, offsets);
784 linearOffset = arith::IndexCastUIOp::create(
785 rewriter, loc, rewriter.getI32Type(), linearOffset);
786 Value basePtrI32 = addOffsetToBaseAddr(rewriter, loc, baseAddr32,
787 linearOffset, elemByteSize);
788
789 // convert base pointer (i32) to LLVM pointer type
790 Value basePtrLLVM =
791 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI32);
792
793 if (op.getSubgroupBlockIoAttr()) {
794 // if the attribute 'subgroup_block_io' is set to true, it lowers to
795 // xevm.blockload
796
797 Type intElemTy = rewriter.getIntegerType(elemBitWidth);
798 VectorType intVecTy =
799 VectorType::get(valOrResVecTy.getShape(), intElemTy);
800
801 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
802 Value loadOp =
803 xevm::BlockLoadOp::create(rewriter, loc, intVecTy, basePtrLLVM);
804 if (intVecTy != valOrResVecTy) {
805 loadOp =
806 vector::BitCastOp::create(rewriter, loc, valOrResVecTy, loadOp);
807 }
808 rewriter.replaceOp(op, loadOp);
809 } else {
810 Value dataToStore = adaptor.getData();
811 if (valOrResVecTy != intVecTy) {
812 dataToStore =
813 vector::BitCastOp::create(rewriter, loc, intVecTy, dataToStore);
814 }
815 xevm::BlockStoreOp::create(rewriter, loc, basePtrLLVM, dataToStore,
816 nullptr);
817 rewriter.eraseOp(op);
818 }
819 return success();
820 }
821
822 if (valOrResVecTy.getNumElements() >= 1) {
823 auto chipOpt = xegpu::getChipStr(op);
824 if (!chipOpt ||
825 (*chipOpt != "pvc" && *chipOpt != "bmg" && *chipOpt != "cri")) {
826 // the lowering for chunk load only works for pvc, bmg or cri
827 return rewriter.notifyMatchFailure(
828 op, "The lowering is specific to pvc, bmg or cri.");
829 }
830 }
831
832 if constexpr (std::is_same_v<OpType, xegpu::LoadMatrixOp>) {
833 // The load result type is taken from the type converter. This maps
834 // element types that are not directly representable in LLVM (e.g.
835 // f8E8M0FNU) to an integer storage type of the same bit width, and
836 // collapses single-element vectors to a scalar, since LLVM load/store
837 // does not support vectors of size 1.
838 Type loadTy =
839 this->getTypeConverter()->convertType(op.getResult().getType());
840 auto loadOp = LLVM::LoadOp::create(rewriter, loc, loadTy, basePtrLLVM);
841 rewriter.replaceOp(op, loadOp);
842 } else {
843 LLVM::StoreOp::create(rewriter, loc, adaptor.getData(), basePtrLLVM);
844 rewriter.eraseOp(op);
845 }
846 return success();
847 }
848};
849
850class PrefetchToXeVMPattern : public OpConversionPattern<xegpu::PrefetchOp> {
851 using OpConversionPattern::OpConversionPattern;
852 LogicalResult
853 matchAndRewrite(xegpu::PrefetchOp op, xegpu::PrefetchOp::Adaptor adaptor,
854 ConversionPatternRewriter &rewriter) const override {
855 auto loc = op.getLoc();
856 auto ctxt = rewriter.getContext();
857 Value basePtrI64 = adaptor.getSource();
858 // Base pointer is passed as i32 or i64 by adaptor, cast to i64 if needed.
859 if (basePtrI64.getType() != rewriter.getI64Type())
860 basePtrI64 = arith::ExtUIOp::create(rewriter, loc, rewriter.getI64Type(),
861 basePtrI64);
862 Value offsets = adaptor.getOffsets();
863 if (offsets) {
864 VectorType offsetsVecTy = dyn_cast<VectorType>(offsets.getType());
865 if (offsetsVecTy) {
866 // Offset needs be scalar.
867 return rewriter.notifyMatchFailure(op,
868 "Expected offsets to be a scalar.");
869 } else {
870 int64_t elemBitWidth{0};
871 int64_t elemByteSize;
872 // Element byte size can come from two sources:
873 if (auto memRefTy = dyn_cast<MemRefType>(op.getSourceType())) {
874 // If memref is available, we use its element type to
875 // determine element byte size.
876 elemBitWidth = memRefTy.getElementType().getIntOrFloatBitWidth();
877 } else {
878 // Otherwise, we use the provided offset byte alignment.
879 elemByteSize = *op.getOffsetAlignByte();
880 }
881 if (elemBitWidth != 0) {
882 if (elemBitWidth % 8 != 0)
883 return rewriter.notifyMatchFailure(
884 op, "Expected element type bit width to be multiple of 8.");
885 elemByteSize = elemBitWidth / 8;
886 }
887 basePtrI64 = addOffsetToBaseAddr(rewriter, loc, basePtrI64, offsets,
888 elemByteSize);
889 }
890 }
891 // Default memory space is global.
892 LLVM::LLVMPointerType ptrTypeLLVM = LLVM::LLVMPointerType::get(
893 ctxt, getNumericXeVMAddrSpace(xegpu::MemorySpace::Global));
894 // If source is a memref, we use its memory space.
895 if (auto memRefTy = dyn_cast<MemRefType>(op.getSource().getType())) {
896 FailureOr<unsigned> addrSpace =
897 getNumericMemorySpace(memRefTy.getMemorySpace());
898 if (failed(addrSpace))
899 return rewriter.notifyMatchFailure(
900 op, "Unsupported memref memory space attribute.");
901 if (*addrSpace != 0)
902 ptrTypeLLVM = LLVM::LLVMPointerType::get(ctxt, *addrSpace);
903 }
904 // Convert base pointer (i64) to LLVM pointer type.
905 Value ptrLLVM =
906 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
907 // Create the prefetch op with cache control attribute.
908 xevm::PrefetchOp::create(
909 rewriter, loc, ptrLLVM,
910 xevm::LoadCacheControlAttr::get(
911 ctxt, translateLoadXeGPUCacheHint(op.getL1Hint(), op.getL3Hint())));
912 rewriter.eraseOp(op);
913 return success();
914 }
915};
916
917class FenceToXeVMPattern : public OpConversionPattern<xegpu::FenceOp> {
918 using OpConversionPattern::OpConversionPattern;
919 LogicalResult
920 matchAndRewrite(xegpu::FenceOp op, xegpu::FenceOp::Adaptor adaptor,
921 ConversionPatternRewriter &rewriter) const override {
922 auto loc = op.getLoc();
923 xevm::MemScope memScope{xevm::MemScope::WORKGROUP};
924 switch (op.getFenceScope()) {
925 case xegpu::FenceScope::Workgroup:
926 memScope = xevm::MemScope::WORKGROUP;
927 break;
928 case xegpu::FenceScope::GPU:
929 memScope = xevm::MemScope::DEVICE;
930 break;
931 }
932 xevm::AddrSpace addrSpace{xevm::AddrSpace::GLOBAL};
933 switch (op.getMemoryKind()) {
934 case xegpu::MemorySpace::Global:
935 addrSpace = xevm::AddrSpace::GLOBAL;
936 break;
937 case xegpu::MemorySpace::SLM:
938 addrSpace = xevm::AddrSpace::SHARED;
939 break;
940 }
941 xevm::MemfenceOp::create(rewriter, loc, memScope, addrSpace);
942 rewriter.eraseOp(op);
943 return success();
944 }
945};
946
947static auto encodePrecision = [](Type type) -> xevm::ElemType {
948 if (type.isBF16())
949 return xevm::ElemType::BF16;
950 else if (type.isF16())
951 return xevm::ElemType::F16;
952 else if (type.isTF32())
953 return xevm::ElemType::TF32;
954 else if (type.isInteger(8)) {
955 if (type.isUnsignedInteger())
956 return xevm::ElemType::U8;
957 return xevm::ElemType::S8;
958 } else if (type.isF32())
959 return xevm::ElemType::F32;
960 else if (type.isInteger(32))
961 return xevm::ElemType::S32;
962 else if (type.isF8E5M2())
963 return xevm::ElemType::BF8;
964 else if (type.isF8E4M3FN())
965 return xevm::ElemType::F8;
966 else if (mlir::isa<Float4E2M1FNType>(type))
967 return xevm::ElemType::E2M1;
968 llvm_unreachable("add more support for ElemType");
969};
970
971static unsigned getNumOperandsPerDword(xevm::ElemType pTy) {
972 switch (pTy) {
973 case xevm::ElemType::TF32:
974 return 1;
975 case xevm::ElemType::BF16:
976 case xevm::ElemType::F16:
977 return 2;
978 case xevm::ElemType::U8:
979 case xevm::ElemType::S8:
980 case xevm::ElemType::F8:
981 case xevm::ElemType::BF8:
982 return 4;
983 case xevm::ElemType::E2M1:
984 return 8;
985 default:
986 llvm_unreachable("unsupported xevm::ElemType");
987 }
988}
989
990class DpasToXeVMPattern : public OpConversionPattern<xegpu::DpasOp> {
991 using OpConversionPattern::OpConversionPattern;
992 LogicalResult
993 matchAndRewrite(xegpu::DpasOp op, xegpu::DpasOp::Adaptor adaptor,
994 ConversionPatternRewriter &rewriter) const override {
995 auto loc = op.getLoc();
996 auto ctxt = rewriter.getContext();
997 auto aTy = cast<VectorType>(op.getLhs().getType());
998 auto bTy = cast<VectorType>(op.getRhs().getType());
999 auto resultType = cast<VectorType>(op.getResultType());
1000
1001 // get the correct dpasInst by getting info from chip
1002 auto chipStr = xegpu::getChipStr(op);
1003 if (!chipStr)
1004 return rewriter.notifyMatchFailure(op, "cannot determine target chip");
1005
1006 const auto *uArch = mlir::xegpu::uArch::getUArch(*chipStr);
1007 if (!uArch)
1008 return rewriter.notifyMatchFailure(op, "unsupported target uArch");
1009
1010 auto *dpasInst = const_cast<xegpu::uArch::SubgroupMatrixMultiplyAcc *>(
1011 llvm::dyn_cast_or_null<xegpu::uArch::SubgroupMatrixMultiplyAcc>(
1012 uArch->getInstruction(
1013 xegpu::uArch::InstructionKind::SubgroupMatrixMultiplyAcc)));
1014 if (!dpasInst)
1015 return rewriter.notifyMatchFailure(op,
1016 "DPAS not supported by target uArch");
1017
1018 auto checkSupportedTypes = [&](VectorType vecTy,
1019 xegpu::uArch::MMAOpndKind kind) -> bool {
1020 auto supported = dpasInst->getSupportedTypes(*ctxt, kind);
1021 return llvm::find(supported, vecTy.getElementType()) != supported.end();
1022 };
1023
1024 if (!checkSupportedTypes(aTy, xegpu::uArch::MMAOpndKind::MatrixA))
1025 return rewriter.notifyMatchFailure(
1026 op, "A-matrix element type not supported by target uArch");
1027 if (!checkSupportedTypes(bTy, xegpu::uArch::MMAOpndKind::MatrixB))
1028 return rewriter.notifyMatchFailure(
1029 op, "B-matrix element type not supported by target uArch");
1030 // NOTE: Supported types for MatrixC and MatrixD are identical
1031 if (!checkSupportedTypes(resultType, xegpu::uArch::MMAOpndKind::MatrixD))
1032 return rewriter.notifyMatchFailure(
1033 op, "result/accumulator element type not supported by target uArch");
1034
1035 xevm::ElemType precATy = encodePrecision(aTy.getElementType());
1036 xevm::ElemType precBTy = encodePrecision(bTy.getElementType());
1037 Value c = op.getAcc();
1038 if (!c) {
1039 auto elementTy = resultType.getElementType();
1040 Attribute initValueAttr;
1041 if (isa<FloatType>(elementTy))
1042 initValueAttr = FloatAttr::get(elementTy, 0.0);
1043 else
1044 initValueAttr = IntegerAttr::get(elementTy, 0);
1045 c = arith::ConstantOp::create(
1046 rewriter, loc, DenseElementsAttr::get(resultType, initValueAttr));
1047 }
1048
1049 Value aVec = op.getLhs();
1050 Value bVec = op.getRhs();
1051 auto cvecty = cast<VectorType>(c.getType());
1052 xevm::ElemType precCTy = encodePrecision(cvecty.getElementType());
1053 xevm::ElemType precDTy = encodePrecision(resultType.getElementType());
1054 VectorType cNty =
1055 VectorType::get(cvecty.getNumElements(), cvecty.getElementType());
1056 if (cvecty != cNty)
1057 c = vector::ShapeCastOp::create(rewriter, loc, cNty, c);
1058 Value dpasRes = xevm::MMAOp::create(
1059 rewriter, loc, cNty, aVec, bVec, c,
1060 xevm::MMAShapeAttr::get(ctxt, cvecty.getNumElements(), executionSize,
1061 systolicDepth *
1062 getNumOperandsPerDword(precATy)),
1063 xevm::MMATypesAttr::get(ctxt, precDTy, precATy, precBTy, precCTy));
1064 if (cvecty != cNty)
1065 dpasRes = vector::ShapeCastOp::create(rewriter, loc, resultType, dpasRes);
1066 rewriter.replaceOp(op, dpasRes);
1067 return success();
1068 }
1069};
1070
1071static std::optional<LLVM::AtomicBinOp>
1072matchSimpleAtomicOp(arith::AtomicRMWKind arithKind) {
1073 switch (arithKind) {
1074 case arith::AtomicRMWKind::addf:
1075 return LLVM::AtomicBinOp::fadd;
1076 case arith::AtomicRMWKind::addi:
1077 return LLVM::AtomicBinOp::add;
1078 case arith::AtomicRMWKind::assign:
1079 return LLVM::AtomicBinOp::xchg;
1080 case arith::AtomicRMWKind::maximumf:
1081 return LLVM::AtomicBinOp::fmax;
1082 case arith::AtomicRMWKind::maxs:
1083 return LLVM::AtomicBinOp::max;
1084 case arith::AtomicRMWKind::maxu:
1085 return LLVM::AtomicBinOp::umax;
1086 case arith::AtomicRMWKind::minimumf:
1087 return LLVM::AtomicBinOp::fmin;
1088 case arith::AtomicRMWKind::mins:
1089 return LLVM::AtomicBinOp::min;
1090 case arith::AtomicRMWKind::minu:
1091 return LLVM::AtomicBinOp::umin;
1092 case arith::AtomicRMWKind::ori:
1093 return LLVM::AtomicBinOp::_or;
1094 case arith::AtomicRMWKind::andi:
1095 return LLVM::AtomicBinOp::_and;
1096 default:
1097 return std::nullopt;
1098 }
1099}
1100
1101class AtomicRMWToXeVMPattern : public OpConversionPattern<xegpu::AtomicRMWOp> {
1102 using OpConversionPattern::OpConversionPattern;
1103 LogicalResult
1104 matchAndRewrite(xegpu::AtomicRMWOp op, xegpu::AtomicRMWOp::Adaptor adaptor,
1105 ConversionPatternRewriter &rewriter) const override {
1106 auto loc = op.getLoc();
1107 auto ctxt = rewriter.getContext();
1108 auto tdesc = op.getTensorDesc().getType();
1109 auto ptrTypeLLVM = LLVM::LLVMPointerType::get(
1110 ctxt, getNumericXeVMAddrSpace(tdesc.getMemorySpace()));
1111 Value basePtrI64 = arith::IndexCastOp::create(
1112 rewriter, loc, rewriter.getI64Type(), adaptor.getTensorDesc());
1113 Value basePtrLLVM =
1114 LLVM::IntToPtrOp::create(rewriter, loc, ptrTypeLLVM, basePtrI64);
1115 VectorType srcOrDstVecTy = cast<VectorType>(op.getValue().getType());
1116 VectorType srcOrDstFlatVecTy = VectorType::get(
1117 srcOrDstVecTy.getNumElements(), srcOrDstVecTy.getElementType());
1118 Value srcFlatVec = vector::ShapeCastOp::create(
1119 rewriter, loc, srcOrDstFlatVecTy, op.getValue());
1120 auto atomicKind = matchSimpleAtomicOp(op.getKind());
1121 assert(atomicKind.has_value());
1122 Value resVec = srcFlatVec;
1123 for (int i = 0; i < srcOrDstVecTy.getNumElements(); i++) {
1124 auto val = vector::ExtractOp::create(rewriter, loc, resVec, i);
1125 Value idx = LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(),
1126 rewriter.getIndexAttr(i));
1127 Value currPtr =
1128 LLVM::GEPOp::create(rewriter, loc, ptrTypeLLVM,
1129 srcOrDstVecTy.getElementType(), basePtrLLVM, idx);
1130 Value newVal =
1131 LLVM::AtomicRMWOp::create(rewriter, loc, atomicKind.value(), currPtr,
1132 val, LLVM::AtomicOrdering::seq_cst);
1133 resVec = vector::InsertOp::create(rewriter, loc, newVal, resVec, i);
1134 }
1135 rewriter.replaceOp(op, resVec);
1136 return success();
1137 }
1138};
1139
1140class DpasMxToXeVMPattern : public OpConversionPattern<xegpu::DpasMxOp> {
1141 using OpConversionPattern::OpConversionPattern;
1142 LogicalResult
1143 matchAndRewrite(xegpu::DpasMxOp op, xegpu::DpasMxOp::Adaptor adaptor,
1144 ConversionPatternRewriter &rewriter) const override {
1145 auto loc = op.getLoc();
1146 auto ctxt = rewriter.getContext();
1147 auto aTy = op.getA().getType();
1148 auto bTy = op.getB().getType();
1149 auto resVecTy =
1150 cast<VectorType>(getTypeConverter()->convertType(op.getType()));
1151
1152 auto chipStr = xegpu::getChipStr(op);
1153 if (!chipStr)
1154 return rewriter.notifyMatchFailure(op, "cannot determine target chip");
1155
1156 const auto *uArch = xegpu::uArch::getUArch(*chipStr);
1157 if (!uArch)
1158 return rewriter.notifyMatchFailure(op, "unsupported target uArch");
1159
1160 // TODO: Add supported shape check
1161
1162 xevm::ElemType precATy = encodePrecision(aTy.getElementType());
1163 xevm::ElemType precBTy = encodePrecision(bTy.getElementType());
1164 Value c = adaptor.getAcc();
1165 if (!c) {
1166 auto elementTy = resVecTy.getElementType();
1167 Attribute initValueAttr;
1168 if (isa<FloatType>(elementTy))
1169 initValueAttr = FloatAttr::get(elementTy, 0.0);
1170 else
1171 initValueAttr = IntegerAttr::get(elementTy, 0);
1172 c = arith::ConstantOp::create(
1173 rewriter, loc, DenseElementsAttr::get(resVecTy, initValueAttr));
1174 }
1175
1176 Value aVec = adaptor.getA();
1177 Value bVec = adaptor.getB();
1178 auto aVecTy = cast<VectorType>(aVec.getType());
1179 auto bVecTy = cast<VectorType>(bVec.getType());
1180 if (aVecTy.getElementTypeBitWidth() == 4)
1181 aVec = vector::BitCastOp::create(
1182 rewriter, loc,
1183 VectorType::get(aVecTy.getNumElements() / 2, rewriter.getI8Type()),
1184 aVec);
1185 if (bVecTy.getElementTypeBitWidth() == 4)
1186 bVec = vector::BitCastOp::create(
1187 rewriter, loc,
1188 VectorType::get(bVecTy.getNumElements() / 2, rewriter.getI8Type()),
1189 bVec);
1190 auto cVecTy = cast<VectorType>(c.getType());
1191 xevm::ElemType precCTy = encodePrecision(cVecTy.getElementType());
1192 xevm::ElemType precDTy = encodePrecision(resVecTy.getElementType());
1193 Value scaleA = adaptor.getScaleA();
1194 Value scaleB = adaptor.getScaleB();
1195 Value dpasMxRes = xevm::MMAMxOp::create(
1196 rewriter, loc, resVecTy, aVec, bVec, scaleA, scaleB, c,
1197 xevm::MMAShapeAttr::get(ctxt, cVecTy.getNumElements(), executionSize,
1198 systolicDepth *
1199 getNumOperandsPerDword(precATy)),
1200 xevm::MMATypesAttr::get(ctxt, precDTy, precATy, precBTy, precCTy));
1201 rewriter.replaceOp(op, dpasMxRes);
1202 return success();
1203 }
1204};
1205
1206//===----------------------------------------------------------------------===//
1207// arith.extf / arith.truncf to xevm.extf / xevm.truncf
1208//===----------------------------------------------------------------------===//
1209//
1210// Micro-scaling (MX) GEMM lowering breaks arith.scaling_extf/scaling_truncf
1211// into plain arith.extf/arith.truncf whose narrow side uses one of the MX float
1212// formats (f8E5M2, f8E4M3FN or f4E2M1FN). These narrow floats have no native
1213// LLVM support, so the conversions are mapped onto the dedicated xevm.extf /
1214// xevm.truncf ops which lower to hardware builtins. The f8E8M0FNU scale type is
1215// intentionally not handled here: it is expanded into integer arithmetic by
1216// arith-expand before this pass runs.
1217
1218// xevm.extf / xevm.truncf only convert between the MX narrow floats and
1219// f16/bf16, and the underlying builtins operate on exactly 16 f16/bf16 values.
1220static constexpr int64_t kXeVMExtfTruncfNumElems = 16;
1221
1222// Maps a narrow MX float element type to the matching xevm.extf source enum.
1223static std::optional<xevm::ExtfSrcElemTypes> getExtfNarrowType(Type etype) {
1224 if (isa<Float8E5M2Type>(etype))
1225 return xevm::ExtfSrcElemTypes::BF8;
1226 if (isa<Float8E4M3FNType>(etype))
1227 return xevm::ExtfSrcElemTypes::F8;
1228 if (isa<Float4E2M1FNType>(etype))
1229 return xevm::ExtfSrcElemTypes::E2M1;
1230 return std::nullopt;
1231}
1232
1233// Maps a narrow MX float element type to the matching xevm.truncf dest enum.
1234static std::optional<xevm::TruncfDstElemTypes> getTruncfNarrowType(Type etype) {
1235 if (isa<Float8E5M2Type>(etype))
1236 return xevm::TruncfDstElemTypes::BF8;
1237 if (isa<Float8E4M3FNType>(etype))
1238 return xevm::TruncfDstElemTypes::F8;
1239 if (isa<Float4E2M1FNType>(etype))
1240 return xevm::TruncfDstElemTypes::E2M1;
1241 return std::nullopt;
1242}
1243
1244// Returns true if `op` is an arith.extf that can be lowered to xevm.extf, i.e.
1245// a rank-1 widening from an MX narrow float to a 16-element f16/bf16 vector.
1246static bool isXeVMExtf(arith::ExtFOp op) {
1247 auto srcTy = dyn_cast<VectorType>(op.getIn().getType());
1248 auto dstTy = dyn_cast<VectorType>(op.getType());
1249 if (!srcTy || !dstTy || srcTy.getRank() != 1 || dstTy.getRank() != 1)
1250 return false;
1251 if (dstTy.getNumElements() != kXeVMExtfTruncfNumElems)
1252 return false;
1253 Type dstETy = dstTy.getElementType();
1254 if (!dstETy.isF16() && !dstETy.isBF16())
1255 return false;
1256 return getExtfNarrowType(srcTy.getElementType()).has_value();
1257}
1258
1259// Returns true if `op` is an arith.truncf that can be lowered to xevm.truncf,
1260// i.e. a rank-1 truncation from a 16-element f16/bf16 vector to an MX narrow
1261// float.
1262static bool isXeVMTruncf(arith::TruncFOp op) {
1263 auto srcTy = dyn_cast<VectorType>(op.getIn().getType());
1264 auto dstTy = dyn_cast<VectorType>(op.getType());
1265 if (!srcTy || !dstTy || srcTy.getRank() != 1 || dstTy.getRank() != 1)
1266 return false;
1267 if (srcTy.getNumElements() != kXeVMExtfTruncfNumElems)
1268 return false;
1269 Type srcETy = srcTy.getElementType();
1270 if (!srcETy.isF16() && !srcETy.isBF16())
1271 return false;
1272 return getTruncfNarrowType(dstTy.getElementType()).has_value();
1273}
1274
1275class ExtfToXeVMPattern : public OpConversionPattern<arith::ExtFOp> {
1276 using OpConversionPattern::OpConversionPattern;
1277 LogicalResult
1278 matchAndRewrite(arith::ExtFOp op, OpAdaptor adaptor,
1279 ConversionPatternRewriter &rewriter) const override {
1280 if (!isXeVMExtf(op))
1281 return rewriter.notifyMatchFailure(op, "not a xevm.extf compatible extf");
1282 Location loc = op.getLoc();
1283 MLIRContext *ctx = op.getContext();
1284 auto srcVecTy = cast<VectorType>(op.getIn().getType());
1285 auto dstVecTy = cast<VectorType>(op.getType());
1286 xevm::ExtfSrcElemTypes srcEnum =
1287 *getExtfNarrowType(srcVecTy.getElementType());
1288 xevm::ExtfDstElemTypes dstEnum = dstVecTy.getElementType().isF16()
1289 ? xevm::ExtfDstElemTypes::F16
1290 : xevm::ExtfDstElemTypes::BF16;
1291 // The narrow float operand has already been type-converted to an integer
1292 // vector of the same bit width (i4 for fp4, i8 for fp8). xevm.extf takes
1293 // the values packed into an i8 vector, so re-pack fp4 (i4) operands.
1294 Value src = adaptor.getIn();
1295 auto convSrcTy = cast<VectorType>(src.getType());
1296 if (convSrcTy.getElementTypeBitWidth() == 4)
1297 src = vector::BitCastOp::create(
1298 rewriter, loc,
1299 VectorType::get(convSrcTy.getNumElements() / 2, rewriter.getI8Type()),
1300 src);
1301 Type resTy = getTypeConverter()->convertType(dstVecTy);
1302 Value res = xevm::ExtfOp::create(
1303 rewriter, loc, resTy, src, xevm::ExtfSrcElemTypeAttr::get(ctx, srcEnum),
1304 xevm::ExtfDstElemTypeAttr::get(ctx, dstEnum));
1305 rewriter.replaceOp(op, res);
1306 return success();
1307 }
1308};
1309
1310class TruncfToXeVMPattern : public OpConversionPattern<arith::TruncFOp> {
1311 using OpConversionPattern::OpConversionPattern;
1312 LogicalResult
1313 matchAndRewrite(arith::TruncFOp op, OpAdaptor adaptor,
1314 ConversionPatternRewriter &rewriter) const override {
1315 if (!isXeVMTruncf(op))
1316 return rewriter.notifyMatchFailure(op,
1317 "not a xevm.truncf compatible truncf");
1318 Location loc = op.getLoc();
1319 MLIRContext *ctx = op.getContext();
1320 auto srcVecTy = cast<VectorType>(op.getIn().getType());
1321 auto dstVecTy = cast<VectorType>(op.getType());
1322 xevm::TruncfSrcElemTypes srcEnum = srcVecTy.getElementType().isF16()
1323 ? xevm::TruncfSrcElemTypes::F16
1324 : xevm::TruncfSrcElemTypes::BF16;
1325 xevm::TruncfDstElemTypes dstEnum =
1326 *getTruncfNarrowType(dstVecTy.getElementType());
1327 // xevm.truncf produces the narrow floats packed into an i8 vector.
1328 int64_t numNarrowBits =
1329 dstVecTy.getNumElements() * dstVecTy.getElementTypeBitWidth();
1330 Type packedTy = VectorType::get(numNarrowBits / 8, rewriter.getI8Type());
1331 Value res =
1332 xevm::TruncfOp::create(rewriter, loc, packedTy, adaptor.getIn(),
1333 xevm::TruncfSrcElemTypeAttr::get(ctx, srcEnum),
1334 xevm::TruncfDstElemTypeAttr::get(ctx, dstEnum));
1335 // Re-shape to the type-converted result type (i4 vector for fp4).
1336 Type resTy = getTypeConverter()->convertType(dstVecTy);
1337 if (res.getType() != resTy)
1338 res = vector::BitCastOp::create(rewriter, loc, resTy, res);
1339 rewriter.replaceOp(op, res);
1340 return success();
1341 }
1342};
1343
1344//===----------------------------------------------------------------------===//
1345// Pass Definition
1346//===----------------------------------------------------------------------===//
1347
1348struct ConvertXeGPUToXeVMPass
1349 : public impl::ConvertXeGPUToXeVMPassBase<ConvertXeGPUToXeVMPass> {
1350 using Base::Base;
1351
1352 void runOnOperation() override {
1353 MLIRContext *context = &getContext();
1354
1355 // XeVM type converter is based on LLVM type converter with the
1356 // following customizations.
1357 // First, type conversion rules are added for xegpu custom types,
1358 // TensorDescType and MemDescType.
1359 // Second, MemRefType is lowered to single integer type
1360 // Third, VectorType of single element or 0D is converted to vector
1361 // element type. Otherwise, vector type is flatten to 1D.
1362 LowerToLLVMOptions options(context);
1363 options.overrideIndexBitwidth(this->use64bitIndex ? 64 : 32);
1364 LLVMTypeConverter typeConverter(context, options);
1365
1366 Type xevmIndexType = typeConverter.convertType(IndexType::get(context));
1367 Type i32Type = IntegerType::get(context, 32);
1368 typeConverter.addConversion([&](VectorType type) -> Type {
1369 auto elemType = typeConverter.convertType(type.getElementType());
1370 // If the vector rank is 0 or has a single element, return the element
1371 unsigned rank = type.getRank();
1372 if (rank == 0 || type.getNumElements() == 1)
1373 return elemType;
1374 // Otherwise, convert the vector to a flat vector type.
1375 int64_t sum = llvm::product_of(type.getShape());
1376 return VectorType::get(sum, elemType);
1377 });
1378 typeConverter.addConversion([&](xegpu::TensorDescType type) -> Type {
1379 if (type.getRank() == 1)
1380 return xevmIndexType;
1381 return VectorType::get(8, i32Type);
1382 });
1383 // SLM access related type conversions.
1384 // TODO: LLVM DLTI provides clean way of representing different pointer size
1385 // based on address space. Currently pointer size of SLM access is hard
1386 // coded to 32bit. Update to use DLTI when switching overall XeGPU lowering
1387 // to use DLTI instead of use64bitIndex option used above.
1388
1389 // Convert MemDescType into i32 for SLM
1390 typeConverter.addConversion(
1391 [&](xegpu::MemDescType type) -> Type { return i32Type; });
1392
1393 typeConverter.addConversion([&](MemRefType type) -> Type {
1394 return isSharedMemRef(type) ? i32Type : xevmIndexType;
1395 });
1396
1397 // LLVM type converter puts unrealized casts for the following cases:
1398 // add materialization casts to handle them.
1399
1400 // Materialization to convert memref to i64 or i32 depending on global/SLM
1401 // Applies only to target materialization.
1402 // Note: int type to memref materialization is not required as xegpu ops
1403 // currently do not produce memrefs as result.
1404 auto memrefToIntMaterializationCast = [](OpBuilder &builder, Type type,
1405 ValueRange inputs,
1406 Location loc) -> Value {
1407 if (inputs.size() != 1)
1408 return {};
1409 auto input = inputs.front();
1410 if (auto memrefTy = dyn_cast<MemRefType>(input.getType())) {
1411 unsigned rank = memrefTy.getRank();
1412 Type indexType = builder.getIndexType();
1413
1414 int64_t intOffsets;
1415 SmallVector<int64_t> intStrides;
1416 Value addr;
1417 Value offset;
1418 if (succeeded(memrefTy.getStridesAndOffset(intStrides, intOffsets)) &&
1419 ShapedType::isStatic(intOffsets)) {
1420 addr = memref::ExtractAlignedPointerAsIndexOp::create(builder, loc,
1421 input);
1422 offset = arith::ConstantOp::create(builder, loc,
1423 builder.getIndexAttr(intOffsets));
1424 } else {
1425
1426 // Result types: [base_memref, offset, stride0, stride1, ...,
1427 // strideN-1, size0, size1, ..., sizeN-1]
1428 SmallVector<Type> resultTypes{
1429 MemRefType::get({}, memrefTy.getElementType(),
1430 MemRefLayoutAttrInterface(),
1431 memrefTy.getMemorySpace()),
1432 indexType};
1433 // strides + sizes
1434 resultTypes.append(2 * rank, indexType);
1435
1436 auto meta = memref::ExtractStridedMetadataOp::create(
1437 builder, loc, resultTypes, input);
1438
1439 addr = memref::ExtractAlignedPointerAsIndexOp::create(
1440 builder, loc, meta.getBaseBuffer());
1441 offset = meta.getOffset();
1442 }
1443
1444 auto addrCasted =
1445 arith::IndexCastUIOp::create(builder, loc, type, addr);
1446 auto offsetCasted =
1447 arith::IndexCastUIOp::create(builder, loc, type, offset);
1448
1449 // Compute the final address: base address + byte offset
1450 auto byteSize = arith::ConstantOp::create(
1451 builder, loc, type,
1452 builder.getIntegerAttr(type,
1453 memrefTy.getElementTypeBitWidth() / 8));
1454 auto byteOffset =
1455 arith::MulIOp::create(builder, loc, offsetCasted, byteSize);
1456 auto addrWithOffset =
1457 arith::AddIOp::create(builder, loc, addrCasted, byteOffset);
1458
1459 return addrWithOffset.getResult();
1460 }
1461 return {};
1462 };
1463
1464 // Materialization to convert ui64 to i64
1465 // Applies only to target materialization.
1466 // Note: i64 to ui64 materialization is not required as xegpu ops
1467 // currently do not produce ui64 as result.
1468 auto ui64ToI64MaterializationCast = [](OpBuilder &builder, Type type,
1469 ValueRange inputs,
1470 Location loc) -> Value {
1471 if (inputs.size() != 1)
1472 return {};
1473 auto input = inputs.front();
1474 if (input.getType() == builder.getIntegerType(64, false)) {
1475 Value cast =
1476 index::CastUOp::create(builder, loc, builder.getIndexType(), input)
1477 .getResult();
1478 return arith::IndexCastUIOp::create(builder, loc, type, cast)
1479 .getResult();
1480 }
1481 return {};
1482 };
1483
1484 // Materialization to convert ui32 to i32
1485 // Applies only to target materialization.
1486 // Note: i32 to ui32 materialization is not required as xegpu ops
1487 // currently do not produce ui32 as result.
1488 auto ui32ToI32MaterializationCast = [](OpBuilder &builder, Type type,
1489 ValueRange inputs,
1490 Location loc) -> Value {
1491 if (inputs.size() != 1)
1492 return {};
1493 auto input = inputs.front();
1494 if (input.getType() == builder.getIntegerType(32, false)) {
1495 Value cast =
1496 index::CastUOp::create(builder, loc, builder.getIndexType(), input)
1497 .getResult();
1498 return arith::IndexCastUIOp::create(builder, loc, type, cast)
1499 .getResult();
1500 }
1501 return {};
1502 };
1503
1504 // Materialization to convert between vector types
1505 // - Add shape cast for different shapes
1506 // - Add bitcast for different element types
1507 // Applies to both source and target materialization.
1508 auto vectorToVectorMaterializationCast = [](OpBuilder &builder, Type type,
1509 ValueRange inputs,
1510 Location loc) -> Value {
1511 if (inputs.size() != 1)
1512 return {};
1513 auto input = inputs.front();
1514 if (auto vecTy = dyn_cast<VectorType>(input.getType())) {
1515 if (auto targetVecTy = dyn_cast<VectorType>(type)) {
1516 Value cast = input;
1517 // If the target type has a different shape, add a shape cast
1518 // If the target type has a different element type, add a bitcast
1519 if (targetVecTy.getShape() != vecTy.getShape()) {
1520 cast = vector::ShapeCastOp::create(
1521 builder, loc,
1522 VectorType::get(targetVecTy.getShape(),
1523 vecTy.getElementType()),
1524 cast)
1525 .getResult();
1526 }
1527 if (targetVecTy.getElementType() != vecTy.getElementType()) {
1528 cast = vector::BitCastOp::create(builder, loc, targetVecTy, cast)
1529 .getResult();
1530 }
1531 return cast;
1532 }
1533 }
1534 return {};
1535 };
1536
1537 // Materialization to convert
1538 // - single element vector to single element of vector element type
1539 // Applies only to target materialization.
1540 auto vectorToSingleElementMaterializationCast =
1541 [](OpBuilder &builder, Type type, ValueRange inputs,
1542 Location loc) -> Value {
1543 if (inputs.size() != 1)
1544 return {};
1545 auto input = inputs.front();
1546 if (auto vecTy = dyn_cast<VectorType>(input.getType())) {
1547 // Source needs to be single element vector
1548 auto rank = vecTy.getRank();
1549 if (rank != 0 && vecTy.getNumElements() != 1)
1550 return {};
1551 auto inElemTy = vecTy.getElementType();
1552 // extract scalar
1553 Value cast = input;
1554 if (rank == 0) {
1555 cast = vector::ExtractOp::create(builder, loc, cast, {}).getResult();
1556 } else {
1557 cast = vector::ExtractOp::create(builder, loc, cast,
1558 SmallVector<int64_t>(rank, 0))
1559 .getResult();
1560 }
1561 // Extracted element type may need conversion
1562 // Two cases
1563 // 1. Index type to integer type
1564 // 2. Other element type mismatch
1565 if (inElemTy.isIndex()) {
1566 cast = arith::IndexCastUIOp::create(builder, loc, type, cast)
1567 .getResult();
1568 } else if (inElemTy != type) {
1569 cast = arith::BitcastOp::create(builder, loc, type, cast).getResult();
1570 }
1571 return cast;
1572 }
1573 return {};
1574 };
1575
1576 // Materialization to convert
1577 // - single element of vector element type to single element vector
1578 // If result type of original op is single element vector and lowered type
1579 // is scalar. This materialization cast creates a single element vector by
1580 // First convert element type if needed and then broadcast to single
1581 // element vector.
1582 // Applies only to source materialization.
1583 auto singleElementToVectorMaterializationCast =
1584 [](OpBuilder &builder, Type type, ValueRange inputs,
1585 Location loc) -> Value {
1586 if (inputs.size() != 1)
1587 return {};
1588 auto input = inputs.front();
1589 auto inTy = input.getType();
1590 if (!inTy.isIntOrFloat())
1591 return {};
1592 // If the target type is a vector of rank 0 or single element vector
1593 // of element type matching input type, broadcast input to target type.
1594 if (auto vecTy = dyn_cast<VectorType>(type)) {
1595 if (vecTy.getRank() != 0 && vecTy.getNumElements() != 1)
1596 return {};
1597 auto outElemTy = vecTy.getElementType();
1598 Value cast = input;
1599 if (outElemTy.isIndex()) {
1600 cast = arith::IndexCastUIOp::create(builder, loc,
1601 builder.getIndexType(), cast)
1602 .getResult();
1603 } else if (inTy != outElemTy) {
1604 cast = arith::BitcastOp::create(builder, loc, outElemTy, cast)
1605 .getResult();
1606 }
1607 return vector::BroadcastOp::create(builder, loc, vecTy, cast)
1608 .getResult();
1609 }
1610 return {};
1611 };
1612 typeConverter.addSourceMaterialization(
1613 singleElementToVectorMaterializationCast);
1614 typeConverter.addSourceMaterialization(vectorToVectorMaterializationCast);
1615 typeConverter.addTargetMaterialization(memrefToIntMaterializationCast);
1616 typeConverter.addTargetMaterialization(ui32ToI32MaterializationCast);
1617 typeConverter.addTargetMaterialization(ui64ToI64MaterializationCast);
1618 typeConverter.addTargetMaterialization(
1619 vectorToSingleElementMaterializationCast);
1620 typeConverter.addTargetMaterialization(vectorToVectorMaterializationCast);
1621 ConversionTarget target(*context);
1622 target.addLegalDialect<xevm::XeVMDialect, LLVM::LLVMDialect,
1623 vector::VectorDialect, arith::ArithDialect,
1624 memref::MemRefDialect, gpu::GPUDialect,
1625 index::IndexDialect>();
1626 target.addIllegalDialect<xegpu::XeGPUDialect>();
1627 // arith.extf/arith.truncf between MX narrow floats and f16/bf16 are routed
1628 // to xevm.extf/xevm.truncf; all other arith float casts stay legal.
1629 target.addDynamicallyLegalOp<arith::ExtFOp>(
1630 [](arith::ExtFOp op) { return !isXeVMExtf(op); });
1631 target.addDynamicallyLegalOp<arith::TruncFOp>(
1632 [](arith::TruncFOp op) { return !isXeVMTruncf(op); });
1633
1634 RewritePatternSet patterns(context);
1635 populateXeGPUToXeVMConversionPatterns(typeConverter, patterns);
1637 patterns, target);
1638 if (failed(applyPartialConversion(getOperation(), target,
1639 std::move(patterns))))
1640 signalPassFailure();
1641 }
1642};
1643} // namespace
1644
1645//===----------------------------------------------------------------------===//
1646// Pattern Population
1647//===----------------------------------------------------------------------===//
1649 const LLVMTypeConverter &typeConverter, RewritePatternSet &patterns) {
1650 patterns.add<CreateNdDescToXeVMPattern,
1651 LoadStorePrefetchNdToXeVMPattern<xegpu::LoadNdOp>,
1652 LoadStorePrefetchNdToXeVMPattern<xegpu::StoreNdOp>,
1653 LoadStorePrefetchNdToXeVMPattern<xegpu::PrefetchNdOp>>(
1654 typeConverter, patterns.getContext());
1655 patterns.add<AtomicRMWToXeVMPattern, PrefetchToXeVMPattern,
1656 LoadStoreToXeVMPattern<xegpu::LoadGatherOp>,
1657 LoadStoreToXeVMPattern<xegpu::StoreScatterOp>>(
1658 typeConverter, patterns.getContext());
1659 patterns.add<LoadStoreMatrixToXeVMPattern<xegpu::LoadMatrixOp>,
1660 LoadStoreMatrixToXeVMPattern<xegpu::StoreMatrixOp>,
1661 CreateMemDescOpPattern>(typeConverter, patterns.getContext());
1662 patterns.add<FenceToXeVMPattern, DpasToXeVMPattern>(typeConverter,
1663 patterns.getContext());
1664 patterns.add<DpasMxToXeVMPattern>(typeConverter, patterns.getContext());
1665 patterns.add<ExtfToXeVMPattern, TruncfToXeVMPattern>(typeConverter,
1666 patterns.getContext());
1667}
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 setAttr(StringAttr name, Attribute value)
If the an attribute exists with the specified name, change it to the new value.
Definition Operation.h:607
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:717
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
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.
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)