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