//===- SPIRVUtil.cpp - SPIR-V Utilities -------------------------*- C++ -*-===// // // The LLVM/SPIRV Translator // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // // Copyright (c) 2014 Advanced Micro Devices, Inc. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal with the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimers. // Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimers in the documentation // and/or other materials provided with the distribution. // Neither the names of Advanced Micro Devices, Inc., nor the names of its // contributors may be used to endorse or promote products derived from this // Software without specific prior written permission. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH // THE SOFTWARE. // //===----------------------------------------------------------------------===// /// \file /// /// This file defines utility classes and functions shared by SPIR-V /// reader/writer. /// //===----------------------------------------------------------------------===// // This file needs to be included before anything that declares // llvm::PointerType to avoid a compilation bug on MSVC. #include "llvm/Demangle/Demangle.h" #include "llvm/Demangle/ItaniumDemangle.h" #include "FunctionDescriptor.h" #include "ManglingUtils.h" #include "NameMangleAPI.h" #include "OCLUtil.h" #include "ParameterType.h" #include "SPIRVInternal.h" #include "SPIRVMDWalker.h" #include "libSPIRV/SPIRVDecorate.h" #include "libSPIRV/SPIRVValue.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Bitcode/BitcodeWriter.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Metadata.h" #include "llvm/IR/Operator.h" #include "llvm/IR/TypedPointerType.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/ToolOutputFile.h" #include #include #define DEBUG_TYPE "spirv" namespace SPIRV { #ifdef _SPIRV_SUPPORT_TEXT_FMT cl::opt UseTextFormat("spirv-text", cl::desc("Use text format for SPIR-V for debugging purpose"), cl::location(SPIRVUseTextFormat)); #endif #ifdef _SPIRVDBG cl::opt EnableDbgOutput("spirv-debug", cl::desc("Enable SPIR-V debug output"), cl::location(SPIRVDbgEnable)); #endif bool isSupportedTriple(Triple T) { return T.isSPIR() || T.isSPIRV(); } void addFnAttr(CallInst *Call, Attribute::AttrKind Attr) { Call->addFnAttr(Attr); } void removeFnAttr(CallInst *Call, Attribute::AttrKind Attr) { Call->removeFnAttr(Attr); } Value *extendVector(Value *V, FixedVectorType *NewType, IRBuilderBase &Builder) { unsigned OldSize = cast(V->getType())->getNumElements(); unsigned NewSize = NewType->getNumElements(); assert(OldSize < NewSize); std::vector Components; IntegerType *Int32Ty = Builder.getInt32Ty(); for (unsigned I = 0; I < NewSize; I++) { if (I < OldSize) Components.push_back(ConstantInt::get(Int32Ty, I)); else Components.push_back(PoisonValue::get(Int32Ty)); } return Builder.CreateShuffleVector(V, PoisonValue::get(V->getType()), ConstantVector::get(Components), "vecext"); } void saveLLVMModule(Module *M, const std::string &OutputFile) { std::error_code EC; ToolOutputFile Out(OutputFile.c_str(), EC, sys::fs::OF_None); if (EC) { SPIRVDBG(errs() << "Fails to open output file: " << EC.message();) return; } WriteBitcodeToFile(*M, Out.os()); Out.keep(); } std::string mapLLVMTypeToOCLType(const Type *Ty, bool Signed, Type *PET) { if (Ty->isHalfTy()) return "half"; if (Ty->isFloatTy()) return "float"; if (Ty->isDoubleTy()) return "double"; if (const auto *IntTy = dyn_cast(Ty)) { std::string SignPrefix; std::string Stem; if (!Signed) SignPrefix = "u"; switch (IntTy->getIntegerBitWidth()) { case 8: Stem = "char"; break; case 16: Stem = "short"; break; case 32: Stem = "int"; break; case 64: Stem = "long"; break; default: Stem = "invalid_type"; break; } return SignPrefix + Stem; } if (const auto *VecTy = dyn_cast(Ty)) { Type *EleTy = VecTy->getElementType(); unsigned Size = VecTy->getNumElements(); std::stringstream Ss; Ss << mapLLVMTypeToOCLType(EleTy, Signed) << Size; return Ss.str(); } // It is expected that `Ty` can be mapped to `ReturnType` from "Optional // Postfixes for SPIR-V Builtin Function Names" section of // SPIRVRepresentationInLLVM.rst document (aka SPIRV-friendly IR). // If `Ty` is not a scalar or vector type mentioned in the document (return // value of some SPIR-V instructions may be represented as pointer to a struct // in LLVM IR) we can mangle the type. BuiltinFuncMangleInfo MangleInfo; if (Ty->isPointerTy()) Ty = TypedPointerType::get(PET, Ty->getPointerAddressSpace()); std::string MangledName = mangleBuiltin("", const_cast(Ty), &MangleInfo); // Remove "_Z0"(3 characters) from the front of the name return MangledName.erase(0, 3); } StructType *getOrCreateOpaqueStructType(Module *M, StringRef Name) { auto *OpaqueType = StructType::getTypeByName(M->getContext(), Name); if (!OpaqueType) OpaqueType = StructType::create(M->getContext(), Name); return OpaqueType; } void getFunctionTypeParameterTypes(llvm::FunctionType *FT, SmallVector &ArgTys) { for (auto I = FT->param_begin(), E = FT->param_end(); I != E; ++I) { ArgTys.push_back(*I); } } bool isVoidFuncTy(FunctionType *FT) { return FT->getReturnType()->isVoidTy(); } bool isOCLImageType(llvm::Type *Ty, StringRef *Name) { if (auto *TPT = dyn_cast_or_null(Ty)) if (auto *ST = dyn_cast_or_null(TPT->getElementType())) if (ST->isOpaque()) { auto FullName = ST->getName(); if (FullName.find(kSPR2TypeName::ImagePrefix) == 0) { if (Name) *Name = FullName.drop_front(strlen(kSPR2TypeName::OCLPrefix)); return true; } } if (auto *TET = dyn_cast_or_null(Ty)) { assert(!Name && "Cannot get the name for a target-extension type image"); return TET->getName() == "spirv.Image"; } return false; } /// \param BaseTyName is the type Name as in spirv.BaseTyName.Postfixes /// \param Postfix contains postfixes extracted from the SPIR-V image /// type Name as spirv.BaseTyName.Postfixes. bool isSPIRVStructType(llvm::Type *Ty, StringRef BaseTyName, StringRef *Postfix) { auto *ST = dyn_cast(Ty); if (!ST) return false; if (ST->isOpaque()) { auto FullName = ST->getName(); std::string N = std::string(kSPIRVTypeName::PrefixAndDelim) + BaseTyName.str(); if (FullName != N) N = N + kSPIRVTypeName::Delimiter; if (FullName.starts_with(N)) { if (Postfix) *Postfix = FullName.drop_front(N.size()); return true; } } return false; } bool isSYCLHalfType(llvm::Type *Ty) { if (auto *ST = dyn_cast(Ty)) { if (!ST->hasName()) return false; StringRef Name = ST->getName(); if (!Name.consume_front("class.")) return false; if ((Name.starts_with("sycl::") || Name.starts_with("cl::sycl::") || Name.starts_with("__sycl_internal::")) && Name.ends_with("::half")) { return true; } } return false; } bool isSYCLBfloat16Type(llvm::Type *Ty) { if (auto *ST = dyn_cast(Ty)) { if (!ST->hasName()) return false; StringRef Name = ST->getName(); if (!Name.consume_front("class.")) return false; if ((Name.starts_with("sycl::") || Name.starts_with("cl::sycl::") || Name.starts_with("__sycl_internal::")) && Name.ends_with("::bfloat16")) { return true; } } return false; } bool isLLVMCooperativeMatrixType(llvm::Type *Ty) { if (auto *TargetTy = dyn_cast(Ty)) return TargetTy->getName() == "spirv.CooperativeMatrixKHR"; return false; } Function *getOrCreateFunction(Module *M, Type *RetTy, ArrayRef ArgTypes, StringRef Name, BuiltinFuncMangleInfo *Mangle, AttributeList *Attrs, bool TakeName) { std::string MangledName{Name}; bool IsVarArg = false; if (Mangle) { MangledName = mangleBuiltin(Name, ArgTypes, Mangle); IsVarArg = 0 <= Mangle->getVarArg(); if (IsVarArg) ArgTypes = ArgTypes.slice(0, Mangle->getVarArg()); } FunctionType *FT = FunctionType::get(RetTy, ArgTypes, IsVarArg); Function *F = M->getFunction(MangledName); if (!TakeName && F && F->getFunctionType() != FT && Mangle != nullptr) { std::string S; raw_string_ostream SS(S); SS << "Error: Attempt to redefine function: " << *F << " => " << *FT << '\n'; report_fatal_error(llvm::Twine(SS.str()), false); } if (!F || F->getFunctionType() != FT) { auto *NewF = Function::Create(FT, GlobalValue::ExternalLinkage, MangledName, M); if (F && TakeName) { NewF->takeName(F); LLVM_DEBUG( dbgs() << "[getOrCreateFunction] Warning: taking function Name\n"); } if (NewF->getName() != MangledName) { LLVM_DEBUG( dbgs() << "[getOrCreateFunction] Warning: function Name changed\n"); } LLVM_DEBUG(dbgs() << "[getOrCreateFunction] "; if (F) dbgs() << *F << " => "; dbgs() << *NewF << '\n';); if (F) NewF->setDSOLocal(F->isDSOLocal()); F = NewF; F->setCallingConv(CallingConv::SPIR_FUNC); if (Attrs) F->setAttributes(*Attrs); } return F; } std::vector getArguments(CallInst *CI, unsigned Start, unsigned End) { std::vector Args; if (End == 0) End = CI->arg_size(); for (; Start != End; ++Start) { Args.push_back(CI->getArgOperand(Start)); } return Args; } uint64_t getArgAsInt(CallInst *CI, unsigned I) { return cast(CI->getArgOperand(I))->getZExtValue(); } Scope getArgAsScope(CallInst *CI, unsigned I) { return static_cast(getArgAsInt(CI, I)); } std::string prefixSPIRVName(const std::string &S) { return std::string(kSPIRVName::Prefix) + S; } StringRef dePrefixSPIRVName(StringRef R, SmallVectorImpl &Postfix) { const size_t Start = strlen(kSPIRVName::Prefix); if (!R.starts_with(kSPIRVName::Prefix)) return R; R = R.drop_front(Start); R.split(Postfix, "_", -1, false); auto Name = Postfix.front(); Postfix.erase(Postfix.begin()); return Name; } std::string getSPIRVFuncName(Op OC, StringRef PostFix) { return prefixSPIRVName(getName(OC) + PostFix.str()); } std::string getSPIRVFuncName(Op OC, const Type *PRetTy, bool IsSigned, Type *PET) { return prefixSPIRVName(getName(OC) + kSPIRVPostfix::Divider + getPostfixForReturnType(PRetTy, IsSigned, PET)); } std::string getSPIRVFuncName(SPIRVBuiltinVariableKind BVKind) { return prefixSPIRVName(getName(BVKind)); } std::string getSPIRVExtFuncName(SPIRVExtInstSetKind Set, unsigned ExtOp, StringRef PostFix) { std::string ExtOpName; switch (Set) { default: llvm_unreachable("invalid extended instruction set"); ExtOpName = "unknown"; break; case SPIRVEIS_OpenCL: ExtOpName = getName(static_cast(ExtOp)); break; } return prefixSPIRVName(SPIRVExtSetShortNameMap::map(Set) + '_' + ExtOpName + PostFix.str()); } SPIRVDecorate *mapPostfixToDecorate(StringRef Postfix, SPIRVEntry *Target) { if (Postfix == kSPIRVPostfix::Sat) return new SPIRVDecorate(spv::DecorationSaturatedConversion, Target); if (Postfix.starts_with(kSPIRVPostfix::Rt)) return new SPIRVDecorate(spv::DecorationFPRoundingMode, Target, map(Postfix.str())); return nullptr; } SPIRVValue *addDecorations(SPIRVValue *Target, const SmallVectorImpl &Decs) { for (auto &I : Decs) if (auto *Dec = mapPostfixToDecorate(I, Target)) Target->addDecorate(Dec); return Target; } std::string getPostfixForReturnType(CallInst *CI, bool IsSigned) { return getPostfixForReturnType(CI->getType(), IsSigned); } std::string getPostfixForReturnType(const Type *PRetTy, bool IsSigned, Type *PET) { return std::string(kSPIRVPostfix::Return) + mapLLVMTypeToOCLType(PRetTy, IsSigned, PET); } // Enqueue kernel, kernel query, pipe and address space cast built-ins // are not mangled. bool isNonMangledOCLBuiltin(StringRef Name) { if (!Name.starts_with("__")) return false; return isEnqueueKernelBI(Name) || isKernelQueryBI(Name) || isPipeOrAddressSpaceCastBI(Name.drop_front(2)); } Op getSPIRVFuncOC(StringRef S, SmallVectorImpl *Dec) { Op OC; SmallVector Postfix; StringRef Name; if (!oclIsBuiltin(S, Name)) Name = S; StringRef R(Name); if ((!Name.starts_with(kSPIRVName::Prefix) && !isNonMangledOCLBuiltin(S)) || !getByName(dePrefixSPIRVName(R, Postfix).str(), OC)) { return OpNop; } if (Dec) for (auto &I : Postfix) Dec->push_back(I.str()); return OC; } bool getSPIRVBuiltin(const std::string &OrigName, spv::BuiltIn &B) { SmallVector Postfix; StringRef R(OrigName); R = dePrefixSPIRVName(R, Postfix); if (!Postfix.empty()) return false; return getByName(R.str(), B); } // DemangledName is a substring of Name. The DemangledName is updated only // if true is returned bool oclIsBuiltin(StringRef Name, StringRef &DemangledName, bool IsCpp) { if (Name == "printf") { DemangledName = "__spirv_ocl_printf"; return true; } if (isNonMangledOCLBuiltin(Name)) { DemangledName = Name.drop_front(2); return true; } if (!Name.starts_with("_Z")) return false; // OpenCL C++ built-ins are declared in cl namespace. // TODO: consider using 'St' abbriviation for cl namespace mangling. // Similar to ::std:: in C++. if (IsCpp) { if (!Name.starts_with("_ZN")) return false; // 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 false; size_t DemangledNameLenStart = NameSpaceStart + 11; size_t Start = Name.find_first_not_of("0123456789", DemangledNameLenStart); size_t Len = 0; if (!Name.substr(DemangledNameLenStart, Start - DemangledNameLenStart) .getAsInteger(10, Len)) { DemangledName = Name.substr(Start, Len); return true; } SPIRVDBG(errs() << "Error in extracting integer value"); return false; } size_t Start = Name.find_first_not_of("0123456789", 2); size_t Len = 0; if (!Name.substr(2, Start - 2).getAsInteger(10, Len)) { DemangledName = Name.substr(Start, Len); return true; } SPIRVDBG(errs() << "Error in extracting integer value"); return false; } // DemangledName is a substring of Name. The DemangledName is updated only // if true is returned. bool isInternalSPIRVBuiltin(StringRef Name, StringRef &DemangledName) { if (!Name.starts_with("_Z")) return false; constexpr unsigned DemangledNameLenStart = 2; size_t Start = Name.find_first_not_of("0123456789", DemangledNameLenStart); if (!Name.substr(Start, Name.size() - 1) .starts_with(kSPIRVName::InternalBuiltinPrefix)) return false; DemangledName = llvm::itaniumDemangle(Name.data(), false); DemangledName.consume_front(kSPIRVName::InternalBuiltinPrefix); return true; } // Check if a mangled type Name is unsigned bool isMangledTypeUnsigned(char Mangled) { return Mangled == 'h' /* uchar */ || Mangled == 't' /* ushort */ || Mangled == 'j' /* uint */ || Mangled == 'm' /* ulong */; } // Check if a mangled type Name is signed bool isMangledTypeSigned(char Mangled) { return Mangled == 'c' /* char */ || Mangled == 'a' /* signed char */ || Mangled == 's' /* short */ || Mangled == 'i' /* int */ || Mangled == 'l' /* long */; } // Check if a mangled type Name is floating point (excludes half) bool isMangledTypeFP(char Mangled) { return Mangled == 'f' /* float */ || Mangled == 'd'; /* double */ } // Check if a mangled type Name is half bool isMangledTypeHalf(std::string Mangled) { return Mangled == "Dh"; /* half */ } void eraseSubstitutionFromMangledName(std::string &MangledName) { auto Len = MangledName.length(); while (Len >= 2 && MangledName.substr(Len - 2, 2) == "S_") { Len -= 2; MangledName.erase(Len, 2); } } ParamType lastFuncParamType(StringRef MangledName) { std::string Copy(MangledName); eraseSubstitutionFromMangledName(Copy); char Mangled = Copy.back(); std::string Mangled2 = Copy.substr(Copy.size() - 2); std::string Mangled5 = Copy.substr(Copy.size() - 5); if (Mangled5 == "DF16b") { return ParamType::FLOAT; } if (isMangledTypeFP(Mangled) || isMangledTypeHalf(Mangled2)) { return ParamType::FLOAT; } else if (isMangledTypeUnsigned(Mangled)) { return ParamType::UNSIGNED; } else if (isMangledTypeSigned(Mangled)) { return ParamType::SIGNED; } return ParamType::UNKNOWN; } // Check if the last argument is signed bool isLastFuncParamSigned(StringRef MangledName) { return lastFuncParamType(MangledName) == ParamType::SIGNED; } // Check if a mangled function Name contains unsigned atomic type bool containsUnsignedAtomicType(StringRef Name) { auto Loc = Name.find(kMangledName::AtomicPrefixIncoming); if (Loc == StringRef::npos) return false; return isMangledTypeUnsigned( Name[Loc + strlen(kMangledName::AtomicPrefixIncoming)]); } bool hasArrayArg(Function *F) { for (auto I = F->arg_begin(), E = F->arg_end(); I != E; ++I) { LLVM_DEBUG(dbgs() << "[hasArrayArg] " << *I << '\n'); if (I->getType()->isArrayTy()) { return true; } } return false; } /// Convert a struct name from the name given to it in Itanium name mangling to /// the name given to it as an LLVM opaque struct. static std::string demangleBuiltinOpenCLTypeName(StringRef MangledStructName) { assert(MangledStructName.starts_with("ocl_") && "Not a valid builtin OpenCL mangled name"); // Bare structure type that starts with ocl_ is a builtin opencl type. // See clang/lib/CodeGen/CGOpenCLRuntime for how these map to LLVM types // and clang/lib/AST/ItaniumMangle for how they are mangled. // In general, ocl_ is mapped to pointer-to-%opencl., but // there is some variance around whether or not _t is included in the // mangled name. std::string LlvmStructName = StringSwitch(MangledStructName) .Case("ocl_sampler", "opencl.sampler_t") .Case("ocl_event", "opencl.event_t") .Case("ocl_clkevent", "opencl.clk_event_t") .Case("ocl_queue", "opencl.queue_t") .Case("ocl_reserveid", "opencl.reserve_id_t") .Default("") .str(); if (LlvmStructName.empty()) { LlvmStructName = "opencl."; LlvmStructName += MangledStructName.substr(4); // Strip off ocl_ if (!MangledStructName.ends_with("_t")) LlvmStructName += "_t"; } return LlvmStructName; } /// Convert a C/C++ type name into an LLVM type, if it's a basic integer or /// floating point type. static Type *parsePrimitiveType(LLVMContext &Ctx, StringRef Name) { return StringSwitch(Name) .Cases("char", "signed char", "unsigned char", Type::getInt8Ty(Ctx)) .Cases("short", "unsigned short", Type::getInt16Ty(Ctx)) .Cases("int", "unsigned int", Type::getInt32Ty(Ctx)) .Cases("long", "unsigned long", Type::getInt64Ty(Ctx)) .Cases("long long", "unsigned long long", Type::getInt64Ty(Ctx)) .Case("half", Type::getHalfTy(Ctx)) .Case("std::bfloat16_t", Type::getBFloatTy(Ctx)) .Case("float", Type::getFloatTy(Ctx)) .Case("double", Type::getDoubleTy(Ctx)) .Case("void", Type::getInt8Ty(Ctx)) .Default(nullptr); } } // namespace SPIRV namespace { // Return the value for when the dimension index of a builtin is out of range. uint64_t getBuiltinOutOfRangeValue(StringRef VarName) { assert(VarName.starts_with("__spirv_BuiltIn")); return StringSwitch(VarName) .EndsWith("GlobalSize", 1) .EndsWith("NumWorkgroups", 1) .EndsWith("WorkgroupSize", 1) .EndsWith("EnqueuedWorkgroupSize", 1) .Default(0); } } // anonymous namespace // The demangler node hierarchy doesn't use LLVM's RTTI helper functions (as it // also needs to live in libcxxabi). By specializing this implementation here, // we can add support for these functions. #define NODE(X) \ template struct llvm::isa_impl { \ static inline bool doit(const From &Val) { \ return Val.getKind() == itanium_demangle::Node::K##X; \ } \ }; #include "llvm/Demangle/ItaniumNodes.def" namespace SPIRV { namespace { // An allocator to use with the demangler API. class DefaultAllocator { BumpPtrAllocator Alloc; public: void reset() { Alloc.Reset(); } template T *makeNode(Args &&...ArgList) { return new (Alloc.Allocate(sizeof(T), alignof(T))) T(std::forward(ArgList)...); } void *allocateNodeArray(size_t Sz) { using namespace llvm::itanium_demangle; return Alloc.Allocate(sizeof(Node *) * Sz, alignof(Node *)); } }; } // unnamed namespace static StringRef stringify(const itanium_demangle::NameType *Node) { return Node->getName(); } /// Convert a mangled name that represents a basic integer, floating-point, /// etc. type into the corresponding LLVM type. static Type *getPrimitiveType(LLVMContext &Ctx, const llvm::itanium_demangle::Node *N) { using namespace llvm::itanium_demangle; if (auto *Name = dyn_cast(N)) { return parsePrimitiveType(Ctx, stringify(Name)); } if (auto *BitInt = dyn_cast(N)) { unsigned BitWidth = 0; BitInt->match([&](const Node *NodeSize, bool) { const StringRef SizeStr(stringify(cast(NodeSize))); SizeStr.getAsInteger(10, BitWidth); }); return Type::getIntNTy(Ctx, BitWidth); } if (auto *FP = dyn_cast(N)) { StringRef SizeStr; FP->match([&](const Node *NodeDimension) { SizeStr = stringify(cast(NodeDimension)); }); return StringSwitch(SizeStr) .Case("16", Type::getHalfTy(Ctx)) .Case("32", Type::getFloatTy(Ctx)) .Case("64", Type::getDoubleTy(Ctx)) .Case("128", Type::getFP128Ty(Ctx)) .Default(nullptr); } return nullptr; } template static TypedPointerType * parseNode(Module *M, const llvm::itanium_demangle::Node *ParamType, FnType GetStructType) { using namespace llvm::itanium_demangle; Type *PointeeTy = nullptr; unsigned AS = 0; if (auto *Name = dyn_cast(ParamType)) { // This corresponds to a simple class name. Since we only care about // pointer element types, the only relevant names are those corresponding // to the OpenCL special types (which all begin with "ocl_"). StringRef Arg(stringify(Name)); if (Arg.starts_with("ocl_")) { const std::string StructName = demangleBuiltinOpenCLTypeName(Arg); PointeeTy = GetStructType(StructName); } else if (Arg.consume_front("__spirv_")) { // This is a pointer to a SPIR-V OpType* opaque struct. In general, // convert __spirv_[__Suffix] to %spirv.Type[._Suffix] auto NameSuffixPair = Arg.split('_'); std::string StructName = "spirv."; StructName += NameSuffixPair.first; if (!NameSuffixPair.second.empty()) { StructName += "."; StructName += NameSuffixPair.second; } PointeeTy = GetStructType(StructName); } else if (Arg == "ndrange_t") { PointeeTy = GetStructType(Arg); } } else if (auto *P = dyn_cast(ParamType)) { const Node *Pointee = P->getPointee(); // Strip through all of the qualifiers on the pointee type. while (true) { if (auto *VendorTy = dyn_cast(Pointee)) { Pointee = VendorTy->getTy(); StringRef Qualifier(&*VendorTy->getExt().begin(), VendorTy->getExt().size()); if (Qualifier.consume_front("AS")) { Qualifier.getAsInteger(10, AS); } } else if (auto *Qual = dyn_cast(Pointee)) { Pointee = Qual->getChild(); } else { break; } } if (auto *Name = dyn_cast(Pointee)) { StringRef MangledStructName(stringify(Name)); if (MangledStructName.consume_front("__spirv_")) { // This is a pointer to a SPIR-V OpType* opaque struct. In general, // convert __spirv_[__Suffix] to %spirv.Type[._Suffix] auto NameSuffixPair = MangledStructName.split('_'); std::string StructName = "spirv."; StructName += NameSuffixPair.first; if (!NameSuffixPair.second.empty()) { StructName += "."; StructName += NameSuffixPair.second; } PointeeTy = GetStructType(StructName); } else if (MangledStructName.starts_with("opencl.")) { PointeeTy = GetStructType(MangledStructName); } else if (MangledStructName.starts_with("ocl_")) { const std::string StructName = demangleBuiltinOpenCLTypeName(MangledStructName); PointeeTy = TypedPointerType::get(GetStructType(StructName), 0); } else { PointeeTy = parsePrimitiveType(M->getContext(), MangledStructName); } } else if (auto *Ty = getPrimitiveType(M->getContext(), Pointee)) { PointeeTy = Ty; } else if (auto *Vec = dyn_cast(Pointee)) { unsigned ElemCount = 0; const StringRef ElemCountStr( stringify(cast(Vec->getDimension()))); ElemCountStr.getAsInteger(10, ElemCount); if (auto *Ty = getPrimitiveType(M->getContext(), Vec->getBaseType())) { PointeeTy = llvm::VectorType::get(Ty, ElemCount, false); } } else if (llvm::isa(Pointee)) { PointeeTy = parseNode(M, Pointee, GetStructType); } else { // Other possible pointee types do not correspond to any of the special // struct types were are looking for here. } } else if (auto *VendorTy = dyn_cast(ParamType)) { if (auto *NameTy = dyn_cast(VendorTy->getTy())) { if (NameTy->getName() == "std::bfloat16_t") PointeeTy = llvm::Type::getBFloatTy(M->getContext()); } // This is a block parameter. Decode the pointee type as if it were a // void (*)(void) function pointer type. if (VendorTy->getExt() == "block_pointer") { PointeeTy = llvm::FunctionType::get(Type::getVoidTy(M->getContext()), false); } } else { // Other parameter types are not likely to be pointer types, so we can // ignore these. } return PointeeTy ? TypedPointerType::get(PointeeTy, AS) : nullptr; } bool getParameterTypes(Function *F, SmallVectorImpl &ArgTys, std::function NameMapFn) { using namespace llvm::itanium_demangle; // If there's no mangled name, we can't do anything. Also, if there's no // parameters, do nothing. StringRef Name = F->getName(); if (!Name.starts_with("_Z") || F->arg_empty()) return Name.starts_with("_Z"); Module *M = F->getParent(); auto GetStructType = [&](StringRef Name) { return getOrCreateOpaqueStructType(M, NameMapFn ? NameMapFn(Name) : Name); }; // Start by filling in a skeleton of information we can get from the LLVM type // itself. ArgTys.clear(); auto *FT = F->getFunctionType(); ArgTys.reserve(FT->getNumParams()); bool HasSret = false; for (Argument &Arg : F->args()) { if (!Arg.getType()->isPointerTy()) ArgTys.push_back(Arg.getType()); else if (Type *Ty = Arg.getParamStructRetType()) { assert(!HasSret && &Arg == F->getArg(0) && "sret parameter should only appear on the first argument"); HasSret = true; unsigned AS = Arg.getType()->getPointerAddressSpace(); if (auto *STy = dyn_cast(Ty)) if (STy->hasName()) ArgTys.push_back( TypedPointerType::get(GetStructType(STy->getName()), AS)); else ArgTys.push_back(TypedPointerType::get(STy, AS)); else ArgTys.push_back(TypedPointerType::get(Ty, AS)); } else { ArgTys.push_back(Arg.getType()); } } // Skip the first argument if it's an sret parameter--this would be an // implicit parameter not recognized as part of the function parameters. auto *ArgIter = ArgTys.begin(); if (HasSret) ++ArgIter; // Demangle the function arguments. If we get an input name of // "_Z12write_imagei20ocl_image1d_array_woDv2_iiDv4_i", then we expect // that Demangler.getFunctionParameters will return // "(ocl_image1d_array_wo, int __vector(2), int, int __vector(4))" (in other // words, the stuff between the parentheses if you ran C++ filt, including // the parentheses itself). const StringRef MangledName(F->getName()); ManglingParser Demangler(MangledName.begin(), MangledName.end()); // We expect to see only function name encodings here. If it's not a function // name encoding, bail out. auto *RootNode = dyn_cast_or_null(Demangler.parse()); if (!RootNode) return false; // Get the parameter list. If the function is a vararg function, drop the last // parameter. NodeArray Params = RootNode->getParams(); if (F->isVarArg()) { bool HasVarArgParam = false; if (!Params.empty()) { if (auto *Name = dyn_cast(Params[Params.size() - 1])) { if (stringify(Name) == "...") HasVarArgParam = true; } } if (HasVarArgParam) { Params = NodeArray(Params.begin(), Params.size() - 1); } else { LLVM_DEBUG(dbgs() << "[getParameterTypes] function " << MangledName << " was expected to have a varargs parameter\n"); return false; } } // Sanity check that the name mangling matches up to the expected number of // arguments. if (Params.size() != (size_t)(ArgTys.end() - ArgIter)) { LLVM_DEBUG(dbgs() << "[getParameterTypes] function " << MangledName << " appears to have " << Params.size() << " arguments but has " << (ArgTys.end() - ArgIter) << "\n"); return false; } // Overwrite the types of pointer-typed arguments with information from // demangling. bool DemangledSuccessfully = true; for (auto *ParamType : Params) { Type *ArgTy = *ArgIter; Type *DemangledTy = parseNode(M, ParamType, GetStructType); if (ArgTy->isPointerTy() && DemangledTy == nullptr) { DemangledTy = TypedPointerType::get(Type::getInt8Ty(ArgTy->getContext()), ArgTy->getPointerAddressSpace()); LLVM_DEBUG(dbgs() << "Failed to recover type of argument " << *ArgTy << " of function " << F->getName() << "\n"); DemangledSuccessfully = false; } else if (ArgTy->isTargetExtTy() || !DemangledTy) DemangledTy = ArgTy; if (auto *TPT = dyn_cast(DemangledTy)) if (ArgTy->isPointerTy() && TPT->getAddressSpace() != ArgTy->getPointerAddressSpace()) DemangledTy = TypedPointerType::get(TPT->getElementType(), ArgTy->getPointerAddressSpace()); *ArgIter++ = DemangledTy; } return DemangledSuccessfully; } bool getRetParamSignedness(Function *F, ParamSignedness &RetSignedness, SmallVectorImpl &ArgSignedness) { using namespace llvm::itanium_demangle; StringRef Name = F->getName(); if (!Name.starts_with("_Z") || F->arg_empty()) return false; ManglingParser Demangler(Name.begin(), Name.end()); // If it's not a function name encoding, bail out. auto *RootNode = dyn_cast_or_null(Demangler.parse()); if (!RootNode) return false; auto GetSignedness = [](const itanium_demangle::Node *N) { if (!N) return ParamSignedness::Unknown; if (const auto *Vec = dyn_cast(N)) N = Vec->getBaseType(); if (const auto *Name = dyn_cast(N)) { StringRef Arg(stringify(Name)); if (Arg.starts_with("unsigned")) return ParamSignedness::Unsigned; if (Arg == "char" || Arg == "short" || Arg == "int" || Arg == "long") return ParamSignedness::Signed; } return ParamSignedness::Unknown; }; RetSignedness = GetSignedness(RootNode->getReturnType()); ArgSignedness.resize(F->arg_size()); for (const auto &[I, ParamType] : llvm::enumerate(RootNode->getParams())) { if (F->getArg(I)->getType()->isIntOrIntVectorTy()) ArgSignedness[I] = GetSignedness(ParamType); else ArgSignedness[I] = ParamSignedness::Unknown; } return true; } CallInst *mutateCallInst( Module *M, CallInst *CI, std::function &)> ArgMutate, BuiltinFuncMangleInfo *Mangle, AttributeList *Attrs, bool TakeFuncName) { LLVM_DEBUG(dbgs() << "[mutateCallInst] " << *CI); auto Args = getArguments(CI); auto NewName = ArgMutate(CI, Args); std::string InstName; if (!CI->getType()->isVoidTy() && CI->hasName()) { InstName = CI->getName().str(); CI->setName(InstName + ".old"); } auto *NewCI = addCallInst(M, NewName, CI->getType(), Args, Attrs, CI, Mangle, InstName, TakeFuncName); NewCI->setDebugLoc(CI->getDebugLoc()); LLVM_DEBUG(dbgs() << " => " << *NewCI << '\n'); CI->replaceAllUsesWith(NewCI); CI->eraseFromParent(); return NewCI; } Instruction *mutateCallInst( Module *M, CallInst *CI, std::function &, Type *&RetTy)> ArgMutate, std::function RetMutate, BuiltinFuncMangleInfo *Mangle, AttributeList *Attrs, bool TakeFuncName) { LLVM_DEBUG(dbgs() << "[mutateCallInst] " << *CI); auto Args = getArguments(CI); Type *RetTy = CI->getType(); auto NewName = ArgMutate(CI, Args, RetTy); StringRef InstName = CI->getName(); auto *NewCI = addCallInst(M, NewName, RetTy, Args, Attrs, CI, Mangle, InstName, TakeFuncName); auto *NewI = RetMutate(NewCI); NewI->takeName(CI); NewI->setDebugLoc(CI->getDebugLoc()); LLVM_DEBUG(dbgs() << " => " << *NewI << '\n'); if (!CI->getType()->isVoidTy()) CI->replaceAllUsesWith(NewI); CI->eraseFromParent(); return NewI; } void mutateFunction( Function *F, std::function &)> ArgMutate, BuiltinFuncMangleInfo *Mangle, AttributeList *Attrs, bool TakeFuncName) { auto *M = F->getParent(); for (auto I = F->user_begin(), E = F->user_end(); I != E;) { if (auto *CI = dyn_cast(*I++)) mutateCallInst(M, CI, ArgMutate, Mangle, Attrs, TakeFuncName); } if (F->use_empty()) F->eraseFromParent(); } void mutateFunction( Function *F, std::function &, Type *&RetTy)> ArgMutate, std::function RetMutate, BuiltinFuncMangleInfo *Mangle, AttributeList *Attrs, bool TakeName) { auto *M = F->getParent(); for (auto I = F->user_begin(), E = F->user_end(); I != E;) { if (auto *CI = dyn_cast(*I++)) mutateCallInst(M, CI, ArgMutate, RetMutate, Mangle, Attrs, TakeName); } if (F->use_empty()) F->eraseFromParent(); } CallInst *addCallInst(Module *M, StringRef FuncName, Type *RetTy, ArrayRef Args, AttributeList *Attrs, Instruction *Pos, BuiltinFuncMangleInfo *Mangle, StringRef InstName, bool TakeFuncName) { auto *F = getOrCreateFunction(M, RetTy, getTypes(Args), FuncName, Mangle, Attrs, TakeFuncName); InsertPosition InsertPos(nullptr); if (Pos) InsertPos = Pos->getIterator(); // Cannot assign a Name to void typed values auto *CI = CallInst::Create(F, Args, RetTy->isVoidTy() ? "" : InstName, InsertPos); CI->setCallingConv(F->getCallingConv()); CI->setAttributes(F->getAttributes()); return CI; } CallInst *addCallInstSPIRV(Module *M, StringRef FuncName, Type *RetTy, ArrayRef Args, AttributeList *Attrs, ArrayRef PointerElementTypes, Instruction *Pos, StringRef InstName) { BuiltinFuncMangleInfo BtnInfo; for (unsigned I = 0; I < PointerElementTypes.size(); I++) { if (Args[I]->getType()->isPointerTy()) BtnInfo.getTypeMangleInfo(I).PointerTy = TypedPointerType::get( PointerElementTypes[I], Args[I]->getType()->getPointerAddressSpace()); } return addCallInst(M, FuncName, RetTy, Args, Attrs, Pos, &BtnInfo, InstName); } bool isValidVectorSize(unsigned I) { return I == 2 || I == 3 || I == 4 || I == 8 || I == 16; } Value *addVector(Instruction *InsPos, ValueVecRange Range) { size_t VecSize = Range.second - Range.first; if (VecSize == 1) return *Range.first; assert(isValidVectorSize(VecSize) && "Invalid vector size"); IRBuilder<> Builder(InsPos); auto *Vec = Builder.CreateVectorSplat(VecSize, *Range.first); unsigned Index = 1; for (++Range.first; Range.first != Range.second; ++Range.first, ++Index) Vec = Builder.CreateInsertElement( Vec, *Range.first, ConstantInt::get(Type::getInt32Ty(InsPos->getContext()), Index, false)); return Vec; } void makeVector(Instruction *InsPos, std::vector &Ops, ValueVecRange Range) { auto *Vec = addVector(InsPos, Range); Ops.erase(Range.first, Range.second); Ops.push_back(Vec); } PointerType *getInt8PtrTy(PointerType *T) { return PointerType::get(T->getContext(), T->getAddressSpace()); } Value *castToInt8Ptr(Value *V, BasicBlock::iterator Pos) { return CastInst::CreatePointerCast( V, getInt8PtrTy(cast(V->getType())), "", Pos); } IntegerType *getSizetType(Module *M) { return IntegerType::getIntNTy(M->getContext(), M->getDataLayout().getPointerSizeInBits(0)); } ConstantInt *getInt64(Module *M, int64_t Value) { return ConstantInt::getSigned(Type::getInt64Ty(M->getContext()), Value); } ConstantInt *getUInt64(Module *M, uint64_t Value) { return ConstantInt::get(Type::getInt64Ty(M->getContext()), Value, false); } Constant *getFloat32(Module *M, float Value) { return ConstantFP::get(Type::getFloatTy(M->getContext()), Value); } ConstantInt *getInt32(Module *M, int Value) { return ConstantInt::get(Type::getInt32Ty(M->getContext()), Value, true); } ConstantInt *getUInt32(Module *M, unsigned Value) { return ConstantInt::get(Type::getInt32Ty(M->getContext()), Value, false); } ConstantInt *getInt(Module *M, int64_t Value) { return Value >> 32 ? getInt64(M, Value) : getInt32(M, static_cast(Value)); } ConstantInt *getUInt(Module *M, uint64_t Value) { return Value >> 32 ? getUInt64(M, Value) : getUInt32(M, static_cast(Value)); } ConstantInt *getUInt16(Module *M, unsigned short Value) { return ConstantInt::get(Type::getInt16Ty(M->getContext()), Value, false); } std::vector getInt32(Module *M, const std::vector &Values) { std::vector V; for (auto &I : Values) V.push_back(getInt32(M, I)); return V; } ConstantInt *getSizet(Module *M, uint64_t Value) { return ConstantInt::get(getSizetType(M), Value, false); } /////////////////////////////////////////////////////////////////////////////// // // Functions for getting metadata // /////////////////////////////////////////////////////////////////////////////// int64_t getMDOperandAsInt(MDNode *N, unsigned I) { return mdconst::dyn_extract(N->getOperand(I))->getZExtValue(); } // Additional helper function to be reused by getMDOperandAs* helpers Metadata *getMDOperandOrNull(MDNode *N, unsigned I) { if (!N) return nullptr; return N->getOperand(I); } StringRef getMDOperandAsString(MDNode *N, unsigned I) { if (auto *Str = dyn_cast_or_null(getMDOperandOrNull(N, I))) return Str->getString(); return ""; } MDNode *getMDOperandAsMDNode(MDNode *N, unsigned I) { return dyn_cast_or_null(getMDOperandOrNull(N, I)); } Type *getMDOperandAsType(MDNode *N, unsigned I) { return cast(N->getOperand(I))->getType(); } std::set getNamedMDAsStringSet(Module *M, const std::string &MDName) { NamedMDNode *NamedMD = M->getNamedMetadata(MDName); std::set StrSet; if (!NamedMD) return StrSet; assert(NamedMD->getNumOperands() > 0 && "Invalid SPIR"); for (unsigned I = 0, E = NamedMD->getNumOperands(); I != E; ++I) { MDNode *MD = NamedMD->getOperand(I); if (!MD || MD->getNumOperands() == 0) continue; for (unsigned J = 0, N = MD->getNumOperands(); J != N; ++J) StrSet.insert(getMDOperandAsString(MD, J).str()); } return StrSet; } std::tuple getSPIRVSource(Module *M) { std::tuple Tup; if (auto N = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::Source).nextOp()) N.get(std::get<0>(Tup)) .get(std::get<1>(Tup)) .setQuiet(true) .get(std::get<2>(Tup)); return Tup; } bool isDecoratedSPIRVFunc(const Function *F, StringRef &UndecoratedName) { if (!F->hasName() || !F->getName().starts_with(kSPIRVName::Prefix)) return false; UndecoratedName = F->getName(); return true; } /// Get TypePrimitiveEnum for special OpenCL type except opencl.block. SPIR::TypePrimitiveEnum getOCLTypePrimitiveEnum(StringRef TyName) { return StringSwitch(TyName) .Case("opencl.image1d_ro_t", SPIR::PRIMITIVE_IMAGE1D_RO_T) .Case("opencl.image1d_array_ro_t", SPIR::PRIMITIVE_IMAGE1D_ARRAY_RO_T) .Case("opencl.image1d_buffer_ro_t", SPIR::PRIMITIVE_IMAGE1D_BUFFER_RO_T) .Case("opencl.image2d_ro_t", SPIR::PRIMITIVE_IMAGE2D_RO_T) .Case("opencl.image2d_array_ro_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_RO_T) .Case("opencl.image2d_depth_ro_t", SPIR::PRIMITIVE_IMAGE2D_DEPTH_RO_T) .Case("opencl.image2d_array_depth_ro_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_DEPTH_RO_T) .Case("opencl.image2d_msaa_ro_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_RO_T) .Case("opencl.image2d_array_msaa_ro_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_RO_T) .Case("opencl.image2d_msaa_depth_ro_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_DEPTH_RO_T) .Case("opencl.image2d_array_msaa_depth_ro_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_DEPTH_RO_T) .Case("opencl.image3d_ro_t", SPIR::PRIMITIVE_IMAGE3D_RO_T) .Case("opencl.image1d_wo_t", SPIR::PRIMITIVE_IMAGE1D_WO_T) .Case("opencl.image1d_array_wo_t", SPIR::PRIMITIVE_IMAGE1D_ARRAY_WO_T) .Case("opencl.image1d_buffer_wo_t", SPIR::PRIMITIVE_IMAGE1D_BUFFER_WO_T) .Case("opencl.image2d_wo_t", SPIR::PRIMITIVE_IMAGE2D_WO_T) .Case("opencl.image2d_array_wo_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_WO_T) .Case("opencl.image2d_depth_wo_t", SPIR::PRIMITIVE_IMAGE2D_DEPTH_WO_T) .Case("opencl.image2d_array_depth_wo_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_DEPTH_WO_T) .Case("opencl.image2d_msaa_wo_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_WO_T) .Case("opencl.image2d_array_msaa_wo_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_WO_T) .Case("opencl.image2d_msaa_depth_wo_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_DEPTH_WO_T) .Case("opencl.image2d_array_msaa_depth_wo_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_DEPTH_WO_T) .Case("opencl.image3d_wo_t", SPIR::PRIMITIVE_IMAGE3D_WO_T) .Case("opencl.image1d_rw_t", SPIR::PRIMITIVE_IMAGE1D_RW_T) .Case("opencl.image1d_array_rw_t", SPIR::PRIMITIVE_IMAGE1D_ARRAY_RW_T) .Case("opencl.image1d_buffer_rw_t", SPIR::PRIMITIVE_IMAGE1D_BUFFER_RW_T) .Case("opencl.image2d_rw_t", SPIR::PRIMITIVE_IMAGE2D_RW_T) .Case("opencl.image2d_array_rw_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_RW_T) .Case("opencl.image2d_depth_rw_t", SPIR::PRIMITIVE_IMAGE2D_DEPTH_RW_T) .Case("opencl.image2d_array_depth_rw_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_DEPTH_RW_T) .Case("opencl.image2d_msaa_rw_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_RW_T) .Case("opencl.image2d_array_msaa_rw_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_RW_T) .Case("opencl.image2d_msaa_depth_rw_t", SPIR::PRIMITIVE_IMAGE2D_MSAA_DEPTH_RW_T) .Case("opencl.image2d_array_msaa_depth_rw_t", SPIR::PRIMITIVE_IMAGE2D_ARRAY_MSAA_DEPTH_RW_T) .Case("opencl.image3d_rw_t", SPIR::PRIMITIVE_IMAGE3D_RW_T) .Case("opencl.event_t", SPIR::PRIMITIVE_EVENT_T) .Case("opencl.pipe_ro_t", SPIR::PRIMITIVE_PIPE_RO_T) .Case("opencl.pipe_wo_t", SPIR::PRIMITIVE_PIPE_WO_T) .Case("opencl.reserve_id_t", SPIR::PRIMITIVE_RESERVE_ID_T) .Case("opencl.queue_t", SPIR::PRIMITIVE_QUEUE_T) .Case("opencl.clk_event_t", SPIR::PRIMITIVE_CLK_EVENT_T) .Case("opencl.sampler_t", SPIR::PRIMITIVE_SAMPLER_T) .Case("struct.ndrange_t", SPIR::PRIMITIVE_NDRANGE_T) .Case("opencl.intel_sub_group_avc_mce_payload_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_MCE_PAYLOAD_T) .Case("opencl.intel_sub_group_avc_ime_payload_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_PAYLOAD_T) .Case("opencl.intel_sub_group_avc_ref_payload_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_REF_PAYLOAD_T) .Case("opencl.intel_sub_group_avc_sic_payload_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_SIC_PAYLOAD_T) .Case("opencl.intel_sub_group_avc_mce_result_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_MCE_RESULT_T) .Case("opencl.intel_sub_group_avc_ime_result_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_RESULT_T) .Case("opencl.intel_sub_group_avc_ref_result_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_REF_RESULT_T) .Case("opencl.intel_sub_group_avc_sic_result_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_SIC_RESULT_T) .Case( "opencl.intel_sub_group_avc_ime_result_single_reference_streamout_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_SINGLE_REF_STREAMOUT_T) .Case("opencl.intel_sub_group_avc_ime_result_dual_reference_streamout_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_DUAL_REF_STREAMOUT_T) .Case("opencl.intel_sub_group_avc_ime_single_reference_streamin_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_SINGLE_REF_STREAMIN_T) .Case("opencl.intel_sub_group_avc_ime_dual_reference_streamin_t", SPIR::PRIMITIVE_SUB_GROUP_AVC_IME_DUAL_REF_STREAMIN_T) .Default(SPIR::PRIMITIVE_NONE); } /// Translates LLVM type to descriptor for mangler. /// \param Signed indicates integer type should be translated as signed. /// \param VoidPtr indicates i8* should be translated as void*. static SPIR::RefParamType transTypeDesc(Type *Ty, const BuiltinArgTypeMangleInfo &Info, StringRef InstName = "") { bool Signed = Info.IsSigned; unsigned Attr = Info.Attr; bool VoidPtr = Info.IsVoidPtr; if (Info.IsEnum) return SPIR::RefParamType(new SPIR::PrimitiveType(Info.Enum)); if (Info.IsSampler) return SPIR::RefParamType( new SPIR::PrimitiveType(SPIR::PRIMITIVE_SAMPLER_T)); if (Ty->isPointerTy()) Ty = TypedPointerType::get(Type::getInt8Ty(Ty->getContext()), Ty->getPointerAddressSpace()); if (Info.IsAtomic && !isa(Ty)) { BuiltinArgTypeMangleInfo DTInfo = Info; DTInfo.IsAtomic = false; return SPIR::RefParamType(new SPIR::AtomicType(transTypeDesc(Ty, DTInfo))); } if (auto *IntTy = dyn_cast(Ty)) { switch (IntTy->getBitWidth()) { case 1: return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_BOOL)); case 8: return SPIR::RefParamType(new SPIR::PrimitiveType( Signed ? SPIR::PRIMITIVE_CHAR : SPIR::PRIMITIVE_UCHAR)); case 16: return SPIR::RefParamType(new SPIR::PrimitiveType( Signed ? SPIR::PRIMITIVE_SHORT : SPIR::PRIMITIVE_USHORT)); case 32: return SPIR::RefParamType(new SPIR::PrimitiveType( Signed ? SPIR::PRIMITIVE_INT : SPIR::PRIMITIVE_UINT)); case 64: return SPIR::RefParamType(new SPIR::PrimitiveType( Signed ? SPIR::PRIMITIVE_LONG : SPIR::PRIMITIVE_ULONG)); default: return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_INT)); } } if (Ty->isVoidTy()) return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_VOID)); if (Ty->isHalfTy()) return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_HALF)); if (Ty->isFloatTy()) return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_FLOAT)); if (Ty->isDoubleTy()) return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_DOUBLE)); if (Ty->isBFloatTy()) return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_BFLOAT)); if (auto *VecTy = dyn_cast(Ty)) { return SPIR::RefParamType(new SPIR::VectorType( transTypeDesc(VecTy->getElementType(), Info), VecTy->getNumElements())); } if (Ty->isArrayTy()) { return transTypeDesc(TypedPointerType::get(Ty->getArrayElementType(), 0), Info); } if (Ty->isStructTy()) { auto Name = Ty->getStructName(); std::string Tmp; if (Name.starts_with(kLLVMTypeName::StructPrefix)) Name = Name.drop_front(strlen(kLLVMTypeName::StructPrefix)); if (Name.starts_with(kSPIRVTypeName::PrefixAndDelim)) { Name = Name.substr(sizeof(kSPIRVTypeName::PrefixAndDelim) - 1); Tmp = Name.str(); auto Pos = Tmp.find(kSPIRVTypeName::Delimiter); // first dot while (Pos != std::string::npos) { Tmp[Pos] = '_'; Pos = Tmp.find(kSPIRVTypeName::Delimiter, Pos); } Name = Tmp = kSPIRVName::Prefix + Tmp; } // ToDo: Create a better unique Name for struct without Name if (Name.empty()) { std::ostringstream OS; OS << reinterpret_cast(Ty); Name = Tmp = std::string("struct_") + OS.str(); } return SPIR::RefParamType(new SPIR::UserDefinedType(Name.str())); } if (auto *TargetTy = dyn_cast(Ty)) { std::string FullName; unsigned AS = 0; { raw_string_ostream OS(FullName); StringRef Name = TargetTy->getName(); if (Name.consume_front(kSPIRVTypeName::PrefixAndDelim)) { OS << "__spirv_" << Name; AS = getOCLOpaqueTypeAddrSpace( SPIRVOpaqueTypeOpCodeMap::map(Name.str())); } else { OS << Name; } if (!TargetTy->int_params().empty()) OS << "_"; for (Type *InnerTy : TargetTy->type_params()) OS << "_" << convertTypeToPostfix(InnerTy); for (unsigned Param : TargetTy->int_params()) OS << "_" << Param; } // Translate as if it's a pointer to the named struct. auto *Inner = new SPIR::UserDefinedType(FullName); auto *PT = new SPIR::PointerType(Inner); PT->setAddressSpace(static_cast( AS + (unsigned)SPIR::ATTR_ADDR_SPACE_FIRST)); return SPIR::RefParamType(PT); } if (auto *TPT = dyn_cast(Ty)) { auto *ET = TPT->getElementType(); SPIR::ParamType *EPT = nullptr; if (isa(ET)) { FunctionType *FT = cast(ET); if (InstName.consume_front(kSPIRVName::Prefix) && InstName.starts_with("TaskSequence")) { EPT = new SPIR::PointerType(transTypeDesc(FT->getReturnType(), Info)); } else { assert((isVoidFuncTy(FT)) && "Not supported"); EPT = new SPIR::BlockType; } } else if (auto *StructTy = dyn_cast(ET)) { LLVM_DEBUG(dbgs() << "ptr to struct: " << *Ty << '\n'); auto TyName = StructTy->getStructName(); if (TyName.starts_with(kSPR2TypeName::OCLPrefix)) { auto DelimPos = TyName.find_first_of(kSPR2TypeName::Delimiter, strlen(kSPR2TypeName::OCLPrefix)); if (DelimPos != StringRef::npos) TyName = TyName.substr(0, DelimPos); } LLVM_DEBUG(dbgs() << " type Name: " << TyName << '\n'); auto Prim = getOCLTypePrimitiveEnum(TyName); if (StructTy->isOpaque()) { if (TyName == "opencl.block") { auto *BlockTy = new SPIR::BlockType; // Handle block with local memory arguments according to OpenCL 2.0 // spec. if (Info.IsLocalArgBlock) { SPIR::RefParamType VoidTyRef( new SPIR::PrimitiveType(SPIR::PRIMITIVE_VOID)); auto *VoidPtrTy = new SPIR::PointerType(VoidTyRef); VoidPtrTy->setAddressSpace(SPIR::ATTR_LOCAL); // "__local void *" BlockTy->setParam(0, SPIR::RefParamType(VoidPtrTy)); // "..." BlockTy->setParam(1, SPIR::RefParamType(new SPIR::PrimitiveType( SPIR::PRIMITIVE_VAR_ARG))); } EPT = BlockTy; } else if (Prim != SPIR::PRIMITIVE_NONE) { if (Prim == SPIR::PRIMITIVE_PIPE_RO_T || Prim == SPIR::PRIMITIVE_PIPE_WO_T) { SPIR::RefParamType OpaqueTyRef(new SPIR::PrimitiveType(Prim)); auto *OpaquePtrTy = new SPIR::PointerType(OpaqueTyRef); OpaquePtrTy->setAddressSpace(getOCLOpaqueTypeAddrSpace(Prim)); EPT = OpaquePtrTy; } else { EPT = new SPIR::PrimitiveType(Prim); } } } else if (Prim == SPIR::PRIMITIVE_NDRANGE_T) // ndrange_t is not opaque type EPT = new SPIR::PrimitiveType(SPIR::PRIMITIVE_NDRANGE_T); } if (EPT) return SPIR::RefParamType(EPT); if (VoidPtr && ET->isIntegerTy(8)) ET = Type::getVoidTy(ET->getContext()); auto *PT = new SPIR::PointerType(transTypeDesc(ET, Info)); PT->setAddressSpace(static_cast( TPT->getAddressSpace() + (unsigned)SPIR::ATTR_ADDR_SPACE_FIRST)); for (unsigned I = SPIR::ATTR_QUALIFIER_FIRST, E = SPIR::ATTR_QUALIFIER_LAST; I <= E; ++I) PT->setQualifier(static_cast(I), I & Attr); return SPIR::RefParamType(PT); } LLVM_DEBUG(dbgs() << "[transTypeDesc] " << *Ty << '\n'); assert(0 && "not implemented"); return SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_INT)); } Value *getScalarOrArray(Value *V, unsigned Size, BasicBlock::iterator Pos) { if (!V->getType()->isPointerTy()) return V; Type *SourceTy; Value *Addr; if (auto *GV = dyn_cast(V)) { SourceTy = GV->getValueType(); Addr = GV; } else if (auto *AI = dyn_cast(V)) { SourceTy = AI->getAllocatedType(); Addr = AI; } else if (auto *GEP = dyn_cast(V)) { assert(GEP->getNumOperands() == 3 && "must be a GEP from an array"); SourceTy = GEP->getSourceElementType(); [[maybe_unused]] auto *OP1 = cast(GEP->getOperand(1)); [[maybe_unused]] auto *OP2 = cast(GEP->getOperand(2)); assert(OP1->getZExtValue() == 0); assert(OP2->getZExtValue() == 0); Addr = GEP->getOperand(0); } else { llvm_unreachable("Unknown array type"); } assert(SourceTy->getArrayNumElements() == Size); return new LoadInst(SourceTy, Addr, "", Pos); } Constant *getScalarOrVectorConstantInt(Type *T, uint64_t V, bool IsSigned) { if (auto *IT = dyn_cast(T)) return ConstantInt::get(IT, V); if (auto *VT = dyn_cast(T)) { std::vector EV( VT->getNumElements(), getScalarOrVectorConstantInt(VT->getElementType(), V, IsSigned)); return ConstantVector::get(EV); } llvm_unreachable("Invalid type"); return nullptr; } Value *getScalarOrArrayConstantInt(BasicBlock::iterator Pos, Type *T, unsigned Len, uint64_t V, bool IsSigned) { if (auto *IT = dyn_cast(T)) { assert(Len == 1 && "Invalid length"); return ConstantInt::get(IT, V, IsSigned); } if (isa(T)) { unsigned PointerSize = Pos->getModule()->getDataLayout().getPointerTypeSizeInBits(T); auto *ET = Type::getIntNTy(T->getContext(), PointerSize); auto *AT = ArrayType::get(ET, Len); std::vector EV(Len, ConstantInt::get(ET, V, IsSigned)); auto *CA = ConstantArray::get(AT, EV); auto *Alloca = new AllocaInst( AT, Pos->getParent()->getParent()->getDataLayout().getAllocaAddrSpace(), "", Pos); new StoreInst(CA, Alloca, Pos); auto *Zero = ConstantInt::getNullValue(Type::getInt32Ty(T->getContext())); Value *Index[] = {Zero, Zero}; auto *Ret = GetElementPtrInst::CreateInBounds(AT, Alloca, Index, "", Pos); LLVM_DEBUG(dbgs() << "[getScalarOrArrayConstantInt] Alloca: " << *Alloca << ", Return: " << *Ret << '\n'); return Ret; } if (auto *AT = dyn_cast(T)) { auto *ET = AT->getArrayElementType(); assert(AT->getArrayNumElements() == Len); std::vector EV(Len, ConstantInt::get(ET, V, IsSigned)); auto *Ret = ConstantArray::get(AT, EV); LLVM_DEBUG(dbgs() << "[getScalarOrArrayConstantInt] Array type: " << *AT << ", Return: " << *Ret << '\n'); return Ret; } llvm_unreachable("Invalid type"); return nullptr; } void dumpUsers(Value *V, StringRef Prompt) { if (!V) return; LLVM_DEBUG(dbgs() << Prompt << " Users of " << *V << " :\n"); for (auto UI = V->user_begin(), UE = V->user_end(); UI != UE; ++UI) LLVM_DEBUG(dbgs() << " " << **UI << '\n'); } std::string getSPIRVTypeName(StringRef BaseName, StringRef Postfixes) { assert(!BaseName.empty() && "Invalid SPIR-V type Name"); auto TN = std::string(kSPIRVTypeName::PrefixAndDelim) + BaseName.str(); if (Postfixes.empty()) return TN; return TN + kSPIRVTypeName::Delimiter + Postfixes.str(); } bool isSPIRVConstantName(StringRef TyName) { if (TyName == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler) || TyName == getSPIRVTypeName(kSPIRVTypeName::ConstantPipeStorage)) return true; return false; } // ToDo: Find a way to represent uint sampled type in LLVM, maybe an // opaque type. Type *getLLVMTypeForSPIRVImageSampledTypePostfix(StringRef Postfix, LLVMContext &Ctx) { if (Postfix == kSPIRVImageSampledTypeName::Void) return Type::getVoidTy(Ctx); if (Postfix == kSPIRVImageSampledTypeName::Float) return Type::getFloatTy(Ctx); if (Postfix == kSPIRVImageSampledTypeName::Half) return Type::getHalfTy(Ctx); if (Postfix == kSPIRVImageSampledTypeName::Int || Postfix == kSPIRVImageSampledTypeName::UInt) return Type::getInt32Ty(Ctx); if (Postfix == kSPIRVImageSampledTypeName::Long || Postfix == kSPIRVImageSampledTypeName::ULong) return Type::getInt64Ty(Ctx); llvm_unreachable("Invalid sampled type postfix"); return nullptr; } std::string convertTypeToPostfix(Type *Ty) { if (Ty->isIntegerTy()) { switch (Ty->getIntegerBitWidth()) { case 8: return "char"; case 16: return "short"; case 32: return "uint"; case 64: return "long"; default: return (Twine("i") + Twine(Ty->getIntegerBitWidth())).str(); } } else if (Ty->isHalfTy()) { return "half"; } else if (Ty->isFloatTy()) { return "float"; } else if (Ty->isDoubleTy()) { return "double"; } else if (Ty->isBFloatTy()) { return "bfloat16"; } else if (Ty->isVoidTy()) { return "void"; } else { report_fatal_error("Unknown LLVM type for element type"); } } std::string getImageBaseTypeName(StringRef Name) { SmallVector SubStrs; const char Delims[] = {kSPR2TypeName::Delimiter, 0}; Name.split(SubStrs, Delims); if (Name.starts_with(kSPR2TypeName::OCLPrefix)) { Name = SubStrs[1]; } else { Name = SubStrs[0]; } std::string ImageTyName{Name}; if (hasAccessQualifiedName(Name)) ImageTyName.erase(ImageTyName.size() - 5, 3); return ImageTyName; } size_t getImageOperandsIndex(Op OpCode) { switch (OpCode) { case OpImageRead: case OpImageSampleExplicitLod: return 2; case OpImageWrite: return 3; default: return ~0U; } } SPIRVTypeImageDescriptor getImageDescriptor(Type *Ty) { if (auto *TET = dyn_cast_or_null(Ty)) { auto IntParams = TET->int_params(); assert(IntParams.size() > 6 && "Expected type to be an image type"); return SPIRVTypeImageDescriptor(SPIRVImageDimKind(IntParams[0]), IntParams[1], IntParams[2], IntParams[3], IntParams[4], IntParams[5]); } StringRef TyName; [[maybe_unused]] bool IsImg = isOCLImageType(Ty, &TyName); assert(IsImg && "Must be an image type"); return map(getImageBaseTypeName(TyName)); } bool eraseIfNoUse(Function *F) { bool Changed = false; if (!F) return Changed; if (!GlobalValue::isInternalLinkage(F->getLinkage()) && !F->isDeclaration()) return Changed; dumpUsers(F, "[eraseIfNoUse] "); for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE;) { auto *U = *UI++; if (auto *CE = dyn_cast(U)) { if (CE->use_empty()) { CE->dropAllReferences(); Changed = true; } } } if (F->use_empty()) { LLVM_DEBUG(dbgs() << "Erase "; F->printAsOperand(dbgs()); dbgs() << '\n'); F->eraseFromParent(); Changed = true; } return Changed; } bool eraseUselessFunctions(Module *M) { bool Changed = false; for (auto I = M->begin(), E = M->end(); I != E;) Changed |= eraseIfNoUse(&(*I++)); return Changed; } // The mangling algorithm follows OpenCL pipe built-ins clang 3.8 CodeGen rules. static SPIR::MangleError manglePipeOrAddressSpaceCastBuiltin(const SPIR::FunctionDescriptor &Fd, std::string &MangledName) { assert(OCLUtil::isPipeOrAddressSpaceCastBI(Fd.Name) && "Method is expected to be called only for pipe and address space cast " "builtins!"); if (Fd.isNull()) { MangledName.assign(SPIR::FunctionDescriptor::nullString()); return SPIR::MANGLE_NULL_FUNC_DESCRIPTOR; } MangledName.assign("__" + Fd.Name); return SPIR::MANGLE_SUCCESS; } std::string mangleBuiltin(StringRef UniqName, ArrayRef ArgTypes, BuiltinFuncMangleInfo *BtnInfo) { if (!BtnInfo) return std::string(UniqName); BtnInfo->init(UniqName); if (BtnInfo->avoidMangling()) return std::string(UniqName); std::string MangledName; LLVM_DEBUG(dbgs() << "[mangle] " << UniqName << " => "); SPIR::FunctionDescriptor FD; FD.Name = BtnInfo->getUnmangledName(); bool BIVarArgNegative = BtnInfo->getVarArg() < 0; if (ArgTypes.empty()) { // Function signature cannot be ()(void, ...) so if there is an ellipsis // it must be ()(...) if (BIVarArgNegative) { FD.Parameters.emplace_back( SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_VOID))); } } else { for (unsigned I = 0, E = BIVarArgNegative ? ArgTypes.size() : (unsigned)BtnInfo->getVarArg(); I != E; ++I) { auto *T = ArgTypes[I]; auto MangleInfo = BtnInfo->getTypeMangleInfo(I); if (MangleInfo.PointerTy && T->isPointerTy()) { T = MangleInfo.PointerTy; } FD.Parameters.emplace_back( transTypeDesc(T, BtnInfo->getTypeMangleInfo(I), UniqName)); } } // Ellipsis must be the last argument of any function if (!BIVarArgNegative) { assert((unsigned)BtnInfo->getVarArg() <= ArgTypes.size() && "invalid index of an ellipsis"); FD.Parameters.emplace_back( SPIR::RefParamType(new SPIR::PrimitiveType(SPIR::PRIMITIVE_VAR_ARG))); } #if defined(SPIRV_SPIR20_MANGLING_REQUIREMENTS) SPIR::NameMangler Mangler(SPIR::SPIR20); Mangler.mangle(FD, MangledName); #else if (OCLUtil::isPipeOrAddressSpaceCastBI(BtnInfo->getUnmangledName())) { manglePipeOrAddressSpaceCastBuiltin(FD, MangledName); } else { SPIR::NameMangler Mangler(SPIR::SPIR20); Mangler.mangle(FD, MangledName); } #endif LLVM_DEBUG(dbgs() << MangledName << '\n'); return MangledName; } /// Check if access qualifier is encoded in the type Name. bool hasAccessQualifiedName(StringRef TyName) { if (TyName.size() < 5) return false; auto Acc = TyName.substr(TyName.size() - 5, 3); return llvm::StringSwitch(Acc) .Case(kAccessQualPostfix::ReadOnly, true) .Case(kAccessQualPostfix::WriteOnly, true) .Case(kAccessQualPostfix::ReadWrite, true) .Default(false); } SPIRVAccessQualifierKind getAccessQualifier(StringRef TyName) { assert(hasAccessQualifiedName(TyName) && "Type is not qualified with access."); auto Acc = TyName.substr(TyName.size() - 5, 3); return llvm::StringSwitch(Acc) .Case(kAccessQualPostfix::ReadOnly, AccessQualifierReadOnly) .Case(kAccessQualPostfix::WriteOnly, AccessQualifierWriteOnly) .Case(kAccessQualPostfix::ReadWrite, AccessQualifierReadWrite); } StringRef getAccessQualifierPostfix(SPIRVAccessQualifierKind Access) { switch (Access) { case AccessQualifierReadOnly: return kAccessQualPostfix::ReadOnly; case AccessQualifierWriteOnly: return kAccessQualPostfix::WriteOnly; case AccessQualifierReadWrite: return kAccessQualPostfix::ReadWrite; default: assert(false && "Unrecognized access qualifier!"); return kAccessQualPostfix::ReadWrite; } } bool hasLoopMetadata(const Module *M) { for (const Function &F : *M) for (const BasicBlock &BB : F) { const Instruction *Term = BB.getTerminator(); if (Term && Term->getMetadata("llvm.loop")) return true; } return false; } bool isSPIRVOCLExtInst(const CallInst *CI, OCLExtOpKind *ExtOp) { StringRef DemangledName; if (!oclIsBuiltin(CI->getCalledFunction()->getName(), DemangledName)) return false; StringRef S = DemangledName; if (!S.starts_with(kSPIRVName::Prefix)) return false; S = S.drop_front(strlen(kSPIRVName::Prefix)); auto Loc = S.find(kSPIRVPostfix::Divider); auto ExtSetName = S.substr(0, Loc); SPIRVExtInstSetKind Set = SPIRVEIS_Count; if (!SPIRVExtSetShortNameMap::rfind(ExtSetName.str(), &Set)) return false; if (Set != SPIRVEIS_OpenCL) return false; auto ExtOpName = S.substr(Loc + 1); auto PostFixPos = ExtOpName.find("_R"); ExtOpName = ExtOpName.substr(0, PostFixPos); OCLExtOpKind EOC; if (!OCLExtOpMap::rfind(ExtOpName.str(), &EOC)) return false; *ExtOp = EOC; return true; } std::string decodeSPIRVTypeName(StringRef Name, SmallVectorImpl &Strs) { SmallVector SubStrs; const char Delim[] = {kSPIRVTypeName::Delimiter, 0}; Name.split(SubStrs, Delim, -1, true); assert(SubStrs.size() >= 2 && "Invalid SPIRV type name"); assert(SubStrs[0] == kSPIRVTypeName::Prefix && "Invalid prefix"); assert((SubStrs.size() == 2 || !SubStrs[2].empty()) && "Invalid postfix"); if (SubStrs.size() > 2) { const char PostDelim[] = {kSPIRVTypeName::PostfixDelim, 0}; SmallVector Postfixes; SubStrs[2].split(Postfixes, PostDelim, -1, true); assert(Postfixes.size() > 1 && Postfixes[0].empty() && "Invalid postfix"); for (unsigned I = 1, E = Postfixes.size(); I != E; ++I) Strs.push_back(std::string(Postfixes[I]).c_str()); } return SubStrs[1].str(); } // Returns true if type(s) and number of elements (if vector) is valid bool checkTypeForSPIRVExtendedInstLowering(IntrinsicInst *II, SPIRVModule *BM) { switch (II->getIntrinsicID()) { case Intrinsic::acos: case Intrinsic::asin: case Intrinsic::atan: case Intrinsic::atan2: case Intrinsic::ceil: case Intrinsic::copysign: case Intrinsic::cos: case Intrinsic::cosh: case Intrinsic::exp: case Intrinsic::exp2: case Intrinsic::fabs: case Intrinsic::floor: case Intrinsic::fma: case Intrinsic::log: case Intrinsic::log10: case Intrinsic::log2: case Intrinsic::maximum: case Intrinsic::maxnum: case Intrinsic::minimum: case Intrinsic::minnum: case Intrinsic::nearbyint: case Intrinsic::pow: case Intrinsic::powi: case Intrinsic::rint: case Intrinsic::round: case Intrinsic::roundeven: case Intrinsic::sin: case Intrinsic::sinh: case Intrinsic::sqrt: case Intrinsic::tan: case Intrinsic::tanh: case Intrinsic::trunc: { // Although some of the intrinsics above take multiple arguments, it is // sufficient to check arg 0 because the LLVM Verifier will have checked // that all floating point operands have the same type and the second // argument of powi is i32. Type *Ty = II->getType(); if (II->getArgOperand(0)->getType() != Ty) return false; int NumElems = 1; if (auto *VecTy = dyn_cast(Ty)) { NumElems = VecTy->getNumElements(); Ty = VecTy->getElementType(); } if (Ty->isBFloatTy() && BM->hasCapability(internal::CapabilityBFloat16ArithmeticINTEL)) return true; if ((!Ty->isFloatTy() && !Ty->isDoubleTy() && !Ty->isHalfTy()) || (!BM->hasCapability(CapabilityVectorAnyINTEL) && !BM->hasCapability(CapabilityLongVectorEXT) && ((NumElems > 4) && (NumElems != 8) && (NumElems != 16)))) { BM->SPIRVCK(false, InvalidFunctionCall, II->getCalledOperand()->getName().str()); return false; } break; } case Intrinsic::abs: { Type *Ty = II->getType(); int NumElems = 1; if (auto *VecTy = dyn_cast(Ty)) { NumElems = VecTy->getNumElements(); Ty = VecTy->getElementType(); } if (Ty->isBFloatTy() && BM->hasCapability(internal::CapabilityBFloat16ArithmeticINTEL)) return true; if ((!Ty->isIntegerTy()) || (!BM->hasCapability(CapabilityVectorAnyINTEL) && !BM->hasCapability(CapabilityLongVectorEXT) && ((NumElems > 4) && (NumElems != 8) && (NumElems != 16)))) { BM->SPIRVCK(false, InvalidFunctionCall, II->getCalledOperand()->getName().str()); } break; } default: break; } return true; } CallInst *setAttrByCalledFunc(CallInst *Call) { Function *F = Call->getCalledFunction(); assert(F); if (F->isIntrinsic()) { return Call; } Call->setCallingConv(F->getCallingConv()); Call->setAttributes(F->getAttributes()); return Call; } bool isSPIRVBuiltinVariable(GlobalVariable *GV, SPIRVBuiltinVariableKind *Kind) { if (!GV->hasName() || !getSPIRVBuiltin(GV->getName().str(), *Kind)) return false; return true; } // Variable like GlobalInvolcationId[x] -> get_global_id(x). // Variable like WorkDim -> get_work_dim(). // Replace the following pattern: // %a = addrspacecast i32 addrspace(1)* @__spirv_BuiltInSubgroupMaxSize to // i32 addrspace(4)* // %b = load i32, i32 addrspace(4)* %a, align 4 // %c = load i32, i32 addrspace(4)* %a, align 4 // With: // %b = call spir_func i32 @_Z22get_max_sub_group_sizev() // %c = call spir_func i32 @_Z22get_max_sub_group_sizev() // And replace the following pattern: // %a = addrspacecast <3 x i64> addrspace(1)* @__spirv_BuiltInWorkgroupId to // <3 x i64> addrspace(4)* // %b = load <3 x i64>, <3 x i64> addrspace(4)* %a, align 32 // %c = extractelement <3 x i64> %b, i32 idx // %d = extractelement <3 x i64> %b, i32 idx // With: // %0 = call spir_func i64 @_Z13get_global_idj(i32 0) #1 // %1 = insertelement <3 x i64> poison, i64 %0, i32 0 // %2 = call spir_func i64 @_Z13get_global_idj(i32 1) #1 // %3 = insertelement <3 x i64> %1, i64 %2, i32 1 // %4 = call spir_func i64 @_Z13get_global_idj(i32 2) #1 // %5 = insertelement <3 x i64> %3, i64 %4, i32 2 // %c = extractelement <3 x i64> %5, i32 idx // %d = extractelement <3 x i64> %5, i32 idx // // Replace the following pattern: // %0 = addrspacecast <3 x i64> addrspace(1)* @__spirv_BuiltInWorkgroupSize to // <3 x i64> addrspace(4)* // %1 = getelementptr <3 x i64>, <3 x i64> addrspace(4)* %0, i64 0, i64 0 // %2 = load i64, i64 addrspace(4)* %1, align 32 // With: // %0 = call spir_func i64 @_Z13get_global_idj(i32 0) #1 // %1 = insertelement <3 x i64> poison, i64 %0, i32 0 // %2 = call spir_func i64 @_Z13get_global_idj(i32 1) #1 // %3 = insertelement <3 x i64> %1, i64 %2, i32 1 // %4 = call spir_func i64 @_Z13get_global_idj(i32 2) #1 // %5 = insertelement <3 x i64> %3, i64 %4, i32 2 // %6 = extractelement <3 x i64> %5, i32 0 /// Recursively look through the uses of a global variable, including casts or /// gep offsets, to find all loads of the variable. Gep offsets that are non-0 /// are accumulated in the AccumulatedOffset parameter, which will eventually be /// used to figure out which index of a variable is being used. static void replaceUsesOfBuiltinVar(Value *V, const APInt &AccumulatedOffset, Function *ReplacementFunc, GlobalVariable *GV) { const DataLayout &DL = ReplacementFunc->getParent()->getDataLayout(); SmallVector InstsToRemove; for (User *U : V->users()) { if (auto *Cast = dyn_cast(U)) { replaceUsesOfBuiltinVar(Cast, AccumulatedOffset, ReplacementFunc, GV); InstsToRemove.push_back(Cast); } else if (auto *GEP = dyn_cast(U)) { APInt NewOffset = AccumulatedOffset.sextOrTrunc( DL.getIndexSizeInBits(GEP->getPointerAddressSpace())); if (!GEP->accumulateConstantOffset(DL, NewOffset)) llvm_unreachable("Illegal GEP of a SPIR-V builtin variable"); replaceUsesOfBuiltinVar(GEP, NewOffset, ReplacementFunc, GV); if (auto *AsInst = dyn_cast(U)) InstsToRemove.push_back(AsInst); } else if (auto *Load = dyn_cast(U)) { // Figure out which index the accumulated offset corresponds to. If we // have a weird offset (e.g., trying to load byte 7), bail out. APInt Index; Type *ScalarTy = ReplacementFunc->getReturnType(); if (!ScalarTy->isIntegerTy(1)) { uint64_t Remainder; APInt::udivrem(AccumulatedOffset, ScalarTy->getScalarSizeInBits() / 8, Index, Remainder); if (Remainder != 0) llvm_unreachable("Illegal GEP of a SPIR-V builtin variable"); } IRBuilder<> Builder(Load); Value *Replacement; if (ReplacementFunc->getFunctionType()->getNumParams() == 0) { if (Load->getType() != ScalarTy) llvm_unreachable("Illegal use of a SPIR-V builtin variable"); Replacement = setAttrByCalledFunc(Builder.CreateCall(ReplacementFunc, {})); } else { // The function has an index parameter. if (auto *VecTy = dyn_cast(Load->getType())) { // Reconstruct the original global variable vector because // the load type may not match. // global <3 x i64>, load <6 x i32> VecTy = cast(GV->getValueType()); if (!Index.isZero() || DL.getTypeSizeInBits(VecTy) != DL.getTypeSizeInBits(Load->getType())) llvm_unreachable("Illegal use of a SPIR-V builtin variable"); Replacement = PoisonValue::get(VecTy); for (unsigned I = 0; I < VecTy->getNumElements(); I++) { Replacement = Builder.CreateInsertElement( Replacement, setAttrByCalledFunc( Builder.CreateCall(ReplacementFunc, {Builder.getInt32(I)})), Builder.getInt32(I)); } // Insert a bitcast from the reconstructed vector to the load vector // type in case they are different. // Input: // %1 = load <6 x i32>, ptr addrspace(1) %0, align 32 // %2 = extractelement <6 x i32> %1, i32 0 // %3 = add i32 5, %2 // Modified: // < reconstruct global vector elements 0 and 1 > // %2 = insertelement <3 x i64> %0, i64 %1, i32 2 // %3 = bitcast <3 x i64> %2 to <6 x i32> // %4 = extractelement <6 x i32> %3, i32 0 // %5 = add i32 5, %4 Replacement = Builder.CreateBitCast(Replacement, Load->getType()); } else if (Load->getType() == ScalarTy) { Replacement = setAttrByCalledFunc(Builder.CreateCall( ReplacementFunc, {Builder.getInt32(Index.getZExtValue())})); } else { llvm_unreachable("Illegal load type of a SPIR-V builtin variable"); } } Load->replaceAllUsesWith(Replacement); InstsToRemove.push_back(Load); } else { llvm_unreachable("Illegal use of a SPIR-V builtin variable"); } } for (Instruction *I : InstsToRemove) I->eraseFromParent(); } bool lowerBuiltinVariableToCall(GlobalVariable *GV, SPIRVBuiltinVariableKind Kind) { // There might be dead constant users of GV (for example, SPIRVLowerConstExpr // replaces ConstExpr uses but those ConstExprs are not deleted, since LLVM // constants are created on demand as needed and never deleted). // Remove them first! GV->removeDeadConstantUsers(); Module *M = GV->getParent(); LLVMContext &C = M->getContext(); std::string FuncName = GV->getName().str(); Type *GVTy = GV->getValueType(); Type *ReturnTy = GVTy; // Some SPIR-V builtin variables are translated to a function with an index // argument. bool HasIndexArg = ReturnTy->isVectorTy() && !(BuiltInSubgroupEqMask <= Kind && Kind <= BuiltInSubgroupLtMask); if (HasIndexArg) ReturnTy = cast(ReturnTy)->getElementType(); std::vector ArgTy; if (HasIndexArg) ArgTy.push_back(Type::getInt32Ty(C)); std::string MangledName; mangleOpenClBuiltin(FuncName, ArgTy, MangledName); Function *Func = M->getFunction(MangledName); if (!Func) { FunctionType *FT = FunctionType::get(ReturnTy, ArgTy, false); Func = Function::Create(FT, GlobalValue::ExternalLinkage, MangledName, M); Func->setCallingConv(CallingConv::SPIR_FUNC); Func->addFnAttr(Attribute::NoUnwind); Func->addFnAttr(Attribute::WillReturn); Func->setDoesNotAccessMemory(); } replaceUsesOfBuiltinVar(GV, APInt(64, 0), Func, GV); return true; } bool lowerBuiltinVariablesToCalls(Module *M) { std::vector WorkList; for (auto I = M->global_begin(), E = M->global_end(); I != E; ++I) { SPIRVBuiltinVariableKind Kind; if (!isSPIRVBuiltinVariable(&(*I), &Kind)) continue; if (!lowerBuiltinVariableToCall(&(*I), Kind)) return false; WorkList.push_back(&(*I)); } for (auto &I : WorkList) { I->eraseFromParent(); } return true; } /// Transforms SPV-IR work-item builtin calls to SPIRV builtin variables. /// e.g. /// SPV-IR: @_Z33__spirv_BuiltInGlobalInvocationIdi(i) /// is transformed as: /// x = load GlobalInvocationId; extract x, i /// e.g. /// SPV-IR: @_Z22__spirv_BuiltInWorkDim() /// is transformed as: /// load WorkDim bool lowerBuiltinCallsToVariables(Module *M) { LLVM_DEBUG(dbgs() << "Enter lowerBuiltinCallsToVariables\n"); // Store instructions and functions that need to be removed. SmallVector ToRemove; for (auto &F : *M) { // Builtins should be declaration only. if (!F.isDeclaration()) continue; StringRef DemangledName; if (!oclIsBuiltin(F.getName(), DemangledName)) continue; LLVM_DEBUG(dbgs() << "Function demangled name: " << DemangledName << '\n'); SmallVector Postfix; // Deprefix "__spirv_" StringRef Name = dePrefixSPIRVName(DemangledName, Postfix); // Lookup SPIRV Builtin map. if (!SPIRVBuiltInNameMap::rfind(Name.str(), nullptr)) continue; std::string BuiltinVarName = DemangledName.str(); LLVM_DEBUG(dbgs() << "builtin variable name: " << BuiltinVarName << '\n'); bool IsVec = F.getFunctionType()->getNumParams() > 0; Type *GVType = IsVec ? FixedVectorType::get(F.getReturnType(), 3) : F.getReturnType(); GlobalVariable *BV = nullptr; // Consider the following LLVM IR: // @__spirv_BuiltInLocalInvocationId = // ..... // define spir_kernel void @kernel1(....) { // %3 = tail call i64 @_Z12get_local_idj(i32 0) // ..... // return void // } // During the OCLToSPIRV pass, the opencl call will get lowered to // yet another global variable with the name // '@__spirv_BuiltInLocalInvocationId'. In such a case, we would want to // create only a single global variable with this name. if (GlobalVariable *GV = M->getGlobalVariable(BuiltinVarName)) BV = GV; else BV = new GlobalVariable(*M, GVType, /*isConstant=*/true, GlobalValue::ExternalLinkage, nullptr, BuiltinVarName, 0, GlobalVariable::NotThreadLocal, SPIRAS_Input); for (auto *U : F.users()) { auto *CI = dyn_cast(U); assert(CI && "invalid instruction"); IRBuilder<> Builder(CI); Value *NewValue = Builder.CreateLoad(GVType, BV); LLVM_DEBUG(dbgs() << "Transform: " << *CI << " => " << *NewValue << '\n'); if (IsVec) { auto *GVVecTy = cast(GVType); ConstantInt *Bound = Builder.getInt32(GVVecTy->getNumElements()); // Create a select on the index first, to avoid undefined behaviour // due to exceeding the vector size by the extractelement. Value *IndexCmp = Builder.CreateICmpULT(CI->getArgOperand(0), Bound); Constant *ZeroIndex = ConstantInt::get(CI->getArgOperand(0)->getType(), 0); Value *ExtractIndex = Builder.CreateSelect(IndexCmp, CI->getArgOperand(0), ZeroIndex); // Extract from builtin variable. NewValue = Builder.CreateExtractElement(NewValue, ExtractIndex); // Clamp to out-of-range value. Constant *OutOfRangeVal = ConstantInt::get( F.getReturnType(), getBuiltinOutOfRangeValue(BuiltinVarName)); NewValue = Builder.CreateSelect(IndexCmp, NewValue, OutOfRangeVal); LLVM_DEBUG(dbgs() << *NewValue << '\n'); } NewValue->takeName(CI); CI->replaceAllUsesWith(NewValue); ToRemove.push_back(CI); } ToRemove.push_back(&F); } for (auto *V : ToRemove) { if (auto *I = dyn_cast(V)) I->eraseFromParent(); else if (auto *F = dyn_cast(V)) F->eraseFromParent(); else llvm_unreachable("Unexpected value to remove!"); } return true; } bool lowerBuiltins(SPIRVModule *BM, Module *M) { auto Format = BM->getBuiltinFormat(); if (Format == BuiltinFormat::Function && !lowerBuiltinVariablesToCalls(M)) return false; if (Format == BuiltinFormat::Global && !lowerBuiltinCallsToVariables(M)) return false; return true; } bool postProcessBuiltinReturningStruct(Function *F) { Module *M = F->getParent(); LLVMContext *Context = &M->getContext(); std::string Name = F->getName().str(); F->setName(Name + ".old"); SmallVector InstToRemove; for (auto *U : F->users()) { if (auto *CI = dyn_cast(U)) { IRBuilder<> Builder(CI->getParent()); Builder.SetInsertPoint(CI); SmallVector Users(CI->users()); Value *A = nullptr; StoreInst *SI = nullptr; for (auto *U : Users) { if ((SI = dyn_cast(U)) != nullptr) { A = SI->getPointerOperand(); InstToRemove.push_back(SI); break; } } if (!A) { A = Builder.CreateAlloca(F->getReturnType()); } SmallVector ArgTys; getFunctionTypeParameterTypes(F->getFunctionType(), ArgTys); ArgTys.insert(ArgTys.begin(), A->getType()); auto *NewF = getOrCreateFunction(M, Type::getVoidTy(*Context), ArgTys, Name); auto SretAttr = Attribute::get(*Context, Attribute::AttrKind::StructRet, F->getReturnType()); NewF->addParamAttr(0, SretAttr); NewF->setCallingConv(F->getCallingConv()); auto Args = getArguments(CI); Args.insert(Args.begin(), A); CallInst *NewCI = Builder.CreateCall(NewF, Args, CI->getName()); NewCI->addParamAttr(0, SretAttr); NewCI->setCallingConv(CI->getCallingConv()); SmallVector UsersToReplace; for (auto *U : Users) if (U != SI) UsersToReplace.push_back(U); if (UsersToReplace.size() > 0) { auto *LI = Builder.CreateLoad(F->getReturnType(), A); for (auto *U : UsersToReplace) U->replaceUsesOfWith(CI, LI); } InstToRemove.push_back(CI); } } for (auto *Inst : InstToRemove) { Inst->dropAllReferences(); Inst->eraseFromParent(); } F->dropAllReferences(); F->eraseFromParent(); return true; } bool postProcessBuiltinWithArrayArguments(Function *F, StringRef DemangledName) { LLVM_DEBUG(dbgs() << "[postProcessOCLBuiltinWithArrayArguments] " << *F << '\n'); auto Attrs = F->getAttributes(); auto Name = F->getName(); mutateFunction( F, [=](CallInst *CI, std::vector &Args) { auto FBegin = CI->getFunction()->begin()->getFirstInsertionPt(); for (auto &I : Args) { auto *T = I->getType(); if (!T->isArrayTy()) continue; auto *Alloca = new AllocaInst( T, F->getParent()->getDataLayout().getAllocaAddrSpace(), "", FBegin); new StoreInst(I, Alloca, false, CI->getIterator()); auto *Zero = ConstantInt::getNullValue(Type::getInt32Ty(T->getContext())); Value *Index[] = {Zero, Zero}; I = GetElementPtrInst::CreateInBounds(T, Alloca, Index, "", CI->getIterator()); } return Name.str(); }, nullptr, &Attrs); return true; } bool postProcessBuiltinsReturningStruct(Module *M, bool IsCpp) { StringRef DemangledName; // postProcessBuiltinReturningStruct may remove some functions from the // module, so use make_early_inc_range for (auto &F : make_early_inc_range(M->functions())) { if (F.hasName() && F.isDeclaration()) { LLVM_DEBUG(dbgs() << "[postProcess sret] " << F << '\n'); if (F.getReturnType()->isStructTy() && oclIsBuiltin(F.getName(), DemangledName, IsCpp)) { if (!postProcessBuiltinReturningStruct(&F)) return false; } } } return true; } bool postProcessBuiltinsWithArrayArguments(Module *M, bool IsCpp) { StringRef DemangledName; // postProcessBuiltinWithArrayArguments may remove some functions from the // module, so use make_early_inc_range for (auto &F : make_early_inc_range(M->functions())) { if (F.hasName() && F.isDeclaration()) { LLVM_DEBUG(dbgs() << "[postProcess array arg] " << F << '\n'); if (hasArrayArg(&F) && oclIsBuiltin(F.getName(), DemangledName, IsCpp)) if (!postProcessBuiltinWithArrayArguments(&F, DemangledName)) return false; } } return true; } } // namespace SPIRV namespace { class SPIRVFriendlyIRMangleInfo : public BuiltinFuncMangleInfo { public: SPIRVFriendlyIRMangleInfo(spv::Op OC, ArrayRef ArgTys, ArrayRef Ops) : OC(OC), ArgTys(ArgTys), Ops(Ops) {} void init(StringRef UniqUnmangledName) override { UnmangledName = UniqUnmangledName.str(); switch (static_cast(OC)) { case OpConvertUToF: case OpUConvert: case OpSatConvertUToS: // Treat all arguments as unsigned addUnsignedArg(-1); break; case OpSubgroupShuffleINTEL: case OpSubgroupShuffleXorINTEL: addUnsignedArg(1); break; case OpSubgroupShuffleDownINTEL: case OpSubgroupShuffleUpINTEL: addUnsignedArg(2); break; case OpSubgroupBlockWriteINTEL: addUnsignedArg(0); addUnsignedArg(1); break; case OpSubgroupImageBlockWriteINTEL: addUnsignedArg(2); break; case OpSubgroupBlockReadINTEL: setArgAttr(0, SPIR::ATTR_CONST); addUnsignedArg(0); break; case OpSubgroupBlockPrefetchINTEL: setArgAttr(0, SPIR::ATTR_CONST); addUnsignedArg(0); addUnsignedArg(1); addUnsignedArg(2); // optional Memory Operands bitmask break; case OpAtomicUMax: case OpAtomicUMin: addUnsignedArg(0); addUnsignedArg(3); break; case OpGroupAsyncCopy: setArgAttr(2, SPIR::ATTR_CONST); addUnsignedArg(3); addUnsignedArg(4); break; case OpGroupUMax: case OpGroupUMin: case OpGroupBroadcast: case OpGroupNonUniformBroadcast: case OpGroupNonUniformBallotBitCount: case OpGroupNonUniformShuffle: case OpGroupNonUniformShuffleXor: case OpGroupNonUniformShuffleUp: case OpGroupNonUniformShuffleDown: addUnsignedArg(2); break; case OpGroupNonUniformRotateKHR: addUnsignedArg(2); if (ArgTys.size() == 4) addUnsignedArg(3); break; case OpGroupNonUniformInverseBallot: case OpGroupNonUniformBallotFindLSB: case OpGroupNonUniformBallotFindMSB: addUnsignedArg(1); break; case OpBitFieldSExtract: case OpGroupNonUniformBallotBitExtract: addUnsignedArg(1); addUnsignedArg(2); break; case OpGroupNonUniformIAdd: case OpGroupNonUniformFAdd: case OpGroupNonUniformIMul: case OpGroupNonUniformFMul: case OpGroupNonUniformSMin: case OpGroupNonUniformFMin: case OpGroupNonUniformSMax: case OpGroupNonUniformFMax: case OpGroupNonUniformBitwiseAnd: case OpGroupNonUniformBitwiseOr: case OpGroupNonUniformBitwiseXor: case OpGroupNonUniformLogicalAnd: case OpGroupNonUniformLogicalOr: case OpGroupNonUniformLogicalXor: addUnsignedArg(3); break; case OpBitFieldInsert: case OpGroupNonUniformUMax: case OpGroupNonUniformUMin: addUnsignedArg(2); addUnsignedArg(3); break; case OpEnqueueMarker: addUnsignedArg(1); break; case OpSubgroupAvcBmeInitializeINTEL: addUnsignedArgs(0, 7); break; case OpSubgroupAvcFmeInitializeINTEL: case OpSubgroupAvcSicConfigureIpeLumaINTEL: addUnsignedArgs(0, 6); break; case OpSubgroupAvcImeAdjustRefOffsetINTEL: addUnsignedArgs(1, 3); break; case OpSubgroupAvcImeGetBorderReachedINTEL: case OpSubgroupAvcImeRefWindowSizeINTEL: case OpSubgroupAvcImeSetEarlySearchTerminationThresholdINTEL: case OpSubgroupAvcImeSetMaxMotionVectorCountINTEL: case OpSubgroupAvcImeSetWeightedSadINTEL: case OpSubgroupAvcMceSetInterBaseMultiReferencePenaltyINTEL: case OpSubgroupAvcMceSetInterDirectionPenaltyINTEL: case OpSubgroupAvcMceSetInterShapePenaltyINTEL: case OpSubgroupAvcMceSetSingleReferenceInterlacedFieldPolarityINTEL: case OpSubgroupAvcMceSetSourceInterlacedFieldPolarityINTEL: case OpSubgroupAvcSicInitializeINTEL: case OpSubgroupAvcSicSetBlockBasedRawSkipSadINTEL: case OpSubgroupAvcSicSetIntraChromaModeCostFunctionINTEL: case OpSubgroupAvcSicSetIntraLumaShapePenaltyINTEL: case OpSubgroupAvcSicSetSkcForwardTransformEnableINTEL: addUnsignedArg(0); break; case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeDistortionsINTEL: case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeMotionVectorsINTEL: case OpSubgroupAvcImeGetStreamoutDualReferenceMajorShapeReferenceIdsINTEL: case OpSubgroupAvcRefEvaluateWithMultiReferenceInterlacedINTEL: case OpSubgroupAvcSicEvaluateWithMultiReferenceInterlacedINTEL: case OpSubgroupAvcRefEvaluateWithMultiReferenceINTEL: case OpSubgroupAvcSicEvaluateWithMultiReferenceINTEL: addUnsignedArgs(1, 2); break; case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeDistortionsINTEL: case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeMotionVectorsINTEL: case OpSubgroupAvcImeGetStreamoutSingleReferenceMajorShapeReferenceIdsINTEL: case OpSubgroupAvcImeSetSingleReferenceINTEL: addUnsignedArg(1); break; case OpBitFieldUExtract: case OpSubgroupAvcImeInitializeINTEL: case OpSubgroupAvcMceSetMotionVectorCostFunctionINTEL: case OpSubgroupAvcSicSetIntraLumaModeCostFunctionINTEL: addUnsignedArgs(0, 2); break; case OpSubgroupAvcImeSetDualReferenceINTEL: addUnsignedArg(2); break; case OpSubgroupAvcMceGetDefaultInterBaseMultiReferencePenaltyINTEL: case OpSubgroupAvcMceGetDefaultInterDirectionPenaltyINTEL: case OpSubgroupAvcMceGetDefaultInterMotionVectorCostTableINTEL: case OpSubgroupAvcMceGetDefaultInterShapePenaltyINTEL: case OpSubgroupAvcMceGetDefaultIntraLumaModePenaltyINTEL: case OpSubgroupAvcMceGetDefaultIntraLumaShapePenaltyINTEL: case OpSubgroupAvcMceGetInterReferenceInterlacedFieldPolaritiesINTEL: case OpSubgroupAvcMceSetDualReferenceInterlacedFieldPolaritiesINTEL: case OpSubgroupAvcSicGetMotionVectorMaskINTEL: addUnsignedArgs(0, 1); break; case OpSubgroupAvcSicConfigureIpeLumaChromaINTEL: addUnsignedArgs(0, 9); break; case OpSubgroupAvcSicConfigureSkcINTEL: addUnsignedArgs(0, 4); break; case OpUDotKHR: case OpUDotAccSatKHR: addUnsignedArg(-1); break; case OpSUDotKHR: case OpSUDotAccSatKHR: addUnsignedArg(1); break; case OpImageWrite: { size_t Idx = getImageOperandsIndex(OC); if (Ops.size() > Idx) { auto ImOp = static_cast(Ops[Idx])->getZExtIntValue(); if (ImOp & ImageOperandsMask::ImageOperandsZeroExtendMask) addUnsignedArg(2); } break; } case internal::OpConvertHandleToImageINTEL: case internal::OpConvertHandleToSamplerINTEL: case internal::OpConvertHandleToSampledImageINTEL: addUnsignedArg(0); break; case OpGenericPtrMemSemantics: setArgAttr(0, SPIR::ATTR_CONST); break; default:; // No special handling is needed } } private: spv::Op OC; ArrayRef ArgTys; ArrayRef Ops; }; class OpenCLStdToSPIRVFriendlyIRMangleInfo : public BuiltinFuncMangleInfo { public: OpenCLStdToSPIRVFriendlyIRMangleInfo(OCLExtOpKind ExtOpId, ArrayRef ArgTys, Type *RetTy) : ExtOpId(ExtOpId), ArgTys(ArgTys), RetTy(RetTy) { std::string Postfix = ""; if (needRetTypePostfix()) Postfix = kSPIRVPostfix::Divider + getPostfixForReturnType(RetTy, true); UnmangledName = getSPIRVExtFuncName(SPIRVEIS_OpenCL, ExtOpId, Postfix); } bool needRetTypePostfix() { switch (ExtOpId) { case OpenCLLIB::Vload_half: case OpenCLLIB::Vload_halfn: case OpenCLLIB::Vloada_halfn: case OpenCLLIB::Vloadn: return true; case OpenCLLIB::Nan: // Only add return type mangling for bfloat16 to disambiguate from half // (both are represented as i16 in LLVM). Float and half use traditional // naming for backward compatibility. return RetTy->getScalarType()->isBFloatTy(); default: return false; } } void init(StringRef) override { switch (ExtOpId) { case OpenCLLIB::UAbs: case OpenCLLIB::UAbs_diff: case OpenCLLIB::UAdd_sat: case OpenCLLIB::UHadd: case OpenCLLIB::URhadd: case OpenCLLIB::UClamp: case OpenCLLIB::UMad_hi: case OpenCLLIB::UMad_sat: case OpenCLLIB::UMax: case OpenCLLIB::UMin: case OpenCLLIB::UMul_hi: case OpenCLLIB::USub_sat: case OpenCLLIB::U_Upsample: case OpenCLLIB::UMad24: case OpenCLLIB::UMul24: // Treat all arguments as unsigned addUnsignedArg(-1); break; case OpenCLLIB::S_Upsample: addUnsignedArg(1); break; case OpenCLLIB::Nan: addUnsignedArg(0); break; case OpenCLLIB::Prefetch: setArgAttr(0, SPIR::ATTR_CONST); addUnsignedArg(1); break; case OpenCLLIB::Shuffle: addUnsignedArg(1); break; case OpenCLLIB::Shuffle2: addUnsignedArg(2); break; case OpenCLLIB::Vloadn: case OpenCLLIB::Vload_half: case OpenCLLIB::Vload_halfn: case OpenCLLIB::Vloada_halfn: addUnsignedArg(0); setArgAttr(1, SPIR::ATTR_CONST); break; case OpenCLLIB::Vstoren: case OpenCLLIB::Vstore_half: case OpenCLLIB::Vstore_half_r: case OpenCLLIB::Vstore_halfn: case OpenCLLIB::Vstore_halfn_r: case OpenCLLIB::Vstorea_halfn: case OpenCLLIB::Vstorea_halfn_r: addUnsignedArg(1); break; default:; // No special handling is needed } } private: OCLExtOpKind ExtOpId; ArrayRef ArgTys; Type *RetTy; }; } // namespace namespace SPIRV { std::string getSPIRVFriendlyIRFunctionName(OCLExtOpKind ExtOpId, ArrayRef ArgTys, Type *RetTy) { OpenCLStdToSPIRVFriendlyIRMangleInfo MangleInfo(ExtOpId, ArgTys, RetTy); return mangleBuiltin(MangleInfo.getUnmangledName(), ArgTys, &MangleInfo); } std::string getSPIRVFriendlyIRFunctionName(const std::string &UniqName, spv::Op OC, ArrayRef ArgTys, ArrayRef Ops) { SPIRVFriendlyIRMangleInfo MangleInfo(OC, ArgTys, Ops); return mangleBuiltin(UniqName, ArgTys, &MangleInfo); } } // namespace SPIRV