365 lines
13 KiB
C++
365 lines
13 KiB
C++
//===--- 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;
|
|
}
|
|
|
|
std::string getFunctionGlobalIdentifier(const Function *F) {
|
|
StringRef Name = F->hasName() ? F->getName() : ".anonymous";
|
|
GlobalValue::LinkageTypes Linkage = F->getLinkage();
|
|
StringRef ModuleFileName = F->getParent()->getSourceFileName();
|
|
return GlobalValue::getGlobalIdentifier(Name, Linkage, ModuleFileName);
|
|
}
|
|
} // namespace llvm
|