Index: llvm/lib/Target/SPIRV/CMakeLists.txt =================================================================== --- llvm/lib/Target/SPIRV/CMakeLists.txt +++ llvm/lib/Target/SPIRV/CMakeLists.txt @@ -14,8 +14,10 @@ add_llvm_target(SPIRVCodeGen SPIRVAsmPrinter.cpp + SPIRVBlockLabeler.cpp SPIRVCallLowering.cpp SPIRVGlobalRegistry.cpp + SPIRVGlobalTypesAndRegNumPass.cpp SPIRVInstrInfo.cpp SPIRVInstructionSelector.cpp SPIRVISelLowering.cpp Index: llvm/lib/Target/SPIRV/SPIRV.h =================================================================== --- llvm/lib/Target/SPIRV/SPIRV.h +++ llvm/lib/Target/SPIRV/SPIRV.h @@ -19,10 +19,16 @@ class SPIRVSubtarget; class InstructionSelector; +FunctionPass *createSPIRVBlockLabelerPass(); +ModulePass *createSPIRVGlobalTypesAndRegNumPass(); + InstructionSelector * createSPIRVInstructionSelector(const SPIRVTargetMachine &TM, const SPIRVSubtarget &Subtarget, const SPIRVRegisterBankInfo &RBI); + +void initializeSPIRVBlockLabelerPass(PassRegistry &); +void initializeSPIRVGlobalTypesAndRegNumPass(PassRegistry &); } // namespace llvm #endif // LLVM_LIB_TARGET_SPIRV_SPIRV_H Index: llvm/lib/Target/SPIRV/SPIRVBlockLabeler.cpp =================================================================== --- /dev/null +++ llvm/lib/Target/SPIRV/SPIRVBlockLabeler.cpp @@ -0,0 +1,188 @@ +//===-- SPIRVBlockLabeler.cpp - SPIR-V Block Labeling Pass Impl -*- 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 +// +//===----------------------------------------------------------------------===// +// +// Implementation of SPIRVBlockLabeler, which ensures ensures all basic blocks +// start with an OpLabel, and ends with a suitable terminator (OpBranch is +// inserted if necessary). +// +// All MBB literals in OpBranchConditional, OpBranch, and OpPhi, are also fixed +// to use the virtual registers defined by OpLabel. +// +//===----------------------------------------------------------------------===// + +#include "SPIRV.h" +#include "SPIRVInstrInfo.h" +#include "SPIRVRegisterInfo.h" +#include "SPIRVUtils.h" +#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h" +#include "llvm/CodeGen/MachineFunctionPass.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/IR/LegacyPassManager.h" +#include "llvm/Pass.h" + +using namespace llvm; +using namespace SPIRV; + +#define DEBUG_TYPE "spirv-block-label" + +namespace { +class SPIRVBlockLabeler : public MachineFunctionPass { +public: + static char ID; + SPIRVBlockLabeler() : MachineFunctionPass(ID) { + initializeSPIRVBlockLabelerPass(*PassRegistry::getPassRegistry()); + } + // Add OpLabels and terminators. Fix any instructions with MBB references + bool runOnMachineFunction(MachineFunction &MF) override; +}; +} // namespace + +// Initialize MIRBuilder with the instruction position for an OpLabel within +// MBB. At this stage, all hoistable global instrs will still be function-local, +// so we must skip past them plus the initial OpFunction and its parameters. +static void setLabelPos(MachineBasicBlock &MBB, MachineIRBuilder &MIRBuilder) { + const auto TII = static_cast(&MIRBuilder.getTII()); + MIRBuilder.setMBB(MBB); + for (auto &I : MBB) { + unsigned o = I.getOpcode(); + if (o == OpFunction || o == OpFunctionParameter || TII->isHeaderInstr(I)) { + continue; + } + MIRBuilder.setInstr(I); + break; + } +} + +// Build an OpLabel at the start of the block (ignoring hoistable instrs) +static Register buildLabel(MachineBasicBlock &MBB, + MachineIRBuilder &MIRBuilder) { + setLabelPos(MBB, MIRBuilder); + auto LabelID = MIRBuilder.getMRI()->createVirtualRegister(&IDRegClass); + MIRBuilder.buildInstr(OpLabel).addDef(LabelID); + buildOpName(LabelID, MBB.getName(), MIRBuilder); + return LabelID; +} + +// Remove one instruction, and insert another in its place +static void replaceInstr(MachineInstr *ToReplace, MachineInstrBuilder &NewInstr, + MachineIRBuilder &MIRBuilder) { + MIRBuilder.setMBB(*ToReplace->getParent()); + MIRBuilder.setInstr(*ToReplace); + MIRBuilder.insertInstr(NewInstr); + ToReplace->removeFromParent(); +} + +// A unique identifier for MBBs for use as a map key +using MBB_ID = int; + +static Register getLabelIDForMBB(const std::map &BBNumMap, + MBB_ID BBNum) { + auto f = BBNumMap.find(BBNum); + if (f != BBNumMap.end()) { + return f->second; + } else { + errs() << "MBB Num: = " << BBNum << "\n"; + llvm_unreachable("MBB number not in MBB to label map."); + } +} + +static MBB_ID getMBBID(const MachineBasicBlock &MBB) { + return MBB.getNumber(); // Use MBB number as a unique identifier +} + +static MBB_ID getMBBID(const MachineOperand &Op) { + return getMBBID(*Op.getMBB()); +} + +// Add OpLabels and terminators. Fix any instructions with MBB references +bool SPIRVBlockLabeler::runOnMachineFunction(MachineFunction &MF) { + MachineIRBuilder MIRBuilder; + MIRBuilder.setMF(MF); + + std::map BBNumToLabelMap; + SmallVector Branches, CondBranches, Phis; + + for (MachineBasicBlock &MBB : MF) { + // Add the missing OpLabel in the right place + auto LabelID = buildLabel(MBB, MIRBuilder); + BBNumToLabelMap.insert({getMBBID(MBB), LabelID}); + // Record all instructions that need to refer to a MBB label ID + for (MachineInstr &MI : MBB) { + if (MI.getOpcode() == OpBranch) { + Branches.push_back(&MI); + } else if (MI.getOpcode() == OpBranchConditional) { + // If previous optimizations generated conditionals with implicit + // fallthrough, add the missing explicit "else" to it valid SPIR-V + if (MI.getNumOperands() < 3) { + MI.addOperand(MachineOperand::CreateMBB(MBB.getNextNode())); + } + CondBranches.push_back(&MI); + } else if (MI.getOpcode() == OpPhi) { + Phis.push_back(&MI); + } + } + + // Add an unconditional branch if the block has no explicit terminator + // and is not the last one. + if (!MBB.getLastNonDebugInstr()->isTerminator() && MBB.getNextNode()) { + MIRBuilder.setMBB(MBB); // Insert at end of block + auto MIB = MIRBuilder.buildInstr(OpBranch).addMBB(MBB.getNextNode()); + Branches.push_back(MIB); + } + } + + // Replace MBB references with label IDs in OpBranch instructions + for (const auto &Branch : Branches) { + auto BBNum = getMBBID(Branch->getOperand(0)); + Register BBID = getLabelIDForMBB(BBNumToLabelMap, BBNum); + auto MIB = MIRBuilder.buildInstrNoInsert(OpBranch).addUse(BBID); + replaceInstr(Branch, MIB, MIRBuilder); + } + + // Replace MBB references with label IDs in OpBranchConditional instructions + for (const auto &Branch : CondBranches) { + auto BBNum0 = getMBBID(Branch->getOperand(1)); + auto BBNum1 = getMBBID(Branch->getOperand(2)); + auto MIB = MIRBuilder.buildInstrNoInsert(OpBranchConditional) + .addUse(Branch->getOperand(0).getReg()) + .addUse(getLabelIDForMBB(BBNumToLabelMap, BBNum0)) + .addUse(getLabelIDForMBB(BBNumToLabelMap, BBNum1)); + replaceInstr(Branch, MIB, MIRBuilder); + } + + // Replace MBB references with label IDs in OpPhi instructions + for (const auto &Phi : Phis) { + auto MIB = MIRBuilder.buildInstrNoInsert(OpPhi) + .addDef(Phi->getOperand(0).getReg()) + .addUse(Phi->getOperand(1).getReg()); + + // PHIs require def, type, and list of (val, MBB) pairs + const unsigned NumOps = Phi->getNumOperands(); + assert(((NumOps % 2) == 0) && "Require even num operands for PHI instrs"); + for (unsigned i = 2; i < NumOps; i += 2) { + MIB.addUse(Phi->getOperand(i).getReg()); + auto BBNum = getMBBID(Phi->getOperand(i + 1)); + MIB.addUse(getLabelIDForMBB(BBNumToLabelMap, BBNum)); + } + replaceInstr(Phi, MIB, MIRBuilder); + } + + // Add OpFunctionEnd at the end of the last MBB + MIRBuilder.setMBB(MF.back()); + MIRBuilder.buildInstr(SPIRV::OpFunctionEnd); + return true; +} + +INITIALIZE_PASS(SPIRVBlockLabeler, DEBUG_TYPE, "SPIRV label blocks", false, + false) + +char SPIRVBlockLabeler::ID = 0; + +FunctionPass *llvm::createSPIRVBlockLabelerPass() { + return new SPIRVBlockLabeler(); +} Index: llvm/lib/Target/SPIRV/SPIRVGlobalTypesAndRegNumPass.cpp =================================================================== --- /dev/null +++ llvm/lib/Target/SPIRV/SPIRVGlobalTypesAndRegNumPass.cpp @@ -0,0 +1,313 @@ +//===-- SPIRVGlobalTypesAndRegNum.cpp - Hoist Globals & Number VRegs - 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 pass hoists all the required function-local instructions to global +// scope, deduplicating them where necessary. +// +// Before this pass, all SPIR-V instructions were local scope, and registers +// were numbered function-locally. However, SPIR-V requires Type instructions, +// global variables, capabilities, annotations etc. to be in global scope and +// occur at the start of the file. This pass hoists these as necessary. +// +// This pass also re-numbers the registers globally, and patches up any +// references to previously local registers that were hoisted, or function IDs +// which require globally scoped registers. +// +// This pass breaks all notion of register def/use, and generated MachineInstrs +// that are technically invalid as a result. As such, it must be the last pass, +// and requires instruction verification to be disabled afterwards. +// +//===----------------------------------------------------------------------===// + +#include "SPIRV.h" +#include "SPIRVGlobalRegistry.h" +#include "SPIRVSubtarget.h" +#include "SPIRVUtils.h" +#include "TargetInfo/SPIRVTargetInfo.h" +#include "llvm/CodeGen/MachineModuleInfo.h" + +using namespace llvm; + +#define DEBUG_TYPE "spirv-global-types-vreg" + +namespace { +struct SPIRVGlobalTypesAndRegNum : public ModulePass { + static char ID; + + SPIRVGlobalTypesAndRegNum() : ModulePass(ID) { + initializeSPIRVGlobalTypesAndRegNumPass(*PassRegistry::getPassRegistry()); + } + +public: + // Perform passes such as hoisting global instructions and numbering vregs. + // See end of file for details + bool runOnModule(Module &M) override; + + // State that we need MachineModuleInfo to operate on MachineFunctions + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + } +}; +} // namespace + +// Basic block indices in the global meta-function representing sections of the +// SPIR-V module header that need filled in a specific order. +enum MetaBlockType { + MB_Capabilities, // All OpCapability instructions + MB_Extensions, // Optional OpExtension instructions + MB_ExtInstImports, // Any language extension imports via OpExtInstImport + MB_MemoryModel, // A single required OpMemoryModel instruction + MB_EntryPoints, // All OpEntryPoint instructions (if any) + MB_ExecutionModes, // All OpExecutionMode or OpExecutionModeId instrs + MB_DebugSourceAndStrings, // OpString, OpSource, OpSourceExtension etc. + MB_DebugNames, // All OpName and OpMemberMemberName intrs. + MB_DebugModuleProcessed, // All OpModuleProcessed instructions. + MB_Annotations, // OpDecorate, OpMemberDecorate etc. + MB_TypeConstVars, // OpTypeXXX, OpConstantXXX, and global OpVariables + MB_ExtFuncDecls, // OpFunction etc. to declare for external funcs + MB_TmpGlobalData, // Tmp data in global vars processing + NUM_META_BLOCKS // Total number of sections requiring basic blocks +}; + +// Set the builder's MBB to one of the sections from the MetaBlockType enum. +static void setMetaBlock(MachineIRBuilder &MetaBuilder, MetaBlockType block) { + const auto &MF = MetaBuilder.getMF(); + MetaBuilder.setMBB(*MF.getBlockNumbered(block)); +} + +// Macros to make it simpler to iterate over MachineFunctions within a module +// Defines MachineFunction *MF and unsigned MFIndex between BEGIN and END lines. + +#define BEGIN_FOR_MF_IN_MODULE(M, MMI) \ + { \ + unsigned MFIndex = 0; \ + for (Function & F : M) { \ + MachineFunction *MF = MMI.getMachineFunction(F); \ + if (MF) { + +#define BEGIN_FOR_MF_IN_MODULE_EXCEPT_FIRST(M, MMI) \ + { \ + unsigned MFIndex = 1; \ + for (auto F = (++M.begin()), E = M.end(); F != E; ++F) { \ + MachineFunction *MF = MMI.getMachineFunction(*F); \ + if (MF) { + +#define END_FOR_MF_IN_MODULE() \ + ++MFIndex; \ + } /* close if statement */ \ + } /* close for loop */ \ + } /* close outer block */ + +// Maps a local VReg id to the corresponding VReg id in the global meta-function +using LocalToGlobalRegTable = std::map; + +// Retrieve an unsigned int from an MDNode with a list of them as operands +static unsigned int getMetadataUInt(MDNode *MdNode, unsigned int OpIndex, + unsigned int DefaultVal = 0) { + if (MdNode && OpIndex < MdNode->getNumOperands()) { + const auto &Op = MdNode->getOperand(OpIndex); + return mdconst::extract(Op)->getZExtValue(); + } else { + return DefaultVal; + } +} + +// Add some initial header instructions such as OpSource and OpMemoryModel +// TODO Maybe move the environment-specific logic used here elsewhere. +static void addHeaderOps(Module &M, MachineIRBuilder &MIRBuilder, + const SPIRVSubtarget &ST) { + setMetaBlock(MIRBuilder, MB_MemoryModel); + unsigned PtrSize = ST.getPointerSize(); + + // Add OpMemoryModel + using namespace AddressingModel; + auto Addr = PtrSize == 32 ? Physical32 : PtrSize == 64 ? Physical64 : Logical; + auto Mem = MemoryModel::OpenCL; + MIRBuilder.buildInstr(SPIRV::OpMemoryModel).addImm(Addr).addImm(Mem); + // Get the OpenCL version number from metadata + unsigned OpenCLVersion = 0; + if (auto VerNode = M.getNamedMetadata("opencl.ocl.version")) { + // Construct version literal according to OpenCL 2.2 environment spec. + auto VersionMD = VerNode->getOperand(0); + unsigned MajorNum = getMetadataUInt(VersionMD, 0, 2); + unsigned MinorNum = getMetadataUInt(VersionMD, 1); + unsigned RevNum = getMetadataUInt(VersionMD, 2); + OpenCLVersion = 0 | (MajorNum << 16) | (MinorNum << 8) | RevNum; + setMetaBlock(MIRBuilder, MB_DebugSourceAndStrings); + } + + // Build the OpSource + auto SrcLang = SourceLanguage::OpenCL_C; + MIRBuilder.buildInstr(SPIRV::OpSource).addImm(SrcLang).addImm(OpenCLVersion); +} + +// Create a new Function and MachineFunction for the given MachineIRBuilder to +// add meta-instructions to. It should have a series of empty basic blocks, one +// for each required SPIR-V module section, so that subsequent users of the +// MachineIRBuilder can hoist instructions into the right places easily. +static void initMetaBlockBuilder(Module &M, MachineModuleInfo &MMI, + MachineIRBuilder &MetaBuilder) { + + // Add an empty placeholder function - MachineInstrs need to exist somewhere. + auto VoidType = Type::getVoidTy(M.getContext()); + auto FType = FunctionType::get(VoidType, false); + auto Linkage = Function::LinkageTypes::InternalLinkage; + Function *F = Function::Create(FType, Linkage, "GlobalStuff"); + + // Add the function and a corresponding MachineFunction to the module + F->getBasicBlockList().push_back(BasicBlock::Create(M.getContext())); + M.getFunctionList().push_front(F); + + // Add the necessary basic blocks according to the MetaBlockTypes enum. + MachineFunction &MetaMF = MMI.getOrCreateMachineFunction(*F); + for (unsigned int i = 0; i < NUM_META_BLOCKS; ++i) { + MachineBasicBlock *MetaMBB = MetaMF.CreateMachineBasicBlock(); + MetaMF.push_back(MetaMBB); + } + + // Tell the builder about the meta function and an initial basic block. + MetaBuilder.setMF(MetaMF); + setMetaBlock(MetaBuilder, MB_TypeConstVars); +} + +// True when all the operands of an instruction are an exact match (after the +// given starting index). +static bool allOpsMatch(const MachineInstr &A, const MachineInstr &B, + unsigned int StartOpIndex = 0) { + const unsigned int NumAOps = A.getNumOperands(); + if (NumAOps == B.getNumOperands() && A.getNumDefs() == B.getNumDefs()) { + bool AllOpsMatch = true; + for (unsigned i = StartOpIndex; i < NumAOps && AllOpsMatch; ++i) { + AllOpsMatch = A.getOperand(i).isIdenticalTo(B.getOperand(i)); + } + if (AllOpsMatch) + return true; + } + return false; +} + +// We need to add dummy registers whenever we need to reference a VReg that +// might be beyond the number of virtual registers declared in the function so +// far. This stops addUse and setReg from crashing when we use the new index. +static void addDummyVRegsUpToIndex(unsigned Index, MachineRegisterInfo &MRI) { + while (Index >= MRI.getNumVirtRegs()) { + MRI.createVirtualRegister(&SPIRV::IDRegClass); + } +} + +// Create a copy of the given instruction in the specified basic block of the +// global metadata function. We assume global register numbering has already +// occurred by this point, so we can directly copy VReg arguments (with padding +// to avoid crashed). We can also directly compare VReg arguments directly when +// detecting duplicates, rather than having to use local-to-global alias tables. +static void hoistMetaInstrWithGlobalRegs(MachineInstr &MI, + MachineIRBuilder &MIRBuilder, + MetaBlockType MbType) { + + setMetaBlock(MIRBuilder, MbType); + auto &MBB = MIRBuilder.getMBB(); + for (const auto &I : MBB) { + if (allOpsMatch(MI, I)) + return; // Found a duplicate, so don't add it + } + + // No duplicates, so add it + auto &MetaMRI = MIRBuilder.getMF().getRegInfo(); + const unsigned NumOperands = MI.getNumOperands(); + auto MIB = MIRBuilder.buildInstr(MI.getOpcode()); + for (unsigned i = 0; i < NumOperands; ++i) { + MachineOperand Op = MI.getOperand(i); + if (Op.isImm()) { + MIB.addImm(Op.getImm()); + } else if (Op.isReg()) { + // Add dummy regs to stop addUse crashing if Reg > max regs in func so far + addDummyVRegsUpToIndex(Op.getReg().virtRegIndex(), MetaMRI); + MIB.addUse(Op.getReg()); + } else { + errs() << MI << "\n"; + llvm_unreachable("Unexpected operand type when copying spirv meta instr"); + } + } +} + +static void extractInstructionsWithGlobalRegsToMetablockForMBB( + MachineBasicBlock &MBB, const SPIRVInstrInfo *TII, + MachineIRBuilder &MIRBuilder) { + SmallVector ToRemove; + for (MachineInstr &MI : MBB) { + const unsigned OpCode = MI.getOpcode(); + if (OpCode == SPIRV::OpName || OpCode == SPIRV::OpMemberName) { + hoistMetaInstrWithGlobalRegs(MI, MIRBuilder, MB_DebugNames); + ToRemove.push_back(&MI); + } else if (OpCode == SPIRV::OpEntryPoint) { + hoistMetaInstrWithGlobalRegs(MI, MIRBuilder, MB_EntryPoints); + ToRemove.push_back(&MI); + } else if (TII->isDecorationInstr(MI)) { + hoistMetaInstrWithGlobalRegs(MI, MIRBuilder, MB_Annotations); + ToRemove.push_back(&MI); + } else if (TII->isConstantInstr(MI)) { + // Now OpSpecConstant*s are not in DT, but they need to be hoisted anyway. + hoistMetaInstrWithGlobalRegs(MI, MIRBuilder, MB_TypeConstVars); + ToRemove.push_back(&MI); + } + } + for (MachineInstr *MI : ToRemove) { + MI->removeFromParent(); + } +} + +// Some global instructions make reference to function-local ID vregs, so cannot +// be hoisted until these registers are globally numbered and can be correctly +// referenced from the instructions' new global context. +// +// This pass extracts and deduplicates these instructions assuming global VReg +// numbers rather than using function-local alias tables like before. +static void +extractInstructionsWithGlobalRegsToMetablock(Module &M, MachineModuleInfo &MMI, + MachineIRBuilder &MIRBuilder) { + const auto TII = static_cast(&MIRBuilder.getTII()); + setMetaBlock(MIRBuilder, MB_DebugNames); + BEGIN_FOR_MF_IN_MODULE_EXCEPT_FIRST(M, MMI) + for (MachineBasicBlock &MBB : *MF) + extractInstructionsWithGlobalRegsToMetablockForMBB(MBB, TII, MIRBuilder); + END_FOR_MF_IN_MODULE() + setMetaBlock(MIRBuilder, MB_TmpGlobalData); + auto &MBB = MIRBuilder.getMBB(); + extractInstructionsWithGlobalRegsToMetablockForMBB(MBB, TII, MIRBuilder); +} + +// Add a meta function containing all OpType, OpConstant etc. +// Extract all OpType, OpConst etc. into this meta block +// Number registers globally, including references to global OpType etc. +bool SPIRVGlobalTypesAndRegNum::runOnModule(Module &M) { + MachineModuleInfoWrapperPass &MMIWrapper = + getAnalysis(); + + MachineIRBuilder MIRBuilder; + initMetaBlockBuilder(M, MMIWrapper.getMMI(), MIRBuilder); + + const auto &MetaMF = MIRBuilder.getMF(); + const auto &ST = static_cast(MetaMF.getSubtarget()); + + addHeaderOps(M, MIRBuilder, ST); + + // Extract instructions like OpName, OpEntryPoint, OpDecorate etc. + // which all rely on globally numbered registers, which they forward-reference + extractInstructionsWithGlobalRegsToMetablock(M, MMIWrapper.getMMI(), + MIRBuilder); + return false; +} + +INITIALIZE_PASS(SPIRVGlobalTypesAndRegNum, DEBUG_TYPE, + "SPIRV hoist OpType etc. & number VRegs globally", false, false) + +char SPIRVGlobalTypesAndRegNum::ID = 0; + +ModulePass *llvm::createSPIRVGlobalTypesAndRegNumPass() { + return new SPIRVGlobalTypesAndRegNum(); +} Index: llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp =================================================================== --- llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp +++ llvm/lib/Target/SPIRV/SPIRVTargetMachine.cpp @@ -45,6 +45,8 @@ PassRegistry &PR = *PassRegistry::getPassRegistry(); initializeGlobalISel(PR); + initializeSPIRVBlockLabelerPass(PR); + initializeSPIRVGlobalTypesAndRegNumPass(PR); } // DataLayout: little or big endian @@ -109,6 +111,8 @@ void addOptimizedRegAlloc() override {} void addPostRegAlloc() override; + + void addPreEmitPass2() override; }; } // namespace @@ -150,6 +154,16 @@ void SPIRVPassConfig::addISelPrepare() { TargetPassConfig::addISelPrepare(); } +// Add custom passes right before emitting asm/obj files. +void SPIRVPassConfig::addPreEmitPass2() { + // Insert missing block labels and terminators. Fix instrs with MBB references + addPass(createSPIRVBlockLabelerPass()); + + // Hoist all global instructions, and number VRegs globally. + // We disable verification after this, as global VRegs are invalid in MIR + addPass(createSPIRVGlobalTypesAndRegNumPass()); +} + bool SPIRVPassConfig::addIRTranslator() { addPass(new IRTranslator(getOptLevel())); return false; Index: llvm/test/CodeGen/SPIRV/function/trivial-function-definition.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/function/trivial-function-definition.ll @@ -0,0 +1,27 @@ +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +; Debug info: +; CHECK: OpName [[FOO:%.+]] "foo" + +; Types: +; CHECK: [[VOID:%.+]] = OpTypeVoid +; CHECK: [[FN:%.+]] = OpTypeFunction [[VOID]] + +; Functions: +; CHECK: [[FOO]] = OpFunction [[VOID]] None [[FN]] +; CHECK-NOT: OpFunctionParameter +; NOTE: In 2.4, it isn't explicitly written that a function always has a least +; one block. In fact, 2.4.11 seems to imply that there are at least two +; blocks in functions with a body, but that doesn't make much sense. +; However, in order to distinguish between function declaration and +; definition, a function needs at least one block, hence why this test +; expects one OpLabel + OpReturn. +; CHECK: OpLabel +; CHECK: OpReturn +; CHECK-NOT: OpLabel +; CHECK: OpFunctionEnd +define void @foo() { + ret void +} Index: llvm/test/CodeGen/SPIRV/function/trivial-function-with-attributes.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/function/trivial-function-with-attributes.ll @@ -0,0 +1,74 @@ +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +; FIXME: Are there any attributes that would make the IR invalid for SPIR-V? + +; Names: +; CHECK-DAG: OpName [[FN1:%.+]] "fn1" +; CHECK-DAG: OpName [[FN2:%.+]] "fn2" +; CHECK-DAG: OpName [[FN3:%.+]] "fn3" +; CHECK-DAG: OpName [[FN4:%.+]] "fn4" +; CHECK-DAG: OpName [[FN5:%.+]] "fn5" +; CHECK-DAG: OpName [[FN6:%.+]] "fn6" +; CHECK-DAG: OpName [[FN7:%.+]] "fn7" + +; CHECK-NOT: DAG-FENCE + +; Types: +; CHECK: [[VOID:%.+]] = OpTypeVoid +; CHECK: [[FN:%.+]] = OpTypeFunction [[VOID]] + + +; Functions: + +define void @fn1() noinline { + ret void +} +; CHECK: [[FN1]] = OpFunction [[VOID]] DontInline [[FN]] +; CHECK-NOT: OpFunctionParameter +; CHECK: OpFunctionEnd + + +attributes #0 = { noinline } +define void @fn2() #0 { + ret void +} +; CHECK: [[FN2]] = OpFunction [[VOID]] DontInline [[FN]] +; CHECK: OpFunctionEnd + + +define void @fn3() alwaysinline { + ret void +} +; CHECK: [[FN3]] = OpFunction [[VOID]] Inline [[FN]] +; CHECK: OpFunctionEnd + + +; NOTE: inlinehint is not an actual requirement. +define void @fn4() inlinehint { + ret void +} +; CHECK: [[FN4]] = OpFunction [[VOID]] None [[FN]] +; CHECK: OpFunctionEnd + + +define void @fn5() readnone { + ret void +} +; CHECK: [[FN5]] = OpFunction [[VOID]] Pure [[FN]] +; CHECK: OpFunctionEnd + + +define void @fn6() readonly { + ret void +} +; CHECK: [[FN6]] = OpFunction [[VOID]] Const [[FN]] +; CHECK: OpFunctionEnd + + +define void @fn7() alwaysinline readnone { + ret void +} +; CHECK: [[FN7]] = OpFunction [[VOID]] Inline|Pure [[FN]] +; CHECK: OpFunctionEnd Index: llvm/test/CodeGen/SPIRV/lit.local.cfg =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/lit.local.cfg @@ -0,0 +1,2 @@ +if not 'SPIRV' in config.root.targets: + config.unsupported = True Index: llvm/test/CodeGen/SPIRV/metadata-opencl12.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/metadata-opencl12.ll @@ -0,0 +1,9 @@ +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +!opencl.ocl.version = !{!0} +!0 = !{i32 1, i32 2} + +; We assume the SPIR-V 2.2 environment spec's version format: 0|Maj|Min|Rev| +; CHECK: OpSource OpenCL_C 66048 Index: llvm/test/CodeGen/SPIRV/metadata-opencl20.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/metadata-opencl20.ll @@ -0,0 +1,9 @@ +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +!opencl.ocl.version = !{!0} +!0 = !{i32 2, i32 0} + +; We assume the SPIR-V 2.2 environment spec's version format: 0|Maj|Min|Rev| +; CHECK: OpSource OpenCL_C 131072 Index: llvm/test/CodeGen/SPIRV/metadata-opencl22.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/metadata-opencl22.ll @@ -0,0 +1,9 @@ +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s + +target triple = "spirv32-unknown-unknown" + +!opencl.ocl.version = !{!0} +!0 = !{i32 2, i32 2} + +; We assume the SPIR-V 2.2 environment spec's version format: 0|Maj|Min|Rev| +; CHECK: OpSource OpenCL_C 131584 Index: llvm/test/CodeGen/SPIRV/optnone.ll =================================================================== --- /dev/null +++ llvm/test/CodeGen/SPIRV/optnone.ll @@ -0,0 +1,38 @@ +; Check that optnone is correctly ignored when extension is not enabled +; RUN: llc -O0 -global-isel %s -o - | FileCheck %s --check-prefix=CHECK-SPIRV-NO-EXT + +; Per SPIR-V spec: +; FunctionControlDontInlineMask = 0x2 (2) +; CHECK-SPIRV-NO-EXT: %{{[0-9]+}} = OpFunction %{{[0-9]+}} DontInline + +target datalayout = "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024" +target triple = "spirv32-unknown-unknown" + +; Function Attrs: nounwind optnone noinline +define spir_func void @_Z3foov() #0 { +entry: + ret void +} + +attributes #0 = { nounwind optnone noinline "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "min-legal-vector-width"="0" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" } + +!llvm.module.flags = !{!0} +!opencl.spir.version = !{!1} +!spirv.Source = !{!2} +!llvm.ident = !{!3} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{i32 1, i32 2} +!2 = !{i32 4, i32 100000} +!3 = !{!"clang version 9.0.0"} +!4 = !{} +!5 = !{!6, !6, i64 0} +!6 = !{!"any pointer", !7, i64 0} +!7 = !{!"omnipotent char", !8, i64 0} +!8 = !{!"Simple C++ TBAA"} +!9 = !{!10, !11, i64 0} +!10 = !{!"_ZTS3bar", !11, i64 0, !7, i64 4, !12, i64 8} +!11 = !{!"int", !7, i64 0} +!12 = !{!"float", !7, i64 0} +!13 = !{!10, !7, i64 4} +!14 = !{!10, !12, i64 8}