//===- SPIRVWriter.cpp - Converts LLVM to SPIR-V ----------------*- 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 LLVM intermediate language to SPIR-V /// binary. /// //===----------------------------------------------------------------------===// #include "SPIRVWriter.h" #include "LLVMToSPIRVDbgTran.h" #include "OCLToSPIRV.h" #include "PreprocessMetadata.h" #include "SPIRVAsm.h" #include "SPIRVBasicBlock.h" #include "SPIRVEntry.h" #include "SPIRVEnum.h" #include "SPIRVExtInst.h" #include "SPIRVFunction.h" #include "SPIRVInstruction.h" #include "SPIRVInternal.h" #include "SPIRVLLVMUtil.h" #include "SPIRVLowerBitCastToNonStandardType.h" #include "SPIRVLowerBool.h" #include "SPIRVLowerConstExpr.h" #include "SPIRVLowerLLVMIntrinsic.h" #include "SPIRVLowerMemmove.h" #include "SPIRVLowerOCLBlocks.h" #include "SPIRVMDWalker.h" #include "SPIRVMemAliasingINTEL.h" #include "SPIRVModule.h" #include "SPIRVRegularizeLLVM.h" #include "SPIRVType.h" #include "SPIRVUtil.h" #include "SPIRVValue.h" #include "VectorComputeUtil.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Analysis/LoopAnalysisManager.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Constants.h" #include "llvm/IR/DerivedTypes.h" #include "llvm/IR/Dominators.h" #include "llvm/IR/Function.h" #include "llvm/IR/InlineAsm.h" #include "llvm/IR/InstrTypes.h" #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Module.h" #include "llvm/IR/Operator.h" #include "llvm/IR/TypedPointerType.h" #include "llvm/Pass.h" #include "llvm/Passes/PassBuilder.h" #include "llvm/Support/Casting.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/Utils/LoopSimplify.h" #include "llvm/Transforms/Utils/Mem2Reg.h" #include #include #include #include #include #include #include #include #define DEBUG_TYPE "spirv" using namespace llvm; using namespace SPIRV; using namespace OCLUtil; namespace { static SPIRVWord convertFloatToSPIRVWord(float F) { union { float F; SPIRVWord Spir; } FPMaxError; FPMaxError.F = F; return FPMaxError.Spir; } /// Return one of the SPIR-V 1.4 SignExtend or ZeroExtend image operands /// for a function name, or 0 if the function does not return or /// write an integer type. int getImageSignZeroExt(Function *F) { bool IsSigned = false; bool IsUnsigned = false; ParamSignedness RetSignedness; SmallVector ArgSignedness; if (!getRetParamSignedness(F, RetSignedness, ArgSignedness)) return 0; StringRef Name = F->getName(); Name = Name.substr(Name.find(kSPIRVName::Prefix)); Name.consume_front(kSPIRVName::Prefix); if (Name.consume_front("ImageRead") || Name.consume_front("ImageSampleExplicitLod")) { if (RetSignedness == ParamSignedness::Signed) IsSigned = true; else if (RetSignedness == ParamSignedness::Unsigned) IsUnsigned = true; else if (F->getReturnType()->isIntOrIntVectorTy() && Name.consume_front("_R")) { // Return type is mangled after _R, e.g. _Z23__spirv_ImageRead_Rint2li IsSigned = isMangledTypeSigned(Name[0]); IsUnsigned = Name.starts_with("u"); } } else if (Name.starts_with("ImageWrite")) { IsSigned = (ArgSignedness[2] == ParamSignedness::Signed); IsUnsigned = (ArgSignedness[2] == ParamSignedness::Unsigned); } if (IsSigned) return ImageOperandsMask::ImageOperandsSignExtendMask; if (IsUnsigned) return ImageOperandsMask::ImageOperandsZeroExtendMask; return 0; } } // namespace namespace SPIRV { static void foreachKernelArgMD( MDNode *MD, SPIRVFunction *BF, std::function Func) { assert(BF->getNumArguments() == MD->getNumOperands() && "Invalid kernel metadata: Number of metadata operands and kernel " "arguments do not match"); for (unsigned I = 0, E = MD->getNumOperands(); I != E; ++I) { SPIRVFunctionParameter *BA = BF->getArgument(I); Func(getMDOperandAsString(MD, I).str(), BA); } } static void foreachKernelArgMD( MDNode *MD, SPIRVFunction *BF, std::function Func) { assert(BF->getNumArguments() == MD->getNumOperands() && "Invalid kernel metadata: Number of metadata operands and kernel " "arguments do not match"); for (unsigned I = 0, E = MD->getNumOperands(); I != E; ++I) { SPIRVFunctionParameter *BA = BF->getArgument(I); Func(MD->getOperand(I), BA); } } static SPIRVMemoryModelKind getMemoryModel(Module &M) { auto *MemoryModelMD = M.getNamedMetadata(kSPIRVMD::MemoryModel); if (MemoryModelMD && (MemoryModelMD->getNumOperands() > 0)) { auto *Ref0 = MemoryModelMD->getOperand(0); if (Ref0 && Ref0->getNumOperands() > 1) { auto &&ModelOp = Ref0->getOperand(1); auto *ModelCI = mdconst::dyn_extract(ModelOp); if (ModelCI && (ModelCI->getValue().getActiveBits() <= 64)) { auto Model = static_cast(ModelCI->getZExtValue()); return Model; } } } return SPIRVMemoryModelKind::MemoryModelMax; } static void translateSEVDecoration(Attribute Sev, SPIRVValue *Val) { assert(Sev.isStringAttribute() && Sev.getKindAsString() == kVCMetadata::VCSingleElementVector); auto *Ty = Val->getType(); assert((Ty->isTypeBool() || Ty->isTypeFloat() || Ty->isTypeInt() || Ty->isTypePointer()) && "This decoration is valid only for Scalar or Pointer types"); if (Ty->isTypePointer()) { SPIRVWord IndirectLevelsOnElement = 0; Sev.getValueAsString().getAsInteger(0, IndirectLevelsOnElement); Val->addDecorate(DecorationSingleElementVectorINTEL, IndirectLevelsOnElement); } else Val->addDecorate(DecorationSingleElementVectorINTEL); } LLVMToSPIRVBase::LLVMToSPIRVBase(SPIRVModule *SMod) : BuiltinCallHelper(ManglingRules::None), M(nullptr), Ctx(nullptr), BM(SMod), SrcLang(0), SrcLangVer(0) { DbgTran = std::make_unique(nullptr, SMod, this); } LLVMToSPIRVBase::~LLVMToSPIRVBase() { for (auto *I : UnboundInst) I->deleteValue(); } bool LLVMToSPIRVBase::runLLVMToSPIRV(Module &Mod) { M = &Mod; initialize(Mod); CG = std::make_unique(Mod); Ctx = &M->getContext(); DbgTran->setModule(M); assert(BM && "SPIR-V module not initialized"); translate(); return true; } SPIRVValue *LLVMToSPIRVBase::getTranslatedValue(const Value *V) const { auto Loc = ValueMap.find(V); if (Loc != ValueMap.end()) return Loc->second; return nullptr; } bool LLVMToSPIRVBase::isKernel(Function *F) { if (F->getCallingConv() == CallingConv::SPIR_KERNEL) return true; return false; } bool LLVMToSPIRVBase::isBuiltinTransToInst(Function *F) { StringRef DemangledName; if (!oclIsBuiltin(F->getName(), DemangledName) && !isDecoratedSPIRVFunc(F, DemangledName)) return false; SPIRVDBG(spvdbgs() << "CallInst: demangled name: " << DemangledName.str() << '\n'); return getSPIRVFuncOC(DemangledName) != OpNop; } bool LLVMToSPIRVBase::isBuiltinTransToExtInst( Function *F, SPIRVExtInstSetKind *ExtSet, SPIRVWord *ExtOp, SmallVectorImpl *Dec) { StringRef DemangledName; if (!oclIsBuiltin(F->getName(), DemangledName)) return false; LLVM_DEBUG(dbgs() << "[oclIsBuiltinTransToExtInst] CallInst: demangled name: " << DemangledName << '\n'); StringRef S = DemangledName; if (!S.starts_with(kSPIRVName::Prefix)) return false; S = S.drop_front(strlen(kSPIRVName::Prefix)); auto Loc = S.find(kSPIRVPostfix::Divider); auto ExtSetName = S.substr(0, Loc); SPIRVExtInstSetKind Set = SPIRVEIS_Count; if (!SPIRVExtSetShortNameMap::rfind(ExtSetName.str(), &Set)) return false; assert((Set == SPIRVEIS_OpenCL || Set == BM->getDebugInfoEIS()) && "Unsupported extended instruction set"); auto ExtOpName = S.substr(Loc + 1); auto Splited = ExtOpName.split(kSPIRVPostfix::ExtDivider); OCLExtOpKind EOC; if (!OCLExtOpMap::rfind(Splited.first.str(), &EOC)) return false; if (ExtSet) *ExtSet = Set; if (ExtOp) *ExtOp = EOC; if (Dec) { SmallVector P; Splited.second.split(P, kSPIRVPostfix::Divider); for (auto &I : P) Dec->push_back(I.str()); } return true; } bool isUniformGroupOperation(Function *F) { auto Name = F->getName(); if (Name.contains("GroupIMulKHR") || Name.contains("GroupFMulKHR") || Name.contains("GroupBitwiseAndKHR") || Name.contains("GroupBitwiseOrKHR") || Name.contains("GroupBitwiseXorKHR") || Name.contains("GroupLogicalAndKHR") || Name.contains("GroupLogicalOrKHR") || Name.contains("GroupLogicalXorKHR")) return true; return false; } static bool recursiveType(const StructType *ST, const Type *Ty) { SmallPtrSet Seen; std::function Run = [&](const Type *Ty) { if (auto *StructTy = dyn_cast(Ty)) { if (StructTy == ST) return true; if (Seen.count(StructTy)) return false; Seen.insert(StructTy); return find_if(StructTy->element_begin(), StructTy->element_end(), Run) != StructTy->element_end(); } if (auto *ArrayTy = dyn_cast(Ty)) return Run(ArrayTy->getArrayElementType()); return false; }; return Run(Ty); } // Add decoration if needed void addFPBuiltinDecoration(SPIRVModule *BM, Instruction *Inst, SPIRVInstruction *I) { bool AllowFPMaxError = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fp_max_error); auto *II = dyn_cast_or_null(Inst); if (II && II->getCalledFunction()->getName().starts_with("llvm.fpbuiltin")) { // Add a new decoration for llvm.builtin intrinsics, if needed if (II->getAttributes().hasFnAttr("fpbuiltin-max-error")) { BM->getErrorLog().checkError(AllowFPMaxError, SPIRVEC_RequiresExtension, "SPV_INTEL_fp_max_error\n"); double F = 0.0; II->getAttributes() .getFnAttr("fpbuiltin-max-error") .getValueAsString() .getAsDouble(F); I->addDecorate(DecorationFPMaxErrorDecorationINTEL, convertFloatToSPIRVWord(F)); } } else if (auto *MD = Inst->getMetadata("fpmath")) { if (!AllowFPMaxError) return; auto *MDVal = mdconst::dyn_extract(MD->getOperand(0)); double ValAsDouble = MDVal->getValue().convertToFloat(); I->addDecorate(DecorationFPMaxErrorDecorationINTEL, convertFloatToSPIRVWord(ValAsDouble)); } } SPIRVType *LLVMToSPIRVBase::transType(Type *T) { LLVMToSPIRVTypeMap::iterator Loc = TypeMap.find(T); if (Loc != TypeMap.end()) return Loc->second; SPIRVDBG(dbgs() << "[transType] " << *T << '\n'); if (T->isVoidTy()) return mapType(T, BM->addVoidType()); if (T->isIntegerTy(1)) return mapType(T, BM->addBoolType()); if (T->isIntegerTy()) { unsigned BitWidth = T->getIntegerBitWidth(); // SPIR-V 2.16.1. Universal Validation Rules: Scalar integer types can be // parameterized only as 32 bit, plus any additional sizes enabled by // capabilities. if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_arbitrary_precision_integers) || BM->getErrorLog().checkError( (BitWidth == 4 && BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_int4)) || BitWidth == 8 || BitWidth == 16 || BitWidth == 32 || BitWidth == 64, SPIRVEC_InvalidBitWidth, std::to_string(BitWidth))) { return mapType(T, BM->addIntegerType(T->getIntegerBitWidth())); } } if (T->isBFloatTy()) { BM->getErrorLog().checkError( BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_bfloat16), SPIRVEC_RequiresExtension, "SPV_KHR_bfloat16\n" "NOTE: LLVM module contains bfloat type, translation of which " "requires this extension"); return mapType(T, BM->addFloatType(16, FPEncodingBFloat16KHR)); } if (T->isFloatingPointTy()) return mapType(T, BM->addFloatType(T->getPrimitiveSizeInBits())); // A pointer to image or pipe type in LLVM is translated to a SPIRV // (non-pointer) image or pipe type. if (T->isPointerTy()) { auto AddrSpc = T->getPointerAddressSpace(); auto *ET = Type::getInt8Ty(T->getContext()); return transPointerType(ET, AddrSpc); } if (auto *TPT = dyn_cast(T)) { return transPointerType(TPT->getElementType(), TPT->getAddressSpace()); } if (auto *VecTy = dyn_cast(T)) { unsigned NumElements = VecTy->getNumElements(); bool IsNonStandardCount = !(NumElements == 2 || NumElements == 3 || NumElements == 4 || NumElements == 8 || NumElements == 16); if (IsNonStandardCount && !BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_long_vector) && !BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) { BM->getErrorLog().checkError( false, SPIRVEC_RequiresExtension, "SPV_EXT_long_vector or SPV_INTEL_vector_compute\n" "NOTE: LLVM module contains a vector with an unsupported number of " "components, translation of which requires one of these extensions"); return nullptr; } if (VecTy->getElementType()->isPointerTy() || isa(VecTy->getElementType())) { // SPV_INTEL_masked_gather_scatter extension changes 2.16.1. Universal // Validation Rules: // Vector types must be parameterized only with numerical types, // [Physical Pointer Type] types or the [OpTypeBool] type. // Without it vector of pointers is not allowed in SPIR-V. if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_masked_gather_scatter)) { BM->getErrorLog().checkError( false, SPIRVEC_RequiresExtension, "SPV_INTEL_masked_gather_scatter\n" "NOTE: LLVM module contains vector of pointers, translation " "of which requires this extension"); return nullptr; } BM->addExtension(ExtensionID::SPV_INTEL_masked_gather_scatter); BM->addCapability(internal::CapabilityMaskedGatherScatterINTEL); } return mapType(T, BM->addVectorType(transType(VecTy->getElementType()), VecTy->getNumElements())); } if (T->isArrayTy()) { // SPIR-V 1.3 s3.32.6: Length is the number of elements in the array. // It must be at least 1. const auto ArraySize = T->getArrayNumElements() ? T->getArrayNumElements() : 1; Type *ElTy = T->getArrayElementType(); SPIRVType *TransType = BM->addArrayType( transType(ElTy), static_cast(transValue( ConstantInt::get(getSizetType(), ArraySize, false), nullptr))); mapType(T, TransType); if (ElTy->isPointerTy()) { Type *ArrTy = ArrayType::get(TypedPointerType::get(Type::getInt8Ty(*Ctx), ElTy->getPointerAddressSpace()), ArraySize); LLVMToSPIRVTypeMap::iterator Loc = TypeMap.find(ArrTy); if (Loc == TypeMap.end()) mapType(ArrTy, TransType); } return TransType; } if (T->isStructTy() && !T->isSized()) { auto *ST = dyn_cast(T); (void)ST; // Silence warning assert(!ST->getName().starts_with(kSPR2TypeName::PipeRO)); assert(!ST->getName().starts_with(kSPR2TypeName::PipeWO)); assert(!ST->getName().starts_with(kSPR2TypeName::ImagePrefix)); return mapType(T, BM->addOpaqueType(T->getStructName().str())); } if (auto *ST = dyn_cast(T)) { assert(ST->isSized()); StringRef Name; if (ST->hasName()) Name = ST->getName(); if (Name == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler)) return transSPIRVOpaqueType("spirv.Sampler", SPIRAS_Constant); if (Name == getSPIRVTypeName(kSPIRVTypeName::ConstantPipeStorage)) return transSPIRVOpaqueType("spirv.PipeStorage", SPIRAS_Constant); constexpr size_t MaxNumElements = MaxWordCount - SPIRVTypeStruct::FixedWC; const size_t NumElements = ST->getNumElements(); size_t SPIRVStructNumElements = NumElements; // In case number of elements is greater than maximum WordCount and // SPV_INTEL_long_composites is not enabled, the error will be // emitted by validate functionality of SPIRVTypeStruct class. if (NumElements > MaxNumElements && BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_long_composites)) { SPIRVStructNumElements = MaxNumElements; } auto *Struct = BM->openStructType(SPIRVStructNumElements, Name.str()); mapType(T, Struct); if (NumElements > MaxNumElements && BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_long_composites)) { uint64_t NumOfContinuedInstructions = NumElements / MaxNumElements - 1; for (uint64_t J = 0; J < NumOfContinuedInstructions; J++) { auto *Continued = BM->addTypeStructContinuedINTEL(MaxNumElements); Struct->addContinuedInstruction( static_cast(Continued)); } uint64_t Remains = NumElements % MaxNumElements; if (Remains) { auto *Continued = BM->addTypeStructContinuedINTEL(Remains); Struct->addContinuedInstruction( static_cast(Continued)); } } SmallVector ForwardRefs; for (unsigned I = 0, E = T->getStructNumElements(); I != E; ++I) { auto *ElemTy = ST->getElementType(I); if ((isa(ElemTy) || isa(ElemTy) || isa(ElemTy) || isa(ElemTy)) && recursiveType(ST, ElemTy)) ForwardRefs.push_back(I); else Struct->setMemberType(I, transType(ST->getElementType(I))); } BM->closeStructType(Struct, ST->isPacked()); for (auto I : ForwardRefs) Struct->setMemberType(I, transType(ST->getElementType(I))); return Struct; } if (FunctionType *FT = dyn_cast(T)) { SPIRVType *RT = transType(FT->getReturnType()); std::vector PT; for (FunctionType::param_iterator I = FT->param_begin(), E = FT->param_end(); I != E; ++I) PT.push_back(transType(*I)); return mapType(T, getSPIRVFunctionType(RT, PT)); } if (auto *TargetTy = dyn_cast(T)) { StringRef Name = TargetTy->getName(); if (Name.consume_front(kSPIRVTypeName::PrefixAndDelim)) { auto Opcode = SPIRVOpaqueTypeOpCodeMap::map(Name.str()); auto CastAccess = [](unsigned Val) { return static_cast(Val); }; switch (static_cast(Opcode)) { case OpTypePipe: { auto *PipeT = BM->addPipeType(); PipeT->setPipeAcessQualifier(CastAccess(TargetTy->getIntParameter(0))); return mapType(T, PipeT); } case OpTypeImage: { auto *SampledTy = transType(TargetTy->getTypeParameter(0)); ArrayRef Ops = TargetTy->int_params(); SPIRVTypeImageDescriptor Desc(static_cast(Ops[0]), Ops[1], Ops[2], Ops[3], Ops[4], Ops[5]); return mapType(T, BM->addImageType(SampledTy, Desc, CastAccess(Ops[6]))); } case OpTypeSampledImage: { auto *ImageTy = static_cast(transType(adjustImageType( T, kSPIRVTypeName::SampledImg, kSPIRVTypeName::Image))); return mapType(T, BM->addSampledImageType(ImageTy)); } case OpTypeVmeImageINTEL: { auto *ImageTy = static_cast(transType(adjustImageType( T, kSPIRVTypeName::VmeImageINTEL, kSPIRVTypeName::Image))); return mapType(T, BM->addVmeImageINTELType(ImageTy)); } case OpTypeQueue: return mapType(T, BM->addQueueType()); case OpTypeDeviceEvent: return mapType(T, BM->addDeviceEventType()); case OpTypeBufferSurfaceINTEL: { ArrayRef Ops = TargetTy->int_params(); return mapType(T, BM->addBufferSurfaceINTELType(CastAccess(Ops[0]))); } case internal::OpTypeJointMatrixINTEL: { // The expected representation is: // target("spirv.JointMatrixINTEL", %element_type, %rows%, %cols%, // %layout%, %scope%, %use%, // (optional) %element_type_interpretation%) auto *ElemTy = transType(TargetTy->getTypeParameter(0)); ArrayRef Ops = TargetTy->int_params(); std::vector Args; for (const auto &Op : Ops) Args.emplace_back(transConstant(getUInt32(M, Op))); return mapType(T, BM->addJointMatrixINTELType(ElemTy, Args)); } case OpTypeCooperativeMatrixKHR: { // The expected representation is: // target("spirv.CooperativeMatrixKHR", %element_type, %scope%, %rows%, // %cols%, %use%) auto *ElemTy = transType(TargetTy->getTypeParameter(0)); ArrayRef Ops = TargetTy->int_params(); std::vector Args; for (const auto &Op : Ops) Args.emplace_back(transConstant(getUInt32(M, Op))); return mapType(T, BM->addCooperativeMatrixKHRType(ElemTy, Args)); } case internal::OpTypeTaskSequenceINTEL: return mapType(T, BM->addTaskSequenceINTELType()); default: if (isSubgroupAvcINTELTypeOpCode(Opcode)) return mapType(T, BM->addSubgroupAvcINTELType(Opcode)); return mapType(T, BM->addOpaqueGenericType(Opcode)); } } } if (T->isTokenTy()) { BM->getErrorLog().checkError( false, SPIRVEC_InvalidModule, "LLVM module contains token type, which doesn't have a counterpart in " "SPIR-V"); return nullptr; } llvm_unreachable("Not implemented!"); return 0; } SPIRVType *LLVMToSPIRVBase::transPointerType(Type *ET, unsigned AddrSpc) { Type *T = PointerType::get(ET->getContext(), AddrSpc); if (ET->isFunctionTy() && !BM->checkExtension(ExtensionID::SPV_INTEL_function_pointers, SPIRVEC_FunctionPointers, toString(T))) return nullptr; std::string TypeKey = (Twine((uintptr_t)ET) + Twine(AddrSpc)).str(); auto Loc = PointeeTypeMap.find(TypeKey); if (Loc != PointeeTypeMap.end()) return Loc->second; // A pointer to image or pipe type in LLVM is translated to a SPIRV // (non-pointer) image or pipe type. auto *ST = dyn_cast(ET); // Lower global_device and global_host address spaces that were added in // SYCL as part of SYCL_INTEL_usm_address_spaces extension to just global // address space if device doesn't support SPV_INTEL_usm_storage_classes // extension if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_usm_storage_classes) && ((AddrSpc == SPIRAS_GlobalDevice) || (AddrSpc == SPIRAS_GlobalHost))) { return transPointerType(ET, SPIRAS_Global); } // Lower function pointer address space to private if // spirv-emit-function-ptr-addr-space is not passed if (AddrSpc == SPIRAS_CodeSectionINTEL && !BM->shouldEmitFunctionPtrAddrSpace()) return transPointerType(ET, SPIRAS_Private); if (ST && !ST->isSized()) { Op OpCode; StringRef STName = ST->getName(); // Workaround for non-conformant SPIR binary if (STName == "struct._event_t") { STName = kSPR2TypeName::Event; ST->setName(STName); } std::pair Key = {STName, AddrSpc}; if (auto *MappedTy = OpaqueStructMap.lookup(Key)) return MappedTy; auto SaveType = [&](SPIRVType *MappedTy) { OpaqueStructMap[Key] = MappedTy; PointeeTypeMap[TypeKey] = MappedTy; return MappedTy; }; if (STName.starts_with(kSPR2TypeName::PipeRO) || STName.starts_with(kSPR2TypeName::PipeWO)) { auto *PipeT = BM->addPipeType(); PipeT->setPipeAcessQualifier(STName.starts_with(kSPR2TypeName::PipeRO) ? AccessQualifierReadOnly : AccessQualifierWriteOnly); return SaveType(PipeT); } if (STName.starts_with(kSPR2TypeName::ImagePrefix)) { assert(AddrSpc == SPIRAS_Global); Type *ImageTy = adjustImageType(TypedPointerType::get(ST, AddrSpc), kSPIRVTypeName::Image, kSPIRVTypeName::Image); return SaveType(transType(ImageTy)); } if (STName == kSPR2TypeName::Sampler) return SaveType(transType(getSPIRVType(OpTypeSampler))); if (STName.starts_with(kSPIRVTypeName::PrefixAndDelim)) return transSPIRVOpaqueType(STName, AddrSpc); if (STName.starts_with(kOCLSubgroupsAVCIntel::TypePrefix)) return SaveType(BM->addSubgroupAvcINTELType( OCLSubgroupINTELTypeOpCodeMap::map(ST->getName().str()))); if (OCLOpaqueTypeOpCodeMap::find(STName.str(), &OpCode)) { Type *RealType = getSPIRVType(OpCode); return SaveType(transType(RealType)); } if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) { if (STName.starts_with(kVCType::VCBufferSurface)) { // VCBufferSurface always have Access Qualifier auto Access = getAccessQualifier(STName); return SaveType(BM->addBufferSurfaceINTELType(Access)); } } if (ST->isOpaque()) { return SaveType(BM->addPointerType( SPIRSPIRVAddrSpaceMap::map(static_cast(AddrSpc)), transType(ET))); } } else { // ET, as a recursive type, may contain exactly the same pointer T, so it // may happen that after translation of ET we already have translated T, // added the translated pointer to the SPIR-V module and mapped T to this // pointer. Now we have to check PointeeTypeMap again. auto Loc = PointeeTypeMap.find(TypeKey); if (Loc != PointeeTypeMap.end()) { return Loc->second; } SPIRVType *ElementType = nullptr; SPIRVType *TranslatedTy = nullptr; if (ET->isPointerTy() && BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) { TranslatedTy = BM->addPointerType( SPIRSPIRVAddrSpaceMap::map(static_cast(AddrSpc)), nullptr); } else { ElementType = transType(ET); TranslatedTy = transPointerType(ElementType, AddrSpc); } PointeeTypeMap[TypeKey] = TranslatedTy; return TranslatedTy; } llvm_unreachable("Not implemented!"); return nullptr; } SPIRVType *LLVMToSPIRVBase::transPointerType(SPIRVType *ET, unsigned AddrSpc) { std::string TypeKey = (Twine((uintptr_t)ET) + Twine(AddrSpc)).str(); auto Loc = PointeeTypeMap.find(TypeKey); if (Loc != PointeeTypeMap.end()) return Loc->second; SPIRVType *TranslatedTy = nullptr; if (AddrSpc == SPIRAS_CodeSectionINTEL && !BM->shouldEmitFunctionPtrAddrSpace()) return transPointerType(ET, SPIRAS_Private); if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers) && !(ET->isTypeArray() || ET->isTypeVector() || ET->isSPIRVOpaqueType())) { TranslatedTy = BM->addPointerType( SPIRSPIRVAddrSpaceMap::map(static_cast(AddrSpc)), nullptr); } else { TranslatedTy = BM->addPointerType( SPIRSPIRVAddrSpaceMap::map(static_cast(AddrSpc)), ET); } PointeeTypeMap[TypeKey] = TranslatedTy; return TranslatedTy; } SPIRVType *LLVMToSPIRVBase::transSPIRVOpaqueType(StringRef STName, unsigned AddrSpace) { std::pair Key = {STName, AddrSpace}; if (auto *MappedTy = OpaqueStructMap.lookup(Key)) return MappedTy; auto SaveType = [&](SPIRVType *MappedTy) { OpaqueStructMap[Key] = MappedTy; return MappedTy; }; StructType *ST = StructType::getTypeByName(M->getContext(), STName); assert(STName.starts_with(kSPIRVTypeName::PrefixAndDelim) && "Invalid SPIR-V opaque type name"); SmallVector Postfixes; auto TN = decodeSPIRVTypeName(STName, Postfixes); if (TN == kSPIRVTypeName::Pipe) { assert(AddrSpace == SPIRAS_Global); assert(Postfixes.size() == 1 && "Invalid pipe type ops"); auto *PipeT = BM->addPipeType(); PipeT->setPipeAcessQualifier( static_cast(atoi(Postfixes[0].c_str()))); return SaveType(PipeT); } else if (TN == kSPIRVTypeName::Image) { assert(AddrSpace == SPIRAS_Global); // The sampled type needs to be translated through LLVM type to guarantee // uniqueness. auto *SampledT = transType( getLLVMTypeForSPIRVImageSampledTypePostfix(Postfixes[0], *Ctx)); SmallVector Ops; for (unsigned I = 1; I < 8; ++I) Ops.push_back(atoi(Postfixes[I].c_str())); SPIRVTypeImageDescriptor Desc(static_cast(Ops[0]), Ops[1], Ops[2], Ops[3], Ops[4], Ops[5]); return SaveType(BM->addImageType( SampledT, Desc, static_cast(Ops[6]))); } else if (TN == kSPIRVTypeName::SampledImg) { return SaveType(BM->addSampledImageType(static_cast( transType(adjustImageType(TypedPointerType::get(ST, SPIRAS_Global), kSPIRVTypeName::SampledImg, kSPIRVTypeName::Image))))); } else if (TN == kSPIRVTypeName::VmeImageINTEL) { // This type is the same as SampledImageType, but consumed by Subgroup AVC // Intel extension instructions. return SaveType(BM->addVmeImageINTELType(static_cast( transType(adjustImageType(TypedPointerType::get(ST, SPIRAS_Global), kSPIRVTypeName::VmeImageINTEL, kSPIRVTypeName::Image))))); } else if (TN == kSPIRVTypeName::Sampler) return SaveType(BM->addSamplerType()); else if (TN == kSPIRVTypeName::DeviceEvent) return SaveType(BM->addDeviceEventType()); else if (TN == kSPIRVTypeName::Queue) return SaveType(BM->addQueueType()); else if (TN == kSPIRVTypeName::PipeStorage) return SaveType(BM->addPipeStorageType()); else if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute) && TN == kSPIRVTypeName::BufferSurfaceINTEL) { auto Access = getAccessQualifier(STName); return SaveType(BM->addBufferSurfaceINTELType(Access)); } else return SaveType( BM->addOpaqueGenericType(SPIRVOpaqueTypeOpCodeMap::map(TN))); } SPIRVType *LLVMToSPIRVBase::transScavengedType(Value *V) { if (auto *F = dyn_cast(V)) { FunctionType *FnTy = Scavenger->getFunctionType(F); // VarArg functions other than printf are not supported in SPIR-V. None of // printf variants can actually reach this point because they're skipped in // 'LLVMToSPIRVBase::translate()'. Specifically, // 'isBuiltinTransToExtInst(&F)' returns true for printf and its variants, // and so they are never added to 'Decls' and 'Defs'. Therefore, it's safe // to only check here if we're dealing with a variadic function to report an // error. To be on the safe side, an assertion is added to check printf // never reaches this point. assert(F->getName() != "printf"); BM->getErrorLog().checkError(!FnTy->isVarArg(), SPIRVEC_UnsupportedVarArgFunction); SPIRVType *RT = transType(FnTy->getReturnType()); std::vector PT; for (Argument &Arg : F->args()) { assert(OCLTypeToSPIRVPtr); Type *Ty = OCLTypeToSPIRVPtr->getAdaptedArgumentType(F, Arg.getArgNo()); if (!Ty) { Ty = FnTy->getParamType(Arg.getArgNo()); } PT.push_back(transType(Ty)); } return getSPIRVFunctionType(RT, PT); } return transType(Scavenger->getScavengedType(V)); } SPIRVType * LLVMToSPIRVBase::getSPIRVFunctionType(SPIRVType *RT, const std::vector &Args) { // Come up with a unique string identifier for the arguments. This is a hacky // way of doing so, but it works. std::string TypeKey; llvm::raw_string_ostream TKS(TypeKey); TKS << (uintptr_t)RT << ","; for (SPIRVType *ArgTy : Args) { TKS << (uintptr_t)ArgTy << ","; } // Create a SPIRVType for the function type. Since SPIRVModule doesn't do // any type uniquing for SPIRVType, we have to do it ourself. TKS.flush(); auto It = PointeeTypeMap.find(TypeKey); if (It == PointeeTypeMap.end()) It = PointeeTypeMap.insert({TypeKey, BM->addFunctionType(RT, Args)}).first; return It->second; } SPIRVFunction *LLVMToSPIRVBase::transFunctionDecl(Function *F) { if (auto *BF = getTranslatedValue(F)) return static_cast(BF); if (F->isIntrinsic() && (!BM->isSPIRVAllowUnknownIntrinsicsEnabled() || isKnownIntrinsic(F->getIntrinsicID()))) { // We should not translate LLVM intrinsics as a function assert(none_of(F->users(), [this](User *U) { return getTranslatedValue(U); }) && "LLVM intrinsics shouldn't be called in SPIRV"); return nullptr; } // Don't translate FP conversion translator builtins as function declarations. auto MangledName = F->getName(); StringRef DemangledName; if (isInternalSPIRVBuiltin(MangledName, DemangledName)) { if (SPIRV::FPConvertToEncodingMap::find(DemangledName)) { // Create an early exit here if none of the extensions are enabled. // Proper checks for the required extensions will be done during TypeFloat // generation. if (!BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_float8) && !BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_int4) && !BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_float4) && !BM->isAllowedToUseExtension( ExtensionID::SPV_EXT_ocp_microscaling_types)) { std::string ErrorStr = "One of the following extensions: " "SPV_EXT_float8, SPV_EXT_ocp_microscaling_types, " "SPV_INTEL_float4, SPV_INTEL_int4 should be enabled to process " "conversion builtins"; getErrorLog().checkError(false, SPIRVEC_RequiresExtension, F, ErrorStr); } return nullptr; } } SPIRVTypeFunction *BFT = static_cast(transScavengedType(F)); SPIRVFunction *BF = static_cast(mapValue(F, BM->addFunction(BFT))); BF->setFunctionControlMask(transFunctionControlMask(F)); if (F->hasName()) { if (isKernel(F)) { /* strip the prefix as the runtime will be looking for this name */ std::string Prefix = kSPIRVName::EntrypointPrefix; std::string Name = F->getName().str(); BM->setName(BF, Name.substr(Prefix.size())); } else { if (isUniformGroupOperation(F)) BM->getErrorLog().checkError( BM->isAllowedToUseExtension( ExtensionID::SPV_KHR_uniform_group_instructions), SPIRVEC_RequiresExtension, "SPV_KHR_uniform_group_instructions\n"); BM->setName(BF, F->getName().str()); } } if (!isKernel(F) && F->getLinkage() != GlobalValue::InternalLinkage) BF->setLinkageType(transLinkageType(F)); // Translate OpenCL/SYCL buffer_location metadata if it's attached to the // translated function declaration MDNode *BufferLocation = nullptr; if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_buffer_location)) BufferLocation = F->getMetadata("kernel_arg_buffer_location"); // Translate runtime_aligned metadata if it's attached to the translated // function declaration MDNode *RuntimeAligned = nullptr; if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_runtime_aligned)) RuntimeAligned = F->getMetadata("kernel_arg_runtime_aligned"); auto Attrs = F->getAttributes(); for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E; ++I) { auto ArgNo = I->getArgNo(); SPIRVFunctionParameter *BA = BF->getArgument(ArgNo); if (I->hasName()) BM->setName(BA, I->getName().str()); if (I->hasByValAttr()) BA->addAttr(FunctionParameterAttributeByVal); if (I->hasNoAliasAttr()) BA->addAttr(FunctionParameterAttributeNoAlias); if (I->hasNoCaptureAttr()) BA->addAttr(FunctionParameterAttributeNoCapture); if (I->hasStructRetAttr()) BA->addAttr(FunctionParameterAttributeSret); if (Attrs.hasParamAttr(ArgNo, Attribute::ReadOnly)) BA->addAttr(FunctionParameterAttributeNoWrite); if (Attrs.hasParamAttr(ArgNo, Attribute::ReadNone)) BA->addAttr(FunctionParameterAttributeNoReadWrite); if (Attrs.hasParamAttr(ArgNo, Attribute::ZExt)) BA->addAttr(FunctionParameterAttributeZext); if (Attrs.hasParamAttr(ArgNo, Attribute::SExt)) BA->addAttr(FunctionParameterAttributeSext); if (Attrs.hasParamAttr(ArgNo, Attribute::Alignment)) { SPIRVWord AlignmentBytes = Attrs.getParamAttr(ArgNo, Attribute::Alignment) .getAlignment() .valueOrOne() .value(); BA->setAlignment(AlignmentBytes); } if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_1) && Attrs.hasParamAttr(ArgNo, Attribute::Dereferenceable)) BA->addDecorate(DecorationMaxByteOffset, Attrs.getParamAttr(ArgNo, Attribute::Dereferenceable) .getDereferenceableBytes()); if (BufferLocation && I->getType()->isPointerTy()) { // Order of integer numbers in MD node follows the order of function // parameters on which we shall attach the appropriate decoration. Add // decoration only if MD value is not negative. int LocID = -1; if (!isa(BufferLocation->getOperand(ArgNo)) && !isa(BufferLocation->getOperand(ArgNo))) LocID = getMDOperandAsInt(BufferLocation, ArgNo); if (LocID >= 0) BA->addDecorate(DecorationBufferLocationINTEL, LocID); } if (RuntimeAligned && I->getType()->isPointerTy()) { // Order of integer numbers in MD node follows the order of function // parameters on which we shall attach the appropriate decoration. Add // decoration only if MD value is 1. int IsRuntimeAligned = 0; if (!isa(RuntimeAligned->getOperand(ArgNo)) && !isa(RuntimeAligned->getOperand(ArgNo))) IsRuntimeAligned = getMDOperandAsInt(RuntimeAligned, ArgNo); if (IsRuntimeAligned == 1) { // TODO: to replace non-conformant to the spec decoration generation // with: // BM->addExtension(ExtensionID::SPV_INTEL_runtime_aligned); // BM->addCapability(CapabilityRuntimeAlignedAttributeINTEL); // BA->addAttr(FunctionParameterAttributeRuntimeAlignedINTEL); BA->addDecorate(internal::DecorationRuntimeAlignedINTEL, IsRuntimeAligned); } } } if (Attrs.hasRetAttr(Attribute::ZExt)) BF->addDecorate(DecorationFuncParamAttr, FunctionParameterAttributeZext); if (Attrs.hasRetAttr(Attribute::SExt)) BF->addDecorate(DecorationFuncParamAttr, FunctionParameterAttributeSext); if (Attrs.hasFnAttr("referenced-indirectly")) { assert(!isKernel(F) && "kernel function was marked as referenced-indirectly"); BF->addDecorate(DecorationReferencedIndirectlyINTEL); } if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) transVectorComputeMetadata(F); transFPGAFunctionMetadata(BF, F); if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_maximum_registers)) transFunctionMetadataAsExecutionMode(BF, F); else transFunctionMetadataAsUserSemanticDecoration(BF, F); transAuxDataInst(BF, F); SPIRVDBG(dbgs() << "[transFunction] " << *F << " => "; spvdbgs() << *BF << '\n';) return BF; } void LLVMToSPIRVBase::transVectorComputeMetadata(Function *F) { using namespace VectorComputeUtil; if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) return; auto *BF = static_cast(getTranslatedValue(F)); assert(BF && "The SPIRVFunction pointer shouldn't be nullptr"); auto Attrs = F->getAttributes(); if (Attrs.hasFnAttr(kVCMetadata::VCStackCall)) BF->addDecorate(DecorationStackCallINTEL); if (Attrs.hasFnAttr(kVCMetadata::VCFunction)) BF->addDecorate(DecorationVectorComputeFunctionINTEL); if (Attrs.hasFnAttr(kVCMetadata::VCSIMTCall)) { SPIRVWord SIMTMode = 0; Attrs.getFnAttr(kVCMetadata::VCSIMTCall) .getValueAsString() .getAsInteger(0, SIMTMode); BF->addDecorate(DecorationSIMTCallINTEL, SIMTMode); } if (Attrs.hasRetAttr(kVCMetadata::VCSingleElementVector)) translateSEVDecoration( Attrs.getAttributeAtIndex(AttributeList::ReturnIndex, kVCMetadata::VCSingleElementVector), BF); for (Function::arg_iterator I = F->arg_begin(), E = F->arg_end(); I != E; ++I) { auto ArgNo = I->getArgNo(); SPIRVFunctionParameter *BA = BF->getArgument(ArgNo); if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCArgumentIOKind)) { SPIRVWord Kind = {}; Attrs.getParamAttr(ArgNo, kVCMetadata::VCArgumentIOKind) .getValueAsString() .getAsInteger(0, Kind); BA->addDecorate(DecorationFuncParamIOKindINTEL, Kind); } if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCSingleElementVector)) translateSEVDecoration( Attrs.getParamAttr(ArgNo, kVCMetadata::VCSingleElementVector), BA); if (Attrs.hasParamAttr(ArgNo, kVCMetadata::VCMediaBlockIO)) { assert(BA->getType()->isTypeImage() && "VCMediaBlockIO attribute valid only on image parameters"); BA->addDecorate(DecorationMediaBlockIOINTEL); } } if (!isKernel(F) && BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_float_controls2) && Attrs.hasFnAttr(kVCMetadata::VCFloatControl)) { SPIRVWord Mode = 0; Attrs.getFnAttr(kVCMetadata::VCFloatControl) .getValueAsString() .getAsInteger(0, Mode); VCFloatTypeSizeMap::foreach ( [&](VCFloatType FloatType, unsigned TargetWidth) { BF->addDecorate(new SPIRVDecorateFunctionDenormModeINTEL( BF, TargetWidth, getFPDenormMode(Mode, FloatType))); BF->addDecorate(new SPIRVDecorateFunctionRoundingModeINTEL( BF, TargetWidth, getFPRoundingMode(Mode))); BF->addDecorate(new SPIRVDecorateFunctionFloatingPointModeINTEL( BF, TargetWidth, getFPOperationMode(Mode))); }); } } static void transMetadataDecorations(Metadata *MD, SPIRVValue *Target); void LLVMToSPIRVBase::transFPGAFunctionMetadata(SPIRVFunction *BF, Function *F) { if (MDNode *StallEnable = F->getMetadata(kSPIR2MD::StallEnable)) { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_cluster_attributes)) { if (getMDOperandAsInt(StallEnable, 0)) BF->addDecorate(new SPIRVDecorateStallEnableINTEL(BF)); } } if (MDNode *StallFree = F->getMetadata(kSPIR2MD::StallFree)) { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_cluster_attributes)) { if (getMDOperandAsInt(StallFree, 0)) { BF->addDecorate(new SPIRVDecorateStallFreeINTEL(BF)); } } } if (MDNode *LoopFuse = F->getMetadata(kSPIR2MD::LoopFuse)) { if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_loop_fuse)) { size_t Depth = getMDOperandAsInt(LoopFuse, 0); size_t Independent = getMDOperandAsInt(LoopFuse, 1); BF->addDecorate( new SPIRVDecorateFuseLoopsInFunctionINTEL(BF, Depth, Independent)); } } if (MDNode *PreferDSP = F->getMetadata(kSPIR2MD::PreferDSP)) { if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_dsp_control)) { size_t Mode = getMDOperandAsInt(PreferDSP, 0); MDNode *PropDSPPref = F->getMetadata(kSPIR2MD::PropDSPPref); size_t Propagate = PropDSPPref ? getMDOperandAsInt(PropDSPPref, 0) : 0; BF->addDecorate(new SPIRVDecorateMathOpDSPModeINTEL(BF, Mode, Propagate)); } } if (MDNode *InitiationInterval = F->getMetadata(kSPIR2MD::InitiationInterval)) { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) { if (size_t Cycles = getMDOperandAsInt(InitiationInterval, 0)) BF->addDecorate(new SPIRVDecorateInitiationIntervalINTEL(BF, Cycles)); } } if (MDNode *MaxConcurrency = F->getMetadata(kSPIR2MD::MaxConcurrency)) { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) { size_t Invocations = getMDOperandAsInt(MaxConcurrency, 0); BF->addDecorate(new SPIRVDecorateMaxConcurrencyINTEL(BF, Invocations)); } } if (MDNode *PipelineKernel = F->getMetadata(kSPIR2MD::PipelineKernel)) { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_invocation_pipelining_attributes)) { size_t Pipeline = getMDOperandAsInt(PipelineKernel, 0); BF->addDecorate(new SPIRVDecoratePipelineEnableINTEL(BF, Pipeline)); } } // In addition, process the decorations on the function if (auto *FDecoMD = F->getMetadata(SPIRV_MD_DECORATIONS)) transMetadataDecorations(FDecoMD, BF); } void LLVMToSPIRVBase::transFunctionMetadataAsExecutionMode(SPIRVFunction *BF, Function *F) { SmallVector RegisterAllocModeMDs; F->getMetadata("RegisterAllocMode", RegisterAllocModeMDs); for (unsigned I = 0; I < RegisterAllocModeMDs.size(); I++) { auto *RegisterAllocMode = RegisterAllocModeMDs[I]->getOperand(0).get(); if (isa(RegisterAllocMode)) { StringRef Str = getMDOperandAsString(RegisterAllocModeMDs[I], 0); NamedMaximumNumberOfRegisters NamedValue = SPIRVNamedMaximumNumberOfRegistersNameMap::rmap(Str.str()); BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, ExecutionModeNamedMaximumRegistersINTEL, NamedValue))); } else if (isa(RegisterAllocMode)) { auto *RegisterAllocNodeMDOp = getMDOperandAsMDNode(RegisterAllocModeMDs[I], 0); int Num = getMDOperandAsInt(RegisterAllocNodeMDOp, 0); auto *Const = BM->addConstant(transType(Type::getInt32Ty(F->getContext())), Num); BF->addExecutionMode(BM->add(new SPIRVExecutionModeId( BF, ExecutionModeMaximumRegistersIdINTEL, Const->getId()))); } else { int64_t RegisterAllocVal = mdconst::dyn_extract(RegisterAllocMode)->getZExtValue(); BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, ExecutionModeMaximumRegistersINTEL, RegisterAllocVal))); } } } void LLVMToSPIRVBase::transFunctionMetadataAsUserSemanticDecoration( SPIRVFunction *BF, Function *F) { if (auto *RegisterAllocModeMD = F->getMetadata("RegisterAllocMode")) { // TODO: Once the design for per-kernel register size allocation is // finalized, we will need to move away from UserSemantic and introduce an // extension int RegisterAllocNodeMDOp = getMDOperandAsInt(RegisterAllocModeMD, 0); // The current RegisterAllocMode metadata format is as follows // AUTO - 0 // SMALL - 1 // LARGE - 2 // DEFAULT - 3 // Currently we only support AUTO, SMALL and LARGE if (RegisterAllocNodeMDOp == 0 || RegisterAllocNodeMDOp == 1 || RegisterAllocNodeMDOp == 2) { // 4 threads per eu means large grf mode, and 8 threads per eu // means small grf mode, 0 means use internal heuristics to choose std::string NumThreads; switch (RegisterAllocNodeMDOp) { case 0: NumThreads = "0"; break; case 1: NumThreads = "8"; break; case 2: NumThreads = "4"; break; default: llvm_unreachable("Not implemented"); } BF->addDecorate(new SPIRVDecorateUserSemanticAttr( BF, "num-thread-per-eu " + NumThreads)); } } } void LLVMToSPIRVBase::transAuxDataInst(SPIRVValue *BV, Value *V) { auto *GO = cast(V); auto *F = dyn_cast(GO); auto *GV = dyn_cast(GO); assert((F || GV) && "Invalid value type"); auto *BM = BV->getModule(); if (!BM->preserveAuxData()) return; if (!BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6)) BM->addExtension(SPIRV::ExtensionID::SPV_KHR_non_semantic_info); else BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_6); const auto &Attrs = F ? F->getAttributes().getFnAttrs() : GV->getAttributes(); for (const auto &Attr : Attrs) { std::vector Ops; Ops.push_back(BV->getId()); if (Attr.isStringAttribute()) { // Format for String attributes is: // NonSemanticAuxData*Attribute ValueName AttrName AttrValue // or, if no value: // NonSemanticAuxData*Attribute ValueName AttrName // // AttrName and AttrValue are always Strings StringRef AttrKind = Attr.getKindAsString(); StringRef AttrValue = Attr.getValueAsString(); auto *KindSpvString = BM->getString(AttrKind.str()); Ops.push_back(KindSpvString->getId()); if (!AttrValue.empty()) { auto *ValueSpvString = BM->getString(AttrValue.str()); Ops.push_back(ValueSpvString->getId()); } } else { // Format for other types is: // NonSemanticAuxData*Attribute ValueName AttrStr // AttrStr is always a String. std::string AttrStr = Attr.getAsString(); auto *AttrSpvString = BM->getString(AttrStr); Ops.push_back(AttrSpvString->getId()); } BM->addAuxData(F ? NonSemanticAuxData::FunctionAttribute : NonSemanticAuxData::GlobalVariableAttribute, transType(Type::getVoidTy(V->getContext())), Ops); } SmallVector> AllMD; SmallVector MDNames; V->getContext().getMDKindNames(MDNames); GO->getAllMetadata(AllMD); for (const auto &MD : AllMD) { std::string MDName = MDNames[MD.first].str(); // spirv.Decorations, spirv.ParameterDecorations and debug information are // handled elsewhere for both forward and reverse translation and are // complicated to support here, so just skip them. if (MDName == SPIRV_MD_DECORATIONS || MDName == SPIRV_MD_PARAMETER_DECORATIONS || MD.first == LLVMContext::MD_dbg) continue; // Format for metadata is: // NonSemanticAuxData*Metadata ValueName MDName MDVals... // MDName is always a String, MDVals have different types as explained // below. Also note this instruction has a variable number of operands std::vector Ops; Ops.push_back(BV->getId()); Ops.push_back(BM->getString(MDName)->getId()); for (unsigned int OpIdx = 0; OpIdx < MD.second->getNumOperands(); OpIdx++) { const auto &CurOp = MD.second->getOperand(OpIdx); if (auto *MDStr = dyn_cast(CurOp)) { // For MDString, MDVal is String auto *SPIRVStr = BM->getString(MDStr->getString().str()); Ops.push_back(SPIRVStr->getId()); } else if (auto *ValueAsMeta = dyn_cast(CurOp)) { // For Value metadata, MDVal is a SPIRVValue auto *SPIRVVal = transValue(ValueAsMeta->getValue(), nullptr); Ops.push_back(SPIRVVal->getId()); } else { assert(false && "Unsupported metadata type"); } } BM->addAuxData(F ? NonSemanticAuxData::FunctionMetadata : NonSemanticAuxData::GlobalVariableMetadata, transType(Type::getVoidTy(V->getContext())), Ops); } } SPIRVValue *LLVMToSPIRVBase::transConstantUse(Constant *C, SPIRVType *ExpectedType) { // Constant expressions expect their pointer types to be i8* in opaque pointer // mode, but the value may have a different "natural" type. If that is the // case, we need to adjust the type of the constant. SPIRVValue *Trans = transValue(C, nullptr, true, FuncTransMode::Pointer); std::vector Ops = {Trans->getId()}; if (Trans->getType() == ExpectedType || Trans->getType()->isTypePipeStorage()) return Trans; assert((C->getType()->isPointerTy() || ExpectedType->isTypeUntypedPointerKHR()) && "Only pointer type mismatches should be possible"); // In the common case of strings ([N x i8] GVs), see if we can emit a GEP // instruction. if (auto *GV = dyn_cast(C)) { if (GV->getValueType()->isArrayTy() && GV->getValueType()->getArrayElementType()->isIntegerTy(8)) { SPIRVWord Offset = transValue(getUInt32(M, 0), nullptr)->getId(); Ops.push_back(Offset); Ops.push_back(Offset); if (ExpectedType->isTypeUntypedPointerKHR()) { llvm::Type *Ty = Scavenger->getScavengedType(C); SPIRVType *PtrTy = nullptr; if (auto *TPT = dyn_cast(Ty)) { PtrTy = transType(TPT->getElementType()); Ops = getVec(PtrTy->getId(), Ops); } } return BM->addPtrAccessChainInst(ExpectedType, std::move(Ops), nullptr, true); } } // Otherwise, just use a bitcast. return BM->addUnaryInst(OpBitcast, ExpectedType, Trans, nullptr); } SPIRVValue *LLVMToSPIRVBase::transConstant(Value *V) { SPIRVType *ExpectedType = transScavengedType(V); if (isa(V)) return BM->addNullConstant(bcast(ExpectedType)); if (isa(V)) return BM->addNullConstant(ExpectedType); if (auto *CAZero = dyn_cast(V)) { Type *AggType = CAZero->getType(); if (const StructType *ST = dyn_cast(AggType)) if (ST->hasName() && ST->getName() == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler)) return BM->addSamplerConstant(transType(AggType), 0, 0, 0); return BM->addNullConstant(transType(AggType)); } if (auto *ConstI = dyn_cast(V)) { unsigned BitWidth = ConstI->getIntegerType()->getBitWidth(); if (BitWidth > 64) { BM->getErrorLog().checkError( BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_arbitrary_precision_integers), SPIRVEC_InvalidBitWidth, std::to_string(BitWidth)); return BM->addConstant(ExpectedType, ConstI->getValue()); } return BM->addConstant(ExpectedType, ConstI->getZExtValue()); } if (auto *ConstFP = dyn_cast(V)) { auto *BT = static_cast(ExpectedType); return BM->addConstant( BT, ConstFP->getValueAPF().bitcastToAPInt().getZExtValue()); } if (auto *ConstDA = dyn_cast(V)) { SPIRVType *InnerTy = ExpectedType->getArrayElementType(); std::vector BV; for (unsigned I = 0, E = ConstDA->getNumElements(); I != E; ++I) BV.push_back(transConstantUse(ConstDA->getElementAsConstant(I), InnerTy)); return BM->addCompositeConstant(ExpectedType, BV); } if (auto *ConstA = dyn_cast(V)) { SPIRVType *InnerTy = ExpectedType->getArrayElementType(); std::vector BV; for (auto I = ConstA->op_begin(), E = ConstA->op_end(); I != E; ++I) BV.push_back(transConstantUse(cast(*I), InnerTy)); return BM->addCompositeConstant(ExpectedType, BV); } if (auto *ConstDV = dyn_cast(V)) { SPIRVType *InnerTy = ExpectedType->getScalarType(); std::vector BV; for (unsigned I = 0, E = ConstDV->getNumElements(); I != E; ++I) BV.push_back(transConstantUse(ConstDV->getElementAsConstant(I), InnerTy)); return BM->addCompositeConstant(ExpectedType, BV); } if (auto *ConstV = dyn_cast(V)) { SPIRVType *InnerTy = ExpectedType->getScalarType(); std::vector BV; for (auto I = ConstV->op_begin(), E = ConstV->op_end(); I != E; ++I) BV.push_back(transConstantUse(cast(*I), InnerTy)); return BM->addCompositeConstant(ExpectedType, BV); } if (const auto *ConstV = dyn_cast(V)) { StringRef StructName; if (ConstV->getType()->hasName()) StructName = ConstV->getType()->getName(); if (StructName == getSPIRVTypeName(kSPIRVTypeName::ConstantSampler)) { assert(ConstV->getNumOperands() == 3); SPIRVWord AddrMode = ConstV->getOperand(0)->getUniqueInteger().getZExtValue(), Normalized = ConstV->getOperand(1)->getUniqueInteger().getZExtValue(), FilterMode = ConstV->getOperand(2)->getUniqueInteger().getZExtValue(); assert(AddrMode < 5 && "Invalid addressing mode"); assert(Normalized < 2 && "Invalid value of normalized coords"); assert(FilterMode < 2 && "Invalid filter mode"); SPIRVType *SamplerTy = transType(ConstV->getType()); return BM->addSamplerConstant(SamplerTy, AddrMode, Normalized, FilterMode); } if (StructName == getSPIRVTypeName(kSPIRVTypeName::ConstantPipeStorage)) { assert(ConstV->getNumOperands() == 3); SPIRVWord PacketSize = ConstV->getOperand(0)->getUniqueInteger().getZExtValue(), PacketAlign = ConstV->getOperand(1)->getUniqueInteger().getZExtValue(), Capacity = ConstV->getOperand(2)->getUniqueInteger().getZExtValue(); assert(PacketAlign >= 1 && "Invalid packet alignment"); assert(PacketSize >= PacketAlign && PacketSize % PacketAlign == 0 && "Invalid packet size and/or alignment."); SPIRVType *PipeStorageTy = transType(ConstV->getType()); return BM->addPipeStorageConstant(PipeStorageTy, PacketSize, PacketAlign, Capacity); } std::vector BV; for (auto I = ConstV->op_begin(), E = ConstV->op_end(); I != E; ++I) { SPIRVType *InnerTy = ExpectedType->getStructMemberType(BV.size()); BV.push_back(transConstantUse(cast(*I), InnerTy)); } return BM->addCompositeConstant(ExpectedType, BV); } if (auto *ConstUE = dyn_cast(V)) { if (auto *GEP = dyn_cast(ConstUE)) { auto *TransPointerOperand = transValue(GEP->getPointerOperand(), nullptr); // Determine the expected pointer type from the GEP source element type. Type *GepSourceElemTy = GEP->getSourceElementType(); SPIRVType *ExpectedPtrTy = transPointerType(GepSourceElemTy, GEP->getPointerAddressSpace()); // Ensure the base pointer's type matches the GEP's effective source // element type. if (TransPointerOperand->getType() != ExpectedPtrTy) { TransPointerOperand = BM->addUnaryInst(OpBitcast, ExpectedPtrTy, TransPointerOperand, nullptr); } std::vector Ops = {TransPointerOperand->getId()}; for (unsigned I = 0, E = GEP->getNumIndices(); I != E; ++I) Ops.push_back(transValue(GEP->getOperand(I + 1), nullptr)->getId()); SPIRVType *TranslatedTy = transScavengedType(GEP); if (TranslatedTy->isTypeUntypedPointerKHR()) { llvm::Type *Ty = Scavenger->getScavengedType(GEP->getPointerOperand()); if (auto *TPT = dyn_cast(Ty)) { SPIRVType *PtrTy = transType(TPT->getElementType()); Ops = getVec(PtrTy->getId(), Ops); } } return BM->addPtrAccessChainInst(TranslatedTy, std::move(Ops), nullptr, GEP->isInBounds()); } auto *Inst = ConstUE->getAsInstruction(); SPIRVDBG(dbgs() << "ConstantExpr: " << *ConstUE << '\n'; dbgs() << "Instruction: " << *Inst << '\n';) auto *BI = transValue(Inst, nullptr, false); Inst->dropAllReferences(); UnboundInst.push_back(Inst); return BI; } // Translate aliases to their aliasee because they can't be represented // directly in SPIR-V. if (auto *const ConstAlias = dyn_cast(V)) { return transValue(ConstAlias->getAliasee(), nullptr, false, FuncTransMode::Pointer); } if (isa(V)) { return BM->addUndef(ExpectedType); } return nullptr; } SPIRVValue *LLVMToSPIRVBase::transValue(Value *V, SPIRVBasicBlock *BB, bool CreateForward, FuncTransMode FuncTrans) { LLVMToSPIRVValueMap::iterator Loc = ValueMap.find(V); if (Loc != ValueMap.end() && (!Loc->second->isForward() || CreateForward) && // do not return forward-decl of a function if we // actually want to create a function pointer !(FuncTrans == FuncTransMode::Pointer && isa(V))) return Loc->second; SPIRVDBG(dbgs() << "[transValue] " << *V << '\n'); assert((!isa(V) || isa(V) || isa(V) || isa(V) || isa(V) || isa(V) || BB) && "Invalid SPIRV BB"); auto *BV = transValueWithoutDecoration(V, BB, CreateForward, FuncTrans); if (!BV) return nullptr; // Only translate decorations for non-forward instructions. Forward // instructions will have their decorations translated when the actual // instruction is seen and rewritten to a real SPIR-V instruction. if (!BV->isForward() && !transDecoration(V, BV)) return nullptr; StringRef Name = V->getName(); if (!Name.empty()) // Don't erase the name, which BM might already have BM->setName(BV, Name.str()); return BV; } SPIRVInstruction *LLVMToSPIRVBase::transBinaryInst(BinaryOperator *B, SPIRVBasicBlock *BB) { unsigned LLVMOC = B->getOpcode(); auto *Op0 = transValue(B->getOperand(0), BB); SPIRVInstruction *BI = BM->addBinaryInst( transBoolOpCode(Op0, OpCodeMap::map(LLVMOC)), transType(B->getType()), Op0, transValue(B->getOperand(1), BB), BB); // BinaryOperator can have no parent if it is handled as an expression inside // another instruction. if (B->getParent() && isUnfusedMulAdd(B)) { Function *F = B->getFunction(); SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName() << ": possible fma candidate " << *B << '\n'); joinFPContract(F, FPContract::DISABLED); } return BI; } SPIRVInstruction *LLVMToSPIRVBase::transCmpInst(CmpInst *Cmp, SPIRVBasicBlock *BB) { auto *Op0 = Cmp->getOperand(0); SPIRVValue *TOp0 = transValue(Op0, BB); SPIRVValue *TOp1 = transValue(Cmp->getOperand(1), BB); if (Op0->getType()->isPointerTy()) { auto P = Cmp->getPredicate(); if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4) && (P == ICmpInst::ICMP_EQ || P == ICmpInst::ICMP_NE) && Cmp->getOperand(1)->getType()->isPointerTy()) { Op OC = P == ICmpInst::ICMP_EQ ? OpPtrEqual : OpPtrNotEqual; return BM->addBinaryInst(OC, transType(Cmp->getType()), TOp0, TOp1, BB); } unsigned AS = cast(Op0->getType())->getAddressSpace(); SPIRVType *Ty = transType(getSizetType(AS)); TOp0 = BM->addUnaryInst(OpConvertPtrToU, Ty, TOp0, BB); TOp1 = BM->addUnaryInst(OpConvertPtrToU, Ty, TOp1, BB); } SPIRVInstruction *BI = BM->addCmpInst(transBoolOpCode(TOp0, CmpMap::map(Cmp->getPredicate())), transType(Cmp->getType()), TOp0, TOp1, BB); return BI; } SPIRVValue *LLVMToSPIRVBase::transUnaryInst(UnaryInstruction *U, SPIRVBasicBlock *BB) { if (isa(U) && U->getType()->isPtrOrPtrVectorTy()) { if (isa(U->getOperand(0))) { SPIRVType *ExpectedTy = transScavengedType(U); return BM->addNullConstant(bcast(ExpectedTy)); } if (isa(U->getOperand(0))) { SPIRVType *ExpectedTy = transScavengedType(U); return BM->addUndef(ExpectedTy); } } Op BOC = OpNop; if (auto *Cast = dyn_cast(U)) { const auto SrcAddrSpace = Cast->getSrcTy()->getPointerAddressSpace(); const auto DestAddrSpace = Cast->getDestTy()->getPointerAddressSpace(); if (DestAddrSpace == SPIRAS_Generic) { getErrorLog().checkError( SrcAddrSpace != SPIRAS_Constant, SPIRVEC_InvalidModule, U, "Casts from constant address space to generic are illegal\n"); BOC = OpPtrCastToGeneric; // In SPIR-V only casts to/from generic are allowed. But with // SPV_INTEL_usm_storage_classes we can also have casts from global_device // and global_host to global addr space and vice versa. } else if (SrcAddrSpace == SPIRAS_GlobalDevice || SrcAddrSpace == SPIRAS_GlobalHost) { getErrorLog().checkError(DestAddrSpace == SPIRAS_Global || DestAddrSpace == SPIRAS_Generic, SPIRVEC_InvalidModule, U, "Casts from global_device/global_host only " "allowed to global/generic\n"); if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_usm_storage_classes)) { if (DestAddrSpace == SPIRAS_Global) return nullptr; BOC = OpPtrCastToGeneric; } else { BOC = OpPtrCastToCrossWorkgroupINTEL; } } else if (DestAddrSpace == SPIRAS_GlobalDevice || DestAddrSpace == SPIRAS_GlobalHost) { getErrorLog().checkError(SrcAddrSpace == SPIRAS_Global || SrcAddrSpace == SPIRAS_Generic, SPIRVEC_InvalidModule, U, "Casts to global_device/global_host only " "allowed from global/generic\n"); if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_usm_storage_classes)) { if (SrcAddrSpace == SPIRAS_Global) return nullptr; BOC = OpGenericCastToPtr; } else { BOC = OpCrossWorkgroupCastToPtrINTEL; } } else { getErrorLog().checkError( SrcAddrSpace == SPIRAS_Generic, SPIRVEC_InvalidModule, U, "Casts from private/local/global address space are allowed only to " "generic\n"); getErrorLog().checkError( DestAddrSpace != SPIRAS_Constant, SPIRVEC_InvalidModule, U, "Casts from generic address space to constant are illegal\n"); BOC = OpGenericCastToPtr; } } else { auto OpCode = U->getOpcode(); BOC = OpCodeMap::map(OpCode); } auto *Op = transValue(U->getOperand(0), BB, true, FuncTransMode::Pointer); SPIRVType *TransTy = transScavengedType(U); return BM->addUnaryInst(transBoolOpCode(Op, BOC), TransTy, Op, BB); } /// This helper class encapsulates information extraction from /// "llvm.loop.parallel_access_indices" metadata hints. Initialize /// with a pointer to an MDNode with the following structure: /// ! = !{!"llvm.loop.parallel_access_indices", !, !, ...} /// OR: /// ! = !{!"llvm.loop.parallel_access_indices", !, i32 } /// /// All of the MDNode-type operands mark the index groups for particular /// array variables. An optional i32 value indicates the safelen (safe /// number of iterations) for the optimization application to these /// array variables. If the safelen value is absent, an infinite /// number of iterations is implied. class LLVMParallelAccessIndices { public: LLVMParallelAccessIndices( MDNode *Node, LLVMToSPIRVBase::LLVMToSPIRVMetadataMap &IndexGroupArrayMap) : Node(Node) { assert(isValid() && "LLVMParallelAccessIndices initialized from an invalid MDNode"); unsigned NumOperands = Node->getNumOperands(); auto *SafeLenExpression = mdconst::dyn_extract_or_null( Node->getOperand(NumOperands - 1)); // If no safelen value is specified and the last operand // casts to an MDNode* rather than an int, 0 will be stored SafeLen = SafeLenExpression ? SafeLenExpression->getZExtValue() : 0; // Count MDNode operands that refer to index groups: // - operand [0] is a string literal and should be ignored; // - depending on whether a particular safelen is specified as the // last operand, we may or may not want to extract the latter // as an index group unsigned NumIdxGroups = SafeLen ? NumOperands - 2 : NumOperands - 1; for (unsigned I = 1; I <= NumIdxGroups; ++I) { MDNode *IdxGroupNode = getMDOperandAsMDNode(Node, I); assert(IdxGroupNode && "Invalid operand in the MDNode for LLVMParallelAccessIndices"); auto IdxGroupArrayPairIt = IndexGroupArrayMap.find(IdxGroupNode); // TODO: Some LLVM IR optimizations (e.g. loop inlining as part of // the function inlining) can result in invalid parallel_access_indices // metadata. Only valid cases will pass the subsequent check and // survive the translation. This check should be replaced with an // assertion once all known cases are handled. if (IdxGroupArrayPairIt != IndexGroupArrayMap.end()) for (SPIRVId ArrayAccessId : IdxGroupArrayPairIt->second) ArrayVariablesVec.push_back(ArrayAccessId); } } bool isValid() { bool IsNamedCorrectly = getMDOperandAsString(Node, 0) == ExpectedName; return Node && IsNamedCorrectly; } unsigned getSafeLen() { return SafeLen; } const std::vector &getArrayVariables() { return ArrayVariablesVec; } private: MDNode *Node; const std::string ExpectedName = "llvm.loop.parallel_access_indices"; std::vector ArrayVariablesVec; unsigned SafeLen; }; /// Go through the operands !llvm.loop metadata attached to the branch /// instruction, fill the Loop Control mask and possible parameters for its /// fields. spv::LoopControlMask LLVMToSPIRVBase::getLoopControl(const BranchInst *Branch, std::vector &Parameters) { if (!Branch) return spv::LoopControlMaskNone; MDNode *LoopMD = Branch->getMetadata("llvm.loop"); if (!LoopMD) return spv::LoopControlMaskNone; size_t LoopControl = spv::LoopControlMaskNone; std::vector> ParametersToSort; // If only a subset of loop count parameters is defined in metadata // then undefined ones should have a default value -1 in SPIR-V. // Preset all loop count parameters with the default value. struct LoopCountInfo { int64_t Min = -1, Max = -1, Avg = -1; } LoopCount; // Unlike with most of the cases, some loop metadata specifications // can occur multiple times - for these, all correspondent tokens // need to be collected first, and only then added to SPIR-V loop // parameters in a separate routine std::vector> DependencyArrayParameters; for (const MDOperand &MDOp : LoopMD->operands()) { if (MDNode *Node = dyn_cast(MDOp)) { StringRef S = getMDOperandAsString(Node, 0); // Set the loop control bits. Parameters are set in the order described // in 3.23 SPIR-V Spec. rev. 1.4: // Bits that are set can indicate whether an additional operand follows, // as described by the table. If there are multiple following operands // indicated, they are ordered: Those indicated by smaller-numbered bits // appear first. if (S == "llvm.loop.unroll.disable") LoopControl |= spv::LoopControlDontUnrollMask; else if (S == "llvm.loop.unroll.enable") LoopControl |= spv::LoopControlUnrollMask; // Attempt to do full unroll of the loop and disable unrolling if the trip // count is not known at compile time by setting PartialCount to 1 else if (S == "llvm.loop.unroll.full") { LoopControl |= spv::LoopControlUnrollMask; if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) { BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4); ParametersToSort.emplace_back(spv::LoopControlPartialCountMask, 1); LoopControl |= spv::LoopControlPartialCountMask; } } // PartialCount must not be used with the DontUnroll bit else if (S == "llvm.loop.unroll.count" && !(LoopControl & LoopControlDontUnrollMask)) { if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) { BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back(spv::LoopControlPartialCountMask, I); LoopControl |= spv::LoopControlPartialCountMask; } } else if (S == "llvm.loop.ivdep.enable") LoopControl |= spv::LoopControlDependencyInfiniteMask; else if (S == "llvm.loop.ivdep.safelen") { size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back(spv::LoopControlDependencyLengthMask, I); LoopControl |= spv::LoopControlDependencyLengthMask; } else if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_loop_controls)) { // Add Intel specific Loop Control masks if (S == "llvm.loop.ii.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back( spv::LoopControlInitiationIntervalINTELMask, I); LoopControl |= spv::LoopControlInitiationIntervalINTELMask; } else if (S == "llvm.loop.max_concurrency.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back(spv::LoopControlMaxConcurrencyINTELMask, I); LoopControl |= spv::LoopControlMaxConcurrencyINTELMask; } else if (S == "llvm.loop.parallel_access_indices") { // Intel FPGA IVDep loop attribute LLVMParallelAccessIndices IVDep(Node, IndexGroupArrayMap); // Store IVDep-specific parameters into an intermediate // container to address the case when there're multiple // IVDep metadata nodes and this condition gets entered multiple // times. The update of the main parameters vector & the loop control // mask will be done later, in the main scope of the function unsigned SafeLen = IVDep.getSafeLen(); for (auto &ArrayId : IVDep.getArrayVariables()) DependencyArrayParameters.emplace_back(ArrayId, SafeLen); } else if (S == "llvm.loop.intel.pipelining.enable") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back(spv::LoopControlPipelineEnableINTELMask, I); LoopControl |= spv::LoopControlPipelineEnableINTELMask; } else if (S == "llvm.loop.coalesce.enable") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); ParametersToSort.emplace_back(spv::LoopControlLoopCoalesceINTELMask, 0); LoopControl |= spv::LoopControlLoopCoalesceINTELMask; } else if (S == "llvm.loop.coalesce.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back(spv::LoopControlLoopCoalesceINTELMask, I); LoopControl |= spv::LoopControlLoopCoalesceINTELMask; } else if (S == "llvm.loop.max_interleaving.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back( spv::LoopControlMaxInterleavingINTELMask, I); LoopControl |= spv::LoopControlMaxInterleavingINTELMask; } else if (S == "llvm.loop.intel.speculated.iterations.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back( spv::LoopControlSpeculatedIterationsINTELMask, I); LoopControl |= spv::LoopControlSpeculatedIterationsINTELMask; } else if (S == "llvm.loop.fusion.disable") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); LoopControl |= spv::LoopControlNoFusionINTELMask; } else if (S == "llvm.loop.intel.loopcount_min") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); LoopCount.Min = getMDOperandAsInt(Node, 1); LoopControl |= spv::LoopControlLoopCountINTELMask; } else if (S == "llvm.loop.intel.loopcount_max") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); LoopCount.Max = getMDOperandAsInt(Node, 1); LoopControl |= spv::LoopControlLoopCountINTELMask; } else if (S == "llvm.loop.intel.loopcount_avg") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); LoopCount.Avg = getMDOperandAsInt(Node, 1); LoopControl |= spv::LoopControlLoopCountINTELMask; } else if (S == "llvm.loop.intel.max_reinvocation_delay.count") { BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); size_t I = getMDOperandAsInt(Node, 1); ParametersToSort.emplace_back( spv::LoopControlMaxReinvocationDelayINTELMask, I); LoopControl |= spv::LoopControlMaxReinvocationDelayINTELMask; } } } } if (LoopControl & spv::LoopControlLoopCountINTELMask) { // LoopCountINTELMask have int64 literal parameters and we need to store // int64 into 2 SPIRVWords ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Min)); ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Min >> 32)); ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Max)); ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Max >> 32)); ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Avg)); ParametersToSort.emplace_back(spv::LoopControlLoopCountINTELMask, static_cast(LoopCount.Avg >> 32)); } // If any loop control parameters were held back until fully collected, // now is the time to move the information to the main parameters collection if (!DependencyArrayParameters.empty()) { // The first parameter states the number of pairs to be // listed ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask, DependencyArrayParameters.size()); for (auto &ArraySflnPair : DependencyArrayParameters) { ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask, ArraySflnPair.first); ParametersToSort.emplace_back(spv::LoopControlDependencyArrayINTELMask, ArraySflnPair.second); } BM->addExtension(ExtensionID::SPV_INTEL_fpga_loop_controls); BM->addCapability(CapabilityFPGALoopControlsINTEL); LoopControl |= spv::LoopControlDependencyArrayINTELMask; } std::stable_sort(ParametersToSort.begin(), ParametersToSort.end(), [](const std::pair &CompareLeft, const std::pair &CompareRight) { return CompareLeft.first < CompareRight.first; }); for (const auto &Param : ParametersToSort) Parameters.push_back(Param.second); return static_cast(LoopControl); } static int transAtomicOrdering(llvm::AtomicOrdering Ordering) { return OCLMemOrderMap::map( static_cast(llvm::toCABI(Ordering))); } SPIRVValue *LLVMToSPIRVBase::transAtomicStore(StoreInst *ST, SPIRVBasicBlock *BB) { spv::Scope S = toSPIRVScope(ST->getContext(), ST->getSyncScopeID()); std::vector Ops{ST->getPointerOperand(), getUInt32(M, S), getUInt32(M, transAtomicOrdering(ST->getOrdering())), ST->getValueOperand()}; std::vector SPIRVOps = transValue(Ops, BB); return mapValue(ST, BM->addInstTemplate(OpAtomicStore, BM->getIds(SPIRVOps), BB, nullptr)); } SPIRVValue *LLVMToSPIRVBase::transAtomicLoad(LoadInst *LD, SPIRVBasicBlock *BB) { spv::Scope S = toSPIRVScope(LD->getContext(), LD->getSyncScopeID()); std::vector Ops{ LD->getPointerOperand(), getUInt32(M, S), getUInt32(M, transAtomicOrdering(LD->getOrdering()))}; std::vector SPIRVOps = transValue(Ops, BB); return mapValue(LD, BM->addInstTemplate(OpAtomicLoad, BM->getIds(SPIRVOps), BB, transScavengedType(LD))); } // Aliasing list MD contains several scope MD nodes whithin it. Each scope MD // has a selfreference and an extra MD node for aliasing domain and also it // can contain an optional string operand. Domain MD contains a self-reference // with an optional string operand. Here we unfold the list, creating SPIR-V // aliasing instructions. // TODO: add support for an optional string operand. SPIRVEntry *addMemAliasingINTELInstructions(SPIRVModule *M, MDNode *AliasingListMD) { if (AliasingListMD->getNumOperands() == 0) return nullptr; std::vector ListId; for (const MDOperand &MDListOp : AliasingListMD->operands()) { if (MDNode *ScopeMD = dyn_cast(MDListOp)) { if (ScopeMD->getNumOperands() < 2) return nullptr; MDNode *DomainMD = dyn_cast(ScopeMD->getOperand(1)); if (!DomainMD) return nullptr; auto *Domain = M->getOrAddAliasDomainDeclINTELInst(std::vector(), DomainMD); auto *Scope = M->getOrAddAliasScopeDeclINTELInst({Domain->getId()}, ScopeMD); ListId.push_back(Scope->getId()); } } return M->getOrAddAliasScopeListDeclINTELInst(ListId, AliasingListMD); } // Translate alias.scope/noalias metadata attached to store and load // instructions. void transAliasingMemAccess(SPIRVModule *BM, MDNode *AliasingListMD, std::vector &MemoryAccess, SPIRVWord MemAccessMask) { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_memory_access_aliasing)) return; auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD); if (!MemAliasList) return; MemoryAccess[0] |= MemAccessMask; MemoryAccess.push_back(MemAliasList->getId()); } /// An instruction may use an instruction from another BB which has not been /// translated. SPIRVForward should be created as place holder for these /// instructions and replaced later by the real instructions. /// Use CreateForward = true to indicate such situation. SPIRVValue * LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, bool CreateForward, FuncTransMode FuncTrans) { if (auto *LBB = dyn_cast(V)) { auto *BF = static_cast(getTranslatedValue(LBB->getParent())); assert(BF && "Function not translated"); BB = static_cast(mapValue(V, BM->addBasicBlock(BF))); BM->setName(BB, LBB->getName().str()); return BB; } if (auto *F = dyn_cast(V)) { if (FuncTrans == FuncTransMode::Decl) return transFunctionDecl(F); if (!BM->checkExtension(ExtensionID::SPV_INTEL_function_pointers, SPIRVEC_FunctionPointers, toString(V))) return nullptr; return BM->addConstantFunctionPointerINTEL( transPointerType(transScavengedType(F), F->getAddressSpace()), static_cast(transValue(F, nullptr))); } if (auto *GV = dyn_cast(V)) { llvm::Type *Ty = Scavenger->getScavengedType(GV); // Though variables with common linkage type are initialized by 0, // they can be represented in SPIR-V as uninitialized variables with // 'Export' linkage type, just as tentative definitions look in C llvm::Constant *Init = GV->hasInitializer() && !GV->hasCommonLinkage() ? GV->getInitializer() : nullptr; SPIRVValue *BVarInit = nullptr; StructType *ST = Init ? dyn_cast(Init->getType()) : nullptr; if (ST && ST->hasName() && isSPIRVConstantName(ST->getName())) { auto *BV = transConstant(Init); assert(BV); transAuxDataInst(BV, V); return mapValue(V, BV); } if (isa_and_nonnull(Init)) { BVarInit = transValue(Init, nullptr); } else if (ST && isa(Init)) { // Undef initializer for LLVM structure be can translated to // OpConstantComposite with OpUndef constituents. auto I = ValueMap.find(Init); if (I == ValueMap.end()) { std::vector Elements; for (Type *E : ST->elements()) Elements.push_back(transValue(UndefValue::get(E), nullptr)); BVarInit = BM->addCompositeConstant(transType(ST), Elements); ValueMap[Init] = BVarInit; } else BVarInit = I->second; } else if (Init && !isa(Init)) { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_long_composites)) { if (auto *ArrTy = dyn_cast_or_null(Init->getType())) { // First 3 words of OpConstantComposite encode: 1) word count & // opcode, 2) Result Type and 3) Result Id. Max length of SPIRV // instruction = 65535 words. constexpr int MaxNumElements = MaxWordCount - SPIRVSpecConstantComposite::FixedWC; if (ArrTy->getNumElements() > MaxNumElements && !isa(Init)) { std::stringstream SS; SS << "Global variable has a constant array initializer with a " << "number of elements greater than OpConstantComposite can " << "have (" << MaxNumElements << "). Should the array be " << "split?\n Original LLVM value:\n" << toString(GV); getErrorLog().checkError(false, SPIRVEC_InvalidWordCount, SS.str()); } } } if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) { BVarInit = transConstantUse(Init, transType(Init->getType())); } else { SPIRVType *TransTy = transType(Ty); BVarInit = transConstantUse(Init, TransTy->getPointerElementType()); } } SPIRVStorageClassKind StorageClass; auto AddressSpace = static_cast(GV->getAddressSpace()); bool IsVectorCompute = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute) && GV->hasAttribute(kVCMetadata::VCGlobalVariable); if (IsVectorCompute) StorageClass = VectorComputeUtil::getVCGlobalVarStorageClass(AddressSpace); else { // Lower global_device and global_host address spaces that were added in // SYCL as part of SYCL_INTEL_usm_address_spaces extension to just global // address space if device doesn't support SPV_INTEL_usm_storage_classes // extension if ((AddressSpace == SPIRAS_GlobalDevice || AddressSpace == SPIRAS_GlobalHost) && !BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_usm_storage_classes)) AddressSpace = SPIRAS_Global; StorageClass = SPIRSPIRVAddrSpaceMap::map(AddressSpace); if (StorageClass == StorageClassFunction) { std::stringstream SS; SS << "Global variable cannot have Function storage class. " << "Consider setting a proper address space.\n " << "Original LLVM value:\n" << toString(GV); getErrorLog().checkError(false, SPIRVEC_InvalidInstruction, SS.str()); } } SPIRVType *TranslatedTy = transType(Ty); auto *BVar = static_cast(BM->addVariable( TranslatedTy, TranslatedTy->isTypeUntypedPointerKHR() ? transType(GV->getValueType()) : nullptr, GV->isConstant(), transLinkageType(GV), BVarInit, GV->getName().str(), StorageClass, nullptr)); if (IsVectorCompute) { BVar->addDecorate(DecorationVectorComputeVariableINTEL); if (GV->hasAttribute(kVCMetadata::VCByteOffset)) { SPIRVWord Offset = {}; GV->getAttribute(kVCMetadata::VCByteOffset) .getValueAsString() .getAsInteger(0, Offset); BVar->addDecorate(DecorationGlobalVariableOffsetINTEL, Offset); } if (GV->hasAttribute(kVCMetadata::VCVolatile)) BVar->addDecorate(DecorationVolatile); if (GV->hasAttribute(kVCMetadata::VCSingleElementVector)) translateSEVDecoration( GV->getAttribute(kVCMetadata::VCSingleElementVector), BVar); } transAuxDataInst(BVar, V); mapValue(V, BVar); spv::BuiltIn Builtin = spv::BuiltInPosition; if (!GV->hasName() || !getSPIRVBuiltin(GV->getName().str(), Builtin)) return BVar; if (static_cast(Builtin) >= internal::BuiltInSubDeviceIDINTEL && static_cast(Builtin) <= internal::BuiltInGlobalHWThreadIDINTEL) { std::string ErrorStr = "SPV_INTEL_hw_thread_queries\n" "Please report to " "https://github.com/intel/llvm in case if you see " "this error.\nRef LLVM Value:"; getErrorLog().checkError(false, SPIRVEC_DeprecatedExtension, V, ErrorStr); } BVar->setBuiltin(Builtin); return BVar; } if (isa(V)) { auto *BV = transConstant(V); assert(BV); // Don't store pointer constants in the map -- they are opaque and thus we // might reuse the wrong type (Example: a null value) if we do so. if (V->getType()->isPointerTy()) return BV; return mapValue(V, BV); } if (auto *Arg = dyn_cast(V)) { unsigned ArgNo = Arg->getArgNo(); SPIRVFunction *BF = BB->getParent(); // assert(BF->existArgument(ArgNo)); return mapValue(V, BF->getArgument(ArgNo)); } if (CreateForward) return mapValue(V, BM->addForward(transScavengedType(V))); if (StoreInst *ST = dyn_cast(V)) { if (ST->isAtomic()) return transAtomicStore(ST, BB); // Keep this vector to store MemoryAccess operands for both Alignment and // Aliasing information. std::vector MemoryAccess(1, 0); if (ST->isVolatile()) MemoryAccess[0] |= MemoryAccessVolatileMask; MemoryAccess[0] |= MemoryAccessAlignedMask; MemoryAccess.push_back(ST->getAlign().value()); if (ST->getMetadata(LLVMContext::MD_nontemporal)) MemoryAccess[0] |= MemoryAccessNontemporalMask; if (MDNode *AliasingListMD = ST->getMetadata(LLVMContext::MD_alias_scope)) transAliasingMemAccess(BM, AliasingListMD, MemoryAccess, MemoryAccessAliasScopeINTELMaskMask); if (MDNode *AliasingListMD = ST->getMetadata(LLVMContext::MD_noalias)) transAliasingMemAccess(BM, AliasingListMD, MemoryAccess, MemoryAccessNoAliasINTELMaskMask); if (MemoryAccess.front() == 0) MemoryAccess.clear(); return mapValue(V, BM->addStoreInst(transValue(ST->getPointerOperand(), BB), transValue(ST->getValueOperand(), BB, true, FuncTransMode::Pointer), MemoryAccess, BB)); } if (LoadInst *LD = dyn_cast(V)) { if (LD->isAtomic()) return transAtomicLoad(LD, BB); // Keep this vector to store MemoryAccess operands for both Alignment and // Aliasing information. std::vector MemoryAccess(1, 0); if (LD->isVolatile()) MemoryAccess[0] |= MemoryAccessVolatileMask; MemoryAccess[0] |= MemoryAccessAlignedMask; MemoryAccess.push_back(LD->getAlign().value()); if (LD->getMetadata(LLVMContext::MD_nontemporal)) MemoryAccess[0] |= MemoryAccessNontemporalMask; if (MDNode *AliasingListMD = LD->getMetadata(LLVMContext::MD_alias_scope)) transAliasingMemAccess(BM, AliasingListMD, MemoryAccess, MemoryAccessAliasScopeINTELMaskMask); if (MDNode *AliasingListMD = LD->getMetadata(LLVMContext::MD_noalias)) transAliasingMemAccess(BM, AliasingListMD, MemoryAccess, MemoryAccessNoAliasINTELMaskMask); if (MemoryAccess.front() == 0) MemoryAccess.clear(); if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) { SPIRVValue *Source = transValue(LD->getPointerOperand(), BB); SPIRVType *PtrElTy = Source->getType()->getPointerElementType(); SPIRVType *LoadTy = transType(LD->getType()); // For special types (images, pipes, etc.) do not use explicit load type, // but rather use the source type (calculated in SPIRVLoad constructor) if (LoadTy->isTypeUntypedPointerKHR() && PtrElTy->isSPIRVOpaqueType()) { LoadTy = nullptr; } return mapValue(V, BM->addLoadInst(Source, MemoryAccess, BB, LoadTy)); } return mapValue(V, BM->addLoadInst(transValue(LD->getPointerOperand(), BB), MemoryAccess, BB)); } if (BinaryOperator *B = dyn_cast(V)) { SPIRVInstruction *BI = transBinaryInst(B, BB); return mapValue(V, BI); } if (dyn_cast(V)) return mapValue(V, BM->addUnreachableInst(BB)); if (auto *RI = dyn_cast(V)) { if (auto *RV = RI->getReturnValue()) { if (auto *II = dyn_cast(RV)) { if (II->getIntrinsicID() == Intrinsic::frexp) { // create composite type from the return value and second operand auto *FrexpResult = transValue(RV, BB); SPIRVValue *IntFromFrexpResult = static_cast(FrexpResult)->getArgValues()[1]; SPIRVType *LoadTy = nullptr; if (IntFromFrexpResult->isUntypedVariable()) LoadTy = static_cast(IntFromFrexpResult) ->getDataType(); IntFromFrexpResult = BM->addLoadInst(IntFromFrexpResult, {}, BB, LoadTy); std::vector Operands = {FrexpResult->getId(), IntFromFrexpResult->getId()}; auto *Compos = BM->addCompositeConstructInst(transType(RV->getType()), Operands, BB); return mapValue(V, BM->addReturnValueInst(Compos, BB)); } } return mapValue(V, BM->addReturnValueInst(transValue(RV, BB), BB)); } return mapValue(V, BM->addReturnInst(BB)); } if (CmpInst *Cmp = dyn_cast(V)) { if (Cmp->getPredicate() == CmpInst::Predicate::FCMP_FALSE) { auto *CmpTy = Cmp->getType(); SPIRVValue *FalseValue = CmpTy->isVectorTy() ? BM->addNullConstant(transType(CmpTy)) : BM->addConstant(BM->addBoolType(), 0); return mapValue(V, FalseValue); } SPIRVInstruction *BI = transCmpInst(Cmp, BB); return mapValue(V, BI); } if (SelectInst *Sel = dyn_cast(V)) return mapValue( V, BM->addSelectInst( transValue(Sel->getCondition(), BB), transValue(Sel->getTrueValue(), BB, true, FuncTransMode::Pointer), transValue(Sel->getFalseValue(), BB, true, FuncTransMode::Pointer), BB)); if (AllocaInst *Alc = dyn_cast(V)) { SPIRVType *TranslatedTy = transScavengedType(V); if (Alc->isArrayAllocation()) { SPIRVValue *Length = transValue(Alc->getArraySize(), BB); assert(Length && "Couldn't translate array size!"); if (isConstantOpCode(Length->getOpCode())) { // Length can be any constant instruction, either a specialization // constant or a non-specialization constant. // // Array allocations need special treatment: as the allocation type will // be 'OpTypePointer(Function, OpTypeArray(ElementType, Length))', we // need to bitcast the obtained pointer to the expected type: // 'OpTypePointer(Function, ElementType). SPIRVType *AllocationType = BM->addPointerType( StorageClassFunction, BM->addArrayType(transType(Alc->getAllocatedType()), Length)); SPIRVValue *Arr = BM->addVariable( AllocationType, nullptr, false, spv::internal::LinkageTypeInternal, nullptr, Alc->getName().str() + "_alloca", StorageClassFunction, BB); // Manually set alignment. OpBitcast created below will be decorated as // that's the SPIR-V value mapped to the original LLVM one. transAlign(Alc, Arr); return mapValue(V, BM->addUnaryInst(OpBitcast, TranslatedTy, Arr, BB)); } if (!BM->checkExtension(ExtensionID::SPV_INTEL_variable_length_array, SPIRVEC_InvalidInstruction, toString(Alc) + "\nTranslation of dynamic alloca requires " "SPV_INTEL_variable_length_array extension.")) return nullptr; return mapValue(V, BM->addInstTemplate(OpVariableLengthArrayINTEL, {Length->getId()}, BB, TranslatedTy)); } SPIRVType *VarTy = TranslatedTy; if (V->getType()->getPointerAddressSpace() == SPIRAS_Generic) { if (TranslatedTy->isTypeUntypedPointerKHR()) VarTy = BM->addPointerType(StorageClassFunction, nullptr); else VarTy = BM->addPointerType(StorageClassFunction, TranslatedTy->getPointerElementType()); } SPIRVValue *Var = BM->addVariable( VarTy, VarTy->isTypeUntypedPointerKHR() ? transType(Alc->getAllocatedType()) : nullptr, false, spv::internal::LinkageTypeInternal, nullptr, Alc->getName().str(), StorageClassFunction, BB); if (V->getType()->getPointerAddressSpace() == SPIRAS_Generic) { SPIRVValue *Cast = BM->addUnaryInst(OpPtrCastToGeneric, TranslatedTy, Var, BB); return mapValue(V, Cast); } return mapValue(V, Var); } if (auto *Switch = dyn_cast(V)) { std::vector Pairs; auto *Select = transValue(Switch->getCondition(), BB); for (auto I = Switch->case_begin(), E = Switch->case_end(); I != E; ++I) { SPIRVSwitch::LiteralTy Lit; uint64_t CaseValue = I->getCaseValue()->getZExtValue(); Lit.push_back(CaseValue); assert(Select->getType()->getBitWidth() <= 64 && "unexpected selector bitwidth"); if (Select->getType()->getBitWidth() == 64) Lit.push_back(CaseValue >> 32); Pairs.push_back( std::make_pair(Lit, static_cast( transValue(I->getCaseSuccessor(), nullptr)))); } return mapValue( V, BM->addSwitchInst(Select, static_cast( transValue(Switch->getDefaultDest(), nullptr)), Pairs, BB)); } if (BranchInst *Branch = dyn_cast(V)) { SPIRVLabel *SuccessorTrue = static_cast(transValue(Branch->getSuccessor(0), BB)); /// Clang attaches !llvm.loop metadata to "latch" BB. This kind of blocks /// has an edge directed to the loop header. Thus latch BB matching to /// "Continue Target" per the SPIR-V spec. This statement is true only after /// applying the loop-simplify pass to the LLVM module. /// For "for" and "while" loops latch BB is terminated by an /// unconditional branch. Also for this kind of loops "Merge Block" can /// be found as block targeted by false edge of the "Header" BB. /// For "do while" loop the latch is terminated by a conditional branch /// with true edge going to the header and the false edge going out of /// the loop, which corresponds to a "Merge Block" per the SPIR-V spec. std::vector Parameters; spv::LoopControlMask LoopControl = getLoopControl(Branch, Parameters); if (Branch->isUnconditional()) { // Usually, "for" and "while" loops llvm.loop metadata is attached to an // unconditional branch instruction. if (LoopControl != spv::LoopControlMaskNone) { // SuccessorTrue is the loop header BB. const SPIRVInstruction *Term = SuccessorTrue->getTerminateInstr(); if (Term && Term->getOpCode() == OpBranchConditional) { const auto *Br = static_cast(Term); BM->addLoopMergeInst(Br->getFalseLabel()->getId(), // Merge Block BB->getId(), // Continue Target LoopControl, Parameters, SuccessorTrue); } else { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_unstructured_loop_controls)) { // For unstructured loop we add a special loop control instruction. // Simple example of unstructured loop is an infinite loop, that has // no terminate instruction. BM->addLoopControlINTELInst(LoopControl, Parameters, SuccessorTrue); } } } return mapValue(V, BM->addBranchInst(SuccessorTrue, BB)); } // For "do-while" (and in some cases, for "for" and "while") loops, // llvm.loop metadata is attached to a conditional branch instructions SPIRVLabel *SuccessorFalse = static_cast(transValue(Branch->getSuccessor(1), BB)); if (LoopControl != spv::LoopControlMaskNone) { Function *Fun = Branch->getFunction(); DominatorTree DomTree(*Fun); LoopInfo LI(DomTree); for (const auto *LoopObj : LI.getLoopsInPreorder()) { // Check whether SuccessorFalse or SuccessorTrue is the loop header BB. // For example consider following LLVM IR: // br i1 %compare, label %for.body, label %for.end // <- SuccessorTrue is 'for.body' aka successor(0) // br i1 %compare.not, label %for.end, label %for.body // <- SuccessorTrue is 'for.end' aka successor(1) // meanwhile the true successor (by definition) should be a loop header // aka 'for.body' if (LoopObj->getHeader() == Branch->getSuccessor(1)) // SuccessorFalse is the loop header BB. BM->addLoopMergeInst(SuccessorTrue->getId(), // Merge Block BB->getId(), // Continue Target LoopControl, Parameters, SuccessorFalse); else // SuccessorTrue is the loop header BB. BM->addLoopMergeInst(SuccessorFalse->getId(), // Merge Block BB->getId(), // Continue Target LoopControl, Parameters, SuccessorTrue); } } return mapValue( V, BM->addBranchConditionalInst(transValue(Branch->getCondition(), BB), SuccessorTrue, SuccessorFalse, BB)); } if (auto *Phi = dyn_cast(V)) { std::vector IncomingPairs; SPIRVType *Ty = transScavengedType(Phi); for (size_t I = 0, E = Phi->getNumIncomingValues(); I != E; ++I) { SPIRVValue *Val = transValue(Phi->getIncomingValue(I), BB, true, FuncTransMode::Pointer); if (Val->getType() != Ty) Val = BM->addUnaryInst(OpBitcast, Ty, Val, BB); IncomingPairs.push_back(Val); IncomingPairs.push_back(transValue(Phi->getIncomingBlock(I), nullptr)); } return mapValue(V, BM->addPhiInst(Ty, IncomingPairs, BB)); } if (auto *Ext = dyn_cast(V)) { if (auto *II = dyn_cast(Ext->getAggregateOperand())) { if (II->getIntrinsicID() == Intrinsic::frexp) { unsigned Idx = Ext->getIndices()[0]; auto *Val = transValue(II, BB); if (Idx == 0) return mapValue(V, Val); // Idx = 1 SPIRVValue *IntFromFrexpResult = static_cast(Val)->getArgValues()[1]; SPIRVType *LoadTy = nullptr; if (IntFromFrexpResult->isUntypedVariable()) LoadTy = static_cast(IntFromFrexpResult) ->getDataType(); IntFromFrexpResult = BM->addLoadInst(IntFromFrexpResult, {}, BB, LoadTy); return mapValue(V, IntFromFrexpResult); } } return mapValue(V, BM->addCompositeExtractInst( transScavengedType(Ext), transValue(Ext->getAggregateOperand(), BB), Ext->getIndices(), BB)); } if (auto *Ins = dyn_cast(V)) { return mapValue(V, BM->addCompositeInsertInst( transValue(Ins->getInsertedValueOperand(), BB), transValue(Ins->getAggregateOperand(), BB), Ins->getIndices(), BB)); } if (UnaryInstruction *U = dyn_cast(V)) { auto *UI = transUnaryInst(U, BB); return mapValue(V, UI ? UI : transValue(U->getOperand(0), BB)); } if (GetElementPtrInst *GEP = dyn_cast(V)) { auto *PointerOperand = GEP->getPointerOperand(); auto *SPVPointerOperand = transValue(PointerOperand, BB); std::vector Ops = {SPVPointerOperand->getId()}; for (unsigned I = 0, E = GEP->getNumIndices(); I != E; ++I) Ops.push_back(transValue(GEP->getOperand(I + 1), BB)->getId()); // Certain array-related optimization hints can be expressed via // LLVM metadata. For the purpose of linking this metadata with // the accessed array variables, our GEP may have been marked into // a so-called index group, an MDNode by itself. if (MDNode *IndexGroup = GEP->getMetadata("llvm.index.group")) { SPIRVValue *ActualMemoryPtr = BM->getValue(Ops[0]); // If the source is a no-op bitcast (generated to fix up types), look // through it to the underlying gep if possible. if (auto *BC = dyn_cast(PointerOperand)) if (BC->getSrcTy() == BC->getDestTy()) { PointerOperand = BC->getOperand(0); } if (auto *Load = dyn_cast(PointerOperand)) { ActualMemoryPtr = transValue(Load->getPointerOperand(), BB); } SPIRVId AccessedArrayId = ActualMemoryPtr->getId(); unsigned NumOperands = IndexGroup->getNumOperands(); // When we're working with embedded loops, it's natural that // the outer loop's hints apply to all code contained within. // The inner loop's specific hints, however, should stay private // to the inner loop's scope. // Consequently, the following division of the index group metadata // nodes emerges: // 1) The metadata node has no operands. It will be directly referenced // from within the optimization hint metadata. if (NumOperands == 0) IndexGroupArrayMap[IndexGroup].insert(AccessedArrayId); // 2) The metadata node has several operands. It serves to link an index // group specific to some embedded loop with other index groups that // mark the same array variable for the outer loop(s). for (unsigned I = 0; I < NumOperands; ++I) { auto *ContainedIndexGroup = getMDOperandAsMDNode(IndexGroup, I); IndexGroupArrayMap[ContainedIndexGroup].insert(AccessedArrayId); } } // GEP can return a vector of pointers, in this case GEP will calculate // addresses for each pointer in the vector SPIRVType *TranslatedTy = transScavengedType(GEP); if (TranslatedTy->isTypeUntypedPointerKHR() || SPVPointerOperand->getType()->isTypeUntypedPointerKHR()) { llvm::Type *Ty = Scavenger->getScavengedType(PointerOperand); SPIRVType *PtrTy = nullptr; if (auto *TPT = dyn_cast(Ty)) { PtrTy = transType(TPT->getElementType()); Ops = getVec(PtrTy->getId(), Ops); } } if (SPVPointerOperand->getType()->isTypeUntypedPointerKHR()) // Untyped pointer as an input operand implies we should use untyped // access chain instructions. Replace return type to do that. TranslatedTy = SPVPointerOperand->getType(); return mapValue(V, BM->addPtrAccessChainInst(TranslatedTy, std::move(Ops), BB, GEP->isInBounds())); } if (auto *Ext = dyn_cast(V)) { auto *Index = Ext->getIndexOperand(); if (auto *Const = dyn_cast(Index)) return mapValue(V, BM->addCompositeExtractInst( transScavengedType(Ext), transValue(Ext->getVectorOperand(), BB), std::vector(1, Const->getZExtValue()), BB)); else return mapValue(V, BM->addVectorExtractDynamicInst( transValue(Ext->getVectorOperand(), BB), transValue(Index, BB), BB)); } if (auto *Ins = dyn_cast(V)) { auto *Index = Ins->getOperand(2); if (auto *Const = dyn_cast(Index)) { return mapValue( V, BM->addCompositeInsertInst( transValue(Ins->getOperand(1), BB, true, FuncTransMode::Pointer), transValue(Ins->getOperand(0), BB), std::vector(1, Const->getZExtValue()), BB)); } else return mapValue( V, BM->addVectorInsertDynamicInst(transValue(Ins->getOperand(0), BB), transValue(Ins->getOperand(1), BB), transValue(Index, BB), BB)); } if (auto *SF = dyn_cast(V)) { std::vector Comp; for (auto &I : SF->getShuffleMask()) Comp.push_back(I); return mapValue(V, BM->addVectorShuffleInst( transScavengedType(SF), transValue(SF->getOperand(0), BB), transValue(SF->getOperand(1), BB), Comp, BB)); } if (AtomicRMWInst *ARMW = dyn_cast(V)) { AtomicRMWInst::BinOp Op = ARMW->getOperation(); bool SupportedAtomicInst = AtomicRMWInst::isFPOperation(Op) ? (Op == AtomicRMWInst::FAdd || Op == AtomicRMWInst::FSub || Op == AtomicRMWInst::FMin || Op == AtomicRMWInst::FMax) : Op != AtomicRMWInst::Nand; if (!BM->getErrorLog().checkError( SupportedAtomicInst, SPIRVEC_InvalidInstruction, V, "Atomic " + AtomicRMWInst::getOperationName(Op).str() + " is not supported in SPIR-V!\n")) return nullptr; AtomicOrderingCABI Ordering = llvm::toCABI(ARMW->getOrdering()); auto MemSem = OCLMemOrderMap::map(static_cast(Ordering)); std::vector Operands(4); Operands[0] = ARMW->getPointerOperand(); spv::Scope S = toSPIRVScope(ARMW->getContext(), ARMW->getSyncScopeID()); Operands[1] = getUInt32(M, S); Operands[2] = getUInt32(M, MemSem); Operands[3] = ARMW->getValOperand(); std::vector OpVals = transValue(Operands, BB); std::vector Ops = BM->getIds(OpVals); SPIRVType *Ty = transScavengedType(ARMW); spv::Op OC; if (Op == AtomicRMWInst::FSub) { // Implement FSub through FNegate and AtomicFAddExt Ops[3] = BM->addUnaryInst(OpFNegate, Ty, OpVals[3], BB)->getId(); OC = OpAtomicFAddEXT; } else OC = LLVMSPIRVAtomicRmwOpCodeMap::map(Op); return mapValue(V, BM->addInstTemplate(OC, Ops, BB, Ty)); } if (IntrinsicInst *II = dyn_cast(V)) { SPIRVValue *BV = transIntrinsicInst(II, BB); return BV ? mapValue(V, BV) : nullptr; } if (FenceInst *FI = dyn_cast(V)) { SPIRVValue *BV = transFenceInst(FI, BB); return BV ? mapValue(V, BV) : nullptr; } if (InlineAsm *IA = dyn_cast(V)) if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_inline_assembly)) return mapValue(V, transAsmINTEL(IA)); if (CallInst *CI = dyn_cast(V)) { if (auto *Alias = dyn_cast_or_null(CI->getCalledOperand())) { CI->setCalledFunction(cast(Alias->getAliasee())); } return mapValue(V, transCallInst(CI, BB)); } if (Instruction *Inst = dyn_cast(V)) { BM->SPIRVCK(false, InvalidInstruction, toString(Inst)); } llvm_unreachable("Not implemented"); return nullptr; } SPIRVType *LLVMToSPIRVBase::mapType(Type *T, SPIRVType *BT) { assert(!T->isPointerTy() && "Pointer types cannot be stored in the type map"); [[maybe_unused]] auto EmplaceStatus = TypeMap.try_emplace(T, BT); assert(EmplaceStatus.second && "The type was already added to the map"); SPIRVDBG(dbgs() << "[mapType] " << *T << " => "; spvdbgs() << *BT << '\n'); return BT; } SPIRVValue *LLVMToSPIRVBase::mapValue(Value *V, SPIRVValue *BV) { auto Loc = ValueMap.find(V); if (Loc != ValueMap.end()) { if (Loc->second == BV) return BV; assert(Loc->second->isForward() && "LLVM Value is mapped to different SPIRV Values"); auto *Forward = static_cast(Loc->second); BM->replaceForward(Forward, BV); } ValueMap[V] = BV; SPIRVDBG(dbgs() << "[mapValue] " << *V << " => "; spvdbgs() << BV << "\n"); return BV; } bool LLVMToSPIRVBase::shouldTryToAddMemAliasingDecoration(Instruction *Inst) { // Limit translation of aliasing metadata with only this set of instructions // gracefully considering others as compilation mistakes and ignoring them if (!Inst->mayReadOrWriteMemory()) return false; // Loads and Stores are handled during memory access mask addition if (isa(Inst) || isa(Inst)) return false; CallInst *CI = dyn_cast(Inst); if (!CI) return true; if (Function *Fun = CI->getCalledFunction()) { // Calls to intrinsics are skipped. At some point lifetime start/end will be // handled separately, but specification isn't ready. if (Fun->isIntrinsic()) return false; // Also skip SPIR-V instructions that don't have result id to attach the // decorations if (isBuiltinTransToInst(Fun)) if (Fun->getReturnType()->isVoidTy()) return false; } return true; } void addFuncPointerCallArgumentAttributes(CallInst *CI, SPIRVValue *FuncPtrCall) { for (unsigned ArgNo = 0; ArgNo < CI->arg_size(); ++ArgNo) { if (CI->doesNotCapture(ArgNo)) FuncPtrCall->addDecorate(new SPIRVDecorate( spv::internal::DecorationArgumentAttributeINTEL, FuncPtrCall, ArgNo, FunctionParameterAttributeNoCapture)); for (const auto &I : CI->getAttributes().getParamAttrs(ArgNo)) { spv::FunctionParameterAttribute Attr = spv::FunctionParameterAttributeMax; SPIRSPIRVFuncParamAttrMap::find(I.getKindAsEnum(), &Attr); if (Attr != spv::FunctionParameterAttributeMax) FuncPtrCall->addDecorate( new SPIRVDecorate(spv::internal::DecorationArgumentAttributeINTEL, FuncPtrCall, ArgNo, Attr)); } } } #define ONE_STRING_DECORATION_CASE(NAME, NAMESPACE) \ case NAMESPACE::Decoration##NAME: { \ ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, \ #NAME " requires exactly 1 extra operand"); \ auto *StrDecoEO = dyn_cast(DecoMD->getOperand(1)); \ ErrLog.checkError(StrDecoEO, SPIRVEC_InvalidLlvmModule, \ #NAME " requires extra operand to be a string"); \ Target->addDecorate( \ new SPIRVDecorate##NAME##Attr(Target, StrDecoEO->getString().str())); \ break; \ } #define ONE_INT_DECORATION_CASE(NAME, NAMESPACE, TYPE) \ case NAMESPACE::Decoration##NAME: { \ ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, \ #NAME " requires exactly 1 extra operand"); \ auto *IntDecoEO = \ mdconst::dyn_extract(DecoMD->getOperand(1)); \ ErrLog.checkError(IntDecoEO, SPIRVEC_InvalidLlvmModule, \ #NAME " requires extra operand to be an integer"); \ Target->addDecorate(new SPIRVDecorate##NAME( \ Target, static_cast(IntDecoEO->getZExtValue()))); \ break; \ } #define TWO_INT_DECORATION_CASE(NAME, NAMESPACE, TYPE1, TYPE2) \ case NAMESPACE::Decoration##NAME: { \ ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, \ #NAME " requires exactly 2 extra operands"); \ auto *IntDecoEO1 = \ mdconst::dyn_extract(DecoMD->getOperand(1)); \ ErrLog.checkError(IntDecoEO1, SPIRVEC_InvalidLlvmModule, \ #NAME " requires first extra operand to be an integer"); \ auto *IntDecoEO2 = \ mdconst::dyn_extract(DecoMD->getOperand(2)); \ ErrLog.checkError(IntDecoEO2, SPIRVEC_InvalidLlvmModule, \ #NAME \ " requires second extra operand to be an integer"); \ Target->addDecorate(new SPIRVDecorate##NAME( \ Target, static_cast(IntDecoEO1->getZExtValue()), \ static_cast(IntDecoEO2->getZExtValue()))); \ break; \ } void checkIsGlobalVar(SPIRVEntry *E, Decoration Dec) { std::string ErrStr = SPIRVDecorationNameMap::map(Dec) + " can only be applied to a variable"; E->getErrorLog().checkError(E->isVariable(), SPIRVEC_InvalidModule, ErrStr); auto AddrSpace = SPIRSPIRVAddrSpaceMap::rmap( static_cast(E)->getStorageClass()); ErrStr += " in a global (module) scope"; E->getErrorLog().checkError(AddrSpace == SPIRAS_Global, SPIRVEC_InvalidModule, ErrStr); } static void transMetadataDecorations(Metadata *MD, SPIRVValue *Target) { SPIRVErrorLog &ErrLog = Target->getErrorLog(); auto *ArgDecoMD = dyn_cast(MD); assert(ArgDecoMD && "Decoration list must be a metadata node"); for (unsigned I = 0, E = ArgDecoMD->getNumOperands(); I != E; ++I) { auto *DecoMD = dyn_cast(ArgDecoMD->getOperand(I)); ErrLog.checkError(DecoMD, SPIRVEC_InvalidLlvmModule, "Decoration does not name metadata"); ErrLog.checkError(DecoMD->getNumOperands() > 0, SPIRVEC_InvalidLlvmModule, "Decoration metadata must have at least one operand"); auto *DecoKindConst = mdconst::dyn_extract(DecoMD->getOperand(0)); ErrLog.checkError(DecoKindConst, SPIRVEC_InvalidLlvmModule, "First operand of decoration must be the kind"); auto DecoKind = static_cast(DecoKindConst->getZExtValue()); const size_t NumOperands = DecoMD->getNumOperands(); switch (static_cast(DecoKind)) { case DecorationAlignment: { // Handle Alignment via SPIRVValue::setAlignment() to avoid duplicate // Alignment decorations. auto *Alignment = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError(Alignment, SPIRVEC_InvalidLlvmModule, "Alignment operand must be an integer."); Target->setAlignment(Alignment->getZExtValue()); break; } ONE_STRING_DECORATION_CASE(MemoryINTEL, spv) ONE_STRING_DECORATION_CASE(UserSemantic, spv) ONE_INT_DECORATION_CASE(AliasScopeINTEL, spv, SPIRVId) ONE_INT_DECORATION_CASE(NoAliasINTEL, spv, SPIRVId) ONE_INT_DECORATION_CASE(InitiationIntervalINTEL, spv, SPIRVWord) ONE_INT_DECORATION_CASE(MaxConcurrencyINTEL, spv, SPIRVWord) ONE_INT_DECORATION_CASE(PipelineEnableINTEL, spv, SPIRVWord) TWO_INT_DECORATION_CASE(FunctionRoundingModeINTEL, spv, SPIRVWord, FPRoundingMode); TWO_INT_DECORATION_CASE(FunctionDenormModeINTEL, spv, SPIRVWord, FPDenormMode); TWO_INT_DECORATION_CASE(FunctionFloatingPointModeINTEL, spv, SPIRVWord, FPOperationMode); TWO_INT_DECORATION_CASE(FuseLoopsInFunctionINTEL, spv, SPIRVWord, SPIRVWord); TWO_INT_DECORATION_CASE(MathOpDSPModeINTEL, spv, SPIRVWord, SPIRVWord); case DecorationConduitKernelArgumentINTEL: case DecorationRegisterMapKernelArgumentINTEL: case DecorationStableKernelArgumentINTEL: case DecorationRestrict: { Target->addDecorate(new SPIRVDecorate(DecoKind, Target)); break; } case DecorationBufferLocationINTEL: case DecorationMMHostInterfaceReadWriteModeINTEL: case DecorationMMHostInterfaceAddressWidthINTEL: case DecorationMMHostInterfaceDataWidthINTEL: case DecorationMMHostInterfaceLatencyINTEL: case DecorationMMHostInterfaceMaxBurstINTEL: case DecorationMMHostInterfaceWaitRequestINTEL: { ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, "MMHost Kernel Argument Annotation requires exactly 2 " "extra operands"); auto *DecoValEO1 = mdconst::dyn_extract(DecoMD->getOperand(1)); Target->addDecorate( new SPIRVDecorate(DecoKind, Target, DecoValEO1->getZExtValue())); break; } case DecorationStallEnableINTEL: { Target->addDecorate(new SPIRVDecorateStallEnableINTEL(Target)); break; } case DecorationStallFreeINTEL: { Target->addDecorate(new SPIRVDecorateStallFreeINTEL(Target)); break; } case DecorationMergeINTEL: { ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, "MergeINTEL requires exactly 3 extra operands"); auto *Name = dyn_cast(DecoMD->getOperand(1)); ErrLog.checkError( Name, SPIRVEC_InvalidLlvmModule, "MergeINTEL requires first extra operand to be a string"); auto *Direction = dyn_cast(DecoMD->getOperand(2)); ErrLog.checkError( Direction, SPIRVEC_InvalidLlvmModule, "MergeINTEL requires second extra operand to be a string"); Target->addDecorate(new SPIRVDecorateMergeINTELAttr( Target, Name->getString().str(), Direction->getString().str())); break; } case DecorationLinkageAttributes: { ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, "LinkageAttributes requires exactly 3 extra operands"); auto *Name = dyn_cast(DecoMD->getOperand(1)); ErrLog.checkError( Name, SPIRVEC_InvalidLlvmModule, "LinkageAttributes requires first extra operand to be a string"); auto *Type = mdconst::dyn_extract(DecoMD->getOperand(2)); ErrLog.checkError( Type, SPIRVEC_InvalidLlvmModule, "LinkageAttributes requires second extra operand to be an int"); auto TypeKind = static_cast(Type->getZExtValue()); Target->addDecorate(new SPIRVDecorateLinkageAttr( Target, Name->getString().str(), TypeKind)); break; } case spv::internal::DecorationHostAccessINTEL: case DecorationHostAccessINTEL: { checkIsGlobalVar(Target, DecoKind); ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, "HostAccessINTEL requires exactly 2 extra operands " "after the decoration kind number"); auto *AccessMode = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError( AccessMode, SPIRVEC_InvalidLlvmModule, "HostAccessINTEL requires first extra operand to be an int"); HostAccessQualifier Q = static_cast(AccessMode->getZExtValue()); auto *Name = dyn_cast(DecoMD->getOperand(2)); ErrLog.checkError( Name, SPIRVEC_InvalidLlvmModule, "HostAccessINTEL requires second extra operand to be a string"); if (DecoKind == DecorationHostAccessINTEL) Target->addDecorate(new SPIRVDecorateHostAccessINTEL( Target, Q, Name->getString().str())); else Target->addDecorate(new SPIRVDecorateHostAccessINTELLegacy( Target, Q, Name->getString().str())); break; } case spv::internal::DecorationInitModeINTEL: case DecorationInitModeINTEL: { checkIsGlobalVar(Target, DecoKind); ErrLog.checkError( static_cast(Target)->getInitializer(), SPIRVEC_InvalidLlvmModule, "InitModeINTEL only be applied to a global (module " "scope) variable which has an Initializer operand"); ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, "InitModeINTEL requires exactly 1 extra operand"); auto *Trigger = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError(Trigger, SPIRVEC_InvalidLlvmModule, "InitModeINTEL requires extra operand to be an int"); InitializationModeQualifier Q = static_cast(Trigger->getZExtValue()); if (DecoKind == DecorationInitModeINTEL) Target->addDecorate(new SPIRVDecorateInitModeINTEL(Target, Q)); else Target->addDecorate(new SPIRVDecorateInitModeINTELLegacy(Target, Q)); break; } case spv::internal::DecorationImplementInCSRINTEL: { checkIsGlobalVar(Target, DecoKind); ErrLog.checkError(NumOperands == 2, SPIRVEC_InvalidLlvmModule, "ImplementInCSRINTEL requires exactly 1 extra operand"); auto *Value = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError( Value, SPIRVEC_InvalidLlvmModule, "ImplementInCSRINTEL requires extra operand to be an integer"); Target->addDecorate( new SPIRVDecorateImplementInCSRINTEL(Target, Value->getZExtValue())); break; } case DecorationImplementInRegisterMapINTEL: { checkIsGlobalVar(Target, DecoKind); ErrLog.checkError( NumOperands == 2, SPIRVEC_InvalidLlvmModule, "ImplementInRegisterMapINTEL requires exactly 1 extra operand"); auto *Value = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError(Value, SPIRVEC_InvalidLlvmModule, "ImplementInRegisterMapINTEL requires extra operand to " "be an integer"); Target->addDecorate(new SPIRVDecorateImplementInRegisterMapINTEL( Target, Value->getZExtValue())); break; } case DecorationCacheControlLoadINTEL: { ErrLog.checkError( NumOperands == 3, SPIRVEC_InvalidLlvmModule, "CacheControlLoadINTEL requires exactly 2 extra operands"); auto *CacheLevel = mdconst::dyn_extract(DecoMD->getOperand(1)); auto *CacheControl = mdconst::dyn_extract(DecoMD->getOperand(2)); ErrLog.checkError(CacheLevel, SPIRVEC_InvalidLlvmModule, "CacheControlLoadINTEL cache level operand is required " "to be an integer"); ErrLog.checkError(CacheControl, SPIRVEC_InvalidLlvmModule, "CacheControlLoadINTEL cache control operand is " "required to be an integer"); Target->addDecorate(new SPIRVDecorateCacheControlLoadINTEL( Target, CacheLevel->getZExtValue(), static_cast(CacheControl->getZExtValue()))); break; } case DecorationCacheControlStoreINTEL: { ErrLog.checkError( NumOperands == 3, SPIRVEC_InvalidLlvmModule, "CacheControlStoreINTEL requires exactly 2 extra operands"); auto *CacheLevel = mdconst::dyn_extract(DecoMD->getOperand(1)); auto *CacheControl = mdconst::dyn_extract(DecoMD->getOperand(2)); ErrLog.checkError(CacheLevel, SPIRVEC_InvalidLlvmModule, "CacheControlStoreINTEL cache level operand is " "required to be an integer"); ErrLog.checkError(CacheControl, SPIRVEC_InvalidLlvmModule, "CacheControlStoreINTEL cache control operand is " "required to be an integer"); Target->addDecorate(new SPIRVDecorateCacheControlStoreINTEL( Target, CacheLevel->getZExtValue(), static_cast(CacheControl->getZExtValue()))); break; } default: { if (NumOperands == 1) { Target->addDecorate(new SPIRVDecorate(DecoKind, Target)); break; } auto *DecoValEO1 = mdconst::dyn_extract(DecoMD->getOperand(1)); ErrLog.checkError( DecoValEO1, SPIRVEC_InvalidLlvmModule, "First extra operand in default decoration case must be integer."); if (NumOperands == 2) { Target->addDecorate( new SPIRVDecorate(DecoKind, Target, DecoValEO1->getZExtValue())); break; } auto *DecoValEO2 = mdconst::dyn_extract(DecoMD->getOperand(2)); ErrLog.checkError( DecoValEO2, SPIRVEC_InvalidLlvmModule, "Second extra operand in default decoration case must be integer."); ErrLog.checkError(NumOperands == 3, SPIRVEC_InvalidLlvmModule, "At most 2 extra operands expected."); Target->addDecorate(new SPIRVDecorate(DecoKind, Target, DecoValEO1->getZExtValue(), DecoValEO2->getZExtValue())); } } } } #undef ONE_STRING_DECORATION_CASE #undef ONE_INT_DECORATION_CASE #undef TWO_INT_DECORATION_CASE bool LLVMToSPIRVBase::transDecoration(Value *V, SPIRVValue *BV) { if (!transAlign(V, BV)) return false; if ((isa(V) && cast(V)->isVolatile()) || (isa(V) && cast(V)->isVolatile())) BV->setVolatile(true); if (auto *BVO = dyn_cast_or_null(V)) { if (BVO->hasNoSignedWrap()) { BV->setNoIntegerDecorationWrap(true); } if (BVO->hasNoUnsignedWrap()) { BV->setNoIntegerDecorationWrap(true); } } if (auto *BVF = dyn_cast_or_null(V)) { auto Opcode = BVF->getOpcode(); if (Opcode == Instruction::FAdd || Opcode == Instruction::FSub || Opcode == Instruction::FMul || Opcode == Instruction::FDiv || Opcode == Instruction::FRem || ((Opcode == Instruction::FNeg || Opcode == Instruction::FCmp || BV->isExtInst()) && BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6))) { FastMathFlags FMF = BVF->getFastMathFlags(); SPIRVWord M{0}; if (FMF.isFast()) M |= FPFastMathModeFastMask; else { if (FMF.noNaNs()) M |= FPFastMathModeNotNaNMask; if (FMF.noInfs()) M |= FPFastMathModeNotInfMask; if (FMF.noSignedZeros()) M |= FPFastMathModeNSZMask; if (FMF.allowReciprocal()) M |= FPFastMathModeAllowRecipMask; if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fp_fast_math_mode)) { if (FMF.allowContract()) { M |= FPFastMathModeAllowContractFastINTELMask; BM->addCapability(CapabilityFPFastMathModeINTEL); BM->addExtension(ExtensionID::SPV_INTEL_fp_fast_math_mode); } if (FMF.allowReassoc()) { M |= FPFastMathModeAllowReassocINTELMask; BM->addCapability(CapabilityFPFastMathModeINTEL); BM->addExtension(ExtensionID::SPV_INTEL_fp_fast_math_mode); } } } // Handle nofpclass attribute. Nothing to do if fast math flag is already // set. if ((BV->isExtInst() && static_cast(BV)->getExtSetKind() == SPIRVEIS_OpenCL) && BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_6) && !(M & FPFastMathModeFastMask)) { auto *F = cast(V)->getCalledFunction(); auto FAttrs = F->getAttributes(); AttributeSet RetAttrs = FAttrs.getRetAttrs(); if (RetAttrs.hasAttribute(Attribute::NoFPClass)) { FPClassTest RetTest = RetAttrs.getAttribute(Attribute::NoFPClass).getNoFPClass(); // Only Nan and Inf tests are representable in SPIR-V now. bool ToAddNoNan = RetTest & fcNan; bool ToAddNoInf = RetTest & fcInf; if (ToAddNoNan || ToAddNoInf) { const auto *FT = F->getFunctionType(); const size_t NumParams = FT->getNumParams(); for (size_t I = 0; I != NumParams; ++I) { if (!FT->getParamType(I)->isFloatTy()) continue; if (!F->hasParamAttribute(I, Attribute::NoFPClass)) { ToAddNoNan = false; ToAddNoInf = false; break; } FPClassTest ArgTest = FAttrs.getParamAttr(I, Attribute::NoFPClass).getNoFPClass(); ToAddNoNan = ToAddNoNan && static_cast(ArgTest & fcNan); ToAddNoInf = ToAddNoInf && static_cast(ArgTest & fcInf); } } if (ToAddNoNan) M |= FPFastMathModeNotNaNMask; if (ToAddNoInf) M |= FPFastMathModeNotInfMask; } } if (M != 0) { BV->setFPFastMathMode(M); if (Opcode == Instruction::FNeg || Opcode == Instruction::FCmp || BV->isExtInst()) BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_6); } } } if (Instruction *Inst = dyn_cast(V)) { if (shouldTryToAddMemAliasingDecoration(Inst)) transMemAliasingINTELDecorations(Inst, BV); if (auto *IDecoMD = Inst->getMetadata(SPIRV_MD_DECORATIONS)) transMetadataDecorations(IDecoMD, BV); if (BV->isInst()) addFPBuiltinDecoration(BM, Inst, static_cast(BV)); } if (auto *CI = dyn_cast(V)) { auto OC = BV->getOpCode(); if (OC == OpSpecConstantTrue || OC == OpSpecConstantFalse || OC == OpSpecConstant) { auto SpecId = cast(CI->getArgOperand(0))->getZExtValue(); BV->addDecorate(DecorationSpecId, SpecId); } if (OC == OpFunctionPointerCallINTEL) addFuncPointerCallArgumentAttributes(CI, BV); } if (auto *GV = dyn_cast(V)) if (auto *GVDecoMD = GV->getMetadata(SPIRV_MD_DECORATIONS)) transMetadataDecorations(GVDecoMD, BV); return true; } bool LLVMToSPIRVBase::transAlign(Value *V, SPIRVValue *BV) { if (auto *AL = dyn_cast(V)) { BM->setAlignment(BV, AL->getAlign().value()); return true; } if (auto *GV = dyn_cast(V)) { BM->setAlignment(BV, GV->getAlignment()); return true; } return true; } // Apply aliasing decorations to instructions annotated with aliasing metadata. // Do it for any instruction but loads and stores. void LLVMToSPIRVBase::transMemAliasingINTELDecorations(Instruction *Inst, SPIRVValue *BV) { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_memory_access_aliasing)) return; if (MDNode *AliasingListMD = Inst->getMetadata(LLVMContext::MD_alias_scope)) { auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD); if (!MemAliasList) return; BV->addDecorate(new SPIRVDecorateId(DecorationAliasScopeINTEL, BV, MemAliasList->getId())); } if (MDNode *AliasingListMD = Inst->getMetadata(LLVMContext::MD_noalias)) { auto *MemAliasList = addMemAliasingINTELInstructions(BM, AliasingListMD); if (!MemAliasList) return; BV->addDecorate( new SPIRVDecorateId(DecorationNoAliasINTEL, BV, MemAliasList->getId())); } } /// Do this after source language is set. bool LLVMToSPIRVBase::transBuiltinSet() { SPIRVId EISId; if (!BM->importBuiltinSet("OpenCL.std", &EISId)) return false; if (SPIRVMDWalker(*M).getNamedMD("llvm.dbg.cu")) { if (!BM->importBuiltinSet( SPIRVBuiltinSetNameMap::map(BM->getDebugInfoEIS()), &EISId)) return false; } if (BM->preserveAuxData()) { if (!BM->importBuiltinSet( SPIRVBuiltinSetNameMap::map(SPIRVEIS_NonSemantic_AuxData), &EISId)) return false; } return true; } /// Translate sampler* spcv.cast(i32 arg) or /// sampler* __translate_sampler_initializer(i32 arg) /// Three cases are possible: /// arg = ConstantInt x -> SPIRVConstantSampler /// arg = i32 argument -> transValue(arg) /// arg = load from sampler -> look through load SPIRVValue *LLVMToSPIRVBase::oclTransSpvcCastSampler(CallInst *CI, SPIRVBasicBlock *BB) { assert(CI->getCalledFunction() && "Unexpected indirect call"); auto *Arg = CI->getArgOperand(0); auto *TransRT = transType(getSPIRVType(OpTypeSampler)); auto GetSamplerConstant = [&](uint64_t SamplerValue) { auto AddrMode = (SamplerValue & 0xE) >> 1; auto Param = SamplerValue & 0x1; auto Filter = SamplerValue ? ((SamplerValue & 0x30) >> 4) - 1 : 0; auto *BV = BM->addSamplerConstant(TransRT, AddrMode, Param, Filter); return BV; }; if (auto *Const = dyn_cast(Arg)) { // Sampler is declared as a kernel scope constant return GetSamplerConstant(Const->getZExtValue()); } else if (auto *Load = dyn_cast(Arg)) { // If value of the sampler is loaded from a global constant, use its // initializer for initialization of the sampler. auto *Op = Load->getPointerOperand(); assert(isa(Op) && "Unknown sampler pattern!"); auto *GV = cast(Op); assert(GV->isConstant() || GV->getType()->getPointerAddressSpace() == SPIRAS_Constant); auto *Initializer = GV->getInitializer(); assert(isa(Initializer) && "sampler not constant int?"); return GetSamplerConstant(cast(Initializer)->getZExtValue()); } // Sampler is a function argument auto *BV = transValue(Arg, BB); assert(BV && BV->getType() == TransRT); return BV; } using DecorationsInfoVec = std::vector>>; struct AnnotationDecorations { DecorationsInfoVec MemoryAttributesVec; DecorationsInfoVec MemoryAccessesVec; DecorationsInfoVec BufferLocationVec; DecorationsInfoVec LatencyControlVec; DecorationsInfoVec CacheControlVec; bool empty() { return (MemoryAttributesVec.empty() && MemoryAccessesVec.empty() && BufferLocationVec.empty() && LatencyControlVec.empty() && CacheControlVec.empty()); } }; struct IntelLSUControlsInfo { void setWithBitMask(unsigned ParamsBitMask) { if (ParamsBitMask & IntelFPGAMemoryAccessesVal::BurstCoalesce) BurstCoalesce = true; if (ParamsBitMask & IntelFPGAMemoryAccessesVal::CacheSizeFlag) CacheSizeInfo = 0; if (ParamsBitMask & IntelFPGAMemoryAccessesVal::DontStaticallyCoalesce) DontStaticallyCoalesce = true; if (ParamsBitMask & IntelFPGAMemoryAccessesVal::PrefetchFlag) PrefetchInfo = 0; } DecorationsInfoVec getDecorationsFromCurrentState() { DecorationsInfoVec ResultVec; // Simple flags if (BurstCoalesce) ResultVec.emplace_back(DecorationBurstCoalesceINTEL, std::vector()); if (DontStaticallyCoalesce) ResultVec.emplace_back(DecorationDontStaticallyCoalesceINTEL, std::vector()); // Conditional values if (CacheSizeInfo.has_value()) { ResultVec.emplace_back( DecorationCacheSizeINTEL, std::vector{std::to_string(CacheSizeInfo.value())}); } if (PrefetchInfo.has_value()) { ResultVec.emplace_back( DecorationPrefetchINTEL, std::vector{std::to_string(PrefetchInfo.value())}); } return ResultVec; } bool BurstCoalesce = false; std::optional CacheSizeInfo; bool DontStaticallyCoalesce = false; std::optional PrefetchInfo; }; // Handle optional var/ptr/global annotation parameter. It can be for example // { %struct.S, i8*, void ()* } { %struct.S undef, i8* null, // void ()* @_Z4blahv } // Now we will just handle integer constants (wrapped in a constant // struct, that is being bitcasted to i8*), converting them to string. // TODO: remove this workaround when/if an extension spec that allows or adds // variadic-arguments UserSemantic decoration void processOptionalAnnotationInfo(Constant *Const, std::string &AnnotationString) { if (!Const->getNumOperands()) return; if (auto *CStruct = dyn_cast(Const->getOperand(0))) { uint32_t NumOperands = CStruct->getNumOperands(); if (!NumOperands) return; if (auto *CInt = dyn_cast(CStruct->getOperand(0))) { AnnotationString += ": "; // For boolean, emit 0/1 for ease of readability. if (CInt->getType()->getIntegerBitWidth() == 1) AnnotationString += std::to_string(CInt->getZExtValue()); else AnnotationString += std::to_string(CInt->getSExtValue()); } for (uint32_t I = 1; I != NumOperands; ++I) { if (auto *CInt = dyn_cast(CStruct->getOperand(I))) { AnnotationString += ", "; AnnotationString += std::to_string(CInt->getSExtValue()); } } } else if (auto *ZeroStruct = dyn_cast(Const->getOperand(0))) { // It covers case when all elements of struct are 0 and they become // zeroinitializer. It represents like: { i32 i32 ... } zeroinitializer uint32_t NumOperands = ZeroStruct->getType()->getStructNumElements(); AnnotationString += ": "; AnnotationString += "0"; for (uint32_t I = 1; I != NumOperands; ++I) { AnnotationString += ", "; AnnotationString += "0"; } } } // Process main var/ptr/global annotation string with the attached optional // integer parameters void processAnnotationString(IntrinsicInst *II, std::string &AnnotationString) { auto *StrVal = II->getArgOperand(1); auto *StrValTy = StrVal->getType(); if (StrValTy->isPointerTy()) { StringRef StrRef; if (getConstantStringInfo(dyn_cast(StrVal), StrRef)) AnnotationString += StrRef.str(); if (auto *C = dyn_cast_or_null(II->getArgOperand(4))) processOptionalAnnotationInfo(C, AnnotationString); return; } if (auto *GEP = dyn_cast(StrVal)) { if (auto *C = dyn_cast(GEP->getOperand(0))) { StringRef StrRef; if (getConstantStringInfo(C, StrRef)) AnnotationString += StrRef.str(); } } if (auto *Cast = dyn_cast(II->getArgOperand(4))) if (auto *C = dyn_cast_or_null(Cast->getOperand(0))) processOptionalAnnotationInfo(C, AnnotationString); } // Try to parse the annotation decoration values in a string. These values must // be separated by a "," and must be either a word (including numbers) or a // quotation mark enclosed string. static bool tryParseAnnotationDecoValues(StringRef ValueStr, std::vector &ParsedArgs) { unsigned ValueStart = 0; bool IsParsingStringLiteral = false; for (unsigned I = 0; I < ValueStr.size(); ++I) { const char CurrentC = ValueStr[I]; if (IsParsingStringLiteral) { if (CurrentC == '"') { // We have reached the end of a string literal and have the arg string // between this character and the start of the string literal. IsParsingStringLiteral = false; ParsedArgs.push_back(ValueStr.substr(ValueStart, I - ValueStart).str()); // End of a string literal must either be at the end of the values or // right before a comma. if (I + 1 != ValueStr.size() && ValueStr[I + 1] != ',') return false; // Skip the , delimiter and go directly to the start of next value. ValueStart = (++I) + 1; continue; } } if (CurrentC == ',') { // Since we are not currently in a string literal, comma denotes a // separation of decoration arguments and we can copy the substring we are // currently parsing. ParsedArgs.push_back(ValueStr.substr(ValueStart, I - ValueStart).str()); ValueStart = I + 1; continue; } if (CurrentC == '"') { // We are entering a string literal. This must be either at the beginning // of the values or right after a comma. if (I != 0 && ValueStr[I - 1] != ',') return false; IsParsingStringLiteral = true; ValueStart = I + 1; continue; } // Any other character will be consumed as part of the argument. } // If we were still parsing a decoration argument when reaching the end of the // parsed string, we must be at the end of the argument. if (ValueStart < ValueStr.size()) ParsedArgs.push_back( ValueStr.substr(ValueStart, ValueStr.size() - ValueStart).str()); // At the end, the arguments parsed are valid if we were not parsing a string // literal with no end. return !IsParsingStringLiteral; } AnnotationDecorations tryParseAnnotationString(SPIRVModule *BM, StringRef AnnotatedCode) { AnnotationDecorations Decorates; // Annotation string decorations are separated into {word} OR // {word:value,value,...} blocks, where value is either a word (including // numbers) or a quotation mark enclosed string. std::regex DecorationRegex("\\{\\w([\\w:,-]|\"[^\"]*\")*\\}"); using RegexIterT = std::regex_iterator; RegexIterT DecorationsIt(AnnotatedCode.begin(), AnnotatedCode.end(), DecorationRegex); RegexIterT DecorationsEnd; // If we didn't find any annotations that are separated as described above, // then add a UserSemantic decoration if (DecorationsIt == DecorationsEnd) { Decorates.MemoryAttributesVec.emplace_back( DecorationUserSemantic, std::vector{AnnotatedCode.str()}); return Decorates; } const bool AllowFPGAMemAccesses = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_memory_accesses); const bool AllowFPGAMemAttr = BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes); const bool AllowFPGABufLoc = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_buffer_location); const bool AllowFPGALatencyControl = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_latency_control); const bool AllowCacheControls = BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_cache_controls); bool ValidDecorationFound = false; DecorationsInfoVec DecorationsVec; IntelLSUControlsInfo LSUControls; for (; DecorationsIt != DecorationsEnd; ++DecorationsIt) { // Drop the braces surrounding the actual decoration const StringRef AnnotatedDecoration = AnnotatedCode.substr( DecorationsIt->position() + 1, DecorationsIt->length() - 2); std::pair Split = AnnotatedDecoration.split(':'); StringRef Name = Split.first, ValueStr = Split.second; SPIRVDBG(spvdbgs() << "[tryParseAnnotationString]: AnnotationString: " << Name.str() << "\n"); unsigned DecorationKind = 0; if (!Name.getAsInteger(10, DecorationKind)) { // If the name is a number it represents the decoration by its kind. std::vector DecValues; if (tryParseAnnotationDecoValues(ValueStr, DecValues)) { ValidDecorationFound = true; if (AllowFPGABufLoc && DecorationKind == DecorationBufferLocationINTEL) { Decorates.BufferLocationVec.emplace_back( static_cast(DecorationKind), std::move(DecValues)); } else if (AllowFPGALatencyControl && (DecorationKind == DecorationLatencyControlLabelINTEL || DecorationKind == DecorationLatencyControlConstraintINTEL)) { Decorates.LatencyControlVec.emplace_back( static_cast(DecorationKind), std::move(DecValues)); } else if (AllowCacheControls && DecorationKind == DecorationCacheControlLoadINTEL) { Decorates.CacheControlVec.emplace_back( static_cast(DecorationKind), std::move(DecValues)); } else if (DecorationKind == DecorationMemoryINTEL) { // SPIRV doesn't allow the same Decoration to be applied multiple // times on a single SPIRVEntry, unless explicitly allowed by the // language spec. Filter out the less specific MemoryINTEL // decorations, if applied multiple times auto CanFilterOut = [](auto &Val) { if (!Val.second.empty()) return (Val.second[0] == "DEFAULT"); return false; }; auto It = std::find_if(DecorationsVec.begin(), DecorationsVec.end(), CanFilterOut); if (It != DecorationsVec.end()) { // replace the less specific decoration *It = {static_cast(DecorationKind), std::move(DecValues)}; } else { // add new decoration DecorationsVec.emplace_back(static_cast(DecorationKind), std::move(DecValues)); } } else { DecorationsVec.emplace_back(static_cast(DecorationKind), std::move(DecValues)); } } continue; } if (AllowFPGAMemAccesses) { if (Name == "params") { ValidDecorationFound = true; unsigned ParamsBitMask = 0; bool Failure = ValueStr.getAsInteger(10, ParamsBitMask); assert(!Failure && "Non-integer LSU controls value"); (void)Failure; LSUControls.setWithBitMask(ParamsBitMask); } else if (Name == "cache-size") { ValidDecorationFound = true; if (!LSUControls.CacheSizeInfo.has_value()) continue; unsigned CacheSizeValue = 0; bool Failure = ValueStr.getAsInteger(10, CacheSizeValue); assert(!Failure && "Non-integer cache size value"); (void)Failure; LSUControls.CacheSizeInfo = CacheSizeValue; } // TODO: Support LSU prefetch size, which currently defaults to 0 } if (AllowFPGAMemAttr) { std::vector DecValues; Decoration Dec; if (Name == "pump") { ValidDecorationFound = true; Dec = llvm::StringSwitch(ValueStr) .Case("1", DecorationSinglepumpINTEL) .Case("2", DecorationDoublepumpINTEL); } else if (Name == "register") { ValidDecorationFound = true; Dec = DecorationRegisterINTEL; } else if (Name == "simple_dual_port") { ValidDecorationFound = true; Dec = DecorationSimpleDualPortINTEL; } else { Dec = llvm::StringSwitch(Name) .Case("memory", DecorationMemoryINTEL) .Case("numbanks", DecorationNumbanksINTEL) .Case("bankwidth", DecorationBankwidthINTEL) .Case("private_copies", DecorationMaxPrivateCopiesINTEL) .Case("max_replicates", DecorationMaxReplicatesINTEL) .Case("bank_bits", DecorationBankBitsINTEL) .Case("merge", DecorationMergeINTEL) .Case("force_pow2_depth", DecorationForcePow2DepthINTEL) .Case("stride_size", DecorationStridesizeINTEL) .Case("word_size", DecorationWordsizeINTEL) .Case("true_dual_port", DecorationTrueDualPortINTEL) .Default(DecorationUserSemantic); if (Dec == DecorationUserSemantic) // Restore the braces to translate the whole input string DecValues = std::vector({'{' + AnnotatedDecoration.str() + '}'}); else if (Dec == DecorationMergeINTEL) { ValidDecorationFound = true; std::pair MergeValues = ValueStr.split(':'); DecValues = std::vector( {MergeValues.first.str(), MergeValues.second.str()}); } else if (Dec == DecorationBankBitsINTEL) { ValidDecorationFound = true; SmallVector BitsStrs; ValueStr.split(BitsStrs, ','); DecValues.reserve(BitsStrs.size()); for (const StringRef &BitsStr : BitsStrs) DecValues.push_back(BitsStr.str()); } else { ValidDecorationFound = true; DecValues = std::vector({ValueStr.str()}); } } DecorationsVec.emplace_back(Dec, std::move(DecValues)); } } // Even if there is an annotation string that is split in blocks like Intel // FPGA annotation, it's not necessarily an FPGA annotation. Translate the // whole string as UserSemantic decoration in this case. if (ValidDecorationFound) Decorates.MemoryAttributesVec = DecorationsVec; else Decorates.MemoryAttributesVec.emplace_back( DecorationUserSemantic, std::vector({AnnotatedCode.str()})); Decorates.MemoryAccessesVec = LSUControls.getDecorationsFromCurrentState(); return Decorates; } std::vector getLiteralsFromStrings(const std::vector &Strings) { std::vector Literals(Strings.size()); for (size_t J = 0; J < Strings.size(); ++J) if (StringRef(Strings[J]).getAsInteger(10, Literals[J])) return {}; return Literals; } void addAnnotationDecorations(SPIRVEntry *E, DecorationsInfoVec &Decorations) { SPIRVModule *M = E->getModule(); for (const auto &I : Decorations) { // Such decoration already exists on a type, try to skip it if (E->hasDecorate(I.first, /*Index=*/0, /*Result=*/nullptr)) // Allow multiple UserSemantic Decorations if (I.first != DecorationUserSemantic) continue; switch (static_cast(I.first)) { case DecorationUserSemantic: M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "UserSemantic requires a single argument."); E->addDecorate(new SPIRVDecorateUserSemanticAttr(E, I.second[0])); break; case DecorationMemoryINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes)) { M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "MemoryINTEL requires a single argument."); E->addDecorate(new SPIRVDecorateMemoryINTELAttr(E, I.second[0])); } } break; case DecorationMergeINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes)) { M->getErrorLog().checkError(I.second.size() == 2, SPIRVEC_InvalidLlvmModule, "MergeINTEL requires two arguments."); // First argument is the name and the second argument is the direction. E->addDecorate( new SPIRVDecorateMergeINTELAttr(E, I.second[0], I.second[1])); } } break; case DecorationBankBitsINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes)) { M->getErrorLog().checkError( I.second.size() > 0, SPIRVEC_InvalidLlvmModule, "BankBitsINTEL requires at least one argument."); E->addDecorate(new SPIRVDecorateBankBitsINTELAttr( E, getLiteralsFromStrings(I.second))); } } break; case DecorationRegisterINTEL: case DecorationSinglepumpINTEL: case DecorationDoublepumpINTEL: case DecorationSimpleDualPortINTEL: case DecorationTrueDualPortINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes)) { M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule, "Decoration takes no arguments."); E->addDecorate(I.first); } } break; case DecorationBurstCoalesceINTEL: case DecorationDontStaticallyCoalesceINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_accesses)) { M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule, "Decoration takes no arguments."); E->addDecorate(I.first); } } break; case DecorationNumbanksINTEL: case DecorationBankwidthINTEL: case DecorationMaxPrivateCopiesINTEL: case DecorationMaxReplicatesINTEL: case DecorationForcePow2DepthINTEL: case DecorationStridesizeINTEL: case DecorationWordsizeINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_attributes)) { M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "Decoration requires a single argument."); SPIRVWord Result = 0; StringRef(I.second[0]).getAsInteger(10, Result); E->addDecorate(I.first, Result); } } break; case DecorationCacheSizeINTEL: case DecorationPrefetchINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_memory_accesses)) { M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "Decoration requires a single argument."); SPIRVWord Result = 0; StringRef(I.second[0]).getAsInteger(10, Result); E->addDecorate(I.first, Result); } } break; case DecorationBufferLocationINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_buffer_location)) { M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "Decoration requires a single argument."); SPIRVWord Result = 0; StringRef(I.second[0]).getAsInteger(10, Result); E->addDecorate(I.first, Result); } } break; case DecorationLatencyControlLabelINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_latency_control)) { M->getErrorLog().checkError( I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "LatencyControlLabelINTEL requires exactly 1 extra operand"); SPIRVWord Label = 0; StringRef(I.second[0]).getAsInteger(10, Label); E->addDecorate( new SPIRVDecorate(DecorationLatencyControlLabelINTEL, E, Label)); } break; } case DecorationLatencyControlConstraintINTEL: { if (M->isAllowedToUseExtension( ExtensionID::SPV_INTEL_fpga_latency_control)) { M->getErrorLog().checkError( I.second.size() == 3, SPIRVEC_InvalidLlvmModule, "LatencyControlConstraintINTEL requires exactly 3 extra operands"); auto Literals = getLiteralsFromStrings(I.second); E->addDecorate( new SPIRVDecorate(DecorationLatencyControlConstraintINTEL, E, Literals[0], Literals[1], Literals[2])); } break; } case DecorationCacheControlLoadINTEL: { if (M->isAllowedToUseExtension(ExtensionID::SPV_INTEL_cache_controls)) { M->getErrorLog().checkError( I.second.size() == 2, SPIRVEC_InvalidLlvmModule, "CacheControlLoadINTEL requires exactly 2 extra operands"); SPIRVWord CacheLevel = 0; SPIRVWord CacheControl = 0; StringRef(I.second[0]).getAsInteger(10, CacheLevel); StringRef(I.second[1]).getAsInteger(10, CacheControl); E->addDecorate(new SPIRVDecorateCacheControlLoadINTEL( E, CacheLevel, static_cast(CacheControl))); } break; } default: // Other decorations are either not supported by the translator or // handled in other places. break; } } } void addAnnotationDecorationsForStructMember(SPIRVEntry *E, SPIRVWord MemberNumber, DecorationsInfoVec &Decorations) { SPIRVModule *M = E->getModule(); for (const auto &I : Decorations) { // Such decoration already exists on a type, skip it if (E->hasMemberDecorate(I.first, /*Index=*/0, MemberNumber, /*Result=*/nullptr)) // Allow multiple UserSemantic Decorations if (I.first != DecorationUserSemantic) continue; switch (I.first) { case DecorationUserSemantic: M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "UserSemantic requires a single argument."); E->addMemberDecorate(new SPIRVMemberDecorateUserSemanticAttr( E, MemberNumber, I.second[0])); break; case DecorationMemoryINTEL: M->getErrorLog().checkError(I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "MemoryINTEL requires a single argument."); E->addMemberDecorate( new SPIRVMemberDecorateMemoryINTELAttr(E, MemberNumber, I.second[0])); break; case DecorationMergeINTEL: { M->getErrorLog().checkError(I.second.size() == 2, SPIRVEC_InvalidLlvmModule, "MergeINTEL requires two arguments."); // First argument is the name, the other is the direction. E->addMemberDecorate(new SPIRVMemberDecorateMergeINTELAttr( E, MemberNumber, I.second[0], I.second[1])); } break; case DecorationBankBitsINTEL: M->getErrorLog().checkError( I.second.size() > 0, SPIRVEC_InvalidLlvmModule, "BankBitsINTEL requires at least one argument."); E->addMemberDecorate(new SPIRVMemberDecorateBankBitsINTELAttr( E, MemberNumber, getLiteralsFromStrings(I.second))); break; case DecorationRegisterINTEL: case DecorationSinglepumpINTEL: case DecorationDoublepumpINTEL: case DecorationSimpleDualPortINTEL: case DecorationTrueDualPortINTEL: M->getErrorLog().checkError(I.second.empty(), SPIRVEC_InvalidLlvmModule, "Member decoration takes no arguments."); E->addMemberDecorate(MemberNumber, I.first); break; // The rest of IntelFPGA decorations: // DecorationNumbanksINTEL // DecorationBankwidthINTEL // DecorationMaxPrivateCopiesINTEL // DecorationMaxReplicatesINTEL // DecorationForcePow2DepthINTEL // DecorarionStridesizeINTEL // DecorationWordsizeINTEL default: M->getErrorLog().checkError( I.second.size() == 1, SPIRVEC_InvalidLlvmModule, "Member decoration requires a single argument."); SPIRVWord Result = 0; StringRef(I.second[0]).getAsInteger(10, Result); E->addMemberDecorate(MemberNumber, I.first, Result); break; } } } bool LLVMToSPIRVBase::isKnownIntrinsic(Intrinsic::ID Id) { // Known intrinsics usually do not need translation of their declaration switch (Id) { case Intrinsic::abs: case Intrinsic::assume: case Intrinsic::bitreverse: case Intrinsic::ceil: case Intrinsic::copysign: case Intrinsic::cos: case Intrinsic::exp: case Intrinsic::exp2: case Intrinsic::fabs: case Intrinsic::floor: case Intrinsic::fma: case Intrinsic::frexp: case Intrinsic::log: case Intrinsic::log10: case Intrinsic::log2: case Intrinsic::maximum: case Intrinsic::maxnum: case Intrinsic::smax: case Intrinsic::umax: case Intrinsic::minimum: case Intrinsic::minnum: case Intrinsic::smin: case Intrinsic::umin: case Intrinsic::scmp: case Intrinsic::ucmp: case Intrinsic::nearbyint: case Intrinsic::pow: case Intrinsic::powi: case Intrinsic::rint: case Intrinsic::round: case Intrinsic::roundeven: case Intrinsic::sin: case Intrinsic::sqrt: case Intrinsic::tan: case Intrinsic::trunc: case Intrinsic::ctpop: case Intrinsic::ctlz: case Intrinsic::cttz: case Intrinsic::expect: case Intrinsic::experimental_noalias_scope_decl: case Intrinsic::experimental_constrained_fadd: case Intrinsic::experimental_constrained_fsub: case Intrinsic::experimental_constrained_fmul: case Intrinsic::experimental_constrained_fdiv: case Intrinsic::experimental_constrained_sqrt: case Intrinsic::experimental_constrained_frem: case Intrinsic::experimental_constrained_fma: case Intrinsic::experimental_constrained_fptoui: case Intrinsic::experimental_constrained_fptosi: case Intrinsic::experimental_constrained_uitofp: case Intrinsic::experimental_constrained_sitofp: case Intrinsic::experimental_constrained_fptrunc: case Intrinsic::experimental_constrained_fpext: case Intrinsic::experimental_constrained_fcmp: case Intrinsic::experimental_constrained_fcmps: case Intrinsic::experimental_constrained_fmuladd: case Intrinsic::fmuladd: case Intrinsic::memset: case Intrinsic::memcpy: case Intrinsic::lifetime_start: case Intrinsic::lifetime_end: case Intrinsic::dbg_declare: case Intrinsic::dbg_value: case Intrinsic::annotation: case Intrinsic::var_annotation: case Intrinsic::ptr_annotation: case Intrinsic::invariant_start: case Intrinsic::invariant_end: case Intrinsic::dbg_label: case Intrinsic::trap: case Intrinsic::ubsantrap: case Intrinsic::debugtrap: case Intrinsic::uadd_with_overflow: case Intrinsic::usub_with_overflow: case Intrinsic::arithmetic_fence: case Intrinsic::masked_gather: case Intrinsic::masked_scatter: case Intrinsic::modf: case Intrinsic::fake_use: return true; default: // Unknown intrinsics' declarations should always be translated return false; } } // Performs mapping of LLVM IR rounding mode to SPIR-V rounding mode // Value *V is metadata argument of // llvm.experimental.constrained.* intrinsics SPIRVInstruction * LLVMToSPIRVBase::applyRoundingModeConstraint(Value *V, SPIRVInstruction *I) { StringRef RMode = cast(cast(V)->getMetadata())->getString(); if (RMode.ends_with("tonearest")) I->addFPRoundingMode(FPRoundingModeRTE); else if (RMode.ends_with("towardzero")) I->addFPRoundingMode(FPRoundingModeRTZ); else if (RMode.ends_with("upward")) I->addFPRoundingMode(FPRoundingModeRTP); else if (RMode.ends_with("downward")) I->addFPRoundingMode(FPRoundingModeRTN); return I; } static SPIRVWord getBuiltinIdForIntrinsic(Intrinsic::ID IID) { switch (IID) { // Note: In some cases the semantics of the OpenCL builtin are not identical // to the semantics of the corresponding LLVM IR intrinsic. The LLVM // intrinsics handled here assume the default floating point environment // (no unmasked exceptions, round-to-nearest-ties-even rounding mode) // and assume that the operations have no side effects (FP status flags // aren't maintained), so the OpenCL builtin behavior should be // acceptable. case Intrinsic::acos: return OpenCLLIB::Acos; case Intrinsic::asin: return OpenCLLIB::Asin; case Intrinsic::atan: return OpenCLLIB::Atan; case Intrinsic::atan2: return OpenCLLIB::Atan2; case Intrinsic::ceil: return OpenCLLIB::Ceil; case Intrinsic::copysign: return OpenCLLIB::Copysign; case Intrinsic::cos: return OpenCLLIB::Cos; case Intrinsic::cosh: return OpenCLLIB::Cosh; case Intrinsic::exp: return OpenCLLIB::Exp; case Intrinsic::exp2: return OpenCLLIB::Exp2; case Intrinsic::fabs: return OpenCLLIB::Fabs; case Intrinsic::floor: return OpenCLLIB::Floor; case Intrinsic::fma: return OpenCLLIB::Fma; case Intrinsic::frexp: return OpenCLLIB::Frexp; case Intrinsic::log: return OpenCLLIB::Log; case Intrinsic::log10: return OpenCLLIB::Log10; case Intrinsic::log2: return OpenCLLIB::Log2; case Intrinsic::maximum: return OpenCLLIB::Fmax; case Intrinsic::maxnum: return OpenCLLIB::Fmax; case Intrinsic::minimum: return OpenCLLIB::Fmin; case Intrinsic::minnum: return OpenCLLIB::Fmin; case Intrinsic::nearbyint: return OpenCLLIB::Rint; case Intrinsic::pow: return OpenCLLIB::Pow; case Intrinsic::powi: return OpenCLLIB::Pown; case Intrinsic::rint: return OpenCLLIB::Rint; case Intrinsic::round: return OpenCLLIB::Round; case Intrinsic::roundeven: return OpenCLLIB::Rint; case Intrinsic::sin: return OpenCLLIB::Sin; case Intrinsic::sinh: return OpenCLLIB::Sinh; case Intrinsic::sqrt: return OpenCLLIB::Sqrt; case Intrinsic::tan: return OpenCLLIB::Tan; case Intrinsic::tanh: return OpenCLLIB::Tanh; case Intrinsic::trunc: return OpenCLLIB::Trunc; case Intrinsic::modf: return OpenCLLIB::Modf; default: assert(false && "Builtin ID requested for Unhandled intrinsic!"); return 0; } } static SPIRVWord getNativeBuiltinIdForIntrinsic(Intrinsic::ID IID) { switch (IID) { case Intrinsic::cos: return OpenCLLIB::Native_cos; case Intrinsic::exp: return OpenCLLIB::Native_exp; case Intrinsic::exp2: return OpenCLLIB::Native_exp2; case Intrinsic::log: return OpenCLLIB::Native_log; case Intrinsic::log10: return OpenCLLIB::Native_log10; case Intrinsic::log2: return OpenCLLIB::Native_log2; case Intrinsic::sin: return OpenCLLIB::Native_sin; case Intrinsic::sqrt: return OpenCLLIB::Native_sqrt; case Intrinsic::tan: return OpenCLLIB::Native_tan; default: return getBuiltinIdForIntrinsic(IID); } } static bool allowsApproxFunction(IntrinsicInst *II) { auto *Ty = II->getType(); // OpenCL native_* built-ins only support single precision data type return II->hasApproxFunc() && (Ty->isFloatTy() || (Ty->isVectorTy() && cast(Ty)->getElementType()->isFloatTy())); } namespace { bool checkMemUser(User *User) { if (isa(User) || isa(User)) return true; if (auto *III = dyn_cast(User)) { if (III->getIntrinsicID() == Intrinsic::memcpy) return true; } return false; } } // namespace bool allowDecorateWithLatencyControlINTEL(IntrinsicInst *II) { for (auto *Inst : II->users()) { // if castInst, check Successors if (auto *Cast = dyn_cast(Inst)) { for (auto *Successor : Cast->users()) if (checkMemUser(Successor)) return true; } else { if (checkMemUser(Inst)) return true; } } return false; } SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II, SPIRVBasicBlock *BB) { auto GetMemoryAccess = [](MemIntrinsic *MI, bool AllowTwoMemAccessMasks) -> std::vector { std::vector MemoryAccess(1, MemoryAccessMaskNone); MaybeAlign DestAlignVal = MI->getDestAlign(); if (DestAlignVal) { Align AlignVal = *DestAlignVal; MemoryAccess[0] |= MemoryAccessAlignedMask; if (auto *MTI = dyn_cast(MI)) { MaybeAlign SourceAlignVal = MTI->getSourceAlign(); assert(SourceAlignVal && "Missed Source alignment!"); // In a case when alignment of source differs from dest one // we either preserve both (allowed since SPIR-V 1.4), or the least // value is guaranteed anyway. if (AllowTwoMemAccessMasks) { if (*DestAlignVal != *SourceAlignVal) { MemoryAccess.push_back(DestAlignVal.valueOrOne().value()); MemoryAccess.push_back(MemoryAccessAlignedMask); AlignVal = *SourceAlignVal; } } else { AlignVal = std::min(*DestAlignVal, *SourceAlignVal); } } MemoryAccess.push_back(AlignVal.value()); } if (MI->isVolatile()) MemoryAccess[0] |= MemoryAccessVolatileMask; return MemoryAccess; }; // LLVM intrinsics with known translation to SPIR-V are handled here. They // also must be registered at isKnownIntrinsic function in order to make // -spirv-allow-unknown-intrinsics work correctly. auto IID = II->getIntrinsicID(); switch (IID) { case Intrinsic::fabs: case Intrinsic::fma: case Intrinsic::maxnum: case Intrinsic::minnum: case Intrinsic::fmuladd: { Type *Ty = II->getType(); if (Ty->isBFloatTy()) BM->addCapability(internal::CapabilityBFloat16ArithmeticINTEL); break; } default: break; } switch (IID) { case Intrinsic::assume: { // llvm.assume translation is currently supported only within // SPV_KHR_expect_assume extension, ignore it otherwise, since it's // an optimization hint if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_expect_assume)) { SPIRVValue *Condition = transValue(II->getArgOperand(0), BB); return BM->addAssumeTrueKHRInst(Condition, BB); } return nullptr; } case Intrinsic::bitreverse: { if (!BM->getErrorLog().checkError( BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_bit_instructions), SPIRVEC_InvalidFunctionCall, II, "Translation of llvm.bitreverse intrinsic requires " "SPV_KHR_bit_instructions extension.")) { return nullptr; } SPIRVType *Ty = transType(II->getType()); SPIRVValue *Op = transValue(II->getArgOperand(0), BB); return BM->addUnaryInst(OpBitReverse, Ty, Op, BB); } // Unary FP intrinsic case Intrinsic::acos: case Intrinsic::asin: case Intrinsic::atan: case Intrinsic::ceil: case Intrinsic::cos: case Intrinsic::cosh: case Intrinsic::exp: case Intrinsic::exp2: case Intrinsic::fabs: case Intrinsic::floor: case Intrinsic::log: case Intrinsic::log10: case Intrinsic::log2: case Intrinsic::nearbyint: case Intrinsic::rint: case Intrinsic::round: case Intrinsic::roundeven: case Intrinsic::sin: case Intrinsic::sinh: case Intrinsic::sqrt: case Intrinsic::tan: case Intrinsic::tanh: case Intrinsic::trunc: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVWord ExtOp = allowsApproxFunction(II) ? getNativeBuiltinIdForIntrinsic(IID) : getBuiltinIdForIntrinsic(IID); SPIRVType *STy = transType(II->getType()); std::vector Ops(1, transValue(II->getArgOperand(0), BB)); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case Intrinsic::lround: { SPIRVType *SourceTy = transType(II->getArgOperand(0)->getType()); SPIRVType *DestTy = transType(II->getType()); SPIRVValue *Rounded = BM->addExtInst( SourceTy, BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Round, {transValue(II->getArgOperand(0), BB)}, BB); return BM->addUnaryInst(OpConvertFToS, DestTy, Rounded, BB); } case Intrinsic::frexp: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVWord ExtOp = getBuiltinIdForIntrinsic(IID); SPIRVType *FTy = transType(II->getType()->getStructElementType(0)); SPIRVTypePointer *ITy = static_cast(transPointerType( II->getType()->getStructElementType(1), SPIRAS_Private)); if (!ITy->isTypeUntypedPointerKHR()) { unsigned BitWidth = ITy->getElementType()->getBitWidth(); BM->getErrorLog().checkError(BitWidth == 32, SPIRVEC_InvalidBitWidth, std::to_string(BitWidth)); } SPIRVValue *IntVal = BM->addVariable(ITy, ITy->isTypeUntypedPointerKHR() ? transType(II->getType()->getStructElementType(1)) : nullptr, false, spv::internal::LinkageTypeInternal, nullptr, "", ITy->getStorageClass(), BB); std::vector Ops{transValue(II->getArgOperand(0), BB), IntVal}; return BM->addExtInst(FTy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case Intrinsic::modf: { // llvm.modf has a single arg --the number to be decomposed-- and returns a // struct { double, double }, while OpenCLLIB::modf has two args --the // number to be decomposed and a pointer--, returns the fractional part and // the integral part is stored in the pointer argument. Therefore, we can't // use directly the OpenCLLIB::modf intrinsic. However, we can do some // scaffolding to make it work. The idea is to create an alloca instruction // to get a ptr, pass this ptr to OpenCLLIB::modf, and then load the value // from this ptr to place it in the struct. // llvm.modf returns the fractional part as the first element of the result, // and the integral part as the second element of the result. Therefore, the // first element is the return value of OpenCLLIB::modf, and the second // element is the value loaded from the ptr of the alloca we created. // Create the alloca instruction. SPIRVType *IntegralTy = transType(II->getType()->getStructElementType(1)); // IntegralTy is the type of the result. We want to create a pointer to this // that we can pass to OpenCLLIB::modf to store the integral part. SPIRVType *GenericPtrTy = BM->addPointerType(StorageClassFunction, IntegralTy); auto *IntegralPtrTy = dyn_cast(GenericPtrTy); // We need to use the entry BB of the function calling llvm.modf.*, instead // of the current BB. For that, we'll find current BB's parent and get its // first BB, which is the entry BB of the function. SPIRVBasicBlock *EntryBB = BB->getParent()->getBasicBlock(0); SPIRVValue *Ptr = BM->addVariable( IntegralPtrTy, nullptr, false, spv::internal::LinkageTypeInternal, nullptr, "", StorageClassFunction, EntryBB); // Create the OpenCLLIB::modf instruction. SPIRVType *FTy = transType(II->getType()->getStructElementType(0)); SPIRVValue *Arg = transValue(II->getArgOperand(0), BB); std::vector Ops{Arg, Ptr}; SPIRVValue *Modf = BM->addExtInst(FTy, BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Modf, Ops, BB); // Load the value from the ptr. SPIRVValue *IntegralVal = BM->addLoadInst(Ptr, {}, BB, IntegralTy); // Create the struct. SPIRVType *STy = transType(II->getType()); std::vector StructVals{Modf->getId(), IntegralVal->getId()}; return BM->addCompositeConstructInst(STy, StructVals, BB); } // Binary FP intrinsics case Intrinsic::atan2: case Intrinsic::copysign: case Intrinsic::pow: case Intrinsic::powi: case Intrinsic::maximum: case Intrinsic::maxnum: case Intrinsic::minimum: case Intrinsic::minnum: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVWord ExtOp = allowsApproxFunction(II) ? getNativeBuiltinIdForIntrinsic(IID) : getBuiltinIdForIntrinsic(IID); SPIRVType *STy = transType(II->getType()); std::vector Ops{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB)}; return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case Intrinsic::umin: case Intrinsic::umax: case Intrinsic::smin: case Intrinsic::smax: { Type *BoolTy = IntegerType::getInt1Ty(M->getContext()); SPIRVValue *FirstArgVal = transValue(II->getArgOperand(0), BB); SPIRVValue *SecondArgVal = transValue(II->getArgOperand(1), BB); Op OC = (IID == Intrinsic::smin) ? OpSLessThan : ((IID == Intrinsic::smax) ? OpSGreaterThan : ((IID == Intrinsic::umin) ? OpULessThan : OpUGreaterThan)); if (auto *VecTy = dyn_cast(II->getArgOperand(0)->getType())) BoolTy = VectorType::get(BoolTy, VecTy->getElementCount()); SPIRVValue *Cmp = BM->addCmpInst(OC, transType(BoolTy), FirstArgVal, SecondArgVal, BB); return BM->addSelectInst(Cmp, FirstArgVal, SecondArgVal, BB); } case Intrinsic::ucmp: case Intrinsic::scmp: { Type *BoolTy = IntegerType::getInt1Ty(M->getContext()); SPIRVValue *FirstArgVal = transValue(II->getArgOperand(0), BB); SPIRVValue *SecondArgVal = transValue(II->getArgOperand(1), BB); SPIRVType *ResTy = transType(II->getType()); SPIRVValue *One = nullptr; SPIRVValue *Zero = nullptr; SPIRVValue *MinusOne = nullptr; if (auto *VecTy = dyn_cast(II->getType())) { auto *ElemTy = transType(VecTy->getElementType()); APInt MinusOneValue(ElemTy->getIntegerBitWidth(), 0, 1); MinusOneValue.setAllBits(); unsigned VecSize = VecTy->getElementCount().getFixedValue(); auto ElemCount = static_cast::size_type>(VecSize); auto *ElemOne = BM->addConstant(ElemTy, 1); auto *ElemZero = BM->addConstant(ElemTy, 0); auto *ElemMinusOne = BM->addConstant(ElemTy, std::move(MinusOneValue)); std::vector ElemsOne(ElemCount, ElemOne); std::vector ElemsZero(ElemCount, ElemZero); std::vector ElemsMinusOne(ElemCount, ElemMinusOne); One = BM->addCompositeConstant(ResTy, ElemsOne); Zero = BM->addCompositeConstant(ResTy, ElemsZero); MinusOne = BM->addCompositeConstant(ResTy, ElemsMinusOne); } else { One = BM->addConstant(ResTy, 1); Zero = BM->addConstant(ResTy, 0); APInt MinusOneValue(ResTy->getIntegerBitWidth(), 0, 1); MinusOneValue.setAllBits(); MinusOne = BM->addConstant(ResTy, std::move(MinusOneValue)); } Op OC1 = (IID == Intrinsic::scmp) ? OpSLessThanEqual : OpULessThanEqual; Op OC2 = (IID == Intrinsic::scmp) ? OpSLessThan : OpULessThan; if (auto *VecTy = dyn_cast(II->getArgOperand(0)->getType())) BoolTy = VectorType::get(BoolTy, VecTy->getElementCount()); SPIRVValue *Cmp1 = BM->addCmpInst(OC1, transType(BoolTy), FirstArgVal, SecondArgVal, BB); SPIRVValue *Cmp2 = BM->addCmpInst(OC2, transType(BoolTy), FirstArgVal, SecondArgVal, BB); auto *ResCmp2 = BM->addSelectInst(Cmp2, MinusOne, Zero, BB); auto *ResCmp1 = BM->addSelectInst(Cmp1, ResCmp2, One, BB); return ResCmp1; } case Intrinsic::fma: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVType *STy = transType(II->getType()); std::vector Ops{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), transValue(II->getArgOperand(2), BB)}; if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma)) return BM->addInstTemplate(OpFmaKHR, BM->getIds(Ops), BB, STy); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Fma, Ops, BB); } case Intrinsic::abs: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; // LLVM has only one version of abs and it is only for signed integers. We // unconditionally choose SAbs here SPIRVWord ExtOp = OpenCLLIB::SAbs; SPIRVType *STy = transType(II->getType()); std::vector Ops(1, transValue(II->getArgOperand(0), BB)); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case Intrinsic::ctpop: { return BM->addUnaryInst(OpBitCount, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); } case Intrinsic::ctlz: case Intrinsic::cttz: { SPIRVWord ExtOp = IID == Intrinsic::ctlz ? OpenCLLIB::Clz : OpenCLLIB::Ctz; SPIRVType *Ty = transType(II->getType()); std::vector Ops(1, transValue(II->getArgOperand(0), BB)); return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case Intrinsic::expect: { // llvm.expect translation is currently supported only within // SPV_KHR_expect_assume extension, replace it with a translated value of #0 // operand otherwise, since it's an optimization hint SPIRVValue *Value = transValue(II->getArgOperand(0), BB); if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_expect_assume)) { SPIRVType *Ty = transType(II->getType()); SPIRVValue *ExpectedValue = transValue(II->getArgOperand(1), BB); return BM->addExpectKHRInst(Ty, Value, ExpectedValue, BB); } return Value; } case Intrinsic::experimental_constrained_fadd: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fadd\" not supported by SPIR-V, dropping it.\n"); return BM->addBinaryInst(OpFAdd, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::experimental_constrained_fsub: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fsub\" not supported by SPIR-V, dropping it.\n"); return BM->addBinaryInst(OpFSub, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::experimental_constrained_fmul: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fmul\" not supported by SPIR-V, dropping it.\n"); return BM->addBinaryInst(OpFMul, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::experimental_constrained_fdiv: { auto *BI = BM->addBinaryInst(OpFDiv, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_rounded_divide_sqrt) && BI->getType()->isTypeIEEE754Float()) { BM->addCapability(CapabilityRoundedDivideSqrtINTEL); return applyRoundingModeConstraint(II->getOperand(2), BI); } SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fdiv\" not supported by SPIR-V without " "SPV_INTEL_rounded_divide_sqrt, dropping it.\n"); return BI; } case Intrinsic::experimental_constrained_sqrt: { SPIRVType *STy = transType(II->getType()); std::vector Ops{transValue(II->getArgOperand(0), BB)}; auto *BI = BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Sqrt, Ops, BB); if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_rounded_divide_sqrt) && BI->getType()->isTypeIEEE754Float()) { BM->addCapability(CapabilityRoundedDivideSqrtINTEL); return applyRoundingModeConstraint(II->getOperand(1), BI); } SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"sqrt\" not supported by SPIR-V without " "SPV_INTEL_rounded_divide_sqrt, dropping it.\n"); return BI; } case Intrinsic::experimental_constrained_frem: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"frem\" not supported by SPIR-V, dropping it.\n"); return BM->addBinaryInst(OpFRem, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::experimental_constrained_fma: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fma\" not supported by SPIR-V, dropping it.\n"); std::vector Args{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), transValue(II->getArgOperand(2), BB)}; SPIRVInstruction *BI; if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma)) BI = BM->addInstTemplate(OpFmaKHR, BM->getIds(Args), BB, transType(II->getType())); else BI = BM->addExtInst(transType(II->getType()), BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Fma, Args, BB); return BI; } case Intrinsic::experimental_constrained_fptoui: { return BM->addUnaryInst(OpConvertFToU, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); } case Intrinsic::experimental_constrained_fptosi: { return BM->addUnaryInst(OpConvertFToS, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); } case Intrinsic::experimental_constrained_uitofp: { auto *BI = BM->addUnaryInst(OpConvertUToF, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); return applyRoundingModeConstraint(II->getOperand(1), BI); } case Intrinsic::experimental_constrained_sitofp: { auto *BI = BM->addUnaryInst(OpConvertSToF, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); return applyRoundingModeConstraint(II->getOperand(1), BI); } case Intrinsic::experimental_constrained_fpext: { return BM->addUnaryInst(OpFConvert, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); } case Intrinsic::experimental_constrained_fptrunc: { auto *BI = BM->addUnaryInst(OpFConvert, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); return applyRoundingModeConstraint(II->getOperand(1), BI); } case Intrinsic::experimental_constrained_fcmp: case Intrinsic::experimental_constrained_fcmps: { auto *MetaMod = cast(II->getOperand(2))->getMetadata(); Op CmpTypeOp = StringSwitch(cast(MetaMod)->getString()) .Case("oeq", OpFOrdEqual) .Case("ogt", OpFOrdGreaterThan) .Case("oge", OpFOrdGreaterThanEqual) .Case("olt", OpFOrdLessThan) .Case("ole", OpFOrdLessThanEqual) .Case("one", OpFOrdNotEqual) .Case("ord", OpOrdered) .Case("ueq", OpFUnordEqual) .Case("ugt", OpFUnordGreaterThan) .Case("uge", OpFUnordGreaterThanEqual) .Case("ult", OpFUnordLessThan) .Case("ule", OpFUnordLessThanEqual) .Case("une", OpFUnordNotEqual) .Case("uno", OpUnordered) .Default(OpNop); assert(CmpTypeOp != OpNop && "Invalid condition code!"); return BM->addCmpInst(CmpTypeOp, transType(II->getType()), transValue(II->getOperand(0), BB), transValue(II->getOperand(1), BB), BB); } case Intrinsic::experimental_constrained_fmuladd: { SPIRVDBG(dbgs() << "WARNING: Rounding Mode of constrained intrinsic " "\"fmuladd\" not supported by SPIR-V, dropping it.\n"); SPIRVType *Ty = transType(II->getType()); SPIRVValue *Mul = BM->addBinaryInst(OpFMul, Ty, transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); return BM->addBinaryInst(OpFAdd, Ty, Mul, transValue(II->getArgOperand(2), BB), BB); } case Intrinsic::fmuladd: { // For llvm.fmuladd.* fusion is not guaranteed. If a fused multiply-add // is required the corresponding llvm.fma.* intrinsic function should be // used instead. // If allowed, let's replace llvm.fmuladd.* with mad from OpenCL extended // instruction set, as it has the same semantic for FULL_PROFILE OpenCL // devices (implementation-defined for EMBEDDED_PROFILE). if (BM->shouldReplaceLLVMFmulAddWithOpenCLMad() || BM->getExtInst() == SPIRV::ExtInst::OpenCL) { std::vector Ops{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), transValue(II->getArgOperand(2), BB)}; return BM->addExtInst(transType(II->getType()), BM->getExtInstSetId(SPIRVEIS_OpenCL), OpenCLLIB::Mad, Ops, BB); } // Otherwise, just break llvm.fmuladd.* into a pair of fmul + fadd SPIRVType *Ty = transType(II->getType()); SPIRVValue *Mul = BM->addBinaryInst(OpFMul, Ty, transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); return BM->addBinaryInst(OpFAdd, Ty, Mul, transValue(II->getArgOperand(2), BB), BB); } case Intrinsic::fptoui_sat: { auto *UI = BM->addUnaryInst(OpConvertFToU, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); UI->setSaturatedConversion(true); return UI; } case Intrinsic::fptosi_sat: { auto *UI = BM->addUnaryInst(OpConvertFToS, transType(II->getType()), transValue(II->getArgOperand(0), BB), BB); UI->setSaturatedConversion(true); return UI; } case Intrinsic::uadd_sat: case Intrinsic::usub_sat: case Intrinsic::sadd_sat: case Intrinsic::ssub_sat: { SPIRVWord ExtOp; if (IID == Intrinsic::uadd_sat) ExtOp = OpenCLLIB::UAdd_sat; else if (IID == Intrinsic::usub_sat) ExtOp = OpenCLLIB::USub_sat; else if (IID == Intrinsic::sadd_sat) ExtOp = OpenCLLIB::SAdd_sat; else ExtOp = OpenCLLIB::SSub_sat; SPIRVType *Ty = transType(II->getType()); std::vector Operands = {transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB)}; return BM->addExtInst(Ty, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, std::move(Operands), BB); } case Intrinsic::uadd_with_overflow: { return BM->addBinaryInst(OpIAddCarry, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::usub_with_overflow: { return BM->addBinaryInst(OpISubBorrow, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case Intrinsic::vector_reduce_add: case Intrinsic::vector_reduce_mul: case Intrinsic::vector_reduce_and: case Intrinsic::vector_reduce_or: case Intrinsic::vector_reduce_xor: { Op Op; if (IID == Intrinsic::vector_reduce_add) { Op = OpIAdd; } else if (IID == Intrinsic::vector_reduce_mul) { Op = OpIMul; } else if (IID == Intrinsic::vector_reduce_and) { Op = OpBitwiseAnd; } else if (IID == Intrinsic::vector_reduce_or) { Op = OpBitwiseOr; } else { Op = OpBitwiseXor; } VectorType *VecTy = cast(II->getArgOperand(0)->getType()); SPIRVValue *VecSVal = transValue(II->getArgOperand(0), BB); SPIRVTypeInt *ResultSType = BM->addIntegerType(VecTy->getElementType()->getIntegerBitWidth()); SPIRVTypeInt *I32STy = BM->addIntegerType(32); unsigned VecSize = VecTy->getElementCount().getFixedValue(); if (VecSize > 0) { SmallVector Extracts(VecSize); for (unsigned Idx = 0; Idx < VecSize; ++Idx) { Extracts[Idx] = BM->addVectorExtractDynamicInst( VecSVal, BM->addIntegerConstant(I32STy, Idx), BB); } unsigned Counter = VecSize >> 1; while (Counter != 0) { for (unsigned Idx = 0; Idx < Counter; ++Idx) { Extracts[Idx] = BM->addBinaryInst(Op, ResultSType, Extracts[Idx << 1], Extracts[(Idx << 1) + 1], BB); } Counter >>= 1; } if ((VecSize & 1) != 0) { Extracts[0] = BM->addBinaryInst(Op, ResultSType, Extracts[0], Extracts[VecSize - 1], BB); } return Extracts[0]; } assert(VecSize && "Zero Extracts size for vector reduce lowering"); return nullptr; } case Intrinsic::vector_reduce_fadd: case Intrinsic::vector_reduce_fmul: { Op Op = IID == Intrinsic::vector_reduce_fadd ? OpFAdd : OpFMul; VectorType *VecTy = cast(II->getArgOperand(1)->getType()); SPIRVValue *VecSVal = transValue(II->getArgOperand(1), BB); SPIRVValue *StartingSVal = transValue(II->getArgOperand(0), BB); SPIRVTypeInt *I32STy = BM->addIntegerType(32); unsigned VecSize = VecTy->getElementCount().getFixedValue(); if (VecSize > 0) { SmallVector Extracts(VecSize); for (unsigned Idx = 0; Idx < VecSize; ++Idx) { Extracts[Idx] = BM->addVectorExtractDynamicInst( VecSVal, BM->addIntegerConstant(I32STy, Idx), BB); } SPIRVValue *V = BM->addBinaryInst(Op, StartingSVal->getType(), StartingSVal, Extracts[0], BB); for (unsigned Idx = 1; Idx < VecSize; ++Idx) { V = BM->addBinaryInst(Op, StartingSVal->getType(), V, Extracts[Idx], BB); } return V; } assert(VecSize && "Zero Extracts size for vector reduce lowering"); return nullptr; } case Intrinsic::vector_reduce_smax: case Intrinsic::vector_reduce_smin: case Intrinsic::vector_reduce_umax: case Intrinsic::vector_reduce_umin: case Intrinsic::vector_reduce_fmax: case Intrinsic::vector_reduce_fmin: case Intrinsic::vector_reduce_fmaximum: case Intrinsic::vector_reduce_fminimum: { Op Op; if (IID == Intrinsic::vector_reduce_smax) { Op = OpSGreaterThan; } else if (IID == Intrinsic::vector_reduce_smin) { Op = OpSLessThan; } else if (IID == Intrinsic::vector_reduce_umax) { Op = OpUGreaterThan; } else if (IID == Intrinsic::vector_reduce_umin) { Op = OpULessThan; } else if (IID == Intrinsic::vector_reduce_fmax) { Op = OpFOrdGreaterThan; } else if (IID == Intrinsic::vector_reduce_fmin) { Op = OpFOrdLessThan; } else if (IID == Intrinsic::vector_reduce_fmaximum) { Op = OpFUnordGreaterThan; } else { Op = OpFUnordLessThan; } VectorType *VecTy = cast(II->getArgOperand(0)->getType()); SPIRVValue *VecSVal = transValue(II->getArgOperand(0), BB); SPIRVType *BoolSTy = transType(Type::getInt1Ty(II->getContext())); SPIRVTypeInt *I32STy = BM->addIntegerType(32); unsigned VecSize = VecTy->getElementCount().getFixedValue(); SmallVector Extracts(VecSize); if (VecSize > 0) { for (unsigned Idx = 0; Idx < VecSize; ++Idx) { Extracts[Idx] = BM->addVectorExtractDynamicInst( VecSVal, BM->addIntegerConstant(I32STy, Idx), BB); } unsigned Counter = VecSize >> 1; while (Counter != 0) { for (unsigned Idx = 0; Idx < Counter; ++Idx) { SPIRVValue *Cond = BM->addBinaryInst(Op, BoolSTy, Extracts[Idx << 1], Extracts[(Idx << 1) + 1], BB); Extracts[Idx] = BM->addSelectInst(Cond, Extracts[Idx << 1], Extracts[(Idx << 1) + 1], BB); } Counter >>= 1; } if ((VecSize & 1) != 0) { SPIRVValue *Cond = BM->addBinaryInst(Op, BoolSTy, Extracts[0], Extracts[VecSize - 1], BB); Extracts[0] = BM->addSelectInst(Cond, Extracts[0], Extracts[VecSize - 1], BB); } return Extracts[0]; } assert(VecSize && "Zero Extracts size for vector reduce lowering"); return nullptr; } case Intrinsic::memset: { // Generally there is no direct mapping of memset to SPIR-V. But it turns // out that memset is emitted by Clang for initialization in default // constructors so we need some basic support. The code below only handles // cases with constant value and constant length. MemSetInst *MSI = cast(II); Value *Val = MSI->getValue(); if (!isa(Val)) { assert(false && "Can't translate llvm.memset with non-const `value` argument"); return nullptr; } Value *Len = MSI->getLength(); if (!isa(Len)) { assert(false && "Can't translate llvm.memset with non-const `length` argument"); return nullptr; } uint64_t NumElements = static_cast(Len)->getZExtValue(); auto *AT = ArrayType::get(Val->getType(), NumElements); SPIRVTypeArray *CompositeTy = static_cast(transType(AT)); SPIRVValue *Init; if (cast(Val)->isZeroValue()) { Init = BM->addNullConstant(CompositeTy); } else { // On 32-bit systems, size_type of std::vector is not a 64-bit type. Let's // assume that we won't encounter memset for more than 2^32 elements and // insert explicit cast to avoid possible warning/error about narrowing // conversion auto TNumElts = static_cast::size_type>(NumElements); std::vector Elts(TNumElts, transValue(Val, BB)); Init = BM->addCompositeConstant(CompositeTy, Elts); } SPIRVType *VarTy = transPointerType(AT, SPIRV::SPIRAS_Constant); SPIRVValue *Var = BM->addVariable(VarTy, nullptr, /*isConstant*/ true, spv::internal::LinkageTypeInternal, Init, "", StorageClassUniformConstant, nullptr); std::vector MemAccess = GetMemoryAccess( MSI, BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)); if (!MemAccess.empty() && MemAccess[0] == MemoryAccessAlignedMask) Var->setAlignment(MemAccess[1]); SPIRVType *SourceTy = transPointerType(Val->getType(), SPIRV::SPIRAS_Constant); SPIRVValue *Source = BM->addUnaryInst(OpBitcast, SourceTy, Var, BB); SPIRVValue *Target = transValue(MSI->getRawDest(), BB); return BM->addCopyMemorySizedInst(Target, Source, CompositeTy->getLength(), MemAccess, BB); } break; case Intrinsic::memcpy: return BM->addCopyMemorySizedInst( transValue(II->getOperand(0), BB), transValue(II->getOperand(1), BB), transValue(II->getOperand(2), BB), GetMemoryAccess(cast(II), BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)), BB); case Intrinsic::lifetime_start: case Intrinsic::lifetime_end: { Op OC = (II->getIntrinsicID() == Intrinsic::lifetime_start) ? OpLifetimeStart : OpLifetimeStop; int64_t Size = dyn_cast(II->getOperand(0))->getSExtValue(); if (Size == -1) Size = 0; Value *LLVMPtrOp = II->getOperand(1); unsigned PtrAS = cast(LLVMPtrOp->getType())->getAddressSpace(); auto *PtrOp = transValue(LLVMPtrOp, BB); if (PtrAS == SPIRAS_Private) return BM->addLifetimeInst(OC, PtrOp, Size, BB); // If pointer address space is Generic - cast to private first BM->getErrorLog().checkError( PtrAS == SPIRAS_Generic, SPIRVEC_InvalidInstruction, II, "lifetime intrinsic pointer operand must be in private or generic AS"); auto *SrcTy = PtrOp->getType(); SPIRVType *DstTy = nullptr; if (SrcTy->isTypeUntypedPointerKHR()) DstTy = BM->addPointerType(StorageClassFunction, nullptr); else DstTy = BM->addPointerType(StorageClassFunction, SrcTy->getPointerElementType()); PtrOp = BM->addUnaryInst(OpGenericCastToPtr, DstTy, PtrOp, BB); ValueMap[LLVMPtrOp] = PtrOp; return BM->addLifetimeInst(OC, PtrOp, Size, BB); } // We don't want to mix translation of regular code and debug info, because // it creates a mess, therefore translation of debug intrinsics is // postponed until LLVMToSPIRVDbgTran::finalizeDebug...() methods. case Intrinsic::dbg_declare: return DbgTran->createDebugDeclarePlaceholder(cast(II), BB); case Intrinsic::dbg_value: return DbgTran->createDebugValuePlaceholder(cast(II), BB); case Intrinsic::annotation: { SPIRVType *Ty = transScavengedType(II); Constant *C = cast(II->getArgOperand(1)->stripPointerCasts()); StringRef AnnotationString; if (!getConstantStringInfo(C, AnnotationString)) return nullptr; if (AnnotationString == kOCLBuiltinName::FPGARegIntel) { if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_reg)) return BM->addFPGARegINTELInst(Ty, transValue(II->getOperand(0), BB), BB); else return transValue(II->getOperand(0), BB); } return nullptr; } case Intrinsic::var_annotation: { SPIRVValue *SV; if (auto *BI = dyn_cast(II->getArgOperand(0))) { SV = transValue(BI->getOperand(0), BB); } else { SV = transValue(II->getOperand(0), BB); } std::string AnnotationString; processAnnotationString(II, AnnotationString); DecorationsInfoVec Decorations = tryParseAnnotationString(BM, AnnotationString).MemoryAttributesVec; // If we didn't find any IntelFPGA-specific decorations, let's add the whole // annotation string as UserSemantic Decoration if (Decorations.empty()) { SV->addDecorate( new SPIRVDecorateUserSemanticAttr(SV, AnnotationString.c_str())); } else { addAnnotationDecorations(SV, Decorations); } return SV; } // The layout of llvm.ptr.annotation is: // declare iN* @llvm.ptr.annotation.p
iN( // iN* , i8* , i8* , i32 , i8* ) // where N is a power of two number, // first i8* stands for the annotation itself, // second i8* is for the location (file name), // i8* is a pointer on a GV, which can carry optinal variadic // clang::annotation attribute expression arguments. case Intrinsic::ptr_annotation: { Value *AnnotSubj = nullptr; if (auto *BI = dyn_cast(II->getArgOperand(0))) { AnnotSubj = BI->getOperand(0); } else { AnnotSubj = II->getOperand(0); } std::string AnnotationString; processAnnotationString(II, AnnotationString); AnnotationDecorations Decorations = tryParseAnnotationString(BM, AnnotationString); // Translate FPGARegIntel annotations to OpFPGARegINTEL. if (AnnotationString == kOCLBuiltinName::FPGARegIntel) { auto *Ty = transScavengedType(II); auto *BI = dyn_cast(II->getOperand(0)); if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_fpga_reg)) return BM->addFPGARegINTELInst(Ty, transValue(BI, BB), BB); return transValue(BI, BB); } SPIRVValue *DecSubj = transValue(AnnotSubj, BB); if (Decorations.empty()) { DecSubj->addDecorate( new SPIRVDecorateUserSemanticAttr(DecSubj, AnnotationString.c_str())); } else { addAnnotationDecorations(DecSubj, Decorations.MemoryAttributesVec); // Apply the LSU parameter decoration to the pointer result of a GEP // to the given struct member (InBoundsPtrAccessChain in SPIR-V). // Decorating the member itself with a MemberDecoration is not feasible, // because multiple accesses to the struct-held memory can require // different LSU parameters. addAnnotationDecorations(DecSubj, Decorations.MemoryAccessesVec); addAnnotationDecorations(DecSubj, Decorations.CacheControlVec); addAnnotationDecorations(DecSubj, Decorations.BufferLocationVec); if (allowDecorateWithLatencyControlINTEL(II)) { addAnnotationDecorations(DecSubj, Decorations.LatencyControlVec); } } II->replaceAllUsesWith(II->getOperand(0)); return DecSubj; } case Intrinsic::stacksave: { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_variable_length_array)) { auto *Ty = transPointerType(Type::getInt8Ty(M->getContext()), 0); return BM->addInstTemplate(OpSaveMemoryINTEL, BB, Ty); } BM->getErrorLog().checkError( BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II, "Translation of llvm.stacksave intrinsic requires " "SPV_INTEL_variable_length_array extension or " "-spirv-allow-unknown-intrinsics option."); break; } case Intrinsic::stackrestore: { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_variable_length_array)) { auto *Ptr = transValue(II->getArgOperand(0), BB); return BM->addInstTemplate(OpRestoreMemoryINTEL, {Ptr->getId()}, BB, nullptr); } BM->getErrorLog().checkError( BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II, "Translation of llvm.restore intrinsic requires " "SPV_INTEL_variable_length_array extension or " "-spirv-allow-unknown-intrinsics option."); break; } // We can just ignore/drop some intrinsics, like optimizations hint. case Intrinsic::experimental_noalias_scope_decl: case Intrinsic::invariant_start: case Intrinsic::invariant_end: case Intrinsic::dbg_label: // llvm.trap and llvm.debugtrap intrinsics are not implemented. But for now // don't crash. This change is pending the trap/abort intrinsic // implementation. case Intrinsic::trap: case Intrinsic::ubsantrap: case Intrinsic::debugtrap: // Just ignore llvm.fake.use intrinsic, as it has no translation in SPIRV. case Intrinsic::fake_use: // llvm.instrprof.* intrinsics are not supported case Intrinsic::instrprof_increment: case Intrinsic::instrprof_increment_step: case Intrinsic::instrprof_value_profile: return nullptr; case Intrinsic::is_constant: { auto *CO = dyn_cast(II->getOperand(0)); if (CO && isManifestConstant(CO)) return transValue(ConstantInt::getTrue(II->getType()), BB, false); else return transValue(ConstantInt::getFalse(II->getType()), BB, false); } case Intrinsic::arithmetic_fence: { SPIRVType *Ty = transType(II->getType()); SPIRVValue *Op = transValue(II->getArgOperand(0), BB); if (BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_arithmetic_fence)) { BM->addCapability(CapabilityArithmeticFenceEXT); BM->addExtension(ExtensionID::SPV_EXT_arithmetic_fence); return BM->addUnaryInst(OpArithmeticFenceEXT, Ty, Op, BB); } if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_arithmetic_fence)) { // Note: SPV_INTEL_arithmetic_fence was unpublished and superseded by // SPV_EXT_arithmetic_fence. BM->addCapability(CapabilityArithmeticFenceEXT); BM->addExtension(ExtensionID::SPV_INTEL_arithmetic_fence); return BM->addUnaryInst(OpArithmeticFenceEXT, Ty, Op, BB); } return Op; } case Intrinsic::masked_gather: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_masked_gather_scatter)) { BM->getErrorLog().checkError( BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II, "Translation of llvm.masked.gather intrinsic requires " "SPV_INTEL_masked_gather_scatter extension or " "-spirv-allow-unknown-intrinsics option."); return nullptr; } SPIRVType *Ty = transScavengedType(II); auto *PtrVector = transValue(II->getArgOperand(0), BB); uint32_t Alignment = cast(II->getArgOperand(1))->getZExtValue(); auto *Mask = transValue(II->getArgOperand(2), BB); auto *FillEmpty = transValue(II->getArgOperand(3), BB); std::vector Ops = {PtrVector->getId(), Alignment, Mask->getId(), FillEmpty->getId()}; return BM->addInstTemplate(internal::OpMaskedGatherINTEL, Ops, BB, Ty); } case Intrinsic::masked_scatter: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_masked_gather_scatter)) { BM->getErrorLog().checkError( BM->isUnknownIntrinsicAllowed(II), SPIRVEC_InvalidFunctionCall, II, "Translation of llvm.masked.scatter intrinsic requires " "SPV_INTEL_masked_gather_scatter extension or " "-spirv-allow-unknown-intrinsics option."); return nullptr; } auto *InputVector = transValue(II->getArgOperand(0), BB); auto *PtrVector = transValue(II->getArgOperand(1), BB); uint32_t Alignment = cast(II->getArgOperand(2))->getZExtValue(); auto *Mask = transValue(II->getArgOperand(3), BB); std::vector Ops = {InputVector->getId(), PtrVector->getId(), Alignment, Mask->getId()}; return BM->addInstTemplate(internal::OpMaskedScatterINTEL, Ops, BB, nullptr); } case Intrinsic::is_fpclass: { // There is no direct counterpart for the intrinsic in SPIR-V, hence // we need to emulate its work by sequence of other instructions SPIRVType *ResTy = transType(II->getType()); llvm::FPClassTest FPClass = static_cast( cast(II->getArgOperand(1))->getZExtValue()); // if no tests are provided - return false if (FPClass == 0) return BM->addConstant(ResTy, false); // if all tests are provided - return true if (FPClass == fcAllFlags) return BM->addConstant(ResTy, true); Type *OpLLVMTy = II->getArgOperand(0)->getType(); SPIRVValue *InputFloat = transValue(II->getArgOperand(0), BB); std::vector ResultVec; // Adds test for Negative/Positive values SPIRVValue *SignBitTest = nullptr; SPIRVValue *NoSignTest = nullptr; auto GetNegPosInstTest = [&](SPIRVValue *TestInst, bool IsNegative) -> SPIRVValue * { SignBitTest = (SignBitTest) ? SignBitTest : BM->addInstTemplate(OpSignBitSet, {InputFloat->getId()}, BB, ResTy); if (IsNegative) { return BM->addInstTemplate( OpLogicalAnd, {SignBitTest->getId(), TestInst->getId()}, BB, ResTy); } NoSignTest = (NoSignTest) ? NoSignTest : BM->addInstTemplate(OpLogicalNot, {SignBitTest->getId()}, BB, ResTy); return BM->addInstTemplate( OpLogicalAnd, {NoSignTest->getId(), TestInst->getId()}, BB, ResTy); }; // Get LLVM Op type converted to integer. It can be either scalar or vector. const uint32_t BitSize = OpLLVMTy->getScalarSizeInBits(); Type *IntOpLLVMTy = IntegerType::getIntNTy(M->getContext(), BitSize); if (OpLLVMTy->isVectorTy()) IntOpLLVMTy = FixedVectorType::get( IntOpLLVMTy, cast(OpLLVMTy)->getNumElements()); SPIRVType *OpSPIRVTy = transType(IntOpLLVMTy); const llvm::fltSemantics &Semantics = OpLLVMTy->getScalarType()->getFltSemantics(); const APInt Inf = APFloat::getInf(Semantics).bitcastToAPInt(); const APInt AllOneMantissa = APFloat::getLargest(Semantics).bitcastToAPInt() & ~Inf; const APInt OneValue = APInt(BitSize, 1); // Some checks can be inverted tests for simple cases, for example // simultaneous check for inf, normal, subnormal and zero is a check for // non nan. auto GetInvertedFPClassTest = [](const llvm::FPClassTest Test) -> llvm::FPClassTest { llvm::FPClassTest InvertedTest = ~Test & fcAllFlags; switch (InvertedTest) { default: break; case fcNan: case fcSNan: case fcQNan: case fcInf: case fcPosInf: case fcNegInf: case fcNormal: case fcPosNormal: case fcNegNormal: case fcSubnormal: case fcPosSubnormal: case fcNegSubnormal: case fcZero: case fcPosZero: case fcNegZero: case fcFinite: case fcPosFinite: case fcNegFinite: return InvertedTest; } return fcNone; }; bool IsInverted = false; if (llvm::FPClassTest InvertedCheck = GetInvertedFPClassTest(FPClass)) { IsInverted = true; FPClass = InvertedCheck; } auto GetInvertedTestIfNeeded = [&](SPIRVValue *TestInst) -> SPIRVValue * { if (!IsInverted) return TestInst; return BM->addInstTemplate(OpLogicalNot, {TestInst->getId()}, BB, ResTy); }; // TODO: we can add some optimization for fcFinite check by replacing it // with fabs + cmp to 0x7FF0000000000000 // Integer parameter of the intrinsic is combined from several bit masks // referenced in FPClassTest enum from FloatingPointMode.h in LLVM. // Since a single intrinsic can provide multiple tests - here we might end // up adding several sequences of SPIR-V instructions if (FPClass & fcNan) { // Map on OpIsNan if we have both QNan and SNan test bits set if (FPClass & fcSNan && FPClass & fcQNan) { auto *TestIsNan = BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy); ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNan)); } else { // isquiet(V) ==> abs(V) >= (unsigned(Inf) | quiet_bit) APInt QNaNBitMask = APInt::getOneBitSet(BitSize, AllOneMantissa.getActiveBits() - 1); APInt InfWithQnanBit = Inf | QNaNBitMask; auto *QNanBitConst = transValue( Constant::getIntegerValue(IntOpLLVMTy, InfWithQnanBit), BB); auto *BitCastToInt = BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB); auto *TestIsQNan = BM->addCmpInst(OpUGreaterThanEqual, ResTy, BitCastToInt, QNanBitConst, BB); if (FPClass & fcQNan) { ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsQNan)); } else { // issignaling(V) ==> isnan(V) && !isquiet(V) auto *TestIsNan = BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy); auto *NotQNan = BM->addInstTemplate(OpLogicalNot, {TestIsQNan->getId()}, BB, ResTy); auto *TestIsSNan = BM->addInstTemplate( OpLogicalAnd, {TestIsNan->getId(), NotQNan->getId()}, BB, ResTy); ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSNan)); } } } if (FPClass & fcInf) { auto *TestIsInf = BM->addInstTemplate(OpIsInf, {InputFloat->getId()}, BB, ResTy); if (FPClass & fcNegInf && FPClass & fcPosInf) // Map on OpIsInf if we have both Inf test bits set ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsInf)); else // Map on OpIsInf with following check for sign bit ResultVec.emplace_back(GetInvertedTestIfNeeded( GetNegPosInstTest(TestIsInf, FPClass & fcNegInf))); } if (FPClass & fcNormal) { auto *TestIsNormal = BM->addInstTemplate(OpIsNormal, {InputFloat->getId()}, BB, ResTy); if (FPClass & fcNegNormal && FPClass & fcPosNormal) // Map on OpIsNormal if we have both Normal test bits set ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNormal)); else // Map on OpIsNormal with following check for sign bit ResultVec.emplace_back(GetInvertedTestIfNeeded( GetNegPosInstTest(TestIsNormal, FPClass & fcNegNormal))); } if (FPClass & fcSubnormal) { // issubnormal(V) ==> unsigned(abs(V) - 1) < (all mantissa bits set) auto *BitCastToInt = BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB); auto *MantissaConst = transValue( Constant::getIntegerValue(IntOpLLVMTy, AllOneMantissa), BB); auto *ConstOne = transValue(Constant::getIntegerValue(IntOpLLVMTy, OneValue), BB); auto *MinusOne = BM->addBinaryInst(OpISub, OpSPIRVTy, BitCastToInt, ConstOne, BB); auto *TestIsSubnormal = BM->addCmpInst(OpULessThan, ResTy, MinusOne, MantissaConst, BB); if (FPClass & fcPosSubnormal && FPClass & fcNegSubnormal) ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSubnormal)); else ResultVec.emplace_back(GetInvertedTestIfNeeded( GetNegPosInstTest(TestIsSubnormal, FPClass & fcNegSubnormal))); } if (FPClass & fcZero) { // Create zero integer constant and check for equality with bitcasted to // int float value auto SetUpCMPToZero = [&](SPIRVValue *BitCastToInt, bool IsPositive) -> SPIRVValue * { APInt ZeroInt = APInt::getZero(BitSize); if (IsPositive) { auto *ZeroConst = transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB); return BM->addCmpInst(OpIEqual, ResTy, BitCastToInt, ZeroConst, BB); } // Created 'negated' zero ZeroInt.setSignBit(); auto *NegZeroConst = transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB); return BM->addCmpInst(OpIEqual, ResTy, BitCastToInt, NegZeroConst, BB); }; auto *BitCastToInt = BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB); if (FPClass & fcPosZero && FPClass & fcNegZero) { APInt ZeroInt = APInt::getZero(BitSize); auto *ZeroConst = transValue(Constant::getIntegerValue(IntOpLLVMTy, ZeroInt), BB); APInt MaskToClearSignBit = APInt::getSignedMaxValue(BitSize); auto *MaskToClearSignBitConst = transValue( Constant::getIntegerValue(IntOpLLVMTy, MaskToClearSignBit), BB); auto *BitwiseAndRes = BM->addBinaryInst( OpBitwiseAnd, OpSPIRVTy, BitCastToInt, MaskToClearSignBitConst, BB); auto *TestIsZero = BM->addCmpInst(OpIEqual, ResTy, BitwiseAndRes, ZeroConst, BB); ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsZero)); } else if (FPClass & fcPosZero) { auto *TestIsPosZero = SetUpCMPToZero(BitCastToInt, true /*'positive' zero*/); ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsPosZero)); } else { auto *TestIsNegZero = SetUpCMPToZero(BitCastToInt, false /*'negated' zero*/); ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNegZero)); } } if (ResultVec.size() == 1) return ResultVec.back(); SPIRVValue *Result = ResultVec.front(); for (size_t I = 1; I != ResultVec.size(); ++I) { // Create a sequence of LogicalOr instructions from ResultVec to get // the overall test result std::vector LogicOps = {Result->getId(), ResultVec[I]->getId()}; Result = BM->addInstTemplate(OpLogicalOr, LogicOps, BB, ResTy); } return Result; } default: if (auto *BVar = transFPBuiltinIntrinsicInst(II, BB)) return BVar; if (BM->isUnknownIntrinsicAllowed(II)) return BM->addCallInst( transFunctionDecl(II->getCalledFunction()), transArguments(II, BB, SPIRVEntry::createUnique(OpFunctionCall).get()), BB); else // Other LLVM intrinsics shouldn't get to SPIRV, because they // can't be represented in SPIRV or aren't implemented yet. BM->SPIRVCK(false, InvalidFunctionCall, II->getCalledOperand()->getName().str()); } return nullptr; } LLVMToSPIRVBase::FPBuiltinType LLVMToSPIRVBase::getFPBuiltinType(IntrinsicInst *II, StringRef &OpName) { StringRef Name = II->getCalledFunction()->getName(); if (!Name.consume_front("llvm.fpbuiltin.")) return FPBuiltinType::UNKNOWN; OpName = Name.split('.').first; FPBuiltinType Type = StringSwitch(OpName) .Cases("fadd", "fsub", "fmul", "fdiv", "frem", FPBuiltinType::REGULAR_MATH) .Cases("sin", "cos", "tan", FPBuiltinType::EXT_1OPS) .Cases("sinh", "cosh", "tanh", FPBuiltinType::EXT_1OPS) .Cases("asin", "acos", "atan", FPBuiltinType::EXT_1OPS) .Cases("asinh", "acosh", "atanh", FPBuiltinType::EXT_1OPS) .Cases("exp", "exp2", "exp10", "expm1", FPBuiltinType::EXT_1OPS) .Cases("log", "log2", "log10", "log1p", FPBuiltinType::EXT_1OPS) .Cases("sqrt", "rsqrt", "erf", "erfc", FPBuiltinType::EXT_1OPS) .Cases("atan2", "pow", "hypot", "ldexp", FPBuiltinType::EXT_2OPS) .Case("sincos", FPBuiltinType::EXT_3OPS) .Default(FPBuiltinType::UNKNOWN); return Type; } SPIRVValue *LLVMToSPIRVBase::transFPBuiltinIntrinsicInst(IntrinsicInst *II, SPIRVBasicBlock *BB) { StringRef OpName; auto FPBuiltinTypeVal = getFPBuiltinType(II, OpName); if (FPBuiltinTypeVal == FPBuiltinType::UNKNOWN) return nullptr; switch (FPBuiltinTypeVal) { case FPBuiltinType::REGULAR_MATH: { auto BinOp = StringSwitch(OpName) .Case("fadd", OpFAdd) .Case("fsub", OpFSub) .Case("fmul", OpFMul) .Case("fdiv", OpFDiv) .Case("frem", OpFRem) .Default(OpUndef); return BM->addBinaryInst(BinOp, transType(II->getType()), transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), BB); } case FPBuiltinType::EXT_1OPS: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVType *STy = transType(II->getType()); std::vector Ops(1, transValue(II->getArgOperand(0), BB)); auto ExtOp = StringSwitch(OpName) .Case("sin", OpenCLLIB::Sin) .Case("cos", OpenCLLIB::Cos) .Case("tan", OpenCLLIB::Tan) .Case("sinh", OpenCLLIB::Sinh) .Case("cosh", OpenCLLIB::Cosh) .Case("tanh", OpenCLLIB::Tanh) .Case("asin", OpenCLLIB::Asin) .Case("acos", OpenCLLIB::Acos) .Case("atan", OpenCLLIB::Atan) .Case("asinh", OpenCLLIB::Asinh) .Case("acosh", OpenCLLIB::Acosh) .Case("atanh", OpenCLLIB::Atanh) .Case("exp", OpenCLLIB::Exp) .Case("exp2", OpenCLLIB::Exp2) .Case("exp10", OpenCLLIB::Exp10) .Case("expm1", OpenCLLIB::Expm1) .Case("log", OpenCLLIB::Log) .Case("log2", OpenCLLIB::Log2) .Case("log10", OpenCLLIB::Log10) .Case("log1p", OpenCLLIB::Log1p) .Case("sqrt", OpenCLLIB::Sqrt) .Case("rsqrt", OpenCLLIB::Rsqrt) .Case("erf", OpenCLLIB::Erf) .Case("erfc", OpenCLLIB::Erfc) .Default(SPIRVWORD_MAX); assert(ExtOp != SPIRVWORD_MAX); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case FPBuiltinType::EXT_2OPS: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVType *STy = transType(II->getType()); std::vector Ops{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB)}; auto ExtOp = StringSwitch(OpName) .Case("atan2", OpenCLLIB::Atan2) .Case("hypot", OpenCLLIB::Hypot) .Case("pow", OpenCLLIB::Pow) .Case("ldexp", OpenCLLIB::Ldexp) .Default(SPIRVWORD_MAX); assert(ExtOp != SPIRVWORD_MAX); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } case FPBuiltinType::EXT_3OPS: { if (!checkTypeForSPIRVExtendedInstLowering(II, BM)) break; SPIRVType *STy = transType(II->getType()); std::vector Ops{transValue(II->getArgOperand(0), BB), transValue(II->getArgOperand(1), BB), transValue(II->getArgOperand(2), BB)}; auto ExtOp = StringSwitch(OpName) .Case("sincos", OpenCLLIB::Sincos) .Default(SPIRVWORD_MAX); assert(ExtOp != SPIRVWORD_MAX); return BM->addExtInst(STy, BM->getExtInstSetId(SPIRVEIS_OpenCL), ExtOp, Ops, BB); } default: return nullptr; } return nullptr; } SPIRVValue *LLVMToSPIRVBase::transFenceInst(FenceInst *FI, SPIRVBasicBlock *BB) { SPIRVWord MemorySemantics; // Fence ordering may only be Acquire, Release, AcquireRelease, or // SequentiallyConsistent switch (FI->getOrdering()) { case llvm::AtomicOrdering::Acquire: MemorySemantics = MemorySemanticsAcquireMask; break; case llvm::AtomicOrdering::Release: MemorySemantics = MemorySemanticsReleaseMask; break; case llvm::AtomicOrdering::AcquireRelease: MemorySemantics = MemorySemanticsAcquireReleaseMask; break; case llvm::AtomicOrdering::SequentiallyConsistent: MemorySemantics = MemorySemanticsSequentiallyConsistentMask; break; default: assert(false && "Unexpected fence ordering"); MemorySemantics = SPIRVWORD_MAX; break; } Module *M = FI->getParent()->getModule(); spv::Scope S = toSPIRVScope(FI->getContext(), FI->getSyncScopeID()); SPIRVValue *RetScope = transConstant(getUInt32(M, S)); SPIRVValue *Val = transConstant(getUInt32(M, MemorySemantics)); assert(RetScope && Val && "RetScope and Value are not constants"); return BM->addMemoryBarrierInst(static_cast(RetScope->getId()), Val->getId(), BB); } SPIRVValue *LLVMToSPIRVBase::transCallInst(CallInst *CI, SPIRVBasicBlock *BB) { assert(CI); Function *F = CI->getFunction(); if (isa(CI->getCalledOperand()) && BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_inline_assembly)) { // Inline asm is opaque, so we cannot reason about its FP contraction // requirements. SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName() << ": inline asm " << *CI << '\n'); joinFPContract(F, FPContract::DISABLED); return transAsmCallINTEL(CI, BB); } if (CI->isIndirectCall()) { // The function is not known in advance SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName() << ": indirect call " << *CI << '\n'); joinFPContract(F, FPContract::DISABLED); return transIndirectCallInst(CI, BB); } return transDirectCallInst(CI, BB); } // Helper function to process mini-float or int4 types for FP conversions. // Processes type width, packing, and creates the appropriate SPIRV type. // Returns the SPIRV type and outputs vector size information. static SPIRVType * processMiniFPOrInt4Type(Type *LLVMTy, FPEncodingWrap Encoding, std::function GetScalarTy, SPIRVModule *BM, unsigned &OutVecSize) { Type *ScalarTy = GetScalarTy(LLVMTy); unsigned TyWidth = cast(ScalarTy)->getBitWidth(); unsigned VecSize = 0; bool IsPacked = Encoding == FPEncodingWrap::E2M1 || Encoding == FPEncodingWrap::E2M1INTEL || Encoding == FPEncodingWrap::Integer; if (IsPacked && (TyWidth == 8 || TyWidth == 16 || TyWidth == 32 || TyWidth == 64)) { // Int4 or FP4 packed in an integer: each N-bit integer holds N/4 values. assert(!isLLVMCooperativeMatrixType(LLVMTy) && "FP4 and Int4 matrices must not be packed"); unsigned OuterVecLen = LLVMTy->isVectorTy() ? cast(LLVMTy)->getElementCount().getFixedValue() : 1; VecSize = (TyWidth / 4) * OuterVecLen; TyWidth = 4; } else { if (LLVMTy->isVectorTy()) VecSize = cast(LLVMTy)->getElementCount().getFixedValue(); } SPIRVType *Ty; if (Encoding == FPEncodingWrap::Integer) { Ty = BM->addIntegerType(TyWidth); } else { Ty = BM->addFloatType(TyWidth, Encoding); } OutVecSize = VecSize; return Ty; } SPIRVValue *LLVMToSPIRVBase::transDirectCallInst(CallInst *CI, SPIRVBasicBlock *BB) { SPIRVExtInstSetKind ExtSetKind = SPIRVEIS_Count; SPIRVWord ExtOp = SPIRVWORD_MAX; llvm::Function *F = CI->getCalledFunction(); auto MangledName = F->getName(); StringRef DemangledName; if (MangledName.starts_with(SPCV_CAST) || MangledName == SAMPLER_INIT) return oclTransSpvcCastSampler(CI, BB); if (oclIsBuiltin(MangledName, DemangledName) || isDecoratedSPIRVFunc(F, DemangledName)) { if (auto *BV = transBuiltinToConstant(DemangledName, CI)) return BV; if (auto *BV = transBuiltinToInst(DemangledName, CI, BB)) return BV; } if (isInternalSPIRVBuiltin(MangledName, DemangledName)) { // Logic of the code below is described in // docs/SPIRVMiniFloatsRepresentationInLLVM.rst // A quick recap of the document: // For FP4 and FP8 types (which don't have appropriate counterparts in LLVM) // the translator expect to see external function calls with __builtin_spirv // prefix, names of the functions encode the used in the conversion // FP types and will be used by the translator for proper TypeFloat values // generation. Since in LLVM IR FP8 types are represented by // integer type, we have to insert bitcasts for dependent values that expect // integer inputs or produces integer output that is used in the conversion. if (SPIRV::FPConvertToEncodingMap::find(DemangledName)) { FPConversionDesc FPDesc = SPIRV::FPConvertToEncodingMap::map(DemangledName); Value *Src = CI->getOperand(0); Type *LLVMSrcTy = Src->getType(); Type *LLVMDstTy = CI->getType(); SPIRVType *SrcTy = nullptr; SPIRVType *DstTy = nullptr; auto GetScalarTy = [&](Type *Ty) -> Type * { if (isLLVMCooperativeMatrixType(Ty)) return cast(Ty)->getTypeParameter(0); return Ty->getScalarType(); }; // FP types representable in LLVM IR, no need for special handling // Also, int4 matrices remain the same. if (FPDesc.SrcEncoding == FPEncodingWrap::IEEE754 || FPDesc.SrcEncoding == FPEncodingWrap::BF16 || (FPDesc.SrcEncoding == FPEncodingWrap::Integer && isLLVMCooperativeMatrixType(LLVMDstTy))) SrcTy = transType(LLVMSrcTy); if (FPDesc.DstEncoding == FPEncodingWrap::IEEE754 || FPDesc.DstEncoding == FPEncodingWrap::BF16 || (FPDesc.DstEncoding == FPEncodingWrap::Integer && (isLLVMCooperativeMatrixType(LLVMDstTy) || FPDesc.SrcEncoding == FPEncodingWrap::Integer))) DstTy = transType(LLVMDstTy); SPIRVValue *SrcOp = transValue(Src, BB); if (!SrcTy) { // Src type is 'mini' float or int4. unsigned SrcVecSize = 0; SrcTy = processMiniFPOrInt4Type(LLVMSrcTy, FPDesc.SrcEncoding, GetScalarTy, BM, SrcVecSize); if (SrcVecSize > 0) SrcTy = BM->addVectorType(SrcTy, SrcVecSize); if (isLLVMCooperativeMatrixType(LLVMSrcTy)) { // Create FP4/FP8 matrix with a new type and insert a bitcast. SrcTy = BM->addCooperativeMatrixKHRType( SrcTy, static_cast(transType(LLVMSrcTy)) ->getArgs()); SrcOp = BM->addUnaryInst(OpBitcast, SrcTy, SrcOp, BB); } else if (FPDesc.SrcEncoding != FPEncodingWrap::Integer || SrcVecSize > 0) { // Create bitcast for FP4, FP8 and packed Int4 (including cases where // both the LLVM and SPIR-V types are vectors but with different // sizes, e.g. <2 x i8> repacked as <4 x Int4>). SrcOp = BM->addUnaryInst(OpBitcast, SrcTy, SrcOp, BB); } } unsigned DstVecSize = 0; if (!DstTy) { // Dst type is 'mini' float or int4. DstTy = processMiniFPOrInt4Type(LLVMDstTy, FPDesc.DstEncoding, GetScalarTy, BM, DstVecSize); if (isLLVMCooperativeMatrixType(LLVMDstTy)) // Create FP8 matrix with a new type. DstTy = BM->addCooperativeMatrixKHRType( DstTy, static_cast(transType(LLVMDstTy)) ->getArgs()); if (DstVecSize > 0) DstTy = BM->addVectorType(DstTy, DstVecSize); } std::vector Ops = {SrcOp}; const auto OC = static_cast(FPDesc.ConvOpCode); // Translate operands for stochastic roundings. for (size_t I = 1; I != CI->arg_size(); ++I) Ops.push_back(transValue(CI->getOperand(I), BB)); SPIRVValue *Conv = BM->addInstTemplate(OC, BM->getIds(Ops), BB, DstTy); if (FPDesc.Saturate) Conv->addDecorate(new SPIRVDecorate( DecorationSaturatedToLargestFloat8NormalConversionEXT, Conv)); // Representable in LLVM FP types: bitcast is not needed. if (FPDesc.DstEncoding == FPEncodingWrap::IEEE754 || FPDesc.DstEncoding == FPEncodingWrap::BF16) return Conv; // Originally not-packed integer (no repacking, or cooperative matrix). if (FPDesc.DstEncoding == FPEncodingWrap::Integer && (DstVecSize == 0 || isLLVMCooperativeMatrixType(LLVMDstTy))) return Conv; // Need to adjust types: create bitcast for FP8 and packed Int4. SPIRVValue *BitCast = BM->addUnaryInst(OpBitcast, transType(CI->getType()), Conv, BB); return BitCast; } } SmallVector Dec; if (isBuiltinTransToExtInst(CI->getCalledFunction(), &ExtSetKind, &ExtOp, &Dec)) { if (const auto *FirstArg = F->getArg(0)) { const auto *Type = FirstArg->getType(); if (Type->isBFloatTy()) BM->addCapability(internal::CapabilityBFloat16ArithmeticINTEL); } if (DemangledName.find("__spirv_ocl_printf") != StringRef::npos) { auto *FormatStrPtr = cast(CI->getArgOperand(0)->getType()); if (FormatStrPtr->getAddressSpace() != SPIR::TypeAttributeEnum::ATTR_CONST) { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_EXT_relaxed_printf_string_address_space)) { std::string ErrorStr = "Either SPV_EXT_relaxed_printf_string_address_space extension " "should be allowed to translate this module, because this LLVM " "module contains the printf function with format string, whose " "address space is not equal to 2 (constant)."; getErrorLog().checkError(false, SPIRVEC_RequiresExtension, CI, ErrorStr); } BM->addExtension( ExtensionID::SPV_EXT_relaxed_printf_string_address_space); } } else if (DemangledName.find("__spirv_ocl_prefetch") != StringRef::npos) { if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) { return BM->addUntypedPrefetchKHRInst( transScavengedType(CI), BM->getIds(transValue(getArguments(CI), BB)), BB); } } else if (DemangledName == "__spirv_ocl_fma") { if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_fma)) return BM->addInstTemplate(OpFmaKHR, BM->getIds(transValue(getArguments(CI), BB)), BB, transScavengedType(CI)); } return addDecorations( BM->addExtInst( transScavengedType(CI), BM->getExtInstSetId(ExtSetKind), ExtOp, transArguments(CI, BB, SPIRVEntry::createUnique(ExtSetKind, ExtOp).get()), BB), Dec); } Function *Callee = CI->getCalledFunction(); if (Callee->isDeclaration()) { SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName().str() << ": call to an undefined function " << *CI << '\n'); joinFPContract(CI->getFunction(), FPContract::DISABLED); } else { FPContract CalleeFPC = getFPContract(Callee); joinFPContract(CI->getFunction(), CalleeFPC); if (CalleeFPC == FPContract::DISABLED) { SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName().str() << ": call to a function with disabled contraction: " << *CI << '\n'); } } return BM->addCallInst( transFunctionDecl(Callee), transArguments(CI, BB, SPIRVEntry::createUnique(OpFunctionCall).get()), BB); } SPIRVValue *LLVMToSPIRVBase::transIndirectCallInst(CallInst *CI, SPIRVBasicBlock *BB) { if (BM->getErrorLog().checkError( BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_function_pointers), SPIRVEC_FunctionPointers, CI)) { return BM->addIndirectCallInst( transValue(CI->getCalledOperand(), BB), transScavengedType(CI), transArguments(CI, BB, SPIRVEntry::createUnique(OpFunctionCall).get()), BB); } return nullptr; } SPIRVValue *LLVMToSPIRVBase::transAsmINTEL(InlineAsm *IA) { assert(IA); // TODO: intention here is to provide information about actual target // but in fact spir-64 is substituted as triple when translator works // eventually we need to fix it (not urgent) StringRef TripleStr(M->getTargetTriple().str()); auto *AsmTarget = static_cast( BM->getOrAddAsmTargetINTEL(TripleStr.str())); auto *SIA = BM->addAsmINTEL( static_cast(transType(IA->getFunctionType())), AsmTarget, IA->getAsmString().str(), IA->getConstraintString().str()); if (IA->hasSideEffects()) SIA->addDecorate(DecorationSideEffectsINTEL); return SIA; } SPIRVValue *LLVMToSPIRVBase::transAsmCallINTEL(CallInst *CI, SPIRVBasicBlock *BB) { assert(CI); auto *IA = cast(CI->getCalledOperand()); return BM->addAsmCallINTELInst( static_cast(transValue(IA, BB, false)), transArguments(CI, BB, SPIRVEntry::createUnique(OpAsmCallINTEL).get()), BB); } bool LLVMToSPIRVBase::transAddressingMode() { Triple TargetTriple(M->getTargetTriple()); if (TargetTriple.isArch32Bit()) BM->setAddressingModel(AddressingModelPhysical32); else BM->setAddressingModel(AddressingModelPhysical64); // Physical addressing model requires Addresses capability BM->addCapability(CapabilityAddresses); return true; } std::vector LLVMToSPIRVBase::transValue(const std::vector &Args, SPIRVBasicBlock *BB) { std::vector BArgs; for (auto &I : Args) BArgs.push_back(transValue(I, BB)); return BArgs; } std::vector LLVMToSPIRVBase::transValue(const std::vector &Args, SPIRVBasicBlock *BB, SPIRVEntry *Entry) { std::vector Operands; for (size_t I = 0, E = Args.size(); I != E; ++I) { Operands.push_back(Entry->isOperandLiteral(I) ? cast(Args[I])->getZExtValue() : transValue(Args[I], BB)->getId()); } return Operands; } std::vector LLVMToSPIRVBase::transArguments(CallInst *CI, SPIRVBasicBlock *BB, SPIRVEntry *Entry) { return transValue(getArguments(CI), BB, Entry); } SPIRVWord LLVMToSPIRVBase::transFunctionControlMask(Function *F) { SPIRVWord FCM = 0; SPIRSPIRVFuncCtlMaskMap::foreach ( [&](Attribute::AttrKind Attr, SPIRVFunctionControlMaskKind Mask) { if (F->hasFnAttribute(Attr)) { if (Attr == Attribute::OptimizeNone) { if (BM->isAllowedToUseExtension(ExtensionID::SPV_EXT_optnone)) { BM->addExtension(ExtensionID::SPV_EXT_optnone); BM->addCapability(CapabilityOptNoneEXT); } else if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_optnone)) { BM->addExtension(ExtensionID::SPV_INTEL_optnone); BM->addCapability(CapabilityOptNoneINTEL); } else return; } FCM |= Mask; } }); return FCM; } void LLVMToSPIRVBase::transGlobalAnnotation(GlobalVariable *V) { SPIRVDBG(dbgs() << "[transGlobalAnnotation] " << *V << '\n'); // @llvm.global.annotations is an array that contains structs with 4 fields. // Get the array of structs with metadata // TODO: actually, now it contains 5 fields, the fifth by default is nullptr // or undef, but it can be defined to include variadic arguments of // clang::annotation attribute. Need to refactor this function to turn on this // translation ConstantArray *CA = cast(V->getOperand(0)); for (Value *Op : CA->operands()) { ConstantStruct *CS = cast(Op); // The first field of the struct contains a pointer to annotated variable Value *AnnotatedVar = CS->getOperand(0)->stripPointerCasts(); SPIRVValue *SV = transValue(AnnotatedVar, nullptr); // The second field contains a pointer to a global annotation string GlobalVariable *GV = cast(CS->getOperand(1)->stripPointerCasts()); StringRef AnnotationString; if (!getConstantStringInfo(GV, AnnotationString)) { assert(false && "Annotation string missing"); return; } DecorationsInfoVec Decorations = tryParseAnnotationString(BM, AnnotationString).MemoryAttributesVec; // If we didn't find any annotation decorations, let's add the whole // annotation string as UserSemantic Decoration if (Decorations.empty()) { SV->addDecorate( new SPIRVDecorateUserSemanticAttr(SV, AnnotationString.str())); } else { addAnnotationDecorations(SV, Decorations); } } } void LLVMToSPIRVBase::transGlobalIOPipeStorage(GlobalVariable *V, MDNode *IO) { SPIRVDBG(dbgs() << "[transGlobalIOPipeStorage] " << *V << '\n'); SPIRVValue *SV = transValue(V, nullptr); assert(SV && "Failed to process OCL PipeStorage object"); if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_io_pipes)) { unsigned ID = getMDOperandAsInt(IO, 0); SV->addDecorate(DecorationIOPipeStorageINTEL, ID); } } bool LLVMToSPIRVBase::transGlobalVariables() { for (auto I = M->global_begin(), E = M->global_end(); I != E; ++I) { if ((*I).getName() == "llvm.global.annotations") transGlobalAnnotation(&(*I)); else if ([I]() -> bool { // Check if the GV is used only in var/ptr instructions. If yes - // skip processing of this since it's only an annotation GV. if (I->user_empty()) return false; for (auto *U : I->users()) { Value *V = U; while (isa(V) || isa(V)) V = cast(V)->getOperand(0); auto *GEP = dyn_cast_or_null(V); if (!GEP) return false; for (auto *GEPU : GEP->users()) { auto *II = dyn_cast(GEPU); if (!II) return false; switch (II->getIntrinsicID()) { case Intrinsic::var_annotation: case Intrinsic::ptr_annotation: continue; default: return false; } } } return true; }()) continue; else if ((I->getName() == "llvm.global_ctors" || I->getName() == "llvm.global_dtors") && !BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_function_pointers)) { // Function pointers are required to represent structor lists; do not // translate the variable if function pointers are not available. continue; } else if (MDNode *IO = ((*I).getMetadata("io_pipe_id"))) transGlobalIOPipeStorage(&(*I), IO); else if (!transValue(&(*I), nullptr)) return false; } return true; } bool LLVMToSPIRVBase::isAnyFunctionReachableFromFunction( const Function *FS, const std::unordered_set Funcs) const { std::unordered_set Done; std::unordered_set ToDo; ToDo.insert(FS); while (!ToDo.empty()) { auto It = ToDo.begin(); const Function *F = *It; if (Funcs.find(F) != Funcs.end()) return true; ToDo.erase(It); Done.insert(F); const CallGraphNode *FN = (*CG)[F]; for (unsigned I = 0; I < FN->size(); ++I) { const CallGraphNode *NN = (*FN)[I]; const Function *NNF = NN->getFunction(); if (!NNF) continue; if (Done.find(NNF) == Done.end()) { ToDo.insert(NNF); } } } return false; } std::vector LLVMToSPIRVBase::collectEntryPointInterfaces(SPIRVFunction *SF, Function *F) { std::vector Interface; for (auto &GV : M->globals()) { const auto AS = GV.getAddressSpace(); SPIRVModule *BM = SF->getModule(); if (!BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) if (AS != SPIRAS_Input && AS != SPIRAS_Output) continue; std::unordered_set Funcs; for (const auto &U : GV.uses()) { const Instruction *Inst = dyn_cast(U.getUser()); if (!Inst) continue; Funcs.insert(Inst->getFunction()); } if (isAnyFunctionReachableFromFunction(F, Funcs)) { SPIRVWord ModuleVersion = static_cast(BM->getSPIRVVersion()); if (AS != SPIRAS_Input && AS != SPIRAS_Output && ModuleVersion < static_cast(VersionNumber::SPIRV_1_4)) BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4); Interface.push_back(ValueMap[&GV]->getId()); } } return Interface; } void LLVMToSPIRVBase::mutateFuncArgType( const std::unordered_map &ChangedType, Function *F) { for (auto &I : ChangedType) { for (auto UI = F->user_begin(), UE = F->user_end(); UI != UE; ++UI) { auto *Call = dyn_cast(*UI); if (!Call) continue; auto *Arg = Call->getArgOperand(I.first); auto *OrigTy = Arg->getType(); if (OrigTy == I.second) continue; SPIRVDBG(dbgs() << "[mutate arg type] " << *Call << ", " << *Arg << '\n'); auto CastF = M->getOrInsertFunction(SPCV_CAST, I.second, OrigTy); std::vector Args; Args.push_back(Arg); auto *Cast = CallInst::Create(CastF, Args, "", Call->getIterator()); Call->replaceUsesOfWith(Arg, Cast); SPIRVDBG(dbgs() << "[mutate arg type] -> " << *Cast << '\n'); } } } // Propagate contraction requirement of F up the call graph. void LLVMToSPIRVBase::fpContractUpdateRecursive(Function *F, FPContract FPC) { std::queue Users; for (User *FU : F->users()) { Users.push(FU); } bool EnableLogger = FPC == FPContract::DISABLED && !Users.empty(); if (EnableLogger) { SPIRVDBG(dbgs() << "[fp-contract] disabled for users of " << F->getName() << '\n'); } while (!Users.empty()) { User *U = Users.front(); Users.pop(); if (EnableLogger) { SPIRVDBG(dbgs() << "[fp-contract] user: " << *U << '\n'); } // Move from an Instruction to its Function if (Instruction *I = dyn_cast(U)) { Users.push(I->getFunction()); continue; } if (Function *F = dyn_cast(U)) { if (!joinFPContract(F, FPC)) { // FP contract was not updated - no need to propagate // This also terminates a recursion (if any). if (EnableLogger) { SPIRVDBG(dbgs() << "[fp-contract] already disabled " << F->getName() << '\n'); } continue; } if (EnableLogger) { SPIRVDBG(dbgs() << "[fp-contract] disabled for " << F->getName() << '\n'); } for (User *FU : F->users()) { Users.push(FU); } continue; } // Unwrap a constant until we reach an Instruction. // This is checked after the Function, because a Function is also a // Constant. if (Constant *C = dyn_cast(U)) { for (User *CU : C->users()) { Users.push(CU); } continue; } llvm_unreachable("Unexpected use."); } } /// Returns a range that traverses \p F ensuring that dominator blocks are /// visited before the blocks they dominate. /// /// Compared to llvm::ReversePostOrderTraversal which also visits dominators /// before dominated blocks, this traversal aims to be more stable and will keep /// basic blocks in their original order as much as possible, only reordering /// them to visit dominators ahead of their dominated blocks when needed. \p DT /// is not copied by this function and needs to outlive any iterators created /// from this range. static auto stablePreDominatorTraversal(Function &F, const DominatorTree &DT) { // A local iterator type for traversing a function in the desired order. class StablePreDominatorIterator : public iterator_facade_base { // The passed DominatorTree; may be unset for end iterators. const DominatorTree *DT = nullptr; // The set of basic blocks already visited in this traversal. SmallPtrSet VisitedBBs; // The next basic block in original function order, or nullptr if the // traversal is over. BasicBlock *NextBB = nullptr; // The current basic block in the traversal, or nullptr for end iterators. BasicBlock *CurBB = nullptr; // Returns the most immediate dominator of \p BB which does not have an // unvisited dominator and so can be visited in this traversal. BasicBlock *visitableDominator(BasicBlock *BB) const { // Find BB's dominator; if there is none, BB can be visited immediately. const auto *const BBNode = DT->getNode(BB); if (!BBNode) return BB; const auto *const DomNode = BBNode->getIDom(); if (!DomNode) return BB; BasicBlock *const Dominator = DomNode->getBlock(); // If the dominator's been visited, BB can now be visited. if (VisitedBBs.contains(Dominator)) return BB; // Otherwise, find the dominator's visitable dominator instead. return visitableDominator(Dominator); } // Advances the iterator and returns the next basic block to be visited in // the traversal. BasicBlock *next() { // If NextBB is nullptr, the end of the traversal has been reached. if (!NextBB) return nullptr; // Check if NextBB has already been visited; if so, advance past it. if (VisitedBBs.contains(NextBB)) { NextBB = NextBB->getNextNode(); return next(); } // If NextBB is unvisited, visit its next visitable dominator. BasicBlock *const ToVisit = visitableDominator(NextBB); VisitedBBs.insert(ToVisit); return ToVisit; } public: // Constructs an end iterator. StablePreDominatorIterator() {} // Constructs a begin iterator at the start of \p F. StablePreDominatorIterator(Function &F, const DominatorTree &DT) : DT(&DT), NextBB(&F.getEntryBlock()) { ++*this; } // Methods required by iterator_facade_base. bool operator==(const StablePreDominatorIterator &Other) const { return CurBB == Other.CurBB; } BasicBlock &operator*() const { return *CurBB; } StablePreDominatorIterator &operator++() { CurBB = next(); return *this; } }; return make_range(StablePreDominatorIterator(F, DT), StablePreDominatorIterator()); } void LLVMToSPIRVBase::transFunction(Function *I) { SPIRVFunction *BF = transFunctionDecl(I); // Creating all basic blocks before creating any instruction. SPIR-V requires // that blocks appear after their dominators, so stablePreDominatorTraversal // is used to ensure blocks are written in the right order. const DominatorTree DT(*I); for (BasicBlock &FI : stablePreDominatorTraversal(*I, DT)) { FI.convertFromNewDbgValues(); transValue(&FI, nullptr); } for (auto &FI : *I) { SPIRVBasicBlock *BB = static_cast(transValue(&FI, nullptr)); for (auto &BI : FI) { transValue(&BI, BB, false); } } // Enable FP contraction unless proven otherwise joinFPContract(I, FPContract::ENABLED); fpContractUpdateRecursive(I, getFPContract(I)); if (isKernel(I)) { auto Interface = collectEntryPointInterfaces(BF, I); BM->addEntryPoint(ExecutionModelKernel, BF->getId(), BF->getName(), Interface); } } bool isEmptyLLVMModule(Module *M) { return M->empty() && // No functions M->global_empty(); // No global variables } // A module is VectorCompute when any function or global variable carries // VectorCompute metadata. static bool hasVectorComputeMetadata(Module *M) { return any_of(*M, [](const Function &F) { return F.hasFnAttribute(kVCMetadata::VCFunction); }) || any_of(M->globals(), [](const GlobalVariable &GV) { return GV.hasAttribute(kVCMetadata::VCGlobalVariable); }); } bool LLVMToSPIRVBase::translate() { BM->setGeneratorVer(KTranslatorVer); if (isEmptyLLVMModule(M)) BM->addCapability(CapabilityLinkage); // Check before type translation so that SPIRVTypeVector can choose the // matching capability. if (BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) BM->setVectorCompute(hasVectorComputeMetadata(M)); if (!lowerBuiltinCallsToVariables(M)) return false; // Use the type scavenger to recover pointer element types. Scavenger = std::make_unique(*M); if (!transSourceLanguage()) return false; if (!transExtension()) return false; if (!transBuiltinSet()) return false; if (!transAddressingMode()) return false; if (!transGlobalVariables()) return false; for (auto &F : *M) { auto *FT = F.getFunctionType(); std::unordered_map ChangedType; oclGetMutatedArgumentTypesByBuiltin(FT, ChangedType, &F); mutateFuncArgType(ChangedType, &F); } // SPIR-V logical layout requires all function declarations go before // function definitions. std::vector Decls, Defs; for (auto &F : *M) { if (isBuiltinTransToInst(&F) || isBuiltinTransToExtInst(&F) || F.getName().starts_with(SPCV_CAST) || F.getName().starts_with(LLVM_MEMCPY) || F.getName().starts_with(SAMPLER_INIT)) continue; if (F.isDeclaration()) Decls.push_back(&F); else Defs.push_back(&F); } for (auto *I : Decls) transFunctionDecl(I); for (auto *I : Defs) transFunction(I); if (!transMetadata()) return false; if (!transExecutionMode()) return false; BM->resolveUnknownStructFields(); DbgTran->transDebugMetadata(); return true; } llvm::IntegerType *LLVMToSPIRVBase::getSizetType(unsigned AS) { return IntegerType::getIntNTy(M->getContext(), M->getDataLayout().getPointerSizeInBits(AS)); } void LLVMToSPIRVBase::oclGetMutatedArgumentTypesByBuiltin( llvm::FunctionType *FT, std::unordered_map &ChangedType, Function *F) { StringRef Demangled; if (!oclIsBuiltin(F->getName(), Demangled)) return; // Note: kSPIRVName::ConvertHandleToSampledImageINTEL contains // kSPIRVName::SampledImage as a substring, but we still want to return in // this case. if (Demangled.find(kSPIRVName::SampledImage) == std::string::npos || Demangled.find(kSPIRVName::ConvertHandleToSampledImageINTEL) != std::string::npos) return; if (FT->getParamType(1)->isIntegerTy()) ChangedType[1] = getSPIRVType(OpTypeSampler, true); } SPIRVValue *LLVMToSPIRVBase::transBuiltinToConstant(StringRef DemangledName, CallInst *CI) { Op OC = getSPIRVFuncOC(DemangledName); if (!isSpecConstantOpCode(OC)) return nullptr; if (OC == spv::OpSpecConstantComposite) { return BM->addSpecConstantComposite(transType(CI->getType()), transValue(getArguments(CI), nullptr)); } Value *V = CI->getArgOperand(1); Type *Ty = CI->getType(); assert(((Ty == V->getType()) || // If bool is stored into memory, then clang will emit it as i8, // however for other usages of bool (like return type of a function), // it is emitted as i1. // Therefore, situation when we encounter // i1 _Z20__spirv_SpecConstant(i32, i8) is valid (Ty->isIntegerTy(1) && V->getType()->isIntegerTy(8))) && "Type mismatch!"); uint64_t Val = 0; if (Ty->isIntegerTy()) Val = cast(V)->getZExtValue(); else if (Ty->isFloatingPointTy()) Val = cast(V)->getValueAPF().bitcastToAPInt().getZExtValue(); else return nullptr; SPIRVValue *SC = BM->addSpecConstant(transType(Ty), Val); return SC; } SPIRVInstruction *LLVMToSPIRVBase::transBuiltinToInst(StringRef DemangledName, CallInst *CI, SPIRVBasicBlock *BB) { SmallVector Dec; auto OC = getSPIRVFuncOC(DemangledName, &Dec); if (OC == OpNop) return nullptr; if (OpReadPipeBlockingINTEL <= OC && OC <= OpWritePipeBlockingINTEL && !BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_blocking_pipes)) return nullptr; if (OpFixedSqrtINTEL <= OC && OC <= OpFixedExpINTEL) BM->getErrorLog().checkError( BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_arbitrary_precision_fixed_point), SPIRVEC_InvalidInstruction, CI->getCalledOperand()->getName().str() + "\nFixed point instructions can't be translated correctly without " "enabled SPV_INTEL_arbitrary_precision_fixed_point extension!\n"); if ((OpArbitraryFloatSinCosPiINTEL <= OC && OC <= OpArbitraryFloatCastToIntINTEL) || (OpArbitraryFloatAddINTEL <= OC && OC <= OpArbitraryFloatPowNINTEL)) BM->getErrorLog().checkError( BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_arbitrary_precision_floating_point), SPIRVEC_InvalidInstruction, CI->getCalledOperand()->getName().str() + "\nFloating point instructions can't be translated correctly " "without enabled SPV_INTEL_arbitrary_precision_floating_point " "extension!\n"); auto *Inst = transBuiltinToInstWithoutDecoration(OC, CI, BB); addDecorations(Inst, Dec); return Inst; } bool LLVMToSPIRVBase::transExecutionMode() { if (auto NMD = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::ExecutionMode)) { while (!NMD.atEnd()) { unsigned EMode = ~0U; Function *F = nullptr; auto N = NMD.nextOp(); /* execution mode MDNode */ N.get(F).get(EMode); SPIRVFunction *BF = static_cast(getTranslatedValue(F)); assert(BF && "Invalid kernel function"); if (!BF) return false; auto AddSingleArgExecutionMode = [&](ExecutionMode EMode) { uint32_t Arg = ~0u; N.get(Arg); BF->addExecutionMode( BM->add(new SPIRVExecutionMode(OpExecutionMode, BF, EMode, Arg))); }; switch (EMode) { case spv::ExecutionModeContractionOff: BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode)))); break; case spv::ExecutionModeInitializer: case spv::ExecutionModeFinalizer: if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_1)) { BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode)))); } else { getErrorLog().checkError(false, SPIRVEC_Requires1_1, "Initializer/Finalizer Execution Mode"); return false; } break; case spv::ExecutionModeLocalSize: case spv::ExecutionModeLocalSizeHint: { unsigned X = 0, Y = 0, Z = 0; N.get(X).get(Y).get(Z); BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode), X, Y, Z))); } break; case spv::ExecutionModeMaxWorkgroupSizeINTEL: { if (BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_kernel_attributes)) { unsigned X = 0, Y = 0, Z = 0; N.get(X).get(Y).get(Z); BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode), X, Y, Z))); BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes); BM->addCapability(CapabilityKernelAttributesINTEL); } } break; case spv::ExecutionModeNoGlobalOffsetINTEL: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_kernel_attributes)) break; BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode)))); BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes); BM->addCapability(CapabilityKernelAttributesINTEL); } break; case spv::ExecutionModeVecTypeHint: case spv::ExecutionModeSubgroupSize: case spv::ExecutionModeSubgroupsPerWorkgroup: AddSingleArgExecutionMode(static_cast(EMode)); break; case spv::ExecutionModeNumSIMDWorkitemsINTEL: case spv::ExecutionModeSchedulerTargetFmaxMhzINTEL: case spv::ExecutionModeMaxWorkDimINTEL: case spv::ExecutionModeRegisterMapInterfaceINTEL: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_kernel_attributes)) break; AddSingleArgExecutionMode(static_cast(EMode)); BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes); BM->addCapability(CapabilityFPGAKernelAttributesINTEL); // RegisterMapInterfaceINTEL mode is defined by the // CapabilityFPGAKernelAttributesv2INTEL capability and that // capability implicitly defines CapabilityFPGAKernelAttributesINTEL if (EMode == spv::ExecutionModeRegisterMapInterfaceINTEL) BM->addCapability(CapabilityFPGAKernelAttributesv2INTEL); } break; case spv::ExecutionModeStreamingInterfaceINTEL: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_kernel_attributes)) break; AddSingleArgExecutionMode(static_cast(EMode)); BM->addExtension(ExtensionID::SPV_INTEL_kernel_attributes); BM->addCapability(CapabilityFPGAKernelAttributesINTEL); } break; case spv::ExecutionModeSharedLocalMemorySizeINTEL: { if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) break; AddSingleArgExecutionMode(static_cast(EMode)); } break; case spv::ExecutionModeNamedBarrierCountINTEL: { if (!BM->isAllowedToUseExtension(ExtensionID::SPV_INTEL_vector_compute)) break; unsigned NBarrierCnt = 0; N.get(NBarrierCnt); BF->addExecutionMode(BM->add(new SPIRVExecutionMode( OpExecutionMode, BF, static_cast(EMode), NBarrierCnt))); BM->addExtension(ExtensionID::SPV_INTEL_vector_compute); BM->addCapability(CapabilityVectorComputeINTEL); } break; case spv::ExecutionModeDenormPreserve: case spv::ExecutionModeDenormFlushToZero: case spv::ExecutionModeSignedZeroInfNanPreserve: case spv::ExecutionModeRoundingModeRTE: case spv::ExecutionModeRoundingModeRTZ: { if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) { BM->setMinSPIRVVersion(VersionNumber::SPIRV_1_4); AddSingleArgExecutionMode(static_cast(EMode)); } else if (BM->isAllowedToUseExtension( ExtensionID::SPV_KHR_float_controls)) { BM->addExtension(ExtensionID::SPV_KHR_float_controls); AddSingleArgExecutionMode(static_cast(EMode)); } } break; case spv::ExecutionModeRoundingModeRTPINTEL: case spv::ExecutionModeRoundingModeRTNINTEL: case spv::ExecutionModeFloatingPointModeALTINTEL: case spv::ExecutionModeFloatingPointModeIEEEINTEL: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_float_controls2)) break; AddSingleArgExecutionMode(static_cast(EMode)); } break; case spv::internal::ExecutionModeNamedSubgroupSizeINTEL: { if (!BM->isAllowedToUseExtension( ExtensionID::SPV_INTEL_subgroup_requirements)) break; AddSingleArgExecutionMode(static_cast(EMode)); } break; default: llvm_unreachable("invalid execution mode"); } } } transFPContract(); return true; } void LLVMToSPIRVBase::transFPContract() { FPContractMode Mode = BM->getFPContractMode(); for (Function &F : *M) { SPIRVValue *TranslatedF = getTranslatedValue(&F); if (!TranslatedF) { continue; } SPIRVFunction *BF = static_cast(TranslatedF); bool IsKernelEntryPoint = BF->getModule()->isEntryPoint(spv::ExecutionModelKernel, BF->getId()); if (!IsKernelEntryPoint) continue; FPContract FPC = getFPContract(&F); assert(FPC != FPContract::UNDEF); bool DisableContraction = false; switch (Mode) { case FPContractMode::Fast: DisableContraction = false; break; case FPContractMode::On: DisableContraction = FPC == FPContract::DISABLED; break; case FPContractMode::Off: DisableContraction = true; break; } if (DisableContraction) { BF->addExecutionMode(BF->getModule()->add(new SPIRVExecutionMode( OpExecutionMode, BF, spv::ExecutionModeContractionOff))); } } } bool LLVMToSPIRVBase::transMetadata() { if (!transOCLMetadata()) return false; auto Model = getMemoryModel(*M); if (Model != SPIRVMemoryModelKind::MemoryModelMax) BM->setMemoryModel(static_cast(Model)); return true; } // Work around to translate kernel_arg_type and kernel_arg_type_qual metadata static void transKernelArgTypeMD(SPIRVModule *BM, Function *F, MDNode *MD, std::string MDName) { std::string Prefix = kSPIRVName::EntrypointPrefix; std::string Name = F->getName().str().substr(Prefix.size()); std::string KernelArgTypesMDStr = std::string(MDName) + "." + Name + "."; for (const auto &TyOp : MD->operands()) KernelArgTypesMDStr += cast(TyOp)->getString().str() + ","; BM->getString(KernelArgTypesMDStr); } bool LLVMToSPIRVBase::transOCLMetadata() { for (auto &F : *M) { if (F.getCallingConv() != CallingConv::SPIR_KERNEL) continue; SPIRVFunction *BF = static_cast(getTranslatedValue(&F)); assert(BF && "Kernel function should be translated first"); // Create 'OpString' as a workaround to store information about // *orignal* (typedef'ed, unsigned integers) type names of kernel arguments. // OpString "kernel_arg_type.%kernel_name%.typename0,typename1,..." if (auto *KernelArgType = F.getMetadata(SPIR_MD_KERNEL_ARG_TYPE)) if (BM->shouldPreserveOCLKernelArgTypeMetadataThroughString()) transKernelArgTypeMD(BM, &F, KernelArgType, SPIR_MD_KERNEL_ARG_TYPE); if (auto *KernelArgTypeQual = F.getMetadata(SPIR_MD_KERNEL_ARG_TYPE_QUAL)) { foreachKernelArgMD( KernelArgTypeQual, BF, [](const std::string &Str, SPIRVFunctionParameter *BA) { if (Str.find("volatile") != std::string::npos) BA->addDecorate(new SPIRVDecorate(DecorationVolatile, BA)); if (Str.find("restrict") != std::string::npos) BA->addDecorate( new SPIRVDecorate(DecorationFuncParamAttr, BA, FunctionParameterAttributeNoAlias)); }); // Create 'OpString' as a workaround to store information about // constant qualifiers of pointer kernel arguments. Store empty string // for a non constant parameter. // OpString "kernel_arg_type_qual.%kernel_name%.qual0,qual1,..." if (BM->shouldPreserveOCLKernelArgTypeMetadataThroughString()) transKernelArgTypeMD(BM, &F, KernelArgTypeQual, SPIR_MD_KERNEL_ARG_TYPE_QUAL); } if (auto *KernelArgName = F.getMetadata(SPIR_MD_KERNEL_ARG_NAME)) { foreachKernelArgMD( KernelArgName, BF, [=](const std::string &Str, SPIRVFunctionParameter *BA) { BM->setName(BA, Str); }); } if (auto *KernArgDecoMD = F.getMetadata(SPIRV_MD_PARAMETER_DECORATIONS)) foreachKernelArgMD(KernArgDecoMD, BF, transMetadataDecorations); } return true; } bool LLVMToSPIRVBase::transSourceLanguage() { auto Src = getSPIRVSource(M); SrcLang = std::get<0>(Src); SrcLangVer = std::get<1>(Src); BM->setSourceLanguage(static_cast(SrcLang), SrcLangVer); return true; } bool LLVMToSPIRVBase::transExtension() { if (auto N = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::Extension)) { while (!N.atEnd()) { std::string S; N.nextOp().get(S); assert(!S.empty() && "Invalid extension"); ExtensionID ExtID = SPIRVMap::rmap(S); if (!BM->getErrorLog().checkError(BM->isAllowedToUseExtension(ExtID), SPIRVEC_RequiresExtension, S)) return false; BM->getExtension().insert(S); } } if (auto N = SPIRVMDWalker(*M).getNamedMD(kSPIRVMD::SourceExtension)) { while (!N.atEnd()) { std::string S; N.nextOp().get(S); assert(!S.empty() && "Invalid extension"); BM->getSourceExtension().insert(S); } } for (auto &I : map(rmap(BM->getExtension()))) BM->addCapability(I); return true; } void LLVMToSPIRVBase::dumpUsers(Value *V) { SPIRVDBG(dbgs() << "Users of " << *V << " :\n"); for (auto UI = V->user_begin(), UE = V->user_end(); UI != UE; ++UI) SPIRVDBG(dbgs() << " " << **UI << '\n'); } Op LLVMToSPIRVBase::transBoolOpCode(SPIRVValue *Opn, Op OC) { if (!Opn->getType()->isTypeVectorOrScalarBool()) return OC; IntBoolOpMap::find(OC, &OC); return OC; } SPIRVInstruction * LLVMToSPIRVBase::transBuiltinToInstWithoutDecoration(Op OC, CallInst *CI, SPIRVBasicBlock *BB) { // OpAtomicCompareExchangeWeak is not "weak" at all, // but instead has the same semantics as OpAtomicCompareExchange. // Moreover, OpAtomicCompareExchangeWeak has been deprecated. if (OC == OpAtomicCompareExchangeWeak) OC = OpAtomicCompareExchange; // We should do this replacement only for SPIR-V 1.5, as OpLessOrGreater is // deprecated there. However we do such replacement for the usual pipeline // (not via SPIR-V friendly calls) without minding the version, so we can do // such thing here as well. if (OC == OpLessOrGreater && BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_5)) OC = OpFOrdNotEqual; if (isGroupOpCode(OC)) BM->addCapability(CapabilityGroups); switch (static_cast(OC)) { case OpControlBarrier: { auto BArgs = transValue(getArguments(CI), BB); return BM->addControlBarrierInst(BArgs[0], BArgs[1], BArgs[2], BB); } break; case OpGroupAsyncCopy: { auto BArgs = transValue(getArguments(CI), BB); return BM->addAsyncGroupCopy(BArgs[0], BArgs[1], BArgs[2], BArgs[3], BArgs[4], BArgs[5], BB); } break; case OpVectorExtractDynamic: { auto BArgs = transValue(getArguments(CI), BB); return BM->addVectorExtractDynamicInst(BArgs[0], BArgs[1], BB); } break; case OpVectorInsertDynamic: { auto BArgs = transValue(getArguments(CI), BB); return BM->addVectorInsertDynamicInst(BArgs[0], BArgs[1], BArgs[2], BB); } break; case OpSampledImage: { // Clang can generate SPIRV-friendly call for OpSampledImage instruction, // i.e. __spirv_SampledImage... But it can't generate correct return type // for this call, because there is no support for type corresponding to // OpTypeSampledImage. So, in this case, we create the required type here. Value *Image = CI->getArgOperand(0); Type *SampledImgTy = adjustImageType(getCallValueType(CI, 0), kSPIRVTypeName::Image, kSPIRVTypeName::SampledImg); Value *Sampler = CI->getArgOperand(1); return BM->addSampledImageInst(transType(SampledImgTy), transValue(Image, BB), transValue(Sampler, BB), BB); } case OpImageRead: case OpImageSampleExplicitLod: case OpImageWrite: { // Image Op needs handling of SignExtend or ZeroExtend image operands. auto Args = getArguments(CI); SPIRVType *SPRetTy = CI->getType()->isVoidTy() ? nullptr : transScavengedType(CI); auto *SPI = SPIRVInstTemplateBase::create(OC); std::vector SPArgs; for (size_t I = 0, E = Args.size(); I != E; ++I) { if (Args[I]->getType()->isPointerTy()) { [[maybe_unused]] Value *Pointee = Args[I]->stripPointerCasts(); assert((Pointee == Args[I] || !isa(Pointee)) && "Illegal use of a function pointer type"); } SPArgs.push_back(SPI->isOperandLiteral(I) ? cast(Args[I])->getZExtValue() : transValue(Args[I], BB)->getId()); } if (BM->isAllowedToUseVersion(VersionNumber::SPIRV_1_4)) { size_t ImOpIdx = getImageOperandsIndex(OC); if (Args.size() > ImOpIdx) { // Update existing ImageOperands with SignExtendMask/ZeroExtendMask. if (auto *ImOp = dyn_cast(Args[ImOpIdx])) { uint64_t ImOpVal = ImOp->getZExtValue(); unsigned SignZeroExtMasks = ImageOperandsMask::ImageOperandsSignExtendMask | ImageOperandsMask::ImageOperandsZeroExtendMask; if (!(ImOpVal & SignZeroExtMasks)) if (int SignZeroExt = getImageSignZeroExt(CI->getCalledFunction())) SPArgs[ImOpIdx] = ImOpVal | SignZeroExt; } } else { // Add new ImageOperands argument. if (int SignZeroExt = getImageSignZeroExt(CI->getCalledFunction())) SPArgs.push_back(SignZeroExt); } } BM->addInstTemplate(SPI, SPArgs, BB, SPRetTy); return SPI; } case OpFixedSqrtINTEL: case OpFixedRecipINTEL: case OpFixedRsqrtINTEL: case OpFixedSinINTEL: case OpFixedCosINTEL: case OpFixedSinCosINTEL: case OpFixedSinPiINTEL: case OpFixedCosPiINTEL: case OpFixedSinCosPiINTEL: case OpFixedLogINTEL: case OpFixedExpINTEL: { // 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) // where A - integer input of any width. // SPIR-V fixed point instruction contains: // ResTy Res In \ // Literal S Literal I Literal rI Literal Q Literal O Type *ResTy = CI->getType(); auto OpItr = CI->value_op_begin(); auto OpEnd = OpItr + CI->arg_size(); // If the return type of an instruction is wider than 64-bit, then this // instruction will return via 'sret' argument added into the arguments // list. Here we reverse this, removing 'sret' argument and restoring // the original return type. if (CI->hasStructRetAttr()) { assert(ResTy->isVoidTy() && "Return type is not void"); ResTy = CI->getParamStructRetType(0); OpItr++; } // Input - integer input of any width or 'byval' pointer to this integer SPIRVValue *Input = transValue(*OpItr, BB); SPIRVType *LoadTy = nullptr; if (Input->isUntypedVariable()) LoadTy = static_cast(Input)->getDataType(); if (OpItr->getType()->isPointerTy()) Input = BM->addLoadInst(Input, {}, BB, LoadTy); OpItr++; std::vector Literals; std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) { return cast(O)->getZExtValue(); }); auto *APIntInst = BM->addFixedPointIntelInst(OC, transType(ResTy), Input, Literals, BB); if (!CI->hasStructRetAttr()) return APIntInst; return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {}, 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: { // Format of instruction CastFromInt: // LLVM arbitrary floating point functions return value type: // iN (arbitrary precision integer of N bits length) // 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 *ResTy = CI->getType(); auto OpItr = CI->value_op_begin(); auto OpEnd = OpItr + CI->arg_size(); // If the return type of an instruction is wider than 64-bit, then this // instruction will return via 'sret' argument added into the arguments // list. Here we reverse this, removing 'sret' argument and restoring // the original return type. if (CI->hasStructRetAttr()) { assert(ResTy->isVoidTy() && "Return type is not void"); ResTy = CI->getParamStructRetType(0); OpItr++; } // Input - integer input of any width or 'byval' pointer to this integer SPIRVValue *Input = transValue(*OpItr, BB); SPIRVType *LoadTy = nullptr; if (Input->isUntypedVariable()) LoadTy = static_cast(Input)->getDataType(); if (OpItr->getType()->isPointerTy()) Input = BM->addLoadInst(Input, {}, BB, LoadTy); OpItr++; std::vector Literals; std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) { return cast(O)->getZExtValue(); }); auto *APIntInst = BM->addArbFloatPointIntelInst(OC, transType(ResTy), Input, nullptr, Literals, BB); if (!CI->hasStructRetAttr()) return APIntInst; return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {}, 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: { // 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 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 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 Type *ResTy = CI->getType(); auto OpItr = CI->value_op_begin(); auto OpEnd = OpItr + CI->arg_size(); // If the return type of an instruction is wider than 64-bit, then this // instruction will return via 'sret' argument added into the arguments // list. Here we reverse this, removing 'sret' argument and restoring // the original return type. if (CI->hasStructRetAttr()) { assert(ResTy->isVoidTy() && "Return type is not void"); ResTy = CI->getParamStructRetType(0); OpItr++; } // InA - integer input of any width or 'byval' pointer to this integer SPIRVValue *InA = transValue(*OpItr, BB); SPIRVType *LoadTy = nullptr; if (InA->isUntypedVariable()) LoadTy = static_cast(InA)->getDataType(); if (OpItr->getType()->isPointerTy()) InA = BM->addLoadInst(InA, {}, BB, LoadTy); OpItr++; std::vector Literals; Literals.push_back(cast(*OpItr++)->getZExtValue()); // InB - integer input of any width or 'byval' pointer to this integer SPIRVValue *InB = transValue(*OpItr, BB); LoadTy = nullptr; if (InB->isUntypedVariable()) LoadTy = static_cast(InB)->getDataType(); if (OpItr->getType()->isPointerTy()) { std::vector Mem; InB = BM->addLoadInst(InB, Mem, BB, LoadTy); } OpItr++; std::transform(OpItr, OpEnd, std::back_inserter(Literals), [](auto *O) { return cast(O)->getZExtValue(); }); auto *APIntInst = BM->addArbFloatPointIntelInst(OC, transType(ResTy), InA, InB, Literals, BB); if (!CI->hasStructRetAttr()) return APIntInst; return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), APIntInst, {}, BB); } case internal::OpTaskSequenceGetINTEL: { Type *ResTy = nullptr; auto OpItr = CI->value_op_begin(); if (CI->hasStructRetAttr()) { assert(CI->getType()->isVoidTy() && "Return type is not void"); ResTy = CI->getParamStructRetType(0); OpItr++; } SPIRVType *RetTy = ResTy ? transType(ResTy) : transScavengedType(CI); auto *TaskSeqGet = BM->addTaskSequenceGetINTELInst(RetTy, transValue(*OpItr++, BB), BB); if (!CI->hasStructRetAttr()) return TaskSeqGet; return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), TaskSeqGet, {}, BB); } case OpLoad: { std::vector MemoryAccess; assert(CI->arg_size() > 0 && "Expected at least 1 operand for OpLoad call"); for (size_t I = 1; I < CI->arg_size(); ++I) MemoryAccess.push_back( cast(CI->getArgOperand(I))->getZExtValue()); return BM->addLoadInst(transValue(CI->getArgOperand(0), BB), MemoryAccess, BB); } case OpStore: { std::vector MemoryAccess; assert(CI->arg_size() > 1 && "Expected at least 2 operands for OpStore call"); for (size_t I = 2; I < CI->arg_size(); ++I) MemoryAccess.push_back( cast(CI->getArgOperand(I))->getZExtValue()); return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), transValue(CI->getArgOperand(1), BB), MemoryAccess, BB); } case OpCompositeConstruct: { std::vector Operands = { transValue(CI->getArgOperand(0), BB)->getId()}; return BM->addCompositeConstructInst(transType(CI->getType()), Operands, BB); } case OpIAddCarry: case OpISubBorrow: case OpUMulExtended: case OpSMulExtended: { Function *F = CI->getCalledFunction(); StructType *St = cast(F->getParamStructRetType(0)); SPIRVValue *V = BM->addBinaryInst(OC, transType(St), transValue(CI->getArgOperand(1), BB), transValue(CI->getArgOperand(2), BB), BB); return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), V, {}, BB); } case OpCooperativeMatrixLengthKHR: { return BM->addCooperativeMatrixLengthKHRInst( transScavengedType(CI), transType(CI->getArgOperand(0)->getType()), BB); } case OpGroupNonUniformShuffle: case OpGroupNonUniformShuffleDown: case OpGroupNonUniformShuffleUp: case OpGroupNonUniformShuffleXor: { Function *F = CI->getCalledFunction(); if (F->arg_size() && F->getArg(0)->hasStructRetAttr()) { StructType *St = cast(F->getParamStructRetType(0)); assert(isSYCLHalfType(St) || isSYCLBfloat16Type(St)); SPIRVValue *InValue = transValue(CI->getArgOperand(0)->stripPointerCasts(), BB); SPIRVId ScopeId = transValue(CI->getArgOperand(1), BB)->getId(); SPIRVValue *Delta = transValue(CI->getArgOperand(3), BB); SPIRVType *LoadTy = nullptr; if (InValue->isUntypedVariable()) LoadTy = static_cast(InValue)->getDataType(); SPIRVValue *Composite0 = BM->addLoadInst(InValue, {}, BB, LoadTy); Type *MemberTy = St->getElementType(0); SPIRVType *ElementTy = transType(MemberTy); SPIRVValue *Element0 = BM->addCompositeExtractInst(ElementTy, Composite0, {0}, BB); SPIRVValue *Src = BM->addGroupInst( OC, ElementTy, static_cast(ScopeId), {Element0, Delta}, BB); SPIRVValue *Composite1 = BM->addCompositeInsertInst(Src, Composite0, {0}, BB); return BM->addStoreInst(InValue, Composite1, {}, BB); } [[fallthrough]]; } default: { if (isCvtOpCode(OC) && OC != OpGenericCastToPtrExplicit) { return BM->addUnaryInst(OC, transScavengedType(CI), transValue(CI->getArgOperand(0), BB), BB); } else if (isCmpOpCode(OC) || isUnaryPredicateOpCode(OC)) { auto *ResultTy = CI->getType(); Type *BoolTy = IntegerType::getInt1Ty(M->getContext()); auto IsVector = ResultTy->isVectorTy(); if (IsVector) BoolTy = FixedVectorType::get( BoolTy, cast(ResultTy)->getNumElements()); auto *BBT = transType(BoolTy); SPIRVInstruction *Res; if (isCmpOpCode(OC)) { assert(CI && CI->arg_size() == 2 && "Invalid call inst"); Res = BM->addCmpInst(OC, BBT, transValue(CI->getArgOperand(0), BB), transValue(CI->getArgOperand(1), BB), BB); } else { assert(CI && CI->arg_size() == 1 && "Invalid call inst"); Res = BM->addUnaryInst(OC, BBT, transValue(CI->getArgOperand(0), BB), BB); } // OpenCL C and OpenCL C++ built-ins may have different return type if (ResultTy == BoolTy) return Res; assert(IsVector || (!IsVector && ResultTy->isIntegerTy(32))); auto *Zero = transValue(Constant::getNullValue(ResultTy), BB); auto *One = transValue( IsVector ? Constant::getAllOnesValue(ResultTy) : getInt32(M, 1), BB); return BM->addSelectInst(Res, One, Zero, BB); } else if (isBinaryOpCode(OC)) { assert(CI && CI->arg_size() == 2 && "Invalid call inst"); return BM->addBinaryInst(OC, transScavengedType(CI), transValue(CI->getArgOperand(0), BB), transValue(CI->getArgOperand(1), BB), BB); } else if (CI->arg_size() == 1 && !CI->getType()->isVoidTy() && !hasExecScope(OC) && !isAtomicOpCode(OC)) { return BM->addUnaryInst(OC, transScavengedType(CI), transValue(CI->getArgOperand(0), BB), BB); } else { auto Args = getArguments(CI); SPIRVType *SPRetTy = nullptr; Type *RetTy = CI->getType(); auto *F = CI->getCalledFunction(); if (!RetTy->isVoidTy()) { SPRetTy = transScavengedType(CI); } else if (Args.size() > 0 && F->arg_begin()->hasStructRetAttr()) { SPRetTy = transType(F->getParamStructRetType(0)); Args.erase(Args.begin()); } if (RetTy->isPointerTy() && BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_untyped_pointers)) { if (OC == OpAccessChain) OC = OpUntypedAccessChainKHR; else if (OC == OpInBoundsAccessChain) OC = OpUntypedInBoundsAccessChainKHR; else if (OC == OpPtrAccessChain) OC = OpUntypedPtrAccessChainKHR; else if (OC == OpInBoundsPtrAccessChain) OC = OpUntypedInBoundsPtrAccessChainKHR; } auto *SPI = SPIRVInstTemplateBase::create(OC); std::vector SPArgs; if (isUntypedAccessChainOpCode(OC)) { // Untyped access chain instructions have an additional argument BaseTy. Type *Ty = Scavenger->getScavengedType(Args[0]); SPIRVType *PtrTy = nullptr; if (auto *TPT = dyn_cast(Ty)) { PtrTy = transType(TPT->getElementType()); SPArgs.push_back(PtrTy->getId()); } } for (size_t I = 0, E = Args.size(); I != E; ++I) { if (Args[I]->getType()->isPointerTy()) { Value *Pointee = Args[I]->stripPointerCasts(); (void)Pointee; assert((Pointee == Args[I] || !isa(Pointee)) && "Illegal use of a function pointer type"); } if (!SPI->isOperandLiteral(I)) { SPIRVValue *Val = transValue(Args[I], BB); SPArgs.push_back(Val->getId()); } else { SPArgs.push_back(cast(Args[I])->getZExtValue()); } } BM->addInstTemplate(SPI, SPArgs, BB, SPRetTy); if (!SPRetTy || !SPRetTy->isTypeStruct()) return SPI; std::vector Mem; SPIRVDBG(spvdbgs() << *SPI << '\n'); return BM->addStoreInst(transValue(CI->getArgOperand(0), BB), SPI, Mem, BB); } } } return nullptr; } SPIRV::SPIRVLinkageTypeKind LLVMToSPIRVBase::transLinkageType(const GlobalValue *GV) { if (GV->isDeclarationForLinker()) return SPIRVLinkageTypeKind::LinkageTypeImport; if (GV->hasInternalLinkage() || GV->hasPrivateLinkage()) return spv::internal::LinkageTypeInternal; if (GV->hasLinkOnceODRLinkage()) if (BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_linkonce_odr)) return SPIRVLinkageTypeKind::LinkageTypeLinkOnceODR; return SPIRVLinkageTypeKind::LinkageTypeExport; } LLVMToSPIRVBase::FPContract LLVMToSPIRVBase::getFPContract(Function *F) { auto It = FPContractMap.find(F); if (It == FPContractMap.end()) { return FPContract::UNDEF; } return It->second; } bool LLVMToSPIRVBase::joinFPContract(Function *F, FPContract C) { FPContract &Existing = FPContractMap[F]; switch (Existing) { case FPContract::UNDEF: if (C != FPContract::UNDEF) { Existing = C; return true; } return false; case FPContract::ENABLED: if (C == FPContract::DISABLED) { Existing = C; return true; } return false; case FPContract::DISABLED: return false; } llvm_unreachable("Unhandled FPContract value."); } } // namespace SPIRV char LLVMToSPIRVLegacy::ID = 0; INITIALIZE_PASS_BEGIN(LLVMToSPIRVLegacy, "llvmtospv", "Translate LLVM to SPIR-V", false, false) INITIALIZE_PASS_DEPENDENCY(OCLTypeToSPIRVLegacy) INITIALIZE_PASS_END(LLVMToSPIRVLegacy, "llvmtospv", "Translate LLVM to SPIR-V", false, false) ModulePass *llvm::createLLVMToSPIRVLegacy(SPIRVModule *SMod) { return new LLVMToSPIRVLegacy(SMod); } void addPassesForSPIRV(ModulePassManager &PassMgr, const SPIRV::TranslatorOpts &Opts) { if (Opts.isSPIRVMemToRegEnabled()) PassMgr.addPass(createModuleToFunctionPassAdaptor(PromotePass())); PassMgr.addPass(PreprocessMetadataPass()); PassMgr.addPass(SPIRVLowerOCLBlocksPass()); PassMgr.addPass(OCLToSPIRVPass()); PassMgr.addPass(SPIRVRegularizeLLVMPass()); PassMgr.addPass(SPIRVLowerConstExprPass()); PassMgr.addPass(SPIRVLowerBoolPass()); PassMgr.addPass(SPIRVLowerMemmovePass()); PassMgr.addPass(SPIRVLowerLLVMIntrinsicPass(Opts)); PassMgr.addPass(createModuleToFunctionPassAdaptor( SPIRVLowerBitCastToNonStandardTypePass(Opts))); } bool isValidLLVMModule(Module *M, SPIRVErrorLog &ErrorLog) { if (!M) return false; if (isEmptyLLVMModule(M)) return true; Triple TT(M->getTargetTriple()); if (!ErrorLog.checkError(isSupportedTriple(TT), SPIRVEC_InvalidTargetTriple, "Actual target triple is " + M->getTargetTriple().str())) return false; return true; } #if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT) namespace llvm { extern "C" bool SPIRVTranslateModule(Module *M, std::string &SpirvObj, std::string &ErrMsg, const std::vector &AllowExtNames, const std::vector &Opts); } // namespace llvm #if defined(_SPIRV_SUPPORT_TEXT_FMT) namespace SPIRV { bool convertSpirv(std::istream &IS, std::ostream &OS, std::string &ErrMsg, bool FromText, bool ToText); } // namespace SPIRV #endif // _SPIRV_SUPPORT_TEXT_FMT #endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT namespace { #if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT) static inline Triple::SubArchType spirvVersionToSubArch(VersionNumber VN) { switch (VN) { case VersionNumber::SPIRV_1_0: return Triple::SPIRVSubArch_v10; case VersionNumber::SPIRV_1_1: return Triple::SPIRVSubArch_v11; case VersionNumber::SPIRV_1_2: return Triple::SPIRVSubArch_v12; case VersionNumber::SPIRV_1_3: return Triple::SPIRVSubArch_v13; case VersionNumber::SPIRV_1_4: return Triple::SPIRVSubArch_v14; case VersionNumber::SPIRV_1_5: return Triple::SPIRVSubArch_v15; case VersionNumber::SPIRV_1_6: return Triple::SPIRVSubArch_v16; } return Triple::NoSubArch; } // TODO: // - consider exposing user facing options that are to set explicitly a Triple // value and an optimization level for the LLVM SPIR-V Backend // - switch to the new API call: see // https://github.com/llvm/llvm-project/pull/124745 bool runSpirvBackend(Module *M, std::string &Result, std::string &ErrMsg, const SPIRV::TranslatorOpts &TranslatorOpts) { // A list of known extensions supported by SPIR-V Backend: it's to be updated // each time when SPIR-V Backend introduces support for a new extension. static const std::set ImplementedBySpirvBE{ SPIRV::ExtensionID::SPV_EXT_shader_atomic_float_add, SPIRV::ExtensionID::SPV_EXT_shader_atomic_float16_add, SPIRV::ExtensionID::SPV_EXT_shader_atomic_float_min_max, SPIRV::ExtensionID::SPV_EXT_arithmetic_fence, SPIRV::ExtensionID::SPV_INTEL_arbitrary_precision_integers, SPIRV::ExtensionID::SPV_INTEL_cache_controls, SPIRV::ExtensionID::SPV_INTEL_float_controls2, SPIRV::ExtensionID::SPV_INTEL_global_variable_fpga_decorations, SPIRV::ExtensionID::SPV_INTEL_global_variable_host_access, SPIRV::ExtensionID::SPV_EXT_optnone, SPIRV::ExtensionID::SPV_INTEL_optnone, SPIRV::ExtensionID::SPV_INTEL_usm_storage_classes, SPIRV::ExtensionID::SPV_INTEL_split_barrier, SPIRV::ExtensionID::SPV_INTEL_subgroups, SPIRV::ExtensionID::SPV_INTEL_media_block_io, SPIRV::ExtensionID::SPV_INTEL_joint_matrix, SPIRV::ExtensionID::SPV_KHR_uniform_group_instructions, SPIRV::ExtensionID::SPV_KHR_no_integer_wrap_decoration, SPIRV::ExtensionID::SPV_KHR_float_controls, SPIRV::ExtensionID::SPV_KHR_expect_assume, SPIRV::ExtensionID::SPV_KHR_bit_instructions, SPIRV::ExtensionID::SPV_KHR_linkonce_odr, SPIRV::ExtensionID::SPV_INTEL_inline_assembly, SPIRV::ExtensionID::SPV_INTEL_bfloat16_conversion, SPIRV::ExtensionID::SPV_KHR_subgroup_rotate, SPIRV::ExtensionID::SPV_INTEL_variable_length_array, SPIRV::ExtensionID::SPV_INTEL_function_pointers, SPIRV::ExtensionID::SPV_KHR_shader_clock, SPIRV::ExtensionID::SPV_KHR_cooperative_matrix, SPIRV::ExtensionID::SPV_KHR_non_semantic_info}; // The fallback for the Triple value. static const std::string DefaultTriple = "spirv64-unknown-unknown"; std::function Filter = [](SPIRV::ExtensionID Ext) -> bool { return ImplementedBySpirvBE.find(Ext) != ImplementedBySpirvBE.end(); }; const std::vector AllowExtNames = TranslatorOpts.getAllowedSPIRVExtensionNames(Filter); // Correct the Triple value if needed Triple TargetTriple(M->getTargetTriple()); if (TargetTriple.isSPIR()) { TargetTriple.setArch(TargetTriple.getArch() == Triple::spir64 ? Triple::spirv64 : Triple::spirv32, TargetTriple.getSubArch()); M->setTargetTriple(TargetTriple); // We need to reset Data Layout to conform with the TargetMachine M->setDataLayout(""); } if (TranslatorOpts.getMaxVersion() != VersionNumber::MaximumVersion) { if (TargetTriple.getTriple().empty()) TargetTriple.setTriple(DefaultTriple); TargetTriple.setArch(TargetTriple.getArch(), spirvVersionToSubArch(TranslatorOpts.getMaxVersion())); M->setTargetTriple(TargetTriple); } // Translate the Module into SPIR-V return llvm::SPIRVTranslateModule(M, Result, ErrMsg, AllowExtNames, {}); } bool runSpirvBackend(Module *M, std::ostream *OS, std::string &ErrMsg, const SPIRV::TranslatorOpts &TranslatorOpts) { std::string Result; bool Status = runSpirvBackend(M, Result, ErrMsg, TranslatorOpts); if (Status && OS) *OS << Result; return Status; } #endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT VersionNumber getVersionFromTriple(const Triple &TT, SPIRVErrorLog &ErrorLog) { switch (TT.getSubArch()) { case Triple::SPIRVSubArch_v10: return VersionNumber::SPIRV_1_0; case Triple::SPIRVSubArch_v11: return VersionNumber::SPIRV_1_1; case Triple::SPIRVSubArch_v12: return VersionNumber::SPIRV_1_2; case Triple::SPIRVSubArch_v13: return VersionNumber::SPIRV_1_3; case Triple::SPIRVSubArch_v14: return VersionNumber::SPIRV_1_4; case Triple::SPIRVSubArch_v15: return VersionNumber::SPIRV_1_5; case Triple::SPIRVSubArch_v16: return VersionNumber::SPIRV_1_6; default: ErrorLog.checkError(false, SPIRVEC_InvalidSubArch, TT.getArchName().str()); return VersionNumber::MaximumVersion; } } bool runSpirvWriterPasses(Module *M, std::ostream *OS, std::string &ErrMsg, const SPIRV::TranslatorOpts &Opts) { // Perform the conversion and write the resulting SPIR-V if an ostream has // been given; otherwise only perform regularization. bool WriteSpirv = OS != nullptr; std::unique_ptr BM(SPIRVModule::createSPIRVModule(Opts)); if (!isValidLLVMModule(M, BM->getErrorLog())) return false; // If the module carries a SPIR-V triple with a version subarch, target // that SPIR-V version. Triple TargetTriple(M->getTargetTriple()); if ((TargetTriple.getArch() == Triple::spirv32 || TargetTriple.getArch() == Triple::spirv64) && TargetTriple.getSubArch() != Triple::NoSubArch) { VersionNumber ModuleVer = getVersionFromTriple(TargetTriple, BM->getErrorLog()); if (!BM->getErrorLog().checkError(ModuleVer <= Opts.getMaxVersion(), SPIRVEC_TripleMaxVersionIncompatible)) return false; BM->setMinSPIRVVersion(ModuleVer); BM->setMaxSPIRVVersion(ModuleVer); } ModulePassManager PassMgr; addPassesForSPIRV(PassMgr, Opts); if (WriteSpirv) { // Run loop simplify pass in order to avoid duplicate OpLoopMerge // instruction. It can happen in case of continue operand in the loop. if (hasLoopMetadata(M)) PassMgr.addPass(createModuleToFunctionPassAdaptor(LoopSimplifyPass())); PassMgr.addPass(LLVMToSPIRVPass(BM.get())); } LoopAnalysisManager LAM; CGSCCAnalysisManager CGAM; FunctionAnalysisManager FAM; ModuleAnalysisManager MAM; PassBuilder PB; PB.registerModuleAnalyses(MAM); PB.registerCGSCCAnalyses(CGAM); PB.registerFunctionAnalyses(FAM); PB.registerLoopAnalyses(LAM); MAM.registerPass([&] { return OCLTypeToSPIRVPass(); }); PB.crossRegisterProxies(LAM, FAM, CGAM, MAM); PassMgr.run(*M, MAM); if (BM->getError(ErrMsg) != SPIRVEC_Success) return false; if (WriteSpirv) *OS << *BM; return true; } } // namespace bool llvm::writeSpirv(Module *M, std::ostream &OS, std::string &ErrMsg) { SPIRV::TranslatorOpts DefaultOpts; // To preserve old behavior of the translator, let's enable all extensions // by default in this API DefaultOpts.enableAllExtensions(); return llvm::writeSpirv(M, DefaultOpts, OS, ErrMsg); } bool llvm::writeSpirv(Module *M, const SPIRV::TranslatorOpts &Opts, std::ostream &OS, std::string &ErrMsg) { #if defined(LLVM_SPIRV_BACKEND_TARGET_PRESENT) // Check if a user asks to convert LLVM to SPIR-V using the LLVM SPIR-V // Backend target if (Opts.getUseLLVMTarget()) { #if !defined(_SPIRV_SUPPORT_TEXT_FMT) return runSpirvBackend(M, &OS, ErrMsg, Opts); #else if (!SPIRVUseTextFormat) return runSpirvBackend(M, &OS, ErrMsg, Opts); // SPIR-V Backend always returns a binary: let's use temporary buffers to // store the binary representation and convert it later into Translator's // textual representation std::string BinResult; if (!runSpirvBackend(M, BinResult, ErrMsg, Opts)) return false; std::istringstream IS(BinResult); return SPIRV::convertSpirv(IS, OS, ErrMsg, false /*FromText*/, true /*ToText*/); #endif // _SPIRV_SUPPORT_TEXT_FMT } #endif // LLVM_SPIRV_BACKEND_TARGET_PRESENT return runSpirvWriterPasses(M, &OS, ErrMsg, Opts); } bool llvm::regularizeLlvmForSpirv(Module *M, std::string &ErrMsg) { SPIRV::TranslatorOpts DefaultOpts; // To preserve old behavior of the translator, let's enable all extensions // by default in this API DefaultOpts.enableAllExtensions(); return llvm::regularizeLlvmForSpirv(M, ErrMsg, DefaultOpts); } bool llvm::regularizeLlvmForSpirv(Module *M, std::string &ErrMsg, const SPIRV::TranslatorOpts &Opts) { return runSpirvWriterPasses(M, nullptr, ErrMsg, Opts); }