xref: /freebsd/contrib/llvm-project/llvm/lib/Target/SPIRV/SPIRVUtils.cpp (revision 2e3507c25e42292b45a5482e116d278f5515d04d)
1 //===--- SPIRVUtils.cpp ---- SPIR-V Utility Functions -----------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, 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 //
9 // This file contains miscellaneous utility functions.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "SPIRVUtils.h"
14 #include "MCTargetDesc/SPIRVBaseInfo.h"
15 #include "SPIRV.h"
16 #include "SPIRVInstrInfo.h"
17 #include "llvm/ADT/StringRef.h"
18 #include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h"
19 #include "llvm/CodeGen/MachineInstr.h"
20 #include "llvm/CodeGen/MachineInstrBuilder.h"
21 #include "llvm/Demangle/Demangle.h"
22 #include "llvm/IR/IntrinsicsSPIRV.h"
23 
24 namespace llvm {
25 
26 // The following functions are used to add these string literals as a series of
27 // 32-bit integer operands with the correct format, and unpack them if necessary
28 // when making string comparisons in compiler passes.
29 // SPIR-V requires null-terminated UTF-8 strings padded to 32-bit alignment.
30 static uint32_t convertCharsToWord(const StringRef &Str, unsigned i) {
31   uint32_t Word = 0u; // Build up this 32-bit word from 4 8-bit chars.
32   for (unsigned WordIndex = 0; WordIndex < 4; ++WordIndex) {
33     unsigned StrIndex = i + WordIndex;
34     uint8_t CharToAdd = 0;       // Initilize char as padding/null.
35     if (StrIndex < Str.size()) { // If it's within the string, get a real char.
36       CharToAdd = Str[StrIndex];
37     }
38     Word |= (CharToAdd << (WordIndex * 8));
39   }
40   return Word;
41 }
42 
43 // Get length including padding and null terminator.
44 static size_t getPaddedLen(const StringRef &Str) {
45   const size_t Len = Str.size() + 1;
46   return (Len % 4 == 0) ? Len : Len + (4 - (Len % 4));
47 }
48 
49 void addStringImm(const StringRef &Str, MCInst &Inst) {
50   const size_t PaddedLen = getPaddedLen(Str);
51   for (unsigned i = 0; i < PaddedLen; i += 4) {
52     // Add an operand for the 32-bits of chars or padding.
53     Inst.addOperand(MCOperand::createImm(convertCharsToWord(Str, i)));
54   }
55 }
56 
57 void addStringImm(const StringRef &Str, MachineInstrBuilder &MIB) {
58   const size_t PaddedLen = getPaddedLen(Str);
59   for (unsigned i = 0; i < PaddedLen; i += 4) {
60     // Add an operand for the 32-bits of chars or padding.
61     MIB.addImm(convertCharsToWord(Str, i));
62   }
63 }
64 
65 void addStringImm(const StringRef &Str, IRBuilder<> &B,
66                   std::vector<Value *> &Args) {
67   const size_t PaddedLen = getPaddedLen(Str);
68   for (unsigned i = 0; i < PaddedLen; i += 4) {
69     // Add a vector element for the 32-bits of chars or padding.
70     Args.push_back(B.getInt32(convertCharsToWord(Str, i)));
71   }
72 }
73 
74 std::string getStringImm(const MachineInstr &MI, unsigned StartIndex) {
75   return getSPIRVStringOperand(MI, StartIndex);
76 }
77 
78 void addNumImm(const APInt &Imm, MachineInstrBuilder &MIB) {
79   const auto Bitwidth = Imm.getBitWidth();
80   if (Bitwidth == 1)
81     return; // Already handled
82   else if (Bitwidth <= 32) {
83     MIB.addImm(Imm.getZExtValue());
84     return;
85   } else if (Bitwidth <= 64) {
86     uint64_t FullImm = Imm.getZExtValue();
87     uint32_t LowBits = FullImm & 0xffffffff;
88     uint32_t HighBits = (FullImm >> 32) & 0xffffffff;
89     MIB.addImm(LowBits).addImm(HighBits);
90     return;
91   }
92   report_fatal_error("Unsupported constant bitwidth");
93 }
94 
95 void buildOpName(Register Target, const StringRef &Name,
96                  MachineIRBuilder &MIRBuilder) {
97   if (!Name.empty()) {
98     auto MIB = MIRBuilder.buildInstr(SPIRV::OpName).addUse(Target);
99     addStringImm(Name, MIB);
100   }
101 }
102 
103 static void finishBuildOpDecorate(MachineInstrBuilder &MIB,
104                                   const std::vector<uint32_t> &DecArgs,
105                                   StringRef StrImm) {
106   if (!StrImm.empty())
107     addStringImm(StrImm, MIB);
108   for (const auto &DecArg : DecArgs)
109     MIB.addImm(DecArg);
110 }
111 
112 void buildOpDecorate(Register Reg, MachineIRBuilder &MIRBuilder,
113                      SPIRV::Decoration::Decoration Dec,
114                      const std::vector<uint32_t> &DecArgs, StringRef StrImm) {
115   auto MIB = MIRBuilder.buildInstr(SPIRV::OpDecorate)
116                  .addUse(Reg)
117                  .addImm(static_cast<uint32_t>(Dec));
118   finishBuildOpDecorate(MIB, DecArgs, StrImm);
119 }
120 
121 void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII,
122                      SPIRV::Decoration::Decoration Dec,
123                      const std::vector<uint32_t> &DecArgs, StringRef StrImm) {
124   MachineBasicBlock &MBB = *I.getParent();
125   auto MIB = BuildMI(MBB, I, I.getDebugLoc(), TII.get(SPIRV::OpDecorate))
126                  .addUse(Reg)
127                  .addImm(static_cast<uint32_t>(Dec));
128   finishBuildOpDecorate(MIB, DecArgs, StrImm);
129 }
130 
131 // TODO: maybe the following two functions should be handled in the subtarget
132 // to allow for different OpenCL vs Vulkan handling.
133 unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) {
134   switch (SC) {
135   case SPIRV::StorageClass::Function:
136     return 0;
137   case SPIRV::StorageClass::CrossWorkgroup:
138     return 1;
139   case SPIRV::StorageClass::UniformConstant:
140     return 2;
141   case SPIRV::StorageClass::Workgroup:
142     return 3;
143   case SPIRV::StorageClass::Generic:
144     return 4;
145   case SPIRV::StorageClass::Input:
146     return 7;
147   default:
148     llvm_unreachable("Unable to get address space id");
149   }
150 }
151 
152 SPIRV::StorageClass::StorageClass
153 addressSpaceToStorageClass(unsigned AddrSpace) {
154   switch (AddrSpace) {
155   case 0:
156     return SPIRV::StorageClass::Function;
157   case 1:
158     return SPIRV::StorageClass::CrossWorkgroup;
159   case 2:
160     return SPIRV::StorageClass::UniformConstant;
161   case 3:
162     return SPIRV::StorageClass::Workgroup;
163   case 4:
164     return SPIRV::StorageClass::Generic;
165   case 7:
166     return SPIRV::StorageClass::Input;
167   default:
168     llvm_unreachable("Unknown address space");
169   }
170 }
171 
172 SPIRV::MemorySemantics::MemorySemantics
173 getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC) {
174   switch (SC) {
175   case SPIRV::StorageClass::StorageBuffer:
176   case SPIRV::StorageClass::Uniform:
177     return SPIRV::MemorySemantics::UniformMemory;
178   case SPIRV::StorageClass::Workgroup:
179     return SPIRV::MemorySemantics::WorkgroupMemory;
180   case SPIRV::StorageClass::CrossWorkgroup:
181     return SPIRV::MemorySemantics::CrossWorkgroupMemory;
182   case SPIRV::StorageClass::AtomicCounter:
183     return SPIRV::MemorySemantics::AtomicCounterMemory;
184   case SPIRV::StorageClass::Image:
185     return SPIRV::MemorySemantics::ImageMemory;
186   default:
187     return SPIRV::MemorySemantics::None;
188   }
189 }
190 
191 SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
192   switch (Ord) {
193   case AtomicOrdering::Acquire:
194     return SPIRV::MemorySemantics::Acquire;
195   case AtomicOrdering::Release:
196     return SPIRV::MemorySemantics::Release;
197   case AtomicOrdering::AcquireRelease:
198     return SPIRV::MemorySemantics::AcquireRelease;
199   case AtomicOrdering::SequentiallyConsistent:
200     return SPIRV::MemorySemantics::SequentiallyConsistent;
201   case AtomicOrdering::Unordered:
202   case AtomicOrdering::Monotonic:
203   case AtomicOrdering::NotAtomic:
204     return SPIRV::MemorySemantics::None;
205   }
206   llvm_unreachable(nullptr);
207 }
208 
209 MachineInstr *getDefInstrMaybeConstant(Register &ConstReg,
210                                        const MachineRegisterInfo *MRI) {
211   MachineInstr *ConstInstr = MRI->getVRegDef(ConstReg);
212   if (ConstInstr->getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
213       ConstInstr->getIntrinsicID() == Intrinsic::spv_track_constant) {
214     ConstReg = ConstInstr->getOperand(2).getReg();
215     ConstInstr = MRI->getVRegDef(ConstReg);
216   } else if (ConstInstr->getOpcode() == SPIRV::ASSIGN_TYPE) {
217     ConstReg = ConstInstr->getOperand(1).getReg();
218     ConstInstr = MRI->getVRegDef(ConstReg);
219   }
220   return ConstInstr;
221 }
222 
223 uint64_t getIConstVal(Register ConstReg, const MachineRegisterInfo *MRI) {
224   const MachineInstr *MI = getDefInstrMaybeConstant(ConstReg, MRI);
225   assert(MI && MI->getOpcode() == TargetOpcode::G_CONSTANT);
226   return MI->getOperand(1).getCImm()->getValue().getZExtValue();
227 }
228 
229 bool isSpvIntrinsic(MachineInstr &MI, Intrinsic::ID IntrinsicID) {
230   return MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
231          MI.getIntrinsicID() == IntrinsicID;
232 }
233 
234 Type *getMDOperandAsType(const MDNode *N, unsigned I) {
235   return cast<ValueAsMetadata>(N->getOperand(I))->getType();
236 }
237 
238 // The set of names is borrowed from the SPIR-V translator.
239 // TODO: may be implemented in SPIRVBuiltins.td.
240 static bool isPipeOrAddressSpaceCastBI(const StringRef MangledName) {
241   return MangledName == "write_pipe_2" || MangledName == "read_pipe_2" ||
242          MangledName == "write_pipe_2_bl" || MangledName == "read_pipe_2_bl" ||
243          MangledName == "write_pipe_4" || MangledName == "read_pipe_4" ||
244          MangledName == "reserve_write_pipe" ||
245          MangledName == "reserve_read_pipe" ||
246          MangledName == "commit_write_pipe" ||
247          MangledName == "commit_read_pipe" ||
248          MangledName == "work_group_reserve_write_pipe" ||
249          MangledName == "work_group_reserve_read_pipe" ||
250          MangledName == "work_group_commit_write_pipe" ||
251          MangledName == "work_group_commit_read_pipe" ||
252          MangledName == "get_pipe_num_packets_ro" ||
253          MangledName == "get_pipe_max_packets_ro" ||
254          MangledName == "get_pipe_num_packets_wo" ||
255          MangledName == "get_pipe_max_packets_wo" ||
256          MangledName == "sub_group_reserve_write_pipe" ||
257          MangledName == "sub_group_reserve_read_pipe" ||
258          MangledName == "sub_group_commit_write_pipe" ||
259          MangledName == "sub_group_commit_read_pipe" ||
260          MangledName == "to_global" || MangledName == "to_local" ||
261          MangledName == "to_private";
262 }
263 
264 static bool isEnqueueKernelBI(const StringRef MangledName) {
265   return MangledName == "__enqueue_kernel_basic" ||
266          MangledName == "__enqueue_kernel_basic_events" ||
267          MangledName == "__enqueue_kernel_varargs" ||
268          MangledName == "__enqueue_kernel_events_varargs";
269 }
270 
271 static bool isKernelQueryBI(const StringRef MangledName) {
272   return MangledName == "__get_kernel_work_group_size_impl" ||
273          MangledName == "__get_kernel_sub_group_count_for_ndrange_impl" ||
274          MangledName == "__get_kernel_max_sub_group_size_for_ndrange_impl" ||
275          MangledName == "__get_kernel_preferred_work_group_size_multiple_impl";
276 }
277 
278 static bool isNonMangledOCLBuiltin(StringRef Name) {
279   if (!Name.startswith("__"))
280     return false;
281 
282   return isEnqueueKernelBI(Name) || isKernelQueryBI(Name) ||
283          isPipeOrAddressSpaceCastBI(Name.drop_front(2)) ||
284          Name == "__translate_sampler_initializer";
285 }
286 
287 std::string getOclOrSpirvBuiltinDemangledName(StringRef Name) {
288   bool IsNonMangledOCL = isNonMangledOCLBuiltin(Name);
289   bool IsNonMangledSPIRV = Name.startswith("__spirv_");
290   bool IsMangled = Name.startswith("_Z");
291 
292   if (!IsNonMangledOCL && !IsNonMangledSPIRV && !IsMangled)
293     return std::string();
294 
295   // Try to use the itanium demangler.
296   if (char *DemangledName = itaniumDemangle(Name.data())) {
297     std::string Result = DemangledName;
298     free(DemangledName);
299     return Result;
300   }
301   // Otherwise use simple demangling to return the function name.
302   if (IsNonMangledOCL || IsNonMangledSPIRV)
303     return Name.str();
304 
305   // Autocheck C++, maybe need to do explicit check of the source language.
306   // OpenCL C++ built-ins are declared in cl namespace.
307   // TODO: consider using 'St' abbriviation for cl namespace mangling.
308   // Similar to ::std:: in C++.
309   size_t Start, Len = 0;
310   size_t DemangledNameLenStart = 2;
311   if (Name.startswith("_ZN")) {
312     // Skip CV and ref qualifiers.
313     size_t NameSpaceStart = Name.find_first_not_of("rVKRO", 3);
314     // All built-ins are in the ::cl:: namespace.
315     if (Name.substr(NameSpaceStart, 11) != "2cl7__spirv")
316       return std::string();
317     DemangledNameLenStart = NameSpaceStart + 11;
318   }
319   Start = Name.find_first_not_of("0123456789", DemangledNameLenStart);
320   Name.substr(DemangledNameLenStart, Start - DemangledNameLenStart)
321       .getAsInteger(10, Len);
322   return Name.substr(Start, Len).str();
323 }
324 
325 const Type *getTypedPtrEltType(const Type *Ty) {
326   auto PType = dyn_cast<PointerType>(Ty);
327   if (!PType || PType->isOpaque())
328     return Ty;
329   return PType->getNonOpaquePointerElementType();
330 }
331 
332 static bool hasBuiltinTypePrefix(StringRef Name) {
333   if (Name.starts_with("opencl.") || Name.starts_with("spirv."))
334     return true;
335   return false;
336 }
337 
338 bool isSpecialOpaqueType(const Type *Ty) {
339   const StructType *SType = dyn_cast<StructType>(getTypedPtrEltType(Ty));
340   if (SType && SType->hasName())
341     return hasBuiltinTypePrefix(SType->getName());
342 
343   if (const TargetExtType *EType =
344           dyn_cast<TargetExtType>(getTypedPtrEltType(Ty)))
345     return hasBuiltinTypePrefix(EType->getName());
346 
347   return false;
348 }
349 } // namespace llvm
350