//===- SPIRVReader.cpp - Converts SPIR-V to LLVM ----------------*- C++ -*-===// // // The LLVM/SPIR-V 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 implements conversion of SPIR-V binary to LLVM IR. /// //===----------------------------------------------------------------------===// #include "SPIRVReader.h" #include "OCLUtil.h" #include "SPIRVAsm.h" #include "SPIRVBasicBlock.h" #include "SPIRVExtInst.h" #include "SPIRVFnVar.h" #include "SPIRVFunction.h" #include "SPIRVInstruction.h" #include "SPIRVInternal.h" #include "SPIRVMDBuilder.h" #include "SPIRVMemAliasingINTEL.h" #include "SPIRVModule.h" #include "SPIRVToLLVMDbgTran.h" #include "SPIRVToOCL.h" #include "SPIRVType.h" #include "SPIRVUtil.h" #include "SPIRVValue.h" #include "VectorComputeUtil.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/IR/AttributeMask.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DebugProgramInstruction.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/InlineAsm.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/MDBuilder.h" #include "llvm/IR/Metadata.h" #include "llvm/IR/Module.h" #include "llvm/IR/PassInstrumentation.h" #include "llvm/IR/Type.h" #include "llvm/IR/TypedPointerType.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/Support/FileSystem.h" #include #include #include #include #include #include #include #include #include #include #define DEBUG_TYPE "spirv" using namespace llvm; using namespace SPIRV; using namespace OCLUtil; namespace SPIRV { cl::opt SPIRVEnableStepExpansion( "spirv-expand-step", cl::init(true), cl::desc("Enable expansion of OpenCL step and smoothstep function")); // Prefix for placeholder global variable name. const char *KPlaceholderPrefix = "placeholder."; // Save the translated LLVM before validation for debugging purpose. static bool DbgSaveTmpLLVM = false; static const char *DbgTmpLLVMFileName = "_tmp_llvmbil.ll"; namespace kOCLTypeQualifierName { const static char *Volatile = "volatile"; const static char *Restrict = "restrict"; const static char *Pipe = "pipe"; } // namespace kOCLTypeQualifierName static bool isKernel(SPIRVFunction *BF) { return BF->getModule()->isEntryPoint(ExecutionModelKernel, BF->getId()); } static void dumpLLVM(Module *M, const std::string &FName) { std::error_code EC; raw_fd_ostream FS(FName, EC, sys::fs::OF_None); if (!EC) { FS << *M; FS.close(); } } static MDNode *getMDNodeStringIntVec(LLVMContext *Context, const std::vector &IntVals) { std::vector ValueVec; for (auto &I : IntVals) ValueVec.push_back(ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), I))); return MDNode::get(*Context, ValueVec); } static MDNode *getMDTwoInt(LLVMContext *Context, unsigned Int1, unsigned Int2) { std::vector ValueVec; ValueVec.push_back(ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Int1))); ValueVec.push_back(ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Int2))); return MDNode::get(*Context, ValueVec); } static void addOCLVersionMetadata(LLVMContext *Context, Module *M, const std::string &MDName, unsigned Major, unsigned Minor) { NamedMDNode *NamedMD = M->getOrInsertNamedMetadata(MDName); NamedMD->addOperand(getMDTwoInt(Context, Major, Minor)); } static void addNamedMetadataStringSet(LLVMContext *Context, Module *M, const std::string &MDName, const std::set &StrSet) { NamedMDNode *NamedMD = M->getOrInsertNamedMetadata(MDName); std::vector ValueVec; for (auto &&Str : StrSet) { ValueVec.push_back(MDString::get(*Context, Str)); } NamedMD->addOperand(MDNode::get(*Context, ValueVec)); } static void addKernelArgumentMetadata( LLVMContext *Context, const std::string &MDName, SPIRVFunction *BF, llvm::Function *Fn, std::function ForeachFnArg) { std::vector ValueVec; BF->foreachArgument([&](SPIRVFunctionParameter *Arg) { ValueVec.push_back(ForeachFnArg(Arg)); }); Fn->setMetadata(MDName, MDNode::get(*Context, ValueVec)); } static void addBufferLocationMetadata( LLVMContext *Context, SPIRVFunction *BF, llvm::Function *Fn, std::function ForeachFnArg) { std::vector ValueVec; bool DecorationFound = false; BF->foreachArgument([&](SPIRVFunctionParameter *Arg) { if (Arg->getType()->isTypePointer() && Arg->hasDecorate(DecorationBufferLocationINTEL)) { DecorationFound = true; ValueVec.push_back(ForeachFnArg(Arg)); } else { llvm::Metadata *DefaultNode = ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), -1)); ValueVec.push_back(DefaultNode); } }); if (DecorationFound) Fn->setMetadata("kernel_arg_buffer_location", MDNode::get(*Context, ValueVec)); } static void addRuntimeAlignedMetadata( LLVMContext *Context, SPIRVFunction *BF, llvm::Function *Fn, std::function ForeachFnArg) { std::vector ValueVec; bool RuntimeAlignedFound = false; [[maybe_unused]] llvm::Metadata *DefaultNode = ConstantAsMetadata::get(ConstantInt::get(Type::getInt1Ty(*Context), 0)); BF->foreachArgument([&](SPIRVFunctionParameter *Arg) { if (Arg->hasAttr(FunctionParameterAttributeRuntimeAlignedINTEL) || Arg->hasDecorate(internal::DecorationRuntimeAlignedINTEL)) { RuntimeAlignedFound = true; ValueVec.push_back(ForeachFnArg(Arg)); } else { ValueVec.push_back(DefaultNode); } }); if (RuntimeAlignedFound) Fn->setMetadata("kernel_arg_runtime_aligned", MDNode::get(*Context, ValueVec)); } Value *SPIRVToLLVM::getTranslatedValue(SPIRVValue *BV) { auto Loc = ValueMap.find(BV); if (Loc != ValueMap.end()) return Loc->second; return nullptr; } static std::optional translateSEVMetadata(SPIRVValue *BV, llvm::LLVMContext &Context) { std::optional RetAttr; if (!BV->hasDecorate(DecorationSingleElementVectorINTEL)) return RetAttr; auto VecDecorateSEV = BV->getDecorations(DecorationSingleElementVectorINTEL); assert(VecDecorateSEV.size() == 1 && "Entry must have no more than one SingleElementVectorINTEL " "decoration"); auto *DecorateSEV = VecDecorateSEV.back(); auto LiteralCount = DecorateSEV->getLiteralCount(); assert(LiteralCount <= 1 && "SingleElementVectorINTEL decoration must " "have no more than one literal"); SPIRVWord IndirectLevelsOnElement = (LiteralCount == 1) ? DecorateSEV->getLiteral(0) : 0; RetAttr = Attribute::get(Context, kVCMetadata::VCSingleElementVector, std::to_string(IndirectLevelsOnElement)); return RetAttr; } IntrinsicInst *SPIRVToLLVM::getLifetimeStartIntrinsic(Instruction *I) { auto *II = dyn_cast(I); if (II && II->getIntrinsicID() == Intrinsic::lifetime_start) return II; // Bitcast might be inserted during translation of OpLifetimeStart auto *BC = dyn_cast(I); if (BC) { for (const auto &U : BC->users()) { II = dyn_cast(U); if (II && II->getIntrinsicID() == Intrinsic::lifetime_start) return II; ; } } return nullptr; } SPIRVErrorLog &SPIRVToLLVM::getErrorLog() { return BM->getErrorLog(); } void SPIRVToLLVM::setCallingConv(CallInst *Call) { Function *F = Call->getCalledFunction(); assert(F && "Function pointers are not allowed in SPIRV"); Call->setCallingConv(F->getCallingConv()); } // For integer types shorter than 32 bit, unsigned/signedness can be inferred // from zext/sext attribute. MDString *SPIRVToLLVM::transOCLKernelArgTypeName(SPIRVFunctionParameter *Arg) { auto *Ty = Arg->isByVal() ? Arg->getType()->getPointerElementType() : Arg->getType(); return MDString::get(*Context, transTypeToOCLTypeName(Ty, !Arg->isZext())); } Value *SPIRVToLLVM::mapFunction(SPIRVFunction *BF, Function *F) { SPIRVDBG(spvdbgs() << "[mapFunction] " << *BF << " -> "; dbgs() << *F << '\n';) FuncMap[BF] = F; return F; } std::optional SPIRVToLLVM::transIdAsConstant(SPIRVId Id) { auto *V = BM->get(Id); const auto *ConstValue = dyn_cast(transValue(V, nullptr, nullptr)); if (!ConstValue) return {}; return ConstValue->getZExtValue(); } std::optional SPIRVToLLVM::getAlignment(SPIRVValue *V) { SPIRVWord AlignmentBytes = 0; if (V->hasAlignment(&AlignmentBytes)) { return AlignmentBytes; } // If there was no Alignment decoration, look for AlignmentId instead. SPIRVId AlignId; if (V->hasDecorateId(DecorationAlignmentId, 0, &AlignId)) { return transIdAsConstant(AlignId); } return {}; } Type *SPIRVToLLVM::transFPType(SPIRVType *T) { switch (T->getFloatBitWidth()) { case 4: // No LLVM IR counter part for FP4 - map it on i4. return Type::getIntNTy(*Context, 4); case 8: // No LLVM IR counter part for FP8 - map it on i8. return Type::getIntNTy(*Context, 8); case 16: if (T->isTypeFloat(16, FPEncodingBFloat16KHR)) return Type::getBFloatTy(*Context); return Type::getHalfTy(*Context); case 32: return Type::getFloatTy(*Context); case 64: return Type::getDoubleTy(*Context); default: llvm_unreachable("Invalid type"); return nullptr; } } std::string SPIRVToLLVM::transVCTypeName(SPIRVTypeBufferSurfaceINTEL *PST) { if (PST->hasAccessQualifier()) return VectorComputeUtil::getVCBufferSurfaceName(PST->getAccessQualifier()); return VectorComputeUtil::getVCBufferSurfaceName(); } template std::optional getAccessQualifier(ImageType *T) { if (!T->hasAccessQualifier()) return {}; return T->getAccessQualifier(); } Type *SPIRVToLLVM::transType(SPIRVType *T, bool UseTPT) { // Try to reuse a known type if it's already matched. However, if we want to // produce a TypedPointerType in lieu of a PointerType, we *do not* want to // pull a PointerType out of the type map, nor do we want to store a // TypedPointerType in there. This is generally safe to do, as types are // usually uniqued by LLVM, but we need to be cautious around struct types. auto Loc = TypeMap.find(T); if (Loc != TypeMap.end() && !UseTPT) return Loc->second; SPIRVDBG(spvdbgs() << "[transType] " << *T << " -> ";) T->validate(); switch (static_cast(T->getOpCode())) { case OpTypeVoid: return mapType(T, Type::getVoidTy(*Context)); case OpTypeBool: return mapType(T, Type::getInt1Ty(*Context)); case OpTypeInt: return mapType(T, Type::getIntNTy(*Context, T->getIntegerBitWidth())); case OpTypeFloat: return mapType(T, transFPType(T)); case OpTypeArray: { // The length might be an OpSpecConstantOp, that needs to be specialized // and evaluated before the LLVM ArrayType can be constructed. auto *LenExpr = static_cast(T)->getLength(); auto *LenValue = cast(transValue(LenExpr, nullptr, nullptr)); return mapType(T, ArrayType::get(transType(T->getArrayElementType()), LenValue->getZExtValue())); } case internal::OpTypeTokenINTEL: return mapType(T, Type::getTokenTy(*Context)); case OpTypePointer: { unsigned AS = SPIRSPIRVAddrSpaceMap::rmap(T->getPointerStorageClass()); if (AS == SPIRAS_CodeSectionINTEL && !BM->shouldEmitFunctionPtrAddrSpace()) AS = SPIRAS_Private; if (BM->shouldEmitFunctionPtrAddrSpace() && T->getPointerElementType()->getOpCode() == OpTypeFunction) AS = SPIRAS_CodeSectionINTEL; Type *ElementTy = transType(T->getPointerElementType(), UseTPT); if (UseTPT) return TypedPointerType::get(ElementTy, AS); return mapType(T, PointerType::get(*Context, AS)); } case OpTypeUntypedPointerKHR: { unsigned AS = SPIRSPIRVAddrSpaceMap::rmap(T->getPointerStorageClass()); if (AS == SPIRAS_CodeSectionINTEL && !BM->shouldEmitFunctionPtrAddrSpace()) AS = SPIRAS_Private; return mapType(T, PointerType::get(*Context, AS)); } case OpTypeVector: return mapType(T, FixedVectorType::get(transType(T->getVectorComponentType()), T->getVectorComponentCount())); case OpTypeMatrix: return mapType(T, ArrayType::get(transType(T->getMatrixColumnType()), T->getMatrixColumnCount())); case OpTypeOpaque: return mapType(T, StructType::create(*Context, T->getName())); case OpTypeFunction: { auto *FT = static_cast(T); auto *RT = transType(FT->getReturnType()); std::vector PT; for (size_t I = 0, E = FT->getNumParameters(); I != E; ++I) PT.push_back(transType(FT->getParameterType(I))); return mapType(T, FunctionType::get(RT, PT, false)); } case OpTypeImage: { auto *ST = static_cast(T); if (ST->isOCLImage()) return mapType(T, getSPIRVType(OpTypeImage, transType(ST->getSampledType()), ST->getDescriptor(), getAccessQualifier(ST), !UseTPT)); else llvm_unreachable("Unsupported image type"); return nullptr; } case OpTypeSampledImage: { const auto *ST = static_cast(T)->getImageType(); return mapType( T, getSPIRVType(OpTypeSampledImage, transType(ST->getSampledType()), ST->getDescriptor(), getAccessQualifier(ST), !UseTPT)); } case OpTypeStruct: { // We do not generate structs with any TypedPointerType members. To ensure // that uniqueness of struct types is maintained, reuse an existing struct // type in the type map, even if UseTPT is true. if (Loc != TypeMap.end()) return Loc->second; auto *ST = static_cast(T); auto Name = ST->getName(); if (!Name.empty()) { if (auto *OldST = StructType::getTypeByName(*Context, Name)) OldST->setName(""); } else { Name = "structtype"; } auto *StructTy = StructType::create(*Context, Name); mapType(ST, StructTy); SmallVector MT; for (size_t I = 0, E = ST->getMemberCount(); I != E; ++I) MT.push_back(transType(ST->getMemberType(I))); for (auto &CI : ST->getContinuedInstructions()) for (size_t I = 0, E = CI->getNumElements(); I != E; ++I) MT.push_back(transType(CI->getMemberType(I))); StructTy->setBody(MT, ST->isPacked()); return StructTy; } case OpTypePipe: { auto *PT = static_cast(T); return mapType(T, getSPIRVType(OpTypePipe, PT->getAccessQualifier(), !UseTPT)); } case OpTypePipeStorage: { StringRef FullName = "spirv.PipeStorage"; auto *STy = StructType::getTypeByName(*Context, FullName); if (!STy) STy = StructType::create(*Context, FullName); if (UseTPT) { return mapType(T, TypedPointerType::get(STy, 1)); } return mapType(T, PointerType::get(*Context, 1)); } case OpTypeVmeImageINTEL: { auto *VT = static_cast(T)->getImageType(); return mapType( T, getSPIRVType(OpTypeVmeImageINTEL, transType(VT->getSampledType()), VT->getDescriptor(), getAccessQualifier(VT), !UseTPT)); } case OpTypeBufferSurfaceINTEL: { auto *PST = static_cast(T); Type *Ty = nullptr; if (UseTPT) { Type *StructTy = getOrCreateOpaqueStructType(M, transVCTypeName(PST)); Ty = TypedPointerType::get(StructTy, SPIRAS_Global); } else { std::vector Params; if (PST->hasAccessQualifier()) { unsigned Access = static_cast(PST->getAccessQualifier()); Params.push_back(Access); } Ty = TargetExtType::get(*Context, "spirv.BufferSurfaceINTEL", {}, Params); } return mapType(T, Ty); } case internal::OpTypeJointMatrixINTEL: { auto *MT = static_cast(T); auto R = static_cast(MT->getRows())->getZExtIntValue(); auto C = static_cast(MT->getColumns())->getZExtIntValue(); std::vector Params = {(unsigned)R, (unsigned)C}; if (auto *Layout = MT->getLayout()) Params.push_back(static_cast(Layout)->getZExtIntValue()); Params.push_back( static_cast(MT->getScope())->getZExtIntValue()); if (auto *Use = MT->getUse()) Params.push_back(static_cast(Use)->getZExtIntValue()); auto *CTI = MT->getComponentTypeInterpretation(); if (!CTI) return mapType( T, llvm::TargetExtType::get(*Context, "spirv.JointMatrixINTEL", transType(MT->getCompType()), Params)); const unsigned CTIValue = static_cast(CTI)->getZExtIntValue(); assert(CTIValue <= internal::InternalJointMatrixCTI::PackedInt4 && "Unknown matrix component type interpretation"); Params.push_back(CTIValue); return mapType( T, llvm::TargetExtType::get(*Context, "spirv.JointMatrixINTEL", transType(MT->getCompType()), Params)); } case OpTypeCooperativeMatrixKHR: { auto *MT = static_cast(T); unsigned Scope = static_cast(MT->getScope())->getZExtIntValue(); unsigned Rows = static_cast(MT->getRows())->getZExtIntValue(); unsigned Cols = static_cast(MT->getColumns())->getZExtIntValue(); unsigned Use = static_cast(MT->getUse())->getZExtIntValue(); std::vector Params = {Scope, Rows, Cols, Use}; return mapType( T, llvm::TargetExtType::get(*Context, "spirv.CooperativeMatrixKHR", transType(MT->getCompType()), Params)); } case OpTypeForwardPointer: { SPIRVTypeForwardPointer *FP = static_cast(static_cast(T)); return mapType(T, transType(static_cast( BM->getEntry(FP->getPointerId())))); } case internal::OpTypeTaskSequenceINTEL: return mapType( T, llvm::TargetExtType::get(*Context, "spirv.TaskSequenceINTEL")); default: { auto OC = T->getOpCode(); if (isOpaqueGenericTypeOpCode(OC) || isSubgroupAvcINTELTypeOpCode(OC)) { return mapType(T, getSPIRVType(OC, !UseTPT)); } llvm_unreachable("Not implemented!"); } } return 0; } std::string SPIRVToLLVM::transTypeToOCLTypeName(SPIRVType *T, bool IsSigned) { switch (T->getOpCode()) { case OpTypeVoid: return "void"; case OpTypeBool: return "bool"; case OpTypeInt: { std::string Prefix = IsSigned ? "" : "u"; switch (T->getIntegerBitWidth()) { case 8: return Prefix + "char"; case 16: return Prefix + "short"; case 32: return Prefix + "int"; case 64: return Prefix + "long"; default: // Arbitrary precision integer return Prefix + std::string("int") + T->getIntegerBitWidth() + "_t"; } } break; case OpTypeFloat: switch (T->getFloatBitWidth()) { case 16: return "half"; case 32: return "float"; case 64: return "double"; default: llvm_unreachable("invalid floating pointer bitwidth"); return std::string("float") + T->getFloatBitWidth() + "_t"; } break; case OpTypeArray: return "array"; case OpTypePointer: { SPIRVType *ET = T->getPointerElementType(); if (isa(ET)) { SPIRVTypeFunction *TF = static_cast(ET); std::string name = transTypeToOCLTypeName(TF->getReturnType()); name += " (*)("; for (unsigned I = 0, E = TF->getNumParameters(); I < E; ++I) name += transTypeToOCLTypeName(TF->getParameterType(I)) + ','; name.back() = ')'; // replace the last comma with a closing brace. return name; } return transTypeToOCLTypeName(ET) + "*"; } case OpTypeUntypedPointerKHR: return "int*"; case OpTypeVector: return transTypeToOCLTypeName(T->getVectorComponentType()) + T->getVectorComponentCount(); case OpTypeMatrix: return transTypeToOCLTypeName(T->getMatrixColumnType()) + T->getMatrixColumnCount(); case OpTypeOpaque: return T->getName(); case OpTypeFunction: llvm_unreachable("Unsupported"); return "function"; case OpTypeStruct: { auto Name = T->getName(); if (Name.find("struct.") == 0) Name[6] = ' '; else if (Name.find("union.") == 0) Name[5] = ' '; return Name; } case OpTypePipe: return "pipe"; case OpTypeSampler: return "sampler_t"; case OpTypeImage: { std::string Name; Name = rmap(static_cast(T)->getDescriptor()); return Name; } default: if (isOpaqueGenericTypeOpCode(T->getOpCode())) { return OCLOpaqueTypeOpCodeMap::rmap(T->getOpCode()); } llvm_unreachable("Not implemented"); return "unknown"; } } std::vector SPIRVToLLVM::transTypeVector(const std::vector &BT, bool UseTPT) { std::vector T; for (auto *I : BT) T.push_back(transType(I, UseTPT)); return T; } static Type *opaquifyType(Type *Ty) { if (auto *TPT = dyn_cast(Ty)) { Ty = PointerType::get(Ty->getContext(), TPT->getAddressSpace()); } return Ty; } static void opaquifyTypedPointers(MutableArrayRef Types) { for (Type *&Ty : Types) { Ty = opaquifyType(Ty); } } std::vector SPIRVToLLVM::transValue(const std::vector &BV, Function *F, BasicBlock *BB) { std::vector V; for (auto *I : BV) V.push_back(transValue(I, F, BB)); return V; } void SPIRVToLLVM::setName(llvm::Value *V, SPIRVValue *BV) { auto Name = BV->getName(); if (!Name.empty() && (!V->hasName() || Name != V->getName())) V->setName(Name); } inline llvm::Metadata *SPIRVToLLVM::getMetadataFromName(std::string Name) { return llvm::MDNode::get(*Context, llvm::MDString::get(*Context, Name)); } inline std::vector SPIRVToLLVM::getMetadataFromNameAndParameter(std::string Name, SPIRVWord Parameter) { return {MDString::get(*Context, Name), ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Parameter))}; } inline llvm::MDNode * SPIRVToLLVM::getMetadataFromNameAndParameter(std::string Name, int64_t Parameter) { std::vector Metadata = { MDString::get(*Context, Name), ConstantAsMetadata::get( ConstantInt::get(Type::getInt64Ty(*Context), Parameter))}; return llvm::MDNode::get(*Context, Metadata); } template void SPIRVToLLVM::setLLVMLoopMetadata(const LoopInstType *LM, const Loop *LoopObj) { if (!LM) return; auto Temp = MDNode::getTemporary(*Context, {}); auto *Self = MDNode::get(*Context, Temp.get()); Self->replaceOperandWith(0, Self); SPIRVWord LC = LM->getLoopControl(); if (LC == LoopControlMaskNone) { LoopObj->setLoopID(Self); return; } unsigned NumParam = 0; std::vector Metadata; std::vector LoopControlParameters = LM->getLoopControlParameters(); Metadata.push_back(llvm::MDNode::get(*Context, Self)); // To correctly decode loop control parameters, order of checks for loop // control masks must match with the order given in the spec (see 3.23), // i.e. check smaller-numbered bits first. // Unroll and UnrollCount loop controls can't be applied simultaneously with // DontUnroll loop control. if (LC & LoopControlUnrollMask && !(LC & LoopControlPartialCountMask)) Metadata.push_back(getMetadataFromName("llvm.loop.unroll.enable")); else if (LC & LoopControlDontUnrollMask) Metadata.push_back(getMetadataFromName("llvm.loop.unroll.disable")); if (LC & LoopControlDependencyInfiniteMask) Metadata.push_back(getMetadataFromName("llvm.loop.ivdep.enable")); if (LC & LoopControlDependencyLengthMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.ivdep.safelen", LoopControlParameters[NumParam]))); ++NumParam; // TODO: Fix the increment/assertion logic in all of the conditions assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } // Placeholder for LoopControls added in SPIR-V 1.4 spec (see 3.23) if (LC & LoopControlMinIterationsMask) { ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlMaxIterationsMask) { ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlIterationMultipleMask) { ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlPeelCountMask) { ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlPartialCountMask && !(LC & LoopControlDontUnrollMask)) { // If unroll factor is set as '1' and Unroll mask is applied attempt to do // full unrolling and disable it if the trip count is not known at compile // time. if (1 == LoopControlParameters[NumParam] && (LC & LoopControlUnrollMask)) Metadata.push_back(getMetadataFromName("llvm.loop.unroll.full")); else Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.unroll.count", LoopControlParameters[NumParam]))); ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlInitiationIntervalINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter( "llvm.loop.ii.count", LoopControlParameters[NumParam]))); ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlMaxConcurrencyINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.max_concurrency.count", LoopControlParameters[NumParam]))); ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlDependencyArrayINTELMask) { // Collect pointer variable <-> safelen information std::unordered_map PointerSflnMap; unsigned NumOperandPairs = LoopControlParameters[NumParam]; unsigned OperandsEndIndex = NumParam + NumOperandPairs * 2; assert(OperandsEndIndex <= LoopControlParameters.size() && "Missing loop control parameter!"); SPIRVModule *M = LM->getModule(); while (NumParam < OperandsEndIndex) { SPIRVId ArraySPIRVId = LoopControlParameters[++NumParam]; Value *PointerVar = ValueMap[M->getValue(ArraySPIRVId)]; unsigned Safelen = LoopControlParameters[++NumParam]; PointerSflnMap.emplace(PointerVar, Safelen); } // A single run over the loop to retrieve all GetElementPtr instructions // that access relevant array variables std::unordered_map> ArrayGEPMap; for (const auto &BB : LoopObj->blocks()) { for (Instruction &I : *BB) { auto *GEP = dyn_cast(&I); if (!GEP) continue; Value *AccessedPointer = GEP->getPointerOperand(); if (auto *BC = dyn_cast(AccessedPointer)) if (BC->getSrcTy() == BC->getDestTy()) AccessedPointer = BC->getOperand(0); if (auto *LI = dyn_cast(AccessedPointer)) AccessedPointer = LI->getPointerOperand(); auto PointerSflnIt = PointerSflnMap.find(AccessedPointer); if (PointerSflnIt != PointerSflnMap.end()) { ArrayGEPMap[AccessedPointer].push_back(GEP); } } } // Create index group metadata nodes - one per each of the array // variables. Mark each GEP accessing a particular array variable // into a corresponding index group std::map> SafelenIdxGroupMap; // Whenever a kernel closure field access is pointed to instead of // an array/pointer variable, ensure that all GEPs to that memory // share the same index group by hashing the newly added index groups. // "Memory offset info" represents a handle to the whole closure block // + an integer offset to a particular captured parameter. using MemoryOffsetInfo = std::pair; std::map OffsetIdxGroupMap; for (auto &ArrayGEPIt : ArrayGEPMap) { MDNode *CurrentDepthIdxGroup = nullptr; if (auto *PrecedingGEP = dyn_cast(ArrayGEPIt.first)) { Value *ClosureFieldPointer = PrecedingGEP->getPointerOperand(); unsigned Offset = cast(PrecedingGEP->getOperand(2))->getZExtValue(); MemoryOffsetInfo Info{ClosureFieldPointer, Offset}; auto OffsetIdxGroupIt = OffsetIdxGroupMap.find(Info); if (OffsetIdxGroupIt == OffsetIdxGroupMap.end()) { // This is the first GEP encountered for this closure field. // Emit a distinct index group that will be referenced from // llvm.loop.parallel_access_indices metadata; hash the new // MDNode for future accesses to the same memory. CurrentDepthIdxGroup = llvm::MDNode::getDistinct(*Context, {}); OffsetIdxGroupMap.emplace(Info, CurrentDepthIdxGroup); } else { // Previous accesses to that field have already been indexed, // just use the already-existing metadata. CurrentDepthIdxGroup = OffsetIdxGroupIt->second; } } else /* Regular kernel-scope array/pointer variable */ { // Emit a distinct index group that will be referenced from // llvm.loop.parallel_access_indices metadata CurrentDepthIdxGroup = llvm::MDNode::getDistinct(*Context, {}); } unsigned Safelen = PointerSflnMap.find(ArrayGEPIt.first)->second; SafelenIdxGroupMap[Safelen].insert(CurrentDepthIdxGroup); for (auto *GEP : ArrayGEPIt.second) { StringRef IdxGroupMDName("llvm.index.group"); llvm::MDNode *PreviousIdxGroup = GEP->getMetadata(IdxGroupMDName); if (!PreviousIdxGroup) { GEP->setMetadata(IdxGroupMDName, CurrentDepthIdxGroup); continue; } // If we're dealing with an embedded loop, it may be the case // that GEP instructions for some of the arrays were already // marked by the algorithm when it went over the outer level loops. // In order to retain the IVDep information for each "loop // dimension", we will mark such GEP's into a separate joined node // that will refer to the previous levels' index groups AND to the // index group specific to the current loop. std::vector CurrentDepthOperands( PreviousIdxGroup->op_begin(), PreviousIdxGroup->op_end()); if (CurrentDepthOperands.empty()) CurrentDepthOperands.push_back(PreviousIdxGroup); CurrentDepthOperands.push_back(CurrentDepthIdxGroup); auto *JointIdxGroup = llvm::MDNode::get(*Context, CurrentDepthOperands); GEP->setMetadata(IdxGroupMDName, JointIdxGroup); } } for (auto &SflnIdxGroupIt : SafelenIdxGroupMap) { auto *Name = MDString::get(*Context, "llvm.loop.parallel_access_indices"); unsigned SflnValue = SflnIdxGroupIt.first; llvm::Metadata *SafelenMDOp = SflnValue ? ConstantAsMetadata::get(ConstantInt::get( Type::getInt32Ty(*Context), SflnValue)) : nullptr; std::vector Parameters{Name}; for (auto *Node : SflnIdxGroupIt.second) Parameters.push_back(Node); if (SafelenMDOp) Parameters.push_back(SafelenMDOp); Metadata.push_back(llvm::MDNode::get(*Context, Parameters)); } ++NumParam; } if (LC & LoopControlPipelineEnableINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.intel.pipelining.enable", LoopControlParameters[NumParam++]))); assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlLoopCoalesceINTELMask) { // If LoopCoalesce has a parameter of '0' if (!LoopControlParameters[NumParam]) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromName("llvm.loop.coalesce.enable"))); } else { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.coalesce.count", LoopControlParameters[NumParam]))); } ++NumParam; assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlMaxInterleavingINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter("llvm.loop.max_interleaving.count", LoopControlParameters[NumParam++]))); assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlSpeculatedIterationsINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter( "llvm.loop.intel.speculated.iterations.count", LoopControlParameters[NumParam++]))); assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } if (LC & LoopControlNoFusionINTELMask) Metadata.push_back(getMetadataFromName("llvm.loop.fusion.disable")); if (LC & spv::LoopControlLoopCountINTELMask) { // LoopCountINTELMask parameters are int64 and each parameter is stored // as 2 SPIRVWords (int32) assert(NumParam + 6 <= LoopControlParameters.size() && "Missing loop control parameter!"); uint64_t LoopCountMin = static_cast(LoopControlParameters[NumParam++]); LoopCountMin |= static_cast(LoopControlParameters[NumParam++]) << 32; if (static_cast(LoopCountMin) >= 0) { Metadata.push_back(getMetadataFromNameAndParameter( "llvm.loop.intel.loopcount_min", static_cast(LoopCountMin))); } uint64_t LoopCountMax = static_cast(LoopControlParameters[NumParam++]); LoopCountMax |= static_cast(LoopControlParameters[NumParam++]) << 32; if (static_cast(LoopCountMax) >= 0) { Metadata.push_back(getMetadataFromNameAndParameter( "llvm.loop.intel.loopcount_max", static_cast(LoopCountMax))); } uint64_t LoopCountAvg = static_cast(LoopControlParameters[NumParam++]); LoopCountAvg |= static_cast(LoopControlParameters[NumParam++]) << 32; if (static_cast(LoopCountAvg) >= 0) { Metadata.push_back(getMetadataFromNameAndParameter( "llvm.loop.intel.loopcount_avg", static_cast(LoopCountAvg))); } } if (LC & spv::LoopControlMaxReinvocationDelayINTELMask) { Metadata.push_back(llvm::MDNode::get( *Context, getMetadataFromNameAndParameter( "llvm.loop.intel.max_reinvocation_delay.count", LoopControlParameters[NumParam++]))); assert(NumParam <= LoopControlParameters.size() && "Missing loop control parameter!"); } llvm::MDNode *Node = llvm::MDNode::get(*Context, Metadata); // Set the first operand to refer itself Node->replaceOperandWith(0, Node); LoopObj->setLoopID(Node); } void SPIRVToLLVM::transLLVMLoopMetadata(const Function *F) { assert(F); if (FuncLoopMetadataMap.empty()) return; // Function declaration doesn't contain loop metadata. if (F->isDeclaration()) return; DominatorTree DomTree(*(const_cast(F))); LoopInfo LI(DomTree); // In SPIRV loop metadata is linked to a header basic block of a loop // whilst in LLVM IR it is linked to a latch basic block (the one // whose back edge goes to a header basic block) of the loop. // To ensure consistent behaviour, we can rely on the `llvm::Loop` // class to handle the metadata placement for (const auto *LoopObj : LI.getLoopsInPreorder()) { // Check that loop header BB contains loop metadata. const auto LMDItr = FuncLoopMetadataMap.find(LoopObj->getHeader()); if (LMDItr == FuncLoopMetadataMap.end()) continue; const auto *LMD = LMDItr->second; if (LMD->getOpCode() == OpLoopMerge) { const auto *LM = static_cast(LMD); setLLVMLoopMetadata(LM, LoopObj); } else if (LMD->getOpCode() == OpLoopControlINTEL) { const auto *LCI = static_cast(LMD); setLLVMLoopMetadata(LCI, LoopObj); } FuncLoopMetadataMap.erase(LMDItr); } } Value *SPIRVToLLVM::transValue(SPIRVValue *BV, Function *F, BasicBlock *BB, bool CreatePlaceHolder) { SPIRVToLLVMValueMap::iterator Loc = ValueMap.find(BV); if (Loc != ValueMap.end() && (!PlaceholderMap.count(BV) || CreatePlaceHolder)) return Loc->second; SPIRVDBG(spvdbgs() << "[transValue] " << *BV << " -> ";) BV->validate(); auto *V = transValueWithoutDecoration(BV, F, BB, CreatePlaceHolder); if (!V) { SPIRVDBG(dbgs() << " Warning ! nullptr\n";) return nullptr; } setName(V, BV); if (!transDecoration(BV, V)) { assert(0 && "trans decoration fail"); return nullptr; } SPIRVDBG(dbgs() << *V << '\n';) return V; } Value *SPIRVToLLVM::transConvertInst(SPIRVValue *BV, Function *F, BasicBlock *BB) { SPIRVUnary *BC = static_cast(BV); auto *Src = transValue(BC->getOperand(0), F, BB, BB ? true : false); auto *Dst = transType(BC->getType()); CastInst::CastOps CO = Instruction::BitCast; bool IsExt = Dst->getScalarSizeInBits() > Src->getType()->getScalarSizeInBits(); auto GetFPEncoding = [](SPIRVType *Ty) -> FPEncodingWrap { if (Ty->isTypeFloat()) { unsigned Enc = static_cast(Ty)->getFloatingPointEncoding(); return static_cast(Enc); } if (Ty->isTypeInt()) return FPEncodingWrap::Integer; return FPEncodingWrap::IEEE754; }; auto IsFP4OrFP8Encoding = [](FPEncodingWrap Encoding) -> bool { return Encoding == FPEncodingWrap::E4M3 || Encoding == FPEncodingWrap::E5M2 || Encoding == FPEncodingWrap::E2M1 || Encoding == FPEncodingWrap::E2M1INTEL; }; switch (static_cast(BC->getOpCode())) { case OpPtrCastToGeneric: case OpGenericCastToPtr: case OpPtrCastToCrossWorkgroupINTEL: case OpCrossWorkgroupCastToPtrINTEL: { // If module has pointers with DeviceOnlyINTEL and HostOnlyINTEL storage // classes there will be a situation, when global_device/global_host // address space will be lowered to just global address space. If there also // is an addrspacecast - we need to replace it with source pointer. if (Src->getType()->getPointerAddressSpace() == Dst->getPointerAddressSpace()) return Src; CO = Instruction::AddrSpaceCast; break; } case OpSConvert: CO = IsExt ? Instruction::SExt : Instruction::Trunc; break; case OpUConvert: CO = IsExt ? Instruction::ZExt : Instruction::Trunc; break; case internal::OpClampConvertFToFINTEL: case internal::OpClampConvertFToSINTEL: case internal::OpStochasticRoundFToFINTEL: case internal::OpClampStochasticRoundFToFINTEL: case internal::OpClampStochasticRoundFToSINTEL: case OpConvertSToF: case OpConvertFToS: case OpConvertUToF: case OpConvertFToU: case OpFConvert: { const auto OC = BC->getOpCode(); // These 2 old opcodes should follow exactly the same translation // path as OpFConvert/OpStochasticRoundFToFINTEL with // SaturatedToLargestFloat8NormalConversionEXT const bool IsOldConvertFToFOp = OC == internal::OpClampConvertFToFINTEL || OC == internal::OpClampStochasticRoundFToFINTEL; { auto SPVOps = BC->getOperands(); auto *SPVSrcTy = SPVOps[0]->getType(); auto *SPVDstTy = BC->getType(); auto GetEncodingAndUpdateType = [GetFPEncoding](SPIRVType *&SPVTy) -> FPEncodingWrap { if (SPVTy->isTypeVector()) { SPVTy = SPVTy->getVectorComponentType(); } else if (SPVTy->isTypeCooperativeMatrixKHR()) { auto *MT = static_cast(SPVTy); SPVTy = MT->getCompType(); } return GetFPEncoding(SPVTy); }; FPEncodingWrap SrcEnc = GetEncodingAndUpdateType(SPVSrcTy); FPEncodingWrap DstEnc = GetEncodingAndUpdateType(SPVDstTy); const bool HasSaturatedFP8Decor = BC->hasDecorate( DecorationSaturatedToLargestFloat8NormalConversionEXT); bool IsSaturatedFP8 = false; if (IsOldConvertFToFOp) { BM->getErrorLog().checkError( !HasSaturatedFP8Decor, SPIRVEC_InvalidInstruction, "SaturatedToLargestFloat8NormalConversionEXT is not valid on " "OpClampConvertFToFINTEL or OpClampStochasticRoundFToFINTEL.\n"); IsSaturatedFP8 = DstEnc == FPEncodingWrap::E4M3 || DstEnc == FPEncodingWrap::E5M2; } else if (HasSaturatedFP8Decor) { BM->getErrorLog().checkError( (OC == OpFConvert || OC == OpConvertSToF || OC == OpConvertUToF || OC == internal::OpStochasticRoundFToFINTEL) && (DstEnc == FPEncodingWrap::E4M3 || DstEnc == FPEncodingWrap::E5M2), SPIRVEC_InvalidInstruction, "SaturatedToLargestFloat8NormalConversionEXT decoration is only " "valid on OpFConvert/OpConvertSToF/OpConvertUToF/" "OpStochasticRoundFToFINTEL whose Result Type uses Float8E4M3EXT " "or Float8E5M2EXT encoding.\n"); IsSaturatedFP8 = true; } if (IsFP4OrFP8Encoding(SrcEnc) || IsFP4OrFP8Encoding(DstEnc) || SPVSrcTy->isTypeInt(4) || SPVDstTy->isTypeInt(4)) { // The old opcodes share the encoding map with their surviving // equivalents: OpClampConvertFToFINTEL with OpFConvert and // OpClampStochasticRoundFToFINTEL with OpStochasticRoundFToFINTEL. SPIRVWord LookupOC = OC; if (OC == internal::OpClampConvertFToFINTEL) LookupOC = OpFConvert; else if (OC == internal::OpClampStochasticRoundFToFINTEL) LookupOC = internal::OpStochasticRoundFToFINTEL; FPEncodingWrap LookupDstEnc = DstEnc; if (LookupOC == internal::OpStochasticRoundFToFINTEL && DstEnc == FPEncodingWrap::E2M1INTEL) LookupDstEnc = FPEncodingWrap::E2M1; FPConversionDesc FPDesc = {SrcEnc, LookupDstEnc, LookupOC, /*Saturate=*/IsSaturatedFP8}; auto Conv = SPIRV::FPConvertToEncodingMap::rmap(FPDesc); std::vector Ops = {Src}; std::vector OpsTys = {Src->getType()}; std::string BuiltinName = kSPIRVName::InternalBuiltinPrefix + std::string(Conv); BuiltinFuncMangleInfo Info; std::string MangledName; // Translate additional Ops for stochastic conversions. if (OC == internal::OpStochasticRoundFToFINTEL || OC == internal::OpClampStochasticRoundFToFINTEL || OC == internal::OpClampStochasticRoundFToSINTEL) { // Seed. Ops.emplace_back(transValue(SPVOps[1], F, BB, true)); OpsTys.emplace_back(Ops[1]->getType()); constexpr unsigned MaxOpsSize = 3; if (SPVOps.size() == MaxOpsSize) { // New Seed. Ops.emplace_back(transValue(SPVOps[2], F, BB, true)); // The following mess is needed to create a function with correct // mangling. SPIRVType *PtrTy = SPVOps[2]->getType(); const unsigned AS = SPIRSPIRVAddrSpaceMap::rmap(PtrTy->getPointerStorageClass()); Type *ElementTy = transType(PtrTy->getPointerElementType()); OpsTys.emplace_back(TypedPointerType::get(ElementTy, AS)); MangledName = mangleBuiltin(BuiltinName, OpsTys, &Info); // But to create function itself we need untyped pointer type. OpsTys[2] = opaquifyType(OpsTys[2]); } } if (MangledName.empty()) MangledName = mangleBuiltin(BuiltinName, OpsTys, &Info); Function *Func = getOrCreateFunction(M, Dst, OpsTys, MangledName); auto *CI = CallInst::Create(Func, Ops, "", BB); CI->setCallingConv(CallingConv::SPIR_FUNC); return CI; } } // OpStochasticRoundFToFINTEL and the old OpClampConvertFToFINTEL / // OpClampStochasticRoundFToFINTEL opcodes have no native LLVM cast // equivalent. For fp4/fp8/int4 types, they are handled via the // __builtin_spirv path above. For the remaining types they are emitted as // an __spirv__R builtin call. if (OC == internal::OpStochasticRoundFToFINTEL || IsOldConvertFToFOp) return mapValue(BV, transSPIRVBuiltinFromInst( static_cast(BV), BB)); if (OC == OpFConvert) { CO = IsExt ? Instruction::FPExt : Instruction::FPTrunc; break; } CO = static_cast(OpCodeMap::rmap(OC)); break; } case OpBitcast: if (!Dst->isPointerTy() && Dst == Src->getType()) return Src; // OpBitcast need to be handled as a special-case when the source is a // pointer and the destination is not a pointer, and where the source is not // a pointer and the destination is a pointer. This is supported by the // SPIR-V bitcast, but not by the LLVM bitcast. CO = Instruction::BitCast; if (Src->getType()->isPointerTy() && !Dst->isPointerTy()) { if (auto *DstVecTy = dyn_cast(Dst)) { unsigned TotalBitWidth = DstVecTy->getElementType()->getIntegerBitWidth() * DstVecTy->getNumElements(); auto *IntTy = Type::getIntNTy(Src->getContext(), TotalBitWidth); if (BB) { Src = CastInst::CreatePointerCast(Src, IntTy, "", BB); } else { Src = ConstantExpr::getPointerCast(dyn_cast(Src), IntTy); } } else { CO = Instruction::PtrToInt; } } else if (!Src->getType()->isPointerTy() && Dst->isPointerTy()) { if (auto *SrcVecTy = dyn_cast(Src->getType())) { unsigned TotalBitWidth = SrcVecTy->getElementType()->getIntegerBitWidth() * SrcVecTy->getNumElements(); auto *IntTy = Type::getIntNTy(Src->getContext(), TotalBitWidth); if (BB) { Src = CastInst::Create(Instruction::BitCast, Src, IntTy, "", BB); } else { Src = ConstantExpr::getBitCast(dyn_cast(Src), IntTy); } } CO = Instruction::IntToPtr; } break; // Workaround: OpConvertPtrToU on a target extension type (e.g. spirv.Image) // is invalid SPIR-V, but is generated by the DPC++ frontend for ESIMD // get_surface_index() on image accessors. // Translate as a builtin call (valid LLVM IR) instead of PtrToInt (invalid // IR). Removing this requires fixing the root cause: either a new SPIR-V // extension for image-handle-to-index conversion, or redesigning ESIMD // accessor storage. case OpConvertPtrToU: { if (Src->getType()->isTargetExtTy()) { if (BM->getExtension().count("SPV_INTEL_vector_compute")) return transSPIRVBuiltinFromInst(BC, BB); BM->getErrorLog().checkError(false, SPIRVEC_InvalidInstruction, "OpConvertPtrToU on a target extension type " "requires SPV_INTEL_vector_compute"); } [[fallthrough]]; } default: CO = static_cast(OpCodeMap::rmap(BC->getOpCode())); } assert(CastInst::isCast(CO) && "Invalid cast op code"); SPIRVDBG(if (!CastInst::castIsValid(CO, Src, Dst)) { spvdbgs() << "Invalid cast: " << *BV << " -> "; dbgs() << "Op = " << CO << ", Src = " << *Src << " Dst = " << *Dst << '\n'; }) if (BB) return CastInst::Create(CO, Src, Dst, BV->getName(), BB); return ConstantExpr::getCast(CO, dyn_cast(Src), Dst); } static void applyNoIntegerWrapDecorations(const SPIRVValue *BV, Instruction *Inst) { if (BV->hasDecorate(DecorationNoSignedWrap)) { Inst->setHasNoSignedWrap(true); } if (BV->hasDecorate(DecorationNoUnsignedWrap)) { Inst->setHasNoUnsignedWrap(true); } } static void applyFPFastMathModeDecorations(const SPIRVValue *BV, Instruction *Inst) { SPIRVWord V; FastMathFlags FMF; if (BV->hasDecorate(DecorationFPFastMathMode, 0, &V)) { if (V & FPFastMathModeNotNaNMask) FMF.setNoNaNs(); if (V & FPFastMathModeNotInfMask) FMF.setNoInfs(); if (V & FPFastMathModeNSZMask) FMF.setNoSignedZeros(); if (V & FPFastMathModeAllowRecipMask) FMF.setAllowReciprocal(); if (V & FPFastMathModeAllowContractFastINTELMask) FMF.setAllowContract(); if (V & FPFastMathModeAllowReassocINTELMask) FMF.setAllowReassoc(); if (V & FPFastMathModeFastMask) FMF.setFast(); Inst->setFastMathFlags(FMF); } } Value *SPIRVToLLVM::transShiftLogicalBitwiseInst(SPIRVValue *BV, BasicBlock *BB, Function *F) { SPIRVBinary *BBN = static_cast(BV); if (BV->getType()->isTypeCooperativeMatrixKHR()) { return mapValue(BV, transSPIRVBuiltinFromInst(BBN, BB)); } Instruction::BinaryOps BO; auto OP = BBN->getOpCode(); if (isLogicalOpCode(OP)) OP = IntBoolOpMap::rmap(OP); BO = static_cast(OpCodeMap::rmap(OP)); Value *Op0 = transValue(BBN->getOperand(0), F, BB); Value *Op1 = transValue(BBN->getOperand(1), F, BB); IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } Value *NewOp = Builder.CreateBinOp(BO, Op0, Op1, BV->getName()); if (auto *Inst = dyn_cast(NewOp)) { applyNoIntegerWrapDecorations(BV, Inst); applyFPFastMathModeDecorations(BV, Inst); } return NewOp; } Value *SPIRVToLLVM::transCmpInst(SPIRVValue *BV, BasicBlock *BB, Function *F) { SPIRVCompare *BC = static_cast(BV); SPIRVType *BT = BC->getOperand(0)->getType(); Value *Inst = nullptr; auto OP = BC->getOpCode(); if (isLogicalOpCode(OP)) OP = IntBoolOpMap::rmap(OP); Value *Op0 = transValue(BC->getOperand(0), F, BB); Value *Op1 = transValue(BC->getOperand(1), F, BB); IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } if (OP == OpLessOrGreater) OP = OpFOrdNotEqual; if (BT->isTypeVectorOrScalarInt() || BT->isTypeVectorOrScalarBool() || BT->isTypePointer()) Inst = Builder.CreateICmp(CmpMap::rmap(OP), Op0, Op1); else if (BT->isTypeVectorOrScalarFloat()) Inst = Builder.CreateFCmp(CmpMap::rmap(OP), Op0, Op1); assert(Inst && "not implemented"); applyFPFastMathModeDecorations(BV, static_cast(Inst)); return Inst; } Type *SPIRVToLLVM::mapType(SPIRVType *BT, Type *T) { SPIRVDBG(dbgs() << *T << '\n';) // We don't want to store a TypedPointerType in the type map, since we can't // actually use it in LLVM IR directly. Note that in the cases where we do // want to construct TypedPointerType, we don't check the type map here. if (!isa(T)) TypeMap[BT] = T; return T; } Value *SPIRVToLLVM::mapValue(SPIRVValue *BV, Value *V) { auto Loc = ValueMap.find(BV); if (Loc != ValueMap.end()) { if (Loc->second == V) return V; auto *LD = dyn_cast(Loc->second); auto *Placeholder = dyn_cast(LD->getPointerOperand()); assert(LD && Placeholder && Placeholder->getName().starts_with(KPlaceholderPrefix) && "A value is translated twice"); // Replaces placeholders for PHI nodes LD->replaceAllUsesWith(V); LD->eraseFromParent(); Placeholder->eraseFromParent(); } ValueMap[BV] = V; return V; } CallInst * SPIRVToLLVM::expandOCLBuiltinWithScalarArg(CallInst *CI, const std::string &FuncName) { if (!CI->getOperand(0)->getType()->isVectorTy() && CI->getOperand(1)->getType()->isVectorTy()) { auto VecElemCount = cast(CI->getOperand(1)->getType())->getElementCount(); auto Mutator = mutateCallInst(CI, FuncName); Mutator.mapArg(0, [=](Value *Arg) { Value *NewVec = nullptr; if (auto *CA = dyn_cast(Arg)) NewVec = ConstantVector::getSplat(VecElemCount, CA); else { NewVec = ConstantVector::getSplat( VecElemCount, Constant::getNullValue(Arg->getType())); NewVec = InsertElementInst::Create(NewVec, Arg, getInt32(M, 0), "", CI->getIterator()); NewVec = new ShuffleVectorInst( NewVec, NewVec, ConstantVector::getSplat(VecElemCount, getInt32(M, 0)), "", CI->getIterator()); } NewVec->takeName(Arg); return NewVec; }); return cast(Mutator.getMutated()); } return CI; } std::string SPIRVToLLVM::transOCLPipeTypeAccessQualifier(SPIRV::SPIRVTypePipe *ST) { return SPIRSPIRVAccessQualifierMap::rmap(ST->getAccessQualifier()); } void SPIRVToLLVM::transGeneratorMD() { SPIRVMDBuilder B(*M); B.addNamedMD(kSPIRVMD::Generator) .addOp() .addU16(BM->getGeneratorId()) .addU16(BM->getGeneratorVer()) .done(); } Value *SPIRVToLLVM::oclTransConstantSampler(SPIRV::SPIRVConstantSampler *BCS, BasicBlock *BB) { auto *SamplerT = getSPIRVType(OpTypeSampler, true); auto *I32Ty = IntegerType::getInt32Ty(*Context); auto *FTy = FunctionType::get(SamplerT, {I32Ty}, false); FunctionCallee Func = M->getOrInsertFunction(SAMPLER_INIT, FTy); auto Lit = (BCS->getAddrMode() << 1) | BCS->getNormalized() | ((BCS->getFilterMode() + 1) << 4); return CallInst::Create(Func, {ConstantInt::get(I32Ty, Lit)}, "", BB); } Value *SPIRVToLLVM::oclTransConstantPipeStorage( SPIRV::SPIRVConstantPipeStorage *BCPS) { std::string CPSName = std::string(kSPIRVTypeName::PrefixAndDelim) + kSPIRVTypeName::ConstantPipeStorage; auto *Int32Ty = IntegerType::getInt32Ty(*Context); auto *CPSTy = StructType::getTypeByName(*Context, CPSName); if (!CPSTy) { Type *CPSElemsTy[] = {Int32Ty, Int32Ty, Int32Ty}; CPSTy = StructType::create(*Context, CPSElemsTy, CPSName); } assert(CPSTy != nullptr && "Could not create spirv.ConstantPipeStorage"); Constant *CPSElems[] = {ConstantInt::get(Int32Ty, BCPS->getPacketSize()), ConstantInt::get(Int32Ty, BCPS->getPacketAlign()), ConstantInt::get(Int32Ty, BCPS->getCapacity())}; return new GlobalVariable(*M, CPSTy, false, GlobalValue::LinkOnceODRLinkage, ConstantStruct::get(CPSTy, CPSElems), BCPS->getName(), nullptr, GlobalValue::NotThreadLocal, SPIRAS_Global); } // Translate aliasing memory access masks for SPIRVLoad and SPIRVStore // instructions. These masks are mapped on alias.scope and noalias // metadata in LLVM. Translation of optional string operand isn't yet supported // in the translator. template void SPIRVToLLVM::transAliasingMemAccess(SPIRVInstType *BI, Instruction *I) { static_assert(std::is_same::value || std::is_same::value, "Only stores and loads can be aliased by memory access mask"); if (BI->SPIRVMemoryAccess::isNoAlias()) addMemAliasMetadata(I, BI->SPIRVMemoryAccess::getNoAliasInstID(), LLVMContext::MD_noalias); if (BI->SPIRVMemoryAccess::isAliasScope()) addMemAliasMetadata(I, BI->SPIRVMemoryAccess::getAliasScopeInstID(), LLVMContext::MD_alias_scope); } // Create and apply alias.scope/noalias metadata void SPIRVToLLVM::addMemAliasMetadata(Instruction *I, SPIRVId AliasListId, uint32_t AliasMDKind) { SPIRVAliasScopeListDeclINTEL *AliasList = BM->get(AliasListId); std::vector AliasScopeIds = AliasList->getArguments(); MDBuilder MDB(*Context); SmallVector MDScopes; for (const auto ScopeId : AliasScopeIds) { SPIRVAliasScopeDeclINTEL *AliasScope = BM->get(ScopeId); std::vector AliasDomainIds = AliasScope->getArguments(); // Currently we expect exactly one argument for aliasing scope // instruction. // TODO: add translation of string scope and domain operand. assert(AliasDomainIds.size() == 1 && "AliasScopeDeclINTEL must have exactly one argument"); SPIRVId AliasDomainId = AliasDomainIds[0]; // Create and store unique domain and scope metadata MDAliasDomainMap.emplace(AliasDomainId, MDB.createAnonymousAliasScopeDomain()); MDAliasScopeMap.emplace(ScopeId, MDB.createAnonymousAliasScope( MDAliasDomainMap[AliasDomainId])); MDScopes.emplace_back(MDAliasScopeMap[ScopeId]); } // Create and store unique alias.scope/noalias metadata MDAliasListMap.emplace(AliasListId, MDNode::concatenate(I->getMetadata(AliasMDKind), MDNode::get(*Context, MDScopes))); I->setMetadata(AliasMDKind, MDAliasListMap[AliasListId]); } void SPIRVToLLVM::transFunctionPointerCallArgumentAttributes( SPIRVValue *BV, CallInst *CI, SPIRVTypeFunction *CalledFnTy) { std::vector ArgumentAttributes = BV->getDecorations(internal::DecorationArgumentAttributeINTEL); for (const auto *Dec : ArgumentAttributes) { std::vector Literals = Dec->getVecLiteral(); SPIRVWord ArgNo = Literals[0]; SPIRVWord SpirvAttr = Literals[1]; // There is no value to rmap SPIR-V FunctionParameterAttributeNoCapture, as // LLVM does not have Attribute::NoCapture anymore. Adding special handling // for this case. if (SpirvAttr == FunctionParameterAttributeNoCapture) { CI->addParamAttr(ArgNo, Attribute::getWithCaptureInfo( CI->getContext(), CaptureInfo::none())); continue; } Attribute::AttrKind LlvmAttrKind = SPIRSPIRVFuncParamAttrMap::rmap( static_cast(SpirvAttr)); auto LlvmAttr = Attribute::isTypeAttrKind(LlvmAttrKind) ? Attribute::get(CI->getContext(), LlvmAttrKind, transType(CalledFnTy->getParameterType(ArgNo) ->getPointerElementType())) : Attribute::get(CI->getContext(), LlvmAttrKind); CI->addParamAttr(ArgNo, LlvmAttr); } } /// For instructions, this function assumes they are created in order /// and appended to the given basic block. An instruction may use a /// instruction from another BB which has not been translated. Such /// instructions should be translated to place holders at the point /// of first use, then replaced by real instructions when they are /// created. /// /// When CreatePlaceHolder is true, create a load instruction of a /// global variable as placeholder for SPIRV instruction. Otherwise, /// create instruction and replace placeholder if there is one. Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F, BasicBlock *BB, bool CreatePlaceHolder) { auto OC = BV->getOpCode(); IntBoolOpMap::rfind(OC, &OC); // Translation of non-instruction values switch (OC) { case OpConstant: case OpSpecConstant: { SPIRVConstant *BConst = static_cast(BV); SPIRVType *BT = BV->getType(); Type *LT = transType(BT); uint64_t ConstValue = BConst->getZExtIntValue(); SPIRVWord SpecId = 0; if (OC == OpSpecConstant && BV->hasDecorate(DecorationSpecId, 0, &SpecId)) { // Update the value with possibly provided external specialization. if (BM->getSpecializationConstant(SpecId, ConstValue)) { assert( (BT->getBitWidth() == 64 || (ConstValue >> BT->getBitWidth()) == 0) && "Size of externally provided specialization constant value doesn't" "fit into the specialization constant type"); } } switch (BT->getOpCode()) { case OpTypeBool: case OpTypeInt: { const unsigned NumBits = BT->getBitWidth(); if (NumBits > 64) { // Translate huge arbitrary precision integer constants const unsigned RawDataNumWords = BConst->getNumWords(); const unsigned BigValNumWords = (RawDataNumWords + 1) / 2; std::vector BigValVec(BigValNumWords); const std::vector &RawData = BConst->getSPIRVWords(); // SPIRV words are integers of 32-bit width, meanwhile llvm::APInt // is storing data using an array of 64-bit words. Here we pack SPIRV // words into 64-bit integer array. for (size_t I = 0; I != RawDataNumWords / 2; ++I) BigValVec[I] = (static_cast(RawData[2 * I + 1]) << SpirvWordBitWidth) | RawData[2 * I]; if (RawDataNumWords % 2) BigValVec.back() = RawData.back(); return mapValue(BV, ConstantInt::get(LT, APInt(NumBits, BigValVec))); } return mapValue( BV, ConstantInt::get(LT, ConstValue, static_cast(BT)->isSigned())); } case OpTypeFloat: { const llvm::fltSemantics *FS = nullptr; switch (BT->getFloatBitWidth()) { case 16: FS = (BT->isTypeFloat(16, FPEncodingBFloat16KHR) ? &APFloat::BFloat() : &APFloat::IEEEhalf()); break; case 32: FS = &APFloat::IEEEsingle(); break; case 64: FS = &APFloat::IEEEdouble(); break; default: llvm_unreachable("invalid floating-point type"); } APFloat FPConstValue(*FS, APInt(BT->getFloatBitWidth(), ConstValue)); return mapValue(BV, ConstantFP::get(*Context, FPConstValue)); } default: llvm_unreachable("Not implemented"); return nullptr; } } case OpConstantTrue: return mapValue(BV, ConstantInt::getTrue(*Context)); case OpConstantFalse: return mapValue(BV, ConstantInt::getFalse(*Context)); case OpSpecConstantTrue: case OpSpecConstantFalse: { bool IsTrue = OC == OpSpecConstantTrue; SPIRVWord SpecId = 0; if (BV->hasDecorate(DecorationSpecId, 0, &SpecId)) { uint64_t ConstValue = 0; if (BM->getSpecializationConstant(SpecId, ConstValue)) { IsTrue = ConstValue; } } return mapValue(BV, IsTrue ? ConstantInt::getTrue(*Context) : ConstantInt::getFalse(*Context)); } case OpConstantNull: { auto *LT = transType(BV->getType()); return mapValue(BV, Constant::getNullValue(LT)); } case OpConstantComposite: case OpSpecConstantComposite: { auto *BCC = static_cast(BV); std::vector CV; for (auto &I : BCC->getElements()) CV.push_back(dyn_cast(transValue(I, F, BB))); for (auto &CI : BCC->getContinuedInstructions()) { for (auto &I : CI->getElements()) CV.push_back(dyn_cast(transValue(I, F, BB))); } switch (BV->getType()->getOpCode()) { case OpTypeVector: return mapValue(BV, ConstantVector::get(CV)); case OpTypeMatrix: case OpTypeArray: { auto *AT = cast(transType(BCC->getType())); for (size_t I = 0; I != AT->getNumElements(); ++I) { auto *ElemTy = AT->getElementType(); if (auto *ElemPtrTy = dyn_cast(ElemTy)) { assert(isa(CV[I]->getType()) && "Constant type doesn't match constexpr array element type"); if (ElemPtrTy->getAddressSpace() != cast(CV[I]->getType())->getAddressSpace()) CV[I] = ConstantExpr::getAddrSpaceCast(CV[I], AT->getElementType()); } } return mapValue(BV, ConstantArray::get(AT, CV)); } case OpTypeStruct: { auto *BCCTy = cast(transType(BCC->getType())); auto Members = BCCTy->getNumElements(); auto Constants = CV.size(); // if we try to initialize constant TypeStruct, add bitcasts // if src and dst types are both pointers but to different types if (Members == Constants) { for (unsigned I = 0; I < Members; ++I) { if (CV[I]->getType() == BCCTy->getElementType(I)) continue; if (!CV[I]->getType()->isPointerTy() || !BCCTy->getElementType(I)->isPointerTy()) continue; if (cast(CV[I]->getType())->getAddressSpace() != cast(BCCTy->getElementType(I))->getAddressSpace()) CV[I] = ConstantExpr::getAddrSpaceCast(CV[I], BCCTy->getElementType(I)); else CV[I] = ConstantExpr::getBitCast(CV[I], BCCTy->getElementType(I)); } } return mapValue(BV, ConstantStruct::get(BCCTy, CV)); } case OpTypeCooperativeMatrixKHR: { assert(CV.size() == 1 && "expecting exactly one operand for cooperative matrix types"); llvm::Type *RetTy = transType(BCC->getType()); llvm::Type *EltTy = transType( static_cast(BV->getType()) ->getCompType()); auto *FTy = FunctionType::get(RetTy, {EltTy}, false); FunctionCallee Func = M->getOrInsertFunction(getSPIRVFuncName(OC, RetTy), FTy); IRBuilder<> Builder(BB); CallInst *Call = Builder.CreateCall(Func, CV.front()); Call->setCallingConv(CallingConv::SPIR_FUNC); return Call; } default: llvm_unreachable("not implemented"); return nullptr; } } case OpConstantSampler: { auto *BCS = static_cast(BV); // Intentially do not map this value. We want to generate constant // sampler initializer every time constant sampler is used, otherwise // initializer may not dominate all its uses. return oclTransConstantSampler(BCS, BB); } case OpConstantPipeStorage: { auto *BCPS = static_cast(BV); return mapValue(BV, oclTransConstantPipeStorage(BCPS)); } case OpSpecConstantOp: { auto *BI = createInstFromSpecConstantOp(static_cast(BV)); return mapValue(BV, transValue(BI, nullptr, nullptr, false)); } case OpConstantFunctionPointerINTEL: { SPIRVConstantFunctionPointerINTEL *BC = static_cast(BV); SPIRVFunction *F = BC->getFunction(); BV->setName(F->getName()); const unsigned AS = BM->shouldEmitFunctionPtrAddrSpace() ? SPIRAS_CodeSectionINTEL : SPIRAS_Private; return mapValue(BV, transFunction(F, AS)); } case OpUndef: return mapValue(BV, UndefValue::get(transType(BV->getType()))); case OpSizeOf: { Type *ResTy = transType(BV->getType()); auto *BI = static_cast(BV); SPIRVType *TypeArg = reinterpret_cast(BI->getOpValue(0)); Type *EltTy = transType(TypeArg->getPointerElementType()); uint64_t Size = M->getDataLayout().getTypeStoreSize(EltTy).getFixedValue(); return mapValue(BV, ConstantInt::get(ResTy, Size)); } case OpVariable: case OpUntypedVariableKHR: { auto *BVar = static_cast(BV); SPIRVType *PreTransTy = BVar->getType()->getPointerElementType(); if (BVar->getType()->isTypeUntypedPointerKHR()) { auto *UntypedVar = static_cast(BVar); if (SPIRVType *DT = UntypedVar->getDataType()) PreTransTy = DT; } auto *Ty = transType(PreTransTy); bool IsConst = BVar->isConstant(); llvm::GlobalValue::LinkageTypes LinkageTy = transLinkageType(BVar); SPIRVStorageClassKind BS = BVar->getStorageClass(); SPIRVValue *Init = BVar->getInitializer(); if (PreTransTy->isTypeSampler() && BS == StorageClassUniformConstant) { // Skip generating llvm code during translation of a variable definition, // generate code only for its uses if (!BB) return nullptr; assert(Init && "UniformConstant OpVariable with sampler type must have " "an initializer!"); return transValue(Init, F, BB); } if (BS == StorageClassFunction) { // A Function storage class variable needs storage for each dynamic // execution instance, so emit an alloca instead of a global. assert(BB && "OpVariable with Function storage class requires BB"); IRBuilder<> Builder(BB); AllocaInst *AI = Builder.CreateAlloca(Ty, nullptr, BV->getName()); if (Init) { auto *Src = transValue(Init, F, BB); const bool IsVolatile = BVar->hasDecorate(DecorationVolatile); Builder.CreateStore(Src, AI, IsVolatile); } return mapValue(BV, AI); } SPIRAddressSpace AddrSpace; bool IsVectorCompute = BVar->hasDecorate(DecorationVectorComputeVariableINTEL); Constant *Initializer = nullptr; if (IsVectorCompute) { AddrSpace = VectorComputeUtil::getVCGlobalVarAddressSpace(BS); Initializer = PoisonValue::get(Ty); } else AddrSpace = SPIRSPIRVAddrSpaceMap::rmap(BS); // Force SPIRV BuiltIn variable's name to be __spirv_BuiltInXXXX. // No matter what BV's linkage name is. SPIRVBuiltinVariableKind BVKind; if (BVar->isBuiltin(&BVKind)) BV->setName(prefixSPIRVName(SPIRVBuiltInNameMap::map(BVKind))); auto *LVar = new GlobalVariable(*M, Ty, IsConst, LinkageTy, /*Initializer=*/nullptr, BV->getName(), 0, GlobalVariable::NotThreadLocal, AddrSpace); auto *Res = mapValue(BV, LVar); if (Init) Initializer = dyn_cast(transValue(Init, F, BB, false)); else if (LinkageTy == GlobalValue::CommonLinkage) // In LLVM, variables with common linkage type must be initialized to 0. Initializer = Constant::getNullValue(Ty); else if (BS == SPIRVStorageClassKind::StorageClassWorkgroup && LinkageTy != GlobalValue::ExternalLinkage) Initializer = dyn_cast(PoisonValue::get(Ty)); else if ((LinkageTy != GlobalValue::ExternalLinkage) && (BS == SPIRVStorageClassKind::StorageClassCrossWorkgroup)) Initializer = Constant::getNullValue(Ty); LVar->setUnnamedAddr((IsConst && Ty->isArrayTy() && Ty->getArrayElementType()->isIntegerTy(8)) ? GlobalValue::UnnamedAddr::Global : GlobalValue::UnnamedAddr::None); LVar->setInitializer(Initializer); if (IsVectorCompute) { LVar->addAttribute(kVCMetadata::VCGlobalVariable); SPIRVWord Offset; if (BVar->hasDecorate(DecorationGlobalVariableOffsetINTEL, 0, &Offset)) LVar->addAttribute(kVCMetadata::VCByteOffset, utostr(Offset)); if (BVar->hasDecorate(DecorationVolatile)) LVar->addAttribute(kVCMetadata::VCVolatile); auto SEVAttr = translateSEVMetadata(BVar, LVar->getContext()); if (SEVAttr) LVar->addAttribute(SEVAttr.value().getKindAsString(), SEVAttr.value().getValueAsString()); } return Res; } case OpFunctionParameter: { auto *BA = static_cast(BV); assert(F && "Invalid function"); unsigned ArgNo = 0; for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E; ++I, ++ArgNo) { if (ArgNo == BA->getArgNo()) return mapValue(BV, &(*I)); } llvm_unreachable("Invalid argument"); return nullptr; } case OpFunction: return mapValue(BV, transFunction(static_cast(BV))); case OpAsmINTEL: return mapValue(BV, transAsmINTEL(static_cast(BV))); case OpLabel: return mapValue(BV, BasicBlock::Create(*Context, BV->getName(), F)); case OpSpecConstantArchitectureINTEL: llvm_unreachable( "Encountered non-specialized OpSpecConstantArchitectureINTEL"); return nullptr; case OpSpecConstantTargetINTEL: llvm_unreachable("Encountered non-specialized OpSpecConstantTargetINTEL"); return nullptr; case OpSpecConstantCapabilitiesINTEL: llvm_unreachable( "Encountered non-specialized OpSpecConstantCapabilitiesINTEL"); return nullptr; default: // do nothing break; } // During translation of OpSpecConstantOp we create an instruction // corresponding to the Opcode operand and then translate this instruction. // For such instruction BB and F should be nullptr, because it is a constant // expression declared out of scope of any basic block or function. // All other values require valid BB pointer. assert(((isSpecConstantOpAllowedOp(OC) && !F && !BB) || BB) && "Invalid BB"); // Creation of place holder if (CreatePlaceHolder) { auto *Ty = transType(BV->getType()); auto *GV = new GlobalVariable(*M, Ty, false, GlobalValue::PrivateLinkage, nullptr, std::string(KPlaceholderPrefix) + BV->getName(), 0, GlobalVariable::NotThreadLocal, 0); auto *LD = new LoadInst(Ty, GV, BV->getName(), BB); PlaceholderMap[BV] = LD; return mapValue(BV, LD); } // Translation of instructions int OpCode = BV->getOpCode(); switch (OpCode) { case OpVariableLengthArrayINTEL: { auto *VLA = static_cast(BV); llvm::Type *Ty = transType(BV->getType()->getPointerElementType()); llvm::Value *ArrSize = transValue(VLA->getOperand(0), F, BB); return mapValue(BV, new AllocaInst(Ty, M->getDataLayout().getAllocaAddrSpace(), ArrSize, BV->getName(), BB)); } case OpRestoreMemoryINTEL: { IRBuilder<> Builder(BB); auto *Restore = static_cast(BV); llvm::Value *Ptr = transValue(Restore->getOperand(0), F, BB); auto *StackRestore = Builder.CreateStackRestore(Ptr); return mapValue(BV, StackRestore); } case OpSaveMemoryINTEL: { IRBuilder<> Builder(BB); auto *StackSave = Builder.CreateStackSave(); return mapValue(BV, StackSave); } case OpBranch: { auto *BR = static_cast(BV); auto *BI = BranchInst::Create( cast(transValue(BR->getTargetLabel(), F, BB)), BB); // Loop metadata will be translated in the end of function translation. return mapValue(BV, BI); } case OpBranchConditional: { auto *BR = static_cast(BV); auto *BC = BranchInst::Create( cast(transValue(BR->getTrueLabel(), F, BB)), cast(transValue(BR->getFalseLabel(), F, BB)), transValue(BR->getCondition(), F, BB), BB); // Loop metadata will be translated in the end of function translation. return mapValue(BV, BC); } case OpPhi: { auto *Phi = static_cast(BV); auto *LPhi = dyn_cast(mapValue( BV, PHINode::Create(transType(Phi->getType()), Phi->getPairs().size() / 2, Phi->getName(), BB))); Phi->foreachPair([&](SPIRVValue *IncomingV, SPIRVBasicBlock *IncomingBB, size_t Index) { auto *Translated = transValue(IncomingV, F, BB); LPhi->addIncoming(Translated, dyn_cast(transValue(IncomingBB, F, BB))); }); return LPhi; } case OpUnreachable: return mapValue(BV, new UnreachableInst(*Context, BB)); case OpReturn: return mapValue(BV, ReturnInst::Create(*Context, BB)); case OpReturnValue: { auto *RV = static_cast(BV); return mapValue( BV, ReturnInst::Create(*Context, transValue(RV->getReturnValue(), F, BB), BB)); } case OpLifetimeStart: { SPIRVLifetimeStart *LTStart = static_cast(BV); IRBuilder<> Builder(BB); SPIRVWord Size = LTStart->getSize(); ConstantInt *S = nullptr; if (Size) S = Builder.getInt64(Size); Value *Var = transValue(LTStart->getObject(), F, BB); CallInst *Start = Builder.CreateLifetimeStart(Var, S); return mapValue(BV, Start); } case OpLifetimeStop: { SPIRVLifetimeStop *LTStop = static_cast(BV); IRBuilder<> Builder(BB); SPIRVWord Size = LTStop->getSize(); ConstantInt *S = nullptr; if (Size) S = Builder.getInt64(Size); auto *Var = transValue(LTStop->getObject(), F, BB); for (const auto &I : Var->users()) if (auto *II = getLifetimeStartIntrinsic(dyn_cast(I))) return mapValue(BV, Builder.CreateLifetimeEnd(II->getOperand(1), S)); return mapValue(BV, Builder.CreateLifetimeEnd(Var, S)); } case OpStore: { SPIRVStore *BS = static_cast(BV); StoreInst *SI = nullptr; auto *Src = transValue(BS->getSrc(), F, BB); auto *Dst = transValue(BS->getDst(), F, BB); bool isVolatile = BS->SPIRVMemoryAccess::isVolatile(); uint64_t AlignValue = BS->SPIRVMemoryAccess::getAlignment(); if (0 == AlignValue) SI = new StoreInst(Src, Dst, isVolatile, BB); else SI = new StoreInst(Src, Dst, isVolatile, Align(AlignValue), BB); if (BS->SPIRVMemoryAccess::isNonTemporal()) transNonTemporalMetadata(SI); transAliasingMemAccess(BS, SI); return mapValue(BV, SI); } case OpLoad: { SPIRVLoad *BL = static_cast(BV); auto *V = transValue(BL->getSrc(), F, BB); Type *Ty = transType(BL->getType()); LoadInst *LI = nullptr; uint64_t AlignValue = BL->SPIRVMemoryAccess::getAlignment(); if (0 == AlignValue) { LI = new LoadInst(Ty, V, BV->getName(), BL->SPIRVMemoryAccess::isVolatile(), BB); } else { LI = new LoadInst(Ty, V, BV->getName(), BL->SPIRVMemoryAccess::isVolatile(), Align(AlignValue), BB); } if (BL->SPIRVMemoryAccess::isNonTemporal()) transNonTemporalMetadata(LI); transAliasingMemAccess(BL, LI); return mapValue(BV, LI); } case OpCopyMemory: { auto *BC = static_cast(BV); llvm::Value *Dst = transValue(BC->getTarget(), F, BB); MaybeAlign Align(BC->getAlignment()); MaybeAlign SrcAlign = BC->getSrcAlignment() ? MaybeAlign(BC->getSrcAlignment()) : Align; Type *EltTy = transType(BC->getSource()->getType()->getPointerElementType()); uint64_t Size = M->getDataLayout().getTypeStoreSize(EltTy).getFixedValue(); bool IsVolatile = BC->SPIRVMemoryAccess::isVolatile(); IRBuilder<> Builder(BB); llvm::Value *Src = transValue(BC->getSource(), F, BB); CallInst *CI = Builder.CreateMemCpy(Dst, Align, Src, SrcAlign, Size, IsVolatile); if (isFuncNoUnwind()) CI->getFunction()->addFnAttr(Attribute::NoUnwind); return mapValue(BV, CI); } case OpCopyMemorySized: { SPIRVCopyMemorySized *BC = static_cast(BV); llvm::Value *Dst = transValue(BC->getTarget(), F, BB); MaybeAlign Align(BC->getAlignment()); MaybeAlign SrcAlign = BC->getSrcAlignment() ? MaybeAlign(BC->getSrcAlignment()) : Align; llvm::Value *Size = transValue(BC->getSize(), F, BB); bool IsVolatile = BC->SPIRVMemoryAccess::isVolatile(); IRBuilder<> Builder(BB); llvm::Value *Src = transValue(BC->getSource(), F, BB); CallInst *CI = Builder.CreateMemCpy(Dst, Align, Src, SrcAlign, Size, IsVolatile); if (isFuncNoUnwind()) CI->getFunction()->addFnAttr(Attribute::NoUnwind); return mapValue(BV, CI); } case OpSelect: { SPIRVSelect *BS = static_cast(BV); IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } return mapValue(BV, Builder.CreateSelect(transValue(BS->getCondition(), F, BB), transValue(BS->getTrueValue(), F, BB), transValue(BS->getFalseValue(), F, BB), BV->getName())); } case OpLine: case OpSelectionMerge: // OpenCL Compiler does not use this instruction return nullptr; case OpLoopMerge: // Will be translated after all other function's case OpLoopControlINTEL: // instructions are translated. FuncLoopMetadataMap[BB] = BV; return nullptr; case OpSwitch: { auto *BS = static_cast(BV); auto *Select = transValue(BS->getSelect(), F, BB); auto *LS = SwitchInst::Create( Select, dyn_cast(transValue(BS->getDefault(), F, BB)), BS->getNumPairs(), BB); BS->foreachPair( [&](SPIRVSwitch::LiteralTy Literals, SPIRVBasicBlock *Label) { assert(!Literals.empty() && "Literals should not be empty"); assert(Literals.size() <= 2 && "Number of literals should not be more then two"); uint64_t Literal = uint64_t(Literals.at(0)); if (Literals.size() == 2) { Literal += uint64_t(Literals.at(1)) << 32; } LS->addCase( ConstantInt::get(cast(Select->getType()), Literal), cast(transValue(Label, F, BB))); }); return mapValue(BV, LS); } case OpVectorTimesScalar: { auto *VTS = static_cast(BV); IRBuilder<> Builder(BB); auto *Scalar = transValue(VTS->getScalar(), F, BB); auto *Vector = transValue(VTS->getVector(), F, BB); auto *VecTy = cast(Vector->getType()); unsigned VecSize = VecTy->getNumElements(); auto *NewVec = Builder.CreateVectorSplat(VecSize, Scalar, Scalar->getName()); NewVec->takeName(Scalar); auto *Scale = Builder.CreateFMul(Vector, NewVec, "scale"); return mapValue(BV, Scale); } case OpVectorTimesMatrix: { auto *VTM = static_cast(BV); IRBuilder<> Builder(BB); Value *Mat = transValue(VTM->getMatrix(), F, BB); Value *Vec = transValue(VTM->getVector(), F, BB); // Vec is of N elements. // Mat is of M columns and N rows. // Mat consists of vectors: V_1, V_2, ..., V_M // // The product is: // // |------- M ----------| // Result = sum ( {Vec_1, Vec_1, ..., Vec_1} * {V_1_1, V_2_1, ..., V_M_1}, // {Vec_2, Vec_2, ..., Vec_2} * {V_1_2, V_2_2, ..., V_M_2}, // ... // {Vec_N, Vec_N, ..., Vec_N} * {V_1_N, V_2_N, ..., V_M_N}); unsigned M = Mat->getType()->getArrayNumElements(); auto *VecTy = cast(Vec->getType()); FixedVectorType *VTy = FixedVectorType::get(VecTy->getElementType(), M); auto *ETy = VTy->getElementType(); unsigned N = VecTy->getNumElements(); Value *V = Builder.CreateVectorSplat(M, ConstantFP::get(ETy, 0.0)); for (unsigned Idx = 0; Idx != N; ++Idx) { Value *S = Builder.CreateExtractElement(Vec, Builder.getInt32(Idx)); Value *Lhs = Builder.CreateVectorSplat(M, S); Value *Rhs = PoisonValue::get(VTy); for (unsigned Idx2 = 0; Idx2 != M; ++Idx2) { Value *Vx = Builder.CreateExtractValue(Mat, Idx2); Value *Vxi = Builder.CreateExtractElement(Vx, Builder.getInt32(Idx)); Rhs = Builder.CreateInsertElement(Rhs, Vxi, Builder.getInt32(Idx2)); } Value *Mul = Builder.CreateFMul(Lhs, Rhs); V = Builder.CreateFAdd(V, Mul); } return mapValue(BV, V); } case OpMatrixTimesScalar: { auto *MTS = static_cast(BV); IRBuilder<> Builder(BB); auto *Scalar = transValue(MTS->getScalar(), F, BB); auto *Matrix = transValue(MTS->getMatrix(), F, BB); uint64_t ColNum = Matrix->getType()->getArrayNumElements(); auto *ColType = cast(Matrix->getType())->getElementType(); auto VecSize = cast(ColType)->getNumElements(); auto *NewVec = Builder.CreateVectorSplat(VecSize, Scalar, Scalar->getName()); NewVec->takeName(Scalar); Value *V = PoisonValue::get(Matrix->getType()); for (uint64_t Idx = 0; Idx != ColNum; Idx++) { auto *Col = Builder.CreateExtractValue(Matrix, Idx); auto *I = Builder.CreateFMul(Col, NewVec); V = Builder.CreateInsertValue(V, I, Idx); } return mapValue(BV, V); } case OpMatrixTimesVector: { auto *MTV = static_cast(BV); IRBuilder<> Builder(BB); Value *Mat = transValue(MTV->getMatrix(), F, BB); Value *Vec = transValue(MTV->getVector(), F, BB); // Result is similar to Matrix * Matrix // Mat is of M columns and N rows. // Mat consists of vectors: V_1, V_2, ..., V_M // where each vector is of size N. // // Vec is of size M. // The product is a vector of size N. // // |------- N ----------| // Result = sum ( {Vec_1, Vec_1, ..., Vec_1} * V_1, // {Vec_2, Vec_2, ..., Vec_2} * V_2, // ... // {Vec_M, Vec_M, ..., Vec_M} * V_N ); // // where sum is defined as vector sum. unsigned M = Mat->getType()->getArrayNumElements(); FixedVectorType *VTy = cast( cast(Mat->getType())->getElementType()); unsigned N = VTy->getNumElements(); auto *ETy = VTy->getElementType(); Value *V = Builder.CreateVectorSplat(N, ConstantFP::get(ETy, 0.0)); for (unsigned Idx = 0; Idx != M; ++Idx) { Value *S = Builder.CreateExtractElement(Vec, Builder.getInt32(Idx)); Value *Lhs = Builder.CreateVectorSplat(N, S); Value *Vx = Builder.CreateExtractValue(Mat, Idx); Value *Mul = Builder.CreateFMul(Lhs, Vx); V = Builder.CreateFAdd(V, Mul); } return mapValue(BV, V); } case OpMatrixTimesMatrix: { auto *MTM = static_cast(BV); IRBuilder<> Builder(BB); Value *M1 = transValue(MTM->getLeftMatrix(), F, BB); Value *M2 = transValue(MTM->getRightMatrix(), F, BB); // Each matrix consists of a list of columns. // M1 (the left matrix) is of C1 columns and R1 rows. // M1 consists of a list of vectors: V_1, V_2, ..., V_C1 // where V_x are vectors of size R1. // // M2 (the right matrix) is of C2 columns and R2 rows. // M2 consists of a list of vectors: U_1, U_2, ..., U_C2 // where U_x are vectors of size R2. // // Now M1 * M2 requires C1 == R2. // The result is a matrix of C2 columns and R1 rows. // That is, consists of C2 vectors of size R1. // // M1 * M2 algorithm is as below: // // Result = { dot_product(U_1, M1), // dot_product(U_2, M1), // ... // dot_product(U_C2, M1) }; // where // dot_product (U, M) is defined as: // // |-------- C1 ------| // Result = sum ( {U[1], U[1], ..., U[1]} * V_1, // {U[2], U[2], ..., U[2]} * V_2, // ... // {U[R2], U[R2], ..., U[R2]} * V_C1 ); // Note that C1 == R2 // sum is defined as vector sum. unsigned C1 = M1->getType()->getArrayNumElements(); unsigned C2 = M2->getType()->getArrayNumElements(); FixedVectorType *V1Ty = cast(cast(M1->getType())->getElementType()); FixedVectorType *V2Ty = cast(cast(M2->getType())->getElementType()); unsigned R1 = V1Ty->getNumElements(); unsigned R2 = V2Ty->getNumElements(); auto *ETy = V1Ty->getElementType(); (void)C1; assert(C1 == R2 && "Unmatched matrix"); auto *VTy = FixedVectorType::get(ETy, R1); auto *ResultTy = ArrayType::get(VTy, C2); Value *Res = PoisonValue::get(ResultTy); for (unsigned Idx = 0; Idx != C2; ++Idx) { Value *U = Builder.CreateExtractValue(M2, Idx); // Calculate dot_product(U, M1) Value *Dot = Builder.CreateVectorSplat(R1, ConstantFP::get(ETy, 0.0)); for (unsigned Idx2 = 0; Idx2 != R2; ++Idx2) { Value *Ux = Builder.CreateExtractElement(U, Builder.getInt32(Idx2)); Value *Lhs = Builder.CreateVectorSplat(R1, Ux); Value *Rhs = Builder.CreateExtractValue(M1, Idx2); Value *Mul = Builder.CreateFMul(Lhs, Rhs); Dot = Builder.CreateFAdd(Dot, Mul); } Res = Builder.CreateInsertValue(Res, Dot, Idx); } return mapValue(BV, Res); } case OpTranspose: { auto *TR = static_cast(BV); IRBuilder<> Builder(BB); auto *Matrix = transValue(TR->getMatrix(), F, BB); unsigned ColNum = Matrix->getType()->getArrayNumElements(); FixedVectorType *ColTy = cast( cast(Matrix->getType())->getElementType()); unsigned RowNum = ColTy->getNumElements(); auto *VTy = FixedVectorType::get(ColTy->getElementType(), ColNum); auto *ResultTy = ArrayType::get(VTy, RowNum); Value *V = PoisonValue::get(ResultTy); SmallVector MCache; MCache.reserve(ColNum); for (unsigned Idx = 0; Idx != ColNum; ++Idx) MCache.push_back(Builder.CreateExtractValue(Matrix, Idx)); if (ColNum == RowNum) { // Fastpath switch (ColNum) { case 2: { Value *V1 = Builder.CreateShuffleVector(MCache[0], MCache[1], ArrayRef{0, 2}); V = Builder.CreateInsertValue(V, V1, 0); Value *V2 = Builder.CreateShuffleVector(MCache[0], MCache[1], ArrayRef{1, 3}); V = Builder.CreateInsertValue(V, V2, 1); return mapValue(BV, V); } case 4: { for (int Idx = 0; Idx < 4; ++Idx) { Value *V1 = Builder.CreateShuffleVector(MCache[0], MCache[1], ArrayRef{Idx, Idx + 4}); Value *V2 = Builder.CreateShuffleVector(MCache[2], MCache[3], ArrayRef{Idx, Idx + 4}); Value *V3 = Builder.CreateShuffleVector(V1, V2, ArrayRef{0, 1, 2, 3}); V = Builder.CreateInsertValue(V, V3, Idx); } return mapValue(BV, V); } default: break; } } // Slowpath for (unsigned Idx = 0; Idx != RowNum; ++Idx) { Value *Vec = PoisonValue::get(VTy); for (unsigned Idx2 = 0; Idx2 != ColNum; ++Idx2) { Value *S = Builder.CreateExtractElement(MCache[Idx2], Builder.getInt32(Idx)); Vec = Builder.CreateInsertElement(Vec, S, Idx2); } V = Builder.CreateInsertValue(V, Vec, Idx); } return mapValue(BV, V); } case OpCopyObject: { SPIRVCopyObject *CO = static_cast(BV); auto *Ty = transType(CO->getOperand()->getType()); AllocaInst *AI = new AllocaInst(Ty, M->getDataLayout().getAllocaAddrSpace(), "", BB); new StoreInst(transValue(CO->getOperand(), F, BB), AI, BB); LoadInst *LI = new LoadInst(Ty, AI, "", BB); return mapValue(BV, LI); } case OpCopyLogical: { SPIRVCopyLogical *CL = static_cast(BV); auto *SrcTy = transType(CL->getOperand()->getType()); auto *DstTy = transType(CL->getType()); assert(M->getDataLayout().getTypeStoreSize(SrcTy).getFixedValue() == M->getDataLayout().getTypeStoreSize(DstTy).getFixedValue() && "Size mismatch in OpCopyLogical"); IRBuilder<> Builder(BB); auto *SrcAI = Builder.CreateAlloca(SrcTy); Builder.CreateAlignedStore(transValue(CL->getOperand(), F, BB), SrcAI, SrcAI->getAlign()); auto *LI = Builder.CreateAlignedLoad(DstTy, SrcAI, SrcAI->getAlign()); return mapValue(BV, LI); } case OpAccessChain: case OpInBoundsAccessChain: case OpPtrAccessChain: case OpInBoundsPtrAccessChain: case OpUntypedAccessChainKHR: case OpUntypedInBoundsAccessChainKHR: case OpUntypedPtrAccessChainKHR: case OpUntypedInBoundsPtrAccessChainKHR: { auto *AC = static_cast(BV); auto *Base = transValue(AC->getBase(), F, BB); SPIRVType *BaseSPVTy = AC->getBaseType(); if ((BaseSPVTy->isTypePointer() && BaseSPVTy->getPointerElementType()->isTypeCooperativeMatrixKHR()) || (isUntypedAccessChainOpCode(OC) && BaseSPVTy->isTypeCooperativeMatrixKHR())) { return mapValue(BV, transSPIRVBuiltinFromInst(AC, BB)); } Type *BaseTy = BaseSPVTy->isTypeVector() ? transType( BaseSPVTy->getVectorComponentType()->getPointerElementType()) : BaseSPVTy->isTypePointer() ? transType(BaseSPVTy->getPointerElementType()) : transType(BaseSPVTy); auto Index = transValue(AC->getIndices(), F, BB); if (!AC->hasPtrIndex()) Index.insert(Index.begin(), getInt32(M, 0)); auto IsInbound = AC->isInBounds(); Value *V = nullptr; if (GEPOrUseMap.count(Base)) { auto IdxToInstMap = GEPOrUseMap[Base]; auto Idx = AC->getIndices(); // In transIntelFPGADecorations we generated GEPs only for the fields of // structure, meaning that GEP to `0` accesses the Structure itself, and // the second `Id` is a Key in the map. if (Idx.size() == 2) { unsigned Idx1 = static_cast(getTranslatedValue(Idx[0])) ->getZExtValue(); if (Idx1 == 0) { unsigned Idx2 = static_cast(getTranslatedValue(Idx[1])) ->getZExtValue(); // If we already have the instruction in a map, use it. if (IdxToInstMap.count(Idx2)) return mapValue(BV, IdxToInstMap[Idx2]); } } } if (BB) { auto *GEP = GetElementPtrInst::Create(BaseTy, Base, Index, BV->getName(), BB); GEP->setIsInBounds(IsInbound); V = GEP; } else { auto *CT = cast(Base); V = ConstantExpr::getGetElementPtr(BaseTy, CT, Index, IsInbound); } return mapValue(BV, V); } case OpPtrEqual: case OpPtrNotEqual: { auto *BC = static_cast(BV); auto Ops = transValue(BC->getOperands(), F, BB); IRBuilder<> Builder(BB); Value *Op1 = Builder.CreatePtrToInt(Ops[0], Type::getInt64Ty(*Context)); Value *Op2 = Builder.CreatePtrToInt(Ops[1], Type::getInt64Ty(*Context)); CmpInst::Predicate P = OC == OpPtrEqual ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE; Value *V = Builder.CreateICmp(P, Op1, Op2); return mapValue(BV, V); } case OpPtrDiff: { auto *BC = static_cast(BV); auto SPVOps = BC->getOperands(); auto Ops = transValue(SPVOps, F, BB); IRBuilder<> Builder(BB); Type *ElemTy = nullptr; if (SPVOps[0]->isUntypedVariable()) ElemTy = transType( static_cast(SPVOps[0])->getDataType()); else ElemTy = transType(SPVOps[0]->getType()->getPointerElementType()); Value *V = Builder.CreatePtrDiff(ElemTy, Ops[0], Ops[1]); return mapValue(BV, V); } case OpCompositeConstruct: { auto *CC = static_cast(BV); auto Constituents = transValue(CC->getOperands(), F, BB); std::vector CV; bool HasRtValues = false; for (const auto &I : Constituents) { auto *C = dyn_cast(I); CV.push_back(C); if (!HasRtValues && C == nullptr) HasRtValues = true; } switch (static_cast(BV->getType()->getOpCode())) { case OpTypeVector: { if (!HasRtValues) return mapValue(BV, ConstantVector::get(CV)); auto *VT = cast(transType(CC->getType())); Value *NewVec = ConstantVector::getSplat( VT->getElementCount(), PoisonValue::get(VT->getElementType())); for (size_t I = 0; I < Constituents.size(); I++) { NewVec = InsertElementInst::Create(NewVec, Constituents[I], getInt32(M, I), "", BB); } return mapValue(BV, NewVec); } case OpTypeArray: { auto *AT = cast(transType(CC->getType())); if (!HasRtValues) return mapValue(BV, ConstantArray::get(AT, CV)); AllocaInst *Alloca = new AllocaInst(AT, M->getDataLayout().getAllocaAddrSpace(), "", BB); // get pointer to the element of the array // store the result of argument for (size_t I = 0; I < Constituents.size(); I++) { auto *GEP = GetElementPtrInst::Create( AT, Alloca, {getInt32(M, 0), getInt32(M, I)}, "gep", BB); GEP->setIsInBounds(true); new StoreInst(Constituents[I], GEP, false, BB); } auto *Load = new LoadInst(AT, Alloca, "load", false, BB); return mapValue(BV, Load); } case OpTypeStruct: { auto *ST = cast(transType(CC->getType())); if (!HasRtValues) return mapValue(BV, ConstantStruct::get(ST, CV)); AllocaInst *Alloca = new AllocaInst(ST, M->getDataLayout().getAllocaAddrSpace(), "", BB); // get pointer to the element of structure // store the result of argument for (size_t I = 0; I < Constituents.size(); I++) { auto *GEP = GetElementPtrInst::Create( ST, Alloca, {getInt32(M, 0), getInt32(M, I)}, "gep", BB); GEP->setIsInBounds(true); new StoreInst(Constituents[I], GEP, false, BB); } auto *Load = new LoadInst(ST, Alloca, "load", false, BB); return mapValue(BV, Load); } case internal::OpTypeJointMatrixINTEL: case OpTypeCooperativeMatrixKHR: case internal::OpTypeTaskSequenceINTEL: return mapValue(BV, transSPIRVBuiltinFromInst(CC, BB)); default: llvm_unreachable("Unhandled type!"); } } case OpCompositeExtract: { SPIRVCompositeExtract *CE = static_cast(BV); IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } if (CE->getComposite()->getType()->isTypeVector()) { assert(CE->getIndices().size() == 1 && "Invalid index"); return mapValue( BV, Builder.CreateExtractElement( transValue(CE->getComposite(), F, BB), ConstantInt::get(*Context, APInt(32, CE->getIndices()[0])), BV->getName())); } return mapValue( BV, Builder.CreateExtractValue(transValue(CE->getComposite(), F, BB), CE->getIndices(), BV->getName())); } case OpVectorExtractDynamic: { auto *VED = static_cast(BV); SPIRVValue *Vec = VED->getVector(); if (Vec->getType()->getOpCode() == internal::OpTypeJointMatrixINTEL) { return mapValue(BV, transSPIRVBuiltinFromInst(VED, BB)); } return mapValue( BV, ExtractElementInst::Create(transValue(Vec, F, BB), transValue(VED->getIndex(), F, BB), BV->getName(), BB)); } case OpCompositeInsert: { auto *CI = static_cast(BV); IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } if (CI->getComposite()->getType()->isTypeVector()) { assert(CI->getIndices().size() == 1 && "Invalid index"); return mapValue( BV, Builder.CreateInsertElement( transValue(CI->getComposite(), F, BB), transValue(CI->getObject(), F, BB), ConstantInt::get(*Context, APInt(32, CI->getIndices()[0])), BV->getName())); } return mapValue( BV, Builder.CreateInsertValue(transValue(CI->getComposite(), F, BB), transValue(CI->getObject(), F, BB), CI->getIndices(), BV->getName())); } case OpVectorInsertDynamic: { auto *VID = static_cast(BV); SPIRVValue *Vec = VID->getVector(); if (Vec->getType()->getOpCode() == internal::OpTypeJointMatrixINTEL) { return mapValue(BV, transSPIRVBuiltinFromInst(VID, BB)); } return mapValue( BV, InsertElementInst::Create( transValue(Vec, F, BB), transValue(VID->getComponent(), F, BB), transValue(VID->getIndex(), F, BB), BV->getName(), BB)); } case OpVectorShuffle: { auto *VS = static_cast(BV); std::vector Components; IntegerType *Int32Ty = IntegerType::get(*Context, 32); for (auto I : VS->getComponents()) { if (I == static_cast(-1)) Components.push_back(PoisonValue::get(Int32Ty)); else Components.push_back(ConstantInt::get(Int32Ty, I)); } IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } Value *Vec1 = transValue(VS->getVector1(), F, BB); Value *Vec2 = transValue(VS->getVector2(), F, BB); auto *Vec1Ty = cast(Vec1->getType()); auto *Vec2Ty = cast(Vec2->getType()); if (Vec1Ty->getNumElements() != Vec2Ty->getNumElements()) { // LLVM's shufflevector requires that the two vector operands have the // same type; SPIR-V's OpVectorShuffle allows the vector operands to // differ in the number of components. Adjust for that by extending // the smaller vector. if (Vec1Ty->getNumElements() < Vec2Ty->getNumElements()) { Vec1 = extendVector(Vec1, Vec2Ty, Builder); // Extending Vec1 requires offsetting any Vec2 indices in Components by // the number of new elements. unsigned Offset = Vec2Ty->getNumElements() - Vec1Ty->getNumElements(); unsigned Vec2Start = Vec1Ty->getNumElements(); for (auto &C : Components) { if (auto *CI = dyn_cast(C)) { uint64_t V = CI->getZExtValue(); if (V >= Vec2Start) { // This is a Vec2 index; add the offset to it. C = ConstantInt::get(Int32Ty, V + Offset); } } } } else { Vec2 = extendVector(Vec2, Vec1Ty, Builder); } } return mapValue( BV, Builder.CreateShuffleVector( Vec1, Vec2, ConstantVector::get(Components), BV->getName())); } case OpBitReverse: { auto *BR = static_cast(BV); auto *Ty = transType(BV->getType()); Function *intr = Intrinsic::getOrInsertDeclaration(M, llvm::Intrinsic::bitreverse, Ty); auto *Call = CallInst::Create(intr, transValue(BR->getOperand(0), F, BB), BR->getName(), BB); return mapValue(BV, Call); } case OpFunctionCall: { SPIRVFunctionCall *BC = static_cast(BV); std::vector Args = transValue(BC->getArgumentValues(), F, BB); auto *Call = CallInst::Create(transFunction(BC->getFunction()), Args, BC->getName(), BB); setCallingConv(Call); setAttrByCalledFunc(Call); return mapValue(BV, Call); } case OpAsmCallINTEL: return mapValue( BV, transAsmCallINTEL(static_cast(BV), F, BB)); case OpFunctionPointerCallINTEL: { SPIRVFunctionPointerCallINTEL *BC = static_cast(BV); auto *V = transValue(BC->getCalledValue(), F, BB); auto *SpirvFnTy = BC->getCalledValue()->getType()->getPointerElementType(); auto *FnTy = cast(transType(SpirvFnTy)); auto *Call = CallInst::Create( FnTy, V, transValue(BC->getArgumentValues(), F, BB), BC->getName(), BB); transFunctionPointerCallArgumentAttributes( BV, Call, static_cast(SpirvFnTy)); // Assuming we are calling a regular device function Call->setCallingConv(CallingConv::SPIR_FUNC); // Don't set attributes, because at translation time we don't know which // function exactly we are calling. return mapValue(BV, Call); } case OpAssumeTrueKHR: { IRBuilder<> Builder(BB); SPIRVAssumeTrueKHR *BC = static_cast(BV); Value *Condition = transValue(BC->getCondition(), F, BB); return mapValue(BV, Builder.CreateAssumption(Condition)); } case OpExpectKHR: { IRBuilder<> Builder(BB); SPIRVExpectKHRInstBase *BC = static_cast(BV); Type *RetTy = transType(BC->getType()); Value *Val = transValue(BC->getOperand(0), F, BB); Value *ExpVal = transValue(BC->getOperand(1), F, BB); return mapValue( BV, Builder.CreateIntrinsic(Intrinsic::expect, RetTy, {Val, ExpVal})); } case OpUntypedPrefetchKHR: { // Do the same as transOCLBuiltinFromExtInst() but for OpUntypedPrefetchKHR. auto *BC = static_cast(BV); std::vector ArgTypes = transTypeVector(BC->getValueTypes(BC->getArguments()), true); Type *RetTy = Type::getVoidTy(*Context); std::string MangledName = getSPIRVFriendlyIRFunctionName(OpenCLLIB::Prefetch, ArgTypes, RetTy); opaquifyTypedPointers(ArgTypes); FunctionType *FT = FunctionType::get(RetTy, ArgTypes, false); Function *F = M->getFunction(MangledName); if (!F) { F = Function::Create(FT, GlobalValue::ExternalLinkage, MangledName, M); F->setCallingConv(CallingConv::SPIR_FUNC); if (isFuncNoUnwind()) F->addFnAttr(Attribute::NoUnwind); if (isFuncReadNone(OCLExtOpMap::map(OpenCLLIB::Prefetch))) F->setDoesNotAccessMemory(); } auto Args = transValue(BC->getValues(BC->getArguments()), F, BB); CallInst *CI = CallInst::Create(F, Args, BC->getName(), BB); setCallingConv(CI); addFnAttr(CI, Attribute::NoUnwind); return mapValue(BV, CI); } case OpExtInst: { auto *ExtInst = static_cast(BV); switch (ExtInst->getExtSetKind()) { case SPIRVEIS_OpenCL: { auto *V = mapValue(BV, transOCLBuiltinFromExtInst(ExtInst, BB)); applyFPFastMathModeDecorations(BV, static_cast(V)); return V; } case SPIRVEIS_Debug: case SPIRVEIS_OpenCL_DebugInfo_100: case SPIRVEIS_NonSemantic_Shader_DebugInfo_100: case SPIRVEIS_NonSemantic_Shader_DebugInfo_200: DbgTran->transDebugIntrinsic(ExtInst, BB); return mapValue(BV, nullptr); default: llvm_unreachable("Unknown extended instruction set!"); } } case OpSNegate: { IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } SPIRVUnary *BC = static_cast(BV); if (BV->getType()->isTypeCooperativeMatrixKHR()) { return mapValue(BV, transSPIRVBuiltinFromInst(BC, BB)); } auto *Neg = Builder.CreateNeg(transValue(BC->getOperand(0), F, BB), BV->getName()); if (auto *NegInst = dyn_cast(Neg)) { applyNoIntegerWrapDecorations(BV, NegInst); } return mapValue(BV, Neg); } case OpFMod: { // translate OpFMod(a, b) to: // r = frem(a, b) // c = copysign(r, b) // needs_fixing = islessgreater(r, c) // result = needs_fixing ? r + b : c IRBuilder<> Builder(BB); SPIRVFMod *FMod = static_cast(BV); auto *Dividend = transValue(FMod->getOperand(0), F, BB); auto *Divisor = transValue(FMod->getOperand(1), F, BB); auto *FRem = Builder.CreateFRem(Dividend, Divisor, "frem.res"); auto *CopySign = Builder.CreateBinaryIntrinsic( llvm::Intrinsic::copysign, FRem, Divisor, nullptr, "copysign.res"); auto *FAdd = Builder.CreateFAdd(FRem, Divisor, "fadd.res"); auto *Cmp = Builder.CreateFCmpONE(FRem, CopySign, "cmp.res"); auto *Select = Builder.CreateSelect(Cmp, FAdd, CopySign); return mapValue(BV, Select); } case OpSMod: { // translate OpSMod(a, b) to: // r = srem(a, b) // needs_fixing = ((a < 0) != (b < 0) && r != 0) // result = needs_fixing ? r + b : r IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } SPIRVSMod *SMod = static_cast(BV); auto *Dividend = transValue(SMod->getOperand(0), F, BB); auto *Divisor = transValue(SMod->getOperand(1), F, BB); auto *SRem = Builder.CreateSRem(Dividend, Divisor, "srem.res"); auto *Xor = Builder.CreateXor(Dividend, Divisor, "xor.res"); auto *Zero = ConstantInt::getNullValue(Dividend->getType()); auto *CmpSign = Builder.CreateICmpSLT(Xor, Zero, "cmpsign.res"); auto *CmpSRem = Builder.CreateICmpNE(SRem, Zero, "cmpsrem.res"); auto *Add = Builder.CreateNSWAdd(SRem, Divisor, "add.res"); auto *Cmp = Builder.CreateAnd(CmpSign, CmpSRem, "cmp.res"); auto *Select = Builder.CreateSelect(Cmp, Add, SRem); return mapValue(BV, Select); } case OpFNegate: { SPIRVUnary *BC = static_cast(BV); if (BV->getType()->isTypeCooperativeMatrixKHR()) { return mapValue(BV, transSPIRVBuiltinFromInst(BC, BB)); } auto *Neg = UnaryOperator::CreateFNeg(transValue(BC->getOperand(0), F, BB), BV->getName(), BB); applyFPFastMathModeDecorations(BV, Neg); return mapValue(BV, Neg); } case OpNot: case OpLogicalNot: { IRBuilder<> Builder(*Context); if (BB) { Builder.SetInsertPoint(BB); } SPIRVUnary *BC = static_cast(BV); return mapValue(BV, Builder.CreateNot(transValue(BC->getOperand(0), F, BB), BV->getName())); } case OpAll: case OpAny: return mapValue(BV, transAllAny(static_cast(BV), BB)); case OpIsFinite: case OpIsInf: case OpIsNan: case OpIsNormal: case OpSignBitSet: return mapValue(BV, transRelational(static_cast(BV), BB)); case OpIAddCarry: case OpISubBorrow: { IRBuilder Builder(BB); auto *BC = static_cast(BV); Intrinsic::ID ID = OC == OpIAddCarry ? Intrinsic::uadd_with_overflow : Intrinsic::usub_with_overflow; auto *Inst = Builder.CreateBinaryIntrinsic(ID, transValue(BC->getOperand(0), F, BB), transValue(BC->getOperand(1), F, BB)); // Extract components of the result. auto *Result = Builder.CreateExtractValue(Inst, 0); // iN result auto *Carry = Builder.CreateExtractValue(Inst, 1); // i1 overflow // Convert {iN, i1} into {iN, iN} for SPIR-V compatibility. Value *CarryInt; if (Carry->getType()->isVectorTy()) { CarryInt = Builder.CreateZExt( Carry, VectorType::get( cast(Result->getType())->getElementType(), cast(Carry->getType())->getElementCount())); } else { CarryInt = Builder.CreateZExt(Carry, Result->getType()); } auto *ResultStruct = Builder.CreateInsertValue(PoisonValue::get(StructType::get( Result->getType(), CarryInt->getType())), Result, 0); ResultStruct = Builder.CreateInsertValue(ResultStruct, CarryInt, 1); return mapValue(BV, ResultStruct); } case OpSMulExtended: { auto *BC = static_cast(BV); return mapValue(BV, transBuiltinFromInst("__spirv_SMulExtended", BC, BB)); } case OpUMulExtended: { auto *BC = static_cast(BV); return mapValue(BV, transBuiltinFromInst("__spirv_UMulExtended", BC, BB)); } case OpGetKernelWorkGroupSize: case OpGetKernelPreferredWorkGroupSizeMultiple: return mapValue( BV, transWGSizeQueryBI(static_cast(BV), BB)); case OpGetKernelNDrangeMaxSubGroupSize: case OpGetKernelNDrangeSubGroupCount: return mapValue( BV, transSGSizeQueryBI(static_cast(BV), BB)); case OpFPGARegINTEL: { IRBuilder<> Builder(BB); SPIRVFPGARegINTELInstBase *BC = static_cast(BV); PointerType *Int8PtrTyPrivate = PointerType::get(*Context, SPIRAS_Private); IntegerType *Int32Ty = Type::getInt32Ty(*Context); Value *UndefInt8Ptr = PoisonValue::get(Int8PtrTyPrivate); Value *UndefInt32 = PoisonValue::get(Int32Ty); Constant *GS = Builder.CreateGlobalString(kOCLBuiltinName::FPGARegIntel); Type *Ty = transType(BC->getType()); Value *Val = transValue(BC->getOperand(0), F, BB); Value *ValAsArg = Val; Type *RetTy = Ty; auto IID = Intrinsic::annotation; if (!isa(Ty)) { // All scalar types can be bitcasted to a same-sized integer if (!isa(Ty) && !isa(Ty)) { RetTy = IntegerType::get(*Context, Ty->getPrimitiveSizeInBits()); ValAsArg = Builder.CreateBitCast(Val, RetTy); } // If pointer type or struct type else { IID = Intrinsic::ptr_annotation; auto *PtrTy = dyn_cast(Ty); if (PtrTy) { RetTy = PtrTy; } else { // If a struct - bitcast to i8* RetTy = Int8PtrTyPrivate; ValAsArg = Builder.CreateBitCast(Val, RetTy); } Value *Args[] = {ValAsArg, GS, UndefInt8Ptr, UndefInt32, UndefInt8Ptr}; auto *IntrinsicCall = Builder.CreateIntrinsic(IID, {RetTy, GS->getType()}, Args); return mapValue(BV, IntrinsicCall); } } Value *Args[] = {ValAsArg, GS, UndefInt8Ptr, UndefInt32}; auto *IntrinsicCall = Builder.CreateIntrinsic(IID, {RetTy, GS->getType()}, Args); return mapValue(BV, IntrinsicCall); } case OpFixedSqrtINTEL: case OpFixedRecipINTEL: case OpFixedRsqrtINTEL: case OpFixedSinINTEL: case OpFixedCosINTEL: case OpFixedSinCosINTEL: case OpFixedSinPiINTEL: case OpFixedCosPiINTEL: case OpFixedSinCosPiINTEL: case OpFixedLogINTEL: case OpFixedExpINTEL: return mapValue( BV, transFixedPointInst(static_cast(BV), BB)); case OpArbitraryFloatCastINTEL: case OpArbitraryFloatCastFromIntINTEL: case OpArbitraryFloatCastToIntINTEL: case OpArbitraryFloatRecipINTEL: case OpArbitraryFloatRSqrtINTEL: case OpArbitraryFloatCbrtINTEL: case OpArbitraryFloatSqrtINTEL: case OpArbitraryFloatLogINTEL: case OpArbitraryFloatLog2INTEL: case OpArbitraryFloatLog10INTEL: case OpArbitraryFloatLog1pINTEL: case OpArbitraryFloatExpINTEL: case OpArbitraryFloatExp2INTEL: case OpArbitraryFloatExp10INTEL: case OpArbitraryFloatExpm1INTEL: case OpArbitraryFloatSinINTEL: case OpArbitraryFloatCosINTEL: case OpArbitraryFloatSinCosINTEL: case OpArbitraryFloatSinPiINTEL: case OpArbitraryFloatCosPiINTEL: case OpArbitraryFloatSinCosPiINTEL: case OpArbitraryFloatASinINTEL: case OpArbitraryFloatASinPiINTEL: case OpArbitraryFloatACosINTEL: case OpArbitraryFloatACosPiINTEL: case OpArbitraryFloatATanINTEL: case OpArbitraryFloatATanPiINTEL: return mapValue(BV, transArbFloatInst(static_cast(BV), BB)); case OpArbitraryFloatAddINTEL: case OpArbitraryFloatSubINTEL: case OpArbitraryFloatMulINTEL: case OpArbitraryFloatDivINTEL: case OpArbitraryFloatGTINTEL: case OpArbitraryFloatGEINTEL: case OpArbitraryFloatLTINTEL: case OpArbitraryFloatLEINTEL: case OpArbitraryFloatEQINTEL: case OpArbitraryFloatHypotINTEL: case OpArbitraryFloatATan2INTEL: case OpArbitraryFloatPowINTEL: case OpArbitraryFloatPowRINTEL: case OpArbitraryFloatPowNINTEL: return mapValue( BV, transArbFloatInst(static_cast(BV), BB, true)); case OpArithmeticFenceEXT: { IRBuilder<> Builder(BB); auto *BC = static_cast(BV); Type *RetTy = transType(BC->getType()); Value *Val = transValue(BC->getOperand(0), F, BB); return mapValue( BV, Builder.CreateIntrinsic(Intrinsic::arithmetic_fence, RetTy, Val)); } case OpFmaKHR: { IRBuilder<> Builder(BB); auto *BC = static_cast(BV); return mapValue( BV, Builder.CreateIntrinsic(Intrinsic::fma, transType(BC->getType()), {transValue(BC->getOperand(0), F, BB), transValue(BC->getOperand(1), F, BB), transValue(BC->getOperand(2), F, BB)})); } case internal::OpMaskedGatherINTEL: { IRBuilder<> Builder(BB); auto *Inst = static_cast(BV); Type *RetTy = transType(Inst->getType()); Value *PtrVector = transValue(Inst->getOperand(0), F, BB); uint32_t Alignment = Inst->getOpWord(1); Value *Mask = transValue(Inst->getOperand(2), F, BB); Value *FillEmpty = transValue(Inst->getOperand(3), F, BB); return mapValue(BV, Builder.CreateMaskedGather(RetTy, PtrVector, Align(Alignment), Mask, FillEmpty)); } case internal::OpMaskedScatterINTEL: { IRBuilder<> Builder(BB); auto *Inst = static_cast(BV); Value *InputVector = transValue(Inst->getOperand(0), F, BB); Value *PtrVector = transValue(Inst->getOperand(1), F, BB); uint32_t Alignment = Inst->getOpWord(2); Value *Mask = transValue(Inst->getOperand(3), F, BB); return mapValue(BV, Builder.CreateMaskedScatter(InputVector, PtrVector, Align(Alignment), Mask)); } default: { auto OC = BV->getOpCode(); if (isCmpOpCode(OC)) return mapValue(BV, transCmpInst(BV, BB, F)); if (OCLSPIRVBuiltinMap::rfind(OC, nullptr)) return mapValue(BV, transSPIRVBuiltinFromInst( static_cast(BV), BB)); if (isBinaryShiftLogicalBitwiseOpCode(OC) || isLogicalOpCode(OC)) return mapValue(BV, transShiftLogicalBitwiseInst(BV, BB, F)); if (isCvtOpCode(OC) && OC != OpGenericCastToPtrExplicit) { auto *BI = static_cast(BV); Value *Inst = nullptr; if (BI->hasFPRoundingMode() || BI->isSaturatedConversion()) { Inst = transSPIRVBuiltinFromInst(BI, BB); } else if (BI->getType()->isTypeCooperativeMatrixKHR()) { // For cooperative matrix conversions generate __builtin_spirv // conversions instead of __spirv_FConvert in case of mini-float // type element type. auto *OutMatrixElementTy = static_cast(BI->getType()) ->getCompType(); auto *InMatrixElementTy = static_cast( static_cast(BI)->getOperand(0)->getType()) ->getCompType(); if (OutMatrixElementTy->isTypeFloat(8, FPEncodingFloat8E4M3EXT) || OutMatrixElementTy->isTypeFloat(8, FPEncodingFloat8E5M2EXT) || InMatrixElementTy->isTypeFloat(8, FPEncodingFloat8E4M3EXT) || InMatrixElementTy->isTypeFloat(8, FPEncodingFloat8E5M2EXT) || OutMatrixElementTy->isTypeFloat(4, FPEncodingFloat4E2M1EXT) || InMatrixElementTy->isTypeFloat(4, FPEncodingFloat4E2M1EXT) || OutMatrixElementTy->isTypeFloat( 4, internal::FPEncodingFloat4E2M1INTEL) || InMatrixElementTy->isTypeFloat(4, internal::FPEncodingFloat4E2M1INTEL)) Inst = transConvertInst(BV, F, BB); else Inst = transSPIRVBuiltinFromInst(BI, BB); } else { Inst = transConvertInst(BV, F, BB); } return mapValue(BV, Inst); } if (isIntelCvtOpCode(OC)) return mapValue(BV, transConvertInst(BV, F, BB)); return mapValue( BV, transSPIRVBuiltinFromInst(static_cast(BV), BB)); } } } // Get meaningful suffix for adding at the end of the function name to avoid // ascending numerical suffixes. It is useful in situations, where the same // function is called twice or more in one basic block. So, the function name is // formed in the following way: [FuncName].[ReturnTy].[InputTy] static std::string getFuncAPIntSuffix(const Type *RetTy, const Type *In1Ty, const Type *In2Ty = nullptr) { std::stringstream Suffix; Suffix << ".i" << RetTy->getIntegerBitWidth() << ".i" << In1Ty->getIntegerBitWidth(); if (In2Ty) Suffix << ".i" << In2Ty->getIntegerBitWidth(); return Suffix.str(); } Value *SPIRVToLLVM::transFixedPointInst(SPIRVInstruction *BI, BasicBlock *BB) { // LLVM fixed point functions return value: // iN (arbitrary precision integer of N bits length) // Arguments: // A(iN), S(i1), I(i32), rI(i32), Quantization(i32), Overflow(i32) // If return value wider than 64 bit: // iN addrspace(4)* sret(iN), A(iN), S(i1), I(i32), rI(i32), // Quantization(i32), Overflow(i32) // SPIR-V fixed point instruction contains: // ResTy Res In Literal S Literal I Literal rI Literal Q Literal O Type *RetTy = transType(BI->getType()); auto *Inst = static_cast(BI); Type *InTy = transType(Inst->getOperand(0)->getType()); IntegerType *Int32Ty = IntegerType::get(*Context, 32); IntegerType *Int1Ty = IntegerType::get(*Context, 1); SmallVector ArgTys; std::vector Args; Args.reserve(8); if (RetTy->getIntegerBitWidth() > 64) { llvm::PointerType *RetPtrTy = llvm::PointerType::get(*Context, SPIRAS_Generic); Value *Alloca = new AllocaInst(RetTy, M->getDataLayout().getAllocaAddrSpace(), "", BB); Value *RetValPtr = new AddrSpaceCastInst(Alloca, RetPtrTy, "", BB); ArgTys.emplace_back(RetPtrTy); Args.emplace_back(RetValPtr); } ArgTys.insert(ArgTys.end(), {InTy, Int1Ty, Int32Ty, Int32Ty, Int32Ty, Int32Ty}); auto Words = Inst->getOpWords(); Args.emplace_back(transValue(Inst->getOperand(0), BB->getParent(), BB)); Args.emplace_back(ConstantInt::get(Int1Ty, Words[1])); for (int I = 2; I <= 5; I++) Args.emplace_back(ConstantInt::get(Int32Ty, Words[I])); Type *FuncRetTy = (RetTy->getIntegerBitWidth() <= 64) ? RetTy : Type::getVoidTy(*Context); FunctionType *FT = FunctionType::get(FuncRetTy, ArgTys, false); Op OpCode = Inst->getOpCode(); std::string FuncName = SPIRVFixedPointIntelMap::rmap(OpCode) + getFuncAPIntSuffix(RetTy, InTy); FunctionCallee FCallee = M->getOrInsertFunction(FuncName, FT); auto *Func = cast(FCallee.getCallee()); Func->setCallingConv(CallingConv::SPIR_FUNC); if (isFuncNoUnwind()) Func->addFnAttr(Attribute::NoUnwind); if (RetTy->getIntegerBitWidth() <= 64) return CallInst::Create(FCallee, Args, "", BB); Func->addParamAttr( 0, Attribute::get(*Context, Attribute::AttrKind::StructRet, RetTy)); CallInst *APIntInst = CallInst::Create(FCallee, Args, "", BB); APIntInst->addParamAttr( 0, Attribute::get(*Context, Attribute::AttrKind::StructRet, RetTy)); return static_cast(new LoadInst(RetTy, Args[0], "", false, BB)); } Value *SPIRVToLLVM::transArbFloatInst(SPIRVInstruction *BI, BasicBlock *BB, bool IsBinaryInst) { // Format of instructions Add, Sub, Mul, Div, Hypot, ATan2, Pow, PowR: // LLVM arbitrary floating point functions return value: // iN (arbitrary precision integer of N bits length) // Arguments: A(iN), MA(i32), B(iN), MB(i32), Mout(i32), // EnableSubnormals(i32), RoundingMode(i32), // RoundingAccuracy(i32) // where A, B and return values are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal MA B Literal MB Literal Mout // Literal EnableSubnormals Literal RoundingMode // Literal RoundingAccuracy // Format of instructions GT, GE, LT, LE, EQ: // LLVM arbitrary floating point functions return value: Bool // Arguments: A(iN), MA(i32), B(iN), MB(i32) // where A and B are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal MA B Literal MB // Format of instruction PowN: // LLVM arbitrary floating point functions return value: iN // Arguments: A(iN), MA(i32), B(iN), SignOfB(i1), Mout(i32), // EnableSubnormals(i32), RoundingMode(i32), // RoundingAccuracy(i32) // where A, B and return values are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal MA B Literal SignOfB Literal Mout // Literal EnableSubnormals Literal RoundingMode // Literal RoundingAccuracy // Format of instruction CastFromInt: // LLVM arbitrary floating point functions return value: iN // Arguments: A(iN), Mout(i32), FromSign(bool), EnableSubnormals(i32), // RoundingMode(i32), RoundingAccuracy(i32) // where A and return values are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal Mout Literal FromSign // Literal EnableSubnormals Literal RoundingMode // Literal RoundingAccuracy // Format of instruction CastToInt: // LLVM arbitrary floating point functions return value: iN // Arguments: A(iN), MA(i32), ToSign(bool), EnableSubnormals(i32), // RoundingMode(i32), RoundingAccuracy(i32) // where A and return values are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal MA Literal ToSign // Literal EnableSubnormals Literal RoundingMode // Literal RoundingAccuracy // Format of other instructions: // LLVM arbitrary floating point functions return value: iN // Arguments: A(iN), MA(i32), Mout(i32), EnableSubnormals(i32), // RoundingMode(i32), RoundingAccuracy(i32) // where A and return values are of arbitrary precision integer type. // SPIR-V arbitrary floating point instruction layout: // ResTy Res A Literal MA Literal Mout Literal EnableSubnormals // Literal RoundingMode Literal RoundingAccuracy Type *RetTy = transType(BI->getType()); IntegerType *Int1Ty = Type::getInt1Ty(*Context); IntegerType *Int32Ty = Type::getInt32Ty(*Context); auto *Inst = static_cast(BI); Type *ATy = transType(Inst->getOperand(0)->getType()); Type *BTy = nullptr; // Words contain: // A [Literal MA] [B] [Literal MB] [Literal Mout] [Literal Sign] // [Literal EnableSubnormals Literal RoundingMode Literal RoundingAccuracy] const std::vector Words = Inst->getOpWords(); auto WordsItr = Words.begin() + 1; /* Skip word for A input id */ SmallVector ArgTys; std::vector Args; if (RetTy->getIntegerBitWidth() > 64) { llvm::PointerType *RetPtrTy = llvm::PointerType::get(*Context, SPIRAS_Generic); ArgTys.push_back(RetPtrTy); Value *Alloca = new AllocaInst(RetTy, M->getDataLayout().getAllocaAddrSpace(), "", BB); Value *RetValPtr = new AddrSpaceCastInst(Alloca, RetPtrTy, "", BB); Args.push_back(RetValPtr); } ArgTys.insert(ArgTys.end(), {ATy, Int32Ty}); // A - input Args.emplace_back(transValue(Inst->getOperand(0), BB->getParent(), BB)); // MA/Mout - width of mantissa Args.emplace_back(ConstantInt::get(Int32Ty, *WordsItr++)); Op OC = Inst->getOpCode(); if (OC == OpArbitraryFloatCastFromIntINTEL || OC == OpArbitraryFloatCastToIntINTEL) { ArgTys.push_back(Int1Ty); Args.push_back(ConstantInt::get(Int1Ty, *WordsItr++)); /* ToSign/FromSign */ } if (IsBinaryInst) { /* B - input */ BTy = transType(Inst->getOperand(2)->getType()); ArgTys.push_back(BTy); Args.push_back(transValue(Inst->getOperand(2), BB->getParent(), BB)); ++WordsItr; /* Skip word for B input id */ if (OC == OpArbitraryFloatPowNINTEL) { ArgTys.push_back(Int1Ty); Args.push_back(ConstantInt::get(Int1Ty, *WordsItr++)); /* SignOfB */ } } std::fill_n(std::back_inserter(ArgTys), Words.end() - WordsItr, Int32Ty); std::transform(WordsItr, Words.end(), std::back_inserter(Args), [Int32Ty](const SPIRVWord &Word) { return ConstantInt::get(Int32Ty, Word); }); std::string FuncName = SPIRVArbFloatIntelMap::rmap(OC) + getFuncAPIntSuffix(RetTy, ATy, BTy); Type *FuncRetTy = (RetTy->getIntegerBitWidth() <= 64) ? RetTy : Type::getVoidTy(*Context); FunctionType *FT = FunctionType::get(FuncRetTy, ArgTys, false); FunctionCallee FCallee = M->getOrInsertFunction(FuncName, FT); auto *Func = cast(FCallee.getCallee()); Func->setCallingConv(CallingConv::SPIR_FUNC); if (isFuncNoUnwind()) Func->addFnAttr(Attribute::NoUnwind); if (RetTy->getIntegerBitWidth() <= 64) return CallInst::Create(Func, Args, "", BB); Func->addParamAttr( 0, Attribute::get(*Context, Attribute::AttrKind::StructRet, RetTy)); CallInst *APFloatInst = CallInst::Create(FCallee, Args, "", BB); APFloatInst->addParamAttr( 0, Attribute::get(*Context, Attribute::AttrKind::StructRet, RetTy)); return static_cast(new LoadInst(RetTy, Args[0], "", false, BB)); } template bool SPIRVToLLVM::foreachFuncCtlMask(SourceTy Source, FuncTy Func) { SPIRVWord FCM = Source->getFuncCtlMask(); SPIRSPIRVFuncCtlMaskMap::foreach ( [&](Attribute::AttrKind Attr, SPIRVFunctionControlMaskKind Mask) { if (FCM & Mask) Func(Attr); }); return true; } void SPIRVToLLVM::transFunctionAttrs(SPIRVFunction *BF, Function *F) { if (BF->hasDecorate(DecorationReferencedIndirectlyINTEL)) F->addFnAttr("referenced-indirectly"); if (isFuncNoUnwind()) F->addFnAttr(Attribute::NoUnwind); foreachFuncCtlMask(BF, [&](Attribute::AttrKind Attr) { F->addFnAttr(Attr); }); for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E; ++I) { auto *BA = BF->getArgument(I->getArgNo()); mapValue(BA, &(*I)); setName(&(*I), BA); AttributeMask IllegalAttrs = AttributeFuncs::typeIncompatible(I->getType(), I->getAttributes()); BA->foreachAttr([&](SPIRVFuncParamAttrKind Kind) { // Skip this function parameter attribute as it will translated among // OpenCL metadata if (Kind == FunctionParameterAttributeRuntimeAlignedINTEL) return; if (Kind == FunctionParameterAttributeNoCapture) { I->addAttr(Attribute::getWithCaptureInfo(F->getContext(), CaptureInfo::none())); return; } Attribute::AttrKind LLVMKind = SPIRSPIRVFuncParamAttrMap::rmap(Kind); if (IllegalAttrs.contains(LLVMKind)) return; Type *AttrTy = nullptr; switch (LLVMKind) { case Attribute::AttrKind::ByVal: case Attribute::AttrKind::StructRet: AttrTy = transType(BA->getType()->getPointerElementType()); break; default: break; // do nothing } // Make sure to use a correct constructor for a typed/typeless attribute auto A = AttrTy ? Attribute::get(*Context, LLVMKind, AttrTy) : Attribute::get(*Context, LLVMKind); I->addAttr(A); }); AttrBuilder Builder(*Context); SPIRVWord MaxOffset = 0; if (BA->hasDecorate(DecorationMaxByteOffset, 0, &MaxOffset)) Builder.addDereferenceableAttr(MaxOffset); else { SPIRVId MaxOffsetId; if (BA->hasDecorateId(DecorationMaxByteOffsetId, 0, &MaxOffsetId)) { if (auto MaxOffsetVal = transIdAsConstant(MaxOffsetId)) { Builder.addDereferenceableAttr(*MaxOffsetVal); } } } if (auto Alignment = getAlignment(BA)) { Builder.addAlignmentAttr(*Alignment); } I->addAttrs(Builder); } BF->foreachReturnValueAttr([&](SPIRVFuncParamAttrKind Kind) { if (Kind == FunctionParameterAttributeNoWrite) return; F->addRetAttr(SPIRSPIRVFuncParamAttrMap::rmap(Kind)); }); } namespace { // One basic block can be a predecessor to another basic block more than // once (https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/2702). // This function fixes any PHIs that break this rule. static void validatePhiPredecessors(Function *F) { for (BasicBlock &BB : *F) { bool UniquePreds = true; DenseMap PredsCnt; for (BasicBlock *PredBB : predecessors(&BB)) { auto It = PredsCnt.try_emplace(PredBB, 1); if (!It.second) { UniquePreds = false; ++It.first->second; } } if (UniquePreds) continue; // `phi` requires an incoming value per each predecessor instance, even // it's the same basic block that has been already inserted as an incoming // value of the `phi`. for (PHINode &Phi : BB.phis()) { SmallVector Vs; SmallVector Bs; SmallPtrSet UsedB; for (auto [V, B] : zip(Phi.incoming_values(), Phi.blocks())) { if (!UsedB.insert(B).second) continue; unsigned N = PredsCnt[B]; Vs.insert(Vs.end(), N, V); Bs.insert(Bs.end(), N, B); } unsigned I = 0; for (unsigned N = Phi.getNumIncomingValues(); I < N; ++I) { Phi.setIncomingValue(I, Vs[I]); Phi.setIncomingBlock(I, Bs[I]); } for (unsigned N = Vs.size(); I < N; ++I) Phi.addIncoming(Vs[I], Bs[I]); } } } } // namespace Function *SPIRVToLLVM::transFunction(SPIRVFunction *BF, unsigned AS) { auto Loc = FuncMap.find(BF); if (Loc != FuncMap.end()) return Loc->second; auto IsKernel = isKernel(BF); if (IsKernel) { // search for a previous function with the same name // upgrade it to a kernel and drop this if it's found for (auto &I : FuncMap) { const auto &BFName = I.getFirst()->getName(); if (BF->getName() == BFName) { auto *F = I.getSecond(); F->setCallingConv(CallingConv::SPIR_KERNEL); F->setLinkage(GlobalValue::ExternalLinkage); F->setDSOLocal(false); F = cast(mapValue(BF, F)); mapFunction(BF, F); transFunctionAttrs(BF, F); return F; } } } auto Linkage = IsKernel ? GlobalValue::ExternalLinkage : transLinkageType(BF); FunctionType *FT = cast(transType(BF->getFunctionType())); std::string FuncName = BF->getName(); StringRef FuncNameRef(FuncName); // Transform "@spirv.llvm_memset_p0i8_i32.volatile" to @llvm.memset.p0i8.i32 // assuming llvm.memset is supported by the device compiler. If this // assumption is not safe, we should have a command line option to control // this behavior. if (FuncNameRef.starts_with("spirv.llvm_memset_p")) { // We can't guarantee that the name is correctly mangled due to opaque // pointers. Derive the correct name from the function type. FuncName = Intrinsic::getOrInsertDeclaration( M, Intrinsic::memset, {FT->getParamType(0), FT->getParamType(2)}) ->getName(); } // Special handling for spirv.llvm_umul_with_overflow_* functions // These were created during forward translation by lowering intrinsics. // During reverse translation, we replace them with intrinsic calls. if (FuncNameRef.starts_with("spirv.llvm_umul_with_overflow_")) { Type *OverloadTy = FT->getParamType(0); Function *F = Intrinsic::getOrInsertDeclaration( M, Intrinsic::umul_with_overflow, {OverloadTy}); F = cast(mapValue(BF, F)); mapFunction(BF, F); return F; // Skip body translation - intrinsic will be used instead } // Normal function handling. if (FuncNameRef.consume_front("spirv.")) { FuncNameRef.consume_back(".volatile"); FuncName = FuncNameRef.str(); std::replace(FuncName.begin(), FuncName.end(), '_', '.'); } Function *F = M->getFunction(FuncName); if (!F) F = Function::Create(FT, Linkage, AS, FuncName, M); F = cast(mapValue(BF, F)); mapFunction(BF, F); if (F->isIntrinsic()) { return F; } F->setCallingConv(IsKernel ? CallingConv::SPIR_KERNEL : CallingConv::SPIR_FUNC); transFunctionAttrs(BF, F); // Creating all basic blocks before creating instructions. for (size_t I = 0, E = BF->getNumBasicBlock(); I != E; ++I) { transValue(BF->getBasicBlock(I), F, nullptr); } for (size_t I = 0, E = BF->getNumBasicBlock(); I != E; ++I) { SPIRVBasicBlock *BBB = BF->getBasicBlock(I); BasicBlock *BB = cast(transValue(BBB, F, nullptr)); for (size_t BI = 0, BE = BBB->getNumInst(); BI != BE; ++BI) { SPIRVInstruction *BInst = BBB->getInst(BI); transValue(BInst, F, BB, false); } } validatePhiPredecessors(F); transLLVMLoopMetadata(F); return F; } Value *SPIRVToLLVM::transAsmINTEL(SPIRVAsmINTEL *BA) { assert(BA); bool HasSideEffect = BA->hasDecorate(DecorationSideEffectsINTEL); return InlineAsm::get( cast(transType(BA->getFunctionType())), BA->getInstructions(), BA->getConstraints(), HasSideEffect, /* IsAlignStack */ false, InlineAsm::AsmDialect::AD_ATT); } CallInst *SPIRVToLLVM::transAsmCallINTEL(SPIRVAsmCallINTEL *BI, Function *F, BasicBlock *BB) { assert(BI); auto *IA = cast(transValue(BI->getAsm(), F, BB)); auto Args = transValue(BM->getValues(BI->getArguments()), F, BB); return CallInst::Create(cast(IA->getFunctionType()), IA, Args, BI->getName(), BB); } /// LLVM convert builtin functions is translated to two instructions: /// y = i32 islessgreater(float x, float z) -> /// y = i32 ZExt(bool LessOrGreater(float x, float z)) /// When translating back, for simplicity, a trunc instruction is inserted /// w = bool LessOrGreater(float x, float z) -> /// w = bool Trunc(i32 islessgreater(float x, float z)) /// Optimizer should be able to remove the redundant trunc/zext void SPIRVToLLVM::transOCLBuiltinFromInstPreproc( SPIRVInstruction *BI, Type *&RetTy, std::vector &Args) { if (!BI->hasType()) return; auto *BT = BI->getType(); if (isCmpOpCode(BI->getOpCode())) { if (BT->isTypeBool()) RetTy = IntegerType::getInt32Ty(*Context); else if (BT->isTypeVectorBool()) RetTy = FixedVectorType::get( IntegerType::get( *Context, Args[0]->getType()->getVectorComponentType()->getBitWidth()), BT->getVectorComponentCount()); else llvm_unreachable("invalid compare instruction"); } } Instruction * SPIRVToLLVM::transOCLBuiltinPostproc(SPIRVInstruction *BI, CallInst *CI, BasicBlock *BB, const std::string &DemangledName) { auto OC = BI->getOpCode(); if (isCmpOpCode(OC) && BI->getType()->isTypeVectorOrScalarBool()) { return CastInst::Create(Instruction::Trunc, CI, transType(BI->getType()), "cvt", BB); } if (SPIRVEnableStepExpansion && (DemangledName == "smoothstep" || DemangledName == "step")) return expandOCLBuiltinWithScalarArg(CI, DemangledName); return CI; } Value *SPIRVToLLVM::transBlockInvoke(SPIRVValue *Invoke, BasicBlock *BB) { auto *TranslatedInvoke = transFunction(static_cast(Invoke)); auto *Int8PtrTyGen = PointerType::get(*Context, SPIRAS_Generic); return CastInst::CreatePointerBitCastOrAddrSpaceCast(TranslatedInvoke, Int8PtrTyGen, "", BB); } Instruction *SPIRVToLLVM::transWGSizeQueryBI(SPIRVInstruction *BI, BasicBlock *BB) { std::string FName = (BI->getOpCode() == OpGetKernelWorkGroupSize) ? "__get_kernel_work_group_size_impl" : "__get_kernel_preferred_work_group_size_multiple_impl"; Function *F = M->getFunction(FName); if (!F) { auto *Int8PtrTyGen = PointerType::get(*Context, SPIRAS_Generic); FunctionType *FT = FunctionType::get(Type::getInt32Ty(*Context), {Int8PtrTyGen, Int8PtrTyGen}, false); F = Function::Create(FT, GlobalValue::ExternalLinkage, FName, M); if (isFuncNoUnwind()) F->addFnAttr(Attribute::NoUnwind); } auto Ops = BI->getOperands(); SmallVector Args = {transBlockInvoke(Ops[0], BB), transValue(Ops[1], F, BB, false)}; auto *Call = CallInst::Create(F, Args, "", BB); setName(Call, BI); setAttrByCalledFunc(Call); return Call; } Instruction *SPIRVToLLVM::transSGSizeQueryBI(SPIRVInstruction *BI, BasicBlock *BB) { std::string FName = (BI->getOpCode() == OpGetKernelNDrangeMaxSubGroupSize) ? "__get_kernel_max_sub_group_size_for_ndrange_impl" : "__get_kernel_sub_group_count_for_ndrange_impl"; auto Ops = BI->getOperands(); Function *F = M->getFunction(FName); if (!F) { auto *Int8PtrTyGen = PointerType::get(*Context, SPIRAS_Generic); SmallVector Tys = { transType(Ops[0]->getType()), // ndrange Int8PtrTyGen, // block_invoke Int8PtrTyGen // block_literal }; auto *FT = FunctionType::get(Type::getInt32Ty(*Context), Tys, false); F = Function::Create(FT, GlobalValue::ExternalLinkage, FName, M); if (isFuncNoUnwind()) F->addFnAttr(Attribute::NoUnwind); } SmallVector Args = { transValue(Ops[0], F, BB, false), // ndrange transBlockInvoke(Ops[1], BB), // block_invoke transValue(Ops[2], F, BB, false) // block_literal }; auto *Call = CallInst::Create(F, Args, "", BB); setName(Call, BI); setAttrByCalledFunc(Call); return Call; } Instruction *SPIRVToLLVM::transBuiltinFromInst(const std::string &FuncName, SPIRVInstruction *BI, BasicBlock *BB) { std::string MangledName; auto Ops = BI->getOperands(); Op OC = BI->getOpCode(); if (isUntypedAccessChainOpCode(OC)) { auto *AC = static_cast(BI); if (AC->getBaseType()->isTypeCooperativeMatrixKHR()) Ops.erase(Ops.begin()); } Type *RetTy = BI->hasType() ? transType(BI->getType()) : Type::getVoidTy(*Context); transOCLBuiltinFromInstPreproc(BI, RetTy, Ops); std::vector ArgTys = transTypeVector(SPIRVInstruction::getOperandTypes(Ops), true); auto Ptr = findFirstPtrType(ArgTys); if (Ptr < ArgTys.size() && BI->getValueType(Ops[Ptr]->getId())->isTypeUntypedPointerKHR()) { // Special handling for "truly" untyped pointers to preserve correct // builtin mangling of atomic and matrix operations. if (isAtomicOpCodeUntypedPtrSupported(OC)) { auto *AI = static_cast(BI); ArgTys[Ptr] = TypedPointerType::get( transType(AI->getSemanticType()), SPIRSPIRVAddrSpaceMap::rmap( BI->getValueType(Ops[Ptr]->getId())->getPointerStorageClass())); } } for (unsigned I = 0; I < ArgTys.size(); I++) { if (isa(ArgTys[I])) { SPIRVType *OpTy = BI->getValueType(Ops[I]->getId()); // `Param` must be a pointer to an 8-bit integer type scalar. // Avoid demangling for this argument if it's a pointer to get `Pc` // mangling. if (OC == OpEnqueueKernel && I == 7) { if (ArgTys[I]->isPointerTy()) continue; } if (OpTy->isTypeUntypedPointerKHR()) { auto *Val = transValue(Ops[I], BB->getParent(), BB); Val = Val->stripPointerCasts(); if (isUntypedAccessChainOpCode(Ops[I]->getOpCode())) { SPIRVType *BaseTy = reinterpret_cast(Ops[I])->getBaseType(); Type *Ty = nullptr; if (BaseTy->isTypeArray()) Ty = transType(BaseTy->getArrayElementType()); else if (BaseTy->isTypeVector()) Ty = transType(BaseTy->getVectorComponentType()); else Ty = transType(BaseTy); ArgTys[I] = TypedPointerType::get( Ty, SPIRSPIRVAddrSpaceMap::rmap(OpTy->getPointerStorageClass())); } else if (auto *GEP = dyn_cast(Val)) { ArgTys[I] = TypedPointerType::get( GEP->getSourceElementType(), SPIRSPIRVAddrSpaceMap::rmap(OpTy->getPointerStorageClass())); } else if (Ops[I]->getOpCode() == OpUntypedVariableKHR) { SPIRVUntypedVariableKHR *UV = static_cast(Ops[I]); Type *Ty = transType(UV->getDataType()); ArgTys[I] = TypedPointerType::get( Ty, SPIRSPIRVAddrSpaceMap::rmap(OpTy->getPointerStorageClass())); } else if (auto *AI = dyn_cast(Val)) { ArgTys[I] = TypedPointerType::get( AI->getAllocatedType(), SPIRSPIRVAddrSpaceMap::rmap(OpTy->getPointerStorageClass())); } else if (Ops[I]->getOpCode() == OpFunctionParameter && !RetTy->isVoidTy()) { // Pointer could be a function parameter. Assume that the type of // the pointer is the same as the return type. Type *Ty = nullptr; // it return type is array type, assign its element type to Ty if (RetTy->isArrayTy()) Ty = RetTy->getArrayElementType(); else if (RetTy->isVectorTy()) Ty = cast(RetTy)->getElementType(); else Ty = RetTy; ArgTys[I] = TypedPointerType::get( Ty, SPIRSPIRVAddrSpaceMap::rmap(OpTy->getPointerStorageClass())); } } } } for (auto &I : ArgTys) { if (isa(I)) { I = TypedPointerType::get(I, SPIRAS_Private); } } if (BM->getDesiredBIsRepresentation() != BIsRepresentation::SPIRVFriendlyIR) mangleOpenClBuiltin(FuncName, ArgTys, MangledName); else MangledName = getSPIRVFriendlyIRFunctionName(FuncName, OC, ArgTys, Ops); opaquifyTypedPointers(ArgTys); Function *Func = M->getFunction(MangledName); FunctionType *FT = FunctionType::get(RetTy, ArgTys, false); // ToDo: Some intermediate functions have duplicate names with // different function types. This is OK if the function name // is used internally and finally translated to unique function // names. However it is better to have a way to differentiate // between intermidiate functions and final functions and make // sure final functions have unique names. SPIRVDBG(if (Func && Func->getFunctionType() != FT) { dbgs() << "Warning: Function name conflict:\n" << *Func << '\n' << " => " << *FT << '\n'; }) if (!Func || Func->getFunctionType() != FT) { LLVM_DEBUG(for (auto &I : ArgTys) { dbgs() << *I << '\n'; }); Func = Function::Create(FT, GlobalValue::ExternalLinkage, MangledName, M); Func->setCallingConv(CallingConv::SPIR_FUNC); if (isFuncNoUnwind()) Func->addFnAttr(Attribute::NoUnwind); if (isGroupOpCode(OC) || isGroupNonUniformOpcode(OC) || isIntelSubgroupOpCode(OC) || isSplitBarrierINTELOpCode(OC) || OC == OpControlBarrier) Func->addFnAttr(Attribute::Convergent); } CallInst *Call; // TODO: Remove the check for matrix type once drivers are updated. if (OC == OpCooperativeMatrixLengthKHR && Ops[0]->getOpCode() == OpTypeCooperativeMatrixKHR) { // OpCooperativeMatrixLengthKHR needs special handling as its operand is // a Type instead of a Value. llvm::Type *MatTy = transType(reinterpret_cast(Ops[0])); Call = CallInst::Create(Func, Constant::getNullValue(MatTy), "", BB); } else { Call = CallInst::Create(Func, transValue(Ops, BB->getParent(), BB), "", BB); } setName(Call, BI); setAttrByCalledFunc(Call); SPIRVDBG(spvdbgs() << "[transInstToBuiltinCall] " << *BI << " -> "; dbgs() << *Call << '\n';) Instruction *Inst = transOCLBuiltinPostproc(BI, Call, BB, FuncName); return Inst; } SPIRVToLLVM::SPIRVToLLVM(Module *LLVMModule, SPIRVModule *TheSPIRVModule) : BuiltinCallHelper(ManglingRules::OpenCL), M(LLVMModule), BM(TheSPIRVModule) { assert(M && "Initialization without an LLVM module is not allowed"); initialize(*M); Context = &M->getContext(); if (BM->getDesiredBIsRepresentation() == BIsRepresentation::SPIRVFriendlyIR) UseTargetTypes = true; DbgTran.reset(new SPIRVToLLVMDbgTran(TheSPIRVModule, LLVMModule, this)); } std::string getSPIRVFuncSuffix(SPIRVInstruction *BI) { std::string Suffix = ""; if (BI->getOpCode() == OpCreatePipeFromPipeStorage) { auto *CPFPS = static_cast(BI); assert(CPFPS->getType()->isTypePipe() && "Invalid type of CreatePipeFromStorage"); auto *PipeType = static_cast(CPFPS->getType()); switch (PipeType->getAccessQualifier()) { default: case AccessQualifierReadOnly: Suffix = "_read"; break; case AccessQualifierWriteOnly: Suffix = "_write"; break; case AccessQualifierReadWrite: Suffix = "_read_write"; break; } } if (BI->hasDecorate(DecorationSaturatedConversion)) { Suffix += kSPIRVPostfix::Divider; Suffix += kSPIRVPostfix::Sat; } SPIRVFPRoundingModeKind Kind; if (BI->hasFPRoundingMode(&Kind)) { Suffix += kSPIRVPostfix::Divider; Suffix += SPIRSPIRVFPRoundingModeMap::rmap(Kind); } if (BI->getOpCode() == OpGenericCastToPtrExplicit) { Suffix += kSPIRVPostfix::Divider; auto *Ty = BI->getType(); auto GenericCastToPtrInst = Ty->isTypeVectorPointer() ? Ty->getVectorComponentType()->getPointerStorageClass() : Ty->getPointerStorageClass(); switch (GenericCastToPtrInst) { case StorageClassCrossWorkgroup: Suffix += std::string(kSPIRVPostfix::ToGlobal); break; case StorageClassWorkgroup: Suffix += std::string(kSPIRVPostfix::ToLocal); break; case StorageClassFunction: Suffix += std::string(kSPIRVPostfix::ToPrivate); break; default: llvm_unreachable("Invalid address space"); } } if (BI->getOpCode() == OpBuildNDRange) { Suffix += kSPIRVPostfix::Divider; auto *NDRangeInst = static_cast(BI); auto *EleTy = ((NDRangeInst->getOperands())[0])->getType(); int Dim = EleTy->isTypeArray() ? EleTy->getArrayLength() : 1; assert((EleTy->isTypeInt() && Dim == 1) || (EleTy->isTypeArray() && Dim >= 2 && Dim <= 3)); std::ostringstream OS; OS << Dim; Suffix += OS.str() + "D"; } return Suffix; } Instruction *SPIRVToLLVM::transSPIRVBuiltinFromInst(SPIRVInstruction *BI, BasicBlock *BB) { assert(BB && "Invalid BB"); const auto OC = BI->getOpCode(); bool AddRetTypePostfix = false; switch (static_cast(OC)) { case OpImageQuerySizeLod: case OpImageQuerySize: case OpImageRead: case OpSubgroupImageBlockReadINTEL: case OpSubgroupImageMediaBlockReadINTEL: case OpSubgroupBlockReadINTEL: case OpImageSampleExplicitLod: case OpSDotKHR: case OpUDotKHR: case OpSUDotKHR: case OpSDotAccSatKHR: case OpUDotAccSatKHR: case OpSUDotAccSatKHR: case OpReadClockKHR: case OpFmaKHR: case internal::OpJointMatrixLoadINTEL: case OpCooperativeMatrixLoadKHR: case internal::OpCooperativeMatrixLoadCheckedINTEL: case internal::OpCooperativeMatrixLoadOffsetINTEL: case internal::OpTaskSequenceCreateINTEL: case internal::OpConvertHandleToImageINTEL: case internal::OpConvertHandleToSampledImageINTEL: case internal::OpClampConvertFToSINTEL: case internal::OpStochasticRoundFToFINTEL: case internal::OpClampStochasticRoundFToSINTEL: // Old opcodes, for backward compatibility. case internal::OpClampConvertFToFINTEL: case internal::OpClampStochasticRoundFToFINTEL: AddRetTypePostfix = true; break; default: { if (isCvtOpCode(OC) && OC != OpGenericCastToPtrExplicit) AddRetTypePostfix = true; break; } } bool IsRetSigned = true; switch (OC) { case OpConvertFToU: case OpSatConvertSToU: case OpUConvert: case OpUDotKHR: case OpUDotAccSatKHR: case OpReadClockKHR: IsRetSigned = false; break; case OpImageRead: case OpImageSampleExplicitLod: { size_t Idx = getImageOperandsIndex(OC); if (auto Ops = BI->getOperands(); Ops.size() > Idx) { auto ImOp = static_cast(Ops[Idx])->getZExtIntValue(); IsRetSigned = !(ImOp & ImageOperandsMask::ImageOperandsZeroExtendMask); } break; } default: break; } if (AddRetTypePostfix) { const Type *RetTy = BI->hasType() ? transType(BI->getType(), true) : Type::getVoidTy(*Context); Type *PET = nullptr; if (auto *TPT = dyn_cast(RetTy)) PET = TPT->getElementType(); return transBuiltinFromInst(getSPIRVFuncName(OC, RetTy, IsRetSigned, PET) + getSPIRVFuncSuffix(BI), BI, BB); } return transBuiltinFromInst(getSPIRVFuncName(OC, getSPIRVFuncSuffix(BI)), BI, BB); } bool SPIRVToLLVM::translate() { if (!transAddressingModel()) return false; // Entry Points should be translated before all debug intrinsics. for (SPIRVExtInst *EI : BM->getDebugInstVec()) { if (EI->getExtOp() == SPIRVDebug::EntryPoint) DbgTran->transDebugInst(EI); } // Compile unit might be needed during translation of debug intrinsics. for (SPIRVExtInst *EI : BM->getDebugInstVec()) { // Translate Compile Units first. if (EI->getExtOp() == SPIRVDebug::CompilationUnit) DbgTran->transDebugInst(EI); } for (unsigned I = 0, E = BM->getNumVariables(); I != E; ++I) { auto *BV = BM->getVariable(I); if (BV->getStorageClass() != StorageClassFunction) transValue(BV, nullptr, nullptr); transGlobalCtorDtors(BV); } // Then translate all debug instructions. for (SPIRVExtInst *EI : BM->getDebugInstVec()) { DbgTran->transDebugInst(EI); } for (auto *FP : BM->getFunctionPointers()) { SPIRVConstantFunctionPointerINTEL *BC = static_cast(FP); SPIRVFunction *F = BC->getFunction(); FP->setName(F->getName()); const unsigned AS = BM->shouldEmitFunctionPtrAddrSpace() ? SPIRAS_CodeSectionINTEL : SPIRAS_Private; mapValue(FP, transFunction(F, AS)); } for (unsigned I = 0, E = BM->getNumFunctions(); I != E; ++I) { transFunction(BM->getFunction(I)); transUserSemantic(BM->getFunction(I)); } transGlobalAnnotations(); if (!transMetadata()) return false; if (!transFPContractMetadata()) return false; transSourceLanguage(); if (!transSourceExtension()) return false; transGeneratorMD(); if (!lowerBuiltins(BM, M)) return false; if (BM->getDesiredBIsRepresentation() == BIsRepresentation::SPIRVFriendlyIR) { SPIRVWord SrcLangVer = 0; BM->getSourceLanguage(&SrcLangVer); bool IsCpp = SrcLangVer == kOCLVer::CLCXX10 || SrcLangVer == kOCLVer::CLCXX2021; if (!postProcessBuiltinsReturningStruct(M, IsCpp)) return false; } for (SPIRVExtInst *EI : BM->getAuxDataInstVec()) { transAuxDataInst(EI); } eraseUselessFunctions(M); DbgTran->addDbgInfoVersion(); DbgTran->finalize(); return true; } bool SPIRVToLLVM::transAddressingModel() { switch (BM->getAddressingModel()) { case AddressingModelPhysical64: M->setTargetTriple(Triple(SPIR_TARGETTRIPLE64)); M->setDataLayout(SPIR_DATALAYOUT64); break; case AddressingModelPhysical32: M->setTargetTriple(Triple(SPIR_TARGETTRIPLE32)); M->setDataLayout(SPIR_DATALAYOUT32); break; case AddressingModelLogical: // Do not set target triple and data layout break; default: SPIRVCKRT(0, InvalidAddressingModel, "Actual addressing mode is " + std::to_string(BM->getAddressingModel())); } return true; } void generateIntelFPGAAnnotation( const SPIRVEntry *E, std::vector> &AnnotStrVec) { llvm::SmallString<256> AnnotStr; llvm::raw_svector_ostream Out(AnnotStr); if (E->hasDecorate(DecorationRegisterINTEL)) Out << "{register:1}"; SPIRVWord Result = 0; if (E->hasDecorate(DecorationMemoryINTEL)) Out << "{memory:" << E->getDecorationStringLiteral(DecorationMemoryINTEL).front() << '}'; if (E->hasDecorate(DecorationBankwidthINTEL, 0, &Result)) Out << "{bankwidth:" << Result << '}'; if (E->hasDecorate(DecorationNumbanksINTEL, 0, &Result)) Out << "{numbanks:" << Result << '}'; if (E->hasDecorate(DecorationMaxPrivateCopiesINTEL, 0, &Result)) Out << "{private_copies:" << Result << '}'; if (E->hasDecorate(DecorationSinglepumpINTEL)) Out << "{pump:1}"; if (E->hasDecorate(DecorationDoublepumpINTEL)) Out << "{pump:2}"; if (E->hasDecorate(DecorationMaxReplicatesINTEL, 0, &Result)) Out << "{max_replicates:" << Result << '}'; if (E->hasDecorate(DecorationSimpleDualPortINTEL)) Out << "{simple_dual_port:1}"; if (E->hasDecorate(DecorationMergeINTEL)) { Out << "{merge"; for (const auto &Str : E->getDecorationStringLiteral(DecorationMergeINTEL)) Out << ":" << Str; Out << '}'; } if (E->hasDecorate(DecorationBankBitsINTEL)) { Out << "{bank_bits:"; auto Literals = E->getDecorationLiterals(DecorationBankBitsINTEL); for (size_t I = 0; I < Literals.size() - 1; ++I) Out << Literals[I] << ","; Out << Literals.back() << '}'; } if (E->hasDecorate(DecorationForcePow2DepthINTEL, 0, &Result)) Out << "{force_pow2_depth:" << Result << '}'; if (E->hasDecorate(DecorationStridesizeINTEL, 0, &Result)) Out << "{stride_size:" << Result << "}"; if (E->hasDecorate(DecorationWordsizeINTEL, 0, &Result)) Out << "{word_size:" << Result << "}"; if (E->hasDecorate(DecorationTrueDualPortINTEL)) Out << "{true_dual_port}"; if (E->hasDecorate(DecorationBufferLocationINTEL, 0, &Result)) Out << "{sycl-buffer-location:" << Result << '}'; if (E->hasDecorate(DecorationLatencyControlLabelINTEL, 0, &Result)) Out << "{sycl-latency-anchor-id:" << Result << '}'; if (E->hasDecorate(DecorationLatencyControlConstraintINTEL)) { auto Literals = E->getDecorationLiterals(DecorationLatencyControlConstraintINTEL); assert(Literals.size() == 3 && "Latency Control Constraint decoration shall have 3 extra operands"); Out << "{sycl-latency-constraint:" << Literals[0] << "," << Literals[1] << "," << Literals[2] << '}'; } unsigned LSUParamsBitmask = 0; llvm::SmallString<32> AdditionalParamsStr; llvm::raw_svector_ostream ParamsOut(AdditionalParamsStr); if (E->hasDecorate(DecorationBurstCoalesceINTEL, 0)) LSUParamsBitmask |= IntelFPGAMemoryAccessesVal::BurstCoalesce; if (E->hasDecorate(DecorationCacheSizeINTEL, 0, &Result)) { LSUParamsBitmask |= IntelFPGAMemoryAccessesVal::CacheSizeFlag; ParamsOut << "{cache-size:" << Result << "}"; } if (E->hasDecorate(DecorationDontStaticallyCoalesceINTEL, 0)) LSUParamsBitmask |= IntelFPGAMemoryAccessesVal::DontStaticallyCoalesce; if (E->hasDecorate(DecorationPrefetchINTEL, 0, &Result)) { LSUParamsBitmask |= IntelFPGAMemoryAccessesVal::PrefetchFlag; // TODO: Enable prefetch size backwards translation // once it is supported } if (LSUParamsBitmask) Out << "{params:" << LSUParamsBitmask << "}" << AdditionalParamsStr; if (!AnnotStr.empty()) AnnotStrVec.emplace_back(AnnotStr); if (E->hasDecorate(DecorationUserSemantic)) { auto Annotations = E->getAllDecorationStringLiterals(DecorationUserSemantic); for (size_t I = 0; I != Annotations.size(); ++I) { // UserSemantic has a single literal string llvm::SmallString<256> UserSemanticStr; llvm::raw_svector_ostream UserSemanticOut(UserSemanticStr); for (const auto &Str : Annotations[I]) UserSemanticOut << Str; AnnotStrVec.emplace_back(UserSemanticStr); } } } void generateIntelFPGAAnnotationForStructMember( const SPIRVEntry *E, SPIRVWord MemberNumber, std::vector> &AnnotStrVec) { llvm::SmallString<256> AnnotStr; llvm::raw_svector_ostream Out(AnnotStr); if (E->hasMemberDecorate(DecorationRegisterINTEL, 0, MemberNumber)) Out << "{register:1}"; SPIRVWord Result = 0; if (E->hasMemberDecorate(DecorationMemoryINTEL, 0, MemberNumber, &Result)) Out << "{memory:" << E->getMemberDecorationStringLiteral(DecorationMemoryINTEL, MemberNumber) .front() << '}'; if (E->hasMemberDecorate(DecorationBankwidthINTEL, 0, MemberNumber, &Result)) Out << "{bankwidth:" << Result << '}'; if (E->hasMemberDecorate(DecorationNumbanksINTEL, 0, MemberNumber, &Result)) Out << "{numbanks:" << Result << '}'; if (E->hasMemberDecorate(DecorationMaxPrivateCopiesINTEL, 0, MemberNumber, &Result)) Out << "{private_copies:" << Result << '}'; if (E->hasMemberDecorate(DecorationSinglepumpINTEL, 0, MemberNumber)) Out << "{pump:1}"; if (E->hasMemberDecorate(DecorationDoublepumpINTEL, 0, MemberNumber)) Out << "{pump:2}"; if (E->hasMemberDecorate(DecorationMaxReplicatesINTEL, 0, MemberNumber, &Result)) Out << "{max_replicates:" << Result << '}'; if (E->hasMemberDecorate(DecorationSimpleDualPortINTEL, 0, MemberNumber)) Out << "{simple_dual_port:1}"; if (E->hasMemberDecorate(DecorationMergeINTEL, 0, MemberNumber)) { Out << "{merge"; for (const auto &Str : E->getMemberDecorationStringLiteral( DecorationMergeINTEL, MemberNumber)) Out << ":" << Str; Out << '}'; } if (E->hasMemberDecorate(DecorationBankBitsINTEL, 0, MemberNumber)) { Out << "{bank_bits:"; auto Literals = E->getMemberDecorationLiterals(DecorationBankBitsINTEL, MemberNumber); for (size_t I = 0; I < Literals.size() - 1; ++I) Out << Literals[I] << ","; Out << Literals.back() << '}'; } if (E->hasMemberDecorate(DecorationForcePow2DepthINTEL, 0, MemberNumber, &Result)) Out << "{force_pow2_depth:" << Result << '}'; if (E->hasMemberDecorate(DecorationStridesizeINTEL, 0, MemberNumber, &Result)) Out << "{stride_size:" << Result << "}"; if (E->hasMemberDecorate(DecorationWordsizeINTEL, 0, MemberNumber, &Result)) Out << "{word_size:" << Result << "}"; if (E->hasMemberDecorate(DecorationTrueDualPortINTEL, 0, MemberNumber)) Out << "{true_dual_port}"; if (!AnnotStr.empty()) AnnotStrVec.emplace_back(AnnotStr); if (E->hasMemberDecorate(DecorationUserSemantic, 0, MemberNumber)) { auto Annotations = E->getAllMemberDecorationStringLiterals( DecorationUserSemantic, MemberNumber); for (size_t I = 0; I != Annotations.size(); ++I) { // UserSemantic has a single literal string llvm::SmallString<256> UserSemanticStr; llvm::raw_svector_ostream UserSemanticOut(UserSemanticStr); for (const auto &Str : Annotations[I]) UserSemanticOut << Str; AnnotStrVec.emplace_back(UserSemanticStr); } } } void SPIRVToLLVM::transIntelFPGADecorations(SPIRVValue *BV, Value *V) { if (!BV->isVariable() && !BV->isInst()) return; if (auto *Inst = dyn_cast(V)) { auto *AL = dyn_cast(Inst); Type *AllocatedTy = AL ? AL->getAllocatedType() : Inst->getType(); IRBuilder<> Builder(Inst->getParent()); Type *Int8PtrTyPrivate = PointerType::get(*Context, SPIRAS_Private); IntegerType *Int32Ty = IntegerType::get(*Context, 32); Value *UndefInt8Ptr = PoisonValue::get(Int8PtrTyPrivate); Value *UndefInt32 = PoisonValue::get(Int32Ty); if (AL && BV->getType()->getPointerElementType()->isTypeStruct()) { auto *ST = BV->getType()->getPointerElementType(); SPIRVTypeStruct *STS = static_cast(ST); for (SPIRVWord I = 0; I < STS->getMemberCount(); ++I) { std::vector> AnnotStrVec; generateIntelFPGAAnnotationForStructMember(ST, I, AnnotStrVec); CallInst *AnnotationCall = nullptr; for (const auto &AnnotStr : AnnotStrVec) { auto *GS = Builder.CreateGlobalString(AnnotStr); Instruction *PtrAnnFirstArg = nullptr; if (GEPOrUseMap.count(AL)) { auto IdxToInstMap = GEPOrUseMap[AL]; if (IdxToInstMap.count(I)) { PtrAnnFirstArg = IdxToInstMap[I]; } } Type *IntTy = nullptr; if (!PtrAnnFirstArg) { GetElementPtrInst *GEP = cast( Builder.CreateConstInBoundsGEP2_32(AllocatedTy, AL, 0, I)); IntTy = GEP->getResultElementType()->isIntegerTy() ? GEP->getType() : Int8PtrTyPrivate; PtrAnnFirstArg = GEP; } else { IntTy = PtrAnnFirstArg->getType(); } auto *AnnotationFn = llvm::Intrinsic::getOrInsertDeclaration( M, Intrinsic::ptr_annotation, {IntTy, Int8PtrTyPrivate}); llvm::Value *Args[] = { Builder.CreateBitCast(PtrAnnFirstArg, IntTy, PtrAnnFirstArg->getName()), Builder.CreateBitCast(GS, Int8PtrTyPrivate), UndefInt8Ptr, UndefInt32, UndefInt8Ptr}; AnnotationCall = Builder.CreateCall(AnnotationFn, Args); GEPOrUseMap[AL][I] = AnnotationCall; } if (AnnotationCall) ValueMap[BV] = AnnotationCall; } } std::vector> AnnotStrVec; generateIntelFPGAAnnotation(BV, AnnotStrVec); CallInst *AnnotationCall = nullptr; for (const auto &AnnotStr : AnnotStrVec) { Constant *GS = nullptr; const auto StringAnnotStr = static_cast(AnnotStr); auto AnnotItr = AnnotationsMap.find(StringAnnotStr); if (AnnotItr != AnnotationsMap.end()) { GS = AnnotItr->second; } else { GS = Builder.CreateGlobalString(AnnotStr); AnnotationsMap.emplace(std::move(StringAnnotStr), GS); } Value *BaseInst = nullptr; if (AnnotationCall && !AnnotationCall->getType()->isVoidTy()) BaseInst = AnnotationCall; else BaseInst = AL ? Builder.CreateBitCast(V, Int8PtrTyPrivate, V->getName()) : Inst; // Try to find alloca instruction for statically allocated variables. // Alloca might be hidden by a couple of casts. bool isStaticMemoryAttribute = AL ? true : false; while (!isStaticMemoryAttribute && Inst && (isa(Inst) || isa(Inst))) { Inst = dyn_cast(Inst->getOperand(0)); isStaticMemoryAttribute = (Inst && isa(Inst)); } auto *AnnotationFn = llvm::Intrinsic::getOrInsertDeclaration( M, isStaticMemoryAttribute ? Intrinsic::var_annotation : Intrinsic::ptr_annotation, {BaseInst->getType(), Int8PtrTyPrivate}); llvm::Value *Args[] = {BaseInst, Builder.CreateBitCast(GS, Int8PtrTyPrivate), UndefInt8Ptr, UndefInt32, UndefInt8Ptr}; AnnotationCall = Builder.CreateCall(AnnotationFn, Args); } if (AnnotationCall && !AnnotationCall->getType()->isVoidTy()) ValueMap[BV] = AnnotationCall; } else if (auto *GV = dyn_cast(V)) { // Do not add annotations for builtin variables if they will be translated // to function calls. SPIRVBuiltinVariableKind Kind; if (BM->getBuiltinFormat() == BuiltinFormat::Function && isSPIRVBuiltinVariable(GV, &Kind)) return; std::vector> AnnotStrVec; generateIntelFPGAAnnotation(BV, AnnotStrVec); if (AnnotStrVec.empty()) { // Check if IO pipe decoration is applied to the global SPIRVWord ID; if (BV->hasDecorate(DecorationIOPipeStorageINTEL, 0, &ID)) { auto Literals = BV->getDecorationLiterals(DecorationIOPipeStorageINTEL); assert(Literals.size() == 1 && "IO PipeStorage decoration shall have 1 extra operand"); GV->setMetadata("io_pipe_id", getMDNodeStringIntVec(Context, Literals)); } return; } for (const auto &AnnotStr : AnnotStrVec) { Constant *StrConstant = ConstantDataArray::getString(*Context, StringRef(AnnotStr)); auto *GS = new GlobalVariable( *GV->getParent(), StrConstant->getType(), /*IsConstant*/ true, GlobalValue::PrivateLinkage, StrConstant, ""); GS->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); GS->setSection("llvm.metadata"); Type *ResType = PointerType::get( GV->getContext(), M->getDataLayout().getDefaultGlobalsAddressSpace()); Constant *C = ConstantExpr::getPointerBitCastOrAddrSpaceCast(GV, ResType); Type *Int8PtrTyPrivate = PointerType::get(*Context, SPIRAS_Private); IntegerType *Int32Ty = Type::getInt32Ty(*Context); llvm::Constant *Fields[5] = { C, ConstantExpr::getBitCast(GS, Int8PtrTyPrivate), PoisonValue::get(Int8PtrTyPrivate), PoisonValue::get(Int32Ty), PoisonValue::get(Int8PtrTyPrivate)}; GlobalAnnotations.push_back(ConstantStruct::getAnon(Fields)); } } } // Translate aliasing decorations applied to instructions. These decorations // are mapped on alias.scope and noalias metadata in LLVM. Translation of // optional string operand isn't yet supported in the translator. void SPIRVToLLVM::transMemAliasingINTELDecorations(SPIRVValue *BV, Value *V) { if (!BV->isInst()) return; Instruction *Inst = dyn_cast(V); if (!Inst) return; if (BV->hasDecorateId(DecorationAliasScopeINTEL)) { std::vector AliasListIds; AliasListIds = BV->getDecorationIdLiterals(DecorationAliasScopeINTEL); assert(AliasListIds.size() == 1 && "Memory aliasing decorations must have one argument"); addMemAliasMetadata(Inst, AliasListIds[0], LLVMContext::MD_alias_scope); } if (BV->hasDecorateId(DecorationNoAliasINTEL)) { std::vector AliasListIds; AliasListIds = BV->getDecorationIdLiterals(DecorationNoAliasINTEL); assert(AliasListIds.size() == 1 && "Memory aliasing decorations must have one argument"); addMemAliasMetadata(Inst, AliasListIds[0], LLVMContext::MD_noalias); } } // Having UserSemantic decoration on Function is against the spec, but we allow // this for various purposes (like prototyping new features when we need to // attach some information on function and propagate that through SPIR-V and // ect.) void SPIRVToLLVM::transUserSemantic(SPIRV::SPIRVFunction *Fun) { auto *TransFun = transFunction(Fun); for (const auto &UsSem : Fun->getDecorationStringLiteral(DecorationUserSemantic)) { auto *V = cast(TransFun); Constant *StrConstant = ConstantDataArray::getString(*Context, StringRef(UsSem)); auto *GS = new GlobalVariable( *TransFun->getParent(), StrConstant->getType(), /*IsConstant*/ true, GlobalValue::PrivateLinkage, StrConstant, ""); GS->setUnnamedAddr(GlobalValue::UnnamedAddr::Global); GS->setSection("llvm.metadata"); Type *ResType = PointerType::get( V->getContext(), M->getDataLayout().getDefaultGlobalsAddressSpace()); Constant *C = ConstantExpr::getPointerBitCastOrAddrSpaceCast(TransFun, ResType); Type *Int8PtrTyPrivate = PointerType::get(*Context, SPIRAS_Private); IntegerType *Int32Ty = Type::getInt32Ty(*Context); llvm::Constant *Fields[5] = { C, ConstantExpr::getBitCast(GS, Int8PtrTyPrivate), PoisonValue::get(Int8PtrTyPrivate), PoisonValue::get(Int32Ty), PoisonValue::get(Int8PtrTyPrivate)}; GlobalAnnotations.push_back(ConstantStruct::getAnon(Fields)); } } void SPIRVToLLVM::transGlobalAnnotations() { if (!GlobalAnnotations.empty()) { Constant *Array = ConstantArray::get(ArrayType::get(GlobalAnnotations[0]->getType(), GlobalAnnotations.size()), GlobalAnnotations); auto *GV = new GlobalVariable(*M, Array->getType(), /*IsConstant*/ false, GlobalValue::AppendingLinkage, Array, "llvm.global.annotations"); GV->setSection("llvm.metadata"); } } static llvm::MDNode * transDecorationsToMetadataList(llvm::LLVMContext *Context, std::vector Decorates) { SmallVector MDs; MDs.reserve(Decorates.size()); for (const auto *Deco : Decorates) { std::vector OPs; auto *KindMD = ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Deco->getDecorateKind())); OPs.push_back(KindMD); switch (static_cast(Deco->getDecorateKind())) { case DecorationLinkageAttributes: { const auto *const LinkAttrDeco = static_cast(Deco); auto *const LinkNameMD = MDString::get(*Context, LinkAttrDeco->getLinkageName()); auto *const LinkTypeMD = ConstantAsMetadata::get(ConstantInt::get( Type::getInt32Ty(*Context), LinkAttrDeco->getLinkageType())); OPs.push_back(LinkNameMD); OPs.push_back(LinkTypeMD); break; } case spv::internal::DecorationHostAccessINTEL: case DecorationHostAccessINTEL: { const auto *const HostAccDeco = static_cast(Deco); auto *const AccModeMD = ConstantAsMetadata::get(ConstantInt::get( Type::getInt32Ty(*Context), HostAccDeco->getAccessMode())); auto *const NameMD = MDString::get(*Context, HostAccDeco->getVarName()); OPs.push_back(AccModeMD); OPs.push_back(NameMD); break; } case DecorationMergeINTEL: { const auto MergeAttrLits = Deco->getVecLiteral(); std::string FirstString = getString(MergeAttrLits); std::string SecondString = getString(MergeAttrLits.cbegin() + getVec(FirstString).size(), MergeAttrLits.cend()); OPs.push_back(MDString::get(*Context, FirstString)); OPs.push_back(MDString::get(*Context, SecondString)); break; } case DecorationMemoryINTEL: case DecorationUserSemantic: { auto *const StrMD = MDString::get(*Context, getString(Deco->getVecLiteral())); OPs.push_back(StrMD); break; } default: { for (const SPIRVWord Lit : Deco->getVecLiteral()) { auto *const LitMD = ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Lit)); OPs.push_back(LitMD); } break; } } MDs.push_back(MDNode::get(*Context, OPs)); } return MDNode::get(*Context, MDs); } void SPIRVToLLVM::transDecorationsToMetadata(SPIRVValue *BV, Value *V) { if (!BV->isVariable() && !BV->isInst()) return; auto SetDecorationsMetadata = [&](auto V) { std::vector Decorates = BV->getDecorations(); if (!Decorates.empty()) { MDNode *MDList = transDecorationsToMetadataList(Context, Decorates); V->setMetadata(SPIRV_MD_DECORATIONS, MDList); } }; if (auto *GV = dyn_cast(V)) SetDecorationsMetadata(GV); else if (auto *I = dyn_cast(V)) SetDecorationsMetadata(I); } namespace { static float convertSPIRVWordToFloat(SPIRVWord Spir) { union { float F; SPIRVWord Spir; } FPMaxError; FPMaxError.Spir = Spir; return FPMaxError.F; } static bool transFPMaxErrorDecoration(SPIRVValue *BV, Value *V, LLVMContext *Context) { SPIRVWord ID; if (Instruction *I = dyn_cast(V)) if (BV->hasDecorate(DecorationFPMaxErrorDecorationINTEL, 0, &ID)) { auto Literals = BV->getDecorationLiterals(DecorationFPMaxErrorDecorationINTEL); assert(Literals.size() == 1 && "FP Max Error decoration shall have 1 operand"); auto F = convertSPIRVWordToFloat(Literals[0]); if (CallInst *CI = dyn_cast(I)) { // Add attribute auto A = llvm::Attribute::get(*Context, "fpbuiltin-max-error", std::to_string(F)); CI->addFnAttr(A); } else { // Add metadata MDNode *N = MDNode::get(*Context, MDString::get(*Context, std::to_string(F))); I->setMetadata("fpbuiltin-max-error", N); } return true; } return false; } } // namespace bool SPIRVToLLVM::transDecoration(SPIRVValue *BV, Value *V) { if (transFPMaxErrorDecoration(BV, V, Context)) return true; if (!transAlign(BV, V)) return false; transIntelFPGADecorations(BV, V); transMemAliasingINTELDecorations(BV, V); // Decoration metadata is only enabled in SPIR-V friendly mode if (BM->getDesiredBIsRepresentation() == BIsRepresentation::SPIRVFriendlyIR) transDecorationsToMetadata(BV, V); DbgTran->transDbgInfo(BV, V); return true; } void SPIRVToLLVM::transGlobalCtorDtors(SPIRVVariableBase *BV) { if (BV->getName() != "llvm.global_ctors" && BV->getName() != "llvm.global_dtors") return; Value *V = transValue(BV, nullptr, nullptr); cast(V)->setLinkage(GlobalValue::AppendingLinkage); } void SPIRVToLLVM::createCXXStructor(const char *ListName, SmallVectorImpl &Funcs) { if (Funcs.empty()) return; // If the SPIR-V input contained a variable for the structor list and it // has already been translated, then don't interfere. if (M->getGlobalVariable(ListName)) return; // Type of a structor entry: { i32, void ()*, i8* } Type *PriorityTy = Type::getInt32Ty(*Context); PointerType *CtorTy = PointerType::getUnqual(*Context); PointerType *ComdatTy = PointerType::getUnqual(*Context); StructType *StructorTy = StructType::get(PriorityTy, CtorTy, ComdatTy); ArrayType *ArrTy = ArrayType::get(StructorTy, Funcs.size()); GlobalVariable *GV = cast(M->getOrInsertGlobal(ListName, ArrTy)); GV->setLinkage(GlobalValue::AppendingLinkage); // Build the initializer. SmallVector ArrayElts; for (auto *F : Funcs) { SmallVector Elts; // SPIR-V does not specify an order between Initializers, so set default // priority. Elts.push_back(ConstantInt::get(PriorityTy, 65535)); Elts.push_back(ConstantExpr::getBitCast(F, CtorTy)); Elts.push_back(ConstantPointerNull::get(ComdatTy)); ArrayElts.push_back(ConstantStruct::get(StructorTy, Elts)); } Constant *NewArray = ConstantArray::get(ArrTy, ArrayElts); GV->setInitializer(NewArray); } bool SPIRVToLLVM::transFPContractMetadata() { bool ContractOff = false; for (unsigned I = 0, E = BM->getNumFunctions(); I != E; ++I) { SPIRVFunction *BF = BM->getFunction(I); if (!isKernel(BF)) continue; if (BF->getExecutionMode(ExecutionModeContractionOff)) { ContractOff = true; break; } } if (!ContractOff) M->getOrInsertNamedMetadata(kSPIR2MD::FPContract); return true; } std::string SPIRVToLLVM::transOCLImageTypeAccessQualifier(SPIRV::SPIRVTypeImage *ST) { return SPIRSPIRVAccessQualifierMap::rmap(ST->hasAccessQualifier() ? ST->getAccessQualifier() : AccessQualifierReadOnly); } bool SPIRVToLLVM::transNonTemporalMetadata(Instruction *I) { Constant *One = ConstantInt::get(Type::getInt32Ty(*Context), 1); MDNode *Node = MDNode::get(*Context, ConstantAsMetadata::get(One)); I->setMetadata(M->getMDKindID("nontemporal"), Node); return true; } // Information of types of kernel arguments may be additionally stored in // 'OpString "kernel_arg_type.%kernel_name%.type1,type2,type3,..' instruction. // Try to find such instruction and generate metadata based on it. // Return 'true' if 'OpString' was found and 'kernel_arg_type' metadata // generated and 'false' otherwise. static bool transKernelArgTypeMedataFromString(LLVMContext *Ctx, SPIRVModule *BM, Function *Kernel, std::string MDName) { // Run W/A translation only if the appropriate option is passed if (!BM->shouldPreserveOCLKernelArgTypeMetadataThroughString()) return false; std::string ArgTypePrefix = std::string(MDName) + "." + Kernel->getName().str() + "."; auto ArgTypeStrIt = std::find_if( BM->getStringVec().begin(), BM->getStringVec().end(), [=](SPIRVString *S) { return S->getStr().find(ArgTypePrefix) == 0; }); if (ArgTypeStrIt == BM->getStringVec().end()) return false; std::string ArgTypeStr = (*ArgTypeStrIt)->getStr().substr(ArgTypePrefix.size()); std::vector TypeMDs; int CountBraces = 0; std::string::size_type Start = 0; for (std::string::size_type I = 0; I < ArgTypeStr.length(); I++) { switch (ArgTypeStr[I]) { case '<': CountBraces++; break; case '>': CountBraces--; break; case ',': if (CountBraces == 0) { TypeMDs.push_back( MDString::get(*Ctx, ArgTypeStr.substr(Start, I - Start))); Start = I + 1; } } } Kernel->setMetadata(MDName, MDNode::get(*Ctx, TypeMDs)); return true; } void SPIRVToLLVM::transFunctionDecorationsToMetadata(SPIRVFunction *BF, Function *F) { size_t TotalParameterDecorations = 0; BF->foreachArgument([&](SPIRVFunctionParameter *Arg) { TotalParameterDecorations += Arg->getNumDecorations(); }); if (TotalParameterDecorations == 0) return; // Generate metadata for spirv.ParameterDecorations addKernelArgumentMetadata(Context, SPIRV_MD_PARAMETER_DECORATIONS, BF, F, [=](SPIRVFunctionParameter *Arg) { return transDecorationsToMetadataList( Context, Arg->getDecorations()); }); } bool SPIRVToLLVM::transMetadata() { SmallVector CtorKernels; for (unsigned I = 0, E = BM->getNumFunctions(); I != E; ++I) { SPIRVFunction *BF = BM->getFunction(I); Function *F = static_cast(getTranslatedValue(BF)); assert(F && "Invalid translated function"); transOCLMetadata(BF); transVectorComputeMetadata(BF); transFPGAFunctionMetadata(BF, F); // Decoration metadata is only enabled in SPIR-V friendly mode if (BM->getDesiredBIsRepresentation() == BIsRepresentation::SPIRVFriendlyIR) transFunctionDecorationsToMetadata(BF, F); if (F->getCallingConv() != CallingConv::SPIR_KERNEL) continue; // Generate metadata for reqd_work_group_size if (auto *EM = BF->getExecutionMode(ExecutionModeLocalSize)) { F->setMetadata(kSPIR2MD::WGSize, getMDNodeStringIntVec(Context, EM->getLiterals())); } else if (auto *EM = BF->getExecutionModeId(ExecutionModeLocalSizeId)) { std::vector Values; for (const auto Id : EM->getLiterals()) { if (auto Val = transIdAsConstant(Id)) { Values.emplace_back(static_cast(*Val)); } } F->setMetadata(kSPIR2MD::WGSize, getMDNodeStringIntVec(Context, Values)); } // Generate metadata for work_group_size_hint if (auto *EM = BF->getExecutionMode(ExecutionModeLocalSizeHint)) { F->setMetadata(kSPIR2MD::WGSizeHint, getMDNodeStringIntVec(Context, EM->getLiterals())); } else if (auto *EM = BF->getExecutionModeId(ExecutionModeLocalSizeHintId)) { std::vector Values; for (const auto Id : EM->getLiterals()) { if (auto Val = transIdAsConstant(Id)) { Values.emplace_back(static_cast(*Val)); } } F->setMetadata(kSPIR2MD::WGSizeHint, getMDNodeStringIntVec(Context, Values)); } // Generate metadata for vec_type_hint if (auto *EM = BF->getExecutionMode(ExecutionModeVecTypeHint)) { std::vector MetadataVec; Type *VecHintTy = decodeVecTypeHint(*Context, EM->getLiterals()[0]); assert(VecHintTy); MetadataVec.push_back(ValueAsMetadata::get(PoisonValue::get(VecHintTy))); MetadataVec.push_back(ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), 1))); F->setMetadata(kSPIR2MD::VecTyHint, MDNode::get(*Context, MetadataVec)); } // Generate metadata for Initializer. if (BF->getExecutionMode(ExecutionModeInitializer)) { CtorKernels.push_back(F); } // Generate metadata for intel_reqd_sub_group_size if (auto *EM = BF->getExecutionMode(ExecutionModeSubgroupSize)) { auto *SizeMD = ConstantAsMetadata::get(getUInt32(M, EM->getLiterals()[0])); F->setMetadata(kSPIR2MD::SubgroupSize, MDNode::get(*Context, SizeMD)); } // Generate metadata for intel_reqd_sub_group_size if (BF->getExecutionMode(internal::ExecutionModeNamedSubgroupSizeINTEL)) { // For now, there is only one named sub group size: primary, which is // represented as a value of 0 as the argument of the OpExecutionMode. assert(BF->getExecutionMode(internal::ExecutionModeNamedSubgroupSizeINTEL) ->getLiterals()[0] == 0 && "Invalid named sub group size"); // On the LLVM IR side, this is represented as the metadata // intel_reqd_sub_group_size with value -1. auto *SizeMD = ConstantAsMetadata::get(getInt32(M, -1)); F->setMetadata(kSPIR2MD::SubgroupSize, MDNode::get(*Context, SizeMD)); } // Generate metadata for SubgroupsPerWorkgroup/SubgroupsPerWorkgroupId. auto EmitSubgroupsPerWorkgroupMD = [this, F](SPIRVExecutionModeKind EMK, uint64_t Value) { NamedMDNode *ExecModeMD = M->getOrInsertNamedMetadata(kSPIRVMD::ExecutionMode); SmallVector OperandVec; OperandVec.push_back(ConstantAsMetadata::get(F)); OperandVec.push_back(ConstantAsMetadata::get(getUInt32(M, EMK))); OperandVec.push_back(ConstantAsMetadata::get(getUInt32(M, Value))); ExecModeMD->addOperand(MDNode::get(*Context, OperandVec)); }; if (auto *EM = BF->getExecutionMode(ExecutionModeSubgroupsPerWorkgroup)) { EmitSubgroupsPerWorkgroupMD(EM->getExecutionMode(), EM->getLiterals()[0]); } else if (auto *EM = BF->getExecutionModeId( ExecutionModeSubgroupsPerWorkgroupId)) { if (auto Val = transIdAsConstant(EM->getLiterals()[0])) { EmitSubgroupsPerWorkgroupMD(EM->getExecutionMode(), *Val); } } // Generate metadata for max_work_group_size if (auto *EM = BF->getExecutionMode(ExecutionModeMaxWorkgroupSizeINTEL)) { F->setMetadata(kSPIR2MD::MaxWGSize, getMDNodeStringIntVec(Context, EM->getLiterals())); } // Generate metadata for no_global_work_offset if (BF->getExecutionMode(ExecutionModeNoGlobalOffsetINTEL)) { F->setMetadata(kSPIR2MD::NoGlobalOffset, MDNode::get(*Context, {})); } // Generate metadata for max_global_work_dim if (auto *EM = BF->getExecutionMode(ExecutionModeMaxWorkDimINTEL)) { F->setMetadata(kSPIR2MD::MaxWGDim, getMDNodeStringIntVec(Context, EM->getLiterals())); } // Generate metadata for num_simd_work_items if (auto *EM = BF->getExecutionMode(ExecutionModeNumSIMDWorkitemsINTEL)) { F->setMetadata(kSPIR2MD::NumSIMD, getMDNodeStringIntVec(Context, EM->getLiterals())); } // Generate metadata for scheduler_target_fmax_mhz if (auto *EM = BF->getExecutionMode(ExecutionModeSchedulerTargetFmaxMhzINTEL)) { F->setMetadata(kSPIR2MD::FmaxMhz, getMDNodeStringIntVec(Context, EM->getLiterals())); } // Generate metadata for Intel FPGA register map interface if (auto *EM = BF->getExecutionMode(ExecutionModeRegisterMapInterfaceINTEL)) { std::vector InterfaceVec = EM->getLiterals(); assert(InterfaceVec.size() == 1 && "Expected RegisterMapInterfaceINTEL to have exactly 1 literal"); std::vector InterfaceMDVec = [&]() -> std::vector { switch (InterfaceVec[0]) { case 0: return {MDString::get(*Context, "csr")}; case 1: return {MDString::get(*Context, "csr"), MDString::get(*Context, "wait_for_done_write")}; default: llvm_unreachable("Invalid register map interface mode"); } }(); F->setMetadata(kSPIR2MD::IntelFPGAIPInterface, MDNode::get(*Context, InterfaceMDVec)); } // Generate metadata for Intel FPGA streaming interface if (auto *EM = BF->getExecutionMode(ExecutionModeStreamingInterfaceINTEL)) { std::vector InterfaceVec = EM->getLiterals(); assert(InterfaceVec.size() == 1 && "Expected StreamingInterfaceINTEL to have exactly 1 literal"); std::vector InterfaceMDVec = [&]() -> std::vector { switch (InterfaceVec[0]) { case 0: return {MDString::get(*Context, "streaming")}; case 1: return {MDString::get(*Context, "streaming"), MDString::get(*Context, "stall_free_return")}; default: llvm_unreachable("Invalid streaming interface mode"); } }(); F->setMetadata(kSPIR2MD::IntelFPGAIPInterface, MDNode::get(*Context, InterfaceMDVec)); } if (auto *EM = BF->getExecutionMode(ExecutionModeMaximumRegistersINTEL)) { NamedMDNode *ExecModeMD = M->getOrInsertNamedMetadata(kSPIRVMD::ExecutionMode); SmallVector ValueVec; ValueVec.push_back(ConstantAsMetadata::get(F)); ValueVec.push_back( ConstantAsMetadata::get(getUInt32(M, EM->getExecutionMode()))); ValueVec.push_back( ConstantAsMetadata::get(getUInt32(M, EM->getLiterals()[0]))); ExecModeMD->addOperand(MDNode::get(*Context, ValueVec)); } if (auto *EM = BF->getExecutionMode(ExecutionModeMaximumRegistersIdINTEL)) { NamedMDNode *ExecModeMD = M->getOrInsertNamedMetadata(kSPIRVMD::ExecutionMode); SmallVector ValueVec; ValueVec.push_back(ConstantAsMetadata::get(F)); ValueVec.push_back( ConstantAsMetadata::get(getUInt32(M, EM->getExecutionMode()))); auto *ExecOp = BF->getModule()->getValue(EM->getLiterals()[0]); ValueVec.push_back( MDNode::get(*Context, ConstantAsMetadata::get(cast( transValue(ExecOp, nullptr, nullptr))))); ExecModeMD->addOperand(MDNode::get(*Context, ValueVec)); } if (auto *EM = BF->getExecutionMode(ExecutionModeNamedMaximumRegistersINTEL)) { NamedMDNode *ExecModeMD = M->getOrInsertNamedMetadata(kSPIRVMD::ExecutionMode); SmallVector ValueVec; ValueVec.push_back(ConstantAsMetadata::get(F)); ValueVec.push_back( ConstantAsMetadata::get(getUInt32(M, EM->getExecutionMode()))); assert(EM->getLiterals()[0] == 0 && "Invalid named maximum number of registers"); ValueVec.push_back(MDString::get(*Context, "AutoINTEL")); ExecModeMD->addOperand(MDNode::get(*Context, ValueVec)); } } NamedMDNode *MemoryModelMD = M->getOrInsertNamedMetadata(kSPIRVMD::MemoryModel); MemoryModelMD->addOperand( getMDTwoInt(Context, static_cast(BM->getAddressingModel()), static_cast(BM->getMemoryModel()))); createCXXStructor("llvm.global_ctors", CtorKernels); return true; } bool SPIRVToLLVM::transOCLMetadata(SPIRVFunction *BF) { Function *F = static_cast(getTranslatedValue(BF)); assert(F && "Invalid translated function"); if (F->getCallingConv() != CallingConv::SPIR_KERNEL) return true; if (BF->hasDecorate(DecorationVectorComputeFunctionINTEL)) return true; // Generate metadata for kernel_arg_addr_space addKernelArgumentMetadata( Context, SPIR_MD_KERNEL_ARG_ADDR_SPACE, BF, F, [=](SPIRVFunctionParameter *Arg) { SPIRVType *ArgTy = Arg->getType(); SPIRAddressSpace AS = SPIRAS_Private; if (ArgTy->isTypePointer()) AS = SPIRSPIRVAddrSpaceMap::rmap(ArgTy->getPointerStorageClass()); else if (ArgTy->isTypeOCLImage() || ArgTy->isTypePipe()) AS = SPIRAS_Global; return ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), AS)); }); // Generate metadata for kernel_arg_access_qual addKernelArgumentMetadata(Context, SPIR_MD_KERNEL_ARG_ACCESS_QUAL, BF, F, [=](SPIRVFunctionParameter *Arg) { std::string Qual; auto *T = Arg->getType(); if (T->isTypeOCLImage()) { auto *ST = static_cast(T); Qual = transOCLImageTypeAccessQualifier(ST); } else if (T->isTypePipe()) { auto *PT = static_cast(T); Qual = transOCLPipeTypeAccessQualifier(PT); } else Qual = "none"; return MDString::get(*Context, Qual); }); // Generate metadata for kernel_arg_type if (!transKernelArgTypeMedataFromString(Context, BM, F, SPIR_MD_KERNEL_ARG_TYPE)) addKernelArgumentMetadata(Context, SPIR_MD_KERNEL_ARG_TYPE, BF, F, [=](SPIRVFunctionParameter *Arg) { return transOCLKernelArgTypeName(Arg); }); // Generate metadata for kernel_arg_type_qual if (!transKernelArgTypeMedataFromString(Context, BM, F, SPIR_MD_KERNEL_ARG_TYPE_QUAL)) addKernelArgumentMetadata( Context, SPIR_MD_KERNEL_ARG_TYPE_QUAL, BF, F, [=](SPIRVFunctionParameter *Arg) { std::string Qual; if (Arg->hasDecorate(DecorationVolatile)) Qual = kOCLTypeQualifierName::Volatile; Arg->foreachAttr([&](SPIRVFuncParamAttrKind Kind) { Qual += Qual.empty() ? "" : " "; if (Kind == FunctionParameterAttributeNoAlias) Qual += kOCLTypeQualifierName::Restrict; }); if (Arg->getType()->isTypePipe()) { Qual += Qual.empty() ? "" : " "; Qual += kOCLTypeQualifierName::Pipe; } return MDString::get(*Context, Qual); }); // Generate metadata for kernel_arg_base_type addKernelArgumentMetadata(Context, SPIR_MD_KERNEL_ARG_BASE_TYPE, BF, F, [=](SPIRVFunctionParameter *Arg) { return transOCLKernelArgTypeName(Arg); }); // Generate metadata for kernel_arg_name if (BM->isGenArgNameMDEnabled()) { addKernelArgumentMetadata(Context, SPIR_MD_KERNEL_ARG_NAME, BF, F, [=](SPIRVFunctionParameter *Arg) { return MDString::get(*Context, Arg->getName()); }); } // Generate metadata for kernel_arg_buffer_location addBufferLocationMetadata(Context, BF, F, [=](SPIRVFunctionParameter *Arg) { auto Literals = Arg->getDecorationLiterals(DecorationBufferLocationINTEL); assert(Literals.size() == 1 && "BufferLocationINTEL decoration shall have 1 ID literal"); return ConstantAsMetadata::get( ConstantInt::get(Type::getInt32Ty(*Context), Literals[0])); }); // Generate metadata for kernel_arg_runtime_aligned addRuntimeAlignedMetadata(Context, BF, F, [=](SPIRVFunctionParameter *Arg) { return ConstantAsMetadata::get( ConstantInt::get(Type::getInt1Ty(*Context), 1)); }); // Generate metadata for spirv.ParameterDecorations. // In the entry-point wrapper pattern two SPIR-V functions map to the same // LLVM function; only one carries decorated parameters. Skip the // undecorated function so it cannot overwrite the decorated one. bool HasDecorations = false; BF->foreachArgument([&](SPIRVFunctionParameter *Arg) { HasDecorations |= Arg->getNumDecorations() > 0; }); if (HasDecorations || !F->hasMetadata(SPIRV_MD_PARAMETER_DECORATIONS)) addKernelArgumentMetadata(Context, SPIRV_MD_PARAMETER_DECORATIONS, BF, F, [=](SPIRVFunctionParameter *Arg) { return transDecorationsToMetadataList( Context, Arg->getDecorations()); }); return true; } bool SPIRVToLLVM::transVectorComputeMetadata(SPIRVFunction *BF) { using namespace VectorComputeUtil; Function *F = static_cast(getTranslatedValue(BF)); assert(F && "Invalid translated function"); if (BF->hasDecorate(DecorationStackCallINTEL)) F->addFnAttr(kVCMetadata::VCStackCall); if (BF->hasDecorate(DecorationVectorComputeFunctionINTEL)) F->addFnAttr(kVCMetadata::VCFunction); SPIRVWord SIMTMode = 0; if (BF->hasDecorate(DecorationSIMTCallINTEL, 0, &SIMTMode)) F->addFnAttr(kVCMetadata::VCSIMTCall, std::to_string(SIMTMode)); auto SEVAttr = translateSEVMetadata(BF, F->getContext()); if (SEVAttr) F->addAttributeAtIndex(AttributeList::ReturnIndex, SEVAttr.value()); for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E; ++I) { auto ArgNo = I->getArgNo(); SPIRVFunctionParameter *BA = BF->getArgument(ArgNo); SPIRVWord Kind; if (BA->hasDecorate(DecorationFuncParamIOKindINTEL, 0, &Kind)) { Attribute Attr = Attribute::get(*Context, kVCMetadata::VCArgumentIOKind, std::to_string(Kind)); F->addParamAttr(ArgNo, Attr); } SEVAttr = translateSEVMetadata(BA, F->getContext()); if (SEVAttr) F->addParamAttr(ArgNo, SEVAttr.value()); if (BA->hasDecorate(DecorationMediaBlockIOINTEL)) { assert(BA->getType()->isTypeImage() && "MediaBlockIOINTEL decoration is valid only on image parameters"); F->addParamAttr(ArgNo, Attribute::get(*Context, kVCMetadata::VCMediaBlockIO)); } } // Do not add float control if there is no any bool IsVCFloatControl = false; unsigned FloatControl = 0; // RoundMode and FloatMode are always same for all types in Cm // While Denorm could be different for double, float and half if (isKernel(BF)) { FPRoundingModeExecModeMap::foreach ( [&](FPRoundingMode VCRM, ExecutionMode EM) { if (BF->getExecutionMode(EM)) { IsVCFloatControl = true; FloatControl |= getVCFloatControl(VCRM); } }); FPOperationModeExecModeMap::foreach ( [&](FPOperationMode VCFM, ExecutionMode EM) { if (BF->getExecutionMode(EM)) { IsVCFloatControl = true; FloatControl |= getVCFloatControl(VCFM); } }); FPDenormModeExecModeMap::foreach ([&](FPDenormMode VCDM, ExecutionMode EM) { auto ExecModes = BF->getExecutionModeRange(EM); for (auto It = ExecModes.first; It != ExecModes.second; It++) { IsVCFloatControl = true; unsigned TargetWidth = (*It).second->getLiterals()[0]; VCFloatType FloatType = VCFloatTypeSizeMap::rmap(TargetWidth); FloatControl |= getVCFloatControl(VCDM, FloatType); } }); } else { if (BF->hasDecorate(DecorationFunctionRoundingModeINTEL)) { std::vector RoundModes = BF->getDecorations(DecorationFunctionRoundingModeINTEL); assert(RoundModes.size() == 3 && "Function must have precisely 3 " "FunctionRoundingModeINTEL decoration"); auto *DecRound = static_cast( RoundModes.at(0)); auto RoundingMode = DecRound->getRoundingMode(); #ifndef NDEBUG for (auto *DecPreCast : RoundModes) { auto *Dec = static_cast( DecPreCast); assert(Dec->getRoundingMode() == RoundingMode && "Rounding Mode must be equal within all targets"); } #endif IsVCFloatControl = true; FloatControl |= getVCFloatControl(RoundingMode); } if (BF->hasDecorate(DecorationFunctionDenormModeINTEL)) { std::vector DenormModes = BF->getDecorations(DecorationFunctionDenormModeINTEL); IsVCFloatControl = true; for (const auto *DecPtr : DenormModes) { const auto *DecDenorm = static_cast(DecPtr); VCFloatType FType = VCFloatTypeSizeMap::rmap(DecDenorm->getTargetWidth()); FloatControl |= getVCFloatControl(DecDenorm->getDenormMode(), FType); } } if (BF->hasDecorate(DecorationFunctionFloatingPointModeINTEL)) { std::vector FloatModes = BF->getDecorations(DecorationFunctionFloatingPointModeINTEL); assert(FloatModes.size() == 3 && "Function must have precisely 3 FunctionFloatingPointModeINTEL " "decoration"); auto *DecFlt = static_cast( FloatModes.at(0)); auto FloatingMode = DecFlt->getOperationMode(); #ifndef NDEBUG for (auto *DecPreCast : FloatModes) { auto *Dec = static_cast( DecPreCast); assert(Dec->getOperationMode() == FloatingMode && "Rounding Mode must be equal within all targets"); } #endif IsVCFloatControl = true; FloatControl |= getVCFloatControl(FloatingMode); } } if (IsVCFloatControl) { Attribute Attr = Attribute::get(*Context, kVCMetadata::VCFloatControl, std::to_string(FloatControl)); F->addFnAttr(Attr); } if (auto *EM = BF->getExecutionMode(ExecutionModeSharedLocalMemorySizeINTEL)) { unsigned int SLMSize = EM->getLiterals()[0]; Attribute Attr = Attribute::get(*Context, kVCMetadata::VCSLMSize, std::to_string(SLMSize)); F->addFnAttr(Attr); } if (auto *EM = BF->getExecutionMode(ExecutionModeNamedBarrierCountINTEL)) { unsigned int NBarrierCnt = EM->getLiterals()[0]; Attribute Attr = Attribute::get(*Context, kVCMetadata::VCNamedBarrierCount, std::to_string(NBarrierCnt)); F->addFnAttr(Attr); } return true; } bool SPIRVToLLVM::transFPGAFunctionMetadata(SPIRVFunction *BF, Function *F) { if (BF->hasDecorate(DecorationStallEnableINTEL)) { std::vector MetadataVec; MetadataVec.push_back(ConstantAsMetadata::get(getInt32(M, 1))); F->setMetadata(kSPIR2MD::StallEnable, MDNode::get(*Context, MetadataVec)); } if (BF->hasDecorate(DecorationStallFreeINTEL)) { std::vector MetadataVec; MetadataVec.push_back(ConstantAsMetadata::get(getInt32(M, 1))); F->setMetadata(kSPIR2MD::StallFree, MDNode::get(*Context, MetadataVec)); } if (BF->hasDecorate(DecorationFuseLoopsInFunctionINTEL)) { std::vector MetadataVec; auto Literals = BF->getDecorationLiterals(DecorationFuseLoopsInFunctionINTEL); MetadataVec.push_back(ConstantAsMetadata::get(getUInt32(M, Literals[0]))); MetadataVec.push_back(ConstantAsMetadata::get(getUInt32(M, Literals[1]))); F->setMetadata(kSPIR2MD::LoopFuse, MDNode::get(*Context, MetadataVec)); } if (BF->hasDecorate(DecorationMathOpDSPModeINTEL)) { std::vector Literals = BF->getDecorationLiterals(DecorationMathOpDSPModeINTEL); assert(Literals.size() == 2 && "MathOpDSPModeINTEL decoration shall have 2 literals"); F->setMetadata(kSPIR2MD::PreferDSP, MDNode::get(*Context, ConstantAsMetadata::get( getUInt32(M, Literals[0])))); if (Literals[1] != 0) { F->setMetadata(kSPIR2MD::PropDSPPref, MDNode::get(*Context, ConstantAsMetadata::get( getUInt32(M, Literals[1])))); } } if (BF->hasDecorate(DecorationInitiationIntervalINTEL)) { std::vector MetadataVec; auto Literals = BF->getDecorationLiterals(DecorationInitiationIntervalINTEL); MetadataVec.push_back(ConstantAsMetadata::get(getUInt32(M, Literals[0]))); F->setMetadata(kSPIR2MD::InitiationInterval, MDNode::get(*Context, MetadataVec)); } if (BF->hasDecorate(DecorationMaxConcurrencyINTEL)) { std::vector MetadataVec; auto Literals = BF->getDecorationLiterals(DecorationMaxConcurrencyINTEL); MetadataVec.push_back(ConstantAsMetadata::get(getUInt32(M, Literals[0]))); F->setMetadata(kSPIR2MD::MaxConcurrency, MDNode::get(*Context, MetadataVec)); } if (BF->hasDecorate(DecorationPipelineEnableINTEL)) { auto Literals = BF->getDecorationLiterals(DecorationPipelineEnableINTEL); std::vector MetadataVec; MetadataVec.push_back(ConstantAsMetadata::get(getInt32(M, Literals[0]))); F->setMetadata(kSPIR2MD::PipelineKernel, MDNode::get(*Context, MetadataVec)); } return true; } bool SPIRVToLLVM::transAlign(SPIRVValue *BV, Value *V) { if (auto *AL = dyn_cast(V)) { if (auto Align = getAlignment(BV)) AL->setAlignment(llvm::Align(*Align)); return true; } if (auto *GV = dyn_cast(V)) { if (auto Align = getAlignment(BV)) GV->setAlignment(MaybeAlign(*Align)); return true; } return true; } Instruction *SPIRVToLLVM::transOCLBuiltinFromExtInst(SPIRVExtInst *BC, BasicBlock *BB) { assert(BB && "Invalid BB"); auto ExtOp = static_cast(BC->getExtOp()); std::string UnmangledName = OCLExtOpMap::map(ExtOp); assert(BM->getBuiltinSet(BC->getExtSetId()) == SPIRVEIS_OpenCL && "Not OpenCL extended instruction"); std::vector ArgTypes = transTypeVector(BC->getArgTypes(), true); for (unsigned I = 0; I < ArgTypes.size(); I++) { // Special handling for "truly" untyped pointers to preserve correct OCL // bultin mangling. if (isa(ArgTypes[I]) && BC->getArgValue(I)->isUntypedVariable()) { auto *BVar = static_cast(BC->getArgValue(I)); ArgTypes[I] = TypedPointerType::get( transType(BVar->getDataType()), SPIRSPIRVAddrSpaceMap::rmap(BVar->getStorageClass())); } } Type *RetTy = transType(BC->getType()); std::string MangledName = getSPIRVFriendlyIRFunctionName(ExtOp, ArgTypes, RetTy); opaquifyTypedPointers(ArgTypes); SPIRVDBG(spvdbgs() << "[transOCLBuiltinFromExtInst] UnmangledName: " << UnmangledName << " MangledName: " << MangledName << '\n'); FunctionType *FT = FunctionType::get(RetTy, ArgTypes, false); Function *F = M->getFunction(MangledName); if (!F) { F = Function::Create(FT, GlobalValue::ExternalLinkage, MangledName, M); F->setCallingConv(CallingConv::SPIR_FUNC); if (isFuncNoUnwind()) F->addFnAttr(Attribute::NoUnwind); if (isFuncReadNone(UnmangledName)) F->setDoesNotAccessMemory(); } auto Args = transValue(BC->getArgValues(), F, BB); SPIRVDBG(dbgs() << "[transOCLBuiltinFromExtInst] Function: " << *F << ", Args: "; for (auto &I : Args) dbgs() << *I << ", "; dbgs() << '\n'); CallInst *CI = CallInst::Create(F, Args, BC->getName(), BB); setCallingConv(CI); addFnAttr(CI, Attribute::NoUnwind); return CI; } void SPIRVToLLVM::transAuxDataInst(SPIRVExtInst *BC) { assert(BC->getExtSetKind() == SPIRV::SPIRVEIS_NonSemantic_AuxData); if (!BC->getModule()->preserveAuxData()) return; auto Args = BC->getArguments(); // Args 0 and 1 are common between attributes and metadata. // 0 is the global object, 1 is the name of the attribute/metadata as a string auto *Arg0 = BC->getModule()->getValue(Args[0]); auto *GO = cast(getTranslatedValue(Arg0)); auto *F = dyn_cast(GO); auto *GV = dyn_cast(GO); assert((F || GV) && "Value should already have been translated!"); auto AttrOrMDName = BC->getModule()->get(Args[1])->getStr(); switch (BC->getExtOp()) { case NonSemanticAuxData::FunctionAttribute: case NonSemanticAuxData::GlobalVariableAttribute: { assert(Args.size() < 4 && "Unexpected FunctionAttribute Args"); // If this attr was specially handled and added elsewhere, skip it. Attribute::AttrKind AsKind = Attribute::getAttrKindFromName(AttrOrMDName); if (AsKind != Attribute::None) if ((F && F->hasFnAttribute(AsKind)) || (GV && GV->hasAttribute(AsKind))) return; if (AsKind == Attribute::None) if ((F && F->hasFnAttribute(AttrOrMDName)) || (GV && GV->hasAttribute(AttrOrMDName))) return; // For attributes, arg 2 is the attribute value as a string, which may not // exist. if (Args.size() == 3) { auto AttrValue = BC->getModule()->get(Args[2])->getStr(); if (F) F->addFnAttr(AttrOrMDName, AttrValue); else GV->addAttribute(AttrOrMDName, AttrValue); } else { if (AsKind != Attribute::None) { if (F) F->addFnAttr(AsKind); else GV->addAttribute(AsKind); } else { if (F) F->addFnAttr(AttrOrMDName); else GV->addAttribute(AttrOrMDName); } } break; } case NonSemanticAuxData::FunctionMetadata: case NonSemanticAuxData::GlobalVariableMetadata: { // If this metadata was specially handled and added elsewhere, skip it. if (GO->hasMetadata(AttrOrMDName)) return; SmallVector MetadataArgs; // Process the metadata values. for (size_t CurArg = 2; CurArg < Args.size(); CurArg++) { auto *Arg = BC->getModule()->get(Args[CurArg]); // For metadata, the metadata values can be either values or strings. if (Arg->getOpCode() == OpString) { auto *ArgAsStr = static_cast(Arg); MetadataArgs.push_back( MDString::get(GO->getContext(), ArgAsStr->getStr())); } else { auto *ArgAsVal = static_cast(Arg); auto *TranslatedMD = transValue(ArgAsVal, nullptr, nullptr); MetadataArgs.push_back(ValueAsMetadata::get(TranslatedMD)); } } GO->setMetadata(AttrOrMDName, MDNode::get(*Context, MetadataArgs)); break; } default: llvm_unreachable("Invalid op"); } } // SPIR-V only contains language version. Use OpenCL language version as // SPIR version. void SPIRVToLLVM::transSourceLanguage() { SPIRVWord Ver = 0; SourceLanguage Lang = BM->getSourceLanguage(&Ver); if (Lang != SourceLanguageUnknown && // Allow unknown for debug info test Lang != SourceLanguageOpenCL_C && Lang != SourceLanguageCPP_for_OpenCL && Lang != SourceLanguageOpenCL_CPP) return; unsigned short Major = 0; unsigned char Minor = 0; unsigned char Rev = 0; std::tie(Major, Minor, Rev) = decodeOCLVer(Ver); SPIRVMDBuilder Builder(*M); Builder.addNamedMD(kSPIRVMD::Source).addOp().add(Lang).add(Ver).done(); // ToDo: Phasing out usage of old SPIR metadata if (Ver <= kOCLVer::CL12) addOCLVersionMetadata(Context, M, kSPIR2MD::SPIRVer, 1, 2); else addOCLVersionMetadata(Context, M, kSPIR2MD::SPIRVer, 2, 0); if (Lang == SourceLanguageOpenCL_C) { addOCLVersionMetadata(Context, M, kSPIR2MD::OCLVer, Major, Minor); return; } if (Lang == SourceLanguageCPP_for_OpenCL) { addOCLVersionMetadata(Context, M, kSPIR2MD::OCLCXXVer, Major, Minor); addOCLVersionMetadata(Context, M, kSPIR2MD::OCLVer, Ver == kOCLVer::CLCXX10 ? 2 : 3, 0); } } bool SPIRVToLLVM::transSourceExtension() { auto ExtSet = rmap(BM->getExtension()); auto CapSet = rmap(BM->getCapability()); ExtSet.insert(CapSet.begin(), CapSet.end()); auto OCLExtensions = map(ExtSet); std::set OCLOptionalCoreFeatures; static const char *OCLOptCoreFeatureNames[] = { "cl_images", "cl_doubles", }; for (auto &I : OCLOptCoreFeatureNames) { auto Loc = OCLExtensions.find(I); if (Loc != OCLExtensions.end()) { OCLExtensions.erase(Loc); OCLOptionalCoreFeatures.insert(I); } } addNamedMetadataStringSet(Context, M, kSPIR2MD::Extensions, OCLExtensions); addNamedMetadataStringSet(Context, M, kSPIR2MD::OptFeatures, OCLOptionalCoreFeatures); return true; } llvm::GlobalValue::LinkageTypes SPIRVToLLVM::transLinkageType(const SPIRVValue *V) { std::string ValueName = V->getName(); if (ValueName == "llvm.used" || ValueName == "llvm.compiler.used") return GlobalValue::AppendingLinkage; int LT = V->getLinkageType(); switch (LT) { case internal::LinkageTypeInternal: return GlobalValue::InternalLinkage; case LinkageTypeImport: // Function declaration if (V->getOpCode() == OpFunction) { if (static_cast(V)->getNumBasicBlock() == 0) return GlobalValue::ExternalLinkage; } // Variable declaration if (V->getOpCode() == OpVariable || V->getOpCode() == OpUntypedVariableKHR) { if (static_cast(V)->getInitializer() == 0) return GlobalValue::ExternalLinkage; } // Definition return GlobalValue::AvailableExternallyLinkage; case LinkageTypeExport: if (V->getOpCode() == OpVariable || V->getOpCode() == OpUntypedVariableKHR) { if (static_cast(V)->getInitializer() == 0) // Tentative definition return GlobalValue::CommonLinkage; } return GlobalValue::ExternalLinkage; case LinkageTypeLinkOnceODR: return GlobalValue::LinkOnceODRLinkage; default: llvm_unreachable("Invalid linkage type"); } } Instruction *SPIRVToLLVM::transAllAny(SPIRVInstruction *I, BasicBlock *BB) { CallInst *CI = cast(transSPIRVBuiltinFromInst(I, BB)); auto Mutator = mutateCallInst( CI, getSPIRVFuncName(I->getOpCode(), getSPIRVFuncSuffix(I))); Mutator.mapArg(0, [](IRBuilder<> &Builder, Value *OldArg) { auto *NewArgTy = OldArg->getType()->getWithNewBitWidth(8); return Builder.CreateSExtOrBitCast(OldArg, NewArgTy); }); return cast(Mutator.getMutated()); } Instruction *SPIRVToLLVM::transRelational(SPIRVInstruction *I, BasicBlock *BB) { CallInst *CI = cast(transSPIRVBuiltinFromInst(I, BB)); auto Mutator = mutateCallInst( CI, getSPIRVFuncName(I->getOpCode(), getSPIRVFuncSuffix(I))); if (CI->getType()->isVectorTy()) { Type *RetTy = CI->getType()->getWithNewBitWidth(8); Mutator.changeReturnType(RetTy, [=](IRBuilder<> &Builder, CallInst *NewCI) { return Builder.CreateTruncOrBitCast(NewCI, CI->getType()); }); } return cast(Mutator.getMutated()); } std::optional getSpirvReport(std::istream &IS) { int IgnoreErrCode; return getSpirvReport(IS, IgnoreErrCode); } std::optional getSpirvReport(std::istream &IS, int &ErrCode) { SPIRVWord Word; std::string Name; std::unique_ptr BM(SPIRVModule::createSPIRVModule()); SPIRVDecoder D(IS, *BM); D >> Word; if (Word != MagicNumber) { ErrCode = SPIRVEC_InvalidMagicNumber; return {}; } D >> Word; if (!isSPIRVVersionKnown(static_cast(Word))) { ErrCode = SPIRVEC_InvalidVersionNumber; return {}; } SPIRVModuleReport Report; Report.Version = static_cast(Word); // Skip: Generator’s magic number, Bound and Reserved word D.ignore(3); bool IsReportGenCompleted = false, IsMemoryModelDefined = false; while (!IS.bad() && !IsReportGenCompleted && D.getWordCountAndOpCode()) { switch (D.OpCode) { case OpCapability: D >> Word; Report.Capabilities.push_back(Word); break; case OpExtension: Name.clear(); D >> Name; Report.Extensions.push_back(Name); break; case OpExtInstImport: Name.clear(); D >> Word >> Name; Report.ExtendedInstructionSets.push_back(Name); break; case OpMemoryModel: if (IsMemoryModelDefined) { ErrCode = SPIRVEC_RepeatedMemoryModel; return {}; } SPIRVAddressingModelKind AddrModel; SPIRVMemoryModelKind MemoryModel; D >> AddrModel >> MemoryModel; if (!isValid(AddrModel)) { ErrCode = SPIRVEC_InvalidAddressingModel; return {}; } if (!isValid(MemoryModel)) { ErrCode = SPIRVEC_InvalidMemoryModel; return {}; } Report.MemoryModel = MemoryModel; Report.AddrModel = AddrModel; IsMemoryModelDefined = true; // In this report we don't analyze instructions after OpMemoryModel IsReportGenCompleted = true; break; default: // No more instructions to gather information about IsReportGenCompleted = true; } } if (IS.bad()) { ErrCode = SPIRVEC_InvalidModule; return {}; } if (!IsMemoryModelDefined) { ErrCode = SPIRVEC_UnspecifiedMemoryModel; return {}; } ErrCode = SPIRVEC_Success; return std::make_optional(std::move(Report)); } constexpr std::string_view formatAddressingModel(uint32_t AddrModel) { switch (AddrModel) { case AddressingModelLogical: return "Logical"; case AddressingModelPhysical32: return "Physical32"; case AddressingModelPhysical64: return "Physical64"; case AddressingModelPhysicalStorageBuffer64: return "PhysicalStorageBuffer64"; default: return "Unknown"; } } constexpr std::string_view formatMemoryModel(uint32_t MemoryModel) { switch (MemoryModel) { case MemoryModelSimple: return "Simple"; case MemoryModelGLSL450: return "GLSL450"; case MemoryModelOpenCL: return "OpenCL"; case MemoryModelVulkan: return "Vulkan"; default: return "Unknown"; } } SPIRVModuleTextReport formatSpirvReport(const SPIRVModuleReport &Report) { SPIRVModuleTextReport TextReport; TextReport.Version = formatVersionNumber(static_cast(Report.Version)); TextReport.AddrModel = formatAddressingModel(Report.AddrModel); TextReport.MemoryModel = formatMemoryModel(Report.MemoryModel); // format capability codes as strings std::string Name; for (auto Capability : Report.Capabilities) { bool Found = SPIRVCapabilityNameMap::find( static_cast(Capability), &Name); TextReport.Capabilities.push_back(Found ? Name : "Unknown"); } // other fields with string content can be copied as is TextReport.Extensions = Report.Extensions; TextReport.ExtendedInstructionSets = Report.ExtendedInstructionSets; return TextReport; } std::unique_ptr readSpirvModule(std::istream &IS, const SPIRV::TranslatorOpts &Opts, std::string &ErrMsg) { std::unique_ptr BM(SPIRVModule::createSPIRVModule(Opts)); IS >> *BM; if (!BM->isModuleValid()) { BM->getError(ErrMsg); return nullptr; } return BM; } std::unique_ptr readSpirvModule(std::istream &IS, std::string &ErrMsg) { SPIRV::TranslatorOpts DefaultOpts; return readSpirvModule(IS, DefaultOpts, ErrMsg); } } // namespace SPIRV std::unique_ptr llvm::convertSpirvToLLVM(LLVMContext &C, SPIRVModule &BM, const SPIRV::TranslatorOpts &Opts, std::string &ErrMsg) { std::unique_ptr M(new Module("", C)); SPIRVToLLVM BTL(M.get(), &BM); if (!BTL.translate()) { BM.getError(ErrMsg); return nullptr; } llvm::ModulePassManager PassMgr; addSPIRVBIsLoweringPass(PassMgr, Opts.getDesiredBIsRepresentation()); llvm::ModuleAnalysisManager MAM; MAM.registerPass([&] { return PassInstrumentationAnalysis(); }); PassMgr.run(*M, MAM); return M; } std::unique_ptr llvm::convertSpirvToLLVM(LLVMContext &C, SPIRVModule &BM, std::string &ErrMsg) { SPIRV::TranslatorOpts DefaultOpts; return llvm::convertSpirvToLLVM(C, BM, DefaultOpts, ErrMsg); } bool llvm::readSpirv(LLVMContext &C, std::istream &IS, Module *&M, std::string &ErrMsg) { SPIRV::TranslatorOpts DefaultOpts; // As it is stated in the documentation, the translator accepts all SPIR-V // extensions by default DefaultOpts.enableAllExtensions(); return llvm::readSpirv(C, DefaultOpts, IS, M, ErrMsg); } bool llvm::readSpirv(LLVMContext &C, const SPIRV::TranslatorOpts &Opts, std::istream &IS, Module *&M, std::string &ErrMsg) { std::unique_ptr BM(readSpirvModule(IS, Opts, ErrMsg)); if (!BM) return false; if (Opts.getFnVarSpecEnable()) { if (!specializeFnVariants(BM.get(), ErrMsg)) { return false; } // Write out the specialized/targeted module if (!BM->getFnVarSpvOut().empty()) { auto SaveOpt = SPIRVUseTextFormat; auto OFSSpv = std::ofstream(BM->getFnVarSpvOut(), std::ios::binary); SPIRVUseTextFormat = false; OFSSpv << *BM; if (BM->getError(ErrMsg) != SPIRVEC_Success) { return false; } SPIRVUseTextFormat = SaveOpt; } } if (BM->getExtension().find("SPV_INTEL_function_variants") != BM->getExtension().end()) { ErrMsg = "Instructions from SPV_INTEL_function_variants are not " "convertible to LLVM IR."; return false; } M = convertSpirvToLLVM(C, *BM, Opts, ErrMsg).release(); if (!M) return false; if (DbgSaveTmpLLVM) dumpLLVM(M, DbgTmpLLVMFileName); return true; } bool llvm::getSpecConstInfo(std::istream &IS, std::vector &SpecConstInfo) { std::unique_ptr BM(SPIRVModule::createSPIRVModule()); BM->setAutoAddExtensions(false); SPIRVDecoder D(IS, *BM); SPIRVWord Magic; D >> Magic; if (!BM->getErrorLog().checkError(Magic == MagicNumber, SPIRVEC_InvalidModule, "invalid magic number")) { return false; } // Skip the rest of the header D.ignore(4); // According to the logical layout of SPIRV module (p2.4 of the spec), // all constant instructions must appear before function declarations. while (D.OpCode != OpFunction && D.getWordCountAndOpCode()) { switch (D.OpCode) { case OpDecorate: // The decoration is added to the module in scope of SPIRVDecorate::decode D.getEntry(); break; case OpTypeBool: case OpTypeInt: case OpTypeFloat: BM->addEntry(D.getEntry()); break; case OpSpecConstant: case OpSpecConstantTrue: case OpSpecConstantFalse: { auto *C = BM->addConstant(static_cast(D.getEntry())); SPIRVWord SpecConstIdLiteral = 0; if (C->hasDecorate(DecorationSpecId, 0, &SpecConstIdLiteral)) { SPIRVType *Ty = C->getType(); uint32_t SpecConstSize = Ty->isTypeBool() ? 1 : Ty->getBitWidth() / 8; std::string TypeString = ""; if (Ty->isTypeBool()) { TypeString = "i1"; } else if (Ty->isTypeInt()) { switch (SpecConstSize) { case 1: TypeString = "i8"; break; case 2: TypeString = "i16"; break; case 4: TypeString = "i32"; break; case 8: TypeString = "i64"; break; } } else if (Ty->isTypeFloat()) { switch (SpecConstSize) { case 2: TypeString = "f16"; break; case 4: TypeString = "f32"; break; case 8: TypeString = "f64"; break; } } if (TypeString == "") return false; SpecConstInfo.emplace_back( SpecConstInfoTy({SpecConstIdLiteral, SpecConstSize, TypeString})); } break; } default: D.ignoreInstruction(); } } return !IS.bad(); } // clang-format off const StringSet<> SPIRVToLLVM::BuiltInConstFunc { "convert", "get_work_dim", "get_global_size", "sub_group_ballot_bit_count", "get_global_id", "get_local_size", "get_local_id", "get_num_groups", "get_group_id", "get_global_offset", "acos", "acosh", "acospi", "asin", "asinh", "asinpi", "atan", "atan2", "atanh", "atanpi", "atan2pi", "cbrt", "ceil", "copysign", "cos", "cosh", "cospi", "erfc", "erf", "exp", "exp2", "exp10", "expm1", "fabs", "fdim", "floor", "fma", "fmax", "fmin", "fmod", "ilogb", "ldexp", "lgamma", "log", "log2", "log10", "log1p", "logb", "mad", "maxmag", "minmag", "nan", "nextafter", "pow", "pown", "powr", "remainder", "rint", "rootn", "round", "rsqrt", "sin", "sinh", "sinpi", "sqrt", "tan", "tanh", "tanpi", "tgamma", "trunc", "half_cos", "half_divide", "half_exp", "half_exp2", "half_exp10", "half_log", "half_log2", "half_log10", "half_powr", "half_recip", "half_rsqrt", "half_sin", "half_sqrt", "half_tan", "native_cos", "native_divide", "native_exp", "native_exp2", "native_exp10", "native_log", "native_log2", "native_log10", "native_powr", "native_recip", "native_rsqrt", "native_sin", "native_sqrt", "native_tan", "abs", "abs_diff", "add_sat", "hadd", "rhadd", "clamp", "clz", "mad_hi", "mad_sat", "max", "min", "mul_hi", "rotate", "sub_sat", "upsample", "popcount", "mad24", "mul24", "degrees", "mix", "radians", "step", "smoothstep", "sign", "cross", "dot", "distance", "length", "normalize", "fast_distance", "fast_length", "fast_normalize", "isequal", "isnotequal", "isgreater", "isgreaterequal", "isless", "islessequal", "islessgreater", "isfinite", "isinf", "isnan", "isnormal", "isordered", "isunordered", "signbit", "any", "all", "bitselect", "select", "shuffle", "shuffle2", "get_image_width", "get_image_height", "get_image_depth", "get_image_channel_data_type", "get_image_channel_order", "get_image_dim", "get_image_array_size", "get_image_array_size", "sub_group_inverse_ballot", "sub_group_ballot_bit_extract", }; // clang-format on