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
24namespace 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.
30static 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.
44static 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
49void 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
57void 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
65void 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
74std::string getStringImm(const MachineInstr &MI, unsigned StartIndex) {
75  return getSPIRVStringOperand(MI, StartIndex);
76}
77
78void addNumImm(const APInt &Imm, MachineInstrBuilder &MIB) {
79  const auto Bitwidth = Imm.getBitWidth();
80  switch (Bitwidth) {
81  case 1:
82    break; // Already handled.
83  case 8:
84  case 16:
85  case 32:
86    MIB.addImm(Imm.getZExtValue());
87    break;
88  case 64: {
89    uint64_t FullImm = Imm.getZExtValue();
90    uint32_t LowBits = FullImm & 0xffffffff;
91    uint32_t HighBits = (FullImm >> 32) & 0xffffffff;
92    MIB.addImm(LowBits).addImm(HighBits);
93    break;
94  }
95  default:
96    report_fatal_error("Unsupported constant bitwidth");
97  }
98}
99
100void buildOpName(Register Target, const StringRef &Name,
101                 MachineIRBuilder &MIRBuilder) {
102  if (!Name.empty()) {
103    auto MIB = MIRBuilder.buildInstr(SPIRV::OpName).addUse(Target);
104    addStringImm(Name, MIB);
105  }
106}
107
108static void finishBuildOpDecorate(MachineInstrBuilder &MIB,
109                                  const std::vector<uint32_t> &DecArgs,
110                                  StringRef StrImm) {
111  if (!StrImm.empty())
112    addStringImm(StrImm, MIB);
113  for (const auto &DecArg : DecArgs)
114    MIB.addImm(DecArg);
115}
116
117void buildOpDecorate(Register Reg, MachineIRBuilder &MIRBuilder,
118                     SPIRV::Decoration::Decoration Dec,
119                     const std::vector<uint32_t> &DecArgs, StringRef StrImm) {
120  auto MIB = MIRBuilder.buildInstr(SPIRV::OpDecorate)
121                 .addUse(Reg)
122                 .addImm(static_cast<uint32_t>(Dec));
123  finishBuildOpDecorate(MIB, DecArgs, StrImm);
124}
125
126void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII,
127                     SPIRV::Decoration::Decoration Dec,
128                     const std::vector<uint32_t> &DecArgs, StringRef StrImm) {
129  MachineBasicBlock &MBB = *I.getParent();
130  auto MIB = BuildMI(MBB, I, I.getDebugLoc(), TII.get(SPIRV::OpDecorate))
131                 .addUse(Reg)
132                 .addImm(static_cast<uint32_t>(Dec));
133  finishBuildOpDecorate(MIB, DecArgs, StrImm);
134}
135
136// TODO: maybe the following two functions should be handled in the subtarget
137// to allow for different OpenCL vs Vulkan handling.
138unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) {
139  switch (SC) {
140  case SPIRV::StorageClass::Function:
141    return 0;
142  case SPIRV::StorageClass::CrossWorkgroup:
143    return 1;
144  case SPIRV::StorageClass::UniformConstant:
145    return 2;
146  case SPIRV::StorageClass::Workgroup:
147    return 3;
148  case SPIRV::StorageClass::Generic:
149    return 4;
150  case SPIRV::StorageClass::Input:
151    return 7;
152  default:
153    llvm_unreachable("Unable to get address space id");
154  }
155}
156
157SPIRV::StorageClass::StorageClass
158addressSpaceToStorageClass(unsigned AddrSpace) {
159  switch (AddrSpace) {
160  case 0:
161    return SPIRV::StorageClass::Function;
162  case 1:
163    return SPIRV::StorageClass::CrossWorkgroup;
164  case 2:
165    return SPIRV::StorageClass::UniformConstant;
166  case 3:
167    return SPIRV::StorageClass::Workgroup;
168  case 4:
169    return SPIRV::StorageClass::Generic;
170  case 7:
171    return SPIRV::StorageClass::Input;
172  default:
173    llvm_unreachable("Unknown address space");
174  }
175}
176
177SPIRV::MemorySemantics::MemorySemantics
178getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC) {
179  switch (SC) {
180  case SPIRV::StorageClass::StorageBuffer:
181  case SPIRV::StorageClass::Uniform:
182    return SPIRV::MemorySemantics::UniformMemory;
183  case SPIRV::StorageClass::Workgroup:
184    return SPIRV::MemorySemantics::WorkgroupMemory;
185  case SPIRV::StorageClass::CrossWorkgroup:
186    return SPIRV::MemorySemantics::CrossWorkgroupMemory;
187  case SPIRV::StorageClass::AtomicCounter:
188    return SPIRV::MemorySemantics::AtomicCounterMemory;
189  case SPIRV::StorageClass::Image:
190    return SPIRV::MemorySemantics::ImageMemory;
191  default:
192    return SPIRV::MemorySemantics::None;
193  }
194}
195
196SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) {
197  switch (Ord) {
198  case AtomicOrdering::Acquire:
199    return SPIRV::MemorySemantics::Acquire;
200  case AtomicOrdering::Release:
201    return SPIRV::MemorySemantics::Release;
202  case AtomicOrdering::AcquireRelease:
203    return SPIRV::MemorySemantics::AcquireRelease;
204  case AtomicOrdering::SequentiallyConsistent:
205    return SPIRV::MemorySemantics::SequentiallyConsistent;
206  case AtomicOrdering::Unordered:
207  case AtomicOrdering::Monotonic:
208  case AtomicOrdering::NotAtomic:
209  default:
210    return SPIRV::MemorySemantics::None;
211  }
212}
213
214MachineInstr *getDefInstrMaybeConstant(Register &ConstReg,
215                                       const MachineRegisterInfo *MRI) {
216  MachineInstr *ConstInstr = MRI->getVRegDef(ConstReg);
217  if (ConstInstr->getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
218      ConstInstr->getIntrinsicID() == Intrinsic::spv_track_constant) {
219    ConstReg = ConstInstr->getOperand(2).getReg();
220    ConstInstr = MRI->getVRegDef(ConstReg);
221  } else if (ConstInstr->getOpcode() == SPIRV::ASSIGN_TYPE) {
222    ConstReg = ConstInstr->getOperand(1).getReg();
223    ConstInstr = MRI->getVRegDef(ConstReg);
224  }
225  return ConstInstr;
226}
227
228uint64_t getIConstVal(Register ConstReg, const MachineRegisterInfo *MRI) {
229  const MachineInstr *MI = getDefInstrMaybeConstant(ConstReg, MRI);
230  assert(MI && MI->getOpcode() == TargetOpcode::G_CONSTANT);
231  return MI->getOperand(1).getCImm()->getValue().getZExtValue();
232}
233
234bool isSpvIntrinsic(MachineInstr &MI, Intrinsic::ID IntrinsicID) {
235  return MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS &&
236         MI.getIntrinsicID() == IntrinsicID;
237}
238
239Type *getMDOperandAsType(const MDNode *N, unsigned I) {
240  return cast<ValueAsMetadata>(N->getOperand(I))->getType();
241}
242
243// The set of names is borrowed from the SPIR-V translator.
244// TODO: may be implemented in SPIRVBuiltins.td.
245static bool isPipeOrAddressSpaceCastBI(const StringRef MangledName) {
246  return MangledName == "write_pipe_2" || MangledName == "read_pipe_2" ||
247         MangledName == "write_pipe_2_bl" || MangledName == "read_pipe_2_bl" ||
248         MangledName == "write_pipe_4" || MangledName == "read_pipe_4" ||
249         MangledName == "reserve_write_pipe" ||
250         MangledName == "reserve_read_pipe" ||
251         MangledName == "commit_write_pipe" ||
252         MangledName == "commit_read_pipe" ||
253         MangledName == "work_group_reserve_write_pipe" ||
254         MangledName == "work_group_reserve_read_pipe" ||
255         MangledName == "work_group_commit_write_pipe" ||
256         MangledName == "work_group_commit_read_pipe" ||
257         MangledName == "get_pipe_num_packets_ro" ||
258         MangledName == "get_pipe_max_packets_ro" ||
259         MangledName == "get_pipe_num_packets_wo" ||
260         MangledName == "get_pipe_max_packets_wo" ||
261         MangledName == "sub_group_reserve_write_pipe" ||
262         MangledName == "sub_group_reserve_read_pipe" ||
263         MangledName == "sub_group_commit_write_pipe" ||
264         MangledName == "sub_group_commit_read_pipe" ||
265         MangledName == "to_global" || MangledName == "to_local" ||
266         MangledName == "to_private";
267}
268
269static bool isEnqueueKernelBI(const StringRef MangledName) {
270  return MangledName == "__enqueue_kernel_basic" ||
271         MangledName == "__enqueue_kernel_basic_events" ||
272         MangledName == "__enqueue_kernel_varargs" ||
273         MangledName == "__enqueue_kernel_events_varargs";
274}
275
276static bool isKernelQueryBI(const StringRef MangledName) {
277  return MangledName == "__get_kernel_work_group_size_impl" ||
278         MangledName == "__get_kernel_sub_group_count_for_ndrange_impl" ||
279         MangledName == "__get_kernel_max_sub_group_size_for_ndrange_impl" ||
280         MangledName == "__get_kernel_preferred_work_group_size_multiple_impl";
281}
282
283static bool isNonMangledOCLBuiltin(StringRef Name) {
284  if (!Name.startswith("__"))
285    return false;
286
287  return isEnqueueKernelBI(Name) || isKernelQueryBI(Name) ||
288         isPipeOrAddressSpaceCastBI(Name.drop_front(2)) ||
289         Name == "__translate_sampler_initializer";
290}
291
292std::string getOclOrSpirvBuiltinDemangledName(StringRef Name) {
293  bool IsNonMangledOCL = isNonMangledOCLBuiltin(Name);
294  bool IsNonMangledSPIRV = Name.startswith("__spirv_");
295  bool IsMangled = Name.startswith("_Z");
296
297  if (!IsNonMangledOCL && !IsNonMangledSPIRV && !IsMangled)
298    return std::string();
299
300  // Try to use the itanium demangler.
301  size_t n;
302  int Status;
303  char *DemangledName = itaniumDemangle(Name.data(), nullptr, &n, &Status);
304
305  if (Status == demangle_success) {
306    std::string Result = DemangledName;
307    free(DemangledName);
308    return Result;
309  }
310  free(DemangledName);
311  // Otherwise use simple demangling to return the function name.
312  if (IsNonMangledOCL || IsNonMangledSPIRV)
313    return Name.str();
314
315  // Autocheck C++, maybe need to do explicit check of the source language.
316  // OpenCL C++ built-ins are declared in cl namespace.
317  // TODO: consider using 'St' abbriviation for cl namespace mangling.
318  // Similar to ::std:: in C++.
319  size_t Start, Len = 0;
320  size_t DemangledNameLenStart = 2;
321  if (Name.startswith("_ZN")) {
322    // Skip CV and ref qualifiers.
323    size_t NameSpaceStart = Name.find_first_not_of("rVKRO", 3);
324    // All built-ins are in the ::cl:: namespace.
325    if (Name.substr(NameSpaceStart, 11) != "2cl7__spirv")
326      return std::string();
327    DemangledNameLenStart = NameSpaceStart + 11;
328  }
329  Start = Name.find_first_not_of("0123456789", DemangledNameLenStart);
330  Name.substr(DemangledNameLenStart, Start - DemangledNameLenStart)
331      .getAsInteger(10, Len);
332  return Name.substr(Start, Len).str();
333}
334
335static bool isOpenCLBuiltinType(const StructType *SType) {
336  return SType->isOpaque() && SType->hasName() &&
337         SType->getName().startswith("opencl.");
338}
339
340static bool isSPIRVBuiltinType(const StructType *SType) {
341  return SType->isOpaque() && SType->hasName() &&
342         SType->getName().startswith("spirv.");
343}
344
345const Type *getTypedPtrEltType(const Type *Ty) {
346  auto PType = dyn_cast<PointerType>(Ty);
347  if (!PType || PType->isOpaque())
348    return Ty;
349  return PType->getNonOpaquePointerElementType();
350}
351
352bool isSpecialOpaqueType(const Type *Ty) {
353  if (auto SType = dyn_cast<StructType>(getTypedPtrEltType(Ty)))
354    return isOpenCLBuiltinType(SType) || isSPIRVBuiltinType(SType);
355  return false;
356}
357} // namespace llvm
358