MLIR 24.0.0git
XeVMToLLVM.cpp
Go to the documentation of this file.
1//===-- XeVMToLLVM.cpp - XeVM to LLVM 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
10
16#include "mlir/Pass/Pass.h"
17#include "mlir/Support/LLVM.h"
18#include "llvm/ADT/ArrayRef.h"
19#include "llvm/Support/FormatVariadic.h"
20
22#include "mlir/IR/Matchers.h"
23#include "mlir/IR/Types.h"
25
26#include "llvm/ADT/TypeSwitch.h"
27
28namespace mlir {
29#define GEN_PASS_DEF_CONVERTXEVMTOLLVMPASS
30#include "mlir/Conversion/Passes.h.inc"
31} // namespace mlir
32
33using namespace mlir;
34using namespace xevm;
35
36namespace {
37
38struct LLVMFuncAttributeOptions {
39 bool isConvergent = false;
40 bool isNoUnwind = false;
41 bool isWillReturn = false;
42 LLVM::MemoryEffectsAttr memEffectsAttr{};
43};
44static constexpr LLVMFuncAttributeOptions noUnwindAttrs = {
45 false, true, false, {}};
46static constexpr LLVMFuncAttributeOptions noUnwindWillReturnAttrs = {
47 false, true, true, {}};
48static constexpr LLVMFuncAttributeOptions convergentNoUnwindWillReturnAttrs = {
49 true, true, true, {}};
50
51std::string getTypeMangling(Type ty, bool isUnsigned = false) {
53 .Case([isUnsigned](VectorType ty) -> std::string {
54 return "Dv" + std::to_string(ty.getNumElements()) + "_" +
55 getTypeMangling(ty.getElementType(), isUnsigned);
56 })
57 .Case([](Float16Type) -> std::string { return "Dh"; })
58 .Case([](Float32Type) -> std::string { return "f"; })
59 .Case([](Float64Type) -> std::string { return "d"; })
60 .Case([isUnsigned](IntegerType ty) -> std::string {
61 switch (ty.getWidth()) {
62 case 8:
63 return isUnsigned ? "h" : "c";
64 case 16:
65 return isUnsigned ? "t" : "s";
66 case 32:
67 return isUnsigned ? "j" : "i";
68 case 64:
69 return isUnsigned ? "m" : "l";
70 default:
71 llvm_unreachable("unhandled integer type");
72 }
73 })
74 .DefaultUnreachable("unhandled type for mangling");
75}
76
77std::string mangle(StringRef baseName, ArrayRef<Type> types,
78 ArrayRef<bool> isUnsigned = {}) {
79 assert((isUnsigned.empty() || isUnsigned.size() == types.size()) &&
80 "Signedness info doesn't match");
81 std::string s;
82 llvm::raw_string_ostream os(s);
83 llvm::SmallDenseMap<Type, unsigned> substitutions;
84 os << "_Z" << baseName.size() << baseName;
85 for (auto [idx, type] : llvm::enumerate(types)) {
86 auto it = substitutions.find(type);
87 if (it != substitutions.end()) {
88 os << "S";
89 // First substitution is `S_`, second is `S0_`, and so on.
90 if (unsigned firstIdx = it->getSecond(); firstIdx > 0)
91 os << firstIdx - 1;
92 os << "_";
93 } else {
94 if (!type.isIntOrFloat())
95 substitutions[type] = substitutions.size();
96 os << getTypeMangling(type, isUnsigned.empty() ? false : isUnsigned[idx]);
97 }
98 }
99 return os.str();
100}
101
102// Returns the mangling of `ty` used to name an overloaded `llvm.genx.GenISA.*`
103// intrinsic: `i32`, `v8i16`, ... Note that this is IGC's own scheme for its
104// intrinsics, not the Itanium mangling used for the SPIR-V friendly and OCL
105// builtins that `mangle` above produces.
106std::string getGenISATypeMangling(Type ty) {
108 .Case([](VectorType ty) -> std::string {
109 return "v" + std::to_string(ty.getNumElements()) +
110 getGenISATypeMangling(ty.getElementType());
111 })
112 .Case([](IntegerType ty) -> std::string {
113 return "i" + std::to_string(ty.getWidth());
114 })
115 .DefaultUnreachable("unhandled type for GenISA mangling");
116}
117
118std::string builtinElemType(ElemType elemType) {
119 switch (elemType) {
120 case ElemType::BF8:
121 return "bf8";
122 case ElemType::F8:
123 return "hf8";
124 case ElemType::BF16:
125 return "bf";
126 case ElemType::F16:
127 return "hf";
128 case ElemType::F32:
129 return "f";
130 default:
131 return stringifyElemType(elemType).str();
132 }
133}
134
135static int32_t getL1CacheControl(LoadCacheControl cc) {
136 int32_t control = 0;
137 switch (cc) {
138 case LoadCacheControl::USE_DEFAULT:
139 control = -1;
140 break;
141 case LoadCacheControl::L1C_L2UC_L3UC:
142 case LoadCacheControl::L1C_L2UC_L3C:
143 case LoadCacheControl::L1C_L2C_L3UC:
144 case LoadCacheControl::L1C_L2C_L3C:
145 control = 1;
146 break;
147 case LoadCacheControl::L1S_L2UC_L3UC:
148 case LoadCacheControl::L1S_L2UC_L3C:
149 case LoadCacheControl::L1S_L2C_L3UC:
150 case LoadCacheControl::L1S_L2C_L3C:
151 control = 2;
152 break;
153 case LoadCacheControl::INVALIDATE_READ:
154 control = 3;
155 break;
156 default:
157 break;
158 }
159 return control;
160}
161
162static int32_t getL1CacheControl(StoreCacheControl cc) {
163 int32_t control = 0;
164 switch (cc) {
165 case StoreCacheControl::USE_DEFAULT:
166 control = -1;
167 break;
168 case StoreCacheControl::L1WT_L2UC_L3UC:
169 case StoreCacheControl::L1WT_L2UC_L3WB:
170 case StoreCacheControl::L1WT_L2WB_L3UC:
171 case StoreCacheControl::L1WT_L2WB_L3WB:
172 control = 1;
173 break;
174 case StoreCacheControl::L1WB_L2UC_L3UC:
175 case StoreCacheControl::L1WB_L2WB_L3UC:
176 case StoreCacheControl::L1WB_L2UC_L3WB:
177 control = 2;
178 break;
179 case StoreCacheControl::L1S_L2UC_L3UC:
180 case StoreCacheControl::L1S_L2UC_L3WB:
181 case StoreCacheControl::L1S_L2WB_L3UC:
182 case StoreCacheControl::L1S_L2WB_L3WB:
183 control = 3;
184 break;
185 default:
186 break;
187 }
188 return control;
189}
190
191static int32_t getL3CacheControl(LoadCacheControl cc) {
192 int32_t control = 0;
193 switch (cc) {
194 case LoadCacheControl::USE_DEFAULT:
195 control = -1;
196 break;
197 case LoadCacheControl::L1UC_L2UC_L3C:
198 case LoadCacheControl::L1UC_L2C_L3C:
199 case LoadCacheControl::L1C_L2UC_L3C:
200 case LoadCacheControl::L1C_L2C_L3C:
201 case LoadCacheControl::L1S_L2UC_L3C:
202 case LoadCacheControl::L1S_L2C_L3C:
203 control = 1;
204 break;
205 case LoadCacheControl::INVALIDATE_READ:
206 control = 3;
207 break;
208 default:
209 break;
210 }
211 return control;
212}
213
214static int32_t getL3CacheControl(StoreCacheControl cc) {
215 int32_t control = 0;
216 switch (cc) {
217 case StoreCacheControl::USE_DEFAULT:
218 control = -1;
219 break;
220 case StoreCacheControl::L1UC_L2UC_L3WB:
221 case StoreCacheControl::L1UC_L2WB_L3WB:
222 case StoreCacheControl::L1WT_L2UC_L3WB:
223 case StoreCacheControl::L1WT_L2WB_L3WB:
224 case StoreCacheControl::L1S_L2UC_L3WB:
225 case StoreCacheControl::L1S_L2WB_L3WB:
226 case StoreCacheControl::L1WB_L2UC_L3WB:
227 control = 2;
228 break;
229 default:
230 break;
231 }
232 return control;
233}
234
235static std::optional<LoadCacheControl> getCacheControl(PrefetchOp op) {
236 return op.getCacheControl();
237}
238
239static std::optional<LoadCacheControl> getCacheControl(BlockLoad2dOp op) {
240 return op.getCacheControl();
241}
242
243static std::optional<LoadCacheControl> getCacheControl(BlockLoadOp op) {
244 return op.getCacheControl();
245}
246
247static std::optional<LoadCacheControl> getCacheControl(BlockPrefetch2dOp op) {
248 return op.getCacheControl();
249}
250
251static std::optional<StoreCacheControl> getCacheControl(BlockStore2dOp op) {
252 return op.getCacheControl();
253}
254
255static std::optional<StoreCacheControl> getCacheControl(BlockStoreOp op) {
256 return op.getCacheControl();
257}
258
259static std::optional<LoadCacheControl> getCacheControl(LLVM::LoadOp op) {
260 if (op->hasAttr("cache_control")) {
261 auto attr = op->getAttrOfType<xevm::LoadCacheControlAttr>("cache_control");
262 if (!attr)
263 return std::nullopt;
264 return std::optional<LoadCacheControl>(attr.getValue());
265 }
266 return std::nullopt;
267}
268
269static std::optional<StoreCacheControl> getCacheControl(LLVM::StoreOp op) {
270 if (op->hasAttr("cache_control")) {
271 auto attr = op->getAttrOfType<xevm::StoreCacheControlAttr>("cache_control");
272 if (!attr)
273 return std::nullopt;
274 return std::optional<StoreCacheControl>(attr.getValue());
275 }
276 return std::nullopt;
277}
278
279template <typename OpType>
280int32_t getL1CacheControl(OpType op) {
281 return getL1CacheControl(*getCacheControl(op));
282}
283
284template <typename OpType>
285int32_t getL3CacheControl(OpType op) {
286 return getL3CacheControl(*getCacheControl(op));
287}
288
289template <typename OpType>
290static std::optional<ArrayAttr>
291getCacheControlMetadata(ConversionPatternRewriter &rewriter, OpType op) {
292 if (!getCacheControl(op))
293 return {};
294
295 constexpr int32_t decorationCacheControlArity{3};
296 constexpr int32_t loadCacheControlKey{6442};
297 constexpr int32_t storeCacheControlKey{6443};
298 constexpr bool isLoad = std::is_same_v<OpType, BlockLoad2dOp> ||
299 std::is_same_v<OpType, BlockPrefetch2dOp> ||
300 std::is_same_v<OpType, LLVM::LoadOp> ||
301 std::is_same_v<OpType, BlockLoadOp> ||
302 std::is_same_v<OpType, PrefetchOp>;
303
304 // If the cache control is USE_DEFAULT, then we don’t emit any metadata.
305 // Assert that if one of the L1 or L3 cache control values is USE_DEFAULT
306 // (represented as -1), then both must be USE_DEFAULT; otherwise there is a
307 // bug.
308 assert(((getL1CacheControl<OpType>(op) == -1) ==
309 (getL3CacheControl<OpType>(op) == -1)) &&
310 "If one of L1 or L3 cache control is USE_DEFAULT, both must be "
311 "USE_DEFAULT");
312
313 if (getL1CacheControl<OpType>(op) == -1 &&
314 getL3CacheControl<OpType>(op) == -1)
315 return {};
316 const int32_t controlKey{isLoad ? loadCacheControlKey : storeCacheControlKey};
318 controlKey, 0, getL1CacheControl<OpType>(op)};
320 controlKey, 1, getL3CacheControl<OpType>(op)};
321 auto arrayAttrL1 = rewriter.getI32ArrayAttr(decorationsL1);
322 auto arrayAttrL3 = rewriter.getI32ArrayAttr(decorationsL3);
323
324 SmallVector<Attribute, 2> combinedAttrs = {arrayAttrL1, arrayAttrL3};
325 return rewriter.getArrayAttr(combinedAttrs);
326}
327
328//===----------------------------------------------------------------------===//
329// Cache control annotation utilities
330//
331// Instead of attaching cache control as MLIR attributes and handling them
332// during LLVM translation, we directly emit llvm.intr.ptr.annotation op in
333// MLIR.
334//===----------------------------------------------------------------------===//
335
336/// Build one cache-control payload string per attribute.
337///
338/// Each Attribute is expected to be an ArrayAttr of 3 IntegerAttr values:
339/// [SPIR-V decoration token, cache level, cache control value]
340///
341/// A single entry produces a string like: {6442:"0,1"}
342/// where the quote characters (0x22) will appear as \22 in LLVM IR textual
343/// form.
345buildCacheControlPayloads(ArrayRef<Attribute> attrs) {
347 llvm::StringMap<bool> seen;
348
349 for (Attribute a : attrs) {
350 auto arr = dyn_cast<ArrayAttr>(a);
351 if (!arr)
352 continue;
353
354 auto vals = arr.getValue();
355 assert(vals.size() == 3 &&
356 "Expected exactly 3 integer values (Token, CacheLevel, "
357 "ControlValue) in cache control attribute.");
358
359 auto tokenAttr = dyn_cast<IntegerAttr>(vals[0]);
360 auto secondAttr = dyn_cast<IntegerAttr>(vals[1]);
361 auto thirdAttr = dyn_cast<IntegerAttr>(vals[2]);
362
363 if (!tokenAttr || !secondAttr || !thirdAttr)
364 continue;
365
366 // Produce: {SPIR-V decoration token:"L1 cache control,L3 cache control"}
367 // The quote char (0x22) is embedded literally; LLVM IR prints it as \22.
368 std::string entry =
369 llvm::formatv("{{{0}:\"{1},{2}\"}", tokenAttr.getValue().getZExtValue(),
370 secondAttr.getValue().getZExtValue(),
371 thirdAttr.getValue().getZExtValue());
372
373 // Deduplicate identical annotations.
374 if (!seen.insert({entry, true}).second)
375 continue;
376
377 payloads.push_back(std::move(entry));
378 }
379 return payloads;
380}
381/// Counter for generating unique global variable names.
382static std::atomic<uint64_t> globalNameCounter{0};
383
384/// Get or create a global metadata string and return a !llvm.ptr<1> value
385/// pointing to it. The AddressOfOp is created at the current rewriter
386/// insertion point; the GlobalOp is created at the module start.
387static Value createMetadataStringPtr(ConversionPatternRewriter &rewriter,
388 Operation *moduleOp, Location loc,
389 StringRef value, StringRef nameHint) {
390 // Build null-terminated string.
391 std::string strWithNull = value.str();
392 strWithNull.push_back('\0');
393 StringRef strRef(strWithNull.data(), strWithNull.size());
394
395 auto as1PtrTy = LLVM::LLVMPointerType::get(rewriter.getContext(), 1);
396
397 // Search for an existing global with the same content.
398 for (auto &op : moduleOp->getRegion(0).front()) {
399 if (auto existingGlobal = dyn_cast<LLVM::GlobalOp>(&op)) {
400 if (!existingGlobal.getSection() ||
401 *existingGlobal.getSection() != "llvm.metadata")
402 continue;
403 if (auto strAttr =
404 dyn_cast_or_null<StringAttr>(existingGlobal.getValueOrNull())) {
405 if (strAttr.getValue() == strRef) {
406 return LLVM::AddressOfOp::create(rewriter, loc, as1PtrTy,
407 existingGlobal.getSymName());
408 }
409 }
410 }
411 }
412
413 // Create new global at module start.
414 auto i8Type = rewriter.getI8Type();
415 auto arrayType = LLVM::LLVMArrayType::get(i8Type, strWithNull.size());
416 std::string globalName =
417 llvm::formatv("{0}.{1}", nameHint,
418 globalNameCounter.fetch_add(1, std::memory_order_relaxed))
419 .str();
420
421 {
422 OpBuilder::InsertionGuard guard(rewriter);
423 rewriter.setInsertionPointToStart(&moduleOp->getRegion(0).front());
424
425 auto globalOp =
426 LLVM::GlobalOp::create(rewriter, loc, arrayType,
427 /*isConstant=*/true, LLVM::Linkage::Private,
428 globalName, rewriter.getStringAttr(strRef));
429 globalOp.setSection(StringRef("llvm.metadata"));
430 globalOp.setUnnamedAddr(LLVM::UnnamedAddr::Global);
431 globalOp.setAlignment(1);
432 globalOp.setAddrSpace(1);
433 }
434 // InsertionGuard restores the original insertion point here.
435
436 return LLVM::AddressOfOp::create(rewriter, loc, as1PtrTy, globalName);
437}
438
439/// Annotate a pointer value with cache control metadata by emitting chained
440/// `llvm.intr.ptr.annotation` ops (LLVM::PtrAnnotation).
441///
442/// This is the MLIR-level equivalent of handleDecorationCacheControl() from
443/// the LLVM translation layer. For each cache control attribute, it emits:
444///
445/// %ann = llvm.intr.ptr.annotation %ptr, @".str.cachecontrol.N",
446/// @".str.file.N", 0, null : !llvm.ptr<AS>
447///
448/// Multiple annotations are chained: the result of each annotation op is
449/// fed as the pointer input to the next one.
450///
451/// \param rewriter The pattern rewriter.
452/// \param loc Source location for created ops.
453/// \param ptr The pointer value to annotate.
454/// \param cacheControls The cache control ArrayAttr (from
455/// getCacheControlMetadata).
456/// \param moduleOp The enclosing module (for creating globals).
457/// \returns The annotated pointer value (or the original ptr if no
458/// annotations).
459static Value annotatePtrWithCacheControl(ConversionPatternRewriter &rewriter,
460 Location loc, Value ptr,
461 ArrayAttr cacheControls,
462 Operation *moduleOp) {
463 SmallVector<std::string> payloads =
464 buildCacheControlPayloads(cacheControls.getValue());
465 if (payloads.empty())
466 return ptr;
467
468 auto ptrType = cast<LLVM::LLVMPointerType>(ptr.getType());
469 auto as1PtrTy = LLVM::LLVMPointerType::get(rewriter.getContext(), 1);
470 auto i32Ty = rewriter.getI32Type();
471
472 // Create shared constants for all annotations on this pointer.
473 Value fileStr =
474 createMetadataStringPtr(rewriter, moduleOp, loc, "", ".str.file");
475 Value lineVal = LLVM::ConstantOp::create(rewriter, loc, i32Ty, 0);
476 Value nullAS1 = LLVM::ZeroOp::create(rewriter, loc, as1PtrTy);
477
478 // Chain: each annotation takes the result of the previous one as its
479 // pointer operand.
480 Value curPtr = ptr;
481 for (const std::string &payload : payloads) {
482 Value annStr = createMetadataStringPtr(rewriter, moduleOp, loc, payload,
483 ".str.cachecontrol");
484 auto annOp = LLVM::PtrAnnotation::create(rewriter, loc, ptrType, curPtr,
485 annStr, fileStr, lineVal, nullAS1);
486 curPtr = annOp.getResult();
487 }
488
489 return curPtr;
490}
491
492/// Helper to apply cache control annotation on a pointer operand of a call.
493/// Replaces the pointer argument of the call with an annotated version.
494///
495/// For operations that produce a call (like block load/store/prefetch), the
496/// pointer is typically the first argument. This function:
497/// 1. Builds the annotation chain on the pointer.
498/// 2. Replaces the pointer operand in the provided args list.
499///
500/// \param rewriter The pattern rewriter.
501/// \param loc Source location.
502/// \param ptr The original pointer value (first arg to the call).
503/// \param cacheControls The cache control metadata.
504/// \param moduleOp The enclosing module.
505/// \param args The argument list (modified in place: args[ptrIdx] is
506/// replaced).
507/// \param ptrIdx Index of the pointer in the args list (default 0).
508template <typename OpType>
509static void
510applyCacheControlAnnotation(ConversionPatternRewriter &rewriter, Location loc,
511 OpType op, SmallVectorImpl<Value> &args,
512 Operation *moduleOp, unsigned ptrIdx = 0) {
513 std::optional<ArrayAttr> optCacheControls =
514 getCacheControlMetadata(rewriter, op);
515 if (!optCacheControls)
516 return;
517
518 Value annotatedPtr = annotatePtrWithCacheControl(rewriter, loc, args[ptrIdx],
519 *optCacheControls, moduleOp);
520 args[ptrIdx] = annotatedPtr;
521}
522
523//===----------------------------------------------------------------------===//
524// End cache control annotation utilities
525//===----------------------------------------------------------------------===//
526
527static LLVM::CallOp createDeviceFunctionCall(
528 ConversionPatternRewriter &rewriter, StringRef funcName, Type retType,
529 ArrayRef<Type> argTypes, ArrayRef<Value> args,
530 mlir::ArrayRef<std::pair<unsigned, mlir::StringRef>> paramAttrs,
531 LLVMFuncAttributeOptions funcAttributeOptions, Operation *op) {
532 auto *moduleOp = op->getParentWithTrait<OpTrait::SymbolTable>();
533 assert(moduleOp && "Expecting module");
534 Location loc = op->getLoc();
535
536 auto funcOpRes =
537 LLVM::lookupOrCreateFn(rewriter, moduleOp, funcName, argTypes, retType);
538 assert(!failed(funcOpRes));
539 LLVM::LLVMFuncOp funcOp = funcOpRes.value();
540 funcOp.setCConv(LLVM::cconv::CConv::SPIR_FUNC);
541 funcOp.setConvergent(funcAttributeOptions.isConvergent);
542 funcOp.setNoUnwind(funcAttributeOptions.isNoUnwind);
543 funcOp.setWillReturn(funcAttributeOptions.isWillReturn);
544
545 if (funcAttributeOptions.memEffectsAttr)
546 funcOp.setMemoryEffectsAttr(funcAttributeOptions.memEffectsAttr);
547
548 for (auto [idx, attrName] : paramAttrs)
549 funcOp.setArgAttr(idx, attrName, rewriter.getUnitAttr());
550
551 auto callOp = LLVM::CallOp::create(rewriter, loc, funcOp, args);
552 callOp->setAttrs(funcOp->getAttrs());
553
554 return callOp;
555}
556
557static unsigned getNumOperandsPerDword(xevm::ElemType pTy) {
558 switch (pTy) {
559 case xevm::ElemType::F32:
560 case xevm::ElemType::TF32:
561 return 1;
562 case xevm::ElemType::BF16:
563 case xevm::ElemType::F16:
564 return 2;
565 case xevm::ElemType::U8:
566 case xevm::ElemType::S8:
567 case xevm::ElemType::BF8:
568 case xevm::ElemType::F8:
569 return 4;
570 case xevm::ElemType::E2M1:
571 case xevm::ElemType::U4:
572 case xevm::ElemType::S4:
573 return 8;
574 default:
575 llvm_unreachable("unsupported xevm::ElemType");
576 }
577}
578
579class MMAToOCLPattern : public OpConversionPattern<xevm::MMAOp> {
580 using OpConversionPattern::OpConversionPattern;
581 LogicalResult
582 matchAndRewrite(xevm::MMAOp op, xevm::MMAOp::Adaptor adaptor,
583 ConversionPatternRewriter &rewriter) const override {
584 if (!op.getC()) {
585 return rewriter.notifyMatchFailure(op, "OCL requires C operand");
586 }
587 auto precisionA = op.getTypes().getA();
588 auto precisionB = op.getTypes().getB();
589 auto precisionC = op.getTypes().getC();
590 auto precisionD = op.getTypes().getD();
591 if (precisionC != precisionD) {
592 return rewriter.notifyMatchFailure(op, "type of C and D need to match");
593 }
594 if (precisionC != xevm::ElemType::S32 &&
595 precisionC != xevm::ElemType::F32 &&
596 precisionC != xevm::ElemType::F16 &&
597 precisionC != xevm::ElemType::BF16) {
598 return rewriter.notifyMatchFailure(
599 op, "type of C and D must be S32, F32, F16 or BF16");
600 }
601 if (precisionA == xevm::ElemType::S32 ||
602 precisionA == xevm::ElemType::F32) {
603 return rewriter.notifyMatchFailure(op, "type of A cannot be S32 or F32");
604 }
605 if (precisionB == xevm::ElemType::S32 ||
606 precisionB == xevm::ElemType::F32) {
607 return rewriter.notifyMatchFailure(op, "type of B cannot be S32 or F32");
608 }
609 constexpr uint32_t bitWidthPackedA{16};
610 constexpr uint32_t bitWidthPackedB{32};
611 auto loc = op.getLoc();
612
613 auto castIfNeeded = [&](Value val, Type packedType) -> Value {
614 VectorType origTy = cast<VectorType>(val.getType());
615 const uint32_t vecBitSize =
616 origTy.getNumElements() *
617 origTy.getElementType().getIntOrFloatBitWidth();
618 VectorType newTy = VectorType::get(
619 vecBitSize / packedType.getIntOrFloatBitWidth(), packedType);
620 if (origTy != newTy)
621 val = LLVM::BitcastOp::create(rewriter, loc, newTy, val);
622 return val;
623 };
624
625 Value a = op.getA();
626 Type packedAType = (op.getTypes().getA() == xevm::ElemType::TF32)
627 ? cast<Type>(rewriter.getF32Type())
628 : rewriter.getIntegerType(bitWidthPackedA);
629 a = castIfNeeded(a, packedAType);
630
631 Value b = op.getB();
632 Type packedBType = (op.getTypes().getB() == xevm::ElemType::TF32)
633 ? cast<Type>(rewriter.getF32Type())
634 : rewriter.getIntegerType(bitWidthPackedB);
635 b = castIfNeeded(b, packedBType);
636
637 Value c = op.getC();
638 VectorType cOrigTy = cast<VectorType>(c.getType());
639 VectorType resOrigTy = cast<VectorType>(op->getResultTypes()[0]);
640 assert(cOrigTy == resOrigTy && "Accumulator and result type mismatch");
641 // OCL builtins encode bfloat16 as int16
642 VectorType cTy =
643 cOrigTy.getElementType().isBF16()
644 ? VectorType::get(cOrigTy.getShape(), rewriter.getIntegerType(16))
645 : cOrigTy;
646 VectorType resTy = cTy;
647 if (cOrigTy != cTy)
648 c = LLVM::BitcastOp::create(rewriter, loc, cTy, c);
649
650 constexpr int32_t systolicDepth{8};
651 std::string fnName =
652 llvm::formatv("intel_sub_group_{0}_{1}_matrix_mad_k{2}",
653 stringifyElemType(op.getTypes().getA()).str(),
654 stringifyElemType(op.getTypes().getB()).str(),
655 systolicDepth *
656 getNumOperandsPerDword(op.getTypes().getA()))
657 .str();
658 SmallVector<Type> argTypes{a.getType(), b.getType(), cTy};
659 fnName = mangle(fnName, argTypes);
660 SmallVector<Value> args{a, b, c};
661
662 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
663 /*other=*/LLVM::ModRefInfo::NoModRef,
664 /*argMem=*/LLVM::ModRefInfo::NoModRef,
665 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
666 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
667 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
668 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
669 auto funcAttrs = convergentNoUnwindWillReturnAttrs;
670 funcAttrs.memEffectsAttr = memAttr;
671 Value result =
672 createDeviceFunctionCall(rewriter, fnName, resTy, argTypes, args, {},
673 funcAttrs, op.getOperation())
674 ->getResult(0);
675
676 if (resOrigTy != resTy)
677 result = LLVM::BitcastOp::create(rewriter, loc, resOrigTy, result);
678
679 rewriter.replaceOp(op, result);
680 return success();
681 }
682};
683
684class PrefetchToOCLPattern : public OpConversionPattern<PrefetchOp> {
685 using OpConversionPattern::OpConversionPattern;
686 LogicalResult
687 matchAndRewrite(PrefetchOp op, PrefetchOp::Adaptor adaptor,
688 ConversionPatternRewriter &rewriter) const override {
689 auto loc = op.getLoc();
690 auto *moduleOp = op->getParentWithTrait<OpTrait::SymbolTable>();
691
692 const std::string fnName{"_Z8prefetchPU3AS1Kcm"};
693 Value one =
694 LLVM::ConstantOp::create(rewriter, loc, rewriter.getI64Type(), 1);
695 SmallVector<Value> args{op.getPtr(), one};
696
697 // Annotate pointer with cache control before passing to the call.
698 applyCacheControlAnnotation(rewriter, loc, op, args, moduleOp,
699 /*ptrIdx=*/0);
700
701 SmallVector<Type> argTypes;
702 for (auto arg : args)
703 argTypes.push_back(arg.getType());
704 auto funcAttr = noUnwindAttrs;
705 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
706 /*other=*/LLVM::ModRefInfo::NoModRef,
707 /*argMem=*/LLVM::ModRefInfo::Ref,
708 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
709 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
710 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
711 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
712 funcAttr.memEffectsAttr = memAttr;
713
714 createDeviceFunctionCall(rewriter, fnName,
715 LLVM::LLVMVoidType::get(rewriter.getContext()),
716 argTypes, args, {}, funcAttr, op.getOperation());
717 rewriter.eraseOp(op);
718 return success();
719 }
720};
721
722class MemfenceToOCLPattern : public OpConversionPattern<MemfenceOp> {
723 using OpConversionPattern::OpConversionPattern;
724 LogicalResult
725 matchAndRewrite(MemfenceOp op, MemfenceOp::Adaptor adaptor,
726 ConversionPatternRewriter &rewriter) const override {
727 auto loc = op.getLoc();
728 const std::string fnName{"atomic_work_item_fence"};
729 int memScope, addrSpace;
730 switch (op.getAddrspace()) {
731 case xevm::AddrSpace::SHARED:
732 addrSpace = 1; // CLK_LOCAL_MEM_FENCE
733 break;
734 case xevm::AddrSpace::GLOBAL:
735 addrSpace = 2; // CLK_GLOBAL_MEM_FENCE
736 break;
737 default:
738 // GENERIC is not supported in OpenCL
739 return rewriter.notifyMatchFailure(
740 op, "Fence only supports global and shared address spaces.");
741 }
742 switch (op.getScope()) {
743 case xevm::MemScope::WORKGROUP:
744 memScope = 1;
745 break;
746 case xevm::MemScope::DEVICE:
747 memScope = 2;
748 break;
749 default:
750 // CLUSTER and SYSTEM are not supported in OpenCL
751 return rewriter.notifyMatchFailure(
752 op, "Fence only supports workgroup and device memory scopes.");
753 }
754 Type i32Type = rewriter.getI32Type();
755 Value acqRel = LLVM::ConstantOp::create(rewriter, loc, i32Type, 4);
756 Value memScopeConst =
757 LLVM::ConstantOp::create(rewriter, loc, i32Type, memScope);
758 Value addrSpaceConst =
759 LLVM::ConstantOp::create(rewriter, loc, i32Type, addrSpace);
760 SmallVector<Value> args{addrSpaceConst, acqRel, memScopeConst};
761 SmallVector<Type> argTypes{3, i32Type};
762 createDeviceFunctionCall(rewriter, mangle(fnName, argTypes),
763 LLVM::LLVMVoidType::get(rewriter.getContext()),
764 argTypes, args, {}, noUnwindAttrs,
765 op.getOperation());
766 rewriter.eraseOp(op);
767 return success();
768 }
769};
770template <typename OpType>
771class LoadStorePrefetchToOCLPattern : public OpConversionPattern<OpType> {
772 using OpConversionPattern<OpType>::OpConversionPattern;
773 LogicalResult
774 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
775 ConversionPatternRewriter &rewriter) const override {
776 constexpr bool isLoad = std::is_same_v<OpType, BlockLoad2dOp>;
777 constexpr bool isPrefetch = std::is_same_v<OpType, BlockPrefetch2dOp>;
778
779 auto loc = op.getLoc();
780 auto *moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
781 VectorType vecType;
782 bool packReg = false;
783 bool transpose = false;
784 if constexpr (isLoad) {
785 vecType = op.getRes().getType();
786 packReg = op.getPackRegister();
787 transpose = op.getTranspose();
788 } else if constexpr (!isPrefetch) {
789 vecType = op.getStoredVal().getType();
790 }
791
792 auto i32Type = rewriter.getI32Type();
793 Value byteCoord =
794 LLVM::UndefOp::create(rewriter, loc, VectorType::get(2, i32Type));
795 Value zero = LLVM::ConstantOp::create(rewriter, loc, i32Type, 0);
796 Value one = LLVM::ConstantOp::create(rewriter, loc, i32Type, 1);
797 byteCoord = LLVM::InsertElementOp::create(
798 rewriter, loc, VectorType::get(2, i32Type), byteCoord, op.getX(), zero);
799 byteCoord = LLVM::InsertElementOp::create(
800 rewriter, loc, VectorType::get(2, i32Type), byteCoord, op.getY(), one);
801 SmallVector<Value> args{op.getPtr(), op.getBaseWidth(), op.getBaseHeight(),
802 op.getBasePitch(), byteCoord};
803
804 // Annotate pointer (args[0]) with cache control before the call.
805 applyCacheControlAnnotation(rewriter, loc, op, args, moduleOp,
806 /*ptrIdx=*/0);
807
808 SmallVector<Type> retTypes;
809 Value spvLoadDstPtr;
810 std::string funcName{"intel_sub_group_2d_block_"};
811 std::string bitWidthId;
812 LLVMFuncAttributeOptions funcAttr{noUnwindWillReturnAttrs};
813 SmallVector<std::pair<unsigned, StringRef>, 4> paramAttrs;
814 if constexpr (isPrefetch) { // Prefetch
815 funcName += "prefetch";
816 paramAttrs = {std::make_pair(0, LLVM::LLVMDialect::getNonNullAttrName())};
817 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
818 /*other=*/LLVM::ModRefInfo::NoModRef,
819 /*argMem=*/LLVM::ModRefInfo::Ref,
820 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
821 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
822 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
823 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
824 funcAttr = noUnwindAttrs;
825 funcAttr.memEffectsAttr = memAttr;
826 } else {
827 auto vecElemType = vecType.getElementType();
828 auto vecElemBitWidth = vecElemType.getIntOrFloatBitWidth();
829 auto vecNumElems = vecType.getNumElements();
830 // OpenCL Intel 2D block load has a special case
831 // when element bit size is 8 and tile width is 32, which is twice
832 // the subgroup size, loaded element is packed as i16.
833 // To reflect this, element bit size is updated to 16 and
834 // vector length is reduced by half.
835 if (op.getElemSizeInBits() == 8 && op.getTileWidth() == 32) {
836 vecElemBitWidth = 16;
837 vecElemType = rewriter.getI16Type();
838 vecNumElems = vecNumElems / 2;
839 }
840 Value numElems =
841 LLVM::ConstantOp::create(rewriter, loc, i32Type, vecNumElems);
842 auto dstOrSrcPtr = LLVM::AllocaOp::create(
843 rewriter, loc, LLVM::LLVMPointerType::get(rewriter.getContext()),
844 vecElemType, numElems);
845 args.push_back(dstOrSrcPtr);
846 if constexpr (isLoad) { // Load
847 funcName += "read";
848 bitWidthId = getTypeMangling(vecElemType, /*isUnsigned=*/true);
849 if (packReg)
850 funcName += "_transform";
851 else if (transpose)
852 funcName += "_transpose";
853 spvLoadDstPtr = dstOrSrcPtr;
854 retTypes.push_back(vecType);
855 paramAttrs = {
856 std::make_pair(0, LLVM::LLVMDialect::getNonNullAttrName()),
857 std::make_pair(0, LLVM::LLVMDialect::getReadonlyAttrName()),
858 std::make_pair(5, LLVM::LLVMDialect::getNonNullAttrName()),
859 std::make_pair(5, LLVM::LLVMDialect::getWriteOnlyAttrName()),
860 };
861 } else { // Store
862 funcName += "write";
863 bitWidthId = (vecElemBitWidth == 32)
864 ? "j"
865 : ((vecElemBitWidth == 16) ? "t" : "h");
866 LLVM::StoreOp::create(rewriter, loc, op.getStoredVal(), dstOrSrcPtr);
867 paramAttrs = {
868 std::make_pair(0, LLVM::LLVMDialect::getNonNullAttrName()),
869 std::make_pair(0, LLVM::LLVMDialect::getWriteOnlyAttrName()),
870 std::make_pair(5, LLVM::LLVMDialect::getNonNullAttrName()),
871 std::make_pair(5, LLVM::LLVMDialect::getReadonlyAttrName()),
872 };
873 }
874 }
875
876 funcName =
877 llvm::formatv("{0}_{1}b_{2}r{3}x{4}c", funcName, op.getElemSizeInBits(),
878 op.getTileHeight(), op.getTileWidth(), op.getVBlocks())
879 .str();
880 std::string prefetchCode("");
881 if (!isPrefetch)
882 prefetchCode += "P";
883 funcName = llvm::formatv("_Z{0}{1}PU3AS1viiiDv2_i{2}{3}", funcName.size(),
884 funcName, prefetchCode, bitWidthId)
885 .str();
886 SmallVector<Type> argTypes;
887 for (auto arg : args) {
888 argTypes.push_back(arg.getType());
889 }
890 createDeviceFunctionCall(
891 rewriter, funcName, LLVM::LLVMVoidType::get(rewriter.getContext()),
892 argTypes, args, paramAttrs, funcAttr, op.getOperation());
893
894 if constexpr (isLoad)
895 rewriter.replaceOp(
896 op, LLVM::LoadOp::create(rewriter, loc, vecType, spvLoadDstPtr));
897 else
898 rewriter.eraseOp(op);
899 return success();
900 }
901};
902
903template <typename OpType>
904class BlockLoadStore1DToOCLPattern : public OpConversionPattern<OpType> {
905 using OpConversionPattern<OpType>::OpConversionPattern;
906 LogicalResult
907 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
908 ConversionPatternRewriter &rewriter) const override {
909 constexpr bool isStore = std::is_same_v<OpType, xevm::BlockStoreOp>;
910 auto loc = op.getLoc();
911 auto *moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
912
913 // Get OpenCL function name
914 // https://registry.khronos.org/OpenCL/extensions/
915 // intel/cl_intel_subgroup_local_block_io.html
916 std::string funcName{"intel_sub_group_block_"};
917 // Value or Result type can be vector or scalar
918 Type valOrResTy;
919 if constexpr (isStore) {
920 funcName += "write_u";
921 valOrResTy = op.getVal().getType();
922 } else {
923 funcName += "read_u";
924 valOrResTy = op.getType();
925 }
926 // Get element type of the vector/scalar
927 VectorType vecTy = dyn_cast<VectorType>(valOrResTy);
928 Type elemType = vecTy ? vecTy.getElementType() : valOrResTy;
929 funcName += getTypeMangling(elemType);
930 if (vecTy)
931 funcName += std::to_string(vecTy.getNumElements());
932 SmallVector<Type, 2> argTypes{};
933 // XeVM BlockLoad/StoreOp always use signless integer types
934 // but OpenCL builtins expect unsigned types
935 // use unsigned types for mangling
936 SmallVector<bool, 2> isUnsigned{};
937 // arg0: pointer to the src/dst address
938 // arg1 - only if store : vector to store
939 // Prepare arguments
940 SmallVector<Value, 2> args{};
941 args.push_back(op.getPtr());
942 argTypes.push_back(op.getPtr().getType());
943 isUnsigned.push_back(true);
944
945 // Annotate pointer (args[0]) with cache control.
946 applyCacheControlAnnotation(rewriter, loc, op, args, moduleOp,
947 /*ptrIdx=*/0);
948 // Update argTypes[0] in case the pointer type changed (it shouldn't
949 // change type, but the value is now the annotated pointer).
950 argTypes[0] = args[0].getType();
951
952 Type retType;
953 if constexpr (isStore) {
954 args.push_back(op.getVal());
955 argTypes.push_back(op.getVal().getType());
956 isUnsigned.push_back(true);
957 retType = LLVM::LLVMVoidType::get(rewriter.getContext());
958 } else {
959 retType = valOrResTy;
960 }
961 funcName = std::string("_Z") + std::to_string(funcName.size()) + funcName +
962 "PU3AS" +
963 std::to_string(op.getPtr().getType().getAddressSpace());
964 funcName += getTypeMangling(elemType, /*isUnsigned=*/true);
965 if constexpr (isStore)
966 funcName += getTypeMangling(valOrResTy, /*isUnsigned=*/true);
967 LLVMFuncAttributeOptions funcAttr{noUnwindWillReturnAttrs};
968
969 LLVM::CallOp call =
970 createDeviceFunctionCall(rewriter, funcName, retType, argTypes, args,
971 {}, funcAttr, op.getOperation());
972
973 if constexpr (isStore)
974 rewriter.eraseOp(op);
975 else
976 rewriter.replaceOp(op, call->getResult(0));
977 return success();
978 }
979};
980
981template <typename OpType>
982class LLVMLoadStoreToOCLPattern : public OpConversionPattern<OpType> {
983 using OpConversionPattern<OpType>::OpConversionPattern;
984 LogicalResult
985 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
986 ConversionPatternRewriter &rewriter) const override {
987 if (!op->hasAttr("cache_control"))
988 return failure();
989
990 auto *moduleOp = op->template getParentWithTrait<OpTrait::SymbolTable>();
991 std::optional<ArrayAttr> optCacheControls =
992 getCacheControlMetadata(rewriter, op);
993 if (!optCacheControls) {
994 rewriter.modifyOpInPlace(op, [&]() { op->removeAttr("cache_control"); });
995 return success();
996 }
997
998 // Determine which operand is the pointer.
999 constexpr bool isStore = std::is_same_v<OpType, LLVM::StoreOp>;
1000 unsigned ptrIdx = isStore ? 1 : 0;
1001 Value ptr = op->getOperand(ptrIdx);
1002
1003 // Emit annotation intrinsic calls on the pointer.
1004 Value annotatedPtr = annotatePtrWithCacheControl(
1005 rewriter, op->getLoc(), ptr, *optCacheControls, moduleOp);
1006
1007 // Replace the pointer operand with the annotated one.
1008 rewriter.modifyOpInPlace(op, [&]() {
1009 op->setOperand(ptrIdx, annotatedPtr);
1010 op->removeAttr("cache_control");
1011 });
1012 return success();
1013 }
1014};
1015
1016//===----------------------------------------------------------------------===//
1017// GPU index id operations
1018//===----------------------------------------------------------------------===//
1019/*
1020// Launch Config ops
1021// dimidx - x, y, z - is fixed to i32
1022// return type is set by XeVM type converter
1023// get_local_id
1024xevm::WorkitemIdXOp;
1025xevm::WorkitemIdYOp;
1026xevm::WorkitemIdZOp;
1027// get_local_size
1028xevm::WorkgroupDimXOp;
1029xevm::WorkgroupDimYOp;
1030xevm::WorkgroupDimZOp;
1031// get_group_id
1032xevm::WorkgroupIdXOp;
1033xevm::WorkgroupIdYOp;
1034xevm::WorkgroupIdZOp;
1035// get_num_groups
1036xevm::GridDimXOp;
1037xevm::GridDimYOp;
1038xevm::GridDimZOp;
1039// get_global_id : to be added if needed
1040*/
1041
1042// Helpers to get the OpenCL function name and dimension argument for each op.
1043static std::pair<StringRef, int64_t> getConfig(xevm::WorkitemIdXOp) {
1044 return {"get_local_id", 0};
1045}
1046static std::pair<StringRef, int64_t> getConfig(xevm::WorkitemIdYOp) {
1047 return {"get_local_id", 1};
1048}
1049static std::pair<StringRef, int64_t> getConfig(xevm::WorkitemIdZOp) {
1050 return {"get_local_id", 2};
1051}
1052static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupDimXOp) {
1053 return {"get_local_size", 0};
1054}
1055static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupDimYOp) {
1056 return {"get_local_size", 1};
1057}
1058static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupDimZOp) {
1059 return {"get_local_size", 2};
1060}
1061static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupIdXOp) {
1062 return {"get_group_id", 0};
1063}
1064static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupIdYOp) {
1065 return {"get_group_id", 1};
1066}
1067static std::pair<StringRef, int64_t> getConfig(xevm::WorkgroupIdZOp) {
1068 return {"get_group_id", 2};
1069}
1070static std::pair<StringRef, int64_t> getConfig(xevm::GridDimXOp) {
1071 return {"get_num_groups", 0};
1072}
1073static std::pair<StringRef, int64_t> getConfig(xevm::GridDimYOp) {
1074 return {"get_num_groups", 1};
1075}
1076static std::pair<StringRef, int64_t> getConfig(xevm::GridDimZOp) {
1077 return {"get_num_groups", 2};
1078}
1079/// Replace `xevm.*` with an `llvm.call` to the corresponding OpenCL func with
1080/// a constant argument for the dimension - x, y or z.
1081template <typename OpType>
1082class LaunchConfigOpToOCLPattern : public OpConversionPattern<OpType> {
1083 using OpConversionPattern<OpType>::OpConversionPattern;
1084 LogicalResult
1085 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
1086 ConversionPatternRewriter &rewriter) const override {
1087 Location loc = op->getLoc();
1088 auto [baseName, dim] = getConfig(op);
1089 Type dimTy = rewriter.getI32Type();
1090 Value dimVal = LLVM::ConstantOp::create(rewriter, loc, dimTy,
1091 static_cast<int64_t>(dim));
1092 std::string func = mangle(baseName, {dimTy}, {true});
1093 Type resTy = op.getType();
1094 auto call =
1095 createDeviceFunctionCall(rewriter, func, resTy, {dimTy}, {dimVal}, {},
1096 noUnwindWillReturnAttrs, op.getOperation());
1097 constexpr auto noModRef = LLVM::ModRefInfo::NoModRef;
1098 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
1099 /*other=*/noModRef,
1100 /*argMem=*/noModRef, /*inaccessibleMem=*/noModRef,
1101 /*errnoMem=*/noModRef,
1102 /*targetMem0=*/noModRef,
1103 /*targetMem1=*/noModRef);
1104 call.setMemoryEffectsAttr(memAttr);
1105 rewriter.replaceOp(op, call);
1106 return success();
1107 }
1108};
1109
1110/*
1111// Subgroup ops
1112// get_sub_group_local_id
1113xevm::LaneIdOp;
1114// get_sub_group_id
1115xevm::SubgroupIdOp;
1116// get_sub_group_size
1117xevm::SubgroupSizeOp;
1118// get_num_sub_groups : to be added if needed
1119*/
1120
1121// Helpers to get the OpenCL function name for each op.
1122static StringRef getConfig(xevm::LaneIdOp) { return "get_sub_group_local_id"; }
1123static StringRef getConfig(xevm::SubgroupIdOp) { return "get_sub_group_id"; }
1124static StringRef getConfig(xevm::SubgroupSizeOp) {
1125 return "get_sub_group_size";
1126}
1127template <typename OpType>
1128class SubgroupOpWorkitemOpToOCLPattern : public OpConversionPattern<OpType> {
1129 using OpConversionPattern<OpType>::OpConversionPattern;
1130 LogicalResult
1131 matchAndRewrite(OpType op, typename OpType::Adaptor adaptor,
1132 ConversionPatternRewriter &rewriter) const override {
1133 std::string func = mangle(getConfig(op).str(), {});
1134 Type resTy = op.getType();
1135 auto call =
1136 createDeviceFunctionCall(rewriter, func, resTy, {}, {}, {},
1137 noUnwindWillReturnAttrs, op.getOperation());
1138 constexpr auto noModRef = LLVM::ModRefInfo::NoModRef;
1139 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
1140 /*other=*/noModRef,
1141 /*argMem=*/noModRef, /*inaccessibleMem=*/noModRef,
1142 /*errnoMem=*/noModRef,
1143 /*targetMem0=*/noModRef,
1144 /*targetMem1=*/noModRef);
1145 call.setMemoryEffectsAttr(memAttr);
1146 rewriter.replaceOp(op, call);
1147 return success();
1148 }
1149};
1150
1151class TruncfToOCLPattern : public OpConversionPattern<TruncfOp> {
1152 using OpConversionPattern::OpConversionPattern;
1153 LogicalResult
1154 matchAndRewrite(TruncfOp op, TruncfOp::Adaptor adaptor,
1155 ConversionPatternRewriter &rewriter) const override {
1156 // Supported source and result types are resticted for now.
1157 auto srcEtype = op.getSrcEtype().getEtype();
1158 auto dstEtype = op.getDstEtype().getEtype();
1159 // Currently only 16 input elements are supported as
1160 // - Any vector beyond 16 elements not a valid OpenCL vector.
1161 // - 2D block load can only load up to 16 16bit elements per lane.
1162 // Widest load is 8x16xi32 with 16 lanes, which is 16 16bit
1163 // elements per lane.
1164 // - mma_mx A and B operands need more than 16 elements per lane
1165 //
1166 // Conversion is done in batches depending on the dst type.
1167 // batch_size =
1168 // 16 if dst type == fp8
1169 // 8 if dst type == fp4
1170 // For num_elem > batch_size
1171 // convert batch of batch_size
1172 // cast batch to i32 elem type vector
1173 // concat batches by shufflevector
1174 // For num_elem = batch_size
1175 // use API for conversion
1176 // Scalar case is not supported until usage case become clear.
1177 auto vecSrcTy = dyn_cast<VectorType>(op.getSrc().getType());
1178 if (!vecSrcTy) {
1179 return rewriter.notifyMatchFailure(op, "Scalar src is not supported.");
1180 }
1181 if (vecSrcTy.getNumElements() != 16)
1182 return rewriter.notifyMatchFailure(
1183 op, "Only vector src of 16 elements is supported");
1184 auto vecDstTy = dyn_cast<VectorType>(op.getDst().getType());
1185 if (!vecDstTy)
1186 return rewriter.notifyMatchFailure(op, "Scalar dst is not supported.");
1187 Value src = op.getSrc();
1188 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
1189 /*other=*/LLVM::ModRefInfo::NoModRef,
1190 /*argMem=*/LLVM::ModRefInfo::NoModRef,
1191 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
1192 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
1193 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
1194 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
1195 auto funcAttrs = convergentNoUnwindWillReturnAttrs;
1196 funcAttrs.memEffectsAttr = memAttr;
1197
1198 // Handle the case where dst type is fp4 first.
1199 if (dstEtype == TruncfDstElemTypes::E2M1) {
1200 // Convert 8 elements at a time.
1201 // To convert 8 elements, vector<8xf16>:
1202 // Use:
1203 // uint __builtin_IB_dnscl_hf16(uint, uint, 1, 0)
1204 // uint __builtin_IB_dnscl_hf16(uint, uint, 1, 3)
1205 // llvm.or
1206 Value cast = LLVM::BitcastOp::create(
1207 rewriter, op.getLoc(), VectorType::get(8, rewriter.getI32Type()),
1208 src);
1209
1210 std::string fnName = "__builtin_IB_dnscl_";
1211 fnName += (srcEtype == TruncfSrcElemTypes::F16) ? "hf16" : "bf16";
1212 auto genDnscl = [&](Value input, Value idx0, Value idx1, Value dstTy,
1213 Value mode) -> Value {
1214 Value arg1 =
1215 LLVM::ExtractElementOp::create(rewriter, op.getLoc(), input, idx0)
1216 ->getResult(0);
1217 Value arg2 =
1218 LLVM::ExtractElementOp::create(rewriter, op.getLoc(), input, idx1)
1219 ->getResult(0);
1220 SmallVector<Type> argTypes{arg1.getType(), arg2.getType(),
1221 dstTy.getType(), mode.getType()};
1222 SmallVector<Value> args{arg1, arg2, dstTy, mode};
1223 Value dnscl = createDeviceFunctionCall(
1224 rewriter, fnName, rewriter.getI32Type(), argTypes,
1225 args, {}, funcAttrs, op.getOperation())
1226 ->getResult(0);
1227 return dnscl;
1228 };
1229
1230 Value zero = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1231 rewriter.getI32Type(), 0);
1232 Value one = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1233 rewriter.getI32Type(), 1);
1234 Value two = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1235 rewriter.getI32Type(), 2);
1236 Value three = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1237 rewriter.getI32Type(), 3);
1238 Value even = genDnscl(cast, zero, two, one, zero);
1239 Value odd = genDnscl(cast, one, three, one, two);
1240 Value firstHalf = LLVM::OrOp::create(rewriter, op.getLoc(), even, odd);
1241 Value four = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1242 rewriter.getI32Type(), 4);
1243 Value five = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1244 rewriter.getI32Type(), 5);
1245 Value six = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1246 rewriter.getI32Type(), 6);
1247 Value seven = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1248 rewriter.getI32Type(), 7);
1249 even = genDnscl(cast, four, six, one, zero);
1250 odd = genDnscl(cast, five, seven, one, two);
1251 Value secondHalf = LLVM::OrOp::create(rewriter, op.getLoc(), even, odd);
1252 // Create vector<2xi32> from two i32 values and then bitcast to
1253 // vector<8xi8> to match the dst type.
1254 Value combined = LLVM::UndefOp::create(
1255 rewriter, op.getLoc(), VectorType::get(2, rewriter.getI32Type()));
1256 combined = LLVM::InsertElementOp::create(rewriter, op.getLoc(), combined,
1257 firstHalf, zero)
1258 ->getResult(0);
1259 combined = LLVM::InsertElementOp::create(rewriter, op.getLoc(), combined,
1260 secondHalf, one)
1261 ->getResult(0);
1262 Value result =
1263 LLVM::BitcastOp::create(rewriter, op.getLoc(), vecDstTy, combined);
1264 rewriter.replaceOp(op, result);
1265 return success();
1266 }
1267
1268 // Handle the case where dst type is fp8.
1269 // BF16 type needs some preprocessing before conversion,
1270 // First extended to F32 and then truncated to F16.
1271 if (srcEtype == TruncfSrcElemTypes::BF16) {
1272 // Step 1: Extend to F32
1273 // Use float16 __builtin_IB_bftof_16(short16)
1274 src = LLVM::BitcastOp::create(
1275 rewriter, op.getLoc(),
1276 VectorType::get(vecSrcTy.getShape(), rewriter.getI16Type()), src);
1277 std::string fnName = "__builtin_IB_bftof_16";
1278 SmallVector<Type> argTypes{src.getType()};
1279 SmallVector<Value> args{src};
1280 Type resTy = VectorType::get(vecSrcTy.getShape(), rewriter.getF32Type());
1281 src = createDeviceFunctionCall(rewriter, fnName, resTy, argTypes, args,
1282 {}, funcAttrs, op.getOperation())
1283 ->getResult(0);
1284 // Step 2: Truncf to F16
1285 // Use half16 convert_half16(float16)
1286 std::string truncFnName = "convert_half16";
1287 SmallVector<Type> truncArgTypes{src.getType()};
1288 SmallVector<Value> truncArgs{src};
1289 truncFnName = mangle(truncFnName, truncArgTypes);
1290 resTy = VectorType::get(vecSrcTy.getShape(), rewriter.getF16Type());
1291 src =
1292 createDeviceFunctionCall(rewriter, truncFnName, resTy, truncArgTypes,
1293 truncArgs, {}, funcAttrs, op.getOperation())
1294 ->getResult(0);
1295 }
1296 if (dstEtype == TruncfDstElemTypes::BF8) { // Float8E5M2Type
1297 // Use char16 __builtin_IB_hftobf8_16(half16)
1298 std::string fnName = "__builtin_IB_hftobf8_16";
1299 SmallVector<Type> argTypes{src.getType()};
1300 SmallVector<Value> args{src};
1301 Value result =
1302 createDeviceFunctionCall(rewriter, fnName, vecDstTy, argTypes, args,
1303 {}, funcAttrs, op.getOperation())
1304 ->getResult(0);
1305
1306 rewriter.replaceOp(op, result);
1307 } else if (dstEtype == TruncfDstElemTypes::F8) { // Float8E4M3FNType
1308 // Use char16 __builtin_IB_hftohf8_16(half16)
1309 std::string fnName = "__builtin_IB_hftohf8_16";
1310 SmallVector<Type> argTypes{src.getType()};
1311 SmallVector<Value> args{src};
1312 Value result =
1313 createDeviceFunctionCall(rewriter, fnName, vecDstTy, argTypes, args,
1314 {}, funcAttrs, op.getOperation())
1315 ->getResult(0);
1316
1317 rewriter.replaceOp(op, result);
1318 } else {
1319 return rewriter.notifyMatchFailure(
1320 op, "Unsupported src, dst element type pair.");
1321 }
1322 return success();
1323 }
1324};
1325
1326class ExtfToOCLPattern : public OpConversionPattern<ExtfOp> {
1327 using OpConversionPattern::OpConversionPattern;
1328 LogicalResult
1329 matchAndRewrite(ExtfOp op, ExtfOp::Adaptor adaptor,
1330 ConversionPatternRewriter &rewriter) const override {
1331 // `xevm.extf` is the inverse of `xevm.truncf`. Supported source and result
1332 // types are restricted for now, mirroring the truncf lowering.
1333 auto srcEtype = op.getSrcEtype().getEtype();
1334 auto dstEtype = op.getDstEtype().getEtype();
1335 // Scalar case is not supported until usage case become clear.
1336 auto vecSrcTy = dyn_cast<VectorType>(op.getSrc().getType());
1337 if (!vecSrcTy)
1338 return rewriter.notifyMatchFailure(op, "Scalar src is not supported.");
1339 auto vecDstTy = dyn_cast<VectorType>(op.getDst().getType());
1340 if (!vecDstTy)
1341 return rewriter.notifyMatchFailure(op, "Scalar dst is not supported.");
1342 Value src = op.getSrc();
1343 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
1344 /*other=*/LLVM::ModRefInfo::NoModRef,
1345 /*argMem=*/LLVM::ModRefInfo::NoModRef,
1346 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
1347 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
1348 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
1349 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
1350 auto funcAttrs = convergentNoUnwindWillReturnAttrs;
1351 funcAttrs.memEffectsAttr = memAttr;
1352
1353 // Handle the case where src type is fp4 (e2m1) first.
1354 if (srcEtype == ExtfSrcElemTypes::E2M1) {
1355 // 16 fp4 values are packed into vector<8xi8>, the result is a
1356 // vector<16xf16> or vector<16xbf16>.
1357 // Use:
1358 // uint16 __builtin_IB_shfl_idx4_lut(int lut_index)
1359 // uint8 __builtin_IB_shfl_idx4_to_fp16_8_packed(uint16 lut,
1360 // char8 source)
1361 // The lookup table selects the target format:
1362 // 7 = e2m1 -> f16, 5 = e2m1 -> bf16.
1363 if (vecSrcTy.getNumElements() != 8 || vecDstTy.getNumElements() != 16)
1364 return rewriter.notifyMatchFailure(
1365 op, "fp4 src expects a vector<8xi8> src and a 16 element dst");
1366 constexpr int kLutE2M1ToF16 = 7;
1367 constexpr int kLutE2M1ToBF16 = 5;
1368 int lutIndex =
1369 (dstEtype == ExtfDstElemTypes::F16) ? kLutE2M1ToF16 : kLutE2M1ToBF16;
1370 Value lutIdx = LLVM::ConstantOp::create(rewriter, op.getLoc(),
1371 rewriter.getI32Type(), lutIndex);
1372 Type lutTy = VectorType::get(16, rewriter.getI32Type());
1373 Value lut =
1374 createDeviceFunctionCall(rewriter, "__builtin_IB_shfl_idx4_lut",
1375 lutTy, {lutIdx.getType()}, {lutIdx}, {},
1376 funcAttrs, op.getOperation())
1377 ->getResult(0);
1378 Type packedResTy = VectorType::get(8, rewriter.getI32Type());
1379 SmallVector<Type> convArgTypes{lut.getType(), src.getType()};
1380 SmallVector<Value> convArgs{lut, src};
1381 Value result =
1382 createDeviceFunctionCall(
1383 rewriter, "__builtin_IB_shfl_idx4_to_fp16_8_packed", packedResTy,
1384 convArgTypes, convArgs, {}, funcAttrs, op.getOperation())
1385 ->getResult(0);
1386 // The builtin returns the f16/bf16 bits packed as i32, bitcast to the
1387 // f16/bf16 dst type.
1388 result = LLVM::BitcastOp::create(rewriter, op.getLoc(), vecDstTy, result);
1389 rewriter.replaceOp(op, result);
1390 return success();
1391 }
1392
1393 // Handle the case where src type is fp8 (bf8/hf8).
1394 // Only 16 input elements are supported, see TruncfToOCLPattern for details.
1395 if (vecSrcTy.getNumElements() != 16)
1396 return rewriter.notifyMatchFailure(
1397 op, "Only vector src of 16 elements is supported");
1398
1399 // Step 1: Extend fp8 (bf8/hf8) to F16.
1400 // bf8 -> half: half16 __builtin_IB_bf8tohf_16(char16)
1401 // hf8 -> half: half16 __builtin_IB_hf8tohf_16(char16)
1402 std::string fnName = (srcEtype == ExtfSrcElemTypes::BF8)
1403 ? "__builtin_IB_bf8tohf_16"
1404 : "__builtin_IB_hf8tohf_16";
1405 Type f16Ty = VectorType::get(vecSrcTy.getShape(), rewriter.getF16Type());
1406 SmallVector<Type> argTypes{src.getType()};
1407 SmallVector<Value> args{src};
1408 Value result =
1409 createDeviceFunctionCall(rewriter, fnName, f16Ty, argTypes, args, {},
1410 funcAttrs, op.getOperation())
1411 ->getResult(0);
1412
1413 // When the destination is F16, we are done.
1414 if (dstEtype == ExtfDstElemTypes::F16) {
1415 rewriter.replaceOp(op, result);
1416 return success();
1417 }
1418
1419 // BF16 destination needs some postprocessing.
1420 // First extend F16 to F32 and then truncate to BF16.
1421 // Step 2: Extend to F32.
1422 // Use float16 convert_float16(half16)
1423 std::string convFnName = "convert_float16";
1424 SmallVector<Type> convArgTypes{result.getType()};
1425 SmallVector<Value> convArgs{result};
1426 convFnName = mangle(convFnName, convArgTypes);
1427 Type f32Ty = VectorType::get(vecSrcTy.getShape(), rewriter.getF32Type());
1428 result =
1429 createDeviceFunctionCall(rewriter, convFnName, f32Ty, convArgTypes,
1430 convArgs, {}, funcAttrs, op.getOperation())
1431 ->getResult(0);
1432 // Step 3: Truncate F32 to BF16.
1433 // Use short16 __builtin_IB_ftobf_16(float16)
1434 constexpr StringRef ftobfFnName = "__builtin_IB_ftobf_16";
1435 SmallVector<Type> ftobfArgTypes{result.getType()};
1436 SmallVector<Value> ftobfArgs{result};
1437 Type i16Ty = VectorType::get(vecSrcTy.getShape(), rewriter.getI16Type());
1438 result =
1439 createDeviceFunctionCall(rewriter, ftobfFnName, i16Ty, ftobfArgTypes,
1440 ftobfArgs, {}, funcAttrs, op.getOperation())
1441 ->getResult(0);
1442 // The builtin returns the bf16 bits as i16, bitcast to the bf16 dst type.
1443 result = LLVM::BitcastOp::create(rewriter, op.getLoc(), vecDstTy, result);
1444 rewriter.replaceOp(op, result);
1445 return success();
1446 }
1447};
1448
1449class MMAMxToOCLPattern : public OpConversionPattern<MMAMxOp> {
1450 using OpConversionPattern::OpConversionPattern;
1451 LogicalResult
1452 matchAndRewrite(MMAMxOp op, MMAMxOp::Adaptor adaptor,
1453 ConversionPatternRewriter &rewriter) const override {
1454 if (!op.getC()) {
1455 return rewriter.notifyMatchFailure(op, "OCL requires C operand");
1456 }
1457 auto precisionC = op.getTypes().getC();
1458 auto precisionD = op.getTypes().getD();
1459 if (precisionC != precisionD) {
1460 return rewriter.notifyMatchFailure(op, "type of C and D need to match");
1461 }
1462
1463 constexpr uint32_t bitWidthPackedA{16};
1464 constexpr uint32_t bitWidthPackedB{32};
1465 auto loc = op.getLoc();
1466
1467 auto castIfNeeded = [&](Value val, Type packedType) -> Value {
1468 VectorType origTy = cast<VectorType>(val.getType());
1469 const uint32_t vecBitSize =
1470 origTy.getNumElements() *
1471 origTy.getElementType().getIntOrFloatBitWidth();
1472 VectorType newTy = VectorType::get(
1473 vecBitSize / packedType.getIntOrFloatBitWidth(), packedType);
1474 if (origTy != newTy)
1475 val = LLVM::BitcastOp::create(rewriter, loc, newTy, val);
1476 return val;
1477 };
1478
1479 Value a = op.getA();
1480 Type packedAType = (op.getTypes().getA() == xevm::ElemType::TF32)
1481 ? cast<Type>(rewriter.getF32Type())
1482 : rewriter.getIntegerType(bitWidthPackedA);
1483 a = castIfNeeded(a, packedAType);
1484
1485 Value b = op.getB();
1486 Type packedBType = (op.getTypes().getB() == xevm::ElemType::TF32)
1487 ? cast<Type>(rewriter.getF32Type())
1488 : rewriter.getIntegerType(bitWidthPackedB);
1489 b = castIfNeeded(b, packedBType);
1490
1491 Value c = op.getC();
1492 VectorType cOrigTy = cast<VectorType>(c.getType());
1493 VectorType resOrigTy = cast<VectorType>(op->getResultTypes()[0]);
1494 assert(cOrigTy == resOrigTy && "Accumulator and result type mismatch");
1495 // OCL builtins encode bfloat16 as int16
1496 VectorType cTy =
1497 cOrigTy.getElementType().isBF16()
1498 ? VectorType::get(cOrigTy.getShape(), rewriter.getIntegerType(16))
1499 : cOrigTy;
1500 VectorType resTy = cTy;
1501 if (cOrigTy != cTy)
1502 c = LLVM::BitcastOp::create(rewriter, loc, cTy, c);
1503
1504 std::string fnName =
1505 llvm::formatv("__builtin_IB_sub_group16_bdpas_{0}_{1}_{2}_{3}_8_8",
1506 builtinElemType(op.getTypes().getD()),
1507 builtinElemType(op.getTypes().getC()),
1508 builtinElemType(op.getTypes().getA()),
1509 builtinElemType(op.getTypes().getB()))
1510 .str();
1511 auto scaleA = op.getScaleA();
1512 auto scaleB = op.getScaleB();
1513 SmallVector<Type> argTypes{cTy, a.getType(), b.getType(), scaleA.getType(),
1514 scaleB.getType()};
1515 SmallVector<Value> args{c, a, b, scaleA, scaleB};
1516
1517 auto memAttr = rewriter.getAttr<LLVM::MemoryEffectsAttr>(
1518 /*other=*/LLVM::ModRefInfo::NoModRef,
1519 /*argMem=*/LLVM::ModRefInfo::NoModRef,
1520 /*inaccessibleMem=*/LLVM::ModRefInfo::NoModRef,
1521 /*errnoMem=*/LLVM::ModRefInfo::NoModRef,
1522 /*targetMem0=*/LLVM::ModRefInfo::NoModRef,
1523 /*targetMem1=*/LLVM::ModRefInfo::NoModRef);
1524 auto funcAttrs = convergentNoUnwindWillReturnAttrs;
1525 funcAttrs.memEffectsAttr = memAttr;
1526 Value result =
1527 createDeviceFunctionCall(rewriter, fnName, resTy, argTypes, args, {},
1528 funcAttrs, op.getOperation())
1529 ->getResult(0);
1530
1531 if (resOrigTy != resTy)
1532 result = LLVM::BitcastOp::create(rewriter, loc, resOrigTy, result);
1533
1534 rewriter.replaceOp(op, result);
1535 return success();
1536 }
1537};
1538
1539// Lowers `xevm.bitcast_shuffle` to a call to the IGC intrinsic
1540// `llvm.genx.GenISA.SubgroupBitcastShuffle`, which is overloaded on both the
1541// result and the operand type. E.g. a `vector<4xi8>` -> `vector<2xi16>` shuffle
1542// becomes a call to
1543// `llvm.genx.GenISA.SubgroupBitcastShuffle.v2i16.v4i8`.
1544//
1545// Only integer types reach here: the op accepts nothing else, so a producer
1546// holding floating point data bitcasts it to a same-width integer beforehand.
1547class BitcastShuffleToGenISAPattern
1548 : public OpConversionPattern<BitcastShuffleOp> {
1549 using OpConversionPattern::OpConversionPattern;
1550 LogicalResult
1551 matchAndRewrite(BitcastShuffleOp op, BitcastShuffleOp::Adaptor adaptor,
1552 ConversionPatternRewriter &rewriter) const override {
1553 Type srcTy = op.getSrc().getType();
1554 Type resTy = op.getRes().getType();
1555
1556 std::string fnName = "llvm.genx.GenISA.SubgroupBitcastShuffle." +
1557 getGenISATypeMangling(resTy) + "." +
1558 getGenISATypeMangling(srcTy);
1559
1560 Value result = createDeviceFunctionCall(
1561 rewriter, fnName, resTy, {srcTy}, {adaptor.getSrc()}, {},
1562 convergentNoUnwindWillReturnAttrs, op.getOperation())
1563 ->getResult(0);
1564
1565 rewriter.replaceOp(op, result);
1566 return success();
1567 }
1568};
1569
1570class AllocaToGlobalPattern : public OpConversionPattern<LLVM::AllocaOp> {
1571 using OpConversionPattern::OpConversionPattern;
1572 LogicalResult
1573 matchAndRewrite(LLVM::AllocaOp op, LLVM::AllocaOp::Adaptor adaptor,
1574 ConversionPatternRewriter &rewriter) const override {
1575 auto ptrType = cast<LLVM::LLVMPointerType>(op.getType());
1576 auto addrSpace = ptrType.getAddressSpace();
1577 if (addrSpace != 3)
1578 return failure();
1579 auto symTable = op->getParentWithTrait<OpTrait::SymbolTable>();
1580 if (!symTable)
1581 return failure();
1582 Block *moduleBody;
1583 if (ModuleOp mod = dyn_cast<ModuleOp>(*symTable)) {
1584 moduleBody = mod.getBody();
1585 } else if (gpu::GPUModuleOp gpuMod =
1586 dyn_cast<gpu::GPUModuleOp>(*symTable)) {
1587 moduleBody = gpuMod.getBody();
1588 } else {
1589 return failure();
1590 }
1591 auto val = op.getArraySize();
1592 APInt cst;
1593 if (!matchPattern(val, m_ConstantInt(&cst)))
1594 return failure();
1595 auto loc = op.getLoc();
1596 auto globalType = LLVM::LLVMArrayType::get(
1597 rewriter.getContext(), op.getElemType(), cst.getZExtValue());
1598 LLVM::GlobalOp globalVar;
1599 {
1600 OpBuilder::InsertionGuard guard(rewriter);
1601 rewriter.setInsertionPointToStart(moduleBody);
1602 auto alignment = op.getAlignment();
1603 globalVar = LLVM::GlobalOp::create(
1604 rewriter, loc, globalType, /*isConstant=*/false,
1605 /*linkage=*/LLVM::Linkage::Internal,
1606 /*name=*/std::string("__global_alloca_") +
1607 std::to_string(getNextGlobalIdx()),
1608 /*value=*/Attribute(),
1609 /*alignment=*/alignment ? *alignment : 0, /*addrSpace=*/addrSpace);
1610 }
1611 rewriter.replaceOpWithNewOp<LLVM::AddressOfOp>(op, globalVar);
1612 return success();
1613 }
1614
1615private:
1616 static unsigned getNextGlobalIdx() {
1617 static unsigned globalIdx = 0;
1618 return globalIdx++;
1619 }
1620};
1621
1622// Checks if shufflevector is used as a way to extract a contiguous slice
1623// from a vector.
1624// - source vector V1 and V2 are the same vector.
1625// - mask size is not greater than the source vector size
1626// - mask values represent a sequence of consecutive increasing numbers
1627// that stay in bounds of the source vector when used for indexing.
1628static bool isExtractingContiguousSlice(LLVM::ShuffleVectorOp op) {
1629 if (op.getV1() != op.getV2())
1630 return false;
1631 auto maskAttr = op.getMask();
1632 int64_t maskSize = static_cast<int64_t>(maskAttr.size());
1633 int64_t sourceSize = op.getV1().getType().getNumElements();
1634 if (maskSize > sourceSize)
1635 return false;
1636 int64_t firstIndex = maskAttr[0];
1637 for (int64_t i = 1; i < maskSize; ++i) {
1638 int64_t index = maskAttr[i];
1639 if (index != firstIndex + i)
1640 return false;
1641 if (index >= sourceSize)
1642 return false;
1643 }
1644 return true;
1645}
1646
1647// Input vector of a shuffle vector op extracting a contiguous slice is an
1648// illegal vector in SPIRV kernel if the vector size is > 16 elements.
1649// To legalize this case, keep applying the following transformations until no
1650// more match:
1651// 1. keep hoisting the shuffle vector op past unary element-wise operations
1652// start with fpext, fptrunc and bitcast for now.
1653// 2. merge with another shuffle vector op
1654// 3. merge with load as a smaller load
1655class HandleVectorExtractPattern
1656 : public OpRewritePattern<LLVM::ShuffleVectorOp> {
1657 using OpRewritePattern<LLVM::ShuffleVectorOp>::OpRewritePattern;
1658
1659 void initialize() { setHasBoundedRewriteRecursion(); }
1660
1661 LogicalResult matchAndRewrite(LLVM::ShuffleVectorOp op,
1662 PatternRewriter &rewriter) const override {
1663
1664 if (!isExtractingContiguousSlice(op))
1665 return failure();
1666
1667 auto mask = op.getMask();
1668 auto loc = op.getLoc();
1669 auto ty = op.getType();
1670 // Check source operand to determine rewrite pattern.
1671 auto src = op.getV1();
1672 // 1. Hoist past unary element-wise operations
1673 if (auto srcOp = src.getDefiningOp()) {
1674 if (isa<LLVM::FPExtOp>(srcOp) || isa<LLVM::FPTruncOp>(srcOp)) {
1675 Value srcInput = srcOp->getOperand(0);
1676 // Create new shuffle vector op with unary input as source.
1677 auto srcVecTy = dyn_cast<VectorType>(srcInput.getType());
1678 auto newShuffleVecTy =
1679 VectorType::get(mask.size(), srcVecTy.getElementType());
1680 auto newShuffle = LLVM::ShuffleVectorOp::create(
1681 rewriter, loc, newShuffleVecTy, srcInput, srcInput, mask);
1682 // Create new unary op with new shuffle as input.
1683 Value newUnaryOp;
1684 if (isa<LLVM::FPExtOp>(srcOp)) {
1685 newUnaryOp = LLVM::FPExtOp::create(rewriter, loc, ty, newShuffle);
1686 } else {
1687 newUnaryOp = LLVM::FPTruncOp::create(rewriter, loc, ty, newShuffle);
1688 }
1689 rewriter.replaceOp(op, newUnaryOp);
1690 } else if (isa<LLVM::BitcastOp>(srcOp)) {
1691 Value srcInput = srcOp->getOperand(0);
1692 // Create new shuffle vector op with unary input as source.
1693 auto srcInputVecTy = dyn_cast<VectorType>(srcInput.getType());
1694 auto srcInputSize = srcInputVecTy.getNumElements();
1695 auto srcResVecTy = dyn_cast<VectorType>(srcOp->getResult(0).getType());
1696 auto srcResSize = srcResVecTy.getNumElements();
1697 auto maskSize = static_cast<int32_t>(mask.size());
1698 if (srcInputSize > srcResSize) {
1699 return failure();
1700 }
1701 if (srcResSize % srcInputSize != 0) {
1702 return failure();
1703 }
1704 auto maskScale = srcResSize / srcInputSize;
1705 if (maskScale != 1) {
1706 if (mask[0] % maskScale != 0) {
1707 return failure();
1708 }
1709 // Create a new mask that maps to the source vector
1710 SmallVector<int32_t> newMask;
1711 int32_t newMaskSize = maskSize / maskScale;
1712 int32_t maskStart = mask[0] / maskScale;
1713 for (int32_t i = 0; i < newMaskSize; ++i) {
1714 newMask.push_back(maskStart + i);
1715 }
1716 mask = newMask;
1717 }
1718 auto newShuffleVecTy =
1719 VectorType::get(srcInputSize, srcInputVecTy.getElementType());
1720 auto newShuffle = LLVM::ShuffleVectorOp::create(
1721 rewriter, loc, newShuffleVecTy, srcInput, srcInput, mask);
1722 // Create new unary op with new shuffle as input.
1723 auto newBitcast =
1724 LLVM::BitcastOp::create(rewriter, loc, ty, newShuffle);
1725 rewriter.replaceOp(op, newBitcast);
1726 } else if (isa<LLVM::ShuffleVectorOp>(srcOp)) {
1727 // 2. Merge with source shuffle vector op if, the source op is
1728 // also extracting a contigous slice and create a new
1729 // shuffle vector op directly from the source of
1730 // the first shuffle.
1731 auto srcShuffle = cast<LLVM::ShuffleVectorOp>(srcOp);
1732 if (!isExtractingContiguousSlice(srcShuffle))
1733 return failure();
1734 auto srcMask = srcShuffle.getMask();
1735 SmallVector<int32_t> combinedMask;
1736 for (auto index : mask) {
1737 combinedMask.push_back(srcMask[index]);
1738 }
1739 auto newShuffle = LLVM::ShuffleVectorOp::create(
1740 rewriter, loc, ty, srcShuffle.getV1(), srcShuffle.getV1(),
1741 DenseI32ArrayAttr::get(rewriter.getContext(), combinedMask));
1742 rewriter.replaceOp(op, newShuffle);
1743 } else if (isa<LLVM::LoadOp>(srcOp)) {
1744 // 3. Merge with load as a smaller load
1745 auto loadOp = cast<LLVM::LoadOp>(srcOp);
1746 auto loadPtr = loadOp.getAddr();
1747 auto loadAddrSpace = loadPtr.getType().getAddressSpace();
1748 if (loadAddrSpace != 0)
1749 return failure();
1750 auto loadTy = dyn_cast<VectorType>(loadOp.getType());
1751 auto elemTy = loadTy.getElementType();
1752 auto firstIndex = mask[0];
1753 auto newVecTy = VectorType::get(mask.size(), elemTy);
1754 // GEPOp is needed if first index is not zero
1755 if (firstIndex) {
1756 auto newPtr = LLVM::GEPOp::create(
1757 rewriter, loc,
1758 LLVM::LLVMPointerType::get(rewriter.getContext(), loadAddrSpace),
1759 elemTy, loadPtr, ArrayRef<LLVM::GEPArg>{firstIndex});
1760 auto newLoad = LLVM::LoadOp::create(rewriter, loc, newVecTy, newPtr);
1761 rewriter.replaceOp(op, newLoad);
1762 } else {
1763 auto newLoad = LLVM::LoadOp::create(rewriter, loc, newVecTy, loadPtr);
1764 rewriter.replaceOp(op, newLoad);
1765 }
1766 } else {
1767 return failure();
1768 }
1769 } else {
1770 // No defining op (e.g. function argument): nothing to hoist/merge.
1771 return failure();
1772 }
1773 return success();
1774 }
1775};
1776
1777//===----------------------------------------------------------------------===//
1778// Pass Definition
1779//===----------------------------------------------------------------------===//
1780
1781struct ConvertXeVMToLLVMPass
1782 : public impl::ConvertXeVMToLLVMPassBase<ConvertXeVMToLLVMPass> {
1783 using Base::Base;
1784
1785 void getDependentDialects(DialectRegistry &registry) const override {
1786 registry.insert<LLVM::LLVMDialect, XeVMDialect>();
1787 }
1788
1789 void runOnOperation() override {
1790 ConversionTarget target(getContext());
1791 RewritePatternSet patterns(&getContext());
1793 if (failed(applyPartialConversion(getOperation(), target,
1794 std::move(patterns))))
1795 signalPassFailure();
1796
1797 // Apply in-dialect lowerings to handle illegal vectors
1798 {
1799 RewritePatternSet vectorPatterns(&getContext());
1800 vectorPatterns.add<HandleVectorExtractPattern>(&getContext());
1801 GreedyRewriteConfig config{};
1802 // folding can remove ops with temporary attributes used to
1803 // represent LLVM metadata, so disable it here.
1804 // Effectively just this single pattern is applied without any
1805 // op folding patterns from dialects.
1806 config.enableFolding(false);
1807 // config.setMaxIterations(GreedyRewriteConfig::kNoLimit);
1808 // config.setMaxNumRewrites(GreedyRewriteConfig::kNoLimit);
1809 (void)applyPatternsGreedily(getOperation(), std::move(vectorPatterns),
1810 config);
1811 }
1812 }
1813};
1814} // namespace
1815
1816//===----------------------------------------------------------------------===//
1817// Pattern Population
1818//===----------------------------------------------------------------------===//
1819
1820void ::mlir::populateXeVMToLLVMConversionPatterns(ConversionTarget &target,
1821 RewritePatternSet &patterns) {
1822 // some LLVM operations need to be converted.
1823 target.addDynamicallyLegalDialect<LLVM::LLVMDialect>([](Operation *op) {
1824 // llvm alloca op with addrspace 3 for OpenCL (Workgroup) is not handled
1825 // properly by SPIRV backend. It needs to be rewritten as a sequence with
1826 // llvm global.
1827 if (isa<LLVM::AllocaOp>(op)) {
1828 LLVM::AllocaOp aOp = cast<LLVM::AllocaOp>(op);
1829 LLVM::LLVMPointerType pTy = cast<LLVM::LLVMPointerType>(aOp.getType());
1830 auto addrSpace = pTy.getAddressSpace();
1831 return addrSpace != 3;
1832 }
1833 // cache_control attribute should be converted.
1834 return !op->hasAttr("cache_control");
1835 });
1836 target.addIllegalDialect<XeVMDialect>();
1837 patterns.add<LoadStorePrefetchToOCLPattern<BlockLoad2dOp>,
1838 LoadStorePrefetchToOCLPattern<BlockStore2dOp>,
1839 LoadStorePrefetchToOCLPattern<BlockPrefetch2dOp>,
1840 MMAToOCLPattern, MemfenceToOCLPattern, PrefetchToOCLPattern,
1841 LLVMLoadStoreToOCLPattern<LLVM::LoadOp>,
1842 LLVMLoadStoreToOCLPattern<LLVM::StoreOp>,
1843 BlockLoadStore1DToOCLPattern<BlockLoadOp>,
1844 BlockLoadStore1DToOCLPattern<BlockStoreOp>,
1845 LaunchConfigOpToOCLPattern<WorkitemIdXOp>,
1846 LaunchConfigOpToOCLPattern<WorkitemIdYOp>,
1847 LaunchConfigOpToOCLPattern<WorkitemIdZOp>,
1848 LaunchConfigOpToOCLPattern<WorkgroupDimXOp>,
1849 LaunchConfigOpToOCLPattern<WorkgroupDimYOp>,
1850 LaunchConfigOpToOCLPattern<WorkgroupDimZOp>,
1851 LaunchConfigOpToOCLPattern<WorkgroupIdXOp>,
1852 LaunchConfigOpToOCLPattern<WorkgroupIdYOp>,
1853 LaunchConfigOpToOCLPattern<WorkgroupIdZOp>,
1854 LaunchConfigOpToOCLPattern<GridDimXOp>,
1855 LaunchConfigOpToOCLPattern<GridDimYOp>,
1856 LaunchConfigOpToOCLPattern<GridDimZOp>,
1857 SubgroupOpWorkitemOpToOCLPattern<LaneIdOp>,
1858 SubgroupOpWorkitemOpToOCLPattern<SubgroupIdOp>,
1859 SubgroupOpWorkitemOpToOCLPattern<SubgroupSizeOp>,
1860 TruncfToOCLPattern, ExtfToOCLPattern, MMAMxToOCLPattern,
1861 BitcastShuffleToGenISAPattern, AllocaToGlobalPattern>(
1862 patterns.getContext());
1863}
return success()
LogicalResult initialize(unsigned origNumLoops, ArrayRef< ReassociationIndices > foldedIterationDims)
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
ArrayAttr()
b getContext())
Attributes are known-constant values of operations.
Definition Attributes.h:25
MLIRContext * getContext() const
Definition Builders.h:56
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition Location.h:76
RAII guard to reset the insertion point of the builder when destroyed.
Definition Builders.h:351
A trait used to provide symbol table functionalities to a region operation.
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:731
Operation * getParentWithTrait()
Returns the closest surrounding parent operation with trait Trait.
Definition Operation.h:273
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:240
Block & front()
Definition Region.h:65
MLIRContext * getContext() const
RewritePatternSet & add(ConstructorArg &&arg, ConstructorArgs &&...args)
Add an instance of each of the pattern types 'Ts' to the pattern list with the given arguments.
virtual void replaceOp(Operation *op, ValueRange newValues)
Replace the results of the given (original) operation with the specified list of values (replacements...
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Operation * getDefiningOp() const
If this value is the result of an operation, return the operation that defines it.
Definition Value.cpp:18
static DenseArrayAttrImpl get(MLIRContext *context, ArrayRef< int32_t > content)
FailureOr< LLVM::LLVMFuncOp > lookupOrCreateFn(OpBuilder &b, Operation *moduleOp, StringRef name, ArrayRef< Type > paramTypes={}, Type resultType={}, bool isVarArg=false, bool isReserved=false, SymbolTableCollection *symbolTables=nullptr)
Create a FuncOp with signature resultType(paramTypes) and name name`.
detail::InFlightRemark failed(Location loc, RemarkOpts opts)
Report an optimization remark that failed.
Definition Remarks.h:717
Include the generated interface declarations.
bool matchPattern(Value value, const Pattern &pattern)
Entry point for matching a pattern over a Value.
Definition Matchers.h:490
detail::constant_int_value_binder m_ConstantInt(IntegerAttr::ValueType *bind_value)
Matches a constant holding a scalar/vector/tensor integer (splat) and writes the integer value to bin...
Definition Matchers.h:527
LogicalResult applyPatternsGreedily(Region &region, const FrozenRewritePatternSet &patterns, GreedyRewriteConfig config=GreedyRewriteConfig(), bool *changed=nullptr)
Rewrite ops in the given region, which must be isolated from above, by repeatedly applying the highes...
void populateXeVMToLLVMConversionPatterns(ConversionTarget &target, RewritePatternSet &patterns)
llvm::TypeSwitch< T, ResultT > TypeSwitch
Definition LLVM.h:139
OpRewritePattern is a wrapper around RewritePattern that allows for matching and rewriting against an...