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