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