diff --git a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp --- a/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp +++ b/llvm/lib/Target/SPIRV/MCTargetDesc/SPIRVMCTargetDesc.cpp @@ -88,7 +88,8 @@ } extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTargetMC() { - for (Target *T : {&getTheSPIRV32Target(), &getTheSPIRV64Target()}) { + for (Target *T : {&getTheSPIRV32Target(), &getTheSPIRV64Target(), + &getTheSPIRVLogicalTarget()}) { RegisterMCAsmInfo X(*T); TargetRegistry::RegisterMCInstrInfo(*T, createSPIRVMCInstrInfo); TargetRegistry::RegisterMCRegInfo(*T, createSPIRVMCRegisterInfo); diff --git a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp --- a/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVAsmPrinter.cpp @@ -66,6 +66,9 @@ void outputExtFuncDecls(); void outputExecutionModeFromMDNode(Register Reg, MDNode *Node, SPIRV::ExecutionMode::ExecutionMode EM); + void outputExecutionModeFromNumthreadsAttribute( + const Register &Reg, const Attribute &Attr, + SPIRV::ExecutionMode::ExecutionMode EM); void outputExecutionMode(const Module &M); void outputAnnotations(const Module &M); void outputModuleSections(); @@ -412,6 +415,29 @@ outputMCInst(Inst); } +void SPIRVAsmPrinter::outputExecutionModeFromNumthreadsAttribute( + const Register &Reg, const Attribute &Attr, + SPIRV::ExecutionMode::ExecutionMode EM) { + assert(Attr.isValid() && "Function called with an invalid attribute."); + + MCInst Inst; + Inst.setOpcode(SPIRV::OpExecutionMode); + Inst.addOperand(MCOperand::createReg(Reg)); + Inst.addOperand(MCOperand::createImm(static_cast(EM))); + + SmallVector NumThreads; + Attr.getValueAsString().split(NumThreads, ','); + assert(NumThreads.size() == 3 && "invalid numthreads"); + for (uint32_t i = 0; i < 3; ++i) { + uint32_t V; + [[maybe_unused]] bool Result = NumThreads[i].getAsInteger(10, V); + assert(!Result && "Failed to parse numthreads"); + Inst.addOperand(MCOperand::createImm(V)); + } + + outputMCInst(Inst); +} + void SPIRVAsmPrinter::outputExecutionMode(const Module &M) { NamedMDNode *Node = M.getNamedMetadata("spirv.ExecutionMode"); if (Node) { @@ -431,6 +457,9 @@ if (MDNode *Node = F.getMetadata("reqd_work_group_size")) outputExecutionModeFromMDNode(FReg, Node, SPIRV::ExecutionMode::LocalSize); + if (Attribute Attr = F.getFnAttribute("hlsl.numthreads"); Attr.isValid()) + outputExecutionModeFromNumthreadsAttribute( + FReg, Attr, SPIRV::ExecutionMode::LocalSize); if (MDNode *Node = F.getMetadata("work_group_size_hint")) outputExecutionModeFromMDNode(FReg, Node, SPIRV::ExecutionMode::LocalSizeHint); @@ -447,7 +476,7 @@ Inst.addOperand(MCOperand::createImm(TypeCode)); outputMCInst(Inst); } - if (!M.getNamedMetadata("spirv.ExecutionMode") && + if (ST->isOpenCLEnv() && !M.getNamedMetadata("spirv.ExecutionMode") && !M.getNamedMetadata("opencl.enable.FP_CONTRACT")) { MCInst Inst; Inst.setOpcode(SPIRV::OpExecutionMode); @@ -542,4 +571,5 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVAsmPrinter() { RegisterAsmPrinter X(getTheSPIRV32Target()); RegisterAsmPrinter Y(getTheSPIRV64Target()); + RegisterAsmPrinter Z(getTheSPIRVLogicalTarget()); } diff --git a/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp b/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp --- a/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVCallLowering.cpp @@ -213,6 +213,41 @@ : StructType::create(F.getContext(), KernelArgTypeStr); } +static bool isEntrypoint(const Function &F) { + // OpenCL handling: any function with the SPIR_KERNEL + // calling convention will be a potential entrypoint. + if (F.getCallingConv() == CallingConv::SPIR_KERNEL) { + return true; + } + + // HLSL handling: special attribute are emitted from the + // front-end. + if (F.getFnAttribute("hlsl.shader").isValid()) { + return true; + } + + return false; +} + +static SPIRV::ExecutionModel::ExecutionModel +getExecutionModel(const SPIRVSubtarget &STI, const Function &F) { + if (STI.isOpenCLEnv()) { + return SPIRV::ExecutionModel::Kernel; + } + + auto attribute = F.getFnAttribute("hlsl.shader"); + if (!attribute.isValid()) { + report_fatal_error( + "This entrypoint lacks mandatory hlsl.shader attribute."); + } + + const auto value = attribute.getValueAsString(); + if (value == "compute") + return SPIRV::ExecutionModel::GLCompute; + + report_fatal_error("This HLSL entrypoint is not supported by this backend."); +} + bool SPIRVCallLowering::lowerFormalArguments(MachineIRBuilder &MIRBuilder, const Function &F, ArrayRef> VRegs, @@ -336,9 +371,11 @@ buildOpName(FuncVReg, F.getName(), MIRBuilder); // Handle entry points and function linkage. - if (F.getCallingConv() == CallingConv::SPIR_KERNEL) { + if (isEntrypoint(F)) { + const auto &STI = MIRBuilder.getMF().getSubtarget(); + auto executionModel = getExecutionModel(STI, F); auto MIB = MIRBuilder.buildInstr(SPIRV::OpEntryPoint) - .addImm(static_cast(SPIRV::ExecutionModel::Kernel)) + .addImm(static_cast(executionModel)) .addUse(FuncVReg); addStringImm(F.getName(), MIB); } else if (F.getLinkage() == GlobalValue::LinkageTypes::ExternalLinkage || diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h --- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.h @@ -69,6 +69,9 @@ // recursing through their implicitly declared capabilities too. void pruneCapabilities(const CapabilityList &ToPrune); + void initAvailableCapabilitiesForOpenCL(const SPIRVSubtarget &ST); + void initAvailableCapabilitiesForVulkan(const SPIRVSubtarget &ST); + public: RequirementHandler() : MinVersion(0), MaxVersion(0) {} void clear() { diff --git a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp --- a/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp @@ -106,11 +106,18 @@ MAI.Mem = static_cast(getMetadataUInt(MemMD, 1)); } else { - MAI.Mem = SPIRV::MemoryModel::OpenCL; - unsigned PtrSize = ST->getPointerSize(); - MAI.Addr = PtrSize == 32 ? SPIRV::AddressingModel::Physical32 - : PtrSize == 64 ? SPIRV::AddressingModel::Physical64 - : SPIRV::AddressingModel::Logical; + // TODO: Add support for VulkanMemoryModel. + MAI.Mem = ST->isOpenCLEnv() ? SPIRV::MemoryModel::OpenCL + : SPIRV::MemoryModel::GLSL450; + if (MAI.Mem == SPIRV::MemoryModel::OpenCL) { + unsigned PtrSize = ST->getPointerSize(); + MAI.Addr = PtrSize == 32 ? SPIRV::AddressingModel::Physical32 + : PtrSize == 64 ? SPIRV::AddressingModel::Physical64 + : SPIRV::AddressingModel::Logical; + } else { + // TODO: Add support for PhysicalStorageBufferAddress. + MAI.Addr = SPIRV::AddressingModel::Logical; + } } // Get the OpenCL version number from metadata. // TODO: support other source languages. @@ -148,9 +155,12 @@ MAI.Reqs.getAndAddRequirements(SPIRV::OperandCategory::AddressingModelOperand, MAI.Addr, *ST); - // TODO: check if it's required by default. - MAI.ExtInstSetMap[static_cast(SPIRV::InstructionSet::OpenCL_std)] = - Register::index2VirtReg(MAI.getNextID()); + if (ST->isOpenCLEnv()) { + // TODO: check if it's required by default. + MAI.ExtInstSetMap[static_cast( + SPIRV::InstructionSet::OpenCL_std)] = + Register::index2VirtReg(MAI.getNextID()); + } } // Collect MI which defines the register in the given machine function. @@ -516,9 +526,21 @@ namespace llvm { namespace SPIRV { void RequirementHandler::initAvailableCapabilities(const SPIRVSubtarget &ST) { - // TODO: Implemented for other targets other then OpenCL. - if (!ST.isOpenCLEnv()) + if (ST.isOpenCLEnv()) { + initAvailableCapabilitiesForOpenCL(ST); + return; + } + + if (ST.isVulkanEnv()) { + initAvailableCapabilitiesForVulkan(ST); return; + } + + report_fatal_error("Unimplemented environment for SPIR-V generation."); +} + +void RequirementHandler::initAvailableCapabilitiesForOpenCL( + const SPIRVSubtarget &ST) { // Add the min requirements for different OpenCL and SPIR-V versions. addAvailableCaps({Capability::Addresses, Capability::Float16Buffer, Capability::Int16, Capability::Int8, Capability::Kernel, @@ -558,6 +580,12 @@ // TODO: add OpenCL extensions. } + +void RequirementHandler::initAvailableCapabilitiesForVulkan( + const SPIRVSubtarget &ST) { + addAvailableCaps({Capability::Shader, Capability::Linkage}); +} + } // namespace SPIRV } // namespace llvm @@ -886,10 +914,23 @@ const Function &F = *FI; if (F.isDeclaration()) continue; - if (F.getMetadata("reqd_work_group_size")) + + if (F.getMetadata("reqd_work_group_size") && + F.getFnAttribute("hlsl.numthreads").isValid()) { + report_fatal_error("Invalid IR: 'reqd_work_group_size' and " + "'hlsl.numthreads' cannot be present together."); + } + + if (F.getMetadata("reqd_work_group_size")) { MAI.Reqs.getAndAddRequirements( SPIRV::OperandCategory::ExecutionModeOperand, SPIRV::ExecutionMode::LocalSize, ST); + } else if (F.getFnAttribute("hlsl.numthreads").isValid()) { + MAI.Reqs.getAndAddRequirements( + SPIRV::OperandCategory::ExecutionModeOperand, + SPIRV::ExecutionMode::LocalSize, ST); + } + if (F.getMetadata("work_group_size_hint")) MAI.Reqs.getAndAddRequirements( SPIRV::OperandCategory::ExecutionModeOperand, diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.h b/llvm/lib/Target/SPIRV/SPIRVSubtarget.h --- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.h +++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.h @@ -24,6 +24,7 @@ #include "llvm/CodeGen/TargetSubtargetInfo.h" #include "llvm/IR/DataLayout.h" #include "llvm/Target/TargetMachine.h" +#include "llvm/TargetParser/Triple.h" #define GET_SUBTARGETINFO_HEADER #include "SPIRVGenSubtargetInfo.inc" @@ -45,6 +46,7 @@ SPIRVInstrInfo InstrInfo; SPIRVFrameLowering FrameLowering; SPIRVTargetLowering TLInfo; + Triple TargetTriple; // GlobalISel related APIs. std::unique_ptr CallLoweringInfo; @@ -70,9 +72,13 @@ unsigned getPointerSize() const { return PointerSize; } bool canDirectlyComparePointers() const; // TODO: this environment is not implemented in Triple, we need to decide - // how to standartize its support. For now, let's assume that we always - // operate with OpenCL. - bool isOpenCLEnv() const { return true; } + // how to standardize its support. For now, let's assume SPIR-V with physical + // addressing is OpenCL, and Logical addressing is Vulkan. + bool isOpenCLEnv() const { + return TargetTriple.getArch() == Triple::spirv32 || + TargetTriple.getArch() == Triple::spirv64; + } + bool isVulkanEnv() const { return TargetTriple.getArch() == Triple::spirv; } uint32_t getSPIRVVersion() const { return SPIRVVersion; }; bool isAtLeastSPIRVVer(uint32_t VerToCompareTo) const; bool isAtLeastOpenCLVer(uint32_t VerToCompareTo) const; diff --git a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp --- a/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVSubtarget.cpp @@ -32,20 +32,14 @@ return Target == 0 || Target >= VerToCompareTo; } -static unsigned computePointerSize(const Triple &TT) { - const auto Arch = TT.getArch(); - // TODO: unify this with pointers legalization. - assert(TT.isSPIRV()); - return Arch == Triple::spirv32 ? 32 : 64; -} - SPIRVSubtarget::SPIRVSubtarget(const Triple &TT, const std::string &CPU, const std::string &FS, const SPIRVTargetMachine &TM) : SPIRVGenSubtargetInfo(TT, CPU, /*TuneCPU=*/CPU, FS), - PointerSize(computePointerSize(TT)), SPIRVVersion(0), OpenCLVersion(0), - InstrInfo(), FrameLowering(initSubtargetDependencies(CPU, FS)), - TLInfo(TM, *this) { + PointerSize(TM.getPointerSizeInBits(/* AS= */ 0)), SPIRVVersion(0), + OpenCLVersion(0), InstrInfo(), + FrameLowering(initSubtargetDependencies(CPU, FS)), TLInfo(TM, *this), + TargetTriple(TT) { // The order of initialization is important. initAvailableExtensions(); initAvailableExtInstSets(); @@ -82,6 +76,10 @@ } bool SPIRVSubtarget::isAtLeastOpenCLVer(uint32_t VerToCompareTo) const { + if (!isOpenCLEnv()) { + return false; + } + return isAtLeastVer(OpenCLVersion, VerToCompareTo); } diff --git a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp --- a/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp @@ -37,6 +37,7 @@ // Register the target. RegisterTargetMachine X(getTheSPIRV32Target()); RegisterTargetMachine Y(getTheSPIRV64Target()); + RegisterTargetMachine Z(getTheSPIRVLogicalTarget()); PassRegistry &PR = *PassRegistry::getPassRegistry(); initializeGlobalISel(PR); @@ -45,7 +46,11 @@ static std::string computeDataLayout(const Triple &TT) { const auto Arch = TT.getArch(); - if (Arch == Triple::spirv32) + // TODO: this probably needs to be revisited: + // AFAIU Logical SPIR-V has no pointer size. So falling-back on ID size. + // Addressing mode can change how some pointers are handled + // (PhysicalStorageBuffer64). + if (Arch == Triple::spirv32 || Arch == Triple::spirv) return "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-" "v96:128-v192:256-v256:256-v512:512-v1024:1024"; return "e-i64:64-v16:16-v24:32-v32:32-v48:64-" diff --git a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h --- a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h +++ b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.h @@ -15,6 +15,7 @@ Target &getTheSPIRV32Target(); Target &getTheSPIRV64Target(); +Target &getTheSPIRVLogicalTarget(); } // namespace llvm diff --git a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp --- a/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp +++ b/llvm/lib/Target/SPIRV/TargetInfo/SPIRVTargetInfo.cpp @@ -19,10 +19,16 @@ static Target TheSPIRV64Target; return TheSPIRV64Target; } +Target &llvm::getTheSPIRVLogicalTarget() { + static Target TheSPIRVLogicalTarget; + return TheSPIRVLogicalTarget; +} extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeSPIRVTargetInfo() { RegisterTarget X(getTheSPIRV32Target(), "spirv32", "SPIR-V 32-bit", "SPIRV"); RegisterTarget Y(getTheSPIRV64Target(), "spirv64", "SPIR-V 64-bit", "SPIRV"); + RegisterTarget Z(getTheSPIRVLogicalTarget(), "spirv", + "SPIR-V Logical", "SPIRV"); } diff --git a/llvm/test/CodeGen/SPIRV/ExecutionMode-ConflictingAttributes.ll b/llvm/test/CodeGen/SPIRV/ExecutionMode-ConflictingAttributes.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/ExecutionMode-ConflictingAttributes.ll @@ -0,0 +1,11 @@ +; RUN: not --crash llc -O0 -mtriple=spirv-unknown-unknown %s -o - 2>&1 | FileCheck %s + +; CHECK: LLVM ERROR: Invalid IR: 'reqd_work_group_size' and 'hlsl.numthreads' cannot be present together. + +define void @main() #1 !reqd_work_group_size !3 { +entry: + ret void +} + +attributes #1 = { "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" } +!3 = !{i32 4, i32 8, i32 12} diff --git a/llvm/test/CodeGen/SPIRV/ExecutionMode-GLCompute.ll b/llvm/test/CodeGen/SPIRV/ExecutionMode-GLCompute.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/ExecutionMode-GLCompute.ll @@ -0,0 +1,11 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +; CHECK-DAG: OpEntryPoint GLCompute %[[#entry:]] "main" +; CHECK-DAG: OpExecutionMode %[[#entry]] LocalSize 4 8 16 + +define void @main() #1 { +entry: + ret void +} + +attributes #1 = { "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" } diff --git a/llvm/test/CodeGen/SPIRV/capability-Shader.ll b/llvm/test/CodeGen/SPIRV/capability-Shader.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/capability-Shader.ll @@ -0,0 +1,12 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +; CHECK-DAG: OpCapability Shader +;; Ensure no other capability is listed. +; CHECK-NOT: OpCapability + +define void @main() #1 { +entry: + ret void +} + +attributes #1 = { "hlsl.numthreads"="4,8,16" "hlsl.shader"="compute" } diff --git a/llvm/test/CodeGen/SPIRV/empty-logical.ll b/llvm/test/CodeGen/SPIRV/empty-logical.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/empty-logical.ll @@ -0,0 +1,9 @@ +; RUN: llc -O0 -mtriple=spirv-unknown-unknown %s -o - | FileCheck %s + +;; Ensure the required Capabilities are listed. +; CHECK-DAG: OpCapability Shader +; CHECK-DAG: OpCapability Linkage + +;; Ensure one, and only one, OpMemoryModel is defined. +; CHECK: OpMemoryModel Logical GLSL450 +; CHECK-NOT: OpMemoryModel