| //===--- SPIRVUtils.cpp ---- SPIR-V Utility Functions -----------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This file contains miscellaneous utility functions. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "SPIRVUtils.h" |
| #include "MCTargetDesc/SPIRVBaseInfo.h" |
| #include "SPIRV.h" |
| #include "SPIRVInstrInfo.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h" |
| #include "llvm/CodeGen/MachineInstr.h" |
| #include "llvm/CodeGen/MachineInstrBuilder.h" |
| #include "llvm/Demangle/Demangle.h" |
| #include "llvm/IR/IntrinsicsSPIRV.h" |
| |
| namespace llvm { |
| |
| // The following functions are used to add these string literals as a series of |
| // 32-bit integer operands with the correct format, and unpack them if necessary |
| // when making string comparisons in compiler passes. |
| // SPIR-V requires null-terminated UTF-8 strings padded to 32-bit alignment. |
| static uint32_t convertCharsToWord(const StringRef &Str, unsigned i) { |
| uint32_t Word = 0u; // Build up this 32-bit word from 4 8-bit chars. |
| for (unsigned WordIndex = 0; WordIndex < 4; ++WordIndex) { |
| unsigned StrIndex = i + WordIndex; |
| uint8_t CharToAdd = 0; // Initilize char as padding/null. |
| if (StrIndex < Str.size()) { // If it's within the string, get a real char. |
| CharToAdd = Str[StrIndex]; |
| } |
| Word |= (CharToAdd << (WordIndex * 8)); |
| } |
| return Word; |
| } |
| |
| // Get length including padding and null terminator. |
| static size_t getPaddedLen(const StringRef &Str) { |
| const size_t Len = Str.size() + 1; |
| return (Len % 4 == 0) ? Len : Len + (4 - (Len % 4)); |
| } |
| |
| void addStringImm(const StringRef &Str, MCInst &Inst) { |
| const size_t PaddedLen = getPaddedLen(Str); |
| for (unsigned i = 0; i < PaddedLen; i += 4) { |
| // Add an operand for the 32-bits of chars or padding. |
| Inst.addOperand(MCOperand::createImm(convertCharsToWord(Str, i))); |
| } |
| } |
| |
| void addStringImm(const StringRef &Str, MachineInstrBuilder &MIB) { |
| const size_t PaddedLen = getPaddedLen(Str); |
| for (unsigned i = 0; i < PaddedLen; i += 4) { |
| // Add an operand for the 32-bits of chars or padding. |
| MIB.addImm(convertCharsToWord(Str, i)); |
| } |
| } |
| |
| void addStringImm(const StringRef &Str, IRBuilder<> &B, |
| std::vector<Value *> &Args) { |
| const size_t PaddedLen = getPaddedLen(Str); |
| for (unsigned i = 0; i < PaddedLen; i += 4) { |
| // Add a vector element for the 32-bits of chars or padding. |
| Args.push_back(B.getInt32(convertCharsToWord(Str, i))); |
| } |
| } |
| |
| std::string getStringImm(const MachineInstr &MI, unsigned StartIndex) { |
| return getSPIRVStringOperand(MI, StartIndex); |
| } |
| |
| void addNumImm(const APInt &Imm, MachineInstrBuilder &MIB) { |
| const auto Bitwidth = Imm.getBitWidth(); |
| switch (Bitwidth) { |
| case 1: |
| break; // Already handled. |
| case 8: |
| case 16: |
| case 32: |
| MIB.addImm(Imm.getZExtValue()); |
| break; |
| case 64: { |
| uint64_t FullImm = Imm.getZExtValue(); |
| uint32_t LowBits = FullImm & 0xffffffff; |
| uint32_t HighBits = (FullImm >> 32) & 0xffffffff; |
| MIB.addImm(LowBits).addImm(HighBits); |
| break; |
| } |
| default: |
| report_fatal_error("Unsupported constant bitwidth"); |
| } |
| } |
| |
| void buildOpName(Register Target, const StringRef &Name, |
| MachineIRBuilder &MIRBuilder) { |
| if (!Name.empty()) { |
| auto MIB = MIRBuilder.buildInstr(SPIRV::OpName).addUse(Target); |
| addStringImm(Name, MIB); |
| } |
| } |
| |
| static void finishBuildOpDecorate(MachineInstrBuilder &MIB, |
| const std::vector<uint32_t> &DecArgs, |
| StringRef StrImm) { |
| if (!StrImm.empty()) |
| addStringImm(StrImm, MIB); |
| for (const auto &DecArg : DecArgs) |
| MIB.addImm(DecArg); |
| } |
| |
| void buildOpDecorate(Register Reg, MachineIRBuilder &MIRBuilder, |
| SPIRV::Decoration::Decoration Dec, |
| const std::vector<uint32_t> &DecArgs, StringRef StrImm) { |
| auto MIB = MIRBuilder.buildInstr(SPIRV::OpDecorate) |
| .addUse(Reg) |
| .addImm(static_cast<uint32_t>(Dec)); |
| finishBuildOpDecorate(MIB, DecArgs, StrImm); |
| } |
| |
| void buildOpDecorate(Register Reg, MachineInstr &I, const SPIRVInstrInfo &TII, |
| SPIRV::Decoration::Decoration Dec, |
| const std::vector<uint32_t> &DecArgs, StringRef StrImm) { |
| MachineBasicBlock &MBB = *I.getParent(); |
| auto MIB = BuildMI(MBB, I, I.getDebugLoc(), TII.get(SPIRV::OpDecorate)) |
| .addUse(Reg) |
| .addImm(static_cast<uint32_t>(Dec)); |
| finishBuildOpDecorate(MIB, DecArgs, StrImm); |
| } |
| |
| // TODO: maybe the following two functions should be handled in the subtarget |
| // to allow for different OpenCL vs Vulkan handling. |
| unsigned storageClassToAddressSpace(SPIRV::StorageClass::StorageClass SC) { |
| switch (SC) { |
| case SPIRV::StorageClass::Function: |
| return 0; |
| case SPIRV::StorageClass::CrossWorkgroup: |
| return 1; |
| case SPIRV::StorageClass::UniformConstant: |
| return 2; |
| case SPIRV::StorageClass::Workgroup: |
| return 3; |
| case SPIRV::StorageClass::Generic: |
| return 4; |
| case SPIRV::StorageClass::Input: |
| return 7; |
| default: |
| llvm_unreachable("Unable to get address space id"); |
| } |
| } |
| |
| SPIRV::StorageClass::StorageClass |
| addressSpaceToStorageClass(unsigned AddrSpace) { |
| switch (AddrSpace) { |
| case 0: |
| return SPIRV::StorageClass::Function; |
| case 1: |
| return SPIRV::StorageClass::CrossWorkgroup; |
| case 2: |
| return SPIRV::StorageClass::UniformConstant; |
| case 3: |
| return SPIRV::StorageClass::Workgroup; |
| case 4: |
| return SPIRV::StorageClass::Generic; |
| case 7: |
| return SPIRV::StorageClass::Input; |
| default: |
| llvm_unreachable("Unknown address space"); |
| } |
| } |
| |
| SPIRV::MemorySemantics::MemorySemantics |
| getMemSemanticsForStorageClass(SPIRV::StorageClass::StorageClass SC) { |
| switch (SC) { |
| case SPIRV::StorageClass::StorageBuffer: |
| case SPIRV::StorageClass::Uniform: |
| return SPIRV::MemorySemantics::UniformMemory; |
| case SPIRV::StorageClass::Workgroup: |
| return SPIRV::MemorySemantics::WorkgroupMemory; |
| case SPIRV::StorageClass::CrossWorkgroup: |
| return SPIRV::MemorySemantics::CrossWorkgroupMemory; |
| case SPIRV::StorageClass::AtomicCounter: |
| return SPIRV::MemorySemantics::AtomicCounterMemory; |
| case SPIRV::StorageClass::Image: |
| return SPIRV::MemorySemantics::ImageMemory; |
| default: |
| return SPIRV::MemorySemantics::None; |
| } |
| } |
| |
| SPIRV::MemorySemantics::MemorySemantics getMemSemantics(AtomicOrdering Ord) { |
| switch (Ord) { |
| case AtomicOrdering::Acquire: |
| return SPIRV::MemorySemantics::Acquire; |
| case AtomicOrdering::Release: |
| return SPIRV::MemorySemantics::Release; |
| case AtomicOrdering::AcquireRelease: |
| return SPIRV::MemorySemantics::AcquireRelease; |
| case AtomicOrdering::SequentiallyConsistent: |
| return SPIRV::MemorySemantics::SequentiallyConsistent; |
| case AtomicOrdering::Unordered: |
| case AtomicOrdering::Monotonic: |
| case AtomicOrdering::NotAtomic: |
| default: |
| return SPIRV::MemorySemantics::None; |
| } |
| } |
| |
| MachineInstr *getDefInstrMaybeConstant(Register &ConstReg, |
| const MachineRegisterInfo *MRI) { |
| MachineInstr *ConstInstr = MRI->getVRegDef(ConstReg); |
| if (ConstInstr->getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS && |
| ConstInstr->getIntrinsicID() == Intrinsic::spv_track_constant) { |
| ConstReg = ConstInstr->getOperand(2).getReg(); |
| ConstInstr = MRI->getVRegDef(ConstReg); |
| } else if (ConstInstr->getOpcode() == SPIRV::ASSIGN_TYPE) { |
| ConstReg = ConstInstr->getOperand(1).getReg(); |
| ConstInstr = MRI->getVRegDef(ConstReg); |
| } |
| return ConstInstr; |
| } |
| |
| uint64_t getIConstVal(Register ConstReg, const MachineRegisterInfo *MRI) { |
| const MachineInstr *MI = getDefInstrMaybeConstant(ConstReg, MRI); |
| assert(MI && MI->getOpcode() == TargetOpcode::G_CONSTANT); |
| return MI->getOperand(1).getCImm()->getValue().getZExtValue(); |
| } |
| |
| bool isSpvIntrinsic(MachineInstr &MI, Intrinsic::ID IntrinsicID) { |
| return MI.getOpcode() == TargetOpcode::G_INTRINSIC_W_SIDE_EFFECTS && |
| MI.getIntrinsicID() == IntrinsicID; |
| } |
| |
| Type *getMDOperandAsType(const MDNode *N, unsigned I) { |
| return cast<ValueAsMetadata>(N->getOperand(I))->getType(); |
| } |
| |
| // The set of names is borrowed from the SPIR-V translator. |
| // TODO: may be implemented in SPIRVBuiltins.td. |
| static bool isPipeOrAddressSpaceCastBI(const StringRef MangledName) { |
| return MangledName == "write_pipe_2" || MangledName == "read_pipe_2" || |
| MangledName == "write_pipe_2_bl" || MangledName == "read_pipe_2_bl" || |
| MangledName == "write_pipe_4" || MangledName == "read_pipe_4" || |
| MangledName == "reserve_write_pipe" || |
| MangledName == "reserve_read_pipe" || |
| MangledName == "commit_write_pipe" || |
| MangledName == "commit_read_pipe" || |
| MangledName == "work_group_reserve_write_pipe" || |
| MangledName == "work_group_reserve_read_pipe" || |
| MangledName == "work_group_commit_write_pipe" || |
| MangledName == "work_group_commit_read_pipe" || |
| MangledName == "get_pipe_num_packets_ro" || |
| MangledName == "get_pipe_max_packets_ro" || |
| MangledName == "get_pipe_num_packets_wo" || |
| MangledName == "get_pipe_max_packets_wo" || |
| MangledName == "sub_group_reserve_write_pipe" || |
| MangledName == "sub_group_reserve_read_pipe" || |
| MangledName == "sub_group_commit_write_pipe" || |
| MangledName == "sub_group_commit_read_pipe" || |
| MangledName == "to_global" || MangledName == "to_local" || |
| MangledName == "to_private"; |
| } |
| |
| static bool isEnqueueKernelBI(const StringRef MangledName) { |
| return MangledName == "__enqueue_kernel_basic" || |
| MangledName == "__enqueue_kernel_basic_events" || |
| MangledName == "__enqueue_kernel_varargs" || |
| MangledName == "__enqueue_kernel_events_varargs"; |
| } |
| |
| static bool isKernelQueryBI(const StringRef MangledName) { |
| return MangledName == "__get_kernel_work_group_size_impl" || |
| MangledName == "__get_kernel_sub_group_count_for_ndrange_impl" || |
| MangledName == "__get_kernel_max_sub_group_size_for_ndrange_impl" || |
| MangledName == "__get_kernel_preferred_work_group_size_multiple_impl"; |
| } |
| |
| static bool isNonMangledOCLBuiltin(StringRef Name) { |
| if (!Name.startswith("__")) |
| return false; |
| |
| return isEnqueueKernelBI(Name) || isKernelQueryBI(Name) || |
| isPipeOrAddressSpaceCastBI(Name.drop_front(2)) || |
| Name == "__translate_sampler_initializer"; |
| } |
| |
| std::string getOclOrSpirvBuiltinDemangledName(StringRef Name) { |
| bool IsNonMangledOCL = isNonMangledOCLBuiltin(Name); |
| bool IsNonMangledSPIRV = Name.startswith("__spirv_"); |
| bool IsMangled = Name.startswith("_Z"); |
| |
| if (!IsNonMangledOCL && !IsNonMangledSPIRV && !IsMangled) |
| return std::string(); |
| |
| // Try to use the itanium demangler. |
| size_t n; |
| int Status; |
| char *DemangledName = itaniumDemangle(Name.data(), nullptr, &n, &Status); |
| |
| if (Status == demangle_success) { |
| std::string Result = DemangledName; |
| free(DemangledName); |
| return Result; |
| } |
| free(DemangledName); |
| // Otherwise use simple demangling to return the function name. |
| if (IsNonMangledOCL || IsNonMangledSPIRV) |
| return Name.str(); |
| |
| // Autocheck C++, maybe need to do explicit check of the source language. |
| // OpenCL C++ built-ins are declared in cl namespace. |
| // TODO: consider using 'St' abbriviation for cl namespace mangling. |
| // Similar to ::std:: in C++. |
| size_t Start, Len = 0; |
| size_t DemangledNameLenStart = 2; |
| if (Name.startswith("_ZN")) { |
| // Skip CV and ref qualifiers. |
| size_t NameSpaceStart = Name.find_first_not_of("rVKRO", 3); |
| // All built-ins are in the ::cl:: namespace. |
| if (Name.substr(NameSpaceStart, 11) != "2cl7__spirv") |
| return std::string(); |
| DemangledNameLenStart = NameSpaceStart + 11; |
| } |
| Start = Name.find_first_not_of("0123456789", DemangledNameLenStart); |
| Name.substr(DemangledNameLenStart, Start - DemangledNameLenStart) |
| .getAsInteger(10, Len); |
| return Name.substr(Start, Len).str(); |
| } |
| |
| static bool isOpenCLBuiltinType(const StructType *SType) { |
| return SType->isOpaque() && SType->hasName() && |
| SType->getName().startswith("opencl."); |
| } |
| |
| static bool isSPIRVBuiltinType(const StructType *SType) { |
| return SType->isOpaque() && SType->hasName() && |
| SType->getName().startswith("spirv."); |
| } |
| |
| const Type *getTypedPtrEltType(const Type *Ty) { |
| auto PType = dyn_cast<PointerType>(Ty); |
| if (!PType || PType->isOpaque()) |
| return Ty; |
| return PType->getNonOpaquePointerElementType(); |
| } |
| |
| bool isSpecialOpaqueType(const Type *Ty) { |
| if (auto SType = dyn_cast<StructType>(getTypedPtrEltType(Ty))) |
| return isOpenCLBuiltinType(SType) || isSPIRVBuiltinType(SType); |
| return false; |
| } |
| } // namespace llvm |