//===- SPIRVBuiltins.cpp - SPIR-V Built-in Functions ------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file implements lowering builtin function calls and types using their // demangled names and TableGen records. // //===----------------------------------------------------------------------===// #include "SPIRVBuiltins.h" #include "SPIRV.h" #include "SPIRVSubtarget.h" #include "SPIRVUtils.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/IntrinsicsSPIRV.h" #include #include #define DEBUG_TYPE "spirv-builtins" namespace llvm { namespace SPIRV { #define GET_BuiltinGroup_DECL #include "SPIRVGenTables.inc" struct DemangledBuiltin { StringRef Name; InstructionSet::InstructionSet Set; BuiltinGroup Group; uint8_t MinNumArgs; uint8_t MaxNumArgs; }; #define GET_DemangledBuiltins_DECL #define GET_DemangledBuiltins_IMPL struct IncomingCall { const std::string BuiltinName; const DemangledBuiltin *Builtin; const Register ReturnRegister; const SPIRVType *ReturnType; const SmallVectorImpl &Arguments; IncomingCall(const std::string BuiltinName, const DemangledBuiltin *Builtin, const Register ReturnRegister, const SPIRVType *ReturnType, const SmallVectorImpl &Arguments) : BuiltinName(BuiltinName), Builtin(Builtin), ReturnRegister(ReturnRegister), ReturnType(ReturnType), Arguments(Arguments) {} bool isSpirvOp() const { return BuiltinName.rfind("__spirv_", 0) == 0; } }; struct NativeBuiltin { StringRef Name; InstructionSet::InstructionSet Set; uint32_t Opcode; }; #define GET_NativeBuiltins_DECL #define GET_NativeBuiltins_IMPL struct GroupBuiltin { StringRef Name; uint32_t Opcode; uint32_t GroupOperation; bool IsElect; bool IsAllOrAny; bool IsAllEqual; bool IsBallot; bool IsInverseBallot; bool IsBallotBitExtract; bool IsBallotFindBit; bool IsLogical; bool NoGroupOperation; bool HasBoolArg; }; #define GET_GroupBuiltins_DECL #define GET_GroupBuiltins_IMPL struct IntelSubgroupsBuiltin { StringRef Name; uint32_t Opcode; bool IsBlock; bool IsWrite; }; #define GET_IntelSubgroupsBuiltins_DECL #define GET_IntelSubgroupsBuiltins_IMPL struct AtomicFloatingBuiltin { StringRef Name; uint32_t Opcode; }; #define GET_AtomicFloatingBuiltins_DECL #define GET_AtomicFloatingBuiltins_IMPL struct GroupUniformBuiltin { StringRef Name; uint32_t Opcode; bool IsLogical; }; #define GET_GroupUniformBuiltins_DECL #define GET_GroupUniformBuiltins_IMPL struct GetBuiltin { StringRef Name; InstructionSet::InstructionSet Set; BuiltIn::BuiltIn Value; }; using namespace BuiltIn; #define GET_GetBuiltins_DECL #define GET_GetBuiltins_IMPL struct ImageQueryBuiltin { StringRef Name; InstructionSet::InstructionSet Set; uint32_t Component; }; #define GET_ImageQueryBuiltins_DECL #define GET_ImageQueryBuiltins_IMPL struct ConvertBuiltin { StringRef Name; InstructionSet::InstructionSet Set; bool IsDestinationSigned; bool IsSaturated; bool IsRounded; bool IsBfloat16; FPRoundingMode::FPRoundingMode RoundingMode; }; struct VectorLoadStoreBuiltin { StringRef Name; InstructionSet::InstructionSet Set; uint32_t Number; uint32_t ElementCount; bool IsRounded; FPRoundingMode::FPRoundingMode RoundingMode; }; using namespace FPRoundingMode; #define GET_ConvertBuiltins_DECL #define GET_ConvertBuiltins_IMPL using namespace InstructionSet; #define GET_VectorLoadStoreBuiltins_DECL #define GET_VectorLoadStoreBuiltins_IMPL #define GET_CLMemoryScope_DECL #define GET_CLSamplerAddressingMode_DECL #define GET_CLMemoryFenceFlags_DECL #define GET_ExtendedBuiltins_DECL #include "SPIRVGenTables.inc" } // namespace SPIRV //===----------------------------------------------------------------------===// // Misc functions for looking up builtins and veryfying requirements using // TableGen records //===----------------------------------------------------------------------===// namespace SPIRV { /// Parses the name part of the demangled builtin call. std::string lookupBuiltinNameHelper(StringRef DemangledCall) { const static std::string PassPrefix = "(anonymous namespace)::"; std::string BuiltinName; // Itanium Demangler result may have "(anonymous namespace)::" prefix if (DemangledCall.starts_with(PassPrefix.c_str())) BuiltinName = DemangledCall.substr(PassPrefix.length()); else BuiltinName = DemangledCall; // Extract the builtin function name and types of arguments from the call // skeleton. BuiltinName = BuiltinName.substr(0, BuiltinName.find('(')); // Account for possible "__spirv_ocl_" prefix in SPIR-V friendly LLVM IR if (BuiltinName.rfind("__spirv_ocl_", 0) == 0) BuiltinName = BuiltinName.substr(12); // Check if the extracted name contains type information between angle // brackets. If so, the builtin is an instantiated template - needs to have // the information after angle brackets and return type removed. if (BuiltinName.find('<') && BuiltinName.back() == '>') { BuiltinName = BuiltinName.substr(0, BuiltinName.find('<')); BuiltinName = BuiltinName.substr(BuiltinName.find_last_of(' ') + 1); } // Check if the extracted name begins with "__spirv_ImageSampleExplicitLod" // contains return type information at the end "_R", if so extract the // plain builtin name without the type information. if (StringRef(BuiltinName).contains("__spirv_ImageSampleExplicitLod") && StringRef(BuiltinName).contains("_R")) { BuiltinName = BuiltinName.substr(0, BuiltinName.find("_R")); } return BuiltinName; } } // namespace SPIRV /// Looks up the demangled builtin call in the SPIRVBuiltins.td records using /// the provided \p DemangledCall and specified \p Set. /// /// The lookup follows the following algorithm, returning the first successful /// match: /// 1. Search with the plain demangled name (expecting a 1:1 match). /// 2. Search with the prefix before or suffix after the demangled name /// signyfying the type of the first argument. /// /// \returns Wrapper around the demangled call and found builtin definition. static std::unique_ptr lookupBuiltin(StringRef DemangledCall, SPIRV::InstructionSet::InstructionSet Set, Register ReturnRegister, const SPIRVType *ReturnType, const SmallVectorImpl &Arguments) { std::string BuiltinName = SPIRV::lookupBuiltinNameHelper(DemangledCall); SmallVector BuiltinArgumentTypes; StringRef BuiltinArgs = DemangledCall.slice(DemangledCall.find('(') + 1, DemangledCall.find(')')); BuiltinArgs.split(BuiltinArgumentTypes, ',', -1, false); // Look up the builtin in the defined set. Start with the plain demangled // name, expecting a 1:1 match in the defined builtin set. const SPIRV::DemangledBuiltin *Builtin; if ((Builtin = SPIRV::lookupBuiltin(BuiltinName, Set))) return std::make_unique( BuiltinName, Builtin, ReturnRegister, ReturnType, Arguments); // If the initial look up was unsuccessful and the demangled call takes at // least 1 argument, add a prefix or suffix signifying the type of the first // argument and repeat the search. if (BuiltinArgumentTypes.size() >= 1) { char FirstArgumentType = BuiltinArgumentTypes[0][0]; // Prefix to be added to the builtin's name for lookup. // For example, OpenCL "abs" taking an unsigned value has a prefix "u_". std::string Prefix; switch (FirstArgumentType) { // Unsigned: case 'u': if (Set == SPIRV::InstructionSet::OpenCL_std) Prefix = "u_"; else if (Set == SPIRV::InstructionSet::GLSL_std_450) Prefix = "u"; break; // Signed: case 'c': case 's': case 'i': case 'l': if (Set == SPIRV::InstructionSet::OpenCL_std) Prefix = "s_"; else if (Set == SPIRV::InstructionSet::GLSL_std_450) Prefix = "s"; break; // Floating-point: case 'f': case 'd': case 'h': if (Set == SPIRV::InstructionSet::OpenCL_std || Set == SPIRV::InstructionSet::GLSL_std_450) Prefix = "f"; break; } // If argument-type name prefix was added, look up the builtin again. if (!Prefix.empty() && (Builtin = SPIRV::lookupBuiltin(Prefix + BuiltinName, Set))) return std::make_unique( BuiltinName, Builtin, ReturnRegister, ReturnType, Arguments); // If lookup with a prefix failed, find a suffix to be added to the // builtin's name for lookup. For example, OpenCL "group_reduce_max" taking // an unsigned value has a suffix "u". std::string Suffix; switch (FirstArgumentType) { // Unsigned: case 'u': Suffix = "u"; break; // Signed: case 'c': case 's': case 'i': case 'l': Suffix = "s"; break; // Floating-point: case 'f': case 'd': case 'h': Suffix = "f"; break; } // If argument-type name suffix was added, look up the builtin again. if (!Suffix.empty() && (Builtin = SPIRV::lookupBuiltin(BuiltinName + Suffix, Set))) return std::make_unique( BuiltinName, Builtin, ReturnRegister, ReturnType, Arguments); } // No builtin with such name was found in the set. return nullptr; } static MachineInstr *getBlockStructInstr(Register ParamReg, MachineRegisterInfo *MRI) { // We expect the following sequence of instructions: // %0:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.alloca) // or = G_GLOBAL_VALUE @block_literal_global // %1:_(pN) = G_INTRINSIC_W_SIDE_EFFECTS intrinsic(@llvm.spv.bitcast), %0 // %2:_(p4) = G_ADDRSPACE_CAST %1:_(pN) MachineInstr *MI = MRI->getUniqueVRegDef(ParamReg); assert(MI->getOpcode() == TargetOpcode::G_ADDRSPACE_CAST && MI->getOperand(1).isReg()); Register BitcastReg = MI->getOperand(1).getReg(); MachineInstr *BitcastMI = MRI->getUniqueVRegDef(BitcastReg); assert(isSpvIntrinsic(*BitcastMI, Intrinsic::spv_bitcast) && BitcastMI->getOperand(2).isReg()); Register ValueReg = BitcastMI->getOperand(2).getReg(); MachineInstr *ValueMI = MRI->getUniqueVRegDef(ValueReg); return ValueMI; } // Return an integer constant corresponding to the given register and // defined in spv_track_constant. // TODO: maybe unify with prelegalizer pass. static unsigned getConstFromIntrinsic(Register Reg, MachineRegisterInfo *MRI) { MachineInstr *DefMI = MRI->getUniqueVRegDef(Reg); assert(isSpvIntrinsic(*DefMI, Intrinsic::spv_track_constant) && DefMI->getOperand(2).isReg()); MachineInstr *DefMI2 = MRI->getUniqueVRegDef(DefMI->getOperand(2).getReg()); assert(DefMI2->getOpcode() == TargetOpcode::G_CONSTANT && DefMI2->getOperand(1).isCImm()); return DefMI2->getOperand(1).getCImm()->getValue().getZExtValue(); } // Return type of the instruction result from spv_assign_type intrinsic. // TODO: maybe unify with prelegalizer pass. static const Type *getMachineInstrType(MachineInstr *MI) { MachineInstr *NextMI = MI->getNextNode(); if (!NextMI) return nullptr; if (isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_name)) if ((NextMI = NextMI->getNextNode()) == nullptr) return nullptr; Register ValueReg = MI->getOperand(0).getReg(); if ((!isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_type) && !isSpvIntrinsic(*NextMI, Intrinsic::spv_assign_ptr_type)) || NextMI->getOperand(1).getReg() != ValueReg) return nullptr; Type *Ty = getMDOperandAsType(NextMI->getOperand(2).getMetadata(), 0); assert(Ty && "Type is expected"); return Ty; } static const Type *getBlockStructType(Register ParamReg, MachineRegisterInfo *MRI) { // In principle, this information should be passed to us from Clang via // an elementtype attribute. However, said attribute requires that // the function call be an intrinsic, which is not. Instead, we rely on being // able to trace this to the declaration of a variable: OpenCL C specification // section 6.12.5 should guarantee that we can do this. MachineInstr *MI = getBlockStructInstr(ParamReg, MRI); if (MI->getOpcode() == TargetOpcode::G_GLOBAL_VALUE) return MI->getOperand(1).getGlobal()->getType(); assert(isSpvIntrinsic(*MI, Intrinsic::spv_alloca) && "Blocks in OpenCL C must be traceable to allocation site"); return getMachineInstrType(MI); } //===----------------------------------------------------------------------===// // Helper functions for building misc instructions //===----------------------------------------------------------------------===// /// Helper function building either a resulting scalar or vector bool register /// depending on the expected \p ResultType. /// /// \returns Tuple of the resulting register and its type. static std::tuple buildBoolRegister(MachineIRBuilder &MIRBuilder, const SPIRVType *ResultType, SPIRVGlobalRegistry *GR) { LLT Type; SPIRVType *BoolType = GR->getOrCreateSPIRVBoolType(MIRBuilder); if (ResultType->getOpcode() == SPIRV::OpTypeVector) { unsigned VectorElements = ResultType->getOperand(2).getImm(); BoolType = GR->getOrCreateSPIRVVectorType(BoolType, VectorElements, MIRBuilder); const FixedVectorType *LLVMVectorType = cast(GR->getTypeForSPIRVType(BoolType)); Type = LLT::vector(LLVMVectorType->getElementCount(), 1); } else { Type = LLT::scalar(1); } Register ResultRegister = MIRBuilder.getMRI()->createGenericVirtualRegister(Type); MIRBuilder.getMRI()->setRegClass(ResultRegister, GR->getRegClass(ResultType)); GR->assignSPIRVTypeToVReg(BoolType, ResultRegister, MIRBuilder.getMF()); return std::make_tuple(ResultRegister, BoolType); } /// Helper function for building either a vector or scalar select instruction /// depending on the expected \p ResultType. static bool buildSelectInst(MachineIRBuilder &MIRBuilder, Register ReturnRegister, Register SourceRegister, const SPIRVType *ReturnType, SPIRVGlobalRegistry *GR) { Register TrueConst, FalseConst; if (ReturnType->getOpcode() == SPIRV::OpTypeVector) { unsigned Bits = GR->getScalarOrVectorBitWidth(ReturnType); uint64_t AllOnes = APInt::getAllOnes(Bits).getZExtValue(); TrueConst = GR->getOrCreateConsIntVector(AllOnes, MIRBuilder, ReturnType); FalseConst = GR->getOrCreateConsIntVector(0, MIRBuilder, ReturnType); } else { TrueConst = GR->buildConstantInt(1, MIRBuilder, ReturnType); FalseConst = GR->buildConstantInt(0, MIRBuilder, ReturnType); } return MIRBuilder.buildSelect(ReturnRegister, SourceRegister, TrueConst, FalseConst); } /// Helper function for building a load instruction loading into the /// \p DestinationReg. static Register buildLoadInst(SPIRVType *BaseType, Register PtrRegister, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR, LLT LowLevelType, Register DestinationReg = Register(0)) { MachineRegisterInfo *MRI = MIRBuilder.getMRI(); if (!DestinationReg.isValid()) { DestinationReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass); MRI->setType(DestinationReg, LLT::scalar(64)); GR->assignSPIRVTypeToVReg(BaseType, DestinationReg, MIRBuilder.getMF()); } // TODO: consider using correct address space and alignment (p0 is canonical // type for selection though). MachinePointerInfo PtrInfo = MachinePointerInfo(); MIRBuilder.buildLoad(DestinationReg, PtrRegister, PtrInfo, Align()); return DestinationReg; } /// Helper function for building a load instruction for loading a builtin global /// variable of \p BuiltinValue value. static Register buildBuiltinVariableLoad( MachineIRBuilder &MIRBuilder, SPIRVType *VariableType, SPIRVGlobalRegistry *GR, SPIRV::BuiltIn::BuiltIn BuiltinValue, LLT LLType, Register Reg = Register(0), bool isConst = true, bool hasLinkageTy = true) { Register NewRegister = MIRBuilder.getMRI()->createVirtualRegister(&SPIRV::iIDRegClass); MIRBuilder.getMRI()->setType(NewRegister, LLT::pointer(0, GR->getPointerSize())); SPIRVType *PtrType = GR->getOrCreateSPIRVPointerType( VariableType, MIRBuilder, SPIRV::StorageClass::Input); GR->assignSPIRVTypeToVReg(PtrType, NewRegister, MIRBuilder.getMF()); // Set up the global OpVariable with the necessary builtin decorations. Register Variable = GR->buildGlobalVariable( NewRegister, PtrType, getLinkStringForBuiltIn(BuiltinValue), nullptr, SPIRV::StorageClass::Input, nullptr, /* isConst= */ isConst, /* HasLinkageTy */ hasLinkageTy, SPIRV::LinkageType::Import, MIRBuilder, false); // Load the value from the global variable. Register LoadedRegister = buildLoadInst(VariableType, Variable, MIRBuilder, GR, LLType, Reg); MIRBuilder.getMRI()->setType(LoadedRegister, LLType); return LoadedRegister; } /// Helper external function for inserting ASSIGN_TYPE instuction between \p Reg /// and its definition, set the new register as a destination of the definition, /// assign SPIRVType to both registers. If SpirvTy is provided, use it as /// SPIRVType in ASSIGN_TYPE, otherwise create it from \p Ty. Defined in /// SPIRVPreLegalizer.cpp. extern Register insertAssignInstr(Register Reg, Type *Ty, SPIRVType *SpirvTy, SPIRVGlobalRegistry *GR, MachineIRBuilder &MIB, MachineRegisterInfo &MRI); // TODO: Move to TableGen. static SPIRV::MemorySemantics::MemorySemantics getSPIRVMemSemantics(std::memory_order MemOrder) { switch (MemOrder) { case std::memory_order::memory_order_relaxed: return SPIRV::MemorySemantics::None; case std::memory_order::memory_order_acquire: return SPIRV::MemorySemantics::Acquire; case std::memory_order::memory_order_release: return SPIRV::MemorySemantics::Release; case std::memory_order::memory_order_acq_rel: return SPIRV::MemorySemantics::AcquireRelease; case std::memory_order::memory_order_seq_cst: return SPIRV::MemorySemantics::SequentiallyConsistent; default: report_fatal_error("Unknown CL memory scope"); } } static SPIRV::Scope::Scope getSPIRVScope(SPIRV::CLMemoryScope ClScope) { switch (ClScope) { case SPIRV::CLMemoryScope::memory_scope_work_item: return SPIRV::Scope::Invocation; case SPIRV::CLMemoryScope::memory_scope_work_group: return SPIRV::Scope::Workgroup; case SPIRV::CLMemoryScope::memory_scope_device: return SPIRV::Scope::Device; case SPIRV::CLMemoryScope::memory_scope_all_svm_devices: return SPIRV::Scope::CrossDevice; case SPIRV::CLMemoryScope::memory_scope_sub_group: return SPIRV::Scope::Subgroup; } report_fatal_error("Unknown CL memory scope"); } static Register buildConstantIntReg32(uint64_t Val, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { return GR->buildConstantInt(Val, MIRBuilder, GR->getOrCreateSPIRVIntegerType(32, MIRBuilder)); } static Register buildScopeReg(Register CLScopeRegister, SPIRV::Scope::Scope Scope, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR, MachineRegisterInfo *MRI) { if (CLScopeRegister.isValid()) { auto CLScope = static_cast(getIConstVal(CLScopeRegister, MRI)); Scope = getSPIRVScope(CLScope); if (CLScope == static_cast(Scope)) { MRI->setRegClass(CLScopeRegister, &SPIRV::iIDRegClass); return CLScopeRegister; } } return buildConstantIntReg32(Scope, MIRBuilder, GR); } static Register buildMemSemanticsReg(Register SemanticsRegister, Register PtrRegister, unsigned &Semantics, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (SemanticsRegister.isValid()) { MachineRegisterInfo *MRI = MIRBuilder.getMRI(); std::memory_order Order = static_cast(getIConstVal(SemanticsRegister, MRI)); Semantics = getSPIRVMemSemantics(Order) | getMemSemanticsForStorageClass(GR->getPointerStorageClass(PtrRegister)); if (Order == Semantics) { MRI->setRegClass(SemanticsRegister, &SPIRV::iIDRegClass); return SemanticsRegister; } } return buildConstantIntReg32(Semantics, MIRBuilder, GR); } static bool buildOpFromWrapper(MachineIRBuilder &MIRBuilder, unsigned Opcode, const SPIRV::IncomingCall *Call, Register TypeReg, ArrayRef ImmArgs = {}) { auto MIB = MIRBuilder.buildInstr(Opcode); if (TypeReg.isValid()) MIB.addDef(Call->ReturnRegister).addUse(TypeReg); unsigned Sz = Call->Arguments.size() - ImmArgs.size(); for (unsigned i = 0; i < Sz; ++i) MIB.addUse(Call->Arguments[i]); for (uint32_t ImmArg : ImmArgs) MIB.addImm(ImmArg); return true; } /// Helper function for translating atomic init to OpStore. static bool buildAtomicInitInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, SPIRV::OpStore, Call, Register(0)); assert(Call->Arguments.size() == 2 && "Need 2 arguments for atomic init translation"); MIRBuilder.buildInstr(SPIRV::OpStore) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]); return true; } /// Helper function for building an atomic load instruction. static bool buildAtomicLoadInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { Register TypeReg = GR->getSPIRVTypeID(Call->ReturnType); if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, SPIRV::OpAtomicLoad, Call, TypeReg); Register PtrRegister = Call->Arguments[0]; // TODO: if true insert call to __translate_ocl_memory_sccope before // OpAtomicLoad and the function implementation. We can use Translator's // output for transcoding/atomic_explicit_arguments.cl as an example. Register ScopeRegister = Call->Arguments.size() > 1 ? Call->Arguments[1] : buildConstantIntReg32(SPIRV::Scope::Device, MIRBuilder, GR); Register MemSemanticsReg; if (Call->Arguments.size() > 2) { // TODO: Insert call to __translate_ocl_memory_order before OpAtomicLoad. MemSemanticsReg = Call->Arguments[2]; } else { int Semantics = SPIRV::MemorySemantics::SequentiallyConsistent | getMemSemanticsForStorageClass(GR->getPointerStorageClass(PtrRegister)); MemSemanticsReg = buildConstantIntReg32(Semantics, MIRBuilder, GR); } MIRBuilder.buildInstr(SPIRV::OpAtomicLoad) .addDef(Call->ReturnRegister) .addUse(TypeReg) .addUse(PtrRegister) .addUse(ScopeRegister) .addUse(MemSemanticsReg); return true; } /// Helper function for building an atomic store instruction. static bool buildAtomicStoreInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, SPIRV::OpAtomicStore, Call, Register(0)); Register ScopeRegister = buildConstantIntReg32(SPIRV::Scope::Device, MIRBuilder, GR); Register PtrRegister = Call->Arguments[0]; int Semantics = SPIRV::MemorySemantics::SequentiallyConsistent | getMemSemanticsForStorageClass(GR->getPointerStorageClass(PtrRegister)); Register MemSemanticsReg = buildConstantIntReg32(Semantics, MIRBuilder, GR); MIRBuilder.buildInstr(SPIRV::OpAtomicStore) .addUse(PtrRegister) .addUse(ScopeRegister) .addUse(MemSemanticsReg) .addUse(Call->Arguments[1]); return true; } /// Helper function for building an atomic compare-exchange instruction. static bool buildAtomicCompareExchangeInst( const SPIRV::IncomingCall *Call, const SPIRV::DemangledBuiltin *Builtin, unsigned Opcode, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, GR->getSPIRVTypeID(Call->ReturnType)); bool IsCmpxchg = Call->Builtin->Name.contains("cmpxchg"); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); Register ObjectPtr = Call->Arguments[0]; // Pointer (volatile A *object.) Register ExpectedArg = Call->Arguments[1]; // Comparator (C* expected). Register Desired = Call->Arguments[2]; // Value (C Desired). SPIRVType *SpvDesiredTy = GR->getSPIRVTypeForVReg(Desired); LLT DesiredLLT = MRI->getType(Desired); assert(GR->getSPIRVTypeForVReg(ObjectPtr)->getOpcode() == SPIRV::OpTypePointer); unsigned ExpectedType = GR->getSPIRVTypeForVReg(ExpectedArg)->getOpcode(); (void)ExpectedType; assert(IsCmpxchg ? ExpectedType == SPIRV::OpTypeInt : ExpectedType == SPIRV::OpTypePointer); assert(GR->isScalarOfType(Desired, SPIRV::OpTypeInt)); SPIRVType *SpvObjectPtrTy = GR->getSPIRVTypeForVReg(ObjectPtr); assert(SpvObjectPtrTy->getOperand(2).isReg() && "SPIRV type is expected"); auto StorageClass = static_cast( SpvObjectPtrTy->getOperand(1).getImm()); auto MemSemStorage = getMemSemanticsForStorageClass(StorageClass); Register MemSemEqualReg; Register MemSemUnequalReg; uint64_t MemSemEqual = IsCmpxchg ? SPIRV::MemorySemantics::None : SPIRV::MemorySemantics::SequentiallyConsistent | MemSemStorage; uint64_t MemSemUnequal = IsCmpxchg ? SPIRV::MemorySemantics::None : SPIRV::MemorySemantics::SequentiallyConsistent | MemSemStorage; if (Call->Arguments.size() >= 4) { assert(Call->Arguments.size() >= 5 && "Need 5+ args for explicit atomic cmpxchg"); auto MemOrdEq = static_cast(getIConstVal(Call->Arguments[3], MRI)); auto MemOrdNeq = static_cast(getIConstVal(Call->Arguments[4], MRI)); MemSemEqual = getSPIRVMemSemantics(MemOrdEq) | MemSemStorage; MemSemUnequal = getSPIRVMemSemantics(MemOrdNeq) | MemSemStorage; if (MemOrdEq == MemSemEqual) MemSemEqualReg = Call->Arguments[3]; if (MemOrdNeq == MemSemEqual) MemSemUnequalReg = Call->Arguments[4]; } if (!MemSemEqualReg.isValid()) MemSemEqualReg = buildConstantIntReg32(MemSemEqual, MIRBuilder, GR); if (!MemSemUnequalReg.isValid()) MemSemUnequalReg = buildConstantIntReg32(MemSemUnequal, MIRBuilder, GR); Register ScopeReg; auto Scope = IsCmpxchg ? SPIRV::Scope::Workgroup : SPIRV::Scope::Device; if (Call->Arguments.size() >= 6) { assert(Call->Arguments.size() == 6 && "Extra args for explicit atomic cmpxchg"); auto ClScope = static_cast( getIConstVal(Call->Arguments[5], MRI)); Scope = getSPIRVScope(ClScope); if (ClScope == static_cast(Scope)) ScopeReg = Call->Arguments[5]; } if (!ScopeReg.isValid()) ScopeReg = buildConstantIntReg32(Scope, MIRBuilder, GR); Register Expected = IsCmpxchg ? ExpectedArg : buildLoadInst(SpvDesiredTy, ExpectedArg, MIRBuilder, GR, LLT::scalar(64)); MRI->setType(Expected, DesiredLLT); Register Tmp = !IsCmpxchg ? MRI->createGenericVirtualRegister(DesiredLLT) : Call->ReturnRegister; if (!MRI->getRegClassOrNull(Tmp)) MRI->setRegClass(Tmp, GR->getRegClass(SpvDesiredTy)); GR->assignSPIRVTypeToVReg(SpvDesiredTy, Tmp, MIRBuilder.getMF()); SPIRVType *IntTy = GR->getOrCreateSPIRVIntegerType(32, MIRBuilder); MIRBuilder.buildInstr(Opcode) .addDef(Tmp) .addUse(GR->getSPIRVTypeID(IntTy)) .addUse(ObjectPtr) .addUse(ScopeReg) .addUse(MemSemEqualReg) .addUse(MemSemUnequalReg) .addUse(Desired) .addUse(Expected); if (!IsCmpxchg) { MIRBuilder.buildInstr(SPIRV::OpStore).addUse(ExpectedArg).addUse(Tmp); MIRBuilder.buildICmp(CmpInst::ICMP_EQ, Call->ReturnRegister, Tmp, Expected); } return true; } /// Helper function for building atomic instructions. static bool buildAtomicRMWInst(const SPIRV::IncomingCall *Call, unsigned Opcode, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, GR->getSPIRVTypeID(Call->ReturnType)); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); Register ScopeRegister = Call->Arguments.size() >= 4 ? Call->Arguments[3] : Register(); assert(Call->Arguments.size() <= 4 && "Too many args for explicit atomic RMW"); ScopeRegister = buildScopeReg(ScopeRegister, SPIRV::Scope::Workgroup, MIRBuilder, GR, MRI); Register PtrRegister = Call->Arguments[0]; unsigned Semantics = SPIRV::MemorySemantics::None; Register MemSemanticsReg = Call->Arguments.size() >= 3 ? Call->Arguments[2] : Register(); MemSemanticsReg = buildMemSemanticsReg(MemSemanticsReg, PtrRegister, Semantics, MIRBuilder, GR); Register ValueReg = Call->Arguments[1]; Register ValueTypeReg = GR->getSPIRVTypeID(Call->ReturnType); // support cl_ext_float_atomics if (Call->ReturnType->getOpcode() == SPIRV::OpTypeFloat) { if (Opcode == SPIRV::OpAtomicIAdd) { Opcode = SPIRV::OpAtomicFAddEXT; } else if (Opcode == SPIRV::OpAtomicISub) { // Translate OpAtomicISub applied to a floating type argument to // OpAtomicFAddEXT with the negative value operand Opcode = SPIRV::OpAtomicFAddEXT; Register NegValueReg = MRI->createGenericVirtualRegister(MRI->getType(ValueReg)); MRI->setRegClass(NegValueReg, GR->getRegClass(Call->ReturnType)); GR->assignSPIRVTypeToVReg(Call->ReturnType, NegValueReg, MIRBuilder.getMF()); MIRBuilder.buildInstr(TargetOpcode::G_FNEG) .addDef(NegValueReg) .addUse(ValueReg); insertAssignInstr(NegValueReg, nullptr, Call->ReturnType, GR, MIRBuilder, MIRBuilder.getMF().getRegInfo()); ValueReg = NegValueReg; } } MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(ValueTypeReg) .addUse(PtrRegister) .addUse(ScopeRegister) .addUse(MemSemanticsReg) .addUse(ValueReg); return true; } /// Helper function for building an atomic floating-type instruction. static bool buildAtomicFloatingRMWInst(const SPIRV::IncomingCall *Call, unsigned Opcode, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { assert(Call->Arguments.size() == 4 && "Wrong number of atomic floating-type builtin"); Register PtrReg = Call->Arguments[0]; Register ScopeReg = Call->Arguments[1]; Register MemSemanticsReg = Call->Arguments[2]; Register ValueReg = Call->Arguments[3]; MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(PtrReg) .addUse(ScopeReg) .addUse(MemSemanticsReg) .addUse(ValueReg); return true; } /// Helper function for building atomic flag instructions (e.g. /// OpAtomicFlagTestAndSet). static bool buildAtomicFlagInst(const SPIRV::IncomingCall *Call, unsigned Opcode, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { bool IsSet = Opcode == SPIRV::OpAtomicFlagTestAndSet; Register TypeReg = GR->getSPIRVTypeID(Call->ReturnType); if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, IsSet ? TypeReg : Register(0)); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); Register PtrRegister = Call->Arguments[0]; unsigned Semantics = SPIRV::MemorySemantics::SequentiallyConsistent; Register MemSemanticsReg = Call->Arguments.size() >= 2 ? Call->Arguments[1] : Register(); MemSemanticsReg = buildMemSemanticsReg(MemSemanticsReg, PtrRegister, Semantics, MIRBuilder, GR); assert((Opcode != SPIRV::OpAtomicFlagClear || (Semantics != SPIRV::MemorySemantics::Acquire && Semantics != SPIRV::MemorySemantics::AcquireRelease)) && "Invalid memory order argument!"); Register ScopeRegister = Call->Arguments.size() >= 3 ? Call->Arguments[2] : Register(); ScopeRegister = buildScopeReg(ScopeRegister, SPIRV::Scope::Device, MIRBuilder, GR, MRI); auto MIB = MIRBuilder.buildInstr(Opcode); if (IsSet) MIB.addDef(Call->ReturnRegister).addUse(TypeReg); MIB.addUse(PtrRegister).addUse(ScopeRegister).addUse(MemSemanticsReg); return true; } /// Helper function for building barriers, i.e., memory/control ordering /// operations. static bool buildBarrierInst(const SPIRV::IncomingCall *Call, unsigned Opcode, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, Register(0)); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); unsigned MemFlags = getIConstVal(Call->Arguments[0], MRI); unsigned MemSemantics = SPIRV::MemorySemantics::None; if (MemFlags & SPIRV::CLK_LOCAL_MEM_FENCE) MemSemantics |= SPIRV::MemorySemantics::WorkgroupMemory; if (MemFlags & SPIRV::CLK_GLOBAL_MEM_FENCE) MemSemantics |= SPIRV::MemorySemantics::CrossWorkgroupMemory; if (MemFlags & SPIRV::CLK_IMAGE_MEM_FENCE) MemSemantics |= SPIRV::MemorySemantics::ImageMemory; if (Opcode == SPIRV::OpMemoryBarrier) { std::memory_order MemOrder = static_cast(getIConstVal(Call->Arguments[1], MRI)); MemSemantics = getSPIRVMemSemantics(MemOrder) | MemSemantics; } else { MemSemantics |= SPIRV::MemorySemantics::SequentiallyConsistent; } Register MemSemanticsReg = MemFlags == MemSemantics ? Call->Arguments[0] : buildConstantIntReg32(MemSemantics, MIRBuilder, GR); Register ScopeReg; SPIRV::Scope::Scope Scope = SPIRV::Scope::Workgroup; SPIRV::Scope::Scope MemScope = Scope; if (Call->Arguments.size() >= 2) { assert( ((Opcode != SPIRV::OpMemoryBarrier && Call->Arguments.size() == 2) || (Opcode == SPIRV::OpMemoryBarrier && Call->Arguments.size() == 3)) && "Extra args for explicitly scoped barrier"); Register ScopeArg = (Opcode == SPIRV::OpMemoryBarrier) ? Call->Arguments[2] : Call->Arguments[1]; SPIRV::CLMemoryScope CLScope = static_cast(getIConstVal(ScopeArg, MRI)); MemScope = getSPIRVScope(CLScope); if (!(MemFlags & SPIRV::CLK_LOCAL_MEM_FENCE) || (Opcode == SPIRV::OpMemoryBarrier)) Scope = MemScope; if (CLScope == static_cast(Scope)) ScopeReg = Call->Arguments[1]; } if (!ScopeReg.isValid()) ScopeReg = buildConstantIntReg32(Scope, MIRBuilder, GR); auto MIB = MIRBuilder.buildInstr(Opcode).addUse(ScopeReg); if (Opcode != SPIRV::OpMemoryBarrier) MIB.addUse(buildConstantIntReg32(MemScope, MIRBuilder, GR)); MIB.addUse(MemSemanticsReg); return true; } static unsigned getNumComponentsForDim(SPIRV::Dim::Dim dim) { switch (dim) { case SPIRV::Dim::DIM_1D: case SPIRV::Dim::DIM_Buffer: return 1; case SPIRV::Dim::DIM_2D: case SPIRV::Dim::DIM_Cube: case SPIRV::Dim::DIM_Rect: return 2; case SPIRV::Dim::DIM_3D: return 3; default: report_fatal_error("Cannot get num components for given Dim"); } } /// Helper function for obtaining the number of size components. static unsigned getNumSizeComponents(SPIRVType *imgType) { assert(imgType->getOpcode() == SPIRV::OpTypeImage); auto dim = static_cast(imgType->getOperand(2).getImm()); unsigned numComps = getNumComponentsForDim(dim); bool arrayed = imgType->getOperand(4).getImm() == 1; return arrayed ? numComps + 1 : numComps; } //===----------------------------------------------------------------------===// // Implementation functions for each builtin group //===----------------------------------------------------------------------===// static bool generateExtInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the extended instruction number in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; uint32_t Number = SPIRV::lookupExtendedBuiltin(Builtin->Name, Builtin->Set)->Number; // Build extended instruction. auto MIB = MIRBuilder.buildInstr(SPIRV::OpExtInst) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addImm(static_cast(SPIRV::InstructionSet::OpenCL_std)) .addImm(Number); for (auto Argument : Call->Arguments) MIB.addUse(Argument); return true; } static bool generateRelationalInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; Register CompareRegister; SPIRVType *RelationType; std::tie(CompareRegister, RelationType) = buildBoolRegister(MIRBuilder, Call->ReturnType, GR); // Build relational instruction. auto MIB = MIRBuilder.buildInstr(Opcode) .addDef(CompareRegister) .addUse(GR->getSPIRVTypeID(RelationType)); for (auto Argument : Call->Arguments) MIB.addUse(Argument); // Build select instruction. return buildSelectInst(MIRBuilder, Call->ReturnRegister, CompareRegister, Call->ReturnType, GR); } static bool generateGroupInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; const SPIRV::GroupBuiltin *GroupBuiltin = SPIRV::lookupGroupBuiltin(Builtin->Name); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); if (Call->isSpirvOp()) { if (GroupBuiltin->NoGroupOperation) return buildOpFromWrapper(MIRBuilder, GroupBuiltin->Opcode, Call, GR->getSPIRVTypeID(Call->ReturnType)); // Group Operation is a literal Register GroupOpReg = Call->Arguments[1]; const MachineInstr *MI = getDefInstrMaybeConstant(GroupOpReg, MRI); if (!MI || MI->getOpcode() != TargetOpcode::G_CONSTANT) report_fatal_error( "Group Operation parameter must be an integer constant"); uint64_t GrpOp = MI->getOperand(1).getCImm()->getValue().getZExtValue(); Register ScopeReg = Call->Arguments[0]; auto MIB = MIRBuilder.buildInstr(GroupBuiltin->Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(ScopeReg) .addImm(GrpOp); for (unsigned i = 2; i < Call->Arguments.size(); ++i) MIB.addUse(Call->Arguments[i]); return true; } Register Arg0; if (GroupBuiltin->HasBoolArg) { SPIRVType *BoolType = GR->getOrCreateSPIRVBoolType(MIRBuilder); Register BoolReg = Call->Arguments[0]; SPIRVType *BoolRegType = GR->getSPIRVTypeForVReg(BoolReg); if (!BoolRegType) report_fatal_error("Can't find a register's type definition"); MachineInstr *ArgInstruction = getDefInstrMaybeConstant(BoolReg, MRI); if (ArgInstruction->getOpcode() == TargetOpcode::G_CONSTANT) { if (BoolRegType->getOpcode() != SPIRV::OpTypeBool) Arg0 = GR->buildConstantInt(getIConstVal(BoolReg, MRI), MIRBuilder, BoolType); } else { if (BoolRegType->getOpcode() == SPIRV::OpTypeInt) { Arg0 = MRI->createGenericVirtualRegister(LLT::scalar(1)); MRI->setRegClass(Arg0, &SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(BoolType, Arg0, MIRBuilder.getMF()); MIRBuilder.buildICmp(CmpInst::ICMP_NE, Arg0, BoolReg, GR->buildConstantInt(0, MIRBuilder, BoolRegType)); insertAssignInstr(Arg0, nullptr, BoolType, GR, MIRBuilder, MIRBuilder.getMF().getRegInfo()); } else if (BoolRegType->getOpcode() != SPIRV::OpTypeBool) { report_fatal_error("Expect a boolean argument"); } // if BoolReg is a boolean register, we don't need to do anything } } Register GroupResultRegister = Call->ReturnRegister; SPIRVType *GroupResultType = Call->ReturnType; // TODO: maybe we need to check whether the result type is already boolean // and in this case do not insert select instruction. const bool HasBoolReturnTy = GroupBuiltin->IsElect || GroupBuiltin->IsAllOrAny || GroupBuiltin->IsAllEqual || GroupBuiltin->IsLogical || GroupBuiltin->IsInverseBallot || GroupBuiltin->IsBallotBitExtract; if (HasBoolReturnTy) std::tie(GroupResultRegister, GroupResultType) = buildBoolRegister(MIRBuilder, Call->ReturnType, GR); auto Scope = Builtin->Name.starts_with("sub_group") ? SPIRV::Scope::Subgroup : SPIRV::Scope::Workgroup; Register ScopeRegister = buildConstantIntReg32(Scope, MIRBuilder, GR); Register VecReg; if (GroupBuiltin->Opcode == SPIRV::OpGroupBroadcast && Call->Arguments.size() > 2) { // For OpGroupBroadcast "LocalId must be an integer datatype. It must be a // scalar, a vector with 2 components, or a vector with 3 components.", // meaning that we must create a vector from the function arguments if // it's a work_group_broadcast(val, local_id_x, local_id_y) or // work_group_broadcast(val, local_id_x, local_id_y, local_id_z) call. Register ElemReg = Call->Arguments[1]; SPIRVType *ElemType = GR->getSPIRVTypeForVReg(ElemReg); if (!ElemType || ElemType->getOpcode() != SPIRV::OpTypeInt) report_fatal_error("Expect an integer argument"); unsigned VecLen = Call->Arguments.size() - 1; VecReg = MRI->createGenericVirtualRegister( LLT::fixed_vector(VecLen, MRI->getType(ElemReg))); MRI->setRegClass(VecReg, &SPIRV::vIDRegClass); SPIRVType *VecType = GR->getOrCreateSPIRVVectorType(ElemType, VecLen, MIRBuilder); GR->assignSPIRVTypeToVReg(VecType, VecReg, MIRBuilder.getMF()); auto MIB = MIRBuilder.buildInstr(TargetOpcode::G_BUILD_VECTOR).addDef(VecReg); for (unsigned i = 1; i < Call->Arguments.size(); i++) { MIB.addUse(Call->Arguments[i]); MRI->setRegClass(Call->Arguments[i], &SPIRV::iIDRegClass); } insertAssignInstr(VecReg, nullptr, VecType, GR, MIRBuilder, MIRBuilder.getMF().getRegInfo()); } // Build work/sub group instruction. auto MIB = MIRBuilder.buildInstr(GroupBuiltin->Opcode) .addDef(GroupResultRegister) .addUse(GR->getSPIRVTypeID(GroupResultType)) .addUse(ScopeRegister); if (!GroupBuiltin->NoGroupOperation) MIB.addImm(GroupBuiltin->GroupOperation); if (Call->Arguments.size() > 0) { MIB.addUse(Arg0.isValid() ? Arg0 : Call->Arguments[0]); MRI->setRegClass(Call->Arguments[0], &SPIRV::iIDRegClass); if (VecReg.isValid()) MIB.addUse(VecReg); else for (unsigned i = 1; i < Call->Arguments.size(); i++) MIB.addUse(Call->Arguments[i]); } // Build select instruction. if (HasBoolReturnTy) buildSelectInst(MIRBuilder, Call->ReturnRegister, GroupResultRegister, Call->ReturnType, GR); return true; } static bool generateIntelSubgroupsInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; MachineFunction &MF = MIRBuilder.getMF(); const auto *ST = static_cast(&MF.getSubtarget()); if (!ST->canUseExtension(SPIRV::Extension::SPV_INTEL_subgroups)) { std::string DiagMsg = std::string(Builtin->Name) + ": the builtin requires the following SPIR-V " "extension: SPV_INTEL_subgroups"; report_fatal_error(DiagMsg.c_str(), false); } const SPIRV::IntelSubgroupsBuiltin *IntelSubgroups = SPIRV::lookupIntelSubgroupsBuiltin(Builtin->Name); uint32_t OpCode = IntelSubgroups->Opcode; if (Call->isSpirvOp()) { bool IsSet = OpCode != SPIRV::OpSubgroupBlockWriteINTEL && OpCode != SPIRV::OpSubgroupImageBlockWriteINTEL; return buildOpFromWrapper(MIRBuilder, OpCode, Call, IsSet ? GR->getSPIRVTypeID(Call->ReturnType) : Register(0)); } if (IntelSubgroups->IsBlock) { // Minimal number or arguments set in TableGen records is 1 if (SPIRVType *Arg0Type = GR->getSPIRVTypeForVReg(Call->Arguments[0])) { if (Arg0Type->getOpcode() == SPIRV::OpTypeImage) { // TODO: add required validation from the specification: // "'Image' must be an object whose type is OpTypeImage with a 'Sampled' // operand of 0 or 2. If the 'Sampled' operand is 2, then some // dimensions require a capability." switch (OpCode) { case SPIRV::OpSubgroupBlockReadINTEL: OpCode = SPIRV::OpSubgroupImageBlockReadINTEL; break; case SPIRV::OpSubgroupBlockWriteINTEL: OpCode = SPIRV::OpSubgroupImageBlockWriteINTEL; break; } } } } // TODO: opaque pointers types should be eventually resolved in such a way // that validation of block read is enabled with respect to the following // specification requirement: // "'Result Type' may be a scalar or vector type, and its component type must // be equal to the type pointed to by 'Ptr'." // For example, function parameter type should not be default i8 pointer, but // depend on the result type of the instruction where it is used as a pointer // argument of OpSubgroupBlockReadINTEL // Build Intel subgroups instruction MachineInstrBuilder MIB = IntelSubgroups->IsWrite ? MIRBuilder.buildInstr(OpCode) : MIRBuilder.buildInstr(OpCode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)); for (size_t i = 0; i < Call->Arguments.size(); ++i) MIB.addUse(Call->Arguments[i]); return true; } static bool generateGroupUniformInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; MachineFunction &MF = MIRBuilder.getMF(); const auto *ST = static_cast(&MF.getSubtarget()); if (!ST->canUseExtension( SPIRV::Extension::SPV_KHR_uniform_group_instructions)) { std::string DiagMsg = std::string(Builtin->Name) + ": the builtin requires the following SPIR-V " "extension: SPV_KHR_uniform_group_instructions"; report_fatal_error(DiagMsg.c_str(), false); } const SPIRV::GroupUniformBuiltin *GroupUniform = SPIRV::lookupGroupUniformBuiltin(Builtin->Name); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); Register GroupResultReg = Call->ReturnRegister; Register ScopeReg = Call->Arguments[0]; Register ValueReg = Call->Arguments[2]; // Group Operation Register ConstGroupOpReg = Call->Arguments[1]; const MachineInstr *Const = getDefInstrMaybeConstant(ConstGroupOpReg, MRI); if (!Const || Const->getOpcode() != TargetOpcode::G_CONSTANT) report_fatal_error( "expect a constant group operation for a uniform group instruction", false); const MachineOperand &ConstOperand = Const->getOperand(1); if (!ConstOperand.isCImm()) report_fatal_error("uniform group instructions: group operation must be an " "integer constant", false); auto MIB = MIRBuilder.buildInstr(GroupUniform->Opcode) .addDef(GroupResultReg) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(ScopeReg); addNumImm(ConstOperand.getCImm()->getValue(), MIB); MIB.addUse(ValueReg); return true; } static bool generateKernelClockInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; MachineFunction &MF = MIRBuilder.getMF(); const auto *ST = static_cast(&MF.getSubtarget()); if (!ST->canUseExtension(SPIRV::Extension::SPV_KHR_shader_clock)) { std::string DiagMsg = std::string(Builtin->Name) + ": the builtin requires the following SPIR-V " "extension: SPV_KHR_shader_clock"; report_fatal_error(DiagMsg.c_str(), false); } Register ResultReg = Call->ReturnRegister; // Deduce the `Scope` operand from the builtin function name. SPIRV::Scope::Scope ScopeArg = StringSwitch(Builtin->Name) .EndsWith("device", SPIRV::Scope::Scope::Device) .EndsWith("work_group", SPIRV::Scope::Scope::Workgroup) .EndsWith("sub_group", SPIRV::Scope::Scope::Subgroup); Register ScopeReg = buildConstantIntReg32(ScopeArg, MIRBuilder, GR); MIRBuilder.buildInstr(SPIRV::OpReadClockKHR) .addDef(ResultReg) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(ScopeReg); return true; } // These queries ask for a single size_t result for a given dimension index, e.g // size_t get_global_id(uint dimindex). In SPIR-V, the builtins corresonding to // these values are all vec3 types, so we need to extract the correct index or // return defaultVal (0 or 1 depending on the query). We also handle extending // or tuncating in case size_t does not match the expected result type's // bitwidth. // // For a constant index >= 3 we generate: // %res = OpConstant %SizeT 0 // // For other indices we generate: // %g = OpVariable %ptr_V3_SizeT Input // OpDecorate %g BuiltIn XXX // OpDecorate %g LinkageAttributes "__spirv_BuiltInXXX" // OpDecorate %g Constant // %loadedVec = OpLoad %V3_SizeT %g // // Then, if the index is constant < 3, we generate: // %res = OpCompositeExtract %SizeT %loadedVec idx // If the index is dynamic, we generate: // %tmp = OpVectorExtractDynamic %SizeT %loadedVec %idx // %cmp = OpULessThan %bool %idx %const_3 // %res = OpSelect %SizeT %cmp %tmp %const_0 // // If the bitwidth of %res does not match the expected return type, we add an // extend or truncate. static bool genWorkgroupQuery(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR, SPIRV::BuiltIn::BuiltIn BuiltinValue, uint64_t DefaultValue) { Register IndexRegister = Call->Arguments[0]; const unsigned ResultWidth = Call->ReturnType->getOperand(1).getImm(); const unsigned PointerSize = GR->getPointerSize(); const SPIRVType *PointerSizeType = GR->getOrCreateSPIRVIntegerType(PointerSize, MIRBuilder); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); auto IndexInstruction = getDefInstrMaybeConstant(IndexRegister, MRI); // Set up the final register to do truncation or extension on at the end. Register ToTruncate = Call->ReturnRegister; // If the index is constant, we can statically determine if it is in range. bool IsConstantIndex = IndexInstruction->getOpcode() == TargetOpcode::G_CONSTANT; // If it's out of range (max dimension is 3), we can just return the constant // default value (0 or 1 depending on which query function). if (IsConstantIndex && getIConstVal(IndexRegister, MRI) >= 3) { Register DefaultReg = Call->ReturnRegister; if (PointerSize != ResultWidth) { DefaultReg = MRI->createGenericVirtualRegister(LLT::scalar(PointerSize)); MRI->setRegClass(DefaultReg, &SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(PointerSizeType, DefaultReg, MIRBuilder.getMF()); ToTruncate = DefaultReg; } auto NewRegister = GR->buildConstantInt(DefaultValue, MIRBuilder, PointerSizeType); MIRBuilder.buildCopy(DefaultReg, NewRegister); } else { // If it could be in range, we need to load from the given builtin. auto Vec3Ty = GR->getOrCreateSPIRVVectorType(PointerSizeType, 3, MIRBuilder); Register LoadedVector = buildBuiltinVariableLoad(MIRBuilder, Vec3Ty, GR, BuiltinValue, LLT::fixed_vector(3, PointerSize)); // Set up the vreg to extract the result to (possibly a new temporary one). Register Extracted = Call->ReturnRegister; if (!IsConstantIndex || PointerSize != ResultWidth) { Extracted = MRI->createGenericVirtualRegister(LLT::scalar(PointerSize)); MRI->setRegClass(Extracted, &SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(PointerSizeType, Extracted, MIRBuilder.getMF()); } // Use Intrinsic::spv_extractelt so dynamic vs static extraction is // handled later: extr = spv_extractelt LoadedVector, IndexRegister. MachineInstrBuilder ExtractInst = MIRBuilder.buildIntrinsic( Intrinsic::spv_extractelt, ArrayRef{Extracted}, true, false); ExtractInst.addUse(LoadedVector).addUse(IndexRegister); // If the index is dynamic, need check if it's < 3, and then use a select. if (!IsConstantIndex) { insertAssignInstr(Extracted, nullptr, PointerSizeType, GR, MIRBuilder, *MRI); auto IndexType = GR->getSPIRVTypeForVReg(IndexRegister); auto BoolType = GR->getOrCreateSPIRVBoolType(MIRBuilder); Register CompareRegister = MRI->createGenericVirtualRegister(LLT::scalar(1)); MRI->setRegClass(CompareRegister, &SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(BoolType, CompareRegister, MIRBuilder.getMF()); // Use G_ICMP to check if idxVReg < 3. MIRBuilder.buildICmp(CmpInst::ICMP_ULT, CompareRegister, IndexRegister, GR->buildConstantInt(3, MIRBuilder, IndexType)); // Get constant for the default value (0 or 1 depending on which // function). Register DefaultRegister = GR->buildConstantInt(DefaultValue, MIRBuilder, PointerSizeType); // Get a register for the selection result (possibly a new temporary one). Register SelectionResult = Call->ReturnRegister; if (PointerSize != ResultWidth) { SelectionResult = MRI->createGenericVirtualRegister(LLT::scalar(PointerSize)); MRI->setRegClass(SelectionResult, &SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(PointerSizeType, SelectionResult, MIRBuilder.getMF()); } // Create the final G_SELECT to return the extracted value or the default. MIRBuilder.buildSelect(SelectionResult, CompareRegister, Extracted, DefaultRegister); ToTruncate = SelectionResult; } else { ToTruncate = Extracted; } } // Alter the result's bitwidth if it does not match the SizeT value extracted. if (PointerSize != ResultWidth) MIRBuilder.buildZExtOrTrunc(Call->ReturnRegister, ToTruncate); return true; } static bool generateBuiltinVar(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the builtin variable record. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; SPIRV::BuiltIn::BuiltIn Value = SPIRV::lookupGetBuiltin(Builtin->Name, Builtin->Set)->Value; if (Value == SPIRV::BuiltIn::GlobalInvocationId) return genWorkgroupQuery(Call, MIRBuilder, GR, Value, 0); // Build a load instruction for the builtin variable. unsigned BitWidth = GR->getScalarOrVectorBitWidth(Call->ReturnType); LLT LLType; if (Call->ReturnType->getOpcode() == SPIRV::OpTypeVector) LLType = LLT::fixed_vector(Call->ReturnType->getOperand(2).getImm(), BitWidth); else LLType = LLT::scalar(BitWidth); return buildBuiltinVariableLoad(MIRBuilder, Call->ReturnType, GR, Value, LLType, Call->ReturnRegister); } static bool generateAtomicInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; switch (Opcode) { case SPIRV::OpStore: return buildAtomicInitInst(Call, MIRBuilder); case SPIRV::OpAtomicLoad: return buildAtomicLoadInst(Call, MIRBuilder, GR); case SPIRV::OpAtomicStore: return buildAtomicStoreInst(Call, MIRBuilder, GR); case SPIRV::OpAtomicCompareExchange: case SPIRV::OpAtomicCompareExchangeWeak: return buildAtomicCompareExchangeInst(Call, Builtin, Opcode, MIRBuilder, GR); case SPIRV::OpAtomicIAdd: case SPIRV::OpAtomicISub: case SPIRV::OpAtomicOr: case SPIRV::OpAtomicXor: case SPIRV::OpAtomicAnd: case SPIRV::OpAtomicExchange: return buildAtomicRMWInst(Call, Opcode, MIRBuilder, GR); case SPIRV::OpMemoryBarrier: return buildBarrierInst(Call, SPIRV::OpMemoryBarrier, MIRBuilder, GR); case SPIRV::OpAtomicFlagTestAndSet: case SPIRV::OpAtomicFlagClear: return buildAtomicFlagInst(Call, Opcode, MIRBuilder, GR); default: if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, GR->getSPIRVTypeID(Call->ReturnType)); return false; } } static bool generateAtomicFloatingInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupAtomicFloatingBuiltin(Builtin->Name)->Opcode; switch (Opcode) { case SPIRV::OpAtomicFAddEXT: case SPIRV::OpAtomicFMinEXT: case SPIRV::OpAtomicFMaxEXT: return buildAtomicFloatingRMWInst(Call, Opcode, MIRBuilder, GR); default: return false; } } static bool generateBarrierInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; return buildBarrierInst(Call, Opcode, MIRBuilder, GR); } static bool generateCastToPtrInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder) { MIRBuilder.buildInstr(TargetOpcode::G_ADDRSPACE_CAST) .addDef(Call->ReturnRegister) .addUse(Call->Arguments[0]); return true; } static bool generateDotOrFMulInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, SPIRV::OpDot, Call, GR->getSPIRVTypeID(Call->ReturnType)); unsigned Opcode = GR->getSPIRVTypeForVReg(Call->Arguments[0])->getOpcode(); bool IsVec = Opcode == SPIRV::OpTypeVector; // Use OpDot only in case of vector args and OpFMul in case of scalar args. MIRBuilder.buildInstr(IsVec ? SPIRV::OpDot : SPIRV::OpFMulS) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]); return true; } static bool generateWaveInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; SPIRV::BuiltIn::BuiltIn Value = SPIRV::lookupGetBuiltin(Builtin->Name, Builtin->Set)->Value; // For now, we only support a single Wave intrinsic with a single return type. assert(Call->ReturnType->getOpcode() == SPIRV::OpTypeInt); LLT LLType = LLT::scalar(GR->getScalarOrVectorBitWidth(Call->ReturnType)); return buildBuiltinVariableLoad( MIRBuilder, Call->ReturnType, GR, Value, LLType, Call->ReturnRegister, /* isConst= */ false, /* hasLinkageTy= */ false); } static bool generateGetQueryInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the builtin record. SPIRV::BuiltIn::BuiltIn Value = SPIRV::lookupGetBuiltin(Call->Builtin->Name, Call->Builtin->Set)->Value; uint64_t IsDefault = (Value == SPIRV::BuiltIn::GlobalSize || Value == SPIRV::BuiltIn::WorkgroupSize || Value == SPIRV::BuiltIn::EnqueuedWorkgroupSize); return genWorkgroupQuery(Call, MIRBuilder, GR, Value, IsDefault ? 1 : 0); } static bool generateImageSizeQueryInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the image size query component number in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; uint32_t Component = SPIRV::lookupImageQueryBuiltin(Builtin->Name, Builtin->Set)->Component; // Query result may either be a vector or a scalar. If return type is not a // vector, expect only a single size component. Otherwise get the number of // expected components. SPIRVType *RetTy = Call->ReturnType; unsigned NumExpectedRetComponents = RetTy->getOpcode() == SPIRV::OpTypeVector ? RetTy->getOperand(2).getImm() : 1; // Get the actual number of query result/size components. SPIRVType *ImgType = GR->getSPIRVTypeForVReg(Call->Arguments[0]); unsigned NumActualRetComponents = getNumSizeComponents(ImgType); Register QueryResult = Call->ReturnRegister; SPIRVType *QueryResultType = Call->ReturnType; if (NumExpectedRetComponents != NumActualRetComponents) { QueryResult = MIRBuilder.getMRI()->createGenericVirtualRegister( LLT::fixed_vector(NumActualRetComponents, 32)); MIRBuilder.getMRI()->setRegClass(QueryResult, &SPIRV::vIDRegClass); SPIRVType *IntTy = GR->getOrCreateSPIRVIntegerType(32, MIRBuilder); QueryResultType = GR->getOrCreateSPIRVVectorType( IntTy, NumActualRetComponents, MIRBuilder); GR->assignSPIRVTypeToVReg(QueryResultType, QueryResult, MIRBuilder.getMF()); } bool IsDimBuf = ImgType->getOperand(2).getImm() == SPIRV::Dim::DIM_Buffer; unsigned Opcode = IsDimBuf ? SPIRV::OpImageQuerySize : SPIRV::OpImageQuerySizeLod; auto MIB = MIRBuilder.buildInstr(Opcode) .addDef(QueryResult) .addUse(GR->getSPIRVTypeID(QueryResultType)) .addUse(Call->Arguments[0]); if (!IsDimBuf) MIB.addUse(buildConstantIntReg32(0, MIRBuilder, GR)); // Lod id. if (NumExpectedRetComponents == NumActualRetComponents) return true; if (NumExpectedRetComponents == 1) { // Only 1 component is expected, build OpCompositeExtract instruction. unsigned ExtractedComposite = Component == 3 ? NumActualRetComponents - 1 : Component; assert(ExtractedComposite < NumActualRetComponents && "Invalid composite index!"); Register TypeReg = GR->getSPIRVTypeID(Call->ReturnType); SPIRVType *NewType = nullptr; if (QueryResultType->getOpcode() == SPIRV::OpTypeVector) { Register NewTypeReg = QueryResultType->getOperand(1).getReg(); if (TypeReg != NewTypeReg && (NewType = GR->getSPIRVTypeForVReg(NewTypeReg)) != nullptr) TypeReg = NewTypeReg; } MIRBuilder.buildInstr(SPIRV::OpCompositeExtract) .addDef(Call->ReturnRegister) .addUse(TypeReg) .addUse(QueryResult) .addImm(ExtractedComposite); if (NewType != nullptr) insertAssignInstr(Call->ReturnRegister, nullptr, NewType, GR, MIRBuilder, MIRBuilder.getMF().getRegInfo()); } else { // More than 1 component is expected, fill a new vector. auto MIB = MIRBuilder.buildInstr(SPIRV::OpVectorShuffle) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(QueryResult) .addUse(QueryResult); for (unsigned i = 0; i < NumExpectedRetComponents; ++i) MIB.addImm(i < NumActualRetComponents ? i : 0xffffffff); } return true; } static bool generateImageMiscQueryInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { assert(Call->ReturnType->getOpcode() == SPIRV::OpTypeInt && "Image samples query result must be of int type!"); // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; Register Image = Call->Arguments[0]; SPIRV::Dim::Dim ImageDimensionality = static_cast( GR->getSPIRVTypeForVReg(Image)->getOperand(2).getImm()); (void)ImageDimensionality; switch (Opcode) { case SPIRV::OpImageQuerySamples: assert(ImageDimensionality == SPIRV::Dim::DIM_2D && "Image must be of 2D dimensionality"); break; case SPIRV::OpImageQueryLevels: assert((ImageDimensionality == SPIRV::Dim::DIM_1D || ImageDimensionality == SPIRV::Dim::DIM_2D || ImageDimensionality == SPIRV::Dim::DIM_3D || ImageDimensionality == SPIRV::Dim::DIM_Cube) && "Image must be of 1D/2D/3D/Cube dimensionality"); break; } MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Image); return true; } // TODO: Move to TableGen. static SPIRV::SamplerAddressingMode::SamplerAddressingMode getSamplerAddressingModeFromBitmask(unsigned Bitmask) { switch (Bitmask & SPIRV::CLK_ADDRESS_MODE_MASK) { case SPIRV::CLK_ADDRESS_CLAMP: return SPIRV::SamplerAddressingMode::Clamp; case SPIRV::CLK_ADDRESS_CLAMP_TO_EDGE: return SPIRV::SamplerAddressingMode::ClampToEdge; case SPIRV::CLK_ADDRESS_REPEAT: return SPIRV::SamplerAddressingMode::Repeat; case SPIRV::CLK_ADDRESS_MIRRORED_REPEAT: return SPIRV::SamplerAddressingMode::RepeatMirrored; case SPIRV::CLK_ADDRESS_NONE: return SPIRV::SamplerAddressingMode::None; default: report_fatal_error("Unknown CL address mode"); } } static unsigned getSamplerParamFromBitmask(unsigned Bitmask) { return (Bitmask & SPIRV::CLK_NORMALIZED_COORDS_TRUE) ? 1 : 0; } static SPIRV::SamplerFilterMode::SamplerFilterMode getSamplerFilterModeFromBitmask(unsigned Bitmask) { if (Bitmask & SPIRV::CLK_FILTER_LINEAR) return SPIRV::SamplerFilterMode::Linear; if (Bitmask & SPIRV::CLK_FILTER_NEAREST) return SPIRV::SamplerFilterMode::Nearest; return SPIRV::SamplerFilterMode::Nearest; } static bool generateReadImageInst(const StringRef DemangledCall, const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { Register Image = Call->Arguments[0]; MachineRegisterInfo *MRI = MIRBuilder.getMRI(); bool HasOclSampler = DemangledCall.contains_insensitive("ocl_sampler"); bool HasMsaa = DemangledCall.contains_insensitive("msaa"); if (HasOclSampler) { Register Sampler = Call->Arguments[1]; if (!GR->isScalarOfType(Sampler, SPIRV::OpTypeSampler) && getDefInstrMaybeConstant(Sampler, MRI)->getOperand(1).isCImm()) { uint64_t SamplerMask = getIConstVal(Sampler, MRI); Sampler = GR->buildConstantSampler( Register(), getSamplerAddressingModeFromBitmask(SamplerMask), getSamplerParamFromBitmask(SamplerMask), getSamplerFilterModeFromBitmask(SamplerMask), MIRBuilder, GR->getSPIRVTypeForVReg(Sampler)); } SPIRVType *ImageType = GR->getSPIRVTypeForVReg(Image); SPIRVType *SampledImageType = GR->getOrCreateOpTypeSampledImage(ImageType, MIRBuilder); Register SampledImage = MRI->createVirtualRegister(&SPIRV::iIDRegClass); MIRBuilder.buildInstr(SPIRV::OpSampledImage) .addDef(SampledImage) .addUse(GR->getSPIRVTypeID(SampledImageType)) .addUse(Image) .addUse(Sampler); Register Lod = GR->buildConstantFP(APFloat::getZero(APFloat::IEEEsingle()), MIRBuilder); if (Call->ReturnType->getOpcode() != SPIRV::OpTypeVector) { SPIRVType *TempType = GR->getOrCreateSPIRVVectorType(Call->ReturnType, 4, MIRBuilder); Register TempRegister = MRI->createGenericVirtualRegister(GR->getRegType(TempType)); MRI->setRegClass(TempRegister, GR->getRegClass(TempType)); GR->assignSPIRVTypeToVReg(TempType, TempRegister, MIRBuilder.getMF()); MIRBuilder.buildInstr(SPIRV::OpImageSampleExplicitLod) .addDef(TempRegister) .addUse(GR->getSPIRVTypeID(TempType)) .addUse(SampledImage) .addUse(Call->Arguments[2]) // Coordinate. .addImm(SPIRV::ImageOperand::Lod) .addUse(Lod); MIRBuilder.buildInstr(SPIRV::OpCompositeExtract) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(TempRegister) .addImm(0); } else { MIRBuilder.buildInstr(SPIRV::OpImageSampleExplicitLod) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(SampledImage) .addUse(Call->Arguments[2]) // Coordinate. .addImm(SPIRV::ImageOperand::Lod) .addUse(Lod); } } else if (HasMsaa) { MIRBuilder.buildInstr(SPIRV::OpImageRead) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Image) .addUse(Call->Arguments[1]) // Coordinate. .addImm(SPIRV::ImageOperand::Sample) .addUse(Call->Arguments[2]); } else { MIRBuilder.buildInstr(SPIRV::OpImageRead) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Image) .addUse(Call->Arguments[1]); // Coordinate. } return true; } static bool generateWriteImageInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { MIRBuilder.buildInstr(SPIRV::OpImageWrite) .addUse(Call->Arguments[0]) // Image. .addUse(Call->Arguments[1]) // Coordinate. .addUse(Call->Arguments[2]); // Texel. return true; } static bool generateSampleImageInst(const StringRef DemangledCall, const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { MachineRegisterInfo *MRI = MIRBuilder.getMRI(); if (Call->Builtin->Name.contains_insensitive( "__translate_sampler_initializer")) { // Build sampler literal. uint64_t Bitmask = getIConstVal(Call->Arguments[0], MRI); Register Sampler = GR->buildConstantSampler( Call->ReturnRegister, getSamplerAddressingModeFromBitmask(Bitmask), getSamplerParamFromBitmask(Bitmask), getSamplerFilterModeFromBitmask(Bitmask), MIRBuilder, Call->ReturnType); return Sampler.isValid(); } else if (Call->Builtin->Name.contains_insensitive("__spirv_SampledImage")) { // Create OpSampledImage. Register Image = Call->Arguments[0]; SPIRVType *ImageType = GR->getSPIRVTypeForVReg(Image); SPIRVType *SampledImageType = GR->getOrCreateOpTypeSampledImage(ImageType, MIRBuilder); Register SampledImage = Call->ReturnRegister.isValid() ? Call->ReturnRegister : MRI->createVirtualRegister(&SPIRV::iIDRegClass); MIRBuilder.buildInstr(SPIRV::OpSampledImage) .addDef(SampledImage) .addUse(GR->getSPIRVTypeID(SampledImageType)) .addUse(Image) .addUse(Call->Arguments[1]); // Sampler. return true; } else if (Call->Builtin->Name.contains_insensitive( "__spirv_ImageSampleExplicitLod")) { // Sample an image using an explicit level of detail. std::string ReturnType = DemangledCall.str(); if (DemangledCall.contains("_R")) { ReturnType = ReturnType.substr(ReturnType.find("_R") + 2); ReturnType = ReturnType.substr(0, ReturnType.find('(')); } SPIRVType *Type = Call->ReturnType ? Call->ReturnType : GR->getOrCreateSPIRVTypeByName(ReturnType, MIRBuilder); if (!Type) { std::string DiagMsg = "Unable to recognize SPIRV type name: " + ReturnType; report_fatal_error(DiagMsg.c_str()); } MIRBuilder.buildInstr(SPIRV::OpImageSampleExplicitLod) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Type)) .addUse(Call->Arguments[0]) // Image. .addUse(Call->Arguments[1]) // Coordinate. .addImm(SPIRV::ImageOperand::Lod) .addUse(Call->Arguments[3]); return true; } return false; } static bool generateSelectInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder) { MIRBuilder.buildSelect(Call->ReturnRegister, Call->Arguments[0], Call->Arguments[1], Call->Arguments[2]); return true; } static bool generateConstructInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { return buildOpFromWrapper(MIRBuilder, SPIRV::OpCompositeConstruct, Call, GR->getSPIRVTypeID(Call->ReturnType)); } static bool generateCoopMatrInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; bool IsSet = Opcode != SPIRV::OpCooperativeMatrixStoreKHR; unsigned ArgSz = Call->Arguments.size(); unsigned LiteralIdx = 0; if (Opcode == SPIRV::OpCooperativeMatrixLoadKHR && ArgSz > 3) LiteralIdx = 3; else if (Opcode == SPIRV::OpCooperativeMatrixStoreKHR && ArgSz > 4) LiteralIdx = 4; SmallVector ImmArgs; MachineRegisterInfo *MRI = MIRBuilder.getMRI(); if (LiteralIdx > 0) ImmArgs.push_back(getConstFromIntrinsic(Call->Arguments[LiteralIdx], MRI)); Register TypeReg = GR->getSPIRVTypeID(Call->ReturnType); if (Opcode == SPIRV::OpCooperativeMatrixLengthKHR) { SPIRVType *CoopMatrType = GR->getSPIRVTypeForVReg(Call->Arguments[0]); if (!CoopMatrType) report_fatal_error("Can't find a register's type definition"); MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(TypeReg) .addUse(CoopMatrType->getOperand(0).getReg()); return true; } return buildOpFromWrapper(MIRBuilder, Opcode, Call, IsSet ? TypeReg : Register(0), ImmArgs); } static bool generateSpecConstantInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; const MachineRegisterInfo *MRI = MIRBuilder.getMRI(); switch (Opcode) { case SPIRV::OpSpecConstant: { // Build the SpecID decoration. unsigned SpecId = static_cast(getIConstVal(Call->Arguments[0], MRI)); buildOpDecorate(Call->ReturnRegister, MIRBuilder, SPIRV::Decoration::SpecId, {SpecId}); // Determine the constant MI. Register ConstRegister = Call->Arguments[1]; const MachineInstr *Const = getDefInstrMaybeConstant(ConstRegister, MRI); assert(Const && (Const->getOpcode() == TargetOpcode::G_CONSTANT || Const->getOpcode() == TargetOpcode::G_FCONSTANT) && "Argument should be either an int or floating-point constant"); // Determine the opcode and built the OpSpec MI. const MachineOperand &ConstOperand = Const->getOperand(1); if (Call->ReturnType->getOpcode() == SPIRV::OpTypeBool) { assert(ConstOperand.isCImm() && "Int constant operand is expected"); Opcode = ConstOperand.getCImm()->getValue().getZExtValue() ? SPIRV::OpSpecConstantTrue : SPIRV::OpSpecConstantFalse; } auto MIB = MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)); if (Call->ReturnType->getOpcode() != SPIRV::OpTypeBool) { if (Const->getOpcode() == TargetOpcode::G_CONSTANT) addNumImm(ConstOperand.getCImm()->getValue(), MIB); else addNumImm(ConstOperand.getFPImm()->getValueAPF().bitcastToAPInt(), MIB); } return true; } case SPIRV::OpSpecConstantComposite: { auto MIB = MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)); for (unsigned i = 0; i < Call->Arguments.size(); i++) MIB.addUse(Call->Arguments[i]); return true; } default: return false; } } static bool buildNDRange(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { MachineRegisterInfo *MRI = MIRBuilder.getMRI(); SPIRVType *PtrType = GR->getSPIRVTypeForVReg(Call->Arguments[0]); assert(PtrType->getOpcode() == SPIRV::OpTypePointer && PtrType->getOperand(2).isReg()); Register TypeReg = PtrType->getOperand(2).getReg(); SPIRVType *StructType = GR->getSPIRVTypeForVReg(TypeReg); MachineFunction &MF = MIRBuilder.getMF(); Register TmpReg = MRI->createVirtualRegister(&SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(StructType, TmpReg, MF); // Skip the first arg, it's the destination pointer. OpBuildNDRange takes // three other arguments, so pass zero constant on absence. unsigned NumArgs = Call->Arguments.size(); assert(NumArgs >= 2); Register GlobalWorkSize = Call->Arguments[NumArgs < 4 ? 1 : 2]; Register LocalWorkSize = NumArgs == 2 ? Register(0) : Call->Arguments[NumArgs < 4 ? 2 : 3]; Register GlobalWorkOffset = NumArgs <= 3 ? Register(0) : Call->Arguments[1]; if (NumArgs < 4) { Register Const; SPIRVType *SpvTy = GR->getSPIRVTypeForVReg(GlobalWorkSize); if (SpvTy->getOpcode() == SPIRV::OpTypePointer) { MachineInstr *DefInstr = MRI->getUniqueVRegDef(GlobalWorkSize); assert(DefInstr && isSpvIntrinsic(*DefInstr, Intrinsic::spv_gep) && DefInstr->getOperand(3).isReg()); Register GWSPtr = DefInstr->getOperand(3).getReg(); // TODO: Maybe simplify generation of the type of the fields. unsigned Size = Call->Builtin->Name == "ndrange_3D" ? 3 : 2; unsigned BitWidth = GR->getPointerSize() == 64 ? 64 : 32; Type *BaseTy = IntegerType::get(MF.getFunction().getContext(), BitWidth); Type *FieldTy = ArrayType::get(BaseTy, Size); SPIRVType *SpvFieldTy = GR->getOrCreateSPIRVType(FieldTy, MIRBuilder); GlobalWorkSize = MRI->createVirtualRegister(&SPIRV::iIDRegClass); GR->assignSPIRVTypeToVReg(SpvFieldTy, GlobalWorkSize, MF); MIRBuilder.buildInstr(SPIRV::OpLoad) .addDef(GlobalWorkSize) .addUse(GR->getSPIRVTypeID(SpvFieldTy)) .addUse(GWSPtr); const SPIRVSubtarget &ST = cast(MIRBuilder.getMF().getSubtarget()); Const = GR->getOrCreateConstIntArray(0, Size, *MIRBuilder.getInsertPt(), SpvFieldTy, *ST.getInstrInfo()); } else { Const = GR->buildConstantInt(0, MIRBuilder, SpvTy); } if (!LocalWorkSize.isValid()) LocalWorkSize = Const; if (!GlobalWorkOffset.isValid()) GlobalWorkOffset = Const; } assert(LocalWorkSize.isValid() && GlobalWorkOffset.isValid()); MIRBuilder.buildInstr(SPIRV::OpBuildNDRange) .addDef(TmpReg) .addUse(TypeReg) .addUse(GlobalWorkSize) .addUse(LocalWorkSize) .addUse(GlobalWorkOffset); return MIRBuilder.buildInstr(SPIRV::OpStore) .addUse(Call->Arguments[0]) .addUse(TmpReg); } // TODO: maybe move to the global register. static SPIRVType * getOrCreateSPIRVDeviceEventPointer(MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { LLVMContext &Context = MIRBuilder.getMF().getFunction().getContext(); Type *OpaqueType = StructType::getTypeByName(Context, "spirv.DeviceEvent"); if (!OpaqueType) OpaqueType = StructType::getTypeByName(Context, "opencl.clk_event_t"); if (!OpaqueType) OpaqueType = StructType::create(Context, "spirv.DeviceEvent"); unsigned SC0 = storageClassToAddressSpace(SPIRV::StorageClass::Function); unsigned SC1 = storageClassToAddressSpace(SPIRV::StorageClass::Generic); Type *PtrType = PointerType::get(PointerType::get(OpaqueType, SC0), SC1); return GR->getOrCreateSPIRVType(PtrType, MIRBuilder); } static bool buildEnqueueKernel(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { MachineRegisterInfo *MRI = MIRBuilder.getMRI(); const DataLayout &DL = MIRBuilder.getDataLayout(); bool IsSpirvOp = Call->isSpirvOp(); bool HasEvents = Call->Builtin->Name.contains("events") || IsSpirvOp; const SPIRVType *Int32Ty = GR->getOrCreateSPIRVIntegerType(32, MIRBuilder); // Make vararg instructions before OpEnqueueKernel. // Local sizes arguments: Sizes of block invoke arguments. Clang generates // local size operands as an array, so we need to unpack them. SmallVector LocalSizes; if (Call->Builtin->Name.contains("_varargs") || IsSpirvOp) { const unsigned LocalSizeArrayIdx = HasEvents ? 9 : 6; Register GepReg = Call->Arguments[LocalSizeArrayIdx]; MachineInstr *GepMI = MRI->getUniqueVRegDef(GepReg); assert(isSpvIntrinsic(*GepMI, Intrinsic::spv_gep) && GepMI->getOperand(3).isReg()); Register ArrayReg = GepMI->getOperand(3).getReg(); MachineInstr *ArrayMI = MRI->getUniqueVRegDef(ArrayReg); const Type *LocalSizeTy = getMachineInstrType(ArrayMI); assert(LocalSizeTy && "Local size type is expected"); const uint64_t LocalSizeNum = cast(LocalSizeTy)->getNumElements(); unsigned SC = storageClassToAddressSpace(SPIRV::StorageClass::Generic); const LLT LLType = LLT::pointer(SC, GR->getPointerSize()); const SPIRVType *PointerSizeTy = GR->getOrCreateSPIRVPointerType( Int32Ty, MIRBuilder, SPIRV::StorageClass::Function); for (unsigned I = 0; I < LocalSizeNum; ++I) { Register Reg = MRI->createVirtualRegister(&SPIRV::iIDRegClass); MRI->setType(Reg, LLType); GR->assignSPIRVTypeToVReg(PointerSizeTy, Reg, MIRBuilder.getMF()); auto GEPInst = MIRBuilder.buildIntrinsic( Intrinsic::spv_gep, ArrayRef{Reg}, true, false); GEPInst .addImm(GepMI->getOperand(2).getImm()) // In bound. .addUse(ArrayMI->getOperand(0).getReg()) // Alloca. .addUse(buildConstantIntReg32(0, MIRBuilder, GR)) // Indices. .addUse(buildConstantIntReg32(I, MIRBuilder, GR)); LocalSizes.push_back(Reg); } } // SPIRV OpEnqueueKernel instruction has 10+ arguments. auto MIB = MIRBuilder.buildInstr(SPIRV::OpEnqueueKernel) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Int32Ty)); // Copy all arguments before block invoke function pointer. const unsigned BlockFIdx = HasEvents ? 6 : 3; for (unsigned i = 0; i < BlockFIdx; i++) MIB.addUse(Call->Arguments[i]); // If there are no event arguments in the original call, add dummy ones. if (!HasEvents) { MIB.addUse(buildConstantIntReg32(0, MIRBuilder, GR)); // Dummy num events. Register NullPtr = GR->getOrCreateConstNullPtr( MIRBuilder, getOrCreateSPIRVDeviceEventPointer(MIRBuilder, GR)); MIB.addUse(NullPtr); // Dummy wait events. MIB.addUse(NullPtr); // Dummy ret event. } MachineInstr *BlockMI = getBlockStructInstr(Call->Arguments[BlockFIdx], MRI); assert(BlockMI->getOpcode() == TargetOpcode::G_GLOBAL_VALUE); // Invoke: Pointer to invoke function. MIB.addGlobalAddress(BlockMI->getOperand(1).getGlobal()); Register BlockLiteralReg = Call->Arguments[BlockFIdx + 1]; // Param: Pointer to block literal. MIB.addUse(BlockLiteralReg); Type *PType = const_cast(getBlockStructType(BlockLiteralReg, MRI)); // TODO: these numbers should be obtained from block literal structure. // Param Size: Size of block literal structure. MIB.addUse(buildConstantIntReg32(DL.getTypeStoreSize(PType), MIRBuilder, GR)); // Param Aligment: Aligment of block literal structure. MIB.addUse(buildConstantIntReg32(DL.getPrefTypeAlign(PType).value(), MIRBuilder, GR)); for (unsigned i = 0; i < LocalSizes.size(); i++) MIB.addUse(LocalSizes[i]); return true; } static bool generateEnqueueInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; switch (Opcode) { case SPIRV::OpRetainEvent: case SPIRV::OpReleaseEvent: return MIRBuilder.buildInstr(Opcode).addUse(Call->Arguments[0]); case SPIRV::OpCreateUserEvent: case SPIRV::OpGetDefaultQueue: return MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)); case SPIRV::OpIsValidEvent: return MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Call->Arguments[0]); case SPIRV::OpSetUserEventStatus: return MIRBuilder.buildInstr(Opcode) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]); case SPIRV::OpCaptureEventProfilingInfo: return MIRBuilder.buildInstr(Opcode) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]) .addUse(Call->Arguments[2]); case SPIRV::OpBuildNDRange: return buildNDRange(Call, MIRBuilder, GR); case SPIRV::OpEnqueueKernel: return buildEnqueueKernel(Call, MIRBuilder, GR); default: return false; } } static bool generateAsyncCopy(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; bool IsSet = Opcode == SPIRV::OpGroupAsyncCopy; Register TypeReg = GR->getSPIRVTypeID(Call->ReturnType); if (Call->isSpirvOp()) return buildOpFromWrapper(MIRBuilder, Opcode, Call, IsSet ? TypeReg : Register(0)); auto Scope = buildConstantIntReg32(SPIRV::Scope::Workgroup, MIRBuilder, GR); switch (Opcode) { case SPIRV::OpGroupAsyncCopy: { SPIRVType *NewType = Call->ReturnType->getOpcode() == SPIRV::OpTypeEvent ? nullptr : GR->getOrCreateSPIRVTypeByName("spirv.Event", MIRBuilder); Register TypeReg = GR->getSPIRVTypeID(NewType ? NewType : Call->ReturnType); unsigned NumArgs = Call->Arguments.size(); Register EventReg = Call->Arguments[NumArgs - 1]; bool Res = MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(TypeReg) .addUse(Scope) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]) .addUse(Call->Arguments[2]) .addUse(Call->Arguments.size() > 4 ? Call->Arguments[3] : buildConstantIntReg32(1, MIRBuilder, GR)) .addUse(EventReg); if (NewType != nullptr) insertAssignInstr(Call->ReturnRegister, nullptr, NewType, GR, MIRBuilder, MIRBuilder.getMF().getRegInfo()); return Res; } case SPIRV::OpGroupWaitEvents: return MIRBuilder.buildInstr(Opcode) .addUse(Scope) .addUse(Call->Arguments[0]) .addUse(Call->Arguments[1]); default: return false; } } static bool generateConvertInst(const StringRef DemangledCall, const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the conversion builtin in the TableGen records. const SPIRV::ConvertBuiltin *Builtin = SPIRV::lookupConvertBuiltin(Call->Builtin->Name, Call->Builtin->Set); if (!Builtin && Call->isSpirvOp()) { const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; return buildOpFromWrapper(MIRBuilder, Opcode, Call, GR->getSPIRVTypeID(Call->ReturnType)); } if (Builtin->IsSaturated) buildOpDecorate(Call->ReturnRegister, MIRBuilder, SPIRV::Decoration::SaturatedConversion, {}); if (Builtin->IsRounded) buildOpDecorate(Call->ReturnRegister, MIRBuilder, SPIRV::Decoration::FPRoundingMode, {(unsigned)Builtin->RoundingMode}); std::string NeedExtMsg; // no errors if empty bool IsRightComponentsNumber = true; // check if input/output accepts vectors unsigned Opcode = SPIRV::OpNop; if (GR->isScalarOrVectorOfType(Call->Arguments[0], SPIRV::OpTypeInt)) { // Int -> ... if (GR->isScalarOrVectorOfType(Call->ReturnRegister, SPIRV::OpTypeInt)) { // Int -> Int if (Builtin->IsSaturated) Opcode = Builtin->IsDestinationSigned ? SPIRV::OpSatConvertUToS : SPIRV::OpSatConvertSToU; else Opcode = Builtin->IsDestinationSigned ? SPIRV::OpUConvert : SPIRV::OpSConvert; } else if (GR->isScalarOrVectorOfType(Call->ReturnRegister, SPIRV::OpTypeFloat)) { // Int -> Float if (Builtin->IsBfloat16) { const auto *ST = static_cast( &MIRBuilder.getMF().getSubtarget()); if (!ST->canUseExtension( SPIRV::Extension::SPV_INTEL_bfloat16_conversion)) NeedExtMsg = "SPV_INTEL_bfloat16_conversion"; IsRightComponentsNumber = GR->getScalarOrVectorComponentCount(Call->Arguments[0]) == GR->getScalarOrVectorComponentCount(Call->ReturnRegister); Opcode = SPIRV::OpConvertBF16ToFINTEL; } else { bool IsSourceSigned = DemangledCall[DemangledCall.find_first_of('(') + 1] != 'u'; Opcode = IsSourceSigned ? SPIRV::OpConvertSToF : SPIRV::OpConvertUToF; } } } else if (GR->isScalarOrVectorOfType(Call->Arguments[0], SPIRV::OpTypeFloat)) { // Float -> ... if (GR->isScalarOrVectorOfType(Call->ReturnRegister, SPIRV::OpTypeInt)) { // Float -> Int if (Builtin->IsBfloat16) { const auto *ST = static_cast( &MIRBuilder.getMF().getSubtarget()); if (!ST->canUseExtension( SPIRV::Extension::SPV_INTEL_bfloat16_conversion)) NeedExtMsg = "SPV_INTEL_bfloat16_conversion"; IsRightComponentsNumber = GR->getScalarOrVectorComponentCount(Call->Arguments[0]) == GR->getScalarOrVectorComponentCount(Call->ReturnRegister); Opcode = SPIRV::OpConvertFToBF16INTEL; } else { Opcode = Builtin->IsDestinationSigned ? SPIRV::OpConvertFToS : SPIRV::OpConvertFToU; } } else if (GR->isScalarOrVectorOfType(Call->ReturnRegister, SPIRV::OpTypeFloat)) { // Float -> Float Opcode = SPIRV::OpFConvert; } } if (!NeedExtMsg.empty()) { std::string DiagMsg = std::string(Builtin->Name) + ": the builtin requires the following SPIR-V " "extension: " + NeedExtMsg; report_fatal_error(DiagMsg.c_str(), false); } if (!IsRightComponentsNumber) { std::string DiagMsg = std::string(Builtin->Name) + ": result and argument must have the same number of components"; report_fatal_error(DiagMsg.c_str(), false); } assert(Opcode != SPIRV::OpNop && "Conversion between the types not implemented!"); MIRBuilder.buildInstr(Opcode) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addUse(Call->Arguments[0]); return true; } static bool generateVectorLoadStoreInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the vector load/store builtin in the TableGen records. const SPIRV::VectorLoadStoreBuiltin *Builtin = SPIRV::lookupVectorLoadStoreBuiltin(Call->Builtin->Name, Call->Builtin->Set); // Build extended instruction. auto MIB = MIRBuilder.buildInstr(SPIRV::OpExtInst) .addDef(Call->ReturnRegister) .addUse(GR->getSPIRVTypeID(Call->ReturnType)) .addImm(static_cast(SPIRV::InstructionSet::OpenCL_std)) .addImm(Builtin->Number); for (auto Argument : Call->Arguments) MIB.addUse(Argument); if (Builtin->Name.contains("load") && Builtin->ElementCount > 1) MIB.addImm(Builtin->ElementCount); // Rounding mode should be passed as a last argument in the MI for builtins // like "vstorea_halfn_r". if (Builtin->IsRounded) MIB.addImm(static_cast(Builtin->RoundingMode)); return true; } static bool generateLoadStoreInst(const SPIRV::IncomingCall *Call, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Lookup the instruction opcode in the TableGen records. const SPIRV::DemangledBuiltin *Builtin = Call->Builtin; unsigned Opcode = SPIRV::lookupNativeBuiltin(Builtin->Name, Builtin->Set)->Opcode; bool IsLoad = Opcode == SPIRV::OpLoad; // Build the instruction. auto MIB = MIRBuilder.buildInstr(Opcode); if (IsLoad) { MIB.addDef(Call->ReturnRegister); MIB.addUse(GR->getSPIRVTypeID(Call->ReturnType)); } // Add a pointer to the value to load/store. MIB.addUse(Call->Arguments[0]); MachineRegisterInfo *MRI = MIRBuilder.getMRI(); // Add a value to store. if (!IsLoad) MIB.addUse(Call->Arguments[1]); // Add optional memory attributes and an alignment. unsigned NumArgs = Call->Arguments.size(); if ((IsLoad && NumArgs >= 2) || NumArgs >= 3) MIB.addImm(getConstFromIntrinsic(Call->Arguments[IsLoad ? 1 : 2], MRI)); if ((IsLoad && NumArgs >= 3) || NumArgs >= 4) MIB.addImm(getConstFromIntrinsic(Call->Arguments[IsLoad ? 2 : 3], MRI)); return true; } namespace SPIRV { // Try to find a builtin function attributes by a demangled function name and // return a tuple , or a special // tuple value <-1, 0, 0> if the builtin function is not found. // Not all builtin functions are supported, only those with a ready-to-use op // code or instruction number defined in TableGen. // TODO: consider a major rework of mapping demangled calls into a builtin // functions to unify search and decrease number of individual cases. std::tuple mapBuiltinToOpcode(const StringRef DemangledCall, SPIRV::InstructionSet::InstructionSet Set) { Register Reg; SmallVector Args; std::unique_ptr Call = lookupBuiltin(DemangledCall, Set, Reg, nullptr, Args); if (!Call) return std::make_tuple(-1, 0, 0); switch (Call->Builtin->Group) { case SPIRV::Relational: case SPIRV::Atomic: case SPIRV::Barrier: case SPIRV::CastToPtr: case SPIRV::ImageMiscQuery: case SPIRV::SpecConstant: case SPIRV::Enqueue: case SPIRV::AsyncCopy: case SPIRV::LoadStore: case SPIRV::CoopMatr: if (const auto *R = SPIRV::lookupNativeBuiltin(Call->Builtin->Name, Call->Builtin->Set)) return std::make_tuple(Call->Builtin->Group, R->Opcode, 0); break; case SPIRV::Extended: if (const auto *R = SPIRV::lookupExtendedBuiltin(Call->Builtin->Name, Call->Builtin->Set)) return std::make_tuple(Call->Builtin->Group, 0, R->Number); break; case SPIRV::VectorLoadStore: if (const auto *R = SPIRV::lookupVectorLoadStoreBuiltin(Call->Builtin->Name, Call->Builtin->Set)) return std::make_tuple(SPIRV::Extended, 0, R->Number); break; case SPIRV::Group: if (const auto *R = SPIRV::lookupGroupBuiltin(Call->Builtin->Name)) return std::make_tuple(Call->Builtin->Group, R->Opcode, 0); break; case SPIRV::AtomicFloating: if (const auto *R = SPIRV::lookupAtomicFloatingBuiltin(Call->Builtin->Name)) return std::make_tuple(Call->Builtin->Group, R->Opcode, 0); break; case SPIRV::IntelSubgroups: if (const auto *R = SPIRV::lookupIntelSubgroupsBuiltin(Call->Builtin->Name)) return std::make_tuple(Call->Builtin->Group, R->Opcode, 0); break; case SPIRV::GroupUniform: if (const auto *R = SPIRV::lookupGroupUniformBuiltin(Call->Builtin->Name)) return std::make_tuple(Call->Builtin->Group, R->Opcode, 0); break; case SPIRV::WriteImage: return std::make_tuple(Call->Builtin->Group, SPIRV::OpImageWrite, 0); case SPIRV::Select: return std::make_tuple(Call->Builtin->Group, TargetOpcode::G_SELECT, 0); case SPIRV::Construct: return std::make_tuple(Call->Builtin->Group, SPIRV::OpCompositeConstruct, 0); case SPIRV::KernelClock: return std::make_tuple(Call->Builtin->Group, SPIRV::OpReadClockKHR, 0); default: return std::make_tuple(-1, 0, 0); } return std::make_tuple(-1, 0, 0); } std::optional lowerBuiltin(const StringRef DemangledCall, SPIRV::InstructionSet::InstructionSet Set, MachineIRBuilder &MIRBuilder, const Register OrigRet, const Type *OrigRetTy, const SmallVectorImpl &Args, SPIRVGlobalRegistry *GR) { LLVM_DEBUG(dbgs() << "Lowering builtin call: " << DemangledCall << "\n"); // SPIR-V type and return register. Register ReturnRegister = OrigRet; SPIRVType *ReturnType = nullptr; if (OrigRetTy && !OrigRetTy->isVoidTy()) { ReturnType = GR->assignTypeToVReg(OrigRetTy, ReturnRegister, MIRBuilder); if (!MIRBuilder.getMRI()->getRegClassOrNull(ReturnRegister)) MIRBuilder.getMRI()->setRegClass(ReturnRegister, GR->getRegClass(ReturnType)); } else if (OrigRetTy && OrigRetTy->isVoidTy()) { ReturnRegister = MIRBuilder.getMRI()->createVirtualRegister(&IDRegClass); MIRBuilder.getMRI()->setType(ReturnRegister, LLT::scalar(64)); ReturnType = GR->assignTypeToVReg(OrigRetTy, ReturnRegister, MIRBuilder); } // Lookup the builtin in the TableGen records. std::unique_ptr Call = lookupBuiltin(DemangledCall, Set, ReturnRegister, ReturnType, Args); if (!Call) { LLVM_DEBUG(dbgs() << "Builtin record was not found!\n"); return std::nullopt; } // TODO: check if the provided args meet the builtin requirments. assert(Args.size() >= Call->Builtin->MinNumArgs && "Too few arguments to generate the builtin"); if (Call->Builtin->MaxNumArgs && Args.size() > Call->Builtin->MaxNumArgs) LLVM_DEBUG(dbgs() << "More arguments provided than required!\n"); // Match the builtin with implementation based on the grouping. switch (Call->Builtin->Group) { case SPIRV::Extended: return generateExtInst(Call.get(), MIRBuilder, GR); case SPIRV::Relational: return generateRelationalInst(Call.get(), MIRBuilder, GR); case SPIRV::Group: return generateGroupInst(Call.get(), MIRBuilder, GR); case SPIRV::Variable: return generateBuiltinVar(Call.get(), MIRBuilder, GR); case SPIRV::Atomic: return generateAtomicInst(Call.get(), MIRBuilder, GR); case SPIRV::AtomicFloating: return generateAtomicFloatingInst(Call.get(), MIRBuilder, GR); case SPIRV::Barrier: return generateBarrierInst(Call.get(), MIRBuilder, GR); case SPIRV::CastToPtr: return generateCastToPtrInst(Call.get(), MIRBuilder); case SPIRV::Dot: return generateDotOrFMulInst(Call.get(), MIRBuilder, GR); case SPIRV::Wave: return generateWaveInst(Call.get(), MIRBuilder, GR); case SPIRV::GetQuery: return generateGetQueryInst(Call.get(), MIRBuilder, GR); case SPIRV::ImageSizeQuery: return generateImageSizeQueryInst(Call.get(), MIRBuilder, GR); case SPIRV::ImageMiscQuery: return generateImageMiscQueryInst(Call.get(), MIRBuilder, GR); case SPIRV::ReadImage: return generateReadImageInst(DemangledCall, Call.get(), MIRBuilder, GR); case SPIRV::WriteImage: return generateWriteImageInst(Call.get(), MIRBuilder, GR); case SPIRV::SampleImage: return generateSampleImageInst(DemangledCall, Call.get(), MIRBuilder, GR); case SPIRV::Select: return generateSelectInst(Call.get(), MIRBuilder); case SPIRV::Construct: return generateConstructInst(Call.get(), MIRBuilder, GR); case SPIRV::SpecConstant: return generateSpecConstantInst(Call.get(), MIRBuilder, GR); case SPIRV::Enqueue: return generateEnqueueInst(Call.get(), MIRBuilder, GR); case SPIRV::AsyncCopy: return generateAsyncCopy(Call.get(), MIRBuilder, GR); case SPIRV::Convert: return generateConvertInst(DemangledCall, Call.get(), MIRBuilder, GR); case SPIRV::VectorLoadStore: return generateVectorLoadStoreInst(Call.get(), MIRBuilder, GR); case SPIRV::LoadStore: return generateLoadStoreInst(Call.get(), MIRBuilder, GR); case SPIRV::IntelSubgroups: return generateIntelSubgroupsInst(Call.get(), MIRBuilder, GR); case SPIRV::GroupUniform: return generateGroupUniformInst(Call.get(), MIRBuilder, GR); case SPIRV::KernelClock: return generateKernelClockInst(Call.get(), MIRBuilder, GR); case SPIRV::CoopMatr: return generateCoopMatrInst(Call.get(), MIRBuilder, GR); } return false; } Type *parseBuiltinCallArgumentBaseType(const StringRef DemangledCall, unsigned ArgIdx, LLVMContext &Ctx) { SmallVector BuiltinArgsTypeStrs; StringRef BuiltinArgs = DemangledCall.slice(DemangledCall.find('(') + 1, DemangledCall.find(')')); BuiltinArgs.split(BuiltinArgsTypeStrs, ',', -1, false); if (ArgIdx >= BuiltinArgsTypeStrs.size()) return nullptr; StringRef TypeStr = BuiltinArgsTypeStrs[ArgIdx].trim(); // Parse strings representing OpenCL builtin types. if (hasBuiltinTypePrefix(TypeStr)) { // OpenCL builtin types in demangled call strings have the following format: // e.g. ocl_image2d_ro [[maybe_unused]] bool IsOCLBuiltinType = TypeStr.consume_front("ocl_"); assert(IsOCLBuiltinType && "Invalid OpenCL builtin prefix"); // Check if this is pointer to a builtin type and not just pointer // representing a builtin type. In case it is a pointer to builtin type, // this will require additional handling in the method calling // parseBuiltinCallArgumentBaseType(...) as this function only retrieves the // base types. if (TypeStr.ends_with("*")) TypeStr = TypeStr.slice(0, TypeStr.find_first_of(" *")); return parseBuiltinTypeNameToTargetExtType("opencl." + TypeStr.str() + "_t", Ctx); } // Parse type name in either "typeN" or "type vector[N]" format, where // N is the number of elements of the vector. Type *BaseType; unsigned VecElts = 0; BaseType = parseBasicTypeName(TypeStr, Ctx); if (!BaseType) // Unable to recognize SPIRV type name. return nullptr; // Handle "typeN*" or "type vector[N]*". TypeStr.consume_back("*"); if (TypeStr.consume_front(" vector[")) TypeStr = TypeStr.substr(0, TypeStr.find(']')); TypeStr.getAsInteger(10, VecElts); if (VecElts > 0) BaseType = VectorType::get( BaseType->isVoidTy() ? Type::getInt8Ty(Ctx) : BaseType, VecElts, false); return BaseType; } struct BuiltinType { StringRef Name; uint32_t Opcode; }; #define GET_BuiltinTypes_DECL #define GET_BuiltinTypes_IMPL struct OpenCLType { StringRef Name; StringRef SpirvTypeLiteral; }; #define GET_OpenCLTypes_DECL #define GET_OpenCLTypes_IMPL #include "SPIRVGenTables.inc" } // namespace SPIRV //===----------------------------------------------------------------------===// // Misc functions for parsing builtin types. //===----------------------------------------------------------------------===// static Type *parseTypeString(const StringRef Name, LLVMContext &Context) { if (Name.starts_with("void")) return Type::getVoidTy(Context); else if (Name.starts_with("int") || Name.starts_with("uint")) return Type::getInt32Ty(Context); else if (Name.starts_with("float")) return Type::getFloatTy(Context); else if (Name.starts_with("half")) return Type::getHalfTy(Context); report_fatal_error("Unable to recognize type!"); } //===----------------------------------------------------------------------===// // Implementation functions for builtin types. //===----------------------------------------------------------------------===// static SPIRVType *getNonParameterizedType(const TargetExtType *ExtensionType, const SPIRV::BuiltinType *TypeRecord, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { unsigned Opcode = TypeRecord->Opcode; // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypeByOpcode(ExtensionType, MIRBuilder, Opcode); } static SPIRVType *getSamplerType(MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypeSampler(MIRBuilder); } static SPIRVType *getPipeType(const TargetExtType *ExtensionType, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { assert(ExtensionType->getNumIntParameters() == 1 && "Invalid number of parameters for SPIR-V pipe builtin!"); // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypePipe(MIRBuilder, SPIRV::AccessQualifier::AccessQualifier( ExtensionType->getIntParameter(0))); } static SPIRVType *getCoopMatrType(const TargetExtType *ExtensionType, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { assert(ExtensionType->getNumIntParameters() == 4 && "Invalid number of parameters for SPIR-V coop matrices builtin!"); assert(ExtensionType->getNumTypeParameters() == 1 && "SPIR-V coop matrices builtin type must have a type parameter!"); const SPIRVType *ElemType = GR->getOrCreateSPIRVType(ExtensionType->getTypeParameter(0), MIRBuilder); // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypeCoopMatr( MIRBuilder, ExtensionType, ElemType, ExtensionType->getIntParameter(0), ExtensionType->getIntParameter(1), ExtensionType->getIntParameter(2), ExtensionType->getIntParameter(3)); } static SPIRVType * getImageType(const TargetExtType *ExtensionType, const SPIRV::AccessQualifier::AccessQualifier Qualifier, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { assert(ExtensionType->getNumTypeParameters() == 1 && "SPIR-V image builtin type must have sampled type parameter!"); const SPIRVType *SampledType = GR->getOrCreateSPIRVType(ExtensionType->getTypeParameter(0), MIRBuilder); assert((ExtensionType->getNumIntParameters() == 7 || ExtensionType->getNumIntParameters() == 6) && "Invalid number of parameters for SPIR-V image builtin!"); SPIRV::AccessQualifier::AccessQualifier accessQualifier = SPIRV::AccessQualifier::None; if (ExtensionType->getNumIntParameters() == 7) { accessQualifier = Qualifier == SPIRV::AccessQualifier::WriteOnly ? SPIRV::AccessQualifier::WriteOnly : SPIRV::AccessQualifier::AccessQualifier( ExtensionType->getIntParameter(6)); } // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypeImage( MIRBuilder, SampledType, SPIRV::Dim::Dim(ExtensionType->getIntParameter(0)), ExtensionType->getIntParameter(1), ExtensionType->getIntParameter(2), ExtensionType->getIntParameter(3), ExtensionType->getIntParameter(4), SPIRV::ImageFormat::ImageFormat(ExtensionType->getIntParameter(5)), accessQualifier); } static SPIRVType *getSampledImageType(const TargetExtType *OpaqueType, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { SPIRVType *OpaqueImageType = getImageType( OpaqueType, SPIRV::AccessQualifier::ReadOnly, MIRBuilder, GR); // Create or get an existing type from GlobalRegistry. return GR->getOrCreateOpTypeSampledImage(OpaqueImageType, MIRBuilder); } namespace SPIRV { TargetExtType *parseBuiltinTypeNameToTargetExtType(std::string TypeName, LLVMContext &Context) { StringRef NameWithParameters = TypeName; // Pointers-to-opaque-structs representing OpenCL types are first translated // to equivalent SPIR-V types. OpenCL builtin type names should have the // following format: e.g. %opencl.event_t if (NameWithParameters.starts_with("opencl.")) { const SPIRV::OpenCLType *OCLTypeRecord = SPIRV::lookupOpenCLType(NameWithParameters); if (!OCLTypeRecord) report_fatal_error("Missing TableGen record for OpenCL type: " + NameWithParameters); NameWithParameters = OCLTypeRecord->SpirvTypeLiteral; // Continue with the SPIR-V builtin type... } // Names of the opaque structs representing a SPIR-V builtins without // parameters should have the following format: e.g. %spirv.Event assert(NameWithParameters.starts_with("spirv.") && "Unknown builtin opaque type!"); // Parameterized SPIR-V builtins names follow this format: // e.g. %spirv.Image._void_1_0_0_0_0_0_0, %spirv.Pipe._0 if (!NameWithParameters.contains('_')) return TargetExtType::get(Context, NameWithParameters); SmallVector Parameters; unsigned BaseNameLength = NameWithParameters.find('_') - 1; SplitString(NameWithParameters.substr(BaseNameLength + 1), Parameters, "_"); SmallVector TypeParameters; bool HasTypeParameter = !isDigit(Parameters[0][0]); if (HasTypeParameter) TypeParameters.push_back(parseTypeString(Parameters[0], Context)); SmallVector IntParameters; for (unsigned i = HasTypeParameter ? 1 : 0; i < Parameters.size(); i++) { unsigned IntParameter = 0; bool ValidLiteral = !Parameters[i].getAsInteger(10, IntParameter); (void)ValidLiteral; assert(ValidLiteral && "Invalid format of SPIR-V builtin parameter literal!"); IntParameters.push_back(IntParameter); } return TargetExtType::get(Context, NameWithParameters.substr(0, BaseNameLength), TypeParameters, IntParameters); } SPIRVType *lowerBuiltinType(const Type *OpaqueType, SPIRV::AccessQualifier::AccessQualifier AccessQual, MachineIRBuilder &MIRBuilder, SPIRVGlobalRegistry *GR) { // In LLVM IR, SPIR-V and OpenCL builtin types are represented as either // target(...) target extension types or pointers-to-opaque-structs. The // approach relying on structs is deprecated and works only in the non-opaque // pointer mode (-opaque-pointers=0). // In order to maintain compatibility with LLVM IR generated by older versions // of Clang and LLVM/SPIR-V Translator, the pointers-to-opaque-structs are // "translated" to target extension types. This translation is temporary and // will be removed in the future release of LLVM. const TargetExtType *BuiltinType = dyn_cast(OpaqueType); if (!BuiltinType) BuiltinType = parseBuiltinTypeNameToTargetExtType( OpaqueType->getStructName().str(), MIRBuilder.getContext()); unsigned NumStartingVRegs = MIRBuilder.getMRI()->getNumVirtRegs(); const StringRef Name = BuiltinType->getName(); LLVM_DEBUG(dbgs() << "Lowering builtin type: " << Name << "\n"); // Lookup the demangled builtin type in the TableGen records. const SPIRV::BuiltinType *TypeRecord = SPIRV::lookupBuiltinType(Name); if (!TypeRecord) report_fatal_error("Missing TableGen record for builtin type: " + Name); // "Lower" the BuiltinType into TargetType. The following get<...>Type methods // use the implementation details from TableGen records or TargetExtType // parameters to either create a new OpType<...> machine instruction or get an // existing equivalent SPIRVType from GlobalRegistry. SPIRVType *TargetType; switch (TypeRecord->Opcode) { case SPIRV::OpTypeImage: TargetType = getImageType(BuiltinType, AccessQual, MIRBuilder, GR); break; case SPIRV::OpTypePipe: TargetType = getPipeType(BuiltinType, MIRBuilder, GR); break; case SPIRV::OpTypeDeviceEvent: TargetType = GR->getOrCreateOpTypeDeviceEvent(MIRBuilder); break; case SPIRV::OpTypeSampler: TargetType = getSamplerType(MIRBuilder, GR); break; case SPIRV::OpTypeSampledImage: TargetType = getSampledImageType(BuiltinType, MIRBuilder, GR); break; case SPIRV::OpTypeCooperativeMatrixKHR: TargetType = getCoopMatrType(BuiltinType, MIRBuilder, GR); break; default: TargetType = getNonParameterizedType(BuiltinType, TypeRecord, MIRBuilder, GR); break; } // Emit OpName instruction if a new OpType<...> instruction was added // (equivalent type was not found in GlobalRegistry). if (NumStartingVRegs < MIRBuilder.getMRI()->getNumVirtRegs()) buildOpName(GR->getSPIRVTypeID(TargetType), Name, MIRBuilder); return TargetType; } } // namespace SPIRV } // namespace llvm